ripencli 0.2.2 → 0.2.4
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 +10 -7
- package/dist/cli.js +50 -45
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
# ripen
|
|
2
2
|
|
|
3
|
-
> Interactive dependency updater for npm, pnpm, and
|
|
3
|
+
> Interactive dependency updater for npm, pnpm, yarn, and bun
|
|
4
|
+
|
|
5
|
+
  
|
|
4
6
|
|
|
5
|
-
 
|
|
6
7
|
|
|
7
8
|
## Features
|
|
8
9
|
|
|
9
10
|
- **Interactive TUI** — navigate packages with arrow keys, select with space
|
|
10
11
|
- **Version picker** — choose any specific version from the npm registry, not just latest
|
|
11
12
|
- **Changelog viewer** — see GitHub release notes before you update
|
|
12
|
-
- **npm, pnpm &
|
|
13
|
-
- **Global packages** — check and update global installs across all package managers
|
|
13
|
+
- **npm, pnpm, yarn & bun** — auto-detects your package manager
|
|
14
|
+
- **Global packages** — check and update global installs across all* package managers
|
|
14
15
|
- **Self-update** — notifies you when a new version of ripen is available
|
|
15
16
|
- **Major bump warnings** — highlights potentially breaking updates
|
|
16
17
|
|
|
@@ -22,6 +23,8 @@ npm install -g ripencli@latest
|
|
|
22
23
|
pnpm add -g ripencli@latest
|
|
23
24
|
# or
|
|
24
25
|
yarn global add ripencli@latest
|
|
26
|
+
# or
|
|
27
|
+
bun add -g ripencli@latest
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
## Usage
|
|
@@ -51,13 +54,13 @@ ripen --help
|
|
|
51
54
|
## How it works
|
|
52
55
|
|
|
53
56
|
1. Reads your `package.json` and checks each dependency against the npm registry directly
|
|
54
|
-
2. Detects your package manager from the lock file (`pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`) for running updates
|
|
57
|
+
2. Detects your package manager from the lock file (`bun.lock`, `pnpm-lock.yaml`, `package-lock.json`, or `yarn.lock`) for running updates
|
|
55
58
|
3. Shows outdated packages in a colorful interactive list
|
|
56
59
|
4. Press `v` on any package to pick a specific version from the npm registry
|
|
57
60
|
5. Press `c` to see GitHub release notes between your current and target version
|
|
58
|
-
6. Select the ones you want and press enter — ripen runs the update commands for you
|
|
61
|
+
6. Select the ones you want and press enter — ripen runs the update commands for you
|
|
59
62
|
|
|
60
|
-
When using `ripen -g`, all available package managers (npm, pnpm, yarn) are checked in parallel so you see every global package in one place.
|
|
63
|
+
When using `ripen -g`, all available package managers (npm, pnpm, yarn) are checked in parallel so you see every global package in one place. Bun is not included in global checking because it doesn't provide a JSON output for its outdated command.
|
|
61
64
|
|
|
62
65
|
## License
|
|
63
66
|
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,8 @@ import { exec } from "child_process";
|
|
|
11
11
|
import { ScrollView } from "ink-scroll-view";
|
|
12
12
|
//#region src/detector.ts
|
|
13
13
|
function detectPackageManager(cwd) {
|
|
14
|
+
if (existsSync(join(cwd, "bun.lock"))) return "bun";
|
|
15
|
+
if (existsSync(join(cwd, "bun.lockb"))) return "bun";
|
|
14
16
|
if (existsSync(join(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
15
17
|
if (existsSync(join(cwd, "pnpm-workspace.yaml"))) return "pnpm";
|
|
16
18
|
if (existsSync(join(cwd, "yarn.lock"))) return "yarn";
|
|
@@ -25,6 +27,7 @@ function detectGlobalInstallManager() {
|
|
|
25
27
|
const scriptPath = (process.argv[1] ?? "").replace(/\\/g, "/").toLowerCase();
|
|
26
28
|
if (scriptPath.includes("/pnpm/") || scriptPath.includes("/pnpm-global/")) return "pnpm";
|
|
27
29
|
if (scriptPath.includes("/yarn/")) return "yarn";
|
|
30
|
+
if (scriptPath.includes("/.bun/") || scriptPath.includes("/bun/")) return "bun";
|
|
28
31
|
return "npm";
|
|
29
32
|
}
|
|
30
33
|
function hasPackageJson(cwd) {
|
|
@@ -439,7 +442,7 @@ async function updatePackages(manager, packages, cwd, global = false, onLine) {
|
|
|
439
442
|
if (devDeps.length > 0) batches.push({
|
|
440
443
|
mgr: manager,
|
|
441
444
|
pkgs: devDeps,
|
|
442
|
-
flags: ["-D"]
|
|
445
|
+
flags: [manager === "bun" ? "-d" : "-D"]
|
|
443
446
|
});
|
|
444
447
|
if (globalPkgs.length > 0) {
|
|
445
448
|
const byManager = /* @__PURE__ */ new Map();
|
|
@@ -449,7 +452,7 @@ async function updatePackages(manager, packages, cwd, global = false, onLine) {
|
|
|
449
452
|
byManager.get(mgr).push(pkg);
|
|
450
453
|
}
|
|
451
454
|
for (const [mgr, pkgs] of byManager) {
|
|
452
|
-
const globalFlags = mgr === "yarn" ? [] : ["--global"];
|
|
455
|
+
const globalFlags = mgr === "yarn" ? [] : mgr === "bun" ? ["-g"] : ["--global"];
|
|
453
456
|
batches.push({
|
|
454
457
|
mgr,
|
|
455
458
|
pkgs,
|
|
@@ -675,11 +678,12 @@ function groupCheckbox(packages) {
|
|
|
675
678
|
};
|
|
676
679
|
}
|
|
677
680
|
function computeMaxPerGroup(terminalRows, groupCount) {
|
|
678
|
-
const available = terminalRows - CHROME_LINES - groupCount * (GROUP_CHROME +
|
|
681
|
+
const available = terminalRows - CHROME_LINES - groupCount * (GROUP_CHROME + 2) - 2;
|
|
679
682
|
const perGroup = Math.floor(available / groupCount);
|
|
680
683
|
return Math.max(3, perGroup);
|
|
681
684
|
}
|
|
682
|
-
function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelectVersion, onViewChangelog, onConfirm, onOpenSettings,
|
|
685
|
+
function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelectVersion, onViewChangelog, onConfirm, onOpenSettings, groupByScope, isActive = true }) {
|
|
686
|
+
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
683
687
|
const allRows = useMemo(() => buildDisplayRows(packages, groupByScope), [packages, groupByScope]);
|
|
684
688
|
const allScopeKeys = useMemo(() => {
|
|
685
689
|
const keys = /* @__PURE__ */ new Set();
|
|
@@ -699,35 +703,19 @@ function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelect
|
|
|
699
703
|
const { stdout } = useStdout();
|
|
700
704
|
const terminalRows = stdout?.rows ?? 24;
|
|
701
705
|
const maxVisible = useMemo(() => computeMaxPerGroup(terminalRows, groups.length), [terminalRows, groups.length]);
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
if (next === offset) return prev;
|
|
713
|
-
return {
|
|
714
|
-
...prev,
|
|
715
|
-
[group.type]: next
|
|
716
|
-
};
|
|
717
|
-
});
|
|
718
|
-
}
|
|
719
|
-
}, [
|
|
720
|
-
focusedIndex,
|
|
721
|
-
groups,
|
|
722
|
-
maxVisible
|
|
723
|
-
]);
|
|
706
|
+
const scrollOffsetsRef = useRef({});
|
|
707
|
+
for (const group of groups) {
|
|
708
|
+
const localIndex = group.items.findIndex((item) => item.visibleIndex === focusedIndex);
|
|
709
|
+
if (localIndex === -1) continue;
|
|
710
|
+
const prev = scrollOffsetsRef.current[group.type] ?? 0;
|
|
711
|
+
let next = prev;
|
|
712
|
+
if (localIndex < prev) next = localIndex;
|
|
713
|
+
else if (localIndex >= prev + maxVisible) next = localIndex - maxVisible + 1;
|
|
714
|
+
scrollOffsetsRef.current[group.type] = next;
|
|
715
|
+
}
|
|
724
716
|
useEffect(() => {
|
|
725
|
-
if (focusedIndex >= visibleRows.length)
|
|
726
|
-
}, [
|
|
727
|
-
visibleRows.length,
|
|
728
|
-
focusedIndex,
|
|
729
|
-
onFocusChange
|
|
730
|
-
]);
|
|
717
|
+
if (focusedIndex >= visibleRows.length) setFocusedIndex(Math.max(0, visibleRows.length - 1));
|
|
718
|
+
}, [visibleRows.length, focusedIndex]);
|
|
731
719
|
const toggleCollapse = (scopeKey) => {
|
|
732
720
|
setCollapsedScopes((prev) => {
|
|
733
721
|
const next = new Set(prev);
|
|
@@ -737,11 +725,13 @@ function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelect
|
|
|
737
725
|
});
|
|
738
726
|
};
|
|
739
727
|
useInput((input, key) => {
|
|
740
|
-
if (key.upArrow)
|
|
741
|
-
if (key.downArrow)
|
|
728
|
+
if (key.upArrow) setFocusedIndex(Math.max(0, focusedIndex - 1));
|
|
729
|
+
if (key.downArrow) setFocusedIndex(Math.min(visibleRows.length - 1, focusedIndex + 1));
|
|
730
|
+
if (key.pageUp) setFocusedIndex(Math.max(0, focusedIndex - maxVisible));
|
|
731
|
+
if (key.pageDown) setFocusedIndex(Math.min(visibleRows.length - 1, focusedIndex + maxVisible));
|
|
742
732
|
if (key.tab) {
|
|
743
733
|
const headerIndices = groups.map((g) => g.headerVisibleIndex);
|
|
744
|
-
|
|
734
|
+
setFocusedIndex(headerIndices[(headerIndices.findIndex((h, i) => {
|
|
745
735
|
const nextHeader = headerIndices[i + 1] ?? visibleRows.length;
|
|
746
736
|
return focusedIndex >= h && focusedIndex < nextHeader;
|
|
747
737
|
}) + 1) % headerIndices.length]);
|
|
@@ -763,9 +753,9 @@ function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelect
|
|
|
763
753
|
if (input === " ") if (focused.kind === "header") onToggleGroup(focused.groupType);
|
|
764
754
|
else if (focused.kind === "scope-header") onToggleMany(focused.packageIndices);
|
|
765
755
|
else onToggle(focused.packageIndex);
|
|
766
|
-
if (input === "v" && focused.kind === "package") onSelectVersion(focused.packageIndex);
|
|
767
|
-
if (input === "c" && focused.kind === "package") onViewChangelog(focused.packageIndex);
|
|
768
|
-
if (input === "s") onOpenSettings?.();
|
|
756
|
+
if (input === "v" && !key.ctrl && focused.kind === "package") onSelectVersion(focused.packageIndex);
|
|
757
|
+
if (input === "c" && !key.ctrl && focused.kind === "package") onViewChangelog(focused.packageIndex);
|
|
758
|
+
if (input === "s" && !key.ctrl) onOpenSettings?.();
|
|
769
759
|
if (key.return) onConfirm();
|
|
770
760
|
}, { isActive });
|
|
771
761
|
const selectedCount = packages.filter((p) => p.selected).length;
|
|
@@ -793,7 +783,7 @@ function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelect
|
|
|
793
783
|
children: [
|
|
794
784
|
/* @__PURE__ */ jsx(Text, {
|
|
795
785
|
color: "white",
|
|
796
|
-
children: "
|
|
786
|
+
children: "↑↓/PgDn/PgUp"
|
|
797
787
|
}),
|
|
798
788
|
" navigate",
|
|
799
789
|
" ",
|
|
@@ -846,7 +836,7 @@ function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelect
|
|
|
846
836
|
const check = groupCheckbox(group.allPackages);
|
|
847
837
|
const headerFocused = focusedIndex === group.headerVisibleIndex;
|
|
848
838
|
const typeColor = TYPE_COLORS[group.type] ?? "white";
|
|
849
|
-
const offset =
|
|
839
|
+
const offset = scrollOffsetsRef.current[group.type] ?? 0;
|
|
850
840
|
const visibleItems = group.items.slice(offset, offset + maxVisible);
|
|
851
841
|
const totalItems = group.items.length;
|
|
852
842
|
const needsScroll = totalItems > maxVisible;
|
|
@@ -1800,13 +1790,22 @@ function SelfUpdatePrompt({ currentVersion, latestVersion, updating, error, onUp
|
|
|
1800
1790
|
//#region src/ui/App.tsx
|
|
1801
1791
|
function App({ project, global, version, installManager }) {
|
|
1802
1792
|
const { exit } = useApp();
|
|
1793
|
+
useInput((_input, key) => {
|
|
1794
|
+
if (key.ctrl && _input === "c") {
|
|
1795
|
+
setScreen("empty");
|
|
1796
|
+
setTimeout(() => {
|
|
1797
|
+
exit();
|
|
1798
|
+
console.log(" \x1B[32mCancelled.\x1B[0m\n");
|
|
1799
|
+
process.exit(0);
|
|
1800
|
+
}, 200);
|
|
1801
|
+
}
|
|
1802
|
+
});
|
|
1803
1803
|
const [screen, setScreen] = useState("self-update-check");
|
|
1804
1804
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
1805
1805
|
const [selfUpdateError, setSelfUpdateError] = useState(null);
|
|
1806
1806
|
const [selfUpdating, setSelfUpdating] = useState(false);
|
|
1807
1807
|
const [config, setConfig] = useState(() => loadConfig());
|
|
1808
1808
|
const [packages, setPackages] = useState([]);
|
|
1809
|
-
const [focusedIndex, setFocusedIndex] = useState(0);
|
|
1810
1809
|
const [activeIndex, setActiveIndex] = useState(0);
|
|
1811
1810
|
const [results, setResults] = useState([]);
|
|
1812
1811
|
const [errorMsg, setErrorMsg] = useState("");
|
|
@@ -1832,7 +1831,15 @@ function App({ project, global, version, installManager }) {
|
|
|
1832
1831
|
const timer = setTimeout(() => {
|
|
1833
1832
|
exit();
|
|
1834
1833
|
process.exit(0);
|
|
1835
|
-
},
|
|
1834
|
+
}, 300);
|
|
1835
|
+
return () => clearTimeout(timer);
|
|
1836
|
+
}, [screen]);
|
|
1837
|
+
useEffect(() => {
|
|
1838
|
+
if (screen !== "empty") return;
|
|
1839
|
+
const timer = setTimeout(() => {
|
|
1840
|
+
exit();
|
|
1841
|
+
process.exit(0);
|
|
1842
|
+
}, 300);
|
|
1836
1843
|
return () => clearTimeout(timer);
|
|
1837
1844
|
}, [screen]);
|
|
1838
1845
|
const [fetchStarted, setFetchStarted] = useState(false);
|
|
@@ -2148,8 +2155,6 @@ function App({ project, global, version, installManager }) {
|
|
|
2148
2155
|
onViewChangelog: handleViewChangelog,
|
|
2149
2156
|
onConfirm: handleConfirm,
|
|
2150
2157
|
onOpenSettings: () => setScreen("settings"),
|
|
2151
|
-
focusedIndex,
|
|
2152
|
-
onFocusChange: setFocusedIndex,
|
|
2153
2158
|
groupByScope: config.groupByScope,
|
|
2154
2159
|
isActive: isListActive
|
|
2155
2160
|
})
|
|
@@ -2197,7 +2202,7 @@ const { waitUntilExit } = render(/* @__PURE__ */ jsx(App, {
|
|
|
2197
2202
|
global: isGlobal,
|
|
2198
2203
|
version: VERSION,
|
|
2199
2204
|
installManager: detectGlobalInstallManager()
|
|
2200
|
-
}));
|
|
2205
|
+
}), { exitOnCtrlC: false });
|
|
2201
2206
|
await waitUntilExit();
|
|
2202
2207
|
//#endregion
|
|
2203
2208
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ripencli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Interactive dependency updater for npm, pnpm, and yarn",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -63,6 +63,6 @@
|
|
|
63
63
|
"@types/react": "^19.2.14",
|
|
64
64
|
"prettier": "^3.8.1",
|
|
65
65
|
"tsdown": "^0.21.4",
|
|
66
|
-
"typescript": "^5.
|
|
66
|
+
"typescript": "^5.9.3"
|
|
67
67
|
}
|
|
68
68
|
}
|