ttsc 0.20.1 → 0.22.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/ttscserver/main.go +79 -3
- package/cmd/ttscserver/run_lsp_uses_native_plugin_manifest_source_test.go +96 -16
- package/driver/lsp.go +4 -0
- package/internal/lspserver/lsp_document_diagnostics_omits_cached_project_when_current_result_has_none_test.go +48 -0
- package/internal/lspserver/lsp_executable_project_input_change_requests_launcher_restart_test.go +98 -0
- package/internal/lspserver/lsp_external_watched_change_retains_program_test.go +53 -0
- package/internal/lspserver/lsp_hints_refresh_scheduled_by_editor_events_test.go +9 -0
- package/internal/lspserver/lsp_native_plugin_source.go +171 -55
- package/internal/lspserver/lsp_native_resident.go +110 -9
- package/internal/lspserver/lsp_plugin.go +6 -0
- package/internal/lspserver/lsp_project_context_transport_identity_test.go +43 -0
- package/internal/lspserver/lsp_project_diagnostics.go +235 -0
- package/internal/lspserver/lsp_project_diagnostics_capability_is_independent_test.go +67 -0
- package/internal/lspserver/lsp_project_diagnostics_program_input_overlap_refreshes_all_producers_test.go +78 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_accumulates_owner_freshness_test.go +65 -0
- package/internal/lspserver/lsp_project_diagnostics_refresh_keeps_each_producer_last_good_test.go +90 -0
- package/internal/lspserver/lsp_project_diagnostics_refreshes_only_input_owners_test.go +62 -0
- package/internal/lspserver/lsp_project_diagnostics_resident_unsupported_falls_back_test.go +123 -0
- package/internal/lspserver/lsp_project_diagnostics_resume_preserves_generation_test.go +52 -0
- package/internal/lspserver/lsp_project_diagnostics_translates_relative_client_project_test.go +90 -0
- package/internal/lspserver/lsp_project_input_ownership_resolves_declared_spellings_test.go +95 -0
- package/internal/lspserver/lsp_project_input_registration.go +504 -0
- package/internal/lspserver/lsp_project_input_registration_rechecks_selection_baseline_test.go +71 -0
- package/internal/lspserver/lsp_project_input_registration_reconciles_rejected_pending_request_test.go +268 -0
- package/internal/lspserver/lsp_project_input_registration_shapes_patterns_test.go +94 -0
- package/internal/lspserver/lsp_project_input_watchers_anchor_missing_paths_test.go +48 -0
- package/internal/lspserver/lsp_project_inputs.go +944 -0
- package/internal/lspserver/lsp_project_inputs_match_missing_exact_and_glob_test.go +129 -0
- package/internal/lspserver/lsp_project_inputs_reject_invalid_and_foreign_snapshots_test.go +110 -0
- package/internal/lspserver/lsp_proxy.go +591 -28
- package/internal/lspserver/lsp_reload_directories_compare_immediate_topology_test.go +188 -0
- package/internal/lspserver/lsp_reload_file_fingerprint_preserves_raw_symlink_target_test.go +47 -0
- package/internal/lspserver/lsp_resident_falls_back_when_serve_unsupported_test.go +1 -1
- package/internal/lspserver/lsp_resident_invalidation_broadcasts_program_input_overlap_test.go +60 -0
- package/internal/lspserver/lsp_resident_invalidation_targets_project_input_owners_test.go +51 -0
- package/internal/lspserver/lsp_same_transport_entries_are_deduplicated_test.go +66 -0
- package/lib/compiler/internal/project/readProjectConfig.d.ts +5 -3
- package/lib/compiler/internal/project/readProjectConfig.js +46 -14
- package/lib/compiler/internal/project/readProjectConfig.js.map +1 -1
- package/lib/compiler/internal/residentCheckProcess.d.ts +46 -0
- package/lib/compiler/internal/residentCheckProcess.js +227 -0
- package/lib/compiler/internal/residentCheckProcess.js.map +1 -0
- package/lib/compiler/internal/resolveEmittedJavaScript.d.ts +2 -1
- package/lib/compiler/internal/resolveEmittedJavaScript.js +35 -18
- package/lib/compiler/internal/resolveEmittedJavaScript.js.map +1 -1
- package/lib/compiler/internal/runBuild.d.ts +64 -1
- package/lib/compiler/internal/runBuild.js +484 -7
- package/lib/compiler/internal/runBuild.js.map +1 -1
- package/lib/compiler/internal/runSingleFileEmit.js +1 -0
- package/lib/compiler/internal/runSingleFileEmit.js.map +1 -1
- package/lib/flags/parser.js +13 -17
- package/lib/flags/parser.js.map +1 -1
- package/lib/flags/schema.d.ts +10 -3
- package/lib/flags/schema.js +99 -0
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +23 -0
- package/lib/internal/projectInputPathIdentity.js +222 -0
- package/lib/internal/projectInputPathIdentity.js.map +1 -0
- package/lib/launcher/internal/runTtsc.d.ts +17 -0
- package/lib/launcher/internal/runTtsc.js +121 -88
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.d.ts +13 -1
- package/lib/launcher/internal/runTtscserver.js +415 -45
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/singleFileOutput.d.ts +16 -0
- package/lib/launcher/internal/singleFileOutput.js +103 -0
- package/lib/launcher/internal/singleFileOutput.js.map +1 -0
- package/lib/launcher/internal/watchTopology.d.ts +214 -2
- package/lib/launcher/internal/watchTopology.js +1560 -41
- package/lib/launcher/internal/watchTopology.js.map +1 -1
- package/lib/plugin/internal/loadProjectPlugins.d.ts +1 -0
- package/lib/plugin/internal/loadProjectPlugins.js +5 -0
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/lib/structures/ITtscPluginCapabilities.d.ts +31 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.d.ts +29 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js +3 -0
- package/lib/structures/internal/ITtscProjectInputSnapshot.js.map +1 -0
- package/lib/structures/internal/TtscSingleFileEmitOptions.d.ts +5 -0
- package/package.json +8 -8
- package/src/compiler/internal/project/readProjectConfig.ts +62 -8
- package/src/compiler/internal/residentCheckProcess.ts +279 -0
- package/src/compiler/internal/resolveEmittedJavaScript.ts +43 -21
- package/src/compiler/internal/runBuild.ts +728 -12
- package/src/compiler/internal/runSingleFileEmit.ts +1 -0
- package/src/flags/parser.ts +17 -17
- package/src/flags/schema.ts +111 -3
- package/src/internal/projectInputPathIdentity.ts +273 -0
- package/src/launcher/internal/runTtsc.ts +141 -117
- package/src/launcher/internal/runTtscserver.ts +561 -54
- package/src/launcher/internal/singleFileOutput.ts +133 -0
- package/src/launcher/internal/watchTopology.ts +2008 -59
- package/src/plugin/internal/loadProjectPlugins.ts +8 -0
- package/src/structures/ITtscPluginCapabilities.ts +34 -0
- package/src/structures/internal/ITtscProjectInputSnapshot.ts +29 -0
- package/src/structures/internal/TtscSingleFileEmitOptions.ts +5 -0
|
@@ -4,7 +4,11 @@ import path from "node:path";
|
|
|
4
4
|
import { TtscCompiler } from "../../TtscCompiler";
|
|
5
5
|
import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
|
|
6
6
|
import { resolveProjectConfig } from "../../compiler/internal/project/resolveProjectConfig";
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
type ResidentCheckWatchChange,
|
|
9
|
+
ResidentCheckWatchSession,
|
|
10
|
+
runBuild,
|
|
11
|
+
} from "../../compiler/internal/runBuild";
|
|
8
12
|
import { runSingleFileEmit } from "../../compiler/internal/runSingleFileEmit";
|
|
9
13
|
import {
|
|
10
14
|
getBoolean,
|
|
@@ -20,10 +24,13 @@ import {
|
|
|
20
24
|
resolveCleanTargets,
|
|
21
25
|
resolveSourceBuildCachePaths,
|
|
22
26
|
} from "../../plugin/internal/buildSourcePlugin";
|
|
27
|
+
import type { ITtscProjectInputSnapshot } from "../../structures/internal/ITtscProjectInputSnapshot";
|
|
23
28
|
import type { TtscBuildOptions } from "../../structures/internal/TtscBuildOptions";
|
|
29
|
+
import type { TtscSingleFileEmitOptions } from "../../structures/internal/TtscSingleFileEmitOptions";
|
|
24
30
|
import { getCompilerVersionText } from "./getCompilerVersionText";
|
|
25
31
|
import { resolveCacheDir } from "./resolveCacheDir";
|
|
26
|
-
import {
|
|
32
|
+
import { resolveSingleFileOutput } from "./singleFileOutput";
|
|
33
|
+
import { type WatchInputChange, WatchTopology } from "./watchTopology";
|
|
27
34
|
|
|
28
35
|
/**
|
|
29
36
|
* CLI entry point for `ttsc`. Dispatches argv to the appropriate build lane
|
|
@@ -570,7 +577,10 @@ function printCacheHelp(): void {
|
|
|
570
577
|
process.stdout.write("\n");
|
|
571
578
|
}
|
|
572
579
|
|
|
573
|
-
function runSingleFile(
|
|
580
|
+
function runSingleFile(
|
|
581
|
+
options: ReturnType<typeof parseBuildArgs> &
|
|
582
|
+
Pick<TtscSingleFileEmitOptions, "onProjectInputs" | "onWatchInputs">,
|
|
583
|
+
): number {
|
|
574
584
|
if (options.files.length !== 1) {
|
|
575
585
|
throw new Error(
|
|
576
586
|
"ttsc: single-file mode currently accepts exactly one input file",
|
|
@@ -580,10 +590,11 @@ function runSingleFile(options: ReturnType<typeof parseBuildArgs>): number {
|
|
|
580
590
|
const file = path.resolve(cwd, options.files[0]!);
|
|
581
591
|
const emit = singleFileShouldEmit(options, cwd, file);
|
|
582
592
|
const out = emit
|
|
583
|
-
?
|
|
593
|
+
? resolveSingleFileOutput({
|
|
584
594
|
cliOutDir: options.outDir,
|
|
585
595
|
cwd,
|
|
586
596
|
file,
|
|
597
|
+
passthrough: options.passthrough,
|
|
587
598
|
tsconfig: options.tsconfig,
|
|
588
599
|
})
|
|
589
600
|
: undefined;
|
|
@@ -592,6 +603,8 @@ function runSingleFile(options: ReturnType<typeof parseBuildArgs>): number {
|
|
|
592
603
|
checkers: options.checkers,
|
|
593
604
|
cwd,
|
|
594
605
|
file,
|
|
606
|
+
onProjectInputs: options.onProjectInputs,
|
|
607
|
+
onWatchInputs: options.onWatchInputs,
|
|
595
608
|
out,
|
|
596
609
|
passthrough: options.passthrough,
|
|
597
610
|
singleThreaded: options.singleThreaded,
|
|
@@ -628,101 +641,6 @@ function singleFileShouldEmit(
|
|
|
628
641
|
return project.compilerOptions.noEmit !== true;
|
|
629
642
|
}
|
|
630
643
|
|
|
631
|
-
function resolveSingleFileOut(opts: {
|
|
632
|
-
cliOutDir?: string;
|
|
633
|
-
cwd: string;
|
|
634
|
-
file: string;
|
|
635
|
-
tsconfig?: string;
|
|
636
|
-
}): string {
|
|
637
|
-
const jsBasename =
|
|
638
|
-
path.basename(opts.file).replace(/\.[cm]?tsx?$/i, "") +
|
|
639
|
-
singleFileJsExtension(opts.file);
|
|
640
|
-
|
|
641
|
-
// Explicit CLI --outDir wins. Mirrors the CWD-relative source layout under
|
|
642
|
-
// the requested directory so existing single-file invocations don't shift.
|
|
643
|
-
if (opts.cliOutDir) {
|
|
644
|
-
const relative = path.relative(opts.cwd, opts.file);
|
|
645
|
-
const jsRelative =
|
|
646
|
-
relative.slice(0, relative.length - path.extname(relative).length) +
|
|
647
|
-
singleFileJsExtension(opts.file);
|
|
648
|
-
return path.resolve(opts.cwd, opts.cliOutDir, jsRelative);
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// No CLI override: honor tsconfig's outDir so `ttsc src/foo.ts` lands the
|
|
652
|
-
// emitted JS at `<outDir>/<relative-from-rootDir>.js` instead of dropping
|
|
653
|
-
// it next to the source file. This matches how project mode emits and how
|
|
654
|
-
// `tsc <file>` would behave with the same tsconfig.
|
|
655
|
-
const projectOutDir = readProjectOutDir({
|
|
656
|
-
cwd: opts.cwd,
|
|
657
|
-
file: opts.file,
|
|
658
|
-
tsconfig: opts.tsconfig,
|
|
659
|
-
});
|
|
660
|
-
if (projectOutDir !== null) {
|
|
661
|
-
const fromRoot = path.relative(projectOutDir.rootDir, opts.file);
|
|
662
|
-
if (fromRoot !== "" && !isOutsideSingleFileLayout(fromRoot)) {
|
|
663
|
-
const jsRelative =
|
|
664
|
-
fromRoot.slice(0, fromRoot.length - path.extname(fromRoot).length) +
|
|
665
|
-
singleFileJsExtension(opts.file);
|
|
666
|
-
return path.resolve(projectOutDir.outDir, jsRelative);
|
|
667
|
-
}
|
|
668
|
-
return path.resolve(projectOutDir.outDir, jsBasename);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
// Last resort (no tsconfig outDir at all): emit next to the source. This
|
|
672
|
-
// preserves the legacy `ttsc <file.ts>` → `<file.js>` behavior for projects
|
|
673
|
-
// that intentionally don't configure outDir.
|
|
674
|
-
return opts.file.replace(/\.[cm]?tsx?$/i, singleFileJsExtension(opts.file));
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
function readProjectOutDir(opts: {
|
|
678
|
-
cwd: string;
|
|
679
|
-
file: string;
|
|
680
|
-
tsconfig?: string;
|
|
681
|
-
}): { outDir: string; rootDir: string } | null {
|
|
682
|
-
try {
|
|
683
|
-
const project = readProjectConfig({
|
|
684
|
-
cwd: opts.cwd,
|
|
685
|
-
file: opts.file,
|
|
686
|
-
tsconfig: opts.tsconfig,
|
|
687
|
-
});
|
|
688
|
-
const outDir = project.compilerOptions.outDir;
|
|
689
|
-
if (typeof outDir !== "string" || outDir.length === 0) {
|
|
690
|
-
return null;
|
|
691
|
-
}
|
|
692
|
-
const rawRoot = project.compilerOptions.rootDir;
|
|
693
|
-
const rootDir =
|
|
694
|
-
typeof rawRoot === "string" && rawRoot.length !== 0
|
|
695
|
-
? path.isAbsolute(rawRoot)
|
|
696
|
-
? rawRoot
|
|
697
|
-
: path.resolve(project.root, rawRoot)
|
|
698
|
-
: project.root;
|
|
699
|
-
return { outDir, rootDir };
|
|
700
|
-
} catch {
|
|
701
|
-
// Missing or unreadable tsconfig: fall back to the legacy behavior so
|
|
702
|
-
// `ttsc <file>` still works outside a configured project.
|
|
703
|
-
return null;
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
function isOutsideSingleFileLayout(relative: string): boolean {
|
|
708
|
-
return (
|
|
709
|
-
relative === ".." ||
|
|
710
|
-
relative.startsWith(`..${path.sep}`) ||
|
|
711
|
-
path.isAbsolute(relative)
|
|
712
|
-
);
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
function singleFileJsExtension(file: string): string {
|
|
716
|
-
switch (path.extname(file).toLowerCase()) {
|
|
717
|
-
case ".mts":
|
|
718
|
-
return ".mjs";
|
|
719
|
-
case ".cts":
|
|
720
|
-
return ".cjs";
|
|
721
|
-
default:
|
|
722
|
-
return ".js";
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
644
|
function runWatch(
|
|
727
645
|
options: ReturnType<typeof parseBuildArgs>,
|
|
728
646
|
checkOnly: boolean,
|
|
@@ -735,6 +653,9 @@ function runWatch(
|
|
|
735
653
|
onWatchInputs: (inputs: readonly string[]) => {
|
|
736
654
|
topology?.setExtraInputs(inputs);
|
|
737
655
|
},
|
|
656
|
+
onProjectInputs: (inputs: ITtscProjectInputSnapshot) => {
|
|
657
|
+
topology?.setProjectInputs(inputs);
|
|
658
|
+
},
|
|
738
659
|
quiet: true,
|
|
739
660
|
};
|
|
740
661
|
const root = path.dirname(
|
|
@@ -746,12 +667,16 @@ function runWatch(
|
|
|
746
667
|
let running = false;
|
|
747
668
|
let rerun = false;
|
|
748
669
|
let timer: NodeJS.Timeout | null = null;
|
|
670
|
+
const resident =
|
|
671
|
+
invocation.files.length === 0 ? new ResidentCheckWatchSession() : undefined;
|
|
672
|
+
const pendingChanges = new PendingResidentCheckWatchChanges();
|
|
749
673
|
// Tracks the most recent build's exit code so the watch session can exit
|
|
750
674
|
// non-zero when its latest rebuild failed, instead of always reporting 0.
|
|
751
675
|
let lastStatus = 0;
|
|
752
676
|
|
|
753
|
-
const runOnce = () => {
|
|
677
|
+
const runOnce = async () => {
|
|
754
678
|
running = true;
|
|
679
|
+
const change = pendingChanges.take();
|
|
755
680
|
let completed = false;
|
|
756
681
|
try {
|
|
757
682
|
if (!options.preserveWatchOutput) {
|
|
@@ -766,24 +691,38 @@ function runWatch(
|
|
|
766
691
|
// to invalid JSON, single-file invariant). Catch here so a throwing
|
|
767
692
|
// rebuild reports a clear failure and the watcher keeps running instead
|
|
768
693
|
// of crashing the process with an uncaught exception.
|
|
769
|
-
const status =
|
|
770
|
-
invocation
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
694
|
+
const status = await (invocation.files.length !== 0
|
|
695
|
+
? Promise.resolve(runSingleFile(invocation))
|
|
696
|
+
: (async () => {
|
|
697
|
+
const buildOptions = checkOnly
|
|
698
|
+
? { ...invocation, emit: false }
|
|
699
|
+
: invocation;
|
|
700
|
+
const result =
|
|
701
|
+
resident === undefined
|
|
702
|
+
? runBuild(buildOptions)
|
|
703
|
+
: await resident.run(buildOptions, change);
|
|
704
|
+
// Watch output is one ordered stream. The diagnostics a build
|
|
705
|
+
// produced and the "[ttsc] watch build …" marker that concludes
|
|
706
|
+
// them reach a single merged reader, and on two pipes the OS may
|
|
707
|
+
// hand it the marker first — a reader that counts the build on the
|
|
708
|
+
// marker then snapshots before the diagnostics land and sees an
|
|
709
|
+
// empty build. The rebuilding banner, the screen clear, and the
|
|
710
|
+
// marker are already on stdout, so the diagnostics join them there,
|
|
711
|
+
// the way tsc --watch prints everything to one place.
|
|
712
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
713
|
+
if (result.stderr) process.stdout.write(result.stderr);
|
|
714
|
+
return result.status;
|
|
715
|
+
})());
|
|
780
716
|
lastStatus = status;
|
|
781
717
|
completed = true;
|
|
782
718
|
process.stdout.write(
|
|
783
719
|
`[ttsc] ${status === 0 ? "watch build complete" : "watch build failed"}\n`,
|
|
784
720
|
);
|
|
785
721
|
} catch (error) {
|
|
786
|
-
|
|
722
|
+
// Same ordered-stream rule as the build path: the failure text goes on
|
|
723
|
+
// the stream the marker below uses, so a merged reader sees them in order
|
|
724
|
+
// rather than racing two pipes.
|
|
725
|
+
process.stdout.write(`${formatError(error)}\n`);
|
|
787
726
|
lastStatus = lastStatus === 0 ? 2 : lastStatus;
|
|
788
727
|
process.stdout.write(`[ttsc] watch build failed\n`);
|
|
789
728
|
} finally {
|
|
@@ -807,13 +746,14 @@ function runWatch(
|
|
|
807
746
|
trigger();
|
|
808
747
|
}
|
|
809
748
|
};
|
|
810
|
-
const trigger = () => {
|
|
749
|
+
const trigger = (change?: WatchInputChange, reload = false) => {
|
|
750
|
+
pendingChanges.push(change, reload);
|
|
811
751
|
if (running) {
|
|
812
752
|
rerun = true;
|
|
813
753
|
return;
|
|
814
754
|
}
|
|
815
755
|
if (timer) clearTimeout(timer);
|
|
816
|
-
timer = setTimeout(runOnce, 60);
|
|
756
|
+
timer = setTimeout(() => void runOnce(), 60);
|
|
817
757
|
};
|
|
818
758
|
|
|
819
759
|
topology = new WatchTopology(invocation, {
|
|
@@ -822,14 +762,29 @@ function runWatch(
|
|
|
822
762
|
`[ttsc] watch error on ${path.relative(cwd, location) || "."}: ${formatError(error)}\n`,
|
|
823
763
|
);
|
|
824
764
|
},
|
|
825
|
-
onInputChange:
|
|
826
|
-
|
|
765
|
+
onInputChange: (change) => {
|
|
766
|
+
debugWatchInputs(
|
|
767
|
+
`change ${change.kind}${change.invalidate === true ? " invalidate" : ""} ${
|
|
768
|
+
change.path === undefined
|
|
769
|
+
? "(unnamed)"
|
|
770
|
+
: path.relative(cwd, change.path)
|
|
771
|
+
}`,
|
|
772
|
+
);
|
|
773
|
+
trigger(change);
|
|
774
|
+
},
|
|
775
|
+
onProjectInputWatchRoots: (roots) => {
|
|
776
|
+
debugWatchInputs(
|
|
777
|
+
`roots ${JSON.stringify(roots.map((root) => path.relative(cwd, root) || "."))}`,
|
|
778
|
+
);
|
|
779
|
+
},
|
|
780
|
+
onTopologyChange: () => trigger(undefined, true),
|
|
827
781
|
});
|
|
828
782
|
topology.refresh(false);
|
|
829
783
|
|
|
830
784
|
const close = () => {
|
|
831
785
|
if (timer) clearTimeout(timer);
|
|
832
786
|
topology?.close();
|
|
787
|
+
resident?.dispose();
|
|
833
788
|
};
|
|
834
789
|
process.on("SIGINT", () => {
|
|
835
790
|
close();
|
|
@@ -842,7 +797,7 @@ function runWatch(
|
|
|
842
797
|
|
|
843
798
|
process.stdout.write(`[ttsc] watching ${path.relative(cwd, root) || "."}\n`);
|
|
844
799
|
try {
|
|
845
|
-
runOnce();
|
|
800
|
+
void runOnce();
|
|
846
801
|
} catch (error) {
|
|
847
802
|
// runOnce already swallows build throws, but guard against any unforeseen
|
|
848
803
|
// throw escaping the first pass: tear down the persistent watchers so the
|
|
@@ -855,12 +810,81 @@ function runWatch(
|
|
|
855
810
|
return toExitCode(lastStatus);
|
|
856
811
|
}
|
|
857
812
|
|
|
813
|
+
/**
|
|
814
|
+
* Coalesces filesystem events until the next resident check-watch cycle.
|
|
815
|
+
*
|
|
816
|
+
* A full reload dominates every narrower signal. Program invalidation remains
|
|
817
|
+
* distinct so a project-input module creation/deletion can cold-load the
|
|
818
|
+
* Program without discarding the selected execution or restarting the sidecar.
|
|
819
|
+
*/
|
|
820
|
+
export class PendingResidentCheckWatchChanges {
|
|
821
|
+
private readonly changed = new Set<string>();
|
|
822
|
+
private readonly external = new Set<string>();
|
|
823
|
+
private invalidate = false;
|
|
824
|
+
private reload = false;
|
|
825
|
+
|
|
826
|
+
public push(change?: WatchInputChange, reload = false): void {
|
|
827
|
+
if (reload || change?.kind === "config" || change?.kind === "plugin") {
|
|
828
|
+
this.reload = true;
|
|
829
|
+
this.invalidate = false;
|
|
830
|
+
this.changed.clear();
|
|
831
|
+
this.external.clear();
|
|
832
|
+
return;
|
|
833
|
+
}
|
|
834
|
+
if (this.reload) return;
|
|
835
|
+
if (change?.invalidate === true) this.invalidate = true;
|
|
836
|
+
if (change?.path === undefined) {
|
|
837
|
+
if (change?.kind === "compiler") {
|
|
838
|
+
this.reload = true;
|
|
839
|
+
this.invalidate = false;
|
|
840
|
+
this.changed.clear();
|
|
841
|
+
this.external.clear();
|
|
842
|
+
}
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
this.changed.add(change.path);
|
|
846
|
+
if (change.kind === "project") this.external.add(change.path);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
public take(): ResidentCheckWatchChange {
|
|
850
|
+
const change: ResidentCheckWatchChange = {
|
|
851
|
+
...(this.reload ? { reload: true } : {}),
|
|
852
|
+
...(this.invalidate ? { invalidate: true } : {}),
|
|
853
|
+
...(this.changed.size === 0 ? {} : { changed: [...this.changed].sort() }),
|
|
854
|
+
...(this.external.size === 0
|
|
855
|
+
? {}
|
|
856
|
+
: { external: [...this.external].sort() }),
|
|
857
|
+
};
|
|
858
|
+
this.reload = false;
|
|
859
|
+
this.invalidate = false;
|
|
860
|
+
this.changed.clear();
|
|
861
|
+
this.external.clear();
|
|
862
|
+
return change;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
858
866
|
// Coerces a build status into a valid process exit code: 0 stays 0, any
|
|
859
867
|
// non-zero (or non-finite) status collapses to 1 so the session signals failure.
|
|
860
868
|
function toExitCode(status: number): number {
|
|
861
869
|
return Number.isInteger(status) && status >= 0 && status <= 255 ? status : 1;
|
|
862
870
|
}
|
|
863
871
|
|
|
872
|
+
/**
|
|
873
|
+
* Opt-in report of what the project-input layer is watching and announcing.
|
|
874
|
+
*
|
|
875
|
+
* A watch that goes quiet is indistinguishable from a watch with nothing to
|
|
876
|
+
* say: the build simply never runs again, and the transcript that survives says
|
|
877
|
+
* only that. This separates the two — the roots line states what is covered,
|
|
878
|
+
* and a change line states what was announced — so a missing rebuild can be
|
|
879
|
+
* attributed to the watch that never saw the file or to the decision that
|
|
880
|
+
* declined to report it. Silent unless asked, and on the same ordered stream as
|
|
881
|
+
* the rest of the watch output so it interleaves with the builds it explains.
|
|
882
|
+
*/
|
|
883
|
+
function debugWatchInputs(message: string): void {
|
|
884
|
+
if (!process.env.TTSC_WATCH_DEBUG_INPUTS) return;
|
|
885
|
+
process.stdout.write(`[ttsc:debug] ${message}\n`);
|
|
886
|
+
}
|
|
887
|
+
|
|
864
888
|
function formatError(error: unknown): string {
|
|
865
889
|
if (error instanceof Error) {
|
|
866
890
|
return error.message;
|