ttsc 0.18.3 → 0.19.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 (173) hide show
  1. package/README.md +5 -6
  2. package/cmd/graphbench/main.go +1 -1
  3. package/cmd/platform/main.go +1 -48
  4. package/cmd/ttsc/build_branches_test.go +5 -1
  5. package/cmd/ttsc/main.go +0 -50
  6. package/cmd/ttsc/main_alias_branches_test.go +6 -12
  7. package/cmd/ttscgraph/dump.go +13 -3
  8. package/cmd/ttscgraph/main.go +18 -12
  9. package/cmd/ttscgraph/run_help_version_and_bad_flags_exit_codes_test.go +23 -5
  10. package/cmd/ttscgraph/serve.go +863 -0
  11. package/cmd/ttscgraph/serve_protocol_rejects_invalid_request_line_test.go +43 -0
  12. package/cmd/ttscgraph/serve_protocol_retries_session_creation_test.go +83 -0
  13. package/cmd/ttscgraph/serve_protocol_reuses_session_test.go +35 -0
  14. package/cmd/ttscgraph/serve_session_applies_source_edit_incrementally_test.go +55 -0
  15. package/cmd/ttscgraph/serve_session_fails_closed_on_deleted_config_test.go +68 -0
  16. package/cmd/ttscgraph/serve_session_fails_closed_on_invalid_config_test.go +50 -0
  17. package/cmd/ttscgraph/serve_session_keeps_missing_literal_root_unchanged_test.go +46 -0
  18. package/cmd/ttscgraph/serve_session_rebuilds_import_graph_change_test.go +60 -0
  19. package/cmd/ttscgraph/serve_session_reloads_created_literal_root_test.go +44 -0
  20. package/cmd/ttscgraph/serve_session_reloads_extended_config_change_test.go +45 -0
  21. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_conditional_export_test.go +60 -0
  22. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_config_types_test.go +44 -0
  23. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_excluded_import_test.go +62 -0
  24. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_package_export_test.go +46 -0
  25. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_package_import_test.go +46 -0
  26. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_path_alias_test.go +47 -0
  27. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_reference_path_test.go +42 -0
  28. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_root_dirs_target_test.go +46 -0
  29. package/cmd/ttscgraph/serve_session_reloads_newly_resolved_type_reference_test.go +42 -0
  30. package/cmd/ttscgraph/serve_session_reloads_root_file_set_test.go +52 -0
  31. package/cmd/ttscgraph/serve_session_reloads_source_deleted_during_load_test.go +60 -0
  32. package/cmd/ttscgraph/serve_session_reloads_transitive_project_reference_test.go +55 -0
  33. package/cmd/ttscgraph/serve_session_reloads_when_program_plugin_linked_test.go +53 -0
  34. package/cmd/ttscgraph/serve_session_reuses_unchanged_preamble_source_test.go +45 -0
  35. package/cmd/ttscgraph/serve_session_reuses_unchanged_snapshot_test.go +52 -0
  36. package/cmd/ttscgraph/serve_session_revisits_source_edited_during_load_test.go +61 -0
  37. package/cmd/ttscgraph/serve_session_tolerates_os_invalid_resolution_candidate_test.go +56 -0
  38. package/cmd/ttscgraph/serve_session_tracks_all_module_specifier_forms_test.go +41 -0
  39. package/cmd/ttscgraph/serve_session_tracks_diamond_project_references_once_test.go +61 -0
  40. package/cmd/ttscserver/main.go +5 -3
  41. package/driver/lsp.go +13 -1
  42. package/driver/plugins.go +56 -5
  43. package/driver/program.go +25 -10
  44. package/driver/rewrite.go +381 -21
  45. package/driver/session.go +11 -3
  46. package/go.mod +1 -0
  47. package/internal/graph/build.go +297 -3
  48. package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +16 -2
  49. package/internal/graph/diagnostics.go +5 -4
  50. package/internal/graph/dump.go +60 -4
  51. package/internal/graph/dump_is_a_function_of_the_source_test.go +71 -0
  52. package/internal/graph/dump_serializes_the_full_graph_test.go +16 -4
  53. package/internal/graph/edges.go +122 -11
  54. package/internal/graph/exports.go +74 -17
  55. package/internal/graph/graph.go +33 -1
  56. package/internal/graph/node_identity_is_position_invariant_test.go +4 -2
  57. package/internal/graph/resolve.go +24 -0
  58. package/internal/graphsymbols/provider.go +50 -14
  59. package/internal/lspserver/lsp_native_plugin_source.go +77 -23
  60. package/internal/lspserver/lsp_plugin.go +19 -2
  61. package/internal/lspserver/lsp_proxy.go +244 -52
  62. package/internal/lspserver/lsp_server.go +35 -27
  63. package/internal/lspserver/lsp_symbols.go +25 -16
  64. package/lib/TtscCompiler.d.ts +13 -2
  65. package/lib/TtscCompiler.js +30 -20
  66. package/lib/TtscCompiler.js.map +1 -1
  67. package/lib/TtscService.js +9 -7
  68. package/lib/TtscService.js.map +1 -1
  69. package/lib/compiler/internal/compileProjectInMemory.js +15 -1
  70. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  71. package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +3 -0
  72. package/lib/compiler/internal/project/createNativeProjectContextArgs.js +14 -0
  73. package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -0
  74. package/lib/compiler/internal/project/readProjectConfig.js +53 -4
  75. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  76. package/lib/compiler/internal/project/resolveProjectConfig.d.ts +9 -2
  77. package/lib/compiler/internal/project/resolveProjectConfig.js +45 -16
  78. package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
  79. package/lib/compiler/internal/residentTransformProcess.d.ts +16 -5
  80. package/lib/compiler/internal/residentTransformProcess.js +68 -20
  81. package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
  82. package/lib/compiler/internal/runBuild.d.ts +18 -0
  83. package/lib/compiler/internal/runBuild.js +220 -24
  84. package/lib/compiler/internal/runBuild.js.map +1 -1
  85. package/lib/compiler/internal/runSingleFileEmit.js +7 -4
  86. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  87. package/lib/compiler/internal/sharedHostHelpers.d.ts +16 -0
  88. package/lib/compiler/internal/sharedHostHelpers.js +23 -0
  89. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  90. package/lib/compiler/internal/startResidentTransform.js +18 -1
  91. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  92. package/lib/compiler/internal/transformProjectInMemory.js +34 -8
  93. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  94. package/lib/flags/parser.d.ts +17 -0
  95. package/lib/flags/parser.js +11 -1
  96. package/lib/flags/parser.js.map +1 -1
  97. package/lib/flags/schema.js +7 -0
  98. package/lib/flags/schema.js.map +1 -1
  99. package/lib/launcher/internal/prepareExecution.js +21 -2
  100. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  101. package/lib/launcher/internal/runTtsc.js +13 -53
  102. package/lib/launcher/internal/runTtsc.js.map +1 -1
  103. package/lib/launcher/internal/runTtscserver.js +11 -0
  104. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  105. package/lib/launcher/internal/runTtsx.js +27 -11
  106. package/lib/launcher/internal/runTtsx.js.map +1 -1
  107. package/lib/launcher/internal/runtimeHooks.d.ts +81 -0
  108. package/lib/launcher/internal/runtimeHooks.js +632 -43
  109. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  110. package/lib/launcher/internal/servedSourceMap.d.ts +25 -0
  111. package/lib/launcher/internal/servedSourceMap.js +164 -0
  112. package/lib/launcher/internal/servedSourceMap.js.map +1 -0
  113. package/lib/plugin/internal/buildSourcePlugin.d.ts +146 -2
  114. package/lib/plugin/internal/buildSourcePlugin.js +651 -113
  115. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  116. package/lib/plugin/internal/loadProjectPlugins.d.ts +7 -0
  117. package/lib/plugin/internal/loadProjectPlugins.js +33 -10
  118. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  119. package/lib/structures/ITtscCompilerContext.d.ts +16 -2
  120. package/lib/structures/ITtscCompilerResult.d.ts +8 -5
  121. package/lib/structures/ITtscPluginCapabilities.d.ts +8 -0
  122. package/lib/structures/ITtscPluginFactoryContext.d.ts +14 -0
  123. package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +3 -0
  124. package/lib/structures/internal/ITtscProjectIdentity.d.ts +17 -0
  125. package/lib/structures/internal/ITtscProjectIdentity.js +3 -0
  126. package/lib/structures/internal/ITtscProjectIdentity.js.map +1 -0
  127. package/lib/structures/internal/TtscCommonOptions.d.ts +8 -0
  128. package/package.json +11 -8
  129. package/shim/ast/lint.go +10 -0
  130. package/shim/ast/shim.go +44 -0
  131. package/shim/checker/enums_gen.go +19 -0
  132. package/shim/checker/shim.go +130 -0
  133. package/shim/diagnosticwriter/lint.go +30 -3
  134. package/shim/printer/shim.go +22 -4
  135. package/shim/scanner/regexp.go +27 -0
  136. package/src/TtscCompiler.ts +29 -22
  137. package/src/TtscService.ts +15 -7
  138. package/src/compiler/internal/compileProjectInMemory.ts +18 -1
  139. package/src/compiler/internal/project/createNativeProjectContextArgs.ts +15 -0
  140. package/src/compiler/internal/project/readProjectConfig.ts +61 -5
  141. package/src/compiler/internal/project/resolveProjectConfig.ts +51 -21
  142. package/src/compiler/internal/residentTransformProcess.ts +95 -21
  143. package/src/compiler/internal/runBuild.ts +313 -32
  144. package/src/compiler/internal/runSingleFileEmit.ts +7 -4
  145. package/src/compiler/internal/sharedHostHelpers.ts +24 -0
  146. package/src/compiler/internal/startResidentTransform.ts +21 -1
  147. package/src/compiler/internal/transformProjectInMemory.ts +49 -6
  148. package/src/flags/parser.ts +28 -1
  149. package/src/flags/schema.ts +8 -0
  150. package/src/launcher/internal/prepareExecution.ts +22 -2
  151. package/src/launcher/internal/runTtsc.ts +13 -61
  152. package/src/launcher/internal/runTtscserver.ts +14 -0
  153. package/src/launcher/internal/runTtsx.ts +28 -13
  154. package/src/launcher/internal/runtimeHooks.ts +771 -53
  155. package/src/launcher/internal/servedSourceMap.ts +183 -0
  156. package/src/plugin/internal/buildSourcePlugin.ts +859 -122
  157. package/src/plugin/internal/loadProjectPlugins.ts +36 -6
  158. package/src/structures/ITtscCompilerContext.ts +17 -2
  159. package/src/structures/ITtscCompilerResult.ts +8 -5
  160. package/src/structures/ITtscPluginCapabilities.ts +9 -0
  161. package/src/structures/ITtscPluginFactoryContext.ts +15 -0
  162. package/src/structures/internal/ITtscParsedProjectConfig.ts +3 -0
  163. package/src/structures/internal/ITtscProjectIdentity.ts +17 -0
  164. package/src/structures/internal/TtscCommonOptions.ts +8 -0
  165. package/tools/shim_audit/baseline.json +16 -7
  166. package/tools/shim_audit/main.go +777 -71
  167. package/tools/shim_audit/main_test.go +28 -1
  168. package/tools/shim_audit/producer_exemptions_require_rationale_test.go +42 -0
  169. package/tools/shim_audit/producer_surface_includes_exposed_methods_test.go +154 -0
  170. package/tools/shim_audit/producer_surface_rejects_unproducible_reference_test.go +142 -0
  171. package/utility/host.go +46 -20
  172. package/utility/serve.go +1 -1
  173. package/shim/checker/signature_api_completeness.go +0 -58
