@hbmartin/create-ts-lib
v1.1.0
Published
Opinionated TypeScript library scaffolder.
Maintainers
Readme
@hbmartin/create-ts-lib
An opinionated initializer for TypeScript libraries.
It scaffolds a Node 22+ ESM library with TypeScript, strict Biome linting, Oxc lint/format tooling, dependency-cruiser, Semgrep policy checks, Vitest coverage, Lefthook, publint, optional GitHub Actions CI, optional Codecov upload, and an optional CLI entry point.
Contents
- Why this tool
- Requirements
- Usage
- CLI Options
- Prompt Flow
- Generated Project Layout
- Generated Defaults
- Post-Scaffold Behavior
- Next Steps After Scaffolding
- Programmatic API
- Local Development
- Contributing
- License
Why this tool
Setting up a publishable TypeScript library means making the same dozen decisions every time: module format, build output, lint and format tooling, test runner and coverage, git hooks, and packaging validation. create-ts-lib makes those decisions for you with a single, modern, opinionated stack:
- ESM-only, Node 22+ — no dual-format build complexity.
- Biome + Oxc for fast linting and formatting instead of ESLint + Prettier.
- Vitest with v8 coverage and enforced thresholds out of the box.
- dependency-cruiser + Semgrep for lightweight architecture and security policy checks.
- Lefthook for lightweight pre-commit hooks.
- publint + are-the-types-wrong wired in so your published package is correct before you ship it.
If you'd rather not maintain this boilerplate by hand — or you want every library you publish to share one consistent setup — this gets you from zero to a building, testing, lint-clean package in one command.
Requirements
- Node.js 22 or newer (the generator and the generated project both target Node 22+).
- A package manager: pnpm (recommended), npm, or yarn.
Usage
Recommended (pnpm):
pnpm create @hbmartin/ts-lib my-libnpm create @hbmartin/ts-lib my-lib
npx @hbmartin/create-ts-lib my-libBy default the generator asks interactive prompts. To accept all detected/default answers without prompting:
npx @hbmartin/create-ts-lib my-lib --yesTo preview the target options and file list without writing anything:
npx @hbmartin/create-ts-lib my-lib --dry-runBy default the generator refuses to write into a non-empty target directory. Use --force only when you intentionally want generated files written into an existing directory:
npx @hbmartin/create-ts-lib my-lib --forceCLI Options
create-ts-lib [directory] [options]| Option | Description |
| ----------------- | ----------------------------------------------- |
| [directory] | Target directory / default project name |
| --yes, -y | Use detected/default answers without prompting |
| --dry-run | Print the scaffold plan without writing files |
| --force | Allow writing into a non-empty target directory |
| --help, -h | Print usage and exit |
| --version, -v | Print the CLI version and exit |
Prompt Flow
The generator asks for:
| Prompt | Default | Notes |
| ------------------------ | ------------------------------------------------ | ----------------------------------------------------- |
| Project name | directory arg or my-lib | Used as the package name; must be npm-name compatible |
| Description | empty string | Written to package.json |
| Author | git config user.name + git config user.email | Combined as Name <email> when available |
| License | Apache-2.0 | Apache-2.0, MIT, ISC, UNLICENSED |
| GitHub repo URL | detected from git remote origin | Normalizes SSH and git+https://github.com/ remotes |
| Include Codecov? | yes | Adds a Codecov upload step to pnpm generated CI |
| Include CLI entry point? | no | Adds bin, meow, and source/cli.ts |
| Package manager | pnpm | pnpm, npm, or yarn; generated CI is pnpm-only |
--yes uses those defaults directly. If @inquirer/prompts cannot load, the CLI prints a warning and falls back to a basic readline prompt implementation.
Generated Project Layout
Typical output:
my-lib/
├── source/
│ ├── index.ts
│ ├── cli.ts # optional
│ ├── types/
│ │ └── index.ts
│ └── utils/
│ └── formatting.ts
├── scripts/
│ └── security-lint.mjs
├── test/
│ └── utils/
│ └── formatting.test.ts
├── .github/
│ └── workflows/
│ ├── ci.yml # pnpm + GitHub repo URL only
│ └── release.yml # pnpm + GitHub repo URL only
├── AGENTS.md
├── .dependency-cruiser.cjs
├── .gitignore
├── biome.jsonc
├── lefthook.yml
├── pnpm-workspace.yaml # pnpm only
├── package.json
├── README.md
├── semgrep.yml
├── tsconfig.json
├── tsconfig.build.json
├── vitest.config.ts
└── LICENSEGitHub CI and release workflows are generated only for pnpm projects when a GitHub repo URL is provided. The CLI file and bin mapping are generated only when CLI support is enabled. Pnpm projects include pnpm-workspace.yaml so Lefthook's install script is allowed explicitly.
Generated Defaults
Generated packages include:
version: "0.1.0"type: "module"exportspointing atdist/index.jsanddist/index.d.tsengines.node: ">=22"@sindresorhus/tsconfig- strict Biome linting with formatting disabled
oxlintandoxfmt- dependency-cruiser architecture checks
- Semgrep policy checks
- Vitest with v8 coverage and 80% thresholds
- Lefthook with a lint-only
pre-commithook zodas a default runtime dependencymeowonly when CLI support is enabled@arethetypeswrong/cliandpublintrelease checkscheck,deps:lint,security:lint,prepublishOnly,publint,types:lint,verify:artifacts,verify:package,size:report, andrelease:checkscriptsAGENTS.mdwith opinionated guidance for Codex and other coding agentsApache-2.0license by default
Release-please is not generated. Pnpm projects with a GitHub repo URL include a release workflow that publishes to npm when a GitHub release is published.
Post-Scaffold Behavior
Before writing files, the generator rejects non-empty target directories unless --force is provided. After writing files, it performs:
git initwhen the target directory is not already inside a Git repository<package-manager> install<package-manager> run build<package-manager> run test
The CLI prints a summary before writing and shows progress during post-scaffold setup. In non-TTY or CI environments it uses plain step logs instead of spinners.
Next Steps After Scaffolding
Once the generator finishes, your library already builds, lints, and tests. To publish it:
cd my-lib
# write your code in source/, then:
pnpm run check # lint + dep/security checks + typecheck + coverage
pnpm run release:check # package validation + npm publish dry run
pnpm run size:report # packed package size report
pnpm version patch # or minor / major
git push --follow-tagsFor pnpm projects with a GitHub repo URL, publishing a GitHub release for the pushed tag runs the generated npm publish workflow. Prereleases publish with the next tag; normal releases publish with latest.
prepublishOnly-style validation (verify:artifacts, publint, are-the-types-wrong,
and a dry-run publish) is wired into the generated package so packaging problems surface
before you ship.
pnpm run security:lint prefers a semgrep binary on PATH and otherwise runs
the pinned uvx [email protected] scan. Install Semgrep directly or install uv
if both commands are missing locally.
Programmatic API
The package exports:
scaffoldProject(config, options)ScaffoldConfigScaffoldOptionsScaffoldProgress
scaffoldProject writes the generated project, rejects invalid package names, refuses non-empty target directories unless force: true is set, and can optionally receive progress callbacks for post-scaffold steps.
import { scaffoldProject, type ScaffoldConfig } from "@hbmartin/create-ts-lib";
const config: ScaffoldConfig = {
projectName: "my-lib",
description: "An example library",
author: "Jane Doe <[email protected]>",
license: "Apache-2.0",
githubRepoUrl: "https://github.com/jane/my-lib",
packageManager: "pnpm",
includeCodecov: true,
includeCli: false,
};
await scaffoldProject(config, {
targetDirectory: "./my-lib",
force: false,
postScaffold: true, // run git init + install + build + test
progress: {
start: (m) => console.log(`▶ ${m}`),
succeed: (m) => console.log(`✔ ${m}`),
info: (m) => console.log(`· ${m}`),
fail: (m) => console.error(`✖ ${m}`),
},
});Local Development
Install dependencies:
pnpm installUseful commands:
pnpm run lint
pnpm run deps:lint
pnpm run security:lint
pnpm run typecheck
pnpm run test
pnpm run test:coverage
pnpm run build
pnpm run smoke:scaffold
pnpm run publint
pnpm run types:lintpnpm run smoke:scaffold builds the generator, scaffolds pnpm projects with and
without CLI support, installs them from a frozen lockfile, and runs their generated
release checks. Set SMOKE_INCLUDE_CLI=true or false to run one variant, and
set SMOKE_DIR to choose the generated project directory.
Template assets live under source/templates/assets and are copied into dist/templates/assets during pnpm build.
Contributing
Contributions are welcome — see CONTRIBUTING.md and the Code of Conduct.
License
Apache-2.0 © Harold Martin.
Generated projects default to the Apache-2.0 license as well; you can choose MIT, ISC, or UNLICENSED during the prompt flow.
