ttsc 0.21.0 → 0.23.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/cmd/ttscserver/main.go +79 -3
  2. package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +96 -16
  3. package/driver/lsp.go +4 -0
  4. package/go.mod +1 -1
  5. package/internal/lspserver/lsp_document_diagnostics_omits_cached_project_when_current_result_has_none_test.go +48 -0
  6. package/internal/lspserver/lsp_executable_project_input_change_requests_launcher_restart_test.go +98 -0
  7. package/internal/lspserver/lsp_external_watched_change_retains_program_test.go +53 -0
  8. package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +9 -0
  9. package/internal/lspserver/lsp_launcher_reload_directory_fingerprint_parity_windows_test.go +94 -0
  10. package/internal/lspserver/lsp_native_plugin_source.go +171 -55
  11. package/internal/lspserver/lsp_native_resident.go +110 -9
  12. package/internal/lspserver/lsp_plugin.go +6 -0
  13. package/internal/lspserver/lsp_project_context_transport_identity_test.go +43 -0
  14. package/internal/lspserver/lsp_project_diagnostics.go +235 -0
  15. package/internal/lspserver/lsp_project_diagnostics_capability_is_independent_test.go +67 -0
  16. package/internal/lspserver/lsp_project_diagnostics_program_input_overlap_refreshes_all_producers_test.go +78 -0
  17. package/internal/lspserver/lsp_project_diagnostics_refresh_accumulates_owner_freshness_test.go +65 -0
  18. package/internal/lspserver/lsp_project_diagnostics_refresh_keeps_each_producer_last_good_test.go +90 -0
  19. package/internal/lspserver/lsp_project_diagnostics_refreshes_only_input_owners_test.go +62 -0
  20. package/internal/lspserver/lsp_project_diagnostics_resident_unsupported_falls_back_test.go +123 -0
  21. package/internal/lspserver/lsp_project_diagnostics_resume_preserves_generation_test.go +52 -0
  22. package/internal/lspserver/lsp_project_diagnostics_translates_relative_client_project_test.go +90 -0
  23. package/internal/lspserver/lsp_project_input_glob_path_other.go +16 -0
  24. package/internal/lspserver/lsp_project_input_glob_path_windows.go +36 -0
  25. package/internal/lspserver/lsp_project_input_ownership_resolves_declared_spellings_test.go +95 -0
  26. package/internal/lspserver/lsp_project_input_path_key_other.go +17 -0
  27. package/internal/lspserver/lsp_project_input_path_key_windows.go +228 -0
  28. package/internal/lspserver/lsp_project_input_path_key_windows_test.go +460 -0
  29. package/internal/lspserver/lsp_project_input_registration.go +504 -0
  30. package/internal/lspserver/lsp_project_input_registration_rechecks_selection_baseline_test.go +71 -0
  31. package/internal/lspserver/lsp_project_input_registration_reconciles_rejected_pending_request_test.go +268 -0
  32. package/internal/lspserver/lsp_project_input_registration_shapes_patterns_test.go +94 -0
  33. package/internal/lspserver/lsp_project_input_watchers_anchor_missing_paths_test.go +48 -0
  34. package/internal/lspserver/lsp_project_inputs.go +1012 -0
  35. package/internal/lspserver/lsp_project_inputs_match_missing_exact_and_glob_test.go +129 -0
  36. package/internal/lspserver/lsp_project_inputs_reject_invalid_and_foreign_snapshots_test.go +110 -0
  37. package/internal/lspserver/lsp_proxy.go +591 -28
  38. package/internal/lspserver/lsp_reload_directories_compare_immediate_topology_test.go +188 -0
  39. package/internal/lspserver/lsp_reload_directories_separate_glob_territory_test.go +181 -0
  40. package/internal/lspserver/lsp_reload_file_fingerprint_preserves_raw_symlink_target_test.go +47 -0
  41. package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +1 -1
  42. package/internal/lspserver/lsp_resident_invalidation_broadcasts_program_input_overlap_test.go +60 -0
  43. package/internal/lspserver/lsp_resident_invalidation_targets_project_input_owners_test.go +51 -0
  44. package/internal/lspserver/lsp_same_transport_entries_are_deduplicated_test.go +66 -0
  45. package/lib/compiler/internal/project/readProjectConfig.d.ts +5 -3
  46. package/lib/compiler/internal/project/readProjectConfig.js +46 -14
  47. package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
  48. package/lib/compiler/internal/residentCheckProcess.d.ts +46 -0
  49. package/lib/compiler/internal/residentCheckProcess.js +227 -0
  50. package/lib/compiler/internal/residentCheckProcess.js.map +1 -0
  51. package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -1
  52. package/lib/compiler/internal/resolveEmittedJavaScript.js +35 -18
  53. package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
  54. package/lib/compiler/internal/runBuild.d.ts +64 -1
  55. package/lib/compiler/internal/runBuild.js +484 -7
  56. package/lib/compiler/internal/runBuild.js.map +1 -1
  57. package/lib/compiler/internal/runSingleFileEmit.js +1 -0
  58. package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
  59. package/lib/flags/parser.js +13 -17
  60. package/lib/flags/parser.js.map +1 -1
  61. package/lib/flags/schema.d.ts +10 -3
  62. package/lib/flags/schema.js +99 -0
  63. package/lib/flags/schema.js.map +1 -1
  64. package/lib/internal/projectInputPathIdentity.d.ts +31 -0
  65. package/lib/internal/projectInputPathIdentity.js +276 -0
  66. package/lib/internal/projectInputPathIdentity.js.map +1 -0
  67. package/lib/internal/windowsDirectoryCaseSensitivity.d.ts +10 -0
  68. package/lib/internal/windowsDirectoryCaseSensitivity.js +34 -0
  69. package/lib/internal/windowsDirectoryCaseSensitivity.js.map +1 -0
  70. package/lib/launcher/internal/runTtsc.d.ts +17 -0
  71. package/lib/launcher/internal/runTtsc.js +126 -88
  72. package/lib/launcher/internal/runTtsc.js.map +1 -1
  73. package/lib/launcher/internal/runTtscserver.d.ts +13 -1
  74. package/lib/launcher/internal/runTtscserver.js +468 -47
  75. package/lib/launcher/internal/runTtscserver.js.map +1 -1
  76. package/lib/launcher/internal/runTtsx.js +61 -7
  77. package/lib/launcher/internal/runTtsx.js.map +1 -1
  78. package/lib/launcher/internal/runtimeHooks.d.ts +6 -5
  79. package/lib/launcher/internal/runtimeHooks.js +7 -15
  80. package/lib/launcher/internal/runtimeHooks.js.map +1 -1
  81. package/lib/launcher/internal/singleFileOutput.d.ts +16 -0
  82. package/lib/launcher/internal/singleFileOutput.js +103 -0
  83. package/lib/launcher/internal/singleFileOutput.js.map +1 -0
  84. package/lib/launcher/internal/watchTopology.d.ts +288 -2
  85. package/lib/launcher/internal/watchTopology.js +1921 -54
  86. package/lib/launcher/internal/watchTopology.js.map +1 -1
  87. package/lib/plugin/internal/descriptorProcessFailure.d.ts +32 -0
  88. package/lib/plugin/internal/descriptorProcessFailure.js +73 -0
  89. package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -0
  90. package/lib/plugin/internal/loadProjectPlugins.d.ts +1 -0
  91. package/lib/plugin/internal/loadProjectPlugins.js +34 -8
  92. package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
  93. package/lib/structures/ITtscPluginCapabilities.d.ts +31 -0
  94. package/lib/structures/internal/ITtscProjectInputSnapshot.d.ts +29 -0
  95. package/lib/structures/internal/ITtscProjectInputSnapshot.js +3 -0
  96. package/lib/structures/internal/ITtscProjectInputSnapshot.js.map +1 -0
  97. package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -0
  98. package/package.json +12 -8
  99. package/src/compiler/internal/project/readProjectConfig.ts +62 -8
  100. package/src/compiler/internal/residentCheckProcess.ts +279 -0
  101. package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -21
  102. package/src/compiler/internal/runBuild.ts +728 -12
  103. package/src/compiler/internal/runSingleFileEmit.ts +1 -0
  104. package/src/flags/parser.ts +17 -17
  105. package/src/flags/schema.ts +112 -3
  106. package/src/internal/projectInputPathIdentity.ts +377 -0
  107. package/src/internal/windowsDirectoryCaseSensitivity.ts +33 -0
  108. package/src/launcher/internal/runTtsc.ts +148 -117
  109. package/src/launcher/internal/runTtscserver.ts +633 -56
  110. package/src/launcher/internal/runTtsx.ts +87 -10
  111. package/src/launcher/internal/runtimeHooks.ts +16 -16
  112. package/src/launcher/internal/singleFileOutput.ts +133 -0
  113. package/src/launcher/internal/watchTopology.ts +2494 -74
  114. package/src/plugin/internal/descriptorProcessFailure.ts +100 -0
  115. package/src/plugin/internal/loadProjectPlugins.ts +49 -8
  116. package/src/structures/ITtscPluginCapabilities.ts +34 -0
  117. package/src/structures/internal/ITtscProjectInputSnapshot.ts +29 -0
  118. package/src/structures/internal/TtscSingleFileEmitOptions.ts +5 -0
  119. package/tools/shim_audit/baseline.json +0 -9
