@orkestrel/probe 0.0.12 → 0.0.13
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 +712 -316
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +447 -136
- package/dist/src/server/index.d.ts +447 -136
- package/dist/src/server/index.js +707 -317
- 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.13";
|
|
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
|
};
|
|
@@ -1166,7 +1391,7 @@ var RuntimeStage = class {
|
|
|
1166
1391
|
*/
|
|
1167
1392
|
constructor(workspace = process.cwd()) {
|
|
1168
1393
|
this.#workspace = workspace;
|
|
1169
|
-
const vitest =
|
|
1394
|
+
const vitest = loadWorkspaceVitest(this.#workspace);
|
|
1170
1395
|
this.#store(this.#warm(vitest.createVitest));
|
|
1171
1396
|
}
|
|
1172
1397
|
get stage() {
|
|
@@ -1175,9 +1400,29 @@ var RuntimeStage = class {
|
|
|
1175
1400
|
get progress() {
|
|
1176
1401
|
return this.#progress;
|
|
1177
1402
|
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Inspects one case.
|
|
1405
|
+
*
|
|
1406
|
+
* @param subject - The candidate drafts and test to inspect
|
|
1407
|
+
* @returns One outcome for this stage
|
|
1408
|
+
* @throws When the workspace's own tool cannot start or has already been destroyed
|
|
1409
|
+
*/
|
|
1178
1410
|
inspect(subject) {
|
|
1179
1411
|
return guardStage(this.stage, this.#inspect(subject));
|
|
1180
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Tears down the resident tool or the mirror and releases its resources.
|
|
1415
|
+
*
|
|
1416
|
+
* @remarks
|
|
1417
|
+
* A stage abandons every inspection it holds rather than waiting behind one, so teardown never
|
|
1418
|
+
* waits for an inspection to return. An abandoned inspection rejects, either at the stage's own
|
|
1419
|
+
* guard or as the owned tool closes. Teardown is bounded whatever the stage's own tool does: a
|
|
1420
|
+
* tool that answers neither its warming exchange nor its ending is signalled and released at the
|
|
1421
|
+
* stage's own deadline. A coordinator replaces a stage whose worker no longer returns because
|
|
1422
|
+
* teardown neither waits for an inspection nor waits past that deadline.
|
|
1423
|
+
*
|
|
1424
|
+
* @returns A promise that settles after the resident tool or the mirror releases its resources
|
|
1425
|
+
*/
|
|
1181
1426
|
destroy() {
|
|
1182
1427
|
if (this.#closing !== void 0) return this.#closing;
|
|
1183
1428
|
this.#closing = guardStage(this.stage, this.#destroy());
|
|
@@ -1521,13 +1766,13 @@ var RuntimeStage = class {
|
|
|
1521
1766
|
const current = this.#vitest;
|
|
1522
1767
|
if (current !== void 0 && this.#specifications < _src_core.PROBE_SPECIFICATIONS) return current;
|
|
1523
1768
|
if (current !== void 0) return this.#store(this.#replace(current));
|
|
1524
|
-
const runner =
|
|
1769
|
+
const runner = loadWorkspaceVitest(this.#workspace);
|
|
1525
1770
|
return this.#store(this.#specifications < _src_core.PROBE_SPECIFICATIONS ? this.#warm(runner.createVitest) : this.#replace(void 0, runner.createVitest));
|
|
1526
1771
|
}
|
|
1527
1772
|
async #replace(current, create) {
|
|
1528
1773
|
if (current !== void 0) await (await current).close();
|
|
1529
1774
|
if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("runtime stage");
|
|
1530
|
-
const runner = create ??
|
|
1775
|
+
const runner = create ?? loadWorkspaceVitest(this.#workspace).createVitest;
|
|
1531
1776
|
const replacement = await this.#warm(runner);
|
|
1532
1777
|
this.#specifications = 0;
|
|
1533
1778
|
return replacement;
|
|
@@ -1571,7 +1816,7 @@ var RuntimeStage = class {
|
|
|
1571
1816
|
}
|
|
1572
1817
|
#snapshot() {
|
|
1573
1818
|
const modules = /* @__PURE__ */ new Map();
|
|
1574
|
-
for (const path of this.#
|
|
1819
|
+
for (const path of collectWorkspaceFiles(this.#workspace)) {
|
|
1575
1820
|
if (!matchesWorkspaceModule(path)) continue;
|
|
1576
1821
|
try {
|
|
1577
1822
|
const digest = (0, node_crypto.createHash)("sha256").update((0, node_fs.readFileSync)(path)).digest("hex");
|
|
@@ -1582,12 +1827,12 @@ var RuntimeStage = class {
|
|
|
1582
1827
|
return modules;
|
|
1583
1828
|
}
|
|
1584
1829
|
#sweep() {
|
|
1585
|
-
for (const path of this.#
|
|
1830
|
+
for (const path of collectWorkspaceFiles(this.#workspace)) {
|
|
1586
1831
|
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
1832
|
const revision = match?.[1];
|
|
1588
1833
|
const owner = match?.[2];
|
|
1589
1834
|
if (revision === void 0 || owner === void 0) continue;
|
|
1590
|
-
if (
|
|
1835
|
+
if (matchesLiveProcess(Number.parseInt(owner, 10))) continue;
|
|
1591
1836
|
if (!this.#owned(path, revision)) continue;
|
|
1592
1837
|
try {
|
|
1593
1838
|
(0, node_fs.unlinkSync)(resolveWorkspaceFile(this.#workspace, (0, node_path.relative)(this.#workspace, path), true));
|
|
@@ -1598,35 +1843,6 @@ var RuntimeStage = class {
|
|
|
1598
1843
|
const outcome = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)(path, "utf8"));
|
|
1599
1844
|
return outcome.success && (0, _src_core.matchesSpecification)(outcome.value, revision);
|
|
1600
1845
|
}
|
|
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
1846
|
#issues(result, file, original) {
|
|
1631
1847
|
const specification = this.#real(file);
|
|
1632
1848
|
const issues = [];
|
|
@@ -1742,21 +1958,32 @@ var RuntimeStage = class {
|
|
|
1742
1958
|
//#endregion
|
|
1743
1959
|
//#region src/server/stages/TypeStage.ts
|
|
1744
1960
|
/**
|
|
1745
|
-
* Inspects TypeScript source
|
|
1961
|
+
* Inspects TypeScript source by running the target workspace's own compiler over a mirror of it.
|
|
1746
1962
|
*
|
|
1747
1963
|
* @remarks
|
|
1748
|
-
*
|
|
1749
|
-
*
|
|
1750
|
-
*
|
|
1751
|
-
*
|
|
1752
|
-
*
|
|
1753
|
-
*
|
|
1754
|
-
*
|
|
1755
|
-
*
|
|
1756
|
-
*
|
|
1757
|
-
*
|
|
1758
|
-
*
|
|
1759
|
-
*
|
|
1964
|
+
* The compiler is a process this stage spawns rather than a module it loads, so nothing here holds
|
|
1965
|
+
* a resident program and the host's event loop is free for the whole check. Construction sweeps a
|
|
1966
|
+
* mirror an earlier host left behind, copies the workspace into a fresh one under
|
|
1967
|
+
* {@link TYPE_MIRROR}, and builds each declared project's incremental state there, so the first
|
|
1968
|
+
* inspection is warm.
|
|
1969
|
+
*
|
|
1970
|
+
* Every inspection refreshes the mirror by content digest, writes each candidate draft and the test
|
|
1971
|
+
* at its mirrored declared path, and runs `tsc --noEmit --pretty false` once per distinct selected
|
|
1972
|
+
* project. A draft therefore shadows the file it replaces: a consumer of that path is checked
|
|
1973
|
+
* against the draft's text, and a draft importing a sibling draft resolves to the sibling draft.
|
|
1974
|
+
* Nothing is written outside `tmp/`, and the workspace's own copy of a drafted file never moves.
|
|
1975
|
+
*
|
|
1976
|
+
* Each run reads a scratch project this stage writes beside the mirrored project it extends, so
|
|
1977
|
+
* every relative path the project declares — its own `rootDir`, `include`, `paths`, and the
|
|
1978
|
+
* projects it extends — resolves inside the mirror without being rewritten. The scratch adds the
|
|
1979
|
+
* drafts as `files`, which the project's own `exclude` does not reach, and its incremental state
|
|
1980
|
+
* lives in the mirror so reuse survives the refresh.
|
|
1981
|
+
*
|
|
1982
|
+
* The diagnostics decide the outcome, never the exit code, which the supported compiler majors
|
|
1983
|
+
* disagree on. A diagnostic naming a project file, and one naming no file at all, is the target
|
|
1984
|
+
* tree's own configuration fault and raises rather than reporting, unless the `.json` file is one
|
|
1985
|
+
* the claim itself drafted; every other diagnostic is a claimant issue at the point the compiler
|
|
1986
|
+
* reported, which is the extent the plain-text output carries.
|
|
1760
1987
|
*
|
|
1761
1988
|
* @example
|
|
1762
1989
|
* ```ts
|
|
@@ -1767,26 +1994,27 @@ var RuntimeStage = class {
|
|
|
1767
1994
|
*/
|
|
1768
1995
|
var TypeStage = class {
|
|
1769
1996
|
#workspace;
|
|
1770
|
-
#
|
|
1771
|
-
#
|
|
1772
|
-
#
|
|
1773
|
-
#
|
|
1774
|
-
#
|
|
1775
|
-
#
|
|
1776
|
-
#
|
|
1777
|
-
#
|
|
1997
|
+
#compiler;
|
|
1998
|
+
#revision = `${process.pid}-${(0, node_crypto.randomUUID)()}`;
|
|
1999
|
+
#mirror;
|
|
2000
|
+
#configs = /* @__PURE__ */ new Map();
|
|
2001
|
+
#mirrored = /* @__PURE__ */ new Map();
|
|
2002
|
+
#drafts = /* @__PURE__ */ new Set();
|
|
2003
|
+
#children = /* @__PURE__ */ new Set();
|
|
2004
|
+
#warming;
|
|
1778
2005
|
#closing;
|
|
1779
2006
|
#progress = 0;
|
|
1780
2007
|
/**
|
|
1781
|
-
*
|
|
2008
|
+
* Resolves the target workspace's compiler and starts warming its mirror.
|
|
1782
2009
|
*
|
|
1783
2010
|
* @param workspace - The target workspace root. Default: the current working directory
|
|
1784
2011
|
*/
|
|
1785
2012
|
constructor(workspace = process.cwd()) {
|
|
1786
2013
|
this.#workspace = workspace;
|
|
1787
|
-
|
|
1788
|
-
this.#
|
|
1789
|
-
this.#
|
|
2014
|
+
this.#compiler = resolveWorkspaceBinary(workspace, "typescript", "tsc");
|
|
2015
|
+
this.#mirror = resolveWorkspaceFile(workspace, `${_src_core.TYPE_MIRROR}/${this.#revision}`);
|
|
2016
|
+
this.#warming = this.#warm();
|
|
2017
|
+
this.#warming.catch(() => {});
|
|
1790
2018
|
}
|
|
1791
2019
|
get stage() {
|
|
1792
2020
|
return "type";
|
|
@@ -1806,7 +2034,8 @@ var TypeStage = class {
|
|
|
1806
2034
|
* @param project - The workspace-relative TypeScript project the candidate drafts are checked
|
|
1807
2035
|
* against. Default: the scoped project each candidate path infers
|
|
1808
2036
|
* @returns One outcome for this stage
|
|
1809
|
-
* @throws When the
|
|
2037
|
+
* @throws When the workspace refuses the mirror, when a project the run reads is malformed, or
|
|
2038
|
+
* when the stage has already been destroyed
|
|
1810
2039
|
*/
|
|
1811
2040
|
inspect(subject, project) {
|
|
1812
2041
|
return guardStage(this.stage, this.#inspect(subject, project));
|
|
@@ -1815,15 +2044,15 @@ var TypeStage = class {
|
|
|
1815
2044
|
* Resolves one project to the path and digest this stage applies for it.
|
|
1816
2045
|
*
|
|
1817
2046
|
* @remarks
|
|
1818
|
-
* Reads the
|
|
1819
|
-
*
|
|
1820
|
-
*
|
|
1821
|
-
*
|
|
2047
|
+
* Reads the configuration the compiler itself prints for the project as the mirror holds it,
|
|
2048
|
+
* refreshed from the workspace first on a cache miss, so the digest names the configuration the
|
|
2049
|
+
* check applies. The reading is cached per project for the life of the stage, so a draft written
|
|
2050
|
+
* afterward cannot move it.
|
|
1822
2051
|
*
|
|
1823
2052
|
* @param project - The workspace-relative TypeScript project to resolve
|
|
1824
2053
|
* @returns The resolved workspace-relative path and the digest of its compiler options
|
|
1825
|
-
* @throws When the project escapes the workspace,
|
|
1826
|
-
* been destroyed
|
|
2054
|
+
* @throws When the project escapes the workspace, when the compiler refuses it, or when the
|
|
2055
|
+
* stage has already been destroyed
|
|
1827
2056
|
*/
|
|
1828
2057
|
resolve(project) {
|
|
1829
2058
|
return guardStage(this.stage, this.#resolve(project));
|
|
@@ -1834,81 +2063,76 @@ var TypeStage = class {
|
|
|
1834
2063
|
return this.#closing;
|
|
1835
2064
|
}
|
|
1836
2065
|
async #inspect(subject, project) {
|
|
1837
|
-
|
|
2066
|
+
this.#refuseDestroyed();
|
|
1838
2067
|
const started = performance.now();
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
const
|
|
2068
|
+
await this.#warming;
|
|
2069
|
+
this.#refuseDestroyed();
|
|
2070
|
+
const test = resolveWorkspaceFile(this.#workspace, subject.test.path);
|
|
2071
|
+
const resolved = subject.files.map((draft) => ({
|
|
1842
2072
|
draft,
|
|
1843
2073
|
path: resolveWorkspaceFile(this.#workspace, draft.path)
|
|
1844
|
-
})).map((candidate) => ({
|
|
1845
|
-
draft: candidate.draft,
|
|
1846
|
-
project: project ?? inferTypeProject(relativeWorkspaceFile(this.#workspace, candidate.path))
|
|
1847
2074
|
}));
|
|
1848
|
-
const
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
2075
|
+
const groups = /* @__PURE__ */ new Map([[this.#contain("tsconfig.json"), [subject.test]]]);
|
|
2076
|
+
for (const candidate of resolved) {
|
|
2077
|
+
const selected = this.#contain(project ?? inferTypeProject(relativeWorkspaceFile(this.#workspace, candidate.path)));
|
|
2078
|
+
const drafts = groups.get(selected);
|
|
2079
|
+
if (drafts === void 0) groups.set(selected, [candidate.draft]);
|
|
2080
|
+
else drafts.push(candidate.draft);
|
|
1854
2081
|
}
|
|
1855
|
-
|
|
1856
|
-
this.#
|
|
2082
|
+
this.#progress += 1;
|
|
2083
|
+
for (const selected of groups.keys()) await this.#configure(selected);
|
|
2084
|
+
this.#refuseDestroyed();
|
|
1857
2085
|
try {
|
|
1858
|
-
this.#
|
|
1859
|
-
|
|
1860
|
-
this.#
|
|
2086
|
+
this.#refresh();
|
|
2087
|
+
this.#drafts.add(this.#place(subject.test.text, test));
|
|
2088
|
+
for (const candidate of resolved) this.#drafts.add(this.#place(candidate.draft.text, candidate.path));
|
|
1861
2089
|
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();
|
|
2090
|
+
for (const [selected, drafts] of groups) {
|
|
2091
|
+
this.#refuseDestroyed();
|
|
2092
|
+
issues.push(...await this.#check(selected, drafts));
|
|
1873
2093
|
}
|
|
1874
2094
|
return {
|
|
1875
2095
|
stage: this.stage,
|
|
1876
2096
|
elapsed: Math.round(performance.now() - started),
|
|
1877
|
-
issues
|
|
2097
|
+
issues: filterUniqueIssues(issues)
|
|
1878
2098
|
};
|
|
1879
2099
|
} finally {
|
|
1880
|
-
|
|
2100
|
+
this.#release();
|
|
1881
2101
|
}
|
|
1882
2102
|
}
|
|
1883
2103
|
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);
|
|
2104
|
+
this.#refuseDestroyed();
|
|
2105
|
+
const contained = this.#contain(project);
|
|
1888
2106
|
this.#progress += 1;
|
|
1889
|
-
this.#
|
|
1890
|
-
await this.#unblock();
|
|
2107
|
+
const config = await this.#configure(contained);
|
|
1891
2108
|
return {
|
|
1892
|
-
path:
|
|
1893
|
-
digest: computeDigest(this.#workspace,
|
|
2109
|
+
path: contained,
|
|
2110
|
+
digest: computeDigest(this.#workspace, config.compilerOptions)
|
|
1894
2111
|
};
|
|
1895
2112
|
}
|
|
1896
2113
|
async #destroy() {
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
this.#
|
|
1900
|
-
this.#
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2114
|
+
const children = [...this.#children];
|
|
2115
|
+
this.#children.clear();
|
|
2116
|
+
for (const child of children) this.#terminate(child);
|
|
2117
|
+
await this.#warming.catch(() => void 0);
|
|
2118
|
+
try {
|
|
2119
|
+
(0, node_fs.rmSync)(this.#mirror, {
|
|
2120
|
+
recursive: true,
|
|
2121
|
+
force: true,
|
|
2122
|
+
maxRetries: 5,
|
|
2123
|
+
retryDelay: 20
|
|
2124
|
+
});
|
|
2125
|
+
} catch {}
|
|
2126
|
+
this.#configs.clear();
|
|
2127
|
+
this.#mirrored.clear();
|
|
2128
|
+
this.#drafts.clear();
|
|
2129
|
+
}
|
|
2130
|
+
async #warm() {
|
|
2131
|
+
this.#sweep();
|
|
2132
|
+
this.#createMirror();
|
|
2133
|
+
this.#refresh();
|
|
2134
|
+
this.#refuseDestroyed();
|
|
2135
|
+
await Promise.all(this.#projects().map((project) => this.#check(project, [])));
|
|
1912
2136
|
}
|
|
1913
2137
|
#projects() {
|
|
1914
2138
|
const projects = ["tsconfig.json"];
|
|
@@ -1924,162 +2148,291 @@ var TypeStage = class {
|
|
|
1924
2148
|
}
|
|
1925
2149
|
return projects;
|
|
1926
2150
|
}
|
|
1927
|
-
|
|
1928
|
-
await (0, node_timers_promises.setTimeout)(0);
|
|
2151
|
+
#refuseDestroyed() {
|
|
1929
2152
|
if (this.#closing !== void 0) throw (0, _src_core.createDestroyedError)("type stage");
|
|
1930
2153
|
}
|
|
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
|
-
}
|
|
2154
|
+
#createMirror() {
|
|
2155
|
+
const marker = resolveWorkspaceFile(this.#workspace, `${_src_core.TYPE_MIRROR}/${this.#revision}/.probe/mirror.txt`, true);
|
|
2156
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(marker), { recursive: true });
|
|
2157
|
+
(0, node_fs.writeFileSync)(marker, (0, _src_core.formatSpecification)("", this.#revision), {
|
|
2158
|
+
encoding: "utf8",
|
|
2159
|
+
flag: "wx"
|
|
1947
2160
|
});
|
|
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
|
-
}
|
|
1985
|
-
#directory() {
|
|
1986
|
-
return this.#workspace;
|
|
1987
|
-
}
|
|
1988
|
-
#defaultLibrary(typescript, options) {
|
|
1989
|
-
return typescript.getDefaultLibFilePath(options);
|
|
1990
|
-
}
|
|
1991
|
-
#fileExists(typescript, file) {
|
|
1992
|
-
return this.#overlay.text(file) !== void 0 || typescript.sys.fileExists(file);
|
|
1993
|
-
}
|
|
1994
|
-
#readFile(typescript, file) {
|
|
1995
|
-
return this.#overlay.text(file) ?? typescript.sys.readFile(file);
|
|
1996
2161
|
}
|
|
1997
|
-
#
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2162
|
+
#sweep() {
|
|
2163
|
+
const root = resolveWorkspaceFile(this.#workspace, _src_core.TYPE_MIRROR);
|
|
2164
|
+
let entries = [];
|
|
2165
|
+
try {
|
|
2166
|
+
entries = (0, node_fs.readdirSync)(root, { withFileTypes: true });
|
|
2167
|
+
} catch {
|
|
2168
|
+
return;
|
|
2169
|
+
}
|
|
2170
|
+
for (const entry of entries) {
|
|
2171
|
+
if (!entry.isDirectory()) continue;
|
|
2172
|
+
const owner = parseRevisionOwner(entry.name);
|
|
2173
|
+
if (owner === void 0 || matchesLiveProcess(owner)) continue;
|
|
2174
|
+
const marker = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)((0, node_path.join)(root, entry.name, ".probe", "mirror.txt"), "utf8"));
|
|
2175
|
+
if (!marker.success || !(0, _src_core.matchesSpecification)(marker.value, entry.name)) continue;
|
|
2176
|
+
try {
|
|
2177
|
+
(0, node_fs.rmSync)((0, node_path.join)(root, entry.name), {
|
|
2178
|
+
recursive: true,
|
|
2179
|
+
force: true,
|
|
2180
|
+
maxRetries: 5,
|
|
2181
|
+
retryDelay: 20
|
|
2182
|
+
});
|
|
2183
|
+
} catch {}
|
|
2184
|
+
}
|
|
2002
2185
|
}
|
|
2003
|
-
#
|
|
2004
|
-
|
|
2186
|
+
#refresh() {
|
|
2187
|
+
const present = /* @__PURE__ */ new Set();
|
|
2188
|
+
for (const path of collectWorkspaceFiles(this.#workspace)) {
|
|
2189
|
+
const contained = relativeWorkspaceFile(this.#workspace, path);
|
|
2190
|
+
present.add(contained);
|
|
2191
|
+
const reading = (0, _orkestrel_contract.attempt)(() => (0, node_fs.readFileSync)(path));
|
|
2192
|
+
if (!reading.success) continue;
|
|
2193
|
+
const digest = (0, node_crypto.createHash)("sha256").update(reading.value).digest("hex");
|
|
2194
|
+
if (this.#mirrored.get(contained) === digest) continue;
|
|
2195
|
+
this.#place(reading.value, path);
|
|
2196
|
+
this.#mirrored.set(contained, digest);
|
|
2197
|
+
}
|
|
2198
|
+
for (const contained of [...this.#mirrored.keys()]) {
|
|
2199
|
+
if (present.has(contained)) continue;
|
|
2200
|
+
this.#remove(contained);
|
|
2201
|
+
this.#mirrored.delete(contained);
|
|
2202
|
+
}
|
|
2005
2203
|
}
|
|
2006
|
-
#
|
|
2007
|
-
|
|
2204
|
+
#release() {
|
|
2205
|
+
for (const contained of this.#drafts) {
|
|
2206
|
+
this.#remove(contained);
|
|
2207
|
+
this.#mirrored.delete(contained);
|
|
2208
|
+
}
|
|
2209
|
+
this.#drafts.clear();
|
|
2210
|
+
}
|
|
2211
|
+
#place(text, path) {
|
|
2212
|
+
const contained = relativeWorkspaceFile(this.#workspace, path);
|
|
2213
|
+
const target = this.#mirrorPath(contained);
|
|
2214
|
+
if ((0, _orkestrel_contract.attempt)(() => {
|
|
2215
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
|
|
2216
|
+
(0, node_fs.writeFileSync)(target, text);
|
|
2217
|
+
}).success) return contained;
|
|
2218
|
+
this.#displace(target);
|
|
2219
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
|
|
2220
|
+
(0, node_fs.writeFileSync)(target, text);
|
|
2221
|
+
return contained;
|
|
2222
|
+
}
|
|
2223
|
+
#displace(target) {
|
|
2224
|
+
const ancestors = [];
|
|
2225
|
+
let directory = (0, node_path.dirname)(target);
|
|
2226
|
+
while (directory !== this.#mirror && directory !== (0, node_path.dirname)(directory)) {
|
|
2227
|
+
ancestors.unshift(directory);
|
|
2228
|
+
directory = (0, node_path.dirname)(directory);
|
|
2229
|
+
}
|
|
2230
|
+
for (const ancestor of [...ancestors, target]) {
|
|
2231
|
+
const reading = (0, _orkestrel_contract.attempt)(() => (0, node_fs.lstatSync)(ancestor));
|
|
2232
|
+
if (!reading.success) continue;
|
|
2233
|
+
if (ancestor !== target && reading.value.isDirectory()) continue;
|
|
2234
|
+
if (ancestor === target && !reading.value.isDirectory()) continue;
|
|
2235
|
+
(0, node_fs.rmSync)(ancestor, {
|
|
2236
|
+
recursive: true,
|
|
2237
|
+
force: true,
|
|
2238
|
+
maxRetries: 5,
|
|
2239
|
+
retryDelay: 20
|
|
2240
|
+
});
|
|
2241
|
+
this.#mirrored.delete(relativeWorkspaceFile(this.#mirror, ancestor));
|
|
2242
|
+
}
|
|
2008
2243
|
}
|
|
2009
|
-
#
|
|
2010
|
-
|
|
2011
|
-
if (this.#diagnostics.has(path)) return;
|
|
2012
|
-
this.#diagnostics.set(path, service.getCompilerOptionsDiagnostics());
|
|
2244
|
+
#mirrorPath(contained) {
|
|
2245
|
+
return (0, node_path.join)(this.#mirror, contained);
|
|
2013
2246
|
}
|
|
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);
|
|
2247
|
+
#contain(project) {
|
|
2248
|
+
return relativeWorkspaceFile(this.#workspace, resolveWorkspaceFile(this.#workspace, project));
|
|
2023
2249
|
}
|
|
2024
|
-
#
|
|
2025
|
-
if (this.#overlay.text(file) !== void 0) return `virtual:${this.#overlay.revision}`;
|
|
2250
|
+
#remove(contained) {
|
|
2026
2251
|
try {
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2252
|
+
(0, node_fs.rmSync)(this.#mirrorPath(contained), {
|
|
2253
|
+
force: true,
|
|
2254
|
+
maxRetries: 5,
|
|
2255
|
+
retryDelay: 20
|
|
2256
|
+
});
|
|
2257
|
+
} catch {}
|
|
2031
2258
|
}
|
|
2032
|
-
#
|
|
2033
|
-
const
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
const
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2259
|
+
async #configure(project) {
|
|
2260
|
+
const contained = this.#contain(project);
|
|
2261
|
+
const existing = this.#configs.get(contained);
|
|
2262
|
+
if (existing !== void 0) return existing;
|
|
2263
|
+
this.#refresh();
|
|
2264
|
+
const execution = await this.#spawn([
|
|
2265
|
+
"--showConfig",
|
|
2266
|
+
"-p",
|
|
2267
|
+
contained
|
|
2268
|
+
], this.#mirror);
|
|
2269
|
+
this.#refuseDestroyed();
|
|
2270
|
+
const config = parseProjectConfig(execution.stdout);
|
|
2271
|
+
if (config === void 0) throw this.#fault(execution, contained);
|
|
2272
|
+
this.#configs.set(contained, config);
|
|
2273
|
+
return config;
|
|
2274
|
+
}
|
|
2275
|
+
async #check(project, drafts) {
|
|
2276
|
+
const config = await this.#configure(project);
|
|
2277
|
+
const scratch = this.#scratch(this.#contain(project), config, drafts);
|
|
2278
|
+
const execution = await this.#spawn([
|
|
2279
|
+
"--noEmit",
|
|
2280
|
+
"--pretty",
|
|
2281
|
+
"false",
|
|
2282
|
+
"-p",
|
|
2283
|
+
`./${scratch}`
|
|
2284
|
+
], this.#mirror);
|
|
2285
|
+
this.#refuseDestroyed();
|
|
2286
|
+
const diagnostics = scanDiagnostics(execution.stdout);
|
|
2287
|
+
if (diagnostics.length === 0 && execution.status !== 0) {
|
|
2288
|
+
const stderr = execution.stderr.trim();
|
|
2289
|
+
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}`;
|
|
2290
|
+
throw new _src_core.ProbeError(this.#translate(message), {
|
|
2291
|
+
origin: "instrument",
|
|
2292
|
+
code: "malformed",
|
|
2051
2293
|
context: {
|
|
2052
2294
|
stage: this.stage,
|
|
2053
2295
|
project
|
|
2054
2296
|
}
|
|
2055
2297
|
});
|
|
2056
|
-
return {
|
|
2057
|
-
origin: "workspace",
|
|
2058
|
-
path: project,
|
|
2059
|
-
message
|
|
2060
|
-
};
|
|
2061
2298
|
}
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2299
|
+
return this.#issues(diagnostics, project);
|
|
2300
|
+
}
|
|
2301
|
+
#scratch(project, config, drafts) {
|
|
2302
|
+
const directory = (0, node_path.dirname)(project);
|
|
2303
|
+
const stem = `${(0, node_path.basename)(project, (0, node_path.extname)(project))}.probe.json`;
|
|
2304
|
+
const contained = directory === "." ? stem : `${directory}/${stem}`;
|
|
2305
|
+
const target = this.#mirrorPath(contained);
|
|
2306
|
+
const buildinfo = (0, node_path.join)(this.#mirror, ".probe", `${project.replaceAll("/", "-")}.tsbuildinfo`);
|
|
2307
|
+
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))))];
|
|
2308
|
+
const body = {
|
|
2309
|
+
extends: `./${(0, node_path.basename)(project)}`,
|
|
2310
|
+
compilerOptions: {
|
|
2311
|
+
noEmit: true,
|
|
2312
|
+
declaration: false,
|
|
2313
|
+
emitDeclarationOnly: false,
|
|
2314
|
+
composite: false,
|
|
2315
|
+
incremental: true,
|
|
2316
|
+
tsBuildInfoFile: relativeWorkspaceFile((0, node_path.dirname)(target), buildinfo)
|
|
2317
|
+
},
|
|
2318
|
+
...files.length === 0 ? {} : { files }
|
|
2067
2319
|
};
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2320
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(target), { recursive: true });
|
|
2321
|
+
(0, node_fs.writeFileSync)(target, `${JSON.stringify(body, void 0, " ")}\n`, "utf8");
|
|
2322
|
+
return contained;
|
|
2323
|
+
}
|
|
2324
|
+
#issues(diagnostics, project) {
|
|
2325
|
+
const issues = [];
|
|
2326
|
+
for (const diagnostic of diagnostics) {
|
|
2327
|
+
const message = this.#translate(diagnostic.message);
|
|
2328
|
+
const resolved = diagnostic.path === void 0 ? void 0 : (0, node_path.resolve)(this.#mirror, diagnostic.path);
|
|
2329
|
+
const drafted = resolved !== void 0 && this.#drafts.has(escapesRoot(this.#mirror, resolved) ? relativeWorkspaceFile(this.#workspace, resolved) : relativeWorkspaceFile(this.#mirror, resolved));
|
|
2330
|
+
if (resolved === void 0 || !drafted && (0, node_path.extname)(resolved) === ".json") throw new _src_core.ProbeError(message, {
|
|
2331
|
+
origin: "workspace",
|
|
2332
|
+
code: "malformed",
|
|
2333
|
+
context: {
|
|
2334
|
+
stage: this.stage,
|
|
2335
|
+
project
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
const path = escapesRoot(this.#mirror, resolved) ? escapesRoot(this.#workspace, resolved) ? normalizePath(resolved) : relativeWorkspaceFile(this.#workspace, resolved) : relativeWorkspaceFile(this.#mirror, resolved);
|
|
2339
|
+
issues.push({
|
|
2340
|
+
origin: "claimant",
|
|
2341
|
+
path,
|
|
2342
|
+
message,
|
|
2343
|
+
...diagnostic.range === void 0 ? {} : { range: diagnostic.range }
|
|
2344
|
+
});
|
|
2345
|
+
}
|
|
2346
|
+
return issues;
|
|
2347
|
+
}
|
|
2348
|
+
#translate(message) {
|
|
2349
|
+
return relativeWorkspaceMessage(this.#workspace, relativeWorkspaceMessage(this.#mirror, message));
|
|
2350
|
+
}
|
|
2351
|
+
#fault(execution, project) {
|
|
2352
|
+
const reported = scanDiagnostics(execution.stdout).map((diagnostic) => diagnostic.message);
|
|
2353
|
+
const text = reported.length > 0 ? reported.join("\n") : execution.stdout.trim();
|
|
2354
|
+
return new _src_core.ProbeError(this.#translate(text === "" ? "The compiler printed no configuration" : text), {
|
|
2355
|
+
origin: "workspace",
|
|
2356
|
+
code: "malformed",
|
|
2357
|
+
context: {
|
|
2358
|
+
stage: this.stage,
|
|
2359
|
+
project
|
|
2075
2360
|
}
|
|
2076
|
-
};
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
#spawn(args, cwd) {
|
|
2364
|
+
this.#refuseDestroyed();
|
|
2365
|
+
return new Promise((settle, refuse) => {
|
|
2366
|
+
const child = (0, node_child_process.spawn)(process.execPath, [this.#compiler, ...args], {
|
|
2367
|
+
cwd,
|
|
2368
|
+
stdio: [
|
|
2369
|
+
"ignore",
|
|
2370
|
+
"pipe",
|
|
2371
|
+
"pipe"
|
|
2372
|
+
]
|
|
2373
|
+
});
|
|
2374
|
+
const output = child.stdout;
|
|
2375
|
+
const errors = child.stderr;
|
|
2376
|
+
if (output === null || errors === null) {
|
|
2377
|
+
this.#terminate(child);
|
|
2378
|
+
refuse(new _src_core.ProbeError("The compiler was spawned without its own output streams", {
|
|
2379
|
+
origin: "instrument",
|
|
2380
|
+
code: "malformed",
|
|
2381
|
+
context: { stage: this.stage }
|
|
2382
|
+
}));
|
|
2383
|
+
return;
|
|
2384
|
+
}
|
|
2385
|
+
this.#children.add(child);
|
|
2386
|
+
let stdout = "";
|
|
2387
|
+
let stderr = "";
|
|
2388
|
+
output.setEncoding("utf8");
|
|
2389
|
+
errors.setEncoding("utf8");
|
|
2390
|
+
output.on("data", (chunk) => {
|
|
2391
|
+
stdout += chunk;
|
|
2392
|
+
});
|
|
2393
|
+
errors.on("data", (chunk) => {
|
|
2394
|
+
stderr += chunk;
|
|
2395
|
+
});
|
|
2396
|
+
child.on("error", (error) => {
|
|
2397
|
+
this.#children.delete(child);
|
|
2398
|
+
refuse(new _src_core.ProbeError("The workspace compiler could not be started", {
|
|
2399
|
+
origin: "workspace",
|
|
2400
|
+
code: "malformed",
|
|
2401
|
+
context: {
|
|
2402
|
+
stage: this.stage,
|
|
2403
|
+
path: this.#compiler
|
|
2404
|
+
},
|
|
2405
|
+
cause: error
|
|
2406
|
+
}));
|
|
2407
|
+
});
|
|
2408
|
+
child.on("close", (code) => {
|
|
2409
|
+
this.#children.delete(child);
|
|
2410
|
+
settle({
|
|
2411
|
+
...code === null ? {} : { status: code },
|
|
2412
|
+
stdout,
|
|
2413
|
+
stderr
|
|
2414
|
+
});
|
|
2415
|
+
});
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
#terminate(child) {
|
|
2419
|
+
const id = child.pid;
|
|
2420
|
+
if (process.platform === "win32" && id !== void 0) {
|
|
2421
|
+
(0, node_child_process.spawnSync)("taskkill", [
|
|
2422
|
+
"/pid",
|
|
2423
|
+
String(id),
|
|
2424
|
+
"/t",
|
|
2425
|
+
"/f"
|
|
2426
|
+
], { stdio: "ignore" });
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
child.kill("SIGTERM");
|
|
2077
2430
|
}
|
|
2078
2431
|
};
|
|
2079
2432
|
//#endregion
|
|
2080
2433
|
//#region src/server/Probe.ts
|
|
2081
2434
|
/**
|
|
2082
|
-
* Answers claims through
|
|
2435
|
+
* Answers claims through its type, lint, and runtime stages.
|
|
2083
2436
|
*
|
|
2084
2437
|
* @remarks
|
|
2085
2438
|
* Construction resolves the target workspace's toolchain and begins warming every stage. The boot
|
|
@@ -2091,10 +2444,45 @@ var TypeStage = class {
|
|
|
2091
2444
|
* claim costs that claim rather than the process. A failed boot is replaced the same way: the next
|
|
2092
2445
|
* claim runs the controls again rather than inheriting a refusal.
|
|
2093
2446
|
*
|
|
2094
|
-
* @example
|
|
2447
|
+
* @example The claim that earns a receipt
|
|
2095
2448
|
* ```ts
|
|
2096
|
-
*
|
|
2449
|
+
* import type { Claim } from '@orkestrel/probe'
|
|
2450
|
+
* import { Probe } from '@orkestrel/probe/server'
|
|
2451
|
+
*
|
|
2452
|
+
* const claim: Claim = {
|
|
2453
|
+
* project: 'configs/src/tsconfig.core.json',
|
|
2454
|
+
* case: {
|
|
2455
|
+
* files: [
|
|
2456
|
+
* {
|
|
2457
|
+
* path: 'src/core/factories.ts',
|
|
2458
|
+
* text: "export function createGreeting(): string {\n\treturn 'hi'\n}\n",
|
|
2459
|
+
* },
|
|
2460
|
+
* ],
|
|
2461
|
+
* test: {
|
|
2462
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
2463
|
+
* text: "import { expect, test } from 'vitest'\nimport { createGreeting } from '../../src/core/factories.js'\ntest('greets', () => expect(createGreeting()).toBe('hi'))\n",
|
|
2464
|
+
* },
|
|
2465
|
+
* },
|
|
2466
|
+
* control: {
|
|
2467
|
+
* files: [
|
|
2468
|
+
* {
|
|
2469
|
+
* path: 'src/core/factories.ts',
|
|
2470
|
+
* text: "export function createGreeting(): number {\n\treturn 'hi'\n}\n",
|
|
2471
|
+
* },
|
|
2472
|
+
* ],
|
|
2473
|
+
* test: {
|
|
2474
|
+
* path: 'tmp/probe/greeting.test.ts',
|
|
2475
|
+
* text: "import { expect, test } from 'vitest'\nimport { createGreeting } from '../../src/core/factories.js'\ntest('greets', () => expect(createGreeting()).toBe('hi'))\n",
|
|
2476
|
+
* },
|
|
2477
|
+
* stage: 'type',
|
|
2478
|
+
* reason: 'a string returned as a number must not compile',
|
|
2479
|
+
* },
|
|
2480
|
+
* }
|
|
2481
|
+
*
|
|
2482
|
+
* const probe = new Probe({ workspace: process.cwd() })
|
|
2097
2483
|
* const verdict = await probe.prove(claim)
|
|
2484
|
+
* verdict.digest // 'fcb88a2dee987b8673c1fc7107979470'
|
|
2485
|
+
* verdict.receipt // 'probe:fcb88a2dee987b8673c1fc7107979470:type:typescript@6.0.3:oxlint@1.82.0:vitest@4.1.11:configs/src/tsconfig.core.json@434f59254d58cf2683d453a26bd0d837'
|
|
2098
2486
|
* await probe.destroy()
|
|
2099
2487
|
* ```
|
|
2100
2488
|
*/
|
|
@@ -2115,7 +2503,7 @@ var Probe = class {
|
|
|
2115
2503
|
#arming;
|
|
2116
2504
|
#closing;
|
|
2117
2505
|
/**
|
|
2118
|
-
* Resolves the target toolchain and starts warming
|
|
2506
|
+
* Resolves the target toolchain and starts warming every stage.
|
|
2119
2507
|
*
|
|
2120
2508
|
* @param options - Workspace, deadline, and initial observation hooks
|
|
2121
2509
|
*/
|
|
@@ -2280,7 +2668,7 @@ var Probe = class {
|
|
|
2280
2668
|
files: [],
|
|
2281
2669
|
test: typeTest,
|
|
2282
2670
|
stage: "type",
|
|
2283
|
-
reason: "the imported type changed
|
|
2671
|
+
reason: "the imported type changed on disk between the two inspections"
|
|
2284
2672
|
}
|
|
2285
2673
|
};
|
|
2286
2674
|
const runtimeClaim = {
|
|
@@ -2527,7 +2915,9 @@ var Probe = class {
|
|
|
2527
2915
|
//#endregion
|
|
2528
2916
|
//#region src/server/ProbeServer.ts
|
|
2529
2917
|
/**
|
|
2530
|
-
*
|
|
2918
|
+
* Implements `ProbeServerInterface` over a `PassThrough` stream this server owns, binding the
|
|
2919
|
+
* published `prove` tool and the dual-era dispatcher to this process's Model Context Protocol
|
|
2920
|
+
* stdio transport.
|
|
2531
2921
|
*
|
|
2532
2922
|
* @remarks
|
|
2533
2923
|
* Construction creates the probe, publishes the `prove` tool, and binds the dual-era dispatcher to
|
|
@@ -2701,20 +3091,25 @@ exports.RuntimeStage = RuntimeStage;
|
|
|
2701
3091
|
exports.TypeStage = TypeStage;
|
|
2702
3092
|
exports.buildRevisionPath = buildRevisionPath;
|
|
2703
3093
|
exports.captureListeners = captureListeners;
|
|
3094
|
+
exports.collectWorkspaceFiles = collectWorkspaceFiles;
|
|
2704
3095
|
exports.computeDigest = computeDigest;
|
|
2705
3096
|
exports.describeUnknown = describeUnknown;
|
|
2706
3097
|
exports.escapesRoot = escapesRoot;
|
|
3098
|
+
exports.filterUniqueIssues = filterUniqueIssues;
|
|
2707
3099
|
exports.findRefusedPaths = findRefusedPaths;
|
|
2708
3100
|
exports.guardStage = guardStage;
|
|
2709
3101
|
exports.inferDocumentLanguage = inferDocumentLanguage;
|
|
2710
3102
|
exports.inferTestProject = inferTestProject;
|
|
2711
3103
|
exports.inferTypeProject = inferTypeProject;
|
|
2712
3104
|
exports.isRefusedName = isRefusedName;
|
|
2713
|
-
exports.
|
|
3105
|
+
exports.loadWorkspaceVitest = loadWorkspaceVitest;
|
|
3106
|
+
exports.matchesLiveProcess = matchesLiveProcess;
|
|
2714
3107
|
exports.matchesWorkspaceModule = matchesWorkspaceModule;
|
|
2715
3108
|
exports.normalizePath = normalizePath;
|
|
2716
3109
|
exports.normalizeValue = normalizeValue;
|
|
2717
3110
|
exports.overwriteFile = overwriteFile;
|
|
3111
|
+
exports.parseProjectConfig = parseProjectConfig;
|
|
3112
|
+
exports.parseRevisionOwner = parseRevisionOwner;
|
|
2718
3113
|
exports.readFaultCode = readFaultCode;
|
|
2719
3114
|
exports.readWorkspaceManifest = readWorkspaceManifest;
|
|
2720
3115
|
exports.relativeWorkspaceFile = relativeWorkspaceFile;
|
|
@@ -2723,5 +3118,6 @@ exports.releaseListeners = releaseListeners;
|
|
|
2723
3118
|
exports.resolveWorkspaceBinary = resolveWorkspaceBinary;
|
|
2724
3119
|
exports.resolveWorkspaceFile = resolveWorkspaceFile;
|
|
2725
3120
|
exports.resolveWorkspaceModule = resolveWorkspaceModule;
|
|
3121
|
+
exports.scanDiagnostics = scanDiagnostics;
|
|
2726
3122
|
|
|
2727
3123
|
//# sourceMappingURL=index.cjs.map
|