ripencli 0.3.4 → 1.0.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 +5 -1
  2. package/dist/cli.js +152 -42
  3. package/package.json +7 -4
package/README.md CHANGED
@@ -15,6 +15,7 @@
15
15
  - **Changelog viewer** — see GitHub release notes before you update
16
16
  - **npm, pnpm, yarn & bun** — auto-detects your package manager
17
17
  - **Global packages** — check and update global installs across all* package managers
18
+ - **Show all packages** — `ripen --all` lists every dependency, not just outdated ones (great for checking changelogs or downgrading)
18
19
  - **Self-update** — notifies you when a new version of ripen is available
19
20
  - **Major bump warnings** — highlights potentially breaking updates
20
21
  - **Scope grouping** — optionally group scoped packages (e.g. `@heroui/*`) together
@@ -41,6 +42,9 @@ ripen
41
42
  # Check global packages (scans npm, pnpm, and yarn)
42
43
  ripen -g
43
44
 
45
+ # Show all packages, not just outdated ones
46
+ ripen --all
47
+
44
48
  # Help
45
49
  ripen --help
46
50
  ```
@@ -61,7 +65,7 @@ ripen --help
61
65
 
62
66
  1. Reads your `package.json` and checks each dependency against the npm registry directly
63
67
  2. Detects your package manager from the lock file (`bun.lock`, `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`) for running updates
64
- 3. Shows outdated packages in a colorful interactive list
68
+ 3. Shows outdated packages in a colorful interactive list (use `--all` to show every package, including up-to-date ones)
65
69
  4. Press `v` on any package to pick a specific version from the npm registry
66
70
  5. Press `c` to see GitHub release notes between your current and target version
67
71
  6. Select the ones you want and press enter — ripen runs the update commands for you
package/dist/cli.js CHANGED
@@ -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
  }
@@ -239,7 +241,7 @@ async function fetchAllLatest(names, concurrency, onLine) {
239
241
  await Promise.all(Array.from({ length: Math.min(concurrency, names.length) }, () => worker()));
240
242
  return results;
241
243
  }
242
- async function getOutdatedPackages(manager, cwd, global = false, onLine) {
244
+ async function getOutdatedPackages(manager, cwd, global = false, onLine, showAll = false) {
243
245
  if (global) return getGlobalOutdatedPackages(manager, cwd, onLine);
244
246
  let deps;
245
247
  try {
@@ -263,7 +265,7 @@ async function getOutdatedPackages(manager, cwd, global = false, onLine) {
263
265
  for (const dep of deps) {
264
266
  const latest = latestVersions.get(dep.name);
265
267
  if (!latest) continue;
266
- if (!isNewerVersion(dep.current, latest)) continue;
268
+ if (!showAll && !isNewerVersion(dep.current, latest)) continue;
267
269
  packages.push({
268
270
  name: dep.name,
269
271
  current: dep.current,
@@ -853,7 +855,7 @@ function computeMaxPerGroup(terminalRows, groupCount) {
853
855
  }
854
856
  //#endregion
855
857
  //#region src/ui/package-list/PackageList.tsx
856
- function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onViewChangelog, onConfirm, onOpenSettings, groupByScope = false, groupScopes, groupsOnTop = false, frequencySort = false, frequency = {}, separateDevDeps = true, isActive = true }) {
858
+ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onViewChangelog, onConfirm, onOpenSettings, groupByScope = false, groupScopes, groupsOnTop = false, frequencySort = false, frequency = {}, separateDevDeps = true, showAll = false, isActive = true }) {
857
859
  const [focusedIndex, setFocusedIndex] = useState(0);
858
860
  const allRows = useMemo(() => buildDisplayRows(packages, groupByScope, groupScopes, groupsOnTop, frequencySort, frequency, separateDevDeps), [
859
861
  packages,
@@ -949,6 +951,7 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
949
951
  if (key.return) onConfirm();
950
952
  }, { isActive });
951
953
  const selectedCount = packages.filter((p) => p.selected).length;
954
+ const outdatedCount = packages.filter((p) => p.current !== p.latest).length;
952
955
  return /* @__PURE__ */ jsxs(Box, {
953
956
  flexDirection: "column",
954
957
  children: [
@@ -1044,7 +1047,18 @@ function PackageList({ packages, onToggle, onToggleMany, onSelectVersion, onView
1044
1047
  children: " selected"
1045
1048
  }),
1046
1049
  " ",
1047
- /* @__PURE__ */ jsxs(Text, {
1050
+ showAll ? /* @__PURE__ */ jsxs(Text, {
1051
+ color: "gray",
1052
+ children: [
1053
+ packages.length,
1054
+ " packages",
1055
+ " ",
1056
+ /* @__PURE__ */ jsxs(Text, {
1057
+ color: outdatedCount > 0 ? "yellow" : "green",
1058
+ children: [outdatedCount, " outdated"]
1059
+ })
1060
+ ]
1061
+ }) : /* @__PURE__ */ jsxs(Text, {
1048
1062
  color: "gray",
1049
1063
  children: [packages.length, " outdated"]
1050
1064
  })
@@ -1186,7 +1200,8 @@ function PackageGroupBox({ group, focusedIndex, collapsedScopes, scrollOffset, m
1186
1200
  }
1187
1201
  if (row.kind !== "package") return null;
1188
1202
  const pkg = row.pkg;
1189
- const isMajorBump = parseInt(pkg.latest) > parseInt(pkg.current);
1203
+ const isUpToDate = pkg.current === pkg.latest;
1204
+ const isMajorBump = !isUpToDate && parseInt(pkg.latest) > parseInt(pkg.current);
1190
1205
  return /* @__PURE__ */ jsxs(Box, {
1191
1206
  gap: 2,
1192
1207
  children: [
@@ -1213,7 +1228,7 @@ function PackageGroupBox({ group, focusedIndex, collapsedScopes, scrollOffset, m
1213
1228
  /* @__PURE__ */ jsx(Box, {
1214
1229
  width: 14,
1215
1230
  children: /* @__PURE__ */ jsx(Text, {
1216
- color: "red",
1231
+ color: isUpToDate ? "green" : "red",
1217
1232
  children: pkg.current
1218
1233
  })
1219
1234
  }),
@@ -1419,9 +1434,13 @@ function openInBrowser(url) {
1419
1434
  }
1420
1435
  //#endregion
1421
1436
  //#region src/ui/MarkdownLine.tsx
1422
- function parseInline(raw) {
1437
+ /** Wrap text in an OSC 8 hyperlink so modern terminals make it clickable. */
1438
+ function osc8(text, url) {
1439
+ return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
1440
+ }
1441
+ function parseInline(raw, repoUrl) {
1423
1442
  const segments = [];
1424
- const re = /(\*\*(.+?)\*\*|`([^`]+)`|\[([^\]]+)\]\(([^)]+)\))/g;
1443
+ 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;
1425
1444
  let last = 0;
