npm-package

npm Package Development (Bun-First)

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "npm-package" with this command: npx skills add jwynia/agent-skills/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

Concern Tool Why

Runtime / package manager Bun Fast install, run, transpile

Bundler Bunup Bun-native, dual output, .d.ts generation

Type declarations Bunup (via tsc) Integrated with build

TypeScript module: "nodenext" , strict: true

  • extras Maximum correctness for published code

Formatting + basic linting Biome v2 10-25x faster than ESLint, single tool

Type-aware linting ESLint + typescript-eslint 40+ type-aware rules Biome can't do

Testing Vitest Test isolation, mature mocking, coverage

Versioning Changesets File-based, explicit, monorepo-ready

Publishing npm publish --provenance

Trusted Publishing / OIDC

Scaffolding a New Package

Run the scaffold script to generate a complete project:

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:

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

Always use "type": "module" in package.json. ESM-only is the correct default. require(esm) works in all supported Node.js versions.

Always use exports field, not main . main is legacy. exports gives precise control over what consumers can access.

types must be the first condition in every exports block. TypeScript silently fails to resolve types if it isn't.

Always export "./package.json": "./package.json" . Many tools need access to the package.json and exports encapsulates completely.

Use files: ["dist"] in package.json. Whitelist approach prevents shipping secrets. Never use .npmignore .

Run npm pack --dry-run before every publish. Verify the tarball contains exactly what you intend.

TypeScript

Use module: "nodenext" for published packages. Not "bundler" . Code satisfying nodenext works everywhere; the reverse is not true.

strict: true is non-negotiable. Without it, your .d.ts files can contain types that error for consumers using strict mode.

Enable noUncheckedIndexedAccess . Catches real runtime bugs from unguarded array/object access.

Ship declarationMap: true . Enables "Go to Definition" to reach original source for consumers.

Do not use path aliases (paths ) in published packages. tsc does not rewrite them in emitted code. Consumers can't resolve them.

Code Quality

any is banned. Use unknown and narrow. Suppress with // biome-ignore suspicious/noExplicitAny: <reason> only when genuinely unavoidable, and always include the reason.

Prefer named exports over default exports. Default exports behave differently across CJS/ESM boundaries.

Always use import type for type-only imports. Enforced by both verbatimModuleSyntax and Biome's useImportType rule.

Build

Build with Bunup using format: ['esm'] (or ['esm', 'cjs'] for dual). Bunup handles .d.ts generation, external detection, and correct file extensions.

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

Use Vitest, not bun:test. bun:test lacks test isolation — module mocks leak between files. Vitest runs each test file in its own worker.

Set coverage thresholds (branches, functions, lines, statements all ≥ 80%). Enforced in vitest.config.ts.

Development Workflow

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:

  • Add the source file: src/utils.ts

  • Add to bunup.config.ts entry: entry: ['src/index.ts', 'src/utils.ts']

  • Add to package.json exports:

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

  • Update bunup.config.ts: format: ['esm', 'cjs']

  • Update package.json exports to include module-sync , import , and require conditions

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

typescript-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
163-jwynia
Coding

code-review

No summary provided by upstream source.

Repository SourceNeeds Review
161-jwynia
Coding

pwa-development

No summary provided by upstream source.

Repository SourceNeeds Review
106-jwynia
Coding

devcontainer

No summary provided by upstream source.

Repository SourceNeeds Review