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

@git.zone/tsbuild

v6.0.2

Published

A tool for compiling TypeScript files using the latest nightly features, offering flexible APIs and a CLI for streamlined development.

Readme

@git.zone/tsbuild

A powerful, modern TypeScript build tool with smart defaults, full tsconfig.json support, automatic output directory management, and cross-module import path rewriting.

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/tsbuild

Why tsbuild?

| Feature | Description | |---------|-------------| | Smart tsconfig.json Integration | Respects all your compiler options with intelligent merging | | Protected Defaults | Critical build settings are safeguarded while staying flexible | | Folder tsconfig.json | A source folder can compile in its own environment, such as a service worker with the webworker library, and editors read the same settings | | Zero Config | Works perfectly without tsconfig.json | | Glob Pattern Support | Compile multiple directories with a single command | | Dependency-Aware | Automatically orders compilation based on module dependencies | | Type Checking | Validate code without emitting files | | Clean Builds | Automatically clears output directories before compilation | | Auto-Unpack | Flattens nested output directories automatically | | Import Path Rewriting | Rewrites cross-module imports to point at compiled output | | CI/CD Ready | JSON output mode and proper exit codes | | Modern Defaults | ESNext, NodeNext modules, decorators out of the box |

Quick Start

CLI Usage

Compile your TypeScript project:

pnpm exec tsbuild

Compiles ./ts/**/*.ts to ./dist_ts/

Custom directories:

pnpm exec tsbuild custom src utils

Compiles:

  • ./src/**/*.ts -> ./dist_src/
  • ./utils/**/*.ts -> ./dist_utils/

Auto-discover and compile in dependency order:

pnpm exec tsbuild tsfolders

Finds all ts_* folders and compiles them respecting dependencies.

Programmatic Usage

Basic compilation:

import { TsCompiler } from '@git.zone/tsbuild';

const compiler = new TsCompiler();
await compiler.compileFilesOrThrow([
  './src/index.ts',
  './src/utils.ts'
], { outDir: './dist' });

Production-ready with error tracking (recommended):

import { TsCompiler } from '@git.zone/tsbuild';

const compiler = new TsCompiler();
const result = await compiler.compileFiles([
  './src/index.ts',
  './src/utils.ts'
], { outDir: './dist' });

if (result.errorSummary.totalErrors > 0) {
  console.error(`Compilation failed with ${result.errorSummary.totalErrors} errors`);
  process.exit(1);
}

console.log(`Compiled ${result.emittedFiles.length} files successfully!`);

Glob pattern compilation:

import { TsCompiler } from '@git.zone/tsbuild';

const compiler = new TsCompiler();
await compiler.compileGlob({
  './ts/**/*.ts': './dist_ts',
  './ts_web/**/*.ts': './dist_web'
});

The TypeScript compiler

tsbuild compiles with TypeScript 7, the native compiler, pinned to one exact version ([email protected]). TypeScript 7's JavaScript API (typescript/unstable/sync) starts a tsc --api process and talks to it over an unversioned protocol that may change with any release, so tsbuild does not accept a version range: every TypeScript upgrade is a deliberate tsbuild release, and tsbuild's golden tests compare the exact output and diagnostics of each fixture to catch any drift.