@@ -7,15 +7,18 @@ import {
7
7
  } from "../../plugin/internal/loadProjectPlugins";
8
8
  import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiagnostic";
9
9
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
10
+ import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
10
11
  import type { TtscBuildOptions } from "../../structures/internal/TtscBuildOptions";
11
12
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
12
13
  import type { TtscCommonOptions } from "../../structures/internal/TtscCommonOptions";
14
+ import { createNativeProjectContextArgs } from "./project/createNativeProjectContextArgs";
13
15
  import { readProjectConfig } from "./project/readProjectConfig";
14
16
  import { resolveBinary } from "./resolveBinary";
15
17
  import { resolveTsgo } from "./resolveTsgo";
16
18
  import {
17
19
  assertSharedHostCompatibility,
18
20
  linkedTransformPlugins,
21
+ resolvePluginConfigDir,
19
22
  selectSharedHostPlugin,
20
23
  } from "./sharedHostHelpers";
21
24
  import { outputText, spawnNative } from "./spawnNative";
@@ -23,6 +26,16 @@ import { outputText, spawnNative } from "./spawnNative";
23
26
  type RunBuildOptions = TtscBuildOptions & {
24
27
  skipDiagnosticsCheck?: boolean;
25
28
  forceListEmittedFiles?: boolean;
29
+ /**
30
+ * Emit an external source map from the direct tsgo build lane even when the
31
+ * project configures none. Set by the ttsx runtime builds so a served emit
32
+ * carries a map to inline under the source URL (issue #353). Applied only to
33
+ * the plain tsgo emit — never forwarded to a native plugin host, whose own
34
+ * emit honours the project's `sourceMap` setting.
35
+ */
36
+ forceRuntimeSourceMap?: boolean;
37
+ /** Retain an already selected project's lexical identity across API lanes. */
38
+ resolvedProject?: ITtscParsedProjectConfig;
26
39
  };
