@cleaver/breakcheck 0.2.5 → 0.3.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/README.md +137 -1
- package/agent-guide.md +107 -0
- package/dist/cli/commands/baseline.d.ts +19 -0
- package/dist/cli/commands/baseline.js +133 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/check.d.ts +27 -0
- package/dist/cli/commands/check.js +204 -0
- package/dist/cli/commands/check.js.map +1 -0
- package/dist/cli/commands/clean.d.ts +25 -0
- package/dist/cli/commands/clean.js +123 -0
- package/dist/cli/commands/clean.js.map +1 -0
- package/dist/cli/commands/compare.js +28 -6
- package/dist/cli/commands/compare.js.map +1 -1
- package/dist/cli/commands/compile.d.ts +5 -0
- package/dist/cli/commands/compile.js +23 -0
- package/dist/cli/commands/compile.js.map +1 -0
- package/dist/cli/commands/help.js +163 -4
- package/dist/cli/commands/help.js.map +1 -1
- package/dist/cli/commands/init.d.ts +26 -0
- package/dist/cli/commands/init.js +224 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list-snapshots.js +7 -3
- package/dist/cli/commands/list-snapshots.js.map +1 -1
- package/dist/cli/commands/new.d.ts +9 -0
- package/dist/cli/commands/new.js +27 -0
- package/dist/cli/commands/new.js.map +1 -0
- package/dist/cli/commands/snapshot.d.ts +20 -0
- package/dist/cli/commands/snapshot.js +116 -39
- package/dist/cli/commands/snapshot.js.map +1 -1
- package/dist/cli/commands/view.d.ts +1 -1
- package/dist/cli/commands/view.js +12 -6
- package/dist/cli/commands/view.js.map +1 -1
- package/dist/cli/config.d.ts +14 -0
- package/dist/cli/config.js +58 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/rules.d.ts +12 -0
- package/dist/cli/rules.js +110 -0
- package/dist/cli/rules.js.map +1 -0
- package/dist/cli/site-environment.d.ts +12 -0
- package/dist/cli/site-environment.js +231 -0
- package/dist/cli/site-environment.js.map +1 -0
- package/dist/cli/url-manifest.js.map +1 -1
- package/dist/cli/utils.d.ts +1 -0
- package/dist/cli/utils.js +4 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -7
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { runSiteCheck, } from "@cleaver/breakcheck-core";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { InteractiveCommand } from "interactive-commander";
|
|
6
|
+
import { addConfigOptions, resolveCliProjectConfig } from "../config.js";
|
|
7
|
+
import { parseUrlManifest, readUrlManifest } from "../url-manifest.js";
|
|
8
|
+
import { withCheckEnvironment } from "../site-environment.js";
|
|
9
|
+
import { configureLogger } from "../utils.js";
|
|
10
|
+
function errorMessage(error) {
|
|
11
|
+
if (error instanceof AggregateError) {
|
|
12
|
+
return [...error.errors].map(errorMessage).join("; ");
|
|
13
|
+
}
|
|
14
|
+
return error instanceof Error ? error.message : String(error);
|
|
15
|
+
}
|
|
16
|
+
function requireHttpUrl(value) {
|
|
17
|
+
if (value === undefined || value.trim().length === 0) {
|
|
18
|
+
throw new Error("A site URL is required. Set baseUrl in breakcheck.config.json or pass --url.");
|
|
19
|
+
}
|
|
20
|
+
let parsed;
|
|
21
|
+
try {
|
|
22
|
+
parsed = new URL(value);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new Error("The site URL must be a valid HTTP(S) URL.", {
|
|
26
|
+
cause: error,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") ||
|
|
30
|
+
!parsed.hostname) {
|
|
31
|
+
throw new Error("The site URL must be a valid HTTP(S) URL.");
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function createRunId() {
|
|
36
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
37
|
+
return `check_${timestamp}_${randomUUID().slice(0, 8)}`;
|
|
38
|
+
}
|
|
39
|
+
function exitCodeFor(status) {
|
|
40
|
+
switch (status) {
|
|
41
|
+
case "passed":
|
|
42
|
+
return 0;
|
|
43
|
+
case "differences":
|
|
44
|
+
return 1;
|
|
45
|
+
case "failed":
|
|
46
|
+
return 2;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async function saveReport(report, reportDir) {
|
|
50
|
+
await mkdir(reportDir, { recursive: true });
|
|
51
|
+
const reportPath = path.join(reportDir, `${report.runId}.json`);
|
|
52
|
+
const savedReport = { ...report, reportPath };
|
|
53
|
+
await writeFile(reportPath, `${JSON.stringify(savedReport, null, 2)}\n`, {
|
|
54
|
+
encoding: "utf8",
|
|
55
|
+
flag: "wx",
|
|
56
|
+
});
|
|
57
|
+
return savedReport;
|
|
58
|
+
}
|
|
59
|
+
export async function executeCheck(options, args = process.argv.slice(2), cwd = process.cwd()) {
|
|
60
|
+
const logger = configureLogger({
|
|
61
|
+
jsonLogs: options.jsonLogs,
|
|
62
|
+
noJsonLogs: options.noJsonLogs,
|
|
63
|
+
silent: options.json,
|
|
64
|
+
});
|
|
65
|
+
const startedAt = new Date().toISOString();
|
|
66
|
+
const runId = createRunId();
|
|
67
|
+
let reportDir;
|
|
68
|
+
let baseUrl;
|
|
69
|
+
let baselineSnapshotId;
|
|
70
|
+
let routes = [];
|
|
71
|
+
let result;
|
|
72
|
+
let lifecycleResult;
|
|
73
|
+
const errors = [];
|
|
74
|
+
try {
|
|
75
|
+
const project = await resolveCliProjectConfig(args, cwd);
|
|
76
|
+
const check = project.check;
|
|
77
|
+
if (!check) {
|
|
78
|
+
throw new Error("No check settings found. Add a version-2 check section to breakcheck.config.json.");
|
|
79
|
+
}
|
|
80
|
+
reportDir = check.reportDir;
|
|
81
|
+
baselineSnapshotId = check.baselineSnapshot;
|
|
82
|
+
baseUrl = requireHttpUrl(options.url ?? project.baseUrl);
|
|
83
|
+
const manifestText = await readUrlManifest(check.routesFile);
|
|
84
|
+
const manifest = parseUrlManifest(manifestText, baseUrl, check.routesFile);
|
|
85
|
+
if (manifest.issues.length > 0) {
|
|
86
|
+
throw new Error(manifest.issues
|
|
87
|
+
.map((issue) => {
|
|
88
|
+
if (issue.type === "entry") {
|
|
89
|
+
return `${issue.source}:${issue.line}: ${issue.message} (${issue.value})`;
|
|
90
|
+
}
|
|
91
|
+
return `${issue.source}: ${issue.message}`;
|
|
92
|
+
})
|
|
93
|
+
.join("\n"));
|
|
94
|
+
}
|
|
95
|
+
routes = manifest.paths;
|
|
96
|
+
const crawlSettings = {
|
|
97
|
+
baseUrl,
|
|
98
|
+
crawlerType: project.crawl.crawlerType ?? "cheerio",
|
|
99
|
+
maxDepth: project.crawl.maxDepth ?? 3,
|
|
100
|
+
maxConcurrency: project.crawl.maxConcurrency ?? 5,
|
|
101
|
+
includePatterns: [],
|
|
102
|
+
excludePatterns: [],
|
|
103
|
+
};
|
|
104
|
+
const checkConfig = {
|
|
105
|
+
baseUrl,
|
|
106
|
+
name: runId,
|
|
107
|
+
baselineSnapshotId: check.baselineSnapshot,
|
|
108
|
+
routes,
|
|
109
|
+
crawlSettings,
|
|
110
|
+
ruleset: project.rulesDir,
|
|
111
|
+
};
|
|
112
|
+
result = await withCheckEnvironment(check, baseUrl, options.url !== undefined, async (signal) => {
|
|
113
|
+
const siteResult = await runSiteCheck(checkConfig, {
|
|
114
|
+
storageDir: project.storageDir,
|
|
115
|
+
signal,
|
|
116
|
+
});
|
|
117
|
+
lifecycleResult = siteResult;
|
|
118
|
+
return siteResult;
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const message = errorMessage(error);
|
|
123
|
+
if (lifecycleResult !== undefined) {
|
|
124
|
+
result = {
|
|
125
|
+
...lifecycleResult,
|
|
126
|
+
status: "failed",
|
|
127
|
+
errors: [...lifecycleResult.errors, message],
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
errors.push(message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
errors.push(...(result?.errors ?? []));
|
|
135
|
+
const status = result?.status ?? "failed";
|
|
136
|
+
let report = {
|
|
137
|
+
schemaVersion: 1,
|
|
138
|
+
runId,
|
|
139
|
+
status,
|
|
140
|
+
exitCode: exitCodeFor(status),
|
|
141
|
+
startedAt,
|
|
142
|
+
completedAt: new Date().toISOString(),
|
|
143
|
+
...(baseUrl === undefined ? {} : { baseUrl }),
|
|
144
|
+
...(baselineSnapshotId === undefined ? {} : { baselineSnapshotId }),
|
|
145
|
+
...(result?.snapshotId === undefined
|
|
146
|
+
? {}
|
|
147
|
+
: { snapshotId: result.snapshotId }),
|
|
148
|
+
routes,
|
|
149
|
+
crawlErrors: result?.crawlErrors ?? [],
|
|
150
|
+
errors,
|
|
151
|
+
...(result?.comparison === undefined
|
|
152
|
+
? {}
|
|
153
|
+
: { comparison: result.comparison }),
|
|
154
|
+
};
|
|
155
|
+
if (reportDir !== undefined) {
|
|
156
|
+
try {
|
|
157
|
+
report = await saveReport(report, reportDir);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
report = {
|
|
161
|
+
...report,
|
|
162
|
+
status: "failed",
|
|
163
|
+
exitCode: 2,
|
|
164
|
+
errors: [
|
|
165
|
+
...report.errors,
|
|
166
|
+
`Unable to save check report: ${errorMessage(error)}`,
|
|
167
|
+
],
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (options.json) {
|
|
172
|
+
process.stdout.write(`${JSON.stringify(report)}\n`);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
if (report.status === "passed") {
|
|
176
|
+
logger.info(`✅ No differences found across ${report.routes.length} route(s).`);
|
|
177
|
+
}
|
|
178
|
+
else if (report.status === "differences") {
|
|
179
|
+
const summary = report.comparison;
|
|
180
|
+
logger.error(`❌ Differences found: ${summary?.pagesWithDifferences ?? 0} changed page(s), ${summary?.newUrls.length ?? 0} added route(s), ${summary?.removedUrls.length ?? 0} removed route(s).`);
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
logger.error("❌ Check could not complete.");
|
|
184
|
+
report.errors.forEach((message) => logger.error(` - ${message}`));
|
|
185
|
+
}
|
|
186
|
+
if (report.reportPath)
|
|
187
|
+
logger.info(`Report: ${report.reportPath}`);
|
|
188
|
+
if (report.comparison?.resultsPath) {
|
|
189
|
+
logger.info(`Comparison details: ${report.comparison.resultsPath}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
process.exitCode = report.exitCode;
|
|
193
|
+
return report;
|
|
194
|
+
}
|
|
195
|
+
export const checkCommand = addConfigOptions(new InteractiveCommand("check")
|
|
196
|
+
.description("Check exact site routes against a stored baseline")
|
|
197
|
+
.option("-u, --url <url>", "Use an already-running site URL")
|
|
198
|
+
.option("--json", "Write one machine-readable check result to stdout")
|
|
199
|
+
.option("--json-logs", "Output logs in JSON format")
|
|
200
|
+
.option("--no-json-logs", "Output logs in pretty format (default)")
|
|
201
|
+
.action(async (options) => {
|
|
202
|
+
await executeCheck(options);
|
|
203
|
+
}));
|
|
204
|
+
//# sourceMappingURL=check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"check.js","sourceRoot":"","sources":["../../../src/cli/commands/check.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { InteractiveCommand } from "interactive-commander";
|
|
2
|
+
export type CleanTarget = "snapshot" | "comparison";
|
|
3
|
+
export interface CleanOptions {
|
|
4
|
+
all?: boolean;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
jsonLogs?: boolean;
|
|
7
|
+
name?: string;
|
|
8
|
+
noJsonLogs?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface CleanResult {
|
|
11
|
+
cancelled: boolean;
|
|
12
|
+
deletedNames: string[];
|
|
13
|
+
target: CleanTarget;
|
|
14
|
+
}
|
|
15
|
+
export interface CleanOperations {
|
|
16
|
+
deleteAll: () => Promise<string[]>;
|
|
17
|
+
deleteOne: (name: string) => Promise<boolean>;
|
|
18
|
+
}
|
|
19
|
+
export interface CleanPrompts {
|
|
20
|
+
confirm: (question: string) => Promise<boolean>;
|
|
21
|
+
isInteractive: boolean;
|
|
22
|
+
promptName: (target: CleanTarget) => Promise<string>;
|
|
23
|
+
}
|
|
24
|
+
export declare function executeClean(target: CleanTarget, options: CleanOptions, operations?: CleanOperations, prompts?: CleanPrompts): Promise<CleanResult>;
|
|
25
|
+
export declare const cleanCommand: InteractiveCommand;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { deleteAllComparisons, deleteAllSnapshots, deleteComparison, deleteSnapshot, } from "@cleaver/breakcheck-core";
|
|
2
|
+
import { createInterface } from "node:readline/promises";
|
|
3
|
+
import { stdin, stdout } from "node:process";
|
|
4
|
+
import { InteractiveCommand } from "interactive-commander";
|
|
5
|
+
import { addConfigOptions, resolveCliProjectConfig } from "../config.js";
|
|
6
|
+
import { configureLogger } from "../utils.js";
|
|
7
|
+
function createCleanOperations(storageDir) {
|
|
8
|
+
const options = { storageDir };
|
|
9
|
+
return {
|
|
10
|
+
snapshot: {
|
|
11
|
+
deleteAll: () => deleteAllSnapshots(options),
|
|
12
|
+
deleteOne: (name) => deleteSnapshot(name, options),
|
|
13
|
+
},
|
|
14
|
+
comparison: {
|
|
15
|
+
deleteAll: () => deleteAllComparisons(options),
|
|
16
|
+
deleteOne: (name) => deleteComparison(name, options),
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const cleanOperations = createCleanOperations();
|
|
21
|
+
function targetLabel(target) {
|
|
22
|
+
return target === "snapshot" ? "snapshot" : "comparison";
|
|
23
|
+
}
|
|
24
|
+
function targetPluralLabel(target) {
|
|
25
|
+
return `${targetLabel(target)}s`;
|
|
26
|
+
}
|
|
27
|
+
function createDefaultPrompts() {
|
|
28
|
+
const isInteractive = Boolean(stdin.isTTY && stdout.isTTY);
|
|
29
|
+
async function ask(question) {
|
|
30
|
+
const readline = createInterface({ input: stdin, output: stdout });
|
|
31
|
+
try {
|
|
32
|
+
return await readline.question(question);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
readline.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
isInteractive,
|
|
40
|
+
promptName: async (target) => ask(`Name of the ${targetLabel(target)} to delete: `),
|
|
41
|
+
confirm: async (question) => {
|
|
42
|
+
const answer = (await ask(`${question} [y/N] `)).trim().toLowerCase();
|
|
43
|
+
return answer === "y" || answer === "yes";
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export async function executeClean(target, options, operations = cleanOperations[target], prompts = createDefaultPrompts()) {
|
|
48
|
+
const hasName = options.name !== undefined;
|
|
49
|
+
const hasAll = options.all === true;
|
|
50
|
+
if (hasName && options.name?.trim().length === 0) {
|
|
51
|
+
throw new Error("--name must not be empty.");
|
|
52
|
+
}
|
|
53
|
+
if (hasName && hasAll) {
|
|
54
|
+
throw new Error("--name and --all cannot be used together.");
|
|
55
|
+
}
|
|
56
|
+
if (!hasName && !hasAll) {
|
|
57
|
+
if (options.force || !prompts.isInteractive) {
|
|
58
|
+
throw new Error("Provide --name <name> or --all; interactive prompting requires a terminal.");
|
|
59
|
+
}
|
|
60
|
+
const promptedName = (await prompts.promptName(target)).trim();
|
|
61
|
+
if (promptedName.length === 0) {
|
|
62
|
+
throw new Error("A name is required to clean a single artifact.");
|
|
63
|
+
}
|
|
64
|
+
options = { ...options, name: promptedName };
|
|
65
|
+
}
|
|
66
|
+
if (!options.force && !prompts.isInteractive) {
|
|
67
|
+
throw new Error("Confirmation requires a terminal; rerun with --force for non-interactive use.");
|
|
68
|
+
}
|
|
69
|
+
const name = options.name?.trim();
|
|
70
|
+
const subject = hasAll
|
|
71
|
+
? `all ${targetPluralLabel(target)}`
|
|
72
|
+
: `${targetLabel(target)} "${name}"`;
|
|
73
|
+
if (!options.force && !(await prompts.confirm(`Delete ${subject}?`))) {
|
|
74
|
+
return { cancelled: true, deletedNames: [], target };
|
|
75
|
+
}
|
|
76
|
+
if (hasAll) {
|
|
77
|
+
return {
|
|
78
|
+
cancelled: false,
|
|
79
|
+
deletedNames: await operations.deleteAll(),
|
|
80
|
+
target,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (name === undefined) {
|
|
84
|
+
throw new Error("A name is required to clean a single artifact.");
|
|
85
|
+
}
|
|
86
|
+
if (!(await operations.deleteOne(name))) {
|
|
87
|
+
throw new Error(`No ${targetLabel(target)} named "${name}" was found.`);
|
|
88
|
+
}
|
|
89
|
+
return { cancelled: false, deletedNames: [name], target };
|
|
90
|
+
}
|
|
91
|
+
function createCleanSubcommand(target) {
|
|
92
|
+
return addConfigOptions(new InteractiveCommand(target)
|
|
93
|
+
.description(`Delete stored ${targetPluralLabel(target)}`)
|
|
94
|
+
.option("--name <name>", `Name of the ${targetLabel(target)} to delete`)
|
|
95
|
+
.option("--all", `Delete all stored ${targetPluralLabel(target)}`)
|
|
96
|
+
.option("--force", "Skip confirmation; required for non-interactive use")
|
|
97
|
+
.option("--json-logs", "Output logs in JSON format")
|
|
98
|
+
.option("--no-json-logs", "Output logs in pretty format (default)")
|
|
99
|
+
.action(async (options) => {
|
|
100
|
+
const logger = configureLogger(options);
|
|
101
|
+
try {
|
|
102
|
+
const projectConfig = await resolveCliProjectConfig();
|
|
103
|
+
const result = await executeClean(target, options, createCleanOperations(projectConfig.storageDir)[target]);
|
|
104
|
+
if (result.cancelled) {
|
|
105
|
+
logger.info(`Cleaning ${targetPluralLabel(target)} cancelled.`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (result.deletedNames.length === 0) {
|
|
109
|
+
logger.info(`No ${targetPluralLabel(target)} found.`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
logger.info(`✅ Deleted ${result.deletedNames.length} ${targetPluralLabel(target)}: ${result.deletedNames.join(", ")}`);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.error({ err: error }, `❌ Error cleaning ${targetPluralLabel(target)}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
export const cleanCommand = addConfigOptions(new InteractiveCommand("clean").description("Delete stored snapshots or comparisons"));
|
|
121
|
+
cleanCommand.addCommand(createCleanSubcommand("snapshot"));
|
|
122
|
+
cleanCommand.addCommand(createCleanSubcommand("comparison"));
|
|
123
|
+
//# sourceMappingURL=clean.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clean.js","sourceRoot":"","sources":["../../../src/cli/commands/clean.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,45 @@
|
|
|
1
1
|
import { runComparison } from "@cleaver/breakcheck-core";
|
|
2
2
|
import { InteractiveCommand } from "interactive-commander";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { addConfigOptions, hasCliFlag, hasCliOption, resolveCliProjectConfig, } from "../config.js";
|
|
3
5
|
import { configureLogger } from "../utils.js";
|
|
4
|
-
export const compareCommand = new InteractiveCommand("compare")
|
|
6
|
+
export const compareCommand = addConfigOptions(new InteractiveCommand("compare")
|
|
5
7
|
.description("Compare two snapshots and save the results to disk")
|
|
6
8
|
.requiredOption("-b, --before <name>", 'Name of the "before" snapshot')
|
|
7
9
|
.requiredOption("-a, --after <name>", 'Name of the "after" snapshot')
|
|
8
|
-
.option("-o, --output <name>", "Name for the comparison output directory"
|
|
10
|
+
.option("-o, --output <name>", "Name for the comparison output directory")
|
|
9
11
|
.option("-r, --rules <directory>", "Directory containing rules.breakcheck (relative to the current working directory)")
|
|
12
|
+
.option("--no-rules", "Clear configured comparison rules")
|
|
10
13
|
.option("--json-logs", "Output logs in JSON format")
|
|
11
14
|
.option("--no-json-logs", "Output logs in pretty format (default)")
|
|
12
15
|
.action(async (options) => {
|
|
13
16
|
// Configure logger based on options
|
|
14
17
|
const logger = configureLogger(options);
|
|
15
18
|
try {
|
|
16
|
-
const
|
|
19
|
+
const projectConfig = await resolveCliProjectConfig();
|
|
20
|
+
const noRules = options.noRules === true || options.rules === false;
|
|
21
|
+
const hasRules = typeof options.rules === "string" ||
|
|
22
|
+
hasCliOption(process.argv.slice(2), "--rules");
|
|
23
|
+
if (noRules &&
|
|
24
|
+
(hasRules || hasCliFlag(process.argv.slice(2), "--rules"))) {
|
|
25
|
+
throw new Error("--rules and --no-rules cannot be used together.");
|
|
26
|
+
}
|
|
27
|
+
const comparisonName = options.output || "compare_default";
|
|
28
|
+
const ruleset = noRules
|
|
29
|
+
? undefined
|
|
30
|
+
: options.rules === undefined || options.rules === false
|
|
31
|
+
? projectConfig.rulesDir
|
|
32
|
+
: path.resolve(process.cwd(), options.rules);
|
|
17
33
|
const config = {
|
|
18
34
|
beforeSnapshotId: options.before,
|
|
19
35
|
afterSnapshotId: options.after,
|
|
20
36
|
comparisonName, // Pass the name to the config
|
|
21
|
-
ruleset
|
|
37
|
+
ruleset,
|
|
22
38
|
};
|
|
23
39
|
logger.info(`🚀 Starting comparison: ${options.before} vs ${options.after}`);
|
|
24
|
-
const summary = await runComparison(config
|
|
40
|
+
const summary = await runComparison(config, {
|
|
41
|
+
storageDir: projectConfig.storageDir,
|
|
42
|
+
}); // API call
|
|
25
43
|
if (summary.status === "completed") {
|
|
26
44
|
logger.info("✅ Comparison complete!");
|
|
27
45
|
logger.info(` - Results saved to: ${summary.resultsPath}`);
|
|
@@ -30,14 +48,18 @@ export const compareCommand = new InteractiveCommand("compare")
|
|
|
30
48
|
logger.info(` - New URLs: ${summary.newUrls.length}`);
|
|
31
49
|
logger.info(` - Removed URLs: ${summary.removedUrls.length}`);
|
|
32
50
|
logger.info(` - Overall result: ${summary.overallResult.toUpperCase()}`);
|
|
51
|
+
if (summary.overallResult === "fail") {
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
}
|
|
33
54
|
}
|
|
34
55
|
else {
|
|
35
56
|
logger.error({ errors: summary.comparisonProcessErrors }, "❌ Comparison failed");
|
|
57
|
+
process.exitCode = 1;
|
|
36
58
|
}
|
|
37
59
|
}
|
|
38
60
|
catch (error) {
|
|
39
61
|
logger.error({ err: error }, "❌ Error running comparison");
|
|
40
62
|
process.exit(1);
|
|
41
63
|
}
|
|
42
|
-
});
|
|
64
|
+
}));
|
|
43
65
|
//# sourceMappingURL=compare.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/cli/commands/compare.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"compare.js","sourceRoot":"","sources":["../../../src/cli/commands/compare.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type RulesDocument } from "@cleaver/breakcheck-core";
|
|
2
|
+
import { InteractiveCommand } from "interactive-commander";
|
|
3
|
+
export declare function runCompile(rulesDirectory: string, cwd?: string): Promise<RulesDocument>;
|
|
4
|
+
export declare function formatCompiledRules(document: RulesDocument): string;
|
|
5
|
+
export declare const compileCommand: InteractiveCommand;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { compileRulesDsl, logger, } from "@cleaver/breakcheck-core";
|
|
2
|
+
import { InteractiveCommand } from "interactive-commander";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
export async function runCompile(rulesDirectory, cwd = process.cwd()) {
|
|
5
|
+
return compileRulesDsl(resolve(cwd, rulesDirectory));
|
|
6
|
+
}
|
|
7
|
+
export function formatCompiledRules(document) {
|
|
8
|
+
return JSON.stringify(document, null, 2) + "\n";
|
|
9
|
+
}
|
|
10
|
+
export const compileCommand = new InteractiveCommand("compile")
|
|
11
|
+
.description("Compile a rules.breakcheck DSL file to JSON")
|
|
12
|
+
.argument("<rules-directory>", "Directory containing rules.breakcheck (relative to the current directory)")
|
|
13
|
+
.action(async (rulesDirectory) => {
|
|
14
|
+
try {
|
|
15
|
+
const document = await runCompile(rulesDirectory);
|
|
16
|
+
process.stdout.write(formatCompiledRules(document));
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
logger.error({ err: error }, "❌ Error compiling rules");
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
//# sourceMappingURL=compile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compile.js","sourceRoot":"","sources":["../../../src/cli/commands/compile.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,MAAM,GAEP,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,cAAsB,EACtB,GAAG,GAAW,OAAO,CAAC,GAAG,EAAE;IAE3B,OAAO,eAAe,CAAC,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAuB;IACzD,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;AAClD,CAAC;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,kBAAkB,CAAC,SAAS,CAAC;KAC5D,WAAW,CAAC,6CAA6C,CAAC;KAC1D,QAAQ,CACP,mBAAmB,EACnB,2EAA2E,CAC5E;KACA,MAAM,CAAC,KAAK,EAAE,cAAsB,EAAE,EAAE;IACvC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,cAAc,CAAC,CAAC;QAClD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,yBAAyB,CAAC,CAAC;QACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
|