ttsc 0.31.0 → 0.31.2

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 (155) hide show
  1. package/cmd/platform/main.go +2 -2
  2. package/internal/sourceprocess/contain_windows.go +10 -13
  3. package/internal/sourceprocess/started_windows.go +41 -0
  4. package/internal/sourceprocess/windows_started_publication_distinguishes_pending_from_refusal_test.go +76 -0
  5. package/internal/watchhelper/run.go +30 -20
  6. package/internal/watchhelper/run_other.go +4 -4
  7. package/internal/watchhelper/run_windows.go +471 -0
  8. package/internal/watchhelper/watchhelper.go +8 -2
  9. package/internal/watchhelper/windows_notifications_preserve_mutations_without_access_noise_test.go +506 -0
  10. package/lib/compiler/internal/build/BuildExecution.d.ts +4 -2
  11. package/lib/compiler/internal/build/BuildExecution.js +40 -12
  12. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  13. package/lib/compiler/internal/startResidentTransform.js +5 -4
  14. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  15. package/lib/compiler/internal/transformProjectInMemory.js +9 -7
  16. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  17. package/lib/internal/javascriptRuntimeCapabilities.d.ts +13 -4
  18. package/lib/internal/javascriptRuntimeCapabilities.js +56 -6
  19. package/lib/internal/javascriptRuntimeCapabilities.js.map +1 -1
  20. package/lib/internal/nativeBinary.d.ts +3 -3
  21. package/lib/internal/nativeBinary.js +3 -3
  22. package/lib/internal/pluginSource.d.ts +1 -0
  23. package/lib/internal/pluginSource.js +1 -0
  24. package/lib/internal/pluginSource.js.map +1 -1
  25. package/lib/internal/resolveNodeBinary.d.ts +6 -1
  26. package/lib/internal/resolveNodeBinary.js +6 -2
  27. package/lib/internal/resolveNodeBinary.js.map +1 -1
  28. package/lib/internal/runtimeExecutableIdentity.d.ts +22 -13
  29. package/lib/internal/runtimeExecutableIdentity.js +192 -102
  30. package/lib/internal/runtimeExecutableIdentity.js.map +1 -1
  31. package/lib/launcher/internal/runtime/installRuntimeHooks.js +58 -4
  32. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  33. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.d.ts +5 -1
  34. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js +7 -2
  35. package/lib/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.js.map +1 -1
  36. package/lib/launcher/internal/ttscserver/runTtscserver.js +8 -1
  37. package/lib/launcher/internal/ttscserver/runTtscserver.js.map +1 -1
  38. package/lib/plugin/internal/CapabilityResolutionFormat.d.ts +21 -62
  39. package/lib/plugin/internal/CapabilityResolutionFormat.js +15 -106
  40. package/lib/plugin/internal/CapabilityResolutionFormat.js.map +1 -1
  41. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +6 -0
  42. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +4 -4
  43. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  44. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +24 -14
  45. package/lib/plugin/internal/load/loadProjectPlugins.js +141 -56
  46. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  47. package/lib/plugin/internal/readCapabilityResolution.d.ts +6 -0
  48. package/lib/plugin/internal/readCapabilityResolution.js +8 -5
  49. package/lib/plugin/internal/readCapabilityResolution.js.map +1 -1
  50. package/lib/plugin/internal/source/GoEnvironmentReading.d.ts +48 -0
  51. package/lib/plugin/internal/source/GoEnvironmentReading.js +156 -0
  52. package/lib/plugin/internal/source/GoEnvironmentReading.js.map +1 -0
  53. package/lib/plugin/internal/source/NativeSourcePackages.d.ts +51 -13
  54. package/lib/plugin/internal/source/NativeSourcePackages.js +182 -27
  55. package/lib/plugin/internal/source/NativeSourcePackages.js.map +1 -1
  56. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +42 -3
  57. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +68 -7
  58. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  59. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.d.ts +5 -3
  60. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js +14 -8
  61. package/lib/plugin/internal/source/PluginBuildEnvironmentWitness.js.map +1 -1
  62. package/lib/plugin/internal/source/PluginContentIdentities.d.ts +265 -0
  63. package/lib/plugin/internal/source/PluginContentIdentities.js +452 -0
  64. package/lib/plugin/internal/source/PluginContentIdentities.js.map +1 -0
  65. package/lib/plugin/internal/source/PluginLoadAnswers.d.ts +63 -0
  66. package/lib/plugin/internal/source/PluginLoadAnswers.js +128 -0
  67. package/lib/plugin/internal/source/PluginLoadAnswers.js.map +1 -0
  68. package/lib/plugin/internal/source/SourceBuildCacheLayout.d.ts +10 -0
  69. package/lib/plugin/internal/source/SourceBuildCacheLayout.js +12 -0
  70. package/lib/plugin/internal/source/SourceBuildCacheLayout.js.map +1 -1
  71. package/lib/plugin/internal/source/SourcePluginWorkspace.d.ts +53 -19
  72. package/lib/plugin/internal/source/SourcePluginWorkspace.js +111 -22
  73. package/lib/plugin/internal/source/SourcePluginWorkspace.js.map +1 -1
  74. package/lib/plugin/internal/source/buildSourcePlugin.d.ts +34 -18
  75. package/lib/plugin/internal/source/buildSourcePlugin.js +52 -24
  76. package/lib/plugin/internal/source/buildSourcePlugin.js.map +1 -1
  77. package/lib/plugin/internal/source/computeCacheKey.d.ts +25 -7
  78. package/lib/plugin/internal/source/computeCacheKey.js +85 -34
  79. package/lib/plugin/internal/source/computeCacheKey.js.map +1 -1
  80. package/lib/plugin/internal/source/hashPluginBuildEnvironment.d.ts +7 -3
  81. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js +75 -102
  82. package/lib/plugin/internal/source/hashPluginBuildEnvironment.js.map +1 -1
  83. package/lib/plugin/internal/source/pluginBuildEnvironment.d.ts +8 -3
  84. package/lib/plugin/internal/source/pluginBuildEnvironment.js +8 -4
  85. package/lib/plugin/internal/source/pluginBuildEnvironment.js.map +1 -1
  86. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js +12 -2
  87. package/lib/plugin/internal/source/pluginBuildEnvironmentWorker.js.map +1 -1
  88. package/lib/plugin/internal/source/pluginBuildVersions.d.ts +2 -2
  89. package/lib/plugin/internal/source/pluginBuildVersions.js +4 -17
  90. package/lib/plugin/internal/source/pluginBuildVersions.js.map +1 -1
  91. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.d.ts +6 -5
  92. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js +10 -17
  93. package/lib/plugin/internal/source/pluginModuleReplaceDirectories.js.map +1 -1
  94. package/lib/plugin/internal/source/pluginSourceState.d.ts +6 -2
  95. package/lib/plugin/internal/source/pluginSourceState.js +20 -8
  96. package/lib/plugin/internal/source/pluginSourceState.js.map +1 -1
  97. package/lib/plugin/internal/source/pluginSourceStateHolds.d.ts +7 -2
  98. package/lib/plugin/internal/source/pluginSourceStateHolds.js +12 -7
  99. package/lib/plugin/internal/source/pluginSourceStateHolds.js.map +1 -1
  100. package/lib/plugin/internal/source/processPluginBuildEnvironment.d.ts +7 -2
  101. package/lib/plugin/internal/source/processPluginBuildEnvironment.js +8 -3
  102. package/lib/plugin/internal/source/processPluginBuildEnvironment.js.map +1 -1
  103. package/lib/plugin/internal/source/provenPluginSourceDigest.d.ts +28 -0
  104. package/lib/plugin/internal/source/provenPluginSourceDigest.js +41 -0
  105. package/lib/plugin/internal/source/provenPluginSourceDigest.js.map +1 -0
  106. package/lib/plugin/internal/source/pruneCacheFileRoot.d.ts +3 -2
  107. package/lib/plugin/internal/source/pruneCacheFileRoot.js +3 -2
  108. package/lib/plugin/internal/source/pruneCacheFileRoot.js.map +1 -1
  109. package/lib/plugin/internal/source/ttscPackageVersion.d.ts +26 -0
  110. package/lib/plugin/internal/source/ttscPackageVersion.js +47 -0
  111. package/lib/plugin/internal/source/ttscPackageVersion.js.map +1 -0
  112. package/lib/plugin/internal/writeCapabilityResolution.d.ts +6 -0
  113. package/lib/plugin/internal/writeCapabilityResolution.js +11 -7
  114. package/lib/plugin/internal/writeCapabilityResolution.js.map +1 -1
  115. package/lib/plugin/resolveCapabilityPlugins.js +67 -15
  116. package/lib/plugin/resolveCapabilityPlugins.js.map +1 -1
  117. package/package.json +8 -8
  118. package/src/compiler/internal/build/BuildExecution.ts +46 -13
  119. package/src/compiler/internal/startResidentTransform.ts +12 -3
  120. package/src/compiler/internal/transformProjectInMemory.ts +10 -2
  121. package/src/internal/javascriptRuntimeCapabilities.ts +71 -5
  122. package/src/internal/nativeBinary.ts +3 -3
  123. package/src/internal/pluginSource.ts +1 -0
  124. package/src/internal/resolveNodeBinary.ts +12 -1
  125. package/src/internal/runtimeExecutableIdentity.ts +227 -141
  126. package/src/launcher/internal/runtime/installRuntimeHooks.ts +68 -3
  127. package/src/launcher/internal/ttscserver/captureLSPPluginSelectionInputs.ts +11 -2
  128. package/src/launcher/internal/ttscserver/runTtscserver.ts +18 -1
  129. package/src/plugin/internal/CapabilityResolutionFormat.ts +23 -107
  130. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +14 -4
  131. package/src/plugin/internal/load/loadProjectPlugins.ts +421 -211
  132. package/src/plugin/internal/readCapabilityResolution.ts +17 -6
  133. package/src/plugin/internal/source/GoEnvironmentReading.ts +178 -0
  134. package/src/plugin/internal/source/NativeSourcePackages.ts +365 -48
  135. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +83 -5
  136. package/src/plugin/internal/source/PluginBuildEnvironmentWitness.ts +18 -8
  137. package/src/plugin/internal/source/PluginContentIdentities.ts +563 -0
  138. package/src/plugin/internal/source/PluginLoadAnswers.ts +151 -0
  139. package/src/plugin/internal/source/SourceBuildCacheLayout.ts +14 -0
  140. package/src/plugin/internal/source/SourcePluginWorkspace.ts +148 -25
  141. package/src/plugin/internal/source/buildSourcePlugin.ts +104 -36
  142. package/src/plugin/internal/source/computeCacheKey.ts +129 -55
  143. package/src/plugin/internal/source/hashPluginBuildEnvironment.ts +152 -105
  144. package/src/plugin/internal/source/pluginBuildEnvironment.ts +9 -2
  145. package/src/plugin/internal/source/pluginBuildEnvironmentWorker.ts +18 -2
  146. package/src/plugin/internal/source/pluginBuildVersions.ts +5 -26
  147. package/src/plugin/internal/source/pluginModuleReplaceDirectories.ts +13 -25
  148. package/src/plugin/internal/source/pluginSourceState.ts +28 -12
  149. package/src/plugin/internal/source/pluginSourceStateHolds.ts +15 -8
  150. package/src/plugin/internal/source/processPluginBuildEnvironment.ts +8 -2
  151. package/src/plugin/internal/source/provenPluginSourceDigest.ts +45 -0
  152. package/src/plugin/internal/source/pruneCacheFileRoot.ts +3 -2
  153. package/src/plugin/internal/source/ttscPackageVersion.ts +45 -0
  154. package/src/plugin/internal/writeCapabilityResolution.ts +29 -8
  155. package/src/plugin/resolveCapabilityPlugins.ts +87 -13
