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
Sourcejwynia/agent-skills
Added on
NPX Install
npx skill4agent add jwynia/agent-skills npm-packageTags
Translated version includes tags in frontmatterSKILL.md Content
View Translation Comparison →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 skill)
npx-cli - 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 | | 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 | | 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 MITOptions:
- — Generate dual CJS/ESM output (default: ESM-only)
--dual - — Skip ESLint, use Biome only
--no-eslint
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-eslintProject 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
└── LICENSECritical 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 — map configuration, dual package hazard,
exports, common mistakesmodule-sync - reference/strict-typescript.md — tsconfig rationale, Biome rules, ESLint type-aware rules, Vitest config
- reference/publishing-workflow.md — Changesets, field, Trusted Publishing, CI pipeline
files
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 usein package.json. ESM-only is the correct default.
"type": "module"works in all supported Node.js versions.require(esm) -
Always usefield, not
exports.mainis legacy.maingives precise control over what consumers can access.exports -
must be the first condition in every exports block. TypeScript silently fails to resolve types if it isn't.
types -
Always export. Many tools need access to the package.json and
"./package.json": "./package.json"encapsulates completely.exports -
Usein package.json. Whitelist approach prevents shipping secrets. Never use
files: ["dist"]..npmignore -
Runbefore every publish. Verify the tarball contains exactly what you intend.
npm pack --dry-run
TypeScript
-
Usefor published packages. Not
module: "nodenext". Code satisfying nodenext works everywhere; the reverse is not true."bundler" -
is non-negotiable. Without it, your .d.ts files can contain types that error for consumers using strict mode.
strict: true -
Enable. Catches real runtime bugs from unguarded array/object access.
noUncheckedIndexedAccess -
Ship. Enables "Go to Definition" to reach original source for consumers.
declarationMap: true -
Do not use path aliases () in published packages. tsc does not rewrite them in emitted code. Consumers can't resolve them.
paths
Code Quality
-
is banned. Use
anyand narrow. Suppress withunknownonly when genuinely unavoidable, and always include the reason.// biome-ignore suspicious/noExplicitAny: <reason> -
Prefer named exports over default exports. Default exports behave differently across CJS/ESM boundaries.
-
Always usefor type-only imports. Enforced by both
import typeand Biome'sverbatimModuleSyntaxrule.useImportType
Build
-
Build with Bunup using(or
format: ['esm']for dual). Bunup handles .d.ts generation, external detection, and correct file extensions.['esm', 'cjs'] -
Setto
engines.nodein package.json. This documents the minimum supported Node.js version (first LTS with stable>=20.19.0).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
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 --provenanceAdding 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:
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:
- Update bunup.config.ts:
format: ['esm', 'cjs'] - Update package.json exports to include ,
module-sync, andimportconditionsrequire - See reference/esm-cjs-guide.md for the exact exports map structure
Bun-Specific Gotchas
- does not generate .d.ts files. Use Bunup (which delegates to tsc) or run
bun buildseparately.tsc --emitDeclarationOnly - CJS output is experimental. Always use
bun buildfor npm-publishable CJS.target: "node"produces Bun-specific wrappers.target: "bun" - does not downlevel syntax. Modern ES2022+ syntax ships as-is. If targeting older runtimes, additional transpilation is needed.
bun build - does not support
bun publish. Use--provenancefor provenance signing.npm publish - uses
bun publish, notNPM_CONFIG_TOKEN. CI pipelines may need adjustment.NODE_AUTH_TOKEN