@rainy-updates/cli 0.5.6 → 0.6.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.
Files changed (102) hide show
  1. package/CHANGELOG.md +133 -0
  2. package/README.md +90 -31
  3. package/dist/bin/cli.js +24 -482
  4. package/dist/bin/dispatch.d.ts +16 -0
  5. package/dist/bin/dispatch.js +147 -0
  6. package/dist/bin/help.d.ts +1 -0
  7. package/dist/bin/help.js +314 -0
  8. package/dist/cache/cache.js +13 -11
  9. package/dist/commands/audit/parser.js +2 -2
  10. package/dist/commands/audit/runner.js +27 -46
  11. package/dist/commands/audit/targets.js +13 -13
  12. package/dist/commands/bisect/oracle.js +28 -11
  13. package/dist/commands/bisect/parser.js +3 -3
  14. package/dist/commands/bisect/runner.js +15 -8
  15. package/dist/commands/changelog/fetcher.js +11 -5
  16. package/dist/commands/dashboard/parser.js +103 -1
  17. package/dist/commands/dashboard/runner.d.ts +2 -2
  18. package/dist/commands/dashboard/runner.js +67 -37
  19. package/dist/commands/doctor/parser.js +15 -4
  20. package/dist/commands/doctor/runner.js +6 -3
  21. package/dist/commands/ga/parser.js +4 -4
  22. package/dist/commands/ga/runner.js +13 -7
  23. package/dist/commands/health/parser.js +2 -2
  24. package/dist/commands/licenses/runner.js +4 -4
  25. package/dist/commands/resolve/runner.js +9 -4
  26. package/dist/commands/review/parser.js +57 -4
  27. package/dist/commands/review/runner.js +31 -5
  28. package/dist/commands/snapshot/runner.js +17 -17
  29. package/dist/commands/snapshot/store.d.ts +0 -12
  30. package/dist/commands/snapshot/store.js +26 -38
  31. package/dist/commands/unused/runner.js +6 -7
  32. package/dist/commands/unused/scanner.js +17 -20
  33. package/dist/config/loader.d.ts +2 -2
  34. package/dist/config/loader.js +2 -5
  35. package/dist/config/policy.js +20 -11
  36. package/dist/core/analysis/options.d.ts +6 -0
  37. package/dist/core/analysis/options.js +69 -0
  38. package/dist/core/analysis/review-items.d.ts +4 -0
  39. package/dist/core/analysis/review-items.js +128 -0
  40. package/dist/core/analysis/run-silenced.d.ts +1 -0
  41. package/dist/core/analysis/run-silenced.js +13 -0
  42. package/dist/core/analysis-bundle.js +3 -211
  43. package/dist/core/artifacts.js +6 -5
  44. package/dist/core/baseline.js +3 -5
  45. package/dist/core/check.js +2 -2
  46. package/dist/core/ci.js +52 -1
  47. package/dist/core/decision-plan.d.ts +14 -0
  48. package/dist/core/decision-plan.js +107 -0
  49. package/dist/core/doctor/findings.d.ts +2 -0
  50. package/dist/core/doctor/findings.js +166 -0
  51. package/dist/core/doctor/render.d.ts +3 -0
  52. package/dist/core/doctor/render.js +44 -0
  53. package/dist/core/doctor/result.d.ts +2 -0
  54. package/dist/core/doctor/result.js +58 -0
  55. package/dist/core/doctor/score.d.ts +5 -0
  56. package/dist/core/doctor/score.js +28 -0
  57. package/dist/core/fix-pr-batch.js +38 -28
  58. package/dist/core/fix-pr.js +27 -24
  59. package/dist/core/init-ci.js +25 -21
  60. package/dist/core/options.js +95 -4
  61. package/dist/core/review-model.d.ts +3 -3
  62. package/dist/core/review-model.js +6 -67
  63. package/dist/core/review-verdict.d.ts +2 -0
  64. package/dist/core/review-verdict.js +14 -0
  65. package/dist/core/summary.js +12 -0
  66. package/dist/core/upgrade.js +64 -2
  67. package/dist/core/verification.d.ts +2 -0
  68. package/dist/core/verification.js +106 -0
  69. package/dist/core/warm-cache.js +2 -2
  70. package/dist/output/format.js +22 -0
  71. package/dist/output/github.js +10 -0
  72. package/dist/output/sarif.js +16 -12
  73. package/dist/parsers/package-json.js +2 -4
  74. package/dist/pm/detect.d.ts +3 -1
  75. package/dist/pm/detect.js +24 -12
  76. package/dist/pm/install.d.ts +2 -1
  77. package/dist/pm/install.js +15 -16
  78. package/dist/registry/npm.js +34 -76
  79. package/dist/rup +0 -0
  80. package/dist/types/index.d.ts +104 -5
  81. package/dist/ui/tui.d.ts +4 -1
  82. package/dist/ui/tui.js +5 -4
  83. package/dist/utils/io.js +5 -6
  84. package/dist/utils/lockfile.js +24 -19
  85. package/dist/utils/runtime-paths.d.ts +4 -0
  86. package/dist/utils/runtime-paths.js +35 -0
  87. package/dist/utils/runtime.d.ts +7 -0
  88. package/dist/utils/runtime.js +32 -0
  89. package/dist/workspace/discover.js +55 -51
  90. package/package.json +16 -16
  91. package/dist/ui/dashboard/DashboardTUI.d.ts +0 -6
  92. package/dist/ui/dashboard/DashboardTUI.js +0 -34
  93. package/dist/ui/dashboard/components/DetailPanel.d.ts +0 -4
  94. package/dist/ui/dashboard/components/DetailPanel.js +0 -30
  95. package/dist/ui/dashboard/components/Footer.d.ts +0 -4
  96. package/dist/ui/dashboard/components/Footer.js +0 -9
  97. package/dist/ui/dashboard/components/Header.d.ts +0 -4
  98. package/dist/ui/dashboard/components/Header.js +0 -12
  99. package/dist/ui/dashboard/components/Sidebar.d.ts +0 -4
  100. package/dist/ui/dashboard/components/Sidebar.js +0 -23
  101. package/dist/ui/dashboard/store.d.ts +0 -34
  102. package/dist/ui/dashboard/store.js +0 -148
