@rainy-updates/cli 0.4.4 → 0.5.0-rc.2

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.
@@ -1,7 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { collectDependencies, readManifest } from "../parsers/package-json.js";
3
3
  import { matchesPattern } from "../utils/pattern.js";
4
- import { applyRangeStyle, classifyDiff, clampTarget, pickTargetVersion } from "../utils/semver.js";
4
+ import { applyRangeStyle, classifyDiff, clampTarget, pickTargetVersionFromAvailable } from "../utils/semver.js";
5
5
  import { VersionCache } from "../cache/cache.js";
6
6
  import { NpmRegistryClient } from "../registry/npm.js";
7
7
  import { detectPackageManager } from "../pm/detect.js";
@@ -11,7 +11,7 @@ export async function check(options) {
11
11
  const packageManager = await detectPackageManager(options.cwd);
12
12
  const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
13
13
  const cache = await VersionCache.create();
14
- const registryClient = new NpmRegistryClient();
14
+ const registryClient = new NpmRegistryClient(options.cwd);
15
15
  const policy = await loadPolicy(options.cwd, options.policyFile);
16
16
  const updates = [];
17
17
  const errors = [];
@@ -53,7 +53,10 @@ export async function check(options) {
53
53
  for (const packageName of uniquePackageNames) {
54
54
  const cached = await cache.getValid(packageName, options.target);
55
55
  if (cached) {
56
- resolvedVersions.set(packageName, cached.latestVersion);
56
+ resolvedVersions.set(packageName, {
57
+ latestVersion: cached.latestVersion,
58
+ availableVersions: cached.availableVersions,
59
+ });
57
60
  }
58
61
  else {
59
62
  unresolvedPackages.push(packageName);
@@ -64,7 +67,10 @@ export async function check(options) {
64
67
  for (const packageName of unresolvedPackages) {
65
68
  const stale = await cache.getAny(packageName, options.target);
66
69
  if (stale) {
67
- resolvedVersions.set(packageName, stale.latestVersion);
70
+ resolvedVersions.set(packageName, {
71
+ latestVersion: stale.latestVersion,
72
+ availableVersions: stale.availableVersions,
73
+ });
68
74
  warnings.push(`Using stale cache for ${packageName} because --offline is enabled.`);
69
75
  }
70
76
  else {
@@ -73,19 +79,25 @@ export async function check(options) {
73
79
  }
74
80
  }
75
81
  else {
76
- const fetched = await registryClient.resolveManyLatestVersions(unresolvedPackages, {
82
+ const fetched = await registryClient.resolveManyPackageMetadata(unresolvedPackages, {
77
83
  concurrency: options.concurrency,
78
84
  });
79
- for (const [packageName, version] of fetched.versions) {
80
- resolvedVersions.set(packageName, version);
81
- if (version) {
82
- await cache.set(packageName, options.target, version, options.cacheTtlSeconds);
85
+ for (const [packageName, metadata] of fetched.metadata) {
86
+ resolvedVersions.set(packageName, {
87
+ latestVersion: metadata.latestVersion,
88
+ availableVersions: metadata.versions,
89
+ });
90
+ if (metadata.latestVersion) {
91
+ await cache.set(packageName, options.target, metadata.latestVersion, metadata.versions, options.cacheTtlSeconds);
83
92
  }
84
93
  }
85
94
  for (const [packageName, error] of fetched.errors) {
86
95
  const stale = await cache.getAny(packageName, options.target);
87
96
  if (stale) {
88
- resolvedVersions.set(packageName, stale.latestVersion);
97
+ resolvedVersions.set(packageName, {
98
+ latestVersion: stale.latestVersion,
99
+ availableVersions: stale.availableVersions,
100
+ });
89
101
  warnings.push(`Using stale cache for ${packageName} due to registry error: ${error}`);
90
102
  }
91
103
  else {
@@ -95,12 +107,12 @@ export async function check(options) {
95
107
  }
96
108
  }
97
109
  for (const task of tasks) {
98
- const latestVersion = resolvedVersions.get(task.dependency.name);
99
- if (!latestVersion)
110
+ const metadata = resolvedVersions.get(task.dependency.name);
111
+ if (!metadata?.latestVersion)
100
112
  continue;
101
113
  const rule = policy.packageRules.get(task.dependency.name);
102
114
  const effectiveTarget = clampTarget(options.target, rule?.maxTarget);
103
- const picked = pickTargetVersion(task.dependency.range, latestVersion, effectiveTarget);
115
+ const picked = pickTargetVersionFromAvailable(task.dependency.range, metadata.availableVersions, metadata.latestVersion, effectiveTarget);
104
116
  if (!picked)
105
117
  continue;
106
118
  const nextRange = applyRangeStyle(task.dependency.range, picked);
@@ -0,0 +1,7 @@
1
+ import type { CheckResult, RunOptions } from "../types/index.js";
2
+ export interface FixPrResult {
3
+ applied: boolean;
4
+ branchName?: string;
5
+ commitSha?: string;
6
+ }
7
+ export declare function applyFixPr(options: RunOptions, result: CheckResult, extraFiles: string[]): Promise<FixPrResult>;
@@ -0,0 +1,68 @@
1
+ import { spawn } from "node:child_process";
2
+ import path from "node:path";
3
+ export async function applyFixPr(options, result, extraFiles) {
4
+ if (!options.fixPr)
5
+ return { applied: false };
6
+ if (result.updates.length === 0)
7
+ return { applied: false };
8
+ const status = await runGit(options.cwd, ["status", "--porcelain"]);
9
+ if (status.stdout.trim().length > 0) {
10
+ throw new Error("Cannot run --fix-pr with a dirty git working tree.");
11
+ }
12
+ const branch = options.fixBranch ?? "chore/rainy-updates";
13
+ const branchCheck = await runGit(options.cwd, ["rev-parse", "--verify", "--quiet", branch], true);
14
+ if (branchCheck.code === 0) {
15
+ await runGit(options.cwd, ["checkout", branch]);
16
+ }
17
+ else {
18
+ await runGit(options.cwd, ["checkout", "-b", branch]);
19
+ }
20
+ if (options.fixDryRun) {
21
+ return {
22
+ applied: false,
23
+ branchName: branch,
24
+ };
25
+ }
26
+ const manifestFiles = Array.from(new Set(result.packagePaths.map((pkgPath) => path.join(pkgPath, "package.json"))));
27
+ const filesToStage = Array.from(new Set([...manifestFiles, ...extraFiles]));
28
+ if (filesToStage.length > 0) {
29
+ await runGit(options.cwd, ["add", "--", ...filesToStage]);
30
+ }
31
+ const stagedCheck = await runGit(options.cwd, ["diff", "--cached", "--quiet"], true);
32
+ if (stagedCheck.code === 0) {
33
+ return {
34
+ applied: false,
35
+ branchName: branch,
36
+ };
37
+ }
38
+ const message = options.fixCommitMessage ?? `chore(deps): apply rainy-updates (${result.updates.length} updates)`;
39
+ await runGit(options.cwd, ["commit", "-m", message]);
40
+ const rev = await runGit(options.cwd, ["rev-parse", "HEAD"]);
41
+ return {
42
+ applied: true,
43
+ branchName: branch,
44
+ commitSha: rev.stdout.trim(),
45
+ };
46
+ }
47
+ async function runGit(cwd, args, allowNonZero = false) {
48
+ return await new Promise((resolve, reject) => {
49
+ const child = spawn("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"] });
50
+ let stdout = "";
51
+ let stderr = "";
52
+ child.stdout.on("data", (chunk) => {
53
+ stdout += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
54
+ });
55
+ child.stderr.on("data", (chunk) => {
56
+ stderr += typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8");
57
+ });
58
+ child.on("error", reject);
59
+ child.on("exit", (code) => {
60
+ const normalized = code ?? 1;
61
+ if (normalized !== 0 && !allowNonZero) {
62
+ reject(new Error(`git ${args.join(" ")} failed (${normalized}): ${stderr.trim()}`));
63
+ return;
64
+ }
65
+ resolve({ code: normalized, stdout, stderr });
66
+ });
67
+ });
68
+ }
@@ -1,4 +1,4 @@
1
- export type InitCiMode = "minimal" | "strict";
1
+ export type InitCiMode = "minimal" | "strict" | "enterprise";
2
2
  export type InitCiSchedule = "weekly" | "daily" | "off";
3
3
  export interface InitCiOptions {
4
4
  mode: InitCiMode;
@@ -15,7 +15,9 @@ export async function initCiWorkflow(cwd, force, options) {
15
15
  const scheduleBlock = renderScheduleBlock(options.schedule);
16
16
  const workflow = options.mode === "minimal"
17
17
  ? minimalWorkflowTemplate(scheduleBlock, packageManager)
18
- : strictWorkflowTemplate(scheduleBlock, packageManager);
18
+ : options.mode === "strict"
19
+ ? strictWorkflowTemplate(scheduleBlock, packageManager)
20
+ : enterpriseWorkflowTemplate(scheduleBlock, packageManager);
19
21
  await mkdir(path.dirname(workflowPath), { recursive: true });
20
22
  await writeFile(workflowPath, workflow, "utf8");
21
23
  return { path: workflowPath, created: true };
@@ -49,3 +51,9 @@ function minimalWorkflowTemplate(scheduleBlock, packageManager) {
49
51
  function strictWorkflowTemplate(scheduleBlock, packageManager) {
50
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 check \\\n --workspace \\\n --offline \\\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`;
51
53
  }
54
+ function enterpriseWorkflowTemplate(scheduleBlock, packageManager) {
55
+ const detectedPmInstall = packageManager === "pnpm"
56
+ ? "corepack enable && corepack prepare pnpm@9 --activate && pnpm install --frozen-lockfile"
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 check \\\n --workspace \\\n --offline \\\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`;
59
+ }
@@ -1,4 +1,4 @@
1
- import type { CheckOptions, UpgradeOptions } from "../types/index.js";
1
+ import type { BaselineOptions, CheckOptions, UpgradeOptions } from "../types/index.js";
2
2
  import type { InitCiMode, InitCiSchedule } from "./init-ci.js";
3
3
  export type ParsedCliArgs = {
4
4
  command: "check";
@@ -17,5 +17,10 @@ export type ParsedCliArgs = {
17
17
  mode: InitCiMode;
18
18
  schedule: InitCiSchedule;
19
19
  };
20
+ } | {
21
+ command: "baseline";
22
+ options: BaselineOptions & {
23
+ action: "save" | "check";
24
+ };
20
25
  };
21
26
  export declare function parseCliArgs(argv: string[]): Promise<ParsedCliArgs>;
@@ -7,7 +7,7 @@ const DEFAULT_INCLUDE_KINDS = [
7
7
  "optionalDependencies",
8
8
  "peerDependencies",
9
9
  ];
10
- const KNOWN_COMMANDS = ["check", "upgrade", "warm-cache", "init-ci"];
10
+ const KNOWN_COMMANDS = ["check", "upgrade", "warm-cache", "init-ci", "baseline"];
11
11
  export async function parseCliArgs(argv) {
12
12
  const firstArg = argv[0];
13
13
  const isKnownCommand = KNOWN_COMMANDS.includes(firstArg);
@@ -34,10 +34,24 @@ export async function parseCliArgs(argv) {
34
34
  offline: false,
35
35
  policyFile: undefined,
36
36
  prReportFile: undefined,
37
+ failOn: "none",
38
+ maxUpdates: undefined,
39
+ fixPr: false,
40
+ fixBranch: "chore/rainy-updates",
41
+ fixCommitMessage: undefined,
42
+ fixDryRun: false,
43
+ noPrReport: false,
37
44
  };
38
45
  let force = false;
39
- let initCiMode = "strict";
46
+ let initCiMode = "enterprise";
40
47
  let initCiSchedule = "weekly";
48
+ let baselineAction = "check";
49
+ let baselineFilePath = path.resolve(base.cwd, ".rainy-updates-baseline.json");
50
+ let jsonFileRaw;
51
+ let githubOutputRaw;
52
+ let sarifFileRaw;
53
+ let policyFileRaw;
54
+ let prReportFileRaw;
41
55
  let resolvedConfig = await loadConfig(base.cwd);
42
56
  applyConfig(base, resolvedConfig);
43
57
  for (let index = 0; index < args.length; index += 1) {
@@ -48,23 +62,36 @@ export async function parseCliArgs(argv) {
48
62
  index += 1;
49
63
  continue;
50
64
  }
65
+ if (current === "--target") {
66
+ throw new Error("Missing value for --target");
67
+ }
51
68
  if (current === "--filter" && next) {
52
69
  base.filter = next;
53
70
  index += 1;
54
71
  continue;
55
72
  }
73
+ if (current === "--filter") {
74
+ throw new Error("Missing value for --filter");
75
+ }
56
76
  if (current === "--reject" && next) {
57
77
  base.reject = next;
58
78
  index += 1;
59
79
  continue;
60
80
  }
81
+ if (current === "--reject") {
82
+ throw new Error("Missing value for --reject");
83
+ }
61
84
  if (current === "--cwd" && next) {
62
85
  base.cwd = path.resolve(next);
63
86
  resolvedConfig = await loadConfig(base.cwd);
64
87
  applyConfig(base, resolvedConfig);
88
+ baselineFilePath = path.resolve(base.cwd, ".rainy-updates-baseline.json");
65
89
  index += 1;
66
90
  continue;
67
91
  }
92
+ if (current === "--cwd") {
93
+ throw new Error("Missing value for --cwd");
94
+ }
68
95
  if (current === "--cache-ttl" && next) {
69
96
  const parsed = Number(next);
70
97
  if (!Number.isFinite(parsed) || parsed < 0) {
@@ -74,11 +101,17 @@ export async function parseCliArgs(argv) {
74
101
  index += 1;
75
102
  continue;
76
103
  }
104
+ if (current === "--cache-ttl") {
105
+ throw new Error("Missing value for --cache-ttl");
106
+ }
77
107
  if (current === "--format" && next) {
78
108
  base.format = ensureFormat(next);
79
109
  index += 1;
80
110
  continue;
81
111
  }
112
+ if (current === "--format") {
113
+ throw new Error("Missing value for --format");
114
+ }
82
115
  if (current === "--ci") {
83
116
  base.ci = true;
84
117
  continue;
@@ -88,20 +121,29 @@ export async function parseCliArgs(argv) {
88
121
  continue;
89
122
  }
90
123
  if (current === "--json-file" && next) {
91
- base.jsonFile = path.resolve(next);
124
+ jsonFileRaw = next;
92
125
  index += 1;
93
126
  continue;
94
127
  }
128
+ if (current === "--json-file") {
129
+ throw new Error("Missing value for --json-file");
130
+ }
95
131
  if (current === "--github-output" && next) {
96
- base.githubOutputFile = path.resolve(next);
132
+ githubOutputRaw = next;
97
133
  index += 1;
98
134
  continue;
99
135
  }
136
+ if (current === "--github-output") {
137
+ throw new Error("Missing value for --github-output");
138
+ }
100
139
  if (current === "--sarif-file" && next) {
101
- base.sarifFile = path.resolve(next);
140
+ sarifFileRaw = next;
102
141
  index += 1;
103
142
  continue;
104
143
  }
144
+ if (current === "--sarif-file") {
145
+ throw new Error("Missing value for --sarif-file");
146
+ }
105
147
  if (current === "--concurrency" && next) {
106
148
  const parsed = Number(next);
107
149
  if (!Number.isInteger(parsed) || parsed <= 0) {
@@ -111,39 +153,160 @@ export async function parseCliArgs(argv) {
111
153
  index += 1;
112
154
  continue;
113
155
  }
156
+ if (current === "--concurrency") {
157
+ throw new Error("Missing value for --concurrency");
158
+ }
114
159
  if (current === "--offline") {
115
160
  base.offline = true;
116
161
  continue;
117
162
  }
118
163
  if (current === "--policy-file" && next) {
119
- base.policyFile = path.resolve(next);
164
+ policyFileRaw = next;
120
165
  index += 1;
121
166
  continue;
122
167
  }
168
+ if (current === "--policy-file") {
169
+ throw new Error("Missing value for --policy-file");
170
+ }
123
171
  if (current === "--pr-report-file" && next) {
124
- base.prReportFile = path.resolve(next);
172
+ prReportFileRaw = next;
125
173
  index += 1;
126
174
  continue;
127
175
  }
176
+ if (current === "--pr-report-file") {
177
+ throw new Error("Missing value for --pr-report-file");
178
+ }
128
179
  if (current === "--force") {
129
180
  force = true;
130
181
  continue;
131
182
  }
183
+ if (current === "--fix-pr") {
184
+ base.fixPr = true;
185
+ continue;
186
+ }
187
+ if (current === "--fix-branch" && next) {
188
+ base.fixBranch = next;
189
+ index += 1;
190
+ continue;
191
+ }
192
+ if (current === "--fix-branch") {
193
+ throw new Error("Missing value for --fix-branch");
194
+ }
195
+ if (current === "--fix-commit-message" && next) {
196
+ base.fixCommitMessage = next;
197
+ index += 1;
198
+ continue;
199
+ }
200
+ if (current === "--fix-commit-message") {
201
+ throw new Error("Missing value for --fix-commit-message");
202
+ }
203
+ if (current === "--fix-dry-run") {
204
+ base.fixDryRun = true;
205
+ continue;
206
+ }
207
+ if (current === "--no-pr-report") {
208
+ base.noPrReport = true;
209
+ continue;
210
+ }
211
+ if (current === "--install" && command === "upgrade") {
212
+ continue;
213
+ }
214
+ if (current === "--sync" && command === "upgrade") {
215
+ continue;
216
+ }
217
+ if (current === "--pm" && next && command === "upgrade") {
218
+ parsePackageManager(args);
219
+ index += 1;
220
+ continue;
221
+ }
222
+ if (current === "--pm" && command === "upgrade") {
223
+ throw new Error("Missing value for --pm");
224
+ }
132
225
  if (current === "--mode" && next) {
133
226
  initCiMode = ensureInitCiMode(next);
134
227
  index += 1;
135
228
  continue;
136
229
  }
230
+ if (current === "--mode") {
231
+ throw new Error("Missing value for --mode");
232
+ }
137
233
  if (current === "--schedule" && next) {
138
234
  initCiSchedule = ensureInitCiSchedule(next);
139
235
  index += 1;
140
236
  continue;
141
237
  }
238
+ if (current === "--schedule") {
239
+ throw new Error("Missing value for --schedule");
240
+ }
142
241
  if (current === "--dep-kinds" && next) {
143
242
  base.includeKinds = parseDependencyKinds(next);
144
243
  index += 1;
145
244
  continue;
146
245
  }
246
+ if (current === "--dep-kinds") {
247
+ throw new Error("Missing value for --dep-kinds");
248
+ }
249
+ if (current === "--fail-on" && next) {
250
+ base.failOn = ensureFailOn(next);
251
+ index += 1;
252
+ continue;
253
+ }
254
+ if (current === "--fail-on") {
255
+ throw new Error("Missing value for --fail-on");
256
+ }
257
+ if (current === "--max-updates" && next) {
258
+ const parsed = Number(next);
259
+ if (!Number.isInteger(parsed) || parsed < 0) {
260
+ throw new Error("--max-updates must be a non-negative integer");
261
+ }
262
+ base.maxUpdates = parsed;
263
+ index += 1;
264
+ continue;
265
+ }
266
+ if (current === "--max-updates") {
267
+ throw new Error("Missing value for --max-updates");
268
+ }
269
+ if (current === "--save") {
270
+ baselineAction = "save";
271
+ continue;
272
+ }
273
+ if (current === "--check") {
274
+ baselineAction = "check";
275
+ continue;
276
+ }
277
+ if (current === "--file" && next) {
278
+ baselineFilePath = path.resolve(base.cwd, next);
279
+ index += 1;
280
+ continue;
281
+ }
282
+ if (current === "--file") {
283
+ throw new Error("Missing value for --file");
284
+ }
285
+ if (current.startsWith("-")) {
286
+ throw new Error(`Unknown option: ${current}`);
287
+ }
288
+ throw new Error(`Unexpected argument: ${current}`);
289
+ }
290
+ if (jsonFileRaw) {
291
+ base.jsonFile = path.resolve(base.cwd, jsonFileRaw);
292
+ }
293
+ if (githubOutputRaw) {
294
+ base.githubOutputFile = path.resolve(base.cwd, githubOutputRaw);
295
+ }
296
+ if (sarifFileRaw) {
297
+ base.sarifFile = path.resolve(base.cwd, sarifFileRaw);
298
+ }
299
+ if (policyFileRaw) {
300
+ base.policyFile = path.resolve(base.cwd, policyFileRaw);
301
+ }
302
+ if (prReportFileRaw) {
303
+ base.prReportFile = path.resolve(base.cwd, prReportFileRaw);
304
+ }
305
+ if (base.noPrReport) {
306
+ base.prReportFile = undefined;
307
+ }
308
+ else if (base.fixPr && !base.prReportFile) {
309
+ base.prReportFile = path.resolve(base.cwd, ".artifacts/deps-report.md");
147
310
  }
148
311
  if (command === "upgrade") {
149
312
  const configPm = resolvedConfig.packageManager;
@@ -170,6 +333,19 @@ export async function parseCliArgs(argv) {
170
333
  },
171
334
  };
172
335
  }
336
+ if (command === "baseline") {
337
+ return {
338
+ command,
339
+ options: {
340
+ action: baselineAction,
341
+ cwd: base.cwd,
342
+ workspace: base.workspace,
343
+ includeKinds: base.includeKinds,
344
+ filePath: baselineFilePath,
345
+ ci: base.ci,
346
+ },
347
+ };
348
+ }
173
349
  return {
174
350
  command: "check",
175
351
  options: base,
@@ -212,6 +388,27 @@ function applyConfig(base, config) {
212
388
  if (typeof config.prReportFile === "string") {
213
389
  base.prReportFile = path.resolve(base.cwd, config.prReportFile);
214
390
  }
391
+ if (typeof config.failOn === "string") {
392
+ base.failOn = ensureFailOn(config.failOn);
393
+ }
394
+ if (typeof config.maxUpdates === "number" && Number.isInteger(config.maxUpdates) && config.maxUpdates >= 0) {
395
+ base.maxUpdates = config.maxUpdates;
396
+ }
397
+ if (typeof config.fixPr === "boolean") {
398
+ base.fixPr = config.fixPr;
399
+ }
400
+ if (typeof config.fixBranch === "string" && config.fixBranch.length > 0) {
401
+ base.fixBranch = config.fixBranch;
402
+ }
403
+ if (typeof config.fixCommitMessage === "string" && config.fixCommitMessage.length > 0) {
404
+ base.fixCommitMessage = config.fixCommitMessage;
405
+ }
406
+ if (typeof config.fixDryRun === "boolean") {
407
+ base.fixDryRun = config.fixDryRun;
408
+ }
409
+ if (typeof config.noPrReport === "boolean") {
410
+ base.noPrReport = config.noPrReport;
411
+ }
215
412
  }
216
413
  function parsePackageManager(args) {
217
414
  const index = args.indexOf("--pm");
@@ -257,10 +454,10 @@ function parseDependencyKinds(value) {
257
454
  return Array.from(new Set(mapped));
258
455
  }
259
456
  function ensureInitCiMode(value) {
260
- if (value === "minimal" || value === "strict") {
457
+ if (value === "minimal" || value === "strict" || value === "enterprise") {
261
458
  return value;
262
459
  }
263
- throw new Error("--mode must be minimal or strict");
460
+ throw new Error("--mode must be minimal, strict or enterprise");
264
461
  }
265
462
  function ensureInitCiSchedule(value) {
266
463
  if (value === "weekly" || value === "daily" || value === "off") {
@@ -268,3 +465,9 @@ function ensureInitCiSchedule(value) {
268
465
  }
269
466
  throw new Error("--schedule must be weekly, daily or off");
270
467
  }
468
+ function ensureFailOn(value) {
469
+ if (value === "none" || value === "patch" || value === "minor" || value === "major" || value === "any") {
470
+ return value;
471
+ }
472
+ throw new Error("--fail-on must be none, patch, minor, major or any");
473
+ }
@@ -8,7 +8,7 @@ export async function warmCache(options) {
8
8
  const packageManager = await detectPackageManager(options.cwd);
9
9
  const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
10
10
  const cache = await VersionCache.create();
11
- const registryClient = new NpmRegistryClient();
11
+ const registryClient = new NpmRegistryClient(options.cwd);
12
12
  const errors = [];
13
13
  const warnings = [];
14
14
  let totalDependencies = 0;
@@ -52,12 +52,12 @@ export async function warmCache(options) {
52
52
  }
53
53
  }
54
54
  else {
55
- const fetched = await registryClient.resolveManyLatestVersions(needsFetch, {
55
+ const fetched = await registryClient.resolveManyPackageMetadata(needsFetch, {
56
56
  concurrency: options.concurrency,
57
57
  });
58
- for (const [pkg, version] of fetched.versions) {
59
- if (version) {
60
- await cache.set(pkg, options.target, version, options.cacheTtlSeconds);
58
+ for (const [pkg, metadata] of fetched.metadata) {
59
+ if (metadata.latestVersion) {
60
+ await cache.set(pkg, options.target, metadata.latestVersion, metadata.versions, options.cacheTtlSeconds);
61
61
  warmed += 1;
62
62
  }
63
63
  }
package/dist/index.d.ts CHANGED
@@ -2,7 +2,8 @@ export { check } from "./core/check.js";
2
2
  export { upgrade } from "./core/upgrade.js";
3
3
  export { warmCache } from "./core/warm-cache.js";
4
4
  export { initCiWorkflow } from "./core/init-ci.js";
5
+ export { saveBaseline, diffBaseline } from "./core/baseline.js";
5
6
  export { createSarifReport } from "./output/sarif.js";
6
7
  export { writeGitHubOutput, renderGitHubAnnotations } from "./output/github.js";
7
8
  export { renderPrReport } from "./output/pr-report.js";
8
- export type { CheckOptions, CheckResult, DependencyKind, OutputFormat, PackageUpdate, RunOptions, TargetLevel, UpgradeOptions, UpgradeResult, } from "./types/index.js";
9
+ export type { CheckOptions, CheckResult, DependencyKind, FailOnLevel, OutputFormat, PackageUpdate, RunOptions, TargetLevel, UpgradeOptions, UpgradeResult, } from "./types/index.js";
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export { check } from "./core/check.js";
2
2
  export { upgrade } from "./core/upgrade.js";
3
3
  export { warmCache } from "./core/warm-cache.js";
4
4
  export { initCiWorkflow } from "./core/init-ci.js";
5
+ export { saveBaseline, diffBaseline } from "./core/baseline.js";
5
6
  export { createSarifReport } from "./output/sarif.js";
6
7
  export { writeGitHubOutput, renderGitHubAnnotations } from "./output/github.js";
7
8
  export { renderPrReport } from "./output/pr-report.js";
@@ -52,5 +52,8 @@ export function renderResult(result, format) {
52
52
  }
53
53
  lines.push("");
54
54
  lines.push(`Summary: ${result.summary.updatesFound} updates, ${result.summary.checkedDependencies}/${result.summary.totalDependencies} checked, ${result.summary.warmedPackages} warmed`);
55
+ if (result.summary.fixPrApplied) {
56
+ lines.push(`Fix PR: applied on branch ${result.summary.fixBranchName ?? "unknown"} (${result.summary.fixCommitSha ?? "no-commit"})`);
57
+ }
55
58
  return lines.join("\n");
56
59
  }
@@ -8,6 +8,9 @@ export async function writeGitHubOutput(filePath, result) {
8
8
  `checked_dependencies=${result.summary.checkedDependencies}`,
9
9
  `scanned_packages=${result.summary.scannedPackages}`,
10
10
  `warmed_packages=${result.summary.warmedPackages}`,
11
+ `fix_pr_applied=${result.summary.fixPrApplied === true ? "1" : "0"}`,
12
+ `fix_pr_branch=${result.summary.fixBranchName ?? ""}`,
13
+ `fix_pr_commit=${result.summary.fixCommitSha ?? ""}`,
11
14
  ];
12
15
  await fs.mkdir(path.dirname(filePath), { recursive: true });
13
16
  await fs.writeFile(filePath, lines.join("\n") + "\n", "utf8");