The pin is a development build because TypeScript 7.0, the stable release, ships an earlier version of that API, without the programs, the emit and the in-memory files tsbuild compiles with; the API is complete from 7.1.0-dev.20260919.1. Builds from 7.1.0-dev.20260929.1 on report TS5115 ("Instantiations of the following types appear infinitely circular") in the declarations of zod 4.5.0 to 4.6.5 (microsoft/TypeScript#64605), so tsbuild pins the last build before that regression.

tsbuild 5, on TypeScript 6, continues as a maintenance line for projects that cannot migrate yet:

pnpm add --save-dev @git.zone/tsbuild@^5
  • One tsc --api process serves each tsbuild operation (a compile, a type check, an emit check); it starts with the operation and stops when the operation ends, also when it fails. It runs in the project directory, the one TsCompiler was given, because TypeScript finds the packages types names, and the default type roots, from there; operations on different projects in one Node process each run their own.
  • tsbuild's API speaks in its own types: compiler options as a tsconfig.json spells them (ICompilerOptions, for example { module: 'nodenext', lib: ['dom', 'esnext'] }) and diagnostics as ITsDiagnostic (file, one-based line and column, code, category, message). No type of the TypeScript compiler appears in tsbuild's API.
  • tsbuild no longer re-exports the compiler. Tooling that works on TypeScript sources - codemods, AST assertions in tests - depends on typescript itself. Note that TypeScript 7 no longer ships the classic JavaScript compiler API (createSourceFile, forEachChild, SyntaxKind, ...) under typescript; its parser and AST are reachable through typescript/unstable/sync (API.createSourceFile) and typescript/unstable/ast, and a tool that needs the classic API stays on typescript@6.
  • TypeScript 7 removed options that TypeScript 6 only deprecated: target: "es5", moduleResolution: "node10" (also spelled "node") and "classic", esModuleInterop: false, allowSyntheticDefaultImports: false, alwaysStrict: false, baseUrl, downlevelIteration, outFile and module amd, umd and system. A project that still sets one fails to compile with TypeScript's own error (TS5102 or TS5108) naming the option.

Migrating from tsbuild 5

  1. Remove the options TypeScript 7 removed from every tsconfig.json tsbuild reads, the project's and each source folder's:

    | Option | Replacement | |--------|-------------| | baseUrl | Remove it and write each paths target relative to the tsconfig.json ("./ts_shared/*") | | moduleResolution: "node", "node10", "nodejs", "classic" | "nodenext", tsbuild's default, or "bundler" for code that only a bundler reads | | target: "es5", "es3" | Remove it (tsbuild's default is esnext) or name es2015 or later | | downlevelIteration | Remove it; it only applied to ES5 output | | esModuleInterop: false, allowSyntheticDefaultImports: false, alwaysStrict: false | Remove them; TypeScript 7 always applies these | | outFile, module: "amd", "umd", "system", "none" | Remove them and bundle the output, for example with @git.zone/tsbundle |

  2. Import TypeScript yourself. Code that imported @git.zone/tsbuild/typescript, or typescript, CompilerOptions, ScriptTarget or ModuleKind from @git.zone/tsbuild, depends on typescript directly: typescript@^6 for the classic compiler API (createProgram, createSourceFile, SyntaxKind, ...).

  3. Expect a few new type errors. TypeScript 7 infers some types for which TypeScript 6 fell back to any under tsbuild's noImplicitAny: false, such as a variable that its own initializer references (const host = new Host({ ready: () => host.started })). Calls through such a variable are type-checked now, and the errors they report are genuine.

  4. Drop memory workarounds for type checks. The checker runs in the native tsc process, outside Node's heap: a --max-old-space-size set for tsbuild no longer applies to it, and tsbuild 6 has no checkerTypeLimit, the bounded checker of tsbuild 5.2.

  5. Expect declarations that differ in form only. Against tsbuild 5, the emitted JavaScript is the same apart from its inline source maps. Declarations print some types differently: as typeof the declaration a value comes from (with an added import, or a declare const for a module-local function), with union and object members in another order, with double-quoted strings, unknown instead of unknown | undefined, and JSDoc kept on tuple elements.

  6. Keep relying on installed @types packages, or name them. TypeScript 5 included every installed @types package; TypeScript 6 and 7 include only the ones types names, and none without it, so process, Buffer or NodeJS became unknown (TS2591, TS2694) in projects that relied on the old default. From 6.0.1 on, tsbuild sets types: ["*"] when no tsconfig.json sets types: TypeScript's spelling for every package in the type roots (those typeRoots names, or else node_modules/@types of the project and of its parent directories), so such a project builds as it did. A types list in the project's or a folder's tsconfig.json, or in programmatic options, replaces it unchanged. Editors read the tsconfig.json with TypeScript 6 or 7 rules and see no @types package it does not name; name them there ("types": ["node"]) for the editor to see what tsbuild compiles with.

