Release v0.1.15
This commit is contained in:
@@ -72,6 +72,20 @@ The source gate validates `pyproject.toml`, the module version declaration
|
|||||||
checks run only after the candidate tags and immutable WebUI lock have been
|
checks run only after the candidate tags and immutable WebUI lock have been
|
||||||
created locally.
|
created locally.
|
||||||
|
|
||||||
|
Release-lock regeneration resolves a fresh immutable lock from the reviewed
|
||||||
|
candidate manifests; it does not seed resolution from the previous release
|
||||||
|
lock. This prevents removed transitive packages and stale peer metadata from
|
||||||
|
blocking or contaminating the new release. Candidate resolution also uses an
|
||||||
|
isolated temporary npm cache, so a locally replaced tag cannot reuse metadata
|
||||||
|
from a failed, unpushed release attempt.
|
||||||
|
|
||||||
|
Modules that retain the same WebUI package identity in both a root publish
|
||||||
|
manifest and `webui/package.json` use the WebUI manifest as the canonical peer
|
||||||
|
contract. The coordinated release synchronizes `peerDependencies` and
|
||||||
|
`peerDependenciesMeta` into the publish manifest before creating the module
|
||||||
|
tag, then synchronizes each lockfile root from the final package metadata. A
|
||||||
|
distinct root package remains independent.
|
||||||
|
|
||||||
It builds one wheel and, where applicable, one npm tarball. The workflow records
|
It builds one wheel and, where applicable, one npm tarball. The workflow records
|
||||||
the source tag, source commit, filename, size, and SHA-256 in
|
the source tag, source commit, filename, size, and SHA-256 in
|
||||||
`package-artifacts.json` before publishing. Gitea rejects a second upload of the
|
`package-artifacts.json` before publishing. Gitea rejects a second upload of the
|
||||||
|
|||||||
@@ -4,82 +4,82 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan"
|
name = "govoplan"
|
||||||
version = "0.1.14"
|
version = "0.1.15"
|
||||||
description = "Developer convenience package for a versioned GovOPlaN composition"
|
description = "Developer convenience package for a versioned GovOPlaN composition"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
license = { text = "AGPL-3.0-or-later" }
|
license = { text = "AGPL-3.0-or-later" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core[server]==0.1.14",
|
"govoplan-core[server]==0.1.15",
|
||||||
"govoplan-tenancy==0.1.8",
|
"govoplan-tenancy==0.1.15",
|
||||||
"govoplan-organizations==0.1.8",
|
"govoplan-organizations==0.1.15",
|
||||||
"govoplan-identity==0.1.8",
|
"govoplan-identity==0.1.15",
|
||||||
"govoplan-idm==0.1.8",
|
"govoplan-idm==0.1.15",
|
||||||
"govoplan-access==0.1.8",
|
"govoplan-access==0.1.15",
|
||||||
"govoplan-admin==0.1.8",
|
"govoplan-admin==0.1.15",
|
||||||
"govoplan-policy==0.1.8",
|
"govoplan-policy==0.1.15",
|
||||||
"govoplan-audit==0.1.8",
|
"govoplan-audit==0.1.15",
|
||||||
"govoplan-dashboard==0.1.8",
|
"govoplan-dashboard==0.1.15",
|
||||||
"govoplan-files==0.1.8",
|
"govoplan-files==0.1.15",
|
||||||
"govoplan-mail==0.1.10",
|
"govoplan-mail==0.1.15",
|
||||||
"govoplan-campaign==0.1.11",
|
"govoplan-campaign==0.1.15",
|
||||||
"govoplan-calendar==0.1.8",
|
"govoplan-calendar==0.1.15",
|
||||||
"govoplan-docs==0.1.8",
|
"govoplan-docs==0.1.15",
|
||||||
"govoplan-ops==0.1.8",
|
"govoplan-ops==0.1.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
full = [
|
full = [
|
||||||
"govoplan-addresses==0.1.9",
|
"govoplan-addresses==0.1.15",
|
||||||
"govoplan-approvals==0.1.14",
|
"govoplan-approvals==0.1.15",
|
||||||
"govoplan-assets==0.1.8",
|
"govoplan-assets==0.1.15",
|
||||||
"govoplan-booking==0.1.8",
|
"govoplan-booking==0.1.15",
|
||||||
"govoplan-cases==0.1.8",
|
"govoplan-cases==0.1.15",
|
||||||
"govoplan-certificates==0.1.8",
|
"govoplan-certificates==0.1.15",
|
||||||
"govoplan-committee==0.1.8",
|
"govoplan-committee==0.1.15",
|
||||||
"govoplan-connectors==0.1.14",
|
"govoplan-connectors==0.1.15",
|
||||||
"govoplan-consultation==0.1.8",
|
"govoplan-consultation==0.1.15",
|
||||||
"govoplan-contracts==0.1.8",
|
"govoplan-contracts==0.1.15",
|
||||||
"govoplan-dataflow==0.1.14",
|
"govoplan-dataflow==0.1.15",
|
||||||
"govoplan-datasources==0.1.14",
|
"govoplan-datasources==0.1.15",
|
||||||
"govoplan-decisions==0.1.14",
|
"govoplan-decisions==0.1.15",
|
||||||
"govoplan-dist-lists==0.1.14",
|
"govoplan-dist-lists==0.1.15",
|
||||||
"govoplan-encryption==0.1.14",
|
"govoplan-encryption==0.1.15",
|
||||||
"govoplan-evaluation==0.1.8",
|
"govoplan-evaluation==0.1.15",
|
||||||
"govoplan-facilities==0.1.8",
|
"govoplan-facilities==0.1.15",
|
||||||
"govoplan-forms==0.1.14",
|
"govoplan-forms==0.1.15",
|
||||||
"govoplan-forms-runtime==0.1.14",
|
"govoplan-forms-runtime==0.1.15",
|
||||||
"govoplan-grants==0.1.8",
|
"govoplan-grants==0.1.15",
|
||||||
"govoplan-helpdesk==0.1.8",
|
"govoplan-helpdesk==0.1.15",
|
||||||
"govoplan-identity-trust==0.1.14",
|
"govoplan-identity-trust==0.1.15",
|
||||||
"govoplan-inspections==0.1.8",
|
"govoplan-inspections==0.1.15",
|
||||||
"govoplan-learning==0.1.8",
|
"govoplan-learning==0.1.15",
|
||||||
"govoplan-mandates==0.1.14",
|
"govoplan-mandates==0.1.15",
|
||||||
"govoplan-notifications==0.1.8",
|
"govoplan-notifications==0.1.15",
|
||||||
"govoplan-parties==0.1.14",
|
"govoplan-parties==0.1.15",
|
||||||
"govoplan-permits==0.1.8",
|
"govoplan-permits==0.1.15",
|
||||||
"govoplan-poll==0.1.11",
|
"govoplan-poll==0.1.15",
|
||||||
"govoplan-portal==0.1.8",
|
"govoplan-portal==0.1.15",
|
||||||
"govoplan-postbox==0.1.2",
|
"govoplan-postbox==0.1.15",
|
||||||
"govoplan-procurement==0.1.8",
|
"govoplan-procurement==0.1.15",
|
||||||
"govoplan-projects==0.1.14",
|
"govoplan-projects==0.1.15",
|
||||||
"govoplan-records==0.1.8",
|
"govoplan-records==0.1.15",
|
||||||
"govoplan-reporting==0.1.14",
|
"govoplan-reporting==0.1.15",
|
||||||
"govoplan-resources==0.1.8",
|
"govoplan-resources==0.1.15",
|
||||||
"govoplan-rest==0.1.7",
|
"govoplan-rest==0.1.15",
|
||||||
"govoplan-risk-compliance==0.1.8",
|
"govoplan-risk-compliance==0.1.15",
|
||||||
"govoplan-scheduling==0.1.11",
|
"govoplan-scheduling==0.1.15",
|
||||||
"govoplan-search==0.1.14",
|
"govoplan-search==0.1.15",
|
||||||
"govoplan-services==0.1.14",
|
"govoplan-services==0.1.15",
|
||||||
"govoplan-soap==0.1.7",
|
"govoplan-soap==0.1.15",
|
||||||
"govoplan-templates==0.1.14",
|
"govoplan-templates==0.1.15",
|
||||||
"govoplan-tickets==0.1.8",
|
"govoplan-tickets==0.1.15",
|
||||||
"govoplan-transparency==0.1.8",
|
"govoplan-transparency==0.1.15",
|
||||||
"govoplan-views==0.1.0",
|
"govoplan-views==0.1.15",
|
||||||
"govoplan-voting==0.1.14",
|
"govoplan-voting==0.1.15",
|
||||||
"govoplan-wiki==0.1.14",
|
"govoplan-wiki==0.1.15",
|
||||||
"govoplan-workflow==0.1.14",
|
"govoplan-workflow==0.1.15",
|
||||||
"govoplan-workflow-engine==0.1.14",
|
"govoplan-workflow-engine==0.1.15",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
|||||||
+15
-15
@@ -1,18 +1,18 @@
|
|||||||
# Whole-product release install from immutable, independently versioned module tags.
|
# Whole-product release install from immutable, independently versioned module tags.
|
||||||
# Only add a module after its referenced tag has been published.
|
# Only add a module after its referenced tag has been published.
|
||||||
../govoplan-core[server]
|
../govoplan-core[server]
|
||||||
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.8
|
govoplan-tenancy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-tenancy.git@v0.1.15
|
||||||
govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.8
|
govoplan-organizations @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-organizations.git@v0.1.15
|
||||||
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.8
|
govoplan-identity @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-identity.git@v0.1.15
|
||||||
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.8
|
govoplan-idm @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-idm.git@v0.1.15
|
||||||
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.8
|
govoplan-access @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-access.git@v0.1.15
|
||||||
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.8
|
govoplan-admin @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-admin.git@v0.1.15
|
||||||
govoplan-policy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git@v0.1.8
|
govoplan-policy @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-policy.git@v0.1.15
|
||||||
govoplan-audit @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git@v0.1.8
|
govoplan-audit @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-audit.git@v0.1.15
|
||||||
govoplan-dashboard @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git@v0.1.8
|
govoplan-dashboard @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-dashboard.git@v0.1.15
|
||||||
govoplan-files @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git@v0.1.8
|
govoplan-files @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-files.git@v0.1.15
|
||||||
govoplan-mail @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git@v0.1.10
|
govoplan-mail @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-mail.git@v0.1.15
|
||||||
govoplan-campaign @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git@v0.1.11
|
govoplan-campaign @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-campaign.git@v0.1.15
|
||||||
govoplan-calendar @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git@v0.1.8
|
govoplan-calendar @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-calendar.git@v0.1.15
|
||||||
govoplan-docs @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git@v0.1.8
|
govoplan-docs @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-docs.git@v0.1.15
|
||||||
govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git@v0.1.8
|
govoplan-ops @ git+ssh://git@git.add-ideas.de/GovOPlaN/govoplan-ops.git@v0.1.15
|
||||||
|
|||||||
@@ -77,8 +77,24 @@ class ReleaseEntrypointGateTests(unittest.TestCase):
|
|||||||
self.assertIn("^manifest\\s*=\\s*ModuleManifest", script)
|
self.assertIn("^manifest\\s*=\\s*ModuleManifest", script)
|
||||||
self.assertIn("could not update module version declaration", script)
|
self.assertIn("could not update module version declaration", script)
|
||||||
self.assertIn("update_package_init_versions", script)
|
self.assertIn("update_package_init_versions", script)
|
||||||
|
self.assertIn("synchronize-webui-package-metadata.py", script)
|
||||||
|
self.assertIn('"peerDependenciesMeta",', script)
|
||||||
|
self.assertLess(
|
||||||
|
script.index('synchronize-webui-package-metadata.py" --repo "$repo"'),
|
||||||
|
script.index('synchronize_lockfile_root "$package_path"', script.index('synchronize-webui-package-metadata.py" --repo "$repo"')),
|
||||||
|
)
|
||||||
self.assertNotIn("could not update ModuleManifest.version", script)
|
self.assertNotIn("could not update ModuleManifest.version", script)
|
||||||
|
|
||||||
|
def test_release_lock_refreshes_candidate_govoplan_metadata(self) -> None:
|
||||||
|
script = (META_ROOT / "tools" / "release" / "generate-release-lock.sh").read_text()
|
||||||
|
|
||||||
|
self.assertEqual(2, script.count('"npm_config_cache=$TMP_DIR/npm-cache"'))
|
||||||
|
self.assertNotIn(
|
||||||
|
'cp "$WEBUI/package-lock.release.json" "$TMP_DIR/package-lock.json"',
|
||||||
|
script,
|
||||||
|
)
|
||||||
|
self.assertIn('cp "$WEBUI/package.release.json" "$TMP_DIR/package.json"', script)
|
||||||
|
|
||||||
def test_source_catalog_generator_enforces_explicit_repo_versions(self) -> None:
|
def test_source_catalog_generator_enforces_explicit_repo_versions(self) -> None:
|
||||||
script = (META_ROOT / "tools" / "release" / "generate-release-catalog.py").read_text()
|
script = (META_ROOT / "tools" / "release" / "generate-release-catalog.py").read_text()
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
META_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
SCRIPT = META_ROOT / "tools" / "release" / "synchronize-webui-package-metadata.py"
|
||||||
|
|
||||||
|
|
||||||
|
class SynchronizeWebuiPackageMetadataTests(unittest.TestCase):
|
||||||
|
def test_copies_peer_contract_without_changing_publish_paths(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
repo = Path(directory)
|
||||||
|
(repo / "webui").mkdir()
|
||||||
|
(repo / "package.json").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"name": "@govoplan/example-webui",
|
||||||
|
"exports": {".": "./webui/src/index.ts"},
|
||||||
|
"peerDependencies": {"vite": "^6"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(repo / "webui" / "package.json").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"name": "@govoplan/example-webui",
|
||||||
|
"peerDependencies": {"vite": "^7"},
|
||||||
|
"peerDependenciesMeta": {"vite": {"optional": True}},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), "--repo", str(repo)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
package = json.loads((repo / "package.json").read_text())
|
||||||
|
self.assertEqual({"vite": "^7"}, package["peerDependencies"])
|
||||||
|
self.assertEqual({"vite": {"optional": True}}, package["peerDependenciesMeta"])
|
||||||
|
self.assertEqual({".": "./webui/src/index.ts"}, package["exports"])
|
||||||
|
|
||||||
|
def test_leaves_distinct_root_and_webui_packages_separate(self) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
repo = Path(directory)
|
||||||
|
(repo / "webui").mkdir()
|
||||||
|
(repo / "package.json").write_text(json.dumps({"name": "@govoplan/one"}))
|
||||||
|
(repo / "webui" / "package.json").write_text(json.dumps({"name": "@govoplan/two"}))
|
||||||
|
|
||||||
|
subprocess.run(
|
||||||
|
[sys.executable, str(SCRIPT), "--repo", str(repo)],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
root = json.loads((repo / "package.json").read_text())
|
||||||
|
self.assertEqual("@govoplan/one", root["name"])
|
||||||
|
self.assertNotIn("peerDependencies", root)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -94,9 +94,6 @@ cleanup() {
|
|||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
cp "$WEBUI/package.release.json" "$TMP_DIR/package.json"
|
cp "$WEBUI/package.release.json" "$TMP_DIR/package.json"
|
||||||
if [[ -f "$WEBUI/package-lock.release.json" ]]; then
|
|
||||||
cp "$WEBUI/package-lock.release.json" "$TMP_DIR/package-lock.json"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo "Generating release lockfile from $WEBUI/package.release.json"
|
echo "Generating release lockfile from $WEBUI/package.release.json"
|
||||||
echo "Temporary workspace: $TMP_DIR"
|
echo "Temporary workspace: $TMP_DIR"
|
||||||
@@ -120,7 +117,10 @@ GIT_ENV+=("GIT_CONFIG_COUNT=$git_config_count")
|
|||||||
|
|
||||||
(
|
(
|
||||||
cd "$TMP_DIR"
|
cd "$TMP_DIR"
|
||||||
"${GIT_ENV[@]}" PATH="$(dirname "$NPM_BIN"):$PATH" "$NPM_BIN" install --package-lock-only --ignore-scripts
|
"${GIT_ENV[@]}" \
|
||||||
|
"npm_config_cache=$TMP_DIR/npm-cache" \
|
||||||
|
PATH="$(dirname "$NPM_BIN"):$PATH" \
|
||||||
|
"$NPM_BIN" install --package-lock-only --ignore-scripts
|
||||||
mapfile -t GIT_PACKAGES < <(
|
mapfile -t GIT_PACKAGES < <(
|
||||||
PATH="$(dirname "$NODE_BIN"):$PATH" "$NODE_BIN" <<'NODE'
|
PATH="$(dirname "$NODE_BIN"):$PATH" "$NODE_BIN" <<'NODE'
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -136,7 +136,10 @@ NODE
|
|||||||
)
|
)
|
||||||
if [[ "${#GIT_PACKAGES[@]}" -gt 0 ]]; then
|
if [[ "${#GIT_PACKAGES[@]}" -gt 0 ]]; then
|
||||||
echo "Refreshing git package lock entries: ${GIT_PACKAGES[*]}"
|
echo "Refreshing git package lock entries: ${GIT_PACKAGES[*]}"
|
||||||
"${GIT_ENV[@]}" PATH="$(dirname "$NPM_BIN"):$PATH" "$NPM_BIN" update --package-lock-only --ignore-scripts "${GIT_PACKAGES[@]}"
|
"${GIT_ENV[@]}" \
|
||||||
|
"npm_config_cache=$TMP_DIR/npm-cache" \
|
||||||
|
PATH="$(dirname "$NPM_BIN"):$PATH" \
|
||||||
|
"$NPM_BIN" update --package-lock-only --ignore-scripts "${GIT_PACKAGES[@]}"
|
||||||
fi
|
fi
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -413,6 +413,11 @@ if project_name != "govoplan-core":
|
|||||||
peers["@govoplan/core-webui"] = f"^{new_version}"
|
peers["@govoplan/core-webui"] = f"^{new_version}"
|
||||||
path.write_text(json.dumps(data, indent=2) + "\n")
|
path.write_text(json.dumps(data, indent=2) + "\n")
|
||||||
PYCODE
|
PYCODE
|
||||||
|
done
|
||||||
|
|
||||||
|
"$PYTHON" "$META_ROOT/tools/release/synchronize-webui-package-metadata.py" --repo "$repo"
|
||||||
|
for package_path in "$repo/package.json" "$repo/webui/package.json"; do
|
||||||
|
[[ -f "$package_path" ]] || continue
|
||||||
synchronize_lockfile_root "$package_path" "${package_path%package.json}package-lock.json"
|
synchronize_lockfile_root "$package_path" "${package_path%package.json}package-lock.json"
|
||||||
done
|
done
|
||||||
|
|
||||||
@@ -461,7 +466,19 @@ if not isinstance(version, str) or not version:
|
|||||||
lock["version"] = version
|
lock["version"] = version
|
||||||
packages = lock.get("packages")
|
packages = lock.get("packages")
|
||||||
if isinstance(packages, dict) and isinstance(packages.get(""), dict):
|
if isinstance(packages, dict) and isinstance(packages.get(""), dict):
|
||||||
packages[""]["version"] = version
|
root = packages[""]
|
||||||
|
root["version"] = version
|
||||||
|
for group in (
|
||||||
|
"dependencies",
|
||||||
|
"devDependencies",
|
||||||
|
"optionalDependencies",
|
||||||
|
"peerDependencies",
|
||||||
|
"peerDependenciesMeta",
|
||||||
|
):
|
||||||
|
if group in package:
|
||||||
|
root[group] = package[group]
|
||||||
|
else:
|
||||||
|
root.pop(group, None)
|
||||||
lock_path.write_text(json.dumps(lock, indent=2) + "\n")
|
lock_path.write_text(json.dumps(lock, indent=2) + "\n")
|
||||||
PYCODE
|
PYCODE
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Synchronize duplicated publish and development WebUI package contracts."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
SYNCHRONIZED_KEYS = ("peerDependencies", "peerDependenciesMeta")
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description=__doc__)
|
||||||
|
parser.add_argument("--repo", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
root_path = args.repo / "package.json"
|
||||||
|
webui_path = args.repo / "webui" / "package.json"
|
||||||
|
if not root_path.exists() or not webui_path.exists():
|
||||||
|
return 0
|
||||||
|
|
||||||
|
root = _load(root_path)
|
||||||
|
webui = _load(webui_path)
|
||||||
|
root_name = root.get("name")
|
||||||
|
webui_name = webui.get("name")
|
||||||
|
if not isinstance(root_name, str) or root_name != webui_name:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
for key in SYNCHRONIZED_KEYS:
|
||||||
|
if key in webui:
|
||||||
|
value = webui[key]
|
||||||
|
if root.get(key) != value:
|
||||||
|
root[key] = value
|
||||||
|
changed = True
|
||||||
|
elif key in root:
|
||||||
|
del root[key]
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
root_path.write_text(json.dumps(root, indent=2) + "\n")
|
||||||
|
print(f"Synchronized WebUI peer metadata in {root_path}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def _load(path: Path) -> dict[str, object]:
|
||||||
|
payload = json.loads(path.read_text())
|
||||||
|
if not isinstance(payload, dict):
|
||||||
|
raise SystemExit(f"package metadata must be an object: {path}")
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user