@westbayberry/dg 1.0.5 → 1.0.7

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 (3) hide show
  1. package/README.md +59 -44
  2. package/dist/index.mjs +316 -341
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @westbayberry/dg
2
2
 
3
- Supply chain security scanner for npm dependencies. Detects malicious packages, typosquatting, dependency confusion, and 20+ attack patterns before they reach production.
3
+ Supply chain security scanner for npm and Python dependencies. Detects malicious packages, typosquatting, dependency confusion, and 20+ attack patterns before they reach production.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,44 +8,42 @@ Supply chain security scanner for npm dependencies. Detects malicious packages,
8
8
  npm install -g @westbayberry/dg
9
9
  ```
10
10
 
11
- Or run without installing:
12
-
13
- ```bash
14
- npx @westbayberry/dg scan
15
- ```
16
-
17
11
  ## Quick Start
18
12
 
19
- 1. Get your API key at [westbayberry.com/dashboard](https://westbayberry.com/dashboard)
20
- 2. Run a scan:
21
-
22
13
  ```bash
23
- export DG_API_KEY=dg_live_your_key_here
14
+ dg login
24
15
  dg scan
25
16
  ```
26
17
 
27
- That's it. The CLI auto-detects changed packages by diffing your lockfile against the base branch.
18
+ The CLI auto-discovers projects in your directory tree npm lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml) and Python dependency files (requirements.txt, Pipfile.lock, poetry.lock). If multiple projects are found, you pick which ones to scan.
28
19
 
29
- ## Usage
20
+ ## Commands
30
21
 
31
22
  ```
32
- dg scan [options]
23
+ dg scan [options] Scan dependencies (auto-discovers npm + Python projects)
24
+ dg npm install <pkg> Scan packages before installing
25
+ dg login Authenticate with your WestBayBerry account
26
+ dg logout Remove saved credentials
27
+ dg hook install Install git pre-commit hook to scan lockfile changes
28
+ dg hook uninstall Remove the pre-commit hook
29
+ dg update Check for and install the latest version
30
+ dg wrap Show instructions to alias npm to dg
33
31
  ```
34
32
 
35
- ### Options
36
-
37
- | Flag | Env Var | Default | Description |
38
- |------|---------|---------|-------------|
39
- | `--api-key <key>` | `DG_API_KEY` | *required* | Your API key |
40
- | `--mode <mode>` | `DG_MODE` | `warn` | `block` / `warn` / `off` |
41
- | `--block-threshold <n>` | | `70` | Score threshold for blocking |
42
- | `--warn-threshold <n>` | | `60` | Score threshold for warnings |
43
- | `--max-packages <n>` | | `200` | Max packages per scan |
44
- | `--allowlist <pkgs>` | `DG_ALLOWLIST` | | Comma-separated packages to skip |
45
- | `--json` | | | Output JSON for CI parsing |
46
- | `--scan-all` | | | Scan all packages, not just changed |
47
- | `--base-lockfile <path>` | | | Explicit base lockfile for diff |
48
- | `--api-url <url>` | `DG_API_URL` | `https://api.westbayberry.com` | API endpoint |
33
+ ### Scan Options
34
+
35
+ | Flag | Default | Description |
36
+ |------|---------|-------------|
37
+ | `--mode <mode>` | `warn` | `block` / `warn` / `off` |
38
+ | `--block-threshold <n>` | `70` | Score threshold for blocking |
39
+ | `--warn-threshold <n>` | `60` | Score threshold for warnings |
40
+ | `--max-packages <n>` | `200` | Max packages per scan |
41
+ | `--allowlist <pkgs>` | | Comma-separated packages to skip |
42
+ | `--json` | | Output JSON for CI parsing |
43
+ | `--scan-all` | | Scan all packages, not just changed |
44
+ | `--base-lockfile <path>` | | Explicit base lockfile for diff |
45
+ | `--workspace <dir>` | | Scan a specific workspace subdirectory |
46
+ | `--debug` | | Show diagnostic output |
49
47
 
50
48
  ### Exit Codes
51
49
 
@@ -54,37 +52,54 @@ dg scan [options]
54
52
  | `0` | Pass | Continue |
55
53
  | `1` | Warning | Advisory — review recommended |
56
54
  | `2` | Block | Fail the pipeline |
55
+ | `3` | Error | Internal error |
56
+
57
+ ## CI Setup
57
58
 
58
- ## CI Examples
59
+ Authenticate first, then add the scan to your pipeline:
59
60
 
60
- ### GitHub Actions CI
61
+ ### GitHub Actions
61
62
 
62
63
  ```yaml
63
64
  - name: Scan dependencies
64
- run: npx @westbayberry/dg scan --mode block
65
- env:
66
- DG_API_KEY: ${{ secrets.DG_API_KEY }}
65
+ run: |
66
+ npx @westbayberry/dg login
67
+ npx @westbayberry/dg scan --mode block --json
67
68
  ```
68
69
 
69
- ### GitLab CI
70
+ ### Any CI
70
71
 
71
- ```yaml
72
- dependency-scan:
73
- script:
74
- - npx @westbayberry/dg scan --mode block
75
- variables:
76
- DG_API_KEY: $DG_API_KEY
72
+ ```bash
73
+ npx @westbayberry/dg login
74
+ npx @westbayberry/dg scan --mode block --json
77
75
  ```
78
76
 
79
- ### Any CI
77
+ ## Git Hook
78
+
79
+ Block commits that introduce risky dependencies:
80
80
 
81
81
  ```bash
