ttsc 0.26.1 → 0.27.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 (119) hide show
  1. package/README.md +10 -1
  2. package/cmd/ttsc/api_transform.go +17 -3
  3. package/cmd/ttscgraph/auxiliary_identity_state_tracks_retargets_not_source_contents_test.go +85 -0
  4. package/cmd/ttscgraph/disk_realpath_other.go +16 -0
  5. package/cmd/ttscgraph/disk_realpath_windows.go +56 -0
  6. package/cmd/ttscgraph/serve.go +104 -35
  7. package/driver/diagnostics_preamble.go +284 -0
  8. package/driver/emit_plugin.go +71 -19
  9. package/driver/graph.go +64 -11
  10. package/driver/input_observation.go +273 -0
  11. package/driver/input_observation_test.go +149 -0
  12. package/driver/plugins.go +384 -4
  13. package/driver/program.go +81 -15
  14. package/driver/resolution_candidates.go +13 -5
  15. package/driver/rewrite.go +47 -4
  16. package/internal/graph/diagnostics.go +4 -3
  17. package/internal/graph/provenance.go +10 -1
  18. package/lib/TtscCompiler.d.ts +12 -11
  19. package/lib/TtscCompiler.js +44 -29
  20. package/lib/TtscCompiler.js.map +1 -1
  21. package/lib/compiler/internal/captureProcessOutput.js +2 -2
  22. package/lib/compiler/internal/captureProcessOutput.js.map +1 -1
  23. package/lib/compiler/internal/compileProjectInMemory.js +11 -3
  24. package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
  25. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -0
  26. package/lib/compiler/internal/resolveEmittedJavaScript.js +3 -0
  27. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  28. package/lib/compiler/internal/runBuild.d.ts +29 -1
  29. package/lib/compiler/internal/runBuild.js +117 -32
  30. package/lib/compiler/internal/runBuild.js.map +1 -1
  31. package/lib/compiler/internal/runSingleFileEmit.js +9 -2
  32. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  33. package/lib/compiler/internal/sharedHostHelpers.d.ts +28 -0
  34. package/lib/compiler/internal/sharedHostHelpers.js +39 -0
  35. package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
  36. package/lib/compiler/internal/startResidentTransform.js +12 -4
  37. package/lib/compiler/internal/startResidentTransform.js.map +1 -1
  38. package/lib/compiler/internal/transformProjectInMemory.d.ts +3 -0
  39. package/lib/compiler/internal/transformProjectInMemory.js +239 -35
  40. package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
  41. package/lib/flags/schema.js +33 -5
  42. package/lib/flags/schema.js.map +1 -1
  43. package/lib/internal/createCanonicalTempDirectory.d.ts +17 -0
  44. package/lib/internal/createCanonicalTempDirectory.js +42 -0
  45. package/lib/internal/createCanonicalTempDirectory.js.map +1 -0
  46. package/lib/internal/projectInputPathIdentity.d.ts +3 -0
  47. package/lib/internal/projectInputPathIdentity.js +11 -5
  48. package/lib/internal/projectInputPathIdentity.js.map +1 -1
  49. package/lib/internal/resolveNodeBinary.d.ts +14 -0
  50. package/lib/internal/resolveNodeBinary.js +150 -0
  51. package/lib/internal/resolveNodeBinary.js.map +1 -0
  52. package/lib/internal/resolveSafeCacheCleanupTargets.d.ts +15 -0
  53. package/lib/internal/resolveSafeCacheCleanupTargets.js +61 -0
  54. package/lib/internal/resolveSafeCacheCleanupTargets.js.map +1 -0
  55. package/lib/launcher/internal/assertNoSolutionBuild.d.ts +31 -0
  56. package/lib/launcher/internal/assertNoSolutionBuild.js +38 -0
  57. package/lib/launcher/internal/assertNoSolutionBuild.js.map +1 -0
  58. package/lib/launcher/internal/prepareExecution.d.ts +3 -0
  59. package/lib/launcher/internal/prepareExecution.js +73 -26
  60. package/lib/launcher/internal/prepareExecution.js.map +1 -1
  61. package/lib/launcher/internal/runTtsc.js +27 -13
  62. package/lib/launcher/internal/runTtsc.js.map +1 -1
  63. package/lib/launcher/internal/runTtscserver.js +18 -3
  64. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  65. package/lib/launcher/internal/runTtsx.js +4 -0
  66. package/lib/launcher/internal/runTtsx.js.map +1 -1
  67. package/lib/launcher/internal/runtimeHooks.d.ts +37 -1
  68. package/lib/launcher/internal/runtimeHooks.js +660 -62
  69. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  70. package/lib/plugin/internal/buildSourcePlugin.d.ts +62 -8
  71. package/lib/plugin/internal/buildSourcePlugin.js +754 -72
  72. package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
  73. package/lib/plugin/internal/loadProjectPlugins.d.ts +17 -5
  74. package/lib/plugin/internal/loadProjectPlugins.js +1339 -77
  75. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  76. package/lib/register.d.ts +1 -0
  77. package/lib/register.js +46 -0
  78. package/lib/register.js.map +1 -0
  79. package/lib/structures/ITtscCompilerContext.d.ts +6 -3
  80. package/lib/structures/ITtscCompilerTransformation.d.ts +31 -0
  81. package/lib/structures/ITtscPlugin.d.ts +33 -0
  82. package/package.json +16 -9
  83. package/shim/checker/go.mod +9 -0
  84. package/shim/checker/go.sum +22 -0
  85. package/shim/checker/shim.go +75 -0
  86. package/shim/compiler/emit_sourcemap.go +56 -15
  87. package/shim/compiler/incremental.go +45 -8
  88. package/src/TtscCompiler.ts +55 -33
  89. package/src/compiler/internal/captureProcessOutput.ts +3 -2
  90. package/src/compiler/internal/compileProjectInMemory.ts +11 -3
  91. package/src/compiler/internal/resolveEmittedJavaScript.ts +6 -0
  92. package/src/compiler/internal/runBuild.ts +172 -36
  93. package/src/compiler/internal/runSingleFileEmit.ts +9 -2
  94. package/src/compiler/internal/sharedHostHelpers.ts +44 -0
  95. package/src/compiler/internal/startResidentTransform.ts +12 -3
  96. package/src/compiler/internal/transformProjectInMemory.ts +410 -39
  97. package/src/flags/schema.ts +35 -5
  98. package/src/internal/createCanonicalTempDirectory.ts +53 -0
  99. package/src/internal/projectInputPathIdentity.ts +17 -4
  100. package/src/internal/resolveNodeBinary.ts +173 -0
  101. package/src/internal/resolveSafeCacheCleanupTargets.ts +85 -0
  102. package/src/launcher/internal/assertNoSolutionBuild.ts +40 -0
  103. package/src/launcher/internal/prepareExecution.ts +78 -26
  104. package/src/launcher/internal/runTtsc.ts +26 -13
  105. package/src/launcher/internal/runTtscserver.ts +18 -3
  106. package/src/launcher/internal/runTtsx.ts +4 -0
  107. package/src/launcher/internal/runtimeHooks.ts +830 -66
  108. package/src/plugin/internal/buildSourcePlugin.ts +984 -79
  109. package/src/plugin/internal/loadProjectPlugins.ts +1718 -104
  110. package/src/register.ts +47 -0
  111. package/src/structures/ITtscCompilerContext.ts +6 -3
  112. package/src/structures/ITtscCompilerTransformation.ts +39 -0
  113. package/src/structures/ITtscPlugin.ts +36 -0
  114. package/tools/shim_audit/baseline.json +0 -1
  115. package/utility/host.go +9 -3
  116. package/lib/internal/assertSafeExplicitCacheDirectory.d.ts +0 -5
  117. package/lib/internal/assertSafeExplicitCacheDirectory.js +0 -26
  118. package/lib/internal/assertSafeExplicitCacheDirectory.js.map +0 -1
  119. package/src/internal/assertSafeExplicitCacheDirectory.ts +0 -29
