@vertaaux/cli 0.2.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 +345 -0
- package/dist/auth/ci-token.d.ts +49 -0
- package/dist/auth/ci-token.d.ts.map +1 -0
- package/dist/auth/ci-token.js +83 -0
- package/dist/auth/device-flow.d.ts +66 -0
- package/dist/auth/device-flow.d.ts.map +1 -0
- package/dist/auth/device-flow.js +156 -0
- package/dist/auth/token-store.d.ts +53 -0
- package/dist/auth/token-store.d.ts.map +1 -0
- package/dist/auth/token-store.js +78 -0
- package/dist/baseline/diff.d.ts +57 -0
- package/dist/baseline/diff.d.ts.map +1 -0
- package/dist/baseline/diff.js +152 -0
- package/dist/baseline/hash.d.ts +54 -0
- package/dist/baseline/hash.d.ts.map +1 -0
- package/dist/baseline/hash.js +66 -0
- package/dist/baseline/manager.d.ts +89 -0
- package/dist/baseline/manager.d.ts.map +1 -0
- package/dist/baseline/manager.js +157 -0
- package/dist/cache/index.d.ts +8 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +7 -0
- package/dist/cache/route-cache.d.ts +119 -0
- package/dist/cache/route-cache.d.ts.map +1 -0
- package/dist/cache/route-cache.js +213 -0
- package/dist/ci/changed-routes.d.ts +95 -0
- package/dist/ci/changed-routes.d.ts.map +1 -0
- package/dist/ci/changed-routes.js +304 -0
- package/dist/ci/github-api.d.ts +68 -0
- package/dist/ci/github-api.d.ts.map +1 -0
- package/dist/ci/github-api.js +138 -0
- package/dist/ci/gitlab-api.d.ts +75 -0
- package/dist/ci/gitlab-api.d.ts.map +1 -0
- package/dist/ci/gitlab-api.js +180 -0
- package/dist/ci/index.d.ts +6 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +4 -0
- package/dist/commands/audit.d.ts +58 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +862 -0
- package/dist/commands/baseline.d.ts +22 -0
- package/dist/commands/baseline.d.ts.map +1 -0
- package/dist/commands/baseline.js +210 -0
- package/dist/commands/comment.d.ts +14 -0
- package/dist/commands/comment.d.ts.map +1 -0
- package/dist/commands/comment.js +363 -0
- package/dist/commands/diff.d.ts +24 -0
- package/dist/commands/diff.d.ts.map +1 -0
- package/dist/commands/diff.js +196 -0
- package/dist/commands/doctor.d.ts +58 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +338 -0
- package/dist/commands/download.d.ts +12 -0
- package/dist/commands/download.d.ts.map +1 -0
- package/dist/commands/download.js +183 -0
- package/dist/commands/explain.d.ts +62 -0
- package/dist/commands/explain.d.ts.map +1 -0
- package/dist/commands/explain.js +302 -0
- package/dist/commands/init.d.ts +12 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +212 -0
- package/dist/commands/login.d.ts +14 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +222 -0
- package/dist/commands/policy.d.ts +13 -0
- package/dist/commands/policy.d.ts.map +1 -0
- package/dist/commands/policy.js +347 -0
- package/dist/commands/upload.d.ts +12 -0
- package/dist/commands/upload.d.ts.map +1 -0
- package/dist/commands/upload.js +158 -0
- package/dist/config/defaults.d.ts +21 -0
- package/dist/config/defaults.d.ts.map +1 -0
- package/dist/config/defaults.js +49 -0
- package/dist/config/loader.d.ts +66 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +167 -0
- package/dist/config/schema.d.ts +55 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +6 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1090 -0
- package/dist/interactive/fix-wizard.d.ts +44 -0
- package/dist/interactive/fix-wizard.d.ts.map +1 -0
- package/dist/interactive/fix-wizard.js +286 -0
- package/dist/interactive/init-wizard.d.ts +32 -0
- package/dist/interactive/init-wizard.d.ts.map +1 -0
- package/dist/interactive/init-wizard.js +193 -0
- package/dist/interactive/prompts.d.ts +62 -0
- package/dist/interactive/prompts.d.ts.map +1 -0
- package/dist/interactive/prompts.js +78 -0
- package/dist/monorepo/detector.d.ts +70 -0
- package/dist/monorepo/detector.d.ts.map +1 -0
- package/dist/monorepo/detector.js +278 -0
- package/dist/monorepo/index.d.ts +9 -0
- package/dist/monorepo/index.d.ts.map +1 -0
- package/dist/monorepo/index.js +8 -0
- package/dist/monorepo/workspace.d.ts +142 -0
- package/dist/monorepo/workspace.d.ts.map +1 -0
- package/dist/monorepo/workspace.js +171 -0
- package/dist/output/envelope.d.ts +21 -0
- package/dist/output/envelope.d.ts.map +1 -0
- package/dist/output/envelope.js +27 -0
- package/dist/output/factory.d.ts +73 -0
- package/dist/output/factory.d.ts.map +1 -0
- package/dist/output/factory.js +60 -0
- package/dist/output/formats.d.ts +11 -0
- package/dist/output/formats.d.ts.map +1 -0
- package/dist/output/formats.js +41 -0
- package/dist/output/html.d.ts +45 -0
- package/dist/output/html.d.ts.map +1 -0
- package/dist/output/html.js +607 -0
- package/dist/output/human.d.ts +41 -0
- package/dist/output/human.d.ts.map +1 -0
- package/dist/output/human.js +274 -0
- package/dist/output/json.d.ts +42 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/json.js +37 -0
- package/dist/output/junit.d.ts +56 -0
- package/dist/output/junit.d.ts.map +1 -0
- package/dist/output/junit.js +135 -0
- package/dist/output/markdown.d.ts +77 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/markdown.js +411 -0
- package/dist/output/sarif.d.ts +160 -0
- package/dist/output/sarif.d.ts.map +1 -0
- package/dist/output/sarif.js +207 -0
- package/dist/policy/evaluator.d.ts +111 -0
- package/dist/policy/evaluator.d.ts.map +1 -0
- package/dist/policy/evaluator.js +362 -0
- package/dist/policy/index.d.ts +15 -0
- package/dist/policy/index.d.ts.map +1 -0
- package/dist/policy/index.js +11 -0
- package/dist/policy/loader.d.ts +97 -0
- package/dist/policy/loader.d.ts.map +1 -0
- package/dist/policy/loader.js +281 -0
- package/dist/policy/schema.d.ts +297 -0
- package/dist/policy/schema.d.ts.map +1 -0
- package/dist/policy/schema.js +230 -0
- package/dist/quality-gate/evaluator.d.ts +58 -0
- package/dist/quality-gate/evaluator.d.ts.map +1 -0
- package/dist/quality-gate/evaluator.js +274 -0
- package/dist/quality-gate/index.d.ts +10 -0
- package/dist/quality-gate/index.d.ts.map +1 -0
- package/dist/quality-gate/index.js +7 -0
- package/dist/quality-gate/types.d.ts +103 -0
- package/dist/quality-gate/types.d.ts.map +1 -0
- package/dist/quality-gate/types.js +23 -0
- package/dist/templates/azure-devops.d.ts +25 -0
- package/dist/templates/azure-devops.d.ts.map +1 -0
- package/dist/templates/azure-devops.js +109 -0
- package/dist/templates/circleci.d.ts +28 -0
- package/dist/templates/circleci.d.ts.map +1 -0
- package/dist/templates/circleci.js +86 -0
- package/dist/templates/github-actions.d.ts +81 -0
- package/dist/templates/github-actions.d.ts.map +1 -0
- package/dist/templates/github-actions.js +393 -0
- package/dist/templates/gitlab-ci.d.ts +26 -0
- package/dist/templates/gitlab-ci.d.ts.map +1 -0
- package/dist/templates/gitlab-ci.js +70 -0
- package/dist/templates/index.d.ts +72 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +112 -0
- package/dist/templates/jenkins.d.ts +26 -0
- package/dist/templates/jenkins.d.ts.map +1 -0
- package/dist/templates/jenkins.js +110 -0
- package/dist/ui/banner.d.ts +31 -0
- package/dist/ui/banner.d.ts.map +1 -0
- package/dist/ui/banner.js +84 -0
- package/dist/ui/diagnostics.d.ts +39 -0
- package/dist/ui/diagnostics.d.ts.map +1 -0
- package/dist/ui/diagnostics.js +153 -0
- package/dist/ui/spinner.d.ts +61 -0
- package/dist/ui/spinner.d.ts.map +1 -0
- package/dist/ui/spinner.js +101 -0
- package/dist/ui/table.d.ts +63 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +236 -0
- package/dist/utils/client.d.ts +82 -0
- package/dist/utils/client.d.ts.map +1 -0
- package/dist/utils/client.js +128 -0
- package/dist/utils/detect-env.d.ts +59 -0
- package/dist/utils/detect-env.d.ts.map +1 -0
- package/dist/utils/detect-env.js +115 -0
- package/dist/utils/exit-codes.d.ts +47 -0
- package/dist/utils/exit-codes.d.ts.map +1 -0
- package/dist/utils/exit-codes.js +61 -0
- package/dist/utils/logger.d.ts +87 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +185 -0
- package/dist/utils/sanitize.d.ts +36 -0
- package/dist/utils/sanitize.d.ts.map +1 -0
- package/dist/utils/sanitize.js +64 -0
- package/dist/utils/validators.d.ts +41 -0
- package/dist/utils/validators.d.ts.map +1 -0
- package/dist/utils/validators.js +123 -0
- package/package.json +63 -0
- package/schemas/vertaaux.config.schema.json +103 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Manages baseline files for tracking technical debt.
|
|
5
|
+
* - Create baseline from audit results
|
|
6
|
+
* - Add individual issues to baseline (ignore)
|
|
7
|
+
* - List baseline summary
|
|
8
|
+
*/
|
|
9
|
+
import type { Command } from "commander";
|
|
10
|
+
export interface BaselineCommandOptions {
|
|
11
|
+
path?: string;
|
|
12
|
+
fromFile?: string;
|
|
13
|
+
ignore?: string;
|
|
14
|
+
reason?: string;
|
|
15
|
+
list?: boolean;
|
|
16
|
+
base?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Register the baseline command with the Commander program.
|
|
20
|
+
*/
|
|
21
|
+
export declare function registerBaselineCommand(program: Command): void;
|
|
22
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/commands/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoIzC,MAAM,WAAW,sBAAsB;IACrC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA8H9D"}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Manages baseline files for tracking technical debt.
|
|
5
|
+
* - Create baseline from audit results
|
|
6
|
+
* - Add individual issues to baseline (ignore)
|
|
7
|
+
* - List baseline summary
|
|
8
|
+
*/
|
|
9
|
+
import { readFile } from "fs/promises";
|
|
10
|
+
import { existsSync } from "fs";
|
|
11
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
12
|
+
import { loadBaseline, saveBaseline, createBaseline, addToBaseline, DEFAULT_BASELINE_PATH, } from "../baseline/manager.js";
|
|
13
|
+
import { generateFingerprint } from "../baseline/hash.js";
|
|
14
|
+
const DEFAULT_API_BASE = "https://vertaaux.ai/v1";
|
|
15
|
+
/**
|
|
16
|
+
* Get API base URL from environment.
|
|
17
|
+
*/
|
|
18
|
+
function getApiBase() {
|
|
19
|
+
return (process.env.VERTAAUX_API_BASE || DEFAULT_API_BASE).replace(/\/$/, "");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Get API key from environment.
|
|
23
|
+
*/
|
|
24
|
+
function getApiKey() {
|
|
25
|
+
const key = process.env.VERTAAUX_API_KEY;
|
|
26
|
+
if (!key) {
|
|
27
|
+
throw new Error("VERTAAUX_API_KEY is required");
|
|
28
|
+
}
|
|
29
|
+
return key;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Fetch audit results from API.
|
|
33
|
+
*/
|
|
34
|
+
async function fetchAudit(jobId) {
|
|
35
|
+
const base = getApiBase();
|
|
36
|
+
const url = `${base}/audit/${jobId}`;
|
|
37
|
+
const res = await fetch(url, {
|
|
38
|
+
method: "GET",
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"X-API-Key": getApiKey(),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
if (!res.ok) {
|
|
45
|
+
let detail = res.statusText;
|
|
46
|
+
try {
|
|
47
|
+
const data = await res.json();
|
|
48
|
+
detail = data.error || data.message || detail;
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// ignore
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`HTTP ${res.status}: ${detail}`);
|
|
54
|
+
}
|
|
55
|
+
return (await res.json());
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Normalize issues from various API response formats.
|
|
59
|
+
*/
|
|
60
|
+
function normalizeIssues(issues) {
|
|
61
|
+
if (Array.isArray(issues))
|
|
62
|
+
return issues;
|
|
63
|
+
if (issues && typeof issues === "object") {
|
|
64
|
+
const values = Object.values(issues);
|
|
65
|
+
return values.flatMap((value) => Array.isArray(value) ? value : []);
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Find an issue by ID from audit results.
|
|
71
|
+
*/
|
|
72
|
+
function findIssueById(issues, id) {
|
|
73
|
+
// Try exact match first
|
|
74
|
+
const exact = issues.find((issue) => issue.id === id || issue.ruleId === id || issue.rule_id === id);
|
|
75
|
+
if (exact)
|
|
76
|
+
return exact;
|
|
77
|
+
// Try fingerprint match
|
|
78
|
+
for (const issue of issues) {
|
|
79
|
+
const fp = generateFingerprint(issue);
|
|
80
|
+
if (fp === id || fp.startsWith(id)) {
|
|
81
|
+
return issue;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Format baseline summary for display.
|
|
88
|
+
*/
|
|
89
|
+
function formatBaselineSummary(baseline) {
|
|
90
|
+
const lines = [];
|
|
91
|
+
lines.push(`Baseline: ${baseline.url}`);
|
|
92
|
+
lines.push(`Issues: ${baseline.issues.length}`);
|
|
93
|
+
lines.push(`Created: ${baseline.created}`);
|
|
94
|
+
lines.push(`Updated: ${baseline.updated}`);
|
|
95
|
+
// Count by severity
|
|
96
|
+
const bySeverity = {};
|
|
97
|
+
for (const issue of baseline.issues) {
|
|
98
|
+
const sev = issue.severity || "unknown";
|
|
99
|
+
bySeverity[sev] = (bySeverity[sev] || 0) + 1;
|
|
100
|
+
}
|
|
101
|
+
const severityCounts = Object.entries(bySeverity)
|
|
102
|
+
.map(([sev, count]) => `${count} ${sev}`)
|
|
103
|
+
.join(", ");
|
|
104
|
+
if (severityCounts) {
|
|
105
|
+
lines.push(`Breakdown: ${severityCounts}`);
|
|
106
|
+
}
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Register the baseline command with the Commander program.
|
|
111
|
+
*/
|
|
112
|
+
export function registerBaselineCommand(program) {
|
|
113
|
+
program
|
|
114
|
+
.command("baseline [job-id]")
|
|
115
|
+
.description("Create or update baseline from audit results")
|
|
116
|
+
.option("--path <path>", "Baseline file path", DEFAULT_BASELINE_PATH)
|
|
117
|
+
.option("--from-file <file>", "Create baseline from JSON audit file")
|
|
118
|
+
.option("--ignore <id>", "Add single issue to existing baseline by ID")
|
|
119
|
+
.option("--reason <text>", "Reason for baseline/ignore (optional)")
|
|
120
|
+
.option("--list", "Show baselined issues count and last update")
|
|
121
|
+
.action(async (jobIdArg, cmdOptions) => {
|
|
122
|
+
try {
|
|
123
|
+
const baselinePath = cmdOptions.path || DEFAULT_BASELINE_PATH;
|
|
124
|
+
// Handle --list: show baseline summary
|
|
125
|
+
if (cmdOptions.list) {
|
|
126
|
+
const baseline = await loadBaseline(baselinePath);
|
|
127
|
+
if (!baseline) {
|
|
128
|
+
console.error("No baseline found.");
|
|
129
|
+
console.error(`Create one with: vertaa baseline <job-id> --path ${baselinePath}`);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
console.error(formatBaselineSummary(baseline));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Handle --ignore: add single issue to baseline
|
|
136
|
+
if (cmdOptions.ignore) {
|
|
137
|
+
const issueId = cmdOptions.ignore;
|
|
138
|
+
let baseline = await loadBaseline(baselinePath);
|
|
139
|
+
if (!baseline) {
|
|
140
|
+
console.error("No baseline found. Create one first with: vertaa baseline <job-id>");
|
|
141
|
+
process.exit(ExitCode.ERROR);
|
|
142
|
+
}
|
|
143
|
+
// Need to find the issue to add it properly
|
|
144
|
+
// If we have a job ID, fetch the audit; otherwise use a minimal issue
|
|
145
|
+
let issue;
|
|
146
|
+
if (jobIdArg) {
|
|
147
|
+
const audit = await fetchAudit(jobIdArg);
|
|
148
|
+
const issues = normalizeIssues(audit.issues);
|
|
149
|
+
const found = findIssueById(issues, issueId);
|
|
150
|
+
if (!found) {
|
|
151
|
+
console.error(`Issue "${issueId}" not found in job ${jobIdArg}`);
|
|
152
|
+
process.exit(ExitCode.ERROR);
|
|
153
|
+
}
|
|
154
|
+
issue = found;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
// Create minimal issue from ID only
|
|
158
|
+
issue = { id: issueId, description: `Ignored: ${issueId}` };
|
|
159
|
+
}
|
|
160
|
+
baseline = addToBaseline(baseline, issue, cmdOptions.reason);
|
|
161
|
+
await saveBaseline(baseline, baselinePath);
|
|
162
|
+
console.error(`Added issue ${issueId} to baseline`);
|
|
163
|
+
if (cmdOptions.reason) {
|
|
164
|
+
console.error(`Reason: ${cmdOptions.reason}`);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Handle --from-file: create baseline from JSON file
|
|
169
|
+
if (cmdOptions.fromFile) {
|
|
170
|
+
const filePath = cmdOptions.fromFile;
|
|
171
|
+
if (!existsSync(filePath)) {
|
|
172
|
+
console.error(`File not found: ${filePath}`);
|
|
173
|
+
process.exit(ExitCode.ERROR);
|
|
174
|
+
}
|
|
175
|
+
const content = await readFile(filePath, "utf-8");
|
|
176
|
+
const data = JSON.parse(content);
|
|
177
|
+
const issues = normalizeIssues(data.issues);
|
|
178
|
+
const url = data.url || "unknown";
|
|
179
|
+
const baseline = createBaseline(issues, url);
|
|
180
|
+
await saveBaseline(baseline, baselinePath);
|
|
181
|
+
console.error(`Baseline created with ${issues.length} issues`);
|
|
182
|
+
console.error(`Saved to: ${baselinePath}`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Handle job-id: create baseline from API audit
|
|
186
|
+
if (!jobIdArg) {
|
|
187
|
+
console.error("Provide job ID or use --from-file or --list");
|
|
188
|
+
console.error("Usage: vertaa baseline <job-id>");
|
|
189
|
+
console.error(" vertaa baseline --from-file <audit.json>");
|
|
190
|
+
console.error(" vertaa baseline --list");
|
|
191
|
+
process.exit(ExitCode.ERROR);
|
|
192
|
+
}
|
|
193
|
+
const audit = await fetchAudit(jobIdArg);
|
|
194
|
+
if (audit.status !== "completed") {
|
|
195
|
+
console.error(`Audit ${jobIdArg} is not completed (status: ${audit.status})`);
|
|
196
|
+
process.exit(ExitCode.ERROR);
|
|
197
|
+
}
|
|
198
|
+
const issues = normalizeIssues(audit.issues);
|
|
199
|
+
const url = audit.url || "unknown";
|
|
200
|
+
const baseline = createBaseline(issues, url);
|
|
201
|
+
await saveBaseline(baseline, baselinePath);
|
|
202
|
+
console.error(`Baseline created with ${issues.length} issues`);
|
|
203
|
+
console.error(`Saved to: ${baselinePath}`);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
207
|
+
process.exit(ExitCode.ERROR);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Generates markdown for PR comments from audit results.
|
|
5
|
+
* Supports posting to GitHub and GitLab via their APIs.
|
|
6
|
+
* Uses sticky comment semantics to update existing comments.
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from "commander";
|
|
9
|
+
/**
|
|
10
|
+
* Register the comment command with the Commander program.
|
|
11
|
+
*/
|
|
12
|
+
export declare function registerCommentCommand(program: Command): void;
|
|
13
|
+
export { formatMarkdownComment as generateComment } from "../output/markdown.js";
|
|
14
|
+
//# sourceMappingURL=comment.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"comment.d.ts","sourceRoot":"","sources":["../../src/commands/comment.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgZpC;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA6F7D;AAGD,OAAO,EAAE,qBAAqB,IAAI,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Comment command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Generates markdown for PR comments from audit results.
|
|
5
|
+
* Supports posting to GitHub and GitLab via their APIs.
|
|
6
|
+
* Uses sticky comment semantics to update existing comments.
|
|
7
|
+
*/
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { loadBaseline } from "../baseline/manager.js";
|
|
11
|
+
import { ExitCode } from "../utils/exit-codes.js";
|
|
12
|
+
import { formatMarkdownComment, categorizeIssuesForComment, } from "../output/markdown.js";
|
|
13
|
+
import { createEnvelope, writeJsonOutput, writeOutput } from "../output/envelope.js";
|
|
14
|
+
import { resolveCommandFormat } from "../output/formats.js";
|
|
15
|
+
import { postOrUpdateGitHubComment, parseRepository, extractPRNumber, } from "../ci/github-api.js";
|
|
16
|
+
import { postOrUpdateGitLabNote, getGitLabConfig, extractMRIid, } from "../ci/gitlab-api.js";
|
|
17
|
+
/**
|
|
18
|
+
* Default path for latest audit results.
|
|
19
|
+
*/
|
|
20
|
+
const LATEST_AUDIT_PATH = ".vertaaux/latest-audit.json";
|
|
21
|
+
/**
|
|
22
|
+
* Hidden header identifier for sticky comments.
|
|
23
|
+
*/
|
|
24
|
+
const COMMENT_HEADER = "vertaaux-audit";
|
|
25
|
+
/**
|
|
26
|
+
* Normalize issues from various API response formats.
|
|
27
|
+
*/
|
|
28
|
+
function normalizeIssues(issues) {
|
|
29
|
+
if (Array.isArray(issues))
|
|
30
|
+
return issues;
|
|
31
|
+
if (issues && typeof issues === "object") {
|
|
32
|
+
const values = Object.values(issues);
|
|
33
|
+
return values.flatMap((value) => Array.isArray(value) ? value : []);
|
|
34
|
+
}
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Detect CI context from environment variables.
|
|
39
|
+
*
|
|
40
|
+
* Checks for GitHub Actions and GitLab CI environments.
|
|
41
|
+
* Returns null if not in a recognized CI environment.
|
|
42
|
+
*/
|
|
43
|
+
function detectCIContext() {
|
|
44
|
+
// GitHub Actions
|
|
45
|
+
if (process.env.GITHUB_ACTIONS) {
|
|
46
|
+
const token = process.env.GITHUB_TOKEN;
|
|
47
|
+
if (!token)
|
|
48
|
+
return null;
|
|
49
|
+
const repo = process.env.GITHUB_REPOSITORY; // 'owner/repo'
|
|
50
|
+
const parsed = repo ? parseRepository(repo) : null;
|
|
51
|
+
const prNumber = extractPRNumber();
|
|
52
|
+
if (!parsed || !prNumber)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
platform: "github",
|
|
56
|
+
token,
|
|
57
|
+
repo: parsed.repo,
|
|
58
|
+
owner: parsed.owner,
|
|
59
|
+
prNumber,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
// GitLab CI
|
|
63
|
+
if (process.env.GITLAB_CI) {
|
|
64
|
+
const config = getGitLabConfig();
|
|
65
|
+
if (!config)
|
|
66
|
+
return null;
|
|
67
|
+
const mrIid = extractMRIid();
|
|
68
|
+
if (!mrIid)
|
|
69
|
+
return null;
|
|
70
|
+
return {
|
|
71
|
+
platform: "gitlab",
|
|
72
|
+
token: config.token,
|
|
73
|
+
apiUrl: config.apiUrl,
|
|
74
|
+
projectId: config.projectId,
|
|
75
|
+
mrIid,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Post comment to GitHub PR.
|
|
82
|
+
*/
|
|
83
|
+
async function postToGitHub(context, markdown) {
|
|
84
|
+
if (!context.owner || !context.repo || !context.prNumber) {
|
|
85
|
+
throw new Error("Missing GitHub context (owner, repo, or PR number)");
|
|
86
|
+
}
|
|
87
|
+
const result = await postOrUpdateGitHubComment({
|
|
88
|
+
token: context.token,
|
|
89
|
+
owner: context.owner,
|
|
90
|
+
repo: context.repo,
|
|
91
|
+
prNumber: context.prNumber,
|
|
92
|
+
body: markdown,
|
|
93
|
+
header: COMMENT_HEADER,
|
|
94
|
+
});
|
|
95
|
+
return { url: result.html_url };
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Post note to GitLab MR.
|
|
99
|
+
*/
|
|
100
|
+
async function postToGitLab(context, markdown) {
|
|
101
|
+
if (!context.apiUrl || !context.projectId || !context.mrIid) {
|
|
102
|
+
throw new Error("Missing GitLab context (apiUrl, projectId, or mrIid)");
|
|
103
|
+
}
|
|
104
|
+
const result = await postOrUpdateGitLabNote({
|
|
105
|
+
token: context.token,
|
|
106
|
+
apiUrl: context.apiUrl,
|
|
107
|
+
projectId: context.projectId,
|
|
108
|
+
mrIid: context.mrIid,
|
|
109
|
+
body: markdown,
|
|
110
|
+
header: COMMENT_HEADER,
|
|
111
|
+
});
|
|
112
|
+
return { url: result.web_url };
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Post comment to the appropriate CI platform.
|
|
116
|
+
*/
|
|
117
|
+
async function postComment(context, markdown) {
|
|
118
|
+
if (context.platform === "github") {
|
|
119
|
+
return postToGitHub(context, markdown);
|
|
120
|
+
}
|
|
121
|
+
else if (context.platform === "gitlab") {
|
|
122
|
+
return postToGitLab(context, markdown);
|
|
123
|
+
}
|
|
124
|
+
throw new Error(`Unsupported CI platform: ${context.platform}`);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Handle the comment command.
|
|
128
|
+
*/
|
|
129
|
+
async function handleComment(options) {
|
|
130
|
+
let auditResult;
|
|
131
|
+
// Load audit results
|
|
132
|
+
if (options.input) {
|
|
133
|
+
// From file
|
|
134
|
+
const inputPath = path.resolve(process.cwd(), options.input);
|
|
135
|
+
if (!fs.existsSync(inputPath)) {
|
|
136
|
+
console.error(`Error: Input file not found: ${options.input}`);
|
|
137
|
+
process.exit(ExitCode.ERROR);
|
|
138
|
+
}
|
|
139
|
+
const content = fs.readFileSync(inputPath, "utf-8");
|
|
140
|
+
auditResult = JSON.parse(content);
|
|
141
|
+
}
|
|
142
|
+
else if (!process.stdin.isTTY) {
|
|
143
|
+
// From stdin
|
|
144
|
+
const chunks = [];
|
|
145
|
+
for await (const chunk of process.stdin) {
|
|
146
|
+
chunks.push(chunk);
|
|
147
|
+
}
|
|
148
|
+
const content = Buffer.concat(chunks).toString("utf-8");
|
|
149
|
+
auditResult = JSON.parse(content);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
// Try latest audit file
|
|
153
|
+
const latestPath = path.resolve(process.cwd(), LATEST_AUDIT_PATH);
|
|
154
|
+
if (fs.existsSync(latestPath)) {
|
|
155
|
+
const content = fs.readFileSync(latestPath, "utf-8");
|
|
156
|
+
auditResult = JSON.parse(content);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
console.error("Error: No audit results found. Provide --input or pipe results.");
|
|
160
|
+
process.exit(ExitCode.ERROR);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Load baseline if specified
|
|
164
|
+
let baseline = null;
|
|
165
|
+
if (options.baseline) {
|
|
166
|
+
baseline = await loadBaseline(options.baseline);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
// Try default baseline
|
|
170
|
+
baseline = await loadBaseline();
|
|
171
|
+
}
|
|
172
|
+
// Categorize issues
|
|
173
|
+
const issues = normalizeIssues(auditResult.issues);
|
|
174
|
+
const { newIssues, fixedIssues, existingIssues } = categorizeIssuesForComment(issues, baseline);
|
|
175
|
+
// Build comment data
|
|
176
|
+
const commentData = {
|
|
177
|
+
auditId: auditResult.job_id,
|
|
178
|
+
url: auditResult.url,
|
|
179
|
+
newIssues,
|
|
180
|
+
fixedIssues,
|
|
181
|
+
existingIssues,
|
|
182
|
+
scores: auditResult.scores,
|
|
183
|
+
};
|
|
184
|
+
// Generate output
|
|
185
|
+
const isJson = options.format === "json";
|
|
186
|
+
const jsonData = {
|
|
187
|
+
auditId: auditResult.job_id,
|
|
188
|
+
url: auditResult.url,
|
|
189
|
+
newIssues: newIssues.length,
|
|
190
|
+
existingIssues: existingIssues.length,
|
|
191
|
+
fixedIssues: fixedIssues.length,
|
|
192
|
+
issues: {
|
|
193
|
+
new: newIssues,
|
|
194
|
+
existing: existingIssues,
|
|
195
|
+
fixed: fixedIssues,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
let output;
|
|
199
|
+
if (isJson) {
|
|
200
|
+
// JSON format - will be wrapped in envelope at output time
|
|
201
|
+
output = JSON.stringify(jsonData, null, 2);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// Markdown format (default)
|
|
205
|
+
output = formatMarkdownComment(commentData, {
|
|
206
|
+
groupBy: options.groupBy || "severity",
|
|
207
|
+
collapse: options.collapse ?? newIssues.length > 3,
|
|
208
|
+
collapseThreshold: 3,
|
|
209
|
+
includeEvidence: true,
|
|
210
|
+
includeFixes: true,
|
|
211
|
+
baseUrl: "https://vertaaux.ai",
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// Handle posting
|
|
215
|
+
if (options.post) {
|
|
216
|
+
// Check if we should skip posting
|
|
217
|
+
if (options.noPostIfClean && newIssues.length === 0) {
|
|
218
|
+
console.error("No new issues found. Skipping comment post (--no-post-if-clean).");
|
|
219
|
+
// Still write output if requested
|
|
220
|
+
if (options.output) {
|
|
221
|
+
const outputPath = path.resolve(process.cwd(), options.output);
|
|
222
|
+
fs.writeFileSync(outputPath, output, "utf-8");
|
|
223
|
+
console.error(`Comment written to: ${outputPath}`);
|
|
224
|
+
}
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Detect or build CI context
|
|
228
|
+
let context = null;
|
|
229
|
+
// Try explicit options first
|
|
230
|
+
if (options.githubToken || process.env.GITHUB_TOKEN) {
|
|
231
|
+
const token = options.githubToken || process.env.GITHUB_TOKEN;
|
|
232
|
+
const repo = options.repo || process.env.GITHUB_REPOSITORY;
|
|
233
|
+
const prNumber = options.pr || extractPRNumber();
|
|
234
|
+
if (repo && prNumber) {
|
|
235
|
+
const parsed = parseRepository(repo);
|
|
236
|
+
if (parsed) {
|
|
237
|
+
context = {
|
|
238
|
+
platform: "github",
|
|
239
|
+
token,
|
|
240
|
+
owner: parsed.owner,
|
|
241
|
+
repo: parsed.repo,
|
|
242
|
+
prNumber,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
else if (options.gitlabToken || process.env.GITLAB_TOKEN || process.env.CI_JOB_TOKEN) {
|
|
248
|
+
const config = getGitLabConfig();
|
|
249
|
+
if (config) {
|
|
250
|
+
const mrIid = options.pr || extractMRIid();
|
|
251
|
+
if (mrIid) {
|
|
252
|
+
context = {
|
|
253
|
+
platform: "gitlab",
|
|
254
|
+
token: config.token,
|
|
255
|
+
apiUrl: config.apiUrl,
|
|
256
|
+
projectId: config.projectId,
|
|
257
|
+
mrIid,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Fall back to auto-detection
|
|
263
|
+
if (!context) {
|
|
264
|
+
context = detectCIContext();
|
|
265
|
+
}
|
|
266
|
+
if (!context) {
|
|
267
|
+
console.error("Error: Cannot post comment - not in a recognized CI environment.");
|
|
268
|
+
console.error("Provide --github-token with --repo and --pr, or run in GitHub Actions/GitLab CI.");
|
|
269
|
+
// Output for debugging via stdout
|
|
270
|
+
if (isJson) {
|
|
271
|
+
writeJsonOutput(jsonData, "comment");
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
writeOutput(output);
|
|
275
|
+
}
|
|
276
|
+
process.exit(ExitCode.ERROR);
|
|
277
|
+
}
|
|
278
|
+
try {
|
|
279
|
+
const result = await postComment(context, output);
|
|
280
|
+
console.error(`Comment posted: ${result.url}`);
|
|
281
|
+
}
|
|
282
|
+
catch (error) {
|
|
283
|
+
console.error(`Error posting comment: ${error instanceof Error ? error.message : String(error)}`);
|
|
284
|
+
// Output for debugging even on failure
|
|
285
|
+
console.error("\nGenerated output (for debugging):");
|
|
286
|
+
if (isJson) {
|
|
287
|
+
writeJsonOutput(jsonData, "comment");
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
writeOutput(output);
|
|
291
|
+
}
|
|
292
|
+
process.exit(ExitCode.ERROR);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
// Write output to file or stdout
|
|
296
|
+
if (options.output) {
|
|
297
|
+
const outputPath = path.resolve(process.cwd(), options.output);
|
|
298
|
+
const fileContent = isJson
|
|
299
|
+
? JSON.stringify(createEnvelope(jsonData, "comment"), null, 2)
|
|
300
|
+
: output;
|
|
301
|
+
fs.writeFileSync(outputPath, fileContent, "utf-8");
|
|
302
|
+
console.error(`Comment written to: ${outputPath}`);
|
|
303
|
+
}
|
|
304
|
+
else if (!options.post) {
|
|
305
|
+
// Only print to stdout if not posting (posting already logs URL)
|
|
306
|
+
if (isJson) {
|
|
307
|
+
writeJsonOutput(jsonData, "comment");
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
writeOutput(output);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Register the comment command with the Commander program.
|
|
316
|
+
*/
|
|
317
|
+
export function registerCommentCommand(program) {
|
|
318
|
+
program
|
|
319
|
+
.command("comment")
|
|
320
|
+
.description("Generate or post PR comment markdown from audit results")
|
|
321
|
+
.option("--input <file>", "Path to audit results JSON")
|
|
322
|
+
.option("--baseline <file>", "Path to baseline JSON for diff comparison")
|
|
323
|
+
.option("--format <type>", "Output format: markdown|json (default: markdown)")
|
|
324
|
+
.option("-o, --output <file>", "Write to file instead of stdout")
|
|
325
|
+
.option("--collapse", "Use collapsible sections (default: true for >3 issues)")
|
|
326
|
+
.option("--no-collapse", "Disable collapsible sections")
|
|
327
|
+
.option("--group-by <field>", "Group issues by: severity|category|route|file|component (default: severity)")
|
|
328
|
+
// Posting options
|
|
329
|
+
.option("--post", "Post/update comment to PR (requires CI context)")
|
|
330
|
+
.option("--github-token <token>", "GitHub token (default: $GITHUB_TOKEN)")
|
|
331
|
+
.option("--gitlab-token <token>", "GitLab token (default: $GITLAB_TOKEN or $CI_JOB_TOKEN)")
|
|
332
|
+
.option("--pr <number>", "PR/MR number (auto-detect from CI env vars)", (val) => parseInt(val, 10))
|
|
333
|
+
.option("--repo <owner/repo>", "Repository (auto-detect from CI env vars)")
|
|
334
|
+
.option("--no-post-if-clean", "Don't post comment if no new issues")
|
|
335
|
+
.action(async (cmdOptions, cmd) => {
|
|
336
|
+
try {
|
|
337
|
+
// Validate format using per-command registry
|
|
338
|
+
const machineMode = cmd.optsWithGlobals?.().machine || false;
|
|
339
|
+
const validatedFormat = resolveCommandFormat("comment", cmdOptions.format, machineMode);
|
|
340
|
+
await handleComment({
|
|
341
|
+
input: cmdOptions.input,
|
|
342
|
+
baseline: cmdOptions.baseline,
|
|
343
|
+
format: validatedFormat,
|
|
344
|
+
output: cmdOptions.output,
|
|
345
|
+
collapse: cmdOptions.collapse,
|
|
346
|
+
groupBy: cmdOptions.groupBy,
|
|
347
|
+
post: cmdOptions.post,
|
|
348
|
+
githubToken: cmdOptions.githubToken,
|
|
349
|
+
gitlabToken: cmdOptions.gitlabToken,
|
|
350
|
+
pr: cmdOptions.pr,
|
|
351
|
+
repo: cmdOptions.repo,
|
|
352
|
+
// Commander --no-X flags become X = false
|
|
353
|
+
noPostIfClean: cmdOptions.postIfClean === false,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
console.error("Error:", error instanceof Error ? error.message : String(error));
|
|
358
|
+
process.exit(ExitCode.ERROR);
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// Re-export for backward compatibility
|
|
363
|
+
export { formatMarkdownComment as generateComment } from "../output/markdown.js";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diff command for VertaaUX CLI.
|
|
3
|
+
*
|
|
4
|
+
* Compares current audit results against baseline or previous audit.
|
|
5
|
+
* Shows three categories: new, fixed, still present.
|
|
6
|
+
*
|
|
7
|
+
* Exit codes:
|
|
8
|
+
* - 0: No new issues
|
|
9
|
+
* - 1: New issues found
|
|
10
|
+
* - 2: Error
|
|
11
|
+
*/
|
|
12
|
+
import type { Command } from "commander";
|
|
13
|
+
export interface DiffCommandOptions {
|
|
14
|
+
baseline?: string;
|
|
15
|
+
compare?: string;
|
|
16
|
+
format?: string;
|
|
17
|
+
verbose?: boolean;
|
|
18
|
+
fromFile?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Register the diff command with the Commander program.
|
|
22
|
+
*/
|
|
23
|
+
export declare function registerDiffCommand(program: Command): void;
|
|
24
|
+
//# sourceMappingURL=diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiFzC,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2I1D"}
|