Skip to Content

Internals: Build and Cache

This page is for debugging source plugin builds.

Cold Build Path

When ttsc sees a plugin source descriptor:

  1. Resolve source.
  2. Hash the plugin source and host versions.
  3. Reuse the cached binary on hit.
  4. Copy source into a scratch directory on miss.
  5. Generate a go.work overlay pointing at ttsc and its shims, plus (when the plugin’s go.mod does not declare github.com/samchon/ttsc/packages/ttsc) a synthesized replace github.com/samchon/ttsc/packages/ttsc v0.0.0 => <workspace overlay> block. The replace block is the seam that lets a plugin’s bare require github.com/samchon/ttsc/packages/ttsc v0.0.0 resolve to the workspace ttsc source at build time.
  6. Run go build -o .ttsc-plugin <entry> in the scratch directory. The leading-dot placeholder name makes the in-progress binary easy to spot in go build stderr; step 7 renames it to plugin (or plugin.exe on Windows) as it moves into the cache.
  7. Move the binary into the cache.
  8. Invoke the binary with check, transform, build, fix, or format.

Cache Key Inputs

The cache key for a built plugin binary is sha256 of, in order:

  1. ttsc=${ttscVersion}
  2. tsgo=${tsgoVersion} — the resolved @typescript/native-preview version.
  3. platform=${process.platform}/${process.arch}
  4. entry=${plugin entry package relative to its go.mod}
  5. go=${resolveGoCompilerIdentity(goBinary)} — the resolved Go binary’s file content hash and go version text, not the install path.
  6. Every effective Go build env value in GO_BUILD_ENV_KEYS: GOOS, GOARCH, GOAMD64, GOARM, GOARM64, GO386, GOMIPS, GOMIPS64, GOPPC64, GORISCV64, GOWASM, GOFLAGS, GOEXPERIMENT, GOFIPS140, GO_EXTLINK_ENABLED, GCCGO, GCCGOTOOLDIR, CGO_ENABLED, the cgo compiler/linker variables, GOTOOLCHAIN, and GOROOT by toolchain-content fingerprint. Command-valued cgo settings also include the resolved tool binary identity.
  7. Plugin source-directory hash. Skipped paths: node_modules, .git, .ttsc, go.work, go.work.sum, *.tgz, *.tar.gz, .DS_Store, Thumbs.db, and editor backup files ending in ~.
  8. Overlay-directory hashes, sorted by directory name (ttsc and shim source under the host’s node_modules).
  9. Contributor source-directory hashes, sorted by contributor name.

The Go-binary identity (5) and GO_BUILD_ENV_KEYS (6) are the load-bearing reason GOOS=linux GOARCH=arm64 ttsc does not poison the native-platform cache slot. Pathless Go identity also lets pnpm installs with different virtual-store paths share the same cached binary when the bundled compiler content is identical.

The cache key does not include:

  • consumer TypeScript source files;
  • plugin options such as lint rules / extends, banner, or calls;
  • CLI flags such as --emit or --outDir.

README, JSON, schema, and other data files inside the plugin source directory do affect the cache key. This keeps //go:embed and other file-backed plugin data aligned with the built binary. Local workspace changes to ttsc’s Go host or shim overlay also invalidate the source plugin cache automatically.

Cache Locations

Default:

Linux: $XDG_CACHE_HOME/ttsc/plugins/<key>/plugin Linux: ~/.cache/ttsc/plugins/<key>/plugin macOS: ~/Library/Caches/ttsc/plugins/<key>/plugin Windows: %LOCALAPPDATA%/ttsc/plugins/<key>/plugin

Override:

TTSC_CACHE_DIR=/tmp/ttsc-cache npx ttsc --emit

--cache-dir and TTSC_CACHE_DIR remain project/test isolation escapes. Relative --cache-dir values resolve from the ttsc working directory and store plugin binaries below <cache-dir>/plugins.

Clean — removes the active cache location and legacy project-local caches (node_modules/.ttsc, .ttsc) so projects upgraded from older ttsc releases do not keep stale binaries:

npx ttsc clean

The default cache is content-addressed and shared across projects on the same machine. It is not tied to a package manager layout: npm installs, pnpm virtual-store paths, and workspace packages share a binary whenever the source plugin, contributors, ttsc overlay, Go toolchain content, platform, and build environment produce the same cache key.

ttsc updates a small last-used marker whenever a binary is reused. Once per day it opportunistically prunes default-cache entries unused for 30 days and trims the cache by least-recently-used order when the cache grows beyond the size budget. Explicit --cache-dir and TTSC_CACHE_DIR caches are not garbage-collected automatically because callers usually use them for CI or test isolation.

Build environment

The plugin builder reads Go’s effective build environment for the cache key. It also injects the TTSC_*_BINARY trio into each plugin process so plugins can re-spawn ttsc helpers.

Cache-key inputs: GO_BUILD_ENV_KEYS

These effective Go build env values fold into the cache key when non-empty, so cross-compile targets, build-tag tweaks, cgo compiler/linker choices, FIPS mode, and toolchain roots never collide with the native build slot. ttsc reads them through go env, so go env -w settings are covered too. GOROOT is normalized to a toolchain-content fingerprint instead of the install path, preserving pnpm path sharing for identical bundled toolchains. Command-valued entries such as CC, CXX, AR, GCCGO, and PKG_CONFIG include the resolved executable content hash as well as the command string.

