skills/bootstrap-ts-oss/SKILL.md
Bootstrap a new TypeScript open-source npm package with Bun, tsup, Biome, Husky, GitHub Actions CI/CD, and dual ESM/CJS publishing. Use when the user wants to create, scaffold, initialize, set up, or bootstrap a new TypeScript library, npm package, or open-source project.
npx skillsauth add miketromba/skills bootstrap-ts-ossInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Scaffold a production-ready TypeScript npm package using Mike's stack: Bun runtime, tsup bundler, Biome linter/formatter, Husky git hooks, and GitHub Actions for CI + npm publishing with provenance.
bun init first so scaffolding stays current, then overlay configs on top.Before starting, if the user hasn't already specified:
mkdir <project-name> && cd <project-name>
bun init -y
Inspect what bun init produced — it scaffolds package.json, tsconfig.json, an entry point, and possibly other files. These evolve over time as Bun updates, so do not blindly overwrite them. The steps below describe which fields/settings to ensure are present; always start from what bun init gave you and adjust from there.
If bun init places an entry file at the root (e.g. index.ts), move it to src/index.ts.
<project-name>/
├── .github/
│ └── workflows/
│ ├── ci.yml
│ └── publish.yml
├── .husky/
│ └── pre-commit
├── src/
│ ├── index.ts # Main entry point
│ └── index.test.ts # Tests (colocated)
├── .gitignore
├── AGENTS.md
├── biome.json
├── LICENSE
├── package.json
├── README.md
├── tsconfig.json
└── tsup.config.ts
bun add -d typescript tsup @biomejs/biome husky @types/bun
Start from whatever bun init generated and ensure the following fields are set. Don't drop fields that bun init added unless they conflict.
Fields to set/add (adapt name, description, repository, etc. to the specific project):
{
"license": "MIT",
"author": "miketromba",
"repository": {
"type": "git",
"url": "https://github.com/miketromba/<package-name>.git"
},
"homepage": "https://github.com/miketromba/<package-name>",
"bugs": {
"url": "https://github.com/miketromba/<package-name>/issues"
},
"keywords": [],
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"files": ["dist", "README.md"],
"scripts": {
"build": "tsup",
"lint": "biome check .",
"lint:fix": "biome check --write .",
"test": "bun test",
"prepare": "husky"
}
}
Key points:
"type": "module" — ESM-firstexports map with separate import/require paths and their types conditions"files" controls what ships to npm — keep it minimal"prepare": "husky" — auto-installs hooks on bun installbun add -d step — do not hardcode versionsbun init entry point field (e.g. "module": "index.ts") that points to a non-dist pathbun init generates a tsconfig.json. Keep its defaults and ensure these settings are present (add or override as needed):
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"declaration": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"rootDir": "src",
"types": ["bun-types"]
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
If bun init adds new useful fields in the future, keep them. The critical settings above must be present for dual-format builds to work.
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
clean: true,
target: 'es2020',
outDir: 'dist'
})
This produces dist/index.js (ESM), dist/index.cjs (CJS), dist/index.d.ts, and dist/index.d.cts.
{
"$schema": "https://biomejs.dev/schemas/2.4.2/schema.json",
"files": {
"includes": ["**", "!dist"]
},
"formatter": {
"enabled": true,
"indentStyle": "tab",
"indentWidth": 4,
"lineWidth": 80
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded",
"trailingCommas": "none",
"arrowParentheses": "asNeeded"
}
}
}
Style: tabs, single quotes, no semicolons, no trailing commas, arrow parens only when needed. Add additional paths to the !dist exclude pattern if the project generates files (e.g. "!data").
bun init may create a .gitignore. If so, keep its contents and ensure these entries are present:
# dependencies
node_modules/
# output
dist/
# env
.env
.env.*
# caches
*.tsbuildinfo
.cache
# OS
.DS_Store
# IDE
.idea/
.vscode/
bunx husky init
Then overwrite .husky/pre-commit with:
bun run lint
bun test
This enforces that every commit passes linting and tests.
.github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Install dependencies
run: bun install
- name: Lint
run: bun run lint
- name: Build
run: bun run build
- name: Test
run: bun test
.github/workflows/publish.yml:
name: Publish to NPM
on:
push:
tags:
- "v*"
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Bun
uses: oven-sh/setup-bun@v2
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "24"
- name: Install dependencies
run: bun install
- name: Lint
run: bun run lint
- name: Build
run: bun run build
- name: Test
run: bun test
- name: Publish to NPM
run: npm publish --access public
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
The id-token: write permission enables npm provenance. Node is needed alongside Bun because npm publish handles registry auth and provenance attestation.
Trusted publishing setup: The first publish of a new package must be done manually via npm publish --access public from the local machine. After that initial publish, go to the package's settings on npmjs.com, navigate to Publishing access and configure Trusted Publishing (OIDC) for the GitHub repository and the publish.yml workflow. Once configured, no NPM_TOKEN secret is needed — the workflow authenticates automatically via GitHub's OIDC token.
Create a standard MIT license file with the copyright holder Michael Tromba and the current year.
Write an AGENTS.md tailored to the project. Follow this structure:
# Agents
- **Runtime/tooling:** Bun (not Node)
- **Build:** `bun run build` — runs tsup
- **Lint:** `bun run lint` (Biome)
- **Test:** `bun test`
- **Pre-commit hook:** Husky runs lint + test automatically
## Shipping
When the user says "ship", that means: **push to remote AND publish to npm**. Do both.
1. `git push` — push commits to the remote
2. `npm version patch` (or `minor` / `major` as appropriate) — bumps version and creates a tag
3. `git push && git push --tags` — pushes the version commit and tag
The `v*` tag triggers `.github/workflows/publish.yml` which lints, builds, tests, publishes to npm with provenance, and creates a GitHub Release.
**Never skip the push + version + tag steps when asked to ship.**
Add project-specific sections as needed (source of truth, conventions, etc.).
Write a README appropriate to the package. Sections to consider (not all required — use judgment based on the package's subject matter):
src/index.ts — the main entry point with the package's public API.
src/index.test.ts — colocated tests using bun:test:
import { describe, expect, test } from 'bun:test'
bun run build
bun run lint
bun test
If the user hasn't specified a repo name, suggest one based on the package name and ask for confirmation.
gh repo create miketromba/<repo-name> --public --source=. --remote=origin --push
This initializes git, creates the remote repo, and pushes in one step. If git is already initialized or the repo already exists, fall back to manual steps as needed.
When the user says "ship":
git pushnpm version patch (or minor / major)git push && git push --tagsThe v* tag triggers the publish workflow automatically.
tools
Vercel Sandbox documentation and guidance for running untrusted code in isolated environments. Use when working with Vercel Sandbox - a compute primitive for safely executing AI-generated code, user-submitted scripts, or developer experiments in Firecracker microVMs. Triggers on questions about Vercel Sandbox SDK, sandbox CLI, @vercel/sandbox, Sandbox.create, snapshots, persistent sandboxes, sandbox authentication, sandbox pricing, sandbox system specs, microVMs, code execution isolation, or ANY Vercel Sandbox-related development tasks.
development
Vercel for Platforms documentation and guidance for building multi-tenant and multi-project applications. Use when working with Vercel Platforms - building SaaS apps serving multiple tenants with custom domains, AI coding platforms, or any platform deploying multiple projects. Triggers on questions about multi-tenant architecture, multi-project platforms, wildcard domains, custom domains on Vercel, tenant middleware, Vercel SDK for platforms, platform elements, deploy actions, vibe coding platforms, or ANY Vercel Platforms-related development tasks.
development
TanStack Query (React Query) documentation and guidance. Use when working with TanStack Query - a powerful async state management library for data fetching, caching, synchronization, and server state management. Triggers on questions about React Query, TanStack Query, useQuery, useMutation, query invalidation, caching strategies, optimistic updates, infinite queries, prefetching, or ANY TanStack Query-related development tasks.
tools
Supabase backend-as-a-service platform documentation and guidance. Use when working with Supabase - an open-source Firebase alternative providing Postgres database, authentication, real-time subscriptions, edge functions, storage, and vector embeddings. Triggers on questions about Supabase setup, database, auth, RLS, edge functions, storage, realtime, pgvector, migrations, CLI, self-hosting, or ANY Supabase-related development tasks.