ttsc 0.26.2 → 0.28.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 (170) hide show
  1. package/README.md +9 -0
  2. package/cmd/graphbench/main.go +26 -16
  3. package/cmd/graphdump/main.go +37 -12
  4. package/cmd/graphdump/run_declares_the_capability_its_output_carries_test.go +137 -0
  5. package/cmd/graphdump/run_reports_an_unknown_flag_without_exiting_the_process_test.go +56 -0
  6. package/cmd/graphdump/run_reports_an_unloadable_project_without_printing_a_dump_test.go +46 -0
  7. package/cmd/ttsc/api_transform.go +28 -3
  8. package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
  9. package/cmd/ttscgraph/disk_realpath_other.go +16 -0
  10. package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
  11. package/cmd/ttscgraph/dump.go +43 -11
  12. package/cmd/ttscgraph/run_dump_claims_artifacts_only_when_it_asked_test.go +135 -0
  13. package/cmd/ttscgraph/run_dump_declares_the_capability_its_output_carries_test.go +96 -0
  14. package/cmd/ttscgraph/serve.go +357 -60
  15. package/cmd/ttscgraph/serve_adopts_a_republished_artifact_set_test.go +348 -0
  16. package/cmd/ttscgraph/serve_projects_an_artifact_no_source_owns_test.go +90 -0
  17. package/cmd/ttscgraph/serve_shards.go +31 -9
  18. package/driver/config_discovery.go +121 -0
  19. package/driver/emit_plugin.go +77 -11
  20. package/driver/graph.go +64 -11
  21. package/driver/input_observation.go +273 -0
  22. package/driver/input_observation_test.go +149 -0
  23. package/driver/plugins.go +506 -15
  24. package/driver/program.go +54 -1
  25. package/driver/resolution_candidates.go +13 -5
  26. package/driver/rewrite.go +45 -2
  27. package/driver/transform_dependencies.go +283 -0
  28. package/driver/transform_dependencies_declares_every_file_without_a_contributor_test.go +28 -0
  29. package/driver/transform_dependencies_drops_a_self_reported_dependency_test.go +21 -0
  30. package/driver/transform_dependencies_keys_a_reported_path_like_the_envelope_test.go +46 -0
  31. package/driver/transform_dependencies_requires_every_contributor_to_declare_test.go +38 -0
  32. package/driver/transform_dependencies_stays_silent_for_an_undeclared_contributor_test.go +23 -0
  33. package/driver/transform_dependencies_withdraws_completeness_after_an_unusable_dependency_test.go +37 -0
  34. package/internal/graph/artifacts.go +230 -0
  35. package/internal/graph/artifacts_index_what_a_plugin_published_test.go +154 -0
  36. package/internal/graph/artifacts_survive_a_partial_rebuild_test.go +89 -0
  37. package/internal/graph/build.go +35 -0
  38. package/internal/graph/build_releases_the_scratch_it_no_longer_reads_test.go +118 -0
  39. package/internal/graph/doc_refs_are_the_use_the_checker_already_counts_test.go +93 -0
  40. package/internal/graph/doc_refs_resolve_documentation_links_test.go +335 -0
  41. package/internal/graph/doc_tags.go +416 -0
  42. package/internal/graph/doc_tags_are_claimed_before_they_are_read_test.go +131 -0
  43. package/internal/graph/doc_tags_capture_every_unrecognized_tag_test.go +165 -0
  44. package/internal/graph/doc_tags_read_only_what_the_parser_attached_test.go +97 -0
  45. package/internal/graph/doc_tags_stop_at_the_external_boundary_test.go +59 -0
  46. package/internal/graph/doc_tags_survive_line_endings_and_repeated_blocks_test.go +146 -0
  47. package/internal/graph/dump.go +60 -13
  48. package/internal/graph/dump_checkout_paths_are_stable_test.go +2 -2
  49. package/internal/graph/dump_paths.go +2 -2
  50. package/internal/graph/dump_paths_are_portable_and_injective_test.go +1 -1
  51. package/internal/graph/edge_kinds_names_every_declared_kind_test.go +49 -0
  52. package/internal/graph/edges.go +1 -0
  53. package/internal/graph/graph.go +79 -4
  54. package/internal/graph/provenance.go +29 -2
  55. package/lib/TtscCompiler.d.ts +12 -11
  56. package/lib/TtscCompiler.js +44 -29
  57. package/lib/TtscCompiler.js.map +1 -1
  58. package/lib/compiler/internal/captureProcessOutput.js +2 -2
  59. package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
  60. package/lib/compiler/internal/compileProjectInMemory.js +4 -3
  61. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  62. package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +9 -0
  63. package/lib/compiler/internal/project/createNativeProjectContextArgs.js +16 -4
  64. package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -1
  65. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
  66. package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
  67. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  68. package/lib/compiler/internal/runBuild.d.ts +1 -1
  69. package/lib/compiler/internal/runBuild.js +80 -34
  70. package/lib/compiler/internal/runBuild.js.map +1 -1
  71. package/lib/compiler/internal/runSingleFileEmit.js +2 -2
  72. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  73. package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
  74. package/lib/compiler/internal/sharedHostHelpers.js +39 -0
  75. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  76. package/lib/compiler/internal/startResidentTransform.js +12 -4
  77. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  78. package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
  79. package/lib/compiler/internal/transformProjectInMemory.js +239 -35
  80. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  81. package/lib/flags/schema.js +12 -5
  82. package/lib/flags/schema.js.map +1 -1
  83. package/lib/index.d.ts +1 -0
  84. package/lib/index.js +1 -0
  85. package/lib/index.js.map +1 -1
  86. package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
  87. package/lib/internal/createCanonicalTempDirectory.js +42 -0
  88. package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
  89. package/lib/internal/projectInputPathIdentity.d.ts +3 -0
  90. package/lib/internal/projectInputPathIdentity.js +11 -5
  91. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  92. package/lib/internal/resolveNodeBinary.d.ts +14 -0
  93. package/lib/internal/resolveNodeBinary.js +150 -0
  94. package/lib/internal/resolveNodeBinary.js.map +1 -0
  95. package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
  96. package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
  97. package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
  98. package/lib/launcher/internal/prepareExecution.d.ts +3 -0
  99. package/lib/launcher/internal/prepareExecution.js +42 -14
  100. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  101. package/lib/launcher/internal/runTtsc.js +25 -13
  102. package/lib/launcher/internal/runTtsc.js.map +1 -1
  103. package/lib/launcher/internal/runTtscserver.js +18 -3
  104. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  105. package/lib/launcher/internal/runTtsx.js +2 -0
  106. package/lib/launcher/internal/runTtsx.js.map +1 -1
  107. package/lib/launcher/internal/runtimeHooks.d.ts +47 -3
  108. package/lib/launcher/internal/runtimeHooks.js +746 -64
  109. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  110. package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
  111. package/lib/plugin/internal/buildSourcePlugin.js +754 -72
  112. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  113. package/lib/plugin/internal/capabilityResolutionCache.d.ts +76 -0
  114. package/lib/plugin/internal/capabilityResolutionCache.js +224 -0
  115. package/lib/plugin/internal/capabilityResolutionCache.js.map +1 -0
  116. package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
  117. package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
  118. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  119. package/lib/plugin/resolveCapabilityPlugins.d.ts +55 -0
  120. package/lib/plugin/resolveCapabilityPlugins.js +118 -0
  121. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -0
  122. package/lib/register.d.ts +1 -0
  123. package/lib/register.js +46 -0
  124. package/lib/register.js.map +1 -0
  125. package/lib/structures/ITtscCompilerContext.d.ts +6 -3
  126. package/lib/structures/ITtscCompilerTransformation.d.ts +38 -0
  127. package/lib/structures/ITtscPlugin.d.ts +33 -0
  128. package/package.json +16 -9
  129. package/shim/ast/parent.go +16 -3
  130. package/shim/checker/enums_gen.go +11 -0
  131. package/shim/checker/go.mod +9 -0
  132. package/shim/checker/go.sum +22 -0
  133. package/shim/checker/shim.go +78 -0
  134. package/shim/compiler/emithook.go +10 -0
  135. package/shim/compiler/incremental.go +45 -8
  136. package/src/TtscCompiler.ts +55 -33
  137. package/src/compiler/internal/captureProcessOutput.ts +3 -2
  138. package/src/compiler/internal/compileProjectInMemory.ts +4 -3
  139. package/src/compiler/internal/project/createNativeProjectContextArgs.ts +19 -4
  140. package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
  141. package/src/compiler/internal/runBuild.ts +102 -39
  142. package/src/compiler/internal/runSingleFileEmit.ts +2 -2
  143. package/src/compiler/internal/sharedHostHelpers.ts +44 -0
  144. package/src/compiler/internal/startResidentTransform.ts +12 -3
  145. package/src/compiler/internal/transformProjectInMemory.ts +410 -39
  146. package/src/flags/schema.ts +12 -5
  147. package/src/index.ts +1 -0
  148. package/src/internal/createCanonicalTempDirectory.ts +53 -0
  149. package/src/internal/projectInputPathIdentity.ts +17 -4
  150. package/src/internal/resolveNodeBinary.ts +173 -0
  151. package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
  152. package/src/launcher/internal/prepareExecution.ts +50 -14
  153. package/src/launcher/internal/runTtsc.ts +24 -13
  154. package/src/launcher/internal/runTtscserver.ts +18 -3
  155. package/src/launcher/internal/runTtsx.ts +2 -0
  156. package/src/launcher/internal/runtimeHooks.ts +934 -68
  157. package/src/plugin/internal/buildSourcePlugin.ts +984 -79
  158. package/src/plugin/internal/capabilityResolutionCache.ts +305 -0
  159. package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
  160. package/src/plugin/resolveCapabilityPlugins.ts +171 -0
  161. package/src/register.ts +47 -0
  162. package/src/structures/ITtscCompilerContext.ts +6 -3
  163. package/src/structures/ITtscCompilerTransformation.ts +46 -0
  164. package/src/structures/ITtscPlugin.ts +36 -0
  165. package/tools/shim_audit/baseline.json +0 -2
  166. package/utility/host.go +18 -3
  167. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
  168. package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
  169. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
  170. package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
