@yabasha/gex 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -47,10 +47,12 @@ gex global [options]
47
47
 
48
48
  Common options:
49
49
 
50
- - f, --output-format <md|json> (default: json)
51
- - o, --out-file <path>
52
- - -full-tree Include the full npm ls JSON under `tree` (default uses depth=0)
53
- - -omit-dev Local only; exclude devDependencies
50
+ - -f, --output-format <md|json> (default: json)
51
+ - -o, --out-file <path>
52
+ - --full-tree Include the full npm ls JSON under `tree` (default uses depth=0)
53
+ - --omit-dev Local only; exclude devDependencies
54
+ - -c, --check-outdated Print a table of outdated packages (skips console report output unless `-o` is set)
55
+ - -u, --update-outdated [pkg1 pkg2 ...] Update outdated packages (omit names to update everything). Node CLI shells out to `npm update`, Bun CLI runs `bun update`/`bun update --global`.
54
56
 
55
57
  Examples:
56
58
 
@@ -83,6 +85,16 @@ gex read global.md -i
83
85
  # Shell redirection (alternative to -o flag)
84
86
  gex > report.json # redirect JSON output to file
85
87
  gex global | jq '.global_packages' # pipe output to jq for processing
88
+
89
+ # Check outdated packages / update them (Node runtime)
90
+ gex local --check-outdated # show outdated local deps as a table
91
+ gex global --check-outdated # show outdated globals
92
+ gex local --update-outdated # update every outdated local dependency
93
+ gex local --update-outdated axios react # update specific packages
94
+
95
+ # Bun runtime uses the same flags
96
+ gex-bun local --check-outdated
97
+ gex-bun global --update-outdated # updates global Bun installs via `bun update`
86
98
  ```
87
99
 
88
100
  > **Note**: Starting from v0.4.0, GEX outputs to console by default instead of creating files automatically. Use the `-o/--out-file` flag to write to a file.
package/dist/cli-bun.mjs CHANGED
@@ -8,6 +8,7 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
8
8
 
9
9
  // src/runtimes/bun/commands.ts
10
10
  import path7 from "path";
11
+ import { readFile as readFile6 } from "fs/promises";
11
12
  import { Command } from "commander";
12
13
 
13
14
  // src/shared/cli/install.ts
@@ -29,8 +30,8 @@ function formatSpec(pkg) {
29
30
  }
30
31
  async function getExecFileAsync() {
31
32
  const { execFile } = await import("child_process");
32
- const { promisify } = await import("util");
33
- return promisify(execFile);
33
+ const { promisify: promisify3 } = await import("util");
34
+ return promisify3(execFile);
34
35
  }
35
36
  async function installFromReport(report, options) {
36
37
  const opts = typeof options === "string" ? { cwd: options } : options;
@@ -218,6 +219,156 @@ async function loadReportFromFile(reportPath) {
218
219
  return JSON.parse(raw);
219
220
  }
220
221
 
222
+ // src/shared/npm-cli.ts
223
+ import { promisify } from "util";
224
+ async function getExecFileAsync2() {
225
+ const { execFile } = await import("child_process");
226
+ return promisify(execFile);
227
+ }
228
+ function formatNpmError(error, commandLabel) {
229
+ const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
230
+ const message = stderr || error?.message || `${commandLabel} failed`;
231
+ return new Error(`${commandLabel} failed: ${message}`);
232
+ }
233
+ async function npmViewVersion(packageName) {
234
+ try {
235
+ const execFileAsync = await getExecFileAsync2();
236
+ const { stdout } = await execFileAsync("npm", ["view", packageName, "version", "--json"], {
237
+ maxBuffer: 5 * 1024 * 1024
238
+ });
239
+ const parsed = JSON.parse(stdout);
240
+ if (typeof parsed === "string") return parsed;
241
+ if (Array.isArray(parsed)) return parsed[parsed.length - 1] ?? "";
242
+ return "";
243
+ } catch (error) {
244
+ throw formatNpmError(error, `npm view ${packageName}`);
245
+ }
246
+ }
247
+
248
+ // src/shared/cli/loader.ts
249
+ var frames = ["-", "\\", "|", "/"];
250
+ function createLoader(message) {
251
+ if (!process.stdout.isTTY) {
252
+ console.log(`${message}...`);
253
+ return {
254
+ stop(finalMessage) {
255
+ if (finalMessage) console.log(finalMessage);
256
+ }
257
+ };
258
+ }
259
+ let index = 0;
260
+ const interval = globalThis.setInterval(() => {
261
+ const frame = frames[index % frames.length];
262
+ index += 1;
263
+ process.stdout.write(`\r${message} ${frame}`);
264
+ }, 80);
265
+ return {
266
+ stop(finalMessage) {
267
+ globalThis.clearInterval(interval);
268
+ process.stdout.write("\r");
269
+ if (finalMessage) {
270
+ console.log(finalMessage);
271
+ } else {
272
+ process.stdout.write("\x1B[2K");
273
+ }
274
+ }
275
+ };
276
+ }
277
+
278
+ // src/shared/cli/outdated.ts
279
+ function normalizeUpdateSelection(value) {
280
+ if (value === void 0) {
281
+ return { shouldUpdate: false, updateAll: false, packages: [] };
282
+ }
283
+ if (value === true) {
284
+ return { shouldUpdate: true, updateAll: true, packages: [] };
285
+ }
286
+ const packages = Array.isArray(value) ? value : typeof value === "string" ? [value] : [];
287
+ const normalized = packages.flatMap((entry) => String(entry).split(",").map((part) => part.trim())).filter(Boolean);
288
+ return {
289
+ shouldUpdate: true,
290
+ updateAll: false,
291
+ packages: normalized
292
+ };
293
+ }
294
+ function formatOutdatedTable(entries) {
295
+ const headers = ["Name", "Current", "Wanted", "Latest", "Type"];
296
+ const rows = entries.map((entry) => [
297
+ entry.name,
298
+ entry.current || "-",
299
+ entry.wanted || "-",
300
+ entry.latest || "-",
301
+ entry.type || "-"
302
+ ]);
303
+ const widths = headers.map(
304
+ (header, index) => Math.max(header.length, ...rows.map((row) => row[index].length))
305
+ );
306
+ const formatRow = (columns) => columns.map((col, idx) => col.padEnd(widths[idx], " ")).join(" ");
307
+ const lines = [formatRow(headers), formatRow(widths.map((w) => "-".repeat(w)))];
308
+ for (const row of rows) {
309
+ lines.push(formatRow(row));
310
+ }
311
+ return lines.join("\n");
312
+ }
313
+ async function handleOutdatedWorkflow(opts) {
314
+ if (!opts.checkOutdated && !opts.selection.shouldUpdate) {
315
+ return true;
316
+ }
317
+ let fetchLoader;
318
+ if (opts.checkOutdated || opts.selection.shouldUpdate) {
319
+ fetchLoader = createLoader("Checking for outdated packages");
320
+ }
321
+ const outdated = await opts.fetchOutdated();
322
+ fetchLoader?.stop("Finished checking outdated packages.");
323
+ if (opts.checkOutdated) {
324
+ if (outdated.length === 0) {
325
+ console.log(`All ${opts.contextLabel} packages are up to date.`);
326
+ } else {
327
+ console.log(formatOutdatedTable(outdated));
328
+ }
329
+ }
330
+ if (opts.selection.shouldUpdate && opts.updateRunner) {
331
+ const packagesToUpdate = opts.selection.updateAll ? outdated.map((entry) => entry.name) : opts.selection.packages;
332
+ if (!packagesToUpdate || packagesToUpdate.length === 0) {
333
+ if (opts.selection.updateAll) {
334
+ console.log("No outdated packages to update.");
335
+ } else {
336
+ console.log("No packages were specified for updating.");
337
+ }
338
+ } else {
339
+ const updateLoader = createLoader("Updating packages");
340
+ await opts.updateRunner(packagesToUpdate);
341
+ updateLoader.stop("Finished updating packages.");
342
+ }
343
+ }
344
+ if (opts.checkOutdated || opts.selection.shouldUpdate) {
345
+ if (!opts.outFile) {
346
+ return false;
347
+ }
348
+ }
349
+ return true;
350
+ }
351
+ async function resolveOutdatedWithNpmView(packages) {
352
+ const results = [];
353
+ for (const pkg of packages) {
354
+ try {
355
+ const latest = await npmViewVersion(pkg.name);
356
+ if (latest && pkg.current && latest !== pkg.current) {
357
+ results.push({
358
+ name: pkg.name,
359
+ current: pkg.current,
360
+ wanted: pkg.declared || latest,
361
+ latest,
362
+ type: pkg.type
363
+ });
364
+ }
365
+ } catch {
366
+ continue;
367
+ }
368
+ }
369
+ return results;
370
+ }
371
+
221
372
  // src/shared/cli/utils.ts
222
373
  import { existsSync } from "fs";
223
374
  import { readFile as readFile2 } from "fs/promises";
@@ -343,6 +494,7 @@ async function buildReportFromNpmTree(tree, opts) {
343
494
  import path5 from "path";
344
495
  import { constants as fsConstants } from "fs";
345
496
  import { access, readFile as readFile4, readdir, stat } from "fs/promises";
497
+ import { promisify as promisify2 } from "util";
346
498
  var IGNORED_ENTRIES = /* @__PURE__ */ new Set([".bin"]);
347
499
  async function bunPmLs(options = {}) {
348
500
  if (options.global) {
@@ -389,6 +541,36 @@ async function bunPmRootLocal(cwd = process.cwd()) {
389
541
  }
390
542
  return path5.join(cwd, "node_modules");
391
543
  }
544
+ async function bunUpdate(options) {
545
+ const execFileAsync = await getExecFileAsync3();
546
+ const packages = options.packages && options.packages.length > 0 ? options.packages : [];
547
+ if (options.global) {
548
+ const targets = packages.length > 0 ? packages : [];
549
+ const list = targets.length > 0 ? targets : [];
550
+ const cmdPackages = list.map((name) => `${name}@latest`);
551
+ try {
552
+ await execFileAsync("bun", ["add", "-g", ...cmdPackages], {
553
+ cwd: options.cwd,
554
+ maxBuffer: 10 * 1024 * 1024
555
+ });
556
+ } catch (error) {
557
+ const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
558
+ throw new Error(`bun add -g failed: ${stderr || error?.message || "unknown error"}`);
559
+ }
560
+ return;
561
+ }
562
+ const args = ["update"];
563
+ if (packages.length > 0) args.push(...packages);
564
+ try {
565
+ await execFileAsync("bun", args, {
566
+ cwd: options.cwd,
567
+ maxBuffer: 10 * 1024 * 1024
568
+ });
569
+ } catch (error) {
570
+ const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
571
+ throw new Error(`bun update failed: ${stderr || error?.message || "unknown error"}`);
572
+ }
573
+ }
392
574
  async function collectPackagesForNames(nodeModulesPath, packages) {
393
575
  const result = {};
394
576
  await Promise.all(
@@ -506,6 +688,10 @@ function getGlobalRootCandidates() {
506
688
  maybeAdd("/opt/homebrew/var/bun/install/global/node_modules");
507
689
  return Array.from(candidates);
508
690
  }
691
+ async function getExecFileAsync3() {
692
+ const { execFile } = await import("child_process");
693
+ return promisify2(execFile);
694
+ }
509
695
 
510
696
  // src/runtimes/bun/report.ts
511
697
  async function produceReport(ctx, options) {
@@ -551,7 +737,10 @@ function addCommonOptions(cmd, { allowOmitDev }) {
551
737
  "Output format: md or json",
552
738
  (val) => val === "md" ? "md" : "json",
553
739
  "json"
554
- ).option("-o, --out-file <path>", "Write report to file").option("--full-tree", "Include full bun pm ls tree (when available)", false);
740
+ ).option("-o, --out-file <path>", "Write report to file").option("--full-tree", "Include full bun pm ls tree (when available)", false).option("-c, --check-outdated", "List outdated packages instead of printing the report", false).option(
741
+ "-u, --update-outdated [packages...]",
742
+ "Update outdated packages (omit package names to update every package)"
743
+ );
555
744
  if (allowOmitDev) {
556
745
  cmd.option("--omit-dev", "Exclude devDependencies (local only)", false);
557
746
  }
@@ -565,6 +754,44 @@ function createLocalCommand(program) {
565
754
  const outFile = opts.outFile;
566
755
  const fullTree = Boolean(opts.fullTree);
567
756
  const omitDev = Boolean(opts.omitDev);
757
+ const cwd = process.cwd();
758
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
759
+ const proceed = await handleOutdatedWorkflow({
760
+ checkOutdated: Boolean(opts.checkOutdated),
761
+ selection,
762
+ contextLabel: "local",
763
+ outFile,
764
+ fetchOutdated: async () => {
765
+ const tree = await bunPmLs({ cwd, omitDev });
766
+ const manifest = await readPackageManifest(cwd);
767
+ const declared = {
768
+ ...manifest?.dependencies || {},
769
+ ...manifest?.optionalDependencies || {},
770
+ ...manifest?.devDependencies || {}
771
+ };
772
+ const packages = Object.entries(tree.dependencies).map(([name, node]) => ({
773
+ name,
774
+ current: node.version,
775
+ declared: declared[name],
776
+ type: "prod"
777
+ }));
778
+ if (tree.devDependencies) {
779
+ for (const [name, node] of Object.entries(tree.devDependencies)) {
780
+ packages.push({
781
+ name,
782
+ current: node.version,
783
+ declared: declared[name],
784
+ type: "dev"
785
+ });
786
+ }
787
+ }
788
+ return resolveOutdatedWithNpmView(packages);
789
+ },
790
+ updateRunner: selection.shouldUpdate ? async (packages) => {
791
+ await bunUpdate({ cwd, packages });
792
+ } : void 0
793
+ });
794
+ if (!proceed) return;
568
795
  const finalOutFile = outFile;
569
796
  const { report, markdownExtras } = await produceReport("local", {
570
797
  outputFormat,
@@ -583,6 +810,27 @@ function createGlobalCommand(program) {
583
810
  const outputFormat = opts.outputFormat ?? "json";
584
811
  const outFile = opts.outFile;
585
812
  const fullTree = Boolean(opts.fullTree);
813
+ const cwd = process.cwd();
814
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
815
+ const proceed = await handleOutdatedWorkflow({
816
+ checkOutdated: Boolean(opts.checkOutdated),
817
+ selection,
818
+ contextLabel: "global",
819
+ outFile,
820
+ fetchOutdated: async () => {
821
+ const tree = await bunPmLs({ global: true });
822
+ const packages = Object.entries(tree.dependencies).map(([name, node]) => ({
823
+ name,
824
+ current: node.version,
825
+ type: "global"
826
+ }));
827
+ return resolveOutdatedWithNpmView(packages);
828
+ },
829
+ updateRunner: selection.shouldUpdate ? async (packages) => {
830
+ await bunUpdate({ cwd, global: true, packages });
831
+ } : void 0
832
+ });
833
+ if (!proceed) return;
586
834
  const finalOutFile = outFile;
587
835
  const { report, markdownExtras } = await produceReport("global", {
588
836
  outputFormat,
@@ -629,6 +877,14 @@ ${ASCII_BANNER}`);
629
877
  createReadCommand(program);
630
878
  return program;
631
879
  }
880
+ async function readPackageManifest(cwd) {
881
+ try {
882
+ const raw = await readFile6(path7.join(cwd, "package.json"), "utf8");
883
+ return JSON.parse(raw);
884
+ } catch {
885
+ return null;
886
+ }
887
+ }
632
888
 
633
889
  // src/runtimes/bun/cli.ts
634
890
  async function run(argv = process.argv) {