ttsc 0.12.4 → 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/ttsc/api_compile.go +15 -2
- package/cmd/ttsc/api_transform.go +15 -2
- package/cmd/ttsc/build.go +34 -4
- 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/driver/program.go +105 -4
- package/driver/rewrite.go +39 -4
- package/lib/compiler/internal/project/resolveProjectConfig.js +18 -0
- package/lib/compiler/internal/project/resolveProjectConfig.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +5 -4
- package/lib/compiler/internal/runBuild.js +243 -47
- package/lib/compiler/internal/runBuild.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/prepareExecution.js +3 -0
- package/lib/launcher/internal/prepareExecution.js.map +1 -1
- package/lib/launcher/internal/runTtsc.js +71 -147
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +104 -72
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.js +58 -23
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- 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/package.json +9 -9
- package/src/compiler/internal/project/resolveProjectConfig.ts +20 -0
- package/src/compiler/internal/runBuild.ts +288 -57
- package/src/flags/parser.ts +367 -0
- package/src/flags/schema.ts +570 -0
- package/src/launcher/internal/prepareExecution.ts +3 -0
- package/src/launcher/internal/runTtsc.ts +78 -136
- package/src/launcher/internal/runTtsx.ts +116 -74
- package/src/plugin/internal/loadProjectPlugins.ts +74 -27
- package/src/structures/ITtscPlugin.ts +26 -0
- 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/utility/flags_gen.go +30 -0
- package/utility/host.go +43 -33
|
@@ -8,6 +8,12 @@ import { resolveProjectConfig } from "../../compiler/internal/project/resolvePro
|
|
|
8
8
|
import { resolveBinary } from "../../compiler/internal/resolveBinary";
|
|
9
9
|
import { runBuild } from "../../compiler/internal/runBuild";
|
|
10
10
|
import { runSingleFileEmit } from "../../compiler/internal/runSingleFileEmit";
|
|
11
|
+
import {
|
|
12
|
+
getBoolean,
|
|
13
|
+
getNumber,
|
|
14
|
+
getString,
|
|
15
|
+
parseFlags,
|
|
16
|
+
} from "../../flags/parser";
|
|
11
17
|
import { defaultPluginCacheCleanTargets } from "../../plugin/internal/buildSourcePlugin";
|
|
12
18
|
import type { TtscBuildOptions } from "../../structures/internal/TtscBuildOptions";
|
|
13
19
|
import { getCompilerVersionText } from "./getCompilerVersionText";
|
|
@@ -282,144 +288,80 @@ function ensureExecutable(binary: string): void {
|
|
|
282
288
|
}
|
|
283
289
|
|
|
284
290
|
function parseProjectArgs(argv: readonly string[]) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
case "--tsconfig":
|
|
301
|
-
case "--project":
|
|
302
|
-
tsconfig = takeValue(current, rest);
|
|
303
|
-
break;
|
|
304
|
-
default:
|
|
305
|
-
if (current.startsWith("--cwd=")) {
|
|
306
|
-
cwd = current.slice("--cwd=".length);
|
|
307
|
-
} else if (current.startsWith("--cache-dir=")) {
|
|
308
|
-
cacheDir = current.slice("--cache-dir=".length);
|
|
309
|
-
} else if (current.startsWith("--tsconfig=")) {
|
|
310
|
-
tsconfig = current.slice("--tsconfig=".length);
|
|
311
|
-
} else if (current.startsWith("--project=")) {
|
|
312
|
-
tsconfig = current.slice("--project=".length);
|
|
313
|
-
} else {
|
|
314
|
-
throw new Error(`ttsc: unknown option ${current}`);
|
|
315
|
-
}
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
return { cacheDir, cwd, tsconfig };
|
|
291
|
+
// `prepare` and `clean` are project-shaped commands. They share the same
|
|
292
|
+
// schema as the build lane; the engine forwards unknown flags as well as
|
|
293
|
+
// build-only flags (e.g. `--strict`) to the launcher's passthrough list
|
|
294
|
+
// so the legacy "unknown option" behaviour is no longer a separate trap
|
|
295
|
+
// (RC-3 + RC-4 prevention; see issue #125 §5 in the RCA).
|
|
296
|
+
const result = parseFlags({
|
|
297
|
+
argv,
|
|
298
|
+
errorPrefix: "ttsc:",
|
|
299
|
+
subcommand: "prepare",
|
|
300
|
+
});
|
|
301
|
+
return {
|
|
302
|
+
cacheDir: getString(result, "--cache-dir"),
|
|
303
|
+
cwd: getString(result, "--cwd"),
|
|
304
|
+
tsconfig: getString(result, "--tsconfig"),
|
|
305
|
+
};
|
|
320
306
|
}
|
|
321
307
|
|
|
322
308
|
function parseBuildArgs(argv: readonly string[], checkOnly: boolean) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
let
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
break;
|
|
352
|
-
case "-w":
|
|
353
|
-
case "--watch":
|
|
354
|
-
watch = true;
|
|
355
|
-
break;
|
|
356
|
-
case "--preserveWatchOutput":
|
|
357
|
-
preserveWatchOutput = true;
|
|
358
|
-
break;
|
|
359
|
-
case "--cwd":
|
|
360
|
-
cwd = takeValue(current, rest);
|
|
361
|
-
break;
|
|
362
|
-
case "--outDir":
|
|
363
|
-
outDir = takeValue(current, rest);
|
|
364
|
-
break;
|
|
365
|
-
case "-p":
|
|
366
|
-
case "--tsconfig":
|
|
367
|
-
case "--project":
|
|
368
|
-
tsconfig = takeValue(current, rest);
|
|
369
|
-
break;
|
|
370
|
-
case "--binary":
|
|
371
|
-
binary = takeValue(current, rest);
|
|
372
|
-
break;
|
|
373
|
-
case "--cache-dir":
|
|
374
|
-
cacheDir = takeValue(current, rest);
|
|
375
|
-
break;
|
|
376
|
-
default:
|
|
377
|
-
if (current.startsWith("--cwd=")) {
|
|
378
|
-
cwd = current.slice("--cwd=".length);
|
|
379
|
-
} else if (current.startsWith("--outDir=")) {
|
|
380
|
-
outDir = current.slice("--outDir=".length);
|
|
381
|
-
} else if (current === "-w") {
|
|
382
|
-
// Unreachable: `-w` is handled by the switch case above. Kept here
|
|
383
|
-
// as a guard in case the switch is refactored in the future.
|
|
384
|
-
watch = true;
|
|
385
|
-
} else if (current.startsWith("--tsconfig=")) {
|
|
386
|
-
tsconfig = current.slice("--tsconfig=".length);
|
|
387
|
-
} else if (current.startsWith("--project=")) {
|
|
388
|
-
tsconfig = current.slice("--project=".length);
|
|
389
|
-
} else if (current.startsWith("--preserveWatchOutput=")) {
|
|
390
|
-
preserveWatchOutput =
|
|
391
|
-
current.slice("--preserveWatchOutput=".length) !== "false";
|
|
392
|
-
} else if (current.startsWith("--binary=")) {
|
|
393
|
-
binary = current.slice("--binary=".length);
|
|
394
|
-
} else if (current.startsWith("--cache-dir=")) {
|
|
395
|
-
cacheDir = current.slice("--cache-dir=".length);
|
|
396
|
-
} else if (current === "--verbose") {
|
|
397
|
-
// Unreachable: `--verbose` is handled by the switch case above.
|
|
398
|
-
quiet = false;
|
|
399
|
-
} else if (current.startsWith("-")) {
|
|
400
|
-
throw new Error(`ttsc: unknown option ${current}`);
|
|
401
|
-
} else {
|
|
402
|
-
files.push(current);
|
|
403
|
-
}
|
|
404
|
-
break;
|
|
405
|
-
}
|
|
406
|
-
}
|
|
309
|
+
const result = parseFlags({
|
|
310
|
+
argv,
|
|
311
|
+
errorPrefix: "ttsc:",
|
|
312
|
+
subcommand: "build",
|
|
313
|
+
});
|
|
314
|
+
// Defaults: pinned by the previous hand-parser. `quiet` defaults true,
|
|
315
|
+
// `--verbose` flips it to false; `emit` defaults `undefined` in build
|
|
316
|
+
// mode (let tsconfig decide) and `false` in check/fix/format mode.
|
|
317
|
+
const verbose = getBoolean(result, "--verbose");
|
|
318
|
+
const quietFlag = getBoolean(result, "--quiet");
|
|
319
|
+
const quiet = verbose === true ? false : (quietFlag ?? true);
|
|
320
|
+
const explicitEmit = getBoolean(result, "--emit");
|
|
321
|
+
const explicitNoEmit = getBoolean(result, "--noEmit");
|
|
322
|
+
let emit: boolean | undefined;
|
|
323
|
+
if (explicitEmit === true) emit = true;
|
|
324
|
+
else if (explicitNoEmit === true) emit = false;
|
|
325
|
+
if (emit === undefined && checkOnly) emit = false;
|
|
326
|
+
|
|
327
|
+
const files = result.positional.filter(looksLikeInputFile);
|
|
328
|
+
// Bare non-file positionals are flag values (e.g. the `es2020` in
|
|
329
|
+
// `--target es2020`). The engine left them in `positional` because the
|
|
330
|
+
// forwarded flag is unknown to the schema; route them back into
|
|
331
|
+
// passthrough so the user's pair reaches tsgo intact.
|
|
332
|
+
const trailingValues = result.positional.filter(
|
|
333
|
+
(token) => !looksLikeInputFile(token),
|
|
334
|
+
);
|
|
335
|
+
const passthrough = [...result.passthrough, ...trailingValues];
|
|
336
|
+
|
|
407
337
|
return {
|
|
408
|
-
binary,
|
|
409
|
-
cacheDir,
|
|
410
|
-
|
|
338
|
+
binary: getString(result, "--binary"),
|
|
339
|
+
cacheDir: getString(result, "--cache-dir"),
|
|
340
|
+
checkers: getNumber(result, "--checkers"),
|
|
341
|
+
cwd: getString(result, "--cwd"),
|
|
411
342
|
emit,
|
|
412
343
|
files,
|
|
413
|
-
fix,
|
|
414
|
-
format,
|
|
415
|
-
outDir,
|
|
416
|
-
|
|
344
|
+
fix: false,
|
|
345
|
+
format: false,
|
|
346
|
+
outDir: getString(result, "--outDir"),
|
|
347
|
+
passthrough,
|
|
348
|
+
preserveWatchOutput: getBoolean(result, "--preserveWatchOutput") === true,
|
|
417
349
|
quiet,
|
|
418
|
-
|
|
419
|
-
|
|
350
|
+
singleThreaded: getBoolean(result, "--singleThreaded") === true,
|
|
351
|
+
tsconfig: getString(result, "--tsconfig"),
|
|
352
|
+
watch: getBoolean(result, "--watch") === true,
|
|
420
353
|
};
|
|
421
354
|
}
|
|
422
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Report whether a bare CLI token is a TypeScript source file ttsc should
|
|
358
|
+
* compile in single-file mode. Anything without a TypeScript source extension
|
|
359
|
+
* is treated as a forwarded flag value rather than an input file.
|
|
360
|
+
*/
|
|
361
|
+
function looksLikeInputFile(token: string): boolean {
|
|
362
|
+
return [".ts", ".tsx", ".mts", ".cts"].some((ext) => token.endsWith(ext));
|
|
363
|
+
}
|
|
364
|
+
|
|
423
365
|
function printHelp(): void {
|
|
424
366
|
process.stdout.write(
|
|
425
367
|
[
|
|
@@ -450,6 +392,11 @@ function printHelp(): void {
|
|
|
450
392
|
" --verbose Print the build summary and emitted files",
|
|
451
393
|
" --binary <path> Use an explicit tsgo binary",
|
|
452
394
|
" --cache-dir <dir> Use this cache root for source-plugin builds",
|
|
395
|
+
" --singleThreaded Run TypeScript-Go single-threaded (one checker)",
|
|
396
|
+
" --checkers <n> Type-checker pool size (default: TypeScript-Go's)",
|
|
397
|
+
"",
|
|
398
|
+
" Any other flag is forwarded to tsgo as-is, so tsgo compiler options",
|
|
399
|
+
" such as --strict or --target work directly (e.g. ttsc --strict file.ts).",
|
|
453
400
|
"",
|
|
454
401
|
"Plugin contract:",
|
|
455
402
|
" ttsc reads compilerOptions.plugins from tsconfig.json.",
|
|
@@ -484,9 +431,12 @@ function runSingleFile(options: ReturnType<typeof parseBuildArgs>): number {
|
|
|
484
431
|
});
|
|
485
432
|
const text = runSingleFileEmit({
|
|
486
433
|
binary: options.binary,
|
|
434
|
+
checkers: options.checkers,
|
|
487
435
|
cwd,
|
|
488
436
|
file,
|
|
489
437
|
out,
|
|
438
|
+
passthrough: options.passthrough,
|
|
439
|
+
singleThreaded: options.singleThreaded,
|
|
490
440
|
tsconfig: options.tsconfig,
|
|
491
441
|
});
|
|
492
442
|
if (!fs.existsSync(out)) {
|
|
@@ -694,14 +644,6 @@ function collectWatchDirectories(root: string): string[] {
|
|
|
694
644
|
return out;
|
|
695
645
|
}
|
|
696
646
|
|
|
697
|
-
function takeValue(flag: string, rest: string[]): string {
|
|
698
|
-
const value = rest.shift();
|
|
699
|
-
if (!value) {
|
|
700
|
-
throw new Error(`ttsc: ${flag} requires a value`);
|
|
701
|
-
}
|
|
702
|
-
return value;
|
|
703
|
-
}
|
|
704
|
-
|
|
705
647
|
function formatError(error: unknown): string {
|
|
706
648
|
if (error instanceof Error) {
|
|
707
649
|
return error.message;
|
|
@@ -2,6 +2,12 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
|
|
5
|
+
import {
|
|
6
|
+
getBoolean,
|
|
7
|
+
getNumber,
|
|
8
|
+
getString,
|
|
9
|
+
parseFlags,
|
|
10
|
+
} from "../../flags/parser";
|
|
5
11
|
import { getCompilerVersionText } from "./getCompilerVersionText";
|
|
6
12
|
import { prepareExecution } from "./prepareExecution";
|
|
7
13
|
import { resolveCacheDir } from "./resolveCacheDir";
|
|
@@ -48,8 +54,20 @@ function run(argv: readonly string[]): number {
|
|
|
48
54
|
const prepared = prepareExecution(entry, {
|
|
49
55
|
binary: parsed.binary,
|
|
50
56
|
cacheDir: resolveCacheDir(cwd, parsed.cacheDir),
|
|
57
|
+
checkers: parsed.checkers,
|
|
51
58
|
cwd,
|
|
59
|
+
passthrough: parsed.tsgoFlags,
|
|
60
|
+
// `--no-plugins` builds the entry's owning project with plugin
|
|
61
|
+
// discovery and loading disabled. ttsc's own config loaders use it
|
|
62
|
+
// when they evaluate a `*.config.ts` through ttsx: that build only
|
|
63
|
+
// needs to type-check and run the config file, so loading the host
|
|
64
|
+
// project's transform/check plugins (`@nestia/core`, `typia`, …)
|
|
65
|
+
// would be both wasteful and wrong — those plugins impose project
|
|
66
|
+
// requirements (e.g. `strict` mode) the ephemeral config-loader
|
|
67
|
+
// tsconfig deliberately does not satisfy.
|
|
68
|
+
plugins: parsed.noPlugins ? false : undefined,
|
|
52
69
|
project: parsed.project,
|
|
70
|
+
singleThreaded: parsed.singleThreaded,
|
|
53
71
|
});
|
|
54
72
|
return runPreparedEntry(parsed, prepared, cwd);
|
|
55
73
|
}
|
|
@@ -62,82 +80,108 @@ function formatError(error: unknown): string {
|
|
|
62
80
|
}
|
|
63
81
|
|
|
64
82
|
function parseCLI(argv: readonly string[]) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
case "--cwd":
|
|
96
|
-
cwd = takeValue(current, head);
|
|
97
|
-
break;
|
|
98
|
-
case "--cache-dir":
|
|
99
|
-
cacheDir = takeValue(current, head);
|
|
100
|
-
break;
|
|
101
|
-
case "-r":
|
|
102
|
-
case "--require":
|
|
103
|
-
preload.push(takeValue(current, head));
|
|
104
|
-
break;
|
|
105
|
-
case "--binary":
|
|
106
|
-
binary = takeValue(current, head);
|
|
107
|
-
break;
|
|
108
|
-
default:
|
|
109
|
-
if (current.startsWith("--project=")) {
|
|
110
|
-
project = current.slice("--project=".length);
|
|
111
|
-
} else if (current.startsWith("--cwd=")) {
|
|
112
|
-
cwd = current.slice("--cwd=".length);
|
|
113
|
-
} else if (current.startsWith("--cache-dir=")) {
|
|
114
|
-
cacheDir = current.slice("--cache-dir=".length);
|
|
115
|
-
} else if (current.startsWith("--binary=")) {
|
|
116
|
-
binary = current.slice("--binary=".length);
|
|
117
|
-
} else if (current.startsWith("-")) {
|
|
118
|
-
throw new Error(`ttsx: unknown option ${current}`);
|
|
119
|
-
} else {
|
|
120
|
-
entry = current;
|
|
121
|
-
}
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
83
|
+
// ttsx accepts ttsc-style flags plus its own `--no-plugins` / `--require`.
|
|
84
|
+
// The shared schema engine recognises both; the engine returns positional
|
|
85
|
+
// tokens (entry file + flag values that aren't `.ts`) and a passthrough
|
|
86
|
+
// list mirroring the pre-schema behaviour.
|
|
87
|
+
//
|
|
88
|
+
// Order pin: ttsx accepts `-P` as an alias for `--project`. The schema
|
|
89
|
+
// declares `-p` for ttsc; ttsx's lowercase shape would collide on `-p`
|
|
90
|
+
// → `--tsconfig`, so the legacy `-P` (uppercase) is treated as `--project`
|
|
91
|
+
// via a manual rewrite before the engine sees argv. We preserve the
|
|
92
|
+
// historical behaviour and emit a structural error otherwise.
|
|
93
|
+
const rewritten = argv.map((token) =>
|
|
94
|
+
token === "-P"
|
|
95
|
+
? "--project"
|
|
96
|
+
: token.startsWith("-P=")
|
|
97
|
+
? `--project=${token.slice("-P=".length)}`
|
|
98
|
+
: token,
|
|
99
|
+
);
|
|
100
|
+
// Terminal flags (--help / --version) short-circuit before parsing so
|
|
101
|
+
// ttsx prints help text even when the entry file is missing.
|
|
102
|
+
for (const token of rewritten) {
|
|
103
|
+
if (token === "-h" || token === "--help") return "help" as const;
|
|
104
|
+
if (token === "-v" || token === "--version") return "version" as const;
|
|
105
|
+
}
|
|
106
|
+
const result = parseFlags({
|
|
107
|
+
argv: rewritten,
|
|
108
|
+
errorPrefix: "ttsx:",
|
|
109
|
+
forwardAfterFirstPositional: true,
|
|
110
|
+
honorDoubleDashSeparator: true,
|
|
111
|
+
subcommand: "ttsx",
|
|
112
|
+
});
|
|
125
113
|
|
|
126
|
-
|
|
114
|
+
const entry = result.positional.find(looksLikeEntryFile);
|
|
115
|
+
if (entry === undefined) {
|
|
127
116
|
throw new Error("ttsx: entry file is required");
|
|
128
117
|
}
|
|
118
|
+
// With `forwardAfterFirstPositional: true` the parser reports
|
|
119
|
+
// `result.positional` as just the entry, `result.passthrough` as flags
|
|
120
|
+
// arriving BEFORE the entry (tsgo-forwarded), and `result.tail` as every
|
|
121
|
+
// token AFTER the entry — those are the user program's argv (e.g. the
|
|
122
|
+
// `generate --input src/input` tail of `ttsx typia.ts generate
|
|
123
|
+
// --input src/input`) and MUST NOT reach tsgo. Anything in positional
|
|
124
|
+
// that is not the entry is a pre-entry flag value (e.g. `--target es2020`)
|
|
125
|
+
// that the parser stored positionally; forward those to tsgo with the
|
|
126
|
+
// rest of `passthrough`.
|
|
127
|
+
const preEntryValues: string[] = result.positional.filter(
|
|
128
|
+
(token) => token !== entry && !looksLikeEntryFile(token),
|
|
129
|
+
);
|
|
130
|
+
const postEntryArgs: string[] = [...result.tail];
|
|
131
|
+
|
|
132
|
+
const preload: string[] = [];
|
|
133
|
+
// `--require` accepts repeated values; the schema engine writes the
|
|
134
|
+
// LAST one into `values`, so reconstruct the full list by scanning the
|
|
135
|
+
// raw argv. Mirrors the legacy parser's `preload.push(takeValue(...))`
|
|
136
|
+
// behaviour.
|
|
137
|
+
//
|
|
138
|
+
// Stop the rescue scan at the first token that begins tail mode —
|
|
139
|
+
// either the entry file or the `--` separator. Without this guard,
|
|
140
|
+
// `ttsx entry.ts -r preload.cjs` would BOTH preload `preload.cjs` AND
|
|
141
|
+
// forward `-r preload.cjs` to the entry's argv, double-effecting the
|
|
142
|
+
// module load. The schema engine already routes post-entry tokens to
|
|
143
|
+
// `result.tail`; the rescue scan must respect the same boundary.
|
|
144
|
+
const scanEnd = rewritten.findIndex(
|
|
145
|
+
(token) => looksLikeEntryFile(token) || token === "--",
|
|
146
|
+
);
|
|
147
|
+
const scanLimit = scanEnd === -1 ? rewritten.length : scanEnd;
|
|
148
|
+
for (let i = 0; i < scanLimit; i += 1) {
|
|
149
|
+
const token = rewritten[i]!;
|
|
150
|
+
if (token === "-r" || token === "--require") {
|
|
151
|
+
const value = rewritten[i + 1];
|
|
152
|
+
if (value !== undefined && !value.startsWith("-")) {
|
|
153
|
+
preload.push(value);
|
|
154
|
+
i += 1;
|
|
155
|
+
}
|
|
156
|
+
} else if (token.startsWith("--require=")) {
|
|
157
|
+
preload.push(token.slice("--require=".length));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
129
160
|
|
|
130
161
|
return {
|
|
131
|
-
binary,
|
|
132
|
-
cacheDir,
|
|
133
|
-
|
|
162
|
+
binary: getString(result, "--binary"),
|
|
163
|
+
cacheDir: getString(result, "--cache-dir"),
|
|
164
|
+
checkers: getNumber(result, "--checkers"),
|
|
165
|
+
cwd: getString(result, "--cwd"),
|
|
134
166
|
entry,
|
|
135
|
-
|
|
167
|
+
noPlugins: getBoolean(result, "--no-plugins") === true,
|
|
168
|
+
passthrough: postEntryArgs,
|
|
136
169
|
preload,
|
|
137
|
-
project,
|
|
170
|
+
project: getString(result, "--tsconfig"),
|
|
171
|
+
singleThreaded: getBoolean(result, "--singleThreaded") === true,
|
|
172
|
+
tsgoFlags: [...result.passthrough, ...preEntryValues],
|
|
138
173
|
};
|
|
139
174
|
}
|
|
140
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Report whether a bare CLI token is the TypeScript entry file rather than a
|
|
178
|
+
* forwarded flag's value. ttsx runs a TypeScript entrypoint, so only a token
|
|
179
|
+
* with a TypeScript source extension is treated as the entry.
|
|
180
|
+
*/
|
|
181
|
+
function looksLikeEntryFile(token: string): boolean {
|
|
182
|
+
return [".ts", ".tsx", ".mts", ".cts"].some((ext) => token.endsWith(ext));
|
|
183
|
+
}
|
|
184
|
+
|
|
141
185
|
function printHelp(): void {
|
|
142
186
|
process.stdout.write(
|
|
143
187
|
[
|
|
@@ -151,10 +195,16 @@ function printHelp(): void {
|
|
|
151
195
|
" --cwd <dir> Resolve entry/project relative to this directory",
|
|
152
196
|
" --cache-dir <dir> Override the runner and source-plugin cache root",
|
|
153
197
|
" --binary <path> Use an explicit tsgo binary",
|
|
198
|
+
" --no-plugins Build the project without ttsc plugins",
|
|
154
199
|
" -r, --require <module> Preload a module before the entrypoint",
|
|
200
|
+
" --singleThreaded Run TypeScript-Go single-threaded (one checker)",
|
|
201
|
+
" --checkers <n> Type-checker pool size (default: TypeScript-Go's)",
|
|
155
202
|
" -h, --help Show this help",
|
|
156
203
|
" -v, --version Print the runner version",
|
|
157
204
|
"",
|
|
205
|
+
" Any other flag before the entry is forwarded to tsgo, so options like",
|
|
206
|
+
" --strict apply to the type-check (e.g. ttsx --strict src/index.ts).",
|
|
207
|
+
"",
|
|
158
208
|
"Examples:",
|
|
159
209
|
" ttsx src/index.ts",
|
|
160
210
|
" ttsx --project tsconfig.json src/index.ts -- --port 3000",
|
|
@@ -181,14 +231,6 @@ function isRelativeSpecifier(specifier: string): boolean {
|
|
|
181
231
|
);
|
|
182
232
|
}
|
|
183
233
|
|
|
184
|
-
function takeValue(flag: string, rest: string[]): string {
|
|
185
|
-
const value = rest.shift();
|
|
186
|
-
if (!value) {
|
|
187
|
-
throw new Error(`ttsx: ${flag} requires a value`);
|
|
188
|
-
}
|
|
189
|
-
return value;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
234
|
function runPreparedEntry(
|
|
193
235
|
parsed: Exclude<ReturnType<typeof parseCLI>, "help" | "version">,
|
|
194
236
|
execution: ReturnType<typeof prepareExecution>,
|
|
@@ -120,12 +120,15 @@ export function loadProjectPlugins(options: {
|
|
|
120
120
|
? `linked_${String(index).padStart(6, "0")}`
|
|
121
121
|
: undefined;
|
|
122
122
|
return {
|
|
123
|
+
capabilities: plugin.capabilities,
|
|
123
124
|
contributors,
|
|
124
125
|
config: entries[index]!.config,
|
|
125
126
|
kind,
|
|
126
127
|
label: pluginLabel(plugin, entries[index]!.config, index),
|
|
127
128
|
linkedContributorName,
|
|
128
129
|
name: plugin.name,
|
|
130
|
+
reportsTypeScriptDiagnostics:
|
|
131
|
+
plugin.reportsTypeScriptDiagnostics === true,
|
|
129
132
|
source,
|
|
130
133
|
stage,
|
|
131
134
|
};
|
|
@@ -196,10 +199,12 @@ export function loadProjectPlugins(options: {
|
|
|
196
199
|
}
|
|
197
200
|
return {
|
|
198
201
|
binary,
|
|
202
|
+
capabilities: record.capabilities,
|
|
199
203
|
config: record.config,
|
|
200
204
|
contributors: record.contributors,
|
|
201
205
|
kind: record.kind,
|
|
202
206
|
name: record.name,
|
|
207
|
+
reportsTypeScriptDiagnostics: record.reportsTypeScriptDiagnostics,
|
|
203
208
|
source: record.source,
|
|
204
209
|
stage: record.stage,
|
|
205
210
|
};
|
|
@@ -288,6 +293,14 @@ function composePluginSources(
|
|
|
288
293
|
...plugin,
|
|
289
294
|
source: aggregate.plugin.source,
|
|
290
295
|
contributors: aggregate.plugin.contributors,
|
|
296
|
+
// The composed plugin's runtime BINARY is the aggregate's binary,
|
|
297
|
+
// so the CLI surface (which flags the sidecar parses) is the
|
|
298
|
+
// aggregate's. Inherit `capabilities` from the aggregate so a
|
|
299
|
+
// capability the aggregate declares — e.g. threadingArgs — does
|
|
300
|
+
// not get silently dropped just because the composed entry's own
|
|
301
|
+
// descriptor omitted it. If the aggregate did not set capabilities
|
|
302
|
+
// we keep the composed plugin's own as a fallback.
|
|
303
|
+
capabilities: aggregate.plugin.capabilities ?? plugin.capabilities,
|
|
291
304
|
};
|
|
292
305
|
});
|
|
293
306
|
}
|
|
@@ -545,38 +558,72 @@ function loadPluginEntry(
|
|
|
545
558
|
context: ITtscPluginFactoryContext,
|
|
546
559
|
baseDir: string,
|
|
547
560
|
): ITtscPlugin {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
const request = resolvePluginRequest(specifier, baseDir);
|
|
554
|
-
const mod = require(request) as {
|
|
555
|
-
createTtscPlugin?: TtscPluginFactory;
|
|
556
|
-
default?: ITtscPlugin | TtscPluginFactory;
|
|
557
|
-
} & Partial<Record<"plugin", ITtscPlugin | TtscPluginFactory>>;
|
|
558
|
-
const candidate =
|
|
559
|
-
mod.createTtscPlugin ??
|
|
560
|
-
mod.default ??
|
|
561
|
-
mod.plugin ??
|
|
562
|
-
(mod as unknown as ITtscPlugin | TtscPluginFactory);
|
|
563
|
-
if (typeof candidate === "function") {
|
|
564
|
-
const plugin = candidate(context);
|
|
565
|
-
if (!isTtscPlugin(plugin)) {
|
|
561
|
+
return withPluginLoaderEnv(() => {
|
|
562
|
+
const specifier = entry.transform;
|
|
563
|
+
if (typeof specifier !== "string" || specifier.length === 0) {
|
|
566
564
|
throw new Error(
|
|
567
|
-
`ttsc: plugin
|
|
565
|
+
`ttsc: plugin entry is missing a string "transform" field`,
|
|
568
566
|
);
|
|
569
567
|
}
|
|
570
|
-
|
|
571
|
-
|
|
568
|
+
|
|
569
|
+
const request = resolvePluginRequest(specifier, baseDir);
|
|
570
|
+
const mod = require(request) as {
|
|
571
|
+
createTtscPlugin?: TtscPluginFactory;
|
|
572
|
+
default?: ITtscPlugin | TtscPluginFactory;
|
|
573
|
+
} & Partial<Record<"plugin", ITtscPlugin | TtscPluginFactory>>;
|
|
574
|
+
const candidate =
|
|
575
|
+
mod.createTtscPlugin ??
|
|
576
|
+
mod.default ??
|
|
577
|
+
mod.plugin ??
|
|
578
|
+
(mod as unknown as ITtscPlugin | TtscPluginFactory);
|
|
579
|
+
if (typeof candidate === "function") {
|
|
580
|
+
const plugin = candidate(context);
|
|
581
|
+
if (!isTtscPlugin(plugin)) {
|
|
582
|
+
throw new Error(
|
|
583
|
+
`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
|
|
584
|
+
);
|
|
585
|
+
}
|
|
586
|
+
rejectJsTransformFunctions(specifier, plugin);
|
|
587
|
+
return plugin;
|
|
588
|
+
}
|
|
589
|
+
if (isTtscPlugin(candidate)) {
|
|
590
|
+
rejectJsTransformFunctions(specifier, candidate);
|
|
591
|
+
return candidate;
|
|
592
|
+
}
|
|
593
|
+
throw new Error(
|
|
594
|
+
`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
|
|
595
|
+
);
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function withPluginLoaderEnv<T>(run: () => T): T {
|
|
600
|
+
const previousNode = process.env.TTSC_NODE_BINARY;
|
|
601
|
+
const previousTtsx = process.env.TTSC_TTSX_BINARY;
|
|
602
|
+
process.env.TTSC_NODE_BINARY ??= process.execPath;
|
|
603
|
+
process.env.TTSC_TTSX_BINARY ??= path.join(
|
|
604
|
+
__dirname,
|
|
605
|
+
"..",
|
|
606
|
+
"..",
|
|
607
|
+
"launcher",
|
|
608
|
+
"ttsx.js",
|
|
609
|
+
);
|
|
610
|
+
try {
|
|
611
|
+
return run();
|
|
612
|
+
} finally {
|
|
613
|
+
restoreEnv("TTSC_NODE_BINARY", previousNode);
|
|
614
|
+
restoreEnv("TTSC_TTSX_BINARY", previousTtsx);
|
|
572
615
|
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
function restoreEnv(
|
|
619
|
+
key: "TTSC_NODE_BINARY" | "TTSC_TTSX_BINARY",
|
|
620
|
+
value: string | undefined,
|
|
621
|
+
): void {
|
|
622
|
+
if (value === undefined) {
|
|
623
|
+
delete process.env[key];
|
|
624
|
+
} else {
|
|
625
|
+
process.env[key] = value;
|
|
576
626
|
}
|
|
577
|
-
throw new Error(
|
|
578
|
-
`ttsc: plugin "${specifier}" does not export a valid ttsc plugin`,
|
|
579
|
-
);
|
|
580
627
|
}
|
|
581
628
|
|
|
582
629
|
function isTtscPlugin(value: unknown): value is ITtscPlugin {
|