@@ -1,8 +1,11 @@
1
1
  import path from "node:path";
2
2
 
3
+ import { resolveNodeBinary } from "../../internal/resolveNodeBinary";
3
4
  import {
4
- hasProjectPluginEntries,
5
+ collectProjectHostInputs,
6
+ hashHostInputPaths,
5
7
  loadProjectPlugins,
8
+ realpathHostInputPaths,
6
9
  } from "../../plugin/internal/loadProjectPlugins";
7
10
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
8
11
  import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiagnostic";
@@ -13,12 +16,13 @@ import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult"
13
16
  import { buildNativeCompiler } from "./buildNativeCompiler";
14
17
  import { packageRootDir } from "./paths";
15
18
  import { createNativeProjectContextArgs } from "./project/createNativeProjectContextArgs";
16
- import { readProjectConfig } from "./project/readProjectConfig";
17
19
  import { resolveBinary } from "./resolveBinary";
18
20
  import { resolveTsgo } from "./resolveTsgo";
19
21
  import { appendBuildOutput, normalizeBuildOutput } from "./runBuild";
20
22
  import {
21
23
  assertSharedHostCompatibility,
24
+ clearInheritedTsgoArgs,
25
+ inheritedSidecarEnv,
22
26
  linkedTransformPlugins,
23
27
  resolvePluginConfigDir,
24
28
  selectSharedHostPlugin,
@@ -44,28 +48,28 @@ export function transformProjectInMemory(options: ITtscCompilerContext): {
44
48
  dependencies?: Record<string, string[]>;
45
49
  dependenciesComplete?: string[];
46
50
  graph?: ITtscCompilerTransformation.IReferenceGraph;
51
+ hostInputHashes?: Record<string, string | null>;
52
+ hostInputRealpaths?: Record<string, string | null>;
53
+ hostInputs?: string[];
47
54
  result: TtscBuildResult;
48
55
  typescript: Record<string, string>;
49
56
  volatile?: string[];
50
57
  } {
51
58
  const cwd = path.resolve(options.cwd ?? process.cwd());
52
- const project = readProjectConfig({
59
+ const loaded = loadProjectPlugins({
60
+ binary: resolveBinary(options) ?? "",
61
+ cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
53
62
  cwd,
63
+ entries: options.plugins,
64
+ env: inheritedSidecarEnv(options.env),
65
+ pluginConfigDir: options.pluginConfigDir,
54
66
  projectRoot: options.projectRoot,
55
67
  tsconfig: options.tsconfig,
56
68
  });
57
- if (hasConfiguredPlugins(options, project)) {
58
- return transformProjectWithPlugins(options, cwd, project);
69
+ if (loaded.nativePlugins.length !== 0) {
70
+ return transformProjectWithPlugins(options, loaded);
59
71
  }
60
- return transformProjectWithNativeHost(options, project);
61
- }
62
-
63
- /** Return true when the project or the call-level options declare any plugins. */
64
- function hasConfiguredPlugins(
65
- options: ITtscCompilerContext,
66
- project: ITtscParsedProjectConfig,
67
- ): boolean {
68
- return hasProjectPluginEntries(project, options.plugins);
72
+ return transformProjectWithNativeHost(options, loaded.project, loaded);
69
73
  }
70
74
 
71
75
  /**
@@ -76,14 +80,47 @@ function hasConfiguredPlugins(
76
80
  function transformProjectWithNativeHost(
77
81
  options: ITtscCompilerContext,
78
82
  project: ITtscParsedProjectConfig,
83
+ baseline?: {
84
+ hostInputHashes: Readonly<Record<string, string | null>>;
85
+ hostInputRealpaths: Readonly<Record<string, string | null>>;
86
+ hostInputs: readonly string[];
87
+ },
79
88
  ): {
80
89
  dependencies?: Record<string, string[]>;
81
90
  dependenciesComplete?: string[];
82
91
  graph?: ITtscCompilerTransformation.IReferenceGraph;
92
+ hostInputHashes?: Record<string, string | null>;
93
+ hostInputRealpaths?: Record<string, string | null>;
94
+ hostInputs?: string[];
83
95
  result: TtscBuildResult;
84
96
  typescript: Record<string, string>;
85
97
  volatile?: string[];
86
98
  } {
99
+ // Capture the project inputs before the native host observes them. Pairing a
100
+ // post-build hash with an earlier result can bless a torn generation when a
101
+ // config changes during the child process.
102
+ // Plugin discovery already ran in loadProjectPlugins. The native host only
103
+ // consumes the resolved config chain here, so dependency package manifests
104
+ // must not become per-project universal inputs a second time.
105
+ const projectHostInputs = collectProjectHostInputs(project, false);
106
+ const projectHostInputHashes = hashHostInputPaths(projectHostInputs);
107
+ const projectHostInputRealpaths = realpathHostInputPaths(projectHostInputs);
108
+ const observedHostInputs = mergeHostInputs(
109
+ baseline?.hostInputs,
110
+ projectHostInputs,
111
+ );
112
+ const observedHostInputHashes = mergeCompatibleHostInputHashes(
113
+ baseline?.hostInputHashes ?? {},
114
+ projectHostInputHashes,
115
+ baseline?.hostInputs ?? [],
116
+ projectHostInputs,
117
+ );
118
+ const observedHostInputRealpaths = mergeCompatibleHostInputHashes(
119
+ baseline?.hostInputRealpaths ?? {},
120
+ projectHostInputRealpaths,
121
+ baseline?.hostInputs ?? [],
122
+ projectHostInputs,
123
+ );
87
124
  const binary = buildNativeCompiler({
88
125
  cacheBaseDir: project.root,
89
126
  cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
@@ -94,7 +131,7 @@ function transformProjectWithNativeHost(
94
131
  ["api-transform", "--cwd", project.root, "--tsconfig", project.path],
95
132
  {
96
133
  cwd: project.root,
97
- env: { ...process.env, ...options.env },
134
+ env: inheritedSidecarEnv(options.env),
98
135
  },
99
136
  );
100
137
  if (res.error) {
@@ -107,8 +144,37 @@ function transformProjectWithNativeHost(
107
144
  outputText(res.stdout),
108
145
  outputText(res.stderr),
109
146
  );
147
+ const finalObservedHostInputHashes = revalidateHostInputHashes(
148
+ observedHostInputHashes,
149
+ observedHostInputs,
150
+ );
151
+ const finalOutputHostInputHashes = revalidateHostInputHashes(
152
+ output.hostInputHashes ?? {},
153
+ output.hostInputs ?? [],
154
+ );
155
+ const finalObservedHostInputRealpaths = revalidateHostInputRealpaths(
156
+ observedHostInputRealpaths,
157
+ observedHostInputs,
158
+ );
159
+ const finalOutputHostInputRealpaths = revalidateHostInputRealpaths(
160
+ output.hostInputRealpaths ?? {},
161
+ output.hostInputs ?? [],
162
+ );
110
163
  return {
111
164
  ...envelopeSideChannels(output),
165
+ hostInputHashes: mergeCompatibleHostInputHashes(
166
+ finalObservedHostInputHashes,
167
+ finalOutputHostInputHashes,
168
+ observedHostInputs,
169
+ output.hostInputs,
170
+ ),
171
+ hostInputRealpaths: mergeCompatibleHostInputHashes(
172
+ finalObservedHostInputRealpaths,
173
+ finalOutputHostInputRealpaths,
174
+ observedHostInputs,
175
+ output.hostInputs,
176
+ ),
177
+ hostInputs: mergeHostInputs(observedHostInputs, output.hostInputs),
112
178
  result: {
113
179
  diagnostics: output.diagnostics,
114
180
  status: res.status ?? 1,
@@ -121,26 +187,19 @@ function transformProjectWithNativeHost(
121
187
 
122
188
  function transformProjectWithPlugins(
123
189
  options: ITtscCompilerContext,
124
- cwd: string,
125
- project: ITtscParsedProjectConfig,
190
+ loaded: ReturnType<typeof loadProjectPlugins>,
126
191
  ): {
127
192
  dependencies?: Record<string, string[]>;
128
193
  dependenciesComplete?: string[];
129
194
  graph?: ITtscCompilerTransformation.IReferenceGraph;
195
+ hostInputHashes?: Record<string, string | null>;
196
+ hostInputRealpaths?: Record<string, string | null>;
197
+ hostInputs?: string[];
130
198
  result: TtscBuildResult;
131
199
  typescript: Record<string, string>;
132
200
  volatile?: string[];
133
201
  } {
134
- const loaded = loadProjectPlugins({
135
- binary: resolveBinary(options) ?? "",
136
- cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
137
- cwd,
138
- entries: options.plugins,
139
- env: { ...process.env, ...options.env },
140
- pluginConfigDir: options.pluginConfigDir,
141
- projectRoot: options.projectRoot,
142
- tsconfig: project.path,
143
- });
202
+ const { project } = loaded;
144
203
  const checks = loaded.nativePlugins.filter(
145
204
  (plugin) => plugin.stage === "check",
146
205
  );
@@ -160,14 +219,44 @@ function transformProjectWithPlugins(
160
219
  );
161
220
  if (checked.status !== 0) {
162
221
  return {
222
+ hostInputHashes: revalidateHostInputHashes(
223
+ loaded.hostInputHashes,
224
+ loaded.hostInputs,
225
+ ),
226
+ hostInputRealpaths: revalidateHostInputRealpaths(
227
+ loaded.hostInputRealpaths,
228
+ loaded.hostInputs,
229
+ ),
230
+ hostInputs: loaded.hostInputs,
163
231
  result: checked,
164
232
  typescript: {},
165
233
  };
166
234
  }
167
235
  if (transformers.length === 0) {
168
236
  const transformed = transformProjectWithNativeHost(options, project);
237
+ const finalLoadedHostInputHashes = revalidateHostInputHashes(
238
+ loaded.hostInputHashes,
239
+ loaded.hostInputs,
240
+ );
241
+ const finalLoadedHostInputRealpaths = revalidateHostInputRealpaths(
242
+ loaded.hostInputRealpaths,
243
+ loaded.hostInputs,
244
+ );
169
245
  return {
170
246
  ...envelopeSideChannels(transformed),
247
+ hostInputHashes: mergeCompatibleHostInputHashes(
248
+ finalLoadedHostInputHashes,
249
+ transformed.hostInputHashes,
250
+ loaded.hostInputs,
251
+ transformed.hostInputs,
252
+ ),
253
+ hostInputRealpaths: mergeCompatibleHostInputHashes(
254
+ finalLoadedHostInputRealpaths,
255
+ transformed.hostInputRealpaths,
256
+ loaded.hostInputs,
257
+ transformed.hostInputs,
258
+ ),
259
+ hostInputs: mergeHostInputs(loaded.hostInputs, transformed.hostInputs),
171
260
  result: appendBuildOutput(checked, transformed.result),
172
261
  typescript: transformed.typescript,
173
262
  };
@@ -184,7 +273,13 @@ function transformProjectWithPlugins(
184
273
  ),
185
274
  {
186
275
  cwd: project.root,
187
- env: nativePluginEnv(options, tsgoBinary, loaded.nativePlugins, plugin),
276
+ env: nativePluginEnv(
277
+ options,
278
+ project.root,
279
+ tsgoBinary,
280
+ loaded.nativePlugins,
281
+ plugin,
282
+ ),
188
283
  },
189
284
  );
190
285
  if (res.error) {
@@ -202,13 +297,76 @@ function transformProjectWithPlugins(
202
297
  stdout: "",
203
298
  stderr: outputText(res.stderr),
204
299
  };
300
+ const finalLoadedHostInputHashes = revalidateHostInputHashes(
301
+ loaded.hostInputHashes,
302
+ loaded.hostInputs,
303
+ );
304
+ const finalOutputHostInputHashes = revalidateHostInputHashes(
305
+ output.hostInputHashes ?? {},
306
+ output.hostInputs ?? [],
307
+ );
308
+ const finalLoadedHostInputRealpaths = revalidateHostInputRealpaths(
309
+ loaded.hostInputRealpaths,
310
+ loaded.hostInputs,
311
+ );
312
+ const finalOutputHostInputRealpaths = revalidateHostInputRealpaths(
313
+ output.hostInputRealpaths ?? {},
314
+ output.hostInputs ?? [],
315
+ );
205
316
  return {
206
317
  ...envelopeSideChannels(output),
318
+ hostInputHashes: mergeCompatibleHostInputHashes(
319
+ finalLoadedHostInputHashes,
320
+ finalOutputHostInputHashes,
321
+ loaded.hostInputs,
322
+ output.hostInputs,
323
+ ),
324
+ hostInputRealpaths: mergeCompatibleHostInputHashes(
325
+ finalLoadedHostInputRealpaths,
326
+ finalOutputHostInputRealpaths,
327
+ loaded.hostInputs,
328
+ output.hostInputs,
329
+ ),
330
+ hostInputs: mergeHostInputs(loaded.hostInputs, output.hostInputs),
207
331
  result: appendBuildOutput(checked, result),
208
332
  typescript: output.typescript,
209
333
  };
210
334
  }
211
335
 
336
+ /** Keep evaluation proof only when the same input still has the same state. */
337
+ function revalidateHostInputHashes(
338
+ initial: Readonly<Record<string, string | null>>,
339
+ inputs: readonly string[],
340
+ ): Record<string, string | null> {
341
+ const current = hashHostInputPaths(inputs);
342
+ return Object.fromEntries(
343
+ inputs.flatMap((input) => {
344
+ const absolute = path.resolve(input);
345
+ return Object.prototype.hasOwnProperty.call(initial, absolute) &&
346
+ initial[absolute] === current[absolute]
347
+ ? ([[absolute, current[absolute]!]] as const)
348
+ : [];
349
+ }),
350
+ );
351
+ }
352
+
353
+ /** Keep evaluation proof only while each lexical path selects the same target. */
354
+ function revalidateHostInputRealpaths(
355
+ initial: Readonly<Record<string, string | null>>,
356
+ inputs: readonly string[],
357
+ ): Record<string, string | null> {
358
+ const current = realpathHostInputPaths(inputs);
359
+ return Object.fromEntries(
360
+ inputs.flatMap((input) => {
361
+ const absolute = path.resolve(input);
362
+ return Object.prototype.hasOwnProperty.call(initial, absolute) &&
363
+ initial[absolute] === current[absolute]
364
+ ? ([[absolute, current[absolute]!]] as const)
365
+ : [];
366
+ }),
367
+ );
368
+ }
369
+
212
370
  /**
213
371
  * Collect the optional advisory envelope fields (`dependencies`,
214
372
  * `dependenciesComplete`, `graph`, `volatile`) into a spreadable object,
@@ -238,6 +396,73 @@ function envelopeSideChannels(output: {
238
396
  };
239
397
  }
240
398
 
399
+ /** Merge JavaScript- and native-host universal inputs by absolute path. */
400
+ function mergeHostInputs(
401
+ ...groups: readonly (readonly string[] | undefined)[]
402
+ ): string[] {
403
+ return [
404
+ ...new Set(
405
+ groups.flatMap((group) =>
406
+ (group ?? []).map((file) => path.resolve(file)),
407
+ ),
408
+ ),
409
+ ].sort();
410
+ }
411
+
412
+ /** Keep only native/descriptor fingerprints that agree on shared paths. */
413
+ function mergeCompatibleHostInputHashes(
414
+ first: Readonly<Record<string, string | null>>,
415
+ second: Readonly<Record<string, string | null>> | undefined,
416
+ firstInputs: readonly string[],
417
+ secondInputs: readonly string[] | undefined,
418
+ ): Record<string, string | null> {
419
+ const firstDeclared = new Set(
420
+ firstInputs.map((input) => path.resolve(input)),
421
+ );
422
+ const secondDeclared = new Set(
423
+ (secondInputs ?? []).map((input) => path.resolve(input)),
424
+ );
425
+ const output = Object.fromEntries(
426
+ Object.entries(first).flatMap(([file, hash]) => {
427
+ const absolute = path.resolve(file);
428
+ return firstDeclared.has(absolute) ? [[absolute, hash] as const] : [];
429
+ }),
430
+ );
431
+ const unproven = new Set<string>();
432
+ for (const input of firstInputs) {
433
+ const absolute = path.resolve(input);
434
+ if (!Object.prototype.hasOwnProperty.call(first, absolute)) {
435
+ delete output[absolute];
436
+ unproven.add(absolute);
437
+ }
438
+ }
439
+ for (const input of secondInputs ?? []) {
440
+ const absolute = path.resolve(input);
441
+ if (!Object.prototype.hasOwnProperty.call(second ?? {}, absolute)) {
442
+ delete output[absolute];
443
+ unproven.add(absolute);
444
+ }
445
+ }
446
+ for (const [file, hash] of Object.entries(second ?? {})) {
447
+ const absolute = path.resolve(file);
448
+ if (!secondDeclared.has(absolute)) continue;
449
+ if (unproven.has(absolute)) continue;
450
+ if (
451
+ Object.prototype.hasOwnProperty.call(output, absolute) &&
452
+ output[absolute] !== hash
453
+ ) {
454
+ // Two evaluation stages observed different states. Dropping proof makes
455
+ // persistent adapters replace the generation without turning advisory
456
+ // cache metadata into a user-facing compile failure.
457
+ delete output[absolute];
458
+ unproven.add(absolute);
459
+ continue;
460
+ }
461
+ output[absolute] = hash;
462
+ }
463
+ return output;
464
+ }
465
+
241
466
  /**
242
467
  * Run every check-stage plugin in sequence, short-circuiting on the first
243
468
  * failure. Returns the aggregated `TtscBuildResult` (status 0 when all pass).
@@ -266,7 +491,13 @@ function runNativeChecks(
266
491
  ),
267
492
  {
268
493
  cwd: project.root,
269
- env: nativePluginEnv(options, tsgoBinary, nativePlugins, plugin),
494
+ env: nativePluginEnv(
495
+ options,
496
+ project.root,
497
+ tsgoBinary,
498
+ nativePlugins,
499
+ plugin,
500
+ ),
270
501
  },
271
502
  );
272
503
  if (res.error) {
@@ -358,6 +589,7 @@ function serializeNativePlugins(
358
589
  */
359
590
  function nativePluginEnv(
360
591
  options: ITtscCompilerContext,
592
+ projectRoot: string,
361
593
  tsgoBinary: string,
362
594
  nativePlugins?: readonly ITtscLoadedNativePlugin[],
363
595
  plugin?: ITtscLoadedNativePlugin,
@@ -365,7 +597,6 @@ function nativePluginEnv(
365
597
  const pluginConfigDir = resolvePluginConfigDir(options);
366
598
  const env: NodeJS.ProcessEnv = {
367
599
  ...process.env,
368
- TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
369
600
  ...(pluginConfigDir === undefined
370
601
  ? {}
371
602
  : { TTSC_PLUGIN_CONFIG_DIR: pluginConfigDir }),
@@ -375,6 +606,9 @@ function nativePluginEnv(
375
606
  path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
376
607
  ...options.env,
377
608
  };
609
+ const node = resolveNodeBinary(env, projectRoot);
610
+ if (node === undefined) delete env.TTSC_NODE_BINARY;
611
+ else env.TTSC_NODE_BINARY = node;
378
612
  // The anchor is per-invocation state owned by this host: when this run
379
613
  // declared none (and the caller's env does not name one), drop any value
380
614
  // inherited from an ancestor ttsc process so a nested build never
@@ -385,6 +619,9 @@ function nativePluginEnv(
385
619
  ) {
386
620
  delete env.TTSC_PLUGIN_CONFIG_DIR;
387
621
  }
622
+ // This lane forwards no tsgo argv of its own, so anything inherited belongs
623
+ // to an outer ttsc run and must not reach these sidecars.
624
+ clearInheritedTsgoArgs(env, options.env);
388
625
  if (plugin?.stage === "transform") {
389
626
  const linked = linkedTransformPlugins(nativePlugins ?? []);
390
627
  if (linked.length !== 0) {
@@ -418,6 +655,9 @@ function parseNativeTransformOutput(
418
655
  dependenciesComplete?: string[];
419
656
  diagnostics: ITtscCompilerDiagnostic[];
420
657
  graph?: ITtscCompilerTransformation.IReferenceGraph;
658
+ hostInputHashes?: Record<string, string | null>;
659
+ hostInputRealpaths?: Record<string, string | null>;
660
+ hostInputs?: string[];
421
661
  typescript: Record<string, string>;
422
662
  volatile?: string[];
423
663
  } {
@@ -427,6 +667,9 @@ function parseNativeTransformOutput(
427
667
  dependenciesComplete?: string[];
428
668
  diagnostics?: ITtscCompilerDiagnostic[];
429
669
  graph?: ITtscCompilerTransformation.IReferenceGraph;
670
+ hostInputHashes?: Record<string, string | null>;
671
+ hostInputRealpaths?: Record<string, string | null>;
672
+ hostInputs?: string[];
430
673
  typescript?: Record<string, string>;
431
674
  volatile?: string[];
432
675
  };
@@ -438,11 +681,19 @@ function parseNativeTransformOutput(
438
681
  const dependencies = parseDependencyLists(parsed.dependencies);
439
682
  const dependenciesComplete = parseFileList(parsed.dependenciesComplete);
440
683
  const graph = parseReferenceGraph(parsed.graph);
684
+ const hostInputHashes = parseHostInputHashes(parsed.hostInputHashes);
685
+ const hostInputRealpaths = parseHostInputRealpaths(
686
+ parsed.hostInputRealpaths,
687
+ );
688
+ const hostInputs = parseFileList(parsed.hostInputs);
441
689
  const volatile = parseFileList(parsed.volatile);
442
690
  return {
443
691
  ...(dependencies === undefined ? {} : { dependencies }),
444
692
  ...(dependenciesComplete === undefined ? {} : { dependenciesComplete }),
445
693
  ...(graph === undefined ? {} : { graph }),
694
+ ...(hostInputHashes === undefined ? {} : { hostInputHashes }),
695
+ ...(hostInputRealpaths === undefined ? {} : { hostInputRealpaths }),
696
+ ...(hostInputs === undefined ? {} : { hostInputs }),
446
697
  ...(volatile === undefined ? {} : { volatile }),
447
698
  diagnostics: Array.isArray(parsed.diagnostics) ? parsed.diagnostics : [],
448
699
  typescript: parsed.typescript,
@@ -458,6 +709,49 @@ function parseNativeTransformOutput(
458
709
  }
459
710
  }
460
711
 
712
+ /** Parse native evaluation-time SHA-256/null host-input fingerprints. */
713
+ function parseHostInputHashes(
714
+ value: unknown,
715
+ ): Record<string, string | null> | undefined {
716
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
717
+ return undefined;
718
+ }
719
+ const output: Record<string, string | null> = {};
720
+ for (const [file, hash] of Object.entries(value)) {
721
+ if (
722
+ !path.isAbsolute(file) ||
723
+ (hash !== null &&
724
+ (typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash)))
725
+ ) {
726
+ continue;
727
+ }
728
+ output[path.resolve(file)] = hash;
729
+ }
730
+ return Object.keys(output).length === 0 ? undefined : output;
731
+ }
732
+
733
+ /** Parse native evaluation-time realpath/null host-input identities. */
734
+ function parseHostInputRealpaths(
735
+ value: unknown,
736
+ ): Record<string, string | null> | undefined {
737
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
738
+ return undefined;
739
+ }
740
+ const output: Record<string, string | null> = {};
741
+ for (const [file, realpath] of Object.entries(value)) {
742
+ if (
743
+ !path.isAbsolute(file) ||
744
+ (realpath !== null &&
745
+ (typeof realpath !== "string" || !path.isAbsolute(realpath)))
746
+ ) {
747
+ continue;
748
+ }
749
+ output[path.resolve(file)] =
750
+ realpath === null ? null : path.resolve(realpath as string);
751
+ }
752
+ return Object.keys(output).length === 0 ? undefined : output;
753
+ }
754
+
461
755
  /**
462
756
  * Normalize the optional `dependencies` envelope field into a record of string
463
757
  * arrays, or `undefined` when absent or carrying nothing usable.
@@ -483,11 +777,37 @@ function parseDependencyLists(
483
777
  return Object.keys(output).length === 0 ? undefined : output;
484
778
  }
485
779
 
780
+ /**
781
+ * Normalize graph adjacency while retaining every well-formed node key.
782
+ *
783
+ * A leaf is intentionally encoded as an empty target array. Its key still
784
+ * declares graph membership and lets persistent hosts bind the compiler-time
785
+ * input proof for that source. A node needs a non-empty source key and an array
786
+ * value; invalid array members are filtered without erasing a valid node.
787
+ */
788
+ function parseGraphEdges(value: unknown): Record<string, string[]> | undefined {
789
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
790
+ return undefined;
791
+ }
792
+ const output: Record<string, string[]> = {};
793
+ for (const [key, entries] of Object.entries(value)) {
794
+ if (key.length === 0 || !Array.isArray(entries)) {
795
+ continue;
796
+ }
797
+ output[key] = entries.filter(
798
+ (entry): entry is string => typeof entry === "string",
799
+ );
800
+ }
801
+ return Object.keys(output).length === 0 ? undefined : output;
802
+ }
803
+
486
804
  /**
487
805
  * Normalize the optional `graph` envelope section with the same tolerance as
488
- * `dependencies`: non-object sections are dropped, edge entries that are not
489
- * string arrays are dropped, and non-string list members are filtered. A
490
- * section carrying nothing usable collapses to `undefined`.
806
+ * `dependencies`: non-object sections are dropped, empty source keys and edge
807
+ * entries that are not arrays are dropped, and non-string list members are
808
+ * filtered. Empty arrays remain as graph nodes because leaf membership binds
809
+ * compiler input proof. A section carrying nothing usable collapses to
810
+ * `undefined`.
491
811
  *
492
812
  * `candidates` is the one optional member, so an empty one is left off the
493
813
  * result instead of being materialized as `{}`. The host omits the key when it
@@ -506,11 +826,15 @@ function parseReferenceGraph(
506
826
  configs?: unknown;
507
827
  edges?: unknown;
508
828
  globals?: unknown;
829
+ inputHashes?: unknown;
830
+ inputRealpaths?: unknown;
509
831
  };
510
832
  const candidates = parseDependencyLists(section.candidates) ?? {};
511
- const edges = parseDependencyLists(section.edges) ?? {};
833
+ const edges = parseGraphEdges(section.edges) ?? {};
512
834
  const globals = parseFileList(section.globals) ?? [];
513
835
  const configs = parseFileList(section.configs) ?? [];
836
+ const inputHashes = parseGraphInputHashes(section.inputHashes);
837
+ const inputRealpaths = parseGraphInputRealpaths(section.inputRealpaths);
514
838
  if (
515
839
  Object.keys(candidates).length === 0 &&
516
840
  Object.keys(edges).length === 0 &&
@@ -519,15 +843,62 @@ function parseReferenceGraph(
519
843
  ) {
520
844
  return undefined;
521
845
  }
522
- return Object.keys(candidates).length === 0
523
- ? { configs, edges, globals }
524
- : { candidates, configs, edges, globals };
846
+ return {
847
+ ...(Object.keys(candidates).length === 0 ? {} : { candidates }),
848
+ configs,
849
+ edges,
850
+ globals,
851
+ ...(inputHashes === undefined ? {} : { inputHashes }),
852
+ ...(inputRealpaths === undefined ? {} : { inputRealpaths }),
853
+ };
854
+ }
855
+
856
+ /** Parse graph-keyed compiler-time content/null observations. */
857
+ function parseGraphInputHashes(
858
+ value: unknown,
859
+ ): Record<string, string | null> | undefined {
860
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
861
+ return undefined;
862
+ }
863
+ const output: Record<string, string | null> = {};
864
+ for (const [file, hash] of Object.entries(value)) {
865
+ if (
866
+ file.length === 0 ||
867
+ (hash !== null &&
868
+ (typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash)))
869
+ ) {
870
+ continue;
871
+ }
872
+ output[file] = hash;
873
+ }
874
+ return Object.keys(output).length === 0 ? undefined : output;
875
+ }
876
+
877
+ /** Parse graph-keyed compiler-time physical/null identities. */
878
+ function parseGraphInputRealpaths(
879
+ value: unknown,
880
+ ): Record<string, string | null> | undefined {
881
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
882
+ return undefined;
883
+ }
884
+ const output: Record<string, string | null> = {};
885
+ for (const [file, realpath] of Object.entries(value)) {
886
+ if (
887
+ file.length === 0 ||
888
+ (realpath !== null &&
889
+ (typeof realpath !== "string" || !path.isAbsolute(realpath)))
890
+ ) {
891
+ continue;
892
+ }
893
+ output[file] = realpath === null ? null : path.resolve(realpath);
894
+ }
895
+ return Object.keys(output).length === 0 ? undefined : output;
525
896
  }
526
897
 
527
898
  /**
528
899
  * Normalize an optional string-list envelope field (`dependenciesComplete`,
529
- * `volatile`, and the `globals`/`configs` graph sections), or `undefined` when
530
- * absent or carrying nothing usable.
900
+ * `hostInputs`, `volatile`, and the `globals`/`configs` graph sections), or
901
+ * `undefined` when absent or carrying nothing usable.
531
902
  */
532
903
  function parseFileList(value: unknown): string[] | undefined {
533
904
  if (!Array.isArray(value)) {
@@ -635,10 +635,17 @@ export const FLAG_SCHEMA: readonly FlagSpec[] = [
635
635
  },
636
636
 
637
637
  // -------------------------------------------------------------------------
638
- // `--tsgo-args` — JSON-encoded passthrough envelope the launcher emits on
639
- // the way down to native sidecars. The sidecars decode it back into the
640
- // tsgo argv. Listed here so the schema describes every flag the Go layers
641
- // accept, not just the user-facing ones.
638
+ // `--tsgo-args` — JSON-encoded passthrough envelope a native sidecar decodes
639
+ // back into the tsgo argv. Listed here so the schema describes every flag
640
+ // the Go layers accept, not just the user-facing ones.
641
+ //
642
+ // The launcher no longer emits it. A CLI flag is fatal to any host whose
643
+ // `flag.FlagSet` does not declare it, and this one was added to a plugin
644
+ // protocol third-party hosts had already frozen, so every forwarded compiler
645
+ // flag exited 2 on a typia/nestia-shaped sidecar (issue #1188). The payload
646
+ // now rides the `TTSC_TSGO_ARGS` environment variable, which an unaware host
647
+ // simply ignores. ttsc's own hosts still accept the flag so an older
648
+ // launcher, or an embedder that composes sidecar argv itself, keeps working.
642
649
  // -------------------------------------------------------------------------
643
650
  {
644
651
  name: "--tsgo-args",
@@ -654,7 +661,7 @@ export const FLAG_SCHEMA: readonly FlagSpec[] = [
654
661
  ],
655
662
  consumedBy: ["host", "lint"],
656
663
  description:
657
- "JSON-encoded tsgo passthrough argv (internal: emitted by runBuild).",
664
+ "JSON-encoded tsgo passthrough argv (internal: accepted by ttsc's own hosts; the launcher forwards it in TTSC_TSGO_ARGS).",
658
665
  },
659
666
  {
660
667
  name: "--plugins-json",