npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@hbmartin/create-ts-lib

v2.0.0

Published

Opinionated TypeScript library scaffolder.

Readme

@hbmartin/create-ts-lib

One command scaffolds a modern, publish-ready TypeScript library.

npm version CI npm downloads node types included install size license

An opinionated initializer that turns a single command into a building, testing, lint-clean TypeScript library you can publish the same day. See Why this tool for the full stack and the rationale behind each choice.

Contents

For a guided walkthrough of everything a scaffolded project contains, see the generated project tour.

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 >=24 or >=26 — no dual-format build complexity.
  • Oxlint + Oxfmt or Biome for fast linting and formatting instead of ESLint + Prettier.
  • Vitest with v8 coverage and enforced thresholds out of the box.
  • fallow + 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.

How it compares

How create-ts-lib stacks up against the common ways to start a TypeScript library:

| Concern | create-ts-lib | tsdx | Hand-rolled template | | ----------------------------- | --------------------------------------- | -------------------- | -------------------- | | Output format | ESM-only (Node >=24 or >=26) | CJS + ESM | You decide | | Lint + format | Oxlint + Oxfmt or Biome | ESLint + Prettier | You wire it up | | Tests + coverage | Vitest + v8, 80% gate | Jest | You wire it up | | Architecture + security gates | fallow + Semgrep | — | — | | Publish validation | publint + are-the-types-wrong + dry-run | — | Manual | | Git hooks | Lefthook (pre-wired) | Husky (manual) | Manual | | CI + release workflow | GitHub Actions (pnpm) | — | Manual | | Project status | Actively maintained | Inactive since ~2021 | n/a |

The trade-off is deliberate: create-ts-lib is opinionated and ESM-only with a focused lint/format choice between Oxlint + Oxfmt and Biome. If you need a dual CJS/ESM build or a different broader tooling stack, a hand-rolled template gives you more control at the cost of the wiring.

Requirements

  • Node.js 24 or newer to run the generator. Generated projects choose their own floor — engines.node: ">=24" (the default, CI-tested on 24 and 26) or ">=26" (CI-tested on 26) — via the --node-target flag.
  • A package manager: pnpm (recommended), npm, or yarn.

Usage

Recommended (pnpm):

pnpm create @hbmartin/ts-lib my-lib
npm create @hbmartin/ts-lib my-lib
npx @hbmartin/create-ts-lib my-lib

By default the generator asks interactive prompts. To accept all detected/default answers without prompting:

npx @hbmartin/create-ts-lib my-lib --yes

To preview the target options and file list without writing anything:

npx @hbmartin/create-ts-lib my-lib --dry-run

To choose Biome instead of the default Oxlint + Oxfmt stack:

npx @hbmartin/create-ts-lib my-lib --lint-format biome

To include Zod as a generated runtime dependency:

npx @hbmartin/create-ts-lib my-lib --zod

To omit the default Codecov upload step from generated pnpm CI:

npx @hbmartin/create-ts-lib my-lib --no-codecov

Every prompt has a matching flag, so a fully non-interactive scaffold needs no --yes guesswork:

npx @hbmartin/create-ts-lib my-lib --yes \
  --name @scope/my-lib \
  --description "An example library" \
  --author "Ada Lovelace <[email protected]>" \
  --license MIT \
  --repo-url https://github.com/ada/my-lib \
  --package-manager pnpm \
  --lint-format biome \
  --bundler tsdown \
  --node-target 24 \
  --no-codecov --jsr --security-workflows

In interactive mode, any provided flag skips its prompt and the generator only asks about the rest.

To scaffold without running git setup or the install/build/test steps (useful in CI, monorepos, or offline):

npx @hbmartin/create-ts-lib my-lib --yes --skip-git --skip-install

By 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 --force

CLI Options

create-ts-lib [directory] [options]
create-ts-lib update [directory] [--dry-run] [--force] [--yes] [--no-backup]
                          [--remove-orphans]
create-ts-lib config path
create-ts-lib config get [key]
create-ts-lib config set <key> <value>
create-ts-lib config unset <key>