27
40
 
28
41
  type BuildTiming = {
@@ -119,9 +132,13 @@ function formatTimingSeconds(ms: number): string {
119
132
 
120
133
  /**
121
134
  * Build the environment for a native plugin spawn. Injects `TTSC_TSGO_BINARY`
122
- * and `TTSC_TTSX_BINARY` alongside the base env from `mergeEnv`. For
123
- * transform-stage plugins, also passes `TTSC_LINKED_PLUGINS_JSON` containing
124
- * any linked sources so they run inside the same process as the host plugin.
135
+ * and `TTSC_TTSX_BINARY` alongside the base env from `mergeEnv`, plus
136
+ * `TTSC_PLUGIN_CONFIG_DIR` when the caller declared a plugin config anchor (an
137
+ * embedder compiling through a generated wrapper tsconfig) so config-file
138
+ * discovery walks the real project instead of the wrapper's temp-dir ancestry.
139
+ * For transform-stage plugins, also passes `TTSC_LINKED_PLUGINS_JSON`
140
+ * containing any linked sources so they run inside the same process as the host
141
+ * plugin.
125
142
  */
126
143
  function nativePluginEnv(
127
144
  extra: NodeJS.ProcessEnv | undefined,
@@ -129,12 +146,25 @@ function nativePluginEnv(
129
146
  plugin?: ITtscLoadedNativePlugin,
130
147
  ): NodeJS.ProcessEnv {
131
148
  const env = mergeEnv({
149
+ ...(execution.pluginConfigDir === undefined
150
+ ? {}
151
+ : { TTSC_PLUGIN_CONFIG_DIR: execution.pluginConfigDir }),
132
152
  TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? execution.tsgo.binary,
133
153
  TTSC_TTSX_BINARY:
134
154
  process.env.TTSC_TTSX_BINARY ??
135
155
  path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
136
156
  ...extra,
137
157
  });
158
+ // The anchor is per-invocation state owned by this host: when this run
159
+ // declared none (and the caller's env does not name one), drop any value
160
+ // inherited from an ancestor ttsc process so a nested build never
161
+ // mis-anchors its plugins at the outer project.
162
+ if (
163
+ execution.pluginConfigDir === undefined &&
164
+ extra?.TTSC_PLUGIN_CONFIG_DIR === undefined
165
+ ) {
166
+ delete env.TTSC_PLUGIN_CONFIG_DIR;
167
+ }
138
168
  if (plugin?.stage === "transform") {
139
169
  const linked = linkedTransformPlugins(execution.nativePlugins);
140
170
  if (linked.length !== 0) {
@@ -160,17 +190,31 @@ function runBuildTimed(
160
190
  ): TtscBuildResult {
161
191
  const setupStartedAt = process.hrtime.bigint();
162
192
  const execution = resolveExecutionContext(options);
163
- if (execution.nativePlugins.length > 0) {
193
+ if (
194
+ execution.nativePlugins.length > 0 ||
195
+ execution.pluginSetupFailure !== undefined
196
+ ) {
164
197
  recordTiming(timing, "ttsc plugin setup time", setupStartedAt);
165
198
  }
166
199
  const buildOptions = applyProjectNoEmit(options, execution);
200
+ if (execution.pluginSetupFailure !== undefined) {
201
+ return appendTypeScriptDiagnosticsAfterPluginFailure(
202
+ execution.pluginSetupFailure,
203
+ buildOptions,
204
+ execution,
205
+ );
206
+ }
167
207
  if (execution.nativePlugins.length > 0) {
168
208
  const compilers = execution.nativePlugins.filter(
169
209
  (plugin) => plugin.stage === "transform",
170
210
  );
171
211
  const checked = runNativeCheckPlugins(buildOptions, execution, timing);
172
212
  if (checked.status !== 0) {
173
- return checked;
213
+ return appendTypeScriptDiagnosticsAfterPluginFailure(
214
+ checked,
215
+ buildOptions,
216
+ execution,
217
+ );
174
218
  }
175
219
 
176
220
  if (buildOptions.emit === false) {
@@ -191,15 +235,20 @@ function runBuildTimed(
191
235
  }
192
236
  if (compilers.length !== 0) {
193
237
  assertSharedHostCompatibility(compilers, "emit");
194
- return appendBuildOutput(
195
- checked,
196
- buildWithNativeCompilerPlugins(
197
- buildOptions,
198
- execution,
199
- compilers,
200
- timing,
201
- ),
238
+ const compiled = buildWithNativeCompilerPlugins(
239
+ buildOptions,
240
+ execution,
241
+ compilers,
242
+ timing,
202
243
  );
244
+ const result = appendBuildOutput(checked, compiled);
245
+ return compiled.status === 0
246
+ ? result
247
+ : appendTypeScriptDiagnosticsAfterPluginFailure(
248
+ result,
249
+ buildOptions,
250
+ execution,
251
+ );
203
252
  }
204
253
  if (checkPluginsReportTypeScriptDiagnostics(execution.nativePlugins)) {
205
254
  return checked;
@@ -213,15 +262,20 @@ function runBuildTimed(
213
262
  let result: TtscBuildResult;
214
263
  if (compilers.length !== 0) {
215
264
  assertSharedHostCompatibility(compilers, "emit");
216
- result = appendBuildOutput(
217
- checked,
218
- buildWithNativeCompilerPlugins(
265
+ const compiled = buildWithNativeCompilerPlugins(
266
+ buildOptions,
267
+ execution,
268
+ compilers,
269
+ timing,
270
+ );
271
+ result = appendBuildOutput(checked, compiled);
272
+ if (compiled.status !== 0) {
273
+ result = appendTypeScriptDiagnosticsAfterPluginFailure(
274
+ result,
219
275
  buildOptions,
220
276
  execution,
221
- compilers,
222
- timing,
223
- ),
224
- );
277
+ );
278
+ }
225
279
  } else {
226
280
  if (
227
281
  buildOptions.skipDiagnosticsCheck !== true &&
@@ -295,6 +349,165 @@ function checkPluginsReportTypeScriptDiagnostics(
295
349
  );
296
350
  }
297
351
 
352
+ /**
353
+ * Preserve a failed plugin's output and status while collecting TypeScript
354
+ * diagnostics through an independent no-emit pass.
355
+ *
356
+ * A sidecar can fail before it loads the project Program, including when a Go
357
+ * panic or another runtime error terminates the process. The plugin failure
358
+ * must still block emit, but it must not hide unrelated errors in the user's
359
+ * TypeScript source. The fallback runs only after a plugin failure, skips modes
360
+ * whose contract intentionally omits diagnostics, and avoids appending a batch
361
+ * the plugin already reported itself.
362
+ */
363
+ function appendTypeScriptDiagnosticsAfterPluginFailure(
364
+ failure: TtscBuildResult,
365
+ options: RunBuildOptions,
366
+ execution: ReturnType<typeof resolveExecutionContext>,
367
+ ): TtscBuildResult {
368
+ if (
369
+ options.format === true ||
370
+ options.skipDiagnosticsCheck === true ||
371
+ forwardsTerminalTsgoFlag(options)
372
+ ) {
373
+ return failure;
374
+ }
375
+ const typechecked = runTsgo(
376
+ execution,
377
+ ["--noEmit"],
378
+ createPluginFailureTypecheckOptions(options),
379
+ );
380
+ const fallback = filterReportedTypeScriptDiagnostics(
381
+ failure,
382
+ typechecked,
383
+ execution.projectRoot,
384
+ );
385
+ if (fallback === null) {
386
+ return failure;
387
+ }
388
+ // Structured consumers (the public API's `IFailure.diagnostics`) never see
389
+ // stdout/stderr, so a plugin failure that reported no parsable diagnostics
390
+ // must be seeded as one before recovered TypeScript diagnostics are appended
391
+ // — otherwise the recovery would replace the plugin error with unrelated
392
+ // type errors instead of surfacing both.
393
+ const seeded =
394
+ failure.diagnostics.length === 0
395
+ ? { ...failure, diagnostics: [createProcessDiagnostic(failure)] }
396
+ : failure;
397
+ const status = failure.status;
398
+ return {
399
+ ...appendBuildOutput(seeded, fallback),
400
+ status,
401
+ };
402
+ }
403
+
404
+ /**
405
+ * Make the recovery pass parseable regardless of the user's display flags. This
406
+ * is an internal second pass, so plain output is required to remove only
407
+ * diagnostics that the failed plugin already printed.
408
+ */
409
+ function createPluginFailureTypecheckOptions(
410
+ options: RunBuildOptions,
411
+ ): RunBuildOptions {
412
+ const passthrough: string[] = [];
413
+ for (let i = 0; i < (options.passthrough?.length ?? 0); i++) {
414
+ const token = options.passthrough![i]!;
415
+ if (token === "--pretty") {
416
+ if (isBooleanLiteral(options.passthrough![i + 1] ?? "")) i++;
417
+ continue;
418
+ }
419
+ if (token.startsWith("--pretty=")) continue;
420
+ passthrough.push(token);
421
+ }
422
+ return {
423
+ ...options,
424
+ passthrough,
425
+ structuredDiagnostics: true,
426
+ };
427
+ }
428
+
429
+ /** Return only fallback diagnostics the failed plugin did not already report. */
430
+ function filterReportedTypeScriptDiagnostics(
431
+ failure: TtscBuildResult,
432
+ typechecked: TtscBuildResult,
433
+ cwd: string,
434
+ ): TtscBuildResult | null {
435
+ if (typechecked.diagnostics.length === 0) {
436
+ return typechecked.status === 0 ? null : typechecked;
437
+ }
438
+ const diagnostics = typechecked.diagnostics.filter(
439
+ (diagnostic) =>
440
+ !failure.diagnostics.some((existing) =>
441
+ compilerDiagnosticsEqual(existing, diagnostic),
442
+ ),
443
+ );
444
+ if (diagnostics.length === 0) return null;
445
+ if (diagnostics.length === typechecked.diagnostics.length) return typechecked;
446
+ return {
447
+ ...typechecked,
448
+ diagnostics,
449
+ stderr: filterCompilerDiagnosticText(typechecked.stderr, diagnostics, cwd),
450
+ stdout: filterCompilerDiagnosticText(typechecked.stdout, diagnostics, cwd),
451
+ };
452
+ }
453
+
454
+ /** Remove diagnostic lines absent from the selected structured result. */
455
+ function filterCompilerDiagnosticText(
456
+ text: string,
457
+ diagnostics: readonly ITtscCompilerDiagnostic[],
458
+ cwd: string,
459
+ ): string {
460
+ const out: string[] = [];
461
+ let keepContinuation = true;
462
+ for (const line of text.split(/\r?\n/)) {
463
+ const plain = stripAnsi(line);
464
+ const diagnostic = parseDiagnosticLine(plain, cwd);
465
+ if (diagnostic !== null) {
466
+ keepContinuation = diagnostics.some((selected) =>
467
+ compilerDiagnosticsEqual(selected, diagnostic),
468
+ );
469
+ if (keepContinuation) out.push(line);
470
+ continue;
471
+ }
472
+ if (/^Found\s+\d+\s+errors?/i.test(plain)) continue;
473
+ if (!keepContinuation && /^\s+/.test(line)) continue;
474
+ keepContinuation = true;
475
+ out.push(line);
476
+ }
477
+ return out.join("\n");
478
+ }
479
+
480
+ /** Compare normalized compiler diagnostics before appending fallback output. */
481
+ function compilerDiagnosticsEqual(
482
+ left: ITtscCompilerDiagnostic,
483
+ right: ITtscCompilerDiagnostic,
484
+ ): boolean {
485
+ return (
486
+ left.category === right.category &&
487
+ left.code === right.code &&
488
+ left.file === right.file &&
489
+ diagnosticPositionsEqual(left, right) &&
490
+ diagnosticHeadline(left.messageText) ===
491
+ diagnosticHeadline(right.messageText)
492
+ );
493
+ }
494
+
495
+ /** Compare offsets when available, otherwise compare rendered line/column. */
496
+ function diagnosticPositionsEqual(
497
+ left: ITtscCompilerDiagnostic,
498
+ right: ITtscCompilerDiagnostic,
499
+ ): boolean {
500
+ if (left.start !== undefined && right.start !== undefined) {
501
+ return left.start === right.start;
502
+ }
503
+ return left.line === right.line && left.character === right.character;
504
+ }
505
+
506
+ /** Remove pretty-rendered source context from a diagnostic message. */
507
+ function diagnosticHeadline(message: string): string {
508
+ return message.split(/\r?\n/, 1)[0]!.trim();
509
+ }
510
+
298
511
  /**
299
512
  * Tsgo CLI flags that make `tsgo` print something and exit instead of building
300
513
  * (`--showConfig`, `--listFilesOnly`, `--help`, …). ttsc must not add
@@ -481,6 +694,12 @@ function createTsgoBuildArgs(
481
694
  if (execution.rewriteRelativeImportExtensionsForEmit) {
482
695
  args.push("--rewriteRelativeImportExtensions");
483
696
  }
697
+ // The ttsx runtime build asks for an external map when the project emits
698
+ // none, so its served emit carries a map to inline under the source URL.
699
+ // Pushed before passthrough so an explicit user `--sourceMap` still wins.
700
+ if (options.forceRuntimeSourceMap === true) {
701
+ args.push("--sourceMap", "true");
702
+ }
484
703
  } else if (options.emit === false) {
485
704
  args.push("--noEmit");
486
705
  }
@@ -548,6 +767,16 @@ function createNativeBuildArgs(
548
767
  "--plugins-json=" + serializeNativePlugins(plugins),
549
768
  "--cwd=" + execution.projectRoot,
550
769
  ];
770
+ if (
771
+ selectSharedHostPlugin(plugins).capabilities?.projectContextArgs === true
772
+ ) {
773
+ args.push(
774
+ ...createNativeProjectContextArgs(
775
+ execution.project,
776
+ execution.pluginConfigDir,
777
+ ),
778
+ );
779
+ }
551
780
  if (options.emit === true) {
552
781
  args.push("--emit");
553
782
  }
@@ -580,6 +809,14 @@ function createNativeCheckArgs(
580
809
  "--plugins-json=" + serializeNativePlugins(execution.nativePlugins),
581
810
  "--cwd=" + execution.projectRoot,
582
811
  ];
812
+ if (plugin.capabilities?.projectContextArgs === true) {
813
+ args.push(
814
+ ...createNativeProjectContextArgs(
815
+ execution.project,
816
+ execution.pluginConfigDir,
817
+ ),
818
+ );
819
+ }
583
820
  if (options.outDir) {
584
821
  args.push("--outDir=" + path.resolve(execution.cwd, options.outDir));
585
822
  }
@@ -850,37 +1087,81 @@ export function appendBuildOutput(
850
1087
  });
851
1088
  }
852
1089
 
1090
+ /**
1091
+ * Synthesize one structured diagnostic from a non-zero process result that
1092
+ * produced no parsable diagnostics, carrying the captured stderr/stdout as the
1093
+ * message. Shared by the public API result mapping and the plugin-failure
1094
+ * recovery pass so the failure text is never dropped from structured output.
1095
+ */
1096
+ export function createProcessDiagnostic(
1097
+ result: TtscBuildResult,
1098
+ ): ITtscCompilerDiagnostic {
1099
+ const messageText =
1100
+ (result.stderr || result.stdout).trim() ||
1101
+ `ttsc exited with status ${result.status}`;
1102
+ return {
1103
+ category: "error",
1104
+ code: "TTSC_PROCESS",
1105
+ file: null,
1106
+ messageText,
1107
+ };
1108
+ }
1109
+
853
1110
  /**
854
1111
  * Resolve all runtime context needed for a build: cwd, tsconfig path, project
855
1112
  * root, tsgo binary location, and the loaded native plugin list. Centralised
856
1113
  * here so every code path in `runBuild` shares the same resolution logic.
857
1114
  */
858
1115
  function resolveExecutionContext(
859
- options: TtscCommonOptions & { emit?: boolean; tsconfig?: string },
1116
+ options: TtscCommonOptions & {
1117
+ emit?: boolean;
1118
+ resolvedProject?: ITtscParsedProjectConfig;
1119
+ tsconfig?: string;
1120
+ },
860
1121
  ) {
861
1122
  const cwd = path.resolve(options.cwd ?? process.cwd());
862
- const project = readProjectConfig({
863
- cwd,
864
- projectRoot: options.projectRoot,
865
- tsconfig: options.tsconfig,
866
- });
1123
+ const project =
1124
+ options.resolvedProject ??
1125
+ readProjectConfig({
1126
+ cwd,
1127
+ projectRoot: options.projectRoot,
1128
+ tsconfig: options.tsconfig,
1129
+ });
867
1130
  const tsconfig = project.path;
868
1131
  const projectRoot = project.root;
869
1132
  const tsgo = resolveTsgo({ ...options, cwd: projectRoot });
870
- const hasPlugins = hasProjectPluginEntries(project, options.plugins);
871
- const loaded = hasPlugins
872
- ? loadProjectPlugins({
1133
+ let pluginSetupFailure: TtscBuildResult | undefined;
1134
+ let nativePlugins: ITtscLoadedNativePlugin[] = [];
1135
+ try {
1136
+ if (hasProjectPluginEntries(project, options.plugins)) {
1137
+ nativePlugins = loadProjectPlugins({
873
1138
  binary: resolveBinary(options) ?? "",
874
1139
  cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
875
1140
  cwd,
876
1141
  entries: options.plugins,
1142
+ env: { ...process.env, ...options.env },
1143
+ pluginConfigDir: options.pluginConfigDir,
877
1144
  projectRoot,
878
1145
  tsconfig,
879
- })
880
- : { nativePlugins: [] };
1146
+ }).nativePlugins;
1147
+ }
1148
+ } catch (error) {
1149
+ pluginSetupFailure = {
1150
+ diagnostics: [],
1151
+ status: 2,
1152
+ stdout: "",
1153
+ stderr: `${error instanceof Error ? error.message : String(error)}\n`,
1154
+ };
1155
+ }
881
1156
  return {
882
1157
  cwd,
883
- nativePlugins: loaded.nativePlugins,
1158
+ nativePlugins,
1159
+ pluginConfigDir: resolvePluginConfigDir({
1160
+ cwd,
1161
+ pluginConfigDir: options.pluginConfigDir,
1162
+ }),
1163
+ pluginSetupFailure,
1164
+ project,
884
1165
  projectNoEmit: project.compilerOptions.noEmit === true,
885
1166
  projectRoot,
886
1167
  rewriteRelativeImportExtensionsForEmit:
@@ -3,7 +3,7 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
 
5
5
  import type { TtscSingleFileEmitOptions } from "../../structures/internal/TtscSingleFileEmitOptions";
6
- import { resolveProjectConfig } from "./project/resolveProjectConfig";
6
+ import { readProjectConfig } from "./project/readProjectConfig";
7
7
  import { resolveEmittedJavaScript } from "./resolveEmittedJavaScript";
8
8
  import { runBuild } from "./runBuild";
9
9
 
@@ -24,12 +24,14 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
24
24
  ? options.file
25
25
  : path.resolve(cwd, options.file),
26
26
  );
27
- const tsconfig = resolveProjectConfig({
27
+ const project = readProjectConfig({
28
28
  cwd,
29
- file: sourceFile,
29
+ file: options.file,
30
+ projectRoot: options.projectRoot,
30
31
  tsconfig: options.tsconfig,
31
32
  });
32
- const projectRoot = path.dirname(tsconfig);
33
+ const tsconfig = project.path;
34
+ const projectRoot = project.root;
33
35
  const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-single-file-"));
34
36
  try {
35
37
  const result = runBuild({
@@ -38,6 +40,7 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
38
40
  emit: true,
39
41
  forceListEmittedFiles: true,
40
42
  outDir,
43
+ resolvedProject: project,
41
44
  tsconfig,
42
45
  });
43
46
  if (result.status !== 0) {
@@ -1,5 +1,29 @@
1
+ import path from "node:path";
2
+
1
3
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
2
4
 
5
+ /**
6
+ * Resolve the caller-declared plugin config anchor to an absolute path, or
7
+ * `undefined` when the caller did not declare one.
8
+ *
9
+ * The anchor exists for embedders that compile through a generated tsconfig
10
+ * outside the project (the bundler adapters' alias overlay): they set
11
+ * `pluginConfigDir` to the real project directory and every native plugin spawn
12
+ * forwards it as `TTSC_PLUGIN_CONFIG_DIR`, so config-file discovery walks the
13
+ * project instead of the wrapper's temp-dir ancestry. Callers that point at a
14
+ * user-authored tsconfig (even a wrapper outside the project) leave it unset,
15
+ * keeping discovery anchored at the tsconfig's own directory.
16
+ */
17
+ export function resolvePluginConfigDir(options: {
18
+ cwd?: string;
19
+ pluginConfigDir?: string;
20
+ }): string | undefined {
21
+ if (options.pluginConfigDir === undefined || options.pluginConfigDir === "") {
22
+ return undefined;
23
+ }
24
+ return path.resolve(options.cwd ?? process.cwd(), options.pluginConfigDir);
25
+ }
26
+
3
27
  /**
4
28
  * Reports whether the given transform source is linked into another compiler
5
29
  * host instead of owning the process itself.
@@ -10,6 +10,7 @@ import { resolveTsgo } from "./resolveTsgo";
10
10
  import {
11
11
  assertSharedHostCompatibility,
12
12
  linkedTransformPlugins,
13
+ resolvePluginConfigDir,
13
14
  selectSharedHostPlugin,
14
15
  } from "./sharedHostHelpers";
15
16
 
@@ -51,6 +52,8 @@ export function startResidentTransform(
51
52
  cacheDir: context.cacheDir ?? context.env?.TTSC_CACHE_DIR,
52
53
  cwd,
53
54
  entries: context.plugins,
55
+ env: { ...process.env, ...context.env },
56
+ pluginConfigDir: context.pluginConfigDir,
54
57
  projectRoot: context.projectRoot,
55
58
  tsconfig: project.path,
56
59
  });
@@ -84,7 +87,10 @@ export function startResidentTransform(
84
87
  /**
85
88
  * Build the environment for the resident host spawn. Matches the per-call
86
89
  * transform spawn: injects the Node, tsgo, and ttsx binaries so the host never
87
- * searches PATH, and forwards linked transform plugins via
90
+ * searches PATH, sets `TTSC_PLUGIN_CONFIG_DIR` when the caller declared a
91
+ * plugin config anchor (an embedder compiling through a generated wrapper
92
+ * tsconfig) so config-file discovery walks the real project instead of the
93
+ * wrapper's temp-dir ancestry, and forwards linked transform plugins via
88
94
  * `TTSC_LINKED_PLUGINS_JSON`.
89
95
  */
90
96
  function residentEnv(
@@ -92,15 +98,29 @@ function residentEnv(
92
98
  tsgoBinary: string,
93
99
  nativePlugins: readonly ITtscLoadedNativePlugin[],
94
100
  ): NodeJS.ProcessEnv {
101
+ const pluginConfigDir = resolvePluginConfigDir(context);
95
102
  const env: NodeJS.ProcessEnv = {
96
103
  ...process.env,
97
104
  TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
105
+ ...(pluginConfigDir === undefined
106
+ ? {}
107
+ : { TTSC_PLUGIN_CONFIG_DIR: pluginConfigDir }),
98
108
  TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgoBinary,
99
109
  TTSC_TTSX_BINARY:
100
110
  process.env.TTSC_TTSX_BINARY ??
101
111
  path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
102
112
  ...context.env,
103
113
  };
114
+ // The anchor is per-invocation state owned by this host: when this run
115
+ // declared none (and the caller's env does not name one), drop any value
116
+ // inherited from an ancestor ttsc process so a nested build never
117
+ // mis-anchors its plugins at the outer project.
118
+ if (
119
+ pluginConfigDir === undefined &&
120
+ context.env?.TTSC_PLUGIN_CONFIG_DIR === undefined
121
+ ) {
122
+ delete env.TTSC_PLUGIN_CONFIG_DIR;
123
+ }
104
124
  const linked = linkedTransformPlugins(nativePlugins);
105
125
  if (linked.length !== 0) {
106
126
  env.TTSC_LINKED_PLUGINS_JSON = serializeNativePlugins(linked);