@vibgrate/cli 2026.4.10 → 2026.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/baseline-FPO7HS7Y.js +9 -0
- package/dist/chunk-42GW43JE.js +97 -0
- package/dist/chunk-74ZJFYEM.js +1936 -0
- package/dist/chunk-JSBRDJBE.js +30 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +1718 -391
- package/dist/index.d.ts +1 -617
- package/dist/index.js +8 -6
- package/dist/semver-JBJZTHUX.js +5 -0
- package/package.json +8 -7
- package/dist/baseline-QZZXBT74.js +0 -10
- package/dist/chunk-2VJCLUTR.js +0 -31
- package/dist/chunk-JQHUH6A3.js +0 -684
- package/dist/chunk-XCIPC2J7.js +0 -12756
- package/dist/fs-D24ONFXR.js +0 -32
package/dist/cli.js
CHANGED
|
@@ -1,34 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
baselineCommand
|
|
4
|
-
} from "./chunk-2VJCLUTR.js";
|
|
5
|
-
import {
|
|
6
|
-
VERSION,
|
|
7
|
-
computeHmac,
|
|
8
|
-
dsnCommand,
|
|
9
|
-
formatMarkdown,
|
|
10
|
-
formatText,
|
|
11
|
-
parseDsn,
|
|
12
|
-
pushCommand,
|
|
13
|
-
scanCommand,
|
|
14
|
-
writeDefaultConfig
|
|
15
|
-
} from "./chunk-XCIPC2J7.js";
|
|
16
2
|
import {
|
|
17
3
|
Semaphore,
|
|
4
|
+
baselineCommand,
|
|
18
5
|
ensureDir,
|
|
19
6
|
pathExists,
|
|
20
7
|
readJsonFile,
|
|
21
8
|
writeTextFile
|
|
22
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-42GW43JE.js";
|
|
10
|
+
import {
|
|
11
|
+
require_semver
|
|
12
|
+
} from "./chunk-74ZJFYEM.js";
|
|
13
|
+
import {
|
|
14
|
+
__toESM
|
|
15
|
+
} from "./chunk-JSBRDJBE.js";
|
|
23
16
|
|
|
24
17
|
// src/cli.ts
|
|
25
|
-
import { Command as
|
|
26
|
-
import
|
|
18
|
+
import { Command as Command11 } from "commander";
|
|
19
|
+
import chalk12 from "chalk";
|
|
27
20
|
|
|
28
21
|
// src/commands/init.ts
|
|
29
22
|
import * as path from "path";
|
|
30
23
|
import { Command } from "commander";
|
|
31
24
|
import chalk from "chalk";
|
|
25
|
+
import { writeDefaultConfig } from "@vibgrate/core";
|
|
32
26
|
var initCommand = new Command("init").description("Initialize vibgrate in a project").argument("[path]", "Path to initialize", ".").option("--baseline", "Create initial baseline after init").option("--yes", "Skip confirmation prompts").action(async (targetPath, opts) => {
|
|
33
27
|
const rootDir = path.resolve(targetPath);
|
|
34
28
|
const vibgrateDir = path.join(rootDir, ".vibgrate");
|
|
@@ -42,7 +36,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
42
36
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
43
37
|
}
|
|
44
38
|
if (opts.baseline) {
|
|
45
|
-
const { runBaseline } = await import("./baseline-
|
|
39
|
+
const { runBaseline } = await import("./baseline-FPO7HS7Y.js");
|
|
46
40
|
await runBaseline(rootDir);
|
|
47
41
|
}
|
|
48
42
|
console.log("");
|
|
@@ -52,15 +46,1044 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
52
46
|
console.log("");
|
|
53
47
|
});
|
|
54
48
|
|
|
55
|
-
// src/commands/
|
|
49
|
+
// src/commands/scan.ts
|
|
50
|
+
import * as path3 from "path";
|
|
51
|
+
import { Command as Command3 } from "commander";
|
|
52
|
+
import chalk3 from "chalk";
|
|
53
|
+
import {
|
|
54
|
+
runScan,
|
|
55
|
+
pathExists as pathExists2,
|
|
56
|
+
parseDsn,
|
|
57
|
+
prepareCompressedUpload,
|
|
58
|
+
fetchScanPreflight,
|
|
59
|
+
computeRepoFingerprint,
|
|
60
|
+
detectVcs,
|
|
61
|
+
resolveRepositoryName
|
|
62
|
+
} from "@vibgrate/core";
|
|
63
|
+
|
|
64
|
+
// src/version.ts
|
|
65
|
+
import { createRequire } from "module";
|
|
66
|
+
var require2 = createRequire(import.meta.url);
|
|
67
|
+
var pkg = require2("../package.json");
|
|
68
|
+
var VERSION = pkg.version;
|
|
69
|
+
|
|
70
|
+
// src/commands/dsn.ts
|
|
71
|
+
import * as crypto from "crypto";
|
|
56
72
|
import * as path2 from "path";
|
|
57
73
|
import { Command as Command2 } from "commander";
|
|
58
74
|
import chalk2 from "chalk";
|
|
59
|
-
var
|
|
60
|
-
|
|
75
|
+
var REGION_HOSTS = {
|
|
76
|
+
us: "us.ingest.vibgrate.com",
|
|
77
|
+
eu: "eu.ingest.vibgrate.com"
|
|
78
|
+
};
|
|
79
|
+
function resolveIngestHost(region, ingest) {
|
|
80
|
+
if (ingest) {
|
|
81
|
+
try {
|
|
82
|
+
return new URL(ingest).host;
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Error(`Invalid ingest URL: ${ingest}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const r = (region ?? "us").toLowerCase();
|
|
88
|
+
const host = REGION_HOSTS[r];
|
|
89
|
+
if (!host) {
|
|
90
|
+
throw new Error(`Unknown region "${r}". Supported: ${Object.keys(REGION_HOSTS).join(", ")}`);
|
|
91
|
+
}
|
|
92
|
+
return host;
|
|
93
|
+
}
|
|
94
|
+
async function provisionDsn(keyId, secret, workspaceId, ingestHost) {
|
|
95
|
+
const url = `https://${ingestHost}/v1/provision`;
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(url, {
|
|
98
|
+
method: "POST",
|
|
99
|
+
headers: {
|
|
100
|
+
"Content-Type": "application/json",
|
|
101
|
+
"Connection": "close"
|
|
102
|
+
// Prevent keep-alive delays on exit
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ keyId, secret, workspaceId })
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const result = await response.json();
|
|
108
|
+
return { success: false, error: result.error || `HTTP ${response.status}` };
|
|
109
|
+
}
|
|
110
|
+
return { success: true };
|
|
111
|
+
} catch (e) {
|
|
112
|
+
return { success: false, error: e instanceof Error ? e.message : String(e) };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
var dsnCommand = new Command2("dsn").description("Manage DSN tokens");
|
|
116
|
+
dsnCommand.command("create").description("Create a new DSN token").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", "Data residency region (us, eu)", "us").requiredOption("--workspace <id>", 'Workspace ID (use "new" to auto-generate)').option("--write <path>", "Write DSN to file").action(async (opts) => {
|
|
117
|
+
const keyId = crypto.randomBytes(12).toString("hex");
|
|
118
|
+
const secret = crypto.randomBytes(32).toString("hex");
|
|
119
|
+
let ingestHost;
|
|
120
|
+
try {
|
|
121
|
+
ingestHost = resolveIngestHost(opts.region, opts.ingest);
|
|
122
|
+
} catch (e) {
|
|
123
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
let workspaceId = opts.workspace;
|
|
127
|
+
const isNewWorkspace = opts.workspace.toLowerCase() === "new";
|
|
128
|
+
if (isNewWorkspace) {
|
|
129
|
+
workspaceId = crypto.randomBytes(8).toString("hex");
|
|
130
|
+
console.log(chalk2.dim(`Provisioning new workspace ${workspaceId}...`));
|
|
131
|
+
}
|
|
132
|
+
if (isNewWorkspace) {
|
|
133
|
+
const result = await provisionDsn(keyId, secret, workspaceId, ingestHost);
|
|
134
|
+
if (!result.success) {
|
|
135
|
+
console.error(chalk2.red(`Failed to provision DSN: ${result.error}`));
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const dsn = `vibgrate+https://${keyId}:${secret}@${ingestHost}/${workspaceId}`;
|
|
140
|
+
console.log(chalk2.green("\u2714") + " DSN created");
|
|
141
|
+
console.log("");
|
|
142
|
+
console.log(chalk2.bold("DSN:"));
|
|
143
|
+
console.log(` ${dsn}`);
|
|
144
|
+
console.log("");
|
|
145
|
+
console.log(chalk2.bold("Key ID:"));
|
|
146
|
+
console.log(` ${keyId}`);
|
|
147
|
+
if (isNewWorkspace) {
|
|
148
|
+
console.log("");
|
|
149
|
+
console.log(chalk2.bold("Workspace ID:"));
|
|
150
|
+
console.log(` ${workspaceId}`);
|
|
151
|
+
}
|
|
152
|
+
console.log("");
|
|
153
|
+
console.log(chalk2.dim("Set this as VIBGRATE_DSN in your CI environment."));
|
|
154
|
+
if (!isNewWorkspace) {
|
|
155
|
+
console.log(chalk2.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
156
|
+
}
|
|
157
|
+
if (opts.write) {
|
|
158
|
+
const writePath = path2.resolve(opts.write);
|
|
159
|
+
await writeTextFile(writePath, dsn + "\n");
|
|
160
|
+
console.log("");
|
|
161
|
+
console.log(chalk2.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
162
|
+
console.log(chalk2.yellow("\u26A0") + " Add this file to .gitignore!");
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/utils/ingest-id-output.ts
|
|
167
|
+
function emitIngestIdLine(ingestId, options) {
|
|
168
|
+
console.log(`VIBGRATE_INGEST_ID=${ingestId}`);
|
|
169
|
+
if (options?.unchanged) {
|
|
170
|
+
console.log("VIBGRATE_INGEST_UNCHANGED=1");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/commands/scan.ts
|
|
175
|
+
async function autoPush(artifact, rootDir, opts) {
|
|
176
|
+
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
177
|
+
if (!dsn) {
|
|
178
|
+
console.error(chalk3.red("No DSN provided for push."));
|
|
179
|
+
console.error(chalk3.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
|
|
180
|
+
if (opts.strict) process.exit(1);
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const parsed = parseDsn(dsn);
|
|
184
|
+
if (!parsed) {
|
|
185
|
+
console.error(chalk3.red("Invalid DSN format."));
|
|
186
|
+
if (opts.strict) process.exit(1);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const { body, contentEncoding } = await prepareCompressedUpload(artifact);
|
|
190
|
+
const timestamp = String(Date.now());
|
|
191
|
+
let host = parsed.host;
|
|
192
|
+
if (opts.region) {
|
|
193
|
+
try {
|
|
194
|
+
host = resolveIngestHost(opts.region);
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
|
|
197
|
+
if (opts.strict) process.exit(1);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
|
|
202
|
+
const originalSize = JSON.stringify(artifact).length;
|
|
203
|
+
const compressedSize = body.length;
|
|
204
|
+
const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
|
|
205
|
+
console.log(chalk3.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
|
|
206
|
+
try {
|
|
207
|
+
const response = await fetch(url, {
|
|
208
|
+
method: "POST",
|
|
209
|
+
headers: {
|
|
210
|
+
"Content-Type": "application/json",
|
|
211
|
+
"Content-Encoding": contentEncoding,
|
|
212
|
+
"X-Vibgrate-Timestamp": timestamp,
|
|
213
|
+
"Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
|
|
214
|
+
"Connection": "close"
|
|
215
|
+
// Prevent keep-alive delays on exit
|
|
216
|
+
},
|
|
217
|
+
body
|
|
218
|
+
});
|
|
219
|
+
if (!response.ok) {
|
|
220
|
+
const text = await response.text();
|
|
221
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
222
|
+
}
|
|
223
|
+
const result = await response.json();
|
|
224
|
+
if (result.unchanged) {
|
|
225
|
+
console.log(
|
|
226
|
+
chalk3.green("\u2714") + ` Repository unchanged since ${result.lastScannedAt ?? "last scan"} \u2014 skipped upload (no credit used).`
|
|
227
|
+
);
|
|
228
|
+
if (result.previousIngestId) {
|
|
229
|
+
emitIngestIdLine(result.previousIngestId, { unchanged: true });
|
|
230
|
+
const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.previousIngestId}`;
|
|
231
|
+
console.log(chalk3.dim(` Previous report: ${dashUrl}`));
|
|
232
|
+
} else if (opts.strict) {
|
|
233
|
+
console.error(chalk3.red("Repository unchanged but no previous ingest id returned."));
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
console.log(chalk3.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
|
|
239
|
+
if (result.ingestId) {
|
|
240
|
+
emitIngestIdLine(result.ingestId);
|
|
241
|
+
const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.ingestId}`;
|
|
242
|
+
const CLEAR_LINE = process.platform === "win32" ? "\x1B[0G\x1B[2K" : "";
|
|
243
|
+
console.log("");
|
|
244
|
+
console.log(CLEAR_LINE + chalk3.dim(" Processing continues in the background. Results available shortly."));
|
|
245
|
+
console.log("");
|
|
246
|
+
console.log(CLEAR_LINE + chalk3.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
247
|
+
console.log(CLEAR_LINE + chalk3.bold(" \u{1F4CA} View Scan Report"));
|
|
248
|
+
console.log(CLEAR_LINE + " " + chalk3.underline.cyan(dashUrl));
|
|
249
|
+
console.log(CLEAR_LINE + chalk3.cyan("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
250
|
+
console.log("");
|
|
251
|
+
console.log("");
|
|
252
|
+
}
|
|
253
|
+
} catch (e) {
|
|
254
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
255
|
+
console.error(chalk3.red(`Upload failed: ${msg}`));
|
|
256
|
+
if (opts.strict) process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function parseNonNegativeNumber(value, label) {
|
|
260
|
+
if (value === void 0) return void 0;
|
|
261
|
+
const parsed = Number(value);
|
|
262
|
+
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
263
|
+
throw new Error(`${label} must be a non-negative number.`);
|
|
264
|
+
}
|
|
265
|
+
return parsed;
|
|
266
|
+
}
|
|
267
|
+
var scanCommand = new Command3("scan").description("Scan a project for upgrade drift").argument("[path]", "Path to scan", ".").option("--out <file>", "Output file path").option("--format <format>", "Output format (text|json|sarif|md)", "text").option("--fail-on <level>", "Fail on warn or error").option("--baseline <file>", "Compare against baseline").option("--changed-only", "Only scan changed files").option("--concurrency <n>", "Max concurrent npm calls", "8").option("--push", "Auto-push results to Vibgrate API after scan").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region for push (us, eu)").option("--strict", "Fail on push errors").option("--ui-purpose", "Enable optional UI purpose evidence extraction (slower)").option("--no-local-artifacts", "Do not write .vibgrate JSON artifacts to disk").option("--max-privacy", "Enable strongest privacy mode (minimal scanners, no local artifacts)").option("--offline", "Run without network calls; do not upload results").option("--package-manifest <file>", "Use local package-version manifest JSON/ZIP (for offline mode)").option("--project-scan-timeout <seconds>", "Per-project scan timeout in seconds (default: 180)").option("--drift-budget <score>", "Fail if drift score is above budget (0-100)").option("--drift-worsening <percent>", "Fail if drift worsens by more than % since baseline").action(async (targetPath, opts) => {
|
|
268
|
+
const rootDir = path3.resolve(targetPath);
|
|
269
|
+
if (!await pathExists2(rootDir)) {
|
|
270
|
+
console.error(chalk3.red(`Path does not exist: ${rootDir}`));
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
const hasDsn = !!(opts.dsn || process.env.VIBGRATE_DSN);
|
|
274
|
+
const willPush = !opts.offline && (opts.push || hasDsn);
|
|
275
|
+
if (willPush && hasDsn) {
|
|
276
|
+
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
277
|
+
const parsed = parseDsn(dsn);
|
|
278
|
+
if (parsed) {
|
|
279
|
+
const ingestHost = opts.region ? resolveIngestHost(opts.region) : parsed.host;
|
|
280
|
+
const vcs = await detectVcs(rootDir);
|
|
281
|
+
const fingerprint = await computeRepoFingerprint(rootDir, vcs);
|
|
282
|
+
const repositoryName = await resolveRepositoryName(rootDir);
|
|
283
|
+
try {
|
|
284
|
+
const preflight = await fetchScanPreflight(parsed, ingestHost, {
|
|
285
|
+
repositoryName,
|
|
286
|
+
vcsSha: fingerprint.vcsSha
|
|
287
|
+
});
|
|
288
|
+
if (preflight.status === "error" || !preflight.scans.allowed) {
|
|
289
|
+
console.error(chalk3.red(preflight.error ?? "Scan ingestion not allowed for this workspace."));
|
|
290
|
+
console.error(
|
|
291
|
+
chalk3.dim(
|
|
292
|
+
`Credits: ${preflight.scans.used}/${preflight.scans.limit} (${preflight.plan.label} plan)`
|
|
293
|
+
)
|
|
294
|
+
);
|
|
295
|
+
if (opts.strict) process.exit(1);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
console.log(
|
|
299
|
+
chalk3.dim(
|
|
300
|
+
`Plan: ${preflight.plan.label} \u2014 scan credits ${preflight.scans.used}/${preflight.scans.limit} this month`
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
if (preflight.repository?.unchanged) {
|
|
304
|
+
console.log(
|
|
305
|
+
chalk3.green("\u2714") + ` Repository unchanged at ${preflight.repository.lastVcsSha?.slice(0, 7) ?? "same revision"} \u2014 skipping scan.`
|
|
306
|
+
);
|
|
307
|
+
if (preflight.repository.lastIngestId) {
|
|
308
|
+
emitIngestIdLine(preflight.repository.lastIngestId, { unchanged: true });
|
|
309
|
+
const dashUrl = `https://dash.vibgrate.com/${parsed.workspaceId}/scan/${preflight.repository.lastIngestId}`;
|
|
310
|
+
console.log(chalk3.dim(` Latest report: ${dashUrl}`));
|
|
311
|
+
} else if (opts.strict) {
|
|
312
|
+
console.error(chalk3.red("Repository unchanged but no previous ingest id available."));
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
} catch (e) {
|
|
318
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
319
|
+
console.error(chalk3.yellow(`Preflight check failed: ${msg}`));
|
|
320
|
+
if (opts.strict) process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const scanOpts = {
|
|
325
|
+
vibgrateVersion: VERSION,
|
|
326
|
+
out: opts.out,
|
|
327
|
+
format: opts.format || "text",
|
|
328
|
+
failOn: opts.failOn,
|
|
329
|
+
baseline: opts.baseline,
|
|
330
|
+
changedOnly: opts.changedOnly,
|
|
331
|
+
concurrency: parseInt(opts.concurrency, 10) || 8,
|
|
332
|
+
push: opts.push,
|
|
333
|
+
dsn: opts.dsn,
|
|
334
|
+
region: opts.region,
|
|
335
|
+
strict: opts.strict,
|
|
336
|
+
uiPurpose: opts.uiPurpose,
|
|
337
|
+
noLocalArtifacts: opts.noLocalArtifacts,
|
|
338
|
+
maxPrivacy: opts.maxPrivacy,
|
|
339
|
+
offline: opts.offline,
|
|
340
|
+
packageManifest: opts.packageManifest,
|
|
341
|
+
driftBudget: parseNonNegativeNumber(opts.driftBudget, "--drift-budget"),
|
|
342
|
+
driftWorseningPercent: parseNonNegativeNumber(opts.driftWorsening, "--drift-worsening"),
|
|
343
|
+
projectScanTimeout: opts.projectScanTimeout ? parseInt(opts.projectScanTimeout, 10) || void 0 : void 0
|
|
344
|
+
};
|
|
345
|
+
const artifact = await runScan(rootDir, scanOpts);
|
|
346
|
+
if (opts.failOn) {
|
|
347
|
+
const hasErrors = artifact.findings.some((f) => f.level === "error");
|
|
348
|
+
const hasWarnings = artifact.findings.some((f) => f.level === "warning");
|
|
349
|
+
if (opts.failOn === "error" && hasErrors) {
|
|
350
|
+
console.error(chalk3.red(`
|
|
351
|
+
Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
|
|
352
|
+
process.exit(2);
|
|
353
|
+
}
|
|
354
|
+
if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
|
|
355
|
+
console.error(chalk3.red(`
|
|
356
|
+
Failing: findings detected at warn level or above.`));
|
|
357
|
+
process.exit(2);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
if (scanOpts.driftBudget !== void 0 && artifact.drift.score > scanOpts.driftBudget) {
|
|
361
|
+
console.error(chalk3.red(`
|
|
362
|
+
Failing fitness function: drift score ${artifact.drift.score}/100 exceeds budget ${scanOpts.driftBudget}.`));
|
|
363
|
+
process.exit(2);
|
|
364
|
+
}
|
|
365
|
+
if (scanOpts.driftWorseningPercent !== void 0) {
|
|
366
|
+
if (artifact.delta === void 0) {
|
|
367
|
+
console.error(chalk3.red("\nFailing fitness function: --drift-worsening requires --baseline to compare against previous drift."));
|
|
368
|
+
process.exit(2);
|
|
369
|
+
}
|
|
370
|
+
if (artifact.delta > 0) {
|
|
371
|
+
const baselineScore = artifact.drift.score - artifact.delta;
|
|
372
|
+
const denominator = Math.max(Math.abs(baselineScore), 1e-4);
|
|
373
|
+
const worseningPercent = artifact.delta / denominator * 100;
|
|
374
|
+
if (worseningPercent > scanOpts.driftWorseningPercent) {
|
|
375
|
+
console.error(chalk3.red(`
|
|
376
|
+
Failing fitness function: drift worsened by ${worseningPercent.toFixed(2)}% (threshold ${scanOpts.driftWorseningPercent}%).`));
|
|
377
|
+
process.exit(2);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (willPush) {
|
|
382
|
+
await autoPush(artifact, rootDir, scanOpts);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// src/commands/report.ts
|
|
387
|
+
import * as path4 from "path";
|
|
388
|
+
import { Command as Command4 } from "commander";
|
|
389
|
+
import chalk5 from "chalk";
|
|
390
|
+
|
|
391
|
+
// src/formatters/text.ts
|
|
392
|
+
import chalk4 from "chalk";
|
|
393
|
+
function formatText(artifact) {
|
|
394
|
+
const lines = [];
|
|
395
|
+
lines.push("");
|
|
396
|
+
lines.push(chalk4.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk4.greenBright("\u279C"));
|
|
397
|
+
lines.push(chalk4.cyan(" \u256D\u2524") + chalk4.greenBright("\u25C9 \u25C9") + chalk4.cyan("\u251C\u256E") + " " + chalk4.bold.white("V I B G R A T E"));
|
|
398
|
+
lines.push(chalk4.cyan(" \u2570\u2524") + chalk4.dim("\u2500\u2500\u2500") + chalk4.cyan("\u251C\u256F") + " " + chalk4.dim(`Drift Intelligence Engine v${VERSION}`));
|
|
399
|
+
lines.push(chalk4.cyan(" \u2570\u2500\u2500\u2500\u256F"));
|
|
400
|
+
lines.push("");
|
|
401
|
+
lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
402
|
+
lines.push(chalk4.bold.cyan("\u2551 Vibgrate Drift Report \u2551"));
|
|
403
|
+
lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
404
|
+
lines.push("");
|
|
405
|
+
for (const project of artifact.projects) {
|
|
406
|
+
lines.push(chalk4.bold(` \u2500\u2500 ${project.name} `) + chalk4.dim(`(${project.type}) ${project.path}`));
|
|
407
|
+
if (project.runtime) {
|
|
408
|
+
const behindStr = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? chalk4.yellow(` (${project.runtimeMajorsBehind} major${project.runtimeMajorsBehind > 1 ? "s" : ""} behind)`) : chalk4.green(" (current)");
|
|
409
|
+
lines.push(` Runtime: ${project.runtime}${behindStr}`);
|
|
410
|
+
}
|
|
411
|
+
if (project.targetFramework) {
|
|
412
|
+
lines.push(` Target: ${project.targetFramework}`);
|
|
413
|
+
}
|
|
414
|
+
if (project.frameworks.length > 0) {
|
|
415
|
+
lines.push(" Frameworks:");
|
|
416
|
+
for (const fw of project.frameworks) {
|
|
417
|
+
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? chalk4.green("current") : chalk4.yellow(`${fw.majorsBehind} behind`) : chalk4.dim("unknown");
|
|
418
|
+
lines.push(` ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const b = project.dependencyAgeBuckets;
|
|
422
|
+
const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
|
|
423
|
+
if (total > 0) {
|
|
424
|
+
lines.push(" Dependencies:");
|
|
425
|
+
lines.push(` ${chalk4.green(`${b.current} current`)} ${chalk4.yellow(`${b.oneBehind} 1-behind`)} ${chalk4.red(`${b.twoPlusBehind} 2+ behind`)} ${chalk4.dim(`${b.unknown} unknown`)}`);
|
|
426
|
+
}
|
|
427
|
+
lines.push("");
|
|
428
|
+
}
|
|
429
|
+
if (artifact.delta !== void 0) {
|
|
430
|
+
const deltaStr = artifact.delta > 0 ? chalk4.green(`+${artifact.delta}`) : artifact.delta < 0 ? chalk4.red(`${artifact.delta}`) : chalk4.dim("0");
|
|
431
|
+
lines.push(chalk4.bold(" Drift Delta: ") + deltaStr + " (vs baseline)");
|
|
432
|
+
lines.push("");
|
|
433
|
+
}
|
|
434
|
+
if (artifact.extended) {
|
|
435
|
+
lines.push(...formatExtended(artifact.extended));
|
|
436
|
+
}
|
|
437
|
+
if (artifact.findings.length > 0) {
|
|
438
|
+
const errors = artifact.findings.filter((f) => f.level === "error");
|
|
439
|
+
const warnings = artifact.findings.filter((f) => f.level === "warning");
|
|
440
|
+
const notes = artifact.findings.filter((f) => f.level === "note");
|
|
441
|
+
const summary = [
|
|
442
|
+
errors.length > 0 ? chalk4.red(`${errors.length} error${errors.length !== 1 ? "s" : ""}`) : "",
|
|
443
|
+
warnings.length > 0 ? chalk4.yellow(`${warnings.length} warning${warnings.length !== 1 ? "s" : ""}`) : "",
|
|
444
|
+
notes.length > 0 ? chalk4.blue(`${notes.length} note${notes.length !== 1 ? "s" : ""}`) : ""
|
|
445
|
+
].filter(Boolean).join(chalk4.dim(", "));
|
|
446
|
+
lines.push(chalk4.bold.underline(` Findings`) + chalk4.dim(` (${summary})`));
|
|
447
|
+
for (const f of artifact.findings) {
|
|
448
|
+
const icon = f.level === "error" ? chalk4.red("\u2716") : f.level === "warning" ? chalk4.yellow("\u26A0") : chalk4.blue("\u2139");
|
|
449
|
+
lines.push(` ${icon} ${f.message}`);
|
|
450
|
+
lines.push(chalk4.dim(` ${f.ruleId} in ${f.location}`));
|
|
451
|
+
}
|
|
452
|
+
lines.push("");
|
|
453
|
+
}
|
|
454
|
+
const actions = generatePriorityActions(artifact);
|
|
455
|
+
if (actions.length > 0) {
|
|
456
|
+
lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
457
|
+
lines.push(chalk4.bold.cyan("\u2551 Top Priority Actions \u2551"));
|
|
458
|
+
lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
459
|
+
lines.push("");
|
|
460
|
+
for (let i = 0; i < actions.length; i++) {
|
|
461
|
+
const a = actions[i];
|
|
462
|
+
const num = chalk4.bold.cyan(` ${i + 1}.`);
|
|
463
|
+
lines.push(`${num} ${chalk4.bold(a.title)}`);
|
|
464
|
+
lines.push(chalk4.dim(` ${a.explanation}`));
|
|
465
|
+
if (a.impact) lines.push(` Impact: ${chalk4.green(a.impact)}`);
|
|
466
|
+
lines.push("");
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (artifact.extended?.architecture) {
|
|
470
|
+
lines.push(...formatArchitectureDiagram(artifact.extended.architecture));
|
|
471
|
+
}
|
|
472
|
+
if (artifact.solutions && artifact.solutions.length > 0) {
|
|
473
|
+
lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
474
|
+
lines.push(chalk4.bold.cyan("\u2551 Solution Drift Summary \u2551"));
|
|
475
|
+
lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
476
|
+
lines.push("");
|
|
477
|
+
for (const solution of artifact.solutions) {
|
|
478
|
+
const solScore = solution.drift?.score;
|
|
479
|
+
const color = typeof solScore === "number" ? solScore >= 70 ? chalk4.green : solScore >= 40 ? chalk4.yellow : chalk4.red : chalk4.dim;
|
|
480
|
+
lines.push(` \u2022 ${solution.name} (${solution.projectPaths.length} projects) \u2014 ${typeof solScore === "number" ? color(`${solScore}/100`) : chalk4.dim("n/a")}`);
|
|
481
|
+
}
|
|
482
|
+
lines.push("");
|
|
483
|
+
}
|
|
484
|
+
const scoreColor = artifact.drift.score >= 70 ? chalk4.green : artifact.drift.score >= 40 ? chalk4.yellow : chalk4.red;
|
|
485
|
+
lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
486
|
+
lines.push(chalk4.bold.cyan("\u2551 Drift Score Summary \u2551"));
|
|
487
|
+
lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
488
|
+
lines.push("");
|
|
489
|
+
lines.push(chalk4.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
|
|
490
|
+
lines.push(chalk4.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
|
|
491
|
+
lines.push(chalk4.bold(" Projects: ") + `${artifact.projects.length}`);
|
|
492
|
+
if (artifact.vcs) {
|
|
493
|
+
const vcsParts = [artifact.vcs.type];
|
|
494
|
+
if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
|
|
495
|
+
if (artifact.vcs.shortSha) vcsParts.push(chalk4.dim(artifact.vcs.shortSha));
|
|
496
|
+
lines.push(chalk4.bold(" VCS: ") + vcsParts.join(" "));
|
|
497
|
+
}
|
|
498
|
+
lines.push("");
|
|
499
|
+
const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
|
|
500
|
+
lines.push(" " + chalk4.bold.underline("Score Breakdown"));
|
|
501
|
+
lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk4.dim("n/a")}`);
|
|
502
|
+
lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk4.dim("n/a")}`);
|
|
503
|
+
lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk4.dim("n/a")}`);
|
|
504
|
+
lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk4.dim("n/a")}`);
|
|
505
|
+
lines.push("");
|
|
506
|
+
const scannedParts = [`Scanned at ${artifact.timestamp}`];
|
|
507
|
+
if (artifact.durationMs !== void 0) {
|
|
508
|
+
const secs = (artifact.durationMs / 1e3).toFixed(1);
|
|
509
|
+
scannedParts.push(`${secs}s`);
|
|
510
|
+
}
|
|
511
|
+
if (artifact.filesScanned !== void 0) {
|
|
512
|
+
scannedParts.push(`${artifact.filesScanned} file${artifact.filesScanned !== 1 ? "s" : ""} scanned`);
|
|
513
|
+
}
|
|
514
|
+
if (artifact.treeSummary) {
|
|
515
|
+
scannedParts.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files`);
|
|
516
|
+
scannedParts.push(`${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
|
|
517
|
+
}
|
|
518
|
+
lines.push(chalk4.dim(` ${scannedParts.join(" \xB7 ")}`));
|
|
519
|
+
lines.push("");
|
|
520
|
+
return lines.join("\n");
|
|
521
|
+
}
|
|
522
|
+
function riskBadge(level) {
|
|
523
|
+
switch (level) {
|
|
524
|
+
case "low":
|
|
525
|
+
return chalk4.bgGreen.black(" LOW ");
|
|
526
|
+
case "moderate":
|
|
527
|
+
return chalk4.bgYellow.black(" MODERATE ");
|
|
528
|
+
case "high":
|
|
529
|
+
return chalk4.bgRed.white(" HIGH ");
|
|
530
|
+
default:
|
|
531
|
+
return level;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
function scoreBar(score) {
|
|
535
|
+
const width = 20;
|
|
536
|
+
const filled = Math.round(score / 100 * width);
|
|
537
|
+
const empty = width - filled;
|
|
538
|
+
const color = score >= 70 ? chalk4.green : score >= 40 ? chalk4.yellow : chalk4.red;
|
|
539
|
+
return color("\u2588".repeat(filled)) + chalk4.dim("\u2591".repeat(empty)) + ` ${Math.round(score)}`;
|
|
540
|
+
}
|
|
541
|
+
var CATEGORY_LABELS = {
|
|
542
|
+
frontend: "Frontend",
|
|
543
|
+
metaFrameworks: "Meta-frameworks",
|
|
544
|
+
bundlers: "Bundlers",
|
|
545
|
+
css: "CSS / UI",
|
|
546
|
+
backend: "Backend",
|
|
547
|
+
orm: "ORM / Database",
|
|
548
|
+
testing: "Testing",
|
|
549
|
+
lintFormat: "Lint & Format",
|
|
550
|
+
apiMessaging: "API & Messaging",
|
|
551
|
+
observability: "Observability",
|
|
552
|
+
payment: "Payment",
|
|
553
|
+
auth: "Auth",
|
|
554
|
+
email: "Email",
|
|
555
|
+
cloud: "Cloud",
|
|
556
|
+
databases: "Databases",
|
|
557
|
+
messaging: "Messaging",
|
|
558
|
+
crm: "CRM & Comms",
|
|
559
|
+
storage: "Storage",
|
|
560
|
+
search: "Search & AI"
|
|
561
|
+
};
|
|
562
|
+
function formatExtended(ext) {
|
|
563
|
+
const lines = [];
|
|
564
|
+
if (ext.toolingInventory) {
|
|
565
|
+
const inv = ext.toolingInventory;
|
|
566
|
+
const categories = Object.entries(inv).filter(([, items]) => items.length > 0);
|
|
567
|
+
if (categories.length > 0) {
|
|
568
|
+
lines.push(chalk4.bold.underline(" Tech Stack"));
|
|
569
|
+
for (const [cat, items] of categories) {
|
|
570
|
+
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
571
|
+
const names = items.map((i) => chalk4.white(i.name)).join(chalk4.dim(", "));
|
|
572
|
+
lines.push(` ${chalk4.cyan(label)}: ${names}`);
|
|
573
|
+
}
|
|
574
|
+
lines.push("");
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (ext.serviceDependencies) {
|
|
578
|
+
const svc = ext.serviceDependencies;
|
|
579
|
+
const categories = Object.entries(svc).filter(([, items]) => items.length > 0);
|
|
580
|
+
if (categories.length > 0) {
|
|
581
|
+
lines.push(chalk4.bold.underline(" Services & Integrations"));
|
|
582
|
+
for (const [cat, items] of categories) {
|
|
583
|
+
const label = CATEGORY_LABELS[cat] ?? cat;
|
|
584
|
+
const names = items.map((i) => {
|
|
585
|
+
const ver = i.version ? chalk4.dim(` ${i.version}`) : "";
|
|
586
|
+
return chalk4.white(i.name) + ver;
|
|
587
|
+
}).join(chalk4.dim(", "));
|
|
588
|
+
lines.push(` ${chalk4.cyan(label)}: ${names}`);
|
|
589
|
+
}
|
|
590
|
+
lines.push("");
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (ext.breakingChangeExposure) {
|
|
594
|
+
const bc = ext.breakingChangeExposure;
|
|
595
|
+
if (bc.deprecatedPackages.length > 0 || bc.legacyPolyfills.length > 0) {
|
|
596
|
+
lines.push(chalk4.bold.underline(" Breaking Change Exposure"));
|
|
597
|
+
const exposureColor = bc.exposureScore >= 40 ? chalk4.red : bc.exposureScore >= 20 ? chalk4.yellow : chalk4.green;
|
|
598
|
+
lines.push(` Exposure Score: ${exposureColor.bold(`${bc.exposureScore}/100`)}`);
|
|
599
|
+
if (bc.deprecatedPackages.length > 0) {
|
|
600
|
+
lines.push(` ${chalk4.red("Deprecated")}: ${bc.deprecatedPackages.map((p) => chalk4.dim(p)).join(", ")}`);
|
|
601
|
+
}
|
|
602
|
+
if (bc.legacyPolyfills.length > 0) {
|
|
603
|
+
lines.push(` ${chalk4.yellow("Polyfills")}: ${bc.legacyPolyfills.map((p) => chalk4.dim(p)).join(", ")}`);
|
|
604
|
+
}
|
|
605
|
+
if (bc.peerConflictsDetected) {
|
|
606
|
+
lines.push(` ${chalk4.red("\u26A0")} Peer dependency conflicts detected`);
|
|
607
|
+
}
|
|
608
|
+
lines.push(` Recommendation: ${chalk4.bold(bc.overallRecommendation)}`);
|
|
609
|
+
const projectsWithPlans = bc.projectIntelligence.filter((p) => p.packages.length > 0).slice(0, 3);
|
|
610
|
+
if (projectsWithPlans.length > 0) {
|
|
611
|
+
lines.push(" Major Upgrade Intelligence:");
|
|
612
|
+
for (const p of projectsWithPlans) {
|
|
613
|
+
lines.push(` - ${p.project} (${p.recommendation})`);
|
|
614
|
+
for (const pkg2 of p.packages.slice(0, 2)) {
|
|
615
|
+
lines.push(` \xB7 ${pkg2.package} ${pkg2.currentVersion ?? "?"} \u2192 ${pkg2.targetVersion ?? "?"} | touched ~${pkg2.usage.touchedPercent}% | ${pkg2.automatable}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
lines.push("");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (ext.tsModernity && ext.tsModernity.typescriptVersion) {
|
|
623
|
+
const ts = ext.tsModernity;
|
|
624
|
+
lines.push(chalk4.bold.underline(" TypeScript"));
|
|
625
|
+
const parts = [];
|
|
626
|
+
parts.push(`v${ts.typescriptVersion}`);
|
|
627
|
+
if (ts.strict === true) parts.push(chalk4.green("strict \u2714"));
|
|
628
|
+
else if (ts.strict === false) parts.push(chalk4.yellow("strict \u2716"));
|
|
629
|
+
if (ts.moduleType) parts.push(ts.moduleType.toUpperCase());
|
|
630
|
+
if (ts.target) parts.push(`target: ${ts.target}`);
|
|
631
|
+
lines.push(` ${parts.join(chalk4.dim(" \xB7 "))}`);
|
|
632
|
+
lines.push("");
|
|
633
|
+
}
|
|
634
|
+
if (ext.buildDeploy) {
|
|
635
|
+
const bd = ext.buildDeploy;
|
|
636
|
+
const hasSomething = bd.ci.length > 0 || bd.docker.dockerfileCount > 0 || bd.packageManagers.length > 0;
|
|
637
|
+
if (hasSomething) {
|
|
638
|
+
lines.push(chalk4.bold.underline(" Build & Deploy"));
|
|
639
|
+
if (bd.ci.length > 0) lines.push(` CI: ${bd.ci.join(", ")}`);
|
|
640
|
+
if (bd.docker.dockerfileCount > 0) {
|
|
641
|
+
lines.push(` Docker: ${bd.docker.dockerfileCount} Dockerfile${bd.docker.dockerfileCount !== 1 ? "s" : ""} (${bd.docker.baseImages.join(", ")})`);
|
|
642
|
+
}
|
|
643
|
+
if (bd.packageManagers.length > 0) lines.push(` Package Managers: ${bd.packageManagers.join(", ")}`);
|
|
644
|
+
if (bd.monorepoTools.length > 0) lines.push(` Monorepo: ${bd.monorepoTools.join(", ")}`);
|
|
645
|
+
if (bd.iac.length > 0) lines.push(` IaC: ${bd.iac.join(", ")}`);
|
|
646
|
+
lines.push("");
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (ext.uiPurpose) {
|
|
650
|
+
const up = ext.uiPurpose;
|
|
651
|
+
lines.push(chalk4.bold.underline(" Product Purpose Signals"));
|
|
652
|
+
lines.push(` Frameworks: ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : chalk4.dim("unknown")}`);
|
|
653
|
+
lines.push(` Evidence: ${up.topEvidence.length}${up.capped ? chalk4.dim(` of ${up.evidenceCount} (capped)`) : ""}`);
|
|
654
|
+
const top = up.topEvidence.slice(0, 8);
|
|
655
|
+
if (top.length > 0) {
|
|
656
|
+
lines.push(" Top Signals:");
|
|
657
|
+
for (const item of top) {
|
|
658
|
+
lines.push(` - [${item.kind}] ${item.value} ${chalk4.dim(`(${item.file})`)}`);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
if (up.unknownSignals.length > 0) {
|
|
662
|
+
lines.push(" Unknowns:");
|
|
663
|
+
for (const u of up.unknownSignals.slice(0, 4)) {
|
|
664
|
+
lines.push(` - ${chalk4.yellow(u)}`);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
lines.push("");
|
|
668
|
+
}
|
|
669
|
+
if (ext.securityPosture) {
|
|
670
|
+
const sec = ext.securityPosture;
|
|
671
|
+
lines.push(chalk4.bold.underline(" Security Posture"));
|
|
672
|
+
const checks = [];
|
|
673
|
+
checks.push(sec.lockfilePresent ? chalk4.green("Lockfile \u2714") : chalk4.red("Lockfile \u2716"));
|
|
674
|
+
checks.push(sec.gitignoreCoversEnv ? chalk4.green(".env \u2714") : chalk4.red(".env \u2716"));
|
|
675
|
+
checks.push(sec.gitignoreCoversNodeModules ? chalk4.green("node_modules \u2714") : chalk4.yellow("node_modules \u2716"));
|
|
676
|
+
if (sec.multipleLockfileTypes) checks.push(chalk4.yellow("Multiple lockfiles \u26A0"));
|
|
677
|
+
if (sec.envFilesTracked) checks.push(chalk4.red("Env files tracked \u2716"));
|
|
678
|
+
lines.push(` ${checks.join(chalk4.dim(" \xB7 "))}`);
|
|
679
|
+
lines.push("");
|
|
680
|
+
}
|
|
681
|
+
if (ext.platformMatrix) {
|
|
682
|
+
const pm = ext.platformMatrix;
|
|
683
|
+
if (pm.nativeModules.length > 0 || pm.dockerBaseImages.length > 0) {
|
|
684
|
+
lines.push(chalk4.bold.underline(" Platform"));
|
|
685
|
+
if (pm.nativeModules.length > 0) {
|
|
686
|
+
lines.push(` Native modules: ${pm.nativeModules.map((m) => chalk4.dim(m)).join(", ")}`);
|
|
687
|
+
}
|
|
688
|
+
if (pm.osAssumptions.length > 0) {
|
|
689
|
+
lines.push(` OS assumptions: ${pm.osAssumptions.join(", ")}`);
|
|
690
|
+
}
|
|
691
|
+
lines.push("");
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (ext.codeQuality) {
|
|
695
|
+
const cq = ext.codeQuality;
|
|
696
|
+
lines.push(chalk4.bold.underline(" Code Quality"));
|
|
697
|
+
lines.push(` Files: ${chalk4.white(`${cq.filesAnalyzed}`)} \xB7 Functions: ${chalk4.white(`${cq.functionsAnalyzed}`)} \xB7 Avg complexity: ${chalk4.white(`${cq.avgCyclomaticComplexity}`)} \xB7 Avg length: ${chalk4.white(`${cq.avgFunctionLength}`)} lines`);
|
|
698
|
+
lines.push(` Max nesting: ${cq.maxNestingDepth} \xB7 Circular deps: ${cq.circularDependencies} \xB7 Dead code: ${cq.deadCodePercent}%`);
|
|
699
|
+
if (cq.godFiles.length > 0) {
|
|
700
|
+
const preview = cq.godFiles.slice(0, 3).map((f) => `${f.path} (${f.lines} lines)`).join(", ");
|
|
701
|
+
lines.push(` ${chalk4.yellow("God files")}: ${preview}`);
|
|
702
|
+
}
|
|
703
|
+
lines.push("");
|
|
704
|
+
}
|
|
705
|
+
if (ext.dependencyGraph) {
|
|
706
|
+
const dg = ext.dependencyGraph;
|
|
707
|
+
if (dg.lockfileType) {
|
|
708
|
+
lines.push(chalk4.bold.underline(" Dependency Graph"));
|
|
709
|
+
lines.push(` ${dg.lockfileType}: ${chalk4.white(`${dg.totalUnique}`)} unique, ${chalk4.white(`${dg.totalInstalled}`)} installed`);
|
|
710
|
+
if (dg.duplicatedPackages.length > 0) {
|
|
711
|
+
lines.push(` ${chalk4.yellow(`${dg.duplicatedPackages.length} duplicated`)} packages`);
|
|
712
|
+
}
|
|
713
|
+
if (dg.phantomDependencies.length > 0) {
|
|
714
|
+
lines.push(` ${chalk4.red(`${dg.phantomDependencies.length} phantom`)} dependencies`);
|
|
715
|
+
}
|
|
716
|
+
lines.push("");
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
return lines;
|
|
720
|
+
}
|
|
721
|
+
function formatArchitectureDiagram(arch) {
|
|
722
|
+
const lines = [];
|
|
723
|
+
lines.push(chalk4.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
724
|
+
lines.push(chalk4.bold.cyan("\u2551 Architecture Layers \u2551"));
|
|
725
|
+
lines.push(chalk4.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
726
|
+
lines.push("");
|
|
727
|
+
lines.push(chalk4.bold(" Archetype: ") + `${arch.archetype}` + chalk4.dim(` (${Math.round(arch.archetypeConfidence * 100)}% confidence)`));
|
|
728
|
+
lines.push(` Files classified: ${arch.totalClassified}` + (arch.unclassified > 0 ? chalk4.dim(` (${arch.unclassified} unclassified)`) : ""));
|
|
729
|
+
lines.push("");
|
|
730
|
+
if (arch.layers.length > 0) {
|
|
731
|
+
for (const layer of arch.layers) {
|
|
732
|
+
const risk = layer.riskLevel === "none" ? chalk4.dim("none") : layer.riskLevel === "low" ? chalk4.green("low") : layer.riskLevel === "moderate" ? chalk4.yellow("moderate") : chalk4.red("high");
|
|
733
|
+
lines.push(` ${chalk4.bold(layer.layer)} ${layer.fileCount} file${layer.fileCount !== 1 ? "s" : ""} drift ${scoreBar(layer.driftScore)} risk ${risk}`);
|
|
734
|
+
}
|
|
735
|
+
lines.push("");
|
|
736
|
+
}
|
|
737
|
+
return lines;
|
|
738
|
+
}
|
|
739
|
+
function generatePriorityActions(artifact) {
|
|
740
|
+
const actions = [];
|
|
741
|
+
const eolProjects = artifact.projects.filter(
|
|
742
|
+
(p) => p.runtimeMajorsBehind !== void 0 && p.runtimeMajorsBehind >= 3
|
|
743
|
+
);
|
|
744
|
+
if (eolProjects.length > 0) {
|
|
745
|
+
const names = eolProjects.map((p) => p.name).join(", ");
|
|
746
|
+
let detail = `End-of-life runtimes no longer receive security patches and block ecosystem upgrades.`;
|
|
747
|
+
const fileLines = [];
|
|
748
|
+
for (const p of eolProjects) {
|
|
749
|
+
fileLines.push(`
|
|
750
|
+
./${p.path}`);
|
|
751
|
+
fileLines.push(`
|
|
752
|
+
${p.runtime} \u2192 ${p.runtimeLatest} (${p.runtimeMajorsBehind} major${p.runtimeMajorsBehind > 1 ? "s" : ""} behind)`);
|
|
753
|
+
}
|
|
754
|
+
detail += fileLines.join("");
|
|
755
|
+
actions.push({
|
|
756
|
+
title: `Upgrade EOL runtime${eolProjects.length > 1 ? "s" : ""} in ${names}`,
|
|
757
|
+
explanation: detail,
|
|
758
|
+
impact: `+${Math.min(eolProjects.length * 10, 30)} points (runtime & EOL scores)`,
|
|
759
|
+
severity: 100
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
const severeFrameworks = [];
|
|
763
|
+
for (const p of artifact.projects) {
|
|
764
|
+
for (const fw of p.frameworks) {
|
|
765
|
+
if (fw.majorsBehind !== null && fw.majorsBehind >= 3) {
|
|
766
|
+
severeFrameworks.push({ name: fw.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}`, behind: fw.majorsBehind, project: p.name, projectPath: p.path });
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
if (severeFrameworks.length > 0) {
|
|
771
|
+
const worst = severeFrameworks.sort((a, b) => b.behind - a.behind)[0];
|
|
772
|
+
const others = severeFrameworks.length > 1 ? ` (+${severeFrameworks.length - 1} more)` : "";
|
|
773
|
+
let detail = `${worst.behind} major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.`;
|
|
774
|
+
const fileLines = [];
|
|
775
|
+
let shown = 0;
|
|
776
|
+
for (const sf of severeFrameworks) {
|
|
777
|
+
if (shown >= 8) break;
|
|
778
|
+
fileLines.push(`
|
|
779
|
+
./${sf.projectPath}`);
|
|
780
|
+
fileLines.push(`
|
|
781
|
+
${sf.name}: ${sf.fw} (${sf.behind} major${sf.behind > 1 ? "s" : ""} behind)`);
|
|
782
|
+
shown++;
|
|
783
|
+
}
|
|
784
|
+
const remaining = severeFrameworks.length - shown;
|
|
785
|
+
detail += fileLines.join("");
|
|
786
|
+
if (remaining > 0) detail += `
|
|
787
|
+
... and ${remaining} more`;
|
|
788
|
+
actions.push({
|
|
789
|
+
title: `Upgrade ${worst.name} ${worst.fw} in ${worst.project}${others}`,
|
|
790
|
+
explanation: detail,
|
|
791
|
+
impact: `+5\u201315 points`,
|
|
792
|
+
severity: 90
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
for (const p of artifact.projects) {
|
|
796
|
+
const b = p.dependencyAgeBuckets;
|
|
797
|
+
const total = b.current + b.oneBehind + b.twoPlusBehind;
|
|
798
|
+
if (total === 0) continue;
|
|
799
|
+
const twoPlusPct = Math.round(b.twoPlusBehind / total * 100);
|
|
800
|
+
if (twoPlusPct >= 40) {
|
|
801
|
+
let detail = `${b.twoPlusBehind} of ${total} dependencies are 2+ majors behind. Run \`npm outdated\` and prioritise packages with known CVEs or breaking API changes.`;
|
|
802
|
+
const worstDeps = p.dependencies.filter((d) => d.majorsBehind !== null && d.majorsBehind >= 2).sort((a, b2) => (b2.majorsBehind ?? 0) - (a.majorsBehind ?? 0));
|
|
803
|
+
if (worstDeps.length > 0) {
|
|
804
|
+
const depLines = [];
|
|
805
|
+
let shown = 0;
|
|
806
|
+
depLines.push(`
|
|
807
|
+
./${p.path}`);
|
|
808
|
+
for (const dep of worstDeps) {
|
|
809
|
+
if (shown >= 8) break;
|
|
810
|
+
const current = dep.resolvedVersion ?? dep.currentSpec;
|
|
811
|
+
const latest = dep.latestStable ?? "?";
|
|
812
|
+
depLines.push(`
|
|
813
|
+
${dep.package}: ${current} \u2192 ${latest} (${dep.majorsBehind} major${dep.majorsBehind > 1 ? "s" : ""} behind)`);
|
|
814
|
+
shown++;
|
|
815
|
+
}
|
|
816
|
+
const remaining = worstDeps.length - shown;
|
|
817
|
+
detail += depLines.join("");
|
|
818
|
+
if (remaining > 0) detail += `
|
|
819
|
+
... and ${remaining} more`;
|
|
820
|
+
}
|
|
821
|
+
actions.push({
|
|
822
|
+
title: `Reduce dependency rot in ${p.name} (${twoPlusPct}% severely outdated)`,
|
|
823
|
+
explanation: detail,
|
|
824
|
+
impact: `+5\u201310 points`,
|
|
825
|
+
severity: 80 + twoPlusPct / 10
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
const twoMajorFrameworks = [];
|
|
830
|
+
for (const p of artifact.projects) {
|
|
831
|
+
for (const fw of p.frameworks) {
|
|
832
|
+
if (fw.majorsBehind === 2) {
|
|
833
|
+
twoMajorFrameworks.push({ name: fw.name, project: p.name, projectPath: p.path, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}` });
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
const uniqueTwo = [...new Map(twoMajorFrameworks.map((f) => [f.name, f])).values()];
|
|
838
|
+
if (uniqueTwo.length > 0) {
|
|
839
|
+
const list = uniqueTwo.slice(0, 3).map((f) => `${f.name} (${f.fw})`).join(", ");
|
|
840
|
+
const moreCount = uniqueTwo.length > 3 ? ` +${uniqueTwo.length - 3} more` : "";
|
|
841
|
+
let detail = `These frameworks are 2 major versions behind. Create upgrade tickets and check migration guides \u2014 the gap will widen with each new release.`;
|
|
842
|
+
const fileLines = [];
|
|
843
|
+
let shown = 0;
|
|
844
|
+
for (const tf of twoMajorFrameworks) {
|
|
845
|
+
if (shown >= 8) break;
|
|
846
|
+
fileLines.push(`
|
|
847
|
+
./${tf.projectPath}`);
|
|
848
|
+
fileLines.push(`
|
|
849
|
+
${tf.name}: ${tf.fw}`);
|
|
850
|
+
shown++;
|
|
851
|
+
}
|
|
852
|
+
const remaining = twoMajorFrameworks.length - shown;
|
|
853
|
+
detail += fileLines.join("");
|
|
854
|
+
if (remaining > 0) detail += `
|
|
855
|
+
... and ${remaining} more`;
|
|
856
|
+
actions.push({
|
|
857
|
+
title: `Plan major framework upgrades: ${list}${moreCount}`,
|
|
858
|
+
explanation: detail,
|
|
859
|
+
impact: `+5\u201310 points`,
|
|
860
|
+
severity: 60
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
if (artifact.extended?.breakingChangeExposure) {
|
|
864
|
+
const bc = artifact.extended.breakingChangeExposure;
|
|
865
|
+
const total = bc.deprecatedPackages.length + bc.legacyPolyfills.length;
|
|
866
|
+
if (total > 0) {
|
|
867
|
+
const items = [...bc.deprecatedPackages, ...bc.legacyPolyfills].slice(0, 5).join(", ");
|
|
868
|
+
const moreCount = total > 5 ? ` +${total - 5} more` : "";
|
|
869
|
+
let detail = `${total} package${total !== 1 ? "s" : ""} are deprecated or legacy polyfills. These receive no updates and may have known vulnerabilities.`;
|
|
870
|
+
const allPkgNames = /* @__PURE__ */ new Set([...bc.deprecatedPackages, ...bc.legacyPolyfills]);
|
|
871
|
+
const fileLines = [];
|
|
872
|
+
let shown = 0;
|
|
873
|
+
for (const p of artifact.projects) {
|
|
874
|
+
const matches = p.dependencies.filter((d) => allPkgNames.has(d.package));
|
|
875
|
+
if (matches.length === 0) continue;
|
|
876
|
+
if (shown >= 10) break;
|
|
877
|
+
fileLines.push(`
|
|
878
|
+
./${p.path}`);
|
|
879
|
+
for (const dep of matches) {
|
|
880
|
+
if (shown >= 10) break;
|
|
881
|
+
const ver = dep.resolvedVersion ?? dep.currentSpec;
|
|
882
|
+
const label = bc.deprecatedPackages.includes(dep.package) ? "deprecated" : "polyfill";
|
|
883
|
+
fileLines.push(`
|
|
884
|
+
${dep.package}: ${ver} (${label})`);
|
|
885
|
+
shown++;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
const remaining = total - shown;
|
|
889
|
+
detail += fileLines.join("");
|
|
890
|
+
if (remaining > 0) detail += `
|
|
891
|
+
... and ${remaining} more`;
|
|
892
|
+
actions.push({
|
|
893
|
+
title: `Replace deprecated/legacy packages: ${items}${moreCount}`,
|
|
894
|
+
explanation: detail,
|
|
895
|
+
severity: 55
|
|
896
|
+
});
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
if (artifact.extended?.dependencyGraph) {
|
|
900
|
+
const dg = artifact.extended.dependencyGraph;
|
|
901
|
+
const phantomCount = dg.phantomDependencies.length;
|
|
902
|
+
if (phantomCount >= 10) {
|
|
903
|
+
let detail = `Packages used in code but not declared in package.json. These rely on transitive installs and can break unpredictably when other packages update.`;
|
|
904
|
+
const details = dg.phantomDependencyDetails;
|
|
905
|
+
if (details && details.length > 0) {
|
|
906
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
907
|
+
for (const d of details) {
|
|
908
|
+
if (!byPath.has(d.sourcePath)) byPath.set(d.sourcePath, []);
|
|
909
|
+
byPath.get(d.sourcePath).push({ package: d.package, spec: d.spec });
|
|
910
|
+
}
|
|
911
|
+
const pathLines = [];
|
|
912
|
+
let shown = 0;
|
|
913
|
+
for (const [srcPath, pkgs] of byPath) {
|
|
914
|
+
if (shown >= 10) break;
|
|
915
|
+
pathLines.push(`
|
|
916
|
+
./${srcPath}`);
|
|
917
|
+
for (const pkg2 of pkgs) {
|
|
918
|
+
if (shown >= 10) break;
|
|
919
|
+
pathLines.push(`
|
|
920
|
+
${pkg2.package}: ${pkg2.spec}`);
|
|
921
|
+
shown++;
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
const remaining = phantomCount - shown;
|
|
925
|
+
detail += pathLines.join("");
|
|
926
|
+
if (remaining > 0) detail += `
|
|
927
|
+
... and ${remaining} more`;
|
|
928
|
+
}
|
|
929
|
+
actions.push({
|
|
930
|
+
title: `Fix ${phantomCount} phantom dependencies`,
|
|
931
|
+
explanation: detail,
|
|
932
|
+
severity: 45
|
|
933
|
+
});
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
if (artifact.extended?.securityPosture) {
|
|
937
|
+
const sec = artifact.extended.securityPosture;
|
|
938
|
+
if (sec.envFilesTracked || !sec.lockfilePresent) {
|
|
939
|
+
const issues = [];
|
|
940
|
+
if (sec.envFilesTracked) issues.push(".env files are tracked in git");
|
|
941
|
+
if (!sec.lockfilePresent) issues.push("no lockfile found");
|
|
942
|
+
let detail;
|
|
943
|
+
if (sec.envFilesTracked) {
|
|
944
|
+
detail = "Environment files may contain secrets. Add them to .gitignore and rotate any exposed credentials immediately.";
|
|
945
|
+
detail += "\n ./.gitignore";
|
|
946
|
+
detail += "\n Add: .env, .env.*, .env.local";
|
|
947
|
+
} else {
|
|
948
|
+
detail = "Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.";
|
|
949
|
+
detail += "\n ./";
|
|
950
|
+
detail += `
|
|
951
|
+
Missing: ${sec.lockfileTypes.length > 0 ? sec.lockfileTypes.join(", ") + " (multiple types detected)" : "package-lock.json, pnpm-lock.yaml, or yarn.lock"}`;
|
|
952
|
+
}
|
|
953
|
+
actions.push({
|
|
954
|
+
title: `Fix security posture: ${issues.join(", ")}`,
|
|
955
|
+
explanation: detail,
|
|
956
|
+
severity: 95
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
if (artifact.extended?.dependencyGraph) {
|
|
961
|
+
const dupes = artifact.extended.dependencyGraph.duplicatedPackages;
|
|
962
|
+
const highImpactDupes = dupes.filter((d) => d.versions.length >= 3);
|
|
963
|
+
if (highImpactDupes.length >= 3) {
|
|
964
|
+
const names = highImpactDupes.slice(0, 4).map((d) => `${d.name} (${d.versions.length}v)`).join(", ");
|
|
965
|
+
let detail = `${highImpactDupes.length} packages have 3+ versions installed. Run \`npm dedupe\` to reduce bundle size and install time.`;
|
|
966
|
+
const dupeLines = [];
|
|
967
|
+
let shown = 0;
|
|
968
|
+
for (const d of highImpactDupes) {
|
|
969
|
+
if (shown >= 8) break;
|
|
970
|
+
dupeLines.push(`
|
|
971
|
+
${d.name}: ${d.versions.join(", ")} (${d.consumers} consumer${d.consumers !== 1 ? "s" : ""})`);
|
|
972
|
+
shown++;
|
|
973
|
+
}
|
|
974
|
+
const remaining = highImpactDupes.length - shown;
|
|
975
|
+
detail += dupeLines.join("");
|
|
976
|
+
if (remaining > 0) detail += `
|
|
977
|
+
... and ${remaining} more`;
|
|
978
|
+
actions.push({
|
|
979
|
+
title: `Deduplicate heavily-versioned packages`,
|
|
980
|
+
explanation: detail,
|
|
981
|
+
severity: 35
|
|
982
|
+
});
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
actions.sort((a, b) => b.severity - a.severity);
|
|
986
|
+
return actions.slice(0, 5);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// src/formatters/markdown.ts
|
|
990
|
+
function formatMarkdown(artifact) {
|
|
991
|
+
const lines = [];
|
|
992
|
+
lines.push("# Vibgrate Drift Report");
|
|
993
|
+
lines.push("");
|
|
994
|
+
lines.push(`| Metric | Value |`);
|
|
995
|
+
lines.push(`|--------|-------|`);
|
|
996
|
+
lines.push(`| **Drift Score** | ${artifact.drift.score}/100 |`);
|
|
997
|
+
lines.push(`| **Risk Level** | ${artifact.drift.riskLevel.toUpperCase()} |`);
|
|
998
|
+
lines.push(`| **Projects** | ${artifact.projects.length} |`);
|
|
999
|
+
const scannedMeta = [artifact.timestamp];
|
|
1000
|
+
if (artifact.durationMs !== void 0) scannedMeta.push(`${(artifact.durationMs / 1e3).toFixed(1)}s`);
|
|
1001
|
+
if (artifact.filesScanned !== void 0) scannedMeta.push(`${artifact.filesScanned} files`);
|
|
1002
|
+
if (artifact.treeSummary) scannedMeta.push(`${artifact.treeSummary.totalFiles.toLocaleString()} workspace files \xB7 ${artifact.treeSummary.totalDirs.toLocaleString()} dirs`);
|
|
1003
|
+
lines.push(`| **Scanned** | ${scannedMeta.join(" \xB7 ")} |`);
|
|
1004
|
+
if (artifact.vcs) {
|
|
1005
|
+
lines.push(`| **VCS** | ${artifact.vcs.type} |`);
|
|
1006
|
+
if (artifact.vcs.branch) lines.push(`| **Branch** | ${artifact.vcs.branch} |`);
|
|
1007
|
+
if (artifact.vcs.sha) lines.push(`| **Commit** | \`${artifact.vcs.shortSha}\` |`);
|
|
1008
|
+
}
|
|
1009
|
+
lines.push("");
|
|
1010
|
+
lines.push("## Score Breakdown");
|
|
1011
|
+
lines.push("");
|
|
1012
|
+
lines.push(`| Component | Score |`);
|
|
1013
|
+
lines.push(`|-----------|-------|`);
|
|
1014
|
+
lines.push(`| Runtime | ${artifact.drift.components.runtimeScore} |`);
|
|
1015
|
+
lines.push(`| Frameworks | ${artifact.drift.components.frameworkScore} |`);
|
|
1016
|
+
lines.push(`| Dependencies | ${artifact.drift.components.dependencyScore} |`);
|
|
1017
|
+
lines.push(`| EOL Risk | ${artifact.drift.components.eolScore} |`);
|
|
1018
|
+
lines.push("");
|
|
1019
|
+
lines.push("## Projects");
|
|
1020
|
+
lines.push("");
|
|
1021
|
+
for (const project of artifact.projects) {
|
|
1022
|
+
lines.push(`### ${project.name} (${project.type})`);
|
|
1023
|
+
lines.push("");
|
|
1024
|
+
if (project.runtime) {
|
|
1025
|
+
const lag = project.runtimeMajorsBehind !== void 0 && project.runtimeMajorsBehind > 0 ? ` \u2014 ${project.runtimeMajorsBehind} major(s) behind` : " \u2014 current";
|
|
1026
|
+
lines.push(`- **Runtime:** ${project.runtime}${lag}`);
|
|
1027
|
+
}
|
|
1028
|
+
if (project.frameworks.length > 0) {
|
|
1029
|
+
lines.push("- **Frameworks:**");
|
|
1030
|
+
for (const fw of project.frameworks) {
|
|
1031
|
+
const lag = fw.majorsBehind !== null ? fw.majorsBehind === 0 ? "current" : `${fw.majorsBehind} behind` : "unknown";
|
|
1032
|
+
lines.push(` - ${fw.name}: ${fw.currentVersion ?? "?"} \u2192 ${fw.latestVersion ?? "?"} (${lag})`);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
const b = project.dependencyAgeBuckets;
|
|
1036
|
+
const total = b.current + b.oneBehind + b.twoPlusBehind + b.unknown;
|
|
1037
|
+
if (total > 0) {
|
|
1038
|
+
lines.push(`- **Dependencies:** ${b.current} current, ${b.oneBehind} 1-behind, ${b.twoPlusBehind} 2+ behind, ${b.unknown} unknown`);
|
|
1039
|
+
}
|
|
1040
|
+
lines.push("");
|
|
1041
|
+
}
|
|
1042
|
+
if (artifact.extended?.uiPurpose) {
|
|
1043
|
+
const up = artifact.extended.uiPurpose;
|
|
1044
|
+
lines.push("## Product Purpose Signals");
|
|
1045
|
+
lines.push("");
|
|
1046
|
+
lines.push(`- **Frameworks:** ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : "unknown"}`);
|
|
1047
|
+
lines.push(`- **Evidence Items:** ${up.topEvidence.length}${up.capped ? ` (capped from ${up.evidenceCount})` : ""}`);
|
|
1048
|
+
if (up.topEvidence.length > 0) {
|
|
1049
|
+
lines.push("- **Top Evidence:**");
|
|
1050
|
+
for (const item of up.topEvidence.slice(0, 10)) {
|
|
1051
|
+
lines.push(` - [${item.kind}] ${item.value} (${item.file})`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (up.unknownSignals.length > 0) {
|
|
1055
|
+
lines.push("- **Unknowns:**");
|
|
1056
|
+
for (const u of up.unknownSignals.slice(0, 5)) {
|
|
1057
|
+
lines.push(` - ${u}`);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
lines.push("");
|
|
1061
|
+
}
|
|
1062
|
+
if (artifact.findings.length > 0) {
|
|
1063
|
+
lines.push("## Findings");
|
|
1064
|
+
lines.push("");
|
|
1065
|
+
lines.push(`| Level | Rule | Message | Location |`);
|
|
1066
|
+
lines.push(`|-------|------|---------|----------|`);
|
|
1067
|
+
for (const f of artifact.findings) {
|
|
1068
|
+
const emoji = f.level === "error" ? "\u{1F534}" : f.level === "warning" ? "\u{1F7E1}" : "\u{1F535}";
|
|
1069
|
+
lines.push(`| ${emoji} ${f.level} | ${f.ruleId} | ${f.message} | ${f.location} |`);
|
|
1070
|
+
}
|
|
1071
|
+
lines.push("");
|
|
1072
|
+
}
|
|
1073
|
+
if (artifact.delta !== void 0) {
|
|
1074
|
+
const dir = artifact.delta > 0 ? "\u{1F4C8}" : artifact.delta < 0 ? "\u{1F4C9}" : "\u27A1\uFE0F";
|
|
1075
|
+
lines.push(`## Drift Delta: ${dir} ${artifact.delta > 0 ? "+" : ""}${artifact.delta} vs baseline`);
|
|
1076
|
+
lines.push("");
|
|
1077
|
+
}
|
|
1078
|
+
return lines.join("\n");
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
// src/commands/report.ts
|
|
1082
|
+
var reportCommand = new Command4("report").description("Generate a drift report from a scan artifact").option("--in <file>", "Input artifact file", ".vibgrate/scan_result.json").option("--format <format>", "Output format (md|text|json)", "text").action(async (opts) => {
|
|
1083
|
+
const artifactPath = path4.resolve(opts.in);
|
|
61
1084
|
if (!await pathExists(artifactPath)) {
|
|
62
|
-
console.error(
|
|
63
|
-
console.error(
|
|
1085
|
+
console.error(chalk5.red(`Artifact not found: ${artifactPath}`));
|
|
1086
|
+
console.error(chalk5.dim('Run "vibgrate scan" first to generate a scan artifact.'));
|
|
64
1087
|
process.exit(1);
|
|
65
1088
|
}
|
|
66
1089
|
const artifact = await readJsonFile(artifactPath);
|
|
@@ -78,20 +1101,370 @@ var reportCommand = new Command2("report").description("Generate a drift report
|
|
|
78
1101
|
}
|
|
79
1102
|
});
|
|
80
1103
|
|
|
1104
|
+
// src/commands/push.ts
|
|
1105
|
+
import * as crypto2 from "crypto";
|
|
1106
|
+
import * as path5 from "path";
|
|
1107
|
+
import { Command as Command5 } from "commander";
|
|
1108
|
+
import chalk6 from "chalk";
|
|
1109
|
+
|
|
1110
|
+
// src/utils/compact-artifact.ts
|
|
1111
|
+
import * as zlib from "zlib";
|
|
1112
|
+
import { promisify } from "util";
|
|
1113
|
+
|
|
1114
|
+
// src/utils/compact-evidence.ts
|
|
1115
|
+
var CATEGORY_PATTERNS = [
|
|
1116
|
+
{ category: "pricing", pattern: /price|pricing|billing|subscri|trial|credit|plan|tier|upgrade|premium|pro|enterprise/i },
|
|
1117
|
+
{ category: "auth", pattern: /sign[- ]?in|sign[- ]?up|log[- ]?in|log[- ]?out|auth|sso|oauth|password|register|invite|onboard/i },
|
|
1118
|
+
{ category: "dashboard", pattern: /dashboard|overview|home|main|summary|stats/i },
|
|
1119
|
+
{ category: "settings", pattern: /setting|config|preference|option|profile|account/i },
|
|
1120
|
+
{ category: "users", pattern: /user|member|team|role|permission|access|admin|owner/i },
|
|
1121
|
+
{ category: "integrations", pattern: /integrat|connect|webhook|api[- ]?key|sync|import|export/i },
|
|
1122
|
+
{ category: "reports", pattern: /report|analy|metric|chart|graph|insight|track/i },
|
|
1123
|
+
{ category: "workflows", pattern: /workflow|automat|schedule|trigger|action|job|task|pipeline/i },
|
|
1124
|
+
{ category: "projects", pattern: /project|workspace|organization|folder|repo/i },
|
|
1125
|
+
{ category: "navigation", pattern: /menu|nav|sidebar|header|footer|breadcrumb/i }
|
|
1126
|
+
];
|
|
1127
|
+
function compactUiPurpose(result, maxSamplesPerCategory = 3) {
|
|
1128
|
+
const evidence = result.topEvidence;
|
|
1129
|
+
const dependencies = evidence.filter((e) => e.kind === "dependency").map((e) => e.value).slice(0, 10);
|
|
1130
|
+
const routes = dedupeRoutes(
|
|
1131
|
+
evidence.filter((e) => e.kind === "route").map((e) => e.value)
|
|
1132
|
+
).slice(0, 15);
|
|
1133
|
+
const textEvidence = evidence.filter(
|
|
1134
|
+
(e) => e.kind !== "dependency" && e.kind !== "route" && e.kind !== "feature_flag"
|
|
1135
|
+
);
|
|
1136
|
+
const byCategory = /* @__PURE__ */ new Map();
|
|
1137
|
+
const categoryCounts = {};
|
|
1138
|
+
for (const item of textEvidence) {
|
|
1139
|
+
const category = categorize(item.value);
|
|
1140
|
+
if (!byCategory.has(category)) {
|
|
1141
|
+
byCategory.set(category, /* @__PURE__ */ new Set());
|
|
1142
|
+
}
|
|
1143
|
+
const normalized = normalizeValue(item.value);
|
|
1144
|
+
if (normalized.length >= 3) {
|
|
1145
|
+
byCategory.get(category).add(normalized);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
const samples = [];
|
|
1149
|
+
for (const [category, values] of byCategory) {
|
|
1150
|
+
const deduped = dedupeStrings([...values]);
|
|
1151
|
+
categoryCounts[category] = deduped.length;
|
|
1152
|
+
for (const value of deduped.slice(0, maxSamplesPerCategory)) {
|
|
1153
|
+
samples.push({ kind: "text", value, category });
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
const featureFlags = evidence.filter((e) => e.kind === "feature_flag");
|
|
1157
|
+
if (featureFlags.length > 0) {
|
|
1158
|
+
categoryCounts["feature_flags"] = featureFlags.length;
|
|
1159
|
+
samples.push({ kind: "feature_flag", value: "feature flags detected", category: "feature_flags" });
|
|
1160
|
+
}
|
|
1161
|
+
return {
|
|
1162
|
+
samples,
|
|
1163
|
+
categoryCounts,
|
|
1164
|
+
originalCount: evidence.length,
|
|
1165
|
+
dependencies,
|
|
1166
|
+
routes,
|
|
1167
|
+
detectedFrameworks: result.detectedFrameworks
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
function categorize(value) {
|
|
1171
|
+
for (const { category, pattern } of CATEGORY_PATTERNS) {
|
|
1172
|
+
if (pattern.test(value)) return category;
|
|
1173
|
+
}
|
|
1174
|
+
return "general";
|
|
1175
|
+
}
|
|
1176
|
+
function normalizeValue(value) {
|
|
1177
|
+
return value.toLowerCase().replace(/[^a-z0-9\s-]/g, " ").replace(/\s+/g, " ").trim().slice(0, 60);
|
|
1178
|
+
}
|
|
1179
|
+
function dedupeStrings(values) {
|
|
1180
|
+
const sorted = values.sort((a, b) => b.length - a.length);
|
|
1181
|
+
const kept = [];
|
|
1182
|
+
for (const value of sorted) {
|
|
1183
|
+
const isDupe = kept.some((k) => {
|
|
1184
|
+
const stem = value.slice(0, 6);
|
|
1185
|
+
return k.startsWith(stem) || k.includes(value) || value.includes(k);
|
|
1186
|
+
});
|
|
1187
|
+
if (!isDupe) {
|
|
1188
|
+
kept.push(value);
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
return kept;
|
|
1192
|
+
}
|
|
1193
|
+
function dedupeRoutes(routes) {
|
|
1194
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1195
|
+
const result = [];
|
|
1196
|
+
for (const route of routes) {
|
|
1197
|
+
const normalized = route.replace(/:[a-z_]+/gi, ":param").replace(/\[\[*\.*\.*[a-z_]+\]*\]/gi, ":param").replace(/\/+$/, "").toLowerCase();
|
|
1198
|
+
if (!seen.has(normalized)) {
|
|
1199
|
+
seen.add(normalized);
|
|
1200
|
+
result.push(route);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
return result;
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/utils/compact-artifact.ts
|
|
1207
|
+
var gzip2 = promisify(zlib.gzip);
|
|
1208
|
+
var MAX_ITEMS = 50;
|
|
1209
|
+
function extractName(entry) {
|
|
1210
|
+
const match = entry.match(/^(.+?)\s*\(/);
|
|
1211
|
+
return match ? match[1].trim() : entry.trim();
|
|
1212
|
+
}
|
|
1213
|
+
function compactDataStores(result) {
|
|
1214
|
+
return {
|
|
1215
|
+
databaseTechnologies: result.databaseTechnologies.slice(0, 10),
|
|
1216
|
+
connectionStrings: [],
|
|
1217
|
+
// Don't include connection strings in upload
|
|
1218
|
+
connectionPoolSettings: result.connectionPoolSettings.slice(0, MAX_ITEMS),
|
|
1219
|
+
replicationSettings: result.replicationSettings.slice(0, 20),
|
|
1220
|
+
readReplicaSettings: result.readReplicaSettings.slice(0, 20),
|
|
1221
|
+
failoverSettings: result.failoverSettings.slice(0, 20),
|
|
1222
|
+
collationAndEncoding: result.collationAndEncoding.slice(0, 20),
|
|
1223
|
+
queryTimeoutDefaults: result.queryTimeoutDefaults.slice(0, 20),
|
|
1224
|
+
manualIndexes: result.manualIndexes.map(extractName).slice(0, MAX_ITEMS),
|
|
1225
|
+
tables: result.tables.map(extractName).slice(0, MAX_ITEMS),
|
|
1226
|
+
views: result.views.map(extractName).slice(0, MAX_ITEMS),
|
|
1227
|
+
storedProcedures: result.storedProcedures.map(extractName).slice(0, MAX_ITEMS),
|
|
1228
|
+
triggers: result.triggers.map(extractName).slice(0, MAX_ITEMS),
|
|
1229
|
+
rowLevelSecurityPolicies: result.rowLevelSecurityPolicies.slice(0, 20),
|
|
1230
|
+
otherServices: result.otherServices.slice(0, 20)
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function compactApiSurface(result) {
|
|
1234
|
+
const seenProviders = /* @__PURE__ */ new Set();
|
|
1235
|
+
const uniqueIntegrations = result.integrations.filter((i) => {
|
|
1236
|
+
const domain = i.provider.split(":")[0];
|
|
1237
|
+
if (seenProviders.has(domain)) return false;
|
|
1238
|
+
seenProviders.add(domain);
|
|
1239
|
+
return true;
|
|
1240
|
+
}).slice(0, MAX_ITEMS).map((i) => ({
|
|
1241
|
+
provider: i.provider,
|
|
1242
|
+
endpoint: "",
|
|
1243
|
+
// Don't include full endpoints
|
|
1244
|
+
version: i.version,
|
|
1245
|
+
parameters: [],
|
|
1246
|
+
// Don't include params
|
|
1247
|
+
configOptions: [],
|
|
1248
|
+
authHints: [],
|
|
1249
|
+
files: []
|
|
1250
|
+
// Don't include file paths
|
|
1251
|
+
}));
|
|
1252
|
+
return {
|
|
1253
|
+
integrations: uniqueIntegrations,
|
|
1254
|
+
openApiSpecifications: result.openApiSpecifications.slice(0, 10),
|
|
1255
|
+
webhookUrls: result.webhookUrls.slice(0, 20),
|
|
1256
|
+
callbackEndpoints: result.callbackEndpoints.slice(0, 20),
|
|
1257
|
+
apiVersionPins: result.apiVersionPins.slice(0, 20),
|
|
1258
|
+
tokenExpirationPolicies: result.tokenExpirationPolicies.slice(0, 20),
|
|
1259
|
+
rateLimitOverrides: result.rateLimitOverrides.slice(0, 20),
|
|
1260
|
+
customHeaders: result.customHeaders.slice(0, 20),
|
|
1261
|
+
corsPolicies: result.corsPolicies.slice(0, 20),
|
|
1262
|
+
oauthScopes: result.oauthScopes.slice(0, 20),
|
|
1263
|
+
apiTokens: []
|
|
1264
|
+
// Don't include token references
|
|
1265
|
+
};
|
|
1266
|
+
}
|
|
1267
|
+
function compactAssetBranding(result) {
|
|
1268
|
+
return {
|
|
1269
|
+
faviconFiles: result.faviconFiles.slice(0, 1),
|
|
1270
|
+
productLogos: []
|
|
1271
|
+
// Don't include logos
|
|
1272
|
+
};
|
|
1273
|
+
}
|
|
1274
|
+
function prepareArtifactForUpload(artifact) {
|
|
1275
|
+
const compacted = { ...artifact };
|
|
1276
|
+
if (compacted.extended) {
|
|
1277
|
+
const ext = { ...compacted.extended };
|
|
1278
|
+
if (ext.dataStores) {
|
|
1279
|
+
ext.dataStores = compactDataStores(ext.dataStores);
|
|
1280
|
+
}
|
|
1281
|
+
if (ext.apiSurface) {
|
|
1282
|
+
ext.apiSurface = compactApiSurface(ext.apiSurface);
|
|
1283
|
+
}
|
|
1284
|
+
if (ext.assetBranding) {
|
|
1285
|
+
ext.assetBranding = compactAssetBranding(ext.assetBranding);
|
|
1286
|
+
}
|
|
1287
|
+
if (ext.uiPurpose) {
|
|
1288
|
+
const compactedUi = compactUiPurpose(ext.uiPurpose);
|
|
1289
|
+
ext.uiPurpose = {
|
|
1290
|
+
enabled: ext.uiPurpose.enabled,
|
|
1291
|
+
detectedFrameworks: compactedUi.detectedFrameworks,
|
|
1292
|
+
evidenceCount: compactedUi.originalCount,
|
|
1293
|
+
capped: ext.uiPurpose.capped,
|
|
1294
|
+
topEvidence: [],
|
|
1295
|
+
// Clear full evidence
|
|
1296
|
+
unknownSignals: [],
|
|
1297
|
+
// Add compacted data under extended properties
|
|
1298
|
+
...{ compacted: compactedUi }
|
|
1299
|
+
};
|
|
1300
|
+
}
|
|
1301
|
+
if (ext.runtimeConfiguration) {
|
|
1302
|
+
ext.runtimeConfiguration = {
|
|
1303
|
+
...ext.runtimeConfiguration,
|
|
1304
|
+
environmentVariables: ext.runtimeConfiguration.environmentVariables.slice(0, 100),
|
|
1305
|
+
hiddenConfigFiles: ext.runtimeConfiguration.hiddenConfigFiles.slice(0, MAX_ITEMS),
|
|
1306
|
+
startupArguments: ext.runtimeConfiguration.startupArguments.slice(0, 100)
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
if (ext.operationalResilience) {
|
|
1310
|
+
const ops = ext.operationalResilience;
|
|
1311
|
+
ext.operationalResilience = {
|
|
1312
|
+
implicitTimeouts: ops.implicitTimeouts.slice(0, 30),
|
|
1313
|
+
defaultPaginationSize: ops.defaultPaginationSize.slice(0, 30),
|
|
1314
|
+
implicitRetryLogic: ops.implicitRetryLogic.slice(0, 30),
|
|
1315
|
+
defaultLocale: ops.defaultLocale.slice(0, 20),
|
|
1316
|
+
defaultCurrency: ops.defaultCurrency.slice(0, 20),
|
|
1317
|
+
implicitTimezone: ops.implicitTimezone.slice(0, 20),
|
|
1318
|
+
defaultCharacterEncoding: ops.defaultCharacterEncoding.slice(0, 20),
|
|
1319
|
+
sessionStores: ops.sessionStores.slice(0, 20),
|
|
1320
|
+
distributedLocks: ops.distributedLocks.slice(0, 20),
|
|
1321
|
+
jobSchedulers: ops.jobSchedulers.slice(0, 30),
|
|
1322
|
+
idempotencyKeys: ops.idempotencyKeys.slice(0, 20),
|
|
1323
|
+
rateLimitingCounters: ops.rateLimitingCounters.slice(0, 20),
|
|
1324
|
+
circuitBreakerState: ops.circuitBreakerState.slice(0, 20),
|
|
1325
|
+
abTestToggles: ops.abTestToggles.slice(0, 20),
|
|
1326
|
+
regionalEnablementRules: ops.regionalEnablementRules.slice(0, 20),
|
|
1327
|
+
betaAccessGroups: ops.betaAccessGroups.slice(0, 20),
|
|
1328
|
+
licensingEnforcementLogic: ops.licensingEnforcementLogic.slice(0, 20),
|
|
1329
|
+
killSwitches: ops.killSwitches.slice(0, 20),
|
|
1330
|
+
connectorRetryLogic: ops.connectorRetryLogic.slice(0, 20),
|
|
1331
|
+
apiPollingIntervals: ops.apiPollingIntervals.slice(0, 20),
|
|
1332
|
+
fieldMappings: ops.fieldMappings.slice(0, 20),
|
|
1333
|
+
schemaRegistryRules: ops.schemaRegistryRules.slice(0, 20),
|
|
1334
|
+
deadLetterQueueBehavior: ops.deadLetterQueueBehavior.slice(0, 20),
|
|
1335
|
+
dataMaskingRules: ops.dataMaskingRules.slice(0, 20),
|
|
1336
|
+
transformationLogic: ops.transformationLogic.slice(0, 20),
|
|
1337
|
+
timezoneHandling: ops.timezoneHandling.slice(0, 20),
|
|
1338
|
+
encryptionSettings: ops.encryptionSettings.slice(0, 30),
|
|
1339
|
+
hardcodedSecretSignals: ops.hardcodedSecretSignals.slice(0, 20)
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
if (ext.dependencyGraph) {
|
|
1343
|
+
ext.dependencyGraph = {
|
|
1344
|
+
...ext.dependencyGraph,
|
|
1345
|
+
phantomDependencies: ext.dependencyGraph.phantomDependencies.slice(0, MAX_ITEMS),
|
|
1346
|
+
phantomDependencyDetails: ext.dependencyGraph.phantomDependencyDetails?.slice(0, MAX_ITEMS),
|
|
1347
|
+
duplicatedPackages: ext.dependencyGraph.duplicatedPackages.slice(0, MAX_ITEMS)
|
|
1348
|
+
};
|
|
1349
|
+
}
|
|
1350
|
+
compacted.extended = ext;
|
|
1351
|
+
}
|
|
1352
|
+
return compacted;
|
|
1353
|
+
}
|
|
1354
|
+
async function compressArtifact(artifact) {
|
|
1355
|
+
const json = JSON.stringify(artifact);
|
|
1356
|
+
return gzip2(json, { level: 9 });
|
|
1357
|
+
}
|
|
1358
|
+
async function prepareCompressedUpload2(artifact) {
|
|
1359
|
+
const compacted = prepareArtifactForUpload(artifact);
|
|
1360
|
+
const compressed = await compressArtifact(compacted);
|
|
1361
|
+
return { body: compressed, contentEncoding: "gzip" };
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
// src/commands/push.ts
|
|
1365
|
+
function parseDsn2(dsn) {
|
|
1366
|
+
const cleaned = dsn.replace(/[\x00-\x1F\x7F\uFEFF\u200B-\u200D\u2060]/g, "").trim();
|
|
1367
|
+
const match = cleaned.match(/^vibgrate\+(https?):?\/\/([^:]+):([^@]+)@([^/]+)\/(.+)$/);
|
|
1368
|
+
if (!match) return null;
|
|
1369
|
+
return {
|
|
1370
|
+
scheme: match[1],
|
|
1371
|
+
keyId: match[2],
|
|
1372
|
+
secret: match[3],
|
|
1373
|
+
host: match[4],
|
|
1374
|
+
workspaceId: match[5]
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
function computeHmac(body, secret) {
|
|
1378
|
+
return crypto2.createHmac("sha256", secret).update(body).digest("base64");
|
|
1379
|
+
}
|
|
1380
|
+
var pushCommand = new Command5("push").description("Push scan results to Vibgrate API").option("--dsn <dsn>", "DSN token (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region (us, eu)").option("--file <file>", "Scan artifact file", ".vibgrate/scan_result.json").option("--strict", "Fail on upload errors").action(async (opts) => {
|
|
1381
|
+
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
1382
|
+
if (!dsn) {
|
|
1383
|
+
console.error(chalk6.red("No DSN provided."));
|
|
1384
|
+
console.error(chalk6.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
|
|
1385
|
+
if (opts.strict) process.exit(1);
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
const parsed = parseDsn2(dsn);
|
|
1389
|
+
if (!parsed) {
|
|
1390
|
+
console.error(chalk6.red("Invalid DSN format."));
|
|
1391
|
+
console.error(chalk6.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
|
|
1392
|
+
if (opts.strict) process.exit(1);
|
|
1393
|
+
return;
|
|
1394
|
+
}
|
|
1395
|
+
const filePath = path5.resolve(opts.file);
|
|
1396
|
+
if (!await pathExists(filePath)) {
|
|
1397
|
+
console.error(chalk6.red(`Scan artifact not found: ${filePath}`));
|
|
1398
|
+
console.error(chalk6.dim('Run "vibgrate scan" first.'));
|
|
1399
|
+
if (opts.strict) process.exit(1);
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
const artifact = await readJsonFile(filePath);
|
|
1403
|
+
const { body, contentEncoding } = await prepareCompressedUpload2(artifact);
|
|
1404
|
+
const timestamp = String(Date.now());
|
|
1405
|
+
let host = parsed.host;
|
|
1406
|
+
if (opts.region) {
|
|
1407
|
+
try {
|
|
1408
|
+
host = resolveIngestHost(opts.region);
|
|
1409
|
+
} catch (e) {
|
|
1410
|
+
console.error(chalk6.red(e instanceof Error ? e.message : String(e)));
|
|
1411
|
+
if (opts.strict) process.exit(1);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
|
|
1416
|
+
const originalSize = JSON.stringify(artifact).length;
|
|
1417
|
+
const compressedSize = body.length;
|
|
1418
|
+
const ratio = ((1 - compressedSize / originalSize) * 100).toFixed(0);
|
|
1419
|
+
console.log(chalk6.dim(`Uploading to ${host}... (${(compressedSize / 1024).toFixed(0)} KB, ${ratio}% smaller)`));
|
|
1420
|
+
try {
|
|
1421
|
+
const response = await fetch(url, {
|
|
1422
|
+
method: "POST",
|
|
1423
|
+
headers: {
|
|
1424
|
+
"Content-Type": "application/json",
|
|
1425
|
+
"Content-Encoding": contentEncoding,
|
|
1426
|
+
"X-Vibgrate-Timestamp": timestamp,
|
|
1427
|
+
"Authorization": `VibgrateDSN ${parsed.keyId}:${parsed.secret}`,
|
|
1428
|
+
"Connection": "close"
|
|
1429
|
+
// Prevent keep-alive delays on exit
|
|
1430
|
+
},
|
|
1431
|
+
body
|
|
1432
|
+
});
|
|
1433
|
+
if (!response.ok) {
|
|
1434
|
+
const text = await response.text();
|
|
1435
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
1436
|
+
}
|
|
1437
|
+
const result = await response.json();
|
|
1438
|
+
console.log(chalk6.green("\u2714") + ` Scan queued for processing (${result.ingestId ?? "ok"})`);
|
|
1439
|
+
console.log();
|
|
1440
|
+
console.log(chalk6.dim("Processing continues in the background. Results available shortly."));
|
|
1441
|
+
console.log();
|
|
1442
|
+
if (result.ingestId) {
|
|
1443
|
+
const dashHost = host.includes("eu.") ? "dash.vibgrate.eu" : "dash.vibgrate.com";
|
|
1444
|
+
const reportUrl = `https://${dashHost}/${parsed.workspaceId}/scan/${result.ingestId}`;
|
|
1445
|
+
console.log(chalk6.dim("View report: ") + chalk6.underline(reportUrl));
|
|
1446
|
+
}
|
|
1447
|
+
} catch (e) {
|
|
1448
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1449
|
+
console.error(chalk6.red(`Upload failed: ${msg}`));
|
|
1450
|
+
if (opts.strict) process.exit(1);
|
|
1451
|
+
}
|
|
1452
|
+
});
|
|
1453
|
+
|
|
81
1454
|
// src/commands/update.ts
|
|
82
1455
|
import { execSync } from "child_process";
|
|
83
|
-
import * as
|
|
84
|
-
import { Command as
|
|
85
|
-
import
|
|
1456
|
+
import * as path7 from "path";
|
|
1457
|
+
import { Command as Command6 } from "commander";
|
|
1458
|
+
import chalk7 from "chalk";
|
|
86
1459
|
|
|
87
1460
|
// src/utils/update-check.ts
|
|
1461
|
+
var import_semver = __toESM(require_semver(), 1);
|
|
88
1462
|
import * as fs from "fs/promises";
|
|
89
|
-
import * as
|
|
1463
|
+
import * as path6 from "path";
|
|
90
1464
|
import * as os from "os";
|
|
91
|
-
import semver from "semver";
|
|
92
1465
|
var REGISTRY_URL = "https://registry.npmjs.org/@vibgrate%2fcli/latest";
|
|
93
|
-
var CACHE_DIR =
|
|
94
|
-
var CACHE_FILE =
|
|
1466
|
+
var CACHE_DIR = path6.join(os.homedir(), ".vibgrate");
|
|
1467
|
+
var CACHE_FILE = path6.join(CACHE_DIR, "update-check.json");
|
|
95
1468
|
var CHECK_INTERVAL_MS = 12 * 60 * 60 * 1e3;
|
|
96
1469
|
async function checkForUpdate() {
|
|
97
1470
|
try {
|
|
@@ -100,7 +1473,7 @@ async function checkForUpdate() {
|
|
|
100
1473
|
return {
|
|
101
1474
|
current: VERSION,
|
|
102
1475
|
latest: cached.latest,
|
|
103
|
-
updateAvailable:
|
|
1476
|
+
updateAvailable: import_semver.default.gt(cached.latest, VERSION)
|
|
104
1477
|
};
|
|
105
1478
|
}
|
|
106
1479
|
const controller = new AbortController();
|
|
@@ -118,12 +1491,12 @@ async function checkForUpdate() {
|
|
|
118
1491
|
if (!response.ok) return null;
|
|
119
1492
|
const data = await response.json();
|
|
120
1493
|
const latest = data.version;
|
|
121
|
-
if (!latest || !
|
|
1494
|
+
if (!latest || !import_semver.default.valid(latest)) return null;
|
|
122
1495
|
await writeCache({ latest, checkedAt: Date.now() });
|
|
123
1496
|
return {
|
|
124
1497
|
current: VERSION,
|
|
125
1498
|
latest,
|
|
126
|
-
updateAvailable:
|
|
1499
|
+
updateAvailable: import_semver.default.gt(latest, VERSION)
|
|
127
1500
|
};
|
|
128
1501
|
} catch {
|
|
129
1502
|
return null;
|
|
@@ -146,7 +1519,7 @@ async function fetchLatestVersion() {
|
|
|
146
1519
|
if (!response.ok) return null;
|
|
147
1520
|
const data = await response.json();
|
|
148
1521
|
const latest = data.version;
|
|
149
|
-
if (!latest || !
|
|
1522
|
+
if (!latest || !import_semver.default.valid(latest)) return null;
|
|
150
1523
|
await writeCache({ latest, checkedAt: Date.now() });
|
|
151
1524
|
return latest;
|
|
152
1525
|
} catch {
|
|
@@ -184,8 +1557,8 @@ function detectGlobalInstall() {
|
|
|
184
1557
|
}
|
|
185
1558
|
return null;
|
|
186
1559
|
}
|
|
187
|
-
function getGlobalUpdateCommand(pm,
|
|
188
|
-
const spec = `${
|
|
1560
|
+
function getGlobalUpdateCommand(pm, pkg2, version) {
|
|
1561
|
+
const spec = `${pkg2}@${version}`;
|
|
189
1562
|
switch (pm) {
|
|
190
1563
|
case "pnpm":
|
|
191
1564
|
return `pnpm add -g ${spec}`;
|
|
@@ -199,13 +1572,13 @@ function getGlobalUpdateCommand(pm, pkg, version) {
|
|
|
199
1572
|
}
|
|
200
1573
|
}
|
|
201
1574
|
async function detectPackageManager(cwd) {
|
|
202
|
-
if (await pathExists(
|
|
203
|
-
if (await pathExists(
|
|
204
|
-
if (await pathExists(
|
|
1575
|
+
if (await pathExists(path7.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
1576
|
+
if (await pathExists(path7.join(cwd, "bun.lockb"))) return "bun";
|
|
1577
|
+
if (await pathExists(path7.join(cwd, "yarn.lock"))) return "yarn";
|
|
205
1578
|
return "npm";
|
|
206
1579
|
}
|
|
207
|
-
function getInstallCommand(pm,
|
|
208
|
-
const spec = `${
|
|
1580
|
+
function getInstallCommand(pm, pkg2, version, isDev) {
|
|
1581
|
+
const spec = `${pkg2}@${version}`;
|
|
209
1582
|
switch (pm) {
|
|
210
1583
|
case "pnpm":
|
|
211
1584
|
return isDev ? `pnpm add -D ${spec}` : `pnpm add ${spec}`;
|
|
@@ -220,30 +1593,30 @@ function getInstallCommand(pm, pkg, version, isDev) {
|
|
|
220
1593
|
}
|
|
221
1594
|
async function isDevDependency(cwd) {
|
|
222
1595
|
try {
|
|
223
|
-
const pkgPath =
|
|
1596
|
+
const pkgPath = path7.join(cwd, "package.json");
|
|
224
1597
|
const raw = await (await import("fs/promises")).readFile(pkgPath, "utf-8");
|
|
225
|
-
const
|
|
226
|
-
return Boolean(
|
|
1598
|
+
const pkg2 = JSON.parse(raw);
|
|
1599
|
+
return Boolean(pkg2.devDependencies?.["@vibgrate/cli"]);
|
|
227
1600
|
} catch {
|
|
228
1601
|
return true;
|
|
229
1602
|
}
|
|
230
1603
|
}
|
|
231
|
-
var updateCommand = new
|
|
232
|
-
console.log(
|
|
233
|
-
console.log(
|
|
1604
|
+
var updateCommand = new Command6("update").description("Update vibgrate to the latest version").option("--check", "Only check for updates, do not install").option("--pm <manager>", "Package manager to use (npm, pnpm, yarn, bun)").option("--global", "Update global installation").action(async (opts) => {
|
|
1605
|
+
console.log(chalk7.dim(`Current version: ${VERSION}`));
|
|
1606
|
+
console.log(chalk7.dim("Checking npm registry..."));
|
|
234
1607
|
const latest = await fetchLatestVersion();
|
|
235
1608
|
if (!latest) {
|
|
236
|
-
console.error(
|
|
1609
|
+
console.error(chalk7.red("Could not reach the npm registry. Check your network connection."));
|
|
237
1610
|
process.exit(1);
|
|
238
1611
|
}
|
|
239
|
-
const semver2 = await import("semver");
|
|
1612
|
+
const semver2 = await import("./semver-JBJZTHUX.js");
|
|
240
1613
|
if (!semver2.gt(latest, VERSION)) {
|
|
241
|
-
console.log(
|
|
1614
|
+
console.log(chalk7.green("\u2714") + ` You are on the latest version (${VERSION}).`);
|
|
242
1615
|
return;
|
|
243
1616
|
}
|
|
244
|
-
console.log(
|
|
1617
|
+
console.log(chalk7.yellow(`Update available: ${VERSION} \u2192 ${latest}`));
|
|
245
1618
|
if (opts.check) {
|
|
246
|
-
console.log(
|
|
1619
|
+
console.log(chalk7.dim('Run "vibgrate update" to install.'));
|
|
247
1620
|
return;
|
|
248
1621
|
}
|
|
249
1622
|
const cwd = process.cwd();
|
|
@@ -253,26 +1626,26 @@ var updateCommand = new Command3("update").description("Update vibgrate to the l
|
|
|
253
1626
|
let cmd;
|
|
254
1627
|
if (isGlobal) {
|
|
255
1628
|
cmd = getGlobalUpdateCommand(pm, "@vibgrate/cli", latest);
|
|
256
|
-
console.log(
|
|
1629
|
+
console.log(chalk7.dim(`Updating global installation with ${pm}: ${cmd}`));
|
|
257
1630
|
} else {
|
|
258
1631
|
const isDev = await isDevDependency(cwd);
|
|
259
1632
|
cmd = getInstallCommand(pm, "@vibgrate/cli", latest, isDev);
|
|
260
|
-
console.log(
|
|
1633
|
+
console.log(chalk7.dim(`Using ${pm}: ${cmd}`));
|
|
261
1634
|
}
|
|
262
1635
|
try {
|
|
263
1636
|
execSync(cmd, { cwd, stdio: "inherit" });
|
|
264
|
-
console.log(
|
|
1637
|
+
console.log(chalk7.green("\u2714") + ` Updated to @vibgrate/cli@${latest}`);
|
|
265
1638
|
} catch {
|
|
266
|
-
console.error(
|
|
1639
|
+
console.error(chalk7.red(`Update failed. Run manually: ${cmd}`));
|
|
267
1640
|
process.exit(1);
|
|
268
1641
|
}
|
|
269
1642
|
});
|
|
270
1643
|
|
|
271
1644
|
// src/commands/sbom.ts
|
|
272
|
-
import * as
|
|
1645
|
+
import * as path8 from "path";
|
|
273
1646
|
import { randomUUID } from "crypto";
|
|
274
|
-
import { Command as
|
|
275
|
-
import
|
|
1647
|
+
import { Command as Command7 } from "commander";
|
|
1648
|
+
import chalk8 from "chalk";
|
|
276
1649
|
function flattenDependencies(artifact) {
|
|
277
1650
|
const rows = [];
|
|
278
1651
|
for (const project of artifact.projects) {
|
|
@@ -410,50 +1783,50 @@ function formatDeltaText(base, current) {
|
|
|
410
1783
|
return lines.join("\n");
|
|
411
1784
|
}
|
|
412
1785
|
async function readArtifactOrExit(filePath) {
|
|
413
|
-
const absolutePath =
|
|
1786
|
+
const absolutePath = path8.resolve(filePath);
|
|
414
1787
|
if (!await pathExists(absolutePath)) {
|
|
415
|
-
console.error(
|
|
1788
|
+
console.error(chalk8.red(`Artifact not found: ${absolutePath}`));
|
|
416
1789
|
process.exit(1);
|
|
417
1790
|
}
|
|
418
1791
|
return readJsonFile(absolutePath);
|
|
419
1792
|
}
|
|
420
|
-
var exportCommand = new
|
|
1793
|
+
var exportCommand = new Command7("export").description("Export scan artifact as SBOM").option("--in <file>", "Input artifact file", ".vibgrate/scan_result.json").option("--out <file>", "Output SBOM file").option("--format <format>", "SBOM format (cyclonedx|spdx)", "cyclonedx").action(async (opts) => {
|
|
421
1794
|
const artifact = await readArtifactOrExit(opts.in);
|
|
422
1795
|
const format = opts.format.toLowerCase();
|
|
423
1796
|
if (format !== "cyclonedx" && format !== "spdx") {
|
|
424
|
-
console.error(
|
|
1797
|
+
console.error(chalk8.red("Invalid SBOM format. Use cyclonedx or spdx."));
|
|
425
1798
|
process.exit(1);
|
|
426
1799
|
}
|
|
427
1800
|
const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
|
|
428
1801
|
const body = JSON.stringify(sbom, null, 2);
|
|
429
1802
|
if (opts.out) {
|
|
430
|
-
await writeTextFile(
|
|
431
|
-
console.log(
|
|
1803
|
+
await writeTextFile(path8.resolve(opts.out), body);
|
|
1804
|
+
console.log(chalk8.green("\u2714") + ` SBOM written to ${opts.out}`);
|
|
432
1805
|
} else {
|
|
433
1806
|
console.log(body);
|
|
434
1807
|
}
|
|
435
1808
|
});
|
|
436
|
-
var deltaCommand = new
|
|
1809
|
+
var deltaCommand = new Command7("delta").description("Show SBOM delta between two scan artifacts").requiredOption("--from <file>", "Baseline scan artifact path").requiredOption("--to <file>", "Current scan artifact path").option("--out <file>", "Write report to file").action(async (opts) => {
|
|
437
1810
|
const base = await readArtifactOrExit(opts.from);
|
|
438
1811
|
const current = await readArtifactOrExit(opts.to);
|
|
439
1812
|
const report = formatDeltaText(base, current);
|
|
440
1813
|
if (opts.out) {
|
|
441
|
-
await writeTextFile(
|
|
442
|
-
console.log(
|
|
1814
|
+
await writeTextFile(path8.resolve(opts.out), report);
|
|
1815
|
+
console.log(chalk8.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
|
|
443
1816
|
} else {
|
|
444
1817
|
console.log(report);
|
|
445
1818
|
}
|
|
446
1819
|
});
|
|
447
|
-
var sbomCommand = new
|
|
1820
|
+
var sbomCommand = new Command7("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
|
|
448
1821
|
|
|
449
1822
|
// src/commands/extract.ts
|
|
450
|
-
import * as
|
|
1823
|
+
import * as path9 from "path";
|
|
451
1824
|
import * as os2 from "os";
|
|
452
1825
|
import * as fs2 from "fs/promises";
|
|
453
1826
|
import { existsSync } from "fs";
|
|
454
1827
|
import { spawn, spawnSync } from "child_process";
|
|
455
|
-
import { Command as
|
|
456
|
-
import
|
|
1828
|
+
import { Command as Command8 } from "commander";
|
|
1829
|
+
import chalk9 from "chalk";
|
|
457
1830
|
var EXIT_SUCCESS = 0;
|
|
458
1831
|
var EXIT_SCHEMA_FAILURE = 1;
|
|
459
1832
|
var EXIT_PARSE_FAILURE = 2;
|
|
@@ -543,8 +1916,8 @@ async function detectLanguages(rootDir, includeTests) {
|
|
|
543
1916
|
}
|
|
544
1917
|
for (const entry of entries) {
|
|
545
1918
|
if (entry.name.startsWith(".")) continue;
|
|
546
|
-
const absPath =
|
|
547
|
-
const relPath =
|
|
1919
|
+
const absPath = path9.join(dir, entry.name);
|
|
1920
|
+
const relPath = path9.join(relBase, entry.name);
|
|
548
1921
|
if (entry.isDirectory()) {
|
|
549
1922
|
if (!SKIP_DIRS.has(entry.name)) {
|
|
550
1923
|
await walk(absPath, relPath);
|
|
@@ -553,7 +1926,7 @@ async function detectLanguages(rootDir, includeTests) {
|
|
|
553
1926
|
}
|
|
554
1927
|
if (!entry.isFile()) continue;
|
|
555
1928
|
if (!includeTests && isTestPath(relPath)) continue;
|
|
556
|
-
const ext =
|
|
1929
|
+
const ext = path9.extname(entry.name).toLowerCase();
|
|
557
1930
|
for (const [lang, exts] of Object.entries(LANGUAGE_EXTENSIONS)) {
|
|
558
1931
|
if (exts.has(ext)) {
|
|
559
1932
|
counts.set(lang, (counts.get(lang) ?? 0) + 1);
|
|
@@ -572,119 +1945,67 @@ function validateFactLine(line) {
|
|
|
572
1945
|
} catch {
|
|
573
1946
|
return { valid: false, error: `Invalid JSON: ${line.substring(0, 80)}...` };
|
|
574
1947
|
}
|
|
575
|
-
const
|
|
576
|
-
if (
|
|
1948
|
+
const obj = parsed;
|
|
1949
|
+
if (obj.factType === "Models" || obj.factType === "References") {
|
|
1950
|
+
if (obj.payload === void 0 || obj.payload === null) {
|
|
1951
|
+
return { valid: false, error: `Preamble ${obj.factType} missing payload` };
|
|
1952
|
+
}
|
|
1953
|
+
return { valid: true };
|
|
1954
|
+
}
|
|
1955
|
+
if (typeof obj.factId !== "string" || !obj.factId) {
|
|
577
1956
|
return { valid: false, error: "Missing or invalid factId" };
|
|
578
1957
|
}
|
|
579
|
-
if (typeof
|
|
1958
|
+
if (typeof obj.m === "string") {
|
|
1959
|
+
if (obj.payload === void 0 || obj.payload === null) {
|
|
1960
|
+
return { valid: false, error: "Compressed fact missing payload" };
|
|
1961
|
+
}
|
|
1962
|
+
return { valid: true };
|
|
1963
|
+
}
|
|
1964
|
+
if (typeof obj.factType !== "string" || !obj.factType) {
|
|
580
1965
|
return { valid: false, error: "Missing or invalid factType" };
|
|
581
1966
|
}
|
|
582
|
-
if (typeof
|
|
1967
|
+
if (typeof obj.language !== "string") {
|
|
583
1968
|
return { valid: false, error: "Missing or invalid language" };
|
|
584
1969
|
}
|
|
585
|
-
if (typeof
|
|
1970
|
+
if (typeof obj.scanner !== "string") {
|
|
586
1971
|
return { valid: false, error: "Missing or invalid scanner" };
|
|
587
1972
|
}
|
|
588
|
-
if (typeof
|
|
1973
|
+
if (typeof obj.scannerVersion !== "string") {
|
|
589
1974
|
return { valid: false, error: "Missing or invalid scannerVersion" };
|
|
590
1975
|
}
|
|
591
|
-
if (typeof
|
|
1976
|
+
if (typeof obj.emittedAt !== "string") {
|
|
592
1977
|
return { valid: false, error: "Missing or invalid emittedAt" };
|
|
593
1978
|
}
|
|
594
|
-
if (
|
|
1979
|
+
if (obj.payload === void 0 || obj.payload === null) {
|
|
595
1980
|
return { valid: false, error: "Missing payload" };
|
|
596
1981
|
}
|
|
597
|
-
return { valid: true
|
|
1982
|
+
return { valid: true };
|
|
598
1983
|
}
|
|
599
|
-
function
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
let refsLine = null;
|
|
603
|
-
const contentLines = [];
|
|
1984
|
+
function splitHcsOutput(lines) {
|
|
1985
|
+
const preamble = [];
|
|
1986
|
+
const facts = [];
|
|
604
1987
|
for (const line of lines) {
|
|
605
1988
|
let parsed;
|
|
606
1989
|
try {
|
|
607
1990
|
parsed = JSON.parse(line);
|
|
608
1991
|
} catch {
|
|
609
|
-
|
|
1992
|
+
facts.push(line);
|
|
610
1993
|
continue;
|
|
611
1994
|
}
|
|
612
1995
|
const obj = parsed;
|
|
613
|
-
if (obj.factType === "Models"
|
|
614
|
-
|
|
615
|
-
} else if (obj.factType === "References" && !refsLine) {
|
|
616
|
-
refsLine = obj;
|
|
1996
|
+
if (obj.factType === "Models" || obj.factType === "References") {
|
|
1997
|
+
preamble.push(line);
|
|
617
1998
|
} else {
|
|
618
|
-
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
if (!modelsLine) return lines;
|
|
622
|
-
const emittedAt = typeof modelsLine.emittedAt === "string" ? modelsLine.emittedAt : (/* @__PURE__ */ new Date()).toISOString();
|
|
623
|
-
const modelsPayload = modelsLine.payload;
|
|
624
|
-
const modelMap = /* @__PURE__ */ new Map();
|
|
625
|
-
for (const m of modelsPayload?.models ?? []) {
|
|
626
|
-
if (m.id) modelMap.set(m.id, m);
|
|
627
|
-
}
|
|
628
|
-
const refsPayload = refsLine?.payload;
|
|
629
|
-
const refMap = /* @__PURE__ */ new Map();
|
|
630
|
-
for (const r of refsPayload?.references ?? []) {
|
|
631
|
-
if (r.id && r.value) refMap.set(r.id, r.value);
|
|
632
|
-
}
|
|
633
|
-
const sortedRefs = [...refMap.entries()].sort((a, b) => b[0].length - a[0].length);
|
|
634
|
-
function expandRefs(text) {
|
|
635
|
-
for (const [token, value] of sortedRefs) {
|
|
636
|
-
text = text.split(token).join(value);
|
|
1999
|
+
facts.push(line);
|
|
637
2000
|
}
|
|
638
|
-
return text;
|
|
639
2001
|
}
|
|
640
|
-
|
|
641
|
-
for (const line of contentLines) {
|
|
642
|
-
let parsed;
|
|
643
|
-
try {
|
|
644
|
-
parsed = JSON.parse(line);
|
|
645
|
-
} catch {
|
|
646
|
-
expanded.push(line);
|
|
647
|
-
continue;
|
|
648
|
-
}
|
|
649
|
-
const obj = parsed;
|
|
650
|
-
if (typeof obj.factType === "string") {
|
|
651
|
-
expanded.push(line);
|
|
652
|
-
continue;
|
|
653
|
-
}
|
|
654
|
-
if (typeof obj.factId !== "string" || typeof obj.m !== "string") {
|
|
655
|
-
expanded.push(line);
|
|
656
|
-
continue;
|
|
657
|
-
}
|
|
658
|
-
const model = modelMap.get(obj.m);
|
|
659
|
-
if (!model) {
|
|
660
|
-
expanded.push(line);
|
|
661
|
-
continue;
|
|
662
|
-
}
|
|
663
|
-
const payloadJson = expandRefs(JSON.stringify(obj.payload));
|
|
664
|
-
let payload;
|
|
665
|
-
try {
|
|
666
|
-
payload = JSON.parse(payloadJson);
|
|
667
|
-
} catch {
|
|
668
|
-
payload = obj.payload;
|
|
669
|
-
}
|
|
670
|
-
const fullEnvelope = {
|
|
671
|
-
factId: obj.factId,
|
|
672
|
-
factType: model.factType ?? "",
|
|
673
|
-
language: model.language ?? "",
|
|
674
|
-
scanner: model.scanner ?? "",
|
|
675
|
-
scannerVersion: model.scannerVersion ?? "",
|
|
676
|
-
emittedAt: model.emittedAt ?? emittedAt,
|
|
677
|
-
payload
|
|
678
|
-
};
|
|
679
|
-
expanded.push(JSON.stringify(fullEnvelope));
|
|
680
|
-
}
|
|
681
|
-
return expanded;
|
|
2002
|
+
return { preamble, facts };
|
|
682
2003
|
}
|
|
683
2004
|
function resolveHcsWorkerBin() {
|
|
684
|
-
const base = import.meta.dirname ??
|
|
685
|
-
const bundledPath =
|
|
2005
|
+
const base = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
|
|
2006
|
+
const bundledPath = path9.resolve(base, "..", "dist", "hcs-worker.js");
|
|
686
2007
|
if (existsSync(bundledPath)) return bundledPath;
|
|
687
|
-
const siblingPath =
|
|
2008
|
+
const siblingPath = path9.resolve(base, "hcs-worker.js");
|
|
688
2009
|
if (existsSync(siblingPath)) return siblingPath;
|
|
689
2010
|
try {
|
|
690
2011
|
const resolved = import.meta.resolve("@vibgrate/hcs-node-worker");
|
|
@@ -692,9 +2013,9 @@ function resolveHcsWorkerBin() {
|
|
|
692
2013
|
if (existsSync(resolvedPath)) return resolvedPath;
|
|
693
2014
|
} catch {
|
|
694
2015
|
}
|
|
695
|
-
const monorepoPath =
|
|
2016
|
+
const monorepoPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "dist", "main.js");
|
|
696
2017
|
if (existsSync(monorepoPath)) return monorepoPath;
|
|
697
|
-
const devPath =
|
|
2018
|
+
const devPath = path9.resolve(base, "..", "..", "..", "vibgrate-hcs", "node", "src", "main.ts");
|
|
698
2019
|
if (existsSync(devPath)) return devPath;
|
|
699
2020
|
throw new Error(
|
|
700
2021
|
'Cannot locate HCS worker. Run "pnpm build" in the CLI package to bundle the worker into dist/.'
|
|
@@ -797,7 +2118,7 @@ async function runNodeWorker(rootDir, language, opts) {
|
|
|
797
2118
|
args.push("--cobol-copybook-paths", opts.copybookPaths);
|
|
798
2119
|
}
|
|
799
2120
|
}
|
|
800
|
-
return new Promise((
|
|
2121
|
+
return new Promise((resolve9) => {
|
|
801
2122
|
const facts = [];
|
|
802
2123
|
const errors = [];
|
|
803
2124
|
let stdoutBuf = "";
|
|
@@ -834,7 +2155,7 @@ async function runNodeWorker(rootDir, language, opts) {
|
|
|
834
2155
|
continue;
|
|
835
2156
|
}
|
|
836
2157
|
if (opts.verbose) {
|
|
837
|
-
process.stderr.write(
|
|
2158
|
+
process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
|
|
838
2159
|
`));
|
|
839
2160
|
}
|
|
840
2161
|
if (trimmed.startsWith("[error]")) {
|
|
@@ -848,15 +2169,15 @@ async function runNodeWorker(rootDir, language, opts) {
|
|
|
848
2169
|
facts.push(stdoutBuf.trim());
|
|
849
2170
|
}
|
|
850
2171
|
if (killed) {
|
|
851
|
-
|
|
2172
|
+
resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
|
|
852
2173
|
} else {
|
|
853
|
-
|
|
2174
|
+
resolve9({ language, facts, errors, exitCode: code ?? 0 });
|
|
854
2175
|
}
|
|
855
2176
|
});
|
|
856
2177
|
child.on("error", (err) => {
|
|
857
2178
|
clearTimeout(timer);
|
|
858
2179
|
errors.push(`Failed to spawn worker: ${err.message}`);
|
|
859
|
-
|
|
2180
|
+
resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
|
|
860
2181
|
});
|
|
861
2182
|
});
|
|
862
2183
|
}
|
|
@@ -876,7 +2197,7 @@ function resolveDotnetPublishedWorker(workersDir) {
|
|
|
876
2197
|
};
|
|
877
2198
|
const candidates = candidatesByPlatform[process.platform] ?? [];
|
|
878
2199
|
for (const filename of candidates) {
|
|
879
|
-
const fullPath =
|
|
2200
|
+
const fullPath = path9.join(workersDir, filename);
|
|
880
2201
|
if (existsSync(fullPath)) {
|
|
881
2202
|
return fullPath;
|
|
882
2203
|
}
|
|
@@ -884,30 +2205,30 @@ function resolveDotnetPublishedWorker(workersDir) {
|
|
|
884
2205
|
return null;
|
|
885
2206
|
}
|
|
886
2207
|
function resolveNativeWorker(language, projectDir) {
|
|
887
|
-
const base = import.meta.dirname ??
|
|
888
|
-
const hcsFromBundle =
|
|
889
|
-
const hcsFromSrc =
|
|
2208
|
+
const base = import.meta.dirname ?? path9.dirname(new URL(import.meta.url).pathname);
|
|
2209
|
+
const hcsFromBundle = path9.resolve(base, "..", "..", "vibgrate-hcs");
|
|
2210
|
+
const hcsFromSrc = path9.resolve(base, "..", "..", "..", "vibgrate-hcs");
|
|
890
2211
|
const hcsRoot = existsSync(hcsFromBundle) ? hcsFromBundle : hcsFromSrc;
|
|
891
|
-
const workersDir =
|
|
2212
|
+
const workersDir = path9.resolve(base, "workers");
|
|
892
2213
|
switch (language) {
|
|
893
2214
|
case "go": {
|
|
894
|
-
const bin =
|
|
2215
|
+
const bin = path9.join(workersDir, process.platform === "win32" ? "vibgrate-hcs-go.exe" : "vibgrate-hcs-go");
|
|
895
2216
|
if (existsSync(bin)) {
|
|
896
2217
|
return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
|
|
897
2218
|
}
|
|
898
|
-
const src =
|
|
899
|
-
if (existsSync(
|
|
2219
|
+
const src = path9.join(hcsRoot, "go");
|
|
2220
|
+
if (existsSync(path9.join(src, "main.go"))) {
|
|
900
2221
|
return { cmd: "go", args: ["run", ".", "--project", projectDir, "--output", "ndjson"], cwd: src };
|
|
901
2222
|
}
|
|
902
2223
|
return null;
|
|
903
2224
|
}
|
|
904
2225
|
case "python": {
|
|
905
|
-
const bin =
|
|
2226
|
+
const bin = path9.join(workersDir, "vibgrate-hcs-python");
|
|
906
2227
|
if (existsSync(bin)) {
|
|
907
2228
|
return { cmd: bin, args: ["--project", projectDir, "--output", "ndjson"] };
|
|
908
2229
|
}
|
|
909
|
-
const src =
|
|
910
|
-
if (existsSync(
|
|
2230
|
+
const src = path9.join(hcsRoot, "python");
|
|
2231
|
+
if (existsSync(path9.join(src, "pyproject.toml"))) {
|
|
911
2232
|
return {
|
|
912
2233
|
cmd: "python3",
|
|
913
2234
|
args: ["-m", "vibgrate_hcs_python.main", "--project", projectDir, "--output", "ndjson"],
|
|
@@ -917,13 +2238,13 @@ function resolveNativeWorker(language, projectDir) {
|
|
|
917
2238
|
return null;
|
|
918
2239
|
}
|
|
919
2240
|
case "java": {
|
|
920
|
-
const jar =
|
|
2241
|
+
const jar = path9.join(workersDir, "vibgrate-hcs-jvm.jar");
|
|
921
2242
|
if (existsSync(jar)) {
|
|
922
2243
|
return { cmd: "java", args: ["-jar", jar, "--project", projectDir, "--output", "ndjson"] };
|
|
923
2244
|
}
|
|
924
|
-
const src =
|
|
925
|
-
if (existsSync(
|
|
926
|
-
const gradlew =
|
|
2245
|
+
const src = path9.join(hcsRoot, "jvm");
|
|
2246
|
+
if (existsSync(path9.join(src, "build.gradle.kts"))) {
|
|
2247
|
+
const gradlew = path9.join(src, process.platform === "win32" ? "gradlew.bat" : "gradlew");
|
|
927
2248
|
const launcher = existsSync(gradlew) ? gradlew : "gradle";
|
|
928
2249
|
return {
|
|
929
2250
|
cmd: launcher,
|
|
@@ -939,11 +2260,11 @@ function resolveNativeWorker(language, projectDir) {
|
|
|
939
2260
|
if (publishedWorker) {
|
|
940
2261
|
return { cmd: publishedWorker, args: ["--project", projectDir, "--output", "ndjson"] };
|
|
941
2262
|
}
|
|
942
|
-
const dll =
|
|
2263
|
+
const dll = path9.join(workersDir, "VibgrateHcsWorker.dll");
|
|
943
2264
|
if (existsSync(dll)) {
|
|
944
2265
|
return { cmd: "dotnet", args: [dll, "--project", projectDir, "--output", "ndjson"] };
|
|
945
2266
|
}
|
|
946
|
-
const csproj =
|
|
2267
|
+
const csproj = path9.join(hcsRoot, "dotnet", "src", "VibgrateHcsWorker", "VibgrateHcsWorker.csproj");
|
|
947
2268
|
if (existsSync(csproj)) {
|
|
948
2269
|
return {
|
|
949
2270
|
cmd: "dotnet",
|
|
@@ -969,19 +2290,19 @@ async function runNativeWorker(rootDir, language, opts) {
|
|
|
969
2290
|
exitCode: EXIT_PARSE_FAILURE
|
|
970
2291
|
};
|
|
971
2292
|
}
|
|
972
|
-
return new Promise((
|
|
2293
|
+
return new Promise((resolve9) => {
|
|
973
2294
|
const facts = [];
|
|
974
2295
|
const errors = [];
|
|
975
2296
|
let stdoutBuf = "";
|
|
976
2297
|
let killed = false;
|
|
977
2298
|
if (opts.verbose) {
|
|
978
|
-
process.stderr.write(
|
|
2299
|
+
process.stderr.write(chalk9.dim(`[${language}] Spawning: ${spec.cmd} ${spec.args.join(" ")}
|
|
979
2300
|
`));
|
|
980
2301
|
}
|
|
981
2302
|
const runtimeReq = NATIVE_RUNTIME_REQUIREMENTS[language];
|
|
982
2303
|
const usesPathCommand = runtimeReq && spec.cmd === runtimeReq.command;
|
|
983
2304
|
if (usesPathCommand && !commandExistsOnPath(spec.cmd)) {
|
|
984
|
-
|
|
2305
|
+
resolve9({
|
|
985
2306
|
language,
|
|
986
2307
|
facts: [],
|
|
987
2308
|
errors: [
|
|
@@ -1016,7 +2337,7 @@ async function runNativeWorker(rootDir, language, opts) {
|
|
|
1016
2337
|
const trimmed = line.trim();
|
|
1017
2338
|
if (!trimmed) continue;
|
|
1018
2339
|
if (opts.verbose) {
|
|
1019
|
-
process.stderr.write(
|
|
2340
|
+
process.stderr.write(chalk9.dim(`[${language}] ${trimmed}
|
|
1020
2341
|
`));
|
|
1021
2342
|
}
|
|
1022
2343
|
if (trimmed.startsWith("[error]")) {
|
|
@@ -1028,9 +2349,9 @@ async function runNativeWorker(rootDir, language, opts) {
|
|
|
1028
2349
|
clearTimeout(timer);
|
|
1029
2350
|
if (stdoutBuf.trim()) facts.push(stdoutBuf.trim());
|
|
1030
2351
|
if (killed) {
|
|
1031
|
-
|
|
2352
|
+
resolve9({ language, facts, errors: ["Worker killed: timeout exceeded"], exitCode: EXIT_TIMEOUT });
|
|
1032
2353
|
} else {
|
|
1033
|
-
|
|
2354
|
+
resolve9({ language, facts, errors, exitCode: code ?? 0 });
|
|
1034
2355
|
}
|
|
1035
2356
|
});
|
|
1036
2357
|
child.on("error", (err) => {
|
|
@@ -1039,21 +2360,21 @@ async function runNativeWorker(rootDir, language, opts) {
|
|
|
1039
2360
|
errors.push(
|
|
1040
2361
|
isNotFound ? `[error] '${spec.cmd}' not found. ${buildNativeInstallHint(language)}` : `[error] Failed to spawn native worker: ${err.message}`
|
|
1041
2362
|
);
|
|
1042
|
-
|
|
2363
|
+
resolve9({ language, facts, errors, exitCode: EXIT_PARSE_FAILURE });
|
|
1043
2364
|
});
|
|
1044
2365
|
});
|
|
1045
2366
|
}
|
|
1046
2367
|
async function pushFacts(facts, dsn, verbose) {
|
|
1047
|
-
const parsed =
|
|
2368
|
+
const parsed = parseDsn2(dsn);
|
|
1048
2369
|
if (!parsed) {
|
|
1049
|
-
process.stderr.write(
|
|
1050
|
-
process.stderr.write(
|
|
2370
|
+
process.stderr.write(chalk9.red("Invalid DSN format.\n"));
|
|
2371
|
+
process.stderr.write(chalk9.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
|
|
1051
2372
|
return false;
|
|
1052
2373
|
}
|
|
1053
2374
|
const body = facts.join("\n") + "\n";
|
|
1054
2375
|
const url = `${parsed.scheme}://${parsed.host}/v1/ingest/hcs`;
|
|
1055
2376
|
if (verbose) {
|
|
1056
|
-
process.stderr.write(
|
|
2377
|
+
process.stderr.write(chalk9.dim(`Pushing ${facts.length} facts to ${parsed.host}...
|
|
1057
2378
|
`));
|
|
1058
2379
|
}
|
|
1059
2380
|
try {
|
|
@@ -1071,17 +2392,17 @@ async function pushFacts(facts, dsn, verbose) {
|
|
|
1071
2392
|
});
|
|
1072
2393
|
if (!response.ok) {
|
|
1073
2394
|
const text = await response.text().catch(() => "");
|
|
1074
|
-
process.stderr.write(
|
|
2395
|
+
process.stderr.write(chalk9.red(`Push failed: HTTP ${response.status} ${text}
|
|
1075
2396
|
`));
|
|
1076
2397
|
return false;
|
|
1077
2398
|
}
|
|
1078
2399
|
if (verbose) {
|
|
1079
|
-
process.stderr.write(
|
|
2400
|
+
process.stderr.write(chalk9.green(`\u2714 Pushed ${facts.length} facts successfully
|
|
1080
2401
|
`));
|
|
1081
2402
|
}
|
|
1082
2403
|
return true;
|
|
1083
2404
|
} catch (err) {
|
|
1084
|
-
process.stderr.write(
|
|
2405
|
+
process.stderr.write(chalk9.red(`Push failed: ${err instanceof Error ? err.message : String(err)}
|
|
1085
2406
|
`));
|
|
1086
2407
|
return false;
|
|
1087
2408
|
}
|
|
@@ -1148,21 +2469,21 @@ var ProgressTracker = class {
|
|
|
1148
2469
|
const parts = [];
|
|
1149
2470
|
for (const [lang, st] of this.langStatus) {
|
|
1150
2471
|
if (st.done) {
|
|
1151
|
-
parts.push(
|
|
2472
|
+
parts.push(chalk9.green(`${lang} \u2713`));
|
|
1152
2473
|
} else if (st.phase === "waiting") {
|
|
1153
|
-
parts.push(
|
|
2474
|
+
parts.push(chalk9.dim(`${lang} \xB7`));
|
|
1154
2475
|
} else if (st.phase === "discovering") {
|
|
1155
|
-
parts.push(
|
|
2476
|
+
parts.push(chalk9.yellow(`${lang} \u2026`));
|
|
1156
2477
|
} else if (st.phase === "scanning" && st.fileCount > 0) {
|
|
1157
2478
|
const pct = Math.round(st.fileIndex / st.fileCount * 100);
|
|
1158
|
-
parts.push(
|
|
2479
|
+
parts.push(chalk9.cyan(`${lang} ${st.fileIndex}/${st.fileCount} ${pct}%`));
|
|
1159
2480
|
} else if (st.phase === "extracting") {
|
|
1160
|
-
parts.push(
|
|
2481
|
+
parts.push(chalk9.cyan(`${lang} extracting`));
|
|
1161
2482
|
} else {
|
|
1162
|
-
parts.push(
|
|
2483
|
+
parts.push(chalk9.dim(`${lang} ${st.phase}`));
|
|
1163
2484
|
}
|
|
1164
2485
|
}
|
|
1165
|
-
const line = ` ${parts.join(" ")} ${
|
|
2486
|
+
const line = ` ${parts.join(" ")} ${chalk9.dim(`${this.totalFacts} facts`)}`;
|
|
1166
2487
|
const padding = Math.max(0, this.lastLineLen - line.length);
|
|
1167
2488
|
process.stderr.write(`\r${line}${" ".repeat(padding)}`);
|
|
1168
2489
|
this.lastLineLen = line.length;
|
|
@@ -1174,10 +2495,10 @@ var ProgressTracker = class {
|
|
|
1174
2495
|
}
|
|
1175
2496
|
}
|
|
1176
2497
|
};
|
|
1177
|
-
var extractCommand = new
|
|
1178
|
-
const rootDir =
|
|
2498
|
+
var extractCommand = new Command8("extract").description("Analyze source code and emit validated HCS facts (NDJSON)").argument("[path]", "Path to source directory", ".").option("-o, --out <file>", "Write NDJSON to file (default: stdout)").option("--language <langs>", "Comma-separated languages to analyze (default: auto-detect)").option("--include-tests", "Include test files in analysis").option("--push", "Stream validated facts to dashboard API").option("--dsn <dsn>", "DSN token for push (or use VIBGRATE_DSN env)").option("--concurrency <n>", "Number of parallel file workers").option("--timeout-mins <mins>", "Total analysis timeout in minutes", "60").option("--feedback <file>", "Load NDJSON diff artifact for refinement").option("--verbose", "Print worker stderr and summary statistics").action(async (targetPath, opts) => {
|
|
2499
|
+
const rootDir = path9.resolve(targetPath);
|
|
1179
2500
|
if (!await pathExists(rootDir)) {
|
|
1180
|
-
process.stderr.write(
|
|
2501
|
+
process.stderr.write(chalk9.red(`Path does not exist: ${rootDir}
|
|
1181
2502
|
`));
|
|
1182
2503
|
process.exit(EXIT_USAGE_ERROR);
|
|
1183
2504
|
}
|
|
@@ -1185,47 +2506,47 @@ var extractCommand = new Command5("extract").description("Analyze source code an
|
|
|
1185
2506
|
try {
|
|
1186
2507
|
stat3 = await fs2.stat(rootDir);
|
|
1187
2508
|
} catch {
|
|
1188
|
-
process.stderr.write(
|
|
2509
|
+
process.stderr.write(chalk9.red(`Cannot read path: ${rootDir}
|
|
1189
2510
|
`));
|
|
1190
2511
|
process.exit(EXIT_USAGE_ERROR);
|
|
1191
2512
|
}
|
|
1192
2513
|
if (!stat3.isDirectory()) {
|
|
1193
|
-
process.stderr.write(
|
|
2514
|
+
process.stderr.write(chalk9.red(`Path must be a directory: ${rootDir}
|
|
1194
2515
|
`));
|
|
1195
2516
|
process.exit(EXIT_USAGE_ERROR);
|
|
1196
2517
|
}
|
|
1197
2518
|
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
1198
2519
|
if (opts.push && !dsn) {
|
|
1199
|
-
process.stderr.write(
|
|
2520
|
+
process.stderr.write(chalk9.red("--push requires --dsn or VIBGRATE_DSN environment variable\n"));
|
|
1200
2521
|
process.exit(EXIT_USAGE_ERROR);
|
|
1201
2522
|
}
|
|
1202
|
-
if (dsn && !
|
|
1203
|
-
process.stderr.write(
|
|
1204
|
-
process.stderr.write(
|
|
2523
|
+
if (dsn && !parseDsn2(dsn)) {
|
|
2524
|
+
process.stderr.write(chalk9.red("Invalid DSN format.\n"));
|
|
2525
|
+
process.stderr.write(chalk9.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>\n"));
|
|
1205
2526
|
process.exit(EXIT_USAGE_ERROR);
|
|
1206
2527
|
}
|
|
1207
2528
|
const concurrency = opts.concurrency ? parseInt(opts.concurrency, 10) : os2.cpus().length;
|
|
1208
2529
|
if (isNaN(concurrency) || concurrency < 1) {
|
|
1209
|
-
process.stderr.write(
|
|
2530
|
+
process.stderr.write(chalk9.red("--concurrency must be >= 1\n"));
|
|
1210
2531
|
process.exit(EXIT_USAGE_ERROR);
|
|
1211
2532
|
}
|
|
1212
2533
|
const timeoutMins = parseInt(opts.timeoutMins, 10);
|
|
1213
2534
|
if (isNaN(timeoutMins) || timeoutMins < 1) {
|
|
1214
|
-
process.stderr.write(
|
|
2535
|
+
process.stderr.write(chalk9.red("--timeout-mins must be >= 1\n"));
|
|
1215
2536
|
process.exit(EXIT_USAGE_ERROR);
|
|
1216
2537
|
}
|
|
1217
2538
|
const timeoutMs = timeoutMins * 60 * 1e3;
|
|
1218
2539
|
if (opts.out) {
|
|
1219
|
-
const outDir =
|
|
2540
|
+
const outDir = path9.dirname(path9.resolve(opts.out));
|
|
1220
2541
|
if (!await pathExists(outDir)) {
|
|
1221
|
-
process.stderr.write(
|
|
2542
|
+
process.stderr.write(chalk9.red(`Output directory does not exist: ${outDir}
|
|
1222
2543
|
`));
|
|
1223
2544
|
process.exit(EXIT_USAGE_ERROR);
|
|
1224
2545
|
}
|
|
1225
2546
|
try {
|
|
1226
|
-
const outStat = await fs2.stat(
|
|
2547
|
+
const outStat = await fs2.stat(path9.resolve(opts.out));
|
|
1227
2548
|
if (outStat.isDirectory()) {
|
|
1228
|
-
process.stderr.write(
|
|
2549
|
+
process.stderr.write(chalk9.red(`--out cannot be a directory: ${opts.out}
|
|
1229
2550
|
`));
|
|
1230
2551
|
process.exit(EXIT_USAGE_ERROR);
|
|
1231
2552
|
}
|
|
@@ -1234,16 +2555,16 @@ var extractCommand = new Command5("extract").description("Analyze source code an
|
|
|
1234
2555
|
}
|
|
1235
2556
|
let feedbackLines;
|
|
1236
2557
|
if (opts.feedback) {
|
|
1237
|
-
const feedbackPath =
|
|
2558
|
+
const feedbackPath = path9.resolve(opts.feedback);
|
|
1238
2559
|
if (!await pathExists(feedbackPath)) {
|
|
1239
|
-
process.stderr.write(
|
|
2560
|
+
process.stderr.write(chalk9.red(`Feedback file not found: ${feedbackPath}
|
|
1240
2561
|
`));
|
|
1241
2562
|
process.exit(EXIT_USAGE_ERROR);
|
|
1242
2563
|
}
|
|
1243
2564
|
try {
|
|
1244
2565
|
feedbackLines = await loadFeedback(feedbackPath);
|
|
1245
2566
|
} catch (err) {
|
|
1246
|
-
process.stderr.write(
|
|
2567
|
+
process.stderr.write(chalk9.red(`Invalid feedback file: ${err instanceof Error ? err.message : String(err)}
|
|
1247
2568
|
`));
|
|
1248
2569
|
process.exit(EXIT_USAGE_ERROR);
|
|
1249
2570
|
}
|
|
@@ -1253,9 +2574,9 @@ var extractCommand = new Command5("extract").description("Analyze source code an
|
|
|
1253
2574
|
targetLanguages = opts.language.split(",").map((l) => normalizeLanguageToken(l)).filter(Boolean);
|
|
1254
2575
|
for (const lang of targetLanguages) {
|
|
1255
2576
|
if (!SUPPORTED_LANGUAGES.has(lang)) {
|
|
1256
|
-
process.stderr.write(
|
|
2577
|
+
process.stderr.write(chalk9.red(`Unknown language: "${lang}"
|
|
1257
2578
|
`));
|
|
1258
|
-
process.stderr.write(
|
|
2579
|
+
process.stderr.write(chalk9.dim(`Supported: ${[...SUPPORTED_LANGUAGES].sort().join(", ")}
|
|
1259
2580
|
`));
|
|
1260
2581
|
process.exit(EXIT_USAGE_ERROR);
|
|
1261
2582
|
}
|
|
@@ -1263,14 +2584,14 @@ var extractCommand = new Command5("extract").description("Analyze source code an
|
|
|
1263
2584
|
} else {
|
|
1264
2585
|
const detected = await detectLanguages(rootDir, opts.includeTests ?? false);
|
|
1265
2586
|
if (detected.length === 0) {
|
|
1266
|
-
process.stderr.write(
|
|
2587
|
+
process.stderr.write(chalk9.yellow("No supported source files detected.\n"));
|
|
1267
2588
|
process.exit(EXIT_SUCCESS);
|
|
1268
2589
|
}
|
|
1269
2590
|
targetLanguages = detected.map((d) => d.language);
|
|
1270
2591
|
if (opts.verbose) {
|
|
1271
|
-
process.stderr.write(
|
|
2592
|
+
process.stderr.write(chalk9.dim("Detected languages:\n"));
|
|
1272
2593
|
for (const d of detected) {
|
|
1273
|
-
process.stderr.write(
|
|
2594
|
+
process.stderr.write(chalk9.dim(` ${d.language}: ${d.fileCount} files
|
|
1274
2595
|
`));
|
|
1275
2596
|
}
|
|
1276
2597
|
}
|
|
@@ -1278,24 +2599,25 @@ var extractCommand = new Command5("extract").description("Analyze source code an
|
|
|
1278
2599
|
const runnableLanguages = targetLanguages.filter((l) => ALL_WORKER_LANGS.has(l));
|
|
1279
2600
|
const unknownWorkerLangs = targetLanguages.filter((l) => !ALL_WORKER_LANGS.has(l));
|
|
1280
2601
|
if (unknownWorkerLangs.length > 0 && opts.verbose) {
|
|
1281
|
-
process.stderr.write(
|
|
2602
|
+
process.stderr.write(chalk9.dim(
|
|
1282
2603
|
`No worker registered for: ${unknownWorkerLangs.join(", ")} \u2014 skipping.
|
|
1283
2604
|
`
|
|
1284
2605
|
));
|
|
1285
2606
|
}
|
|
1286
2607
|
if (runnableLanguages.length === 0) {
|
|
1287
|
-
process.stderr.write(
|
|
2608
|
+
process.stderr.write(chalk9.yellow("No languages with available HCS workers found.\n"));
|
|
1288
2609
|
process.exit(EXIT_SUCCESS);
|
|
1289
2610
|
}
|
|
1290
2611
|
const startTime = Date.now();
|
|
1291
2612
|
const globalDeadline = startTime + timeoutMs;
|
|
1292
2613
|
process.stderr.write(
|
|
1293
|
-
|
|
1294
|
-
`) +
|
|
2614
|
+
chalk9.bold(`Extracting HCS facts from ${rootDir}
|
|
2615
|
+
`) + chalk9.dim(`Languages: ${runnableLanguages.join(", ")} Concurrency: ${concurrency} Timeout: ${timeoutMins}m
|
|
1295
2616
|
`)
|
|
1296
2617
|
);
|
|
1297
2618
|
const progress = new ProgressTracker(runnableLanguages);
|
|
1298
2619
|
const sem = new Semaphore(concurrency);
|
|
2620
|
+
const allPreamble = [];
|
|
1299
2621
|
const allFacts = [];
|
|
1300
2622
|
const allErrors = [];
|
|
1301
2623
|
let hasSchemaFailure = false;
|
|
@@ -1321,23 +2643,27 @@ var extractCommand = new Command5("extract").description("Analyze source code an
|
|
|
1321
2643
|
if (result.exitCode === EXIT_TIMEOUT) {
|
|
1322
2644
|
hasTimeout = true;
|
|
1323
2645
|
}
|
|
1324
|
-
const
|
|
2646
|
+
const { preamble, facts } = splitHcsOutput(result.facts);
|
|
2647
|
+
allPreamble.push(...preamble);
|
|
1325
2648
|
let langFactCount = 0;
|
|
1326
|
-
for (const line of
|
|
2649
|
+
for (const line of [...preamble, ...facts]) {
|
|
1327
2650
|
const validation = validateFactLine(line);
|
|
1328
|
-
if (validation.valid) {
|
|
1329
|
-
allFacts.push(line);
|
|
1330
|
-
langFactCount++;
|
|
1331
|
-
progress.onFact();
|
|
1332
|
-
} else {
|
|
2651
|
+
if (!validation.valid) {
|
|
1333
2652
|
hasSchemaFailure = true;
|
|
1334
2653
|
allErrors.push(`[${language}] Schema validation: ${validation.error}`);
|
|
1335
2654
|
if (opts.verbose) {
|
|
1336
|
-
process.stderr.write(
|
|
2655
|
+
process.stderr.write(chalk9.red(`[${language}] Invalid fact: ${validation.error}
|
|
1337
2656
|
`));
|
|
1338
2657
|
}
|
|
1339
2658
|
}
|
|
1340
2659
|
}
|
|
2660
|
+
for (const line of facts) {
|
|
2661
|
+
if (validateFactLine(line).valid) {
|
|
2662
|
+
allFacts.push(line);
|
|
2663
|
+
langFactCount++;
|
|
2664
|
+
progress.onFact();
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
1341
2667
|
progress.setLanguageFactCount(language, langFactCount);
|
|
1342
2668
|
if (result.errors.length > 0) {
|
|
1343
2669
|
allErrors.push(...result.errors.map((e) => `[${language}] ${e}`));
|
|
@@ -1351,64 +2677,65 @@ var extractCommand = new Command5("extract").description("Analyze source code an
|
|
|
1351
2677
|
const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
|
|
1352
2678
|
progress.finish(elapsed);
|
|
1353
2679
|
allFacts.sort();
|
|
1354
|
-
const
|
|
2680
|
+
const allLines = [...allPreamble, ...allFacts];
|
|
2681
|
+
const ndjsonOutput = allLines.join("\n") + (allLines.length > 0 ? "\n" : "");
|
|
1355
2682
|
if (opts.out) {
|
|
1356
|
-
const outPath =
|
|
2683
|
+
const outPath = path9.resolve(opts.out);
|
|
1357
2684
|
await fs2.writeFile(outPath, ndjsonOutput, "utf-8");
|
|
1358
|
-
process.stderr.write(
|
|
2685
|
+
process.stderr.write(chalk9.green(`\u2714 Wrote ${allFacts.length} facts to ${outPath}
|
|
1359
2686
|
`));
|
|
1360
2687
|
} else {
|
|
1361
2688
|
process.stdout.write(ndjsonOutput);
|
|
1362
2689
|
}
|
|
1363
2690
|
if (opts.push && dsn && allFacts.length > 0) {
|
|
1364
|
-
const pushOk = await pushFacts(
|
|
2691
|
+
const pushOk = await pushFacts(allLines, dsn, opts.verbose ?? false);
|
|
1365
2692
|
if (!pushOk) {
|
|
1366
2693
|
process.exit(EXIT_PUSH_FAILURE);
|
|
1367
2694
|
}
|
|
1368
2695
|
}
|
|
1369
2696
|
if (opts.verbose) {
|
|
1370
|
-
process.stderr.write(
|
|
1371
|
-
process.stderr.write(
|
|
2697
|
+
process.stderr.write(chalk9.dim("\n\u2500\u2500 Summary \u2500\u2500\n"));
|
|
2698
|
+
process.stderr.write(chalk9.dim(` Facts emitted : ${allFacts.length}
|
|
1372
2699
|
`));
|
|
1373
|
-
process.stderr.write(
|
|
2700
|
+
process.stderr.write(chalk9.dim(` Languages : ${runnableLanguages.join(", ")}
|
|
1374
2701
|
`));
|
|
1375
|
-
process.stderr.write(
|
|
2702
|
+
process.stderr.write(chalk9.dim(` Elapsed : ${elapsed}s
|
|
1376
2703
|
`));
|
|
1377
2704
|
if (allErrors.length > 0) {
|
|
1378
|
-
process.stderr.write(
|
|
2705
|
+
process.stderr.write(chalk9.dim(` Errors : ${allErrors.length}
|
|
1379
2706
|
`));
|
|
1380
2707
|
for (const err of allErrors.slice(0, 10)) {
|
|
1381
|
-
process.stderr.write(
|
|
2708
|
+
process.stderr.write(chalk9.dim(` ${err}
|
|
1382
2709
|
`));
|
|
1383
2710
|
}
|
|
1384
2711
|
if (allErrors.length > 10) {
|
|
1385
|
-
process.stderr.write(
|
|
2712
|
+
process.stderr.write(chalk9.dim(` ... and ${allErrors.length - 10} more
|
|
1386
2713
|
`));
|
|
1387
2714
|
}
|
|
1388
2715
|
}
|
|
1389
|
-
process.stderr.write(
|
|
2716
|
+
process.stderr.write(chalk9.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n"));
|
|
1390
2717
|
}
|
|
1391
2718
|
if (hasTimeout) {
|
|
1392
|
-
process.stderr.write(
|
|
2719
|
+
process.stderr.write(chalk9.red(`Timeout exceeded (${timeoutMins} minutes)
|
|
1393
2720
|
`));
|
|
1394
2721
|
process.exit(EXIT_TIMEOUT);
|
|
1395
2722
|
}
|
|
1396
2723
|
if (hasSchemaFailure) {
|
|
1397
|
-
process.stderr.write(
|
|
2724
|
+
process.stderr.write(chalk9.red("Fact schema validation failures detected\n"));
|
|
1398
2725
|
process.exit(EXIT_SCHEMA_FAILURE);
|
|
1399
2726
|
}
|
|
1400
2727
|
if (hasParseFailure) {
|
|
1401
|
-
process.stderr.write(
|
|
2728
|
+
process.stderr.write(chalk9.red("Parsing failures detected\n"));
|
|
1402
2729
|
process.exit(EXIT_PARSE_FAILURE);
|
|
1403
2730
|
}
|
|
1404
2731
|
});
|
|
1405
2732
|
|
|
1406
2733
|
// src/commands/diff.ts
|
|
1407
|
-
import * as
|
|
2734
|
+
import * as path10 from "path";
|
|
1408
2735
|
import * as fs3 from "fs/promises";
|
|
1409
|
-
import * as
|
|
1410
|
-
import { Command as
|
|
1411
|
-
import
|
|
2736
|
+
import * as crypto3 from "crypto";
|
|
2737
|
+
import { Command as Command9 } from "commander";
|
|
2738
|
+
import chalk10 from "chalk";
|
|
1412
2739
|
var EXIT_INVALID_PATH = 2;
|
|
1413
2740
|
var EXIT_USAGE_ERROR2 = 5;
|
|
1414
2741
|
var VALID_FORMATS = /* @__PURE__ */ new Set(["ndjson", "json", "patch"]);
|
|
@@ -1430,8 +2757,8 @@ async function discoverFiles(root, includeGlobs, excludeGlobs) {
|
|
|
1430
2757
|
}
|
|
1431
2758
|
for (const entry of entries) {
|
|
1432
2759
|
if (entry.name.startsWith(".") && SKIP_DIRS2.has(entry.name)) continue;
|
|
1433
|
-
const absPath =
|
|
1434
|
-
const relPath =
|
|
2760
|
+
const absPath = path10.join(dir, entry.name);
|
|
2761
|
+
const relPath = path10.join(relBase, entry.name);
|
|
1435
2762
|
if (entry.isDirectory()) {
|
|
1436
2763
|
if (!SKIP_DIRS2.has(entry.name)) {
|
|
1437
2764
|
await walk(absPath, relPath);
|
|
@@ -1602,7 +2929,7 @@ function lcsRatio(a, b) {
|
|
|
1602
2929
|
return 2 * lcsLen / (n + m);
|
|
1603
2930
|
}
|
|
1604
2931
|
function generateDeltaId(kind, filePath) {
|
|
1605
|
-
const hash =
|
|
2932
|
+
const hash = crypto3.createHash("sha256").update(`${kind}:${filePath}`).digest("hex").substring(0, 16);
|
|
1606
2933
|
return `hcs:delta:${hash}`;
|
|
1607
2934
|
}
|
|
1608
2935
|
function formatNdjson(deltas) {
|
|
@@ -1614,8 +2941,8 @@ function formatJson(deltas) {
|
|
|
1614
2941
|
function formatPatch(deltas, originalPath, generatedPath) {
|
|
1615
2942
|
const lines = [];
|
|
1616
2943
|
for (const delta of deltas) {
|
|
1617
|
-
const aFile = delta.originalPath ?
|
|
1618
|
-
const bFile = delta.generatedPath ?
|
|
2944
|
+
const aFile = delta.originalPath ? path10.join("a", delta.originalPath) : "/dev/null";
|
|
2945
|
+
const bFile = delta.generatedPath ? path10.join("b", delta.generatedPath) : "/dev/null";
|
|
1619
2946
|
if (delta.changeKind === "renamed" && delta.originalPath && delta.generatedPath) {
|
|
1620
2947
|
lines.push(`diff --vibgrate ${aFile} ${bFile}`);
|
|
1621
2948
|
lines.push(`similarity index ${Math.round((delta.similarity ?? 0) * 100)}%`);
|
|
@@ -1636,12 +2963,12 @@ function formatPatch(deltas, originalPath, generatedPath) {
|
|
|
1636
2963
|
}
|
|
1637
2964
|
return lines.join("\n");
|
|
1638
2965
|
}
|
|
1639
|
-
var diffCommand = new
|
|
1640
|
-
const origDir =
|
|
1641
|
-
const genDir =
|
|
2966
|
+
var diffCommand = new Command9("diff").description("Compare two directory trees and emit structured delta facts").argument("<original_path>", "Original source directory").argument("<generated_path>", "Generated source directory").option("-o, --out <file>", "Output file path", "vibgrate.diff.ndjson").option("--format <fmt>", "Output format (ndjson|json|patch)", "ndjson").option("--ignore-whitespace", "Ignore whitespace-only changes").option("--context <n>", "Context lines for patch output", "3").option("--include <globs>", "Only include matching files (comma-separated)").option("--exclude <globs>", "Exclude matching files (comma-separated)").option("--stats", "Include insertions/deletions summary per file").option("--verbose", "Print file match decisions and rename detection").action(async (originalPath, generatedPath, opts) => {
|
|
2967
|
+
const origDir = path10.resolve(originalPath);
|
|
2968
|
+
const genDir = path10.resolve(generatedPath);
|
|
1642
2969
|
for (const [label, dir] of [["original_path", origDir], ["generated_path", genDir]]) {
|
|
1643
2970
|
if (!await pathExists(dir)) {
|
|
1644
|
-
process.stderr.write(
|
|
2971
|
+
process.stderr.write(chalk10.red(`${label} does not exist: ${dir}
|
|
1645
2972
|
`));
|
|
1646
2973
|
process.exit(EXIT_INVALID_PATH);
|
|
1647
2974
|
}
|
|
@@ -1649,25 +2976,25 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1649
2976
|
try {
|
|
1650
2977
|
stat3 = await fs3.stat(dir);
|
|
1651
2978
|
} catch {
|
|
1652
|
-
process.stderr.write(
|
|
2979
|
+
process.stderr.write(chalk10.red(`Cannot read ${label}: ${dir}
|
|
1653
2980
|
`));
|
|
1654
2981
|
process.exit(EXIT_INVALID_PATH);
|
|
1655
2982
|
}
|
|
1656
2983
|
if (!stat3.isDirectory()) {
|
|
1657
|
-
process.stderr.write(
|
|
2984
|
+
process.stderr.write(chalk10.red(`${label} must be a directory: ${dir}
|
|
1658
2985
|
`));
|
|
1659
2986
|
process.exit(EXIT_INVALID_PATH);
|
|
1660
2987
|
}
|
|
1661
2988
|
}
|
|
1662
2989
|
const format = opts.format.toLowerCase();
|
|
1663
2990
|
if (!VALID_FORMATS.has(format)) {
|
|
1664
|
-
process.stderr.write(
|
|
2991
|
+
process.stderr.write(chalk10.red(`Invalid format: "${opts.format}". Allowed: ndjson, json, patch
|
|
1665
2992
|
`));
|
|
1666
2993
|
process.exit(EXIT_USAGE_ERROR2);
|
|
1667
2994
|
}
|
|
1668
2995
|
const contextLines = parseInt(opts.context, 10);
|
|
1669
2996
|
if (isNaN(contextLines) || contextLines < 0) {
|
|
1670
|
-
process.stderr.write(
|
|
2997
|
+
process.stderr.write(chalk10.red("--context must be >= 0\n"));
|
|
1671
2998
|
process.exit(EXIT_USAGE_ERROR2);
|
|
1672
2999
|
}
|
|
1673
3000
|
const includeGlobs = opts.include ? opts.include.split(",").map((g) => g.trim()).filter(Boolean) : void 0;
|
|
@@ -1679,9 +3006,9 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1679
3006
|
const origMap = new Map(origFiles.map((f) => [f.relPath, f]));
|
|
1680
3007
|
const genMap = new Map(genFiles.map((f) => [f.relPath, f]));
|
|
1681
3008
|
if (opts.verbose) {
|
|
1682
|
-
process.stderr.write(
|
|
3009
|
+
process.stderr.write(chalk10.dim(`Original: ${origFiles.length} files
|
|
1683
3010
|
`));
|
|
1684
|
-
process.stderr.write(
|
|
3011
|
+
process.stderr.write(chalk10.dim(`Generated: ${genFiles.length} files
|
|
1685
3012
|
`));
|
|
1686
3013
|
}
|
|
1687
3014
|
const deltas = [];
|
|
@@ -1715,7 +3042,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1715
3042
|
}
|
|
1716
3043
|
deltas.push(delta);
|
|
1717
3044
|
if (opts.verbose) {
|
|
1718
|
-
process.stderr.write(
|
|
3045
|
+
process.stderr.write(chalk10.dim(` modified: ${relPath} (+${diffResult.insertions} -${diffResult.deletions})
|
|
1719
3046
|
`));
|
|
1720
3047
|
}
|
|
1721
3048
|
}
|
|
@@ -1746,7 +3073,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1746
3073
|
const pairs = [];
|
|
1747
3074
|
for (const [origPath, origContent] of origContents) {
|
|
1748
3075
|
for (const [genPath, genContent] of genContents) {
|
|
1749
|
-
if (
|
|
3076
|
+
if (path10.extname(origPath) !== path10.extname(genPath)) continue;
|
|
1750
3077
|
const similarity = computeSimilarity(origContent, genContent);
|
|
1751
3078
|
if (similarity >= RENAME_THRESHOLD) {
|
|
1752
3079
|
pairs.push({ origPath, genPath, similarity });
|
|
@@ -1779,7 +3106,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1779
3106
|
}
|
|
1780
3107
|
deltas.push(delta);
|
|
1781
3108
|
if (opts.verbose) {
|
|
1782
|
-
process.stderr.write(
|
|
3109
|
+
process.stderr.write(chalk10.dim(
|
|
1783
3110
|
` renamed: ${pair.origPath} \u2192 ${pair.genPath} (${(pair.similarity * 100).toFixed(0)}% similar)
|
|
1784
3111
|
`
|
|
1785
3112
|
));
|
|
@@ -1804,7 +3131,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1804
3131
|
}
|
|
1805
3132
|
deltas.push(delta);
|
|
1806
3133
|
if (opts.verbose) {
|
|
1807
|
-
process.stderr.write(
|
|
3134
|
+
process.stderr.write(chalk10.dim(` removed: ${file.relPath}
|
|
1808
3135
|
`));
|
|
1809
3136
|
}
|
|
1810
3137
|
}
|
|
@@ -1826,7 +3153,7 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1826
3153
|
}
|
|
1827
3154
|
deltas.push(delta);
|
|
1828
3155
|
if (opts.verbose) {
|
|
1829
|
-
process.stderr.write(
|
|
3156
|
+
process.stderr.write(chalk10.dim(` added: ${file.relPath}
|
|
1830
3157
|
`));
|
|
1831
3158
|
}
|
|
1832
3159
|
}
|
|
@@ -1847,16 +3174,16 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1847
3174
|
output = formatPatch(deltas, originalPath, generatedPath);
|
|
1848
3175
|
break;
|
|
1849
3176
|
}
|
|
1850
|
-
const outPath =
|
|
3177
|
+
const outPath = path10.resolve(opts.out);
|
|
1851
3178
|
await fs3.writeFile(outPath, output, "utf-8");
|
|
1852
3179
|
const added = deltas.filter((d) => d.changeKind === "added").length;
|
|
1853
3180
|
const removed = deltas.filter((d) => d.changeKind === "removed").length;
|
|
1854
3181
|
const modified = deltas.filter((d) => d.changeKind === "modified").length;
|
|
1855
3182
|
const renamed = deltas.filter((d) => d.changeKind === "renamed").length;
|
|
1856
3183
|
process.stderr.write(
|
|
1857
|
-
|
|
3184
|
+
chalk10.green(`\u2714 `) + `${deltas.length} changes: ` + chalk10.green(`+${added} added`) + ", " + chalk10.red(`-${removed} removed`) + ", " + chalk10.yellow(`~${modified} modified`) + ", " + chalk10.blue(`\u2192${renamed} renamed`) + "\n"
|
|
1858
3185
|
);
|
|
1859
|
-
process.stderr.write(
|
|
3186
|
+
process.stderr.write(chalk10.dim(` Written to ${outPath}
|
|
1860
3187
|
`));
|
|
1861
3188
|
if (opts.verbose && opts.stats) {
|
|
1862
3189
|
let totalIns = 0;
|
|
@@ -1867,193 +3194,193 @@ var diffCommand = new Command6("diff").description("Compare two directory trees
|
|
|
1867
3194
|
totalDel += d.stats.deletions;
|
|
1868
3195
|
}
|
|
1869
3196
|
}
|
|
1870
|
-
process.stderr.write(
|
|
3197
|
+
process.stderr.write(chalk10.dim(` Total: +${totalIns} insertions, -${totalDel} deletions
|
|
1871
3198
|
`));
|
|
1872
3199
|
}
|
|
1873
3200
|
});
|
|
1874
3201
|
|
|
1875
3202
|
// src/commands/help.ts
|
|
1876
|
-
import { Command as
|
|
1877
|
-
import
|
|
3203
|
+
import { Command as Command10 } from "commander";
|
|
3204
|
+
import chalk11 from "chalk";
|
|
1878
3205
|
var HELP_URL = "https://vibgrate.com/help";
|
|
1879
3206
|
function printFooter() {
|
|
1880
3207
|
console.log("");
|
|
1881
|
-
console.log(
|
|
3208
|
+
console.log(chalk11.dim(`See ${HELP_URL} for more guidance`));
|
|
1882
3209
|
}
|
|
1883
3210
|
var detailedHelp = {
|
|
1884
3211
|
scan: () => {
|
|
1885
3212
|
console.log("");
|
|
1886
|
-
console.log(
|
|
3213
|
+
console.log(chalk11.bold.underline("vibgrate scan") + chalk11.dim(" \u2014 Scan a project for upgrade drift"));
|
|
1887
3214
|
console.log("");
|
|
1888
|
-
console.log(
|
|
3215
|
+
console.log(chalk11.bold("Usage:"));
|
|
1889
3216
|
console.log(" vibgrate scan [path] [options]");
|
|
1890
3217
|
console.log("");
|
|
1891
|
-
console.log(
|
|
1892
|
-
console.log(` ${
|
|
3218
|
+
console.log(chalk11.bold("Arguments:"));
|
|
3219
|
+
console.log(` ${chalk11.cyan("[path]")} Path to scan (default: current directory)`);
|
|
1893
3220
|
console.log("");
|
|
1894
|
-
console.log(
|
|
1895
|
-
console.log(` ${
|
|
1896
|
-
console.log(` ${
|
|
3221
|
+
console.log(chalk11.bold("Output options:"));
|
|
3222
|
+
console.log(` ${chalk11.cyan("--format <format>")} Output format: ${chalk11.white("text")} | json | sarif | md (default: text)`);
|
|
3223
|
+
console.log(` ${chalk11.cyan("--out <file>")} Write output to a file instead of stdout`);
|
|
1897
3224
|
console.log("");
|
|
1898
|
-
console.log(
|
|
1899
|
-
console.log(` ${
|
|
1900
|
-
console.log(` ${
|
|
1901
|
-
console.log(` ${
|
|
1902
|
-
console.log(` ${
|
|
3225
|
+
console.log(chalk11.bold("Baseline & gating:"));
|
|
3226
|
+
console.log(` ${chalk11.cyan("--baseline <file>")} Compare results against a saved baseline`);
|
|
3227
|
+
console.log(` ${chalk11.cyan("--drift-budget <score>")} Fail if drift score exceeds this value (0\u2013100)`);
|
|
3228
|
+
console.log(` ${chalk11.cyan("--drift-worsening <percent>")} Fail if drift worsens by more than % since baseline`);
|
|
3229
|
+
console.log(` ${chalk11.cyan("--fail-on <level>")} Fail exit code on warn or error findings`);
|
|
1903
3230
|
console.log("");
|
|
1904
|
-
console.log(
|
|
1905
|
-
console.log(` ${
|
|
1906
|
-
console.log(` ${
|
|
3231
|
+
console.log(chalk11.bold("Performance:"));
|
|
3232
|
+
console.log(` ${chalk11.cyan("--concurrency <n>")} Max concurrent registry calls (default: 8)`);
|
|
3233
|
+
console.log(` ${chalk11.cyan("--changed-only")} Only scan files changed since last git commit`);
|
|
1907
3234
|
console.log("");
|
|
1908
|
-
console.log(
|
|
1909
|
-
console.log(` ${
|
|
1910
|
-
console.log(` ${
|
|
1911
|
-
console.log(` ${
|
|
1912
|
-
console.log(` ${
|
|
3235
|
+
console.log(chalk11.bold("Privacy & offline:"));
|
|
3236
|
+
console.log(` ${chalk11.cyan("--offline")} Run without any network calls; skip result upload`);
|
|
3237
|
+
console.log(` ${chalk11.cyan("--package-manifest <file>")} Use a local package-version manifest (JSON or ZIP) for offline mode`);
|
|
3238
|
+
console.log(` ${chalk11.cyan("--no-local-artifacts")} Do not write .vibgrate JSON artifacts to disk`);
|
|
3239
|
+
console.log(` ${chalk11.cyan("--max-privacy")} Strongest privacy mode: minimal scanners + no local artifacts`);
|
|
1913
3240
|
console.log("");
|
|
1914
|
-
console.log(
|
|
1915
|
-
console.log(` ${
|
|
1916
|
-
console.log(` ${
|
|
3241
|
+
console.log(chalk11.bold("Tooling:"));
|
|
3242
|
+
console.log(` ${chalk11.cyan("--install-tools")} Auto-install missing security scanners via Homebrew`);
|
|
3243
|
+
console.log(` ${chalk11.cyan("--ui-purpose")} Enable UI purpose evidence extraction (slower)`);
|
|
1917
3244
|
console.log("");
|
|
1918
|
-
console.log(
|
|
1919
|
-
console.log(` ${
|
|
1920
|
-
console.log(` ${
|
|
1921
|
-
console.log(` ${
|
|
1922
|
-
console.log(` ${
|
|
3245
|
+
console.log(chalk11.bold("Uploading results:"));
|
|
3246
|
+
console.log(` ${chalk11.cyan("--push")} Auto-push results to Vibgrate API after scan`);
|
|
3247
|
+
console.log(` ${chalk11.cyan("--dsn <dsn>")} DSN token for push (or set VIBGRATE_DSN env var)`);
|
|
3248
|
+
console.log(` ${chalk11.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
|
|
3249
|
+
console.log(` ${chalk11.cyan("--strict")} Fail if the upload to Vibgrate API fails`);
|
|
1923
3250
|
console.log("");
|
|
1924
|
-
console.log(
|
|
1925
|
-
console.log(` ${
|
|
3251
|
+
console.log(chalk11.bold("Examples:"));
|
|
3252
|
+
console.log(` ${chalk11.dim("# Scan the current directory and display a text report")}`);
|
|
1926
3253
|
console.log(" vibgrate scan .");
|
|
1927
3254
|
console.log("");
|
|
1928
|
-
console.log(` ${
|
|
3255
|
+
console.log(` ${chalk11.dim("# Scan, fail if drift score > 40, and write SARIF for GitHub Actions")}`);
|
|
1929
3256
|
console.log(" vibgrate scan . --drift-budget 40 --format sarif --out drift.sarif");
|
|
1930
3257
|
console.log("");
|
|
1931
|
-
console.log(` ${
|
|
3258
|
+
console.log(` ${chalk11.dim("# Scan and automatically upload results via a DSN")}`);
|
|
1932
3259
|
console.log(" vibgrate scan . --push --dsn $VIBGRATE_DSN");
|
|
1933
3260
|
console.log("");
|
|
1934
|
-
console.log(` ${
|
|
3261
|
+
console.log(` ${chalk11.dim("# Offline scan using a pre-downloaded package manifest")}`);
|
|
1935
3262
|
console.log(" vibgrate scan . --offline --package-manifest ./manifest.zip");
|
|
1936
3263
|
},
|
|
1937
3264
|
init: () => {
|
|
1938
3265
|
console.log("");
|
|
1939
|
-
console.log(
|
|
3266
|
+
console.log(chalk11.bold.underline("vibgrate init") + chalk11.dim(" \u2014 Initialise vibgrate in a project directory"));
|
|
1940
3267
|
console.log("");
|
|
1941
|
-
console.log(
|
|
3268
|
+
console.log(chalk11.bold("Usage:"));
|
|
1942
3269
|
console.log(" vibgrate init [path] [options]");
|
|
1943
3270
|
console.log("");
|
|
1944
|
-
console.log(
|
|
1945
|
-
console.log(` ${
|
|
3271
|
+
console.log(chalk11.bold("Arguments:"));
|
|
3272
|
+
console.log(` ${chalk11.cyan("[path]")} Directory to initialise (default: current directory)`);
|
|
1946
3273
|
console.log("");
|
|
1947
|
-
console.log(
|
|
1948
|
-
console.log(` ${
|
|
1949
|
-
console.log(` ${
|
|
3274
|
+
console.log(chalk11.bold("Options:"));
|
|
3275
|
+
console.log(` ${chalk11.cyan("--baseline")} Create an initial drift baseline after init`);
|
|
3276
|
+
console.log(` ${chalk11.cyan("--yes")} Skip all confirmation prompts`);
|
|
1950
3277
|
console.log("");
|
|
1951
|
-
console.log(
|
|
3278
|
+
console.log(chalk11.bold("What it does:"));
|
|
1952
3279
|
console.log(" \u2022 Creates a .vibgrate/ directory");
|
|
1953
3280
|
console.log(" \u2022 Writes a vibgrate.config.ts starter config");
|
|
1954
3281
|
console.log(" \u2022 Optionally runs an initial baseline scan (--baseline)");
|
|
1955
3282
|
console.log("");
|
|
1956
|
-
console.log(
|
|
3283
|
+
console.log(chalk11.bold("Examples:"));
|
|
1957
3284
|
console.log(" vibgrate init");
|
|
1958
3285
|
console.log(" vibgrate init ./my-project --baseline");
|
|
1959
3286
|
},
|
|
1960
3287
|
baseline: () => {
|
|
1961
3288
|
console.log("");
|
|
1962
|
-
console.log(
|
|
3289
|
+
console.log(chalk11.bold.underline("vibgrate baseline") + chalk11.dim(" \u2014 Save a drift baseline snapshot"));
|
|
1963
3290
|
console.log("");
|
|
1964
|
-
console.log(
|
|
3291
|
+
console.log(chalk11.bold("Usage:"));
|
|
1965
3292
|
console.log(" vibgrate baseline [path]");
|
|
1966
3293
|
console.log("");
|
|
1967
|
-
console.log(
|
|
1968
|
-
console.log(` ${
|
|
3294
|
+
console.log(chalk11.bold("Arguments:"));
|
|
3295
|
+
console.log(` ${chalk11.cyan("[path]")} Path to baseline (default: current directory)`);
|
|
1969
3296
|
console.log("");
|
|
1970
|
-
console.log(
|
|
3297
|
+
console.log(chalk11.bold("What it does:"));
|
|
1971
3298
|
console.log(" Runs a full scan and saves the result as .vibgrate/baseline.json.");
|
|
1972
3299
|
console.log(" Future scans can compare against this file using --baseline.");
|
|
1973
3300
|
console.log("");
|
|
1974
|
-
console.log(
|
|
3301
|
+
console.log(chalk11.bold("Examples:"));
|
|
1975
3302
|
console.log(" vibgrate baseline .");
|
|
1976
3303
|
console.log(" vibgrate scan . --baseline .vibgrate/baseline.json --drift-worsening 10");
|
|
1977
3304
|
},
|
|
1978
3305
|
report: () => {
|
|
1979
3306
|
console.log("");
|
|
1980
|
-
console.log(
|
|
3307
|
+
console.log(chalk11.bold.underline("vibgrate report") + chalk11.dim(" \u2014 Generate a report from a saved scan artifact"));
|
|
1981
3308
|
console.log("");
|
|
1982
|
-
console.log(
|
|
3309
|
+
console.log(chalk11.bold("Usage:"));
|
|
1983
3310
|
console.log(" vibgrate report [options]");
|
|
1984
3311
|
console.log("");
|
|
1985
|
-
console.log(
|
|
1986
|
-
console.log(` ${
|
|
1987
|
-
console.log(` ${
|
|
3312
|
+
console.log(chalk11.bold("Options:"));
|
|
3313
|
+
console.log(` ${chalk11.cyan("--in <file>")} Input artifact file (default: .vibgrate/scan_result.json)`);
|
|
3314
|
+
console.log(` ${chalk11.cyan("--format <format>")} Output format: ${chalk11.white("text")} | md | json (default: text)`);
|
|
1988
3315
|
console.log("");
|
|
1989
|
-
console.log(
|
|
3316
|
+
console.log(chalk11.bold("Examples:"));
|
|
1990
3317
|
console.log(" vibgrate report");
|
|
1991
3318
|
console.log(" vibgrate report --format md > DRIFT-REPORT.md");
|
|
1992
3319
|
console.log(" vibgrate report --in ./ci/scan_result.json --format json");
|
|
1993
3320
|
},
|
|
1994
3321
|
sbom: () => {
|
|
1995
3322
|
console.log("");
|
|
1996
|
-
console.log(
|
|
3323
|
+
console.log(chalk11.bold.underline("vibgrate sbom") + chalk11.dim(" \u2014 Export a Software Bill of Materials from a scan artifact"));
|
|
1997
3324
|
console.log("");
|
|
1998
|
-
console.log(
|
|
3325
|
+
console.log(chalk11.bold("Usage:"));
|
|
1999
3326
|
console.log(" vibgrate sbom [options]");
|
|
2000
3327
|
console.log("");
|
|
2001
|
-
console.log(
|
|
2002
|
-
console.log(` ${
|
|
2003
|
-
console.log(` ${
|
|
2004
|
-
console.log(` ${
|
|
3328
|
+
console.log(chalk11.bold("Options:"));
|
|
3329
|
+
console.log(` ${chalk11.cyan("--in <file>")} Input artifact (default: .vibgrate/scan_result.json)`);
|
|
3330
|
+
console.log(` ${chalk11.cyan("--format <format>")} SBOM format: ${chalk11.white("cyclonedx")} | spdx (default: cyclonedx)`);
|
|
3331
|
+
console.log(` ${chalk11.cyan("--out <file>")} Write SBOM to file instead of stdout`);
|
|
2005
3332
|
console.log("");
|
|
2006
|
-
console.log(
|
|
3333
|
+
console.log(chalk11.bold("Examples:"));
|
|
2007
3334
|
console.log(" vibgrate sbom --format cyclonedx --out sbom.json");
|
|
2008
3335
|
console.log(" vibgrate sbom --format spdx --out sbom.spdx.json");
|
|
2009
3336
|
},
|
|
2010
3337
|
push: () => {
|
|
2011
3338
|
console.log("");
|
|
2012
|
-
console.log(
|
|
3339
|
+
console.log(chalk11.bold.underline("vibgrate push") + chalk11.dim(" \u2014 Upload a scan artifact to the Vibgrate API"));
|
|
2013
3340
|
console.log("");
|
|
2014
|
-
console.log(
|
|
3341
|
+
console.log(chalk11.bold("Usage:"));
|
|
2015
3342
|
console.log(" vibgrate push [options]");
|
|
2016
3343
|
console.log("");
|
|
2017
|
-
console.log(
|
|
2018
|
-
console.log(` ${
|
|
2019
|
-
console.log(` ${
|
|
2020
|
-
console.log(` ${
|
|
2021
|
-
console.log(` ${
|
|
3344
|
+
console.log(chalk11.bold("Options:"));
|
|
3345
|
+
console.log(` ${chalk11.cyan("--dsn <dsn>")} DSN token (or set VIBGRATE_DSN env var)`);
|
|
3346
|
+
console.log(` ${chalk11.cyan("--file <file>")} Artifact to upload (default: .vibgrate/scan_result.json)`);
|
|
3347
|
+
console.log(` ${chalk11.cyan("--region <region>")} Override data residency region: us | eu`);
|
|
3348
|
+
console.log(` ${chalk11.cyan("--strict")} Fail with non-zero exit code on upload error`);
|
|
2022
3349
|
console.log("");
|
|
2023
|
-
console.log(
|
|
3350
|
+
console.log(chalk11.bold("Examples:"));
|
|
2024
3351
|
console.log(" vibgrate push --dsn $VIBGRATE_DSN");
|
|
2025
3352
|
console.log(" vibgrate push --file ./ci/scan_result.json --strict");
|
|
2026
3353
|
},
|
|
2027
3354
|
dsn: () => {
|
|
2028
3355
|
console.log("");
|
|
2029
|
-
console.log(
|
|
3356
|
+
console.log(chalk11.bold.underline("vibgrate dsn") + chalk11.dim(" \u2014 Manage DSN tokens for API authentication"));
|
|
2030
3357
|
console.log("");
|
|
2031
|
-
console.log(
|
|
2032
|
-
console.log(` ${
|
|
3358
|
+
console.log(chalk11.bold("Subcommands:"));
|
|
3359
|
+
console.log(` ${chalk11.cyan("vibgrate dsn create")} Generate a new DSN token`);
|
|
2033
3360
|
console.log("");
|
|
2034
|
-
console.log(
|
|
2035
|
-
console.log(` ${
|
|
2036
|
-
console.log(` ${
|
|
2037
|
-
console.log(` ${
|
|
2038
|
-
console.log(` ${
|
|
3361
|
+
console.log(chalk11.bold("dsn create options:"));
|
|
3362
|
+
console.log(` ${chalk11.cyan("--workspace <id>")} Workspace ID or "new" to auto-generate ${chalk11.red("(required)")}`);
|
|
3363
|
+
console.log(` ${chalk11.cyan("--region <region>")} Data residency region: us | eu (default: us)`);
|
|
3364
|
+
console.log(` ${chalk11.cyan("--ingest <url>")} Override ingest API URL`);
|
|
3365
|
+
console.log(` ${chalk11.cyan("--write <path>")} Write the DSN to a file (add to .gitignore!)`);
|
|
2039
3366
|
console.log("");
|
|
2040
|
-
console.log(
|
|
3367
|
+
console.log(chalk11.bold("Examples:"));
|
|
2041
3368
|
console.log(" vibgrate dsn create --workspace new");
|
|
2042
3369
|
console.log(" vibgrate dsn create --workspace abc123");
|
|
2043
3370
|
console.log(" vibgrate dsn create --workspace new --region eu --write .vibgrate/.dsn");
|
|
2044
3371
|
},
|
|
2045
3372
|
update: () => {
|
|
2046
3373
|
console.log("");
|
|
2047
|
-
console.log(
|
|
3374
|
+
console.log(chalk11.bold.underline("vibgrate update") + chalk11.dim(" \u2014 Update the vibgrate CLI to the latest version"));
|
|
2048
3375
|
console.log("");
|
|
2049
|
-
console.log(
|
|
3376
|
+
console.log(chalk11.bold("Usage:"));
|
|
2050
3377
|
console.log(" vibgrate update [options]");
|
|
2051
3378
|
console.log("");
|
|
2052
|
-
console.log(
|
|
2053
|
-
console.log(` ${
|
|
2054
|
-
console.log(` ${
|
|
3379
|
+
console.log(chalk11.bold("Options:"));
|
|
3380
|
+
console.log(` ${chalk11.cyan("--check")} Check for a newer version without installing`);
|
|
3381
|
+
console.log(` ${chalk11.cyan("--pm <manager>")} Force a package manager: npm | pnpm | yarn | bun`);
|
|
2055
3382
|
console.log("");
|
|
2056
|
-
console.log(
|
|
3383
|
+
console.log(chalk11.bold("Examples:"));
|
|
2057
3384
|
console.log(" vibgrate update");
|
|
2058
3385
|
console.log(" vibgrate update --check");
|
|
2059
3386
|
console.log(" vibgrate update --pm pnpm");
|
|
@@ -2061,40 +3388,40 @@ var detailedHelp = {
|
|
|
2061
3388
|
};
|
|
2062
3389
|
function printSummaryHelp() {
|
|
2063
3390
|
console.log("");
|
|
2064
|
-
console.log(
|
|
3391
|
+
console.log(chalk11.bold("vibgrate") + chalk11.dim(" \u2014 Continuous Drift Intelligence"));
|
|
2065
3392
|
console.log("");
|
|
2066
|
-
console.log(
|
|
3393
|
+
console.log(chalk11.bold("Usage:"));
|
|
2067
3394
|
console.log(" vibgrate <command> [options]");
|
|
2068
3395
|
console.log(" vibgrate help [command] Show detailed help for a command");
|
|
2069
3396
|
console.log("");
|
|
2070
|
-
console.log(
|
|
2071
|
-
console.log(` ${
|
|
3397
|
+
console.log(chalk11.bold("Getting started:"));
|
|
3398
|
+
console.log(` ${chalk11.cyan("init")} Initialise vibgrate in a project (creates config & .vibgrate/ dir)`);
|
|
2072
3399
|
console.log("");
|
|
2073
|
-
console.log(
|
|
2074
|
-
console.log(` ${
|
|
2075
|
-
console.log(` ${
|
|
3400
|
+
console.log(chalk11.bold("Core scanning:"));
|
|
3401
|
+
console.log(` ${chalk11.cyan("scan")} Scan a project for upgrade drift and generate a report`);
|
|
3402
|
+
console.log(` ${chalk11.cyan("baseline")} Save a baseline snapshot to compare future scans against`);
|
|
2076
3403
|
console.log("");
|
|
2077
|
-
console.log(
|
|
2078
|
-
console.log(` ${
|
|
2079
|
-
console.log(` ${
|
|
3404
|
+
console.log(chalk11.bold("Reporting & export:"));
|
|
3405
|
+
console.log(` ${chalk11.cyan("report")} Re-generate a report from a previously saved scan artifact`);
|
|
3406
|
+
console.log(` ${chalk11.cyan("sbom")} Export a Software Bill of Materials (CycloneDX or SPDX)`);
|
|
2080
3407
|
console.log("");
|
|
2081
|
-
console.log(
|
|
2082
|
-
console.log(` ${
|
|
2083
|
-
console.log(` ${
|
|
3408
|
+
console.log(chalk11.bold("CI/CD integration:"));
|
|
3409
|
+
console.log(` ${chalk11.cyan("push")} Upload a scan artifact to the Vibgrate API`);
|
|
3410
|
+
console.log(` ${chalk11.cyan("dsn")} Create and manage DSN tokens for API authentication`);
|
|
2084
3411
|
console.log("");
|
|
2085
|
-
console.log(
|
|
2086
|
-
console.log(` ${
|
|
3412
|
+
console.log(chalk11.bold("Maintenance:"));
|
|
3413
|
+
console.log(` ${chalk11.cyan("update")} Update the vibgrate CLI to the latest version`);
|
|
2087
3414
|
console.log("");
|
|
2088
|
-
console.log(
|
|
3415
|
+
console.log(chalk11.dim("Run") + ` ${chalk11.cyan("vibgrate help <command>")} ` + chalk11.dim("for detailed options, e.g.") + ` ${chalk11.cyan("vibgrate help scan")}`);
|
|
2089
3416
|
}
|
|
2090
|
-
var helpCommand = new
|
|
3417
|
+
var helpCommand = new Command10("help").description("Show help for vibgrate commands").argument("[command]", "Command to show detailed help for").helpOption(false).action((cmd) => {
|
|
2091
3418
|
const name = cmd?.toLowerCase().trim();
|
|
2092
3419
|
if (name && detailedHelp[name]) {
|
|
2093
3420
|
detailedHelp[name]();
|
|
2094
3421
|
} else if (name) {
|
|
2095
3422
|
console.log("");
|
|
2096
|
-
console.log(
|
|
2097
|
-
console.log(
|
|
3423
|
+
console.log(chalk11.red(`Unknown command: ${name}`));
|
|
3424
|
+
console.log(chalk11.dim(`Available commands: ${Object.keys(detailedHelp).join(", ")}`));
|
|
2098
3425
|
printSummaryHelp();
|
|
2099
3426
|
} else {
|
|
2100
3427
|
printSummaryHelp();
|
|
@@ -2103,7 +3430,7 @@ var helpCommand = new Command7("help").description("Show help for vibgrate comma
|
|
|
2103
3430
|
});
|
|
2104
3431
|
|
|
2105
3432
|
// src/cli.ts
|
|
2106
|
-
var program = new
|
|
3433
|
+
var program = new Command11();
|
|
2107
3434
|
program.name("vibgrate").description("Continuous Drift Intelligence").version(VERSION).addHelpText("after", "\nSee https://vibgrate.com/help for more guidance");
|
|
2108
3435
|
program.addCommand(helpCommand);
|
|
2109
3436
|
program.addCommand(initCommand);
|
|
@@ -2120,8 +3447,8 @@ function notifyIfUpdateAvailable() {
|
|
|
2120
3447
|
void checkForUpdate().then((update) => {
|
|
2121
3448
|
if (!update?.updateAvailable) return;
|
|
2122
3449
|
console.error("");
|
|
2123
|
-
console.error(
|
|
2124
|
-
console.error(
|
|
3450
|
+
console.error(chalk12.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
|
|
3451
|
+
console.error(chalk12.dim(' Run "vibgrate update" to install the latest version.'));
|
|
2125
3452
|
console.error("");
|
|
2126
3453
|
}).catch(() => {
|
|
2127
3454
|
});
|