ttsc 0.22.0 → 0.23.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/go.mod +1 -1
- package/internal/lspserver/lsp_launcher_reload_directory_fingerprint_parity_windows_test.go +94 -0
- package/internal/lspserver/lsp_project_input_glob_path_other.go +16 -0
- package/internal/lspserver/lsp_project_input_glob_path_windows.go +36 -0
- package/internal/lspserver/lsp_project_input_path_key_other.go +17 -0
- package/internal/lspserver/lsp_project_input_path_key_windows.go +228 -0
- package/internal/lspserver/lsp_project_input_path_key_windows_test.go +460 -0
- package/internal/lspserver/lsp_project_inputs.go +117 -49
- package/internal/lspserver/lsp_reload_directories_separate_glob_territory_test.go +181 -0
- package/lib/flags/schema.js +1 -1
- package/lib/flags/schema.js.map +1 -1
- package/lib/internal/projectInputPathIdentity.d.ts +13 -5
- package/lib/internal/projectInputPathIdentity.js +95 -41
- package/lib/internal/projectInputPathIdentity.js.map +1 -1
- package/lib/internal/windowsDirectoryCaseSensitivity.d.ts +10 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js +34 -0
- package/lib/internal/windowsDirectoryCaseSensitivity.js.map +1 -0
- package/lib/launcher/internal/runTtsc.js +5 -0
- package/lib/launcher/internal/runTtsc.js.map +1 -1
- package/lib/launcher/internal/runTtscserver.js +59 -8
- package/lib/launcher/internal/runTtscserver.js.map +1 -1
- package/lib/launcher/internal/runTtsx.js +61 -7
- package/lib/launcher/internal/runTtsx.js.map +1 -1
- package/lib/launcher/internal/runtimeHooks.d.ts +6 -5
- package/lib/launcher/internal/runtimeHooks.js +7 -15
- package/lib/launcher/internal/runtimeHooks.js.map +1 -1
- package/lib/launcher/internal/watchTopology.d.ts +82 -8
- package/lib/launcher/internal/watchTopology.js +535 -187
- package/lib/launcher/internal/watchTopology.js.map +1 -1
- package/lib/plugin/internal/descriptorProcessFailure.d.ts +32 -0
- package/lib/plugin/internal/descriptorProcessFailure.js +73 -0
- package/lib/plugin/internal/descriptorProcessFailure.js.map +1 -0
- package/lib/plugin/internal/loadProjectPlugins.js +29 -8
- package/lib/plugin/internal/loadProjectPlugins.js.map +1 -1
- package/package.json +12 -8
- package/src/flags/schema.ts +2 -1
- package/src/internal/projectInputPathIdentity.ts +150 -46
- package/src/internal/windowsDirectoryCaseSensitivity.ts +33 -0
- package/src/launcher/internal/runTtsc.ts +7 -0
- package/src/launcher/internal/runTtscserver.ts +81 -11
- package/src/launcher/internal/runTtsx.ts +87 -10
- package/src/launcher/internal/runtimeHooks.ts +16 -16
- package/src/launcher/internal/watchTopology.ts +679 -208
- package/src/plugin/internal/descriptorProcessFailure.ts +100 -0
- package/src/plugin/internal/loadProjectPlugins.ts +41 -8
- package/tools/shim_audit/baseline.json +0 -9
|
@@ -8,7 +8,11 @@ import { createNativeProjectContextArgs } from "../../compiler/internal/project/
|
|
|
8
8
|
import { readProjectConfig } from "../../compiler/internal/project/readProjectConfig";
|
|
9
9
|
import { resolveBinary } from "../../compiler/internal/resolveBinary";
|
|
10
10
|
import { resolveTsgo } from "../../compiler/internal/resolveTsgo";
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type ProjectInputPathIdentityContext,
|
|
13
|
+
createProjectInputPathIdentityContext,
|
|
14
|
+
resolveProjectInputPath,
|
|
15
|
+
} from "../../internal/projectInputPathIdentity";
|
|
12
16
|
import {
|
|
13
17
|
hasProjectPluginEntries,
|
|
14
18
|
loadProjectPlugins,
|
|
@@ -313,8 +317,7 @@ function loadLSPProjectPlugins(
|
|
|
313
317
|
binary: resolveBinary() ?? "",
|
|
314
318
|
cwd,
|
|
315
319
|
pluginConfigDir: pluginConfigOrigin,
|
|
316
|
-
|
|
317
|
-
tsconfig: project.path,
|
|
320
|
+
tsconfig: project.identity.logicalConfigPath,
|
|
318
321
|
})
|
|
319
322
|
: { nativePlugins: [], project };
|
|
320
323
|
}
|
|
@@ -472,9 +475,8 @@ export function fingerprintInitialLSPProjectInputSnapshot(
|
|
|
472
475
|
const reloadDirectoryDigests: Record<string, string> = {};
|
|
473
476
|
const reloadFileDigests: Record<string, string> = {};
|
|
474
477
|
for (const directory of snapshot.reloadDirectories ?? []) {
|
|
475
|
-
reloadDirectoryDigests[directory] =
|
|
476
|
-
|
|
477
|
-
);
|
|
478
|
+
reloadDirectoryDigests[directory] =
|
|
479
|
+
lspProjectInputReloadDirectoryDigest(directory);
|
|
478
480
|
}
|
|
479
481
|
for (const file of snapshot.reloadFiles ?? []) {
|
|
480
482
|
reloadFileDigests[file] = lspProjectInputFileDigest(
|
|
@@ -495,7 +497,7 @@ export function initialLSPProjectInputSnapshotIsCurrent(
|
|
|
495
497
|
(snapshot.reloadDirectories ?? []).every(
|
|
496
498
|
(directory) =>
|
|
497
499
|
snapshot.reloadDirectoryDigests[directory] ===
|
|
498
|
-
|
|
500
|
+
lspProjectInputReloadDirectoryDigest(directory),
|
|
499
501
|
) &&
|
|
500
502
|
(snapshot.reloadFiles ?? []).every(
|
|
501
503
|
(file) =>
|
|
@@ -524,16 +526,84 @@ function lspSelectionSignature(
|
|
|
524
526
|
});
|
|
525
527
|
}
|
|
526
528
|
|
|
527
|
-
function
|
|
529
|
+
function lspProjectInputReloadDirectoryDigest(location: string): string {
|
|
530
|
+
const identities = createProjectInputPathIdentityContext();
|
|
531
|
+
const identity = lspProjectInputPhysicalPathIdentity(location, identities);
|
|
532
|
+
return createHash("sha256")
|
|
533
|
+
.update(
|
|
534
|
+
Buffer.concat([
|
|
535
|
+
Buffer.from("directory\0"),
|
|
536
|
+
identity,
|
|
537
|
+
Buffer.from([0]),
|
|
538
|
+
Buffer.from(
|
|
539
|
+
lspProjectInputDirectoryTopologyDigest(
|
|
540
|
+
process.platform === "win32"
|
|
541
|
+
? identities.resolve(location).path
|
|
542
|
+
: identity,
|
|
543
|
+
),
|
|
544
|
+
),
|
|
545
|
+
]),
|
|
546
|
+
)
|
|
547
|
+
.digest("hex");
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
function lspProjectInputPhysicalPathIdentity(
|
|
551
|
+
location: string,
|
|
552
|
+
identities: ProjectInputPathIdentityContext,
|
|
553
|
+
): Buffer {
|
|
554
|
+
if (process.platform === "win32") {
|
|
555
|
+
return Buffer.from(
|
|
556
|
+
identities.resolve(location).path.replaceAll("\\", "/"),
|
|
557
|
+
"utf8",
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
let existing = path.resolve(location);
|
|
561
|
+
const missing: Buffer[] = [];
|
|
562
|
+
while (true) {
|
|
563
|
+
try {
|
|
564
|
+
const realpath = fs.realpathSync.native ?? fs.realpathSync;
|
|
565
|
+
let physical = realpath(Buffer.from(existing), {
|
|
566
|
+
encoding: "buffer",
|
|
567
|
+
});
|
|
568
|
+
for (const segment of missing) {
|
|
569
|
+
physical = Buffer.concat([
|
|
570
|
+
physical,
|
|
571
|
+
physical.at(-1) === 0x2f ? Buffer.alloc(0) : Buffer.from("/"),
|
|
572
|
+
segment,
|
|
573
|
+
]);
|
|
574
|
+
}
|
|
575
|
+
return physical;
|
|
576
|
+
} catch (error) {
|
|
577
|
+
if (
|
|
578
|
+
error instanceof Error &&
|
|
579
|
+
"code" in error &&
|
|
580
|
+
error.code !== "ENOENT" &&
|
|
581
|
+
error.code !== "ENOTDIR"
|
|
582
|
+
) {
|
|
583
|
+
throw error;
|
|
584
|
+
}
|
|
585
|
+
const parent = path.dirname(existing);
|
|
586
|
+
if (parent === existing) return Buffer.from(existing);
|
|
587
|
+
missing.unshift(Buffer.from(path.basename(existing)));
|
|
588
|
+
existing = parent;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function lspProjectInputDirectoryTopologyDigest(
|
|
594
|
+
location: string | Buffer,
|
|
595
|
+
): string {
|
|
528
596
|
const entries: Buffer[] = [];
|
|
529
597
|
try {
|
|
530
598
|
if (process.platform === "win32") {
|
|
531
|
-
|
|
599
|
+
const directory =
|
|
600
|
+
typeof location === "string" ? location : location.toString("utf8");
|
|
601
|
+
for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
|
|
532
602
|
let target = Buffer.alloc(0);
|
|
533
603
|
if (entry.isSymbolicLink()) {
|
|
534
604
|
try {
|
|
535
605
|
target = Buffer.from(
|
|
536
|
-
fs.readlinkSync(path.join(
|
|
606
|
+
fs.readlinkSync(path.join(directory, entry.name)),
|
|
537
607
|
"utf8",
|
|
538
608
|
);
|
|
539
609
|
} catch {
|
|
@@ -558,7 +628,7 @@ function lspProjectInputDirectoryDigest(location: string): string {
|
|
|
558
628
|
try {
|
|
559
629
|
target = fs.readlinkSync(
|
|
560
630
|
Buffer.concat([
|
|
561
|
-
Buffer.from(location),
|
|
631
|
+
Buffer.isBuffer(location) ? location : Buffer.from(location),
|
|
562
632
|
Buffer.from(path.sep),
|
|
563
633
|
entry.name,
|
|
564
634
|
]),
|
|
@@ -299,20 +299,52 @@ function runPreparedEntry(
|
|
|
299
299
|
sourceEntry,
|
|
300
300
|
...parsed.passthrough,
|
|
301
301
|
];
|
|
302
|
+
const evaluatorBoundary = readEvaluatorBoundary(process.env);
|
|
303
|
+
const runtimeEnv: NodeJS.ProcessEnv = {
|
|
304
|
+
...process.env,
|
|
305
|
+
NODE_OPTIONS: appendNodeOption(
|
|
306
|
+
process.env.NODE_OPTIONS,
|
|
307
|
+
`--require ${JSON.stringify(path.join(__dirname, "runtimeHookPreload.js"))}`,
|
|
308
|
+
),
|
|
309
|
+
TTSC_TSGO_BINARY: process.env.TTSC_TSGO_BINARY ?? tsgo,
|
|
310
|
+
TTSX_RUNTIME_MANIFEST: manifestPath,
|
|
311
|
+
};
|
|
312
|
+
delete runtimeEnv[TTSC_TTSX_EVALUATOR_DEADLINE_ENV];
|
|
313
|
+
delete runtimeEnv[TTSC_TTSX_EVALUATOR_MAX_BUFFER_ENV];
|
|
314
|
+
delete runtimeEnv[TTSC_TTSX_EVALUATOR_STATUS_FD_ENV];
|
|
315
|
+
|
|
302
316
|
const result = spawnSync(process.execPath, args, {
|
|
303
317
|
cwd,
|
|
304
|
-
env:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
318
|
+
env: runtimeEnv,
|
|
319
|
+
...(evaluatorBoundary
|
|
320
|
+
? {
|
|
321
|
+
killSignal: "SIGKILL" as const,
|
|
322
|
+
timeout: Math.max(
|
|
323
|
+
1,
|
|
324
|
+
Math.min(
|
|
325
|
+
MAX_CHILD_PROCESS_TIMEOUT_MS,
|
|
326
|
+
evaluatorBoundary.deadlineMs - Date.now(),
|
|
327
|
+
),
|
|
328
|
+
),
|
|
329
|
+
maxBuffer: evaluatorBoundary.maxBuffer,
|
|
330
|
+
stdio: ["inherit", "pipe", "pipe"] as const,
|
|
331
|
+
}
|
|
332
|
+
: { stdio: "inherit" as const }),
|
|
314
333
|
windowsHide: true,
|
|
315
334
|
});
|
|
335
|
+
const nestedCode = (result.error as NodeJS.ErrnoException | undefined)
|
|
336
|
+
?.code;
|
|
337
|
+
if (
|
|
338
|
+
evaluatorBoundary &&
|
|
339
|
+
(nestedCode === "ETIMEDOUT" || nestedCode === "ENOBUFS")
|
|
340
|
+
) {
|
|
341
|
+
reportEvaluatorFailure(evaluatorBoundary.statusFd, nestedCode);
|
|
342
|
+
return 1;
|
|
343
|
+
}
|
|
344
|
+
if (evaluatorBoundary) {
|
|
345
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
346
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
347
|
+
}
|
|
316
348
|
if (result.error) {
|
|
317
349
|
process.stderr.write(`${result.error.message}\n`);
|
|
318
350
|
return 1;
|
|
@@ -323,6 +355,51 @@ function runPreparedEntry(
|
|
|
323
355
|
}
|
|
324
356
|
}
|
|
325
357
|
|
|
358
|
+
interface TtsxEvaluatorBoundary {
|
|
359
|
+
deadlineMs: number;
|
|
360
|
+
maxBuffer: number;
|
|
361
|
+
statusFd: number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const TTSC_TTSX_EVALUATOR_DEADLINE_ENV = "TTSC_TTSX_EVALUATOR_DEADLINE_MS";
|
|
365
|
+
const TTSC_TTSX_EVALUATOR_MAX_BUFFER_ENV =
|
|
366
|
+
"TTSC_TTSX_EVALUATOR_MAX_BUFFER_BYTES";
|
|
367
|
+
const TTSC_TTSX_EVALUATOR_STATUS_FD_ENV = "TTSC_TTSX_EVALUATOR_STATUS_FD";
|
|
368
|
+
const TTSC_TTSX_EVALUATOR_STATUS_FD = 3;
|
|
369
|
+
const MAX_CHILD_PROCESS_TIMEOUT_MS = 2_147_483_647;
|
|
370
|
+
|
|
371
|
+
function readEvaluatorBoundary(
|
|
372
|
+
env: NodeJS.ProcessEnv,
|
|
373
|
+
): TtsxEvaluatorBoundary | undefined {
|
|
374
|
+
const deadlineMs = Number(env[TTSC_TTSX_EVALUATOR_DEADLINE_ENV]);
|
|
375
|
+
const maxBuffer = Number(env[TTSC_TTSX_EVALUATOR_MAX_BUFFER_ENV]);
|
|
376
|
+
const statusFd = Number(env[TTSC_TTSX_EVALUATOR_STATUS_FD_ENV]);
|
|
377
|
+
if (
|
|
378
|
+
!Number.isSafeInteger(deadlineMs) ||
|
|
379
|
+
deadlineMs <= 0 ||
|
|
380
|
+
!Number.isSafeInteger(maxBuffer) ||
|
|
381
|
+
maxBuffer <= 0 ||
|
|
382
|
+
maxBuffer > MAX_CHILD_PROCESS_TIMEOUT_MS ||
|
|
383
|
+
statusFd !== TTSC_TTSX_EVALUATOR_STATUS_FD
|
|
384
|
+
) {
|
|
385
|
+
return undefined;
|
|
386
|
+
}
|
|
387
|
+
return { deadlineMs, maxBuffer, statusFd };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function reportEvaluatorFailure(
|
|
391
|
+
statusFd: number,
|
|
392
|
+
code: "ENOBUFS" | "ETIMEDOUT",
|
|
393
|
+
): void {
|
|
394
|
+
try {
|
|
395
|
+
fs.writeSync(statusFd, code);
|
|
396
|
+
} catch {
|
|
397
|
+
// The boundary is an internal parent/child protocol. If a standalone ttsx
|
|
398
|
+
// caller supplied only the environment variables, preserve the ordinary
|
|
399
|
+
// non-zero bounded-process result instead of replacing it with an fd error.
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
326
403
|
function removeRuntimeOutput(directory: string): void {
|
|
327
404
|
try {
|
|
328
405
|
fs.rmSync(directory, { force: true, recursive: true });
|
|
@@ -15,6 +15,10 @@ import { resolveEmittedJavaScript } from "../../compiler/internal/resolveEmitted
|
|
|
15
15
|
import { resolveTsgo } from "../../compiler/internal/resolveTsgo";
|
|
16
16
|
import { runBuild } from "../../compiler/internal/runBuild";
|
|
17
17
|
import { outputText, spawnNative } from "../../compiler/internal/spawnNative";
|
|
18
|
+
import {
|
|
19
|
+
type FilesystemPathIdentityContext,
|
|
20
|
+
createFilesystemPathIdentityContext,
|
|
21
|
+
} from "../../internal/projectInputPathIdentity";
|
|
18
22
|
import { inlineServedSourceMap } from "./servedSourceMap";
|
|
19
23
|
|
|
20
24
|
/**
|
|
@@ -1143,23 +1147,19 @@ function serveEntryEmit(real: string): ServedSource | null {
|
|
|
1143
1147
|
* Both sides are normalized to native separators first: a manifest `rootDir`
|
|
1144
1148
|
* arrives slash-normalized from the synthesized tsconfig (`C:/` on Windows)
|
|
1145
1149
|
* while `real` paths are native, and a raw prefix comparison across the two
|
|
1146
|
-
* forms silently never matches.
|
|
1147
|
-
*
|
|
1148
|
-
*
|
|
1149
|
-
*
|
|
1150
|
+
* forms silently never matches. Filesystem identity preserves ordinary Windows
|
|
1151
|
+
* aliases while keeping case-distinct paths under an opted-in directory
|
|
1152
|
+
* separate. Exported for direct exercise by the ttsx e2e suite — spawned runs
|
|
1153
|
+
* cannot pin both Windows case-semantics branches on CI.
|
|
1150
1154
|
*/
|
|
1151
|
-
export function isWithin(
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
return true;
|
|
1160
|
-
}
|
|
1161
|
-
const prefix = dir.endsWith(path.sep) ? dir : dir + path.sep;
|
|
1162
|
-
return target.startsWith(prefix);
|
|
1155
|
+
export function isWithin(
|
|
1156
|
+
real: string,
|
|
1157
|
+
directory: string,
|
|
1158
|
+
identities: FilesystemPathIdentityContext = createFilesystemPathIdentityContext(
|
|
1159
|
+
{ throwOnRealpathError: false },
|
|
1160
|
+
),
|
|
1161
|
+
): boolean {
|
|
1162
|
+
return identities.isWithin(directory, real);
|
|
1163
1163
|
}
|
|
1164
1164
|
|
|
1165
1165
|
/**
|