ripencli 1.1.0 → 1.1.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.
Files changed (3) hide show
  1. package/README.md +3 -0
  2. package/dist/cli.js +146 -95
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -47,6 +47,9 @@ ripen -g
47
47
  # Show all packages, not just outdated ones
48
48
  ripen --all
49
49
 
50
+ # Show all global packages, not just outdated ones
51
+ ripen -g -a
52
+
50
53
  # Help
51
54
  ripen --help
52
55
  ```
package/dist/cli.js CHANGED
@@ -221,7 +221,7 @@ async function fetchRegistryInfoWithRetry(packageName) {
221
221
  clearTimeout(timeout);
222
222
  if (!res.ok) return null;
223
223
  const data = await res.json();
224
- const version = data["dist-tags"]?.latest ?? null;
224
+ const version = data["dist-tags"]?.latest ?? Object.keys(data.versions ?? {}).at(-1) ?? null;
225
225
  if (!version) return null;
226
226
  return {
227
227
  version,
@@ -291,6 +291,108 @@ async function getOutdatedPackages(manager, cwd, global = false, onLine, showAll
291
291
  packages
292
292
  };
293
293
  }
294
+ /** Fetch publish dates from the registry and attach them to existing packages in-place. */
295
+ async function hydratePublishDates(packages) {
296
+ if (packages.length === 0) return;
297
+ const info = await fetchAllLatest(packages.map((p) => p.name), 8);
298
+ for (const pkg of packages) {
299
+ const r = info.get(pkg.name);
300
+ if (r?.publishedAt) pkg.latestPublishedAt = r.publishedAt;
301
+ }
302
+ }
303
+ /**
304
+ * List all globally installed packages for a manager.
305
+ * Returns name + currently installed version.
306
+ */
307
+ async function listGlobalPackages(manager, cwd) {
308
+ try {
309
+ if (manager === "npm") {
310
+ const { stdout } = await execa("npm", [
311
+ "list",
312
+ "-g",
313
+ "--depth=0",
314
+ "--json"
315
+ ], {
316
+ cwd,
317
+ reject: false
318
+ });
319
+ const data = JSON.parse(stdout);
320
+ return Object.entries(data.dependencies ?? {}).map(([name, info]) => ({
321
+ name,
322
+ current: info.version ?? "N/A"
323
+ }));
324
+ }
325
+ if (manager === "pnpm") {
326
+ const { stdout } = await execa("pnpm", [
327
+ "list",
328
+ "-g",
329
+ "--json"
330
+ ], {
331
+ cwd,
332
+ reject: false
333
+ });
334
+ const data = JSON.parse(stdout);
335
+ const deps = Array.isArray(data) ? data[0]?.dependencies ?? {} : data.dependencies ?? {};
336
+ return Object.entries(deps).map(([name, info]) => ({
337
+ name,
338
+ current: info.version ?? "N/A"
339
+ }));
340
+ }
341
+ if (manager === "yarn") {
342
+ const { stdout } = await execa("yarn", [
343
+ "global",
344
+ "list",
345
+ "--depth=0",
346
+ "--json"
347
+ ], {
348
+ cwd,
349
+ reject: false
350
+ });
351
+ const pkgs = [];
352
+ for (const line of stdout.trim().split("\n")) try {
353
+ const obj = JSON.parse(line);
354
+ if (obj.type === "tree" && obj.data?.trees) for (const tree of obj.data.trees) {
355
+ const match = tree.name?.match(/^(.+)@([^@]+)$/);
356
+ if (match) pkgs.push({
357
+ name: match[1],
358
+ current: match[2]
359
+ });
360
+ }
361
+ } catch {}
362
+ return pkgs;
363
+ }
364
+ } catch {}
365
+ return [];
366
+ }
367
+ /** Global mode, showAll=true: list all installed packages then check registry for latest. */
368
+ async function getGlobalAllPackages(manager, cwd, onLine) {
369
+ const installed = await listGlobalPackages(manager, cwd);
370
+ if (installed.length === 0) return {
371
+ ok: true,
372
+ packages: []
373
+ };
374
+ const registryInfo = await fetchAllLatest(installed.map((p) => p.name), 8, onLine);
375
+ const packages = [];
376
+ for (const dep of installed) {
377
+ const info = registryInfo.get(dep.name);
378
+ if (!info) continue;
379
+ packages.push({
380
+ name: dep.name,
381
+ current: dep.current,
382
+ wanted: info.version,
383
+ latest: info.version,
384
+ dependent: "",
385
+ type: "global",
386
+ selected: false,
387
+ targetVersion: info.version,
388
+ latestPublishedAt: info.publishedAt || void 0
389
+ });
390
+ }
391
+ return {
392
+ ok: true,
393
+ packages
394
+ };
395
+ }
294
396
  /** Global mode: shell out to manager's outdated command */
295
397
  async function getGlobalOutdatedPackages(manager, cwd, onLine) {
296
398
  const args = [
@@ -336,34 +438,36 @@ async function getGlobalOutdatedPackages(manager, cwd, onLine) {
336
438
  ok: true,
337
439
  packages: []
338
440
  };
441
+ let packages;
339
442
  if (manager === "yarn") try {
340
- return {
341
- ok: true,
342
- packages: parseYarnOutdated(raw, true)
343
- };
443
+ packages = parseYarnOutdated(raw, true);
344
444
  } catch {
345
445
  return {
346
446
  ok: false,
347
447
  error: "Failed to parse yarn outdated output. Try again."
348
448
  };
349
449
  }
350
- const jsonStr = extractJson(raw);
351
- if (!jsonStr) return {
352
- ok: false,
353
- error: stderr.trim() || raw.slice(0, 120)
354
- };
355
- try {
356
- const data = JSON.parse(jsonStr);
357
- return {
358
- ok: true,
359
- packages: manager === "pnpm" ? parsePnpmOutdated(data) : parseNpmOutdated(data)
360
- };
361
- } catch {
362
- return {
450
+ else {
451
+ const jsonStr = extractJson(raw);
452
+ if (!jsonStr) return {
363
453
  ok: false,
364
- error: "Failed to parse outdated output. Try again."
454
+ error: stderr.trim() || raw.slice(0, 120)
365
455
  };
456
+ try {
457
+ const data = JSON.parse(jsonStr);
458
+ packages = manager === "pnpm" ? parsePnpmOutdated(data) : parseNpmOutdated(data);
459
+ } catch {
460
+ return {
461
+ ok: false,
462
+ error: "Failed to parse outdated output. Try again."
463
+ };
464
+ }
366
465
  }
466
+ await hydratePublishDates(packages);
467
+ return {
468
+ ok: true,
469
+ packages
470
+ };
367
471
  }
368
472
  /**
369
473
  * Extract the first top-level JSON object from a string that may contain
@@ -419,15 +523,16 @@ const ALL_MANAGERS = [
419
523
  "yarn"
420
524
  ];
421
525
  /**
422
- * Check all available package managers for global outdated packages in parallel.
526
+ * Check all available package managers for global packages in parallel.
423
527
  * Each returned package is tagged with its owning manager.
528
+ * showAll=true lists every installed package, not just outdated ones.
424
529
  */
425
- async function getAllGlobalOutdated(cwd, onLine) {
530
+ async function getAllGlobalOutdated(cwd, onLine, showAll = false) {
426
531
  const managers = (await Promise.all(ALL_MANAGERS.map(async (m) => ({
427
532
  manager: m,
428
533
  ok: await isManagerAvailable(m)
429
534
  })))).filter((a) => a.ok).map((a) => a.manager);
430
- const results = await Promise.all(managers.map((m) => getOutdatedPackages(m, cwd, true, onLine)));
535
+ const results = await Promise.all(managers.map((m) => showAll ? getGlobalAllPackages(m, cwd, onLine) : getOutdatedPackages(m, cwd, true, onLine)));
431
536
  const allPackages = [];
432
537
  for (let i = 0; i < managers.length; i++) {
433
538
  const result = results[i];
@@ -2111,10 +2216,9 @@ function Settings({ config, onConfigChange, onClose }) {
2111
2216
  }
2112
2217
  //#endregion
2113
2218
  //#region src/ui/SelfUpdatePrompt.tsx
2114
- function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUpdate, onSkip }) {
2219
+ function SelfUpdatePrompt({ currentVersion, latestVersion, onUpdate, onSkip }) {
2115
2220
  const [selected, setSelected] = useState(0);
2116
- useInput((input, key) => {
2117
- if (updating) return;
2221
+ useInput((_input, key) => {
2118
2222
  if (key.upArrow) setSelected(0);
2119
2223
  if (key.downArrow) setSelected(1);
2120
2224
  if (key.return) if (selected === 0) onUpdate();
@@ -2131,9 +2235,9 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
2131
2235
  }),
2132
2236
  /* @__PURE__ */ jsx(Box, {
2133
2237
  marginTop: 1,
2134
- flexDirection: "column",
2135
2238
  children: /* @__PURE__ */ jsxs(Text, { children: [
2136
- "A new version is available! ",
2239
+ "A new version is available!",
2240
+ " ",
2137
2241
  /* @__PURE__ */ jsx(Text, {
2138
2242
  color: "gray",
2139
2243
  children: currentVersion
@@ -2148,23 +2252,7 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
2148
2252
  })
2149
2253
  ] })
2150
2254
  }),
2151
- error && /* @__PURE__ */ jsxs(Box, {
2152
- marginTop: 1,
2153
- children: [/* @__PURE__ */ jsxs(Text, {
2154
- color: "red",
2155
- children: ["✗ Update failed: ", error]
2156
- }), /* @__PURE__ */ jsx(Text, {
2157
- color: "gray",
2158
- children: " Continuing…"
2159
- })]
2160
- }),
2161
- updating ? /* @__PURE__ */ jsx(Box, {
2162
- marginTop: 1,
2163
- children: /* @__PURE__ */ jsx(Text, {
2164
- color: "gray",
2165
- children: "Updating ripen…"
2166
- })
2167
- }) : /* @__PURE__ */ jsxs(Box, {
2255
+ /* @__PURE__ */ jsxs(Box, {
2168
2256
  marginTop: 1,
2169
2257
  flexDirection: "column",
2170
2258
  children: [/* @__PURE__ */ jsxs(Text, { children: [selected === 0 ? /* @__PURE__ */ jsx(Text, {
@@ -2172,7 +2260,7 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
2172
2260
  children: "❯ "
2173
2261
  }) : /* @__PURE__ */ jsx(Text, { children: " " }), /* @__PURE__ */ jsx(Text, {
2174
2262
  color: selected === 0 ? "white" : "gray",
2175
- children: "Update"
2263
+ children: "Update (copies command to clipboard)"
2176
2264
  })] }), /* @__PURE__ */ jsxs(Text, { children: [selected === 1 ? /* @__PURE__ */ jsx(Text, {
2177
2265
  color: "greenBright",
2178
2266
  children: "❯ "
@@ -2233,8 +2321,6 @@ function TerminalOutputBox({ message, command, outputLines, maxLines }) {
2233
2321
  //#region src/ui/hooks/use-self-update.ts
2234
2322
  function useSelfUpdate(currentVersion, installManager) {
2235
2323
  const [latestVersion, setLatestVersion] = useState(null);
2236
- const [selfUpdating, setSelfUpdating] = useState(false);
2237
- const [selfUpdateError, setSelfUpdateError] = useState(null);
2238
2324
  const [checkComplete, setCheckComplete] = useState(false);
2239
2325
  const [hasUpdate, setHasUpdate] = useState(false);
2240
2326
  useEffect(() => {
@@ -2251,33 +2337,17 @@ function useSelfUpdate(currentVersion, installManager) {
2251
2337
  cancelled = true;
2252
2338
  };
2253
2339
  }, []);
2254
- const performUpdate = async () => {
2255
- setSelfUpdating(true);
2256
- try {
2257
- await execa(installManager, installManager === "yarn" ? [
2258
- "global",
2259
- "add",
2260
- `ripencli@${latestVersion}`
2261
- ] : [
2262
- "add",
2263
- "--global",
2264
- `ripencli@${latestVersion}`
2265
- ]);
2266
- setSelfUpdating(false);
2267
- return true;
2268
- } catch (err) {
2269
- setSelfUpdateError(err.message ?? "Unknown error");
2270
- setSelfUpdating(false);
2271
- return false;
2272
- }
2340
+ const buildUpdateCommand = () => {
2341
+ const version = latestVersion ?? "latest";
2342
+ if (installManager === "yarn") return `yarn global add ripencli@${version}`;
2343
+ if (installManager === "bun") return `bun add -g ripencli@${version}`;
2344
+ return `${installManager} add -g ripencli@${version}`;
2273
2345
  };
2274
2346
  return {
2275
2347
  latestVersion,
2276
- selfUpdating,
2277
- selfUpdateError,
2278
2348
  checkComplete,
2279
2349
  hasUpdate,
2280
- performUpdate
2350
+ buildUpdateCommand
2281
2351
  };
2282
2352
  }
2283
2353
  //#endregion
@@ -2367,7 +2437,7 @@ function App({ project, global, showAll, version, installManager, onCancelled, o
2367
2437
  useInput((_input, key) => {
2368
2438
  if (key.ctrl && _input === "c") setScreen("cancelled");
2369
2439
  });
2370
- useExitOnScreen(screen, ["self-update-done", "empty"], exit);
2440
+ useExitOnScreen(screen, ["empty"], exit);
2371
2441
  useExitOnScreen(screen, ["cancelled"], exit, {
2372
2442
  delay: 200,
2373
2443
  beforeExit: () => onCancelled?.()
@@ -2377,9 +2447,11 @@ function App({ project, global, showAll, version, installManager, onCancelled, o
2377
2447
  if (screen !== "self-update-check") return;
2378
2448
  setScreen(selfUpdate.hasUpdate ? "self-update" : "loading");
2379
2449
  }, [selfUpdate.checkComplete]);
2380
- const handleSelfUpdate = async () => {
2381
- if (await selfUpdate.performUpdate()) setScreen("self-update-done");
2382
- else setTimeout(() => setScreen("loading"), 2e3);
2450
+ const handleSelfUpdate = () => {
2451
+ const cmd = selfUpdate.buildUpdateCommand();
2452
+ copyToClipboard(cmd);
2453
+ onCopied?.([cmd]);
2454
+ exit();
2383
2455
  };
2384
2456
  const [fetchStarted, setFetchStarted] = useState(false);
2385
2457
  useEffect(() => {
@@ -2387,7 +2459,7 @@ function App({ project, global, showAll, version, installManager, onCancelled, o
2387
2459
  setFetchStarted(true);
2388
2460
  terminal.setLoadingMsg("Checking for outdated packages…");
2389
2461
  terminal.setTerminalCmd(global ? "Checking all package managers…" : "Checking npm registry…");
2390
- (global ? getAllGlobalOutdated(project.cwd, terminal.onLine) : getOutdatedPackages(project.manager, project.cwd, false, terminal.onLine, showAll)).then((result) => {
2462
+ (global ? getAllGlobalOutdated(project.cwd, terminal.onLine, showAll) : getOutdatedPackages(project.manager, project.cwd, false, terminal.onLine, showAll)).then((result) => {
2391
2463
  if (!result.ok) {
2392
2464
  setErrorMsg(result.error);
2393
2465
  setScreen("error");
@@ -2433,30 +2505,9 @@ function App({ project, global, showAll, version, installManager, onCancelled, o
2433
2505
  if (screen === "self-update") return /* @__PURE__ */ jsx(SelfUpdatePrompt, {
2434
2506
  currentVersion: version,
2435
2507
  latestVersion: selfUpdate.latestVersion,
2436
- updating: selfUpdate.selfUpdating,
2437
- error: selfUpdate.selfUpdateError,
2438
2508
  onUpdate: handleSelfUpdate,
2439
2509
  onSkip: () => setScreen("loading")
2440
2510
  });
2441
- if (screen === "self-update-done") return /* @__PURE__ */ jsxs(Box, {
2442
- flexDirection: "column",
2443
- padding: 1,
2444
- children: [/* @__PURE__ */ jsxs(Text, {
2445
- color: "greenBright",
2446
- bold: true,
2447
- children: [" ", "ripen"]
2448
- }), /* @__PURE__ */ jsx(Box, {
2449
- marginTop: 1,
2450
- children: /* @__PURE__ */ jsxs(Text, {
2451
- color: "green",
2452
- children: [
2453
- "✓ Updated to v",
2454
- selfUpdate.latestVersion,
2455
- ". Run ripen again to use the new version."
2456
- ]
2457
- })
2458
- })]
2459
- });
2460
2511
  if (screen === "loading") return /* @__PURE__ */ jsx(TerminalOutputBox, {
2461
2512
  message: terminal.loadingMsg,
2462
2513
  command: terminal.terminalCmd,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ripencli",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Interactive dependency updater for npm, pnpm, yarn, and bun",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -28,7 +28,7 @@
28
28
  },
29
29
  "type": "module",
30
30
  "bin": {
31
- "ripen": "./dist/cli.js"
31
+ "ripen": "dist/cli.js"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsdown",