@rainy-updates/cli 0.5.2-rc.2 → 0.5.3

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 (58) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/README.md +105 -22
  3. package/dist/bin/cli.js +124 -9
  4. package/dist/cache/cache.d.ts +1 -0
  5. package/dist/cache/cache.js +9 -2
  6. package/dist/commands/audit/runner.js +8 -1
  7. package/dist/commands/audit/sources/index.js +8 -1
  8. package/dist/commands/doctor/parser.d.ts +2 -0
  9. package/dist/commands/doctor/parser.js +92 -0
  10. package/dist/commands/doctor/runner.d.ts +2 -0
  11. package/dist/commands/doctor/runner.js +13 -0
  12. package/dist/commands/resolve/runner.js +3 -0
  13. package/dist/commands/review/parser.d.ts +2 -0
  14. package/dist/commands/review/parser.js +174 -0
  15. package/dist/commands/review/runner.d.ts +2 -0
  16. package/dist/commands/review/runner.js +30 -0
  17. package/dist/config/loader.d.ts +3 -0
  18. package/dist/core/check.js +64 -5
  19. package/dist/core/errors.d.ts +11 -0
  20. package/dist/core/errors.js +6 -0
  21. package/dist/core/options.d.ts +8 -1
  22. package/dist/core/options.js +43 -0
  23. package/dist/core/review-model.d.ts +5 -0
  24. package/dist/core/review-model.js +372 -0
  25. package/dist/core/summary.js +11 -2
  26. package/dist/core/upgrade.d.ts +1 -0
  27. package/dist/core/upgrade.js +27 -21
  28. package/dist/core/warm-cache.js +28 -4
  29. package/dist/index.d.ts +3 -1
  30. package/dist/index.js +2 -0
  31. package/dist/output/format.d.ts +4 -1
  32. package/dist/output/format.js +41 -3
  33. package/dist/output/github.js +6 -1
  34. package/dist/output/sarif.js +14 -0
  35. package/dist/registry/npm.d.ts +22 -0
  36. package/dist/registry/npm.js +33 -4
  37. package/dist/risk/index.d.ts +3 -0
  38. package/dist/risk/index.js +24 -0
  39. package/dist/risk/scorer.d.ts +3 -0
  40. package/dist/risk/scorer.js +114 -0
  41. package/dist/risk/signals/fresh-package.d.ts +3 -0
  42. package/dist/risk/signals/fresh-package.js +22 -0
  43. package/dist/risk/signals/install-scripts.d.ts +3 -0
  44. package/dist/risk/signals/install-scripts.js +10 -0
  45. package/dist/risk/signals/maintainer-churn.d.ts +3 -0
  46. package/dist/risk/signals/maintainer-churn.js +11 -0
  47. package/dist/risk/signals/metadata.d.ts +3 -0
  48. package/dist/risk/signals/metadata.js +18 -0
  49. package/dist/risk/signals/mutable-source.d.ts +3 -0
  50. package/dist/risk/signals/mutable-source.js +24 -0
  51. package/dist/risk/signals/typosquat.d.ts +3 -0
  52. package/dist/risk/signals/typosquat.js +70 -0
  53. package/dist/risk/types.d.ts +15 -0
  54. package/dist/risk/types.js +1 -0
  55. package/dist/types/index.d.ts +85 -0
  56. package/dist/ui/tui.d.ts +0 -4
  57. package/dist/ui/tui.js +103 -21
  58. package/package.json +10 -2
