@rainy-updates/cli 0.4.0 → 0.5.0-rc.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 CHANGED
@@ -2,6 +2,58 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.5.0-rc.1] - 2026-02-27
6
+
7
+ ### Added
8
+
9
+ - New CI rollout controls:
10
+ - `--fail-on none|patch|minor|major|any`
11
+ - `--max-updates <n>`
12
+ - New baseline workflow command:
13
+ - `baseline --save --file <path>` to snapshot dependency state
14
+ - `baseline --check --file <path>` to detect dependency drift
15
+ - New `init-ci --mode enterprise` template:
16
+ - Node runtime matrix (`20`, `22`)
17
+ - stricter default permissions
18
+ - artifact retention policy
19
+ - built-in rollout gate flags (`--fail-on`, `--max-updates`)
20
+
21
+ ### Changed
22
+
23
+ - Dependency target selection now evaluates available package versions from registry metadata, improving `patch|minor|major` accuracy.
24
+ - CLI parser now rejects unknown options and missing option values with explicit errors (safer CI behavior).
25
+ - SARIF output now reports the actual package version dynamically.
26
+
27
+ ### Tests
28
+
29
+ - Added baseline snapshot/diff tests.
30
+ - Added enterprise workflow generation tests.
31
+ - Added semver target selection tests using available version sets.
32
+ - Added parser tests for baseline command, rollout flags, and unknown option rejection.
33
+
34
+ ## [0.4.4] - 2026-02-27
35
+
36
+ ### Changed
37
+
38
+ - Version bump to `0.4.4` for production stabilization.
39
+ - Simplified public documentation to focus on end-user CLI usage.
40
+ - Removed user-facing instructions for GitHub Actions configuration from README.
41
+
42
+ ### Fixed
43
+
44
+ - Removed optional `better-sqlite3` dependency to avoid deprecated native install warnings (`prebuild-install`).
45
+ - Cache backend now uses `bun:sqlite` when available and falls back cleanly to file-based cache without native Node addons.
46
+
47
+ ### Added
48
+
49
+ - `SECURITY.md` with vulnerability disclosure guidance.
50
+ - `CODE_OF_CONDUCT.md` for OSS community standards.
51
+ - Automatic CI bootstrap improvements in `init-ci`:
52
+ - `--mode minimal|strict`
53
+ - `--schedule weekly|daily|off`
54
+ - package-manager-aware install step generation (npm/pnpm)
55
+
56
+
5
57
  ## [0.4.0] - 2026-02-27
6
58
 
7
59
  ### Added
@@ -126,7 +178,7 @@ All notable changes to this project are documented in this file.
126
178
  - `--dep-kinds deps,dev,optional,peer`
127
179
  - Runtime controls:
128
180
  - `--concurrency` for parallel dependency checks.
129
- - `--cache-ttl` for cache freshness tuning.
181
+ - `--cache-ttl` for cache freshness tuning.
130
182
  - Cache layer improvements:
131
183
  - SQLite-first cache backend when available.
132
184
  - JSON fallback cache backend.
@@ -0,0 +1,25 @@
1
+ # Code of Conduct
2
+
3
+ ## Our Standards
4
+
5
+ We are committed to a respectful, inclusive, and harassment-free community.
6
+
7
+ Expected behavior:
8
+
9
+ - be respectful and constructive
10
+ - focus on technical issues, not personal attacks
11
+ - welcome feedback and different viewpoints
12
+
13
+ Unacceptable behavior:
14
+
15
+ - harassment, discrimination, or abusive language
16
+ - doxxing or threats
17
+ - trolling and persistent disruption
18
+
19
+ ## Enforcement
20
+
21
+ Project maintainers are responsible for clarifying and enforcing this code of conduct.
22
+
23
+ ## Reporting
24
+
25
+ Report unacceptable behavior through private communication with maintainers or GitHub moderation tools.
package/README.md CHANGED
@@ -1,6 +1,16 @@
1
1
  # @rainy-updates/cli
2
2
 