CLI Commands

1. Default Build

pnpm exec tsbuild [options]

Compiles all TypeScript files from ./ts/ to ./dist_ts/

Options: | Flag | Description | |------|-------------| | --skiplibcheck | Skip type checking of declaration files | | --confirmskiplibcheck | Skip lib check with extended warning (5s pause) | | --disallowimplicitany | Disallow implicit any types | | --commonjs | Emit CommonJS instead of ECMAScript modules: module: "commonjs", moduleResolution: "bundler" and verbatimModuleSyntax: false | | --json | Output results as JSON (for CI/CD) | | --quiet | Suppress console output |

Examples:

# Standard build
pnpm exec tsbuild

# Build with JSON output for CI
pnpm exec tsbuild --json --quiet

# CommonJS build
pnpm exec tsbuild --commonjs

# Strict mode
pnpm exec tsbuild --disallowimplicitany

2. Custom Directories

pnpm exec tsbuild custom <dir1> <dir2> ... [options]

Compile specific directories to their corresponding dist_ folders.

# Compile src and utils
pnpm exec tsbuild custom src utils
# Creates: ./dist_src/ and ./dist_utils/

# Multiple directories with options
pnpm exec tsbuild custom api models services --commonjs

3. TSFolders (Dependency-Aware)

pnpm exec tsbuild tsfolders [options]

Automatically discovers and compiles all ts_* folders in dependency order:

  1. Prioritizes ts_interfaces first (if no tspublish.json)
  2. Prioritizes ts_shared second (if no tspublish.json)
  3. Reads tspublish.json in each folder for order property
  4. Compiles in correct sequence

Example output:

TypeScript Folder Compilation Plan (5 folders)
  1. ts_interfaces
  2. ts_shared
  3. ts_core
  4. ts_utils
  5. ts_modules

4. Emit Check

pnpm exec tsbuild emitcheck <file_or_pattern> [more...] [options]

Validates TypeScript files can be compiled without actually emitting them.

# Check specific files
pnpm exec tsbuild emitcheck src/main.ts src/utils.ts

# Check with glob patterns
pnpm exec tsbuild emitcheck "src/**/*.ts" "test/**/*.ts"

Exit codes:

  • 0 - All files can be emitted
  • 1 - One or more files have errors

5. Type Check

pnpm exec tsbuild check [pattern] [more...] [options]

Performs type checking without emitting files.

With arguments: Check specified files/patterns

pnpm exec tsbuild check "ts/**/*.ts"
pnpm exec tsbuild check "src/**/*.ts" "test/**/*.ts"

Without arguments: Two-phase default check

  1. Phase 1: Type check ts/**/* (strict, includes .d.ts)
  2. Phase 2: Type check test/**/* (relaxed, skipLibCheck: true)
pnpm exec tsbuild check
# Running default type checking sequence...
# Checking ts/**/* files...
# Checking test/**/* files with --skiplibcheck...
# All default type checks passed!

Both forms check every file under the compiler options of the folder that owns it, including files that a checked file imports from another folder; see Folder tsconfig.json.

API Reference

TsCompiler Class

The main class for TypeScript compilation.

import { TsCompiler } from '@git.zone/tsbuild';

const compiler = new TsCompiler(cwd?: string, argvArg?: any);

Constructor Parameters:

  • cwd - Working directory (defaults to process.cwd())
  • argvArg - CLI arguments object for flags like --skiplibcheck, --quiet, etc.

compileFiles(fileNames, customOptions?, taskInfo?)

Compile files with error tracking. Returns result instead of throwing.

const result = await compiler.compileFiles(
  ['./src/index.ts', './src/utils.ts'],
  { outDir: './dist' }
);

console.log(`Emitted: ${result.emittedFiles.length} files`);
console.log(`Errors: ${result.errorSummary.totalErrors}`);

Returns: Promise<ICompileResult>

interface ICompileResult {
  emittedFiles: string[];
  errorSummary: IErrorSummary;
}