@@ -299,20 +299,52 @@ function runPreparedEntry(
299
299
  sourceEntry,
300
300
  ...parsed.passthrough,
301
301
  ];
302
+ const evaluatorBoundary = readEvaluatorBoundary(process.env);
303
+ const runtimeEnv: NodeJS.ProcessEnv = {
304
+ ...process.env,
305
+ NODE_OPTIONS: appendNodeOption(
306
+ process.env.NODE_OPTIONS,
307
+ `--require ${JSON.stringify(path.join(__dirname, "runtimeHookPreload.js"))}`,
308
+ ),
309
+ TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgo,
310
+ TTSX_RUNTIME_MANIFEST: manifestPath,
311
+ };
312
+ delete runtimeEnv[TTSC_TTSX_EVALUATOR_DEADLINE_ENV];
313
+ delete runtimeEnv[TTSC_TTSX_EVALUATOR_MAX_BUFFER_ENV];
314
+ delete runtimeEnv[TTSC_TTSX_EVALUATOR_STATUS_FD_ENV];
315
+
302
316
  const result = spawnSync(process.execPath, args, {
303
317
  cwd,
304
- env: {
305
- ...process.env,
306
- NODE_OPTIONS: appendNodeOption(
307
- process.env.NODE_OPTIONS,
308
- `--require ${JSON.stringify(path.join(__dirname, "runtimeHookPreload.js"))}`,
309
- ),
310
- TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgo,
311
- TTSX_RUNTIME_MANIFEST: manifestPath,
312
- },
313
- stdio: "inherit",
318
+ env: runtimeEnv,
319
+ ...(evaluatorBoundary
320
+ ? {
321
+ killSignal: "SIGKILL" as const,
322
+ timeout: Math.max(
323
+ 1,
324
+ Math.min(
325
+ MAX_CHILD_PROCESS_TIMEOUT_MS,
326
+ evaluatorBoundary.deadlineMs - Date.now(),
327
+ ),
328
+ ),
329
+ maxBuffer: evaluatorBoundary.maxBuffer,
330
+ stdio: ["inherit", "pipe", "pipe"] as const,
331
+ }
332
+ : { stdio: "inherit" as const }),
314
333
  windowsHide: true,
315
334
  });