@@ -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 the 3-line quick verdict without counts
89
+ --workspace Scan all workspace packages
90
+ --json-file <path> Write JSON doctor report to file
91
+ --cwd <path>
92
+ `.trimStart();
@@ -0,0 +1,2 @@
1
+ import type { DoctorOptions, DoctorResult } from "../../types/index.js";
2
+ export declare function runDoctor(options: DoctorOptions): Promise<DoctorResult>;
@@ -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);
@@ -0,0 +1,2 @@
1
+ import type { ReviewOptions } from "../../types/index.js";
2
+ export declare function parseReviewArgs(args: string[]): ReviewOptions;
@@ -0,0 +1,174 @@
1
+ import path from "node:path";
2
+ import process from "node:process";
3
+ import { ensureRiskLevel } from "../../core/options.js";
4
+ export function parseReviewArgs(args) {
5
+ const options = {
6
+ cwd: process.cwd(),
7
+ target: "latest",
8
+ filter: undefined,
9
+ reject: undefined,
10
+ cacheTtlSeconds: 3600,
11
+ includeKinds: ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"],
12
+ ci: false,
13
+ format: "table",
14
+ workspace: false,
15
+ jsonFile: undefined,
16
+ githubOutputFile: undefined,
17
+ sarifFile: undefined,
18
+ concurrency: 16,
19
+ registryTimeoutMs: 8000,
20
+ registryRetries: 3,
21
+ offline: false,
22
+ stream: false,
23
+ policyFile: undefined,
24
+ prReportFile: undefined,
25
+ failOn: "none",
26
+ maxUpdates: undefined,
27
+ fixPr: false,
28
+ fixBranch: "chore/rainy-updates",
29
+ fixCommitMessage: undefined,
30
+ fixDryRun: false,
31
+ fixPrNoCheckout: false,
32
+ fixPrBatchSize: undefined,
33
+ noPrReport: false,
34
+ logLevel: "info",
35
+ groupBy: "risk",
36
+ groupMax: undefined,
37
+ cooldownDays: undefined,
38
+ prLimit: undefined,
39
+ onlyChanged: false,
40
+ ciProfile: "minimal",
41
+ lockfileMode: "preserve",
42
+ interactive: false,
43
+ showImpact: true,
44
+ showHomepage: true,
45
+ securityOnly: false,
46
+ risk: undefined,
47
+ diff: undefined,
48
+ applySelected: false,
49
+ };
50
+ for (let i = 0; i < args.length; i += 1) {
51
+ const current = args[i];
52
+ const next = args[i + 1];
53
+ if (current === "--cwd" && next) {
54
+ options.cwd = path.resolve(next);
55
+ i += 1;
56
+ continue;
57
+ }
58
+ if (current === "--cwd")
59
+ throw new Error("Missing value for --cwd");
60
+ if (current === "--workspace") {
61
+ options.workspace = true;
62
+ continue;
63
+ }
64
+ if (current === "--interactive") {
65
+ options.interactive = true;
66
+ continue;
67
+ }
68
+ if (current === "--security-only") {
69
+ options.securityOnly = true;
70
+ continue;
71
+ }
72
+ if (current === "--risk" && next) {
73
+ options.risk = ensureRiskLevel(next);
74
+ i += 1;
75
+ continue;
76
+ }
77
+ if (current === "--risk")
78
+ throw new Error("Missing value for --risk");
79
+ if (current === "--diff" && next) {
80
+ if (next === "patch" ||
81
+ next === "minor" ||
82
+ next === "major" ||
83
+ next === "latest") {
84
+ options.diff = next;
85
+ i += 1;
86
+ continue;
87
+ }
88
+ throw new Error("--diff must be patch, minor, major or latest");
89
+ }
90
+ if (current === "--diff")
91
+ throw new Error("Missing value for --diff");
92
+ if (current === "--apply-selected") {
93
+ options.applySelected = true;
94
+ continue;
95
+ }
96
+ if (current === "--json-file" && next) {
97
+ options.jsonFile = path.resolve(options.cwd, next);
98
+ i += 1;
99
+ continue;
100
+ }
101
+ if (current === "--json-file")
102
+ throw new Error("Missing value for --json-file");
103
+ if (current === "--policy-file" && next) {
104
+ options.policyFile = path.resolve(options.cwd, next);
105
+ i += 1;
106
+ continue;
107
+ }
108
+ if (current === "--policy-file")
109
+ throw new Error("Missing value for --policy-file");
110
+ if (current === "--concurrency" && next) {
111
+ const parsed = Number(next);
112
+ if (!Number.isInteger(parsed) || parsed <= 0) {
113
+ throw new Error("--concurrency must be a positive integer");
114
+ }
115
+ options.concurrency = parsed;
116
+ i += 1;
117
+ continue;
118
+ }
119
+ if (current === "--concurrency")
120
+ throw new Error("Missing value for --concurrency");
121
+ if (current === "--registry-timeout-ms" && next) {
122
+ const parsed = Number(next);
123
+ if (!Number.isInteger(parsed) || parsed <= 0) {
124
+ throw new Error("--registry-timeout-ms must be a positive integer");
125
+ }
126
+ options.registryTimeoutMs = parsed;
127
+ i += 1;
128
+ continue;
129
+ }
130
+ if (current === "--registry-timeout-ms") {
131
+ throw new Error("Missing value for --registry-timeout-ms");
132
+ }
133
+ if (current === "--registry-retries" && next) {
134
+ const parsed = Number(next);
135
+ if (!Number.isInteger(parsed) || parsed <= 0) {
136
+ throw new Error("--registry-retries must be a positive integer");
137
+ }
138
+ options.registryRetries = parsed;
139
+ i += 1;
140
+ continue;
141
+ }
142
+ if (current === "--registry-retries") {
143
+ throw new Error("Missing value for --registry-retries");
144
+ }
145
+ if (current === "--help" || current === "-h") {
146
+ process.stdout.write(REVIEW_HELP);
147
+ process.exit(0);
148
+ }
149
+ if (current.startsWith("-"))
150
+ throw new Error(`Unknown review option: ${current}`);
151
+ throw new Error(`Unexpected review argument: ${current}`);
152
+ }
153
+ return options;
154
+ }
155
+ const REVIEW_HELP = `
156
+ rup review — Guided dependency review across updates, security, peer conflicts, and policy
157
+
158
+ Usage:
159
+ rup review [options]
160
+
161
+ Options:
162
+ --interactive Launch the interactive review TUI
163
+ --security-only Show only packages with advisories
164
+ --risk <level> Minimum risk: critical, high, medium, low
165
+ --diff <level> Filter by patch, minor, major, latest
166
+ --apply-selected Apply all filtered updates after review
167
+ --workspace Scan all workspace packages
168
+ --policy-file <path> Load policy overrides
169
+ --json-file <path> Write JSON review report to file
170
+ --registry-timeout-ms <n>
171
+ --registry-retries <n>
172
+ --concurrency <n>
173
+ --cwd <path>
174
+ `.trimStart();
@@ -0,0 +1,2 @@
1
+ import type { ReviewOptions, ReviewResult } from "../../types/index.js";
2
+ export declare function runReview(options: ReviewOptions): Promise<ReviewResult>;
@@ -0,0 +1,30 @@
1
+ import process from "node:process";
2
+ import { runTui } from "../../ui/tui.js";
3
+ import { buildReviewResult, renderReviewResult } from "../../core/review-model.js";
4
+ import { applySelectedUpdates } from "../../core/upgrade.js";
5
+ import { stableStringify } from "../../utils/stable-json.js";
6
+ import { writeFileAtomic } from "../../utils/io.js";
7
+ export async function runReview(options) {
8
+ const review = await buildReviewResult(options);
9
+ let selectedUpdates = review.updates;
10
+ if (options.interactive && review.updates.length > 0) {
11
+ selectedUpdates = await runTui(review.updates);
12
+ }
13
+ if (options.applySelected && selectedUpdates.length > 0) {
14
+ await applySelectedUpdates({
15
+ ...options,
16
+ install: false,
17
+ packageManager: "auto",
18
+ sync: false,
19
+ }, selectedUpdates);
20
+ }
21
+ process.stdout.write(renderReviewResult({
22
+ ...review,
23
+ updates: selectedUpdates,
24
+ items: review.items.filter((item) => selectedUpdates.some((selected) => selected.name === item.update.name && selected.packagePath === item.update.packagePath)),
25
+ }) + "\n");
26
+ if (options.jsonFile) {
27
+ await writeFileAtomic(options.jsonFile, stableStringify(review, 2) + "\n");
28
+ }
29
+ return review;
30
+ }
@@ -35,6 +35,9 @@ export interface FileConfig {
35
35
  onlyChanged?: boolean;
36
36
  ciProfile?: CiProfile;
37
37
  lockfileMode?: LockfileMode;
38
+ interactive?: boolean;
39
+ showImpact?: boolean;
40
+ showHomepage?: boolean;
38
41
  install?: boolean;
39
42
  packageManager?: "auto" | "npm" | "pnpm";
40
43
  sync?: boolean;
@@ -9,6 +9,8 @@ import { detectPackageManager } from "../pm/detect.js";
9
9
  import { discoverPackageDirs } from "../workspace/discover.js";
10
10
  import { loadPolicy, resolvePolicyRule } from "../config/policy.js";
11
11
  import { createSummary, finalizeSummary } from "./summary.js";
12
+ import { applyImpactScores } from "./impact.js";
13
+ import { formatClassifiedMessage } from "./errors.js";
12
14
  export async function check(options) {
13
15
  const startedAt = Date.now();
14
16
  let discoveryMs = 0;
@@ -28,7 +30,13 @@ export async function check(options) {
28
30
  const errors = [];
29
31
  const warnings = [];
30
32
  if (cache.degraded) {
31
- warnings.push("SQLite cache backend unavailable in Bun runtime. Falling back to file cache backend.");
33
+ warnings.push(formatClassifiedMessage({
34
+ code: "CACHE_BACKEND_FALLBACK",
35
+ whatFailed: cache.fallbackReason ?? "Preferred SQLite cache backend is unavailable.",
36
+ intact: "Dependency analysis continues with the file cache backend.",
37
+ validity: "partial",
38
+ next: "Run `rup warm-cache` after restoring SQLite support if you want the preferred backend again.",
39
+ }));
32
40
  }
33
41
  let totalDependencies = 0;
34
42
  const tasks = [];
@@ -85,6 +93,8 @@ export async function check(options) {
85
93
  latestVersion: cached.latestVersion,
86
94
  availableVersions: cached.availableVersions,
87
95
  publishedAtByVersion: {},
96
+ installScriptByVersion: {},
97
+ maintainerCount: null,
88
98
  });
89
99
  }
90
100
  else {
@@ -102,11 +112,19 @@ export async function check(options) {
102
112
  latestVersion: stale.latestVersion,
103
113
  availableVersions: stale.availableVersions,
104
114
  publishedAtByVersion: {},
115
+ installScriptByVersion: {},
116
+ maintainerCount: null,
105
117
  });
106
118
  warnings.push(`Using stale cache for ${packageName} because --offline is enabled.`);
107
119
  }
108
120
  else {
109
- errors.push(`Offline cache miss for ${packageName}. Run once without --offline to warm cache.`);
121
+ errors.push(formatClassifiedMessage({
122
+ code: "REGISTRY_ERROR",
123
+ whatFailed: `Offline cache miss for ${packageName}.`,
124
+ intact: "Local manifests and previously cached packages remain unchanged.",
125
+ validity: "invalid",
126
+ next: `Run \`rup warm-cache --cwd ${options.cwd}\` or retry without --offline.`,
127
+ }));
110
128
  }
