@git.zone/tsrepo
The git.zone TypeScript repository toolchain in one package: build, type check, test, bundle, run, publish and watch TypeScript projects, generate their documentation and build Node.js single executable applications, with the tsrepo command.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
pnpm add --save-dev @git.zone/tsrepo
tsrepo needs Node.js 22.14 or later.
Commands
| Command | Behaves exactly like |
|---|---|
tsrepo build [args], tsrepo build custom|emitcheck|tsfolders … |
tsbuild [args] |
tsrepo check [patterns…] [flags] |
tsbuild check [patterns…] [flags] |
tsrepo test [path] [flags], tsrepo test clean [--dry-run], tsrepo test ledger check … |
tstest [path] [flags], tstest clean …, tstest ledger check … |
tsrepo bundle [command] [flags] |
tsbundle [command] [flags] |
tsrepo run <file> [args] |
tsrun <file> [args] |
tsrepo publish plan, tsrepo publish prepare <new-output-directory> |
tspublish plan, tspublish prepare … |
tsrepo docs [typedoc|website|aidoc|readme|description|commit|auth|test] [flags] |
tsdoc [command] [flags] |
tsrepo watch [--port <n>], tsrepo watch init |
tswatch [--port <n>], tswatch init |
tsrepo sea build|fetch [--target <t>…] [flags], tsrepo sea help |
tssea build|fetch …, tssea help |
tsrepo --help, tsrepo --version |
help and version of tsrepo itself |
Each command takes the arguments and flags of the tool it replaces and gives the same output and exit codes; tsrepo build --help prints tsbuild's help with every flag, tsrepo test --help tstest's, tsrepo bundle --help tsbundle's, tsrepo docs --help tsdoc's, tsrepo watch --help tswatch's and tsrepo sea help tssea's. The one difference is --version: every command reports the tsrepo version (tsrepo test --version prints tstest version <tsrepo version>, and the first line of tsrepo sea help reads @git.zone/tssea v<tsrepo version>).
tsrepo check is a TypeScript type check without emitting files, with tsbuild's semantics: without patterns it checks ts/**/*.ts and then test/**/*.ts. It is not a quality gate: rsrepo check (@git.zone/rsrepo) is the Rust quality gate (rustfmt, clippy, tests and docs), a different command with the same name.
Programmatic use
| Import | Content |
|---|---|
@git.zone/tsrepo |
runCli(argv) (the tsrepo command line) and version. Importing it loads no module; every command loads its module on demand, so tsrepo test never loads typedoc or the AI code of the docs module. |
@git.zone/tsrepo/build |
the former @git.zone/tsbuild API: TsCompiler, TsConfig, TsFolderConfig, TsPublishConfig, TsUnpacker, TsPathRewriter, TsBuildLogger, FsHelpers, TsBuildCli, runCli(), and runBuildCli(argv) |
@git.zone/tsrepo/run |
the former @git.zone/tsrun API: runPath(), runCli(), spawnPath(), and runRunCli(argv) |
@git.zone/tsrepo/publish |
the former @git.zone/tspublish API: TsPublish, its interfaces, runCli(args), and runPublishCli(argv) |
@git.zone/tsrepo/test |
the former @git.zone/tstest API: checkLedger(), the configuration, artifact and affected-selection helpers and types, TestExecutionMode, runCli(), and runTestCli(argv) |
@git.zone/tsrepo/tapbundle |
the former @git.zone/tstest/tapbundle: tap, expect, TapWrap, TapTools, webhelpers, setProtocolEmitter |
@git.zone/tsrepo/tapbundle_serverside |
the former @git.zone/tstest/tapbundle_serverside: TapNodeTools and its types |
@git.zone/tsrepo/tapbundle_protocol |
the former @git.zone/tstest/tapbundle_protocol: ProtocolEmitter, ProtocolParser, PROTOCOL_MARKERS, PROTOCOL_VERSION and the protocol types |
@git.zone/tsrepo/bundle |
the former @git.zone/tsbundle API: TsBundle, AssetsHandler, HtmlHandler, CustomBundleHandler, runCustomBundles, Base64TsOutput, the presets, the notices API, the interfaces, runCli(), and runBundleCli(argv) |
@git.zone/tsrepo/bundle/notices |
the former @git.zone/tsbundle/notices: the third-party notices API; it loads only Node.js builtins, @push.rocks/smartconfig and tsrepo's internal configuration resolver (Node.js builtins only). The same module is published on its own as @git.zone/notices (see below) |
@git.zone/tsrepo/docs |
the former @git.zone/tsdoc API: AiDoc, its configuration types, NoChangesError, runCli(), and runDocsCli(argv) |
@git.zone/tsrepo/docs/website |
the former @git.zone/tsdoc/website: DocsWebsiteBuilder, DocsWebsiteServer and the website interfaces |
@git.zone/tsrepo/watch |
the former @git.zone/tswatch API: TsWatch, Watcher, ConfigHandler, TswatchInit, runInit(), the environment marker, registerCommands(), the interfaces, runCli(), and runWatchCli(argv) |
@git.zone/tsrepo/sea |
the former @git.zone/tssea API: TsSea, runCli(), runCommandLine(), parseCommandLine(), usage, TsseaUsageError, TsseaError, the configuration types, tsseaTargets, tsseaConfigKey, nodeReleasePins, resolveCacheRoot, cacheDirEnvVar, and runSeaCli(argv) |
@git.zone/tsrepo/sea/config |
the former @git.zone/tssea/config: tsseaConfigKey, resolveOutputs, ITsseaOutput(s), TTsseaTarget, TsseaError; it loads only node:fs, node:path and @push.rocks/smartsea/layout. It also exports the validator and target table the sea module shares with it (resolveConfig, tsseaTargets, targetInfos and their helpers); only the former @git.zone/tssea/config names are meant for other tools. |
@git.zone/tsrepo/sea/cache |
the former @git.zone/tssea/cache: the layout of the Node.js download cache and classifyCacheEntry; it imports nothing |
Each subpath keeps the API of the package it replaces, so the API documentation of tstest 8.0.0 (including tapbundle, tapbundle_serverside and tapbundle_protocol), tsbundle 3.7.0, tsbuild 6.0.2, tsrun 3.0.3, tspublish 2.1.0, tsdoc 3.0.2, tswatch 7.1.1 and tssea 1.2.1 (with the fixes of its main branch after 1.2.1: the bounded --build-sea run and its buildTimeoutMs key) applies with the import path from this table.
run<Module>Cli(argv) is a module's command line; argv has the layout of process.argv, [execPath, <entry>, ...args].
import { spawnPath } from '@git.zone/tsrepo/run';
const child = spawnPath('./script.ts', import.meta.url, { args: ['--flag'], timeout: 60_000 });
console.log(await child.exitCode);
spawnPath() and runPath(path, fromUrl, { cwd }) start <tsrepo>/cli.js run <script> … in a child process; the script sees [execPath, <absolute script path>, ...args], as under the tsrun binary.
The run module loads nothing but Node.js builtins and tsx, so tsrepo run starts as fast as tsrun did.
import { tap, expect } from '@git.zone/tsrepo/tapbundle';
tap.test('adds', async () => {
expect(1 + 1).toEqual(2);
});
export default tap.start();
tsrepo test runs Node.js test files through tsrepo run and bundles Chromium test files with the bundle module.
tsrepo watch bundles with the bundle module and serves with @api.global/typedserver; tsrepo docs typedoc runs the typedoc that tsrepo itself depends on, resolved from tsrepo's install (typedoc/package.json and its bin), wherever the package manager put it. tsrepo docs delegates its AI work (readme, description, commit) to @git.zone/tsagent.
tsrepo sea takes a bundle that tsrepo bundle wrote with "sea": true and builds the executables; its notices are read and written with the bundle notices module, so ThirdPartyNoticesError from @git.zone/tsrepo/bundle/notices is the class @git.zone/tsrepo/sea reports. The Node.js download cache stays where tssea kept it (~/.cache/tssea on Linux, $XDG_CACHE_HOME/tssea, ~/Library/Caches/tssea, %LOCALAPPDATA%\tssea; TSSEA_CACHE_DIR overrides it), with the same layout, so tssea's downloads are reused.
Module map
| Folder | Subpath | Carries |
|---|---|---|
ts/ |
. |
the tsrepo router and root API |
ts_shared/ |
(internal) | .smartconfig.json resolution of the @git.zone/tsrepo key and the tsrepo version |
ts_run/ |
./run |
tsrun 3.0.3 |
ts_publish/ |
./publish |
tspublish 2.1.0 |
ts_build/ |
./build |
tsbuild 6.0.2 |
ts_bundle_notices/ |
./bundle/notices; published as @git.zone/notices |
tsbundle 3.7.0 ts/mod_notices; documented in ts_bundle_notices/readme.md |
ts_bundle/ |
./bundle |
the rest of tsbundle 3.7.0 |
ts_tapbundle_protocol/, ts_tapbundle/, ts_tapbundle_serverside/ |
./tapbundle_protocol, ./tapbundle, ./tapbundle_serverside |
tstest 8.0.0's tapbundle family; documented in ts_tapbundle/readme.md, ts_tapbundle_serverside/readme.md, ts_tapbundle_protocol/readme.md and the protocol's design notes ts_tapbundle_protocol/protocol.md |
ts_test/ |
./test |
the tstest 8.0.0 runner; documented in ts_test/readme.md |
ts_sea_config/, ts_sea_cache/, ts_sea/ |
./sea/config, ./sea/cache, ./sea |
tssea ts_config/, ts_cache/ and ts/ |
ts_watch/ |
./watch |
tswatch 7.1.1 |
ts_docs_web/ |
(bundled) | tsdoc 3.0.2's documentation website UI (ts_web/, html/), bundled into ts_docs/bundled_website.ts |
ts_docs/ |
./docs, ./docs/website |
tsdoc 3.0.2 |
A module imports another module only through that module's index.ts; modules load one another on demand, so a command pays only for what it uses.
@git.zone/notices
The notices module is also published as its own package, @git.zone/notices: the third-party notices format and resolver that tsrepo's bundle and sea modules and @git.zone/rsrepo share. It depends only on Node.js builtins and @push.rocks/smartconfig, not on @git.zone/tsrepo or @git.zone/rsrepo, so a tool that writes or reads notices installs neither toolchain. It carries ts_bundle_notices/ and tsrepo's configuration resolver ts_shared/, has the API of @git.zone/tsrepo/bundle/notices and the version of the tsrepo release it ships with; gitzone release publishes it before @git.zone/tsrepo. tsrepo itself uses the module in-tree.
WebAssembly
@git.zone/rsrepo compiles Rust crates to wasm32-unknown-unknown and writes <name>.wasm, a dependency-free ESM loader <name>.js, <name>.d.ts and the crates' third-party notices into a package's dist_wasm/. The loader finds its binary with the standard asset pattern and reads it with node:fs from a file: URL, or fetches it otherwise:
const wasmUrl = new URL('./<name>.wasm', import.meta.url);
const compileModule = async () => {
if (wasmUrl.protocol === 'file:') {
const { readFile } = process.getBuiltinModule('node:fs/promises');
return WebAssembly.compile(await readFile(wasmUrl));
}
return WebAssembly.compileStreaming(fetch(wasmUrl));
};
export const instantiate = async () => {
const instance = await WebAssembly.instantiate(await compileModule());
return instance.exports;
};
The Node.js branch takes node:fs/promises from process.getBuiltinModule() (Node.js 22.3 and later) rather than an import, so no bundler resolves a Node.js built-in for a browser build.
tsrepo bundle(and every bundle tsrepo builds, Chromium test bundles included) emits the binary of eachnew URL('./<file>.wasm', import.meta.url), a string literal starting with./or../, and rewrites the URL. A browser bundle carries the binary as an asset belowchunks/<namespace>/beside it, with esbuild, rolldown and rspack, split or not; the URL then names that asset relative to the file that contains it. Aplatform: "node"bundle (esbuild or rolldown) stays one file: the URL becomes adata:application/wasm;base64,…URL, which the loader fetches. A URL that names a missing binary fails the build. Browser module workers that import a loader carry the binary the same way, emitted by the worker build into the samechunks/<namespace>/and located relative to the worker chunk (esbuild and rspack; rolldown bundles no workers).- Third-party notices: a bundle whose notices are enabled (by default its production builds) merges the notices of every binary it emits or inlines,
<file>.wasm.third-party-notices.jsonbeside the binary as rsrepo writes it when the project configuresnotices, into<bundle>.third-party-notices.jsonand.txt; the crates appear ascargo:<crate>@<version>entries next to the npm packages, each package and file once. A binary without that file fails such a build, naming the binary and the file it expects, as a bundled npm package without license text does; a malformed one fails it with the parser's message. Development builds write no notices and read none. tsrepo testruns a test file that imports a loader in Node.js, unbundled, and in Chromium, bundled; the Chromium test server sends.wasmfiles asapplication/wasm, whichWebAssembly.compileStreamingrequires.tsrepo buildcompiles onlyts*folders into theirdist_ts*folders and never touchesdist_wasm/. A package ships it when itsfilescover it, asdist_*/**/*does; atspublish.jsonsub-package ships adist_wasm/its modules import when it lists it ininclude("include": ["dist_wasm"]).
The TypeScript compilers
tsrepo buildandtsrepo checkcompile with TypeScript 7, the native compiler, pinned to7.1.0-dev.20260928.1as in tsbuild 6. It is installed under the npm aliastypescript-native("typescript-native": "npm:typescript@7.1.0-dev.20260928.1"), and the build module is its only user.typescriptitself is 6.0.3, the classic compiler API, which the publish and bundle modules and typedoc 0.28 (tsrepo docs typedoc) use.
Configuration
tsrepo reads .smartconfig.json. The key @git.zone/tsrepo takes one subsection per configurable module:
| Subsection | Same shape as the legacy key |
|---|---|
build |
none; tsbuild read no configuration, and the build module reads none yet |
test |
@git.zone/tstest |
bundle |
@git.zone/tsbundle |
docs |
@git.zone/tsdoc |
watch |
@git.zone/tswatch |
sea |
@git.zone/tssea |
A module reads its subsection when it exists, and otherwise its legacy key. A module configured under both fails with an error that names both keys; tsrepo never merges them.
{
"@git.zone/tsrepo": {
"bundle": {
"bundles": [{ "from": "./ts/cli.ts", "to": "./dist_node/main.mjs", "bundler": "esbuild", "platform": "node", "sea": true, "production": true }]
},
"sea": {
"nodeVersion": "26.11.1",
"bundle": "./dist_node/main.mjs",
"outDir": "./dist/binaries",
"targets": [{ "target": "linux-x64", "name": "myapp-linux-x64" }]
},
"watch": { "preset": "npm" }
}
}
testandbundle: for the bundle module this includes thethirdPartyNoticessettings and the notices supplements a bundled package ships in its own.smartconfig.json, andtsrepo bundle init, which adds bundles to thebundlesubsection when it exists and otherwise to@git.zone/tsbundle, and refuses a project configured under both.watch:tsrepo watchstarts from the watch section;tsrepo watch init(and the wizardtsrepo watchruns without one) writes thewatchsubsection when it exists and otherwise@git.zone/tswatch.sea:tsrepo sea buildandfetchread the sea section; a project configured under both keys fails withCONFIG_INVALID. Its validation messages keep naming the block"@git.zone/tssea", as tssea's did.@git.zone/tsrepo/sea/config'sresolveOutputs(block, projectDir)takes the block itself, as@git.zone/tssea/configdid: a tool that reads a project's configuration picks the block by the same rule.docs: the readme command readslegal, andtsrepo docs website(andDocsWebsiteBuilder) reads itswebsiteblock, from the docs section; as tsdoc did, both also fall back to an oldtsdockey (forwebsite, its keys fill in what the docs section leaves unset). The AI settings (model,provider,authMode, theaiblock) are read by@git.zone/tsagentfrom the top-level keys@git.zone/tsagent,tsdocand@git.zone/tsdoconly; it does not read"@git.zone/tsrepo".docsyet, so keep AI settings under@git.zone/tsagentortsdoc. A project that sets"@git.zone/tsrepo".docsand also keeps a@git.zone/tsdocblock is configured under both keys and fails (the readme command and the website both refuse it), so move the AI settings out of@git.zone/tsdocfirst.
The build module reads tsconfig.json, folder tsconfig.json files and tspublish.json exactly as tsbuild 6 does.
Migrating from the single tools
Commands:
| Before | Now |
|---|---|
tsbuild … |
tsrepo build … |
tsbuild check … |
tsrepo check … |
tsrun file.ts … |
tsrepo run file.ts … |
tspublish plan / prepare <dir> |
tsrepo publish plan / prepare <dir> |
tstest [path] …, tstest clean |
tsrepo test [path] …, tsrepo test clean |
tsbundle [command] … |
tsrepo bundle [command] … |
tsdoc, tsdoc typedoc|website|aidoc|readme|description|commit|auth|test … |
tsrepo docs, tsrepo docs typedoc|website|aidoc|readme|description|commit|auth|test … |
tswatch, tswatch init |
tsrepo watch, tsrepo watch init |
tssea build|fetch|help … |
tsrepo sea build|fetch|help … |
a cli.ts.js that calls tsrun.runPath('./cli.child.js', import.meta.url) |
a cli.ts.js that registers tsx (import { register } from 'tsx/esm/api') and imports the TypeScript entry |
Imports:
| Before | Now |
|---|---|
@git.zone/tsbuild |
@git.zone/tsrepo/build |
@git.zone/tsrun |
@git.zone/tsrepo/run |
@git.zone/tspublish |
@git.zone/tsrepo/publish |
@git.zone/tstest |
@git.zone/tsrepo/test |
@git.zone/tstest/tapbundle |
@git.zone/tsrepo/tapbundle |
@git.zone/tstest/tapbundle_serverside |
@git.zone/tsrepo/tapbundle_serverside |
@git.zone/tstest/tapbundle_protocol |
@git.zone/tsrepo/tapbundle_protocol |
@git.zone/tsbundle |
@git.zone/tsrepo/bundle |
@git.zone/tsbundle/notices |
@git.zone/tsrepo/bundle/notices |
@git.zone/tsdoc |
@git.zone/tsrepo/docs |
@git.zone/tsdoc/website |
@git.zone/tsrepo/docs/website |
@git.zone/tswatch |
@git.zone/tsrepo/watch |
@git.zone/tssea |
@git.zone/tsrepo/sea |
@git.zone/tssea/config |
@git.zone/tsrepo/sea/config |
@git.zone/tssea/cache |
@git.zone/tsrepo/sea/cache |
Configuration keys (the legacy keys keep working; see "Configuration"):
| Legacy key | New subsection |
|---|---|
@git.zone/tstest |
"@git.zone/tsrepo".test |
@git.zone/tsbundle |
"@git.zone/tsrepo".bundle |
@git.zone/tsdoc |
"@git.zone/tsrepo".docs (AI settings stay under @git.zone/tsagent or @git.zone/tsdoc; see "Configuration") |
@git.zone/tswatch |
"@git.zone/tsrepo".watch |
@git.zone/tssea |
"@git.zone/tsrepo".sea |
Move a section by renaming it, never by copying it: a module configured under both keys fails. Tools older than tsrepo read only the legacy keys, so move the sections only once the project uses tsrepo.
Dependency build scripts: pnpm 12 refuses to install packages whose build scripts the project has not decided on, and two of tsrepo's dependencies have one. A project whose pnpm-workspace.yaml does not decide on them fails pnpm add -D @git.zone/tsrepo with ERR_PNPM_IGNORED_BUILDS (Ignored build scripts: esbuild, puppeteer). Decide them as tsrepo's own workspace does:
# pnpm-workspace.yaml
allowBuilds:
esbuild: true
puppeteer: false
puppeteer: false skips puppeteer's browser download; Chromium test files and TapNodeTools.createBrowser() then use a google-chrome, chromium or chromium-browser binary from the PATH. A project whose lockfile still resolves sharp to 0.34.x (reached through the docs module's AI dependencies; 0.34 has an install script, a fresh resolution picks one without) fails the same way on sharp until it adds sharp: false, which tsrepo's own workspace also declares: sharp's platform binaries are optional dependencies and need no source build.
- Keep one tsrepo version in a project's install. Two versions (for example a project's tsrepo and an older one pulled in through another dependency) are two copies of every module, so
tapfrom one is not thetapof the other andinstanceofchecks across them fail (ThirdPartyNoticesError,TsseaError). Depend on tsrepo with a caret range (^1.0.0) and runpnpm dedupeafter upgrading;pnpm why @git.zone/tsrepolists the versions installed. - Test files import
tapandexpectfrom@git.zone/tsrepo/tapbundle. A test file that still imports@git.zone/tstest/tapbundleresolves it from an installed@git.zone/tstest. - The tstest run ledger stays in
<git-common-dir>/tstest/. Its run key now carries the tsrepo version, so the firsttsrepo testafter a switch from tstest does not reuse a run recorded by tstest. - Bundle output keeps the owner marker and notices generator
@git.zone/tsbundle, so directories tsbundle wrote stay owned and are updated in place. - tsbundle's packaged default tsconfig is
assets/tsconfig.jsonat the tsrepo package root. - The tssea download cache (
~/.cache/tsseaand the other locations above) and its layout are unchanged;@git.zone/tsrepo/sea/cachedescribes it as@git.zone/tssea/cachedid. tsrepo docs typedocfinds typedoc in tsrepo's own install; tsdoc looked only in<tsdoc>/node_modules/.bin.tsrepo checkis a type check (tsbuild semantics);rsrepo checkis the Rust quality gate.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.