@westbayberry/dg 1.0.17 → 1.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.mjs +323 -189
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -39,6 +39,149 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  mod
40
40
  ));
41
41
 
42
+ // src/auth.ts
43
+ var auth_exports = {};
44
+ __export(auth_exports, {
45
+ clearCredentials: () => clearCredentials,
46
+ createAuthSession: () => createAuthSession,
47
+ getOrCreateDeviceId: () => getOrCreateDeviceId,
48
+ getStoredApiKey: () => getStoredApiKey,
49
+ openBrowser: () => openBrowser,
50
+ pollAuthSession: () => pollAuthSession,
51
+ saveCredentials: () => saveCredentials
52
+ });
53
+ import { readFileSync, writeFileSync, chmodSync, unlinkSync, existsSync } from "node:fs";
54
+ import { join } from "node:path";
55
+ import { homedir } from "node:os";
56
+ import { exec } from "node:child_process";
57
+ import { randomUUID } from "node:crypto";
58
+ async function createAuthSession() {
59
+ let res;
60
+ try {
61
+ res = await globalThis.fetch(`${WEB_BASE}/cli/auth/sessions`, {
62
+ method: "POST",
63
+ headers: { "Content-Type": "application/json" }
64
+ });
65
+ } catch {
66
+ throw new Error("Could not connect to westbayberry.com");
67
+ }
68
+ if (!res.ok) {
69
+ const body = await res.text().catch(() => "");
70
+ throw new Error(
71
+ `Failed to create auth session (HTTP ${res.status})${body ? `: ${body}` : ""}`
72
+ );
73
+ }
74
+ const json = await res.json();
75
+ return {
76
+ sessionId: json.session_id,
77
+ verifyUrl: json.verify_url,
78
+ expiresIn: json.expires_in
79
+ };
80
+ }
81
+ async function pollAuthSession(sessionId) {
82
+ let res;
83
+ try {
84
+ res = await globalThis.fetch(
85
+ `${WEB_BASE}/cli/auth/sessions/${sessionId}/token`
86
+ );
87
+ } catch {
88
+ return { status: "expired" };
89
+ }
90
+ if (res.status === 404) {
91
+ return { status: "expired" };
92
+ }
93
+ if (!res.ok) {
94
+ return { status: "expired" };
95
+ }
96
+ const json = await res.json();
97
+ return {
98
+ status: json.status,
99
+ apiKey: json.api_key,
100
+ email: json.email
101
+ };
102
+ }
103
+ function configPath() {
104
+ return join(homedir(), CONFIG_FILE);
105
+ }
106
+ function readConfig() {
107
+ try {
108
+ const raw = readFileSync(configPath(), "utf-8");
109
+ const parsed = JSON.parse(raw);
110
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
111
+ return parsed;
112
+ }
113
+ return {};
114
+ } catch {
115
+ return {};
116
+ }
117
+ }
118
+ function saveCredentials(apiKey) {
119
+ const data = readConfig();
120
+ data.apiKey = apiKey;
121
+ const p = configPath();
122
+ writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf-8");
123
+ chmodSync(p, 384);
124
+ }
125
+ function clearCredentials() {
126
+ const p = configPath();
127
+ if (!existsSync(p)) return;
128
+ const data = readConfig();
129
+ delete data.apiKey;
130
+ if (Object.keys(data).length === 0) {
131
+ unlinkSync(p);
132
+ } else {
133
+ writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf-8");
134
+ chmodSync(p, 384);
135
+ }
136
+ }
137
+ function getStoredApiKey() {
138
+ const data = readConfig();
139
+ if (typeof data.apiKey === "string" && data.apiKey.length > 0) {
140
+ return data.apiKey;
141
+ }
142
+ return null;
143
+ }
144
+ function getOrCreateDeviceId() {
145
+ const data = readConfig();
146
+ if (typeof data.deviceId === "string" && data.deviceId.length > 0) {
147
+ return data.deviceId;
148
+ }
149
+ const deviceId = randomUUID();
150
+ data.deviceId = deviceId;
151
+ const p = configPath();
152
+ writeFileSync(p, JSON.stringify(data, null, 2) + "\n", "utf-8");
153
+ chmodSync(p, 384);
154
+ return deviceId;
155
+ }
156
+ function openBrowser(url) {
157
+ if (!/^https?:\/\//i.test(url)) return;
158
+ const escaped = url.replace(/"/g, '\\"');
159
+ let cmd;
160
+ switch (process.platform) {
161
+ case "darwin":
162
+ cmd = `open "${escaped}"`;
163
+ break;
164
+ case "linux":
165
+ cmd = `xdg-open "${escaped}"`;
166
+ break;
167
+ case "win32":
168
+ cmd = `start "" "${escaped}"`;
169
+ break;
170
+ default:
171
+ return;
172
+ }
173
+ exec(cmd, () => {
174
+ });
175
+ }
176
+ var WEB_BASE, CONFIG_FILE;
177
+ var init_auth = __esm({
178
+ "src/auth.ts"() {
179
+ "use strict";
180
+ WEB_BASE = "https://westbayberry.com";
181
+ CONFIG_FILE = ".dgrc.json";
182
+ }
183
+ });
184
+
42
185
  // src/config.ts
43
186
  var config_exports = {};
44
187
  __export(config_exports, {
@@ -47,19 +190,19 @@ __export(config_exports, {
47
190
  parseConfig: () => parseConfig
48
191
  });
49
192
  import { parseArgs } from "node:util";
50
- import { readFileSync, existsSync } from "node:fs";
51
- import { join, dirname } from "node:path";
193
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
194
+ import { join as join2, dirname } from "node:path";
52
195
  import { fileURLToPath } from "node:url";
53
- import { homedir } from "node:os";
196
+ import { homedir as homedir2 } from "node:os";
54
197
  function loadDgrc() {
55
198
  const candidates = [
56
- join(process.cwd(), ".dgrc.json"),
57
- join(homedir(), ".dgrc.json")
199
+ join2(process.cwd(), ".dgrc.json"),
200
+ join2(homedir2(), ".dgrc.json")
58
201
  ];
59
202
  for (const filepath of candidates) {
60
- if (existsSync(filepath)) {
203
+ if (existsSync2(filepath)) {
61
204
  try {
62
- return JSON.parse(readFileSync(filepath, "utf-8"));
205
+ return JSON.parse(readFileSync2(filepath, "utf-8"));
63
206
  } catch {
64
207
  process.stderr.write(`Warning: Failed to parse ${filepath}, ignoring.
65
208
  `);
@@ -72,7 +215,7 @@ function getVersion() {
72
215
  try {
73
216
  const thisDir = dirname(fileURLToPath(import.meta.url));
74
217
  const pkg = JSON.parse(
75
- readFileSync(join(thisDir, "..", "package.json"), "utf-8")
218
+ readFileSync2(join2(thisDir, "..", "package.json"), "utf-8")
76
219
  );
77
220
  return pkg.version ?? "1.0.0";
78
221
  } catch {
@@ -113,13 +256,8 @@ function parseConfig(argv) {
113
256
  const command = positionals[0] ?? "scan";
114
257
  const noConfig = values["no-config"];
115
258
  const dgrc = noConfig ? {} : loadDgrc();
116
- const apiKey = dgrc.apiKey ?? "";
117
- if (!apiKey) {
118
- process.stderr.write(
119
- "Error: Not logged in. Run `dg login` to authenticate.\n"
120
- );
121
- process.exit(1);
122
- }
259
+ const apiKey = dgrc.apiKey && dgrc.apiKey.length > 0 ? dgrc.apiKey : null;
260
+ const deviceId = getOrCreateDeviceId();
123
261
  const modeRaw = values.mode ?? process.env.DG_MODE ?? dgrc.mode ?? "warn";
124
262
  if (!["block", "warn", "off"].includes(modeRaw)) {
125
263
  process.stderr.write(
@@ -147,6 +285,7 @@ function parseConfig(argv) {
147
285
  }
148
286
  return {
149
287
  apiKey,
288
+ deviceId,
150
289
  apiUrl: values["api-url"] ?? process.env.DG_API_URL ?? dgrc.apiUrl ?? "https://api.westbayberry.com",
151
290
  mode: modeRaw,
152
291
  blockThreshold,
@@ -165,6 +304,7 @@ var USAGE;
165
304
  var init_config = __esm({
166
305
  "src/config.ts"() {
167
306
  "use strict";
307
+ init_auth();
168
308
  USAGE = `
169
309
  Dependency Guardian \u2014 Supply chain security scanner
170
310
 
@@ -231,8 +371,8 @@ var init_config = __esm({
231
371
 
232
372
  // src/npm-wrapper.ts
233
373
  import { spawn } from "node:child_process";
234
- import { readFileSync as readFileSync2, existsSync as existsSync2 } from "node:fs";
235
- import { join as join2 } from "node:path";
374
+ import { readFileSync as readFileSync3, existsSync as existsSync3 } from "node:fs";
375
+ import { join as join3 } from "node:path";
236
376
  function parseNpmArgs(args) {
237
377
  let dgForce = false;
238
378
  const filtered = [];
@@ -378,10 +518,10 @@ function runNpm(args) {
378
518
  });
379
519
  }
380
520
  function readBareInstallPackages(cwd2) {
381
- const pkgPath = join2(cwd2, "package.json");
382
- if (!existsSync2(pkgPath)) return [];
521
+ const pkgPath = join3(cwd2, "package.json");
522
+ if (!existsSync3(pkgPath)) return [];
383
523
  try {
384
- const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
524
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
385
525
  const specs = [];
386
526
  for (const [name, range] of Object.entries(pkg.dependencies ?? {})) {
387
527
  if (typeof range === "string") specs.push(`${name}@${range}`);
@@ -27448,9 +27588,9 @@ var init_ansi_styles = __esm({
27448
27588
 
27449
27589
  // node_modules/wrap-ansi/index.js
27450
27590
  function wrapAnsi(string, columns, options) {
27451
- return String(string).normalize().replaceAll("\r\n", "\n").split("\n").map((line) => exec(line, columns, options)).join("\n");
27591
+ return String(string).normalize().replaceAll("\r\n", "\n").split("\n").map((line) => exec2(line, columns, options)).join("\n");
27452
27592
  }
27453
- var ESCAPES, END_CODE, ANSI_ESCAPE_BELL, ANSI_CSI, ANSI_OSC, ANSI_SGR_TERMINATOR, ANSI_ESCAPE_LINK, wrapAnsiCode, wrapAnsiHyperlink, wordLengths, wrapWord, stringVisibleTrimSpacesRight, exec;
27593
+ var ESCAPES, END_CODE, ANSI_ESCAPE_BELL, ANSI_CSI, ANSI_OSC, ANSI_SGR_TERMINATOR, ANSI_ESCAPE_LINK, wrapAnsiCode, wrapAnsiHyperlink, wordLengths, wrapWord, stringVisibleTrimSpacesRight, exec2;
27454
27594
  var init_wrap_ansi = __esm({
27455
27595
  "node_modules/wrap-ansi/index.js"() {
27456
27596
  init_string_width();
@@ -27522,7 +27662,7 @@ var init_wrap_ansi = __esm({
27522
27662
  }
27523
27663
  return words.slice(0, last).join(" ") + words.slice(last).join("");
27524
27664
  };
27525
- exec = (string, columns, options = {}) => {
27665
+ exec2 = (string, columns, options = {}) => {
27526
27666
  if (options.trim !== false && string.trim() === "") {
27527
27667
  return "";
27528
27668
  }
@@ -35955,135 +36095,6 @@ var init_build2 = __esm({
35955
36095
  }
35956
36096
  });
35957
36097
 
35958
- // src/auth.ts
35959
- var auth_exports = {};
35960
- __export(auth_exports, {
35961
- clearCredentials: () => clearCredentials,
35962
- createAuthSession: () => createAuthSession,
35963
- getStoredApiKey: () => getStoredApiKey,
35964
- openBrowser: () => openBrowser,
35965
- pollAuthSession: () => pollAuthSession,
35966
- saveCredentials: () => saveCredentials
35967
- });
35968
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, chmodSync, unlinkSync, existsSync as existsSync4 } from "node:fs";
35969
- import { join as join4 } from "node:path";
35970
- import { homedir as homedir3 } from "node:os";
35971
- import { exec as exec2 } from "node:child_process";
35972
- async function createAuthSession() {
35973
- let res;
35974
- try {
35975
- res = await globalThis.fetch(`${WEB_BASE}/cli/auth/sessions`, {
35976
- method: "POST",
35977
- headers: { "Content-Type": "application/json" }
35978
- });
35979
- } catch {
35980
- throw new Error("Could not connect to westbayberry.com");
35981
- }
35982
- if (!res.ok) {
35983
- const body = await res.text().catch(() => "");
35984
- throw new Error(
35985
- `Failed to create auth session (HTTP ${res.status})${body ? `: ${body}` : ""}`
35986
- );
35987
- }
35988
- const json = await res.json();
35989
- return {
35990
- sessionId: json.session_id,
35991
- verifyUrl: json.verify_url,
35992
- expiresIn: json.expires_in
35993
- };
35994
- }
35995
- async function pollAuthSession(sessionId) {
35996
- let res;
35997
- try {
35998
- res = await globalThis.fetch(
35999
- `${WEB_BASE}/cli/auth/sessions/${sessionId}/token`
36000
- );
36001
- } catch {
36002
- return { status: "expired" };
36003
- }
36004
- if (res.status === 404) {
36005
- return { status: "expired" };
36006
- }
36007
- if (!res.ok) {
36008
- return { status: "expired" };
36009
- }
36010
- const json = await res.json();
36011
- return {
36012
- status: json.status,
36013
- apiKey: json.api_key,
36014
- email: json.email
36015
- };
36016
- }
36017
- function configPath() {
36018
- return join4(homedir3(), CONFIG_FILE);
36019
- }
36020
- function readConfig() {
36021
- try {
36022
- const raw = readFileSync5(configPath(), "utf-8");
36023
- const parsed = JSON.parse(raw);
36024
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
36025
- return parsed;
36026
- }
36027
- return {};
36028
- } catch {
36029
- return {};
36030
- }
36031
- }
36032
- function saveCredentials(apiKey) {
36033
- const data = readConfig();
36034
- data.apiKey = apiKey;
36035
- const p = configPath();
36036
- writeFileSync2(p, JSON.stringify(data, null, 2) + "\n", "utf-8");
36037
- chmodSync(p, 384);
36038
- }
36039
- function clearCredentials() {
36040
- const p = configPath();
36041
- if (!existsSync4(p)) return;
36042
- const data = readConfig();
36043
- delete data.apiKey;
36044
- if (Object.keys(data).length === 0) {
36045
- unlinkSync(p);
36046
- } else {
36047
- writeFileSync2(p, JSON.stringify(data, null, 2) + "\n", "utf-8");
36048
- chmodSync(p, 384);
36049
- }
36050
- }
36051
- function getStoredApiKey() {
36052
- const data = readConfig();
36053
- if (typeof data.apiKey === "string" && data.apiKey.length > 0) {
36054
- return data.apiKey;
36055
- }
36056
- return null;
36057
- }
36058
- function openBrowser(url) {
36059
- if (!/^https?:\/\//i.test(url)) return;
36060
- const escaped = url.replace(/"/g, '\\"');
36061
- let cmd;
36062
- switch (process.platform) {
36063
- case "darwin":
36064
- cmd = `open "${escaped}"`;
36065
- break;
36066
- case "linux":
36067
- cmd = `xdg-open "${escaped}"`;
36068
- break;
36069
- case "win32":
36070
- cmd = `start "" "${escaped}"`;
36071
- break;
36072
- default:
36073
- return;
36074
- }
36075
- exec2(cmd, () => {
36076
- });
36077
- }
36078
- var WEB_BASE, CONFIG_FILE;
36079
- var init_auth = __esm({
36080
- "src/auth.ts"() {
36081
- "use strict";
36082
- WEB_BASE = "https://westbayberry.com";
36083
- CONFIG_FILE = ".dgrc.json";
36084
- }
36085
- });
36086
-
36087
36098
  // src/ui/hooks/useLogin.ts
36088
36099
  function reducer(state, action) {
36089
36100
  switch (action.type) {
@@ -38909,9 +38920,34 @@ var init_LoginApp = __esm({
38909
38920
  });
38910
38921
 
38911
38922
  // src/api.ts
38923
+ function buildHeaders(config) {
38924
+ const headers = {
38925
+ "Content-Type": "application/json",
38926
+ "User-Agent": "dependency-guardian-cli/1.0.0"
38927
+ };
38928
+ if (config.apiKey) {
38929
+ headers["Authorization"] = `Bearer ${config.apiKey}`;
38930
+ } else {
38931
+ headers["X-Device-Id"] = config.deviceId;
38932
+ }
38933
+ return headers;
38934
+ }
38935
+ async function handleTrialExhausted(response) {
38936
+ if (response.status === 403) {
38937
+ const body = await response.json().catch(() => ({}));
38938
+ if (body && body.trialExhausted) {
38939
+ const b = body;
38940
+ throw new TrialExhaustedError(b.scansUsed, b.maxScans);
38941
+ }
38942
+ throw new APIError("Forbidden", 403, JSON.stringify(body));
38943
+ }
38944
+ }
38912
38945
  async function callAnalyzeAPI(packages, config, onProgress) {
38913
38946
  if (packages.length <= BATCH_SIZE) {
38914
- return callAnalyzeBatch(packages, config);
38947
+ if (onProgress) onProgress(0, packages.length, packages.map((p) => p.name));
38948
+ const result = await callBatchWithRetry(packages, config);
38949
+ if (onProgress) onProgress(packages.length, packages.length, packages.map((p) => p.name));
38950
+ return result;
38915
38951
  }
38916
38952
  const batches = [];
38917
38953
  for (let i = 0; i < packages.length; i += BATCH_SIZE) {
@@ -38981,29 +39017,26 @@ async function callAnalyzeBatch(packages, config) {
38981
39017
  }
38982
39018
  };
38983
39019
  const controller = new AbortController();
38984
- const timeoutId = setTimeout(() => controller.abort(), 12e4);
39020
+ const timeoutId = setTimeout(() => controller.abort(), 18e4);
38985
39021
  let response;
38986
39022
  try {
38987
39023
  response = await fetch(url, {
38988
39024
  method: "POST",
38989
- headers: {
38990
- "Content-Type": "application/json",
38991
- Authorization: `Bearer ${config.apiKey}`,
38992
- "User-Agent": "dependency-guardian-cli/1.0.0"
38993
- },
39025
+ headers: buildHeaders(config),
38994
39026
  body: JSON.stringify(payload),
38995
39027
  signal: controller.signal
38996
39028
  });
38997
39029
  } catch (error) {
38998
39030
  clearTimeout(timeoutId);
38999
39031
  if (error instanceof Error && error.name === "AbortError") {
39000
- throw new APIError("Request timed out after 120s. Try scanning fewer packages.", 408, "");
39032
+ throw new APIError("Request timed out after 180s. Try scanning fewer packages.", 408, "");
39001
39033
  }
39002
39034
  const cause = error instanceof Error && error.cause;
39003
39035
  const detail = cause ? `: ${cause.message || cause}` : "";
39004
39036
  throw new Error(`fetch failed${detail}`);
39005
39037
  }
39006
39038
  clearTimeout(timeoutId);
39039
+ await handleTrialExhausted(response);
39007
39040
  if (response.status === 401) {
39008
39041
  throw new APIError(
39009
39042
  "Invalid API key. Run `dg logout` then `dg login` to re-authenticate.",
@@ -39061,16 +39094,12 @@ async function callPyPIBatch(packages, config) {
39061
39094
  }
39062
39095
  };
39063
39096
  const controller = new AbortController();
39064
- const timeoutId = setTimeout(() => controller.abort(), 12e4);
39097
+ const timeoutId = setTimeout(() => controller.abort(), 18e4);
39065
39098
  let response;
39066
39099
  try {
39067
39100
  response = await fetch(url, {
39068
39101
  method: "POST",
39069
- headers: {
39070
- "Content-Type": "application/json",
39071
- Authorization: `Bearer ${config.apiKey}`,
39072
- "User-Agent": "dependency-guardian-cli/1.0.0"
39073
- },
39102
+ headers: buildHeaders(config),
39074
39103
  body: JSON.stringify(payload),
39075
39104
  signal: controller.signal
39076
39105
  });
@@ -39084,6 +39113,7 @@ async function callPyPIBatch(packages, config) {
39084
39113
  throw new Error(`fetch failed${detail}`);
39085
39114
  }
39086
39115
  clearTimeout(timeoutId);
39116
+ await handleTrialExhausted(response);
39087
39117
  if (response.status === 401) {
39088
39118
  throw new APIError(
39089
39119
  "Invalid API key. Run `dg logout` then `dg login` to re-authenticate.",
@@ -39104,7 +39134,7 @@ async function callPyPIBatch(packages, config) {
39104
39134
  }
39105
39135
  return await response.json();
39106
39136
  }
39107
- var APIError, BATCH_SIZE, MAX_RETRIES, RETRY_DELAY_MS;
39137
+ var APIError, TrialExhaustedError, BATCH_SIZE, MAX_RETRIES, RETRY_DELAY_MS;
39108
39138
  var init_api = __esm({
39109
39139
  "src/api.ts"() {
39110
39140
  "use strict";
@@ -39116,7 +39146,15 @@ var init_api = __esm({
39116
39146
  this.name = "APIError";
39117
39147
  }
39118
39148
  };
39119
- BATCH_SIZE = 15;
39149
+ TrialExhaustedError = class extends Error {
39150
+ constructor(scansUsed, maxScans) {
39151
+ super("Free trial scans used up. Run `dg login` to create a free account and continue scanning.");
39152
+ this.scansUsed = scansUsed;
39153
+ this.maxScans = maxScans;
39154
+ this.name = "TrialExhaustedError";
39155
+ }
39156
+ };
39157
+ BATCH_SIZE = 75;
39120
39158
  MAX_RETRIES = 2;
39121
39159
  RETRY_DELAY_MS = 5e3;
39122
39160
  }
@@ -39733,6 +39771,31 @@ __export(static_output_exports, {
39733
39771
  runStaticLogin: () => runStaticLogin,
39734
39772
  runStaticNpm: () => runStaticNpm
39735
39773
  });
39774
+ function printTrialBanner(result) {
39775
+ if (result.trialScansRemaining === void 0) return;
39776
+ const remaining = result.trialScansRemaining;
39777
+ if (remaining > 0) {
39778
+ process.stderr.write(
39779
+ import_chalk4.default.dim(` ${remaining} free scan${remaining !== 1 ? "s" : ""} remaining. `) + import_chalk4.default.dim(`Run \`dg login\` for unlimited scans.
39780
+ `)
39781
+ );
39782
+ } else {
39783
+ process.stderr.write(
39784
+ import_chalk4.default.yellow(` No free scans remaining. `) + import_chalk4.default.yellow(`Run \`dg login\` to continue scanning.
39785
+ `)
39786
+ );
39787
+ }
39788
+ }
39789
+ function handleTrialExhausted2(error) {
39790
+ if (error instanceof TrialExhaustedError) {
39791
+ process.stderr.write(
39792
+ import_chalk4.default.yellow("\n Free trial scans used up.\n") + import_chalk4.default.white(" Run `dg login` to create a free account and continue scanning.\n\n")
39793
+ );
39794
+ process.exit(1);
39795
+ return true;
39796
+ }
39797
+ return false;
39798
+ }
39736
39799
  function severityColor(sev) {
39737
39800
  if (sev >= 5) return (s) => import_chalk4.default.bold.red(s);
39738
39801
  if (sev >= 4) return import_chalk4.default.red;
@@ -39934,16 +39997,23 @@ async function runStatic(config) {
39934
39997
  dbg(
39935
39998
  `packages to scan: ${packages.map((p) => `${p.name}@${p.version}`).slice(0, 10).join(", ")}${packages.length > 10 ? ` (+${packages.length - 10} more)` : ""}`
39936
39999
  );
39937
- const startMs = Date.now();
39938
- const result = await callAnalyzeAPI(packages, config, (done, total) => {
39939
- process.stderr.write(import_chalk4.default.dim(` Analyzed ${done}/${total}...
40000
+ let result;
40001
+ try {
40002
+ const startMs = Date.now();
40003
+ result = await callAnalyzeAPI(packages, config, (done, total) => {
40004
+ process.stderr.write(import_chalk4.default.dim(` Analyzed ${done}/${total}...
39940
40005
  `));
39941
- });
39942
- dbg(
39943
- `API responded in ${Date.now() - startMs}ms, action=${result.action}, score=${result.score}`
39944
- );
40006
+ });
40007
+ dbg(
40008
+ `API responded in ${Date.now() - startMs}ms, action=${result.action}, score=${result.score}`
40009
+ );
40010
+ } catch (error) {
40011
+ if (handleTrialExhausted2(error)) return;
40012
+ throw error;
40013
+ }
39945
40014
  const output = renderResultStatic(result, config);
39946
40015
  process.stdout.write(output + "\n");
40016
+ printTrialBanner(result);
39947
40017
  if (result.action === "block" && config.mode === "block") {
39948
40018
  process.exit(2);
39949
40019
  } else if (result.action === "block" || result.action === "warn") {
@@ -40038,6 +40108,7 @@ async function scanAndInstallStatic(resolved, parsed, config) {
40038
40108
  );
40039
40109
  }
40040
40110
  } catch (error) {
40111
+ if (handleTrialExhausted2(error)) return;
40041
40112
  const msg = error instanceof Error ? error.message : String(error);
40042
40113
  process.stderr.write(
40043
40114
  import_chalk4.default.yellow(
@@ -40053,15 +40124,17 @@ async function scanAndInstallStatic(resolved, parsed, config) {
40053
40124
  process.stderr.write(
40054
40125
  import_chalk4.default.green(
40055
40126
  ` ${import_chalk4.default.bold("\u2713")} ${toScan.length} package${toScan.length !== 1 ? "s" : ""} scanned \u2014 all clear
40056
-
40057
40127
  `
40058
40128
  )
40059
40129
  );
40130
+ printTrialBanner(result);
40131
+ process.stderr.write("\n");
40060
40132
  const code = await runNpm(parsed.rawArgs);
40061
40133
  process.exit(code);
40062
40134
  }
40063
40135
  const output = renderResultStatic(result, config);
40064
40136
  process.stdout.write(output + "\n");
40137
+ printTrialBanner(result);
40065
40138
  if (result.action === "warn") {
40066
40139
  process.stderr.write(
40067
40140
  import_chalk4.default.yellow(" Warnings detected. Proceeding with install.\n\n")
@@ -40380,6 +40453,8 @@ function reducer2(_state, action) {
40380
40453
  return { phase: "done", exitCode: action.exitCode };
40381
40454
  case "ERROR":
40382
40455
  return { phase: "error", message: action.message, proceed: action.proceed };
40456
+ case "TRIAL_EXHAUSTED":
40457
+ return { phase: "trial_exhausted" };
40383
40458
  }
40384
40459
  }
40385
40460
  function useNpmWrapper(npmArgs, config) {
@@ -40484,6 +40559,10 @@ function useNpmWrapper(npmArgs, config) {
40484
40559
  return;
40485
40560
  }
40486
40561
  } catch (error) {
40562
+ if (error instanceof TrialExhaustedError) {
40563
+ dispatch({ type: "TRIAL_EXHAUSTED" });
40564
+ return;
40565
+ }
40487
40566
  const message = error instanceof Error ? error.message : String(error);
40488
40567
  dispatch({ type: "ERROR", message, proceed: true });
40489
40568
  dispatch({ type: "INSTALLING" });
@@ -40858,6 +40937,11 @@ var init_NpmWrapperApp = __esm({
40858
40937
  const timer = setTimeout(() => exit(), 0);
40859
40938
  return () => clearTimeout(timer);
40860
40939
  }
40940
+ if (state.phase === "trial_exhausted") {
40941
+ process.exitCode = 1;
40942
+ const timer = setTimeout(() => exit(), 0);
40943
+ return () => clearTimeout(timer);
40944
+ }
40861
40945
  if (state.phase === "passthrough") {
40862
40946
  }
40863
40947
  }, [state, exit]);
@@ -40957,6 +41041,15 @@ var init_NpmWrapperApp = __esm({
40957
41041
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ErrorView, { error: new Error(state.message) });
40958
41042
  case "passthrough":
40959
41043
  return null;
41044
+ case "trial_exhausted":
41045
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
41046
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "yellow", bold: true, children: "Free trial scans used up." }),
41047
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { children: [
41048
+ "Run ",
41049
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
41050
+ " to create a free account and continue scanning."
41051
+ ] })
41052
+ ] });
40960
41053
  }
40961
41054
  };
40962
41055
  }
@@ -40979,6 +41072,8 @@ function reducer3(_state, action) {
40979
41072
  return { phase: "results", result: action.result, durationMs: action.durationMs, skippedCount: action.skippedCount };
40980
41073
  case "ERROR":
40981
41074
  return { phase: "error", error: action.error };
41075
+ case "TRIAL_EXHAUSTED":
41076
+ return { phase: "trial_exhausted", scansUsed: action.scansUsed, maxScans: action.maxScans };
40982
41077
  }
40983
41078
  }
40984
41079
  function useScan(config) {
@@ -41037,6 +41132,10 @@ async function runNpmScan(packages, skippedCount, config, dispatch) {
41037
41132
  });
41038
41133
  dispatch({ type: "SCAN_COMPLETE", result, durationMs: Date.now() - startMs, skippedCount });
41039
41134
  } catch (error) {
41135
+ if (error instanceof TrialExhaustedError) {
41136
+ dispatch({ type: "TRIAL_EXHAUSTED", scansUsed: error.scansUsed, maxScans: error.maxScans });
41137
+ return;
41138
+ }
41040
41139
  dispatch({ type: "ERROR", error: error instanceof Error ? error : new Error(String(error)) });
41041
41140
  }
41042
41141
  }
@@ -41044,21 +41143,35 @@ async function scanProjects(projects, config, dispatch) {
41044
41143
  try {
41045
41144
  const npmProjects = projects.filter((p) => p.ecosystem === "npm");
41046
41145
  const pypiProjects = projects.filter((p) => p.ecosystem === "pypi");
41146
+ const fullScanConfig = { ...config, scanAll: true };
41047
41147
  const npmPackages = [];
41048
41148
  const pypiPackages = [];
41149
+ const seenNpm = /* @__PURE__ */ new Set();
41150
+ const seenPypi = /* @__PURE__ */ new Set();
41049
41151
  for (const proj of npmProjects) {
41050
41152
  try {
41051
- const discovery = discoverChanges(proj.path, config);
41153
+ const discovery = discoverChanges(proj.path, fullScanConfig);
41052
41154
  for (const pkg of discovery.packages) {
41053
- if (!config.allowlist.includes(pkg.name)) npmPackages.push(pkg);
41155
+ const key = `${pkg.name}@${pkg.version}`;
41156
+ if (!config.allowlist.includes(pkg.name) && !seenNpm.has(key)) {
41157
+ seenNpm.add(key);
41158
+ npmPackages.push(pkg);
41159
+ }
41054
41160
  }
41055
41161
  } catch {
41056
41162
  }
41057
41163
  }
41058
41164
  for (const proj of pypiProjects) {
41059
- const packages = parsePythonDepFile(proj.path, proj.depFile);
41060
- for (const pkg of packages) {
41061
- if (!config.allowlist.includes(pkg.name)) pypiPackages.push(pkg);
41165
+ try {
41166
+ const packages = parsePythonDepFile(proj.path, proj.depFile);
41167
+ for (const pkg of packages) {
41168
+ const key = `${pkg.name}@${pkg.version}`;
41169
+ if (!config.allowlist.includes(pkg.name) && !seenPypi.has(key)) {
41170
+ seenPypi.add(key);
41171
+ pypiPackages.push(pkg);
41172
+ }
41173
+ }
41174
+ } catch {
41062
41175
  }
41063
41176
  }
41064
41177
  const totalPackages = npmPackages.length + pypiPackages.length;
@@ -41107,6 +41220,10 @@ async function scanProjects(projects, config, dispatch) {
41107
41220
  };
41108
41221
  dispatch({ type: "SCAN_COMPLETE", result: merged, durationMs: merged.durationMs, skippedCount: 0 });
41109
41222
  } catch (error) {
41223
+ if (error instanceof TrialExhaustedError) {
41224
+ dispatch({ type: "TRIAL_EXHAUSTED", scansUsed: error.scansUsed, maxScans: error.maxScans });
41225
+ return;
41226
+ }
41110
41227
  dispatch({ type: "ERROR", error: error instanceof Error ? error : new Error(String(error)) });
41111
41228
  }
41112
41229
  }
@@ -41799,10 +41916,8 @@ var init_App2 = __esm({
41799
41916
  (0, import_react32.useEffect)(() => {
41800
41917
  if (!process.stdout.isTTY) return;
41801
41918
  process.stdout.write("\x1B[?1049h");
41802
- process.stdout.write("\x1B[?1003h");
41803
41919
  process.stdout.write("\x1B[2J\x1B[H");
41804
41920
  return () => {
41805
- process.stdout.write("\x1B[?1003l");
41806
41921
  process.stdout.write("\x1B[?1049l");
41807
41922
  process.stdout.write("\x1B[?25h");
41808
41923
  };
@@ -41829,11 +41944,21 @@ var init_App2 = __esm({
41829
41944
  (0, import_react32.useEffect)(() => {
41830
41945
  if (state.phase === "empty") {
41831
41946
  process.exitCode = 0;
41947
+ process.stderr.write(`${state.message}
41948
+ `);
41832
41949
  const timer = setTimeout(() => exit(), 0);
41833
41950
  return () => clearTimeout(timer);
41834
41951
  }
41835
41952
  if (state.phase === "error") {
41836
41953
  process.exitCode = 3;
41954
+ process.stderr.write(`Error: ${state.error.message}
41955
+ `);
41956
+ const timer = setTimeout(() => exit(), 0);
41957
+ return () => clearTimeout(timer);
41958
+ }
41959
+ if (state.phase === "trial_exhausted") {
41960
+ process.exitCode = 1;
41961
+ process.stderr.write("Free trial scans used up. Run `dg login` to create a free account and continue scanning.\n");
41837
41962
  const timer = setTimeout(() => exit(), 0);
41838
41963
  return () => clearTimeout(timer);
41839
41964
  }
@@ -41887,6 +42012,15 @@ var init_App2 = __esm({
41887
42012
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { dimColor: true, children: state.message });
41888
42013
  case "error":
41889
42014
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ErrorView, { error: state.error });
42015
+ case "trial_exhausted":
42016
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", paddingLeft: 2, children: [
42017
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "yellow", bold: true, children: "Free trial scans used up." }),
42018
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { children: [
42019
+ "Run ",
42020
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: "cyan", bold: true, children: "dg login" }),
42021
+ " to create a free account and continue scanning."
42022
+ ] })
42023
+ ] });
41890
42024
  }
41891
42025
  })();
41892
42026
  return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { flexDirection: "column", height: termRows, children: content });
@@ -41899,16 +42033,16 @@ init_config();
41899
42033
  init_npm_wrapper();
41900
42034
 
41901
42035
  // src/update-check.ts
41902
- import { readFileSync as readFileSync3, writeFileSync } from "node:fs";
41903
- import { homedir as homedir2 } from "node:os";
41904
- import { join as join3 } from "node:path";
42036
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
42037
+ import { homedir as homedir3 } from "node:os";
42038
+ import { join as join4 } from "node:path";
41905
42039
  import { spawn as spawn2, execSync } from "node:child_process";
41906
42040
  var PKG_NAME = "@westbayberry/dg";
41907
- var CACHE_FILE = join3(homedir2(), ".dg-update-check.json");
42041
+ var CACHE_FILE = join4(homedir3(), ".dg-update-check.json");
41908
42042
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
41909
42043
  function readCache() {
41910
42044
  try {
41911
- const raw = readFileSync3(CACHE_FILE, "utf-8");
42045
+ const raw = readFileSync4(CACHE_FILE, "utf-8");
41912
42046
  const data = JSON.parse(raw);
41913
42047
  if (typeof data.latest === "string" && typeof data.checkedAt === "number") {
41914
42048
  return data;
@@ -41919,7 +42053,7 @@ function readCache() {
41919
42053
  }
41920
42054
  function writeCache(entry) {
41921
42055
  try {
41922
- writeFileSync(CACHE_FILE, JSON.stringify(entry), "utf-8");
42056
+ writeFileSync2(CACHE_FILE, JSON.stringify(entry), "utf-8");
41923
42057
  } catch {
41924
42058
  }
41925
42059
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@westbayberry/dg",
3
- "version": "1.0.17",
3
+ "version": "1.0.22",
4
4
  "description": "Supply chain security scanner for npm and Python dependencies — detects malicious packages, typosquatting, dependency confusion, and 26+ attack patterns",
5
5
  "bin": {
6
6
  "dependency-guardian": "dist/index.mjs",