@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.cjs CHANGED
@@ -31,40 +31,60 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  // src/cli.ts
32
32
  var cli_exports = {};
33
33
  __export(cli_exports, {
34
- run: () => run
34
+ run: () => run2
35
35
  });
36
36
  module.exports = __toCommonJS(cli_exports);
37
37
 
38
- // src/cli/commands.ts
38
+ // src/runtimes/node/commands.ts
39
39
  var import_node_path6 = __toESM(require("path"), 1);
40
40
  var import_commander = require("commander");
41
41
 
42
- // src/cli/install.ts
42
+ // src/shared/cli/install.ts
43
+ var INSTALL_COMMANDS = {
44
+ npm: {
45
+ global: ["i", "-g"],
46
+ local: ["i"],
47
+ dev: ["i", "-D"]
48
+ },
49
+ bun: {
50
+ global: ["add", "-g"],
51
+ local: ["add"],
52
+ dev: ["add", "-d"]
53
+ }
54
+ };
55
+ var MAX_BUFFER = 10 * 1024 * 1024;
56
+ function formatSpec(pkg) {
57
+ return pkg.version ? `${pkg.name}@${pkg.version}` : pkg.name;
58
+ }
43
59
  async function getExecFileAsync() {
44
60
  const { execFile } = await import("child_process");
45
- const { promisify } = await import("util");
46
- return promisify(execFile);
61
+ const { promisify: promisify2 } = await import("util");
62
+ return promisify2(execFile);
47
63
  }
48
- async function installFromReport(report, cwd) {
49
- const globalPkgs = report.global_packages.map((p) => `${p.name}@${p.version}`).filter(Boolean);
50
- const localPkgs = report.local_dependencies.map((p) => `${p.name}@${p.version}`).filter(Boolean);
51
- const devPkgs = report.local_dev_dependencies.map((p) => `${p.name}@${p.version}`).filter(Boolean);
64
+ async function installFromReport(report, options) {
65
+ const opts = typeof options === "string" ? { cwd: options } : options;
66
+ const { cwd, packageManager = "npm" } = opts;
67
+ const globalPkgs = report.global_packages.map(formatSpec).filter(Boolean);
68
+ const localPkgs = report.local_dependencies.map(formatSpec).filter(Boolean);
69
+ const devPkgs = report.local_dev_dependencies.map(formatSpec).filter(Boolean);
52
70
  if (globalPkgs.length === 0 && localPkgs.length === 0 && devPkgs.length === 0) {
53
71
  console.log("No packages to install from report.");
54
72
  return;
55
73
  }
56
74
  const execFileAsync = await getExecFileAsync();
75
+ const cmd = INSTALL_COMMANDS[packageManager];
76
+ const binary = packageManager === "bun" ? "bun" : "npm";
57
77
  if (globalPkgs.length > 0) {
58
78
  console.log(`Installing global: ${globalPkgs.join(" ")}`);
59
- await execFileAsync("npm", ["i", "-g", ...globalPkgs], { cwd, maxBuffer: 10 * 1024 * 1024 });
79
+ await execFileAsync(binary, [...cmd.global, ...globalPkgs], { cwd, maxBuffer: MAX_BUFFER });
60
80
  }
61
81
  if (localPkgs.length > 0) {
62
82
  console.log(`Installing local deps: ${localPkgs.join(" ")}`);
63
- await execFileAsync("npm", ["i", ...localPkgs], { cwd, maxBuffer: 10 * 1024 * 1024 });
83
+ await execFileAsync(binary, [...cmd.local, ...localPkgs], { cwd, maxBuffer: MAX_BUFFER });
64
84
  }
65
85
  if (devPkgs.length > 0) {
66
86
  console.log(`Installing local devDeps: ${devPkgs.join(" ")}`);
67
- await execFileAsync("npm", ["i", "-D", ...devPkgs], { cwd, maxBuffer: 10 * 1024 * 1024 });
87
+ await execFileAsync(binary, [...cmd.dev, ...devPkgs], { cwd, maxBuffer: MAX_BUFFER });
68
88
  }
69
89
  }
70
90
  function printFromReport(report) {
@@ -95,10 +115,10 @@ function printFromReport(report) {
95
115
  console.log(lines.join("\n"));
96
116
  }
97
117
 
98
- // src/cli/output.ts
118
+ // src/shared/cli/output.ts
99
119
  var import_node_path = __toESM(require("path"), 1);
100
120
 
101
- // src/report/json.ts
121
+ // src/shared/report/json.ts
102
122
  function renderJson(report) {
103
123
  const r = {
104
124
  ...report,
@@ -111,7 +131,7 @@ function renderJson(report) {
111
131
  return JSON.stringify(r, null, 2);
112
132
  }
113
133
 
114
- // src/report/md.ts
134
+ // src/shared/report/md.ts
115
135
  function table(headers, rows) {
116
136
  const header = `| ${headers.join(" | ")} |`;
117
137
  const sep = `| ${headers.map(() => "---").join(" | ")} |`;
@@ -164,211 +184,9 @@ function renderMarkdown(report) {
164
184
  return lines.join("\n");
165
185
  }
166
186
 
167
- // src/report/html.ts
168
- function table2(headers, rows) {
169
- const headerHtml = `<tr>${headers.map((h) => `<th>${h}</th>`).join("")}</tr>`;
170
- const bodyHtml = rows.map((row) => `<tr>${row.map((cell) => `<td>${cell}</td>`).join("")}</tr>`).join("");
171
- return `<table><thead>${headerHtml}</thead><tbody>${bodyHtml}</tbody></table>`;
172
- }
173
- function escapeHtml(text) {
174
- const htmlEntities = {
175
- "&": "&amp;",
176
- "<": "&lt;",
177
- ">": "&gt;",
178
- '"': "&quot;",
179
- "'": "&#39;"
180
- };
181
- return text.replace(/[&<>"']/g, (char) => htmlEntities[char]);
182
- }
183
- function renderHtml(report) {
184
- const hasProjectMeta = report.project_name || report.project_version || report.project_description || report.project_homepage || report.project_bugs;
185
- return `<!DOCTYPE html>
186
- <html lang="en">
187
- <head>
188
- <meta charset="UTF-8">
189
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
190
- <title>GEX Report - ${report.project_name || "Dependency Audit"}</title>
191
- <style>
192
- body {
193
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
194
- line-height: 1.6;
195
- color: #333;
196
- max-width: 1200px;
197
- margin: 0 auto;
198
- padding: 20px;
199
- background-color: #f5f5f5;
200
- }
201
- .container {
202
- background: white;
203
- border-radius: 8px;
204
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
205
- padding: 30px;
206
- }
207
- h1 {
208
- color: #2c3e50;
209
- border-bottom: 3px solid #3498db;
210
- padding-bottom: 10px;
211
- margin-top: 0;
212
- }
213
- h2 {
214
- color: #34495e;
215
- border-bottom: 2px solid #bdc3c7;
216
- padding-bottom: 8px;
217
- margin-top: 40px;
218
- margin-bottom: 20px;
219
- }
220
- .metadata {
221
- background: #ecf0f1;
222
- border-radius: 6px;
223
- padding: 20px;
224
- margin-bottom: 20px;
225
- }
226
- .metadata dl {
227
- margin: 0;
228
- }
229
- .metadata dt {
230
- font-weight: bold;
231
- color: #2c3e50;
232
- margin-top: 10px;
233
- }
234
- .metadata dt:first-child {
235
- margin-top: 0;
236
- }
237
- .metadata dd {
238
- margin: 5px 0 0 20px;
239
- color: #555;
240
- }
241
- .metadata dd a {
242
- color: #3498db;
243
- text-decoration: none;
244
- }
245
- .metadata dd a:hover {
246
- text-decoration: underline;
247
- }
248
- table {
249
- width: 100%;
250
- border-collapse: collapse;
251
- margin-bottom: 30px;
252
- background: white;
253
- border-radius: 6px;
254
- overflow: hidden;
255
- box-shadow: 0 1px 3px rgba(0,0,0,0.1);
256
- }
257
- th {
258
- background: #3498db;
259
- color: white;
260
- padding: 12px;
261
- text-align: left;
262
- font-weight: 600;
263
- }
264
- td {
265
- padding: 12px;
266
- border-bottom: 1px solid #ecf0f1;
267
- font-family: 'SFMono-Regular', Consolas, monospace;
268
- font-size: 14px;
269
- }
270
- tr:nth-child(even) {
271
- background: #f8f9fa;
272
- }
273
- tr:hover {
274
- background: #e8f4fd;
275
- }
276
- .footer {
277
- text-align: center;
278
- color: #7f8c8d;
279
- font-size: 14px;
280
- margin-top: 40px;
281
- padding-top: 20px;
282
- border-top: 1px solid #ecf0f1;
283
- }
284
- .footer strong {
285
- color: #2c3e50;
286
- }
287
- .no-data {
288
- text-align: center;
289
- color: #7f8c8d;
290
- font-style: italic;
291
- padding: 40px;
292
- }
293
- .timestamp {
294
- color: #95a5a6;
295
- font-size: 14px;
296
- margin-top: 10px;
297
- }
298
- </style>
299
- </head>
300
- <body>
301
- <div class="container">
302
- <h1>GEX Dependency Report</h1>
303
-
304
- ${hasProjectMeta ? `
305
- <section class="metadata">
306
- <h2>Project Information</h2>
307
- <dl>
308
- ${report.project_name ? `<dt>Project Name</dt><dd>${escapeHtml(report.project_name)}</dd>` : ""}
309
- ${report.project_version ? `<dt>Version</dt><dd>${escapeHtml(report.project_version)}</dd>` : ""}
310
- ${report.project_description ? `<dt>Description</dt><dd>${escapeHtml(report.project_description)}</dd>` : ""}
311
- ${report.project_homepage ? `<dt>Homepage</dt><dd><a href="${escapeHtml(report.project_homepage)}" target="_blank">${escapeHtml(report.project_homepage)}</a></dd>` : ""}
312
- ${report.project_bugs ? `<dt>Bugs</dt><dd><a href="${escapeHtml(report.project_bugs)}" target="_blank">${escapeHtml(report.project_bugs)}</a></dd>` : ""}
313
- <dt>Report Generated</dt><dd>${new Date(report.timestamp).toLocaleString()}</dd>
314
- </dl>
315
- </section>
316
- ` : ""}
317
-
318
- ${report.global_packages.length > 0 ? `
319
- <section>
320
- <h2>Global Packages <small>(${report.global_packages.length})</small></h2>
321
- ${table2(
322
- ["Name", "Version", "Path"],
323
- report.global_packages.map((p) => [
324
- escapeHtml(p.name),
325
- escapeHtml(p.version || ""),
326
- escapeHtml(p.resolved_path || "")
327
- ])
328
- )}
329
- </section>
330
- ` : '<section><h2>Global Packages</h2><div class="no-data">No global packages found</div></section>'}
331
-
332
- ${report.local_dependencies.length > 0 ? `
333
- <section>
334
- <h2>Local Dependencies <small>(${report.local_dependencies.length})</small></h2>
335
- ${table2(
336
- ["Name", "Version", "Path"],
337
- report.local_dependencies.map((p) => [
338
- escapeHtml(p.name),
339
- escapeHtml(p.version || ""),
340
- escapeHtml(p.resolved_path || "")
341
- ])
342
- )}
343
- </section>
344
- ` : '<section><h2>Local Dependencies</h2><div class="no-data">No local dependencies found</div></section>'}
345
-
346
- ${report.local_dev_dependencies.length > 0 ? `
347
- <section>
348
- <h2>Local Dev Dependencies <small>(${report.local_dev_dependencies.length})</small></h2>
349
- ${table2(
350
- ["Name", "Version", "Path"],
351
- report.local_dev_dependencies.map((p) => [
352
- escapeHtml(p.name),
353
- escapeHtml(p.version || ""),
354
- escapeHtml(p.resolved_path || "")
355
- ])
356
- )}
357
- </section>
358
- ` : '<section><h2>Local Dev Dependencies</h2><div class="no-data">No local dev dependencies found</div></section>'}
359
-
360
- <footer class="footer">
361
- <p>Generated by <strong>GEX v${escapeHtml(report.tool_version)}</strong> on ${new Date(report.timestamp).toLocaleString()}</p>
362
- <p>Report format version: ${escapeHtml(report.report_version)}</p>
363
- </footer>
364
- </div>
365
- </body>
366
- </html>`;
367
- }
368
-
369
- // src/cli/output.ts
187
+ // src/shared/cli/output.ts
370
188
  async function outputReport(report, format, outFile, markdownExtras) {
371
- const content = format === "json" ? renderJson(report) : format === "html" ? renderHtml({ ...report, ...markdownExtras || {} }) : renderMarkdown({ ...report, ...markdownExtras || {} });
189
+ const content = format === "json" ? renderJson(report) : renderMarkdown({ ...report, ...markdownExtras || {} });
372
190
  if (outFile) {
373
191
  const outDir = import_node_path.default.dirname(outFile);
374
192
  const { mkdir, writeFile } = await import("fs/promises");
@@ -380,7 +198,7 @@ async function outputReport(report, format, outFile, markdownExtras) {
380
198
  }
381
199
  }
382
200
 
383
- // src/cli/parser.ts
201
+ // src/shared/cli/parser.ts
384
202
  var import_promises = require("fs/promises");
385
203
  var import_node_path2 = __toESM(require("path"), 1);
386
204
  function isMarkdownReportFile(filePath) {
@@ -429,60 +247,228 @@ async function loadReportFromFile(reportPath) {
429
247
  return JSON.parse(raw);
430
248
  }
431
249
 
432
- // src/cli/report.ts
433
- var import_promises4 = require("fs/promises");
434
- var import_node_path5 = __toESM(require("path"), 1);
435
-
436
- // src/npm.ts
250
+ // src/shared/npm-cli.ts
251
+ var import_node_util = require("util");
437
252
  async function getExecFileAsync2() {
438
253
  const { execFile } = await import("child_process");
439
- const { promisify } = await import("util");
440
- return promisify(execFile);
254
+ return (0, import_node_util.promisify)(execFile);
441
255
  }
442
- async function npmLs(options = {}) {
443
- const args = ["ls", "--json"];
256
+ async function npmOutdated(options = {}) {
257
+ const args = ["outdated", "--json"];
444
258
  if (options.global) args.push("--global");
445
- if (options.omitDev) args.push("--omit=dev");
446
- if (options.depth0) args.push("--depth=0");
447
259
  try {
448
260
  const execFileAsync = await getExecFileAsync2();
449
261
  const { stdout } = await execFileAsync("npm", args, {
450
262
  cwd: options.cwd,
451
263
  maxBuffer: 10 * 1024 * 1024
452
264
  });
453
- if (stdout && stdout.trim()) return JSON.parse(stdout);
454
- return {};
455
- } catch (err) {
456
- const stdout = err?.stdout;
457
- if (typeof stdout === "string" && stdout.trim()) {
458
- try {
459
- return JSON.parse(stdout);
460
- } catch (parseErr) {
461
- if (process.env.DEBUG?.includes("gex")) {
462
- console.warn("npm ls stdout parse failed:", parseErr);
463
- }
464
- }
265
+ return normalizeOutdated(stdout);
266
+ } catch (error) {
267
+ const stdout = typeof error?.stdout === "string" ? error.stdout : "";
268
+ if (stdout.trim()) {
269
+ return normalizeOutdated(stdout);
465
270
  }
466
- const stderr = err?.stderr;
467
- const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm ls failed";
468
- throw new Error(`npm ls failed: ${msg}`);
271
+ throw formatNpmError(error, "npm outdated");
469
272
  }
470
273
  }
471
- async function npmRootGlobal() {
274
+ async function npmUpdate(options) {
275
+ const args = ["update"];
276
+ if (options.global) args.push("-g");
277
+ if (options.packages && options.packages.length > 0) args.push(...options.packages);
472
278
  try {
473
279
  const execFileAsync = await getExecFileAsync2();
474
- const { stdout } = await execFileAsync("npm", ["root", "-g"]);
475
- return stdout.trim();
476
- } catch (err) {
477
- const stderr = err?.stderr;
478
- const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm root -g failed";
479
- throw new Error(`npm root -g failed: ${msg}`);
280
+ await execFileAsync("npm", args, {
281
+ cwd: options.cwd,
282
+ maxBuffer: 10 * 1024 * 1024
283
+ });
284
+ } catch (error) {
285
+ throw formatNpmError(error, "npm update");
286
+ }
287
+ }
288
+ function normalizeOutdated(stdout) {
289
+ if (!stdout.trim()) return [];
290
+ let data;
291
+ try {
292
+ data = JSON.parse(stdout);
293
+ } catch {
294
+ return [];
480
295
  }
296
+ if (!data) return [];
297
+ return Object.entries(data).map(([name, info]) => ({
298
+ name,
299
+ current: info?.current ? String(info.current) : "",
300
+ wanted: info?.wanted ? String(info.wanted) : "",
301
+ latest: info?.latest ? String(info.latest) : "",
302
+ type: info?.type ? String(info.type) : void 0
303
+ }));
304
+ }
305
+ function formatNpmError(error, commandLabel) {
306
+ const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
307
+ const message = stderr || error?.message || `${commandLabel} failed`;
308
+ return new Error(`${commandLabel} failed: ${message}`);
481
309
  }
482
310
 
483
- // src/transform.ts
484
- var import_node_path3 = __toESM(require("path"), 1);
311
+ // src/shared/cli/loader.ts
312
+ var frames = ["-", "\\", "|", "/"];
313
+ function createLoader(message) {
314
+ if (!process.stdout.isTTY) {
315
+ console.log(`${message}...`);
316
+ return {
317
+ stop(finalMessage) {
318
+ if (finalMessage) console.log(finalMessage);
319
+ }
320
+ };
321
+ }
322
+ let index = 0;
323
+ const interval = setInterval(() => {
324
+ const frame = frames[index % frames.length];
325
+ index += 1;
326
+ process.stdout.write(`\r${message} ${frame}`);
327
+ }, 80);
328
+ return {
329
+ stop(finalMessage) {
330
+ clearInterval(interval);
331
+ process.stdout.write("\r");
332
+ if (finalMessage) {
333
+ console.log(finalMessage);
334
+ } else {
335
+ process.stdout.write("\x1B[2K");
336
+ }
337
+ }
338
+ };
339
+ }
340
+
341
+ // src/shared/cli/outdated.ts
342
+ function normalizeUpdateSelection(value) {
343
+ if (value === void 0) {
344
+ return { shouldUpdate: false, updateAll: false, packages: [] };
345
+ }
346
+ if (value === true) {
347
+ return { shouldUpdate: true, updateAll: true, packages: [] };
348
+ }
349
+ const packages = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
350
+ const normalized = packages.flatMap((entry) => String(entry).split(",").map((part) => part.trim())).filter(Boolean);
351
+ return {
352
+ shouldUpdate: true,
353
+ updateAll: false,
354
+ packages: normalized
355
+ };
356
+ }
357
+ function formatOutdatedTable(entries) {
358
+ const headers = ["Name", "Current", "Wanted", "Latest", "Type"];
359
+ const rows = entries.map((entry) => [
360
+ entry.name,
361
+ entry.current || "-",
362
+ entry.wanted || "-",
363
+ entry.latest || "-",
364
+ entry.type || "-"
365
+ ]);
366
+ const widths = headers.map(
367
+ (header, index) => Math.max(header.length, ...rows.map((row) => row[index].length))
368
+ );
369
+ const formatRow = (columns) => columns.map((col, idx) => col.padEnd(widths[idx], " ")).join(" ");
370
+ const lines = [formatRow(headers), formatRow(widths.map((w) => "-".repeat(w)))];
371
+ for (const row of rows) {
372
+ lines.push(formatRow(row));
373
+ }
374
+ return lines.join("\n");
375
+ }
376
+ async function handleOutdatedWorkflow(opts) {
377
+ if (!opts.checkOutdated && !opts.selection.shouldUpdate) {
378
+ return true;
379
+ }
380
+ let fetchLoader;
381
+ if (opts.checkOutdated || opts.selection.shouldUpdate) {
382
+ fetchLoader = createLoader("Checking for outdated packages");
383
+ }
384
+ const outdated = await opts.fetchOutdated();
385
+ fetchLoader?.stop("Finished checking outdated packages.");
386
+ if (opts.checkOutdated) {
387
+ if (outdated.length === 0) {
388
+ console.log(`All ${opts.contextLabel} packages are up to date.`);
389
+ } else {
390
+ console.log(formatOutdatedTable(outdated));
391
+ }
392
+ }
393
+ if (opts.selection.shouldUpdate && opts.updateRunner) {
394
+ const packagesToUpdate = opts.selection.updateAll ? outdated.map((entry) => entry.name) : opts.selection.packages;
395
+ if (!packagesToUpdate || packagesToUpdate.length === 0) {
396
+ if (opts.selection.updateAll) {
397
+ console.log("No outdated packages to update.");
398
+ } else {
399
+ console.log("No packages were specified for updating.");
400
+ }
401
+ } else {
402
+ const updateLoader = createLoader("Updating packages");
403
+ await opts.updateRunner(packagesToUpdate);
404
+ updateLoader.stop("Finished updating packages.");
405
+ }
406
+ }
407
+ if (opts.checkOutdated || opts.selection.shouldUpdate) {
408
+ if (!opts.outFile) {
409
+ return false;
410
+ }
411
+ }
412
+ return true;
413
+ }
414
+
415
+ // src/shared/cli/utils.ts
416
+ var import_node_fs = require("fs");
485
417
  var import_promises2 = require("fs/promises");
418
+ var import_node_path3 = __toESM(require("path"), 1);
419
+ var import_node_url = require("url");
420
+ var import_meta = {};
421
+ function getPkgJsonPath() {
422
+ let startDir;
423
+ try {
424
+ const __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
425
+ startDir = import_node_path3.default.dirname(__filename);
426
+ } catch {
427
+ startDir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
428
+ }
429
+ return findPackageJson(startDir);
430
+ }
431
+ function findPackageJson(startDir) {
432
+ let current = startDir;
433
+ const maxDepth = 6;
434
+ for (let i = 0; i < maxDepth; i++) {
435
+ const candidate = import_node_path3.default.resolve(current, "package.json");
436
+ if ((0, import_node_fs.existsSync)(candidate)) {
437
+ return candidate;
438
+ }
439
+ const parent = import_node_path3.default.dirname(current);
440
+ if (parent === current) break;
441
+ current = parent;
442
+ }
443
+ return import_node_path3.default.resolve(process.cwd(), "package.json");
444
+ }
445
+ async function getToolVersion() {
446
+ try {
447
+ const pkgPath = getPkgJsonPath();
448
+ const raw = await (0, import_promises2.readFile)(pkgPath, "utf8");
449
+ const pkg = JSON.parse(raw);
450
+ return pkg.version || "0.0.0";
451
+ } catch {
452
+ return "0.0.0";
453
+ }
454
+ }
455
+ var ASCII_BANNER = String.raw`
456
+ ________ __
457
+ / _____/ ____ _____/ |_ ____ ____
458
+ / \ ___ / _ \ / _ \ __\/ __ \ / \
459
+ \ \_\ ( <_> | <_> ) | \ ___/| | \
460
+ \______ /\____/ \____/|__| \___ >___| /
461
+ \/ \/ \/
462
+ GEX
463
+ `;
464
+
465
+ // src/runtimes/node/report.ts
466
+ var import_promises4 = require("fs/promises");
467
+ var import_node_path5 = __toESM(require("path"), 1);
468
+
469
+ // src/shared/transform.ts
470
+ var import_node_path4 = __toESM(require("path"), 1);
471
+ var import_promises3 = require("fs/promises");
486
472
  function toPkgArray(obj) {
487
473
  if (!obj) return [];
488
474
  return Object.keys(obj).map((name) => ({ name, node: obj[name] })).filter((p) => p && p.node);
@@ -500,21 +486,30 @@ async function buildReportFromNpmTree(tree, opts) {
500
486
  if (opts.context === "local") {
501
487
  let pkgMeta = null;
502
488
  try {
503
- const pkgJsonPath = import_node_path3.default.join(opts.cwd || process.cwd(), "package.json");
504
- const raw = await (0, import_promises2.readFile)(pkgJsonPath, "utf8");
489
+ const pkgJsonPath = import_node_path4.default.join(opts.cwd || process.cwd(), "package.json");
490
+ const raw = await (0, import_promises3.readFile)(pkgJsonPath, "utf8");
505
491
  pkgMeta = JSON.parse(raw);
506
492
  } catch {
507
493
  }
508
494
  if (pkgMeta?.name) report.project_name = pkgMeta.name;
509
495
  if (pkgMeta?.version) report.project_version = pkgMeta.version;
510
496
  const depsObj = tree?.dependencies;
511
- const items = toPkgArray(depsObj);
512
- const devKeys = new Set(Object.keys(pkgMeta?.devDependencies || {}));
513
- for (const { name, node } of items) {
497
+ const devDepsObj = tree?.devDependencies;
498
+ const prodItems = toPkgArray(depsObj);
499
+ const treeDevItems = toPkgArray(devDepsObj);
500
+ if (treeDevItems.length > 0) {
501
+ for (const { name, node } of treeDevItems) {
502
+ const version = node && node.version || "";
503
+ const resolvedPath = node && node.path || import_node_path4.default.join(opts.cwd || process.cwd(), "node_modules", name);
504
+ report.local_dev_dependencies.push({ name, version, resolved_path: resolvedPath });
505
+ }
506
+ }
507
+ const devKeys = treeDevItems.length > 0 ? new Set(treeDevItems.map((entry) => entry.name)) : new Set(Object.keys(pkgMeta?.devDependencies || {}));
508
+ for (const { name, node } of prodItems) {
514
509
  const version = node && node.version || "";
515
- const resolvedPath = node && node.path || import_node_path3.default.join(opts.cwd || process.cwd(), "node_modules", name);
510
+ const resolvedPath = node && node.path || import_node_path4.default.join(opts.cwd || process.cwd(), "node_modules", name);
516
511
  const pkg = { name, version, resolved_path: resolvedPath };
517
- if (devKeys.has(name)) {
512
+ if (!treeDevItems.length && devKeys.has(name)) {
518
513
  report.local_dev_dependencies.push(pkg);
519
514
  } else {
520
515
  report.local_dependencies.push(pkg);
@@ -527,7 +522,7 @@ async function buildReportFromNpmTree(tree, opts) {
527
522
  const items = toPkgArray(depsObj);
528
523
  for (const { name, node } of items) {
529
524
  const version = node && node.version || "";
530
- const resolvedPath = node && node.path || import_node_path3.default.join(opts.globalRoot || "", name);
525
+ const resolvedPath = node && node.path || import_node_path4.default.join(opts.globalRoot || "", name);
531
526
  const pkg = { name, version, resolved_path: resolvedPath };
532
527
  report.global_packages.push(pkg);
533
528
  }
@@ -539,42 +534,54 @@ async function buildReportFromNpmTree(tree, opts) {
539
534
  return report;
540
535
  }
541
536
 
542
- // src/cli/utils.ts
543
- var import_promises3 = require("fs/promises");
544
- var import_node_path4 = __toESM(require("path"), 1);
545
- var import_node_url = require("url");
546
- var import_meta = {};
547
- function getPkgJsonPath() {
537
+ // src/runtimes/node/package-manager.ts
538
+ async function getExecFileAsync3() {
539
+ const { execFile } = await import("child_process");
540
+ const { promisify: promisify2 } = await import("util");
541
+ return promisify2(execFile);
542
+ }
543
+ async function npmLs(options = {}) {
544
+ const args = ["ls", "--json"];
545
+ if (options.global) args.push("--global");
546
+ if (options.omitDev) args.push("--omit=dev");
547
+ if (options.depth0) args.push("--depth=0");
548
548
  try {
549
- const __filename = (0, import_node_url.fileURLToPath)(import_meta.url);
550
- const __dirnameLocal = import_node_path4.default.dirname(__filename);
551
- return import_node_path4.default.resolve(__dirnameLocal, "..", "..", "package.json");
552
- } catch {
553
- const dir = typeof __dirname !== "undefined" ? __dirname : process.cwd();
554
- return import_node_path4.default.resolve(dir, "..", "package.json");
549
+ const execFileAsync = await getExecFileAsync3();
550
+ const { stdout } = await execFileAsync("npm", args, {
551
+ cwd: options.cwd,
552
+ maxBuffer: 10 * 1024 * 1024
553
+ });
554
+ if (stdout && stdout.trim()) return JSON.parse(stdout);
555
+ return {};
556
+ } catch (err) {
557
+ const stdout = err?.stdout;
558
+ if (typeof stdout === "string" && stdout.trim()) {
559
+ try {
560
+ return JSON.parse(stdout);
561
+ } catch (parseErr) {
562
+ if (process.env.DEBUG?.includes("gex")) {
563
+ console.warn("npm ls stdout parse failed:", parseErr);
564
+ }
565
+ }
566
+ }
567
+ const stderr = err?.stderr;
568
+ const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm ls failed";
569
+ throw new Error(`npm ls failed: ${msg}`);
555
570
  }
556
571
  }
557
- async function getToolVersion() {
572
+ async function npmRootGlobal() {
558
573
  try {
559
- const pkgPath = getPkgJsonPath();
560
- const raw = await (0, import_promises3.readFile)(pkgPath, "utf8");
561
- const pkg = JSON.parse(raw);
562
- return pkg.version || "0.0.0";
563
- } catch {
564
- return "0.0.0";
574
+ const execFileAsync = await getExecFileAsync3();
575
+ const { stdout } = await execFileAsync("npm", ["root", "-g"]);
576
+ return stdout.trim();
577
+ } catch (err) {
578
+ const stderr = err?.stderr;
579
+ const msg = typeof stderr === "string" && stderr.trim() || err?.message || "npm root -g failed";
580
+ throw new Error(`npm root -g failed: ${msg}`);
565
581
  }
566
582
  }
567
- var ASCII_BANNER = String.raw`
568
- ________ __
569
- / _____/ ____ _____/ |_ ____ ____
570
- / \ ___ / _ \ / _ \ __\/ __ \ / \
571
- \ \_\ ( <_> | <_> ) | \ ___/| | \
572
- \______ /\____/ \____/|__| \___ >___| /
573
- \/ \/ \/
574
- GEX
575
- `;
576
583
 
577
- // src/cli/report.ts
584
+ // src/runtimes/node/report.ts
578
585
  async function produceReport(ctx, options) {
579
586
  const toolVersion = await getToolVersion();
580
587
  const depth0 = !options.fullTree;
@@ -612,18 +619,17 @@ async function produceReport(ctx, options) {
612
619
  return { report, markdownExtras };
613
620
  }
614
621
 
615
- // src/cli/commands.ts
622
+ // src/runtimes/node/commands.ts
616
623
  function addCommonOptions(cmd, { allowOmitDev }) {
617
624
  cmd.option(
618
625
  "-f, --output-format <format>",
619
- "Output format: json, md, or html",
620
- (val) => {
621
- if (val === "md") return "md";
622
- if (val === "html") return "html";
623
- return "json";
624
- },
626
+ "Output format: md or json",
627
+ (val) => val === "md" ? "md" : "json",
625
628
  "json"
626
- ).option("-o, --out-file <path>", "Write report to file").option("--full-tree", "Include full npm ls tree (omit depth=0 default)", false);
629
+ ).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(
630
+ "-u, --update-outdated [packages...]",
631
+ "Update outdated packages (omit package names to update every package)"
632
+ );
627
633
  if (allowOmitDev) {
628
634
  cmd.option("--omit-dev", "Exclude devDependencies (local only)", false);
629
635
  }
@@ -637,6 +643,19 @@ function createLocalCommand(program) {
637
643
  const outFile = opts.outFile;
638
644
  const fullTree = Boolean(opts.fullTree);
639
645
  const omitDev = Boolean(opts.omitDev);
646
+ const cwd = process.cwd();
647
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
648
+ const proceed = await handleOutdatedWorkflow({
649
+ checkOutdated: Boolean(opts.checkOutdated),
650
+ selection,
651
+ contextLabel: "local",
652
+ outFile,
653
+ fetchOutdated: () => npmOutdated({ cwd }),
654
+ updateRunner: selection.shouldUpdate ? async (packages) => {
655
+ await npmUpdate({ cwd, packages });
656
+ } : void 0
657
+ });
658
+ if (!proceed) return;
640
659
  const finalOutFile = outFile;
641
660
  const { report, markdownExtras } = await produceReport("local", {
642
661
  outputFormat,
@@ -655,6 +674,19 @@ function createGlobalCommand(program) {
655
674
  const outputFormat = opts.outputFormat ?? "json";
656
675
  const outFile = opts.outFile;
657
676
  const fullTree = Boolean(opts.fullTree);
677
+ const cwd = process.cwd();
678
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
679
+ const proceed = await handleOutdatedWorkflow({
680
+ checkOutdated: Boolean(opts.checkOutdated),
681
+ selection,
682
+ contextLabel: "global",
683
+ outFile,
684
+ fetchOutdated: () => npmOutdated({ cwd, global: true }),
685
+ updateRunner: selection.shouldUpdate ? async (packages) => {
686
+ await npmUpdate({ cwd, global: true, packages });
687
+ } : void 0
688
+ });
689
+ if (!proceed) return;
658
690
  const finalOutFile = outFile;
659
691
  const { report, markdownExtras } = await produceReport("global", {
660
692
  outputFormat,
@@ -680,7 +712,7 @@ function createReadCommand(program) {
680
712
  printFromReport(parsed);
681
713
  }
682
714
  if (doInstall) {
683
- await installFromReport(parsed, process.cwd());
715
+ await installFromReport(parsed, { cwd: process.cwd(), packageManager: "npm" });
684
716
  }
685
717
  } catch (err) {
686
718
  const isMd = isMarkdownReportFile(reportPath);
@@ -702,7 +734,7 @@ ${ASCII_BANNER}`);
702
734
  return program;
703
735
  }
704
736
 
705
- // src/cli.ts
737
+ // src/runtimes/node/cli.ts
706
738
  var import_meta2 = {};
707
739
  async function run(argv = process.argv) {
708
740
  const program = await createProgram();
@@ -727,6 +759,31 @@ if (isMainModule) {
727
759
  process.exitCode = 1;
728
760
  });
729
761
  }
762
+
763
+ // src/cli.ts
764
+ var import_meta3 = {};
765
+ async function run2(argv = process.argv) {
766
+ return run(argv);
767
+ }
768
+ var isMainModule2 = (() => {
769
+ try {
770
+ if (typeof require !== "undefined" && typeof module !== "undefined") {
771
+ return require.main === module;
772
+ }
773
+ if (typeof import_meta3 !== "undefined") {
774
+ return import_meta3.url === `file://${process.argv[1]}`;
775
+ }
776
+ return false;
777
+ } catch {
778
+ return false;
779
+ }
780
+ })();
781
+ if (isMainModule2) {
782
+ run2().catch((error) => {
783
+ console.error("CLI error:", error);
784
+ process.exitCode = 1;
785
+ });
786
+ }
730
787
  // Annotate the CommonJS export names for ESM import in node:
731
788
  0 && (module.exports = {
732
789
  run