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
@@ -10,8 +10,6 @@ import { createFilesystemPathIdentityContext } from "../../internal/projectInput
10
10
  import type { TtscCommonOptions } from "../../structures/internal/TtscCommonOptions";
11
11
  import { type OwningModuleOptions, projectModuleOptions } from "./runtimeHooks";
12
12
 
13
- /** Subdirectory name that isolates concurrent ttsx processes by PID. */
14
- const PROCESS_CACHE_KEY = String(process.pid);
15
13
  /**
16
14
  * Maximum number of ancestor directories above the project root that the
17
15
  * virtual filesystem overlay mirrors. Three levels covers the common monorepo
@@ -32,12 +30,15 @@ export function prepareExecution(
32
30
  options: TtscCommonOptions & {
33
31
  cacheDir?: string;
34
32
  project?: string;
33
+ /** Internal cache key for more than one checked entry in this process. */
34
+ runtimeCacheKey?: string;
35
35
  } = {},
36
36
  ): {
37
37
  cleanupDir: string;
38
38
  emitDir: string;
39
39
  emittedFiles?: readonly string[];
40
40
  entryFile: string;
41
+ entrySource: string;
41
42
  moduleOptions: OwningModuleOptions;
42
43
  projectRoot: string;
43
44
  rootDir: string;
@@ -57,7 +58,6 @@ export function prepareExecution(
57
58
  const context = createProjectContext(
58
59
  cwd,
59
60
  path.resolve(cwd, entryFile),
60
- entry,
61
61
  options,
62
62
  );
63
63
  try {
@@ -75,6 +75,7 @@ export function prepareExecution(
75
75
  emitDir: context.emitDir,
76
76
  emittedFiles: context.emittedFiles ?? undefined,
77
77
  entryFile: emittedEntry,
78
+ entrySource: entry,
78
79
  moduleOptions: context.moduleOptions,
79
80
  projectRoot: context.root,
80
81
  rootDir: context.runtimeRootDir,
@@ -148,16 +149,26 @@ function resolveEntrySpelling(cwd: string, entryFile: string): string {
148
149
  return identities.resolve(path.resolve(cwd, entryFile)).path;
149
150
  }
150
151
 
152
+ /**
153
+ * Directory-safe identity for one prepared runtime. Direct `ttsx` retains its
154
+ * historical PID directory; the public preload supplies a distinct key for
155
+ * every late TypeScript root so one preparation cannot erase another's emit.
156
+ */
157
+ function resolveRuntimeCacheKey(runtimeCacheKey: string | undefined): string {
158
+ const key = runtimeCacheKey ?? String(process.pid);
159
+ if (!/^[A-Za-z0-9._-]+$/.test(key) || key === "." || key === "..") {
160
+ throw new Error(`ttsx: invalid runtime cache key ${JSON.stringify(key)}`);
161
+ }
162
+ return key;
163
+ }
164
+
151
165
  /**
152
166
  * @param discoveryFile - The entry as the user named it. Project discovery
153
167
  * walks up from here, so it must not be retargeted through a symlink.
154
- * @param entry - The entry in its physical spelling, which is what the emit
155
- * layout and the runtime hooks both speak.
156
168
  */
157
169
  function createProjectContext(
158
170
  cwd: string,
159
171
  discoveryFile: string,
160
- entry: string,
161
172
  options: NonNullable<Parameters<typeof prepareExecution>[1]>,
162
173
  ) {
163
174
  const project = readProjectConfig(
@@ -172,21 +183,30 @@ function createProjectContext(
172
183
  const tsconfig = project.path;
173
184
  const root = project.root;
174
185
  const explicitCacheDir = resolveCacheDir(cwd, options.cacheDir);
175
- const cacheDir =
186
+ const cacheDirSpelling =
176
187
  explicitCacheDir ??
177
188
  path.join(root, "node_modules", ".cache", "ttsc", "ttsx");
178
- const processDir = path.join(cacheDir, "project", PROCESS_CACHE_KEY);
179
- const virtualRoot = path.join(processDir, "fs");
189
+ const runtimeCacheKey = resolveRuntimeCacheKey(options.runtimeCacheKey);
180
190
  // Resolved once: it now costs a realpath (and, for a missing directory on
181
191
  // Windows, a case-sensitivity probe) rather than a string join.
182
- const runtimeRootDir = resolveRuntimeSourceRoot(project, entry);
192
+ const runtimeRootDir = resolveRuntimeSourceRoot(project);
193
+ fs.mkdirSync(cacheDirSpelling, { recursive: true });
194
+ // Pin the cache parent before deriving a generation path. Descriptors run
195
+ // after this point and may retarget a caller-controlled symlink or junction;
196
+ // every later runtime write and recursive cleanup must remain below the
197
+ // physical parent selected here.
198
+ const cacheDir =
199
+ createFilesystemPathIdentityContext().resolve(cacheDirSpelling).path;
200
+ const processDir = path.join(cacheDir, "project", runtimeCacheKey);
201
+ const virtualRoot = path.join(processDir, "fs");
183
202
  return {
184
203
  project,
185
204
  tsconfig,
186
205
  root,
187
206
  cacheDir,
207
+ runtimeCacheKey,
188
208
  processDir,
189
- pluginCacheDir: explicitCacheDir,
209
+ pluginCacheDir: explicitCacheDir === undefined ? undefined : cacheDir,
190
210
  virtualRoot,
191
211
  emitDir: project.compilerOptions.outDir
192
212
  ? virtualPath(virtualRoot, project.compilerOptions.outDir)
@@ -217,6 +237,16 @@ function createProjectContext(
217
237
  * The source-tree root the emit mirrors, in the same physical spelling as the
218
238
  * entry it will be compared against.
219
239
  *
240
+ * Undeclared, the root is the project's own directory, because that is the one
241
+ * tsgo uses: with a config file in play `GetCommonSourceDirectory` answers that
242
+ * file's directory and never computes a common directory of the input files.
243
+ * The entry's directory is not that root — it is only the same directory when
244
+ * the entry happens to sit beside the tsconfig, which is precisely why a
245
+ * `src/`-shaped project mislaid its emit here (issue #1172) while a flat one
246
+ * worked. `runtimeHooks.ts::resolveDependencySourceRoot` and
247
+ * `watchTopology.ts::inferPerSourceCompilerOutputs` already model the same
248
+ * rule, and `runBuild.ts::pinnedRootDirArgs` pins it for tsgo itself.
249
+ *
220
250
  * Resolving it is the other half of `resolveEntrySpelling`, and skipping it
221
251
  * leaves the comparison mixed rather than merely imprecise. `project.root`
222
252
  * arrives through plain `fs.realpathSync`, which resolves reparse points but
@@ -227,22 +257,25 @@ function createProjectContext(
227
257
  * way the gate reads an in-project entry as outside its own root, pays a second
228
258
  * whole build for it, and publishes a wider root than the project has.
229
259
  *
230
- * `path.dirname(entry)` is already physical, so only the declared branch needs
231
- * the pass.
260
+ * The pass costs nothing in agreement with the root tsgo was pinned to, which
261
+ * stays unresolved on purpose so it matches the spelling tsgo gives the input
262
+ * file names it compares against it. Only the prefix differs between the two;
263
+ * each side strips its own, so the relative path below the root — the part that
264
+ * decides where the emit lands and where the lookup reads — is identical.
232
265
  */
233
266
  function resolveRuntimeSourceRoot(
234
267
  project: ReturnType<typeof readProjectConfig>,
235
- entry: string,
236
268
  ): string {
237
269
  const rootDir = project.compilerOptions.rootDir;
238
- if (typeof rootDir !== "string") {
239
- return path.dirname(entry);
240
- }
241
270
  const identities = createFilesystemPathIdentityContext({
242
271
  throwOnRealpathError: false,
243
272
  });
244
273
  return identities.resolve(
245
- path.isAbsolute(rootDir) ? rootDir : path.resolve(project.root, rootDir),
274
+ typeof rootDir !== "string"
275
+ ? project.root
276
+ : path.isAbsolute(rootDir)
277
+ ? rootDir
278
+ : path.resolve(project.root, rootDir),
246
279
  ).path;
247
280
  }
248
281
 
@@ -265,6 +298,13 @@ function buildProject(
265
298
  cacheDir: context.pluginCacheDir,
266
299
  outDir: context.emitDir,
267
300
  passthrough: options.passthrough,
301
+ // `context.emitDir` is ttsx's own temp directory, not an output the project
302
+ // asked for, and tsgo demands an explicit `rootDir` (TS5011) as soon as any
303
+ // `outDir` is in play. Pinning the root tsgo would infer keeps a check-only
304
+ // project runnable without moving its emit (issue #1172); a project that
305
+ // declares `rootDir` is left exactly as it is, which is also the root
306
+ // `resolveRuntimeSourceRoot` published above.
307
+ pinInferredRootDir: true,
268
308
  // Emit a source map on the transient entry emit (a PID-isolated temp dir,
269
309
  // never the consumer's `outDir`) so the serve path can inline it under the
270
310
  // source URL. Routed as a dedicated build option, not a forwarded tsgo
@@ -345,7 +385,7 @@ function buildEntryProject(
345
385
  const rootDir = commonAncestorDirectory(path.dirname(entry), context.root);
346
386
  const tsconfig = path.join(
347
387
  context.root,
348
- `.ttsx-entry.${PROCESS_CACHE_KEY}.tsconfig.json`,
388
+ `.ttsx-entry.${context.runtimeCacheKey}.tsconfig.json`,
349
389
  );
350
390
  fs.writeFileSync(
351
391
  tsconfig,
@@ -414,8 +454,8 @@ function buildEntryProject(
414
454
  fs.rmSync(tsconfig, { force: true });
415
455
  } catch {
416
456
  // Best effort: a leftover synthesized tsconfig must not mask a build
417
- // failure, and it is PID-scoped so it can never be mistaken for a real
418
- // project config.
457
+ // failure, and it is runtime-scoped so it can never be mistaken for a
458
+ // real project config.
419
459
  }
420
460
  }
421
461
  }
@@ -559,6 +599,9 @@ function isDirectorySymlinkTarget(realEntry: string): boolean {
559
599
  */
560
600
  function collectLinkDirectories(projectRoot: string): string[] {
561
601
  const out: string[] = [];
602
+ const identities = createFilesystemPathIdentityContext({
603
+ throwOnRealpathError: false,
604
+ });
562
605
  let current = projectRoot;
563
606
  for (let depth = 0; depth <= MAX_VIRTUAL_PARENT_DEPTH; depth += 1) {
564
607
  out.push(current);
@@ -570,7 +613,7 @@ function collectLinkDirectories(projectRoot: string): string[] {
570
613
  break;
571
614
  }
572
615
  const parent = path.dirname(current);
573
- if (parent === current || isUnsafeVirtualParent(parent)) {
616
+ if (parent === current || isUnsafeVirtualParent(parent, identities)) {
574
617
  break;
575
618
  }
576
619
  current = parent;
@@ -578,10 +621,19 @@ function collectLinkDirectories(projectRoot: string): string[] {
578
621
  return out.reverse();
579
622
  }
580
623
 
581
- function isUnsafeVirtualParent(directory: string): boolean {
582
- const resolved = path.resolve(directory);
583
- const root = path.parse(resolved).root;
584
- return resolved === root || resolved === path.resolve(os.tmpdir());
624
+ /**
625
+ * Whether mirroring `directory` would reach a filesystem or temporary root.
626
+ * Identity comparison is required on Windows, where `os.tmpdir()` can carry an
627
+ * 8.3 component while a project created below it is returned in long spelling.
628
+ */
629
+ function isUnsafeVirtualParent(
630
+ directory: string,
631
+ identities: ReturnType<typeof createFilesystemPathIdentityContext>,
632
+ ): boolean {
633
+ const resolved = identities.resolve(directory);
634
+ const root = identities.resolve(path.parse(resolved.path).root);
635
+ const temporaryRoot = identities.resolve(os.tmpdir());
636
+ return resolved.key === root.key || resolved.key === temporaryRoot.key;
585
637
  }
586
638
 
587
639
  /**
@@ -17,7 +17,7 @@ import {
17
17
  parseFlags,
18
18
  } from "../../flags/parser";
19
19
  import { resolveFlagSpec } from "../../flags/schema";
20
- import { assertSafeExplicitCacheDirectory } from "../../internal/assertSafeExplicitCacheDirectory";
20
+ import { resolveSafeCacheCleanupTargets } from "../../internal/resolveSafeCacheCleanupTargets";
21
21
  import {
22
22
  isPathWithin,
23
23
  legacyGlobalCacheTargets,
@@ -27,6 +27,7 @@ import {
27
27
  import type { ITtscProjectInputSnapshot } from "../../structures/internal/ITtscProjectInputSnapshot";
28
28
  import type { TtscBuildOptions } from "../../structures/internal/TtscBuildOptions";
29
29
  import type { TtscSingleFileEmitOptions } from "../../structures/internal/TtscSingleFileEmitOptions";
30
+ import { assertNoSolutionBuild } from "./assertNoSolutionBuild";
30
31
  import { getCompilerVersionText } from "./getCompilerVersionText";
31
32
  import { resolveCacheDir } from "./resolveCacheDir";
32
33
  import { resolveSingleFileOutput } from "./singleFileOutput";
@@ -206,9 +207,6 @@ function runClean(argv: readonly string[]): number {
206
207
  const explicitCacheDir = options.cacheDir
207
208
  ? path.resolve(cwd, options.cacheDir)
208
209
  : undefined;
209
- if (explicitCacheDir !== undefined) {
210
- assertSafeExplicitCacheDirectory(projectRoot, explicitCacheDir);
211
- }
212
210
  const targets = explicitCacheDir
213
211
  ? // Explicit `ttsc clean --cache-dir X`: the user names X as the cache to
214
212
  // remove for this command, so remove it wholesale plus the legacy
@@ -222,11 +220,19 @@ function runClean(argv: readonly string[]): number {
222
220
  // possibly-shared root is never deleted) plus the pre-0.17 machine-global
223
221
  // cache so upgraders reclaim that disk.
224
222
  [...resolveCleanTargets(projectRoot), ...legacyGlobalCacheTargets()];
223
+ // Check the complete deletion set before removing the first directory. An
224
+ // environment-selected TTSC_GO_CACHE_DIR and a legacy-global cache can be as
225
+ // destructive as an explicit --cache-dir when either equals or contains the
226
+ // project through its lexical spelling or a filesystem alias.
227
+ const safeTargets = resolveSafeCacheCleanupTargets(projectRoot, targets);
225
228
  const removed: string[] = [];
226
- for (const target of targets) {
227
- if (!fs.existsSync(target)) continue;
228
- fs.rmSync(target, { recursive: true, force: true });
229
- removed.push(target);
229
+ const visited = new Set<string>();
230
+ for (const target of safeTargets) {
231
+ if (visited.has(target.path)) continue;
232
+ visited.add(target.path);
233
+ if (!target.exists || !fs.existsSync(target.path)) continue;
234
+ fs.rmSync(target.path, { recursive: true, force: true });
235
+ removed.push(target.requestedPath);
230
236
  }
231
237
  if (removed.length === 0) {
232
238
  process.stdout.write(
@@ -268,24 +274,28 @@ function runCachePaths(argv: readonly string[]): number {
268
274
  const cacheDir = resolveCacheDir(cwd, options.cacheDir);
269
275
  const projectRoot = resolveCleanProjectRoot(cwd, options.tsconfig);
270
276
  const paths = resolveSourceBuildCachePaths(projectRoot, cacheDir);
271
- // The minimal directory set a CI cache step must persist. The Go build cache
272
- // is folded in only when it lives outside the ttsc cache root (an explicit
273
- // GOCACHE / TTSC_GO_CACHE_DIR); by default it nests under `root`.
277
+ // Legacy combined roots retained for JSON compatibility. `requiredRoots`
278
+ // identifies the compiled binaries that skip cold builds;
279
+ // `acceleratorRoots` separately identifies the optional Go object cache.
274
280
  const cacheableRoots = [
275
281
  paths.root,
276
282
  ...(isPathWithin(paths.goBuildRoot, paths.root) ? [] : [paths.goBuildRoot]),
277
283
  ];
284
+ const requiredRoots = [paths.pluginRoot];
285
+ const acceleratorRoots = [paths.goBuildRoot];
278
286
  if (options.json) {
279
287
  process.stdout.write(
280
288
  `${JSON.stringify(
281
289
  {
282
290
  cacheRoot: paths.root,
291
+ acceleratorRoots,
283
292
  cacheableRoots,
284
293
  cwd,
285
294
  goBuildCacheRoot: paths.goBuildRoot,
286
295
  goBuildCacheSource: paths.goBuildRootSource,
287
296
  pluginCacheRoot: paths.pluginRoot,
288
297
  projectRoot,
298
+ requiredRoots,
289
299
  },
290
300
  null,
291
301
  2,
@@ -300,8 +310,10 @@ function runCachePaths(argv: readonly string[]): number {
300
310
  ` plugin binaries ${paths.pluginRoot}`,
301
311
  ` go build cache ${paths.goBuildRoot} (${paths.goBuildRootSource})`,
302
312
  "",
303
- "Persist these between CI jobs to skip cold plugin rebuilds:",
304
- ...cacheableRoots.map((root) => ` ${root}`),
313
+ "Persist this required cache to skip cold plugin rebuilds:",
314
+ ...requiredRoots.map((root) => ` ${root}`),
315
+ "Optionally persist this Go object accelerator for future cache misses:",
316
+ ...acceleratorRoots.map((root) => ` ${root}`),
305
317
  "",
306
318
  ].join("\n"),
307
319
  );
@@ -441,6 +453,7 @@ function parseBuildArgs(argv: readonly string[]) {
441
453
  isPositional: looksLikeInputFile,
442
454
  subcommand: "build",
443
455
  });
456
+ assertNoSolutionBuild(result, "ttsc:");
444
457
  // Defaults: pinned by the previous hand-parser. `quiet` defaults true,
445
458
  // `--verbose` flips it to false; `emit` defaults `undefined` so the resolved
446
459
  // project controls ordinary build mode. `runCompatibleBuild` applies the
@@ -9,11 +9,13 @@ import { readProjectConfig } from "../../compiler/internal/project/readProjectCo
9
9
  import { resolveBinary } from "../../compiler/internal/resolveBinary";
10
10
  import { resolveTsgo } from "../../compiler/internal/resolveTsgo";
11
11
  import { outputText, spawnNative } from "../../compiler/internal/spawnNative";
12
+ import { createCanonicalTempDirectory } from "../../internal/createCanonicalTempDirectory";
12
13
  import {
13
14
  type ProjectInputPathIdentityContext,
14
15
  createProjectInputPathIdentityContext,
15
16
  resolveProjectInputPath,
16
17
  } from "../../internal/projectInputPathIdentity";
18
+ import { resolveNodeBinary } from "../../internal/resolveNodeBinary";
17
19
  import {
18
20
  hasProjectPluginEntries,
19
21
  loadProjectPlugins,
@@ -127,6 +129,9 @@ function resolveTtscserverEnv(argv: readonly string[]): TtscserverEnvironment {
127
129
  }
128
130
  const context = resolveLspExecutionContext(argv);
129
131
  const env = lspSidecarEnvironment({
132
+ cwd:
133
+ context.projectContext?.logicalProjectRoot ??
134
+ path.resolve(optionValue(argv, "--cwd") ?? process.cwd()),
130
135
  pluginConfigOrigin: context.projectContext?.pluginConfigOrigin,
131
136
  tsgoBinary: context.tsgoBinary,
132
137
  });
@@ -170,7 +175,7 @@ export function materializeLSPPluginManifest(manifest: unknown): {
170
175
  dispose(): void;
171
176
  path: string;
172
177
  } {
173
- const directory = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-lsp-"));
178
+ const directory = createCanonicalTempDirectory("ttsc-lsp-");
174
179
  const location = path.join(directory, "plugins.json");
175
180
  try {
176
181
  fs.writeFileSync(location, JSON.stringify(manifest), {
@@ -194,17 +199,20 @@ export function materializeLSPPluginManifest(manifest: unknown): {
194
199
  }
195
200
 
196
201
  function lspSidecarEnvironment(options: {
202
+ cwd: string;
197
203
  pluginConfigOrigin: string | undefined;
198
204
  tsgoBinary: string;
199
205
  }): NodeJS.ProcessEnv {
200
206
  const env: NodeJS.ProcessEnv = {
201
207
  ...process.env,
202
- TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
203
208
  TTSC_TSGO_BINARY: options.tsgoBinary,
204
209
  TTSC_TTSX_BINARY:
205
210
  process.env.TTSC_TTSX_BINARY ??
206
211
  path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
207
212
  };
213
+ const node = resolveNodeBinary(env, options.cwd);
214
+ if (node === undefined) delete env.TTSC_NODE_BINARY;
215
+ else env.TTSC_NODE_BINARY = node;
208
216
  if (options.pluginConfigOrigin === undefined) {
209
217
  delete env.TTSC_PLUGIN_CONFIG_DIR;
210
218
  } else {
@@ -318,7 +326,13 @@ function loadLSPProjectPlugins(
318
326
  pluginConfigDir: pluginConfigOrigin,
319
327
  tsconfig: project.identity.logicalConfigPath,
320
328
  })
321
- : { nativePlugins: [], project };
329
+ : {
330
+ hostInputHashes: {},
331
+ hostInputRealpaths: {},
332
+ hostInputs: [...project.configPaths],
333
+ nativePlugins: [],
334
+ project,
335
+ };
322
336
  }
323
337
 
324
338
  function captureInitialLSPProjectInputs(options: {
@@ -355,6 +369,7 @@ function captureInitialLSPProjectInputs(options: {
355
369
  );
356
370
  }
357
371
  const env = lspSidecarEnvironment({
372
+ cwd: options.project.root,
358
373
  pluginConfigOrigin: options.pluginConfigOrigin,
359
374
  tsgoBinary: options.tsgoBinary,
360
375
  });
@@ -11,6 +11,7 @@ import {
11
11
  parseFlags,
12
12
  } from "../../flags/parser";
13
13
  import { resolveFlagSpec } from "../../flags/schema";
14
+ import { assertNoSolutionBuild } from "./assertNoSolutionBuild";
14
15
  import { getCompilerVersionText } from "./getCompilerVersionText";
15
16
  import { prepareExecution } from "./prepareExecution";
16
17
  import { resolveCacheDir } from "./resolveCacheDir";
@@ -130,6 +131,7 @@ function parseCLI(argv: readonly string[]) {
130
131
  isPositional: looksLikeEntryFile,
131
132
  subcommand: "ttsx",
132
133
  });
134
+ assertNoSolutionBuild(result, "ttsx:");
133
135
 
134
136
  const entry = result.positional.find(looksLikeEntryFile);
135
137
  if (entry === undefined) {
@@ -276,6 +278,8 @@ function runPreparedEntry(
276
278
  depCacheDir,
277
279
  emitDir: execution.emitDir,
278
280
  emittedFiles: execution.emittedFiles,
281
+ entryFile: execution.entryFile,
282
+ entrySource: execution.entrySource,
279
283
  moduleOptions: execution.moduleOptions,
280
284
  projectRoot: execution.projectRoot,
281
285
  rootDir: execution.rootDir,