@rainy-updates/cli 0.6.0 → 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 (55) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/bin/cli.js +12 -127
  3. package/dist/bin/dispatch.js +6 -0
  4. package/dist/bin/help.js +47 -0
  5. package/dist/bin/main.d.ts +1 -0
  6. package/dist/bin/main.js +126 -0
  7. package/dist/commands/audit/parser.js +36 -0
  8. package/dist/commands/audit/runner.js +17 -18
  9. package/dist/commands/bisect/oracle.js +18 -15
  10. package/dist/commands/bisect/runner.js +4 -3
  11. package/dist/commands/dashboard/parser.js +41 -0
  12. package/dist/commands/doctor/parser.js +44 -0
  13. package/dist/commands/ga/parser.js +39 -0
  14. package/dist/commands/ga/runner.js +10 -7
  15. package/dist/commands/health/parser.js +36 -0
  16. package/dist/commands/health/runner.js +5 -1
  17. package/dist/commands/hook/parser.d.ts +2 -0
  18. package/dist/commands/hook/parser.js +40 -0
  19. package/dist/commands/hook/runner.d.ts +2 -0
  20. package/dist/commands/hook/runner.js +174 -0
  21. package/dist/commands/licenses/parser.js +39 -0
  22. package/dist/commands/licenses/runner.js +5 -1
  23. package/dist/commands/resolve/graph/builder.js +5 -1
  24. package/dist/commands/resolve/parser.js +39 -0
  25. package/dist/commands/resolve/runner.js +5 -0
  26. package/dist/commands/review/parser.js +44 -0
  27. package/dist/commands/snapshot/parser.js +39 -0
  28. package/dist/commands/snapshot/runner.js +4 -1
  29. package/dist/commands/unused/parser.js +39 -0
  30. package/dist/commands/unused/runner.js +4 -1
  31. package/dist/commands/unused/scanner.d.ts +2 -1
  32. package/dist/commands/unused/scanner.js +60 -44
  33. package/dist/core/check.js +5 -1
  34. package/dist/core/init-ci.js +28 -26
  35. package/dist/core/options.d.ts +4 -1
  36. package/dist/core/options.js +57 -0
  37. package/dist/core/verification.js +8 -6
  38. package/dist/core/warm-cache.js +5 -1
  39. package/dist/generated/version.d.ts +1 -0
  40. package/dist/generated/version.js +2 -0
  41. package/dist/git/scope.d.ts +19 -0
  42. package/dist/git/scope.js +167 -0
  43. package/dist/index.d.ts +2 -1
  44. package/dist/index.js +1 -0
  45. package/dist/output/sarif.js +2 -8
  46. package/dist/pm/detect.d.ts +37 -0
  47. package/dist/pm/detect.js +133 -2
  48. package/dist/pm/install.d.ts +2 -1
  49. package/dist/pm/install.js +7 -5
  50. package/dist/rup +0 -0
  51. package/dist/types/index.d.ts +58 -0
  52. package/dist/ui/tui.js +152 -64
  53. package/dist/workspace/discover.d.ts +7 -1
  54. package/dist/workspace/discover.js +12 -3
  55. package/package.json +10 -5