3
- Agentic OSS CLI for dependency updates focused on speed, CI automation, and workspace-scale maintenance.
3
+ Agentic CLI to detect, control, and apply dependency updates across npm/pnpm projects and monorepos.
4
+
5
+ `@rainy-updates/cli` is built for teams that need fast dependency intelligence, policy-aware upgrades, and automation-ready output for CI/CD and pull request workflows.
6
+
7
+ ## Why this package
8
+
9
+ - Detects updates quickly across single-package repos and workspaces.
10
+ - Applies updates safely with configurable targets (`patch`, `minor`, `major`, `latest`).
11
+ - Enforces policy rules per package (ignore rules and max upgrade level).
12
+ - Supports offline and cache-warmed execution for deterministic CI runs.
13
+ - Produces machine-readable artifacts (JSON, SARIF, GitHub outputs, PR markdown report).
4
14
 
5
15
  ## Install
6
16
 
@@ -10,64 +20,58 @@ npm i -D @rainy-updates/cli
10
20
  pnpm add -D @rainy-updates/cli
11
21
  ```
12
22
 
13
- ## Commands
23
+ ## Core commands
14
24
 
15
- - `check`: detect available dependency updates.
16
- - `upgrade`: rewrite dependency ranges (optional install + workspace sync).
17
- - `warm-cache`: pre-fetch package metadata into local cache for faster CI checks.
18
- - `init-ci`: scaffold `.github/workflows/rainy-updates.yml`.
25
+ - `check`: analyze dependencies and report available updates.
26
+ - `upgrade`: rewrite dependency ranges in manifests, optionally install lockfile updates.
27
+ - `warm-cache`: prefetch package metadata for fast and offline checks.
28
+ - `baseline`: save and compare dependency baseline snapshots.
19
29
 
20
- ## Quick start
30
+ ## Quick usage
21
31
 
22
32
  ```bash
23
- # check and fail CI if updates are found
24
- npx @rainy-updates/cli check --ci --format json --json-file .artifacts/deps-report.json
33
+ # 1) Detect updates
34
+ npx @rainy-updates/cli check --format table
25
35
 
26
- # pre-warm cache before strict offline checks
27
- npx @rainy-updates/cli warm-cache --workspace --concurrency 32
28
- npx @rainy-updates/cli check --workspace --offline --ci
36
+ # 2) Strict CI mode (non-zero when updates exist)
37
+ npx @rainy-updates/cli check --workspace --ci --format json --json-file .artifacts/updates.json
29
38
 
30
- # upgrade ranges and install lockfiles
39
+ # 3) Apply upgrades with workspace sync
31
40
  npx @rainy-updates/cli upgrade --target latest --workspace --sync --install
32
41
 
33
- # scaffold GitHub Actions workflow
34
- npx @rainy-updates/cli init-ci
42
+ # 4) Warm cache for deterministic offline checks
43
+ npx @rainy-updates/cli warm-cache --workspace --concurrency 32
44
+ npx @rainy-updates/cli check --workspace --offline --ci
45
+
46
+ # 5) Save and compare baseline drift in CI
47
+ npx @rainy-updates/cli baseline --save --file .artifacts/deps-baseline.json --workspace
48
+ npx @rainy-updates/cli baseline --check --file .artifacts/deps-baseline.json --workspace --ci
35
49
  ```
36
50
 
37
- ## Core options
51
+ ## What it does in production
38
52
 
39
- - `--target patch|minor|major|latest`
40
- - `--filter <pattern>`
41
- - `--reject <pattern>`
42
- - `--dep-kinds deps,dev,optional,peer`
43
- - `--workspace`
44
- - `--concurrency <n>`
45
- - `--cache-ttl <seconds>`
46
- - `--offline` (cache-only mode)
47
- - `--cwd <path>`
53
+ ### Update detection engine
48
54
 
49
- ## Output options
55
+ - Scans dependency groups: `dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`.
56
+ - Resolves versions per unique package to reduce duplicate network requests.
57
+ - Uses network concurrency controls and resilient retries.
58
+ - Supports stale-cache fallback when registry calls fail.
50
59
 
51
- - `--format table|json|minimal|github`
52
- - `--json-file <path>`
53
- - `--github-output <path>`
54
- - `--sarif-file <path>`
55
- - `--pr-report-file <path>` (generates markdown report for PR comments)
60
+ ### Workspace support
56
61
 
57
- ## Upgrade options
62
+ - Detects package workspaces from:
63
+ - `package.json` workspaces
64
+ - `pnpm-workspace.yaml`
65
+ - Handles multi-manifest upgrade flows.
66
+ - Graph-aware sync mode (`--sync`) avoids breaking `workspace:*` references.
58
67
 
59
- - `--install`
60
- - `--pm auto|npm|pnpm`
61
- - `--sync` (graph-aware version alignment across workspace packages)
62
-
63
- ## Policy controls
68
+ ### Policy-aware control
64
69
 
65
- - `--policy-file <path>` to apply package-level policy rules.
66
- - default policy discovery:
67
- - `.rainyupdates-policy.json`
68
- - `rainy-updates.policy.json`
70
+ - Apply global ignore patterns.
71
+ - Apply package-specific rules.
72
+ - Enforce max upgrade target per package (for safer rollout).
69
73
 
70
- Policy example:
74
+ Example policy file:
71
75
 
72
76
  ```json
