@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
package/dist/output/github.js
CHANGED
|
@@ -34,6 +34,16 @@ export async function writeGitHubOutput(filePath, result) {
|
|
|
34
34
|
`cache_backend=${result.summary.cacheBackend ?? ""}`,
|
|
35
35
|
`degraded_sources=${(result.summary.degradedSources ?? []).join(",")}`,
|
|
36
36
|
`ga_ready=${result.summary.gaReady === true ? "1" : "0"}`,
|
|
37
|
+
`dependency_health_score=${result.summary.dependencyHealthScore ?? ""}`,
|
|
38
|
+
`primary_finding_code=${result.summary.primaryFindingCode ?? ""}`,
|
|
39
|
+
`primary_finding_category=${result.summary.primaryFindingCategory ?? ""}`,
|
|
40
|
+
`next_action_reason=${result.summary.nextActionReason ?? ""}`,
|
|
41
|
+
`suggested_command=${result.summary.suggestedCommand ?? ""}`,
|
|
42
|
+
`decision_plan=${result.summary.decisionPlan ?? ""}`,
|
|
43
|
+
`interactive_surface=${result.summary.interactiveSurface ?? ""}`,
|
|
44
|
+
`queue_focus=${result.summary.queueFocus ?? ""}`,
|
|
45
|
+
`verification_state=${result.summary.verificationState ?? "not-run"}`,
|
|
46
|
+
`verification_failures=${result.summary.verificationFailures ?? 0}`,
|
|
37
47
|
`fix_pr_applied=${result.summary.fixPrApplied === true ? "1" : "0"}`,
|
|
38
48
|
`fix_pr_branches_created=${result.summary.fixPrBranchesCreated}`,
|
|
39
49
|
`fix_pr_branch=${result.summary.fixBranchName ?? ""}`,
|
package/dist/output/sarif.js
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
2
|
export function createSarifReport(result) {
|
|
5
3
|
const dependencyRuleId = "rainy-updates/dependency-update";
|
|
6
4
|
const runtimeRuleId = "rainy-updates/runtime-error";
|
|
@@ -46,7 +44,9 @@ export function createSarifReport(result) {
|
|
|
46
44
|
runId: result.summary.runId,
|
|
47
45
|
},
|
|
48
46
|
}));
|
|
49
|
-
const errorResults = [...result.errors]
|
|
47
|
+
const errorResults = [...result.errors]
|
|
48
|
+
.sort((a, b) => a.localeCompare(b))
|
|
49
|
+
.map((error) => ({
|
|
50
50
|
ruleId: runtimeRuleId,
|
|
51
51
|
level: "error",
|
|
52
52
|
message: {
|
|
@@ -66,12 +66,16 @@ export function createSarifReport(result) {
|
|
|
66
66
|
{
|
|
67
67
|
id: dependencyRuleId,
|
|
68
68
|
shortDescription: { text: "Dependency update available" },
|
|
69
|
-
fullDescription: {
|
|
69
|
+
fullDescription: {
|
|
70
|
+
text: "A dependency has a newer version according to configured target.",
|
|
71
|
+
},
|
|
70
72
|
},
|
|
71
73
|
{
|
|
72
74
|
id: runtimeRuleId,
|
|
73
75
|
shortDescription: { text: "Dependency resolution error" },
|
|
74
|
-
fullDescription: {
|
|
76
|
+
fullDescription: {
|
|
77
|
+
text: "The resolver could not fetch or parse package metadata.",
|
|
78
|
+
},
|
|
75
79
|
},
|
|
76
80
|
],
|
|
77
81
|
},
|
|
@@ -101,6 +105,10 @@ export function createSarifReport(result) {
|
|
|
101
105
|
monitorPackages: result.summary.monitorPackages ?? 0,
|
|
102
106
|
degradedSources: result.summary.degradedSources ?? [],
|
|
103
107
|
cacheBackend: result.summary.cacheBackend,
|
|
108
|
+
dependencyHealthScore: result.summary.dependencyHealthScore,
|
|
109
|
+
primaryFindingCode: result.summary.primaryFindingCode,
|
|
110
|
+
primaryFindingCategory: result.summary.primaryFindingCategory,
|
|
111
|
+
nextActionReason: result.summary.nextActionReason,
|
|
104
112
|
},
|
|
105
113
|
},
|
|
106
114
|
],
|
|
@@ -111,15 +119,11 @@ function getToolVersion() {
|
|
|
111
119
|
if (TOOL_VERSION_CACHE)
|
|
112
120
|
return TOOL_VERSION_CACHE;
|
|
113
121
|
try {
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
const content = readFileSync(packageJsonPath, "utf8");
|
|
117
|
-
const parsed = JSON.parse(content);
|
|
118
|
-
TOOL_VERSION_CACHE = parsed.version ?? "0.0.0";
|
|
119
|
-
return TOOL_VERSION_CACHE;
|
|
122
|
+
const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
|
|
123
|
+
TOOL_VERSION_CACHE = packageJson.version ?? "0.0.0";
|
|
120
124
|
}
|
|
121
125
|
catch {
|
|
122
126
|
TOOL_VERSION_CACHE = "0.0.0";
|
|
123
|
-
return TOOL_VERSION_CACHE;
|
|
124
127
|
}
|
|
128
|
+
return TOOL_VERSION_CACHE;
|
|
125
129
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
1
|
import path from "node:path";
|
|
3
2
|
const DEPENDENCY_KINDS = [
|
|
4
3
|
"dependencies",
|
|
@@ -11,13 +10,12 @@ export function getPackageJsonPath(cwd) {
|
|
|
11
10
|
}
|
|
12
11
|
export async function readManifest(cwd) {
|
|
13
12
|
const filePath = getPackageJsonPath(cwd);
|
|
14
|
-
|
|
15
|
-
return JSON.parse(content);
|
|
13
|
+
return (await Bun.file(filePath).json());
|
|
16
14
|
}
|
|
17
15
|
export async function writeManifest(cwd, manifest) {
|
|
18
16
|
const filePath = getPackageJsonPath(cwd);
|
|
19
17
|
const content = JSON.stringify(manifest, null, 2) + "\n";
|
|
20
|
-
await
|
|
18
|
+
await Bun.write(filePath, content);
|
|
21
19
|
}
|
|
22
20
|
export function collectDependencies(manifest, includeKinds) {
|
|
23
21
|
const deps = [];
|
package/dist/pm/detect.d.ts
CHANGED
|
@@ -1 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import type { DetectedPackageManager, SelectedPackageManager, SupportedPackageManager } from "../types/index.js";
|
|
2
|
+
export declare function detectPackageManager(cwd: string): Promise<DetectedPackageManager>;
|
|
3
|
+
export declare function resolvePackageManager(requested: SelectedPackageManager, detected: DetectedPackageManager, fallback?: SupportedPackageManager): SupportedPackageManager;
|
package/dist/pm/detect.js
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
|
-
import { access } from "node:fs/promises";
|
|
2
1
|
import path from "node:path";
|
|
2
|
+
const PACKAGE_MANAGER_LOCKFILES = [
|
|
3
|
+
["bun.lock", "bun"],
|
|
4
|
+
["bun.lockb", "bun"],
|
|
5
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
6
|
+
["package-lock.json", "npm"],
|
|
7
|
+
["npm-shrinkwrap.json", "npm"],
|
|
8
|
+
["yarn.lock", "yarn"],
|
|
9
|
+
];
|
|
3
10
|
export async function detectPackageManager(cwd) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
return "pnpm";
|
|
9
|
-
}
|
|
10
|
-
catch {
|
|
11
|
-
// noop
|
|
11
|
+
for (const [lockfile, packageManager] of PACKAGE_MANAGER_LOCKFILES) {
|
|
12
|
+
if (await fileExists(path.join(cwd, lockfile))) {
|
|
13
|
+
return packageManager;
|
|
14
|
+
}
|
|
12
15
|
}
|
|
16
|
+
return "unknown";
|
|
17
|
+
}
|
|
18
|
+
export function resolvePackageManager(requested, detected, fallback = "npm") {
|
|
19
|
+
if (requested !== "auto")
|
|
20
|
+
return requested;
|
|
21
|
+
if (detected !== "unknown")
|
|
22
|
+
return detected;
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
async function fileExists(filePath) {
|
|
13
26
|
try {
|
|
14
|
-
await
|
|
15
|
-
return "npm";
|
|
27
|
+
return await Bun.file(filePath).exists();
|
|
16
28
|
}
|
|
17
29
|
catch {
|
|
18
|
-
return
|
|
30
|
+
return false;
|
|
19
31
|
}
|
|
20
32
|
}
|
package/dist/pm/install.d.ts
CHANGED
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
import type { DetectedPackageManager, SelectedPackageManager } from "../types/index.js";
|
|
2
|
+
export declare function installDependencies(cwd: string, packageManager: SelectedPackageManager, detected: DetectedPackageManager): Promise<void>;
|
package/dist/pm/install.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolvePackageManager } from "./detect.js";
|
|
2
2
|
export async function installDependencies(cwd, packageManager, detected) {
|
|
3
|
-
const
|
|
4
|
-
const command = selected;
|
|
3
|
+
const command = resolvePackageManager(packageManager, detected);
|
|
5
4
|
const args = ["install"];
|
|
6
|
-
|
|
7
|
-
const
|
|
5
|
+
try {
|
|
6
|
+
const proc = Bun.spawn([command, ...args], {
|
|
8
7
|
cwd,
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
stdin: "inherit",
|
|
9
|
+
stdout: "inherit",
|
|
10
|
+
stderr: "inherit",
|
|
11
11
|
});
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
});
|
|
12
|
+
const code = await proc.exited;
|
|
13
|
+
if (code !== 0) {
|
|
14
|
+
throw new Error(`${command} ${args.join(" ")} failed with exit code ${code}`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
19
|
+
}
|
|
21
20
|
}
|
package/dist/registry/npm.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { promises as fs } from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
1
|
import path from "node:path";
|
|
4
|
-
import process from "node:process";
|
|
5
2
|
import { asyncPool } from "../utils/async-pool.js";
|
|
3
|
+
import { getHomeDir } from "../utils/runtime-paths.js";
|
|
4
|
+
import { getRuntimeCwd, readEnv } from "../utils/runtime.js";
|
|
6
5
|
const DEFAULT_TIMEOUT_MS = 8000;
|
|
7
6
|
const USER_AGENT = "@rainy-updates/cli";
|
|
8
7
|
const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
@@ -52,7 +51,9 @@ export class NpmRegistryClient {
|
|
|
52
51
|
catch (error) {
|
|
53
52
|
lastError = String(error);
|
|
54
53
|
if (attempt < retries) {
|
|
55
|
-
const backoffMs = error instanceof RetryableRegistryError
|
|
54
|
+
const backoffMs = error instanceof RetryableRegistryError
|
|
55
|
+
? error.waitMs
|
|
56
|
+
: computeBackoffMs(attempt);
|
|
56
57
|
await sleep(backoffMs);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
@@ -136,10 +137,7 @@ class RetryableRegistryError extends Error {
|
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
async function createRequester(cwd) {
|
|
139
|
-
const registryConfig = await loadRegistryConfig(cwd ??
|
|
140
|
-
const undiciRequester = await tryCreateUndiciRequester(registryConfig);
|
|
141
|
-
if (undiciRequester)
|
|
142
|
-
return undiciRequester;
|
|
140
|
+
const registryConfig = await loadRegistryConfig(cwd ?? getRuntimeCwd());
|
|
143
141
|
return async (packageName, timeoutMs) => {
|
|
144
142
|
const controller = new AbortController();
|
|
145
143
|
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -158,7 +156,9 @@ async function createRequester(cwd) {
|
|
|
158
156
|
headers,
|
|
159
157
|
signal: controller.signal,
|
|
160
158
|
});
|
|
161
|
-
const data = (await response
|
|
159
|
+
const data = (await response
|
|
160
|
+
.json()
|
|
161
|
+
.catch(() => null));
|
|
162
162
|
return {
|
|
163
163
|
status: response.status,
|
|
164
164
|
data,
|
|
@@ -170,74 +170,17 @@ async function createRequester(cwd) {
|
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
-
async function tryCreateUndiciRequester(registryConfig) {
|
|
174
|
-
try {
|
|
175
|
-
const dynamicImport = Function("specifier", "return import(specifier)");
|
|
176
|
-
const undici = await dynamicImport("undici");
|
|
177
|
-
return async (packageName, timeoutMs) => {
|
|
178
|
-
const controller = new AbortController();
|
|
179
|
-
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
180
|
-
const registry = resolveRegistryForPackage(packageName, registryConfig);
|
|
181
|
-
const url = buildRegistryUrl(registry, packageName);
|
|
182
|
-
const authHeader = resolveAuthHeader(registry, registryConfig);
|
|
183
|
-
const headers = {
|
|
184
|
-
accept: "application/json",
|
|
185
|
-
"user-agent": USER_AGENT,
|
|
186
|
-
};
|
|
187
|
-
if (authHeader) {
|
|
188
|
-
headers.authorization = authHeader;
|
|
189
|
-
}
|
|
190
|
-
try {
|
|
191
|
-
const res = await undici.request(url, {
|
|
192
|
-
method: "GET",
|
|
193
|
-
headers,
|
|
194
|
-
signal: controller.signal,
|
|
195
|
-
});
|
|
196
|
-
const bodyText = await res.body.text();
|
|
197
|
-
let data = null;
|
|
198
|
-
try {
|
|
199
|
-
data = JSON.parse(bodyText);
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
data = null;
|
|
203
|
-
}
|
|
204
|
-
const retryAfter = (() => {
|
|
205
|
-
const header = res.headers["retry-after"];
|
|
206
|
-
if (Array.isArray(header))
|
|
207
|
-
return header[0] ?? null;
|
|
208
|
-
if (typeof header === "string")
|
|
209
|
-
return header;
|
|
210
|
-
return null;
|
|
211
|
-
})();
|
|
212
|
-
return {
|
|
213
|
-
status: res.statusCode,
|
|
214
|
-
data,
|
|
215
|
-
retryAfterMs: parseRetryAfterHeader(retryAfter),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
finally {
|
|
219
|
-
clearTimeout(timeout);
|
|
220
|
-
}
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
catch {
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
173
|
export async function loadRegistryConfig(cwd) {
|
|
228
|
-
const homeNpmrc = path.join(
|
|
174
|
+
const homeNpmrc = path.join(getHomeDir(), ".npmrc");
|
|
229
175
|
const projectNpmrc = path.join(cwd, ".npmrc");
|
|
230
176
|
const merged = new Map();
|
|
231
177
|
for (const filePath of [homeNpmrc, projectNpmrc]) {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
catch {
|
|
240
|
-
// ignore missing/unreadable file
|
|
178
|
+
const content = await readOptionalTextFile(filePath);
|
|
179
|
+
if (!content)
|
|
180
|
+
continue;
|
|
181
|
+
const parsed = parseNpmrc(content);
|
|
182
|
+
for (const [key, value] of parsed) {
|
|
183
|
+
merged.set(key, value);
|
|
241
184
|
}
|
|
242
185
|
}
|
|
243
186
|
const defaultRegistry = normalizeRegistryUrl(merged.get("registry") ?? DEFAULT_REGISTRY);
|
|
@@ -272,18 +215,33 @@ export async function loadRegistryConfig(cwd) {
|
|
|
272
215
|
authByRegistry.set(registry, current);
|
|
273
216
|
}
|
|
274
217
|
if (merged.get("always-auth") === "true") {
|
|
275
|
-
const current = authByRegistry.get(defaultRegistry) ?? {
|
|
218
|
+
const current = authByRegistry.get(defaultRegistry) ?? {
|
|
219
|
+
alwaysAuth: false,
|
|
220
|
+
};
|
|
276
221
|
current.alwaysAuth = true;
|
|
277
222
|
authByRegistry.set(defaultRegistry, current);
|
|
278
223
|
}
|
|
279
224
|
return { defaultRegistry, scopedRegistries, authByRegistry };
|
|
280
225
|
}
|
|
226
|
+
async function readOptionalTextFile(filePath) {
|
|
227
|
+
try {
|
|
228
|
+
const file = Bun.file(filePath);
|
|
229
|
+
if (!(await file.exists()))
|
|
230
|
+
return null;
|
|
231
|
+
return await file.text();
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
281
237
|
function parseNpmrc(content) {
|
|
282
238
|
const values = new Map();
|
|
283
239
|
const lines = content.split(/\r?\n/);
|
|
284
240
|
for (const line of lines) {
|
|
285
241
|
const trimmed = line.trim();
|
|
286
|
-
if (trimmed.length === 0 ||
|
|
242
|
+
if (trimmed.length === 0 ||
|
|
243
|
+
trimmed.startsWith("#") ||
|
|
244
|
+
trimmed.startsWith(";"))
|
|
287
245
|
continue;
|
|
288
246
|
const separator = trimmed.indexOf("=");
|
|
289
247
|
if (separator <= 0)
|
|
@@ -297,7 +255,7 @@ function parseNpmrc(content) {
|
|
|
297
255
|
return values;
|
|
298
256
|
}
|
|
299
257
|
function substituteEnvValue(value) {
|
|
300
|
-
return value.replace(/\$\{([^}]+)\}/g, (_match, name) =>
|
|
258
|
+
return value.replace(/\$\{([^}]+)\}/g, (_match, name) => readEnv(name) ?? "");
|
|
301
259
|
}
|
|
302
260
|
function normalizeRegistryUrl(value) {
|
|
303
261
|
const normalized = value.endsWith("/") ? value : `${value}/`;
|
package/dist/rup
ADDED
|
Binary file
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
export type DependencyKind = "dependencies" | "devDependencies" | "optionalDependencies" | "peerDependencies";
|
|
2
|
+
export type SupportedPackageManager = "bun" | "npm" | "pnpm" | "yarn";
|
|
3
|
+
export type DetectedPackageManager = SupportedPackageManager | "unknown";
|
|
4
|
+
export type SelectedPackageManager = "auto" | SupportedPackageManager;
|
|
2
5
|
export type TargetLevel = "patch" | "minor" | "major" | "latest";
|
|
3
6
|
export type GroupBy = "none" | "name" | "scope" | "kind" | "risk";
|
|
4
7
|
export type CiProfile = "minimal" | "strict" | "enterprise";
|
|
5
8
|
export type LockfileMode = "preserve" | "update" | "error";
|
|
6
9
|
export type Verdict = "safe" | "review" | "blocked" | "actionable";
|
|
7
10
|
export type RiskLevel = "critical" | "high" | "medium" | "low";
|
|
11
|
+
export type DashboardMode = "check" | "review" | "upgrade";
|
|
12
|
+
export type QueueFocus = "all" | "security" | "risk" | "major" | "blocked" | "workspace";
|
|
13
|
+
export type InteractiveSurface = "dashboard";
|
|
14
|
+
export type VerificationState = "not-run" | "passed" | "failed";
|
|
15
|
+
export type VerificationMode = "none" | "install" | "test" | "install,test";
|
|
16
|
+
export type CiGate = "check" | "doctor" | "review" | "upgrade";
|
|
17
|
+
export type DoctorFindingSeverity = "error" | "warning";
|
|
18
|
+
export type DoctorScoreLabel = "Strong" | "Needs Review" | "Action Needed" | "Blocked / Critical";
|
|
19
|
+
export type DoctorFindingCategory = "Security" | "Compatibility" | "Policy" | "Operational Health" | "Licensing" | "Unused / Cleanup" | "Release Risk" | "Registry / Execution" | "Workspace Integrity";
|
|
8
20
|
export type RiskCategory = "known-vulnerability" | "behavioral-risk" | "operational-health";
|
|
9
21
|
export type MaintainerChurnStatus = "unknown" | "stable" | "elevated-change";
|
|
10
22
|
export type PolicyAction = "allow" | "review" | "block" | "monitor";
|
|
@@ -53,13 +65,19 @@ export interface RunOptions {
|
|
|
53
65
|
interactive: boolean;
|
|
54
66
|
showImpact: boolean;
|
|
55
67
|
showHomepage: boolean;
|
|
68
|
+
decisionPlanFile?: string;
|
|
69
|
+
verify: VerificationMode;
|
|
70
|
+
testCommand?: string;
|
|
71
|
+
verificationReportFile?: string;
|
|
72
|
+
ciGate: CiGate;
|
|
56
73
|
}
|
|
57
74
|
export interface CheckOptions extends RunOptions {
|
|
58
75
|
}
|
|
59
76
|
export interface UpgradeOptions extends RunOptions {
|
|
60
77
|
install: boolean;
|
|
61
|
-
packageManager:
|
|
78
|
+
packageManager: SelectedPackageManager;
|
|
62
79
|
sync: boolean;
|
|
80
|
+
fromPlanFile?: string;
|
|
63
81
|
}
|
|
64
82
|
export interface BaselineOptions {
|
|
65
83
|
cwd: string;
|
|
@@ -143,8 +161,51 @@ export interface ArtifactManifest {
|
|
|
143
161
|
githubOutputFile?: string;
|
|
144
162
|
sarifFile?: string;
|
|
145
163
|
prReportFile?: string;
|
|
164
|
+
verificationReportFile?: string;
|
|
146
165
|
};
|
|
147
166
|
}
|
|
167
|
+
export interface VerificationCheck {
|
|
168
|
+
name: "install" | "test";
|
|
169
|
+
command: string;
|
|
170
|
+
passed: boolean;
|
|
171
|
+
exitCode: number;
|
|
172
|
+
durationMs: number;
|
|
173
|
+
error?: string;
|
|
174
|
+
}
|
|
175
|
+
export interface VerificationResult {
|
|
176
|
+
mode: VerificationMode;
|
|
177
|
+
passed: boolean;
|
|
178
|
+
checks: VerificationCheck[];
|
|
179
|
+
}
|
|
180
|
+
export interface DecisionPlanItem {
|
|
181
|
+
packagePath: string;
|
|
182
|
+
name: string;
|
|
183
|
+
kind: DependencyKind;
|
|
184
|
+
fromRange: string;
|
|
185
|
+
toRange: string;
|
|
186
|
+
toVersionResolved: string;
|
|
187
|
+
diffType: TargetLevel;
|
|
188
|
+
riskLevel?: RiskLevel;
|
|
189
|
+
riskScore?: number;
|
|
190
|
+
policyAction?: PolicyAction;
|
|
191
|
+
decisionState?: DecisionState;
|
|
192
|
+
selected: boolean;
|
|
193
|
+
}
|
|
194
|
+
export interface DecisionPlan {
|
|
195
|
+
contractVersion: "1";
|
|
196
|
+
createdAt: string;
|
|
197
|
+
sourceCommand: string;
|
|
198
|
+
mode: DashboardMode;
|
|
199
|
+
focus: QueueFocus;
|
|
200
|
+
projectPath: string;
|
|
201
|
+
target: TargetLevel;
|
|
202
|
+
interactiveSurface: InteractiveSurface;
|
|
203
|
+
summary: {
|
|
204
|
+
totalItems: number;
|
|
205
|
+
selectedItems: number;
|
|
206
|
+
};
|
|
207
|
+
items: DecisionPlanItem[];
|
|
208
|
+
}
|
|
148
209
|
export interface Summary {
|
|
149
210
|
contractVersion: "2";
|
|
150
211
|
scannedPackages: number;
|
|
@@ -204,11 +265,23 @@ export interface Summary {
|
|
|
204
265
|
cacheBackend?: "sqlite" | "file";
|
|
205
266
|
binaryRecommended?: boolean;
|
|
206
267
|
gaReady?: boolean;
|
|
268
|
+
dependencyHealthScore?: number;
|
|
269
|
+
findingCountsByCategory?: Partial<Record<DoctorFindingCategory, number>>;
|
|
270
|
+
findingCountsBySeverity?: Partial<Record<DoctorFindingSeverity, number>>;
|
|
271
|
+
primaryFindingCode?: string;
|
|
272
|
+
primaryFindingCategory?: DoctorFindingCategory;
|
|
273
|
+
nextActionReason?: string;
|
|
274
|
+
suggestedCommand?: string;
|
|
275
|
+
decisionPlan?: string;
|
|
276
|
+
interactiveSurface?: InteractiveSurface;
|
|
277
|
+
queueFocus?: QueueFocus;
|
|
278
|
+
verificationState?: VerificationState;
|
|
279
|
+
verificationFailures?: number;
|
|
207
280
|
}
|
|
208
281
|
export interface CheckResult {
|
|
209
282
|
projectPath: string;
|
|
210
283
|
packagePaths: string[];
|
|
211
|
-
packageManager:
|
|
284
|
+
packageManager: DetectedPackageManager;
|
|
212
285
|
target: TargetLevel;
|
|
213
286
|
timestamp: string;
|
|
214
287
|
summary: Summary;
|
|
@@ -251,7 +324,7 @@ export interface AuditOptions {
|
|
|
251
324
|
fix: boolean;
|
|
252
325
|
dryRun: boolean;
|
|
253
326
|
commit: boolean;
|
|
254
|
-
packageManager:
|
|
327
|
+
packageManager: SelectedPackageManager;
|
|
255
328
|
reportFormat: AuditReportFormat;
|
|
256
329
|
sourceMode: AuditSourceMode;
|
|
257
330
|
jsonFile?: string;
|
|
@@ -423,6 +496,7 @@ export interface ReviewResult {
|
|
|
423
496
|
updates: PackageUpdate[];
|
|
424
497
|
errors: string[];
|
|
425
498
|
warnings: string[];
|
|
499
|
+
decisionPlan?: DecisionPlan;
|
|
426
500
|
}
|
|
427
501
|
export interface ReviewOptions extends CheckOptions {
|
|
428
502
|
securityOnly: boolean;
|
|
@@ -430,17 +504,37 @@ export interface ReviewOptions extends CheckOptions {
|
|
|
430
504
|
diff?: TargetLevel;
|
|
431
505
|
applySelected: boolean;
|
|
432
506
|
showChangelog?: boolean;
|
|
507
|
+
queueFocus?: QueueFocus;
|
|
433
508
|
}
|
|
434
509
|
export interface DoctorOptions extends CheckOptions {
|
|
435
510
|
verdictOnly: boolean;
|
|
436
511
|
includeChangelog?: boolean;
|
|
512
|
+
agentReport?: boolean;
|
|
513
|
+
}
|
|
514
|
+
export interface DoctorFinding {
|
|
515
|
+
id: string;
|
|
516
|
+
code: string;
|
|
517
|
+
category: DoctorFindingCategory;
|
|
518
|
+
severity: DoctorFindingSeverity;
|
|
519
|
+
scope: "project" | "package";
|
|
520
|
+
packageName?: string;
|
|
521
|
+
workspace?: string;
|
|
522
|
+
summary: string;
|
|
523
|
+
details?: string;
|
|
524
|
+
help?: string;
|
|
525
|
+
recommendedAction?: string;
|
|
526
|
+
evidence?: string[];
|
|
437
527
|
}
|
|
438
528
|
export interface DoctorResult {
|
|
439
529
|
verdict: Verdict;
|
|
530
|
+
score: number;
|
|
531
|
+
scoreLabel: DoctorScoreLabel;
|
|
440
532
|
summary: Summary;
|
|
441
533
|
review: ReviewResult;
|
|
534
|
+
findings: DoctorFinding[];
|
|
442
535
|
primaryFindings: string[];
|
|
443
536
|
recommendedCommand: string;
|
|
537
|
+
nextActionReason: string;
|
|
444
538
|
}
|
|
445
539
|
export interface AnalysisBundle {
|
|
446
540
|
check: CheckResult;
|
|
@@ -454,11 +548,16 @@ export interface AnalysisBundle {
|
|
|
454
548
|
}
|
|
455
549
|
export interface DashboardOptions extends CheckOptions {
|
|
456
550
|
view?: "dependencies" | "security" | "health";
|
|
551
|
+
mode: DashboardMode;
|
|
552
|
+
focus: QueueFocus;
|
|
553
|
+
applySelected: boolean;
|
|
457
554
|
}
|
|
458
555
|
export interface DashboardResult {
|
|
459
556
|
completed: boolean;
|
|
460
557
|
errors: string[];
|
|
461
558
|
warnings: string[];
|
|
559
|
+
selectedUpdates: number;
|
|
560
|
+
decisionPlanFile?: string;
|
|
462
561
|
}
|
|
463
562
|
export type UnusedKind = "declared-not-imported" | "imported-not-declared";
|
|
464
563
|
export interface UnusedDependency {
|
|
@@ -573,14 +672,14 @@ export interface GaOptions {
|
|
|
573
672
|
jsonFile?: string;
|
|
574
673
|
}
|
|
575
674
|
export interface GaCheck {
|
|
576
|
-
name: "package-manager" | "workspace-discovery" | "lockfile" | "cache-backend" | "dist-build" | "benchmark-gates" | "docs-contract";
|
|
675
|
+
name: "package-manager" | "workspace-discovery" | "lockfile" | "cache-backend" | "dist-build" | "runtime-artifacts" | "benchmark-gates" | "docs-contract";
|
|
577
676
|
status: "pass" | "warn" | "fail";
|
|
578
677
|
detail: string;
|
|
579
678
|
}
|
|
580
679
|
export interface GaResult {
|
|
581
680
|
ready: boolean;
|
|
582
681
|
projectPath: string;
|
|
583
|
-
packageManager:
|
|
682
|
+
packageManager: DetectedPackageManager;
|
|
584
683
|
workspacePackages: number;
|
|
585
684
|
cacheBackend: "sqlite" | "file";
|
|
586
685
|
checks: GaCheck[];
|
package/dist/ui/tui.d.ts
CHANGED
package/dist/ui/tui.js
CHANGED
|
@@ -22,7 +22,7 @@ const DETAIL_TABS = [
|
|
|
22
22
|
"health",
|
|
23
23
|
"changelog",
|
|
24
24
|
];
|
|
25
|
-
function TuiApp({ items, onComplete }) {
|
|
25
|
+
function TuiApp({ items, title, subtitle, onComplete }) {
|
|
26
26
|
const [cursorIndex, setCursorIndex] = useState(0);
|
|
27
27
|
const [filterIndex, setFilterIndex] = useState(0);
|
|
28
28
|
const [sortIndex, setSortIndex] = useState(0);
|
|
@@ -137,7 +137,8 @@ function TuiApp({ items, onComplete }) {
|
|
|
137
137
|
onComplete(items.filter((_, index) => selectedIndices.has(index)));
|
|
138
138
|
}
|
|
139
139
|
});
|
|
140
|
-
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children:
|
|
140
|
+
return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: title ?? "Rainy Dashboard" }), _jsx(Text, { color: "gray", children: subtitle ??
|
|
141
|
+
"Check detects, doctor summarizes, dashboard decides, upgrade applies." }), _jsx(Text, { color: "gray", children: "Filters: \u2190/\u2192 Sort: o Group: g Tabs: Tab Search: / Help: ? Space: toggle Enter: confirm" }), _jsxs(Box, { marginTop: 1, flexDirection: "row", children: [_jsxs(Box, { width: 24, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Filter Rail" }), FILTER_ORDER.map((filter, index) => (_jsxs(Text, { color: index === filterIndex ? "cyan" : "gray", children: [index === filterIndex ? ">" : " ", " ", filter] }, filter))), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Search" }), _jsx(Text, { color: searchMode ? "cyan" : "gray", children: searchMode ? `/${search}` : search ? `/${search}` : "inactive" })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { bold: true, children: "Modes" }), _jsxs(Text, { color: "gray", children: ["sort: ", activeSort] }), _jsxs(Text, { color: "gray", children: ["group: ", activeGroup] }), _jsxs(Text, { color: "gray", children: ["tab: ", activeTab] })] })] }), _jsxs(Box, { marginLeft: 1, width: 82, flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsx(Text, { bold: true, children: "Review Queue" }), itemRows.length === 0 ? (_jsx(Text, { color: "gray", children: "No review candidates match this view." })) : (visibleRows.map((row, visibleIndex) => {
|
|
141
142
|
if (row.kind === "group") {
|
|
142
143
|
return (_jsx(Text, { bold: true, color: "gray", children: row.label }, `group:${row.label}`));
|
|
143
144
|
}
|
|
@@ -353,9 +354,9 @@ function decisionColor(label) {
|
|
|
353
354
|
return "green";
|
|
354
355
|
}
|
|
355
356
|
}
|
|
356
|
-
export async function runTui(items) {
|
|
357
|
+
export async function runTui(items, options) {
|
|
357
358
|
return new Promise((resolve) => {
|
|
358
|
-
const { unmount } = render(_jsx(TuiApp, { items: items, onComplete: (selected) => {
|
|
359
|
+
const { unmount } = render(_jsx(TuiApp, { items: items, title: options?.title, subtitle: options?.subtitle, onComplete: (selected) => {
|
|
359
360
|
unmount();
|
|
360
361
|
resolve(selected);
|
|
361
362
|
} }));
|
package/dist/utils/io.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, rename } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import crypto from "node:crypto";
|
|
4
3
|
export async function writeFileAtomic(filePath, content) {
|
|
5
4
|
const dir = path.dirname(filePath);
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
await
|
|
9
|
-
await
|
|
5
|
+
const tempFile = path.join(dir, `.tmp-${path.basename(filePath)}-${crypto.randomUUID()}`);
|
|
6
|
+
await mkdir(dir, { recursive: true });
|
|
7
|
+
await Bun.write(tempFile, content);
|
|
8
|
+
await rename(tempFile, filePath);
|
|
10
9
|
}
|