1426
1445
  let m;
1427
1446
  while ((m = re.exec(raw)) !== null) {
@@ -1432,30 +1451,123 @@ function parseInline(raw) {
1432
1451
  });
1433
1452
  else if (m[3] !== void 0) segments.push({
1434
1453
  text: m[3],
1454
+ italic: true
1455
+ });
1456
+ else if (m[4] !== void 0) segments.push({
1457
+ text: m[4],
1458
+ strikethrough: true
1459
+ });
1460
+ else if (m[5] !== void 0) segments.push({
1461
+ text: m[5],
1435
1462
  code: true
1436
1463
  });
1437
- else if (m[4] !== void 0) {
1438
- segments.push({ text: m[4] });
1464
+ else if (m[6] !== void 0) {
1465
+ const linkText = m[6].trim() || m[7];
1439
1466
  segments.push({
1440
- text: ` (${m[5]})`,
1441
- dim: true
1467
+ text: linkText,
1468
+ link: m[7]
1442
1469
  });
1443
- }
1470
+ } else if (m[8] !== void 0) segments.push({
1471
+ text: m[8],
1472
+ bold: true
1473
+ });
1474
+ else if (m[9] !== void 0) segments.push({
1475
+ text: m[9],
1476
+ italic: true
1477
+ });
1478
+ else if (m[10] !== void 0) segments.push({
1479
+ text: m[10],
1480
+ strikethrough: true
1481
+ });
1482
+ else if (m[11] !== void 0) segments.push({
1483
+ text: m[11],
1484
+ code: true
1485
+ });
1486
+ else if (m[12] !== void 0) {
1487
+ const num = m[12];
1488
+ const url = repoUrl ? `${repoUrl}/issues/${num}` : void 0;
1489
+ segments.push(url ? {
1490
+ text: `#${num}`,
1491
+ link: url
1492
+ } : {
1493
+ text: `#${num}`,
1494
+ code: true
1495
+ });
1496
+ } else if (m[13] !== void 0) {
1497
+ const user = m[13];
1498
+ segments.push({
1499
+ text: `@${user}`,
1500
+ link: `https://github.com/${user}`
1501
+ });
1502
+ } else if (m[14] !== void 0) segments.push({
1503
+ text: m[14],
1504
+ link: m[14]
1505
+ });
1444
1506
  last = m.index + m[0].length;