@@ -40,6 +40,11 @@ export function parseDashboardArgs(args) {
40
40
  cooldownDays: undefined,
41
41
  prLimit: undefined,
42
42
  onlyChanged: false,
43
+ affected: false,
44
+ staged: false,
45
+ baseRef: undefined,
46
+ headRef: undefined,
47
+ sinceRef: undefined,
43
48
  ciProfile: "minimal",
44
49
  lockfileMode: "preserve",
45
50
  interactive: true,
@@ -148,6 +153,42 @@ export function parseDashboardArgs(args) {
148
153
  options.workspace = true;
149
154
  continue;
150
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
+ }
151
192
  if (arg === "--cwd" && nextArg) {
152
193
  options.cwd = path.resolve(nextArg);
153
194
  i++;
@@ -36,6 +36,11 @@ export function parseDoctorArgs(args) {
36
36
  cooldownDays: undefined,
37
37
  prLimit: undefined,
38
38
  onlyChanged: false,
39
+ affected: false,
40
+ staged: false,
41
+ baseRef: undefined,
42
+ headRef: undefined,
43
+ sinceRef: undefined,
39
44
  ciProfile: "minimal",
40
45
  lockfileMode: "preserve",
41
46
  interactive: false,
@@ -64,6 +69,39 @@ export function parseDoctorArgs(args) {
64
69
  options.workspace = true;
65
70
  continue;
66
71
  }
72
+ if (current === "--only-changed") {
73
+ options.onlyChanged = true;
74
+ continue;
75
+ }
76
+ if (current === "--affected") {
77
+ options.affected = true;
78
+ continue;
79
+ }
80
+ if (current === "--staged") {
81
+ options.staged = true;
82
+ continue;
83
+ }
84
+ if (current === "--base" && next) {
85
+ options.baseRef = next;
86
+ i += 1;
87
+ continue;
88
+ }
89
+ if (current === "--base")
90
+ throw new Error("Missing value for --base");
91
+ if (current === "--head" && next) {
92
+ options.headRef = next;
93
+ i += 1;
94
+ continue;
95
+ }
96
+ if (current === "--head")
97
+ throw new Error("Missing value for --head");
98
+ if (current === "--since" && next) {
99
+ options.sinceRef = next;
100
+ i += 1;
101
+ continue;
102
+ }
103
+ if (current === "--since")
104
+ throw new Error("Missing value for --since");
67
105
  if (current === "--verdict-only") {
68
106
  options.verdictOnly = true;
69
107
  continue;
@@ -104,6 +142,12 @@ Options:
104
142
  --include-changelog Include release note summaries in the aggregated review data
105
143
  --agent-report Print a prompt-ready remediation report for coding agents
106
144
  --workspace Scan all workspace packages
145
+ --only-changed Limit analysis to changed packages
146
+ --affected Include changed packages and their dependents
147
+ --staged Limit analysis to staged changes
148
+ --base <ref> Compare changes against a base git ref
149
+ --head <ref> Compare changes against a head git ref
150
+ --since <ref> Compare changes since a git ref
107
151
  --json-file <path> Write JSON doctor report to file
108
152
  --cwd <path>
109
153
  `.trimStart();
@@ -4,6 +4,11 @@ export function parseGaArgs(args) {
4
4
  const options = {
5
5
  cwd: getRuntimeCwd(),
6
6
  workspace: false,
7
+ affected: false,
8
+ staged: false,
9
+ baseRef: undefined,
10
+ headRef: undefined,
11
+ sinceRef: undefined,
7
12
  jsonFile: undefined,
8
13
  };
9
14
  for (let i = 0; i < args.length; i += 1) {
@@ -20,6 +25,35 @@ export function parseGaArgs(args) {
20
25
  options.workspace = true;
21
26
  continue;
22
27
  }
28
+ if (current === "--affected") {
29
+ options.affected = true;
30
+ continue;
31
+ }
32
+ if (current === "--staged") {
33
+ options.staged = true;
34
+ continue;
35
+ }
36
+ if (current === "--base" && next) {
37
+ options.baseRef = next;
38
+ i += 1;
39
+ continue;
40
+ }
41
+ if (current === "--base")
42
+ throw new Error("Missing value for --base");
43
+ if (current === "--head" && next) {
44
+ options.headRef = next;
45
+ i += 1;
46
+ continue;
47
+ }
48
+ if (current === "--head")
49
+ throw new Error("Missing value for --head");
50
+ if (current === "--since" && next) {
51
+ options.sinceRef = next;
52
+ i += 1;
53
+ continue;
54
+ }
55
+ if (current === "--since")
56
+ throw new Error("Missing value for --since");
23
57
  if (current === "--json-file" && next) {
24
58
  options.jsonFile = path.resolve(options.cwd, next);
25
59
  i += 1;
@@ -45,6 +79,11 @@ Usage:
45
79
 
46
80
  Options:
47
81
  --workspace Evaluate workspace package coverage
82
+ --affected Evaluate changed workspace packages and dependents
83
+ --staged Limit GA discovery to staged changes
84
+ --base <ref> Compare changes against a base git ref
85
+ --head <ref> Compare changes against a head git ref
86
+ --since <ref> Compare changes since a git ref
48
87
  --json-file <path> Write JSON GA report to file
49
88
  --cwd <path>
50
89
  `.trimStart();
@@ -1,21 +1,24 @@
1
1
  import path from "node:path";
2
2
  import { VersionCache } from "../../cache/cache.js";
3
- import { detectPackageManager } from "../../pm/detect.js";
3
+ import { detectPackageManagerDetails } from "../../pm/detect.js";
4
4
  import { discoverPackageDirs } from "../../workspace/discover.js";
5
5
  import { stableStringify } from "../../utils/stable-json.js";
6
6
  import { writeFileAtomic } from "../../utils/io.js";
7
7
  import { writeStdout } from "../../utils/runtime.js";
8
8
  export async function runGa(options) {
9
- const packageManager = await detectPackageManager(options.cwd);
10
- const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
9
+ const packageManager = await detectPackageManagerDetails(options.cwd);
10
+ const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
11
+ git: options,
12
+ includeDependents: options.affected === true,
13
+ });
11
14
  const cache = await VersionCache.create();
12
15
  const checks = [];
13
16
  checks.push({
14
17
  name: "package-manager",
15
- status: packageManager === "unknown" ? "warn" : "pass",
16
- detail: packageManager === "unknown"
18
+ status: packageManager.manager === "unknown" ? "warn" : "pass",
19
+ detail: packageManager.manager === "unknown"
17
20
  ? "No supported lockfile was detected. Rainy can still run, but runtime/package verification will fall back to generic defaults."
18
- : `Detected package manager: ${packageManager}. Runtime and verification can align with this package ecosystem, including Bun.`,
21
+ : `Detected package manager: ${packageManager.manager} via ${packageManager.source}${packageManager.lockfile ? ` (${packageManager.lockfile})` : ""}${packageManager.packageManagerField ? ` [${packageManager.packageManagerField}]` : ""}. Runtime and verification can align with this package ecosystem, including Bun.`,
19
22
  });
20
23
  checks.push({
21
24
  name: "workspace-discovery",
@@ -68,7 +71,7 @@ export async function runGa(options) {
68
71
  const result = {
69
72
  ready: errors.length === 0,
70
73
  projectPath: options.cwd,
71
- packageManager,
74
+ packageManager: packageManager.manager,
72
75
  workspacePackages: packageDirs.length,
73
76
  cacheBackend: cache.backend,
74
77
  checks,
@@ -4,6 +4,11 @@ export function parseHealthArgs(args) {
4
4
  const options = {
5
5
  cwd: getRuntimeCwd(),
6
6
  workspace: false,
7
+ affected: false,
8
+ staged: false,
9
+ baseRef: undefined,
10
+ headRef: undefined,
11
+ sinceRef: undefined,
7
12
  staleDays: 365,
8
13
  includeDeprecated: true,
9
14
  includeAlternatives: false,
@@ -28,6 +33,37 @@ export function parseHealthArgs(args) {
28
33
  index += 1;
29
34
  continue;
30
35
  }
36
+ if (current === "--affected") {
37
+ options.affected = true;
38
+ index += 1;
39
+ continue;
40
+ }
41
+ if (current === "--staged") {
42
+ options.staged = true;
43
+ index += 1;
44
+ continue;
45
+ }
46
+ if (current === "--base" && next) {
47
+ options.baseRef = next;
48
+ index += 2;
49
+ continue;
50
+ }
51
+ if (current === "--base")
52
+ throw new Error("Missing value for --base");
53
+ if (current === "--head" && next) {
54
+ options.headRef = next;
55
+ index += 2;
56
+ continue;
57
+ }
58
+ if (current === "--head")
59
+ throw new Error("Missing value for --head");
60
+ if (current === "--since" && next) {
61
+ options.sinceRef = next;
62
+ index += 2;
63
+ continue;
64
+ }
65
+ if (current === "--since")
66
+ throw new Error("Missing value for --since");
31
67
  if (current === "--stale" && next) {
32
68
  // Accept "12m" → 365, "6m" → 180, "365d" → 365, or plain number
33
69
  const match = next.match(/^(\d+)(m|d)?$/);
@@ -89,7 +89,11 @@ export async function runHealth(options) {
89
89
  errors: [],
90
90
  warnings: [],
91
91
  };
92
- const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
92
+ const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
93
+ git: options,
94
+ includeKinds: ["dependencies", "devDependencies", "optionalDependencies"],
95
+ includeDependents: options.affected === true,
96
+ });
93
97
  const versionMap = new Map();
94
98
  for (const dir of packageDirs) {
95
99
  let manifest;
@@ -0,0 +1,2 @@
1
+ import type { HookOptions } from "../../types/index.js";
2
+ export declare function parseHookArgs(args: string[]): HookOptions;
@@ -0,0 +1,40 @@
1
+ import path from "node:path";
2
+ export function parseHookArgs(args) {
3
+ let cwd = process.cwd();
4
+ let action = "doctor";
5
+ for (let index = 0; index < args.length; index += 1) {
6
+ const current = args[index];
7
+ const next = args[index + 1];
8
+ if ((current === "install" || current === "uninstall" || current === "doctor") && index === 0) {
9
+ action = current;
10
+ continue;
11
+ }
12
+ if (current === "--cwd" && next) {
13
+ cwd = path.resolve(next);
14
+ index += 1;
15
+ continue;
16
+ }
17
+ if (current === "--cwd") {
18
+ throw new Error("Missing value for --cwd");
19
+ }
20
+ if (current === "--help" || current === "-h") {
21
+ process.stdout.write(HOOK_HELP);
22
+ process.exit(0);
23
+ }
24
+ if (current.startsWith("-")) {
25
+ throw new Error(`Unknown hook option: ${current}`);
26
+ }
27
+ throw new Error(`Unexpected hook argument: ${current}`);
28
+ }
29
+ return { cwd, action };
30
+ }
31
+ const HOOK_HELP = `
32
+ rup hook — Install or inspect Rainy-managed git hooks
33
+
34
+ Usage:
35
+ rup hook <install|uninstall|doctor> [options]
36
+
37
+ Options:
38
+ --cwd <path> Working directory (default: cwd)
39
+ --help Show this help
40
+ `.trimStart();
@@ -0,0 +1,2 @@
1
+ import type { HookOptions, HookResult } from "../../types/index.js";
2
+ export declare function runHook(options: HookOptions): Promise<HookResult>;
@@ -0,0 +1,174 @@
1
+ import { chmod, mkdir, unlink } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { writeStdout } from "../../utils/runtime.js";
4
+ const MANAGED_MARKER = "# rainy-updates managed hook";
5
+ const HOOKS = [
6
+ {
7
+ name: "pre-commit",
8
+ command: '$(resolve_rup) unused --workspace --staged && $(resolve_rup) resolve --workspace --staged',
9
+ },
10
+ {
11
+ name: "pre-push",
12
+ command: '$(resolve_rup) audit --workspace --affected --report summary',
13
+ },
14
+ ];
15
+ export async function runHook(options) {
16
+ const hookDir = await resolveHookDir(options.cwd);
17
+ const result = {
18
+ action: options.action,
19
+ hookDir,
20
+ installed: [],
21
+ removed: [],
22
+ checked: [],
23
+ errors: [],
24
+ warnings: [],
25
+ };
26
+ if (options.action === "install") {
27
+ await mkdir(hookDir, { recursive: true });
28
+ for (const hook of HOOKS) {
29
+ const hookPath = path.join(hookDir, hook.name);
30
+ const existing = await readHookState(hookPath);
31
+ if (existing.status === "foreign") {
32
+ result.warnings.push(`Skipped ${hook.name}: existing hook is not Rainy-managed.`);
33
+ result.checked.push({ name: hook.name, status: "foreign" });
34
+ continue;
35
+ }
36
+ await Bun.write(hookPath, renderHookScript(hook.command));
37
+ await makeExecutable(hookPath);
38
+ result.installed.push(hook.name);
39
+ result.checked.push({ name: hook.name, status: "managed" });
40
+ }
41
+ }
42
+ else if (options.action === "uninstall") {
43
+ for (const hook of HOOKS) {
44
+ const hookPath = path.join(hookDir, hook.name);
45
+ const existing = await readHookState(hookPath);
46
+ if (existing.status === "managed") {
47
+ await unlink(hookPath).catch(() => undefined);
48
+ result.removed.push(hook.name);
49
+ }
50
+ else if (existing.status === "foreign") {
51
+ result.warnings.push(`Left ${hook.name} untouched: existing hook is not Rainy-managed.`);
52
+ }
53
+ result.checked.push({ name: hook.name, status: existing.status });
54
+ }
55
+ }
56
+ else {
57
+ for (const hook of HOOKS) {
58
+ const hookPath = path.join(hookDir, hook.name);
59
+ const existing = await readHookState(hookPath);
60
+ result.checked.push({ name: hook.name, status: existing.status });
61
+ if (existing.status === "foreign") {
62
+ result.warnings.push(`${hook.name} exists but is not Rainy-managed.`);
63
+ }
64
+ }
65
+ }
66
+ writeStdout(renderHookResult(result) + "\n");
67
+ return result;
68
+ }
69
+ async function resolveHookDir(cwd) {
70
+ const resolved = await runGit(cwd, ["rev-parse", "--git-path", "hooks"]);
71
+ if (!resolved.ok) {
72
+ throw new Error(`Unable to resolve git hooks directory: ${resolved.error}`);
73
+ }
74
+ const hookDir = resolved.stdout.trim();
75
+ if (hookDir.length === 0) {
76
+ throw new Error("Git hooks directory could not be determined.");
77
+ }
78
+ return path.resolve(cwd, hookDir);
79
+ }
80
+ async function readHookState(hookPath) {
81
+ try {
82
+ const file = Bun.file(hookPath);
83
+ if (!(await file.exists())) {
84
+ return { status: "missing" };
85
+ }
86
+ const content = await file.text();
87
+ return content.includes(MANAGED_MARKER)
88
+ ? { status: "managed", content }
89
+ : { status: "foreign", content };
90
+ }
91
+ catch {
92
+ return { status: "missing" };
93
+ }
94
+ }
95
+ function renderHookScript(command) {
96
+ return `#!/bin/sh
97
+ ${MANAGED_MARKER}
98
+ set -eu
99
+
100
+ resolve_rup() {
101
+ if command -v rup >/dev/null 2>&1; then
102
+ printf '%s' "rup"
103
+ return
104
+ fi
105
+ if command -v rainy-updates >/dev/null 2>&1; then
106
+ printf '%s' "rainy-updates"
107
+ return
108
+ fi
109
+ if command -v rainy-up >/dev/null 2>&1; then
110
+ printf '%s' "rainy-up"
111
+ return
112
+ fi
113
+ printf '%s\n' "Rainy Updates CLI not found in PATH." >&2
114
+ exit 127
115
+ }
116
+
117
+ ${command}
118
+ `;
119
+ }
120
+ async function makeExecutable(filePath) {
121
+ await chmod(filePath, 0o755);
122
+ }
123
+ async function runGit(cwd, args) {
124
+ try {
125
+ const proc = Bun.spawn(["git", ...args], {
126
+ cwd,
127
+ stdout: "pipe",
128
+ stderr: "pipe",
129
+ });
130
+ const [stdout, stderr, code] = await Promise.all([
131
+ new Response(proc.stdout).text(),
132
+ new Response(proc.stderr).text(),
133
+ proc.exited,
134
+ ]);
135
+ if (code !== 0) {
136
+ return { ok: false, error: stderr.trim() || `git ${args.join(" ")} failed` };
137
+ }
138
+ return { ok: true, stdout };
139
+ }
140
+ catch (error) {
141
+ return { ok: false, error: String(error) };
142
+ }
143
+ }
144
+ function renderHookResult(result) {
145
+ const lines = [
146
+ `Hook action: ${result.action}`,
147
+ `Hook dir: ${result.hookDir ?? "unknown"}`,
148
+ ];
149
+ if (result.installed.length > 0) {
150
+ lines.push(`Installed: ${result.installed.join(", ")}`);
151
+ }
152
+ if (result.removed.length > 0) {
153
+ lines.push(`Removed: ${result.removed.join(", ")}`);
154
+ }
155
+ if (result.checked.length > 0) {
156
+ lines.push("Checks:");
157
+ for (const check of result.checked) {
158
+ lines.push(`- ${check.name}: ${check.status}`);
159
+ }
160
+ }
161
+ if (result.warnings.length > 0) {
162
+ lines.push("Warnings:");
163
+ for (const warning of result.warnings) {
164
+ lines.push(`- ${warning}`);
165
+ }
166
+ }
167
+ if (result.errors.length > 0) {
168
+ lines.push("Errors:");
169
+ for (const error of result.errors) {
170
+ lines.push(`- ${error}`);
171
+ }
172
+ }
173
+ return lines.join("\n");
174
+ }
@@ -2,6 +2,11 @@ export function parseLicensesArgs(args) {
2
2
  const options = {
3
3
  cwd: process.cwd(),
4
4
  workspace: false,
5
+ affected: false,
6
+ staged: false,
7
+ baseRef: undefined,
8
+ headRef: undefined,
9
+ sinceRef: undefined,
5
10
  allow: undefined,
6
11
  deny: undefined,
7
12
  sbomFile: undefined,
@@ -25,6 +30,35 @@ export function parseLicensesArgs(args) {
25
30
  options.workspace = true;
26
31
  continue;
27
32
  }
33
+ if (current === "--affected") {
34
+ options.affected = true;
35
+ continue;
36
+ }
37
+ if (current === "--staged") {
38
+ options.staged = true;
39
+ continue;
40
+ }
41
+ if (current === "--base" && next) {
42
+ options.baseRef = next;
43
+ i++;
44
+ continue;
45
+ }
46
+ if (current === "--base")
47
+ throw new Error("Missing value for --base");
48
+ if (current === "--head" && next) {
49
+ options.headRef = next;
50
+ i++;
51
+ continue;
52
+ }
53
+ if (current === "--head")
54
+ throw new Error("Missing value for --head");
55
+ if (current === "--since" && next) {
56
+ options.sinceRef = next;
57
+ i++;
58
+ continue;
59
+ }
60
+ if (current === "--since")
61
+ throw new Error("Missing value for --since");
28
62
  if (current === "--diff") {
29
63
  options.diffMode = true;
30
64
  continue;
@@ -105,6 +139,11 @@ Options:
105
139
  --json-file <path> Write JSON report to file
106
140
  --diff Show only packages with a different license than last scan
107
141
  --workspace Scan all workspace packages
142
+ --affected Scan changed workspace packages and dependents
143
+ --staged Limit scanning to staged changes
144
+ --base <ref> Compare changes against a base git ref
145
+ --head <ref> Compare changes against a head git ref
146
+ --since <ref> Compare changes since a git ref
108
147
  --timeout <ms> Registry request timeout (default: 10000)
109
148
  --concurrency <n> Parallel registry requests (default: 12)
110
149
  --cwd <path> Working directory (default: cwd)
@@ -20,7 +20,11 @@ export async function runLicenses(options) {
20
20
  errors: [],
21
21
  warnings: [],
22
22
  };
23
- const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
23
+ const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
24
+ git: options,
25
+ includeKinds: ["dependencies", "devDependencies", "optionalDependencies"],
26
+ includeDependents: options.affected === true,
27
+ });
24
28
  const allDeps = new Map(); // name → resolved version
25
29
  for (const packageDir of packageDirs) {
26
30
  let manifest;
@@ -22,7 +22,11 @@ export async function buildPeerGraph(options,
22
22
  * to inject proposed upgrade versions before writing them to disk).
23
23
  */
24
24
  resolvedVersionOverrides) {
25
- const packageDirs = await discoverPackageDirs(options.cwd, options.workspace);
25
+ const packageDirs = await discoverPackageDirs(options.cwd, options.workspace, {
26
+ git: options,
27
+ includeKinds: ["dependencies", "devDependencies", "optionalDependencies"],
28
+ includeDependents: options.affected === true,
29
+ });
26
30
  const cache = await VersionCache.create();
27
31
  const registry = new NpmRegistryClient(options.cwd, {
28
32
  timeoutMs: options.registryTimeoutMs,
@@ -2,6 +2,11 @@ export function parseResolveArgs(args) {
2
2
  const options = {
3
3
  cwd: process.cwd(),
4
4
  workspace: false,
5
+ affected: false,
6
+ staged: false,
7
+ baseRef: undefined,
8
+ headRef: undefined,
9
+ sinceRef: undefined,
5
10
  afterUpdate: false,
6
11
  safe: false,
7
12
  jsonFile: undefined,
@@ -23,6 +28,35 @@ export function parseResolveArgs(args) {
23
28
  options.workspace = true;
24
29
  continue;
25
30
  }
31
+ if (current === "--affected") {
32
+ options.affected = true;
33
+ continue;
34
+ }
35
+ if (current === "--staged") {
36
+ options.staged = true;
37
+ continue;
38
+ }
39
+ if (current === "--base" && next) {
40
+ options.baseRef = next;
41
+ i++;
42
+ continue;
43
+ }
44
+ if (current === "--base")
45
+ throw new Error("Missing value for --base");
46
+ if (current === "--head" && next) {
47
+ options.headRef = next;
48
+ i++;
49
+ continue;
50
+ }
51
+ if (current === "--head")
52
+ throw new Error("Missing value for --head");
53
+ if (current === "--since" && next) {
54
+ options.sinceRef = next;
55
+ i++;
56
+ continue;
57
+ }
58
+ if (current === "--since")
59
+ throw new Error("Missing value for --since");
26
60
  if (current === "--after-update") {
27
61
  options.afterUpdate = true;
28
62
  continue;
@@ -77,6 +111,11 @@ Options:
77
111
  --after-update Simulate conflicts after applying pending \`rup check\` updates
78
112
  --safe Exit non-zero if any error-level conflicts exist
79
113
  --workspace Scan all workspace packages
114
+ --affected Scan changed workspace packages and their dependents
115
+ --staged Limit scanning to staged changes
116
+ --base <ref> Compare changes against a base git ref
117
+ --head <ref> Compare changes against a head git ref
118
+ --since <ref> Compare changes since a git ref
80
119
  --json-file <path> Write JSON conflict report to file
81
120
  --timeout <ms> Registry request timeout in ms (default: 10000)
82
121
  --concurrency <n> Parallel registry requests (default: 12)
@@ -95,6 +95,11 @@ async function fetchProposedVersions(options) {
95
95
  cooldownDays: undefined,
96
96
  prLimit: undefined,
97
97
  onlyChanged: false,
98
+ affected: options.affected,
99
+ staged: options.staged,
100
+ baseRef: options.baseRef,
101
+ headRef: options.headRef,
102
+ sinceRef: options.sinceRef,
98
103
  ciProfile: "minimal",
99
104
  lockfileMode: "preserve",
100
105
  interactive: false,