111
129
  }
112
130
  cacheMs += Date.now() - cacheFallbackStartedAt;
@@ -125,6 +143,10 @@ export async function check(options) {
125
143
  latestVersion: metadata.latestVersion,
126
144
  availableVersions: metadata.versions,
127
145
  publishedAtByVersion: metadata.publishedAtByVersion,
146
+ homepage: metadata.homepage,
147
+ repository: metadata.repository,
148
+ installScriptByVersion: metadata.installScriptByVersion,
149
+ maintainerCount: metadata.maintainerCount,
128
150
  });
129
151
  if (metadata.latestVersion) {
130
152
  await cache.set(packageName, options.target, metadata.latestVersion, metadata.versions, options.cacheTtlSeconds);
@@ -139,12 +161,25 @@ export async function check(options) {
139
161
  latestVersion: stale.latestVersion,
140
162
  availableVersions: stale.availableVersions,
141
163
  publishedAtByVersion: {},
164
+ installScriptByVersion: {},
165
+ maintainerCount: null,
142
166
  });
143
167
  warnings.push(`Using stale cache for ${packageName} due to registry error: ${error}`);
144
168
  }
145
169
  else {
146
- errors.push(`Unable to resolve ${packageName}: ${error}`);
147
- emitStream(`[error] Unable to resolve ${packageName}: ${error}`);
170
+ const classified = formatClassifiedMessage({
171
+ code: error.includes("401") || error.includes("403")
172
+ ? "AUTH_ERROR"
173
+ : "REGISTRY_ERROR",
174
+ whatFailed: `Unable to resolve ${packageName}: ${error}.`,
175
+ intact: "Other package results and local files remain intact.",
176
+ validity: "partial",
177
+ next: error.includes("401") || error.includes("403")
178
+ ? "Check .npmrc scoped registry credentials and retry."
179
+ : "Retry `rup check` or warm the cache before offline runs.",
180
+ });
181
+ errors.push(classified);
182
+ emitStream(`[error] ${classified}`);
148
183
  }
