git-cli-scanner 1.1.2 → 1.1.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.
Files changed (2) hide show
  1. package/dist/cli.js +121 -107
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -538,117 +538,131 @@ var init_logger = __esm({
538
538
  // src/utils/explorer.ts
539
539
  var explorer_exports = {};
540
540
  __export(explorer_exports, {
541
- explorePrompt: () => explorePrompt
541
+ runExplorer: () => runExplorer
542
542
  });
543
- var import_core, import_picocolors3, fs2, path2, explorePrompt;
543
+ async function scanFile(filePath, baseDir) {
544
+ console.log(`
545
+ ${import_picocolors3.default.dim(" Scanning")} ${import_picocolors3.default.cyan(path2.basename(filePath))}${import_picocolors3.default.dim("...")}`);
546
+ const issues = await scanDirectory(path2.dirname(filePath));
547
+ const relativePath = path2.relative(baseDir, filePath).replace(/\\/g, "/");
548
+ const fileIssues = issues.filter((i) => {
549
+ const issuePath = i.file.replace(/\\/g, "/");
550
+ return issuePath === relativePath || issuePath === path2.basename(filePath);
551
+ });
552
+ if (fileIssues.length === 0) {
553
+ console.log(import_picocolors3.default.green("\n No vulnerabilities found in this file.\n"));
554
+ } else {
555
+ console.log(`
556
+ ${import_picocolors3.default.bold(`Found ${fileIssues.length} issue(s):`)}
557
+ `);
558
+ fileIssues.forEach((issue) => {
559
+ const severity = issue.severity === "high" ? import_picocolors3.default.red("HIGH") : issue.severity === "medium" ? import_picocolors3.default.yellow("MEDIUM") : import_picocolors3.default.dim("DUMMY");
560
+ const indicator = issue.severity === "dummy" ? import_picocolors3.default.dim("\u25CB") : "\u25CF";
561
+ console.log(` ${indicator} ${severity} ${import_picocolors3.default.dim("\xB7")} ${issue.type}`);
562
+ console.log(` ${import_picocolors3.default.dim("Line:")} ${issue.line || "?"}`);
563
+ console.log(` ${import_picocolors3.default.dim("Match:")} ${issue.match.substring(0, 60)}`);
564
+ if (issue.solution) {
565
+ console.log(` ${import_picocolors3.default.dim("Fix:")} ${import_picocolors3.default.green(issue.solution)}`);
566
+ }
567
+ console.log();
568
+ });
569
+ }
570
+ }
571
+ function buildChoices(dirPath) {
572
+ const choices = [];
573
+ choices.push({
574
+ name: import_picocolors3.default.dim(".. (go back)"),
575
+ value: "__BACK__"
576
+ });
577
+ let entries = [];
578
+ try {
579
+ entries = fs2.readdirSync(dirPath, { withFileTypes: true });
580
+ } catch (err) {
581
+ return choices;
582
+ }
583
+ entries = entries.filter((e) => e.name !== ".git" && e.name !== "node_modules");
584
+ entries.sort((a, b) => {
585
+ if (a.isDirectory() && !b.isDirectory()) return -1;
586
+ if (!a.isDirectory() && b.isDirectory()) return 1;
587
+ return a.name.localeCompare(b.name);
588
+ });
589
+ for (const entry of entries) {
590
+ if (entry.isDirectory()) {
591
+ choices.push({
592
+ name: `${import_picocolors3.default.blue("/")}${entry.name}`,
593
+ value: path2.join(dirPath, entry.name),
594
+ description: "folder"
595
+ });
596
+ } else {
597
+ choices.push({
598
+ name: ` ${entry.name}`,
599
+ value: path2.join(dirPath, entry.name),
600
+ description: `${(fs2.statSync(path2.join(dirPath, entry.name)).size / 1024).toFixed(1)} KB`
601
+ });
602
+ }
603
+ }
604
+ choices.push({
605
+ name: import_picocolors3.default.dim("Exit explorer"),
606
+ value: "__EXIT__"
607
+ });
608
+ return choices;
609
+ }
610
+ async function runExplorer(startDir) {
611
+ let currentDir = path2.resolve(startDir);
612
+ const baseDir = currentDir;
613
+ console.log(`
614
+ ${import_picocolors3.default.bold("Git CLI Scanner")} ${import_picocolors3.default.dim("\xB7 Interactive Explorer")}`);
615
+ console.log(import_picocolors3.default.dim(" Use arrow keys to navigate, Enter to select\n"));
616
+ while (true) {
617
+ const choices = buildChoices(currentDir);
618
+ let selected;
619
+ try {
620
+ selected = await (0, import_prompts2.select)({
621
+ message: `${import_picocolors3.default.cyan(currentDir)}`,
622
+ choices,
623
+ pageSize: 20,
624
+ loop: false
625
+ });
626
+ } catch (err) {
627
+ break;
628
+ }
629
+ if (selected === "__EXIT__") {
630
+ break;
631
+ }
632
+ if (selected === "__BACK__") {
633
+ currentDir = path2.dirname(currentDir);
634
+ continue;
635
+ }
636
+ try {
637
+ const stat = fs2.statSync(selected);
638
+ if (stat.isDirectory()) {
639
+ currentDir = selected;
640
+ } else {
641
+ await scanFile(selected, baseDir);
642
+ const next = await (0, import_prompts2.select)({
643
+ message: "What next?",
644
+ choices: [
645
+ { name: "Continue browsing", value: "continue" },
646
+ { name: "Exit explorer", value: "exit" }
647
+ ]
648
+ });
649
+ if (next === "exit") break;
650
+ }
651
+ } catch (err) {
652
+ console.log(import_picocolors3.default.red(` Cannot access: ${selected}`));
653
+ }
654
+ }
655
+ console.log(import_picocolors3.default.dim("\n Explorer closed.\n"));
656
+ }
657
+ var import_prompts2, import_picocolors3, fs2, path2;
544
658
  var init_explorer = __esm({
545
659
  "src/utils/explorer.ts"() {
546
660
  "use strict";
547
- import_core = require("@inquirer/core");
661
+ import_prompts2 = require("@inquirer/prompts");
548
662
  import_picocolors3 = __toESM(require("picocolors"));
549
663
  fs2 = __toESM(require("fs"));
550
664
  path2 = __toESM(require("path"));
551
665
  init_scanner();
552
- explorePrompt = (0, import_core.createPrompt)(
553
- (config, done) => {
554
- const [currentPath, setCurrentPath] = (0, import_core.useState)(config.currentDir);
555
- const [selectedIndex, setSelectedIndex] = (0, import_core.useState)(0);
556
- const [scanResult, setScanResult] = (0, import_core.useState)(null);
557
- const [scanningFile, setScanningFile] = (0, import_core.useState)(null);
558
- const prefix = (0, import_core.usePrefix)({ status: "idle" });
559
- let items = [];
560
- try {
561
- items = fs2.readdirSync(currentPath, { withFileTypes: true });
562
- } catch (err) {
563
- }
564
- items.sort((a, b) => {
565
- if (a.isDirectory() && !b.isDirectory()) return -1;
566
- if (!a.isDirectory() && b.isDirectory()) return 1;
567
- return a.name.localeCompare(b.name);
568
- });
569
- const choices = [
570
- { name: "\u{1F519} Go Back (or press Left Arrow)", type: "back" },
571
- ...items.map((i) => ({
572
- name: i.isDirectory() ? `\u{1F4C1} ${i.name}` : `\u{1F4C4} ${i.name}`,
573
- type: i.isDirectory() ? "dir" : "file",
574
- nameRaw: i.name
575
- }))
576
- ];
577
- (0, import_core.useKeypress)(async (key, rl) => {
578
- if ((0, import_core.isEnterKey)(key) || key.name === "right") {
579
- const selected = choices[selectedIndex];
580
- if (selected.type === "back") {
581
- setCurrentPath(path2.dirname(currentPath));
582
- setSelectedIndex(0);
583
- setScanResult(null);
584
- } else if (selected.type === "dir") {
585
- setCurrentPath(path2.join(currentPath, selected.nameRaw));
586
- setSelectedIndex(0);
587
- setScanResult(null);
588
- } else if (selected.type === "file") {
589
- const fullPath = path2.join(currentPath, selected.nameRaw);
590
- setScanningFile(fullPath);
591
- setScanResult(null);
592
- const issues = await scanDirectory(currentPath);
593
- const fileIssues = issues.filter((i) => i.file === path2.relative(process.cwd(), fullPath).replace(/\\/g, "/"));
594
- setScanResult(fileIssues);
595
- setScanningFile(null);
596
- }
597
- } else if (key.name === "left") {
598
- setCurrentPath(path2.dirname(currentPath));
599
- setSelectedIndex(0);
600
- setScanResult(null);
601
- } else if ((0, import_core.isUpKey)(key)) {
602
- setSelectedIndex((prev) => prev > 0 ? prev - 1 : choices.length - 1);
603
- setScanResult(null);
604
- } else if ((0, import_core.isDownKey)(key)) {
605
- setSelectedIndex((prev) => prev < choices.length - 1 ? prev + 1 : 0);
606
- setScanResult(null);
607
- } else if (key.name === "c" && key.ctrl) {
608
- done("");
609
- }
610
- });
611
- const page = (0, import_core.usePagination)({
612
- items: choices,
613
- active: selectedIndex,
614
- renderItem: ({ item, isActive }) => {
615
- if (isActive) {
616
- return import_picocolors3.default.cyan(`\u276F ${item.name}`);
617
- }
618
- return ` ${item.name}`;
619
- },
620
- pageSize: 15,
621
- loop: false
622
- });
623
- let message = `${prefix} ${import_picocolors3.default.bold("Exploring:")} ${import_picocolors3.default.cyan(currentPath)}
624
-
625
- ${page}`;
626
- if (scanResult) {
627
- message += `
628
-
629
- ${import_picocolors3.default.bold("Scan Results for " + choices[selectedIndex].nameRaw + ":")}
630
- `;
631
- if (scanResult.length === 0) {
632
- message += import_picocolors3.default.green("\u2714 No vulnerabilities found in this file.\n");
633
- } else {
634
- scanResult.forEach((issue) => {
635
- message += ` ${issue.severity === "high" ? import_picocolors3.default.red("\u25CF HIGH") : issue.severity === "medium" ? import_picocolors3.default.yellow("\u25CF MEDIUM") : import_picocolors3.default.dim("\u25CB DUMMY")} \xB7 ${issue.type}
636
- `;
637
- if (issue.solution) {
638
- message += ` Fix: ${import_picocolors3.default.green(issue.solution)}
639
- `;
640
- }
641
- });
642
- }
643
- } else if (scanningFile) {
644
- message += `
645
-
646
- ${import_picocolors3.default.yellow("Scanning...")} (Please wait)
647
- `;
648
- }
649
- return message;
650
- }
651
- );
652
666
  }
653
667
  });
654
668
 
@@ -773,7 +787,7 @@ program.command("scan").description("Interactive vulnerability scan for staged f
773
787
  "Thinking..."
774
788
  ]);
775
789
  spinner.start();
776
- await new Promise((resolve) => setTimeout(resolve, 3e3));
790
+ await new Promise((resolve2) => setTimeout(resolve2, 3e3));
777
791
  const issues = await scanDiff();
778
792
  if (issues.length > 0) {
779
793
  const blockerIssues = issues.filter((i) => i.severity !== "dummy");
@@ -818,7 +832,7 @@ program.command("scan-all [dir]").description("Scan an entire directory or codeb
818
832
  ]);
819
833
  spinner.start();
820
834
  const { scanDirectory: scanDirectory2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
821
- await new Promise((resolve) => setTimeout(resolve, 2e3));
835
+ await new Promise((resolve2) => setTimeout(resolve2, 2e3));
822
836
  const issues = await scanDirectory2(scanDir);
823
837
  if (issues.length > 0) {
824
838
  const blockerIssues = issues.filter((i) => i.severity !== "dummy");
@@ -841,8 +855,8 @@ program.command("scan-all [dir]").description("Scan an entire directory or codeb
841
855
  });
842
856
  program.command("explore").description("Launch an interactive Terminal UI to browse and scan files").action(async () => {
843
857
  try {
844
- const { explorePrompt: explorePrompt2 } = (init_explorer(), __toCommonJS(explorer_exports));
845
- await explorePrompt2({ currentDir: process.cwd() });
858
+ const { runExplorer: runExplorer2 } = (init_explorer(), __toCommonJS(explorer_exports));
859
+ await runExplorer2(process.cwd());
846
860
  process.exit(0);
847
861
  } catch (err) {
848
862
  if (err.name === "ExitPromptError" || err.message?.includes("closed")) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-cli-scanner",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "A powerful interactive CLI tool that scans your codebase for hardcoded secrets, API keys, passwords, and private keys before they reach your Git history.",
5
5
  "main": "dist/cli.js",
6
6
  "scripts": {