interface IErrorSummary {
  errorsByFile: Record<string, ITsDiagnostic[]>;
  generalErrors: ITsDiagnostic[];
  totalErrors: number;
  totalFiles: number;
}

// tsbuild's own diagnostic type
interface ITsDiagnostic {
  fileName: string | null; // absolute; null for a problem of the compilation as a whole
  line: number | null; // one-based
  column: number | null; // one-based
  code: number; // 2322 for TS2322; 0 for tsbuild's own diagnostics
  category: 'error' | 'warning' | 'suggestion' | 'message';
  message: string; // a chain of messages, one per line, indented by depth
  sourceLine: string | null;
}

customOptions of every method are compiler options as a tsconfig.json spells them (ICompilerOptions); relative paths resolve from cwd.

compileFilesOrThrow(fileNames, customOptions?)

Compile files and throw on error. For simple scripts.

try {
  const emittedFiles = await compiler.compileFilesOrThrow(
    ['./src/index.ts'],
    { outDir: './dist' }
  );
  console.log('Compiled:', emittedFiles);
} catch (error) {
  console.error('Compilation failed!');
  process.exit(1);
}

Returns: Promise<string[]> - Array of emitted file paths

compileGlob(globPatterns, customOptions?)

Compile multiple glob patterns to different destinations. Automatically clears output directories before compilation, unpacks nested output, and rewrites cross-module import paths.

const result = await compiler.compileGlob({
  './ts/**/*.ts': './dist_ts',
  './ts_web/**/*.ts': './dist_web',
  './ts_node/**/*.ts': './dist_node'
});

Returns: Promise<ICompileResult>

checkTypes(fileNames, customOptions?)

Type check files without emitting. Fast validation.

const success = await compiler.checkTypes(['./src/**/*.ts']);

if (!success) {
  console.error('Type errors found!');
  process.exit(1);
}

Returns: Promise<boolean> - true if no errors

checkEmit(fileNames, customOptions?)

Validate files can be emitted without actually emitting.

const canEmit = await compiler.checkEmit(['./src/index.ts']);

if (!canEmit) {
  console.error('Cannot emit these files!');
}

Returns: Promise<boolean> - true if can emit

createOptions(customOptions?, folderName?)

Get merged compiler options (useful for debugging). Pass a folder name to include the compiler options that folder adds in its own tsconfig.json.

const options = compiler.createOptions({ strict: true });
console.log(options); // Shows merged options

const workerOptions = compiler.createOptions({}, 'ts_web_serviceworker');
console.log(workerOptions.lib); // ['webworker', 'esnext']

Supporting Classes

TsConfig

TypeScript configuration management.

import { TsConfig } from '@git.zone/tsbuild';

const config = new TsConfig(process.cwd());
const options = config.merge({ target: 'ES2022' }, argvArg);

// Folder compiler options: what ts_web_serviceworker/tsconfig.json adds to the project's, or null without one
const folderOptions = config.loadFolder('ts_web_serviceworker');
// Every source folder with its own tsconfig.json; throws for the first invalid one
const configuredFolders = config.getConfiguredFolders(); // ['ts_web_serviceworker']
// The folder whose options govern a file, or null for files on the project options
const owner = config.getOwningFolder('./ts_web_serviceworker/index.ts'); // 'ts_web_serviceworker'
const workerOptions = config.merge({}, argvArg, 'ts_web_serviceworker');

TsPublishConfig

Reads tspublish.json for module configuration. A missing file means no configuration; a file that cannot be read, is not a JSON object, or still carries the compilerOptions key of tsbuild 4.6.0 throws with its path.

import { TsPublishConfig } from '@git.zone/tsbuild';

const pubConfig = new TsPublishConfig('./ts_core');
console.log(pubConfig.shouldUnpack);    // true/false
console.log(pubConfig.order);           // number, Infinity if unset

TsUnpacker

Flattens nested TypeScript output directories.

import { TsUnpacker } from '@git.zone/tsbuild';

