@rainy-updates/cli 0.5.7 → 0.6.1
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 +134 -0
- package/README.md +90 -31
- package/dist/bin/cli.js +11 -126
- package/dist/bin/dispatch.js +35 -32
- package/dist/bin/help.js +79 -2
- package/dist/bin/main.d.ts +1 -0
- package/dist/bin/main.js +126 -0
- package/dist/cache/cache.js +13 -11
- package/dist/commands/audit/parser.js +38 -2
- package/dist/commands/audit/runner.js +41 -61
- package/dist/commands/audit/targets.js +13 -13
- package/dist/commands/bisect/oracle.js +31 -11
- package/dist/commands/bisect/parser.js +3 -3
- package/dist/commands/bisect/runner.js +16 -8
- package/dist/commands/changelog/fetcher.js +11 -5
- package/dist/commands/dashboard/parser.js +144 -1
- package/dist/commands/dashboard/runner.d.ts +2 -2
- package/dist/commands/dashboard/runner.js +67 -37
- package/dist/commands/doctor/parser.js +53 -4
- package/dist/commands/doctor/runner.js +2 -2
- package/dist/commands/ga/parser.js +43 -4
- package/dist/commands/ga/runner.js +22 -13
- package/dist/commands/health/parser.js +38 -2
- package/dist/commands/health/runner.js +5 -1
- package/dist/commands/hook/parser.d.ts +2 -0
- package/dist/commands/hook/parser.js +40 -0
- package/dist/commands/hook/runner.d.ts +2 -0
- package/dist/commands/hook/runner.js +174 -0
- package/dist/commands/licenses/parser.js +39 -0
- package/dist/commands/licenses/runner.js +9 -5
- package/dist/commands/resolve/graph/builder.js +5 -1
- package/dist/commands/resolve/parser.js +39 -0
- package/dist/commands/resolve/runner.js +14 -4
- package/dist/commands/review/parser.js +101 -4
- package/dist/commands/review/runner.js +31 -5
- package/dist/commands/snapshot/parser.js +39 -0
- package/dist/commands/snapshot/runner.js +21 -18
- package/dist/commands/snapshot/store.d.ts +0 -12
- package/dist/commands/snapshot/store.js +26 -38
- package/dist/commands/unused/parser.js +39 -0
- package/dist/commands/unused/runner.js +10 -8
- package/dist/commands/unused/scanner.d.ts +2 -1
- package/dist/commands/unused/scanner.js +65 -52
- 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 +7 -3
- 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 +34 -28
- package/dist/core/options.d.ts +4 -1
- package/dist/core/options.js +152 -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 +108 -0
- package/dist/core/warm-cache.js +7 -3
- package/dist/generated/version.d.ts +1 -0
- package/dist/generated/version.js +2 -0
- package/dist/git/scope.d.ts +19 -0
- package/dist/git/scope.js +167 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/output/format.js +15 -0
- package/dist/output/github.js +6 -0
- package/dist/output/sarif.js +12 -18
- package/dist/parsers/package-json.js +2 -4
- package/dist/pm/detect.d.ts +40 -1
- package/dist/pm/detect.js +152 -9
- package/dist/pm/install.d.ts +3 -1
- package/dist/pm/install.js +18 -17
- package/dist/registry/npm.js +34 -76
- package/dist/rup +0 -0
- package/dist/types/index.d.ts +134 -5
- package/dist/ui/tui.d.ts +4 -1
- package/dist/ui/tui.js +156 -67
- 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.d.ts +7 -1
- package/dist/workspace/discover.js +67 -54
- package/package.json +24 -19
- 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,10 +1,9 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
import { promises as fs } from "node:fs";
|
|
3
|
-
import path from "node:path";
|
|
4
1
|
import { collectDependencies, readManifest, } from "../../parsers/package-json.js";
|
|
2
|
+
import { buildAddInvocation, createPackageManagerProfile, detectPackageManagerDetails, } from "../../pm/detect.js";
|
|
5
3
|
import { discoverPackageDirs } from "../../workspace/discover.js";
|
|
6
4
|
import { writeFileAtomic } from "../../utils/io.js";
|
|
7
5
|
import { stableStringify } from "../../utils/stable-json.js";
|
|
6
|
+
import { writeStderr, writeStdout } from "../../utils/runtime.js";
|
|
8
7
|
import { fetchAdvisories } from "./fetcher.js";
|
|
9
8
|
import { resolveAuditTargets } from "./targets.js";
|
|
10
9
|
import { filterBySeverity, buildPatchMap, renderAuditSourceHealth, renderAuditSummary, renderAuditTable, summarizeAdvisories, } from "./mapper.js";
|
|
@@ -29,7 +28,15 @@ export async function runAudit(options) {
|
|
|
29
28
|
unresolved: 0,
|
|
30
29
|
},
|
|
31
30
|
};
|
|
32
|
-
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace
|
|
31
|
+
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
|
|
32
|
+
git: options,
|
|
33
|
+
includeKinds: [
|
|
34
|
+
"dependencies",
|
|
35
|
+
"devDependencies",
|
|
36
|
+
"optionalDependencies",
|
|
37
|
+
],
|
|
38
|
+
includeDependents: options.affected === true,
|
|
39
|
+
});
|
|
33
40
|
const depsByDir = new Map();
|
|
34
41
|
for (const dir of packageDirs) {
|
|
35
42
|
let manifest;
|
|
@@ -55,7 +62,7 @@ export async function runAudit(options) {
|
|
|
55
62
|
return result;
|
|
56
63
|
}
|
|
57
64
|
if (!options.silent) {
|
|
58
|
-
|
|
65
|
+
writeStderr(`[audit] Querying ${describeSourceMode(options.sourceMode)} for ${targetResolution.targets.length} dependency version${targetResolution.targets.length === 1 ? "" : "s"}...\n`);
|
|
59
66
|
}
|
|
60
67
|
const fetched = await fetchAdvisories(targetResolution.targets, {
|
|
61
68
|
concurrency: options.concurrency,
|
|
@@ -81,12 +88,12 @@ export async function runAudit(options) {
|
|
|
81
88
|
result.autoFixable = advisories.filter((a) => a.patchedVersion !== null).length;
|
|
82
89
|
if (!options.silent) {
|
|
83
90
|
if (options.reportFormat === "summary") {
|
|
84
|
-
|
|
91
|
+
writeStdout(renderAuditSummary(result.packages) +
|
|
85
92
|
renderAuditSourceHealth(result.sourceHealth) +
|
|
86
93
|
"\n");
|
|
87
94
|
}
|
|
88
95
|
else if (options.reportFormat === "table" || !options.jsonFile) {
|
|
89
|
-
|
|
96
|
+
writeStdout(renderAuditTable(advisories) +
|
|
90
97
|
renderAuditSourceHealth(result.sourceHealth) +
|
|
91
98
|
"\n");
|
|
92
99
|
}
|
|
@@ -102,7 +109,7 @@ export async function runAudit(options) {
|
|
|
102
109
|
warnings: result.warnings,
|
|
103
110
|
}, 2) + "\n");
|
|
104
111
|
if (!options.silent) {
|
|
105
|
-
|
|
112
|
+
writeStderr(`[audit] JSON report written to ${options.jsonFile}\n`);
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
115
|
if (options.fix && result.autoFixable > 0) {
|
|
@@ -122,54 +129,46 @@ async function applyFix(advisories, options) {
|
|
|
122
129
|
const patchMap = buildPatchMap(advisories);
|
|
123
130
|
if (patchMap.size === 0)
|
|
124
131
|
return;
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
const
|
|
132
|
+
const detected = await detectPackageManagerDetails(options.cwd);
|
|
133
|
+
const profile = createPackageManagerProfile(options.packageManager, detected);
|
|
134
|
+
const install = buildInstallArgs(profile, patchMap);
|
|
135
|
+
const installCmd = install.display;
|
|
128
136
|
if (options.dryRun) {
|
|
129
137
|
if (!options.silent) {
|
|
130
|
-
|
|
138
|
+
writeStderr(`[audit] --dry-run: would execute:\n ${installCmd}\n`);
|
|
131
139
|
if (options.commit) {
|
|
132
140
|
const msg = buildCommitMessage(patchMap);
|
|
133
|
-
|
|
141
|
+
writeStderr(`[audit] --dry-run: would commit:\n git commit -m "${msg}"\n`);
|
|
134
142
|
}
|
|
135
143
|
}
|
|
136
144
|
return;
|
|
137
145
|
}
|
|
138
146
|
if (!options.silent) {
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
writeStderr(`[audit] Applying ${patchMap.size} fix(es)...\n`);
|
|
148
|
+
writeStderr(` → ${installCmd}\n`);
|
|
141
149
|
}
|
|
142
150
|
try {
|
|
143
|
-
await runCommand(
|
|
151
|
+
await runCommand(install.command, install.args, options.cwd);
|
|
144
152
|
}
|
|
145
153
|
catch (err) {
|
|
146
154
|
if (!options.silent) {
|
|
147
|
-
|
|
155
|
+
writeStderr(`[audit] Install failed: ${String(err)}\n`);
|
|
148
156
|
}
|
|
149
157
|
return;
|
|
150
158
|
}
|
|
151
159
|
if (!options.silent) {
|
|
152
|
-
|
|
160
|
+
writeStderr(`[audit] ✔ Patches applied successfully.\n`);
|
|
153
161
|
}
|
|
154
162
|
if (options.commit) {
|
|
155
163
|
await commitFix(patchMap, options.cwd, options.silent);
|
|
156
164
|
}
|
|
157
165
|
else if (!options.silent) {
|
|
158
|
-
|
|
166
|
+
writeStderr(`[audit] Tip: run with --commit to automatically commit the changes.\n`);
|
|
159
167
|
}
|
|
160
168
|
}
|
|
161
|
-
function buildInstallArgs(
|
|
169
|
+
function buildInstallArgs(profile, patchMap) {
|
|
162
170
|
const packages = [...patchMap.entries()].map(([n, v]) => `${n}@${v}`);
|
|
163
|
-
|
|
164
|
-
case "pnpm":
|
|
165
|
-
return ["add", ...packages];
|
|
166
|
-
case "bun":
|
|
167
|
-
return ["add", ...packages];
|
|
168
|
-
case "yarn":
|
|
169
|
-
return ["add", ...packages];
|
|
170
|
-
default:
|
|
171
|
-
return ["install", ...packages]; // npm
|
|
172
|
-
}
|
|
171
|
+
return buildAddInvocation(profile, packages);
|
|
173
172
|
}
|
|
174
173
|
async function commitFix(patchMap, cwd, silent) {
|
|
175
174
|
const msg = buildCommitMessage(patchMap);
|
|
@@ -205,48 +204,29 @@ function buildCommitMessage(patchMap) {
|
|
|
205
204
|
const names = items.map(([n]) => n).join(", ");
|
|
206
205
|
return `fix(security): patch ${items.length} vulnerabilities — ${names} (rup audit)`;
|
|
207
206
|
}
|
|
208
|
-
/** Detects the package manager in use by checking for lockfiles. */
|
|
209
|
-
async function detectPackageManager(cwd, explicit) {
|
|
210
|
-
if (explicit !== "auto")
|
|
211
|
-
return explicit;
|
|
212
|
-
const checks = [
|
|
213
|
-
["bun.lock", "bun"],
|
|
214
|
-
["bun.lockb", "bun"],
|
|
215
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
216
|
-
["yarn.lock", "yarn"],
|
|
217
|
-
];
|
|
218
|
-
for (const [lockfile, pm] of checks) {
|
|
219
|
-
try {
|
|
220
|
-
await fs.access(path.join(cwd, lockfile));
|
|
221
|
-
return pm;
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
// not found, try next
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
return "npm"; // default
|
|
228
|
-
}
|
|
229
207
|
/** Spawns a subprocess, pipes stdio live to the terminal. */
|
|
230
208
|
function runCommand(cmd, args, cwd, ignoreErrors = false) {
|
|
231
|
-
return new Promise((resolve, reject) => {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
209
|
+
return new Promise(async (resolve, reject) => {
|
|
210
|
+
try {
|
|
211
|
+
const proc = Bun.spawn([cmd, ...args], {
|
|
212
|
+
cwd,
|
|
213
|
+
stdin: "inherit",
|
|
214
|
+
stdout: "inherit",
|
|
215
|
+
stderr: "inherit",
|
|
216
|
+
});
|
|
217
|
+
const code = await proc.exited;
|
|
238
218
|
if (code === 0 || ignoreErrors) {
|
|
239
219
|
resolve();
|
|
240
220
|
}
|
|
241
221
|
else {
|
|
242
222
|
reject(new Error(`${cmd} exited with code ${code}`));
|
|
243
223
|
}
|
|
244
|
-
}
|
|
245
|
-
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
246
226
|
if (ignoreErrors)
|
|
247
227
|
resolve();
|
|
248
228
|
else
|
|
249
229
|
reject(err);
|
|
250
|
-
}
|
|
230
|
+
}
|
|
251
231
|
});
|
|
252
232
|
}
|
|
@@ -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 { buildAddInvocation, createPackageManagerProfile, detectPackageManagerDetails, } 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,12 @@ 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 detectPackageManagerDetails(options.cwd);
|
|
15
|
+
const profile = createPackageManagerProfile("auto", detected, "bun");
|
|
16
|
+
const installResult = await runCommand(buildAddInvocation(profile, [`${packageName}@${version}`], {
|
|
17
|
+
exact: true,
|
|
18
|
+
noSave: true,
|
|
19
|
+
}), options.cwd);
|
|
15
20
|
if (installResult !== 0) {
|
|
16
21
|
process.stderr.write(`[bisect] Failed to install ${packageName}@${version}, skipping.\n`);
|
|
17
22
|
return "skip";
|
|
@@ -22,15 +27,30 @@ export async function bisectOracle(packageName, version, options) {
|
|
|
22
27
|
process.stderr.write(`[bisect] ${packageName}@${version} → ${outcome}\n`);
|
|
23
28
|
return outcome;
|
|
24
29
|
}
|
|
25
|
-
function runShell(command, cwd) {
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
const
|
|
30
|
+
async function runShell(command, cwd) {
|
|
31
|
+
try {
|
|
32
|
+
const shellCmd = process.env.SHELL || "sh";
|
|
33
|
+
const proc = Bun.spawn([shellCmd, "-c", command], {
|
|
29
34
|
cwd: path.resolve(cwd),
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
stdout: "pipe",
|
|
36
|
+
stderr: "pipe",
|
|
32
37
|
});
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
return await proc.exited;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function runCommand(command, cwd) {
|
|
45
|
+
try {
|
|
46
|
+
const proc = Bun.spawn([command.command, ...command.args], {
|
|
47
|
+
cwd: path.resolve(cwd),
|
|
48
|
+
stdout: "pipe",
|
|
49
|
+
stderr: "pipe",
|
|
50
|
+
});
|
|
51
|
+
return await proc.exited;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return 1;
|
|
55
|
+
}
|
|
36
56
|
}
|
|
@@ -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,34 @@
|
|
|
1
|
+
import { buildTestCommand, createPackageManagerProfile, detectPackageManagerDetails, } 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 detectPackageManagerDetails(options.cwd);
|
|
9
|
+
const profile = createPackageManagerProfile("auto", detected, "bun");
|
|
10
|
+
const runtimeOptions = {
|
|
11
|
+
...options,
|
|
12
|
+
testCommand: options.testCommand ||
|
|
13
|
+
buildTestCommand(profile),
|
|
14
|
+
};
|
|
15
|
+
process.stderr.write(`\n[bisect] Fetching available versions for ${runtimeOptions.packageName}...\n`);
|
|
16
|
+
const versions = await fetchBisectVersions(runtimeOptions);
|
|
9
17
|
if (versions.length === 0) {
|
|
10
|
-
throw new Error(`No versions found for package "${
|
|
18
|
+
throw new Error(`No versions found for package "${runtimeOptions.packageName}".`);
|
|
11
19
|
}
|
|
12
20
|
process.stderr.write(`[bisect] Found ${versions.length} versions. Starting binary search...\n`);
|
|
13
|
-
if (
|
|
14
|
-
process.stderr.write(`[bisect] Range: ${
|
|
21
|
+
if (runtimeOptions.versionRange) {
|
|
22
|
+
process.stderr.write(`[bisect] Range: ${runtimeOptions.versionRange}\n`);
|
|
15
23
|
}
|
|
16
|
-
const result = await bisectVersions(versions,
|
|
24
|
+
const result = await bisectVersions(versions, runtimeOptions);
|
|
17
25
|
if (result.breakingVersion) {
|
|
18
|
-
process.stdout.write(`\n✖ Break introduced in ${
|
|
26
|
+
process.stdout.write(`\n✖ Break introduced in ${runtimeOptions.packageName}@${result.breakingVersion}\n` +
|
|
19
27
|
` Last good version: ${result.lastGoodVersion ?? "none"}\n` +
|
|
20
28
|
` Tested: ${result.totalVersionsTested} versions in ${result.iterations} iterations\n`);
|
|
21
29
|
}
|
|
22
30
|
else {
|
|
23
|
-
process.stdout.write(`\n✔ No breaking version found for ${
|
|
31
|
+
process.stdout.write(`\n✔ No breaking version found for ${runtimeOptions.packageName} (all versions passed).\n` +
|
|
24
32
|
` Tested: ${result.totalVersionsTested} versions\n`);
|
|
25
33
|
}
|
|
26
34
|
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,49 @@ 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,
|
|
43
|
+
affected: false,
|
|
44
|
+
staged: false,
|
|
45
|
+
baseRef: undefined,
|
|
46
|
+
headRef: undefined,
|
|
47
|
+
sinceRef: undefined,
|
|
23
48
|
ciProfile: "minimal",
|
|
24
49
|
lockfileMode: "preserve",
|
|
25
50
|
interactive: true,
|
|
26
51
|
showImpact: false,
|
|
27
52
|
showHomepage: true,
|
|
53
|
+
mode: "review",
|
|
54
|
+
focus: "all",
|
|
55
|
+
applySelected: false,
|
|
56
|
+
decisionPlanFile: undefined,
|
|
57
|
+
verify: "none",
|
|
58
|
+
testCommand: undefined,
|
|
59
|
+
verificationReportFile: undefined,
|
|
60
|
+
ciGate: "check",
|
|
28
61
|
};
|
|
29
62
|
for (let i = 0; i < args.length; i++) {
|
|
30
63
|
const arg = args[i];
|
|
@@ -44,16 +77,126 @@ export function parseDashboardArgs(args) {
|
|
|
44
77
|
if (arg === "--view") {
|
|
45
78
|
throw new Error("Missing value for --view");
|
|
46
79
|
}
|
|
80
|
+
if (arg === "--mode" && nextArg) {
|
|
81
|
+
if (nextArg === "check" ||
|
|
82
|
+
nextArg === "review" ||
|
|
83
|
+
nextArg === "upgrade") {
|
|
84
|
+
options.mode = nextArg;
|
|
85
|
+
i++;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Invalid --mode: ${nextArg}`);
|
|
89
|
+
}
|
|
90
|
+
if (arg === "--mode") {
|
|
91
|
+
throw new Error("Missing value for --mode");
|
|
92
|
+
}
|
|
93
|
+
if (arg === "--focus" && nextArg) {
|
|
94
|
+
if (nextArg === "all" ||
|
|
95
|
+
nextArg === "security" ||
|
|
96
|
+
nextArg === "risk" ||
|
|
97
|
+
nextArg === "major" ||
|
|
98
|
+
nextArg === "blocked" ||
|
|
99
|
+
nextArg === "workspace") {
|
|
100
|
+
options.focus = nextArg;
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Invalid --focus: ${nextArg}`);
|
|
105
|
+
}
|
|
106
|
+
if (arg === "--focus") {
|
|
107
|
+
throw new Error("Missing value for --focus");
|
|
108
|
+
}
|
|
109
|
+
if (arg === "--apply-selected") {
|
|
110
|
+
options.applySelected = true;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (arg === "--verify" && nextArg) {
|
|
114
|
+
if (nextArg === "none" ||
|
|
115
|
+
nextArg === "install" ||
|
|
116
|
+
nextArg === "test" ||
|
|
117
|
+
nextArg === "install,test") {
|
|
118
|
+
options.verify = nextArg;
|
|
119
|
+
i++;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
throw new Error(`Invalid --verify: ${nextArg}`);
|
|
123
|
+
}
|
|
124
|
+
if (arg === "--verify") {
|
|
125
|
+
throw new Error("Missing value for --verify");
|
|
126
|
+
}
|
|
127
|
+
if (arg === "--test-command" && nextArg) {
|
|
128
|
+
options.testCommand = nextArg;
|
|
129
|
+
i++;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (arg === "--test-command") {
|
|
133
|
+
throw new Error("Missing value for --test-command");
|
|
134
|
+
}
|
|
135
|
+
if (arg === "--verification-report-file" && nextArg) {
|
|
136
|
+
options.verificationReportFile = path.resolve(options.cwd, nextArg);
|
|
137
|
+
i++;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
if (arg === "--verification-report-file") {
|
|
141
|
+
throw new Error("Missing value for --verification-report-file");
|
|
142
|
+
}
|
|
143
|
+
if (arg === "--plan-file" && nextArg) {
|
|
144
|
+
options.decisionPlanFile = path.resolve(options.cwd, nextArg);
|
|
145
|
+
i++;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (arg === "--plan-file") {
|
|
149
|
+
throw new Error("Missing value for --plan-file");
|
|
150
|
+
}
|
|
47
151
|
// Pass through common workspace / cwd args
|
|
48
152
|
if (arg === "--workspace") {
|
|
49
153
|
options.workspace = true;
|
|
50
154
|
continue;
|
|
51
155
|
}
|
|
156
|
+
if (arg === "--only-changed") {
|
|
157
|
+
options.onlyChanged = true;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (arg === "--affected") {
|
|
161
|
+
options.affected = true;
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
if (arg === "--staged") {
|
|
165
|
+
options.staged = true;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (arg === "--base" && nextArg) {
|
|
169
|
+
options.baseRef = nextArg;
|
|
170
|
+
i++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (arg === "--base") {
|
|
174
|
+
throw new Error("Missing value for --base");
|
|
175
|
+
}
|
|
176
|
+
if (arg === "--head" && nextArg) {
|
|
177
|
+
options.headRef = nextArg;
|
|
178
|
+
i++;
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
if (arg === "--head") {
|
|
182
|
+
throw new Error("Missing value for --head");
|
|
183
|
+
}
|
|
184
|
+
if (arg === "--since" && nextArg) {
|
|
185
|
+
options.sinceRef = nextArg;
|
|
186
|
+
i++;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (arg === "--since") {
|
|
190
|
+
throw new Error("Missing value for --since");
|
|
191
|
+
}
|
|
52
192
|
if (arg === "--cwd" && nextArg) {
|
|
53
|
-
options.cwd = nextArg;
|
|
193
|
+
options.cwd = path.resolve(nextArg);
|
|
54
194
|
i++;
|
|
55
195
|
continue;
|
|
56
196
|
}
|
|
197
|
+
if (arg.startsWith("-")) {
|
|
198
|
+
throw new Error(`Unknown dashboard option: ${arg}`);
|
|
199
|
+
}
|
|
57
200
|
}
|
|
58
201
|
return options;
|
|
59
202
|
}
|
|
@@ -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>;
|