@vibgrate/cli 1.0.45 → 1.0.47
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 +293 -95
- package/README.md +212 -206
- package/dist/{baseline-FDWMBM2O.js → baseline-K7V6GAP3.js} +2 -2
- package/dist/{chunk-LO66M6OC.js → chunk-NASGRGXK.js} +1 -1
- package/dist/{chunk-GN3IWKSY.js → chunk-PTMLMDZU.js} +20 -0
- package/dist/{chunk-YFJC5JSQ.js → chunk-UVFIFNYG.js} +966 -305
- package/dist/cli.js +190 -10
- package/dist/index.d.ts +69 -0
- package/dist/index.js +2 -2
- package/package.json +1 -1
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-NASGRGXK.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-UVFIFNYG.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-K7V6GAP3.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
|
@@ -31,6 +31,10 @@ interface ProjectScan {
|
|
|
31
31
|
name: string;
|
|
32
32
|
/** Deterministic project ID: SHA-256 hash of `${path}:${name}:${workspaceId}` */
|
|
33
33
|
projectId?: string;
|
|
34
|
+
/** Optional solution identifier when project belongs to a solution/workspace file */
|
|
35
|
+
solutionId?: string;
|
|
36
|
+
/** Optional solution name resolved from solution/workspace metadata */
|
|
37
|
+
solutionName?: string;
|
|
34
38
|
runtime?: string;
|
|
35
39
|
runtimeLatest?: string;
|
|
36
40
|
runtimeMajorsBehind?: number;
|
|
@@ -49,6 +53,30 @@ interface ProjectScan {
|
|
|
49
53
|
projectReferences?: ProjectReference[];
|
|
50
54
|
/** Number of source files in the project directory */
|
|
51
55
|
fileCount?: number;
|
|
56
|
+
/** Project-level architecture layer diagram (Mermaid flowchart) */
|
|
57
|
+
architectureMermaid?: string;
|
|
58
|
+
/** Project-level relationship diagram (first-level parents + children) */
|
|
59
|
+
relationshipDiagram?: MermaidDiagram;
|
|
60
|
+
}
|
|
61
|
+
interface SolutionScan {
|
|
62
|
+
/** Deterministic solution ID: SHA-256 hash of `${path}:${name}:${workspaceId}` */
|
|
63
|
+
solutionId: string;
|
|
64
|
+
/** Relative path to solution file */
|
|
65
|
+
path: string;
|
|
66
|
+
/** Solution display name */
|
|
67
|
+
name: string;
|
|
68
|
+
/** Solution file type */
|
|
69
|
+
type: 'dotnet-sln';
|
|
70
|
+
/** Projects resolved as belonging to this solution (by relative project path) */
|
|
71
|
+
projectPaths: string[];
|
|
72
|
+
/** Aggregate drift score for all resolved projects in this solution */
|
|
73
|
+
drift?: DriftScore;
|
|
74
|
+
/** Solution relationship diagram with top-level solution node and project links */
|
|
75
|
+
relationshipDiagram?: MermaidDiagram;
|
|
76
|
+
}
|
|
77
|
+
interface MermaidDiagram {
|
|
78
|
+
mermaid: string;
|
|
79
|
+
svg?: string;
|
|
52
80
|
}
|
|
53
81
|
interface DriftScore {
|
|
54
82
|
score: number;
|
|
@@ -75,6 +103,13 @@ interface VcsInfo {
|
|
|
75
103
|
sha?: string;
|
|
76
104
|
shortSha?: string;
|
|
77
105
|
branch?: string;
|
|
106
|
+
remoteUrl?: string;
|
|
107
|
+
}
|
|
108
|
+
interface RepositoryInfo {
|
|
109
|
+
name: string;
|
|
110
|
+
version?: string;
|
|
111
|
+
pipeline?: string;
|
|
112
|
+
remoteUrl?: string;
|
|
78
113
|
}
|
|
79
114
|
interface TreeCount {
|
|
80
115
|
/** Total files discovered (excluding skipped dirs like node_modules, .git, dist) */
|
|
@@ -88,7 +123,9 @@ interface ScanArtifact {
|
|
|
88
123
|
vibgrateVersion: string;
|
|
89
124
|
rootPath: string;
|
|
90
125
|
vcs?: VcsInfo;
|
|
126
|
+
repository?: RepositoryInfo;
|
|
91
127
|
projects: ProjectScan[];
|
|
128
|
+
solutions?: SolutionScan[];
|
|
92
129
|
drift: DriftScore;
|
|
93
130
|
findings: Finding[];
|
|
94
131
|
baseline?: string;
|
|
@@ -100,6 +137,8 @@ interface ScanArtifact {
|
|
|
100
137
|
filesScanned?: number;
|
|
101
138
|
/** Workspace tree summary (file & directory counts from discovery) */
|
|
102
139
|
treeSummary?: TreeCount;
|
|
140
|
+
/** Workspace-level relationship diagram */
|
|
141
|
+
relationshipDiagram?: MermaidDiagram;
|
|
103
142
|
}
|
|
104
143
|
interface ScanOptions {
|
|
105
144
|
out?: string;
|
|
@@ -117,6 +156,20 @@ interface ScanOptions {
|
|
|
117
156
|
strict?: boolean;
|
|
118
157
|
/** Auto-install missing security tools via Homebrew */
|
|
119
158
|
installTools?: boolean;
|
|
159
|
+
/** Enable optional UI-purpose evidence extraction (slower, richer context for dashboard) */
|
|
160
|
+
uiPurpose?: boolean;
|
|
161
|
+
/** Prevent writing .vibgrate JSON artifacts to disk */
|
|
162
|
+
noLocalArtifacts?: boolean;
|
|
163
|
+
/** Enable strongest privacy profile: minimize scanners and suppress local artifacts */
|
|
164
|
+
maxPrivacy?: boolean;
|
|
165
|
+
/** Run without any network calls; drift may be partial without a package manifest */
|
|
166
|
+
offline?: boolean;
|
|
167
|
+
/** Path to package-version manifest JSON or ZIP used in offline/privacy workflows */
|
|
168
|
+
packageManifest?: string;
|
|
169
|
+
/** Fail the run if drift score is above this absolute budget */
|
|
170
|
+
driftBudget?: number;
|
|
171
|
+
/** Fail when drift worsens by more than this percentage vs baseline */
|
|
172
|
+
driftWorseningPercent?: number;
|
|
120
173
|
}
|
|
121
174
|
interface ScannerToggle {
|
|
122
175
|
enabled: boolean;
|
|
@@ -140,6 +193,7 @@ interface ScannersConfig {
|
|
|
140
193
|
architecture?: ScannerToggle;
|
|
141
194
|
codeQuality?: ScannerToggle;
|
|
142
195
|
owaspCategoryMapping?: OwaspScannerConfig;
|
|
196
|
+
uiPurpose?: ScannerToggle;
|
|
143
197
|
}
|
|
144
198
|
interface VibgrateConfig {
|
|
145
199
|
include?: string[];
|
|
@@ -363,6 +417,20 @@ interface CodeQualityResult {
|
|
|
363
417
|
circularDependencies: number;
|
|
364
418
|
deadCodePercent: number;
|
|
365
419
|
}
|
|
420
|
+
interface UiPurposeEvidenceItem {
|
|
421
|
+
kind: 'route' | 'nav' | 'title' | 'heading' | 'cta' | 'copy' | 'dependency' | 'feature_flag';
|
|
422
|
+
value: string;
|
|
423
|
+
file: string;
|
|
424
|
+
weight: number;
|
|
425
|
+
}
|
|
426
|
+
interface UiPurposeResult {
|
|
427
|
+
enabled: boolean;
|
|
428
|
+
detectedFrameworks: string[];
|
|
429
|
+
evidenceCount: number;
|
|
430
|
+
capped: boolean;
|
|
431
|
+
topEvidence: UiPurposeEvidenceItem[];
|
|
432
|
+
unknownSignals: string[];
|
|
433
|
+
}
|
|
366
434
|
interface ExtendedScanResults {
|
|
367
435
|
platformMatrix?: PlatformMatrixResult;
|
|
368
436
|
dependencyRisk?: DependencyRiskResult;
|
|
@@ -378,6 +446,7 @@ interface ExtendedScanResults {
|
|
|
378
446
|
architecture?: ArchitectureResult;
|
|
379
447
|
codeQuality?: CodeQualityResult;
|
|
380
448
|
owaspCategoryMapping?: OwaspCategoryMappingResult;
|
|
449
|
+
uiPurpose?: UiPurposeResult;
|
|
381
450
|
}
|
|
382
451
|
interface OwaspFinding {
|
|
383
452
|
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-UVFIFNYG.js";
|
|
11
11
|
import "./chunk-RNVZIZNL.js";
|
|
12
12
|
export {
|
|
13
13
|
computeDriftScore,
|