const unpacker = new TsUnpacker('ts_core', './dist_ts_core');
await unpacker.unpack();

TsPathRewriter

Rewrites cross-module import paths in compiled output files. When TypeScript compiles files that import from sibling directories (e.g., ../ts_shared/helper.js), TsPathRewriter rewrites those paths to reference the compiled output directories (../dist_ts_shared/helper.js).

import { TsPathRewriter } from '@git.zone/tsbuild';

// Auto-detect all ts_* folders in the project
const rewriter = await TsPathRewriter.fromProjectDirectory(process.cwd());
const filesModified = await rewriter.rewriteDirectory('./dist_ts_core');

// Or from explicit glob patterns
const rewriter2 = TsPathRewriter.fromGlobPatterns({
  './ts_core/**/*.ts': './dist_ts_core',
  './ts_shared/**/*.ts': './dist_ts_shared',
});

FsHelpers

Static filesystem utilities.

import { FsHelpers } from '@git.zone/tsbuild';

const files = await FsHelpers.listFilesWithGlob('./', 'ts/**/*.ts');
const exists = await FsHelpers.fileExists('./tsconfig.json');
const dirExists = await FsHelpers.directoryExists('./ts');

TsBuildCli

CLI command handler. Used internally by the CLI.

import { TsBuildCli, runCli } from '@git.zone/tsbuild';

// Run the CLI
runCli();

// Or with custom working directory
const cli = new TsBuildCli('/path/to/project');
cli.run();

Configuration

tsconfig.json Support

tsbuild fully supports all compiler options from tsconfig.json. Your project configuration is respected and intelligently merged.

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "verbatimModuleSyntax": true
  }
}

Configuration Priority (6 Levels)

Compiler option values use TypeScript's standard JSON syntax. For example, "lib": ["ES2022"] selects the ES2022 library and replaces the default DOM/ESNext selection; Node projects can pair it with "types": ["node"]. TSBuild validates these values with TypeScript's tsconfig.json parser, reports invalid values with their TypeScript diagnostic codes and hands them to TypeScript as written; relative paths resolve from the project directory. Each compilation task retains its own inferred source root and output layout.

A type check (check, emitcheck, checkTypes(), checkEmit()) emits nothing and therefore runs without outDir and declarationDir: a test can import the package by its own name through the exports map of its package.json (@scope/package/module), and the import resolves to the declarations of the last build, as with tsc -p, so build before you check. The package's own sources import each other by relative path; an import of the package by its own name from the compiled ts/ sources is not supported.

When multiple configuration sources exist, they merge in this order (later overrides earlier):

| Priority | Source | Description | |----------|--------|-------------| | 1 | Default Options | tsbuild's sensible defaults | | 2 | tsconfig.json | All options from your tsconfig.json (if present) | | 3 | Folder tsconfig.json | What the tsconfig.json of the source folder that owns the files adds (if present), see Folder tsconfig.json | | 4 | Protected Defaults | Critical options for build integrity | | 5 | Programmatic Options | Options passed to API functions | | 6 | CLI Flags | Command-line arguments (highest priority) |

Protected Options

These options cannot be overridden by tsconfig.json, and a folder tsconfig.json cannot set them at all (but they can be overridden programmatically or via CLI):

| Option | Value | Reason | |--------|-------|--------| | outDir | 'dist_ts/' | Required for automatic path transformations | | noEmitOnError | true | Prevents broken builds from being emitted | | declaration | true | Ensures .d.ts files for library consumers | | inlineSourceMap | true | Consistent debugging experience |

Default Compiler Options

When no tsconfig.json exists:

{
  declaration: true,              // Generate .d.ts files
  inlineSourceMap: true,          // Debug-friendly
  noEmitOnError: true,            // Fail-fast on errors
  outDir: 'dist_ts/',             // Output directory
  module: 'nodenext',             // Modern Node.js modules
  target: 'esnext',               // Latest JavaScript
  moduleResolution: 'nodenext',
  lib: ['dom', 'esnext'],         // Browser globals and the latest ECMAScript library
  noImplicitAny: false,           // Flexible for quick development
  verbatimModuleSyntax: true,     // Explicit imports/exports
  types: ['*']                    // Every installed @types package, as TypeScript 5 included them
}