335
+ const nestedCode = (result.error as NodeJS.ErrnoException | undefined)
336
+ ?.code;
337
+ if (
338
+ evaluatorBoundary &&
339
+ (nestedCode === "ETIMEDOUT" || nestedCode === "ENOBUFS")
340
+ ) {
341
+ reportEvaluatorFailure(evaluatorBoundary.statusFd, nestedCode);
342
+ return 1;
343
+ }
344
+ if (evaluatorBoundary) {
345
+ if (result.stdout) process.stdout.write(result.stdout);
346
+ if (result.stderr) process.stderr.write(result.stderr);
347
+ }
316
348
  if (result.error) {
317
349
  process.stderr.write(`${result.error.message}\n`);
318
350
  return 1;
@@ -323,6 +355,51 @@ function runPreparedEntry(
323
355
  }
324
356
  }
325
357
 
358
+ interface TtsxEvaluatorBoundary {
359
+ deadlineMs: number;
360
+ maxBuffer: number;
361
+ statusFd: number;
362
+ }
363
+
364
+ const TTSC_TTSX_EVALUATOR_DEADLINE_ENV = "TTSC_TTSX_EVALUATOR_DEADLINE_MS";
365
+ const TTSC_TTSX_EVALUATOR_MAX_BUFFER_ENV =
366
+ "TTSC_TTSX_EVALUATOR_MAX_BUFFER_BYTES";
367
+ const TTSC_TTSX_EVALUATOR_STATUS_FD_ENV = "TTSC_TTSX_EVALUATOR_STATUS_FD";
368
+ const TTSC_TTSX_EVALUATOR_STATUS_FD = 3;
369
+ const MAX_CHILD_PROCESS_TIMEOUT_MS = 2_147_483_647;
370
+
371
+ function readEvaluatorBoundary(
372
+ env: NodeJS.ProcessEnv,
373
+ ): TtsxEvaluatorBoundary | undefined {
374
+ const deadlineMs = Number(env[TTSC_TTSX_EVALUATOR_DEADLINE_ENV]);
375
+ const maxBuffer = Number(env[TTSC_TTSX_EVALUATOR_MAX_BUFFER_ENV]);
376
+ const statusFd = Number(env[TTSC_TTSX_EVALUATOR_STATUS_FD_ENV]);
377
+ if (
378
+ !Number.isSafeInteger(deadlineMs) ||
379
+ deadlineMs <= 0 ||
380
+ !Number.isSafeInteger(maxBuffer) ||
381
+ maxBuffer <= 0 ||
382
+ maxBuffer > MAX_CHILD_PROCESS_TIMEOUT_MS ||
383
+ statusFd !== TTSC_TTSX_EVALUATOR_STATUS_FD
384
+ ) {
385
+ return undefined;
386
+ }
387
+ return { deadlineMs, maxBuffer, statusFd };
388
+ }
389
+
390
+ function reportEvaluatorFailure(
391
+ statusFd: number,
392
+ code: "ENOBUFS" | "ETIMEDOUT",
393
+ ): void {
394
+ try {
395
+ fs.writeSync(statusFd, code);
396
+ } catch {
397
+ // The boundary is an internal parent/child protocol. If a standalone ttsx
398
+ // caller supplied only the environment variables, preserve the ordinary
399
+ // non-zero bounded-process result instead of replacing it with an fd error.
400
+ }
401
+ }
402
+
326
403
  function removeRuntimeOutput(directory: string): void {
327
404
  try {
328
405
  fs.rmSync(directory, { force: true, recursive: true });
@@ -15,6 +15,10 @@ import { resolveEmittedJavaScript } from "../../compiler/internal/resolveEmitted
15
15
  import { resolveTsgo } from "../../compiler/internal/resolveTsgo";
16
16
  import { runBuild } from "../../compiler/internal/runBuild";
17
17
  import { outputText, spawnNative } from "../../compiler/internal/spawnNative";
18
+ import {
19
+ type FilesystemPathIdentityContext,
20
+ createFilesystemPathIdentityContext,
21
+ } from "../../internal/projectInputPathIdentity";
18
22
  import { inlineServedSourceMap } from "./servedSourceMap";
19
23
 
20
24
  /**
@@ -1143,23 +1147,19 @@ function serveEntryEmit(real: string): ServedSource | null {
1143
1147
  * Both sides are normalized to native separators first: a manifest `rootDir`
1144
1148
  * arrives slash-normalized from the synthesized tsconfig (`C:/` on Windows)
1145
1149
  * while `real` paths are native, and a raw prefix comparison across the two
1146
- * forms silently never matches. Windows paths also compare case-insensitively
1147
- * (a lowercase `TEMP` yields a `c:`-rooted `rootDir` for the same volume the
1148
- * real paths spell `C:`). Exported for direct exercise by the ttsx e2e suite —
1149
- * spawned runs cannot pin the Windows normalization branches on CI.
1150
+ * forms silently never matches. Filesystem identity preserves ordinary Windows
1151
+ * aliases while keeping case-distinct paths under an opted-in directory
1152
+ * separate. Exported for direct exercise by the ttsx e2e suite — spawned runs
1153
+ * cannot pin both Windows case-semantics branches on CI.
1150
1154
  */
1151
- export function isWithin(real: string, directory: string): boolean {
1152
- const fold = (value: string): string =>
1153
- process.platform === "win32"
1154
- ? path.normalize(value).toLowerCase()
1155
- : path.normalize(value);
1156
- const target = fold(real);
1157
- const dir = fold(directory);
1158
- if (target === dir) {
1159
- return true;
1160
- }
1161
- const prefix = dir.endsWith(path.sep) ? dir : dir + path.sep;
1162
- return target.startsWith(prefix);
1155
+ export function isWithin(
1156
+ real: string,
1157
+ directory: string,
1158
+ identities: FilesystemPathIdentityContext = createFilesystemPathIdentityContext(
1159
+ { throwOnRealpathError: false },
1160
+ ),
1161
+ ): boolean {
1162
+ return identities.isWithin(directory, real);
1163
1163
  }
1164
1164
 
1165
1165
  /**
@@ -0,0 +1,133 @@
1
+ import path from "node:path";
2
+
3
+ import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
4
+ import { resolveFlagSpec } from "../../flags/schema";
5
+
6
+ /**
7
+ * Resolves the only file that positional `ttsc <source>` can materialize in the
8
+ * user's tree.
9
+ *
10
+ * The compiler itself emits into a private temporary directory. The launcher
11
+ * then copies one transformed JavaScript file to this path, so project-mode
12
+ * declaration, map, build-info, outFile, and broad outDir products are not
13
+ * positional outputs.
14
+ */
15
+ export function resolveSingleFileOutput(options: {
16
+ cliOutDir?: string;
17
+ cwd: string;
18
+ file: string;
19
+ passthrough?: readonly string[];
20
+ tsconfig?: string;
21
+ }): string {
22
+ const project = readProjectSettings(options);
23
+ const extension = singleFileJavaScriptExtension(
24
+ options.file,
25
+ passthroughStringOption(options.passthrough, "--jsx") ?? project?.jsx,
26
+ );
27
+ const jsBasename =
28
+ path.basename(options.file).replace(/\.(?:[cm]?tsx?|jsx)$/i, "") +
29
+ extension;
30
+
31
+ if (options.cliOutDir) {
32
+ const relative = path.relative(options.cwd, options.file);
33
+ const jsRelative =
34
+ relative.slice(0, relative.length - path.extname(relative).length) +
35
+ extension;
36
+ return path.resolve(options.cwd, options.cliOutDir, jsRelative);
37
+ }
38
+
39
+ if (project?.outDir !== undefined) {
40
+ const fromRoot = path.relative(project.rootDir, options.file);
41
+ if (fromRoot !== "" && !isOutsideSingleFileLayout(fromRoot)) {
42
+ const jsRelative =
43
+ fromRoot.slice(0, fromRoot.length - path.extname(fromRoot).length) +
44
+ extension;
45
+ return path.resolve(project.outDir, jsRelative);
46
+ }
47
+ return path.resolve(project.outDir, jsBasename);
48
+ }
49
+
50
+ return options.file.replace(/\.(?:[cm]?tsx?|jsx)$/i, extension);
51
+ }
52
+
53
+ function readProjectSettings(options: {
54
+ cwd: string;
55
+ file: string;
56
+ tsconfig?: string;
57
+ }): { jsx?: string; outDir?: string; rootDir: string } | null {
58
+ try {
59
+ const project = readProjectConfig({
60
+ cwd: options.cwd,
61
+ file: options.file,
62
+ tsconfig: options.tsconfig,
63
+ });
64
+ const outDir = project.compilerOptions.outDir;
65
+ const rawRoot = project.compilerOptions.rootDir;
66
+ const rootDir =
67
+ typeof rawRoot === "string" && rawRoot.length !== 0
68
+ ? path.isAbsolute(rawRoot)
69
+ ? rawRoot
70
+ : path.resolve(project.root, rawRoot)
71
+ : project.root;
72
+ const rawJsx = project.compilerOptions.jsx;
73
+ return {
74
+ jsx:
75
+ typeof rawJsx === "string" && rawJsx.length !== 0
76
+ ? rawJsx.toLowerCase()
77
+ : undefined,
78
+ outDir:
79
+ typeof outDir === "string" && outDir.length !== 0 ? outDir : undefined,
80
+ rootDir,
81
+ };
82
+ } catch {
83
+ return null;
84
+ }
85
+ }
86
+
87
+ function isOutsideSingleFileLayout(relative: string): boolean {
88
+ return (
89
+ relative === ".." ||
90
+ relative.startsWith(`..${path.sep}`) ||
91
+ path.isAbsolute(relative)
92
+ );
93
+ }
94
+
95
+ function singleFileJavaScriptExtension(
96
+ file: string,
97
+ jsx: string | undefined,
98
+ ): string {
99
+ switch (path.extname(file).toLowerCase()) {
100
+ case ".mts":
101
+ return ".mjs";
102
+ case ".cts":
103
+ return ".cjs";
104
+ case ".tsx":
105
+ case ".jsx":
106
+ return jsx === "preserve" ? ".jsx" : ".js";
107
+ default:
108
+ return ".js";
109
+ }
110
+ }
111
+
112
+ function passthroughStringOption(
113
+ tokens: readonly string[] | undefined,
114
+ name: string,
115
+ ): string | undefined {
116
+ let value: string | undefined;
117
+ for (let index = 0; index < (tokens?.length ?? 0); index++) {
118
+ const token = tokens?.[index];
119
+ if (
120
+ token === undefined ||
121
+ token.includes("=") ||
122
+ resolveFlagSpec(token)?.name !== resolveFlagSpec(name)?.name
123
+ ) {
124
+ continue;
125
+ }
126
+ const next = tokens?.[index + 1];
127
+ if (next !== undefined) {
128
+ value = next.toLowerCase();
129
+ index++;
130
+ }
131
+ }
132
+ return value;
133
+ }