Skip to Content

Compile (ttsc)

Day-to-day compiler commands and the full flag reference.

Build the project

npx ttsc

Reads tsconfig.json from the current directory, type-checks the project, runs every configured plugin, and writes JavaScript + declaration files to outDir. Same as ttsc build.

Type-check only

npx ttsc --noEmit npx ttsc check # same thing, named subcommand

No files written. Non-zero exit on failure. This is the CI form.

Watch

npx ttsc --watch

Re-compiles on save. Pass --preserveWatchOutput to keep your scrollback.

The watcher follows TypeScript-Go’s resolved program inputs instead of guessing from directory names. It reconciles the set after each build and when directory topology changes, so newly included folders, extends configs, project references, loaded declarations, and selected Go plugin sources join the running session. A check-stage plugin may also publish exact local files and glob populations through the project-input protocol; missing exact paths and zero-match globs remain live, while unrelated workspace documents stay quiet. Executable plugin-selection dependencies additionally declare exact reload files and immediate directory topology, causing a fresh launcher evaluation when module resolution or contributor selection changes. Compiler output and cache products are not inputs, so an emit-mode build stays quiet until a real input changes.

In ttsc check --watch, a capable check-stage sidecar and its no-emit Program stay resident across ordinary saves and declared external-data changes. Every cycle still creates fresh rule and reporting state. A config edit, root addition or deletion, selected plugin or Go contributor change, or project-input topology change starts a fresh session; an unsupported sidecar or protocol failure uses the established one-shot check path. Transform and emit builds are unchanged.

Other day-to-day commands

npx ttsc -p tsconfig.build.json # pick a tsconfig npx ttsc --outDir build/server # override outDir npx ttsc --emit # force emit when noEmit: true is set npx ttsc --verbose # print every emitted file path npx ttsc fix # ask check plugins to apply fixes, then re-check npx ttsc format # ask format-capable check plugins to format only npx ttsc cache paths --json # print cache paths for CI npx ttsc prepare # pre-build plugin cache (CI warmup) npx ttsc clean # drop the plugin cache

ttsc fix and ttsc format invoke enabled check-stage plugins that implement those subcommands. With @ttsc/lint, fix applies lint + format autofixes and then re-checks, while format applies only format-class edits. See the Lint & Prettier overview for the common workflow.

Subcommands

SubcommandPurpose
ttscDefault. Same as ttsc build.
ttsc buildBuild + emit.
ttsc checkType-check only. Same as ttsc --noEmit.
ttsc fixAsk enabled check-stage plugins to apply fixes, then run a no-emit check. With @ttsc/lint, this means lint + format autofixes. Cannot combine with --watch or --emit.
ttsc formatAsk format-capable check-stage plugins to apply format-only edits. With @ttsc/lint, this means format-class rules only.
ttsc cache paths --jsonPrint the cache root, plugin binary cache root, Go build cache root, and cacheable roots for CI.
ttsc preparePre-build every plugin binary into the cache.
ttsc cleanDelete the plugin binary cache and ttsc-owned Go build cache.
ttsc versionPrint the CLI version.
ttsc helpPrint help.

Flags

FlagMeaning
-p, --project <file>tsconfig file. Same as --tsconfig.
--tsconfig <file>tsconfig file.
--cwd <dir>Resolve relative paths against this directory.
--noEmitType-check only, no file writes, including a positional single-file build and every rebuild under --watch. check has the same no-write behavior.
--emitForce emit even when tsconfig sets noEmit: true. In the ttsc and ttsc build lanes, --emit=false disables writes and --noEmit=false explicitly restores them. On a plugin build, output stays confined to outDir: a source pulled in from outside rootDir (e.g. a dependency package’s raw .ts entry) whose computed output would escape outDir is skipped instead of written next to its own source.
-w, --watchWatch source files and re-check on change.
--preserveWatchOutputDon’t clear the screen between watch runs.
--outDir <dir>Override compilerOptions.outDir.
--quietSuppress per-file output (default).
--verbosePrint build summary and emitted file paths.
--binary <path>Use a specific tsc binary instead of the bundled one.
--cache-dir <dir>Cache root for compiled plugins, resolved relative to --cwd when not absolute. Defaults to the workspace-local node_modules/.cache/ttsc.
--singleThreadedRun TypeScript-Go single-threaded, one checker, serial parse/check/emit. Mirrors tsc --singleThreaded.
--checkers <n>Type-checker pool size. Mirrors tsc --checkers; defaults to TypeScript-Go’s own default.
--initWrite a starter tsconfig.json and exit. Works in a directory that has no project yet, so it is the first thing you can run in a fresh package.