TypeScript 7 always applies the CommonJS interop that esModuleInterop switched on, so tsbuild no longer sets it.

Folder tsconfig.json

A source folder - ts or ts_* - can compile in its own environment. The typical case is a service worker or web worker: its code needs the webworker library, which cannot be combined with the DOM library of the defaults because both declare the same globals differently (TS2403). Give the folder a tsconfig.json that extends the project's tsconfig.json, for example ts_web_serviceworker/tsconfig.json:

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "lib": ["webworker", "esnext"]
  }
}

The folder then compiles against the worker globals, and without the DOM:

declare const self: ServiceWorkerGlobalScope;

self.addEventListener('install', (eventArg: ExtendableEvent) => {
  eventArg.waitUntil(self.skipWaiting());
});

self.addEventListener('fetch', (eventArg: FetchEvent) => {
  eventArg.respondWith(fetch(eventArg.request));
});

What tsbuild reads. For every file below the folder, tsbuild merges what the folder's tsconfig.json adds over the project's options, in tsbuild, custom, tsfolders, check, emitcheck and the TsCompiler API. Protected options still apply, programmatic options and CLI flags still win, and a list such as lib or types replaces the inherited one. The options use the tsconfig.json syntax, comments included, and TypeScript validates them; path-valued options such as typeRoots resolve from the file that declares them. tsbuild never changes the project's tsconfig.json.

What the editor sees. An editor applies the nearest tsconfig.json to a file, so it checks the folder's files with the same settings tsbuild uses: the project's plus the folder's. tsbuild keeps it that way:

  • extends must lead to the project's tsconfig.json, directly or through other tsconfig files of the project, such as a ../tsconfig.worker.json that extends ./tsconfig.json. A folder cannot detach from the project's settings.
  • A folder tsconfig.json holds only extends and compilerOptions. tsbuild compiles every file below the folder with it, which is exactly the set of files an editor applies it to when it leaves the file selection to TypeScript's default; files, include and exclude would make the two differ and are rejected. For the same reason the project's tsconfig.json must not declare files or include, which the folder's tsconfig.json would inherit. references is rejected as well: tsbuild reads other folders through their declarations itself.
  • The defaults and protected options of tsbuild are in no tsconfig.json, for the folder as for the project.
  • At a folder boundary tsbuild is stricter than an editor: an editor that shows a file importing the folder checks the folder's sources under that file's settings, while tsbuild reads the folder's declarations (see below).

Allowed options. A folder tsconfig.json may add environment settings and switch on stricter checks, never weaken checking:

| Kind | Options | |------|---------| | Environment | lib, types, typeRoots, target, jsx, jsxFactory, jsxFragmentFactory, jsxImportSource | | Stricter checks, true only | strict, noImplicitAny, strictNullChecks, strictFunctionTypes, strictBindCallApply, strictPropertyInitialization, strictBuiltinIteratorReturn, noImplicitThis, useUnknownInCatchVariables, alwaysStrict, noUnusedLocals, noUnusedParameters, exactOptionalPropertyTypes, noImplicitReturns, noFallthroughCasesInSwitch, noUncheckedIndexedAccess, noImplicitOverride, noPropertyAccessFromIndexSignature, noUncheckedSideEffectImports, isolatedModules, isolatedDeclarations, erasableSyntaxOnly, verbatimModuleSyntax, forceConsistentCasingInFileNames | | Stricter checks, false only | allowUnreachableCode, allowUnusedLabels |

Every other option - rootDir, outDir, paths, module, moduleResolution, declaration, noEmitOnError, inlineSourceMap and the rest - belongs in the project's tsconfig.json; options that weaken checking, such as skipLibCheck and noCheck, belong nowhere. tsbuild rejects it, and a strictness flag set to its weaker value, with an error that names the option and the file. Only ts and ts_* folders carry their own tsconfig.json; tsbuild does not read one in another folder such as test/, whose files compile with the project's options.

