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-cliskill) - 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 —
exportsmap 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,
filesfield, 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
exportsfield, notmain.mainis legacy.exportsgives precise control over what consumers can access. -
typesmust 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 andexportsencapsulates completely. -
Use
files: ["dist"]in package.json. Whitelist approach prevents shipping secrets. Never use.npmignore. -
Run
npm pack --dry-runbefore 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: trueis 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
-
anyis banned. Useunknownand 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 typefor type-only imports. Enforced by bothverbatimModuleSyntaxand Biome'suseImportTyperule.
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.nodeto>=20.19.0in package.json. This documents the minimum supported Node.js version (first LTS with stablerequire(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, andrequireconditions - See reference/esm-cjs-guide.md for the exact exports map structure
Bun-Specific Gotchas
bun builddoes not generate .d.ts files. Use Bunup (which delegates to tsc) or runtsc --emitDeclarationOnlyseparately.bun buildCJS output is experimental. Always usetarget: "node"for npm-publishable CJS.target: "bun"produces Bun-specific wrappers.bun builddoes not downlevel syntax. Modern ES2022+ syntax ships as-is. If targeting older runtimes, additional transpilation is needed.bun publishdoes not support--provenance. Usenpm publishfor provenance signing.bun publishusesNPM_CONFIG_TOKEN, notNODE_AUTH_TOKEN. CI pipelines may need adjustment.
More from jwynia/agent-skills
frontend-design
Create distinctive, production-grade frontend interfaces with high design quality. Provides analysis tools for auditing existing designs and generation tools for creating color palettes, typography systems, design tokens, and component templates. Supports React, Vue, Svelte, and vanilla HTML/CSS. Use when building web components, pages, or applications. Keywords: design, UI, frontend, CSS, components, palette, typography, tokens, accessibility.
2.0Krequirements-analysis
Diagnose requirements problems and guide discovery of real needs and constraints
1.8Kgodot-best-practices
Guide AI agents through Godot 4.x GDScript coding best practices including scene organization, signals, resources, state machines, and performance optimization. This skill should be used when generating GDScript code, creating Godot scenes, designing game architecture, implementing state machines, object pooling, save/load systems, or when the user asks about Godot patterns, node structure, or GDScript standards. Keywords: godot, gdscript, game development, signals, resources, scenes, nodes, state machine, object pooling, save system, autoload, export, type hints.
1.4Kpresentation-design
Design and evaluate presentations that communicate effectively. Use when designing a presentation, creating slides, getting presentation feedback, structuring a talk, or reviewing slides. Keywords: presentation, slides, talk, PowerPoint, Keynote, reveal.js.
1.3Kweb-search-tavily
Search the web using Tavily API for high-quality, AI-optimized results with advanced filtering options. Use when you need structured search results, domain filtering, relevance scores, or AI-generated answer summaries. Requires TAVILY_API_KEY. Keywords: tavily, advanced search, filtered search, domain filtering, relevance scoring.
1.0Kstory-coach
Act as an assistive writing coach who guides but never writes for the user. Use when helping someone develop their own writing through questions, diagnosis, and frameworks. Critical constraint - never generate story prose, dialogue, or narrative content. Instead ask questions, identify issues, suggest approaches, and let the writer write.
702