Flag names are matched the way tsc matches them: case-insensitively, with one or two leading dashes. --noEmit, --noemit, and -noEmit all select the same flag, so a CI script or a shell that spells a flag differently still gets the flag it asked for instead of one ttsc forwards without recognizing.

Any flag ttsc does not own is forwarded as-is to tsc, so classic tsc compilerOptions work on the command line too, ttsc --strict src/main.ts, ttsc --target es2020, ttsc --listFiles. The usual home for target, module, strict, lib, and friends is still tsconfig.json; the CLI form just overrides it for one run. On a project that configures a ttsc plugin, the forwarded flag still reaches the in-process compiler, though tsc’s reporting flags (--listFiles, --showConfig, …) only print on a plain, plugin-free build. A plugin build receives the flags through the environment rather than its command line, so a sidecar with a strict flag parser is never handed an option it does not know; see Plugin protocol → Environment.

Build information

incremental and composite make tsc write a .tsbuildinfo next to the tsconfig, or wherever tsBuildInfoFile points. ttsc writes the same file, in the same format and at the same path, on both lanes: a plain build, and a build a ttsc plugin owns.

What ttsc does not do is read one back to skip work. A plugin’s output is not a function of the source text alone, and a .tsbuildinfo records only what tsc knows about: file versions, resolved options, and signatures. It has no room for the plugin binary, its config file, or its contributors, so reusing a previous run’s record could serve output produced by a plugin that has since changed. Every ttsc build therefore emits every file, and the build information it leaves behind is a record for other consumers — a CI cache key, a later tsc run that should treat the outputs as current — rather than an input to the next ttsc run.

One consequence is worth knowing if you inspect the file: on a build where the plugin assembled the JavaScript itself, the record marks the JavaScript emit of each file as still pending, because tsc’s own emitter never ran for it. The error is one-directional — a consumer can only decide to emit again, never to skip a file the plugin actually transformed.

Exit codes

CodeMeaning
0Success, no errors. Warnings may have been printed.
1Build failure, type errors, lint errors, or plugin failure.
2Configuration error, bad tsconfig.json, missing plugin, invalid flags.

Plugin cache

The plugin binary cache is project-local. By default it lives in the workspace’s node_modules/.cache/ttsc (the find-cache-dir convention). In a monorepo it resolves to the workspace root’s node_modules, so every package builds a given plugin once and shares it; rm -rf node_modules (or deleting the repo) reclaims everything and ttsc keeps no machine-global cache. Plugin binaries live under <cache-dir>/plugins and ttsc’s Go object cache under <cache-dir>/go-build, unless TTSC_GO_CACHE_DIR or a user GOCACHE redirects it.

Override the root with --cache-dir or TTSC_CACHE_DIR (relative --cache-dir resolves from --cwd, relative TTSC_CACHE_DIR/TTSC_GO_CACHE_DIR from the project root). Point these at a directory dedicated to ttsc, and use ttsc as the root’s basename when ttsc clean should remove its nested go-build/ as well as plugins/. The basename is a deletion-safety boundary, so do not set TTSC_CACHE_DIR to a shared cache such as ~/.cache; use a dedicated path such as .ttsc-cache/ttsc. Before deleting anything, ttsc clean validates every resolved target, including an external TTSC_GO_CACHE_DIR, and refuses a filesystem root or a lexical/physical path that equals or contains the project.

The cache is safe to delete at any time (ttsc clean); binaries rebuild on next use. The default cache prunes plugin binaries unused for 30 days and bounds its Go object cache at 8 GiB, pruning toward 6 GiB outside active builds. Explicit cache roots and a user GOCACHE remain caller-owned. ttsc clean also reclaims the machine-global cache left by pre-0.17 releases.

Every CI job and every container build starts from a clean filesystem, so a source plugin recompiles on each one unless the cache is carried across. Build cache covers that: what invalidates the cache, why a CI key has to separate architectures and not only operating systems, the npm ci ordering trap that silently disables the cache, and worked GitHub Actions and Docker recipes.

See also

Last updated on