@rainy-updates/cli 0.5.2-rc.1 → 0.5.2

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 (56) hide show
  1. package/CHANGELOG.md +77 -0
  2. package/README.md +34 -1
  3. package/dist/bin/cli.js +128 -3
  4. package/dist/cache/cache.d.ts +1 -0
  5. package/dist/cache/cache.js +9 -2
  6. package/dist/commands/audit/fetcher.d.ts +2 -6
  7. package/dist/commands/audit/fetcher.js +2 -79
  8. package/dist/commands/audit/mapper.d.ts +8 -1
  9. package/dist/commands/audit/mapper.js +105 -9
  10. package/dist/commands/audit/parser.js +36 -2
  11. package/dist/commands/audit/runner.js +186 -15
  12. package/dist/commands/audit/sources/github.d.ts +2 -0
  13. package/dist/commands/audit/sources/github.js +125 -0
  14. package/dist/commands/audit/sources/index.d.ts +6 -0
  15. package/dist/commands/audit/sources/index.js +99 -0
  16. package/dist/commands/audit/sources/osv.d.ts +2 -0
  17. package/dist/commands/audit/sources/osv.js +131 -0
  18. package/dist/commands/audit/sources/types.d.ts +21 -0
  19. package/dist/commands/audit/sources/types.js +1 -0
  20. package/dist/commands/audit/targets.d.ts +20 -0
  21. package/dist/commands/audit/targets.js +314 -0
  22. package/dist/commands/changelog/fetcher.d.ts +9 -0
  23. package/dist/commands/changelog/fetcher.js +130 -0
  24. package/dist/commands/doctor/parser.d.ts +2 -0
  25. package/dist/commands/doctor/parser.js +92 -0
  26. package/dist/commands/doctor/runner.d.ts +2 -0
  27. package/dist/commands/doctor/runner.js +13 -0
  28. package/dist/commands/resolve/runner.js +3 -0
  29. package/dist/commands/review/parser.d.ts +2 -0
  30. package/dist/commands/review/parser.js +174 -0
  31. package/dist/commands/review/runner.d.ts +2 -0
  32. package/dist/commands/review/runner.js +30 -0
  33. package/dist/config/loader.d.ts +3 -0
  34. package/dist/core/check.js +39 -5
  35. package/dist/core/errors.d.ts +11 -0
  36. package/dist/core/errors.js +6 -0
  37. package/dist/core/options.d.ts +8 -1
  38. package/dist/core/options.js +43 -0
  39. package/dist/core/review-model.d.ts +5 -0
  40. package/dist/core/review-model.js +382 -0
  41. package/dist/core/summary.js +11 -2
  42. package/dist/core/upgrade.d.ts +1 -0
  43. package/dist/core/upgrade.js +27 -21
  44. package/dist/core/warm-cache.js +28 -4
  45. package/dist/index.d.ts +2 -1
  46. package/dist/index.js +1 -0
  47. package/dist/output/format.d.ts +4 -1
  48. package/dist/output/format.js +29 -3
  49. package/dist/output/github.js +5 -0
  50. package/dist/output/sarif.js +11 -0
  51. package/dist/registry/npm.d.ts +20 -0
  52. package/dist/registry/npm.js +27 -4
  53. package/dist/types/index.d.ts +91 -1
  54. package/dist/ui/tui.d.ts +2 -0
  55. package/dist/ui/tui.js +107 -0
  56. package/package.json +12 -2
@@ -30,6 +30,12 @@ export function createSarifReport(result) {
30
30
  kind: update.kind,
31
31
  diffType: update.diffType,
32
32
  resolvedVersion: update.toVersionResolved,
33
+ impactRank: update.impactScore?.rank,
34
+ impactScore: update.impactScore?.score,
35
+ riskLevel: update.riskLevel,
36
+ advisoryCount: update.advisoryCount ?? 0,
37
+ peerConflictSeverity: update.peerConflictSeverity ?? "none",
38
+ licenseStatus: update.licenseStatus ?? "allowed",
33
39
  },
34
40
  }));
