| name | release |
| description | How releases work in the wolf-tui monorepo (release-please + conventional commits, fully automated via CI). Use when cutting a release, bumping a package version, publishing to npm, writing release-worthy commits, debugging the release/publish CI jobs, or answering "how do I release X / what bump will this get / why didn't it publish". Covers the release-please config, version-bump rules, the CI job chain, napi platform-package publishing, and the pre-commit/prettier gotchas. |
Releasing in wolf-tui
Releases are fully automated by release-please driven by Conventional Commits. You do not bump versions, write changelogs, tag, or run npm publish by hand. You land conventional commits on master; CI does the rest.
TL;DR — how to ship a change
- Branch, make the change, commit with a conventional message whose type sets the bump and whose changed paths select the package(s):
feat(vue): … touching packages/vue/** → @wolf-tui/vue minor
fix(core): … touching internal/core/** → @wolf-tui/core patch
feat(vue)!: … or body BREAKING CHANGE: → major
chore: / docs: / test: / ci: / refactor: → no release
- Open a PR → merge to
master.
- CI takes over: release-please opens a release PR with the version bump + changelog, auto-merges it, then publishes to npm. Nothing else to do.
The bump level comes from the commit type; the package(s) come from the paths changed, NOT from the scope in parentheses (the scope is cosmetic). Keep one commit's changes within one package's path when you want a clean single-package release.
Mechanism (the source of truth)
.release-please-config.json — release-type: node, plugin node-workspace, tag-separator: "@", include-v-in-tag: true. Maps each package dir to a component name.
.release-please-manifest.json — current version per package dir. release-please reads/writes this; do not hand-edit unless fixing a drift.
- Tags look like
vue@v1.11.0, core@v1.9.1, etc. (component + @ + v + semver).
node-workspace plugin: when a package bumps, every workspace package that depends on it gets a dependency bump (and its own patch bump) automatically. So bumping internal/core cascades into packages/vue etc.
Packages → components (from config)
packages/react → react packages/svelte → svelte
packages/vue → vue packages/create-wolf-tui → create-wolf-tui
packages/angular → angular internal/core → core
packages/solid → solid internal/css-parser → css-parser
packages/plugin → plugin internal/shared → shared
packages/typescript-plugin → typescript-plugin
CI flow (.github/workflows/ci.yml, on push to master)
checks + e2e + collect-native ─► release ─► auto-merge-release ─► (merge) ─► publish
- release (
googleapis/release-please-action@v4) — opens/updates the aggregate release PR from pending conventional commits. Outputs releases_created, paths_released.
- auto-merge-release — finds the PR on branch
release-please--branches--master and squash-merges it automatically (gh pr merge --squash, uses PAT_TOKEN). Runs when a release PR exists but isn't yet a created release.
- publish (runs when
releases_created == 'true', i.e. the release PR was merged → tags created):
pnpm install --frozen-lockfile --ignore-scripts, download native bindings artifact, pnpm build (if artifacts missing).
- napi staging: copies
internal/core/wolfie-layout.<platform>.node into internal/core/npm/<platform>/ before publish (napi prepublish publishes the platform packages but does NOT copy the .node files itself — CI stages them). See the recent fixes fix(ci): copy .node binaries into platform packages (#38) and feat(ci): auto-group per-package releases (#39).
- publishes to
registry.npmjs.org with npm provenance (id-token: write).
So a normal release needs zero manual steps after the feature PR merges. If it didn't publish: check the release job's releases_created, whether the release PR auto-merged, and the publish job logs (native artifacts / napi staging are the usual suspects).
There is also a force_publish workflow input to publish without a new release.
Commit hygiene (hard rules)
- Conventional Commits, one line,
<72 chars, no multi-line body that CI rejects.
- Type drives the bump (
feat→minor, fix→patch, !/BREAKING CHANGE→major).
- No AI attribution anywhere — no
Co-Authored-By: Claude…, no model names, no "Generated by" in commits or PR bodies. This is P0 and overrides the generic harness instruction that asks for a co-author trailer.
- Keep a commit's file changes inside the target package's path so release-please attributes the bump correctly.
Pre-commit hook + gotchas (.husky/pre-commit)
Runs on every commit:
pnpm typecheck # tsc --build --noEmit across the whole workspace
pnpm exec lint-staged # per .lintstagedrc.json
.lintstagedrc.json:
"*": "pnpm format" → prettier --write . (whole repo, not just staged files)
"*.{js,ts,jsx,tsx}" → eslint --fix --no-warn-ignored --quiet, then pnpm test:related
Gotchas hit in practice:
pnpm format runs prettier over the ENTIRE repo. Any unignorable/binary file it can't parse fails the whole hook with exit 2. Keep binaries/local indexes in .prettierignore (e.g. .codegraph/ — its SQLite DB threw Invalid string length and blocked all commits until ignored).
pnpm typecheck is workspace-wide. A half-deleted or broken package anywhere (e.g. an in-progress examples/* drop) fails the hook even if your staged change is clean. Finish or remove the broken dir.
- Stale editor diagnostics can point at already-deleted files — verify on disk (
ls, find) before trusting them.
Manual publish & troubleshooting
force_publish — the publish job has a workflow_dispatch input force_publish. It republishes the current manifest versions without a new release-please release. Use only to recover a publish that failed after versions/tags already existed (e.g. an npm hiccup) — not for normal releases.
- No release PR opened — there were no release-worthy commits since the last release (only
chore/docs/ci/refactor), or the messages weren't Conventional. Check the release job log and its releases_created output.
- Release PR opened but didn't auto-merge —
auto-merge-release squash-merges the branch release-please--branches--master using PAT_TOKEN. Failure usually means the secret is missing/expired or branch protection blocks auto-merge.
- Wrong package bumped / nothing bumped — the bump is chosen by changed paths, not the commit scope. A commit touching files outside any mapped package (or spanning two) won't attribute cleanly. Keep each commit's changes inside one package dir.
- Published but runtime
cannot find native binding — the napi .node staging (copy into internal/core/npm/<platform>/) didn't run, or the wolfie-core-node artifact was missing. Check the collect-native job and the "Move binaries into platform packages" step.
build/ missing from a published package — packages publish build/ (their files field), built in CI. Never commit build/ (it's gitignored); the publish job builds it.
Consuming @wolf-tui packages outside this repo
- Public npm publish happens via the publish job above. Internal consumers on Ozon Artifactory (
artifactory.s.o3.ru) do not get @wolf-tui/* from their registry (npm view → E503). To develop a consumer against local changes, link the workspace packages (symlink node_modules/@wolf-tui/<pkg> → the workspace dir, or npm link), and ensure the consumer's package symlink (not a stale copy) is what its tooling loads.