Skip to content

Commit 92ca3b7

Browse files
ci+chore: version-alignment gate + resync package-lock to 5.6.0 (#182)
* ci+chore: add version-alignment gate + fix package-lock drift TS SDK's release workflow bumps package.json and regenerates src/version.ts at publish time but never commits the bump back. The repo stays in sync for those two files on merge (because the feature PR that ships a version bump updates them), but the package-lock.json was left at 5.5.0 from the prior release even as package.json moved to 5.6.0 — a real drift this PR caught and fixed before anyone noticed. Two things in this PR: 1. validate-version-alignment CI gate — a new workflow + script, patterned on the axonflow-enterprise script of the same name and the newly-landed equivalents in axonflow-sdk-go (PR #130) and axonflow-sdk-python (PR #153). Fails any PR or push to main that leaves package.json, src/version.ts, or package- lock.json out of sync with the most recent ## [X.Y.Z] section in CHANGELOG.md. 2. package-lock.json resync — regenerated via `npm install --package-lock-only` so it matches package.json at 5.6.0. This was real drift (lock said 5.5.0, everything else 5.6.0) that the new gate surfaced on its first local run. The invariant on main going forward: package.json::version == src/version.ts::VERSION == package-lock.json::version == first ## [X.Y.Z] section in CHANGELOG.md Release-prep PRs going forward must rename [Unreleased] → [X.Y.Z] AND bump the three manifest files (via `npm version X.Y.Z --no-git-tag-version` + `npm run stamp-version`) in the same commit, so the gate sees them as consistent throughout. Verified locally: - Pre-fix state (lock 5.5.0, everyone else 5.6.0) → gate fires with a clean 'expected 5.6.0' diff on package-lock.json and the correct remediation command (`npm version`) - Post-fix state (all at 5.6.0) → gate passes * fix(script): handle grep-no-match without silently aborting Under 'set -euo pipefail', the LATEST_VERSION extraction would abort the whole script before reaching the friendly '-z' error check when CHANGELOG has no released section (e.g. fresh repo, or someone deleted the last section). The grep's non-zero exit failed the command substitution, which set -e treated as a script-wide abort. Wrapped the grep in { ... || true; } so the -z branch fires with the intended error message. Same fix now live in the Go arm (PR #130) and Python arm (PR #153). Verified by running the exact pipeline pattern against a no-match input outside the script: reaches LATEST=[] and the -z branch, no silent abort. * fix(script): deep-check package-lock.json — both top-level and packages[""].version Review finding: the script comment claimed to check both the top-level package-lock.json.version AND the root-package entry packages[""].version, but the implementation only read the top-level one. That left a real silent-drift path — a PR could keep top-level aligned while the root-package entry stayed stale and the gate would still pass. npm keeps both fields synchronized through normal flows (npm install, npm version), but manual edits, partial merges, and tool quirks can desync them. Either field being stale breaks the npm publish / npm ci invariants. Fix: validate both fields independently. Error message points at which specific field drifted so the remediation is unambiguous. Verified locally: - Baseline (all at 5.6.0) → PASS - packages[""].version manually set to 5.5.0 while top-level stays 5.6.0 → gate fires cleanly on 'packages[""].version is "5.5.0", expected "5.6.0"' with the correct field named - Post-revert (npm install --package-lock-only) → PASS again
1 parent 65e1aa8 commit 92ca3b7

3 files changed

Lines changed: 162 additions & 2 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
#!/usr/bin/env bash
2+
# Validates that the TypeScript SDK's version declarations match the
3+
# most recent released section of CHANGELOG.md. Patterned on the same
4+
# script in axonflow-enterprise, axonflow-sdk-go, and axonflow-sdk-python.
5+
#
6+
# Why: the release workflow runs `npm version` / sed-rewrites package.json
7+
# and regenerates src/version.ts at publish time but never commits the
8+
# bump back to main, so the repo version silently lags the registry
9+
# version between releases. This gate enforces the invariant on every PR:
10+
#
11+
# package.json::version
12+
# == src/version.ts::VERSION
13+
# == most recent `## [X.Y.Z]` section in CHANGELOG.md
14+
#
15+
# When it's time to release, a single release-prep PR renames
16+
# [Unreleased] → [X.Y.Z] - DATE AND bumps both manifest files in the
17+
# same commit (regenerated via `npm run stamp-version` + `npm install`)
18+
# so this gate always sees them together.
19+
#
20+
# Run locally:
21+
# ./.github/scripts/validate-version-alignment.sh
22+
23+
set -euo pipefail
24+
25+
ERRORS=0
26+
27+
# Latest RELEASED version = first `## [x.y.z]` line that isn't
28+
# [Unreleased] (which starts with a letter, not a digit).
29+
#
30+
# `{ grep || true; }` is deliberate: under `set -euo pipefail`, a
31+
# failing grep (no match) aborts the whole command substitution
32+
# before we reach the -z check, killing the script silently. The
33+
# wrapper lets the `-z` check produce the real user-facing error.
34+
LATEST_VERSION=$({ grep -m1 -E '^## \[[0-9]' CHANGELOG.md || true; } | sed 's/## \[\(.*\)\].*/\1/' | sed 's/^v//')
35+
36+
if [ -z "${LATEST_VERSION:-}" ]; then
37+
echo "❌ Could not extract a released version (## [X.Y.Z]) from CHANGELOG.md"
38+
exit 1
39+
fi
40+
41+
echo "📋 Latest CHANGELOG version: $LATEST_VERSION"
42+
echo ""
43+
44+
# Check package.json::version (via python — available on all CI runners)
45+
echo "📦 Checking package.json..."
46+
PACKAGE_VER=$(python3 -c "import json,sys; print(json.load(open('package.json')).get('version',''))" || true)
47+
if [ -z "${PACKAGE_VER:-}" ]; then
48+
echo " ❌ package.json — could not read version"
49+
ERRORS=$((ERRORS + 1))
50+
elif [ "$PACKAGE_VER" != "$LATEST_VERSION" ]; then
51+
echo " ❌ package.json — version is \"$PACKAGE_VER\", expected \"$LATEST_VERSION\""
52+
ERRORS=$((ERRORS + 1))
53+
else
54+
echo " ✅ package.json — $PACKAGE_VER"
55+
fi
56+
57+
# Check src/version.ts::VERSION
58+
echo "🔧 Checking src/version.ts..."
59+
VERSION_TS=$(grep -m1 -E "export const VERSION = ['\"]" src/version.ts | sed "s/.*['\"]\(.*\)['\"].*/\1/" || true)
60+
if [ -z "${VERSION_TS:-}" ]; then
61+
echo " ❌ src/version.ts — could not read VERSION"
62+
ERRORS=$((ERRORS + 1))
63+
elif [ "$VERSION_TS" != "$LATEST_VERSION" ]; then
64+
echo " ❌ src/version.ts — VERSION is \"$VERSION_TS\", expected \"$LATEST_VERSION\""
65+
ERRORS=$((ERRORS + 1))
66+
else
67+
echo " ✅ src/version.ts — $VERSION_TS"
68+
fi
69+
70+
# package-lock.json carries the version in two places that npm keeps
71+
# in sync: the top-level `version` AND the root-package entry
72+
# `packages[""].version`. Both get rewritten by `npm install` from
73+
# package.json. Drift between them is rare but possible (manual edits,
74+
# partial merges, tool bugs), and either one being stale breaks
75+
# `npm publish` / `npm ci` invariants. Validate both, independently.
76+
echo "🔒 Checking package-lock.json..."
77+
LOCK_TOP_VER=$(python3 -c "import json; d=json.load(open('package-lock.json')); print(d.get('version',''))" || true)
78+
if [ -z "${LOCK_TOP_VER:-}" ]; then
79+
echo " ❌ package-lock.json — could not read top-level version"
80+
ERRORS=$((ERRORS + 1))
81+
elif [ "$LOCK_TOP_VER" != "$LATEST_VERSION" ]; then
82+
echo " ❌ package-lock.json — top-level version is \"$LOCK_TOP_VER\", expected \"$LATEST_VERSION\""
83+
ERRORS=$((ERRORS + 1))
84+
else
85+
echo " ✅ package-lock.json — top-level $LOCK_TOP_VER"
86+
fi
87+
88+
LOCK_ROOT_VER=$(python3 -c "import json; d=json.load(open('package-lock.json')); print(d.get('packages',{}).get('',{}).get('version',''))" || true)
89+
if [ -z "${LOCK_ROOT_VER:-}" ]; then
90+
echo " ❌ package-lock.json — could not read packages[\"\"].version (lockfile v2/v3 root entry)"
91+
ERRORS=$((ERRORS + 1))
92+
elif [ "$LOCK_ROOT_VER" != "$LATEST_VERSION" ]; then
93+
echo " ❌ package-lock.json — packages[\"\"].version is \"$LOCK_ROOT_VER\", expected \"$LATEST_VERSION\""
94+
ERRORS=$((ERRORS + 1))
95+
else
96+
echo " ✅ package-lock.json — packages[\"\"].version $LOCK_ROOT_VER"
97+
fi
98+
99+
echo ""
100+
101+
if [ "$ERRORS" -gt 0 ]; then
102+
echo "❌ Found $ERRORS version misalignment(s)."
103+
echo ""
104+
echo "Fix:"
105+
echo " 1. npm version $LATEST_VERSION --no-git-tag-version (bumps package.json + package-lock.json)"
106+
echo " 2. npm run stamp-version (regenerates src/version.ts)"
107+
echo "Or, if CHANGELOG is behind a tag you already pushed, add the"
108+
echo "missing '## [X.Y.Z] - YYYY-MM-DD' section."
109+
exit 1
110+
fi
111+
112+
echo "✅ All version constants match CHANGELOG v$LATEST_VERSION."
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Version Alignment Check
2+
3+
# Blocks merges that would leave package.json / src/version.ts /
4+
# package-lock.json out of sync with CHANGELOG.md's most recent
5+
# released section. The invariant on main:
6+
# package.json::version
7+
# == src/version.ts::VERSION
8+
# == package-lock.json::version
9+
# == first ## [X.Y.Z] section in CHANGELOG
10+
#
11+
# When it's time to ship a new release, a single release-prep PR
12+
# renames [Unreleased] → [X.Y.Z] - YYYY-MM-DD AND bumps the
13+
# manifests in the same commit (via npm version + npm run
14+
# stamp-version), so this gate always sees them together.
15+
#
16+
# See .github/scripts/validate-version-alignment.sh for the script.
17+
18+
on:
19+
pull_request:
20+
branches: [main]
21+
paths:
22+
- 'CHANGELOG.md'
23+
- 'package.json'
24+
- 'package-lock.json'
25+
- 'src/version.ts'
26+
- '.github/scripts/validate-version-alignment.sh'
27+
- '.github/workflows/validate-version-alignment.yml'
28+
push:
29+
branches: [main]
30+
paths:
31+
- 'CHANGELOG.md'
32+
- 'package.json'
33+
- 'package-lock.json'
34+
- 'src/version.ts'
35+
- '.github/scripts/validate-version-alignment.sh'
36+
- '.github/workflows/validate-version-alignment.yml'
37+
38+
permissions:
39+
contents: read
40+
41+
jobs:
42+
validate-versions:
43+
name: Validate Version Alignment
44+
runs-on: ubuntu-latest
45+
steps:
46+
- uses: actions/checkout@v4
47+
- name: Check version alignment
48+
run: ./.github/scripts/validate-version-alignment.sh

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)