Imports across folders. A file that imports a folder with its own tsconfig.json reads it through the declarations that folder emits under its own options - the .d.ts files its dist_ folder publishes, generated in memory, so the build order does not matter. tsbuild emits these declarations folder by folder in dependency order, each folder over the declarations of the folders it imports, and hands them to the importing program in place of the folder's sources:

  • Types keep their meaning across the boundary, including types the folder infers from its own environment. A type the importing side cannot express, such as FetchEvent in a DOM folder or a test, is an error in those declarations, reported with the source file and the folder's tsconfig.json, instead of silently turning into any. Keep what both sides use expressible in both environments, or in a folder without its own tsconfig.json such as ts_interfaces.
  • The imported files are type-checked under their own folder's options: by the folder's own task when the same tsbuild, custom, tsfolders or compileGlob() run compiles that folder, otherwise by the compilation or check that imports them. Their errors fail it.
  • A compilation never emits the imported folder's files; its output imports the folder's dist_ folder.
  • Folders with their own tsconfig.json cannot import each other in a cycle, just as TypeScript project references cannot.
  • skipLibCheck skips declaration files, and with them the declarations read at a folder boundary, as it does for project references in tsc --build.
  • compileFiles() refuses root files of folders with different options, because one program has one set of options. Compile such folders as separate tasks, as compileGlob() and tsfolders do.

Errors before cleaning. Before compileGlob() - and with it every build command - clears an output directory, it reads the project's tsconfig.json, every folder tsconfig.json, the owning folder of every task and the tspublish.json that decides how a task's output is unpacked. An invalid configuration fails the build and leaves the previous output in place; the CLI prints the error, or {"success": false, "error": "..."} under --json, and exits with code 1.

Path Transformation

tsbuild automatically transforms path mappings:

tsconfig.json:

{
  "compilerOptions": {
    "paths": {
      "@models/*": ["./ts_models/*"]
    }
  }
}

Automatic transformation:

./ts_models/* -> ./dist_ts_models/*

Features

Clean Builds

Output directories are automatically cleared before compilation:

Clearing output directory: ./dist_ts
Compiling 14 files from ./ts/**/*.ts

This ensures no stale files remain from previous builds.

Import Path Rewriting

When working with multi-module projects (multiple ts_* folders), TypeScript compiles import paths relative to source directories. After compilation and unpacking, these paths would be wrong because the directory structure changes.

tsbuild automatically detects all ts_* folders in the project and rewrites import paths in the compiled output:

# Source code
import { helper } from '../ts_shared/helper.js';

# Compiled output (after rewriting)
import { helper } from '../dist_ts_shared/helper.js';

This works for ES module imports, dynamic import() calls, and CommonJS require() statements. The rewriting happens automatically as part of compileGlob().

Monorepo Support with tspublish

tsbuild is designed to work seamlessly with @git.zone/tspublish for monorepo workflows. This enables building and publishing multiple packages from a single repository.

Directory Structure

my-project/
  ts/                    # Main package source
  ts_interfaces/         # Shared interfaces (order: 1)
  ts_shared/             # Shared utilities (order: 2)
  ts_core/               # Core logic (order: 3)
  ts_web/                # Web-specific code (order: 4)
  ts_node/               # Node-specific code (order: 5)

Each ts_* folder can contain its own tspublish.json to configure compilation and publishing behavior.

tspublish.json Configuration

Create a tspublish.json in each publishable ts_* folder. @git.zone/tspublish expects publishing fields, while tsbuild additionally reads order and unpack for build ordering and output flattening. A folder's compiler options live in its own tsconfig.json, not here; a compilerOptions key in tspublish.json fails the build with a message naming that file:

{
  "name": "@myorg/core",
  "order": 3,
  "unpack": true,
  "dependencies": ["@myorg/interfaces", "@myorg/shared"],
  "registries": ["useBase"],
  "bin": []
}

