@rainy-updates/cli 0.5.7 → 0.6.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.
Files changed (105) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/README.md +90 -31
  3. package/dist/bin/cli.js +11 -126
  4. package/dist/bin/dispatch.js +35 -32
  5. package/dist/bin/help.js +79 -2
  6. package/dist/bin/main.d.ts +1 -0
  7. package/dist/bin/main.js +126 -0
  8. package/dist/cache/cache.js +13 -11
  9. package/dist/commands/audit/parser.js +38 -2
  10. package/dist/commands/audit/runner.js +41 -61
  11. package/dist/commands/audit/targets.js +13 -13
  12. package/dist/commands/bisect/oracle.js +31 -11
  13. package/dist/commands/bisect/parser.js +3 -3
  14. package/dist/commands/bisect/runner.js +16 -8
  15. package/dist/commands/changelog/fetcher.js +11 -5
  16. package/dist/commands/dashboard/parser.js +144 -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 +53 -4
  20. package/dist/commands/doctor/runner.js +2 -2
  21. package/dist/commands/ga/parser.js +43 -4
  22. package/dist/commands/ga/runner.js +22 -13
  23. package/dist/commands/health/parser.js +38 -2
  24. package/dist/commands/health/runner.js +5 -1
  25. package/dist/commands/hook/parser.d.ts +2 -0
  26. package/dist/commands/hook/parser.js +40 -0
  27. package/dist/commands/hook/runner.d.ts +2 -0
  28. package/dist/commands/hook/runner.js +174 -0
  29. package/dist/commands/licenses/parser.js +39 -0
  30. package/dist/commands/licenses/runner.js +9 -5
  31. package/dist/commands/resolve/graph/builder.js +5 -1
  32. package/dist/commands/resolve/parser.js +39 -0
  33. package/dist/commands/resolve/runner.js +14 -4
  34. package/dist/commands/review/parser.js +101 -4
  35. package/dist/commands/review/runner.js +31 -5
  36. package/dist/commands/snapshot/parser.js +39 -0
  37. package/dist/commands/snapshot/runner.js +21 -18
  38. package/dist/commands/snapshot/store.d.ts +0 -12
  39. package/dist/commands/snapshot/store.js +26 -38
  40. package/dist/commands/unused/parser.js +39 -0
  41. package/dist/commands/unused/runner.js +10 -8
  42. package/dist/commands/unused/scanner.d.ts +2 -1
  43. package/dist/commands/unused/scanner.js +65 -52
  44. package/dist/config/loader.d.ts +2 -2
  45. package/dist/config/loader.js +2 -5
  46. package/dist/config/policy.js +20 -11
  47. package/dist/core/analysis/run-silenced.js +0 -1
  48. package/dist/core/artifacts.js +6 -5
  49. package/dist/core/baseline.js +3 -5
  50. package/dist/core/check.js +7 -3
  51. package/dist/core/ci.js +52 -1
  52. package/dist/core/decision-plan.d.ts +14 -0
  53. package/dist/core/decision-plan.js +107 -0
  54. package/dist/core/doctor/result.js +8 -5
  55. package/dist/core/fix-pr-batch.js +38 -28
  56. package/dist/core/fix-pr.js +27 -24
  57. package/dist/core/init-ci.js +34 -28
  58. package/dist/core/options.d.ts +4 -1
  59. package/dist/core/options.js +152 -4
  60. package/dist/core/review-model.js +3 -0
  61. package/dist/core/summary.js +6 -0
  62. package/dist/core/upgrade.js +64 -2
  63. package/dist/core/verification.d.ts +2 -0
  64. package/dist/core/verification.js +108 -0
  65. package/dist/core/warm-cache.js +7 -3
  66. package/dist/generated/version.d.ts +1 -0
  67. package/dist/generated/version.js +2 -0
  68. package/dist/git/scope.d.ts +19 -0
  69. package/dist/git/scope.js +167 -0
  70. package/dist/index.d.ts +2 -1
  71. package/dist/index.js +1 -0
  72. package/dist/output/format.js +15 -0
  73. package/dist/output/github.js +6 -0
  74. package/dist/output/sarif.js +12 -18
  75. package/dist/parsers/package-json.js +2 -4
  76. package/dist/pm/detect.d.ts +40 -1
  77. package/dist/pm/detect.js +152 -9
  78. package/dist/pm/install.d.ts +3 -1
  79. package/dist/pm/install.js +18 -17
  80. package/dist/registry/npm.js +34 -76
  81. package/dist/rup +0 -0
  82. package/dist/types/index.d.ts +134 -5
  83. package/dist/ui/tui.d.ts +4 -1
  84. package/dist/ui/tui.js +156 -67
  85. package/dist/utils/io.js +5 -6
  86. package/dist/utils/lockfile.js +24 -19
  87. package/dist/utils/runtime-paths.d.ts +4 -0
  88. package/dist/utils/runtime-paths.js +35 -0
  89. package/dist/utils/runtime.d.ts +7 -0
  90. package/dist/utils/runtime.js +32 -0
  91. package/dist/workspace/discover.d.ts +7 -1
  92. package/dist/workspace/discover.js +67 -54
  93. package/package.json +24 -19
  94. package/dist/ui/dashboard/DashboardTUI.d.ts +0 -6
  95. package/dist/ui/dashboard/DashboardTUI.js +0 -34
  96. package/dist/ui/dashboard/components/DetailPanel.d.ts +0 -4
  97. package/dist/ui/dashboard/components/DetailPanel.js +0 -30
  98. package/dist/ui/dashboard/components/Footer.d.ts +0 -4
  99. package/dist/ui/dashboard/components/Footer.js +0 -9
  100. package/dist/ui/dashboard/components/Header.d.ts +0 -4
  101. package/dist/ui/dashboard/components/Header.js +0 -12
  102. package/dist/ui/dashboard/components/Sidebar.d.ts +0 -4
  103. package/dist/ui/dashboard/components/Sidebar.js +0 -23
  104. package/dist/ui/dashboard/store.d.ts +0 -34
  105. package/dist/ui/dashboard/store.js +0 -148
