@westbayberry/dg 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -44
- package/dist/index.mjs +357 -298
- 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
|
-
|
|
14
|
+
dg login
|
|
24
15
|
dg scan
|
|
25
16
|
```
|
|
26
17
|
|
|
27
|
-
|
|
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
|
-
##
|
|
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 |
|
|
38
|
-
|
|
39
|
-
| `--
|
|
40
|
-
| `--
|
|
41
|
-
| `--
|
|
42
|
-
| `--
|
|
43
|
-
| `--
|
|
44
|
-
| `--
|
|
45
|
-
| `--
|
|
46
|
-
| `--
|
|
47
|
-
| `--
|
|
48
|
-
| `--
|
|
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
|
-
|
|
59
|
+
Authenticate first, then add the scan to your pipeline:
|
|
59
60
|
|
|
60
|
-
### GitHub Actions
|
|
61
|
+
### GitHub Actions
|
|
61
62
|
|
|
62
63
|
```yaml
|
|
63
64
|
- name: Scan dependencies
|
|
64
|
-
run:
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
run: |
|
|
66
|
+
npx @westbayberry/dg login
|
|
67
|
+
npx @westbayberry/dg scan --mode block --json
|
|
67
68
|
```
|
|
68
69
|
|
|
69
|
-
###
|
|
70
|
+
### Any CI
|
|
70
71
|
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
+
## Git Hook
|
|
78
|
+
|
|
79
|
+
Block commits that introduce risky dependencies:
|
|
80
80
|
|
|
81
81
|
```bash
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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
|
|
3297
|
+
function useState5(initialState) {
|
|
3110
3298
|
var dispatcher = resolveDispatcher();
|
|
3111
3299
|
return dispatcher.useState(initialState);
|
|
3112
3300
|
}
|
|
3113
|
-
function
|
|
3301
|
+
function useReducer5(reducer4, initialArg, init) {
|
|
3114
3302
|
var dispatcher = resolveDispatcher();
|
|
3115
3303
|
return dispatcher.useReducer(reducer4, initialArg, init);
|
|
3116
3304
|
}
|
|
@@ -3130,7 +3318,7 @@ var require_react_development = __commonJS({
|
|
|
3130
3318
|
var dispatcher = resolveDispatcher();
|
|
3131
3319
|
return dispatcher.useLayoutEffect(create2, deps);
|
|
3132
3320
|
}
|
|
3133
|
-
function
|
|
3321
|
+
function useCallback5(callback, deps) {
|
|
3134
3322
|
var dispatcher = resolveDispatcher();
|
|
3135
3323
|
return dispatcher.useCallback(callback, deps);
|
|
3136
3324
|
}
|
|
@@ -3897,7 +4085,7 @@ var require_react_development = __commonJS({
|
|
|
3897
4085
|
exports.memo = memo;
|
|
3898
4086
|
exports.startTransition = startTransition;
|
|
3899
4087
|
exports.unstable_act = act;
|
|
3900
|
-
exports.useCallback =
|
|
4088
|
+
exports.useCallback = useCallback5;
|
|
3901
4089
|
exports.useContext = useContext7;
|
|
3902
4090
|
exports.useDebugValue = useDebugValue;
|
|
3903
4091
|
exports.useDeferredValue = useDeferredValue;
|
|
@@ -3907,9 +4095,9 @@ var require_react_development = __commonJS({
|
|
|
3907
4095
|
exports.useInsertionEffect = useInsertionEffect;
|
|
3908
4096
|
exports.useLayoutEffect = useLayoutEffect2;
|
|
3909
4097
|
exports.useMemo = useMemo4;
|
|
3910
|
-
exports.useReducer =
|
|
4098
|
+
exports.useReducer = useReducer5;
|
|
3911
4099
|
exports.useRef = useRef6;
|
|
3912
|
-
exports.useState =
|
|
4100
|
+
exports.useState = useState5;
|
|
3913
4101
|
exports.useSyncExternalStore = useSyncExternalStore;
|
|
3914
4102
|
exports.useTransition = useTransition;
|
|
3915
4103
|
exports.version = ReactVersion;
|
|
@@ -35768,9 +35956,6 @@ var init_build2 = __esm({
|
|
|
35768
35956
|
// src/auth.ts
|
|
35769
35957
|
var auth_exports = {};
|
|
35770
35958
|
__export(auth_exports, {
|
|
35771
|
-
MAX_POLL_ATTEMPTS: () => MAX_POLL_ATTEMPTS,
|
|
35772
|
-
POLL_INTERVAL_MS: () => POLL_INTERVAL_MS,
|
|
35773
|
-
WEB_BASE: () => WEB_BASE,
|
|
35774
35959
|
clearCredentials: () => clearCredentials,
|
|
35775
35960
|
createAuthSession: () => createAuthSession,
|
|
35776
35961
|
getStoredApiKey: () => getStoredApiKey,
|
|
@@ -35801,7 +35986,6 @@ async function createAuthSession() {
|
|
|
35801
35986
|
const json = await res.json();
|
|
35802
35987
|
return {
|
|
35803
35988
|
sessionId: json.session_id,
|
|
35804
|
-
userCode: json.user_code,
|
|
35805
35989
|
verifyUrl: json.verify_url,
|
|
35806
35990
|
expiresIn: json.expires_in
|
|
35807
35991
|
};
|
|
@@ -35889,24 +36073,25 @@ function openBrowser(url) {
|
|
|
35889
36073
|
exec2(cmd, () => {
|
|
35890
36074
|
});
|
|
35891
36075
|
}
|
|
35892
|
-
var WEB_BASE,
|
|
36076
|
+
var WEB_BASE, CONFIG_FILE;
|
|
35893
36077
|
var init_auth = __esm({
|
|
35894
36078
|
"src/auth.ts"() {
|
|
35895
36079
|
"use strict";
|
|
35896
36080
|
WEB_BASE = "https://westbayberry.com";
|
|
35897
|
-
POLL_INTERVAL_MS = 2e3;
|
|
35898
|
-
MAX_POLL_ATTEMPTS = 150;
|
|
35899
36081
|
CONFIG_FILE = ".dgrc.json";
|
|
35900
36082
|
}
|
|
35901
36083
|
});
|
|
35902
36084
|
|
|
35903
36085
|
// src/ui/hooks/useLogin.ts
|
|
35904
|
-
function reducer(
|
|
36086
|
+
function reducer(state, action) {
|
|
35905
36087
|
switch (action.type) {
|
|
35906
36088
|
case "ALREADY_LOGGED_IN":
|
|
35907
36089
|
return { phase: "already_logged_in", apiKey: action.apiKey };
|
|
35908
|
-
case "
|
|
35909
|
-
return { phase: "
|
|
36090
|
+
case "SESSION_READY":
|
|
36091
|
+
return { phase: "ready", verifyUrl: action.verifyUrl };
|
|
36092
|
+
case "BROWSER_OPENED":
|
|
36093
|
+
if (state.phase !== "ready") return state;
|
|
36094
|
+
return { phase: "waiting", verifyUrl: state.verifyUrl };
|
|
35910
36095
|
case "AUTH_COMPLETE":
|
|
35911
36096
|
return { phase: "success", email: action.email };
|
|
35912
36097
|
case "AUTH_EXPIRED":
|
|
@@ -35918,10 +36103,11 @@ function reducer(_state, action) {
|
|
|
35918
36103
|
function useLogin() {
|
|
35919
36104
|
const [state, dispatch] = (0, import_react22.useReducer)(reducer, { phase: "creating" });
|
|
35920
36105
|
const started = (0, import_react22.useRef)(false);
|
|
36106
|
+
const sessionRef = (0, import_react22.useRef)(null);
|
|
36107
|
+
const cancelledRef = (0, import_react22.useRef)(false);
|
|
35921
36108
|
(0, import_react22.useEffect)(() => {
|
|
35922
36109
|
if (started.current) return;
|
|
35923
36110
|
started.current = true;
|
|
35924
|
-
let cancelled = false;
|
|
35925
36111
|
(async () => {
|
|
35926
36112
|
try {
|
|
35927
36113
|
const existing = getStoredApiKey();
|
|
@@ -35939,16 +36125,30 @@ function useLogin() {
|
|
|
35939
36125
|
});
|
|
35940
36126
|
return;
|
|
35941
36127
|
}
|
|
36128
|
+
sessionRef.current = session;
|
|
36129
|
+
dispatch({ type: "SESSION_READY", verifyUrl: session.verifyUrl });
|
|
36130
|
+
} catch (err) {
|
|
35942
36131
|
dispatch({
|
|
35943
|
-
type: "
|
|
35944
|
-
|
|
35945
|
-
verifyUrl: session.verifyUrl
|
|
36132
|
+
type: "ERROR",
|
|
36133
|
+
message: err instanceof Error ? err.message : String(err)
|
|
35946
36134
|
});
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
|
|
35951
|
-
|
|
36135
|
+
}
|
|
36136
|
+
})();
|
|
36137
|
+
return () => {
|
|
36138
|
+
cancelledRef.current = true;
|
|
36139
|
+
};
|
|
36140
|
+
}, []);
|
|
36141
|
+
const openAndPoll = (0, import_react22.useCallback)(() => {
|
|
36142
|
+
const session = sessionRef.current;
|
|
36143
|
+
if (!session) return;
|
|
36144
|
+
dispatch({ type: "BROWSER_OPENED" });
|
|
36145
|
+
openBrowser(session.verifyUrl);
|
|
36146
|
+
(async () => {
|
|
36147
|
+
try {
|
|
36148
|
+
for (let i = 0; i < MAX_POLL_ATTEMPTS; i++) {
|
|
36149
|
+
if (cancelledRef.current) return;
|
|
36150
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
36151
|
+
if (cancelledRef.current) return;
|
|
35952
36152
|
try {
|
|
35953
36153
|
const result = await pollAuthSession(session.sessionId);
|
|
35954
36154
|
if (result.status === "complete" && result.apiKey) {
|
|
@@ -35971,20 +36171,17 @@ function useLogin() {
|
|
|
35971
36171
|
});
|
|
35972
36172
|
}
|
|
35973
36173
|
})();
|
|
35974
|
-
return () => {
|
|
35975
|
-
cancelled = true;
|
|
35976
|
-
};
|
|
35977
36174
|
}, []);
|
|
35978
|
-
return state;
|
|
36175
|
+
return { state, openAndPoll };
|
|
35979
36176
|
}
|
|
35980
|
-
var import_react22,
|
|
36177
|
+
var import_react22, POLL_INTERVAL_MS, MAX_POLL_ATTEMPTS;
|
|
35981
36178
|
var init_useLogin = __esm({
|
|
35982
36179
|
"src/ui/hooks/useLogin.ts"() {
|
|
35983
36180
|
"use strict";
|
|
35984
36181
|
import_react22 = __toESM(require_react());
|
|
35985
36182
|
init_auth();
|
|
35986
|
-
|
|
35987
|
-
|
|
36183
|
+
POLL_INTERVAL_MS = 2e3;
|
|
36184
|
+
MAX_POLL_ATTEMPTS = 150;
|
|
35988
36185
|
}
|
|
35989
36186
|
});
|
|
35990
36187
|
|
|
@@ -38623,7 +38820,7 @@ var init_LoginApp = __esm({
|
|
|
38623
38820
|
await init_Spinner();
|
|
38624
38821
|
import_jsx_runtime2 = __toESM(require_jsx_runtime());
|
|
38625
38822
|
LoginApp = () => {
|
|
38626
|
-
const state = useLogin();
|
|
38823
|
+
const { state, openAndPoll } = useLogin();
|
|
38627
38824
|
const { exit } = use_app_default();
|
|
38628
38825
|
(0, import_react24.useEffect)(() => {
|
|
38629
38826
|
if (state.phase === "success") {
|
|
@@ -38642,6 +38839,11 @@ var init_LoginApp = __esm({
|
|
|
38642
38839
|
return () => clearTimeout(timer);
|
|
38643
38840
|
}
|
|
38644
38841
|
}, [state, exit]);
|
|
38842
|
+
use_input_default((_input, key) => {
|
|
38843
|
+
if (state.phase === "ready" && key.return) {
|
|
38844
|
+
openAndPoll();
|
|
38845
|
+
}
|
|
38846
|
+
});
|
|
38645
38847
|
switch (state.phase) {
|
|
38646
38848
|
case "creating":
|
|
38647
38849
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner2, { label: "Creating login session..." });
|
|
@@ -38654,19 +38856,19 @@ var init_LoginApp = __esm({
|
|
|
38654
38856
|
" first to re-authenticate."
|
|
38655
38857
|
] })
|
|
38656
38858
|
] });
|
|
38657
|
-
case "
|
|
38859
|
+
case "ready":
|
|
38658
38860
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
|
|
38659
38861
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
|
|
38660
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, {
|
|
38862
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: "Authenticate your account at:" }),
|
|
38863
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "cyan", children: state.verifyUrl }),
|
|
38661
38864
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
|
|
38662
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
38663
|
-
|
|
38664
|
-
|
|
38665
|
-
|
|
38865
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, children: "Press ENTER to open in the browser..." })
|
|
38866
|
+
] });
|
|
38867
|
+
case "waiting":
|
|
38868
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 1, children: [
|
|
38666
38869
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
|
|
38667
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: "
|
|
38668
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, {
|
|
38669
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { dimColor: true, color: "cyan", children: state.verifyUrl }),
|
|
38870
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: "Authenticate your account at:" }),
|
|
38871
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: "cyan", children: state.verifyUrl }),
|
|
38670
38872
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
|
|
38671
38873
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Spinner2, { label: "Waiting for authorization..." })
|
|
38672
38874
|
] });
|
|
@@ -39758,11 +39960,22 @@ async function runStaticLogin() {
|
|
|
39758
39960
|
process.exit(1);
|
|
39759
39961
|
}
|
|
39760
39962
|
process.stderr.write(`
|
|
39761
|
-
|
|
39963
|
+
Authenticate your account at:
|
|
39762
39964
|
`);
|
|
39763
|
-
process.stderr.write(`
|
|
39965
|
+
process.stderr.write(` ${import_chalk4.default.cyan(session.verifyUrl)}
|
|
39764
39966
|
|
|
39765
39967
|
`);
|
|
39968
|
+
process.stderr.write(import_chalk4.default.dim(" Press ENTER to open in the browser...\n"));
|
|
39969
|
+
await new Promise((resolve2) => {
|
|
39970
|
+
const onData = () => {
|
|
39971
|
+
process.stdin.removeListener("data", onData);
|
|
39972
|
+
if (process.stdin.unref) process.stdin.unref();
|
|
39973
|
+
resolve2();
|
|
39974
|
+
};
|
|
39975
|
+
process.stdin.setEncoding("utf-8");
|
|
39976
|
+
process.stdin.resume();
|
|
39977
|
+
process.stdin.on("data", onData);
|
|
39978
|
+
});
|
|
39766
39979
|
try {
|
|
39767
39980
|
openBrowser2(session.verifyUrl);
|
|
39768
39981
|
} catch {
|
|
@@ -40115,13 +40328,10 @@ function useNpmWrapper(npmArgs, config) {
|
|
|
40115
40328
|
}
|
|
40116
40329
|
} catch (error) {
|
|
40117
40330
|
const message = error instanceof Error ? error.message : String(error);
|
|
40118
|
-
|
|
40119
|
-
dispatch({ type: "
|
|
40120
|
-
|
|
40121
|
-
|
|
40122
|
-
const code = await runNpm(parsedRef.current.rawArgs);
|
|
40123
|
-
dispatch({ type: "DONE", exitCode: code });
|
|
40124
|
-
}
|
|
40331
|
+
dispatch({ type: "ERROR", message, proceed: true });
|
|
40332
|
+
dispatch({ type: "INSTALLING" });
|
|
40333
|
+
const code = await runNpm(parsedRef.current.rawArgs);
|
|
40334
|
+
dispatch({ type: "DONE", exitCode: code });
|
|
40125
40335
|
}
|
|
40126
40336
|
})();
|
|
40127
40337
|
}, [npmArgs, config]);
|
|
@@ -40427,7 +40637,7 @@ function getHint(error) {
|
|
|
40427
40637
|
if (typeof statusCode !== "number") return null;
|
|
40428
40638
|
switch (statusCode) {
|
|
40429
40639
|
case 401:
|
|
40430
|
-
return "
|
|
40640
|
+
return "Not authenticated. Run `dg login` to sign in.";
|
|
40431
40641
|
case 429:
|
|
40432
40642
|
return "Rate limit exceeded. Upgrade at westbayberry.com/pricing";
|
|
40433
40643
|
case 504:
|
|
@@ -40734,6 +40944,8 @@ function reducer3(_state, action) {
|
|
|
40734
40944
|
switch (action.type) {
|
|
40735
40945
|
case "PROJECTS_FOUND":
|
|
40736
40946
|
return { phase: "selecting", projects: action.projects };
|
|
40947
|
+
case "RESTART_SELECTION":
|
|
40948
|
+
return { phase: "selecting", projects: action.projects };
|
|
40737
40949
|
case "DISCOVERY_COMPLETE":
|
|
40738
40950
|
return { phase: "scanning", done: 0, total: action.packages.length, currentBatch: [] };
|
|
40739
40951
|
case "DISCOVERY_EMPTY":
|
|
@@ -40749,6 +40961,7 @@ function reducer3(_state, action) {
|
|
|
40749
40961
|
function useScan(config) {
|
|
40750
40962
|
const [state, dispatch] = (0, import_react27.useReducer)(reducer3, { phase: "discovering" });
|
|
40751
40963
|
const started = (0, import_react27.useRef)(false);
|
|
40964
|
+
const discoveredProjects = (0, import_react27.useRef)(null);
|
|
40752
40965
|
(0, import_react27.useEffect)(() => {
|
|
40753
40966
|
if (started.current) return;
|
|
40754
40967
|
started.current = true;
|
|
@@ -40764,6 +40977,7 @@ function useScan(config) {
|
|
|
40764
40977
|
runNpmScan(packages, discovery.skipped.length, config, dispatch);
|
|
40765
40978
|
} catch {
|
|
40766
40979
|
const projects = discoverProjects(process.cwd());
|
|
40980
|
+
discoveredProjects.current = projects.length > 1 ? projects : null;
|
|
40767
40981
|
if (projects.length === 0) {
|
|
40768
40982
|
dispatch({ type: "DISCOVERY_EMPTY", message: "No dependency files found." });
|
|
40769
40983
|
return;
|
|
@@ -40780,7 +40994,15 @@ function useScan(config) {
|
|
|
40780
40994
|
dispatch({ type: "DISCOVERY_COMPLETE", packages: [], skippedCount: 0 });
|
|
40781
40995
|
scanProjects(projects, config, dispatch);
|
|
40782
40996
|
}, [config]);
|
|
40783
|
-
|
|
40997
|
+
const restartSelection = (0, import_react27.useCallback)(() => {
|
|
40998
|
+
if (!discoveredProjects.current) return;
|
|
40999
|
+
dispatch({ type: "RESTART_SELECTION", projects: discoveredProjects.current });
|
|
41000
|
+
}, []);
|
|
41001
|
+
return {
|
|
41002
|
+
state,
|
|
41003
|
+
scanSelectedProjects,
|
|
41004
|
+
restartSelection: discoveredProjects.current ? restartSelection : null
|
|
41005
|
+
};
|
|
40784
41006
|
}
|
|
40785
41007
|
async function runNpmScan(packages, skippedCount, config, dispatch) {
|
|
40786
41008
|
try {
|
|
@@ -41016,7 +41238,17 @@ function affectsLine(group) {
|
|
|
41016
41238
|
if (names.length <= 5) return names.join(", ");
|
|
41017
41239
|
return names.slice(0, 5).join(", ") + ` + ${names.length - 5} more`;
|
|
41018
41240
|
}
|
|
41019
|
-
|
|
41241
|
+
function viewReducer(_state, action) {
|
|
41242
|
+
switch (action.type) {
|
|
41243
|
+
case "MOVE":
|
|
41244
|
+
return { ..._state, cursor: action.cursor, viewport: action.viewport };
|
|
41245
|
+
case "EXPAND":
|
|
41246
|
+
return { ..._state, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
|
|
41247
|
+
case "MOVE_EXPAND":
|
|
41248
|
+
return { cursor: action.cursor, expandedIndex: action.expandedIndex, expandLevel: action.expandLevel, viewport: action.viewport };
|
|
41249
|
+
}
|
|
41250
|
+
}
|
|
41251
|
+
var import_react29, import_chalk10, import_jsx_runtime10, SEVERITY_LABELS3, SEVERITY_COLORS, EVIDENCE_LIMIT2, FIXED_CHROME, InteractiveResultsView, T, FindingsSummary, FindingsDetail;
|
|
41020
41252
|
var init_InteractiveResultsView = __esm({
|
|
41021
41253
|
async "src/ui/components/InteractiveResultsView.tsx"() {
|
|
41022
41254
|
"use strict";
|
|
@@ -41042,19 +41274,18 @@ var init_InteractiveResultsView = __esm({
|
|
|
41042
41274
|
};
|
|
41043
41275
|
EVIDENCE_LIMIT2 = 2;
|
|
41044
41276
|
FIXED_CHROME = 16;
|
|
41045
|
-
MOUSE_ON = "\x1B[?1000h\x1B[?1003h\x1B[?1006h";
|
|
41046
|
-
MOUSE_OFF = "\x1B[?1006l\x1B[?1003l\x1B[?1000l";
|
|
41047
41277
|
InteractiveResultsView = ({
|
|
41048
41278
|
result,
|
|
41049
41279
|
config,
|
|
41050
41280
|
durationMs,
|
|
41051
|
-
onExit
|
|
41281
|
+
onExit,
|
|
41282
|
+
onBack
|
|
41052
41283
|
}) => {
|
|
41053
41284
|
(0, import_react29.useEffect)(() => {
|
|
41054
41285
|
if (!process.stdout.isTTY) return;
|
|
41055
|
-
process.stdout.write("\x1B[?1049h"
|
|
41286
|
+
process.stdout.write("\x1B[?1049h");
|
|
41056
41287
|
return () => {
|
|
41057
|
-
process.stdout.write(
|
|
41288
|
+
process.stdout.write("\x1B[?1049l");
|
|
41058
41289
|
};
|
|
41059
41290
|
}, []);
|
|
41060
41291
|
const flagged = (0, import_react29.useMemo)(
|
|
@@ -41067,45 +41298,41 @@ var init_InteractiveResultsView = __esm({
|
|
|
41067
41298
|
);
|
|
41068
41299
|
const total = result.packages.length;
|
|
41069
41300
|
const groups = (0, import_react29.useMemo)(() => groupPackages3(flagged), [flagged]);
|
|
41070
|
-
const [
|
|
41071
|
-
|
|
41072
|
-
|
|
41073
|
-
|
|
41074
|
-
|
|
41075
|
-
|
|
41076
|
-
const
|
|
41077
|
-
|
|
41078
|
-
cursorRef.current = cursorIndex;
|
|
41079
|
-
expandLevelRef.current = expandLevel;
|
|
41080
|
-
expandedIdxRef.current = expandedIndex;
|
|
41081
|
-
viewportRef.current = viewportStart;
|
|
41301
|
+
const [view, dispatchView] = (0, import_react29.useReducer)(viewReducer, {
|
|
41302
|
+
cursor: 0,
|
|
41303
|
+
expandLevel: null,
|
|
41304
|
+
expandedIndex: null,
|
|
41305
|
+
viewport: 0
|
|
41306
|
+
});
|
|
41307
|
+
const viewRef = (0, import_react29.useRef)(view);
|
|
41308
|
+
viewRef.current = view;
|
|
41082
41309
|
const { stdout } = use_stdout_default();
|
|
41083
41310
|
const termCols = stdout?.columns ?? process.stdout.columns ?? 80;
|
|
41084
41311
|
const termRows = stdout?.rows ?? process.stdout.rows ?? 24;
|
|
41085
41312
|
const availableRows = Math.max(5, termRows - FIXED_CHROME);
|
|
41086
41313
|
const innerWidth = Math.max(40, termCols - 6);
|
|
41087
41314
|
const getLevel = (idx) => {
|
|
41088
|
-
return expandedIndex === idx ? expandLevel : null;
|
|
41315
|
+
return view.expandedIndex === idx ? view.expandLevel : null;
|
|
41089
41316
|
};
|
|
41090
41317
|
const expandTargetHeight = (0, import_react29.useMemo)(() => {
|
|
41091
|
-
if (expandedIndex === null || expandLevel === null) return 0;
|
|
41092
|
-
const group = groups[expandedIndex];
|
|
41318
|
+
if (view.expandedIndex === null || view.expandLevel === null) return 0;
|
|
41319
|
+
const group = groups[view.expandedIndex];
|
|
41093
41320
|
if (!group) return 0;
|
|
41094
|
-
if (expandLevel === "summary") return findingsSummaryHeight(group);
|
|
41321
|
+
if (view.expandLevel === "summary") return findingsSummaryHeight(group);
|
|
41095
41322
|
return findingsDetailHeight(group, result.safeVersions);
|
|
41096
|
-
}, [expandedIndex, expandLevel, groups, result.safeVersions]);
|
|
41323
|
+
}, [view.expandedIndex, view.expandLevel, groups, result.safeVersions]);
|
|
41097
41324
|
const { visibleLines: animVisibleLines } = useExpandAnimation(
|
|
41098
41325
|
expandTargetHeight,
|
|
41099
|
-
expandedIndex !== null
|
|
41326
|
+
view.expandedIndex !== null
|
|
41100
41327
|
);
|
|
41101
41328
|
const animatedGroupHeight = (group, level, idx) => {
|
|
41102
41329
|
if (level === null) return 1;
|
|
41103
|
-
if (idx === expandedIndex) return 1 + animVisibleLines;
|
|
41330
|
+
if (idx === view.expandedIndex) return 1 + animVisibleLines;
|
|
41104
41331
|
return groupRowHeight(group, level, result.safeVersions);
|
|
41105
41332
|
};
|
|
41106
41333
|
const visibleEnd = (0, import_react29.useMemo)(() => {
|
|
41107
41334
|
let consumed = 0;
|
|
41108
|
-
let end =
|
|
41335
|
+
let end = view.viewport;
|
|
41109
41336
|
while (end < groups.length) {
|
|
41110
41337
|
const level = getLevel(end);
|
|
41111
41338
|
const h = animatedGroupHeight(groups[end], level, end);
|
|
@@ -41113,9 +41340,9 @@ var init_InteractiveResultsView = __esm({
|
|
|
41113
41340
|
consumed += h;
|
|
41114
41341
|
end++;
|
|
41115
41342
|
}
|
|
41116
|
-
if (end ===
|
|
41343
|
+
if (end === view.viewport && groups.length > 0) end = view.viewport + 1;
|
|
41117
41344
|
return end;
|
|
41118
|
-
}, [
|
|
41345
|
+
}, [view.viewport, groups, view.expandedIndex, view.expandLevel, animVisibleLines, availableRows, result.safeVersions]);
|
|
41119
41346
|
const adjustViewport = (cursor, expIdx, expLvl, currentStart) => {
|
|
41120
41347
|
if (cursor < currentStart) return cursor;
|
|
41121
41348
|
const getLvl = (i) => expIdx === i ? expLvl : null;
|
|
@@ -41140,43 +41367,39 @@ var init_InteractiveResultsView = __esm({
|
|
|
41140
41367
|
if (input === "q" || key.return) onExit();
|
|
41141
41368
|
return;
|
|
41142
41369
|
}
|
|
41143
|
-
const cursor =
|
|
41144
|
-
const expLvl = expandLevelRef.current;
|
|
41145
|
-
const expIdx = expandedIdxRef.current;
|
|
41146
|
-
const vpStart = viewportRef.current;
|
|
41370
|
+
const { cursor, expandLevel: expLvl, expandedIndex: expIdx, viewport: vpStart } = viewRef.current;
|
|
41147
41371
|
if (key.upArrow) {
|
|
41148
41372
|
const next = Math.max(0, cursor - 1);
|
|
41149
41373
|
const newVp = adjustViewport(next, expIdx, expLvl, vpStart < next ? vpStart : next);
|
|
41150
|
-
|
|
41151
|
-
setViewportStart(newVp);
|
|
41374
|
+
dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
|
|
41152
41375
|
} else if (key.downArrow) {
|
|
41153
41376
|
const next = Math.min(groups.length - 1, cursor + 1);
|
|
41154
41377
|
const newVp = adjustViewport(next, expIdx, expLvl, vpStart);
|
|
41155
|
-
|
|
41156
|
-
setViewportStart(newVp);
|
|
41378
|
+
dispatchView({ type: "MOVE", cursor: next, viewport: newVp });
|
|
41157
41379
|
} else if (key.return) {
|
|
41158
41380
|
let newExpIdx;
|
|
41159
41381
|
let newExpLvl;
|
|
41160
|
-
if (expIdx !==
|
|
41161
|
-
newExpIdx = cursor;
|
|
41162
|
-
newExpLvl = "summary";
|
|
41163
|
-
} else if (expLvl === "summary") {
|
|
41164
|
-
newExpIdx = cursor;
|
|
41165
|
-
newExpLvl = "detail";
|
|
41166
|
-
} else {
|
|
41382
|
+
if (expIdx === cursor && expLvl !== null) {
|
|
41167
41383
|
newExpIdx = null;
|
|
41168
41384
|
newExpLvl = null;
|
|
41385
|
+
} else {
|
|
41386
|
+
newExpIdx = cursor;
|
|
41387
|
+
newExpLvl = "summary";
|
|
41169
41388
|
}
|
|
41170
|
-
setExpandedIndex(newExpIdx);
|
|
41171
|
-
setExpandLevel(newExpLvl);
|
|
41172
41389
|
const newVp = adjustViewport(cursor, newExpIdx, newExpLvl, vpStart);
|
|
41173
|
-
|
|
41390
|
+
dispatchView({ type: "EXPAND", expandedIndex: newExpIdx, expandLevel: newExpLvl, viewport: newVp });
|
|
41391
|
+
} else if (input === "e") {
|
|
41392
|
+
if (expIdx === cursor && expLvl === "detail") return;
|
|
41393
|
+
const newVp = adjustViewport(cursor, cursor, "detail", vpStart);
|
|
41394
|
+
dispatchView({ type: "EXPAND", expandedIndex: cursor, expandLevel: "detail", viewport: newVp });
|
|
41395
|
+
} else if (input === "b" && onBack) {
|
|
41396
|
+
onBack();
|
|
41174
41397
|
} else if (input === "q") {
|
|
41175
41398
|
onExit();
|
|
41176
41399
|
}
|
|
41177
41400
|
});
|
|
41178
|
-
const visibleGroups = groups.slice(
|
|
41179
|
-
const aboveCount =
|
|
41401
|
+
const visibleGroups = groups.slice(view.viewport, visibleEnd);
|
|
41402
|
+
const aboveCount = view.viewport;
|
|
41180
41403
|
const belowCount = groups.length - visibleEnd;
|
|
41181
41404
|
const nameCol = Math.max(20, innerWidth - 22);
|
|
41182
41405
|
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(Box_default, { flexDirection: "column", children: [
|
|
@@ -41215,8 +41438,8 @@ var init_InteractiveResultsView = __esm({
|
|
|
41215
41438
|
" more above"
|
|
41216
41439
|
] }),
|
|
41217
41440
|
visibleGroups.map((group, visIdx) => {
|
|
41218
|
-
const globalIdx =
|
|
41219
|
-
const isCursor = globalIdx ===
|
|
41441
|
+
const globalIdx = view.viewport + visIdx;
|
|
41442
|
+
const isCursor = globalIdx === view.cursor;
|
|
41220
41443
|
const level = getLevel(globalIdx);
|
|
41221
41444
|
const rep = group.packages[0];
|
|
41222
41445
|
const { label, color } = actionBadge4(rep.score, config);
|
|
@@ -41235,7 +41458,7 @@ var init_InteractiveResultsView = __esm({
|
|
|
41235
41458
|
{
|
|
41236
41459
|
group,
|
|
41237
41460
|
maxWidth: innerWidth - 8,
|
|
41238
|
-
maxLines: globalIdx === expandedIndex ? animVisibleLines : void 0
|
|
41461
|
+
maxLines: globalIdx === view.expandedIndex ? animVisibleLines : void 0
|
|
41239
41462
|
}
|
|
41240
41463
|
),
|
|
41241
41464
|
level === "detail" && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
@@ -41244,7 +41467,7 @@ var init_InteractiveResultsView = __esm({
|
|
|
41244
41467
|
group,
|
|
41245
41468
|
safeVersion: result.safeVersions[rep.name],
|
|
41246
41469
|
maxWidth: innerWidth - 8,
|
|
41247
|
-
maxLines: globalIdx === expandedIndex ? animVisibleLines : void 0
|
|
41470
|
+
maxLines: globalIdx === view.expandedIndex ? animVisibleLines : void 0
|
|
41248
41471
|
}
|
|
41249
41472
|
)
|
|
41250
41473
|
] }, group.key);
|
|
@@ -41291,13 +41514,23 @@ var init_InteractiveResultsView = __esm({
|
|
|
41291
41514
|
" navigate",
|
|
41292
41515
|
" ",
|
|
41293
41516
|
import_chalk10.default.cyan("\u23CE"),
|
|
41294
|
-
"
|
|
41517
|
+
" toggle",
|
|
41518
|
+
" ",
|
|
41519
|
+
import_chalk10.default.cyan("e"),
|
|
41520
|
+
" detail",
|
|
41295
41521
|
" ",
|
|
41522
|
+
onBack && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
41523
|
+
import_chalk10.default.cyan("b"),
|
|
41524
|
+
" back",
|
|
41525
|
+
" "
|
|
41526
|
+
] }),
|
|
41296
41527
|
import_chalk10.default.cyan("q"),
|
|
41297
41528
|
" quit"
|
|
41298
41529
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
|
|
41299
41530
|
"Press ",
|
|
41300
41531
|
import_chalk10.default.cyan("q"),
|
|
41532
|
+
" or ",
|
|
41533
|
+
import_chalk10.default.cyan("Enter"),
|
|
41301
41534
|
" to exit"
|
|
41302
41535
|
] })
|
|
41303
41536
|
] })
|
|
@@ -41305,13 +41538,9 @@ var init_InteractiveResultsView = __esm({
|
|
|
41305
41538
|
};
|
|
41306
41539
|
T = {
|
|
41307
41540
|
branch: import_chalk10.default.dim("\u251C\u2500\u2500"),
|
|
41308
|
-
// ├──
|
|
41309
41541
|
last: import_chalk10.default.dim("\u2514\u2500\u2500"),
|
|
41310
|
-
// └──
|
|
41311
41542
|
pipe: import_chalk10.default.dim("\u2502"),
|
|
41312
|
-
// │
|
|
41313
41543
|
blank: " "
|
|
41314
|
-
//
|
|
41315
41544
|
};
|
|
41316
41545
|
FindingsSummary = ({ group, maxWidth, maxLines }) => {
|
|
41317
41546
|
const rep = group.packages[0];
|
|
@@ -41477,12 +41706,7 @@ var init_ProjectSelector = __esm({
|
|
|
41477
41706
|
return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: isCursor, inverse: isCursor, children: line }, i);
|
|
41478
41707
|
}),
|
|
41479
41708
|
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: "" }),
|
|
41480
|
-
/* @__PURE__ */ (0, import_jsx_runtime11.
|
|
41481
|
-
selected.size,
|
|
41482
|
-
" of ",
|
|
41483
|
-
projects.length,
|
|
41484
|
-
" selected"
|
|
41485
|
-
] })
|
|
41709
|
+
/* @__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` })
|
|
41486
41710
|
] });
|
|
41487
41711
|
};
|
|
41488
41712
|
}
|
|
@@ -41507,7 +41731,7 @@ var init_App2 = __esm({
|
|
|
41507
41731
|
await init_ProjectSelector();
|
|
41508
41732
|
import_jsx_runtime12 = __toESM(require_jsx_runtime());
|
|
41509
41733
|
App2 = ({ config }) => {
|
|
41510
|
-
const { state, scanSelectedProjects } = useScan(config);
|
|
41734
|
+
const { state, scanSelectedProjects, restartSelection } = useScan(config);
|
|
41511
41735
|
const { exit } = use_app_default();
|
|
41512
41736
|
const handleResultsExit = (0, import_react31.useCallback)(() => {
|
|
41513
41737
|
if (state.phase === "results") {
|
|
@@ -41574,7 +41798,8 @@ var init_App2 = __esm({
|
|
|
41574
41798
|
result: state.result,
|
|
41575
41799
|
config,
|
|
41576
41800
|
durationMs: state.durationMs,
|
|
41577
|
-
onExit: handleResultsExit
|
|
41801
|
+
onExit: handleResultsExit,
|
|
41802
|
+
onBack: restartSelection ?? void 0
|
|
41578
41803
|
}
|
|
41579
41804
|
);
|
|
41580
41805
|
case "empty":
|
|
@@ -41586,183 +41811,8 @@ var init_App2 = __esm({
|
|
|
41586
41811
|
}
|
|
41587
41812
|
});
|
|
41588
41813
|
|
|
41589
|
-
// src/config.ts
|
|
41590
|
-
import { parseArgs } from "node:util";
|
|
41591
|
-
import { readFileSync, existsSync } from "node:fs";
|
|
41592
|
-
import { join } from "node:path";
|
|
41593
|
-
import { homedir } from "node:os";
|
|
41594
|
-
function loadDgrc() {
|
|
41595
|
-
const candidates = [
|
|
41596
|
-
join(process.cwd(), ".dgrc.json"),
|
|
41597
|
-
join(homedir(), ".dgrc.json")
|
|
41598
|
-
];
|
|
41599
|
-
for (const filepath of candidates) {
|
|
41600
|
-
if (existsSync(filepath)) {
|
|
41601
|
-
try {
|
|
41602
|
-
return JSON.parse(readFileSync(filepath, "utf-8"));
|
|
41603
|
-
} catch {
|
|
41604
|
-
process.stderr.write(`Warning: Failed to parse ${filepath}, ignoring.
|
|
41605
|
-
`);
|
|
41606
|
-
}
|
|
41607
|
-
}
|
|
41608
|
-
}
|
|
41609
|
-
return {};
|
|
41610
|
-
}
|
|
41611
|
-
var USAGE = `
|
|
41612
|
-
Dependency Guardian \u2014 Supply chain security scanner
|
|
41613
|
-
|
|
41614
|
-
Usage:
|
|
41615
|
-
dependency-guardian scan [options]
|
|
41616
|
-
dg scan [options]
|
|
41617
|
-
dg npm install <pkg> [npm-flags]
|
|
41618
|
-
dg wrap
|
|
41619
|
-
|
|
41620
|
-
Commands:
|
|
41621
|
-
scan Scan dependencies (auto-discovers npm + Python projects)
|
|
41622
|
-
npm Wrap npm commands \u2014 scans packages before installing
|
|
41623
|
-
hook install Install git pre-commit hook to scan lockfile changes
|
|
41624
|
-
hook uninstall Remove the pre-commit hook
|
|
41625
|
-
update Check for and install the latest version
|
|
41626
|
-
login Authenticate with your WestBayBerry account
|
|
41627
|
-
logout Remove saved credentials
|
|
41628
|
-
wrap Show instructions to alias npm to dg
|
|
41629
|
-
|
|
41630
|
-
Options:
|
|
41631
|
-
--api-url <url> API base URL (default: https://api.westbayberry.com)
|
|
41632
|
-
--mode <mode> block | warn | off (default: warn)
|
|
41633
|
-
--block-threshold <n> Score threshold for blocking (default: 70)
|
|
41634
|
-
--warn-threshold <n> Score threshold for warnings (default: 60)
|
|
41635
|
-
--max-packages <n> Max packages per scan (default: 200)
|
|
41636
|
-
--allowlist <pkgs> Comma-separated package names to skip
|
|
41637
|
-
--json Output JSON for CI parsing
|
|
41638
|
-
--scan-all Scan all packages, not just changed
|
|
41639
|
-
--base-lockfile <path> Path to base lockfile for explicit diff
|
|
41640
|
-
--workspace <dir> Scan a specific workspace subdirectory
|
|
41641
|
-
--debug Show diagnostic output (discovery, batches, timing)
|
|
41642
|
-
--no-config Skip loading .dgrc.json config file
|
|
41643
|
-
--help Show this help message
|
|
41644
|
-
--version Show version number
|
|
41645
|
-
|
|
41646
|
-
Config File:
|
|
41647
|
-
Place a .dgrc.json in your project root or home directory.
|
|
41648
|
-
Precedence: CLI flags > env vars > .dgrc.json > defaults
|
|
41649
|
-
|
|
41650
|
-
Environment Variables:
|
|
41651
|
-
DG_API_URL API base URL
|
|
41652
|
-
DG_MODE Mode (block/warn/off)
|
|
41653
|
-
DG_ALLOWLIST Comma-separated allowlist
|
|
41654
|
-
DG_DEBUG Enable debug output (set to 1)
|
|
41655
|
-
DG_WORKSPACE Workspace subdirectory to scan
|
|
41656
|
-
|
|
41657
|
-
Exit Codes:
|
|
41658
|
-
0 pass \u2014 No risks detected
|
|
41659
|
-
1 warn \u2014 Risks detected (advisory)
|
|
41660
|
-
2 block \u2014 High-risk packages detected
|
|
41661
|
-
3 error \u2014 Internal error (API failure, config error)
|
|
41662
|
-
|
|
41663
|
-
Examples:
|
|
41664
|
-
dg scan
|
|
41665
|
-
dg scan --json
|
|
41666
|
-
dg scan --scan-all --mode block
|
|
41667
|
-
dg scan --base-lockfile ./main-lockfile.json
|
|
41668
|
-
dg npm install express lodash
|
|
41669
|
-
dg npm install @scope/pkg@^2.0.0
|
|
41670
|
-
dg npm install risky-pkg --dg-force
|
|
41671
|
-
`.trimStart();
|
|
41672
|
-
function getVersion() {
|
|
41673
|
-
try {
|
|
41674
|
-
const pkg = JSON.parse(
|
|
41675
|
-
readFileSync(join(__dirname, "..", "package.json"), "utf-8")
|
|
41676
|
-
);
|
|
41677
|
-
return pkg.version ?? "1.0.0";
|
|
41678
|
-
} catch {
|
|
41679
|
-
return "1.0.0";
|
|
41680
|
-
}
|
|
41681
|
-
}
|
|
41682
|
-
function parseConfig(argv) {
|
|
41683
|
-
const { values, positionals } = parseArgs({
|
|
41684
|
-
args: argv.slice(2),
|
|
41685
|
-
options: {
|
|
41686
|
-
"api-url": { type: "string" },
|
|
41687
|
-
mode: { type: "string" },
|
|
41688
|
-
"block-threshold": { type: "string" },
|
|
41689
|
-
"warn-threshold": { type: "string" },
|
|
41690
|
-
"max-packages": { type: "string" },
|
|
41691
|
-
allowlist: { type: "string" },
|
|
41692
|
-
json: { type: "boolean", default: false },
|
|
41693
|
-
"scan-all": { type: "boolean", default: false },
|
|
41694
|
-
"base-lockfile": { type: "string" },
|
|
41695
|
-
workspace: { type: "string", short: "w" },
|
|
41696
|
-
debug: { type: "boolean", default: false },
|
|
41697
|
-
"no-config": { type: "boolean", default: false },
|
|
41698
|
-
help: { type: "boolean", default: false },
|
|
41699
|
-
version: { type: "boolean", default: false }
|
|
41700
|
-
},
|
|
41701
|
-
allowPositionals: true,
|
|
41702
|
-
strict: false
|
|
41703
|
-
});
|
|
41704
|
-
if (values.help) {
|
|
41705
|
-
process.stdout.write(USAGE);
|
|
41706
|
-
process.exit(0);
|
|
41707
|
-
}
|
|
41708
|
-
if (values.version) {
|
|
41709
|
-
process.stdout.write(`dependency-guardian v${getVersion()}
|
|
41710
|
-
`);
|
|
41711
|
-
process.exit(0);
|
|
41712
|
-
}
|
|
41713
|
-
const command = positionals[0] ?? "scan";
|
|
41714
|
-
const noConfig = values["no-config"];
|
|
41715
|
-
const dgrc = noConfig ? {} : loadDgrc();
|
|
41716
|
-
const apiKey = dgrc.apiKey ?? "";
|
|
41717
|
-
if (!apiKey) {
|
|
41718
|
-
process.stderr.write(
|
|
41719
|
-
"Error: Not logged in. Run `dg login` to authenticate.\n"
|
|
41720
|
-
);
|
|
41721
|
-
process.exit(1);
|
|
41722
|
-
}
|
|
41723
|
-
const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
|
|
41724
|
-
if (!["block", "warn", "off"].includes(modeRaw)) {
|
|
41725
|
-
process.stderr.write(
|
|
41726
|
-
`Error: Invalid mode "${modeRaw}". Must be block, warn, or off.
|
|
41727
|
-
`
|
|
41728
|
-
);
|
|
41729
|
-
process.exit(1);
|
|
41730
|
-
}
|
|
41731
|
-
const allowlistRaw = values.allowlist ?? process.env.DG_ALLOWLIST ?? "";
|
|
41732
|
-
const blockThreshold = Number(values["block-threshold"] ?? dgrc.blockThreshold ?? "70");
|
|
41733
|
-
const warnThreshold = Number(values["warn-threshold"] ?? dgrc.warnThreshold ?? "60");
|
|
41734
|
-
const maxPackages = Number(values["max-packages"] ?? dgrc.maxPackages ?? "200");
|
|
41735
|
-
const debug = values.debug || process.env.DG_DEBUG === "1";
|
|
41736
|
-
if (isNaN(blockThreshold) || blockThreshold < 0 || blockThreshold > 100) {
|
|
41737
|
-
process.stderr.write("Error: --block-threshold must be a number between 0 and 100\n");
|
|
41738
|
-
process.exit(1);
|
|
41739
|
-
}
|
|
41740
|
-
if (isNaN(warnThreshold) || warnThreshold < 0 || warnThreshold > 100) {
|
|
41741
|
-
process.stderr.write("Error: --warn-threshold must be a number between 0 and 100\n");
|
|
41742
|
-
process.exit(1);
|
|
41743
|
-
}
|
|
41744
|
-
if (isNaN(maxPackages) || maxPackages < 1 || maxPackages > 1e4) {
|
|
41745
|
-
process.stderr.write("Error: --max-packages must be a number between 1 and 10000\n");
|
|
41746
|
-
process.exit(1);
|
|
41747
|
-
}
|
|
41748
|
-
return {
|
|
41749
|
-
apiKey,
|
|
41750
|
-
apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
|
|
41751
|
-
mode: modeRaw,
|
|
41752
|
-
blockThreshold,
|
|
41753
|
-
warnThreshold,
|
|
41754
|
-
maxPackages,
|
|
41755
|
-
allowlist: allowlistRaw ? allowlistRaw.split(",").map((s) => s.trim()).filter(Boolean) : dgrc.allowlist ?? [],
|
|
41756
|
-
json: values.json,
|
|
41757
|
-
scanAll: values["scan-all"],
|
|
41758
|
-
baseLockfile: values["base-lockfile"] ?? null,
|
|
41759
|
-
workspace: values.workspace ?? process.env.DG_WORKSPACE ?? null,
|
|
41760
|
-
command,
|
|
41761
|
-
debug
|
|
41762
|
-
};
|
|
41763
|
-
}
|
|
41764
|
-
|
|
41765
41814
|
// src/bin.ts
|
|
41815
|
+
init_config();
|
|
41766
41816
|
init_npm_wrapper();
|
|
41767
41817
|
|
|
41768
41818
|
// src/update-check.ts
|
|
@@ -41824,8 +41874,7 @@ function spawnBackgroundUpdate(version) {
|
|
|
41824
41874
|
try {
|
|
41825
41875
|
const child = spawn2("npm", ["install", "-g", `${PKG_NAME}@${version}`], {
|
|
41826
41876
|
detached: true,
|
|
41827
|
-
stdio: "ignore"
|
|
41828
|
-
shell: true
|
|
41877
|
+
stdio: "ignore"
|
|
41829
41878
|
});
|
|
41830
41879
|
child.unref();
|
|
41831
41880
|
} catch {
|
|
@@ -41887,10 +41936,20 @@ async function runUpdate(currentVersion) {
|
|
|
41887
41936
|
}
|
|
41888
41937
|
|
|
41889
41938
|
// src/bin.ts
|
|
41890
|
-
var CLI_VERSION =
|
|
41939
|
+
var CLI_VERSION = getVersion();
|
|
41891
41940
|
var isInteractive = process.stdout.isTTY === true && !process.env.CI && !process.env.NO_COLOR;
|
|
41892
41941
|
async function main() {
|
|
41893
41942
|
const rawCommand = process.argv[2];
|
|
41943
|
+
if (!rawCommand || rawCommand === "--help" || rawCommand === "-h") {
|
|
41944
|
+
const { USAGE: USAGE3 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
41945
|
+
process.stdout.write(USAGE3);
|
|
41946
|
+
return;
|
|
41947
|
+
}
|
|
41948
|
+
if (rawCommand === "--version" || rawCommand === "-v") {
|
|
41949
|
+
process.stdout.write(`dependency-guardian v${CLI_VERSION}
|
|
41950
|
+
`);
|
|
41951
|
+
return;
|
|
41952
|
+
}
|
|
41894
41953
|
if (rawCommand === "wrap") {
|
|
41895
41954
|
handleWrapCommand();
|
|
41896
41955
|
return;
|
|
@@ -41921,7 +41980,7 @@ async function main() {
|
|
|
41921
41980
|
const { clearCredentials: clearCredentials2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
41922
41981
|
clearCredentials2();
|
|
41923
41982
|
const chalk9 = (await Promise.resolve().then(() => __toESM(require_source()))).default;
|
|
41924
|
-
process.stderr.write(chalk9.green(" Logged out
|
|
41983
|
+
process.stderr.write(chalk9.green(" Logged out.\n"));
|
|
41925
41984
|
return;
|
|
41926
41985
|
}
|
|
41927
41986
|
const config = parseConfig(process.argv);
|