ripencli 0.2.5 → 0.2.7

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 +14 -0
  2. package/dist/cli.js +243 -87
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -14,6 +14,8 @@
14
14
  - **Global packages** — check and update global installs across all* package managers
15
15
  - **Self-update** — notifies you when a new version of ripen is available
16
16
  - **Major bump warnings** — highlights potentially breaking updates
17
+ - **Scope grouping** — optionally group scoped packages (e.g. `@heroui/*`) together
18
+ - **Frequency sorting** — packages you update often can be surfaced to the top
17
19
 
18
20
  ## Install
19
21
 
@@ -49,6 +51,7 @@ ripen --help
49
51
  | `v` | Pick specific version |
50
52
  | `c` | View changelog / release notes |
51
53
  | `enter` | Update selected packages |
54
+ | `s` | Open settings |
52
55
  | `esc` | Cancel / go back |
53
56
 
54
57
  ## How it works
@@ -60,6 +63,17 @@ ripen --help
60
63
  5. Press `c` to see GitHub release notes between your current and target version
61
64
  6. Select the ones you want and press enter — ripen runs the update commands for you
62
65
 
66
+ ## Settings
67
+
68
+ Press `s` to open the settings screen. Settings are persisted at `~/.config/ripen/config.json`.
69
+
70
+ | Setting | Default | Description |
71
+ | ---------------------------- | ------- | ------------------------------------------------------------ |
72
+ | Sort by update frequency | Off | Packages you update often appear at the top |
73
+ | Enable scope grouping | Off | Group scoped packages under their scope prefix |
74
+ | Show grouped scopes on top | Off | Grouped scopes appear before ungrouped packages |
75
+ | Grouped scopes | — | List of scopes to group (e.g. `@heroui`, `@radix-ui`) |
76
+
63
77
  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.
64
78
 
65
79
  ## License
package/dist/cli.js CHANGED
@@ -522,16 +522,12 @@ async function updatePackages(manager, packages, cwd, global = false, onLine) {
522
522
  }
523
523
  //#endregion
524
524
  //#region src/config.ts
525
- const DEFAULT_UNGROUP_SCOPES = ["@types", "@react-types"];
526
525
  const DEFAULT_CONFIG = {
527
- groupByScope: true,
528
- addedScopes: [],
529
- removedDefaults: []
526
+ groupByScope: false,
527
+ groupScopes: [],
528
+ groupsOnTop: false,
529
+ frequencySort: false
530
530
  };
531
- /** Compute the effective ungroup scopes list: (defaults - removedDefaults) + addedScopes */
532
- function getEffectiveUngroupScopes(config) {
533
- return [...DEFAULT_UNGROUP_SCOPES.filter((s) => !config.removedDefaults.includes(s)), ...config.addedScopes];
534
- }
535
531
  const CONFIG_DIR = join(homedir(), ".config", "ripen");
536
532
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
537
533
  function loadConfig() {
@@ -552,6 +548,23 @@ function saveConfig(config) {
552
548
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + "\n", "utf-8");
553
549
  } catch {}
554
550
  }
551
+ const FREQUENCY_PATH = join(CONFIG_DIR, "frequency.json");
552
+ function loadFrequency() {
553
+ try {
554
+ const raw = readFileSync(FREQUENCY_PATH, "utf-8");
555
+ return JSON.parse(raw);
556
+ } catch {
557
+ return {};
558
+ }
559
+ }
560
+ function incrementFrequency(packageNames) {
561
+ try {
562
+ const freq = loadFrequency();
563
+ for (const name of packageNames) freq[name] = (freq[name] ?? 0) + 1;
564
+ mkdirSync(CONFIG_DIR, { recursive: true });
565
+ writeFileSync(FREQUENCY_PATH, JSON.stringify(freq, null, 2) + "\n", "utf-8");
566
+ } catch {}
567
+ }
555
568
  //#endregion
556
569
  //#region src/ui/PackageList.tsx
