@yabasha/gex 1.0.1 → 1.3.0

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/dist/cli.mjs CHANGED
@@ -6,36 +6,56 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
6
6
  throw Error('Dynamic require of "' + x + '" is not supported');
7
7
  });
8
8
 
9
- // src/cli/commands.ts
9
+ // src/runtimes/node/commands.ts
10
10
  import path6 from "path";
11
11
  import { Command } from "commander";
12
12
 
13
- // src/cli/install.ts
13
+ // src/shared/cli/install.ts
14
+ var INSTALL_COMMANDS = {
15
+ npm: {
16
+ global: ["i", "-g"],
17
+ local: ["i"],
18
+ dev: ["i", "-D"]
19
+ },
20
+ bun: {
21
+ global: ["add", "-g"],
22
+ local: ["add"],
23
+ dev: ["add", "-d"]
24
+ }
25
+ };
26
+ var MAX_BUFFER = 10 * 1024 * 1024;
27
+ function formatSpec(pkg) {
28
+ return pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
29
+ }
14
30
  async function getExecFileAsync() {
15
31
  const { execFile } = await import("child_process");
16
- const { promisify } = await import("util");
17
- return promisify(execFile);
32
+ const { promisify: promisify2 } = await import("util");
33
+ return promisify2(execFile);
18
34
  }