149
184
  }
150
185
  cacheMs += Date.now() - cacheStaleStartedAt;
@@ -182,10 +217,23 @@ export async function check(options) {
182
217
  filtered: false,
183
218
  autofix: rule?.autofix !== false,
184
219
  reason: rule?.maxTarget ? `policy maxTarget=${rule.maxTarget}` : undefined,
220
+ homepage: metadata.homepage,
221
+ repository: metadata.repository,
222
+ publishedAt: metadata.publishedAtByVersion[picked]
223
+ ? new Date(metadata.publishedAtByVersion[picked]).toISOString()
224
+ : undefined,
225
+ publishAgeDays: metadata.publishedAtByVersion[picked]
226
+ ? Math.max(0, Math.floor((Date.now() - metadata.publishedAtByVersion[picked]) /
227
+ (1000 * 60 * 60 * 24)))
228
+ : null,
229
+ hasInstallScript: metadata.installScriptByVersion[picked] ?? false,
230
+ maintainerCount: metadata.maintainerCount,
231
+ maintainerChurn: deriveMaintainerChurn(metadata.maintainerCount, metadata.publishedAtByVersion[picked]),
185
232
  });
186
233
  emitStream(`[update] ${task.dependency.name} ${task.dependency.range} -> ${nextRange} (${classifyDiff(task.dependency.range, picked)})`);