73
77
  {
@@ -79,63 +83,127 @@ Policy example:
79
83
  }
80
84
  ```
81
85
 
82
- ## CLI help
86
+ Use it with:
83
87
 
84
88
  ```bash
85
- rainy-updates --help
86
- rainy-updates <command> --help
87
- rainy-updates --version
89
+ npx @rainy-updates/cli check --policy-file .rainyupdates-policy.json
88
90
  ```
89
91
 
90
- ## CI behavior
92
+ ## Output and reporting
91
93
 
92
- - `--ci`: returns exit code `1` when updates are found.
93
- - returns exit code `2` for operational errors (registry/IO/runtime failures).
94
+ ### Human output
94
95
 
95
- ## Config files
96
+ - `--format table`
97
+ - `--format minimal`
96
98
 
97
- Supported:
99
+ ### Automation output
98
100
 
99
- - `.rainyupdatesrc`
100
- - `.rainyupdatesrc.json`
101
- - `package.json` -> `rainyUpdates`
101
+ - `--format json`
102
+ - `--json-file <path>`
103
+ - `--sarif-file <path>`
104
+ - `--github-output <path>`
105
+ - `--pr-report-file <path>`
102
106
 
103
- Example:
107
+ These outputs are designed for CI pipelines, security tooling, and PR review automation.
104
108
 
105
- ```json
106
- {
107
- "rainyUpdates": {
108
- "target": "minor",
109
- "workspace": true,
110
- "concurrency": 24,
111
- "offline": false,
112
- "format": "json",
113
- "cacheTtlSeconds": 1800,
114
- "jsonFile": ".artifacts/deps.json",
115
- "sarifFile": ".artifacts/deps.sarif",
116
- "prReportFile": ".artifacts/deps.md",
117
- "policyFile": ".rainyupdates-policy.json"
118
- }
119
- }
109
+
110
+ ## Automatic CI bootstrap
111
+
112
+ Generate a workflow in the target project automatically:
113
+
114
+ ```bash
115
+ # strict mode (recommended)
116
+ npx @rainy-updates/cli init-ci --mode enterprise --schedule weekly
117
+
118
+ # lightweight mode
119
+ npx @rainy-updates/cli init-ci --mode minimal --schedule daily
120
120
  ```
121
121
 