GOOS GOARCH GOAMD64 GOARM GOARM64 GO386 GOMIPS GOMIPS64 GOPPC64 GORISCV64 GOWASM GOFLAGS GOEXPERIMENT GOFIPS140 GO_EXTLINK_ENABLED GCCGO GCCGOTOOLDIR CGO_ENABLED AR CC CXX FC PKG_CONFIG CGO_CFLAGS CGO_CFLAGS_ALLOW CGO_CFLAGS_DISALLOW CGO_CPPFLAGS CGO_CPPFLAGS_ALLOW CGO_CPPFLAGS_DISALLOW CGO_CXXFLAGS CGO_CXXFLAGS_ALLOW CGO_CXXFLAGS_DISALLOW CGO_FFLAGS CGO_FFLAGS_ALLOW CGO_FFLAGS_DISALLOW CGO_LDFLAGS CGO_LDFLAGS_ALLOW CGO_LDFLAGS_DISALLOW GOTOOLCHAIN GOROOT

ttsc also folds selected external cgo/pkg-config environment values into the key when they are set, including include/library search path variables (CPATH, LIBRARY_PATH, INCLUDE, LIB, LIBPATH), pkg-config search variables (PKG_CONFIG_PATH, PKG_CONFIG_LIBDIR, PKG_CONFIG_SYSROOT_DIR), and Apple SDK/deployment variables (SDKROOT, MACOSX_DEPLOYMENT_TARGET).

Source: packages/ttsc/src/plugin/internal/buildSourcePlugin.ts (GO_BUILD_ENV_KEYS and EXTERNAL_GO_BUILD_ENV_KEYS; the cache-key loop folds each non-empty effective value into the hash).

Injected into the plugin process

Before spawning a plugin, ttsc sets three env vars (TTSC_NODE_BINARY, TTSC_TSGO_BINARY, TTSC_TTSX_BINARY) in the child environment so the plugin can re-spawn the ttsc toolchain without resolving paths itself:

  • TTSC_NODE_BINARY — absolute path of the Node binary that launched ttsc.
  • TTSC_TSGO_BINARY — resolved tsgo native binary used by this ttsc invocation.
  • TTSC_TTSX_BINARY — resolved ttsx launcher.

A plugin that wants to re-spawn ttsx or tsgo (for example to type-check a generated file) should call these vars rather than re-resolving from $PATH. They guarantee the same host the consumer ran. Sources: runBuild.ts and transformProjectInMemory.ts. See Recipes → Re-spawn ttsx or tsgo from a plugin for usage.

GOROOT is auto-injected only when unset

When the resolved Go binary is the bundled SDK shipped by @ttsc/{platform}-{arch}, ttsc infers GOROOT from the binary’s parent directory and injects it into the build env — but only if process.env.GOROOT is empty. A stale GOROOT exported by a shell rc file from a different Go install will be honored as-is and silently bind the bundled compiler to the wrong stdlib. If a freshly-installed plugin build fails with errors like runtime/runtime0.go: no such file or directory, unset GOROOT before re-running.

Go Toolchain Resolution

ttsc resolves the Go compiler in this order — first match wins:

  1. TTSC_GO_BINARY, when set to a non-empty path.
  2. The installed @ttsc/{platform}-{arch} package’s bundled Go SDK, resolved via createRequire against @ttsc/{platform}-{arch}/bin/go/bin/go.
  3. The workspace-development fallback <repo>/packages/ttsc-{platform}-{arch}/bin/go/bin/go.
  4. The legacy local-workspace fallback <repo>/native/go/bin/go.
  5. $HOME/go-sdk/go/bin/go — the convention used by scripts/build-platform-package.cjs and by maintainers who bootstrap a bundled-Go layout without going through the platform package.
  6. Literal "go" — a $PATH lookup; the development fallback.

Published consumer installs should land on step 2. Steps 3–5 exist for development workflows; step 6 should only fire when none of the above are present.

Debugging Build Failures

If go build fails, ttsc prints the Go stderr:

ttsc: building plugin "my-plugin" via "go build" failed: .ttsc-plugin/main.go:42:8: undefined: shimast.Something

Check:

  • missing shim require in go.mod;
  • missing source files in the npm tarball;
  • wrong source directory or package entry;
  • stale cache, fixed with npx ttsc clean;
  • pnpm local dev layout, fixed in Editor setup;
  • on POSIX, ttsc chmod 0755s the bundled go binary and every file under pkg/tool/ before invoking it; a sandbox or read-only mount that blocks chmod will surface here as a Go spawn failure.

Concurrency

Concurrent ttsc processes may build the same missing key at the same time. Scratch directories are unique, and the final move is atomic. This is wasteful but safe.

See also

  • Walkthroughs — shipped plugins as worked examples.
  • Pitfalls — failure modes specific to plugin authoring (including the goToolchainNotFoundMessage exact text).
  • Driver API — the Go façade plugin authors should call.
  • Plugin Protocol — the wire contract.
Last updated on