@vibgrate/cli 1.0.30 → 1.0.31
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.
|
@@ -1015,6 +1015,11 @@ function formatExtended(ext) {
|
|
|
1015
1015
|
if (ss.heuristicFindings.length > 0) {
|
|
1016
1016
|
lines.push(` ${chalk.red("Potential secret signals")}: ${ss.heuristicFindings.length}`);
|
|
1017
1017
|
}
|
|
1018
|
+
const missingNames = tools.filter((t) => !t.available).map((t) => t.name);
|
|
1019
|
+
if (missingNames.length > 0) {
|
|
1020
|
+
lines.push(` ${chalk.dim("Install with:")} ${chalk.cyan(`brew install ${missingNames.join(" ")}`)}`);
|
|
1021
|
+
lines.push(` ${chalk.dim("Or run:")} ${chalk.cyan("vibgrate scan --install-tools")}`);
|
|
1022
|
+
}
|
|
1018
1023
|
lines.push("");
|
|
1019
1024
|
}
|
|
1020
1025
|
if (ext.platformMatrix) {
|
|
@@ -1643,7 +1648,7 @@ var pushCommand = new Command2("push").description("Push scan results to Vibgrat
|
|
|
1643
1648
|
// src/commands/scan.ts
|
|
1644
1649
|
import * as path20 from "path";
|
|
1645
1650
|
import { Command as Command3 } from "commander";
|
|
1646
|
-
import
|
|
1651
|
+
import chalk6 from "chalk";
|
|
1647
1652
|
|
|
1648
1653
|
// src/scanners/node-scanner.ts
|
|
1649
1654
|
import * as path5 from "path";
|
|
@@ -5955,6 +5960,98 @@ async function scanOwaspCategoryMapping(rootDir, cache, options = {}, runner = r
|
|
|
5955
5960
|
};
|
|
5956
5961
|
}
|
|
5957
5962
|
|
|
5963
|
+
// src/utils/tool-installer.ts
|
|
5964
|
+
import { spawn as spawn4 } from "child_process";
|
|
5965
|
+
import chalk5 from "chalk";
|
|
5966
|
+
var SECURITY_TOOLS = [
|
|
5967
|
+
{ name: "semgrep", command: "semgrep", brew: "semgrep", winget: null, scoop: null, pip: "semgrep" },
|
|
5968
|
+
{ name: "gitleaks", command: "gitleaks", brew: "gitleaks", winget: "gitleaks.gitleaks", scoop: "gitleaks", pip: null },
|
|
5969
|
+
{ name: "trufflehog", command: "trufflehog", brew: "trufflehog", winget: null, scoop: "trufflehog", pip: null }
|
|
5970
|
+
];
|
|
5971
|
+
var IS_WIN = process.platform === "win32";
|
|
5972
|
+
function runCommand(cmd, args) {
|
|
5973
|
+
return new Promise((resolve9) => {
|
|
5974
|
+
const child = spawn4(cmd, args, {
|
|
5975
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
5976
|
+
shell: IS_WIN
|
|
5977
|
+
// required for .cmd/.ps1 wrappers on Windows
|
|
5978
|
+
});
|
|
5979
|
+
let stdout = "";
|
|
5980
|
+
let stderr = "";
|
|
5981
|
+
child.stdout.on("data", (d) => {
|
|
5982
|
+
stdout += d.toString();
|
|
5983
|
+
});
|
|
5984
|
+
child.stderr.on("data", (d) => {
|
|
5985
|
+
stderr += d.toString();
|
|
5986
|
+
});
|
|
5987
|
+
child.on("error", () => resolve9({ exitCode: 127, stdout, stderr }));
|
|
5988
|
+
child.on("close", (code) => resolve9({ exitCode: code ?? 1, stdout, stderr }));
|
|
5989
|
+
});
|
|
5990
|
+
}
|
|
5991
|
+
async function commandExists(command) {
|
|
5992
|
+
const checker = IS_WIN ? "where" : "which";
|
|
5993
|
+
const { exitCode } = await runCommand(checker, [command]);
|
|
5994
|
+
return exitCode === 0;
|
|
5995
|
+
}
|
|
5996
|
+
async function tryInstall(tool, strategies, log) {
|
|
5997
|
+
for (const { pm, args } of strategies) {
|
|
5998
|
+
log(chalk5.dim(` ${pm} ${args.join(" ")}\u2026`));
|
|
5999
|
+
const { exitCode, stderr } = await runCommand(pm, args);
|
|
6000
|
+
if (exitCode === 0) {
|
|
6001
|
+
log(` ${chalk5.green("\u2714")} ${tool.name} installed via ${pm}`);
|
|
6002
|
+
return { ok: true, pm };
|
|
6003
|
+
}
|
|
6004
|
+
const hint = stderr.split("\n").find((l) => l.trim().length > 0) ?? "";
|
|
6005
|
+
log(chalk5.dim(` ${pm} failed${hint ? `: ${hint.trim()}` : ""}`));
|
|
6006
|
+
}
|
|
6007
|
+
return { ok: false, pm: "" };
|
|
6008
|
+
}
|
|
6009
|
+
async function installMissingTools(log = (m) => process.stderr.write(m + "\n")) {
|
|
6010
|
+
const result = { installed: [], failed: [], skipped: [], packageManager: null };
|
|
6011
|
+
const missing = [];
|
|
6012
|
+
for (const tool of SECURITY_TOOLS) {
|
|
6013
|
+
if (!await commandExists(tool.command)) {
|
|
6014
|
+
missing.push(tool);
|
|
6015
|
+
}
|
|
6016
|
+
}
|
|
6017
|
+
if (missing.length === 0) return result;
|
|
6018
|
+
const [hasBrew, hasWinget, hasScoop, hasPipx, hasPip] = await Promise.all([
|
|
6019
|
+
commandExists("brew"),
|
|
6020
|
+
IS_WIN ? commandExists("winget") : Promise.resolve(false),
|
|
6021
|
+
IS_WIN ? commandExists("scoop") : Promise.resolve(false),
|
|
6022
|
+
commandExists("pipx"),
|
|
6023
|
+
commandExists("pip")
|
|
6024
|
+
]);
|
|
6025
|
+
log(chalk5.dim(` Installing ${missing.map((t) => t.name).join(", ")}\u2026`));
|
|
6026
|
+
for (const tool of missing) {
|
|
6027
|
+
const strategies = [];
|
|
6028
|
+
if (!IS_WIN) {
|
|
6029
|
+
if (hasBrew) strategies.push({ pm: "brew", args: ["install", tool.brew] });
|
|
6030
|
+
if (tool.pip && hasPipx) strategies.push({ pm: "pipx", args: ["install", tool.pip] });
|
|
6031
|
+
if (tool.pip && hasPip) strategies.push({ pm: "pip", args: ["install", tool.pip] });
|
|
6032
|
+
} else {
|
|
6033
|
+
if (tool.winget && hasWinget) strategies.push({ pm: "winget", args: ["install", "--id", tool.winget, "-e", "--accept-source-agreements", "--accept-package-agreements"] });
|
|
6034
|
+
if (tool.scoop && hasScoop) strategies.push({ pm: "scoop", args: ["install", tool.scoop] });
|
|
6035
|
+
if (tool.pip && hasPipx) strategies.push({ pm: "pipx", args: ["install", tool.pip] });
|
|
6036
|
+
if (tool.pip && hasPip) strategies.push({ pm: "pip", args: ["install", tool.pip] });
|
|
6037
|
+
}
|
|
6038
|
+
if (strategies.length === 0) {
|
|
6039
|
+
result.skipped.push(tool.name);
|
|
6040
|
+
log(` ${chalk5.yellow("\u26A0")} ${tool.name}: no supported package manager found`);
|
|
6041
|
+
continue;
|
|
6042
|
+
}
|
|
6043
|
+
const { ok, pm } = await tryInstall(tool, strategies, log);
|
|
6044
|
+
if (ok) {
|
|
6045
|
+
result.installed.push(tool.name);
|
|
6046
|
+
if (!result.packageManager) result.packageManager = pm;
|
|
6047
|
+
} else {
|
|
6048
|
+
result.failed.push(tool.name);
|
|
6049
|
+
log(` ${chalk5.red("\u2716")} ${tool.name}: all install methods failed`);
|
|
6050
|
+
}
|
|
6051
|
+
}
|
|
6052
|
+
return result;
|
|
6053
|
+
}
|
|
6054
|
+
|
|
5958
6055
|
// src/commands/scan.ts
|
|
5959
6056
|
async function runScan(rootDir, opts) {
|
|
5960
6057
|
const scanStart = Date.now();
|
|
@@ -6001,14 +6098,14 @@ async function runScan(rootDir, opts) {
|
|
|
6001
6098
|
progress.finish();
|
|
6002
6099
|
const msg = [
|
|
6003
6100
|
"",
|
|
6004
|
-
|
|
6101
|
+
chalk6.red.bold(" \u2716 Vibgrate cannot connect to the npm registry to check package versions."),
|
|
6005
6102
|
"",
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6103
|
+
chalk6.dim(" Possible causes:"),
|
|
6104
|
+
chalk6.dim(" \u2022 No internet connection"),
|
|
6105
|
+
chalk6.dim(" \u2022 Corporate proxy/firewall blocking registry.npmjs.org"),
|
|
6106
|
+
chalk6.dim(" \u2022 npm is not installed or not in PATH"),
|
|
6010
6107
|
"",
|
|
6011
|
-
|
|
6108
|
+
chalk6.dim(" Try running: ") + chalk6.cyan("npm view npm dist-tags.latest"),
|
|
6012
6109
|
""
|
|
6013
6110
|
].join("\n");
|
|
6014
6111
|
console.error(msg);
|
|
@@ -6123,6 +6220,13 @@ async function runScan(rootDir, opts) {
|
|
|
6123
6220
|
);
|
|
6124
6221
|
}
|
|
6125
6222
|
if (scanners?.securityScanners?.enabled !== false) {
|
|
6223
|
+
if (opts.installTools) {
|
|
6224
|
+
const installResult = await installMissingTools();
|
|
6225
|
+
if (installResult.installed.length > 0) {
|
|
6226
|
+
process.stderr.write(chalk6.dim(` Installed: ${installResult.installed.join(", ")}
|
|
6227
|
+
`));
|
|
6228
|
+
}
|
|
6229
|
+
}
|
|
6126
6230
|
progress.startStep("secscan");
|
|
6127
6231
|
scannerTasks.push(
|
|
6128
6232
|
scanSecurityScanners(rootDir, fileCache).then((result) => {
|
|
@@ -6265,35 +6369,35 @@ async function runScan(rootDir, opts) {
|
|
|
6265
6369
|
const skippedLarge = fileCache.skippedLargeFiles;
|
|
6266
6370
|
if (stuckPaths.length > 0) {
|
|
6267
6371
|
console.log(
|
|
6268
|
-
|
|
6372
|
+
chalk6.yellow(`
|
|
6269
6373
|
\u26A0 ${stuckPaths.length} path${stuckPaths.length === 1 ? "" : "s"} timed out (>60s) and ${stuckPaths.length === 1 ? "was" : "were"} skipped:`)
|
|
6270
6374
|
);
|
|
6271
6375
|
for (const d of stuckPaths) {
|
|
6272
|
-
console.log(
|
|
6376
|
+
console.log(chalk6.dim(` \u2192 ${d}`));
|
|
6273
6377
|
}
|
|
6274
6378
|
const newExcludes = stuckPaths.map((d) => `${d}/**`);
|
|
6275
6379
|
const updated = await appendExcludePatterns(rootDir, newExcludes);
|
|
6276
6380
|
if (updated) {
|
|
6277
|
-
console.log(
|
|
6381
|
+
console.log(chalk6.green("\u2714") + ` Added ${newExcludes.length} pattern${newExcludes.length !== 1 ? "s" : ""} to exclude list in config`);
|
|
6278
6382
|
}
|
|
6279
6383
|
}
|
|
6280
6384
|
if (skippedLarge.length > 0) {
|
|
6281
6385
|
const sizeLimit = config.maxFileSizeToScan ?? 5242880;
|
|
6282
6386
|
const sizeMB = (sizeLimit / 1048576).toFixed(0);
|
|
6283
6387
|
console.log(
|
|
6284
|
-
|
|
6388
|
+
chalk6.yellow(`
|
|
6285
6389
|
\u26A0 ${skippedLarge.length} file${skippedLarge.length === 1 ? "" : "s"} skipped (>${sizeMB} MB):`)
|
|
6286
6390
|
);
|
|
6287
6391
|
for (const f of skippedLarge.slice(0, 10)) {
|
|
6288
|
-
console.log(
|
|
6392
|
+
console.log(chalk6.dim(` \u2192 ${f}`));
|
|
6289
6393
|
}
|
|
6290
6394
|
if (skippedLarge.length > 10) {
|
|
6291
|
-
console.log(
|
|
6395
|
+
console.log(chalk6.dim(` \u2026 and ${skippedLarge.length - 10} more`));
|
|
6292
6396
|
}
|
|
6293
6397
|
}
|
|
6294
6398
|
fileCache.clear();
|
|
6295
6399
|
if (allProjects.length === 0) {
|
|
6296
|
-
console.log(
|
|
6400
|
+
console.log(chalk6.yellow("No projects found."));
|
|
6297
6401
|
}
|
|
6298
6402
|
if (extended.fileHotspots) filesScanned += extended.fileHotspots.totalFiles;
|
|
6299
6403
|
if (extended.securityPosture) filesScanned += 1;
|
|
@@ -6329,7 +6433,7 @@ async function runScan(rootDir, opts) {
|
|
|
6329
6433
|
artifact.baseline = baselinePath;
|
|
6330
6434
|
artifact.delta = artifact.drift.score - baseline.drift.score;
|
|
6331
6435
|
} catch {
|
|
6332
|
-
console.error(
|
|
6436
|
+
console.error(chalk6.yellow(`Warning: Could not read baseline file: ${baselinePath}`));
|
|
6333
6437
|
}
|
|
6334
6438
|
}
|
|
6335
6439
|
}
|
|
@@ -6366,7 +6470,7 @@ async function runScan(rootDir, opts) {
|
|
|
6366
6470
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
6367
6471
|
if (opts.out) {
|
|
6368
6472
|
await writeTextFile(path20.resolve(opts.out), jsonStr);
|
|
6369
|
-
console.log(
|
|
6473
|
+
console.log(chalk6.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
6370
6474
|
} else {
|
|
6371
6475
|
console.log(jsonStr);
|
|
6372
6476
|
}
|
|
@@ -6375,7 +6479,7 @@ async function runScan(rootDir, opts) {
|
|
|
6375
6479
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
6376
6480
|
if (opts.out) {
|
|
6377
6481
|
await writeTextFile(path20.resolve(opts.out), sarifStr);
|
|
6378
|
-
console.log(
|
|
6482
|
+
console.log(chalk6.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
6379
6483
|
} else {
|
|
6380
6484
|
console.log(sarifStr);
|
|
6381
6485
|
}
|
|
@@ -6391,14 +6495,14 @@ async function runScan(rootDir, opts) {
|
|
|
6391
6495
|
async function autoPush(artifact, rootDir, opts) {
|
|
6392
6496
|
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
6393
6497
|
if (!dsn) {
|
|
6394
|
-
console.error(
|
|
6395
|
-
console.error(
|
|
6498
|
+
console.error(chalk6.red("No DSN provided for push."));
|
|
6499
|
+
console.error(chalk6.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
|
|
6396
6500
|
if (opts.strict) process.exit(1);
|
|
6397
6501
|
return;
|
|
6398
6502
|
}
|
|
6399
6503
|
const parsed = parseDsn(dsn);
|
|
6400
6504
|
if (!parsed) {
|
|
6401
|
-
console.error(
|
|
6505
|
+
console.error(chalk6.red("Invalid DSN format."));
|
|
6402
6506
|
if (opts.strict) process.exit(1);
|
|
6403
6507
|
return;
|
|
6404
6508
|
}
|
|
@@ -6409,13 +6513,13 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
6409
6513
|
try {
|
|
6410
6514
|
host = resolveIngestHost(opts.region);
|
|
6411
6515
|
} catch (e) {
|
|
6412
|
-
console.error(
|
|
6516
|
+
console.error(chalk6.red(e instanceof Error ? e.message : String(e)));
|
|
6413
6517
|
if (opts.strict) process.exit(1);
|
|
6414
6518
|
return;
|
|
6415
6519
|
}
|
|
6416
6520
|
}
|
|
6417
6521
|
const url = `${parsed.scheme}://${host}/v1/ingest/scan`;
|
|
6418
|
-
console.log(
|
|
6522
|
+
console.log(chalk6.dim(`Uploading to ${host}...`));
|
|
6419
6523
|
try {
|
|
6420
6524
|
const response = await fetch(url, {
|
|
6421
6525
|
method: "POST",
|
|
@@ -6431,20 +6535,20 @@ async function autoPush(artifact, rootDir, opts) {
|
|
|
6431
6535
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
6432
6536
|
}
|
|
6433
6537
|
const result = await response.json();
|
|
6434
|
-
console.log(
|
|
6538
|
+
console.log(chalk6.green("\u2714") + ` Uploaded successfully (${result.ingestId ?? "ok"})`);
|
|
6435
6539
|
if (result.ingestId) {
|
|
6436
|
-
console.log(
|
|
6540
|
+
console.log(chalk6.dim(` See Dashboard for full report: `) + chalk6.cyan(`https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.ingestId}`));
|
|
6437
6541
|
}
|
|
6438
6542
|
} catch (e) {
|
|
6439
6543
|
const msg = e instanceof Error ? e.message : String(e);
|
|
6440
|
-
console.error(
|
|
6544
|
+
console.error(chalk6.red(`Upload failed: ${msg}`));
|
|
6441
6545
|
if (opts.strict) process.exit(1);
|
|
6442
6546
|
}
|
|
6443
6547
|
}
|
|
6444
|
-
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)", "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").action(async (targetPath, opts) => {
|
|
6548
|
+
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)", "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("--install-tools", "Auto-install missing security scanners via Homebrew").action(async (targetPath, opts) => {
|
|
6445
6549
|
const rootDir = path20.resolve(targetPath);
|
|
6446
6550
|
if (!await pathExists(rootDir)) {
|
|
6447
|
-
console.error(
|
|
6551
|
+
console.error(chalk6.red(`Path does not exist: ${rootDir}`));
|
|
6448
6552
|
process.exit(1);
|
|
6449
6553
|
}
|
|
6450
6554
|
const scanOpts = {
|
|
@@ -6457,19 +6561,20 @@ var scanCommand = new Command3("scan").description("Scan a project for upgrade d
|
|
|
6457
6561
|
push: opts.push,
|
|
6458
6562
|
dsn: opts.dsn,
|
|
6459
6563
|
region: opts.region,
|
|
6460
|
-
strict: opts.strict
|
|
6564
|
+
strict: opts.strict,
|
|
6565
|
+
installTools: opts.installTools
|
|
6461
6566
|
};
|
|
6462
6567
|
const artifact = await runScan(rootDir, scanOpts);
|
|
6463
6568
|
if (opts.failOn) {
|
|
6464
6569
|
const hasErrors = artifact.findings.some((f) => f.level === "error");
|
|
6465
6570
|
const hasWarnings = artifact.findings.some((f) => f.level === "warning");
|
|
6466
6571
|
if (opts.failOn === "error" && hasErrors) {
|
|
6467
|
-
console.error(
|
|
6572
|
+
console.error(chalk6.red(`
|
|
6468
6573
|
Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
|
|
6469
6574
|
process.exit(2);
|
|
6470
6575
|
}
|
|
6471
6576
|
if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
|
|
6472
|
-
console.error(
|
|
6577
|
+
console.error(chalk6.red(`
|
|
6473
6578
|
Failing: findings detected at warn level or above.`));
|
|
6474
6579
|
process.exit(2);
|
|
6475
6580
|
}
|
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-GN3IWKSY.js";
|
|
5
5
|
import {
|
|
6
6
|
baselineCommand
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-BHUP76M7.js";
|
|
8
8
|
import {
|
|
9
9
|
VERSION,
|
|
10
10
|
dsnCommand,
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
readJsonFile,
|
|
16
16
|
scanCommand,
|
|
17
17
|
writeDefaultConfig
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-B4D6EY5P.js";
|
|
19
19
|
|
|
20
20
|
// src/cli.ts
|
|
21
21
|
import { Command as Command4 } from "commander";
|
|
@@ -38,7 +38,7 @@ var initCommand = new Command("init").description("Initialize vibgrate in a proj
|
|
|
38
38
|
console.log(chalk.green("\u2714") + ` Created ${chalk.bold("vibgrate.config.ts")}`);
|
|
39
39
|
}
|
|
40
40
|
if (opts.baseline) {
|
|
41
|
-
const { runBaseline } = await import("./baseline-
|
|
41
|
+
const { runBaseline } = await import("./baseline-IWG6EIKC.js");
|
|
42
42
|
await runBaseline(rootDir);
|
|
43
43
|
}
|
|
44
44
|
console.log("");
|
package/dist/index.d.ts
CHANGED
|
@@ -103,6 +103,8 @@ interface ScanOptions {
|
|
|
103
103
|
region?: string;
|
|
104
104
|
/** Fail on push errors (like --strict on push command) */
|
|
105
105
|
strict?: boolean;
|
|
106
|
+
/** Auto-install missing security tools via Homebrew */
|
|
107
|
+
installTools?: boolean;
|
|
106
108
|
}
|
|
107
109
|
interface ScannerToggle {
|
|
108
110
|
enabled: boolean;
|
package/dist/index.js
CHANGED