ripencli 1.0.1 → 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.
- package/README.md +20 -12
- package/dist/cli.js +289 -336
- package/package.json +12 -12
package/README.md
CHANGED
|
@@ -11,10 +11,12 @@
|
|
|
11
11
|
## Features
|
|
12
12
|
|
|
13
13
|
- **Interactive TUI** — navigate packages with arrow keys, select with space
|
|
14
|
+
- **Clipboard-first** — copies the ready-to-run update command to your clipboard, nothing is executed for you
|
|
15
|
+
- **Publish age** — shows how long ago the latest version was published (yellow if < 1 day, useful for pnpm's `minimumReleaseAge`)
|
|
14
16
|
- **Version picker** — choose any specific version from the npm registry, not just latest
|
|
15
17
|
- **Changelog viewer** — see GitHub release notes before you update
|
|
16
18
|
- **npm, pnpm, yarn & bun** — auto-detects your package manager
|
|
17
|
-
- **Global packages** — check
|
|
19
|
+
- **Global packages** — check global installs across all* package managers
|
|
18
20
|
- **Show all packages** — `ripen --all` lists every dependency, not just outdated ones (great for checking changelogs or downgrading)
|
|
19
21
|
- **Self-update** — notifies you when a new version of ripen is available
|
|
20
22
|
- **Major bump warnings** — highlights potentially breaking updates
|
|
@@ -45,30 +47,36 @@ ripen -g
|
|
|
45
47
|
# Show all packages, not just outdated ones
|
|
46
48
|
ripen --all
|
|
47
49
|
|
|
50
|
+
# Show all global packages, not just outdated ones
|
|
51
|
+
ripen -g -a
|
|
52
|
+
|
|
48
53
|
# Help
|
|
49
54
|
ripen --help
|
|
50
55
|
```
|
|
51
56
|
|
|
52
57
|
## Controls
|
|
53
58
|
|
|
54
|
-
| Key
|
|
55
|
-
|
|
|
56
|
-
| `↑ ↓`
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
|
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
59
|
+
| Key | Action |
|
|
60
|
+
| ------------ | ----------------------------------- |
|
|
61
|
+
| `↑ ↓` | Navigate packages |
|
|
62
|
+
| `PgUp PgDn` | Scroll a full page |
|
|
63
|
+
| `Tab` | Jump between groups |
|
|
64
|
+
| `← →` | Collapse / expand scope groups |
|
|
65
|
+
| `space` | Toggle select |
|
|
66
|
+
| `v` | Pick specific version |
|
|
67
|
+
| `c` | View changelog / release notes |
|
|
68
|
+
| `enter` | Copy update command & exit |
|
|
69
|
+
| `s` | Open settings |
|
|
70
|
+
| `esc` | Cancel / go back |
|
|
63
71
|
|
|
64
72
|
## How it works
|
|
65
73
|
|
|
66
74
|
1. Reads your `package.json` and checks each dependency against the npm registry directly
|
|
67
|
-
2. Detects your package manager from the lock file (`bun.lock`, `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`)
|
|
75
|
+
2. Detects your package manager from the lock file (`bun.lock`, `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`)
|
|
68
76
|
3. Shows outdated packages in a colorful interactive list (use `--all` to show every package, including up-to-date ones)
|
|
69
77
|
4. Press `v` on any package to pick a specific version from the npm registry
|
|
70
78
|
5. Press `c` to see GitHub release notes between your current and target version
|
|
71
|
-
6. Select the ones you want and press enter — ripen
|
|
79
|
+
6. Select the ones you want and press `enter` — ripen copies the exact update command to your clipboard and exits
|
|
72
80
|
|
|
73
81
|
## Settings
|
|
74
82
|
|
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { Box, Text, render, useApp, useInput,
|
|
2
|
+
import { Box, Text, render, useApp, useInput, useWindowSize } from "ink";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
7
|
import { execa } from "execa";
|
|
8
8
|
import { homedir } from "os";
|
|
9
|
+
import { exec, execSync } from "child_process";
|
|
9
10
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
10
11
|
import { ScrollView } from "ink-scroll-view";
|
|
11
|
-
import { exec } from "child_process";
|
|
12
12
|
//#region src/detector.ts
|
|
13
13
|
function detectPackageManager(cwd) {
|
|
14
14
|
if (existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
@@ -98,7 +98,7 @@ function parseBaseVersion(range) {
|
|
|
98
98
|
const prefixMatch = v.match(/^([~^>=<]+)/);
|
|
99
99
|
const prefix = prefixMatch ? prefixMatch[1] : "";
|
|
100
100
|
v = v.replace(/^[~^>=<]+/, "").trim();
|
|
101
|
-
if (/^\d
|
|
101
|
+
if (/^\d+(\.\d+(\.\d+)?)?/.test(v)) return {
|
|
102
102
|
version: v,
|
|
103
103
|
prefix
|
|
104
104
|
};
|
|
@@ -213,14 +213,20 @@ function readPackageJsonDeps(cwd) {
|
|
|
213
213
|
}
|
|
214
214
|
return entries;
|
|
215
215
|
}
|
|
216
|
-
async function
|
|
216
|
+
async function fetchRegistryInfoWithRetry(packageName) {
|
|
217
217
|
for (let attempt = 0; attempt < 3; attempt++) try {
|
|
218
218
|
const controller = new AbortController();
|
|
219
219
|
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
220
|
-
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}
|
|
220
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, { signal: controller.signal });
|
|
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 ?? Object.keys(data.versions ?? {}).at(-1) ?? null;
|
|
225
|
+
if (!version) return null;
|
|
226
|
+
return {
|
|
227
|
+
version,
|
|
228
|
+
publishedAt: data.time?.[version] ?? ""
|
|
229
|
+
};
|
|
224
230
|
} catch {
|
|
225
231
|
if (attempt === 2) return null;
|
|
226
232
|
}
|
|
@@ -234,7 +240,7 @@ async function fetchAllLatest(names, concurrency, onLine) {
|
|
|
234
240
|
while (index < names.length) {
|
|
235
241
|
const name = names[index++];
|
|
236
242
|
onLine?.(`Checking ${name} (${completed + 1}/${names.length})...`);
|
|
237
|
-
results.set(name, await
|
|
243
|
+
results.set(name, await fetchRegistryInfoWithRetry(name));
|
|
238
244
|
completed++;
|
|
239
245
|
}
|
|
240
246
|
}
|
|
@@ -263,8 +269,9 @@ async function getOutdatedPackages(manager, cwd, global = false, onLine, showAll
|
|
|
263
269
|
};
|
|
264
270
|
const packages = [];
|
|
265
271
|
for (const dep of deps) {
|
|
266
|
-
const
|
|
267
|
-
if (!
|
|
272
|
+
const info = latestVersions.get(dep.name);
|
|
273
|
+
if (!info) continue;
|
|
274
|
+
const { version: latest, publishedAt } = info;
|
|
268
275
|
if (!showAll && !isNewerVersion(dep.current, latest)) continue;
|
|
269
276
|
packages.push({
|
|
270
277
|
name: dep.name,
|
|
@@ -275,7 +282,110 @@ async function getOutdatedPackages(manager, cwd, global = false, onLine, showAll
|
|
|
275
282
|
type: dep.type,
|
|
276
283
|
selected: false,
|
|
277
284
|
targetVersion: latest,
|
|
278
|
-
rangePrefix: dep.prefix
|
|
285
|
+
rangePrefix: dep.prefix,
|
|
286
|
+
latestPublishedAt: publishedAt || void 0
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
ok: true,
|
|
291
|
+
packages
|
|
292
|
+
};
|
|
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
|
|
279
389
|
});
|
|
280
390
|
}
|
|
281
391
|
return {
|
|
@@ -328,34 +438,36 @@ async function getGlobalOutdatedPackages(manager, cwd, onLine) {
|
|
|
328
438
|
ok: true,
|
|
329
439
|
packages: []
|
|
330
440
|
};
|
|
441
|
+
let packages;
|
|
331
442
|
if (manager === "yarn") try {
|
|
332
|
-
|
|
333
|
-
ok: true,
|
|
334
|
-
packages: parseYarnOutdated(raw, true)
|
|
335
|
-
};
|
|
443
|
+
packages = parseYarnOutdated(raw, true);
|
|
336
444
|
} catch {
|
|
337
445
|
return {
|
|
338
446
|
ok: false,
|
|
339
447
|
error: "Failed to parse yarn outdated output. Try again."
|
|
340
448
|
};
|
|
341
449
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
error: stderr.trim() || raw.slice(0, 120)
|
|
346
|
-
};
|
|
347
|
-
try {
|
|
348
|
-
const data = JSON.parse(jsonStr);
|
|
349
|
-
return {
|
|
350
|
-
ok: true,
|
|
351
|
-
packages: manager === "pnpm" ? parsePnpmOutdated(data) : parseNpmOutdated(data)
|
|
352
|
-
};
|
|
353
|
-
} catch {
|
|
354
|
-
return {
|
|
450
|
+
else {
|
|
451
|
+
const jsonStr = extractJson(raw);
|
|
452
|
+
if (!jsonStr) return {
|
|
355
453
|
ok: false,
|
|
356
|
-
error:
|
|
454
|
+
error: stderr.trim() || raw.slice(0, 120)
|
|
357
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
|
+
}
|
|
358
465
|
}
|
|
466
|
+
await hydratePublishDates(packages);
|
|
467
|
+
return {
|
|
468
|
+
ok: true,
|
|
469
|
+
packages
|
|
470
|
+
};
|
|
359
471
|
}
|
|
360
472
|
/**
|
|
361
473
|
* Extract the first top-level JSON object from a string that may contain
|
|
@@ -411,15 +523,16 @@ const ALL_MANAGERS = [
|
|
|
411
523
|
"yarn"
|
|
412
524
|
];
|
|
413
525
|
/**
|
|
414
|
-
* Check all available package managers for global
|
|
526
|
+
* Check all available package managers for global packages in parallel.
|
|
415
527
|
* Each returned package is tagged with its owning manager.
|
|
528
|
+
* showAll=true lists every installed package, not just outdated ones.
|
|
416
529
|
*/
|
|
417
|
-
async function getAllGlobalOutdated(cwd, onLine) {
|
|
530
|
+
async function getAllGlobalOutdated(cwd, onLine, showAll = false) {
|
|
418
531
|
const managers = (await Promise.all(ALL_MANAGERS.map(async (m) => ({
|
|
419
532
|
manager: m,
|
|
420
533
|
ok: await isManagerAvailable(m)
|
|
421
534
|
})))).filter((a) => a.ok).map((a) => a.manager);
|
|
422
|
-
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)));
|
|
423
536
|
const allPackages = [];
|
|
424
537
|
for (let i = 0; i < managers.length; i++) {
|
|
425
538
|
const result = results[i];
|
|
@@ -460,22 +573,28 @@ function parseYarnOutdated(raw, global) {
|
|
|
460
573
|
}
|
|
461
574
|
//#endregion
|
|
462
575
|
//#region src/executor.ts
|
|
463
|
-
|
|
464
|
-
const
|
|
576
|
+
function buildUpdateCommands(manager, packages, global = false) {
|
|
577
|
+
const commands = [];
|
|
465
578
|
const deps = packages.filter((p) => !global && p.type === "dependencies");
|
|
466
579
|
const devDeps = packages.filter((p) => !global && p.type === "devDependencies");
|
|
467
|
-
const globalPkgs = packages.filter((p) => global);
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
580
|
+
const globalPkgs = packages.filter((p) => global || p.type === "global");
|
|
581
|
+
const makeCmd = (mgr, pkgs, flags) => {
|
|
582
|
+
const pkgArgs = pkgs.map((pkg) => {
|
|
583
|
+
const version = pkg.targetVersion ?? pkg.latest;
|
|
584
|
+
return `${pkg.name}@${version}`;
|
|
585
|
+
});
|
|
586
|
+
return `${mgr} ${(mgr === "yarn" && global ? [
|
|
587
|
+
"global",
|
|
588
|
+
"add",
|
|
589
|
+
...pkgArgs
|
|
590
|
+
] : [
|
|
591
|
+
"add",
|
|
592
|
+
...flags,
|
|
593
|
+
...pkgArgs
|
|
594
|
+
]).join(" ")}`;
|
|
595
|
+
};
|
|
596
|
+
if (deps.length > 0) commands.push(makeCmd(manager, deps, []));
|
|
597
|
+
if (devDeps.length > 0) commands.push(makeCmd(manager, devDeps, [manager === "bun" ? "-d" : "-D"]));
|
|
479
598
|
if (globalPkgs.length > 0) {
|
|
480
599
|
const byManager = /* @__PURE__ */ new Map();
|
|
481
600
|
for (const pkg of globalPkgs) {
|
|
@@ -485,72 +604,10 @@ async function updatePackages(manager, packages, cwd, global = false, onLine) {
|
|
|
485
604
|
}
|
|
486
605
|
for (const [mgr, pkgs] of byManager) {
|
|
487
606
|
const globalFlags = mgr === "yarn" ? [] : mgr === "bun" ? ["-g"] : ["--global"];
|
|
488
|
-
|
|
489
|
-
mgr,
|
|
490
|
-
pkgs,
|
|
491
|
-
flags: globalFlags
|
|
492
|
-
});
|
|
607
|
+
commands.push(makeCmd(mgr, pkgs, globalFlags));
|
|
493
608
|
}
|
|
494
609
|
}
|
|
495
|
-
|
|
496
|
-
const pkgArgs = batch.pkgs.map((pkg) => {
|
|
497
|
-
const version = pkg.targetVersion ?? pkg.latest;
|
|
498
|
-
const prefix = pkg.rangePrefix ?? "";
|
|
499
|
-
return `${pkg.name}@${prefix}${version}`;
|
|
500
|
-
});
|
|
501
|
-
const args = batch.mgr === "yarn" && global ? [
|
|
502
|
-
"global",
|
|
503
|
-
"add",
|
|
504
|
-
...pkgArgs
|
|
505
|
-
] : [
|
|
506
|
-
"add",
|
|
507
|
-
...batch.flags,
|
|
508
|
-
...pkgArgs
|
|
509
|
-
];
|
|
510
|
-
onLine?.(`$ ${batch.mgr} ${args.join(" ")}`);
|
|
511
|
-
try {
|
|
512
|
-
const proc = execa(batch.mgr, args, {
|
|
513
|
-
cwd,
|
|
514
|
-
reject: false
|
|
515
|
-
});
|
|
516
|
-
if (onLine) {
|
|
517
|
-
const forward = (chunk) => {
|
|
518
|
-
const lines = chunk.toString().split("\n");
|
|
519
|
-
for (const line of lines) {
|
|
520
|
-
const trimmed = line.trim();
|
|
521
|
-
if (trimmed) onLine(trimmed);
|
|
522
|
-
}
|
|
523
|
-
};
|
|
524
|
-
proc.stderr?.on("data", forward);
|
|
525
|
-
proc.stdout?.on("data", forward);
|
|
526
|
-
}
|
|
527
|
-
const result = await proc;
|
|
528
|
-
if (result.exitCode !== 0) {
|
|
529
|
-
const errMsg = result.stderr?.trim() || `exited with code ${result.exitCode}`;
|
|
530
|
-
for (const pkg of batch.pkgs) results.push({
|
|
531
|
-
name: pkg.name,
|
|
532
|
-
fromVersion: pkg.current,
|
|
533
|
-
version: pkg.targetVersion ?? pkg.latest,
|
|
534
|
-
success: false,
|
|
535
|
-
error: errMsg
|
|
536
|
-
});
|
|
537
|
-
} else for (const pkg of batch.pkgs) results.push({
|
|
538
|
-
name: pkg.name,
|
|
539
|
-
fromVersion: pkg.current,
|
|
540
|
-
version: pkg.targetVersion ?? pkg.latest,
|
|
541
|
-
success: true
|
|
542
|
-
});
|
|
543
|
-
} catch (err) {
|
|
544
|
-
for (const pkg of batch.pkgs) results.push({
|
|
545
|
-
name: pkg.name,
|
|
546
|
-
fromVersion: pkg.current,
|
|
547
|
-
version: pkg.targetVersion ?? pkg.latest,
|
|
548
|
-
success: false,
|
|
549
|
-
error: err.message ?? "Unknown error"
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
return results;
|
|
610
|
+
return commands;
|
|
554
611
|
}
|
|
555
612
|
//#endregion
|
|
556
613
|
//#region src/config.ts
|
|
@@ -599,6 +656,36 @@ function incrementFrequency(packageNames) {
|
|
|
599
656
|
} catch {}
|
|
600
657
|
}
|
|
601
658
|
//#endregion
|
|
659
|
+
//#region src/lib/utils.ts
|
|
660
|
+
/** Open a URL in the user's default browser (cross-platform). */
|
|
661
|
+
function openInBrowser(url) {
|
|
662
|
+
exec(process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`);
|
|
663
|
+
}
|
|
664
|
+
/** Copy text to the system clipboard (best-effort, silently fails if unavailable). */
|
|
665
|
+
function copyToClipboard(text) {
|
|
666
|
+
try {
|
|
667
|
+
if (process.platform === "win32") execSync("clip", { input: text });
|
|
668
|
+
else if (process.platform === "darwin") execSync("pbcopy", { input: text });
|
|
669
|
+
else try {
|
|
670
|
+
execSync("xclip -selection clipboard", { input: text });
|
|
671
|
+
} catch {
|
|
672
|
+
execSync("xsel --clipboard --input", { input: text });
|
|
673
|
+
}
|
|
674
|
+
} catch {}
|
|
675
|
+
}
|
|
676
|
+
/** Convert an ISO date string to a human-readable relative age like "21h", "3d", "1mo", "2y". */
|
|
677
|
+
function formatAge(dateStr) {
|
|
678
|
+
if (!dateStr) return "";
|
|
679
|
+
const ms = Date.now() - new Date(dateStr).getTime();
|
|
680
|
+
if (ms < 0) return "";
|
|
681
|
+
const min = 6e4, hour = 36e5, day = 864e5;
|
|
682
|
+
if (ms < hour) return `${Math.floor(ms / min)}m`;
|
|
683
|
+
if (ms < day) return `${Math.floor(ms / hour)}h`;
|
|
684
|
+
if (ms < 30 * day) return `${Math.floor(ms / day)}d`;
|
|
685
|
+
if (ms < 365 * day) return `${Math.floor(ms / (30 * day))}mo`;
|
|
686
|
+
return `${Math.floor(ms / (365 * day))}y`;
|
|
687
|
+
}
|
|
688
|
+
//#endregion
|
|
602
689
|
//#region src/ui/package-list/types.ts
|
|
603
690
|
const TYPE_COLORS = {
|
|
604
691
|
dependencies: "cyan",
|
|
@@ -882,8 +969,7 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
|
|
|
882
969
|
const effectiveCollapsed = !initialized && allScopeKeys.size > 0 ? allScopeKeys : collapsedScopes;
|
|
883
970
|
const visibleRows = useMemo(() => filterCollapsed(allRows, effectiveCollapsed), [allRows, effectiveCollapsed]);
|
|
884
971
|
const groups = useMemo(() => buildGroups(visibleRows), [visibleRows]);
|
|
885
|
-
const {
|
|
886
|
-
const terminalRows = stdout?.rows ?? 24;
|
|
972
|
+
const { rows: terminalRows } = useWindowSize();
|
|
887
973
|
const maxVisible = useMemo(() => computeMaxPerGroup(terminalRows, groups.length), [terminalRows, groups.length]);
|
|
888
974
|
const scrollOffsetsRef = useRef({});
|
|
889
975
|
for (const group of groups) {
|
|
@@ -1020,7 +1106,7 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
|
|
|
1020
1106
|
color: "white",
|
|
1021
1107
|
children: "enter"
|
|
1022
1108
|
}),
|
|
1023
|
-
"
|
|
1109
|
+
" copy & exit"
|
|
1024
1110
|
]
|
|
1025
1111
|
})
|
|
1026
1112
|
})]
|
|
@@ -1064,7 +1150,7 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
|
|
|
1064
1150
|
})
|
|
1065
1151
|
] }), selectedCount > 0 && /* @__PURE__ */ jsx(Text, {
|
|
1066
1152
|
color: "greenBright",
|
|
1067
|
-
children: " Press enter to
|
|
1153
|
+
children: " Press enter to copy command →"
|
|
1068
1154
|
})]
|
|
1069
1155
|
})
|
|
1070
1156
|
})
|
|
@@ -1156,6 +1242,13 @@ function PackageGroupBox({ group, focusedIndex, collapsedScopes, scrollOffset, m
|
|
|
1156
1242
|
color: "gray",
|
|
1157
1243
|
children: "latest"
|
|
1158
1244
|
})
|
|
1245
|
+
}),
|
|
1246
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1247
|
+
width: 5,
|
|
1248
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1249
|
+
color: "gray",
|
|
1250
|
+
children: "age"
|
|
1251
|
+
})
|
|
1159
1252
|
})
|
|
1160
1253
|
]
|
|
1161
1254
|
}),
|
|
@@ -1246,6 +1339,16 @@ function PackageGroupBox({ group, focusedIndex, collapsedScopes, scrollOffset, m
|
|
|
1246
1339
|
children: pkg.latest
|
|
1247
1340
|
})
|
|
1248
1341
|
}),
|
|
1342
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1343
|
+
width: 5,
|
|
1344
|
+
children: pkg.latestPublishedAt ? /* @__PURE__ */ jsx(Text, {
|
|
1345
|
+
color: Date.now() - new Date(pkg.latestPublishedAt).getTime() < 864e5 ? "yellow" : "gray",
|
|
1346
|
+
children: formatAge(pkg.latestPublishedAt)
|
|
1347
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
1348
|
+
color: "gray",
|
|
1349
|
+
children: " "
|
|
1350
|
+
})
|
|
1351
|
+
}),
|
|
1249
1352
|
/* @__PURE__ */ jsx(Box, {
|
|
1250
1353
|
width: 9,
|
|
1251
1354
|
children: isMajorBump ? /* @__PURE__ */ jsx(Text, {
|
|
@@ -1271,7 +1374,8 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1271
1374
|
const [loading, setLoading] = useState(true);
|
|
1272
1375
|
const [cursor, setCursor] = useState(0);
|
|
1273
1376
|
const [scroll, setScroll] = useState(0);
|
|
1274
|
-
const
|
|
1377
|
+
const { rows } = useWindowSize();
|
|
1378
|
+
const PAGE = Math.max(1, rows - 11);
|
|
1275
1379
|
useEffect(() => {
|
|
1276
1380
|
fetchVersions(pkg.name).then((v) => {
|
|
1277
1381
|
setVersions(v);
|
|
@@ -1340,13 +1444,17 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1340
1444
|
})
|
|
1341
1445
|
]
|
|
1342
1446
|
}),
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1447
|
+
Array.from({ length: PAGE }, (_, i) => {
|
|
1448
|
+
if (loading) return /* @__PURE__ */ jsx(Text, {
|
|
1449
|
+
color: "gray",
|
|
1450
|
+
children: i === 0 ? " fetching versions…" : " "
|
|
1451
|
+
}, `_${i}`);
|
|
1452
|
+
if (versions.length === 0) return /* @__PURE__ */ jsx(Text, {
|
|
1453
|
+
color: i === 0 ? "red" : "gray",
|
|
1454
|
+
children: i === 0 ? " Could not fetch versions." : " "
|
|
1455
|
+
}, `_${i}`);
|
|
1456
|
+
const v = visible[i];
|
|
1457
|
+
if (!v) return /* @__PURE__ */ jsx(Text, { children: " " }, `_${i}`);
|
|
1350
1458
|
const isFocused = scroll + i === cursor;
|
|
1351
1459
|
const isCurrent = v.version === pkg.current;
|
|
1352
1460
|
const isLatest = v.tag === "latest";
|
|
@@ -1366,10 +1474,10 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1366
1474
|
})
|
|
1367
1475
|
}),
|
|
1368
1476
|
/* @__PURE__ */ jsx(Box, {
|
|
1369
|
-
width:
|
|
1477
|
+
width: 6,
|
|
1370
1478
|
children: /* @__PURE__ */ jsx(Text, {
|
|
1371
|
-
color: "gray",
|
|
1372
|
-
children: v.date
|
|
1479
|
+
color: v.date && Date.now() - new Date(v.date).getTime() < 864e5 ? "yellow" : "gray",
|
|
1480
|
+
children: formatAge(v.date)
|
|
1373
1481
|
})
|
|
1374
1482
|
}),
|
|
1375
1483
|
isLatest && /* @__PURE__ */ jsx(Text, {
|
|
@@ -1382,18 +1490,11 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1382
1490
|
})
|
|
1383
1491
|
]
|
|
1384
1492
|
}, v.version);
|
|
1385
|
-
}),
|
|
1493
|
+
}),
|
|
1494
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1386
1495
|
color: "gray",
|
|
1387
|
-
children:
|
|
1388
|
-
|
|
1389
|
-
"showing ",
|
|
1390
|
-
scroll + 1,
|
|
1391
|
-
"–",
|
|
1392
|
-
Math.min(scroll + PAGE, versions.length),
|
|
1393
|
-
" of ",
|
|
1394
|
-
versions.length
|
|
1395
|
-
]
|
|
1396
|
-
})] }),
|
|
1496
|
+
children: !loading && versions.length > PAGE ? ` showing ${scroll + 1}–${Math.min(scroll + PAGE, versions.length)} of ${versions.length}` : " "
|
|
1497
|
+
}),
|
|
1397
1498
|
/* @__PURE__ */ jsx(Box, {
|
|
1398
1499
|
marginTop: 1,
|
|
1399
1500
|
children: /* @__PURE__ */ jsx(Text, {
|
|
@@ -1427,12 +1528,6 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1427
1528
|
});
|
|
1428
1529
|
}
|
|
1429
1530
|
//#endregion
|
|
1430
|
-
//#region src/lib/utils.ts
|
|
1431
|
-
/** Open a URL in the user's default browser (cross-platform). */
|
|
1432
|
-
function openInBrowser(url) {
|
|
1433
|
-
exec(process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`);
|
|
1434
|
-
}
|
|
1435
|
-
//#endregion
|
|
1436
1531
|
//#region src/ui/MarkdownLine.tsx
|
|
1437
1532
|
/** Wrap text in an OSC 8 hyperlink so modern terminals make it clickable. */
|
|
1438
1533
|
function osc8(text, url) {
|
|
@@ -1603,7 +1698,7 @@ function ChangelogPanel({ pkg, onClose }) {
|
|
|
1603
1698
|
const [opened, setOpened] = useState(false);
|
|
1604
1699
|
const [activeEntry, setActiveEntry] = useState(0);
|
|
1605
1700
|
const scrollRef = useRef(null);
|
|
1606
|
-
const {
|
|
1701
|
+
const { columns, rows } = useWindowSize();
|
|
1607
1702
|
const isUpToDate = pkg.current === (pkg.targetVersion ?? pkg.latest);
|
|
1608
1703
|
useEffect(() => {
|
|
1609
1704
|
Promise.all([fetchChangelog(pkg.name, isUpToDate ? "" : pkg.current, pkg.targetVersion ?? pkg.latest), fetchRepoUrl(pkg.name)]).then(([e, repo]) => {
|
|
@@ -1614,12 +1709,8 @@ function ChangelogPanel({ pkg, onClose }) {
|
|
|
1614
1709
|
});
|
|
1615
1710
|
}, [pkg.name]);
|
|
1616
1711
|
useEffect(() => {
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
return () => {
|
|
1620
|
-
stdout?.off("resize", handleResize);
|
|
1621
|
-
};
|
|
1622
|
-
}, [stdout]);
|
|
1712
|
+
scrollRef.current?.remeasure();
|
|
1713
|
+
}, [columns, rows]);
|
|
1623
1714
|
const releasesPageUrl = repoUrl ? `${repoUrl}/releases` : "";
|
|
1624
1715
|
const triggerOpen = (url) => {
|
|
1625
1716
|
openInBrowser(url);
|
|
@@ -1836,85 +1927,6 @@ function ChangelogPanel({ pkg, onClose }) {
|
|
|
1836
1927
|
});
|
|
1837
1928
|
}
|
|
1838
1929
|
//#endregion
|
|
1839
|
-
//#region src/ui/UpdateResults.tsx
|
|
1840
|
-
function UpdateResults({ results, onDone }) {
|
|
1841
|
-
useEffect(() => {
|
|
1842
|
-
const timer = setTimeout(onDone, 1e3);
|
|
1843
|
-
return () => clearTimeout(timer);
|
|
1844
|
-
}, []);
|
|
1845
|
-
const success = results.filter((r) => r.success);
|
|
1846
|
-
const failed = results.filter((r) => !r.success);
|
|
1847
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1848
|
-
flexDirection: "column",
|
|
1849
|
-
children: [
|
|
1850
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1851
|
-
marginBottom: 1,
|
|
1852
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
1853
|
-
bold: true,
|
|
1854
|
-
color: "greenBright",
|
|
1855
|
-
children: [" ", "Update complete"]
|
|
1856
|
-
})
|
|
1857
|
-
}),
|
|
1858
|
-
success.map((r) => /* @__PURE__ */ jsxs(Box, {
|
|
1859
|
-
gap: 2,
|
|
1860
|
-
children: [
|
|
1861
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1862
|
-
color: "greenBright",
|
|
1863
|
-
children: "✓"
|
|
1864
|
-
}),
|
|
1865
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1866
|
-
color: "white",
|
|
1867
|
-
children: r.name
|
|
1868
|
-
}),
|
|
1869
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1870
|
-
color: "gray",
|
|
1871
|
-
children: r.fromVersion
|
|
1872
|
-
}),
|
|
1873
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1874
|
-
color: "gray",
|
|
1875
|
-
children: "→"
|
|
1876
|
-
}),
|
|
1877
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1878
|
-
color: "greenBright",
|
|
1879
|
-
children: r.version
|
|
1880
|
-
})
|
|
1881
|
-
]
|
|
1882
|
-
}, r.name)),
|
|
1883
|
-
failed.map((r) => /* @__PURE__ */ jsxs(Box, {
|
|
1884
|
-
flexDirection: "column",
|
|
1885
|
-
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1886
|
-
gap: 2,
|
|
1887
|
-
children: [
|
|
1888
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1889
|
-
color: "red",
|
|
1890
|
-
children: "✗"
|
|
1891
|
-
}),
|
|
1892
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1893
|
-
color: "white",
|
|
1894
|
-
children: r.name
|
|
1895
|
-
}),
|
|
1896
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1897
|
-
color: "gray",
|
|
1898
|
-
children: r.fromVersion
|
|
1899
|
-
}),
|
|
1900
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1901
|
-
color: "gray",
|
|
1902
|
-
children: "→"
|
|
1903
|
-
}),
|
|
1904
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1905
|
-
color: "red",
|
|
1906
|
-
children: r.version
|
|
1907
|
-
})
|
|
1908
|
-
]
|
|
1909
|
-
}), r.error && /* @__PURE__ */ jsxs(Text, {
|
|
1910
|
-
color: "red",
|
|
1911
|
-
children: [" ", r.error.slice(0, 80)]
|
|
1912
|
-
})]
|
|
1913
|
-
}, r.name))
|
|
1914
|
-
]
|
|
1915
|
-
});
|
|
1916
|
-
}
|
|
1917
|
-
//#endregion
|
|
1918
1930
|
//#region src/ui/SettingsToggle.tsx
|
|
1919
1931
|
function SettingsToggle({ label, description, checked, focused, disabled = false }) {
|
|
1920
1932
|
return /* @__PURE__ */ jsxs(Box, {
|
|
@@ -2204,10 +2216,9 @@ function Settings({ config, onConfigChange, onClose }) {
|
|
|
2204
2216
|
}
|
|
2205
2217
|
//#endregion
|
|
2206
2218
|
//#region src/ui/SelfUpdatePrompt.tsx
|
|
2207
|
-
function SelfUpdatePrompt({ currentVersion, latestVersion,
|
|
2219
|
+
function SelfUpdatePrompt({ currentVersion, latestVersion, onUpdate, onSkip }) {
|
|
2208
2220
|
const [selected, setSelected] = useState(0);
|
|
2209
|
-
useInput((
|
|
2210
|
-
if (updating) return;
|
|
2221
|
+
useInput((_input, key) => {
|
|
2211
2222
|
if (key.upArrow) setSelected(0);
|
|
2212
2223
|
if (key.downArrow) setSelected(1);
|
|
2213
2224
|
if (key.return) if (selected === 0) onUpdate();
|
|
@@ -2224,9 +2235,9 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
|
|
|
2224
2235
|
}),
|
|
2225
2236
|
/* @__PURE__ */ jsx(Box, {
|
|
2226
2237
|
marginTop: 1,
|
|
2227
|
-
flexDirection: "column",
|
|
2228
2238
|
children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
2229
|
-
"A new version is available!
|
|
2239
|
+
"A new version is available!",
|
|
2240
|
+
" ",
|
|
2230
2241
|
/* @__PURE__ */ jsx(Text, {
|
|
2231
2242
|
color: "gray",
|
|
2232
2243
|
children: currentVersion
|
|
@@ -2241,23 +2252,7 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
|
|
|
2241
2252
|
})
|
|
2242
2253
|
] })
|
|
2243
2254
|
}),
|
|
2244
|
-
|
|
2245
|
-
marginTop: 1,
|
|
2246
|
-
children: [/* @__PURE__ */ jsxs(Text, {
|
|
2247
|
-
color: "red",
|
|
2248
|
-
children: ["✗ Update failed: ", error]
|
|
2249
|
-
}), /* @__PURE__ */ jsx(Text, {
|
|
2250
|
-
color: "gray",
|
|
2251
|
-
children: " Continuing…"
|
|
2252
|
-
})]
|
|
2253
|
-
}),
|
|
2254
|
-
updating ? /* @__PURE__ */ jsx(Box, {
|
|
2255
|
-
marginTop: 1,
|
|
2256
|
-
children: /* @__PURE__ */ jsx(Text, {
|
|
2257
|
-
color: "gray",
|
|
2258
|
-
children: "Updating ripen…"
|
|
2259
|
-
})
|
|
2260
|
-
}) : /* @__PURE__ */ jsxs(Box, {
|
|
2255
|
+
/* @__PURE__ */ jsxs(Box, {
|
|
2261
2256
|
marginTop: 1,
|
|
2262
2257
|
flexDirection: "column",
|
|
2263
2258
|
children: [/* @__PURE__ */ jsxs(Text, { children: [selected === 0 ? /* @__PURE__ */ jsx(Text, {
|
|
@@ -2265,7 +2260,7 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
|
|
|
2265
2260
|
children: "❯ "
|
|
2266
2261
|
}) : /* @__PURE__ */ jsx(Text, { children: " " }), /* @__PURE__ */ jsx(Text, {
|
|
2267
2262
|
color: selected === 0 ? "white" : "gray",
|
|
2268
|
-
children: "Update"
|
|
2263
|
+
children: "Update (copies command to clipboard)"
|
|
2269
2264
|
})] }), /* @__PURE__ */ jsxs(Text, { children: [selected === 1 ? /* @__PURE__ */ jsx(Text, {
|
|
2270
2265
|
color: "greenBright",
|
|
2271
2266
|
children: "❯ "
|
|
@@ -2280,6 +2275,8 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
|
|
|
2280
2275
|
//#endregion
|
|
2281
2276
|
//#region src/ui/TerminalOutputBox.tsx
|
|
2282
2277
|
function TerminalOutputBox({ message, command, outputLines, maxLines }) {
|
|
2278
|
+
const { columns } = useWindowSize();
|
|
2279
|
+
const boxWidth = Math.min(64, columns - 4);
|
|
2283
2280
|
return /* @__PURE__ */ jsxs(Box, {
|
|
2284
2281
|
flexDirection: "column",
|
|
2285
2282
|
padding: 1,
|
|
@@ -2302,7 +2299,7 @@ function TerminalOutputBox({ message, command, outputLines, maxLines }) {
|
|
|
2302
2299
|
borderStyle: "round",
|
|
2303
2300
|
borderColor: "gray",
|
|
2304
2301
|
paddingX: 1,
|
|
2305
|
-
width:
|
|
2302
|
+
width: boxWidth,
|
|
2306
2303
|
height: maxLines + 3,
|
|
2307
2304
|
overflow: "hidden",
|
|
2308
2305
|
children: [command !== "" && /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -2324,8 +2321,6 @@ function TerminalOutputBox({ message, command, outputLines, maxLines }) {
|
|
|
2324
2321
|
//#region src/ui/hooks/use-self-update.ts
|
|
2325
2322
|
function useSelfUpdate(currentVersion, installManager) {
|
|
2326
2323
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
2327
|
-
const [selfUpdating, setSelfUpdating] = useState(false);
|
|
2328
|
-
const [selfUpdateError, setSelfUpdateError] = useState(null);
|
|
2329
2324
|
const [checkComplete, setCheckComplete] = useState(false);
|
|
2330
2325
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
2331
2326
|
useEffect(() => {
|
|
@@ -2342,33 +2337,17 @@ function useSelfUpdate(currentVersion, installManager) {
|
|
|
2342
2337
|
cancelled = true;
|
|
2343
2338
|
};
|
|
2344
2339
|
}, []);
|
|
2345
|
-
const
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
"add",
|
|
2351
|
-
`ripencli@${latestVersion}`
|
|
2352
|
-
] : [
|
|
2353
|
-
"add",
|
|
2354
|
-
"--global",
|
|
2355
|
-
`ripencli@${latestVersion}`
|
|
2356
|
-
]);
|
|
2357
|
-
setSelfUpdating(false);
|
|
2358
|
-
return true;
|
|
2359
|
-
} catch (err) {
|
|
2360
|
-
setSelfUpdateError(err.message ?? "Unknown error");
|
|
2361
|
-
setSelfUpdating(false);
|
|
2362
|
-
return false;
|
|
2363
|
-
}
|
|
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}`;
|
|
2364
2345
|
};
|
|
2365
2346
|
return {
|
|
2366
2347
|
latestVersion,
|
|
2367
|
-
selfUpdating,
|
|
2368
|
-
selfUpdateError,
|
|
2369
2348
|
checkComplete,
|
|
2370
2349
|
hasUpdate,
|
|
2371
|
-
|
|
2350
|
+
buildUpdateCommand
|
|
2372
2351
|
};
|
|
2373
2352
|
}
|
|
2374
2353
|
//#endregion
|
|
@@ -2445,7 +2424,7 @@ function useExitOnScreen(screen, targetScreens, exit, options) {
|
|
|
2445
2424
|
}
|
|
2446
2425
|
//#endregion
|
|
2447
2426
|
//#region src/ui/App.tsx
|
|
2448
|
-
function App({ project, global, showAll, version, installManager }) {
|
|
2427
|
+
function App({ project, global, showAll, version, installManager, onCancelled, onCopied }) {
|
|
2449
2428
|
const { exit } = useApp();
|
|
2450
2429
|
const [screen, setScreen] = useState("self-update-check");
|
|
2451
2430
|
const [config, setConfig] = useState(() => loadConfig());
|
|
@@ -2458,19 +2437,21 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2458
2437
|
useInput((_input, key) => {
|
|
2459
2438
|
if (key.ctrl && _input === "c") setScreen("cancelled");
|
|
2460
2439
|
});
|
|
2461
|
-
useExitOnScreen(screen, ["
|
|
2440
|
+
useExitOnScreen(screen, ["empty"], exit);
|
|
2462
2441
|
useExitOnScreen(screen, ["cancelled"], exit, {
|
|
2463
2442
|
delay: 200,
|
|
2464
|
-
beforeExit: () =>
|
|
2443
|
+
beforeExit: () => onCancelled?.()
|
|
2465
2444
|
});
|
|
2466
2445
|
useEffect(() => {
|
|
2467
2446
|
if (!selfUpdate.checkComplete) return;
|
|
2468
2447
|
if (screen !== "self-update-check") return;
|
|
2469
2448
|
setScreen(selfUpdate.hasUpdate ? "self-update" : "loading");
|
|
2470
2449
|
}, [selfUpdate.checkComplete]);
|
|
2471
|
-
const handleSelfUpdate =
|
|
2472
|
-
|
|
2473
|
-
|
|
2450
|
+
const handleSelfUpdate = () => {
|
|
2451
|
+
const cmd = selfUpdate.buildUpdateCommand();
|
|
2452
|
+
copyToClipboard(cmd);
|
|
2453
|
+
onCopied?.([cmd]);
|
|
2454
|
+
exit();
|
|
2474
2455
|
};
|
|
2475
2456
|
const [fetchStarted, setFetchStarted] = useState(false);
|
|
2476
2457
|
useEffect(() => {
|
|
@@ -2478,7 +2459,7 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2478
2459
|
setFetchStarted(true);
|
|
2479
2460
|
terminal.setLoadingMsg("Checking for outdated packages…");
|
|
2480
2461
|
terminal.setTerminalCmd(global ? "Checking all package managers…" : "Checking npm registry…");
|
|
2481
|
-
(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) => {
|
|
2482
2463
|
if (!result.ok) {
|
|
2483
2464
|
setErrorMsg(result.error);
|
|
2484
2465
|
setScreen("error");
|
|
@@ -2496,21 +2477,15 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2496
2477
|
setConfig(newConfig);
|
|
2497
2478
|
saveConfig(newConfig);
|
|
2498
2479
|
};
|
|
2499
|
-
const
|
|
2500
|
-
const handleConfirm = async () => {
|
|
2480
|
+
const handleConfirm = () => {
|
|
2501
2481
|
const selected = packages.filter((p) => p.selected);
|
|
2502
2482
|
if (selected.length === 0) return;
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
const successNames = res.filter((r) => r.success).map((r) => r.name);
|
|
2510
|
-
if (successNames.length > 0) {
|
|
2511
|
-
incrementFrequency(successNames);
|
|
2512
|
-
setFrequency(loadFrequency());
|
|
2513
|
-
}
|
|
2483
|
+
const commands = buildUpdateCommands(project.manager, selected, global);
|
|
2484
|
+
copyToClipboard(commands.join(" && "));
|
|
2485
|
+
incrementFrequency(selected.map((p) => p.name));
|
|
2486
|
+
setFrequency(loadFrequency());
|
|
2487
|
+
onCopied?.(commands);
|
|
2488
|
+
exit();
|
|
2514
2489
|
};
|
|
2515
2490
|
if (screen === "self-update-check") return /* @__PURE__ */ jsxs(Box, {
|
|
2516
2491
|
flexDirection: "column",
|
|
@@ -2530,30 +2505,9 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2530
2505
|
if (screen === "self-update") return /* @__PURE__ */ jsx(SelfUpdatePrompt, {
|
|
2531
2506
|
currentVersion: version,
|
|
2532
2507
|
latestVersion: selfUpdate.latestVersion,
|
|
2533
|
-
updating: selfUpdate.selfUpdating,
|
|
2534
|
-
error: selfUpdate.selfUpdateError,
|
|
2535
2508
|
onUpdate: handleSelfUpdate,
|
|
2536
2509
|
onSkip: () => setScreen("loading")
|
|
2537
2510
|
});
|
|
2538
|
-
if (screen === "self-update-done") return /* @__PURE__ */ jsxs(Box, {
|
|
2539
|
-
flexDirection: "column",
|
|
2540
|
-
padding: 1,
|
|
2541
|
-
children: [/* @__PURE__ */ jsxs(Text, {
|
|
2542
|
-
color: "greenBright",
|
|
2543
|
-
bold: true,
|
|
2544
|
-
children: [" ", "ripen"]
|
|
2545
|
-
}), /* @__PURE__ */ jsx(Box, {
|
|
2546
|
-
marginTop: 1,
|
|
2547
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
2548
|
-
color: "green",
|
|
2549
|
-
children: [
|
|
2550
|
-
"✓ Updated to v",
|
|
2551
|
-
selfUpdate.latestVersion,
|
|
2552
|
-
". Run ripen again to use the new version."
|
|
2553
|
-
]
|
|
2554
|
-
})
|
|
2555
|
-
})]
|
|
2556
|
-
});
|
|
2557
2511
|
if (screen === "loading") return /* @__PURE__ */ jsx(TerminalOutputBox, {
|
|
2558
2512
|
message: terminal.loadingMsg,
|
|
2559
2513
|
command: terminal.terminalCmd,
|
|
@@ -2611,22 +2565,6 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2611
2565
|
});
|
|
2612
2566
|
const isListActive = screen === "list";
|
|
2613
2567
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2614
|
-
screen === "updating" && /* @__PURE__ */ jsx(TerminalOutputBox, {
|
|
2615
|
-
message: terminal.loadingMsg,
|
|
2616
|
-
command: terminal.terminalCmd,
|
|
2617
|
-
outputLines: terminal.outputLines,
|
|
2618
|
-
maxLines: terminal.maxLines
|
|
2619
|
-
}),
|
|
2620
|
-
screen === "results" && /* @__PURE__ */ jsx(Box, {
|
|
2621
|
-
padding: 1,
|
|
2622
|
-
children: /* @__PURE__ */ jsx(UpdateResults, {
|
|
2623
|
-
results,
|
|
2624
|
-
onDone: () => {
|
|
2625
|
-
exit();
|
|
2626
|
-
process.exit(0);
|
|
2627
|
-
}
|
|
2628
|
-
})
|
|
2629
|
-
}),
|
|
2630
2568
|
screen === "settings" && /* @__PURE__ */ jsx(Box, {
|
|
2631
2569
|
padding: 1,
|
|
2632
2570
|
children: /* @__PURE__ */ jsx(Settings, {
|
|
@@ -2709,7 +2647,7 @@ if (showHelp) {
|
|
|
2709
2647
|
space toggle select
|
|
2710
2648
|
v pick specific version
|
|
2711
2649
|
c view changelog / release notes
|
|
2712
|
-
enter update
|
|
2650
|
+
enter copy update command to clipboard & exit
|
|
2713
2651
|
esc cancel / go back
|
|
2714
2652
|
`);
|
|
2715
2653
|
process.exit(0);
|
|
@@ -2720,13 +2658,28 @@ if (!isGlobal && !hasPackageJson(cwd)) {
|
|
|
2720
2658
|
console.log(" Run ripen inside a Node.js project, or use ripen -g for global packages.\n");
|
|
2721
2659
|
process.exit(1);
|
|
2722
2660
|
}
|
|
2661
|
+
const project = getProjectInfo(cwd);
|
|
2662
|
+
const installManager = detectGlobalInstallManager();
|
|
2663
|
+
let wasCancelled = false;
|
|
2664
|
+
let copiedCommands = [];
|
|
2723
2665
|
const { waitUntilExit } = render(/* @__PURE__ */ jsx(App, {
|
|
2724
|
-
project
|
|
2666
|
+
project,
|
|
2725
2667
|
global: isGlobal,
|
|
2726
2668
|
showAll,
|
|
2727
2669
|
version: VERSION,
|
|
2728
|
-
installManager
|
|
2729
|
-
|
|
2670
|
+
installManager,
|
|
2671
|
+
onCancelled: () => {
|
|
2672
|
+
wasCancelled = true;
|
|
2673
|
+
},
|
|
2674
|
+
onCopied: (cmds) => {
|
|
2675
|
+
copiedCommands = cmds;
|
|
2676
|
+
}
|
|
2677
|
+
}), {
|
|
2678
|
+
exitOnCtrlC: false,
|
|
2679
|
+
alternateScreen: true
|
|
2680
|
+
});
|
|
2730
2681
|
await waitUntilExit();
|
|
2682
|
+
if (wasCancelled) process.stdout.write(" \x1B[32mCancelled.\x1B[0m\n");
|
|
2683
|
+
else if (copiedCommands.length > 0) process.stdout.write(" \x1B[32mCopied to clipboard.\x1B[0m\n");
|
|
2731
2684
|
//#endregion
|
|
2732
2685
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ripencli",
|
|
3
|
-
"version": "1.
|
|
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,14 +28,14 @@
|
|
|
28
28
|
},
|
|
29
29
|
"type": "module",
|
|
30
30
|
"bin": {
|
|
31
|
-
"ripen": "
|
|
31
|
+
"ripen": "dist/cli.js"
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsdown",
|
|
35
35
|
"dev": "tsx src/cli.tsx",
|
|
36
36
|
"typecheck": "tsc --noEmit",
|
|
37
37
|
"start": "node dist/cli.js",
|
|
38
|
-
"
|
|
38
|
+
"check": "node scripts/pr-checks.ts",
|
|
39
39
|
"docs:dev": "pnpm --prefix docs dev",
|
|
40
40
|
"docs:build": "pnpm --prefix docs build",
|
|
41
41
|
"docs:start": "pnpm --prefix docs start"
|
|
@@ -65,16 +65,16 @@
|
|
|
65
65
|
"dist"
|
|
66
66
|
],
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"execa": "
|
|
69
|
-
"ink": "
|
|
70
|
-
"ink-scroll-view": "
|
|
71
|
-
"react": "
|
|
68
|
+
"execa": "9.6.1",
|
|
69
|
+
"ink": "7.0.3",
|
|
70
|
+
"ink-scroll-view": "0.3.7",
|
|
71
|
+
"react": "19.2.6"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
74
|
-
"@types/node": "
|
|
75
|
-
"@types/react": "
|
|
76
|
-
"prettier": "
|
|
77
|
-
"tsdown": "
|
|
78
|
-
"typescript": "
|
|
74
|
+
"@types/node": "24.12.2",
|
|
75
|
+
"@types/react": "19.2.14",
|
|
76
|
+
"prettier": "3.8.3",
|
|
77
|
+
"tsdown": "0.22.0",
|
|
78
|
+
"typescript": "6.0.3"
|
|
79
79
|
}
|
|
80
80
|
}
|