@westbayberry/dg 1.0.14 → 1.0.16

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/index.mjs +239 -137
  2. package/package.json +5 -1
package/dist/index.mjs CHANGED
@@ -114,12 +114,6 @@ function parseConfig(argv) {
114
114
  const noConfig = values["no-config"];
115
115
  const dgrc = noConfig ? {} : loadDgrc();
116
116
  const apiKey = dgrc.apiKey ?? "";
117
- if (!apiKey) {
118
- process.stderr.write(
119
- "Error: Not logged in. Run `dg login` to authenticate.\n"
120
- );
121
- process.exit(1);
122
- }
123
117
  const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
124
118
  if (!["block", "warn", "off"].includes(modeRaw)) {
125
119
  process.stderr.write(
@@ -131,7 +125,10 @@ function parseConfig(argv) {
131
125
  const allowlistRaw = values.allowlist ?? process.env.DG_ALLOWLIST ?? "";
132
126
  const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
133
127
  const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
134
- const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
128
+ let maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
129
+ if (!apiKey) {
130
+ maxPackages = Math.min(maxPackages, 50);
131
+ }
135
132
  const debug = values.debug || process.env.DG_DEBUG === "1";
136
133
  if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
137
134
  process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
@@ -3296,7 +3293,7 @@ var require_react_development = __commonJS({
3296
3293
  }
3297
3294
  return dispatcher.useContext(Context);
3298
3295
  }
3299
- function useState6(initialState) {
3296
+ function useState7(initialState) {
3300
3297
  var dispatcher = resolveDispatcher();
3301
3298
  return dispatcher.useState(initialState);
3302
3299
  }
@@ -3304,7 +3301,7 @@ var require_react_development = __commonJS({
3304
3301
  var dispatcher = resolveDispatcher();
3305
3302
  return dispatcher.useReducer(reducer4, initialArg, init);
3306
3303
  }
3307
- function useRef6(initialValue) {
3304
+ function useRef7(initialValue) {
3308
3305
  var dispatcher = resolveDispatcher();
3309
3306
  return dispatcher.useRef(initialValue);
3310
3307
  }
@@ -4098,8 +4095,8 @@ var require_react_development = __commonJS({
4098
4095
  exports.useLayoutEffect = useLayoutEffect2;
4099
4096
  exports.useMemo = useMemo4;
4100
4097
  exports.useReducer = useReducer5;
4101
- exports.useRef = useRef6;
4102
- exports.useState = useState6;
4098
+ exports.useRef = useRef7;
4099
+ exports.useState = useState7;
4103
4100
  exports.useSyncExternalStore = useSyncExternalStore;
4104
4101
  exports.useTransition = useTransition;
4105
4102
  exports.version = ReactVersion;
@@ -38917,15 +38914,35 @@ async function callAnalyzeAPI(packages, config, onProgress) {
38917
38914
  for (let i = 0; i < packages.length; i += BATCH_SIZE) {
38918
38915
  batches.push(packages.slice(i, i + BATCH_SIZE));
38919
38916
  }
38920
- const results = [];
38917
+ const MAX_CONCURRENT_BATCHES = 2;
38918
+ const results = new Array(batches.length);
38921
38919
  let completed = 0;
38922
- for (const batch of batches) {
38923
- const result = await callBatchWithRetry(batch, config);
38924
- completed += batch.length;
38920
+ let nextIdx = 0;
38921
+ async function runBatch(idx) {
38922
+ const result = await callBatchWithRetry(batches[idx], config);
38923
+ results[idx] = result;
38924
+ completed += batches[idx].length;
38925
38925
  if (onProgress) {
38926
- onProgress(completed, packages.length, batch.map((p) => p.name));
38926
+ onProgress(completed, packages.length, batches[idx].map((p) => p.name));
38927
+ }
38928
+ }
38929
+ const inFlight = /* @__PURE__ */ new Set();
38930
+ while (nextIdx < batches.length && inFlight.size < MAX_CONCURRENT_BATCHES) {
38931
+ const idx = nextIdx++;
38932
+ const p = runBatch(idx).then(() => {
38933
+ inFlight.delete(p);
38934
+ });
38935
+ inFlight.add(p);
38936
+ }
38937
+ while (inFlight.size > 0) {
38938
+ await Promise.race(inFlight);
38939
+ while (nextIdx < batches.length && inFlight.size < MAX_CONCURRENT_BATCHES) {
38940
+ const idx = nextIdx++;
38941
+ const p = runBatch(idx).then(() => {
38942
+ inFlight.delete(p);
38943
+ });
38944
+ inFlight.add(p);
38927
38945
  }
38928
- results.push(result);
38929
38946
  }
38930
38947
  return mergeResponses(results, config);
38931
38948
  }
@@ -38984,13 +39001,16 @@ async function callAnalyzeBatch(packages, config) {
38984
39001
  const timeoutId = setTimeout(() => controller.abort(), 12e4);
38985
39002
  let response;
38986
39003
  try {
39004
+ const headers = {
39005
+ "Content-Type": "application/json",
39006
+ "User-Agent": "dependency-guardian-cli/1.0.0"
39007
+ };
39008
+ if (config.apiKey) {
39009
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
39010
+ }
38987
39011
  response = await fetch(url, {
38988
39012
  method: "POST",
38989
- headers: {
38990
- "Content-Type": "application/json",
38991
- Authorization: `Bearer ${config.apiKey}`,
38992
- "User-Agent": "dependency-guardian-cli/1.0.0"
38993
- },
39013
+ headers,
38994
39014
  body: JSON.stringify(payload),
38995
39015
  signal: controller.signal
38996
39016
  });
@@ -39012,6 +39032,14 @@ async function callAnalyzeBatch(packages, config) {
39012
39032
  );
39013
39033
  }
39014
39034
  if (response.status === 429) {
39035
+ const body = await response.json().catch(() => ({}));
39036
+ if (body.anonymous) {
39037
+ throw new APIError(
39038
+ "Anonymous scan limit reached. Run `dg login` for 200 free scans/month.",
39039
+ 429,
39040
+ ""
39041
+ );
39042
+ }
39015
39043
  throw new APIError(
39016
39044
  "Rate limit exceeded. Upgrade your plan at https://westbayberry.com/pricing",
39017
39045
  429,
@@ -39064,13 +39092,16 @@ async function callPyPIBatch(packages, config) {
39064
39092
  const timeoutId = setTimeout(() => controller.abort(), 12e4);
39065
39093
  let response;
39066
39094
  try {
39095
+ const headers = {
39096
+ "Content-Type": "application/json",
39097
+ "User-Agent": "dependency-guardian-cli/1.0.0"
39098
+ };
39099
+ if (config.apiKey) {
39100
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
39101
+ }
39067
39102
  response = await fetch(url, {
39068
39103
  method: "POST",
39069
- headers: {
39070
- "Content-Type": "application/json",
39071
- Authorization: `Bearer ${config.apiKey}`,
39072
- "User-Agent": "dependency-guardian-cli/1.0.0"
39073
- },
39104
+ headers,
39074
39105
  body: JSON.stringify(payload),
39075
39106
  signal: controller.signal
39076
39107
  });
@@ -39092,6 +39123,14 @@ async function callPyPIBatch(packages, config) {
39092
39123
  );
39093
39124
  }
39094
39125
  if (response.status === 429) {
39126
+ const body = await response.json().catch(() => ({}));
39127
+ if (body.anonymous) {
39128
+ throw new APIError(
39129
+ "Anonymous scan limit reached. Run `dg login` for 200 free scans/month.",
39130
+ 429,
39131
+ ""
39132
+ );
39133
+ }
39095
39134
  throw new APIError(
39096
39135
  "Rate limit exceeded. Upgrade your plan at https://westbayberry.com/pricing",
39097
39136
  429,
@@ -39116,7 +39155,7 @@ var init_api = __esm({
39116
39155
  this.name = "APIError";
39117
39156
  }
39118
39157
  };
39119
- BATCH_SIZE = 15;
39158
+ BATCH_SIZE = 200;
39120
39159
  MAX_RETRIES = 2;
39121
39160
  RETRY_DELAY_MS = 5e3;
39122
39161
  }
@@ -39755,7 +39794,7 @@ function truncate(s, max) {
39755
39794
  function groupPackages(packages) {
39756
39795
  const map = /* @__PURE__ */ new Map();
39757
39796
  for (const pkg of packages) {
39758
- const fingerprint = pkg.findings.length === 0 ? `__clean_${pkg.score}` : pkg.findings.map((f) => `${f.id}:${f.severity}`).sort().join("|") + `|score:${pkg.score}`;
39797
+ const fingerprint = pkg.findings.length === 0 ? `__clean_${pkg.score}` : pkg.findings.map((f) => `${f.category}:${f.severity}`).sort().join("|") + `|score:${pkg.score}`;
39759
39798
  const group = map.get(fingerprint) ?? [];
39760
39799
  group.push(pkg);
39761
39800
  map.set(fingerprint, group);
@@ -39837,7 +39876,7 @@ function renderResultStatic(result, config) {
39837
39876
  const sevLabel = SEVERITY_LABELS[finding.severity] ?? "INFO";
39838
39877
  const colorFn = severityColor(finding.severity);
39839
39878
  lines.push(
39840
- ` ${colorFn(pad(sevLabel, 10))}${finding.id} \u2014 ${finding.title}`
39879
+ ` ${colorFn(pad(sevLabel, 10))}${finding.category} \u2014 ${finding.title}`
39841
39880
  );
39842
39881
  const evidenceLimit = 3;
39843
39882
  for (let i = 0; i < Math.min(finding.evidence.length, evidenceLimit); i++) {
@@ -39944,6 +39983,11 @@ async function runStatic(config) {
39944
39983
  );
39945
39984
  const output = renderResultStatic(result, config);
39946
39985
  process.stdout.write(output + "\n");
39986
+ if (!config.apiKey && !config.json) {
39987
+ process.stderr.write(
39988
+ "\n" + import_chalk4.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + import_chalk4.default.cyan(" Want this on every PR? ") + "Run " + import_chalk4.default.white("dg login") + import_chalk4.default.dim(" \u2192 free tier, 200 scans/month\n") + import_chalk4.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
39989
+ );
39990
+ }
39947
39991
  if (result.action === "block" && config.mode === "block") {
39948
39992
  process.exit(2);
39949
39993
  } else if (result.action === "block" || result.action === "warn") {
@@ -40062,6 +40106,11 @@ async function scanAndInstallStatic(resolved, parsed, config) {
40062
40106
  }
40063
40107
  const output = renderResultStatic(result, config);
40064
40108
  process.stdout.write(output + "\n");
40109
+ if (!config.apiKey) {
40110
+ process.stderr.write(
40111
+ "\n" + import_chalk4.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n") + import_chalk4.default.cyan(" Want this on every PR? ") + "Run " + import_chalk4.default.white("dg login") + import_chalk4.default.dim(" \u2192 free tier, 200 scans/month\n") + import_chalk4.default.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n")
40112
+ );
40113
+ }
40065
40114
  if (result.action === "warn") {
40066
40115
  process.stderr.write(
40067
40116
  import_chalk4.default.yellow(" Warnings detected. Proceeding with install.\n\n")
@@ -40602,7 +40651,7 @@ var init_DurationLine = __esm({
40602
40651
  function groupPackages2(packages) {
40603
40652
  const map = /* @__PURE__ */ new Map();
40604
40653
  for (const pkg of packages) {
40605
- const fingerprint = pkg.findings.length === 0 ? `__clean_${pkg.score}` : pkg.findings.map((f) => `${f.id}:${f.severity}`).sort().join("|") + `|score:${pkg.score}`;
40654
+ const fingerprint = pkg.findings.length === 0 ? `__clean_${pkg.score}` : pkg.findings.map((f) => `${f.category}:${f.severity}`).sort().join("|") + `|score:${pkg.score}`;
40606
40655
  const group = map.get(fingerprint) ?? [];
40607
40656
  group.push(pkg);
40608
40657
  map.set(fingerprint, group);
@@ -40722,7 +40771,7 @@ var init_ResultsView = __esm({
40722
40771
  /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
40723
40772
  " ",
40724
40773
  colorFn(pad2(sevLabel, 10)),
40725
- finding.id,
40774
+ finding.category,
40726
40775
  " ",
40727
40776
  "\u2014",
40728
40777
  " ",
@@ -40737,7 +40786,7 @@ var init_ResultsView = __esm({
40737
40786
  import_chalk6.default.dim(`... and ${overflow} more`)
40738
40787
  ] }),
40739
40788
  /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(Text, { children: " " })
40740
- ] }, `${finding.id}-${idx}`);
40789
+ ] }, `${finding.category}-${idx}`);
40741
40790
  }),
40742
40791
  safeVersion && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(Text, { children: [
40743
40792
  " ",
@@ -40990,14 +41039,14 @@ function useScan(config) {
40990
41039
  started.current = true;
40991
41040
  const projects = discoverProjects(process.cwd());
40992
41041
  if (projects.length > 1) setMultiProjects(projects);
41042
+ if (projects.length > 1) {
41043
+ dispatch({ type: "PROJECTS_FOUND", projects });
41044
+ return;
41045
+ }
40993
41046
  try {
40994
41047
  const discovery = discoverChanges(process.cwd(), config);
40995
41048
  const packages = discovery.packages.filter((p) => !config.allowlist.includes(p.name));
40996
41049
  if (packages.length === 0) {
40997
- if (discovery.packages.length === 0 && projects.length > 0) {
40998
- dispatch({ type: "PROJECTS_FOUND", projects });
40999
- return;
41000
- }
41001
41050
  const message = discovery.packages.length === 0 ? "No package changes detected." : "All changed packages are allowlisted.";
41002
41051
  dispatch({ type: "DISCOVERY_EMPTY", message });
41003
41052
  return;
@@ -41009,12 +41058,8 @@ function useScan(config) {
41009
41058
  dispatch({ type: "DISCOVERY_EMPTY", message: "No dependency files found." });
41010
41059
  return;
41011
41060
  }
41012
- if (projects.length === 1) {
41013
- dispatch({ type: "DISCOVERY_COMPLETE", packages: [], skippedCount: 0 });
41014
- scanProjects(projects, config, dispatch);
41015
- return;
41016
- }
41017
- dispatch({ type: "PROJECTS_FOUND", projects });
41061
+ dispatch({ type: "DISCOVERY_COMPLETE", packages: [], skippedCount: 0 });
41062
+ scanProjects(projects, config, dispatch);
41018
41063
  }
41019
41064
  }, [config]);
41020
41065
  const scanSelectedProjects = (0, import_react27.useCallback)((projects) => {
@@ -41205,11 +41250,45 @@ var init_useExpandAnimation = __esm({
41205
41250
  }
41206
41251
  });
41207
41252
 
41253
+ // src/ui/hooks/useTerminalSize.ts
41254
+ function useTerminalSize() {
41255
+ const { stdout } = use_stdout_default();
41256
+ const [size, setSize] = (0, import_react29.useState)({
41257
+ rows: stdout?.rows ?? process.stdout.rows ?? 24,
41258
+ cols: stdout?.columns ?? process.stdout.columns ?? 80
41259
+ });
41260
+ (0, import_react29.useEffect)(() => {
41261
+ const handle = () => {
41262
+ const rows = process.stdout.rows ?? 24;
41263
+ const cols = process.stdout.columns ?? 80;
41264
+ if (process.stdout.isTTY) {
41265
+ process.stdout.write("\x1B[2J\x1B[H");
41266
+ }
41267
+ setSize({ rows, cols });
41268
+ };
41269
+ process.stdout.setMaxListeners(process.stdout.getMaxListeners() + 1);
41270
+ process.stdout.on("resize", handle);
41271
+ return () => {
41272
+ process.stdout.off("resize", handle);
41273
+ process.stdout.setMaxListeners(Math.max(0, process.stdout.getMaxListeners() - 1));
41274
+ };
41275
+ }, []);
41276
+ return size;
41277
+ }
41278
+ var import_react29;
41279
+ var init_useTerminalSize = __esm({
41280
+ async "src/ui/hooks/useTerminalSize.ts"() {
41281
+ "use strict";
41282
+ import_react29 = __toESM(require_react());
41283
+ await init_build2();
41284
+ }
41285
+ });
41286
+
41208
41287
  // src/ui/components/InteractiveResultsView.tsx
41209
41288
  function groupPackages3(packages) {
41210
41289
  const map = /* @__PURE__ */ new Map();
41211
41290
  for (const pkg of packages) {
41212
- const fingerprint = pkg.findings.length === 0 ? `__clean_${pkg.score}` : pkg.findings.map((f) => `${f.id}:${f.severity}`).sort().join("|") + `|score:${pkg.score}`;
41291
+ const fingerprint = pkg.findings.length === 0 ? `__clean_${pkg.score}` : pkg.findings.map((f) => `${f.category}:${f.severity}`).sort().join("|") + `|score:${pkg.score}`;
41213
41292
  const group = map.get(fingerprint) ?? [];
41214
41293
  group.push(pkg);
41215
41294
  map.set(fingerprint, group);
@@ -41275,15 +41354,16 @@ function viewReducer(_state, action) {
41275
41354
  return { cursor: action.cursor, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
41276
41355
  }
41277
41356
  }
41278
- var import_react29, import_chalk10, import_jsx_runtime10, SEVERITY_LABELS3, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, InteractiveResultsView, T, FindingsSummary, FindingsDetail;
41357
+ var import_react30, import_chalk10, import_jsx_runtime10, SEVERITY_LABELS3, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, InteractiveResultsView, T, FindingsSummary, FindingsDetail;
41279
41358
  var init_InteractiveResultsView = __esm({
41280
41359
  async "src/ui/components/InteractiveResultsView.tsx"() {
41281
41360
  "use strict";
41282
- import_react29 = __toESM(require_react());
41361
+ import_react30 = __toESM(require_react());
41283
41362
  await init_build2();
41284
41363
  import_chalk10 = __toESM(require_source());
41285
41364
  await init_ScoreHeader();
41286
41365
  init_useExpandAnimation();
41366
+ await init_useTerminalSize();
41287
41367
  import_jsx_runtime10 = __toESM(require_jsx_runtime());
41288
41368
  SEVERITY_LABELS3 = {
41289
41369
  5: "CRIT",
@@ -41308,40 +41388,32 @@ var init_InteractiveResultsView = __esm({
41308
41388
  onExit,
41309
41389
  onBack
41310
41390
  }) => {
41311
- (0, import_react29.useEffect)(() => {
41312
- if (!process.stdout.isTTY) return;
41313
- process.stdout.write("\x1B[?1049h");
41314
- return () => {
41315
- process.stdout.write("\x1B[?1049l");
41316
- };
41317
- }, []);
41318
- const flagged = (0, import_react29.useMemo)(
41391
+ const flagged = (0, import_react30.useMemo)(
41319
41392
  () => result.packages.filter((p) => p.score > 0),
41320
41393
  [result.packages]
41321
41394
  );
41322
- const clean = (0, import_react29.useMemo)(
41395
+ const clean = (0, import_react30.useMemo)(
41323
41396
  () => result.packages.filter((p) => p.score === 0),
41324
41397
  [result.packages]
41325
41398
  );
41326
41399
  const total = result.packages.length;
41327
- const groups = (0, import_react29.useMemo)(() => groupPackages3(flagged), [flagged]);
41328
- const [view, dispatchView] = (0, import_react29.useReducer)(viewReducer, {
41400
+ const groups = (0, import_react30.useMemo)(() => groupPackages3(flagged), [flagged]);
41401
+ const [view, dispatchView] = (0, import_react30.useReducer)(viewReducer, {
41329
41402
  cursor: 0,
41330
41403
  expandLevel: null,
41331
41404
  expandedIndex: null,
41332
41405
  viewport: 0
41333
41406
  });
41334
- const viewRef = (0, import_react29.useRef)(view);
41407
+ const viewRef = (0, import_react30.useRef)(view);
41335
41408
  viewRef.current = view;
41336
- const { stdout } = use_stdout_default();
41337
- const termCols = stdout?.columns ?? process.stdout.columns ?? 80;
41338
- const termRows = stdout?.rows ?? process.stdout.rows ?? 24;
41339
- const availableRows = Math.max(5, termRows - FIXED_CHROME);
41409
+ const { rows: termRows, cols: termCols } = useTerminalSize();
41410
+ const chromeHeight = FIXED_CHROME + (config.apiKey ? 0 : 2);
41411
+ const availableRows = Math.max(5, termRows - chromeHeight);
41340
41412
  const innerWidth = Math.max(40, termCols - 6);
41341
41413
  const getLevel = (idx) => {
41342
41414
  return view.expandedIndex === idx ? view.expandLevel : null;
41343
41415
  };
41344
- const expandTargetHeight = (0, import_react29.useMemo)(() => {
41416
+ const expandTargetHeight = (0, import_react30.useMemo)(() => {
41345
41417
  if (view.expandedIndex === null || view.expandLevel === null) return 0;
41346
41418
  const group = groups[view.expandedIndex];
41347
41419
  if (!group) return 0;
@@ -41357,7 +41429,7 @@ var init_InteractiveResultsView = __esm({
41357
41429
  if (idx === view.expandedIndex) return 1 + animVisibleLines;
41358
41430
  return groupRowHeight(group, level, result.safeVersions);
41359
41431
  };
41360
- const visibleEnd = (0, import_react29.useMemo)(() => {
41432
+ const visibleEnd = (0, import_react30.useMemo)(() => {
41361
41433
  let consumed = 0;
41362
41434
  let end = view.viewport;
41363
41435
  while (end < groups.length) {
@@ -41389,6 +41461,13 @@ var init_InteractiveResultsView = __esm({
41389
41461
  }
41390
41462
  return newStart;
41391
41463
  };
41464
+ (0, import_react30.useEffect)(() => {
41465
+ if (groups.length === 0) return;
41466
+ const { cursor, expandedIndex, expandLevel, viewport } = viewRef.current;
41467
+ const clamped = Math.min(viewport, Math.max(0, groups.length - 1));
41468
+ const newVp = adjustViewport(cursor, expandedIndex, expandLevel, clamped);
41469
+ dispatchView({ type: "MOVE", cursor, viewport: newVp });
41470
+ }, [availableRows]);
41392
41471
  use_input_default((input, key) => {
41393
41472
  if (groups.length === 0) {
41394
41473
  if (input === "q" || key.return) onExit();
@@ -41416,13 +41495,15 @@ var init_InteractiveResultsView = __esm({
41416
41495
  const newVp = adjustViewport(cursor, newExpIdx, newExpLvl, vpStart);
41417
41496
  dispatchView({ type: "EXPAND", expandedIndex: newExpIdx, expandLevel: newExpLvl, viewport: newVp });
41418
41497
  } else if (input === "e") {
41419
- if (expIdx === cursor && expLvl === "detail") return;
41420
- const newVp = adjustViewport(cursor, cursor, "detail", vpStart);
41421
- dispatchView({ type: "EXPAND", expandedIndex: cursor, expandLevel: "detail", viewport: newVp });
41422
- } else if (input === "c") {
41423
- if (expIdx !== null && expLvl !== null) {
41498
+ if (expIdx === cursor && expLvl === "detail") {
41499
+ const newVp = adjustViewport(cursor, cursor, "summary", vpStart);
41500
+ dispatchView({ type: "EXPAND", expandedIndex: cursor, expandLevel: "summary", viewport: newVp });
41501
+ } else if (expIdx === cursor && expLvl === "summary") {
41424
41502
  const newVp = adjustViewport(cursor, null, null, vpStart);
41425
41503
  dispatchView({ type: "EXPAND", expandedIndex: null, expandLevel: null, viewport: newVp });
41504
+ } else {
41505
+ const newVp = adjustViewport(cursor, cursor, "detail", vpStart);
41506
+ dispatchView({ type: "EXPAND", expandedIndex: cursor, expandLevel: "detail", viewport: newVp });
41426
41507
  }
41427
41508
  } else if (input === "b" && onBack) {
41428
41509
  onBack();
@@ -41539,6 +41620,12 @@ var init_InteractiveResultsView = __esm({
41539
41620
  ]
41540
41621
  }
41541
41622
  ),
41623
+ !config.apiKey && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Box_default, { paddingLeft: 1, paddingRight: 1, width: "100%", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { children: [
41624
+ import_chalk10.default.cyan("Want this on every PR?"),
41625
+ " Run ",
41626
+ import_chalk10.default.white("dg login"),
41627
+ import_chalk10.default.dim(" \u2192 free tier, 200 scans/month")
41628
+ ] }) }),
41542
41629
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
41543
41630
  " ",
41544
41631
  groups.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
@@ -41549,10 +41636,7 @@ var init_InteractiveResultsView = __esm({
41549
41636
  " toggle",
41550
41637
  " ",
41551
41638
  import_chalk10.default.cyan("e"),
41552
- " expand",
41553
- " ",
41554
- import_chalk10.default.cyan("c"),
41555
- " collapse",
41639
+ " expand/collapse",
41556
41640
  " ",
41557
41641
  onBack && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
41558
41642
  import_chalk10.default.cyan("b"),
@@ -41594,8 +41678,8 @@ var init_InteractiveResultsView = __esm({
41594
41678
  " ",
41595
41679
  sevColor(pad3(sevLabel, 4)),
41596
41680
  " ",
41597
- import_chalk10.default.dim(f.id)
41598
- ] }, `${f.id}-${idx}`)
41681
+ import_chalk10.default.dim(f.category)
41682
+ ] }, `${f.category}-${idx}`)
41599
41683
  );
41600
41684
  }
41601
41685
  if (hasAffects) {
@@ -41633,15 +41717,15 @@ var init_InteractiveResultsView = __esm({
41633
41717
  " ",
41634
41718
  sevColor(pad3(sevLabel, 4)),
41635
41719
  " ",
41636
- import_chalk10.default.bold(finding.id)
41637
- ] }, `${finding.id}-${idx}-badge`)
41720
+ import_chalk10.default.bold(finding.category)
41721
+ ] }, `${finding.category}-${idx}-badge`)
41638
41722
  );
41639
41723
  allLines.push(
41640
41724
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Text, { dimColor: true, children: [
41641
41725
  continuation,
41642
41726
  " ",
41643
41727
  finding.title
41644
- ] }, `${finding.id}-${idx}-title`)
41728
+ ] }, `${finding.category}-${idx}-title`)
41645
41729
  );
41646
41730
  for (let i = 0; i < evidenceSlice.length; i++) {
41647
41731
  allLines.push(
@@ -41651,7 +41735,7 @@ var init_InteractiveResultsView = __esm({
41651
41735
  import_chalk10.default.dim("\u203A"),
41652
41736
  " ",
41653
41737
  truncate3(evidenceSlice[i], evidenceWidth)
41654
- ] }, `${finding.id}-${idx}-ev-${i}`)
41738
+ ] }, `${finding.category}-${idx}-ev-${i}`)
41655
41739
  );
41656
41740
  }
41657
41741
  if (overflow > 0) {
@@ -41660,7 +41744,7 @@ var init_InteractiveResultsView = __esm({
41660
41744
  continuation,
41661
41745
  " ",
41662
41746
  import_chalk10.default.dim(`+${overflow} more`)
41663
- ] }, `${finding.id}-${idx}-overflow`)
41747
+ ] }, `${finding.category}-${idx}-overflow`)
41664
41748
  );
41665
41749
  }
41666
41750
  }
@@ -41689,19 +41773,16 @@ var init_InteractiveResultsView = __esm({
41689
41773
  });
41690
41774
 
41691
41775
  // src/ui/components/ProjectSelector.tsx
41692
- var import_react30, import_jsx_runtime11, ProjectSelector;
41776
+ var import_react31, import_jsx_runtime11, ProjectSelector;
41693
41777
  var init_ProjectSelector = __esm({
41694
41778
  async "src/ui/components/ProjectSelector.tsx"() {
41695
41779
  "use strict";
41696
- import_react30 = __toESM(require_react());
41780
+ import_react31 = __toESM(require_react());
41697
41781
  await init_build2();
41698
41782
  import_jsx_runtime11 = __toESM(require_jsx_runtime());
41699
41783
  ProjectSelector = ({ projects, onConfirm, onCancel }) => {
41700
- const [cursor, setCursor] = (0, import_react30.useState)(0);
41701
- (0, import_react30.useEffect)(() => {
41702
- process.stdout.write("\x1B[2J\x1B[H");
41703
- }, []);
41704
- const [selected, setSelected] = (0, import_react30.useState)(() => new Set(projects.map((_, i) => i)));
41784
+ const [cursor, setCursor] = (0, import_react31.useState)(0);
41785
+ const [selected, setSelected] = (0, import_react31.useState)(() => new Set(projects.map((_, i) => i)));
41705
41786
  use_input_default((input, key) => {
41706
41787
  if (key.upArrow) {
41707
41788
  setCursor((c) => Math.max(0, c - 1));
@@ -41755,11 +41836,11 @@ var App_exports = {};
41755
41836
  __export(App_exports, {
41756
41837
  App: () => App2
41757
41838
  });
41758
- var import_react31, import_jsx_runtime12, App2;
41839
+ var import_react32, import_jsx_runtime12, App2;
41759
41840
  var init_App2 = __esm({
41760
41841
  async "src/ui/App.tsx"() {
41761
41842
  "use strict";
41762
- import_react31 = __toESM(require_react());
41843
+ import_react32 = __toESM(require_react());
41763
41844
  await init_build2();
41764
41845
  init_useScan();
41765
41846
  await init_Spinner();
@@ -41767,11 +41848,29 @@ var init_App2 = __esm({
41767
41848
  await init_InteractiveResultsView();
41768
41849
  await init_ErrorView();
41769
41850
  await init_ProjectSelector();
41851
+ await init_useTerminalSize();
41770
41852
  import_jsx_runtime12 = __toESM(require_jsx_runtime());
41771
41853
  App2 = ({ config }) => {
41772
41854
  const { state, scanSelectedProjects, restartSelection } = useScan(config);
41773
41855
  const { exit } = use_app_default();
41774
- const handleResultsExit = (0, import_react31.useCallback)(() => {
41856
+ const { rows: termRows } = useTerminalSize();
41857
+ const prevPhaseRef = (0, import_react32.useRef)(state.phase);
41858
+ (0, import_react32.useEffect)(() => {
41859
+ if (!process.stdout.isTTY) return;
41860
+ process.stdout.write("\x1B[?1049h");
41861
+ process.stdout.write("\x1B[2J\x1B[H");
41862
+ return () => {
41863
+ process.stdout.write("\x1B[?1049l");
41864
+ process.stdout.write("\x1B[?25h");
41865
+ };
41866
+ }, []);
41867
+ (0, import_react32.useEffect)(() => {
41868
+ if (prevPhaseRef.current !== state.phase && process.stdout.isTTY) {
41869
+ process.stdout.write("\x1B[2J\x1B[H");
41870
+ }
41871
+ prevPhaseRef.current = state.phase;
41872
+ }, [state.phase]);
41873
+ const handleResultsExit = (0, import_react32.useCallback)(() => {
41775
41874
  if (state.phase === "results") {
41776
41875
  const { result } = state;
41777
41876
  if (result.action === "block" && config.mode === "block") {
@@ -41784,7 +41883,7 @@ var init_App2 = __esm({
41784
41883
  }
41785
41884
  exit();
41786
41885
  }, [state, config, exit]);
41787
- (0, import_react31.useEffect)(() => {
41886
+ (0, import_react32.useEffect)(() => {
41788
41887
  if (state.phase === "empty") {
41789
41888
  process.exitCode = 0;
41790
41889
  const timer = setTimeout(() => exit(), 0);
@@ -41796,55 +41895,58 @@ var init_App2 = __esm({
41796
41895
  return () => clearTimeout(timer);
41797
41896
  }
41798
41897
  }, [state, config, exit]);
41799
- switch (state.phase) {
41800
- case "discovering":
41801
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Spinner2, { label: "Searching for dependencies..." });
41802
- case "selecting":
41803
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
41804
- ProjectSelector,
41805
- {
41806
- projects: state.projects,
41807
- onConfirm: scanSelectedProjects,
41808
- onCancel: () => {
41809
- process.exitCode = 0;
41810
- exit();
41898
+ const content = (() => {
41899
+ switch (state.phase) {
41900
+ case "discovering":
41901
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Spinner2, { label: "Searching for dependencies..." });
41902
+ case "selecting":
41903
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
41904
+ ProjectSelector,
41905
+ {
41906
+ projects: state.projects,
41907
+ onConfirm: scanSelectedProjects,
41908
+ onCancel: () => {
41909
+ process.exitCode = 0;
41910
+ exit();
41911
+ }
41811
41912
  }
41812
- }
41813
- );
41814
- case "scanning":
41815
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", children: [
41816
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
41817
- ProgressBar,
41913
+ );
41914
+ case "scanning":
41915
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", children: [
41916
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
41917
+ ProgressBar,
41918
+ {
41919
+ value: state.done,
41920
+ total: state.total,
41921
+ label: state.currentBatch.length > 0 ? state.currentBatch[state.currentBatch.length - 1] : void 0
41922
+ }
41923
+ ),
41924
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { dimColor: true, children: [
41925
+ "Scanning ",
41926
+ state.done,
41927
+ "/",
41928
+ state.total,
41929
+ " packages..."
41930
+ ] })
41931
+ ] });
41932
+ case "results":
41933
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
41934
+ InteractiveResultsView,
41818
41935
  {
41819
- value: state.done,
41820
- total: state.total,
41821
- label: state.currentBatch.length > 0 ? state.currentBatch[state.currentBatch.length - 1] : void 0
41936
+ result: state.result,
41937
+ config,
41938
+ durationMs: state.durationMs,
41939
+ onExit: handleResultsExit,
41940
+ onBack: restartSelection ?? void 0
41822
41941
  }
41823
- ),
41824
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { dimColor: true, children: [
41825
- "Scanning ",
41826
- state.done,
41827
- "/",
41828
- state.total,
41829
- " packages..."
41830
- ] })
41831
- ] });
41832
- case "results":
41833
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
41834
- InteractiveResultsView,
41835
- {
41836
- result: state.result,
41837
- config,
41838
- durationMs: state.durationMs,
41839
- onExit: handleResultsExit,
41840
- onBack: restartSelection ?? void 0
41841
- }
41842
- );
41843
- case "empty":
41844
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { dimColor: true, children: state.message });
41845
- case "error":
41846
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorView, { error: state.error });
41847
- }
41942
+ );
41943
+ case "empty":
41944
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { dimColor: true, children: state.message });
41945
+ case "error":
41946
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorView, { error: state.error });
41947
+ }
41948
+ })();
41949
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { flexDirection: "column", height: termRows, children: content });
41848
41950
  };
41849
41951
  }
41850
41952
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Supply chain security scanner for npm and Python dependencies — detects malicious packages, typosquatting, dependency confusion, and 26+ attack patterns",
5
5
  "bin": {
6
6
  "dependency-guardian": "dist/index.mjs",
@@ -12,6 +12,10 @@
12
12
  "license": "UNLICENSED",
13
13
  "author": "WestBayBerry",
14
14
  "homepage": "https://westbayberry.com",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/WestBayBerry/dependency-guardian-action.git"
18
+ },
15
19
  "keywords": [
16
20
  "security",
17
21
  "npm",