@rainy-updates/cli 0.5.7 → 0.6.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 (105) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +90 -31
  3. package/dist/bin/cli.js +11 -126
  4. package/dist/bin/dispatch.js +35 -32
  5. package/dist/bin/help.js +79 -2
  6. package/dist/bin/main.d.ts +1 -0
  7. package/dist/bin/main.js +126 -0
  8. package/dist/cache/cache.js +13 -11
  9. package/dist/commands/audit/parser.js +38 -2
  10. package/dist/commands/audit/runner.js +41 -61
  11. package/dist/commands/audit/targets.js +13 -13
  12. package/dist/commands/bisect/oracle.js +31 -11
  13. package/dist/commands/bisect/parser.js +3 -3
  14. package/dist/commands/bisect/runner.js +16 -8
  15. package/dist/commands/changelog/fetcher.js +11 -5
  16. package/dist/commands/dashboard/parser.js +144 -1
  17. package/dist/commands/dashboard/runner.d.ts +2 -2
  18. package/dist/commands/dashboard/runner.js +67 -37
  19. package/dist/commands/doctor/parser.js +53 -4
  20. package/dist/commands/doctor/runner.js +2 -2
  21. package/dist/commands/ga/parser.js +43 -4
  22. package/dist/commands/ga/runner.js +22 -13
  23. package/dist/commands/health/parser.js +38 -2
  24. package/dist/commands/health/runner.js +5 -1
  25. package/dist/commands/hook/parser.d.ts +2 -0
  26. package/dist/commands/hook/parser.js +40 -0
  27. package/dist/commands/hook/runner.d.ts +2 -0
  28. package/dist/commands/hook/runner.js +174 -0
  29. package/dist/commands/licenses/parser.js +39 -0
  30. package/dist/commands/licenses/runner.js +9 -5
  31. package/dist/commands/resolve/graph/builder.js +5 -1
  32. package/dist/commands/resolve/parser.js +39 -0
  33. package/dist/commands/resolve/runner.js +14 -4
  34. package/dist/commands/review/parser.js +101 -4
  35. package/dist/commands/review/runner.js +31 -5
  36. package/dist/commands/snapshot/parser.js +39 -0
  37. package/dist/commands/snapshot/runner.js +21 -18
  38. package/dist/commands/snapshot/store.d.ts +0 -12
  39. package/dist/commands/snapshot/store.js +26 -38
  40. package/dist/commands/unused/parser.js +39 -0
  41. package/dist/commands/unused/runner.js +10 -8
  42. package/dist/commands/unused/scanner.d.ts +2 -1
  43. package/dist/commands/unused/scanner.js +65 -52
  44. package/dist/config/loader.d.ts +2 -2
  45. package/dist/config/loader.js +2 -5
  46. package/dist/config/policy.js +20 -11
  47. package/dist/core/analysis/run-silenced.js +0 -1
  48. package/dist/core/artifacts.js +6 -5
  49. package/dist/core/baseline.js +3 -5
  50. package/dist/core/check.js +7 -3
  51. package/dist/core/ci.js +52 -1
  52. package/dist/core/decision-plan.d.ts +14 -0
  53. package/dist/core/decision-plan.js +107 -0
  54. package/dist/core/doctor/result.js +8 -5
  55. package/dist/core/fix-pr-batch.js +38 -28
  56. package/dist/core/fix-pr.js +27 -24
  57. package/dist/core/init-ci.js +34 -28
  58. package/dist/core/options.d.ts +4 -1
  59. package/dist/core/options.js +152 -4
  60. package/dist/core/review-model.js +3 -0
  61. package/dist/core/summary.js +6 -0
  62. package/dist/core/upgrade.js +64 -2
  63. package/dist/core/verification.d.ts +2 -0
  64. package/dist/core/verification.js +108 -0
  65. package/dist/core/warm-cache.js +7 -3
  66. package/dist/generated/version.d.ts +1 -0
  67. package/dist/generated/version.js +2 -0
  68. package/dist/git/scope.d.ts +19 -0
  69. package/dist/git/scope.js +167 -0
  70. package/dist/index.d.ts +2 -1
  71. package/dist/index.js +1 -0
  72. package/dist/output/format.js +15 -0
  73. package/dist/output/github.js +6 -0
  74. package/dist/output/sarif.js +12 -18
  75. package/dist/parsers/package-json.js +2 -4
  76. package/dist/pm/detect.d.ts +40 -1
  77. package/dist/pm/detect.js +152 -9
  78. package/dist/pm/install.d.ts +3 -1
  79. package/dist/pm/install.js +18 -17
  80. package/dist/registry/npm.js +34 -76
  81. package/dist/rup +0 -0
  82. package/dist/types/index.d.ts +134 -5
  83. package/dist/ui/tui.d.ts +4 -1
  84. package/dist/ui/tui.js +156 -67
  85. package/dist/utils/io.js +5 -6
  86. package/dist/utils/lockfile.js +24 -19
  87. package/dist/utils/runtime-paths.d.ts +4 -0
  88. package/dist/utils/runtime-paths.js +35 -0
  89. package/dist/utils/runtime.d.ts +7 -0
  90. package/dist/utils/runtime.js +32 -0
  91. package/dist/workspace/discover.d.ts +7 -1
  92. package/dist/workspace/discover.js +67 -54
  93. package/package.json +24 -19
  94. package/dist/ui/dashboard/DashboardTUI.d.ts +0 -6
  95. package/dist/ui/dashboard/DashboardTUI.js +0 -34
  96. package/dist/ui/dashboard/components/DetailPanel.d.ts +0 -4
  97. package/dist/ui/dashboard/components/DetailPanel.js +0 -30
  98. package/dist/ui/dashboard/components/Footer.d.ts +0 -4
  99. package/dist/ui/dashboard/components/Footer.js +0 -9
  100. package/dist/ui/dashboard/components/Header.d.ts +0 -4
  101. package/dist/ui/dashboard/components/Header.js +0 -12
  102. package/dist/ui/dashboard/components/Sidebar.d.ts +0 -4
  103. package/dist/ui/dashboard/components/Sidebar.js +0 -23
  104. package/dist/ui/dashboard/store.d.ts +0 -34
  105. package/dist/ui/dashboard/store.js +0 -148