82
- export DG_API_KEY="$DG_API_KEY"
83
- npx @westbayberry/dg scan --mode block --json
82
+ dg hook install
83
+ ```
84
+
85
+ This installs a pre-commit hook that runs `dg scan --mode block` whenever a lockfile change is staged. Use `dg hook uninstall` to remove it.
86
+
87
+ ## npm Wrapper
88
+
89
+ Scan packages before installing:
90
+
91
+ ```bash
92
+ dg npm install express lodash
93
+ ```
94
+
95
+ Use `--dg-force` to bypass a block. Or alias npm globally:
96
+
97
+ ```bash
98
+ echo 'alias npm="dg npm"' >> ~/.zshrc
84
99
  ```
85
100
 
86
101
  ## Links
87
102
 
88
- - [Dashboard & API Keys](https://westbayberry.com/dashboard)
103
+ - [Dashboard](https://westbayberry.com/dashboard)
89
104
  - [Documentation](https://westbayberry.com/docs)
90
105
  - [Pricing](https://westbayberry.com/pricing)
package/dist/index.mjs CHANGED
@@ -39,6 +39,194 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  mod
40
40
  ));
41
41
 
42
+ // src/config.ts
43
+ var config_exports = {};
44
+ __export(config_exports, {
45
+ USAGE: () => USAGE,
46
+ getVersion: () => getVersion,
47
+ parseConfig: () => parseConfig
48
+ });
49
+ import { parseArgs } from "node:util";
50
+ import { readFileSync, existsSync } from "node:fs";
51
+ import { join } from "node:path";
52
+ import { homedir } from "node:os";
53
+ function loadDgrc() {
54
+ const candidates = [
55
+ join(process.cwd(), ".dgrc.json"),
56
+ join(homedir(), ".dgrc.json")
57
+ ];
58
+ for (const filepath of candidates) {
59
+ if (existsSync(filepath)) {
60
+ try {
61
+ return JSON.parse(readFileSync(filepath, "utf-8"));
62
+ } catch {
63
+ process.stderr.write(`Warning: Failed to parse ${filepath}, ignoring.
64
+ `);
65
+ }
66
+ }
67
+ }
68
+ return {};
69
+ }
70
+ function getVersion() {
71
+ try {
72
+ const pkg = JSON.parse(
73
+ readFileSync(join(__dirname, "..", "package.json"), "utf-8")
74
+ );
75
+ return pkg.version ?? "1.0.0";
76
+ } catch {
77
+ return "1.0.0";
78
+ }
79
+ }
80
+ function parseConfig(argv) {
81
+ const { values, positionals } = parseArgs({
82
+ args: argv.slice(2),
83
+ options: {
84
+ "api-url": { type: "string" },
85
+ mode: { type: "string" },
86
+ "block-threshold": { type: "string" },
87
+ "warn-threshold": { type: "string" },
88
+ "max-packages": { type: "string" },
89
+ allowlist: { type: "string" },
90
+ json: { type: "boolean", default: false },
91
+ "scan-all": { type: "boolean", default: false },
92
+ "base-lockfile": { type: "string" },
93
+ workspace: { type: "string", short: "w" },
94
+ debug: { type: "boolean", default: false },
95
+ "no-config": { type: "boolean", default: false },
96
+ help: { type: "boolean", default: false },
97
+ version: { type: "boolean", default: false }
98
+ },
99
+ allowPositionals: true,
100
+ strict: false
101
+ });
102
+ if (values.help) {
103
+ process.stdout.write(USAGE);
104
+ process.exit(0);
105
+ }
106
+ if (values.version) {
107
+ process.stdout.write(`dependency-guardian v${getVersion()}
108
+ `);
109
+ process.exit(0);
110
+ }
111
+ const command = positionals[0] ?? "scan";
112
+ const noConfig = values["no-config"];
113
+ const dgrc = noConfig ? {} : loadDgrc();
114
+ const apiKey = dgrc.apiKey ?? "";
115
+ if (!apiKey) {
116
+ process.stderr.write(
117
+ "Error: Not logged in. Run `dg login` to authenticate.\n"
118
+ );
119
+ process.exit(1);
120
+ }
121
+ const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
122
+ if (!["block", "warn", "off"].includes(modeRaw)) {
123
+ process.stderr.write(
124
+ `Error: Invalid mode "${modeRaw}". Must be block, warn, or off.
125
+ `
126
+ );
127
+ process.exit(1);
128
+ }
129
+ const allowlistRaw = values.allowlist ?? process.env.DG_ALLOWLIST ?? "";
130
+ const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
131
+ const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
132
+ const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
133
+ const debug = values.debug || process.env.DG_DEBUG === "1";
134
+ if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
135
+ process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
136
+ process.exit(1);
137
+ }
138
+ if (isNaN(warnThreshold) || warnThreshold < 0 || warnThreshold > 100) {
139
+ process.stderr.write("Error: --warn-threshold must be a number between 0 and 100\n");
140
+ process.exit(1);
141
+ }
142
+ if (isNaN(maxPackages) || maxPackages < 1 || maxPackages > 1e4) {
143
+ process.stderr.write("Error: --max-packages must be a number between 1 and 10000\n");
144
+ process.exit(1);
145
+ }
146
+ return {
147
+ apiKey,
148
+ apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
149
+ mode: modeRaw,
150
+ blockThreshold,
151
+ warnThreshold,
152
+ maxPackages,
153
+ allowlist: allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? [],
154
+ json: values.json,
155
+ scanAll: values["scan-all"],
156
+ baseLockfile: values["base-lockfile"] ?? null,
157
+ workspace: values.workspace ?? process.env.DG_WORKSPACE ?? null,
158
+ command,
159
+ debug
160
+ };
161
+ }
162
+ var USAGE;
163
+ var init_config = __esm({
164
+ "src/config.ts"() {
165
+ "use strict";
166
+ USAGE = `
167
+ Dependency Guardian \u2014 Supply chain security scanner
168
+
169
+ Usage:
170
+ dependency-guardian scan [options]
171
+ dg scan [options]
172
+ dg npm install <pkg> [npm-flags]
173
+ dg wrap
174
+
175
+ Commands:
176
+ scan Scan dependencies (auto-discovers npm + Python projects)
177
+ npm Wrap npm commands \u2014 scans packages before installing
178
+ hook install Install git pre-commit hook to scan lockfile changes
179
+ hook uninstall Remove the pre-commit hook
180
+ update Check for and install the latest version
181
+ login Authenticate with your WestBayBerry account
182
+ logout Remove saved credentials
183
+ wrap Show instructions to alias npm to dg
184
+
185
+ Options:
186
+ --api-url <url> API base URL (default: https://api.westbayberry.com)
187
+ --mode <mode> block | warn | off (default: warn)
188
+ --block-threshold <n> Score threshold for blocking (default: 70)
189
+ --warn-threshold <n> Score threshold for warnings (default: 60)
190
+ --max-packages <n> Max packages per scan (default: 200)
191
+ --allowlist <pkgs> Comma-separated package names to skip
192
+ --json Output JSON for CI parsing
193
+ --scan-all Scan all packages, not just changed
194
+ --base-lockfile <path> Path to base lockfile for explicit diff
195
+ --workspace <dir> Scan a specific workspace subdirectory
196
+ --debug Show diagnostic output (discovery, batches, timing)
197
+ --no-config Skip loading .dgrc.json config file
198
+ --help Show this help message
199
+ --version Show version number
200
+
201
+ Config File:
202
+ Place a .dgrc.json in your project root or home directory.
203
+ Precedence: CLI flags > env vars > .dgrc.json > defaults
204
+
205
+ Environment Variables:
206
+ DG_API_URL API base URL
207
+ DG_MODE Mode (block/warn/off)
208
+ DG_ALLOWLIST Comma-separated allowlist
209
+ DG_DEBUG Enable debug output (set to 1)
210
+ DG_WORKSPACE Workspace subdirectory to scan
211
+
212
+ Exit Codes:
213
+ 0 pass \u2014 No risks detected
214
+ 1 warn \u2014 Risks detected (advisory)
215
+ 2 block \u2014 High-risk packages detected
216
+ 3 error \u2014 Internal error (API failure, config error)
217
+
218
+ Examples:
219
+ dg scan
220
+ dg scan --json
221
+ dg scan --scan-all --mode block
222
+ dg scan --base-lockfile ./main-lockfile.json
223
+ dg npm install express lodash
224
+ dg npm install @scope/pkg@^2.0.0
225
+ dg npm install risky-pkg --dg-force
226
+ `.trimStart();
227
+ }
228
+ });
229
+
42
230
  // src/npm-wrapper.ts
43
231
  import { spawn } from "node:child_process";
44
232
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
@@ -3106,11 +3294,11 @@ var require_react_development = __commonJS({
3106
3294
  }
3107
3295
  return dispatcher.useContext(Context);
3108
3296
  }
3109
- function useState6(initialState) {
3297
+ function useState5(initialState) {
3110
3298
  var dispatcher = resolveDispatcher();
3111
3299
  return dispatcher.useState(initialState);
3112
3300
  }
3113
- function useReducer4(reducer4, initialArg, init) {
3301
+ function useReducer5(reducer4, initialArg, init) {
3114
3302
  var dispatcher = resolveDispatcher();
3115
3303
  return dispatcher.useReducer(reducer4, initialArg, init);
3116
3304
  }
@@ -3118,7 +3306,7 @@ var require_react_development = __commonJS({
3118
3306
  var dispatcher = resolveDispatcher();
3119
3307
  return dispatcher.useRef(initialValue);
3120
3308
  }
3121
- function useEffect11(create2, deps) {
3309
+ function useEffect12(create2, deps) {
3122
3310
  var dispatcher = resolveDispatcher();
3123
3311
  return dispatcher.useEffect(create2, deps);
3124
3312
  }
@@ -3901,15 +4089,15 @@ var require_react_development = __commonJS({
3901
4089
  exports.useContext = useContext7;
3902
4090
  exports.useDebugValue = useDebugValue;
3903
4091
  exports.useDeferredValue = useDeferredValue;
3904
- exports.useEffect = useEffect11;
4092
+ exports.useEffect = useEffect12;
3905
4093
  exports.useId = useId;
3906
4094
  exports.useImperativeHandle = useImperativeHandle;
3907
4095
  exports.useInsertionEffect = useInsertionEffect;
3908
4096
  exports.useLayoutEffect = useLayoutEffect2;
3909
4097
  exports.useMemo = useMemo4;
3910
- exports.useReducer = useReducer4;
4098
+ exports.useReducer = useReducer5;
3911
4099
  exports.useRef = useRef6;
3912
- exports.useState = useState6;
4100
+ exports.useState = useState5;
3913
4101
  exports.useSyncExternalStore = useSyncExternalStore;
3914
4102
  exports.useTransition = useTransition;
3915
4103
  exports.version = ReactVersion;
@@ -4282,47 +4470,6 @@ var init_base = __esm({
4282
4470
  });
4283
4471
 
4284
4472
  // node_modules/ansi-escapes/index.js
4285
- var ansi_escapes_exports = {};
4286
- __export(ansi_escapes_exports, {
4287
- ConEmu: () => ConEmu,
4288
- beep: () => beep,
4289
- beginSynchronizedOutput: () => beginSynchronizedOutput,
4290
- clearScreen: () => clearScreen,
4291
- clearTerminal: () => clearTerminal,
4292
- clearViewport: () => clearViewport,
4293
- cursorBackward: () => cursorBackward,
4294
- cursorDown: () => cursorDown,
4295
- cursorForward: () => cursorForward,
4296
- cursorGetPosition: () => cursorGetPosition,
4297
- cursorHide: () => cursorHide,
4298
- cursorLeft: () => cursorLeft,
4299
- cursorMove: () => cursorMove,
4300
- cursorNextLine: () => cursorNextLine,
4301
- cursorPrevLine: () => cursorPrevLine,
4302
- cursorRestorePosition: () => cursorRestorePosition,
4303
- cursorSavePosition: () => cursorSavePosition,
4304
- cursorShow: () => cursorShow,
4305
- cursorTo: () => cursorTo,
4306
- cursorUp: () => cursorUp,
4307
- default: () => base_exports,
4308
- endSynchronizedOutput: () => endSynchronizedOutput,
4309
- enterAlternativeScreen: () => enterAlternativeScreen,
4310
- eraseDown: () => eraseDown,
4311
- eraseEndLine: () => eraseEndLine,
4312
- eraseLine: () => eraseLine,
4313
- eraseLines: () => eraseLines,
4314
- eraseScreen: () => eraseScreen,
4315
- eraseStartLine: () => eraseStartLine,
4316
- eraseUp: () => eraseUp,
4317
- exitAlternativeScreen: () => exitAlternativeScreen,
4318
- iTerm: () => iTerm,
4319
- image: () => image,
4320
- link: () => link,
4321
- scrollDown: () => scrollDown,
4322
- scrollUp: () => scrollUp,
4323
- setCwd: () => setCwd,
4324
- synchronizedOutput: () => synchronizedOutput
4325
- });
4326
4473
  var init_ansi_escapes = __esm({
4327
4474
  "node_modules/ansi-escapes/index.js"() {
4328
4475
  init_base();
@@ -35809,9 +35956,6 @@ var init_build2 = __esm({
35809
35956
  // src/auth.ts
35810
35957
  var auth_exports = {};
35811
35958
  __export(auth_exports, {
35812
- MAX_POLL_ATTEMPTS: () => MAX_POLL_ATTEMPTS,
35813
- POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
35814
- WEB_BASE: () => WEB_BASE,
35815
35959
  clearCredentials: () => clearCredentials,
35816
35960
  createAuthSession: () => createAuthSession,
35817
35961
  getStoredApiKey: () => getStoredApiKey,
@@ -35842,7 +35986,6 @@ async function createAuthSession() {
35842
35986
  const json = await res.json();
35843
35987
  return {
35844
35988
  sessionId: json.session_id,
35845
- userCode: json.user_code,
35846
35989
  verifyUrl: json.verify_url,
35847
35990
  expiresIn: json.expires_in
35848
35991
  };
@@ -35930,13 +36073,11 @@ function openBrowser(url) {
35930
36073
  exec2(cmd, () => {
35931
36074
  });
35932
36075
  }
35933
- var WEB_BASE, POLL_INTERVAL_MS, MAX_POLL_ATTEMPTS, CONFIG_FILE;
36076
+ var WEB_BASE, CONFIG_FILE;
35934
36077
  var init_auth = __esm({
35935
36078
  "src/auth.ts"() {
35936
36079
  "use strict";
35937
36080
  WEB_BASE = "https://westbayberry.com";
35938
- POLL_INTERVAL_MS = 2e3;
35939
- MAX_POLL_ATTEMPTS = 150;
35940
36081
  CONFIG_FILE = ".dgrc.json";
35941
36082
  }
35942
36083
  });
@@ -35947,7 +36088,7 @@ function reducer(_state, action) {
35947
36088
  case "ALREADY_LOGGED_IN":
35948
36089
  return { phase: "already_logged_in", apiKey: action.apiKey };
35949
36090
  case "SESSION_CREATED":
35950
- return { phase: "waiting", userCode: action.userCode, verifyUrl: action.verifyUrl };
36091
+ return { phase: "waiting", verifyUrl: action.verifyUrl };
35951
36092
  case "AUTH_COMPLETE":
35952
36093
  return { phase: "success", email: action.email };
35953
36094
  case "AUTH_EXPIRED":
@@ -35982,13 +36123,12 @@ function useLogin() {
35982
36123
  }
35983
36124
  dispatch({
35984
36125
  type: "SESSION_CREATED",
35985
- userCode: session.userCode,
35986
36126
  verifyUrl: session.verifyUrl
35987
36127
  });
35988
36128
  openBrowser(session.verifyUrl);
35989
- for (let i = 0; i < MAX_POLL_ATTEMPTS2; i++) {
36129
+ for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
35990
36130
  if (cancelled) return;
35991
- await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS2));
36131
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
35992
36132
  if (cancelled) return;
35993
36133
  try {
35994
36134
  const result = await pollAuthSession(session.sessionId);
@@ -36018,14 +36158,14 @@ function useLogin() {
36018
36158
  }, []);
36019
36159
  return state;
36020
36160
  }
36021
- var import_react22, POLL_INTERVAL_MS2, MAX_POLL_ATTEMPTS2;
36161
+ var import_react22, POLL_INTERVAL_MS, MAX_POLL_ATTEMPTS;
36022
36162
  var init_useLogin = __esm({
36023
36163
  "src/ui/hooks/useLogin.ts"() {
36024
36164
  "use strict";
36025
36165
  import_react22 = __toESM(require_react());
36026
36166
  init_auth();
36027
- POLL_INTERVAL_MS2 = 2e3;
36028
- MAX_POLL_ATTEMPTS2 = 150;
36167
+ POLL_INTERVAL_MS = 2e3;
36168
+ MAX_POLL_ATTEMPTS = 150;
36029
36169
  }
36030
36170
  });
36031
36171
 
@@ -38697,13 +38837,6 @@ var init_LoginApp = __esm({
38697
38837
  ] });
38698
38838
  case "waiting":
38699
38839
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
38700
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
38701
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { bold: true, children: "Your verification code:" }),
38702
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
38703
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { bold: true, color: "green", children: [
38704
- " ",
38705
- state.userCode
38706
- ] }),
38707
38840
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
38708
38841
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: "Opening browser to authenticate..." }),
38709
38842
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "If it didn't open, visit:" }),
@@ -38843,7 +38976,7 @@ async function callAnalyzeBatch(packages, config) {
38843
38976
  clearTimeout(timeoutId);
38844
38977
  if (response.status === 401) {
38845
38978
  throw new APIError(
38846
- "Invalid API key. Check your --api-key or DG_API_KEY value.\nGet your key at https://westbayberry.com/dashboard",
38979
+ "Invalid API key. Run `dg logout` then `dg login` to re-authenticate.",
38847
38980
  401,
38848
38981
  ""
38849
38982
  );
@@ -38923,7 +39056,7 @@ async function callPyPIBatch(packages, config) {
38923
39056
  clearTimeout(timeoutId);
38924
39057
  if (response.status === 401) {
38925
39058
  throw new APIError(
38926
- "Invalid API key. Check your --api-key or DG_API_KEY value.\nGet your key at https://westbayberry.com/dashboard",
39059
+ "Invalid API key. Run `dg logout` then `dg login` to re-authenticate.",
38927
39060
  401,
38928
39061
  ""
38929
39062
  );
@@ -39799,7 +39932,7 @@ async function runStaticLogin() {
39799
39932
  process.exit(1);
39800
39933
  }
39801
39934
  process.stderr.write(`
39802
- Your verification code: ${import_chalk4.default.bold.green(session.userCode)}
39935
+ Opening browser to authenticate...
39803
39936
  `);
39804
39937
  process.stderr.write(` Visit: ${import_chalk4.default.cyan(session.verifyUrl)}
39805
39938
 
@@ -40156,13 +40289,10 @@ function useNpmWrapper(npmArgs, config) {
40156
40289
  }
40157
40290
  } catch (error) {
40158
40291
  const message = error instanceof Error ? error.message : String(error);
40159
- const proceed = true;
40160
- dispatch({ type: "ERROR", message, proceed });
40161
- if (proceed) {
40162
- dispatch({ type: "INSTALLING" });
40163
- const code = await runNpm(parsedRef.current.rawArgs);
40164
- dispatch({ type: "DONE", exitCode: code });
40165
- }
40292
+ dispatch({ type: "ERROR", message, proceed: true });
40293
+ dispatch({ type: "INSTALLING" });
40294
+ const code = await runNpm(parsedRef.current.rawArgs);
40295
+ dispatch({ type: "DONE", exitCode: code });
40166
40296
  }
