@rainy-updates/cli 0.5.1-rc.2 → 0.5.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 +84 -1
- package/README.md +8 -1
- package/dist/bin/cli.js +62 -12
- package/dist/commands/audit/fetcher.d.ts +6 -0
- package/dist/commands/audit/fetcher.js +79 -0
- package/dist/commands/audit/mapper.d.ts +16 -0
- package/dist/commands/audit/mapper.js +61 -0
- package/dist/commands/audit/parser.d.ts +3 -0
- package/dist/commands/audit/parser.js +87 -0
- package/dist/commands/audit/runner.d.ts +7 -0
- package/dist/commands/audit/runner.js +64 -0
- package/dist/commands/bisect/engine.d.ts +12 -0
- package/dist/commands/bisect/engine.js +89 -0
- package/dist/commands/bisect/oracle.d.ts +7 -0
- package/dist/commands/bisect/oracle.js +36 -0
- package/dist/commands/bisect/parser.d.ts +2 -0
- package/dist/commands/bisect/parser.js +73 -0
- package/dist/commands/bisect/runner.d.ts +6 -0
- package/dist/commands/bisect/runner.js +27 -0
- package/dist/commands/health/parser.d.ts +2 -0
- package/dist/commands/health/parser.js +90 -0
- package/dist/commands/health/runner.d.ts +7 -0
- package/dist/commands/health/runner.js +130 -0
- package/dist/config/loader.d.ts +5 -1
- package/dist/config/policy.d.ts +4 -0
- package/dist/config/policy.js +2 -0
- package/dist/core/check.js +56 -3
- package/dist/core/fix-pr-batch.js +3 -2
- package/dist/core/fix-pr.js +19 -4
- package/dist/core/init-ci.js +3 -3
- package/dist/core/options.d.ts +10 -1
- package/dist/core/options.js +129 -13
- package/dist/core/summary.d.ts +1 -0
- package/dist/core/summary.js +11 -1
- package/dist/core/upgrade.js +10 -0
- package/dist/core/warm-cache.js +19 -1
- package/dist/output/format.js +4 -0
- package/dist/output/github.js +3 -0
- package/dist/registry/npm.d.ts +9 -2
- package/dist/registry/npm.js +87 -17
- package/dist/types/index.d.ts +83 -0
- package/dist/utils/lockfile.d.ts +5 -0
- package/dist/utils/lockfile.js +44 -0
- package/package.json +13 -4
|
@@ -4,11 +4,12 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { readManifest, writeManifest } from "../parsers/package-json.js";
|
|
6
6
|
export async function applyFixPrBatches(options, result) {
|
|
7
|
-
|
|
7
|
+
const autofixUpdates = result.updates.filter((update) => update.autofix !== false);
|
|
8
|
+
if (!options.fixPr || autofixUpdates.length === 0) {
|
|
8
9
|
return { applied: false, branches: [], commits: [] };
|
|
9
10
|
}
|
|
10
11
|
const baseRef = await resolveBaseRef(options.cwd, options.fixPrNoCheckout);
|
|
11
|
-
const groups = groupUpdates(
|
|
12
|
+
const groups = groupUpdates(autofixUpdates, options.groupBy);
|
|
12
13
|
const plans = planFixPrBatches(groups, options.fixBranch ?? "chore/rainy-updates", options.fixPrBatchSize ?? 1);
|
|
13
14
|
if (options.fixDryRun) {
|
|
14
15
|
return { applied: false, branches: plans.map((plan) => plan.branchName), commits: [] };
|
package/dist/core/fix-pr.js
CHANGED
|
@@ -3,7 +3,8 @@ import path from "node:path";
|
|
|
3
3
|
export async function applyFixPr(options, result, extraFiles) {
|
|
4
4
|
if (!options.fixPr)
|
|
5
5
|
return { applied: false };
|
|
6
|
-
|
|
6
|
+
const autofixUpdates = result.updates.filter((update) => update.autofix !== false);
|
|
7
|
+
if (autofixUpdates.length === 0) {
|
|
7
8
|
return {
|
|
8
9
|
applied: false,
|
|
9
10
|
branchName: options.fixBranch ?? "chore/rainy-updates",
|
|
@@ -35,8 +36,11 @@ export async function applyFixPr(options, result, extraFiles) {
|
|
|
35
36
|
commitSha: "",
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
|
-
const manifestFiles = Array.from(new Set(
|
|
39
|
-
const
|
|
39
|
+
const manifestFiles = Array.from(new Set(autofixUpdates.map((update) => path.resolve(update.packagePath, "package.json"))));
|
|
40
|
+
const lockfileFiles = options.lockfileMode === "update"
|
|
41
|
+
? (await collectChangedLockfiles(options.cwd)).map((entry) => path.resolve(options.cwd, entry))
|
|
42
|
+
: [];
|
|
43
|
+
const filesToStage = Array.from(new Set([...manifestFiles, ...extraFiles, ...lockfileFiles]
|
|
40
44
|
.map((entry) => path.resolve(options.cwd, entry))
|
|
41
45
|
.filter((entry) => entry.startsWith(path.resolve(options.cwd) + path.sep) || entry === path.resolve(options.cwd)))).sort((a, b) => a.localeCompare(b));
|
|
42
46
|
if (filesToStage.length > 0) {
|
|
@@ -50,7 +54,7 @@ export async function applyFixPr(options, result, extraFiles) {
|
|
|
50
54
|
commitSha: "",
|
|
51
55
|
};
|
|
52
56
|
}
|
|
53
|
-
const message = options.fixCommitMessage ?? `chore(deps): apply rainy-updates (${
|
|
57
|
+
const message = options.fixCommitMessage ?? `chore(deps): apply rainy-updates (${autofixUpdates.length} updates)`;
|
|
54
58
|
await runGit(options.cwd, ["commit", "-m", message]);
|
|
55
59
|
const rev = await runGit(options.cwd, ["rev-parse", "HEAD"]);
|
|
56
60
|
return {
|
|
@@ -81,3 +85,14 @@ async function runGit(cwd, args, allowNonZero = false) {
|
|
|
81
85
|
});
|
|
82
86
|
});
|
|
83
87
|
}
|
|
88
|
+
async function collectChangedLockfiles(cwd) {
|
|
89
|
+
const status = await runGit(cwd, ["status", "--porcelain"], true);
|
|
90
|
+
const allowed = new Set(["package-lock.json", "npm-shrinkwrap.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock"]);
|
|
91
|
+
const changed = status.stdout
|
|
92
|
+
.split(/\r?\n/)
|
|
93
|
+
.map((line) => line.trim())
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
.map((line) => line.slice(3).trim())
|
|
96
|
+
.filter((entry) => allowed.has(path.basename(entry)));
|
|
97
|
+
return Array.from(new Set(changed)).sort((a, b) => a.localeCompare(b));
|
|
98
|
+
}
|
package/dist/core/init-ci.js
CHANGED
|
@@ -46,14 +46,14 @@ function installStep(packageManager) {
|
|
|
46
46
|
return ` - name: Install dependencies\n run: npm ci`;
|
|
47
47
|
}
|
|
48
48
|
function minimalWorkflowTemplate(scheduleBlock, packageManager) {
|
|
49
|
-
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\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: '20'\n\n${installStep(packageManager)}\n\n - name: Run dependency check\n run: |\n npx @rainy-updates/cli ci \\\n --workspace \\\n --mode minimal \\\n --ci \\\n --format table\n`;
|
|
49
|
+
return `name: Rainy Updates\n\non:\n${scheduleBlock}\n\njobs:\n dependency-check:\n runs-on: ubuntu-latest\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: '20'\n\n${installStep(packageManager)}\n\n - name: Run dependency check\n run: |\n npx @rainy-updates/cli ci \\\n --workspace \\\n --mode minimal \\\n --ci \\\n --stream \\\n --registry-timeout-ms 12000 \\\n --registry-retries 4 \\\n --format table\n`;
|
|
50
50
|
}
|
|
51
51
|
function strictWorkflowTemplate(scheduleBlock, packageManager) {
|
|
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 - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: '20'\n\n${installStep(packageManager)}\n\n - name: Warm cache\n run: npx @rainy-updates/cli warm-cache --workspace --concurrency 32\n\n - name: Run strict dependency check\n run: |\n npx @rainy-updates/cli ci \\\n --workspace \\\n --mode strict \\\n --ci \\\n --concurrency 32 \\\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`;
|
|
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 - name: Checkout\n uses: actions/checkout@v4\n\n - name: Setup Node\n uses: actions/setup-node@v4\n with:\n node-version: '20'\n\n${installStep(packageManager)}\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: Run strict dependency check\n run: |\n npx @rainy-updates/cli ci \\\n --workspace \\\n --mode strict \\\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
53
|
}
|
|
54
54
|
function enterpriseWorkflowTemplate(scheduleBlock, packageManager) {
|
|
55
55
|
const detectedPmInstall = packageManager === "pnpm"
|
|
56
56
|
? "corepack enable && corepack prepare pnpm@9 --activate && pnpm install --frozen-lockfile"
|
|
57
57
|
: "npm ci";
|
|
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\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 --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`;
|
|
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`;
|
|
59
59
|
}
|
package/dist/core/options.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaselineOptions, CheckOptions, UpgradeOptions } from "../types/index.js";
|
|
1
|
+
import type { BaselineOptions, CheckOptions, UpgradeOptions, AuditOptions, BisectOptions, HealthOptions } from "../types/index.js";
|
|
2
2
|
import type { InitCiMode, InitCiSchedule } from "./init-ci.js";
|
|
3
3
|
export type ParsedCliArgs = {
|
|
4
4
|
command: "check";
|
|
@@ -25,5 +25,14 @@ export type ParsedCliArgs = {
|
|
|
25
25
|
options: BaselineOptions & {
|
|
26
26
|
action: "save" | "check";
|
|
27
27
|
};
|
|
28
|
+
} | {
|
|
29
|
+
command: "bisect";
|
|
30
|
+
options: BisectOptions;
|
|
31
|
+
} | {
|
|
32
|
+
command: "audit";
|
|
33
|
+
options: AuditOptions;
|
|
34
|
+
} | {
|
|
35
|
+
command: "health";
|
|
36
|
+
options: HealthOptions;
|
|
28
37
|
};
|
|
29
38
|
export declare function parseCliArgs(argv: string[]): Promise<ParsedCliArgs>;
|
package/dist/core/options.js
CHANGED
|
@@ -7,7 +7,17 @@ const DEFAULT_INCLUDE_KINDS = [
|
|
|
7
7
|
"optionalDependencies",
|
|
8
8
|
"peerDependencies",
|
|
9
9
|
];
|
|
10
|
-
const KNOWN_COMMANDS = [
|
|
10
|
+
const KNOWN_COMMANDS = [
|
|
11
|
+
"check",
|
|
12
|
+
"upgrade",
|
|
13
|
+
"warm-cache",
|
|
14
|
+
"init-ci",
|
|
15
|
+
"baseline",
|
|
16
|
+
"ci",
|
|
17
|
+
"bisect",
|
|
18
|
+
"audit",
|
|
19
|
+
"health",
|
|
20
|
+
];
|
|
11
21
|
export async function parseCliArgs(argv) {
|
|
12
22
|
const firstArg = argv[0];
|
|
13
23
|
const isKnownCommand = KNOWN_COMMANDS.includes(firstArg);
|
|
@@ -31,7 +41,10 @@ export async function parseCliArgs(argv) {
|
|
|
31
41
|
githubOutputFile: undefined,
|
|
32
42
|
sarifFile: undefined,
|
|
33
43
|
concurrency: 16,
|
|
44
|
+
registryTimeoutMs: 8000,
|
|
45
|
+
registryRetries: 3,
|
|
34
46
|
offline: false,
|
|
47
|
+
stream: false,
|
|
35
48
|
policyFile: undefined,
|
|
36
49
|
prReportFile: undefined,
|
|
37
50
|
failOn: "none",
|
|
@@ -50,6 +63,7 @@ export async function parseCliArgs(argv) {
|
|
|
50
63
|
prLimit: undefined,
|
|
51
64
|
onlyChanged: false,
|
|
52
65
|
ciProfile: "minimal",
|
|
66
|
+
lockfileMode: "preserve",
|
|
53
67
|
};
|
|
54
68
|
let force = false;
|
|
55
69
|
let initCiMode = "enterprise";
|
|
@@ -165,10 +179,38 @@ export async function parseCliArgs(argv) {
|
|
|
165
179
|
if (current === "--concurrency") {
|
|
166
180
|
throw new Error("Missing value for --concurrency");
|
|
167
181
|
}
|
|
182
|
+
if (current === "--registry-timeout-ms" && next) {
|
|
183
|
+
const parsed = Number(next);
|
|
184
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
185
|
+
throw new Error("--registry-timeout-ms must be a positive integer");
|
|
186
|
+
}
|
|
187
|
+
base.registryTimeoutMs = parsed;
|
|
188
|
+
index += 1;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
if (current === "--registry-timeout-ms") {
|
|
192
|
+
throw new Error("Missing value for --registry-timeout-ms");
|
|
193
|
+
}
|
|
194
|
+
if (current === "--registry-retries" && next) {
|
|
195
|
+
const parsed = Number(next);
|
|
196
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
197
|
+
throw new Error("--registry-retries must be a positive integer");
|
|
198
|
+
}
|
|
199
|
+
base.registryRetries = parsed;
|
|
200
|
+
index += 1;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (current === "--registry-retries") {
|
|
204
|
+
throw new Error("Missing value for --registry-retries");
|
|
205
|
+
}
|
|
168
206
|
if (current === "--offline") {
|
|
169
207
|
base.offline = true;
|
|
170
208
|
continue;
|
|
171
209
|
}
|
|
210
|
+
if (current === "--stream") {
|
|
211
|
+
base.stream = true;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
172
214
|
if (current === "--policy-file" && next) {
|
|
173
215
|
policyFileRaw = next;
|
|
174
216
|
index += 1;
|
|
@@ -352,6 +394,14 @@ export async function parseCliArgs(argv) {
|
|
|
352
394
|
base.onlyChanged = true;
|
|
353
395
|
continue;
|
|
354
396
|
}
|
|
397
|
+
if (current === "--lockfile-mode" && next) {
|
|
398
|
+
base.lockfileMode = ensureLockfileMode(next);
|
|
399
|
+
index += 1;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
if (current === "--lockfile-mode") {
|
|
403
|
+
throw new Error("Missing value for --lockfile-mode");
|
|
404
|
+
}
|
|
355
405
|
if (current === "--save") {
|
|
356
406
|
baselineAction = "save";
|
|
357
407
|
continue;
|
|
@@ -435,6 +485,19 @@ export async function parseCliArgs(argv) {
|
|
|
435
485
|
},
|
|
436
486
|
};
|
|
437
487
|
}
|
|
488
|
+
// ─── New v0.5.1 commands: lazy-parsed by isolated sub-parsers ────────────
|
|
489
|
+
if (command === "bisect") {
|
|
490
|
+
const { parseBisectArgs } = await import("../commands/bisect/parser.js");
|
|
491
|
+
return { command, options: parseBisectArgs(args) };
|
|
492
|
+
}
|
|
493
|
+
if (command === "audit") {
|
|
494
|
+
const { parseAuditArgs } = await import("../commands/audit/parser.js");
|
|
495
|
+
return { command, options: parseAuditArgs(args) };
|
|
496
|
+
}
|
|
497
|
+
if (command === "health") {
|
|
498
|
+
const { parseHealthArgs } = await import("../commands/health/parser.js");
|
|
499
|
+
return { command, options: parseHealthArgs(args) };
|
|
500
|
+
}
|
|
438
501
|
return {
|
|
439
502
|
command: "check",
|
|
440
503
|
options: base,
|
|
@@ -465,12 +528,27 @@ function applyConfig(base, config) {
|
|
|
465
528
|
if (typeof config.sarifFile === "string") {
|
|
466
529
|
base.sarifFile = path.resolve(base.cwd, config.sarifFile);
|
|
467
530
|
}
|
|
468
|
-
if (typeof config.concurrency === "number" &&
|
|
531
|
+
if (typeof config.concurrency === "number" &&
|
|
532
|
+
Number.isInteger(config.concurrency) &&
|
|
533
|
+
config.concurrency > 0) {
|
|
469
534
|
base.concurrency = config.concurrency;
|
|
470
535
|
}
|
|
536
|
+
if (typeof config.registryTimeoutMs === "number" &&
|
|
537
|
+
Number.isInteger(config.registryTimeoutMs) &&
|
|
538
|
+
config.registryTimeoutMs > 0) {
|
|
539
|
+
base.registryTimeoutMs = config.registryTimeoutMs;
|
|
540
|
+
}
|
|
541
|
+
if (typeof config.registryRetries === "number" &&
|
|
542
|
+
Number.isInteger(config.registryRetries) &&
|
|
543
|
+
config.registryRetries > 0) {
|
|
544
|
+
base.registryRetries = config.registryRetries;
|
|
545
|
+
}
|
|
471
546
|
if (typeof config.offline === "boolean") {
|
|
472
547
|
base.offline = config.offline;
|
|
473
548
|
}
|
|
549
|
+
if (typeof config.stream === "boolean") {
|
|
550
|
+
base.stream = config.stream;
|
|
551
|
+
}
|
|
474
552
|
if (typeof config.policyFile === "string") {
|
|
475
553
|
base.policyFile = path.resolve(base.cwd, config.policyFile);
|
|
476
554
|
}
|
|
@@ -480,7 +558,9 @@ function applyConfig(base, config) {
|
|
|
480
558
|
if (typeof config.failOn === "string") {
|
|
481
559
|
base.failOn = ensureFailOn(config.failOn);
|
|
482
560
|
}
|
|
483
|
-
if (typeof config.maxUpdates === "number" &&
|
|
561
|
+
if (typeof config.maxUpdates === "number" &&
|
|
562
|
+
Number.isInteger(config.maxUpdates) &&
|
|
563
|
+
config.maxUpdates >= 0) {
|
|
484
564
|
base.maxUpdates = config.maxUpdates;
|
|
485
565
|
}
|
|
486
566
|
if (typeof config.fixPr === "boolean") {
|
|
@@ -489,7 +569,8 @@ function applyConfig(base, config) {
|
|
|
489
569
|
if (typeof config.fixBranch === "string" && config.fixBranch.length > 0) {
|
|
490
570
|
base.fixBranch = config.fixBranch;
|
|
491
571
|
}
|
|
492
|
-
if (typeof config.fixCommitMessage === "string" &&
|
|
572
|
+
if (typeof config.fixCommitMessage === "string" &&
|
|
573
|
+
config.fixCommitMessage.length > 0) {
|
|
493
574
|
base.fixCommitMessage = config.fixCommitMessage;
|
|
494
575
|
}
|
|
495
576
|
if (typeof config.fixDryRun === "boolean") {
|
|
@@ -498,7 +579,9 @@ function applyConfig(base, config) {
|
|
|
498
579
|
if (typeof config.fixPrNoCheckout === "boolean") {
|
|
499
580
|
base.fixPrNoCheckout = config.fixPrNoCheckout;
|
|
500
581
|
}
|
|
501
|
-
if (typeof config.fixPrBatchSize === "number" &&
|
|
582
|
+
if (typeof config.fixPrBatchSize === "number" &&
|
|
583
|
+
Number.isInteger(config.fixPrBatchSize) &&
|
|
584
|
+
config.fixPrBatchSize > 0) {
|
|
502
585
|
base.fixPrBatchSize = config.fixPrBatchSize;
|
|
503
586
|
}
|
|
504
587
|
if (typeof config.noPrReport === "boolean") {
|
|
@@ -510,13 +593,19 @@ function applyConfig(base, config) {
|
|
|
510
593
|
if (typeof config.groupBy === "string") {
|
|
511
594
|
base.groupBy = ensureGroupBy(config.groupBy);
|
|
512
595
|
}
|
|
513
|
-
if (typeof config.groupMax === "number" &&
|
|
596
|
+
if (typeof config.groupMax === "number" &&
|
|
597
|
+
Number.isInteger(config.groupMax) &&
|
|
598
|
+
config.groupMax > 0) {
|
|
514
599
|
base.groupMax = config.groupMax;
|
|
515
600
|
}
|
|
516
|
-
if (typeof config.cooldownDays === "number" &&
|
|
601
|
+
if (typeof config.cooldownDays === "number" &&
|
|
602
|
+
Number.isInteger(config.cooldownDays) &&
|
|
603
|
+
config.cooldownDays >= 0) {
|
|
517
604
|
base.cooldownDays = config.cooldownDays;
|
|
518
605
|
}
|
|
519
|
-
if (typeof config.prLimit === "number" &&
|
|
606
|
+
if (typeof config.prLimit === "number" &&
|
|
607
|
+
Number.isInteger(config.prLimit) &&
|
|
608
|
+
config.prLimit > 0) {
|
|
520
609
|
base.prLimit = config.prLimit;
|
|
521
610
|
}
|
|
522
611
|
if (typeof config.onlyChanged === "boolean") {
|
|
@@ -525,6 +614,9 @@ function applyConfig(base, config) {
|
|
|
525
614
|
if (typeof config.ciProfile === "string") {
|
|
526
615
|
base.ciProfile = ensureCiProfile(config.ciProfile);
|
|
527
616
|
}
|
|
617
|
+
if (typeof config.lockfileMode === "string") {
|
|
618
|
+
base.lockfileMode = ensureLockfileMode(config.lockfileMode);
|
|
619
|
+
}
|
|
528
620
|
}
|
|
529
621
|
function parsePackageManager(args) {
|
|
530
622
|
const index = args.indexOf("--pm");
|
|
@@ -537,19 +629,29 @@ function parsePackageManager(args) {
|
|
|
537
629
|
throw new Error("--pm must be auto, npm or pnpm");
|
|
538
630
|
}
|
|
539
631
|
function ensureTarget(value) {
|
|
540
|
-
if (value === "patch" ||
|
|
632
|
+
if (value === "patch" ||
|
|
633
|
+
value === "minor" ||
|
|
634
|
+
value === "major" ||
|
|
635
|
+
value === "latest") {
|
|
541
636
|
return value;
|
|
542
637
|
}
|
|
543
638
|
throw new Error("--target must be patch, minor, major, latest");
|
|
544
639
|
}
|
|
545
640
|
function ensureFormat(value) {
|
|
546
|
-
if (value === "table" ||
|
|
641
|
+
if (value === "table" ||
|
|
642
|
+
value === "json" ||
|
|
643
|
+
value === "minimal" ||
|
|
644
|
+
value === "github" ||
|
|
645
|
+
value === "metrics") {
|
|
547
646
|
return value;
|
|
548
647
|
}
|
|
549
648
|
throw new Error("--format must be table, json, minimal, github or metrics");
|
|
550
649
|
}
|
|
551
650
|
function ensureLogLevel(value) {
|
|
552
|
-
if (value === "error" ||
|
|
651
|
+
if (value === "error" ||
|
|
652
|
+
value === "warn" ||
|
|
653
|
+
value === "info" ||
|
|
654
|
+
value === "debug") {
|
|
553
655
|
return value;
|
|
554
656
|
}
|
|
555
657
|
throw new Error("--log-level must be error, warn, info or debug");
|
|
@@ -588,13 +690,21 @@ function ensureInitCiSchedule(value) {
|
|
|
588
690
|
throw new Error("--schedule must be weekly, daily or off");
|
|
589
691
|
}
|
|
590
692
|
function ensureFailOn(value) {
|
|
591
|
-
if (value === "none" ||
|
|
693
|
+
if (value === "none" ||
|
|
694
|
+
value === "patch" ||
|
|
695
|
+
value === "minor" ||
|
|
696
|
+
value === "major" ||
|
|
697
|
+
value === "any") {
|
|
592
698
|
return value;
|
|
593
699
|
}
|
|
594
700
|
throw new Error("--fail-on must be none, patch, minor, major or any");
|
|
595
701
|
}
|
|
596
702
|
function ensureGroupBy(value) {
|
|
597
|
-
if (value === "none" ||
|
|
703
|
+
if (value === "none" ||
|
|
704
|
+
value === "name" ||
|
|
705
|
+
value === "scope" ||
|
|
706
|
+
value === "kind" ||
|
|
707
|
+
value === "risk") {
|
|
598
708
|
return value;
|
|
599
709
|
}
|
|
600
710
|
throw new Error("--group-by must be none, name, scope, kind or risk");
|
|
@@ -605,3 +715,9 @@ function ensureCiProfile(value) {
|
|
|
605
715
|
}
|
|
606
716
|
throw new Error("--mode must be minimal, strict or enterprise");
|
|
607
717
|
}
|
|
718
|
+
function ensureLockfileMode(value) {
|
|
719
|
+
if (value === "preserve" || value === "update" || value === "error") {
|
|
720
|
+
return value;
|
|
721
|
+
}
|
|
722
|
+
throw new Error("--lockfile-mode must be preserve, update or error");
|
|
723
|
+
}
|
package/dist/core/summary.d.ts
CHANGED
|
@@ -20,6 +20,7 @@ export declare function createSummary(input: {
|
|
|
20
20
|
cooldownSkipped?: number;
|
|
21
21
|
ciProfile?: Summary["ciProfile"];
|
|
22
22
|
prLimitHit?: boolean;
|
|
23
|
+
policyOverridesApplied?: number;
|
|
23
24
|
}): Summary;
|
|
24
25
|
export declare function finalizeSummary(summary: Summary): Summary;
|
|
25
26
|
export declare function resolveFailReason(updates: PackageUpdate[], errors: string[], failOn: FailOnLevel | undefined, maxUpdates: number | undefined, ciMode: boolean): FailReason;
|
package/dist/core/summary.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export function createSummary(input) {
|
|
2
2
|
const offlineCacheMiss = input.errors.filter((error) => isOfflineCacheMissError(error)).length;
|
|
3
3
|
const registryFailure = input.errors.filter((error) => isRegistryFailureError(error)).length;
|
|
4
|
+
const registryAuthFailure = input.errors.filter((error) => isRegistryAuthError(error)).length;
|
|
4
5
|
const staleCache = input.warnings.filter((warning) => warning.includes("Using stale cache")).length;
|
|
5
6
|
return {
|
|
6
7
|
contractVersion: "2",
|
|
@@ -16,6 +17,7 @@ export function createSummary(input) {
|
|
|
16
17
|
total: input.errors.length,
|
|
17
18
|
offlineCacheMiss,
|
|
18
19
|
registryFailure,
|
|
20
|
+
registryAuthFailure,
|
|
19
21
|
other: 0,
|
|
20
22
|
},
|
|
21
23
|
warningCounts: {
|
|
@@ -38,10 +40,15 @@ export function createSummary(input) {
|
|
|
38
40
|
cooldownSkipped: Math.max(0, Math.round(input.cooldownSkipped ?? 0)),
|
|
39
41
|
ciProfile: input.ciProfile ?? "minimal",
|
|
40
42
|
prLimitHit: input.prLimitHit === true,
|
|
43
|
+
streamedEvents: 0,
|
|
44
|
+
policyOverridesApplied: Math.max(0, Math.round(input.policyOverridesApplied ?? 0)),
|
|
41
45
|
};
|
|
42
46
|
}
|
|
43
47
|
export function finalizeSummary(summary) {
|
|
44
|
-
const errorOther = summary.errorCounts.total -
|
|
48
|
+
const errorOther = summary.errorCounts.total -
|
|
49
|
+
summary.errorCounts.offlineCacheMiss -
|
|
50
|
+
summary.errorCounts.registryFailure -
|
|
51
|
+
summary.errorCounts.registryAuthFailure;
|
|
45
52
|
const warningOther = summary.warningCounts.total - summary.warningCounts.staleCache;
|
|
46
53
|
summary.errorCounts.other = Math.max(0, errorOther);
|
|
47
54
|
summary.warningCounts.other = Math.max(0, warningOther);
|
|
@@ -81,3 +88,6 @@ function isRegistryFailureError(value) {
|
|
|
81
88
|
value.includes("Registry request failed") ||
|
|
82
89
|
value.includes("Registry temporary error"));
|
|
83
90
|
}
|
|
91
|
+
function isRegistryAuthError(value) {
|
|
92
|
+
return value.includes("Registry authentication failed") || value.includes("401");
|
|
93
|
+
}
|
package/dist/core/upgrade.js
CHANGED
|
@@ -3,7 +3,10 @@ import { readManifest, writeManifest } from "../parsers/package-json.js";
|
|
|
3
3
|
import { installDependencies } from "../pm/install.js";
|
|
4
4
|
import { applyRangeStyle, parseVersion, compareVersions } from "../utils/semver.js";
|
|
5
5
|
import { buildWorkspaceGraph } from "../workspace/graph.js";
|
|
6
|
+
import { captureLockfileSnapshot, changedLockfiles, validateLockfileMode } from "../utils/lockfile.js";
|
|
6
7
|
export async function upgrade(options) {
|
|
8
|
+
validateLockfileMode(options.lockfileMode, options.install);
|
|
9
|
+
const lockfilesBefore = await captureLockfileSnapshot(options.cwd);
|
|
7
10
|
const checkResult = await check(options);
|
|
8
11
|
if (checkResult.updates.length === 0) {
|
|
9
12
|
return {
|
|
@@ -34,6 +37,13 @@ export async function upgrade(options) {
|
|
|
34
37
|
if (options.install) {
|
|
35
38
|
await installDependencies(options.cwd, options.packageManager, checkResult.packageManager);
|
|
36
39
|
}
|
|
40
|
+
const lockfileChanges = await changedLockfiles(options.cwd, lockfilesBefore);
|
|
41
|
+
if (lockfileChanges.length > 0 && (options.lockfileMode === "preserve" || options.lockfileMode === "error")) {
|
|
42
|
+
throw new Error(`Lockfile changes detected in ${options.lockfileMode} mode: ${lockfileChanges.join(", ")}`);
|
|
43
|
+
}
|
|
44
|
+
if (lockfileChanges.length > 0 && options.lockfileMode === "update") {
|
|
45
|
+
checkResult.warnings.push(`Lockfiles changed: ${lockfileChanges.map((item) => item.split("/").pop()).join(", ")}`);
|
|
46
|
+
}
|
|
37
47
|
return {
|
|
38
48
|
...checkResult,
|
|
39
49
|
changed: true,
|
package/dist/core/warm-cache.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import process from "node:process";
|
|
1
2
|
import { collectDependencies, readManifest } from "../parsers/package-json.js";
|
|
2
3
|
import { matchesPattern } from "../utils/pattern.js";
|
|
3
4
|
import { VersionCache } from "../cache/cache.js";
|
|
@@ -15,7 +16,10 @@ export async function warmCache(options) {
|
|
|
15
16
|
const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
|
|
16
17
|
discoveryMs += Date.now() - discoveryStartedAt;
|
|
17
18
|
const cache = await VersionCache.create();
|
|
18
|
-
const registryClient = new NpmRegistryClient(options.cwd
|
|
19
|
+
const registryClient = new NpmRegistryClient(options.cwd, {
|
|
20
|
+
timeoutMs: options.registryTimeoutMs,
|
|
21
|
+
retries: options.registryRetries,
|
|
22
|
+
});
|
|
19
23
|
const errors = [];
|
|
20
24
|
const warnings = [];
|
|
21
25
|
if (cache.degraded) {
|
|
@@ -23,6 +27,13 @@ export async function warmCache(options) {
|
|
|
23
27
|
}
|
|
24
28
|
let totalDependencies = 0;
|
|
25
29
|
const packageNames = new Set();
|
|
30
|
+
let streamedEvents = 0;
|
|
31
|
+
const emitStream = (message) => {
|
|
32
|
+
if (!options.stream)
|
|
33
|
+
return;
|
|
34
|
+
streamedEvents += 1;
|
|
35
|
+
process.stdout.write(`${message}\n`);
|
|
36
|
+
};
|
|
26
37
|
for (const packageDir of packageDirs) {
|
|
27
38
|
try {
|
|
28
39
|
const manifest = await readManifest(packageDir);
|
|
@@ -57,10 +68,12 @@ export async function warmCache(options) {
|
|
|
57
68
|
const stale = await cache.getAny(pkg, options.target);
|
|
58
69
|
if (stale) {
|
|
59
70
|
warnings.push(`Using stale cache for ${pkg} in offline warm-cache mode.`);
|
|
71
|
+
emitStream(`[warm-cache-stale] ${pkg}`);
|
|
60
72
|
warmed += 1;
|
|
61
73
|
}
|
|
62
74
|
else {
|
|
63
75
|
errors.push(`Offline cache miss for ${pkg}. Cannot warm cache in --offline mode.`);
|
|
76
|
+
emitStream(`[error] Offline cache miss for ${pkg}`);
|
|
64
77
|
}
|
|
65
78
|
}
|
|
66
79
|
cacheMs += Date.now() - cacheFallbackStartedAt;
|
|
@@ -69,6 +82,8 @@ export async function warmCache(options) {
|
|
|
69
82
|
const registryStartedAt = Date.now();
|
|
70
83
|
const fetched = await registryClient.resolveManyPackageMetadata(needsFetch, {
|
|
71
84
|
concurrency: options.concurrency,
|
|
85
|
+
retries: options.registryRetries,
|
|
86
|
+
timeoutMs: options.registryTimeoutMs,
|
|
72
87
|
});
|
|
73
88
|
registryMs += Date.now() - registryStartedAt;
|
|
74
89
|
const cacheWriteStartedAt = Date.now();
|
|
@@ -76,11 +91,13 @@ export async function warmCache(options) {
|
|
|
76
91
|
if (metadata.latestVersion) {
|
|
77
92
|
await cache.set(pkg, options.target, metadata.latestVersion, metadata.versions, options.cacheTtlSeconds);
|
|
78
93
|
warmed += 1;
|
|
94
|
+
emitStream(`[warmed] ${pkg}@${metadata.latestVersion}`);
|
|
79
95
|
}
|
|
80
96
|
}
|
|
81
97
|
cacheMs += Date.now() - cacheWriteStartedAt;
|
|
82
98
|
for (const [pkg, error] of fetched.errors) {
|
|
83
99
|
errors.push(`Unable to warm ${pkg}: ${error}`);
|
|
100
|
+
emitStream(`[error] Unable to warm ${pkg}: ${error}`);
|
|
84
101
|
}
|
|
85
102
|
}
|
|
86
103
|
}
|
|
@@ -104,6 +121,7 @@ export async function warmCache(options) {
|
|
|
104
121
|
},
|
|
105
122
|
ciProfile: options.ciProfile,
|
|
106
123
|
}));
|
|
124
|
+
summary.streamedEvents = streamedEvents;
|
|
107
125
|
return {
|
|
108
126
|
projectPath: options.cwd,
|
|
109
127
|
packagePaths: packageDirs,
|
package/dist/output/format.js
CHANGED
|
@@ -36,6 +36,9 @@ export function renderResult(result, format) {
|
|
|
36
36
|
`cooldown_skipped=${result.summary.cooldownSkipped}`,
|
|
37
37
|
`ci_profile=${result.summary.ciProfile}`,
|
|
38
38
|
`pr_limit_hit=${result.summary.prLimitHit ? "1" : "0"}`,
|
|
39
|
+
`streamed_events=${result.summary.streamedEvents}`,
|
|
40
|
+
`policy_overrides_applied=${result.summary.policyOverridesApplied}`,
|
|
41
|
+
`registry_auth_failures=${result.summary.errorCounts.registryAuthFailure}`,
|
|
39
42
|
`fix_pr_branches_created=${result.summary.fixPrBranchesCreated}`,
|
|
40
43
|
].join("\n");
|
|
41
44
|
}
|
|
@@ -76,6 +79,7 @@ export function renderResult(result, format) {
|
|
|
76
79
|
lines.push("");
|
|
77
80
|
lines.push(`Summary: ${result.summary.updatesFound} updates, ${result.summary.checkedDependencies}/${result.summary.totalDependencies} checked, ${result.summary.warmedPackages} warmed`);
|
|
78
81
|
lines.push(`Groups=${result.summary.groupedUpdates}, cooldownSkipped=${result.summary.cooldownSkipped}, ciProfile=${result.summary.ciProfile}, prLimitHit=${result.summary.prLimitHit ? "yes" : "no"}`);
|
|
82
|
+
lines.push(`StreamedEvents=${result.summary.streamedEvents}, policyOverrides=${result.summary.policyOverridesApplied}, registryAuthFailures=${result.summary.errorCounts.registryAuthFailure}`);
|
|
79
83
|
lines.push(`Contract v${result.summary.contractVersion}, failReason=${result.summary.failReason}, duration=${result.summary.durationMs.total}ms`);
|
|
80
84
|
if (result.summary.fixPrApplied) {
|
|
81
85
|
lines.push(`Fix PR: applied on branch ${result.summary.fixBranchName ?? "unknown"} (${result.summary.fixCommitSha ?? "no-commit"})`);
|
package/dist/output/github.js
CHANGED
|
@@ -18,6 +18,9 @@ export async function writeGitHubOutput(filePath, result) {
|
|
|
18
18
|
`cooldown_skipped=${result.summary.cooldownSkipped}`,
|
|
19
19
|
`ci_profile=${result.summary.ciProfile}`,
|
|
20
20
|
`pr_limit_hit=${result.summary.prLimitHit === true ? "1" : "0"}`,
|
|
21
|
+
`streamed_events=${result.summary.streamedEvents}`,
|
|
22
|
+
`policy_overrides_applied=${result.summary.policyOverridesApplied}`,
|
|
23
|
+
`registry_auth_failures=${result.summary.errorCounts.registryAuthFailure}`,
|
|
21
24
|
`fix_pr_applied=${result.summary.fixPrApplied === true ? "1" : "0"}`,
|
|
22
25
|
`fix_pr_branches_created=${result.summary.fixPrBranchesCreated}`,
|
|
23
26
|
`fix_pr_branch=${result.summary.fixBranchName ?? ""}`,
|
package/dist/registry/npm.d.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export interface ResolveManyOptions {
|
|
2
2
|
concurrency: number;
|
|
3
3
|
timeoutMs?: number;
|
|
4
|
+
retries?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface RegistryClientOptions {
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
retries?: number;
|
|
4
9
|
}
|
|
5
10
|
export interface ResolveManyResult {
|
|
6
11
|
metadata: Map<string, {
|
|
@@ -12,8 +17,10 @@ export interface ResolveManyResult {
|
|
|
12
17
|
}
|
|
13
18
|
export declare class NpmRegistryClient {
|
|
14
19
|
private readonly requesterPromise;
|
|
15
|
-
|
|
16
|
-
|
|
20
|
+
private readonly defaultTimeoutMs;
|
|
21
|
+
private readonly defaultRetries;
|
|
22
|
+
constructor(cwd?: string, options?: RegistryClientOptions);
|
|
23
|
+
resolvePackageMetadata(packageName: string, timeoutMs?: number, retries?: number): Promise<{
|
|
17
24
|
latestVersion: string | null;
|
|
18
25
|
versions: string[];
|
|
19
26
|
publishedAtByVersion: Record<string, number>;
|