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.
Files changed (3) hide show
  1. package/README.md +10 -7
  2. package/dist/cli.js +50 -45
  3. 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 yarn
3
+ > Interactive dependency updater for npm, pnpm, yarn, and bun
4
+
5
+ ![npm version](https://img.shields.io/npm/v/ripencli) ![node](https://img.shields.io/node/v/ripencli) ![GitHub License](https://img.shields.io/github/license/yusifaliyevpro/ripen)
4
6
 
5
- ![npm version](https://img.shields.io/npm/v/ripencli) ![node](https://img.shields.io/node/v/ripencli)
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 & yarn** — auto-detects your package manager
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, preserving your version range prefixes (`^`, `~`, etc.)
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 + 1);
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, focusedIndex, onFocusChange, groupByScope, isActive = true }) {
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 [scrollOffsets, setScrollOffsets] = useState({});
703
- useEffect(() => {
704
- for (const group of groups) {
705
- const localIndex = group.items.findIndex((item) => item.visibleIndex === focusedIndex);
706
- if (localIndex === -1) continue;
707
- setScrollOffsets((prev) => {
708
- const offset = prev[group.type] ?? 0;
709
- let next = offset;
710
- if (localIndex < offset) next = localIndex;
711
- else if (localIndex >= offset + maxVisible) next = localIndex - maxVisible + 1;
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) onFocusChange(Math.max(0, visibleRows.length - 1));
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) onFocusChange(Math.max(0, focusedIndex - 1));
741
- if (key.downArrow) onFocusChange(Math.min(visibleRows.length - 1, focusedIndex + 1));
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
- onFocusChange(headerIndices[(headerIndices.findIndex((h, i) => {
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 = scrollOffsets[group.type] ?? 0;
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
- }, 1500);
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.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.5.0"
66
+ "typescript": "^5.9.3"
67
67
  }
68
68
  }