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
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createProjectInputPathIdentityContext = createProjectInputPathIdentityContext;
|
|
7
|
+
exports.isProjectInputPathIdentityWithin = isProjectInputPathIdentityWithin;
|
|
8
|
+
exports.resolveProjectInputPath = resolveProjectInputPath;
|
|
9
|
+
const node_child_process_1 = __importDefault(require("node:child_process"));
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
/**
|
|
13
|
+
* Create one filesystem-identity resolver for a project-input transaction.
|
|
14
|
+
*
|
|
15
|
+
* Existing segments use their physical spelling. A missing suffix keeps exact
|
|
16
|
+
* spelling only under a case-sensitive directory; otherwise its canonical
|
|
17
|
+
* spelling is folded so aliases remain one declaration before they exist.
|
|
18
|
+
*/
|
|
19
|
+
function createProjectInputPathIdentityContext(operations = {}) {
|
|
20
|
+
const identities = new Map();
|
|
21
|
+
const realpaths = new Map();
|
|
22
|
+
const sensitivities = new Map();
|
|
23
|
+
const realpath = operations.realpath ?? physicalRealpath;
|
|
24
|
+
const caseSensitive = operations.caseSensitive ?? filesystemDirectoryIsCaseSensitive;
|
|
25
|
+
const resolve = (location) => {
|
|
26
|
+
const normalized = resolveProjectInputPath(location);
|
|
27
|
+
const cached = identities.get(normalized);
|
|
28
|
+
if (cached !== undefined)
|
|
29
|
+
return cached.identity;
|
|
30
|
+
let existing = normalized;
|
|
31
|
+
const missing = [];
|
|
32
|
+
while (true) {
|
|
33
|
+
const physical = cachedRealpath(realpaths, realpath, existing);
|
|
34
|
+
if (physical !== undefined) {
|
|
35
|
+
const sensitive = missing.length === 0
|
|
36
|
+
? true
|
|
37
|
+
: cachedCaseSensitivity(sensitivities, caseSensitive, physical);
|
|
38
|
+
const suffix = sensitive
|
|
39
|
+
? missing
|
|
40
|
+
: missing.map((segment) => segment.toLowerCase());
|
|
41
|
+
const canonical = node_path_1.default.resolve(physical, ...suffix);
|
|
42
|
+
const identity = {
|
|
43
|
+
key: canonical,
|
|
44
|
+
path: canonical,
|
|
45
|
+
};
|
|
46
|
+
identities.set(normalized, {
|
|
47
|
+
ancestor: physical,
|
|
48
|
+
identity,
|
|
49
|
+
});
|
|
50
|
+
return identity;
|
|
51
|
+
}
|
|
52
|
+
const parent = node_path_1.default.dirname(existing);
|
|
53
|
+
if (parent === existing) {
|
|
54
|
+
const identity = {
|
|
55
|
+
key: normalized,
|
|
56
|
+
path: normalized,
|
|
57
|
+
};
|
|
58
|
+
identities.set(normalized, {
|
|
59
|
+
ancestor: normalized,
|
|
60
|
+
identity,
|
|
61
|
+
});
|
|
62
|
+
return identity;
|
|
63
|
+
}
|
|
64
|
+
missing.unshift(node_path_1.default.basename(existing));
|
|
65
|
+
existing = parent;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
caseSensitive: (directory) => {
|
|
70
|
+
const normalized = resolveProjectInputPath(directory);
|
|
71
|
+
resolve(normalized);
|
|
72
|
+
return cachedCaseSensitivity(sensitivities, caseSensitive, identities.get(normalized).ancestor);
|
|
73
|
+
},
|
|
74
|
+
isWithin: (root, candidate) => isProjectInputPathIdentityWithin(resolve(root).key, resolve(candidate).key),
|
|
75
|
+
resolve,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function isProjectInputPathIdentityWithin(root, candidate) {
|
|
79
|
+
if (candidate === root)
|
|
80
|
+
return true;
|
|
81
|
+
return candidate.startsWith(root.endsWith(node_path_1.default.sep) ? root : `${root}${node_path_1.default.sep}`);
|
|
82
|
+
}
|
|
83
|
+
function resolveProjectInputPath(location) {
|
|
84
|
+
if (process.platform !== "win32") {
|
|
85
|
+
return node_path_1.default.resolve(location);
|
|
86
|
+
}
|
|
87
|
+
const normalized = location.replaceAll("/", "\\");
|
|
88
|
+
if (normalized.toLowerCase().startsWith("\\\\?\\unc\\")) {
|
|
89
|
+
return node_path_1.default.resolve(`\\\\${normalized.slice(8)}`);
|
|
90
|
+
}
|
|
91
|
+
if (normalized.startsWith("\\\\?\\") &&
|
|
92
|
+
/^[A-Za-z]:\\/.test(normalized.slice(4))) {
|
|
93
|
+
return node_path_1.default.resolve(normalized.slice(4));
|
|
94
|
+
}
|
|
95
|
+
return node_path_1.default.resolve(normalized);
|
|
96
|
+
}
|
|
97
|
+
function cachedRealpath(cache, realpath, location) {
|
|
98
|
+
const cached = cache.get(location);
|
|
99
|
+
if (cached !== undefined)
|
|
100
|
+
return cached.found ? cached.path : undefined;
|
|
101
|
+
try {
|
|
102
|
+
const physical = resolveProjectInputPath(realpath(location));
|
|
103
|
+
cache.set(location, { found: true, path: physical });
|
|
104
|
+
return physical;
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (isMissingFilesystemEntry(error) === false)
|
|
108
|
+
throw error;
|
|
109
|
+
cache.set(location, { found: false });
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function cachedCaseSensitivity(cache, caseSensitive, directory) {
|
|
114
|
+
const cached = cache.get(directory);
|
|
115
|
+
if (cached !== undefined)
|
|
116
|
+
return cached;
|
|
117
|
+
const sensitive = caseSensitive(directory);
|
|
118
|
+
cache.set(directory, sensitive);
|
|
119
|
+
return sensitive;
|
|
120
|
+
}
|
|
121
|
+
function physicalRealpath(location) {
|
|
122
|
+
return node_fs_1.default.realpathSync.native?.(location) ?? node_fs_1.default.realpathSync(location);
|
|
123
|
+
}
|
|
124
|
+
function filesystemDirectoryIsCaseSensitive(directory) {
|
|
125
|
+
let entries;
|
|
126
|
+
try {
|
|
127
|
+
entries = node_fs_1.default.readdirSync(directory);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return unprovenCaseSensitivity();
|
|
131
|
+
}
|
|
132
|
+
const foldedNames = new Map();
|
|
133
|
+
for (const name of entries) {
|
|
134
|
+
const folded = name.toLowerCase();
|
|
135
|
+
const previous = foldedNames.get(folded);
|
|
136
|
+
if (previous !== undefined && previous !== name)
|
|
137
|
+
return true;
|
|
138
|
+
foldedNames.set(folded, name);
|
|
139
|
+
}
|
|
140
|
+
for (const name of entries) {
|
|
141
|
+
const alternate = alternateCase(name);
|
|
142
|
+
if (alternate === name)
|
|
143
|
+
continue;
|
|
144
|
+
try {
|
|
145
|
+
physicalRealpath(node_path_1.default.join(directory, alternate));
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (isMissingFilesystemEntry(error))
|
|
150
|
+
continue;
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (process.platform === "darwin") {
|
|
155
|
+
// APFS/HFS case semantics are volume-wide. An empty directory has no child
|
|
156
|
+
// name to probe, so ask the same read-only question of its existing name in
|
|
157
|
+
// the parent and walk upward until a name with ASCII case is available.
|
|
158
|
+
// This avoids a write probe while still distinguishing default APFS from a
|
|
159
|
+
// case-sensitive volume.
|
|
160
|
+
let current = resolveProjectInputPath(directory);
|
|
161
|
+
while (true) {
|
|
162
|
+
const parent = node_path_1.default.dirname(current);
|
|
163
|
+
if (parent === current)
|
|
164
|
+
break;
|
|
165
|
+
const name = node_path_1.default.basename(current);
|
|
166
|
+
const alternate = alternateCase(name);
|
|
167
|
+
if (alternate !== name) {
|
|
168
|
+
try {
|
|
169
|
+
physicalRealpath(node_path_1.default.join(parent, alternate));
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (isMissingFilesystemEntry(error) === false)
|
|
174
|
+
throw error;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
current = parent;
|
|
178
|
+
}
|
|
179
|
+
return unprovenCaseSensitivity();
|
|
180
|
+
}
|
|
181
|
+
if (process.platform !== "win32")
|
|
182
|
+
return true;
|
|
183
|
+
// Node does not expose the Windows per-directory flag. Prefer fsutil's
|
|
184
|
+
// read-only answer, then conservatively preserve distinct declarations.
|
|
185
|
+
const result = node_child_process_1.default.spawnSync("fsutil.exe", ["file", "queryCaseSensitiveInfo", directory], { encoding: "utf8", windowsHide: true });
|
|
186
|
+
if (result.error === undefined && result.status === 0) {
|
|
187
|
+
if (/\bdisabled\b/iu.test(result.stdout))
|
|
188
|
+
return false;
|
|
189
|
+
if (/\benabled\b/iu.test(result.stdout))
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
return unprovenCaseSensitivity();
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* What to assume when the volume refused to answer.
|
|
196
|
+
*
|
|
197
|
+
* Every probe above returns a proof: two names that fold together prove the
|
|
198
|
+
* volume keeps them apart, and a name that opens under the other case proves it
|
|
199
|
+
* does not. This is the remaining case — an empty or unreadable directory, a
|
|
200
|
+
* name with no letter to alter, a Windows build whose `fsutil` lacks the
|
|
201
|
+
* subcommand — and the only honest answer is the platform's own default.
|
|
202
|
+
*
|
|
203
|
+
* NTFS and APFS are case-insensitive unless a volume or a directory was opted
|
|
204
|
+
* out, so guessing sensitive there splits one file into two identities under
|
|
205
|
+
* two spellings, which is the failure this module exists to remove. The rarer
|
|
206
|
+
* mistake, on a volume that really was opted out, merges two files differing
|
|
207
|
+
* only in case into one watched identity.
|
|
208
|
+
*/
|
|
209
|
+
function unprovenCaseSensitivity() {
|
|
210
|
+
return process.platform !== "win32" && process.platform !== "darwin";
|
|
211
|
+
}
|
|
212
|
+
function alternateCase(value) {
|
|
213
|
+
return value.replace(/[A-Za-z]/g, (character) => character === character.toLowerCase()
|
|
214
|
+
? character.toUpperCase()
|
|
215
|
+
: character.toLowerCase());
|
|
216
|
+
}
|
|
217
|
+
function isMissingFilesystemEntry(error) {
|
|
218
|
+
return (error instanceof Error &&
|
|
219
|
+
"code" in error &&
|
|
220
|
+
(error.code === "ENOENT" || error.code === "ENOTDIR"));
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=projectInputPathIdentity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projectInputPathIdentity.js","sourceRoot":"","sources":["../../src/internal/projectInputPathIdentity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ResidentCheckWatchChange } from "../../compiler/internal/runBuild";
|
|
2
|
+
import { type WatchInputChange } from "./watchTopology";
|
|
1
3
|
/**
|
|
2
4
|
* CLI entry point for `ttsc`. Dispatches argv to the appropriate build lane
|
|
3
5
|
* (build, check, fix, format, prepare, clean, or native-delegate) and returns
|
|
@@ -9,3 +11,18 @@
|
|
|
9
11
|
* error or build failure.
|
|
10
12
|
*/
|
|
11
13
|
export declare function runTtsc(argv?: readonly string[]): number;
|
|
14
|
+
/**
|
|
15
|
+
* Coalesces filesystem events until the next resident check-watch cycle.
|
|
16
|
+
*
|
|
17
|
+
* A full reload dominates every narrower signal. Program invalidation remains
|
|
18
|
+
* distinct so a project-input module creation/deletion can cold-load the
|
|
19
|
+
* Program without discarding the selected execution or restarting the sidecar.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PendingResidentCheckWatchChanges {
|
|
22
|
+
private readonly changed;
|
|
23
|
+
private readonly external;
|
|
24
|
+
private invalidate;
|
|
25
|
+
private reload;
|
|
26
|
+
push(change?: WatchInputChange, reload?: boolean): void;
|
|
27
|
+
take(): ResidentCheckWatchChange;
|
|
28
|
+
}
|
|
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PendingResidentCheckWatchChanges = void 0;
|
|
6
7
|
exports.runTtsc = runTtsc;
|
|
7
8
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -17,6 +18,7 @@ const assertSafeExplicitCacheDirectory_1 = require("../../internal/assertSafeExp
|
|
|
17
18
|
const buildSourcePlugin_1 = require("../../plugin/internal/buildSourcePlugin");
|
|
18
19
|
const getCompilerVersionText_1 = require("./getCompilerVersionText");
|
|
19
20
|
const resolveCacheDir_1 = require("./resolveCacheDir");
|
|
21
|
+
const singleFileOutput_1 = require("./singleFileOutput");
|
|
20
22
|
const watchTopology_1 = require("./watchTopology");
|
|
21
23
|
/**
|
|
22
24
|
* CLI entry point for `ttsc`. Dispatches argv to the appropriate build lane
|
|
@@ -493,10 +495,11 @@ function runSingleFile(options) {
|
|
|
493
495
|
const file = node_path_1.default.resolve(cwd, options.files[0]);
|
|
494
496
|
const emit = singleFileShouldEmit(options, cwd, file);
|
|
495
497
|
const out = emit
|
|
496
|
-
?
|
|
498
|
+
? (0, singleFileOutput_1.resolveSingleFileOutput)({
|
|
497
499
|
cliOutDir: options.outDir,
|
|
498
500
|
cwd,
|
|
499
501
|
file,
|
|
502
|
+
passthrough: options.passthrough,
|
|
500
503
|
tsconfig: options.tsconfig,
|
|
501
504
|
})
|
|
502
505
|
: undefined;
|
|
@@ -505,6 +508,8 @@ function runSingleFile(options) {
|
|
|
505
508
|
checkers: options.checkers,
|
|
506
509
|
cwd,
|
|
507
510
|
file,
|
|
511
|
+
onProjectInputs: options.onProjectInputs,
|
|
512
|
+
onWatchInputs: options.onWatchInputs,
|
|
508
513
|
out,
|
|
509
514
|
passthrough: options.passthrough,
|
|
510
515
|
singleThreaded: options.singleThreaded,
|
|
@@ -536,80 +541,6 @@ function singleFileShouldEmit(options, cwd, file) {
|
|
|
536
541
|
});
|
|
537
542
|
return project.compilerOptions.noEmit !== true;
|
|
538
543
|
}
|
|
539
|
-
function resolveSingleFileOut(opts) {
|
|
540
|
-
const jsBasename = node_path_1.default.basename(opts.file).replace(/\.[cm]?tsx?$/i, "") +
|
|
541
|
-
singleFileJsExtension(opts.file);
|
|
542
|
-
// Explicit CLI --outDir wins. Mirrors the CWD-relative source layout under
|
|
543
|
-
// the requested directory so existing single-file invocations don't shift.
|
|
544
|
-
if (opts.cliOutDir) {
|
|
545
|
-
const relative = node_path_1.default.relative(opts.cwd, opts.file);
|
|
546
|
-
const jsRelative = relative.slice(0, relative.length - node_path_1.default.extname(relative).length) +
|
|
547
|
-
singleFileJsExtension(opts.file);
|
|
548
|
-
return node_path_1.default.resolve(opts.cwd, opts.cliOutDir, jsRelative);
|
|
549
|
-
}
|
|
550
|
-
// No CLI override: honor tsconfig's outDir so `ttsc src/foo.ts` lands the
|
|
551
|
-
// emitted JS at `<outDir>/<relative-from-rootDir>.js` instead of dropping
|
|
552
|
-
// it next to the source file. This matches how project mode emits and how
|
|
553
|
-
// `tsc <file>` would behave with the same tsconfig.
|
|
554
|
-
const projectOutDir = readProjectOutDir({
|
|
555
|
-
cwd: opts.cwd,
|
|
556
|
-
file: opts.file,
|
|
557
|
-
tsconfig: opts.tsconfig,
|
|
558
|
-
});
|
|
559
|
-
if (projectOutDir !== null) {
|
|
560
|
-
const fromRoot = node_path_1.default.relative(projectOutDir.rootDir, opts.file);
|
|
561
|
-
if (fromRoot !== "" && !isOutsideSingleFileLayout(fromRoot)) {
|
|
562
|
-
const jsRelative = fromRoot.slice(0, fromRoot.length - node_path_1.default.extname(fromRoot).length) +
|
|
563
|
-
singleFileJsExtension(opts.file);
|
|
564
|
-
return node_path_1.default.resolve(projectOutDir.outDir, jsRelative);
|
|
565
|
-
}
|
|
566
|
-
return node_path_1.default.resolve(projectOutDir.outDir, jsBasename);
|
|
567
|
-
}
|
|
568
|
-
// Last resort (no tsconfig outDir at all): emit next to the source. This
|
|
569
|
-
// preserves the legacy `ttsc <file.ts>` → `<file.js>` behavior for projects
|
|
570
|
-
// that intentionally don't configure outDir.
|
|
571
|
-
return opts.file.replace(/\.[cm]?tsx?$/i, singleFileJsExtension(opts.file));
|
|
572
|
-
}
|
|
573
|
-
function readProjectOutDir(opts) {
|
|
574
|
-
try {
|
|
575
|
-
const project = (0, readProjectConfig_1.readProjectConfig)({
|
|
576
|
-
cwd: opts.cwd,
|
|
577
|
-
file: opts.file,
|
|
578
|
-
tsconfig: opts.tsconfig,
|
|
579
|
-
});
|
|
580
|
-
const outDir = project.compilerOptions.outDir;
|
|
581
|
-
if (typeof outDir !== "string" || outDir.length === 0) {
|
|
582
|
-
return null;
|
|
583
|
-
}
|
|
584
|
-
const rawRoot = project.compilerOptions.rootDir;
|
|
585
|
-
const rootDir = typeof rawRoot === "string" && rawRoot.length !== 0
|
|
586
|
-
? node_path_1.default.isAbsolute(rawRoot)
|
|
587
|
-
? rawRoot
|
|
588
|
-
: node_path_1.default.resolve(project.root, rawRoot)
|
|
589
|
-
: project.root;
|
|
590
|
-
return { outDir, rootDir };
|
|
591
|
-
}
|
|
592
|
-
catch {
|
|
593
|
-
// Missing or unreadable tsconfig: fall back to the legacy behavior so
|
|
594
|
-
// `ttsc <file>` still works outside a configured project.
|
|
595
|
-
return null;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
function isOutsideSingleFileLayout(relative) {
|
|
599
|
-
return (relative === ".." ||
|
|
600
|
-
relative.startsWith(`..${node_path_1.default.sep}`) ||
|
|
601
|
-
node_path_1.default.isAbsolute(relative));
|
|
602
|
-
}
|
|
603
|
-
function singleFileJsExtension(file) {
|
|
604
|
-
switch (node_path_1.default.extname(file).toLowerCase()) {
|
|
605
|
-
case ".mts":
|
|
606
|
-
return ".mjs";
|
|
607
|
-
case ".cts":
|
|
608
|
-
return ".cjs";
|
|
609
|
-
default:
|
|
610
|
-
return ".js";
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
544
|
function runWatch(options, checkOnly) {
|
|
614
545
|
const cwd = node_path_1.default.resolve(options.cwd ?? process.cwd());
|
|
615
546
|
let topology;
|
|
@@ -619,6 +550,9 @@ function runWatch(options, checkOnly) {
|
|
|
619
550
|
onWatchInputs: (inputs) => {
|
|
620
551
|
topology?.setExtraInputs(inputs);
|
|
621
552
|
},
|
|
553
|
+
onProjectInputs: (inputs) => {
|
|
554
|
+
topology?.setProjectInputs(inputs);
|
|
555
|
+
},
|
|
622
556
|
quiet: true,
|
|
623
557
|
};
|
|
624
558
|
const root = node_path_1.default.dirname((0, resolveProjectConfig_1.resolveProjectConfig)({
|
|
@@ -628,11 +562,14 @@ function runWatch(options, checkOnly) {
|
|
|
628
562
|
let running = false;
|
|
629
563
|
let rerun = false;
|
|
630
564
|
let timer = null;
|
|
565
|
+
const resident = invocation.files.length === 0 ? new runBuild_1.ResidentCheckWatchSession() : undefined;
|
|
566
|
+
const pendingChanges = new PendingResidentCheckWatchChanges();
|
|
631
567
|
// Tracks the most recent build's exit code so the watch session can exit
|
|
632
568
|
// non-zero when its latest rebuild failed, instead of always reporting 0.
|
|
633
569
|
let lastStatus = 0;
|
|
634
|
-
const runOnce = () => {
|
|
570
|
+
const runOnce = async () => {
|
|
635
571
|
running = true;
|
|
572
|
+
const change = pendingChanges.take();
|
|
636
573
|
let completed = false;
|
|
637
574
|
try {
|
|
638
575
|
if (!options.preserveWatchOutput) {
|
|
@@ -645,22 +582,38 @@ function runWatch(options, checkOnly) {
|
|
|
645
582
|
// to invalid JSON, single-file invariant). Catch here so a throwing
|
|
646
583
|
// rebuild reports a clear failure and the watcher keeps running instead
|
|
647
584
|
// of crashing the process with an uncaught exception.
|
|
648
|
-
const status = invocation.files.length !== 0
|
|
649
|
-
? runSingleFile(invocation)
|
|
650
|
-
: (() => {
|
|
651
|
-
const
|
|
585
|
+
const status = await (invocation.files.length !== 0
|
|
586
|
+
? Promise.resolve(runSingleFile(invocation))
|
|
587
|
+
: (async () => {
|
|
588
|
+
const buildOptions = checkOnly
|
|
589
|
+
? { ...invocation, emit: false }
|
|
590
|
+
: invocation;
|
|
591
|
+
const result = resident === undefined
|
|
592
|
+
? (0, runBuild_1.runBuild)(buildOptions)
|
|
593
|
+
: await resident.run(buildOptions, change);
|
|
594
|
+
// Watch output is one ordered stream. The diagnostics a build
|
|
595
|
+
// produced and the "[ttsc] watch build …" marker that concludes
|
|
596
|
+
// them reach a single merged reader, and on two pipes the OS may
|
|
597
|
+
// hand it the marker first — a reader that counts the build on the
|
|
598
|
+
// marker then snapshots before the diagnostics land and sees an
|
|
599
|
+
// empty build. The rebuilding banner, the screen clear, and the
|
|
600
|
+
// marker are already on stdout, so the diagnostics join them there,
|
|
601
|
+
// the way tsc --watch prints everything to one place.
|
|
652
602
|
if (result.stdout)
|
|
653
603
|
process.stdout.write(result.stdout);
|
|
654
604
|
if (result.stderr)
|
|
655
|
-
process.
|
|
605
|
+
process.stdout.write(result.stderr);
|
|
656
606
|
return result.status;
|
|
657
|
-
})();
|
|
607
|
+
})());
|
|
658
608
|
lastStatus = status;
|
|
659
609
|
completed = true;
|
|
660
610
|
process.stdout.write(`[ttsc] ${status === 0 ? "watch build complete" : "watch build failed"}\n`);
|
|
661
611
|
}
|
|
662
612
|
catch (error) {
|
|
663
|
-
|
|
613
|
+
// Same ordered-stream rule as the build path: the failure text goes on
|
|
614
|
+
// the stream the marker below uses, so a merged reader sees them in order
|
|
615
|
+
// rather than racing two pipes.
|
|
616
|
+
process.stdout.write(`${formatError(error)}\n`);
|
|
664
617
|
lastStatus = lastStatus === 0 ? 2 : lastStatus;
|
|
665
618
|
process.stdout.write(`[ttsc] watch build failed\n`);
|
|
666
619
|
}
|
|
@@ -684,27 +637,37 @@ function runWatch(options, checkOnly) {
|
|
|
684
637
|
trigger();
|
|
685
638
|
}
|
|
686
639
|
};
|
|
687
|
-
const trigger = () => {
|
|
640
|
+
const trigger = (change, reload = false) => {
|
|
641
|
+
pendingChanges.push(change, reload);
|
|
688
642
|
if (running) {
|
|
689
643
|
rerun = true;
|
|
690
644
|
return;
|
|
691
645
|
}
|
|
692
646
|
if (timer)
|
|
693
647
|
clearTimeout(timer);
|
|
694
|
-
timer = setTimeout(runOnce, 60);
|
|
648
|
+
timer = setTimeout(() => void runOnce(), 60);
|
|
695
649
|
};
|
|
696
650
|
topology = new watchTopology_1.WatchTopology(invocation, {
|
|
697
651
|
onError: (location, error) => {
|
|
698
652
|
process.stderr.write(`[ttsc] watch error on ${node_path_1.default.relative(cwd, location) || "."}: ${formatError(error)}\n`);
|
|
699
653
|
},
|
|
700
|
-
onInputChange:
|
|
701
|
-
|
|
654
|
+
onInputChange: (change) => {
|
|
655
|
+
debugWatchInputs(`change ${change.kind}${change.invalidate === true ? " invalidate" : ""} ${change.path === undefined
|
|
656
|
+
? "(unnamed)"
|
|
657
|
+
: node_path_1.default.relative(cwd, change.path)}`);
|
|
658
|
+
trigger(change);
|
|
659
|
+
},
|
|
660
|
+
onProjectInputWatchRoots: (roots) => {
|
|
661
|
+
debugWatchInputs(`roots ${JSON.stringify(roots.map((root) => node_path_1.default.relative(cwd, root) || "."))}`);
|
|
662
|
+
},
|
|
663
|
+
onTopologyChange: () => trigger(undefined, true),
|
|
702
664
|
});
|
|
703
665
|
topology.refresh(false);
|
|
704
666
|
const close = () => {
|
|
705
667
|
if (timer)
|
|
706
668
|
clearTimeout(timer);
|
|
707
669
|
topology?.close();
|
|
670
|
+
resident?.dispose();
|
|
708
671
|
};
|
|
709
672
|
process.on("SIGINT", () => {
|
|
710
673
|
close();
|
|
@@ -716,7 +679,7 @@ function runWatch(options, checkOnly) {
|
|
|
716
679
|
});
|
|
717
680
|
process.stdout.write(`[ttsc] watching ${node_path_1.default.relative(cwd, root) || "."}\n`);
|
|
718
681
|
try {
|
|
719
|
-
runOnce();
|
|
682
|
+
void runOnce();
|
|
720
683
|
}
|
|
721
684
|
catch (error) {
|
|
722
685
|
// runOnce already swallows build throws, but guard against any unforeseen
|
|
@@ -729,11 +692,81 @@ function runWatch(options, checkOnly) {
|
|
|
729
692
|
}
|
|
730
693
|
return toExitCode(lastStatus);
|
|
731
694
|
}
|
|
695
|
+
/**
|
|
696
|
+
* Coalesces filesystem events until the next resident check-watch cycle.
|
|
697
|
+
*
|
|
698
|
+
* A full reload dominates every narrower signal. Program invalidation remains
|
|
699
|
+
* distinct so a project-input module creation/deletion can cold-load the
|
|
700
|
+
* Program without discarding the selected execution or restarting the sidecar.
|
|
701
|
+
*/
|
|
702
|
+
class PendingResidentCheckWatchChanges {
|
|
703
|
+
changed = new Set();
|
|
704
|
+
external = new Set();
|
|
705
|
+
invalidate = false;
|
|
706
|
+
reload = false;
|
|
707
|
+
push(change, reload = false) {
|
|
708
|
+
if (reload || change?.kind === "config" || change?.kind === "plugin") {
|
|
709
|
+
this.reload = true;
|
|
710
|
+
this.invalidate = false;
|
|
711
|
+
this.changed.clear();
|
|
712
|
+
this.external.clear();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
if (this.reload)
|
|
716
|
+
return;
|
|
717
|
+
if (change?.invalidate === true)
|
|
718
|
+
this.invalidate = true;
|
|
719
|
+
if (change?.path === undefined) {
|
|
720
|
+
if (change?.kind === "compiler") {
|
|
721
|
+
this.reload = true;
|
|
722
|
+
this.invalidate = false;
|
|
723
|
+
this.changed.clear();
|
|
724
|
+
this.external.clear();
|
|
725
|
+
}
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
this.changed.add(change.path);
|
|
729
|
+
if (change.kind === "project")
|
|
730
|
+
this.external.add(change.path);
|
|
731
|
+
}
|
|
732
|
+
take() {
|
|
733
|
+
const change = {
|
|
734
|
+
...(this.reload ? { reload: true } : {}),
|
|
735
|
+
...(this.invalidate ? { invalidate: true } : {}),
|
|
736
|
+
...(this.changed.size === 0 ? {} : { changed: [...this.changed].sort() }),
|
|
737
|
+
...(this.external.size === 0
|
|
738
|
+
? {}
|
|
739
|
+
: { external: [...this.external].sort() }),
|
|
740
|
+
};
|
|
741
|
+
this.reload = false;
|
|
742
|
+
this.invalidate = false;
|
|
743
|
+
this.changed.clear();
|
|
744
|
+
this.external.clear();
|
|
745
|
+
return change;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
exports.PendingResidentCheckWatchChanges = PendingResidentCheckWatchChanges;
|
|
732
749
|
// Coerces a build status into a valid process exit code: 0 stays 0, any
|
|
733
750
|
// non-zero (or non-finite) status collapses to 1 so the session signals failure.
|
|
734
751
|
function toExitCode(status) {
|
|
735
752
|
return Number.isInteger(status) && status >= 0 && status <= 255 ? status : 1;
|
|
736
753
|
}
|
|
754
|
+
/**
|
|
755
|
+
* Opt-in report of what the project-input layer is watching and announcing.
|
|
756
|
+
*
|
|
757
|
+
* A watch that goes quiet is indistinguishable from a watch with nothing to
|
|
758
|
+
* say: the build simply never runs again, and the transcript that survives says
|
|
759
|
+
* only that. This separates the two — the roots line states what is covered,
|
|
760
|
+
* and a change line states what was announced — so a missing rebuild can be
|
|
761
|
+
* attributed to the watch that never saw the file or to the decision that
|
|
762
|
+
* declined to report it. Silent unless asked, and on the same ordered stream as
|
|
763
|
+
* the rest of the watch output so it interleaves with the builds it explains.
|
|
764
|
+
*/
|
|
765
|
+
function debugWatchInputs(message) {
|
|
766
|
+
if (!process.env.TTSC_WATCH_DEBUG_INPUTS)
|
|
767
|
+
return;
|
|
768
|
+
process.stdout.write(`[ttsc:debug] ${message}\n`);
|
|
769
|
+
}
|
|
737
770
|
function formatError(error) {
|
|
738
771
|
if (error instanceof Error) {
|
|
739
772
|
return error.message;
|