122
- ## Production release
122
+ Generated file:
123
+
124
+ - `.github/workflows/rainy-updates.yml`
125
+
126
+ Modes:
127
+
128
+ - `strict`: warm-cache + offline check + artifacts + SARIF upload.
129
+ - `enterprise`: strict checks + runtime matrix + retention policy + rollout gates.
130
+ - `minimal`: fast check-only workflow for quick adoption.
131
+
132
+ Schedule:
133
+
134
+ - `weekly`, `daily`, or `off` (manual dispatch only).
135
+
136
+ ## Command options
137
+
138
+ ### Global
139
+
140
+ - `--cwd <path>`
141
+ - `--workspace`
142
+ - `--target patch|minor|major|latest`
143
+ - `--filter <pattern>`
144
+ - `--reject <pattern>`
145
+ - `--dep-kinds deps,dev,optional,peer`
146
+ - `--concurrency <n>`
147
+ - `--cache-ttl <seconds>`
148
+ - `--offline`
149
+ - `--fail-on none|patch|minor|major|any`
150
+ - `--max-updates <n>`
151
+ - `--policy-file <path>`
152
+ - `--format table|json|minimal|github`
153
+ - `--json-file <path>`
154
+ - `--github-output <path>`
155
+ - `--sarif-file <path>`
156
+ - `--pr-report-file <path>`
157
+ - `--ci`
158
+
159
+ ### Upgrade-only
160
+
161
+ - `--install`
162
+ - `--pm auto|npm|pnpm`
163
+ - `--sync`
164
+
165
+ ### Baseline-only
166
+
167
+ - `--save`
168
+ - `--check`
169
+ - `--file <path>`
170
+
171
+ ## Config support
172
+
173
+ Configuration can be loaded from:
174
+
175
+ - `.rainyupdatesrc`
176
+ - `.rainyupdatesrc.json`
177
+ - `package.json` field: `rainyUpdates`
178
+
179
+ ## CLI help
123
180
 
124
181
  ```bash
125
- bun run prepublishOnly
126
- node scripts/release-preflight.mjs
127
- npm publish --provenance --access public
182
+ rainy-updates --help
183
+ rainy-updates <command> --help
184
+ rainy-updates --version
128
185
  ```
129
186
 
130
- If publishing in GitHub Actions, set `NPM_TOKEN` in repository secrets.
187
+ ## Reliability characteristics
188
+
189
+ - Node.js 20+ runtime.
190
+ - Works with npm and pnpm workflows.
191
+ - Uses optional `undici` pool path for high-throughput HTTP.
192
+ - Cache-first architecture for speed and resilience.
193
+
194
+ ## CI/CD included
195
+
196
+ This package ships with production CI/CD pipelines in the repository:
197
+
198
+ - Continuous integration pipeline for typecheck, tests, build, and production smoke checks.
199
+ - Tag-driven release pipeline for npm publishing with provenance.
200
+ - Release preflight validation for npm auth/scope checks before publishing.
201
+
131
202
 
132
- The repository includes:
203
+ ## Product roadmap
133
204
 
134
- - `.github/workflows/ci.yml` for test/typecheck/build/smoke checks.
135
- - `.github/workflows/release.yml` for tag-driven npm publishing.
205
+ The long-term roadmap is maintained in [`ROADMAP.md`](./ROADMAP.md).
136
206
 
137
- ## Performance and runtime notes
207
+ ## License
138
208
 
139
- - Resolves dependency metadata by unique package name to avoid duplicate network calls.
140
- - Uses `undici` pool with HTTP/2 when available; falls back to native `fetch` automatically.
141
- - Uses layered cache with stale fallback for resilient CI runs.
209
+ MIT
package/SECURITY.md ADDED
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are applied to the latest released version.
6
+
7
+ ## Reporting vulnerabilities
8
+
9
+ Report vulnerabilities privately through GitHub Security Advisories.
10
+
11
+ Include:
12
+
13
+ - affected version
14
+ - reproduction steps
15
+ - impact assessment
16
+ - proof-of-concept if available
17
+
18
+ Do not open public issues for unpatched security vulnerabilities.
package/dist/bin/cli.js CHANGED
@@ -8,6 +8,7 @@ import { check } from "../core/check.js";
8
8
  import { upgrade } from "../core/upgrade.js";
9
9
  import { warmCache } from "../core/warm-cache.js";
10
10
  import { initCiWorkflow } from "../core/init-ci.js";
11
+ import { diffBaseline, saveBaseline } from "../core/baseline.js";
11
12
  import { renderResult } from "../output/format.js";
12
13
  import { writeGitHubOutput } from "../output/github.js";
13
14
  import { createSarifReport } from "../output/sarif.js";
@@ -25,12 +26,40 @@ async function main() {
25
26
  }
26
27
  const parsed = await parseCliArgs(argv);
