@vibgrate/cli 1.0.30 → 1.0.32

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