187
234
  }
188
- const grouped = groupUpdates(updates, options.groupBy);
235
+ const scoredUpdates = applyImpactScores(updates);
236
+ const grouped = groupUpdates(scoredUpdates, options.groupBy);
189
237
  const groupedUpdates = grouped.length;
190
238
  const groupedSorted = sortUpdates(grouped.flatMap((group) => group.items));
191
239
  const groupedCapped = typeof options.groupMax === "number" ? groupedSorted.slice(0, options.groupMax) : groupedSorted;
@@ -219,6 +267,7 @@ export async function check(options) {
219
267
  policyOverridesApplied,
220
268
  }));
221
269
  summary.streamedEvents = streamedEvents;
270
+ summary.riskPackages = limitedUpdates.filter((item) => item.impactScore?.rank === "critical" || item.impactScore?.rank === "high").length;
222
271
  return {
223
272
  projectPath: options.cwd,
224
273
  packagePaths: packageDirs,
@@ -231,6 +280,16 @@ export async function check(options) {
231
280
  warnings: sortedWarnings,
232
281
  };
233
282
  }
283
+ function deriveMaintainerChurn(maintainerCount, publishedAt) {
284
+ if (maintainerCount === null || publishedAt === undefined) {
285
+ return "unknown";
286
+ }
287
+ const ageDays = Math.max(0, Math.floor((Date.now() - publishedAt) / (1000 * 60 * 60 * 24)));
288
+ if (maintainerCount <= 1 && ageDays <= 30) {
289
+ return "elevated-change";
290
+ }
291
+ return "stable";
292
+ }
234
293
  function groupUpdates(updates, groupBy) {
235
294
  if (updates.length === 0) {
236
295
  return [];
@@ -0,0 +1,11 @@
1
+ export type ErrorCode = "REGISTRY_ERROR" | "AUTH_ERROR" | "ADVISORY_SOURCE_DEGRADED" | "ADVISORY_SOURCE_DOWN" | "CACHE_BACKEND_FALLBACK";
2
+ export type ErrorValidity = "partial" | "invalid" | "intact";
3
+ export interface ClassifiedMessageInput {
4
+ code: ErrorCode;
5
+ whatFailed: string;
6
+ intact: string;
7
+ validity: ErrorValidity;
8
+ next: string;
9
+ }
10
+ export declare function formatClassifiedMessage(input: ClassifiedMessageInput): string;
11
+ export declare function hasErrorCode(value: string, code: ErrorCode): boolean;
@@ -0,0 +1,6 @@
1
+ export function formatClassifiedMessage(input) {
2
+ return `[${input.code}] ${input.whatFailed} Intact: ${input.intact} Result: ${input.validity}. Next: ${input.next}`;
3
+ }
4
+ export function hasErrorCode(value, code) {
5
+ return value.startsWith(`[${code}]`);
6
+ }
@@ -1,4 +1,4 @@
1
- import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions } from "../types/index.js";
1
+ import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions, ReviewOptions, DoctorOptions, RiskLevel } from "../types/index.js";
2
2
  import type { InitCiMode, InitCiSchedule } from "./init-ci.js";
