readyup 0.0.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +123 -0
- package/bin/rdy.js +11 -0
- package/dist/esm/.cache +1 -0
- package/dist/esm/assertIsPreflightCollection.d.ts +2 -0
- package/dist/esm/assertIsPreflightCollection.js +27 -0
- package/dist/esm/assertIsRdyKit.d.ts +2 -0
- package/dist/esm/assertIsRdyKit.js +27 -0
- package/dist/esm/authoring.d.ts +6 -0
- package/dist/esm/authoring.js +22 -0
- package/dist/esm/bin/preflight.d.ts +1 -0
- package/dist/esm/bin/preflight.js +12 -0
- package/dist/esm/bin/rdy.d.ts +1 -0
- package/dist/esm/bin/rdy.js +12 -0
- package/dist/esm/bin/route.d.ts +1 -0
- package/dist/esm/bin/route.js +202 -0
- package/dist/esm/check-utils/filesystem.d.ts +4 -0
- package/dist/esm/check-utils/filesystem.js +26 -0
- package/dist/esm/check-utils/index.d.ts +3 -0
- package/dist/esm/check-utils/index.js +14 -0
- package/dist/esm/check-utils/package-json.d.ts +6 -0
- package/dist/esm/check-utils/package-json.js +40 -0
- package/dist/esm/check-utils/semver.d.ts +1 -0
- package/dist/esm/check-utils/semver.js +12 -0
- package/dist/esm/cli.d.ts +36 -0
- package/dist/esm/cli.js +285 -0
- package/dist/esm/compile/compileCommand.d.ts +1 -0
- package/dist/esm/compile/compileCommand.js +121 -0
- package/dist/esm/compile/compileConfig.d.ts +5 -0
- package/dist/esm/compile/compileConfig.js +52 -0
- package/dist/esm/compile/validateCompiledOutput.d.ts +1 -0
- package/dist/esm/compile/validateCompiledOutput.js +29 -0
- package/dist/esm/config.d.ts +2 -0
- package/dist/esm/config.js +28 -0
- package/dist/esm/expandGitHubShorthand.d.ts +1 -0
- package/dist/esm/expandGitHubShorthand.js +26 -0
- package/dist/esm/formatCombinedSummary.d.ts +2 -0
- package/dist/esm/formatCombinedSummary.js +40 -0
- package/dist/esm/formatJsonError.d.ts +1 -0
- package/dist/esm/formatJsonError.js +6 -0
- package/dist/esm/formatJsonReport.d.ts +10 -0
- package/dist/esm/formatJsonReport.js +90 -0
- package/dist/esm/index.d.ts +4 -0
- package/dist/esm/index.js +37 -0
- package/dist/esm/init/initCommand.d.ts +6 -0
- package/dist/esm/init/initCommand.js +33 -0
- package/dist/esm/init/scaffold.d.ts +11 -0
- package/dist/esm/init/scaffold.js +15 -0
- package/dist/esm/init/templates.d.ts +2 -0
- package/dist/esm/init/templates.js +37 -0
- package/dist/esm/isRecord.d.ts +1 -0
- package/dist/esm/isRecord.js +6 -0
- package/dist/esm/jitiImport.d.ts +1 -0
- package/dist/esm/jitiImport.js +23 -0
- package/dist/esm/loadConfig.d.ts +2 -0
- package/dist/esm/loadConfig.js +74 -0
- package/dist/esm/loadRemoteCollection.d.ts +6 -0
- package/dist/esm/loadRemoteCollection.js +40 -0
- package/dist/esm/loadRemoteKit.d.ts +6 -0
- package/dist/esm/loadRemoteKit.js +40 -0
- package/dist/esm/parseArgs.d.ts +15 -0
- package/dist/esm/parseArgs.js +93 -0
- package/dist/esm/reportPreflight.d.ts +7 -0
- package/dist/esm/reportPreflight.js +105 -0
- package/dist/esm/reportRdy.d.ts +7 -0
- package/dist/esm/reportRdy.js +105 -0
- package/dist/esm/resolveCollectionExports.d.ts +1 -0
- package/dist/esm/resolveCollectionExports.js +20 -0
- package/dist/esm/resolveGitHubToken.d.ts +1 -0
- package/dist/esm/resolveGitHubToken.js +22 -0
- package/dist/esm/resolveKitExports.d.ts +1 -0
- package/dist/esm/resolveKitExports.js +20 -0
- package/dist/esm/resolveRequestedNames.d.ts +2 -0
- package/dist/esm/resolveRequestedNames.js +38 -0
- package/dist/esm/runPreflight.d.ts +7 -0
- package/dist/esm/runPreflight.js +157 -0
- package/dist/esm/runRdy.d.ts +7 -0
- package/dist/esm/runRdy.js +157 -0
- package/dist/esm/terminal.d.ts +6 -0
- package/dist/esm/terminal.js +55 -0
- package/dist/esm/types.d.ts +139 -0
- package/dist/esm/types.js +10 -0
- package/dist/esm/validateCollection.d.ts +2 -0
- package/dist/esm/validateCollection.js +27 -0
- package/dist/esm/validateKit.d.ts +2 -0
- package/dist/esm/validateKit.js +27 -0
- package/dist/esm/version.d.ts +1 -0
- package/dist/esm/version.js +4 -0
- package/dist/esm/writeFileWithCheck.d.ts +10 -0
- package/dist/esm/writeFileWithCheck.js +41 -0
- package/package.json +56 -10
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { meetsThreshold } from "./runPreflight.js";
|
|
2
|
+
import { isPercentProgress } from "./types.js";
|
|
3
|
+
const ICON_PASSED = "\u{1F7E2}";
|
|
4
|
+
const ICON_ERROR_FAILED = "\u{1F534}";
|
|
5
|
+
const ICON_WARN_FAILED = "\u{1F7E0}";
|
|
6
|
+
const ICON_RECOMMEND_FAILED = "\u{1F7E1}";
|
|
7
|
+
const ICON_SKIPPED_NA = "\u26AA";
|
|
8
|
+
const ICON_SKIPPED_PRECONDITION = "\u26D4";
|
|
9
|
+
const ICON_FIX = "\u{1F48A}";
|
|
10
|
+
function formatDuration(ms) {
|
|
11
|
+
return `${Math.round(ms)}ms`;
|
|
12
|
+
}
|
|
13
|
+
function getIcon(result) {
|
|
14
|
+
if (result.status === "passed") return ICON_PASSED;
|
|
15
|
+
if (result.status === "skipped") {
|
|
16
|
+
return result.skipReason === "precondition" ? ICON_SKIPPED_PRECONDITION : ICON_SKIPPED_NA;
|
|
17
|
+
}
|
|
18
|
+
if (result.severity === "warn") return ICON_WARN_FAILED;
|
|
19
|
+
if (result.severity === "recommend") return ICON_RECOMMEND_FAILED;
|
|
20
|
+
return ICON_ERROR_FAILED;
|
|
21
|
+
}
|
|
22
|
+
function formatProgress(progress) {
|
|
23
|
+
if (isPercentProgress(progress)) {
|
|
24
|
+
return `${progress.percent}%`;
|
|
25
|
+
}
|
|
26
|
+
return `${progress.passedCount} of ${progress.count}`;
|
|
27
|
+
}
|
|
28
|
+
function formatSummaryCounts(passed, failed, skipped) {
|
|
29
|
+
const parts = [];
|
|
30
|
+
if (passed > 0) parts.push(`${ICON_PASSED} ${passed} passed`);
|
|
31
|
+
if (failed > 0) parts.push(`${ICON_ERROR_FAILED} ${failed} failed`);
|
|
32
|
+
if (skipped > 0) parts.push(`${ICON_SKIPPED_PRECONDITION} ${skipped} skipped`);
|
|
33
|
+
return parts.join(", ");
|
|
34
|
+
}
|
|
35
|
+
function collectInlineDetails(result, includeFix) {
|
|
36
|
+
const details = [];
|
|
37
|
+
if (result.error !== null) {
|
|
38
|
+
details.push(` Error: ${result.error.message}`);
|
|
39
|
+
}
|
|
40
|
+
if (includeFix && result.fix !== null) {
|
|
41
|
+
details.push(` ${ICON_FIX} Fix: ${result.fix}`);
|
|
42
|
+
}
|
|
43
|
+
return details;
|
|
44
|
+
}
|
|
45
|
+
function* iterateWithNaSuppression(results) {
|
|
46
|
+
let suppressBelowDepth = null;
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
if (suppressBelowDepth !== null) {
|
|
49
|
+
if (result.depth > suppressBelowDepth) continue;
|
|
50
|
+
suppressBelowDepth = null;
|
|
51
|
+
}
|
|
52
|
+
if (result.status === "skipped" && result.skipReason === "n/a") {
|
|
53
|
+
suppressBelowDepth = result.depth;
|
|
54
|
+
}
|
|
55
|
+
yield result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function countResults(results) {
|
|
59
|
+
let passed = 0;
|
|
60
|
+
let failed = 0;
|
|
61
|
+
let skipped = 0;
|
|
62
|
+
for (const r of iterateWithNaSuppression(results)) {
|
|
63
|
+
if (r.status === "passed") passed++;
|
|
64
|
+
else if (r.status === "failed") failed++;
|
|
65
|
+
else skipped++;
|
|
66
|
+
}
|
|
67
|
+
return { passed, failed, skipped };
|
|
68
|
+
}
|
|
69
|
+
function reportPreflight(report, options) {
|
|
70
|
+
const fixLocation = options?.fixLocation ?? "end";
|
|
71
|
+
const reportOn = options?.reportOn ?? "recommend";
|
|
72
|
+
const lines = [];
|
|
73
|
+
const collectedFixes = [];
|
|
74
|
+
const visibleResults = report.results.filter((r) => meetsThreshold(r.severity, reportOn));
|
|
75
|
+
for (const result of iterateWithNaSuppression(visibleResults)) {
|
|
76
|
+
const indent = " ".repeat(result.depth);
|
|
77
|
+
const icon = getIcon(result);
|
|
78
|
+
let checkLine = `${indent}${icon} ${result.name} (${formatDuration(result.durationMs)})`;
|
|
79
|
+
if (result.detail !== null) {
|
|
80
|
+
checkLine += ` \u2014 ${result.detail}`;
|
|
81
|
+
}
|
|
82
|
+
if (result.progress !== null) {
|
|
83
|
+
checkLine += ` \u2014 ${formatProgress(result.progress)}`;
|
|
84
|
+
}
|
|
85
|
+
lines.push(checkLine);
|
|
86
|
+
if (result.status === "failed") {
|
|
87
|
+
const includeFix = fixLocation === "inline";
|
|
88
|
+
const details = collectInlineDetails(result, includeFix);
|
|
89
|
+
lines.push(...details.map((line) => `${indent}${line}`));
|
|
90
|
+
if (!includeFix && result.fix !== null) {
|
|
91
|
+
collectedFixes.push(result.fix);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const { passed, failed, skipped } = countResults(visibleResults);
|
|
96
|
+
lines.push("", `${formatSummaryCounts(passed, failed, skipped)} (${formatDuration(report.durationMs)})`);
|
|
97
|
+
if (fixLocation === "end" && collectedFixes.length > 0) {
|
|
98
|
+
lines.push("", "Fixes:", ...collectedFixes.map((fix) => ` ${ICON_FIX} ${fix}`));
|
|
99
|
+
}
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
formatSummaryCounts,
|
|
104
|
+
reportPreflight
|
|
105
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FixLocation, RdyReport, Severity } from './types.ts';
|
|
2
|
+
export interface ReportRdyOptions {
|
|
3
|
+
fixLocation?: FixLocation;
|
|
4
|
+
reportOn?: Severity;
|
|
5
|
+
}
|
|
6
|
+
export declare function formatSummaryCounts(passed: number, failed: number, skipped: number): string;
|
|
7
|
+
export declare function reportRdy(report: RdyReport, options?: ReportRdyOptions): string;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { meetsThreshold } from "./runRdy.js";
|
|
2
|
+
import { isPercentProgress } from "./types.js";
|
|
3
|
+
const ICON_PASSED = "\u{1F7E2}";
|
|
4
|
+
const ICON_ERROR_FAILED = "\u{1F534}";
|
|
5
|
+
const ICON_WARN_FAILED = "\u{1F7E0}";
|
|
6
|
+
const ICON_RECOMMEND_FAILED = "\u{1F7E1}";
|
|
7
|
+
const ICON_SKIPPED_NA = "\u26AA";
|
|
8
|
+
const ICON_SKIPPED_PRECONDITION = "\u26D4";
|
|
9
|
+
const ICON_FIX = "\u{1F48A}";
|
|
10
|
+
function formatDuration(ms) {
|
|
11
|
+
return `${Math.round(ms)}ms`;
|
|
12
|
+
}
|
|
13
|
+
function getIcon(result) {
|
|
14
|
+
if (result.status === "passed") return ICON_PASSED;
|
|
15
|
+
if (result.status === "skipped") {
|
|
16
|
+
return result.skipReason === "precondition" ? ICON_SKIPPED_PRECONDITION : ICON_SKIPPED_NA;
|
|
17
|
+
}
|
|
18
|
+
if (result.severity === "warn") return ICON_WARN_FAILED;
|
|
19
|
+
if (result.severity === "recommend") return ICON_RECOMMEND_FAILED;
|
|
20
|
+
return ICON_ERROR_FAILED;
|
|
21
|
+
}
|
|
22
|
+
function formatProgress(progress) {
|
|
23
|
+
if (isPercentProgress(progress)) {
|
|
24
|
+
return `${progress.percent}%`;
|
|
25
|
+
}
|
|
26
|
+
return `${progress.passedCount} of ${progress.count}`;
|
|
27
|
+
}
|
|
28
|
+
function formatSummaryCounts(passed, failed, skipped) {
|
|
29
|
+
const parts = [];
|
|
30
|
+
if (passed > 0) parts.push(`${ICON_PASSED} ${passed} passed`);
|
|
31
|
+
if (failed > 0) parts.push(`${ICON_ERROR_FAILED} ${failed} failed`);
|
|
32
|
+
if (skipped > 0) parts.push(`${ICON_SKIPPED_PRECONDITION} ${skipped} skipped`);
|
|
33
|
+
return parts.join(", ");
|
|
34
|
+
}
|
|
35
|
+
function collectInlineDetails(result, includeFix) {
|
|
36
|
+
const details = [];
|
|
37
|
+
if (result.error !== null) {
|
|
38
|
+
details.push(` Error: ${result.error.message}`);
|
|
39
|
+
}
|
|
40
|
+
if (includeFix && result.fix !== null) {
|
|
41
|
+
details.push(` ${ICON_FIX} Fix: ${result.fix}`);
|
|
42
|
+
}
|
|
43
|
+
return details;
|
|
44
|
+
}
|
|
45
|
+
function* iterateWithNaSuppression(results) {
|
|
46
|
+
let suppressBelowDepth = null;
|
|
47
|
+
for (const result of results) {
|
|
48
|
+
if (suppressBelowDepth !== null) {
|
|
49
|
+
if (result.depth > suppressBelowDepth) continue;
|
|
50
|
+
suppressBelowDepth = null;
|
|
51
|
+
}
|
|
52
|
+
if (result.status === "skipped" && result.skipReason === "n/a") {
|
|
53
|
+
suppressBelowDepth = result.depth;
|
|
54
|
+
}
|
|
55
|
+
yield result;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function countResults(results) {
|
|
59
|
+
let passed = 0;
|
|
60
|
+
let failed = 0;
|
|
61
|
+
let skipped = 0;
|
|
62
|
+
for (const r of iterateWithNaSuppression(results)) {
|
|
63
|
+
if (r.status === "passed") passed++;
|
|
64
|
+
else if (r.status === "failed") failed++;
|
|
65
|
+
else skipped++;
|
|
66
|
+
}
|
|
67
|
+
return { passed, failed, skipped };
|
|
68
|
+
}
|
|
69
|
+
function reportRdy(report, options) {
|
|
70
|
+
const fixLocation = options?.fixLocation ?? "end";
|
|
71
|
+
const reportOn = options?.reportOn ?? "recommend";
|
|
72
|
+
const lines = [];
|
|
73
|
+
const collectedFixes = [];
|
|
74
|
+
const visibleResults = report.results.filter((r) => meetsThreshold(r.severity, reportOn));
|
|
75
|
+
for (const result of iterateWithNaSuppression(visibleResults)) {
|
|
76
|
+
const indent = " ".repeat(result.depth);
|
|
77
|
+
const icon = getIcon(result);
|
|
78
|
+
let checkLine = `${indent}${icon} ${result.name} (${formatDuration(result.durationMs)})`;
|
|
79
|
+
if (result.detail !== null) {
|
|
80
|
+
checkLine += ` \u2014 ${result.detail}`;
|
|
81
|
+
}
|
|
82
|
+
if (result.progress !== null) {
|
|
83
|
+
checkLine += ` \u2014 ${formatProgress(result.progress)}`;
|
|
84
|
+
}
|
|
85
|
+
lines.push(checkLine);
|
|
86
|
+
if (result.status === "failed") {
|
|
87
|
+
const includeFix = fixLocation === "inline";
|
|
88
|
+
const details = collectInlineDetails(result, includeFix);
|
|
89
|
+
lines.push(...details.map((line) => `${indent}${line}`));
|
|
90
|
+
if (!includeFix && result.fix !== null) {
|
|
91
|
+
collectedFixes.push(result.fix);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const { passed, failed, skipped } = countResults(visibleResults);
|
|
96
|
+
lines.push("", `${formatSummaryCounts(passed, failed, skipped)} (${formatDuration(report.durationMs)})`);
|
|
97
|
+
if (fixLocation === "end" && collectedFixes.length > 0) {
|
|
98
|
+
lines.push("", "Fixes:", ...collectedFixes.map((fix) => ` ${ICON_FIX} ${fix}`));
|
|
99
|
+
}
|
|
100
|
+
return lines.join("\n");
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
formatSummaryCounts,
|
|
104
|
+
reportRdy
|
|
105
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveCollectionExports(moduleRecord: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isRecord } from "./isRecord.js";
|
|
2
|
+
function resolveCollectionExports(moduleRecord) {
|
|
3
|
+
const source = isRecord(moduleRecord.default) ? moduleRecord.default : moduleRecord;
|
|
4
|
+
if (source.checklists === void 0) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
"Collection file must export checklists (e.g., `export default definePreflightCollection({ checklists: [...] })` or `export const checklists = [...]`)"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
checklists: source.checklists,
|
|
11
|
+
...source.defaultSeverity !== void 0 && { defaultSeverity: source.defaultSeverity },
|
|
12
|
+
...source.failOn !== void 0 && { failOn: source.failOn },
|
|
13
|
+
...source.fixLocation !== void 0 && { fixLocation: source.fixLocation },
|
|
14
|
+
...source.reportOn !== void 0 && { reportOn: source.reportOn },
|
|
15
|
+
...source.suites !== void 0 && { suites: source.suites }
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
resolveCollectionExports
|
|
20
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveGitHubToken(): string | undefined;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
function resolveGitHubToken() {
|
|
3
|
+
const envToken = process.env.GITHUB_TOKEN;
|
|
4
|
+
if (envToken !== void 0 && envToken !== "") {
|
|
5
|
+
return envToken;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
const output = execFileSync("gh", ["auth", "token"], {
|
|
9
|
+
encoding: "utf8",
|
|
10
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
11
|
+
});
|
|
12
|
+
const trimmed = output.trim();
|
|
13
|
+
if (trimmed !== "") {
|
|
14
|
+
return trimmed;
|
|
15
|
+
}
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
resolveGitHubToken
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function resolveKitExports(moduleRecord: Record<string, unknown>): Record<string, unknown>;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { isRecord } from "./isRecord.js";
|
|
2
|
+
function resolveKitExports(moduleRecord) {
|
|
3
|
+
const source = isRecord(moduleRecord.default) ? moduleRecord.default : moduleRecord;
|
|
4
|
+
if (source.checklists === void 0) {
|
|
5
|
+
throw new Error(
|
|
6
|
+
"Kit file must export checklists (e.g., `export default defineRdyKit({ checklists: [...] })` or `export const checklists = [...]`)"
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
return {
|
|
10
|
+
checklists: source.checklists,
|
|
11
|
+
...source.defaultSeverity !== void 0 && { defaultSeverity: source.defaultSeverity },
|
|
12
|
+
...source.failOn !== void 0 && { failOn: source.failOn },
|
|
13
|
+
...source.fixLocation !== void 0 && { fixLocation: source.fixLocation },
|
|
14
|
+
...source.reportOn !== void 0 && { reportOn: source.reportOn },
|
|
15
|
+
...source.suites !== void 0 && { suites: source.suites }
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export {
|
|
19
|
+
resolveKitExports
|
|
20
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
function resolveRequestedNames(requestedNames, kit) {
|
|
2
|
+
if (requestedNames.length === 0) {
|
|
3
|
+
return kit.checklists.map((c) => c.name);
|
|
4
|
+
}
|
|
5
|
+
const checklistNames = new Set(kit.checklists.map((c) => c.name));
|
|
6
|
+
const suites = kit.suites ?? {};
|
|
7
|
+
const unknownNames = [];
|
|
8
|
+
const resolved = [];
|
|
9
|
+
const seen = /* @__PURE__ */ new Set();
|
|
10
|
+
for (const name of requestedNames) {
|
|
11
|
+
const suiteEntries = suites[name];
|
|
12
|
+
if (suiteEntries !== void 0) {
|
|
13
|
+
for (const entry of suiteEntries) {
|
|
14
|
+
if (!seen.has(entry)) {
|
|
15
|
+
seen.add(entry);
|
|
16
|
+
resolved.push(entry);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
} else if (checklistNames.has(name)) {
|
|
20
|
+
if (!seen.has(name)) {
|
|
21
|
+
seen.add(name);
|
|
22
|
+
resolved.push(name);
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
unknownNames.push(name);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
if (unknownNames.length > 0) {
|
|
29
|
+
const checklistList = [...checklistNames].join(", ");
|
|
30
|
+
const suiteNames = Object.keys(suites);
|
|
31
|
+
const suiteList = suiteNames.length > 0 ? `. Suites: ${suiteNames.join(", ")}` : "";
|
|
32
|
+
throw new Error(`Unknown name(s): ${unknownNames.join(", ")}. Checklists: ${checklistList}${suiteList}`);
|
|
33
|
+
}
|
|
34
|
+
return resolved;
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
resolveRequestedNames
|
|
38
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PreflightChecklist, PreflightReport, PreflightStagedChecklist, Severity } from './types.ts';
|
|
2
|
+
export interface RunPreflightOptions {
|
|
3
|
+
defaultSeverity?: Severity;
|
|
4
|
+
failOn?: Severity;
|
|
5
|
+
}
|
|
6
|
+
export declare function meetsThreshold(severity: Severity, threshold: Severity): boolean;
|
|
7
|
+
export declare function runPreflight(checklist: PreflightChecklist | PreflightStagedChecklist, options?: RunPreflightOptions): Promise<PreflightReport>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { isFlatChecklist } from "./types.js";
|
|
3
|
+
const SEVERITY_RANK = {
|
|
4
|
+
error: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
recommend: 2
|
|
7
|
+
};
|
|
8
|
+
function meetsThreshold(severity, threshold) {
|
|
9
|
+
return SEVERITY_RANK[severity] <= SEVERITY_RANK[threshold];
|
|
10
|
+
}
|
|
11
|
+
function resolveSeverity(check, defaultSeverity) {
|
|
12
|
+
return check.severity ?? defaultSeverity;
|
|
13
|
+
}
|
|
14
|
+
function buildPassedResult(name, severity, durationMs, detail, fix, progress, depth = 0) {
|
|
15
|
+
return { name, status: "passed", ok: true, severity, detail, fix, error: null, progress, durationMs, depth };
|
|
16
|
+
}
|
|
17
|
+
function buildFailedResult(name, severity, durationMs, detail, fix, error, progress, depth = 0) {
|
|
18
|
+
return { name, status: "failed", ok: false, severity, detail, fix, error, progress, durationMs, depth };
|
|
19
|
+
}
|
|
20
|
+
function buildSkippedResult(name, severity, skipReason, detail, fix, depth = 0) {
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
status: "skipped",
|
|
24
|
+
ok: null,
|
|
25
|
+
severity,
|
|
26
|
+
skipReason,
|
|
27
|
+
detail,
|
|
28
|
+
fix,
|
|
29
|
+
error: null,
|
|
30
|
+
progress: null,
|
|
31
|
+
durationMs: 0,
|
|
32
|
+
depth
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function executeCheck(check, defaultSeverity, depth = 0) {
|
|
36
|
+
const severity = resolveSeverity(check, defaultSeverity);
|
|
37
|
+
const fix = check.fix ?? null;
|
|
38
|
+
const children = check.checks ?? [];
|
|
39
|
+
if (check.skip !== void 0) {
|
|
40
|
+
const start2 = performance.now();
|
|
41
|
+
try {
|
|
42
|
+
const skipResult = await check.skip();
|
|
43
|
+
if (typeof skipResult === "string") {
|
|
44
|
+
const result = buildSkippedResult(check.name, severity, "n/a", skipResult, fix, depth);
|
|
45
|
+
const childResults = skipAllDescendants(children, defaultSeverity, "n/a", depth + 1);
|
|
46
|
+
return [result, ...childResults];
|
|
47
|
+
}
|
|
48
|
+
} catch (error_) {
|
|
49
|
+
const durationMs = performance.now() - start2;
|
|
50
|
+
const error = error_ instanceof Error ? error_ : new Error(String(error_));
|
|
51
|
+
const result = buildFailedResult(check.name, severity, durationMs, null, fix, error, null, depth);
|
|
52
|
+
const childResults = skipAllDescendants(children, defaultSeverity, "precondition", depth + 1);
|
|
53
|
+
return [result, ...childResults];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const start = performance.now();
|
|
57
|
+
try {
|
|
58
|
+
const raw = await check.check();
|
|
59
|
+
const durationMs = performance.now() - start;
|
|
60
|
+
let result;
|
|
61
|
+
if (typeof raw === "boolean") {
|
|
62
|
+
result = raw ? buildPassedResult(check.name, severity, durationMs, null, fix, null, depth) : buildFailedResult(check.name, severity, durationMs, null, fix, null, null, depth);
|
|
63
|
+
} else {
|
|
64
|
+
const detail = raw.detail ?? null;
|
|
65
|
+
const progress = raw.progress ?? null;
|
|
66
|
+
result = raw.ok ? buildPassedResult(check.name, severity, durationMs, detail, fix, progress, depth) : buildFailedResult(check.name, severity, durationMs, detail, fix, null, progress, depth);
|
|
67
|
+
}
|
|
68
|
+
const childResults = await collectChildResults(result, children, defaultSeverity, depth + 1);
|
|
69
|
+
return [result, ...childResults];
|
|
70
|
+
} catch (error_) {
|
|
71
|
+
const durationMs = performance.now() - start;
|
|
72
|
+
const error = error_ instanceof Error ? error_ : new Error(String(error_));
|
|
73
|
+
const result = buildFailedResult(check.name, severity, durationMs, null, fix, error, null, depth);
|
|
74
|
+
const childResults = skipAllDescendants(children, defaultSeverity, "precondition", depth + 1);
|
|
75
|
+
return [result, ...childResults];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function collectChildResults(parentResult, children, defaultSeverity, childDepth) {
|
|
79
|
+
if (children.length === 0) return [];
|
|
80
|
+
if (parentResult.status === "failed") {
|
|
81
|
+
return skipAllDescendants(children, defaultSeverity, "precondition", childDepth);
|
|
82
|
+
}
|
|
83
|
+
if (parentResult.status === "skipped") {
|
|
84
|
+
const reason = parentResult.skipReason === "n/a" ? "n/a" : "precondition";
|
|
85
|
+
return skipAllDescendants(children, defaultSeverity, reason, childDepth);
|
|
86
|
+
}
|
|
87
|
+
return runSiblingChecks(children, defaultSeverity, childDepth);
|
|
88
|
+
}
|
|
89
|
+
async function runSiblingChecks(checks, defaultSeverity, depth) {
|
|
90
|
+
const siblingTrees = await Promise.all(checks.map((c) => executeCheck(c, defaultSeverity, depth)));
|
|
91
|
+
return siblingTrees.flat();
|
|
92
|
+
}
|
|
93
|
+
function skipAllDescendants(checks, defaultSeverity, skipReason, depth) {
|
|
94
|
+
const results = [];
|
|
95
|
+
for (const check of checks) {
|
|
96
|
+
results.push(skipCheck(check, defaultSeverity, skipReason, depth));
|
|
97
|
+
if (check.checks !== void 0 && check.checks.length > 0) {
|
|
98
|
+
results.push(...skipAllDescendants(check.checks, defaultSeverity, skipReason, depth + 1));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
function skipCheck(check, defaultSeverity, skipReason = "precondition", depth = 0) {
|
|
104
|
+
const severity = resolveSeverity(check, defaultSeverity);
|
|
105
|
+
return buildSkippedResult(check.name, severity, skipReason, null, check.fix ?? null, depth);
|
|
106
|
+
}
|
|
107
|
+
async function runPreconditions(preconditions, results, defaultSeverity) {
|
|
108
|
+
if (preconditions.length === 0) return true;
|
|
109
|
+
const trees = await Promise.all(preconditions.map((c) => executeCheck(c, defaultSeverity)));
|
|
110
|
+
const flat = trees.flat();
|
|
111
|
+
results.push(...flat);
|
|
112
|
+
return flat.filter((r) => r.depth === 0).every((r) => r.status === "passed");
|
|
113
|
+
}
|
|
114
|
+
async function runFlatChecks(checklist, results, preconditionsPassed, defaultSeverity) {
|
|
115
|
+
if (!preconditionsPassed) {
|
|
116
|
+
results.push(...skipAllDescendants(checklist.checks, defaultSeverity, "precondition", 0));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const checkResults = await runSiblingChecks(checklist.checks, defaultSeverity, 0);
|
|
120
|
+
results.push(...checkResults);
|
|
121
|
+
}
|
|
122
|
+
async function runStagedChecks(checklist, results, preconditionsPassed, defaultSeverity, failOn) {
|
|
123
|
+
if (!preconditionsPassed) {
|
|
124
|
+
for (const group of checklist.groups) {
|
|
125
|
+
results.push(...skipAllDescendants(group, defaultSeverity, "precondition", 0));
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let shouldSkipRemaining = false;
|
|
130
|
+
for (const group of checklist.groups) {
|
|
131
|
+
if (shouldSkipRemaining) {
|
|
132
|
+
results.push(...skipAllDescendants(group, defaultSeverity, "precondition", 0));
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const groupResults = await runSiblingChecks(group, defaultSeverity, 0);
|
|
136
|
+
results.push(...groupResults);
|
|
137
|
+
const topLevelFailed = groupResults.filter((r) => r.depth === 0).some((r) => r.status === "failed" && meetsThreshold(r.severity, failOn));
|
|
138
|
+
if (topLevelFailed) {
|
|
139
|
+
shouldSkipRemaining = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function runPreflight(checklist, options = {}) {
|
|
144
|
+
const defaultSeverity = options.defaultSeverity ?? "error";
|
|
145
|
+
const failOn = options.failOn ?? "error";
|
|
146
|
+
const start = performance.now();
|
|
147
|
+
const results = [];
|
|
148
|
+
const preconditionsPassed = await runPreconditions(checklist.preconditions ?? [], results, defaultSeverity);
|
|
149
|
+
await (isFlatChecklist(checklist) ? runFlatChecks(checklist, results, preconditionsPassed, defaultSeverity) : runStagedChecks(checklist, results, preconditionsPassed, defaultSeverity, failOn));
|
|
150
|
+
const durationMs = performance.now() - start;
|
|
151
|
+
const passed = !results.some((r) => r.status === "failed" && meetsThreshold(r.severity, failOn));
|
|
152
|
+
return { results, passed, durationMs };
|
|
153
|
+
}
|
|
154
|
+
export {
|
|
155
|
+
meetsThreshold,
|
|
156
|
+
runPreflight
|
|
157
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RdyChecklist, RdyReport, RdyStagedChecklist, Severity } from './types.ts';
|
|
2
|
+
export interface RunRdyOptions {
|
|
3
|
+
defaultSeverity?: Severity;
|
|
4
|
+
failOn?: Severity;
|
|
5
|
+
}
|
|
6
|
+
export declare function meetsThreshold(severity: Severity, threshold: Severity): boolean;
|
|
7
|
+
export declare function runRdy(checklist: RdyChecklist | RdyStagedChecklist, options?: RunRdyOptions): Promise<RdyReport>;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
2
|
+
import { isFlatChecklist } from "./types.js";
|
|
3
|
+
const SEVERITY_RANK = {
|
|
4
|
+
error: 0,
|
|
5
|
+
warn: 1,
|
|
6
|
+
recommend: 2
|
|
7
|
+
};
|
|
8
|
+
function meetsThreshold(severity, threshold) {
|
|
9
|
+
return SEVERITY_RANK[severity] <= SEVERITY_RANK[threshold];
|
|
10
|
+
}
|
|
11
|
+
function resolveSeverity(check, defaultSeverity) {
|
|
12
|
+
return check.severity ?? defaultSeverity;
|
|
13
|
+
}
|
|
14
|
+
function buildPassedResult(name, severity, durationMs, detail, fix, progress, depth = 0) {
|
|
15
|
+
return { name, status: "passed", ok: true, severity, detail, fix, error: null, progress, durationMs, depth };
|
|
16
|
+
}
|
|
17
|
+
function buildFailedResult(name, severity, durationMs, detail, fix, error, progress, depth = 0) {
|
|
18
|
+
return { name, status: "failed", ok: false, severity, detail, fix, error, progress, durationMs, depth };
|
|
19
|
+
}
|
|
20
|
+
function buildSkippedResult(name, severity, skipReason, detail, fix, depth = 0) {
|
|
21
|
+
return {
|
|
22
|
+
name,
|
|
23
|
+
status: "skipped",
|
|
24
|
+
ok: null,
|
|
25
|
+
severity,
|
|
26
|
+
skipReason,
|
|
27
|
+
detail,
|
|
28
|
+
fix,
|
|
29
|
+
error: null,
|
|
30
|
+
progress: null,
|
|
31
|
+
durationMs: 0,
|
|
32
|
+
depth
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
async function executeCheck(check, defaultSeverity, depth = 0) {
|
|
36
|
+
const severity = resolveSeverity(check, defaultSeverity);
|
|
37
|
+
const fix = check.fix ?? null;
|
|
38
|
+
const children = check.checks ?? [];
|
|
39
|
+
if (check.skip !== void 0) {
|
|
40
|
+
const start2 = performance.now();
|
|
41
|
+
try {
|
|
42
|
+
const skipResult = await check.skip();
|
|
43
|
+
if (typeof skipResult === "string") {
|
|
44
|
+
const result = buildSkippedResult(check.name, severity, "n/a", skipResult, fix, depth);
|
|
45
|
+
const childResults = skipAllDescendants(children, defaultSeverity, "n/a", depth + 1);
|
|
46
|
+
return [result, ...childResults];
|
|
47
|
+
}
|
|
48
|
+
} catch (error_) {
|
|
49
|
+
const durationMs = performance.now() - start2;
|
|
50
|
+
const error = error_ instanceof Error ? error_ : new Error(String(error_));
|
|
51
|
+
const result = buildFailedResult(check.name, severity, durationMs, null, fix, error, null, depth);
|
|
52
|
+
const childResults = skipAllDescendants(children, defaultSeverity, "precondition", depth + 1);
|
|
53
|
+
return [result, ...childResults];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const start = performance.now();
|
|
57
|
+
try {
|
|
58
|
+
const raw = await check.check();
|
|
59
|
+
const durationMs = performance.now() - start;
|
|
60
|
+
let result;
|
|
61
|
+
if (typeof raw === "boolean") {
|
|
62
|
+
result = raw ? buildPassedResult(check.name, severity, durationMs, null, fix, null, depth) : buildFailedResult(check.name, severity, durationMs, null, fix, null, null, depth);
|
|
63
|
+
} else {
|
|
64
|
+
const detail = raw.detail ?? null;
|
|
65
|
+
const progress = raw.progress ?? null;
|
|
66
|
+
result = raw.ok ? buildPassedResult(check.name, severity, durationMs, detail, fix, progress, depth) : buildFailedResult(check.name, severity, durationMs, detail, fix, null, progress, depth);
|
|
67
|
+
}
|
|
68
|
+
const childResults = await collectChildResults(result, children, defaultSeverity, depth + 1);
|
|
69
|
+
return [result, ...childResults];
|
|
70
|
+
} catch (error_) {
|
|
71
|
+
const durationMs = performance.now() - start;
|
|
72
|
+
const error = error_ instanceof Error ? error_ : new Error(String(error_));
|
|
73
|
+
const result = buildFailedResult(check.name, severity, durationMs, null, fix, error, null, depth);
|
|
74
|
+
const childResults = skipAllDescendants(children, defaultSeverity, "precondition", depth + 1);
|
|
75
|
+
return [result, ...childResults];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async function collectChildResults(parentResult, children, defaultSeverity, childDepth) {
|
|
79
|
+
if (children.length === 0) return [];
|
|
80
|
+
if (parentResult.status === "failed") {
|
|
81
|
+
return skipAllDescendants(children, defaultSeverity, "precondition", childDepth);
|
|
82
|
+
}
|
|
83
|
+
if (parentResult.status === "skipped") {
|
|
84
|
+
const reason = parentResult.skipReason === "n/a" ? "n/a" : "precondition";
|
|
85
|
+
return skipAllDescendants(children, defaultSeverity, reason, childDepth);
|
|
86
|
+
}
|
|
87
|
+
return runSiblingChecks(children, defaultSeverity, childDepth);
|
|
88
|
+
}
|
|
89
|
+
async function runSiblingChecks(checks, defaultSeverity, depth) {
|
|
90
|
+
const siblingTrees = await Promise.all(checks.map((c) => executeCheck(c, defaultSeverity, depth)));
|
|
91
|
+
return siblingTrees.flat();
|
|
92
|
+
}
|
|
93
|
+
function skipAllDescendants(checks, defaultSeverity, skipReason, depth) {
|
|
94
|
+
const results = [];
|
|
95
|
+
for (const check of checks) {
|
|
96
|
+
results.push(skipCheck(check, defaultSeverity, skipReason, depth));
|
|
97
|
+
if (check.checks !== void 0 && check.checks.length > 0) {
|
|
98
|
+
results.push(...skipAllDescendants(check.checks, defaultSeverity, skipReason, depth + 1));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
}
|
|
103
|
+
function skipCheck(check, defaultSeverity, skipReason = "precondition", depth = 0) {
|
|
104
|
+
const severity = resolveSeverity(check, defaultSeverity);
|
|
105
|
+
return buildSkippedResult(check.name, severity, skipReason, null, check.fix ?? null, depth);
|
|
106
|
+
}
|
|
107
|
+
async function runPreconditions(preconditions, results, defaultSeverity) {
|
|
108
|
+
if (preconditions.length === 0) return true;
|
|
109
|
+
const trees = await Promise.all(preconditions.map((c) => executeCheck(c, defaultSeverity)));
|
|
110
|
+
const flat = trees.flat();
|
|
111
|
+
results.push(...flat);
|
|
112
|
+
return flat.filter((r) => r.depth === 0).every((r) => r.status === "passed");
|
|
113
|
+
}
|
|
114
|
+
async function runFlatChecks(checklist, results, preconditionsPassed, defaultSeverity) {
|
|
115
|
+
if (!preconditionsPassed) {
|
|
116
|
+
results.push(...skipAllDescendants(checklist.checks, defaultSeverity, "precondition", 0));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
const checkResults = await runSiblingChecks(checklist.checks, defaultSeverity, 0);
|
|
120
|
+
results.push(...checkResults);
|
|
121
|
+
}
|
|
122
|
+
async function runStagedChecks(checklist, results, preconditionsPassed, defaultSeverity, failOn) {
|
|
123
|
+
if (!preconditionsPassed) {
|
|
124
|
+
for (const group of checklist.groups) {
|
|
125
|
+
results.push(...skipAllDescendants(group, defaultSeverity, "precondition", 0));
|
|
126
|
+
}
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
let shouldSkipRemaining = false;
|
|
130
|
+
for (const group of checklist.groups) {
|
|
131
|
+
if (shouldSkipRemaining) {
|
|
132
|
+
results.push(...skipAllDescendants(group, defaultSeverity, "precondition", 0));
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const groupResults = await runSiblingChecks(group, defaultSeverity, 0);
|
|
136
|
+
results.push(...groupResults);
|
|
137
|
+
const topLevelFailed = groupResults.filter((r) => r.depth === 0).some((r) => r.status === "failed" && meetsThreshold(r.severity, failOn));
|
|
138
|
+
if (topLevelFailed) {
|
|
139
|
+
shouldSkipRemaining = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function runRdy(checklist, options = {}) {
|
|
144
|
+
const defaultSeverity = options.defaultSeverity ?? "error";
|
|
145
|
+
const failOn = options.failOn ?? "error";
|
|
146
|
+
const start = performance.now();
|
|
147
|
+
const results = [];
|
|
148
|
+
const preconditionsPassed = await runPreconditions(checklist.preconditions ?? [], results, defaultSeverity);
|
|
149
|
+
await (isFlatChecklist(checklist) ? runFlatChecks(checklist, results, preconditionsPassed, defaultSeverity) : runStagedChecks(checklist, results, preconditionsPassed, defaultSeverity, failOn));
|
|
150
|
+
const durationMs = performance.now() - start;
|
|
151
|
+
const passed = !results.some((r) => r.status === "failed" && meetsThreshold(r.severity, failOn));
|
|
152
|
+
return { results, passed, durationMs };
|
|
153
|
+
}
|
|
154
|
+
export {
|
|
155
|
+
meetsThreshold,
|
|
156
|
+
runRdy
|
|
157
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { WriteResult } from './writeFileWithCheck.ts';
|
|
2
|
+
export declare function printStep(message: string): void;
|
|
3
|
+
export declare function printSuccess(message: string): void;
|
|
4
|
+
export declare function printSkip(message: string): void;
|
|
5
|
+
export declare function printError(message: string): void;
|
|
6
|
+
export declare function reportWriteResult(result: WriteResult, dryRun: boolean): void;
|