@rainy-updates/cli 0.5.7 → 0.6.0
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 +81 -0
- package/README.md +90 -31
- package/dist/bin/cli.js +16 -16
- package/dist/bin/dispatch.js +29 -32
- package/dist/bin/help.js +32 -2
- package/dist/cache/cache.js +13 -11
- package/dist/commands/audit/parser.js +2 -2
- package/dist/commands/audit/runner.js +27 -46
- package/dist/commands/audit/targets.js +13 -13
- package/dist/commands/bisect/oracle.js +28 -11
- package/dist/commands/bisect/parser.js +3 -3
- package/dist/commands/bisect/runner.js +15 -8
- package/dist/commands/changelog/fetcher.js +11 -5
- package/dist/commands/dashboard/parser.js +103 -1
- package/dist/commands/dashboard/runner.d.ts +2 -2
- package/dist/commands/dashboard/runner.js +67 -37
- package/dist/commands/doctor/parser.js +9 -4
- package/dist/commands/doctor/runner.js +2 -2
- package/dist/commands/ga/parser.js +4 -4
- package/dist/commands/ga/runner.js +13 -7
- package/dist/commands/health/parser.js +2 -2
- package/dist/commands/licenses/runner.js +4 -4
- package/dist/commands/resolve/runner.js +9 -4
- package/dist/commands/review/parser.js +57 -4
- package/dist/commands/review/runner.js +31 -5
- package/dist/commands/snapshot/runner.js +17 -17
- package/dist/commands/snapshot/store.d.ts +0 -12
- package/dist/commands/snapshot/store.js +26 -38
- package/dist/commands/unused/runner.js +6 -7
- package/dist/commands/unused/scanner.js +17 -20
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +2 -5
- package/dist/config/policy.js +20 -11
- package/dist/core/analysis/run-silenced.js +0 -1
- package/dist/core/artifacts.js +6 -5
- package/dist/core/baseline.js +3 -5
- package/dist/core/check.js +2 -2
- package/dist/core/ci.js +52 -1
- package/dist/core/decision-plan.d.ts +14 -0
- package/dist/core/decision-plan.js +107 -0
- package/dist/core/doctor/result.js +8 -5
- package/dist/core/fix-pr-batch.js +38 -28
- package/dist/core/fix-pr.js +27 -24
- package/dist/core/init-ci.js +25 -21
- package/dist/core/options.js +95 -4
- package/dist/core/review-model.js +3 -0
- package/dist/core/summary.js +6 -0
- package/dist/core/upgrade.js +64 -2
- package/dist/core/verification.d.ts +2 -0
- package/dist/core/verification.js +106 -0
- package/dist/core/warm-cache.js +2 -2
- package/dist/output/format.js +15 -0
- package/dist/output/github.js +6 -0
- package/dist/output/sarif.js +12 -12
- package/dist/parsers/package-json.js +2 -4
- package/dist/pm/detect.d.ts +3 -1
- package/dist/pm/detect.js +24 -12
- package/dist/pm/install.d.ts +2 -1
- package/dist/pm/install.js +15 -16
- package/dist/registry/npm.js +34 -76
- package/dist/rup +0 -0
- package/dist/types/index.d.ts +76 -5
- package/dist/ui/tui.d.ts +4 -1
- package/dist/ui/tui.js +5 -4
- package/dist/utils/io.js +5 -6
- package/dist/utils/lockfile.js +24 -19
- package/dist/utils/runtime-paths.d.ts +4 -0
- package/dist/utils/runtime-paths.js +35 -0
- package/dist/utils/runtime.d.ts +7 -0
- package/dist/utils/runtime.js +32 -0
- package/dist/workspace/discover.js +55 -51
- package/package.json +16 -16
- package/dist/ui/dashboard/DashboardTUI.d.ts +0 -6
- package/dist/ui/dashboard/DashboardTUI.js +0 -34
- package/dist/ui/dashboard/components/DetailPanel.d.ts +0 -4
- package/dist/ui/dashboard/components/DetailPanel.js +0 -30
- package/dist/ui/dashboard/components/Footer.d.ts +0 -4
- package/dist/ui/dashboard/components/Footer.js +0 -9
- package/dist/ui/dashboard/components/Header.d.ts +0 -4
- package/dist/ui/dashboard/components/Header.js +0 -12
- package/dist/ui/dashboard/components/Sidebar.d.ts +0 -4
- package/dist/ui/dashboard/components/Sidebar.js +0 -23
- package/dist/ui/dashboard/store.d.ts +0 -34
- package/dist/ui/dashboard/store.js +0 -148
|
@@ -1,47 +1,77 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
export async function runDashboard(options) {
|
|
8
|
-
|
|
9
|
-
const resolvedConfig = await loadConfig(options.cwd);
|
|
10
|
-
// Create an initial check result. In a real scenario, this could run
|
|
11
|
-
// progressively in the TUI, but for simplicity we fetch initial data first.
|
|
12
|
-
const checkResult = await check({
|
|
1
|
+
import { createDecisionPlan, defaultDecisionPlanPath, filterReviewItemsByFocus, writeDecisionPlan, } from "../../core/decision-plan.js";
|
|
2
|
+
import { buildReviewResult, renderReviewResult } from "../../core/review-model.js";
|
|
3
|
+
import { applySelectedUpdates } from "../../core/upgrade.js";
|
|
4
|
+
import { runVerification } from "../../core/verification.js";
|
|
5
|
+
import { runTui } from "../../ui/tui.js";
|
|
6
|
+
import { writeStderr, writeStdout } from "../../utils/runtime.js";
|
|
7
|
+
export async function runDashboard(options, prebuiltReview) {
|
|
8
|
+
const review = prebuiltReview ?? (await buildReviewResult({
|
|
13
9
|
...options,
|
|
14
|
-
|
|
15
|
-
logLevel: "error",
|
|
16
|
-
});
|
|
17
|
-
// Render the interactive Ink Dashboard
|
|
18
|
-
const { waitUntilExit } = render(React.createElement(DashboardTUI, {
|
|
19
|
-
options,
|
|
20
|
-
initialResult: checkResult,
|
|
10
|
+
interactive: false,
|
|
21
11
|
}));
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
12
|
+
const visibleItems = filterReviewItemsByFocus(review.items, options.focus);
|
|
13
|
+
const selectedItems = await selectDashboardItems(options, visibleItems);
|
|
14
|
+
const decisionPlan = createDecisionPlan({
|
|
15
|
+
review,
|
|
16
|
+
selectedItems,
|
|
17
|
+
sourceCommand: "dashboard",
|
|
18
|
+
mode: options.mode,
|
|
19
|
+
focus: options.focus,
|
|
20
|
+
});
|
|
21
|
+
const decisionPlanFile = options.decisionPlanFile ?? defaultDecisionPlanPath(options.cwd);
|
|
22
|
+
await writeDecisionPlan(decisionPlanFile, decisionPlan);
|
|
23
|
+
review.decisionPlan = decisionPlan;
|
|
24
|
+
review.summary.decisionPlan = decisionPlanFile;
|
|
25
|
+
review.summary.interactiveSurface = "dashboard";
|
|
26
|
+
review.summary.queueFocus = options.focus;
|
|
27
|
+
review.summary.suggestedCommand =
|
|
28
|
+
options.mode === "upgrade" || options.applySelected
|
|
29
|
+
? `rup upgrade --from-plan ${decisionPlanFile}`
|
|
30
|
+
: `rup upgrade --from-plan ${decisionPlanFile}`;
|
|
31
|
+
if ((options.mode === "upgrade" || options.applySelected) && selectedItems.length > 0) {
|
|
30
32
|
await applySelectedUpdates({
|
|
31
33
|
...options,
|
|
32
|
-
install: false,
|
|
34
|
+
install: false,
|
|
33
35
|
packageManager: "auto",
|
|
34
|
-
sync:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
sync: options.workspace,
|
|
37
|
+
}, selectedItems.map((item) => item.update));
|
|
38
|
+
if (options.verify !== "none") {
|
|
39
|
+
const verification = await runVerification({
|
|
40
|
+
cwd: options.cwd,
|
|
41
|
+
verify: options.verify,
|
|
42
|
+
testCommand: options.testCommand,
|
|
43
|
+
verificationReportFile: options.verificationReportFile,
|
|
44
|
+
packageManager: "auto",
|
|
45
|
+
});
|
|
46
|
+
if (!verification.passed) {
|
|
47
|
+
review.errors.push(...verification.checks
|
|
48
|
+
.filter((check) => !check.passed)
|
|
49
|
+
.map((check) => `Verification failed for ${check.name}: ${check.error ?? check.command}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
41
52
|
}
|
|
53
|
+
writeStdout(renderReviewResult({
|
|
54
|
+
...review,
|
|
55
|
+
items: selectedItems,
|
|
56
|
+
updates: selectedItems.map((item) => item.update),
|
|
57
|
+
}) + "\n");
|
|
58
|
+
writeStderr(`[dashboard] decision plan written to ${decisionPlanFile}\n`);
|
|
42
59
|
return {
|
|
43
60
|
completed: true,
|
|
44
|
-
errors:
|
|
45
|
-
warnings:
|
|
61
|
+
errors: review.errors,
|
|
62
|
+
warnings: review.warnings,
|
|
63
|
+
selectedUpdates: selectedItems.length,
|
|
64
|
+
decisionPlanFile,
|
|
46
65
|
};
|
|
47
66
|
}
|
|
67
|
+
async function selectDashboardItems(options, visibleItems) {
|
|
68
|
+
if (visibleItems.length === 0) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
return runTui(visibleItems, {
|
|
72
|
+
title: options.mode === "upgrade"
|
|
73
|
+
? "Rainy Dashboard: Upgrade Queue"
|
|
74
|
+
: "Rainy Dashboard: Review Queue",
|
|
75
|
+
subtitle: `focus=${options.focus} mode=${options.mode} Enter confirms the selected decision set`,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import { exitProcess, getRuntimeCwd, writeStdout, } from "../../utils/runtime.js";
|
|
3
3
|
export function parseDoctorArgs(args) {
|
|
4
4
|
const options = {
|
|
5
|
-
cwd:
|
|
5
|
+
cwd: getRuntimeCwd(),
|
|
6
6
|
target: "latest",
|
|
7
7
|
filter: undefined,
|
|
8
8
|
reject: undefined,
|
|
@@ -41,6 +41,11 @@ export function parseDoctorArgs(args) {
|
|
|
41
41
|
interactive: false,
|
|
42
42
|
showImpact: true,
|
|
43
43
|
showHomepage: true,
|
|
44
|
+
decisionPlanFile: undefined,
|
|
45
|
+
verify: "none",
|
|
46
|
+
testCommand: undefined,
|
|
47
|
+
verificationReportFile: undefined,
|
|
48
|
+
ciGate: "check",
|
|
44
49
|
verdictOnly: false,
|
|
45
50
|
includeChangelog: false,
|
|
46
51
|
agentReport: false,
|
|
@@ -79,8 +84,8 @@ export function parseDoctorArgs(args) {
|
|
|
79
84
|
if (current === "--json-file")
|
|
80
85
|
throw new Error("Missing value for --json-file");
|
|
81
86
|
if (current === "--help" || current === "-h") {
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
writeStdout(DOCTOR_HELP);
|
|
88
|
+
exitProcess(0);
|
|
84
89
|
}
|
|
85
90
|
if (current.startsWith("-"))
|
|
86
91
|
throw new Error(`Unknown doctor option: ${current}`);
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { buildReviewResult, createDoctorResult, renderDoctorAgentReport, renderDoctorResult, } from "../../core/review-model.js";
|
|
3
2
|
import { stableStringify } from "../../utils/stable-json.js";
|
|
4
3
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
4
|
+
import { writeStdout } from "../../utils/runtime.js";
|
|
5
5
|
export async function runDoctor(options) {
|
|
6
6
|
const review = await buildReviewResult(options);
|
|
7
7
|
const doctor = createDoctorResult(review);
|
|
8
8
|
const output = options.agentReport
|
|
9
9
|
? renderDoctorAgentReport(doctor)
|
|
10
10
|
: renderDoctorResult(doctor, options.verdictOnly);
|
|
11
|
-
|
|
11
|
+
writeStdout(output + "\n");
|
|
12
12
|
if (options.jsonFile) {
|
|
13
13
|
await writeFileAtomic(options.jsonFile, stableStringify(doctor, 2) + "\n");
|
|
14
14
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import { exitProcess, getRuntimeCwd, writeStdout, } from "../../utils/runtime.js";
|
|
3
3
|
export function parseGaArgs(args) {
|
|
4
4
|
const options = {
|
|
5
|
-
cwd:
|
|
5
|
+
cwd: getRuntimeCwd(),
|
|
6
6
|
workspace: false,
|
|
7
7
|
jsonFile: undefined,
|
|
8
8
|
};
|
|
@@ -28,8 +28,8 @@ export function parseGaArgs(args) {
|
|
|
28
28
|
if (current === "--json-file")
|
|
29
29
|
throw new Error("Missing value for --json-file");
|
|
30
30
|
if (current === "--help" || current === "-h") {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
writeStdout(GA_HELP);
|
|
32
|
+
exitProcess(0);
|
|
33
33
|
}
|
|
34
34
|
if (current.startsWith("-"))
|
|
35
35
|
throw new Error(`Unknown ga option: ${current}`);
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
|
-
import process from "node:process";
|
|
4
2
|
import { VersionCache } from "../../cache/cache.js";
|
|
5
3
|
import { detectPackageManager } from "../../pm/detect.js";
|
|
6
4
|
import { discoverPackageDirs } from "../../workspace/discover.js";
|
|
7
5
|
import { stableStringify } from "../../utils/stable-json.js";
|
|
8
6
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
7
|
+
import { writeStdout } from "../../utils/runtime.js";
|
|
9
8
|
export async function runGa(options) {
|
|
10
9
|
const packageManager = await detectPackageManager(options.cwd);
|
|
11
10
|
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
|
|
@@ -15,8 +14,8 @@ export async function runGa(options) {
|
|
|
15
14
|
name: "package-manager",
|
|
16
15
|
status: packageManager === "unknown" ? "warn" : "pass",
|
|
17
16
|
detail: packageManager === "unknown"
|
|
18
|
-
? "No supported lockfile was detected.
|
|
19
|
-
: `Detected package manager: ${packageManager}.`,
|
|
17
|
+
? "No supported lockfile was detected. Rainy can still run, but runtime/package verification will fall back to generic defaults."
|
|
18
|
+
: `Detected package manager: ${packageManager}. Runtime and verification can align with this package ecosystem, including Bun.`,
|
|
20
19
|
});
|
|
21
20
|
checks.push({
|
|
22
21
|
name: "workspace-discovery",
|
|
@@ -40,6 +39,14 @@ export async function runGa(options) {
|
|
|
40
39
|
? "Built CLI entrypoint exists in dist/bin/cli.js."
|
|
41
40
|
: "Built CLI entrypoint is missing; run the build before publishing a release artifact.",
|
|
42
41
|
});
|
|
42
|
+
const compiledBinaryExists = await fileExists(path.resolve(options.cwd, "dist/rup"));
|
|
43
|
+
checks.push({
|
|
44
|
+
name: "runtime-artifacts",
|
|
45
|
+
status: compiledBinaryExists ? "pass" : "warn",
|
|
46
|
+
detail: compiledBinaryExists
|
|
47
|
+
? "Compiled Bun runtime artifact exists in dist/rup."
|
|
48
|
+
: "Compiled Bun runtime artifact is missing; run bun run build:exe before publishing Bun-first release artifacts.",
|
|
49
|
+
});
|
|
43
50
|
checks.push({
|
|
44
51
|
name: "benchmark-gates",
|
|
45
52
|
status: (await fileExists(path.resolve(options.cwd, "scripts/perf-smoke.mjs"))) &&
|
|
@@ -68,7 +75,7 @@ export async function runGa(options) {
|
|
|
68
75
|
warnings,
|
|
69
76
|
errors,
|
|
70
77
|
};
|
|
71
|
-
|
|
78
|
+
writeStdout(renderGaResult(result) + "\n");
|
|
72
79
|
if (options.jsonFile) {
|
|
73
80
|
await writeFileAtomic(options.jsonFile, stableStringify(result, 2) + "\n");
|
|
74
81
|
}
|
|
@@ -120,8 +127,7 @@ async function detectLockfile(cwd) {
|
|
|
120
127
|
}
|
|
121
128
|
async function fileExists(filePath) {
|
|
122
129
|
try {
|
|
123
|
-
await
|
|
124
|
-
return true;
|
|
130
|
+
return await Bun.file(filePath).exists();
|
|
125
131
|
}
|
|
126
132
|
catch {
|
|
127
133
|
return false;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import { getRuntimeCwd } from "../../utils/runtime.js";
|
|
3
3
|
export function parseHealthArgs(args) {
|
|
4
4
|
const options = {
|
|
5
|
-
cwd:
|
|
5
|
+
cwd: getRuntimeCwd(),
|
|
6
6
|
workspace: false,
|
|
7
7
|
staleDays: 365,
|
|
8
8
|
includeDeprecated: true,
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { discoverPackageDirs } from "../../workspace/discover.js";
|
|
3
2
|
import { readManifest, collectDependencies, } from "../../parsers/package-json.js";
|
|
4
3
|
import { asyncPool } from "../../utils/async-pool.js";
|
|
5
4
|
import { stableStringify } from "../../utils/stable-json.js";
|
|
6
5
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
6
|
+
import { writeStderr, writeStdout } from "../../utils/runtime.js";
|
|
7
7
|
import { generateSbom } from "./sbom.js";
|
|
8
8
|
/**
|
|
9
9
|
* Entry point for `rup licenses`. Lazy-loaded by cli.ts.
|
|
@@ -63,17 +63,17 @@ export async function runLicenses(options) {
|
|
|
63
63
|
}
|
|
64
64
|
result.totalViolations = result.violations.length;
|
|
65
65
|
// Render
|
|
66
|
-
|
|
66
|
+
writeStdout(renderLicenseTable(result) + "\n");
|
|
67
67
|
// SBOM output
|
|
68
68
|
if (options.sbomFile) {
|
|
69
69
|
const sbom = generateSbom(result.packages, options.cwd);
|
|
70
70
|
await writeFileAtomic(options.sbomFile, stableStringify(sbom, 2) + "\n");
|
|
71
|
-
|
|
71
|
+
writeStderr(`[licenses] SBOM written to ${options.sbomFile}\n`);
|
|
72
72
|
}
|
|
73
73
|
// JSON output
|
|
74
74
|
if (options.jsonFile) {
|
|
75
75
|
await writeFileAtomic(options.jsonFile, stableStringify(result, 2) + "\n");
|
|
76
|
-
|
|
76
|
+
writeStderr(`[licenses] JSON report written to ${options.jsonFile}\n`);
|
|
77
77
|
}
|
|
78
78
|
return result;
|
|
79
79
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { buildPeerGraph } from "./graph/builder.js";
|
|
3
2
|
import { resolvePeerConflicts } from "./graph/resolver.js";
|
|
4
3
|
import { stableStringify } from "../../utils/stable-json.js";
|
|
5
4
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
5
|
+
import { writeStderr, writeStdout } from "../../utils/runtime.js";
|
|
6
6
|
/**
|
|
7
7
|
* Entry point for `rup resolve`. Lazy-loaded by cli.ts.
|
|
8
8
|
*
|
|
@@ -26,7 +26,7 @@ export async function runResolve(options) {
|
|
|
26
26
|
if (options.afterUpdate) {
|
|
27
27
|
versionOverrides = await fetchProposedVersions(options);
|
|
28
28
|
if (versionOverrides.size === 0 && !options.silent) {
|
|
29
|
-
|
|
29
|
+
writeStderr("[resolve] No pending updates found — checking current state.\n");
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
let graph;
|
|
@@ -42,12 +42,12 @@ export async function runResolve(options) {
|
|
|
42
42
|
result.errorConflicts = conflicts.filter((c) => c.severity === "error").length;
|
|
43
43
|
result.warningConflicts = conflicts.filter((c) => c.severity === "warning").length;
|
|
44
44
|
if (!options.silent) {
|
|
45
|
-
|
|
45
|
+
writeStdout(renderConflictsTable(result, options) + "\n");
|
|
46
46
|
}
|
|
47
47
|
if (options.jsonFile) {
|
|
48
48
|
await writeFileAtomic(options.jsonFile, stableStringify(result, 2) + "\n");
|
|
49
49
|
if (!options.silent) {
|
|
50
|
-
|
|
50
|
+
writeStderr(`[resolve] JSON report written to ${options.jsonFile}\n`);
|
|
51
51
|
}
|
|
52
52
|
}
|
|
53
53
|
return result;
|
|
@@ -100,6 +100,11 @@ async function fetchProposedVersions(options) {
|
|
|
100
100
|
interactive: false,
|
|
101
101
|
showImpact: false,
|
|
102
102
|
showHomepage: false,
|
|
103
|
+
decisionPlanFile: undefined,
|
|
104
|
+
verify: "none",
|
|
105
|
+
testCommand: undefined,
|
|
106
|
+
verificationReportFile: undefined,
|
|
107
|
+
ciGate: "check",
|
|
103
108
|
});
|
|
104
109
|
for (const update of checkResult.updates ?? []) {
|
|
105
110
|
overrides.set(update.name, update.toVersionResolved);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import process from "node:process";
|
|
3
2
|
import { ensureRiskLevel } from "../../core/options.js";
|
|
3
|
+
import { exitProcess, getRuntimeCwd, writeStdout, } from "../../utils/runtime.js";
|
|
4
4
|
export function parseReviewArgs(args) {
|
|
5
5
|
const options = {
|
|
6
|
-
cwd:
|
|
6
|
+
cwd: getRuntimeCwd(),
|
|
7
7
|
target: "latest",
|
|
8
8
|
filter: undefined,
|
|
9
9
|
reject: undefined,
|
|
@@ -47,6 +47,12 @@ export function parseReviewArgs(args) {
|
|
|
47
47
|
diff: undefined,
|
|
48
48
|
applySelected: false,
|
|
49
49
|
showChangelog: false,
|
|
50
|
+
decisionPlanFile: undefined,
|
|
51
|
+
queueFocus: "all",
|
|
52
|
+
verify: "none",
|
|
53
|
+
testCommand: undefined,
|
|
54
|
+
verificationReportFile: undefined,
|
|
55
|
+
ciGate: "check",
|
|
50
56
|
};
|
|
51
57
|
for (let i = 0; i < args.length; i += 1) {
|
|
52
58
|
const current = args[i];
|
|
@@ -94,6 +100,41 @@ export function parseReviewArgs(args) {
|
|
|
94
100
|
options.applySelected = true;
|
|
95
101
|
continue;
|
|
96
102
|
}
|
|
103
|
+
if (current === "--plan-file" && next) {
|
|
104
|
+
options.decisionPlanFile = path.resolve(options.cwd, next);
|
|
105
|
+
i += 1;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (current === "--plan-file")
|
|
109
|
+
throw new Error("Missing value for --plan-file");
|
|
110
|
+
if (current === "--verify" && next) {
|
|
111
|
+
if (next === "none" ||
|
|
112
|
+
next === "install" ||
|
|
113
|
+
next === "test" ||
|
|
114
|
+
next === "install,test") {
|
|
115
|
+
options.verify = next;
|
|
116
|
+
i += 1;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
throw new Error("--verify must be none, install, test or install,test");
|
|
120
|
+
}
|
|
121
|
+
if (current === "--verify")
|
|
122
|
+
throw new Error("Missing value for --verify");
|
|
123
|
+
if (current === "--test-command" && next) {
|
|
124
|
+
options.testCommand = next;
|
|
125
|
+
i += 1;
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (current === "--test-command")
|
|
129
|
+
throw new Error("Missing value for --test-command");
|
|
130
|
+
if (current === "--verification-report-file" && next) {
|
|
131
|
+
options.verificationReportFile = path.resolve(options.cwd, next);
|
|
132
|
+
i += 1;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (current === "--verification-report-file") {
|
|
136
|
+
throw new Error("Missing value for --verification-report-file");
|
|
137
|
+
}
|
|
97
138
|
if (current === "--show-changelog") {
|
|
98
139
|
options.showChangelog = true;
|
|
99
140
|
continue;
|
|
@@ -148,13 +189,22 @@ export function parseReviewArgs(args) {
|
|
|
148
189
|
throw new Error("Missing value for --registry-retries");
|
|
149
190
|
}
|
|
150
191
|
if (current === "--help" || current === "-h") {
|
|
151
|
-
|
|
152
|
-
|
|
192
|
+
writeStdout(REVIEW_HELP);
|
|
193
|
+
exitProcess(0);
|
|
153
194
|
}
|
|
154
195
|
if (current.startsWith("-"))
|
|
155
196
|
throw new Error(`Unknown review option: ${current}`);
|
|
156
197
|
throw new Error(`Unexpected review argument: ${current}`);
|
|
157
198
|
}
|
|
199
|
+
if (options.securityOnly) {
|
|
200
|
+
options.queueFocus = "security";
|
|
201
|
+
}
|
|
202
|
+
else if (options.risk === "critical" || options.risk === "high") {
|
|
203
|
+
options.queueFocus = "risk";
|
|
204
|
+
}
|
|
205
|
+
else if (options.diff === "major") {
|
|
206
|
+
options.queueFocus = "major";
|
|
207
|
+
}
|
|
158
208
|
return options;
|
|
159
209
|
}
|
|
160
210
|
const REVIEW_HELP = `
|
|
@@ -169,6 +219,9 @@ Options:
|
|
|
169
219
|
--risk <level> Minimum risk: critical, high, medium, low
|
|
170
220
|
--diff <level> Filter by patch, minor, major, latest
|
|
171
221
|
--apply-selected Apply all filtered updates after review
|
|
222
|
+
--plan-file <path> Write the selected decision set to a reusable plan file
|
|
223
|
+
--verify <mode> Run post-apply verification: none, install, test, install,test
|
|
224
|
+
--test-command <cmd> Override the command used for test verification
|
|
172
225
|
--show-changelog Fetch release notes summaries for review output
|
|
173
226
|
--workspace Scan all workspace packages
|
|
174
227
|
--policy-file <path> Load policy overrides
|
|
@@ -1,16 +1,42 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
|
-
import { runTui } from "../../ui/tui.js";
|
|
3
1
|
import { buildReviewResult, renderReviewResult } from "../../core/review-model.js";
|
|
4
2
|
import { applySelectedUpdates } from "../../core/upgrade.js";
|
|
3
|
+
import { createDecisionPlan, writeDecisionPlan } from "../../core/decision-plan.js";
|
|
5
4
|
import { stableStringify } from "../../utils/stable-json.js";
|
|
6
5
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
6
|
+
import { writeStdout } from "../../utils/runtime.js";
|
|
7
7
|
export async function runReview(options) {
|
|
8
8
|
const review = await buildReviewResult(options);
|
|
9
|
-
let selectedItems = review.items;
|
|
10
9
|
if (options.interactive && review.updates.length > 0) {
|
|
11
|
-
|
|
10
|
+
const { runDashboard } = await import("../dashboard/runner.js");
|
|
11
|
+
const dashboard = await runDashboard({
|
|
12
|
+
...options,
|
|
13
|
+
mode: options.applySelected ? "upgrade" : "review",
|
|
14
|
+
focus: options.queueFocus ?? "all",
|
|
15
|
+
applySelected: options.applySelected,
|
|
16
|
+
}, review);
|
|
17
|
+
review.summary.decisionPlan = dashboard.decisionPlanFile;
|
|
18
|
+
review.summary.interactiveSurface = "dashboard";
|
|
19
|
+
review.summary.queueFocus = options.queueFocus ?? "all";
|
|
20
|
+
if (options.jsonFile) {
|
|
21
|
+
await writeFileAtomic(options.jsonFile, stableStringify(review, 2) + "\n");
|
|
22
|
+
}
|
|
23
|
+
return review;
|
|
12
24
|
}
|
|
25
|
+
let selectedItems = review.items;
|
|
13
26
|
const selectedUpdates = selectedItems.map((item) => item.update);
|
|
27
|
+
if (options.decisionPlanFile) {
|
|
28
|
+
const decisionPlan = createDecisionPlan({
|
|
29
|
+
review,
|
|
30
|
+
selectedItems,
|
|
31
|
+
sourceCommand: "review",
|
|
32
|
+
mode: options.applySelected ? "upgrade" : "review",
|
|
33
|
+
focus: options.queueFocus ?? "all",
|
|
34
|
+
});
|
|
35
|
+
const decisionPlanFile = options.decisionPlanFile;
|
|
36
|
+
await writeDecisionPlan(decisionPlanFile, decisionPlan);
|
|
37
|
+
review.decisionPlan = decisionPlan;
|
|
38
|
+
review.summary.decisionPlan = decisionPlanFile;
|
|
39
|
+
}
|
|
14
40
|
if (options.applySelected && selectedUpdates.length > 0) {
|
|
15
41
|
await applySelectedUpdates({
|
|
16
42
|
...options,
|
|
@@ -19,7 +45,7 @@ export async function runReview(options) {
|
|
|
19
45
|
sync: false,
|
|
20
46
|
}, selectedUpdates);
|
|
21
47
|
}
|
|
22
|
-
|
|
48
|
+
writeStdout(renderReviewResult({
|
|
23
49
|
...review,
|
|
24
50
|
items: selectedItems,
|
|
25
51
|
updates: selectedUpdates,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { discoverPackageDirs } from "../../workspace/discover.js";
|
|
3
2
|
import { SnapshotStore, captureState, restoreState, diffManifests, } from "./store.js";
|
|
3
|
+
import { writeStderr, writeStdout } from "../../utils/runtime.js";
|
|
4
4
|
/**
|
|
5
5
|
* Entry point for `rup snapshot`. Lazy-loaded by cli.ts.
|
|
6
6
|
*
|
|
@@ -27,7 +27,7 @@ export async function runSnapshot(options) {
|
|
|
27
27
|
const entry = await store.saveSnapshot(manifests, lockfileHashes, label);
|
|
28
28
|
result.snapshotId = entry.id;
|
|
29
29
|
result.label = entry.label;
|
|
30
|
-
|
|
30
|
+
writeStdout(`✔ Snapshot saved: ${entry.label} (${entry.id})\n`);
|
|
31
31
|
break;
|
|
32
32
|
}
|
|
33
33
|
// ─ list ──────────────────────────────────────────────────────────────────
|
|
@@ -39,20 +39,20 @@ export async function runSnapshot(options) {
|
|
|
39
39
|
createdAt: new Date(e.createdAt).toISOString(),
|
|
40
40
|
}));
|
|
41
41
|
if (entries.length === 0) {
|
|
42
|
-
|
|
42
|
+
writeStdout("No snapshots saved yet. Use `rup snapshot save` to create one.\n");
|
|
43
43
|
}
|
|
44
44
|
else {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
writeStdout(`\n${entries.length} snapshot(s):\n\n`);
|
|
46
|
+
writeStdout(" " + "ID".padEnd(30) + "Label".padEnd(30) + "Created\n");
|
|
47
|
+
writeStdout(" " + "─".repeat(75) + "\n");
|
|
48
48
|
for (const e of entries) {
|
|
49
|
-
|
|
49
|
+
writeStdout(" " +
|
|
50
50
|
e.id.padEnd(30) +
|
|
51
51
|
e.label.padEnd(30) +
|
|
52
52
|
new Date(e.createdAt).toLocaleString() +
|
|
53
53
|
"\n");
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
writeStdout("\n");
|
|
56
56
|
}
|
|
57
57
|
break;
|
|
58
58
|
}
|
|
@@ -72,8 +72,8 @@ export async function runSnapshot(options) {
|
|
|
72
72
|
result.snapshotId = entry.id;
|
|
73
73
|
result.label = entry.label;
|
|
74
74
|
const count = Object.keys(entry.manifests).length;
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
writeStdout(`✔ Restored ${count} package.json file(s) from snapshot "${entry.label}" (${entry.id})\n`);
|
|
76
|
+
writeStdout(" Re-run your package manager install to apply.\n");
|
|
77
77
|
break;
|
|
78
78
|
}
|
|
79
79
|
// ─ diff ──────────────────────────────────────────────────────────────────
|
|
@@ -92,23 +92,23 @@ export async function runSnapshot(options) {
|
|
|
92
92
|
const changes = diffManifests(entry.manifests, currentManifests);
|
|
93
93
|
result.diff = changes;
|
|
94
94
|
if (changes.length === 0) {
|
|
95
|
-
|
|
95
|
+
writeStdout(`✔ No dependency changes since snapshot "${entry.label}"\n`);
|
|
96
96
|
}
|
|
97
97
|
else {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
98
|
+
writeStdout(`\nDependency changes since snapshot "${entry.label}":\n\n`);
|
|
99
|
+
writeStdout(" " + "Package".padEnd(35) + "Before".padEnd(20) + "After\n");
|
|
100
|
+
writeStdout(" " + "─".repeat(65) + "\n");
|
|
101
101
|
for (const c of changes) {
|
|
102
|
-
|
|
102
|
+
writeStdout(" " + c.name.padEnd(35) + c.from.padEnd(20) + c.to + "\n");
|
|
103
103
|
}
|
|
104
|
-
|
|
104
|
+
writeStdout("\n");
|
|
105
105
|
}
|
|
106
106
|
break;
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
if (result.errors.length > 0) {
|
|
110
110
|
for (const err of result.errors) {
|
|
111
|
-
|
|
111
|
+
writeStderr(`[snapshot] ✖ ${err}\n`);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
114
|
return result;
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
import type { SnapshotEntry } from "../../types/index.js";
|
|
2
|
-
/**
|
|
3
|
-
* Lightweight SQLite-free snapshot store (uses a JSON file in the project root).
|
|
4
|
-
*
|
|
5
|
-
* Design goals:
|
|
6
|
-
* - No extra runtime dependencies (SQLite bindings vary by runtime)
|
|
7
|
-
* - Human-readable store file (git-committable if desired)
|
|
8
|
-
* - Atomic writes via tmp-rename to prevent corruption
|
|
9
|
-
* - Fast: entire store fits in memory for typical use (< 50 snapshots)
|
|
10
|
-
*/
|
|
11
2
|
export declare class SnapshotStore {
|
|
12
3
|
private readonly storePath;
|
|
13
4
|
private entries;
|
|
@@ -20,14 +11,11 @@ export declare class SnapshotStore {
|
|
|
20
11
|
findSnapshot(idOrLabel: string): Promise<SnapshotEntry | null>;
|
|
21
12
|
deleteSnapshot(idOrLabel: string): Promise<boolean>;
|
|
22
13
|
}
|
|
23
|
-
/** Captures current package.json and lockfile state for a set of directories. */
|
|
24
14
|
export declare function captureState(packageDirs: string[]): Promise<{
|
|
25
15
|
manifests: Record<string, string>;
|
|
26
16
|
lockfileHashes: Record<string, string>;
|
|
27
17
|
}>;
|
|
28
|
-
/** Restores package.json files from a snapshot's manifest map. */
|
|
29
18
|
export declare function restoreState(entry: SnapshotEntry): Promise<void>;
|
|
30
|
-
/** Computes a diff of dependency versions between two manifest snapshots. */
|
|
31
19
|
export declare function diffManifests(before: Record<string, string>, after: Record<string, string>): Array<{
|
|
32
20
|
name: string;
|
|
33
21
|
from: string;
|