UNPKG

ttsc

Version:

General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.

245 lines (244 loc) 10.7 kB
import { type SpawnSyncOptionsWithStringEncoding, type SpawnSyncReturns } from "node:child_process"; /** One contributor's resolved Go source plus its target sub-package name. */ export interface ITtscBuildContributor { /** Sub-package suffix: scratch lands at `<host>/contrib/<name>/`. */ name: string; /** Absolute path to the contributor's source directory. */ source: string; } /** Source-plugin cache locations resolved for one ttsc invocation. */ export interface ITtscSourceBuildCachePaths { /** Root directory containing all ttsc-owned source build caches. */ root: string; /** Directory containing content-addressed compiled plugin binaries. */ pluginRoot: string; /** Directory passed to Go as `GOCACHE` for source-plugin builds. */ goBuildRoot: string; /** How `goBuildRoot` was selected. */ goBuildRootSource: "ttsc-cache" | "TTSC_GO_CACHE_DIR" | "GOCACHE"; } /** * Build one Go source plugin into a cached executable. * * `opts.env` is the effective environment for this build — the caller merges `{ * ...process.env, ...context.env }` so a programmatic `TtscCompiler` instance * can pin its own Go toolchain (`TTSC_GO_BINARY`), Go build cache * (`TTSC_GO_CACHE_DIR`), and Go build variables (`GOFLAGS`, `CGO_*`, …) without * mutating the shared `process.env`. CLI callers omit it and inherit * `process.env`, so ambient behavior is unchanged. */ export declare function buildSourcePlugin(opts: { source: string; pluginName: string; baseDir: string; cacheDir?: string; contributors?: readonly ITtscBuildContributor[]; env?: NodeJS.ProcessEnv; label?: string; overlayDirs?: readonly string[]; quiet?: boolean; ttscVersion: string; tsgoVersion: string; }): string; /** Opaque identity of one observed lock generation. */ export type PluginBuildLockFence = { protocol: "legacy" | "v2"; generation: string; }; /** Ownership token returned only to the process that acquired `current`. */ export type PluginBuildLockLease = { protocol: "v2"; generation: string; }; /** * Atomically acquire the current generation in a v2 coordination directory. * * A non-empty candidate is renamed to `current`. Directory rename cannot * replace a non-empty `current`, so exactly one contender wins without an * empty-owner publication window. `null` means either another v2 holder won or * the path is a legacy lock that must be observed before it can be reclaimed. * * Exported for deterministic multi-process tests. */ export declare function acquirePluginBuildLock(lockDir: string): PluginBuildLockLease | null; /** Retire a held generation during the holder's `finally`. */ export declare function releasePluginBuildLock(lockDir: string, lease: PluginBuildLockLease): boolean; /** * Retire exactly the generation carried by an abandoned observation. * * Exported for deterministic multi-process tests. */ export declare function reclaimPluginBuildLock(lockDir: string, fence: PluginBuildLockFence): boolean; /** * Outcome of one waiting session on another process's plugin build lock. * * - `published`: the binary exists and can be reused. * - `released`: the observed generation no longer exists and no binary appeared — * the holder freed the key normally, so the caller should retry ordinary * acquisition without reporting or removing anything. * - `abandoned`: the lock still exists but is provably stale (dead owner, old * legacy lock) or the wait budget expired; the caller may report and retire * precisely the attached generation. * * Exported for unit tests. */ export type PluginBinaryWaitResult = { outcome: "published"; } | { outcome: "released"; } | { outcome: "abandoned"; reason: string; fence: PluginBuildLockFence; }; /** * Poll for the locked builder to publish its binary, up to `timeoutMs`. * * Exported for unit tests. */ export declare function waitForPluginBinary(opts: { binaryPath: string; lockDir: string; lockInfo: { label: string; pluginName: string; quiet: boolean; }; timeoutMs: number; }): PluginBinaryWaitResult; /** * One observation of a plugin build lock directory's state. * * - `active`: the lock exists and its owner is alive (or cannot be disproven: * another host, no metadata but young). Keep waiting. * - `abandoned`: the lock still exists and the evidence says nobody will ever * release it — a same-host owner that is no longer running, or an old * metadata-less legacy lock. Retiring its fenced generation is justified. * - `released`: the observed generation no longer exists. In v2 the persistent * coordination root remains while `current` is absent. This is a routine * handoff, never an infinitely old abandoned lock (issue #421). * * Exported for unit tests. */ export type PluginBuildLockObservation = { state: "active"; owner: string; fence: PluginBuildLockFence; } | { state: "abandoned"; reason: string; fence: PluginBuildLockFence; } | { state: "released"; }; /** * Classify the current state of a plugin build lock directory. * * Exported for unit tests. */ export declare function inspectPluginBuildLock(lockDir: string, now: number): PluginBuildLockObservation; /** * Render a millisecond duration for lock diagnostics (`137ms`, `42s`, `9m 3s`). * * Total over every number: no caller produces a non-finite duration anymore * (the lock state machine reports "released" instead of an Infinity age), but * as defense in depth a non-finite input renders as `an unknown time` so no * public diagnostic can ever print `Infinitym NaNs` again (issue #421). * * Exported for unit tests. */ export declare function formatDuration(ms: number): string; /** * Format an absolute filesystem path as a single `go.work`/`go.mod` token. * * The modfile grammar shared by `go.mod` and `go.work` (parsed by * `golang.org/x/mod/modfile`) is whitespace-tokenized, so a `use`/`replace` * path that contains a space — a home or project directory such as `/Users/John * Smith/...` or `C:\Users\John Smith\...` — must be emitted as a quoted string * or `go` cannot parse the generated `go.work`. Normalize Windows separators to * `/` (the workspace convention) and then delegate to * {@link autoQuoteGoModToken}, which mirrors `modfile.AutoQuote`. * * Separator normalization is itself a quoting trigger. A Windows UNC * (`\\server\share\...`) or extended-length (`\\?\C:\...`) path normalizes into * a token that starts with `//`, and the modfile lexer reads `//` as a line * comment wherever it appears. Emitted bare, such a token turns its whole * `use`/`replace` line into a comment: `go` exits 0, reports nothing, and the * overlay module simply disappears from the workspace. * * Exported for unit tests. */ export declare function formatGoWorkPath(p: string): string; /** * Quote `token` for a `go.mod`/`go.work` line exactly as * `golang.org/x/mod/modfile`'s `AutoQuote` does: return it unchanged when it is * already a clean bare token, otherwise return its Go double-quoted form so the * value round-trips through the modfile lexer. A clean bare token is therefore * emitted byte-for-byte as before; only tokens that would otherwise be split or * interpreted as comments are quoted. * * Exported for unit tests. */ export declare function autoQuoteGoModToken(token: string): string; export declare function spawnGoTool(goBinary: string, args: readonly string[], options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns<string>; /** Build the fixed cmd.exe switch sequence for one already quoted payload. */ export declare function windowsGoCommandArgs(payload: string): string[]; /** * Resolve the directory where compiled plugin binaries are cached. * * Delegates to {@link resolveSourceBuildCachePaths}; kept as a thin accessor for * callers (and tests) that only need the plugin-binary root. Triggers the * opportunistic project-cache GC as a side effect for the default location. */ export declare function resolvePluginCacheRoot(projectRoot: string, cacheDir?: string, env?: NodeJS.ProcessEnv): string; /** * Resolve all source-plugin build cache directories for one invocation. * * `pluginRoot` stores compiled plugin binaries; `goBuildRoot` is the Go object * cache passed as `GOCACHE` while ttsc builds those binaries. Both live under a * single `root`, so persisting one directory covers the whole source-build * cache without depending on ttsc internals. */ export declare function resolveSourceBuildCachePaths(projectRoot: string, cacheDir?: string, env?: NodeJS.ProcessEnv): ITtscSourceBuildCachePaths; /** * Return every directory `ttsc clean` should remove for `projectRoot`. * * Covers the resolved cache root (which holds `plugins/` and, when ttsc-owned, * `go-build/`), a ttsc-owned Go build cache that lives OUTSIDE that root * (`TTSC_GO_CACHE_DIR`), and the two legacy project-local caches. A * user-provided `GOCACHE` is never removed. Pure over `env`, so the CLI passes * `process.env` and a programmatic caller can pass an injected environment. */ export declare function resolveCleanTargets(projectRoot: string, cacheDir?: string, env?: NodeJS.ProcessEnv): string[]; /** * Machine-global cache directories created by pre-0.17 ttsc releases (XDG / * AppData / Library / `~/.cache`). ttsc no longer writes to any of these, but * an upgraded machine can still hold a multi-GB orphaned cache here, so `ttsc * clean` offers them for removal to reclaim that disk. Each entry is the whole * `<userCacheRoot>/ttsc` directory (both its `plugins` and `go-build`), which * was entirely ttsc-owned in those releases and is safe to remove. */ export declare function legacyGlobalCacheTargets(): string[]; /** Report whether `child` equals `parent` or is nested beneath it. */ export declare function isPathWithin(child: string, parent: string): boolean; /** * Compute a deterministic SHA-256 cache key for a plugin build. * * The key covers every input that can produce a different binary: ttsc/tsgo * versions, platform, entry package, Go compiler identity, Go build environment * variables, overlay module sources, plugin source files, and contributor * source files. Contributors are sorted by name so declaration order does not * affect the key. * * Exposed for testing and for the `ttsc cache` CLI command. */ export declare function computeCacheKey(inputs: { contributors?: readonly ITtscBuildContributor[]; dir: string; entry: string; env?: NodeJS.ProcessEnv; goBinary?: string; overlayDirs?: readonly string[]; ttscVersion: string; tsgoVersion: string; }): string;