| 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 | | --name <name> | Package name (defaults to the directory name) | | --description <text> | Package description | | --author <author> | Package author, e.g. "Ada <[email protected]>" | | --license <license> | Choose Apache-2.0, MIT, ISC, or UNLICENSED | | --repo-url <url> | GitHub repository URL for generated metadata | | --package-manager <pm> | Choose pnpm, npm, or yarn | | --lint-format <tooling> | Choose oxlint-oxfmt or biome | | --bundler <bundler> | Choose tsc or tsdown | | --node-target <major> | Choose Node 24 or 26 as the generated engines floor | | --[no-]cli | Include or omit the CLI entry point | | --[no-]codecov | Include or omit Codecov upload in generated CI | | --[no-]zod | Include or omit Zod in the generated project | | --[no-]jsr | Include or omit JSR publishing support | | --[no-]security-workflows | Include or omit CodeQL and Scorecard workflows | | --[no-]community-files | Include or omit CONTRIBUTING/CODE_OF_CONDUCT/SECURITY | | --[no-]workspace | Scaffold as a package inside an existing workspace | | --no-backup | Skip <file>.orig backups when update overwrites edits | | --remove-orphans | With --force, delete unmodified files the templates no longer generate | | --save-defaults | Save this run's reusable answers as personal defaults | | --config <path> | Read and write personal defaults at this path | | --skip-git | Skip git init and git remote setup | | --skip-install | Skip dependency install, build, and test | | --help, -h | Print usage and exit | | --version, -v | Print the CLI version and exit |

Personal Defaults

The generator reads personal defaults from $XDG_CONFIG_HOME/create-ts-lib/config.json (usually ~/.config/create-ts-lib/config.json). Values there become the prompt defaults and the --yes answers; CLI flags always win over the config file.

{
  "author": "Ada Lovelace <[email protected]>",
  "license": "MIT",
  "lintFormatTooling": "biome",
  "bundler": "tsdown",
  "nodeTarget": "26",
  "packageManager": "pnpm",
  "includeCodecov": false
}

Supported keys: author, license, lintFormatTooling, bundler, nodeTarget, packageManager, includeCli, includeCodecov, includeCommunityFiles, includeJsr, includeSecurityWorkflows, and includeZod. Per-project answers (name, description, repo URL, workspace mode) are deliberately not configurable here. Invalid config files are reported and ignored.

Managing defaults from the CLI

You do not have to hand-write that file:

create-ts-lib config path                 # print the resolved config path
create-ts-lib config get                  # print every set key
create-ts-lib config get license          # print one key
create-ts-lib config set license MIT      # set a key (validated before writing)
create-ts-lib config unset license        # remove a key

Boolean keys take true or false. Unknown keys and invalid values are rejected without touching the file, and writes are atomic.

You can also save the answers from a scaffold run:

npx @hbmartin/create-ts-lib my-lib --save-defaults

That persists only the reusable answers — never the project name, description, repo URL, copyright year, or workspace mode.

Pass --config <path> to any of these (and to a scaffold run) to read and write somewhere other than the default location, which is useful in CI and for project-local presets.

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 and is checked against npm; --name skips the prompt | | Description | empty string | Written to package.json; --description skips the prompt | | Author | git config user.name + git config user.email | Combined as Name <email> when available; --author skips the prompt | | Scaffold as a workspace package? | yes when a workspace is detected | Only asked when a parent workspace is found; omits root-owned files and never offers to create a GitHub repo. Automation can pass --workspace or --no-workspace | | License | Apache-2.0 | Apache-2.0, MIT, ISC, UNLICENSED; --license skips the prompt | | Lint and format tooling | Oxlint + Oxfmt | Oxlint + Oxfmt or Biome; automation can pass --lint-format oxlint-oxfmt or --lint-format biome | | Build tool | tsc | tsc or tsdown; automation can pass --bundler tsc or --bundler tsdown | | Minimum Node version | 24 | 24 or 26; sets engines.node, the @types/node major, and the CI matrix; --node-target skips the prompt | | GitHub repo URL | existing personal GitHub repo found by gh | Missing repos can be created public/private or entered manually; normalizes SSH remotes; --repo-url skips the flow. In workspace mode the default is the detected workspace remote and creation is not offered | | Include Codecov? | yes | Adds a Codecov upload step to pnpm-generated CI; automation can pass --no-codecov to omit it | | Include CodeQL and Scorecard workflows? | no | Adds SHA-pinned codeql.yml and scorecard.yml for pnpm + GitHub projects; automation can pass --security-workflows | | Include CONTRIBUTING, CODE_OF_CONDUCT, and SECURITY files? | no | Adds the three community-health files OpenSSF Scorecard grades; automation can pass --community-files | | Include CLI entry point? | no | Adds bin, meow, source/cli.ts, and CLI coverage; automation can pass --cli or --no-cli | | Include Zod? | no | Adds zod as a runtime dependency and Zod guidance to generated AGENTS.md; automation can pass --zod | | Also publish to JSR? | no | Adds jsr.json, pinned jsr tooling, a jsr:publish script, and a JSR release step; automation can pass --jsr | | Package manager | pnpm | pnpm, npm, or yarn; generated CI is pnpm-only |

