Files
paperclip/doc/PUBLISHING.md
Dotta e8acd6a8b0 Add bump-and-publish script for one-command npm releases
Combines version bump, build, publish, restore, commit, and tag into
a single ./scripts/bump-and-publish.sh command. Supports --dry-run.
Also restores cli/package.json to dev format after v0.1.1 publish.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:46:06 -06:00

6.4 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 paperclipai package
  • Logged in to npm: npm login

One-Command Publish

The fastest way to publish — bumps version, builds, publishes, restores, commits, and tags in one shot:

./scripts/bump-and-publish.sh patch          # 0.1.1 → 0.1.2
./scripts/bump-and-publish.sh minor          # 0.1.1 → 0.2.0
./scripts/bump-and-publish.sh major          # 0.1.1 → 1.0.0
./scripts/bump-and-publish.sh 2.0.0          # set explicit version
./scripts/bump-and-publish.sh patch --dry-run # everything except npm publish

The script runs all 6 steps below in order. It requires a clean working tree and an active npm login session (unless --dry-run). After it finishes, push:

git push && git push origin v<version>

Manual Step-by-Step

If you prefer to run each step individually:

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 truth
  • cli/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:

  1. 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.
  2. TypeScript type-check — runs pnpm -r typecheck across all workspace packages.
  3. esbuild bundle — bundles the CLI entry point (cli/src/index.ts) and all workspace package code (@paperclipai/*) into a single file at cli/dist/index.js. External npm dependencies (express, postgres, etc.) are kept as regular imports.
  4. Generate publishable package.json — replaces cli/package.json with a version that has real npm dependency ranges instead of workspace:* references (see package.dev.json below).
  5. 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:

  1. Before building: cli/package.json has workspace:* refs (the dev version).
  2. During build (build-npm.sh step 4):
    • The dev package.json is copied to package.dev.json as a backup.
    • generate-npm-package-json.mjs reads every workspace package's package.json, collects all their external npm dependencies, and writes a new cli/package.json with those real dependency ranges — no workspace:* refs.
  3. 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
bump-and-publish pnpm bump-and-publish <type> One-command bump + build + publish + commit + tag
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