@@ -5,6 +5,7 @@ import { createCanonicalTempDirectory } from "../../../internal/createCanonicalT
5
5
  import { resolveNodeBinary } from "../../../internal/resolveNodeBinary";
6
6
  import { hasProjectPluginEntries } from "../../../plugin/internal/load/hasProjectPluginEntries";
7
7
  import { loadProjectPlugins } from "../../../plugin/internal/load/loadProjectPlugins";
8
+ import { PluginContentIdentities } from "../../../plugin/internal/source/PluginContentIdentities";
8
9
  import type { ITtscLoadedNativePlugin } from "../../../structures/internal/ITtscLoadedNativePlugin";
9
10
  import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITtscParsedProjectConfig";
10
11
  import type { ITtscProjectInputSnapshot } from "../../../structures/internal/ITtscProjectInputSnapshot";
@@ -70,19 +71,37 @@ export namespace BuildExecution {
70
71
  * Merge extra environment variables over `process.env`, always injecting
71
72
  * `TTSC_NODE_BINARY` selected by the runtime capability owner. Explicit
72
73
  * compatible overrides can select a different Node executable; this is not
73
- * necessarily the current process binary.
74
+ * necessarily the current process binary. An invocation that loaded plugins
75
+ * passes its record store, so the runtime is proven from metadata instead of
76
+ * streamed again before every native spawn (#1723).
74
77
  */
75
78
  function mergeEnv(
76
- extra?: NodeJS.ProcessEnv,
77
- cwd: string = process.cwd(),
79
+ extra: NodeJS.ProcessEnv | undefined,
80
+ cwd: string,
81
+ identities: PluginContentIdentities.Store | undefined,
78
82
  ...later: readonly (NodeJS.ProcessEnv | undefined)[]
79
83
  ): NodeJS.ProcessEnv {
80
84
  const env = SidecarEnvironment.merge(process.env, extra, ...later);
81
- const node = resolveNodeBinary(env, cwd);
85
+ const node = resolveNodeBinary(env, cwd, identities);
82
86
  SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
83
87
  return env;
84
88
  }
85
89
 
90
+ /**
91
+ * Merge extra environment variables over `process.env` for a spawn of the
92
+ * selected TypeScript-Go compiler itself.
93
+ *
94
+ * WARNING (#1726): the compiler never reads `TTSC_NODE_BINARY`, which only
95
+ * plugin sidecars consume (`mergeEnv`). Selecting it streamed the 85 MB Node
96
+ * executable twice and probed it on every launch, plugins or not, about 0.2 s
97
+ * of a 0.5 s build without plugins.
98
+ */
99
+ function compilerEnv(
100
+ extra: NodeJS.ProcessEnv | undefined,
101
+ ): NodeJS.ProcessEnv {
102
+ return SidecarEnvironment.merge(process.env, extra);
103
+ }
104
+
86
105
  /**
87
106
  * Build the environment for a native plugin spawn. Injects `TTSC_TSGO_BINARY`
88
107
  * and `TTSC_TTSX_BINARY` alongside the base env from `mergeEnv`, plus
@@ -123,6 +142,7 @@ export namespace BuildExecution {
123
142
  path.join(__dirname, "..", "..", "..", "launcher", "ttsx.js"),
124
143
  },
125
144
  execution.projectRoot,
145
+ execution.identities,
126
146
  extra,
127
147
  // The invocation's resolved compiler owns the final environment layer.
128
148
  { TTSC_TSGO_BINARY: execution.tsgo.binary },
@@ -453,7 +473,7 @@ export namespace BuildExecution {
453
473
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The caught resolution failure is legitimate only for commands not requiring a project; ordinary compilation failures do not enter this bypass.
454
474
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain project-independent commands, existing-project behavior and null applicability; the catch comment states its deliberate broad premise.
455
475
  * @evidence contracts/portability.md#os-neutral-implementation Invocation cwd is resolved natively and spawnNative receives executable/argv separately with platform-aware environment merging.
456
- * @evidence contracts/performance.md#efficient-algorithms Flag selection scans forwarded argument/name text before config IO. An applicable lane includes native cwd/config resolution, binary and runtime capability lookup, argv/env composition, complete captured output and diagnostic normalization; one requested command does not bound its native duration or output bytes.
476
+ * @evidence contracts/performance.md#efficient-algorithms Flag selection scans forwarded argument/name text before config IO. An applicable lane includes native cwd/config resolution, binary lookup, argv/env composition without runtime capability work (#1726), complete captured output and diagnostic normalization; one requested command does not bound its native duration or output bytes.
457
477
  *
458
478
  * @evidenceExclude contracts/performance.md#reuse-equivalent-work A project probe and effectful terminal invocation establish no retained or shared cross-request result.
459
479
  *
@@ -482,7 +502,7 @@ export namespace BuildExecution {
482
502
  const tsgo = resolveTsgo({ ...options, cwd });
483
503
  const res = spawnNative(tsgo.binary, [...(options.passthrough ?? [])], {
484
504
  cwd,
485
- env: mergeEnv(options.env, cwd),
505
+ env: compilerEnv(options.env),
486
506
  encoding: "utf8",
487
507
  });
488
508
  if (res.error) {
@@ -796,7 +816,7 @@ export namespace BuildExecution {
796
816
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Supported compiler options express the check contract without intercepting writes or replacing compiler APIs; spawn errors remain errors.
797
817
  * @evidence contracts/common.md#meaningful-documentation Native prose explains the no-emit use and load-bearing argument precedence.
798
818
  * @evidence contracts/portability.md#os-neutral-implementation spawnNative uses the selected executable, separate argv and project cwd; environment composition handles native variable aliases and output text decoding is explicit UTF-8.
799
- * @evidence contracts/performance.md#efficient-algorithms One argv composition includes delegated option-presence classification, threading/isolation text and argument copies. Environment composition can perform native runtime capability probes before the compiler launch; complete capture/decoding and diagnostic normalization follow. Work/storage depend on argument/env/path/report bytes and native child work, without an asserted measured cost ranking.
819
+ * @evidence contracts/performance.md#efficient-algorithms One argv composition includes delegated option-presence classification, threading/isolation text and argument copies. Environment composition only layers variables; the compiler reads no Node selection, so no runtime capability probe precedes the launch (#1726). Complete capture/decoding and diagnostic normalization follow. Work/storage depend on argument/env/path/report bytes and native child work, without an asserted measured cost ranking.
800
820
  *
801
821
  * @evidenceExclude contracts/performance.md#reuse-equivalent-work This executes the requested compiler pass; equivalence with other phases must be established by orchestration rather than caching a result here.
802
822
  *
@@ -822,7 +842,7 @@ export namespace BuildExecution {
822
842
  ],
823
843
  {
824
844
  cwd: execution.compilerSelection.compilerArgsCwd,
825
- env: mergeEnv(options.env, execution.projectRoot),
845
+ env: compilerEnv(options.env),
826
846
  encoding: "utf8",
827
847
  },
828
848
  );
@@ -856,7 +876,7 @@ export namespace BuildExecution {
856
876
  options: RunBuildOptions,
857
877
  args: readonly string[],
858
878
  ): TtscBuildResult {
859
- const env = mergeEnv(options.env, execution.projectRoot);
879
+ const env = compilerEnv(options.env);
860
880
  const userOptions = readEffectiveCompilerOptions(
861
881
  execution.project,
862
882
  options.passthrough,
@@ -1183,19 +1203,31 @@ export namespace BuildExecution {
1183
1203
  const tsgo = resolveTsgo({ ...options, cwd: projectRoot });
1184
1204
  let pluginSetupFailure: TtscBuildResult | undefined;
1185
1205
  let nativePlugins: ITtscLoadedNativePlugin[] = [];
1206
+ // Opened only for an invocation that loads plugins, so a project without
1207
+ // any never gains a cache directory; the load and every later native spawn
1208
+ // of this invocation prove their unchanged inputs through it (#1722, #1723).
1209
+ let identities: PluginContentIdentities.Store | undefined;
1186
1210
  try {
1187
1211
  if (
1188
1212
  compilerSelection.inspectionError === undefined &&
1189
1213
  hasProjectPluginEntries(project, options.plugins)
1190
1214
  ) {
1215
+ const cacheDir =
1216
+ options.cacheDir ??
1217
+ SidecarEnvironment.read(options.env, "TTSC_CACHE_DIR");
1218
+ const env = inheritedSidecarEnv(options.env, options.binary);
1219
+ identities = PluginContentIdentities.open({
1220
+ projectRoot,
1221
+ cacheDir,
1222
+ env,
1223
+ });
1191
1224
  nativePlugins = loadProjectPlugins({
1192
1225
  binary: resolveBinary(options) ?? "",
1193
- cacheDir:
1194
- options.cacheDir ??
1195
- SidecarEnvironment.read(options.env, "TTSC_CACHE_DIR"),
1226
+ cacheDir,
1196
1227
  cwd,
1197
1228
  entries: options.plugins,
1198
- env: inheritedSidecarEnv(options.env, options.binary),
1229
+ env,
1230
+ identities,
1199
1231
  onWatchInputs: options.onWatchInputs,
1200
1232
  pluginConfigDir: options.pluginConfigDir,
1201
1233
  projectRoot,
@@ -1215,6 +1247,7 @@ export namespace BuildExecution {
1215
1247
  return {
1216
1248
  compilerSelection,
1217
1249
  cwd,
1250
+ ...(identities === undefined ? {} : { identities }),
1218
1251
  nativePlugins,
1219
1252
  pluginConfigDir: resolvePluginConfigDir({
1220
1253
  cwd,
@@ -2,6 +2,7 @@ import path from "node:path";
2
2
 
3
3
  import { resolveNodeBinary } from "../../internal/resolveNodeBinary";
4
4
  import { loadProjectPlugins } from "../../plugin/internal/load/loadProjectPlugins";
5
+ import type { PluginContentIdentities } from "../../plugin/internal/source/PluginContentIdentities";
5
6
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
6
7
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
7
8
  import { ResidentTransformProcess } from "./ResidentTransformProcess";
@@ -94,7 +95,13 @@ export function startResidentTransform(
94
95
  ],
95
96
  binary: host.binary,
96
97
  cwd: project.root,
97
- env: residentEnv(context, project.root, tsgoBinary, loaded.nativePlugins),
98
+ env: residentEnv(
99
+ context,
100
+ project.root,
101
+ tsgoBinary,
102
+ loaded.nativePlugins,
103
+ loaded.identities,
104
+ ),
98
105
  });
99
106
  return { process: resident, projectRoot: project.root };
100
107
  }
@@ -106,13 +113,15 @@ export function startResidentTransform(
106
113
  * plugin config anchor (an embedder compiling through a generated wrapper
107
114
  * tsconfig) so config-file discovery walks the real project instead of the
108
115
  * wrapper's temp-dir ancestry, and forwards linked transform plugins via
109
- * `TTSC_LINKED_PLUGINS_JSON`.
116
+ * `TTSC_LINKED_PLUGINS_JSON`. The load's record store proves the Node runtime
117
+ * from metadata instead of streaming it again (#1723).
110
118
  */
111
119
  function residentEnv(
112
120
  context: ITtscCompilerContext,
113
121
  projectRoot: string,
114
122
  tsgoBinary: string,
115
123
  nativePlugins: readonly ITtscLoadedNativePlugin[],
124
+ identities: PluginContentIdentities.Store | undefined,
116
125
  ): NodeJS.ProcessEnv {
117
126
  const pluginConfigDir = resolvePluginConfigDir(context);
118
127
  const env = SidecarEnvironment.merge(
@@ -128,7 +137,7 @@ function residentEnv(
128
137
  context.env,
129
138
  { TTSC_TSGO_BINARY: tsgoBinary },
130
139
  );
131
- const node = resolveNodeBinary(env, projectRoot);
140
+ const node = resolveNodeBinary(env, projectRoot, identities);
132
141
  SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
133
142
  // The anchor is per-invocation state owned by this host: when this run
134
143
  // declared none (and the caller's env does not name one), drop any value
@@ -5,6 +5,7 @@ import { collectProjectHostInputs } from "../../plugin/internal/load/collectProj
5
5
  import { hashHostInputPaths } from "../../plugin/internal/load/hashHostInputPaths";
6
6
  import { loadProjectPlugins } from "../../plugin/internal/load/loadProjectPlugins";
7
7
  import { realpathHostInputPaths } from "../../plugin/internal/load/realpathHostInputPaths";
8
+ import type { PluginContentIdentities } from "../../plugin/internal/source/PluginContentIdentities";
8
9
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
9
10
  import type { ITtscCompilerTransformation } from "../../structures/ITtscCompilerTransformation";
10
11
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
@@ -301,6 +302,7 @@ function transformProjectWithPlugins(
301
302
  tsgoBinary,
302
303
  loaded.nativePlugins,
303
304
  checks,
305
+ loaded.identities,
304
306
  );
305
307
  const checkedHostInputs = mergeHostInputs(
306
308
  loaded.hostInputs,
@@ -414,6 +416,7 @@ function transformProjectWithPlugins(
414
416
  tsgoBinary,
415
417
  loaded.nativePlugins,
416
418
  plugin,
419
+ loaded.identities,
417
420
  ),
418
421
  },
419
422
  );
@@ -688,6 +691,7 @@ function runNativeChecks(
688
691
  tsgoBinary: string,
689
692
  nativePlugins: readonly ITtscLoadedNativePlugin[],
690
693
  checks: readonly ITtscLoadedNativePlugin[],
694
+ identities: PluginContentIdentities.Store | undefined,
691
695
  ): TtscBuildResult {
692
696
  let result: TtscBuildResult = {
693
697
  diagnostics: [],
@@ -716,6 +720,7 @@ function runNativeChecks(
716
720
  tsgoBinary,
717
721
  nativePlugins,
718
722
  plugin,
723
+ identities,
719
724
  ),
720
725
  },
721
726
  );
@@ -804,7 +809,9 @@ function serializeNativePlugins(
804
809
  * the caller declared a plugin config anchor (an embedder compiling through a
805
810
  * generated wrapper tsconfig) so config-file discovery walks the real project
806
811
  * instead of the wrapper's temp-dir ancestry. For transform plugins, also
807
- * passes `TTSC_LINKED_PLUGINS_JSON` when linked sources are present.
812
+ * passes `TTSC_LINKED_PLUGINS_JSON` when linked sources are present. The load's
813
+ * record store proves the Node runtime from metadata instead of streaming it
814
+ * before every spawn of every generation (#1723).
808
815
  */
809
816
  function nativePluginEnv(
810
817
  options: ITtscCompilerContext,
@@ -812,6 +819,7 @@ function nativePluginEnv(
812
819
  tsgoBinary: string,
813
820
  nativePlugins?: readonly ITtscLoadedNativePlugin[],
814
821
  plugin?: ITtscLoadedNativePlugin,
822
+ identities?: PluginContentIdentities.Store,
815
823
  ): NodeJS.ProcessEnv {
816
824
  const pluginConfigDir = resolvePluginConfigDir(options);
817
825
  const env = SidecarEnvironment.merge(
@@ -827,7 +835,7 @@ function nativePluginEnv(
827
835
  options.env,
828
836
  { TTSC_TSGO_BINARY: tsgoBinary },
829
837
  );
830
- const node = resolveNodeBinary(env, projectRoot);
838
+ const node = resolveNodeBinary(env, projectRoot, identities);
831
839
  SidecarEnvironment.write(env, "TTSC_NODE_BINARY", node);
832
840
  // The anchor is per-invocation state owned by this host: when this run
833
841
  // declared none (and the caller's env does not name one), drop any value
@@ -4,6 +4,8 @@ import path from "node:path";
4
4
 
5
5
  import { captureProcessOutput } from "../compiler/internal/captureProcessOutput";
6
6
  import { SidecarEnvironment } from "../compiler/internal/sharedHost/SidecarEnvironment";
7
+ import type { PluginContentIdentities } from "../plugin/internal/source/PluginContentIdentities";
8
+ import { PluginLoadAnswers } from "../plugin/internal/source/PluginLoadAnswers";
7
9
  import { E2ETrace } from "./E2ETrace";
8
10
  import type { IJavaScriptRuntimeCapabilities } from "./IJavaScriptRuntimeCapabilities";
9
11
  import { OwnedSynchronousProcess } from "./OwnedSynchronousProcess";
@@ -20,28 +22,56 @@ import { spawnSyncWithLowDescriptors } from "./spawnSyncWithLowDescriptors";
20
22
  * Cache-ineligible candidates do not acquire executable proofs used only by
21
23
  * cache lookup and publication.
22
24
  *
23
- * @evidence contracts/common.md#principled-implementation A child reports its own feature availability and executable; byte-aware before/after identity and same-executable proof authorize reuse only for a stable absolute runtime without NODE_OPTIONS preload authority.
25
+ * With a record store, the executable identity is proven from metadata and a
26
+ * probe answer recorded for that identity is reused by a later process (#1723);
27
+ * a recorded answer must still name an executable this runtime is.
28
+ *
29
+ * @param runtime The interpreter to probe.
30
+ * @param env Caller environment merged over this process's.
31
+ * @param cwd Working directory of the probe.
32
+ * @param identities Optional record store of the caller's plugin cache.
33
+ * @evidence contracts/common.md#principled-implementation A child reports its own feature availability and executable; byte-aware before/after identity and same-executable proof authorize reuse only for a stable absolute runtime without NODE_OPTIONS preload authority, in process or through an answer recorded under that identity.
24
34
  * @evidence contracts/common.md#clear-and-simple-design One probe owner coordinates identity, spawning and parsing; the shared fingerprint helper owns file-content proof and the low-descriptor helper owns the POSIX launch distinction.
25
35
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Feature availability comes from the actual interpreter rather than its name; descriptor fallback handles a supported kernel constraint and failures produce unsupported capabilities, not fabricated success.
26
36
  * @evidence contracts/common.md#meaningful-documentation Native paragraphs explain measured capabilities, reuse restrictions and freshness, with comments separating platform launch and cache permission from tags.
27
37
  * @evidence contracts/portability.md#os-neutral-implementation Shared environment merge and lookup apply native name identity to caller precedence and NODE_OPTIONS preload authority, including Windows aliases; Node spawn receives an executable and argv without shell syntax, and only POSIX descriptor exhaustion uses the isolated broker.
28
- * @evidence contracts/performance.md#efficient-algorithms Environment merge/name handling first decides cache eligibility. Eligible hits read B executable bytes; eligible misses acquire before/after content proofs around the actual probe. Ineligible calls retain the fresh child without reading executable bytes solely for the forbidden cache. Probe spawning/parsing, a possible descriptor-exhaustion retry and capture-file reads add text/environment/native lookup costs and transient output storage; this adapter sets no explicit byte quota.
29
- * @evidence contracts/performance.md#reuse-equivalent-work Stable absolute candidates that report their own executable share measured capability results, including false feature flags; changed executable bytes, link/target identity, preload options or wrapper identity require another probe, while failed probes without executable identity are not reused.
38
+ * @evidence contracts/performance.md#efficient-algorithms Environment merge/name handling first decides cache eligibility. Eligible hits prove the executable from a record (three stats and one small read) or read B executable bytes; eligible misses acquire before/after identities around the actual probe. Ineligible calls retain the fresh child without reading executable bytes solely for the forbidden cache. Probe spawning/parsing, a possible descriptor-exhaustion retry and capture-file reads add text/environment/native lookup costs and transient output storage; this adapter sets no explicit byte quota.
39
+ * @evidence contracts/performance.md#reuse-equivalent-work Stable absolute candidates that report their own executable share measured capability results, including false feature flags, within the process and across processes through the record store keyed by the full identity; changed executable bytes, link/target identity, preload options or wrapper identity require another probe, while failed probes without executable identity are not reused.
30
40
  * @evidence contracts/performance.md#bound-retention-and-release-resources The synchronous probe owns child completion and fallback captures; finally attempts capture close/removal, whose helper suppresses cleanup failures, so native/file release is unconfirmed on failure. Capability entries retain absolute spellings and path/identity text with no eviction quota; failed identity validation can remove an entry. Returned capability copies transfer to callers.
31
41
  */
32
42
  export function javascriptRuntimeCapabilities(
33
43
  runtime: string,
34
44
  env: NodeJS.ProcessEnv,
35
45
  cwd: string,
46
+ identities?: PluginContentIdentities.Store,
36
47
  ): IJavaScriptRuntimeCapabilities {
37
48
  const effectiveEnv = SidecarEnvironment.merge(process.env, env);
38
49
  const cacheKey = runtimeCapabilityCacheKey(runtime, effectiveEnv);
39
50
  const beforeIdentity =
40
- cacheKey === undefined ? undefined : runtimeExecutableIdentity(runtime);
51
+ cacheKey === undefined
52
+ ? undefined
53
+ : runtimeExecutableIdentity(runtime, identities);
41
54
  if (cacheKey !== undefined && beforeIdentity !== undefined) {
42
55
  const cached = runtimeCapabilityCache.get(cacheKey);
43
56
  if (cached?.identity === beforeIdentity) return { ...cached.capabilities };
44
57
  runtimeCapabilityCache.delete(cacheKey);
58
+ // WARNING (#1723): a launch is a new process, so the memo above is empty
59
+ // on every CLI run. The same proven executable answers the same probe; the
60
+ // record store carries that answer across processes.
61
+ const recorded = recordedCapabilities(
62
+ PluginLoadAnswers.read(identities, RUNTIME_CAPABILITIES, [
63
+ cacheKey,
64
+ beforeIdentity,
65
+ ]),
66
+ runtime,
67
+ );
68
+ if (recorded !== undefined) {
69
+ runtimeCapabilityCache.set(cacheKey, {
70
+ capabilities: { ...recorded },
71
+ identity: beforeIdentity,
72
+ });
73
+ return recorded;
74
+ }
45
75
  }
46
76
  const args = [
47
77
  "-e",
@@ -113,7 +143,9 @@ export function javascriptRuntimeCapabilities(
113
143
  // This removes a process spawn from the common path without
114
144
  // authorizing a replaced or redirected runtime in a long-lived host.
115
145
  const afterIdentity =
116
- cacheKey === undefined ? undefined : runtimeExecutableIdentity(runtime);
146
+ cacheKey === undefined
147
+ ? undefined
148
+ : runtimeExecutableIdentity(runtime, identities);
117
149
  if (
118
150
  cacheKey !== undefined &&
119
151
  capabilities.executable !== undefined &&
@@ -125,10 +157,44 @@ export function javascriptRuntimeCapabilities(
125
157
  capabilities: { ...capabilities },
126
158
  identity: afterIdentity,
127
159
  });
160
+ PluginLoadAnswers.write(
161
+ identities,
162
+ RUNTIME_CAPABILITIES,
163
+ [cacheKey, afterIdentity],
164
+ capabilities,
165
+ );
128
166
  }
129
167
  return capabilities;
130
168
  }
131
169
 
170
+ /** Answer family of measured runtime capabilities (`PluginLoadAnswers`). */
171
+ const RUNTIME_CAPABILITIES = "javascript-runtime-capabilities";
172
+
173
+ /**
174
+ * A recorded probe answer in the shape the probe produces, still naming an
175
+ * executable this runtime is, or `undefined`.
176
+ */
177
+ function recordedCapabilities(
178
+ value: unknown,
179
+ runtime: string,
180
+ ): IJavaScriptRuntimeCapabilities | undefined {
181
+ if (typeof value !== "object" || value === null) return undefined;
182
+ const recorded = value as Partial<IJavaScriptRuntimeCapabilities>;
183
+ if (
184
+ typeof recorded.bun !== "boolean" ||
185
+ typeof recorded.registerHooks !== "boolean" ||
186
+ typeof recorded.executable !== "string" ||
187
+ !path.isAbsolute(recorded.executable) ||
188
+ !sameRuntimeExecutable(runtime, recorded.executable)
189
+ )
190
+ return undefined;
191
+ return {
192
+ bun: recorded.bun,
193
+ executable: recorded.executable,
194
+ registerHooks: recorded.registerHooks,
195
+ };
196
+ }
197
+
132
198
  type RuntimeCapabilityCacheEntry = {
133
199
  capabilities: IJavaScriptRuntimeCapabilities;
134
200
  identity: string;
@@ -4,8 +4,8 @@
4
4
  * The rules that find ttsc's platform binary (an absolute `TTSC_BINARY`, the
5
5
  * per-platform package, then the repository's own dev layout) are ttsc's alone.
6
6
  * A tool host that runs one of that binary's helper commands, as
7
- * `@ttsc/unplugin` runs its Linux watch helper, imports them from here, so the
8
- * helper always comes from the binary ttsc itself uses and no consumer carries
9
- * a copy of the rules.
7
+ * `@ttsc/unplugin` runs its native watch helpers, imports them from here, so
8
+ * the helper always comes from the binary ttsc itself uses and no consumer
9
+ * carries a copy of the rules.
10
10
  */
11
11
  export { resolveBinary } from "../compiler/internal/resolveBinary";
@@ -22,5 +22,6 @@ export * from "../plugin/internal/source/pluginSourceDigest";
22
22
  export * from "../plugin/internal/source/pluginSourceState";
23
23
  export * from "../plugin/internal/source/pluginSourceStateHolds";
24
24
  export * from "../plugin/internal/source/processPluginBuildEnvironment";
25
+ export * from "../plugin/internal/source/provenPluginSourceDigest";
25
26
  export * from "../plugin/internal/source/PluginBuildEnvironmentReadings";
26
27
  export * from "../plugin/internal/source/prunesPluginSourceDirectory";
@@ -1,4 +1,5 @@
1
1
  import { SidecarEnvironment } from "../compiler/internal/sharedHost/SidecarEnvironment";
2
+ import type { PluginContentIdentities } from "../plugin/internal/source/PluginContentIdentities";
2
3
  import { javascriptRuntimeCapabilities } from "./javascriptRuntimeCapabilities";
3
4
 
4
5
  /**
@@ -7,6 +8,10 @@ import { javascriptRuntimeCapabilities } from "./javascriptRuntimeCapabilities";
7
8
  * Bun can directly evaluate a descriptor, but it does not implement the
8
9
  * synchronous `module.registerHooks` contract used by those loaders.
9
10
  *
11
+ * @param env Effective environment naming `TTSC_NODE_BINARY`.
12
+ * @param cwd Working directory of the probes.
13
+ * @param identities Optional record store that lets probes of an unchanged
14
+ * runtime be answered across processes (#1723).
10
15
  * @evidence contracts/common.md#principled-implementation Ordered candidates are accepted only after an actual runtime probe reports non-Bun registerHooks support and its absolute executable; names alone do not establish the loader capability.
11
16
  * @evidence contracts/common.md#clear-and-simple-design Candidate precedence and duplicate suppression remain here, while the shared probe owns launch, capability parsing and freshness policy.
12
17
  * @evidence contracts/common.md#prohibited-implementation-shortcuts The node spelling is a supported final discovery candidate, not an assumed successful runtime; incompatible candidates are skipped without patching modules or globals.
@@ -19,6 +24,7 @@ import { javascriptRuntimeCapabilities } from "./javascriptRuntimeCapabilities";
19
24
  export function resolveNodeBinary(
20
25
  env: NodeJS.ProcessEnv = process.env,
21
26
  cwd: string = process.cwd(),
27
+ identities?: PluginContentIdentities.Store,
22
28
  ): string | undefined {
23
29
  const candidates = [
24
30
  SidecarEnvironment.read(env, "TTSC_NODE_BINARY"),
@@ -31,7 +37,12 @@ export function resolveNodeBinary(
31
37
  if (candidate === undefined || candidate.trim() === "") continue;
32
38
  if (seen.has(candidate)) continue;
33
39
  seen.add(candidate);
34
- const capabilities = javascriptRuntimeCapabilities(candidate, env, cwd);
40
+ const capabilities = javascriptRuntimeCapabilities(
41
+ candidate,
42
+ env,
43
+ cwd,
44
+ identities,
45
+ );
35
46
  if (
36
47
  !capabilities.bun &&
37
48
  capabilities.registerHooks &&