fix(release): validate independent module versions
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
# Whole-product release install from tagged module repositories.
|
# Whole-product release install from immutable, independently versioned module tags.
|
||||||
# Update GOVOPLAN_RELEASE_TAG together with pyproject/package versions when
|
# Only add a module after its referenced tag has been published.
|
||||||
# cutting a release.
|
|
||||||
../govoplan-core[server]
|
../govoplan-core[server]
|
||||||
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-tenancy.git@v0.1.8
|
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-tenancy.git@v0.1.8
|
||||||
govoplan-organizations @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-organizations.git@v0.1.8
|
govoplan-organizations @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-organizations.git@v0.1.8
|
||||||
@@ -16,9 +15,7 @@ govoplan-files @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-files.git@v0.
|
|||||||
govoplan-mail @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-mail.git@v0.1.8
|
govoplan-mail @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-mail.git@v0.1.8
|
||||||
govoplan-campaign @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-campaign.git@v0.1.8
|
govoplan-campaign @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-campaign.git@v0.1.8
|
||||||
govoplan-calendar @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-calendar.git@v0.1.8
|
govoplan-calendar @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-calendar.git@v0.1.8
|
||||||
govoplan-poll @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-poll.git@v0.1.8
|
|
||||||
govoplan-scheduling @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-scheduling.git@v0.1.8
|
govoplan-scheduling @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-scheduling.git@v0.1.8
|
||||||
govoplan-notifications @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-notifications.git@v0.1.8
|
govoplan-notifications @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-notifications.git@v0.1.8
|
||||||
govoplan-evaluation @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-evaluation.git@v0.1.8
|
|
||||||
govoplan-docs @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-docs.git@v0.1.8
|
govoplan-docs @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-docs.git@v0.1.8
|
||||||
govoplan-ops @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-ops.git@v0.1.8
|
govoplan-ops @ git+ssh://git@git.add-ideas.de/add-ideas/govoplan-ops.git@v0.1.8
|
||||||
|
|||||||
@@ -55,6 +55,13 @@ class ReleaseEntrypointGateTests(unittest.TestCase):
|
|||||||
self.assertIn("write_module_directory(", publisher)
|
self.assertIn("write_module_directory(", publisher)
|
||||||
self.assertNotIn("shutil.copytree(candidate_modules", publisher)
|
self.assertNotIn("shutil.copytree(candidate_modules", publisher)
|
||||||
|
|
||||||
|
def test_release_integration_honors_independent_module_versions(self) -> None:
|
||||||
|
script = (META_ROOT / "tools" / "checks" / "check-release-integration.sh").read_text()
|
||||||
|
|
||||||
|
self.assertNotIn('expected_tag = os.environ["RELEASE_TAG"]', script)
|
||||||
|
self.assertIn('dependency_version = tag_match.group(1)', script)
|
||||||
|
self.assertIn('repo_tag="$(release_tag_for_package "$repo")"', script)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -32,9 +32,8 @@ with (Path.cwd() / "pyproject.toml").open("rb") as handle:
|
|||||||
print(tomllib.load(handle)["project"]["version"])
|
print(tomllib.load(handle)["project"]["version"])
|
||||||
PY
|
PY
|
||||||
)"
|
)"
|
||||||
RELEASE_TAG="${GOVOPLAN_RELEASE_TAG:-v$RELEASE_VERSION}"
|
|
||||||
|
|
||||||
export RELEASE_TAG RELEASE_VERSION
|
export RELEASE_VERSION
|
||||||
export META_ROOT
|
export META_ROOT
|
||||||
export GOVOPLAN_CORE_SOURCE_ROOT="$ROOT"
|
export GOVOPLAN_CORE_SOURCE_ROOT="$ROOT"
|
||||||
export GOVOPLAN_MIGRATION_TRACK=release
|
export GOVOPLAN_MIGRATION_TRACK=release
|
||||||
@@ -91,6 +90,30 @@ install_cloned_webui_dependencies() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
release_tag_for_package() {
|
||||||
|
local package_name="$1"
|
||||||
|
"$PYTHON" - "$META_ROOT/requirements-release.txt" "$package_name" <<'PY'
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
requirements_path = Path(sys.argv[1])
|
||||||
|
package_name = sys.argv[2]
|
||||||
|
pattern = re.compile(
|
||||||
|
rf"^{re.escape(package_name)}\s+@\s+git\+ssh://.+?\.git@(v[0-9]+\.[0-9]+\.[0-9]+)$"
|
||||||
|
)
|
||||||
|
for line in requirements_path.read_text(encoding="utf-8").splitlines():
|
||||||
|
match = pattern.match(line.strip())
|
||||||
|
if match:
|
||||||
|
print(match.group(1))
|
||||||
|
raise SystemExit(0)
|
||||||
|
raise SystemExit(f"No immutable release reference found for {package_name}")
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
run_step "Validate release refs and installed package metadata"
|
run_step "Validate release refs and installed package metadata"
|
||||||
"$PYTHON" "$META_ROOT/tools/checks/check-version-alignment.py" --release-composition
|
"$PYTHON" "$META_ROOT/tools/checks/check-version-alignment.py" --release-composition
|
||||||
"$PYTHON" "$META_ROOT/tools/checks/check-contracts.py" --no-impact
|
"$PYTHON" "$META_ROOT/tools/checks/check-contracts.py" --no-impact
|
||||||
@@ -109,7 +132,6 @@ root = Path.cwd()
|
|||||||
release_requirements = Path(os.environ["META_ROOT"]) / "requirements-release.txt"
|
release_requirements = Path(os.environ["META_ROOT"]) / "requirements-release.txt"
|
||||||
release_webui = root / "webui" / "package.release.json"
|
release_webui = root / "webui" / "package.release.json"
|
||||||
expected_version = os.environ["RELEASE_VERSION"]
|
expected_version = os.environ["RELEASE_VERSION"]
|
||||||
expected_tag = os.environ["RELEASE_TAG"]
|
|
||||||
errors: list[str] = []
|
errors: list[str] = []
|
||||||
|
|
||||||
python_requirements: list[tuple[str, str, str]] = []
|
python_requirements: list[tuple[str, str, str]] = []
|
||||||
@@ -122,8 +144,6 @@ if not python_requirements:
|
|||||||
errors.append("No GovOPlaN git+ssh release requirements found.")
|
errors.append("No GovOPlaN git+ssh release requirements found.")
|
||||||
|
|
||||||
for package_name, repo_url, tag in python_requirements:
|
for package_name, repo_url, tag in python_requirements:
|
||||||
if tag != expected_tag:
|
|
||||||
errors.append(f"{package_name} release requirement uses {tag!r}, expected {expected_tag!r}.")
|
|
||||||
version = metadata.version(package_name)
|
version = metadata.version(package_name)
|
||||||
if version != tag.removeprefix("v"):
|
if version != tag.removeprefix("v"):
|
||||||
errors.append(f"{package_name} installed version {version!r} does not match {tag!r}.")
|
errors.append(f"{package_name} installed version {version!r} does not match {tag!r}.")
|
||||||
@@ -147,15 +167,18 @@ for package_name, spec in sorted(webui_dependencies.items()):
|
|||||||
if not package_name.startswith("@govoplan/"):
|
if not package_name.startswith("@govoplan/"):
|
||||||
continue
|
continue
|
||||||
tag = spec.rsplit("#", 1)[-1] if "#" in spec else ""
|
tag = spec.rsplit("#", 1)[-1] if "#" in spec else ""
|
||||||
if tag != expected_tag:
|
tag_match = re.fullmatch(r"v([0-9]+\.[0-9]+\.[0-9]+)", tag)
|
||||||
errors.append(f"{package_name} release dependency uses {tag!r}, expected {expected_tag}.")
|
if tag_match is None:
|
||||||
|
errors.append(f"{package_name} release dependency has invalid immutable tag {tag!r}.")
|
||||||
|
continue
|
||||||
|
dependency_version = tag_match.group(1)
|
||||||
package_json = root / "webui" / "node_modules" / package_name / "package.json"
|
package_json = root / "webui" / "node_modules" / package_name / "package.json"
|
||||||
if not package_json.exists():
|
if not package_json.exists():
|
||||||
errors.append(f"{package_name} is missing from release node_modules.")
|
errors.append(f"{package_name} is missing from release node_modules.")
|
||||||
continue
|
continue
|
||||||
installed = json.loads(package_json.read_text(encoding="utf-8"))
|
installed = json.loads(package_json.read_text(encoding="utf-8"))
|
||||||
if installed.get("version") != expected_version:
|
if installed.get("version") != dependency_version:
|
||||||
errors.append(f"{package_name} installed version {installed.get('version')!r}, expected {expected_version!r}.")
|
errors.append(f"{package_name} installed version {installed.get('version')!r}, expected {dependency_version!r} from {tag!r}.")
|
||||||
|
|
||||||
shared_peers = ("lucide-react", "react", "react-dom", "react-router-dom")
|
shared_peers = ("lucide-react", "react", "react-dom", "react-router-dom")
|
||||||
root_peers = release_package.get("peerDependencies", {})
|
root_peers = release_package.get("peerDependencies", {})
|
||||||
@@ -252,7 +275,8 @@ PY
|
|||||||
|
|
||||||
run_step "Clone release module test sources"
|
run_step "Clone release module test sources"
|
||||||
for repo in govoplan-mail govoplan-calendar govoplan-campaign; do
|
for repo in govoplan-mail govoplan-calendar govoplan-campaign; do
|
||||||
git clone --depth 1 --branch "$RELEASE_TAG" "git@git.add-ideas.de:add-ideas/${repo}.git" "$WORK_ROOT/$repo"
|
repo_tag="$(release_tag_for_package "$repo")"
|
||||||
|
git clone --depth 1 --branch "$repo_tag" "git@git.add-ideas.de:add-ideas/${repo}.git" "$WORK_ROOT/$repo"
|
||||||
done
|
done
|
||||||
|
|
||||||
run_step "Run core backend release tests"
|
run_step "Run core backend release tests"
|
||||||
|
|||||||
Reference in New Issue
Block a user