@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
package/dist/core/ci.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { check } from "./check.js";
|
|
2
2
|
import { warmCache } from "./warm-cache.js";
|
|
3
|
+
import { buildReviewResult, createDoctorResult } from "./review-model.js";
|
|
4
|
+
import { createDecisionPlan, defaultDecisionPlanPath, writeDecisionPlan, } from "./decision-plan.js";
|
|
5
|
+
import { upgrade } from "./upgrade.js";
|
|
3
6
|
export async function runCi(options) {
|
|
4
7
|
const profile = options.ciProfile;
|
|
5
8
|
if (profile !== "minimal") {
|
|
@@ -16,5 +19,53 @@ export async function runCi(options) {
|
|
|
16
19
|
offline: profile === "minimal" ? options.offline : true,
|
|
17
20
|
concurrency: profile === "enterprise" ? Math.max(options.concurrency, 32) : options.concurrency,
|
|
18
21
|
};
|
|
19
|
-
|
|
22
|
+
if (options.ciGate === "check") {
|
|
23
|
+
return await check(checkOptions);
|
|
24
|
+
}
|
|
25
|
+
if (options.ciGate === "doctor") {
|
|
26
|
+
const review = await buildReviewResult(checkOptions);
|
|
27
|
+
createDoctorResult(review);
|
|
28
|
+
return reviewToCheckResult(review);
|
|
29
|
+
}
|
|
30
|
+
if (options.ciGate === "review") {
|
|
31
|
+
const review = await buildReviewResult(checkOptions);
|
|
32
|
+
const selectedItems = review.items.filter((item) => item.update.selectedByDefault !== false &&
|
|
33
|
+
item.update.decisionState !== "blocked");
|
|
34
|
+
const decisionPlanFile = options.decisionPlanFile ?? defaultDecisionPlanPath(options.cwd);
|
|
35
|
+
const plan = createDecisionPlan({
|
|
36
|
+
review,
|
|
37
|
+
selectedItems,
|
|
38
|
+
sourceCommand: "ci",
|
|
39
|
+
mode: "review",
|
|
40
|
+
focus: "all",
|
|
41
|
+
});
|
|
42
|
+
await writeDecisionPlan(decisionPlanFile, plan);
|
|
43
|
+
review.decisionPlan = plan;
|
|
44
|
+
review.summary.decisionPlan = decisionPlanFile;
|
|
45
|
+
review.summary.interactiveSurface = "dashboard";
|
|
46
|
+
review.summary.queueFocus = "all";
|
|
47
|
+
review.summary.suggestedCommand = `rup upgrade --from-plan ${decisionPlanFile}`;
|
|
48
|
+
return reviewToCheckResult(review);
|
|
49
|
+
}
|
|
50
|
+
const decisionPlanFile = options.decisionPlanFile ?? defaultDecisionPlanPath(options.cwd);
|
|
51
|
+
return upgrade({
|
|
52
|
+
...checkOptions,
|
|
53
|
+
install: false,
|
|
54
|
+
packageManager: "auto",
|
|
55
|
+
sync: checkOptions.workspace,
|
|
56
|
+
fromPlanFile: decisionPlanFile,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
function reviewToCheckResult(review) {
|
|
60
|
+
return {
|
|
61
|
+
projectPath: review.projectPath,
|
|
62
|
+
packagePaths: review.analysis.check.packagePaths,
|
|
63
|
+
packageManager: review.analysis.check.packageManager,
|
|
64
|
+
target: review.target,
|
|
65
|
+
timestamp: review.analysis.check.timestamp,
|
|
66
|
+
summary: review.summary,
|
|
67
|
+
updates: review.updates,
|
|
68
|
+
errors: review.errors,
|
|
69
|
+
warnings: review.warnings,
|
|
70
|
+
};
|
|
20
71
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DashboardMode, DecisionPlan, PackageUpdate, QueueFocus, ReviewItem, ReviewResult, UpgradeOptions } from "../types/index.js";
|
|
2
|
+
export declare function defaultDecisionPlanPath(cwd: string): string;
|
|
3
|
+
export declare function filterReviewItemsByFocus(items: ReviewItem[], focus: QueueFocus): ReviewItem[];
|
|
4
|
+
export declare function createDecisionPlan(input: {
|
|
5
|
+
review: ReviewResult;
|
|
6
|
+
selectedItems: ReviewItem[];
|
|
7
|
+
sourceCommand: string;
|
|
8
|
+
mode: DashboardMode;
|
|
9
|
+
focus: QueueFocus;
|
|
10
|
+
}): DecisionPlan;
|
|
11
|
+
export declare function writeDecisionPlan(filePath: string, plan: DecisionPlan): Promise<void>;
|
|
12
|
+
export declare function readDecisionPlan(filePath: string): Promise<DecisionPlan>;
|
|
13
|
+
export declare function selectedUpdatesFromPlan(plan: DecisionPlan): PackageUpdate[];
|
|
14
|
+
export declare function resolveDecisionPlanPath(options: Pick<UpgradeOptions, "cwd" | "decisionPlanFile">, explicit?: string): string;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { stableStringify } from "../utils/stable-json.js";
|
|
3
|
+
import { writeFileAtomic } from "../utils/io.js";
|
|
4
|
+
export function defaultDecisionPlanPath(cwd) {
|
|
5
|
+
return path.resolve(cwd, ".artifacts/decision-plan.json");
|
|
6
|
+
}
|
|
7
|
+
export function filterReviewItemsByFocus(items, focus) {
|
|
8
|
+
if (focus === "security") {
|
|
9
|
+
return items.filter((item) => item.advisories.length > 0);
|
|
10
|
+
}
|
|
11
|
+
if (focus === "risk") {
|
|
12
|
+
return items.filter((item) => item.update.riskLevel === "critical" ||
|
|
13
|
+
item.update.riskLevel === "high");
|
|
14
|
+
}
|
|
15
|
+
if (focus === "major") {
|
|
16
|
+
return items.filter((item) => item.update.diffType === "major");
|
|
17
|
+
}
|
|
18
|
+
if (focus === "blocked") {
|
|
19
|
+
return items.filter((item) => item.update.decisionState === "blocked");
|
|
20
|
+
}
|
|
21
|
+
if (focus === "workspace") {
|
|
22
|
+
return items.filter((item) => Boolean(item.update.workspaceGroup));
|
|
23
|
+
}
|
|
24
|
+
return items;
|
|
25
|
+
}
|
|
26
|
+
export function createDecisionPlan(input) {
|
|
27
|
+
const selectedKeys = new Set(input.selectedItems.map((item) => packageUpdateKey(item.update)));
|
|
28
|
+
const items = input.review.items.map((item) => ({
|
|
29
|
+
...toDecisionPlanItem(item.update),
|
|
30
|
+
selected: selectedKeys.has(packageUpdateKey(item.update)),
|
|
31
|
+
}));
|
|
32
|
+
return {
|
|
33
|
+
contractVersion: "1",
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
sourceCommand: input.sourceCommand,
|
|
36
|
+
mode: input.mode,
|
|
37
|
+
focus: input.focus,
|
|
38
|
+
projectPath: input.review.projectPath,
|
|
39
|
+
target: input.review.target,
|
|
40
|
+
interactiveSurface: "dashboard",
|
|
41
|
+
summary: {
|
|
42
|
+
totalItems: items.length,
|
|
43
|
+
selectedItems: items.filter((item) => item.selected).length,
|
|
44
|
+
},
|
|
45
|
+
items,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function writeDecisionPlan(filePath, plan) {
|
|
49
|
+
await writeFileAtomic(filePath, stableStringify(plan, 2) + "\n");
|
|
50
|
+
}
|
|
51
|
+
export async function readDecisionPlan(filePath) {
|
|
52
|
+
const parsed = (await Bun.file(filePath).json());
|
|
53
|
+
if (parsed.contractVersion !== "1" ||
|
|
54
|
+
!Array.isArray(parsed.items) ||
|
|
55
|
+
typeof parsed.projectPath !== "string") {
|
|
56
|
+
throw new Error(`Invalid decision plan: ${filePath}`);
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
export function selectedUpdatesFromPlan(plan) {
|
|
61
|
+
return plan.items.filter((item) => item.selected).map(toPackageUpdate);
|
|
62
|
+
}
|
|
63
|
+
export function resolveDecisionPlanPath(options, explicit) {
|
|
64
|
+
return (explicit ?? options.decisionPlanFile ?? defaultDecisionPlanPath(options.cwd));
|
|
65
|
+
}
|
|
66
|
+
function toDecisionPlanItem(update) {
|
|
67
|
+
return {
|
|
68
|
+
packagePath: update.packagePath,
|
|
69
|
+
name: update.name,
|
|
70
|
+
kind: update.kind,
|
|
71
|
+
fromRange: update.fromRange,
|
|
72
|
+
toRange: update.toRange,
|
|
73
|
+
toVersionResolved: update.toVersionResolved,
|
|
74
|
+
diffType: update.diffType,
|
|
75
|
+
riskLevel: update.riskLevel,
|
|
76
|
+
riskScore: update.riskScore,
|
|
77
|
+
policyAction: update.policyAction,
|
|
78
|
+
decisionState: update.decisionState,
|
|
79
|
+
selected: true,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function toPackageUpdate(item) {
|
|
83
|
+
return {
|
|
84
|
+
packagePath: item.packagePath,
|
|
85
|
+
name: item.name,
|
|
86
|
+
kind: item.kind,
|
|
87
|
+
fromRange: item.fromRange,
|
|
88
|
+
toRange: item.toRange,
|
|
89
|
+
toVersionResolved: item.toVersionResolved,
|
|
90
|
+
diffType: item.diffType,
|
|
91
|
+
filtered: false,
|
|
92
|
+
autofix: true,
|
|
93
|
+
riskLevel: item.riskLevel,
|
|
94
|
+
riskScore: item.riskScore,
|
|
95
|
+
policyAction: item.policyAction,
|
|
96
|
+
decisionState: item.decisionState,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function packageUpdateKey(update) {
|
|
100
|
+
return [
|
|
101
|
+
update.packagePath,
|
|
102
|
+
update.kind,
|
|
103
|
+
update.name,
|
|
104
|
+
update.fromRange,
|
|
105
|
+
update.toRange,
|
|
106
|
+
].join("::");
|
|
107
|
+
}
|
|
@@ -17,6 +17,7 @@ export function createDoctorResult(review) {
|
|
|
17
17
|
review.summary.primaryFindingCode = findings[0]?.code;
|
|
18
18
|
review.summary.primaryFindingCategory = findings[0]?.category;
|
|
19
19
|
review.summary.nextActionReason = nextActionReason;
|
|
20
|
+
review.summary.suggestedCommand = recommendedCommand;
|
|
20
21
|
return {
|
|
21
22
|
verdict,
|
|
22
23
|
score,
|
|
@@ -31,11 +32,13 @@ export function createDoctorResult(review) {
|
|
|
31
32
|
}
|
|
32
33
|
function recommendDoctorCommand(review, verdict) {
|
|
33
34
|
if (verdict === "blocked")
|
|
34
|
-
return "rup review --
|
|
35
|
-
if ((review.summary.securityPackages ?? 0) > 0)
|
|
36
|
-
return "rup review --security
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
return "rup dashboard --mode review --focus blocked";
|
|
36
|
+
if ((review.summary.securityPackages ?? 0) > 0) {
|
|
37
|
+
return "rup dashboard --mode review --focus security";
|
|
38
|
+
}
|
|
39
|
+
if (review.errors.length > 0 || review.items.length > 0) {
|
|
40
|
+
return "rup dashboard --mode review";
|
|
41
|
+
}
|
|
39
42
|
return "rup check";
|
|
40
43
|
}
|
|
41
44
|
function describeNextActionReason(review, verdict) {
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { promises as fs } from "node:fs";
|
|
3
|
-
import os from "node:os";
|
|
1
|
+
import { $ } from "bun";
|
|
4
2
|
import path from "node:path";
|
|
5
3
|
import { readManifest, writeManifest } from "../parsers/package-json.js";
|
|
4
|
+
import { createTempDir } from "../utils/runtime-paths.js";
|
|
6
5
|
export async function applyFixPrBatches(options, result) {
|
|
7
6
|
const autofixUpdates = result.updates.filter((update) => update.autofix !== false);
|
|
8
7
|
if (!options.fixPr || autofixUpdates.length === 0) {
|
|
@@ -12,14 +11,25 @@ export async function applyFixPrBatches(options, result) {
|
|
|
12
11
|
const groups = groupUpdates(autofixUpdates, options.groupBy);
|
|
13
12
|
const plans = planFixPrBatches(groups, options.fixBranch ?? "chore/rainy-updates", options.fixPrBatchSize ?? 1);
|
|
14
13
|
if (options.fixDryRun) {
|
|
15
|
-
return {
|
|
14
|
+
return {
|
|
15
|
+
applied: false,
|
|
16
|
+
branches: plans.map((plan) => plan.branchName),
|
|
17
|
+
commits: [],
|
|
18
|
+
};
|
|
16
19
|
}
|
|
17
20
|
const branches = [];
|
|
18
21
|
const commits = [];
|
|
19
22
|
for (const plan of plans) {
|
|
20
|
-
const tempDir = await
|
|
23
|
+
const tempDir = await createTempDir("rainy-fix-pr-batch-");
|
|
21
24
|
try {
|
|
22
|
-
await runGit(options.cwd, [
|
|
25
|
+
await runGit(options.cwd, [
|
|
26
|
+
"worktree",
|
|
27
|
+
"add",
|
|
28
|
+
"-B",
|
|
29
|
+
plan.branchName,
|
|
30
|
+
tempDir,
|
|
31
|
+
baseRef,
|
|
32
|
+
]);
|
|
23
33
|
await applyUpdatesInWorktree(options.cwd, tempDir, plan.updates);
|
|
24
34
|
await stageUpdatedManifests(options.cwd, tempDir, plan.updates);
|
|
25
35
|
const message = renderCommitMessage(options, plan, plans.length);
|
|
@@ -47,7 +57,9 @@ export function planFixPrBatches(groups, baseBranch, batchSize) {
|
|
|
47
57
|
chunks.push(groups.slice(index, index + size));
|
|
48
58
|
}
|
|
49
59
|
return chunks.map((chunk, index) => {
|
|
50
|
-
const suffix = chunk.length === 1
|
|
60
|
+
const suffix = chunk.length === 1
|
|
61
|
+
? sanitizeBranchToken(chunk[0]?.key ?? `batch-${index + 1}`)
|
|
62
|
+
: `batch-${index + 1}`;
|
|
51
63
|
return {
|
|
52
64
|
index: index + 1,
|
|
53
65
|
groups: chunk,
|
|
@@ -147,27 +159,25 @@ function renderCommitMessage(options, plan, totalBatches) {
|
|
|
147
159
|
return `${baseMessage} (${plan.index}/${totalBatches}, ${plan.updates.length} updates)`;
|
|
148
160
|
}
|
|
149
161
|
function sanitizeBranchToken(value) {
|
|
150
|
-
return value
|
|
162
|
+
return (value
|
|
163
|
+
.replace(/^@/, "")
|
|
164
|
+
.replace(/[^a-zA-Z0-9._-]+/g, "-")
|
|
165
|
+
.replace(/^-+|-+$/g, "") || "batch");
|
|
151
166
|
}
|
|
152
167
|
async function runGit(cwd, args, allowNonZero = false) {
|
|
153
|
-
|
|
154
|
-
const
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
resolve({ code: normalized, stdout, stderr });
|
|
171
|
-
});
|
|
172
|
-
});
|
|
168
|
+
try {
|
|
169
|
+
const res = await $ `git ${args}`.cwd(cwd).quiet().nothrow();
|
|
170
|
+
const code = res.exitCode;
|
|
171
|
+
const stdout = res.stdout.toString();
|
|
172
|
+
const stderr = res.stderr.toString();
|
|
173
|
+
if (code !== 0 && !allowNonZero) {
|
|
174
|
+
throw new Error(`git ${args.join(" ")} failed (${code}): ${stderr.trim()}`);
|
|
175
|
+
}
|
|
176
|
+
return { code, stdout, stderr };
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
if (allowNonZero)
|
|
180
|
+
return { code: 1, stdout: "", stderr: "" };
|
|
181
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
182
|
+
}
|
|
173
183
|
}
|
package/dist/core/fix-pr.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $ } from "bun";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
export async function applyFixPr(options, result, extraFiles) {
|
|
4
4
|
if (!options.fixPr)
|
|
@@ -42,7 +42,8 @@ export async function applyFixPr(options, result, extraFiles) {
|
|
|
42
42
|
: [];
|
|
43
43
|
const filesToStage = Array.from(new Set([...manifestFiles, ...extraFiles, ...lockfileFiles]
|
|
44
44
|
.map((entry) => path.resolve(options.cwd, entry))
|
|
45
|
-
.filter((entry) => entry.startsWith(path.resolve(options.cwd) + path.sep) ||
|
|
45
|
+
.filter((entry) => entry.startsWith(path.resolve(options.cwd) + path.sep) ||
|
|
46
|
+
entry === path.resolve(options.cwd)))).sort((a, b) => a.localeCompare(b));
|
|
46
47
|
if (filesToStage.length > 0) {
|
|
47
48
|
await runGit(options.cwd, ["add", "--", ...filesToStage]);
|
|
48
49
|
}
|
|
@@ -54,7 +55,8 @@ export async function applyFixPr(options, result, extraFiles) {
|
|
|
54
55
|
commitSha: "",
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
|
-
const message = options.fixCommitMessage ??
|
|
58
|
+
const message = options.fixCommitMessage ??
|
|
59
|
+
`chore(deps): apply rainy-updates (${autofixUpdates.length} updates)`;
|
|
58
60
|
await runGit(options.cwd, ["commit", "-m", message]);
|
|
59
61
|
const rev = await runGit(options.cwd, ["rev-parse", "HEAD"]);
|
|
60
62
|
return {
|
|
@@ -64,30 +66,31 @@ export async function applyFixPr(options, result, extraFiles) {
|
|
|
64
66
|
};
|
|
65
67
|
}
|
|
66
68
|
async function runGit(cwd, args, allowNonZero = false) {
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
resolve({ code: normalized, stdout, stderr });
|
|
85
|
-
});
|
|
86
|
-
});
|
|
69
|
+
try {
|
|
70
|
+
const res = await $ `git ${args}`.cwd(cwd).quiet().nothrow();
|
|
71
|
+
const code = res.exitCode;
|
|
72
|
+
const stdout = res.stdout.toString();
|
|
73
|
+
const stderr = res.stderr.toString();
|
|
74
|
+
if (code !== 0 && !allowNonZero) {
|
|
75
|
+
throw new Error(`git ${args.join(" ")} failed (${code}): ${stderr.trim()}`);
|
|
76
|
+
}
|
|
77
|
+
return { code, stdout, stderr };
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
if (allowNonZero)
|
|
81
|
+
return { code: 1, stdout: "", stderr: "" };
|
|
82
|
+
throw err instanceof Error ? err : new Error(String(err));
|
|
83
|
+
}
|
|
87
84
|
}
|
|
88
85
|
async function collectChangedLockfiles(cwd) {
|
|
89
86
|
const status = await runGit(cwd, ["status", "--porcelain"], true);
|
|
90
|
-
const allowed = new Set([
|
|
87
|
+
const allowed = new Set([
|
|
88
|
+
"package-lock.json",
|
|
89
|
+
"npm-shrinkwrap.json",
|
|
90
|
+
"pnpm-lock.yaml",
|
|
91
|
+
"yarn.lock",
|
|
92
|
+
"bun.lock",
|
|
93
|
+
]);
|
|
91
94
|
const changed = status.stdout
|
|
92
95
|
.split(/\r?\n/)
|
|
93
96
|
.map((line) => line.trim())
|
package/dist/core/init-ci.js
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { buildInstallInvocation, buildTestCommand, createPackageManagerProfile, detectPackageManagerDetails, } from "../pm/detect.js";
|
|
3
4
|
export async function initCiWorkflow(cwd, force, options) {
|
|
4
5
|
const workflowPath = path.join(cwd, ".github", "workflows", "rainy-updates.yml");
|
|
5
6
|
try {
|
|
6
7
|
if (!force) {
|
|
7
|
-
await
|
|
8
|
-
|
|
8
|
+
if (await Bun.file(workflowPath).exists()) {
|
|
9
|
+
return { path: workflowPath, created: false };
|
|
10
|
+
}
|
|
9
11
|
}
|
|
10
12
|
}
|
|
11
13
|
catch {
|
|
12
14
|
// missing file, continue create
|
|
13
15
|
}
|
|
14
|
-
const
|
|
16
|
+
const detected = await detectPackageManagerDetails(cwd);
|
|
17
|
+
const packageManager = createPackageManagerProfile("auto", detected);
|
|
15
18
|
const scheduleBlock = renderScheduleBlock(options.schedule);
|
|
16
19
|
const workflow = options.mode === "minimal"
|
|
17
20
|
? minimalWorkflowTemplate(scheduleBlock, packageManager)
|
|
@@ -19,19 +22,9 @@ export async function initCiWorkflow(cwd, force, options) {
|
|
|
19
22
|
? strictWorkflowTemplate(scheduleBlock, packageManager)
|
|
20
23
|
: enterpriseWorkflowTemplate(scheduleBlock, packageManager);
|
|
21
24
|
await mkdir(path.dirname(workflowPath), { recursive: true });
|
|
22
|
-
await
|
|
25
|
+
await Bun.write(workflowPath, workflow);
|
|
23
26
|
return { path: workflowPath, created: true };
|
|
24
27
|
}
|
|
25
|
-
async function detectPackageManager(cwd) {
|
|
26
|
-
const pnpmLock = path.join(cwd, "pnpm-lock.yaml");
|
|
27
|
-
try {
|
|
28
|
-
await access(pnpmLock);
|
|
29
|
-
return "pnpm";
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return "npm";
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
28
|
function renderScheduleBlock(schedule) {
|
|
36
29
|
if (schedule === "off") {
|
|
37
30
|
return " workflow_dispatch:";
|
|
@@ -39,21 +32,34 @@ function renderScheduleBlock(schedule) {
|
|
|
39
32
|
const cron = schedule === "daily" ? "0 8 * * *" : "0 8 * * 1";
|
|
40
33
|
return ` schedule:\n - cron: '${cron}'\n workflow_dispatch:`;
|
|
41
34
|
}
|
|
42
|
-
function installStep(
|
|
43
|
-
|
|
44
|
-
|
|
35
|
+
function installStep(profile) {
|
|
36
|
+
const install = buildInstallInvocation(profile, { frozen: true, ci: true });
|
|
37
|
+
return ` - name: Install dependencies\n run: ${install.display}`;
|
|
38
|
+
}
|
|
39
|
+
function runtimeSetupSteps(profile) {
|
|
40
|
+
const lines = [
|
|
41
|
+
` - name: Checkout\n uses: actions/checkout@v4`,
|
|
42
|
+
];
|
|
43
|
+
if (profile.manager !== "bun") {
|
|
44
|
+
lines.push(` - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: 22`);
|
|
45
|
+
}
|
|
46
|
+
lines.push(` - name: Setup Bun\n uses: oven-sh/setup-bun@v1`);
|
|
47
|
+
if (profile.manager === "pnpm" || profile.manager === "yarn") {
|
|
48
|
+
lines.push(` - name: Enable Corepack\n run: corepack enable`);
|
|
49
|
+
}
|
|
50
|
+
if (profile.manager === "pnpm") {
|
|
51
|
+
lines.push(` - name: Prepare pnpm\n run: corepack prepare pnpm@9 --activate`);
|
|
45
52
|
}
|
|
46
|
-
return
|
|
53
|
+
return lines.join("\n\n");
|
|
47
54
|
}
|
|
48
|
-
function minimalWorkflowTemplate(scheduleBlock,
|
|
49
|
-
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n
|
|
55
|
+
function minimalWorkflowTemplate(scheduleBlock, profile) {
|
|
56
|
+
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n${runtimeSetupSteps(profile)}\n\n${installStep(profile)}\n\n - name: Run dependency check\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode minimal \\\n --gate check \\\n --ci \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --format table\n`;
|
|
50
57
|
}
|
|
51
|
-
function strictWorkflowTemplate(scheduleBlock,
|
|
52
|
-
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\npermissions:\n contents: read\n security-events: write\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n
|
|
58
|
+
function strictWorkflowTemplate(scheduleBlock, profile) {
|
|
59
|
+
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\npermissions:\n contents: read\n security-events: write\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n steps:\n${runtimeSetupSteps(profile)}\n\n${installStep(profile)}\n\n - name: Warm cache\n run: bunx --bun @rainy-updates/cli warm-cache --workspace --concurrency 32 --registry-timeout-ms 12000 --registry-retries 4\n\n - name: Generate reviewed decision plan\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode strict \\\n --gate review \\\n --plan-file .artifacts/decision-plan.json \\\n --ci \\\n --concurrency 32 \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --format github \\\n --json-file .artifacts/deps-report.json \\\n --pr-report-file .artifacts/deps-report.md \\\n --sarif-file .artifacts/deps-report.sarif \\\n --github-output $GITHUB_OUTPUT\n\n - name: Upload report artifacts\n uses: actions/upload-artifact@v4\n with:\n name: rainy-updates-report\n path: .artifacts/\n\n - name: Upload SARIF\n uses: github/codeql-action/upload-sarif@v3\n with:\n sarif_file: .artifacts/deps-report.sarif\n`;
|
|
53
60
|
}
|
|
54
|
-
function enterpriseWorkflowTemplate(scheduleBlock,
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
: "
|
|
58
|
-
return `name: Rainy Updates Enterprise\n\non:\n${scheduleBlock}\n\npermissions:\n contents: read\n security-events: write\n actions: read\n\nconcurrency:\n group: rainy-updates-\${{ github.ref }}\n cancel-in-progress: false\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n node: [20, 22]\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: \${{ matrix.node }}\n\n - name: Install dependencies\n run: ${detectedPmInstall}\n\n - name: Warm cache\n run: npx @rainy-updates/cli warm-cache --workspace --concurrency 32 --registry-timeout-ms 12000 --registry-retries 4\n\n - name: Check updates with rollout controls\n run: |\n npx @rainy-updates/cli ci \\\n --workspace \\\n --mode enterprise \\\n --concurrency 32 \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --lockfile-mode preserve \\\n --format github \\\n --fail-on minor \\\n --max-updates 50 \\\n --json-file .artifacts/deps-report-node-\${{ matrix.node }}.json \\\n --pr-report-file .artifacts/deps-report-node-\${{ matrix.node }}.md \\\n --sarif-file .artifacts/deps-report-node-\${{ matrix.node }}.sarif \\\n --github-output $GITHUB_OUTPUT\n\n - name: Upload report artifacts\n uses: actions/upload-artifact@v4\n with:\n name: rainy-updates-report-node-\${{ matrix.node }}\n path: .artifacts/\n retention-days: 14\n\n - name: Upload SARIF\n uses: github/codeql-action/upload-sarif@v3\n with:\n sarif_file: .artifacts/deps-report-node-\${{ matrix.node }}.sarif\n`;
|
|
61
|
+
function enterpriseWorkflowTemplate(scheduleBlock, profile) {
|
|
62
|
+
const install = buildInstallInvocation(profile, { frozen: true, ci: true });
|
|
63
|
+
const testCmd = buildTestCommand(profile);
|
|
64
|
+
return `name: Rainy Updates Enterprise\n\non:\n${scheduleBlock}\n\npermissions:\n contents: read\n security-events: write\n actions: read\n\nconcurrency:\n group: rainy-updates-\${{ github.ref }}\n cancel-in-progress: false\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\n strategy:\n fail-fast: false\n matrix:\n node: [20, 22]\n steps:\n - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: \${{ matrix.node }}\n\n - name: Setup Bun\n uses: oven-sh/setup-bun@v1\n\n${profile.manager === "pnpm" || profile.manager === "yarn" ? ' - name: Enable Corepack\n run: corepack enable\n\n' : ""}${profile.manager === "pnpm" ? ' - name: Prepare pnpm\n run: corepack prepare pnpm@9 --activate\n\n' : ""} - name: Install dependencies\n run: ${install.display}\n\n - name: Warm cache\n run: bunx --bun @rainy-updates/cli warm-cache --workspace --concurrency 32 --registry-timeout-ms 12000 --registry-retries 4\n\n - name: Generate reviewed decision plan\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode enterprise \\\n --gate review \\\n --plan-file .artifacts/decision-plan.json \\\n --concurrency 32 \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --lockfile-mode preserve \\\n --format github \\\n --fail-on minor \\\n --max-updates 50 \\\n --json-file .artifacts/deps-report-node-\${{ matrix.node }}.json \\\n --pr-report-file .artifacts/deps-report-node-\${{ matrix.node }}.md \\\n --sarif-file .artifacts/deps-report-node-\${{ matrix.node }}.sarif \\\n --github-output $GITHUB_OUTPUT\n\n - name: Replay approved plan with verification\n run: |\n bunx --bun @rainy-updates/cli ci \\\n --workspace \\\n --mode enterprise \\\n --gate upgrade \\\n --from-plan .artifacts/decision-plan.json \\\n --verify test \\\n --test-command "${testCmd}" \\\n --verification-report-file .artifacts/verification-node-\${{ matrix.node }}.json\n\n - name: Upload report artifacts\n uses: actions/upload-artifact@v4\n with:\n name: rainy-updates-report-node-\${{ matrix.node }}\n path: .artifacts/\n retention-days: 14\n\n - name: Upload SARIF\n uses: github/codeql-action/upload-sarif@v3\n with:\n sarif_file: .artifacts/deps-report-node-\${{ matrix.node }}.sarif\n`;
|
|
59
65
|
}
|
package/dist/core/options.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions, ReviewOptions, DoctorOptions, RiskLevel, DashboardOptions, GaOptions } from "../types/index.js";
|
|
1
|
+
import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions, UnusedOptions, ResolveOptions, LicenseOptions, SnapshotOptions, ReviewOptions, DoctorOptions, RiskLevel, DashboardOptions, GaOptions, HookOptions } from "../types/index.js";
|
|
2
2
|
import type { InitCiMode, InitCiSchedule } from "./init-ci.js";
|
|
3
3
|
export type ParsedCliArgs = {
|
|
4
4
|
command: "check";
|
|
@@ -58,6 +58,9 @@ export type ParsedCliArgs = {
|
|
|
58
58
|
} | {
|
|
59
59
|
command: "ga";
|
|
60
60
|
options: GaOptions;
|
|
61
|
+
} | {
|
|
62
|
+
command: "hook";
|
|
63
|
+
options: HookOptions;
|
|
61
64
|
};
|
|
62
65
|
export declare function parseCliArgs(argv: string[]): Promise<ParsedCliArgs>;
|
|
63
66
|
export declare function ensureRiskLevel(value: string): RiskLevel;
|