typescript-plugin/skills/bun-publishing/SKILL.md
Publish npm packages built with Bun: package.json config, CLI tool packaging, provenance signing, release automation. Use when setting up `publishConfig`/`files`/`bin`, packaging a CLI, enabling `--provenance`, or wiring release-please.
npx skillsauth add laurigates/claude-plugins bun-publishingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
| Scenario | Use this skill | Alternative |
|----------|---------------|-------------|
| Configuring package.json for npm publishing | Yes | N/A |
| Publishing a package to npm registry | Yes | N/A |
| Setting up CLI tool packaging with bin | Yes | N/A |
| Configuring provenance signing | Yes | N/A |
| Setting up release-please automation | Yes | N/A |
| Validating tarball contents before publish | Yes | N/A |
| Installing or updating dependencies | No - use bun-package-manager | bun-add for quick additions |
| Building/bundling before publish | No - use bun-development | bun-build for quick builds |
Publishing npm packages built with Bun:
{
"name": "@org/package-name",
"version": "1.0.0",
"description": "Package description",
"main": "build/index.js",
"type": "module",
"publishConfig": {
"access": "public"
},
"files": [
"build/",
"README.md",
"LICENSE"
],
"scripts": {
"build": "tsc && chmod +x build/index.js",
"prepublishOnly": "bun run build"
},
"engines": {
"node": ">=20.0.0",
"bun": ">=1.0.0"
}
}
| Field | Purpose |
|-------|---------|
| publishConfig.access | public required for scoped packages |
| files | Whitelist of files/dirs to include in tarball |
| main | Entry point for CommonJS/ES import |
| type | module for ESM, commonjs for CJS |
| engines | Runtime version requirements |
| prepublishOnly | Runs before npm publish |
Scoped packages (@org/name) require explicit public access:
{
"name": "@myorg/mypackage",
"publishConfig": {
"access": "public"
}
}
Or use the CLI flag:
npm publish --access public
{
"bin": {
"mycli": "build/index.js"
}
}
For single-command packages:
{
"bin": "build/index.js"
}
Entry point requires shebang and executable permission:
#!/usr/bin/env node
// build/index.js
import { main } from "./main.js";
main();
Build script must set permissions:
{
"scripts": {
"build": "tsc && chmod +x build/index.js"
}
}
build-prod:
rm -rf build
tsc --declaration --sourceMap
chmod +x build/index.js
publish: build-prod
npm publish --access public
# Standard publish (runs prepublishOnly)
npm publish
# Scoped package (requires --access public)
npm publish --access public
# Dry run to verify contents
npm publish --dry-run
# View what would be published
npm pack --dry-run
Supply chain security via npm provenance:
npm publish --provenance --access public
Requires:
id-token: write permission in workflow{
"files": [
"build/",
"README.md",
"LICENSE"
]
}
| Include | Examples |
|---------|----------|
| Build output | build/, dist/ |
| Type definitions | *.d.ts (usually in build) |
| Documentation | README.md, LICENSE |
| Config scripts | User-facing setup scripts |
Already excluded by npm (no need to list):
node_modules/.git/.env**.logExplicitly exclude via .npmignore if needed:
src/
tests/
*.test.ts
.github/
.github/workflows/release-please.yml:
name: Release Please
on:
push:
branches: [main]
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
release-please:
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
steps:
- uses: google-github-actions/release-please-action@v4
id: release
with:
release-type: node
package-name: mypackage
publish:
needs: release-please
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: https://registry.npmjs.org
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run build
- run: npm publish --provenance --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
| Commit Type | Version Bump |
|-------------|--------------|
| feat: | Minor (1.x.0) |
| fix: | Patch (1.0.x) |
| feat!: or BREAKING CHANGE: | Major (x.0.0) |
| chore:, docs:, refactor: | No bump |
{
"scripts": {
"prepublishOnly": "bun run build"
}
}
# Type check
bun run tsc --noEmit
# Lint
bun run check
# Test
bun test
# Build
bun run build
# Verify tarball contents
npm pack --dry-run
Complete metadata for npm and GitHub:
{
"repository": {
"type": "git",
"url": "https://github.com/org/repo.git"
},
"homepage": "https://github.com/org/repo#readme",
"bugs": {
"url": "https://github.com/org/repo/issues"
},
"keywords": ["keyword1", "keyword2"],
"author": "Name <email>",
"license": "MIT"
}
| Context | Command |
|---------|---------|
| Preview tarball | npm pack --dry-run |
| Preview publish | npm publish --dry-run |
| Scoped publish | npm publish --access public |
| Provenance | npm publish --provenance --access public |
| Check outdated | npm outdated |
| View package | npm view @org/pkg |
| Flag | Description |
|------|-------------|
| --access public | Required for scoped packages |
| --provenance | Enable supply chain provenance |
| --dry-run | Preview without publishing |
| --tag <tag> | Publish with dist-tag (e.g., beta) |
| Script | When It Runs |
|--------|--------------|
| prepublish | Before pack and publish (deprecated) |
| prepublishOnly | Before publish only |
| prepack | Before pack and publish |
| postpack | After pack |
| postpublish | After publish |
| Variable | Purpose |
|----------|---------|
| NODE_AUTH_TOKEN | npm authentication |
| NPM_TOKEN | Alternative npm token name |
Scoped package 402 error:
# Add --access public for scoped packages
npm publish --access public
Missing files in tarball:
# Check what's included
npm pack --dry-run
# Verify files array in package.json
Binary not executable after install:
# Ensure shebang in entry point
#!/usr/bin/env node
# Ensure chmod in build script
chmod +x build/index.js
2FA publish (EOTP) is not automatable:
A non-interactive shell — a CI step, or an agent's Bash tool — cannot
supply a one-time password. npm publish fails with:
npm error code EOTP
npm error This operation requires a one-time password.
Two ways through:
npm publish --otp=<6-digit-code> (the code
comes from the publisher's authenticator app, not from npm on demand).NODE_AUTH_TOKEN / NPM_TOKEN. This is exactly why the release-please
workflow above publishes cleanly while a local npm publish prompts —
CI uses the token, the human uses the OTP.An agent that hits EOTP should hand the publish to the user or switch
the repo to an automation token — it cannot mint or supply one itself
(npm token create also requires a valid, OTP-satisfied session).
Fresh publish 404s for a few minutes (propagation lag):
Right after a first publish, public / unauthenticated reads can 404 while the package is genuinely live — the access record updates instantly but the public metadata CDN lags. Don't conclude the publish failed. Confirm with authenticated, version-pinned checks:
npm access list packages # shows it under your account immediately
npm view @org/pkg@<version> _id # version-pinned, authed — resolves once live
npm pack @org/pkg@<version> # actually fetches the tarball (definitive)
Signature of propagation lag (not a private/failed publish): a public
404 plus npm access list packages shows the package plus an
authed npm pack @org/pkg@<version> succeeds. (npm config get //registry.npmjs.org/:_authToken often returns empty even when logged
in, so a hand-rolled curl with that "token" is an unauthenticated probe
— use the npm CLI itself, which reads auth correctly, to test.)
development
Debug HTTP APIs: trace requests, inspect headers. Use when a request fails: check status first.
documentation
Render architecture diagrams from text sources. Use when documenting system topology.
tools
Inspect JSON payloads and extract nested fields. Use when parsing API responses.
tools
--- name: no-description allowed-tools: Read --- # No Description This skill has no description and must be dropped with a warning.