ttsc 0.12.3 → 0.13.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/cmd/platform/main.go +9 -0
- package/cmd/ttsc/api_compile.go +122 -101
- package/cmd/ttsc/api_compile_branches_test.go +55 -57
- package/cmd/ttsc/api_transform.go +67 -49
- package/cmd/ttsc/api_transform_branches_test.go +30 -32
- package/cmd/ttsc/build.go +141 -108
- package/cmd/ttsc/build_branches_test.go +95 -97
- package/cmd/ttsc/filter.go +70 -0
- package/cmd/ttsc/filter_strips_unknown_forwarded_flag_test.go +61 -0
- package/cmd/ttsc/flags_gen.go +30 -0
- package/cmd/ttsc/helpers_test.go +39 -28
- package/cmd/ttsc/main.go +111 -104
- package/cmd/ttsc/main_alias_branches_test.go +21 -23
- package/cmd/ttscserver/main.go +96 -90
- package/cmd/ttscserver/run_lsp_prefers_tsgo_flag_test.go +22 -22
- package/cmd/ttscserver/run_lsp_reports_server_error_test.go +22 -22
- package/cmd/ttscserver/run_lsp_uses_tsgo_environment_test.go +23 -23
- package/cmd/utility-host/main.go +30 -23
- package/driver/lsp.go +53 -7
- package/driver/plugins.go +88 -72
- package/driver/program.go +464 -340
- package/driver/rewrite.go +477 -380
- package/internal/lspserver/lsp_envelope.go +64 -64
- package/internal/lspserver/lsp_frame.go +60 -60
- package/internal/lspserver/lsp_plugin.go +57 -57
- package/internal/lspserver/lsp_proxy.go +265 -251
- package/internal/lspserver/lsp_server.go +155 -144
- package/lib/TtscCompiler.d.ts +7 -0
- package/lib/TtscCompiler.js +7 -0
- package/lib/TtscCompiler.js.map +1 -1
- package/lib/compiler/internal/buildNativeCompiler.d.ts +11 -1
- package/lib/compiler/internal/buildNativeCompiler.js +23 -3
- package/lib/compiler/internal/buildNativeCompiler.js.map +1 -1
- package/lib/compiler/internal/compileProjectInMemory.d.ts +9 -0
- package/lib/compiler/internal/compileProjectInMemory.js +36 -21
- package/lib/compiler/internal/compileProjectInMemory.js.map +1 -1
- package/lib/compiler/internal/paths.d.ts +27 -0
- package/lib/compiler/internal/paths.js +70 -0
- package/lib/compiler/internal/paths.js.map +1 -0
- package/lib/compiler/internal/project/readProjectConfig.d.ts +9 -1
- package/lib/compiler/internal/project/readProjectConfig.js +80 -1
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/project/resolveProjectConfig.d.ts +13 -1
- package/lib/compiler/internal/project/resolveProjectConfig.js +42 -1
- package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
- package/lib/compiler/internal/resolveBinary.d.ts +9 -1
- package/lib/compiler/internal/resolveBinary.js +17 -1
- package/lib/compiler/internal/resolveBinary.js.map +1 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +16 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.js +42 -10
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/resolveTsgo.d.ts +21 -1
- package/lib/compiler/internal/resolveTsgo.js +23 -1
- package/lib/compiler/internal/resolveTsgo.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +24 -4
- package/lib/compiler/internal/runBuild.js +355 -93
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.d.ts +10 -1
- package/lib/compiler/internal/runSingleFileEmit.js +14 -1
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/compiler/internal/sharedHostHelpers.d.ts +5 -0
- package/lib/compiler/internal/sharedHostHelpers.js +6 -2
- package/lib/compiler/internal/sharedHostHelpers.js.map +1 -1
- package/lib/compiler/internal/spawnNative.d.ts +21 -0
- package/lib/compiler/internal/spawnNative.js +59 -0
- package/lib/compiler/internal/spawnNative.js.map +1 -0
- package/lib/compiler/internal/transformProjectInMemory.d.ts +15 -1
- package/lib/compiler/internal/transformProjectInMemory.js +57 -61
- package/lib/compiler/internal/transformProjectInMemory.js.map +1 -1
- package/lib/flags/parser.d.ts +63 -0
- package/lib/flags/parser.js +254 -0
- package/lib/flags/parser.js.map +1 -0
- package/lib/flags/schema.d.ts +147 -0
- package/lib/flags/schema.js +405 -0
- package/lib/flags/schema.js.map +1 -0
- package/lib/launcher/internal/getCompilerVersionText.js +7 -0
- package/lib/launcher/internal/getCompilerVersionText.js.map +1 -1
- package/lib/launcher/internal/prepareExecution.js +35 -0
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/resolveCacheDir.d.ts +9 -0
- package/lib/launcher/internal/resolveCacheDir.js +22 -0
- package/lib/launcher/internal/resolveCacheDir.js.map +1 -0
- package/lib/launcher/internal/resolveTtscserverBinary.js +2 -0
- package/lib/launcher/internal/resolveTtscserverBinary.js.map +1 -1
- package/lib/launcher/internal/runTtsc.d.ts +10 -0
- package/lib/launcher/internal/runTtsc.js +90 -153
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +10 -35
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.d.ts +8 -0
- package/lib/launcher/internal/runTtsx.js +137 -79
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/plugin/internal/buildSourcePlugin.d.ts +38 -0
- package/lib/plugin/internal/buildSourcePlugin.js +40 -17
- package/lib/plugin/internal/buildSourcePlugin.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +26 -0
- package/lib/plugin/internal/loadProjectPlugins.js +90 -41
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscCompilerTransformation.d.ts +15 -2
- package/lib/structures/ITtscPlugin.d.ts +24 -0
- package/lib/structures/ITtscPluginCapabilities.d.ts +29 -0
- package/lib/structures/ITtscPluginCapabilities.js +3 -0
- package/lib/structures/ITtscPluginCapabilities.js.map +1 -0
- package/lib/structures/index.d.ts +1 -0
- package/lib/structures/index.js +1 -0
- package/lib/structures/index.js.map +1 -1
- package/lib/structures/internal/ITtscLoadedNativePlugin.d.ts +5 -0
- package/lib/structures/internal/TtscCommonOptions.d.ts +19 -0
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +0 -2
- package/package.json +9 -9
- package/shim/ast/shim.go +24 -0
- package/shim/checker/shim.go +35 -0
- package/shim/core/shim.go +17 -0
- package/shim/diagnosticwriter/shim.go +4 -0
- package/shim/lsp/shim.go +3 -3
- package/shim/printer/shim.go +20 -0
- package/src/TtscCompiler.ts +7 -0
- package/src/compiler/internal/buildNativeCompiler.ts +23 -3
- package/src/compiler/internal/compileProjectInMemory.ts +34 -25
- package/src/compiler/internal/paths.ts +68 -0
- package/src/compiler/internal/project/readProjectConfig.ts +86 -1
- package/src/compiler/internal/project/resolveProjectConfig.ts +44 -1
- package/src/compiler/internal/resolveBinary.ts +17 -1
- package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -12
- package/src/compiler/internal/resolveTsgo.ts +29 -1
- package/src/compiler/internal/runBuild.ts +398 -112
- package/src/compiler/internal/runSingleFileEmit.ts +14 -1
- package/src/compiler/internal/sharedHostHelpers.ts +7 -7
- package/src/compiler/internal/spawnNative.ts +64 -0
- package/src/compiler/internal/transformProjectInMemory.ts +47 -67
- package/src/flags/parser.ts +367 -0
- package/src/flags/schema.ts +570 -0
- package/src/launcher/internal/getCompilerVersionText.ts +7 -0
- package/src/launcher/internal/prepareExecution.ts +35 -0
- package/src/launcher/internal/resolveCacheDir.ts +19 -0
- package/src/launcher/internal/resolveTtscserverBinary.ts +2 -0
- package/src/launcher/internal/runTtsc.ts +97 -143
- package/src/launcher/internal/runTtscserver.ts +9 -1
- package/src/launcher/internal/runTtsx.ts +149 -82
- package/src/plugin/internal/buildSourcePlugin.ts +40 -14
- package/src/plugin/internal/loadProjectPlugins.ts +116 -45
- package/src/structures/ITtscCompilerTransformation.ts +15 -2
- package/src/structures/ITtscPlugin.ts +26 -1
- package/src/structures/ITtscPluginCapabilities.ts +29 -0
- package/src/structures/index.ts +1 -0
- package/src/structures/internal/ITtscLoadedNativePlugin.ts +5 -0
- package/src/structures/internal/TtscCommonOptions.ts +19 -0
- package/src/structures/internal/TtscSingleFileEmitOptions.ts +0 -2
- package/tools/gen_shims/main.go +43 -8
- package/utility/flags_gen.go +30 -0
- package/utility/host.go +304 -245
|
@@ -7,7 +7,16 @@ import { resolveProjectConfig } from "./project/resolveProjectConfig";
|
|
|
7
7
|
import { resolveEmittedJavaScript } from "./resolveEmittedJavaScript";
|
|
8
8
|
import { runBuild } from "./runBuild";
|
|
9
9
|
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* Emit one source file by building its project into a temporary directory.
|
|
12
|
+
*
|
|
13
|
+
* The full project is compiled with `forceListEmittedFiles` so that the emitted
|
|
14
|
+
* file list is available for `resolveEmittedJavaScript`. The temp directory is
|
|
15
|
+
* always cleaned up in the `finally` block, even on error.
|
|
16
|
+
*
|
|
17
|
+
* @returns The transformed JavaScript source text.
|
|
18
|
+
* @throws When the build exits non-zero or no output is produced for the file.
|
|
19
|
+
*/
|
|
11
20
|
export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
|
|
12
21
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
13
22
|
const sourceFile = realpathIfExists(
|
|
@@ -64,6 +73,10 @@ export function runSingleFileEmit(options: TtscSingleFileEmitOptions): string {
|
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Resolve symlinks on `file` when it exists; return the original path when the
|
|
78
|
+
* file is not yet on disk (e.g. a synthetic path used in tests).
|
|
79
|
+
*/
|
|
67
80
|
function realpathIfExists(file: string): string {
|
|
68
81
|
try {
|
|
69
82
|
return fs.realpathSync(file);
|
|
@@ -4,9 +4,7 @@ import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoa
|
|
|
4
4
|
* Reports whether the given transform source is linked into another compiler
|
|
5
5
|
* host instead of owning the process itself.
|
|
6
6
|
*/
|
|
7
|
-
export function isLinkedTransform(
|
|
8
|
-
plugin: ITtscLoadedNativePlugin,
|
|
9
|
-
): boolean {
|
|
7
|
+
export function isLinkedTransform(plugin: ITtscLoadedNativePlugin): boolean {
|
|
10
8
|
return plugin.stage === "transform" && plugin.kind === "linked";
|
|
11
9
|
}
|
|
12
10
|
|
|
@@ -59,12 +57,14 @@ export function assertSharedHostCompatibility(
|
|
|
59
57
|
export function selectSharedHostPlugin(
|
|
60
58
|
plugins: readonly ITtscLoadedNativePlugin[],
|
|
61
59
|
): ITtscLoadedNativePlugin {
|
|
62
|
-
return (
|
|
63
|
-
plugins.find((plugin) => !isLinkedTransform(plugin)) ??
|
|
64
|
-
plugins[0]!
|
|
65
|
-
);
|
|
60
|
+
return plugins.find((plugin) => !isLinkedTransform(plugin)) ?? plugins[0]!;
|
|
66
61
|
}
|
|
67
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Return every plugin whose transform source is linked into another host binary
|
|
65
|
+
* rather than owning the process. The host binary passes these via
|
|
66
|
+
* `TTSC_LINKED_PLUGINS_JSON` so their Go code runs inside the same process.
|
|
67
|
+
*/
|
|
68
68
|
export function linkedTransformPlugins(
|
|
69
69
|
plugins: readonly ITtscLoadedNativePlugin[],
|
|
70
70
|
): ITtscLoadedNativePlugin[] {
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Spawn a native binary (or a Node.js script when the path has a JS/TS
|
|
6
|
+
* extension) with the given args and return the `spawnSync` result. Sets a
|
|
7
|
+
* generous 256 MiB output buffer so large projects do not truncate.
|
|
8
|
+
*
|
|
9
|
+
* When `options.encoding` is omitted it defaults to `"utf8"`. Pass an explicit
|
|
10
|
+
* value when the caller needs a different encoding or raw `Buffer` output.
|
|
11
|
+
*/
|
|
12
|
+
export function spawnNative(
|
|
13
|
+
binary: string,
|
|
14
|
+
args: readonly string[],
|
|
15
|
+
options: {
|
|
16
|
+
cwd?: string;
|
|
17
|
+
env?: NodeJS.ProcessEnv;
|
|
18
|
+
encoding?: BufferEncoding;
|
|
19
|
+
},
|
|
20
|
+
) {
|
|
21
|
+
const viaNode = /\.(?:[cm]?js|ts)$/i.test(binary);
|
|
22
|
+
if (!viaNode) {
|
|
23
|
+
ensureExecutable(binary);
|
|
24
|
+
}
|
|
25
|
+
return spawnSync(
|
|
26
|
+
viaNode ? process.execPath : binary,
|
|
27
|
+
viaNode ? [binary, ...args] : [...args],
|
|
28
|
+
{
|
|
29
|
+
cwd: options.cwd,
|
|
30
|
+
encoding: options.encoding ?? "utf8",
|
|
31
|
+
env: options.env,
|
|
32
|
+
maxBuffer: 1024 * 1024 * 256,
|
|
33
|
+
windowsHide: true,
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ensure the binary has the executable bit set on POSIX systems. Silently skips
|
|
40
|
+
* on Windows and swallows `chmod` errors to let the original spawn error
|
|
41
|
+
* surface instead of masking it with a permission error.
|
|
42
|
+
*/
|
|
43
|
+
export function ensureExecutable(binary: string): void {
|
|
44
|
+
if (process.platform === "win32") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const mode = fs.statSync(binary).mode & 0o777;
|
|
49
|
+
if ((mode & 0o111) !== 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
fs.chmodSync(binary, mode | 0o755);
|
|
53
|
+
} catch {
|
|
54
|
+
/* keep the original spawn error path */
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Coerce a `spawnSync` output value to a plain string, defaulting to `""`. */
|
|
59
|
+
export function outputText(value: string | Buffer | null | undefined): string {
|
|
60
|
+
if (value == null) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
return typeof value === "string" ? value : value.toString("utf8");
|
|
64
|
+
}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import fs from "node:fs";
|
|
3
1
|
import path from "node:path";
|
|
4
2
|
|
|
5
3
|
import {
|
|
@@ -12,6 +10,7 @@ import type { ITtscLoadedNativePlugin } from "../../structures/internal/ITtscLoa
|
|
|
12
10
|
import type { ITtscParsedProjectConfig } from "../../structures/internal/ITtscParsedProjectConfig";
|
|
13
11
|
import type { TtscBuildResult } from "../../structures/internal/TtscBuildResult";
|
|
14
12
|
import { buildNativeCompiler } from "./buildNativeCompiler";
|
|
13
|
+
import { packageRootDir } from "./paths";
|
|
15
14
|
import { readProjectConfig } from "./project/readProjectConfig";
|
|
16
15
|
import { resolveBinary } from "./resolveBinary";
|
|
17
16
|
import { resolveTsgo } from "./resolveTsgo";
|
|
@@ -21,8 +20,23 @@ import {
|
|
|
21
20
|
linkedTransformPlugins,
|
|
22
21
|
selectSharedHostPlugin,
|
|
23
22
|
} from "./sharedHostHelpers";
|
|
23
|
+
import { outputText, spawnNative } from "./spawnNative";
|
|
24
24
|
|
|
25
|
-
/**
|
|
25
|
+
/**
|
|
26
|
+
* Transform a project and capture TypeScript source output in memory.
|
|
27
|
+
*
|
|
28
|
+
* When no plugins are configured the fast path spawns the native ttsc compiler
|
|
29
|
+
* host (`cmd/ttsc api-transform`) which returns a JSON map of transformed
|
|
30
|
+
* TypeScript sources. When plugins are present:
|
|
31
|
+
*
|
|
32
|
+
* 1. Check-stage plugins run first and abort on failure.
|
|
33
|
+
* 2. If there are no transform-stage plugins the host is used as the transformer.
|
|
34
|
+
* 3. If transform plugins exist they are dispatched through the shared-host binary
|
|
35
|
+
* with linked plugins passed via `TTSC_LINKED_PLUGINS_JSON`.
|
|
36
|
+
*
|
|
37
|
+
* @returns A `{ result, typescript }` pair where `typescript` maps output paths
|
|
38
|
+
* to their transformed TypeScript source text.
|
|
39
|
+
*/
|
|
26
40
|
export function transformProjectInMemory(options: ITtscCompilerContext): {
|
|
27
41
|
result: TtscBuildResult;
|
|
28
42
|
typescript: Record<string, string>;
|
|
@@ -39,6 +53,7 @@ export function transformProjectInMemory(options: ITtscCompilerContext): {
|
|
|
39
53
|
return transformProjectWithNativeHost(options, project);
|
|
40
54
|
}
|
|
41
55
|
|
|
56
|
+
/** Return true when the project or the call-level options declare any plugins. */
|
|
42
57
|
function hasConfiguredPlugins(
|
|
43
58
|
options: ITtscCompilerContext,
|
|
44
59
|
project: ITtscParsedProjectConfig,
|
|
@@ -46,6 +61,11 @@ function hasConfiguredPlugins(
|
|
|
46
61
|
return hasProjectPluginEntries(project, options.plugins);
|
|
47
62
|
}
|
|
48
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Transform via the built-in native compiler host (`cmd/ttsc api-transform`).
|
|
66
|
+
* Used when no user plugins are configured, or as the fallback transformer when
|
|
67
|
+
* check-stage plugins pass and no transform-stage plugins are declared.
|
|
68
|
+
*/
|
|
49
69
|
function transformProjectWithNativeHost(
|
|
50
70
|
options: ITtscCompilerContext,
|
|
51
71
|
project: ITtscParsedProjectConfig,
|
|
@@ -160,6 +180,10 @@ function transformProjectWithPlugins(
|
|
|
160
180
|
};
|
|
161
181
|
}
|
|
162
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Run every check-stage plugin in sequence, short-circuiting on the first
|
|
185
|
+
* failure. Returns the aggregated `TtscBuildResult` (status 0 when all pass).
|
|
186
|
+
*/
|
|
163
187
|
function runNativeChecks(
|
|
164
188
|
options: ITtscCompilerContext,
|
|
165
189
|
project: ITtscParsedProjectConfig,
|
|
@@ -204,6 +228,7 @@ function runNativeChecks(
|
|
|
204
228
|
return result;
|
|
205
229
|
}
|
|
206
230
|
|
|
231
|
+
/** Build the CLI argument list for the `transform` subcommand. */
|
|
207
232
|
function createNativeTransformArgs(
|
|
208
233
|
project: ITtscParsedProjectConfig,
|
|
209
234
|
plugins: readonly ITtscLoadedNativePlugin[],
|
|
@@ -216,6 +241,7 @@ function createNativeTransformArgs(
|
|
|
216
241
|
];
|
|
217
242
|
}
|
|
218
243
|
|
|
244
|
+
/** Build the CLI argument list for the `check` subcommand. */
|
|
219
245
|
function createNativeCheckArgs(
|
|
220
246
|
project: ITtscParsedProjectConfig,
|
|
221
247
|
plugins: readonly ITtscLoadedNativePlugin[],
|
|
@@ -228,6 +254,10 @@ function createNativeCheckArgs(
|
|
|
228
254
|
];
|
|
229
255
|
}
|
|
230
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Serialize the plugin list to a JSON string for `--plugins-json=`. Only the
|
|
259
|
+
* fields the native binary needs are included to keep the arg short.
|
|
260
|
+
*/
|
|
231
261
|
function serializeNativePlugins(
|
|
232
262
|
plugins: readonly ITtscLoadedNativePlugin[],
|
|
233
263
|
): string {
|
|
@@ -240,6 +270,12 @@ function serializeNativePlugins(
|
|
|
240
270
|
);
|
|
241
271
|
}
|
|
242
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Build the environment for a native plugin spawn. Injects `TTSC_NODE_BINARY`,
|
|
275
|
+
* `TTSC_TSGO_BINARY`, and `TTSC_TTSX_BINARY` so the sidecar can re-invoke
|
|
276
|
+
* Node.js or tsgo without searching PATH. For transform plugins, also passes
|
|
277
|
+
* `TTSC_LINKED_PLUGINS_JSON` when linked sources are present.
|
|
278
|
+
*/
|
|
243
279
|
function nativePluginEnv(
|
|
244
280
|
options: ITtscCompilerContext,
|
|
245
281
|
projectRoot: string,
|
|
@@ -265,53 +301,13 @@ function nativePluginEnv(
|
|
|
265
301
|
return env;
|
|
266
302
|
}
|
|
267
303
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
) {
|
|
276
|
-
const viaNode = /\.(?:[cm]?js|ts)$/i.test(binary);
|
|
277
|
-
if (!viaNode) {
|
|
278
|
-
ensureExecutable(binary);
|
|
279
|
-
}
|
|
280
|
-
return spawnSync(
|
|
281
|
-
viaNode ? process.execPath : binary,
|
|
282
|
-
viaNode ? [binary, ...args] : [...args],
|
|
283
|
-
{
|
|
284
|
-
cwd: options.cwd,
|
|
285
|
-
encoding: "utf8",
|
|
286
|
-
env: options.env,
|
|
287
|
-
maxBuffer: 1024 * 1024 * 256,
|
|
288
|
-
windowsHide: true,
|
|
289
|
-
},
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function ensureExecutable(binary: string): void {
|
|
294
|
-
if (process.platform === "win32") {
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
try {
|
|
298
|
-
const mode = fs.statSync(binary).mode & 0o777;
|
|
299
|
-
if ((mode & 0o111) !== 0) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
fs.chmodSync(binary, mode | 0o755);
|
|
303
|
-
} catch {
|
|
304
|
-
/* keep the original spawn error path */
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
function outputText(value: string | Buffer | null | undefined): string {
|
|
309
|
-
if (value == null) {
|
|
310
|
-
return "";
|
|
311
|
-
}
|
|
312
|
-
return typeof value === "string" ? value : value.toString("utf8");
|
|
313
|
-
}
|
|
314
|
-
|
|
304
|
+
/**
|
|
305
|
+
* Parse the JSON envelope written by the native transform host to stdout.
|
|
306
|
+
*
|
|
307
|
+
* The `typescript` field must be a `Record<string, string>`. Any other shape is
|
|
308
|
+
* treated as a protocol error and throws with the stderr/stdout context. JSON
|
|
309
|
+
* parse errors are also wrapped with the same context message.
|
|
310
|
+
*/
|
|
315
311
|
function parseNativeTransformOutput(
|
|
316
312
|
stdout: string,
|
|
317
313
|
stderr: string,
|
|
@@ -344,6 +340,7 @@ function parseNativeTransformOutput(
|
|
|
344
340
|
}
|
|
345
341
|
}
|
|
346
342
|
|
|
343
|
+
/** Type guard: true when `value` is a non-null, non-array object of strings. */
|
|
347
344
|
function isTextRecord(value: unknown): value is Record<string, string> {
|
|
348
345
|
return (
|
|
349
346
|
typeof value === "object" &&
|
|
@@ -352,20 +349,3 @@ function isTextRecord(value: unknown): value is Record<string, string> {
|
|
|
352
349
|
Object.values(value).every((entry) => typeof entry === "string")
|
|
353
350
|
);
|
|
354
351
|
}
|
|
355
|
-
|
|
356
|
-
function packageRootDir(): string {
|
|
357
|
-
let current = path.resolve(__dirname);
|
|
358
|
-
while (true) {
|
|
359
|
-
if (
|
|
360
|
-
fs.existsSync(path.join(current, "package.json")) &&
|
|
361
|
-
fs.existsSync(path.join(current, "go.mod"))
|
|
362
|
-
) {
|
|
363
|
-
return fs.realpathSync.native?.(current) ?? fs.realpathSync(current);
|
|
364
|
-
}
|
|
365
|
-
const parent = path.dirname(current);
|
|
366
|
-
if (parent === current) {
|
|
367
|
-
throw new Error("ttsc: package root not found for native compiler build");
|
|
368
|
-
}
|
|
369
|
-
current = parent;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
// Schema-driven CLI parser used by both `runTtsc.ts` and `runTtsx.ts`.
|
|
2
|
+
//
|
|
3
|
+
// One engine, one schema. Each subcommand picks the subset of FLAG_SCHEMA it
|
|
4
|
+
// accepts and feeds it through `parseFlags`. The engine returns:
|
|
5
|
+
//
|
|
6
|
+
// * `values` — typed values for every consumed flag.
|
|
7
|
+
// * `passthrough` — flags ttsc did not consume but must forward verbatim
|
|
8
|
+
// to tsgo (or to native sidecars via `--tsgo-args`).
|
|
9
|
+
// * `positional` — bare non-flag tokens (file paths, entry files, …).
|
|
10
|
+
//
|
|
11
|
+
// The schema is the spec; behaviour at every layer boundary is determined by
|
|
12
|
+
// the FlagSpec attributes, not by ad-hoc `if (arg === "--foo")` branches.
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
AnySubcommand,
|
|
16
|
+
FlagSpec,
|
|
17
|
+
ValueValidator,
|
|
18
|
+
} from "./schema";
|
|
19
|
+
import { FLAG_BY_NAME, flagsForSubcommand } from "./schema";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Per-subcommand parse result. `values` is keyed by the canonical flag
|
|
23
|
+
* name (e.g. `"--singleThreaded"`); boolean flags resolve to `true`/`false`,
|
|
24
|
+
* value flags to the parsed value type. Callers narrow with the helpers
|
|
25
|
+
* below (`getBoolean`, `getString`, `getNumber`).
|
|
26
|
+
*/
|
|
27
|
+
export interface ParseResult {
|
|
28
|
+
/** Canonical flag name → resolved value. */
|
|
29
|
+
readonly values: ReadonlyMap<string, string | boolean | number>;
|
|
30
|
+
/** Flags the engine did not consume — forwarded to tsgo. */
|
|
31
|
+
readonly passthrough: readonly string[];
|
|
32
|
+
/** Bare non-flag positional arguments, in original order. */
|
|
33
|
+
readonly positional: readonly string[];
|
|
34
|
+
/**
|
|
35
|
+
* Tokens that arrived after the `forwardAfterFirstPositional` sentinel.
|
|
36
|
+
* These are intended for the user's program (e.g. ttsx's entry-file argv);
|
|
37
|
+
* they are NOT forwarded to tsgo. Always empty when
|
|
38
|
+
* `forwardAfterFirstPositional` is false.
|
|
39
|
+
*/
|
|
40
|
+
readonly tail: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Options controlling a single `parseFlags` invocation.
|
|
45
|
+
*/
|
|
46
|
+
export interface ParseOptions {
|
|
47
|
+
/** Which subcommand's flag subset to accept. */
|
|
48
|
+
readonly subcommand: AnySubcommand;
|
|
49
|
+
/** Argv tail (the launcher has already split off the subcommand). */
|
|
50
|
+
readonly argv: readonly string[];
|
|
51
|
+
/**
|
|
52
|
+
* Error prefix used when the parser throws (`"ttsc:"` or `"ttsx:"`). The
|
|
53
|
+
* engine itself is product-neutral; the caller controls the brand.
|
|
54
|
+
*/
|
|
55
|
+
readonly errorPrefix: string;
|
|
56
|
+
/**
|
|
57
|
+
* `true` to treat the FIRST positional token as a sentinel that switches
|
|
58
|
+
* the engine to "forward everything after" mode (ttsx's entry-file
|
|
59
|
+
* behaviour: tokens after the entry are runtime argv, not tsgo flags).
|
|
60
|
+
* The sentinel itself is still recorded as a positional argument.
|
|
61
|
+
*/
|
|
62
|
+
readonly forwardAfterFirstPositional?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* Optional `"--"` separator handling: when present in argv, every token
|
|
65
|
+
* after `--` is appended to `passthrough` as-is (ttsx already does this).
|
|
66
|
+
*/
|
|
67
|
+
readonly honorDoubleDashSeparator?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse `argv` according to FLAG_SCHEMA filtered by `subcommand`. Returns a
|
|
72
|
+
* `ParseResult`. Throws `Error` (with the configured prefix) on invalid
|
|
73
|
+
* input — unknown subcommand-only flag, missing required value, value that
|
|
74
|
+
* fails its validator.
|
|
75
|
+
*/
|
|
76
|
+
export function parseFlags(opts: ParseOptions): ParseResult {
|
|
77
|
+
const accepted = new Map<string, FlagSpec>();
|
|
78
|
+
for (const flag of flagsForSubcommand(opts.subcommand)) {
|
|
79
|
+
// A flag enters the launcher's `accepted` set only when its
|
|
80
|
+
// `consumedBy` includes `"launcher"`. Flags consumed solely by tsgo
|
|
81
|
+
// or by native sidecars (e.g. `--showConfig`, `--listFilesOnly`)
|
|
82
|
+
// must fall through to `forwardKnownButUnaccepted` so the launcher
|
|
83
|
+
// forwards them verbatim instead of storing them in `values` where
|
|
84
|
+
// no consumer reads them back out. The previous shape — filter on
|
|
85
|
+
// `subcommands` only — silently dropped every tsgo-only terminal
|
|
86
|
+
// flag at the launcher boundary (the RC-1 / RC-2 class the schema
|
|
87
|
+
// is meant to make impossible).
|
|
88
|
+
if (!flag.consumedBy.includes("launcher")) continue;
|
|
89
|
+
accepted.set(flag.name, flag);
|
|
90
|
+
for (const alias of flag.aliases ?? []) accepted.set(alias, flag);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const values = new Map<string, string | boolean | number>();
|
|
94
|
+
const passthrough: string[] = [];
|
|
95
|
+
const positional: string[] = [];
|
|
96
|
+
const tail: string[] = [];
|
|
97
|
+
|
|
98
|
+
let remainder: string[] | null = null;
|
|
99
|
+
if (opts.honorDoubleDashSeparator === true) {
|
|
100
|
+
const separator = opts.argv.indexOf("--");
|
|
101
|
+
if (separator !== -1) {
|
|
102
|
+
remainder = [...opts.argv.slice(separator + 1)];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const head: string[] = [
|
|
106
|
+
...(remainder === null ? opts.argv : opts.argv.slice(0, indexOfSeparator(opts.argv))),
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
let forwardingTail = false;
|
|
110
|
+
while (head.length !== 0) {
|
|
111
|
+
const current = head.shift()!;
|
|
112
|
+
if (forwardingTail) {
|
|
113
|
+
// Post-sentinel tokens belong to the user's program (e.g. the typia.ts
|
|
114
|
+
// entry's own argv: `ttsx typia.ts generate --input X`). They MUST NOT
|
|
115
|
+
// be forwarded to tsgo — the caller distinguishes `tail` from
|
|
116
|
+
// `passthrough` so script args never reach tsgo's option parser.
|
|
117
|
+
tail.push(current);
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// `--foo=value` / `-p=value` form: split before resolving against the
|
|
122
|
+
// schema. Both long (`--foo`) and short (`-p`) aliases support the
|
|
123
|
+
// inline-value shape — without splitting the short form, `-p=value`
|
|
124
|
+
// would fall through as an unknown token and be forwarded to tsgo,
|
|
125
|
+
// bypassing the launcher's own consumer (e.g. plugin discovery against
|
|
126
|
+
// the wrong project root).
|
|
127
|
+
const equalsIndex = current.startsWith("-") ? current.indexOf("=") : -1;
|
|
128
|
+
const token = equalsIndex === -1 ? current : current.slice(0, equalsIndex);
|
|
129
|
+
const inlineValue =
|
|
130
|
+
equalsIndex === -1 ? undefined : current.slice(equalsIndex + 1);
|
|
131
|
+
|
|
132
|
+
const flag = accepted.get(token);
|
|
133
|
+
if (flag !== undefined) {
|
|
134
|
+
consumeFlag(values, flag, token, inlineValue, head, opts.errorPrefix);
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Token IS a known flag but is not accepted by THIS subcommand. The
|
|
139
|
+
// engine forwards it to tsgo just like an unknown flag — the same
|
|
140
|
+
// policy the bare-lane parser applied (RC-1 prevention).
|
|
141
|
+
const globalFlag = FLAG_BY_NAME.get(token);
|
|
142
|
+
if (globalFlag !== undefined) {
|
|
143
|
+
forwardKnownButUnaccepted(
|
|
144
|
+
passthrough,
|
|
145
|
+
globalFlag,
|
|
146
|
+
current,
|
|
147
|
+
inlineValue,
|
|
148
|
+
head,
|
|
149
|
+
);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Truly unknown `-`-prefixed token: forward to tsgo verbatim. This
|
|
154
|
+
// is what makes `ttsc --strict file.ts` work — ttsc does not need to
|
|
155
|
+
// re-implement every tsgo flag.
|
|
156
|
+
if (current.startsWith("-")) {
|
|
157
|
+
passthrough.push(current);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Bare token: positional argument (file / entry / project path).
|
|
162
|
+
positional.push(current);
|
|
163
|
+
if (
|
|
164
|
+
opts.forwardAfterFirstPositional === true &&
|
|
165
|
+
positional.length === 1
|
|
166
|
+
) {
|
|
167
|
+
forwardingTail = true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (remainder !== null) {
|
|
172
|
+
// `--` separator: everything after goes to the user program when we are
|
|
173
|
+
// in tail mode (ttsx after the entry), otherwise to tsgo as passthrough.
|
|
174
|
+
const sink = forwardingTail ? tail : passthrough;
|
|
175
|
+
for (const token of remainder) sink.push(token);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return { values, passthrough, positional, tail };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Pull the value for `flag` from either `inlineValue` (`--flag=value`) or
|
|
183
|
+
* the next argv token (`--flag value`), validate it per the schema, and
|
|
184
|
+
* write it to the result map.
|
|
185
|
+
*/
|
|
186
|
+
function consumeFlag(
|
|
187
|
+
values: Map<string, string | boolean | number>,
|
|
188
|
+
flag: FlagSpec,
|
|
189
|
+
token: string,
|
|
190
|
+
inlineValue: string | undefined,
|
|
191
|
+
rest: string[],
|
|
192
|
+
errorPrefix: string,
|
|
193
|
+
): void {
|
|
194
|
+
if (flag.kind === "boolean") {
|
|
195
|
+
if (inlineValue !== undefined) {
|
|
196
|
+
// `--flag=false` / `--flag=true` inline form. Anything other than
|
|
197
|
+
// a recognised literal stays loud: `--singleThreaded=yes` silently
|
|
198
|
+
// becoming `true` is the kind of footgun the RCA's RC-4 class
|
|
199
|
+
// covers. Mirrors `validatePositiveInt`'s style.
|
|
200
|
+
const literal = parseBooleanLiteral(inlineValue);
|
|
201
|
+
if (literal === undefined) {
|
|
202
|
+
throw new Error(
|
|
203
|
+
`${errorPrefix} ${token} expects \`true\` or \`false\`, got ${JSON.stringify(
|
|
204
|
+
inlineValue,
|
|
205
|
+
)}`,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
values.set(flag.name, literal);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
// Space form `--flag true` / `--flag false`: peek the next token and
|
|
212
|
+
// consume it only when it parses as a boolean literal. tsgo accepts
|
|
213
|
+
// this shape natively; the launcher must mirror it so `ttsc --noEmit
|
|
214
|
+
// false` does not corrupt argv (positional sink getting `false`,
|
|
215
|
+
// tsgo seeing it as a stray input file).
|
|
216
|
+
if (rest.length > 0) {
|
|
217
|
+
const peek = parseBooleanLiteral(rest[0]!);
|
|
218
|
+
if (peek !== undefined) {
|
|
219
|
+
rest.shift();
|
|
220
|
+
values.set(flag.name, peek);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
values.set(flag.name, true);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const raw =
|
|
229
|
+
inlineValue !== undefined ? inlineValue : takeValueToken(token, rest, errorPrefix);
|
|
230
|
+
if (flag.validator === "positiveInt") {
|
|
231
|
+
values.set(flag.name, validatePositiveInt(token, raw, errorPrefix));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
values.set(flag.name, raw);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Read the value token that follows `flag`. Throws if argv ends here OR if
|
|
239
|
+
* the next token looks like another flag (`-` prefix). Without the
|
|
240
|
+
* "looks like a flag" guard `ttsc --cwd --strict src/main.ts` would silently
|
|
241
|
+
* consume `--strict` as the value of `--cwd`, leaving `--strict` lost and
|
|
242
|
+
* `cwd` set to a junk path. Mirrors the symmetric guard already in
|
|
243
|
+
* `forwardKnownButUnaccepted` (RC-1 fairness — the two value-resolution
|
|
244
|
+
* paths must agree on what counts as "a missing value").
|
|
245
|
+
*/
|
|
246
|
+
function takeValueToken(
|
|
247
|
+
flag: string,
|
|
248
|
+
rest: string[],
|
|
249
|
+
errorPrefix: string,
|
|
250
|
+
): string {
|
|
251
|
+
const value = rest.shift();
|
|
252
|
+
if (value === undefined) {
|
|
253
|
+
throw new Error(`${errorPrefix} ${flag} requires a value`);
|
|
254
|
+
}
|
|
255
|
+
if (value.startsWith("-")) {
|
|
256
|
+
// Put it back so the next loop iteration parses it as its own flag.
|
|
257
|
+
rest.unshift(value);
|
|
258
|
+
throw new Error(
|
|
259
|
+
`${errorPrefix} ${flag} requires a value (next token ${JSON.stringify(
|
|
260
|
+
value,
|
|
261
|
+
)} starts with "-")`,
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return value;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Parse a CLI boolean literal — `true`/`false` only (case-sensitive to
|
|
269
|
+
* match tsgo's parser). Returns `undefined` for any other token so the
|
|
270
|
+
* caller knows to throw or treat as a non-value.
|
|
271
|
+
*/
|
|
272
|
+
function parseBooleanLiteral(raw: string): boolean | undefined {
|
|
273
|
+
if (raw === "true") return true;
|
|
274
|
+
if (raw === "false") return false;
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Validate a `positiveInt` value. Mirrors tsgo's `--checkers minValue:1`
|
|
280
|
+
* constraint so a typo fails loudly at the launcher rather than reaching
|
|
281
|
+
* tsgo with an invalid argument.
|
|
282
|
+
*/
|
|
283
|
+
function validatePositiveInt(
|
|
284
|
+
flag: string,
|
|
285
|
+
raw: string,
|
|
286
|
+
errorPrefix: string,
|
|
287
|
+
): number {
|
|
288
|
+
const value = Number(raw);
|
|
289
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
290
|
+
throw new Error(
|
|
291
|
+
`${errorPrefix} ${flag} expects a positive integer, got ${JSON.stringify(raw)}`,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
return value;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Forward a flag the schema knows about but the current subcommand does
|
|
299
|
+
* not accept. The launcher will hand it to tsgo (or to native sidecars
|
|
300
|
+
* via `--tsgo-args`); without this branch the parser would lose the
|
|
301
|
+
* value token of a `--flag value` pair.
|
|
302
|
+
*/
|
|
303
|
+
function forwardKnownButUnaccepted(
|
|
304
|
+
passthrough: string[],
|
|
305
|
+
flag: FlagSpec,
|
|
306
|
+
original: string,
|
|
307
|
+
inlineValue: string | undefined,
|
|
308
|
+
rest: string[],
|
|
309
|
+
): void {
|
|
310
|
+
passthrough.push(original);
|
|
311
|
+
// Boolean flags carry no value. `--foo=value` already encodes the value
|
|
312
|
+
// inline. Otherwise the next argv token is the flag's value: keep it
|
|
313
|
+
// adjacent so tsgo receives the pair intact.
|
|
314
|
+
if (flag.kind === "boolean" || inlineValue !== undefined) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
if (rest.length === 0) return;
|
|
318
|
+
if (rest[0]!.startsWith("-")) return;
|
|
319
|
+
passthrough.push(rest.shift()!);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Return the index of the `--` separator in `argv`, or `argv.length` when
|
|
324
|
+
* absent. The parser slices on this index when honorDoubleDashSeparator.
|
|
325
|
+
*/
|
|
326
|
+
function indexOfSeparator(argv: readonly string[]): number {
|
|
327
|
+
const index = argv.indexOf("--");
|
|
328
|
+
return index === -1 ? argv.length : index;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// -----------------------------------------------------------------------------
|
|
332
|
+
// Typed accessors. The parser stores raw values (string | boolean | number) so
|
|
333
|
+
// the engine stays untyped; callers pick the shape per flag.
|
|
334
|
+
// -----------------------------------------------------------------------------
|
|
335
|
+
|
|
336
|
+
/** Return the boolean value of `flag` or `undefined` if not present. */
|
|
337
|
+
export function getBoolean(
|
|
338
|
+
result: ParseResult,
|
|
339
|
+
flag: string,
|
|
340
|
+
): boolean | undefined {
|
|
341
|
+
const value = result.values.get(flag);
|
|
342
|
+
if (typeof value === "boolean") return value;
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** Return the string value of `flag` or `undefined` if not present. */
|
|
347
|
+
export function getString(
|
|
348
|
+
result: ParseResult,
|
|
349
|
+
flag: string,
|
|
350
|
+
): string | undefined {
|
|
351
|
+
const value = result.values.get(flag);
|
|
352
|
+
if (typeof value === "string") return value;
|
|
353
|
+
return undefined;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/** Return the numeric value of `flag` or `undefined` if not present. */
|
|
357
|
+
export function getNumber(
|
|
358
|
+
result: ParseResult,
|
|
359
|
+
flag: string,
|
|
360
|
+
): number | undefined {
|
|
361
|
+
const value = result.values.get(flag);
|
|
362
|
+
if (typeof value === "number") return value;
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/** Marker type so docs callers can name the validator without an import dance. */
|
|
367
|
+
export type { ValueValidator };
|