@@ -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 { buildAddInvocation, createPackageManagerProfile, detectPackageManagerDetails, } 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";
@@ -29,7 +28,15 @@ export async function runAudit(options) {
29
28
  unresolved: 0,
30
29
  },
31
30
  };
32
- const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
31
+ const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
32
+ git: options,
33
+ includeKinds: [
34
+ "dependencies",
35
+ "devDependencies",
36
+ "optionalDependencies",
37
+ ],
38
+ includeDependents: options.affected === true,
39
+ });
33
40
  const depsByDir = new Map();
34
41
  for (const dir of packageDirs) {
35
42
  let manifest;
@@ -55,7 +62,7 @@ export async function runAudit(options) {
55
62
  return result;
56
63
  }
57
64
  if (!options.silent) {
58
- process.stderr.write(`[audit] Querying ${describeSourceMode(options.sourceMode)} for ${targetResolution.targets.length} dependency version${targetResolution.targets.length === 1 ? "" : "s"}...\n`);
65
+ writeStderr(`[audit] Querying ${describeSourceMode(options.sourceMode)} for ${targetResolution.targets.length} dependency version${targetResolution.targets.length === 1 ? "" : "s"}...\n`);
59
66
  }
60
67
  const fetched = await fetchAdvisories(targetResolution.targets, {
61
68
  concurrency: options.concurrency,
@@ -81,12 +88,12 @@ export async function runAudit(options) {
81
88
  result.autoFixable = advisories.filter((a) => a.patchedVersion !== null).length;
82
89
  if (!options.silent) {
83
90
  if (options.reportFormat === "summary") {
84
- process.stdout.write(renderAuditSummary(result.packages) +
91
+ writeStdout(renderAuditSummary(result.packages) +
85
92
  renderAuditSourceHealth(result.sourceHealth) +
86
93
  "\n");
87
94
  }
88
95
  else if (options.reportFormat === "table" || !options.jsonFile) {
89
- process.stdout.write(renderAuditTable(advisories) +
96
+ writeStdout(renderAuditTable(advisories) +
90
97
  renderAuditSourceHealth(result.sourceHealth) +
91
98
  "\n");
92
99
  }
@@ -102,7 +109,7 @@ export async function runAudit(options) {
102
109
  warnings: result.warnings,
103
110
  }, 2) + "\n");
104
111
  if (!options.silent) {
105
- process.stderr.write(`[audit] JSON report written to ${options.jsonFile}\n`);
112
+ writeStderr(`[audit] JSON report written to ${options.jsonFile}\n`);
106
113
  }
107
114
  }
108
115
  if (options.fix && result.autoFixable > 0) {
@@ -122,54 +129,46 @@ async function applyFix(advisories, options) {
122
129
  const patchMap = buildPatchMap(advisories);
123
130
  if (patchMap.size === 0)
124
131
  return;
125
- const pm = await detectPackageManager(options.cwd, options.packageManager);
126
- const installArgs = buildInstallArgs(pm, patchMap);
127
- const installCmd = `${pm} ${installArgs.join(" ")}`;
132
+ const detected = await detectPackageManagerDetails(options.cwd);
133
+ const profile = createPackageManagerProfile(options.packageManager, detected);
134
+ const install = buildInstallArgs(profile, patchMap);
135
+ const installCmd = install.display;
128
136
  if (options.dryRun) {
129
137
  if (!options.silent) {
130
- process.stderr.write(`[audit] --dry-run: would execute:\n ${installCmd}\n`);
138
+ writeStderr(`[audit] --dry-run: would execute:\n ${installCmd}\n`);
131
139
  if (options.commit) {
132
140
  const msg = buildCommitMessage(patchMap);
133
- process.stderr.write(`[audit] --dry-run: would commit:\n git commit -m "${msg}"\n`);
141
+ writeStderr(`[audit] --dry-run: would commit:\n git commit -m "${msg}"\n`);
134
142
  }
135
143
  }
136
144
  return;
137
145
  }
138
146
  if (!options.silent) {
139
- process.stderr.write(`[audit] Applying ${patchMap.size} fix(es)...\n`);
140
- process.stderr.write(` → ${installCmd}\n`);
147
+ writeStderr(`[audit] Applying ${patchMap.size} fix(es)...\n`);
148
+ writeStderr(` → ${installCmd}\n`);
141
149
  }
142
150
  try {
143
- await runCommand(pm, installArgs, options.cwd);
151
+ await runCommand(install.command, install.args, options.cwd);
144
152
  }
145
153
  catch (err) {
146
154
  if (!options.silent) {
147
- process.stderr.write(`[audit] Install failed: ${String(err)}\n`);
155
+ writeStderr(`[audit] Install failed: ${String(err)}\n`);
148
156
  }
149
157
  return;
150
158
  }
151
159
  if (!options.silent) {
152
- process.stderr.write(`[audit] ✔ Patches applied successfully.\n`);
160
+ writeStderr(`[audit] ✔ Patches applied successfully.\n`);
153
161
  }
154
162
  if (options.commit) {
155
163
  await commitFix(patchMap, options.cwd, options.silent);
156
164
  }
157
165
  else if (!options.silent) {
158
- process.stderr.write(`[audit] Tip: run with --commit to automatically commit the changes.\n`);
166
+ writeStderr(`[audit] Tip: run with --commit to automatically commit the changes.\n`);
159
167
  }
160
168
  }
161
- function buildInstallArgs(pm, patchMap) {
169
+ function buildInstallArgs(profile, patchMap) {
162
170
  const packages = [...patchMap.entries()].map(([n, v]) => `${n}@${v}`);
163
- switch (pm) {
164
- case "pnpm":
165
- return ["add", ...packages];
166
- case "bun":
167
- return ["add", ...packages];
168
- case "yarn":
169
- return ["add", ...packages];
170
- default:
171
- return ["install", ...packages]; // npm
172
- }
171
+ return buildAddInvocation(profile, packages);
173
172
  }
174
173
  async function commitFix(patchMap, cwd, silent) {
175
174
  const msg = buildCommitMessage(patchMap);
@@ -205,48 +204,29 @@ function buildCommitMessage(patchMap) {
205
204
  const names = items.map(([n]) => n).join(", ");
206
205
  return `fix(security): patch ${items.length} vulnerabilities — ${names} (rup audit)`;
207
206
  }
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
207
  /** Spawns a subprocess, pipes stdio live to the terminal. */
230
208
  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) => {
209
+ return new Promise(async (resolve, reject) => {
210
+ try {
211
+ const proc = Bun.spawn([cmd, ...args], {
212
+ cwd,
213
+ stdin: "inherit",
214
+ stdout: "inherit",
215
+ stderr: "inherit",
216
+ });
217
+ const code = await proc.exited;
238
218
  if (code === 0 || ignoreErrors) {
239
219
  resolve();
240
220
  }
241
221
  else {
242
222
  reject(new Error(`${cmd} exited with code ${code}`));
243
223
  }
244
- });
245
- child.on("error", (err) => {
224
+ }
225
+ catch (err) {
246
226
  if (ignoreErrors)
247
227
  resolve();
248
228
  else
249
229
  reject(err);
250
- });
230
+ }
251
231
  });
252
232
  }
@@ -1,4 +1,3 @@
1
- import { promises as fs } from "node:fs";
2
1
  import path from "node:path";
3
2
  const LOCKFILE_PRIORITY = [
4
3
  "package-lock.json",
@@ -86,13 +85,9 @@ async function findNearestLockfiles(rootCwd, startDir) {
86
85
  while (true) {
87
86
  for (const fileName of LOCKFILE_PRIORITY) {
88
87
  const candidate = path.join(current, fileName);
89
- try {
90
- await fs.access(candidate);
88
+ if (await fileExists(candidate)) {
91
89
  found.push(candidate);
92
90
  }
93
- catch {
94
- // ignore missing
95
- }
96
91
  }
97
92
  if (current === rootCwd)
98
93
  break;
@@ -115,9 +110,6 @@ async function resolveFromPackageLock(lockfilePath, packageDir, packageName) {
115
110
  if (version)
116
111
  return version;
117
112
  }
118
- if (!relDir) {
119
- return parsed.dependencies?.[packageName]?.version ?? null;
120
- }
121
113
  return parsed.dependencies?.[packageName]?.version ?? null;
122
114
  }
123
115
  async function resolveFromPnpmLock(lockfilePath, packageDir, packageName) {
@@ -149,8 +141,8 @@ async function resolveFromBunLock(lockfilePath, packageDir, packageName) {
149
141
  async function readPackageLock(lockfilePath) {
150
142
  let promise = packageLockCache.get(lockfilePath);
151
143
  if (!promise) {
152
- promise = fs
153
- .readFile(lockfilePath, "utf8")
144
+ promise = Bun.file(lockfilePath)
145
+ .text()
154
146
  .then((content) => JSON.parse(content));
155
147
  packageLockCache.set(lockfilePath, promise);
156
148
  }
@@ -159,7 +151,7 @@ async function readPackageLock(lockfilePath) {
159
151
  async function readPnpmLock(lockfilePath) {
160
152
  let promise = pnpmLockCache.get(lockfilePath);
161
153
  if (!promise) {
162
- promise = fs.readFile(lockfilePath, "utf8").then(parsePnpmLock);
154
+ promise = Bun.file(lockfilePath).text().then(parsePnpmLock);
163
155
  pnpmLockCache.set(lockfilePath, promise);
164
156
  }
165
157
  return await promise;
@@ -167,7 +159,7 @@ async function readPnpmLock(lockfilePath) {
167
159
  async function readBunLock(lockfilePath) {
168
160
  let promise = bunLockCache.get(lockfilePath);
169
161
  if (!promise) {
170
- promise = fs.readFile(lockfilePath, "utf8").then(parseBunLock);
162
+ promise = Bun.file(lockfilePath).text().then(parseBunLock);
171
163
  bunLockCache.set(lockfilePath, promise);
172
164
  }
173
165
  return await promise;
@@ -312,3 +304,11 @@ function normalizePnpmVersion(value) {
312
304
  function trimYamlKey(value) {
313
305
  return value.trim().replace(/^['"]|['"]$/g, "");
314
306
  }
307
+ async function fileExists(filePath) {
308
+ try {
309
+ return await Bun.file(filePath).exists();
310
+ }
311
+ catch {
312
+ return false;
313
+ }
314
+ }
@@ -1,5 +1,5 @@
1
- import { spawn } from "node:child_process";
2
1
  import path from "node:path";
2
+ import { buildAddInvocation, createPackageManagerProfile, detectPackageManagerDetails, } from "../../pm/detect.js";
3
3
  /**
4
4
  * The "oracle" for bisect: installs a specific version of a package
5
5
  * into the project's node_modules (via the shell), then runs --cmd.
@@ -11,7 +11,12 @@ export async function bisectOracle(packageName, version, options) {
11
11
  process.stderr.write(`[bisect:dry-run] Would test ${packageName}@${version}\n`);
12
12
  return "skip";
13
13
  }
14
- const installResult = await runShell(`npm install --no-save ${packageName}@${version}`, options.cwd);
14
+ const detected = await detectPackageManagerDetails(options.cwd);
15
+ const profile = createPackageManagerProfile("auto", detected, "bun");
16
+ const installResult = await runCommand(buildAddInvocation(profile, [`${packageName}@${version}`], {
17
+ exact: true,
18
+ noSave: true,
19
+ }), options.cwd);
15
20
  if (installResult !== 0) {
16
21
  process.stderr.write(`[bisect] Failed to install ${packageName}@${version}, skipping.\n`);
17
22
  return "skip";
@@ -22,15 +27,30 @@ export async function bisectOracle(packageName, version, options) {
22
27
  process.stderr.write(`[bisect] ${packageName}@${version} → ${outcome}\n`);
23
28
  return outcome;
24
29
  }
25
- function runShell(command, cwd) {
26
- return new Promise((resolve) => {
27
- const [bin, ...args] = command.split(" ");
28
- const child = spawn(bin, args, {
30
+ async function runShell(command, cwd) {
31
+ try {
32
+ const shellCmd = process.env.SHELL || "sh";
33
+ const proc = Bun.spawn([shellCmd, "-c", command], {
29
34
  cwd: path.resolve(cwd),
30
- shell: true,
31
- stdio: "pipe",
35
+ stdout: "pipe",
36
+ stderr: "pipe",
32
37
  });
33
- child.on("close", (code) => resolve(code ?? 1));
34
- child.on("error", () => resolve(1));
35
- });
38
+ return await proc.exited;
39
+ }
40
+ catch {
41
+ return 1;
42
+ }
43
+ }
44
+ async function runCommand(command, cwd) {
45
+ try {
46
+ const proc = Bun.spawn([command.command, ...command.args], {
47
+ cwd: path.resolve(cwd),
48
+ stdout: "pipe",
49
+ stderr: "pipe",
50
+ });
51
+ return await proc.exited;
52
+ }
53
+ catch {
54
+ return 1;
55
+ }
36
56
  }
@@ -1,11 +1,11 @@
1
1
  import path from "node:path";
2
- import process from "node:process";
2
+ import { getRuntimeCwd } from "../../utils/runtime.js";
3
3
  export function parseBisectArgs(args) {
4
4
  const options = {
5
- cwd: process.cwd(),
5
+ cwd: getRuntimeCwd(),
6
6
  packageName: "",
7
7
  versionRange: undefined,
8
- testCommand: "npm test",
8
+ testCommand: "",
9
9
  concurrency: 4,
10
10
  registryTimeoutMs: 8000,
11
11
  cacheTtlSeconds: 3600,
@@ -1,26 +1,34 @@
1
+ import { buildTestCommand, createPackageManagerProfile, detectPackageManagerDetails, } from "../../pm/detect.js";
1
2
  import { fetchBisectVersions, bisectVersions } from "./engine.js";
2
3
  /**
3
4
  * Entry point for the `bisect` command. Lazy-loaded by cli.ts.
4
5
  * Fully isolated: does NOT import anything from core/options.ts.
5
6
  */
6
7
  export async function runBisect(options) {
7
- process.stderr.write(`\n[bisect] Fetching available versions for ${options.packageName}...\n`);
8
- const versions = await fetchBisectVersions(options);
8
+ const detected = await detectPackageManagerDetails(options.cwd);
9
+ const profile = createPackageManagerProfile("auto", detected, "bun");
10
+ const runtimeOptions = {
11
+ ...options,
12
+ testCommand: options.testCommand ||
13
+ buildTestCommand(profile),
14
+ };
15
+ process.stderr.write(`\n[bisect] Fetching available versions for ${runtimeOptions.packageName}...\n`);
16
+ const versions = await fetchBisectVersions(runtimeOptions);
9
17
  if (versions.length === 0) {
10
- throw new Error(`No versions found for package "${options.packageName}".`);
18
+ throw new Error(`No versions found for package "${runtimeOptions.packageName}".`);
11
19
  }
12
20
  process.stderr.write(`[bisect] Found ${versions.length} versions. Starting binary search...\n`);
13
- if (options.versionRange) {
14
- process.stderr.write(`[bisect] Range: ${options.versionRange}\n`);
21
+ if (runtimeOptions.versionRange) {
22
+ process.stderr.write(`[bisect] Range: ${runtimeOptions.versionRange}\n`);
15
23
  }
16
- const result = await bisectVersions(versions, options);
24
+ const result = await bisectVersions(versions, runtimeOptions);
17
25
  if (result.breakingVersion) {
18
- process.stdout.write(`\n✖ Break introduced in ${options.packageName}@${result.breakingVersion}\n` +
26
+ process.stdout.write(`\n✖ Break introduced in ${runtimeOptions.packageName}@${result.breakingVersion}\n` +
19
27
  ` Last good version: ${result.lastGoodVersion ?? "none"}\n` +
20
28
  ` Tested: ${result.totalVersionsTested} versions in ${result.iterations} iterations\n`);
21
29
  }
22
30
  else {
23
- process.stdout.write(`\n✔ No breaking version found for ${options.packageName} (all versions passed).\n` +
31
+ process.stdout.write(`\n✔ No breaking version found for ${runtimeOptions.packageName} (all versions passed).\n` +
24
32
  ` Tested: ${result.totalVersionsTested} versions\n`);
25
33
  }
26
34
  return result;
@@ -1,12 +1,12 @@
1
- import os from "node:os";
1
+ import { mkdir } from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { promises as fs } from "node:fs";
3
+ import { getCacheDir } from "../../utils/runtime-paths.js";
4
4
  const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
5
5
  class ChangelogCache {
6
6
  db = null;
7
7
  dbPath;
8
8
  constructor() {
9
- const basePath = path.join(os.homedir(), ".cache", "rainy-updates");
9
+ const basePath = getCacheDir();
10
10
  this.dbPath = path.join(basePath, "cache.db");
11
11
  }
12
12
  async init() {
@@ -14,7 +14,7 @@ class ChangelogCache {
14
14
  return;
15
15
  try {
16
16
  if (typeof Bun !== "undefined") {
17
- await fs.mkdir(path.dirname(this.dbPath), { recursive: true });
17
+ await mkdir(path.dirname(this.dbPath), { recursive: true });
18
18
  const mod = await import("bun:sqlite");
19
19
  this.db = new mod.Database(this.dbPath, { create: true });
20
20
  this.db.exec(`
@@ -111,7 +111,7 @@ export async function fetchChangelog(packageName, repositoryUrl) {
111
111
  if (contentsRes.ok) {
112
112
  const fileContent = await contentsRes.json();
113
113
  if (fileContent.content && fileContent.encoding === "base64") {
114
- content = Buffer.from(fileContent.content, "base64").toString("utf-8");
114
+ content = decodeBase64Utf8(fileContent.content);
115
115
  }
116
116
  }
117
117
  }
@@ -128,3 +128,9 @@ export async function fetchChangelog(packageName, repositoryUrl) {
128
128
  return null;
129
129
  }
130
130
  }
131
+ function decodeBase64Utf8(value) {
132
+ const normalized = value.replace(/\s+/g, "");
133
+ const binary = atob(normalized);
134
+ const bytes = Uint8Array.from(binary, (char) => char.charCodeAt(0));
135
+ return new TextDecoder().decode(bytes);
136
+ }
@@ -1,7 +1,10 @@
1
+ import path from "node:path";
1
2
  export function parseDashboardArgs(args) {
2
3
  const options = {
3
4
  cwd: process.cwd(),
4
5
  target: "latest",
6
+ filter: undefined,
7
+ reject: undefined,
5
8
  includeKinds: [
6
9
  "dependencies",
7
10
  "devDependencies",
@@ -12,19 +15,49 @@ export function parseDashboardArgs(args) {
12
15
  ci: false,
13
16
  format: "table",
14
17
  workspace: false,
18
+ jsonFile: undefined,
19
+ githubOutputFile: undefined,
20
+ sarifFile: undefined,
15
21
  concurrency: 16,
16
22
  registryTimeoutMs: 8000,
17
23
  registryRetries: 3,
18
24
  offline: false,
19
25
  stream: false,
26
+ policyFile: undefined,
27
+ prReportFile: undefined,
28
+ failOn: "none",
29
+ maxUpdates: undefined,
30
+ fixPr: false,
31
+ fixBranch: "chore/rainy-updates",
32
+ fixCommitMessage: undefined,
33
+ fixDryRun: false,
34
+ fixPrNoCheckout: false,
35
+ fixPrBatchSize: undefined,
36
+ noPrReport: false,
20
37
  logLevel: "info",
21
38
  groupBy: "none",
39
+ groupMax: undefined,
40
+ cooldownDays: undefined,
41
+ prLimit: undefined,
22
42
  onlyChanged: false,
43
+ affected: false,
44
+ staged: false,
45
+ baseRef: undefined,
46
+ headRef: undefined,
47
+ sinceRef: undefined,
23
48
  ciProfile: "minimal",
24
49
  lockfileMode: "preserve",
25
50
  interactive: true,
26
51
  showImpact: false,
27
52
  showHomepage: true,
53
+ mode: "review",
54
+ focus: "all",
55
+ applySelected: false,
56
+ decisionPlanFile: undefined,
57
+ verify: "none",
58
+ testCommand: undefined,
59
+ verificationReportFile: undefined,
60
+ ciGate: "check",
28
61
  };
29
62
  for (let i = 0; i < args.length; i++) {
30
63
  const arg = args[i];
@@ -44,16 +77,126 @@ export function parseDashboardArgs(args) {
44
77
  if (arg === "--view") {
45
78
  throw new Error("Missing value for --view");
46
79
  }
80
+ if (arg === "--mode" && nextArg) {
81
+ if (nextArg === "check" ||
82
+ nextArg === "review" ||
83
+ nextArg === "upgrade") {
84
+ options.mode = nextArg;
85
+ i++;
86
+ continue;
87
+ }
88
+ throw new Error(`Invalid --mode: ${nextArg}`);
89
+ }
90
+ if (arg === "--mode") {
91
+ throw new Error("Missing value for --mode");
92
+ }
93
+ if (arg === "--focus" && nextArg) {
94
+ if (nextArg === "all" ||
95
+ nextArg === "security" ||
96
+ nextArg === "risk" ||
97
+ nextArg === "major" ||
98
+ nextArg === "blocked" ||
99
+ nextArg === "workspace") {
100
+ options.focus = nextArg;
101
+ i++;
102
+ continue;
103
+ }
104
+ throw new Error(`Invalid --focus: ${nextArg}`);
105
+ }
106
+ if (arg === "--focus") {
107
+ throw new Error("Missing value for --focus");
108
+ }
109
+ if (arg === "--apply-selected") {
110
+ options.applySelected = true;
111
+ continue;
112
+ }
113
+ if (arg === "--verify" && nextArg) {
114
+ if (nextArg === "none" ||
115
+ nextArg === "install" ||
116
+ nextArg === "test" ||
117
+ nextArg === "install,test") {
118
+ options.verify = nextArg;
119
+ i++;
120
+ continue;
121
+ }
122
+ throw new Error(`Invalid --verify: ${nextArg}`);
123
+ }
124
+ if (arg === "--verify") {
125
+ throw new Error("Missing value for --verify");
126
+ }
127
+ if (arg === "--test-command" && nextArg) {
128
+ options.testCommand = nextArg;
129
+ i++;
130
+ continue;
131
+ }
132
+ if (arg === "--test-command") {
133
+ throw new Error("Missing value for --test-command");
134
+ }
135
+ if (arg === "--verification-report-file" && nextArg) {
136
+ options.verificationReportFile = path.resolve(options.cwd, nextArg);
137
+ i++;
138
+ continue;
139
+ }
140
+ if (arg === "--verification-report-file") {
141
+ throw new Error("Missing value for --verification-report-file");
142
+ }
143
+ if (arg === "--plan-file" && nextArg) {
144
+ options.decisionPlanFile = path.resolve(options.cwd, nextArg);
145
+ i++;
146
+ continue;
147
+ }
148
+ if (arg === "--plan-file") {
149
+ throw new Error("Missing value for --plan-file");
150
+ }
47
151
  // Pass through common workspace / cwd args
48
152
  if (arg === "--workspace") {
49
153
  options.workspace = true;
50
154
  continue;
51
155
  }
156
+ if (arg === "--only-changed") {
157
+ options.onlyChanged = true;
158
+ continue;
159
+ }
160
+ if (arg === "--affected") {
161
+ options.affected = true;
162
+ continue;
163
+ }
164
+ if (arg === "--staged") {
165
+ options.staged = true;
166
+ continue;
167
+ }
168
+ if (arg === "--base" && nextArg) {
169
+ options.baseRef = nextArg;
170
+ i++;
171
+ continue;
172
+ }
173
+ if (arg === "--base") {
174
+ throw new Error("Missing value for --base");
175
+ }
176
+ if (arg === "--head" && nextArg) {
177
+ options.headRef = nextArg;
178
+ i++;
179
+ continue;
180
+ }
181
+ if (arg === "--head") {
182
+ throw new Error("Missing value for --head");
183
+ }
184
+ if (arg === "--since" && nextArg) {
185
+ options.sinceRef = nextArg;
186
+ i++;
187
+ continue;
188
+ }
189
+ if (arg === "--since") {
190
+ throw new Error("Missing value for --since");
191
+ }
52
192
  if (arg === "--cwd" && nextArg) {
53
- options.cwd = nextArg;
193
+ options.cwd = path.resolve(nextArg);
54
194
  i++;
55
195
  continue;
56
196
  }
197
+ if (arg.startsWith("-")) {
198
+ throw new Error(`Unknown dashboard option: ${arg}`);
199
+ }
57
200
  }
58
201
  return options;
59
202
  }
@@ -1,2 +1,2 @@
1
- import type { DashboardOptions, DashboardResult } from "../../types/index.js";
2
- export declare function runDashboard(options: DashboardOptions): Promise<DashboardResult>;
1
+ import type { DashboardOptions, DashboardResult, ReviewResult } from "../../types/index.js";
2
+ export declare function runDashboard(options: DashboardOptions, prebuiltReview?: ReviewResult): Promise<DashboardResult>;