40167
40297
  })();
40168
40298
  }, [npmArgs, config]);
@@ -40468,7 +40598,7 @@ function getHint(error) {
40468
40598
  if (typeof statusCode !== "number") return null;
40469
40599
  switch (statusCode) {
40470
40600
  case 401:
40471
- return "Check your --api-key";
40601
+ return "Not authenticated. Run `dg login` to sign in.";
40472
40602
  case 429:
40473
40603
  return "Rate limit exceeded. Upgrade at westbayberry.com/pricing";
40474
40604
  case 504:
@@ -40759,7 +40889,10 @@ var init_discover = __esm({
40759
40889
  "coverage",
40760
40890
  ".cache",
40761
40891
  ".pytest_cache",
40762
- ".mypy_cache"
40892
+ ".mypy_cache",
40893
+ "validation-results",
40894
+ "test-fixtures",
40895
+ "fixtures"
40763
40896
  ]);
40764
40897
  NPM_LOCKFILES = ["package-lock.json", "yarn.lock", "pnpm-lock.yaml", "npm-shrinkwrap.json"];
40765
40898
  PYTHON_DEPFILES = ["requirements.txt", "Pipfile.lock", "poetry.lock"];
@@ -40772,6 +40905,8 @@ function reducer3(_state, action) {
40772
40905
  switch (action.type) {
40773
40906
  case "PROJECTS_FOUND":
40774
40907
  return { phase: "selecting", projects: action.projects };
40908
+ case "RESTART_SELECTION":
40909
+ return { phase: "selecting", projects: action.projects };
40775
40910
  case "DISCOVERY_COMPLETE":
40776
40911
  return { phase: "scanning", done: 0, total: action.packages.length, currentBatch: [] };
40777
40912
  case "DISCOVERY_EMPTY":
@@ -40787,6 +40922,7 @@ function reducer3(_state, action) {
40787
40922
  function useScan(config) {
40788
40923
  const [state, dispatch] = (0, import_react27.useReducer)(reducer3, { phase: "discovering" });
40789
40924
  const started = (0, import_react27.useRef)(false);
40925
+ const discoveredProjects = (0, import_react27.useRef)(null);
40790
40926
  (0, import_react27.useEffect)(() => {
40791
40927
  if (started.current) return;
40792
40928
  started.current = true;
@@ -40802,6 +40938,7 @@ function useScan(config) {
40802
40938
  runNpmScan(packages, discovery.skipped.length, config, dispatch);
40803
40939
  } catch {
40804
40940
  const projects = discoverProjects(process.cwd());
40941
+ discoveredProjects.current = projects.length > 1 ? projects : null;
40805
40942
  if (projects.length === 0) {
40806
40943
  dispatch({ type: "DISCOVERY_EMPTY", message: "No dependency files found." });
40807
40944
  return;
@@ -40818,7 +40955,15 @@ function useScan(config) {
40818
40955
  dispatch({ type: "DISCOVERY_COMPLETE", packages: [], skippedCount: 0 });
40819
40956
  scanProjects(projects, config, dispatch);
40820
40957
  }, [config]);
40821
- return { state, scanSelectedProjects };
40958
+ const restartSelection = (0, import_react27.useCallback)(() => {
40959
+ if (!discoveredProjects.current) return;
40960
+ dispatch({ type: "RESTART_SELECTION", projects: discoveredProjects.current });
40961
+ }, []);
40962
+ return {
40963
+ state,
40964
+ scanSelectedProjects,
40965
+ restartSelection: discoveredProjects.current ? restartSelection : null
40966
+ };
40822
40967
  }
40823
40968
  async function runNpmScan(packages, skippedCount, config, dispatch) {
40824
40969
  try {
@@ -41054,6 +41199,16 @@ function affectsLine(group) {
41054
41199
  if (names.length <= 5) return names.join(", ");
41055
41200
  return names.slice(0, 5).join(", ") + ` + ${names.length - 5} more`;
41056
41201
  }
41202
+ function viewReducer(_state, action) {
41203
+ switch (action.type) {
41204
+ case "MOVE":
41205
+ return { ..._state, cursor: action.cursor, viewport: action.viewport };
41206
+ case "EXPAND":
41207
+ return { ..._state, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
41208
+ case "MOVE_EXPAND":
41209
+ return { cursor: action.cursor, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
41210
+ }
41211
+ }
41057
41212
  var import_react29, import_chalk10, import_jsx_runtime10, SEVERITY_LABELS3, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, InteractiveResultsView, T, FindingsSummary, FindingsDetail;
41058
41213
  var init_InteractiveResultsView = __esm({
41059
41214
  async "src/ui/components/InteractiveResultsView.tsx"() {
@@ -41084,8 +41239,16 @@ var init_InteractiveResultsView = __esm({
41084
41239
  result,
41085
41240
  config,
41086
41241
  durationMs,
41087
- onExit
41242
+ onExit,
41243
+ onBack
41088
41244
  }) => {
41245
+ (0, import_react29.useEffect)(() => {
41246
+ if (!process.stdout.isTTY) return;
41247
+ process.stdout.write("\x1B[?1049h");
41248
+ return () => {
41249
+ process.stdout.write("\x1B[?1049l");
41250
+ };
41251
+ }, []);
41089
41252
  const flagged = (0, import_react29.useMemo)(
41090
41253
  () => result.packages.filter((p) => p.score > 0),
41091
41254
  [result.packages]
@@ -41096,45 +41259,41 @@ var init_InteractiveResultsView = __esm({
41096
41259
  );
41097
41260
  const total = result.packages.length;
41098
41261
  const groups = (0, import_react29.useMemo)(() => groupPackages3(flagged), [flagged]);
41099
- const [cursorIndex, setCursorIndex] = (0, import_react29.useState)(0);
41100
- const [expandLevel, setExpandLevel] = (0, import_react29.useState)(null);
41101
- const [expandedIndex, setExpandedIndex] = (0, import_react29.useState)(null);
41102
- const [viewportStart, setViewportStart] = (0, import_react29.useState)(0);
41103
- const cursorRef = (0, import_react29.useRef)(cursorIndex);
41104
- const expandLevelRef = (0, import_react29.useRef)(expandLevel);
41105
- const expandedIdxRef = (0, import_react29.useRef)(expandedIndex);
41106
- const viewportRef = (0, import_react29.useRef)(viewportStart);
41107
- cursorRef.current = cursorIndex;
41108
- expandLevelRef.current = expandLevel;
41109
- expandedIdxRef.current = expandedIndex;
41110
- viewportRef.current = viewportStart;
41262
+ const [view, dispatchView] = (0, import_react29.useReducer)(viewReducer, {
41263
+ cursor: 0,
41264
+ expandLevel: null,
41265
+ expandedIndex: null,
41266
+ viewport: 0
41267
+ });
41268
+ const viewRef = (0, import_react29.useRef)(view);
41269
+ viewRef.current = view;
41111
41270
  const { stdout } = use_stdout_default();
41112
41271
  const termCols = stdout?.columns ?? process.stdout.columns ?? 80;
41113
41272
  const termRows = stdout?.rows ?? process.stdout.rows ?? 24;
41114
41273
  const availableRows = Math.max(5, termRows - FIXED_CHROME);
41115
41274
  const innerWidth = Math.max(40, termCols - 6);
41116
41275
  const getLevel = (idx) => {
41117
- return expandedIndex === idx ? expandLevel : null;
41276
+ return view.expandedIndex === idx ? view.expandLevel : null;
41118
41277
  };
41119
41278
  const expandTargetHeight = (0, import_react29.useMemo)(() => {
41120
- if (expandedIndex === null || expandLevel === null) return 0;
41121
- const group = groups[expandedIndex];
41279
+ if (view.expandedIndex === null || view.expandLevel === null) return 0;
41280
+ const group = groups[view.expandedIndex];
41122
41281
  if (!group) return 0;
41123
- if (expandLevel === "summary") return findingsSummaryHeight(group);
41282
+ if (view.expandLevel === "summary") return findingsSummaryHeight(group);
41124
41283
  return findingsDetailHeight(group, result.safeVersions);
41125
- }, [expandedIndex, expandLevel, groups, result.safeVersions]);
41284
+ }, [view.expandedIndex, view.expandLevel, groups, result.safeVersions]);
41126
41285
  const { visibleLines: animVisibleLines } = useExpandAnimation(
41127
41286
  expandTargetHeight,
41128
- expandedIndex !== null
41287
+ view.expandedIndex !== null
41129
41288
  );
41130
41289
  const animatedGroupHeight = (group, level, idx) => {
41131
41290
  if (level === null) return 1;
41132
- if (idx === expandedIndex) return 1 + animVisibleLines;
41291
+ if (idx === view.expandedIndex) return 1 + animVisibleLines;
41133
41292
  return groupRowHeight(group, level, result.safeVersions);
41134
41293
  };
41135
41294
  const visibleEnd = (0, import_react29.useMemo)(() => {
41136
41295
  let consumed = 0;
41137
- let end = viewportStart;
41296
+ let end = view.viewport;
41138
41297
  while (end < groups.length) {
41139
41298
  const level = getLevel(end);
41140
41299
  const h = animatedGroupHeight(groups[end], level, end);
@@ -41142,9 +41301,9 @@ var init_InteractiveResultsView = __esm({
41142
41301
  consumed += h;
41143
41302
  end++;
41144
41303
  }
41145
- if (end === viewportStart && groups.length > 0) end = viewportStart + 1;
41304
+ if (end === view.viewport && groups.length > 0) end = view.viewport + 1;
41146
41305
  return end;
41147
- }, [viewportStart, groups, expandedIndex, expandLevel, animVisibleLines, availableRows, result.safeVersions]);
41306
+ }, [view.viewport, groups, view.expandedIndex, view.expandLevel, animVisibleLines, availableRows, result.safeVersions]);
41148
41307
  const adjustViewport = (cursor, expIdx, expLvl, currentStart) => {
41149
41308
  if (cursor < currentStart) return cursor;
41150
41309
  const getLvl = (i) => expIdx === i ? expLvl : null;
@@ -41169,43 +41328,39 @@ var init_InteractiveResultsView = __esm({
41169
41328
  if (input === "q" || key.return) onExit();
41170
41329
  return;
41171
41330
  }
41172
- const cursor = cursorRef.current;
41173
- const expLvl = expandLevelRef.current;
41174
- const expIdx = expandedIdxRef.current;
41175
- const vpStart = viewportRef.current;
41331
+ const { cursor, expandLevel: expLvl, expandedIndex: expIdx, viewport: vpStart } = viewRef.current;
41176
41332
  if (key.upArrow) {
41177
41333
  const next = Math.max(0, cursor - 1);
41178
41334
  const newVp = adjustViewport(next, expIdx, expLvl, vpStart < next ? vpStart : next);
41179
- setCursorIndex(next);
41180
- setViewportStart(newVp);
41335
+ dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
41181
41336
  } else if (key.downArrow) {
41182
41337
  const next = Math.min(groups.length - 1, cursor + 1);
41183
41338
  const newVp = adjustViewport(next, expIdx, expLvl, vpStart);
41184
- setCursorIndex(next);
41185
- setViewportStart(newVp);
41339
+ dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
41186
41340
  } else if (key.return) {
41187
41341
  let newExpIdx;
41188
41342
  let newExpLvl;
41189
- if (expIdx !== cursor) {
41190
- newExpIdx = cursor;
41191
- newExpLvl = "summary";
41192
- } else if (expLvl === "summary") {
41193
- newExpIdx = cursor;
41194
- newExpLvl = "detail";
41195
- } else {
41343
+ if (expIdx === cursor && expLvl !== null) {
41196
41344
  newExpIdx = null;
41197
41345
  newExpLvl = null;
41346
+ } else {
41347
+ newExpIdx = cursor;
41348
+ newExpLvl = "summary";
41198
41349
  }
41199
- setExpandedIndex(newExpIdx);
41200
- setExpandLevel(newExpLvl);
41201
41350
  const newVp = adjustViewport(cursor, newExpIdx, newExpLvl, vpStart);
41202
- setViewportStart(newVp);
41351
+ dispatchView({ type: "EXPAND", expandedIndex: newExpIdx, expandLevel: newExpLvl, viewport: newVp });
41352
+ } else if (input === "e") {
41353
+ if (expIdx === cursor && expLvl === "detail") return;
41354
+ const newVp = adjustViewport(cursor, cursor, "detail", vpStart);
41355
+ dispatchView({ type: "EXPAND", expandedIndex: cursor, expandLevel: "detail", viewport: newVp });
41356
+ } else if (input === "b" && onBack) {
41357
+ onBack();
41203
41358
  } else if (input === "q") {
41204
41359
  onExit();
41205
41360
  }
41206
41361
  });
41207
- const visibleGroups = groups.slice(viewportStart, visibleEnd);
41208
- const aboveCount = viewportStart;
41362
+ const visibleGroups = groups.slice(view.viewport, visibleEnd);
41363
+ const aboveCount = view.viewport;
41209
41364
  const belowCount = groups.length - visibleEnd;
41210
41365
  const nameCol = Math.max(20, innerWidth - 22);
41211
41366
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
@@ -41244,8 +41399,8 @@ var init_InteractiveResultsView = __esm({
41244
41399
  " more above"
41245
41400
  ] }),
41246
41401
  visibleGroups.map((group, visIdx) => {
41247
- const globalIdx = viewportStart + visIdx;
41248
- const isCursor = globalIdx === cursorIndex;
41402
+ const globalIdx = view.viewport + visIdx;
41403
+ const isCursor = globalIdx === view.cursor;
41249
41404
  const level = getLevel(globalIdx);
41250
41405
  const rep = group.packages[0];
41251
41406
  const { label, color } = actionBadge4(rep.score, config);
@@ -41264,7 +41419,7 @@ var init_InteractiveResultsView = __esm({
41264
41419
  {
41265
41420
  group,
41266
41421
  maxWidth: innerWidth - 8,
41267
- maxLines: globalIdx === expandedIndex ? animVisibleLines : void 0
41422
+ maxLines: globalIdx === view.expandedIndex ? animVisibleLines : void 0
41268
41423
  }
41269
41424
  ),
41270
41425
  level === "detail" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
@@ -41273,7 +41428,7 @@ var init_InteractiveResultsView = __esm({
41273
41428
  group,
41274
41429
  safeVersion: result.safeVersions[rep.name],
41275
41430
  maxWidth: innerWidth - 8,
41276
- maxLines: globalIdx === expandedIndex ? animVisibleLines : void 0
41431
+ maxLines: globalIdx === view.expandedIndex ? animVisibleLines : void 0
41277
41432
  }
41278
41433
  )
41279
41434
  ] }, group.key);
@@ -41320,13 +41475,23 @@ var init_InteractiveResultsView = __esm({
41320
41475
  " navigate",
41321
41476
  " ",
41322
41477
  import_chalk10.default.cyan("\u23CE"),
41323
- " expand/collapse",
41478
+ " toggle",
41479
+ " ",
41480
+ import_chalk10.default.cyan("e"),
41481
+ " detail",
41324
41482
  " ",
41483
+ onBack && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
41484
+ import_chalk10.default.cyan("b"),
41485
+ " back",
41486
+ " "
41487
+ ] }),
41325
41488
  import_chalk10.default.cyan("q"),
41326
41489
  " quit"
41327
41490
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
41328
41491
  "Press ",
41329
41492
  import_chalk10.default.cyan("q"),
41493
+ " or ",
41494
+ import_chalk10.default.cyan("Enter"),
41330
41495
  " to exit"
41331
41496
  ] })
41332
41497
  ] })
@@ -41334,13 +41499,9 @@ var init_InteractiveResultsView = __esm({
41334
41499
  };
41335
41500
  T = {
41336
41501
  branch: import_chalk10.default.dim("\u251C\u2500\u2500"),
41337
- // ├──
41338
41502
  last: import_chalk10.default.dim("\u2514\u2500\u2500"),
41339
- // └──
41340
41503
  pipe: import_chalk10.default.dim("\u2502"),
41341
- // │
41342
41504
  blank: " "
41343
- //
41344
41505
  };
41345
41506
  FindingsSummary = ({ group, maxWidth, maxLines }) => {
41346
41507
  const rep = group.packages[0];
@@ -41506,12 +41667,7 @@ var init_ProjectSelector = __esm({
41506
41667
  return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: isCursor, inverse: isCursor, children: line }, i);
41507
41668
  }),
41508
41669
  /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
41509
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { dimColor: true, children: [
41510
- selected.size,
41511
- " of ",
41512
- projects.length,
41513
- " selected"
41514
- ] })
41670
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { dimColor: true, children: selected.size === 0 ? "Select at least 1 project to scan" : `${selected.size} of ${projects.length} selected` })
41515
41671
  ] });
41516
41672
  };
41517
41673
  }
@@ -41536,7 +41692,7 @@ var init_App2 = __esm({
41536
41692
  await init_ProjectSelector();
41537
41693
  import_jsx_runtime12 = __toESM(require_jsx_runtime());
41538
41694
  App2 = ({ config }) => {
41539
- const { state, scanSelectedProjects } = useScan(config);
41695
+ const { state, scanSelectedProjects, restartSelection } = useScan(config);
41540
41696
  const { exit } = use_app_default();
41541
41697
  const handleResultsExit = (0, import_react31.useCallback)(() => {
41542
41698
  if (state.phase === "results") {
@@ -41603,7 +41759,8 @@ var init_App2 = __esm({
41603
41759
  result: state.result,
41604
41760
  config,
41605
41761
  durationMs: state.durationMs,
41606
- onExit: handleResultsExit
41762
+ onExit: handleResultsExit,
41763
+ onBack: restartSelection ?? void 0
41607
41764
  }
41608
41765
  );
41609
41766
  case "empty":
@@ -41615,191 +41772,8 @@ var init_App2 = __esm({
41615
41772
  }
41616
41773
  });
41617
41774
 
41618
- // src/config.ts
41619
- import { parseArgs } from "node:util";
41620
- import { readFileSync, existsSync } from "node:fs";
41621
- import { join } from "node:path";
41622
- import { homedir } from "node:os";
41623
- function loadDgrc() {
41624
- const candidates = [
41625
- join(process.cwd(), ".dgrc.json"),
41626
- join(homedir(), ".dgrc.json")
41627
- ];
41628
- for (const filepath of candidates) {
41629
- if (existsSync(filepath)) {
41630
- try {
41631
- return JSON.parse(readFileSync(filepath, "utf-8"));
41632
- } catch {
41633
- process.stderr.write(`Warning: Failed to parse ${filepath}, ignoring.
41634
- `);
41635
- }
41636
- }
41637
- }
41638
- return {};
41639
- }
41640
- var USAGE = `
41641
- Dependency Guardian \u2014 Supply chain security scanner
41642
-
41643
- Usage:
41644
- dependency-guardian scan [options]
41645
- dg scan [options]
41646
- dg npm install <pkg> [npm-flags]
41647
- dg wrap
41648
-
41649
- Commands:
41650
- scan Scan dependencies (auto-discovers npm + Python projects)
41651
- npm Wrap npm commands \u2014 scans packages before installing
41652
- hook install Install git pre-commit hook to scan lockfile changes
41653
- hook uninstall Remove the pre-commit hook
41654
- update Check for and install the latest version
41655
- login Authenticate with your WestBayBerry account
41656
- logout Remove saved credentials
41657
- wrap Show instructions to alias npm to dg
41658
-
41659
- Options:
41660
- --api-key <key> API key (or set DG_API_KEY env var)
41661
- --api-url <url> API base URL (default: https://api.westbayberry.com)
41662
- --mode <mode> block | warn | off (default: warn)
41663
- --block-threshold <n> Score threshold for blocking (default: 70)
41664
- --warn-threshold <n> Score threshold for warnings (default: 60)
41665
- --max-packages <n> Max packages per scan (default: 200)
41666
- --allowlist <pkgs> Comma-separated package names to skip
41667
- --json Output JSON for CI parsing
41668
- --scan-all Scan all packages, not just changed
41669
- --base-lockfile <path> Path to base lockfile for explicit diff
41670
- --workspace <dir> Scan a specific workspace subdirectory
41671
- --debug Show diagnostic output (discovery, batches, timing)
41672
- --no-config Skip loading .dgrc.json config file
41673
- --help Show this help message
41674
- --version Show version number
41675
-
41676
- Config File:
41677
- Place a .dgrc.json in your project root or home directory.
41678
- Precedence: CLI flags > env vars > .dgrc.json > defaults
41679
-
41680
- Environment Variables:
41681
- DG_API_KEY API key
41682
- DG_API_URL API base URL
41683
- DG_MODE Mode (block/warn/off)
41684
- DG_ALLOWLIST Comma-separated allowlist
41685
- DG_DEBUG Enable debug output (set to 1)
41686
- DG_WORKSPACE Workspace subdirectory to scan
41687
-
41688
- Exit Codes:
41689
- 0 pass \u2014 No risks detected
41690
- 1 warn \u2014 Risks detected (advisory)
41691
- 2 block \u2014 High-risk packages detected
41692
- 3 error \u2014 Internal error (API failure, config error)
41693
-
41694
- Examples:
41695
- DG_API_KEY=dg_live_xxx dg scan
41696
- dg scan --api-key dg_live_xxx --json
41697
- dg scan --scan-all --mode block
41698
- dg scan --base-lockfile ./main-lockfile.json
41699
- dg npm install express lodash
41700
- dg npm install @scope/pkg@^2.0.0
41701
- dg npm install risky-pkg --dg-force
41702
- `.trimStart();
41703
- function getVersion() {
41704
- try {
41705
- const pkg = JSON.parse(
41706
- readFileSync(join(__dirname, "..", "package.json"), "utf-8")
41707
- );
41708
- return pkg.version ?? "1.0.0";
41709
- } catch {
41710
- return "1.0.0";
41711
- }
41712
- }
41713
- function parseConfig(argv) {
41714
- const { values, positionals } = parseArgs({
41715
- args: argv.slice(2),
41716
- options: {
41717
- "api-key": { type: "string" },
41718
- "api-url": { type: "string" },
41719
- mode: { type: "string" },
41720
- "block-threshold": { type: "string" },
41721
- "warn-threshold": { type: "string" },
41722
- "max-packages": { type: "string" },
41723
- allowlist: { type: "string" },
41724
- json: { type: "boolean", default: false },
41725
- "scan-all": { type: "boolean", default: false },
41726
- "base-lockfile": { type: "string" },
41727
- workspace: { type: "string", short: "w" },
41728
- debug: { type: "boolean", default: false },
41729
- "no-config": { type: "boolean", default: false },
41730
- help: { type: "boolean", default: false },
41731
- version: { type: "boolean", default: false }
41732
- },
41733
- allowPositionals: true,
41734
- strict: false
41735
- });
41736
- if (values.help) {
41737
- process.stdout.write(USAGE);
41738
- process.exit(0);
41739
- }
41740
- if (values.version) {
41741
- process.stdout.write(`dependency-guardian v${getVersion()}
41742
- `);
41743
- process.exit(0);
41744
- }
41745
- const command = positionals[0] ?? "scan";
41746
- const noConfig = values["no-config"];
41747
- const dgrc = noConfig ? {} : loadDgrc();
41748
- if (values["api-key"]) {
41749
- process.stderr.write(
41750
- "Warning: --api-key is deprecated (visible in process list). Use DG_API_KEY env var instead.\n"
41751
- );
41752
- }
41753
- const apiKey = values["api-key"] ?? process.env.DG_API_KEY ?? dgrc.apiKey ?? "";
41754
- if (!apiKey) {
41755
- process.stderr.write(
41756
- "Error: API key required. Run `dg login` to authenticate,\nor set the DG_API_KEY environment variable.\nGet your key at https://westbayberry.com/dashboard\n"
41757
- );
41758
- process.exit(1);
41759
- }
41760
- const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
41761
- if (!["block", "warn", "off"].includes(modeRaw)) {
41762
- process.stderr.write(
41763
- `Error: Invalid mode "${modeRaw}". Must be block, warn, or off.
41764
- `
41765
- );
41766
- process.exit(1);
41767
- }
41768
- const allowlistRaw = values.allowlist ?? process.env.DG_ALLOWLIST ?? "";
41769
- const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
41770
- const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
41771
- const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
41772
- const debug = values.debug || process.env.DG_DEBUG === "1";
41773
- if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
41774
- process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
41775
- process.exit(1);
41776
- }
41777
- if (isNaN(warnThreshold) || warnThreshold < 0 || warnThreshold > 100) {
41778
- process.stderr.write("Error: --warn-threshold must be a number between 0 and 100\n");
41779
- process.exit(1);
41780
- }
41781
- if (isNaN(maxPackages) || maxPackages < 1 || maxPackages > 1e4) {
41782
- process.stderr.write("Error: --max-packages must be a number between 1 and 10000\n");
41783
- process.exit(1);
41784
- }
41785
- return {
41786
- apiKey,
41787
- apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
41788
- mode: modeRaw,
41789
- blockThreshold,
41790
- warnThreshold,
41791
- maxPackages,
41792
- allowlist: allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? [],
41793
- json: values.json,
41794
- scanAll: values["scan-all"],
41795
- baseLockfile: values["base-lockfile"] ?? null,
41796
- workspace: values.workspace ?? process.env.DG_WORKSPACE ?? null,
41797
- command,
41798
- debug
41799
- };
41800
- }
41801
-
41802
41775
  // src/bin.ts
41776
+ init_config();
41803
41777
  init_npm_wrapper();
41804
41778
 
41805
41779
  // src/update-check.ts
@@ -41861,8 +41835,7 @@ function spawnBackgroundUpdate(version) {
41861
41835
  try {
41862
41836
  const child = spawn2("npm", ["install", "-g", `${PKG_NAME}@${version}`], {
41863
41837
  detached: true,
41864
- stdio: "ignore",
41865
- shell: true
41838
+ stdio: "ignore"
41866
41839
  });
41867
41840
  child.unref();
41868
41841
  } catch {
@@ -41924,10 +41897,20 @@ async function runUpdate(currentVersion) {
41924
41897
  }
41925
41898
 
41926
41899
  // src/bin.ts
41927
- var CLI_VERSION = "1.0.4";
41900
+ var CLI_VERSION = getVersion();
41928
41901
  var isInteractive = process.stdout.isTTY === true && !process.env.CI && !process.env.NO_COLOR;
41929
41902
  async function main() {
41930
41903
  const rawCommand = process.argv[2];
41904
+ if (!rawCommand || rawCommand === "--help" || rawCommand === "-h") {
41905
+ const { USAGE: USAGE3 } = await Promise.resolve().then(() => (init_config(), config_exports));
41906
+ process.stdout.write(USAGE3);
41907
+ return;
41908
+ }
41909
+ if (rawCommand === "--version" || rawCommand === "-v") {
41910
+ process.stdout.write(`dependency-guardian v${CLI_VERSION}
41911
+ `);
41912
+ return;
41913
+ }
41931
41914
  if (rawCommand === "wrap") {
41932
41915
  handleWrapCommand();
41933
41916
  return;
@@ -41958,7 +41941,7 @@ async function main() {
41958
41941
  const { clearCredentials: clearCredentials2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
41959
41942
  clearCredentials2();
41960
41943
  const chalk9 = (await Promise.resolve().then(() => __toESM(require_source()))).default;
41961
- process.stderr.write(chalk9.green(" Logged out.") + chalk9.dim(" API key removed from ~/.dgrc.json\n"));
41944
+ process.stderr.write(chalk9.green(" Logged out.\n"));
41962
41945
  return;
41963
41946
  }
41964
41947
  const config = parseConfig(process.argv);
@@ -41993,18 +41976,10 @@ async function main() {
41993
41976
  process.exit(0);
41994
41977
  }
41995
41978
  const { App: App3 } = await init_App2().then(() => App_exports);
41996
- const { enterAlternativeScreen: enterAlternativeScreen2, exitAlternativeScreen: exitAlternativeScreen2 } = await Promise.resolve().then(() => (init_ansi_escapes(), ansi_escapes_exports));
41997
- const MOUSE_ON = "\x1B[?1000h\x1B[?1003h\x1B[?1006h";
41998
- const MOUSE_OFF = "\x1B[?1006l\x1B[?1003l\x1B[?1000l";
41999
- process.stdout.write(enterAlternativeScreen2 + MOUSE_ON);
42000
- try {
42001
- const { waitUntilExit } = render2(
42002
- React16.createElement(App3, { config })
42003
- );
42004
- await waitUntilExit();
42005
- } finally {
42006
- process.stdout.write(MOUSE_OFF + exitAlternativeScreen2);
42007
- }
41979
+ const { waitUntilExit } = render2(
41980
+ React16.createElement(App3, { config })
41981
+ );
41982
+ await waitUntilExit();
42008
41983
  }
42009
41984
  const updateMsg = await updatePromise;
42010
41985
  if (updateMsg) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "1.0.5",
3
+ "version": "1.0.7",
4
4
  "description": "Supply chain security scanner — scan npm dependencies in any CI or terminal",
5
5
  "bin": {
6
6
  "dependency-guardian": "dist/index.mjs",