@rainy-updates/cli 0.5.7 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/README.md +90 -31
- package/dist/bin/cli.js +16 -16
- package/dist/bin/dispatch.js +29 -32
- package/dist/bin/help.js +32 -2
- package/dist/cache/cache.js +13 -11
- package/dist/commands/audit/parser.js +2 -2
- package/dist/commands/audit/runner.js +27 -46
- package/dist/commands/audit/targets.js +13 -13
- package/dist/commands/bisect/oracle.js +28 -11
- package/dist/commands/bisect/parser.js +3 -3
- package/dist/commands/bisect/runner.js +15 -8
- package/dist/commands/changelog/fetcher.js +11 -5
- package/dist/commands/dashboard/parser.js +103 -1
- package/dist/commands/dashboard/runner.d.ts +2 -2
- package/dist/commands/dashboard/runner.js +67 -37
- package/dist/commands/doctor/parser.js +9 -4
- package/dist/commands/doctor/runner.js +2 -2
- package/dist/commands/ga/parser.js +4 -4
- package/dist/commands/ga/runner.js +13 -7
- package/dist/commands/health/parser.js +2 -2
- package/dist/commands/licenses/runner.js +4 -4
- package/dist/commands/resolve/runner.js +9 -4
- package/dist/commands/review/parser.js +57 -4
- package/dist/commands/review/runner.js +31 -5
- package/dist/commands/snapshot/runner.js +17 -17
- package/dist/commands/snapshot/store.d.ts +0 -12
- package/dist/commands/snapshot/store.js +26 -38
- package/dist/commands/unused/runner.js +6 -7
- package/dist/commands/unused/scanner.js +17 -20
- package/dist/config/loader.d.ts +2 -2
- package/dist/config/loader.js +2 -5
- package/dist/config/policy.js +20 -11
- package/dist/core/analysis/run-silenced.js +0 -1
- package/dist/core/artifacts.js +6 -5
- package/dist/core/baseline.js +3 -5
- package/dist/core/check.js +2 -2
- package/dist/core/ci.js +52 -1
- package/dist/core/decision-plan.d.ts +14 -0
- package/dist/core/decision-plan.js +107 -0
- package/dist/core/doctor/result.js +8 -5
- package/dist/core/fix-pr-batch.js +38 -28
- package/dist/core/fix-pr.js +27 -24
- package/dist/core/init-ci.js +25 -21
- package/dist/core/options.js +95 -4
- package/dist/core/review-model.js +3 -0
- package/dist/core/summary.js +6 -0
- package/dist/core/upgrade.js +64 -2
- package/dist/core/verification.d.ts +2 -0
- package/dist/core/verification.js +106 -0
- package/dist/core/warm-cache.js +2 -2
- package/dist/output/format.js +15 -0
- package/dist/output/github.js +6 -0
- package/dist/output/sarif.js +12 -12
- package/dist/parsers/package-json.js +2 -4
- package/dist/pm/detect.d.ts +3 -1
- package/dist/pm/detect.js +24 -12
- package/dist/pm/install.d.ts +2 -1
- package/dist/pm/install.js +15 -16
- package/dist/registry/npm.js +34 -76
- package/dist/rup +0 -0
- package/dist/types/index.d.ts +76 -5
- package/dist/ui/tui.d.ts +4 -1
- package/dist/ui/tui.js +5 -4
- package/dist/utils/io.js +5 -6
- package/dist/utils/lockfile.js +24 -19
- package/dist/utils/runtime-paths.d.ts +4 -0
- package/dist/utils/runtime-paths.js +35 -0
- package/dist/utils/runtime.d.ts +7 -0
- package/dist/utils/runtime.js +32 -0
- package/dist/workspace/discover.js +55 -51
- package/package.json +16 -16
- package/dist/ui/dashboard/DashboardTUI.d.ts +0 -6
- package/dist/ui/dashboard/DashboardTUI.js +0 -34
- package/dist/ui/dashboard/components/DetailPanel.d.ts +0 -4
- package/dist/ui/dashboard/components/DetailPanel.js +0 -30
- package/dist/ui/dashboard/components/Footer.d.ts +0 -4
- package/dist/ui/dashboard/components/Footer.js +0 -9
- package/dist/ui/dashboard/components/Header.d.ts +0 -4
- package/dist/ui/dashboard/components/Header.js +0 -12
- package/dist/ui/dashboard/components/Sidebar.d.ts +0 -4
- package/dist/ui/dashboard/components/Sidebar.js +0 -23
- package/dist/ui/dashboard/store.d.ts +0 -34
- package/dist/ui/dashboard/store.js +0 -148
package/dist/core/warm-cache.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import process from "node:process";
|
|
2
1
|
import { collectDependencies, readManifest } from "../parsers/package-json.js";
|
|
3
2
|
import { matchesPattern } from "../utils/pattern.js";
|
|
4
3
|
import { VersionCache } from "../cache/cache.js";
|
|
@@ -7,6 +6,7 @@ import { detectPackageManager } from "../pm/detect.js";
|
|
|
7
6
|
import { discoverPackageDirs } from "../workspace/discover.js";
|
|
8
7
|
import { createSummary, finalizeSummary } from "./summary.js";
|
|
9
8
|
import { formatClassifiedMessage } from "./errors.js";
|
|
9
|
+
import { writeStdout } from "../utils/runtime.js";
|
|
10
10
|
export async function warmCache(options) {
|
|
11
11
|
const startedAt = Date.now();
|
|
12
12
|
let discoveryMs = 0;
|
|
@@ -39,7 +39,7 @@ export async function warmCache(options) {
|
|
|
39
39
|
if (!options.stream)
|
|
40
40
|
return;
|
|
41
41
|
streamedEvents += 1;
|
|
42
|
-
|
|
42
|
+
writeStdout(`${message}\n`);
|
|
43
43
|
};
|
|
44
44
|
for (const packageDir of packageDirs) {
|
|
45
45
|
try {
|
package/dist/output/format.js
CHANGED
|
@@ -74,6 +74,12 @@ export function renderResult(result, format, display = {}) {
|
|
|
74
74
|
`primary_finding_code=${result.summary.primaryFindingCode ?? ""}`,
|
|
75
75
|
`primary_finding_category=${result.summary.primaryFindingCategory ?? ""}`,
|
|
76
76
|
`next_action_reason=${result.summary.nextActionReason ?? ""}`,
|
|
77
|
+
`suggested_command=${result.summary.suggestedCommand ?? ""}`,
|
|
78
|
+
`decision_plan=${result.summary.decisionPlan ?? ""}`,
|
|
79
|
+
`interactive_surface=${result.summary.interactiveSurface ?? ""}`,
|
|
80
|
+
`queue_focus=${result.summary.queueFocus ?? ""}`,
|
|
81
|
+
`verification_state=${result.summary.verificationState ?? "not-run"}`,
|
|
82
|
+
`verification_failures=${result.summary.verificationFailures ?? 0}`,
|
|
77
83
|
].join("\n");
|
|
78
84
|
}
|
|
79
85
|
const lines = [];
|
|
@@ -137,6 +143,15 @@ export function renderResult(result, format, display = {}) {
|
|
|
137
143
|
if (typeof result.summary.dependencyHealthScore === "number") {
|
|
138
144
|
lines.push(`DependencyHealthScore=${result.summary.dependencyHealthScore}, primaryFinding=${result.summary.primaryFindingCode ?? "none"}, category=${result.summary.primaryFindingCategory ?? "none"}`);
|
|
139
145
|
}
|
|
146
|
+
if (result.summary.suggestedCommand) {
|
|
147
|
+
lines.push(`SuggestedCommand=${result.summary.suggestedCommand}`);
|
|
148
|
+
}
|
|
149
|
+
if (result.summary.decisionPlan) {
|
|
150
|
+
lines.push(`DecisionPlan=${result.summary.decisionPlan}, surface=${result.summary.interactiveSurface ?? "none"}, focus=${result.summary.queueFocus ?? "all"}`);
|
|
151
|
+
}
|
|
152
|
+
if (result.summary.verificationState && result.summary.verificationState !== "not-run") {
|
|
153
|
+
lines.push(`Verification=${result.summary.verificationState}, failures=${result.summary.verificationFailures ?? 0}`);
|
|
154
|
+
}
|
|
140
155
|
if (result.summary.runId) {
|
|
141
156
|
lines.push(`RunId=${result.summary.runId}, artifactManifest=${result.summary.artifactManifest ?? "none"}, blockedPackages=${result.summary.blockedPackages ?? 0}, reviewPackages=${result.summary.reviewPackages ?? 0}, monitorPackages=${result.summary.monitorPackages ?? 0}`);
|
|
142
157
|
}
|
package/dist/output/github.js
CHANGED
|
@@ -38,6 +38,12 @@ export async function writeGitHubOutput(filePath, result) {
|
|
|
38
38
|
`primary_finding_code=${result.summary.primaryFindingCode ?? ""}`,
|
|
39
39
|
`primary_finding_category=${result.summary.primaryFindingCategory ?? ""}`,
|
|
40
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}`,
|
|
41
47
|
`fix_pr_applied=${result.summary.fixPrApplied === true ? "1" : "0"}`,
|
|
42
48
|
`fix_pr_branches_created=${result.summary.fixPrBranchesCreated}`,
|
|
43
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
|
},
|
|
@@ -115,15 +119,11 @@ function getToolVersion() {
|
|
|
115
119
|
if (TOOL_VERSION_CACHE)
|
|
116
120
|
return TOOL_VERSION_CACHE;
|
|
117
121
|
try {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
const content = readFileSync(packageJsonPath, "utf8");
|
|
121
|
-
const parsed = JSON.parse(content);
|
|
122
|
-
TOOL_VERSION_CACHE = parsed.version ?? "0.0.0";
|
|
123
|
-
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";
|
|
124
124
|
}
|
|
125
125
|
catch {
|
|
126
126
|
TOOL_VERSION_CACHE = "0.0.0";
|
|
127
|
-
return TOOL_VERSION_CACHE;
|
|
128
127
|
}
|
|
128
|
+
return TOOL_VERSION_CACHE;
|
|
129
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,19 @@
|
|
|
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";
|
|
8
17
|
export type DoctorFindingSeverity = "error" | "warning";
|
|
9
18
|
export type DoctorScoreLabel = "Strong" | "Needs Review" | "Action Needed" | "Blocked / Critical";
|
|
10
19
|
export type DoctorFindingCategory = "Security" | "Compatibility" | "Policy" | "Operational Health" | "Licensing" | "Unused / Cleanup" | "Release Risk" | "Registry / Execution" | "Workspace Integrity";
|
|
@@ -56,13 +65,19 @@ export interface RunOptions {
|
|
|
56
65
|
interactive: boolean;
|
|
57
66
|
showImpact: boolean;
|
|
58
67
|
showHomepage: boolean;
|
|
68
|
+
decisionPlanFile?: string;
|
|
69
|
+
verify: VerificationMode;
|
|
70
|
+
testCommand?: string;
|
|
71
|
+
verificationReportFile?: string;
|
|
72
|
+
ciGate: CiGate;
|
|
59
73
|
}
|
|
60
74
|
export interface CheckOptions extends RunOptions {
|
|
61
75
|
}
|
|
62
76
|
export interface UpgradeOptions extends RunOptions {
|
|
63
77
|
install: boolean;
|
|
64
|
-
packageManager:
|
|
78
|
+
packageManager: SelectedPackageManager;
|
|
65
79
|
sync: boolean;
|
|
80
|
+
fromPlanFile?: string;
|
|
66
81
|
}
|
|
67
82
|
export interface BaselineOptions {
|
|
68
83
|
cwd: string;
|
|
@@ -146,8 +161,51 @@ export interface ArtifactManifest {
|
|
|
146
161
|
githubOutputFile?: string;
|
|
147
162
|
sarifFile?: string;
|
|
148
163
|
prReportFile?: string;
|
|
164
|
+
verificationReportFile?: string;
|
|
149
165
|
};
|
|
150
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
|
+
}
|
|
151
209
|
export interface Summary {
|
|
152
210
|
contractVersion: "2";
|
|
153
211
|
scannedPackages: number;
|
|
@@ -213,11 +271,17 @@ export interface Summary {
|
|
|
213
271
|
primaryFindingCode?: string;
|
|
214
272
|
primaryFindingCategory?: DoctorFindingCategory;
|
|
215
273
|
nextActionReason?: string;
|
|
274
|
+
suggestedCommand?: string;
|
|
275
|
+
decisionPlan?: string;
|
|
276
|
+
interactiveSurface?: InteractiveSurface;
|
|
277
|
+
queueFocus?: QueueFocus;
|
|
278
|
+
verificationState?: VerificationState;
|
|
279
|
+
verificationFailures?: number;
|
|
216
280
|
}
|
|
217
281
|
export interface CheckResult {
|
|
218
282
|
projectPath: string;
|
|
219
283
|
packagePaths: string[];
|
|
220
|
-
packageManager:
|
|
284
|
+
packageManager: DetectedPackageManager;
|
|
221
285
|
target: TargetLevel;
|
|
222
286
|
timestamp: string;
|
|
223
287
|
summary: Summary;
|
|
@@ -260,7 +324,7 @@ export interface AuditOptions {
|
|
|
260
324
|
fix: boolean;
|
|
261
325
|
dryRun: boolean;
|
|
262
326
|
commit: boolean;
|
|
263
|
-
packageManager:
|
|
327
|
+
packageManager: SelectedPackageManager;
|
|
264
328
|
reportFormat: AuditReportFormat;
|
|
265
329
|
sourceMode: AuditSourceMode;
|
|
266
330
|
jsonFile?: string;
|
|
@@ -432,6 +496,7 @@ export interface ReviewResult {
|
|
|
432
496
|
updates: PackageUpdate[];
|
|
433
497
|
errors: string[];
|
|
434
498
|
warnings: string[];
|
|
499
|
+
decisionPlan?: DecisionPlan;
|
|
435
500
|
}
|
|
436
501
|
export interface ReviewOptions extends CheckOptions {
|
|
437
502
|
securityOnly: boolean;
|
|
@@ -439,6 +504,7 @@ export interface ReviewOptions extends CheckOptions {
|
|
|
439
504
|
diff?: TargetLevel;
|
|
440
505
|
applySelected: boolean;
|
|
441
506
|
showChangelog?: boolean;
|
|
507
|
+
queueFocus?: QueueFocus;
|
|
442
508
|
}
|
|
443
509
|
export interface DoctorOptions extends CheckOptions {
|
|
444
510
|
verdictOnly: boolean;
|
|
@@ -482,11 +548,16 @@ export interface AnalysisBundle {
|
|
|
482
548
|
}
|
|
483
549
|
export interface DashboardOptions extends CheckOptions {
|
|
484
550
|
view?: "dependencies" | "security" | "health";
|
|
551
|
+
mode: DashboardMode;
|
|
552
|
+
focus: QueueFocus;
|
|
553
|
+
applySelected: boolean;
|
|
485
554
|
}
|
|
486
555
|
export interface DashboardResult {
|
|
487
556
|
completed: boolean;
|
|
488
557
|
errors: string[];
|
|
489
558
|
warnings: string[];
|
|
559
|
+
selectedUpdates: number;
|
|
560
|
+
decisionPlanFile?: string;
|
|
490
561
|
}
|
|
491
562
|
export type UnusedKind = "declared-not-imported" | "imported-not-declared";
|
|
492
563
|
export interface UnusedDependency {
|
|
@@ -601,14 +672,14 @@ export interface GaOptions {
|
|
|
601
672
|
jsonFile?: string;
|
|
602
673
|
}
|
|
603
674
|
export interface GaCheck {
|
|
604
|
-
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";
|
|
605
676
|
status: "pass" | "warn" | "fail";
|
|
606
677
|
detail: string;
|
|
607
678
|
}
|
|
608
679
|
export interface GaResult {
|
|
609
680
|
ready: boolean;
|
|
610
681
|
projectPath: string;
|
|
611
|
-
packageManager:
|
|
682
|
+
packageManager: DetectedPackageManager;
|
|
612
683
|
workspacePackages: number;
|
|
613
684
|
cacheBackend: "sqlite" | "file";
|
|
614
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
|
}
|