@@ -0,0 +1,147 @@
1
+ import { check } from "../core/check.js";
2
+ import { upgrade } from "../core/upgrade.js";
3
+ import { warmCache } from "../core/warm-cache.js";
4
+ import { runCi } from "../core/ci.js";
5
+ import { initCiWorkflow } from "../core/init-ci.js";
6
+ import { diffBaseline, saveBaseline } from "../core/baseline.js";
7
+ import { setRuntimeExitCode, writeStdout, } from "../utils/runtime.js";
8
+ export async function handleDirectCommand(parsed) {
9
+ if (parsed.command === "init-ci") {
10
+ const workflow = await initCiWorkflow(parsed.options.cwd, parsed.options.force, {
11
+ mode: parsed.options.mode,
12
+ schedule: parsed.options.schedule,
13
+ });
14
+ writeStdout(workflow.created
15
+ ? `Created CI workflow at ${workflow.path}\n`
16
+ : `CI workflow already exists at ${workflow.path}. Use --force to overwrite.\n`);
17
+ return true;
18
+ }
19
+ if (parsed.command === "baseline") {
20
+ if (parsed.options.action === "save") {
21
+ const saved = await saveBaseline(parsed.options);
22
+ writeStdout(`Saved baseline at ${saved.filePath} (${saved.entries} entries)\n`);
23
+ return true;
24
+ }
25
+ const diff = await diffBaseline(parsed.options);
26
+ const changes = diff.added.length + diff.removed.length + diff.changed.length;
27
+ if (changes === 0) {
28
+ writeStdout(`No baseline drift detected (${diff.filePath}).\n`);
29
+ return true;
30
+ }
31
+ writeStdout(`Baseline drift detected (${diff.filePath}).\n`);
32
+ if (diff.added.length > 0)
33
+ writeStdout(`Added: ${diff.added.length}\n`);
34
+ if (diff.removed.length > 0)
35
+ writeStdout(`Removed: ${diff.removed.length}\n`);
36
+ if (diff.changed.length > 0)
37
+ writeStdout(`Changed: ${diff.changed.length}\n`);
38
+ setRuntimeExitCode(1);
39
+ return true;
40
+ }
41
+ if (parsed.command === "bisect") {
42
+ const { runBisect } = await import("../commands/bisect/runner.js");
43
+ const result = await runBisect(parsed.options);
44
+ setRuntimeExitCode(result.breakingVersion ? 1 : 0);
45
+ return true;
46
+ }
47
+ if (parsed.command === "audit") {
48
+ const { runAudit } = await import("../commands/audit/runner.js");
49
+ const result = await runAudit(parsed.options);
50
+ setRuntimeExitCode(result.advisories.length > 0 ? 1 : 0);
51
+ return true;
52
+ }
53
+ if (parsed.command === "health") {
54
+ const { runHealth } = await import("../commands/health/runner.js");
55
+ const result = await runHealth(parsed.options);
56
+ setRuntimeExitCode(result.totalFlagged > 0 ? 1 : 0);
57
+ return true;
58
+ }
59
+ if (parsed.command === "unused") {
60
+ const { runUnused } = await import("../commands/unused/runner.js");
61
+ const result = await runUnused(parsed.options);
62
+ setRuntimeExitCode(result.totalUnused > 0 || result.totalMissing > 0 ? 1 : 0);
63
+ return true;
64
+ }
65
+ if (parsed.command === "resolve") {
66
+ const { runResolve } = await import("../commands/resolve/runner.js");
67
+ const result = await runResolve(parsed.options);
68
+ setRuntimeExitCode(result.errorConflicts > 0 ? 1 : 0);
69
+ return true;
70
+ }
71
+ if (parsed.command === "licenses") {
72
+ const { runLicenses } = await import("../commands/licenses/runner.js");
73
+ const result = await runLicenses(parsed.options);
74
+ setRuntimeExitCode(result.totalViolations > 0 ? 1 : 0);
75
+ return true;
76
+ }
77
+ if (parsed.command === "snapshot") {
78
+ const { runSnapshot } = await import("../commands/snapshot/runner.js");
79
+ const result = await runSnapshot(parsed.options);
80
+ setRuntimeExitCode(result.errors.length > 0 ? 1 : 0);
81
+ return true;
82
+ }
83
+ if (parsed.command === "review") {
84
+ const { runReview } = await import("../commands/review/runner.js");
85
+ const result = await runReview(parsed.options);
86
+ setRuntimeExitCode(result.summary.verdict === "blocked" ||
87
+ result.summary.verdict === "actionable" ||
88
+ result.summary.verdict === "review"
89
+ ? 1
90
+ : 0);
91
+ return true;
92
+ }
93
+ if (parsed.command === "doctor") {
94
+ const { runDoctor } = await import("../commands/doctor/runner.js");
95
+ const result = await runDoctor(parsed.options);
96
+ setRuntimeExitCode(result.verdict === "safe" ? 0 : 1);
97
+ return true;
98
+ }
99
+ if (parsed.command === "dashboard") {
100
+ const { runDashboard } = await import("../commands/dashboard/runner.js");
101
+ const result = await runDashboard(parsed.options);
102
+ setRuntimeExitCode(result.errors.length > 0 ? 1 : 0);
103
+ return true;
104
+ }
105
+ if (parsed.command === "ga") {
106
+ const { runGa } = await import("../commands/ga/runner.js");
107
+ const result = await runGa(parsed.options);
108
+ setRuntimeExitCode(result.ready ? 0 : 1);
109
+ return true;
110
+ }
111
+ if (parsed.options.interactive &&
112
+ (parsed.command === "check" ||
113
+ parsed.command === "upgrade" ||
114
+ parsed.command === "ci")) {
115
+ const { runDashboard } = await import("../commands/dashboard/runner.js");
116
+ const result = await runDashboard({
117
+ ...parsed.options,
118
+ mode: parsed.command === "upgrade" ? "upgrade" : "review",
119
+ focus: "all",
120
+ applySelected: parsed.command === "upgrade",
121
+ });
122
+ setRuntimeExitCode(result.errors.length > 0 ? 1 : 0);
123
+ return true;
124
+ }
125
+ return false;
126
+ }
127
+ export async function runPrimaryCommand(parsed) {
128
+ if (parsed.command === "upgrade") {
129
+ return upgrade(parsed.options);
130
+ }
131
+ if (parsed.command === "warm-cache") {
132
+ return warmCache(parsed.options);
133
+ }
134
+ if (parsed.command === "ci") {
135
+ return runCi(parsed.options);
136
+ }
137
+ if (parsed.options.fixPr) {
138
+ const upgradeOptions = {
139
+ ...parsed.options,
140
+ install: false,
141
+ packageManager: "auto",
142
+ sync: false,
143
+ };
144
+ return upgrade(upgradeOptions);
145
+ }
146
+ return check(parsed.options);
147
+ }
@@ -0,0 +1 @@
1
+ export declare function renderHelp(command?: string): string;
@@ -0,0 +1,314 @@
1
+ export function renderHelp(command) {
2
+ const isCommand = command && !command.startsWith("-");
3
+ if (isCommand && command === "check") {
4
+ return `rainy-updates check [options]
5
+
6
+ Detect candidate dependency updates. This is the first step in the flow:
7
+ check detects
8
+ doctor summarizes
9
+ review decides
10
+ upgrade applies
11
+
12
+ Options:
13
+ --workspace
14
+ --target patch|minor|major|latest
15
+ --filter <pattern>
16
+ --reject <pattern>
17
+ --dep-kinds deps,dev,optional,peer
18
+ --concurrency <n>
19
+ --registry-timeout-ms <n>
20
+ --registry-retries <n>
21
+ --cache-ttl <seconds>
22
+ --stream
23
+ --policy-file <path>
24
+ --offline
25
+ --fix-pr
26
+ --fix-branch <name>
27
+ --fix-commit-message <text>
28
+ --fix-dry-run
29
+ --fix-pr-no-checkout
30
+ --fix-pr-batch-size <n>
31
+ --no-pr-report
32
+ --json-file <path>
33
+ --github-output <path>
34
+ --sarif-file <path>
35
+ --pr-report-file <path>
36
+ --fail-on none|patch|minor|major|any
37
+ --max-updates <n>
38
+ --group-by none|name|scope|kind|risk
39
+ --group-max <n>
40
+ --cooldown-days <n>
41
+ --pr-limit <n>
42
+ --only-changed
43
+ --interactive
44
+ --plan-file <path>
45
+ --verify none|install|test|install,test
46
+ --test-command <cmd>
47
+ --verification-report-file <path>
48
+ --show-impact
49
+ --show-links
50
+ --show-homepage
51
+ --lockfile-mode preserve|update|error
52
+ --log-level error|warn|info|debug
53
+ --ci`;
54
+ }
55
+ if (isCommand && command === "warm-cache") {
56
+ return `rainy-updates warm-cache [options]
57
+
58
+ Pre-warm local metadata cache for faster CI checks.
59
+
60
+ Options:
61
+ --workspace
62
+ --target patch|minor|major|latest
63
+ --filter <pattern>
64
+ --reject <pattern>
65
+ --dep-kinds deps,dev,optional,peer
66
+ --concurrency <n>
67
+ --registry-timeout-ms <n>
68
+ --registry-retries <n>
69
+ --cache-ttl <seconds>
70
+ --offline
71
+ --stream
72
+ --json-file <path>
73
+ --github-output <path>
74
+ --sarif-file <path>
75
+ --pr-report-file <path>`;
76
+ }
77
+ if (isCommand && command === "upgrade") {
78
+ return `rainy-updates upgrade [options]
79
+
80
+ Apply an approved change set to package.json manifests.
81
+
82
+ Options:
83
+ --workspace
84
+ --sync
85
+ --install
86
+ --pm auto|bun|npm|pnpm|yarn
87
+ --target patch|minor|major|latest
88
+ --policy-file <path>
89
+ --concurrency <n>
90
+ --registry-timeout-ms <n>
91
+ --registry-retries <n>
92
+ --fix-pr
93
+ --fix-branch <name>
94
+ --fix-commit-message <text>
95
+ --fix-dry-run
96
+ --fix-pr-no-checkout
97
+ --fix-pr-batch-size <n>
98
+ --interactive
99
+ --from-plan <path>
100
+ --verify none|install|test|install,test
101
+ --test-command <cmd>
102
+ --verification-report-file <path>
103
+ --lockfile-mode preserve|update|error
104
+ --no-pr-report
105
+ --json-file <path>
106
+ --pr-report-file <path>`;
107
+ }
108
+ if (isCommand && command === "ci") {
109
+ return `rainy-updates ci [options]
110
+
111
+ Run CI-oriented automation around the same lifecycle:
112
+ check detects
113
+ doctor summarizes
114
+ review decides
115
+ upgrade applies
116
+
117
+ Options:
118
+ --workspace
119
+ --mode minimal|strict|enterprise
120
+ --gate check|doctor|review|upgrade
121
+ --group-by none|name|scope|kind|risk
122
+ --group-max <n>
123
+ --cooldown-days <n>
124
+ --pr-limit <n>
125
+ --only-changed
126
+ --offline
127
+ --concurrency <n>
128
+ --registry-timeout-ms <n>
129
+ --registry-retries <n>
130
+ --stream
131
+ --fix-pr
132
+ --fix-branch <name>
133
+ --fix-commit-message <text>
134
+ --fix-dry-run
135
+ --fix-pr-no-checkout
136
+ --fix-pr-batch-size <n>
137
+ --no-pr-report
138
+ --plan-file <path>
139
+ --verify none|install|test|install,test
140
+ --test-command <cmd>
141
+ --verification-report-file <path>
142
+ --json-file <path>
143
+ --github-output <path>
144
+ --sarif-file <path>
145
+ --pr-report-file <path>
146
+ --fail-on none|patch|minor|major|any
147
+ --max-updates <n>
148
+ --lockfile-mode preserve|update|error
149
+ --log-level error|warn|info|debug
150
+ --ci`;
151
+ }
152
+ if (isCommand && command === "init-ci") {
153
+ return `rainy-updates init-ci [options]
154
+
155
+ Create a GitHub Actions workflow template at:
156
+ .github/workflows/rainy-updates.yml
157
+
158
+ Options:
159
+ --force
160
+ --mode minimal|strict|enterprise
161
+ --schedule weekly|daily|off`;
162
+ }
163
+ if (isCommand && command === "baseline") {
164
+ return `rainy-updates baseline [options]
165
+
166
+ Save or compare dependency baseline snapshots.
167
+
168
+ Options:
169
+ --save
170
+ --check
171
+ --file <path>
172
+ --workspace
173
+ --dep-kinds deps,dev,optional,peer
174
+ --ci`;
175
+ }
176
+ if (isCommand && command === "audit") {
177
+ return `rainy-updates audit [options]
178
+
179
+ Scan dependencies for CVEs using OSV.dev and GitHub Advisory Database.
180
+
181
+ Options:
182
+ --workspace
183
+ --severity critical|high|medium|low
184
+ --summary
185
+ --report table|summary|json
186
+ --source auto|osv|github|all
187
+ --fix
188
+ --dry-run
189
+ --commit
190
+ --pm auto|npm|pnpm|bun|yarn
191
+ --json-file <path>
192
+ --concurrency <n>
193
+ --registry-timeout-ms <n>`;
194
+ }
195
+ if (isCommand && command === "review") {
196
+ return `rainy-updates review [options]
197
+
198
+ Review is the decision center of Rainy Updates.
199
+ Use it to inspect risk, security, peer, license, and policy context before applying changes.
200
+
201
+ Options:
202
+ --workspace
203
+ --interactive
204
+ --security-only
205
+ --risk critical|high|medium|low
206
+ --diff patch|minor|major|latest
207
+ --apply-selected
208
+ --plan-file <path>
209
+ --show-changelog
210
+ --policy-file <path>
211
+ --json-file <path>
212
+ --concurrency <n>
213
+ --registry-timeout-ms <n>
214
+ --registry-retries <n>`;
215
+ }
216
+ if (isCommand && command === "doctor") {
217
+ return `rainy-updates doctor [options]
218
+
219
+ Produce a fast summary verdict and point the operator to review when action is needed.
220
+
221
+ Options:
222
+ --workspace
223
+ --verdict-only
224
+ --include-changelog
225
+ --json-file <path>`;
226
+ }
227
+ if (isCommand && command === "dashboard") {
228
+ return `rainy-updates dashboard [options]
229
+
230
+ Open the primary interactive dependency operations console.
231
+
232
+ Options:
233
+ --workspace
234
+ --mode check|review|upgrade
235
+ --focus all|security|risk|major|blocked|workspace
236
+ --apply-selected
237
+ --plan-file <path>
238
+ --verify none|install|test|install,test
239
+ --test-command <cmd>
240
+ --verification-report-file <path>
241
+ --cwd <path>`;
242
+ }
243
+ if (isCommand && command === "ga") {
244
+ return `rainy-updates ga [options]
245
+
246
+ Audit release and CI readiness for Rainy Updates.
247
+
248
+ Options:
249
+ --workspace
250
+ --json-file <path>
251
+ --cwd <path>`;
252
+ }
253
+ return `rainy-updates (rup / rainy-up) <command> [options]
254
+
255
+ Commands:
256
+ check Detect candidate updates
257
+ doctor Summarize what matters
258
+ review Decide what to do
259
+ upgrade Apply the approved change set
260
+ dashboard Open the primary interactive dependency dashboard
261
+ ci Run CI-focused orchestration
262
+ warm-cache Warm local cache for fast/offline checks
263
+ init-ci Scaffold GitHub Actions workflow
264
+ baseline Save/check dependency baseline snapshots
265
+ audit Scan dependencies for CVEs (OSV.dev + GitHub)
266
+ health Detect stale/deprecated/unmaintained packages
267
+ bisect Find which version of a dep introduced a failure
268
+ unused Detect unused or missing npm dependencies
269
+ resolve Check peer dependency conflicts (pure-TS, no subprocess)
270
+ licenses Scan dependency licenses and generate SPDX SBOM
271
+ snapshot Save, list, restore, and diff dependency state snapshots
272
+ ga Audit GA and CI readiness for this checkout
273
+
274
+ Global options:
275
+ --cwd <path>
276
+ --workspace
277
+ --target patch|minor|major|latest
278
+ --format table|json|minimal|github|metrics
279
+ --json-file <path>
280
+ --github-output <path>
281
+ --sarif-file <path>
282
+ --pr-report-file <path>
283
+ --policy-file <path>
284
+ --fail-on none|patch|minor|major|any
285
+ --max-updates <n>
286
+ --group-by none|name|scope|kind|risk
287
+ --group-max <n>
288
+ --cooldown-days <n>
289
+ --pr-limit <n>
290
+ --only-changed
291
+ --interactive
292
+ --show-impact
293
+ --show-links
294
+ --show-homepage
295
+ --mode minimal|strict|enterprise
296
+ --fix-pr
297
+ --fix-branch <name>
298
+ --fix-commit-message <text>
299
+ --fix-dry-run
300
+ --fix-pr-no-checkout
301
+ --fix-pr-batch-size <n>
302
+ --no-pr-report
303
+ --log-level error|warn|info|debug
304
+ --concurrency <n>
305
+ --registry-timeout-ms <n>
306
+ --registry-retries <n>
307
+ --cache-ttl <seconds>
308
+ --offline
309
+ --stream
310
+ --lockfile-mode preserve|update|error
311
+ --ci
312
+ --help, -h
313
+ --version, -v`;
314
+ }
@@ -1,7 +1,7 @@
1
- import { promises as fs } from "node:fs";
2
- import os from "node:os";
3
1
  import path from "node:path";