557
570
  const TYPE_COLORS = {
@@ -574,7 +587,7 @@ const GROUP_CHROME = 5;
574
587
  function getScope(name) {
575
588
  return name.match(/^(@[^/]+)\//)?.[1] ?? null;
576
589
  }
577
- function buildDisplayRows(packages, groupByScope, ungroupScopes = []) {
590
+ function buildDisplayRows(packages, groupByScope = false, groupScopes = [], groupsOnTop = false, frequencySort = false, frequency = {}) {
578
591
  const grouped = /* @__PURE__ */ new Map();
579
592
  packages.forEach((pkg, i) => {
580
593
  if (!grouped.has(pkg.type)) grouped.set(pkg.type, []);
@@ -583,6 +596,12 @@ function buildDisplayRows(packages, groupByScope, ungroupScopes = []) {
583
596
  index: i
584
597
  });
585
598
  });
599
+ const freqSort = (a, b) => {
600
+ const fa = frequency[a.pkg.name] ?? 0;
601
+ const fb = frequency[b.pkg.name] ?? 0;
602
+ if (fb !== fa) return fb - fa;
603
+ return a.pkg.name.localeCompare(b.pkg.name);
604
+ };
586
605
  const rows = [];
587
606
  for (const type of GROUP_ORDER) {
588
607
  const items = grouped.get(type);
@@ -594,54 +613,105 @@ function buildDisplayRows(packages, groupByScope, ungroupScopes = []) {
594
613
  label: GROUP_LABELS[type] ?? type,
595
614
  packages: allPkgs
596
615
  });
597
- if (groupByScope) {
616
+ if (groupByScope && groupScopes.length > 0) {
598
617
  const scopeMap = /* @__PURE__ */ new Map();
599
- const unscoped = [];
618
+ const ungrouped = [];
600
619
  for (const item of items) {
601
620
  const scope = getScope(item.pkg.name);
602
- if (scope && !ungroupScopes.includes(scope)) {
621
+ if (scope && groupScopes.includes(scope)) {
603
622
  if (!scopeMap.has(scope)) scopeMap.set(scope, []);
604
623
  scopeMap.get(scope).push(item);
605
- } else unscoped.push(item);
624
+ } else ungrouped.push(item);
606
625
  }
607
- const emittedScopes = /* @__PURE__ */ new Set();
608
- for (const item of items) {
609
- const scope = getScope(item.pkg.name);
610
- if (scope && scopeMap.has(scope) && scopeMap.get(scope).length >= 2) {
611
- if (!emittedScopes.has(scope)) {
612
- emittedScopes.add(scope);
613
- const scopeItems = scopeMap.get(scope);
614
- const scopeKey = `${type}::${scope}`;
615
- rows.push({
616
- kind: "scope-header",
617
- groupType: type,
618
- scope,
619
- packageIndices: scopeItems.map((si) => si.index),
620
- packages: scopeItems.map((si) => si.pkg)
621
- });
622
- for (const si of scopeItems) rows.push({
623
- kind: "package",
624
- pkg: si.pkg,
625
- packageIndex: si.index,
626
- indented: true,
627
- scopeKey
628
- });
629
- }
630
- } else rows.push({
626
+ const scopeGroups = [];
627
+ for (const [scope, scopeItems] of scopeMap) if (scopeItems.length >= 2) scopeGroups.push({
628
+ scope,
629
+ items: scopeItems
630
+ });
631
+ else ungrouped.push(...scopeItems);
632
+ if (frequencySort) scopeGroups.sort((a, b) => {
633
+ const maxA = Math.max(...a.items.map((i) => frequency[i.pkg.name] ?? 0));
634
+ const maxB = Math.max(...b.items.map((i) => frequency[i.pkg.name] ?? 0));
635
+ if (maxB !== maxA) return maxB - maxA;
636
+ return a.scope.localeCompare(b.scope);
637
+ });
638
+ else scopeGroups.sort((a, b) => a.scope.localeCompare(b.scope));
639
+ const emitScopeGroups = () => {
640
+ for (const { scope, items: scopeItems } of scopeGroups) {
641
+ if (frequencySort) scopeItems.sort(freqSort);
642
+ const scopeKey = `${type}::${scope}`;
643
+ rows.push({
644
+ kind: "scope-header",
645
+ groupType: type,
646
+ scope,
647
+ packageIndices: scopeItems.map((si) => si.index),
648
+ packages: scopeItems.map((si) => si.pkg)
649
+ });
650
+ for (const si of scopeItems) rows.push({
651
+ kind: "package",
652
+ pkg: si.pkg,
653
+ packageIndex: si.index,
654
+ indented: true,
655
+ scopeKey
656
+ });
657
+ }
658
+ };
659
+ const emitUngrouped = () => {
660
+ if (frequencySort) ungrouped.sort(freqSort);
661
+ for (const item of ungrouped) rows.push({
631
662
  kind: "package",
632
663
  pkg: item.pkg,
633
664
  packageIndex: item.index,
634
665
  indented: false,
635
666
  scopeKey: null
636
667
  });
668
+ };
669
+ if (groupsOnTop || frequencySort) {
670
+ emitScopeGroups();
671
+ emitUngrouped();
672
+ } else {
673
+ const emittedScopes = /* @__PURE__ */ new Set();
674
+ for (const item of items) {
675
+ const scope = getScope(item.pkg.name);
676
+ const group = scope ? scopeGroups.find((g) => g.scope === scope) : void 0;
677
+ if (group) {
678
+ if (!emittedScopes.has(scope)) {
679
+ emittedScopes.add(scope);
680
+ const scopeKey = `${type}::${scope}`;
681
+ rows.push({
682
+ kind: "scope-header",
683
+ groupType: type,
684
+ scope,
685
+ packageIndices: group.items.map((si) => si.index),
686
+ packages: group.items.map((si) => si.pkg)
687
+ });
688
+ for (const si of group.items) rows.push({
689
+ kind: "package",
690
+ pkg: si.pkg,
691
+ packageIndex: si.index,
692
+ indented: true,
693
+ scopeKey
694
+ });
695
+ }
696
+ } else rows.push({
697
+ kind: "package",
698
+ pkg: item.pkg,
699
+ packageIndex: item.index,
700
+ indented: false,
701
+ scopeKey: null
702
+ });
703
+ }
637
704
  }
638
- } else for (const item of items) rows.push({
639
- kind: "package",
640
- pkg: item.pkg,
641
- packageIndex: item.index,
642
- indented: false,
643
- scopeKey: null
644
- });
705
+ } else {
706
+ const sorted = frequencySort ? [...items].sort(freqSort) : items;
707
+ for (const item of sorted) rows.push({
708
+ kind: "package",
709
+ pkg: item.pkg,
710
+ packageIndex: item.index,
711
+ indented: false,
712
+ scopeKey: null
713
+ });
714
+ }
645
715
  }
646
716
  return rows;
647
717
  }
@@ -691,12 +761,15 @@ function computeMaxPerGroup(terminalRows, groupCount) {
691
761
  const perGroup = Math.floor(available / groupCount);
692
762
  return Math.max(3, perGroup);
693
763
  }
694
- function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelectVersion, onViewChangelog, onConfirm, onOpenSettings, groupByScope, ungroupScopes, isActive = true }) {
764
+ function PackageList({ packages, onToggle, onToggleGroup, onToggleMany, onSelectVersion, onViewChangelog, onConfirm, onOpenSettings, groupByScope = false, groupScopes, groupsOnTop = false, frequencySort = false, frequency = {}, isActive = true }) {
695
765
  const [focusedIndex, setFocusedIndex] = useState(0);
696
- const allRows = useMemo(() => buildDisplayRows(packages, groupByScope, ungroupScopes), [
766
+ const allRows = useMemo(() => buildDisplayRows(packages, groupByScope, groupScopes, groupsOnTop, frequencySort, frequency), [
697
767
  packages,
698
768
  groupByScope,
699
- ungroupScopes
769
+ groupScopes,
770
+ groupsOnTop,
771
+ frequencySort,
772
+ frequency
700
773
  ]);
701
774
  const allScopeKeys = useMemo(() => {
702
775
  const keys = /* @__PURE__ */ new Set();
@@ -1625,11 +1698,13 @@ function UpdateResults({ results, onDone }) {
1625
1698
  function Settings({ config, onConfigChange, onClose }) {
1626
1699
  const [inputMode, setInputMode] = useState(false);
1627
1700
  const [inputValue, setInputValue] = useState("");
1628
- const effectiveScopes = getEffectiveUngroupScopes(config);
1701
+ const scopes = config.groupScopes;
1629
1702
  const rows = [
1630
- { type: "toggle" },
1703
+ { type: "toggle-frequency" },
1704
+ { type: "toggle-group" },
1705
+ { type: "toggle-groups-top" },
1631
1706
  { type: "list-header" },
1632
- ...effectiveScopes.map((_, i) => ({
1707
+ ...scopes.map((_, i) => ({
1633
1708
  type: "list-item",
1634
1709
  listItemIndex: i
1635
1710
  }))
@@ -1638,24 +1713,16 @@ function Settings({ config, onConfigChange, onClose }) {
1638
1713
  const currentRow = rows[flatCursor];
1639
1714
  const addScope = (value) => {
1640
1715
  const scope = value.startsWith("@") ? value : `@${value}`;
1641
- if (effectiveScopes.includes(scope)) return;
1642
- if (DEFAULT_UNGROUP_SCOPES.includes(scope)) onConfigChange({
1643
- ...config,
1644
- removedDefaults: config.removedDefaults.filter((s) => s !== scope)
1645
- });
1646
- else onConfigChange({
1716
+ if (scopes.includes(scope)) return;
1717
+ onConfigChange({
1647
1718
  ...config,
1648
- addedScopes: [...config.addedScopes, scope]
1719
+ groupScopes: [...scopes, scope]
1649
1720
  });
1650
1721
  };
1651
1722
  const removeScope = (scope) => {
1652
- if (DEFAULT_UNGROUP_SCOPES.includes(scope)) onConfigChange({
1723
+ onConfigChange({
1653
1724
  ...config,
1654
- removedDefaults: [...config.removedDefaults, scope]
1655
- });
1656
- else onConfigChange({
1657
- ...config,
1658
- addedScopes: config.addedScopes.filter((s) => s !== scope)
1725
+ groupScopes: scopes.filter((s) => s !== scope)
1659
1726
  });
1660
1727
  };
1661
1728
  useInput((input, key) => {
@@ -1682,10 +1749,18 @@ function Settings({ config, onConfigChange, onClose }) {
1682
1749
  if (key.upArrow) setFlatCursor((c) => Math.max(0, c - 1));
1683
1750
  if (key.downArrow) setFlatCursor((c) => Math.min(rows.length - 1, c + 1));
1684
1751
  if (input === " " || key.return) {
1685
- if (currentRow?.type === "toggle") onConfigChange({
1752
+ if (currentRow?.type === "toggle-frequency") onConfigChange({
1753
+ ...config,
1754
+ frequencySort: !config.frequencySort
1755
+ });
1756
+ else if (currentRow?.type === "toggle-group") onConfigChange({
1686
1757
  ...config,
1687
1758
  groupByScope: !config.groupByScope
1688
1759
  });
1760
+ else if (currentRow?.type === "toggle-groups-top") onConfigChange({
1761
+ ...config,
1762
+ groupsOnTop: !config.groupsOnTop
1763
+ });
1689
1764
  else if (currentRow?.type === "list-header") {
1690
1765
  setInputMode(true);
1691
1766
  setInputValue("");
@@ -1696,14 +1771,16 @@ function Settings({ config, onConfigChange, onClose }) {
1696
1771
  setInputValue("");
1697
1772
  }
1698
1773
  if ((key.backspace || key.delete) && currentRow?.type === "list-item" && currentRow.listItemIndex !== void 0) {
1699
- const scope = effectiveScopes[currentRow.listItemIndex];
1774
+ const scope = scopes[currentRow.listItemIndex];
1700
1775
  removeScope(scope);
1701
1776
  if (flatCursor >= rows.length - 1) setFlatCursor(Math.max(0, flatCursor - 1));
1702
1777
  }
1703
1778
  if (key.escape || input === "s") onClose();
1704
1779
  });
1705
- const toggleFocused = flatCursor === 0;
1706
- const listHeaderFocused = flatCursor === 1;
1780
+ const freqToggleFocused = currentRow?.type === "toggle-frequency";
1781
+ const groupToggleFocused = currentRow?.type === "toggle-group";
1782
+ const groupsTopFocused = currentRow?.type === "toggle-groups-top";
1783
+ const listHeaderFocused = currentRow?.type === "list-header";
1707
1784
  return /* @__PURE__ */ jsxs(Box, {
1708
1785
  flexDirection: "column",
1709
1786
  children: [
@@ -1731,7 +1808,39 @@ function Settings({ config, onConfigChange, onClose }) {
1731
1808
  children: [
1732
1809
  /* @__PURE__ */ jsx(Text, {
1733
1810
  color: "greenBright",
1734
- children: toggleFocused ? ">" : " "
1811
+ children: freqToggleFocused ? ">" : " "
1812
+ }),
1813
+ /* @__PURE__ */ jsxs(Text, {
1814
+ color: config.frequencySort ? "greenBright" : "gray",
1815
+ children: [
1816
+ "[",
1817
+ config.frequencySort ? "x" : " ",
1818
+ "]"
1819
+ ]
1820
+ }),
1821
+ /* @__PURE__ */ jsx(Text, {
1822
+ bold: freqToggleFocused,
1823
+ color: freqToggleFocused ? "whiteBright" : "white",
1824
+ children: "Sort by update frequency"
1825
+ })
1826
+ ]
1827
+ }), /* @__PURE__ */ jsx(Box, {
1828
+ marginLeft: 6,
1829
+ children: /* @__PURE__ */ jsx(Text, {
1830
+ color: "gray",
1831
+ children: "Packages you update often appear at the top"
1832
+ })
1833
+ })]
1834
+ }),
1835
+ /* @__PURE__ */ jsxs(Box, {
1836
+ flexDirection: "column",
1837
+ marginBottom: 1,
1838
+ children: [/* @__PURE__ */ jsxs(Box, {
1839
+ gap: 1,
1840
+ children: [
1841
+ /* @__PURE__ */ jsx(Text, {
1842
+ color: "greenBright",
1843
+ children: groupToggleFocused ? ">" : " "
1735
1844
  }),
1736
1845
  /* @__PURE__ */ jsxs(Text, {
1737
1846
  color: config.groupByScope ? "greenBright" : "gray",
@@ -1742,16 +1851,52 @@ function Settings({ config, onConfigChange, onClose }) {
1742
1851
  ]
1743
1852
  }),
1744
1853
  /* @__PURE__ */ jsx(Text, {
1745
- bold: toggleFocused,
1746
- color: toggleFocused ? "whiteBright" : "white",
1747
- children: "Group packages by scope"
1854
+ bold: groupToggleFocused,
1855
+ color: groupToggleFocused ? "whiteBright" : "white",
1856
+ children: "Enable scope grouping"
1748
1857
  })
1749
1858
  ]
1750
1859
  }), /* @__PURE__ */ jsx(Box, {
1751
1860
  marginLeft: 6,
1752
1861
  children: /* @__PURE__ */ jsx(Text, {
1753
1862
  color: "gray",
1754
- children: "Sub-group scoped packages (@org/pkg) under their scope prefix"
1863
+ children: "Group scoped packages listed below under their scope prefix"
1864
+ })
1865
+ })]
1866
+ }),
1867
+ /* @__PURE__ */ jsxs(Box, {
1868
+ flexDirection: "column",
1869
+ marginBottom: 1,
1870
+ children: [/* @__PURE__ */ jsxs(Box, {
1871
+ gap: 1,
1872
+ children: [
1873
+ /* @__PURE__ */ jsx(Text, {
1874
+ dimColor: !config.groupByScope,
1875
+ color: "greenBright",
1876
+ children: groupsTopFocused ? ">" : " "
1877
+ }),
1878
+ /* @__PURE__ */ jsxs(Text, {
1879
+ dimColor: !config.groupByScope,
1880
+ color: config.groupsOnTop && config.groupByScope ? "greenBright" : "gray",
1881
+ children: [
1882
+ "[",
1883
+ config.groupsOnTop ? "x" : " ",
1884
+ "]"
1885
+ ]
1886
+ }),
1887
+ /* @__PURE__ */ jsx(Text, {
1888
+ dimColor: !config.groupByScope,
1889
+ bold: groupsTopFocused,
1890
+ color: !config.groupByScope ? "gray" : groupsTopFocused ? "whiteBright" : "white",
1891
+ children: "Show grouped scopes on top"
1892
+ })
1893
+ ]
1894
+ }), /* @__PURE__ */ jsx(Box, {
1895
+ marginLeft: 6,
1896
+ children: /* @__PURE__ */ jsx(Text, {
1897
+ dimColor: !config.groupByScope,
1898
+ color: "gray",
1899
+ children: "Grouped scope packages appear before ungrouped ones"
1755
1900
  })
1756
1901
  })]
1757
1902
  }),
@@ -1765,43 +1910,44 @@ function Settings({ config, onConfigChange, onClose }) {
1765
1910
  children: [/* @__PURE__ */ jsxs(Box, {
1766
1911
  gap: 1,
1767
1912
  children: [/* @__PURE__ */ jsx(Text, {
1913
+ dimColor: !config.groupByScope,
1768
1914
  color: "greenBright",
1769
1915
  children: listHeaderFocused ? ">" : " "
1770
1916
  }), /* @__PURE__ */ jsx(Text, {
1917
+ dimColor: !config.groupByScope,
1771
1918
  bold: listHeaderFocused,
1772
- color: listHeaderFocused ? "whiteBright" : "white",
1773
- children: "Excluded scopes from grouping"
1919
+ color: !config.groupByScope ? "gray" : listHeaderFocused ? "whiteBright" : "white",
1920
+ children: "Grouped scopes"
1774
1921
  })]
1775
1922
  }), /* @__PURE__ */ jsx(Box, {
1776
1923
  marginLeft: 4,
1777
1924
  children: /* @__PURE__ */ jsx(Text, {
1925
+ dimColor: !config.groupByScope,
1778
1926
  color: "gray",
1779
- children: "Scopes listed here won't be sub-grouped (type to add, delete to remove)"
1927
+ children: "Scoped packages (@scope/*) listed here will be sub-grouped together"
1780
1928
  })
1781
1929
  })]
1782
1930
  }),
1783
- effectiveScopes.map((scope, i) => {
1784
- const itemFocused = flatCursor === 2 + i;
1785
- const isDefault = DEFAULT_UNGROUP_SCOPES.includes(scope);
1931
+ scopes.map((scope, i) => {
1932
+ const itemFocused = flatCursor === 4 + i;
1786
1933
  return /* @__PURE__ */ jsxs(Box, {
1787
1934
  marginLeft: 4,
1788
1935
  gap: 1,
1789
1936
  children: [
1790
1937
  /* @__PURE__ */ jsx(Text, {
1938
+ dimColor: !config.groupByScope,
1791
1939
  color: "greenBright",
1792
1940
  children: itemFocused ? ">" : " "
1793
1941
  }),
1794
1942
  /* @__PURE__ */ jsx(Text, {
1795
- color: itemFocused ? "whiteBright" : "white",
1943
+ dimColor: !config.groupByScope,
1944
+ color: !config.groupByScope ? "gray" : itemFocused ? "whiteBright" : "white",
1796
1945
  children: scope
1797
1946
  }),
1798
- isDefault && /* @__PURE__ */ jsx(Text, {
1947
+ itemFocused && /* @__PURE__ */ jsxs(Text, {
1948
+ dimColor: !config.groupByScope,
1799
1949
  color: "gray",
1800
- children: "(default)"
1801
- }),
1802
- itemFocused && /* @__PURE__ */ jsx(Text, {
1803
- color: "gray",
1804
- children: " delete to remove"
1950
+ children: [" ", "delete to remove"]
1805
1951
  })
1806
1952
  ]
1807
1953
  }, scope);
@@ -1962,6 +2108,7 @@ function App({ project, global, version, installManager }) {
1962
2108
  const [selfUpdateError, setSelfUpdateError] = useState(null);
1963
2109
  const [selfUpdating, setSelfUpdating] = useState(false);
1964
2110
  const [config, setConfig] = useState(() => loadConfig());
2111
+ const [frequency, setFrequency] = useState(() => loadFrequency());
1965
2112
  const [packages, setPackages] = useState([]);
1966
2113
  const [activeIndex, setActiveIndex] = useState(0);
1967
2114
  const [results, setResults] = useState([]);
@@ -2094,8 +2241,14 @@ function App({ project, global, version, installManager }) {
2094
2241
  const onLine = (line) => {
2095
2242
  setOutputLines((prev) => [...prev.slice(-(MAX_TERMINAL_LINES - 1)), line]);
2096
2243
  };
2097
- setResults(await updatePackages(project.manager, selected, project.cwd, global, onLine));
2244
+ const res = await updatePackages(project.manager, selected, project.cwd, global, onLine);
2245
+ setResults(res);
2098
2246
  setScreen("results");
2247
+ const successNames = res.filter((r) => r.success).map((r) => r.name);
2248
+ if (successNames.length > 0) {
2249
+ incrementFrequency(successNames);
2250
+ setFrequency(loadFrequency());
2251
+ }
2099
2252
  };
2100
2253
  if (screen === "self-update-check") return /* @__PURE__ */ jsxs(Box, {
2101
2254
  flexDirection: "column",
@@ -2313,7 +2466,10 @@ function App({ project, global, version, installManager }) {
2313
2466
  onConfirm: handleConfirm,
2314
2467
  onOpenSettings: () => setScreen("settings"),
2315
2468
  groupByScope: config.groupByScope,
2316
- ungroupScopes: getEffectiveUngroupScopes(config),
2469
+ groupScopes: config.groupScopes,
2470
+ groupsOnTop: config.groupsOnTop,
2471
+ frequencySort: config.frequencySort,
2472
+ frequency,
2317
2473
  isActive: isListActive
2318
2474
  })
2319
2475
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ripencli",
3
- "version": "0.2.5",
3
+ "version": "0.2.7",
4
4
  "description": "Interactive dependency updater for npm, pnpm, and yarn",
5
5
  "license": "MIT",
6
6
  "author": {