@rainy-updates/cli 0.5.1 → 0.5.2-rc.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 (61) hide show
  1. package/CHANGELOG.md +93 -1
  2. package/README.md +88 -25
  3. package/dist/bin/cli.js +50 -1
  4. package/dist/commands/audit/fetcher.d.ts +2 -6
  5. package/dist/commands/audit/fetcher.js +2 -79
  6. package/dist/commands/audit/mapper.d.ts +8 -1
  7. package/dist/commands/audit/mapper.js +106 -10
  8. package/dist/commands/audit/parser.js +36 -2
  9. package/dist/commands/audit/runner.js +179 -15
  10. package/dist/commands/audit/sources/github.d.ts +2 -0
  11. package/dist/commands/audit/sources/github.js +125 -0
  12. package/dist/commands/audit/sources/index.d.ts +6 -0
  13. package/dist/commands/audit/sources/index.js +92 -0
  14. package/dist/commands/audit/sources/osv.d.ts +2 -0
  15. package/dist/commands/audit/sources/osv.js +131 -0
  16. package/dist/commands/audit/sources/types.d.ts +21 -0
  17. package/dist/commands/audit/sources/types.js +1 -0
  18. package/dist/commands/audit/targets.d.ts +20 -0
  19. package/dist/commands/audit/targets.js +314 -0
  20. package/dist/commands/changelog/fetcher.d.ts +9 -0
  21. package/dist/commands/changelog/fetcher.js +130 -0
  22. package/dist/commands/licenses/parser.d.ts +2 -0
  23. package/dist/commands/licenses/parser.js +116 -0
  24. package/dist/commands/licenses/runner.d.ts +9 -0
  25. package/dist/commands/licenses/runner.js +163 -0
  26. package/dist/commands/licenses/sbom.d.ts +10 -0
  27. package/dist/commands/licenses/sbom.js +70 -0
  28. package/dist/commands/resolve/graph/builder.d.ts +20 -0
  29. package/dist/commands/resolve/graph/builder.js +183 -0
  30. package/dist/commands/resolve/graph/conflict.d.ts +20 -0
  31. package/dist/commands/resolve/graph/conflict.js +52 -0
  32. package/dist/commands/resolve/graph/resolver.d.ts +17 -0
  33. package/dist/commands/resolve/graph/resolver.js +71 -0
  34. package/dist/commands/resolve/parser.d.ts +2 -0
  35. package/dist/commands/resolve/parser.js +89 -0
  36. package/dist/commands/resolve/runner.d.ts +13 -0
  37. package/dist/commands/resolve/runner.js +136 -0
  38. package/dist/commands/snapshot/parser.d.ts +2 -0
  39. package/dist/commands/snapshot/parser.js +80 -0
  40. package/dist/commands/snapshot/runner.d.ts +11 -0
  41. package/dist/commands/snapshot/runner.js +115 -0
  42. package/dist/commands/snapshot/store.d.ts +35 -0
  43. package/dist/commands/snapshot/store.js +158 -0
  44. package/dist/commands/unused/matcher.d.ts +22 -0
  45. package/dist/commands/unused/matcher.js +95 -0
  46. package/dist/commands/unused/parser.d.ts +2 -0
  47. package/dist/commands/unused/parser.js +95 -0
  48. package/dist/commands/unused/runner.d.ts +11 -0
  49. package/dist/commands/unused/runner.js +113 -0
  50. package/dist/commands/unused/scanner.d.ts +18 -0
  51. package/dist/commands/unused/scanner.js +129 -0
  52. package/dist/core/impact.d.ts +36 -0
  53. package/dist/core/impact.js +82 -0
  54. package/dist/core/options.d.ts +13 -1
  55. package/dist/core/options.js +35 -13
  56. package/dist/types/index.d.ts +187 -1
  57. package/dist/ui/tui.d.ts +6 -0
  58. package/dist/ui/tui.js +50 -0
  59. package/dist/utils/semver.d.ts +18 -0
  60. package/dist/utils/semver.js +88 -3
  61. package/package.json +8 -1
@@ -64,6 +64,15 @@ export interface PackageDependency {
64
64
  range: string;
65
65
  kind: DependencyKind;
66
66
  }
