@yabasha/gex 1.3.4 → 1.3.5

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
@@ -37,15 +37,38 @@ node dist/cli.cjs --help
37
37
 
38
38
  ## Usage
39
39
 
40
- Synopsis:
40
+ ### Top-level entry: `gex` (interactive selector)
41
+
42
+ Starting with v1.3.5, the primary `gex` binary is an interactive launcher that lets you choose which runtime to use:
43
+
44
+ ```bash
45
+ gex
46
+ ```
47
+
48
+ You will be prompted with:
49
+
50
+ ```text
51
+ Select a runtime to use:
52
+ 1) gex-bun (Bun package manager)
53
+ 2) gex-npm (npm / Node.js package manager)
54
+ ```
55
+
56
+ Enter `1` for the Bun-based CLI (`gex-bun`) or `2` for the Node/npm-based CLI (`gex-npm`). Any additional arguments you pass after `gex` are forwarded to the selected runtime, e.g.:
57
+
58
+ ```bash
59
+ gex local --check-outdated # choose Bun or npm, then run `local` on that runtime
60
+ ```
61
+
62
+ ### Direct runtimes: `gex-npm` (Node) and `gex-bun` (Bun)
63
+
64
+ You can still call each runtime directly without the interactive selector:
41
65
 
42
66
  ```bash
43
- gex [options] # defaults to: gex local
44
- gex local [options]
45
- gex global [options]
67
+ gex-npm [command] [options] # Node.js / npm runtime (formerly `gex`)
68
+ gex-bun [command] [options] # Bun runtime
46
69
  ```
47
70
 
48
- Common options:
71
+ Common command options:
49
72
 
50
73
  - -f, --output-format <md|json> (default: json)
51
74
  - -o, --out-file <path>
@@ -54,45 +77,45 @@ Common options:
54
77
  - -c, --check-outdated Print a table of outdated packages (shows a lightweight spinner while checking; skips console report output unless `-o` is set so you can write the report to file instead)
55
78
  - -u, --update-outdated [pkg1 pkg2 ...] Update outdated packages (omit names to update everything). Node CLI shells out to `npm update`; Bun CLI mirrors `bun update` for locals and reinstalls globals via `bun add -g pkg@latest`.
56
79
 
57
- Examples:
80
+ Examples (Node/npm runtime via `gex-npm`):
58
81
 
59
82
  ```bash
60
83
  # Local (default): JSON output to console
61
- gex # prints JSON to console (same as: gex local)
62
- gex -o report.json # writes JSON to file
63
- gex -f md # prints markdown to console
64
- gex -f md -o report.md # writes markdown to file
84
+ gex-npm # prints JSON to console (same as: gex-npm local)
85
+ gex-npm -o report.json # writes JSON to file
86
+ gex-npm -f md # prints markdown to console
87
+ gex-npm -f md -o report.md # writes markdown to file
65
88
 
66
89
  # Local: exclude devDependencies
67
- gex local --omit-dev # prints JSON to console
68
- gex local --omit-dev -o deps.json # writes JSON to file
90
+ gex-npm local --omit-dev # prints JSON to console
91
+ gex-npm local --omit-dev -o deps.json # writes JSON to file
69
92
 
70
93
  # Global packages
71
- gex global # prints JSON to console
72
- gex global -o global.json # writes JSON to file
73
- gex global -f md # prints markdown to console
94
+ gex-npm global # prints JSON to console
95
+ gex-npm global -o global.json # writes JSON to file
96
+ gex-npm global -f md # prints markdown to console
74
97
 
75
98
  # Read a previous report (JSON or Markdown)
76
99
  # Default prints names@versions; add -i to install
77
100
  # Positional path or -r/--report are accepted
78
101
  # JSON
79
- gex read
80
- gex read -r path/to/report.json -i
102
+ gex-npm read
103
+ gex-npm read -r path/to/report.json -i
81
104
  # Markdown
82
- gex read global.md
83
- gex read global.md -i
105
+ gex-npm read global.md
106
+ gex-npm read global.md -i
84
107
 
85
108
  # Shell redirection (alternative to -o flag)
86
- gex > report.json # redirect JSON output to file
87
- gex global | jq '.global_packages' # pipe output to jq for processing
109
+ gex-npm > report.json # redirect JSON output to file
110
+ gex-npm global | jq '.global_packages' # pipe output to jq for processing
88
111
 
89
112
  # Check outdated packages / update them (Node runtime)
90
- gex local --check-outdated # show outdated local deps as a table
91
- gex global -c # short flag works too
92
- gex local --update-outdated # update every outdated local dependency
93
- gex local -u axios react # update specific packages
113
+ gex-npm local --check-outdated # show outdated local deps as a table
114
+ gex-npm global -c # short flag works too
115
+ gex-npm local --update-outdated # update every outdated local dependency
116
+ gex-npm local -u axios react # update specific packages
94
117
 
95
- # Bun runtime uses the same flags
118
+ # Bun runtime uses the same flags with Bun semantics
96
119
  gex-bun local --check-outdated
97
120
  gex-bun global --update-outdated # updates global Bun installs via `bun update`/`bun add -g`
98
121
  ```
