bun-publishing

v2026.09.24

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.

GitHub
Install command
npx skhub add laurigates/bun-publishing
Markdown
SKILL.md

Bun npm Publishing

When to Use This Skill

ScenarioUse this skillAlternative
Configuring package.json for npm publishingYesN/A
Publishing a package to npm registryYesN/A
Setting up CLI tool packaging with binYesN/A
Configuring provenance signingYesN/A
Setting up release-please automationYesN/A
Validating tarball contents before publishYesN/A
Installing or updating dependenciesNo - use bun-package-managerbun-add for quick additions
Building/bundling before publishNo - use bun-developmentbun-build for quick builds

Core Expertise

Publishing npm packages built with Bun:

  • Package.json configuration for npm registry
  • CLI tool packaging with executable binaries
  • Provenance signing for supply chain security
  • Release automation with release-please

Package.json Configuration

Essential Publishing Fields

{
  "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 Reference

FieldPurpose
publishConfig.accesspublic required for scoped packages
filesWhitelist of files/dirs to include in tarball
mainEntry point for CommonJS/ES import
typemodule for ESM, commonjs for CJS
enginesRuntime version requirements
prepublishOnlyRuns before npm publish

Scoped Packages

Scoped packages (@org/name) require explicit public access:

{
  "name": "@myorg/mypackage",
  "publishConfig": {
    "access": "public"
  }
}

Or use the CLI flag:

npm publish --access public

CLI Tool Packaging

Binary Entry Configuration

{
  "bin": {
    "mycli": "build/index.js"
  }
}

For single-command packages:

{
  "bin": "build/index.js"
}

Executable Setup

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"
  }
}

Makefile Integration

build-prod:
	rm -rf build
	tsc --declaration --sourceMap
	chmod +x build/index.js

publish: build-prod
	npm publish --access public

Publishing Commands

Manual Publishing

# 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

With Provenance

Supply chain security via npm provenance:

npm publish --provenance --access public

Requires:

  • CI environment (GitHub Actions, GitLab CI)
  • id-token: write permission in workflow
  • npm registry-url configured

Files Whitelist

Recommended Pattern

{
  "files": [
    "build/",
    "README.md",
    "LICENSE"
  ]
}

What to Include

IncludeExamples
Build outputbuild/, dist/
Type definitions*.d.ts (usually in build)
DocumentationREADME.md, LICENSE
Config scriptsUser-facing setup scripts

What to Exclude

Already excluded by npm (no need to list):

  • node_modules/
  • .git/
  • .env*
  • *.log

Explicitly exclude via .npmignore if needed:

src/
tests/
*.test.ts
.github/

Release Automation

Release-Please Integration

.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 }}

Conventional Commits for Versioning

Commit TypeVersion Bump
feat:Minor (1.x.0)
fix:Patch (1.0.x)
feat!: or BREAKING CHANGE:Major (x.0.0)
chore:, docs:, refactor:No bump

Pre-publish Validation

Build Hook

{
  "scripts": {
    "prepublishOnly": "bun run build"
  }
}

Full Validation

# Type check
bun run tsc --noEmit

# Lint
bun run check

# Test
bun test

# Build
bun run build

# Verify tarball contents
npm pack --dry-run

Repository Metadata

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"
}

Agentic Optimizations

ContextCommand
Preview tarballnpm pack --dry-run
Preview publishnpm publish --dry-run
Scoped publishnpm publish --access public
Provenancenpm publish --provenance --access public
Check outdatednpm outdated
View packagenpm view @org/pkg

Quick Reference

Publishing Flags

FlagDescription
--access publicRequired for scoped packages
--provenanceEnable supply chain provenance
--dry-runPreview without publishing
--tag <tag>Publish with dist-tag (e.g., beta)

Package.json Scripts

ScriptWhen It Runs
prepublishBefore pack and publish (deprecated)
prepublishOnlyBefore publish only
prepackBefore pack and publish
postpackAfter pack
postpublishAfter publish

CI Environment Variables

VariablePurpose
NODE_AUTH_TOKENnpm authentication
NPM_TOKENAlternative npm token name

Common Issues

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:

  • Human, interactive: npm publish --otp=<6-digit-code> (the code comes from the publisher's authenticator app, not from npm on demand).
  • Automation token (CI / unattended): a Granular/Automation token (npmjs.com → Access Tokens) bypasses 2FA at publish time. Put it in 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.)

Discovery
Tags

No tags published for this skill.

Version
Latest version metadata

Version

v2026.09.24

Published

Sep 24, 2026

Category

Uncategorized

License

MIT

Source path

typescript-plugin/skills/bun-publishing

Default branch

main

Latest commit

1668324

Tree SHA

b2d4cc3