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
@@ -11,6 +11,7 @@ import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscPa
11
11
  import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
12
12
  import { buildNativeCompiler } from "./buildNativeCompiler";
13
13
  import { packageRootDir } from "./paths";
14
+ import { createNativeProjectContextArgs } from "./project/createNativeProjectContextArgs";
14
15
  import { readProjectConfig } from "./project/readProjectConfig";
15
16
  import { resolveBinary } from "./resolveBinary";
16
17
  import { resolveTsgo } from "./resolveTsgo";
@@ -18,6 +19,7 @@ import { appendBuildOutput, normalizeBuildOutput } from "./runBuild";
18
19
  import {
19
20
  assertSharedHostCompatibility,
20
21
  linkedTransformPlugins,
22
+ resolvePluginConfigDir,
21
23
  selectSharedHostPlugin,
22
24
  } from "./sharedHostHelpers";
23
25
  import { outputText, spawnNative } from "./spawnNative";
@@ -126,6 +128,8 @@ function transformProjectWithPlugins(
126
128
  cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
127
129
  cwd,
128
130
  entries: options.plugins,
131
+ env: { ...process.env, ...options.env },
132
+ pluginConfigDir: options.pluginConfigDir,
129
133
  projectRoot: options.projectRoot,
130
134
  tsconfig: project.path,
131
135
  });