35
41
  const errorResults = [...result.errors].sort((a, b) => a.localeCompare(b)).map((error) => ({
@@ -75,6 +81,11 @@ export function createSarifReport(result) {
75
81
  prLimitHit: result.summary.prLimitHit,
76
82
  fixPrBranchesCreated: result.summary.fixPrBranchesCreated,
77
83
  durationMs: result.summary.durationMs,
84
+ verdict: result.summary.verdict,
85
+ riskPackages: result.summary.riskPackages ?? 0,
86
+ securityPackages: result.summary.securityPackages ?? 0,
87
+ peerConflictPackages: result.summary.peerConflictPackages ?? 0,
88
+ licenseViolationPackages: result.summary.licenseViolationPackages ?? 0,
78
89
  },
79
90
  },
80
91
  ],
@@ -1,3 +1,13 @@
1
+ interface RegistryConfig {
2
+ defaultRegistry: string;
3
+ scopedRegistries: Map<string, string>;
4
+ authByRegistry: Map<string, RegistryAuth>;
5
+ }
6
+ interface RegistryAuth {
7
+ token?: string;
8
+ basicAuth?: string;
9
+ alwaysAuth: boolean;
10
+ }
1
11
  export interface ResolveManyOptions {
2
12
  concurrency: number;
3
13
  timeoutMs?: number;
@@ -12,6 +22,9 @@ export interface ResolveManyResult {
12
22
  latestVersion: string | null;
13
23
  versions: string[];
14
24
  publishedAtByVersion: Record<string, number>;
25
+ homepage?: string;
26
+ repository?: string;
27
+ hasInstallScript: boolean;
15
28
  }>;
16
29
  errors: Map<string, string>;
17
30
  }
@@ -24,6 +37,9 @@ export declare class NpmRegistryClient {
24
37
  latestVersion: string | null;
25
38
  versions: string[];
26
39
  publishedAtByVersion: Record<string, number>;
40
+ homepage?: string;
41
+ repository?: string;
42
+ hasInstallScript: boolean;
27
43
  }>;
28
44
  resolveLatestVersion(packageName: string, timeoutMs?: number): Promise<string | null>;
29
45
  resolveManyPackageMetadata(packageNames: string[], options: ResolveManyOptions): Promise<ResolveManyResult>;
@@ -32,3 +48,7 @@ export declare class NpmRegistryClient {
32
48
  errors: Map<string, string>;
33
49
  }>;
34
50
  }
