ripencli 1.0.0 → 1.1.0
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 +17 -12
- package/dist/cli.js +271 -276
- package/package.json +11 -11
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
|
|
@@ -51,24 +53,27 @@ ripen --help
|
|
|
51
53
|
|
|
52
54
|
## Controls
|
|
53
55
|
|
|
54
|
-
| Key
|
|
55
|
-
|
|
|
56
|
-
| `↑ ↓`
|
|
57
|
-
| `
|
|
58
|
-
| `
|
|
59
|
-
|
|
|
60
|
-
| `
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
56
|
+
| Key | Action |
|
|
57
|
+
| ------------ | ----------------------------------- |
|
|
58
|
+
| `↑ ↓` | Navigate packages |
|
|
59
|
+
| `PgUp PgDn` | Scroll a full page |
|
|
60
|
+
| `Tab` | Jump between groups |
|
|
61
|
+
| `← →` | Collapse / expand scope groups |
|
|
62
|
+
| `space` | Toggle select |
|
|
63
|
+
| `v` | Pick specific version |
|
|
64
|
+
| `c` | View changelog / release notes |
|
|
65
|
+
| `enter` | Copy update command & exit |
|
|
66
|
+
| `s` | Open settings |
|
|
67
|
+
| `esc` | Cancel / go back |
|
|
63
68
|
|
|
64
69
|
## How it works
|
|
65
70
|
|
|
66
71
|
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`)
|
|
72
|
+
2. Detects your package manager from the lock file (`bun.lock`, `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`)
|
|
68
73
|
3. Shows outdated packages in a colorful interactive list (use `--all` to show every package, including up-to-date ones)
|
|
69
74
|
4. Press `v` on any package to pick a specific version from the npm registry
|
|
70
75
|
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
|
|
76
|
+
6. Select the ones you want and press `enter` — ripen copies the exact update command to your clipboard and exits
|
|
72
77
|
|
|
73
78
|
## Settings
|
|
74
79
|
|
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
|
};
|
|
@@ -141,8 +141,10 @@ async function fetchChangelog(packageName, fromVersion, toVersion) {
|
|
|
141
141
|
const ghRes = await fetch(`https://api.github.com/repos/${repo}/releases?per_page=30`, { headers: { Accept: "application/vnd.github+json" } });
|
|
142
142
|
if (!ghRes.ok) return [];
|
|
143
143
|
const releases = await ghRes.json();
|
|
144
|
+
const toMajor = parseVersion(toVersion)[0];
|
|
144
145
|
const filtered = releases.filter((r) => {
|
|
145
146
|
if (r.draft || r.prerelease) return false;
|
|
147
|
+
if (fromVersion === "") return parseVersion(r.tag_name)[0] === toMajor && compareVersions(r.tag_name, toVersion) <= 0;
|
|
146
148
|
return compareVersions(r.tag_name, fromVersion) > 0 && compareVersions(r.tag_name, toVersion) <= 0;
|
|
147
149
|
}).map((r) => ({
|
|
148
150
|
version: r.tag_name,
|
|
@@ -157,7 +159,7 @@ async function fetchChangelog(packageName, fromVersion, toVersion) {
|
|
|
157
159
|
url: latest.html_url
|
|
158
160
|
}];
|
|
159
161
|
}
|
|
160
|
-
return filtered;
|
|
162
|
+
return filtered.sort((a, b) => compareVersions(a.version, b.version));
|
|
161
163
|
} catch {
|
|
162
164
|
return [];
|
|
163
165
|
}
|
|
@@ -211,14 +213,20 @@ function readPackageJsonDeps(cwd) {
|
|
|
211
213
|
}
|
|
212
214
|
return entries;
|
|
213
215
|
}
|
|
214
|
-
async function
|
|
216
|
+
async function fetchRegistryInfoWithRetry(packageName) {
|
|
215
217
|
for (let attempt = 0; attempt < 3; attempt++) try {
|
|
216
218
|
const controller = new AbortController();
|
|
217
219
|
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
218
|
-
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}
|
|
220
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(packageName)}`, { signal: controller.signal });
|
|
219
221
|
clearTimeout(timeout);
|
|
220
222
|
if (!res.ok) return null;
|
|
221
|
-
|
|
223
|
+
const data = await res.json();
|
|
224
|
+
const version = data["dist-tags"]?.latest ?? null;
|
|
225
|
+
if (!version) return null;
|
|
226
|
+
return {
|
|
227
|
+
version,
|
|
228
|
+
publishedAt: data.time?.[version] ?? ""
|
|
229
|
+
};
|
|
222
230
|
} catch {
|
|
223
231
|
if (attempt === 2) return null;
|
|
224
232
|
}
|
|
@@ -232,7 +240,7 @@ async function fetchAllLatest(names, concurrency, onLine) {
|
|
|
232
240
|
while (index < names.length) {
|
|
233
241
|
const name = names[index++];
|
|
234
242
|
onLine?.(`Checking ${name} (${completed + 1}/${names.length})...`);
|
|
235
|
-
results.set(name, await
|
|
243
|
+
results.set(name, await fetchRegistryInfoWithRetry(name));
|
|
236
244
|
completed++;
|
|
237
245
|
}
|
|
238
246
|
}
|
|
@@ -261,8 +269,9 @@ async function getOutdatedPackages(manager, cwd, global = false, onLine, showAll
|
|
|
261
269
|
};
|
|
262
270
|
const packages = [];
|
|
263
271
|
for (const dep of deps) {
|
|
264
|
-
const
|
|
265
|
-
if (!
|
|
272
|
+
const info = latestVersions.get(dep.name);
|
|
273
|
+
if (!info) continue;
|
|
274
|
+
const { version: latest, publishedAt } = info;
|
|
266
275
|
if (!showAll && !isNewerVersion(dep.current, latest)) continue;
|
|
267
276
|
packages.push({
|
|
268
277
|
name: dep.name,
|
|
@@ -273,7 +282,8 @@ async function getOutdatedPackages(manager, cwd, global = false, onLine, showAll
|
|
|
273
282
|
type: dep.type,
|
|
274
283
|
selected: false,
|
|
275
284
|
targetVersion: latest,
|
|
276
|
-
rangePrefix: dep.prefix
|
|
285
|
+
rangePrefix: dep.prefix,
|
|
286
|
+
latestPublishedAt: publishedAt || void 0
|
|
277
287
|
});
|
|
278
288
|
}
|
|
279
289
|
return {
|
|
@@ -458,22 +468,28 @@ function parseYarnOutdated(raw, global) {
|
|
|
458
468
|
}
|
|
459
469
|
//#endregion
|
|
460
470
|
//#region src/executor.ts
|
|
461
|
-
|
|
462
|
-
const
|
|
471
|
+
function buildUpdateCommands(manager, packages, global = false) {
|
|
472
|
+
const commands = [];
|
|
463
473
|
const deps = packages.filter((p) => !global && p.type === "dependencies");
|
|
464
474
|
const devDeps = packages.filter((p) => !global && p.type === "devDependencies");
|
|
465
|
-
const globalPkgs = packages.filter((p) => global);
|
|
466
|
-
const
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
475
|
+
const globalPkgs = packages.filter((p) => global || p.type === "global");
|
|
476
|
+
const makeCmd = (mgr, pkgs, flags) => {
|
|
477
|
+
const pkgArgs = pkgs.map((pkg) => {
|
|
478
|
+
const version = pkg.targetVersion ?? pkg.latest;
|
|
479
|
+
return `${pkg.name}@${version}`;
|
|
480
|
+
});
|
|
481
|
+
return `${mgr} ${(mgr === "yarn" && global ? [
|
|
482
|
+
"global",
|
|
483
|
+
"add",
|
|
484
|
+
...pkgArgs
|
|
485
|
+
] : [
|
|
486
|
+
"add",
|
|
487
|
+
...flags,
|
|
488
|
+
...pkgArgs
|
|
489
|
+
]).join(" ")}`;
|
|
490
|
+
};
|
|
491
|
+
if (deps.length > 0) commands.push(makeCmd(manager, deps, []));
|
|
492
|
+
if (devDeps.length > 0) commands.push(makeCmd(manager, devDeps, [manager === "bun" ? "-d" : "-D"]));
|
|
477
493
|
if (globalPkgs.length > 0) {
|
|
478
494
|
const byManager = /* @__PURE__ */ new Map();
|
|
479
495
|
for (const pkg of globalPkgs) {
|
|
@@ -483,72 +499,10 @@ async function updatePackages(manager, packages, cwd, global = false, onLine) {
|
|
|
483
499
|
}
|
|
484
500
|
for (const [mgr, pkgs] of byManager) {
|
|
485
501
|
const globalFlags = mgr === "yarn" ? [] : mgr === "bun" ? ["-g"] : ["--global"];
|
|
486
|
-
|
|
487
|
-
mgr,
|
|
488
|
-
pkgs,
|
|
489
|
-
flags: globalFlags
|
|
490
|
-
});
|
|
502
|
+
commands.push(makeCmd(mgr, pkgs, globalFlags));
|
|
491
503
|
}
|
|
492
504
|
}
|
|
493
|
-
|
|
494
|
-
const pkgArgs = batch.pkgs.map((pkg) => {
|
|
495
|
-
const version = pkg.targetVersion ?? pkg.latest;
|
|
496
|
-
const prefix = pkg.rangePrefix ?? "";
|
|
497
|
-
return `${pkg.name}@${prefix}${version}`;
|
|
498
|
-
});
|
|
499
|
-
const args = batch.mgr === "yarn" && global ? [
|
|
500
|
-
"global",
|
|
501
|
-
"add",
|
|
502
|
-
...pkgArgs
|
|
503
|
-
] : [
|
|
504
|
-
"add",
|
|
505
|
-
...batch.flags,
|
|
506
|
-
...pkgArgs
|
|
507
|
-
];
|
|
508
|
-
onLine?.(`$ ${batch.mgr} ${args.join(" ")}`);
|
|
509
|
-
try {
|
|
510
|
-
const proc = execa(batch.mgr, args, {
|
|
511
|
-
cwd,
|
|
512
|
-
reject: false
|
|
513
|
-
});
|
|
514
|
-
if (onLine) {
|
|
515
|
-
const forward = (chunk) => {
|
|
516
|
-
const lines = chunk.toString().split("\n");
|
|
517
|
-
for (const line of lines) {
|
|
518
|
-
const trimmed = line.trim();
|
|
519
|
-
if (trimmed) onLine(trimmed);
|
|
520
|
-
}
|
|
521
|
-
};
|
|
522
|
-
proc.stderr?.on("data", forward);
|
|
523
|
-
proc.stdout?.on("data", forward);
|
|
524
|
-
}
|
|
525
|
-
const result = await proc;
|
|
526
|
-
if (result.exitCode !== 0) {
|
|
527
|
-
const errMsg = result.stderr?.trim() || `exited with code ${result.exitCode}`;
|
|
528
|
-
for (const pkg of batch.pkgs) results.push({
|
|
529
|
-
name: pkg.name,
|
|
530
|
-
fromVersion: pkg.current,
|
|
531
|
-
version: pkg.targetVersion ?? pkg.latest,
|
|
532
|
-
success: false,
|
|
533
|
-
error: errMsg
|
|
534
|
-
});
|
|
535
|
-
} else for (const pkg of batch.pkgs) results.push({
|
|
536
|
-
name: pkg.name,
|
|
537
|
-
fromVersion: pkg.current,
|
|
538
|
-
version: pkg.targetVersion ?? pkg.latest,
|
|
539
|
-
success: true
|
|
540
|
-
});
|
|
541
|
-
} catch (err) {
|
|
542
|
-
for (const pkg of batch.pkgs) results.push({
|
|
543
|
-
name: pkg.name,
|
|
544
|
-
fromVersion: pkg.current,
|
|
545
|
-
version: pkg.targetVersion ?? pkg.latest,
|
|
546
|
-
success: false,
|
|
547
|
-
error: err.message ?? "Unknown error"
|
|
548
|
-
});
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
return results;
|
|
505
|
+
return commands;
|
|
552
506
|
}
|
|
553
507
|
//#endregion
|
|
554
508
|
//#region src/config.ts
|
|
@@ -597,6 +551,36 @@ function incrementFrequency(packageNames) {
|
|
|
597
551
|
} catch {}
|
|
598
552
|
}
|
|
599
553
|
//#endregion
|
|
554
|
+
//#region src/lib/utils.ts
|
|
555
|
+
/** Open a URL in the user's default browser (cross-platform). */
|
|
556
|
+
function openInBrowser(url) {
|
|
557
|
+
exec(process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`);
|
|
558
|
+
}
|
|
559
|
+
/** Copy text to the system clipboard (best-effort, silently fails if unavailable). */
|
|
560
|
+
function copyToClipboard(text) {
|
|
561
|
+
try {
|
|
562
|
+
if (process.platform === "win32") execSync("clip", { input: text });
|
|
563
|
+
else if (process.platform === "darwin") execSync("pbcopy", { input: text });
|
|
564
|
+
else try {
|
|
565
|
+
execSync("xclip -selection clipboard", { input: text });
|
|
566
|
+
} catch {
|
|
567
|
+
execSync("xsel --clipboard --input", { input: text });
|
|
568
|
+
}
|
|
569
|
+
} catch {}
|
|
570
|
+
}
|
|
571
|
+
/** Convert an ISO date string to a human-readable relative age like "21h", "3d", "1mo", "2y". */
|
|
572
|
+
function formatAge(dateStr) {
|
|
573
|
+
if (!dateStr) return "";
|
|
574
|
+
const ms = Date.now() - new Date(dateStr).getTime();
|
|
575
|
+
if (ms < 0) return "";
|
|
576
|
+
const min = 6e4, hour = 36e5, day = 864e5;
|
|
577
|
+
if (ms < hour) return `${Math.floor(ms / min)}m`;
|
|
578
|
+
if (ms < day) return `${Math.floor(ms / hour)}h`;
|
|
579
|
+
if (ms < 30 * day) return `${Math.floor(ms / day)}d`;
|
|
580
|
+
if (ms < 365 * day) return `${Math.floor(ms / (30 * day))}mo`;
|
|
581
|
+
return `${Math.floor(ms / (365 * day))}y`;
|
|
582
|
+
}
|
|
583
|
+
//#endregion
|
|
600
584
|
//#region src/ui/package-list/types.ts
|
|
601
585
|
const TYPE_COLORS = {
|
|
602
586
|
dependencies: "cyan",
|
|
@@ -880,8 +864,7 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
|
|
|
880
864
|
const effectiveCollapsed = !initialized && allScopeKeys.size > 0 ? allScopeKeys : collapsedScopes;
|
|
881
865
|
const visibleRows = useMemo(() => filterCollapsed(allRows, effectiveCollapsed), [allRows, effectiveCollapsed]);
|
|
882
866
|
const groups = useMemo(() => buildGroups(visibleRows), [visibleRows]);
|
|
883
|
-
const {
|
|
884
|
-
const terminalRows = stdout?.rows ?? 24;
|
|
867
|
+
const { rows: terminalRows } = useWindowSize();
|
|
885
868
|
const maxVisible = useMemo(() => computeMaxPerGroup(terminalRows, groups.length), [terminalRows, groups.length]);
|
|
886
869
|
const scrollOffsetsRef = useRef({});
|
|
887
870
|
for (const group of groups) {
|
|
@@ -1018,7 +1001,7 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
|
|
|
1018
1001
|
color: "white",
|
|
1019
1002
|
children: "enter"
|
|
1020
1003
|
}),
|
|
1021
|
-
"
|
|
1004
|
+
" copy & exit"
|
|
1022
1005
|
]
|
|
1023
1006
|
})
|
|
1024
1007
|
})]
|
|
@@ -1062,7 +1045,7 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
|
|
|
1062
1045
|
})
|
|
1063
1046
|
] }), selectedCount > 0 && /* @__PURE__ */ jsx(Text, {
|
|
1064
1047
|
color: "greenBright",
|
|
1065
|
-
children: " Press enter to
|
|
1048
|
+
children: " Press enter to copy command →"
|
|
1066
1049
|
})]
|
|
1067
1050
|
})
|
|
1068
1051
|
})
|
|
@@ -1154,6 +1137,13 @@ function PackageGroupBox({ group, focusedIndex, collapsedScopes, scrollOffset, m
|
|
|
1154
1137
|
color: "gray",
|
|
1155
1138
|
children: "latest"
|
|
1156
1139
|
})
|
|
1140
|
+
}),
|
|
1141
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1142
|
+
width: 5,
|
|
1143
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1144
|
+
color: "gray",
|
|
1145
|
+
children: "age"
|
|
1146
|
+
})
|
|
1157
1147
|
})
|
|
1158
1148
|
]
|
|
1159
1149
|
}),
|
|
@@ -1244,6 +1234,16 @@ function PackageGroupBox({ group, focusedIndex, collapsedScopes, scrollOffset, m
|
|
|
1244
1234
|
children: pkg.latest
|
|
1245
1235
|
})
|
|
1246
1236
|
}),
|
|
1237
|
+
/* @__PURE__ */ jsx(Box, {
|
|
1238
|
+
width: 5,
|
|
1239
|
+
children: pkg.latestPublishedAt ? /* @__PURE__ */ jsx(Text, {
|
|
1240
|
+
color: Date.now() - new Date(pkg.latestPublishedAt).getTime() < 864e5 ? "yellow" : "gray",
|
|
1241
|
+
children: formatAge(pkg.latestPublishedAt)
|
|
1242
|
+
}) : /* @__PURE__ */ jsx(Text, {
|
|
1243
|
+
color: "gray",
|
|
1244
|
+
children: " "
|
|
1245
|
+
})
|
|
1246
|
+
}),
|
|
1247
1247
|
/* @__PURE__ */ jsx(Box, {
|
|
1248
1248
|
width: 9,
|
|
1249
1249
|
children: isMajorBump ? /* @__PURE__ */ jsx(Text, {
|
|
@@ -1269,7 +1269,8 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1269
1269
|
const [loading, setLoading] = useState(true);
|
|
1270
1270
|
const [cursor, setCursor] = useState(0);
|
|
1271
1271
|
const [scroll, setScroll] = useState(0);
|
|
1272
|
-
const
|
|
1272
|
+
const { rows } = useWindowSize();
|
|
1273
|
+
const PAGE = Math.max(1, rows - 11);
|
|
1273
1274
|
useEffect(() => {
|
|
1274
1275
|
fetchVersions(pkg.name).then((v) => {
|
|
1275
1276
|
setVersions(v);
|
|
@@ -1338,13 +1339,17 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1338
1339
|
})
|
|
1339
1340
|
]
|
|
1340
1341
|
}),
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1342
|
+
Array.from({ length: PAGE }, (_, i) => {
|
|
1343
|
+
if (loading) return /* @__PURE__ */ jsx(Text, {
|
|
1344
|
+
color: "gray",
|
|
1345
|
+
children: i === 0 ? " fetching versions…" : " "
|
|
1346
|
+
}, `_${i}`);
|
|
1347
|
+
if (versions.length === 0) return /* @__PURE__ */ jsx(Text, {
|
|
1348
|
+
color: i === 0 ? "red" : "gray",
|
|
1349
|
+
children: i === 0 ? " Could not fetch versions." : " "
|
|
1350
|
+
}, `_${i}`);
|
|
1351
|
+
const v = visible[i];
|
|
1352
|
+
if (!v) return /* @__PURE__ */ jsx(Text, { children: " " }, `_${i}`);
|
|
1348
1353
|
const isFocused = scroll + i === cursor;
|
|
1349
1354
|
const isCurrent = v.version === pkg.current;
|
|
1350
1355
|
const isLatest = v.tag === "latest";
|
|
@@ -1364,10 +1369,10 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1364
1369
|
})
|
|
1365
1370
|
}),
|
|
1366
1371
|
/* @__PURE__ */ jsx(Box, {
|
|
1367
|
-
width:
|
|
1372
|
+
width: 6,
|
|
1368
1373
|
children: /* @__PURE__ */ jsx(Text, {
|
|
1369
|
-
color: "gray",
|
|
1370
|
-
children: v.date
|
|
1374
|
+
color: v.date && Date.now() - new Date(v.date).getTime() < 864e5 ? "yellow" : "gray",
|
|
1375
|
+
children: formatAge(v.date)
|
|
1371
1376
|
})
|
|
1372
1377
|
}),
|
|
1373
1378
|
isLatest && /* @__PURE__ */ jsx(Text, {
|
|
@@ -1380,18 +1385,11 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1380
1385
|
})
|
|
1381
1386
|
]
|
|
1382
1387
|
}, v.version);
|
|
1383
|
-
}),
|
|
1388
|
+
}),
|
|
1389
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1384
1390
|
color: "gray",
|
|
1385
|
-
children:
|
|
1386
|
-
|
|
1387
|
-
"showing ",
|
|
1388
|
-
scroll + 1,
|
|
1389
|
-
"–",
|
|
1390
|
-
Math.min(scroll + PAGE, versions.length),
|
|
1391
|
-
" of ",
|
|
1392
|
-
versions.length
|
|
1393
|
-
]
|
|
1394
|
-
})] }),
|
|
1391
|
+
children: !loading && versions.length > PAGE ? ` showing ${scroll + 1}–${Math.min(scroll + PAGE, versions.length)} of ${versions.length}` : " "
|
|
1392
|
+
}),
|
|
1395
1393
|
/* @__PURE__ */ jsx(Box, {
|
|
1396
1394
|
marginTop: 1,
|
|
1397
1395
|
children: /* @__PURE__ */ jsx(Text, {
|
|
@@ -1425,16 +1423,14 @@ function VersionPicker({ pkg, onSelect, onCancel }) {
|
|
|
1425
1423
|
});
|
|
1426
1424
|
}
|
|
1427
1425
|
//#endregion
|
|
1428
|
-
//#region src/lib/utils.ts
|
|
1429
|
-
/** Open a URL in the user's default browser (cross-platform). */
|
|
1430
|
-
function openInBrowser(url) {
|
|
1431
|
-
exec(process.platform === "win32" ? `start "" "${url}"` : process.platform === "darwin" ? `open "${url}"` : `xdg-open "${url}"`);
|
|
1432
|
-
}
|
|
1433
|
-
//#endregion
|
|
1434
1426
|
//#region src/ui/MarkdownLine.tsx
|
|
1435
|
-
|
|
1427
|
+
/** Wrap text in an OSC 8 hyperlink so modern terminals make it clickable. */
|
|
1428
|
+
function osc8(text, url) {
|
|
1429
|
+
return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
|
|
1430
|
+
}
|
|
1431
|
+
function parseInline(raw, repoUrl) {
|
|
1436
1432
|
const segments = [];
|
|
1437
|
-
const re = /(\*\*(.+?)
|
|
1433
|
+
const re = /(\*\*(.+?)\*\*|\*([^*\n]+)\*|~~([^~\n]+)~~|`([^`]+)`|\[([^\]]*)\]\(([^)]+)\)|<(?:strong|b)>(.+?)<\/(?:strong|b)>|<(?:em|i)>(.+?)<\/(?:em|i)>|<(?:del|s)>(.+?)<\/(?:del|s)>|<(?:code|samp|kbd)>(.+?)<\/(?:code|samp|kbd)>|(?<![/\w&#])#(\d+)|(?<!\w)@([a-zA-Z0-9][a-zA-Z0-9-]*)|(https?:\/\/[^\s)>\]]+))/g;
|
|
1438
1434
|
let last = 0;
|
|
1439
1435
|
let m;
|
|
1440
1436
|
while ((m = re.exec(raw)) !== null) {
|
|
@@ -1445,30 +1441,123 @@ function parseInline(raw) {
|
|
|
1445
1441
|
});
|
|
1446
1442
|
else if (m[3] !== void 0) segments.push({
|
|
1447
1443
|
text: m[3],
|
|
1444
|
+
italic: true
|
|
1445
|
+
});
|
|
1446
|
+
else if (m[4] !== void 0) segments.push({
|
|
1447
|
+
text: m[4],
|
|
1448
|
+
strikethrough: true
|
|
1449
|
+
});
|
|
1450
|
+
else if (m[5] !== void 0) segments.push({
|
|
1451
|
+
text: m[5],
|
|
1448
1452
|
code: true
|
|
1449
1453
|
});
|
|
1450
|
-
else if (m[
|
|
1451
|
-
|
|
1454
|
+
else if (m[6] !== void 0) {
|
|
1455
|
+
const linkText = m[6].trim() || m[7];
|
|
1452
1456
|
segments.push({
|
|
1453
|
-
text:
|
|
1454
|
-
|
|
1457
|
+
text: linkText,
|
|
1458
|
+
link: m[7]
|
|
1455
1459
|
});
|
|
1456
|
-
}
|
|
1460
|
+
} else if (m[8] !== void 0) segments.push({
|
|
1461
|
+
text: m[8],
|
|
1462
|
+
bold: true
|
|
1463
|
+
});
|
|
1464
|
+
else if (m[9] !== void 0) segments.push({
|
|
1465
|
+
text: m[9],
|
|
1466
|
+
italic: true
|
|
1467
|
+
});
|
|
1468
|
+
else if (m[10] !== void 0) segments.push({
|
|
1469
|
+
text: m[10],
|
|
1470
|
+
strikethrough: true
|
|
1471
|
+
});
|
|
1472
|
+
else if (m[11] !== void 0) segments.push({
|
|
1473
|
+
text: m[11],
|
|
1474
|
+
code: true
|
|
1475
|
+
});
|
|
1476
|
+
else if (m[12] !== void 0) {
|
|
1477
|
+
const num = m[12];
|
|
1478
|
+
const url = repoUrl ? `${repoUrl}/issues/${num}` : void 0;
|
|
1479
|
+
segments.push(url ? {
|
|
1480
|
+
text: `#${num}`,
|
|
1481
|
+
link: url
|
|
1482
|
+
} : {
|
|
1483
|
+
text: `#${num}`,
|
|
1484
|
+
code: true
|
|
1485
|
+
});
|
|
1486
|
+
} else if (m[13] !== void 0) {
|
|
1487
|
+
const user = m[13];
|
|
1488
|
+
segments.push({
|
|
1489
|
+
text: `@${user}`,
|
|
1490
|
+
link: `https://github.com/${user}`
|
|
1491
|
+
});
|
|
1492
|
+
} else if (m[14] !== void 0) segments.push({
|
|
1493
|
+
text: m[14],
|
|
1494
|
+
link: m[14]
|
|
1495
|
+
});
|
|
1457
1496
|
last = m.index + m[0].length;
|
|
1458
1497
|
}
|
|
1459
1498
|
if (last < raw.length) segments.push({ text: raw.slice(last) });
|
|
1460
1499
|
return segments;
|
|
1461
1500
|
}
|
|
1462
|
-
function
|
|
1463
|
-
|
|
1464
|
-
|
|
1501
|
+
function Segment({ s, baseColor }) {
|
|
1502
|
+
if (s.link) return /* @__PURE__ */ jsx(Text, {
|
|
1503
|
+
color: "blueBright",
|
|
1504
|
+
underline: true,
|
|
1505
|
+
children: osc8(s.text, s.link)
|
|
1506
|
+
});
|
|
1507
|
+
if (s.code) return /* @__PURE__ */ jsx(Text, {
|
|
1508
|
+
color: "cyan",
|
|
1509
|
+
children: s.text
|
|
1510
|
+
});
|
|
1511
|
+
if (s.bold) return /* @__PURE__ */ jsx(Text, {
|
|
1512
|
+
bold: true,
|
|
1513
|
+
color: baseColor,
|
|
1514
|
+
children: s.text
|
|
1515
|
+
});
|
|
1516
|
+
if (s.italic) return /* @__PURE__ */ jsx(Text, {
|
|
1517
|
+
italic: true,
|
|
1518
|
+
color: baseColor,
|
|
1519
|
+
children: s.text
|
|
1520
|
+
});
|
|
1521
|
+
if (s.strikethrough) return /* @__PURE__ */ jsx(Text, {
|
|
1522
|
+
strikethrough: true,
|
|
1523
|
+
color: "gray",
|
|
1524
|
+
children: s.text
|
|
1525
|
+
});
|
|
1526
|
+
return /* @__PURE__ */ jsx(Text, {
|
|
1527
|
+
color: baseColor,
|
|
1528
|
+
children: s.text
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
const HTML_ENTITIES = {
|
|
1532
|
+
" ": " ",
|
|
1533
|
+
"&": "&",
|
|
1534
|
+
"<": "<",
|
|
1535
|
+
">": ">",
|
|
1536
|
+
""": "\"",
|
|
1537
|
+
"'": "'",
|
|
1538
|
+
"—": "—",
|
|
1539
|
+
"–": "–",
|
|
1540
|
+
"…": "…",
|
|
1541
|
+
"«": "«",
|
|
1542
|
+
"»": "»"
|
|
1543
|
+
};
|
|
1544
|
+
function decodeEntities(s) {
|
|
1545
|
+
return s.replace(/&[a-z]+;/g, (e) => HTML_ENTITIES[e] ?? e).replace(/&#(\d+);/g, (_, n) => String.fromCodePoint(Number(n))).replace(/&#x([0-9a-f]+);/gi, (_, h) => String.fromCodePoint(parseInt(h, 16)));
|
|
1546
|
+
}
|
|
1547
|
+
function MarkdownLine({ line, baseColor = "white", repoUrl }) {
|
|
1548
|
+
const raw = decodeEntities(line);
|
|
1549
|
+
const headingMatch = raw.match(/^(#{1,6})\s+(.*)/);
|
|
1465
1550
|
if (headingMatch) {
|
|
1466
1551
|
const level = headingMatch[1].length;
|
|
1467
1552
|
const text = headingMatch[2];
|
|
1553
|
+
const color = level === 1 ? "whiteBright" : level === 2 ? "cyanBright" : level === 3 ? "cyan" : "gray";
|
|
1468
1554
|
return /* @__PURE__ */ jsxs(Text, {
|
|
1469
|
-
bold:
|
|
1470
|
-
color
|
|
1471
|
-
children: [" ", text
|
|
1555
|
+
bold: level <= 3,
|
|
1556
|
+
color,
|
|
1557
|
+
children: [" " + " ".repeat(Math.max(0, level - 3)), parseInline(text, repoUrl).map((s, i) => /* @__PURE__ */ jsx(Segment, {
|
|
1558
|
+
s,
|
|
1559
|
+
baseColor: color
|
|
1560
|
+
}, i))]
|
|
1472
1561
|
});
|
|
1473
1562
|
}
|
|
1474
1563
|
if (raw.startsWith("> ")) return /* @__PURE__ */ jsxs(Text, {
|
|
@@ -1479,11 +1568,10 @@ function MarkdownLine({ line, baseColor = "white", baseDim = false }) {
|
|
|
1479
1568
|
if (listMatch) {
|
|
1480
1569
|
const indent = listMatch[1].match(/^\s*/)?.[0].length ?? 0;
|
|
1481
1570
|
const content = listMatch[2];
|
|
1482
|
-
const segments = parseInline(content);
|
|
1483
|
-
return /* @__PURE__ */ jsxs(Text, { children: [" " + " ".repeat(indent) + "• ", segments.map((s, i) => /* @__PURE__ */ jsx(
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
children: s.text
|
|
1571
|
+
const segments = parseInline(content, repoUrl);
|
|
1572
|
+
return /* @__PURE__ */ jsxs(Text, { children: [" " + " ".repeat(indent) + "• ", segments.map((s, i) => /* @__PURE__ */ jsx(Segment, {
|
|
1573
|
+
s,
|
|
1574
|
+
baseColor
|
|
1487
1575
|
}, i))] });
|
|
1488
1576
|
}
|
|
1489
1577
|
if (/^[-*_]{3,}$/.test(raw.trim())) return /* @__PURE__ */ jsx(Text, {
|
|
@@ -1491,10 +1579,9 @@ function MarkdownLine({ line, baseColor = "white", baseDim = false }) {
|
|
|
1491
1579
|
children: " ────────────────────"
|
|
1492
1580
|
});
|
|
1493
1581
|
if (!raw.trim()) return /* @__PURE__ */ jsx(Text, { children: " " });
|
|
1494
|
-
return /* @__PURE__ */ jsxs(Text, { children: [" ", parseInline(raw).map((s, i) => /* @__PURE__ */ jsx(
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
children: s.text
|
|
1582
|
+
return /* @__PURE__ */ jsxs(Text, { children: [" ", parseInline(raw, repoUrl).map((s, i) => /* @__PURE__ */ jsx(Segment, {
|
|
1583
|
+
s,
|
|
1584
|
+
baseColor
|
|
1498
1585
|
}, i))] });
|
|
1499
1586
|
}
|
|
1500
1587
|
//#endregion
|
|
@@ -1506,22 +1593,19 @@ function ChangelogPanel({ pkg, onClose }) {
|
|
|
1506
1593
|
const [opened, setOpened] = useState(false);
|
|
1507
1594
|
const [activeEntry, setActiveEntry] = useState(0);
|
|
1508
1595
|
const scrollRef = useRef(null);
|
|
1509
|
-
const {
|
|
1596
|
+
const { columns, rows } = useWindowSize();
|
|
1597
|
+
const isUpToDate = pkg.current === (pkg.targetVersion ?? pkg.latest);
|
|
1510
1598
|
useEffect(() => {
|
|
1511
|
-
Promise.all([fetchChangelog(pkg.name, pkg.current, pkg.targetVersion ?? pkg.latest), fetchRepoUrl(pkg.name)]).then(([e, repo]) => {
|
|
1599
|
+
Promise.all([fetchChangelog(pkg.name, isUpToDate ? "" : pkg.current, pkg.targetVersion ?? pkg.latest), fetchRepoUrl(pkg.name)]).then(([e, repo]) => {
|
|
1512
1600
|
setEntries(e);
|
|
1513
|
-
setActiveEntry(0);
|
|
1601
|
+
setActiveEntry(isUpToDate ? Math.max(0, e.length - 1) : 0);
|
|
1514
1602
|
setRepoUrl(repo);
|
|
1515
1603
|
setLoading(false);
|
|
1516
1604
|
});
|
|
1517
1605
|
}, [pkg.name]);
|
|
1518
1606
|
useEffect(() => {
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
return () => {
|
|
1522
|
-
stdout?.off("resize", handleResize);
|
|
1523
|
-
};
|
|
1524
|
-
}, [stdout]);
|
|
1607
|
+
scrollRef.current?.remeasure();
|
|
1608
|
+
}, [columns, rows]);
|
|
1525
1609
|
const releasesPageUrl = repoUrl ? `${repoUrl}/releases` : "";
|
|
1526
1610
|
const triggerOpen = (url) => {
|
|
1527
1611
|
openInBrowser(url);
|
|
@@ -1597,14 +1681,6 @@ function ChangelogPanel({ pkg, onClose }) {
|
|
|
1597
1681
|
})
|
|
1598
1682
|
]
|
|
1599
1683
|
}),
|
|
1600
|
-
repoUrl && /* @__PURE__ */ jsxs(Text, {
|
|
1601
|
-
color: "gray",
|
|
1602
|
-
children: [
|
|
1603
|
-
" ",
|
|
1604
|
-
repoUrl,
|
|
1605
|
-
"/releases"
|
|
1606
|
-
]
|
|
1607
|
-
}),
|
|
1608
1684
|
/* @__PURE__ */ jsx(Text, {
|
|
1609
1685
|
color: "gray",
|
|
1610
1686
|
children: "────────────────────────────────────────────────────"
|
|
@@ -1674,7 +1750,10 @@ function ChangelogPanel({ pkg, onClose }) {
|
|
|
1674
1750
|
ref: scrollRef,
|
|
1675
1751
|
children: /* @__PURE__ */ jsx(Box, {
|
|
1676
1752
|
flexDirection: "column",
|
|
1677
|
-
children: currentEntry.body.split("\n").map((line, j) => /* @__PURE__ */ jsx(MarkdownLine, {
|
|
1753
|
+
children: currentEntry.body.split("\n").map((line, j) => /* @__PURE__ */ jsx(MarkdownLine, {
|
|
1754
|
+
line,
|
|
1755
|
+
repoUrl
|
|
1756
|
+
}, j))
|
|
1678
1757
|
})
|
|
1679
1758
|
})
|
|
1680
1759
|
}) : null,
|
|
@@ -1743,85 +1822,6 @@ function ChangelogPanel({ pkg, onClose }) {
|
|
|
1743
1822
|
});
|
|
1744
1823
|
}
|
|
1745
1824
|
//#endregion
|
|
1746
|
-
//#region src/ui/UpdateResults.tsx
|
|
1747
|
-
function UpdateResults({ results, onDone }) {
|
|
1748
|
-
useEffect(() => {
|
|
1749
|
-
const timer = setTimeout(onDone, 1e3);
|
|
1750
|
-
return () => clearTimeout(timer);
|
|
1751
|
-
}, []);
|
|
1752
|
-
const success = results.filter((r) => r.success);
|
|
1753
|
-
const failed = results.filter((r) => !r.success);
|
|
1754
|
-
return /* @__PURE__ */ jsxs(Box, {
|
|
1755
|
-
flexDirection: "column",
|
|
1756
|
-
children: [
|
|
1757
|
-
/* @__PURE__ */ jsx(Box, {
|
|
1758
|
-
marginBottom: 1,
|
|
1759
|
-
children: /* @__PURE__ */ jsxs(Text, {
|
|
1760
|
-
bold: true,
|
|
1761
|
-
color: "greenBright",
|
|
1762
|
-
children: [" ", "Update complete"]
|
|
1763
|
-
})
|
|
1764
|
-
}),
|
|
1765
|
-
success.map((r) => /* @__PURE__ */ jsxs(Box, {
|
|
1766
|
-
gap: 2,
|
|
1767
|
-
children: [
|
|
1768
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1769
|
-
color: "greenBright",
|
|
1770
|
-
children: "✓"
|
|
1771
|
-
}),
|
|
1772
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1773
|
-
color: "white",
|
|
1774
|
-
children: r.name
|
|
1775
|
-
}),
|
|
1776
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1777
|
-
color: "gray",
|
|
1778
|
-
children: r.fromVersion
|
|
1779
|
-
}),
|
|
1780
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1781
|
-
color: "gray",
|
|
1782
|
-
children: "→"
|
|
1783
|
-
}),
|
|
1784
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1785
|
-
color: "greenBright",
|
|
1786
|
-
children: r.version
|
|
1787
|
-
})
|
|
1788
|
-
]
|
|
1789
|
-
}, r.name)),
|
|
1790
|
-
failed.map((r) => /* @__PURE__ */ jsxs(Box, {
|
|
1791
|
-
flexDirection: "column",
|
|
1792
|
-
children: [/* @__PURE__ */ jsxs(Box, {
|
|
1793
|
-
gap: 2,
|
|
1794
|
-
children: [
|
|
1795
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1796
|
-
color: "red",
|
|
1797
|
-
children: "✗"
|
|
1798
|
-
}),
|
|
1799
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1800
|
-
color: "white",
|
|
1801
|
-
children: r.name
|
|
1802
|
-
}),
|
|
1803
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1804
|
-
color: "gray",
|
|
1805
|
-
children: r.fromVersion
|
|
1806
|
-
}),
|
|
1807
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1808
|
-
color: "gray",
|
|
1809
|
-
children: "→"
|
|
1810
|
-
}),
|
|
1811
|
-
/* @__PURE__ */ jsx(Text, {
|
|
1812
|
-
color: "red",
|
|
1813
|
-
children: r.version
|
|
1814
|
-
})
|
|
1815
|
-
]
|
|
1816
|
-
}), r.error && /* @__PURE__ */ jsxs(Text, {
|
|
1817
|
-
color: "red",
|
|
1818
|
-
children: [" ", r.error.slice(0, 80)]
|
|
1819
|
-
})]
|
|
1820
|
-
}, r.name))
|
|
1821
|
-
]
|
|
1822
|
-
});
|
|
1823
|
-
}
|
|
1824
|
-
//#endregion
|
|
1825
1825
|
//#region src/ui/SettingsToggle.tsx
|
|
1826
1826
|
function SettingsToggle({ label, description, checked, focused, disabled = false }) {
|
|
1827
1827
|
return /* @__PURE__ */ jsxs(Box, {
|
|
@@ -2187,6 +2187,8 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
|
|
|
2187
2187
|
//#endregion
|
|
2188
2188
|
//#region src/ui/TerminalOutputBox.tsx
|
|
2189
2189
|
function TerminalOutputBox({ message, command, outputLines, maxLines }) {
|
|
2190
|
+
const { columns } = useWindowSize();
|
|
2191
|
+
const boxWidth = Math.min(64, columns - 4);
|
|
2190
2192
|
return /* @__PURE__ */ jsxs(Box, {
|
|
2191
2193
|
flexDirection: "column",
|
|
2192
2194
|
padding: 1,
|
|
@@ -2209,7 +2211,7 @@ function TerminalOutputBox({ message, command, outputLines, maxLines }) {
|
|
|
2209
2211
|
borderStyle: "round",
|
|
2210
2212
|
borderColor: "gray",
|
|
2211
2213
|
paddingX: 1,
|
|
2212
|
-
width:
|
|
2214
|
+
width: boxWidth,
|
|
2213
2215
|
height: maxLines + 3,
|
|
2214
2216
|
overflow: "hidden",
|
|
2215
2217
|
children: [command !== "" && /* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(Text, {
|
|
@@ -2352,7 +2354,7 @@ function useExitOnScreen(screen, targetScreens, exit, options) {
|
|
|
2352
2354
|
}
|
|
2353
2355
|
//#endregion
|
|
2354
2356
|
//#region src/ui/App.tsx
|
|
2355
|
-
function App({ project, global, showAll, version, installManager }) {
|
|
2357
|
+
function App({ project, global, showAll, version, installManager, onCancelled, onCopied }) {
|
|
2356
2358
|
const { exit } = useApp();
|
|
2357
2359
|
const [screen, setScreen] = useState("self-update-check");
|
|
2358
2360
|
const [config, setConfig] = useState(() => loadConfig());
|
|
@@ -2368,7 +2370,7 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2368
2370
|
useExitOnScreen(screen, ["self-update-done", "empty"], exit);
|
|
2369
2371
|
useExitOnScreen(screen, ["cancelled"], exit, {
|
|
2370
2372
|
delay: 200,
|
|
2371
|
-
beforeExit: () =>
|
|
2373
|
+
beforeExit: () => onCancelled?.()
|
|
2372
2374
|
});
|
|
2373
2375
|
useEffect(() => {
|
|
2374
2376
|
if (!selfUpdate.checkComplete) return;
|
|
@@ -2403,21 +2405,15 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2403
2405
|
setConfig(newConfig);
|
|
2404
2406
|
saveConfig(newConfig);
|
|
2405
2407
|
};
|
|
2406
|
-
const
|
|
2407
|
-
const handleConfirm = async () => {
|
|
2408
|
+
const handleConfirm = () => {
|
|
2408
2409
|
const selected = packages.filter((p) => p.selected);
|
|
2409
2410
|
if (selected.length === 0) return;
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
const successNames = res.filter((r) => r.success).map((r) => r.name);
|
|
2417
|
-
if (successNames.length > 0) {
|
|
2418
|
-
incrementFrequency(successNames);
|
|
2419
|
-
setFrequency(loadFrequency());
|
|
2420
|
-
}
|
|
2411
|
+
const commands = buildUpdateCommands(project.manager, selected, global);
|
|
2412
|
+
copyToClipboard(commands.join(" && "));
|
|
2413
|
+
incrementFrequency(selected.map((p) => p.name));
|
|
2414
|
+
setFrequency(loadFrequency());
|
|
2415
|
+
onCopied?.(commands);
|
|
2416
|
+
exit();
|
|
2421
2417
|
};
|
|
2422
2418
|
if (screen === "self-update-check") return /* @__PURE__ */ jsxs(Box, {
|
|
2423
2419
|
flexDirection: "column",
|
|
@@ -2518,22 +2514,6 @@ function App({ project, global, showAll, version, installManager }) {
|
|
|
2518
2514
|
});
|
|
2519
2515
|
const isListActive = screen === "list";
|
|
2520
2516
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2521
|
-
screen === "updating" && /* @__PURE__ */ jsx(TerminalOutputBox, {
|
|
2522
|
-
message: terminal.loadingMsg,
|
|
2523
|
-
command: terminal.terminalCmd,
|
|
2524
|
-
outputLines: terminal.outputLines,
|
|
2525
|
-
maxLines: terminal.maxLines
|
|
2526
|
-
}),
|
|
2527
|
-
screen === "results" && /* @__PURE__ */ jsx(Box, {
|
|
2528
|
-
padding: 1,
|
|
2529
|
-
children: /* @__PURE__ */ jsx(UpdateResults, {
|
|
2530
|
-
results,
|
|
2531
|
-
onDone: () => {
|
|
2532
|
-
exit();
|
|
2533
|
-
process.exit(0);
|
|
2534
|
-
}
|
|
2535
|
-
})
|
|
2536
|
-
}),
|
|
2537
2517
|
screen === "settings" && /* @__PURE__ */ jsx(Box, {
|
|
2538
2518
|
padding: 1,
|
|
2539
2519
|
children: /* @__PURE__ */ jsx(Settings, {
|
|
@@ -2616,7 +2596,7 @@ if (showHelp) {
|
|
|
2616
2596
|
space toggle select
|
|
2617
2597
|
v pick specific version
|
|
2618
2598
|
c view changelog / release notes
|
|
2619
|
-
enter update
|
|
2599
|
+
enter copy update command to clipboard & exit
|
|
2620
2600
|
esc cancel / go back
|
|
2621
2601
|
`);
|
|
2622
2602
|
process.exit(0);
|
|
@@ -2627,13 +2607,28 @@ if (!isGlobal && !hasPackageJson(cwd)) {
|
|
|
2627
2607
|
console.log(" Run ripen inside a Node.js project, or use ripen -g for global packages.\n");
|
|
2628
2608
|
process.exit(1);
|
|
2629
2609
|
}
|
|
2610
|
+
const project = getProjectInfo(cwd);
|
|
2611
|
+
const installManager = detectGlobalInstallManager();
|
|
2612
|
+
let wasCancelled = false;
|
|
2613
|
+
let copiedCommands = [];
|
|
2630
2614
|
const { waitUntilExit } = render(/* @__PURE__ */ jsx(App, {
|
|
2631
|
-
project
|
|
2615
|
+
project,
|
|
2632
2616
|
global: isGlobal,
|
|
2633
2617
|
showAll,
|
|
2634
2618
|
version: VERSION,
|
|
2635
|
-
installManager
|
|
2636
|
-
|
|
2619
|
+
installManager,
|
|
2620
|
+
onCancelled: () => {
|
|
2621
|
+
wasCancelled = true;
|
|
2622
|
+
},
|
|
2623
|
+
onCopied: (cmds) => {
|
|
2624
|
+
copiedCommands = cmds;
|
|
2625
|
+
}
|
|
2626
|
+
}), {
|
|
2627
|
+
exitOnCtrlC: false,
|
|
2628
|
+
alternateScreen: true
|
|
2629
|
+
});
|
|
2637
2630
|
await waitUntilExit();
|
|
2631
|
+
if (wasCancelled) process.stdout.write(" \x1B[32mCancelled.\x1B[0m\n");
|
|
2632
|
+
else if (copiedCommands.length > 0) process.stdout.write(" \x1B[32mCopied to clipboard.\x1B[0m\n");
|
|
2638
2633
|
//#endregion
|
|
2639
2634
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ripencli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Interactive dependency updater for npm, pnpm, yarn, and bun",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -35,7 +35,7 @@
|
|
|
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
|
}
|