package/dist/cli.cjs CHANGED
@@ -31,9 +31,10 @@ 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: () => run2
34
+ run: () => run3
35
35
  });
36
36
  module.exports = __toCommonJS(cli_exports);
37
+ var import_node_readline = __toESM(require("readline"), 1);
37
38
 
38
39
  // src/runtimes/node/commands.ts
39
40
  var import_node_path6 = __toESM(require("path"), 1);
@@ -58,8 +59,8 @@ function formatSpec(pkg) {
58
59
  }
59
60
  async function getExecFileAsync() {
60
61
  const { execFile } = await import("child_process");
61
- const { promisify: promisify2 } = await import("util");
62
- return promisify2(execFile);
62
+ const { promisify: promisify3 } = await import("util");
63
+ return promisify3(execFile);
63
64
  }
64
65
  async function installFromReport(report, options) {
65
66
  const opts = typeof options === "string" ? { cwd: options } : options;
@@ -307,6 +308,20 @@ function formatNpmError(error, commandLabel) {
307
308
  const message = stderr || error?.message || `${commandLabel} failed`;
308
309
  return new Error(`${commandLabel} failed: ${message}`);
309
310
  }
311
+ async function npmViewVersion(packageName) {
312
+ try {
313
+ const execFileAsync = await getExecFileAsync2();
314
+ const { stdout } = await execFileAsync("npm", ["view", packageName, "version", "--json"], {
315
+ maxBuffer: 5 * 1024 * 1024
316
+ });
317
+ const parsed = JSON.parse(stdout);
318
+ if (typeof parsed === "string") return parsed;
319
+ if (Array.isArray(parsed)) return parsed[parsed.length - 1] ?? "";
320
+ return "";
321
+ } catch (error) {
322
+ throw formatNpmError(error, `npm view ${packageName}`);
323
+ }
324
+ }
310
325
 
311
326
  // src/shared/cli/loader.ts
312
327
  var frames = ["-", "\\", "|", "/"];
@@ -402,6 +417,26 @@ async function handleOutdatedWorkflow(opts) {
402
417
  const proceed = !((opts.checkOutdated || opts.selection.shouldUpdate) && !opts.outFile);
403
418
  return { proceed, outdated };
404
419
  }
420
+ async function resolveOutdatedWithNpmView(packages) {
421
+ const results = [];
422
+ for (const pkg of packages) {
423
+ try {
424
+ const latest = await npmViewVersion(pkg.name);
425
+ if (latest && pkg.current && latest !== pkg.current) {
426
+ results.push({
427
+ name: pkg.name,
428
+ current: pkg.current,
429
+ wanted: pkg.declared || latest,
430
+ latest,
431
+ type: pkg.type
432
+ });
433
+ }
434
+ } catch {
435
+ continue;
436
+ }
437
+ }
438
+ return results;
439
+ }
405
440
 
406
441
  // src/shared/cli/utils.ts
407
442
  var import_node_fs = require("fs");
@@ -528,8 +563,8 @@ async function buildReportFromNpmTree(tree, opts) {
528
563
  // src/runtimes/node/package-manager.ts
529
564
  async function getExecFileAsync3() {
530
565
  const { execFile } = await import("child_process");
531
- const { promisify: promisify2 } = await import("util");
532
- return promisify2(execFile);
566
+ const { promisify: promisify3 } = await import("util");
567
+ return promisify3(execFile);
533
568
  }
534
569
  async function npmLs(options = {}) {
535
570
  const args = ["ls", "--json"];
@@ -759,10 +794,424 @@ if (isMainModule) {
759
794
  });
760
795
  }
761
796
 
762
- // src/cli.ts
797
+ // src/runtimes/bun/commands.ts
798
+ var import_node_path9 = __toESM(require("path"), 1);
799
+ var import_promises7 = require("fs/promises");
800
+ var import_commander2 = require("commander");
801
+
802
+ // src/runtimes/bun/report.ts
803
+ var import_promises6 = require("fs/promises");
804
+ var import_node_path8 = __toESM(require("path"), 1);
805
+
806
+ // src/runtimes/bun/package-manager.ts
807
+ var import_node_path7 = __toESM(require("path"), 1);
808
+ var import_node_fs2 = require("fs");
809
+ var import_promises5 = require("fs/promises");
810
+ var import_node_util2 = require("util");
811
+ var IGNORED_ENTRIES = /* @__PURE__ */ new Set([".bin"]);
812
+ async function bunPmLs(options = {}) {
813
+ if (options.global) {
814
+ const root = await bunPmRootGlobal();
815
+ const manifest2 = await readJson(import_node_path7.default.join(import_node_path7.default.dirname(root), "package.json"));
816
+ const selections2 = buildSelections(manifest2, { includeDev: false });
817
+ const dependencies2 = selections2.prod.size > 0 ? await collectPackagesForNames(root, mapSelections(selections2.prod)) : await collectPackagesFromNodeModules(root);
818
+ return { dependencies: dependencies2, node_modules_path: root };
819
+ }
820
+ const cwd = options.cwd || process.cwd();
821
+ const nodeModulesPath = await bunPmRootLocal(cwd);
822
+ const manifest = await readJson(import_node_path7.default.join(cwd, "package.json"));
823
+ const includeDev = !options.omitDev;
824
+ const selections = buildSelections(manifest, { includeDev });
825
+ const dependencies = selections.prod.size > 0 ? await collectPackagesForNames(nodeModulesPath, mapSelections(selections.prod)) : await collectPackagesFromNodeModules(nodeModulesPath);
826
+ let devDependencies;
827
+ if (includeDev) {
828
+ if (selections.dev.size > 0) {
829
+ devDependencies = await collectPackagesForNames(
830
+ nodeModulesPath,
831
+ mapSelections(selections.dev)
832
+ );
833
+ } else {
834
+ devDependencies = {};
835
+ }
836
+ }
837
+ return { dependencies, devDependencies, node_modules_path: nodeModulesPath };
838
+ }
839
+ async function bunPmRootGlobal() {
840
+ if (process.env.GEX_BUN_GLOBAL_ROOT) {
841
+ return process.env.GEX_BUN_GLOBAL_ROOT;
842
+ }
843
+ const candidates = getGlobalRootCandidates();
844
+ for (const candidate of candidates) {
845
+ if (candidate && await pathExists(candidate)) {
846
+ return candidate;
847
+ }
848
+ }
849
+ return candidates.find((c) => Boolean(c)) || import_node_path7.default.join(process.env.HOME || process.cwd(), ".bun", "install", "global", "node_modules");
850
+ }
851
+ async function bunPmRootLocal(cwd = process.cwd()) {
852
+ if (process.env.GEX_BUN_LOCAL_ROOT) {
853
+ return process.env.GEX_BUN_LOCAL_ROOT;
854
+ }
855
+ return import_node_path7.default.join(cwd, "node_modules");
856
+ }
857
+ async function bunUpdate(options) {
858
+ const execFileAsync = await getExecFileAsync4();
859
+ const packages = options.packages && options.packages.length > 0 ? options.packages : [];
860
+ if (options.global) {
861
+ const targets = packages.length > 0 ? packages : [];
862
+ const list = targets.length > 0 ? targets : [];
863
+ const cmdPackages = list.map((name) => `${name}@latest`);
864
+ try {
865
+ await execFileAsync("bun", ["add", "-g", ...cmdPackages], {
866
+ cwd: options.cwd,
867
+ maxBuffer: 10 * 1024 * 1024
868
+ });
869
+ } catch (error) {
870
+ const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
871
+ throw new Error(`bun add -g failed: ${stderr || error?.message || "unknown error"}`);
872
+ }
873
+ return;
874
+ }
875
+ const args = ["update"];
876
+ if (packages.length > 0) args.push(...packages);
877
+ try {
878
+ await execFileAsync("bun", args, {
879
+ cwd: options.cwd,
880
+ maxBuffer: 10 * 1024 * 1024
881
+ });
882
+ } catch (error) {
883
+ const stderr = typeof error?.stderr === "string" ? error.stderr.trim() : "";
884
+ throw new Error(`bun update failed: ${stderr || error?.message || "unknown error"}`);
885
+ }
886
+ }
887
+ async function collectPackagesForNames(nodeModulesPath, packages) {
888
+ const result = {};
889
+ await Promise.all(
890
+ packages.map(async ({ name, declared }) => {
891
+ const pkgDir = packageDir(nodeModulesPath, name);
892
+ const manifest = await readJson(import_node_path7.default.join(pkgDir, "package.json"));
893
+ const pkgName = typeof manifest?.name === "string" ? manifest.name : name;
894
+ const version = typeof manifest?.version === "string" ? manifest.version : declared || "";
895
+ result[pkgName] = { version, path: pkgDir };
896
+ })
897
+ );
898
+ return result;
899
+ }
900
+ async function collectPackagesFromNodeModules(root) {
901
+ const result = {};
902
+ const entries = await safeReadDir(root);
903
+ for (const entry of entries) {
904
+ if (!entry || !entry.name || entry.name.startsWith(".") || IGNORED_ENTRIES.has(entry.name)) {
905
+ continue;
906
+ }
907
+ const entryPath = import_node_path7.default.join(root, entry.name);
908
+ if (!await isDir(entry, entryPath)) continue;
909
+ if (entry.name.startsWith("@")) {
910
+ const scopedEntries = await safeReadDir(entryPath);
911
+ for (const scopedEntry of scopedEntries) {
912
+ if (!scopedEntry || !scopedEntry.name || scopedEntry.name.startsWith(".")) continue;
913
+ const scopedPath = import_node_path7.default.join(entryPath, scopedEntry.name);
914
+ if (!await isDir(scopedEntry, scopedPath)) continue;
915
+ const manifest = await readJson(import_node_path7.default.join(scopedPath, "package.json"));
916
+ const pkgName = typeof manifest?.name === "string" ? manifest.name : `${entry.name}/${scopedEntry.name}`;
917
+ const version = typeof manifest?.version === "string" ? manifest.version : "";
918
+ result[pkgName] = { version, path: scopedPath };
919
+ }
920
+ } else {
921
+ const manifest = await readJson(import_node_path7.default.join(entryPath, "package.json"));
922
+ const pkgName = typeof manifest?.name === "string" ? manifest.name : entry.name;
923
+ const version = typeof manifest?.version === "string" ? manifest.version : "";
924
+ result[pkgName] = { version, path: entryPath };
925
+ }
926
+ }
927
+ return result;
928
+ }
929
+ async function readJson(file) {
930
+ try {
931
+ const raw = await (0, import_promises5.readFile)(file, "utf8");
932
+ return JSON.parse(raw);
933
+ } catch {
934
+ return null;
935
+ }
936
+ }
937
+ function packageDir(root, packageName) {
938
+ const segments = packageName.startsWith("@") ? packageName.split("/") : [packageName];
939
+ return import_node_path7.default.join(root, ...segments);
940
+ }
941
+ function buildSelections(manifest, { includeDev }) {
942
+ const prod = /* @__PURE__ */ new Map();
943
+ const dev = /* @__PURE__ */ new Map();
944
+ const addAll = (target, record) => {
945
+ if (!record) return;
946
+ for (const [name, range] of Object.entries(record)) {
947
+ if (!target.has(name)) {
948
+ target.set(name, range);
949
+ }
950
+ }
951
+ };
952
+ addAll(prod, manifest?.dependencies);
953
+ addAll(prod, manifest?.optionalDependencies);
954
+ if (includeDev) addAll(dev, manifest?.devDependencies);
955
+ return { prod, dev };
956
+ }
957
+ function mapSelections(map) {
958
+ return Array.from(map.entries()).map(([name, declared]) => ({ name, declared }));
959
+ }
960
+ async function safeReadDir(dir) {
961
+ try {
962
+ return await (0, import_promises5.readdir)(dir, { withFileTypes: true });
963
+ } catch {
964
+ return [];
965
+ }
966
+ }
967
+ async function isDir(entry, fullPath) {
968
+ if (entry.isDirectory()) return true;
969
+ if (entry.isSymbolicLink()) {
970
+ try {
971
+ const stats = await (0, import_promises5.stat)(fullPath);
972
+ return stats.isDirectory();
973
+ } catch {
974
+ return false;
975
+ }
976
+ }
977
+ return false;
978
+ }
979
+ async function pathExists(target) {
980
+ try {
981
+ await (0, import_promises5.access)(target, import_node_fs2.constants.R_OK);
982
+ return true;
983
+ } catch {
984
+ return false;
985
+ }
986
+ }
987
+ function getGlobalRootCandidates() {
988
+ const candidates = /* @__PURE__ */ new Set();
989
+ const bunInstall = process.env.BUN_INSTALL || (process.env.HOME ? import_node_path7.default.join(process.env.HOME, ".bun") : void 0);
990
+ const maybeAdd = (value) => {
991
+ if (value) candidates.add(value);
992
+ };
993
+ if (bunInstall) {
994
+ maybeAdd(import_node_path7.default.join(bunInstall, "install", "global", "node_modules"));
995
+ maybeAdd(import_node_path7.default.join(bunInstall, "global", "node_modules"));
996
+ }
997
+ if (process.env.XDG_DATA_HOME) {
998
+ maybeAdd(import_node_path7.default.join(process.env.XDG_DATA_HOME, "bun", "install", "global", "node_modules"));
999
+ }
1000
+ maybeAdd("/usr/local/share/bun/global/node_modules");
1001
+ maybeAdd("/opt/homebrew/var/bun/install/global/node_modules");
1002
+ return Array.from(candidates);
1003
+ }
1004
+ async function getExecFileAsync4() {
1005
+ const { execFile } = await import("child_process");
1006
+ return (0, import_node_util2.promisify)(execFile);
1007
+ }
1008
+
1009
+ // src/runtimes/bun/report.ts
1010
+ async function produceReport2(ctx, options) {
1011
+ const toolVersion = await getToolVersion();
1012
+ const cwd = options.cwd || process.cwd();
1013
+ const tree = await bunPmLs({
1014
+ global: ctx === "global",
1015
+ omitDev: ctx === "local" ? Boolean(options.omitDev) : false,
1016
+ cwd
1017
+ });
1018
+ const nodeModulesPath = tree?.node_modules_path;
1019
+ let project_description;
1020
+ let project_homepage;
1021
+ let project_bugs;
1022
+ if (ctx === "local") {
1023
+ try {
1024
+ const pkgRaw = await (0, import_promises6.readFile)(import_node_path8.default.join(cwd, "package.json"), "utf8");
1025
+ const pkg = JSON.parse(pkgRaw);
1026
+ project_description = pkg.description;
1027
+ project_homepage = pkg.homepage;
1028
+ if (typeof pkg.bugs === "string") project_bugs = pkg.bugs;
1029
+ else if (pkg.bugs && typeof pkg.bugs.url === "string") project_bugs = pkg.bugs.url;
1030
+ } catch {
1031
+ }
1032
+ }
1033
+ const resolvedRoot = nodeModulesPath ? nodeModulesPath : ctx === "global" ? await bunPmRootGlobal().catch(() => void 0) : await bunPmRootLocal(cwd).catch(() => `${cwd}/node_modules`);
1034
+ const report = await buildReportFromNpmTree(tree, {
1035
+ context: ctx,
1036
+ includeTree: Boolean(options.fullTree),
1037
+ omitDev: Boolean(options.omitDev),
1038
+ cwd,
1039
+ toolVersion,
1040
+ globalRoot: resolvedRoot
1041
+ });
1042
+ const markdownExtras = { project_description, project_homepage, project_bugs };
1043
+ return { report, markdownExtras };
1044
+ }
1045
+
1046
+ // src/runtimes/bun/commands.ts
1047
+ function addCommonOptions2(cmd, { allowOmitDev }) {
1048
+ cmd.option(
1049
+ "-f, --output-format <format>",
1050
+ "Output format: md or json",
1051
+ (val) => val === "md" ? "md" : "json",
1052
+ "json"
1053
+ ).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(
1054
+ "-u, --update-outdated [packages...]",
1055
+ "Update outdated packages (omit package names to update every package)"
1056
+ );
1057
+ if (allowOmitDev) {
1058
+ cmd.option("--omit-dev", "Exclude devDependencies (local only)", false);
1059
+ }
1060
+ return cmd;
1061
+ }
1062
+ function createLocalCommand2(program) {
1063
+ const localCmd = program.command("local", { isDefault: true }).description("Generate a report for the current Bun project's dependencies");
1064
+ addCommonOptions2(localCmd, { allowOmitDev: true });
1065
+ localCmd.action(async (opts) => {
1066
+ const outputFormat = opts.outputFormat ?? "json";
1067
+ const outFile = opts.outFile;
1068
+ const fullTree = Boolean(opts.fullTree);
1069
+ const omitDev = Boolean(opts.omitDev);
1070
+ const cwd = process.cwd();
1071
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
1072
+ const result = await handleOutdatedWorkflow({
1073
+ checkOutdated: Boolean(opts.checkOutdated),
1074
+ selection,
1075
+ contextLabel: "local",
1076
+ outFile,
1077
+ fetchOutdated: async () => {
1078
+ const tree = await bunPmLs({ cwd, omitDev });
1079
+ const manifest = await readPackageManifest(cwd);
1080
+ const declared = {
1081
+ ...manifest?.dependencies || {},
1082
+ ...manifest?.optionalDependencies || {},
1083
+ ...manifest?.devDependencies || {}
1084
+ };
1085
+ const packages = Object.entries(tree.dependencies).map(([name, node]) => ({
1086
+ name,
1087
+ current: node.version,
1088
+ declared: declared[name],
1089
+ type: "prod"
1090
+ }));
1091
+ if (tree.devDependencies) {
1092
+ for (const [name, node] of Object.entries(tree.devDependencies)) {
1093
+ packages.push({
1094
+ name,
1095
+ current: node.version,
1096
+ declared: declared[name],
1097
+ type: "dev"
1098
+ });
1099
+ }
1100
+ }
1101
+ return resolveOutdatedWithNpmView(packages);
1102
+ },
1103
+ updateRunner: selection.shouldUpdate ? async (packages) => {
1104
+ await bunUpdate({ cwd, packages });
1105
+ } : void 0
1106
+ });
1107
+ if (opts.checkOutdated) {
1108
+ if (result.outdated.length === 0) console.log("All local packages are up to date.");
1109
+ else console.log(formatOutdatedTable(result.outdated));
1110
+ }
1111
+ if (!result.proceed) return;
1112
+ const finalOutFile = outFile;
1113
+ const { report, markdownExtras } = await produceReport2("local", {
1114
+ outputFormat,
1115
+ outFile: finalOutFile,
1116
+ fullTree,
1117
+ omitDev
1118
+ });
1119
+ await outputReport(report, outputFormat, finalOutFile, markdownExtras);
1120
+ });
1121
+ return localCmd;
1122
+ }
1123
+ function createGlobalCommand2(program) {
1124
+ const globalCmd = program.command("global").description("Generate a report of globally installed Bun packages");
1125
+ addCommonOptions2(globalCmd, { allowOmitDev: false });
1126
+ globalCmd.action(async (opts) => {
1127
+ const outputFormat = opts.outputFormat ?? "json";
1128
+ const outFile = opts.outFile;
1129
+ const fullTree = Boolean(opts.fullTree);
1130
+ const cwd = process.cwd();
1131
+ const selection = normalizeUpdateSelection(opts.updateOutdated);
1132
+ const result = await handleOutdatedWorkflow({
1133
+ checkOutdated: Boolean(opts.checkOutdated),
1134
+ selection,
1135
+ contextLabel: "global",
1136
+ outFile,
1137
+ fetchOutdated: async () => {
1138
+ const tree = await bunPmLs({ global: true });
1139
+ const packages = Object.entries(tree.dependencies).map(([name, node]) => ({
1140
+ name,
1141
+ current: node.version,
1142
+ type: "global"
1143
+ }));
1144
+ return resolveOutdatedWithNpmView(packages);
1145
+ },
1146
+ updateRunner: selection.shouldUpdate ? async (packages) => {
1147
+ await bunUpdate({ cwd, global: true, packages });
1148
+ } : void 0
1149
+ });
1150
+ if (opts.checkOutdated) {
1151
+ if (result.outdated.length === 0) console.log("All global packages are up to date.");
1152
+ else console.log(formatOutdatedTable(result.outdated));
1153
+ }
1154
+ if (!result.proceed) return;
1155
+ const finalOutFile = outFile;
1156
+ const { report, markdownExtras } = await produceReport2("global", {
1157
+ outputFormat,
1158
+ outFile: finalOutFile,
1159
+ fullTree
1160
+ });
1161
+ await outputReport(report, outputFormat, finalOutFile, markdownExtras);
1162
+ });
1163
+ return globalCmd;
1164
+ }
1165
+ function createReadCommand2(program) {
1166
+ const readCmd = program.command("read").description(
1167
+ "Read a previously generated report (JSON or Markdown) and either print package names or install them"
1168
+ ).argument("[report]", "Path to report file (JSON or Markdown)", "bun-report.json").option("-r, --report <path>", "Path to report file (JSON or Markdown)").option("-p, --print", "Print package names/versions from the report (default)", false).option("-i, --install", "Install packages from the report using Bun", false);
1169
+ readCmd.action(async (reportArg, opts) => {
1170
+ const chosen = opts.report || reportArg || "bun-report.json";
1171
+ const reportPath = import_node_path9.default.resolve(process.cwd(), chosen);
1172
+ try {
1173
+ const parsed = await loadReportFromFile(reportPath);
1174
+ const doInstall = Boolean(opts.install);
1175
+ const doPrint = Boolean(opts.print) || !doInstall;
1176
+ if (doPrint) {
1177
+ printFromReport(parsed);
1178
+ }
1179
+ if (doInstall) {
1180
+ await installFromReport(parsed, { cwd: process.cwd(), packageManager: "bun" });
1181
+ }
1182
+ } catch (err) {
1183
+ const isMd = isMarkdownReportFile(reportPath);
1184
+ const hint = isMd ? "Try generating a JSON report with: gex-bun global -f json -o global.json, then: gex-bun read global.json" : "Specify a report path with: gex-bun read <path-to-report.json>";
1185
+ console.error(`Failed to read report at ${reportPath}: ${err?.message || err}`);
1186
+ console.error(hint);
1187
+ process.exitCode = 1;
1188
+ }
1189
+ });
1190
+ return readCmd;
1191
+ }
1192
+ async function createProgram2() {
1193
+ const program = new import_commander2.Command().name("gex-bun").description("GEX: Dependency auditing and documentation for Bun (local and global).").version(await getToolVersion());
1194
+ program.addHelpText("beforeAll", `
1195
+ ${ASCII_BANNER}`);
1196
+ createLocalCommand2(program);
1197
+ createGlobalCommand2(program);
1198
+ createReadCommand2(program);
1199
+ return program;
1200
+ }
1201
+ async function readPackageManifest(cwd) {
1202
+ try {
1203
+ const raw = await (0, import_promises7.readFile)(import_node_path9.default.join(cwd, "package.json"), "utf8");
1204
+ return JSON.parse(raw);
1205
+ } catch {
1206
+ return null;
1207
+ }
1208
+ }
1209
+
1210
+ // src/runtimes/bun/cli.ts
763
1211
  var import_meta3 = {};
764
1212
  async function run2(argv = process.argv) {
765
- return run(argv);
1213
+ const program = await createProgram2();
1214
+ await program.parseAsync(argv);
766
1215
  }
767
1216
  var isMainModule2 = (() => {
768
1217
  try {
@@ -779,6 +1228,66 @@ var isMainModule2 = (() => {
779
1228
  })();
780
1229
  if (isMainModule2) {
781
1230
  run2().catch((error) => {
1231
+ console.error("Bun CLI error:", error);
1232
+ process.exitCode = 1;
1233
+ });
1234
+ }
1235
+
1236
+ // src/cli.ts
1237
+ var import_meta4 = {};
1238
+ async function promptRuntimeSelection(io = {}) {
1239
+ const input = io.input ?? process.stdin;
1240
+ const output = io.output ?? process.stdout;
1241
+ return new Promise((resolve) => {
1242
+ const rl = import_node_readline.default.createInterface({ input, output });
1243
+ output.write("\nSelect a runtime to use:\n");
1244
+ output.write(" 1) gex-bun (Bun package manager)\n");
1245
+ output.write(" 2) gex-npm (npm / Node.js package manager)\n\n");
1246
+ rl.question("Enter your choice (1-2): ", (answer) => {
1247
+ rl.close();
1248
+ const choice = answer.trim().toLowerCase();
1249
+ if (choice === "1" || choice === "gex-bun" || choice === "bun") {
1250
+ resolve("bun");
1251
+ return;
1252
+ }
1253
+ if (choice === "2" || choice === "gex-npm" || choice === "gex-node" || choice === "npm" || choice === "node") {
1254
+ resolve("npm");
1255
+ return;
1256
+ }
1257
+ resolve(null);
1258
+ });
1259
+ });
1260
+ }
1261
+ async function run3(argv = process.argv, io = {}) {
1262
+ const effectiveArgv = argv ?? process.argv;
1263
+ const choice = await promptRuntimeSelection(io);
1264
+ if (choice === "bun") {
1265
+ await run2(effectiveArgv);
1266
+ return;
1267
+ }
1268
+ if (choice === "npm") {
1269
+ await run(effectiveArgv);
1270
+ return;
1271
+ }
1272
+ const output = io.output ?? process.stdout;
1273
+ output.write("Invalid selection. Please run `gex` again and choose 1 or 2.\n");
1274
+ process.exitCode = 1;
1275
+ }
1276
+ var isMainModule3 = (() => {
1277
+ try {
1278
+ if (typeof require !== "undefined" && typeof module !== "undefined") {
1279
+ return require.main === module;
1280
+ }
1281
+ if (typeof import_meta4 !== "undefined") {
1282
+ return import_meta4.url === `file://${process.argv[1]}`;
1283
+ }
1284
+ return false;
1285
+ } catch {
1286
+ return false;
1287
+ }
1288
+ })();
1289
+ if (isMainModule3) {
1290
+ run3().catch((error) => {
782
1291
  console.error("CLI error:", error);
783
1292
  process.exitCode = 1;
784
1293
  });