51
+ export declare function loadRegistryConfig(cwd: string): Promise<RegistryConfig>;
52
+ export declare function resolveRegistryForPackage(packageName: string, config: RegistryConfig): string;
53
+ export declare function resolveAuthHeader(registry: string, config: RegistryConfig): string | undefined;
54
+ export {};
@@ -22,7 +22,7 @@ export class NpmRegistryClient {
22
22
  try {
23
23
  const response = await requester(packageName, timeoutMs);
24
24
  if (response.status === 404) {
25
- return { latestVersion: null, versions: [], publishedAtByVersion: {} };
25
+ return { latestVersion: null, versions: [], publishedAtByVersion: {}, hasInstallScript: false };
26
26
  }
27
27
  if (response.status === 429 || response.status >= 500) {
28
28
  throw new RetryableRegistryError(`Registry temporary error: ${response.status}`, response.retryAfterMs ?? computeBackoffMs(attempt));
@@ -35,6 +35,9 @@ export class NpmRegistryClient {
35
35
  latestVersion: response.data?.["dist-tags"]?.latest ?? null,
36
36
  versions,
37
37
  publishedAtByVersion: extractPublishTimes(response.data?.time),
38
+ homepage: response.data?.homepage,
39
+ repository: normalizeRepository(response.data?.repository),
40
+ hasInstallScript: detectInstallScript(response.data?.versions),
38
41
  };
39
42
  }
40
43
  catch (error) {
@@ -94,6 +97,26 @@ export class NpmRegistryClient {
94
97
  function sleep(ms) {
95
98
  return new Promise((resolve) => setTimeout(resolve, ms));
96
99
  }
100
+ function normalizeRepository(value) {
101
+ if (!value)
102
+ return undefined;
103
+ if (typeof value === "string")
104
+ return value;
105
+ return value.url;
106
+ }
107
+ function detectInstallScript(versions) {
108
+ if (!versions)
109
+ return false;
110
+ for (const metadata of Object.values(versions)) {
111
+ const scripts = metadata?.scripts;
112
+ if (!scripts)
113
+ continue;
114
+ if (scripts.preinstall || scripts.install || scripts.postinstall) {
115
+ return true;
116
+ }
117
+ }
118
+ return false;
119
+ }
97
120
  function computeBackoffMs(attempt) {
98
121
  const baseMs = Math.max(120, attempt * 180);
99
122
  const jitterMs = Math.floor(Math.random() * 120);
@@ -195,7 +218,7 @@ async function tryCreateUndiciRequester(registryConfig) {
195
218
  return null;
196
219
  }
197
220
  }
198
- async function loadRegistryConfig(cwd) {
221
+ export async function loadRegistryConfig(cwd) {
199
222
  const homeNpmrc = path.join(os.homedir(), ".npmrc");
200
223
  const projectNpmrc = path.join(cwd, ".npmrc");
201
224
  const merged = new Map();
@@ -274,7 +297,7 @@ function normalizeRegistryUrl(value) {
274
297
  const normalized = value.endsWith("/") ? value : `${value}/`;
275
298
  return normalized;
276
299
  }
277
- function resolveRegistryForPackage(packageName, config) {
300
+ export function resolveRegistryForPackage(packageName, config) {
278
301
  const scope = extractScope(packageName);
279
302
  if (scope) {
280
303
  const scoped = config.scopedRegistries.get(scope);
@@ -295,7 +318,7 @@ function buildRegistryUrl(registry, packageName) {
295
318
  const base = normalizeRegistryUrl(registry);
296
319
  return new URL(encodeURIComponent(packageName), base).toString();
297
320
  }
298
- function resolveAuthHeader(registry, config) {
321
+ export function resolveAuthHeader(registry, config) {
299
322
  const registryUrl = normalizeRegistryUrl(registry);
300
323
  const auth = findRegistryAuth(registryUrl, config.authByRegistry);
301
324
  if (!auth)
@@ -3,6 +3,8 @@ export type TargetLevel = "patch" | "minor" | "major" | "latest";
3
3
  export type GroupBy = "none" | "name" | "scope" | "kind" | "risk";
4
4
  export type CiProfile = "minimal" | "strict" | "enterprise";
5
5
  export type LockfileMode = "preserve" | "update" | "error";
6
+ export type Verdict = "safe" | "review" | "blocked" | "actionable";
7
+ export type RiskLevel = "critical" | "high" | "medium" | "low";
6
8
  export type OutputFormat = "table" | "json" | "minimal" | "github" | "metrics";
7
9
  export type FailOnLevel = "none" | "patch" | "minor" | "major" | "any";
8
10
  export type LogLevel = "error" | "warn" | "info" | "debug";
@@ -44,6 +46,9 @@ export interface RunOptions {
44
46
  onlyChanged: boolean;
45
47
  ciProfile: CiProfile;
46
48
  lockfileMode: LockfileMode;
49
+ interactive: boolean;
50
+ showImpact: boolean;
51
+ showHomepage: boolean;
47
52
  }
48
53
  export interface CheckOptions extends RunOptions {
49
54
  }
@@ -86,6 +91,12 @@ export interface PackageUpdate {
86
91
  reason?: string;
87
92
  impactScore?: ImpactScore;
88
93
  homepage?: string;
94
+ riskLevel?: RiskLevel;
95
+ riskReasons?: string[];
96
+ advisoryCount?: number;
97
+ peerConflictSeverity?: "none" | PeerConflictSeverity;
98
+ licenseStatus?: "allowed" | "review" | "denied";
99
+ healthStatus?: "healthy" | HealthFlag;
89
100
  }
90
101
  export interface Summary {
91
102
  contractVersion: "2";
@@ -126,6 +137,13 @@ export interface Summary {
126
137
  prLimitHit: boolean;
127
138
  streamedEvents: number;
128
139
  policyOverridesApplied: number;
140
+ verdict?: Verdict;
141
+ interactiveSession?: boolean;
142
+ riskPackages?: number;
143
+ securityPackages?: number;
144
+ peerConflictPackages?: number;
145
+ licenseViolationPackages?: number;
146
+ privateRegistryPackages?: number;
129
147
  }
130
148
  export interface CheckResult {
131
149
  projectPath: string;
@@ -162,14 +180,20 @@ export interface VersionResolver {
162
180
  resolveLatestVersion(packageName: string): Promise<string | null>;
163
181
  }
164
182
  export type AuditSeverity = "critical" | "high" | "medium" | "low";
165
- export type AuditReportFormat = "table" | "json";
183
+ export type AuditReportFormat = "table" | "json" | "summary";
184
+ export type AuditSourceMode = "auto" | "osv" | "github" | "all";
185
+ export type AuditSourceName = "osv" | "github";
186
+ export type AuditSourceStatusLevel = "ok" | "partial" | "failed";
166
187
  export interface AuditOptions {
167
188
  cwd: string;
168
189
  workspace: boolean;
169
190
  severity?: AuditSeverity;
170
191
  fix: boolean;
171
192
  dryRun: boolean;
193
+ commit: boolean;
194
+ packageManager: "auto" | "npm" | "pnpm" | "bun" | "yarn";
172
195
  reportFormat: AuditReportFormat;
196
+ sourceMode: AuditSourceMode;
173
197
  jsonFile?: string;
174
198
  concurrency: number;
175
199
  registryTimeoutMs: number;
@@ -177,17 +201,44 @@ export interface AuditOptions {
177
201
  export interface CveAdvisory {
178
202
  cveId: string;
179
203
  packageName: string;
204
+ currentVersion: string | null;
180
205
  severity: AuditSeverity;
181
206
  vulnerableRange: string;
182
207
  patchedVersion: string | null;
183
208
  title: string;
184
209
  url: string;
210
+ sources: readonly AuditSourceName[];
211
+ }
212
+ export interface AuditPackageSummary {
213
+ packageName: string;
214
+ currentVersion: string | null;
215
+ severity: AuditSeverity;
216
+ advisoryCount: number;
217
+ patchedVersion: string | null;
218
+ sources: readonly AuditSourceName[];
219
+ }
220
+ export interface AuditSourceStatus {
221
+ source: AuditSourceName;
222
+ status: AuditSourceStatusLevel;
223
+ attemptedTargets: number;
224
+ successfulTargets: number;
225
+ failedTargets: number;
226
+ advisoriesFound: number;
227
+ message?: string;
185
228
  }
186
229
  export interface AuditResult {
187
230
  advisories: CveAdvisory[];
231
+ packages: AuditPackageSummary[];
188
232
  autoFixable: number;
189
233
  errors: string[];
190
234
  warnings: string[];
235
+ sourcesUsed: AuditSourceName[];
236
+ sourceHealth: AuditSourceStatus[];
237
+ resolution: {
238
+ lockfile: number;
239
+ manifest: number;
240
+ unresolved: number;
241
+ };
191
242
  }
192
243
  export interface BisectOptions {
193
244
  cwd: string;
@@ -270,6 +321,45 @@ export interface ResolveResult {
270
321
  errors: string[];
271
322
  warnings: string[];
272
323
  }
324
+ export interface RiskSignal {
325
+ packageName: string;
326
+ level: RiskLevel;
327
+ reasons: string[];
328
+ }
329
+ export interface ReviewItem {
330
+ update: PackageUpdate;
331
+ advisories: CveAdvisory[];
332
+ health?: PackageHealthMetric;
333
+ peerConflicts: PeerConflict[];
334
+ license?: PackageLicense;
335
+ unusedIssues: UnusedDependency[];
336
+ selected: boolean;
337
+ }
338
+ export interface ReviewResult {
339
+ projectPath: string;
340
+ target: TargetLevel;
341
+ summary: Summary;
342
+ items: ReviewItem[];
343
+ updates: PackageUpdate[];
344
+ errors: string[];
345
+ warnings: string[];
346
+ }
347
+ export interface ReviewOptions extends CheckOptions {
348
+ securityOnly: boolean;
349
+ risk?: RiskLevel;
350
+ diff?: TargetLevel;
351
+ applySelected: boolean;
352
+ }
353
+ export interface DoctorOptions extends CheckOptions {
354
+ verdictOnly: boolean;
355
+ }
356
+ export interface DoctorResult {
357
+ verdict: Verdict;
358
+ summary: Summary;
359
+ review: ReviewResult;
360
+ primaryFindings: string[];
361
+ recommendedCommand: string;
362
+ }
273
363
  export type UnusedKind = "declared-not-imported" | "imported-not-declared";
274
364
  export interface UnusedDependency {
275
365
  name: string;
@@ -0,0 +1,2 @@
1
+ import type { PackageUpdate } from "../types/index.js";
2
+ export declare function runTui(updates: PackageUpdate[]): Promise<PackageUpdate[]>;
package/dist/ui/tui.js ADDED
@@ -0,0 +1,107 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Box, render, Text, useInput } from "ink";
4
+ const FILTER_ORDER = [
5
+ "all",
6
+ "security",
7
+ "risky",
8
+ "major",
9
+ ];
10
+ function VersionDiff({ from, to }) {
11
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "gray", children: from }), _jsxs(Text, { color: "gray", children: [" ", " -> ", " "] }), _jsx(Text, { color: "green", children: to })] }));
12
+ }
13
+ function riskColor(level) {
14
+ switch (level) {
15
+ case "critical":
16
+ return "red";
17
+ case "high":
18
+ return "yellow";
19
+ case "medium":
20
+ return "cyan";
21
+ default:
22
+ return "green";
23
+ }
24
+ }
25
+ function diffColor(level) {
26
+ switch (level) {
27
+ case "major":
28
+ return "red";
29
+ case "minor":
30
+ return "yellow";
31
+ case "patch":
32
+ return "green";
33
+ default:
34
+ return "cyan";
35
+ }
36
+ }
37
+ function TuiApp({ updates, onComplete }) {
38
+ const [cursorIndex, setCursorIndex] = useState(0);
39
+ const [filterIndex, setFilterIndex] = useState(0);
40
+ const [selectedIndices, setSelectedIndices] = useState(new Set(updates.map((_, index) => index)));
41
+ const activeFilter = FILTER_ORDER[filterIndex] ?? "all";
42
+ const filteredIndices = updates
43
+ .map((update, index) => ({ update, index }))
44
+ .filter(({ update }) => {
45
+ if (activeFilter === "security")
46
+ return (update.advisoryCount ?? 0) > 0;
47
+ if (activeFilter === "risky") {
48
+ return update.riskLevel === "critical" || update.riskLevel === "high";
49
+ }
50
+ if (activeFilter === "major")
51
+ return update.diffType === "major";
52
+ return true;
53
+ })
54
+ .map(({ index }) => index);
55
+ const boundedCursor = Math.min(cursorIndex, Math.max(0, filteredIndices.length - 1));
56
+ const focusedIndex = filteredIndices[boundedCursor] ?? 0;
57
+ const focusedUpdate = updates[focusedIndex];
58
+ useInput((input, key) => {
59
+ if (key.leftArrow) {
60
+ setFilterIndex((prev) => Math.max(0, prev - 1));
61
+ setCursorIndex(0);
62
+ }
63
+ if (key.rightArrow) {
64
+ setFilterIndex((prev) => Math.min(FILTER_ORDER.length - 1, prev + 1));
65
+ setCursorIndex(0);
66
+ }
67
+ if (key.upArrow) {
68
+ setCursorIndex((prev) => Math.max(0, prev - 1));
69
+ }
70
+ if (key.downArrow) {
71
+ setCursorIndex((prev) => Math.min(filteredIndices.length - 1, Math.max(0, prev + 1)));
72
+ }
73
+ if (input === "a") {
74
+ setSelectedIndices(new Set(filteredIndices));
75
+ }
76
+ if (input === "n") {
77
+ setSelectedIndices(new Set());
78
+ }
79
+ if (input === " ") {
80
+ setSelectedIndices((prev) => {
81
+ const next = new Set(prev);
82
+ if (next.has(focusedIndex))
83
+ next.delete(focusedIndex);
84
+ else
85
+ next.add(focusedIndex);
86
+ return next;
87
+ });
88
+ }
89
+ if (key.return) {
90
+ onComplete(updates.filter((_, index) => selectedIndices.has(index)));
91
+ }
92
+ });
93
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Rainy Review TUI" }), _jsx(Text, { color: "gray", children: "Left/Right filter Up/Down move Space toggle A select all view N clear Enter confirm" }), _jsx(Box, { marginTop: 1, children: FILTER_ORDER.map((filter, index) => (_jsx(Box, { marginRight: 2, children: _jsxs(Text, { color: index === filterIndex ? "cyan" : "gray", children: ["[", filter, "]"] }) }, filter))) }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsxs(Box, { width: 72, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Updates" }), filteredIndices.length === 0 ? (_jsx(Text, { color: "gray", children: "No updates match this filter." })) : (filteredIndices.map((index, visibleIndex) => {
94
+ const update = updates[index];
95
+ const isFocused = visibleIndex === boundedCursor;
96
+ const isSelected = selectedIndices.has(index);
97
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: isFocused ? "cyan" : "gray", children: [isFocused ? ">" : " ", " ", isSelected ? "[x]" : "[ ]", " "] }), _jsx(Box, { width: 22, children: _jsx(Text, { bold: isFocused, children: update.name }) }), _jsx(Box, { width: 10, children: _jsx(Text, { color: diffColor(update.diffType), children: update.diffType }) }), _jsx(Box, { width: 18, children: _jsx(Text, { color: riskColor(update.riskLevel), children: update.riskLevel ?? update.impactScore?.rank ?? "low" }) }), _jsx(VersionDiff, { from: update.fromRange, to: update.toVersionResolved })] }, `${update.packagePath}:${update.name}`));
98
+ }))] }), _jsxs(Box, { marginLeft: 1, width: 46, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Details" }), focusedUpdate ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: focusedUpdate.name }), _jsxs(Text, { color: "gray", children: ["package: ", focusedUpdate.packagePath] }), _jsxs(Text, { children: ["diff: ", _jsx(Text, { color: diffColor(focusedUpdate.diffType), children: focusedUpdate.diffType })] }), _jsxs(Text, { children: ["risk: ", _jsx(Text, { color: riskColor(focusedUpdate.riskLevel), children: focusedUpdate.riskLevel ?? focusedUpdate.impactScore?.rank ?? "low" })] }), _jsxs(Text, { children: ["impact: ", focusedUpdate.impactScore?.score ?? 0] }), _jsxs(Text, { children: ["advisories: ", focusedUpdate.advisoryCount ?? 0] }), _jsxs(Text, { children: ["peer: ", focusedUpdate.peerConflictSeverity ?? "none"] }), _jsxs(Text, { children: ["license: ", focusedUpdate.licenseStatus ?? "allowed"] }), _jsxs(Text, { children: ["health: ", focusedUpdate.healthStatus ?? "healthy"] }), focusedUpdate.homepage ? (_jsxs(Text, { color: "blue", children: ["homepage: ", focusedUpdate.homepage] })) : (_jsx(Text, { color: "gray", children: "homepage: unavailable" })), focusedUpdate.riskReasons && focusedUpdate.riskReasons.length > 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: "Reasons" }), focusedUpdate.riskReasons.slice(0, 4).map((reason) => (_jsxs(Text, { color: "gray", children: ["- ", reason] }, reason)))] })) : (_jsx(Text, { color: "gray", children: "No elevated risk reasons." }))] })) : (_jsx(Text, { color: "gray", children: "No update selected." }))] })] }), _jsx(Box, { marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: _jsxs(Text, { color: "gray", children: [selectedIndices.size, " selected of ", updates.length, ". Filter: ", activeFilter, "."] }) })] }));
99
+ }
100
+ export async function runTui(updates) {
101
+ return new Promise((resolve) => {
102
+ const { unmount } = render(_jsx(TuiApp, { updates: updates, onComplete: (selected) => {
103
+ unmount();
104
+ resolve(selected);
105
+ } }));
106
+ });
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainy-updates/cli",
3
- "version": "0.5.2-rc.1",
3
+ "version": "0.5.2",
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,
@@ -56,7 +56,10 @@
56
56
  "test": "bun test",
57
57
  "lint": "bunx biome check src tests",
58
58
  "check": "bun run typecheck && bun test",
59
- "perf:smoke": "node scripts/perf-smoke.mjs",
59
+ "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",
60
+ "perf:check": "node scripts/perf-smoke.mjs",
61
+ "perf:resolve": "RAINY_UPDATES_PERF_SCENARIO=resolve node scripts/perf-smoke.mjs",
62
+ "perf:ci": "RAINY_UPDATES_PERF_SCENARIO=ci node scripts/perf-smoke.mjs",
60
63
  "test:prod": "node dist/bin/cli.js --help && node dist/bin/cli.js --version",
61
64
  "prepublishOnly": "bun run check && bun run build && bun run test:prod"
62
65
  },
@@ -69,9 +72,16 @@
69
72
  },
70
73
  "devDependencies": {
71
74
  "@types/bun": "latest",
75
+ "@types/react": "^19.2.14",
76
+ "ink-testing-library": "^4.0.0",
72
77
  "typescript": "^5.9.3"
73
78
  },
74
79
  "optionalDependencies": {
75
80
  "undici": "^7.22.0"
81
+ },
82
+ "dependencies": {
83
+ "ink": "^6.8.0",
84
+ "react": "^19.2.4",
85
+ "zod": "^4.3.6"
76
86
  }
77
87
  }