@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
package/CHANGELOG.md CHANGED
@@ -2,6 +2,59 @@
2
2
 
3
3
  All notable changes to this project are documented in this file.
4
4
 
5
+ ## [0.6.1] - 2026-03-03
6
+
7
+ Compatibility, git-aware workspace scoping, and release-readiness stabilization for the `v0.6` line.
8
+
9
+ ### Added
10
+
11
+ - **First-class package-manager profile layer**:
12
+ - detection now prefers `package.json.packageManager` before falling back to lockfiles,
13
+ - additive package-manager metadata for lockfile source and Yarn flavor detection,
14
+ - centralized install, add, and test command construction for npm, pnpm, Bun, and Yarn.
15
+ - **Git-aware workspace scoping**:
16
+ - `--affected`,
17
+ - `--staged`,
18
+ - `--base <ref>`,
19
+ - `--head <ref>`,
20
+ - `--since <ref>`.
21
+ - **Workspace dependent expansion for affected scans**:
22
+ - changed packages can now expand to dependent workspace packages instead of stopping at direct file matches.
23
+ - **New `hook` command**:
24
+ - `rup hook install`,
25
+ - `rup hook uninstall`,
26
+ - `rup hook doctor`.
27
+ - **Rainy-managed git hooks**:
28
+ - `pre-commit` runs `rup unused --workspace --staged` and `rup resolve --workspace --staged`,
29
+ - `pre-push` runs `rup audit --workspace --affected --report summary`.
30
+ - **New test coverage** for:
31
+ - package-manager field precedence and Yarn Berry behavior,
32
+ - git-scoped workspace discovery,
33
+ - hook install/doctor/uninstall lifecycle,
34
+ - scoped standalone parser support.
35
+
36
+ ### Changed
37
+
38
+ - `init-ci` workflow generation now uses the centralized package-manager profile layer instead of special-casing npm/pnpm/Bun only.
39
+ - Yarn support is now explicit in generated workflows:
40
+ - Corepack enablement for Yarn/pnpm repos,
41
+ - Yarn Berry uses immutable installs,
42
+ - Yarn package adds no longer fall back to npm command construction.
43
+ - `verification`, `audit --fix`, and `bisect` now reuse the same package-manager command model as `upgrade`.
44
+ - `ga` package-manager reporting now includes detection source details and respects the git-scoped workspace discovery flow.
45
+ - `check`, `warm-cache`, `audit`, `unused`, `resolve`, `health`, `licenses`, `snapshot`, and `ga` now share the same git-aware workspace scoping path.
46
+ - Command help and parser support were aligned so git-scoping flags are consistently accepted across the primary and standalone command surfaces.
47
+
48
+ ### Tests
49
+
50
+ - Full release validation passed:
51
+ - `pnpm -s exec tsc --noEmit`
52
+ - `bun test`
53
+ - `pnpm run build`
54
+ - `bun run build:exe`
55
+ - `bun run test:prod`
56
+ - `bun ./dist/bin/cli.js ga --workspace`
57
+
5
58
  ## [0.6.0] - 2026-03-01
6
59
 
7
60
  Dashboard-first release candidate for the `v0.6` series, focused on unifying the interactive surface, introducing replayable decision plans, tightening CI/apply verification flows, and undergoing a complete native Bun performance optimization.
