ttsc 0.18.4 → 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.
- package/README.md +5 -6
- package/cmd/graphbench/main.go +1 -1
- package/cmd/platform/main.go +1 -48
- package/cmd/ttsc/build_branches_test.go +5 -1
- package/cmd/ttsc/main.go +0 -50
- package/cmd/ttsc/main_alias_branches_test.go +6 -12
- package/cmd/ttscgraph/dump.go +13 -3
- package/cmd/ttscserver/main.go +5 -3
- package/driver/lsp.go +13 -1
- package/driver/plugins.go +35 -0
- package/driver/program.go +7 -8
- package/driver/rewrite.go +381 -21
- package/go.mod +1 -0
- package/internal/graph/build.go +297 -3
- package/internal/graph/build_records_a_node_per_top_level_declaration_test.go +16 -2
- package/internal/graph/diagnostics.go +5 -4
- package/internal/graph/dump.go +60 -4
- package/internal/graph/dump_is_a_function_of_the_source_test.go +71 -0
- package/internal/graph/dump_serializes_the_full_graph_test.go +16 -4
- package/internal/graph/edges.go +122 -11
- package/internal/graph/exports.go +74 -17
- package/internal/graph/graph.go +33 -1
- package/internal/graph/node_identity_is_position_invariant_test.go +4 -2
- package/internal/graph/resolve.go +24 -0
- package/internal/graphsymbols/provider.go +50 -14
- package/internal/lspserver/lsp_native_plugin_source.go +77 -23
- package/internal/lspserver/lsp_plugin.go +19 -2
- package/internal/lspserver/lsp_proxy.go +244 -52
- package/internal/lspserver/lsp_server.go +35 -27
- package/internal/lspserver/lsp_symbols.go +25 -16
- package/lib/TtscCompiler.d.ts +10 -0
- package/lib/TtscCompiler.js +24 -6
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/TtscService.js +9 -7
- package/lib/TtscService.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.js +1 -0
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/project/createNativeProjectContextArgs.d.ts +3 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js +14 -0
- package/lib/compiler/internal/project/createNativeProjectContextArgs.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.js +53 -4
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/project/resolveProjectConfig.d.ts +9 -2
- package/lib/compiler/internal/project/resolveProjectConfig.js +45 -16
- package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentTransformProcess.d.ts +16 -5
- package/lib/compiler/internal/residentTransformProcess.js +68 -20
- package/lib/compiler/internal/residentTransformProcess.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +11 -0
- package/lib/compiler/internal/runBuild.js +44 -8
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +7 -4
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +16 -0
- package/lib/compiler/internal/sharedHostHelpers.js +23 -0
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/startResidentTransform.js +18 -1
- package/lib/compiler/internal/startResidentTransform.js.map +1 -1
- package/lib/compiler/internal/transformProjectInMemory.js +34 -8
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +17 -0
- package/lib/flags/parser.js +11 -1
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.js +7 -0
- package/lib/flags/schema.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.js +21 -2
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +13 -53
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +11 -0
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +27 -11
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +81 -0
- package/lib/launcher/internal/runtimeHooks.js +632 -43
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/servedSourceMap.d.ts +25 -0
- package/lib/launcher/internal/servedSourceMap.js +164 -0
- package/lib/launcher/internal/servedSourceMap.js.map +1 -0
- package/lib/plugin/internal/buildSourcePlugin.d.ts +146 -2
- package/lib/plugin/internal/buildSourcePlugin.js +651 -113
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +7 -0
- package/lib/plugin/internal/loadProjectPlugins.js +33 -10
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerContext.d.ts +16 -2
- package/lib/structures/ITtscPluginCapabilities.d.ts +8 -0
- package/lib/structures/ITtscPluginFactoryContext.d.ts +14 -0
- package/lib/structures/internal/ITtscParsedProjectConfig.d.ts +3 -0
- package/lib/structures/internal/ITtscProjectIdentity.d.ts +17 -0
- package/lib/structures/internal/ITtscProjectIdentity.js +3 -0
- package/lib/structures/internal/ITtscProjectIdentity.js.map +1 -0
- package/lib/structures/internal/TtscCommonOptions.d.ts +8 -0
- package/package.json +11 -8
- package/shim/ast/lint.go +10 -0
- package/shim/ast/shim.go +44 -0
- package/shim/checker/enums_gen.go +19 -0
- package/shim/checker/shim.go +130 -0
- package/shim/diagnosticwriter/lint.go +30 -3
- package/shim/scanner/regexp.go +27 -0
- package/src/TtscCompiler.ts +25 -6
- package/src/TtscService.ts +15 -7
- package/src/compiler/internal/compileProjectInMemory.ts +1 -0
- package/src/compiler/internal/project/createNativeProjectContextArgs.ts +15 -0
- package/src/compiler/internal/project/readProjectConfig.ts +61 -5
- package/src/compiler/internal/project/resolveProjectConfig.ts +51 -21
- package/src/compiler/internal/residentTransformProcess.ts +95 -21
- package/src/compiler/internal/runBuild.ts +76 -9
- package/src/compiler/internal/runSingleFileEmit.ts +7 -4
- package/src/compiler/internal/sharedHostHelpers.ts +24 -0
- package/src/compiler/internal/startResidentTransform.ts +21 -1
- package/src/compiler/internal/transformProjectInMemory.ts +49 -6
- package/src/flags/parser.ts +28 -1
- package/src/flags/schema.ts +8 -0
- package/src/launcher/internal/prepareExecution.ts +22 -2
- package/src/launcher/internal/runTtsc.ts +13 -61
- package/src/launcher/internal/runTtscserver.ts +14 -0
- package/src/launcher/internal/runTtsx.ts +28 -13
- package/src/launcher/internal/runtimeHooks.ts +771 -53
- package/src/launcher/internal/servedSourceMap.ts +183 -0
- package/src/plugin/internal/buildSourcePlugin.ts +859 -122
- package/src/plugin/internal/loadProjectPlugins.ts +36 -6
- package/src/structures/ITtscCompilerContext.ts +17 -2
- package/src/structures/ITtscPluginCapabilities.ts +9 -0
- package/src/structures/ITtscPluginFactoryContext.ts +15 -0
- package/src/structures/internal/ITtscParsedProjectConfig.ts +3 -0
- package/src/structures/internal/ITtscProjectIdentity.ts +17 -0
- package/src/structures/internal/TtscCommonOptions.ts +8 -0
- package/tools/shim_audit/baseline.json +16 -7
- package/tools/shim_audit/main.go +777 -71
- package/tools/shim_audit/main_test.go +28 -1
- package/tools/shim_audit/producer_exemptions_require_rationale_test.go +42 -0
- package/tools/shim_audit/producer_surface_includes_exposed_methods_test.go +154 -0
- package/tools/shim_audit/producer_surface_rejects_unproducible_reference_test.go +142 -0
- package/utility/host.go +46 -20
- package/utility/serve.go +1 -1
- package/shim/checker/signature_api_completeness.go +0 -58
|
@@ -5,7 +5,7 @@ import path from "node:path";
|
|
|
5
5
|
import type { ITtscProjectPluginConfig } from "../../../structures/ITtscProjectPluginConfig";
|
|
6
6
|
import type { ITtscParsedProjectConfig } from "../../../structures/internal/ITtscParsedProjectConfig";
|
|
7
7
|
import type { ITtscProjectLocatorOptions } from "../../../structures/internal/ITtscProjectLocatorOptions";
|
|
8
|
-
import {
|
|
8
|
+
import { resolveProjectIdentity } from "./resolveProjectConfig";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Compiler option keys whose values are file-system paths that must be resolved
|
|
@@ -40,10 +40,9 @@ type ResolvedCompilerOptions = {
|
|
|
40
40
|
export function readProjectConfig(
|
|
41
41
|
opts: ITtscProjectLocatorOptions = {},
|
|
42
42
|
): ITtscParsedProjectConfig {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
: path.dirname(tsconfig);
|
|
43
|
+
const identity = resolveProjectIdentity(opts);
|
|
44
|
+
const tsconfig = identity.physicalConfigPath;
|
|
45
|
+
const root = identity.physicalProjectRoot;
|
|
47
46
|
const compilerOptions = readResolvedCompilerOptions(tsconfig);
|
|
48
47
|
return {
|
|
49
48
|
compilerOptions: {
|
|
@@ -51,6 +50,7 @@ export function readProjectConfig(
|
|
|
51
50
|
outDir: compilerOptions.outDir,
|
|
52
51
|
plugins: compilerOptions.plugins,
|
|
53
52
|
},
|
|
53
|
+
identity,
|
|
54
54
|
path: tsconfig,
|
|
55
55
|
pluginBaseDirs: compilerOptions.pluginBaseDirs,
|
|
56
56
|
root,
|
|
@@ -254,6 +254,14 @@ function resolveExtendsConfig(tsconfig: string, specifier: string): string {
|
|
|
254
254
|
return resolveExistingExtendsPath(path.resolve(baseDir, specifier));
|
|
255
255
|
}
|
|
256
256
|
const resolver = createRequire(tsconfig);
|
|
257
|
+
// A bare package root selects its preset through `package.json#tsconfig`,
|
|
258
|
+
// matching TypeScript's config resolution. Presets shipped this way often
|
|
259
|
+
// have no JavaScript/JSON entrypoint at all, so Node's entrypoint resolver
|
|
260
|
+
// and the `<specifier>.json` fallback below both miss them.
|
|
261
|
+
const viaManifest = resolvePackageManifestTsconfig(resolver, specifier);
|
|
262
|
+
if (viaManifest !== undefined) {
|
|
263
|
+
return resolveExistingExtendsPath(viaManifest);
|
|
264
|
+
}
|
|
257
265
|
try {
|
|
258
266
|
return resolveRealPath(resolver.resolve(specifier));
|
|
259
267
|
} catch {
|
|
@@ -261,6 +269,54 @@ function resolveExtendsConfig(tsconfig: string, specifier: string): string {
|
|
|
261
269
|
}
|
|
262
270
|
}
|
|
263
271
|
|
|
272
|
+
/**
|
|
273
|
+
* When `specifier` names a bare package root, resolve the config file its
|
|
274
|
+
* `package.json#tsconfig` field selects (anchored at the package directory).
|
|
275
|
+
* Returns `undefined` when the specifier is a subpath, the manifest is
|
|
276
|
+
* unresolvable/unparsable, or it declares no `tsconfig` field, so the caller
|
|
277
|
+
* falls back to Node entrypoint resolution.
|
|
278
|
+
*/
|
|
279
|
+
function resolvePackageManifestTsconfig(
|
|
280
|
+
resolver: NodeRequire,
|
|
281
|
+
specifier: string,
|
|
282
|
+
): string | undefined {
|
|
283
|
+
if (!isBarePackageRoot(specifier)) {
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
let manifestPath: string;
|
|
287
|
+
try {
|
|
288
|
+
manifestPath = resolver.resolve(`${specifier}/package.json`);
|
|
289
|
+
} catch {
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
let field: unknown;
|
|
293
|
+
try {
|
|
294
|
+
field = (
|
|
295
|
+
JSON.parse(stripLeadingBom(fs.readFileSync(manifestPath, "utf8"))) as {
|
|
296
|
+
tsconfig?: unknown;
|
|
297
|
+
}
|
|
298
|
+
).tsconfig;
|
|
299
|
+
} catch {
|
|
300
|
+
return undefined;
|
|
301
|
+
}
|
|
302
|
+
if (typeof field !== "string" || field.length === 0) {
|
|
303
|
+
return undefined;
|
|
304
|
+
}
|
|
305
|
+
return path.resolve(path.dirname(manifestPath), field);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Return true when `specifier` is a bare package root (no subpath): a plain
|
|
310
|
+
* package name (`preset`) or a scoped name (`@scope/preset`). Subpaths such as
|
|
311
|
+
* `@scope/preset/base.json` resolve directly and keep their current meaning.
|
|
312
|
+
*/
|
|
313
|
+
function isBarePackageRoot(specifier: string): boolean {
|
|
314
|
+
if (specifier.startsWith("@")) {
|
|
315
|
+
return specifier.split("/").length === 2;
|
|
316
|
+
}
|
|
317
|
+
return !specifier.includes("/");
|
|
318
|
+
}
|
|
319
|
+
|
|
264
320
|
/**
|
|
265
321
|
* Given an on-disk location, try the path as-is, with `.json` appended, and as
|
|
266
322
|
* a directory containing `tsconfig.json`. Returns the first match or throws.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
|
+
import type { ITtscProjectIdentity } from "../../../structures/internal/ITtscProjectIdentity";
|
|
4
5
|
import type { ITtscProjectLocatorOptions } from "../../../structures/internal/ITtscProjectLocatorOptions";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -13,13 +14,29 @@ import type { ITtscProjectLocatorOptions } from "../../../structures/internal/IT
|
|
|
13
14
|
* walking up from the file's directory.
|
|
14
15
|
* 3. `opts.cwd` — the nearest ancestor config walking up from cwd.
|
|
15
16
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
17
|
+
* Returns the real (symlink-resolved) absolute config path. Use
|
|
18
|
+
* {@link resolveProjectIdentity} when both the caller-selected spelling and the
|
|
19
|
+
* Program identity are required.
|
|
18
20
|
*/
|
|
19
21
|
export function resolveProjectConfig(
|
|
20
22
|
opts: ITtscProjectLocatorOptions = {},
|
|
21
23
|
): string {
|
|
24
|
+
return resolveProjectIdentity(opts).physicalConfigPath;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve the selected config while retaining its lexical spelling separately
|
|
29
|
+
* from the physical paths used by the TypeScript Program.
|
|
30
|
+
*/
|
|
31
|
+
export function resolveProjectIdentity(
|
|
32
|
+
opts: ITtscProjectLocatorOptions = {},
|
|
33
|
+
): Omit<ITtscProjectIdentity, "pluginConfigOrigin"> {
|
|
22
34
|
const cwd = path.resolve(opts.cwd ?? process.cwd());
|
|
35
|
+
const explicitProjectRoot =
|
|
36
|
+
opts.projectRoot === undefined || opts.projectRoot === ""
|
|
37
|
+
? undefined
|
|
38
|
+
: resolveAbsolutePath(cwd, opts.projectRoot);
|
|
39
|
+
let logicalConfigPath: string;
|
|
23
40
|
if (opts.tsconfig) {
|
|
24
41
|
const resolved = resolveAbsolutePath(cwd, opts.tsconfig);
|
|
25
42
|
if (!fs.existsSync(resolved)) {
|
|
@@ -35,30 +52,43 @@ export function resolveProjectConfig(
|
|
|
35
52
|
if (isDirectory(resolved)) {
|
|
36
53
|
const tsconfigInDir = path.join(resolved, "tsconfig.json");
|
|
37
54
|
if (fs.existsSync(tsconfigInDir)) {
|
|
38
|
-
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
logicalConfigPath = tsconfigInDir;
|
|
56
|
+
} else {
|
|
57
|
+
const jsconfigInDir = path.join(resolved, "jsconfig.json");
|
|
58
|
+
if (fs.existsSync(jsconfigInDir)) {
|
|
59
|
+
logicalConfigPath = jsconfigInDir;
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`ttsc: directory has no tsconfig.json / jsconfig.json: ${resolved}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
43
65
|
}
|
|
66
|
+
} else {
|
|
67
|
+
logicalConfigPath = resolved;
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
const start = opts.file ? resolveAbsolutePath(cwd, opts.file) : cwd;
|
|
71
|
+
const from = isDirectory(start) ? start : path.dirname(start);
|
|
72
|
+
const found = findUp(from, ["tsconfig.json", "jsconfig.json"]);
|
|
73
|
+
if (!found) {
|
|
44
74
|
throw new Error(
|
|
45
|
-
`ttsc:
|
|
75
|
+
`ttsc: could not find tsconfig.json or jsconfig.json starting from ${from}`,
|
|
46
76
|
);
|
|
47
77
|
}
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const start = opts.file
|
|
52
|
-
? resolveRealPath(resolveAbsolutePath(cwd, opts.file))
|
|
53
|
-
: cwd;
|
|
54
|
-
const from = isDirectory(start) ? start : path.dirname(start);
|
|
55
|
-
const found = findUp(from, ["tsconfig.json", "jsconfig.json"]);
|
|
56
|
-
if (!found) {
|
|
57
|
-
throw new Error(
|
|
58
|
-
`ttsc: could not find tsconfig.json or jsconfig.json starting from ${from}`,
|
|
59
|
-
);
|
|
78
|
+
logicalConfigPath = found;
|
|
60
79
|
}
|
|
61
|
-
|
|
80
|
+
const physicalConfigPath = resolveRealPath(logicalConfigPath);
|
|
81
|
+
const physicalProjectRoot = resolveRealPath(
|
|
82
|
+
explicitProjectRoot ?? path.dirname(physicalConfigPath),
|
|
83
|
+
);
|
|
84
|
+
return {
|
|
85
|
+
...(explicitProjectRoot === undefined ? {} : { explicitProjectRoot }),
|
|
86
|
+
invocationCwd: cwd,
|
|
87
|
+
logicalConfigPath,
|
|
88
|
+
logicalProjectRoot: path.dirname(logicalConfigPath),
|
|
89
|
+
physicalConfigPath,
|
|
90
|
+
physicalProjectRoot,
|
|
91
|
+
};
|
|
62
92
|
}
|
|
63
93
|
|
|
64
94
|
/** Resolve `target` against `cwd` when it is not already absolute. */
|
|
@@ -4,6 +4,19 @@ import { type Interface, createInterface } from "node:readline";
|
|
|
4
4
|
/** Cap on retained stderr so a long-lived host cannot grow it without bound. */
|
|
5
5
|
const STDERR_TAIL_LIMIT = 64 * 1024;
|
|
6
6
|
|
|
7
|
+
/** Cap on how much of an offending line an error message echoes back. */
|
|
8
|
+
const REPLY_ECHO_LIMIT = 200;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The operation a request expects a reply for. The host answers a transform
|
|
12
|
+
* request (`{"file":...}`) with `{"typescript":...,"found":...}` and an update
|
|
13
|
+
* request (`{"update":...,"content":...}`) with `{"updated":...}`. The client
|
|
14
|
+
* knows which it sent, so it validates the reply's shape against the operation
|
|
15
|
+
* before handing it back — a well-formed JSON object of the wrong operation
|
|
16
|
+
* shape is a protocol error, not a valid negative result.
|
|
17
|
+
*/
|
|
18
|
+
export type ResidentReplyKind = "transform" | "update";
|
|
19
|
+
|
|
7
20
|
/** Options for spawning the resident transform host. */
|
|
8
21
|
export interface ResidentTransformProcessOptions {
|
|
9
22
|
args: readonly string[];
|
|
@@ -13,6 +26,7 @@ export interface ResidentTransformProcessOptions {
|
|
|
13
26
|
}
|
|
14
27
|
|
|
15
28
|
interface PendingRequest {
|
|
29
|
+
kind: ResidentReplyKind;
|
|
16
30
|
reject: (reason: Error) => void;
|
|
17
31
|
resolve: (reply: Record<string, unknown>) => void;
|
|
18
32
|
}
|
|
@@ -78,13 +92,16 @@ export class ResidentTransformProcess {
|
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
/**
|
|
81
|
-
* Send one request to the host and resolve with its
|
|
82
|
-
* host answers in FIFO order
|
|
83
|
-
* payload
|
|
84
|
-
*
|
|
95
|
+
* Send one request to the host and resolve with its validated JSON reply. The
|
|
96
|
+
* host answers in FIFO order; `kind` tells the client which reply shape the
|
|
97
|
+
* payload asks for, so the reply is validated as a well-formed `kind` reply
|
|
98
|
+
* before it resolves. Rejects when the reply is not a valid `kind` reply,
|
|
99
|
+
* when the host has already failed or exited, or if writing the request
|
|
100
|
+
* fails.
|
|
85
101
|
*/
|
|
86
102
|
public request(
|
|
87
103
|
payload: Record<string, unknown>,
|
|
104
|
+
kind: ResidentReplyKind,
|
|
88
105
|
): Promise<Record<string, unknown>> {
|
|
89
106
|
if (this.failure !== undefined) {
|
|
90
107
|
return Promise.reject(this.failure);
|
|
@@ -96,7 +113,7 @@ export class ResidentTransformProcess {
|
|
|
96
113
|
);
|
|
97
114
|
}
|
|
98
115
|
return new Promise<Record<string, unknown>>((resolve, reject) => {
|
|
99
|
-
const pending: PendingRequest = { reject, resolve };
|
|
116
|
+
const pending: PendingRequest = { kind, reject, resolve };
|
|
100
117
|
this.pending.push(pending);
|
|
101
118
|
stdin.write(`${JSON.stringify(payload)}\n`, (error) => {
|
|
102
119
|
if (error) {
|
|
@@ -149,7 +166,39 @@ export class ResidentTransformProcess {
|
|
|
149
166
|
}
|
|
150
167
|
return;
|
|
151
168
|
}
|
|
152
|
-
|
|
169
|
+
const reply = parseReplyObject(trimmed);
|
|
170
|
+
if (reply === undefined) {
|
|
171
|
+
// Framing violation: the line is not a JSON object, so it cannot
|
|
172
|
+
// represent any reply. This request already left the FIFO, so reject it,
|
|
173
|
+
// then fail the whole process: a bad line may be corruption that shifted
|
|
174
|
+
// the stream, and the host's real reply that follows must not be paired
|
|
175
|
+
// with a later request. `fail` marks the failure so that trailing line is
|
|
176
|
+
// treated as benign instead of unsolicited.
|
|
177
|
+
const error = new Error(
|
|
178
|
+
`ttsc: resident transform host sent a malformed reply: ${echoLine(
|
|
179
|
+
trimmed,
|
|
180
|
+
)}`,
|
|
181
|
+
);
|
|
182
|
+
request.reject(error);
|
|
183
|
+
this.fail(error);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (!isValidReply(reply, request.kind)) {
|
|
187
|
+
// Operation-shape violation: a well-formed JSON object that is not a valid
|
|
188
|
+
// reply for the operation this request sent. FIFO framing is intact — one
|
|
189
|
+
// line consumed exactly one slot — so only this request is corrupt; later
|
|
190
|
+
// replies still pair correctly. Reject just this request instead of
|
|
191
|
+
// failing the whole process.
|
|
192
|
+
request.reject(
|
|
193
|
+
new Error(
|
|
194
|
+
`ttsc: resident transform host sent an invalid ${request.kind} reply: ${echoLine(
|
|
195
|
+
trimmed,
|
|
196
|
+
)}`,
|
|
197
|
+
),
|
|
198
|
+
);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
request.resolve(reply);
|
|
153
202
|
}
|
|
154
203
|
|
|
155
204
|
private onReaderClose(): void {
|
|
@@ -194,23 +243,48 @@ export class ResidentTransformProcess {
|
|
|
194
243
|
}
|
|
195
244
|
|
|
196
245
|
/**
|
|
197
|
-
* Parse one reply line into a plain object
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
246
|
+
* Parse one reply line into a plain object, or `undefined` when the line is not
|
|
247
|
+
* a JSON object. Invalid JSON, arrays, primitives, and `null` all yield
|
|
248
|
+
* `undefined`: none of them can carry a reply's fields, so the caller treats
|
|
249
|
+
* them as a framing failure rather than an empty reply. Returning `{}` here
|
|
250
|
+
* would let a corrupt line masquerade as a valid negative result.
|
|
201
251
|
*/
|
|
202
|
-
function
|
|
252
|
+
function parseReplyObject(line: string): Record<string, unknown> | undefined {
|
|
253
|
+
let parsed: unknown;
|
|
203
254
|
try {
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
typeof parsed === "object" &&
|
|
207
|
-
parsed !== null &&
|
|
208
|
-
!Array.isArray(parsed)
|
|
209
|
-
) {
|
|
210
|
-
return parsed as Record<string, unknown>;
|
|
211
|
-
}
|
|
255
|
+
parsed = JSON.parse(line);
|
|
212
256
|
} catch {
|
|
213
|
-
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
260
|
+
return parsed as Record<string, unknown>;
|
|
261
|
+
}
|
|
262
|
+
return undefined;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Whether a parsed reply object is a well-formed reply for the given operation.
|
|
267
|
+
* A transform reply must carry a boolean `found`, and when `found` is `true` a
|
|
268
|
+
* string `typescript` (a found file always carries its transformed text); when
|
|
269
|
+
* `found` is `false` the text is irrelevant. An update reply must carry a
|
|
270
|
+
* boolean `updated`. Every other object shape is a protocol error.
|
|
271
|
+
*/
|
|
272
|
+
function isValidReply(
|
|
273
|
+
reply: Record<string, unknown>,
|
|
274
|
+
kind: ResidentReplyKind,
|
|
275
|
+
): boolean {
|
|
276
|
+
if (kind === "transform") {
|
|
277
|
+
if (typeof reply.found !== "boolean") {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
return reply.found ? typeof reply.typescript === "string" : true;
|
|
214
281
|
}
|
|
215
|
-
return
|
|
282
|
+
return typeof reply.updated === "boolean";
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Truncate an offending reply line so error messages stay bounded. */
|
|
286
|
+
function echoLine(line: string): string {
|
|
287
|
+
return line.length > REPLY_ECHO_LIMIT
|
|
288
|
+
? `${line.slice(0, REPLY_ECHO_LIMIT)}…`
|
|
289
|
+
: line;
|
|
216
290
|
}
|
|
@@ -7,15 +7,18 @@ import {
|
|
|
7
7
|
} from "../../plugin/internal/loadProjectPlugins";
|
|
8
8
|
import type { ITtscCompilerDiagnostic } from "../../structures/ITtscCompilerDiagnostic";
|
|
9
9
|
import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
|
|
10
|
+
import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
|
|
10
11
|
import type { TtscBuildOptions } from "../../structures/internal/TtscBuildOptions";
|
|
11
12
|
import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
|
|
12
13
|
import type { TtscCommonOptions } from "../../structures/internal/TtscCommonOptions";
|
|
14
|
+
import { createNativeProjectContextArgs } from "./project/createNativeProjectContextArgs";
|
|
13
15
|
import { readProjectConfig } from "./project/readProjectConfig";
|
|
14
16
|
import { resolveBinary } from "./resolveBinary";
|
|
15
17
|
import { resolveTsgo } from "./resolveTsgo";
|
|
16
18
|
import {
|
|
17
19
|
assertSharedHostCompatibility,
|
|
18
20
|
linkedTransformPlugins,
|
|
21
|
+
resolvePluginConfigDir,
|
|
19
22
|
selectSharedHostPlugin,
|
|
20
23
|
} from "./sharedHostHelpers";
|
|
21
24
|
import { outputText, spawnNative } from "./spawnNative";
|
|
@@ -23,6 +26,16 @@ import { outputText, spawnNative } from "./spawnNative";
|
|
|
23
26
|
type RunBuildOptions = TtscBuildOptions & {
|
|
24
27
|
skipDiagnosticsCheck?: boolean;
|
|
25
28
|
forceListEmittedFiles?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Emit an external source map from the direct tsgo build lane even when the
|
|
31
|
+
* project configures none. Set by the ttsx runtime builds so a served emit
|
|
32
|
+
* carries a map to inline under the source URL (issue #353). Applied only to
|
|
33
|
+
* the plain tsgo emit — never forwarded to a native plugin host, whose own
|
|
34
|
+
* emit honours the project's `sourceMap` setting.
|
|
35
|
+
*/
|
|
36
|
+
forceRuntimeSourceMap?: boolean;
|
|
37
|
+
/** Retain an already selected project's lexical identity across API lanes. */
|
|
38
|
+
resolvedProject?: ITtscParsedProjectConfig;
|
|
26
39
|
};
|
|
27
40
|
|
|
28
41
|
type BuildTiming = {
|
|
@@ -119,9 +132,13 @@ function formatTimingSeconds(ms: number): string {
|
|
|
119
132
|
|
|
120
133
|
/**
|
|
121
134
|
* Build the environment for a native plugin spawn. Injects `TTSC_TSGO_BINARY`
|
|
122
|
-
* and `TTSC_TTSX_BINARY` alongside the base env from `mergeEnv
|
|
123
|
-
*
|
|
124
|
-
*
|
|
135
|
+
* and `TTSC_TTSX_BINARY` alongside the base env from `mergeEnv`, plus
|
|
136
|
+
* `TTSC_PLUGIN_CONFIG_DIR` when the caller declared a plugin config anchor (an
|
|
137
|
+
* embedder compiling through a generated wrapper tsconfig) so config-file
|
|
138
|
+
* discovery walks the real project instead of the wrapper's temp-dir ancestry.
|
|
139
|
+
* For transform-stage plugins, also passes `TTSC_LINKED_PLUGINS_JSON`
|
|
140
|
+
* containing any linked sources so they run inside the same process as the host
|
|
141
|
+
* plugin.
|
|
125
142
|
*/
|
|
126
143
|
function nativePluginEnv(
|
|
127
144
|
extra: NodeJS.ProcessEnv | undefined,
|
|
@@ -129,12 +146,25 @@ function nativePluginEnv(
|
|
|
129
146
|
plugin?: ITtscLoadedNativePlugin,
|
|
130
147
|
): NodeJS.ProcessEnv {
|
|
131
148
|
const env = mergeEnv({
|
|
149
|
+
...(execution.pluginConfigDir === undefined
|
|
150
|
+
? {}
|
|
151
|
+
: { TTSC_PLUGIN_CONFIG_DIR: execution.pluginConfigDir }),
|
|
132
152
|
TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? execution.tsgo.binary,
|
|
133
153
|
TTSC_TTSX_BINARY:
|
|
134
154
|
process.env.TTSC_TTSX_BINARY ??
|
|
135
155
|
path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
|
|
136
156
|
...extra,
|
|
137
157
|
});
|
|
158
|
+
// The anchor is per-invocation state owned by this host: when this run
|
|
159
|
+
// declared none (and the caller's env does not name one), drop any value
|
|
160
|
+
// inherited from an ancestor ttsc process so a nested build never
|
|
161
|
+
// mis-anchors its plugins at the outer project.
|
|
162
|
+
if (
|
|
163
|
+
execution.pluginConfigDir === undefined &&
|
|
164
|
+
extra?.TTSC_PLUGIN_CONFIG_DIR === undefined
|
|
165
|
+
) {
|
|
166
|
+
delete env.TTSC_PLUGIN_CONFIG_DIR;
|
|
167
|
+
}
|
|
138
168
|
if (plugin?.stage === "transform") {
|
|
139
169
|
const linked = linkedTransformPlugins(execution.nativePlugins);
|
|
140
170
|
if (linked.length !== 0) {
|
|
@@ -664,6 +694,12 @@ function createTsgoBuildArgs(
|
|
|
664
694
|
if (execution.rewriteRelativeImportExtensionsForEmit) {
|
|
665
695
|
args.push("--rewriteRelativeImportExtensions");
|
|
666
696
|
}
|
|
697
|
+
// The ttsx runtime build asks for an external map when the project emits
|
|
698
|
+
// none, so its served emit carries a map to inline under the source URL.
|
|
699
|
+
// Pushed before passthrough so an explicit user `--sourceMap` still wins.
|
|
700
|
+
if (options.forceRuntimeSourceMap === true) {
|
|
701
|
+
args.push("--sourceMap", "true");
|
|
702
|
+
}
|
|
667
703
|
} else if (options.emit === false) {
|
|
668
704
|
args.push("--noEmit");
|
|
669
705
|
}
|
|
@@ -731,6 +767,16 @@ function createNativeBuildArgs(
|
|
|
731
767
|
"--plugins-json=" + serializeNativePlugins(plugins),
|
|
732
768
|
"--cwd=" + execution.projectRoot,
|
|
733
769
|
];
|
|
770
|
+
if (
|
|
771
|
+
selectSharedHostPlugin(plugins).capabilities?.projectContextArgs === true
|
|
772
|
+
) {
|
|
773
|
+
args.push(
|
|
774
|
+
...createNativeProjectContextArgs(
|
|
775
|
+
execution.project,
|
|
776
|
+
execution.pluginConfigDir,
|
|
777
|
+
),
|
|
778
|
+
);
|
|
779
|
+
}
|
|
734
780
|
if (options.emit === true) {
|
|
735
781
|
args.push("--emit");
|
|
736
782
|
}
|
|
@@ -763,6 +809,14 @@ function createNativeCheckArgs(
|
|
|
763
809
|
"--plugins-json=" + serializeNativePlugins(execution.nativePlugins),
|
|
764
810
|
"--cwd=" + execution.projectRoot,
|
|
765
811
|
];
|
|
812
|
+
if (plugin.capabilities?.projectContextArgs === true) {
|
|
813
|
+
args.push(
|
|
814
|
+
...createNativeProjectContextArgs(
|
|
815
|
+
execution.project,
|
|
816
|
+
execution.pluginConfigDir,
|
|
817
|
+
),
|
|
818
|
+
);
|
|
819
|
+
}
|
|
766
820
|
if (options.outDir) {
|
|
767
821
|
args.push("--outDir=" + path.resolve(execution.cwd, options.outDir));
|
|
768
822
|
}
|
|
@@ -1059,14 +1113,20 @@ export function createProcessDiagnostic(
|
|
|
1059
1113
|
* here so every code path in `runBuild` shares the same resolution logic.
|
|
1060
1114
|
*/
|
|
1061
1115
|
function resolveExecutionContext(
|
|
1062
|
-
options: TtscCommonOptions & {
|
|
1116
|
+
options: TtscCommonOptions & {
|
|
1117
|
+
emit?: boolean;
|
|
1118
|
+
resolvedProject?: ITtscParsedProjectConfig;
|
|
1119
|
+
tsconfig?: string;
|
|
1120
|
+
},
|
|
1063
1121
|
) {
|
|
1064
1122
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
1065
|
-
const project =
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1123
|
+
const project =
|
|
1124
|
+
options.resolvedProject ??
|
|
1125
|
+
readProjectConfig({
|
|
1126
|
+
cwd,
|
|
1127
|
+
projectRoot: options.projectRoot,
|
|
1128
|
+
tsconfig: options.tsconfig,
|
|
1129
|
+
});
|
|
1070
1130
|
const tsconfig = project.path;
|
|
1071
1131
|
const projectRoot = project.root;
|
|
1072
1132
|
const tsgo = resolveTsgo({ ...options, cwd: projectRoot });
|
|
@@ -1079,6 +1139,8 @@ function resolveExecutionContext(
|
|
|
1079
1139
|
cacheDir: options.cacheDir ?? options.env?.TTSC_CACHE_DIR,
|
|
1080
1140
|
cwd,
|
|
1081
1141
|
entries: options.plugins,
|
|
1142
|
+
env: { ...process.env, ...options.env },
|
|
1143
|
+
pluginConfigDir: options.pluginConfigDir,
|
|
1082
1144
|
projectRoot,
|
|
1083
1145
|
tsconfig,
|
|
1084
1146
|
}).nativePlugins;
|
|
@@ -1094,7 +1156,12 @@ function resolveExecutionContext(
|
|
|
1094
1156
|
return {
|
|
1095
1157
|
cwd,
|
|
1096
1158
|
nativePlugins,
|
|
1159
|
+
pluginConfigDir: resolvePluginConfigDir({
|
|
1160
|
+
cwd,
|
|
1161
|
+
pluginConfigDir: options.pluginConfigDir,
|
|
1162
|
+
}),
|
|
1097
1163
|
pluginSetupFailure,
|
|
1164
|
+
project,
|
|
1098
1165
|
projectNoEmit: project.compilerOptions.noEmit === true,
|
|
1099
1166
|
projectRoot,
|
|
1100
1167
|
rewriteRelativeImportExtensionsForEmit:
|
|
@@ -3,7 +3,7 @@ import os from "node:os";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
5
|
import type { TtscSingleFileEmitOptions } from "../../structures/internal/TtscSingleFileEmitOptions";
|
|
6
|
-
import {
|
|
6
|
+
import { readProjectConfig } from "./project/readProjectConfig";
|
|
7
7
|
import { resolveEmittedJavaScript } from "./resolveEmittedJavaScript";
|
|
8
8
|
import { runBuild } from "./runBuild";
|
|
9
9
|
|
|
@@ -24,12 +24,14 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
|
|
|
24
24
|
? options.file
|
|
25
25
|
: path.resolve(cwd, options.file),
|
|
26
26
|
);
|
|
27
|
-
const
|
|
27
|
+
const project = readProjectConfig({
|
|
28
28
|
cwd,
|
|
29
|
-
file:
|
|
29
|
+
file: options.file,
|
|
30
|
+
projectRoot: options.projectRoot,
|
|
30
31
|
tsconfig: options.tsconfig,
|
|
31
32
|
});
|
|
32
|
-
const
|
|
33
|
+
const tsconfig = project.path;
|
|
34
|
+
const projectRoot = project.root;
|
|
33
35
|
const outDir = fs.mkdtempSync(path.join(os.tmpdir(), "ttsc-single-file-"));
|
|
34
36
|
try {
|
|
35
37
|
const result = runBuild({
|
|
@@ -38,6 +40,7 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
|
|
|
38
40
|
emit: true,
|
|
39
41
|
forceListEmittedFiles: true,
|
|
40
42
|
outDir,
|
|
43
|
+
resolvedProject: project,
|
|
41
44
|
tsconfig,
|
|
42
45
|
});
|
|
43
46
|
if (result.status !== 0) {
|
|
@@ -1,5 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
1
3
|
import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoadedNativePlugin";
|
|
2
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the caller-declared plugin config anchor to an absolute path, or
|
|
7
|
+
* `undefined` when the caller did not declare one.
|
|
8
|
+
*
|
|
9
|
+
* The anchor exists for embedders that compile through a generated tsconfig
|
|
10
|
+
* outside the project (the bundler adapters' alias overlay): they set
|
|
11
|
+
* `pluginConfigDir` to the real project directory and every native plugin spawn
|
|
12
|
+
* forwards it as `TTSC_PLUGIN_CONFIG_DIR`, so config-file discovery walks the
|
|
13
|
+
* project instead of the wrapper's temp-dir ancestry. Callers that point at a
|
|
14
|
+
* user-authored tsconfig (even a wrapper outside the project) leave it unset,
|
|
15
|
+
* keeping discovery anchored at the tsconfig's own directory.
|
|
16
|
+
*/
|
|
17
|
+
export function resolvePluginConfigDir(options: {
|
|
18
|
+
cwd?: string;
|
|
19
|
+
pluginConfigDir?: string;
|
|
20
|
+
}): string | undefined {
|
|
21
|
+
if (options.pluginConfigDir === undefined || options.pluginConfigDir === "") {
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return path.resolve(options.cwd ?? process.cwd(), options.pluginConfigDir);
|
|
25
|
+
}
|
|
26
|
+
|
|
3
27
|
/**
|
|
4
28
|
* Reports whether the given transform source is linked into another compiler
|
|
5
29
|
* host instead of owning the process itself.
|
|
@@ -10,6 +10,7 @@ import { resolveTsgo } from "./resolveTsgo";
|
|
|
10
10
|
import {
|
|
11
11
|
assertSharedHostCompatibility,
|
|
12
12
|
linkedTransformPlugins,
|
|
13
|
+
resolvePluginConfigDir,
|
|
13
14
|
selectSharedHostPlugin,
|
|
14
15
|
} from "./sharedHostHelpers";
|
|
15
16
|
|
|
@@ -51,6 +52,8 @@ export function startResidentTransform(
|
|
|
51
52
|
cacheDir: context.cacheDir ?? context.env?.TTSC_CACHE_DIR,
|
|
52
53
|
cwd,
|
|
53
54
|
entries: context.plugins,
|
|
55
|
+
env: { ...process.env, ...context.env },
|
|
56
|
+
pluginConfigDir: context.pluginConfigDir,
|
|
54
57
|
projectRoot: context.projectRoot,
|
|
55
58
|
tsconfig: project.path,
|
|
56
59
|
});
|
|
@@ -84,7 +87,10 @@ export function startResidentTransform(
|
|
|
84
87
|
/**
|
|
85
88
|
* Build the environment for the resident host spawn. Matches the per-call
|
|
86
89
|
* transform spawn: injects the Node, tsgo, and ttsx binaries so the host never
|
|
87
|
-
* searches PATH,
|
|
90
|
+
* searches PATH, sets `TTSC_PLUGIN_CONFIG_DIR` when the caller declared a
|
|
91
|
+
* plugin config anchor (an embedder compiling through a generated wrapper
|
|
92
|
+
* tsconfig) so config-file discovery walks the real project instead of the
|
|
93
|
+
* wrapper's temp-dir ancestry, and forwards linked transform plugins via
|
|
88
94
|
* `TTSC_LINKED_PLUGINS_JSON`.
|
|
89
95
|
*/
|
|
90
96
|
function residentEnv(
|
|
@@ -92,15 +98,29 @@ function residentEnv(
|
|
|
92
98
|
tsgoBinary: string,
|
|
93
99
|
nativePlugins: readonly ITtscLoadedNativePlugin[],
|
|
94
100
|
): NodeJS.ProcessEnv {
|
|
101
|
+
const pluginConfigDir = resolvePluginConfigDir(context);
|
|
95
102
|
const env: NodeJS.ProcessEnv = {
|
|
96
103
|
...process.env,
|
|
97
104
|
TTSC_NODE_BINARY: process.env.TTSC_NODE_BINARY ?? process.execPath,
|
|
105
|
+
...(pluginConfigDir === undefined
|
|
106
|
+
? {}
|
|
107
|
+
: { TTSC_PLUGIN_CONFIG_DIR: pluginConfigDir }),
|
|
98
108
|
TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgoBinary,
|
|
99
109
|
TTSC_TTSX_BINARY:
|
|
100
110
|
process.env.TTSC_TTSX_BINARY ??
|
|
101
111
|
path.join(__dirname, "..", "..", "launcher", "ttsx.js"),
|
|
102
112
|
...context.env,
|
|
103
113
|
};
|
|
114
|
+
// The anchor is per-invocation state owned by this host: when this run
|
|
115
|
+
// declared none (and the caller's env does not name one), drop any value
|
|
116
|
+
// inherited from an ancestor ttsc process so a nested build never
|
|
117
|
+
// mis-anchors its plugins at the outer project.
|
|
118
|
+
if (
|
|
119
|
+
pluginConfigDir === undefined &&
|
|
120
|
+
context.env?.TTSC_PLUGIN_CONFIG_DIR === undefined
|
|
121
|
+
) {
|
|
122
|
+
delete env.TTSC_PLUGIN_CONFIG_DIR;
|
|
123
|
+
}
|
|
104
124
|
const linked = linkedTransformPlugins(nativePlugins);
|
|
105
125
|
if (linked.length !== 0) {
|
|
106
126
|
env.TTSC_LINKED_PLUGINS_JSON = serializeNativePlugins(linked);
|