@rainy-updates/cli 0.5.6 → 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 +133 -0
- package/README.md +90 -31
- package/dist/bin/cli.js +24 -482
- package/dist/bin/dispatch.d.ts +16 -0
- package/dist/bin/dispatch.js +147 -0
- package/dist/bin/help.d.ts +1 -0
- package/dist/bin/help.js +314 -0
- 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 +15 -4
- package/dist/commands/doctor/runner.js +6 -3
- 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/options.d.ts +6 -0
- package/dist/core/analysis/options.js +69 -0
- package/dist/core/analysis/review-items.d.ts +4 -0
- package/dist/core/analysis/review-items.js +128 -0
- package/dist/core/analysis/run-silenced.d.ts +1 -0
- package/dist/core/analysis/run-silenced.js +13 -0
- package/dist/core/analysis-bundle.js +3 -211
- 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/findings.d.ts +2 -0
- package/dist/core/doctor/findings.js +166 -0
- package/dist/core/doctor/render.d.ts +3 -0
- package/dist/core/doctor/render.js +44 -0
- package/dist/core/doctor/result.d.ts +2 -0
- package/dist/core/doctor/result.js +58 -0
- package/dist/core/doctor/score.d.ts +5 -0
- package/dist/core/doctor/score.js +28 -0
- 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.d.ts +3 -3
- package/dist/core/review-model.js +6 -67
- package/dist/core/review-verdict.d.ts +2 -0
- package/dist/core/review-verdict.js +14 -0
- package/dist/core/summary.js +12 -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 +22 -0
- package/dist/output/github.js +10 -0
- package/dist/output/sarif.js +16 -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 +104 -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,4 +1,3 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
const LOCKFILE_PRIORITY = [
|
|
4
3
|
"package-lock.json",
|
|
@@ -86,13 +85,9 @@ async function findNearestLockfiles(rootCwd, startDir) {
|
|
|
86
85
|
while (true) {
|
|
87
86
|
for (const fileName of LOCKFILE_PRIORITY) {
|
|
88
87
|
const candidate = path.join(current, fileName);
|
|
89
|
-
|
|
90
|
-
await fs.access(candidate);
|
|
88
|
+
if (await fileExists(candidate)) {
|
|
91
89
|
found.push(candidate);
|
|
92
90
|
}
|
|
93
|
-
catch {
|
|
94
|
-
// ignore missing
|
|
95
|
-
}
|
|
96
91
|
}
|
|
97
92
|
if (current === rootCwd)
|
|
98
93
|
break;
|
|
@@ -115,9 +110,6 @@ async function resolveFromPackageLock(lockfilePath, packageDir, packageName) {
|
|
|
115
110
|
if (version)
|
|
116
111
|
return version;
|
|
117
112
|
}
|
|
118
|
-
if (!relDir) {
|
|
119
|
-
return parsed.dependencies?.[packageName]?.version ?? null;
|
|
120
|
-
}
|
|
121
113
|
return parsed.dependencies?.[packageName]?.version ?? null;
|
|
122
114
|
}
|
|
123
115
|
async function resolveFromPnpmLock(lockfilePath, packageDir, packageName) {
|
|
@@ -149,8 +141,8 @@ async function resolveFromBunLock(lockfilePath, packageDir, packageName) {
|
|
|
149
141
|
async function readPackageLock(lockfilePath) {
|
|
150
142
|
let promise = packageLockCache.get(lockfilePath);
|
|
151
143
|
if (!promise) {
|
|
152
|
-
promise =
|
|
153
|
-
.
|
|
144
|
+
promise = Bun.file(lockfilePath)
|
|
145
|
+
.text()
|
|
154
146
|
.then((content) => JSON.parse(content));
|
|
155
147
|
packageLockCache.set(lockfilePath, promise);
|
|
156
148
|
}
|
|
@@ -159,7 +151,7 @@ async function readPackageLock(lockfilePath) {
|
|
|
159
151
|
async function readPnpmLock(lockfilePath) {
|
|
160
152
|
let promise = pnpmLockCache.get(lockfilePath);
|
|
161
153
|
if (!promise) {
|
|
162
|
-
promise =
|
|
154
|
+
promise = Bun.file(lockfilePath).text().then(parsePnpmLock);
|
|
163
155
|
pnpmLockCache.set(lockfilePath, promise);
|
|
164
156
|
}
|
|
165
157
|
return await promise;
|
|
@@ -167,7 +159,7 @@ async function readPnpmLock(lockfilePath) {
|
|
|
167
159
|
async function readBunLock(lockfilePath) {
|
|
168
160
|
let promise = bunLockCache.get(lockfilePath);
|
|
169
161
|
if (!promise) {
|
|
170
|
-
promise =
|
|
162
|
+
promise = Bun.file(lockfilePath).text().then(parseBunLock);
|
|
171
163
|
bunLockCache.set(lockfilePath, promise);
|
|
172
164
|
}
|
|
173
165
|
return await promise;
|
|
@@ -312,3 +304,11 @@ function normalizePnpmVersion(value) {
|
|
|
312
304
|
function trimYamlKey(value) {
|
|
313
305
|
return value.trim().replace(/^['"]|['"]$/g, "");
|
|
314
306
|
}
|
|
307
|
+
async function fileExists(filePath) {
|
|
308
|
+
try {
|
|
309
|
+
return await Bun.file(filePath).exists();
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
import { detectPackageManager, resolvePackageManager } from "../../pm/detect.js";
|
|
3
3
|
/**
|
|
4
4
|
* The "oracle" for bisect: installs a specific version of a package
|
|
5
5
|
* into the project's node_modules (via the shell), then runs --cmd.
|
|
@@ -11,7 +11,9 @@ export async function bisectOracle(packageName, version, options) {
|
|
|
11
11
|
process.stderr.write(`[bisect:dry-run] Would test ${packageName}@${version}\n`);
|
|
12
12
|
return "skip";
|
|
13
13
|
}
|
|
14
|
-
const
|
|
14
|
+
const detected = await detectPackageManager(options.cwd);
|
|
15
|
+
const packageManager = resolvePackageManager("auto", detected, "bun");
|
|
16
|
+
const installResult = await runShell(buildInstallCommand(packageManager, packageName, version), options.cwd);
|
|
15
17
|
if (installResult !== 0) {
|
|
16
18
|
process.stderr.write(`[bisect] Failed to install ${packageName}@${version}, skipping.\n`);
|
|
17
19
|
return "skip";
|
|
@@ -22,15 +24,30 @@ export async function bisectOracle(packageName, version, options) {
|
|
|
22
24
|
process.stderr.write(`[bisect] ${packageName}@${version} → ${outcome}\n`);
|
|
23
25
|
return outcome;
|
|
24
26
|
}
|
|
25
|
-
function runShell(command, cwd) {
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
27
|
+
async function runShell(command, cwd) {
|
|
28
|
+
try {
|
|
29
|
+
const shellCmd = process.env.SHELL || "sh";
|
|
30
|
+
const proc = Bun.spawn([shellCmd, "-c", command], {
|
|
29
31
|
cwd: path.resolve(cwd),
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
stdout: "pipe",
|
|
33
|
+
stderr: "pipe",
|
|
32
34
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
return await proc.exited;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function buildInstallCommand(packageManager, packageName, version) {
|
|
42
|
+
const spec = `${packageName}@${version}`;
|
|
43
|
+
switch (packageManager) {
|
|
44
|
+
case "bun":
|
|
45
|
+
return `bun add --exact --no-save ${spec}`;
|
|
46
|
+
case "pnpm":
|
|
47
|
+
return `pnpm add --save-exact --no-save ${spec}`;
|
|
48
|
+
case "yarn":
|
|
49
|
+
return `npm install --no-save --save-exact ${spec}`;
|
|
50
|
+
default:
|
|
51
|
+
return `npm install --no-save --save-exact ${spec}`;
|
|
52
|
+
}
|
|
36
53
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import
|
|
2
|
+
import { getRuntimeCwd } from "../../utils/runtime.js";
|
|
3
3
|
export function parseBisectArgs(args) {
|
|
4
4
|
const options = {
|
|
5
|
-
cwd:
|
|
5
|
+
cwd: getRuntimeCwd(),
|
|
6
6
|
packageName: "",
|
|
7
7
|
versionRange: undefined,
|
|
8
|
-
testCommand: "
|
|
8
|
+
testCommand: "",
|
|
9
9
|
concurrency: 4,
|
|
10
10
|
registryTimeoutMs: 8000,
|
|
11
11
|
cacheTtlSeconds: 3600,
|
|
@@ -1,26 +1,33 @@
|
|
|
1
|
+
import { detectPackageManager, resolvePackageManager } from "../../pm/detect.js";
|
|
1
2
|
import { fetchBisectVersions, bisectVersions } from "./engine.js";
|
|
2
3
|
/**
|
|
3
4
|
* Entry point for the `bisect` command. Lazy-loaded by cli.ts.
|
|
4
5
|
* Fully isolated: does NOT import anything from core/options.ts.
|
|
5
6
|
*/
|
|
6
7
|
export async function runBisect(options) {
|
|
7
|
-
|
|
8
|
-
const
|
|
8
|
+
const detected = await detectPackageManager(options.cwd);
|
|
9
|
+
const runtimeOptions = {
|
|
10
|
+
...options,
|
|
11
|
+
testCommand: options.testCommand ||
|
|
12
|
+
`${resolvePackageManager("auto", detected, "bun")} test`,
|
|
13
|
+
};
|
|
14
|
+
process.stderr.write(`\n[bisect] Fetching available versions for ${runtimeOptions.packageName}...\n`);
|
|
15
|
+
const versions = await fetchBisectVersions(runtimeOptions);
|
|
9
16
|
if (versions.length === 0) {
|
|
10
|
-
throw new Error(`No versions found for package "${
|
|
17
|
+
throw new Error(`No versions found for package "${runtimeOptions.packageName}".`);
|
|
11
18
|
}
|
|
12
19
|
process.stderr.write(`[bisect] Found ${versions.length} versions. Starting binary search...\n`);
|
|
13
|
-
if (
|
|
14
|
-
process.stderr.write(`[bisect] Range: ${
|
|
20
|
+
if (runtimeOptions.versionRange) {
|
|
21
|
+
process.stderr.write(`[bisect] Range: ${runtimeOptions.versionRange}\n`);
|
|
15
22
|
}
|
|
16
|
-
const result = await bisectVersions(versions,
|
|
23
|
+
const result = await bisectVersions(versions, runtimeOptions);
|
|
17
24
|
if (result.breakingVersion) {
|
|
18
|
-
process.stdout.write(`\n✖ Break introduced in ${
|
|
25
|
+
process.stdout.write(`\n✖ Break introduced in ${runtimeOptions.packageName}@${result.breakingVersion}\n` +
|
|
19
26
|
` Last good version: ${result.lastGoodVersion ?? "none"}\n` +
|
|
20
27
|
` Tested: ${result.totalVersionsTested} versions in ${result.iterations} iterations\n`);
|
|
21
28
|
}
|
|
22
29
|
else {
|
|
23
|
-
process.stdout.write(`\n✔ No breaking version found for ${
|
|
30
|
+
process.stdout.write(`\n✔ No breaking version found for ${runtimeOptions.packageName} (all versions passed).\n` +
|
|
24
31
|
` Tested: ${result.totalVersionsTested} versions\n`);
|
|
25
32
|
}
|
|
26
33
|
return result;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { getCacheDir } from "../../utils/runtime-paths.js";
|
|
4
4
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
5
5
|
class ChangelogCache {
|
|
6
6
|
db = null;
|
|
7
7
|
dbPath;
|
|
8
8
|
constructor() {
|
|
9
|
-
const basePath =
|
|
9
|
+
const basePath = getCacheDir();
|
|
10
10
|
this.dbPath = path.join(basePath, "cache.db");
|
|
11
11
|
}
|
|
12
12
|
async init() {
|
|
@@ -14,7 +14,7 @@ class ChangelogCache {
|
|
|
14
14
|
return;
|
|
15
15
|
try {
|
|
16
16
|
if (typeof Bun !== "undefined") {
|
|
17
|
-
await
|
|
17
|
+
await mkdir(path.dirname(this.dbPath), { recursive: true });
|
|
18
18
|
const mod = await import("bun:sqlite");
|
|
19
19
|
this.db = new mod.Database(this.dbPath, { create: true });
|
|
20
20
|
this.db.exec(`
|
|
@@ -111,7 +111,7 @@ export async function fetchChangelog(packageName, repositoryUrl) {
|
|
|
111
111
|
if (contentsRes.ok) {
|
|
112
112
|
const fileContent = await contentsRes.json();
|
|
113
113
|
if (fileContent.content && fileContent.encoding === "base64") {
|
|
114
|
-
content =
|
|
114
|
+
content = decodeBase64Utf8(fileContent.content);
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
}
|
|
@@ -128,3 +128,9 @@ export async function fetchChangelog(packageName, repositoryUrl) {
|
|
|
128
128
|
return null;
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
function decodeBase64Utf8(value) {
|
|
132
|
+
const normalized = value.replace(/\s+/g, "");
|
|
133
|
+
const binary = atob(normalized);
|
|
134
|
+
const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
|
|
135
|
+
return new TextDecoder().decode(bytes);
|
|
136
|
+
}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
export function parseDashboardArgs(args) {
|
|
2
3
|
const options = {
|
|
3
4
|
cwd: process.cwd(),
|
|
4
5
|
target: "latest",
|
|
6
|
+
filter: undefined,
|
|
7
|
+
reject: undefined,
|
|
5
8
|
includeKinds: [
|
|
6
9
|
"dependencies",
|
|
7
10
|
"devDependencies",
|
|
@@ -12,19 +15,44 @@ export function parseDashboardArgs(args) {
|
|
|
12
15
|
ci: false,
|
|
13
16
|
format: "table",
|
|
14
17
|
workspace: false,
|
|
18
|
+
jsonFile: undefined,
|
|
19
|
+
githubOutputFile: undefined,
|
|
20
|
+
sarifFile: undefined,
|
|
15
21
|
concurrency: 16,
|
|
16
22
|
registryTimeoutMs: 8000,
|
|
17
23
|
registryRetries: 3,
|
|
18
24
|
offline: false,
|
|
19
25
|
stream: false,
|
|
26
|
+
policyFile: undefined,
|
|
27
|
+
prReportFile: undefined,
|
|
28
|
+
failOn: "none",
|
|
29
|
+
maxUpdates: undefined,
|
|
30
|
+
fixPr: false,
|
|
31
|
+
fixBranch: "chore/rainy-updates",
|
|
32
|
+
fixCommitMessage: undefined,
|
|
33
|
+
fixDryRun: false,
|
|
34
|
+
fixPrNoCheckout: false,
|
|
35
|
+
fixPrBatchSize: undefined,
|
|
36
|
+
noPrReport: false,
|
|
20
37
|
logLevel: "info",
|
|
21
38
|
groupBy: "none",
|
|
39
|
+
groupMax: undefined,
|
|
40
|
+
cooldownDays: undefined,
|
|
41
|
+
prLimit: undefined,
|
|
22
42
|
onlyChanged: false,
|
|
23
43
|
ciProfile: "minimal",
|
|
24
44
|
lockfileMode: "preserve",
|
|
25
45
|
interactive: true,
|
|
26
46
|
showImpact: false,
|
|
27
47
|
showHomepage: true,
|
|
48
|
+
mode: "review",
|
|
49
|
+
focus: "all",
|
|
50
|
+
applySelected: false,
|
|
51
|
+
decisionPlanFile: undefined,
|
|
52
|
+
verify: "none",
|
|
53
|
+
testCommand: undefined,
|
|
54
|
+
verificationReportFile: undefined,
|
|
55
|
+
ciGate: "check",
|
|
28
56
|
};
|
|
29
57
|
for (let i = 0; i < args.length; i++) {
|
|
30
58
|
const arg = args[i];
|
|
@@ -44,16 +72,90 @@ export function parseDashboardArgs(args) {
|
|
|
44
72
|
if (arg === "--view") {
|
|
45
73
|
throw new Error("Missing value for --view");
|
|
46
74
|
}
|
|
75
|
+
if (arg === "--mode" && nextArg) {
|
|
76
|
+
if (nextArg === "check" ||
|
|
77
|
+
nextArg === "review" ||
|
|
78
|
+
nextArg === "upgrade") {
|
|
79
|
+
options.mode = nextArg;
|
|
80
|
+
i++;
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
throw new Error(`Invalid --mode: ${nextArg}`);
|
|
84
|
+
}
|
|
85
|
+
if (arg === "--mode") {
|
|
86
|
+
throw new Error("Missing value for --mode");
|
|
87
|
+
}
|
|
88
|
+
if (arg === "--focus" && nextArg) {
|
|
89
|
+
if (nextArg === "all" ||
|
|
90
|
+
nextArg === "security" ||
|
|
91
|
+
nextArg === "risk" ||
|
|
92
|
+
nextArg === "major" ||
|
|
93
|
+
nextArg === "blocked" ||
|
|
94
|
+
nextArg === "workspace") {
|
|
95
|
+
options.focus = nextArg;
|
|
96
|
+
i++;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
throw new Error(`Invalid --focus: ${nextArg}`);
|
|
100
|
+
}
|
|
101
|
+
if (arg === "--focus") {
|
|
102
|
+
throw new Error("Missing value for --focus");
|
|
103
|
+
}
|
|
104
|
+
if (arg === "--apply-selected") {
|
|
105
|
+
options.applySelected = true;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (arg === "--verify" && nextArg) {
|
|
109
|
+
if (nextArg === "none" ||
|
|
110
|
+
nextArg === "install" ||
|
|
111
|
+
nextArg === "test" ||
|
|
112
|
+
nextArg === "install,test") {
|
|
113
|
+
options.verify = nextArg;
|
|
114
|
+
i++;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
throw new Error(`Invalid --verify: ${nextArg}`);
|
|
118
|
+
}
|
|
119
|
+
if (arg === "--verify") {
|
|
120
|
+
throw new Error("Missing value for --verify");
|
|
121
|
+
}
|
|
122
|
+
if (arg === "--test-command" && nextArg) {
|
|
123
|
+
options.testCommand = nextArg;
|
|
124
|
+
i++;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (arg === "--test-command") {
|
|
128
|
+
throw new Error("Missing value for --test-command");
|
|
129
|
+
}
|
|
130
|
+
if (arg === "--verification-report-file" && nextArg) {
|
|
131
|
+
options.verificationReportFile = path.resolve(options.cwd, nextArg);
|
|
132
|
+
i++;
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
if (arg === "--verification-report-file") {
|
|
136
|
+
throw new Error("Missing value for --verification-report-file");
|
|
137
|
+
}
|
|
138
|
+
if (arg === "--plan-file" && nextArg) {
|
|
139
|
+
options.decisionPlanFile = path.resolve(options.cwd, nextArg);
|
|
140
|
+
i++;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (arg === "--plan-file") {
|
|
144
|
+
throw new Error("Missing value for --plan-file");
|
|
145
|
+
}
|
|
47
146
|
// Pass through common workspace / cwd args
|
|
48
147
|
if (arg === "--workspace") {
|
|
49
148
|
options.workspace = true;
|
|
50
149
|
continue;
|
|
51
150
|
}
|
|
52
151
|
if (arg === "--cwd" && nextArg) {
|
|
53
|
-
options.cwd = nextArg;
|
|
152
|
+
options.cwd = path.resolve(nextArg);
|
|
54
153
|
i++;
|
|
55
154
|
continue;
|
|
56
155
|
}
|
|
156
|
+
if (arg.startsWith("-")) {
|
|
157
|
+
throw new Error(`Unknown dashboard option: ${arg}`);
|
|
158
|
+
}
|
|
57
159
|
}
|
|
58
160
|
return options;
|
|
59
161
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { DashboardOptions, DashboardResult } from "../../types/index.js";
|
|
2
|
-
export declare function runDashboard(options: DashboardOptions): Promise<DashboardResult>;
|
|
1
|
+
import type { DashboardOptions, DashboardResult, ReviewResult } from "../../types/index.js";
|
|
2
|
+
export declare function runDashboard(options: DashboardOptions, prebuiltReview?: ReviewResult): Promise<DashboardResult>;
|
|
@@ -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,8 +41,14 @@ 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,
|
|
51
|
+
agentReport: false,
|
|
46
52
|
};
|
|
47
53
|
for (let i = 0; i < args.length; i += 1) {
|
|
48
54
|
const current = args[i];
|
|
@@ -66,6 +72,10 @@ export function parseDoctorArgs(args) {
|
|
|
66
72
|
options.includeChangelog = true;
|
|
67
73
|
continue;
|
|
68
74
|
}
|
|
75
|
+
if (current === "--agent-report") {
|
|
76
|
+
options.agentReport = true;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
69
79
|
if (current === "--json-file" && next) {
|
|
70
80
|
options.jsonFile = path.resolve(options.cwd, next);
|
|
71
81
|
i += 1;
|
|
@@ -74,8 +84,8 @@ export function parseDoctorArgs(args) {
|
|
|
74
84
|
if (current === "--json-file")
|
|
75
85
|
throw new Error("Missing value for --json-file");
|
|
76
86
|
if (current === "--help" || current === "-h") {
|
|
77
|
-
|
|
78
|
-
|
|
87
|
+
writeStdout(DOCTOR_HELP);
|
|
88
|
+
exitProcess(0);
|
|
79
89
|
}
|
|
80
90
|
if (current.startsWith("-"))
|
|
81
91
|
throw new Error(`Unknown doctor option: ${current}`);
|
|
@@ -92,6 +102,7 @@ Usage:
|
|
|
92
102
|
Options:
|
|
93
103
|
--verdict-only Print the 3-line quick verdict without counts
|
|
94
104
|
--include-changelog Include release note summaries in the aggregated review data
|
|
105
|
+
--agent-report Print a prompt-ready remediation report for coding agents
|
|
95
106
|
--workspace Scan all workspace packages
|
|
96
107
|
--json-file <path> Write JSON doctor report to file
|
|
97
108
|
--cwd <path>
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { buildReviewResult, createDoctorResult, renderDoctorResult } from "../../core/review-model.js";
|
|
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
|
+
? renderDoctorAgentReport(doctor)
|
|
10
|
+
: renderDoctorResult(doctor, options.verdictOnly);
|
|
11
|
+
writeStdout(output + "\n");
|
|
9
12
|
if (options.jsonFile) {
|
|
10
13
|
await writeFileAtomic(options.jsonFile, stableStringify(doctor, 2) + "\n");
|
|
11
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,
|