19
- async function installFromReport(report, cwd) {
20
- const globalPkgs = report.global_packages.map((p) => `${p.name}@${p.version}`).filter(Boolean);
21
- const localPkgs = report.local_dependencies.map((p) => `${p.name}@${p.version}`).filter(Boolean);
22
- const devPkgs = report.local_dev_dependencies.map((p) => `${p.name}@${p.version}`).filter(Boolean);
35
+ async function installFromReport(report, options) {
36
+ const opts = typeof options === "string" ? { cwd: options } : options;
37
+ const { cwd, packageManager = "npm" } = opts;
38
+ const globalPkgs = report.global_packages.map(formatSpec).filter(Boolean);
39
+ const localPkgs = report.local_dependencies.map(formatSpec).filter(Boolean);
40
+ const devPkgs = report.local_dev_dependencies.map(formatSpec).filter(Boolean);
23
41
  if (globalPkgs.length === 0 && localPkgs.length === 0 && devPkgs.length === 0) {
24
42
  console.log("No packages to install from report.");
25
43
  return;
26
44
  }
27
45
  const execFileAsync = await getExecFileAsync();
46
+ const cmd = INSTALL_COMMANDS[packageManager];
47
+ const binary = packageManager === "bun" ? "bun" : "npm";
28
48
  if (globalPkgs.length > 0) {
29
49
  console.log(`Installing global: ${globalPkgs.join(" ")}`);
30
- await execFileAsync("npm", ["i", "-g", ...globalPkgs], { cwd, maxBuffer: 10 * 1024 * 1024 });
50
+ await execFileAsync(binary, [...cmd.global, ...globalPkgs], { cwd, maxBuffer: MAX_BUFFER });
31
51
  }
32
52
  if (localPkgs.length > 0) {
33
53
  console.log(`Installing local deps: ${localPkgs.join(" ")}`);
34
- await execFileAsync("npm", ["i", ...localPkgs], { cwd, maxBuffer: 10 * 1024 * 1024 });
54
+ await execFileAsync(binary, [...cmd.local, ...localPkgs], { cwd, maxBuffer: MAX_BUFFER });
35
55
  }
36
56
  if (devPkgs.length > 0) {
37
57
  console.log(`Installing local devDeps: ${devPkgs.join(" ")}`);
38
- await execFileAsync("npm", ["i", "-D", ...devPkgs], { cwd, maxBuffer: 10 * 1024 * 1024 });
58
+ await execFileAsync(binary, [...cmd.dev, ...devPkgs], { cwd, maxBuffer: MAX_BUFFER });
39
59
  }
40
60
  }
41
61
  function printFromReport(report) {
@@ -66,10 +86,10 @@ function printFromReport(report) {
66
86
  console.log(lines.join("\n"));
67
87
  }
68
88
 
69
- // src/cli/output.ts
89
+ // src/shared/cli/output.ts
70
90
  import path from "path";
71
91
 
72
- // src/report/json.ts
92
+ // src/shared/report/json.ts
73
93
  function renderJson(report) {
74
94
  const r = {
75
95
  ...report,
@@ -82,7 +102,7 @@ function renderJson(report) {
82
102
  return JSON.stringify(r, null, 2);
83
103
  }
84
104
 
85
- // src/report/md.ts
105
+ // src/shared/report/md.ts
86
106
  function table(headers, rows) {
87
107
  const header = `| ${headers.join(" | ")} |`;
88
108
  const sep = `| ${headers.map(() => "---").join(" | ")} |`;
@@ -135,211 +155,9 @@ function renderMarkdown(report) {
135
155
  return lines.join("\n");
136
156
  }
137
157
 
138
- // src/report/html.ts
139
- function table2(headers, rows) {
140
- const headerHtml = `<tr>${headers.map((h) => `<th>${h}</th>`).join("")}</tr>`;
141
- const bodyHtml = rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
142
- return `<table><thead>${headerHtml}</thead><tbody>${bodyHtml}</tbody></table>`;
143
- }
144
- function escapeHtml(text) {
145
- const htmlEntities = {
146
- "&": "&amp;",
147
- "<": "&lt;",
148
- ">": "&gt;",
149
- '"': "&quot;",
150
- "'": "&#39;"
151
- };
152
- return text.replace(/[&<>"']/g, (char) => htmlEntities[char]);
153
- }
154
- function renderHtml(report) {
155
- const hasProjectMeta = report.project_name || report.project_version || report.project_description || report.project_homepage || report.project_bugs;
156
- return `<!DOCTYPE html>
157
- <html lang="en">
158
- <head>
159
- <meta charset="UTF-8">
160
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
161
- <title>GEX Report - ${report.project_name || "Dependency Audit"}</title>
162
- <style>
163
- body {
164
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
165
- line-height: 1.6;
166
- color: #333;
167
- max-width: 1200px;
168
- margin: 0 auto;
169
- padding: 20px;
170
- background-color: #f5f5f5;
171
- }
172
- .container {
173
- background: white;
174
- border-radius: 8px;
175
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
176
- padding: 30px;
177
- }
178
- h1 {
179
- color: #2c3e50;
180
- border-bottom: 3px solid #3498db;
181
- padding-bottom: 10px;
182
- margin-top: 0;
183
- }
184
- h2 {
185
- color: #34495e;
186
- border-bottom: 2px solid #bdc3c7;
187
- padding-bottom: 8px;
188
- margin-top: 40px;
189
- margin-bottom: 20px;
190
- }
191
- .metadata {
192
- background: #ecf0f1;
193
- border-radius: 6px;
194
- padding: 20px;
195
- margin-bottom: 20px;
196
- }
197
- .metadata dl {
198
- margin: 0;
199
- }
200
- .metadata dt {
201
- font-weight: bold;
202
- color: #2c3e50;
203
- margin-top: 10px;
204
- }
205
- .metadata dt:first-child {
206
- margin-top: 0;
207
- }
208
- .metadata dd {
209
- margin: 5px 0 0 20px;
210
- color: #555;
211
- }
212
- .metadata dd a {
213
- color: #3498db;
214
- text-decoration: none;
215
- }
216
- .metadata dd a:hover {
217
- text-decoration: underline;
218
- }
219
- table {
220
- width: 100%;
221
- border-collapse: collapse;
222
- margin-bottom: 30px;
223
- background: white;
224
- border-radius: 6px;
225
- overflow: hidden;
226
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
227
- }
228
- th {
229
- background: #3498db;
230
- color: white;
231
- padding: 12px;
232
- text-align: left;
233
- font-weight: 600;
234
- }
235
- td {
236
- padding: 12px;
237
- border-bottom: 1px solid #ecf0f1;
238
- font-family: 'SFMono-Regular', Consolas, monospace;
239
- font-size: 14px;
240
- }
241
- tr:nth-child(even) {
242
- background: #f8f9fa;
243
- }
244
- tr:hover {
245
- background: #e8f4fd;
246
- }
247
- .footer {
248
- text-align: center;
249
- color: #7f8c8d;
250
- font-size: 14px;
251
- margin-top: 40px;
252
- padding-top: 20px;
253
- border-top: 1px solid #ecf0f1;
254
- }
255
- .footer strong {
256
- color: #2c3e50;
257
- }
258
- .no-data {
259
- text-align: center;
260
- color: #7f8c8d;
261
- font-style: italic;
262
- padding: 40px;
263
- }
264
- .timestamp {
265
- color: #95a5a6;
266
- font-size: 14px;
267
- margin-top: 10px;
268
- }
269
- </style>
270
- </head>
271
- <body>
272
- <div class="container">
273
- <h1>GEX Dependency Report</h1>
274
-
275
- ${hasProjectMeta ? `
276
- <section class="metadata">
277
- <h2>Project Information</h2>
278
- <dl>
279
- ${report.project_name ? `<dt>Project Name</dt><dd>${escapeHtml(report.project_name)}</dd>` : ""}
280
- ${report.project_version ? `<dt>Version</dt><dd>${escapeHtml(report.project_version)}</dd>` : ""}
281
- ${report.project_description ? `<dt>Description</dt><dd>${escapeHtml(report.project_description)}</dd>` : ""}
282
- ${report.project_homepage ? `<dt>Homepage</dt><dd><a href="${escapeHtml(report.project_homepage)}" target="_blank">${escapeHtml(report.project_homepage)}</a></dd>` : ""}
283
- ${report.project_bugs ? `<dt>Bugs</dt><dd><a href="${escapeHtml(report.project_bugs)}" target="_blank">${escapeHtml(report.project_bugs)}</a></dd>` : ""}
284
- <dt>Report Generated</dt><dd>${new Date(report.timestamp).toLocaleString()}</dd>
285
- </dl>
286
- </section>
287
- ` : ""}
288
-
289
- ${report.global_packages.length > 0 ? `
290
- <section>
291
- <h2>Global Packages <small>(${report.global_packages.length})</small></h2>
292
- ${table2(
293
- ["Name", "Version", "Path"],
294
- report.global_packages.map((p) => [
295
- escapeHtml(p.name),
296
- escapeHtml(p.version || ""),
297
- escapeHtml(p.resolved_path || "")
298
- ])
299
- )}
300
- </section>
301
- ` : '<section><h2>Global Packages</h2><div class="no-data">No global packages found</div></section>'}
302
-
303
- ${report.local_dependencies.length > 0 ? `
304
- <section>
305
- <h2>Local Dependencies <small>(${report.local_dependencies.length})</small></h2>
306
- ${table2(
307
- ["Name", "Version", "Path"],
308
- report.local_dependencies.map((p) => [
309
- escapeHtml(p.name),
310
- escapeHtml(p.version || ""),
311
- escapeHtml(p.resolved_path || "")
312
- ])
313
- )}
314
- </section>
315
- ` : '<section><h2>Local Dependencies</h2><div class="no-data">No local dependencies found</div></section>'}
316
-
317
- ${report.local_dev_dependencies.length > 0 ? `
318
- <section>
319
- <h2>Local Dev Dependencies <small>(${report.local_dev_dependencies.length})</small></h2>
320
- ${table2(
321
- ["Name", "Version", "Path"],
322
- report.local_dev_dependencies.map((p) => [
323
- escapeHtml(p.name),
324
- escapeHtml(p.version || ""),
325
- escapeHtml(p.resolved_path || "")
326
- ])
327
- )}
328
- </section>
329
- ` : '<section><h2>Local Dev Dependencies</h2><div class="no-data">No local dev dependencies found</div></section>'}
330
-
331
- <footer class="footer">
332
- <p>Generated by <strong>GEX v${escapeHtml(report.tool_version)}</strong> on ${new Date(report.timestamp).toLocaleString()}</p>
333
- <p>Report format version: ${escapeHtml(report.report_version)}</p>
334
- </footer>
335
- </div>
336
- </body>
337
- </html>`;
338
- }
339
-
340
- // src/cli/output.ts
158
+ // src/shared/cli/output.ts
341
159
  async function outputReport(report, format, outFile, markdownExtras) {
342
- const content = format === "json" ? renderJson(report) : format === "html" ? renderHtml({ ...report, ...markdownExtras || {} }) : renderMarkdown({ ...report, ...markdownExtras || {} });
160
+ const content = format === "json" ? renderJson(report) : renderMarkdown({ ...report, ...markdownExtras || {} });
343
161
  if (outFile) {
344
162
  const outDir = path.dirname(outFile);
345
163
  const { mkdir, writeFile } = await import("fs/promises");
@@ -351,7 +169,7 @@ async function outputReport(report, format, outFile, markdownExtras) {
351
169
  }
352
170
  }
353
171
 
354
- // src/cli/parser.ts
172
+ // src/shared/cli/parser.ts
355
173
  import { readFile } from "fs/promises";
356
174
  import path2 from "path";
357
175
  function isMarkdownReportFile(filePath) {
@@ -400,60 +218,227 @@ async function loadReportFromFile(reportPath) {
400
218
  return JSON.parse(raw);
401
219
  }
402
220
 
403
- // src/cli/report.ts
404
- import { readFile as readFile4 } from "fs/promises";
405
- import path5 from "path";
406
-
407
- // src/npm.ts
221
+ // src/shared/npm-cli.ts
222
+ import { promisify } from "util";
408
223
  async function getExecFileAsync2() {
409
224
  const { execFile } = await import("child_process");
410
- const { promisify } = await import("util");
411
225
  return promisify(execFile);
412
226
  }
413
- async function npmLs(options = {}) {
414
- const args = ["ls", "--json"];
227
+ async function npmOutdated(options = {}) {
228
+ const args = ["outdated", "--json"];
415
229
  if (options.global) args.push("--global");
416
- if (options.omitDev) args.push("--omit=dev");
417
- if (options.depth0) args.push("--depth=0");
418
230
  try {
419
231
  const execFileAsync = await getExecFileAsync2();
420
232
  const { stdout } = await execFileAsync("npm", args, {
421
233
  cwd: options.cwd,
422
234
  maxBuffer: 10 * 1024 * 1024
423
235
  });
424
- if (stdout && stdout.trim()) return JSON.parse(stdout);
425
- return {};
426
- } catch (err) {
427
- const stdout = err?.stdout;
428
- if (typeof stdout === "string" && stdout.trim()) {
429
- try {
430
- return JSON.parse(stdout);
431
- } catch (parseErr) {
432
- if (process.env.DEBUG?.includes("gex")) {
433
- console.warn("npm ls stdout parse failed:", parseErr);
434
- }
435
- }
236
+ return normalizeOutdated(stdout);
237
+ } catch (error) {
238
+ const stdout = typeof error?.stdout === "string" ? error.stdout : "";
239
+ if (stdout.trim()) {
240
+ return normalizeOutdated(stdout);
436
241
  }
437
- const stderr = err?.stderr;
438
- const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm ls failed";
439
- throw new Error(`npm ls failed: ${msg}`);
242
+ throw formatNpmError(error, "npm outdated");
440
243
  }
441
244
  }
442
- async function npmRootGlobal() {
245
+ async function npmUpdate(options) {
246
+ const args = ["update"];
247
+ if (options.global) args.push("-g");
248
+ if (options.packages && options.packages.length > 0) args.push(...options.packages);
443
249
  try {
444
250
  const execFileAsync = await getExecFileAsync2();
445
- const { stdout } = await execFileAsync("npm", ["root", "-g"]);
446
- return stdout.trim();
447
- } catch (err) {
448
- const stderr = err?.stderr;
449
- const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm root -g failed";
450
- throw new Error(`npm root -g failed: ${msg}`);
251
+ await execFileAsync("npm", args, {
252
+ cwd: options.cwd,
253
+ maxBuffer: 10 * 1024 * 1024
254
+ });
255
+ } catch (error) {
256
+ throw formatNpmError(error, "npm update");
257
+ }
258
+ }
259
+ function normalizeOutdated(stdout) {
260
+ if (!stdout.trim()) return [];
261
+ let data;
262
+ try {
263
+ data = JSON.parse(stdout);
264
+ } catch {
265
+ return [];
451
266
  }
267
+ if (!data) return [];
268
+ return Object.entries(data).map(([name, info]) => ({
269
+ name,
270
+ current: info?.current ? String(info.current) : "",
271
+ wanted: info?.wanted ? String(info.wanted) : "",
272
+ latest: info?.latest ? String(info.latest) : "",
273
+ type: info?.type ? String(info.type) : void 0
274
+ }));
275
+ }
276
+ function formatNpmError(error, commandLabel) {
277
+ const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
278
+ const message = stderr || error?.message || `${commandLabel} failed`;
279
+ return new Error(`${commandLabel} failed: ${message}`);
452
280
  }
453
281
 
454
- // src/transform.ts
455
- import path3 from "path";
282
+ // src/shared/cli/loader.ts
283
+ var frames = ["-", "\\", "|", "/"];
284
+ function createLoader(message) {
285
+ if (!process.stdout.isTTY) {
286
+ console.log(`${message}...`);
287
+ return {
288
+ stop(finalMessage) {
289
+ if (finalMessage) console.log(finalMessage);
290
+ }
291
+ };
292
+ }
293
+ let index = 0;
294
+ const interval = setInterval(() => {
295
+ const frame = frames[index % frames.length];
296
+ index += 1;
297
+ process.stdout.write(`\r${message} ${frame}`);
298
+ }, 80);
299
+ return {
300
+ stop(finalMessage) {
301
+ clearInterval(interval);
302
+ process.stdout.write("\r");
303
+ if (finalMessage) {
304
+ console.log(finalMessage);
305
+ } else {
306
+ process.stdout.write("\x1B[2K");
307
+ }
308
+ }
309
+ };
310
+ }
311
+
312
+ // src/shared/cli/outdated.ts
313
+ function normalizeUpdateSelection(value) {
314
+ if (value === void 0) {
315
+ return { shouldUpdate: false, updateAll: false, packages: [] };
316
+ }
317
+ if (value === true) {
318
+ return { shouldUpdate: true, updateAll: true, packages: [] };
319
+ }
320
+ const packages = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
321
+ const normalized = packages.flatMap((entry) => String(entry).split(",").map((part) => part.trim())).filter(Boolean);
322
+ return {
323
+ shouldUpdate: true,
324
+ updateAll: false,
325
+ packages: normalized
326
+ };
327
+ }
328
+ function formatOutdatedTable(entries) {
329
+ const headers = ["Name", "Current", "Wanted", "Latest", "Type"];
330
+ const rows = entries.map((entry) => [
331
+ entry.name,
332
+ entry.current || "-",
333
+ entry.wanted || "-",
334
+ entry.latest || "-",
335
+ entry.type || "-"
336
+ ]);
337
+ const widths = headers.map(
338
+ (header, index) => Math.max(header.length, ...rows.map((row) => row[index].length))
339
+ );
340
+ const formatRow = (columns) => columns.map((col, idx) => col.padEnd(widths[idx], " ")).join(" ");
341
+ const lines = [formatRow(headers), formatRow(widths.map((w) => "-".repeat(w)))];
342
+ for (const row of rows) {
343
+ lines.push(formatRow(row));
344
+ }
345
+ return lines.join("\n");
346
+ }
347
+ async function handleOutdatedWorkflow(opts) {
348
+ if (!opts.checkOutdated && !opts.selection.shouldUpdate) {
349
+ return true;
350
+ }
351
+ let fetchLoader;
352
+ if (opts.checkOutdated || opts.selection.shouldUpdate) {
353
+ fetchLoader = createLoader("Checking for outdated packages");
354
+ }
355
+ const outdated = await opts.fetchOutdated();
356
+ fetchLoader?.stop("Finished checking outdated packages.");
357
+ if (opts.checkOutdated) {
358
+ if (outdated.length === 0) {
359
+ console.log(`All ${opts.contextLabel} packages are up to date.`);
360
+ } else {
361
+ console.log(formatOutdatedTable(outdated));
362
+ }
363
+ }
364
+ if (opts.selection.shouldUpdate && opts.updateRunner) {
365
+ const packagesToUpdate = opts.selection.updateAll ? outdated.map((entry) => entry.name) : opts.selection.packages;
366
+ if (!packagesToUpdate || packagesToUpdate.length === 0) {
367
+ if (opts.selection.updateAll) {
368
+ console.log("No outdated packages to update.");
369
+ } else {
370
+ console.log("No packages were specified for updating.");
371
+ }
372
+ } else {
373
+ const updateLoader = createLoader("Updating packages");
374
+ await opts.updateRunner(packagesToUpdate);
375
+ updateLoader.stop("Finished updating packages.");
376
+ }
377
+ }
378
+ if (opts.checkOutdated || opts.selection.shouldUpdate) {
379
+ if (!opts.outFile) {
380
+ return false;
381
+ }
382
+ }
383
+ return true;
384
+ }
385
+
386
+ // src/shared/cli/utils.ts
387
+ import { existsSync } from "fs";
456
388
  import { readFile as readFile2 } from "fs/promises";
389
+ import path3 from "path";
390
+ import { fileURLToPath } from "url";
391
+ function getPkgJsonPath() {
392
+ let startDir;
393
+ try {
394
+ const __filename = fileURLToPath(import.meta.url);
395
+ startDir = path3.dirname(__filename);
396
+ } catch {
397
+ startDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
398
+ }
399
+ return findPackageJson(startDir);
400
+ }
401
+ function findPackageJson(startDir) {
402
+ let current = startDir;
403
+ const maxDepth = 6;
404
+ for (let i = 0; i < maxDepth; i++) {
405
+ const candidate = path3.resolve(current, "package.json");
406
+ if (existsSync(candidate)) {
407
+ return candidate;
408
+ }
409
+ const parent = path3.dirname(current);
410
+ if (parent === current) break;
411
+ current = parent;
412
+ }
413
+ return path3.resolve(process.cwd(), "package.json");
414
+ }
415
+ async function getToolVersion() {
416
+ try {
417
+ const pkgPath = getPkgJsonPath();
418
+ const raw = await readFile2(pkgPath, "utf8");
419
+ const pkg = JSON.parse(raw);
420
+ return pkg.version || "0.0.0";
421
+ } catch {
422
+ return "0.0.0";
423
+ }
424
+ }
425
+ var ASCII_BANNER = String.raw`
426
+ ________ __
427
+ / _____/ ____ _____/ |_ ____ ____
428
+ / \ ___ / _ \ / _ \ __\/ __ \ / \
429
+ \ \_\ ( <_> | <_> ) | \ ___/| | \
430
+ \______ /\____/ \____/|__| \___ >___| /
431
+ \/ \/ \/
432
+ GEX
433
+ `;
434
+
435
+ // src/runtimes/node/report.ts
436
+ import { readFile as readFile4 } from "fs/promises";
437
+ import path5 from "path";
438
+
439
+ // src/shared/transform.ts
440
+ import path4 from "path";
441
+ import { readFile as readFile3 } from "fs/promises";
457
442
  function toPkgArray(obj) {
458
443
  if (!obj) return [];
459
444
  return Object.keys(obj).map((name) => ({ name, node: obj[name] })).filter((p) => p && p.node);
@@ -471,21 +456,30 @@ async function buildReportFromNpmTree(tree, opts) {
471
456
  if (opts.context === "local") {
472
457
  let pkgMeta = null;
473
458
  try {
474
- const pkgJsonPath = path3.join(opts.cwd || process.cwd(), "package.json");
475
- const raw = await readFile2(pkgJsonPath, "utf8");
459
+ const pkgJsonPath = path4.join(opts.cwd || process.cwd(), "package.json");
460
+ const raw = await readFile3(pkgJsonPath, "utf8");
476
461
  pkgMeta = JSON.parse(raw);
477
462
  } catch {
478
463
  }
479
464
  if (pkgMeta?.name) report.project_name = pkgMeta.name;
480
465
  if (pkgMeta?.version) report.project_version = pkgMeta.version;
481
466
  const depsObj = tree?.dependencies;
482
- const items = toPkgArray(depsObj);
483
- const devKeys = new Set(Object.keys(pkgMeta?.devDependencies || {}));
484
- for (const { name, node } of items) {
467
+ const devDepsObj = tree?.devDependencies;
468
+ const prodItems = toPkgArray(depsObj);
469
+ const treeDevItems = toPkgArray(devDepsObj);
470
+ if (treeDevItems.length > 0) {
471
+ for (const { name, node } of treeDevItems) {
472
+ const version = node && node.version || "";
473
+ const resolvedPath = node && node.path || path4.join(opts.cwd || process.cwd(), "node_modules", name);
474
+ report.local_dev_dependencies.push({ name, version, resolved_path: resolvedPath });
475
+ }
476
+ }
477
+ const devKeys = treeDevItems.length > 0 ? new Set(treeDevItems.map((entry) => entry.name)) : new Set(Object.keys(pkgMeta?.devDependencies || {}));
478
+ for (const { name, node } of prodItems) {
485
479
  const version = node && node.version || "";
486
- const resolvedPath = node && node.path || path3.join(opts.cwd || process.cwd(), "node_modules", name);
480
+ const resolvedPath = node && node.path || path4.join(opts.cwd || process.cwd(), "node_modules", name);
487
481
  const pkg = { name, version, resolved_path: resolvedPath };
488
- if (devKeys.has(name)) {
482
+ if (!treeDevItems.length && devKeys.has(name)) {
489
483
  report.local_dev_dependencies.push(pkg);
490
484
  } else {
491
485
  report.local_dependencies.push(pkg);
@@ -498,7 +492,7 @@ async function buildReportFromNpmTree(tree, opts) {
498
492
  const items = toPkgArray(depsObj);
499
493
  for (const { name, node } of items) {
500
494
  const version = node && node.version || "";
501
- const resolvedPath = node && node.path || path3.join(opts.globalRoot || "", name);
495
+ const resolvedPath = node && node.path || path4.join(opts.globalRoot || "", name);
502
496
  const pkg = { name, version, resolved_path: resolvedPath };
503
497
  report.global_packages.push(pkg);
504
498
  }
@@ -510,41 +504,54 @@ async function buildReportFromNpmTree(tree, opts) {
510
504
  return report;
511
505
  }
512
506
 
513
- // src/cli/utils.ts
514
- import { readFile as readFile3 } from "fs/promises";
515
- import path4 from "path";
516
- import { fileURLToPath } from "url";
517
- function getPkgJsonPath() {
507
+ // src/runtimes/node/package-manager.ts
508
+ async function getExecFileAsync3() {
509
+ const { execFile } = await import("child_process");
510
+ const { promisify: promisify2 } = await import("util");
511
+ return promisify2(execFile);
512
+ }
513
+ async function npmLs(options = {}) {
514
+ const args = ["ls", "--json"];
515
+ if (options.global) args.push("--global");
516
+ if (options.omitDev) args.push("--omit=dev");
517
+ if (options.depth0) args.push("--depth=0");
518
518
  try {
519
- const __filename = fileURLToPath(import.meta.url);
520
- const __dirnameLocal = path4.dirname(__filename);
521
- return path4.resolve(__dirnameLocal, "..", "..", "package.json");
522
- } catch {
523
- const dir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
524
- return path4.resolve(dir, "..", "package.json");
519
+ const execFileAsync = await getExecFileAsync3();
520
+ const { stdout } = await execFileAsync("npm", args, {
521
+ cwd: options.cwd,
522
+ maxBuffer: 10 * 1024 * 1024
523
+ });
524
+ if (stdout && stdout.trim()) return JSON.parse(stdout);
525
+ return {};
526
+ } catch (err) {
527
+ const stdout = err?.stdout;
528
+ if (typeof stdout === "string" && stdout.trim()) {
529
+ try {
530
+ return JSON.parse(stdout);
531
+ } catch (parseErr) {
532
+ if (process.env.DEBUG?.includes("gex")) {
533
+ console.warn("npm ls stdout parse failed:", parseErr);
534
+ }
535
+ }
536
+ }
537
+ const stderr = err?.stderr;
538
+ const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm ls failed";
539
+ throw new Error(`npm ls failed: ${msg}`);
525
540
  }
526
541
  }
527
- async function getToolVersion() {
542
+ async function npmRootGlobal() {
528
543
  try {
529
- const pkgPath = getPkgJsonPath();
530
- const raw = await readFile3(pkgPath, "utf8");
531
- const pkg = JSON.parse(raw);
532
- return pkg.version || "0.0.0";
533
- } catch {
534
- return "0.0.0";
544
+ const execFileAsync = await getExecFileAsync3();
545
+ const { stdout } = await execFileAsync("npm", ["root", "-g"]);
546
+ return stdout.trim();
547
+ } catch (err) {
548
+ const stderr = err?.stderr;
549
+ const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm root -g failed";
550
+ throw new Error(`npm root -g failed: ${msg}`);
535
551
  }
536
552
  }
537
- var ASCII_BANNER = String.raw`
538
- ________ __
539
- / _____/ ____ _____/ |_ ____ ____
540
- / \ ___ / _ \ / _ \ __\/ __ \ / \
541
- \ \_\ ( <_> | <_> ) | \ ___/| | \
542
- \______ /\____/ \____/|__| \___ >___| /
543
- \/ \/ \/
544
- GEX
545
- `;
546
553
 
547
- // src/cli/report.ts
554
+ // src/runtimes/node/report.ts
548
555
  async function produceReport(ctx, options) {
549
556
  const toolVersion = await getToolVersion();
550
557
  const depth0 = !options.fullTree;
@@ -582,18 +589,17 @@ async function produceReport(ctx, options) {
582
589
  return { report, markdownExtras };
583
590
  }
584
591
 
585
- // src/cli/commands.ts
592
+ // src/runtimes/node/commands.ts
586
593
  function addCommonOptions(cmd, { allowOmitDev }) {
587
594
  cmd.option(
588
595
  "-f, --output-format <format>",
589
- "Output format: json, md, or html",
590
- (val) => {
591
- if (val === "md") return "md";
592
- if (val === "html") return "html";
593
- return "json";
594
- },
596
+ "Output format: md or json",
597
+ (val) => val === "md" ? "md" : "json",
595
598
  "json"
596
- ).option("-o, --out-file <path>", "Write report to file").option("--full-tree", "Include full npm ls tree (omit depth=0 default)", false);
599
+ ).option("-o, --out-file <path>", "Write report to file").option("--full-tree", "Include full npm ls tree (omit depth=0 default)", false).option("-c, --check-outdated", "List outdated packages instead of printing the report", false).option(
600
+ "-u, --update-outdated [packages...]",
601
+ "Update outdated packages (omit package names to update every package)"
602
+ );
597
603
  if (allowOmitDev) {
598
604
  cmd.option("--omit-dev", "Exclude devDependencies (local only)", false);
599
605
  }
@@ -607,6 +613,19 @@ function createLocalCommand(program) {
607
613
  const outFile = opts.outFile;
608
614
  const fullTree = Boolean(opts.fullTree);
609
615
  const omitDev = Boolean(opts.omitDev);
616
+ const cwd = process.cwd();
617
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
618
+ const proceed = await handleOutdatedWorkflow({
619
+ checkOutdated: Boolean(opts.checkOutdated),
620
+ selection,
621
+ contextLabel: "local",
622
+ outFile,
623
+ fetchOutdated: () => npmOutdated({ cwd }),
624
+ updateRunner: selection.shouldUpdate ? async (packages) => {
625
+ await npmUpdate({ cwd, packages });
626
+ } : void 0
627
+ });
628
+ if (!proceed) return;
610
629
  const finalOutFile = outFile;
611
630
  const { report, markdownExtras } = await produceReport("local", {
612
631
  outputFormat,
@@ -625,6 +644,19 @@ function createGlobalCommand(program) {
625
644
  const outputFormat = opts.outputFormat ?? "json";
626
645
  const outFile = opts.outFile;
627
646
  const fullTree = Boolean(opts.fullTree);
647
+ const cwd = process.cwd();
648
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
649
+ const proceed = await handleOutdatedWorkflow({
650
+ checkOutdated: Boolean(opts.checkOutdated),
651
+ selection,
652
+ contextLabel: "global",
653
+ outFile,
654
+ fetchOutdated: () => npmOutdated({ cwd, global: true }),
655
+ updateRunner: selection.shouldUpdate ? async (packages) => {
656
+ await npmUpdate({ cwd, global: true, packages });
657
+ } : void 0
658
+ });
659
+ if (!proceed) return;
628
660
  const finalOutFile = outFile;
629
661
  const { report, markdownExtras } = await produceReport("global", {
630
662
  outputFormat,
@@ -650,7 +682,7 @@ function createReadCommand(program) {
650
682
  printFromReport(parsed);
651
683
  }
652
684
  if (doInstall) {
653
- await installFromReport(parsed, process.cwd());
685
+ await installFromReport(parsed, { cwd: process.cwd(), packageManager: "npm" });
654
686
  }
655
687
  } catch (err) {
656
688
  const isMd = isMarkdownReportFile(reportPath);
@@ -672,7 +704,7 @@ ${ASCII_BANNER}`);
672
704
  return program;
673
705
  }
674
706
 
675
- // src/cli.ts
707
+ // src/runtimes/node/cli.ts
676
708
  async function run(argv = process.argv) {
677
709
  const program = await createProgram();
678
710
  await program.parseAsync(argv);
@@ -696,7 +728,31 @@ if (isMainModule) {
696
728
  process.exitCode = 1;
697
729
  });
698
730
  }
731
+
732
+ // src/cli.ts
733
+ async function run2(argv = process.argv) {
734
+ return run(argv);
735
+ }
736
+ var isMainModule2 = (() => {
737
+ try {
738
+ if (typeof __require !== "undefined" && typeof module !== "undefined") {
739
+ return __require.main === module;
740
+ }
741
+ if (typeof import.meta !== "undefined") {
742
+ return import.meta.url === `file://${process.argv[1]}`;
743
+ }
744
+ return false;
745
+ } catch {
746
+ return false;
747
+ }
748
+ })();
749
+ if (isMainModule2) {
750
+ run2().catch((error) => {
751
+ console.error("CLI error:", error);
752
+ process.exitCode = 1;
753
+ });
754
+ }
699
755
  export {
700
- run
756
+ run2 as run
701
757
  };
702
758
  //# sourceMappingURL=cli.mjs.map