27
28
  if (parsed.command === "init-ci") {
28
- const workflow = await initCiWorkflow(parsed.options.cwd, parsed.options.force);
29
+ const workflow = await initCiWorkflow(parsed.options.cwd, parsed.options.force, {
30
+ mode: parsed.options.mode,
31
+ schedule: parsed.options.schedule,
32
+ });
29
33
  process.stdout.write(workflow.created
30
34
  ? `Created CI workflow at ${workflow.path}\n`
31
35
  : `CI workflow already exists at ${workflow.path}. Use --force to overwrite.\n`);
32
36
  return;
33
37
  }
38
+ if (parsed.command === "baseline") {
39
+ if (parsed.options.action === "save") {
40
+ const saved = await saveBaseline(parsed.options);
41
+ process.stdout.write(`Saved baseline at ${saved.filePath} (${saved.entries} entries)\n`);
42
+ return;
43
+ }
44
+ const diff = await diffBaseline(parsed.options);
45
+ const changes = diff.added.length + diff.removed.length + diff.changed.length;
46
+ if (changes === 0) {
47
+ process.stdout.write(`No baseline drift detected (${diff.filePath}).\n`);
48
+ return;
49
+ }
50
+ process.stdout.write(`Baseline drift detected (${diff.filePath}).\n`);
51
+ if (diff.added.length > 0) {
52
+ process.stdout.write(`Added: ${diff.added.length}\n`);
53
+ }
54
+ if (diff.removed.length > 0) {
55
+ process.stdout.write(`Removed: ${diff.removed.length}\n`);
56
+ }
57
+ if (diff.changed.length > 0) {
58
+ process.stdout.write(`Changed: ${diff.changed.length}\n`);
59
+ }
60
+ process.exitCode = 1;
61
+ return;
62
+ }
34
63
  const result = parsed.command === "upgrade"
35
64
  ? await upgrade(parsed.options)
36
65
  : parsed.command === "warm-cache"
@@ -55,13 +84,7 @@ async function main() {
55
84
  await fs.mkdir(path.dirname(parsed.options.sarifFile), { recursive: true });
56
85
  await fs.writeFile(parsed.options.sarifFile, JSON.stringify(sarif, null, 2) + "\n", "utf8");
57
86
  }
58
- if (parsed.options.ci && result.updates.length > 0) {
59
- process.exitCode = 1;
60
- return;
61
- }
62
- if (result.errors.length > 0) {
63
- process.exitCode = 2;
64
- }
87
+ process.exitCode = resolveExitCode(result, parsed.options.failOn, parsed.options.maxUpdates, parsed.options.ci);
65
88
  }
