Covers the full publish flow: version bump, build, publish, and restore. Explains the package.dev.json mechanism (why workspace:* refs need swapping for npm), how the esbuild bundle works, and forbidden token enforcement. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5.5 KiB
Publishing to npm
This document covers how to build and publish the paperclipai CLI package to npm.
Prerequisites
- Node.js 20+
- pnpm 9.15+
- An npm account with publish access to the
paperclipaipackage - Logged in to npm:
npm login
Quick Reference
# Bump version
./scripts/version-bump.sh patch # 0.1.0 → 0.1.1
# Build
./scripts/build-npm.sh
# Preview what will be published
cd cli && npm pack --dry-run
# Publish
cd cli && npm publish --access public
# Restore dev package.json
mv cli/package.dev.json cli/package.json
Step-by-Step
1. Bump the version
./scripts/version-bump.sh <patch|minor|major|X.Y.Z>
This updates the version in two places:
cli/package.json— the source of truthcli/src/index.ts— the Commander.version()call
Examples:
./scripts/version-bump.sh patch # 0.1.0 → 0.1.1
./scripts/version-bump.sh minor # 0.1.0 → 0.2.0
./scripts/version-bump.sh major # 0.1.0 → 1.0.0
./scripts/version-bump.sh 1.2.3 # set explicit version
2. Build
./scripts/build-npm.sh
The build script runs five steps:
- Forbidden token check — scans tracked files for tokens listed in
.git/hooks/forbidden-tokens.txt. If the file is missing (e.g. on a contributor's machine), the check passes silently. The script never prints which tokens it's searching for. - TypeScript type-check — runs
pnpm -r typecheckacross all workspace packages. - esbuild bundle — bundles the CLI entry point (
cli/src/index.ts) and all workspace package code (@paperclipai/*) into a single file atcli/dist/index.js. External npm dependencies (express, postgres, etc.) are kept as regular imports. - Generate publishable package.json — replaces
cli/package.jsonwith a version that has real npm dependency ranges instead ofworkspace:*references (see package.dev.json below). - Summary — prints the bundle size and next steps.
To skip the forbidden token check (e.g. in CI without the token list):
./scripts/build-npm.sh --skip-checks
3. Preview (optional)
See what npm will publish:
cd cli && npm pack --dry-run
4. Publish
cd cli && npm publish --access public
5. Restore dev package.json
After publishing, restore the workspace-aware package.json:
mv cli/package.dev.json cli/package.json
6. Commit and tag
git add cli/package.json cli/src/index.ts
git commit -m "chore: bump version to X.Y.Z"
git tag vX.Y.Z
package.dev.json
During development, cli/package.json contains workspace:* references like:
{
"dependencies": {
"@paperclipai/server": "workspace:*",
"@paperclipai/db": "workspace:*"
}
}
These tell pnpm to resolve those packages from the local monorepo. This is great for development but npm doesn't understand workspace:* — publishing with these references would cause install failures for users.
The build script solves this with a two-file swap:
- Before building:
cli/package.jsonhasworkspace:*refs (the dev version). - During build (
build-npm.shstep 4):- The dev
package.jsonis copied topackage.dev.jsonas a backup. generate-npm-package-json.mjsreads every workspace package'spackage.json, collects all their external npm dependencies, and writes a newcli/package.jsonwith those real dependency ranges — noworkspace:*refs.
- The dev
- After publishing: you restore the dev version with
mv package.dev.json package.json.
The generated publishable package.json looks like:
{
"name": "paperclipai",
"version": "0.1.0",
"bin": { "paperclipai": "./dist/index.js" },
"dependencies": {
"express": "^5.1.0",
"postgres": "^3.4.5",
"commander": "^13.1.0"
}
}
package.dev.json is listed in .gitignore — it only exists temporarily on disk during the build/publish cycle.
How the bundle works
The CLI is a monorepo package that imports code from @paperclipai/server, @paperclipai/db, @paperclipai/shared, and several adapter packages. These workspace packages don't exist on npm.
esbuild bundles all workspace TypeScript code into a single dist/index.js file (~250kb). External npm packages (express, postgres, zod, etc.) are left as normal import statements — they get installed by npm when a user runs npx paperclipai onboard.
The esbuild configuration lives at cli/esbuild.config.mjs. It automatically reads every workspace package's package.json to determine which dependencies are external (real npm packages) vs. internal (workspace code to bundle).
Forbidden token enforcement
The build process includes the same forbidden-token check used by the git pre-commit hook. This catches any accidentally committed tokens before they reach npm.
- Token list:
.git/hooks/forbidden-tokens.txt(one token per line,#comments supported) - The file lives inside
.git/and is never committed - If the file is missing, the check passes — contributors without the list can still build
- The script never prints which tokens are being searched for
- Matches are printed so you know which files to fix, but not which token triggered it
Run the check standalone:
pnpm check:tokens
npm scripts reference
| Script | Command | Description |
|---|---|---|
build:npm |
pnpm build:npm |
Full build (check + typecheck + bundle + package.json) |
version:bump |
pnpm version:bump <type> |
Bump CLI version |
check:tokens |
pnpm check:tokens |
Run forbidden token check only |