@vibgrate/cli 1.0.44 → 1.0.46
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/DOCS.md +202 -89
- package/README.md +150 -209
- package/dist/{baseline-FDWMBM2O.js → baseline-AWRL3ITR.js} +2 -2
- package/dist/{chunk-YFJC5JSQ.js → chunk-HEILEAVO.js} +442 -127
- package/dist/{chunk-GN3IWKSY.js → chunk-PTMLMDZU.js} +20 -0
- package/dist/{chunk-LO66M6OC.js → chunk-SKROLJET.js} +1 -1
- package/dist/cli.js +190 -10
- package/dist/index.d.ts +32 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
|
@@ -51,6 +51,26 @@ function formatMarkdown(artifact) {
|
|
|
51
51
|
}
|
|
52
52
|
lines.push("");
|
|
53
53
|
}
|
|
54
|
+
if (artifact.extended?.uiPurpose) {
|
|
55
|
+
const up = artifact.extended.uiPurpose;
|
|
56
|
+
lines.push("## Product Purpose Signals");
|
|
57
|
+
lines.push("");
|
|
58
|
+
lines.push(`- **Frameworks:** ${up.detectedFrameworks.length > 0 ? up.detectedFrameworks.join(", ") : "unknown"}`);
|
|
59
|
+
lines.push(`- **Evidence Items:** ${up.topEvidence.length}${up.capped ? ` (capped from ${up.evidenceCount})` : ""}`);
|
|
60
|
+
if (up.topEvidence.length > 0) {
|
|
61
|
+
lines.push("- **Top Evidence:**");
|
|
62
|
+
for (const item of up.topEvidence.slice(0, 10)) {
|
|
63
|
+
lines.push(` - [${item.kind}] ${item.value} (${item.file})`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (up.unknownSignals.length > 0) {
|
|
67
|
+
lines.push("- **Unknowns:**");
|
|
68
|
+
for (const u of up.unknownSignals.slice(0, 5)) {
|
|
69
|
+
lines.push(` - ${u}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
lines.push("");
|
|
73
|
+
}
|
|
54
74
|
if (artifact.findings.length > 0) {
|
|
55
75
|
lines.push("## Findings");
|
|
56
76
|
lines.push("");
|
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
formatMarkdown
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PTMLMDZU.js";
|
|
5
5
|
import {
|
|
6
6
|
baselineCommand
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-SKROLJET.js";
|
|
8
8
|
import {
|
|
9
9
|
VERSION,
|
|
10
10
|
dsnCommand,
|
|
@@ -12,16 +12,17 @@ import {
|
|
|
12
12
|
pushCommand,
|
|
13
13
|
scanCommand,
|
|
14
14
|
writeDefaultConfig
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-HEILEAVO.js";
|
|
16
16
|
import {
|
|
17
17
|
ensureDir,
|
|
18
18
|
pathExists,
|
|
19
|
-
readJsonFile
|
|
19
|
+
readJsonFile,
|
|
20
|
+
writeTextFile
|
|
20
21
|
} from "./chunk-RNVZIZNL.js";
|
|
21
22
|
|
|
22
23
|
// src/cli.ts
|
|
23
|
-
import { Command as
|
|
24
|
-
import
|
|
24
|
+
import { Command as Command5 } from "commander";
|
|
25
|
+
import chalk5 from "chalk";
|
|
25
26
|
|
|
26
27
|
// src/commands/init.ts
|
|
27
28
|
import * as path from "path";
|
|
@@ -40,7 +41,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
40
41
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
41
42
|
}
|
|
42
43
|
if (opts.baseline) {
|
|
43
|
-
const { runBaseline } = await import("./baseline-
|
|
44
|
+
const { runBaseline } = await import("./baseline-AWRL3ITR.js");
|
|
44
45
|
await runBaseline(rootDir);
|
|
45
46
|
}
|
|
46
47
|
console.log("");
|
|
@@ -222,8 +223,186 @@ var updateCommand = new Command3("update").description("Update vibgrate to the l
|
|
|
222
223
|
}
|
|
223
224
|
});
|
|
224
225
|
|
|
226
|
+
// src/commands/sbom.ts
|
|
227
|
+
import * as path5 from "path";
|
|
228
|
+
import { randomUUID } from "crypto";
|
|
229
|
+
import { Command as Command4 } from "commander";
|
|
230
|
+
import chalk4 from "chalk";
|
|
231
|
+
function flattenDependencies(artifact) {
|
|
232
|
+
const rows = [];
|
|
233
|
+
for (const project of artifact.projects) {
|
|
234
|
+
for (const dep of project.dependencies) {
|
|
235
|
+
rows.push({
|
|
236
|
+
project: project.name,
|
|
237
|
+
package: dep.package,
|
|
238
|
+
version: dep.resolvedVersion ?? dep.currentSpec,
|
|
239
|
+
currentSpec: dep.currentSpec,
|
|
240
|
+
drift: dep.drift,
|
|
241
|
+
majorsBehind: dep.majorsBehind
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return rows;
|
|
246
|
+
}
|
|
247
|
+
function toCycloneDx(artifact) {
|
|
248
|
+
const dependencies = flattenDependencies(artifact);
|
|
249
|
+
return {
|
|
250
|
+
bomFormat: "CycloneDX",
|
|
251
|
+
specVersion: "1.5",
|
|
252
|
+
serialNumber: `urn:uuid:${randomUUID()}`,
|
|
253
|
+
version: 1,
|
|
254
|
+
metadata: {
|
|
255
|
+
timestamp: artifact.timestamp,
|
|
256
|
+
tools: [
|
|
257
|
+
{
|
|
258
|
+
vendor: "Vibgrate",
|
|
259
|
+
name: "@vibgrate/cli",
|
|
260
|
+
version: artifact.vibgrateVersion
|
|
261
|
+
}
|
|
262
|
+
],
|
|
263
|
+
component: {
|
|
264
|
+
type: "application",
|
|
265
|
+
name: artifact.rootPath
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
components: dependencies.map((dep) => ({
|
|
269
|
+
type: "library",
|
|
270
|
+
name: dep.package,
|
|
271
|
+
version: dep.version,
|
|
272
|
+
properties: [
|
|
273
|
+
{ name: "vibgrate:project", value: dep.project },
|
|
274
|
+
{ name: "vibgrate:currentSpec", value: dep.currentSpec },
|
|
275
|
+
{ name: "vibgrate:drift", value: dep.drift },
|
|
276
|
+
{ name: "vibgrate:majorsBehind", value: String(dep.majorsBehind ?? "unknown") }
|
|
277
|
+
]
|
|
278
|
+
}))
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
function toSpdx(artifact) {
|
|
282
|
+
const dependencies = flattenDependencies(artifact);
|
|
283
|
+
return {
|
|
284
|
+
spdxVersion: "SPDX-2.3",
|
|
285
|
+
dataLicense: "CC0-1.0",
|
|
286
|
+
SPDXID: "SPDXRef-DOCUMENT",
|
|
287
|
+
name: `${artifact.rootPath}-sbom`,
|
|
288
|
+
documentNamespace: `https://vibgrate.com/spdx/${artifact.rootPath}/${randomUUID()}`,
|
|
289
|
+
creationInfo: {
|
|
290
|
+
created: artifact.timestamp,
|
|
291
|
+
creators: [`Tool: @vibgrate/cli-${artifact.vibgrateVersion}`]
|
|
292
|
+
},
|
|
293
|
+
packages: dependencies.map((dep, i) => ({
|
|
294
|
+
name: dep.package,
|
|
295
|
+
SPDXID: `SPDXRef-Package-${i + 1}`,
|
|
296
|
+
versionInfo: dep.version,
|
|
297
|
+
downloadLocation: "NOASSERTION",
|
|
298
|
+
filesAnalyzed: false,
|
|
299
|
+
externalRefs: [
|
|
300
|
+
{
|
|
301
|
+
referenceCategory: "PACKAGE-MANAGER",
|
|
302
|
+
referenceType: "purl",
|
|
303
|
+
referenceLocator: `pkg:npm/${encodeURIComponent(dep.package)}@${encodeURIComponent(dep.version)}`
|
|
304
|
+
}
|
|
305
|
+
],
|
|
306
|
+
annotations: [
|
|
307
|
+
{
|
|
308
|
+
annotationType: "OTHER",
|
|
309
|
+
annotator: "Tool: @vibgrate/cli",
|
|
310
|
+
annotationDate: artifact.timestamp,
|
|
311
|
+
comment: `project=${dep.project}; drift=${dep.drift}; majorsBehind=${dep.majorsBehind ?? "unknown"}`
|
|
312
|
+
}
|
|
313
|
+
]
|
|
314
|
+
}))
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function projectDependencyMap(artifact) {
|
|
318
|
+
const map = /* @__PURE__ */ new Map();
|
|
319
|
+
for (const project of artifact.projects) {
|
|
320
|
+
for (const dep of project.dependencies) {
|
|
321
|
+
map.set(`${project.name}:${dep.package}`, dep);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return map;
|
|
325
|
+
}
|
|
326
|
+
function formatDeltaText(base, current) {
|
|
327
|
+
const baseMap = projectDependencyMap(base);
|
|
328
|
+
const currentMap = projectDependencyMap(current);
|
|
329
|
+
const added = [];
|
|
330
|
+
const removed = [];
|
|
331
|
+
const changed = [];
|
|
332
|
+
for (const [key, dep] of currentMap.entries()) {
|
|
333
|
+
if (!baseMap.has(key)) {
|
|
334
|
+
added.push(`${key} @ ${dep.resolvedVersion ?? dep.currentSpec}`);
|
|
335
|
+
continue;
|
|
336
|
+
}
|
|
337
|
+
const prev = baseMap.get(key);
|
|
338
|
+
const prevVersion = prev.resolvedVersion ?? prev.currentSpec;
|
|
339
|
+
const nowVersion = dep.resolvedVersion ?? dep.currentSpec;
|
|
340
|
+
if (prevVersion !== nowVersion || prev.majorsBehind !== dep.majorsBehind) {
|
|
341
|
+
changed.push(`${key} ${prevVersion} -> ${nowVersion} (majorsBehind ${prev.majorsBehind ?? "unknown"} -> ${dep.majorsBehind ?? "unknown"})`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const [key, dep] of baseMap.entries()) {
|
|
345
|
+
if (!currentMap.has(key)) {
|
|
346
|
+
removed.push(`${key} @ ${dep.resolvedVersion ?? dep.currentSpec}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
const lines = [
|
|
350
|
+
"Vibgrate SBOM Delta",
|
|
351
|
+
"===================",
|
|
352
|
+
`Baseline: ${base.timestamp}`,
|
|
353
|
+
`Current: ${current.timestamp}`,
|
|
354
|
+
`Drift score delta: ${(current.drift.score - base.drift.score).toFixed(2)} points`,
|
|
355
|
+
"",
|
|
356
|
+
`Added dependencies (${added.length})`,
|
|
357
|
+
...added.map((d) => ` + ${d}`),
|
|
358
|
+
"",
|
|
359
|
+
`Removed dependencies (${removed.length})`,
|
|
360
|
+
...removed.map((d) => ` - ${d}`),
|
|
361
|
+
"",
|
|
362
|
+
`Changed dependencies (${changed.length})`,
|
|
363
|
+
...changed.map((d) => ` * ${d}`)
|
|
364
|
+
];
|
|
365
|
+
return lines.join("\n");
|
|
366
|
+
}
|
|
367
|
+
async function readArtifactOrExit(filePath) {
|
|
368
|
+
const absolutePath = path5.resolve(filePath);
|
|
369
|
+
if (!await pathExists(absolutePath)) {
|
|
370
|
+
console.error(chalk4.red(`Artifact not found: ${absolutePath}`));
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
return readJsonFile(absolutePath);
|
|
374
|
+
}
|
|
375
|
+
var exportCommand = new Command4("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) => {
|
|
376
|
+
const artifact = await readArtifactOrExit(opts.in);
|
|
377
|
+
const format = opts.format.toLowerCase();
|
|
378
|
+
if (format !== "cyclonedx" && format !== "spdx") {
|
|
379
|
+
console.error(chalk4.red("Invalid SBOM format. Use cyclonedx or spdx."));
|
|
380
|
+
process.exit(1);
|
|
381
|
+
}
|
|
382
|
+
const sbom = format === "cyclonedx" ? toCycloneDx(artifact) : toSpdx(artifact);
|
|
383
|
+
const body = JSON.stringify(sbom, null, 2);
|
|
384
|
+
if (opts.out) {
|
|
385
|
+
await writeTextFile(path5.resolve(opts.out), body);
|
|
386
|
+
console.log(chalk4.green("\u2714") + ` SBOM written to ${opts.out}`);
|
|
387
|
+
} else {
|
|
388
|
+
console.log(body);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
var deltaCommand = new Command4("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) => {
|
|
392
|
+
const base = await readArtifactOrExit(opts.from);
|
|
393
|
+
const current = await readArtifactOrExit(opts.to);
|
|
394
|
+
const report = formatDeltaText(base, current);
|
|
395
|
+
if (opts.out) {
|
|
396
|
+
await writeTextFile(path5.resolve(opts.out), report);
|
|
397
|
+
console.log(chalk4.green("\u2714") + ` SBOM delta report written to ${opts.out}`);
|
|
398
|
+
} else {
|
|
399
|
+
console.log(report);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
var sbomCommand = new Command4("sbom").description("SBOM export and delta reports for dependency drift tracking").addCommand(exportCommand).addCommand(deltaCommand);
|
|
403
|
+
|
|
225
404
|
// src/cli.ts
|
|
226
|
-
var program = new
|
|
405
|
+
var program = new Command5();
|
|
227
406
|
program.name("vibgrate").description("Continuous Drift Intelligence for Node & .NET").version(VERSION);
|
|
228
407
|
program.addCommand(initCommand);
|
|
229
408
|
program.addCommand(scanCommand);
|
|
@@ -232,12 +411,13 @@ program.addCommand(reportCommand);
|
|
|
232
411
|
program.addCommand(dsnCommand);
|
|
233
412
|
program.addCommand(pushCommand);
|
|
234
413
|
program.addCommand(updateCommand);
|
|
414
|
+
program.addCommand(sbomCommand);
|
|
235
415
|
program.parseAsync().then(async () => {
|
|
236
416
|
const update = await checkForUpdate();
|
|
237
417
|
if (update?.updateAvailable) {
|
|
238
418
|
console.error("");
|
|
239
|
-
console.error(
|
|
240
|
-
console.error(
|
|
419
|
+
console.error(chalk5.yellow(` Update available: ${update.current} \u2192 ${update.latest}`));
|
|
420
|
+
console.error(chalk5.dim(' Run "vibgrate update" to install the latest version.'));
|
|
241
421
|
console.error("");
|
|
242
422
|
}
|
|
243
423
|
}).catch((err) => {
|
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,14 @@ interface ProjectScan {
|
|
|
49
49
|
projectReferences?: ProjectReference[];
|
|
50
50
|
/** Number of source files in the project directory */
|
|
51
51
|
fileCount?: number;
|
|
52
|
+
/** Project-level architecture layer diagram (Mermaid flowchart) */
|
|
53
|
+
architectureMermaid?: string;
|
|
54
|
+
/** Project-level relationship diagram (first-level parents + children) */
|
|
55
|
+
relationshipDiagram?: MermaidDiagram;
|
|
56
|
+
}
|
|
57
|
+
interface MermaidDiagram {
|
|
58
|
+
mermaid: string;
|
|
59
|
+
svg?: string;
|
|
52
60
|
}
|
|
53
61
|
interface DriftScore {
|
|
54
62
|
score: number;
|
|
@@ -100,6 +108,8 @@ interface ScanArtifact {
|
|
|
100
108
|
filesScanned?: number;
|
|
101
109
|
/** Workspace tree summary (file & directory counts from discovery) */
|
|
102
110
|
treeSummary?: TreeCount;
|
|
111
|
+
/** Workspace-level relationship diagram */
|
|
112
|
+
relationshipDiagram?: MermaidDiagram;
|
|
103
113
|
}
|
|
104
114
|
interface ScanOptions {
|
|
105
115
|
out?: string;
|
|
@@ -117,6 +127,12 @@ interface ScanOptions {
|
|
|
117
127
|
strict?: boolean;
|
|
118
128
|
/** Auto-install missing security tools via Homebrew */
|
|
119
129
|
installTools?: boolean;
|
|
130
|
+
/** Enable optional UI-purpose evidence extraction (slower, richer context for dashboard) */
|
|
131
|
+
uiPurpose?: boolean;
|
|
132
|
+
/** Fail the run if drift score is above this absolute budget */
|
|
133
|
+
driftBudget?: number;
|
|
134
|
+
/** Fail when drift worsens by more than this percentage vs baseline */
|
|
135
|
+
driftWorseningPercent?: number;
|
|
120
136
|
}
|
|
121
137
|
interface ScannerToggle {
|
|
122
138
|
enabled: boolean;
|
|
@@ -140,6 +156,7 @@ interface ScannersConfig {
|
|
|
140
156
|
architecture?: ScannerToggle;
|
|
141
157
|
codeQuality?: ScannerToggle;
|
|
142
158
|
owaspCategoryMapping?: OwaspScannerConfig;
|
|
159
|
+
uiPurpose?: ScannerToggle;
|
|
143
160
|
}
|
|
144
161
|
interface VibgrateConfig {
|
|
145
162
|
include?: string[];
|
|
@@ -363,6 +380,20 @@ interface CodeQualityResult {
|
|
|
363
380
|
circularDependencies: number;
|
|
364
381
|
deadCodePercent: number;
|
|
365
382
|
}
|
|
383
|
+
interface UiPurposeEvidenceItem {
|
|
384
|
+
kind: 'route' | 'nav' | 'title' | 'heading' | 'cta' | 'copy' | 'dependency' | 'feature_flag';
|
|
385
|
+
value: string;
|
|
386
|
+
file: string;
|
|
387
|
+
weight: number;
|
|
388
|
+
}
|
|
389
|
+
interface UiPurposeResult {
|
|
390
|
+
enabled: boolean;
|
|
391
|
+
detectedFrameworks: string[];
|
|
392
|
+
evidenceCount: number;
|
|
393
|
+
capped: boolean;
|
|
394
|
+
topEvidence: UiPurposeEvidenceItem[];
|
|
395
|
+
unknownSignals: string[];
|
|
396
|
+
}
|
|
366
397
|
interface ExtendedScanResults {
|
|
367
398
|
platformMatrix?: PlatformMatrixResult;
|
|
368
399
|
dependencyRisk?: DependencyRiskResult;
|
|
@@ -378,6 +409,7 @@ interface ExtendedScanResults {
|
|
|
378
409
|
architecture?: ArchitectureResult;
|
|
379
410
|
codeQuality?: CodeQualityResult;
|
|
380
411
|
owaspCategoryMapping?: OwaspCategoryMappingResult;
|
|
412
|
+
uiPurpose?: UiPurposeResult;
|
|
381
413
|
}
|
|
382
414
|
interface OwaspFinding {
|
|
383
415
|
ruleId: string;
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatMarkdown
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PTMLMDZU.js";
|
|
4
4
|
import {
|
|
5
5
|
computeDriftScore,
|
|
6
6
|
formatSarif,
|
|
7
7
|
formatText,
|
|
8
8
|
generateFindings,
|
|
9
9
|
runScan
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HEILEAVO.js";
|
|
11
11
|
import "./chunk-RNVZIZNL.js";
|
|
12
12
|
export {
|
|
13
13
|
computeDriftScore,
|