@vibgrate/cli 1.0.29 → 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.
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  baselineCommand,
3
3
  runBaseline
4
- } from "./chunk-T4GNX4OC.js";
5
- import "./chunk-XLRCQ476.js";
4
+ } from "./chunk-BHUP76M7.js";
5
+ import "./chunk-B4D6EY5P.js";
6
6
  export {
7
7
  baselineCommand,
8
8
  runBaseline
@@ -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 chalk5 from "chalk";
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
- chalk5.red.bold(" \u2716 Vibgrate cannot connect to the npm registry to check package versions."),
6101
+ chalk6.red.bold(" \u2716 Vibgrate cannot connect to the npm registry to check package versions."),
6005
6102
  "",
6006
- chalk5.dim(" Possible causes:"),
6007
- chalk5.dim(" \u2022 No internet connection"),
6008
- chalk5.dim(" \u2022 Corporate proxy/firewall blocking registry.npmjs.org"),
6009
- chalk5.dim(" \u2022 npm is not installed or not in PATH"),
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
- chalk5.dim(" Try running: ") + chalk5.cyan("npm view npm dist-tags.latest"),
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
- chalk5.yellow(`
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(chalk5.dim(` \u2192 ${d}`));
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(chalk5.green("\u2714") + ` Added ${newExcludes.length} pattern${newExcludes.length !== 1 ? "s" : ""} to exclude list in config`);
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
- chalk5.yellow(`
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(chalk5.dim(` \u2192 ${f}`));
6392
+ console.log(chalk6.dim(` \u2192 ${f}`));
6289
6393
  }
6290
6394
  if (skippedLarge.length > 10) {
6291
- console.log(chalk5.dim(` \u2026 and ${skippedLarge.length - 10} more`));
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(chalk5.yellow("No projects found."));
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(chalk5.yellow(`Warning: Could not read baseline file: ${baselinePath}`));
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(chalk5.green("\u2714") + ` JSON written to ${opts.out}`);
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(chalk5.green("\u2714") + ` SARIF written to ${opts.out}`);
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(chalk5.red("No DSN provided for push."));
6395
- console.error(chalk5.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
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(chalk5.red("Invalid DSN format."));
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(chalk5.red(e instanceof Error ? e.message : String(e)));
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(chalk5.dim(`Uploading to ${host}...`));
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(chalk5.green("\u2714") + ` Uploaded successfully (${result.ingestId ?? "ok"})`);
6538
+ console.log(chalk6.green("\u2714") + ` Uploaded successfully (${result.ingestId ?? "ok"})`);
6435
6539
  if (result.ingestId) {
6436
- console.log(chalk5.dim(` See Dashboard for full report: `) + chalk5.cyan(`https://dash.vibgrate.com/${parsed.workspaceId}/scan/${result.ingestId}`));
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(chalk5.red(`Upload failed: ${msg}`));
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(chalk5.red(`Path does not exist: ${rootDir}`));
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(chalk5.red(`
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(chalk5.red(`
6577
+ console.error(chalk6.red(`
6473
6578
  Failing: findings detected at warn level or above.`));
6474
6579
  process.exit(2);
6475
6580
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  runScan,
3
3
  writeJsonFile
4
- } from "./chunk-XLRCQ476.js";
4
+ } from "./chunk-B4D6EY5P.js";
5
5
 
6
6
  // src/commands/baseline.ts
7
7
  import * as path from "path";
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-T4GNX4OC.js";
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-XLRCQ476.js";
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-AA2FVYAX.js");
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
@@ -7,7 +7,7 @@ import {
7
7
  formatText,
8
8
  generateFindings,
9
9
  runScan
10
- } from "./chunk-XLRCQ476.js";
10
+ } from "./chunk-B4D6EY5P.js";
11
11
  export {
12
12
  computeDriftScore,
13
13
  formatMarkdown,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibgrate/cli",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "CLI for measuring upgrade drift across Node & .NET projects",
5
5
  "type": "module",
6
6
  "bin": {