Defaults come from your personal defaults file when one exists, then from git detection and the built-in values. Any prompt whose value was provided as a CLI flag is skipped.

After the final project name is accepted, interactive mode starts a gh lookup for a matching personal GitHub repository while it continues asking local project prompts. Existing repos are offered as the repo URL default. When no matching repo exists, the CLI lets you create a public or private repo with gh repo create after the scaffold summary and before files are written; --dry-run shows the predicted URL and skips creation. When gh is unavailable, unauthenticated, or returns an unexpected error, the CLI warns and asks for a repo URL with no default. Passing --repo-url skips the lookup and prompts entirely. Repo creation is never offered for a workspace package: the workspace repository already owns the remote, and a second repo would be left empty and wired to nothing.

If the project name already exists on npm, interactive mode warns and lets you rename it or continue anyway. --yes uses defaults directly, still uses git remote origin as the GitHub repo URL default for generated metadata such as package.json repository fields, warns on an existing npm name, and continues without gh lookup, repo creation, or remote setup (unless --repo-url is passed explicitly, which also configures the origin remote). 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/
│   ├── cli.test.ts             # optional
│   └── utils/
│       └── formatting.test.ts
├── .github/
│   └── workflows/
│       ├── ci.yml              # pnpm + GitHub repo URL only
│       ├── release.yml         # pnpm + GitHub repo URL only
│       ├── codeql.yml          # optional (--security-workflows)
│       └── scorecard.yml       # optional (--security-workflows)
├── .vscode/
│   ├── extensions.json
│   └── settings.json
├── AGENTS.md
├── CODE_OF_CONDUCT.md          # optional (--community-files)
├── CONTRIBUTING.md             # optional (--community-files)
├── SECURITY.md                 # optional (--community-files)
├── .create-ts-lib.json         # scaffold state used by the update command
├── .fallowrc.jsonc
├── .gitignore
├── biome.jsonc                # Biome projects only
├── .oxfmtrc.json              # Oxlint + Oxfmt projects only
├── .oxlintrc.json             # Oxlint + Oxfmt projects only
├── jsr.json                    # optional (--jsr)
├── lefthook.yml
├── pnpm-workspace.yaml         # pnpm only
├── package.json
├── README.md
├── renovate.json               # GitHub repo URL only
├── semgrep.yml
├── tsconfig.json
├── tsconfig.build.json         # tsc bundler only
├── tsdown.config.ts            # tsdown bundler only
├── vitest.config.ts
└── LICENSE

GitHub CI and release workflows are generated only for pnpm projects when a GitHub repo URL is provided; the optional CodeQL and Scorecard workflows follow the same rule. 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. The .vscode/ files recommend and configure the editor extension matching the chosen lint tooling (Biome or Oxc) with format-on-save enabled.

For a deeper explanation of what each file does, read the generated project tour.

Generated Defaults

