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
@@ -3,17 +3,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE = void 0;
6
+ exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE = exports.COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE = void 0;
7
7
  exports.loadProjectPlugins = loadProjectPlugins;
8
+ exports.hashHostInputPaths = hashHostInputPaths;
9
+ exports.realpathHostInput = realpathHostInput;
10
+ exports.realpathHostInputPaths = realpathHostInputPaths;
11
+ exports.collectProjectHostInputs = collectProjectHostInputs;
8
12
  exports.hasProjectPluginEntries = hasProjectPluginEntries;
9
13
  const node_child_process_1 = __importDefault(require("node:child_process"));
14
+ const node_crypto_1 = __importDefault(require("node:crypto"));
10
15
  const node_fs_1 = __importDefault(require("node:fs"));
11
16
  const node_module_1 = require("node:module");
12
- const node_os_1 = __importDefault(require("node:os"));
13
17
  const node_path_1 = __importDefault(require("node:path"));
18
+ const node_url_1 = require("node:url");
14
19
  const paths_1 = require("../../compiler/internal/paths");
15
20
  const readConfigJson_1 = require("../../compiler/internal/project/readConfigJson");
16
21
  const readProjectConfig_1 = require("../../compiler/internal/project/readProjectConfig");
22
+ const createCanonicalTempDirectory_1 = require("../../internal/createCanonicalTempDirectory");
23
+ const resolveNodeBinary_1 = require("../../internal/resolveNodeBinary");
17
24
  const buildSourcePlugin_1 = require("./buildSourcePlugin");
18
25
  const descriptorProcessFailure_1 = require("./descriptorProcessFailure");
19
26
  const GO_MOD_SEARCH_MAX_DEPTH = 3;
@@ -23,17 +30,18 @@ const GO_MOD_SEARCH_MAX_DEPTH = 3;
23
30
  * Reads the project config, discovers plugin entries (from tsconfig and package
24
31
  * auto-discovery), validates and composes their descriptors, then invokes
25
32
  * `buildSourcePlugin` to compile each Go source package into a cached binary.
26
- * Returns the ordered set of loaded native plugins alongside the parsed project
27
- * config.
33
+ * Returns the ordered native plugins, parsed project config, and exact
34
+ * JavaScript-host files that universally influence the loaded selection.
28
35
  *
29
36
  * @param options.binary - Absolute path to the ttsc native helper binary.
30
37
  * @param options.cacheDir - Override the plugin binary cache directory.
31
38
  * @param options.cwd - Working directory for resolving relative paths.
32
39
  * @param options.entries - Explicit plugin entries; `false` disables all
33
40
  * plugins (skips both tsconfig entries and package auto-discovery).
34
- * @param options.env - Effective environment for source-plugin builds and the
35
- * `ttsx` descriptor child (`{ ...process.env, ...context.env }`). Defaults to
36
- * `process.env` for CLI callers, so ambient behavior is unchanged.
41
+ * @param options.env - Effective environment for source-plugin builds and
42
+ * isolated descriptor evaluators, including the `ttsx` fallback (`{
43
+ * ...process.env, ...context.env }`). Defaults to `process.env` for CLI
44
+ * callers, so ambient behavior is unchanged.
37
45
  * @param options.file - Path to the tsconfig/jsconfig file.
38
46
  * @param options.pluginConfigDir - Caller-declared anchor for plugin
39
47
  * config-file discovery (see `ITtscPluginFactoryContext.pluginConfigDir`).
@@ -41,19 +49,29 @@ const GO_MOD_SEARCH_MAX_DEPTH = 3;
41
49
  * @param options.tsconfig - Alias for `file`.
42
50
  */
