Skip to content

Commit c2ea928

Browse files
rafmacalabaavsolatorio
authored andcommitted
test(release): assert release config stays in sync with package versions; document first-release drive
1 parent 0c2df00 commit c2ea928

2 files changed

Lines changed: 88 additions & 0 deletions

File tree

DEVELOPMENT.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,17 @@ Releases are driven by [release-please](https://github.com/googleapis/release-pl
168168
- **Cutting a release:** merge the Release PR. release-please creates the GitHub Release tagged `vX.Y.Z` and pushes the tag.
169169
- **Publication:** the tag triggers the PyPI publish workflow (`data360-mcp` only); the published release triggers the npm publish workflow (`@data360/tool-types`, `@data360/mcp-viz-core`, `@data360/mcp-ui`, `@data360/mcp-ui-angular`).
170170
- **Rollback:** pin consumers to any prior release—PyPI `pip install data360-mcp==X.Y.Z`, npm `@data360/mcp-ui@X.Y.Z` (see README).
171+
172+
### First release (post-merge validation)
173+
174+
The first time this pipeline runs after merge, treat it as an explicit smoke test. `release-please` needs at least one conventional commit after seeding before it opens the Release PR; a dedicated `docs:` commit is enough to trigger a patch bump.
175+
176+
1. Ensure the repo has a GitHub Actions environment named `pypi` wired to PyPI trusted publishing, and a `NPM_TOKEN` repo secret.
177+
2. Push a conventional commit to `dev` (e.g. `chore(release): cut initial release via release-please`). Wait for the Release PR to appear.
178+
3. Merge the Release PR. Confirm, in order:
179+
- git tag `vX.Y.Z` created and a GitHub Release published with an autogenerated changelog.
180+
- `python-publish.yml` ran: wheel built, smoke-tested (`import data360` prints a version), then published to PyPI (check https://pypi.org/project/data360-mcp/).
181+
- `npm-release.yml` ran: workspaces built and changed `@data360/*` packages published (check the npm registry per package).
182+
4. Rollback drill: `pip install data360-mcp==<previous-version>` succeeds using a version listed on the Releases page.
183+
184+
If trusted-publisher config or `NPM_TOKEN` are missing, the GitHub Release + tag still land (release-please cuts those in-repo); the PyPI/npm publish steps fail independently. Add the secrets and re-run the failed workflow from the Actions UI — both workflows are idempotent (PyPI re-publish of same version is a no-op in uv; npm skip-already-published).

tests/test_release_config_sync.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""Ensure release-please version manifest and config stay in sync with package versions."""
2+
3+
import json
4+
import re
5+
from pathlib import Path
6+
7+
import pytest
8+
9+
REPO_ROOT = Path(__file__).resolve().parents[1]
10+
11+
CONFIG_PATH = REPO_ROOT / ".github" / "release-please-config.json"
12+
MANIFEST_PATH = REPO_ROOT / ".github" / "release-please-manifest.json"
13+
NPM_WORKFLOW_PATH = REPO_ROOT / ".github" / "workflows" / "npm-release.yml"
14+
15+
_PYPROJECT_VERSION = re.compile(r'^version\s*=\s*"([^"]+)"', re.MULTILINE)
16+
17+
18+
def _npm_version(name: str) -> str:
19+
data = json.loads((REPO_ROOT / "packages" / name / "package.json").read_text(encoding="utf-8"))
20+
assert isinstance(data.get("name"), str)
21+
assert data["name"] == f"@data360/{name}", f"{name} package.json name mismatch"
22+
v = data.get("version")
23+
assert isinstance(v, str) and v.strip()
24+
return v.strip()
25+
26+
27+
def _py_version() -> str:
28+
text = (REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")
29+
match = _PYPROJECT_VERSION.search(text)
30+
assert match is not None, "missing version = ... in pyproject.toml"
31+
return match.group(1)
32+
33+
34+
@pytest.fixture
35+
def release_config() -> dict:
36+
return json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
37+
38+
39+
@pytest.fixture
40+
def release_manifest() -> dict:
41+
return json.loads(MANIFEST_PATH.read_text(encoding="utf-8"))
42+
43+
44+
def test_manifest_keys_match_config_packages(release_config: dict, release_manifest: dict) -> None:
45+
assert set(release_manifest) == set(release_config["packages"]), (
46+
"release-please-manifest keys must match config packages"
47+
)
48+
49+
50+
def test_manifest_versions_match_package_versions(release_manifest: dict) -> None:
51+
expected = {
52+
".": _py_version(),
53+
"packages/tool-types": _npm_version("tool-types"),
54+
"packages/mcp-viz-core": _npm_version("mcp-viz-core"),
55+
"packages/mcp-ui": _npm_version("mcp-ui"),
56+
"packages/mcp-ui-angular": _npm_version("mcp-ui-angular"),
57+
}
58+
for key, version in expected.items():
59+
assert release_manifest[key] == version, (
60+
f"manifest {key} version {release_manifest[key]!r} != package version {version!r}"
61+
)
62+
63+
64+
def test_npm_workflow_publishes_all_config_npm_packages(release_config: dict) -> None:
65+
npm_names = {
66+
pkg[len("packages/") :]
67+
for pkg, cfg in release_config["packages"].items()
68+
if cfg["release-type"] == "node"
69+
}
70+
workflow_text = NPM_WORKFLOW_PATH.read_text(encoding="utf-8")
71+
for name in sorted(npm_names):
72+
assert f"@data360/{name}" in workflow_text, (
73+
f"npm-release.yml must publish @data360/{name} (missing from in-scope list)"
74+
)

0 commit comments

Comments
 (0)