ttsc 0.23.0 → 0.25.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.
- package/LICENSE +21 -21
- package/README.md +66 -63
- package/cmd/ttscgraph/serve.go +264 -48
- package/cmd/ttscgraph/serve_answers_a_project_that_implements_its_own_ambient_declaration_test.go +69 -0
- package/cmd/ttscgraph/serve_early_error_encode_failure_is_observable_test.go +40 -0
- package/cmd/ttscgraph/serve_graph_snapshot_version_matches_typescript_client_test.go +32 -0
- package/cmd/ttscgraph/serve_phase_trace_accounts_for_load_failure_test.go +45 -0
- package/cmd/ttscgraph/serve_phase_trace_is_opt_in_and_payload_free_test.go +52 -0
- package/cmd/ttscgraph/serve_protocol_negotiates_incremental_shards_test.go +44 -0
- package/cmd/ttscgraph/serve_session_reloads_commonjs_package_entry_for_esm_test.go +2 -2
- package/cmd/ttscgraph/serve_session_reloads_superseding_module_candidates_test.go +46 -20
- package/cmd/ttscgraph/serve_session_retries_failed_dump_test.go +9 -9
- package/cmd/ttscgraph/serve_shards.go +1390 -0
- package/cmd/ttscgraph/serve_shards_abandoned_generation_converges_on_retry_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_delete_removed_source_test.go +57 -0
- package/cmd/ttscgraph/serve_shards_external_endpoints_test.go +53 -0
- package/cmd/ttscgraph/serve_shards_keep_source_distributed_dependencies_at_the_boundary_test.go +108 -0
- package/cmd/ttscgraph/serve_shards_match_full_dump_oracle_test.go +88 -0
- package/cmd/ttscgraph/serve_shards_merge_pending_and_new_edits_test.go +50 -0
- package/cmd/ttscgraph/serve_shards_prune_unreferenced_external_endpoints_test.go +65 -0
- package/cmd/ttscgraph/serve_shards_publish_created_source_test.go +92 -0
- package/cmd/ttscgraph/serve_shards_rebuild_declaration_edit_test.go +43 -0
- package/cmd/ttscgraph/serve_shards_refresh_cross_file_implementation_test.go +63 -0
- package/cmd/ttscgraph/serve_shards_refresh_dependent_diagnostics_test.go +80 -0
- package/cmd/ttscgraph/serve_shards_rename_moves_source_identity_test.go +95 -0
- package/cmd/ttscgraph/serve_shards_retry_preserves_committed_generation_test.go +48 -0
- package/cmd/ttscgraph/serve_shards_reuse_unaffected_sources_test.go +121 -0
- package/driver/declaration_shape.go +85 -0
- package/driver/program.go +39 -10
- package/go.sum +22 -22
- package/internal/graph/assigned_implementations_of_declaration_file_symbols_leave_no_edge_source_test.go +84 -0
- package/internal/graph/assigned_implementations_of_node_modules_symbols_leave_no_edge_source_test.go +85 -0
- package/internal/graph/build.go +62 -10
- package/internal/graph/build_files_reuses_committed_cross_file_endpoints_test.go +87 -0
- package/internal/graph/build_keeps_source_distributed_dependencies_at_the_external_boundary_test.go +74 -0
- package/internal/graph/dump.go +84 -43
- package/internal/graph/dump_path_mapper_caches_alias_resolution_test.go +29 -0
- package/internal/graph/dump_paths.go +134 -11
- package/internal/graph/dump_paths_canonicalize_windows_short_root_test.go +39 -0
- package/internal/graph/dump_rejects_provenance_only_path_errors_test.go +32 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +2 -2
- package/internal/graph/dump_symlink_project_publishes_matching_base_test.go +82 -0
- package/internal/graph/edges.go +73 -17
- package/internal/graph/exports.go +9 -2
- package/internal/graph/graph.go +47 -7
- package/internal/graph/graph_kind_contracts_match_their_producers_test.go +121 -0
- package/internal/graph/node_modifiers_emit_union_strings_test.go +1 -1
- package/internal/graph/provenance.go +11 -1
- package/internal/graph/resolve.go +1 -4
- package/internal/graph/source.go +23 -0
- package/internal/graph/wire_node_ids_share_alias_resolution_test.go +29 -0
- package/internal/lspserver/lsp_native_plugin_source.go +5 -6
- package/internal/lspserver/lsp_native_resident.go +17 -24
- package/lib/TtscService.d.ts +1 -6
- package/lib/TtscService.js +2 -2
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/captureProcessOutput.d.ts +26 -0
- package/lib/compiler/internal/captureProcessOutput.js +92 -0
- package/lib/compiler/internal/captureProcessOutput.js.map +1 -0
- package/lib/compiler/internal/compileProjectInMemory.js +4 -7
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/residentCheckProcess.d.ts +0 -2
- package/lib/compiler/internal/residentCheckProcess.js +1 -15
- package/lib/compiler/internal/residentCheckProcess.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +0 -6
- package/lib/compiler/internal/residentTransformProcess.js +0 -25
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/spawnNative.d.ts +14 -6
- package/lib/compiler/internal/spawnNative.js +32 -11
- package/lib/compiler/internal/spawnNative.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.d.ts +1 -5
- package/lib/compiler/internal/startResidentTransform.js +1 -3
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.d.ts +2 -1
- package/lib/launcher/internal/prepareExecution.js +269 -23
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/registerRuntimeHooks.js +6 -1
- package/lib/launcher/internal/registerRuntimeHooks.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +4 -9
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +2 -55
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +25 -2
- package/lib/launcher/internal/runtimeHooks.js +344 -69
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.js +31 -3
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/descriptorProcessFailure.d.ts +23 -19
- package/lib/plugin/internal/descriptorProcessFailure.js +40 -51
- package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +12 -0
- package/lib/plugin/internal/loadProjectPlugins.js +77 -28
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/package.json +8 -8
- package/shim/ast/extra-shim.json +4 -4
- package/shim/ast/go.mod +14 -14
- package/shim/ast/go.sum +20 -20
- package/shim/astnav/go.mod +14 -14
- package/shim/astnav/go.sum +20 -20
- package/shim/bundled/extra-shim.json +1 -1
- package/shim/bundled/go.mod +5 -5
- package/shim/checker/extra-shim.json +31 -31
- package/shim/checker/go.mod +5 -5
- package/shim/compiler/extra-shim.json +4 -4
- package/shim/compiler/go.mod +14 -14
- package/shim/compiler/go.sum +22 -22
- package/shim/core/extra-shim.json +4 -4
- package/shim/core/go.mod +5 -5
- package/shim/diagnosticwriter/go.mod +5 -5
- package/shim/lsp/extra-shim.json +4 -4
- package/shim/lsp/go.mod +16 -16
- package/shim/lsp/go.sum +26 -26
- package/shim/parser/extra-shim.json +4 -4
- package/shim/parser/go.mod +5 -5
- package/shim/printer/go.mod +14 -14
- package/shim/printer/go.sum +22 -22
- package/shim/scanner/extra-shim.json +4 -4
- package/shim/scanner/go.mod +5 -5
- package/shim/transformers/go.mod +14 -14
- package/shim/transformers/go.sum +20 -20
- package/shim/tsoptions/extra-shim.json +4 -4
- package/shim/tsoptions/go.mod +5 -5
- package/shim/tspath/extra-shim.json +4 -4
- package/shim/tspath/go.mod +5 -5
- package/shim/vfs/cachedvfs/extra-shim.json +4 -4
- package/shim/vfs/cachedvfs/go.mod +5 -5
- package/shim/vfs/extra-shim.json +4 -4
- package/shim/vfs/go.mod +5 -5
- package/shim/vfs/osvfs/extra-shim.json +4 -4
- package/shim/vfs/osvfs/go.mod +5 -5
- package/src/TtscService.ts +8 -20
- package/src/compiler/internal/captureProcessOutput.ts +96 -0
- package/src/compiler/internal/compileProjectInMemory.ts +7 -7
- package/src/compiler/internal/residentCheckProcess.ts +1 -21
- package/src/compiler/internal/residentTransformProcess.ts +0 -42
- package/src/compiler/internal/spawnNative.ts +39 -18
- package/src/compiler/internal/startResidentTransform.ts +1 -12
- package/src/launcher/internal/prepareExecution.ts +297 -26
- package/src/launcher/internal/registerRuntimeHooks.ts +11 -2
- package/src/launcher/internal/runTtscserver.ts +4 -9
- package/src/launcher/internal/runTtsx.ts +2 -78
- package/src/launcher/internal/runtimeHooks.ts +385 -82
- package/src/plugin/internal/buildSourcePlugin.ts +36 -3
- package/src/plugin/internal/descriptorProcessFailure.ts +34 -65
- package/src/plugin/internal/loadProjectPlugins.ts +78 -39
- package/tools/gen_shims/go.mod +14 -14
- package/tools/gen_shims/go.sum +12 -12
- package/tools/shim_audit/go.mod +10 -10
- package/tools/shim_audit/go.sum +8 -8
|
@@ -88,9 +88,21 @@ interface LoadResult {
|
|
|
88
88
|
shortCircuit?: boolean;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* The compiler options that decide the emit format of a file, as declared by
|
|
93
|
+
* the project that emitted it. Both fields matter: tsgo derives the module kind
|
|
94
|
+
* from `target` whenever `module` is absent, so carrying only `module` cannot
|
|
95
|
+
* reproduce its decision.
|
|
96
|
+
*/
|
|
97
|
+
export interface OwningModuleOptions {
|
|
98
|
+
module?: string;
|
|
99
|
+
target?: string;
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
interface ServedSource {
|
|
92
103
|
source: string;
|
|
93
|
-
|
|
104
|
+
/** Options of the project that emitted this source; `null` when none did. */
|
|
105
|
+
moduleOptions: OwningModuleOptions | null;
|
|
94
106
|
emittedFile?: string;
|
|
95
107
|
sourceFile?: string;
|
|
96
108
|
}
|
|
@@ -114,8 +126,12 @@ interface RuntimeManifest {
|
|
|
114
126
|
emitDir: string;
|
|
115
127
|
/** Emitted file list from the entry build, for source→output matching. */
|
|
116
128
|
emittedFiles?: readonly string[];
|
|
117
|
-
/**
|
|
118
|
-
|
|
129
|
+
/**
|
|
130
|
+
* The entry tsconfig's `module` and `target`, deciding emit CJS/ESM per file.
|
|
131
|
+
* `target` is not decoration: an absent `module` makes tsgo derive the module
|
|
132
|
+
* kind from it.
|
|
133
|
+
*/
|
|
134
|
+
moduleOptions?: OwningModuleOptions;
|
|
119
135
|
/** Root directory for per-dependency build output. */
|
|
120
136
|
depCacheDir: string;
|
|
121
137
|
}
|
|
@@ -280,12 +296,16 @@ function installCommonJsHook(): void {
|
|
|
280
296
|
|
|
281
297
|
/**
|
|
282
298
|
* The module format of the entry source file, derived from the entry project's
|
|
283
|
-
*
|
|
299
|
+
* compiler options (via the runtime manifest) the same way the served files are
|
|
284
300
|
* classified. The bootstrap uses it to load the entry through a CommonJS
|
|
285
301
|
* `require` or an ESM `import`.
|
|
286
302
|
*/
|
|
287
303
|
export function entryModuleFormat(entryFile: string): "module" | "commonjs" {
|
|
288
|
-
|
|
304
|
+
const m = manifest();
|
|
305
|
+
return moduleFormat(
|
|
306
|
+
entryFile,
|
|
307
|
+
m === null ? null : (m.moduleOptions ?? {}),
|
|
308
|
+
) === "module"
|
|
289
309
|
? "module"
|
|
290
310
|
: "commonjs";
|
|
291
311
|
}
|
|
@@ -346,7 +366,7 @@ interface BuiltProject {
|
|
|
346
366
|
emitDir: string;
|
|
347
367
|
rootDir: string;
|
|
348
368
|
emittedFiles?: readonly string[];
|
|
349
|
-
|
|
369
|
+
moduleOptions: OwningModuleOptions;
|
|
350
370
|
}
|
|
351
371
|
const builtProjects = new Map<string, BuiltProject>();
|
|
352
372
|
|
|
@@ -379,7 +399,7 @@ function resolveRuntimeSource(
|
|
|
379
399
|
url: string = pathToFileURL(filename).href,
|
|
380
400
|
): { format: string; source: string } {
|
|
381
401
|
const served = resolveServedSource(filename, url);
|
|
382
|
-
const format = moduleFormat(filename, served.
|
|
402
|
+
const format = moduleFormat(filename, served.moduleOptions);
|
|
383
403
|
return {
|
|
384
404
|
format,
|
|
385
405
|
source:
|
|
@@ -403,6 +423,17 @@ function rememberCommonJsNamedInterop(
|
|
|
403
423
|
return result;
|
|
404
424
|
}
|
|
405
425
|
|
|
426
|
+
/**
|
|
427
|
+
* Whether a CommonJS-classified TypeScript source reached from an ESM parent
|
|
428
|
+
* needs its nested `export *` names exposed.
|
|
429
|
+
*
|
|
430
|
+
* Only the parent side is decided here. The child's own format is re-checked
|
|
431
|
+
* authoritatively in `resolveRuntimeSource` against the format its served
|
|
432
|
+
* source actually carries, so this predicate deliberately does not repeat that
|
|
433
|
+
* check: doing so would need the child's owning project, which is not known
|
|
434
|
+
* until the source is served, and an answer guessed from the nearest tsconfig
|
|
435
|
+
* silently under-exposes a file that tsconfig does not compile.
|
|
436
|
+
*/
|
|
406
437
|
function shouldExposeCommonJsNamedExports(
|
|
407
438
|
url: string,
|
|
408
439
|
parentURL: string | undefined,
|
|
@@ -415,49 +446,70 @@ function shouldExposeCommonJsNamedExports(
|
|
|
415
446
|
return false;
|
|
416
447
|
}
|
|
417
448
|
const parentFile = fileURLToPath(parentURL);
|
|
418
|
-
if (
|
|
419
|
-
moduleFormat(parentFile, moduleOptionForSource(parentFile)) !== "module"
|
|
420
|
-
) {
|
|
449
|
+
if (moduleFormat(parentFile, owningModuleOptions(parentFile)) !== "module") {
|
|
421
450
|
return false;
|
|
422
451
|
}
|
|
423
|
-
|
|
424
|
-
return (
|
|
425
|
-
isTypeScriptSource(filename) &&
|
|
426
|
-
moduleFormat(filename, moduleOptionForSource(filename)) === "commonjs"
|
|
427
|
-
);
|
|
452
|
+
return isTypeScriptSource(fileURLToPath(url));
|
|
428
453
|
}
|
|
429
454
|
|
|
430
|
-
|
|
455
|
+
/**
|
|
456
|
+
* The emit-deciding compiler options of the project that owns `filename`, or
|
|
457
|
+
* `null` when none does.
|
|
458
|
+
*
|
|
459
|
+
* The entry project owns a file only when it actually emitted it. Testing
|
|
460
|
+
* `isWithin(rootDir)` alone would claim every file under a wide `rootDir` —
|
|
461
|
+
* including the volume-root `rootDir` a config-loader project uses — and hand
|
|
462
|
+
* them the entry project's options even though the dependency or orphan lane is
|
|
463
|
+
* what serves them.
|
|
464
|
+
*/
|
|
465
|
+
function owningModuleOptions(filename: string): OwningModuleOptions | null {
|
|
431
466
|
if (!isTypeScriptSource(filename)) {
|
|
432
|
-
return
|
|
467
|
+
return null;
|
|
433
468
|
}
|
|
434
469
|
const real = realPath(filename);
|
|
435
470
|
const m = manifest();
|
|
436
|
-
if (m !== null &&
|
|
437
|
-
return m.
|
|
471
|
+
if (m !== null && entryEmitPath(m, real) !== null) {
|
|
472
|
+
return m.moduleOptions ?? {};
|
|
438
473
|
}
|
|
439
474
|
const tsconfig = nearestTsconfig(real);
|
|
440
475
|
if (tsconfig === null) {
|
|
441
|
-
return
|
|
476
|
+
return null;
|
|
442
477
|
}
|
|
443
|
-
|
|
444
|
-
|
|
478
|
+
const cached = moduleOptionsCache.get(tsconfig);
|
|
479
|
+
if (cached !== undefined) {
|
|
480
|
+
return cached;
|
|
445
481
|
}
|
|
446
|
-
let
|
|
482
|
+
let options: OwningModuleOptions = {};
|
|
447
483
|
try {
|
|
448
484
|
const project = readProjectConfig({
|
|
449
485
|
cwd: path.dirname(tsconfig),
|
|
450
486
|
tsconfig,
|
|
451
487
|
});
|
|
452
|
-
|
|
453
|
-
typeof project.compilerOptions.module === "string"
|
|
454
|
-
? project.compilerOptions.module
|
|
455
|
-
: undefined;
|
|
488
|
+
options = projectModuleOptions(project.compilerOptions);
|
|
456
489
|
} catch {
|
|
457
|
-
|
|
490
|
+
// The owning project cannot be read, so nothing is known about the format
|
|
491
|
+
// it would have emitted. That is the same state as having no project at
|
|
492
|
+
// all, and it is what the dependency lane will conclude too when its build
|
|
493
|
+
// fails and the file falls through to the orphan type-strip.
|
|
494
|
+
moduleOptionsCache.set(tsconfig, null);
|
|
495
|
+
return null;
|
|
458
496
|
}
|
|
459
|
-
|
|
460
|
-
return
|
|
497
|
+
moduleOptionsCache.set(tsconfig, options);
|
|
498
|
+
return options;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/** Narrow a resolved project's compiler options to the emit-format pair. */
|
|
502
|
+
export function projectModuleOptions(
|
|
503
|
+
compilerOptions: Record<string, unknown>,
|
|
504
|
+
): OwningModuleOptions {
|
|
505
|
+
return {
|
|
506
|
+
...(typeof compilerOptions.module === "string"
|
|
507
|
+
? { module: compilerOptions.module }
|
|
508
|
+
: {}),
|
|
509
|
+
...(typeof compilerOptions.target === "string"
|
|
510
|
+
? { target: compilerOptions.target }
|
|
511
|
+
: {}),
|
|
512
|
+
};
|
|
461
513
|
}
|
|
462
514
|
|
|
463
515
|
/**
|
|
@@ -474,17 +526,14 @@ function resolveServedSource(
|
|
|
474
526
|
const real = realPath(filename);
|
|
475
527
|
const served = serveEntryEmit(real);
|
|
476
528
|
if (served !== null) {
|
|
477
|
-
return withInlineSourceMap(
|
|
478
|
-
moduleOption: manifest()?.moduleOption,
|
|
479
|
-
...served,
|
|
480
|
-
});
|
|
529
|
+
return withInlineSourceMap(served);
|
|
481
530
|
}
|
|
482
531
|
const built = serveDependencyEmit(real);
|
|
483
532
|
if (built !== null) {
|
|
484
533
|
return withInlineSourceMap(built);
|
|
485
534
|
}
|
|
486
535
|
return {
|
|
487
|
-
|
|
536
|
+
moduleOptions: null,
|
|
488
537
|
sourceFile: filename,
|
|
489
538
|
source: transformOrphanSource(filename, url),
|
|
490
539
|
};
|
|
@@ -523,7 +572,7 @@ function withInlineSourceMap(served: ServedSource): ServedSource {
|
|
|
523
572
|
* owning project; an ESM-format orphan keeps the fast in-process strip.
|
|
524
573
|
*/
|
|
525
574
|
function transformOrphanSource(filename: string, url: string): string {
|
|
526
|
-
if (moduleFormat(filename,
|
|
575
|
+
if (moduleFormat(filename, null) === "commonjs") {
|
|
527
576
|
const lowered = emitOrphanAsCommonJs(filename);
|
|
528
577
|
if (lowered !== null) {
|
|
529
578
|
return lowered;
|
|
@@ -1122,24 +1171,52 @@ function serveEntryEmit(real: string): ServedSource | null {
|
|
|
1122
1171
|
if (m === null) {
|
|
1123
1172
|
return null;
|
|
1124
1173
|
}
|
|
1125
|
-
|
|
1126
|
-
return null;
|
|
1127
|
-
}
|
|
1128
|
-
const emitted = resolveEmittedJavaScript({
|
|
1129
|
-
emittedFiles: m.emittedFiles,
|
|
1130
|
-
outDir: m.emitDir,
|
|
1131
|
-
projectRoot: m.rootDir,
|
|
1132
|
-
sourceFile: real,
|
|
1133
|
-
});
|
|
1174
|
+
const emitted = entryEmitPath(m, real);
|
|
1134
1175
|
if (emitted === null) {
|
|
1135
1176
|
return null;
|
|
1136
1177
|
}
|
|
1137
1178
|
const source = readFileOrNull(emitted);
|
|
1138
1179
|
return source === null
|
|
1139
1180
|
? null
|
|
1140
|
-
: {
|
|
1181
|
+
: {
|
|
1182
|
+
emittedFile: emitted,
|
|
1183
|
+
moduleOptions: m.moduleOptions ?? {},
|
|
1184
|
+
source,
|
|
1185
|
+
sourceFile: real,
|
|
1186
|
+
};
|
|
1141
1187
|
}
|
|
1142
1188
|
|
|
1189
|
+
/**
|
|
1190
|
+
* The entry project's emitted JavaScript for `real`, or `null` when that
|
|
1191
|
+
* project did not emit it. Shared with `owningModuleOptions` so "the entry
|
|
1192
|
+
* project owns this file" means exactly one thing in both places.
|
|
1193
|
+
*/
|
|
1194
|
+
function entryEmitPath(m: RuntimeManifest, real: string): string | null {
|
|
1195
|
+
const cached = entryEmitPathCache.get(real);
|
|
1196
|
+
if (cached !== undefined) {
|
|
1197
|
+
return cached;
|
|
1198
|
+
}
|
|
1199
|
+
const resolved = isWithin(real, m.rootDir)
|
|
1200
|
+
? resolveEmittedJavaScript({
|
|
1201
|
+
emittedFiles: m.emittedFiles,
|
|
1202
|
+
outDir: m.emitDir,
|
|
1203
|
+
projectRoot: m.rootDir,
|
|
1204
|
+
sourceFile: real,
|
|
1205
|
+
})
|
|
1206
|
+
: null;
|
|
1207
|
+
entryEmitPathCache.set(real, resolved);
|
|
1208
|
+
return resolved;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/**
|
|
1212
|
+
* Memo for `entryEmitPath`, because it is now on the `resolve` hook's path
|
|
1213
|
+
* through `owningModuleOptions` — once per import specifier — and a miss inside
|
|
1214
|
+
* `resolveEmittedJavaScript` walks the whole emit tree. The entry emit is
|
|
1215
|
+
* written once before the run starts and never changes under it, so one answer
|
|
1216
|
+
* per path is the only one there is.
|
|
1217
|
+
*/
|
|
1218
|
+
const entryEmitPathCache = new Map<string, string | null>();
|
|
1219
|
+
|
|
1143
1220
|
/**
|
|
1144
1221
|
* True when `real` is `directory` itself or sits beneath it. Handles a root
|
|
1145
1222
|
* `directory` (`/`, `C:\`): naively appending a separator would yield `//`,
|
|
@@ -1207,7 +1284,7 @@ function serveBuiltDependency(
|
|
|
1207
1284
|
? null
|
|
1208
1285
|
: {
|
|
1209
1286
|
emittedFile: emitted,
|
|
1210
|
-
|
|
1287
|
+
moduleOptions: built.moduleOptions,
|
|
1211
1288
|
source,
|
|
1212
1289
|
sourceFile: real,
|
|
1213
1290
|
};
|
|
@@ -1227,7 +1304,7 @@ function serveBuiltDependency(
|
|
|
1227
1304
|
interface DependencyCacheMeta {
|
|
1228
1305
|
generation: string;
|
|
1229
1306
|
rootDir: string;
|
|
1230
|
-
|
|
1307
|
+
moduleOptions?: OwningModuleOptions;
|
|
1231
1308
|
}
|
|
1232
1309
|
|
|
1233
1310
|
/**
|
|
@@ -1328,7 +1405,15 @@ export function readDependencyCache(
|
|
|
1328
1405
|
}
|
|
1329
1406
|
if (
|
|
1330
1407
|
!isDependencyGeneration(meta.generation) ||
|
|
1331
|
-
typeof meta.rootDir !== "string"
|
|
1408
|
+
typeof meta.rootDir !== "string" ||
|
|
1409
|
+
// A marker with no `moduleOptions` object predates this field and cannot
|
|
1410
|
+
// say which format its emit carries. Treating the absence as "no options"
|
|
1411
|
+
// would classify a CommonJS emit as an ES module, so the generation is
|
|
1412
|
+
// rejected and rebuilt instead. Every marker this version writes carries
|
|
1413
|
+
// the object, empty or not.
|
|
1414
|
+
typeof meta.moduleOptions !== "object" ||
|
|
1415
|
+
meta.moduleOptions === null ||
|
|
1416
|
+
Array.isArray(meta.moduleOptions)
|
|
1332
1417
|
) {
|
|
1333
1418
|
return null;
|
|
1334
1419
|
}
|
|
@@ -1339,9 +1424,18 @@ export function readDependencyCache(
|
|
|
1339
1424
|
return {
|
|
1340
1425
|
emitDir,
|
|
1341
1426
|
emittedFiles: undefined,
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1427
|
+
moduleOptions: projectModuleOptions(
|
|
1428
|
+
meta.moduleOptions as Record<string, unknown>,
|
|
1429
|
+
),
|
|
1430
|
+
// Resolved on the way out, not trusted as written. `rootDir` never gated
|
|
1431
|
+
// reuse — the marker's generation, module options, and a non-empty emit do
|
|
1432
|
+
// — so a marker carrying an unresolved spelling was already being reused,
|
|
1433
|
+
// and then served every file of that dependency through the whole-tree stem
|
|
1434
|
+
// rescan. The pass is idempotent and runs only on a hit, which
|
|
1435
|
+
// `ensureProjectBuilt` memoizes per tsconfig. A marker from an earlier ttsc
|
|
1436
|
+
// survives only under the shared `os.tmpdir()/ttsx-dep` fallback root; the
|
|
1437
|
+
// manifest's own `depCacheDir` is per-process and removed with the run.
|
|
1438
|
+
rootDir: resolvePhysicalPath(meta.rootDir),
|
|
1345
1439
|
};
|
|
1346
1440
|
}
|
|
1347
1441
|
|
|
@@ -1480,16 +1574,10 @@ function buildDependency(
|
|
|
1480
1574
|
.join("\n"),
|
|
1481
1575
|
);
|
|
1482
1576
|
}
|
|
1483
|
-
const rootDir =
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
const moduleOption =
|
|
1488
|
-
typeof project.compilerOptions.module === "string"
|
|
1489
|
-
? project.compilerOptions.module
|
|
1490
|
-
: undefined;
|
|
1491
|
-
publishDependencyMeta(metaPath, { generation, moduleOption, rootDir });
|
|
1492
|
-
return { emitDir, emittedFiles: undefined, moduleOption, rootDir };
|
|
1577
|
+
const rootDir = resolveDependencySourceRoot(project);
|
|
1578
|
+
const moduleOptions = projectModuleOptions(project.compilerOptions);
|
|
1579
|
+
publishDependencyMeta(metaPath, { generation, moduleOptions, rootDir });
|
|
1580
|
+
return { emitDir, emittedFiles: undefined, moduleOptions, rootDir };
|
|
1493
1581
|
}
|
|
1494
1582
|
|
|
1495
1583
|
/**
|
|
@@ -1896,7 +1984,7 @@ function formatDuration(ms: number): string {
|
|
|
1896
1984
|
|
|
1897
1985
|
/** Owning-tsconfig cache keyed by directory, mirroring `packageTypeCache`. */
|
|
1898
1986
|
const tsconfigCache = new Map<string, string | null>();
|
|
1899
|
-
const
|
|
1987
|
+
const moduleOptionsCache = new Map<string, OwningModuleOptions | null>();
|
|
1900
1988
|
|
|
1901
1989
|
/**
|
|
1902
1990
|
* The nearest `tsconfig.json` at or above `file`'s directory, or `null`. The
|
|
@@ -1955,14 +2043,86 @@ function readFileOrNull(file: string | null): string | null {
|
|
|
1955
2043
|
}
|
|
1956
2044
|
}
|
|
1957
2045
|
|
|
1958
|
-
|
|
2046
|
+
/**
|
|
2047
|
+
* The physical path, in the same spelling the launcher decided on.
|
|
2048
|
+
*
|
|
2049
|
+
* `realpathSync.native` first, because the plain implementation resolves
|
|
2050
|
+
* reparse points but leaves a Windows 8.3 component alone — and `TEMP` is
|
|
2051
|
+
* `C:\Users\RUNNER~1\...` on a GitHub Windows runner. The launcher resolves the
|
|
2052
|
+
* entry through `fs.realpathSync.native`, so answering here with the short name
|
|
2053
|
+
* would put a `..` in `path.relative(rootDir, real)`, drop the exact-mirror
|
|
2054
|
+
* lane, and leave the entry to whatever the trailing-stem matcher picks.
|
|
2055
|
+
*/
|
|
2056
|
+
export function realPath(target: string): string {
|
|
1959
2057
|
try {
|
|
1960
|
-
return fs.realpathSync(target);
|
|
2058
|
+
return fs.realpathSync.native?.(target) ?? fs.realpathSync(target);
|
|
1961
2059
|
} catch {
|
|
1962
2060
|
return target;
|
|
1963
2061
|
}
|
|
1964
2062
|
}
|
|
1965
2063
|
|
|
2064
|
+
/**
|
|
2065
|
+
* The physical spelling of a path, produced the way the served sources it will
|
|
2066
|
+
* be compared against are produced.
|
|
2067
|
+
*
|
|
2068
|
+
* `createFilesystemPathIdentityContext` is the same resolver the entry lane
|
|
2069
|
+
* uses (`prepareExecution.ts::resolveRuntimeSourceRoot`). For a path that
|
|
2070
|
+
* exists it is one `realpathSync.native`. For one that does not it resolves as
|
|
2071
|
+
* far as the filesystem goes and folds the missing tail by the case semantics
|
|
2072
|
+
* of the surviving ancestor, which costs a directory read and, on Windows, can
|
|
2073
|
+
* cost an `fsutil` query — worth knowing, but off the path a real dependency
|
|
2074
|
+
* root takes.
|
|
2075
|
+
*
|
|
2076
|
+
* Total on purpose. `throwOnRealpathError: false` silences a failed realpath,
|
|
2077
|
+
* but the case-sensitivity probe can still fail on its own (an unreadable
|
|
2078
|
+
* ancestor, a denied alternate-case `lstat`), and neither caller has anywhere
|
|
2079
|
+
* to put that: `readDependencyCache` is contracted to answer `null` rather than
|
|
2080
|
+
* throw, and `buildDependency` has already produced its emit. The unresolved
|
|
2081
|
+
* spelling is exactly what both had before this pass, so it is the fallback.
|
|
2082
|
+
*/
|
|
2083
|
+
function resolvePhysicalPath(location: string): string {
|
|
2084
|
+
try {
|
|
2085
|
+
return createFilesystemPathIdentityContext({
|
|
2086
|
+
throwOnRealpathError: false,
|
|
2087
|
+
}).resolve(location).path;
|
|
2088
|
+
} catch {
|
|
2089
|
+
return location;
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
/**
|
|
2094
|
+
* The source-tree root a dependency's emit mirrors, in the spelling
|
|
2095
|
+
* `resolveEmittedJavaScript` compares a served source against.
|
|
2096
|
+
*
|
|
2097
|
+
* Both branches need the pass, for different reasons. A declared `rootDir`
|
|
2098
|
+
* arrives from `readProjectConfig` joined against the config that declared it
|
|
2099
|
+
* but never resolved, so a `rootDir` that is itself a symlinked directory stays
|
|
2100
|
+
* unresolved. `project.root` arrives through plain `fs.realpathSync`, which
|
|
2101
|
+
* follows reparse points but leaves a Windows 8.3 component alone — and
|
|
2102
|
+
* {@link realPath} uses `fs.realpathSync.native`, which expands it. Either way
|
|
2103
|
+
* `path.relative` puts a `..` in front of an in-project source,
|
|
2104
|
+
* `resolveExactEmittedFiles` returns nothing, and every served file of that
|
|
2105
|
+
* dependency falls to the trailing-stem matcher, which rescans the whole emit
|
|
2106
|
+
* tree per file because a dependency build publishes no emitted-file list.
|
|
2107
|
+
*
|
|
2108
|
+
* This is the pass the entry lane settled on for the same mixed pair; the two
|
|
2109
|
+
* lanes now read alike, `path.isAbsolute` guard included. `readProjectConfig`
|
|
2110
|
+
* absolutizes every path option against the config that declared it, so that
|
|
2111
|
+
* guard is a mirror of the entry lane rather than a live branch.
|
|
2112
|
+
*/
|
|
2113
|
+
function resolveDependencySourceRoot(
|
|
2114
|
+
project: ReturnType<typeof readProjectConfig>,
|
|
2115
|
+
): string {
|
|
2116
|
+
const rootDir = project.compilerOptions.rootDir;
|
|
2117
|
+
return resolvePhysicalPath(
|
|
2118
|
+
typeof rootDir !== "string"
|
|
2119
|
+
? project.root
|
|
2120
|
+
: path.isAbsolute(rootDir)
|
|
2121
|
+
? rootDir
|
|
2122
|
+
: path.resolve(project.root, rootDir),
|
|
2123
|
+
);
|
|
2124
|
+
}
|
|
2125
|
+
|
|
1966
2126
|
/**
|
|
1967
2127
|
* Map the JavaScript extension a relative `specifier` carries to the TypeScript
|
|
1968
2128
|
* source extensions tsgo would have emitted it from. Running from source, a
|
|
@@ -2050,16 +2210,27 @@ function probeRescuableSpecifier(
|
|
|
2050
2210
|
|
|
2051
2211
|
/**
|
|
2052
2212
|
* Decide the module format the way Node and tsgo do — from configuration, never
|
|
2053
|
-
* by sniffing the emitted text.
|
|
2054
|
-
*
|
|
2055
|
-
*
|
|
2056
|
-
*
|
|
2057
|
-
*
|
|
2058
|
-
*
|
|
2213
|
+
* by sniffing the emitted text.
|
|
2214
|
+
*
|
|
2215
|
+
* The file extension is authoritative first (`.mts`/`.mjs` → module,
|
|
2216
|
+
* `.cts`/`.cjs` → commonjs), exactly as tsgo's
|
|
2217
|
+
* `getImpliedNodeFormatForEmitWorker` checks it ahead of everything else.
|
|
2218
|
+
*
|
|
2219
|
+
* After that the decision belongs to the project that emitted the file, so
|
|
2220
|
+
* `options` is the whole compiler-option pair tsgo consults, not just `module`:
|
|
2221
|
+
* an absent `module` is NOT "ask Node", it is "derive the kind from `target`"
|
|
2222
|
+
* (tsgo's `getEmitModuleKind`), and TypeScript 7 defaults `target` to the
|
|
2223
|
+
* latest standard, which means ES modules. Only the `node*` family defers to
|
|
2224
|
+
* the nearest `package.json` `type`, because only that family makes tsgo
|
|
2225
|
+
* consult it.
|
|
2226
|
+
*
|
|
2227
|
+
* `options` is `null` for a file no tsconfig owns at all — a raw `.ts` shipped
|
|
2228
|
+
* under `node_modules`. Nothing emitted it, so Node's own rule is the only rule
|
|
2229
|
+
* there is, and the package `type` decides.
|
|
2059
2230
|
*/
|
|
2060
2231
|
function moduleFormat(
|
|
2061
2232
|
filename: string,
|
|
2062
|
-
|
|
2233
|
+
options: OwningModuleOptions | null,
|
|
2063
2234
|
): string {
|
|
2064
2235
|
if (filename.endsWith(".mts") || filename.endsWith(".mjs")) {
|
|
2065
2236
|
return "module";
|
|
@@ -2067,24 +2238,120 @@ function moduleFormat(
|
|
|
2067
2238
|
if (filename.endsWith(".cts") || filename.endsWith(".cjs")) {
|
|
2068
2239
|
return "commonjs";
|
|
2069
2240
|
}
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2241
|
+
if (options === null) {
|
|
2242
|
+
return nearestPackageType(filename);
|
|
2243
|
+
}
|
|
2244
|
+
// A package that states its `type` outright decides for the files inside it
|
|
2245
|
+
// whatever the module kind is. This is where a source-shipping dependency's
|
|
2246
|
+
// own declaration overrides the compiling project's `module`; see
|
|
2247
|
+
// `declaredNodeModulesPackageType` for why `node_modules` bounds it.
|
|
2248
|
+
const declared = declaredNodeModulesPackageType(filename);
|
|
2249
|
+
if (declared !== null) {
|
|
2250
|
+
return declared;
|
|
2073
2251
|
}
|
|
2252
|
+
const kind = effectiveModuleKind(options);
|
|
2074
2253
|
if (
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
option === "preserve"
|
|
2254
|
+
kind === "node16" ||
|
|
2255
|
+
kind === "node18" ||
|
|
2256
|
+
kind === "node20" ||
|
|
2257
|
+
kind === "nodenext"
|
|
2080
2258
|
) {
|
|
2081
2259
|
return nearestPackageType(filename);
|
|
2082
2260
|
}
|
|
2083
|
-
|
|
2084
|
-
|
|
2261
|
+
if (kind === "commonjs") {
|
|
2262
|
+
return "commonjs";
|
|
2263
|
+
}
|
|
2264
|
+
// Everything that remains (es2015 … esnext, and `preserve`, which keeps the
|
|
2265
|
+
// authored ESM syntax verbatim whatever the package `type` says) is emitted
|
|
2266
|
+
// as ECMAScript modules. `amd`, `umd`, and `system` are not among them:
|
|
2267
|
+
// TypeScript 7 removed all three, so a project declaring one cannot build,
|
|
2268
|
+
// and a file it would have owned reaches the run through the orphan lane
|
|
2269
|
+
// instead.
|
|
2085
2270
|
return "module";
|
|
2086
2271
|
}
|
|
2087
2272
|
|
|
2273
|
+
/**
|
|
2274
|
+
* The `"type"` a `node_modules` package states for `filename`, or `null`.
|
|
2275
|
+
*
|
|
2276
|
+
* Tsgo's `GetImpliedNodeFormatForEmitWorker` consults a file's
|
|
2277
|
+
* `packageJsonType` for **every** module kind, not just the `node*` family. So
|
|
2278
|
+
* a source-shipping dependency that declares `"type": "commonjs"` is emitted as
|
|
2279
|
+
* CommonJS even while the compiling project asks for `esnext`, and the mirror
|
|
2280
|
+
* has to honour the same override or Node is handed the wrong format.
|
|
2281
|
+
*
|
|
2282
|
+
* Outside `node_modules` that field is empty for every module kind this
|
|
2283
|
+
* override can change. `loadSourceFileMetaData` does fill it elsewhere — for a
|
|
2284
|
+
* file whose extension does not itself state the format (anything but `.mts`,
|
|
2285
|
+
* `.cts`, `.mjs`, `.cjs`) whose project sets `moduleResolution` to the
|
|
2286
|
+
* `node16`…`nodenext` family — but `program.go` rejects that resolution unless
|
|
2287
|
+
* `module` is in the same family, and for a `node*` `module` the package `type`
|
|
2288
|
+
* is already the whole answer, which the caller's own `node*` branch produces.
|
|
2289
|
+
* So answering from a manifest outside `node_modules` could only override the
|
|
2290
|
+
* project's own `module` option, the very confusion this classifier exists to
|
|
2291
|
+
* end.
|
|
2292
|
+
*/
|
|
2293
|
+
function declaredNodeModulesPackageType(
|
|
2294
|
+
filename: string,
|
|
2295
|
+
): "module" | "commonjs" | null {
|
|
2296
|
+
if (!pathHasNodeModulesSegment(filename)) {
|
|
2297
|
+
return null;
|
|
2298
|
+
}
|
|
2299
|
+
return declaredPackageType(filename);
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
/**
|
|
2303
|
+
* Whether any path segment of `filename` is literally `node_modules`.
|
|
2304
|
+
*
|
|
2305
|
+
* Case-sensitive on every platform, because the upstream test this mirrors is a
|
|
2306
|
+
* case-sensitive substring search and Node's own resolver recognises only the
|
|
2307
|
+
* exact spelling. A directory named `Node_Modules` is a package store to
|
|
2308
|
+
* neither of them, and must not become one here.
|
|
2309
|
+
*/
|
|
2310
|
+
function pathHasNodeModulesSegment(filename: string): boolean {
|
|
2311
|
+
return filename.split(/[\\/]/).includes("node_modules");
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
/**
|
|
2315
|
+
* The `module` kind tsgo actually emits with, mirroring `getEmitModuleKind`:
|
|
2316
|
+
* the declared `module` when there is one, and otherwise the kind implied by
|
|
2317
|
+
* `target`. `"none"` is the spelling of an unset `module`, so it derives the
|
|
2318
|
+
* same way.
|
|
2319
|
+
*/
|
|
2320
|
+
function effectiveModuleKind(options: OwningModuleOptions): string {
|
|
2321
|
+
const declared = (options.module ?? "").toLowerCase();
|
|
2322
|
+
if (declared !== "" && declared !== "none") {
|
|
2323
|
+
return declared;
|
|
2324
|
+
}
|
|
2325
|
+
const target = (options.target ?? "").toLowerCase();
|
|
2326
|
+
if (target === "esnext") {
|
|
2327
|
+
return "esnext";
|
|
2328
|
+
}
|
|
2329
|
+
const year = scriptTargetYear(target);
|
|
2330
|
+
if (year === null) {
|
|
2331
|
+
// An unset `target` is `ScriptTargetLatestStandard` upstream, well above the
|
|
2332
|
+
// ES2022 boundary. Every other spelling lands here too: TypeScript 7 removed
|
|
2333
|
+
// `ES5` and dropped `ES3` entirely, and its only remaining targets are
|
|
2334
|
+
// year-numbered ones, so nothing that reaches emit derives CommonJS.
|
|
2335
|
+
return "es2022";
|
|
2336
|
+
}
|
|
2337
|
+
if (year >= 2022) return "es2022";
|
|
2338
|
+
if (year >= 2020) return "es2020";
|
|
2339
|
+
return "es2015";
|
|
2340
|
+
}
|
|
2341
|
+
|
|
2342
|
+
/**
|
|
2343
|
+
* Year of an `ES<year>` script target, with `es6` normalized to its `es2015`
|
|
2344
|
+
* synonym. Returns `null` when the spelling is not a year-numbered target,
|
|
2345
|
+
* which the caller resolves to the modern default.
|
|
2346
|
+
*/
|
|
2347
|
+
function scriptTargetYear(target: string): number | null {
|
|
2348
|
+
if (target === "es6") {
|
|
2349
|
+
return 2015;
|
|
2350
|
+
}
|
|
2351
|
+
const match = /^es(\d{4})$/.exec(target);
|
|
2352
|
+
return match === null ? null : Number(match[1]);
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2088
2355
|
/** Package-type cache keyed by directory, mirroring Node's own lookup walk. */
|
|
2089
2356
|
const packageTypeCache = new Map<string, "module" | "commonjs">();
|
|
2090
2357
|
|
|
@@ -2135,6 +2402,42 @@ function readPackageType(directory: string): "module" | "commonjs" | null {
|
|
|
2135
2402
|
}
|
|
2136
2403
|
}
|
|
2137
2404
|
|
|
2405
|
+
/**
|
|
2406
|
+
* The `"type"` the nearest `package.json` states outright, or `null` when the
|
|
2407
|
+
* nearest manifest omits it (or there is none).
|
|
2408
|
+
*
|
|
2409
|
+
* `nearestPackageType` answers "what format would Node use", which defaults a
|
|
2410
|
+
* silent manifest to CommonJS. This answers the narrower question "did a
|
|
2411
|
+
* package actually say", which is what an override has to be built on: a
|
|
2412
|
+
* manifest that says nothing must not out-vote the compiling project's own
|
|
2413
|
+
* `module` option.
|
|
2414
|
+
*/
|
|
2415
|
+
function declaredPackageType(filename: string): "module" | "commonjs" | null {
|
|
2416
|
+
let directory = path.dirname(filename);
|
|
2417
|
+
while (true) {
|
|
2418
|
+
const manifestPath = path.join(directory, "package.json");
|
|
2419
|
+
if (isFile(manifestPath)) {
|
|
2420
|
+
try {
|
|
2421
|
+
const parsed = JSON.parse(fs.readFileSync(manifestPath, "utf8")) as {
|
|
2422
|
+
type?: unknown;
|
|
2423
|
+
};
|
|
2424
|
+
// The walk stops at the first manifest either way, exactly as Node's
|
|
2425
|
+
// package-scope lookup does; only the answer differs.
|
|
2426
|
+
return parsed.type === "module" || parsed.type === "commonjs"
|
|
2427
|
+
? parsed.type
|
|
2428
|
+
: null;
|
|
2429
|
+
} catch {
|
|
2430
|
+
return null;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
const parent = path.dirname(directory);
|
|
2434
|
+
if (parent === directory) {
|
|
2435
|
+
return null;
|
|
2436
|
+
}
|
|
2437
|
+
directory = parent;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2138
2441
|
function isRelativeSpecifier(specifier: string): boolean {
|
|
2139
2442
|
return (
|
|
2140
2443
|
specifier === "." ||
|