npm-package

Original🇺🇸 English
Translated
4 scriptsChecked / no sensitive code detected

Build and publish npm packages using Bun as the primary toolchain with npm-compatible output. Use when the user wants to create a new npm library, set up a TypeScript package for publishing, configure build/test/lint tooling for a package, fix CJS/ESM interop issues, or publish to npm. Covers scaffolding, strict TypeScript, Biome + ESLint linting, Vitest testing, Bunup bundling, and publishing workflows. Keywords: npm, package, library, publish, bun, bunup, esm, cjs, exports, typescript, biome, vitest, changesets.

9installs
Added on

NPX Install

npx skill4agent add jwynia/agent-skills npm-package

npm Package Development (Bun-First)

Build and publish npm packages using Bun as the primary runtime and toolchain, producing output that works everywhere npm packages are consumed.

When to Use This Skill

Use when:
  • Creating a new npm library package from scratch
  • Setting up build/test/lint tooling for an existing package
  • Fixing CJS/ESM interop, exports map, or TypeScript declaration issues
  • Publishing a package to npm
  • Reviewing or improving package configuration
Do NOT use when:
  • Building an npx-executable CLI tool (use the
    npx-cli
    skill)
  • Building an application (not a published package)
  • Working in a monorepo (this skill targets single-package repos)

Toolchain

ConcernToolWhy
Runtime / package managerBunFast install, run, transpile
BundlerBunupBun-native, dual output, .d.ts generation
Type declarationsBunup (via tsc)Integrated with build
TypeScript
module: "nodenext"
,
strict: true
+ extras
Maximum correctness for published code
Formatting + basic lintingBiome v210-25x faster than ESLint, single tool
Type-aware lintingESLint + typescript-eslint40+ type-aware rules Biome can't do
TestingVitestTest isolation, mature mocking, coverage
VersioningChangesetsFile-based, explicit, monorepo-ready
Publishing
npm publish --provenance
Trusted Publishing / OIDC

Scaffolding a New Package

Run the scaffold script to generate a complete project:
bash
bun run <skill-path>/scripts/scaffold.ts ./my-package \
  --name my-package \
  --description "What this package does" \
  --author "Your Name" \
  --license MIT
Options:
  • --dual
    — Generate dual CJS/ESM output (default: ESM-only)
  • --no-eslint
    — Skip ESLint, use Biome only
Then install dependencies:
bash
cd my-package
bun install
bun add -d bunup typescript vitest @vitest/coverage-v8 @biomejs/biome @changesets/cli
bun add -d eslint typescript-eslint  # unless --no-eslint

Project Structure

my-package/
├── src/
│   ├── index.ts            # Package entry point — all public API exports here
│   └── index.test.ts       # Tests co-located with source
├── dist/                   # Built output (gitignored, included in published tarball)
├── .changeset/
│   └── config.json
├── package.json
├── tsconfig.json
├── bunup.config.ts
├── biome.json
├── eslint.config.ts        # Type-aware rules only
├── vitest.config.ts
├── .gitignore
├── README.md
└── LICENSE

Critical Configuration Details

Read these reference docs before modifying any configuration. They contain the reasoning behind each decision and the sharp edges that cause subtle breakage:
  • reference/esm-cjs-guide.md
    exports
    map configuration, dual package hazard,
    module-sync
    , common mistakes
  • reference/strict-typescript.md — tsconfig rationale, Biome rules, ESLint type-aware rules, Vitest config
  • reference/publishing-workflow.md — Changesets,
    files
    field, Trusted Publishing, CI pipeline

Key Rules (Non-Negotiable)

These are the rules that, when violated, cause the most common and painful bugs in published packages. Follow these without exception.

Package Configuration

  1. Always use
    "type": "module"
    in package.json.
    ESM-only is the correct default.
    require(esm)
    works in all supported Node.js versions.
  2. Always use
    exports
    field, not
    main
    .
    main
    is legacy.
    exports
    gives precise control over what consumers can access.
  3. types
    must be the first condition
    in every exports block. TypeScript silently fails to resolve types if it isn't.
  4. Always export
    "./package.json": "./package.json"
    .
    Many tools need access to the package.json and
    exports
    encapsulates completely.
  5. Use
    files: ["dist"]
    in package.json.
    Whitelist approach prevents shipping secrets. Never use
    .npmignore
    .
  6. Run
    npm pack --dry-run
    before every publish.
    Verify the tarball contains exactly what you intend.

