@orkestrel/probe 0.0.12 → 0.0.14
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/README.md +22 -11
- package/dist/src/core/index.cjs +43 -17
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +148 -96
- package/dist/src/core/index.d.ts +148 -96
- package/dist/src/core/index.js +43 -18
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +755 -327
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +473 -149
- package/dist/src/server/index.d.ts +473 -149
- package/dist/src/server/index.js +750 -328
- package/dist/src/server/index.js.map +1 -1
- package/package.json +18 -19
|
@@ -12,7 +12,7 @@ let _orkestrel_timeout = require("@orkestrel/timeout");
|
|
|
12
12
|
let _orkestrel_lsp = require("@orkestrel/lsp");
|
|
13
13
|
let _orkestrel_lsp_server = require("@orkestrel/lsp/server");
|
|
14
14
|
let node_stream = require("node:stream");
|
|
15
|
-
let
|
|
15
|
+
let node_child_process = require("node:child_process");
|
|
16
16
|
let _orkestrel_mcp = require("@orkestrel/mcp");
|
|
17
17
|
let _orkestrel_mcp_server = require("@orkestrel/mcp/server");
|
|
18
18
|
let _orkestrel_tool = require("@orkestrel/tool");
|
|
@@ -325,6 +325,65 @@ function relativeWorkspaceMessage(workspace, message) {
|
|
|
325
325
|
return text;
|
|
326
326
|
}
|
|
327
327
|
/**
|
|
328
|
+
* Scans the plain-text output of one compiler run into the diagnostics it reported.
|
|
329
|
+
*
|
|
330
|
+
* @remarks
|
|
331
|
+
* Reads the shape `tsc --pretty false` prints: `path(line,column): error TSnnnn: message` for a
|
|
332
|
+
* diagnostic about a file, and `error TSnnnn: message` for one about a project. A line that carries
|
|
333
|
+
* neither prefix and begins with whitespace is an elaboration of the diagnostic above it and joins
|
|
334
|
+
* that message with a newline; any other line is skipped, so a runtime's own failure text yields no
|
|
335
|
+
* diagnostic and a caller reads that as the fault it is. The compiler counts lines and columns from
|
|
336
|
+
* one and columns in UTF-16 code units, so each location is lowered to the zero-based UTF-16 point
|
|
337
|
+
* `Diagnostic.range` fixes and `end` resolves to `start`, which is the extent this output carries.
|
|
338
|
+
* A warning reads as a diagnostic, because the workspace's own gate reports it the same way. The
|
|
339
|
+
* `TS\d+` token that follows `error` or `warning` is what recognizes the line; nothing is captured
|
|
340
|
+
* from it, because `Diagnostic` carries no field for it.
|
|
341
|
+
*
|
|
342
|
+
* @param text - The compiler's output
|
|
343
|
+
* @returns One record per reported diagnostic, in the compiler's own order
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* ```ts
|
|
347
|
+
* scanDiagnostics("src/a.ts(1,14): error TS2322: Type 'string' is not assignable to type 'number'.")
|
|
348
|
+
* // [{ path: 'src/a.ts', range: { start: { line: 0, character: 13 }, end: { line: 0, character: 13 } }, message: "Type 'string' is not assignable to type 'number'." }]
|
|
349
|
+
* scanDiagnostics('error TS5058: The specified path does not exist.')
|
|
350
|
+
* // [{ message: 'The specified path does not exist.' }]
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
353
|
+
function scanDiagnostics(text) {
|
|
354
|
+
const diagnostics = [];
|
|
355
|
+
for (const line of text.split(/\r\n|\n/)) {
|
|
356
|
+
const located = /^(.+)\((\d+),(\d+)\): (?:error|warning) TS\d+: (.*)$/u.exec(line);
|
|
357
|
+
if (located !== null) {
|
|
358
|
+
const point = {
|
|
359
|
+
line: Number.parseInt(located[2] ?? "1", 10) - 1,
|
|
360
|
+
character: Number.parseInt(located[3] ?? "1", 10) - 1
|
|
361
|
+
};
|
|
362
|
+
diagnostics.push({
|
|
363
|
+
path: normalizePath(located[1] ?? ""),
|
|
364
|
+
range: {
|
|
365
|
+
start: point,
|
|
366
|
+
end: point
|
|
367
|
+
},
|
|
368
|
+
message: located[4] ?? ""
|
|
369
|
+
});
|
|
370
|
+
continue;
|
|
371
|
+
}
|
|
372
|
+
const bare = /^(?:error|warning) TS\d+: (.*)$/u.exec(line);
|
|
373
|
+
if (bare !== null) {
|
|
374
|
+
diagnostics.push({ message: bare[1] ?? "" });
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
const previous = diagnostics.at(-1);
|
|
378
|
+
if (previous === void 0 || !/^\s+\S/u.test(line)) continue;
|
|
379
|
+
diagnostics[diagnostics.length - 1] = {
|
|
380
|
+
...previous,
|
|
381
|
+
message: `${previous.message}\n${line}`
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
return diagnostics;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
328
387
|
* Resolves one installed module from the target workspace.
|
|
329
388
|
*
|
|
330
389
|
* @param workspace - The target workspace root
|
|
@@ -350,18 +409,35 @@ function resolveWorkspaceModule(workspace, specifier) {
|
|
|
350
409
|
cause: error
|
|
351
410
|
});
|
|
352
411
|
}
|
|
353
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Loads the installed `vitest/node` module from a target workspace.
|
|
414
|
+
*
|
|
415
|
+
* @remarks
|
|
416
|
+
* The only tool this package drives in process. The compiler is a process this package spawns
|
|
417
|
+
* rather than a module it loads, so `typescript` is resolved through {@link resolveWorkspaceBinary}
|
|
418
|
+
* instead.
|
|
419
|
+
*
|
|
420
|
+
* @param workspace - The target workspace root
|
|
421
|
+
* @returns The installed `vitest/node` module
|
|
422
|
+
* @throws When the workspace cannot load `vitest/node`
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* ```ts
|
|
426
|
+
* const vitest = loadWorkspaceVitest(process.cwd())
|
|
427
|
+
* typeof vitest.createVitest === 'function' // true
|
|
428
|
+
* ```
|
|
429
|
+
*/
|
|
430
|
+
function loadWorkspaceVitest(workspace) {
|
|
354
431
|
const outcome = (0, _orkestrel_contract.attempt)(() => {
|
|
355
|
-
|
|
356
|
-
return specifier === "typescript" ? require$1("typescript") : require$1("vitest/node");
|
|
432
|
+
return (0, node_module.createRequire)((0, node_path.resolve)(workspace, "package.json"))("vitest/node");
|
|
357
433
|
});
|
|
358
434
|
if (outcome.success) return outcome.value;
|
|
359
435
|
const error = outcome.error;
|
|
360
436
|
const missing = readFaultCode(error) === "MODULE_NOT_FOUND";
|
|
361
|
-
throw new _src_core.ProbeError(
|
|
437
|
+
throw new _src_core.ProbeError("The workspace cannot load vitest/node", {
|
|
362
438
|
origin: "workspace",
|
|
363
439
|
code: missing ? "missing" : "malformed",
|
|
364
|
-
context: { name:
|
|
440
|
+
context: { name: "vitest/node" },
|
|
365
441
|
cause: error
|
|
366
442
|
});
|
|
367
443
|
}
|
|
@@ -434,20 +510,26 @@ function readWorkspaceManifest(workspace, name) {
|
|
|
434
510
|
/**
|
|
435
511
|
* Resolves a package's portable JavaScript binary from the target workspace.
|
|
436
512
|
*
|
|
513
|
+
* @remarks
|
|
514
|
+
* A package publishes its binaries under keys of its own choosing, and that key is often not the
|
|
515
|
+
* package name: `typescript` publishes `tsc`. Name the key as `command` wherever the two differ.
|
|
516
|
+
* Default: the package name.
|
|
517
|
+
*
|
|
437
518
|
* @param workspace - The target workspace root
|
|
438
|
-
* @param name - The installed package name
|
|
519
|
+
* @param name - The installed package name
|
|
520
|
+
* @param command - The `bin` key to read. Default: `name`
|
|
439
521
|
* @returns The absolute JavaScript entry named by the package's `bin` field
|
|
440
|
-
* @throws When the package does not publish a binary under the requested
|
|
522
|
+
* @throws When the package does not publish a binary under the requested key
|
|
441
523
|
*
|
|
442
524
|
* @example
|
|
443
525
|
* ```ts
|
|
444
526
|
* relativeWorkspaceFile(process.cwd(), resolveWorkspaceBinary(process.cwd(), 'oxlint'))
|
|
445
527
|
* // 'node_modules/oxlint/bin/oxlint'
|
|
446
|
-
* resolveWorkspaceBinary(process.cwd(), 'typescript')
|
|
447
|
-
* //
|
|
528
|
+
* relativeWorkspaceFile(process.cwd(), resolveWorkspaceBinary(process.cwd(), 'typescript', 'tsc'))
|
|
529
|
+
* // 'node_modules/typescript/bin/tsc'
|
|
448
530
|
* ```
|
|
449
531
|
*/
|
|
450
|
-
function resolveWorkspaceBinary(workspace, name) {
|
|
532
|
+
function resolveWorkspaceBinary(workspace, name, command = name) {
|
|
451
533
|
const manifest = readWorkspaceManifest(workspace, name);
|
|
452
534
|
const bin = manifest.contents.bin;
|
|
453
535
|
if (bin === void 0) throw new _src_core.ProbeError(`${name} does not publish a bin field`, {
|
|
@@ -456,13 +538,13 @@ function resolveWorkspaceBinary(workspace, name) {
|
|
|
456
538
|
context: { name }
|
|
457
539
|
});
|
|
458
540
|
if (typeof bin === "string") return (0, node_path.resolve)(manifest.path, "..", bin);
|
|
459
|
-
if (!(0, _orkestrel_contract.isRecord)(bin) || !(
|
|
541
|
+
if (!(0, _orkestrel_contract.isRecord)(bin) || !(command in bin)) throw new _src_core.ProbeError(`${name} does not publish the ${command} binary`, {
|
|
460
542
|
origin: "workspace",
|
|
461
543
|
code: "missing",
|
|
462
544
|
context: { name }
|
|
463
545
|
});
|
|
464
|
-
const entry = bin[
|
|
465
|
-
if (typeof entry !== "string") throw new _src_core.ProbeError(`${name} publishes an invalid ${
|
|
546
|
+
const entry = bin[command];
|
|
547
|
+
if (typeof entry !== "string") throw new _src_core.ProbeError(`${name} publishes an invalid ${command} binary`, {
|
|
466
548
|
origin: "workspace",
|
|
467
549
|
code: "malformed",
|
|
468
550
|
context: {
|
|
@@ -563,6 +645,39 @@ function buildRevisionPath(workspace, path, revision) {
|
|
|
563
645
|
return `${extension === "" ? file : file.slice(0, -extension.length)}.probe-${revision}${extension}`;
|
|
564
646
|
}
|
|
565
647
|
/**
|
|
648
|
+
* Filters one issue list to the distinct issues it carries, in the order they arrived.
|
|
649
|
+
*
|
|
650
|
+
* @remarks
|
|
651
|
+
* A stage that reads one candidate through more than one tool run reports that candidate's
|
|
652
|
+
* diagnostic once per run. Two issues are the same issue when their party, path, message, and range
|
|
653
|
+
* all agree, because those are every value an issue carries.
|
|
654
|
+
*
|
|
655
|
+
* @param issues - The issues to filter
|
|
656
|
+
* @returns The distinct issues, in the order the list carried them
|
|
657
|
+
*
|
|
658
|
+
* @example
|
|
659
|
+
* ```ts
|
|
660
|
+
* const issue: Issue = { origin: 'claimant', path: 'src/core/greeting.ts', message: 'not assignable' }
|
|
661
|
+
* filterUniqueIssues([issue, { ...issue }]).length // 1
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
function filterUniqueIssues(issues) {
|
|
665
|
+
const seen = /* @__PURE__ */ new Set();
|
|
666
|
+
const distinct = [];
|
|
667
|
+
for (const issue of issues) {
|
|
668
|
+
const key = JSON.stringify([
|
|
669
|
+
issue.origin,
|
|
670
|
+
issue.path,
|
|
671
|
+
issue.message,
|
|
672
|
+
issue.range
|
|
673
|
+
]);
|
|
674
|
+
if (seen.has(key)) continue;
|
|
675
|
+
seen.add(key);
|
|
676
|
+
distinct.push(issue);
|
|
677
|
+
}
|
|
678
|
+
return distinct;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
566
681
|
* Reports whether a path is a workspace module Vitest can cache.
|
|
567
682
|
*
|
|
568
683
|
* @param path - The candidate file path
|
|
@@ -578,6 +693,75 @@ function matchesWorkspaceModule(path) {
|
|
|
578
693
|
return /\.(?:[cm]?[jt]sx?|vue|json)$/.test(path);
|
|
579
694
|
}
|
|
580
695
|
/**
|
|
696
|
+
* Reports whether the host that wrote one file is still running.
|
|
697
|
+
*
|
|
698
|
+
* @remarks
|
|
699
|
+
* Signal 0 delivers nothing and reports reachability alone. A process this one may not signal
|
|
700
|
+
* reports `EPERM` and reads as running, and a non-positive identity names a process group rather
|
|
701
|
+
* than a process, so both answer true: the safe direction is to keep a file this package cannot
|
|
702
|
+
* account for.
|
|
703
|
+
*
|
|
704
|
+
* @param id - The process id a file's own revision names
|
|
705
|
+
* @returns True if that process is running or cannot be judged; false otherwise
|
|
706
|
+
*
|
|
707
|
+
* @example
|
|
708
|
+
* ```ts
|
|
709
|
+
* matchesLiveProcess(process.pid) // true
|
|
710
|
+
* matchesLiveProcess(0) // true
|
|
711
|
+
* ```
|
|
712
|
+
*/
|
|
713
|
+
function matchesLiveProcess(id) {
|
|
714
|
+
if (!Number.isSafeInteger(id) || id <= 0) return true;
|
|
715
|
+
try {
|
|
716
|
+
process.kill(id, 0);
|
|
717
|
+
return true;
|
|
718
|
+
} catch (error) {
|
|
719
|
+
return readFaultCode(error) === "EPERM";
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Collects every regular file a target workspace holds, skipping the trees no inspection reads.
|
|
724
|
+
*
|
|
725
|
+
* @remarks
|
|
726
|
+
* Skips a directory named `.git`, `dist`, or `node_modules` at any depth, and the directory
|
|
727
|
+
* {@link TYPE_MIRROR} names, which would otherwise carry this package's own mirror into the walk. A
|
|
728
|
+
* directory this host cannot list is skipped rather than raised, so one unreadable directory in a
|
|
729
|
+
* consumer's tree is not a reason to refuse the whole walk. A symbolic link is not carried, whether
|
|
730
|
+
* it names a file or a directory, so a file reached only through one is absent from the result; a
|
|
731
|
+
* `Dirent` that is neither a directory nor a regular file is skipped the same way.
|
|
732
|
+
*
|
|
733
|
+
* @param workspace - The target workspace root
|
|
734
|
+
* @returns The absolute path of every regular file the walk found
|
|
735
|
+
*
|
|
736
|
+
* @example
|
|
737
|
+
* ```ts
|
|
738
|
+
* collectWorkspaceFiles(process.cwd()).some((path) => path.endsWith('package.json')) // true
|
|
739
|
+
* ```
|
|
740
|
+
*/
|
|
741
|
+
function collectWorkspaceFiles(workspace) {
|
|
742
|
+
const excluded = normalizePath(resolveWorkspaceFile(workspace, _src_core.TYPE_MIRROR));
|
|
743
|
+
const files = [];
|
|
744
|
+
const directories = [(0, node_path.resolve)(workspace)];
|
|
745
|
+
while (directories.length > 0) {
|
|
746
|
+
const directory = directories.pop();
|
|
747
|
+
if (directory === void 0) break;
|
|
748
|
+
let entries = [];
|
|
749
|
+
try {
|
|
750
|
+
entries = (0, node_fs.readdirSync)(directory, { withFileTypes: true });
|
|
751
|
+
} catch {}
|
|
752
|
+
for (const entry of entries) {
|
|
753
|
+
if (entry.name === ".git" || entry.name === "dist" || entry.name === "node_modules") continue;
|
|
754
|
+
const path = (0, node_path.join)(directory, entry.name);
|
|
755
|
+
if (entry.isDirectory()) {
|
|
756
|
+
if (normalizePath(path) !== excluded) directories.push(path);
|
|
757
|
+
continue;
|
|
758
|
+
}
|
|
759
|
+
if (entry.isFile()) files.push(path);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
return files;
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
581
765
|
* Normalizes a caught or foreign error into readable text.
|
|
582
766
|
*
|
|
583
767
|
* @param value - The value to describe
|
|
@@ -596,9 +780,9 @@ function describeUnknown(value) {
|
|
|
596
780
|
return String(value);
|
|
597
781
|
}
|
|
598
782
|
/**
|
|
599
|
-
* Guards one
|
|
783
|
+
* Guards one stage operation with the stage failure contract.
|
|
600
784
|
*
|
|
601
|
-
* @param stage - The
|
|
785
|
+
* @param stage - The stage serving the operation
|
|
602
786
|
* @param operation - The operation to settle
|
|
603
787
|
* @returns The operation's fulfilled value
|
|
604
788
|
* @throws The original `ProbeError`, or an instrument-owned malformed failure retaining the cause
|
|
@@ -670,12 +854,12 @@ function findRefusedPaths(value) {
|
|
|
670
854
|
* sorts every record's keys.
|
|
671
855
|
*
|
|
672
856
|
* @remarks
|
|
673
|
-
*
|
|
674
|
-
*
|
|
675
|
-
*
|
|
676
|
-
*
|
|
677
|
-
*
|
|
678
|
-
* sorting removes a difference that means nothing.
|
|
857
|
+
* `tsc --showConfig` spells a path relative to the project file, so a printed record carries an
|
|
858
|
+
* absolute path only where a project itself declares one. Rewriting a contained absolute path to
|
|
859
|
+
* its relative form keeps one commit checked out at two roots reading to one digest. A path that
|
|
860
|
+
* escapes the workspace is left as it stands, because rewriting it would name a file outside the
|
|
861
|
+
* tree by a relative spelling. Key order is the compiler's own and it differs between the supported
|
|
862
|
+
* majors, so sorting removes a difference that means nothing.
|
|
679
863
|
*
|
|
680
864
|
* @param workspace - The target workspace root
|
|
681
865
|
* @param value - The value to rewrite
|
|
@@ -776,9 +960,66 @@ function releaseListeners(emitter, capture) {
|
|
|
776
960
|
}
|
|
777
961
|
}
|
|
778
962
|
//#endregion
|
|
963
|
+
//#region src/server/parsers.ts
|
|
964
|
+
/**
|
|
965
|
+
* Parses the configuration one compiler run printed for a TypeScript project.
|
|
966
|
+
*
|
|
967
|
+
* @remarks
|
|
968
|
+
* Reads what `tsc --showConfig` writes: a JSON record carrying the resolved `compilerOptions`
|
|
969
|
+
* beside the project's own file selection. `compilerOptions` is carried unvalidated, because a
|
|
970
|
+
* caller digests it rather than reading a member out of it. `files` and `include` are read only
|
|
971
|
+
* where the printed record carries one whose entries are all strings, so a member the compiler
|
|
972
|
+
* printed in another shape reads as absent rather than as an empty selection. Text that is not a
|
|
973
|
+
* JSON record, including the diagnostic the compiler prints for a project it refuses, yields
|
|
974
|
+
* `undefined`.
|
|
975
|
+
*
|
|
976
|
+
* @param text - The compiler's printed configuration
|
|
977
|
+
* @returns The project's resolved configuration, or `undefined` when the text carries no record
|
|
978
|
+
*
|
|
979
|
+
* @example
|
|
980
|
+
* ```ts
|
|
981
|
+
* parseProjectConfig('{"compilerOptions":{"strict":true}}')?.compilerOptions // { strict: true }
|
|
982
|
+
* parseProjectConfig("error TS5058: The specified path does not exist.") // undefined
|
|
983
|
+
* ```
|
|
984
|
+
*/
|
|
985
|
+
function parseProjectConfig(text) {
|
|
986
|
+
const parsed = (0, _orkestrel_contract.attempt)(() => JSON.parse(text));
|
|
987
|
+
if (!parsed.success || !(0, _orkestrel_contract.isRecord)(parsed.value)) return void 0;
|
|
988
|
+
const files = parsed.value.files;
|
|
989
|
+
const include = parsed.value.include;
|
|
990
|
+
return {
|
|
991
|
+
compilerOptions: parsed.value.compilerOptions,
|
|
992
|
+
...(0, _orkestrel_contract.isArray)(files) && files.every((entry) => typeof entry === "string") ? { files } : {},
|
|
993
|
+
...(0, _orkestrel_contract.isArray)(include) && include.every((entry) => typeof entry === "string") ? { include } : {}
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Parses the process id one revision identity names.
|
|
998
|
+
*
|
|
999
|
+
* @remarks
|
|
1000
|
+
* Every file and directory this package writes into a target carries the writing host's process id
|
|
1001
|
+
* and a fresh UUID, joined by `-`. A sweep reads that id to decide whether the host that wrote the
|
|
1002
|
+
* name is gone. The whole identity must match, so a name carrying anything else yields `undefined`
|
|
1003
|
+
* and the sweep leaves what it found alone.
|
|
1004
|
+
*
|
|
1005
|
+
* @param revision - The revision identity a name carries
|
|
1006
|
+
* @returns The process id it names, or `undefined` when the text is not a revision identity
|
|
1007
|
+
*
|
|
1008
|
+
* @example
|
|
1009
|
+
* ```ts
|
|
1010
|
+
* parseRevisionOwner('4821-1b4e28ba-2fa1-11d2-883f-0016d3cca427') // 4821
|
|
1011
|
+
* parseRevisionOwner('4821-9f0c') // undefined
|
|
1012
|
+
* ```
|
|
1013
|
+
*/
|
|
1014
|
+
function parseRevisionOwner(revision) {
|
|
1015
|
+
const owner = /^(\d+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/u.exec(revision)?.[1];
|
|
1016
|
+
return owner === void 0 ? void 0 : Number.parseInt(owner, 10);
|
|
1017
|
+
}
|
|
1018
|
+
//#endregion
|
|
779
1019
|
//#region src/server/Overlay.ts
|
|
780
1020
|
/**
|
|
781
|
-
*
|
|
1021
|
+
* Implements `OverlayInterface` over a private map from normalized absolute path to candidate text,
|
|
1022
|
+
* minting at construction the `revision` a resident tool caches its answers against.
|
|
782
1023
|
*
|
|
783
1024
|
* @remarks
|
|
784
1025
|
* One overlay belongs to one inspection: the inspection creates it, records every candidate it
|
|
@@ -787,55 +1028,44 @@ function releaseListeners(emitter, capture) {
|
|
|
787
1028
|
* is what makes a resident tool re-read a path this overlay holds, so two inspections that supply
|
|
788
1029
|
* different text for one path never share a cached answer.
|
|
789
1030
|
*
|
|
790
|
-
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
*
|
|
794
|
-
* file
|
|
795
|
-
*
|
|
796
|
-
* against, so a folded key never reaches the paths a tool is handed and containment stays an exact
|
|
797
|
-
* comparison.
|
|
1031
|
+
* A lookup key matches a recorded path exactly, after both pass through `normalizePath`, so a tool
|
|
1032
|
+
* that reports its own paths with backslashes still reaches a candidate recorded with forward
|
|
1033
|
+
* slashes. Case is never folded: the recorded spelling is what `paths` reports, what `covers`
|
|
1034
|
+
* compares a directory against, and what `text` answers under. A stage whose tool resolves two
|
|
1035
|
+
* spellings of one file name to one file therefore reports the path its tool served rather than
|
|
1036
|
+
* matching the two spellings here.
|
|
798
1037
|
*
|
|
799
1038
|
* @example
|
|
800
1039
|
* ```ts
|
|
801
1040
|
* const overlay = new Overlay()
|
|
802
|
-
*
|
|
803
|
-
*
|
|
1041
|
+
* const path = '/srv/checkout/src/core/factories.ts'
|
|
1042
|
+
* overlay.set(path, "export function createGreeting(): string {\n\treturn 'hi'\n}\n")
|
|
1043
|
+
* console.log(overlay.text(path))
|
|
804
1044
|
* overlay.clear()
|
|
805
1045
|
* ```
|
|
806
1046
|
*/
|
|
807
1047
|
var Overlay = class {
|
|
808
1048
|
#revision = (0, node_crypto.randomUUID)();
|
|
809
|
-
#sensitive;
|
|
810
1049
|
#candidates = /* @__PURE__ */ new Map();
|
|
811
|
-
/**
|
|
812
|
-
* Creates an empty candidate set under a fresh identity.
|
|
813
|
-
*
|
|
814
|
-
* @param options - Construction options
|
|
815
|
-
*/
|
|
816
|
-
constructor(options = {}) {
|
|
817
|
-
this.#sensitive = options.sensitive ?? true;
|
|
818
|
-
}
|
|
819
1050
|
get revision() {
|
|
820
1051
|
return this.#revision;
|
|
821
1052
|
}
|
|
822
1053
|
get paths() {
|
|
823
|
-
return [...this.#candidates.
|
|
1054
|
+
return [...this.#candidates.keys()];
|
|
824
1055
|
}
|
|
825
1056
|
/**
|
|
826
1057
|
* Records one candidate's text against the absolute path it stands in for.
|
|
827
1058
|
*
|
|
828
1059
|
* @remarks
|
|
829
|
-
* Recording a candidate under a path this overlay
|
|
830
|
-
*
|
|
1060
|
+
* Recording a candidate under a path this overlay already holds replaces that candidate, the way
|
|
1061
|
+
* one file holds one text.
|
|
831
1062
|
*
|
|
832
1063
|
* @param path - The absolute path the candidate replaces
|
|
833
1064
|
* @param text - The candidate's full contents
|
|
834
1065
|
* @returns Nothing
|
|
835
1066
|
*/
|
|
836
1067
|
set(path, text) {
|
|
837
|
-
|
|
838
|
-
this.#candidates.set(this.#key(recorded), [recorded, text]);
|
|
1068
|
+
this.#candidates.set(normalizePath(path), text);
|
|
839
1069
|
}
|
|
840
1070
|
/**
|
|
841
1071
|
* Reads the candidate text recorded for one absolute path.
|
|
@@ -844,7 +1074,7 @@ var Overlay = class {
|
|
|
844
1074
|
* @returns The recorded text, or `undefined` when this overlay holds no candidate there
|
|
845
1075
|
*/
|
|
846
1076
|
text(path) {
|
|
847
|
-
return this.#candidates.get(
|
|
1077
|
+
return this.#candidates.get(normalizePath(path));
|
|
848
1078
|
}
|
|
849
1079
|
/**
|
|
850
1080
|
* Checks whether a candidate sits beneath one directory.
|
|
@@ -853,16 +1083,14 @@ var Overlay = class {
|
|
|
853
1083
|
* The answer is derived from the paths the overlay holds rather than stored, so it stops being
|
|
854
1084
|
* true exactly when the inspection that declared those candidates clears them. Both sides pass
|
|
855
1085
|
* through `normalizePath` first, because a tool that normalizes its own paths asks about a
|
|
856
|
-
* directory in a spelling the recorded path may not share.
|
|
857
|
-
* containment comparison, and it reads the recorded spelling whatever an overlay minted for a
|
|
858
|
-
* case-folding host matches its lookup keys by.
|
|
1086
|
+
* directory in a spelling the recorded path may not share.
|
|
859
1087
|
*
|
|
860
1088
|
* @param directory - The absolute directory path to check
|
|
861
1089
|
* @returns True if a candidate path sits beneath the directory; false otherwise
|
|
862
1090
|
*/
|
|
863
1091
|
covers(directory) {
|
|
864
1092
|
const base = `${normalizePath(directory).replace(/\/+$/, "")}/`;
|
|
865
|
-
for (const
|
|
1093
|
+
for (const path of this.#candidates.keys()) if (path.startsWith(base)) return true;
|
|
866
1094
|
return false;
|
|
867
1095
|
}
|
|
868
1096
|
/**
|
|
@@ -873,15 +1101,12 @@ var Overlay = class {
|
|
|
873
1101
|
clear() {
|
|
874
1102
|
this.#candidates.clear();
|
|
875
1103
|
}
|
|
876
|
-
#key(path) {
|
|
877
|
-
return this.#sensitive ? path : path.toLowerCase();
|
|
878
|
-
}
|
|
879
1104
|
};
|
|
880
1105
|
//#endregion
|
|
881
1106
|
//#region package.json
|
|
882
|
-
var version = "0.0.
|
|
1107
|
+
var version = "0.0.14";
|
|
883
1108
|
var peerDependencies = {
|
|
884
|
-
"oxlint": "^1.
|
|
1109
|
+
"oxlint": "^1.82.0",
|
|
885
1110
|
"typescript": "^6.0.3",
|
|
886
1111
|
"vitest": "^4.1.11"
|
|
887
1112
|
};
|
|
@@ -1162,12 +1387,14 @@ var RuntimeStage = class {
|
|
|
1162
1387
|
/**
|
|
1163
1388
|
* Starts warming the target workspace's Vitest service.
|
|
1164
1389
|
*
|
|
1390
|
+
* @remarks Loading starts during this call. A loader refusal rejects the owned warm operation
|
|
1391
|
+
* rather than construction, so `inspect` reports it and `destroy` remains available.
|
|
1392
|
+
*
|
|
1165
1393
|
* @param workspace - The target workspace root. Default: the current working directory
|
|
1166
1394
|
*/
|
|
1167
1395
|
constructor(workspace = process.cwd()) {
|
|
1168
1396
|
this.#workspace = workspace;
|
|
1169
|
-
|
|
1170
|
-
this.#store(this.#warm(vitest.createVitest));
|
|
1397
|
+
this.#store(this.#warm());
|
|
1171
1398
|
}
|
|
1172
1399
|
get stage() {
|
|
1173
1400
|
return "runtime";
|
|
@@ -1175,9 +1402,29 @@ var RuntimeStage = class {
|
|
|
1175
1402
|
get progress() {
|
|
1176
1403
|
return this.#progress;
|
|
1177
1404
|
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Inspects one case.
|
|
1407
|
+
*
|
|
1408
|
+
* @param subject - The candidate drafts and test to inspect
|
|
1409
|
+
* @returns One outcome for this stage
|
|
1410
|
+
* @throws When the workspace's own tool cannot start or has already been destroyed
|
|
1411
|
+
*/
|
|
1178
1412
|
inspect(subject) {
|
|
1179
1413
|
return guardStage(this.stage, this.#inspect(subject));
|
|
1180
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Tears down the resident tool or the mirror and releases its resources.
|
|
1417
|
+
*
|
|
1418
|
+
* @remarks
|
|
1419
|
+
* A stage abandons every inspection it holds rather than waiting behind one, so teardown never
|
|
1420
|
+
* waits for an inspection to return. An abandoned inspection rejects, either at the stage's own
|
|
1421
|
+
* guard or as the owned tool closes. Teardown is bounded whatever the stage's own tool does: a
|
|
1422
|
+
* tool that answers neither its warming exchange nor its ending is signalled and released at the
|
|
1423
|
+
* stage's own deadline. A coordinator replaces a stage whose worker no longer returns because
|
|
1424
|
+
* teardown neither waits for an inspection nor waits past that deadline.
|
|
1425
|
+
*
|
|
1426
|
+
* @returns A promise that settles after the resident tool or the mirror releases its resources
|
|
1427
|
+
*/
|
|
1181
1428
|
destroy() {
|
|
1182
1429
|
if (this.#closing !== void 0) return this.#closing;
|
|
1183
1430
|
this.#closing = guardStage(this.stage, this.#destroy());
|
|
@@ -1278,13 +1525,14 @@ var RuntimeStage = class {
|
|
|
1278
1525
|
}
|
|
1279
1526
|
}
|
|
1280
1527
|
async #warm(create) {
|
|
1528
|
+
const factory = create ?? loadWorkspaceVitest(this.#workspace).createVitest;
|
|
1281
1529
|
this.#sweep();
|
|
1282
1530
|
const output = new node_stream.PassThrough();
|
|
1283
1531
|
output.resume();
|
|
1284
1532
|
const signals = captureListeners(process, ["SIGINT", "SIGTERM"]);
|
|
1285
1533
|
let warming;
|
|
1286
1534
|
try {
|
|
1287
|
-
warming =
|
|
1535
|
+
warming = factory("test", {
|
|
1288
1536
|
root: this.#workspace,
|
|
1289
1537
|
config: resolveWorkspaceFile(this.#workspace, "vite.config.ts"),
|
|
1290
1538
|
watch: false,
|
|
@@ -1521,13 +1769,13 @@ var RuntimeStage = class {
|
|
|
1521
1769
|
const current = this.#vitest;
|
|
1522
1770
|
if (current !== void 0 && this.#specifications < _src_core.PROBE_SPECIFICATIONS) return current;
|
|
1523
1771
|
if (current !== void 0) return this.#store(this.#replace(current));
|
|
1524
|
-
const runner =
|
|
1772
|
+
const runner = loadWorkspaceVitest(this.#workspace);
|
|
1525
1773
|
return this.#store(this.#specifications < _src_core.PROBE_SPECIFICATIONS ? this.#warm(runner.createVitest) : this.#replace(void 0, runner.createVitest));
|
|
1526
1774
|
}
|
|
1527
1775
|
async #replace(current, create) {
|
|
1528
1776
|
if (current !== void 0) await (await current).close();
|
|
1529
1777
|
if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("runtime stage");
|
|
1530
|
-
const runner = create ??
|
|
1778
|
+
const runner = create ?? loadWorkspaceVitest(this.#workspace).createVitest;
|
|
1531
1779
|
const replacement = await this.#warm(runner);
|
|
1532
1780
|
this.#specifications = 0;
|
|
1533
1781
|
return replacement;
|
|
@@ -1571,7 +1819,7 @@ var RuntimeStage = class {
|
|
|
1571
1819
|
}
|
|
1572
1820
|
#snapshot() {
|
|
1573
1821
|
const modules = /* @__PURE__ */ new Map();
|
|
1574
|
-
for (const path of this.#
|
|
1822
|
+
for (const path of collectWorkspaceFiles(this.#workspace)) {
|
|
1575
1823
|
if (!matchesWorkspaceModule(path)) continue;
|
|
1576
1824
|
try {
|
|
1577
1825
|
const digest = (0, node_crypto.createHash)("sha256").update((0, node_fs.readFileSync)(path)).digest("hex");
|
|
@@ -1582,12 +1830,12 @@ var RuntimeStage = class {
|
|
|
1582
1830
|
return modules;
|
|
1583
1831
|
}
|
|
1584
1832
|
#sweep() {
|
|
1585
|
-
for (const path of this.#
|
|
1833
|
+
for (const path of collectWorkspaceFiles(this.#workspace)) {
|
|
1586
1834
|
const match = [...(0, node_path.basename)(path).matchAll(/\.probe-((\d+)-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})(?=\.|$)/gu)].at(-1);
|
|
1587
1835
|
const revision = match?.[1];
|
|
1588
1836
|
const owner = match?.[2];
|
|
1589
1837
|
if (revision === void 0 || owner === void 0) continue;
|
|
1590
|
-
if (
|
|
1838
|
+
if (matchesLiveProcess(Number.parseInt(owner, 10))) continue;
|
|
1591
1839
|
if (!this.#owned(path, revision)) continue;
|
|
1592
1840
|
try {
|
|
1593
1841
|
(0, node_fs.unlinkSync)(resolveWorkspaceFile(this.#workspace, (0, node_path.relative)(this.#workspace, path), true));
|
|
@@ -1598,35 +1846,6 @@ var RuntimeStage = class {
|
|
|
1598
1846
|
const outcome = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)(path, "utf8"));
|
|
1599
1847
|
return outcome.success && (0, _src_core.matchesSpecification)(outcome.value, revision);
|
|
1600
1848
|
}
|
|
1601
|
-
#alive(id) {
|
|
1602
|
-
if (!Number.isSafeInteger(id) || id <= 0) return true;
|
|
1603
|
-
try {
|
|
1604
|
-
process.kill(id, 0);
|
|
1605
|
-
return true;
|
|
1606
|
-
} catch (error) {
|
|
1607
|
-
return readFaultCode(error) === "EPERM";
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
*#walk() {
|
|
1611
|
-
const directories = [(0, node_path.resolve)(this.#workspace)];
|
|
1612
|
-
while (directories.length > 0) {
|
|
1613
|
-
const directory = directories.pop();
|
|
1614
|
-
if (directory === void 0) break;
|
|
1615
|
-
let entries = [];
|
|
1616
|
-
try {
|
|
1617
|
-
entries = (0, node_fs.readdirSync)(directory, { withFileTypes: true });
|
|
1618
|
-
} catch {}
|
|
1619
|
-
for (const entry of entries) {
|
|
1620
|
-
if (entry.name === ".git" || entry.name === "dist" || entry.name === "node_modules") continue;
|
|
1621
|
-
const path = (0, node_path.join)(directory, entry.name);
|
|
1622
|
-
if (entry.isDirectory()) {
|
|
1623
|
-
directories.push(path);
|
|
1624
|
-
continue;
|
|
1625
|
-
}
|
|
1626
|
-
if (entry.isFile()) yield path;
|
|
1627
|
-
}
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
1849
|
#issues(result, file, original) {
|
|
1631
1850
|
const specification = this.#real(file);
|
|
1632
1851
|
const issues = [];
|
|
@@ -1742,21 +1961,32 @@ var RuntimeStage = class {
|
|
|
1742
1961
|
//#endregion
|
|
1743
1962
|
//#region src/server/stages/TypeStage.ts
|
|
1744
1963
|
/**
|
|
1745
|
-
* Inspects TypeScript source
|
|
1964
|
+
* Inspects TypeScript source by running the target workspace's own compiler over a mirror of it.
|
|
1746
1965
|
*
|
|
1747
1966
|
* @remarks
|
|
1748
|
-
*
|
|
1749
|
-
*
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1967
|
+
* The compiler is a process this stage spawns rather than a module it loads, so nothing here holds
|
|
1968
|
+
* a resident program and the host's event loop is free for the whole check. Construction sweeps a
|
|
1969
|
+
* mirror an earlier host left behind, copies the workspace into a fresh one under
|
|
1970
|
+
* {@link TYPE_MIRROR}, and builds each declared project's incremental state there, so the first
|
|
1971
|
+
* inspection is warm.
|
|
1972
|
+
*
|
|
1973
|
+
* Every inspection refreshes the mirror by content digest, writes each candidate draft and the test
|
|
1974
|
+
* at its mirrored declared path, and runs `tsc --noEmit --pretty false` once per distinct selected
|
|
1975
|
+
* project. A draft therefore shadows the file it replaces: a consumer of that path is checked
|
|
1976
|
+
* against the draft's text, and a draft importing a sibling draft resolves to the sibling draft.
|
|
1977
|
+
* Nothing is written outside `tmp/`, and the workspace's own copy of a drafted file never moves.
|
|
1978
|
+
*
|
|
1979
|
+
* Each run reads a scratch project this stage writes beside the mirrored project it extends, so
|
|
1980
|
+
* every relative path the project declares — its own `rootDir`, `include`, `paths`, and the
|
|
1981
|
+
* projects it extends — resolves inside the mirror without being rewritten. The scratch adds the
|
|
1982
|
+
* drafts as `files`, which the project's own `exclude` does not reach, and its incremental state
|
|
1983
|
+
* lives in the mirror so reuse survives the refresh.
|
|
1984
|
+
*
|
|
1985
|
+
* The diagnostics decide the outcome, never the exit code, which the supported compiler majors
|
|
1986
|
+
* disagree on. A diagnostic naming a project file, and one naming no file at all, is the target
|
|
1987
|
+
* tree's own configuration fault and raises rather than reporting, unless the `.json` file is one
|
|
1988
|
+
* the claim itself drafted; every other diagnostic is a claimant issue at the point the compiler
|
|
1989
|
+
* reported, which is the extent the plain-text output carries.
|
|
1760
1990
|
*
|
|
1761
1991
|
* @example
|
|
1762
1992
|
* ```ts
|
|
@@ -1767,26 +1997,27 @@ var RuntimeStage = class {
|
|
|
1767
1997
|
*/
|
|
1768
1998
|
var TypeStage = class {
|
|
1769
1999
|
#workspace;
|
|
1770
|
-
#
|
|
1771
|
-
#
|
|
1772
|
-
#
|
|
1773
|
-
#
|
|
1774
|
-
#
|
|
1775
|
-
#
|
|
1776
|
-
#
|
|
1777
|
-
#
|
|
2000
|
+
#compiler;
|
|
2001
|
+
#revision = `${process.pid}-${(0, node_crypto.randomUUID)()}`;
|
|
2002
|
+
#mirror;
|
|
2003
|
+
#configs = /* @__PURE__ */ new Map();
|
|
2004
|
+
#mirrored = /* @__PURE__ */ new Map();
|
|
2005
|
+
#drafts = /* @__PURE__ */ new Set();
|
|
2006
|
+
#children = /* @__PURE__ */ new Set();
|
|
2007
|
+
#warming;
|
|
1778
2008
|
#closing;
|
|
1779
2009
|
#progress = 0;
|
|
1780
2010
|
/**
|
|
1781
|
-
*
|
|
2011
|
+
* Resolves the target workspace's compiler and starts warming its mirror.
|
|
1782
2012
|
*
|
|
1783
2013
|
* @param workspace - The target workspace root. Default: the current working directory
|
|
1784
2014
|
*/
|
|
1785
2015
|
constructor(workspace = process.cwd()) {
|
|
1786
2016
|
this.#workspace = workspace;
|
|
1787
|
-
|
|
1788
|
-
this.#
|
|
1789
|
-
this.#
|
|
2017
|
+
this.#compiler = resolveWorkspaceBinary(workspace, "typescript", "tsc");
|
|
2018
|
+
this.#mirror = resolveWorkspaceFile(workspace, `${_src_core.TYPE_MIRROR}/${this.#revision}`);
|
|
2019
|
+
this.#warming = this.#warm();
|
|
2020
|
+
this.#warming.catch(() => {});
|
|
1790
2021
|
}
|
|
1791
2022
|
get stage() {
|
|
1792
2023
|
return "type";
|
|
@@ -1806,7 +2037,8 @@ var TypeStage = class {
|
|
|
1806
2037
|
* @param project - The workspace-relative TypeScript project the candidate drafts are checked
|
|
1807
2038
|
* against. Default: the scoped project each candidate path infers
|
|
1808
2039
|
* @returns One outcome for this stage
|
|
1809
|
-
* @throws When the
|
|
2040
|
+
* @throws When the workspace refuses the mirror, when a project the run reads is malformed, or
|
|
2041
|
+
* when the stage has already been destroyed
|
|
1810
2042
|
*/
|
|
1811
2043
|
inspect(subject, project) {
|
|
1812
2044
|
return guardStage(this.stage, this.#inspect(subject, project));
|
|
@@ -1815,15 +2047,15 @@ var TypeStage = class {
|
|
|
1815
2047
|
* Resolves one project to the path and digest this stage applies for it.
|
|
1816
2048
|
*
|
|
1817
2049
|
* @remarks
|
|
1818
|
-
* Reads the
|
|
1819
|
-
*
|
|
1820
|
-
*
|
|
1821
|
-
*
|
|
2050
|
+
* Reads the configuration the compiler itself prints for the project as the mirror holds it,
|
|
2051
|
+
* refreshed from the workspace first on a cache miss, so the digest names the configuration the
|
|
2052
|
+
* check applies. The reading is cached per project for the life of the stage, so a draft written
|
|
2053
|
+
* afterward cannot move it.
|
|
1822
2054
|
*
|
|
1823
2055
|
* @param project - The workspace-relative TypeScript project to resolve
|
|
1824
2056
|
* @returns The resolved workspace-relative path and the digest of its compiler options
|
|
1825
|
-
* @throws When the project escapes the workspace,
|
|
1826
|
-
* been destroyed
|
|
2057
|
+
* @throws When the project escapes the workspace, when the compiler refuses it, or when the
|
|
2058
|
+
* stage has already been destroyed
|
|
1827
2059
|
*/
|
|
1828
2060
|
resolve(project) {
|
|
1829
2061
|
return guardStage(this.stage, this.#resolve(project));
|
|
@@ -1834,81 +2066,76 @@ var TypeStage = class {
|
|
|
1834
2066
|
return this.#closing;
|
|
1835
2067
|
}
|
|
1836
2068
|
async #inspect(subject, project) {
|
|
1837
|
-
|
|
2069
|
+
this.#refuseDestroyed();
|
|
1838
2070
|
const started = performance.now();
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
const
|
|
2071
|
+
await this.#warming;
|
|
2072
|
+
this.#refuseDestroyed();
|
|
2073
|
+
const test = resolveWorkspaceFile(this.#workspace, subject.test.path);
|
|
2074
|
+
const resolved = subject.files.map((draft) => ({
|
|
1842
2075
|
draft,
|
|
1843
2076
|
path: resolveWorkspaceFile(this.#workspace, draft.path)
|
|
1844
|
-
})).map((candidate) => ({
|
|
1845
|
-
draft: candidate.draft,
|
|
1846
|
-
project: project ?? inferTypeProject(relativeWorkspaceFile(this.#workspace, candidate.path))
|
|
1847
2077
|
}));
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2078
|
+
const groups = /* @__PURE__ */ new Map([[this.#contain("tsconfig.json"), [subject.test]]]);
|
|
2079
|
+
for (const candidate of resolved) {
|
|
2080
|
+
const selected = this.#contain(project ?? inferTypeProject(relativeWorkspaceFile(this.#workspace, candidate.path)));
|
|
2081
|
+
const drafts = groups.get(selected);
|
|
2082
|
+
if (drafts === void 0) groups.set(selected, [candidate.draft]);
|
|
2083
|
+
else drafts.push(candidate.draft);
|
|
1854
2084
|
}
|
|
1855
|
-
|
|
1856
|
-
this.#
|
|
2085
|
+
this.#progress += 1;
|
|
2086
|
+
for (const selected of groups.keys()) await this.#configure(selected);
|
|
2087
|
+
this.#refuseDestroyed();
|
|
1857
2088
|
try {
|
|
1858
|
-
this.#
|
|
1859
|
-
|
|
1860
|
-
this.#
|
|
2089
|
+
this.#refresh();
|
|
2090
|
+
this.#drafts.add(this.#place(subject.test.text, test));
|
|
2091
|
+
for (const candidate of resolved) this.#drafts.add(this.#place(candidate.draft.text, candidate.path));
|
|
1861
2092
|
const issues = [];
|
|
1862
|
-
const
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
await this.#unblock();
|
|
1866
|
-
for (const selection of selections) {
|
|
1867
|
-
const draft = selection.draft;
|
|
1868
|
-
const selected = selection.project;
|
|
1869
|
-
const service = this.#service(typescript, selected);
|
|
1870
|
-
issues.push(...this.#issues(typescript, service, draft, selected, project !== void 0, !projects.has(selected)));
|
|
1871
|
-
projects.add(selected);
|
|
1872
|
-
await this.#unblock();
|
|
2093
|
+
for (const [selected, drafts] of groups) {
|
|
2094
|
+
this.#refuseDestroyed();
|
|
2095
|
+
issues.push(...await this.#check(selected, drafts));
|
|
1873
2096
|
}
|
|
1874
2097
|
return {
|
|
1875
2098
|
stage: this.stage,
|
|
1876
2099
|
elapsed: Math.round(performance.now() - started),
|
|
1877
|
-
issues
|
|
2100
|
+
issues: filterUniqueIssues(issues)
|
|
1878
2101
|
};
|
|
1879
2102
|
} finally {
|
|
1880
|
-
|
|
2103
|
+
this.#release();
|
|
1881
2104
|
}
|
|
1882
2105
|
}
|
|
1883
2106
|
async #resolve(project) {
|
|
1884
|
-
|
|
1885
|
-
const
|
|
1886
|
-
if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
|
|
1887
|
-
const resolved = resolveWorkspaceFile(this.#workspace, project);
|
|
2107
|
+
this.#refuseDestroyed();
|
|
2108
|
+
const contained = this.#contain(project);
|
|
1888
2109
|
this.#progress += 1;
|
|
1889
|
-
this.#
|
|
1890
|
-
await this.#unblock();
|
|
2110
|
+
const config = await this.#configure(contained);
|
|
1891
2111
|
return {
|
|
1892
|
-
path:
|
|
1893
|
-
digest: computeDigest(this.#workspace,
|
|
2112
|
+
path: contained,
|
|
2113
|
+
digest: computeDigest(this.#workspace, config.compilerOptions)
|
|
1894
2114
|
};
|
|
1895
2115
|
}
|
|
1896
2116
|
async #destroy() {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
this.#
|
|
1900
|
-
this.#
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2117
|
+
const children = [...this.#children];
|
|
2118
|
+
this.#children.clear();
|
|
2119
|
+
for (const child of children) this.#terminate(child);
|
|
2120
|
+
await this.#warming.catch(() => void 0);
|
|
2121
|
+
try {
|
|
2122
|
+
(0, node_fs.rmSync)(this.#mirror, {
|
|
2123
|
+
recursive: true,
|
|
2124
|
+
force: true,
|
|
2125
|
+
maxRetries: 5,
|
|
2126
|
+
retryDelay: 20
|
|
2127
|
+
});
|
|
2128
|
+
} catch {}
|
|
2129
|
+
this.#configs.clear();
|
|
2130
|
+
this.#mirrored.clear();
|
|
2131
|
+
this.#drafts.clear();
|
|
2132
|
+
}
|
|
2133
|
+
async #warm() {
|
|
2134
|
+
this.#sweep();
|
|
2135
|
+
this.#createMirror();
|
|
2136
|
+
this.#refresh();
|
|
2137
|
+
this.#refuseDestroyed();
|
|
2138
|
+
await Promise.all(this.#projects().map((project) => this.#check(project, [])));
|
|
1912
2139
|
}
|
|
1913
2140
|
#projects() {
|
|
1914
2141
|
const projects = ["tsconfig.json"];
|
|
@@ -1924,162 +2151,291 @@ var TypeStage = class {
|
|
|
1924
2151
|
}
|
|
1925
2152
|
return projects;
|
|
1926
2153
|
}
|
|
1927
|
-
|
|
1928
|
-
await (0, node_timers_promises.setTimeout)(0);
|
|
2154
|
+
#refuseDestroyed() {
|
|
1929
2155
|
if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
|
|
1930
2156
|
}
|
|
1931
|
-
#
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
if (existing !== void 0) return existing;
|
|
1938
|
-
const spelling = normalizePath(path);
|
|
1939
|
-
const config = typescript.readConfigFile(spelling, typescript.sys.readFile);
|
|
1940
|
-
if (config.error !== void 0) throw new _src_core.ProbeError(this.#translate(typescript, config.error.messageText), {
|
|
1941
|
-
origin: "workspace",
|
|
1942
|
-
code: "malformed",
|
|
1943
|
-
context: {
|
|
1944
|
-
stage: this.stage,
|
|
1945
|
-
project
|
|
1946
|
-
}
|
|
2157
|
+
#createMirror() {
|
|
2158
|
+
const marker = resolveWorkspaceFile(this.#workspace, `${_src_core.TYPE_MIRROR}/${this.#revision}/.probe/mirror.txt`, true);
|
|
2159
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(marker), { recursive: true });
|
|
2160
|
+
(0, node_fs.writeFileSync)(marker, (0, _src_core.formatSpecification)("", this.#revision), {
|
|
2161
|
+
encoding: "utf8",
|
|
2162
|
+
flag: "wx"
|
|
1947
2163
|
});
|
|
1948
|
-
const parsed = typescript.parseJsonConfigFileContent(config.config, typescript.sys, (0, node_path.dirname)(spelling), void 0, spelling);
|
|
1949
|
-
if (parsed.errors.length > 0) throw new _src_core.ProbeError(this.#translate(typescript, parsed.errors[0]?.messageText), {
|
|
1950
|
-
origin: "workspace",
|
|
1951
|
-
code: "malformed",
|
|
1952
|
-
context: {
|
|
1953
|
-
stage: this.stage,
|
|
1954
|
-
project
|
|
1955
|
-
}
|
|
1956
|
-
});
|
|
1957
|
-
this.#options.set(path, parsed.options);
|
|
1958
|
-
this.#files.set(path, parsed.fileNames);
|
|
1959
|
-
const host = {
|
|
1960
|
-
getCompilationSettings: this.#compilationSettings.bind(this, path),
|
|
1961
|
-
getScriptFileNames: this.#scriptFiles.bind(this, path),
|
|
1962
|
-
getScriptVersion: this.#version.bind(this),
|
|
1963
|
-
getScriptSnapshot: this.#snapshot.bind(this, typescript),
|
|
1964
|
-
getCurrentDirectory: this.#directory.bind(this),
|
|
1965
|
-
getDefaultLibFileName: this.#defaultLibrary.bind(this, typescript),
|
|
1966
|
-
fileExists: this.#fileExists.bind(this, typescript),
|
|
1967
|
-
readFile: this.#readFile.bind(this, typescript),
|
|
1968
|
-
readDirectory: typescript.sys.readDirectory,
|
|
1969
|
-
directoryExists: this.#directoryExists.bind(this, typescript),
|
|
1970
|
-
getDirectories: typescript.sys.getDirectories,
|
|
1971
|
-
useCaseSensitiveFileNames: this.#caseSensitive.bind(this, typescript),
|
|
1972
|
-
getNewLine: this.#newline.bind(this, typescript)
|
|
1973
|
-
};
|
|
1974
|
-
const service = typescript.createLanguageService(host);
|
|
1975
|
-
this.#services.set(path, service);
|
|
1976
|
-
if (!this.#resident.has(path)) this.#recycle(path);
|
|
1977
|
-
return service;
|
|
1978
|
-
}
|
|
1979
|
-
#compilationSettings(path) {
|
|
1980
|
-
return this.#options.get(path) ?? {};
|
|
1981
|
-
}
|
|
1982
|
-
#scriptFiles(path) {
|
|
1983
|
-
return [...this.#files.get(path) ?? [], ...this.#overlay.paths];
|
|
1984
2164
|
}
|
|
1985
|
-
#
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2165
|
+
#sweep() {
|
|
2166
|
+
const root = resolveWorkspaceFile(this.#workspace, _src_core.TYPE_MIRROR);
|
|
2167
|
+
let entries = [];
|
|
2168
|
+
try {
|
|
2169
|
+
entries = (0, node_fs.readdirSync)(root, { withFileTypes: true });
|
|
2170
|
+
} catch {
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
for (const entry of entries) {
|
|
2174
|
+
if (!entry.isDirectory()) continue;
|
|
2175
|
+
const owner = parseRevisionOwner(entry.name);
|
|
2176
|
+
if (owner === void 0 || matchesLiveProcess(owner)) continue;
|
|
2177
|
+
const marker = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)((0, node_path.join)(root, entry.name, ".probe", "mirror.txt"), "utf8"));
|
|
2178
|
+
if (!marker.success || !(0, _src_core.matchesSpecification)(marker.value, entry.name)) continue;
|
|
2179
|
+
try {
|
|
2180
|
+
(0, node_fs.rmSync)((0, node_path.join)(root, entry.name), {
|
|
2181
|
+
recursive: true,
|
|
2182
|
+
force: true,
|
|
2183
|
+
maxRetries: 5,
|
|
2184
|
+
retryDelay: 20
|
|
2185
|
+
});
|
|
2186
|
+
} catch {}
|
|
2187
|
+
}
|
|
2002
2188
|
}
|
|
2003
|
-
#
|
|
2004
|
-
|
|
2189
|
+
#refresh() {
|
|
2190
|
+
const present = /* @__PURE__ */ new Set();
|
|
2191
|
+
for (const path of collectWorkspaceFiles(this.#workspace)) {
|
|
2192
|
+
const contained = relativeWorkspaceFile(this.#workspace, path);
|
|
2193
|
+
present.add(contained);
|
|
2194
|
+
const reading = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)(path));
|
|
2195
|
+
if (!reading.success) continue;
|
|
2196
|
+
const digest = (0, node_crypto.createHash)("sha256").update(reading.value).digest("hex");
|
|
2197
|
+
if (this.#mirrored.get(contained) === digest) continue;
|
|
2198
|
+
this.#place(reading.value, path);
|
|
2199
|
+
this.#mirrored.set(contained, digest);
|
|
2200
|
+
}
|
|
2201
|
+
for (const contained of [...this.#mirrored.keys()]) {
|
|
2202
|
+
if (present.has(contained)) continue;
|
|
2203
|
+
this.#remove(contained);
|
|
2204
|
+
this.#mirrored.delete(contained);
|
|
2205
|
+
}
|
|
2005
2206
|
}
|
|
2006
|
-
#
|
|
2007
|
-
|
|
2207
|
+
#release() {
|
|
2208
|
+
for (const contained of this.#drafts) {
|
|
2209
|
+
this.#remove(contained);
|
|
2210
|
+
this.#mirrored.delete(contained);
|
|
2211
|
+
}
|
|
2212
|
+
this.#drafts.clear();
|
|
2213
|
+
}
|
|
2214
|
+
#place(text, path) {
|
|
2215
|
+
const contained = relativeWorkspaceFile(this.#workspace, path);
|
|
2216
|
+
const target = this.#mirrorPath(contained);
|
|
2217
|
+
if ((0, _orkestrel_contract.attempt)(() => {
|
|
2218
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
|
|
2219
|
+
(0, node_fs.writeFileSync)(target, text);
|
|
2220
|
+
}).success) return contained;
|
|
2221
|
+
this.#displace(target);
|
|
2222
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
|
|
2223
|
+
(0, node_fs.writeFileSync)(target, text);
|
|
2224
|
+
return contained;
|
|
2225
|
+
}
|
|
2226
|
+
#displace(target) {
|
|
2227
|
+
const ancestors = [];
|
|
2228
|
+
let directory = (0, node_path.dirname)(target);
|
|
2229
|
+
while (directory !== this.#mirror && directory !== (0, node_path.dirname)(directory)) {
|
|
2230
|
+
ancestors.unshift(directory);
|
|
2231
|
+
directory = (0, node_path.dirname)(directory);
|
|
2232
|
+
}
|
|
2233
|
+
for (const ancestor of [...ancestors, target]) {
|
|
2234
|
+
const reading = (0, _orkestrel_contract.attempt)(() => (0, node_fs.lstatSync)(ancestor));
|
|
2235
|
+
if (!reading.success) continue;
|
|
2236
|
+
if (ancestor !== target && reading.value.isDirectory()) continue;
|
|
2237
|
+
if (ancestor === target && !reading.value.isDirectory()) continue;
|
|
2238
|
+
(0, node_fs.rmSync)(ancestor, {
|
|
2239
|
+
recursive: true,
|
|
2240
|
+
force: true,
|
|
2241
|
+
maxRetries: 5,
|
|
2242
|
+
retryDelay: 20
|
|
2243
|
+
});
|
|
2244
|
+
this.#mirrored.delete(relativeWorkspaceFile(this.#mirror, ancestor));
|
|
2245
|
+
}
|
|
2008
2246
|
}
|
|
2009
|
-
#
|
|
2010
|
-
|
|
2011
|
-
if (this.#diagnostics.has(path)) return;
|
|
2012
|
-
this.#diagnostics.set(path, service.getCompilerOptionsDiagnostics());
|
|
2247
|
+
#mirrorPath(contained) {
|
|
2248
|
+
return (0, node_path.join)(this.#mirror, contained);
|
|
2013
2249
|
}
|
|
2014
|
-
#
|
|
2015
|
-
|
|
2016
|
-
this.#recycled = path;
|
|
2017
|
-
if (previous === void 0 || previous === path) return;
|
|
2018
|
-
this.#services.get(previous)?.dispose();
|
|
2019
|
-
this.#services.delete(previous);
|
|
2020
|
-
this.#options.delete(previous);
|
|
2021
|
-
this.#files.delete(previous);
|
|
2022
|
-
this.#diagnostics.delete(previous);
|
|
2250
|
+
#contain(project) {
|
|
2251
|
+
return relativeWorkspaceFile(this.#workspace, resolveWorkspaceFile(this.#workspace, project));
|
|
2023
2252
|
}
|
|
2024
|
-
#
|
|
2025
|
-
if (this.#overlay.text(file) !== void 0) return `virtual:${this.#overlay.revision}`;
|
|
2253
|
+
#remove(contained) {
|
|
2026
2254
|
try {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2255
|
+
(0, node_fs.rmSync)(this.#mirrorPath(contained), {
|
|
2256
|
+
force: true,
|
|
2257
|
+
maxRetries: 5,
|
|
2258
|
+
retryDelay: 20
|
|
2259
|
+
});
|
|
2260
|
+
} catch {}
|
|
2031
2261
|
}
|
|
2032
|
-
#
|
|
2033
|
-
const
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2262
|
+
async #configure(project) {
|
|
2263
|
+
const contained = this.#contain(project);
|
|
2264
|
+
const existing = this.#configs.get(contained);
|
|
2265
|
+
if (existing !== void 0) return existing;
|
|
2266
|
+
this.#refresh();
|
|
2267
|
+
const execution = await this.#spawn([
|
|
2268
|
+
"--showConfig",
|
|
2269
|
+
"-p",
|
|
2270
|
+
contained
|
|
2271
|
+
], this.#mirror);
|
|
2272
|
+
this.#refuseDestroyed();
|
|
2273
|
+
const config = parseProjectConfig(execution.stdout);
|
|
2274
|
+
if (config === void 0) throw this.#fault(execution, contained);
|
|
2275
|
+
this.#configs.set(contained, config);
|
|
2276
|
+
return config;
|
|
2277
|
+
}
|
|
2278
|
+
async #check(project, drafts) {
|
|
2279
|
+
const config = await this.#configure(project);
|
|
2280
|
+
const scratch = this.#scratch(this.#contain(project), config, drafts);
|
|
2281
|
+
const execution = await this.#spawn([
|
|
2282
|
+
"--noEmit",
|
|
2283
|
+
"--pretty",
|
|
2284
|
+
"false",
|
|
2285
|
+
"-p",
|
|
2286
|
+
`./${scratch}`
|
|
2287
|
+
], this.#mirror);
|
|
2288
|
+
this.#refuseDestroyed();
|
|
2289
|
+
const diagnostics = scanDiagnostics(execution.stdout);
|
|
2290
|
+
if (diagnostics.length === 0 && execution.status !== 0) {
|
|
2291
|
+
const stderr = execution.stderr.trim();
|
|
2292
|
+
const message = stderr !== "" ? stderr : execution.status === void 0 ? "The compiler reported no diagnostic and was ended by a signal" : `The compiler reported no diagnostic and exited ${execution.status}`;
|
|
2293
|
+
throw new _src_core.ProbeError(this.#translate(message), {
|
|
2294
|
+
origin: "instrument",
|
|
2295
|
+
code: "malformed",
|
|
2051
2296
|
context: {
|
|
2052
2297
|
stage: this.stage,
|
|
2053
2298
|
project
|
|
2054
2299
|
}
|
|
2055
2300
|
});
|
|
2056
|
-
return {
|
|
2057
|
-
origin: "workspace",
|
|
2058
|
-
path: project,
|
|
2059
|
-
message
|
|
2060
|
-
};
|
|
2061
2301
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2302
|
+
return this.#issues(diagnostics, project);
|
|
2303
|
+
}
|
|
2304
|
+
#scratch(project, config, drafts) {
|
|
2305
|
+
const directory = (0, node_path.dirname)(project);
|
|
2306
|
+
const stem = `${(0, node_path.basename)(project, (0, node_path.extname)(project))}.probe.json`;
|
|
2307
|
+
const contained = directory === "." ? stem : `${directory}/${stem}`;
|
|
2308
|
+
const target = this.#mirrorPath(contained);
|
|
2309
|
+
const buildinfo = (0, node_path.join)(this.#mirror, ".probe", `${project.replaceAll("/", "-")}.tsbuildinfo`);
|
|
2310
|
+
const files = [...(config.files ?? []).filter((entry) => (0, node_fs.existsSync)((0, node_path.resolve)((0, node_path.dirname)(target), entry))), ...drafts.map((draft) => relativeWorkspaceFile((0, node_path.dirname)(target), this.#mirrorPath(this.#contain(draft.path))))];
|
|
2311
|
+
const body = {
|
|
2312
|
+
extends: `./${(0, node_path.basename)(project)}`,
|
|
2313
|
+
compilerOptions: {
|
|
2314
|
+
noEmit: true,
|
|
2315
|
+
declaration: false,
|
|
2316
|
+
emitDeclarationOnly: false,
|
|
2317
|
+
composite: false,
|
|
2318
|
+
incremental: true,
|
|
2319
|
+
tsBuildInfoFile: relativeWorkspaceFile((0, node_path.dirname)(target), buildinfo)
|
|
2320
|
+
},
|
|
2321
|
+
...files.length === 0 ? {} : { files }
|
|
2067
2322
|
};
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2323
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
|
|
2324
|
+
(0, node_fs.writeFileSync)(target, `${JSON.stringify(body, void 0, " ")}\n`, "utf8");
|
|
2325
|
+
return contained;
|
|
2326
|
+
}
|
|
2327
|
+
#issues(diagnostics, project) {
|
|
2328
|
+
const issues = [];
|
|
2329
|
+
for (const diagnostic of diagnostics) {
|
|
2330
|
+
const message = this.#translate(diagnostic.message);
|
|
2331
|
+
const resolved = diagnostic.path === void 0 ? void 0 : (0, node_path.resolve)(this.#mirror, diagnostic.path);
|
|
2332
|
+
const drafted = resolved !== void 0 && this.#drafts.has(escapesRoot(this.#mirror, resolved) ? relativeWorkspaceFile(this.#workspace, resolved) : relativeWorkspaceFile(this.#mirror, resolved));
|
|
2333
|
+
if (resolved === void 0 || !drafted && (0, node_path.extname)(resolved) === ".json") throw new _src_core.ProbeError(message, {
|
|
2334
|
+
origin: "workspace",
|
|
2335
|
+
code: "malformed",
|
|
2336
|
+
context: {
|
|
2337
|
+
stage: this.stage,
|
|
2338
|
+
project
|
|
2339
|
+
}
|
|
2340
|
+
});
|
|
2341
|
+
const path = escapesRoot(this.#mirror, resolved) ? escapesRoot(this.#workspace, resolved) ? normalizePath(resolved) : relativeWorkspaceFile(this.#workspace, resolved) : relativeWorkspaceFile(this.#mirror, resolved);
|
|
2342
|
+
issues.push({
|
|
2343
|
+
origin: "claimant",
|
|
2344
|
+
path,
|
|
2345
|
+
message,
|
|
2346
|
+
...diagnostic.range === void 0 ? {} : { range: diagnostic.range }
|
|
2347
|
+
});
|
|
2348
|
+
}
|
|
2349
|
+
return issues;
|
|
2350
|
+
}
|
|
2351
|
+
#translate(message) {
|
|
2352
|
+
return relativeWorkspaceMessage(this.#workspace, relativeWorkspaceMessage(this.#mirror, message));
|
|
2353
|
+
}
|
|
2354
|
+
#fault(execution, project) {
|
|
2355
|
+
const reported = scanDiagnostics(execution.stdout).map((diagnostic) => diagnostic.message);
|
|
2356
|
+
const text = reported.length > 0 ? reported.join("\n") : execution.stdout.trim();
|
|
2357
|
+
return new _src_core.ProbeError(this.#translate(text === "" ? "The compiler printed no configuration" : text), {
|
|
2358
|
+
origin: "workspace",
|
|
2359
|
+
code: "malformed",
|
|
2360
|
+
context: {
|
|
2361
|
+
stage: this.stage,
|
|
2362
|
+
project
|
|
2075
2363
|
}
|
|
2076
|
-
};
|
|
2364
|
+
});
|
|
2365
|
+
}
|
|
2366
|
+
#spawn(args, cwd) {
|
|
2367
|
+
this.#refuseDestroyed();
|
|
2368
|
+
return new Promise((settle, refuse) => {
|
|
2369
|
+
const child = (0, node_child_process.spawn)(process.execPath, [this.#compiler, ...args], {
|
|
2370
|
+
cwd,
|
|
2371
|
+
stdio: [
|
|
2372
|
+
"ignore",
|
|
2373
|
+
"pipe",
|
|
2374
|
+
"pipe"
|
|
2375
|
+
]
|
|
2376
|
+
});
|
|
2377
|
+
const output = child.stdout;
|
|
2378
|
+
const errors = child.stderr;
|
|
2379
|
+
if (output === null || errors === null) {
|
|
2380
|
+
this.#terminate(child);
|
|
2381
|
+
refuse(new _src_core.ProbeError("The compiler was spawned without its own output streams", {
|
|
2382
|
+
origin: "instrument",
|
|
2383
|
+
code: "malformed",
|
|
2384
|
+
context: { stage: this.stage }
|
|
2385
|
+
}));
|
|
2386
|
+
return;
|
|
2387
|
+
}
|
|
2388
|
+
this.#children.add(child);
|
|
2389
|
+
let stdout = "";
|
|
2390
|
+
let stderr = "";
|
|
2391
|
+
output.setEncoding("utf8");
|
|
2392
|
+
errors.setEncoding("utf8");
|
|
2393
|
+
output.on("data", (chunk) => {
|
|
2394
|
+
stdout += chunk;
|
|
2395
|
+
});
|
|
2396
|
+
errors.on("data", (chunk) => {
|
|
2397
|
+
stderr += chunk;
|
|
2398
|
+
});
|
|
2399
|
+
child.on("error", (error) => {
|
|
2400
|
+
this.#children.delete(child);
|
|
2401
|
+
refuse(new _src_core.ProbeError("The workspace compiler could not be started", {
|
|
2402
|
+
origin: "workspace",
|
|
2403
|
+
code: "malformed",
|
|
2404
|
+
context: {
|
|
2405
|
+
stage: this.stage,
|
|
2406
|
+
path: this.#compiler
|
|
2407
|
+
},
|
|
2408
|
+
cause: error
|
|
2409
|
+
}));
|
|
2410
|
+
});
|
|
2411
|
+
child.on("close", (code) => {
|
|
2412
|
+
this.#children.delete(child);
|
|
2413
|
+
settle({
|
|
2414
|
+
...code === null ? {} : { status: code },
|
|
2415
|
+
stdout,
|
|
2416
|
+
stderr
|
|
2417
|
+
});
|
|
2418
|
+
});
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
#terminate(child) {
|
|
2422
|
+
const id = child.pid;
|
|
2423
|
+
if (process.platform === "win32" && id !== void 0) {
|
|
2424
|
+
(0, node_child_process.spawnSync)("taskkill", [
|
|
2425
|
+
"/pid",
|
|
2426
|
+
String(id),
|
|
2427
|
+
"/t",
|
|
2428
|
+
"/f"
|
|
2429
|
+
], { stdio: "ignore" });
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
child.kill("SIGTERM");
|
|
2077
2433
|
}
|
|
2078
2434
|
};
|
|
2079
2435
|
//#endregion
|
|
2080
2436
|
//#region src/server/Probe.ts
|
|
2081
2437
|
/**
|
|
2082
|
-
* Answers claims through
|
|
2438
|
+
* Answers claims through its type, lint, and runtime stages.
|
|
2083
2439
|
*
|
|
2084
2440
|
* @remarks
|
|
2085
2441
|
* Construction resolves the target workspace's toolchain and begins warming every stage. The boot
|
|
@@ -2091,10 +2447,45 @@ var TypeStage = class {
|
|
|
2091
2447
|
* claim costs that claim rather than the process. A failed boot is replaced the same way: the next
|
|
2092
2448
|
* claim runs the controls again rather than inheriting a refusal.
|
|
2093
2449
|
*
|
|
2094
|
-
* @example
|
|
2450
|
+
* @example The claim that earns a receipt
|
|
2095
2451
|
* ```ts
|
|
2096
|
-
*
|
|
2452
|
+
* import type { Claim } from '@orkestrel/probe'
|
|
2453
|
+
* import { Probe } from '@orkestrel/probe/server'
|
|
2454
|
+
*
|
|
2455
|
+
* const claim: Claim = {
|
|
2456
|
+
* project: 'configs/src/tsconfig.core.json',
|
|
2457
|
+
* case: {
|
|
2458
|
+
* files: [
|
|
2459
|
+
* {
|
|
2460
|
+
* path: 'src/core/factories.ts',
|
|
2461
|
+
* text: "export function createGreeting(): string {\n\treturn 'hi'\n}\n",
|
|
2462
|
+
* },
|
|
2463
|
+
* ],
|
|
2464
|
+
* test: {
|
|
2465
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
2466
|
+
* text: "import { expect, test } from 'vitest'\nimport { createGreeting } from '../../src/core/factories.js'\ntest('greets', () => expect(createGreeting()).toBe('hi'))\n",
|
|
2467
|
+
* },
|
|
2468
|
+
* },
|
|
2469
|
+
* control: {
|
|
2470
|
+
* files: [
|
|
2471
|
+
* {
|
|
2472
|
+
* path: 'src/core/factories.ts',
|
|
2473
|
+
* text: "export function createGreeting(): number {\n\treturn 'hi'\n}\n",
|
|
2474
|
+
* },
|
|
2475
|
+
* ],
|
|
2476
|
+
* test: {
|
|
2477
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
2478
|
+
* text: "import { expect, test } from 'vitest'\nimport { createGreeting } from '../../src/core/factories.js'\ntest('greets', () => expect(createGreeting()).toBe('hi'))\n",
|
|
2479
|
+
* },
|
|
2480
|
+
* stage: 'type',
|
|
2481
|
+
* reason: 'a string returned as a number must not compile',
|
|
2482
|
+
* },
|
|
2483
|
+
* }
|
|
2484
|
+
*
|
|
2485
|
+
* const probe = new Probe({ workspace: process.cwd() })
|
|
2097
2486
|
* const verdict = await probe.prove(claim)
|
|
2487
|
+
* verdict.digest // 'fcb88a2dee987b8673c1fc7107979470'
|
|
2488
|
+
* verdict.receipt // 'probe:fcb88a2dee987b8673c1fc7107979470:type:typescript@6.0.3:oxlint@1.83.0:vitest@4.1.11:configs/src/tsconfig.core.json@434f59254d58cf2683d453a26bd0d837'
|
|
2098
2489
|
* await probe.destroy()
|
|
2099
2490
|
* ```
|
|
2100
2491
|
*/
|
|
@@ -2115,7 +2506,7 @@ var Probe = class {
|
|
|
2115
2506
|
#arming;
|
|
2116
2507
|
#closing;
|
|
2117
2508
|
/**
|
|
2118
|
-
* Resolves the target toolchain and starts warming
|
|
2509
|
+
* Resolves the target toolchain and starts warming every stage.
|
|
2119
2510
|
*
|
|
2120
2511
|
* @param options - Workspace, deadline, and initial observation hooks
|
|
2121
2512
|
*/
|
|
@@ -2280,7 +2671,7 @@ var Probe = class {
|
|
|
2280
2671
|
files: [],
|
|
2281
2672
|
test: typeTest,
|
|
2282
2673
|
stage: "type",
|
|
2283
|
-
reason: "the imported type changed
|
|
2674
|
+
reason: "the imported type changed on disk between the two inspections"
|
|
2284
2675
|
}
|
|
2285
2676
|
};
|
|
2286
2677
|
const runtimeClaim = {
|
|
@@ -2527,12 +2918,16 @@ var Probe = class {
|
|
|
2527
2918
|
//#endregion
|
|
2528
2919
|
//#region src/server/ProbeServer.ts
|
|
2529
2920
|
/**
|
|
2530
|
-
*
|
|
2921
|
+
* Implements `ProbeServerInterface` over a `PassThrough` stream this server owns, binding the
|
|
2922
|
+
* published `prove` tool and the dual-era dispatcher to this process's Model Context Protocol
|
|
2923
|
+
* stdio transport.
|
|
2531
2924
|
*
|
|
2532
2925
|
* @remarks
|
|
2533
|
-
* Construction
|
|
2534
|
-
* the stdio transport.
|
|
2535
|
-
* arming
|
|
2926
|
+
* Construction snapshots the probe options, publishes the `prove` tool, and binds the dual-era
|
|
2927
|
+
* dispatcher to the stdio transport. An admitted `prove` call creates the probe and begins
|
|
2928
|
+
* workspace arming, so discovery does not depend on the workspace toolchain. Construction is held
|
|
2929
|
+
* before it begins, so teardown entered through a construction callback waits for the resulting
|
|
2930
|
+
* probe and releases it. A constructor refusal clears that held operation for a later retry.
|
|
2536
2931
|
*
|
|
2537
2932
|
* `start` seizes standard input and registers the signals a harness ends a child with, and records
|
|
2538
2933
|
* whether standard input was already flowing. Every listener it attaches is held as a field, and
|
|
@@ -2554,7 +2949,7 @@ var Probe = class {
|
|
|
2554
2949
|
* ```
|
|
2555
2950
|
*/
|
|
2556
2951
|
var ProbeServer = class {
|
|
2557
|
-
#
|
|
2952
|
+
#options;
|
|
2558
2953
|
#stream;
|
|
2559
2954
|
#transport;
|
|
2560
2955
|
#data;
|
|
@@ -2563,14 +2958,25 @@ var ProbeServer = class {
|
|
|
2563
2958
|
#signal;
|
|
2564
2959
|
#server;
|
|
2565
2960
|
#owns;
|
|
2961
|
+
#construction;
|
|
2566
2962
|
#closing;
|
|
2567
2963
|
/**
|
|
2568
|
-
*
|
|
2964
|
+
* Snapshots the probe options this server will use and binds the published schema to this
|
|
2965
|
+
* process's stdio transport. An admitted `prove` call creates the probe.
|
|
2569
2966
|
*
|
|
2570
|
-
* @param options - Workspace, deadline, and initial observation hooks for the probe
|
|
2967
|
+
* @param options - Workspace, deadline, and initial observation hooks for the deferred probe
|
|
2571
2968
|
*/
|
|
2572
2969
|
constructor(options) {
|
|
2573
|
-
|
|
2970
|
+
const workspace = options?.workspace;
|
|
2971
|
+
const deadline = options?.deadline;
|
|
2972
|
+
const on = options?.on;
|
|
2973
|
+
const error = options?.error;
|
|
2974
|
+
this.#options = Object.freeze({
|
|
2975
|
+
workspace: (0, node_path.resolve)(workspace ?? process.cwd()),
|
|
2976
|
+
...deadline === void 0 ? {} : { deadline },
|
|
2977
|
+
...on === void 0 ? {} : { on: Object.freeze({ ...on }) },
|
|
2978
|
+
...error === void 0 ? {} : { error }
|
|
2979
|
+
});
|
|
2574
2980
|
this.#stream = new node_stream.PassThrough();
|
|
2575
2981
|
this.#server = this.#publish();
|
|
2576
2982
|
this.#transport = (0, _orkestrel_mcp_server.createStdioServer)((0, _orkestrel_mcp.createMCPLegacy)(this.#server), { input: this.#stream });
|
|
@@ -2605,7 +3011,10 @@ var ProbeServer = class {
|
|
|
2605
3011
|
if (this.#owns === true && process.stdin.listenerCount("data") === 0) process.stdin.pause();
|
|
2606
3012
|
this.#owns = void 0;
|
|
2607
3013
|
this.#stream.destroy();
|
|
2608
|
-
|
|
3014
|
+
const construction = this.#construction;
|
|
3015
|
+
if (construction === void 0) return;
|
|
3016
|
+
const probe = await construction.catch(() => void 0);
|
|
3017
|
+
if (probe !== void 0) await probe.destroy();
|
|
2609
3018
|
}
|
|
2610
3019
|
#receive(chunk) {
|
|
2611
3020
|
this.#stream.write(chunk);
|
|
@@ -2651,7 +3060,20 @@ var ProbeServer = class {
|
|
|
2651
3060
|
context: { value: input }
|
|
2652
3061
|
});
|
|
2653
3062
|
}
|
|
2654
|
-
return this.#
|
|
3063
|
+
return (await this.#resolveProbe()).prove(input);
|
|
3064
|
+
}
|
|
3065
|
+
async #resolveProbe() {
|
|
3066
|
+
if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("probe server");
|
|
3067
|
+
const current = this.#construction;
|
|
3068
|
+
if (current !== void 0) return await current;
|
|
3069
|
+
const construction = Promise.resolve().then(() => new Probe(this.#options));
|
|
3070
|
+
this.#construction = construction;
|
|
3071
|
+
try {
|
|
3072
|
+
return await construction;
|
|
3073
|
+
} catch (error) {
|
|
3074
|
+
if (this.#construction === construction) this.#construction = void 0;
|
|
3075
|
+
throw error;
|
|
3076
|
+
}
|
|
2655
3077
|
}
|
|
2656
3078
|
async #execute(context) {
|
|
2657
3079
|
const result = await context.tools.execute(context.call);
|
|
@@ -2701,20 +3123,25 @@ exports.RuntimeStage = RuntimeStage;
|
|
|
2701
3123
|
exports.TypeStage = TypeStage;
|
|
2702
3124
|
exports.buildRevisionPath = buildRevisionPath;
|
|
2703
3125
|
exports.captureListeners = captureListeners;
|
|
3126
|
+
exports.collectWorkspaceFiles = collectWorkspaceFiles;
|
|
2704
3127
|
exports.computeDigest = computeDigest;
|
|
2705
3128
|
exports.describeUnknown = describeUnknown;
|
|
2706
3129
|
exports.escapesRoot = escapesRoot;
|
|
3130
|
+
exports.filterUniqueIssues = filterUniqueIssues;
|
|
2707
3131
|
exports.findRefusedPaths = findRefusedPaths;
|
|
2708
3132
|
exports.guardStage = guardStage;
|
|
2709
3133
|
exports.inferDocumentLanguage = inferDocumentLanguage;
|
|
2710
3134
|
exports.inferTestProject = inferTestProject;
|
|
2711
3135
|
exports.inferTypeProject = inferTypeProject;
|
|
2712
3136
|
exports.isRefusedName = isRefusedName;
|
|
2713
|
-
exports.
|
|
3137
|
+
exports.loadWorkspaceVitest = loadWorkspaceVitest;
|
|
3138
|
+
exports.matchesLiveProcess = matchesLiveProcess;
|
|
2714
3139
|
exports.matchesWorkspaceModule = matchesWorkspaceModule;
|
|
2715
3140
|
exports.normalizePath = normalizePath;
|
|
2716
3141
|
exports.normalizeValue = normalizeValue;
|
|
2717
3142
|
exports.overwriteFile = overwriteFile;
|
|
3143
|
+
exports.parseProjectConfig = parseProjectConfig;
|
|
3144
|
+
exports.parseRevisionOwner = parseRevisionOwner;
|
|
2718
3145
|
exports.readFaultCode = readFaultCode;
|
|
2719
3146
|
exports.readWorkspaceManifest = readWorkspaceManifest;
|
|
2720
3147
|
exports.relativeWorkspaceFile = relativeWorkspaceFile;
|
|
@@ -2723,5 +3150,6 @@ exports.releaseListeners = releaseListeners;
|
|
|
2723
3150
|
exports.resolveWorkspaceBinary = resolveWorkspaceBinary;
|
|
2724
3151
|
exports.resolveWorkspaceFile = resolveWorkspaceFile;
|
|
2725
3152
|
exports.resolveWorkspaceModule = resolveWorkspaceModule;
|
|
3153
|
+
exports.scanDiagnostics = scanDiagnostics;
|
|
2726
3154
|
|
|
2727
3155
|
//# sourceMappingURL=index.cjs.map
|