4
- import process from "node:process";
2
+ import { writeFileAtomic } from "../utils/io.js";
3
+ import { getCacheDir } from "../utils/runtime-paths.js";
4
+ import { readEnv } from "../utils/runtime.js";
5
5
  class FileCacheStore {
6
6
  filePath;
7
7
  constructor(filePath) {
@@ -15,19 +15,19 @@ class FileCacheStore {
15
15
  return null;
16
16
  return {
17
17
  ...entry,
18
- availableVersions: Array.isArray(entry.availableVersions) ? entry.availableVersions : [entry.latestVersion],
18
+ availableVersions: Array.isArray(entry.availableVersions)
19
+ ? entry.availableVersions
20
+ : [entry.latestVersion],
19
21
  };
20
22
  }
21
23
  async set(entry) {
22
24
  const entries = await this.readEntries();
23
25
  entries[this.getKey(entry.packageName, entry.target)] = entry;
24
- await fs.mkdir(path.dirname(this.filePath), { recursive: true });
25
- await fs.writeFile(this.filePath, JSON.stringify(entries), "utf8");
26
+ await writeFileAtomic(this.filePath, JSON.stringify(entries));
26
27
  }
27
28
  async readEntries() {
28
29
  try {
29
- const content = await fs.readFile(this.filePath, "utf8");
30
- return JSON.parse(content);
30
+ return (await Bun.file(this.filePath).json());
31
31
  }
32
32
  catch {
33
33
  return {};
@@ -93,7 +93,9 @@ class SqliteCacheStore {
93
93
  }
94
94
  ensureSchema() {
95
95
  try {
96
- const columns = this.db.prepare("PRAGMA table_info(versions);").all();
96
+ const columns = this.db
97
+ .prepare("PRAGMA table_info(versions);")
98
+ .all();
97
99
  const hasAvailableVersions = columns.some((column) => column.name === "available_versions");
98
100
  if (!hasAvailableVersions) {
99
101
  this.db.exec("ALTER TABLE versions ADD COLUMN available_versions TEXT;");
@@ -117,8 +119,8 @@ export class VersionCache {
117
119
  this.fallbackReason = fallbackReason;
118
120
  }
119
121
  static async create(customPath) {
120
- const basePath = customPath ?? path.join(os.homedir(), ".cache", "rainy-updates");
121
- if (process.env.RAINY_UPDATES_CACHE_BACKEND === "file") {
122
+ const basePath = customPath ?? getCacheDir();
123
+ if (readEnv("RAINY_UPDATES_CACHE_BACKEND") === "file") {
122
124
  const jsonPath = path.join(basePath, "cache.json");
123
125
  return new VersionCache(new FileCacheStore(jsonPath), "file", true, "forced via RAINY_UPDATES_CACHE_BACKEND=file");
124
126
  }
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import process from "node:process";
2
+ import { getRuntimeCwd } from "../../utils/runtime.js";
3
3
  const SEVERITY_LEVELS = ["critical", "high", "medium", "low"];
4
4
  const SOURCE_MODES = ["auto", "osv", "github", "all"];
5
5
  export function parseSeverity(value) {
@@ -10,7 +10,7 @@ export function parseSeverity(value) {
10
10
  }
11
11
  export function parseAuditArgs(args) {
12
12
  const options = {
13
- cwd: process.cwd(),
13
+ cwd: getRuntimeCwd(),
14
14
  workspace: false,
15
15
  severity: undefined,
16
16
  fix: false,
@@ -1,10 +1,9 @@
1
- import { spawn } from "node:child_process";
2
- import { promises as fs } from "node:fs";
3
- import path from "node:path";
4
1
  import { collectDependencies, readManifest, } from "../../parsers/package-json.js";
2
+ import { detectPackageManager as detectProjectPackageManager, resolvePackageManager, } from "../../pm/detect.js";
5
3
  import { discoverPackageDirs } from "../../workspace/discover.js";
6
4
  import { writeFileAtomic } from "../../utils/io.js";
7
5
  import { stableStringify } from "../../utils/stable-json.js";
6
+ import { writeStderr, writeStdout } from "../../utils/runtime.js";
8
7
  import { fetchAdvisories } from "./fetcher.js";
9
8
  import { resolveAuditTargets } from "./targets.js";
10
9
  import { filterBySeverity, buildPatchMap, renderAuditSourceHealth, renderAuditSummary, renderAuditTable, summarizeAdvisories, } from "./mapper.js";
@@ -55,7 +54,7 @@ export async function runAudit(options) {
55
54
  return result;
56
55
  }
57
56
  if (!options.silent) {
58
- process.stderr.write(`[audit] Querying ${describeSourceMode(options.sourceMode)} for ${targetResolution.targets.length} dependency version${targetResolution.targets.length === 1 ? "" : "s"}...\n`);
57
+ writeStderr(`[audit] Querying ${describeSourceMode(options.sourceMode)} for ${targetResolution.targets.length} dependency version${targetResolution.targets.length === 1 ? "" : "s"}...\n`);
59
58
  }
60
59
  const fetched = await fetchAdvisories(targetResolution.targets, {
61
60
  concurrency: options.concurrency,
@@ -81,12 +80,12 @@ export async function runAudit(options) {
81
80
  result.autoFixable = advisories.filter((a) => a.patchedVersion !== null).length;
82
81
  if (!options.silent) {
83
82
  if (options.reportFormat === "summary") {
84
- process.stdout.write(renderAuditSummary(result.packages) +
83
+ writeStdout(renderAuditSummary(result.packages) +
85
84
  renderAuditSourceHealth(result.sourceHealth) +
86
85
  "\n");
87
86
  }
88
87
  else if (options.reportFormat === "table" || !options.jsonFile) {
89
- process.stdout.write(renderAuditTable(advisories) +
88
+ writeStdout(renderAuditTable(advisories) +
90
89
  renderAuditSourceHealth(result.sourceHealth) +
91
90
  "\n");
92
91
  }
@@ -102,7 +101,7 @@ export async function runAudit(options) {
102
101
  warnings: result.warnings,
103
102
  }, 2) + "\n");
104
103
  if (!options.silent) {
105
- process.stderr.write(`[audit] JSON report written to ${options.jsonFile}\n`);
104
+ writeStderr(`[audit] JSON report written to ${options.jsonFile}\n`);
106
105
  }
107
106
  }
108
107
  if (options.fix && result.autoFixable > 0) {
@@ -122,40 +121,41 @@ async function applyFix(advisories, options) {
122
121
  const patchMap = buildPatchMap(advisories);
123
122
  if (patchMap.size === 0)
124
123
  return;
125
- const pm = await detectPackageManager(options.cwd, options.packageManager);
124
+ const detected = await detectProjectPackageManager(options.cwd);
125
+ const pm = resolvePackageManager(options.packageManager, detected);
126
126
  const installArgs = buildInstallArgs(pm, patchMap);
127
127
  const installCmd = `${pm} ${installArgs.join(" ")}`;
128
128
  if (options.dryRun) {
129
129
  if (!options.silent) {
130
- process.stderr.write(`[audit] --dry-run: would execute:\n ${installCmd}\n`);
130
+ writeStderr(`[audit] --dry-run: would execute:\n ${installCmd}\n`);
131
131
  if (options.commit) {
132
132
  const msg = buildCommitMessage(patchMap);
133
- process.stderr.write(`[audit] --dry-run: would commit:\n git commit -m "${msg}"\n`);
133
+ writeStderr(`[audit] --dry-run: would commit:\n git commit -m "${msg}"\n`);
134
134
  }
135
135
  }
136
136
  return;
137
137
  }
138
138
  if (!options.silent) {
139
- process.stderr.write(`[audit] Applying ${patchMap.size} fix(es)...\n`);
140
- process.stderr.write(` → ${installCmd}\n`);
139
+ writeStderr(`[audit] Applying ${patchMap.size} fix(es)...\n`);
140
+ writeStderr(` → ${installCmd}\n`);
141
141
  }
142
142
  try {
143
143
  await runCommand(pm, installArgs, options.cwd);
144
144
  }
145
145
  catch (err) {
146
146
  if (!options.silent) {
147
- process.stderr.write(`[audit] Install failed: ${String(err)}\n`);
147
+ writeStderr(`[audit] Install failed: ${String(err)}\n`);
148
148
  }
149
149
  return;
150
150
  }
151
151
  if (!options.silent) {
152
- process.stderr.write(`[audit] ✔ Patches applied successfully.\n`);
152
+ writeStderr(`[audit] ✔ Patches applied successfully.\n`);
153
153
  }
154
154
  if (options.commit) {
155
155
  await commitFix(patchMap, options.cwd, options.silent);
156
156
  }
157
157
  else if (!options.silent) {
158
- process.stderr.write(`[audit] Tip: run with --commit to automatically commit the changes.\n`);
158
+ writeStderr(`[audit] Tip: run with --commit to automatically commit the changes.\n`);
159
159
  }
160
160
  }
161
161
  function buildInstallArgs(pm, patchMap) {
@@ -205,48 +205,29 @@ function buildCommitMessage(patchMap) {
205
205
  const names = items.map(([n]) => n).join(", ");
206
206
  return `fix(security): patch ${items.length} vulnerabilities — ${names} (rup audit)`;
207
207
  }
208
- /** Detects the package manager in use by checking for lockfiles. */
209
- async function detectPackageManager(cwd, explicit) {
210
- if (explicit !== "auto")
211
- return explicit;
212
- const checks = [
213
- ["bun.lock", "bun"],
214
- ["bun.lockb", "bun"],
215
- ["pnpm-lock.yaml", "pnpm"],
216
- ["yarn.lock", "yarn"],
217
- ];
218
- for (const [lockfile, pm] of checks) {
219
- try {
220
- await fs.access(path.join(cwd, lockfile));
221
- return pm;
222
- }
223
- catch {
224
- // not found, try next
225
- }
226
- }
227
- return "npm"; // default
228
- }
229
208
  /** Spawns a subprocess, pipes stdio live to the terminal. */
230
209
  function runCommand(cmd, args, cwd, ignoreErrors = false) {
231
- return new Promise((resolve, reject) => {
232
- const child = spawn(cmd, args, {
233
- cwd,
234
- stdio: "inherit", // stream stdout/stderr live
235
- shell: process.platform === "win32",
236
- });
237
- child.on("close", (code) => {
210
+ return new Promise(async (resolve, reject) => {
211
+ try {
212
+ const proc = Bun.spawn([cmd, ...args], {
213
+ cwd,
214
+ stdin: "inherit",
215
+ stdout: "inherit",
216
+ stderr: "inherit",
217
+ });
218
+ const code = await proc.exited;
238
219
  if (code === 0 || ignoreErrors) {
239
220
  resolve();
240
221
  }
241
222
  else {
242
223
  reject(new Error(`${cmd} exited with code ${code}`));
243
224
  }
244
- });
245
- child.on("error", (err) => {
225
+ }
226
+ catch (err) {
246
227
  if (ignoreErrors)
247
228
  resolve();
248
229
  else
249
230
  reject(err);
250
- });
231
+ }
251
232
  });
252
233
  }