doc/PUBLISHING.md
Low-level reference for how Paperclip packages are prepared and published to npm.
For the maintainer workflow, use doc/RELEASING.md. This document focuses on packaging internals.
Use these scripts:
scripts/release.sh for canary and stable publish flowsscripts/create-github-release.sh after pushing a stable tagscripts/rollback-latest.sh to repoint latestscripts/build-npm.sh for the CLI packaging buildPaperclip no longer uses release branches or Changesets for publishing.
The CLI package, paperclipai, imports code from workspace packages such as:
@paperclipai/server@paperclipai/db@paperclipai/sharedpackages/adapters/Those workspace references are valid in development but not in a publishable npm package. The release flow rewrites versions temporarily, then builds a publishable CLI bundle.
build-npm.shRun:
./scripts/build-npm.sh
This script:
--skip-checks is suppliedpnpm -r typecheckcli/dist/index.jsnode --checkcli/package.json into a publishable npm manifest and stores the dev copy as cli/package.dev.jsonREADME.md into cli/README.md for npm metadataAfter the release script exits, the dev manifest and temporary files are restored automatically.
Public packages are discovered from:
packages/server/ui/cli/The version rewrite step now uses scripts/release-package-map.mjs, which:
workspace:* dependency references to the exact target versionThose rewrites are temporary. The working tree is restored after publish or dry-run.
@paperclipai/ui packagingThe UI package publishes prebuilt static assets, not the source workspace.
The ui package uses scripts/generate-ui-package-json.mjs during prepack to swap in a lean publish manifest that:
name and versiondist/After packing or publishing, postpack restores the development manifest automatically.
@paperclipai/uiIf you need to publish only the UI package once by hand, use the real package name:
@paperclipai/uiRecommended flow from the repo root:
# optional sanity check: this 404s until the first publish exists
npm view @paperclipai/ui version
# make sure the dist payload is fresh
pnpm --filter @paperclipai/ui build
# confirm your local npm auth before the real publish
npm whoami
# safe preview of the exact publish payload
cd ui
pnpm publish --dry-run --no-git-checks --access public
# real publish
pnpm publish --no-git-checks --access public
Notes:
ui/, not the repo root.prepack automatically rewrites ui/package.json to the lean publish manifest, and postpack restores the dev manifest after the command finishes.npm view @paperclipai/ui version already returns the same version that is in ui/package.json, do not republish. Bump the version or use the normal repo-wide release flow in scripts/release.sh.If the first real publish returns npm E404, check npm-side prerequisites before retrying:
npm whoami must succeed first. An expired or missing npm login will block the publish.@paperclipai/ui, the paperclipai npm organization must exist and the publisher must be a member with permission to publish to that scope.--access public for a public scoped package.Paperclip uses calendar versions:
YYYY.MDD.PYYYY.MDD.P-canary.NExamples:
2026.318.02026.318.1-canary.2Canaries publish under the npm dist-tag canary.
Example:
This keeps the default install path unchanged while allowing explicit installs with:
npx paperclipai@canary onboard
The release script now verifies two things after a canary publish:
canary dist-tag resolves to the version that was just published@paperclipai/* dependency referenced by that manifest exists on npmIt also treats latest -> canary as a failure by default, because npm metadata can otherwise leave the default install path pointing at an unreleased canary dependency graph. Only pass ./scripts/release.sh canary --allow-canary-latest when that latest behavior is explicitly intended.
Stable publishes use the npm dist-tag latest.
Example:
Stable publishes do not create a release commit. Instead:
vYYYY.MDD.P points at that original commitThe intended CI model is npm trusted publishing through GitHub OIDC.
That means:
NPM_TOKEN in repository secretsSee doc/RELEASE-AUTOMATION-SETUP.md for the GitHub/npm setup steps.
Paperclip does not auto-publish every non-private workspace package anymore.
CI publishing is controlled by scripts/release-package-manifest.json.
When you add a new public package:
"publishFromCi": false"publishFromCi": true after npm trusted publishing is configured for that packagePR CI now checks changed release-enabled package manifests against npm. That catches a missing first-publish bootstrap before the change reaches master.
The first publish of a brand-new package still needs one human maintainer with npm write access. After that, trusted publishing can take over.
Example for @paperclipai/adapter-acpx-local from the repo root:
# safe preview
pnpm run release:bootstrap-package -- @paperclipai/adapter-acpx-local
# one-time first publish from an authenticated maintainer machine
pnpm run release:bootstrap-package -- @paperclipai/adapter-acpx-local --publish --otp 123456
The helper script:
--skip-build is passednpm pack --dry-run in the package directorynpm publish --access public when --publish --otp <code> is providedFor the real --publish step, the maintainer machine must already be authenticated to npm.
If npm whoami returns 401, first run npm logout --registry=https://registry.npmjs.org/ to clear any stale local auth, then run npm login or npm adduser locally as an npm org member, and finally rerun the helper.
That local human auth is fine for the one-time bootstrap publish; we just do not want the same auth model inside CI.
The helper now requires --otp <code> up front for --publish, so it fails before the real publish attempt if the one-time password is missing.
After that first publish succeeds:
https://www.npmjs.com/package/@paperclipai/adapter-acpx-localSettings → Trusted publishingpaperclipai/papercliprelease.ymlSettings → Publishing access and enable Require two-factor authentication and disallow tokenspublishFromCi: true in scripts/release-package-manifest.jsonOnce those steps are done, future canary and stable publishes for that package are automated through GitHub OIDC. The manual step is only the first package creation on npm.
Rollback does not unpublish anything.
It repoints the latest dist-tag to a prior stable version:
./scripts/rollback-latest.sh 2026.318.0
This is the fastest way to restore the default install path if a stable release is bad.