Skip to content

Commit 9eb73ac

Browse files
colbymchenryclaude
andauthored
feat(release): auto-sync package-lock.json on version drift + CLAUDE.md: don't bump version unless asked (colbymchenry#439)
Paired with the maintainer-preference clarification that Claude shouldn't proactively bump versions, and that version bumps are often made via the GitHub web UI (single-file edit to package.json only). **Workflow change** (`.github/workflows/release.yml`): Adds a 'Sync package-lock.json if version drifted' step BEFORE the existing `npm ci` step. It: 1. Reads the version field from both package.json and package-lock.json. 2. If they match, no-ops. 3. Otherwise runs `npm install --package-lock-only --ignore-scripts` which rewrites just the lock file's version fields (top-level + packages."") without touching node_modules — ~100ms locally. 4. Auto-commits + pushes the lock-file change back to main with `[skip ci]`, same pattern as the prepare-release auto-promote step. Effect: a maintainer can now edit ONLY package.json (e.g. via the GitHub web UI) and trigger the workflow. The previously-fatal `npm ci` mismatch is detected, fixed, and committed before the build proceeds. Editing both files locally still works — the sync step just no-ops in that case. Verified the `npm install --package-lock-only --ignore-scripts` mechanic against a synthetic drifted lock file locally: both the top-level `version` and `packages."".version` get rewritten to match package.json in one command. **CLAUDE.md change** (§ Release flow): Adds an explicit 'Claude does NOT bump the version unless explicitly asked' rule. Documents that the maintainer typically bumps package.json via the GitHub web UI (single-file edit). Explains the new sync step and lists the workflow's 5-step internals (sync lock → promote CHANGELOG → bundles → release → npm) for future Claude sessions to understand. 940/942 existing tests still pass; no new tests needed (the sync step is a thin wrapper around an npm CLI invocation; the verification was the local synthetic-drift smoke test in the commit-message above). Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
1 parent f6fabe9 commit 9eb73ac

2 files changed

Lines changed: 59 additions & 17 deletions

File tree

.github/workflows/release.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,43 @@ jobs:
3333
with:
3434
node-version: 22
3535
registry-url: https://registry.npmjs.org
36+
37+
- name: Sync package-lock.json if version drifted
38+
# When the maintainer bumps the version on package.json only — for
39+
# example via a GitHub web-UI edit — `npm ci` would refuse to run
40+
# with `EUSAGE: npm ci can only install packages when your
41+
# package.json and package-lock.json … are in sync`. This step
42+
# rewrites just the lock-file's version fields (top-level + the
43+
# `packages.""` entry) to match package.json, then auto-commits
44+
# and pushes the result so on-disk truth on `main` stays
45+
# consistent. Idempotent: if the lock file already matches, no
46+
# commit is made.
47+
run: |
48+
set -euo pipefail
49+
PKG_V=$(node -p "require('./package.json').version")
50+
LOCK_V=$(node -p "require('./package-lock.json').version")
51+
if [ "$PKG_V" = "$LOCK_V" ]; then
52+
echo "package-lock.json already at $PKG_V — nothing to sync."
53+
exit 0
54+
fi
55+
echo "Lock-file version drift: lock=$LOCK_V, package=$PKG_V. Syncing."
56+
# `--package-lock-only` rewrites only the lock file, doesn't
57+
# touch node_modules or actually install anything. Cheap.
58+
npm install --package-lock-only --ignore-scripts
59+
# Sanity: lockfile should now report the package version.
60+
NEW_LOCK_V=$(node -p "require('./package-lock.json').version")
61+
if [ "$NEW_LOCK_V" != "$PKG_V" ]; then
62+
echo "::error::lock-file still at $NEW_LOCK_V after sync attempt; expected $PKG_V"; exit 1
63+
fi
64+
if git diff --quiet -- package-lock.json; then
65+
echo "lock file unchanged after sync? bailing"; exit 1
66+
fi
67+
git config user.name "github-actions[bot]"
68+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
69+
git add package-lock.json
70+
git commit -m "release: sync package-lock.json to ${PKG_V}" -m "[skip ci] Auto-generated by Release workflow."
71+
git push origin "HEAD:${GITHUB_REF#refs/heads/}"
72+
3673
- run: npm ci
3774
- name: Ensure zip/unzip
3875
run: sudo apt-get update -qq && sudo apt-get install -y -qq zip unzip

CLAUDE.md

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -225,23 +225,28 @@ and publishes both the GitHub Release and the npm thin-installer
225225
Publishing manually is **wrong** now — a plain `npm publish` ships the root
226226
package (non-bundled), which breaks anyone on Node < 22.5.
227227

228-
The release cut itself is:
229-
230-
```bash
231-
# CHANGELOG already has entries under [Unreleased] — no manual moves needed.
232-
# Just bump the version:
233-
npm version --no-git-tag-version <X.Y.Z> # or edit package.json manually
234-
git add package.json package-lock.json
235-
git commit -m "release: X.Y.Z"
236-
git push
237-
```
238-
239-
Then trigger **Actions → Release → Run workflow** (on `main`). The workflow:
240-
241-
1. Runs `prepare-release.mjs <X.Y.Z>` → promotes `[Unreleased]``[X.Y.Z] - <today>` in `CHANGELOG.md`, appends the link reference, commits + pushes the move with `[skip ci]`.
242-
2. Builds every platform bundle on one runner, generates `SHA256SUMS`.
243-
3. Creates the GitHub Release with notes from the freshly-promoted `[X.Y.Z]` block.
244-
4. Publishes the npm shim + per-platform packages. Requires the `NPM_TOKEN` repo secret.
228+
**Claude does NOT bump the version unless explicitly asked.** The maintainer
229+
typically does it themselves — often by editing `package.json` directly via
230+
the GitHub web UI. Don't proactively commit a version bump as part of
231+
unrelated work, and don't propose one when summarizing a PR.
232+
233+
When the maintainer DOES bump the version, the only edit strictly required is
234+
to `package.json` — the workflow's "Sync package-lock.json" step detects a
235+
mismatch between `package.json` and `package-lock.json`, runs
236+
`npm install --package-lock-only --ignore-scripts` to rewrite the lock file's
237+
version fields (top-level + `packages.""`), and auto-commits + pushes the
238+
result back to `main` with `[skip ci]`. So a GitHub-web-UI single-file edit to
239+
`package.json` is enough to kick off a clean release. (If they edit both files
240+
locally, that's fine too — the sync step no-ops.)
241+
242+
Once `package.json` is at the target version on `main`, trigger
243+
**Actions → Release → Run workflow** (on `main`). The workflow:
244+
245+
1. Syncs `package-lock.json` to `package.json`'s version if they've drifted; commits + pushes that change.
246+
2. Runs `prepare-release.mjs <X.Y.Z>` → promotes `[Unreleased]``[X.Y.Z] - <today>` in `CHANGELOG.md`, appends the link reference, commits + pushes the move with `[skip ci]`.
247+
3. Builds every platform bundle on one runner, generates `SHA256SUMS`.
248+
4. Creates the GitHub Release with notes from the freshly-promoted `[X.Y.Z]` block.
249+
5. Publishes the npm shim + per-platform packages. Requires the `NPM_TOKEN` repo secret.
245250

246251
**Do not run `npm publish`, `git push`, or `git tag` yourself** — these are
247252
publish actions on shared state. Write the files, hand the user the commands.

0 commit comments

Comments
 (0)