ttsc 0.32.0 → 0.32.1

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 (164) hide show
  1. package/lib/TtscCompiler.js +1 -1
  2. package/lib/TtscCompiler.js.map +1 -1
  3. package/lib/compiler/internal/ResidentCheckProcess.d.ts +74 -96
  4. package/lib/compiler/internal/ResidentCheckProcess.js +150 -335
  5. package/lib/compiler/internal/ResidentCheckProcess.js.map +1 -1
  6. package/lib/compiler/internal/ResidentCheckProcessHost.d.ts +127 -0
  7. package/lib/compiler/internal/ResidentCheckProcessHost.js +447 -0
  8. package/lib/compiler/internal/ResidentCheckProcessHost.js.map +1 -0
  9. package/lib/compiler/internal/ResidentCheckProcessProtocol.d.ts +64 -0
  10. package/lib/compiler/internal/ResidentCheckProcessProtocol.js +3 -0
  11. package/lib/compiler/internal/ResidentCheckProcessProtocol.js.map +1 -0
  12. package/lib/compiler/internal/build/BuildExecution.d.ts +103 -1
  13. package/lib/compiler/internal/build/BuildExecution.js +91 -11
  14. package/lib/compiler/internal/build/BuildExecution.js.map +1 -1
  15. package/lib/compiler/internal/build/ResidentCheckWatchSession.d.ts +16 -11
  16. package/lib/compiler/internal/build/ResidentCheckWatchSession.js +37 -21
  17. package/lib/compiler/internal/build/ResidentCheckWatchSession.js.map +1 -1
  18. package/lib/compiler/internal/build/RunBuildOptions.d.ts +6 -5
  19. package/lib/compiler/internal/build/WatchInputPublication.d.ts +59 -0
  20. package/lib/compiler/internal/build/WatchInputPublication.js +93 -0
  21. package/lib/compiler/internal/build/WatchInputPublication.js.map +1 -0
  22. package/lib/compiler/internal/build/runBuild.d.ts +29 -2
  23. package/lib/compiler/internal/build/runBuild.js +45 -19
  24. package/lib/compiler/internal/build/runBuild.js.map +1 -1
  25. package/lib/compiler/internal/buildNativeCompiler.d.ts +2 -0
  26. package/lib/compiler/internal/buildNativeCompiler.js +1 -0
  27. package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
  28. package/lib/compiler/internal/compileProjectInMemory.js +4 -2
  29. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  30. package/lib/compiler/internal/project/resolveOwningProjectConfig.d.ts +7 -2
  31. package/lib/compiler/internal/project/resolveOwningProjectConfig.js +6 -3
  32. package/lib/compiler/internal/project/resolveOwningProjectConfig.js.map +1 -1
  33. package/lib/compiler/internal/readEffectiveCompilerOptions.js +1 -1
  34. package/lib/compiler/internal/readEffectiveCompilerOptions.js.map +1 -1
  35. package/lib/compiler/internal/resolveBinary.d.ts +1 -0
  36. package/lib/compiler/internal/resolveBinary.js.map +1 -1
  37. package/lib/compiler/internal/resolveTsgo.d.ts +6 -0
  38. package/lib/compiler/internal/resolveTsgo.js.map +1 -1
  39. package/lib/compiler/internal/runSingleFileEmit.d.ts +28 -1
  40. package/lib/compiler/internal/runSingleFileEmit.js +120 -86
  41. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  42. package/lib/compiler/internal/startResidentTransform.js +8 -2
  43. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  44. package/lib/compiler/internal/transformProjectInMemory.js +11 -3
  45. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  46. package/lib/compiler/internal/transformProjectInWorker.d.ts +1 -1
  47. package/lib/compiler/internal/transformProjectInWorker.js +3 -2
  48. package/lib/compiler/internal/transformProjectInWorker.js.map +1 -1
  49. package/lib/internal/workerModuleUrl.d.ts +28 -0
  50. package/lib/internal/workerModuleUrl.js +40 -0
  51. package/lib/internal/workerModuleUrl.js.map +1 -0
  52. package/lib/launcher/internal/buildSingleRootProject.js +2 -1
  53. package/lib/launcher/internal/buildSingleRootProject.js.map +1 -1
  54. package/lib/launcher/internal/getCompilerVersionText.js +3 -2
  55. package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
  56. package/lib/launcher/internal/prepareExecution.js +2 -1
  57. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  58. package/lib/launcher/internal/runTtsc.js +91 -41
  59. package/lib/launcher/internal/runTtsc.js.map +1 -1
  60. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.d.ts +10 -6
  61. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js +11 -9
  62. package/lib/launcher/internal/runtime/CommonJsRuntimeSource.js.map +1 -1
  63. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.d.ts +20 -0
  64. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js +27 -0
  65. package/lib/launcher/internal/runtime/advertiseTypeScriptExtensions.js.map +1 -0
  66. package/lib/launcher/internal/runtime/installRuntimeHooks.d.ts +5 -3
  67. package/lib/launcher/internal/runtime/installRuntimeHooks.js +7 -3
  68. package/lib/launcher/internal/runtime/installRuntimeHooks.js.map +1 -1
  69. package/lib/launcher/internal/watch/AsyncWatchTopology.d.ts +113 -0
  70. package/lib/launcher/internal/watch/AsyncWatchTopology.js +232 -0
  71. package/lib/launcher/internal/watch/AsyncWatchTopology.js.map +1 -0
  72. package/lib/launcher/internal/watch/WatchSettlement.d.ts +49 -0
  73. package/lib/launcher/internal/watch/WatchSettlement.js +70 -0
  74. package/lib/launcher/internal/watch/WatchSettlement.js.map +1 -0
  75. package/lib/launcher/internal/watch/WatchTopology.d.ts +29 -1
  76. package/lib/launcher/internal/watch/WatchTopology.js +86 -10
  77. package/lib/launcher/internal/watch/WatchTopology.js.map +1 -1
  78. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.d.ts +121 -0
  79. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js +3 -0
  80. package/lib/launcher/internal/watch/WatchTopologyWorkerProtocol.js.map +1 -0
  81. package/lib/launcher/internal/watch/serveWatchTopologyWorker.d.ts +19 -0
  82. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js +62 -0
  83. package/lib/launcher/internal/watch/serveWatchTopologyWorker.js.map +1 -0
  84. package/lib/launcher/internal/watch/watchTopologyWorker.d.ts +1 -0
  85. package/lib/launcher/internal/watch/watchTopologyWorker.js +22 -0
  86. package/lib/launcher/internal/watch/watchTopologyWorker.js.map +1 -0
  87. package/lib/plugin/CapabilityPluginResolver.d.ts +1 -1
  88. package/lib/plugin/CapabilityPluginResolver.js +3 -2
  89. package/lib/plugin/CapabilityPluginResolver.js.map +1 -1
  90. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.d.ts +15 -12
  91. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js +13 -11
  92. package/lib/plugin/internal/load/PluginDescriptorEvaluationCache.js.map +1 -1
  93. package/lib/plugin/internal/load/PluginPackageResolution.d.ts +2 -2
  94. package/lib/plugin/internal/load/PluginPackageResolution.js +13 -7
  95. package/lib/plugin/internal/load/PluginPackageResolution.js.map +1 -1
  96. package/lib/plugin/internal/load/ProjectPluginEntries.d.ts +23 -7
  97. package/lib/plugin/internal/load/ProjectPluginEntries.js +36 -14
  98. package/lib/plugin/internal/load/ProjectPluginEntries.js.map +1 -1
  99. package/lib/plugin/internal/load/collectProjectHostInputs.d.ts +3 -1
  100. package/lib/plugin/internal/load/collectProjectHostInputs.js +5 -2
  101. package/lib/plugin/internal/load/collectProjectHostInputs.js.map +1 -1
  102. package/lib/plugin/internal/load/hasProjectPluginEntries.d.ts +3 -1
  103. package/lib/plugin/internal/load/hasProjectPluginEntries.js +4 -2
  104. package/lib/plugin/internal/load/hasProjectPluginEntries.js.map +1 -1
  105. package/lib/plugin/internal/load/loadProjectPlugins.d.ts +36 -5
  106. package/lib/plugin/internal/load/loadProjectPlugins.js +466 -416
  107. package/lib/plugin/internal/load/loadProjectPlugins.js.map +1 -1
  108. package/lib/plugin/internal/load/pluginResolutionFailure.d.ts +18 -0
  109. package/lib/plugin/internal/load/pluginResolutionFailure.js +32 -0
  110. package/lib/plugin/internal/load/pluginResolutionFailure.js.map +1 -0
  111. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.d.ts +1 -1
  112. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js +3 -2
  113. package/lib/plugin/internal/source/PluginBuildEnvironmentReadings.js.map +1 -1
  114. package/lib/structures/ITtscCompilerContext.d.ts +12 -12
  115. package/lib/structures/ITtscPlugin.d.ts +7 -5
  116. package/lib/structures/ITtscPluginFactoryContext.d.ts +9 -8
  117. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +12 -6
  118. package/package.json +9 -9
  119. package/src/TtscCompiler.ts +1 -1
  120. package/src/compiler/internal/ResidentCheckProcess.ts +159 -374
  121. package/src/compiler/internal/ResidentCheckProcessHost.ts +502 -0
  122. package/src/compiler/internal/ResidentCheckProcessProtocol.ts +53 -0
  123. package/src/compiler/internal/build/BuildExecution.ts +118 -9
  124. package/src/compiler/internal/build/ResidentCheckWatchSession.ts +41 -24
  125. package/src/compiler/internal/build/RunBuildOptions.ts +9 -5
  126. package/src/compiler/internal/build/WatchInputPublication.ts +106 -0
  127. package/src/compiler/internal/build/runBuild.ts +58 -34
  128. package/src/compiler/internal/buildNativeCompiler.ts +4 -0
  129. package/src/compiler/internal/compileProjectInMemory.ts +8 -2
  130. package/src/compiler/internal/project/resolveOwningProjectConfig.ts +16 -5
  131. package/src/compiler/internal/readEffectiveCompilerOptions.ts +1 -1
  132. package/src/compiler/internal/resolveBinary.ts +4 -1
  133. package/src/compiler/internal/resolveTsgo.ts +7 -0
  134. package/src/compiler/internal/runSingleFileEmit.ts +139 -99
  135. package/src/compiler/internal/startResidentTransform.ts +9 -2
  136. package/src/compiler/internal/transformProjectInMemory.ts +12 -3
  137. package/src/compiler/internal/transformProjectInWorker.ts +10 -2
  138. package/src/internal/workerModuleUrl.ts +34 -0
  139. package/src/launcher/internal/buildSingleRootProject.ts +2 -0
  140. package/src/launcher/internal/getCompilerVersionText.ts +3 -2
  141. package/src/launcher/internal/prepareExecution.ts +2 -0
  142. package/src/launcher/internal/runTtsc.ts +123 -45
  143. package/src/launcher/internal/runtime/CommonJsRuntimeSource.ts +11 -9
  144. package/src/launcher/internal/runtime/advertiseTypeScriptExtensions.ts +25 -0
  145. package/src/launcher/internal/runtime/installRuntimeHooks.ts +7 -3
  146. package/src/launcher/internal/watch/AsyncWatchTopology.ts +267 -0
  147. package/src/launcher/internal/watch/WatchSettlement.ts +67 -0
  148. package/src/launcher/internal/watch/WatchTopology.ts +84 -13
  149. package/src/launcher/internal/watch/WatchTopologyWorkerProtocol.ts +106 -0
  150. package/src/launcher/internal/watch/serveWatchTopologyWorker.ts +75 -0
  151. package/src/launcher/internal/watch/watchTopologyWorker.ts +29 -0
  152. package/src/plugin/CapabilityPluginResolver.ts +9 -2
  153. package/src/plugin/internal/load/PluginDescriptorEvaluationCache.ts +17 -12
  154. package/src/plugin/internal/load/PluginPackageResolution.ts +14 -9
  155. package/src/plugin/internal/load/ProjectPluginEntries.ts +40 -15
  156. package/src/plugin/internal/load/collectProjectHostInputs.ts +7 -1
  157. package/src/plugin/internal/load/hasProjectPluginEntries.ts +8 -3
  158. package/src/plugin/internal/load/loadProjectPlugins.ts +672 -594
  159. package/src/plugin/internal/load/pluginResolutionFailure.ts +37 -0
  160. package/src/plugin/internal/source/PluginBuildEnvironmentReadings.ts +8 -2
  161. package/src/structures/ITtscCompilerContext.ts +12 -12
  162. package/src/structures/ITtscPlugin.ts +7 -5
  163. package/src/structures/ITtscPluginFactoryContext.ts +9 -8
  164. package/src/structures/internal/TtscSingleFileEmitOptions.ts +22 -11
