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
|
@@ -2,10 +2,13 @@ import fs from "node:fs";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import { isOutsideRelativePath } from "../../compiler/internal/paths";
|
|
5
6
|
import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
|
|
6
7
|
import { resolveEmittedJavaScript } from "../../compiler/internal/resolveEmittedJavaScript";
|
|
7
8
|
import { runBuild } from "../../compiler/internal/runBuild";
|
|
9
|
+
import { createFilesystemPathIdentityContext } from "../../internal/projectInputPathIdentity";
|
|
8
10
|
import type { TtscCommonOptions } from "../../structures/internal/TtscCommonOptions";
|
|
11
|
+
import { type OwningModuleOptions, projectModuleOptions } from "./runtimeHooks";
|
|
9
12
|
|
|
10
13
|
/** Subdirectory name that isolates concurrent ttsx processes by PID. */
|
|
11
14
|
const PROCESS_CACHE_KEY = String(process.pid);
|
|
@@ -16,6 +19,12 @@ const PROCESS_CACHE_KEY = String(process.pid);
|
|
|
16
19
|
* resolve correctly without reaching an unsafe boundary.
|
|
17
20
|
*/
|
|
18
21
|
const MAX_VIRTUAL_PARENT_DEPTH = 3;
|
|
22
|
+
/**
|
|
23
|
+
* Emit directory of the entry-only fallback build, a sibling of the virtual
|
|
24
|
+
* layout's volume-label directories so it can never collide with a mirrored
|
|
25
|
+
* project path.
|
|
26
|
+
*/
|
|
27
|
+
const ENTRY_PROJECT_EMIT_DIR = "entry-project";
|
|
19
28
|
|
|
20
29
|
/** Build the owning project and locate the emitted JavaScript entry for `ttsx`. */
|
|
21
30
|
export function prepareExecution(
|
|
@@ -29,23 +38,35 @@ export function prepareExecution(
|
|
|
29
38
|
emitDir: string;
|
|
30
39
|
emittedFiles?: readonly string[];
|
|
31
40
|
entryFile: string;
|
|
32
|
-
|
|
41
|
+
moduleOptions: OwningModuleOptions;
|
|
33
42
|
projectRoot: string;
|
|
34
43
|
rootDir: string;
|
|
35
44
|
} {
|
|
45
|
+
// Two paths, because two different questions are being asked.
|
|
46
|
+
//
|
|
47
|
+
// *Which project compiles this?* is answered from the path the user named.
|
|
48
|
+
// Project discovery walks up from it, so resolving a symlinked entry first
|
|
49
|
+
// would start that walk in the target's tree — finding another project's
|
|
50
|
+
// tsconfig, or none at all.
|
|
51
|
+
//
|
|
52
|
+
// *Where does the compiler put the output, and what does the runtime load?*
|
|
53
|
+
// is answered from the physical path, because that is the spelling Node
|
|
54
|
+
// forces. See `resolveEntrySpelling`.
|
|
55
|
+
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
56
|
+
const entry = resolveEntrySpelling(cwd, entryFile);
|
|
36
57
|
const context = createProjectContext(
|
|
37
|
-
|
|
38
|
-
entryFile,
|
|
58
|
+
cwd,
|
|
59
|
+
path.resolve(cwd, entryFile),
|
|
60
|
+
entry,
|
|
39
61
|
options,
|
|
40
62
|
);
|
|
41
63
|
try {
|
|
42
64
|
buildProject(context, options);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
});
|
|
65
|
+
let emittedEntry = emittedEntryOf(context, entry);
|
|
66
|
+
if (emittedEntry === null) {
|
|
67
|
+
buildEntryProject(context, options, entry);
|
|
68
|
+
emittedEntry = emittedEntryOf(context, entry);
|
|
69
|
+
}
|
|
49
70
|
if (emittedEntry === null) {
|
|
50
71
|
throw new Error(`ttsx: emitted entry not found for ${entryFile}`);
|
|
51
72
|
}
|
|
@@ -54,7 +75,7 @@ export function prepareExecution(
|
|
|
54
75
|
emitDir: context.emitDir,
|
|
55
76
|
emittedFiles: context.emittedFiles ?? undefined,
|
|
56
77
|
entryFile: emittedEntry,
|
|
57
|
-
|
|
78
|
+
moduleOptions: context.moduleOptions,
|
|
58
79
|
projectRoot: context.root,
|
|
59
80
|
rootDir: context.runtimeRootDir,
|
|
60
81
|
};
|
|
@@ -64,9 +85,79 @@ export function prepareExecution(
|
|
|
64
85
|
}
|
|
65
86
|
}
|
|
66
87
|
|
|
88
|
+
/**
|
|
89
|
+
* The JavaScript this build emitted for `entry`, or `null` when it emitted none
|
|
90
|
+
* — which is the signal that the entry sits outside the project's file set.
|
|
91
|
+
*
|
|
92
|
+
* The guard is what makes this an ownership answer rather than a guess. tsgo
|
|
93
|
+
* strips `runtimeRootDir` from every output path, so a file outside that root
|
|
94
|
+
* cannot have an output under `outDir` at all; without the guard the lookup
|
|
95
|
+
* falls through to `resolveEmittedJavaScript`'s trailing-stem matcher, and a
|
|
96
|
+
* `build/release.ts` would happily match the `release.js` emitted for an
|
|
97
|
+
* unrelated `src/release.ts` — running the wrong file instead of compiling the
|
|
98
|
+
* requested one.
|
|
99
|
+
*
|
|
100
|
+
* It is lexical on purpose, and it is the same test `resolveEmittedJavaScript`
|
|
101
|
+
* then applies — including its rejection of an entry that _is_ the root. That
|
|
102
|
+
* only holds because both sides are _produced_ physically: the entry by
|
|
103
|
+
* `resolveEntrySpelling`, the root by `resolveRuntimeSourceRoot`. Folding here
|
|
104
|
+
* instead would paper over a mixed pair and disagree with the mirror that runs
|
|
105
|
+
* immediately after; keeping the pair honest is what makes folding
|
|
106
|
+
* unnecessary.
|
|
107
|
+
*/
|
|
108
|
+
function emittedEntryOf(
|
|
109
|
+
context: ReturnType<typeof createProjectContext>,
|
|
110
|
+
entry: string,
|
|
111
|
+
): string | null {
|
|
112
|
+
const relative = path.relative(context.runtimeRootDir, entry);
|
|
113
|
+
if (relative === "" || isOutsideRelativePath(relative)) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
return resolveEmittedJavaScript({
|
|
117
|
+
emittedFiles: context.emittedFiles ?? undefined,
|
|
118
|
+
outDir: context.emitDir,
|
|
119
|
+
projectRoot: context.runtimeRootDir,
|
|
120
|
+
sourceFile: entry,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* The entry in the filesystem's own spelling, symlinked file included.
|
|
126
|
+
*
|
|
127
|
+
* Node is what forces the choice. Without `--preserve-symlinks` it keys a
|
|
128
|
+
* module by its real path, so the runtime hooks identify a served file that way
|
|
129
|
+
* too — and the emit has to be findable under the same name. tsgo does not
|
|
130
|
+
* force anything: it takes `files` verbatim and never resolves them, which is
|
|
131
|
+
* exactly why it must be handed the spelling Node will use rather than a
|
|
132
|
+
* different one.
|
|
133
|
+
*
|
|
134
|
+
* An entry spelled any other way is not a nicer name for the same file. The
|
|
135
|
+
* gate would claim to own an emit the runtime then refuses to serve, and the
|
|
136
|
+
* entry would run through the orphan type-strip lane with the project's
|
|
137
|
+
* transform plugins, `target`, `paths`, and source map all silently dropped —
|
|
138
|
+
* from a run that still prints and still exits zero.
|
|
139
|
+
*
|
|
140
|
+
* Resolving the link widens `rootDir` to the ancestor the two trees share,
|
|
141
|
+
* which is not a cost but the requirement: the file genuinely lives outside the
|
|
142
|
+
* project, and no root that excludes it can compile it.
|
|
143
|
+
*/
|
|
144
|
+
function resolveEntrySpelling(cwd: string, entryFile: string): string {
|
|
145
|
+
const identities = createFilesystemPathIdentityContext({
|
|
146
|
+
throwOnRealpathError: false,
|
|
147
|
+
});
|
|
148
|
+
return identities.resolve(path.resolve(cwd, entryFile)).path;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param discoveryFile - The entry as the user named it. Project discovery
|
|
153
|
+
* 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
|
+
*/
|
|
67
157
|
function createProjectContext(
|
|
68
158
|
cwd: string,
|
|
69
|
-
|
|
159
|
+
discoveryFile: string,
|
|
160
|
+
entry: string,
|
|
70
161
|
options: NonNullable<Parameters<typeof prepareExecution>[1]>,
|
|
71
162
|
) {
|
|
72
163
|
const project = readProjectConfig(
|
|
@@ -76,7 +167,7 @@ function createProjectContext(
|
|
|
76
167
|
projectRoot: options.projectRoot,
|
|
77
168
|
tsconfig: path.resolve(cwd, options.project),
|
|
78
169
|
}
|
|
79
|
-
: { cwd, file:
|
|
170
|
+
: { cwd, file: discoveryFile, projectRoot: options.projectRoot },
|
|
80
171
|
);
|
|
81
172
|
const tsconfig = project.path;
|
|
82
173
|
const root = project.root;
|
|
@@ -86,6 +177,9 @@ function createProjectContext(
|
|
|
86
177
|
path.join(root, "node_modules", ".cache", "ttsc", "ttsx");
|
|
87
178
|
const processDir = path.join(cacheDir, "project", PROCESS_CACHE_KEY);
|
|
88
179
|
const virtualRoot = path.join(processDir, "fs");
|
|
180
|
+
// Resolved once: it now costs a realpath (and, for a missing directory on
|
|
181
|
+
// Windows, a case-sensitivity probe) rather than a string join.
|
|
182
|
+
const runtimeRootDir = resolveRuntimeSourceRoot(project, entry);
|
|
89
183
|
return {
|
|
90
184
|
project,
|
|
91
185
|
tsconfig,
|
|
@@ -96,17 +190,17 @@ function createProjectContext(
|
|
|
96
190
|
virtualRoot,
|
|
97
191
|
emitDir: project.compilerOptions.outDir
|
|
98
192
|
? virtualPath(virtualRoot, project.compilerOptions.outDir)
|
|
99
|
-
: virtualPath(virtualRoot,
|
|
193
|
+
: virtualPath(virtualRoot, runtimeRootDir),
|
|
100
194
|
// The source-tree root the emit mirrors (tsgo strips this prefix). Used to
|
|
101
195
|
// map a source `.ts` back to its emitted `.js` when the runtime hooks serve
|
|
102
196
|
// the built entry under its source URL.
|
|
103
|
-
runtimeRootDir
|
|
104
|
-
// The tsconfig
|
|
105
|
-
// file
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
197
|
+
runtimeRootDir,
|
|
198
|
+
// The tsconfig options that decide the emit format, so the runtime hooks
|
|
199
|
+
// classify each served file the same way tsgo chose when emitting it.
|
|
200
|
+
// `target` belongs here as much as `module` does: with `module` absent tsgo
|
|
201
|
+
// derives the module kind from `target`, so publishing only `module` makes
|
|
202
|
+
// the hooks guess.
|
|
203
|
+
moduleOptions: projectModuleOptions(project.compilerOptions),
|
|
110
204
|
// Force a source map on the transient runtime emit only when the project
|
|
111
205
|
// configures none — when it already emits `sourceMap` or `inlineSourceMap`,
|
|
112
206
|
// the serve path inlines/absolutizes that map, so no override is needed
|
|
@@ -119,17 +213,37 @@ function createProjectContext(
|
|
|
119
213
|
};
|
|
120
214
|
}
|
|
121
215
|
|
|
216
|
+
/**
|
|
217
|
+
* The source-tree root the emit mirrors, in the same physical spelling as the
|
|
218
|
+
* entry it will be compared against.
|
|
219
|
+
*
|
|
220
|
+
* Resolving it is the other half of `resolveEntrySpelling`, and skipping it
|
|
221
|
+
* leaves the comparison mixed rather than merely imprecise. `project.root`
|
|
222
|
+
* arrives through plain `fs.realpathSync`, which resolves reparse points but
|
|
223
|
+
* leaves a Windows 8.3 component alone, while the entry arrives through
|
|
224
|
+
* `fs.realpathSync.native`, which expands it — and `path.relative` folds case
|
|
225
|
+
* but not 8.3. A declared `rootDir` is worse still: it is joined verbatim, so a
|
|
226
|
+
* `rootDir` that is itself a symlinked directory never resolves at all. Either
|
|
227
|
+
* way the gate reads an in-project entry as outside its own root, pays a second
|
|
228
|
+
* whole build for it, and publishes a wider root than the project has.
|
|
229
|
+
*
|
|
230
|
+
* `path.dirname(entry)` is already physical, so only the declared branch needs
|
|
231
|
+
* the pass.
|
|
232
|
+
*/
|
|
122
233
|
function resolveRuntimeSourceRoot(
|
|
123
234
|
project: ReturnType<typeof readProjectConfig>,
|
|
124
|
-
|
|
235
|
+
entry: string,
|
|
125
236
|
): string {
|
|
126
237
|
const rootDir = project.compilerOptions.rootDir;
|
|
127
|
-
if (typeof rootDir
|
|
128
|
-
return path.
|
|
129
|
-
? rootDir
|
|
130
|
-
: path.resolve(project.root, rootDir);
|
|
238
|
+
if (typeof rootDir !== "string") {
|
|
239
|
+
return path.dirname(entry);
|
|
131
240
|
}
|
|
132
|
-
|
|
241
|
+
const identities = createFilesystemPathIdentityContext({
|
|
242
|
+
throwOnRealpathError: false,
|
|
243
|
+
});
|
|
244
|
+
return identities.resolve(
|
|
245
|
+
path.isAbsolute(rootDir) ? rootDir : path.resolve(project.root, rootDir),
|
|
246
|
+
).path;
|
|
133
247
|
}
|
|
134
248
|
|
|
135
249
|
function buildProject(
|
|
@@ -184,6 +298,163 @@ function buildProject(
|
|
|
184
298
|
throw new Error(detail);
|
|
185
299
|
}
|
|
186
300
|
|
|
301
|
+
/**
|
|
302
|
+
* Build an entry the owning project's file set does not contain.
|
|
303
|
+
*
|
|
304
|
+
* `ttsc` selects a _file set_: a project whose `include` is `src` must emit
|
|
305
|
+
* only `src` into `outDir`, and a `clear.ts`, a `build/release.ts`, or a
|
|
306
|
+
* `lint.config.ts` beside the tsconfig has no business in `lib`. `ttsx` selects
|
|
307
|
+
* an _entry_: it needs that same project's compiler options, not its file list.
|
|
308
|
+
* Those two requirements are not in conflict, but the whole-project build
|
|
309
|
+
* cannot satisfy the second one, so an entry it did not emit is compiled here
|
|
310
|
+
* through a project that inherits every option and declares only the entry.
|
|
311
|
+
*
|
|
312
|
+
* The synthesized tsconfig is written beside the real one on purpose. `extends`
|
|
313
|
+
* with an absolute path would resolve from anywhere, but `${configDir}` and
|
|
314
|
+
* `paths` are anchored to the directory of the config that consumes them, so
|
|
315
|
+
* any other location silently retargets them. It is removed as soon as the
|
|
316
|
+
* build returns.
|
|
317
|
+
*
|
|
318
|
+
* `rootDir` widens to the nearest directory holding both the project root and
|
|
319
|
+
* the entry — for the layout this exists for, the project root itself; for an
|
|
320
|
+
* entry that is a symlink out of the tree, the ancestor the two trees share. It
|
|
321
|
+
* has to widen at least that far, because the inherited `rootDir` (`src`) does
|
|
322
|
+
* not contain the entry and tsgo emits an input outside `rootDir` to its own
|
|
323
|
+
* source path.
|
|
324
|
+
*
|
|
325
|
+
* Widening costs precision, not safety. The manifest's `rootDir` bounds which
|
|
326
|
+
* files the runtime hooks will try to serve from this emit, so a wide one
|
|
327
|
+
* admits more sources to the lookup — but the lookup only ever answers with a
|
|
328
|
+
* file from this build's own emit directory, whether from `emittedFiles` or a
|
|
329
|
+
* scan of `outDir` (`resolveEmittedJavaScript`). What a wide root risks is the
|
|
330
|
+
* exact mirror missing and the trailing-stem matcher picking the wrong output
|
|
331
|
+
* _of this build_; it cannot reach a raw source on disk.
|
|
332
|
+
*/
|
|
333
|
+
function buildEntryProject(
|
|
334
|
+
context: ReturnType<typeof createProjectContext>,
|
|
335
|
+
options: NonNullable<Parameters<typeof prepareExecution>[1]>,
|
|
336
|
+
entry: string,
|
|
337
|
+
): void {
|
|
338
|
+
// `entry` already carries the one spelling `prepareExecution` decided on.
|
|
339
|
+
// tsgo compares it against `rootDir` textually — `GetCommonSourceDirectory`
|
|
340
|
+
// takes `rootDir` verbatim and `ContainsPath` is lexical — so a mismatch here
|
|
341
|
+
// is not a near miss: the entry counts as outside `rootDir`, and tsgo emits
|
|
342
|
+
// it to its own source path with the extension changed instead of under
|
|
343
|
+
// `outDir`, writing a `.js` and its map beside the user's `.ts` where nothing
|
|
344
|
+
// cleans them up.
|
|
345
|
+
const rootDir = commonAncestorDirectory(path.dirname(entry), context.root);
|
|
346
|
+
const tsconfig = path.join(
|
|
347
|
+
context.root,
|
|
348
|
+
`.ttsx-entry.${PROCESS_CACHE_KEY}.tsconfig.json`,
|
|
349
|
+
);
|
|
350
|
+
fs.writeFileSync(
|
|
351
|
+
tsconfig,
|
|
352
|
+
JSON.stringify(
|
|
353
|
+
{
|
|
354
|
+
extends: context.tsconfig.replace(/\\/g, "/"),
|
|
355
|
+
compilerOptions: { rootDir: rootDir.replace(/\\/g, "/") },
|
|
356
|
+
// `files` alone does not displace an inherited `include`, and an
|
|
357
|
+
// inherited `exclude` could drop the entry back out of the program, so
|
|
358
|
+
// both are overridden explicitly.
|
|
359
|
+
files: [entry.replace(/\\/g, "/")],
|
|
360
|
+
include: [],
|
|
361
|
+
exclude: [],
|
|
362
|
+
},
|
|
363
|
+
null,
|
|
364
|
+
2,
|
|
365
|
+
),
|
|
366
|
+
"utf8",
|
|
367
|
+
);
|
|
368
|
+
try {
|
|
369
|
+
const project = readProjectConfig({
|
|
370
|
+
cwd: context.root,
|
|
371
|
+
projectRoot: options.projectRoot,
|
|
372
|
+
tsconfig,
|
|
373
|
+
});
|
|
374
|
+
const emitDir = path.join(context.virtualRoot, ENTRY_PROJECT_EMIT_DIR);
|
|
375
|
+
fs.mkdirSync(emitDir, { recursive: true });
|
|
376
|
+
const result = runBuild({
|
|
377
|
+
binary: options.binary,
|
|
378
|
+
checkers: options.checkers,
|
|
379
|
+
cwd: context.root,
|
|
380
|
+
emit: true,
|
|
381
|
+
env: options.env,
|
|
382
|
+
forceListEmittedFiles: true,
|
|
383
|
+
cacheDir: context.pluginCacheDir,
|
|
384
|
+
outDir: emitDir,
|
|
385
|
+
passthrough: options.passthrough,
|
|
386
|
+
forceRuntimeSourceMap: context.forceRuntimeSourceMap,
|
|
387
|
+
pluginConfigDir: options.pluginConfigDir,
|
|
388
|
+
plugins: options.plugins,
|
|
389
|
+
quiet: true,
|
|
390
|
+
resolvedProject: project,
|
|
391
|
+
singleThreaded: options.singleThreaded,
|
|
392
|
+
tsconfig,
|
|
393
|
+
});
|
|
394
|
+
if (result.status !== 0) {
|
|
395
|
+
removeRuntimeOutput(context.processDir);
|
|
396
|
+
throw new Error(
|
|
397
|
+
[
|
|
398
|
+
`ttsx: entry check failed for ${entry}`,
|
|
399
|
+
result.stderr || result.stdout,
|
|
400
|
+
]
|
|
401
|
+
.filter((line) => line.trim().length !== 0)
|
|
402
|
+
.join("\n"),
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
context.emitDir = emitDir;
|
|
406
|
+
context.runtimeRootDir = rootDir;
|
|
407
|
+
context.moduleOptions = projectModuleOptions(project.compilerOptions);
|
|
408
|
+
context.emittedFiles =
|
|
409
|
+
result.emittedFiles && result.emittedFiles.length !== 0
|
|
410
|
+
? result.emittedFiles
|
|
411
|
+
: undefined;
|
|
412
|
+
} finally {
|
|
413
|
+
try {
|
|
414
|
+
fs.rmSync(tsconfig, { force: true });
|
|
415
|
+
} catch {
|
|
416
|
+
// 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.
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* The nearest directory containing both `left` and `right`, in the physical
|
|
425
|
+
* spelling both of them share.
|
|
426
|
+
*
|
|
427
|
+
* Containment is asked through the same filesystem-identity predicate the
|
|
428
|
+
* runtime hooks use, and the answer is resolved through it too. Its caller now
|
|
429
|
+
* passes an already-resolved directory, so the two spellings agree before the
|
|
430
|
+
* walk starts; the predicate stays because this returns a `rootDir` that tsgo
|
|
431
|
+
* takes verbatim, and answering in anything but the physical spelling would
|
|
432
|
+
* leave tsgo unable to place a sibling source under it.
|
|
433
|
+
*
|
|
434
|
+
* Falls back to the entry's directory when there genuinely is no shared
|
|
435
|
+
* ancestor, as on two different Windows volumes: the entry still has to
|
|
436
|
+
* compile, and a root that contains it is the closest thing to correct
|
|
437
|
+
* available.
|
|
438
|
+
*/
|
|
439
|
+
function commonAncestorDirectory(left: string, right: string): string {
|
|
440
|
+
const identities = createFilesystemPathIdentityContext({
|
|
441
|
+
throwOnRealpathError: false,
|
|
442
|
+
});
|
|
443
|
+
const from = identities.resolve(path.resolve(left)).path;
|
|
444
|
+
const target = identities.resolve(path.resolve(right)).path;
|
|
445
|
+
let current = from;
|
|
446
|
+
for (;;) {
|
|
447
|
+
if (identities.isWithin(current, target)) {
|
|
448
|
+
return current;
|
|
449
|
+
}
|
|
450
|
+
const parent = path.dirname(current);
|
|
451
|
+
if (parent === current) {
|
|
452
|
+
return from;
|
|
453
|
+
}
|
|
454
|
+
current = parent;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
187
458
|
function removeRuntimeOutput(directory: string): void {
|
|
188
459
|
try {
|
|
189
460
|
fs.rmSync(directory, { recursive: true, force: true });
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { pathToFileURL } from "node:url";
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
entryModuleFormat,
|
|
6
|
+
installRuntimeHooks,
|
|
7
|
+
realPath,
|
|
8
|
+
} from "./runtimeHooks";
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
11
|
* Bootstrap `ttsx` spawns as the child's main module: it installs the runtime
|
|
@@ -29,7 +33,12 @@ process.argv = [process.argv[0]!, entry, ...forwarded];
|
|
|
29
33
|
|
|
30
34
|
installRuntimeHooks();
|
|
31
35
|
|
|
32
|
-
|
|
36
|
+
// The physical path, because the format question is a runtime question: the
|
|
37
|
+
// hooks serve the entry under its real path, and `moduleFormat` walks from the
|
|
38
|
+
// path it is handed to find the `package.json` that decides. Asking from a
|
|
39
|
+
// symlink's own directory reads the wrong package scope and hands an ESM emit
|
|
40
|
+
// to `require`.
|
|
41
|
+
const kind = entryModuleFormat(realPath(path.resolve(entry)));
|
|
33
42
|
|
|
34
43
|
function fail(error: unknown): never {
|
|
35
44
|
// Match Node's own uncaught-exception surfacing for a thrown entry error.
|
|
@@ -8,6 +8,7 @@ import { createNativeProjectContextArgs } from "../../compiler/internal/project/
|
|
|
8
8
|
import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
|
|
9
9
|
import { resolveBinary } from "../../compiler/internal/resolveBinary";
|
|
10
10
|
import { resolveTsgo } from "../../compiler/internal/resolveTsgo";
|
|
11
|
+
import { outputText, spawnNative } from "../../compiler/internal/spawnNative";
|
|
11
12
|
import {
|
|
12
13
|
type ProjectInputPathIdentityContext,
|
|
13
14
|
createProjectInputPathIdentityContext,
|
|
@@ -43,8 +44,6 @@ type TtscserverEnvironment = {
|
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
const LSP_SELECTION_STABILITY_ATTEMPTS = 3;
|
|
46
|
-
const LSP_PROJECT_INPUT_TIMEOUT_MS = 30_000;
|
|
47
|
-
const LSP_PROJECT_INPUT_MAX_BUFFER = 4 * 1024 * 1024;
|
|
48
47
|
|
|
49
48
|
/**
|
|
50
49
|
* Drive the ttscserver native binary from a node launcher. The launcher is
|
|
@@ -359,22 +358,18 @@ function captureInitialLSPProjectInputs(options: {
|
|
|
359
358
|
pluginConfigOrigin: options.pluginConfigOrigin,
|
|
360
359
|
tsgoBinary: options.tsgoBinary,
|
|
361
360
|
});
|
|
362
|
-
const result =
|
|
361
|
+
const result = spawnNative(plugin.binary, args, {
|
|
363
362
|
cwd: options.project.root,
|
|
364
|
-
encoding: "utf8",
|
|
365
363
|
env,
|
|
366
|
-
maxBuffer: LSP_PROJECT_INPUT_MAX_BUFFER,
|
|
367
|
-
timeout: LSP_PROJECT_INPUT_TIMEOUT_MS,
|
|
368
|
-
windowsHide: true,
|
|
369
364
|
});
|
|
370
365
|
if (result.error) {
|
|
371
366
|
throw new Error(
|
|
372
367
|
`ttscserver: ${plugin.name ?? plugin.binary} project-inputs failed: ${result.error.message}`,
|
|
373
368
|
);
|
|
374
369
|
}
|
|
375
|
-
const stdout = result.stdout.trim();
|
|
370
|
+
const stdout = outputText(result.stdout).trim();
|
|
376
371
|
if (result.status !== 0) {
|
|
377
|
-
const detail = result.stderr.trim() || stdout;
|
|
372
|
+
const detail = outputText(result.stderr).trim() || stdout;
|
|
378
373
|
throw new Error(
|
|
379
374
|
`ttscserver: ${plugin.name ?? plugin.binary} project-inputs failed${detail ? `: ${detail}` : ""}`,
|
|
380
375
|
);
|
|
@@ -276,7 +276,7 @@ function runPreparedEntry(
|
|
|
276
276
|
depCacheDir,
|
|
277
277
|
emitDir: execution.emitDir,
|
|
278
278
|
emittedFiles: execution.emittedFiles,
|
|
279
|
-
|
|
279
|
+
moduleOptions: execution.moduleOptions,
|
|
280
280
|
projectRoot: execution.projectRoot,
|
|
281
281
|
rootDir: execution.rootDir,
|
|
282
282
|
}),
|
|
@@ -299,7 +299,6 @@ function runPreparedEntry(
|
|
|
299
299
|
sourceEntry,
|
|
300
300
|
...parsed.passthrough,
|
|
301
301
|
];
|
|
302
|
-
const evaluatorBoundary = readEvaluatorBoundary(process.env);
|
|
303
302
|
const runtimeEnv: NodeJS.ProcessEnv = {
|
|
304
303
|
...process.env,
|
|
305
304
|
NODE_OPTIONS: appendNodeOption(
|
|
@@ -309,42 +308,12 @@ function runPreparedEntry(
|
|
|
309
308
|
TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgo,
|
|
310
309
|
TTSX_RUNTIME_MANIFEST: manifestPath,
|
|
311
310
|
};
|
|
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
|
-
|
|
316
311
|
const result = spawnSync(process.execPath, args, {
|
|
317
312
|
cwd,
|
|
318
313
|
env: runtimeEnv,
|
|
319
|
-
|
|
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
|
+
stdio: "inherit",
|
|
333
315
|
windowsHide: true,
|
|
334
316
|
});
|
|
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
|
-
}
|
|
348
317
|
if (result.error) {
|
|
349
318
|
process.stderr.write(`${result.error.message}\n`);
|
|
350
319
|
return 1;
|
|
@@ -355,51 +324,6 @@ function runPreparedEntry(
|
|
|
355
324
|
}
|
|
356
325
|
}
|
|
357
326
|
|
|
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
|
-
|
|
403
327
|
function removeRuntimeOutput(directory: string): void {
|
|
404
328
|
try {
|
|
405
329
|
fs.rmSync(directory, { force: true, recursive: true });
|