Generated packages include:

  • version: "0.1.0"
  • type: "module"
  • packageManager matching this generator's pnpm version for pnpm projects
  • a package-manager-specific @types/node pin via overrides, pnpm.overrides, or resolutions
  • exports pointing at dist/index.js and dist/index.d.ts
  • engines.node: ">=24" by default, or ">=26" with --node-target 26, with @types/node pinned to the matching major
  • @sindresorhus/tsconfig
  • a tsc build by default, or a tsdown bundler build when selected
  • oxlint and oxfmt by default, or @biomejs/biome when selected
  • matching lint/format scripts and config files for the selected tooling
  • VS Code extension recommendations and format-on-save settings for the selected tooling
  • fallow architecture and dependency checks
  • Semgrep policy checks
  • Vitest with v8 coverage and 80% thresholds
  • SHA-pinned GitHub Actions CI and release workflows for pnpm projects, with npm publishing via OIDC trusted publishing and provenance
  • optional SHA-pinned CodeQL and Scorecard workflows when selected
  • optional CONTRIBUTING.md, CODE_OF_CONDUCT.md (Contributor Covenant 2.1), and SECURITY.md when selected — the three files OpenSSF Scorecard grades, so they pair naturally with the security workflows
  • a Renovate config whenever a GitHub repo URL is provided
  • Lefthook with a lint-only pre-commit hook
  • optional zod runtime dependency when selected
  • optional JSR publishing (jsr.json, pinned jsr tooling, a jsr:publish script, and a release workflow step) when selected
  • meow and CLI coverage only when CLI support is enabled, including a CLI test mock with default flags and input
  • @arethetypeswrong/cli and publint release checks
  • check, deps:lint, security:lint, prepublishOnly, publint, types:lint, verify:artifacts, verify:package, size:report, and release:check scripts
  • AGENTS.md with opinionated, package-manager-aware guidance for Codex and other coding agents
  • a .create-ts-lib.json state file so create-ts-lib update can re-sync tooling files later
  • Apache-2.0 license by default, stamped with the scaffold year and recorded in .create-ts-lib.json so update never rewrites your copyright year later

Versioning: generated packages start at 0.1.0, signaling a pre-1.0 library where minor bumps may carry breaking changes until you cut 1.0.0. You own the version with pnpm version — nothing bumps it for you.

Release automation: rather than bundling release-please or changesets, pnpm projects with a GitHub repo URL include a lightweight release workflow that publishes to npm when you publish a GitHub release. This keeps versioning fully in your hands while still automating the npm publish. The workflow authenticates with npm trusted publishing (OIDC) and publishes with provenance — no long-lived NPM_TOKEN secret required. The generated README documents the one-time trusted-publisher setup.

Workspace Mode

When the generator finds a workspace manifest above the target directory, it offers to scaffold the new package as a workspace package. Detection looks for a pnpm-workspace.yaml or a package.json with a workspaces field, searching upward but never past the enclosing Git repository. A manifest whose only package pattern is "." — the single-package pnpm idiom this generator itself emits — is deliberately ignored, so a standalone generated project is never mistaken for a monorepo root.

In workspace mode the generator omits the files a repository root already owns:

| Omitted (root-owned) | Still generated | | --------------------- | ------------------------------------------ | | .gitignore | package.json, tsconfig.json | | .github/workflows/* | vitest.config.ts, .fallowrc.jsonc | | renovate.json | semgrep.yml, scripts/security-lint.mjs | | .vscode/* | lint/format config, source/, test/ | | lefthook.yml | README.md, LICENSE, AGENTS.md | | pnpm-workspace.yaml | .create-ts-lib.json | | CODE_OF_CONDUCT.md | | | CONTRIBUTING.md | | | SECURITY.md | |

The community-health files are omitted even with --community-files: GitHub only surfaces them at the repository root, so a copy inside packages/<name>/ is a file nothing reads.

The generated package.json also drops packageManager, the prepare hook, and the lefthook devDependency, since the root pins all three. That is a precondition, not something detection verifies: a pnpm-workspace.yaml or a workspaces field is enough to offer workspace mode, so on an npm or Yarn root that does not already own these, add them at the root — or add them back to the package — or the new package ends up without a pinned package manager and without Git hooks. Git setup is skipped entirely — the workspace repository owns it, so the printed next steps leave out the git push instructions even when a repository URL is configured. That URL still lands in package.json: it is the parent repository's.

No GitHub repository is created either. The repo URL prompt defaults to the detected workspace remote rather than to a personal repo named after the package, since that is not where packages/<name> lives.

Because Codecov, the security workflows, and the community-health files all depend on files the root owns, answering yes to any of them changes nothing about a workspace package. The scaffold summary marks them yes (root-owned) rather than claiming they were applied, and the Codecov setup URL is not printed. The answers are still recorded in .create-ts-lib.json, so they take effect again if the package is ever moved out of the workspace.

Workspace mode never writes outside the target directory. Registering the package in the parent workspace globs is left to you, and the printed next steps say so. Pass --workspace or --no-workspace to skip the prompt; under --yes a detected workspace enables the mode automatically and the scaffold summary reports it.

Post-Scaffold Behavior

Before creating a GitHub repo or writing files, the generator rejects non-empty target directories unless --force is provided. After writing files, it performs:

  1. git init when the target directory is not already inside a Git repository
  2. git remote add origin <repo-url> when a repo URL is provided and the target is its own Git repository
  3. <package-manager> install
  4. <package-manager> run build
  5. <package-manager> run test

--skip-git skips step 1–2 and --skip-install skips steps 3–5; the printed next steps then include the commands you skipped. Remote setup is best-effort: if the target is inside a parent Git repository, or if origin already exists, the CLI reports the issue and continues. The CLI prints a summary before writing and shows progress during post-scaffold setup. When a repo URL is configured interactively or passed with --repo-url (and --skip-git is not set), the final next steps include git add, git commit, and git push -u origin HEAD. When Codecov is enabled for a pnpm project with a GitHub repo URL, the final next steps also include the Codecov setup URL for that repository — except in workspace mode, where the upload step lives in a CI workflow the package does not generate. In non-TTY or CI environments it uses plain step logs instead of spinners. If a setup command fails after files are written, the CLI prints the created project path and the commands to retry the failed and remaining setup steps.

Updating a Generated Project

Generated projects include a .create-ts-lib.json state file recording the scaffold configuration and a hash of every generated file. When a new create-ts-lib release improves the templates, re-sync from inside the project (or pass its path):

npx @hbmartin/create-ts-lib update              # plan + choose + apply
npx @hbmartin/create-ts-lib update --dry-run    # preview only
npx @hbmartin/create-ts-lib update --force      # also overwrite files you changed
npx @hbmartin/create-ts-lib update --yes        # apply every safe update, no prompt
npx @hbmartin/create-ts-lib update --no-backup  # skip .orig backups
npx @hbmartin/create-ts-lib update --force --remove-orphans  # also delete files no longer generated

For every generated file, the update command reports one of:

| Status | Meaning | | -------- | --------------------------------------------------------------------- | | ok | Already matches the current template | | update | Unmodified since scaffolding — safely rewritten with the new template | | create | Missing on disk — recreated | | skip | You modified it after scaffolding — left alone unless --force |

Files you have edited (usually package.json, source/, and test/) are detected via the recorded hashes and never overwritten by default. After an update that touches package.json, re-run your package manager's install.

Choosing what to apply

Interactively, update prints the plan and then offers three choices:

  • Apply all — write every pending file
  • Choose files… — pick individual files from a checklist
  • Cancel — write nothing

--yes applies every pending file without prompting.

Backups

When --force overwrites a file you modified, your previous contents are kept alongside it as <file>.orig before the new template is written, and the paths are printed. An existing backup is never overwritten: a taken name moves the new backup to <file>.orig.1, <file>.orig.2, and so on, which is why the printed paths are worth reading rather than assuming. Generated .gitignore files ignore *.orig. Files that were never modified are not backed up — there is nothing to lose. Pass --no-backup to opt out.

Files the templates no longer generate

When your recorded config no longer renders a file the state file remembers, update lists it under No longer generated by these templates:

| Label | Meaning | | --------- | ------------------------------------------------------------------- | | remove | Still matches the hash recorded at scaffold time, so it can be deleted | | keep | You edited it. Never removed automatically, under any flags | | outside | The recorded path points outside the project and is ignored |

Nothing is removed by default. --force --remove-orphans deletes only the remove rows; --remove-orphans without --force is an error, so the --yes --force you may already have in a script stays non-destructive.

Read the list before passing those flags. A file stops being generated for two very different reasons: the templates dropped it, or your config changed. Flipping lintFormatTooling, bundler, packageManager, or workspaceMode in .create-ts-lib.json makes a batch of files you still want look unwanted — switching to workspace mode alone accounts for .gitignore, lefthook.yml, pnpm-workspace.yaml, the editor settings, and every workflow at once.

Next Steps After Scaffolding

Once the generator finishes, your library already builds, lints, and tests. Write your code in source/, then follow the path below.

1. Validate before publishing (required):

cd my-lib
pnpm run release:check  # full check suite + package validation + npm publish dry run

release:check already runs pnpm run check (lint + dep/security checks + typecheck + coverage) for you, so there's no need to run check separately first.

2. Inspect the packed package (optional):

pnpm run size:report    # packed package size report

3. Cut a release:

pnpm version patch      # or minor / major — bumps package.json and creates a git tag
git push --follow-tags  # push the commit and the new tag

What actually triggers the publish: the tag push alone does not publish. For pnpm projects with a GitHub repo URL, creating a GitHub release for the pushed tag runs the generated npm-publish workflow. Prereleases publish under the next tag; normal releases publish under 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 semgrep scan. Install Semgrep directly or install uv if both commands are missing locally.

Programmatic API

The package exports:

  • defaultScaffoldConfig(overrides?)
  • buildProjectFiles(config)
  • scaffoldProject(config, options)
  • readScaffoldState(targetDirectory)
  • planUpdate(targetDirectory, state)
  • applyUpdatePlan(targetDirectory, plan, options?)
  • Bundler, LicenseName, NodeTarget, PackageManager
  • GeneratedFile
  • ScaffoldConfig
  • ScaffoldConfigOverrides
  • ScaffoldOptions
  • ScaffoldProgress
  • ScaffoldState, UpdateFileStatus, UpdatePlan, UpdatePlanEntry
  • OrphanedFile, OrphanRemovalResult, OrphanStatus

ScaffoldConfig is a complete required config shape. Use defaultScaffoldConfig() to start from the documented defaults and override only the project-specific values. undefined override values are ignored, preserving the documented defaults. defaultScaffoldConfig() also fills copyrightYear with the current UTC year; pass it explicitly to render a project deterministically.

buildProjectFiles(config) renders the whole project to an in-memory GeneratedFile[] ({ path, content, executable? }) without touching the filesystem. It is a pure function of its config, so it is the entry point to use for previewing a scaffold, diffing template changes, or writing to a virtual filesystem.

scaffoldProject writes the generated project, rejects invalid package names, refuses non-empty target directories unless force: true is set, can optionally receive progress callbacks for post-scaffold steps, and can add a best-effort origin remote with gitRemoteOriginUrl. skipGit: true and skipInstall: true skip the corresponding post-scaffold steps.

readScaffoldState, planUpdate, and applyUpdatePlan power the update command programmatically: read a project's .create-ts-lib.json, classify every generated file (up-to-date, update, create, or skip-modified), and apply the safe subset.

plan.orphans lists paths the state file records that the config no longer renders. applyUpdatePlan deletes them only when both force and removeOrphans are set, and only the ones still matching their recorded hash — re-checked immediately before the delete, so a file edited between the plan and the apply is skipped rather than lost. Removals are reported through the onOrphan callback rather than the return value, because the re-check means the set removed is not the set requested.

import { defaultScaffoldConfig, scaffoldProject } from "@hbmartin/create-ts-lib";

const config = defaultScaffoldConfig({
  projectName: "my-lib",
  description: "An example library",
  author: "Jane Doe <[email protected]>",
  githubRepoUrl: "https://github.com/jane/my-lib",
});

await scaffoldProject(config, {
  targetDirectory: "./my-lib",
  force: false,
  gitRemoteOriginUrl: config.githubRepoUrl,
  postScaffold: true, // run git init + optional origin setup + 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 install

Useful 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:lint

pnpm 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, SMOKE_LINT_FORMAT=biome to exercise the Biome stack, SMOKE_BUNDLER=tsdown to exercise the tsdown build, and SMOKE_DIR to choose the generated project directory. SMOKE_NODE_TARGET picks the generated project's Node floor. CI runs the oxlint-oxfmt/tsc matrix on Node 24 and 26 — each leg scaffolding for the major it runs on, so both @types/node pins get installed and typechecked — plus Biome and tsdown variants on Node 24.

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.