@@ -9,6 +9,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
9
9
  const node_module_1 = require("node:module");
10
10
  const node_path_1 = __importDefault(require("node:path"));
11
11
  const node_url_1 = require("node:url");
12
+ const WatchInputPublication_1 = require("../../../compiler/internal/build/WatchInputPublication");
12
13
  const readJsonFile_1 = require("../../../compiler/internal/project/readJsonFile");
13
14
  const readProjectConfig_1 = require("../../../compiler/internal/project/readProjectConfig");
14
15
  const SidecarEnvironment_1 = require("../../../compiler/internal/sharedHost/SidecarEnvironment");
@@ -80,6 +81,9 @@ const visitImportMappedCandidates_1 = require("./visitImportMappedCandidates");
80
81
  * declaration. Sequential content, metadata and physical-path observations are
81
82
  * not an atomic snapshot or detection of every omitted read.
82
83
  *
84
+ * Synchronous callers require synchronous watch-input handlers; use
85
+ * {@link loadProjectPlugins.async} to await a worker's baseline publication.
86
+ *
83
87
  * @param options.binary - Absolute path to the ttsc native helper binary.
84
88
  * @param options.cacheDir - Override the plugin binary cache directory.
85
89
  * @param options.cwd - Working directory for resolving relative paths.
@@ -90,11 +94,12 @@ const visitImportMappedCandidates_1 = require("./visitImportMappedCandidates");
90
94
  * native environment-name identity. Defaults to `process.env` for CLI
91
95
  * callers, so ambient behavior is unchanged.
92
96
  * @param options.file - Path to the tsconfig/jsconfig file.
93
- * @param options.pluginConfigDir - Caller-declared anchor for plugin
94
- * config-file discovery (see `ITtscPluginFactoryContext.pluginConfigDir`).
95
- * @param options.projectRoot - Override the project root directory.
97
+ * @param options.pluginConfigDir - Caller-declared consumer directory for
98
+ * plugin packages, factory contexts and config-file discovery.
99
+ * @param options.projectRoot - Override the source root, native working
100
+ * directory, cache anchor and returned relative Go-source base.
96
101
  * @param options.tsconfig - Alias for `file`.
97
- * @evidence contracts/common.md#principled-implementation The loader brackets project discovery observations, evaluates descriptors in isolated processes and validates declarations before publishing conservative source repair interests. Metadata refusal remains terminal; valid records narrow watch interests before final admission/build, while contradictory content or physical observations are omitted from proof rather than retroactively blessed.
102
+ * @evidence contracts/common.md#principled-implementation The loader brackets discovery at the selected config or explicit pluginConfigDir and factories receive that consumer anchor, independently of the source root retained for descriptor/native process cwd, caches and relative Go sources. It validates declarations before publishing conservative source repair interests. Metadata refusal remains terminal; valid records narrow watch interests before final admission/build, while contradictory content or physical observations are omitted from proof rather than retroactively blessed.
98
103
  * @evidence contracts/common.md#clear-and-simple-design The operation owns one ordered load generation; private helpers separate discovery, evaluation, validation, composition and proof merging, while package resolution and source building remain their own modules.
99
104
  * @evidence contracts/common.md#prohibited-implementation-shortcuts Descriptor module caches are isolated rather than deleting application singletons. ttsx retry occurs only for explicit supported TypeScript loader incompatibility, with plugins disabled to avoid recursive self-hosting; arbitrary descriptor failures are not retried into false success.
100
105
  * @evidence contracts/common.md#meaningful-documentation Native JSDoc explains result provenance, environment and path options; helper comments explain input races, conservative proof omission, fallback authority and cleanup. Member/tag spacing and separated concepts follow the documentation skill.
@@ -104,420 +109,463 @@ const visitImportMappedCandidates_1 = require("./visitImportMappedCandidates");
104
109
  * @evidence contracts/performance.md#bound-retention-and-release-resources Finally restores injected environment locators, attempts evaluator directory removal and closes direct-evaluator diagnostic descriptors. Close/removal can fail; synchronous completion concerns the selected evaluator and does not certify arbitrary descendants are gone. Generation maps/results scale with observed data, and disk diagnostics/result/observation files have no independent byte ceiling here. Default disk pruning has its own interval/protection/failure policy; explicit roots remain caller-owned.
105
110
  */