TypeScript

  1. Use
    module: "nodenext"
    for published packages.
    Not
    "bundler"
    . Code satisfying nodenext works everywhere; the reverse is not true.
  2. strict: true
    is non-negotiable.
    Without it, your .d.ts files can contain types that error for consumers using strict mode.
  3. Enable
    noUncheckedIndexedAccess
    .
    Catches real runtime bugs from unguarded array/object access.
  4. Ship
    declarationMap: true
    .
    Enables "Go to Definition" to reach original source for consumers.
  5. Do not use path aliases (
    paths
    ) in published packages.
    tsc does not rewrite them in emitted code. Consumers can't resolve them.

Code Quality

  1. any
    is banned.
    Use
    unknown
    and narrow. Suppress with
    // biome-ignore suspicious/noExplicitAny: <reason>
    only when genuinely unavoidable, and always include the reason.
  2. Prefer named exports over default exports. Default exports behave differently across CJS/ESM boundaries.
  3. Always use
    import type
    for type-only imports.
    Enforced by both
    verbatimModuleSyntax
    and Biome's
    useImportType
    rule.

Build

  1. Build with Bunup using
    format: ['esm']
    (or
    ['esm', 'cjs']
    for dual). Bunup handles .d.ts generation, external detection, and correct file extensions.
  2. Set
    engines.node
    to
    >=20.19.0
    in package.json. This documents the minimum supported Node.js version (first LTS with stable
    require(esm)
    ).

Testing

  1. Use Vitest, not bun:test. bun:test lacks test isolation — module mocks leak between files. Vitest runs each test file in its own worker.
  2. Set coverage thresholds (branches, functions, lines, statements all ≥ 80%). Enforced in vitest.config.ts.

Development Workflow

bash
# Write code and tests
bun run test:watch    # Vitest watch mode

# Check everything
bun run lint          # Biome + ESLint
bun run typecheck     # tsc --noEmit
bun run test          # Vitest run

# Build
bun run build         # Bunup → dist/

# Prepare release
bunx changeset        # Create changeset describing changes
bunx changeset version  # Bump version, update CHANGELOG

# Publish
bun run release       # Build + npm publish --provenance

Adding Subpath Exports

When the package needs to expose multiple entry points:
  1. Add the source file:
    src/utils.ts
  2. Add to bunup.config.ts entry:
    entry: ['src/index.ts', 'src/utils.ts']
  3. Add to package.json exports:
json
{
  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    },
    "./utils": {
      "types": "./dist/utils.d.ts",
      "default": "./dist/utils.js"
    },
    "./package.json": "./package.json"
  }
}
Reminder: Adding or removing export paths is a semver-major change.

Switching to Dual CJS/ESM Output

If consumers require CJS support for Node.js < 20.19.0:
  1. Update bunup.config.ts:
    format: ['esm', 'cjs']
  2. Update package.json exports to include
    module-sync
    ,
    import
    , and
    require
    conditions
  3. See reference/esm-cjs-guide.md for the exact exports map structure

Bun-Specific Gotchas

  • bun build
    does not generate .d.ts files.
    Use Bunup (which delegates to tsc) or run
    tsc --emitDeclarationOnly
    separately.
  • bun build
    CJS output is experimental.
    Always use
    target: "node"
    for npm-publishable CJS.
    target: "bun"
    produces Bun-specific wrappers.
  • bun build
    does not downlevel syntax.
    Modern ES2022+ syntax ships as-is. If targeting older runtimes, additional transpilation is needed.
  • bun publish
    does not support
    --provenance
    .
    Use
    npm publish
    for provenance signing.
  • bun publish
    uses
    NPM_CONFIG_TOKEN
    , not
    NODE_AUTH_TOKEN
    . CI pipelines may need adjustment.