| Option | Type | Default | Description | |--------|------|---------|-------------| | name | string | -- | Package name for publishing | | order | number | Infinity | Build sequence (lower builds first) | | unpack | boolean | true | Flatten nested output directories | | dependencies | string[] | [] | Published package dependencies resolved from the monorepo package.json | | registries | string[] | -- | Publish destination; gitzone release requires ["useBase"] and publishes to the registries in release.targets.npm of .smartconfig.json | | bin | string[] | [] | CLI binary names generated by @git.zone/tspublish |

Build Order

The tsfolders command respects the order property:

pnpm exec tsbuild tsfolders

Default ordering (without tspublish.json):

  1. ts_interfaces - always first (shared types)
  2. ts_shared - always second (shared utilities)
  3. Other folders sorted by order property
  4. Folders without order are built last

Auto-Unpack

When TypeScript compiles files that import from sibling directories, it creates nested output:

dist_ts_core/
  ts_core/        <-- nested output
  ts_shared/      <-- pulled-in dependency

tsbuild automatically flattens this to:

dist_ts_core/
  index.js        <-- flat, clean structure

This is especially important for monorepos where packages import from each other.

Control via tspublish.json:

{
  "unpack": true
}
  • "unpack": true (default) - Flatten nested directories after compilation
  • "unpack": false - Keep original nested structure

What gets unpacked:

  • The source folder's contents (ts_core/) are moved to the root of dist_ts_core/
  • Sibling folders (ts_shared/) that were pulled in are removed (they have their own dist)

Decorator Support

tsbuild supports both TC39 standard decorators (recommended) and legacy experimental decorators.

TC39 Standard Decorators (Preferred)

We strongly recommend using TC39 standard decorators for all new code. They are the official JavaScript standard and provide better semantics:

// TC39 standard decorator
function log(target: any, context: ClassMethodDecoratorContext) {
  return function (...args: any[]) {
    console.log(`Calling ${String(context.name)}`);
    return target.apply(this, args);
  };
}

class UserService {
  @log
  getUser(id: string) {
    return { id, name: 'John' };
  }
}

Legacy Decorators (Backwards Compatibility)

For existing projects using frameworks that still require experimental decorators:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Structured Logging

tsbuild uses a 4-level visual hierarchy for console output, making it easy to follow compilation progress:

  • HEADER - Top-level section start (emoji + bold text + separator)
  • STEP - Major action within a section (emoji + text)
  • DETAIL - Supplementary info under a step (indented)
  • SUCCESS/ERROR/WARN - Outcome indicators with color coding

All output uses ANSI color codes for terminal readability. Use --quiet to suppress output or --json for machine-readable format.

CI/CD Integration

GitHub Actions

name: Build
on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'

      - run: pnpm install
      - run: pnpm exec tsbuild

JSON Output

pnpm exec tsbuild --json --quiet
{
  "success": true,
  "totals": {
    "errors": 0,
    "filesWithErrors": 0,
    "tasks": 1
  },
  "errorsByFile": {}
}

When the build cannot start, for example because a folder tsconfig.json is invalid, the output is the error instead, and the exit code is 1:

{
  "success": false,
  "error": "/path/to/project/ts_web_serviceworker/tsconfig.json: compilerOptions.skipLibCheck is not allowed in a folder tsconfig.json. ..."
}

Package.json Scripts

{
  "scripts": {
    "build": "tsbuild",
    "build:prod": "tsbuild --disallowimplicitany",
    "typecheck": "tsbuild check",
    "pretest": "tsbuild emitcheck 'test/**/*.ts'"
  }
}

Troubleshooting

Common Issues

"Cannot find module" errors in compiled output

Make sure path mappings are configured in tsconfig.json. tsbuild automatically transforms them.

Decorator errors

Ensure your tsconfig.json has:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  }
}

Slow compilation

Use --skiplibcheck to skip declaration file checking:

pnpm exec tsbuild --skiplibcheck

Only use this if you trust your dependencies' type definitions.

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 [email protected].

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.