ttsc
Version:
General-purpose TypeScript-Go compiler, runtime, plugin host, and LSP host.
155 lines (154 loc) • 7.65 kB
TypeScript
import { type FilesystemPathIdentityContext } from "../../internal/projectInputPathIdentity";
interface ResolveResult {
url: string;
format?: string | null;
shortCircuit?: boolean;
}
/**
* The compiler options that decide the emit format of a file, as declared by
* the project that emitted it. Both fields matter: tsgo derives the module kind
* from `target` whenever `module` is absent, so carrying only `module` cannot
* reproduce its decision.
*/
export interface OwningModuleOptions {
module?: string;
target?: string;
}
/**
* Lowest Node.js the ttsx source runtime supports. The synchronous
* `module.registerHooks` (Node 22.15.0) is the highest floor among the runtime
* APIs these hooks depend on — `stripTypeScriptTypes` (22.13.0) and the child's
* `--disable-warning` flag (20.11.0) are both lower — so it sets the effective
* minimum. Kept in sync with `packages/ttsc/package.json#engines.node` and the
* documented requirement in `website/src/content/docs/development/index.mdx`.
*/
export declare const TTSX_MINIMUM_NODE_VERSION = "22.15.0";
/**
* Report why the running (or a candidate) Node.js version cannot execute the
* ttsx source runtime, or `null` when it can. Returning an actionable message —
* rather than letting the child die with an internal `TypeError` on the missing
* `registerHooks`, or Node 18 rejecting `--disable-warning` with exit 9 — is
* what turns an opaque internal failure into a clear version diagnostic.
*
* Exported for direct exercise by the ttsx e2e suite: the built launcher can
* only be spawned under the Node version running the tests, so the boundary
* around the floor cannot otherwise be pinned on CI.
*/
export declare function checkNodeRuntimeSupport(version: string): string | null;
/**
* Install the source-loading hooks on the current (main) thread. Idempotent:
* the bootstrap installs them for the entry process, and `NODE_OPTIONS`
* re-imports the installer in every child process the program spawns — both may
* run in the same process.
*
* Two hooks are needed, because `module.registerHooks` does not intercept a
* `require()` made from inside a CommonJS module that was itself reached
* through an ESM `import` (the interop translator loads it on the raw CJS
* path). The ESM graph goes through `registerHooks`; the CommonJS `require`
* graph goes through `Module._extensions` — the canonical loader extension
* point `ts-node`/`tsx` use for the same reason.
*/
export declare function installRuntimeHooks(): void;
/**
* The module format of the entry source file, derived from the entry project's
* compiler options (via the runtime manifest) the same way the served files are
* classified. The bootstrap uses it to load the entry through a CommonJS
* `require` or an ESM `import`.
*/
export declare function entryModuleFormat(entryFile: string): "module" | "commonjs";
/**
* Restore a `node:` builtin URL when affected Node releases return the exact
* prefix-stripped spelling from their synchronous CommonJS resolver.
*
* Every other result passes through unchanged. In particular, a user hook that
* intentionally remaps a `node:` specifier to another URL retains ownership of
* that mapping, while ordinary and ESM builtin results already carrying the
* scheme avoid an unnecessary copy.
*/
export declare function restoreStrippedNodeBuiltinScheme(specifier: string, result: ResolveResult): ResolveResult;
/** Cache of built projects keyed by owning tsconfig path. */
interface BuiltProject {
emitDir: string;
rootDir: string;
emittedFiles?: readonly string[];
moduleOptions: OwningModuleOptions;
}
/** Narrow a resolved project's compiler options to the emit-format pair. */
export declare function projectModuleOptions(compilerOptions: Record<string, unknown>): OwningModuleOptions;
/**
* True when `real` is `directory` itself or sits beneath it. Handles a root
* `directory` (`/`, `C:\`): naively appending a separator would yield `//`,
* which no path starts with, so a root `rootDir` project would serve nothing.
* Both sides are normalized to native separators first: a manifest `rootDir`
* arrives slash-normalized from the synthesized tsconfig (`C:/` on Windows)
* while `real` paths are native, and a raw prefix comparison across the two
* forms silently never matches. Filesystem identity preserves ordinary Windows
* aliases while keeping case-distinct paths under an opted-in directory
* separate. Exported for direct exercise by the ttsx e2e suite — spawned runs
* cannot pin both Windows case-semantics branches on CI.
*/
export declare function isWithin(real: string, directory: string, identities?: FilesystemPathIdentityContext): boolean;
/**
* Reuse a dependency another process (or an earlier import) already built.
*
* The completion marker names the exact emit generation, so this reads metadata
* and emit as one unit: it returns a hit only when the marker parses to a valid
* generation AND that generation's directory holds emitted JavaScript. A reader
* that runs while a replacement build is populating a DIFFERENT generation
* directory keeps returning the previous complete generation until the atomic
* marker swap points at the new one — never a mix of old metadata and a partial
* new emit.
*
* Exported for the ttsx dependency-cache regressions.
*/
export declare function readDependencyCache(cacheDir: string, metaPath: string): BuiltProject | null;
/** Ownership token returned only to the process that acquired `current`. */
export type DependencyBuildLockLease = {
generation: string;
};
/** Opaque identity of one observed lock generation. */
export type DependencyBuildLockFence = {
generation: string;
};
/**
* Atomically acquire the current generation of a dependency build lock, or
* `null` when another process already holds it. Exported for the deterministic
* multi-process cache regressions.
*/
export declare function acquireDependencyBuildLock(lockDir: string): DependencyBuildLockLease | null;
/** Retire a held generation during the holder's `finally`. */
export declare function releaseDependencyBuildLock(lockDir: string, lease: DependencyBuildLockLease): boolean;
/**
* Retire exactly the generation carried by an abandoned observation. Exported
* for the deterministic multi-process cache regressions.
*/
export declare function reclaimDependencyBuildLock(lockDir: string, fence: DependencyBuildLockFence): boolean;
/** One observation of a dependency build lock's state. */
export type DependencyBuildLockObservation = {
state: "active";
owner: string;
fence: DependencyBuildLockFence;
} | {
state: "abandoned";
reason: string;
fence: DependencyBuildLockFence;
} | {
state: "released";
};
/**
* Classify the current state of a dependency build lock directory. Exported for
* the deterministic multi-process cache regressions.
*/
export declare function inspectDependencyBuildLock(lockDir: string, now: number): DependencyBuildLockObservation;
/**
* The physical path, in the same spelling the launcher decided on.
*
* `realpathSync.native` first, because the plain implementation resolves
* reparse points but leaves a Windows 8.3 component alone — and `TEMP` is
* `C:\Users\RUNNER~1\...` on a GitHub Windows runner. The launcher resolves the
* entry through `fs.realpathSync.native`, so answering here with the short name
* would put a `..` in `path.relative(rootDir, real)`, drop the exact-mirror
* lane, and leave the entry to whatever the trailing-stem matcher picks.
*/
export declare function realPath(target: string): string;
export {};