@vibgrate/cli 1.0.1 → 1.0.3
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/README.md +46 -7
- package/dist/{baseline-35XRSRAD.js → baseline-F7SZ4GYH.js} +2 -2
- package/dist/{chunk-VMNBKARQ.js → chunk-5YYLI4QL.js} +457 -130
- package/dist/{chunk-NTRKEIKP.js → chunk-LFLJ2AXJ.js} +1 -1
- package/dist/cli.js +31 -164
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import * as path from "path";
|
|
|
4
4
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
5
5
|
"node_modules",
|
|
6
6
|
".git",
|
|
7
|
+
".vibgrate",
|
|
7
8
|
".next",
|
|
8
9
|
"dist",
|
|
9
10
|
"build",
|
|
@@ -80,6 +81,7 @@ async function writeTextFile(filePath, content) {
|
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
// src/scoring/drift-score.ts
|
|
84
|
+
import * as crypto from "crypto";
|
|
83
85
|
var DEFAULT_THRESHOLDS = {
|
|
84
86
|
failOnError: {
|
|
85
87
|
eolDays: 180,
|
|
@@ -274,6 +276,10 @@ function generateFindings(projects, config) {
|
|
|
274
276
|
}
|
|
275
277
|
return findings;
|
|
276
278
|
}
|
|
279
|
+
function computeProjectId(relativePath, projectName, workspaceId) {
|
|
280
|
+
const input = workspaceId ? `${relativePath}:${projectName}:${workspaceId}` : `${relativePath}:${projectName}`;
|
|
281
|
+
return crypto.createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
282
|
+
}
|
|
277
283
|
|
|
278
284
|
// src/version.ts
|
|
279
285
|
import { createRequire } from "module";
|
|
@@ -295,24 +301,6 @@ function formatText(artifact) {
|
|
|
295
301
|
lines.push(chalk.bold.cyan("\u2551 Vibgrate Drift Report \u2551"));
|
|
296
302
|
lines.push(chalk.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
297
303
|
lines.push("");
|
|
298
|
-
const scoreColor = artifact.drift.score >= 70 ? chalk.green : artifact.drift.score >= 40 ? chalk.yellow : chalk.red;
|
|
299
|
-
lines.push(chalk.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
|
|
300
|
-
lines.push(chalk.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
|
|
301
|
-
lines.push(chalk.bold(" Projects: ") + `${artifact.projects.length}`);
|
|
302
|
-
if (artifact.vcs) {
|
|
303
|
-
const vcsParts = [artifact.vcs.type];
|
|
304
|
-
if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
|
|
305
|
-
if (artifact.vcs.shortSha) vcsParts.push(chalk.dim(artifact.vcs.shortSha));
|
|
306
|
-
lines.push(chalk.bold(" VCS: ") + vcsParts.join(" "));
|
|
307
|
-
}
|
|
308
|
-
lines.push("");
|
|
309
|
-
const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
|
|
310
|
-
lines.push(chalk.bold.underline(" Score Breakdown"));
|
|
311
|
-
lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk.dim("n/a")}`);
|
|
312
|
-
lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk.dim("n/a")}`);
|
|
313
|
-
lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk.dim("n/a")}`);
|
|
314
|
-
lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk.dim("n/a")}`);
|
|
315
|
-
lines.push("");
|
|
316
304
|
for (const project of artifact.projects) {
|
|
317
305
|
lines.push(chalk.bold(` \u2500\u2500 ${project.name} `) + chalk.dim(`(${project.type}) ${project.path}`));
|
|
318
306
|
if (project.runtime) {
|
|
@@ -377,6 +365,28 @@ function formatText(artifact) {
|
|
|
377
365
|
lines.push("");
|
|
378
366
|
}
|
|
379
367
|
}
|
|
368
|
+
const scoreColor = artifact.drift.score >= 70 ? chalk.green : artifact.drift.score >= 40 ? chalk.yellow : chalk.red;
|
|
369
|
+
lines.push(chalk.bold.cyan("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
370
|
+
lines.push(chalk.bold.cyan("\u2551 Drift Score Summary \u2551"));
|
|
371
|
+
lines.push(chalk.bold.cyan("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
372
|
+
lines.push("");
|
|
373
|
+
lines.push(chalk.bold(" Drift Score: ") + scoreColor.bold(`${artifact.drift.score}/100`));
|
|
374
|
+
lines.push(chalk.bold(" Risk Level: ") + riskBadge(artifact.drift.riskLevel));
|
|
375
|
+
lines.push(chalk.bold(" Projects: ") + `${artifact.projects.length}`);
|
|
376
|
+
if (artifact.vcs) {
|
|
377
|
+
const vcsParts = [artifact.vcs.type];
|
|
378
|
+
if (artifact.vcs.branch) vcsParts.push(artifact.vcs.branch);
|
|
379
|
+
if (artifact.vcs.shortSha) vcsParts.push(chalk.dim(artifact.vcs.shortSha));
|
|
380
|
+
lines.push(chalk.bold(" VCS: ") + vcsParts.join(" "));
|
|
381
|
+
}
|
|
382
|
+
lines.push("");
|
|
383
|
+
const m = new Set(artifact.drift.measured ?? ["runtime", "framework", "dependency", "eol"]);
|
|
384
|
+
lines.push(chalk.bold.underline(" Score Breakdown"));
|
|
385
|
+
lines.push(` Runtime: ${m.has("runtime") ? scoreBar(artifact.drift.components.runtimeScore) : chalk.dim("n/a")}`);
|
|
386
|
+
lines.push(` Frameworks: ${m.has("framework") ? scoreBar(artifact.drift.components.frameworkScore) : chalk.dim("n/a")}`);
|
|
387
|
+
lines.push(` Dependencies: ${m.has("dependency") ? scoreBar(artifact.drift.components.dependencyScore) : chalk.dim("n/a")}`);
|
|
388
|
+
lines.push(` EOL Risk: ${m.has("eol") ? scoreBar(artifact.drift.components.eolScore) : chalk.dim("n/a")}`);
|
|
389
|
+
lines.push("");
|
|
380
390
|
const scannedParts = [`Scanned at ${artifact.timestamp}`];
|
|
381
391
|
if (artifact.durationMs !== void 0) {
|
|
382
392
|
const secs = (artifact.durationMs / 1e3).toFixed(1);
|
|
@@ -553,10 +563,17 @@ function generatePriorityActions(artifact) {
|
|
|
553
563
|
);
|
|
554
564
|
if (eolProjects.length > 0) {
|
|
555
565
|
const names = eolProjects.map((p) => p.name).join(", ");
|
|
556
|
-
|
|
566
|
+
let detail = `End-of-life runtimes no longer receive security patches and block ecosystem upgrades.`;
|
|
567
|
+
const fileLines = [];
|
|
568
|
+
for (const p of eolProjects) {
|
|
569
|
+
fileLines.push(`
|
|
570
|
+
./${p.path}`);
|
|
571
|
+
fileLines.push(` ${p.runtime} \u2192 ${p.runtimeLatest} (${p.runtimeMajorsBehind} major${p.runtimeMajorsBehind > 1 ? "s" : ""} behind)`);
|
|
572
|
+
}
|
|
573
|
+
detail += fileLines.join("");
|
|
557
574
|
actions.push({
|
|
558
575
|
title: `Upgrade EOL runtime${eolProjects.length > 1 ? "s" : ""} in ${names}`,
|
|
559
|
-
explanation:
|
|
576
|
+
explanation: detail,
|
|
560
577
|
impact: `+${Math.min(eolProjects.length * 10, 30)} points (runtime & EOL scores)`,
|
|
561
578
|
severity: 100
|
|
562
579
|
});
|
|
@@ -565,16 +582,30 @@ function generatePriorityActions(artifact) {
|
|
|
565
582
|
for (const p of artifact.projects) {
|
|
566
583
|
for (const fw of p.frameworks) {
|
|
567
584
|
if (fw.majorsBehind !== null && fw.majorsBehind >= 3) {
|
|
568
|
-
severeFrameworks.push({ name: fw.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}`, behind: fw.majorsBehind, project: p.name });
|
|
585
|
+
severeFrameworks.push({ name: fw.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}`, behind: fw.majorsBehind, project: p.name, projectPath: p.path });
|
|
569
586
|
}
|
|
570
587
|
}
|
|
571
588
|
}
|
|
572
589
|
if (severeFrameworks.length > 0) {
|
|
573
590
|
const worst = severeFrameworks.sort((a, b) => b.behind - a.behind)[0];
|
|
574
591
|
const others = severeFrameworks.length > 1 ? ` (+${severeFrameworks.length - 1} more)` : "";
|
|
592
|
+
let detail = `${worst.behind} major versions behind. Major framework drift increases breaking change risk and blocks access to security fixes and performance improvements.`;
|
|
593
|
+
const fileLines = [];
|
|
594
|
+
let shown = 0;
|
|
595
|
+
for (const sf of severeFrameworks) {
|
|
596
|
+
if (shown >= 8) break;
|
|
597
|
+
fileLines.push(`
|
|
598
|
+
./${sf.projectPath}`);
|
|
599
|
+
fileLines.push(` ${sf.name}: ${sf.fw} (${sf.behind} major${sf.behind > 1 ? "s" : ""} behind)`);
|
|
600
|
+
shown++;
|
|
601
|
+
}
|
|
602
|
+
const remaining = severeFrameworks.length - shown;
|
|
603
|
+
detail += fileLines.join("");
|
|
604
|
+
if (remaining > 0) detail += `
|
|
605
|
+
... and ${remaining} more`;
|
|
575
606
|
actions.push({
|
|
576
607
|
title: `Upgrade ${worst.name} ${worst.fw} in ${worst.project}${others}`,
|
|
577
|
-
explanation:
|
|
608
|
+
explanation: detail,
|
|
578
609
|
impact: `+5\u201315 points (framework score)`,
|
|
579
610
|
severity: 90
|
|
580
611
|
});
|
|
@@ -585,9 +616,28 @@ function generatePriorityActions(artifact) {
|
|
|
585
616
|
if (total === 0) continue;
|
|
586
617
|
const twoPlusPct = Math.round(b.twoPlusBehind / total * 100);
|
|
587
618
|
if (twoPlusPct >= 40) {
|
|
619
|
+
let detail = `${b.twoPlusBehind} of ${total} dependencies are 2+ majors behind. Run \`npm outdated\` and prioritise packages with known CVEs or breaking API changes.`;
|
|
620
|
+
const worstDeps = p.dependencies.filter((d) => d.majorsBehind !== null && d.majorsBehind >= 2).sort((a, b2) => (b2.majorsBehind ?? 0) - (a.majorsBehind ?? 0));
|
|
621
|
+
if (worstDeps.length > 0) {
|
|
622
|
+
const depLines = [];
|
|
623
|
+
let shown = 0;
|
|
624
|
+
depLines.push(`
|
|
625
|
+
./${p.path}`);
|
|
626
|
+
for (const dep of worstDeps) {
|
|
627
|
+
if (shown >= 8) break;
|
|
628
|
+
const current = dep.resolvedVersion ?? dep.currentSpec;
|
|
629
|
+
const latest = dep.latestStable ?? "?";
|
|
630
|
+
depLines.push(` ${dep.package}: ${current} \u2192 ${latest} (${dep.majorsBehind} major${dep.majorsBehind > 1 ? "s" : ""} behind)`);
|
|
631
|
+
shown++;
|
|
632
|
+
}
|
|
633
|
+
const remaining = worstDeps.length - shown;
|
|
634
|
+
detail += depLines.join("");
|
|
635
|
+
if (remaining > 0) detail += `
|
|
636
|
+
... and ${remaining} more`;
|
|
637
|
+
}
|
|
588
638
|
actions.push({
|
|
589
639
|
title: `Reduce dependency rot in ${p.name} (${twoPlusPct}% severely outdated)`,
|
|
590
|
-
explanation:
|
|
640
|
+
explanation: detail,
|
|
591
641
|
impact: `+5\u201310 points (dependency score)`,
|
|
592
642
|
severity: 80 + twoPlusPct / 10
|
|
593
643
|
});
|
|
@@ -597,7 +647,7 @@ function generatePriorityActions(artifact) {
|
|
|
597
647
|
for (const p of artifact.projects) {
|
|
598
648
|
for (const fw of p.frameworks) {
|
|
599
649
|
if (fw.majorsBehind === 2) {
|
|
600
|
-
twoMajorFrameworks.push({ name: fw.name, project: p.name, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}` });
|
|
650
|
+
twoMajorFrameworks.push({ name: fw.name, project: p.name, projectPath: p.path, fw: `${fw.currentVersion} \u2192 ${fw.latestVersion}` });
|
|
601
651
|
}
|
|
602
652
|
}
|
|
603
653
|
}
|
|
@@ -605,9 +655,23 @@ function generatePriorityActions(artifact) {
|
|
|
605
655
|
if (uniqueTwo.length > 0) {
|
|
606
656
|
const list = uniqueTwo.slice(0, 3).map((f) => `${f.name} (${f.fw})`).join(", ");
|
|
607
657
|
const moreCount = uniqueTwo.length > 3 ? ` +${uniqueTwo.length - 3} more` : "";
|
|
658
|
+
let detail = `These frameworks are 2 major versions behind. Create upgrade tickets and check migration guides \u2014 the gap will widen with each new release.`;
|
|
659
|
+
const fileLines = [];
|
|
660
|
+
let shown = 0;
|
|
661
|
+
for (const tf of twoMajorFrameworks) {
|
|
662
|
+
if (shown >= 8) break;
|
|
663
|
+
fileLines.push(`
|
|
664
|
+
./${tf.projectPath}`);
|
|
665
|
+
fileLines.push(` ${tf.name}: ${tf.fw}`);
|
|
666
|
+
shown++;
|
|
667
|
+
}
|
|
668
|
+
const remaining = twoMajorFrameworks.length - shown;
|
|
669
|
+
detail += fileLines.join("");
|
|
670
|
+
if (remaining > 0) detail += `
|
|
671
|
+
... and ${remaining} more`;
|
|
608
672
|
actions.push({
|
|
609
673
|
title: `Plan major framework upgrades: ${list}${moreCount}`,
|
|
610
|
-
explanation:
|
|
674
|
+
explanation: detail,
|
|
611
675
|
impact: `+5\u201310 points (framework score)`,
|
|
612
676
|
severity: 60
|
|
613
677
|
});
|
|
@@ -618,9 +682,31 @@ function generatePriorityActions(artifact) {
|
|
|
618
682
|
if (total > 0) {
|
|
619
683
|
const items = [...bc.deprecatedPackages, ...bc.legacyPolyfills].slice(0, 5).join(", ");
|
|
620
684
|
const moreCount = total > 5 ? ` +${total - 5} more` : "";
|
|
685
|
+
let detail = `${total} package${total !== 1 ? "s" : ""} are deprecated or legacy polyfills. These receive no updates and may have known vulnerabilities.`;
|
|
686
|
+
const allPkgNames = /* @__PURE__ */ new Set([...bc.deprecatedPackages, ...bc.legacyPolyfills]);
|
|
687
|
+
const fileLines = [];
|
|
688
|
+
let shown = 0;
|
|
689
|
+
for (const p of artifact.projects) {
|
|
690
|
+
const matches = p.dependencies.filter((d) => allPkgNames.has(d.package));
|
|
691
|
+
if (matches.length === 0) continue;
|
|
692
|
+
if (shown >= 10) break;
|
|
693
|
+
fileLines.push(`
|
|
694
|
+
./${p.path}`);
|
|
695
|
+
for (const dep of matches) {
|
|
696
|
+
if (shown >= 10) break;
|
|
697
|
+
const ver = dep.resolvedVersion ?? dep.currentSpec;
|
|
698
|
+
const label = bc.deprecatedPackages.includes(dep.package) ? "deprecated" : "polyfill";
|
|
699
|
+
fileLines.push(` ${dep.package}: ${ver} (${label})`);
|
|
700
|
+
shown++;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
const remaining = total - shown;
|
|
704
|
+
detail += fileLines.join("");
|
|
705
|
+
if (remaining > 0) detail += `
|
|
706
|
+
... and ${remaining} more`;
|
|
621
707
|
actions.push({
|
|
622
708
|
title: `Replace deprecated/legacy packages: ${items}${moreCount}`,
|
|
623
|
-
explanation:
|
|
709
|
+
explanation: detail,
|
|
624
710
|
severity: 55
|
|
625
711
|
});
|
|
626
712
|
}
|
|
@@ -667,9 +753,20 @@ function generatePriorityActions(artifact) {
|
|
|
667
753
|
const issues = [];
|
|
668
754
|
if (sec.envFilesTracked) issues.push(".env files are tracked in git");
|
|
669
755
|
if (!sec.lockfilePresent) issues.push("no lockfile found");
|
|
756
|
+
let detail;
|
|
757
|
+
if (sec.envFilesTracked) {
|
|
758
|
+
detail = "Environment files may contain secrets. Add them to .gitignore and rotate any exposed credentials immediately.";
|
|
759
|
+
detail += "\n ./.gitignore";
|
|
760
|
+
detail += "\n Add: .env, .env.*, .env.local";
|
|
761
|
+
} else {
|
|
762
|
+
detail = "Without a lockfile, installs are non-deterministic. Run the install command to generate one and commit it.";
|
|
763
|
+
detail += "\n ./";
|
|
764
|
+
detail += `
|
|
765
|
+
Missing: ${sec.lockfileTypes.length > 0 ? sec.lockfileTypes.join(", ") + " (multiple types detected)" : "package-lock.json, pnpm-lock.yaml, or yarn.lock"}`;
|
|
766
|
+
}
|
|
670
767
|
actions.push({
|
|
671
768
|
title: `Fix security posture: ${issues.join(", ")}`,
|
|
672
|
-
explanation:
|
|
769
|
+
explanation: detail,
|
|
673
770
|
severity: 95
|
|
674
771
|
});
|
|
675
772
|
}
|
|
@@ -679,9 +776,22 @@ function generatePriorityActions(artifact) {
|
|
|
679
776
|
const highImpactDupes = dupes.filter((d) => d.versions.length >= 3);
|
|
680
777
|
if (highImpactDupes.length >= 3) {
|
|
681
778
|
const names = highImpactDupes.slice(0, 4).map((d) => `${d.name} (${d.versions.length}v)`).join(", ");
|
|
779
|
+
let detail = `${highImpactDupes.length} packages have 3+ versions installed. Run \`npm dedupe\` to reduce bundle size and install time.`;
|
|
780
|
+
const dupeLines = [];
|
|
781
|
+
let shown = 0;
|
|
782
|
+
for (const d of highImpactDupes) {
|
|
783
|
+
if (shown >= 8) break;
|
|
784
|
+
dupeLines.push(`
|
|
785
|
+
${d.name}: ${d.versions.join(", ")} (${d.consumers} consumer${d.consumers !== 1 ? "s" : ""})`);
|
|
786
|
+
shown++;
|
|
787
|
+
}
|
|
788
|
+
const remaining = highImpactDupes.length - shown;
|
|
789
|
+
detail += dupeLines.join("");
|
|
790
|
+
if (remaining > 0) detail += `
|
|
791
|
+
... and ${remaining} more`;
|
|
682
792
|
actions.push({
|
|
683
793
|
title: `Deduplicate heavily-versioned packages`,
|
|
684
|
-
explanation:
|
|
794
|
+
explanation: detail,
|
|
685
795
|
severity: 35
|
|
686
796
|
});
|
|
687
797
|
}
|
|
@@ -772,13 +882,146 @@ function toSarifResult(finding) {
|
|
|
772
882
|
};
|
|
773
883
|
}
|
|
774
884
|
|
|
775
|
-
// src/commands/
|
|
776
|
-
import * as
|
|
885
|
+
// src/commands/dsn.ts
|
|
886
|
+
import * as crypto2 from "crypto";
|
|
887
|
+
import * as path2 from "path";
|
|
777
888
|
import { Command } from "commander";
|
|
889
|
+
import chalk2 from "chalk";
|
|
890
|
+
var REGION_HOSTS = {
|
|
891
|
+
us: "us.ingest.vibgrate.com",
|
|
892
|
+
eu: "eu.ingest.vibgrate.com"
|
|
893
|
+
};
|
|
894
|
+
function resolveIngestHost(region, ingest) {
|
|
895
|
+
if (ingest) {
|
|
896
|
+
try {
|
|
897
|
+
return new URL(ingest).host;
|
|
898
|
+
} catch {
|
|
899
|
+
throw new Error(`Invalid ingest URL: ${ingest}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const r = (region ?? "us").toLowerCase();
|
|
903
|
+
const host = REGION_HOSTS[r];
|
|
904
|
+
if (!host) {
|
|
905
|
+
throw new Error(`Unknown region "${r}". Supported: ${Object.keys(REGION_HOSTS).join(", ")}`);
|
|
906
|
+
}
|
|
907
|
+
return host;
|
|
908
|
+
}
|
|
909
|
+
var dsnCommand = new Command("dsn").description("Manage DSN tokens");
|
|
910
|
+
dsnCommand.command("create").description("Create a new DSN token").option("--ingest <url>", "Ingest API URL (overrides --region)").option("--region <region>", "Data residency region (us, eu)", "us").requiredOption("--workspace <id>", "Workspace ID").option("--write <path>", "Write DSN to file").action(async (opts) => {
|
|
911
|
+
const keyId = crypto2.randomBytes(8).toString("hex");
|
|
912
|
+
const secret = crypto2.randomBytes(32).toString("hex");
|
|
913
|
+
let ingestHost;
|
|
914
|
+
try {
|
|
915
|
+
ingestHost = resolveIngestHost(opts.region, opts.ingest);
|
|
916
|
+
} catch (e) {
|
|
917
|
+
console.error(chalk2.red(e instanceof Error ? e.message : String(e)));
|
|
918
|
+
process.exit(1);
|
|
919
|
+
}
|
|
920
|
+
const dsn = `vibgrate+https://${keyId}:${secret}@${ingestHost}/${opts.workspace}`;
|
|
921
|
+
console.log(chalk2.green("\u2714") + " DSN created");
|
|
922
|
+
console.log("");
|
|
923
|
+
console.log(chalk2.bold("DSN:"));
|
|
924
|
+
console.log(` ${dsn}`);
|
|
925
|
+
console.log("");
|
|
926
|
+
console.log(chalk2.bold("Key ID:"));
|
|
927
|
+
console.log(` ${keyId}`);
|
|
928
|
+
console.log("");
|
|
929
|
+
console.log(chalk2.dim("Set this as VIBGRATE_DSN in your CI environment."));
|
|
930
|
+
console.log(chalk2.dim("The secret must be registered on your Vibgrate ingest API."));
|
|
931
|
+
if (opts.write) {
|
|
932
|
+
const writePath = path2.resolve(opts.write);
|
|
933
|
+
await writeTextFile(writePath, dsn + "\n");
|
|
934
|
+
console.log("");
|
|
935
|
+
console.log(chalk2.green("\u2714") + ` DSN written to ${opts.write}`);
|
|
936
|
+
console.log(chalk2.yellow("\u26A0") + " Add this file to .gitignore!");
|
|
937
|
+
}
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
// src/commands/push.ts
|
|
941
|
+
import * as crypto3 from "crypto";
|
|
942
|
+
import * as path3 from "path";
|
|
943
|
+
import { Command as Command2 } from "commander";
|
|
778
944
|
import chalk3 from "chalk";
|
|
945
|
+
function parseDsn(dsn) {
|
|
946
|
+
const match = dsn.match(/^vibgrate\+https:\/\/([^:]+):([^@]+)@([^/]+)\/(.+)$/);
|
|
947
|
+
if (!match) return null;
|
|
948
|
+
return {
|
|
949
|
+
keyId: match[1],
|
|
950
|
+
secret: match[2],
|
|
951
|
+
host: match[3],
|
|
952
|
+
workspaceId: match[4]
|
|
953
|
+
};
|
|
954
|
+
}
|
|
955
|
+
function computeHmac(body, secret) {
|
|
956
|
+
return crypto3.createHmac("sha256", secret).update(body).digest("base64");
|
|
957
|
+
}
|
|
958
|
+
var pushCommand = new Command2("push").description("Push scan results to Vibgrate API").option("--dsn <dsn>", "DSN token (or use VIBGRATE_DSN env)").option("--region <region>", "Override data residency region (us, eu)").option("--file <file>", "Scan artifact file", ".vibgrate/scan_result.json").option("--strict", "Fail on upload errors").action(async (opts) => {
|
|
959
|
+
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
960
|
+
if (!dsn) {
|
|
961
|
+
console.error(chalk3.red("No DSN provided."));
|
|
962
|
+
console.error(chalk3.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
|
|
963
|
+
if (opts.strict) process.exit(1);
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
const parsed = parseDsn(dsn);
|
|
967
|
+
if (!parsed) {
|
|
968
|
+
console.error(chalk3.red("Invalid DSN format."));
|
|
969
|
+
console.error(chalk3.dim("Expected: vibgrate+https://<key_id>:<secret>@<host>/<workspace_id>"));
|
|
970
|
+
if (opts.strict) process.exit(1);
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
const filePath = path3.resolve(opts.file);
|
|
974
|
+
if (!await pathExists(filePath)) {
|
|
975
|
+
console.error(chalk3.red(`Scan artifact not found: ${filePath}`));
|
|
976
|
+
console.error(chalk3.dim('Run "vibgrate scan" first.'));
|
|
977
|
+
if (opts.strict) process.exit(1);
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const body = await readTextFile(filePath);
|
|
981
|
+
const timestamp = String(Date.now());
|
|
982
|
+
const hmac = computeHmac(body, parsed.secret);
|
|
983
|
+
let host = parsed.host;
|
|
984
|
+
if (opts.region) {
|
|
985
|
+
try {
|
|
986
|
+
host = resolveIngestHost(opts.region);
|
|
987
|
+
} catch (e) {
|
|
988
|
+
console.error(chalk3.red(e instanceof Error ? e.message : String(e)));
|
|
989
|
+
if (opts.strict) process.exit(1);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
const url = `https://${host}/v1/ingest/scan`;
|
|
994
|
+
console.log(chalk3.dim(`Uploading to ${host}...`));
|
|
995
|
+
try {
|
|
996
|
+
const response = await fetch(url, {
|
|
997
|
+
method: "POST",
|
|
998
|
+
headers: {
|
|
999
|
+
"Content-Type": "application/json",
|
|
1000
|
+
"X-Vibgrate-Timestamp": timestamp,
|
|
1001
|
+
"Authorization": `VibgrateDSN ${parsed.keyId}:${hmac}`
|
|
1002
|
+
},
|
|
1003
|
+
body
|
|
1004
|
+
});
|
|
1005
|
+
if (!response.ok) {
|
|
1006
|
+
const text = await response.text();
|
|
1007
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
1008
|
+
}
|
|
1009
|
+
const result = await response.json();
|
|
1010
|
+
console.log(chalk3.green("\u2714") + ` Uploaded successfully (${result.ingestId ?? "ok"})`);
|
|
1011
|
+
} catch (e) {
|
|
1012
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
1013
|
+
console.error(chalk3.red(`Upload failed: ${msg}`));
|
|
1014
|
+
if (opts.strict) process.exit(1);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
// src/commands/scan.ts
|
|
1019
|
+
import * as path14 from "path";
|
|
1020
|
+
import { Command as Command3 } from "commander";
|
|
1021
|
+
import chalk5 from "chalk";
|
|
779
1022
|
|
|
780
1023
|
// src/scanners/node-scanner.ts
|
|
781
|
-
import * as
|
|
1024
|
+
import * as path4 from "path";
|
|
782
1025
|
import * as semver2 from "semver";
|
|
783
1026
|
|
|
784
1027
|
// src/scanners/npm-cache.ts
|
|
@@ -793,7 +1036,7 @@ function maxStable(versions) {
|
|
|
793
1036
|
return stable.sort(semver.rcompare)[0] ?? null;
|
|
794
1037
|
}
|
|
795
1038
|
async function npmViewJson(args, cwd) {
|
|
796
|
-
return new Promise((
|
|
1039
|
+
return new Promise((resolve6, reject) => {
|
|
797
1040
|
const child = spawn("npm", ["view", ...args, "--json"], {
|
|
798
1041
|
cwd,
|
|
799
1042
|
stdio: ["ignore", "pipe", "pipe"]
|
|
@@ -810,13 +1053,13 @@ async function npmViewJson(args, cwd) {
|
|
|
810
1053
|
}
|
|
811
1054
|
const trimmed = out.trim();
|
|
812
1055
|
if (!trimmed) {
|
|
813
|
-
|
|
1056
|
+
resolve6(null);
|
|
814
1057
|
return;
|
|
815
1058
|
}
|
|
816
1059
|
try {
|
|
817
|
-
|
|
1060
|
+
resolve6(JSON.parse(trimmed));
|
|
818
1061
|
} catch {
|
|
819
|
-
|
|
1062
|
+
resolve6(trimmed.replace(/^"|"$/g, ""));
|
|
820
1063
|
}
|
|
821
1064
|
});
|
|
822
1065
|
});
|
|
@@ -968,8 +1211,8 @@ async function scanNodeProjects(rootDir, npmCache) {
|
|
|
968
1211
|
}
|
|
969
1212
|
async function scanOnePackageJson(packageJsonPath, rootDir, npmCache) {
|
|
970
1213
|
const pj = await readJsonFile(packageJsonPath);
|
|
971
|
-
const absProjectPath =
|
|
972
|
-
const projectPath =
|
|
1214
|
+
const absProjectPath = path4.dirname(packageJsonPath);
|
|
1215
|
+
const projectPath = path4.relative(rootDir, absProjectPath) || ".";
|
|
973
1216
|
const nodeEngine = pj.engines?.node ?? void 0;
|
|
974
1217
|
let runtimeLatest;
|
|
975
1218
|
let runtimeMajorsBehind;
|
|
@@ -1051,7 +1294,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache) {
|
|
|
1051
1294
|
return {
|
|
1052
1295
|
type: "node",
|
|
1053
1296
|
path: projectPath,
|
|
1054
|
-
name: pj.name ??
|
|
1297
|
+
name: pj.name ?? path4.basename(absProjectPath),
|
|
1055
1298
|
runtime: nodeEngine,
|
|
1056
1299
|
runtimeLatest,
|
|
1057
1300
|
runtimeMajorsBehind,
|
|
@@ -1062,7 +1305,7 @@ async function scanOnePackageJson(packageJsonPath, rootDir, npmCache) {
|
|
|
1062
1305
|
}
|
|
1063
1306
|
|
|
1064
1307
|
// src/scanners/dotnet-scanner.ts
|
|
1065
|
-
import * as
|
|
1308
|
+
import * as path5 from "path";
|
|
1066
1309
|
import { XMLParser } from "fast-xml-parser";
|
|
1067
1310
|
var parser = new XMLParser({
|
|
1068
1311
|
ignoreAttributes: false,
|
|
@@ -1263,7 +1506,7 @@ function parseCsproj(xml, filePath) {
|
|
|
1263
1506
|
const parsed = parser.parse(xml);
|
|
1264
1507
|
const project = parsed?.Project;
|
|
1265
1508
|
if (!project) {
|
|
1266
|
-
return { targetFrameworks: [], packageReferences: [], projectName:
|
|
1509
|
+
return { targetFrameworks: [], packageReferences: [], projectName: path5.basename(filePath, ".csproj") };
|
|
1267
1510
|
}
|
|
1268
1511
|
const propertyGroups = Array.isArray(project.PropertyGroup) ? project.PropertyGroup : project.PropertyGroup ? [project.PropertyGroup] : [];
|
|
1269
1512
|
const targetFrameworks = [];
|
|
@@ -1291,7 +1534,7 @@ function parseCsproj(xml, filePath) {
|
|
|
1291
1534
|
return {
|
|
1292
1535
|
targetFrameworks: [...new Set(targetFrameworks)],
|
|
1293
1536
|
packageReferences,
|
|
1294
|
-
projectName:
|
|
1537
|
+
projectName: path5.basename(filePath, ".csproj")
|
|
1295
1538
|
};
|
|
1296
1539
|
}
|
|
1297
1540
|
async function scanDotnetProjects(rootDir) {
|
|
@@ -1301,12 +1544,12 @@ async function scanDotnetProjects(rootDir) {
|
|
|
1301
1544
|
for (const slnPath of slnFiles) {
|
|
1302
1545
|
try {
|
|
1303
1546
|
const slnContent = await readTextFile(slnPath);
|
|
1304
|
-
const slnDir =
|
|
1547
|
+
const slnDir = path5.dirname(slnPath);
|
|
1305
1548
|
const projectRegex = /Project\("[^"]*"\)\s*=\s*"[^"]*",\s*"([^"]+\.csproj)"/g;
|
|
1306
1549
|
let match;
|
|
1307
1550
|
while ((match = projectRegex.exec(slnContent)) !== null) {
|
|
1308
1551
|
if (match[1]) {
|
|
1309
|
-
const csprojPath =
|
|
1552
|
+
const csprojPath = path5.resolve(slnDir, match[1].replace(/\\/g, "/"));
|
|
1310
1553
|
slnCsprojPaths.add(csprojPath);
|
|
1311
1554
|
}
|
|
1312
1555
|
}
|
|
@@ -1362,7 +1605,7 @@ async function scanOneCsproj(csprojPath, rootDir) {
|
|
|
1362
1605
|
const buckets = { current: 0, oneBehind: 0, twoPlusBehind: 0, unknown: dependencies.length };
|
|
1363
1606
|
return {
|
|
1364
1607
|
type: "dotnet",
|
|
1365
|
-
path:
|
|
1608
|
+
path: path5.relative(rootDir, path5.dirname(csprojPath)) || ".",
|
|
1366
1609
|
name: data.projectName,
|
|
1367
1610
|
targetFramework,
|
|
1368
1611
|
runtime: primaryTfm,
|
|
@@ -1394,7 +1637,7 @@ var Semaphore = class {
|
|
|
1394
1637
|
this.available--;
|
|
1395
1638
|
return Promise.resolve();
|
|
1396
1639
|
}
|
|
1397
|
-
return new Promise((
|
|
1640
|
+
return new Promise((resolve6) => this.queue.push(resolve6));
|
|
1398
1641
|
}
|
|
1399
1642
|
release() {
|
|
1400
1643
|
const next = this.queue.shift();
|
|
@@ -1404,7 +1647,7 @@ var Semaphore = class {
|
|
|
1404
1647
|
};
|
|
1405
1648
|
|
|
1406
1649
|
// src/config.ts
|
|
1407
|
-
import * as
|
|
1650
|
+
import * as path6 from "path";
|
|
1408
1651
|
import * as fs2 from "fs/promises";
|
|
1409
1652
|
var CONFIG_FILES = [
|
|
1410
1653
|
"vibgrate.config.ts",
|
|
@@ -1427,7 +1670,7 @@ var DEFAULT_CONFIG = {
|
|
|
1427
1670
|
};
|
|
1428
1671
|
async function loadConfig(rootDir) {
|
|
1429
1672
|
for (const file of CONFIG_FILES) {
|
|
1430
|
-
const configPath =
|
|
1673
|
+
const configPath = path6.join(rootDir, file);
|
|
1431
1674
|
if (await pathExists(configPath)) {
|
|
1432
1675
|
if (file.endsWith(".json")) {
|
|
1433
1676
|
const txt = await readTextFile(configPath);
|
|
@@ -1443,7 +1686,7 @@ async function loadConfig(rootDir) {
|
|
|
1443
1686
|
return DEFAULT_CONFIG;
|
|
1444
1687
|
}
|
|
1445
1688
|
async function writeDefaultConfig(rootDir) {
|
|
1446
|
-
const configPath =
|
|
1689
|
+
const configPath = path6.join(rootDir, "vibgrate.config.ts");
|
|
1447
1690
|
const content = `import type { VibgrateConfig } from '@vibgrate/cli';
|
|
1448
1691
|
|
|
1449
1692
|
const config: VibgrateConfig = {
|
|
@@ -1468,7 +1711,7 @@ export default config;
|
|
|
1468
1711
|
}
|
|
1469
1712
|
|
|
1470
1713
|
// src/utils/vcs.ts
|
|
1471
|
-
import * as
|
|
1714
|
+
import * as path7 from "path";
|
|
1472
1715
|
import * as fs3 from "fs/promises";
|
|
1473
1716
|
async function detectVcs(rootDir) {
|
|
1474
1717
|
try {
|
|
@@ -1482,7 +1725,7 @@ async function detectGit(rootDir) {
|
|
|
1482
1725
|
if (!gitDir) {
|
|
1483
1726
|
return { type: "unknown" };
|
|
1484
1727
|
}
|
|
1485
|
-
const headPath =
|
|
1728
|
+
const headPath = path7.join(gitDir, "HEAD");
|
|
1486
1729
|
let headContent;
|
|
1487
1730
|
try {
|
|
1488
1731
|
headContent = (await fs3.readFile(headPath, "utf8")).trim();
|
|
@@ -1506,10 +1749,10 @@ async function detectGit(rootDir) {
|
|
|
1506
1749
|
};
|
|
1507
1750
|
}
|
|
1508
1751
|
async function findGitDir(startDir) {
|
|
1509
|
-
let dir =
|
|
1510
|
-
const root =
|
|
1752
|
+
let dir = path7.resolve(startDir);
|
|
1753
|
+
const root = path7.parse(dir).root;
|
|
1511
1754
|
while (dir !== root) {
|
|
1512
|
-
const gitPath =
|
|
1755
|
+
const gitPath = path7.join(dir, ".git");
|
|
1513
1756
|
try {
|
|
1514
1757
|
const stat3 = await fs3.stat(gitPath);
|
|
1515
1758
|
if (stat3.isDirectory()) {
|
|
@@ -1518,18 +1761,18 @@ async function findGitDir(startDir) {
|
|
|
1518
1761
|
if (stat3.isFile()) {
|
|
1519
1762
|
const content = (await fs3.readFile(gitPath, "utf8")).trim();
|
|
1520
1763
|
if (content.startsWith("gitdir: ")) {
|
|
1521
|
-
const resolved =
|
|
1764
|
+
const resolved = path7.resolve(dir, content.slice(8));
|
|
1522
1765
|
return resolved;
|
|
1523
1766
|
}
|
|
1524
1767
|
}
|
|
1525
1768
|
} catch {
|
|
1526
1769
|
}
|
|
1527
|
-
dir =
|
|
1770
|
+
dir = path7.dirname(dir);
|
|
1528
1771
|
}
|
|
1529
1772
|
return null;
|
|
1530
1773
|
}
|
|
1531
1774
|
async function resolveRef(gitDir, refPath) {
|
|
1532
|
-
const loosePath =
|
|
1775
|
+
const loosePath = path7.join(gitDir, refPath);
|
|
1533
1776
|
try {
|
|
1534
1777
|
const sha = (await fs3.readFile(loosePath, "utf8")).trim();
|
|
1535
1778
|
if (/^[0-9a-f]{40}$/i.test(sha)) {
|
|
@@ -1537,7 +1780,7 @@ async function resolveRef(gitDir, refPath) {
|
|
|
1537
1780
|
}
|
|
1538
1781
|
} catch {
|
|
1539
1782
|
}
|
|
1540
|
-
const packedPath =
|
|
1783
|
+
const packedPath = path7.join(gitDir, "packed-refs");
|
|
1541
1784
|
try {
|
|
1542
1785
|
const packed = await fs3.readFile(packedPath, "utf8");
|
|
1543
1786
|
for (const line of packed.split("\n")) {
|
|
@@ -1553,16 +1796,16 @@ async function resolveRef(gitDir, refPath) {
|
|
|
1553
1796
|
}
|
|
1554
1797
|
|
|
1555
1798
|
// src/ui/progress.ts
|
|
1556
|
-
import
|
|
1799
|
+
import chalk4 from "chalk";
|
|
1557
1800
|
var ROBOT = [
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1801
|
+
chalk4.cyan(" \u256D\u2500\u2500\u2500\u256E") + chalk4.greenBright("\u279C"),
|
|
1802
|
+
chalk4.cyan(" \u256D\u2524") + chalk4.greenBright("\u25C9 \u25C9") + chalk4.cyan("\u251C\u256E"),
|
|
1803
|
+
chalk4.cyan(" \u2570\u2524") + chalk4.dim("\u2500\u2500\u2500") + chalk4.cyan("\u251C\u256F"),
|
|
1804
|
+
chalk4.cyan(" \u2570\u2500\u2500\u2500\u256F")
|
|
1562
1805
|
];
|
|
1563
1806
|
var BRAND = [
|
|
1564
|
-
|
|
1565
|
-
|
|
1807
|
+
chalk4.bold.white(" V I B G R A T E"),
|
|
1808
|
+
chalk4.dim(` Drift Intelligence Engine`) + chalk4.dim(` v${VERSION}`)
|
|
1566
1809
|
];
|
|
1567
1810
|
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1568
1811
|
var ScanProgress = class {
|
|
@@ -1656,7 +1899,7 @@ var ScanProgress = class {
|
|
|
1656
1899
|
const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
1657
1900
|
const doneCount = this.steps.filter((s) => s.status === "done").length;
|
|
1658
1901
|
process.stderr.write(
|
|
1659
|
-
|
|
1902
|
+
chalk4.dim(` \u2714 ${doneCount} scanners completed in ${elapsed}s
|
|
1660
1903
|
|
|
1661
1904
|
`)
|
|
1662
1905
|
);
|
|
@@ -1688,16 +1931,16 @@ var ScanProgress = class {
|
|
|
1688
1931
|
lines.push(` ${ROBOT[0]} ${BRAND[0]}`);
|
|
1689
1932
|
lines.push(` ${ROBOT[1]} ${BRAND[1]}`);
|
|
1690
1933
|
lines.push(` ${ROBOT[2]}`);
|
|
1691
|
-
lines.push(` ${ROBOT[3]} ${
|
|
1934
|
+
lines.push(` ${ROBOT[3]} ${chalk4.dim(this.rootDir)}`);
|
|
1692
1935
|
lines.push("");
|
|
1693
1936
|
const totalSteps = this.steps.length;
|
|
1694
1937
|
const doneSteps = this.steps.filter((s) => s.status === "done" || s.status === "skipped").length;
|
|
1695
1938
|
const pct = totalSteps > 0 ? Math.round(doneSteps / totalSteps * 100) : 0;
|
|
1696
1939
|
const barWidth = 30;
|
|
1697
1940
|
const filled = Math.round(doneSteps / Math.max(totalSteps, 1) * barWidth);
|
|
1698
|
-
const bar =
|
|
1941
|
+
const bar = chalk4.greenBright("\u2501".repeat(filled)) + chalk4.dim("\u254C".repeat(barWidth - filled));
|
|
1699
1942
|
const elapsed = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
1700
|
-
lines.push(` ${bar} ${
|
|
1943
|
+
lines.push(` ${bar} ${chalk4.bold.white(`${pct}%`)} ${chalk4.dim(`${elapsed}s`)}`);
|
|
1701
1944
|
lines.push("");
|
|
1702
1945
|
for (const step of this.steps) {
|
|
1703
1946
|
lines.push(this.renderStep(step));
|
|
@@ -1716,27 +1959,27 @@ var ScanProgress = class {
|
|
|
1716
1959
|
let detail = "";
|
|
1717
1960
|
switch (step.status) {
|
|
1718
1961
|
case "done":
|
|
1719
|
-
icon =
|
|
1720
|
-
label =
|
|
1962
|
+
icon = chalk4.green("\u2714");
|
|
1963
|
+
label = chalk4.white(step.label);
|
|
1721
1964
|
break;
|
|
1722
1965
|
case "active":
|
|
1723
|
-
icon =
|
|
1724
|
-
label =
|
|
1966
|
+
icon = chalk4.cyan(spinner);
|
|
1967
|
+
label = chalk4.bold.white(step.label);
|
|
1725
1968
|
break;
|
|
1726
1969
|
case "skipped":
|
|
1727
|
-
icon =
|
|
1728
|
-
label =
|
|
1970
|
+
icon = chalk4.dim("\u25CC");
|
|
1971
|
+
label = chalk4.dim.strikethrough(step.label);
|
|
1729
1972
|
break;
|
|
1730
1973
|
default:
|
|
1731
|
-
icon =
|
|
1732
|
-
label =
|
|
1974
|
+
icon = chalk4.dim("\u25CB");
|
|
1975
|
+
label = chalk4.dim(step.label);
|
|
1733
1976
|
break;
|
|
1734
1977
|
}
|
|
1735
1978
|
if (step.detail) {
|
|
1736
|
-
detail =
|
|
1979
|
+
detail = chalk4.dim(` \xB7 ${step.detail}`);
|
|
1737
1980
|
}
|
|
1738
1981
|
if (step.count !== void 0 && step.count > 0) {
|
|
1739
|
-
detail +=
|
|
1982
|
+
detail += chalk4.cyan(` (${step.count})`);
|
|
1740
1983
|
}
|
|
1741
1984
|
return ` ${icon} ${label}${detail}`;
|
|
1742
1985
|
}
|
|
@@ -1748,18 +1991,18 @@ var ScanProgress = class {
|
|
|
1748
1991
|
const e = this.stats.findings.errors;
|
|
1749
1992
|
const n = this.stats.findings.notes;
|
|
1750
1993
|
const parts = [
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1994
|
+
chalk4.bold.white(` ${p}`) + chalk4.dim(` project${p !== 1 ? "s" : ""}`),
|
|
1995
|
+
chalk4.white(`${d}`) + chalk4.dim(` dep${d !== 1 ? "s" : ""}`),
|
|
1996
|
+
chalk4.white(`${f}`) + chalk4.dim(` framework${f !== 1 ? "s" : ""}`)
|
|
1754
1997
|
];
|
|
1755
1998
|
const findingParts = [];
|
|
1756
|
-
if (e > 0) findingParts.push(
|
|
1757
|
-
if (w > 0) findingParts.push(
|
|
1758
|
-
if (n > 0) findingParts.push(
|
|
1999
|
+
if (e > 0) findingParts.push(chalk4.red(`${e} \u2716`));
|
|
2000
|
+
if (w > 0) findingParts.push(chalk4.yellow(`${w} \u26A0`));
|
|
2001
|
+
if (n > 0) findingParts.push(chalk4.blue(`${n} \u2139`));
|
|
1759
2002
|
if (findingParts.length > 0) {
|
|
1760
|
-
parts.push(findingParts.join(
|
|
2003
|
+
parts.push(findingParts.join(chalk4.dim(" \xB7 ")));
|
|
1761
2004
|
}
|
|
1762
|
-
return ` ${
|
|
2005
|
+
return ` ${chalk4.dim("\u2503")} ${parts.join(chalk4.dim(" \u2502 "))}`;
|
|
1763
2006
|
}
|
|
1764
2007
|
/** Simple CI-friendly output (no ANSI rewriting) */
|
|
1765
2008
|
lastCIStep = null;
|
|
@@ -1778,7 +2021,7 @@ var ScanProgress = class {
|
|
|
1778
2021
|
};
|
|
1779
2022
|
|
|
1780
2023
|
// src/scanners/platform-matrix.ts
|
|
1781
|
-
import * as
|
|
2024
|
+
import * as path8 from "path";
|
|
1782
2025
|
var NATIVE_MODULE_PACKAGES = /* @__PURE__ */ new Set([
|
|
1783
2026
|
// Image / media processing
|
|
1784
2027
|
"sharp",
|
|
@@ -2055,7 +2298,7 @@ async function scanPlatformMatrix(rootDir) {
|
|
|
2055
2298
|
}
|
|
2056
2299
|
result.dockerBaseImages = [...baseImages].sort();
|
|
2057
2300
|
for (const file of [".nvmrc", ".node-version", ".tool-versions"]) {
|
|
2058
|
-
if (await pathExists(
|
|
2301
|
+
if (await pathExists(path8.join(rootDir, file))) {
|
|
2059
2302
|
result.nodeVersionFiles.push(file);
|
|
2060
2303
|
}
|
|
2061
2304
|
}
|
|
@@ -2131,7 +2374,7 @@ function scanDependencyRisk(projects) {
|
|
|
2131
2374
|
}
|
|
2132
2375
|
|
|
2133
2376
|
// src/scanners/dependency-graph.ts
|
|
2134
|
-
import * as
|
|
2377
|
+
import * as path9 from "path";
|
|
2135
2378
|
function parsePnpmLock(content) {
|
|
2136
2379
|
const entries = [];
|
|
2137
2380
|
const regex = /^\s+\/?(@?[^@\s][^@\s]*?)@(\d+\.\d+\.\d+[^:\s]*)\s*:/gm;
|
|
@@ -2190,9 +2433,9 @@ async function scanDependencyGraph(rootDir) {
|
|
|
2190
2433
|
phantomDependencies: []
|
|
2191
2434
|
};
|
|
2192
2435
|
let entries = [];
|
|
2193
|
-
const pnpmLock =
|
|
2194
|
-
const npmLock =
|
|
2195
|
-
const yarnLock =
|
|
2436
|
+
const pnpmLock = path9.join(rootDir, "pnpm-lock.yaml");
|
|
2437
|
+
const npmLock = path9.join(rootDir, "package-lock.json");
|
|
2438
|
+
const yarnLock = path9.join(rootDir, "yarn.lock");
|
|
2196
2439
|
if (await pathExists(pnpmLock)) {
|
|
2197
2440
|
result.lockfileType = "pnpm";
|
|
2198
2441
|
const content = await readTextFile(pnpmLock);
|
|
@@ -2237,7 +2480,7 @@ async function scanDependencyGraph(rootDir) {
|
|
|
2237
2480
|
for (const pjPath of pkgFiles) {
|
|
2238
2481
|
try {
|
|
2239
2482
|
const pj = await readJsonFile(pjPath);
|
|
2240
|
-
const relPath =
|
|
2483
|
+
const relPath = path9.relative(rootDir, pjPath);
|
|
2241
2484
|
for (const section of ["dependencies", "devDependencies"]) {
|
|
2242
2485
|
const deps = pj[section];
|
|
2243
2486
|
if (!deps) continue;
|
|
@@ -2583,7 +2826,7 @@ function scanToolingInventory(projects) {
|
|
|
2583
2826
|
}
|
|
2584
2827
|
|
|
2585
2828
|
// src/scanners/build-deploy.ts
|
|
2586
|
-
import * as
|
|
2829
|
+
import * as path10 from "path";
|
|
2587
2830
|
var CI_FILES = {
|
|
2588
2831
|
".github/workflows": "github-actions",
|
|
2589
2832
|
".gitlab-ci.yml": "gitlab-ci",
|
|
@@ -2633,12 +2876,12 @@ async function scanBuildDeploy(rootDir) {
|
|
|
2633
2876
|
};
|
|
2634
2877
|
const ciSystems = /* @__PURE__ */ new Set();
|
|
2635
2878
|
for (const [file, system] of Object.entries(CI_FILES)) {
|
|
2636
|
-
const fullPath =
|
|
2879
|
+
const fullPath = path10.join(rootDir, file);
|
|
2637
2880
|
if (await pathExists(fullPath)) {
|
|
2638
2881
|
ciSystems.add(system);
|
|
2639
2882
|
}
|
|
2640
2883
|
}
|
|
2641
|
-
const ghWorkflowDir =
|
|
2884
|
+
const ghWorkflowDir = path10.join(rootDir, ".github", "workflows");
|
|
2642
2885
|
if (await pathExists(ghWorkflowDir)) {
|
|
2643
2886
|
try {
|
|
2644
2887
|
const files = await findFiles(
|
|
@@ -2686,11 +2929,11 @@ async function scanBuildDeploy(rootDir) {
|
|
|
2686
2929
|
(name) => name.endsWith(".cfn.json") || name.endsWith(".cfn.yaml")
|
|
2687
2930
|
);
|
|
2688
2931
|
if (cfnFiles.length > 0) iacSystems.add("cloudformation");
|
|
2689
|
-
if (await pathExists(
|
|
2932
|
+
if (await pathExists(path10.join(rootDir, "Pulumi.yaml"))) iacSystems.add("pulumi");
|
|
2690
2933
|
result.iac = [...iacSystems].sort();
|
|
2691
2934
|
const releaseTools = /* @__PURE__ */ new Set();
|
|
2692
2935
|
for (const [file, tool] of Object.entries(RELEASE_FILES)) {
|
|
2693
|
-
if (await pathExists(
|
|
2936
|
+
if (await pathExists(path10.join(rootDir, file))) releaseTools.add(tool);
|
|
2694
2937
|
}
|
|
2695
2938
|
const pkgFiles = await findPackageJsonFiles(rootDir);
|
|
2696
2939
|
for (const pjPath of pkgFiles) {
|
|
@@ -2715,19 +2958,19 @@ async function scanBuildDeploy(rootDir) {
|
|
|
2715
2958
|
};
|
|
2716
2959
|
const managers = /* @__PURE__ */ new Set();
|
|
2717
2960
|
for (const [file, manager] of Object.entries(lockfileMap)) {
|
|
2718
|
-
if (await pathExists(
|
|
2961
|
+
if (await pathExists(path10.join(rootDir, file))) managers.add(manager);
|
|
2719
2962
|
}
|
|
2720
2963
|
result.packageManagers = [...managers].sort();
|
|
2721
2964
|
const monoTools = /* @__PURE__ */ new Set();
|
|
2722
2965
|
for (const [file, tool] of Object.entries(MONOREPO_FILES)) {
|
|
2723
|
-
if (await pathExists(
|
|
2966
|
+
if (await pathExists(path10.join(rootDir, file))) monoTools.add(tool);
|
|
2724
2967
|
}
|
|
2725
2968
|
result.monorepoTools = [...monoTools].sort();
|
|
2726
2969
|
return result;
|
|
2727
2970
|
}
|
|
2728
2971
|
|
|
2729
2972
|
// src/scanners/ts-modernity.ts
|
|
2730
|
-
import * as
|
|
2973
|
+
import * as path11 from "path";
|
|
2731
2974
|
async function scanTsModernity(rootDir) {
|
|
2732
2975
|
const result = {
|
|
2733
2976
|
typescriptVersion: null,
|
|
@@ -2765,7 +3008,7 @@ async function scanTsModernity(rootDir) {
|
|
|
2765
3008
|
if (hasEsm && hasCjs) result.moduleType = "mixed";
|
|
2766
3009
|
else if (hasEsm) result.moduleType = "esm";
|
|
2767
3010
|
else if (hasCjs) result.moduleType = "cjs";
|
|
2768
|
-
let tsConfigPath =
|
|
3011
|
+
let tsConfigPath = path11.join(rootDir, "tsconfig.json");
|
|
2769
3012
|
if (!await pathExists(tsConfigPath)) {
|
|
2770
3013
|
const tsConfigs = await findFiles(rootDir, (name) => name === "tsconfig.json");
|
|
2771
3014
|
if (tsConfigs.length > 0) {
|
|
@@ -3111,7 +3354,7 @@ function scanBreakingChangeExposure(projects) {
|
|
|
3111
3354
|
|
|
3112
3355
|
// src/scanners/file-hotspots.ts
|
|
3113
3356
|
import * as fs4 from "fs/promises";
|
|
3114
|
-
import * as
|
|
3357
|
+
import * as path12 from "path";
|
|
3115
3358
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
3116
3359
|
"node_modules",
|
|
3117
3360
|
".git",
|
|
@@ -3166,15 +3409,15 @@ async function scanFileHotspots(rootDir) {
|
|
|
3166
3409
|
for (const e of entries) {
|
|
3167
3410
|
if (e.isDirectory) {
|
|
3168
3411
|
if (SKIP_DIRS2.has(e.name)) continue;
|
|
3169
|
-
await walk(
|
|
3412
|
+
await walk(path12.join(dir, e.name), depth + 1);
|
|
3170
3413
|
} else if (e.isFile) {
|
|
3171
|
-
const ext =
|
|
3414
|
+
const ext = path12.extname(e.name).toLowerCase();
|
|
3172
3415
|
if (SKIP_EXTENSIONS.has(ext)) continue;
|
|
3173
3416
|
extensionCounts[ext] = (extensionCounts[ext] ?? 0) + 1;
|
|
3174
3417
|
try {
|
|
3175
|
-
const stat3 = await fs4.stat(
|
|
3418
|
+
const stat3 = await fs4.stat(path12.join(dir, e.name));
|
|
3176
3419
|
allFiles.push({
|
|
3177
|
-
path:
|
|
3420
|
+
path: path12.relative(rootDir, path12.join(dir, e.name)),
|
|
3178
3421
|
bytes: stat3.size
|
|
3179
3422
|
});
|
|
3180
3423
|
} catch {
|
|
@@ -3196,7 +3439,7 @@ async function scanFileHotspots(rootDir) {
|
|
|
3196
3439
|
}
|
|
3197
3440
|
|
|
3198
3441
|
// src/scanners/security-posture.ts
|
|
3199
|
-
import * as
|
|
3442
|
+
import * as path13 from "path";
|
|
3200
3443
|
var LOCKFILES = {
|
|
3201
3444
|
"pnpm-lock.yaml": "pnpm",
|
|
3202
3445
|
"package-lock.json": "npm",
|
|
@@ -3215,14 +3458,14 @@ async function scanSecurityPosture(rootDir) {
|
|
|
3215
3458
|
};
|
|
3216
3459
|
const foundLockfiles = [];
|
|
3217
3460
|
for (const [file, type] of Object.entries(LOCKFILES)) {
|
|
3218
|
-
if (await pathExists(
|
|
3461
|
+
if (await pathExists(path13.join(rootDir, file))) {
|
|
3219
3462
|
foundLockfiles.push(type);
|
|
3220
3463
|
}
|
|
3221
3464
|
}
|
|
3222
3465
|
result.lockfilePresent = foundLockfiles.length > 0;
|
|
3223
3466
|
result.multipleLockfileTypes = foundLockfiles.length > 1;
|
|
3224
3467
|
result.lockfileTypes = foundLockfiles.sort();
|
|
3225
|
-
const gitignorePath =
|
|
3468
|
+
const gitignorePath = path13.join(rootDir, ".gitignore");
|
|
3226
3469
|
if (await pathExists(gitignorePath)) {
|
|
3227
3470
|
try {
|
|
3228
3471
|
const content = await readTextFile(gitignorePath);
|
|
@@ -3237,7 +3480,7 @@ async function scanSecurityPosture(rootDir) {
|
|
|
3237
3480
|
}
|
|
3238
3481
|
}
|
|
3239
3482
|
for (const envFile of [".env", ".env.local", ".env.development", ".env.production"]) {
|
|
3240
|
-
if (await pathExists(
|
|
3483
|
+
if (await pathExists(path13.join(rootDir, envFile))) {
|
|
3241
3484
|
if (!result.gitignoreCoversEnv) {
|
|
3242
3485
|
result.envFilesTracked = true;
|
|
3243
3486
|
break;
|
|
@@ -3715,6 +3958,13 @@ async function runScan(rootDir, opts) {
|
|
|
3715
3958
|
progress.addProjects(dotnetProjects.length);
|
|
3716
3959
|
progress.completeStep("dotnet", `${dotnetProjects.length} project${dotnetProjects.length !== 1 ? "s" : ""}`, dotnetProjects.length);
|
|
3717
3960
|
const allProjects = [...nodeProjects, ...dotnetProjects];
|
|
3961
|
+
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
3962
|
+
const parsedDsn = dsn ? parseDsn(dsn) : null;
|
|
3963
|
+
const workspaceId = parsedDsn?.workspaceId;
|
|
3964
|
+
for (const project of allProjects) {
|
|
3965
|
+
project.drift = computeDriftScore([project]);
|
|
3966
|
+
project.projectId = computeProjectId(project.path, project.name, workspaceId);
|
|
3967
|
+
}
|
|
3718
3968
|
const extended = {};
|
|
3719
3969
|
if (scanners !== false) {
|
|
3720
3970
|
const scannerTasks = [];
|
|
@@ -3852,7 +4102,7 @@ async function runScan(rootDir, opts) {
|
|
|
3852
4102
|
progress.completeStep("findings", findingParts.join(", ") || "none");
|
|
3853
4103
|
progress.finish();
|
|
3854
4104
|
if (allProjects.length === 0) {
|
|
3855
|
-
console.log(
|
|
4105
|
+
console.log(chalk5.yellow("No projects found."));
|
|
3856
4106
|
}
|
|
3857
4107
|
if (extended.fileHotspots) filesScanned += extended.fileHotspots.totalFiles;
|
|
3858
4108
|
if (extended.securityPosture) filesScanned += 1;
|
|
@@ -3867,7 +4117,7 @@ async function runScan(rootDir, opts) {
|
|
|
3867
4117
|
schemaVersion: "1.0",
|
|
3868
4118
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3869
4119
|
vibgrateVersion: VERSION,
|
|
3870
|
-
rootPath:
|
|
4120
|
+
rootPath: path14.basename(rootDir),
|
|
3871
4121
|
...vcs.type !== "unknown" ? { vcs } : {},
|
|
3872
4122
|
projects: allProjects,
|
|
3873
4123
|
drift,
|
|
@@ -3877,25 +4127,44 @@ async function runScan(rootDir, opts) {
|
|
|
3877
4127
|
filesScanned
|
|
3878
4128
|
};
|
|
3879
4129
|
if (opts.baseline) {
|
|
3880
|
-
const baselinePath =
|
|
4130
|
+
const baselinePath = path14.resolve(opts.baseline);
|
|
3881
4131
|
if (await pathExists(baselinePath)) {
|
|
3882
4132
|
try {
|
|
3883
4133
|
const baseline = await readJsonFile(baselinePath);
|
|
3884
4134
|
artifact.baseline = baselinePath;
|
|
3885
4135
|
artifact.delta = artifact.drift.score - baseline.drift.score;
|
|
3886
4136
|
} catch {
|
|
3887
|
-
console.error(
|
|
4137
|
+
console.error(chalk5.yellow(`Warning: Could not read baseline file: ${baselinePath}`));
|
|
3888
4138
|
}
|
|
3889
4139
|
}
|
|
3890
4140
|
}
|
|
3891
|
-
const vibgrateDir =
|
|
4141
|
+
const vibgrateDir = path14.join(rootDir, ".vibgrate");
|
|
3892
4142
|
await ensureDir(vibgrateDir);
|
|
3893
|
-
await writeJsonFile(
|
|
4143
|
+
await writeJsonFile(path14.join(vibgrateDir, "scan_result.json"), artifact);
|
|
4144
|
+
for (const project of allProjects) {
|
|
4145
|
+
if (project.drift && project.path) {
|
|
4146
|
+
const projectDir = path14.resolve(rootDir, project.path);
|
|
4147
|
+
const projectVibgrateDir = path14.join(projectDir, ".vibgrate");
|
|
4148
|
+
await ensureDir(projectVibgrateDir);
|
|
4149
|
+
await writeJsonFile(path14.join(projectVibgrateDir, "project_score.json"), {
|
|
4150
|
+
projectId: project.projectId,
|
|
4151
|
+
name: project.name,
|
|
4152
|
+
type: project.type,
|
|
4153
|
+
path: project.path,
|
|
4154
|
+
score: project.drift.score,
|
|
4155
|
+
riskLevel: project.drift.riskLevel,
|
|
4156
|
+
components: project.drift.components,
|
|
4157
|
+
measured: project.drift.measured,
|
|
4158
|
+
scannedAt: artifact.timestamp,
|
|
4159
|
+
vibgrateVersion: VERSION
|
|
4160
|
+
});
|
|
4161
|
+
}
|
|
4162
|
+
}
|
|
3894
4163
|
if (opts.format === "json") {
|
|
3895
4164
|
const jsonStr = JSON.stringify(artifact, null, 2);
|
|
3896
4165
|
if (opts.out) {
|
|
3897
|
-
await writeTextFile(
|
|
3898
|
-
console.log(
|
|
4166
|
+
await writeTextFile(path14.resolve(opts.out), jsonStr);
|
|
4167
|
+
console.log(chalk5.green("\u2714") + ` JSON written to ${opts.out}`);
|
|
3899
4168
|
} else {
|
|
3900
4169
|
console.log(jsonStr);
|
|
3901
4170
|
}
|
|
@@ -3903,8 +4172,8 @@ async function runScan(rootDir, opts) {
|
|
|
3903
4172
|
const sarif = formatSarif(artifact);
|
|
3904
4173
|
const sarifStr = JSON.stringify(sarif, null, 2);
|
|
3905
4174
|
if (opts.out) {
|
|
3906
|
-
await writeTextFile(
|
|
3907
|
-
console.log(
|
|
4175
|
+
await writeTextFile(path14.resolve(opts.out), sarifStr);
|
|
4176
|
+
console.log(chalk5.green("\u2714") + ` SARIF written to ${opts.out}`);
|
|
3908
4177
|
} else {
|
|
3909
4178
|
console.log(sarifStr);
|
|
3910
4179
|
}
|
|
@@ -3912,15 +4181,66 @@ async function runScan(rootDir, opts) {
|
|
|
3912
4181
|
const text = formatText(artifact);
|
|
3913
4182
|
console.log(text);
|
|
3914
4183
|
if (opts.out) {
|
|
3915
|
-
await writeTextFile(
|
|
4184
|
+
await writeTextFile(path14.resolve(opts.out), text);
|
|
3916
4185
|
}
|
|
3917
4186
|
}
|
|
3918
4187
|
return artifact;
|
|
3919
4188
|
}
|
|
3920
|
-
|
|
3921
|
-
const
|
|
4189
|
+
async function autoPush(artifact, rootDir, opts) {
|
|
4190
|
+
const dsn = opts.dsn || process.env.VIBGRATE_DSN;
|
|
4191
|
+
if (!dsn) {
|
|
4192
|
+
console.error(chalk5.red("No DSN provided for push."));
|
|
4193
|
+
console.error(chalk5.dim("Set VIBGRATE_DSN environment variable or use --dsn flag."));
|
|
4194
|
+
if (opts.strict) process.exit(1);
|
|
4195
|
+
return;
|
|
4196
|
+
}
|
|
4197
|
+
const parsed = parseDsn(dsn);
|
|
4198
|
+
if (!parsed) {
|
|
4199
|
+
console.error(chalk5.red("Invalid DSN format."));
|
|
4200
|
+
if (opts.strict) process.exit(1);
|
|
4201
|
+
return;
|
|
4202
|
+
}
|
|
4203
|
+
const body = JSON.stringify(artifact);
|
|
4204
|
+
const timestamp = String(Date.now());
|
|
4205
|
+
const hmac = computeHmac(body, parsed.secret);
|
|
4206
|
+
let host = parsed.host;
|
|
4207
|
+
if (opts.region) {
|
|
4208
|
+
try {
|
|
4209
|
+
host = resolveIngestHost(opts.region);
|
|
4210
|
+
} catch (e) {
|
|
4211
|
+
console.error(chalk5.red(e instanceof Error ? e.message : String(e)));
|
|
4212
|
+
if (opts.strict) process.exit(1);
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
const url = `https://${host}/v1/ingest/scan`;
|
|
4217
|
+
console.log(chalk5.dim(`Uploading to ${host}...`));
|
|
4218
|
+
try {
|
|
4219
|
+
const response = await fetch(url, {
|
|
4220
|
+
method: "POST",
|
|
4221
|
+
headers: {
|
|
4222
|
+
"Content-Type": "application/json",
|
|
4223
|
+
"X-Vibgrate-Timestamp": timestamp,
|
|
4224
|
+
"Authorization": `VibgrateDSN ${parsed.keyId}:${hmac}`
|
|
4225
|
+
},
|
|
4226
|
+
body
|
|
4227
|
+
});
|
|
4228
|
+
if (!response.ok) {
|
|
4229
|
+
const text = await response.text();
|
|
4230
|
+
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
4231
|
+
}
|
|
4232
|
+
const result = await response.json();
|
|
4233
|
+
console.log(chalk5.green("\u2714") + ` Uploaded successfully (${result.ingestId ?? "ok"})`);
|
|
4234
|
+
} catch (e) {
|
|
4235
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
4236
|
+
console.error(chalk5.red(`Upload failed: ${msg}`));
|
|
4237
|
+
if (opts.strict) process.exit(1);
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
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) => {
|
|
4241
|
+
const rootDir = path14.resolve(targetPath);
|
|
3922
4242
|
if (!await pathExists(rootDir)) {
|
|
3923
|
-
console.error(
|
|
4243
|
+
console.error(chalk5.red(`Path does not exist: ${rootDir}`));
|
|
3924
4244
|
process.exit(1);
|
|
3925
4245
|
}
|
|
3926
4246
|
const scanOpts = {
|
|
@@ -3929,38 +4249,45 @@ var scanCommand = new Command("scan").description("Scan a project for upgrade dr
|
|
|
3929
4249
|
failOn: opts.failOn,
|
|
3930
4250
|
baseline: opts.baseline,
|
|
3931
4251
|
changedOnly: opts.changedOnly,
|
|
3932
|
-
concurrency: parseInt(opts.concurrency, 10) || 8
|
|
4252
|
+
concurrency: parseInt(opts.concurrency, 10) || 8,
|
|
4253
|
+
push: opts.push,
|
|
4254
|
+
dsn: opts.dsn,
|
|
4255
|
+
region: opts.region,
|
|
4256
|
+
strict: opts.strict
|
|
3933
4257
|
};
|
|
3934
4258
|
const artifact = await runScan(rootDir, scanOpts);
|
|
3935
4259
|
if (opts.failOn) {
|
|
3936
4260
|
const hasErrors = artifact.findings.some((f) => f.level === "error");
|
|
3937
4261
|
const hasWarnings = artifact.findings.some((f) => f.level === "warning");
|
|
3938
4262
|
if (opts.failOn === "error" && hasErrors) {
|
|
3939
|
-
console.error(
|
|
4263
|
+
console.error(chalk5.red(`
|
|
3940
4264
|
Failing: ${artifact.findings.filter((f) => f.level === "error").length} error finding(s) detected.`));
|
|
3941
4265
|
process.exit(2);
|
|
3942
4266
|
}
|
|
3943
4267
|
if (opts.failOn === "warn" && (hasErrors || hasWarnings)) {
|
|
3944
|
-
console.error(
|
|
4268
|
+
console.error(chalk5.red(`
|
|
3945
4269
|
Failing: findings detected at warn level or above.`));
|
|
3946
4270
|
process.exit(2);
|
|
3947
4271
|
}
|
|
3948
4272
|
}
|
|
4273
|
+
if (opts.push) {
|
|
4274
|
+
await autoPush(artifact, rootDir, scanOpts);
|
|
4275
|
+
}
|
|
3949
4276
|
});
|
|
3950
4277
|
|
|
3951
4278
|
export {
|
|
3952
4279
|
readJsonFile,
|
|
3953
|
-
readTextFile,
|
|
3954
4280
|
pathExists,
|
|
3955
4281
|
ensureDir,
|
|
3956
4282
|
writeJsonFile,
|
|
3957
|
-
writeTextFile,
|
|
3958
4283
|
writeDefaultConfig,
|
|
3959
4284
|
computeDriftScore,
|
|
3960
4285
|
generateFindings,
|
|
3961
4286
|
VERSION,
|
|
3962
4287
|
formatText,
|
|
3963
4288
|
formatSarif,
|
|
4289
|
+
dsnCommand,
|
|
4290
|
+
pushCommand,
|
|
3964
4291
|
runScan,
|
|
3965
4292
|
scanCommand
|
|
3966
4293
|
};
|