@sleepy-hollow/framework 0.3.3 → 0.3.5
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/CHANGELOG.md +21 -0
- package/dist/{chunk-4PPSJ2LE.js → chunk-2GNHTFCZ.js} +4 -4
- package/dist/{chunk-LZ2HLHDW.js → chunk-AADQFGT4.js} +3 -3
- package/dist/chunk-EHBEUJIZ.js +139 -0
- package/dist/chunk-EHBEUJIZ.js.map +1 -0
- package/dist/{chunk-S3Z6CO7J.js → chunk-LPPASMRR.js} +2 -2
- package/dist/chunk-LPPASMRR.js.map +1 -0
- package/dist/{chunk-JRFHLLLF.js → chunk-QY3TSXM6.js} +2 -2
- package/dist/{chunk-M4D63YC7.js → chunk-YBEJCNGD.js} +17 -3
- package/dist/chunk-YBEJCNGD.js.map +1 -0
- package/dist/cli.d.ts +4 -2
- package/dist/cli.js +410 -56
- package/dist/cli.js.map +1 -1
- package/dist/index.js +5 -5
- package/dist/routing.js +2 -2
- package/dist/security.js +4 -4
- package/dist/server.d.ts +15 -6
- package/dist/server.js +1 -1
- package/dist/testing.d.ts +13 -3
- package/dist/testing.js +7 -5
- package/dist/{types-Bet36nZS.d.ts → types-t92f8Gqs.d.ts} +1 -1
- package/dist/validation.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-LNJDFJGT.js +0 -47
- package/dist/chunk-LNJDFJGT.js.map +0 -1
- package/dist/chunk-M4D63YC7.js.map +0 -1
- package/dist/chunk-S3Z6CO7J.js.map +0 -1
- /package/dist/{chunk-4PPSJ2LE.js.map → chunk-2GNHTFCZ.js.map} +0 -0
- /package/dist/{chunk-LZ2HLHDW.js.map → chunk-AADQFGT4.js.map} +0 -0
- /package/dist/{chunk-JRFHLLLF.js.map → chunk-QY3TSXM6.js.map} +0 -0
package/dist/cli.js
CHANGED
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
resolveConfiguration
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-2GNHTFCZ.js";
|
|
5
5
|
import {
|
|
6
|
+
CRITERION_TEST_DISCOVERY,
|
|
7
|
+
createTestManifest,
|
|
6
8
|
createTraceabilityReport,
|
|
7
9
|
selectAffectedTests
|
|
8
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-YBEJCNGD.js";
|
|
9
11
|
import "./chunk-BAKXP7IR.js";
|
|
10
12
|
import {
|
|
11
13
|
composeProjectSecurity
|
|
12
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-AADQFGT4.js";
|
|
13
15
|
import {
|
|
14
16
|
normalizeRoutes
|
|
15
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-QY3TSXM6.js";
|
|
16
18
|
import {
|
|
17
19
|
discoverRoutes,
|
|
18
20
|
platform
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-LPPASMRR.js";
|
|
22
|
+
import "./chunk-EHBEUJIZ.js";
|
|
21
23
|
import "./chunk-5WRI5ZAA.js";
|
|
22
24
|
|
|
23
25
|
// cli/main.ts
|
|
24
26
|
import { realpathSync } from "fs";
|
|
25
|
-
import { pathToFileURL as
|
|
27
|
+
import { pathToFileURL as pathToFileURL5 } from "url";
|
|
26
28
|
|
|
27
29
|
// cli/adapters.ts
|
|
28
30
|
import { isAbsolute as isAbsolute5, join as join4, relative as relative5, sep as sep6 } from "path";
|
|
@@ -1006,7 +1008,7 @@ var CreationError = class extends Error {
|
|
|
1006
1008
|
};
|
|
1007
1009
|
|
|
1008
1010
|
// cli/create/create.ts
|
|
1009
|
-
var VERSION = "0.3.
|
|
1011
|
+
var VERSION = "0.3.5";
|
|
1010
1012
|
var NAME = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
1011
1013
|
function files(name) {
|
|
1012
1014
|
return {
|
|
@@ -1142,6 +1144,12 @@ owners:
|
|
|
1142
1144
|
|
|
1143
1145
|
Use the official Sleepy Hollow skill to replace this planning placeholder with
|
|
1144
1146
|
the complete application requirements before generating endpoints.
|
|
1147
|
+
|
|
1148
|
+
## Cross-cutting acceptance criteria
|
|
1149
|
+
|
|
1150
|
+
- AC-APP-PLACEHOLDER-001: The application requirements are completed and approved before implementation.
|
|
1151
|
+
|
|
1152
|
+
## Governance record
|
|
1145
1153
|
`,
|
|
1146
1154
|
"sleepyhollow.config.ts": `import { defineProject } from "./.sleepyhollow/project.ts";
|
|
1147
1155
|
|
|
@@ -1173,7 +1181,7 @@ async function pathExists(path) {
|
|
|
1173
1181
|
throw error;
|
|
1174
1182
|
}
|
|
1175
1183
|
}
|
|
1176
|
-
var FRAMEWORK_VERSION = "0.3.
|
|
1184
|
+
var FRAMEWORK_VERSION = "0.3.5";
|
|
1177
1185
|
async function createProject(options) {
|
|
1178
1186
|
if (!NAME.test(options.name) || options.name.length > 64) {
|
|
1179
1187
|
throw creationError(
|
|
@@ -1201,10 +1209,10 @@ async function createProject(options) {
|
|
|
1201
1209
|
);
|
|
1202
1210
|
try {
|
|
1203
1211
|
await platform.mkdir(staging);
|
|
1204
|
-
for (const
|
|
1205
|
-
const target = join2(staging,
|
|
1212
|
+
for (const relative8 of createdFiles) {
|
|
1213
|
+
const target = join2(staging, relative8);
|
|
1206
1214
|
await platform.mkdir(resolve(target, ".."), { recursive: true });
|
|
1207
|
-
await platform.writeTextFile(target, contents[
|
|
1215
|
+
await platform.writeTextFile(target, contents[relative8], { createNew: true });
|
|
1208
1216
|
}
|
|
1209
1217
|
await platform.rename(staging, destination);
|
|
1210
1218
|
} catch (error) {
|
|
@@ -1359,6 +1367,7 @@ async function startWorker(options, expectedRoutes) {
|
|
|
1359
1367
|
const child = workerCommand(options, "serve").spawn();
|
|
1360
1368
|
const stdout = readFirstLine(child.stdout);
|
|
1361
1369
|
const stderr = readBounded(child.stderr);
|
|
1370
|
+
const status = child.status;
|
|
1362
1371
|
let timeout;
|
|
1363
1372
|
const timer = new Promise((_, reject) => {
|
|
1364
1373
|
timeout = setTimeout(
|
|
@@ -1387,9 +1396,34 @@ async function startWorker(options, expectedRoutes) {
|
|
|
1387
1396
|
);
|
|
1388
1397
|
}
|
|
1389
1398
|
let stopped = false;
|
|
1399
|
+
const failure3 = new Promise((_, reject) => {
|
|
1400
|
+
void status.then(async (result) => {
|
|
1401
|
+
if (stopped) return;
|
|
1402
|
+
if (result.success) {
|
|
1403
|
+
reject(new DevCommandError([{
|
|
1404
|
+
code: "SH_DEV_WORKER_EXITED",
|
|
1405
|
+
severity: "error",
|
|
1406
|
+
summary: "The development worker exited after becoming active",
|
|
1407
|
+
correction: "Inspect the worker diagnostics and retry."
|
|
1408
|
+
}]));
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
reject(await workerFailure(
|
|
1412
|
+
await stderr,
|
|
1413
|
+
"The development worker failed after becoming active"
|
|
1414
|
+
));
|
|
1415
|
+
}, async () => {
|
|
1416
|
+
if (stopped) return;
|
|
1417
|
+
reject(await workerFailure(
|
|
1418
|
+
await stderr,
|
|
1419
|
+
"The development worker failed after becoming active"
|
|
1420
|
+
));
|
|
1421
|
+
});
|
|
1422
|
+
});
|
|
1390
1423
|
return {
|
|
1391
1424
|
url: `http://${options.hostname}:${options.port}/`,
|
|
1392
1425
|
routeCount: expectedRoutes,
|
|
1426
|
+
failure: failure3,
|
|
1393
1427
|
async stop() {
|
|
1394
1428
|
if (stopped) return;
|
|
1395
1429
|
stopped = true;
|
|
@@ -1620,6 +1654,7 @@ function failure2(error, fallback) {
|
|
|
1620
1654
|
}
|
|
1621
1655
|
function changedPaths(projectRoot, paths) {
|
|
1622
1656
|
const normalized = [];
|
|
1657
|
+
const runtimeDatabaseArtifact = /^\.sleepyhollow\/[^/]+\.(?:sqlite|db)(?:-(?:wal|shm))?$/;
|
|
1623
1658
|
for (const path of paths) {
|
|
1624
1659
|
const absolute = isAbsolute(path) ? resolve2(path) : resolve2(projectRoot, path);
|
|
1625
1660
|
const local = relative(projectRoot, absolute).split(sep).join("/");
|
|
@@ -1630,7 +1665,7 @@ function changedPaths(projectRoot, paths) {
|
|
|
1630
1665
|
"Watch only project-contained application inputs."
|
|
1631
1666
|
);
|
|
1632
1667
|
}
|
|
1633
|
-
if (local === ".git" || local.startsWith(".git/") || local === "generated" || local.startsWith("generated/") || local.includes("/node_modules/") || /(?:^|\/)(?:\.DS_Store|.*(?:\.swp|~))$/.test(local)) continue;
|
|
1668
|
+
if (local === ".git" || local.startsWith(".git/") || local === "generated" || local.startsWith("generated/") || local.includes("/node_modules/") || runtimeDatabaseArtifact.test(local) || /(?:^|\/)(?:\.DS_Store|.*(?:\.swp|~))$/.test(local)) continue;
|
|
1634
1669
|
normalized.push(local);
|
|
1635
1670
|
}
|
|
1636
1671
|
return Object.freeze([...new Set(normalized)].sort());
|
|
@@ -1645,6 +1680,21 @@ function abortReason(signal) {
|
|
|
1645
1680
|
}
|
|
1646
1681
|
return "cancelled";
|
|
1647
1682
|
}
|
|
1683
|
+
async function nextChange(iterator, active) {
|
|
1684
|
+
const change = iterator.next();
|
|
1685
|
+
if (!active?.failure) return change;
|
|
1686
|
+
return Promise.race([
|
|
1687
|
+
change,
|
|
1688
|
+
active.failure.then(() => {
|
|
1689
|
+
throw new DevCommandError([{
|
|
1690
|
+
code: "SH_DEV_WORKER_EXITED",
|
|
1691
|
+
severity: "error",
|
|
1692
|
+
summary: "The active development worker exited unexpectedly",
|
|
1693
|
+
correction: "Inspect the worker diagnostics and retry."
|
|
1694
|
+
}]);
|
|
1695
|
+
})
|
|
1696
|
+
]);
|
|
1697
|
+
}
|
|
1648
1698
|
async function runDevCommand(args, io, suppliedDependencies) {
|
|
1649
1699
|
const parsed = parse2(args);
|
|
1650
1700
|
if ("code" in parsed) {
|
|
@@ -1766,7 +1816,11 @@ ${parsed.correction}`
|
|
|
1766
1816
|
return 1;
|
|
1767
1817
|
}
|
|
1768
1818
|
if (signal.aborted) await closeWatcher();
|
|
1769
|
-
|
|
1819
|
+
const iterator = watcher[Symbol.asyncIterator]();
|
|
1820
|
+
while (true) {
|
|
1821
|
+
const next = await nextChange(iterator, active);
|
|
1822
|
+
if (next.done) break;
|
|
1823
|
+
const paths = next.value;
|
|
1770
1824
|
if (signal.aborted) break;
|
|
1771
1825
|
const changes = changedPaths(projectRoot, paths);
|
|
1772
1826
|
if (!Array.isArray(changes)) {
|
|
@@ -1919,14 +1973,26 @@ function safeDiagnostic(error, intent) {
|
|
|
1919
1973
|
...typeof item.key === "string" ? { configuration: [item.key] } : {}
|
|
1920
1974
|
}));
|
|
1921
1975
|
}
|
|
1922
|
-
|
|
1976
|
+
const nodeError = error;
|
|
1977
|
+
const nodeCode = typeof nodeError?.code === "string" ? nodeError.code : void 0;
|
|
1978
|
+
const isListenFailure = error instanceof platform.errors.AddrInUse || error instanceof platform.errors.PermissionDenied || nodeError?.syscall === "listen" || nodeCode === "EADDRINUSE" || nodeCode === "EACCES" || nodeCode === "EPERM";
|
|
1979
|
+
if (intent === "serve" && isListenFailure) {
|
|
1923
1980
|
return [{
|
|
1924
1981
|
code: "SH_DEV_BIND_FAILED",
|
|
1925
1982
|
severity: "error",
|
|
1926
|
-
summary: "The loopback listener could not bind",
|
|
1983
|
+
summary: nodeCode ? `The loopback listener failed with ${nodeCode}` : "The loopback listener could not bind",
|
|
1927
1984
|
correction: "Choose an available port and confirm local network access."
|
|
1928
1985
|
}];
|
|
1929
1986
|
}
|
|
1987
|
+
if (intent === "serve") {
|
|
1988
|
+
const cause = error instanceof Error ? `${error.name}: ${error.message}`.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").replace(/(?:Bearer|token|password|secret|authorization)[=: ]+\S+/gi, "$1=[redacted]").replace(/(?:[A-Za-z]:)?\/[^\s]+/g, "[path]").slice(0, 180) : void 0;
|
|
1989
|
+
return [{
|
|
1990
|
+
code: "SH_DEV_SERVE_FAILED",
|
|
1991
|
+
severity: "error",
|
|
1992
|
+
summary: cause ? `The development server failed: ${cause}` : "The development server failed",
|
|
1993
|
+
correction: "Inspect the bounded server diagnostic and retry after repairing the host boundary."
|
|
1994
|
+
}];
|
|
1995
|
+
}
|
|
1930
1996
|
return [{
|
|
1931
1997
|
code: "SH_DEV_PROJECT_INVALID",
|
|
1932
1998
|
severity: "error",
|
|
@@ -1998,20 +2064,20 @@ async function runDevWorker(args) {
|
|
|
1998
2064
|
const [intent, projectRoot, hostname, rawPort] = args;
|
|
1999
2065
|
const port = Number(rawPort);
|
|
2000
2066
|
if (hostname !== "127.0.0.1" || !Number.isInteger(port) || port < 1 || port > 65535) return 2;
|
|
2067
|
+
let server;
|
|
2001
2068
|
try {
|
|
2002
2069
|
const { runtime: runtime2, routeCount } = await loadRuntime(projectRoot);
|
|
2003
2070
|
if (intent === "validate") {
|
|
2004
2071
|
console.log(JSON.stringify({ ready: true, routeCount }));
|
|
2005
2072
|
return 0;
|
|
2006
2073
|
}
|
|
2007
|
-
|
|
2074
|
+
server = platform.serve({ hostname, port }, (request) => runtime2.fetch(request));
|
|
2075
|
+
await server.ready;
|
|
2008
2076
|
console.log(JSON.stringify({ ready: true, routeCount }));
|
|
2009
|
-
await
|
|
2010
|
-
server.once("close", resolve5);
|
|
2011
|
-
server.once("error", reject);
|
|
2012
|
-
});
|
|
2077
|
+
await server.finished;
|
|
2013
2078
|
return 0;
|
|
2014
2079
|
} catch (error) {
|
|
2080
|
+
await server?.shutdown().catch(() => void 0);
|
|
2015
2081
|
console.error(
|
|
2016
2082
|
JSON.stringify({
|
|
2017
2083
|
ready: false,
|
|
@@ -2911,7 +2977,7 @@ function renderArtifacts(inventory2) {
|
|
|
2911
2977
|
];
|
|
2912
2978
|
const manifestContent = canonicalJson({
|
|
2913
2979
|
schema: "sleepy-hollow-generated-manifest/v1",
|
|
2914
|
-
generatorVersion: "0.3.
|
|
2980
|
+
generatorVersion: "0.3.5",
|
|
2915
2981
|
serviceId: normalized.serviceId,
|
|
2916
2982
|
inputDigest: digest2(input),
|
|
2917
2983
|
artifacts: Object.fromEntries(
|
|
@@ -3185,7 +3251,7 @@ function inventoryFromRoutes(routes2, options) {
|
|
|
3185
3251
|
return {
|
|
3186
3252
|
serviceId: options.serviceId,
|
|
3187
3253
|
title: options.title ?? options.serviceId,
|
|
3188
|
-
version: options.version ?? "0.3.
|
|
3254
|
+
version: options.version ?? "0.3.5",
|
|
3189
3255
|
...options.description ? { description: options.description } : {},
|
|
3190
3256
|
operations,
|
|
3191
3257
|
securitySchemes: options.securitySchemes ?? {}
|
|
@@ -3416,7 +3482,7 @@ function invocation(plan2, options) {
|
|
|
3416
3482
|
const args = [
|
|
3417
3483
|
"./node_modules/vitest/vitest.mjs",
|
|
3418
3484
|
"run",
|
|
3419
|
-
"--reporter=
|
|
3485
|
+
"--reporter=tap-flat",
|
|
3420
3486
|
...plan2.filter ? ["--testNamePattern", plan2.filter] : [],
|
|
3421
3487
|
...files2
|
|
3422
3488
|
];
|
|
@@ -3464,11 +3530,16 @@ function parseTap(source, inventory2, failureEvidence) {
|
|
|
3464
3530
|
);
|
|
3465
3531
|
const events = [];
|
|
3466
3532
|
for (const line of lines) {
|
|
3467
|
-
const matched = line.match(
|
|
3533
|
+
const matched = line.match(
|
|
3534
|
+
/^(ok|not ok) \d+ - (.*?)(?:\s+#\s+(SKIP)\b.*|\s+#\s+.*)?$/i
|
|
3535
|
+
);
|
|
3468
3536
|
if (!matched) continue;
|
|
3469
|
-
const
|
|
3537
|
+
const rawName = matched[2];
|
|
3538
|
+
const separator = rawName.indexOf(" > ");
|
|
3539
|
+
const file = separator >= 0 ? rawName.slice(0, separator) : byName.get(rawName) ?? "<runner>";
|
|
3540
|
+
const name = separator >= 0 ? rawName.slice(separator + 3) : rawName;
|
|
3470
3541
|
events.push({
|
|
3471
|
-
file
|
|
3542
|
+
file,
|
|
3472
3543
|
name,
|
|
3473
3544
|
status: matched[3]?.toUpperCase() === "SKIP" ? "skipped" : matched[1] === "ok" ? "passed" : "failed",
|
|
3474
3545
|
...matched[1] === "not ok" && failureEvidence ? { evidence: failureEvidence } : {}
|
|
@@ -3829,6 +3900,32 @@ function renderJson(result) {
|
|
|
3829
3900
|
}
|
|
3830
3901
|
|
|
3831
3902
|
// cli/test/command.ts
|
|
3903
|
+
async function persistEvidence(inventory2, result) {
|
|
3904
|
+
if (!inventory2.manifestPath || !inventory2.resultsPath) return;
|
|
3905
|
+
const results = result.tests.filter((test) => !test.id.startsWith("unmapped:")).map((test) => ({
|
|
3906
|
+
testId: test.id,
|
|
3907
|
+
status: test.status === "unmapped" ? "failed" : test.status,
|
|
3908
|
+
...test.durationMs === null ? {} : { durationMs: test.durationMs },
|
|
3909
|
+
...test.evidence ? { evidence: test.evidence } : {}
|
|
3910
|
+
}));
|
|
3911
|
+
await platform.mkdir(inventory2.manifestPath.slice(
|
|
3912
|
+
0,
|
|
3913
|
+
inventory2.manifestPath.lastIndexOf("/")
|
|
3914
|
+
), { recursive: true });
|
|
3915
|
+
await platform.writeTextFile(
|
|
3916
|
+
inventory2.manifestPath,
|
|
3917
|
+
`${JSON.stringify(inventory2.manifest, null, 2)}
|
|
3918
|
+
`
|
|
3919
|
+
);
|
|
3920
|
+
await platform.writeTextFile(
|
|
3921
|
+
inventory2.resultsPath,
|
|
3922
|
+
`${JSON.stringify({
|
|
3923
|
+
schema: "sleepy-hollow-test-results/v1",
|
|
3924
|
+
results
|
|
3925
|
+
}, null, 2)}
|
|
3926
|
+
`
|
|
3927
|
+
);
|
|
3928
|
+
}
|
|
3832
3929
|
function parse3(args) {
|
|
3833
3930
|
let json2 = false;
|
|
3834
3931
|
let scope = { kind: "full" };
|
|
@@ -3954,7 +4051,7 @@ async function execute(args, io, load, runner) {
|
|
|
3954
4051
|
} catch {
|
|
3955
4052
|
return emit(loadFailure(parsed.scope), parsed.json, io);
|
|
3956
4053
|
}
|
|
3957
|
-
const capturePath = `${io.cwd}/generated/capture.json`;
|
|
4054
|
+
const capturePath = inventory2.captureArtifactPath ?? `${io.cwd}/generated/capture.json`;
|
|
3958
4055
|
inventory2 = { ...inventory2, captureArtifactPath: capturePath };
|
|
3959
4056
|
const testPlan = plan(inventory2, parsed.scope);
|
|
3960
4057
|
if (testPlan.diagnostics.some((item) => item.severity === "error")) {
|
|
@@ -4006,6 +4103,26 @@ async function execute(args, io, load, runner) {
|
|
|
4006
4103
|
};
|
|
4007
4104
|
}
|
|
4008
4105
|
const normalized = normalizeRunnerResult(testPlan, inventory2, runnerResult);
|
|
4106
|
+
let persistedResult = normalized;
|
|
4107
|
+
if (inventory2.manifestPath && inventory2.resultsPath) {
|
|
4108
|
+
try {
|
|
4109
|
+
await persistEvidence(inventory2, normalized);
|
|
4110
|
+
} catch {
|
|
4111
|
+
persistedResult = {
|
|
4112
|
+
...normalized,
|
|
4113
|
+
ok: false,
|
|
4114
|
+
diagnostics: [
|
|
4115
|
+
...normalized.diagnostics,
|
|
4116
|
+
{
|
|
4117
|
+
code: "SH_TEST_EVIDENCE_NOT_PERSISTED",
|
|
4118
|
+
severity: "error",
|
|
4119
|
+
summary: "The test run completed but governed test evidence could not be persisted.",
|
|
4120
|
+
correction: "Repair the generated directory and rerun hollow test before checking the project."
|
|
4121
|
+
}
|
|
4122
|
+
]
|
|
4123
|
+
};
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4009
4126
|
let persisted = true;
|
|
4010
4127
|
try {
|
|
4011
4128
|
await platform.stat(capturePath);
|
|
@@ -4013,8 +4130,8 @@ async function execute(args, io, load, runner) {
|
|
|
4013
4130
|
persisted = false;
|
|
4014
4131
|
}
|
|
4015
4132
|
return emit(
|
|
4016
|
-
persisted ?
|
|
4017
|
-
...
|
|
4133
|
+
persisted ? persistedResult : {
|
|
4134
|
+
...persistedResult,
|
|
4018
4135
|
diagnostics: [
|
|
4019
4136
|
...normalized.diagnostics,
|
|
4020
4137
|
{
|
|
@@ -4039,7 +4156,7 @@ var CLI_COMMANDS = [
|
|
|
4039
4156
|
"generate",
|
|
4040
4157
|
"deploy"
|
|
4041
4158
|
];
|
|
4042
|
-
var CLI_VERSION = "0.3.
|
|
4159
|
+
var CLI_VERSION = "0.3.5";
|
|
4043
4160
|
var commandMetadata = {
|
|
4044
4161
|
create: {
|
|
4045
4162
|
description: "Create one deterministic Sleepy Hollow project.",
|
|
@@ -5169,10 +5286,11 @@ function digestOf(source, path) {
|
|
|
5169
5286
|
}
|
|
5170
5287
|
function approvalOf(source, digest3, criteria) {
|
|
5171
5288
|
const governance = source.slice(source.indexOf("## Governance record"));
|
|
5172
|
-
const
|
|
5173
|
-
|
|
5289
|
+
const approvals = [...governance.matchAll(/^### Approval[^\r\n]*\r?$/gm)];
|
|
5290
|
+
const latest = approvals.at(-1);
|
|
5291
|
+
if (!latest || latest.index === void 0) return void 0;
|
|
5174
5292
|
const approvalRemainder = governance.slice(
|
|
5175
|
-
|
|
5293
|
+
latest.index + latest[0].length
|
|
5176
5294
|
);
|
|
5177
5295
|
const next = approvalRemainder.search(/^### /m);
|
|
5178
5296
|
const approval = next < 0 ? approvalRemainder : approvalRemainder.slice(0, next);
|
|
@@ -5181,7 +5299,7 @@ function approvalOf(source, digest3, criteria) {
|
|
|
5181
5299
|
const approvedAt = /- Approved at:\s*(.+?)\.?(?:\r?\n|$)/.exec(approval)?.[1]?.replace(/\.$/, "");
|
|
5182
5300
|
const criterionText = /- Approved criteria:\s*(.+?)\.?(?:\r?\n|$)/.exec(approval)?.[1] ?? "";
|
|
5183
5301
|
const recordedDigest = /sha256:([a-f0-9]{64})/.exec(approval)?.[1];
|
|
5184
|
-
const decisionSource = /- Decision source:\s*([\s\S]*?)(?=\r?\n- [A-Z]|$)/.exec(approval)?.[1]?.replace(/\r?\n\s+/g, " ").replace(/\.$/, "");
|
|
5302
|
+
const decisionSource = /- Decision source:\s*([\s\S]*?)(?=\r?\n- [A-Z]|$)/.exec(approval)?.[1]?.replace(/\r?\n\s+/g, " ").trim().replace(/\.$/, "");
|
|
5185
5303
|
const approvedCriteria = criteria.filter(
|
|
5186
5304
|
(item) => criterionText.includes(item.id)
|
|
5187
5305
|
).map((item) => item.id);
|
|
@@ -5197,6 +5315,18 @@ function approvalOf(source, digest3, criteria) {
|
|
|
5197
5315
|
valid
|
|
5198
5316
|
};
|
|
5199
5317
|
}
|
|
5318
|
+
function redStateValid(source) {
|
|
5319
|
+
const governance = source.slice(source.indexOf("## Governance record"));
|
|
5320
|
+
const records = [
|
|
5321
|
+
...governance.matchAll(/^### Red-state evidence[^\r\n]*\r?$/gm)
|
|
5322
|
+
];
|
|
5323
|
+
const latest = records.at(-1);
|
|
5324
|
+
if (!latest || latest.index === void 0) return false;
|
|
5325
|
+
const remainder = governance.slice(latest.index + latest[0].length);
|
|
5326
|
+
const next = remainder.search(/^### /m);
|
|
5327
|
+
const record = next < 0 ? remainder : remainder.slice(0, next);
|
|
5328
|
+
return /- Status:\s*(?:credible red state captured|failed as expected)\./i.test(record) && /- (?:Base revision|Observed at):\s*\S+/i.test(record) && /- Result:\s*\S+/i.test(record);
|
|
5329
|
+
}
|
|
5200
5330
|
function parseRequirementDocument(source, path, kind) {
|
|
5201
5331
|
const { metadata, body, bodyOffset } = frontmatterOf(source, path);
|
|
5202
5332
|
const diagnostics = [];
|
|
@@ -5377,7 +5507,8 @@ function parseRequirementDocument(source, path, kind) {
|
|
|
5377
5507
|
criteria: criterionResult.criteria,
|
|
5378
5508
|
sections,
|
|
5379
5509
|
governedContentDigest,
|
|
5380
|
-
approval
|
|
5510
|
+
approval,
|
|
5511
|
+
redStateValid: redStateValid(source)
|
|
5381
5512
|
};
|
|
5382
5513
|
}
|
|
5383
5514
|
|
|
@@ -5390,7 +5521,7 @@ function parseRequirement(source, path, kind) {
|
|
|
5390
5521
|
function issue2(code, path, summary, correction, line) {
|
|
5391
5522
|
return { code, path, summary, correction, ...line ? { line } : {} };
|
|
5392
5523
|
}
|
|
5393
|
-
async function collect(absoluteRoot, displayRoot, serviceId2, options) {
|
|
5524
|
+
async function collect(absoluteRoot, displayRoot, serviceId2, kind, options) {
|
|
5394
5525
|
const list = options.listDirectory ?? (async (path) => {
|
|
5395
5526
|
const entries = [];
|
|
5396
5527
|
for await (const entry of platform.readDir(path)) {
|
|
@@ -5422,6 +5553,7 @@ async function collect(absoluteRoot, displayRoot, serviceId2, options) {
|
|
|
5422
5553
|
path: nextDisplay,
|
|
5423
5554
|
absolutePath: nextAbsolute,
|
|
5424
5555
|
legacy: entry.name === "requirements.md",
|
|
5556
|
+
kind,
|
|
5425
5557
|
...serviceId2 ? { serviceId: serviceId2 } : {}
|
|
5426
5558
|
});
|
|
5427
5559
|
}
|
|
@@ -5442,16 +5574,45 @@ async function requirements(project, options) {
|
|
|
5442
5574
|
serviceId: void 0
|
|
5443
5575
|
}];
|
|
5444
5576
|
const files2 = [];
|
|
5577
|
+
files2.push({
|
|
5578
|
+
path: project.requirementsFile,
|
|
5579
|
+
absolutePath: `${project.projectRoot}/${project.requirementsFile}`,
|
|
5580
|
+
legacy: project.requirementsFile.endsWith("requirements.md"),
|
|
5581
|
+
kind: "application"
|
|
5582
|
+
});
|
|
5583
|
+
for (const service of project.services) {
|
|
5584
|
+
const absolutePath = `${project.projectRoot}/${service.requirementsPath}`;
|
|
5585
|
+
try {
|
|
5586
|
+
await platform.lstat(absolutePath);
|
|
5587
|
+
files2.push({
|
|
5588
|
+
path: service.requirementsPath,
|
|
5589
|
+
absolutePath,
|
|
5590
|
+
legacy: service.requirementsPath.endsWith("requirements.md"),
|
|
5591
|
+
kind: "application"
|
|
5592
|
+
});
|
|
5593
|
+
} catch (error) {
|
|
5594
|
+
if (!platform.isNotFound(error)) throw error;
|
|
5595
|
+
}
|
|
5596
|
+
}
|
|
5445
5597
|
for (const root of roots) {
|
|
5446
5598
|
files2.push(
|
|
5447
|
-
...await collect(
|
|
5599
|
+
...await collect(
|
|
5600
|
+
root.absolute,
|
|
5601
|
+
root.display,
|
|
5602
|
+
root.serviceId,
|
|
5603
|
+
"endpoint",
|
|
5604
|
+
options
|
|
5605
|
+
)
|
|
5448
5606
|
);
|
|
5449
5607
|
}
|
|
5450
|
-
|
|
5608
|
+
const uniqueFiles = [...new Map(
|
|
5609
|
+
files2.map((file) => [file.absolutePath, file])
|
|
5610
|
+
).values()];
|
|
5611
|
+
uniqueFiles.sort((left, right) => left.path.localeCompare(right.path));
|
|
5451
5612
|
const diagnostics = [];
|
|
5452
5613
|
const loaded = [];
|
|
5453
5614
|
const seen = /* @__PURE__ */ new Map();
|
|
5454
|
-
for (const file of
|
|
5615
|
+
for (const file of uniqueFiles) {
|
|
5455
5616
|
if (file.legacy) {
|
|
5456
5617
|
diagnostics.push(issue2(
|
|
5457
5618
|
"SH_EVIDENCE_REQUIREMENT_LEGACY_FILENAME",
|
|
@@ -5475,7 +5636,7 @@ async function requirements(project, options) {
|
|
|
5475
5636
|
}
|
|
5476
5637
|
let parsed;
|
|
5477
5638
|
try {
|
|
5478
|
-
parsed = parseRequirement(source, file.path,
|
|
5639
|
+
parsed = parseRequirement(source, file.path, file.kind);
|
|
5479
5640
|
} catch (error) {
|
|
5480
5641
|
if (error instanceof PlanningError) {
|
|
5481
5642
|
for (const item of error.diagnostics) {
|
|
@@ -5527,7 +5688,14 @@ async function requirements(project, options) {
|
|
|
5527
5688
|
} : {},
|
|
5528
5689
|
path: file.path,
|
|
5529
5690
|
...file.serviceId ? { serviceId: file.serviceId } : {},
|
|
5530
|
-
approvalBound
|
|
5691
|
+
approvalBound,
|
|
5692
|
+
redStateValid: parsed.redStateValid,
|
|
5693
|
+
...file.kind === "endpoint" ? {
|
|
5694
|
+
routePath: typeof parsed.metadata.path === "string" ? parsed.metadata.path : void 0,
|
|
5695
|
+
methods: Array.isArray(parsed.metadata.methods) ? parsed.metadata.methods.filter(
|
|
5696
|
+
(method) => typeof method === "string"
|
|
5697
|
+
) : void 0
|
|
5698
|
+
} : {}
|
|
5531
5699
|
});
|
|
5532
5700
|
}
|
|
5533
5701
|
if (diagnostics.length > 0) throw new EvidenceError(diagnostics);
|
|
@@ -5544,12 +5712,15 @@ async function requirements(project, options) {
|
|
|
5544
5712
|
criteria: item.criteria,
|
|
5545
5713
|
...item.approval ? { approval: item.approval } : {},
|
|
5546
5714
|
path: item.path,
|
|
5547
|
-
redStateValid:
|
|
5715
|
+
redStateValid: item.redStateValid,
|
|
5716
|
+
...item.routePath ? { routePath: item.routePath } : {},
|
|
5717
|
+
...item.methods ? { methods: item.methods } : {}
|
|
5548
5718
|
}))
|
|
5549
5719
|
};
|
|
5550
5720
|
}
|
|
5551
5721
|
|
|
5552
5722
|
// cli/evidence/behavior.ts
|
|
5723
|
+
import { relative as relative6, sep as sep7 } from "path";
|
|
5553
5724
|
var CAPTURE_FILE = "capture.json";
|
|
5554
5725
|
async function capture(project, options) {
|
|
5555
5726
|
const path = `${project.projectRoot}/${project.generatedDirectory}/${CAPTURE_FILE}`;
|
|
@@ -5612,7 +5783,16 @@ function authenticationOf(security2) {
|
|
|
5612
5783
|
if (typeof declared !== "object" || declared === null) return "none";
|
|
5613
5784
|
return declared.mode === "required" ? "required" : "none";
|
|
5614
5785
|
}
|
|
5615
|
-
|
|
5786
|
+
function normalizedRequirementPath(path) {
|
|
5787
|
+
return path.replace(/\[([A-Za-z_][A-Za-z0-9_]*)\]/g, ":$1");
|
|
5788
|
+
}
|
|
5789
|
+
function ownerOf(route, requirements2) {
|
|
5790
|
+
const owners = requirements2.filter(
|
|
5791
|
+
(requirement) => requirement.routePath !== void 0 && normalizedRequirementPath(requirement.routePath) === route.path && requirement.methods?.includes(route.method)
|
|
5792
|
+
);
|
|
5793
|
+
return owners.length === 1 ? owners[0].id : void 0;
|
|
5794
|
+
}
|
|
5795
|
+
async function routes(project, artifact2, requirements2 = []) {
|
|
5616
5796
|
const roots = project.services.length > 0 ? project.services.map((service) => ({
|
|
5617
5797
|
absolute: `${project.projectRoot}/${service.apiRoot}`,
|
|
5618
5798
|
display: service.apiRoot
|
|
@@ -5645,10 +5825,12 @@ async function routes(project, artifact2) {
|
|
|
5645
5825
|
}
|
|
5646
5826
|
statuses2.add(request.responseStatus);
|
|
5647
5827
|
}
|
|
5828
|
+
const source = route.source.startsWith("/") ? relative6(root.absolute, route.source).split(sep7).join("/") : route.source;
|
|
5648
5829
|
found.push({
|
|
5830
|
+
requirementId: ownerOf(route, requirements2),
|
|
5649
5831
|
method: route.method,
|
|
5650
5832
|
path: route.path,
|
|
5651
|
-
source: `${root.display}
|
|
5833
|
+
source: `${root.display}/${source}`,
|
|
5652
5834
|
requestSchemaLocations: declaredLocations(route.operation.schemas),
|
|
5653
5835
|
requiredRequestLocations: [...readLocations].sort(),
|
|
5654
5836
|
responseSchemaStatuses: declaredStatuses(route.operation.schemas),
|
|
@@ -5663,11 +5845,172 @@ async function routes(project, artifact2) {
|
|
|
5663
5845
|
);
|
|
5664
5846
|
}
|
|
5665
5847
|
|
|
5848
|
+
// cli/evidence/tests.ts
|
|
5849
|
+
import { relative as relative7, sep as sep8 } from "path";
|
|
5850
|
+
import { pathToFileURL as pathToFileURL4 } from "url";
|
|
5851
|
+
var MANIFEST_FILE = "test-manifest.json";
|
|
5852
|
+
var RESULTS_FILE = "test-results.json";
|
|
5853
|
+
var TEST_FILE = /(?:_test|\.test)\.(?:ts|tsx|mts|cts)$/;
|
|
5854
|
+
var discoverySequence = 0;
|
|
5855
|
+
function relativeProjectPath(root, path) {
|
|
5856
|
+
return relative7(root, path).split(sep8).join("/");
|
|
5857
|
+
}
|
|
5858
|
+
async function collect2(root, projectRoot) {
|
|
5859
|
+
const found = [];
|
|
5860
|
+
const walk = async (directory) => {
|
|
5861
|
+
let entries;
|
|
5862
|
+
try {
|
|
5863
|
+
entries = [];
|
|
5864
|
+
for await (const entry of platform.readDir(directory)) entries.push(entry);
|
|
5865
|
+
} catch {
|
|
5866
|
+
return;
|
|
5867
|
+
}
|
|
5868
|
+
entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
5869
|
+
for (const entry of entries) {
|
|
5870
|
+
if (entry.isSymlink) continue;
|
|
5871
|
+
const absolute = `${directory}/${entry.name}`;
|
|
5872
|
+
if (entry.isDirectory) {
|
|
5873
|
+
await walk(absolute);
|
|
5874
|
+
} else if (entry.isFile && TEST_FILE.test(entry.name)) {
|
|
5875
|
+
found.push({ absolute, relative: relativeProjectPath(projectRoot, absolute) });
|
|
5876
|
+
}
|
|
5877
|
+
}
|
|
5878
|
+
};
|
|
5879
|
+
await walk(root);
|
|
5880
|
+
return found;
|
|
5881
|
+
}
|
|
5882
|
+
function testRoots(project) {
|
|
5883
|
+
const roots = project.services.length > 0 ? project.services.flatMap((service) => [
|
|
5884
|
+
`${project.projectRoot}/${service.testsRoot}`,
|
|
5885
|
+
`${project.projectRoot}/${service.apiRoot}`
|
|
5886
|
+
]) : [
|
|
5887
|
+
`${project.projectRoot}/tests`,
|
|
5888
|
+
`${project.projectRoot}/${project.apiDirectory}`
|
|
5889
|
+
];
|
|
5890
|
+
return [...new Set(roots)];
|
|
5891
|
+
}
|
|
5892
|
+
async function discoverFiles(project) {
|
|
5893
|
+
const files2 = (await Promise.all(testRoots(project).map(
|
|
5894
|
+
(root) => collect2(root, project.projectRoot)
|
|
5895
|
+
))).flat();
|
|
5896
|
+
const unique = new Map(files2.map((file) => [file.absolute, file]));
|
|
5897
|
+
return [...unique.values()].sort(
|
|
5898
|
+
(left, right) => left.relative.localeCompare(right.relative)
|
|
5899
|
+
);
|
|
5900
|
+
}
|
|
5901
|
+
async function discoverManifest(project) {
|
|
5902
|
+
const descriptors = [];
|
|
5903
|
+
const sources = {};
|
|
5904
|
+
const hook = {
|
|
5905
|
+
onRegistered: (descriptor) => descriptors.push(descriptor)
|
|
5906
|
+
};
|
|
5907
|
+
const registry = globalThis;
|
|
5908
|
+
registry[CRITERION_TEST_DISCOVERY] = hook;
|
|
5909
|
+
try {
|
|
5910
|
+
for (const file of await discoverFiles(project)) {
|
|
5911
|
+
const source = await platform.readTextFile(file.absolute);
|
|
5912
|
+
if (!/\bcriterionTest\s*\(/.test(source)) continue;
|
|
5913
|
+
sources[file.relative] = source;
|
|
5914
|
+
try {
|
|
5915
|
+
discoverySequence += 1;
|
|
5916
|
+
await import(`${pathToFileURL4(file.absolute).href}?sleepy_hollow_discovery=${discoverySequence}`);
|
|
5917
|
+
} catch (error) {
|
|
5918
|
+
throw new EvidenceError([{
|
|
5919
|
+
code: "SH_EVIDENCE_TEST_DISCOVERY_FAILED",
|
|
5920
|
+
path: file.relative,
|
|
5921
|
+
summary: error instanceof Error ? error.message : "A governed test could not be loaded for discovery.",
|
|
5922
|
+
correction: "Repair the criterionTest registration and its imports, then retry."
|
|
5923
|
+
}]);
|
|
5924
|
+
}
|
|
5925
|
+
}
|
|
5926
|
+
} finally {
|
|
5927
|
+
delete registry[CRITERION_TEST_DISCOVERY];
|
|
5928
|
+
}
|
|
5929
|
+
return createTestManifest({ descriptors, sources });
|
|
5930
|
+
}
|
|
5931
|
+
function emptyManifest() {
|
|
5932
|
+
return { schema: "sleepy-hollow-test-manifest/v1", tests: [] };
|
|
5933
|
+
}
|
|
5934
|
+
function parseManifest(value, path) {
|
|
5935
|
+
if (!value || typeof value !== "object" || value.schema !== "sleepy-hollow-test-manifest/v1" || !Array.isArray(value.tests)) {
|
|
5936
|
+
throw new EvidenceError([{
|
|
5937
|
+
code: "SH_EVIDENCE_TEST_MANIFEST_INVALID",
|
|
5938
|
+
path,
|
|
5939
|
+
summary: "The persisted test manifest has an unsupported shape.",
|
|
5940
|
+
correction: "Regenerate test evidence with hollow test."
|
|
5941
|
+
}]);
|
|
5942
|
+
}
|
|
5943
|
+
return value;
|
|
5944
|
+
}
|
|
5945
|
+
function parseResults(value, path) {
|
|
5946
|
+
if (!value || typeof value !== "object" || value.schema !== "sleepy-hollow-test-results/v1" || !Array.isArray(value.results)) {
|
|
5947
|
+
throw new EvidenceError([{
|
|
5948
|
+
code: "SH_EVIDENCE_TEST_RESULTS_INVALID",
|
|
5949
|
+
path,
|
|
5950
|
+
summary: "The persisted test results have an unsupported shape.",
|
|
5951
|
+
correction: "Regenerate test evidence with hollow test."
|
|
5952
|
+
}]);
|
|
5953
|
+
}
|
|
5954
|
+
return value.results;
|
|
5955
|
+
}
|
|
5956
|
+
async function readJson(path) {
|
|
5957
|
+
let source;
|
|
5958
|
+
try {
|
|
5959
|
+
source = await platform.readTextFile(path);
|
|
5960
|
+
} catch (error) {
|
|
5961
|
+
if (platform.isNotFound(error)) return void 0;
|
|
5962
|
+
throw new EvidenceError([{
|
|
5963
|
+
code: "SH_EVIDENCE_TEST_ARTIFACT_UNREADABLE",
|
|
5964
|
+
path,
|
|
5965
|
+
summary: "A persisted test evidence artifact could not be read.",
|
|
5966
|
+
correction: "Repair the generated evidence permissions and retry."
|
|
5967
|
+
}]);
|
|
5968
|
+
}
|
|
5969
|
+
try {
|
|
5970
|
+
return JSON.parse(source);
|
|
5971
|
+
} catch {
|
|
5972
|
+
throw new EvidenceError([{
|
|
5973
|
+
code: "SH_EVIDENCE_TEST_ARTIFACT_INVALID",
|
|
5974
|
+
path,
|
|
5975
|
+
summary: "A persisted test evidence artifact is not valid JSON.",
|
|
5976
|
+
correction: "Regenerate test evidence with hollow test."
|
|
5977
|
+
}]);
|
|
5978
|
+
}
|
|
5979
|
+
}
|
|
5980
|
+
async function readTestEvidence(project) {
|
|
5981
|
+
const generated = `${project.projectRoot}/${project.generatedDirectory}`;
|
|
5982
|
+
const manifestPath = `${generated}/${MANIFEST_FILE}`;
|
|
5983
|
+
const resultsPath = `${generated}/${RESULTS_FILE}`;
|
|
5984
|
+
const previousPath = `${generated}/previous-test-manifest.json`;
|
|
5985
|
+
const [manifestSource, resultsSource, previousSource] = await Promise.all([
|
|
5986
|
+
readJson(manifestPath),
|
|
5987
|
+
readJson(resultsPath),
|
|
5988
|
+
readJson(previousPath)
|
|
5989
|
+
]);
|
|
5990
|
+
const manifest = manifestSource === void 0 ? emptyManifest() : parseManifest(manifestSource, `${project.generatedDirectory}/${MANIFEST_FILE}`);
|
|
5991
|
+
const results = resultsSource === void 0 ? [] : parseResults(resultsSource, `${project.generatedDirectory}/${RESULTS_FILE}`);
|
|
5992
|
+
return {
|
|
5993
|
+
manifest,
|
|
5994
|
+
...previousSource === void 0 ? {} : { previousManifest: parseManifest(previousSource, `${project.generatedDirectory}/previous-test-manifest.json`) },
|
|
5995
|
+
results
|
|
5996
|
+
};
|
|
5997
|
+
}
|
|
5998
|
+
async function discoverTestEvidence(project) {
|
|
5999
|
+
return discoverManifest(project);
|
|
6000
|
+
}
|
|
6001
|
+
function testEvidencePaths(projectRoot, generatedDirectory) {
|
|
6002
|
+
const generated = `${projectRoot}/${generatedDirectory}`;
|
|
6003
|
+
return {
|
|
6004
|
+
manifestPath: `${generated}/${MANIFEST_FILE}`,
|
|
6005
|
+
resultsPath: `${generated}/${RESULTS_FILE}`
|
|
6006
|
+
};
|
|
6007
|
+
}
|
|
6008
|
+
|
|
5666
6009
|
// cli/evidence/inventory.ts
|
|
5667
6010
|
async function inventory(project, options) {
|
|
5668
6011
|
const artifact2 = await capture(project, options);
|
|
5669
6012
|
const governed = await requirements(project, options);
|
|
5670
|
-
const discovered = await routes(project, artifact2);
|
|
6013
|
+
const discovered = await routes(project, artifact2, governed.requirements);
|
|
5671
6014
|
return {
|
|
5672
6015
|
projectRootDisplay: project.projectRoot,
|
|
5673
6016
|
requirements: governed.requirements,
|
|
@@ -5692,10 +6035,7 @@ async function inventory(project, options) {
|
|
|
5692
6035
|
async function checkLoader(projectRoot, revision2, _scope) {
|
|
5693
6036
|
const project = await locations({ projectRoot });
|
|
5694
6037
|
const evidence = await inventory(project, { projectRoot, revision: revision2 });
|
|
5695
|
-
const
|
|
5696
|
-
schema: "sleepy-hollow-test-manifest/v1",
|
|
5697
|
-
tests: []
|
|
5698
|
-
};
|
|
6038
|
+
const testEvidence = await readTestEvidence(project);
|
|
5699
6039
|
return {
|
|
5700
6040
|
projectRootDisplay: projectRoot,
|
|
5701
6041
|
requestedScope: { kind: "full" },
|
|
@@ -5707,14 +6047,17 @@ async function checkLoader(projectRoot, revision2, _scope) {
|
|
|
5707
6047
|
criteria: item.criteria,
|
|
5708
6048
|
...item.approval ? { approval: item.approval } : {},
|
|
5709
6049
|
path: item.path,
|
|
5710
|
-
redStateValid: item.
|
|
6050
|
+
redStateValid: item.redStateValid,
|
|
6051
|
+
...item.routePath ? { routePath: item.routePath } : {},
|
|
6052
|
+
...item.methods ? { methods: item.methods } : {}
|
|
5711
6053
|
})),
|
|
5712
6054
|
dependencyGraph: evidence.requirements.map((item) => ({
|
|
5713
6055
|
id: item.id,
|
|
5714
6056
|
dependsOn: item.dependsOn ?? []
|
|
5715
6057
|
})),
|
|
5716
|
-
testManifest,
|
|
5717
|
-
|
|
6058
|
+
testManifest: testEvidence.manifest,
|
|
6059
|
+
previousTestManifest: testEvidence.previousManifest,
|
|
6060
|
+
testResults: testEvidence.results,
|
|
5718
6061
|
routes: evidence.routes.map((route) => ({
|
|
5719
6062
|
requirementId: route.requirementId ?? "",
|
|
5720
6063
|
method: route.method,
|
|
@@ -5744,6 +6087,11 @@ async function checkLoader(projectRoot, revision2, _scope) {
|
|
|
5744
6087
|
async function testLoader(projectRoot) {
|
|
5745
6088
|
const project = await locations({ projectRoot });
|
|
5746
6089
|
const governed = await requirements(project, { projectRoot });
|
|
6090
|
+
const manifest = await discoverTestEvidence(project);
|
|
6091
|
+
const evidencePaths = testEvidencePaths(
|
|
6092
|
+
projectRoot,
|
|
6093
|
+
project.generatedDirectory
|
|
6094
|
+
);
|
|
5747
6095
|
return {
|
|
5748
6096
|
projectRootDisplay: projectRoot,
|
|
5749
6097
|
requirements: governed.requirements.map((item) => ({
|
|
@@ -5759,8 +6107,14 @@ async function testLoader(projectRoot) {
|
|
|
5759
6107
|
dependsOn: item.dependsOn ?? []
|
|
5760
6108
|
})),
|
|
5761
6109
|
routes: [],
|
|
5762
|
-
manifest
|
|
5763
|
-
isolation:
|
|
6110
|
+
manifest,
|
|
6111
|
+
isolation: manifest.tests.map((test) => ({
|
|
6112
|
+
testId: test.id,
|
|
6113
|
+
policy: "isolated"
|
|
6114
|
+
})),
|
|
6115
|
+
manifestPath: evidencePaths.manifestPath,
|
|
6116
|
+
resultsPath: evidencePaths.resultsPath,
|
|
6117
|
+
captureArtifactPath: `${projectRoot}/${project.generatedDirectory}/capture.json`
|
|
5764
6118
|
};
|
|
5765
6119
|
}
|
|
5766
6120
|
|
|
@@ -5793,7 +6147,7 @@ function runCli(args, io, dependencies = {}) {
|
|
|
5793
6147
|
function invokedAsCli(entry = process.argv[1]) {
|
|
5794
6148
|
if (!entry) return false;
|
|
5795
6149
|
try {
|
|
5796
|
-
return import.meta.url ===
|
|
6150
|
+
return import.meta.url === pathToFileURL5(realpathSync(entry)).href;
|
|
5797
6151
|
} catch {
|
|
5798
6152
|
return false;
|
|
5799
6153
|
}
|