@rainy-updates/cli 0.5.2-rc.2 → 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 +52 -0
- package/README.md +29 -0
- package/dist/bin/cli.js +108 -2
- package/dist/cache/cache.d.ts +1 -0
- package/dist/cache/cache.js +9 -2
- package/dist/commands/audit/runner.js +8 -1
- package/dist/commands/audit/sources/index.js +8 -1
- 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 +57 -0
- package/dist/ui/tui.d.ts +0 -4
- package/dist/ui/tui.js +78 -21
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project are documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.5.2] - 2026-03-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **New `review` command**: Aggregates pending updates with security, peer-conflict, license, health, and unused-dependency signals for guided dependency review.
|
|
10
|
+
- `--interactive` launches the upgraded Ink review TUI.
|
|
11
|
+
- `--security-only`, `--risk <level>`, and `--diff <level>` filter the review set.
|
|
12
|
+
- `--apply-selected` can apply the filtered/selected updates after review.
|
|
13
|
+
- **New `doctor` command**: Produces a fast dependency verdict for local triage and CI summaries.
|
|
14
|
+
- Verdict classes: `safe`, `review`, `blocked`, `actionable`.
|
|
15
|
+
- `--verdict-only` prints a one-line CI-friendly summary.
|
|
16
|
+
- **Interactive review TUI overhaul**:
|
|
17
|
+
- multi-pane layout with filters, selection state, detail panel, and status bar,
|
|
18
|
+
- risk/security/peer/license context inline per package,
|
|
19
|
+
- explicit selection controls for interactive upgrade review.
|
|
20
|
+
- **Additive output contract metadata**:
|
|
21
|
+
- summary fields: `verdict`, `riskPackages`, `securityPackages`, `peerConflictPackages`, `licenseViolationPackages`, `interactiveSession`,
|
|
22
|
+
- GitHub outputs: `verdict`, `risk_packages`, `security_packages`, `peer_conflict_packages`, `license_violation_packages`,
|
|
23
|
+
- SARIF result properties for impact/risk/advisory/license context.
|
|
24
|
+
- **New display controls**:
|
|
25
|
+
- `--interactive`
|
|
26
|
+
- `--show-impact`
|
|
27
|
+
- `--show-homepage`
|
|
28
|
+
- **RC3 hardening layer on top of GA surfaces**:
|
|
29
|
+
- centralized classified error taxonomy in `src/core/errors.ts`,
|
|
30
|
+
- compatibility coverage for scoped private registries and cache backend fallback,
|
|
31
|
+
- explicit cache fallback reason reporting for SQLite → file cache degradation,
|
|
32
|
+
- dedicated performance scenarios for `check`, `resolve`, and `ci`,
|
|
33
|
+
- new comparison document: `docs/why-rainy-vs-dependabot-renovate.md`.
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- `check` now computes impact scores in the core pipeline and carries homepage metadata when available.
|
|
38
|
+
- `upgrade --interactive` now routes through the guided review flow before applying selected updates.
|
|
39
|
+
- CLI help and package exports now cover the new review/verdict surfaces.
|
|
40
|
+
- `doctor` output now follows the RC3 quick-verdict shape:
|
|
41
|
+
- `State`
|
|
42
|
+
- `PrimaryRisk`
|
|
43
|
+
- `NextAction`
|
|
44
|
+
- `check`, `warm-cache`, and `audit` now emit RC3-style classified warnings/errors for:
|
|
45
|
+
- registry failures,
|
|
46
|
+
- auth failures,
|
|
47
|
+
- advisory-source degradation/outage,
|
|
48
|
+
- cache backend fallback.
|
|
49
|
+
|
|
50
|
+
### Tests
|
|
51
|
+
|
|
52
|
+
- Added compatibility tests for:
|
|
53
|
+
- scoped `.npmrc` private registry resolution,
|
|
54
|
+
- forced cache backend fallback behavior.
|
|
55
|
+
- Updated audit coverage to assert the new classified advisory degradation warning format.
|
|
56
|
+
|
|
5
57
|
## [0.5.2-rc.2] - 2026-02-27
|
|
6
58
|
|
|
7
59
|
### Added
|
package/README.md
CHANGED
|
@@ -4,6 +4,9 @@ The fastest DevOps-first dependency CLI. Checks, audits, upgrades, bisects, and
|
|
|
4
4
|
|
|
5
5
|
`@rainy-updates/cli` is built for teams that need fast dependency intelligence, security auditing, policy-aware upgrades, and automation-ready output for CI/CD and pull request workflows.
|
|
6
6
|
|
|
7
|
+
Comparison:
|
|
8
|
+
[Why Rainy vs Dependabot and Renovate](./docs/why-rainy-vs-dependabot-renovate.md)
|
|
9
|
+
|
|
7
10
|
## Why this package
|
|
8
11
|
|
|
9
12
|
- Detects updates quickly across single-package repos and workspaces.
|
|
@@ -56,6 +59,8 @@ npx @rainy-updates/cli ci --workspace --mode strict
|
|
|
56
59
|
- `ci` — run CI-focused dependency automation (warm cache, check/upgrade, policy gates)
|
|
57
60
|
- `warm-cache` — prefetch package metadata for fast and offline checks
|
|
58
61
|
- `baseline` — save and compare dependency baseline snapshots
|
|
62
|
+
- `review` — guided review across updates, security, peer conflicts, licenses, and risk
|
|
63
|
+
- `doctor` — fast verdict command for local triage and CI summaries
|
|
59
64
|
|
|
60
65
|
### Security & health (_new in v0.5.1_)
|
|
61
66
|
|
|
@@ -117,6 +122,15 @@ npx @rainy-updates/cli bisect axios --cmd "bun test"
|
|
|
117
122
|
npx @rainy-updates/cli bisect react --range "18.0.0..19.0.0" --cmd "npm test"
|
|
118
123
|
npx @rainy-updates/cli bisect lodash --cmd "npm run test:unit" --dry-run
|
|
119
124
|
rup bisect axios --cmd "bun test" # if installed
|
|
125
|
+
|
|
126
|
+
# 11) Review updates with risk and security context ── NEW in v0.5.2 GA
|
|
127
|
+
npx @rainy-updates/cli review --security-only
|
|
128
|
+
rup review --interactive
|
|
129
|
+
rup review --risk high --diff major
|
|
130
|
+
|
|
131
|
+
# 12) Get a fast dependency verdict for CI or local triage ── NEW in v0.5.2 GA
|
|
132
|
+
npx @rainy-updates/cli doctor
|
|
133
|
+
rup doctor --verdict-only
|
|
120
134
|
```
|
|
121
135
|
|
|
122
136
|
## What it does in production
|
|
@@ -129,6 +143,7 @@ rup bisect axios --cmd "bun test" # if installed
|
|
|
129
143
|
- Supports explicit registry retry/timeout tuning (`--registry-retries`, `--registry-timeout-ms`).
|
|
130
144
|
- Supports stale-cache fallback when registry calls fail.
|
|
131
145
|
- Supports streamed progress output for long CI runs (`--stream`).
|
|
146
|
+
- Exposes impact/risk metadata and homepage context in update output (`--show-impact`, `--show-homepage`).
|
|
132
147
|
|
|
133
148
|
### Workspace support
|
|
134
149
|
|
|
@@ -229,6 +244,9 @@ Schedule:
|
|
|
229
244
|
- `--cooldown-days <n>`
|
|
230
245
|
- `--pr-limit <n>`
|
|
231
246
|
- `--only-changed`
|
|
247
|
+
- `--interactive`
|
|
248
|
+
- `--show-impact`
|
|
249
|
+
- `--show-homepage`
|
|
232
250
|
- `--mode minimal|strict|enterprise` (for `ci`)
|
|
233
251
|
- `--fix-pr-batch-size <n>` (for batched fix branches in `ci`)
|
|
234
252
|
- `--policy-file <path>`
|
|
@@ -251,6 +269,17 @@ Schedule:
|
|
|
251
269
|
- `--pm auto|npm|pnpm`
|
|
252
270
|
- `--sync`
|
|
253
271
|
|
|
272
|
+
### Review-only
|
|
273
|
+
|
|
274
|
+
- `--security-only`
|
|
275
|
+
- `--risk critical|high|medium|low`
|
|
276
|
+
- `--diff patch|minor|major|latest`
|
|
277
|
+
- `--apply-selected`
|
|
278
|
+
|
|
279
|
+
### Doctor-only
|
|
280
|
+
|
|
281
|
+
- `--verdict-only`
|
|
282
|
+
|
|
254
283
|
### Baseline-only
|
|
255
284
|
|
|
256
285
|
- `--save`
|
package/dist/bin/cli.js
CHANGED
|
@@ -111,6 +111,39 @@ async function main() {
|
|
|
111
111
|
process.exitCode = result.errors.length > 0 ? 1 : 0;
|
|
112
112
|
return;
|
|
113
113
|
}
|
|
114
|
+
if (parsed.command === "review") {
|
|
115
|
+
const { runReview } = await import("../commands/review/runner.js");
|
|
116
|
+
const result = await runReview(parsed.options);
|
|
117
|
+
process.exitCode =
|
|
118
|
+
result.summary.verdict === "blocked" ||
|
|
119
|
+
result.summary.verdict === "actionable" ||
|
|
120
|
+
result.summary.verdict === "review"
|
|
121
|
+
? 1
|
|
122
|
+
: 0;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (parsed.command === "doctor") {
|
|
126
|
+
const { runDoctor } = await import("../commands/doctor/runner.js");
|
|
127
|
+
const result = await runDoctor(parsed.options);
|
|
128
|
+
process.exitCode = result.verdict === "safe" ? 0 : 1;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (parsed.options.interactive &&
|
|
132
|
+
(parsed.command === "check" ||
|
|
133
|
+
parsed.command === "upgrade" ||
|
|
134
|
+
parsed.command === "ci")) {
|
|
135
|
+
const { runReview } = await import("../commands/review/runner.js");
|
|
136
|
+
const result = await runReview({
|
|
137
|
+
...parsed.options,
|
|
138
|
+
securityOnly: false,
|
|
139
|
+
risk: undefined,
|
|
140
|
+
diff: undefined,
|
|
141
|
+
applySelected: parsed.command === "upgrade",
|
|
142
|
+
});
|
|
143
|
+
process.exitCode =
|
|
144
|
+
result.summary.verdict === "safe" && result.updates.length === 0 ? 0 : 1;
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
114
147
|
const result = await runCommand(parsed);
|
|
115
148
|
if (parsed.options.fixPr &&
|
|
116
149
|
(parsed.command === "check" ||
|
|
@@ -148,11 +181,17 @@ async function main() {
|
|
|
148
181
|
}
|
|
149
182
|
result.summary.failReason = resolveFailReason(result.updates, result.errors, parsed.options.failOn, parsed.options.maxUpdates, parsed.options.ci);
|
|
150
183
|
const renderStartedAt = Date.now();
|
|
151
|
-
let rendered = renderResult(result, parsed.options.format
|
|
184
|
+
let rendered = renderResult(result, parsed.options.format, {
|
|
185
|
+
showImpact: parsed.options.showImpact,
|
|
186
|
+
showHomepage: parsed.options.showHomepage,
|
|
187
|
+
});
|
|
152
188
|
result.summary.durationMs.render = Math.max(0, Date.now() - renderStartedAt);
|
|
153
189
|
if (parsed.options.format === "json" ||
|
|
154
190
|
parsed.options.format === "metrics") {
|
|
155
|
-
rendered = renderResult(result, parsed.options.format
|
|
191
|
+
rendered = renderResult(result, parsed.options.format, {
|
|
192
|
+
showImpact: parsed.options.showImpact,
|
|
193
|
+
showHomepage: parsed.options.showHomepage,
|
|
194
|
+
});
|
|
156
195
|
}
|
|
157
196
|
if (parsed.options.onlyChanged &&
|
|
158
197
|
result.updates.length === 0 &&
|
|
@@ -220,6 +259,9 @@ Options:
|
|
|
220
259
|
--cooldown-days <n>
|
|
221
260
|
--pr-limit <n>
|
|
222
261
|
--only-changed
|
|
262
|
+
--interactive
|
|
263
|
+
--show-impact
|
|
264
|
+
--show-homepage
|
|
223
265
|
--lockfile-mode preserve|update|error
|
|
224
266
|
--log-level error|warn|info|debug
|
|
225
267
|
--ci`;
|
|
@@ -267,6 +309,7 @@ Options:
|
|
|
267
309
|
--fix-dry-run
|
|
268
310
|
--fix-pr-no-checkout
|
|
269
311
|
--fix-pr-batch-size <n>
|
|
312
|
+
--interactive
|
|
270
313
|
--lockfile-mode preserve|update|error
|
|
271
314
|
--no-pr-report
|
|
272
315
|
--json-file <path>
|
|
@@ -349,6 +392,34 @@ Options:
|
|
|
349
392
|
--json-file <path>
|
|
350
393
|
--concurrency <n>
|
|
351
394
|
--registry-timeout-ms <n>`;
|
|
395
|
+
}
|
|
396
|
+
if (isCommand && command === "review") {
|
|
397
|
+
return `rainy-updates review [options]
|
|
398
|
+
|
|
399
|
+
Review updates with risk, security, peer, and policy context.
|
|
400
|
+
|
|
401
|
+
Options:
|
|
402
|
+
--workspace
|
|
403
|
+
--interactive
|
|
404
|
+
--security-only
|
|
405
|
+
--risk critical|high|medium|low
|
|
406
|
+
--diff patch|minor|major|latest
|
|
407
|
+
--apply-selected
|
|
408
|
+
--policy-file <path>
|
|
409
|
+
--json-file <path>
|
|
410
|
+
--concurrency <n>
|
|
411
|
+
--registry-timeout-ms <n>
|
|
412
|
+
--registry-retries <n>`;
|
|
413
|
+
}
|
|
414
|
+
if (isCommand && command === "doctor") {
|
|
415
|
+
return `rainy-updates doctor [options]
|
|
416
|
+
|
|
417
|
+
Produce a fast overall dependency verdict.
|
|
418
|
+
|
|
419
|
+
Options:
|
|
420
|
+
--workspace
|
|
421
|
+
--verdict-only
|
|
422
|
+
--json-file <path>`;
|
|
352
423
|
}
|
|
353
424
|
return `rainy-updates (rup / rainy-up) <command> [options]
|
|
354
425
|
|
|
@@ -366,6 +437,8 @@ Commands:
|
|
|
366
437
|
resolve Check peer dependency conflicts (pure-TS, no subprocess)
|
|
367
438
|
licenses Scan dependency licenses and generate SPDX SBOM
|
|
368
439
|
snapshot Save, list, restore, and diff dependency state snapshots
|
|
440
|
+
review Guided dependency review with risk/security context
|
|
441
|
+
doctor Fast dependency verdict for local or CI use
|
|
369
442
|
|
|
370
443
|
Global options:
|
|
371
444
|
--cwd <path>
|
|
@@ -384,6 +457,9 @@ Global options:
|
|
|
384
457
|
--cooldown-days <n>
|
|
385
458
|
--pr-limit <n>
|
|
386
459
|
--only-changed
|
|
460
|
+
--interactive
|
|
461
|
+
--show-impact
|
|
462
|
+
--show-homepage
|
|
387
463
|
--mode minimal|strict|enterprise
|
|
388
464
|
--fix-pr
|
|
389
465
|
--fix-branch <name>
|
|
@@ -405,6 +481,36 @@ Global options:
|
|
|
405
481
|
--version, -v`;
|
|
406
482
|
}
|
|
407
483
|
async function runCommand(parsed) {
|
|
484
|
+
if (parsed.command === "review") {
|
|
485
|
+
const { runReview } = await import("../commands/review/runner.js");
|
|
486
|
+
const result = await runReview(parsed.options);
|
|
487
|
+
return {
|
|
488
|
+
projectPath: result.projectPath,
|
|
489
|
+
packagePaths: result.items.map((item) => item.update.packagePath),
|
|
490
|
+
packageManager: "unknown",
|
|
491
|
+
target: result.target,
|
|
492
|
+
timestamp: new Date().toISOString(),
|
|
493
|
+
summary: result.summary,
|
|
494
|
+
updates: result.updates,
|
|
495
|
+
errors: result.errors,
|
|
496
|
+
warnings: result.warnings,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (parsed.command === "doctor") {
|
|
500
|
+
const { runDoctor } = await import("../commands/doctor/runner.js");
|
|
501
|
+
const result = await runDoctor(parsed.options);
|
|
502
|
+
return {
|
|
503
|
+
projectPath: result.review.projectPath,
|
|
504
|
+
packagePaths: result.review.items.map((item) => item.update.packagePath),
|
|
505
|
+
packageManager: "unknown",
|
|
506
|
+
target: result.review.target,
|
|
507
|
+
timestamp: new Date().toISOString(),
|
|
508
|
+
summary: result.summary,
|
|
509
|
+
updates: result.review.updates,
|
|
510
|
+
errors: result.review.errors,
|
|
511
|
+
warnings: result.review.warnings,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
408
514
|
if (parsed.command === "upgrade") {
|
|
409
515
|
return await upgrade(parsed.options);
|
|
410
516
|
}
|
package/dist/cache/cache.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export declare class VersionCache {
|
|
|
3
3
|
private readonly store;
|
|
4
4
|
readonly backend: "sqlite" | "file";
|
|
5
5
|
readonly degraded: boolean;
|
|
6
|
+
readonly fallbackReason?: string;
|
|
6
7
|
private constructor();
|
|
7
8
|
static create(customPath?: string): Promise<VersionCache>;
|
|
8
9
|
getValid(packageName: string, target: TargetLevel): Promise<CachedVersion | null>;
|
package/dist/cache/cache.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { promises as fs } from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
4
5
|
class FileCacheStore {
|
|
5
6
|
filePath;
|
|
6
7
|
constructor(filePath) {
|
|
@@ -108,20 +109,26 @@ export class VersionCache {
|
|
|
108
109
|
store;
|
|
109
110
|
backend;
|
|
110
111
|
degraded;
|
|
111
|
-
|
|
112
|
+
fallbackReason;
|
|
113
|
+
constructor(store, backend, degraded, fallbackReason) {
|
|
112
114
|
this.store = store;
|
|
113
115
|
this.backend = backend;
|
|
114
116
|
this.degraded = degraded;
|
|
117
|
+
this.fallbackReason = fallbackReason;
|
|
115
118
|
}
|
|
116
119
|
static async create(customPath) {
|
|
117
120
|
const basePath = customPath ?? path.join(os.homedir(), ".cache", "rainy-updates");
|
|
121
|
+
if (process.env.RAINY_UPDATES_CACHE_BACKEND === "file") {
|
|
122
|
+
const jsonPath = path.join(basePath, "cache.json");
|
|
123
|
+
return new VersionCache(new FileCacheStore(jsonPath), "file", true, "forced via RAINY_UPDATES_CACHE_BACKEND=file");
|
|
124
|
+
}
|
|
118
125
|
const sqlitePath = path.join(basePath, "cache.db");
|
|
119
126
|
const sqliteStore = await tryCreateSqliteStore(sqlitePath);
|
|
120
127
|
if (sqliteStore)
|
|
121
128
|
return new VersionCache(sqliteStore, "sqlite", false);
|
|
122
129
|
const jsonPath = path.join(basePath, "cache.json");
|
|
123
130
|
const degraded = typeof Bun !== "undefined";
|
|
124
|
-
return new VersionCache(new FileCacheStore(jsonPath), "file", degraded);
|
|
131
|
+
return new VersionCache(new FileCacheStore(jsonPath), "file", degraded, degraded ? "bun:sqlite unavailable; using file cache backend" : undefined);
|
|
125
132
|
}
|
|
126
133
|
async getValid(packageName, target) {
|
|
127
134
|
const entry = await this.store.get(packageName, target);
|
|
@@ -8,6 +8,7 @@ import { stableStringify } from "../../utils/stable-json.js";
|
|
|
8
8
|
import { fetchAdvisories } from "./fetcher.js";
|
|
9
9
|
import { resolveAuditTargets } from "./targets.js";
|
|
10
10
|
import { filterBySeverity, buildPatchMap, renderAuditSourceHealth, renderAuditSummary, renderAuditTable, summarizeAdvisories, } from "./mapper.js";
|
|
11
|
+
import { formatClassifiedMessage } from "../../core/errors.js";
|
|
11
12
|
/**
|
|
12
13
|
* Entry point for `rup audit`. Lazy-loaded by cli.ts.
|
|
13
14
|
* Discovers packages, fetches CVE advisories, filters by severity, and
|
|
@@ -63,7 +64,13 @@ export async function runAudit(options) {
|
|
|
63
64
|
result.sourceHealth = fetched.sourceHealth;
|
|
64
65
|
result.warnings.push(...fetched.warnings);
|
|
65
66
|
if (fetched.sourceHealth.every((item) => item.status === "failed")) {
|
|
66
|
-
result.errors.push(
|
|
67
|
+
result.errors.push(formatClassifiedMessage({
|
|
68
|
+
code: "ADVISORY_SOURCE_DOWN",
|
|
69
|
+
whatFailed: "All advisory sources failed.",
|
|
70
|
+
intact: "Dependency target resolution completed, but no advisory coverage was returned.",
|
|
71
|
+
validity: "invalid",
|
|
72
|
+
next: "Retry `rup audit` later or select a single healthy source with --source.",
|
|
73
|
+
}));
|
|
67
74
|
}
|
|
68
75
|
let advisories = fetched.advisories;
|
|
69
76
|
advisories = filterBySeverity(advisories, options.severity);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { compareVersions, parseVersion } from "../../../utils/semver.js";
|
|
2
2
|
import { githubAuditSource } from "./github.js";
|
|
3
3
|
import { osvAuditSource } from "./osv.js";
|
|
4
|
+
import { formatClassifiedMessage } from "../../../core/errors.js";
|
|
4
5
|
const SOURCE_MAP = {
|
|
5
6
|
osv: osvAuditSource,
|
|
6
7
|
github: githubAuditSource,
|
|
@@ -62,7 +63,13 @@ function normalizeSourceWarnings(warnings, sourceHealth) {
|
|
|
62
63
|
const successfulNames = successful
|
|
63
64
|
.map((item) => formatSourceName(item.source))
|
|
64
65
|
.join(", ");
|
|
65
|
-
normalized.push(
|
|
66
|
+
normalized.push(formatClassifiedMessage({
|
|
67
|
+
code: "ADVISORY_SOURCE_DEGRADED",
|
|
68
|
+
whatFailed: `${failedNames} advisory source(s) failed during the audit query.`,
|
|
69
|
+
intact: `${successfulNames} still returned advisory results.`,
|
|
70
|
+
validity: "partial",
|
|
71
|
+
next: "Retry `rup audit` later or pin `--source` to a healthy backend.",
|
|
72
|
+
}));
|
|
66
73
|
}
|
|
67
74
|
return normalized;
|
|
68
75
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import process from "node:process";
|
|
3
|
+
export function parseDoctorArgs(args) {
|
|
4
|
+
const options = {
|
|
5
|
+
cwd: process.cwd(),
|
|
6
|
+
target: "latest",
|
|
7
|
+
filter: undefined,
|
|
8
|
+
reject: undefined,
|
|
9
|
+
cacheTtlSeconds: 3600,
|
|
10
|
+
includeKinds: ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"],
|
|
11
|
+
ci: false,
|
|
12
|
+
format: "table",
|
|
13
|
+
workspace: false,
|
|
14
|
+
jsonFile: undefined,
|
|
15
|
+
githubOutputFile: undefined,
|
|
16
|
+
sarifFile: undefined,
|
|
17
|
+
concurrency: 16,
|
|
18
|
+
registryTimeoutMs: 8000,
|
|
19
|
+
registryRetries: 3,
|
|
20
|
+
offline: false,
|
|
21
|
+
stream: false,
|
|
22
|
+
policyFile: undefined,
|
|
23
|
+
prReportFile: undefined,
|
|
24
|
+
failOn: "none",
|
|
25
|
+
maxUpdates: undefined,
|
|
26
|
+
fixPr: false,
|
|
27
|
+
fixBranch: "chore/rainy-updates",
|
|
28
|
+
fixCommitMessage: undefined,
|
|
29
|
+
fixDryRun: false,
|
|
30
|
+
fixPrNoCheckout: false,
|
|
31
|
+
fixPrBatchSize: undefined,
|
|
32
|
+
noPrReport: false,
|
|
33
|
+
logLevel: "info",
|
|
34
|
+
groupBy: "risk",
|
|
35
|
+
groupMax: undefined,
|
|
36
|
+
cooldownDays: undefined,
|
|
37
|
+
prLimit: undefined,
|
|
38
|
+
onlyChanged: false,
|
|
39
|
+
ciProfile: "minimal",
|
|
40
|
+
lockfileMode: "preserve",
|
|
41
|
+
interactive: false,
|
|
42
|
+
showImpact: true,
|
|
43
|
+
showHomepage: true,
|
|
44
|
+
verdictOnly: false,
|
|
45
|
+
};
|
|
46
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
47
|
+
const current = args[i];
|
|
48
|
+
const next = args[i + 1];
|
|
49
|
+
if (current === "--cwd" && next) {
|
|
50
|
+
options.cwd = path.resolve(next);
|
|
51
|
+
i += 1;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (current === "--cwd")
|
|
55
|
+
throw new Error("Missing value for --cwd");
|
|
56
|
+
if (current === "--workspace") {
|
|
57
|
+
options.workspace = true;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
if (current === "--verdict-only") {
|
|
61
|
+
options.verdictOnly = true;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
if (current === "--json-file" && next) {
|
|
65
|
+
options.jsonFile = path.resolve(options.cwd, next);
|
|
66
|
+
i += 1;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (current === "--json-file")
|
|
70
|
+
throw new Error("Missing value for --json-file");
|
|
71
|
+
if (current === "--help" || current === "-h") {
|
|
72
|
+
process.stdout.write(DOCTOR_HELP);
|
|
73
|
+
process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
if (current.startsWith("-"))
|
|
76
|
+
throw new Error(`Unknown doctor option: ${current}`);
|
|
77
|
+
throw new Error(`Unexpected doctor argument: ${current}`);
|
|
78
|
+
}
|
|
79
|
+
return options;
|
|
80
|
+
}
|
|
81
|
+
const DOCTOR_HELP = `
|
|
82
|
+
rup doctor — Fast dependency verdict across updates, security, policy, and peer conflicts
|
|
83
|
+
|
|
84
|
+
Usage:
|
|
85
|
+
rup doctor [options]
|
|
86
|
+
|
|
87
|
+
Options:
|
|
88
|
+
--verdict-only Print one-line verdict for CI summaries
|
|
89
|
+
--workspace Scan all workspace packages
|
|
90
|
+
--json-file <path> Write JSON doctor report to file
|
|
91
|
+
--cwd <path>
|
|
92
|
+
`.trimStart();
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { buildReviewResult, createDoctorResult, renderDoctorResult } from "../../core/review-model.js";
|
|
3
|
+
import { stableStringify } from "../../utils/stable-json.js";
|
|
4
|
+
import { writeFileAtomic } from "../../utils/io.js";
|
|
5
|
+
export async function runDoctor(options) {
|
|
6
|
+
const review = await buildReviewResult(options);
|
|
7
|
+
const doctor = createDoctorResult(review);
|
|
8
|
+
process.stdout.write(renderDoctorResult(doctor, options.verdictOnly) + "\n");
|
|
9
|
+
if (options.jsonFile) {
|
|
10
|
+
await writeFileAtomic(options.jsonFile, stableStringify(doctor, 2) + "\n");
|
|
11
|
+
}
|
|
12
|
+
return doctor;
|
|
13
|
+
}
|
|
@@ -93,6 +93,9 @@ async function fetchProposedVersions(options) {
|
|
|
93
93
|
onlyChanged: false,
|
|
94
94
|
ciProfile: "minimal",
|
|
95
95
|
lockfileMode: "preserve",
|
|
96
|
+
interactive: false,
|
|
97
|
+
showImpact: false,
|
|
98
|
+
showHomepage: false,
|
|
96
99
|
});
|
|
97
100
|
for (const update of checkResult.updates ?? []) {
|
|
98
101
|
overrides.set(update.name, update.toVersionResolved);
|