66
89
  catch (error) {
67
90
  process.stderr.write(`rainy-updates: ${String(error)}\n`);
@@ -107,10 +130,28 @@ Options:
107
130
  --pr-report-file <path>`;
108
131
  }
109
132
  if (isCommand && command === "init-ci") {
110
- return `rainy-updates init-ci [--force]
133
+ return `rainy-updates init-ci [options]
111
134
 
112
135
  Create a GitHub Actions workflow template at:
113
- .github/workflows/rainy-updates.yml`;
136
+ .github/workflows/rainy-updates.yml
137
+
138
+ Options:
139
+ --force
140
+ --mode minimal|strict|enterprise
141
+ --schedule weekly|daily|off`;
142
+ }
143
+ if (isCommand && command === "baseline") {
144
+ return `rainy-updates baseline [options]
145
+
146
+ Save or compare dependency baseline snapshots.
147
+
148
+ Options:
149
+ --save
150
+ --check
151
+ --file <path>
152
+ --workspace
153
+ --dep-kinds deps,dev,optional,peer
154
+ --ci`;
114
155
  }
115
156
  return `rainy-updates <command> [options]
116
157
 
@@ -119,6 +160,7 @@ Commands:
119
160
  upgrade Apply updates to manifests
120
161
  warm-cache Warm local cache for fast/offline checks
121
162
  init-ci Scaffold GitHub Actions workflow
163
+ baseline Save/check dependency baseline snapshots
122
164
 
123
165
  Global options:
124
166
  --cwd <path>
@@ -130,6 +172,8 @@ Global options:
130
172
  --sarif-file <path>
131
173
  --pr-report-file <path>
132
174
  --policy-file <path>
175
+ --fail-on none|patch|minor|major|any
176
+ --max-updates <n>
133
177
  --concurrency <n>
134
178
  --cache-ttl <seconds>
135
179
  --offline
@@ -144,3 +188,22 @@ async function readPackageVersion() {
144
188
  const parsed = JSON.parse(content);
145
189
  return parsed.version ?? "0.0.0";
146
190
  }
191
+ function resolveExitCode(result, failOn, maxUpdates, ciMode) {
192
+ if (result.errors.length > 0)
193
+ return 2;
194
+ if (typeof maxUpdates === "number" && result.updates.length > maxUpdates)
195
+ return 1;
196
+ const effectiveFailOn = failOn && failOn !== "none" ? failOn : ciMode ? "any" : "none";
197
+ if (!shouldFailForUpdates(result.updates, effectiveFailOn))
198
+ return 0;
199
+ return 1;
200
+ }
201
+ function shouldFailForUpdates(updates, failOn) {
202
+ if (failOn === "none")
203
+ return false;
204
+ if (failOn === "any" || failOn === "patch")
205
+ return updates.length > 0;
206
+ if (failOn === "minor")
207
+ return updates.some((update) => update.diffType === "minor" || update.diffType === "major");
208
+ return updates.some((update) => update.diffType === "major");
209
+ }
@@ -5,5 +5,5 @@ export declare class VersionCache {
5
5
  static create(customPath?: string): Promise<VersionCache>;
6
6
  getValid(packageName: string, target: TargetLevel): Promise<CachedVersion | null>;
7
7
  getAny(packageName: string, target: TargetLevel): Promise<CachedVersion | null>;
8
- set(packageName: string, target: TargetLevel, latestVersion: string, ttlSeconds: number): Promise<void>;
8
+ set(packageName: string, target: TargetLevel, latestVersion: string, availableVersions: string[], ttlSeconds: number): Promise<void>;
9
9
  }
@@ -9,7 +9,13 @@ class FileCacheStore {
9
9
  async get(packageName, target) {
10
10
  const entries = await this.readEntries();
11
11
  const key = this.getKey(packageName, target);
12
- return entries[key] ?? null;
12
+ const entry = entries[key];
13
+ if (!entry)
14
+ return null;
15
+ return {
16
+ ...entry,
17
+ availableVersions: Array.isArray(entry.availableVersions) ? entry.availableVersions : [entry.latestVersion],
18
+ };
13
19
  }
14
20
  async set(entry) {
15
21
  const entries = await this.readEntries();
@@ -39,31 +45,63 @@ class SqliteCacheStore {
39
45
  package_name TEXT NOT NULL,
40
46
  target TEXT NOT NULL,
41
47
  latest_version TEXT NOT NULL,
48
+ available_versions TEXT NOT NULL,
42
49
  fetched_at INTEGER NOT NULL,
43
50
  ttl_seconds INTEGER NOT NULL,
44
51
  PRIMARY KEY (package_name, target)
45
52
  );
46
53
  `);
54
+ this.ensureSchema();
47
55
  }
48
56
  async get(packageName, target) {
49
- const row = this.db
50
- .prepare(`SELECT package_name, target, latest_version, fetched_at, ttl_seconds FROM versions WHERE package_name = ? AND target = ?`)
51
- .get(packageName, target);
57
+ let row;
58
+ try {
59
+ row = this.db
60
+ .prepare(`SELECT package_name, target, latest_version, available_versions, fetched_at, ttl_seconds FROM versions WHERE package_name = ? AND target = ?`)
61
+ .get(packageName, target);
62
+ }
63
+ catch {
64
+ row = this.db
65
+ .prepare(`SELECT package_name, target, latest_version, fetched_at, ttl_seconds FROM versions WHERE package_name = ? AND target = ?`)
66
+ .get(packageName, target);
67
+ }
52
68
  if (!row)
53
69
  return null;
54
70
  return {
55
71
  packageName: row.package_name,
56
72
  target: row.target,
57
73
  latestVersion: row.latest_version,
74
+ availableVersions: parseJsonArray(row.available_versions ?? row.latest_version, row.latest_version),
58
75
  fetchedAt: row.fetched_at,
59
76
  ttlSeconds: row.ttl_seconds,
60
77
  };
61
78
  }
62
79
  async set(entry) {
63
- this.db
64
- .prepare(`INSERT OR REPLACE INTO versions (package_name, target, latest_version, fetched_at, ttl_seconds)
65
- VALUES (?, ?, ?, ?, ?)`)
66
- .run(entry.packageName, entry.target, entry.latestVersion, entry.fetchedAt, entry.ttlSeconds);
80
+ try {
81
+ this.db
82
+ .prepare(`INSERT OR REPLACE INTO versions (package_name, target, latest_version, available_versions, fetched_at, ttl_seconds)
83
+ VALUES (?, ?, ?, ?, ?, ?)`)
84
+ .run(entry.packageName, entry.target, entry.latestVersion, JSON.stringify(entry.availableVersions), entry.fetchedAt, entry.ttlSeconds);
85
+ }
86
+ catch {
87
+ this.db
88
+ .prepare(`INSERT OR REPLACE INTO versions (package_name, target, latest_version, fetched_at, ttl_seconds)
89
+ VALUES (?, ?, ?, ?, ?)`)
90
+ .run(entry.packageName, entry.target, entry.latestVersion, entry.fetchedAt, entry.ttlSeconds);
91
+ }
92
+ }
93
+ ensureSchema() {
94
+ try {
95
+ const columns = this.db.prepare("PRAGMA table_info(versions);").all();
96
+ const hasAvailableVersions = columns.some((column) => column.name === "available_versions");
97
+ if (!hasAvailableVersions) {
98
+ this.db.exec("ALTER TABLE versions ADD COLUMN available_versions TEXT;");
99
+ }
100
+ this.db.exec("UPDATE versions SET available_versions = latest_version WHERE available_versions IS NULL;");
101
+ }
102
+ catch {
103
+ // Best-effort migration.
104
+ }
67
105
  }
68
106
  }
69
107
  export class VersionCache {
@@ -92,11 +130,12 @@ export class VersionCache {
92
130
  async getAny(packageName, target) {
93
131
  return this.store.get(packageName, target);
94
132
  }
95
- async set(packageName, target, latestVersion, ttlSeconds) {
133
+ async set(packageName, target, latestVersion, availableVersions, ttlSeconds) {
96
134
  await this.store.set({
97
135
  packageName,
98
136
  target,
99
137
  latestVersion,
138
+ availableVersions,
100
139
  fetchedAt: Date.now(),
101
140
  ttlSeconds,
102
141
  });
@@ -111,15 +150,21 @@ async function tryCreateSqliteStore(dbPath) {
111
150
  }
112
151
  }
113
152
  catch {
114
- // noop
153
+ return null;
115
154
  }
155
+ return null;
156
+ }
157
+ function parseJsonArray(raw, fallback) {
158
+ if (typeof raw !== "string")
159
+ return [fallback];
116
160
  try {
117
- const maybeRequire = Function("return require")();
118
- const Database = maybeRequire("better-sqlite3");
119
- const db = new Database(dbPath);
120
- return new SqliteCacheStore(db);
161
+ const parsed = JSON.parse(raw);
162
+ if (!Array.isArray(parsed))
163
+ return [fallback];
164
+ const values = parsed.filter((value) => typeof value === "string");
165
+ return values.length > 0 ? values : [fallback];
121
166
  }
122
167
  catch {
123
- return null;
168
+ return [fallback];
124
169
  }
125
170
  }
@@ -1,4 +1,4 @@
1
- import type { DependencyKind, OutputFormat, TargetLevel } from "../types/index.js";
1
+ import type { DependencyKind, FailOnLevel, OutputFormat, TargetLevel } from "../types/index.js";
2
2
  export interface FileConfig {
3
3
  target?: TargetLevel;
4
4
  filter?: string;
@@ -15,6 +15,8 @@ export interface FileConfig {
15
15
  offline?: boolean;
16
16
  policyFile?: string;
17
17
  prReportFile?: string;
18
+ failOn?: FailOnLevel;
19
+ maxUpdates?: number;
18
20
  install?: boolean;
19
21
  packageManager?: "auto" | "npm" | "pnpm";
20
22
  sync?: boolean;