ttsc 0.34.2 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/driver/compileraliases/names.go +69 -0
  2. package/driver/compileraliases/names_test.go +57 -0
  3. package/lib/compiler/internal/build/BuildExecution.d.ts +26 -0
  4. package/lib/compiler/internal/build/BuildExecution.js +13 -3
  5. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  6. package/lib/compiler/internal/build/RunBuildOptions.d.ts +2 -0
  7. package/lib/compiler/internal/build/RuntimeBuildCache.d.ts +47 -0
  8. package/lib/compiler/internal/build/RuntimeBuildCache.js +235 -0
  9. package/lib/compiler/internal/build/RuntimeBuildCache.js.map +1 -0
  10. package/lib/compiler/internal/build/RuntimeBuildInputs.d.ts +44 -0
  11. package/lib/compiler/internal/build/RuntimeBuildInputs.js +282 -0
  12. package/lib/compiler/internal/build/RuntimeBuildInputs.js.map +1 -0
  13. package/lib/compiler/internal/build/RuntimeInputProof.d.ts +50 -0
  14. package/lib/compiler/internal/build/RuntimeInputProof.js +315 -0
  15. package/lib/compiler/internal/build/RuntimeInputProof.js.map +1 -0
  16. package/lib/compiler/internal/build/TsgoArguments.d.ts +5 -4
  17. package/lib/compiler/internal/build/TsgoArguments.js +11 -5
  18. package/lib/compiler/internal/build/TsgoArguments.js.map +1 -1
  19. package/lib/compiler/internal/build/runBuild.d.ts +3 -3
  20. package/lib/compiler/internal/build/runBuild.js +17 -4
  21. package/lib/compiler/internal/build/runBuild.js.map +1 -1
  22. package/lib/compiler/internal/resolveNativeTypeScriptPackage.d.ts +27 -0
  23. package/lib/compiler/internal/resolveNativeTypeScriptPackage.js +103 -0
  24. package/lib/compiler/internal/resolveNativeTypeScriptPackage.js.map +1 -0
  25. package/lib/compiler/internal/resolveTsgo.d.ts +7 -6
  26. package/lib/compiler/internal/resolveTsgo.js +16 -35
  27. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  28. package/lib/compiler/internal/runNativeCheckWithObservations.d.ts +18 -14
  29. package/lib/compiler/internal/runNativeCheckWithObservations.js +42 -18
  30. package/lib/compiler/internal/runNativeCheckWithObservations.js.map +1 -1
  31. package/lib/flags/FLAG_SCHEMA.js +7 -0
  32. package/lib/flags/FLAG_SCHEMA.js.map +1 -1
  33. package/lib/internal/nativeBinary.d.ts +4 -0
  34. package/lib/internal/nativeBinary.js +6 -1
  35. package/lib/internal/nativeBinary.js.map +1 -1
  36. package/lib/launcher/internal/parseTtsxCLI.d.ts +1 -0
  37. package/lib/launcher/internal/parseTtsxCLI.js +1 -0
  38. package/lib/launcher/internal/parseTtsxCLI.js.map +1 -1
  39. package/lib/launcher/internal/prepareExecution.d.ts +2 -0
  40. package/lib/launcher/internal/prepareExecution.js +11 -2
  41. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  42. package/lib/launcher/internal/runTtsx.js +5 -0
  43. package/lib/launcher/internal/runTtsx.js.map +1 -1
  44. package/lib/launcher/internal/runtime/installRuntimeHooks.js +7 -0
  45. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  46. package/lib/plugin/internal/load/loadProjectPlugins.js +1 -0
  47. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  48. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +2 -0
  49. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +3 -0
  50. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  51. package/lib/plugin/internal/source/pluginBuildVersions.d.ts +8 -7
  52. package/lib/plugin/internal/source/pluginBuildVersions.js +13 -13
  53. package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
  54. package/lib/structures/ITtscPluginCapabilities.d.ts +13 -2
  55. package/package.json +9 -9
  56. package/src/compiler/internal/build/BuildExecution.ts +32 -16
  57. package/src/compiler/internal/build/RunBuildOptions.ts +3 -0
  58. package/src/compiler/internal/build/RuntimeBuildCache.ts +252 -0
  59. package/src/compiler/internal/build/RuntimeBuildInputs.ts +349 -0
  60. package/src/compiler/internal/build/RuntimeInputProof.ts +346 -0
  61. package/src/compiler/internal/build/TsgoArguments.ts +11 -5
  62. package/src/compiler/internal/build/runBuild.ts +20 -5
  63. package/src/compiler/internal/resolveNativeTypeScriptPackage.ts +110 -0
  64. package/src/compiler/internal/resolveTsgo.ts +17 -36
  65. package/src/compiler/internal/runNativeCheckWithObservations.ts +41 -18
  66. package/src/flags/FLAG_SCHEMA.ts +7 -0
  67. package/src/internal/nativeBinary.ts +4 -0
  68. package/src/launcher/internal/parseTtsxCLI.ts +1 -0
  69. package/src/launcher/internal/prepareExecution.ts +20 -2
  70. package/src/launcher/internal/runTtsx.ts +5 -0
  71. package/src/launcher/internal/runtime/installRuntimeHooks.ts +11 -0
  72. package/src/plugin/internal/load/loadProjectPlugins.ts +1 -0
  73. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +4 -0
  74. package/src/plugin/internal/source/pluginBuildVersions.ts +13 -15
  75. package/src/structures/ITtscPluginCapabilities.ts +14 -2
  76. package/utility/host.go +22 -1