1445
1507
  }
1446
1508
  if (last < raw.length) segments.push({ text: raw.slice(last) });
1447
1509
  return segments;
1448
1510
  }
1449
- function MarkdownLine({ line, baseColor = "white", baseDim = false }) {
1450
- const raw = line;
1451
- const headingMatch = raw.match(/^(#{1,3})\s+(.*)/);
1511
+ function Segment({ s, baseColor }) {
1512
+ if (s.link) return /* @__PURE__ */ jsx(Text, {
1513
+ color: "blueBright",
1514
+ underline: true,
1515
+ children: osc8(s.text, s.link)
1516
+ });
1517
+ if (s.code) return /* @__PURE__ */ jsx(Text, {
1518
+ color: "cyan",
1519
+ children: s.text
1520
+ });
1521
+ if (s.bold) return /* @__PURE__ */ jsx(Text, {
1522
+ bold: true,
1523
+ color: baseColor,
1524
+ children: s.text
1525
+ });
1526
+ if (s.italic) return /* @__PURE__ */ jsx(Text, {
1527
+ italic: true,
1528
+ color: baseColor,
1529
+ children: s.text
1530
+ });
1531
+ if (s.strikethrough) return /* @__PURE__ */ jsx(Text, {
1532
+ strikethrough: true,
1533
+ color: "gray",
1534
+ children: s.text
1535
+ });
1536
+ return /* @__PURE__ */ jsx(Text, {
1537
+ color: baseColor,
1538
+ children: s.text
1539
+ });
1540
+ }
1541
+ const HTML_ENTITIES = {
1542
+ "&nbsp;": " ",
1543
+ "&amp;": "&",
1544
+ "&lt;": "<",
1545
+ "&gt;": ">",
1546
+ "&quot;": "\"",
1547
+ "&apos;": "'",
1548
+ "&mdash;": "—",
1549
+ "&ndash;": "–",
1550
+ "&hellip;": "…",
1551
+ "&laquo;": "«",
1552
+ "&raquo;": "»"
1553
+ };
1554
+ function decodeEntities(s) {
1555
+ 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)));
1556
+ }
1557
+ function MarkdownLine({ line, baseColor = "white", repoUrl }) {
1558
+ const raw = decodeEntities(line);
1559
+ const headingMatch = raw.match(/^(#{1,6})\s+(.*)/);
1452
1560
  if (headingMatch) {
1453
1561
  const level = headingMatch[1].length;
1454
1562
  const text = headingMatch[2];
1563
+ const color = level === 1 ? "whiteBright" : level === 2 ? "cyanBright" : level === 3 ? "cyan" : "gray";
1455
1564
  return /* @__PURE__ */ jsxs(Text, {
1456
- bold: true,
1457
- color: level === 1 ? "whiteBright" : level === 2 ? "white" : "gray",
1458
- children: [" ", text]
1565
+ bold: level <= 3,
1566
+ color,
1567
+ children: [" " + " ".repeat(Math.max(0, level - 3)), parseInline(text, repoUrl).map((s, i) => /* @__PURE__ */ jsx(Segment, {
1568
+ s,
1569
+ baseColor: color
1570
+ }, i))]
1459
1571
  });
1460
1572
  }
1461
1573
  if (raw.startsWith("> ")) return /* @__PURE__ */ jsxs(Text, {
@@ -1466,11 +1578,10 @@ function MarkdownLine({ line, baseColor = "white", baseDim = false }) {
1466
1578
  if (listMatch) {
1467
1579
  const indent = listMatch[1].match(/^\s*/)?.[0].length ?? 0;
1468
1580
  const content = listMatch[2];
1469
- const segments = parseInline(content);
1470
- return /* @__PURE__ */ jsxs(Text, { children: [" " + " ".repeat(indent) + "• ", segments.map((s, i) => /* @__PURE__ */ jsx(Text, {
1471
- bold: s.bold,
1472
- color: s.code ? "cyan" : s.dim ? "gray" : baseColor,
1473
- children: s.text
1581
+ const segments = parseInline(content, repoUrl);
1582
+ return /* @__PURE__ */ jsxs(Text, { children: [" " + " ".repeat(indent) + "• ", segments.map((s, i) => /* @__PURE__ */ jsx(Segment, {
1583
+ s,
1584
+ baseColor
1474
1585
  }, i))] });
1475
1586
  }
1476
1587
  if (/^[-*_]{3,}$/.test(raw.trim())) return /* @__PURE__ */ jsx(Text, {
@@ -1478,10 +1589,9 @@ function MarkdownLine({ line, baseColor = "white", baseDim = false }) {
1478
1589
  children: " ────────────────────"
1479
1590
  });
1480
1591
  if (!raw.trim()) return /* @__PURE__ */ jsx(Text, { children: " " });
1481
- return /* @__PURE__ */ jsxs(Text, { children: [" ", parseInline(raw).map((s, i) => /* @__PURE__ */ jsx(Text, {
1482
- bold: s.bold,
1483
- color: s.code ? "cyan" : s.dim ? "gray" : baseColor,
1484
- children: s.text
1592
+ return /* @__PURE__ */ jsxs(Text, { children: [" ", parseInline(raw, repoUrl).map((s, i) => /* @__PURE__ */ jsx(Segment, {
1593
+ s,
1594
+ baseColor
1485
1595
  }, i))] });
1486
1596
  }
1487
1597
  //#endregion
@@ -1494,10 +1604,11 @@ function ChangelogPanel({ pkg, onClose }) {
1494
1604
  const [activeEntry, setActiveEntry] = useState(0);
1495
1605
  const scrollRef = useRef(null);
1496
1606
  const { stdout } = useStdout();
1607
+ const isUpToDate = pkg.current === (pkg.targetVersion ?? pkg.latest);
1497
1608
  useEffect(() => {
1498
- Promise.all([fetchChangelog(pkg.name, pkg.current, pkg.targetVersion ?? pkg.latest), fetchRepoUrl(pkg.name)]).then(([e, repo]) => {
1609
+ Promise.all([fetchChangelog(pkg.name, isUpToDate ? "" : pkg.current, pkg.targetVersion ?? pkg.latest), fetchRepoUrl(pkg.name)]).then(([e, repo]) => {
1499
1610
  setEntries(e);
1500
- setActiveEntry(0);
1611
+ setActiveEntry(isUpToDate ? Math.max(0, e.length - 1) : 0);
1501
1612
  setRepoUrl(repo);
1502
1613
  setLoading(false);
1503
1614
  });
@@ -1584,14 +1695,6 @@ function ChangelogPanel({ pkg, onClose }) {
1584
1695
  })
1585
1696
  ]
1586
1697
  }),
1587
- repoUrl && /* @__PURE__ */ jsxs(Text, {
1588
- color: "gray",
1589
- children: [
1590
- " ",
1591
- repoUrl,
1592
- "/releases"
1593
- ]
1594
- }),
1595
1698
  /* @__PURE__ */ jsx(Text, {
1596
1699
  color: "gray",
1597
1700
  children: "────────────────────────────────────────────────────"
@@ -1661,7 +1764,10 @@ function ChangelogPanel({ pkg, onClose }) {
1661
1764
  ref: scrollRef,
1662
1765
  children: /* @__PURE__ */ jsx(Box, {
1663
1766
  flexDirection: "column",
1664
- children: currentEntry.body.split("\n").map((line, j) => /* @__PURE__ */ jsx(MarkdownLine, { line }, j))
1767
+ children: currentEntry.body.split("\n").map((line, j) => /* @__PURE__ */ jsx(MarkdownLine, {
1768
+ line,
1769
+ repoUrl
1770
+ }, j))
1665
1771
  })
1666
1772
  })
1667
1773
  }) : null,
@@ -2339,7 +2445,7 @@ function useExitOnScreen(screen, targetScreens, exit, options) {
2339
2445
  }
2340
2446
  //#endregion
2341
2447
  //#region src/ui/App.tsx
2342
- function App({ project, global, version, installManager }) {
2448
+ function App({ project, global, showAll, version, installManager }) {
2343
2449
  const { exit } = useApp();
2344
2450
  const [screen, setScreen] = useState("self-update-check");
2345
2451
  const [config, setConfig] = useState(() => loadConfig());
@@ -2372,7 +2478,7 @@ function App({ project, global, version, installManager }) {
2372
2478
  setFetchStarted(true);
2373
2479
  terminal.setLoadingMsg("Checking for outdated packages…");
2374
2480
  terminal.setTerminalCmd(global ? "Checking all package managers…" : "Checking npm registry…");
2375
- (global ? getAllGlobalOutdated(project.cwd, terminal.onLine) : getOutdatedPackages(project.manager, project.cwd, false, terminal.onLine)).then((result) => {
2481
+ (global ? getAllGlobalOutdated(project.cwd, terminal.onLine) : getOutdatedPackages(project.manager, project.cwd, false, terminal.onLine, showAll)).then((result) => {
2376
2482
  if (!result.ok) {
2377
2483
  setErrorMsg(result.error);
2378
2484
  setScreen("error");
@@ -2570,6 +2676,7 @@ function App({ project, global, version, installManager }) {
2570
2676
  frequencySort: config.frequencySort,
2571
2677
  frequency,
2572
2678
  separateDevDeps: config.separateDevDeps,
2679
+ showAll,
2573
2680
  isActive: isListActive
2574
2681
  })
2575
2682
  })
@@ -2580,6 +2687,7 @@ function App({ project, global, version, installManager }) {
2580
2687
  const { version: VERSION } = createRequire(import.meta.url)("../package.json");
2581
2688
  const args = process.argv.slice(2);
2582
2689
  const isGlobal = args.includes("--global") || args.includes("-g");
2690
+ const showAll = args.includes("--all") || args.includes("-a");
2583
2691
  const showHelp = args.includes("--help") || args.includes("-h");
2584
2692
  if (args.includes("--version") || args.includes("-V")) {
2585
2693
  console.log(VERSION);
@@ -2592,6 +2700,7 @@ if (showHelp) {
2592
2700
  Usage:
2593
2701
  ripen check current project
2594
2702
  ripen -g check global packages
2703
+ ripen -a show all packages, not just outdated ones
2595
2704
  ripen --help show this help
2596
2705
  ripen --version show version
2597
2706
 
@@ -2614,6 +2723,7 @@ if (!isGlobal && !hasPackageJson(cwd)) {
2614
2723
  const { waitUntilExit } = render(/* @__PURE__ */ jsx(App, {
2615
2724
  project: getProjectInfo(cwd),
2616
2725
  global: isGlobal,
2726
+ showAll,
2617
2727
  version: VERSION,
2618
2728
  installManager: detectGlobalInstallManager()
2619
2729
  }), { exitOnCtrlC: false });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ripencli",
3
- "version": "0.3.4",
3
+ "version": "1.0.1",
4
4
  "description": "Interactive dependency updater for npm, pnpm, yarn, and bun",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -35,7 +35,10 @@
35
35
  "dev": "tsx src/cli.tsx",
36
36
  "typecheck": "tsc --noEmit",
37
37
  "start": "node dist/cli.js",
38
- "checks": "node scripts/pr-checks.ts"
38
+ "checks": "node scripts/pr-checks.ts",
39
+ "docs:dev": "pnpm --prefix docs dev",
40
+ "docs:build": "pnpm --prefix docs build",
41
+ "docs:start": "pnpm --prefix docs start"
39
42
  },
40
43
  "keywords": [
41
44
  "npm",
@@ -71,7 +74,7 @@
71
74
  "@types/node": "^25.5.0",
72
75
  "@types/react": "^19.2.14",
73
76
  "prettier": "^3.8.1",
74
- "tsdown": "^0.21.4",
75
- "typescript": "^5.9.3"
77
+ "tsdown": "^0.21.6",
78
+ "typescript": "^6.0.2"
76
79
  }
77
80
  }