package/dist/bin/cli.js CHANGED
@@ -1,134 +1,19 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync } from "node:fs";
3
- import { parseCliArgs } from "../core/options.js";
4
- import { applyFixPr } from "../core/fix-pr.js";
5
- import { applyFixPrBatches } from "../core/fix-pr-batch.js";
6
- import { createRunId, writeArtifactManifest } from "../core/artifacts.js";
7
- import { renderResult } from "../output/format.js";
8
- import { writeGitHubOutput } from "../output/github.js";
9
- import { createSarifReport } from "../output/sarif.js";
10
- import { renderPrReport } from "../output/pr-report.js";
11
- import { writeFileAtomic } from "../utils/io.js";
12
- import { resolveFailReason } from "../core/summary.js";
13
- import { stableStringify } from "../utils/stable-json.js";
14
- import { getRuntimeArgv, setRuntimeExitCode, writeStderr, writeStdout, } from "../utils/runtime.js";
15
- import { handleDirectCommand, runPrimaryCommand } from "./dispatch.js";
16
- import { renderHelp } from "./help.js";
2
+ import { spawnSync } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { runCli } from "./main.js";
17
5
  async function main() {
18
- try {
19
- const argv = getRuntimeArgv();
20
- if (argv.includes("--version") || argv.includes("-v")) {
21
- writeStdout((await readPackageVersion()) + "\n");
22
- return;
23
- }
24
- if (argv.includes("--help") || argv.includes("-h")) {
25
- writeStdout(renderHelp(argv[0]) + "\n");
26
- return;
27
- }
28
- const parsed = await parseCliArgs(argv);
29
- if (await handleDirectCommand(parsed))
30
- return;
31
- if (parsed.command !== "check" &&
32
- parsed.command !== "upgrade" &&
33
- parsed.command !== "warm-cache" &&
34
- parsed.command !== "ci") {
35
- throw new Error(`Unhandled command: ${parsed.command}`);
36
- }
37
- const result = await runPrimaryCommand(parsed);
38
- result.summary.runId = createRunId(parsed.command, parsed.options, result);
39
- if (parsed.options.fixPr &&
40
- (parsed.command === "check" ||
41
- parsed.command === "upgrade" ||
42
- parsed.command === "ci")) {
43
- result.summary.fixPrApplied = false;
44
- result.summary.fixBranchName =
45
- parsed.options.fixBranch ?? "chore/rainy-updates";
46
- result.summary.fixCommitSha = "";
47
- result.summary.fixPrBranchesCreated = 0;
48
- if (parsed.command === "ci") {
49
- const batched = await applyFixPrBatches(parsed.options, result);
50
- result.summary.fixPrApplied = batched.applied;
51
- result.summary.fixBranchName =
52
- batched.branches[0] ??
53
- parsed.options.fixBranch ??
54
- "chore/rainy-updates";
55
- result.summary.fixCommitSha = batched.commits[0] ?? "";
56
- result.summary.fixPrBranchesCreated = batched.branches.length;
57
- if (batched.branches.length > 1) {
58
- result.warnings.push(`Created ${batched.branches.length} fix-pr batch branches.`);
59
- }
60
- }
61
- else {
62
- const fixResult = await applyFixPr(parsed.options, result, []);
63
- result.summary.fixPrApplied = fixResult.applied;
64
- result.summary.fixBranchName = fixResult.branchName ?? "";
65
- result.summary.fixCommitSha = fixResult.commitSha ?? "";
66
- result.summary.fixPrBranchesCreated = fixResult.applied ? 1 : 0;
67
- }
68
- }
69
- if (parsed.options.prReportFile) {
70
- const markdown = renderPrReport(result);
71
- await writeFileAtomic(parsed.options.prReportFile, markdown + "\n");
72
- }
73
- const artifactManifest = await writeArtifactManifest(parsed.command, parsed.options, result);
74
- if (artifactManifest) {
75
- result.summary.artifactManifest = artifactManifest.artifactManifestPath;
76
- }
77
- result.summary.failReason = resolveFailReason(result.updates, result.errors, parsed.options.failOn, parsed.options.maxUpdates, parsed.options.ci);
78
- const renderStartedAt = Date.now();
79
- let rendered = renderResult(result, parsed.options.format, {
80
- showImpact: parsed.options.showImpact,
81
- showHomepage: parsed.options.showHomepage,
6
+ if (typeof Bun === "undefined") {
7
+ const currentFile = fileURLToPath(import.meta.url);
8
+ const result = spawnSync("bun", [currentFile, ...process.argv.slice(2)], {
9
+ stdio: "inherit",
82
10
  });
83
- result.summary.durationMs.render = Math.max(0, Date.now() - renderStartedAt);
84
- if (parsed.options.format === "json" ||
85
- parsed.options.format === "metrics") {
86
- rendered = renderResult(result, parsed.options.format, {
87
- showImpact: parsed.options.showImpact,
88
- showHomepage: parsed.options.showHomepage,
89
- });
90
- }
91
- if (parsed.options.onlyChanged &&
92
- result.updates.length === 0 &&
93
- result.errors.length === 0 &&
94
- result.warnings.length === 0 &&
95
- (parsed.options.format === "table" ||
96
- parsed.options.format === "minimal" ||
97
- parsed.options.format === "github")) {
98
- rendered = "";
99
- }
100
- if (parsed.options.jsonFile) {
101
- await writeFileAtomic(parsed.options.jsonFile, stableStringify(result, 2) + "\n");
11
+ if (result.error) {
12
+ process.stderr.write("rainy-updates (rup): Bun is required to run the published JavaScript entrypoint. Install Bun or use the compiled binary release.\n");
13
+ process.exit(1);
102
14
  }
103
- if (parsed.options.githubOutputFile) {
104
- await writeGitHubOutput(parsed.options.githubOutputFile, result);
105
- }
106
- if (parsed.options.sarifFile) {
107
- const sarif = createSarifReport(result);
108
- await writeFileAtomic(parsed.options.sarifFile, stableStringify(sarif, 2) + "\n");
109
- }
110
- writeStdout(rendered + "\n");
111
- setRuntimeExitCode(resolveExitCode(result, result.summary.failReason));
112
- }
113
- catch (error) {
114
- writeStderr(`rainy-updates (rup): ${String(error)}\n`);
115
- setRuntimeExitCode(2);
15
+ process.exit(result.status ?? 1);
116
16
  }
17
+ await runCli();
117
18
  }
118
19
  void main();
119
- async function readPackageVersion() {
120
- try {
121
- const packageJson = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8"));
122
- return packageJson.version ?? "0.0.0";
123
- }
124
- catch {
125
- return "0.0.0";
126
- }
127
- }
128
- function resolveExitCode(result, failReason) {
129
- if (result.errors.length > 0)
130
- return 2;
131
- if (failReason !== "none")
132
- return 1;
133
- return 0;
134
- }
@@ -108,6 +108,12 @@ export async function handleDirectCommand(parsed) {
108
108
  setRuntimeExitCode(result.ready ? 0 : 1);
109
109
  return true;
110
110
  }
111
+ if (parsed.command === "hook") {
112
+ const { runHook } = await import("../commands/hook/runner.js");
113
+ const result = await runHook(parsed.options);
114
+ setRuntimeExitCode(result.errors.length > 0 ? 1 : 0);
115
+ return true;
116
+ }
111
117
  if (parsed.options.interactive &&
112
118
  (parsed.command === "check" ||
113
119
  parsed.command === "upgrade" ||
package/dist/bin/help.js CHANGED
@@ -40,6 +40,11 @@ Options:
40
40
  --cooldown-days <n>
41
41
  --pr-limit <n>
42
42
  --only-changed
43
+ --affected
44
+ --staged
45
+ --base <ref>
46
+ --head <ref>
47
+ --since <ref>
43
48
  --interactive
44
49
  --plan-file <path>
45
50
  --verify none|install|test|install,test
@@ -123,6 +128,11 @@ Options:
123
128
  --cooldown-days <n>
124
129
  --pr-limit <n>
125
130
  --only-changed
131
+ --affected
132
+ --staged
133
+ --base <ref>
134
+ --head <ref>
135
+ --since <ref>
126
136
  --offline
127
137
  --concurrency <n>
128
138
  --registry-timeout-ms <n>
@@ -180,6 +190,11 @@ Scan dependencies for CVEs using OSV.dev and GitHub Advisory Database.
180
190
 
181
191
  Options:
182
192
  --workspace
193
+ --affected
194
+ --staged
195
+ --base <ref>
196
+ --head <ref>
197
+ --since <ref>
183
198
  --severity critical|high|medium|low
184
199
  --summary
185
200
  --report table|summary|json
@@ -200,6 +215,12 @@ Use it to inspect risk, security, peer, license, and policy context before apply
200
215
 
201
216
  Options:
202
217
  --workspace
218
+ --only-changed
219
+ --affected
220
+ --staged
221
+ --base <ref>
222
+ --head <ref>
223
+ --since <ref>
203
224
  --interactive
204
225
  --security-only
205
226
  --risk critical|high|medium|low
@@ -220,6 +241,12 @@ Produce a fast summary verdict and point the operator to review when action is n
220
241
 
221
242
  Options:
222
243
  --workspace
244
+ --only-changed
245
+ --affected
246
+ --staged
247
+ --base <ref>
248
+ --head <ref>
249
+ --since <ref>
223
250
  --verdict-only
224
251
  --include-changelog
225
252
  --json-file <path>`;
@@ -231,6 +258,12 @@ Open the primary interactive dependency operations console.
231
258
 
232
259
  Options:
233
260
  --workspace
261
+ --only-changed
262
+ --affected
263
+ --staged
264
+ --base <ref>
265
+ --head <ref>
266
+ --since <ref>
234
267
  --mode check|review|upgrade
235
268
  --focus all|security|risk|major|blocked|workspace
236
269
  --apply-selected
@@ -248,6 +281,14 @@ Audit release and CI readiness for Rainy Updates.
248
281
  Options:
249
282
  --workspace
250
283
  --json-file <path>
284
+ --cwd <path>`;
285
+ }
286
+ if (isCommand && command === "hook") {
287
+ return `rainy-updates hook <install|uninstall|doctor> [options]
288
+
289
+ Install, remove, or inspect Rainy-managed git hooks.
290
+
291
+ Options:
251
292
  --cwd <path>`;
252
293
  }
253
294
  return `rainy-updates (rup / rainy-up) <command> [options]
@@ -270,6 +311,7 @@ Commands:
270
311
  licenses Scan dependency licenses and generate SPDX SBOM
271
312
  snapshot Save, list, restore, and diff dependency state snapshots
272
313
  ga Audit GA and CI readiness for this checkout
314
+ hook Install or inspect Rainy-managed git hooks
273
315
 
274
316
  Global options:
275
317
  --cwd <path>
@@ -288,6 +330,11 @@ Global options:
288
330
  --cooldown-days <n>
289
331
  --pr-limit <n>
290
332
  --only-changed
333
+ --affected
334
+ --staged
335
+ --base <ref>
336
+ --head <ref>
337
+ --since <ref>
291
338
  --interactive
292
339
  --show-impact
293
340
  --show-links
@@ -0,0 +1 @@
1
+ export declare function runCli(): Promise<void>;
@@ -0,0 +1,126 @@
1
+ import { parseCliArgs } from "../core/options.js";
2
+ import { createRunId, writeArtifactManifest } from "../core/artifacts.js";
3
+ import { renderResult } from "../output/format.js";
4
+ import { writeGitHubOutput } from "../output/github.js";
5
+ import { createSarifReport } from "../output/sarif.js";
6
+ import { renderPrReport } from "../output/pr-report.js";
7
+ import { writeFileAtomic } from "../utils/io.js";
8
+ import { resolveFailReason } from "../core/summary.js";
9
+ import { stableStringify } from "../utils/stable-json.js";
10
+ import { getRuntimeArgv, setRuntimeExitCode, writeStderr, writeStdout, } from "../utils/runtime.js";
11
+ import { CLI_VERSION } from "../generated/version.js";
12
+ import { handleDirectCommand, runPrimaryCommand } from "./dispatch.js";
13
+ import { renderHelp } from "./help.js";
14
+ export async function runCli() {
15
+ try {
16
+ const argv = getRuntimeArgv();
17
+ if (argv.includes("--version") || argv.includes("-v")) {
18
+ writeStdout((await readPackageVersion()) + "\n");
19
+ return;
20
+ }
21
+ if (argv.includes("--help") || argv.includes("-h")) {
22
+ writeStdout(renderHelp(argv[0]) + "\n");
23
+ return;
24
+ }
25
+ const parsed = await parseCliArgs(argv);
26
+ if (await handleDirectCommand(parsed))
27
+ return;
28
+ if (parsed.command !== "check" &&
29
+ parsed.command !== "upgrade" &&
30
+ parsed.command !== "warm-cache" &&
31
+ parsed.command !== "ci") {
32
+ throw new Error(`Unhandled command: ${parsed.command}`);
33
+ }
34
+ const result = await runPrimaryCommand(parsed);
35
+ result.summary.runId = createRunId(parsed.command, parsed.options, result);
36
+ if (parsed.options.fixPr &&
37
+ (parsed.command === "check" ||
38
+ parsed.command === "upgrade" ||
39
+ parsed.command === "ci")) {
40
+ result.summary.fixPrApplied = false;
41
+ result.summary.fixBranchName =
42
+ parsed.options.fixBranch ?? "chore/rainy-updates";
43
+ result.summary.fixCommitSha = "";
44
+ result.summary.fixPrBranchesCreated = 0;
45
+ if (parsed.command === "ci") {
46
+ const { applyFixPrBatches } = await import("../core/fix-pr-batch.js");
47
+ const batched = await applyFixPrBatches(parsed.options, result);
48
+ result.summary.fixPrApplied = batched.applied;
49
+ result.summary.fixBranchName =
50
+ batched.branches[0] ??
51
+ parsed.options.fixBranch ??
52
+ "chore/rainy-updates";
53
+ result.summary.fixCommitSha = batched.commits[0] ?? "";
54
+ result.summary.fixPrBranchesCreated = batched.branches.length;
55
+ if (batched.branches.length > 1) {
56
+ result.warnings.push(`Created ${batched.branches.length} fix-pr batch branches.`);
57
+ }
58
+ }
59
+ else {
60
+ const { applyFixPr } = await import("../core/fix-pr.js");
61
+ const fixResult = await applyFixPr(parsed.options, result, []);
62
+ result.summary.fixPrApplied = fixResult.applied;
63
+ result.summary.fixBranchName = fixResult.branchName ?? "";
64
+ result.summary.fixCommitSha = fixResult.commitSha ?? "";
65
+ result.summary.fixPrBranchesCreated = fixResult.applied ? 1 : 0;
66
+ }
67
+ }
68
+ if (parsed.options.prReportFile) {
69
+ const markdown = renderPrReport(result);
70
+ await writeFileAtomic(parsed.options.prReportFile, markdown + "\n");
71
+ }
72
+ const artifactManifest = await writeArtifactManifest(parsed.command, parsed.options, result);
73
+ if (artifactManifest) {
74
+ result.summary.artifactManifest = artifactManifest.artifactManifestPath;
75
+ }
76
+ result.summary.failReason = resolveFailReason(result.updates, result.errors, parsed.options.failOn, parsed.options.maxUpdates, parsed.options.ci);
77
+ const renderStartedAt = Date.now();
78
+ let rendered = renderResult(result, parsed.options.format, {
79
+ showImpact: parsed.options.showImpact,
80
+ showHomepage: parsed.options.showHomepage,
81
+ });
82
+ result.summary.durationMs.render = Math.max(0, Date.now() - renderStartedAt);
83
+ if (parsed.options.format === "json" ||
84
+ parsed.options.format === "metrics") {
85
+ rendered = renderResult(result, parsed.options.format, {
86
+ showImpact: parsed.options.showImpact,
87
+ showHomepage: parsed.options.showHomepage,
88
+ });
89
+ }
90
+ if (parsed.options.onlyChanged &&
91
+ result.updates.length === 0 &&
92
+ result.errors.length === 0 &&
93
+ result.warnings.length === 0 &&
94
+ (parsed.options.format === "table" ||
95
+ parsed.options.format === "minimal" ||
96
+ parsed.options.format === "github")) {
97
+ rendered = "";
98
+ }
99
+ if (parsed.options.jsonFile) {
100
+ await writeFileAtomic(parsed.options.jsonFile, stableStringify(result, 2) + "\n");
101
+ }
102
+ if (parsed.options.githubOutputFile) {
103
+ await writeGitHubOutput(parsed.options.githubOutputFile, result);
104
+ }
105
+ if (parsed.options.sarifFile) {
106
+ const sarif = createSarifReport(result);
107
+ await writeFileAtomic(parsed.options.sarifFile, stableStringify(sarif, 2) + "\n");
108
+ }
109
+ writeStdout(rendered + "\n");
110
+ setRuntimeExitCode(resolveExitCode(result, result.summary.failReason));
111
+ }
112
+ catch (error) {
113
+ writeStderr(`rainy-updates (rup): ${String(error)}\n`);
114
+ setRuntimeExitCode(2);
115
+ }
116
+ }
117
+ async function readPackageVersion() {
118
+ return CLI_VERSION;
119
+ }
120
+ function resolveExitCode(result, failReason) {
121
+ if (result.errors.length > 0)
122
+ return 2;
123
+ if (failReason !== "none")
124
+ return 1;
125
+ return 0;
126
+ }
@@ -12,6 +12,11 @@ export function parseAuditArgs(args) {
12
12
  const options = {
13
13
  cwd: getRuntimeCwd(),
14
14
  workspace: false,
15
+ affected: false,
16
+ staged: false,
17
+ baseRef: undefined,
18
+ headRef: undefined,
19
+ sinceRef: undefined,
15
20
  severity: undefined,
16
21
  fix: false,
17
22
  dryRun: false,
@@ -39,6 +44,37 @@ export function parseAuditArgs(args) {
39
44
  index += 1;
40
45
  continue;
41
46
  }
47
+ if (current === "--affected") {
48
+ options.affected = true;
49
+ index += 1;
50
+ continue;
51
+ }
52
+ if (current === "--staged") {
53
+ options.staged = true;
54
+ index += 1;
55
+ continue;
56
+ }
57
+ if (current === "--base" && next) {
58
+ options.baseRef = next;
59
+ index += 2;
60
+ continue;
61
+ }
62
+ if (current === "--base")
63
+ throw new Error("Missing value for --base");
64
+ if (current === "--head" && next) {
65
+ options.headRef = next;
66
+ index += 2;
67
+ continue;
68
+ }
69
+ if (current === "--head")
70
+ throw new Error("Missing value for --head");
71
+ if (current === "--since" && next) {
72
+ options.sinceRef = next;
73
+ index += 2;
74
+ continue;
75
+ }
76
+ if (current === "--since")
77
+ throw new Error("Missing value for --since");
42
78
  if (current === "--severity" && next) {
43
79
  options.severity = parseSeverity(next);
44
80
  index += 2;
@@ -1,5 +1,5 @@
1
1
  import { collectDependencies, readManifest, } from "../../parsers/package-json.js";
2
- import { detectPackageManager as detectProjectPackageManager, resolvePackageManager, } from "../../pm/detect.js";
2
+ import { buildAddInvocation, createPackageManagerProfile, detectPackageManagerDetails, } from "../../pm/detect.js";
3
3
  import { discoverPackageDirs } from "../../workspace/discover.js";
4
4
  import { writeFileAtomic } from "../../utils/io.js";
5
5
  import { stableStringify } from "../../utils/stable-json.js";
@@ -28,7 +28,15 @@ export async function runAudit(options) {
28
28
  unresolved: 0,
29
29
  },
30
30
  };
31
- 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
+ });
32
40
  const depsByDir = new Map();
33
41
  for (const dir of packageDirs) {
34
42
  let manifest;
@@ -121,10 +129,10 @@ async function applyFix(advisories, options) {
121
129
  const patchMap = buildPatchMap(advisories);
122
130
  if (patchMap.size === 0)
123
131
  return;
124
- const detected = await detectProjectPackageManager(options.cwd);
125
- const pm = resolvePackageManager(options.packageManager, detected);
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
138
  writeStderr(`[audit] --dry-run: would execute:\n ${installCmd}\n`);
@@ -140,7 +148,7 @@ async function applyFix(advisories, options) {
140
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) {
@@ -158,18 +166,9 @@ async function applyFix(advisories, options) {
158
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);
@@ -1,5 +1,5 @@
1
1
  import path from "node:path";
2
- import { detectPackageManager, resolvePackageManager } from "../../pm/detect.js";
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,9 +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 detected = await detectPackageManager(options.cwd);
15
- const packageManager = resolvePackageManager("auto", detected, "bun");
16
- const installResult = await runShell(buildInstallCommand(packageManager, 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);
17
20
  if (installResult !== 0) {
18
21
  process.stderr.write(`[bisect] Failed to install ${packageName}@${version}, skipping.\n`);
19
22
  return "skip";
@@ -38,16 +41,16 @@ async function runShell(command, cwd) {
38
41
  return 1;
39
42
  }
40
43
  }
41
- function buildInstallCommand(packageManager, packageName, version) {
42
- const spec = `${packageName}@${version}`;
43
- switch (packageManager) {
44
- case "bun":
45
- return `bun add --exact --no-save ${spec}`;
46
- case "pnpm":
47
- return `pnpm add --save-exact --no-save ${spec}`;
48
- case "yarn":
49
- return `npm install --no-save --save-exact ${spec}`;
50
- default:
51
- return `npm install --no-save --save-exact ${spec}`;
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;
52
55
  }
53
56
  }
@@ -1,15 +1,16 @@
1
- import { detectPackageManager, resolvePackageManager } from "../../pm/detect.js";
1
+ import { buildTestCommand, createPackageManagerProfile, detectPackageManagerDetails, } from "../../pm/detect.js";
2
2
  import { fetchBisectVersions, bisectVersions } from "./engine.js";
3
3
  /**
4
4
  * Entry point for the `bisect` command. Lazy-loaded by cli.ts.
5
5
  * Fully isolated: does NOT import anything from core/options.ts.
6
6
  */
7
7
  export async function runBisect(options) {
8
- const detected = await detectPackageManager(options.cwd);
8
+ const detected = await detectPackageManagerDetails(options.cwd);
9
+ const profile = createPackageManagerProfile("auto", detected, "bun");
9
10
  const runtimeOptions = {
10
11
  ...options,
11
12
  testCommand: options.testCommand ||
12
- `${resolvePackageManager("auto", detected, "bun")} test`,
13
+ buildTestCommand(profile),
13
14
  };
14
15
  process.stderr.write(`\n[bisect] Fetching available versions for ${runtimeOptions.packageName}...\n`);
15
16
  const versions = await fetchBisectVersions(runtimeOptions);