Skip to content

ci(packages): add npm version lifecycle hooks to releasable packages - #132

Open
elycruz wants to merge 1 commit into
mainfrom
101-npm-version-scripts
Open

ci(packages): add npm version lifecycle hooks to releasable packages#132
elycruz wants to merge 1 commit into
mainfrom
101-npm-version-scripts

Conversation

@elycruz

@elycruz elycruz commented Jul 31, 2026

Copy link
Copy Markdown
Member

Summary

Adds npm's preversion / version / postversion lifecycle hooks to the three
releasable packages so that a version bump is gated by tests and a build, and
produces a proper release commit + tag.

Closes #101

Work unit: 101-npm-version-scripts

Changes

  • packages/fjl/package.json, packages/fjl-validator/package.json,
    packages/fjl-inputfilter/package.json — added the three lifecycle hooks.
  • node_scripts/tasks/version.mjs (new) — precheck and commit-and-tag
    subcommands used by the hooks.
  • md/RELEASING.md (new) — the release/versioning flow, and the reasoning
    behind every deviation from the issue's example snippet.
  • README.md — a two-line pointer to the new doc.

Root package.json was not touched; the hooks reuse the test:* and
build scripts that already exist there.

The hooks, e.g. for fjl:

{
  "preversion": "node ../../node_scripts/tasks/version.mjs precheck && pnpm -w run test:fjl",
  "version": "pnpm -w run build",
  "postversion": "node ../../node_scripts/tasks/version.mjs commit-and-tag"
}

Which packages, and which were skipped

Package Hooks Why
fjl yes Published by .github/workflows/publish.yml; built by rollup; tested by jest.
fjl-validator yes In root workspaces, built, tested, published on npm.
fjl-inputfilter yes In root workspaces, built, tested, published on npm.
fjl-validator-recaptcha no Its rollup target and its jest project are both commented out, so there is currently neither a build nor a runnable suite to gate a release on (jest --selectProjects fjl-validator-recaptcha exits 1 with "no projects were found").
fjl-filter no Not in root workspaces, not in rollup.config.mjs or jest.config.mjs, and its main/module point at a fjl-input.js bundle nothing produces. Dormant.
fjl-labs no Listed in pnpm-workspace.yaml but has no package.json at all — sources only.

Deviations from the issue's example snippet

The issue's snippet is "preversion": "npm test", "version": "npm run build && git add -A dist", "postversion": "git push && git push --tags && rm -rf build/temp". Every line needed adapting:

  1. npm test / npm run build do not exist per package. Jest is configured
    once at the root (jest.config.mjs, a multi-project runner selected with
    --selectProjects) and rollup likewise (rollup.config.mjs, builds all
    packages in one pass). The hooks therefore call back into the root with
    pnpm -w run <script>.
  2. git add -A dist would stage nothing. **/dist is in .gitignore;
    build output is never committed and CI rebuilds it on publish. The version
    hook still runs the build, but as a gate — the bump aborts if the package
    stopped compiling.
  3. rm -rf build/temp has no analogue. No such directory;
    rollup.config.mjs already cleans each packages/*/dist/ before writing.
  4. postversion commits and tags instead of pushing. This is the
    non-obvious one. npm version (which pnpm 8.13.1 delegates to — it does not
    implement version itself) only performs git work when a .git entry sits
    directly in its cwd: @npmcli/git's is() is
    stat(cwd + '/.git') and never walks up. The only .git here is at the repo
    root, so pnpm version inside packages/<name> bumps package.json and
    runs all three hooks but silently skips its clean-tree guard, the release
    commit, and the tag
    . Running it from the root does not help either:
    npm version --workspace <pkg> hard-codes 'git-tag-version': false in
    lib/commands/version.js. node_scripts/tasks/version.mjs puts the two
    missing steps back.
  5. Tags are package-qualified (fjl@2.0.0-alpha.6) rather than npm's bare
    v<version>, because siblings collide otherwise — fjl-inputfilter and
    fjl-validator-recaptcha are both at 1.3.0 today.
  6. Pushing is not automated. publish.yml triggers on release: [created],
    so a tag push alone publishes nothing and a manual GitHub Release is required
    regardless; the repo's pre-push hook runs the full pnpm test && pnpm build, making a push-per-package expensive; and coordinated multi-package
    bumps are better pushed once. commit-and-tag prints the exact
    git push --follow-tags to run next.

How this was validated without cutting a release

No version was bumped, no tag was created, and nothing was published in this
repo.
git diff origin/main touches only scripts blocks; the three package
versions are still 2.0.0-alpha.5, 0.8.0, 1.3.0, and the tag count is
unchanged at 139.

  1. Every referenced command was run directly. From each package directory,
    pnpm run preversion and pnpm run version were executed on their own — all
    exit 0 (fjl 125 suites / 920 tests, fjl-validator 6 / 33,
    fjl-inputfilter 2 / 113; pnpm -w run build exits 0 from a package dir).
  2. precheck was exercised in this repo (it is read-only): it correctly
    failed while the working tree was dirty and passed once the tree was clean.
  3. The full lifecycle was run end-to-end in a throwaway scratch repo that
    mirrors this layout (root pnpm-workspace.yaml + packages/fjl,
    packageManager pinned to pnpm@8.13.1), using a byte-for-byte copy of
    version.mjs and the real scripts block from packages/fjl/package.json,
    with the root test:fjl/build stubbed to echo. Result: hooks ran in
    order, and the commit chore(release): fjl@2.0.0-alpha.6 plus annotated tag
    fjl@2.0.0-alpha.6 were created, touching only packages/fjl/package.json.
    Guard rails were verified too — re-running commit-and-tag refuses an
    existing tag, and an unknown subcommand exits 1.
  4. The generated commit message was checked against the repo's commitlint
    config
    : echo "chore(release): fjl@2.0.0-alpha.6" | npx commitlint exits 0.
  5. The npm behaviour claims were verified against source, not guessed — read
    from the installed libnpmversion/lib/version.js,
    @npmcli/git/lib/is.js, and npm/lib/commands/version.js.
  6. eslint and tsc-files --noEmit pass on the new version.mjs.

Hooks

No hook was bypassed. commit-msg, pre-commit (lint-staged), and pre-push
(pnpm test && pnpm build) all ran and passed normally. The pre-existing
pnpm lint failures on main and the pre-existing rollup .d.ts warnings for
packages/fjl/dist/esm/object/setTheory.d.ts are untouched by this change.

🤖 Generated with Claude Code

Adds `preversion`/`version`/`postversion` hooks to the three releasable
packages (fjl, fjl-validator, fjl-inputfilter), plus
`node_scripts/tasks/version.mjs` and `md/RELEASING.md`.

The hooks are adapted to this repo rather than copied from npm's docs:
jest and rollup are configured once at the repo root, so the hooks call
back with `pnpm -w run <script>`; `**/dist` is gitignored, so the build
runs as a gate rather than to stage anything.

`npm version` (which pnpm 8.13.1 delegates to) only does git work when
`.git` sits directly in its cwd - `@npmcli/git`'s `is()` never walks up -
so inside `packages/<name>` it skips its clean-tree guard, the release
commit, and the tag. `version.mjs` restores those two steps and tags
package-qualified (`fjl@2.0.0-alpha.6`) to avoid sibling collisions.

fjl-validator-recaptcha, fjl-filter, and fjl-labs are deliberately left
alone; reasons are documented in md/RELEASING.md.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI/CD - Update packages to use npm/pnpm's version feature

1 participant