@rainy-updates/cli 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +158 -0
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/bin/cli.d.ts +2 -0
- package/dist/bin/cli.js +146 -0
- package/dist/cache/cache.d.ts +9 -0
- package/dist/cache/cache.js +125 -0
- package/dist/config/loader.d.ts +22 -0
- package/dist/config/loader.js +35 -0
- package/dist/config/policy.d.ts +16 -0
- package/dist/config/policy.js +32 -0
- package/dist/core/check.d.ts +2 -0
- package/dist/core/check.js +141 -0
- package/dist/core/init-ci.d.ts +4 -0
- package/dist/core/init-ci.js +20 -0
- package/dist/core/options.d.ts +17 -0
- package/dist/core/options.js +238 -0
- package/dist/core/upgrade.d.ts +2 -0
- package/dist/core/upgrade.js +88 -0
- package/dist/core/warm-cache.d.ts +2 -0
- package/dist/core/warm-cache.js +89 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +7 -0
- package/dist/output/format.d.ts +2 -0
- package/dist/output/format.js +56 -0
- package/dist/output/github.d.ts +3 -0
- package/dist/output/github.js +30 -0
- package/dist/output/pr-report.d.ts +2 -0
- package/dist/output/pr-report.js +38 -0
- package/dist/output/sarif.d.ts +2 -0
- package/dist/output/sarif.js +60 -0
- package/dist/parsers/package-json.d.ts +5 -0
- package/dist/parsers/package-json.js +35 -0
- package/dist/pm/detect.d.ts +1 -0
- package/dist/pm/detect.js +20 -0
- package/dist/pm/install.d.ts +1 -0
- package/dist/pm/install.js +21 -0
- package/dist/registry/npm.d.ts +14 -0
- package/dist/registry/npm.js +128 -0
- package/dist/types/index.d.ts +86 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/async-pool.d.ts +1 -0
- package/dist/utils/async-pool.js +21 -0
- package/dist/utils/pattern.d.ts +1 -0
- package/dist/utils/pattern.js +11 -0
- package/dist/utils/semver.d.ts +13 -0
- package/dist/utils/semver.js +80 -0
- package/dist/workspace/discover.d.ts +1 -0
- package/dist/workspace/discover.js +88 -0
- package/dist/workspace/graph.d.ts +13 -0
- package/dist/workspace/graph.js +86 -0
- package/package.json +66 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## [0.4.0] - 2026-02-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Production hardening for CLI UX:
|
|
10
|
+
- global and command-level help (`--help`, `-h`),
|
|
11
|
+
- version output (`--version`, `-v`),
|
|
12
|
+
- strict unknown command rejection.
|
|
13
|
+
- OSS/release infrastructure:
|
|
14
|
+
- `LICENSE` (MIT),
|
|
15
|
+
- `CONTRIBUTING.md`,
|
|
16
|
+
- project CI workflow (`.github/workflows/ci.yml`),
|
|
17
|
+
- npm release workflow (`.github/workflows/release.yml`).
|
|
18
|
+
- Packaging stabilization:
|
|
19
|
+
- `types` export path,
|
|
20
|
+
- production scripts (`clean`, `test:prod`, `prepublishOnly`),
|
|
21
|
+
- publish config with npm provenance and public access.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Registry client now retries latest-version resolution with backoff for transient failures.
|
|
26
|
+
- Output formatting now shows cache warming summary when relevant.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Parser now fails fast for unknown commands instead of silently defaulting to `check`.
|
|
31
|
+
|
|
32
|
+
## [0.3.0] - 2026-02-27
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- `warm-cache` command:
|
|
37
|
+
- pre-fetches package metadata into cache,
|
|
38
|
+
- supports workspace scanning,
|
|
39
|
+
- supports offline behavior with explicit cache-miss reporting.
|
|
40
|
+
- `init-ci` command:
|
|
41
|
+
- scaffolds `.github/workflows/rainy-updates.yml`,
|
|
42
|
+
- supports `--force` overwrite behavior.
|
|
43
|
+
- Policy engine:
|
|
44
|
+
- `--policy-file` to load package update rules,
|
|
45
|
+
- default discovery of `.rainyupdates-policy.json` and `rainy-updates.policy.json`,
|
|
46
|
+
- rule-level controls:
|
|
47
|
+
- global ignore patterns,
|
|
48
|
+
- per-package ignore,
|
|
49
|
+
- per-package `maxTarget` update ceiling.
|
|
50
|
+
- PR report output:
|
|
51
|
+
- `--pr-report-file` emits markdown report for pull request comments.
|
|
52
|
+
- New summary metric:
|
|
53
|
+
- `warmedPackages` in results and GitHub output values.
|
|
54
|
+
- New tests:
|
|
55
|
+
- warm cache behavior,
|
|
56
|
+
- policy loading,
|
|
57
|
+
- CI workflow scaffolding,
|
|
58
|
+
- PR markdown report rendering,
|
|
59
|
+
- parser support for new commands/flags.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- Check pipeline now applies policy constraints before update proposals.
|
|
64
|
+
- Output summary now includes warmed cache package count.
|
|
65
|
+
- CLI command parser supports additional commands (`warm-cache`, `init-ci`) and options (`--policy-file`, `--pr-report-file`, `--force`).
|
|
66
|
+
|
|
67
|
+
## [0.2.0] - 2026-02-27
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- High-throughput registry resolution architecture:
|
|
72
|
+
- batched unique package resolution,
|
|
73
|
+
- configurable concurrency with `--concurrency`,
|
|
74
|
+
- optional `undici` pool + HTTP/2 path when available,
|
|
75
|
+
- automatic fallback to native `fetch` when `undici` is unavailable.
|
|
76
|
+
- Offline execution mode:
|
|
77
|
+
- `--offline` runs in cache-only mode,
|
|
78
|
+
- reports cache misses explicitly for deterministic CI behavior.
|
|
79
|
+
- Workspace graph module:
|
|
80
|
+
- detects local package graph,
|
|
81
|
+
- computes topological order,
|
|
82
|
+
- detects simple cycle groups and surfaces warnings.
|
|
83
|
+
- Graph-aware sync in upgrade flow:
|
|
84
|
+
- `--sync` now aligns versions following workspace graph order,
|
|
85
|
+
- preserves `workspace:*` protocol references.
|
|
86
|
+
- Additional test coverage:
|
|
87
|
+
- workspace graph ordering,
|
|
88
|
+
- workspace protocol edge handling,
|
|
89
|
+
- offline cache miss behavior.
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
|
|
93
|
+
- `check` pipeline now resolves versions by unique dependency name first, then applies results across all manifests.
|
|
94
|
+
- stale cache fallback is applied after registry failures to reduce flaky CI checks.
|
|
95
|
+
- options/config surface expanded with `offline` and stronger CI-oriented controls.
|
|
96
|
+
|
|
97
|
+
### OSS Quality
|
|
98
|
+
|
|
99
|
+
- README expanded with performance/runtime notes and complete options matrix.
|
|
100
|
+
- Output and artifact model reinforced for CI systems (JSON, GitHub outputs, SARIF).
|
|
101
|
+
|
|
102
|
+
## [0.1.0] - 2026-02-27
|
|
103
|
+
|
|
104
|
+
### Added
|
|
105
|
+
|
|
106
|
+
- New npm package identity: `@rainy-updates/cli` with CLI binary `rainy-updates`.
|
|
107
|
+
- Core commands:
|
|
108
|
+
- `check` for update detection.
|
|
109
|
+
- `upgrade` for manifest rewriting with optional installation step.
|
|
110
|
+
- Multi-format output system:
|
|
111
|
+
- `table`, `json`, `minimal`, and `github` annotation output.
|
|
112
|
+
- CI artifact outputs:
|
|
113
|
+
- `--json-file` for machine-readable check results.
|
|
114
|
+
- `--github-output` for key-value GitHub Actions outputs.
|
|
115
|
+
- `--sarif-file` for SARIF 2.1.0 compatible reports.
|
|
116
|
+
- Configuration support:
|
|
117
|
+
- `.rainyupdatesrc`
|
|
118
|
+
- `.rainyupdatesrc.json`
|
|
119
|
+
- `package.json` field: `rainyUpdates`
|
|
120
|
+
- Workspace-aware scanning (`--workspace`):
|
|
121
|
+
- `package.json` workspaces detection.
|
|
122
|
+
- `pnpm-workspace.yaml` package pattern detection.
|
|
123
|
+
- Workspace upgrade harmonization:
|
|
124
|
+
- `--sync` aligns dependency versions across scanned manifests.
|
|
125
|
+
- Dependency category filtering:
|
|
126
|
+
- `--dep-kinds deps,dev,optional,peer`
|
|
127
|
+
- Runtime controls:
|
|
128
|
+
- `--concurrency` for parallel dependency checks.
|
|
129
|
+
- `--cache-ttl` for cache freshness tuning.
|
|
130
|
+
- Cache layer improvements:
|
|
131
|
+
- SQLite-first cache backend when available.
|
|
132
|
+
- JSON fallback cache backend.
|
|
133
|
+
- stale cache fallback path when registry requests fail.
|
|
134
|
+
- Programmatic exports:
|
|
135
|
+
- `check`, `upgrade`, SARIF and GitHub output helpers.
|
|
136
|
+
- Test suite expanded with coverage for:
|
|
137
|
+
- semver utilities,
|
|
138
|
+
- CLI option parsing,
|
|
139
|
+
- config loading,
|
|
140
|
+
- workspace discovery,
|
|
141
|
+
- SARIF generation,
|
|
142
|
+
- GitHub output writing.
|
|
143
|
+
|
|
144
|
+
### Changed
|
|
145
|
+
|
|
146
|
+
- Project structure refactored into modular layers (`core`, `config`, `workspace`, `output`, `cache`, `registry`, `pm`, `utils`, `types`).
|
|
147
|
+
- CLI parser upgraded to async workflow to support config loading and cwd-based config resolution.
|
|
148
|
+
- Upgrade pipeline now supports writing updates across multiple workspace package manifests.
|
|
149
|
+
|
|
150
|
+
### Fixed
|
|
151
|
+
|
|
152
|
+
- Argument parsing now correctly handles flags when command is omitted (`check` default mode).
|
|
153
|
+
- Type-safe discriminated command parsing for `check` vs `upgrade` options.
|
|
154
|
+
|
|
155
|
+
### Notes
|
|
156
|
+
|
|
157
|
+
- In network-restricted environments, registry lookups fail gracefully and return exit code `2` with detailed error output.
|
|
158
|
+
- `pnpm` and `npm` are the official package-manager scope for this release.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rainy Updates
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @rainy-updates/cli
|
|
2
|
+
|
|
3
|
+
Agentic OSS CLI for dependency updates focused on speed, CI automation, and workspace-scale maintenance.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i -D @rainy-updates/cli
|
|
9
|
+
# or
|
|
10
|
+
pnpm add -D @rainy-updates/cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
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`.
|
|
19
|
+
|
|
20
|
+
## Quick start
|
|
21
|
+
|
|
22
|
+
```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
|
|
25
|
+
|
|
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
|
|
29
|
+
|
|
30
|
+
# upgrade ranges and install lockfiles
|
|
31
|
+
npx @rainy-updates/cli upgrade --target latest --workspace --sync --install
|
|
32
|
+
|
|
33
|
+
# scaffold GitHub Actions workflow
|
|
34
|
+
npx @rainy-updates/cli init-ci
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Core options
|
|
38
|
+
|
|
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>`
|
|
48
|
+
|
|
49
|
+
## Output options
|
|
50
|
+
|
|
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)
|
|
56
|
+
|
|
57
|
+
## Upgrade options
|
|
58
|
+
|
|
59
|
+
- `--install`
|
|
60
|
+
- `--pm auto|npm|pnpm`
|
|
61
|
+
- `--sync` (graph-aware version alignment across workspace packages)
|
|
62
|
+
|
|
63
|
+
## Policy controls
|
|
64
|
+
|
|
65
|
+
- `--policy-file <path>` to apply package-level policy rules.
|
|
66
|
+
- default policy discovery:
|
|
67
|
+
- `.rainyupdates-policy.json`
|
|
68
|
+
- `rainy-updates.policy.json`
|
|
69
|
+
|
|
70
|
+
Policy example:
|
|
71
|
+
|
|
72
|
+
```json
|
|
73
|
+
{
|
|
74
|
+
"ignore": ["@types/*", "eslint*"],
|
|
75
|
+
"packageRules": {
|
|
76
|
+
"react": { "maxTarget": "minor" },
|
|
77
|
+
"typescript": { "ignore": true }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## CLI help
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
rainy-updates --help
|
|
86
|
+
rainy-updates <command> --help
|
|
87
|
+
rainy-updates --version
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## CI behavior
|
|
91
|
+
|
|
92
|
+
- `--ci`: returns exit code `1` when updates are found.
|
|
93
|
+
- returns exit code `2` for operational errors (registry/IO/runtime failures).
|
|
94
|
+
|
|
95
|
+
## Config files
|
|
96
|
+
|
|
97
|
+
Supported:
|
|
98
|
+
|
|
99
|
+
- `.rainyupdatesrc`
|
|
100
|
+
- `.rainyupdatesrc.json`
|
|
101
|
+
- `package.json` -> `rainyUpdates`
|
|
102
|
+
|
|
103
|
+
Example:
|
|
104
|
+
|
|
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
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Production release
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
bun run prepublishOnly
|
|
126
|
+
node scripts/release-preflight.mjs
|
|
127
|
+
npm publish --provenance --access public
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If publishing in GitHub Actions, set `NPM_TOKEN` in repository secrets.
|
|
131
|
+
|
|
132
|
+
The repository includes:
|
|
133
|
+
|
|
134
|
+
- `.github/workflows/ci.yml` for test/typecheck/build/smoke checks.
|
|
135
|
+
- `.github/workflows/release.yml` for tag-driven npm publishing.
|
|
136
|
+
|
|
137
|
+
## Performance and runtime notes
|
|
138
|
+
|
|
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.
|
package/dist/bin/cli.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { parseCliArgs } from "../core/options.js";
|
|
7
|
+
import { check } from "../core/check.js";
|
|
8
|
+
import { upgrade } from "../core/upgrade.js";
|
|
9
|
+
import { warmCache } from "../core/warm-cache.js";
|
|
10
|
+
import { initCiWorkflow } from "../core/init-ci.js";
|
|
11
|
+
import { renderResult } from "../output/format.js";
|
|
12
|
+
import { writeGitHubOutput } from "../output/github.js";
|
|
13
|
+
import { createSarifReport } from "../output/sarif.js";
|
|
14
|
+
import { renderPrReport } from "../output/pr-report.js";
|
|
15
|
+
async function main() {
|
|
16
|
+
try {
|
|
17
|
+
const argv = process.argv.slice(2);
|
|
18
|
+
if (argv.includes("--version") || argv.includes("-v")) {
|
|
19
|
+
process.stdout.write((await readPackageVersion()) + "\n");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
23
|
+
process.stdout.write(renderHelp(argv[0]) + "\n");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const parsed = await parseCliArgs(argv);
|
|
27
|
+
if (parsed.command === "init-ci") {
|
|
28
|
+
const workflow = await initCiWorkflow(parsed.options.cwd, parsed.options.force);
|
|
29
|
+
process.stdout.write(workflow.created
|
|
30
|
+
? `Created CI workflow at ${workflow.path}\n`
|
|
31
|
+
: `CI workflow already exists at ${workflow.path}. Use --force to overwrite.\n`);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const result = parsed.command === "upgrade"
|
|
35
|
+
? await upgrade(parsed.options)
|
|
36
|
+
: parsed.command === "warm-cache"
|
|
37
|
+
? await warmCache(parsed.options)
|
|
38
|
+
: await check(parsed.options);
|
|
39
|
+
const rendered = renderResult(result, parsed.options.format);
|
|
40
|
+
process.stdout.write(rendered + "\n");
|
|
41
|
+
if (parsed.options.jsonFile) {
|
|
42
|
+
await fs.mkdir(path.dirname(parsed.options.jsonFile), { recursive: true });
|
|
43
|
+
await fs.writeFile(parsed.options.jsonFile, JSON.stringify(result, null, 2) + "\n", "utf8");
|
|
44
|
+
}
|
|
45
|
+
if (parsed.options.prReportFile) {
|
|
46
|
+
const markdown = renderPrReport(result);
|
|
47
|
+
await fs.mkdir(path.dirname(parsed.options.prReportFile), { recursive: true });
|
|
48
|
+
await fs.writeFile(parsed.options.prReportFile, markdown + "\n", "utf8");
|
|
49
|
+
}
|
|
50
|
+
if (parsed.options.githubOutputFile) {
|
|
51
|
+
await writeGitHubOutput(parsed.options.githubOutputFile, result);
|
|
52
|
+
}
|
|
53
|
+
if (parsed.options.sarifFile) {
|
|
54
|
+
const sarif = createSarifReport(result);
|
|
55
|
+
await fs.mkdir(path.dirname(parsed.options.sarifFile), { recursive: true });
|
|
56
|
+
await fs.writeFile(parsed.options.sarifFile, JSON.stringify(sarif, null, 2) + "\n", "utf8");
|
|
57
|
+
}
|
|
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
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
process.stderr.write(`rainy-updates: ${String(error)}\n`);
|
|
68
|
+
process.exitCode = 2;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
void main();
|
|
72
|
+
function renderHelp(command) {
|
|
73
|
+
const isCommand = command && !command.startsWith("-");
|
|
74
|
+
if (isCommand && command === "warm-cache") {
|
|
75
|
+
return `rainy-updates warm-cache [options]
|
|
76
|
+
|
|
77
|
+
Pre-warm local metadata cache for faster CI checks.
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
--workspace
|
|
81
|
+
--target patch|minor|major|latest
|
|
82
|
+
--filter <pattern>
|
|
83
|
+
--reject <pattern>
|
|
84
|
+
--dep-kinds deps,dev,optional,peer
|
|
85
|
+
--concurrency <n>
|
|
86
|
+
--cache-ttl <seconds>
|
|
87
|
+
--offline
|
|
88
|
+
--json-file <path>
|
|
89
|
+
--github-output <path>
|
|
90
|
+
--sarif-file <path>
|
|
91
|
+
--pr-report-file <path>`;
|
|
92
|
+
}
|
|
93
|
+
if (isCommand && command === "upgrade") {
|
|
94
|
+
return `rainy-updates upgrade [options]
|
|
95
|
+
|
|
96
|
+
Apply dependency updates to package.json manifests.
|
|
97
|
+
|
|
98
|
+
Options:
|
|
99
|
+
--workspace
|
|
100
|
+
--sync
|
|
101
|
+
--install
|
|
102
|
+
--pm auto|npm|pnpm
|
|
103
|
+
--target patch|minor|major|latest
|
|
104
|
+
--policy-file <path>
|
|
105
|
+
--concurrency <n>
|
|
106
|
+
--json-file <path>
|
|
107
|
+
--pr-report-file <path>`;
|
|
108
|
+
}
|
|
109
|
+
if (isCommand && command === "init-ci") {
|
|
110
|
+
return `rainy-updates init-ci [--force]
|
|
111
|
+
|
|
112
|
+
Create a GitHub Actions workflow template at:
|
|
113
|
+
.github/workflows/rainy-updates.yml`;
|
|
114
|
+
}
|
|
115
|
+
return `rainy-updates <command> [options]
|
|
116
|
+
|
|
117
|
+
Commands:
|
|
118
|
+
check Detect available updates
|
|
119
|
+
upgrade Apply updates to manifests
|
|
120
|
+
warm-cache Warm local cache for fast/offline checks
|
|
121
|
+
init-ci Scaffold GitHub Actions workflow
|
|
122
|
+
|
|
123
|
+
Global options:
|
|
124
|
+
--cwd <path>
|
|
125
|
+
--workspace
|
|
126
|
+
--target patch|minor|major|latest
|
|
127
|
+
--format table|json|minimal|github
|
|
128
|
+
--json-file <path>
|
|
129
|
+
--github-output <path>
|
|
130
|
+
--sarif-file <path>
|
|
131
|
+
--pr-report-file <path>
|
|
132
|
+
--policy-file <path>
|
|
133
|
+
--concurrency <n>
|
|
134
|
+
--cache-ttl <seconds>
|
|
135
|
+
--offline
|
|
136
|
+
--ci
|
|
137
|
+
--help, -h
|
|
138
|
+
--version, -v`;
|
|
139
|
+
}
|
|
140
|
+
async function readPackageVersion() {
|
|
141
|
+
const currentFile = fileURLToPath(import.meta.url);
|
|
142
|
+
const packageJsonPath = path.resolve(path.dirname(currentFile), "../../package.json");
|
|
143
|
+
const content = await fs.readFile(packageJsonPath, "utf8");
|
|
144
|
+
const parsed = JSON.parse(content);
|
|
145
|
+
return parsed.version ?? "0.0.0";
|
|
146
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CachedVersion, TargetLevel } from "../types/index.js";
|
|
2
|
+
export declare class VersionCache {
|
|
3
|
+
private readonly store;
|
|
4
|
+
private constructor();
|
|
5
|
+
static create(customPath?: string): Promise<VersionCache>;
|
|
6
|
+
getValid(packageName: string, target: TargetLevel): Promise<CachedVersion | null>;
|
|
7
|
+
getAny(packageName: string, target: TargetLevel): Promise<CachedVersion | null>;
|
|
8
|
+
set(packageName: string, target: TargetLevel, latestVersion: string, ttlSeconds: number): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
class FileCacheStore {
|
|
5
|
+
filePath;
|
|
6
|
+
constructor(filePath) {
|
|
7
|
+
this.filePath = filePath;
|
|
8
|
+
}
|
|
9
|
+
async get(packageName, target) {
|
|
10
|
+
const entries = await this.readEntries();
|
|
11
|
+
const key = this.getKey(packageName, target);
|
|
12
|
+
return entries[key] ?? null;
|
|
13
|
+
}
|
|
14
|
+
async set(entry) {
|
|
15
|
+
const entries = await this.readEntries();
|
|
16
|
+
entries[this.getKey(entry.packageName, entry.target)] = entry;
|
|
17
|
+
await fs.mkdir(path.dirname(this.filePath), { recursive: true });
|
|
18
|
+
await fs.writeFile(this.filePath, JSON.stringify(entries), "utf8");
|
|
19
|
+
}
|
|
20
|
+
async readEntries() {
|
|
21
|
+
try {
|
|
22
|
+
const content = await fs.readFile(this.filePath, "utf8");
|
|
23
|
+
return JSON.parse(content);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
getKey(packageName, target) {
|
|
30
|
+
return `${packageName}:${target}`;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
class SqliteCacheStore {
|
|
34
|
+
db;
|
|
35
|
+
constructor(db) {
|
|
36
|
+
this.db = db;
|
|
37
|
+
this.db.exec(`
|
|
38
|
+
CREATE TABLE IF NOT EXISTS versions (
|
|
39
|
+
package_name TEXT NOT NULL,
|
|
40
|
+
target TEXT NOT NULL,
|
|
41
|
+
latest_version TEXT NOT NULL,
|
|
42
|
+
fetched_at INTEGER NOT NULL,
|
|
43
|
+
ttl_seconds INTEGER NOT NULL,
|
|
44
|
+
PRIMARY KEY (package_name, target)
|
|
45
|
+
);
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
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);
|
|
52
|
+
if (!row)
|
|
53
|
+
return null;
|
|
54
|
+
return {
|
|
55
|
+
packageName: row.package_name,
|
|
56
|
+
target: row.target,
|
|
57
|
+
latestVersion: row.latest_version,
|
|
58
|
+
fetchedAt: row.fetched_at,
|
|
59
|
+
ttlSeconds: row.ttl_seconds,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
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);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export class VersionCache {
|
|
70
|
+
store;
|
|
71
|
+
constructor(store) {
|
|
72
|
+
this.store = store;
|
|
73
|
+
}
|
|
74
|
+
static async create(customPath) {
|
|
75
|
+
const basePath = customPath ?? path.join(os.homedir(), ".cache", "rainy-updates");
|
|
76
|
+
const sqlitePath = path.join(basePath, "cache.db");
|
|
77
|
+
const sqliteStore = await tryCreateSqliteStore(sqlitePath);
|
|
78
|
+
if (sqliteStore)
|
|
79
|
+
return new VersionCache(sqliteStore);
|
|
80
|
+
const jsonPath = path.join(basePath, "cache.json");
|
|
81
|
+
return new VersionCache(new FileCacheStore(jsonPath));
|
|
82
|
+
}
|
|
83
|
+
async getValid(packageName, target) {
|
|
84
|
+
const entry = await this.store.get(packageName, target);
|
|
85
|
+
if (!entry)
|
|
86
|
+
return null;
|
|
87
|
+
const expiresAt = entry.fetchedAt + entry.ttlSeconds * 1000;
|
|
88
|
+
if (Date.now() > expiresAt)
|
|
89
|
+
return null;
|
|
90
|
+
return entry;
|
|
91
|
+
}
|
|
92
|
+
async getAny(packageName, target) {
|
|
93
|
+
return this.store.get(packageName, target);
|
|
94
|
+
}
|
|
95
|
+
async set(packageName, target, latestVersion, ttlSeconds) {
|
|
96
|
+
await this.store.set({
|
|
97
|
+
packageName,
|
|
98
|
+
target,
|
|
99
|
+
latestVersion,
|
|
100
|
+
fetchedAt: Date.now(),
|
|
101
|
+
ttlSeconds,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
async function tryCreateSqliteStore(dbPath) {
|
|
106
|
+
try {
|
|
107
|
+
if (typeof Bun !== "undefined") {
|
|
108
|
+
const mod = await import("bun:sqlite");
|
|
109
|
+
const db = new mod.Database(dbPath, { create: true });
|
|
110
|
+
return new SqliteCacheStore(db);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// noop
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
const maybeRequire = Function("return require")();
|
|
118
|
+
const Database = maybeRequire("better-sqlite3");
|
|
119
|
+
const db = new Database(dbPath);
|
|
120
|
+
return new SqliteCacheStore(db);
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { DependencyKind, OutputFormat, TargetLevel } from "../types/index.js";
|
|
2
|
+
export interface FileConfig {
|
|
3
|
+
target?: TargetLevel;
|
|
4
|
+
filter?: string;
|
|
5
|
+
reject?: string;
|
|
6
|
+
cacheTtlSeconds?: number;
|
|
7
|
+
includeKinds?: DependencyKind[];
|
|
8
|
+
ci?: boolean;
|
|
9
|
+
format?: OutputFormat;
|
|
10
|
+
workspace?: boolean;
|
|
11
|
+
jsonFile?: string;
|
|
12
|
+
githubOutputFile?: string;
|
|
13
|
+
sarifFile?: string;
|
|
14
|
+
concurrency?: number;
|
|
15
|
+
offline?: boolean;
|
|
16
|
+
policyFile?: string;
|
|
17
|
+
prReportFile?: string;
|
|
18
|
+
install?: boolean;
|
|
19
|
+
packageManager?: "auto" | "npm" | "pnpm";
|
|
20
|
+
sync?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function loadConfig(cwd: string): Promise<FileConfig>;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export async function loadConfig(cwd) {
|
|
4
|
+
const fromRc = await loadRcFile(cwd);
|
|
5
|
+
const fromPackage = await loadPackageConfig(cwd);
|
|
6
|
+
return {
|
|
7
|
+
...fromPackage,
|
|
8
|
+
...fromRc,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
async function loadRcFile(cwd) {
|
|
12
|
+
const candidates = [".rainyupdatesrc", ".rainyupdatesrc.json"];
|
|
13
|
+
for (const candidate of candidates) {
|
|
14
|
+
const filePath = path.join(cwd, candidate);
|
|
15
|
+
try {
|
|
16
|
+
const content = await fs.readFile(filePath, "utf8");
|
|
17
|
+
return JSON.parse(content);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
// noop
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
async function loadPackageConfig(cwd) {
|
|
26
|
+
const packagePath = path.join(cwd, "package.json");
|
|
27
|
+
try {
|
|
28
|
+
const content = await fs.readFile(packagePath, "utf8");
|
|
29
|
+
const parsed = JSON.parse(content);
|
|
30
|
+
return parsed.rainyUpdates ?? {};
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TargetLevel } from "../types/index.js";
|
|
2
|
+
export interface PolicyConfig {
|
|
3
|
+
ignore?: string[];
|
|
4
|
+
packageRules?: Record<string, {
|
|
5
|
+
maxTarget?: TargetLevel;
|
|
6
|
+
ignore?: boolean;
|
|
7
|
+
}>;
|
|
8
|
+
}
|
|
9
|
+
export interface ResolvedPolicy {
|
|
10
|
+
ignorePatterns: string[];
|
|
11
|
+
packageRules: Map<string, {
|
|
12
|
+
maxTarget?: TargetLevel;
|
|
13
|
+
ignore: boolean;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
export declare function loadPolicy(cwd: string, policyFile?: string): Promise<ResolvedPolicy>;
|