Releases and npm publishing
SkillDev toolsThis skill should be used when operating releases or npm publishing in brepjs — cutting or merging a release-please PR, recovering a failed npm publish, or deciding whether a commit may carry a breaking marker. Trigger phrases include "release PR", "release-please", "autorelease: pending", "publish to npm", "npm publish failed", "republish", "ETARGET from a leaf release PR ordered ahead of root", "version bump", "why did the major version bump", "breaking change commit", "trusted publisher", "dry_run", "release tag", "manifest conflict", or editing .github/workflows/publish-*.yml or release-please-config.json.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Releases and npm publishing skill
What this skill tells your AI
The instructions your AI receives, as published by andymai/brepjs in .claude/skills/release-publishing/SKILL.md and read by ahel’s review.
Every release in this repo flows through one workflow: .github/workflows/release-please.yml. Its inline comments are the canonical deep documentation — read them before changing anything. This skill is the operator's playbook: how a normal release flows, the hard rules that prevent outages, and the recovery recipes when a step fails.
Mental model
- release-please, manifest mode. Versions live in
.release-please-manifest.json; managed packages are declared inrelease-please-config.json. On every push tomain, release-please opens/updates a per-package release PR (separate-pull-requests: true), bumping the version and CHANGELOG from Conventional Commits. - Six managed packages, two deliberately unmanaged. Managed: root
.(brepjs),packages/brepjs-opencascade,packages/brepjs-voxel-wasm,packages/brepjs-cad,packages/brepjs-bim,packages/brepjs-sheetmetal. NOT managed by release-please:brepjs-viewer(versioned/published manually — see below) andpackages/brepjs-voxel(unpublished workspace consumer). - node-workspace re-pins cross-deps. The
node-workspaceplugin rewrites each leaf package'sbrepjsdependency to the version of the pending root release. This is the source of the ETARGET hazard (below). - npm auth is OIDC trusted publishers, bound to workflow filenames. Each package's publish authenticates via an npm trusted publisher tied to a specific
.github/workflows/publish-*.ymlfilename. Renaming a publish workflow breaks auth silently.
How a normal release flows
- Commits land on
mainwith Conventional-Commit subjects (enforced by commitlint — seecommitlint.config.js,.husky/commit-msg; format detail lives in the git-pr-workflow skill). - The
release-pleasejob (App token,googleapis/release-please-action@v5) opens or updates release PRs labeledautorelease: pending. - The
auto-mergejob serializes root before leaves: it lists openautorelease: pendingPRs and, per branch name:*--components--brepjs-opencascade→ permanently held (manual, expensive WASM build).- root branch
release-please--branches--main--components--brepjs→gh pr merge --auto --squash(merges first). - any other leaf → held while a root release PR is open; merged on a later run after root lands.
- Root release PR merges. CI passes trivially on release PRs —
ci.ymlskips the paths-filter forrelease-please--*branches, andci-passtreats skipped jobs as pass — so--autocompletes. publish-brepjsruns inline inrelease-please.yml:npm ci && npm run build && npm publish --provenance(OIDC). This is the ONLY package published inline.- Merging bumps
main. The next release-please run regenerates each leaf PR with a now-validbrepjspin, auto-merges them, and each auto-publishing leaf's job dispatches its ownpublish-brepjs-<pkg>.ymlwith-f dry_run=false --ref <release-tag>. (brepjs-opencascadeis the exception — always manual viapublish-opencascade.yml;brepjs-voxel-wasmhas no publish workflow.)
Leaves dispatch against the immutable release tag release-please just created, never main: the dispatch API accepts a tag but rejects a raw SHA, and the tag pin avoids publishing the wrong commit if another push lands mid-window.
Hard rules
| Rule | Why | Where |
|---|---|---|
Never put !/BREAKING CHANGE on a commit unless intentionally breaking the brepjs public API. | Root is 18.x; any breaking marker majors the library immediately. | .release-please-manifest.json |
Root exclude-paths are apps and every packages/* workspace, so a satellite-only commit never bumps root. A breaking commit touching root docs/, README.md, scripts/, or .github/ still majors root. | Every non-excluded path attributes to the root component. | release-please-config.json:8-21 |
Never rename or move a publish-*.yml file without re-registering the npm trusted publisher for that package. | OIDC auth is bound to the exact filename; a rename makes npm publish fail auth. | comments in each publish-*.yml |
Never merge a leaf release PR while the root brepjs release PR is open. | node-workspace pins the leaf to an unpublished brepjs version → npm install ETARGET → Vercel deploys break. Also conflicts the root PR on the shared manifest. | release-please.yml:57-72 |
| Never cancel an in-flight Release Please run. | Cancelling mid-publish leaves a tagged-but-unpublished release; the concurrency group queues, never cancels. | release-please.yml:17-24 |
Manual publish dispatches default to dry_run: true (build-only). A real publish needs -f dry_run=false and a ref. | A bare dispatch is a safe smoke build. | every publish-*.yml |
brepjs-opencascade is always manual. | ~60-min Docker WASM build from ghcr.io/andymai/opencascade.js:v8 (needs GHCR_TOKEN). | publish-opencascade.yml |
Recovery playbook
Root brepjs publish failed (release tagged, npm empty). Do NOT re-run the failed push job — release-please would try to re-tag. Instead re-dispatch the whole workflow in republish mode:
gh workflow run release-please.yml -f republish=true
This skips the release-please job and runs only publish-brepjs against the current main package.json version. Republish covers root brepjs only.
A leaf publish failed (brepjs-cad/bim/sheetmetal). Re-dispatch that leaf's own publish workflow against the release tag:
gh workflow run publish-brepjs-<pkg>.yml -f dry_run=false --ref <release-tag>
brepjs-opencascade needs publishing. Manual only:
gh workflow run publish-opencascade.yml -f dry_run=false --ref <release-tag>
Conflicted release PRs (serializer bypassed — e.g. a human merged a leaf early). Not tooled; manual fix. For each still-open release PR: merge main into its branch, take the union of .release-please-manifest.json (keep every package's highest intended version), resolve CHANGELOG/package.json, push. Then let the auto-merge job re-arm on the next run.
npm install/CI fails with ETARGET after a merge. A leaf on main is pinned to a brepjs version not yet on npm. Confirm root actually published (npm view brepjs version); if root is fine, the leaf pin is ahead — publish root first (republish above), or bump the leaf's pin down to a published version and open a fix PR. Related consumer-side symptoms are covered in ci-triage and companion-packages.
Pack validation failed (prepack). Root npm pack/npm publish runs scripts/validate-pack.sh (wired as prepack, with prepublishOnly: npm run build). It fails on more than 500 files or any .d.ts.map sidecar. Inspect with npm pack --dry-run. .d.ts.map files mean declarationMap got re-enabled — check vite.config.ts.
Package publish matrix
| Package | Managed? | Auto-merge? | Auto-publish? | Publish route |
|---|---|---|---|---|
brepjs (root) | yes | yes (first) | yes, inline | release-please.yml job publish-brepjs (OIDC) |
brepjs-cad | yes | yes (after root) | yes, dispatched | publish-brepjs-cad.yml |
brepjs-bim | yes | yes (after root) | yes, dispatched | publish-brepjs-bim.yml |
brepjs-sheetmetal | yes | yes (after root) | yes, dispatched | publish-brepjs-sheetmetal.yml |
brepjs-opencascade | yes | held (manual) | manual only | publish-opencascade.yml (Docker WASM) |
brepjs-voxel-wasm | yes (bumps Cargo.toml) | yes (after root) | no workflow, not on npm | none |
brepjs-viewer | no | n/a | manual | publish-brepjs-viewer.yml |
brepjs-voxel | no | n/a | not published | none |
Build prerequisites baked into the publish workflows: brepjs-cad restores OCCT WASM (scripts/ensure-wasm.sh, 3× retry) then builds root + brepjs-viewer + itself (viewer is a build-time devDep whose dist is bundled into the brepjs-cad build output). bim and sheetmetal build root brepjs first (their vite-plugin-dts needs root's emitted types). brepjs-viewer uses npm ci --ignore-scripts and publishes --access public.
Notes and gotchas
brepjs-vieweris intentionally de-managed from release-please. node-workspace kept re-pinningbrepjs-cad's build-timebrepjs-viewerdevDep ("brepjs-viewer": "*") to viewer's pending unpublished version, breaking monoreponpm ci. Version and publish it by hand viapublish-brepjs-viewer.yml.- The Claude-plugin marketplace version is independent of the npm version. release-please does not bump
packages/brepjs-cad/.claude-plugin/plugin.json; the plugin version and the npmbrepjs-cadversion move separately. - Docs deploy is not release-triggered.
.github/workflows/docs.ymlchains off successful CI onmain(workflow_run); emergency redeploy is a main-onlyworkflow_dispatch. scripts/publish-all.shis a legacy manual OTP fallback (opencascade → root only,--no-provenance). The OIDC workflows supersede it; use only as a local last resort, and note it does not cover cad/bim/sheetmetal/viewer.
Additional resources
- Commit format and the
!decision:git-pr-workflowskill. - CI job failure modes (npm ci EUSAGE/ETARGET, release PR won't merge, publish red):
ci-triageskill. - Which package to edit and how a change ripples to consumers:
companion-packagesskill. - Canonical deep reference: the inline comments in
.github/workflows/release-please.yml.
Signals
- GitHub stars
- 101
- Forks
- 9
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
release-publishing- Source
- github.com/andymai/brepjs