43
51
  function loadProjectPlugins(options) {
44
- const effectiveEnv = options.env ?? process.env;
45
- const project = (0, readProjectConfig_1.readProjectConfig)({
52
+ // Snapshot the caller environment before `withPluginLoaderEnv` injects
53
+ // host-owned Node/ttsx locators into process.env. Under a Bun parent the
54
+ // direct descriptor evaluator must remain Bun unless the caller explicitly
55
+ // selected another runtime.
56
+ const effectiveEnv = { ...(options.env ?? process.env) };
57
+ const projectSnapshot = readProjectHostInputSnapshot({
46
58
  cwd: options.cwd,
47
59
  file: options.file,
60
+ includePluginDiscovery: options.entries === undefined,
48
61
  projectRoot: options.projectRoot,
49
62
  tsconfig: options.tsconfig,
50
63
  });
64
+ const { project } = projectSnapshot;
65
+ const projectHostInputs = projectSnapshot.hostInputs;
66
+ const projectHostInputHashes = projectSnapshot.hostInputHashes;
67
+ const projectHostInputRealpaths = projectSnapshot.hostInputRealpaths;
51
68
  const entries = options.entries === false
52
69
  ? []
53
70
  : resolvePluginEntries(project, options.entries).filter((entry) => entry.config.enabled !== false);
54
71
  if (entries.length === 0) {
55
72
  options.onWatchInputs?.([]);
56
73
  return {
74
+ ...collectHostInputSnapshot(project, {}, [], projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
57
75
  nativePlugins: [],
58
76
  project,
59
77
  };
@@ -68,7 +86,38 @@ function loadProjectPlugins(options) {
68
86
  projectRoot: project.root,
69
87
  tsconfig: project.path,
70
88
  };
71
- const plugins = composePluginSources(entries, entries.map((entry) => loadPluginEntry(entry.config, { ...context, plugin: entry.config }, entry.baseDir, effectiveEnv)));
89
+ const loadedEntries = withPluginLoaderEnv(() => entries.map((entry) => {
90
+ const specifier = entry.config.transform;
91
+ if (typeof specifier !== "string" || specifier.length === 0) {
92
+ throw new Error(`ttsc: plugin entry is missing a string "transform" field`);
93
+ }
94
+ const entryCandidates = collectModuleResolutionCandidates(specifier, node_path_1.default.join(entry.baseDir, "package.json"), undefined);
95
+ // Capture every candidate before resolution chooses the descriptor entry.
96
+ // A post-resolution snapshot could bless a higher-priority file created
97
+ // after the resolver had already selected the old entry.
98
+ const entryCandidateHashes = hashHostInputPaths(entryCandidates);
99
+ const entryCandidateRealpaths = realpathHostInputPaths(entryCandidates);
100
+ const request = resolvePluginRequest(specifier, entry.baseDir);
101
+ const loaded = loadPluginEntry(entry.config, { ...context, plugin: entry.config }, request, effectiveEnv);
102
+ const loadedHostInputHashes = mergeObservedHostInputHashes(loaded.hostInputHashes, hashHostInputPaths(Object.keys(loaded.hostInputHashes)));
103
+ const loadedHostInputRealpaths = mergeObservedHostInputRealpaths(loaded.hostInputRealpaths, realpathHostInputPaths(Object.keys(loaded.hostInputRealpaths)));
104
+ const hostInputHashes = mergeObservedHostInputHashes(entryCandidateHashes, loadedHostInputHashes);
105
+ const hostInputRealpaths = mergeObservedHostInputRealpaths(entryCandidateRealpaths, loadedHostInputRealpaths);
106
+ for (const input of loaded.hostInputs) {
107
+ const absolute = node_path_1.default.resolve(input);
108
+ if (!Object.prototype.hasOwnProperty.call(loadedHostInputHashes, absolute)) {
109
+ delete hostInputHashes[absolute];
110
+ }
111
+ }
112
+ return {
113
+ ...loaded,
114
+ hostInputHashes,
115
+ hostInputRealpaths,
116
+ hostInputs: [...loaded.hostInputs, ...entryCandidates],
117
+ request,
118
+ };
119
+ }));
120
+ const plugins = composePluginSources(entries, loadedEntries.map((entry) => entry.plugin));
72
121
  const ttscVersion = readTtscVersion();
73
122
  const tsgoVersion = readTsgoVersion(context.projectRoot);
74
123
  const records = plugins.map((plugin, index) => {
@@ -83,6 +132,9 @@ function loadProjectPlugins(options) {
83
132
  const linkedContributorName = kind === "linked"
84
133
  ? `linked_${String(index).padStart(6, "0")}`
85
134
  : undefined;
135
+ const hostInputs = validatePluginHostInputs(plugin, index);
136
+ const pluginHostInputHashes = validatePluginHostInputHashes(plugin, index, hostInputs);
137
+ const pluginHostInputRealpaths = validatePluginHostInputRealpaths(plugin, index, hostInputs);
86
138
  return {
87
139
  capabilities: plugin.capabilities,
88
140
  contributors,
@@ -92,6 +144,10 @@ function loadProjectPlugins(options) {
92
144
  linkedContributorName,
93
145
  name: plugin.name,
94
146
  reportsTypeScriptDiagnostics: plugin.reportsTypeScriptDiagnostics === true,
147
+ request: loadedEntries[index].request,
148
+ hostInputHashes: mergePluginHostInputHashes(loadedEntries[index].hostInputHashes, pluginHostInputHashes, loadedEntries[index].hostInputs, hostInputs),
149
+ hostInputRealpaths: mergePluginHostInputHashes(loadedEntries[index].hostInputRealpaths, pluginHostInputRealpaths, loadedEntries[index].hostInputs, hostInputs),
150
+ hostInputs: [...loadedEntries[index].hostInputs, ...hostInputs],
95
151
  source,
96
152
  stage,
97
153
  };
@@ -167,10 +223,451 @@ function loadProjectPlugins(options) {
167
223
  };
168
224
  });
169
225
  return {
226
+ ...collectHostInputSnapshot(project, context, records, projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
170
227
  nativePlugins: orderNativePlugins(nativePlugins),
171
228
  project,
172
229
  };
173
230
  }
231
+ /**
232
+ * Collect exact JavaScript-host files that universally influence the loaded
233
+ * transform. Program files belong to the native reference graph; this list is
234
+ * deliberately limited to config ancestry, descriptor entries, the project
235
+ * manifest controlling auto-discovery, and explicit plugin config files.
236
+ */
237
+ function collectHostInputSnapshot(project, context, records, baselineInputs, baselineHashes, baselineRealpaths) {
238
+ const inputs = new Set(baselineInputs.map((file) => node_path_1.default.resolve(file)));
239
+ const configBase = context.pluginConfigDir ?? node_path_1.default.dirname(project.path);
240
+ for (const record of records) {
241
+ inputs.add(node_path_1.default.resolve(record.request));
242
+ for (const hostInput of record.hostInputs) {
243
+ inputs.add(node_path_1.default.resolve(hostInput));
244
+ }
245
+ const descriptorManifest = findNearestPackageJson(record.request);
246
+ if (descriptorManifest !== undefined) {
247
+ inputs.add(node_path_1.default.resolve(descriptorManifest));
248
+ }
249
+ const configFile = record.config.configFile;
250
+ if (typeof configFile === "string" && configFile.trim() !== "") {
251
+ inputs.add(node_path_1.default.isAbsolute(configFile)
252
+ ? node_path_1.default.resolve(configFile)
253
+ : node_path_1.default.resolve(configBase, configFile));
254
+ }
255
+ }
256
+ const hostInputs = [...inputs].sort();
257
+ const evaluationHashes = mergeObservedHostInputHashes(baselineHashes, ...records.map((record) => record.hostInputHashes));
258
+ const evaluationRealpaths = mergeObservedHostInputRealpaths(baselineRealpaths, ...records.map((record) => record.hostInputRealpaths));
259
+ for (const record of records) {
260
+ for (const input of record.hostInputs) {
261
+ const absolute = node_path_1.default.resolve(input);
262
+ if (!Object.prototype.hasOwnProperty.call(record.hostInputHashes, absolute)) {
263
+ delete evaluationHashes[absolute];
264
+ }
265
+ if (!Object.prototype.hasOwnProperty.call(record.hostInputRealpaths, absolute)) {
266
+ delete evaluationRealpaths[absolute];
267
+ }
268
+ }
269
+ }
270
+ const hostInputHashes = Object.fromEntries(hostInputs.flatMap((file) => Object.prototype.hasOwnProperty.call(evaluationHashes, file)
271
+ ? [[file, evaluationHashes[file]]]
272
+ : []));
273
+ const hostInputRealpaths = Object.fromEntries(hostInputs.flatMap((file) => Object.prototype.hasOwnProperty.call(evaluationRealpaths, file)
274
+ ? [[file, evaluationRealpaths[file]]]
275
+ : []));
276
+ return { hostInputHashes, hostInputRealpaths, hostInputs };
277
+ }
278
+ /**
279
+ * Read one project configuration while its complete discovery surface remains
280
+ * unchanged. The preliminary read discovers the input set; the accepted read is
281
+ * bracketed by equal fingerprints for that same set. A repeatedly changing
282
+ * project is still returned so compilation can make progress, but without any
283
+ * cache proof for the ambiguous snapshot.
284
+ */
285
+ function readProjectHostInputSnapshot(options) {
286
+ const { includePluginDiscovery, ...projectOptions } = options;
287
+ let project = (0, readProjectConfig_1.readProjectConfig)(projectOptions);
288
+ let hostInputs = collectProjectHostInputs(project, includePluginDiscovery);
289
+ const observedInputs = new Set(hostInputs);
290
+ for (let attempt = 0; attempt < 3; attempt++) {
291
+ const before = hashHostInputPaths(hostInputs);
292
+ const beforeRealpaths = realpathHostInputPaths(hostInputs);
293
+ const beforeSignatures = hostInputMetadataSignatures(hostInputs);
294
+ const candidateProject = (0, readProjectConfig_1.readProjectConfig)(projectOptions);
295
+ const candidateInputs = collectProjectHostInputs(candidateProject, includePluginDiscovery);
296
+ for (const input of candidateInputs)
297
+ observedInputs.add(input);
298
+ const after = hashHostInputPaths(candidateInputs);
299
+ const afterRealpaths = realpathHostInputPaths(candidateInputs);
300
+ const afterSignatures = hostInputMetadataSignatures(candidateInputs);
301
+ if (equalHostInputLists(hostInputs, candidateInputs) &&
302
+ equalHostInputHashes(before, after) &&
303
+ equalHostInputHashes(beforeRealpaths, afterRealpaths) &&
304
+ beforeSignatures !== undefined &&
305
+ afterSignatures !== undefined &&
306
+ equalHostInputHashes(beforeSignatures, afterSignatures)) {
307
+ return {
308
+ hostInputHashes: after,
309
+ hostInputRealpaths: afterRealpaths,
310
+ hostInputs: candidateInputs,
311
+ project: candidateProject,
312
+ };
313
+ }
314
+ project = candidateProject;
315
+ hostInputs = candidateInputs;
316
+ }
317
+ return {
318
+ hostInputHashes: {},
319
+ hostInputRealpaths: {},
320
+ hostInputs: [...observedInputs].sort(),
321
+ project,
322
+ };
323
+ }
324
+ function equalHostInputLists(first, second) {
325
+ return (first.length === second.length &&
326
+ first.every((file, index) => node_path_1.default.resolve(file) === node_path_1.default.resolve(second[index])));
327
+ }
328
+ function equalHostInputHashes(first, second) {
329
+ const files = Object.keys(first);
330
+ return (files.length === Object.keys(second).length &&
331
+ files.every((file) => Object.prototype.hasOwnProperty.call(second, file) &&
332
+ first[file] === second[file]));
333
+ }
334
+ /** Retain proof only for inputs whose initial and final observations agree. */
335
+ function revalidateHostInputHashes(initial, inputs) {
336
+ const current = hashHostInputPaths(inputs);
337
+ const output = {};
338
+ for (const input of inputs) {
339
+ const absolute = node_path_1.default.resolve(input);
340
+ if (Object.prototype.hasOwnProperty.call(initial, absolute) &&
341
+ initial[absolute] === current[absolute]) {
342
+ output[absolute] = current[absolute];
343
+ }
344
+ }
345
+ return output;
346
+ }
347
+ /** Keep physical-identity proof only while the same path resolves identically. */
348
+ function revalidateHostInputRealpaths(initial, inputs) {
349
+ const current = realpathHostInputPaths(inputs);
350
+ return Object.fromEntries(inputs.flatMap((input) => {
351
+ const absolute = node_path_1.default.resolve(input);
352
+ return Object.prototype.hasOwnProperty.call(initial, absolute) &&
353
+ initial[absolute] === current[absolute]
354
+ ? [[absolute, current[absolute]]]
355
+ : [];
356
+ }));
357
+ }
358
+ function hashHostInput(file) {
359
+ try {
360
+ if (node_fs_1.default.statSync(file).isDirectory()) {
361
+ return node_crypto_1.default
362
+ .createHash("sha256")
363
+ .update("ttsc:host-input:directory\0")
364
+ .digest("hex");
365
+ }
366
+ return node_crypto_1.default
367
+ .createHash("sha256")
368
+ .update(node_fs_1.default.readFileSync(file))
369
+ .digest("hex");
370
+ }
371
+ catch {
372
+ return null;
373
+ }
374
+ }
375
+ function hashHostInputPaths(files) {
376
+ return Object.fromEntries(files.map((file) => [node_path_1.default.resolve(file), hashHostInput(file)]));
377
+ }
378
+ /** Physical path selected by a host input, or null while it is unresolved. */
379
+ function realpathHostInput(file) {
380
+ try {
381
+ return node_fs_1.default.realpathSync.native(file);
382
+ }
383
+ catch {
384
+ return null;
385
+ }
386
+ }
387
+ function realpathHostInputPaths(files) {
388
+ return Object.fromEntries(files.map((file) => [node_path_1.default.resolve(file), realpathHostInput(file)]));
389
+ }
390
+ /**
391
+ * Metadata identity that survives content-preserving reads but exposes A-B-A
392
+ * replacement. Missing paths are tied to the nearest existing ancestor whose
393
+ * directory metadata changes when the missing branch appears or disappears.
394
+ */
395
+ function hostInputMetadataSignature(file) {
396
+ const requested = node_path_1.default.resolve(file);
397
+ let current = requested;
398
+ for (;;) {
399
+ try {
400
+ const link = node_fs_1.default.lstatSync(current, { bigint: true });
401
+ let target = link;
402
+ if (link.isSymbolicLink()) {
403
+ try {
404
+ target = node_fs_1.default.statSync(current, { bigint: true });
405
+ }
406
+ catch {
407
+ // A broken link's own metadata cannot expose its target appearing
408
+ // and disappearing during evaluation. Decline cache proof instead.
409
+ return undefined;
410
+ }
411
+ }
412
+ return [
413
+ node_path_1.default.relative(current, requested),
414
+ link.dev,
415
+ link.ino,
416
+ link.mode,
417
+ link.size,
418
+ link.mtimeNs,
419
+ link.ctimeNs,
420
+ target.dev,
421
+ target.ino,
422
+ target.mode,
423
+ target.size,
424
+ target.mtimeNs,
425
+ target.ctimeNs,
426
+ ].join(":");
427
+ }
428
+ catch (error) {
429
+ if (!isMissingPathError(error))
430
+ return undefined;
431
+ const parent = node_path_1.default.dirname(current);
432
+ if (parent === current)
433
+ return undefined;
434
+ current = parent;
435
+ }
436
+ }
437
+ }
438
+ function hostInputMetadataSignatures(files) {
439
+ const output = {};
440
+ for (const file of files) {
441
+ const signature = hostInputMetadataSignature(file);
442
+ if (signature === undefined)
443
+ return undefined;
444
+ output[node_path_1.default.resolve(file)] = signature;
445
+ }
446
+ return output;
447
+ }
448
+ function isMissingPathError(error) {
449
+ const code = error?.code;
450
+ return code === "ENOENT" || code === "ENOTDIR";
451
+ }
452
+ /** Return config ancestry and the manifest controlling package discovery. */
453
+ function collectProjectHostInputs(project, includePluginDiscovery = true) {
454
+ const inputs = new Set(project.configPaths.map((file) => node_path_1.default.resolve(file)));
455
+ if (!includePluginDiscovery)
456
+ return [...inputs].sort();
457
+ const manifestCandidates = collectNearestPackageJsonCandidates(project.root);
458
+ for (const candidate of manifestCandidates)
459
+ inputs.add(candidate);
460
+ const manifest = manifestCandidates.find(existingFile);
461
+ if (manifest !== undefined) {
462
+ const projectManifest = readPackageManifest(manifest);
463
+ if (projectManifest !== undefined) {
464
+ const projectRoot = node_path_1.default.dirname(manifest);
465
+ for (const dependency of directDependencyNames(projectManifest)) {
466
+ const dependencyManifest = resolveDependencyPackageJson(dependency, projectRoot);
467
+ for (const candidate of collectDependencyManifestCandidates(dependency, manifest, dependencyManifest)) {
468
+ inputs.add(candidate);
469
+ }
470
+ if (dependencyManifest !== undefined) {
471
+ inputs.add(node_path_1.default.resolve(dependencyManifest));
472
+ }
473
+ }
474
+ }
475
+ }
476
+ return [...inputs].sort();
477
+ }
478
+ // The direct evaluator preloads ttsx's supported Node hook, whose
479
+ // extensionless rescue order includes TypeScript and ESM/CJS spellings in
480
+ // addition to Node's ordinary CommonJS probes.
481
+ const MODULE_PROBE_EXTENSIONS = [
482
+ ".ts",
483
+ ".tsx",
484
+ ".mts",
485
+ ".cts",
486
+ ".js",
487
+ ".mjs",
488
+ ".cjs",
489
+ ".json",
490
+ ".node",
491
+ ];
492
+ /** Record every file whose later appearance can change one module resolution. */
493
+ function collectModuleResolutionCandidates(specifier, parentFile, resolvedFile) {
494
+ const inputs = new Set();
495
+ const recordedBases = new Set();
496
+ const candidates = (base) => [
497
+ base,
498
+ ...MODULE_PROBE_EXTENSIONS.map((extension) => base + extension),
499
+ node_path_1.default.join(base, "package.json"),
500
+ ...MODULE_PROBE_EXTENSIONS.map((extension) => node_path_1.default.join(base, `index${extension}`)),
501
+ ];
502
+ const recordManifestTargets = (value, directory, allowBare = false) => {
503
+ if (typeof value === "string") {
504
+ if (value !== "" &&
505
+ (allowBare || value.startsWith("./") || value.startsWith("../"))) {
506
+ recordBase(node_path_1.default.resolve(directory, value));
507
+ }
508
+ return;
509
+ }
510
+ if (Array.isArray(value)) {
511
+ for (const item of value) {
512
+ recordManifestTargets(item, directory, allowBare);
513
+ }
514
+ return;
515
+ }
516
+ if (isRecord(value)) {
517
+ for (const item of Object.values(value)) {
518
+ recordManifestTargets(item, directory, allowBare);
519
+ }
520
+ }
521
+ };
522
+ const recordBase = (base) => {
523
+ const resolvedBase = node_path_1.default.resolve(base);
524
+ if (recordedBases.has(resolvedBase))
525
+ return;
526
+ recordedBases.add(resolvedBase);
527
+ for (const candidate of candidates(resolvedBase)) {
528
+ inputs.add(node_path_1.default.resolve(candidate));
529
+ }
530
+ try {
531
+ const manifest = (0, readConfigJson_1.readJsonFile)(node_path_1.default.join(resolvedBase, "package.json"));
532
+ if (isRecord(manifest)) {
533
+ recordManifestTargets(manifest.exports, resolvedBase);
534
+ recordManifestTargets(manifest.module, resolvedBase, true);
535
+ recordManifestTargets(manifest.main, resolvedBase, true);
536
+ }
537
+ }
538
+ catch {
539
+ // A malformed selected manifest is reported by normal resolution/load.
540
+ }
541
+ };
542
+ const selectedBy = (base) => {
543
+ if (resolvedFile === undefined)
544
+ return false;
545
+ let selected;
546
+ try {
547
+ selected = node_fs_1.default.realpathSync.native(resolvedFile);
548
+ }
549
+ catch {
550
+ selected = node_path_1.default.resolve(resolvedFile);
551
+ }
552
+ for (const candidate of candidates(base)) {
553
+ try {
554
+ const canonical = node_fs_1.default.realpathSync.native(candidate);
555
+ const relative = node_path_1.default.relative(canonical, selected);
556
+ if (relative === "" ||
557
+ (node_fs_1.default.statSync(canonical).isDirectory() &&
558
+ relative !== ".." &&
559
+ !relative.startsWith(`..${node_path_1.default.sep}`) &&
560
+ !node_path_1.default.isAbsolute(relative))) {
561
+ return true;
562
+ }
563
+ }
564
+ catch {
565
+ // Missing candidates are the inputs this function intentionally keeps.
566
+ }
567
+ }
568
+ return false;
569
+ };
570
+ const localBases = () => {
571
+ if (specifier.startsWith("file:"))
572
+ return [(0, node_url_1.fileURLToPath)(specifier)];
573
+ const directory = node_path_1.default.dirname(parentFile);
574
+ const raw = node_path_1.default.resolve(directory, specifier);
575
+ const suffixStart = specifier.search(/[?#]/);
576
+ if (suffixStart === -1)
577
+ return [raw];
578
+ const pathname = specifier.slice(0, suffixStart);
579
+ return pathname === ""
580
+ ? [raw]
581
+ : [...new Set([raw, node_path_1.default.resolve(directory, pathname)])];
582
+ };
583
+ if (specifier.startsWith(".") ||
584
+ node_path_1.default.isAbsolute(specifier) ||
585
+ specifier.startsWith("file:")) {
586
+ try {
587
+ for (const base of localBases()) {
588
+ // An existing exact file wins before extension and directory probes.
589
+ // Its own recorded identity is therefore sufficient; siblings cannot
590
+ // supersede it while it exists.
591
+ if (selectedByExactFile(base, resolvedFile) ||
592
+ (resolvedFile === undefined && existingFile(base))) {
593
+ inputs.add(node_path_1.default.resolve(base));
594
+ }
595
+ else {
596
+ recordBase(base);
597
+ }
598
+ }
599
+ }
600
+ catch {
601
+ // Invalid URL spellings are diagnosed by the real resolver.
602
+ }
603
+ return [...inputs];
604
+ }
605
+ const parts = specifier.split("/");
606
+ const packageParts = parts[0]?.startsWith("@")
607
+ ? parts.slice(0, 2)
608
+ : parts.slice(0, 1);
609
+ if (packageParts.some((part) => part === undefined || part === "")) {
610
+ return [...inputs];
611
+ }
612
+ const packageName = packageParts.join("/");
613
+ const subpath = parts.slice(packageParts.length);
614
+ const searchPaths = (0, node_module_1.createRequire)(parentFile).resolve.paths(specifier) ?? [];
615
+ for (const searchPath of searchPaths) {
616
+ const packageDirectory = node_path_1.default.join(searchPath, packageName);
617
+ recordBase(packageDirectory);
618
+ if (subpath.length !== 0) {
619
+ recordBase(node_path_1.default.join(packageDirectory, ...subpath));
620
+ }
621
+ if (selectedBy(packageDirectory))
622
+ break;
623
+ }
624
+ return [...inputs];
625
+ }
626
+ function existingFile(file) {
627
+ try {
628
+ return node_fs_1.default.statSync(file).isFile();
629
+ }
630
+ catch {
631
+ return false;
632
+ }
633
+ }
634
+ function selectedByExactFile(candidate, selected) {
635
+ if (selected === undefined)
636
+ return false;
637
+ try {
638
+ return (node_fs_1.default.realpathSync.native(candidate) === node_fs_1.default.realpathSync.native(selected));
639
+ }
640
+ catch {
641
+ return node_path_1.default.resolve(candidate) === node_path_1.default.resolve(selected);
642
+ }
643
+ }
644
+ /** Record package manifests whose later appearance can redirect discovery. */
645
+ function collectDependencyManifestCandidates(packageName, parentFile, selectedManifest) {
646
+ const out = [];
647
+ let selectedDirectory;
648
+ if (selectedManifest !== undefined) {
649
+ try {
650
+ selectedDirectory = node_fs_1.default.realpathSync.native(node_path_1.default.dirname(selectedManifest));
651
+ }
652
+ catch {
653
+ selectedDirectory = node_path_1.default.resolve(node_path_1.default.dirname(selectedManifest));
654
+ }
655
+ }
656
+ for (const searchPath of (0, node_module_1.createRequire)(parentFile).resolve.paths(packageName) ?? []) {
657
+ const packageDirectory = node_path_1.default.join(searchPath, ...packageName.split("/"));
658
+ out.push(node_path_1.default.join(packageDirectory, "package.json"));
659
+ if (selectedDirectory === undefined)
660
+ continue;
661
+ try {
662
+ if (node_fs_1.default.realpathSync.native(packageDirectory) === selectedDirectory)
663
+ break;
664
+ }
665
+ catch {
666
+ // Keep searching until the selected package directory is reached.
667
+ }
668
+ }
669
+ return out;
670
+ }
174
671
  function composePluginSources(entries, plugins) {
175
672
  const aggregates = plugins
176
673
  .map((plugin, index) => ({ index, plugin }))
@@ -379,7 +876,7 @@ function directDependencyNames(manifest) {
379
876
  function resolveDependencyPackageJson(name, projectRoot) {
380
877
  const direct = node_path_1.default.join(projectRoot, "node_modules", ...name.split("/"));
381
878
  const directManifest = node_path_1.default.join(direct, "package.json");
382
- if (node_fs_1.default.existsSync(directManifest)) {
879
+ if (existingFile(directManifest)) {
383
880
  return resolveRealPath(directManifest);
384
881
  }
385
882
  const projectPackage = node_path_1.default.join(projectRoot, "package.json");
@@ -397,18 +894,23 @@ function resolveDependencyPackageJson(name, projectRoot) {
397
894
  }
398
895
  }
399
896
  function findNearestPackageJson(location) {
897
+ const selected = collectNearestPackageJsonCandidates(location).find(existingFile);
898
+ return selected === undefined ? undefined : resolveRealPath(selected);
899
+ }
900
+ /** Every package-scope candidate through the first regular manifest file. */
901
+ function collectNearestPackageJsonCandidates(location) {
400
902
  let current = node_fs_1.default.statSync(location).isDirectory()
401
903
  ? location
402
904
  : node_path_1.default.dirname(location);
905
+ const candidates = [];
403
906
  while (true) {
404
- const manifest = node_path_1.default.join(current, "package.json");
405
- if (node_fs_1.default.existsSync(manifest)) {
406
- return resolveRealPath(manifest);
407
- }
907
+ const manifest = node_path_1.default.resolve(current, "package.json");
908
+ candidates.push(manifest);
909
+ if (existingFile(manifest))
910
+ return candidates;
408
911
  const parent = node_path_1.default.dirname(current);
409
- if (parent === current) {
410
- return undefined;
411
- }
912
+ if (parent === current)
913
+ return candidates;
412
914
  current = parent;
413
915
  }
414
916
  }
@@ -419,7 +921,7 @@ function findNearestPackageJson(location) {
419
921
  * message worse here than anywhere else.
420
922
  */
421
923
  function readPackageManifest(file) {
422
- if (!node_fs_1.default.existsSync(file)) {
924
+ if (!existingFile(file)) {
423
925
  return undefined;
424
926
  }
425
927
  const parsed = (0, readConfigJson_1.readJsonFile)(file);
@@ -448,42 +950,32 @@ function orderNativePlugins(plugins) {
448
950
  ...plugins.filter((plugin) => plugin.stage === "transform"),
449
951
  ];
450
952
  }
451
- function loadPluginEntry(entry, base, baseDir, effectiveEnv) {
452
- return withPluginLoaderEnv(() => {
453
- const specifier = entry.transform;
454
- if (typeof specifier !== "string" || specifier.length === 0) {
455
- throw new Error(`ttsc: plugin entry is missing a string "transform" field`);
456
- }
457
- const request = resolvePluginRequest(specifier, baseDir);
458
- // `dirname`/`filename` are per-entry: each plugin entry resolves to its own
459
- // descriptor module, so they are derived here from the resolved `request`
460
- // rather than carried on the shared base context. They give factories a
461
- // load-mode-independent stand-in for `__dirname`/`__filename`, which are
462
- // undefined when a descriptor loads through ttsx or as ESM.
463
- const context = {
464
- ...base,
465
- dirname: node_path_1.default.dirname(request),
466
- filename: request,
953
+ function loadPluginEntry(entry, base, request, effectiveEnv) {
954
+ const specifier = entry.transform;
955
+ if (typeof specifier !== "string" || specifier.length === 0) {
956
+ throw new Error(`ttsc: plugin entry is missing a string "transform" field`);
957
+ }
958
+ // `dirname`/`filename` are per-entry: each plugin entry resolves to its own
959
+ // descriptor module, so they are derived here from the resolved `request`
960
+ // rather than carried on the shared base context. They give factories a
961
+ // load-mode-independent stand-in for `__dirname`/`__filename`, which are
962
+ // undefined when a descriptor loads through ttsx or as ESM.
963
+ const context = {
964
+ ...base,
965
+ dirname: node_path_1.default.dirname(request),
966
+ filename: request,
967
+ };
968
+ const loaded = loadPluginDescriptor(request, context, effectiveEnv);
969
+ if (isTtscPlugin(loaded.descriptor)) {
970
+ rejectJsTransformFunctions(specifier, loaded.descriptor);
971
+ return {
972
+ hostInputHashes: loaded.hostInputHashes,
973
+ hostInputRealpaths: loaded.hostInputRealpaths,
974
+ hostInputs: loaded.inputs,
975
+ plugin: loaded.descriptor,
467
976
  };
468
- const mod = requirePluginEntry(request, context, effectiveEnv);
469
- const candidate = mod.createTtscPlugin ??
470
- mod.default ??
471
- mod.plugin ??
472
- mod;
473
- if (typeof candidate === "function") {
474
- const plugin = candidate(context);
475
- if (!isTtscPlugin(plugin)) {
476
- throw new Error(`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`);
477
- }
478
- rejectJsTransformFunctions(specifier, plugin);
479
- return plugin;
480
- }
481
- if (isTtscPlugin(candidate)) {
482
- rejectJsTransformFunctions(specifier, candidate);
483
- return candidate;
484
- }
485
- throw new Error(`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`);
486
- });
977
+ }
978
+ throw new Error(`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`);
487
979
  }
488
980
  /**
489
981
  * Require a plugin descriptor entry, falling back to `ttsx` when Node cannot
@@ -500,20 +992,624 @@ function loadPluginEntry(entry, base, baseDir, effectiveEnv) {
500
992
  * never runs and cannot deadlock. A package that loads directly (a compiled
501
993
  * descriptor, or Bun's native `.ts`) never reaches the fallback.
502
994
  */
503
- function requirePluginEntry(request, context, effectiveEnv) {
995
+ function loadPluginDescriptor(request, context, effectiveEnv) {
504
996
  try {
505
- return require(request);
997
+ return loadCommonJsDescriptor(request, context, effectiveEnv);
506
998
  }
507
999
  catch (error) {
508
- if (!TS_SOURCE_PATTERN.test(request)) {
1000
+ if (!TS_SOURCE_PATTERN.test(request) ||
1001
+ !(error instanceof CommonJsDescriptorLoadError) ||
1002
+ !error.retryWithTtsx) {
509
1003
  throw error;
510
1004
  }
511
- const descriptor = loadDescriptorViaTtsx(request, context, effectiveEnv);
512
- if (descriptor === undefined) {
1005
+ const loaded = loadDescriptorViaTtsx(request, context, effectiveEnv);
1006
+ if (loaded === undefined) {
513
1007
  throw error;
514
1008
  }
515
- return { default: descriptor };
1009
+ return loaded;
1010
+ }
1011
+ }
1012
+ class CommonJsDescriptorLoadError extends Error {
1013
+ retryWithTtsx;
1014
+ constructor(message, retryWithTtsx) {
1015
+ super(message);
1016
+ this.retryWithTtsx = retryWithTtsx;
1017
+ this.name = "CommonJsDescriptorLoadError";
1018
+ }
1019
+ }
1020
+ /**
1021
+ * Evaluate one CommonJS descriptor in a fresh runtime module-cache generation.
1022
+ *
1023
+ * Reloading an external descriptor dependency inside this process has no safe
1024
+ * cache operation: retaining it serves stale exports, while deleting it splits
1025
+ * any application singleton that required the same module first. Isolation
1026
+ * gives every descriptor load current bytes without mutating the host's cache.
1027
+ * The child invokes the factory before walking its graph, so lazy `require()`
1028
+ * calls are included, and a failed first load cannot strand poisoned children.
1029
+ */
1030
+ function loadCommonJsDescriptor(request, context, effectiveEnv) {
1031
+ const runtime = pluginDescriptorRuntimeBinary(effectiveEnv);
1032
+ const runtimeCapabilities = (0, resolveNodeBinary_1.javascriptRuntimeCapabilities)(runtime, effectiveEnv, context.projectRoot);
1033
+ const node = !runtimeCapabilities.bun &&
1034
+ runtimeCapabilities.registerHooks &&
1035
+ runtimeCapabilities.executable !== undefined
1036
+ ? runtimeCapabilities.executable
1037
+ : (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, context.projectRoot);
1038
+ const dir = createEvaluationTempDir();
1039
+ const out = node_path_1.default.join(dir, "descriptor.json");
1040
+ const inputsOut = node_path_1.default.join(dir, "descriptor-inputs.ndjson");
1041
+ const diagnostics = node_path_1.default.join(dir, "descriptor.stderr");
1042
+ const bunConfig = node_path_1.default.join(dir, "bunfig.toml");
1043
+ const runtimeHookPreload = node_path_1.default.join(__dirname, "..", "..", "launcher", "internal", "runtimeHookPreload.js");
1044
+ try {
1045
+ if (runtimeCapabilities.bun) {
1046
+ // A descriptor receives exactly the environment supplied by its ttsc
1047
+ // invocation. Bun otherwise auto-loads project `.env*`, local/global
1048
+ // bunfig preloads and loaders, and may install a missing package from the
1049
+ // network. Those implicit authorities are neither part of Node's loader
1050
+ // contract nor reproducible host inputs, so isolate this evaluator from
1051
+ // them while retaining Bun's native TypeScript/module semantics.
1052
+ node_fs_1.default.writeFileSync(bunConfig, "", "utf8");
1053
+ }
1054
+ const diagnosticsFd = node_fs_1.default.openSync(diagnostics, "w");
1055
+ let result;
1056
+ try {
1057
+ result = node_child_process_1.default.spawnSync(runtime, [
1058
+ ...(runtimeCapabilities.bun
1059
+ ? [
1060
+ "--no-env-file",
1061
+ "--no-install",
1062
+ `--config=${bunConfig}`,
1063
+ `--tsconfig-override=${context.tsconfig}`,
1064
+ ]
1065
+ : []),
1066
+ ...(runtimeCapabilities.registerHooks
1067
+ ? ["--require", runtimeHookPreload]
1068
+ : []),
1069
+ "-e",
1070
+ exports.COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE,
1071
+ ], {
1072
+ cwd: context.projectRoot,
1073
+ env: {
1074
+ ...effectiveEnv,
1075
+ // The direct evaluator may be Bun, but ttsx and native config
1076
+ // loaders require a real Node runtime with synchronous hooks.
1077
+ ...(node === undefined ? {} : { TTSC_NODE_BINARY: node }),
1078
+ TTSC_TTSX_BINARY: effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY,
1079
+ TTSC_PLUGIN_CONTEXT: JSON.stringify(context),
1080
+ TTSC_PLUGIN_DESCRIPTOR_LOAD: "1",
1081
+ TTSC_PLUGIN_DESCRIPTOR_OUT: out,
1082
+ TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE: "1",
1083
+ TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT: inputsOut,
1084
+ TTSC_PLUGIN_ENTRY: request,
1085
+ },
1086
+ // Hold direct-evaluator diagnostics until its retry decision is
1087
+ // known. A successful ttsx fallback must not inherit the expected
1088
+ // loader stack from the discarded first attempt.
1089
+ stdio: ["ignore", diagnosticsFd, diagnosticsFd],
1090
+ windowsHide: true,
1091
+ });
1092
+ }
1093
+ finally {
1094
+ node_fs_1.default.closeSync(diagnosticsFd);
1095
+ }
1096
+ const failure = commonJsDescriptorProcessFailure(result, request);
1097
+ if (failure !== undefined) {
1098
+ const reason = (0, descriptorProcessFailure_1.pluginDescriptorFailureReason)(out);
1099
+ const retryWithTtsx = commonJsDescriptorRetryWithTtsx(out);
1100
+ if (!retryWithTtsx)
1101
+ replayEvaluationDiagnostics(diagnostics);
1102
+ throw new CommonJsDescriptorLoadError(reason === "" ? failure.message : `${failure.message}\n${reason}`, retryWithTtsx);
1103
+ }
1104
+ replayEvaluationDiagnostics(diagnostics);
1105
+ if (!node_fs_1.default.existsSync(out)) {
1106
+ throw new Error(`ttsc: plugin descriptor "${request}" evaluation in an isolated process produced no descriptor output.`);
1107
+ }
1108
+ let parsed;
1109
+ try {
1110
+ parsed = JSON.parse(node_fs_1.default.readFileSync(out, "utf8"));
1111
+ }
1112
+ catch (error) {
1113
+ throw new Error(`ttsc: plugin descriptor "${request}" produced invalid isolated output: ${errorMessage(error)}`);
1114
+ }
1115
+ if (!isRecord(parsed) || !Array.isArray(parsed.inputs)) {
1116
+ throw new Error(`ttsc: plugin descriptor "${request}" produced an invalid isolated result`);
1117
+ }
1118
+ const parsedHashes = isRecord(parsed.inputHashes)
1119
+ ? Object.fromEntries(Object.entries(parsed.inputHashes).flatMap(([file, hash]) => typeof hash === "string" || hash === null
1120
+ ? [[node_path_1.default.resolve(file), hash]]
1121
+ : []))
1122
+ : {};
1123
+ const parsedRealpaths = isRecord(parsed.inputRealpaths)
1124
+ ? Object.fromEntries(Object.entries(parsed.inputRealpaths).flatMap(([file, realpath]) => (typeof realpath === "string" && node_path_1.default.isAbsolute(realpath)) ||
1125
+ realpath === null
1126
+ ? [
1127
+ [
1128
+ node_path_1.default.resolve(file),
1129
+ realpath === null ? null : node_path_1.default.resolve(realpath),
1130
+ ],
1131
+ ]
1132
+ : []))
1133
+ : {};
1134
+ const stableParsedRealpaths = mergeObservedHostInputRealpaths(parsedRealpaths, realpathHostInputPaths(Object.keys(parsedRealpaths)));
1135
+ for (const input of parsed.inputs) {
1136
+ const absolute = node_path_1.default.resolve(String(input));
1137
+ if (!Object.prototype.hasOwnProperty.call(stableParsedRealpaths, absolute)) {
1138
+ delete parsedHashes[absolute];
1139
+ }
1140
+ }
1141
+ const runtimeInputs = readTtsxDescriptorInputs(inputsOut, request);
1142
+ return {
1143
+ descriptor: parsed.descriptor,
1144
+ hostInputHashes: omitUnstableHostInputHashes(mergeObservedHostInputHashes(parsedHashes, runtimeInputs.hostInputHashes), runtimeInputs.unstableInputs),
1145
+ hostInputRealpaths: mergeObservedHostInputRealpaths(stableParsedRealpaths, runtimeInputs.hostInputRealpaths),
1146
+ inputs: [
1147
+ ...new Set([
1148
+ ...parsed.inputs.map((input) => String(input)),
1149
+ ...runtimeInputs.inputs,
1150
+ ]),
1151
+ ].sort(),
1152
+ };
1153
+ }
1154
+ finally {
1155
+ removeEvaluationTempDir(dir);
1156
+ }
1157
+ }
1158
+ /** Validate plugin-declared universal host inputs. */
1159
+ function validatePluginHostInputs(plugin, index) {
1160
+ const label = plugin.name ?? `#${index + 1}`;
1161
+ if (plugin.hostInputs === undefined)
1162
+ return [];
1163
+ if (!Array.isArray(plugin.hostInputs)) {
1164
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputs"; expected an array of absolute paths`);
1165
+ }
1166
+ return plugin.hostInputs.map((file) => {
1167
+ if (typeof file !== "string" || !node_path_1.default.isAbsolute(file)) {
1168
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputs" entry ${JSON.stringify(file)}; expected an absolute path`);
1169
+ }
1170
+ return node_path_1.default.resolve(file);
1171
+ });
1172
+ }
1173
+ /** Validate descriptor-supplied evaluation fingerprints for host inputs. */
1174
+ function validatePluginHostInputHashes(plugin, index, hostInputs) {
1175
+ const label = plugin.name ?? `#${index + 1}`;
1176
+ if (plugin.hostInputHashes === undefined)
1177
+ return {};
1178
+ if (!isRecord(plugin.hostInputHashes) ||
1179
+ Array.isArray(plugin.hostInputHashes)) {
1180
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputHashes"; expected an object keyed by absolute hostInputs paths`);
1181
+ }
1182
+ const allowed = new Set(hostInputs.map((file) => node_path_1.default.resolve(file)));
1183
+ const output = {};
1184
+ for (const [file, hash] of Object.entries(plugin.hostInputHashes)) {
1185
+ if (!node_path_1.default.isAbsolute(file)) {
1186
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputHashes" key ${JSON.stringify(file)}; expected an absolute path`);
1187
+ }
1188
+ const absolute = node_path_1.default.resolve(file);
1189
+ if (!allowed.has(absolute)) {
1190
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} fingerprints ${JSON.stringify(file)} without listing it in "hostInputs"`);
1191
+ }
1192
+ if (hash !== null &&
1193
+ (typeof hash !== "string" || !/^[0-9a-f]{64}$/.test(hash))) {
1194
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid fingerprint for ${JSON.stringify(file)}; expected a lowercase SHA-256 digest or null`);
1195
+ }
1196
+ output[absolute] = hash;
1197
+ }
1198
+ return output;
1199
+ }
1200
+ /** Validate descriptor-supplied physical identities for host inputs. */
1201
+ function validatePluginHostInputRealpaths(plugin, index, hostInputs) {
1202
+ const label = plugin.name ?? `#${index + 1}`;
1203
+ if (plugin.hostInputRealpaths === undefined)
1204
+ return {};
1205
+ if (!isRecord(plugin.hostInputRealpaths) ||
1206
+ Array.isArray(plugin.hostInputRealpaths)) {
1207
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputRealpaths"; expected an object keyed by absolute hostInputs paths`);
1208
+ }
1209
+ const allowed = new Set(hostInputs.map((file) => node_path_1.default.resolve(file)));
1210
+ const output = {};
1211
+ for (const [file, realpath] of Object.entries(plugin.hostInputRealpaths)) {
1212
+ if (!node_path_1.default.isAbsolute(file)) {
1213
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid "hostInputRealpaths" key ${JSON.stringify(file)}; expected an absolute path`);
1214
+ }
1215
+ const absolute = node_path_1.default.resolve(file);
1216
+ if (!allowed.has(absolute)) {
1217
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} identifies ${JSON.stringify(file)} without listing it in "hostInputs"`);
1218
+ }
1219
+ if (realpath !== null &&
1220
+ (typeof realpath !== "string" || !node_path_1.default.isAbsolute(realpath))) {
1221
+ throw new Error(`ttsc: plugin ${JSON.stringify(label)} has invalid physical identity for ${JSON.stringify(file)}; expected an absolute realpath or null`);
1222
+ }
1223
+ output[absolute] =
1224
+ realpath === null ? null : node_path_1.default.resolve(realpath);
1225
+ }
1226
+ return output;
1227
+ }
1228
+ /** Merge snapshots and omit every path observed in contradictory states. */
1229
+ function mergeObservedHostInputHashes(...sources) {
1230
+ const conflicts = new Set();
1231
+ const output = {};
1232
+ for (const source of sources) {
1233
+ for (const [file, hash] of Object.entries(source)) {
1234
+ const absolute = node_path_1.default.resolve(file);
1235
+ if (conflicts.has(absolute))
1236
+ continue;
1237
+ if (Object.prototype.hasOwnProperty.call(output, absolute) &&
1238
+ output[absolute] !== hash) {
1239
+ delete output[absolute];
1240
+ conflicts.add(absolute);
1241
+ continue;
1242
+ }
1243
+ output[absolute] = hash;
1244
+ }
1245
+ }
1246
+ return output;
1247
+ }
1248
+ const mergeObservedHostInputRealpaths = mergeObservedHostInputHashes;
1249
+ /** Prevent a later plugin claim from reviving an unstable loader input. */
1250
+ function mergePluginHostInputHashes(first, second, loaderInputs, pluginInputs) {
1251
+ const output = { ...first };
1252
+ const guarded = new Set(loaderInputs.map((file) => node_path_1.default.resolve(file)));
1253
+ for (const input of pluginInputs) {
1254
+ const absolute = node_path_1.default.resolve(input);
1255
+ if (!Object.prototype.hasOwnProperty.call(second, absolute)) {
1256
+ delete output[absolute];
1257
+ }
1258
+ }
1259
+ for (const [file, hash] of Object.entries(second)) {
1260
+ const absolute = node_path_1.default.resolve(file);
1261
+ if (guarded.has(absolute) &&
1262
+ !Object.prototype.hasOwnProperty.call(first, absolute)) {
1263
+ continue;
1264
+ }
1265
+ if (Object.prototype.hasOwnProperty.call(output, absolute) &&
1266
+ output[absolute] !== hash) {
1267
+ delete output[absolute];
1268
+ continue;
1269
+ }
1270
+ output[absolute] = hash;
1271
+ }
1272
+ return output;
1273
+ }
1274
+ function omitUnstableHostInputHashes(hashes, unstableInputs) {
1275
+ for (const input of unstableInputs)
1276
+ delete hashes[node_path_1.default.resolve(input)];
1277
+ return hashes;
1278
+ }
1279
+ /** CommonJS evaluator emitted into a clean Node process for every load. */
1280
+ exports.COMMONJS_PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
1281
+ `const crypto = require("node:crypto");`,
1282
+ `const fs = require("node:fs");`,
1283
+ `const Module = require("node:module");`,
1284
+ `const path = require("node:path");`,
1285
+ `const { fileURLToPath } = require("node:url");`,
1286
+ `const out = process.env.TTSC_PLUGIN_DESCRIPTOR_OUT;`,
1287
+ `let retryWithTtsx = false;`,
1288
+ `const moduleLoadFailures = new WeakMap();`,
1289
+ `const moduleResolutionFailures = new WeakMap();`,
1290
+ `function existingFile(file) { try { return fs.statSync(file).isFile(); } catch { return false; } }`,
1291
+ `function shouldRetryWithTtsx(error) {`,
1292
+ ` const loadFailure = error && (typeof error === "object" || typeof error === "function") ? moduleLoadFailures.get(error) : undefined;`,
1293
+ ` if (loadFailure?.retryWithTtsx === true) return true;`,
1294
+ ` const failure = error && (typeof error === "object" || typeof error === "function") ? moduleResolutionFailures.get(error) : undefined;`,
1295
+ ` return failure?.retryWithTtsx === true;`,
1296
+ `}`,
1297
+ `try {`,
1298
+ ` const request = process.env.TTSC_PLUGIN_ENTRY;`,
1299
+ ` const context = JSON.parse(process.env.TTSC_PLUGIN_CONTEXT);`,
1300
+ ` const inputs = new Set();`,
1301
+ ` const inputHashes = new Map();`,
1302
+ ` const inputRealpaths = new Map();`,
1303
+ ` const inputSignatures = new Map();`,
1304
+ ` const unstableInputHashes = new Set();`,
1305
+ ` function missingPathError(error) { return error && (error.code === "ENOENT" || error.code === "ENOTDIR"); }`,
1306
+ ` function inputMetadataSignature(file) {`,
1307
+ ` const requested = path.resolve(file);`,
1308
+ ` let current = requested;`,
1309
+ ` for (;;) {`,
1310
+ ` try {`,
1311
+ ` const link = fs.lstatSync(current, { bigint: true });`,
1312
+ ` let target = link;`,
1313
+ ` if (link.isSymbolicLink()) {`,
1314
+ ` try { target = fs.statSync(current, { bigint: true }); }`,
1315
+ ` catch { return undefined; }`,
1316
+ ` }`,
1317
+ ` return [path.relative(current, requested), link.dev, link.ino, link.mode, link.size, link.mtimeNs, link.ctimeNs, target.dev, target.ino, target.mode, target.size, target.mtimeNs, target.ctimeNs].join(":");`,
1318
+ ` } catch (error) {`,
1319
+ ` if (!missingPathError(error)) return undefined;`,
1320
+ ` const parent = path.dirname(current);`,
1321
+ ` if (parent === current) return undefined;`,
1322
+ ` current = parent;`,
1323
+ ` }`,
1324
+ ` }`,
1325
+ ` }`,
1326
+ ` function recordOneInput(file) {`,
1327
+ ` file = path.resolve(file);`,
1328
+ ` inputs.add(file);`,
1329
+ ` if (unstableInputHashes.has(file)) return;`,
1330
+ ` const beforeSignature = inputMetadataSignature(file);`,
1331
+ ` let observedRealpath;`,
1332
+ ` try { observedRealpath = fs.realpathSync.native(file); } catch { observedRealpath = null; }`,
1333
+ ` let observed;`,
1334
+ ` try { observed = fs.statSync(file).isDirectory() ? crypto.createHash("sha256").update("ttsc:host-input:directory\\0").digest("hex") : crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex"); } catch { observed = null; }`,
1335
+ ` const afterSignature = inputMetadataSignature(file);`,
1336
+ ` if (beforeSignature === undefined || afterSignature === undefined || beforeSignature !== afterSignature || (inputSignatures.has(file) && inputSignatures.get(file) !== afterSignature) || (inputRealpaths.has(file) && inputRealpaths.get(file) !== observedRealpath) || (inputHashes.has(file) && inputHashes.get(file) !== observed)) { inputHashes.delete(file); inputRealpaths.delete(file); inputSignatures.delete(file); unstableInputHashes.add(file); return; }`,
1337
+ ` inputSignatures.set(file, afterSignature);`,
1338
+ ` inputRealpaths.set(file, observedRealpath);`,
1339
+ ` inputHashes.set(file, observed);`,
1340
+ ` }`,
1341
+ ` function recordInput(file) {`,
1342
+ ` file = path.resolve(file);`,
1343
+ ` recordOneInput(file);`,
1344
+ ` const parsed = path.parse(file);`,
1345
+ ` let current = parsed.root;`,
1346
+ ` const relative = path.relative(parsed.root, file);`,
1347
+ ` for (const segment of relative.split(path.sep).slice(0, -1)) {`,
1348
+ ` if (segment === "") continue;`,
1349
+ ` current = path.join(current, segment);`,
1350
+ ` try {`,
1351
+ ` if (fs.lstatSync(current).isSymbolicLink()) recordOneInput(current);`,
1352
+ ` } catch {`,
1353
+ ` break;`,
1354
+ ` }`,
1355
+ ` }`,
1356
+ ` }`,
1357
+ ` function asFile(resolved) {`,
1358
+ ` if (typeof resolved !== "string") return undefined;`,
1359
+ ` if (!resolved.startsWith("file:")) return path.isAbsolute(resolved) ? path.resolve(resolved) : undefined;`,
1360
+ ` try { return path.resolve(fileURLToPath(resolved)); } catch { return undefined; }`,
1361
+ ` }`,
1362
+ ` function recordFile(resolved) {`,
1363
+ ` const file = asFile(resolved);`,
1364
+ ` if (file === undefined) return;`,
1365
+ ` recordInput(file);`,
1366
+ ` for (let directory = path.dirname(file);;) {`,
1367
+ ` const manifest = path.join(directory, "package.json");`,
1368
+ ` recordInput(manifest);`,
1369
+ ` if (existingFile(manifest)) break;`,
1370
+ ` const parent = path.dirname(directory);`,
1371
+ ` if (parent === directory) break;`,
1372
+ ` directory = parent;`,
1373
+ ` }`,
1374
+ ` }`,
1375
+ ` function recordPackageManifests(file) {`,
1376
+ ` for (let directory = path.dirname(path.resolve(file));;) {`,
1377
+ ` const manifest = path.join(directory, "package.json");`,
1378
+ ` recordInput(manifest);`,
1379
+ ` if (existingFile(manifest)) return;`,
1380
+ ` const parent = path.dirname(directory);`,
1381
+ ` if (parent === directory) return;`,
1382
+ ` directory = parent;`,
1383
+ ` }`,
1384
+ ` }`,
1385
+ ` recordFile(request);`,
1386
+ ` const moduleProbeExtensions = typeof globalThis.Bun === "object" ? [".tsx", ".jsx", ".ts", ".mjs", ".js", ".cjs", ".json"] : [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json", ".node"];`,
1387
+ ` const jsToTsProbeExtensions = new Map([[".js", [".ts", ".tsx"]], [".jsx", [".tsx"]], [".mjs", [".mts"]], [".cjs", [".cts"]]]);`,
1388
+ ` function sourceSubstitutionCandidates(base) {`,
1389
+ ` const extension = path.extname(base).toLowerCase();`,
1390
+ ` const substitutions = jsToTsProbeExtensions.get(extension);`,
1391
+ ` if (substitutions === undefined) return [];`,
1392
+ ` const stem = base.slice(0, base.length - extension.length);`,
1393
+ ` return substitutions.map((candidate) => stem + candidate);`,
1394
+ ` }`,
1395
+ ` function moduleCandidates(base) {`,
1396
+ ` return [base, ...sourceSubstitutionCandidates(base), ...moduleProbeExtensions.map((extension) => base + extension), path.join(base, "package.json"), ...moduleProbeExtensions.map((extension) => path.join(base, "index" + extension))];`,
1397
+ ` }`,
1398
+ ` const recordedModuleBases = new Set();`,
1399
+ ` function recordManifestTargets(value, directory, allowBare = false) {`,
1400
+ ` if (typeof value === "string") {`,
1401
+ ` if (value !== "" && (allowBare || value.startsWith("./") || value.startsWith("../"))) recordModuleCandidates(path.resolve(directory, value));`,
1402
+ ` return;`,
1403
+ ` }`,
1404
+ ` if (Array.isArray(value)) { for (const item of value) recordManifestTargets(item, directory, allowBare); return; }`,
1405
+ ` if (value && typeof value === "object") for (const item of Object.values(value)) recordManifestTargets(item, directory, allowBare);`,
1406
+ ` }`,
1407
+ ` function recordModuleCandidates(base) {`,
1408
+ ` const resolvedBase = path.resolve(base);`,
1409
+ ` if (recordedModuleBases.has(resolvedBase)) return;`,
1410
+ ` recordedModuleBases.add(resolvedBase);`,
1411
+ ` for (const candidate of moduleCandidates(resolvedBase)) recordInput(candidate);`,
1412
+ ` try {`,
1413
+ ` const manifest = JSON.parse(fs.readFileSync(path.join(resolvedBase, "package.json"), "utf8").replace(/^\uFEFF/, ""));`,
1414
+ ` recordManifestTargets(manifest.exports, resolvedBase);`,
1415
+ ` recordManifestTargets(manifest.module, resolvedBase, true);`,
1416
+ ` recordManifestTargets(manifest.main, resolvedBase, true);`,
1417
+ ` } catch {}`,
1418
+ ` }`,
1419
+ ` function candidateSelected(base, selected) {`,
1420
+ ` if (selected === undefined) return false;`,
1421
+ ` for (const candidate of moduleCandidates(base)) {`,
1422
+ ` try {`,
1423
+ ` const canonical = fs.realpathSync.native(candidate);`,
1424
+ ` const relative = path.relative(canonical, selected);`,
1425
+ ` if (relative === "" || (fs.statSync(canonical).isDirectory() && relative !== ".." && !relative.startsWith(".." + path.sep) && !path.isAbsolute(relative))) return true;`,
1426
+ ` } catch {}`,
1427
+ ` }`,
1428
+ ` return false;`,
1429
+ ` }`,
1430
+ ` function localBases(specifier, parentFile) {`,
1431
+ ` if (specifier.startsWith("file:")) return [fileURLToPath(specifier)];`,
1432
+ ` const directory = path.dirname(parentFile);`,
1433
+ ` const raw = path.resolve(directory, specifier);`,
1434
+ ` const suffixStart = specifier.search(/[?#]/);`,
1435
+ ` if (suffixStart === -1) return [raw];`,
1436
+ ` const pathname = specifier.slice(0, suffixStart);`,
1437
+ ` return pathname === "" ? [raw] : [...new Set([raw, path.resolve(directory, pathname)])];`,
1438
+ ` }`,
1439
+ ` function recordResolutionCandidates(specifier, parent, resolved) {`,
1440
+ ` const parentFile = asFile(parent);`,
1441
+ ` if (typeof specifier !== "string" || parentFile === undefined) return;`,
1442
+ ` let selected;`,
1443
+ ` try { const file = asFile(resolved); selected = file === undefined ? undefined : fs.realpathSync.native(file); } catch {}`,
1444
+ ` if (specifier.startsWith(".") || path.isAbsolute(specifier) || specifier.startsWith("file:")) {`,
1445
+ ` try {`,
1446
+ ` for (const base of localBases(specifier, parentFile)) {`,
1447
+ ` recordPackageManifests(base);`,
1448
+ ` let exact = false;`,
1449
+ ` try { exact = selected === undefined ? fs.statSync(base).isFile() : fs.realpathSync.native(base) === selected; } catch {}`,
1450
+ ` if (exact) recordInput(base);`,
1451
+ ` if (!exact) recordModuleCandidates(base);`,
1452
+ ` }`,
1453
+ ` } catch {}`,
1454
+ ` return;`,
1455
+ ` }`,
1456
+ ` if (Module.isBuiltin(specifier) || specifier.startsWith("#")) return;`,
1457
+ ` const parts = specifier.split("/");`,
1458
+ ` const packageParts = parts[0].startsWith("@") ? parts.slice(0, 2) : parts.slice(0, 1);`,
1459
+ ` if (packageParts.some((part) => part === undefined || part === "")) return;`,
1460
+ ` const packageName = packageParts.join("/");`,
1461
+ ` const subpath = parts.slice(packageParts.length);`,
1462
+ ` const searchPaths = Module.createRequire(parentFile).resolve.paths(specifier) ?? [];`,
1463
+ ` for (const searchPath of searchPaths) {`,
1464
+ ` const packageDirectory = path.join(searchPath, packageName);`,
1465
+ ` recordModuleCandidates(packageDirectory);`,
1466
+ ` if (subpath.length !== 0) recordModuleCandidates(path.join(packageDirectory, ...subpath));`,
1467
+ ` if (candidateSelected(packageDirectory, selected)) break;`,
1468
+ ` }`,
1469
+ ` }`,
1470
+ ` function scanBunModuleGraph(entry) {`,
1471
+ ` if (typeof globalThis.Bun !== "object" || typeof globalThis.Bun.Transpiler !== "function") return;`,
1472
+ ` const scanned = new Set();`,
1473
+ ` function scan(file) {`,
1474
+ ` file = asFile(file);`,
1475
+ ` if (file === undefined) return;`,
1476
+ ` let canonical;`,
1477
+ ` try { canonical = fs.realpathSync.native(file); } catch { return; }`,
1478
+ ` if (scanned.has(canonical)) return;`,
1479
+ ` scanned.add(canonical);`,
1480
+ ` const extension = path.extname(canonical).toLowerCase();`,
1481
+ ` if (![".js", ".jsx", ".mjs", ".cjs", ".ts", ".tsx", ".mts", ".cts"].includes(extension)) return;`,
1482
+ ` const loader = extension === ".tsx" ? "tsx" : extension === ".jsx" ? "jsx" : [".ts", ".mts", ".cts"].includes(extension) ? "ts" : "js";`,
1483
+ ` let imports;`,
1484
+ ` try {`,
1485
+ ` const source = fs.readFileSync(canonical, "utf8");`,
1486
+ ` imports = new globalThis.Bun.Transpiler({ loader }).scan(source).imports;`,
1487
+ ` } catch { return; }`,
1488
+ ` for (const imported of imports) {`,
1489
+ ` const specifier = imported && imported.path;`,
1490
+ ` if (typeof specifier !== "string" || specifier.startsWith("node:") || Module.isBuiltin(specifier)) continue;`,
1491
+ ` recordResolutionCandidates(specifier, canonical, undefined);`,
1492
+ ` try {`,
1493
+ ` const resolved = globalThis.Bun.resolveSync(specifier, path.dirname(canonical));`,
1494
+ ` recordResolutionCandidates(specifier, canonical, resolved);`,
1495
+ ` recordFile(resolved);`,
1496
+ ` scan(resolved);`,
1497
+ ` } catch {`,
1498
+ ` // The real descriptor load below owns user-facing resolution errors.`,
1499
+ ` }`,
1500
+ ` }`,
1501
+ ` }`,
1502
+ ` scan(entry);`,
1503
+ ` }`,
1504
+ ` if (typeof Module.registerHooks === "function") {`,
1505
+ ` Module.registerHooks({`,
1506
+ ` load(url, loadContext, nextLoad) {`,
1507
+ ` try { return nextLoad(url, loadContext); }`,
1508
+ ` catch (error) {`,
1509
+ ` if (error && (typeof error === "object" || typeof error === "function")) moduleLoadFailures.set(error, { retryWithTtsx: error.code === "ERR_UNKNOWN_FILE_EXTENSION" });`,
1510
+ ` throw error;`,
1511
+ ` }`,
1512
+ ` },`,
1513
+ ` resolve(specifier, resolveContext, nextResolve) {`,
1514
+ ` recordResolutionCandidates(specifier, resolveContext.parentURL, undefined);`,
1515
+ ` let resolved;`,
1516
+ ` try {`,
1517
+ ` resolved = nextResolve(specifier, resolveContext);`,
1518
+ ` } catch (error) {`,
1519
+ ` if (error && (typeof error === "object" || typeof error === "function")) {`,
1520
+ ` const parent = asFile(resolveContext.parentURL);`,
1521
+ ` const anchor = typeof parent === "string" ? parent : request;`,
1522
+ ` const candidate = specifier.startsWith(".") && path.extname(specifier) === "" ? path.resolve(path.dirname(anchor), specifier) : undefined;`,
1523
+ ` const retryWithTtsx = candidate !== undefined && [".ts", ".cts", ".mts", ".tsx"].some((extension) => existingFile(candidate + extension) || existingFile(path.join(candidate, "index" + extension)));`,
1524
+ ` moduleResolutionFailures.set(error, { retryWithTtsx });`,
1525
+ ` }`,
1526
+ ` throw error;`,
1527
+ ` }`,
1528
+ ` const url = typeof resolved === "string" ? resolved : resolved && resolved.url;`,
1529
+ ` recordResolutionCandidates(specifier, resolveContext.parentURL, url);`,
1530
+ ` recordFile(url);`,
1531
+ ` return resolved;`,
1532
+ ` },`,
1533
+ ` });`,
1534
+ ` }`,
1535
+ ` if (typeof globalThis.Bun === "object" && typeof globalThis.Bun.plugin === "function") {`,
1536
+ ` let insideBunResolve = false;`,
1537
+ ` globalThis.Bun.plugin({`,
1538
+ ` name: "ttsc-plugin-descriptor-inputs",`,
1539
+ ` setup(build) {`,
1540
+ ` build.onResolve({ filter: /.*/ }, (args) => {`,
1541
+ ` if (insideBunResolve || args.path.startsWith("node:")) return;`,
1542
+ ` try {`,
1543
+ ` insideBunResolve = true;`,
1544
+ ` const importer = args.importer && args.importer.startsWith("file:") ? fileURLToPath(args.importer) : args.importer;`,
1545
+ ` const from = importer ? path.dirname(importer) : process.cwd();`,
1546
+ ` recordResolutionCandidates(args.path, importer, undefined);`,
1547
+ ` const resolved = globalThis.Bun.resolveSync(args.path, from);`,
1548
+ ` recordResolutionCandidates(args.path, importer, resolved);`,
1549
+ ` recordFile(resolved);`,
1550
+ ` } catch {`,
1551
+ ` // The real resolver below owns user-facing resolution errors.`,
1552
+ ` } finally {`,
1553
+ ` insideBunResolve = false;`,
1554
+ ` }`,
1555
+ ` });`,
1556
+ ` },`,
1557
+ ` });`,
1558
+ ` }`,
1559
+ ` scanBunModuleGraph(request);`,
1560
+ ` let mod;`,
1561
+ ` try {`,
1562
+ ` mod = require(request);`,
1563
+ ` } catch (error) {`,
1564
+ ` retryWithTtsx = shouldRetryWithTtsx(error);`,
1565
+ ` throw error;`,
1566
+ ` }`,
1567
+ ` const candidate = mod.createTtscPlugin ?? mod.default ?? mod.plugin ?? mod;`,
1568
+ ` const descriptor = typeof candidate === "function" ? candidate(context) : candidate;`,
1569
+ ` if (descriptor && typeof descriptor === "object" && ("transformSource" in descriptor || "transformOutput" in descriptor)) {`,
1570
+ ` throw new Error("ttsc: plugin descriptor declares unsupported JS transform functions; declare a native backend instead");`,
1571
+ ` }`,
1572
+ // Serialize the descriptor while the module-resolution hooks are still
1573
+ // collecting. Getters are allowed by JavaScript's object model and can
1574
+ // lazily require a config; snapshotting inputs first would silently omit
1575
+ // that influence.
1576
+ ` const serializedDescriptor = JSON.stringify(descriptor);`,
1577
+ ` for (const input of [...inputs]) recordInput(input);`,
1578
+ ` const payload = { inputHashes: Object.fromEntries(inputHashes), inputRealpaths: Object.fromEntries(inputRealpaths), inputs: [...inputs].sort() };`,
1579
+ ` if (serializedDescriptor !== undefined) payload.descriptor = JSON.parse(serializedDescriptor);`,
1580
+ ` fs.writeFileSync(out, JSON.stringify(payload));`,
1581
+ `} catch (error) {`,
1582
+ ` const message = error instanceof Error ? error.message : String(error);`,
1583
+ ` const stack = error instanceof Error && error.stack ? error.stack : String(error);`,
1584
+ ` try {`,
1585
+ ` fs.writeFileSync(out, JSON.stringify({ __ttscLoaderError: message, __ttscRetryWithTtsx: retryWithTtsx }));`,
1586
+ ` } catch {}`,
1587
+ ` try { process.stderr.write(stack + "\\n"); } catch {}`,
1588
+ ` process.exit(1);`,
1589
+ `}`,
1590
+ ``,
1591
+ ].join("\n");
1592
+ /** Read the isolated loader's explicit retry classification. */
1593
+ function commonJsDescriptorRetryWithTtsx(file) {
1594
+ try {
1595
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
1596
+ return isRecord(parsed) && parsed.__ttscRetryWithTtsx === true;
1597
+ }
1598
+ catch {
1599
+ return false;
1600
+ }
1601
+ }
1602
+ function commonJsDescriptorProcessFailure(result, request) {
1603
+ if (result.error !== undefined) {
1604
+ return new Error(`ttsc: failed to launch an isolated process for plugin descriptor "${request}": ${result.error.message}`);
1605
+ }
1606
+ if (result.signal !== null) {
1607
+ return new Error(`ttsc: plugin descriptor "${request}" isolated evaluation was killed by signal ${result.signal}.`);
516
1608
  }
1609
+ if (result.status !== 0) {
1610
+ return new Error(`ttsc: plugin descriptor "${request}" isolated evaluation failed with exit code ${String(result.status)}`);
1611
+ }
1612
+ return undefined;
517
1613
  }
518
1614
  const TS_SOURCE_PATTERN = /\.(?:[cm]?ts|tsx)$/i;
519
1615
  /**
@@ -537,6 +1633,11 @@ exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE = [
537
1633
  // both — "Cannot find module ./missing" is as actionable as anything the
538
1634
  // factory could have said.
539
1635
  `try {`,
1636
+ // Runtime hooks are installed before this shim loads. Arm their internal
1637
+ // side channel only for the descriptor import itself, after this shim's own
1638
+ // imports have resolved, so ttsc implementation files never become project
1639
+ // cache inputs.
1640
+ ` process.env.TTSC_PLUGIN_DESCRIPTOR_INPUTS_ACTIVE = "1";`,
540
1641
  ` const mod = await import(pathToFileURL(process.env.TTSC_PLUGIN_ENTRY).href);`,
541
1642
  ` const context = JSON.parse(process.env.TTSC_PLUGIN_CONTEXT);`,
542
1643
  ` const candidate = mod.createTtscPlugin ?? mod.default ?? mod.plugin ?? mod;`,
@@ -570,28 +1671,27 @@ function loadDescriptorViaTtsx(request, context, effectiveEnv) {
570
1671
  // Binary discovery prefers the instance environment, then the ambient
571
1672
  // process.env (where `withPluginLoaderEnv` injects ttsc's own node/ttsx paths
572
1673
  // just before this runs), then the running interpreter.
573
- const node = effectiveEnv.TTSC_NODE_BINARY ??
574
- process.env.TTSC_NODE_BINARY ??
575
- process.execPath;
1674
+ const node = (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, context.projectRoot);
576
1675
  const ttsx = effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY;
577
- if (ttsx === undefined || ttsx.length === 0) {
1676
+ if (node === undefined || ttsx === undefined || ttsx.length === 0) {
578
1677
  return undefined;
579
1678
  }
580
- const dir = node_fs_1.default.mkdtempSync(node_path_1.default.join(node_os_1.default.tmpdir(), "ttsc-plugin-descriptor-"));
1679
+ const dir = createEvaluationTempDir();
581
1680
  const out = node_path_1.default.join(dir, "descriptor.json");
1681
+ const inputsOut = node_path_1.default.join(dir, "descriptor-inputs.ndjson");
582
1682
  const shim = node_path_1.default.join(dir, "load-descriptor.mts");
583
1683
  // ttsx type-checks and builds the shim's own project, so it needs a tsconfig
584
1684
  // to anchor on; a minimal one is enough (the shim is `@ts-nocheck`).
585
- node_fs_1.default.writeFileSync(node_path_1.default.join(dir, "tsconfig.json"), JSON.stringify({
586
- compilerOptions: {
587
- module: "nodenext",
588
- moduleResolution: "nodenext",
589
- skipLibCheck: true,
590
- target: "es2022",
591
- },
592
- }));
593
- node_fs_1.default.writeFileSync(shim, exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE);
594
1685
  try {
1686
+ node_fs_1.default.writeFileSync(node_path_1.default.join(dir, "tsconfig.json"), JSON.stringify({
1687
+ compilerOptions: {
1688
+ module: "nodenext",
1689
+ moduleResolution: "nodenext",
1690
+ skipLibCheck: true,
1691
+ target: "es2022",
1692
+ },
1693
+ }));
1694
+ node_fs_1.default.writeFileSync(shim, exports.PLUGIN_DESCRIPTOR_SHIM_SOURCE);
595
1695
  const result = node_child_process_1.default.spawnSync(node, [ttsx, "--no-plugins", shim], {
596
1696
  cwd: context.projectRoot,
597
1697
  encoding: "utf8",
@@ -608,11 +1708,13 @@ function loadDescriptorViaTtsx(request, context, effectiveEnv) {
608
1708
  dirname: context.dirname,
609
1709
  filename: context.filename,
610
1710
  plugin: context.plugin,
1711
+ pluginConfigDir: context.pluginConfigDir,
611
1712
  projectRoot: context.projectRoot,
612
1713
  tsconfig: context.tsconfig,
613
1714
  }),
614
1715
  TTSC_PLUGIN_DESCRIPTOR_LOAD: "1",
615
1716
  TTSC_PLUGIN_DESCRIPTOR_OUT: out,
1717
+ TTSC_PLUGIN_DESCRIPTOR_INPUTS_OUT: inputsOut,
616
1718
  TTSC_PLUGIN_ENTRY: request,
617
1719
  },
618
1720
  // Both child streams are human output, and they go straight to this
@@ -638,7 +1740,13 @@ ${reason}`);
638
1740
  }
639
1741
  const text = node_fs_1.default.readFileSync(out, "utf8");
640
1742
  try {
641
- return JSON.parse(text);
1743
+ const inputSnapshot = readTtsxDescriptorInputs(inputsOut, request);
1744
+ return {
1745
+ descriptor: JSON.parse(text),
1746
+ hostInputHashes: omitUnstableHostInputHashes(inputSnapshot.hostInputHashes, inputSnapshot.unstableInputs),
1747
+ hostInputRealpaths: inputSnapshot.hostInputRealpaths,
1748
+ inputs: inputSnapshot.inputs,
1749
+ };
642
1750
  }
643
1751
  catch (error) {
644
1752
  throw new Error(`ttsc: plugin descriptor "${request}" produced invalid JSON: ${errorMessage(error)}`);
@@ -648,13 +1756,121 @@ ${reason}`);
648
1756
  removeEvaluationTempDir(dir);
649
1757
  }
650
1758
  }
1759
+ /**
1760
+ * Expand the ttsx runtime's selected module edges into the same exact and
1761
+ * missing resolution inputs used by the direct isolated evaluator.
1762
+ */
1763
+ function readTtsxDescriptorInputs(file, request) {
1764
+ const inputs = new Set([node_path_1.default.resolve(request)]);
1765
+ const hashes = new Map();
1766
+ const realpaths = new Map();
1767
+ const signatures = new Map();
1768
+ const unstableInputs = new Set();
1769
+ let text;
1770
+ try {
1771
+ text = node_fs_1.default.readFileSync(file, "utf8");
1772
+ }
1773
+ catch {
1774
+ return {
1775
+ hostInputHashes: {},
1776
+ hostInputRealpaths: {},
1777
+ inputs: [...inputs],
1778
+ unstableInputs: [],
1779
+ };
1780
+ }
1781
+ for (const line of text.split(/\r?\n/)) {
1782
+ if (line.trim() === "")
1783
+ continue;
1784
+ let record;
1785
+ try {
1786
+ record = JSON.parse(line);
1787
+ }
1788
+ catch {
1789
+ continue;
1790
+ }
1791
+ if (typeof record.resolved === "string") {
1792
+ const resolved = node_path_1.default.resolve(record.resolved);
1793
+ inputs.add(resolved);
1794
+ if (record.unstable === true) {
1795
+ hashes.delete(resolved);
1796
+ realpaths.delete(resolved);
1797
+ signatures.delete(resolved);
1798
+ unstableInputs.add(resolved);
1799
+ continue;
1800
+ }
1801
+ if (typeof record.signature !== "string" ||
1802
+ (signatures.has(resolved) &&
1803
+ signatures.get(resolved) !== record.signature)) {
1804
+ hashes.delete(resolved);
1805
+ realpaths.delete(resolved);
1806
+ signatures.delete(resolved);
1807
+ unstableInputs.add(resolved);
1808
+ continue;
1809
+ }
1810
+ signatures.set(resolved, record.signature);
1811
+ if ((typeof record.realpath === "string" &&
1812
+ node_path_1.default.isAbsolute(record.realpath)) ||
1813
+ record.realpath === null) {
1814
+ const observed = record.realpath === null ? null : node_path_1.default.resolve(record.realpath);
1815
+ if (realpaths.has(resolved) && realpaths.get(resolved) !== observed) {
1816
+ hashes.delete(resolved);
1817
+ realpaths.delete(resolved);
1818
+ signatures.delete(resolved);
1819
+ unstableInputs.add(resolved);
1820
+ continue;
1821
+ }
1822
+ realpaths.set(resolved, observed);
1823
+ }
1824
+ if (typeof record.hash === "string" || record.hash === null) {
1825
+ if (unstableInputs.has(resolved)) {
1826
+ // Keep a previously observed contradiction unstable.
1827
+ }
1828
+ else if (hashes.has(resolved) &&
1829
+ hashes.get(resolved) !== record.hash) {
1830
+ hashes.delete(resolved);
1831
+ realpaths.delete(resolved);
1832
+ signatures.delete(resolved);
1833
+ unstableInputs.add(resolved);
1834
+ }
1835
+ else {
1836
+ hashes.set(resolved, record.hash);
1837
+ }
1838
+ }
1839
+ }
1840
+ if (typeof record.specifier === "string" &&
1841
+ typeof record.parent === "string") {
1842
+ for (const candidate of collectModuleResolutionCandidates(record.specifier, record.parent, record.resolved)) {
1843
+ inputs.add(node_path_1.default.resolve(candidate));
1844
+ }
1845
+ }
1846
+ }
1847
+ for (const [resolved, observed] of realpaths) {
1848
+ if (realpathHostInput(resolved) === observed &&
1849
+ signatures.get(resolved) === hostInputMetadataSignature(resolved)) {
1850
+ continue;
1851
+ }
1852
+ hashes.delete(resolved);
1853
+ realpaths.delete(resolved);
1854
+ signatures.delete(resolved);
1855
+ unstableInputs.add(resolved);
1856
+ }
1857
+ return {
1858
+ hostInputHashes: Object.fromEntries(hashes),
1859
+ hostInputRealpaths: Object.fromEntries(realpaths),
1860
+ inputs: [...inputs].sort(),
1861
+ unstableInputs: [...unstableInputs],
1862
+ };
1863
+ }
651
1864
  function errorMessage(error) {
652
1865
  return error instanceof Error ? error.message : String(error);
653
1866
  }
654
1867
  function withPluginLoaderEnv(run) {
655
1868
  const previousNode = process.env.TTSC_NODE_BINARY;
656
1869
  const previousTtsx = process.env.TTSC_TTSX_BINARY;
657
- process.env.TTSC_NODE_BINARY ??= process.execPath;
1870
+ const node = (0, resolveNodeBinary_1.resolveNodeBinary)({}, process.cwd());
1871
+ if (process.env.TTSC_NODE_BINARY === undefined && node !== undefined) {
1872
+ process.env.TTSC_NODE_BINARY = node;
1873
+ }
658
1874
  process.env.TTSC_TTSX_BINARY ??= node_path_1.default.join(__dirname, "..", "..", "launcher", "ttsx.js");
659
1875
  try {
660
1876
  return run();
@@ -664,6 +1880,45 @@ function withPluginLoaderEnv(run) {
664
1880
  restoreEnv("TTSC_TTSX_BINARY", previousTtsx);
665
1881
  }
666
1882
  }
1883
+ /**
1884
+ * Select the executable for isolated descriptor evaluation.
1885
+ *
1886
+ * Under Bun, `process.execPath` names Bun itself. Bun implements enough of the
1887
+ * Node module surface to evaluate descriptors natively, but not Node's
1888
+ * synchronous `module.registerHooks` preload. The caller therefore recognizes
1889
+ * this default and omits the Node-only preload while retaining the fresh
1890
+ * process boundary. An explicitly configured Node executable remains
1891
+ * authoritative and receives the preload as usual.
1892
+ */
1893
+ function pluginDescriptorRuntimeBinary(env) {
1894
+ if (env.TTSC_NODE_BINARY === undefined &&
1895
+ typeof process.versions.bun ===
1896
+ "string") {
1897
+ return process.execPath;
1898
+ }
1899
+ return (env.TTSC_NODE_BINARY ?? process.env.TTSC_NODE_BINARY ?? process.execPath);
1900
+ }
1901
+ /** Replay a child's human output without imposing a fixed output ceiling. */
1902
+ function replayEvaluationDiagnostics(file) {
1903
+ let fd;
1904
+ try {
1905
+ fd = node_fs_1.default.openSync(file, "r");
1906
+ const buffer = Buffer.allocUnsafe(64 * 1024);
1907
+ for (;;) {
1908
+ const length = node_fs_1.default.readSync(fd, buffer, 0, buffer.length, null);
1909
+ if (length === 0)
1910
+ break;
1911
+ node_fs_1.default.writeSync(2, buffer, 0, length);
1912
+ }
1913
+ }
1914
+ catch {
1915
+ // Diagnostic replay must never replace the descriptor result.
1916
+ }
1917
+ finally {
1918
+ if (fd !== undefined)
1919
+ node_fs_1.default.closeSync(fd);
1920
+ }
1921
+ }
667
1922
  function restoreEnv(key, value) {
668
1923
  if (value === undefined) {
669
1924
  delete process.env[key];
@@ -910,7 +2165,7 @@ function resolvePluginExportCondition(specifier, baseDir) {
910
2165
  return null;
911
2166
  }
912
2167
  const file = node_path_1.default.resolve(node_path_1.default.dirname(packageJson), resolved);
913
- return node_fs_1.default.existsSync(file) ? resolveRealPath(file) : null;
2168
+ return existingFile(file) ? resolveRealPath(file) : null;
914
2169
  }
915
2170
  /**
916
2171
  * Split a bare specifier into its package name and the `.`-prefixed subpath it
@@ -1098,6 +2353,13 @@ function readTsgoVersion(projectRoot) {
1098
2353
  return "unknown";
1099
2354
  }
1100
2355
  }
2356
+ /**
2357
+ * Create an evaluator directory whose cleanup path cannot follow a retargeted
2358
+ * parent alias.
2359
+ */
2360
+ function createEvaluationTempDir() {
2361
+ return (0, createCanonicalTempDirectory_1.createCanonicalTempDirectory)("ttsc-plugin-descriptor-");
2362
+ }
1101
2363
  /**
1102
2364
  * Remove an evaluation temp directory without letting cleanup replace a result.
1103
2365
  *