@@ -167,7 +171,11 @@ function transformProjectWithPlugins(
167
171
  const plugin = selectSharedHostPlugin(transformers);
168
172
  const res = spawnNative(
169
173
  plugin.binary,
170
- createNativeTransformArgs(project, transformers),
174
+ createNativeTransformArgs(
175
+ project,
176
+ transformers,
177
+ resolvePluginConfigDir(options),
178
+ ),
171
179
  {
172
180
  cwd: project.root,
173
181
  env: nativePluginEnv(options, tsgoBinary, loaded.nativePlugins, plugin),
@@ -217,7 +225,12 @@ function runNativeChecks(
217
225
  for (const plugin of checks) {
218
226
  const res = spawnNative(
219
227
  plugin.binary,
220
- createNativeCheckArgs(project, nativePlugins),
228
+ createNativeCheckArgs(
229
+ project,
230
+ nativePlugins,
231
+ plugin,
232
+ resolvePluginConfigDir(options),
233
+ ),
221
234
  {
222
235
  cwd: project.root,
223
236
  env: nativePluginEnv(options, tsgoBinary, nativePlugins, plugin),
@@ -250,26 +263,39 @@ function runNativeChecks(
250
263
  function createNativeTransformArgs(
251
264
  project: ITtscParsedProjectConfig,
252
265
  plugins: readonly ITtscLoadedNativePlugin[],
266
+ pluginConfigOrigin?: string,
253
267
  ): string[] {
254
- return [
268
+ const args = [
255
269
  "transform",
256
270
  "--tsconfig=" + project.path,
257
271
  "--plugins-json=" + serializeNativePlugins(plugins),
258
272
  "--cwd=" + project.root,
259
273
  ];
274
+ if (
275
+ selectSharedHostPlugin(plugins).capabilities?.projectContextArgs === true
276
+ ) {
277
+ args.push(...createNativeProjectContextArgs(project, pluginConfigOrigin));
278
+ }
279
+ return args;
260
280
  }
261
281
 
262
282
  /** Build the CLI argument list for the `check` subcommand. */
263
283
  function createNativeCheckArgs(
264
284
  project: ITtscParsedProjectConfig,
265
285
  plugins: readonly ITtscLoadedNativePlugin[],
286
+ plugin: ITtscLoadedNativePlugin,
287
+ pluginConfigOrigin?: string,
266
288
  ): string[] {
267
- return [
289
+ const args = [
268
290
  "check",
269
291
  "--tsconfig=" + project.path,
270
292
  "--plugins-json=" + serializeNativePlugins(plugins),
271
293
  "--cwd=" + project.root,
272
294
  ];
295
+ if (plugin.capabilities?.projectContextArgs === true) {
296
+ args.push(...createNativeProjectContextArgs(project, pluginConfigOrigin));
297
+ }
298
+ return args;
273
299
  }
274
300
 
275
301
  /**
@@ -291,8 +317,11 @@ function serializeNativePlugins(
291
317
  /**
292
318
  * Build the environment for a native plugin spawn. Injects `TTSC_NODE_BINARY`,
293
319
  * `TTSC_TSGO_BINARY`, and `TTSC_TTSX_BINARY` so the sidecar can re-invoke
294
- * Node.js or tsgo without searching PATH. For transform plugins, also passes
295
- * `TTSC_LINKED_PLUGINS_JSON` when linked sources are present.
320
+ * Node.js or tsgo without searching PATH, plus `TTSC_PLUGIN_CONFIG_DIR` when
321
+ * the caller declared a plugin config anchor (an embedder compiling through a
322
+ * generated wrapper tsconfig) so config-file discovery walks the real project
323
+ * instead of the wrapper's temp-dir ancestry. For transform plugins, also
324
+ * passes `TTSC_LINKED_PLUGINS_JSON` when linked sources are present.
296
325
  */
297
326
  function nativePluginEnv(
298
327
  options: ITtscCompilerContext,
@@ -300,15 +329,29 @@ function nativePluginEnv(
300
329
  nativePlugins?: readonly ITtscLoadedNativePlugin[],
301
330
  plugin?: ITtscLoadedNativePlugin,
302
331
  ): NodeJS.ProcessEnv {
332
+ const pluginConfigDir = resolvePluginConfigDir(options);
303
333
  const env: NodeJS.ProcessEnv = {
304
334
  ...process.env,
305
335
  TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
336
+ ...(pluginConfigDir === undefined
337
+ ? {}
338
+ : { TTSC_PLUGIN_CONFIG_DIR: pluginConfigDir }),
306
339
  TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgoBinary,
307
340
  TTSC_TTSX_BINARY:
308
341
  process.env.TTSC_TTSX_BINARY ??
309
342
  path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
310
343
  ...options.env,
311
344
  };
345
+ // The anchor is per-invocation state owned by this host: when this run
346
+ // declared none (and the caller's env does not name one), drop any value
347
+ // inherited from an ancestor ttsc process so a nested build never
348
+ // mis-anchors its plugins at the outer project.
349
+ if (
350
+ pluginConfigDir === undefined &&
351
+ options.env?.TTSC_PLUGIN_CONFIG_DIR === undefined
352
+ ) {
353
+ delete env.TTSC_PLUGIN_CONFIG_DIR;
354
+ }
312
355
  if (plugin?.stage === "transform") {
313
356
  const linked = linkedTransformPlugins(nativePlugins ?? []);
314
357
  if (linked.length !== 0) {
@@ -58,6 +58,23 @@ export interface ParseOptions {
58
58
  * `--` is appended to `passthrough` as-is (ttsx already does this).
59
59
  */
60
60
  readonly honorDoubleDashSeparator?: boolean;
61
+ /**
62
+ * Classifies a bare (non-dash) token as a genuine positional argument (a
63
+ * source file, the ttsx entry, a project path) rather than the
64
+ * space-separated value of a preceding forwarded flag.
65
+ *
66
+ * When omitted, every bare token is a positional — the historical behaviour
67
+ * for project-shaped subcommands that never forward `--flag value` pairs.
68
+ *
69
+ * When provided, a bare token that fails the predicate is appended to
70
+ * `passthrough` in its original position instead of `positional`, so an
71
+ * unknown `--flag value` pair reaches tsgo with its adjacency and relative
72
+ * order intact. The parser deliberately does not guess a forwarded flag's
73
+ * arity from the flag itself (it has no schema for a truly unknown flag); the
74
+ * predicate is the only signal that separates a forwarded value from a real
75
+ * input file, and both callers key it on the TypeScript source extension.
76
+ */
77
+ readonly isPositional?: (token: string) => boolean;
61
78
  }
62
79
 
63
80
  /**
@@ -153,7 +170,17 @@ export function parseFlags(opts: ParseOptions): ParseResult {
153
170
  continue;
154
171
  }
155
172
 
156
- // Bare token: positional argument (file / entry / project path).
173
+ // Bare token: either a genuine positional argument (file / entry / project
174
+ // path) or the space-separated value of a preceding forwarded flag. When a
175
+ // caller forwards unknown flags it supplies `isPositional` to tell the two
176
+ // apart; a forwarded value is appended to `passthrough` in place so the
177
+ // `--flag value` pair reaches tsgo in its original order and adjacency,
178
+ // instead of being split into a separate bucket the caller later
179
+ // concatenates out of order.
180
+ if (opts.isPositional !== undefined && !opts.isPositional(current)) {
181
+ passthrough.push(current);
182
+ continue;
183
+ }
157
184
  positional.push(current);
158
185
  if (opts.forwardAfterFirstPositional === true && positional.length === 1) {
159
186
  forwardingTail = true;
@@ -477,6 +477,14 @@ export const FLAG_SCHEMA: readonly FlagSpec[] = [
477
477
  description:
478
478
  "JSON-encoded ttsc plugin manifest (internal: emitted by runBuild).",
479
479
  },
480
+ {
481
+ name: "--project-context-json",
482
+ kind: "value",
483
+ subcommands: ["build", "check", "fix", "format"],
484
+ consumedBy: ["lint"],
485
+ description:
486
+ "JSON-encoded lexical and physical project identity (internal: emitted by runBuild).",
487
+ },
480
488
  {
481
489
  name: "--manifest",
482
490
  kind: "value",
@@ -71,8 +71,12 @@ function createProjectContext(
71
71
  ) {
72
72
  const project = readProjectConfig(
73
73
  options.project
74
- ? { cwd, tsconfig: path.resolve(cwd, options.project) }
75
- : { cwd, file: filename },
74
+ ? {
75
+ cwd,
76
+ projectRoot: options.projectRoot,
77
+ tsconfig: path.resolve(cwd, options.project),
78
+ }
79
+ : { cwd, file: filename, projectRoot: options.projectRoot },
76
80
  );
77
81
  const tsconfig = project.path;
78
82
  const root = project.root;
@@ -83,6 +87,7 @@ function createProjectContext(
83
87
  const processDir = path.join(cacheDir, "project", PROCESS_CACHE_KEY);
84
88
  const virtualRoot = path.join(processDir, "fs");
85
89
  return {
90
+ project,
86
91
  tsconfig,
87
92
  root,
88
93
  cacheDir,
@@ -102,6 +107,13 @@ function createProjectContext(
102
107
  typeof project.compilerOptions.module === "string"
103
108
  ? project.compilerOptions.module
104
109
  : undefined,
110
+ // Force a source map on the transient runtime emit only when the project
111
+ // configures none — when it already emits `sourceMap` or `inlineSourceMap`,
112
+ // the serve path inlines/absolutizes that map, so no override is needed
113
+ // (issue #353).
114
+ forceRuntimeSourceMap:
115
+ project.compilerOptions.sourceMap !== true &&
116
+ project.compilerOptions.inlineSourceMap !== true,
105
117
  built: false,
106
118
  emittedFiles: undefined as string[] | undefined,
107
119
  };
@@ -139,8 +151,16 @@ function buildProject(
139
151
  cacheDir: context.pluginCacheDir,
140
152
  outDir: context.emitDir,
141
153
  passthrough: options.passthrough,
154
+ // Emit a source map on the transient entry emit (a PID-isolated temp dir,
155
+ // never the consumer's `outDir`) so the serve path can inline it under the
156
+ // source URL. Routed as a dedicated build option, not a forwarded tsgo
157
+ // flag, so it never reaches a native plugin host's argument parser (issue
158
+ // #353).
159
+ forceRuntimeSourceMap: context.forceRuntimeSourceMap,
160
+ pluginConfigDir: options.pluginConfigDir,
142
161
  plugins: options.plugins,
143
162
  quiet: true,
163
+ resolvedProject: context.project,
144
164
  singleThreaded: options.singleThreaded,
145
165
  tsconfig: context.tsconfig,
146
166
  });
@@ -1,11 +1,9 @@
1
- import { spawnSync } from "node:child_process";
2
1
  import fs from "node:fs";
3
2
  import path from "node:path";
4
3
 
5
4
  import { TtscCompiler } from "../../TtscCompiler";
6
5
  import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
7
6
  import { resolveProjectConfig } from "../../compiler/internal/project/resolveProjectConfig";
8
- import { resolveBinary } from "../../compiler/internal/resolveBinary";
9
7
  import { runBuild } from "../../compiler/internal/runBuild";
10
8
  import { runSingleFileEmit } from "../../compiler/internal/runSingleFileEmit";
11
9
  import {
@@ -68,8 +66,6 @@ export function runTtsc(
68
66
  return runClean(rest);
69
67
  case "prepare":
70
68
  return runPrepare(rest);
71
- case "demo":
72
- return delegateToNative(argv);
73
69
  case "-p":
74
70
  case "--project":
75
71
  return runCompatibleBuild(argv, "build");
@@ -391,54 +387,6 @@ function isOutsideRelativePath(relative: string): boolean {
391
387
  );
392
388
  }
393
389
 
394
- function delegateToNative(argv: readonly string[]): number {
395
- const bin = resolveBinary();
396
- if (!bin) {
397
- process.stderr.write(
398
- [
399
- `ttsc: platform-specific helper binary not found (@ttsc/${process.platform}-${process.arch}).`,
400
- `Set TTSC_BINARY to an absolute helper path or reinstall with optional dependencies enabled.`,
401
- ].join("\n") + "\n",
402
- );
403
- return 1;
404
- }
405
- const viaNode = /\.(?:[cm]?js|ts)$/i.test(bin);
406
- if (!viaNode) {
407
- ensureExecutable(bin);
408
- }
409
- const result = spawnSync(
410
- viaNode ? process.execPath : bin,
411
- viaNode ? [bin, ...argv] : [...argv],
412
- {
413
- stdio: "inherit",
414
- env: process.env,
415
- windowsHide: true,
416
- },
417
- );
418
- if (result.error) {
419
- process.stderr.write(`${result.error.message}\n`);
420
- return 1;
421
- }
422
- return result.status ?? 1;
423
- }
424
-
425
- function ensureExecutable(binary: string): void {
426
- if (process.platform === "win32") {
427
- return;
428
- }
429
- try {
430
- fs.accessSync(binary, fs.constants.X_OK);
431
- return;
432
- } catch {
433
- try {
434
- const mode = fs.statSync(binary).mode & 0o777;
435
- fs.chmodSync(binary, mode | 0o755);
436
- } catch {
437
- /* keep the original spawn error path */
438
- }
439
- }
440
- }
441
-
442
390
  function parseProjectArgs(argv: readonly string[]) {
443
391
  // `prepare` and `clean` are project-shaped commands. They share the same
444
392
  // schema as the build lane; the engine forwards unknown flags as well as
@@ -461,6 +409,12 @@ function parseBuildArgs(argv: readonly string[], checkOnly: boolean) {
461
409
  const result = parseFlags({
462
410
  argv,
463
411
  errorPrefix: "ttsc:",
412
+ // A bare token is a single-file input only when it carries a TypeScript
413
+ // source extension; any other bare token is the space-separated value of a
414
+ // preceding forwarded flag (e.g. the `es2020` in `--target es2020`). The
415
+ // parser routes those values into `passthrough` in place, so the forwarded
416
+ // flag/value pairs reach tsgo in their original order.
417
+ isPositional: looksLikeInputFile,
464
418
  subcommand: "build",
465
419
  });
466
420
  // Defaults: pinned by the previous hand-parser. `quiet` defaults true,
@@ -476,15 +430,13 @@ function parseBuildArgs(argv: readonly string[], checkOnly: boolean) {
476
430
  else if (explicitNoEmit === true) emit = false;
477
431
  if (emit === undefined && checkOnly) emit = false;
478
432
 
479
- const files = result.positional.filter(looksLikeInputFile);
480
- // Bare non-file positionals are flag values (e.g. the `es2020` in
481
- // `--target es2020`). The engine left them in `positional` because the
482
- // forwarded flag is unknown to the schema; route them back into
483
- // passthrough so the user's pair reaches tsgo intact.
484
- const trailingValues = result.positional.filter(
485
- (token) => !looksLikeInputFile(token),
486
- );
487
- const passthrough = [...result.passthrough, ...trailingValues];
433
+ // `isPositional: looksLikeInputFile` guarantees every `result.positional`
434
+ // token is a TypeScript input file; forwarded flag values already live in
435
+ // `result.passthrough` in their original order, so no reconstruction is
436
+ // needed here (the previous `[...passthrough, ...trailingValues]` concat
437
+ // reordered every flag ahead of every value).
438
+ const files = [...result.positional];
439
+ const passthrough = [...result.passthrough];
488
440
 
489
441
  return {
490
442
  binary: getString(result, "--binary"),
@@ -11,6 +11,7 @@ import {
11
11
  loadProjectPlugins,
12
12
  } from "../../plugin/internal/loadProjectPlugins";
13
13
  import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
14
+ import type { ITtscProjectIdentity } from "../../structures/internal/ITtscProjectIdentity";
14
15
  import { resolveTtscserverBinary } from "./resolveTtscserverBinary";
15
16
 
16
17
  /**
@@ -107,9 +108,11 @@ function resolveTtscserverEnv(argv: readonly string[]): NodeJS.ProcessEnv {
107
108
  if (lspPlugins.length > 0) {
108
109
  env.TTSC_LSP_PLUGINS_JSON = JSON.stringify({
109
110
  plugins: serializeNativePlugins(context.nativePlugins),
111
+ projectContext: context.projectContext,
110
112
  lspPlugins: lspPlugins.map((plugin) => ({
111
113
  binary: plugin.binary,
112
114
  name: plugin.name,
115
+ projectContextArgs: plugin.capabilities?.projectContextArgs === true,
113
116
  stage: plugin.stage,
114
117
  })),
115
118
  });
@@ -119,10 +122,16 @@ function resolveTtscserverEnv(argv: readonly string[]): NodeJS.ProcessEnv {
119
122
 
120
123
  function resolveLspExecutionContext(argv: readonly string[]): {
121
124
  nativePlugins: readonly ITtscLoadedNativePlugin[];
125
+ projectContext?: ITtscProjectIdentity;
122
126
  tsgoBinary: string;
123
127
  } {
124
128
  const cwd = path.resolve(optionValue(argv, "--cwd") ?? process.cwd());
125
129
  const tsconfig = optionValue(argv, "--tsconfig");
130
+ const pluginConfigOrigin =
131
+ process.env.TTSC_PLUGIN_CONFIG_DIR === undefined ||
132
+ process.env.TTSC_PLUGIN_CONFIG_DIR === ""
133
+ ? undefined
134
+ : path.resolve(cwd, process.env.TTSC_PLUGIN_CONFIG_DIR);
126
135
  let project: ReturnType<typeof readProjectConfig>;
127
136
  try {
128
137
  project = readProjectConfig({ cwd, tsconfig });
@@ -149,12 +158,17 @@ function resolveLspExecutionContext(argv: readonly string[]): {
149
158
  ? loadProjectPlugins({
150
159
  binary: resolveBinary() ?? "",
151
160
  cwd,
161
+ pluginConfigDir: pluginConfigOrigin,
152
162
  projectRoot: project.root,
153
163
  tsconfig: project.path,
154
164
  })
155
165
  : { nativePlugins: [] };
156
166
  return {
157
167
  nativePlugins: loaded.nativePlugins,
168
+ projectContext: {
169
+ ...project.identity,
170
+ ...(pluginConfigOrigin === undefined ? {} : { pluginConfigOrigin }),
171
+ },
158
172
  tsgoBinary: tsgo.binary,
159
173
  };
160
174
  }
@@ -12,6 +12,7 @@ import {
12
12
  import { getCompilerVersionText } from "./getCompilerVersionText";
13
13
  import { prepareExecution } from "./prepareExecution";
14
14
  import { resolveCacheDir } from "./resolveCacheDir";
15
+ import { checkNodeRuntimeSupport } from "./runtimeHooks";
15
16
 
16
17
  /**
17
18
  * CLI entry point for `ttsx`. Type-checks the owning project via tsgo, emits
@@ -45,6 +46,17 @@ function run(argv: readonly string[]): number {
45
46
  return 0;
46
47
  }
47
48
 
49
+ // Refuse an unsupported Node.js before type-checking and spawning the child:
50
+ // the child inherits this process's Node version, so an early diagnostic here
51
+ // pre-empts both the Node 18 `--disable-warning` rejection and the Node 20
52
+ // missing-`registerHooks` TypeError with one actionable message. `--help` and
53
+ // `--version` are handled above so they still print on any Node.
54
+ const nodeSupport = checkNodeRuntimeSupport(process.versions.node);
55
+ if (nodeSupport !== null) {
56
+ process.stderr.write(`ttsx: ${nodeSupport}\n`);
57
+ return 2;
58
+ }
59
+
48
60
  const cwd = path.resolve(parsed.cwd ?? process.cwd());
49
61
  const entry = path.resolve(cwd, parsed.entry);
50
62
  if (!fs.existsSync(entry)) {
@@ -109,6 +121,14 @@ function parseCLI(argv: readonly string[]) {
109
121
  errorPrefix: "ttsx:",
110
122
  forwardAfterFirstPositional: true,
111
123
  honorDoubleDashSeparator: true,
124
+ // Only a TypeScript-extensioned bare token is the entry; every other bare
125
+ // token before it (e.g. the `es2020` in `--target es2020 entry.ts`) is a
126
+ // forwarded flag value. Classifying values via the predicate keeps them in
127
+ // `passthrough` in order AND stops a pre-entry value from being mistaken for
128
+ // the first positional sentinel — which previously flipped the parser into
129
+ // tail mode and pushed the real entry into `tail`, failing with
130
+ // "entry file is required".
131
+ isPositional: looksLikeEntryFile,
112
132
  subcommand: "ttsx",
113
133
  });
114
134
 
@@ -116,18 +136,13 @@ function parseCLI(argv: readonly string[]) {
116
136
  if (entry === undefined) {
117
137
  throw new Error("ttsx: entry file is required");
118
138
  }
119
- // With `forwardAfterFirstPositional: true` the parser reports
120
- // `result.positional` as just the entry, `result.passthrough` as flags
121
- // arriving BEFORE the entry (tsgo-forwarded), and `result.tail` as every
122
- // token AFTER the entry — those are the user program's argv (e.g. the
123
- // `generate --input src/input` tail of `ttsx typia.ts generate
124
- // --input src/input`) and MUST NOT reach tsgo. Anything in positional
125
- // that is not the entry is a pre-entry flag value (e.g. `--target es2020`)
126
- // that the parser stored positionally; forward those to tsgo with the
127
- // rest of `passthrough`.
128
- const preEntryValues: string[] = result.positional.filter(
129
- (token) => token !== entry && !looksLikeEntryFile(token),
130
- );
139
+ // With `forwardAfterFirstPositional: true` and `isPositional:
140
+ // looksLikeEntryFile`, the parser reports `result.positional` as just the
141
+ // entry, `result.passthrough` as the tsgo-forwarded flags (and their
142
+ // in-order space values) arriving BEFORE the entry, and `result.tail` as
143
+ // every token AFTER the entry — the user program's argv (e.g. the `generate
144
+ // --input src/input` tail of `ttsx typia.ts generate --input src/input`),
145
+ // which MUST NOT reach tsgo.
131
146
  const postEntryArgs: string[] = [...result.tail];
132
147
 
133
148
  const preload: string[] = [];
@@ -170,7 +185,7 @@ function parseCLI(argv: readonly string[]) {
170
185
  preload,
171
186
  project: getString(result, "--tsconfig"),
172
187
  singleThreaded: getBoolean(result, "--singleThreaded") === true,
173
- tsgoFlags: [...result.passthrough, ...preEntryValues],
188
+ tsgoFlags: [...result.passthrough],
174
189
  };
175
190
  }
176
191