@@ -1,15 +1,31 @@
1
- import { promises as fs } from "node:fs";
2
1
  import path from "node:path";
3
- const HARD_IGNORE_DIRS = new Set(["node_modules", ".git", ".turbo", ".next", "dist", "coverage"]);
2
+ import { scopePackageDirsByGit } from "../git/scope.js";
3
+ const HARD_IGNORE_DIRS = new Set([
4
+ "node_modules",
5
+ ".git",
6
+ ".turbo",
7
+ ".next",
8
+ "dist",
9
+ "coverage",
10
+ ]);
4
11
  const MAX_DISCOVERED_DIRS = 20000;
5
- export async function discoverPackageDirs(cwd, workspaceMode) {
12
+ export async function discoverPackageDirs(cwd, workspaceMode, options = {}) {
6
13
  if (!workspaceMode) {
7
- return [cwd];
14
+ const scoped = await scopePackageDirsByGit(cwd, [cwd], options.git ?? {}, {
15
+ includeKinds: options.includeKinds,
16
+ includeDependents: options.includeDependents,
17
+ });
18
+ return scoped.packageDirs;
8
19
  }
9
20
  const roots = new Set([cwd]);
10
- const patterns = [...(await readPackageJsonWorkspacePatterns(cwd)), ...(await readPnpmWorkspacePatterns(cwd))];
21
+ const patterns = [
22
+ ...(await readPackageJsonWorkspacePatterns(cwd)),
23
+ ...(await readPnpmWorkspacePatterns(cwd)),
24
+ ];
11
25
  const include = patterns.filter((item) => !item.startsWith("!"));
12
- const exclude = patterns.filter((item) => item.startsWith("!")).map((item) => item.slice(1));
26
+ const exclude = patterns
27
+ .filter((item) => item.startsWith("!"))
28
+ .map((item) => item.slice(1));
13
29
  for (const pattern of include) {
14
30
  const dirs = await expandWorkspacePattern(cwd, pattern);
15
31
  for (const dir of dirs) {
@@ -25,21 +41,28 @@ export async function discoverPackageDirs(cwd, workspaceMode) {
25
41
  const existing = [];
26
42
  for (const dir of roots) {
27
43
  const packageJsonPath = path.join(dir, "package.json");
28
- try {
29
- await fs.access(packageJsonPath);
44
+ if (await packageFileExists(packageJsonPath)) {
30
45
  existing.push(dir);
31
46
  }
32
- catch {
33
- // ignore missing package.json
34
- }
35
47
  }
36
- return existing.sort();
48
+ const scoped = await scopePackageDirsByGit(cwd, existing.sort(), options.git ?? {}, {
49
+ includeKinds: options.includeKinds,
50
+ includeDependents: options.includeDependents,
51
+ });
52
+ return scoped.packageDirs;
53
+ }
54
+ async function packageFileExists(packageJsonPath) {
55
+ try {
56
+ return await Bun.file(packageJsonPath).exists();
57
+ }
58
+ catch {
59
+ return false;
60
+ }
37
61
  }
38
62
  async function readPackageJsonWorkspacePatterns(cwd) {
39
63
  const packagePath = path.join(cwd, "package.json");
40
64
  try {
41
- const content = await fs.readFile(packagePath, "utf8");
42
- const parsed = JSON.parse(content);
65
+ const parsed = (await Bun.file(packagePath).json());
43
66
  if (Array.isArray(parsed.workspaces)) {
44
67
  return parsed.workspaces;
45
68
  }
@@ -55,7 +78,7 @@ async function readPackageJsonWorkspacePatterns(cwd) {
55
78
  async function readPnpmWorkspacePatterns(cwd) {
56
79
  const workspacePath = path.join(cwd, "pnpm-workspace.yaml");
57
80
  try {
58
- const content = await fs.readFile(workspacePath, "utf8");
81
+ const content = await Bun.file(workspacePath).text();
59
82
  const lines = content.split(/\r?\n/);
60
83
  const patterns = [];
61
84
  for (const line of lines) {
@@ -74,53 +97,43 @@ async function readPnpmWorkspacePatterns(cwd) {
74
97
  }
75
98
  }
76
99
  async function expandWorkspacePattern(cwd, pattern) {
77
- const normalized = pattern.replace(/\\/g, "/").replace(/^\.\//, "").replace(/\/+$/, "");
100
+ const normalized = pattern
101
+ .replace(/\\/g, "/")
102
+ .replace(/^\.\//, "")
103
+ .replace(/\/+$/, "");
78
104
  if (normalized.length === 0)
79
105
  return [];
80
106
  if (!normalized.includes("*")) {
81
107
  return [path.resolve(cwd, normalized)];
82
108
  }
83
- const segments = normalized.split("/").filter(Boolean);
84
109
  const results = new Set();
85
- await collectMatches(path.resolve(cwd), segments, 0, results);
86
- return Array.from(results);
87
- }
88
- async function collectMatches(baseDir, segments, index, out) {
89
- if (out.size > MAX_DISCOVERED_DIRS) {
90
- throw new Error(`Workspace discovery exceeded ${MAX_DISCOVERED_DIRS} directories. Refine workspace patterns.`);
91
- }
92
- if (index >= segments.length) {
93
- out.add(baseDir);
94
- return;
95
- }
96
- const segment = segments[index];
97
- if (segment === "**") {
98
- await collectMatches(baseDir, segments, index + 1, out);
99
- const children = await readChildDirs(baseDir);
100
- for (const child of children) {
101
- await collectMatches(child, segments, index, out);
102
- }
103
- return;
104
- }
105
- if (segment === "*") {
106
- const children = await readChildDirs(baseDir);
107
- for (const child of children) {
108
- await collectMatches(child, segments, index + 1, out);
110
+ const manifestPattern = normalized.endsWith("/package.json")
111
+ ? normalized
112
+ : `${normalized}/package.json`;
113
+ const glob = new Bun.Glob(manifestPattern);
114
+ for await (const match of glob.scan({
115
+ cwd,
116
+ absolute: true,
117
+ dot: false,
118
+ onlyFiles: true,
119
+ })) {
120
+ const dir = path.dirname(match);
121
+ if (shouldIgnoreWorkspaceDir(cwd, dir))
122
+ continue;
123
+ results.add(dir);
124
+ if (results.size > MAX_DISCOVERED_DIRS) {
125
+ throw new Error(`Workspace discovery exceeded ${MAX_DISCOVERED_DIRS} directories. Refine workspace patterns.`);
109
126
  }
110
- return;
111
127
  }
112
- await collectMatches(path.join(baseDir, segment), segments, index + 1, out);
128
+ return Array.from(results);
113
129
  }
114
- async function readChildDirs(dir) {
115
- try {
116
- const entries = await fs.readdir(dir, { withFileTypes: true });
117
- return entries
118
- .filter((entry) => entry.isDirectory())
119
- .filter((entry) => !HARD_IGNORE_DIRS.has(entry.name))
120
- .filter((entry) => !entry.name.startsWith("."))
121
- .map((entry) => path.join(dir, entry.name));
122
- }
123
- catch {
124
- return [];
130
+ function shouldIgnoreWorkspaceDir(cwd, dir) {
131
+ const relative = path.relative(cwd, dir);
132
+ if (relative.length === 0 || relative.startsWith("..")) {
133
+ return false;
125
134
  }
135
+ return relative
136
+ .split(path.sep)
137
+ .filter(Boolean)
138
+ .some((segment) => HARD_IGNORE_DIRS.has(segment) || segment.startsWith("."));
126
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainy-updates/cli",
3
- "version": "0.5.7",
3
+ "version": "0.6.1",
4
4
  "description": "The fastest DevOps-first dependency CLI. Checks, audits, upgrades, bisects, and automates npm/pnpm dependencies in CI.",
5
5
  "type": "module",
6
6
  "private": false,
@@ -30,6 +30,7 @@
30
30
  "renovate"
31
31
  ],
32
32
  "bin": {
33
+ "cli": "./dist/bin/cli.js",
33
34
  "rainy-updates": "./dist/bin/cli.js",
34
35
  "rainy-up": "./dist/bin/cli.js",
35
36
  "rup": "./dist/bin/cli.js"
@@ -51,24 +52,29 @@
51
52
  ],
52
53
  "scripts": {
53
54
  "clean": "rm -rf dist",
54
- "build": "tsc -p tsconfig.build.json",
55
- "typecheck": "tsc --noEmit",
56
- "test": "bun test",
55
+ "version:sync": "bun scripts/sync-version.mjs",
56
+ "version:check": "bun scripts/sync-version.mjs --check",
57
+ "version": "bun run version:sync",
58
+ "build": "bun run version:sync && tsc -p tsconfig.build.json",
59
+ "build:exe": "bun run version:sync && bun build ./src/bin/cli.ts --compile --outfile dist/rup",
60
+ "typecheck": "bun run version:check && tsc --noEmit",
61
+ "test": "bun run version:check && bun test",
57
62
  "lint": "bunx biome check src tests",
58
63
  "check": "bun run typecheck && bun test",
59
- "bench:fixtures": "node scripts/generate-benchmark-fixtures.mjs",
60
- "bench:check": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs single-100 check cold 3 && node scripts/benchmark.mjs single-100 check warm 3",
61
- "bench:review": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs single-100 review cold 3 && node scripts/benchmark.mjs single-100 review warm 3",
62
- "bench:resolve": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs single-100 resolve cold 3 && node scripts/benchmark.mjs single-100 resolve warm 3",
63
- "bench:ci": "bun run build && bun run bench:fixtures && node scripts/benchmark.mjs mono-1000 ci cold 3 && node scripts/benchmark.mjs mono-1000 ci warm 3",
64
- "perf:smoke": "node scripts/perf-smoke.mjs && RAINY_UPDATES_PERF_SCENARIO=resolve node scripts/perf-smoke.mjs && RAINY_UPDATES_PERF_SCENARIO=ci node scripts/perf-smoke.mjs",
65
- "perf:check": "node scripts/perf-smoke.mjs",
66
- "perf:resolve": "RAINY_UPDATES_PERF_SCENARIO=resolve node scripts/perf-smoke.mjs",
67
- "perf:ci": "RAINY_UPDATES_PERF_SCENARIO=ci node scripts/perf-smoke.mjs",
68
- "test:prod": "node dist/bin/cli.js --help && node dist/bin/cli.js --version",
69
- "prepublishOnly": "bun run check && bun run build && bun run test:prod"
64
+ "bench:fixtures": "bun run scripts/generate-benchmark-fixtures.mjs",
65
+ "bench:check": "bun run build && bun run bench:fixtures && bun run scripts/benchmark.mjs single-100 check cold 3 && bun run scripts/benchmark.mjs single-100 check warm 3",
66
+ "bench:review": "bun run build && bun run bench:fixtures && bun run scripts/benchmark.mjs single-100 review cold 3 && bun run scripts/benchmark.mjs single-100 review warm 3",
67
+ "bench:resolve": "bun run build && bun run bench:fixtures && bun run scripts/benchmark.mjs single-100 resolve cold 3 && bun run scripts/benchmark.mjs single-100 resolve warm 3",
68
+ "bench:ci": "bun run build && bun run bench:fixtures && bun run scripts/benchmark.mjs mono-1000 ci cold 3 && bun run scripts/benchmark.mjs mono-1000 ci warm 3",
69
+ "perf:smoke": "bun run scripts/perf-smoke.mjs && RAINY_UPDATES_PERF_SCENARIO=resolve bun run scripts/perf-smoke.mjs && RAINY_UPDATES_PERF_SCENARIO=ci bun run scripts/perf-smoke.mjs",
70
+ "perf:check": "bun run scripts/perf-smoke.mjs",
71
+ "perf:resolve": "RAINY_UPDATES_PERF_SCENARIO=resolve bun run scripts/perf-smoke.mjs",
72
+ "perf:ci": "RAINY_UPDATES_PERF_SCENARIO=ci bun run scripts/perf-smoke.mjs",
73
+ "test:prod": "bun ./dist/bin/cli.js --help && bun ./dist/bin/cli.js --version && (test -x ./dist/rup || bun run build:exe) && ./dist/rup --help && ./dist/rup --version",
74
+ "prepublishOnly": "bun run check && bun run build && bun run build:exe && bun run test:prod"
70
75
  },
71
76
  "engines": {
77
+ "bun": ">=1.3.9",
72
78
  "node": ">=20"
73
79
  },
74
80
  "publishConfig": {
@@ -76,16 +82,15 @@
76
82
  "provenance": true
77
83
  },
78
84
  "devDependencies": {
79
- "@types/bun": "latest",
85
+ "@types/bun": "1.3.9",
80
86
  "@types/react": "^19.2.14",
81
87
  "ink-testing-library": "^4.0.0",
88
+ "react-devtools-core": "^7.0.1",
82
89
  "typescript": "^5.9.3"
83
90
  },
84
- "optionalDependencies": {
85
- "undici": "^7.22.0"
86
- },
87
91
  "dependencies": {
88
92
  "ink": "^6.8.0",
93
+ "oxc-parser": "^0.115.0",
89
94
  "react": "^19.2.4",
90
95
  "zod": "^4.3.6"
91
96
  }
@@ -1,6 +0,0 @@
1
- import type { DashboardOptions, CheckResult } from "../../types/index.js";
2
- export interface DashboardTUIProps {
3
- options: DashboardOptions;
4
- initialResult: CheckResult;
5
- }
6
- export declare function DashboardTUI({ options, initialResult }: DashboardTUIProps): import("react/jsx-runtime").JSX.Element;
@@ -1,34 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Box, useInput, useApp } from "ink";
3
- import { initStore, dashboardActions } from "./store.js";
4
- import { Header } from "./components/Header.js";
5
- import { Sidebar } from "./components/Sidebar.js";
6
- import { DetailPanel } from "./components/DetailPanel.js";
7
- import { Footer } from "./components/Footer.js";
8
- export function DashboardTUI({ options, initialResult }) {
9
- const { exit } = useApp();
10
- // Initialize the singleton store synchronously before rendering children
11
- // so that components can access it on the first render pass natively.
12
- initStore(options, initialResult);
13
- // Handle global keyboard input (doesn't trigger React re-renders unless store state affects this component)
14
- // Our static layout theoretically will not re-render off this hook alone.
15
- useInput((input, key) => {
16
- if (key.upArrow) {
17
- dashboardActions.moveCursorUp();
18
- }
19
- if (key.downArrow) {
20
- dashboardActions.moveCursorDown();
21
- }
22
- if (key.return) {
23
- dashboardActions.setShouldApply(true);
24
- exit();
25
- }
26
- if (input === "r") {
27
- void dashboardActions.runResolveAction();
28
- }
29
- if (input === "a") {
30
- void dashboardActions.runAuditAction();
31
- }
32
- });
33
- return (_jsxs(Box, { flexDirection: "column", minHeight: 25, children: [_jsx(Header, {}), _jsxs(Box, { flexDirection: "row", width: "100%", children: [_jsx(Sidebar, {}), _jsx(DetailPanel, {})] }), _jsx(Footer, {})] }));
34
- }
@@ -1,4 +0,0 @@
1
- import React from "react";
2
- declare function DetailPanelComponent(): import("react/jsx-runtime").JSX.Element;
3
- export declare const DetailPanel: React.MemoExoticComponent<typeof DetailPanelComponent>;
4
- export {};
@@ -1,30 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Text } from "ink";
4
- import { useDashboardStore } from "../store.js";
5
- function getRiskColor(risk) {
6
- switch (risk) {
7
- case "critical":
8
- return "red";
9
- case "high":
10
- return "red";
11
- case "medium":
12
- return "yellow";
13
- case "low":
14
- return "blue";
15
- default:
16
- return "gray";
17
- }
18
- }
19
- function DetailPanelComponent() {
20
- const selectedIndex = useDashboardStore((s) => s.selectedIndex);
21
- const updates = useDashboardStore((s) => s.updates);
22
- // Fallback if list is empty
23
- const update = updates[selectedIndex];
24
- if (!update) {
25
- return (_jsx(Box, { width: "50%", height: 22, padding: 1, borderStyle: "single", borderColor: "gray", children: _jsx(Text, { dimColor: true, children: "No package selected." }) }));
26
- }
27
- const { name, publishedAt, publishAgeDays, riskLevel, homepage, advisoryCount, peerConflictSeverity, } = update;
28
- return (_jsxs(Box, { width: "50%", height: 22, flexDirection: "column", paddingX: 2, borderStyle: "single", borderColor: "gray", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "cyan", children: name }) }), _jsxs(Box, { marginBottom: 2, flexDirection: "column", children: [_jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Risk: " }), _jsx(Text, { color: getRiskColor(riskLevel), children: riskLevel || "unknown" })] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Advisories: " }), _jsx(Text, { color: advisoryCount ? "red" : "green", children: advisoryCount || 0 })] }), _jsxs(Text, { children: [_jsx(Text, { bold: true, children: "Peer Conflicts: " }), _jsx(Text, { color: peerConflictSeverity ? "red" : "green", children: peerConflictSeverity || "none" })] })] }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(Text, { color: "gray", children: homepage || "No homepage provided" }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["Published ", publishAgeDays, " days ago", " ", publishedAt ? `(${publishedAt})` : ""] }) })] }));
29
- }
30
- export const DetailPanel = React.memo(DetailPanelComponent);
@@ -1,4 +0,0 @@
1
- import React from "react";
2
- declare function FooterComponent(): import("react/jsx-runtime").JSX.Element;
3
- export declare const Footer: React.MemoExoticComponent<typeof FooterComponent>;
4
- export {};
@@ -1,9 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Text } from "ink";
4
- import { useDashboardStore } from "../store.js";
5
- function FooterComponent() {
6
- const modal = useDashboardStore((s) => s.modal);
7
- return (_jsxs(Box, { width: "100%", paddingX: 1, flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { children: _jsxs(Text, { dimColor: true, children: [_jsx(Text, { bold: true, color: "white", children: "\u2191\u2193" }), " ", "Navigate |", _jsxs(Text, { bold: true, color: "white", children: [" ", "Enter"] }), " ", "Apply |", _jsxs(Text, { bold: true, color: "white", children: [" ", "r"] }), "esolve |", _jsxs(Text, { bold: true, color: "white", children: [" ", "a"] }), "udit |", _jsxs(Text, { bold: true, color: "white", children: [" ", "i"] }), "gnore"] }) }), _jsx(Box, { children: _jsx(Text, { color: "yellow", children: modal !== "none" ? `Status: ${modal}...` : "Status: Idle" }) })] }));
8
- }
9
- export const Footer = React.memo(FooterComponent);
@@ -1,4 +0,0 @@
1
- import React from "react";
2
- declare function HeaderComponent(): import("react/jsx-runtime").JSX.Element;
3
- export declare const Header: React.MemoExoticComponent<typeof HeaderComponent>;
4
- export {};
@@ -1,12 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Text } from "ink";
4
- import { useDashboardStore } from "../store.js";
5
- function HeaderComponent() {
6
- const scanned = useDashboardStore((s) => s.summary.scannedPackages);
7
- const totalUpdates = useDashboardStore((s) => s.updates.length);
8
- const view = useDashboardStore((s) => s.view);
9
- return (_jsxs(Box, { width: "100%", paddingX: 1, borderStyle: "single", borderColor: "blue", flexDirection: "row", justifyContent: "space-between", children: [_jsx(Box, { children: _jsx(Text, { color: "cyan", bold: true, children: "\uD83C\uDF27\uFE0F Rainy Updates Dashboard" }) }), _jsx(Box, { children: _jsxs(Text, { children: [_jsx(Text, { color: view === "dependencies" ? "green" : "gray", children: "[Dependencies]" }), " ", _jsx(Text, { color: view === "security" ? "green" : "gray", children: "[Security]" }), " ", _jsx(Text, { color: view === "health" ? "green" : "gray", children: "[Health]" })] }) }), _jsx(Box, { children: _jsxs(Text, { dimColor: true, children: ["Packages: ", scanned, " | Found: ", totalUpdates] }) })] }));
10
- }
11
- // Memoize to ensure Header only renders when deeply affected (which is rare)
12
- export const Header = React.memo(HeaderComponent);
@@ -1,4 +0,0 @@
1
- import React from "react";
2
- declare function SidebarComponent(): import("react/jsx-runtime").JSX.Element;
3
- export declare const Sidebar: React.MemoExoticComponent<typeof SidebarComponent>;
4
- export {};
@@ -1,23 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from "react";
3
- import { Box, Text } from "ink";
4
- import { useDashboardStore } from "../store.js";
5
- // A heavily memoized single row
6
- const DependencyRow = React.memo(({ update, isActive }) => {
7
- return (_jsxs(Box, { paddingX: 1, width: "100%", children: [_jsx(Box, { width: 2, children: _jsx(Text, { color: "cyan", children: isActive ? "> " : " " }) }), _jsx(Box, { flexGrow: 1, children: _jsx(Text, { color: isActive ? "white" : "gray", bold: isActive, children: update.name }) }), _jsx(Box, { width: 15, justifyContent: "flex-end", children: _jsx(Text, { dimColor: true, children: update.fromRange }) }), _jsx(Box, { width: 3, justifyContent: "center", children: _jsx(Text, { dimColor: true, children: "\u2192" }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: "green", children: update.toRange }) })] }));
8
- });
9
- DependencyRow.displayName = "DependencyRow";
10
- function SidebarComponent() {
11
- const updates = useDashboardStore((s) => s.updates);
12
- const selectedIndex = useDashboardStore((s) => s.selectedIndex);
13
- // Simple windowing: in a real robust TUI we'd calculate terminal height
14
- // For now we'll just slice the array based on a fixed viewport (e.g., 20 items)
15
- const windowSize = 20;
16
- const start = Math.max(0, Math.min(selectedIndex - windowSize / 2, updates.length - windowSize));
17
- const visibleUpdates = updates.slice(start, start + windowSize);
18
- return (_jsxs(Box, { width: "50%", flexDirection: "column", borderStyle: "single", borderColor: "gray", height: windowSize + 2, children: [visibleUpdates.map((update, i) => {
19
- const actualIndex = start + i;
20
- return (_jsx(DependencyRow, { update: update, index: actualIndex, isActive: actualIndex === selectedIndex }, `${update.name}-${update.toRange}`));
21
- }), updates.length === 0 && (_jsx(Box, { paddingX: 1, children: _jsx(Text, { dimColor: true, children: "No updates found." }) }))] }));
22
- }
23
- export const Sidebar = React.memo(SidebarComponent);
@@ -1,34 +0,0 @@
1
- import type { DashboardOptions, CheckResult, PackageUpdate } from "../../types/index.js";
2
- export interface DashboardState {
3
- selectedIndex: number;
4
- view: "dependencies" | "security" | "health";
5
- modal: "none" | "resolving" | "auditing" | "applying";
6
- updates: PackageUpdate[];
7
- summary: CheckResult["summary"];
8
- options: DashboardOptions;
9
- error?: string;
10
- shouldApply: boolean;
11
- }
12
- type Listener = () => void;
13
- declare class DashboardStore {
14
- private state;
15
- private listeners;
16
- constructor(initialState: DashboardState);
17
- getState: () => DashboardState;
18
- setState: (partial: Partial<DashboardState> | ((state: DashboardState) => Partial<DashboardState>)) => void;
19
- subscribe: (listener: Listener) => () => boolean;
20
- private emit;
21
- }
22
- export declare function initStore(options: DashboardOptions, initialResult: CheckResult): DashboardStore;
23
- export declare function useDashboardStore<T>(selector: (state: DashboardState) => T): T;
24
- export declare const dashboardActions: {
25
- moveCursorUp: () => void;
26
- moveCursorDown: () => void;
27
- setView: (view: DashboardState["view"]) => void;
28
- setModal: (modal: DashboardState["modal"]) => void;
29
- setShouldApply: (shouldApply: boolean) => void;
30
- runResolveAction: () => Promise<void>;
31
- runAuditAction: () => Promise<void>;
32
- };
33
- export declare function getStore(): DashboardStore | null;
34
- export {};
@@ -1,148 +0,0 @@
1
- import { useSyncExternalStore } from "react";
2
- import { runResolve } from "../../commands/resolve/runner.js";
3
- import { runAudit } from "../../commands/audit/runner.js";
4
- class DashboardStore {
5
- state;
6
- listeners = new Set();
7
- constructor(initialState) {
8
- this.state = initialState;
9
- }
10
- getState = () => this.state;
11
- setState = (partial) => {
12
- const changes = typeof partial === "function" ? partial(this.state) : partial;
13
- this.state = { ...this.state, ...changes };
14
- this.emit();
15
- };
16
- subscribe = (listener) => {
17
- this.listeners.add(listener);
18
- return () => this.listeners.delete(listener);
19
- };
20
- emit() {
21
- for (const listener of this.listeners) {
22
- listener();
23
- }
24
- }
25
- }
26
- // Global singleton per run
27
- let store = null;
28
- export function initStore(options, initialResult) {
29
- if (!store) {
30
- store = new DashboardStore({
31
- selectedIndex: 0,
32
- view: options.view ?? "dependencies",
33
- modal: "none",
34
- updates: initialResult.updates,
35
- summary: initialResult.summary,
36
- options,
37
- shouldApply: false,
38
- });
39
- }
40
- return store;
41
- }
42
- // Hook to use the store in components, taking a selector to prevent unnecessary re-renders
43
- export function useDashboardStore(selector) {
44
- if (!store)
45
- throw new Error("Store not initialized");
46
- // Custom equality check could be added, but returning primitive/stable references from selector works best
47
- return useSyncExternalStore(store.subscribe, () => selector(store.getState()));
48
- }
49
- // Export actions to modify state without re-rendering the caller
50
- export const dashboardActions = {
51
- moveCursorUp: () => {
52
- store?.setState((s) => ({
53
- selectedIndex: Math.max(0, s.selectedIndex - 1),
54
- }));
55
- },
56
- moveCursorDown: () => {
57
- store?.setState((s) => ({
58
- selectedIndex: Math.min(s.updates.length - 1, s.selectedIndex + 1),
59
- }));
60
- },
61
- setView: (view) => {
62
- store?.setState({ view, selectedIndex: 0 }); // reset cursor on view change
63
- },
64
- setModal: (modal) => {
65
- store?.setState({ modal });
66
- },
67
- setShouldApply: (shouldApply) => {
68
- store?.setState({ shouldApply });
69
- },
70
- runResolveAction: async () => {
71
- if (!store)
72
- return;
73
- const s = store.getState();
74
- store.setState({ modal: "resolving" });
75
- try {
76
- const resolveOpts = {
77
- cwd: s.options.cwd,
78
- workspace: s.options.workspace,
79
- afterUpdate: true,
80
- safe: true,
81
- concurrency: s.options.concurrency,
82
- registryTimeoutMs: s.options.registryTimeoutMs,
83
- cacheTtlSeconds: s.options.cacheTtlSeconds,
84
- silent: true,
85
- };
86
- const result = await runResolve(resolveOpts);
87
- // Update updates array with the conflict severity
88
- const updatedUpdates = s.updates.map((update) => {
89
- const hasError = result.conflicts.some((c) => c.requester === update.name && c.severity === "error");
90
- const hasWarning = result.conflicts.some((c) => c.requester === update.name && c.severity === "warning");
91
- const severity = (hasError ? "error" : hasWarning ? "warning" : "none");
92
- return { ...update, peerConflictSeverity: severity };
93
- });
94
- store.setState({ updates: updatedUpdates });
95
- }
96
- catch (err) {
97
- store.setState({ error: String(err) });
98
- }
99
- finally {
100
- store.setState({ modal: "none" });
101
- }
102
- },
103
- runAuditAction: async () => {
104
- if (!store)
105
- return;
106
- const s = store.getState();
107
- store.setState({ modal: "auditing" });
108
- try {
109
- const auditOpts = {
110
- cwd: s.options.cwd,
111
- workspace: s.options.workspace,
112
- fix: false,
113
- dryRun: false,
114
- commit: false,
115
- packageManager: "auto",
116
- reportFormat: "summary",
117
- sourceMode: "auto",
118
- concurrency: s.options.concurrency,
119
- registryTimeoutMs: s.options.registryTimeoutMs,
120
- silent: true,
121
- };
122
- const result = await runAudit(auditOpts);
123
- // Map advisories back to updates
124
- const updatedUpdates = s.updates.map((update) => {
125
- const pkgSummary = result.packages.find((p) => p.packageName === update.name);
126
- if (pkgSummary) {
127
- return {
128
- ...update,
129
- riskLevel: pkgSummary.severity,
130
- advisoryCount: pkgSummary.advisoryCount,
131
- toRange: pkgSummary.patchedVersion || update.toRange, // suggest the patch!
132
- };
133
- }
134
- return update;
135
- });
136
- store.setState({ updates: updatedUpdates });
137
- }
138
- catch (err) {
139
- store.setState({ error: String(err) });
140
- }
141
- finally {
142
- store.setState({ modal: "none" });
143
- }
144
- },
145
- };
146
- export function getStore() {
147
- return store;
148
- }