3
3
  export type ParsedCliArgs = {
4
4
  command: "check";
@@ -46,5 +46,12 @@ export type ParsedCliArgs = {
46
46
  } | {
47
47
  command: "snapshot";
48
48
  options: SnapshotOptions;
49
+ } | {
50
+ command: "review";
51
+ options: ReviewOptions;
52
+ } | {
53
+ command: "doctor";
54
+ options: DoctorOptions;
49
55
  };
50
56
  export declare function parseCliArgs(argv: string[]): Promise<ParsedCliArgs>;
57
+ export declare function ensureRiskLevel(value: string): RiskLevel;
@@ -21,6 +21,8 @@ const KNOWN_COMMANDS = [
21
21
  "resolve",
22
22
  "licenses",
23
23
  "snapshot",
24
+ "review",
25
+ "doctor",
24
26
  ];
25
27
  export async function parseCliArgs(argv) {
26
28
  const firstArg = argv[0];
@@ -62,6 +64,14 @@ export async function parseCliArgs(argv) {
62
64
  const { parseSnapshotArgs } = await import("../commands/snapshot/parser.js");
63
65
  return { command, options: parseSnapshotArgs(args) };
64
66
  }
67
+ if (command === "review") {
68
+ const { parseReviewArgs } = await import("../commands/review/parser.js");
69
+ return { command, options: parseReviewArgs(args) };
70
+ }
71
+ if (command === "doctor") {
72
+ const { parseDoctorArgs } = await import("../commands/doctor/parser.js");
73
+ return { command, options: parseDoctorArgs(args) };
74
+ }
65
75
  const base = {
66
76
  cwd: process.cwd(),
67
77
  target: "latest",
@@ -99,6 +109,9 @@ export async function parseCliArgs(argv) {
99
109
  onlyChanged: false,
100
110
  ciProfile: "minimal",
101
111
  lockfileMode: "preserve",
112
+ interactive: false,
113
+ showImpact: false,
114
+ showHomepage: false,
102
115
  };
103
116
  let force = false;
104
117
  let initCiMode = "enterprise";
@@ -429,6 +442,18 @@ export async function parseCliArgs(argv) {
429
442
  base.onlyChanged = true;
430
443
  continue;
431
444
  }
445
+ if (current === "--interactive") {
446
+ base.interactive = true;
447
+ continue;
448
+ }
449
+ if (current === "--show-impact") {
450
+ base.showImpact = true;
451
+ continue;
452
+ }
453
+ if (current === "--show-homepage") {
454
+ base.showHomepage = true;
455
+ continue;
456
+ }
432
457
  if (current === "--lockfile-mode" && next) {
433
458
  base.lockfileMode = ensureLockfileMode(next);
434
459
  index += 1;
@@ -639,6 +664,15 @@ function applyConfig(base, config) {
639
664
  if (typeof config.lockfileMode === "string") {
640
665
  base.lockfileMode = ensureLockfileMode(config.lockfileMode);
641
666
  }
667
+ if (typeof config.interactive === "boolean") {
668
+ base.interactive = config.interactive;
669
+ }
670
+ if (typeof config.showImpact === "boolean") {
671
+ base.showImpact = config.showImpact;
672
+ }
673
+ if (typeof config.showHomepage === "boolean") {
674
+ base.showHomepage = config.showHomepage;
675
+ }
642
676
  }
643
677
  function parsePackageManager(args) {
644
678
  const index = args.indexOf("--pm");
@@ -743,3 +777,12 @@ function ensureLockfileMode(value) {
743
777
  }
744
778
  throw new Error("--lockfile-mode must be preserve, update or error");
745
779
  }
780
+ export function ensureRiskLevel(value) {
781
+ if (value === "critical" ||
782
+ value === "high" ||
783
+ value === "medium" ||
784
+ value === "low") {
785
+ return value;
786
+ }
787
+ throw new Error("--risk must be critical, high, medium or low");
788
+ }
@@ -0,0 +1,5 @@
1
+ import type { CheckOptions, DoctorOptions, DoctorResult, ReviewOptions, ReviewResult } from "../types/index.js";
2
+ export declare function buildReviewResult(options: ReviewOptions | DoctorOptions | CheckOptions): Promise<ReviewResult>;
3
+ export declare function createDoctorResult(review: ReviewResult): DoctorResult;
4
+ export declare function renderReviewResult(review: ReviewResult): string;
5
+ export declare function renderDoctorResult(result: DoctorResult, verdictOnly?: boolean): string;