@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 +16 -4
- package/dist/cli-bun.mjs +259 -3
- package/dist/cli-bun.mjs.map +1 -1
- package/dist/cli-node.cjs +202 -8
- package/dist/cli-node.cjs.map +1 -1
- package/dist/cli-node.mjs +202 -8
- package/dist/cli-node.mjs.map +1 -1
- package/dist/cli.cjs +202 -8
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +202 -8
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
-
|
|
53
|
-
-
|
|
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
|
|
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) {
|