fix(release): centralize pubspec version parsing
This commit is contained in:
@@ -32,10 +32,8 @@ jobs:
|
|||||||
- name: Extract current build number
|
- name: Extract current build number
|
||||||
id: get_build
|
id: get_build
|
||||||
run: |
|
run: |
|
||||||
# Extract current build number from pubspec.yaml
|
CURRENT_BUILD=$(python3 scripts/pubspec_version.py --build-number pubspec.yaml)
|
||||||
CURRENT_VERSION=$(grep '^version:' pubspec.yaml | sed 's/version: //')
|
NEW_BUILD=$((10#$CURRENT_BUILD + 1))
|
||||||
CURRENT_BUILD=$(echo "$CURRENT_VERSION" | cut -d'+' -f2)
|
|
||||||
NEW_BUILD=$((CURRENT_BUILD + 1))
|
|
||||||
|
|
||||||
echo "current_build=$CURRENT_BUILD" >> $GITHUB_OUTPUT
|
echo "current_build=$CURRENT_BUILD" >> $GITHUB_OUTPUT
|
||||||
echo "new_build=$NEW_BUILD" >> $GITHUB_OUTPUT
|
echo "new_build=$NEW_BUILD" >> $GITHUB_OUTPUT
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Read and validate the top-level version from a Flutter pubspec."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
_TOP_LEVEL_VERSION = re.compile(r"^version:[ \t]*(.*)$")
|
||||||
|
_VERSION = re.compile(
|
||||||
|
r"(?P<version>"
|
||||||
|
r"(?:0|[1-9][0-9]*)\."
|
||||||
|
r"(?:0|[1-9][0-9]*)\."
|
||||||
|
r"(?:0|[1-9][0-9]*)\+"
|
||||||
|
r"(?P<build>[0-9]+)"
|
||||||
|
r")"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_pubspec_version(contents: str) -> tuple[str, str]:
|
||||||
|
values = []
|
||||||
|
for line in contents.splitlines():
|
||||||
|
match = _TOP_LEVEL_VERSION.match(line)
|
||||||
|
if match:
|
||||||
|
value = re.sub(r"[ \t]+#.*$", "", match.group(1)).strip()
|
||||||
|
values.append(value)
|
||||||
|
|
||||||
|
if len(values) != 1:
|
||||||
|
raise ValueError(
|
||||||
|
f"expected one top-level 'version:' field, found {len(values)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
match = _VERSION.fullmatch(values[0])
|
||||||
|
if not match:
|
||||||
|
raise ValueError(
|
||||||
|
"top-level version must use major.minor.patch+numeric-build syntax; "
|
||||||
|
f"got {values[0]!r}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return match.group("version"), match.group("build")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="Read and validate the top-level pubspec version."
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"pubspec",
|
||||||
|
nargs="?",
|
||||||
|
type=Path,
|
||||||
|
default=Path("pubspec.yaml"),
|
||||||
|
help="path to pubspec.yaml (default: ./pubspec.yaml)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--build-number",
|
||||||
|
action="store_true",
|
||||||
|
help="print only the numeric build metadata",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
contents = args.pubspec.read_text(encoding="utf-8")
|
||||||
|
version, build = parse_pubspec_version(contents)
|
||||||
|
except (OSError, UnicodeError, ValueError) as error:
|
||||||
|
print(f"Error: {args.pubspec}: {error}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print(build if args.build_number else version)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
+1
-1
@@ -22,7 +22,7 @@ create_changelogs() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
local version_code
|
local version_code
|
||||||
version_code=$(grep -E 'version:' "$PROJECT_ROOT/pubspec.yaml" | sed -E 's/.*\+([0-9]+).*/\1/')
|
version_code=$(python3 "$SCRIPT_DIR/pubspec_version.py" --build-number "$PROJECT_ROOT/pubspec.yaml")
|
||||||
|
|
||||||
local prompt="Below is a changelog for a cross-platform Flutter app (iOS, Android, macOS, Linux, Windows). Return ONLY the entries relevant to the given platform. Keep the same format (section headers + bullet points). If a section has no relevant entries, omit it entirely. If an entry is not platform-specific, include it. You MUST stay under the character limit. Aggressively drop less important entries and consolidate similar ones to fit. Count your output characters before responding. Output nothing else."
|
local prompt="Below is a changelog for a cross-platform Flutter app (iOS, Android, macOS, Linux, Windows). Return ONLY the entries relevant to the given platform. Keep the same format (section headers + bullet points). If a section has no relevant entries, omit it entirely. If an entry is not platform-specific, include it. You MUST stay under the character limit. Aggressively drop less important entries and consolidate similar ones to fit. Count your output characters before responding. Output nothing else."
|
||||||
local notes
|
local notes
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SCRIPT_DIR = Path(__file__).resolve().parent
|
||||||
|
sys.path.insert(0, str(SCRIPT_DIR))
|
||||||
|
|
||||||
|
from pubspec_version import parse_pubspec_version
|
||||||
|
|
||||||
|
|
||||||
|
SCRIPT = SCRIPT_DIR / "pubspec_version.py"
|
||||||
|
|
||||||
|
|
||||||
|
class ParsePubspecVersionTest(unittest.TestCase):
|
||||||
|
def test_ignores_duplicate_nested_version_keys(self) -> None:
|
||||||
|
contents = """\
|
||||||
|
name: example
|
||||||
|
dependencies:
|
||||||
|
first:
|
||||||
|
version: 9.9.9+999
|
||||||
|
version: 2.8.0+119
|
||||||
|
metadata:
|
||||||
|
version: malformed
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.assertEqual(parse_pubspec_version(contents), ("2.8.0+119", "119"))
|
||||||
|
|
||||||
|
def test_rejects_malformed_versions(self) -> None:
|
||||||
|
for value in ("2.8", "2.8.0", "02.8.0+1", "2.8.0+build.1"):
|
||||||
|
with self.subTest(value=value), self.assertRaises(ValueError):
|
||||||
|
parse_pubspec_version(f"version: {value}\n")
|
||||||
|
|
||||||
|
def test_rejects_missing_top_level_version(self) -> None:
|
||||||
|
contents = """\
|
||||||
|
name: example
|
||||||
|
dependency:
|
||||||
|
version: 2.8.0+119
|
||||||
|
"""
|
||||||
|
|
||||||
|
with self.assertRaisesRegex(ValueError, "found 0"):
|
||||||
|
parse_pubspec_version(contents)
|
||||||
|
|
||||||
|
def test_accepts_numeric_build_metadata(self) -> None:
|
||||||
|
self.assertEqual(
|
||||||
|
parse_pubspec_version("version: 10.20.30+0007 # release build\n"),
|
||||||
|
("10.20.30+0007", "0007"),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_cli_prints_build_number(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
pubspec = Path(directory) / "pubspec.yaml"
|
||||||
|
pubspec.write_text("version: 1.2.3+456\n", encoding="utf-8")
|
||||||
|
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), "--build-number", str(pubspec)],
|
||||||
|
check=False,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(result.returncode, 0)
|
||||||
|
self.assertEqual(result.stdout, "456\n")
|
||||||
|
self.assertEqual(result.stderr, "")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user