CLI
Contents
The napikit CLI is the dev-time and release-time tool for @teakit/napi projects. It requires Node.js >=20.19.0, is installed as a dev dependency by npx napikit new (or your manual npm install -D napikit), and is invoked through npx napikit or your package.json scripts.
Commands
napikit new
Scaffolds a new @teakit/napi project. Prompts interactively for anything not provided.
What it writes:
build.zig,build.zig.zonpackage.jsonsrc/lib.zig(starter with two example functions)test.mjs.github/workflows/publish.yml- A
.gitignoreand aREADME.md
After scaffolding, it installs with the selected package manager, fetches the napi Zig dependency at the exact @teakit/napi Git commit embedded in this napikit build, and runs an initial napikit build so the binary is available immediately. The generated package.json records the exact selected package-manager version, and the generated lockfile is used with a frozen/immutable install in CI.
Project names are limited so the longest generated scoped binding still fits npm's package-name limit. npm-reserved names, Windows device names, trailing dots, and unsafe existing targets such as files or symbolic links are rejected before writing. Install, Zig fetch, and the initial build all run in an isolated staging directory. Only after they succeed does napikit promote the finished tree. Regular files are created through an exclusive file descriptor, and rollback uses the descriptor-captured identity plus a tombstone move before removing anything. A concurrent replacement is restored or retained instead of being deleted. Node.js cannot atomically prove ownership of a newly created directory or symbolic link, so a failed promotion conservatively retains any such uncertain entry and names every retained or recovery path in the error. A failure before promotion still leaves an absent target absent, and a pre-existing empty target directory is always preserved.
Leaving the repository prompt blank is supported for local development. Before the first release build, set .npm.repository in build.zig to owner/repo; the generated README and terminal summary call this out because npm provenance needs the value in every generated manifest. The first-publish instructions use this order: configure repository metadata, run build --release, commit and push the generated npm/ tree and workflow, run npm-init, then run bump from the clean tracked branch.
Generated projects pin Zig 0.16.0. Package-manager baselines are npm 11.18.0, pnpm 10.34.5, Yarn 4.17.1, and Bun 1.3.14.
napikit build
Without --release, builds for the current platform in the optimization mode set by your build.zig (defaults to Debug). Produces:
The CLI also drops a generated top-level my-addon.js re-exporter so the import path and default/named exports match what users will see after publish. It reads the root package.json: projects with "type": "module" receive an ESM wrapper, while "type": "commonjs" or no type receives a CommonJS wrapper that Node can statically expose to ESM named imports. Invalid JSON or an unsupported type fails before Zig or output cleanup runs. If the pinned Zig dependency predates export-aware wrapper sidecars, the build preserves default-import compatibility and reports that named imports require synchronizing the napikit and @teakit/napi versions.
With --release, cross-compiles every platform listed in .npm.platforms and lays out the full npm/ package tree. See Cross-compiling.
--only and --current speed up local iteration; both are additive and leave the rest of npm/ untouched. See Building a subset.
napikit bump
Bumps the version in every publishable package.json (all addons' main and per-platform binding packages, plus extra packages), creates an annotated tag, and pushes branch + tag.
The push triggers your tag-based CI workflow.
Before changing any package file, napikit bump verifies that every publishable package has one common version and that the worktree (including untracked files) is clean. An unknown positional release value fails instead of opening the interactive picker, and an explicit target cannot downgrade the current version. Every bump and exact-version resume captures a symbolic branch and exact HEAD; detached HEAD is rejected even with --no-push. When push is enabled, it resolves the upstream from that captured branch, fetches it, and rejects a local branch that is behind or diverged. The branch and HEAD are re-attested at every mutation and push boundary, so switching branches during the release aborts safely. When tagging is enabled, both local and remote v<version> conflicts are rejected. A branch-plus-tag push is atomic and names only those two verified refs; --no-tag pushes the single tracked branch without requiring remote atomic-push support or pushing any other local tags. --no-push skips remote/upstream operations, not the symbolic-branch requirement; use it or --no-tag only when you intentionally want to skip the corresponding operation and preflight.
Version manifests are replaced transactionally; each write revalidates the real file identity, mode, and original bytes, and temporary or recovery files are removed only after the command has proved that it still owns their exact identity. An I/O failure restores only earlier files that still belong to this transaction. Concurrent edits, partial writes, or replacements are preserved and reported instead of overwritten. Manifest paths, modes, and staged bytes are then validated with literal Git pathspecs and committed with a detached HEAD and isolated temporary index, so concurrently staged files cannot be swept into the release commit. After Git hooks finish, napikit verifies the commit parent, complete changed-path set, manifest modes and bytes, and worktree copies before atomically advancing the captured branch with a compare-and-swap ref update. An unexpected hook-created commit never updates a branch or tag; hook changes to the worktree remain available for inspection. The command reattests the branch and exact HEAD before creating a tag or pushing. Ignored or assume-unchanged manifests fail before commit. If git commit itself fails, napikit conditionally restores only index entries whose mode and object ID still match the release; a concurrent restage is preserved. If tag creation fails after a verified release commit, or the final atomic push fails, the local release commit is kept. Fix the cause and run napikit bump <same-version> (preserving --no-push when the original release was local-only): the retry first proves that the current commit is exactly a version-only rewrite of its single parent, can create a missing local tag, requires existing release tags to be annotated, verifies them against HEAD and, when pushing, the remote, then pushes the captured commit and annotated-tag object IDs rather than mutable local refs.
napikit publish
Packs each package in npm/ with the selected package manager, then publishes the isolated tarball with npm publish --access public. Designed to be run in CI after napikit build --release.
Per-platform binding packages are published before the main package, so users who install during the publish window always get a working set.
Publishing first requires one common version across main, binding, and extra packages and captures every source manifest before a pack or registry side effect. Generated manifests must retain their complete validated policy; their managed runtime, declaration, and native files must appear as regular tar entries with the exact captured size and SHA-256. Extra packages keep their user-owned manifest fields, but their packed identity and discovered package kind cannot change. Each pack runs in an isolated temporary destination, requires one regular .tgz output, and never deletes or overwrites an archive already present in a package directory. The bounded tar parser verifies checksums, canonical package/ paths, duplicate entries, and package/package.json; npm receives a private immutable copy whose identity and bytes are checked immediately before and after publish. Yarn 3/4 nested generated packages that are not declared workspaces are packed from an isolated staging copy; only the copy receives the project-boundary lockfile, so an existing package or root lockfile remains byte-for-byte unchanged. npm operations are pinned to https://registry.npmjs.org/. A registry conflict counts as an idempotent skip only when an exact name@version lookup succeeds. With provenance, every source and packed manifest must resolve to a canonical GitHub repository; in GitHub Actions it must match GITHUB_REPOSITORY. The release ref must be a tag named exactly v<common-version> before any package is published.
Publishing continues far enough to report useful per-package results, but any required package failure makes the command fail with a non-zero exit instead of reporting success.
napikit npm-init
One-time setup: verifies that every generated and extra package repository resolves to --repo, then checks that npm supports trusted publishing before contacting the registry. It publishes any missing packages at the repository's current common version (initially 0.0.0 in a new repository), then verifies or configures npm trusted publishing (OIDC) for every package. Initial publication uses the same isolated pack, tar, payload, repository, and immutable-copy attestation as napikit publish, so lifecycle scripts cannot redirect the package being trusted. Existing trust must match the requested repository, workflow, and publish permission. Every registry operation explicitly targets npmjs. After this, your CI does not need an NPM_TOKEN.
You only run this once per package. The CI pipeline takes over from here. See Publishing for the full pipeline.
Repository names, workflow filenames, npm package names and versions, and generated binding paths are validated before subprocess or filesystem use. Existing trusted-publisher records are skipped only when their provider, repository, workflow filename, and publish permission all match the requested configuration. A mismatched or unverifiable record is retried, and a conflict remains an error instead of being reported as success. Initial publishing and trusted-publisher setup report failures per package; if any required operation fails, the command exits non-zero and can be re-run to retry the incomplete packages.