@@ -6,6 +6,7 @@ import {
6
6
  type ProjectInputPathIdentityContext,
7
7
  createProjectInputPathIdentityContext,
8
8
  } from "../../internal/projectInputPathIdentity";
9
+ import { resolveNodeBinary } from "../../internal/resolveNodeBinary";
9
10
  import {
10
11
  hasProjectPluginEntries,
11
12
  loadProjectPlugins,
@@ -26,7 +27,10 @@ import {
26
27
  import { resolveBinary } from "./resolveBinary";
27
28
  import { resolveTsgo } from "./resolveTsgo";
28
29
  import {
30
+ TSGO_ARGS_ENV,
29
31
  assertSharedHostCompatibility,
32
+ clearInheritedTsgoArgs,
33
+ inheritedSidecarEnv,
30
34
  linkedTransformPlugins,
31
35
  resolvePluginConfigDir,
32
36
  selectSharedHostPlugin,
@@ -38,6 +42,34 @@ export type RunBuildOptions = TtscBuildOptions & {
38
42
  forceListEmittedFiles?: boolean;
39
43
  /** Keep every compiler-owned side product inside this private directory. */
40
44
  isolateOutputsTo?: string;
45
+ /**
46
+ * Hand tsgo the `rootDir` it would otherwise infer, for a build whose
47
+ * `outDir` this process injected rather than the project declaring it.
48
+ *
49
+ * Tsgo answers an inferred common source directory with TS5011 the moment an
50
+ * `outDir` is in play, so injecting one turns a project that declares no
51
+ * output at all — the `noEmit` check-only shape `tsgo`, `ttsc`, and `ttsc
52
+ * --emit` all accept — into one that must configure the layout of output the
53
+ * user never asked for and never sees (issue #1172).
54
+ *
55
+ * The pinned value is the one tsgo itself infers: with a config file in play
56
+ * its common source directory is that file's directory, never the computed
57
+ * common directory of the input files (`outputpaths.GetCommonSourceDirectory`
58
+ * consults the file list only for a config-less program). Pinning it
59
+ * therefore silences the demand without moving a single output, and it is the
60
+ * same source root `prepareExecution.ts::resolveRuntimeSourceRoot`,
61
+ * `runtimeHooks.ts::resolveDependencySourceRoot`, and
62
+ * `watchTopology.ts::inferPerSourceCompilerOutputs` already model on the
63
+ * JavaScript side.
64
+ *
65
+ * Ignored when the project declares its own `rootDir`: that project already
66
+ * satisfies tsgo, and overriding it would relocate the emit out from under
67
+ * every consumer that mirrors the declared root.
68
+ *
69
+ * Never set for a user-supplied `--outDir`. That outDir is the user's own
70
+ * request, and TS5011 is then tsgo's genuine answer to it.
71
+ */
72
+ pinInferredRootDir?: boolean;
41
73
  /**
42
74
  * Receives selected native-plugin source roots after the project resolves.
43
75
  * The watch launcher uses these roots to invalidate a sidecar when its Go
@@ -73,13 +105,18 @@ type BuildTiming = {
73
105
  * `TTSC_NODE_BINARY` so child processes can re-invoke the same Node.js binary
74
106
  * without searching `PATH`.
75
107
  */
76
- function mergeEnv(extra?: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
77
- const base = {
108
+ function mergeEnv(
109
+ extra?: NodeJS.ProcessEnv,
110
+ cwd: string = process.cwd(),
111
+ ): NodeJS.ProcessEnv {
112
+ const env: NodeJS.ProcessEnv = {
78
113
  ...process.env,
79
- TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
114
+ ...extra,
80
115
  };
81
- if (!extra) return base;
82
- return { ...base, ...extra };
116
+ const node = resolveNodeBinary(env, cwd);
117
+ if (node === undefined) delete env.TTSC_NODE_BINARY;
118
+ else env.TTSC_NODE_BINARY = node;
119
+ return env;
83
120
  }
84
121
 
85
122
  function createBuildTiming(options: TtscCommonOptions): BuildTiming {
@@ -170,17 +207,29 @@ function nativePluginEnv(
170
207
  extra: NodeJS.ProcessEnv | undefined,
171
208
  execution: ReturnType<typeof resolveExecutionContext>,
172
209
  plugin?: ITtscLoadedNativePlugin,
210
+ tsgoArgs?: string,
173
211
  ): NodeJS.ProcessEnv {
174
- const env = mergeEnv({
175
- ...(execution.pluginConfigDir === undefined
176
- ? {}
177
- : { TTSC_PLUGIN_CONFIG_DIR: execution.pluginConfigDir }),
178
- TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? execution.tsgo.binary,
179
- TTSC_TTSX_BINARY:
180
- process.env.TTSC_TTSX_BINARY ??
181
- path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
182
- ...extra,
183
- });
212
+ const env = mergeEnv(
213
+ {
214
+ ...(execution.pluginConfigDir === undefined
215
+ ? {}
216
+ : { TTSC_PLUGIN_CONFIG_DIR: execution.pluginConfigDir }),
217
+ TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? execution.tsgo.binary,
218
+ TTSC_TTSX_BINARY:
219
+ process.env.TTSC_TTSX_BINARY ??
220
+ path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
221
+ ...extra,
222
+ },
223
+ execution.projectRoot,
224
+ );
225
+ // Forwarded tsgo argv is per-invocation state this host owns, exactly like
226
+ // the config anchor below: publish this run's payload, or drop whatever an
227
+ // ancestor ttsc process left behind when this lane forwards nothing.
228
+ if (tsgoArgs !== undefined) {
229
+ env[TSGO_ARGS_ENV] = tsgoArgs;
230
+ } else {
231
+ clearInheritedTsgoArgs(env, extra);
232
+ }
184
233
  // The anchor is per-invocation state owned by this host: when this run
185
234
  // declared none (and the caller's env does not name one), drop any value
186
235
  // inherited from an ancestor ttsc process so a nested build never
@@ -363,8 +412,11 @@ export class ResidentCheckWatchSession {
363
412
  stderr: "",
364
413
  stdout: "",
365
414
  };
366
- const checks = planResidentCheckEntries(execution.nativePlugins, (plugin) =>
367
- createNativeCheckArgs(execution, options, plugin),
415
+ const tsgoArgs = createNativeTsgoArgs(options);
416
+ const checks = planResidentCheckEntries(
417
+ execution.nativePlugins,
418
+ (plugin) => createNativeCheckArgs(execution, options, plugin),
419
+ tsgoArgs,
368
420
  );
369
421
  const request = residentCheckRequest(change, execution.projectRoot);
370
422
  // Buffer the cycle for every resident plugin before running any of them.
@@ -383,6 +435,7 @@ export class ResidentCheckWatchSession {
383
435
  "ttsc.check",
384
436
  timing,
385
437
  `ttsc check plugin ${plugin.name} time`,
438
+ tsgoArgs,
386
439
  );
387
440
  } else {
388
441
  const startedAt = process.hrtime.bigint();
@@ -392,7 +445,7 @@ export class ResidentCheckWatchSession {
392
445
  args: ["check-serve", ...args.slice(1)],
393
446
  binary: plugin.binary,
394
447
  cwd: execution.projectRoot,
395
- env: nativePluginEnv(options.env, execution, plugin),
448
+ env: nativePluginEnv(options.env, execution, plugin, tsgoArgs),
396
449
  });
397
450
  this.processes.set(key, resident);
398
451
  }
@@ -478,8 +531,9 @@ function residentCheckExecutionIsCompatible(
478
531
  function residentCheckProcessKey(
479
532
  plugin: ITtscLoadedNativePlugin,
480
533
  args: readonly string[],
534
+ tsgoArgs?: string,
481
535
  ): string {
482
- return `${plugin.binary}\0${plugin.name}\0${JSON.stringify(args)}`;
536
+ return `${plugin.binary}\0${plugin.name}\0${JSON.stringify(args)}\0${tsgoArgs ?? ""}`;
483
537
  }
484
538
 
485
539
  export type ResidentCheckEntryPlan = {
@@ -496,6 +550,11 @@ export type ResidentCheckEntryPlan = {
496
550
  export function planResidentCheckEntries(
497
551
  plugins: readonly ITtscLoadedNativePlugin[],
498
552
  createArgs: (plugin: ITtscLoadedNativePlugin) => string[],
553
+ // Part of the process identity even though it never appears in argv: the
554
+ // forwarded tsgo payload rides the environment, and a resident process holds
555
+ // the compiler options it was started with. Two cycles that forward different
556
+ // flags must not share one warm Program.
557
+ tsgoArgs?: string,
499
558
  ): ResidentCheckEntryPlan[] {
500
559
  return plugins
501
560
  .filter((candidate) => candidate.stage === "check")
@@ -506,7 +565,7 @@ export function planResidentCheckEntries(
506
565
  entryIndex,
507
566
  key:
508
567
  plugin.capabilities?.residentCheck === true
509
- ? residentCheckProcessKey(plugin, args)
568
+ ? residentCheckProcessKey(plugin, args, tsgoArgs)
510
569
  : undefined,
511
570
  plugin,
512
571
  };
@@ -802,7 +861,7 @@ function runProjectFreeTerminalFlag(
802
861
  const tsgo = resolveTsgo({ ...options, cwd });
803
862
  const res = spawnNative(tsgo.binary, [...(options.passthrough ?? [])], {
804
863
  cwd,
805
- env: mergeEnv(options.env),
864
+ env: mergeEnv(options.env, cwd),
806
865
  encoding: "utf8",
807
866
  });
808
867
  if (res.error) {
@@ -1084,7 +1143,7 @@ function forwardsInternalShadowFlag(
1084
1143
  * inside it via the `--plugins-json` flag.
1085
1144
  */
1086
1145
  function buildWithNativeCompilerPlugins(
1087
- options: TtscBuildOptions,
1146
+ options: RunBuildOptions,
1088
1147
  execution: ReturnType<typeof resolveExecutionContext>,
1089
1148
  plugins: readonly ITtscLoadedNativePlugin[],
1090
1149
  timing: BuildTiming,
@@ -1098,6 +1157,7 @@ function buildWithNativeCompilerPlugins(
1098
1157
  "ttsc.build",
1099
1158
  timing,
1100
1159
  transformHostTimingLabel(plugins),
1160
+ createNativeBuildTsgoArgs(execution, options),
1101
1161
  );
1102
1162
  }
1103
1163
 
@@ -1122,7 +1182,7 @@ function runTsgo(
1122
1182
  ],
1123
1183
  {
1124
1184
  cwd: execution.projectRoot,
1125
- env: mergeEnv(options.env),
1185
+ env: mergeEnv(options.env, execution.projectRoot),
1126
1186
  encoding: "utf8",
1127
1187
  },
1128
1188
  );
@@ -1156,7 +1216,7 @@ function runTsgoBuild(
1156
1216
  ): TtscBuildResult {
1157
1217
  const res = spawnNative(execution.tsgo.binary, args, {
1158
1218
  cwd: execution.projectRoot,
1159
- env: mergeEnv(options.env),
1219
+ env: mergeEnv(options.env, execution.projectRoot),
1160
1220
  encoding: "utf8",
1161
1221
  });
1162
1222
  if (res.error) {
@@ -1245,6 +1305,9 @@ function createTsgoBuildArgs(
1245
1305
  if (options.forceRuntimeSourceMap === true) {
1246
1306
  args.push("--sourceMap", "true");
1247
1307
  }
1308
+ // Pushed before passthrough for the same reason: a user `--rootDir` still
1309
+ // wins over the value ttsc pins for its own injected `outDir`.
1310
+ args.push(...pinnedRootDirArgs(execution, options));
1248
1311
  } else if (options.emit === false) {
1249
1312
  args.push("--noEmit");
1250
1313
  }
@@ -1264,6 +1327,37 @@ function createTsgoBuildArgs(
1264
1327
  return args;
1265
1328
  }
1266
1329
 
1330
+ /**
1331
+ * The explicit `--rootDir` a caller-injected `outDir` needs, or nothing.
1332
+ *
1333
+ * See {@link RunBuildOptions.pinInferredRootDir} for why an injected `outDir`
1334
+ * needs one at all. Three conditions gate it, and each one is load-bearing:
1335
+ *
1336
+ * - The caller injected the `outDir` itself, so a user `--outDir` keeps tsgo's
1337
+ * own TS5011 answer;
1338
+ * - This pass emits, because a no-emit pass has no layout to pin (tsgo skips the
1339
+ * check for `noEmit` too);
1340
+ * - The project declares no `rootDir` of its own, so a declared layout is never
1341
+ * overridden.
1342
+ *
1343
+ * `execution.projectRoot` is the directory of the tsconfig ttsc resolved and
1344
+ * hands tsgo as `-p`, which is exactly the directory tsgo infers, and it is
1345
+ * spelled the way tsgo will spell the input file names it compares against it —
1346
+ * both come from the same `fs.realpathSync` pass. Resolving it any further (a
1347
+ * Windows 8.3 expansion, say) would leave the comparison lexically mixed, and
1348
+ * `ContainsPath` is lexical: every input would count as outside `rootDir` and
1349
+ * tsgo would emit it beside the user's source instead of under `outDir`.
1350
+ */
1351
+ function pinnedRootDirArgs(
1352
+ execution: ReturnType<typeof resolveExecutionContext>,
1353
+ options: RunBuildOptions,
1354
+ ): string[] {
1355
+ if (options.pinInferredRootDir !== true) return [];
1356
+ if (options.emit !== true) return [];
1357
+ if (typeof execution.project.compilerOptions.rootDir === "string") return [];
1358
+ return ["--rootDir", execution.projectRoot];
1359
+ }
1360
+
1267
1361
  /**
1268
1362
  * Return `["--pretty", "false"]` when structured diagnostics are requested so
1269
1363
  * that the output can be parsed line-by-line, or an empty array otherwise.
@@ -1304,7 +1398,7 @@ function createTsgoThreadingArgs(options: TtscCommonOptions): string[] {
1304
1398
  /** Build the argument list for a native plugin `build`/`check` invocation. */
1305
1399
  function createNativeBuildArgs(
1306
1400
  execution: ReturnType<typeof resolveExecutionContext>,
1307
- options: TtscBuildOptions,
1401
+ options: RunBuildOptions,
1308
1402
  plugins: readonly ITtscLoadedNativePlugin[],
1309
1403
  ): string[] {
1310
1404
  const args = [
@@ -1339,10 +1433,23 @@ function createNativeBuildArgs(
1339
1433
  args.push("--quiet");
1340
1434
  }
1341
1435
  }
1342
- args.push(...createNativeTsgoArgs(options));
1343
1436
  return args;
1344
1437
  }
1345
1438
 
1439
+ /**
1440
+ * The forwarded-tsgo payload for a native `build`/`check` emit invocation.
1441
+ *
1442
+ * The sidecar builds its Program in-process, so the pinned `rootDir` has to
1443
+ * travel the same channel every other tsgo option takes to it — the host flag
1444
+ * set does not declare `--rootDir`, and `filterHostArgs` would strip it.
1445
+ */
1446
+ function createNativeBuildTsgoArgs(
1447
+ execution: ReturnType<typeof resolveExecutionContext>,
1448
+ options: RunBuildOptions,
1449
+ ): string | undefined {
1450
+ return createNativeTsgoArgs(options, pinnedRootDirArgs(execution, options));
1451
+ }
1452
+
1346
1453
  /** Build the argument list for a native plugin check/fix/format invocation. */
1347
1454
  function createNativeCheckArgs(
1348
1455
  execution: ReturnType<typeof resolveExecutionContext>,
@@ -1373,7 +1480,6 @@ function createNativeCheckArgs(
1373
1480
  }
1374
1481
  args.push(...createNativeCheckThreadingArgs(options, plugin));
1375
1482
  args.push(...createNativeCheckDiagnosticsArgs(options, plugin));
1376
- args.push(...createNativeTsgoArgs(options));
1377
1483
  return args;
1378
1484
  }
1379
1485
 
@@ -1483,20 +1589,48 @@ function transformHostTimingLabel(
1483
1589
 
1484
1590
  /**
1485
1591
  * Forward the tsgo flags ttsc did not recognize to a native sidecar as one
1486
- * JSON-encoded `--tsgo-args` flag. The sidecar replays them through tsgo's own
1487
- * option parser onto `CompilerOptions`, so a flag like `ttsc --strict` reaches
1488
- * a plugin build the same way it reaches the plain tsgo lane. Encoded as a
1489
- * single token so the sidecars' unknown-flag filters keep it intact.
1592
+ * JSON-encoded payload. The sidecar replays them through tsgo's own option
1593
+ * parser onto `CompilerOptions`, so a flag like `ttsc --strict` reaches a
1594
+ * plugin build the same way it reaches the plain tsgo lane.
1595
+ *
1596
+ * The payload travels in the `TTSC_TSGO_ARGS` environment variable, not on the
1597
+ * sidecar's command line. #113 shipped it as a `--tsgo-args` flag, which is an
1598
+ * addition to a plugin protocol third-party hosts had already frozen: a Go
1599
+ * `flag.FlagSet` created with `flag.ContinueOnError` treats an undeclared flag
1600
+ * as fatal, so every pre-#113 sidecar answered `flag provided but not defined:
1601
+ * -tsgo-args` and exited 2. That took down `ttsc --strict`, `ttsc
1602
+ * --declaration`, `ttsx --strict` and — because {@link isolatedTsgoOutputArgs}
1603
+ * makes this payload non-empty on its own — plain `ttsc <file.ts>`, on every
1604
+ * project carrying a typia/nestia-era transform host (issue #1188).
1605
+ *
1606
+ * The environment is the channel ttsc already uses for host-owned payloads that
1607
+ * must not collide with a third-party flag set (`TTSC_LINKED_PLUGINS_JSON`,
1608
+ * `TTSC_PLUGIN_CONFIG_DIR`). It reaches those hosts without any change on their
1609
+ * side, because `buildSourcePlugin.ts::sourceBuildWorkspaceReplacements` builds
1610
+ * every source plugin against the installed ttsc's own driver, and
1611
+ * `driver.LoadProgram` reads the variable whenever the caller supplied no
1612
+ * explicit argv. It is strictly better than the capability gate `ad3443a` used
1613
+ * for `--singleThreaded` / `--checkers`: that one drops the flag for hosts that
1614
+ * cannot take it, which is acceptable for a threading knob and not for
1615
+ * `--strict`.
1616
+ *
1617
+ * Returns the JSON payload, or `undefined` when this lane forwards nothing.
1490
1618
  */
1491
- function createNativeTsgoArgs(options: TtscCommonOptions): string[] {
1619
+ function createNativeTsgoArgs(
1620
+ options: TtscCommonOptions,
1621
+ leading: readonly string[] = [],
1622
+ ): string | undefined {
1492
1623
  const passthrough = [
1624
+ // Ahead of the user's own flags, so the same precedence holds here as on
1625
+ // the direct tsgo lane: whatever the user forwarded wins.
1626
+ ...leading,
1493
1627
  ...(nativeTsgoPassthroughArgs(options) ?? []),
1494
1628
  ...isolatedTsgoOutputArgs(options),
1495
1629
  ];
1496
1630
  if (passthrough.length === 0) {
1497
- return [];
1631
+ return undefined;
1498
1632
  }
1499
- return ["--tsgo-args=" + JSON.stringify(passthrough)];
1633
+ return JSON.stringify(passthrough);
1500
1634
  }
1501
1635
 
1502
1636
  function isolatedTsgoOutputArgs(options: TtscCommonOptions): string[] {
@@ -1605,6 +1739,7 @@ function runNativeCheckPlugins(
1605
1739
  "ttsc.check",
1606
1740
  timing,
1607
1741
  `ttsc check plugin ${plugin.name} time`,
1742
+ createNativeTsgoArgs(options),
1608
1743
  );
1609
1744
  out = appendBuildOutput(out, result);
1610
1745
  if (result.status !== 0) {
@@ -1876,11 +2011,12 @@ function runNativePluginCommand(
1876
2011
  label: string,
1877
2012
  timing: BuildTiming,
1878
2013
  timingLabel: string,
2014
+ tsgoArgs?: string,
1879
2015
  ): TtscBuildResult {
1880
2016
  const startedAt = process.hrtime.bigint();
1881
2017
  const res = spawnNative(plugin.binary, args, {
1882
2018
  cwd: execution.projectRoot,
1883
- env: nativePluginEnv(options.env, execution, plugin),
2019
+ env: nativePluginEnv(options.env, execution, plugin, tsgoArgs),
1884
2020
  encoding: "utf8",
1885
2021
  });
1886
2022
  recordTiming(timing, timingLabel, startedAt);
@@ -1974,7 +2110,7 @@ function resolveExecutionContext(
1974
2110
  cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
1975
2111
  cwd,
1976
2112
  entries: options.plugins,
1977
- env: { ...process.env, ...options.env },
2113
+ env: inheritedSidecarEnv(options.env),
1978
2114
  onWatchInputs: options.onWatchInputs,
1979
2115
  pluginConfigDir: options.pluginConfigDir,
1980
2116
  projectRoot,
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
- import os from "node:os";
3
2
  import path from "node:path";
4
3
 
4
+ import { createCanonicalTempDirectory } from "../../internal/createCanonicalTempDirectory";
5
5
  import type { TtscSingleFileEmitOptions } from "../../structures/internal/TtscSingleFileEmitOptions";
6
6
  import { readProjectConfig } from "./project/readProjectConfig";
7
7
  import { resolveEmittedJavaScript } from "./resolveEmittedJavaScript";
@@ -32,7 +32,7 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
32
32
  });
33
33
  const tsconfig = project.path;
34
34
  const projectRoot = project.root;
35
- const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-single-file-"));
35
+ const outDir = createCanonicalTempDirectory("ttsc-single-file-");
36
36
  try {
37
37
  const result = runBuild({
38
38
  ...options,
@@ -41,6 +41,13 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
41
41
  forceListEmittedFiles: true,
42
42
  isolateOutputsTo: outDir,
43
43
  outDir,
44
+ // The private temp directory above is an `outDir` this lane injected, not
45
+ // one the project declared, and tsgo answers an inferred common source
46
+ // directory with TS5011 as soon as any `outDir` is in play. Pinning the
47
+ // root tsgo would infer keeps `ttsc <file.ts>` working on a project that
48
+ // declares no output at all, and it is the same root
49
+ // `resolveEmittedJavaScript` mirrors below (issue #1172).
50
+ pinInferredRootDir: true,
44
51
  resolvedProject: project,
45
52
  tsconfig,
46
53
  });
@@ -2,6 +2,50 @@ import path from "node:path";
2
2
 
3
3
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
4
4
 
5
+ /**
6
+ * Environment channel carrying the JSON-encoded tsgo argv ttsc forwards to a
7
+ * native sidecar. Mirrors `driver.TsgoArgsEnv` on the Go side; see
8
+ * `runBuild.ts::createNativeTsgoArgs` for why the payload is not a CLI flag.
9
+ */
10
+ export const TSGO_ARGS_ENV = "TTSC_TSGO_ARGS";
11
+
12
+ /**
13
+ * Drop a forwarded-tsgo payload this invocation did not publish itself.
14
+ *
15
+ * Every sidecar env starts from `process.env`, so a ttsc that is itself running
16
+ * inside a plugin sidecar — `@ttsc/lint` evaluating a config file through
17
+ * `ttsx`, for instance — would otherwise hand the outer run's `--strict` to its
18
+ * own sidecars, and `driver.LoadProgram` would apply it. The forwarded argv is
19
+ * per-invocation state the spawning host owns, the same rule
20
+ * `TTSC_PLUGIN_CONFIG_DIR` already follows. A caller that named the variable
21
+ * explicitly keeps it.
22
+ */
23
+ export function clearInheritedTsgoArgs(
24
+ env: NodeJS.ProcessEnv,
25
+ callerEnv: NodeJS.ProcessEnv | undefined,
26
+ ): void {
27
+ if (callerEnv?.[TSGO_ARGS_ENV] === undefined) {
28
+ delete env[TSGO_ARGS_ENV];
29
+ }
30
+ }
31
+
32
+ /**
33
+ * The `{ ...process.env, ...callerEnv }` a child process inherits, minus a
34
+ * forwarded-tsgo payload this lane never published.
35
+ *
36
+ * Use it at every spawn whose child can reach `driver.LoadProgram` — the
37
+ * `api-compile` / `api-transform` hosts, and the plugin loader, whose `ttsx`
38
+ * descriptor evaluation compiles a project of its own. One rule at every site
39
+ * beats four sites each reasoning about whether the variable could be present.
40
+ */
41
+ export function inheritedSidecarEnv(
42
+ callerEnv: NodeJS.ProcessEnv | undefined,
43
+ ): NodeJS.ProcessEnv {
44
+ const env: NodeJS.ProcessEnv = { ...process.env, ...callerEnv };
45
+ clearInheritedTsgoArgs(env, callerEnv);
46
+ return env;
47
+ }
48
+
5
49
  /**
6
50
  * Resolve the caller-declared plugin config anchor to an absolute path, or
7
51
  * `undefined` when the caller did not declare one.
@@ -1,5 +1,6 @@
1
1
  import path from "node:path";
2
2
 
3
+ import { resolveNodeBinary } from "../../internal/resolveNodeBinary";
3
4
  import { loadProjectPlugins } from "../../plugin/internal/loadProjectPlugins";
4
5
  import type { ITtscCompilerContext } from "../../structures/ITtscCompilerContext";
5
6
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
@@ -9,6 +10,8 @@ import { resolveBinary } from "./resolveBinary";
9
10
  import { resolveTsgo } from "./resolveTsgo";
10
11
  import {
11
12
  assertSharedHostCompatibility,
13
+ clearInheritedTsgoArgs,
14
+ inheritedSidecarEnv,
12
15
  linkedTransformPlugins,
13
16
  resolvePluginConfigDir,
14
17
  selectSharedHostPlugin,
@@ -52,7 +55,7 @@ export function startResidentTransform(
52
55
  cacheDir: context.cacheDir ?? context.env?.TTSC_CACHE_DIR,
53
56
  cwd,
54
57
  entries: context.plugins,
55
- env: { ...process.env, ...context.env },
58
+ env: inheritedSidecarEnv(context.env),
56
59
  pluginConfigDir: context.pluginConfigDir,
57
60
  projectRoot: context.projectRoot,
58
61
  tsconfig: project.path,
@@ -79,7 +82,7 @@ export function startResidentTransform(
79
82
  ],
80
83
  binary: host.binary,
81
84
  cwd: project.root,
82
- env: residentEnv(context, tsgoBinary, loaded.nativePlugins),
85
+ env: residentEnv(context, project.root, tsgoBinary, loaded.nativePlugins),
83
86
  });
84
87
  return { process: resident, projectRoot: project.root };
85
88
  }
@@ -95,13 +98,13 @@ export function startResidentTransform(
95
98
  */
96
99
  function residentEnv(
97
100
  context: ITtscCompilerContext,
101
+ projectRoot: string,
98
102
  tsgoBinary: string,
99
103
  nativePlugins: readonly ITtscLoadedNativePlugin[],
100
104
  ): NodeJS.ProcessEnv {
101
105
  const pluginConfigDir = resolvePluginConfigDir(context);
102
106
  const env: NodeJS.ProcessEnv = {
103
107
  ...process.env,
104
- TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
105
108
  ...(pluginConfigDir === undefined
106
109
  ? {}
107
110
  : { TTSC_PLUGIN_CONFIG_DIR: pluginConfigDir }),
@@ -111,6 +114,9 @@ function residentEnv(
111
114
  path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
112
115
  ...context.env,
113
116
  };
117
+ const node = resolveNodeBinary(env, projectRoot);
118
+ if (node === undefined) delete env.TTSC_NODE_BINARY;
119
+ else env.TTSC_NODE_BINARY = node;
114
120
  // The anchor is per-invocation state owned by this host: when this run
115
121
  // declared none (and the caller's env does not name one), drop any value
116
122
  // inherited from an ancestor ttsc process so a nested build never
@@ -121,6 +127,9 @@ function residentEnv(
121
127
  ) {
122
128
  delete env.TTSC_PLUGIN_CONFIG_DIR;
123
129
  }
130
+ // This lane forwards no tsgo argv of its own, so anything inherited belongs
131
+ // to an outer ttsc run and must not reach the resident sidecar.
132
+ clearInheritedTsgoArgs(env, context.env);
124
133
  const linked = linkedTransformPlugins(nativePlugins);
125
134
  if (linked.length !== 0) {
126
135
  env.TTSC_LINKED_PLUGINS_JSON = serializeNativePlugins(linked);