@@ -0,0 +1,252 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import { E2ETrace } from "../../../internal/E2ETrace";
6
+ import { recordCacheFileUse } from "../../../plugin/internal/source/recordCacheFileUse";
7
+ import type { TtscBuildResult } from "../../../structures/internal/TtscBuildResult";
8
+
9
+ /**
10
+ * Persist completed private runtime emits as checksummed, atomically published
11
+ * documents. Readers copy admitted bytes into their own run directory, so cache
12
+ * collection and concurrent publications cannot remove an executing run's emit.
13
+ * Input discovery and producer-proof admission belong to the supplied owners.
14
+ *
15
+ * @evidence contracts/common.md#principled-implementation Content-addressed snapshots select complete checksummed result and output documents. SHA-256 consumes exact UTF-8 text; native normalization rejects absolute, dot, parent and Windows drive-relative output coordinates. Relocation changes producer output coordinates and private-root stream text while retaining source paths.
16
+ * @evidence contracts/common.md#clear-and-simple-design One operation coordinates lookup, proof, restore and publication, with private hashing, containment and relocation helpers. Input capture and producer validation remain separate callbacks.
17
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Misses execute the original build exactly once; failed or absent proofs never manufacture successful output. Every transported output must be a normalized private descendant, without filename or consumer-name exceptions.
18
+ * @evidence contracts/common.md#meaningful-documentation Native prose distinguishes persistent storage, run-owned copies and proof ownership; run states callback stability and failure behavior, and private helper comments identify byte, path and relocation domains.
19
+ * @evidence contracts/portability.md#os-neutral-implementation Native paths, lstat, relative/join and rename own storage and containment, including Windows drive-relative rejection. Source coordinates remain unchanged; transported symlinks are refused rather than followed.
20
+ * @evidence contracts/performance.md#efficient-algorithms Lookup hashes snapshot text and reads/hashes the bounded document. Restore and publication scale with output bytes and file count, without unrelated source traversal; normalization scans path bytes and relocation scans output paths and stream text.
21
+ * @evidence contracts/performance.md#reuse-equivalent-work Equal snapshots reuse completed emits only after independent producer admission and unchanged recapture. A restored run owns its copied bytes and rebased output ledger; concurrent publishers may duplicate compilation but cannot expose partial documents.
22
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Documents are capped at 128 MiB, with fresh output collection limited to a 0.7 raw-byte budget before encoding, and join the existing collected single-file cache population. Snapshots and decoded bytes are invocation-owned; staging removal is attempted in finally and run owners release output. Cleanup or collector failures can retain files; helpers retain no handles or history.
23
+ */
24
+ export namespace RuntimeBuildCache {
25
+ const FORMAT = "ttsx-runtime-build-v1";
26
+ const MAX_BYTES = 128 * 1024 * 1024;
27
+
28
+ /**
29
+ * Reuse a proven private emit, or invoke the original producer once. Capture
30
+ * returns a deterministic complete input snapshot, or undefined to decline
31
+ * caching. Admission must validate the producer's extra input observations;
32
+ * the second argument is the output root that producer actually used. Cache
33
+ * IO or cache-admission failure costs reuse only. Errors thrown by the
34
+ * original build retain their normal failure policy.
35
+ *
36
+ * @evidence contracts/common.md#principled-implementation Lookup and publication bracket current input snapshots, preserve the actual producer result and require explicit extra-input admission; cache-hit output is copied before the result is rebased.
37
+ * @evidence contracts/common.md#clear-and-simple-design Snapshot, proof and build callbacks expose their independent responsibilities while the operation alone owns storage and restore cleanup.
38
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Only successful complete emits are published; malformed, oversized, damaged or unproved documents fall through to the caller's real producer without changing compiler selection.
39
+ * @evidence contracts/common.md#meaningful-documentation Native prose states complete-snapshot meaning, producer-root coordinates, callback error policy and recoverable cache IO.
40
+ * @evidence contracts/portability.md#os-neutral-implementation Native lstat refuses aliases, path.relative checks containment and native mkdir/write/rename preserve byte payloads and host path semantics.
41
+ * @evidence contracts/performance.md#efficient-algorithms Snapshot owners dominate input work; document read/parse/hash and base64 restore scale with bounded encoded bytes, while publication walks only private output and serializes it once.
42
+ * @evidence contracts/performance.md#reuse-equivalent-work A format-and-snapshot digest names one candidate; checksum, output grammar, producer admission and unchanged recapture are independent reuse gates. Every miss calls build once.
43
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Call-local decoded outputs and serialized documents are bounded by the 128 MiB admission ceiling, except fresh output collection precedes serialization; partial restores are unlinked and staging removal is attempted in finally. Collector failures can retain files under the existing cache policy.
44
+ */
45
+ export function run(props: {
46
+ /** Collected single-file cache root, or undefined to bypass persistence. */
47
+ directory?: string;
48
+ /** Empty private output directory owned by this build's caller. */
49
+ emitDir: string;
50
+ /** Fresh complete input snapshot; undefined preserves ordinary execution. */
51
+ capture: () => string | undefined;
52
+ /** Producer-specific proof, with its original private output root. */
53
+ admit: (result: TtscBuildResult, emitDir: string) => boolean;
54
+ /** Original build operation, including its diagnostic policy. */
55
+ build: () => TtscBuildResult;
56
+ }): TtscBuildResult {
57
+ if (props.directory === undefined) return props.build();
58
+ let snapshot: string | undefined;
59
+ try {
60
+ snapshot = props.capture();
61
+ } catch {
62
+ /* An unavailable proof is a miss. */
63
+ }
64
+ if (snapshot === undefined) return props.build();
65
+ const key = digest(FORMAT + "\0" + snapshot);
66
+ const file = path.join(props.directory, key + ".json");
67
+ const restored: string[] = [];
68
+ try {
69
+ const stat = fs.lstatSync(file);
70
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_BYTES)
71
+ throw new Error("unusable runtime cache document");
72
+ const document = JSON.parse(fs.readFileSync(file, "utf8"));
73
+ if (
74
+ document.format !== FORMAT ||
75
+ typeof document.payload !== "string" ||
76
+ document.hash !== digest(document.payload)
77
+ )
78
+ throw new Error("invalid runtime cache checksum");
79
+ const entry = JSON.parse(document.payload);
80
+ if (
81
+ entry.snapshot !== snapshot ||
82
+ typeof entry.emitDir !== "string" ||
83
+ !path.isAbsolute(entry.emitDir) ||
84
+ entry.result?.status !== 0 ||
85
+ !Array.isArray(entry.outputs) ||
86
+ !props.admit(entry.result, entry.emitDir)
87
+ )
88
+ throw new Error("unproved runtime cache document");
89
+ const names = new Set<string>();
90
+ for (const output of entry.outputs) {
91
+ if (
92
+ !Array.isArray(output) ||
93
+ output.length !== 2 ||
94
+ typeof output[0] !== "string" ||
95
+ typeof output[1] !== "string" ||
96
+ !safeRelative(output[0]) ||
97
+ names.has(output[0]) ||
98
+ Buffer.from(output[1], "base64").toString("base64") !== output[1]
99
+ )
100
+ throw new Error("invalid runtime cache output");
101
+ names.add(output[0]);
102
+ }
103
+ for (const output of Object.keys(entry.result.emittedSources ?? {}))
104
+ if (!names.has(path.relative(entry.emitDir, output)))
105
+ throw new Error("missing runtime cache provenance output");
106
+ for (const [name, bytes] of entry.outputs) {
107
+ const target = path.join(props.emitDir, name);
108
+ fs.mkdirSync(path.dirname(target), { recursive: true });
109
+ fs.writeFileSync(target, Buffer.from(bytes, "base64"), { flag: "wx" });
110
+ restored.push(target);
111
+ }
112
+ if (
113
+ props.capture() !== snapshot ||
114
+ !props.admit(entry.result, entry.emitDir)
115
+ )
116
+ throw new Error("runtime inputs moved during restore");
117
+ recordCacheFileUse(file);
118
+ E2ETrace.capabilityResolution("runtime-build-cache", {
119
+ outcome: "reused",
120
+ key,
121
+ });
122
+ return relocate(entry.result, entry.emitDir, props.emitDir);
123
+ } catch {
124
+ for (const output of restored)
125
+ try {
126
+ fs.unlinkSync(output);
127
+ } catch {
128
+ /* Rejected cache output cannot prevent the ordinary producer. */
129
+ }
130
+ }
131
+ const result = props.build();
132
+ if (result.status !== 0) return result;
133
+ let temporary: string | undefined;
134
+ try {
135
+ const outputs: [string, string][] = [];
136
+ let outputBytes = 0;
137
+ const pending = [props.emitDir];
138
+ while (pending.length !== 0) {
139
+ const directory = pending.pop()!;
140
+ for (const child of fs.readdirSync(directory, {
141
+ withFileTypes: true,
142
+ })) {
143
+ const target = path.join(directory, child.name);
144
+ if (child.isSymbolicLink()) throw new Error("aliased runtime output");
145
+ if (child.isDirectory()) pending.push(target);
146
+ else if (child.isFile()) {
147
+ outputBytes += fs.statSync(target).size;
148
+ if (outputBytes > MAX_BYTES * 0.7) return result;
149
+ outputs.push([
150
+ path.relative(props.emitDir, target),
151
+ fs.readFileSync(target).toString("base64"),
152
+ ]);
153
+ } else throw new Error("nonordinary runtime output");
154
+ }
155
+ }
156
+ if (outputs.length === 0) return result;
157
+ const payload = JSON.stringify({
158
+ snapshot,
159
+ emitDir: props.emitDir,
160
+ result,
161
+ outputs,
162
+ });
163
+ const text = JSON.stringify({
164
+ format: FORMAT,
165
+ hash: digest(payload),
166
+ payload,
167
+ });
168
+ if (Buffer.byteLength(text) > MAX_BYTES) return result;
169
+ if (props.capture() !== snapshot || !props.admit(result, props.emitDir))
170
+ return result;
171
+ fs.mkdirSync(props.directory, { recursive: true });
172
+ if (fs.lstatSync(props.directory).isSymbolicLink()) return result;
173
+ temporary = path.join(
174
+ props.directory,
175
+ `${key}.${crypto.randomUUID()}.tmp`,
176
+ );
177
+ fs.writeFileSync(temporary, text, { flag: "wx" });
178
+ fs.renameSync(temporary, file);
179
+ E2ETrace.capabilityResolution("runtime-build-cache", {
180
+ outcome: "recorded",
181
+ key,
182
+ });
183
+ } catch {
184
+ /* Persistence never replaces a fresh successful result. */
185
+ } finally {
186
+ if (temporary !== undefined) {
187
+ try {
188
+ fs.unlinkSync(temporary);
189
+ } catch {
190
+ /* Rename or later collection owns it. */
191
+ }
192
+ }
193
+ }
194
+ return result;
195
+ }
196
+
197
+ /** Hash encoded input or document bytes with SHA-256. */
198
+ function digest(text: string): string {
199
+ return crypto.createHash("sha256").update(text).digest("hex");
200
+ }
201
+
202
+ /** Admit only a nonempty relative descendant coordinate. */
203
+ function safeRelative(name: string): boolean {
204
+ return (
205
+ name.length !== 0 &&
206
+ !name.includes("\0") &&
207
+ !path.isAbsolute(name) &&
208
+ path.parse(name).root === "" &&
209
+ path.normalize(name) === name &&
210
+ name !== "." &&
211
+ name !== ".." &&
212
+ !name.startsWith(".." + path.sep)
213
+ );
214
+ }
215
+
216
+ /** Move producer-owned output coordinates to the current private run. */
217
+ function relocate(
218
+ result: TtscBuildResult,
219
+ previous: string,
220
+ current: string,
221
+ ): TtscBuildResult {
222
+ const move = (output: string): string => {
223
+ const relative = path.relative(previous, output);
224
+ if (!safeRelative(relative))
225
+ throw new Error("runtime provenance escapes its output root");
226
+ return path.join(current, relative);
227
+ };
228
+ return {
229
+ ...result,
230
+ emittedFiles: result.emittedFiles?.map(move),
231
+ emittedSources:
232
+ result.emittedSources === undefined
233
+ ? undefined
234
+ : Object.fromEntries(
235
+ Object.entries(result.emittedSources).map(([output, sources]) => [
236
+ move(output),
237
+ sources,
238
+ ]),
239
+ ),
240
+ emittedSourceProofFailures:
241
+ result.emittedSourceProofFailures === undefined
242
+ ? undefined
243
+ : Object.fromEntries(
244
+ Object.entries(result.emittedSourceProofFailures).map(
245
+ ([output, reason]) => [move(output), reason],
246
+ ),
247
+ ),
248
+ stdout: result.stdout.split(previous).join(current),
249
+ stderr: result.stderr.split(previous).join(current),
250
+ };
251
+ }
252
+ }
@@ -0,0 +1,349 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+
5
+ import { resolveNodeBinary } from "../../../internal/resolveNodeBinary";
6
+ import { runtimeExecutableIdentity } from "../../../internal/runtimeExecutableIdentity";
7
+ import { PluginContentIdentities } from "../../../plugin/internal/source/PluginContentIdentities";
8
+ import { pluginSourceStateHolds } from "../../../plugin/internal/source/pluginSourceStateHolds";
9
+ import type { TtscBuildResult } from "../../../structures/internal/TtscBuildResult";
10
+ import { CompilerArgumentsInspection } from "../CompilerArgumentsInspection";
11
+ import { resolveTsgo } from "../resolveTsgo";
12
+ import { SidecarEnvironment } from "../sharedHost/SidecarEnvironment";
13
+ import { spawnNative } from "../spawnNative";
14
+ import type { BuildExecution } from "./BuildExecution";
15
+ import { PassthroughFlags } from "./PassthroughFlags";
16
+ import type { RunBuildOptions } from "./RunBuildOptions";
17
+ import { RuntimeInputProof } from "./RuntimeInputProof";
18
+ import { TsgoArguments } from "./TsgoArguments";
19
+
20
+ /**
21
+ * Prove runtime build inputs through the selected upstream compiler's read-only
22
+ * effective configuration and source-selection commands. Repeating selection
23
+ * discovers added roots and changed resolution candidates without guessing a
24
+ * project boundary or treating workspace links as immutable dependencies.
25
+ * Native plugin observations add their independently replayed input closure.
26
+ * Custom compiler overrides require the physical identity of the installed
27
+ * package compiler; wrappers with undisclosed external inputs stay fresh.
28
+ *
29
+ * @evidence contracts/common.md#principled-implementation The actual selected compiler defines effective options and source membership; content and physical identities cover that closure and package scopes, with separate native producer proof for checks.
30
+ * @evidence contracts/common.md#clear-and-simple-design One factory supplies input capture and result admission to the runtime document store without changing normal build dispatch.
31
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Incomplete descriptors, unsupported effectful compiler modes and unproved native producers and custom compilers without package authority use the existing fresh build rather than weakening diagnostics or substituting a compiler.
32
+ * @evidence contracts/common.md#meaningful-documentation Native prose describes compiler-owned membership, workspace scope and independent plugin observations.
33
+ * @evidence contracts/portability.md#os-neutral-implementation The selected executable receives native argv and cwd; source and package scopes use native physical paths and preserve lexical spellings without OS-wide case folding.
34
+ * @evidence contracts/performance.md#efficient-algorithms Each snapshot runs two read-only native queries and hashes selected source, package, executable and declared host input bytes; shared ancestor sets avoid repeated package-scope walks.
35
+ * @evidence contracts/performance.md#reuse-equivalent-work A completed emit may be reused only for equal selected input snapshots and currently valid native witnesses; unsupported proof falls back to compilation.
36
+ * @evidence contracts/performance.md#bound-retention-and-release-resources Query buffers and path/hash records are call-local, with source/report bytes unbounded by this factory; the document store owns persistent byte limits and retention.
37
+ */
38
+ export namespace RuntimeBuildInputs {
39
+ /**
40
+ * Construct the proof owners for one private runtime request.
41
+ * Program-mutating transform hosts need their declared emit-input sidecar.
42
+ * Check hosts need their declared observation protocol and complete
43
+ * descriptor discovery; no-plugin builds use the actual compiler's
44
+ * source/configuration selection directly. Custom selections are reusable
45
+ * only when they name that same package-backed compiler.
46
+ *
47
+ * @evidence contracts/common.md#principled-implementation Eligibility requires a private emitting request, complete descriptor selection and supported check witnesses; captures bracket content identities and native source selection, and result admission requires complete emitted-source ownership.
48
+ * @evidence contracts/common.md#clear-and-simple-design Two closures share the resolved request while capture owns current selection and admission owns retained generation proof.
49
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Native transforms without complete emit observations and arbitrary external-code or profiling modes decline reuse rather than inventing input completeness. Custom compiler overrides need matching installed package identity, including inherited dependency hints; the original build remains the fallback.
50
+ * @evidence contracts/common.md#meaningful-documentation Native prose states current producer support and private-runtime scope, including why fresh transformation remains necessary.
51
+ * @evidence contracts/portability.md#os-neutral-implementation Source and package observations use native paths and bracketed byte reads, and upstream probes use the original compiler working directory and argv without shell composition.
52
+ * @evidence contracts/performance.md#efficient-algorithms Capture performs two upstream selection/config queries, actual executable proofs and O(selected bytes) hashes; unique ancestor and path sets bound duplicate reads within one snapshot. Admission replays only recorded native inputs.
53
+ * @evidence contracts/performance.md#reuse-equivalent-work Equal deterministic snapshots retain compiler arguments, versions, content, scope manifests, plugin binaries/configuration and environment; graph and host witnesses separately gate cached check reuse.
54
+ * @evidence contracts/performance.md#bound-retention-and-release-resources The resolved context and closures belong to one build; each capture releases local source and native-output buffers on return, with no resident process or historical map retained.
55
+ */
56
+ export function create(
57
+ options: RunBuildOptions,
58
+ execution: ReturnType<typeof BuildExecution.resolveExecutionContext>,
59
+ ):
60
+ | {
61
+ capture: () => string | undefined;
62
+ admit: (result: TtscBuildResult, emitDir: string) => boolean;
63
+ }
64
+ | undefined {
65
+ if (
66
+ options.runtimeCacheDir === undefined ||
67
+ typeof options.isolateOutputsTo !== "string" ||
68
+ options.emit !== true ||
69
+ options.format === true ||
70
+ execution.pluginSetupFailure !== undefined ||
71
+ execution.compilerSelection.inspectionError !== undefined ||
72
+ PassthroughFlags.forwardsTerminalTsgoFlag(options)
73
+ )
74
+ return undefined;
75
+ const loaded = execution.pluginLoad;
76
+ if (
77
+ execution.nativePlugins.some((plugin) =>
78
+ plugin.stage === "check"
79
+ ? plugin.capabilities?.checkObservations !== true
80
+ : plugin.capabilities?.emitObservations !== true,
81
+ ) ||
82
+ (loaded !== undefined &&
83
+ (!loaded.discoveryInputsComplete ||
84
+ !loaded.descriptorReadsDeclared ||
85
+ loaded.observationsComplete === false))
86
+ )
87
+ return undefined;
88
+ const emitDir = options.isolateOutputsTo;
89
+ const args = TsgoArguments.createTsgoBuildArgs(execution, options, {
90
+ listEmittedFiles: false,
91
+ });
92
+ const env = SidecarEnvironment.merge(process.env, options.env);
93
+ // A custom executable can be a wrapper with undisclosed external inputs.
94
+ // Inherited hints to the installed compiler still have package authority.
95
+ if (execution.tsgo.packageJson === "") {
96
+ try {
97
+ const packaged = resolveTsgo({
98
+ cwd: execution.projectRoot,
99
+ env: SidecarEnvironment.merge(env, { TTSC_TSGO_BINARY: undefined }),
100
+ });
101
+ if (
102
+ fs.realpathSync.native(packaged.binary) !==
103
+ fs.realpathSync.native(execution.tsgo.binary)
104
+ )
105
+ return undefined;
106
+ } catch {
107
+ return undefined;
108
+ }
109
+ }
110
+ const identities =
111
+ execution.identities ??
112
+ PluginContentIdentities.open({
113
+ projectRoot: execution.projectRoot,
114
+ cacheDir: path.dirname(options.runtimeCacheDir),
115
+ env,
116
+ });
117
+ if (identities === undefined) return undefined;
118
+ let inspected: ReturnType<typeof CompilerArgumentsInspection.inspect>;
119
+ try {
120
+ inspected = CompilerArgumentsInspection.inspect(
121
+ args,
122
+ execution.compilerSelection.compilerArgsCwd,
123
+ );
124
+ } catch {
125
+ return undefined;
126
+ }
127
+ if (
128
+ inspected.args.some((arg) =>
129
+ /^--?(?:runExternalCode|generateTrace|generateCpuProfile|pprofDir|diagnostics|extendedDiagnostics|traceResolution|explainFiles|listFiles|listEmittedFiles|build|watch)(?:=|$)/i.test(
130
+ arg,
131
+ ),
132
+ )
133
+ )
134
+ return undefined;
135
+ return {
136
+ capture: () => {
137
+ try {
138
+ if (process.env.TTSC_PLUGIN_DESCRIPTOR_LOAD === "1") return undefined;
139
+ const node =
140
+ execution.nativePlugins.length === 0
141
+ ? undefined
142
+ : resolveNodeBinary(env, execution.projectRoot, identities);
143
+ if (execution.nativePlugins.length !== 0 && node === undefined)
144
+ return undefined;
145
+ const binaries = [
146
+ ...new Set([
147
+ execution.tsgo.binary,
148
+ ...execution.nativePlugins.map((plugin) => plugin.binary),
149
+ ...(node === undefined ? [] : [node]),
150
+ ]),
151
+ ];
152
+ const compilerIdentities = binaries.map((binary) =>
153
+ runtimeExecutableIdentity(binary, identities),
154
+ );
155
+ if (compilerIdentities.some((identity) => identity === undefined))
156
+ return undefined;
157
+ for (const [directory, state] of Object.entries(
158
+ loaded?.pluginSources ?? {},
159
+ ))
160
+ if (
161
+ !pluginSourceStateHolds(directory, state, {
162
+ projectRoot: execution.projectRoot,
163
+ })
164
+ )
165
+ return undefined;
166
+ const query = (flags: string[]) => {
167
+ const result = spawnNative(
168
+ execution.tsgo.binary,
169
+ [...args, ...flags],
170
+ { cwd: execution.compilerSelection.compilerArgsCwd, env },
171
+ );
172
+ if (result.error || result.status !== 0 || result.signal !== null)
173
+ throw new Error("runtime input inspection unavailable");
174
+ return String(result.stdout);
175
+ };
176
+ const config = JSON.parse(query(["--showConfig"]));
177
+ if (
178
+ !config ||
179
+ typeof config.compilerOptions !== "object" ||
180
+ config.compilerOptions === null ||
181
+ config.references?.length ||
182
+ config.compilerOptions.runExternalCode === true ||
183
+ [
184
+ "generateTrace",
185
+ "generateCpuProfile",
186
+ "pprofDir",
187
+ "diagnostics",
188
+ "extendedDiagnostics",
189
+ "traceResolution",
190
+ "explainFiles",
191
+ "listFiles",
192
+ "listEmittedFiles",
193
+ ].some((name) => config.compilerOptions[name])
194
+ )
195
+ return undefined;
196
+ const listed = query([
197
+ "--listFilesOnly",
198
+ "true",
199
+ "--listEmittedFiles",
200
+ "false",
201
+ ])
202
+ .split(/\r?\n/)
203
+ .filter((line) => line.length !== 0);
204
+ if (
205
+ listed.length === 0 ||
206
+ !listed.every(
207
+ (file) =>
208
+ path.isAbsolute(file) && /\.(?:[cm]?[jt]sx?|json)$/i.test(file),
209
+ )
210
+ )
211
+ return undefined;
212
+ const inputs = new Set([
213
+ ...listed,
214
+ ...execution.project.configPaths,
215
+ ...inspected.observations.keys(),
216
+ ]);
217
+ const scopes = new Set<string>();
218
+ for (const file of listed) {
219
+ for (const spelling of [file, fs.realpathSync.native(file)]) {
220
+ let directory = path.dirname(spelling);
221
+ while (!scopes.has(directory)) {
222
+ scopes.add(directory);
223
+ inputs.add(path.join(directory, "package.json"));
224
+ const parent = path.dirname(directory);
225
+ if (parent === directory) break;
226
+ directory = parent;
227
+ }
228
+ }
229
+ }
230
+ const hashes = [...inputs].sort().map((file) => {
231
+ try {
232
+ const observed = JSON.parse(
233
+ CompilerArgumentsInspection.observeInputFile(file),
234
+ );
235
+ return [file, observed[0], observed[2]];
236
+ } catch (error) {
237
+ if (
238
+ (error as NodeJS.ErrnoException).code !== "ENOENT" &&
239
+ (error as NodeJS.ErrnoException).code !== "ENOTDIR"
240
+ )
241
+ throw error;
242
+ return [file, null, null];
243
+ }
244
+ });
245
+ const hostInputs: [string, string | null, string | null][] = [];
246
+ for (const [file, expected] of Object.entries(
247
+ loaded?.hostInputHashes ?? {},
248
+ ).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))) {
249
+ const [physical, hash] = RuntimeInputProof.observeHostInput(file);
250
+ if (
251
+ hash !== expected ||
252
+ physical !== loaded?.hostInputRealpaths[file]
253
+ )
254
+ return undefined;
255
+ hostInputs.push([file, physical, hash]);
256
+ }
257
+ for (const [file, before] of inspected.observations)
258
+ if (CompilerArgumentsInspection.observeInputFile(file) !== before)
259
+ return undefined;
260
+ // Only private destination values are normalized. The relative layout
261
+ // remains in the identity because source maps are output-relative.
262
+ for (const key of [
263
+ "outDir",
264
+ "declarationDir",
265
+ "tsBuildInfoFile",
266
+ "outFile",
267
+ ])
268
+ if (
269
+ typeof config.compilerOptions[key] === "string" &&
270
+ (path.resolve(
271
+ path.dirname(execution.tsconfig),
272
+ config.compilerOptions[key],
273
+ ) === options.isolateOutputsTo ||
274
+ path
275
+ .resolve(
276
+ path.dirname(execution.tsconfig),
277
+ config.compilerOptions[key],
278
+ )
279
+ .startsWith(emitDir + path.sep))
280
+ )
281
+ config.compilerOptions[key] =
282
+ "<private-output>" +
283
+ path.relative(
284
+ emitDir,
285
+ path.resolve(
286
+ path.dirname(execution.tsconfig),
287
+ config.compilerOptions[key],
288
+ ),
289
+ );
290
+ // Upstream synthesizes an exclusion for the overridden outDir too.
291
+ if (Array.isArray(config.exclude))
292
+ config.exclude = config.exclude.map((name: unknown) =>
293
+ typeof name === "string" &&
294
+ path.resolve(path.dirname(execution.tsconfig), name) === emitDir
295
+ ? "<private-output>"
296
+ : name,
297
+ );
298
+ const environment = Object.entries(env)
299
+ .filter(([key]) => !/^TTSX_RUNTIME_/i.test(key))
300
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
301
+ return JSON.stringify({
302
+ format: 1,
303
+ version: identities.version,
304
+ compilerIdentities,
305
+ config,
306
+ hashes,
307
+ hostInputs,
308
+ environment: crypto
309
+ .createHash("sha256")
310
+ .update(JSON.stringify(environment))
311
+ .digest("hex"),
312
+ project: execution.tsconfig,
313
+ cwd: execution.compilerSelection.compilerArgsCwd,
314
+ layout: path.relative(emitDir, execution.projectRoot),
315
+ unchecked: options.skipDiagnosticsCheck === true,
316
+ plugins: execution.nativePlugins,
317
+ pluginSources: loaded?.pluginSources,
318
+ descriptor: loaded?.hostInputHashes,
319
+ });
320
+ } catch {
321
+ return undefined;
322
+ }
323
+ },
324
+ admit: (result, emitDir) => {
325
+ if (
326
+ result.status !== 0 ||
327
+ result.emittedSources === undefined ||
328
+ Object.keys(result.emittedSourceProofFailures ?? {}).length !== 0 ||
329
+ !Object.values(result.emittedSources).every(
330
+ (sources) =>
331
+ Array.isArray(sources) &&
332
+ sources.length > 0 &&
333
+ sources.every(
334
+ (source) =>
335
+ typeof source === "string" && path.isAbsolute(source),
336
+ ),
337
+ )
338
+ )
339
+ return false;
340
+ if (
341
+ execution.nativePlugins.length !== 0 &&
342
+ result.graph?.inputObservations === undefined
343
+ )
344
+ return false;
345
+ return RuntimeInputProof.holds(result, execution.projectRoot, emitDir);
346
+ },
347
+ };
348
+ }
349
+ }