106
111
  function loadProjectPlugins(options) {
107
- // Snapshot the caller environment before `withPluginLoaderEnv` injects
108
- // host-owned Node/ttsx locators into process.env. Under a Bun parent the
109
- // direct descriptor evaluator must remain Bun unless the caller explicitly
110
- // selected another runtime.
111
- const effectiveEnv = SidecarEnvironment_1.SidecarEnvironment.merge(options.env ?? process.env);
112
- const projectSnapshot = readProjectHostInputSnapshot({
113
- cwd: options.cwd,
114
- file: options.file,
115
- includePluginDiscovery: options.entries === undefined,
116
- projectRoot: options.projectRoot,
117
- tsconfig: options.tsconfig,
118
- });
119
- const { project } = projectSnapshot;
120
- const projectHostInputs = projectSnapshot.hostInputs;
121
- const projectHostInputHashes = projectSnapshot.hostInputHashes;
122
- const projectHostInputRealpaths = projectSnapshot.hostInputRealpaths;
123
- const entries = options.entries === false
124
- ? []
125
- : ProjectPluginEntries_1.ProjectPluginEntries.resolvePluginEntries(project, options.entries).filter((entry) => entry.config.enabled !== false);
126
- if (entries.length === 0) {
127
- options.onWatchInputs?.([]);
128
- return {
129
- ...collectHostInputSnapshot(project, {}, [], projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
130
- descriptorReadsDeclared: true,
131
- discoveryInputsComplete: project.configInputsComplete === true,
132
- nativePlugins: [],
133
- pluginSources: {},
134
- project,
135
- };
112
+ return WatchInputPublication_1.WatchInputPublication.run(loadProjectPlugins.steps(options));
113
+ }
114
+ /**
115
+ * Keep synchronous loading and awaited watch handoffs on one load sequence.
116
+ *
117
+ * @evidence contracts/common.md#principled-implementation Both completion modes advance one ordered discovery, admission, publication and build sequence. Awaited stepping preserves baseline-before-read handoffs before package selection and source builds, including empty selection and conservative repair roots.
118
+ * @evidence contracts/common.md#clear-and-simple-design The namespace adds an awaited runner beside synchronous loading and exposes one generator to build orchestration; existing helpers retain discovery, validation and compilation responsibilities.
119
+ * @evidence contracts/common.md#prohibited-implementation-shortcuts Publication suspension retains failed-build interests and source proof rather than rebuilding a baseline after compilation. Pending publication cannot establish observation; rejection enters the real load failure path without replacing admission or native builds.
120
+ * @evidence contracts/common.md#meaningful-documentation Entry prose distinguishes completion modes, baseline-before-read ordering and internal sequence completion/rejection duties; the primary loader documents input and result provenance.
121
+ * @evidence contracts/portability.md#os-neutral-implementation The shared sequence retains existing native resolution, paths, environment and argument-array handling; the namespace adds no independent boundary.
122
+ * @evidence contracts/performance.md#efficient-algorithms Discovery, source hashing, Go metadata, validation and builds retain their original input costs; each publication adds one suspension without repeating those phases.
123
+ * @evidence contracts/performance.md#reuse-equivalent-work Validated descriptor/source records and load-local digest/reader sharing remain available across suspension; the completion wrapper retains no reusable load or in-flight cache.
124
+ * @evidence contracts/performance.md#bound-retention-and-release-resources One load generation stays live while its current handoff settles; evaluator cleanup and returned-generation ownership remain with the sequence, and handler rejection enters generator cleanup.
125
+ */
126
+ (function (loadProjectPlugins) {
127
+ /**
128
+ * Load plugins after each watch owner confirms its reported input baseline. A
129
+ * rejected handoff fails loading before the next source read or build.
130
+ */
131
+ function async(options) {
132
+ return WatchInputPublication_1.WatchInputPublication.runAsync(steps(options));
136
133
  }
137
- const cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
138
- const context = {
139
- binary: options.binary,
140
- cwd,
141
- ...(options.pluginConfigDir === undefined || options.pluginConfigDir === ""
142
- ? {}
143
- : { pluginConfigDir: node_path_1.default.resolve(cwd, options.pluginConfigDir) }),
144
- projectRoot: project.root,
145
- tsconfig: project.path,
146
- };
147
- // Where isolated descriptor evaluations keep their answers across launches
148
- // (`PluginDescriptorEvaluationCache`).
149
- // WARNING (#1721, #1722, #1723): a warm load must prove its unchanged inputs
150
- // from this store, not re-read them. Every launch is a new process, so any
151
- // module-level memo of this load's work dies with it; a 27 s warm typia
152
- // launch whose native build took 0.28 s is what that looked like.
153
- const identities = options.identities ??
154
- PluginContentIdentities_1.PluginContentIdentities.open({
155
- projectRoot: project.root,
156
- cacheDir: options.cacheDir,
157
- env: effectiveEnv,
134
+ loadProjectPlugins.async = async;
135
+ /**
136
+ * Produce one load, suspending at each watch-input handoff before continuing.
137
+ * Internal build orchestration consumes the whole sequence with its chosen
138
+ * completion runner; it must deliver rejection back into the sequence.
139
+ */
140
+ function* steps(options) {
141
+ // Snapshot the caller environment before `withPluginLoaderEnv` injects
142
+ // host-owned Node/ttsx locators into process.env. Under a Bun parent the
143
+ // direct descriptor evaluator must remain Bun unless the caller explicitly
144
+ // selected another runtime.
145
+ const effectiveEnv = SidecarEnvironment_1.SidecarEnvironment.merge(options.env ?? process.env);
146
+ const projectSnapshot = readProjectHostInputSnapshot({
147
+ cwd: options.cwd,
148
+ file: options.file,
149
+ includePluginDiscovery: options.entries === undefined,
150
+ pluginConfigDir: options.pluginConfigDir,
151
+ projectRoot: options.projectRoot,
152
+ tsconfig: options.tsconfig,
158
153
  });
159
- const descriptorCache = {
160
- cacheDir: options.cacheDir,
161
- identities,
162
- version: (0, pluginBuildVersions_1.pluginBuildVersions)(project.root).ttsc,
163
- };
164
- const loadedEntries = withPluginLoaderEnv(() => entries.map((entry) => {
165
- const specifier = entry.config.transform;
166
- if (typeof specifier !== "string" || specifier.length === 0) {
167
- throw new Error(`ttsc: plugin entry is missing a string "transform" field`);
168
- }
169
- const entryParent = node_path_1.default.join(entry.baseDir, "package.json");
170
- const probedCandidates = collectModuleResolutionCandidates(specifier, entryParent, undefined);
171
- // Capture every candidate before resolution chooses the descriptor entry.
172
- // A post-resolution snapshot could bless a higher-priority file created
173
- // after the resolver had already selected the old entry.
174
- const probedCandidateHashes = (0, hashHostInputPaths_1.hashHostInputPaths)(probedCandidates);
175
- const probedCandidateRealpaths = (0, realpathHostInputPaths_1.realpathHostInputPaths)(probedCandidates);
176
- const request = PluginPackageResolution_1.PluginPackageResolution.resolvePluginRequest(specifier, entry.baseDir);
177
- // Keep the candidates of the search roots up to the one the entry
178
- // resolved in: the lookup never read the roots after it
179
- // (`moduleResolutionBaseSelects`).
180
- const read = new Set(collectModuleResolutionCandidates(specifier, entryParent, request).map((candidate) => node_path_1.default.resolve(candidate)));
181
- const entryCandidates = probedCandidates.filter((candidate) => read.has(node_path_1.default.resolve(candidate)));
182
- const entryCandidateHashes = pickHostInputEntries(probedCandidateHashes, entryCandidates);
183
- const entryCandidateRealpaths = pickHostInputEntries(probedCandidateRealpaths, entryCandidates);
184
- const loaded = loadPluginEntry(entry.config, { ...context, plugin: entry.config }, request, effectiveEnv, descriptorCache);
185
- const loadedHostInputHashes = mergeObservedHostInputHashes(loaded.hostInputHashes, (0, hashHostInputPaths_1.hashHostInputPaths)(Object.keys(loaded.hostInputHashes)));
186
- const loadedHostInputRealpaths = mergeObservedHostInputRealpaths(loaded.hostInputRealpaths, (0, realpathHostInputPaths_1.realpathHostInputPaths)(Object.keys(loaded.hostInputRealpaths)));
187
- const hostInputHashes = mergeObservedHostInputHashes(entryCandidateHashes, loadedHostInputHashes);
188
- const hostInputRealpaths = mergeObservedHostInputRealpaths(entryCandidateRealpaths, loadedHostInputRealpaths);
189
- for (const input of loaded.hostInputs) {
190
- const absolute = node_path_1.default.resolve(input);
191
- if (!Object.prototype.hasOwnProperty.call(loadedHostInputHashes, absolute)) {
192
- delete hostInputHashes[absolute];
193
- }
154
+ const { project } = projectSnapshot;
155
+ const projectHostInputs = projectSnapshot.hostInputs;
156
+ const projectHostInputHashes = projectSnapshot.hostInputHashes;
157
+ const projectHostInputRealpaths = projectSnapshot.hostInputRealpaths;
158
+ const entries = options.entries === false
159
+ ? []
160
+ : ProjectPluginEntries_1.ProjectPluginEntries.resolvePluginEntries(project, options.entries, options.pluginConfigDir).filter((entry) => entry.config.enabled !== false);
161
+ if (entries.length === 0) {
162
+ yield options.onWatchInputs?.([]);
163
+ return {
164
+ ...collectHostInputSnapshot(project, {}, [], projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
165
+ descriptorReadsDeclared: true,
166
+ discoveryInputsComplete: project.configInputsComplete === true,
167
+ nativePlugins: [],
168
+ pluginSources: {},
169
+ project,
170
+ };
194
171
  }
195
- return {
196
- ...loaded,
197
- hostInputHashes,
198
- hostInputRealpaths,
199
- hostInputs: [...loaded.hostInputs, ...entryCandidates],
200
- request,
172
+ const cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
173
+ const context = {
174
+ binary: options.binary,
175
+ cwd,
176
+ ...(options.pluginConfigDir === undefined ||
177
+ options.pluginConfigDir === ""
178
+ ? {}
179
+ : { pluginConfigDir: node_path_1.default.resolve(cwd, options.pluginConfigDir) }),
180
+ projectRoot: project.root,
181
+ tsconfig: project.path,
201
182
  };
202
- }), identities);
203
- const plugins = (0, composePluginSources_1.composePluginSources)(entries, loadedEntries.map((entry) => entry.plugin));
204
- const { ttsc: ttscVersion, tsgo: tsgoVersion } = (0, pluginBuildVersions_1.pluginBuildVersions)(context.projectRoot);
205
- const candidates = plugins
206
- .map((plugin, index) => {
207
- const stage = PluginDescriptorAdmission_1.PluginDescriptorAdmission.stage(plugin);
208
- (0, validatePluginSource_1.validatePluginSource)(plugin);
209
- const contributors = (0, validatePluginContributors_1.validatePluginContributors)(plugin);
210
- const source = resolvePluginSource(plugin.source, context.projectRoot);
211
- const label = (0, pluginLabel_1.pluginLabel)(plugin, entries[index].config, index);
212
- (0, requirePluginSource_1.requirePluginSource)(source, label);
213
- return { plugin, index, stage, contributors, source, label };
214
- })
215
- .map((candidate) => ({
216
- ...candidate,
217
- ...(0, resolvePluginGoModule_1.resolvePluginGoModule)(candidate.source, candidate.label),
218
- }));
219
- // One reading of each source directory, shared by package selection and
220
- // every build below and reported as the state the binaries were keyed on.
221
- const sourceDigests = new Map();
222
- // And one reading of the environment each build directory is keyed on, which
223
- // a plugin module root's state reports as it is.
224
- const environmentDigests = new Map();
225
- // With the witness of each such reading, so a build of a directory package
226
- // selection already read keys on that reading instead of probing Go again.
227
- const environmentWitnesses = new Map();
228
- const readings = {
229
- sources: sourceDigests,
230
- environments: environmentDigests,
231
- witnesses: environmentWitnesses,
232
- };
233
- // Records describing sources must never land inside one, so the store is
234
- // admitted for source proofs only once every source of the load is known.
235
- const sourceIdentities = PluginContentIdentities_1.PluginContentIdentities.outside(identities, [
236
- ...candidates.flatMap((candidate) => [
237
- candidate.moduleRoot,
238
- candidate.packageDir,
239
- ...(candidate.contributors ?? []).map((input) => input.source),
240
- ]),
241
- ...SourcePluginWorkspace_1.SourcePluginWorkspace.findTtscOverlayDirs(),
242
- ]);
243
- // One fixed-effective-environment load owns manifest syntax sharing from the
244
- // first watch query through metadata and builds. Do not allocate this after
245
- // watch projection: that silently repeats selected-Go work (#1712).
246
- const packageReaders = new Map();
247
- const watchReplacements = options.onWatchInputs === undefined
248
- ? undefined
249
- : new Map();
250
- if (options.onWatchInputs !== undefined) {
251
- // The prospective context reads the admitted module layout and anchors its
252
- // tool there before kind exists. Keep both possible final-root baselines:
253
- // a linked package keeps packageDir, and an executable keeps moduleRoot.
254
- const interests = new Set(candidates
255
- .flatMap((candidate) => [
256
- candidate.moduleRoot,
257
- candidate.packageDir,
258
- ...(candidate.contributors ?? []).map((input) => input.source),
259
- ])
260
- .filter(reportsPluginSource));
261
- // WARNING (#1728): hand the watch owner this load's store with the roots.
262
- // It baselines each root before any build reads it; without the store it
263
- // read every source the load proves from records a moment later.
264
- options.onWatchInputs([...interests].sort(), sourceIdentities);
265
- const initialSize = interests.size;
266
- for (const candidate of candidates) {
267
- // Nested source-only proposals replace the outer root manifests. Do not
268
- // activate their irrelevant module graph just to discover watch inputs.
269
- if (candidate.stage === "transform" && candidate.entry !== ".")
270
- continue;
271
- for (const directory of pluginReplacementWatchDirectories(candidate.moduleRoot, effectiveEnv, watchReplacements, packageReaders, sourceIdentities))
272
- if (reportsPluginSource(directory))
273
- interests.add(directory);
274
- }
275
- if (interests.size !== initialSize)
276
- options.onWatchInputs([...interests].sort(), sourceIdentities);
277
- }
278
- const proposals = NativeSourcePackages_1.NativeSourcePackages.propose(candidates.map((candidate) => ({
279
- ...candidate,
280
- ownModule: candidate.stage !== "transform",
281
- })), effectiveEnv, packageReaders, sourceIdentities, readings);
282
- const records = candidates.map(({ plugin, index, stage, contributors, source, packageDir }) => {
283
- const { kind, moduleRoot } = (0, resolveNativeSource_1.resolveNativeSource)(source, plugin, entries[index].config, index, {
284
- env: effectiveEnv,
285
- observation: proposals[index].observation,
286
- });
287
- if (kind === "linked" && stage !== "transform") {
288
- throw new Error(`ttsc: plugin "${(0, pluginLabel_1.pluginLabel)(plugin, entries[index].config, index)}" source is a linked Go package, but only transform-stage plugins can be linked into a compiler host`);
289
- }
290
- const linkedContributorName = kind === "linked"
291
- ? `linked_${String(index).padStart(6, "0")}`
292
- : undefined;
293
- const hostInputs = validatePluginHostInputs(plugin, index);
294
- const pluginHostInputHashes = validatePluginHostInputHashes(plugin, index, hostInputs);
295
- const pluginHostInputRealpaths = validatePluginHostInputRealpaths(plugin, index, hostInputs);
296
- return {
297
- capabilities: plugin.capabilities,
298
- contributors,
299
- config: entries[index].config,
300
- kind,
301
- label: (0, pluginLabel_1.pluginLabel)(plugin, entries[index].config, index),
302
- linkedContributorName,
303
- name: plugin.name,
304
- reportsTypeScriptDiagnostics: plugin.reportsTypeScriptDiagnostics === true,
305
- request: loadedEntries[index].request,
306
- hostInputHashes: mergePluginHostInputHashes(loadedEntries[index].hostInputHashes, pluginHostInputHashes, loadedEntries[index].hostInputs, hostInputs),
307
- hostInputRealpaths: mergePluginHostInputHashes(loadedEntries[index].hostInputRealpaths, pluginHostInputRealpaths, loadedEntries[index].hostInputs, hostInputs),
308
- hostInputs: [...loadedEntries[index].hostInputs, ...hostInputs],
309
- moduleRoot,
310
- packageDir,
311
- source,
312
- stage,
183
+ // Where isolated descriptor evaluations keep their answers across launches
184
+ // (`PluginDescriptorEvaluationCache`).
185
+ // WARNING (#1721, #1722, #1723): a warm load must prove its unchanged inputs
186
+ // from this store, not re-read them. Every launch is a new process, so any
187
+ // module-level memo of this load's work dies with it; a 27 s warm typia
188
+ // launch whose native build took 0.28 s is what that looked like.
189
+ const identities = options.identities ??
190
+ PluginContentIdentities_1.PluginContentIdentities.open({
191
+ projectRoot: project.root,
192
+ cacheDir: options.cacheDir,
193
+ env: effectiveEnv,
194
+ });
195
+ const descriptorCache = {
196
+ cacheDir: options.cacheDir,
197
+ identities,
198
+ projectRoot: project.root,
199
+ version: (0, pluginBuildVersions_1.pluginBuildVersions)(project.root).ttsc,
313
200
  };
314
- });
315
- const executableCandidates = candidates.filter((candidate) => records[candidate.index].kind === "executable" &&
316
- !proposals[candidate.index].ownModule);
317
- const buildDirectories = pluginBuildDirectories(records, effectiveEnv, watchReplacements, packageReaders, sourceIdentities);
318
- // Admission can now narrow the provisional interests without restamping the
319
- // retained package/module baselines. Final host errors still keep these roots.
320
- options.onWatchInputs?.(buildDirectories, sourceIdentities);
321
- const keyedDirectories = new Set(buildDirectories);
322
- const linkedContributors = records
323
- .filter((record) => record.stage === "transform")
324
- .flatMap((record) => record.kind === "linked"
325
- ? [{ name: record.linkedContributorName, source: record.source }]
326
- : []);
327
- const transformHosts = records.filter((record) => record.stage === "transform" && record.kind === "executable");
328
- const hostContributors = linkedContributors.length === 0 ? undefined : linkedContributors;
329
- // Every host compiles the linked sources under its own module/tool context.
330
- // Equivalent entries in one module share this metadata command and reader;
331
- // a second executable host can select a genuinely different Go context.
332
- const admittedExecutables = new Set();
333
- if (linkedContributors.length !== 0) {
334
- const hosts = transformHosts.length === 0
335
- ? [
336
- {
337
- source: node_path_1.default.join(ttscPackageRoot(), "cmd", "utility-host"),
338
- label: "linked-plugin-host",
339
- },
340
- ]
341
- : transformHosts;
342
- const contexts = new Map();
343
- for (const host of hosts) {
344
- const moduleRoot = (0, resolvePluginGoModule_1.resolvePluginGoModule)(host.source, host.label).moduleRoot;
345
- let context = contexts.get(moduleRoot);
346
- if (context === undefined) {
347
- context = { source: host.source, label: host.label, records: [] };
348
- contexts.set(moduleRoot, context);
201
+ const loadedEntries = withPluginLoaderEnv(() => entries.map((entry) => {
202
+ const specifier = entry.config.transform;
203
+ if (typeof specifier !== "string" || specifier.length === 0) {
204
+ throw new Error(`ttsc: plugin entry is missing a string "transform" field`);
349
205
  }
206
+ const entryParent = node_path_1.default.join(entry.baseDir, "package.json");
207
+ const probedCandidates = collectModuleResolutionCandidates(specifier, entryParent, undefined);
208
+ // Capture every candidate before resolution chooses the descriptor entry.
209
+ // A post-resolution snapshot could bless a higher-priority file created
210
+ // after the resolver had already selected the old entry.
211
+ const probedCandidateHashes = (0, hashHostInputPaths_1.hashHostInputPaths)(probedCandidates);
212
+ const probedCandidateRealpaths = (0, realpathHostInputPaths_1.realpathHostInputPaths)(probedCandidates);
213
+ const request = PluginPackageResolution_1.PluginPackageResolution.resolvePluginRequest(specifier, entry.baseDir);
214
+ // Keep the candidates of the search roots up to the one the entry
215
+ // resolved in: the lookup never read the roots after it
216
+ // (`moduleResolutionBaseSelects`).
217
+ const read = new Set(collectModuleResolutionCandidates(specifier, entryParent, request).map((candidate) => node_path_1.default.resolve(candidate)));
218
+ const entryCandidates = probedCandidates.filter((candidate) => read.has(node_path_1.default.resolve(candidate)));
219
+ const entryCandidateHashes = pickHostInputEntries(probedCandidateHashes, entryCandidates);
220
+ const entryCandidateRealpaths = pickHostInputEntries(probedCandidateRealpaths, entryCandidates);
221
+ const loaded = loadPluginEntry(entry.config, {
222
+ ...context,
223
+ plugin: entry.config,
224
+ // Package factories such as typia resolve their own package from
225
+ // this consumer anchor. Keep the compiler source root below for
226
+ // native cwd, cache ownership and returned relative Go sources.
227
+ projectRoot: ProjectPluginEntries_1.ProjectPluginEntries.resolvePluginBaseDir(project, options.pluginConfigDir),
228
+ }, request, effectiveEnv, descriptorCache);
229
+ const loadedHostInputHashes = mergeObservedHostInputHashes(loaded.hostInputHashes, (0, hashHostInputPaths_1.hashHostInputPaths)(Object.keys(loaded.hostInputHashes)));
230
+ const loadedHostInputRealpaths = mergeObservedHostInputRealpaths(loaded.hostInputRealpaths, (0, realpathHostInputPaths_1.realpathHostInputPaths)(Object.keys(loaded.hostInputRealpaths)));
231
+ const hostInputHashes = mergeObservedHostInputHashes(entryCandidateHashes, loadedHostInputHashes);
232
+ const hostInputRealpaths = mergeObservedHostInputRealpaths(entryCandidateRealpaths, loadedHostInputRealpaths);
233
+ for (const input of loaded.hostInputs) {
234
+ const absolute = node_path_1.default.resolve(input);
235
+ if (!Object.prototype.hasOwnProperty.call(loadedHostInputHashes, absolute)) {
236
+ delete hostInputHashes[absolute];
237
+ }
238
+ }
239
+ return {
240
+ ...loaded,
241
+ hostInputHashes,
242
+ hostInputRealpaths,
243
+ hostInputs: [...loaded.hostInputs, ...entryCandidates],
244
+ request,
245
+ };
246
+ }), identities);
247
+ const plugins = (0, composePluginSources_1.composePluginSources)(entries, loadedEntries.map((entry) => entry.plugin));
248
+ const { ttsc: ttscVersion, tsgo: tsgoVersion } = (0, pluginBuildVersions_1.pluginBuildVersions)(context.projectRoot);
249
+ const candidates = plugins
250
+ .map((plugin, index) => {
251
+ const stage = PluginDescriptorAdmission_1.PluginDescriptorAdmission.stage(plugin);
252
+ (0, validatePluginSource_1.validatePluginSource)(plugin);
253
+ const contributors = (0, validatePluginContributors_1.validatePluginContributors)(plugin);
254
+ const source = resolvePluginSource(plugin.source, context.projectRoot);
255
+ const label = (0, pluginLabel_1.pluginLabel)(plugin, entries[index].config, index);
256
+ (0, requirePluginSource_1.requirePluginSource)(source, label);
257
+ return { plugin, index, stage, contributors, source, label };
258
+ })
259
+ .map((candidate) => ({
260
+ ...candidate,
261
+ ...(0, resolvePluginGoModule_1.resolvePluginGoModule)(candidate.source, candidate.label),
262
+ }));
263
+ // One reading of each source directory, shared by package selection and
264
+ // every build below and reported as the state the binaries were keyed on.
265
+ const sourceDigests = new Map();
266
+ // And one reading of the environment each build directory is keyed on, which
267
+ // a plugin module root's state reports as it is.
268
+ const environmentDigests = new Map();
269
+ // With the witness of each such reading, so a build of a directory package
270
+ // selection already read keys on that reading instead of probing Go again.
271
+ const environmentWitnesses = new Map();
272
+ const readings = {
273
+ sources: sourceDigests,
274
+ environments: environmentDigests,
275
+ witnesses: environmentWitnesses,
276
+ };
277
+ // Records describing sources must never land inside one, so the store is
278
+ // admitted for source proofs only once every source of the load is known.
279
+ const sourceIdentities = PluginContentIdentities_1.PluginContentIdentities.outside(identities, [
280
+ ...candidates.flatMap((candidate) => [
281
+ candidate.moduleRoot,
282
+ candidate.packageDir,
283
+ ...(candidate.contributors ?? []).map((input) => input.source),
284
+ ]),
285
+ ...SourcePluginWorkspace_1.SourcePluginWorkspace.findTtscOverlayDirs(),
286
+ ]);
287
+ // One fixed-effective-environment load owns manifest syntax sharing from the
288
+ // first watch query through metadata and builds. Do not allocate this after
289
+ // watch projection: that silently repeats selected-Go work (#1712).
290
+ const packageReaders = new Map();
291
+ const watchReplacements = options.onWatchInputs === undefined
292
+ ? undefined
293
+ : new Map();
294
+ if (options.onWatchInputs !== undefined) {
295
+ // The prospective context reads the admitted module layout and anchors its
296
+ // tool there before kind exists. Keep both possible final-root baselines:
297
+ // a linked package keeps packageDir, and an executable keeps moduleRoot.
298
+ const interests = new Set(candidates
299
+ .flatMap((candidate) => [
300
+ candidate.moduleRoot,
301
+ candidate.packageDir,
302
+ ...(candidate.contributors ?? []).map((input) => input.source),
303
+ ])
304
+ .filter(reportsPluginSource));
305
+ // WARNING (#1728): hand the watch owner this load's store with the roots.
306
+ // It baselines each root before any build reads it; without the store it
307
+ // read every source the load proves from records a moment later.
308
+ // A worker owns the watch baseline now. Complete this handoff before Go
309
+ // metadata or source proof consumes the roots, preserving #1728 (#1758).
310
+ yield options.onWatchInputs([...interests].sort(), sourceIdentities);
311
+ const initialSize = interests.size;
312
+ for (const candidate of candidates) {
313
+ // Nested source-only proposals replace the outer root manifests. Do not
314
+ // activate their irrelevant module graph just to discover watch inputs.
315
+ if (candidate.stage === "transform" && candidate.entry !== ".")
316
+ continue;
317
+ for (const directory of pluginReplacementWatchDirectories(candidate.moduleRoot, effectiveEnv, watchReplacements, packageReaders, sourceIdentities))
318
+ if (reportsPluginSource(directory))
319
+ interests.add(directory);
320
+ }
321
+ if (interests.size !== initialSize)
322
+ yield options.onWatchInputs([...interests].sort(), sourceIdentities);
350
323
  }
351
- for (const record of transformHosts)
352
- contexts.get(record.moduleRoot).records.push(record);
353
- for (const host of contexts.values()) {
354
- const moduleHosts = host.records;
355
- const hostEntries = moduleHosts.map((record) => ({
356
- entry: (0, resolvePluginGoModule_1.resolvePluginGoModule)(record.source, record.label).entry,
357
- }));
358
- const observations = NativeSourcePackages_1.NativeSourcePackages.inspect({
359
- source: host.source,
360
- pluginName: host.label,
324
+ const proposals = NativeSourcePackages_1.NativeSourcePackages.propose(candidates.map((candidate) => ({
325
+ ...candidate,
326
+ ownModule: candidate.stage !== "transform",
327
+ })), effectiveEnv, packageReaders, sourceIdentities, readings);
328
+ const records = candidates.map(({ plugin, index, stage, contributors, source, packageDir }) => {
329
+ const { kind, moduleRoot } = (0, resolveNativeSource_1.resolveNativeSource)(source, plugin, entries[index].config, index, {
361
330
  env: effectiveEnv,
362
- readers: packageReaders,
363
- identities: sourceIdentities,
364
- readings,
365
- packages: [
366
- ...hostEntries,
367
- ...linkedContributors.map((input) => ({
368
- ...input,
369
- entry: `./contrib/${input.name}`,
370
- })),
371
- ],
372
- });
373
- moduleHosts.forEach((record, index) => {
374
- NativeSourcePackages_1.NativeSourcePackages.kind(observations[index], record.label, "executable");
375
- admittedExecutables.add(record.source);
331
+ observation: proposals[index].observation,
376
332
  });
377
- linkedContributors.forEach((input, index) => {
378
- NativeSourcePackages_1.NativeSourcePackages.kind(observations[hostEntries.length + index], `${input.name} inside host ${host.label}`, "linked");
379
- });
380
- }
381
- }
382
- // A nested main proposal still needs its actual owning manifests. Hosts
383
- // with linked contributors were admitted together above; other equivalent
384
- // entries share one standalone observation, with no repeated host copy.
385
- const standaloneCandidates = executableCandidates.filter((candidate) => !admittedExecutables.has(candidate.source));
386
- const executablePackages = NativeSourcePackages_1.NativeSourcePackages.ownPackages(standaloneCandidates, effectiveEnv, packageReaders, sourceIdentities, readings);
387
- standaloneCandidates.forEach((candidate, index) => {
388
- NativeSourcePackages_1.NativeSourcePackages.kind(executablePackages[index], candidate.label, "executable");
389
- });
390
- const packageOwnership = (source, label, linked) => [
391
- {
392
- entry: (0, resolvePluginGoModule_1.resolvePluginGoModule)(source, label).entry,
393
- kind: "executable",
394
- },
395
- ...(linked
396
- ? linkedContributors.map((input) => ({
397
- entry: `./contrib/${input.name}`,
398
- kind: "linked",
399
- }))
400
- : []),
401
- ];
402
- const builtTransformHosts = new Map();
403
- for (const record of transformHosts) {
404
- builtTransformHosts.set(record, (0, buildSourcePlugin_1.buildSourcePlugin)({
405
- baseDir: context.projectRoot,
406
- cacheDir: options.cacheDir,
407
- contributors: mergeContributors(record.contributors, hostContributors),
408
- env: effectiveEnv,
409
- goModReaders: packageReaders,
410
- identities: sourceIdentities,
411
- pluginName: record.label,
412
- packageOwnership: packageOwnership(record.source, record.label, true),
413
- source: record.source,
414
- environmentDigests,
415
- environmentWitnesses,
416
- sourceDigests,
417
- ttscVersion,
418
- tsgoVersion,
419
- }));
420
- }
421
- const fallbackDriverHost = transformHosts.length === 0 && linkedContributors.length !== 0
422
- ? (0, buildSourcePlugin_1.buildSourcePlugin)({
423
- baseDir: context.projectRoot,
424
- cacheDir: options.cacheDir,
425
- contributors: linkedContributors,
426
- env: effectiveEnv,
427
- goModReaders: packageReaders,
428
- identities: sourceIdentities,
429
- label: "linked plugin host",
430
- pluginName: "linked-plugin-host",
431
- source: node_path_1.default.join(ttscPackageRoot(), "cmd", "utility-host"),
432
- packageOwnership: packageOwnership(node_path_1.default.join(ttscPackageRoot(), "cmd", "utility-host"), "linked-plugin-host", true),
433
- environmentDigests,
434
- environmentWitnesses,
435
- sourceDigests,
436
- ttscVersion,
437
- tsgoVersion,
438
- })
439
- : undefined;
440
- const selectedTransformHost = transformHosts.length === 0
441
- ? fallbackDriverHost
442
- : builtTransformHosts.get(transformHosts[0]);
443
- const nativePlugins = records.map((record) => {
444
- const binary = record.stage === "transform" && record.kind === "linked"
445
- ? selectedTransformHost
446
- : record.stage === "transform"
447
- ? builtTransformHosts.get(record)
448
- : (0, buildSourcePlugin_1.buildSourcePlugin)({
449
- baseDir: context.projectRoot,
450
- cacheDir: options.cacheDir,
451
- contributors: record.contributors,
333
+ if (kind === "linked" && stage !== "transform") {
334
+ throw new Error(`ttsc: plugin "${(0, pluginLabel_1.pluginLabel)(plugin, entries[index].config, index)}" source is a linked Go package, but only transform-stage plugins can be linked into a compiler host`);
335
+ }
336
+ const linkedContributorName = kind === "linked"
337
+ ? `linked_${String(index).padStart(6, "0")}`
338
+ : undefined;
339
+ const hostInputs = validatePluginHostInputs(plugin, index);
340
+ const pluginHostInputHashes = validatePluginHostInputHashes(plugin, index, hostInputs);
341
+ const pluginHostInputRealpaths = validatePluginHostInputRealpaths(plugin, index, hostInputs);
342
+ return {
343
+ capabilities: plugin.capabilities,
344
+ contributors,
345
+ config: entries[index].config,
346
+ kind,
347
+ label: (0, pluginLabel_1.pluginLabel)(plugin, entries[index].config, index),
348
+ linkedContributorName,
349
+ name: plugin.name,
350
+ reportsTypeScriptDiagnostics: plugin.reportsTypeScriptDiagnostics === true,
351
+ request: loadedEntries[index].request,
352
+ hostInputHashes: mergePluginHostInputHashes(loadedEntries[index].hostInputHashes, pluginHostInputHashes, loadedEntries[index].hostInputs, hostInputs),
353
+ hostInputRealpaths: mergePluginHostInputHashes(loadedEntries[index].hostInputRealpaths, pluginHostInputRealpaths, loadedEntries[index].hostInputs, hostInputs),
354
+ hostInputs: [...loadedEntries[index].hostInputs, ...hostInputs],
355
+ moduleRoot,
356
+ packageDir,
357
+ source,
358
+ stage,
359
+ };
360
+ });
361
+ const executableCandidates = candidates.filter((candidate) => records[candidate.index].kind === "executable" &&
362
+ !proposals[candidate.index].ownModule);
363
+ const buildDirectories = pluginBuildDirectories(records, effectiveEnv, watchReplacements, packageReaders, sourceIdentities);
364
+ // Admission can now narrow the provisional interests without restamping the
365
+ // retained package/module baselines. Final host errors still keep these roots.
366
+ yield options.onWatchInputs?.(buildDirectories, sourceIdentities);
367
+ const keyedDirectories = new Set(buildDirectories);
368
+ const linkedContributors = records
369
+ .filter((record) => record.stage === "transform")
370
+ .flatMap((record) => record.kind === "linked"
371
+ ? [{ name: record.linkedContributorName, source: record.source }]
372
+ : []);
373
+ const transformHosts = records.filter((record) => record.stage === "transform" && record.kind === "executable");
374
+ const hostContributors = linkedContributors.length === 0 ? undefined : linkedContributors;
375
+ // Every host compiles the linked sources under its own module/tool context.
376
+ // Equivalent entries in one module share this metadata command and reader;
377
+ // a second executable host can select a genuinely different Go context.
378
+ const admittedExecutables = new Set();
379
+ if (linkedContributors.length !== 0) {
380
+ const hosts = transformHosts.length === 0
381
+ ? [
382
+ {
383
+ source: node_path_1.default.join(ttscPackageRoot(), "cmd", "utility-host"),
384
+ label: "linked-plugin-host",
385
+ },
386
+ ]
387
+ : transformHosts;
388
+ const contexts = new Map();
389
+ for (const host of hosts) {
390
+ const moduleRoot = (0, resolvePluginGoModule_1.resolvePluginGoModule)(host.source, host.label).moduleRoot;
391
+ let context = contexts.get(moduleRoot);
392
+ if (context === undefined) {
393
+ context = { source: host.source, label: host.label, records: [] };
394
+ contexts.set(moduleRoot, context);
395
+ }
396
+ }
397
+ for (const record of transformHosts)
398
+ contexts.get(record.moduleRoot).records.push(record);
399
+ for (const host of contexts.values()) {
400
+ const moduleHosts = host.records;
401
+ const hostEntries = moduleHosts.map((record) => ({
402
+ entry: (0, resolvePluginGoModule_1.resolvePluginGoModule)(record.source, record.label).entry,
403
+ }));
404
+ const observations = NativeSourcePackages_1.NativeSourcePackages.inspect({
405
+ source: host.source,
406
+ pluginName: host.label,
452
407
  env: effectiveEnv,
453
- goModReaders: packageReaders,
408
+ readers: packageReaders,
454
409
  identities: sourceIdentities,
455
- pluginName: record.label,
456
- packageOwnership: packageOwnership(record.source, record.label, false),
457
- source: record.source,
458
- environmentDigests,
459
- environmentWitnesses,
460
- sourceDigests,
461
- ttscVersion,
462
- tsgoVersion,
410
+ readings,
411
+ packages: [
412
+ ...hostEntries,
413
+ ...linkedContributors.map((input) => ({
414
+ ...input,
415
+ entry: `./contrib/${input.name}`,
416
+ })),
417
+ ],
418
+ });
419
+ moduleHosts.forEach((record, index) => {
420
+ NativeSourcePackages_1.NativeSourcePackages.kind(observations[index], record.label, "executable");
421
+ admittedExecutables.add(record.source);
463
422
  });
464
- if (binary === undefined) {
465
- throw new Error(`ttsc: plugin "${record.label}" is a linked Go package, but no compiler host is available`);
423
+ linkedContributors.forEach((input, index) => {
424
+ NativeSourcePackages_1.NativeSourcePackages.kind(observations[hostEntries.length + index], `${input.name} inside host ${host.label}`, "linked");
425
+ });
426
+ }
427
+ }
428
+ // A nested main proposal still needs its actual owning manifests. Hosts
429
+ // with linked contributors were admitted together above; other equivalent
430
+ // entries share one standalone observation, with no repeated host copy.
431
+ const standaloneCandidates = executableCandidates.filter((candidate) => !admittedExecutables.has(candidate.source));
432
+ const executablePackages = NativeSourcePackages_1.NativeSourcePackages.ownPackages(standaloneCandidates, effectiveEnv, packageReaders, sourceIdentities, readings);
433
+ standaloneCandidates.forEach((candidate, index) => {
434
+ NativeSourcePackages_1.NativeSourcePackages.kind(executablePackages[index], candidate.label, "executable");
435
+ });
436
+ const packageOwnership = (source, label, linked) => [
437
+ {
438
+ entry: (0, resolvePluginGoModule_1.resolvePluginGoModule)(source, label).entry,
439
+ kind: "executable",
440
+ },
441
+ ...(linked
442
+ ? linkedContributors.map((input) => ({
443
+ entry: `./contrib/${input.name}`,
444
+ kind: "linked",
445
+ }))
446
+ : []),
447
+ ];
448
+ const builtTransformHosts = new Map();
449
+ for (const record of transformHosts) {
450
+ builtTransformHosts.set(record, (0, buildSourcePlugin_1.buildSourcePlugin)({
451
+ baseDir: context.projectRoot,
452
+ cacheDir: options.cacheDir,
453
+ contributors: mergeContributors(record.contributors, hostContributors),
454
+ env: effectiveEnv,
455
+ goModReaders: packageReaders,
456
+ identities: sourceIdentities,
457
+ pluginName: record.label,
458
+ packageOwnership: packageOwnership(record.source, record.label, true),
459
+ source: record.source,
460
+ environmentDigests,
461
+ environmentWitnesses,
462
+ sourceDigests,
463
+ ttscVersion,
464
+ tsgoVersion,
465
+ }));
466
466
  }
467
+ const fallbackDriverHost = transformHosts.length === 0 && linkedContributors.length !== 0
468
+ ? (0, buildSourcePlugin_1.buildSourcePlugin)({
469
+ baseDir: context.projectRoot,
470
+ cacheDir: options.cacheDir,
471
+ contributors: linkedContributors,
472
+ env: effectiveEnv,
473
+ goModReaders: packageReaders,
474
+ identities: sourceIdentities,
475
+ label: "linked plugin host",
476
+ pluginName: "linked-plugin-host",
477
+ source: node_path_1.default.join(ttscPackageRoot(), "cmd", "utility-host"),
478
+ packageOwnership: packageOwnership(node_path_1.default.join(ttscPackageRoot(), "cmd", "utility-host"), "linked-plugin-host", true),
479
+ environmentDigests,
480
+ environmentWitnesses,
481
+ sourceDigests,
482
+ ttscVersion,
483
+ tsgoVersion,
484
+ })
485
+ : undefined;
486
+ const selectedTransformHost = transformHosts.length === 0
487
+ ? fallbackDriverHost
488
+ : builtTransformHosts.get(transformHosts[0]);
489
+ const nativePlugins = records.map((record) => {
490
+ const binary = record.stage === "transform" && record.kind === "linked"
491
+ ? selectedTransformHost
492
+ : record.stage === "transform"
493
+ ? builtTransformHosts.get(record)
494
+ : (0, buildSourcePlugin_1.buildSourcePlugin)({
495
+ baseDir: context.projectRoot,
496
+ cacheDir: options.cacheDir,
497
+ contributors: record.contributors,
498
+ env: effectiveEnv,
499
+ goModReaders: packageReaders,
500
+ identities: sourceIdentities,
501
+ pluginName: record.label,
502
+ packageOwnership: packageOwnership(record.source, record.label, false),
503
+ source: record.source,
504
+ environmentDigests,
505
+ environmentWitnesses,
506
+ sourceDigests,
507
+ ttscVersion,
508
+ tsgoVersion,
509
+ });
510
+ if (binary === undefined) {
511
+ throw new Error(`ttsc: plugin "${record.label}" is a linked Go package, but no compiler host is available`);
512
+ }
513
+ return {
514
+ binary,
515
+ // This fallback is compiled from our utility-host, whose actual writer
516
+ // publishes provenance and supports a separate compiler argument cwd.
517
+ // A separately selected executable host must opt in itself; a linked
518
+ // library cannot certify that foreign host. An undeclared capability
519
+ // only withholds the optional protocol: build execution still runs such
520
+ // a host and observes it from outside, never rejecting it.
521
+ capabilities: record.stage === "transform" &&
522
+ record.kind === "linked" &&
523
+ fallbackDriverHost !== undefined
524
+ ? {
525
+ ...record.capabilities,
526
+ compilerArgsCwd: true,
527
+ emitProvenance: true,
528
+ }
529
+ : record.capabilities,
530
+ config: record.config,
531
+ contributors: record.contributors,
532
+ kind: record.kind,
533
+ name: record.name,
534
+ reportsTypeScriptDiagnostics: record.reportsTypeScriptDiagnostics,
535
+ source: record.source,
536
+ stage: record.stage,
537
+ };
538
+ });
467
539
  return {
468
- binary,
469
- // This fallback is compiled from our utility-host, whose actual writer
470
- // publishes provenance and supports a separate compiler argument cwd.
471
- // A separately selected executable host must opt in itself; a linked
472
- // library cannot certify that foreign host. An undeclared capability
473
- // only withholds the optional protocol: build execution still runs such
474
- // a host and observes it from outside, never rejecting it.
475
- capabilities: record.stage === "transform" &&
476
- record.kind === "linked" &&
477
- fallbackDriverHost !== undefined
478
- ? {
479
- ...record.capabilities,
480
- compilerArgsCwd: true,
481
- emitProvenance: true,
482
- }
483
- : record.capabilities,
484
- config: record.config,
485
- contributors: record.contributors,
486
- kind: record.kind,
487
- name: record.name,
488
- reportsTypeScriptDiagnostics: record.reportsTypeScriptDiagnostics,
489
- source: record.source,
490
- stage: record.stage,
540
+ ...collectHostInputSnapshot(project, context, records, projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
541
+ descriptorReadsDeclared: loadedEntries.every((entry) => entry.observationsComplete && (0, declaresHostInputReads_1.declaresHostInputReads)(entry.plugin)),
542
+ discoveryInputsComplete: project.configInputsComplete === true,
543
+ ...(identities === undefined ? {} : { identities }),
544
+ nativePlugins: orderNativePlugins(nativePlugins),
545
+ ...(loadedEntries.some((entry) => !entry.observationsComplete)
546
+ ? { observationsComplete: false }
547
+ : {}),
548
+ // The directories the watch inputs named before the builds, as the builds
549
+ // read them. Package selection shares these readings and also reads a
550
+ // linked plugin's module root, which no build keys on, so only the build
551
+ // directories are reported.
552
+ pluginSources: Object.fromEntries([...sourceDigests]
553
+ .filter(([directory]) => keyedDirectories.has(directory))
554
+ .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
555
+ .map(([directory, sourceDigest]) => [
556
+ directory,
557
+ (0, pluginSourceState_1.pluginSourceState)(directory, {
558
+ env: effectiveEnv,
559
+ sourceDigest,
560
+ environment: environmentDigests.get(directory) ??
561
+ (0, pluginBuildEnvironment_1.pluginBuildEnvironment)(directory, effectiveEnv, undefined, sourceIdentities),
562
+ }),
563
+ ])),
564
+ project,
491
565
  };
492
- });
493
- return {
494
- ...collectHostInputSnapshot(project, context, records, projectHostInputs, revalidateHostInputHashes(projectHostInputHashes, projectHostInputs), revalidateHostInputRealpaths(projectHostInputRealpaths, projectHostInputs)),
495
- descriptorReadsDeclared: loadedEntries.every((entry) => entry.observationsComplete && (0, declaresHostInputReads_1.declaresHostInputReads)(entry.plugin)),
496
- discoveryInputsComplete: project.configInputsComplete === true,
497
- ...(identities === undefined ? {} : { identities }),
498
- nativePlugins: orderNativePlugins(nativePlugins),
499
- ...(loadedEntries.some((entry) => !entry.observationsComplete)
500
- ? { observationsComplete: false }
501
- : {}),
502
- // The directories the watch inputs named before the builds, as the builds
503
- // read them. Package selection shares these readings and also reads a
504
- // linked plugin's module root, which no build keys on, so only the build
505
- // directories are reported.
506
- pluginSources: Object.fromEntries([...sourceDigests]
507
- .filter(([directory]) => keyedDirectories.has(directory))
508
- .sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0))
509
- .map(([directory, sourceDigest]) => [
510
- directory,
511
- (0, pluginSourceState_1.pluginSourceState)(directory, {
512
- env: effectiveEnv,
513
- sourceDigest,
514
- environment: environmentDigests.get(directory) ??
515
- (0, pluginBuildEnvironment_1.pluginBuildEnvironment)(directory, effectiveEnv, undefined, sourceIdentities),
516
- }),
517
- ])),
518
- project,
519
- };
520
- }
566
+ }
567
+ loadProjectPlugins.steps = steps;
568
+ })(loadProjectPlugins || (exports.loadProjectPlugins = loadProjectPlugins = {}));
521
569
  /**
522
570
  * Collect exact JavaScript-host files that universally influence the loaded
523
571
  * transform. Program files belong to the native reference graph; this list is
@@ -598,16 +646,16 @@ function collectHostInputSnapshot(project, context, records, baselineInputs, bas
598
646
  * cache proof for the ambiguous snapshot.
599
647
  */
600
648
  function readProjectHostInputSnapshot(options) {
601
- const { includePluginDiscovery, ...projectOptions } = options;
649
+ const { includePluginDiscovery, pluginConfigDir, ...projectOptions } = options;
602
650
  let project = (0, readProjectConfig_1.readProjectConfig)(projectOptions);
603
- let hostInputs = (0, collectProjectHostInputs_1.collectProjectHostInputs)(project, includePluginDiscovery);
651
+ let hostInputs = (0, collectProjectHostInputs_1.collectProjectHostInputs)(project, includePluginDiscovery, pluginConfigDir);
604
652
  const observedInputs = new Set(hostInputs);
605
653
  for (let attempt = 0; attempt < 3; attempt++) {
606
654
  const before = (0, hashHostInputPaths_1.hashHostInputPaths)(hostInputs);
607
655
  const beforeRealpaths = (0, realpathHostInputPaths_1.realpathHostInputPaths)(hostInputs);
608
656
  const beforeSignatures = hostInputMetadataSignatures(hostInputs);
609
657
  const candidateProject = (0, readProjectConfig_1.readProjectConfig)(projectOptions);
610
- const candidateInputs = (0, collectProjectHostInputs_1.collectProjectHostInputs)(candidateProject, includePluginDiscovery);
658
+ const candidateInputs = (0, collectProjectHostInputs_1.collectProjectHostInputs)(candidateProject, includePluginDiscovery, pluginConfigDir);
611
659
  for (const input of candidateInputs)
612
660
  observedInputs.add(input);
613
661
  const after = (0, hashHostInputPaths_1.hashHostInputPaths)(candidateInputs);
@@ -928,7 +976,7 @@ function loadPluginDescriptor(request, context, effectiveEnv, descriptorCache) {
928
976
  !error.retryWithTtsx) {
929
977
  throw error;
930
978
  }
931
- const loaded = loadDescriptorViaTtsx(request, context, effectiveEnv, descriptorCache.identities);
979
+ const loaded = loadDescriptorViaTtsx(request, context, effectiveEnv, descriptorCache);
932
980
  if (loaded === undefined) {
933
981
  throw error;
934
982
  }
@@ -965,17 +1013,19 @@ function loadCommonJsDescriptor(request, context, loadEnv, descriptorCache) {
965
1013
  const effectiveEnv = TtsxNodeOptions_1.TtsxNodeOptions.forHelpers(loadEnv);
966
1014
  const runtime = pluginDescriptorRuntimeBinary(effectiveEnv);
967
1015
  const ttsx = effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY;
968
- const runtimeCapabilities = (0, javascriptRuntimeCapabilities_1.javascriptRuntimeCapabilities)(runtime, effectiveEnv, context.projectRoot, descriptorCache.identities);
1016
+ const runtimeCapabilities = (0, javascriptRuntimeCapabilities_1.javascriptRuntimeCapabilities)(runtime, effectiveEnv, descriptorCache.projectRoot, descriptorCache.identities);
969
1017
  const node = !runtimeCapabilities.bun &&
970
1018
  runtimeCapabilities.registerHooks &&
971
1019
  runtimeCapabilities.executable !== undefined
972
1020
  ? runtimeCapabilities.executable
973
- : (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, context.projectRoot, descriptorCache.identities);
1021
+ : (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, descriptorCache.projectRoot, descriptorCache.identities);
974
1022
  const cacheAuthority = {
975
1023
  additionalRuntime: node,
976
1024
  cacheDir: descriptorCache.cacheDir,
977
1025
  identities: descriptorCache.identities,
978
1026
  context,
1027
+ // Factory context anchors the consumer; the evaluator runs at the source root.
1028
+ cwd: descriptorCache.projectRoot,
979
1029
  // Everything the child receives besides the per-evaluation output paths.
980
1030
  env: {
981
1031
  ...effectiveEnv,
@@ -983,7 +1033,7 @@ function loadCommonJsDescriptor(request, context, loadEnv, descriptorCache) {
983
1033
  ...descriptorRecordRoot(descriptorCache.identities),
984
1034
  TTSC_TTSX_BINARY: ttsx,
985
1035
  },
986
- projectRoot: context.projectRoot,
1036
+ projectRoot: descriptorCache.projectRoot,
987
1037
  request,
988
1038
  runtime,
989
1039
  version: descriptorCache.version,
@@ -1049,7 +1099,7 @@ function loadCommonJsDescriptor(request, context, loadEnv, descriptorCache) {
1049
1099
  ...(runtimeCapabilities.bun ? [] : ["--input-type", ""]),
1050
1100
  shim,
1051
1101
  ], {
1052
- cwd: context.projectRoot,
1102
+ cwd: descriptorCache.projectRoot,
1053
1103
  env: {
1054
1104
  ...effectiveEnv,
1055
1105
  // The direct evaluator may be Bun, but ttsx and native config
@@ -1312,11 +1362,11 @@ const TS_SOURCE_PATTERN = /\.(?:[cm]?ts|tsx)$/i;
1312
1362
  * shim with plugins disabled across the whole graph. Returns `undefined` when
1313
1363
  * `ttsx` is unavailable, so the caller can rethrow the original load error.
1314
1364
  */
1315
- function loadDescriptorViaTtsx(request, context, effectiveEnv, identities) {
1365
+ function loadDescriptorViaTtsx(request, context, effectiveEnv, descriptorCache) {
1316
1366
  // Binary discovery prefers the instance environment, then the ambient
1317
1367
  // process.env (where `withPluginLoaderEnv` injects ttsc's own node/ttsx paths
1318
1368
  // just before this runs), then the running interpreter.
1319
- const node = (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, context.projectRoot, identities);
1369
+ const node = (0, resolveNodeBinary_1.resolveNodeBinary)(effectiveEnv, descriptorCache.projectRoot, descriptorCache.identities);
1320
1370
  const ttsx = effectiveEnv.TTSC_TTSX_BINARY ?? process.env.TTSC_TTSX_BINARY;
1321
1371
  if (node === undefined || ttsx === undefined || ttsx.length === 0) {
1322
1372
  return undefined;
@@ -1337,9 +1387,9 @@ function loadDescriptorViaTtsx(request, context, effectiveEnv, identities) {
1337
1387
  },
1338
1388
  }));
1339
1389
  node_fs_1.default.writeFileSync(shim, PLUGIN_DESCRIPTOR_SHIM_SOURCE_1.PLUGIN_DESCRIPTOR_SHIM_SOURCE);
1340
- const trace = E2ETrace_1.E2ETrace.begin(node, [ttsx, "--no-plugins", shim], { cwd: context.projectRoot }, "plugin-descriptor");
1390
+ const trace = E2ETrace_1.E2ETrace.begin(node, [ttsx, "--no-plugins", shim], { cwd: descriptorCache.projectRoot }, "plugin-descriptor");
1341
1391
  const result = spawnOwnedDescriptor(node, [ttsx, "--no-plugins", shim], {
1342
- cwd: context.projectRoot,
1392
+ cwd: descriptorCache.projectRoot,
1343
1393
  encoding: "utf8",
1344
1394
  env: {
1345
1395
  ...effectiveEnv,
@@ -1347,7 +1397,7 @@ function loadDescriptorViaTtsx(request, context, effectiveEnv, identities) {
1347
1397
  // may recurse into further descriptor loads) finds them even when the
1348
1398
  // instance-env snapshot predates `withPluginLoaderEnv`.
1349
1399
  TTSC_NODE_BINARY: node,
1350
- ...descriptorRecordRoot(identities),
1400
+ ...descriptorRecordRoot(descriptorCache.identities),
1351
1401
  TTSC_TTSX_BINARY: ttsx,
1352
1402
  TTSC_PLUGIN_CONTEXT: JSON.stringify({
1353
1403
  binary: context.binary,