67
+ export interface ImpactScore {
68
+ rank: "critical" | "high" | "medium" | "low";
69
+ score: number;
70
+ factors: {
71
+ diffTypeWeight: number;
72
+ hasAdvisory: boolean;
73
+ affectedWorkspaceCount: number;
74
+ };
75
+ }
67
76
  export interface PackageUpdate {
68
77
  packagePath: string;
69
78
  name: string;
@@ -75,6 +84,8 @@ export interface PackageUpdate {
75
84
  filtered: boolean;
76
85
  autofix: boolean;
77
86
  reason?: string;
87
+ impactScore?: ImpactScore;
88
+ homepage?: string;
78
89
  }
79
90
  export interface Summary {
80
91
  contractVersion: "2";
@@ -151,14 +162,20 @@ export interface VersionResolver {
151
162
  resolveLatestVersion(packageName: string): Promise<string | null>;
152
163
  }
153
164
  export type AuditSeverity = "critical" | "high" | "medium" | "low";
154
- export type AuditReportFormat = "table" | "json";
165
+ export type AuditReportFormat = "table" | "json" | "summary";
166
+ export type AuditSourceMode = "auto" | "osv" | "github" | "all";
167
+ export type AuditSourceName = "osv" | "github";
168
+ export type AuditSourceStatusLevel = "ok" | "partial" | "failed";
155
169
  export interface AuditOptions {
156
170
  cwd: string;
157
171
  workspace: boolean;
158
172
  severity?: AuditSeverity;
159
173
  fix: boolean;
160
174
  dryRun: boolean;
175
+ commit: boolean;
176
+ packageManager: "auto" | "npm" | "pnpm" | "bun" | "yarn";
161
177
  reportFormat: AuditReportFormat;
178
+ sourceMode: AuditSourceMode;
162
179
  jsonFile?: string;
163
180
  concurrency: number;
164
181
  registryTimeoutMs: number;
@@ -166,17 +183,44 @@ export interface AuditOptions {
166
183
  export interface CveAdvisory {
167
184
  cveId: string;
168
185
  packageName: string;
186
+ currentVersion: string | null;
169
187
  severity: AuditSeverity;
170
188
  vulnerableRange: string;
171
189
  patchedVersion: string | null;
172
190
  title: string;
173
191
  url: string;
192
+ sources: readonly AuditSourceName[];
193
+ }
194
+ export interface AuditPackageSummary {
195
+ packageName: string;
196
+ currentVersion: string | null;
197
+ severity: AuditSeverity;
198
+ advisoryCount: number;
199
+ patchedVersion: string | null;
200
+ sources: readonly AuditSourceName[];
201
+ }
202
+ export interface AuditSourceStatus {
203
+ source: AuditSourceName;
204
+ status: AuditSourceStatusLevel;
205
+ attemptedTargets: number;
206
+ successfulTargets: number;
207
+ failedTargets: number;
208
+ advisoriesFound: number;
209
+ message?: string;
174
210
  }
175
211
  export interface AuditResult {
176
212
  advisories: CveAdvisory[];
213
+ packages: AuditPackageSummary[];
177
214
  autoFixable: number;
178
215
  errors: string[];
179
216
  warnings: string[];
217
+ sourcesUsed: AuditSourceName[];
218
+ sourceHealth: AuditSourceStatus[];
219
+ resolution: {
220
+ lockfile: number;
221
+ manifest: number;
222
+ unresolved: number;
223
+ };
180
224
  }
181
225
  export interface BisectOptions {
182
226
  cwd: string;
@@ -224,3 +268,145 @@ export interface HealthResult {
224
268
  errors: string[];
225
269
  warnings: string[];
226
270
  }
271
+ export interface PeerNode {
272
+ name: string;
273
+ resolvedVersion: string;
274
+ peerRequirements: Map<string, string>;
275
+ }
276
+ export interface PeerGraph {
277
+ nodes: Map<string, PeerNode>;
278
+ roots: string[];
279
+ }
280
+ export type PeerConflictSeverity = "error" | "warning";
281
+ export interface PeerConflict {
282
+ requester: string;
283
+ peer: string;
284
+ requiredRange: string;
285
+ resolvedVersion: string;
286
+ severity: PeerConflictSeverity;
287
+ suggestion: string;
288
+ }
289
+ export interface ResolveOptions {
290
+ cwd: string;
291
+ workspace: boolean;
292
+ afterUpdate: boolean;
293
+ safe: boolean;
294
+ jsonFile?: string;
295
+ concurrency: number;
296
+ registryTimeoutMs: number;
297
+ cacheTtlSeconds: number;
298
+ }
299
+ export interface ResolveResult {
300
+ conflicts: PeerConflict[];
301
+ errorConflicts: number;
302
+ warningConflicts: number;
303
+ errors: string[];
304
+ warnings: string[];
305
+ }
306
+ export type UnusedKind = "declared-not-imported" | "imported-not-declared";
307
+ export interface UnusedDependency {
308
+ name: string;
309
+ kind: UnusedKind;
310
+ declaredIn?: string;
311
+ importedFrom?: string;
312
+ }
313
+ export interface UnusedOptions {
314
+ cwd: string;
315
+ workspace: boolean;
316
+ srcDirs: string[];
317
+ includeDevDependencies: boolean;
318
+ fix: boolean;
319
+ dryRun: boolean;
320
+ jsonFile?: string;
321
+ concurrency: number;
322
+ }
323
+ export interface UnusedResult {
324
+ unused: UnusedDependency[];
325
+ missing: UnusedDependency[];
326
+ totalUnused: number;
327
+ totalMissing: number;
328
+ errors: string[];
329
+ warnings: string[];
330
+ }
331
+ export interface PackageLicense {
332
+ name: string;
333
+ version: string;
334
+ license: string;
335
+ spdxExpression: string | null;
336
+ homepage?: string;
337
+ repository?: string;
338
+ }
339
+ export interface SbomDocument {
340
+ spdxVersion: "SPDX-2.3";
341
+ dataLicense: "CC0-1.0";
342
+ name: string;
343
+ documentNamespace: string;
344
+ packages: SbomPackage[];
345
+ relationships: SbomRelationship[];
346
+ }
347
+ export interface SbomPackage {
348
+ SPDXID: string;
349
+ name: string;
350
+ versionInfo: string;
351
+ downloadLocation: string;
352
+ licenseConcluded: string;
353
+ licenseDeclared: string;
354
+ copyrightText: string;
355
+ }
356
+ export interface SbomRelationship {
357
+ spdxElementId: string;
358
+ relationshipType: "DESCRIBES" | "DEPENDS_ON";
359
+ relatedSpdxElement: string;
360
+ }
361
+ export interface LicenseOptions {
362
+ cwd: string;
363
+ workspace: boolean;
364
+ allow?: string[];
365
+ deny?: string[];
366
+ sbomFile?: string;
367
+ jsonFile?: string;
368
+ diffMode: boolean;
369
+ concurrency: number;
370
+ registryTimeoutMs: number;
371
+ cacheTtlSeconds: number;
372
+ }
373
+ export interface LicenseResult {
374
+ packages: PackageLicense[];
375
+ violations: PackageLicense[];
376
+ totalViolations: number;
377
+ errors: string[];
378
+ warnings: string[];
379
+ }
380
+ export interface SnapshotEntry {
381
+ id: string;
382
+ label: string;
383
+ createdAt: number;
384
+ manifests: Record<string, string>;
385
+ lockfileHashes: Record<string, string>;
386
+ }
387
+ export type SnapshotAction = "save" | "list" | "restore" | "diff";
388
+ export interface SnapshotOptions {
389
+ cwd: string;
390
+ workspace: boolean;
391
+ action: SnapshotAction;
392
+ label?: string;
393
+ snapshotId?: string;
394
+ storeFile?: string;
395
+ }
396
+ export interface SnapshotResult {
397
+ action: SnapshotAction;
398
+ snapshotId?: string;
399
+ label?: string;
400
+ entries?: Array<{
401
+ id: string;
402
+ label: string;
403
+ createdAt: string;
404
+ }>;
405
+ diff?: Array<{
406
+ name: string;
407
+ from: string;
408
+ to: string;
409
+ }>;
410
+ errors: string[];
411
+ warnings: string[];
412
+ }
@@ -0,0 +1,6 @@
1
+ import type { PackageUpdate } from "../types/index.js";
2
+ export declare function VersionDiff({ from, to }: {
3
+ from: string;
4
+ to: string;
5
+ }): import("react/jsx-runtime").JSX.Element;
6
+ export declare function runTui(updates: PackageUpdate[]): Promise<PackageUpdate[]>;
package/dist/ui/tui.js ADDED
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { render, Text, Box, useInput } from "ink";
4
+ // Basic version diff string parser to split major.minor.patch
5
+ export function VersionDiff({ from, to }) {
6
+ if (from === to)
7
+ return _jsx(Text, { color: "gray", children: to });
8
+ // Very simplistic semver coloring: highlight the changed part
9
+ // E.g., from 1.2.3 to 1.3.0 -> 1 is dim, 3.0 is bright green
10
+ return (_jsxs(Box, { children: [_jsxs(Text, { color: "gray", children: [from, " \u2192 "] }), _jsx(Text, { color: "green", children: to })] }));
11
+ }
12
+ function TuiApp({ updates, onComplete }) {
13
+ const [cursorIndex, setCursorIndex] = useState(0);
14
+ const [selectedIndices, setSelectedIndices] = useState(new Set(updates.map((_, i) => i)));
15
+ useInput((input, key) => {
16
+ if (key.upArrow) {
17
+ setCursorIndex((prev) => Math.max(0, prev - 1));
18
+ }
19
+ if (key.downArrow) {
20
+ setCursorIndex((prev) => Math.min(updates.length - 1, prev + 1));
21
+ }
22
+ if (input === " ") {
23
+ setSelectedIndices((prev) => {
24
+ const next = new Set(prev);
25
+ if (next.has(cursorIndex))
26
+ next.delete(cursorIndex);
27
+ else
28
+ next.add(cursorIndex);
29
+ return next;
30
+ });
31
+ }
32
+ if (key.return) {
33
+ const selected = updates.filter((_, i) => selectedIndices.has(i));
34
+ onComplete(selected);
35
+ }
36
+ });
37
+ return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Choose updates to install (Space to toggle, Enter to confirm, Up/Down to navigate)" }), _jsx(Box, { flexDirection: "column", marginTop: 1, children: updates.map((update, index) => {
38
+ const isSelected = selectedIndices.has(index);
39
+ const isFocused = cursorIndex === index;
40
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: isFocused ? "cyan" : "gray", children: [isFocused ? "❯ " : " ", isSelected ? "◉ " : "◯ "] }), _jsx(Box, { width: 30, children: _jsx(Text, { bold: isFocused, children: update.name }) }), _jsx(Box, { width: 15, children: _jsx(Text, { color: "gray", children: update.diffType }) }), _jsx(VersionDiff, { from: update.fromRange, to: update.toVersionResolved })] }, update.name));
41
+ }) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "gray", children: [selectedIndices.size, " of ", updates.length, " selected"] }) })] }));
42
+ }
43
+ export async function runTui(updates) {
44
+ return new Promise((resolve) => {
45
+ const { unmount } = render(_jsx(TuiApp, { updates: updates, onComplete: (selected) => {
46
+ unmount();
47
+ resolve(selected);
48
+ } }));
49
+ });
50
+ }
@@ -12,3 +12,21 @@ export declare function pickTargetVersion(currentRange: string, latestVersion: s
12
12
  export declare function pickTargetVersionFromAvailable(currentRange: string, availableVersions: string[], latestVersion: string, target: TargetLevel): string | null;
13
13
  export declare function applyRangeStyle(previousRange: string, version: string): string;
14
14
  export declare function clampTarget(requested: TargetLevel, maxAllowed?: TargetLevel): TargetLevel;
15
+ /**
16
+ * Checks whether a concrete version satisfies a semver range expression.
17
+ *
18
+ * Handles the common npm range operators used in peerDependencies:
19
+ * exact: "1.2.3" → version must equal
20
+ * ^: "^1.2.3" → major must match, version must be >=
21
+ * ~: "~1.2.3" → major+minor must match, version must be >=
22
+ * >=: ">=1.2.3" → version must be >=
23
+ * <=: "<=1.2.3" → version must be <=
24
+ * >: ">1.2.3" → version must be >
25
+ * <: "<1.2.3" → version must be <
26
+ * *: "*" | "" → always true
27
+ * ranges: ">=1 <3" → all space-separated clauses AND-ed together
28
+ *
29
+ * Does NOT handle || unions or hyphen ranges — those are rare in peerDependencies
30
+ * and degrade gracefully (returns true to avoid false-positive conflicts).
31
+ */
32
+ export declare function satisfies(version: string, range: string): boolean;
@@ -45,13 +45,16 @@ export function pickTargetVersion(currentRange, latestVersion, target) {
45
45
  if (!current || target === "latest")
46
46
  return latestVersion;
47
47
  if (target === "patch") {
48
- if (current.major === latest.major && current.minor === latest.minor && latest.patch > current.patch) {
48
+ if (current.major === latest.major &&
49
+ current.minor === latest.minor &&
50
+ latest.patch > current.patch) {
49
51
  return latestVersion;
50
52
  }
51
53
  return null;
52
54
  }
53
55
  if (target === "minor") {
54
- if (current.major === latest.major && compareVersions(latest, current) > 0) {
56
+ if (current.major === latest.major &&
57
+ compareVersions(latest, current) > 0) {
55
58
  return latestVersion;
56
59
  }
57
60
  return null;
@@ -83,7 +86,8 @@ export function pickTargetVersionFromAvailable(currentRange, availableVersions,
83
86
  return sameMajor.length > 0 ? sameMajor[sameMajor.length - 1].raw : null;
84
87
  }
85
88
  if (target === "patch") {
86
- const sameLine = parsed.filter((item) => item.parsed.major === current.major && item.parsed.minor === current.minor);
89
+ const sameLine = parsed.filter((item) => item.parsed.major === current.major &&
90
+ item.parsed.minor === current.minor);
87
91
  return sameLine.length > 0 ? sameLine[sameLine.length - 1].raw : null;
88
92
  }
89
93
  return latestVersion;
@@ -102,3 +106,84 @@ export function clampTarget(requested, maxAllowed) {
102
106
  return requested;
103
107
  return TARGET_ORDER[Math.min(requestedIndex, allowedIndex)];
104
108
  }
109
+ /**
110
+ * Checks whether a concrete version satisfies a semver range expression.
111
+ *
112
+ * Handles the common npm range operators used in peerDependencies:
113
+ * exact: "1.2.3" → version must equal
114
+ * ^: "^1.2.3" → major must match, version must be >=
115
+ * ~: "~1.2.3" → major+minor must match, version must be >=
116
+ * >=: ">=1.2.3" → version must be >=
117
+ * <=: "<=1.2.3" → version must be <=
118
+ * >: ">1.2.3" → version must be >
119
+ * <: "<1.2.3" → version must be <
120
+ * *: "*" | "" → always true
121
+ * ranges: ">=1 <3" → all space-separated clauses AND-ed together
122
+ *
123
+ * Does NOT handle || unions or hyphen ranges — those are rare in peerDependencies
124
+ * and degrade gracefully (returns true to avoid false-positive conflicts).
125
+ */
126
+ export function satisfies(version, range) {
127
+ const trimmedRange = range.trim();
128
+ if (!trimmedRange || trimmedRange === "*")
129
+ return true;
130
+ const parsed = parseVersion(version);
131
+ if (!parsed)
132
+ return true; // non-semver versions (e.g. "latest", "workspace:*") pass through
133
+ // Handle OR unions — split on " || " and return true if any clause matches
134
+ if (trimmedRange.includes("||")) {
135
+ return trimmedRange
136
+ .split("||")
137
+ .some((clause) => satisfies(version, clause.trim()));
138
+ }
139
+ // Handle AND ranges — split on whitespace and require all clauses to match
140
+ const clauses = trimmedRange.split(/\s+/).filter(Boolean);
141
+ if (clauses.length > 1) {
142
+ return clauses.every((clause) => satisfies(version, clause));
143
+ }
144
+ const clause = clauses[0] ?? "";
145
+ const op = parseRangeOperator(clause);
146
+ if (!op)
147
+ return true;
148
+ const cmp = compareVersions(parsed, op.version);
149
+ switch (op.operator) {
150
+ case "^": {
151
+ // same major, version >= bound
152
+ return parsed.major === op.version.major && cmp >= 0;
153
+ }
154
+ case "~": {
155
+ // same major+minor, version >= bound
156
+ return (parsed.major === op.version.major &&
157
+ parsed.minor === op.version.minor &&
158
+ cmp >= 0);
159
+ }
160
+ case ">=":
161
+ return cmp >= 0;
162
+ case "<=":
163
+ return cmp <= 0;
164
+ case ">":
165
+ return cmp > 0;
166
+ case "<":
167
+ return cmp < 0;
168
+ case "=":
169
+ return cmp === 0;
170
+ default:
171
+ return true;
172
+ }
173
+ }
174
+ function parseRangeOperator(clause) {
175
+ const ops = [">=", "<=", "^", "~", ">", "<", "="];
176
+ for (const op of ops) {
177
+ if (clause.startsWith(op)) {
178
+ const versionStr = clause.slice(op.length);
179
+ const parsed = parseVersion(versionStr);
180
+ if (parsed)
181
+ return { operator: op, version: parsed };
182
+ }
183
+ }
184
+ // Bare version string — treat as exact
185
+ const parsed = parseVersion(clause);
186
+ if (parsed)
187
+ return { operator: "=", version: parsed };
188
+ return null;
189
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rainy-updates/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2-rc.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,
@@ -69,9 +69,16 @@
69
69
  },
70
70
  "devDependencies": {
71
71
  "@types/bun": "latest",
72
+ "@types/react": "^19.2.14",
73
+ "ink-testing-library": "^4.0.0",
72
74
  "typescript": "^5.9.3"
73
75
  },
74
76
  "optionalDependencies": {
75
77
  "undici": "^7.22.0"
78
+ },
79
+ "dependencies": {
80
+ "ink": "^6.8.0",
81
+ "react": "^19.2.4",
82
+ "zod": "^4.3.6"
76
83
  }
77
84
  }