Repository navigation
Releases
v2.3.0
Dependency updates now come from GitHub's built-in Dependabot, with no app or third-party account to set up, and CI checks every install for known vulnerabilities.
Changed
- Dependabot replaces Renovate (#23).
.github/dependabot.ymlopens update PRs every Monday, only for releases at least 3 days old. Minor and patch dev-tool updates arrive as one PR, and GitHub Actions updates as another. Exact pins stay exact. The Renovate app now requires registering a Mend account, which a starter template shouldn't require. - Some versions are raised by hand. Dependabot doesn't update the Node version in
devEngines.runtime, the zizmor version inci.yml, or@types/nodemajors. There is no weekly lockfile refresh. - CI runs
pnpm audit --audit-level high(#23) after installing, and fails on high or critical advisories. GitHub's Dependabot alerts can't see this template's dependencies: pnpm 12 writes a two-documentpnpm-lock.yaml, and GitHub's dependency graph reads only the first document (dependabot-core#15904). - No known advisories.
fast-uri3.1.9, which arrives through commitlint, clears two moderate and two low advisories.
Fixed
- The marker check ignores prose (#22). Lines like
## Example: usageor// For example:no longer fail the template's own CI. This only affects contributors to the template.
Changes for existing projects
| Before (v2.2.0) | After (v2.3.0) |
|---|---|
renovate.json |
.github/dependabot.yml; uninstall the Renovate app if you installed it |
ci.yml # renovate: comment on ZIZMOR_VERSION |
a plain comment; raise the version by hand |
| no audit step | ci.yml: pnpm audit --audit-level high after "Install dependencies" |
| no zizmor config | .github/zizmor.yml: rules.dependabot-cooldown.config.days: 3; without it, zizmor fails CI on the 3-day cooldown |
Notes:
- Keep three settings equal:
minimumReleaseAgeinpnpm-workspace.yaml,cooldown.default-daysindependabot.yml, anddaysin.github/zizmor.yml. - Run
pnpm auditbefore adding the CI step, and fix what it finds first. Moderate and low advisories don't fail CI.
v2.2.0
The examples are named by role, each owns its own tools, and every example-specific line is marked. This prepares the template for a scaffolding CLI that generates any combination of examples. Nothing here forces an existing project to change.
Changed
- Examples named by role (#17, #18):
sharedandlib: a library other tools import, built on shared codecli: a command people runweb: a library loaded in web pages by a script tag
- The web example owns its tools (#19). Playwright, axe, size-limit, the Playwright config and the browser tsconfig live in
packages/web, so deleting the folder removes them.pnpm test:browserandpnpm sizerun through turbo, which builds first, and passCIandPLAYWRIGHT_*through to Playwright. - Example markers (#20, #21). Every line that belongs to one example sits between
<!-- example:NAME -->,# example:NAMEor// example:NAMEmarkers. A template-only CI check keeps it that way, and CONTRIBUTING.md documents the rules. Removing any combination of examples leaves a repo that passes its own CI; all seven combinations were tested. - web has a unit test (#21), so a web-only project passes
pnpm test.
Node stays pinned at 26.12.1. Moving to the first Node 26 LTS release comes in a later update.
Changes for existing projects
These changes only matter if you want your project to match the template's new layout.
| Before (v2.1.x) | After (v2.2.0) |
|---|---|
packages/core, packages/utils (@monorepo/core, @monorepo/utils) |
packages/shared (@monorepo/shared); update imports |
packages/feature-a (@monorepo/feature-a) |
packages/lib (@monorepo/lib) |
packages/cli-example, bin cli-example |
packages/cli (@monorepo/cli), bin cli |
packages/browser-example, window.BrowserExample, data-browser-example |
packages/web (@monorepo/web), window.Web, data-web |
root playwright.config.ts, tsconfig.browser.json, and the @playwright/test, @axe-core/playwright, size-limit, @size-limit/file devDependencies |
inside packages/web |
knip.json |
deleted; knip finds the browser tests through packages/web/playwright.config.ts |
root "test:browser": "playwright test", size via pnpm -r |
turbo test:browser, turbo size; turbo.json adds both tasks (test:browser with cache: false and passThroughEnv: ["CI", "PLAYWRIGHT_*"]) |
ci.yml pnpm exec playwright … |
pnpm --filter @monorepo/web exec playwright … |
Notes:
- Without the turbo
passThroughEnv, Playwright'sforbidOnlyand its GitHub reporter turn off silently in CI. - Don't port the markers or
scripts/check-examples.*. They are template machinery for generating projects. - Projects still on v2.1.0 should also take v2.1.1's
engines.pnpm >=12guard.
v2.1.1
A patch release: the template has a new name, refuses old pnpm, and its pnpm and publishing docs are corrected.
Changed
- Renamed to
ts-monorepo-starter(#15). GitHub redirects the oldprosdevlab/ts-monorepoURLs. The root package name, the README's clone commands and folder tree, and the CONTRIBUTING heading follow. - pnpm older than 12 is refused (#16). Root
engines.pnpmis>=12. pnpm 9 used to install silently with itself, ignoring the pinned pnpm, the 3-day release rule and the pinned Node, and rewriting the lockfile. It now stops withERR_PNPM_UNSUPPORTED_ENGINE. - pnpm 10 docs corrected (#16). pnpm 10.26 or newer switches itself to pnpm 12 from
packageManager, so a global pnpm 12 install isn't needed. TheENOEXECfailure came from a Volta-managed pnpm 10, and its fix isvolta install pnpm@12.15.0. - The publishing steps add a
repositoryfield (#16). npm provenance checks it against the repository that publishes.
Changes for existing projects
- Add
"pnpm": ">=12"to the rootpackage.jsonengines. - Before publishing a package, add
"repository": {"type": "git", "url": "git+https://github.com/<owner>/<repo>.git", "directory": "packages/<name>"}to itspackage.json. - Optionally, copy the corrected install step and the
ENOEXECandERR_PNPM_UNSUPPORTED_ENGINEtroubleshooting entries from README.md and AGENTS.md.
The rename changes nothing in existing projects.
v2.1.0
Parity with the tooling used by the most-starred TypeScript library monorepos, plus a security fix for the release workflow.
Security
- The release workflow could run code from a fork with write access (#11). It triggered on
workflow_runfor CI runs on a branch namedmain. GitHub matches that name against the branch the run came from, so a fork's PR from its ownmainbranch qualified. Templates v1.1.0 through v2.0.0 then checked out the fork's commit and ran its install scripts in jobs holding write tokens, and in the publish job, npm publishing credentials. Release now runs only as the last job of CI, on pushes tomain. v1.0.0 also fired for fork PRs, but it checked outmain, so it never ran fork code.
Added
- Published packages support Node.js 24 and later (#9). The tests run on Node 24 and 26, on Linux, macOS and Windows. Development stays on the pinned Node 26.12.1.
- Preview packages for every PR (#14). pkg.pr.new publishes each package per commit and comments the install commands. It's off until the
PKG_PR_NEWrepository variable is set, and fork PRs never run it. - Workflow security audit (#12). zizmor checks every workflow on every PR and fails on any finding.
- Unused code checks (#13). knip fails on unused files, exports and dependencies, and on undeclared dependencies.
- Repository health files (#10): issue forms, a PR template,
SECURITY.md(private vulnerability reporting) and.editorconfig.
Changed
- npm publishing is gated by the
NPM_PUBLISHrepository variable. Packages are no longer marked"private": true, because previews skip private packages. - The npm trusted publisher names
ci.yml, notrelease.yml: npm checks the calling workflow. - CI on a PR cancels the PR's older run when a new push arrives.
Changes for existing projects
- Security (projects from v1.0.0 to v2.0.0): in
release.yml, replace theworkflow_runtrigger withworkflow_call:and remove theref: ${{ github.event.workflow_run.head_sha ... }}lines. Gateselect-modeongithub.ref == 'refs/heads/main'. Inci.yml, add thereleasejob:needson every other job,if: github.event_name == 'push' && github.ref == 'refs/heads/main', the three write permissions, anduses: ./.github/workflows/release.yml. If you have a trusted publisher on npmjs.com, change its workflow file toci.yml. - If you publish to npm today, set the repository variable
NPM_PUBLISHtotruewhen you take step 1'srelease.yml. The pack job now requires it, and without it releases stop. - Node 24 support:
- Set
engines.nodeto>=24in Node packages. - In the root
tsconfig.json, extend@tsconfig/node24. - Use
@types/node24.x in packages, and add the two Renovate@types/noderules. - Replace
rm -rf distandchmod +xwith thenode -eforms. - Add the
compatjob.
- Set
- zizmor: add the
workflowsjob withZIZMOR_VERSION, andcustomManagers:githubActionsVersionsto the Renovateextends. Makereleaseneedworkflows. - knip: add
knip6.43.0, thecheck:unusedscript, the CI step andknip.json. Then fix what it reports, or add entries for false positives. - Previews (optional): install the pkg.pr.new GitHub App and set
PKG_PR_NEWtotrue. Addpkg-pr-new0.0.91 and thepreviewjob. Keep"private": trueonly on packages that must never be previewed or published. Leave previews off in private repositories, because previews are public. - Health files: copy
.github/ISSUE_TEMPLATE/,.github/PULL_REQUEST_TEMPLATE.md,SECURITY.mdand.editorconfig. Then turn on Settings → Advanced Security → Private vulnerability reporting, and set the reply time inSECURITY.md.
v2.0.0
A new generation of the template:
- Node 26 and TypeScript 7, the native compiler.
- Every package builds with the TypeScript compiler; tsup is gone.
- pnpm 12 pins the Node version, which replaces Volta.
- The README has diagrams of how the pieces fit together.
Changed
- TypeScript 7.0.2. It ships no JavaScript compiler API, and tsup calls that API, so tsup is removed.
tscbuilds every package. It writes one ES module and one declaration file per source file, with maps. Build scripts deletedistfirst.- Rollup builds the script-tag file. Rollup 4.65.0 with terser bundles
tsc's output for the browser example. The minified file dropped from 731 B to 511 B. - Node 26. It becomes LTS on 2026-10-28. The template uses the
@tsconfig/node26preset,@types/node26.6.5 andengines.node >=26. Node packages no longer see browser globals:DOMis set only in the browser tests' config. - pnpm 12.15.0 replaces Volta, which is unmaintained.
devEngines.runtimepins Node 26.12.1. pnpm downloads that Node and records it in the lockfile, and CI reads the same pin.- npm and npx refuse to run in the repository unless Node matches the pin exactly. Use
pnpm dlxorpnpm execinstead.
- Stricter installs.
minimumReleaseAgeStrict: truemakes a too-new version fail the install.- pnpm 12 rejects misspelled settings.
- Install scripts run only for packages listed in
allowBuilds.
- Renovate holds
@types/nodeto 26.x. - README diagrams cover the package graph and build order, build outputs, where each version is pinned, the dependency update flow, and the release jobs with their permissions.
Changes for existing projects
A project created from v1.2.1 would change the following to match:
- Install pnpm 12:
npm install -g pnpm@12.15.0. pnpm 10 can't switch itself to pnpm 12. package.json(root).- Set
packageManagertopnpm@12.15.0andengines.nodeto>=26. - Remove the
voltablock. - Add
"devEngines": { "runtime": { "name": "node", "version": "26.12.1", "onFail": "download" } }. - Don't add
devEngines.packageManager: npm pack rejects it.
- Set
- TypeScript 7.
- Pin
typescriptto7.0.2. Replace@tsconfig/node-ltswith@tsconfig/node26@26.0.1, and pin@types/nodeto26.6.5. - In the root
tsconfig.json, extend@tsconfig/node26/tsconfig.json, and removecompositeand theliboverride. - Remove project
referencesfrom package tsconfigs. - Put
"lib": ["ES2025", "DOM"]in any tsconfig that needs browser globals.
- Pin
- Builds.
- Remove
tsupand everytsup.config.ts. - Set each package's
buildtorm -rf dist && tsc -p tsconfig.json, and itsdevtotsc -p tsconfig.json --watch. - Command packages append
&& chmod +x dist/cli.jstobuild. - Script-tag builds append
&& rollup -c, with arollup.config.jsthat bundlesdist/index.jsasiifethrough@rollup/plugin-terser. - Imports between source files need file extensions (
./thing.js), whichNodeNextalready enforces.
- Remove
pnpm-workspace.yaml. AddminimumReleaseAgeStrict: true, and anallowBuildsmap with an entry for each dependency that has an install script (esbuild: falsehere). Then reinstall.- Renovate. Change the
@types/noderule'sallowedVersionsto26.x.
Declarations change shape: one .d.ts per source file, where tsup produced one bundled file per entry. Run pnpm check:packages to confirm the packages still resolve for consumers.
v1.2.1
Node 24.22.1, with every Node 24 security fix to date, plus current pnpm 10 and dev tool releases. Renovate now keeps @types/node on the Node major the template runs.
Changed
- Node 24.12.0 → 24.22.1. This picks up five security releases: 24.13.0, 24.14.1, 24.17.0, 24.18.1 and 24.22.1. CI reads the same version from the Volta pin. It bundles npm 11.19.1, which supports trusted publishing.
- pnpm 10.26.2 → 10.34.8.
- Dev tools: commitlint 21.2.4, Vitest 5.0.5, Playwright 1.64.1, Turborepo 2.11.10, axe 4.14.0, Biome 2.6.2, size-limit 14.3.0.
@types/nodestays on 24.x. A Renovate rule holds it to the Node major Volta pins. Types for a newer Node let code typecheck against APIs the runtime lacks, so Node and its types are upgraded together.
Changes for existing projects
A project created from v1.2.0 would change the following to match:
- Node and pnpm. In
package.json, setvolta.nodeto24.22.1,volta.pnpmto10.34.8andpackageManagertopnpm@10.34.8. - Dev tools. Pin the versions listed above. If you moved to Biome 2.6, update the
$schemaURL inbiome.jsonto2.6.2. Then runpnpm installandpnpm lint; minor Biome releases can add rules. - Renovate. Add this package rule to
renovate.json:{ "matchPackageNames": ["@types/node"], "allowedVersions": "24.x" }.
v1.2.0
Dependencies can now install once they are 3 days old, down from 14, and an urgent security fix has a documented way through on its release day. Packages build with tsup 9, which removes the TypeScript 6 workaround from every package config.
Changed
- 3-day dependency age rule. pnpm refuses any version published in the last 3 days, including transitive ones, and Renovate waits the same 3 days before proposing an update. The two values must stay equal, or Renovate opens PRs that pnpm refuses.
- Urgent security fixes. One exact version may be added to
minimumReleaseAgeExclude, with its advisory linked, and is removed once it is 3 days old. A person approves each exception, andAGENTS.mdtells agents never to add one. The README's "Dependency Updates" section has the steps. - Security-fix PRs. Renovate labels vulnerability fixes
securityand opens them outside its weekly schedule. This needs Dependabot alerts turned on for the repository. - tsup 9.0.0. JavaScript output is byte-identical to tsup 8.5.1. Declaration files now put type-only exports in a separate
export type { … }statement, which is equivalent.
Changes for existing projects
A project created from v1.1.0 would change the following to match:
- Dependency age. In
pnpm-workspace.yaml, setminimumReleaseAge: 4320. Inrenovate.json, set"minimumReleaseAge": "3 days"and add"vulnerabilityAlerts": { "labels": ["security"] }. - tsup 9. Pin
tsupto9.0.0in everypackage.jsonthat lists it. In eachtsup.config.ts, replacedts: { compilerOptions: { ignoreDeprecations: '6.0' } }withdts: true, keeping any otherdtsoptions such asentry. tsup 9 requires Node 22.18 or later; the template already requires Node 24.
v1.1.0
The template now builds browser libraries and command-line tools as well as Node libraries. It publishes without any stored npm secret, checks every package before it ships, and refuses dependencies published in the last 14 days.
Added
- Browser library example (
packages/browser-example): an ES module plus a script-tag build, Playwright browser tests with an axe accessibility check, and a size budget that fails CI when the build grows. - Command-line example (
packages/cli-example): abinentry, a Node 24 build target, and a test that runs the built command. - Package checks (
pnpm check:packages): each package is packed as it would publish, then checked with publint and Are The Types Wrong. CI and the release workflow both run them. - 14-day dependency age rule: pnpm refuses any version published in the last 14 days, including transitive ones. Renovate (opt-in, via its GitHub App) proposes weekly grouped updates once releases pass that age.
- Agent instructions:
AGENTS.mdcovers setup, the definition of done, lockfile hygiene and troubleshooting.CLAUDE.mdimports it.
Changed
- Publishing uses npm trusted publishing. No npm secret is stored, and public packages get provenance. The release workflow runs as four jobs; only the publish job can obtain npm credentials, and it never builds.
- CI hardening: least-privilege permissions, and every action pinned to a commit SHA.
- Tooling: Node 24 (Volta 24.12.0), pnpm 10.26.2, TypeScript 6.0.3, tsup 8.5.1, Turborepo 2.11.7, Biome 2.5.15, Vitest 5.0.4, Changesets 3.0.3, commitlint 21.2.3.
- Versioning: template versions mark milestones, not compatibility, because a template is copied once and never upgraded in place. This section is where compatibility is described.
Changes for existing projects
A project created from v1.0.0 would change the following to match:
- Node 24 and pnpm 10. Raise
engines.nodeto>=24. SetpackageManagertopnpm@10.26.2and the Volta pins. Reinstall to regenerate the lockfile. - Builds use tsup. Packages built with
tscmove to atsup.config.ts. Add"type": "module", anexportsmap intodist/, and"files": ["dist"]. Withoutfiles,npm packships nodist/. - TypeScript 6 deprecates
baseUrl. Remove it from the roottsconfig.jsonand prefixpathswith./. Packages built with tsup 8 also needdts: { compilerOptions: { ignoreDeprecations: '6.0' } }until tsup 9. - Biome 2 and Turborepo 2. Migrate
biome.json(biome migrate) and renamepipelinetotasksinturbo.json. - Changesets 3 no longer versions private packages by default. To keep that behavior, set
"privatePackages": { "version": true, "tag": false }in.changeset/config.json. - Publishing. Delete the
NPM_TOKENsecret. Replacerelease.yml. Add a trusted publisher on npmjs.com forrelease.ymland thenpmenvironment, then set the package to "disallow tokens". The README's "Setting Up npm Publishing" section covers each step. - Dependency age. Add
minimumReleaseAge: 20160topnpm-workspace.yaml. If the lockfile already holds young versions, deletenode_modulesand the lockfile, then reinstall.
v1.0.0
Full Changelog: https://github.com/prosdev/ts-monorepo/commits/v1.0.0