@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.
- package/CHANGELOG.md +77 -0
- package/README.md +34 -1
- package/dist/bin/cli.js +128 -3
- package/dist/cache/cache.d.ts +1 -0
- package/dist/cache/cache.js +9 -2
- package/dist/commands/audit/fetcher.d.ts +2 -6
- package/dist/commands/audit/fetcher.js +2 -79
- package/dist/commands/audit/mapper.d.ts +8 -1
- package/dist/commands/audit/mapper.js +105 -9
- package/dist/commands/audit/parser.js +36 -2
- package/dist/commands/audit/runner.js +186 -15
- package/dist/commands/audit/sources/github.d.ts +2 -0
- package/dist/commands/audit/sources/github.js +125 -0
- package/dist/commands/audit/sources/index.d.ts +6 -0
- package/dist/commands/audit/sources/index.js +99 -0
- package/dist/commands/audit/sources/osv.d.ts +2 -0
- package/dist/commands/audit/sources/osv.js +131 -0
- package/dist/commands/audit/sources/types.d.ts +21 -0
- package/dist/commands/audit/sources/types.js +1 -0
- package/dist/commands/audit/targets.d.ts +20 -0
- package/dist/commands/audit/targets.js +314 -0
- package/dist/commands/changelog/fetcher.d.ts +9 -0
- package/dist/commands/changelog/fetcher.js +130 -0
- package/dist/commands/doctor/parser.d.ts +2 -0
- package/dist/commands/doctor/parser.js +92 -0
- package/dist/commands/doctor/runner.d.ts +2 -0
- package/dist/commands/doctor/runner.js +13 -0
- package/dist/commands/resolve/runner.js +3 -0
- package/dist/commands/review/parser.d.ts +2 -0
- package/dist/commands/review/parser.js +174 -0
- package/dist/commands/review/runner.d.ts +2 -0
- package/dist/commands/review/runner.js +30 -0
- package/dist/config/loader.d.ts +3 -0
- package/dist/core/check.js +39 -5
- package/dist/core/errors.d.ts +11 -0
- package/dist/core/errors.js +6 -0
- package/dist/core/options.d.ts +8 -1
- package/dist/core/options.js +43 -0
- package/dist/core/review-model.d.ts +5 -0
- package/dist/core/review-model.js +382 -0
- package/dist/core/summary.js +11 -2
- package/dist/core/upgrade.d.ts +1 -0
- package/dist/core/upgrade.js +27 -21
- package/dist/core/warm-cache.js +28 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/output/format.d.ts +4 -1
- package/dist/output/format.js +29 -3
- package/dist/output/github.js +5 -0
- package/dist/output/sarif.js +11 -0
- package/dist/registry/npm.d.ts +20 -0
- package/dist/registry/npm.js +27 -4
- package/dist/types/index.d.ts +91 -1
- package/dist/ui/tui.d.ts +2 -0
- package/dist/ui/tui.js +107 -0
- package/package.json +12 -2
package/dist/output/sarif.js
CHANGED
|
@@ -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
|
],
|
package/dist/registry/npm.d.ts
CHANGED
|
@@ -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 {};
|
package/dist/registry/npm.js
CHANGED
|
@@ -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)
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/ui/tui.d.ts
ADDED
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
|
|
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
|
}
|