@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
@@ -0,0 +1,167 @@
1
+ import path from "node:path";
2
+ import { readManifest } from "../parsers/package-json.js";
3
+ import { buildWorkspaceGraph } from "../workspace/graph.js";
4
+ export function hasGitScope(options) {
5
+ return (options.onlyChanged === true ||
6
+ options.affected === true ||
7
+ options.staged === true ||
8
+ typeof options.baseRef === "string" ||
9
+ typeof options.headRef === "string" ||
10
+ typeof options.sinceRef === "string");
11
+ }
12
+ export async function scopePackageDirsByGit(cwd, packageDirs, options, config = {}) {
13
+ if (!hasGitScope(options)) {
14
+ return {
15
+ packageDirs,
16
+ warnings: [],
17
+ changedFiles: [],
18
+ };
19
+ }
20
+ const changedFiles = await listChangedFiles(cwd, options);
21
+ if ("warnings" in changedFiles) {
22
+ return {
23
+ packageDirs,
24
+ warnings: changedFiles.warnings,
25
+ changedFiles: [],
26
+ };
27
+ }
28
+ const changedPackageDirs = mapFilesToPackageDirs(cwd, packageDirs, changedFiles.files);
29
+ if (changedPackageDirs.length === 0) {
30
+ return {
31
+ packageDirs: [],
32
+ warnings: [],
33
+ changedFiles: changedFiles.files,
34
+ };
35
+ }
36
+ if (!config.includeDependents) {
37
+ return {
38
+ packageDirs: changedPackageDirs,
39
+ warnings: [],
40
+ changedFiles: changedFiles.files,
41
+ };
42
+ }
43
+ const affectedPackageDirs = await expandToDependents(changedPackageDirs, packageDirs, config.includeKinds ?? ["dependencies", "devDependencies"]);
44
+ return {
45
+ packageDirs: affectedPackageDirs,
46
+ warnings: [],
47
+ changedFiles: changedFiles.files,
48
+ };
49
+ }
50
+ async function listChangedFiles(cwd, options) {
51
+ const primaryArgs = buildGitDiffArgs(options);
52
+ const diff = await runGit(cwd, primaryArgs);
53
+ if (!diff.ok) {
54
+ return {
55
+ warnings: [
56
+ `Git scope could not be resolved (${diff.error}). Falling back to full workspace scan.`,
57
+ ],
58
+ };
59
+ }
60
+ const untracked = await runGit(cwd, ["ls-files", "--others", "--exclude-standard"]);
61
+ const files = new Set();
62
+ for (const value of [...diff.lines, ...(untracked.ok ? untracked.lines : [])]) {
63
+ const trimmed = value.trim();
64
+ if (trimmed.length === 0)
65
+ continue;
66
+ files.add(trimmed);
67
+ }
68
+ return {
69
+ files: Array.from(files).sort((left, right) => left.localeCompare(right)),
70
+ };
71
+ }
72
+ function buildGitDiffArgs(options) {
73
+ if (options.staged) {
74
+ return ["diff", "--name-only", "--cached"];
75
+ }
76
+ if (options.baseRef && options.headRef) {
77
+ return ["diff", "--name-only", `${options.baseRef}...${options.headRef}`];
78
+ }
79
+ if (options.baseRef) {
80
+ return ["diff", "--name-only", `${options.baseRef}...HEAD`];
81
+ }
82
+ if (options.sinceRef) {
83
+ return ["diff", "--name-only", `${options.sinceRef}..HEAD`];
84
+ }
85
+ return ["diff", "--name-only", "HEAD"];
86
+ }
87
+ async function runGit(cwd, args) {
88
+ try {
89
+ const proc = Bun.spawn(["git", ...args], {
90
+ cwd,
91
+ stdout: "pipe",
92
+ stderr: "pipe",
93
+ });
94
+ const [stdout, stderr, exitCode] = await Promise.all([
95
+ new Response(proc.stdout).text(),
96
+ new Response(proc.stderr).text(),
97
+ proc.exited,
98
+ ]);
99
+ if (exitCode !== 0) {
100
+ const message = stderr.trim() || `git ${args.join(" ")} exited with code ${exitCode}`;
101
+ return { ok: false, error: message };
102
+ }
103
+ return {
104
+ ok: true,
105
+ lines: stdout.split(/\r?\n/).filter(Boolean),
106
+ };
107
+ }
108
+ catch (error) {
109
+ return { ok: false, error: String(error) };
110
+ }
111
+ }
112
+ function mapFilesToPackageDirs(cwd, packageDirs, files) {
113
+ const sortedDirs = [...packageDirs].sort((left, right) => right.length - left.length);
114
+ const matched = new Set();
115
+ for (const file of files) {
116
+ const absoluteFile = path.resolve(cwd, file);
117
+ let bestMatch;
118
+ for (const packageDir of sortedDirs) {
119
+ const relative = path.relative(packageDir, absoluteFile);
120
+ if (relative === "" || (!relative.startsWith("..") && !path.isAbsolute(relative))) {
121
+ bestMatch = packageDir;
122
+ break;
123
+ }
124
+ }
125
+ if (bestMatch) {
126
+ matched.add(bestMatch);
127
+ }
128
+ }
129
+ return Array.from(matched).sort((left, right) => left.localeCompare(right));
130
+ }
131
+ async function expandToDependents(changedPackageDirs, packageDirs, includeKinds) {
132
+ const manifestsByPath = new Map();
133
+ for (const packageDir of packageDirs) {
134
+ try {
135
+ manifestsByPath.set(packageDir, await readManifest(packageDir));
136
+ }
137
+ catch {
138
+ // Skip unreadable manifests; callers already handle manifest read failures.
139
+ }
140
+ }
141
+ const graph = buildWorkspaceGraph(manifestsByPath, includeKinds);
142
+ const pathByName = new Map(graph.nodes.map((node) => [node.packageName, node.packagePath]));
143
+ const nameByPath = new Map(graph.nodes.map((node) => [node.packagePath, node.packageName]));
144
+ const dependents = new Map();
145
+ for (const node of graph.nodes) {
146
+ for (const dependency of node.dependsOn) {
147
+ const list = dependents.get(dependency) ?? [];
148
+ list.push(node.packageName);
149
+ dependents.set(dependency, list);
150
+ }
151
+ }
152
+ const selected = new Set(changedPackageDirs);
153
+ const queue = changedPackageDirs
154
+ .map((packageDir) => nameByPath.get(packageDir))
155
+ .filter((value) => typeof value === "string");
156
+ while (queue.length > 0) {
157
+ const current = queue.shift();
158
+ for (const dependent of dependents.get(current) ?? []) {
159
+ const dependentPath = pathByName.get(dependent);
160
+ if (!dependentPath || selected.has(dependentPath))
161
+ continue;
162
+ selected.add(dependentPath);
163
+ queue.push(dependent);
164
+ }
165
+ }
166
+ return Array.from(selected).sort((left, right) => left.localeCompare(right));
167
+ }
package/dist/index.d.ts CHANGED
@@ -4,9 +4,10 @@ export { warmCache } from "./core/warm-cache.js";
4
4
  export { runCi } from "./core/ci.js";
5
5
  export { initCiWorkflow } from "./core/init-ci.js";
6
6
  export { saveBaseline, diffBaseline } from "./core/baseline.js";
7
+ export { runHook } from "./commands/hook/runner.js";
7
8
  export { createSarifReport } from "./output/sarif.js";
8
9
  export { writeGitHubOutput, renderGitHubAnnotations } from "./output/github.js";
9
10
  export { renderPrReport } from "./output/pr-report.js";
10
11
  export { buildReviewResult, createDoctorResult } from "./core/review-model.js";
11
12
  export { applyRiskAssessments } from "./risk/index.js";
12
- export type { CheckOptions, CheckResult, CiProfile, DependencyKind, FailOnLevel, GroupBy, OutputFormat, PackageUpdate, RunOptions, TargetLevel, UpgradeOptions, UpgradeResult, ReviewOptions, ReviewResult, DoctorOptions, DoctorResult, Verdict, RiskLevel, RiskCategory, RiskAssessment, RiskFactor, MaintainerChurnStatus, } from "./types/index.js";
13
+ export type { CheckOptions, CheckResult, CiProfile, DependencyKind, FailOnLevel, GroupBy, OutputFormat, PackageUpdate, RunOptions, TargetLevel, UpgradeOptions, UpgradeResult, ReviewOptions, ReviewResult, DoctorOptions, DoctorResult, HookOptions, HookResult, Verdict, RiskLevel, RiskCategory, RiskAssessment, RiskFactor, MaintainerChurnStatus, } from "./types/index.js";
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { warmCache } from "./core/warm-cache.js";
4
4
  export { runCi } from "./core/ci.js";
5
5
  export { initCiWorkflow } from "./core/init-ci.js";
6
6
  export { saveBaseline, diffBaseline } from "./core/baseline.js";
7
+ export { runHook } from "./commands/hook/runner.js";
7
8
  export { createSarifReport } from "./output/sarif.js";
8
9
  export { writeGitHubOutput, renderGitHubAnnotations } from "./output/github.js";
9
10
  export { renderPrReport } from "./output/pr-report.js";
@@ -74,6 +74,12 @@ export function renderResult(result, format, display = {}) {
74
74
  `primary_finding_code=${result.summary.primaryFindingCode ?? ""}`,
75
75
  `primary_finding_category=${result.summary.primaryFindingCategory ?? ""}`,
76
76
  `next_action_reason=${result.summary.nextActionReason ?? ""}`,
77
+ `suggested_command=${result.summary.suggestedCommand ?? ""}`,
78
+ `decision_plan=${result.summary.decisionPlan ?? ""}`,
79
+ `interactive_surface=${result.summary.interactiveSurface ?? ""}`,
80
+ `queue_focus=${result.summary.queueFocus ?? ""}`,
81
+ `verification_state=${result.summary.verificationState ?? "not-run"}`,
82
+ `verification_failures=${result.summary.verificationFailures ?? 0}`,
77
83
  ].join("\n");
78
84
  }
79
85
  const lines = [];
@@ -137,6 +143,15 @@ export function renderResult(result, format, display = {}) {
137
143
  if (typeof result.summary.dependencyHealthScore === "number") {
138
144
  lines.push(`DependencyHealthScore=${result.summary.dependencyHealthScore}, primaryFinding=${result.summary.primaryFindingCode ?? "none"}, category=${result.summary.primaryFindingCategory ?? "none"}`);
139
145
  }
146
+ if (result.summary.suggestedCommand) {
147
+ lines.push(`SuggestedCommand=${result.summary.suggestedCommand}`);
148
+ }
149
+ if (result.summary.decisionPlan) {
150
+ lines.push(`DecisionPlan=${result.summary.decisionPlan}, surface=${result.summary.interactiveSurface ?? "none"}, focus=${result.summary.queueFocus ?? "all"}`);
151
+ }
152
+ if (result.summary.verificationState && result.summary.verificationState !== "not-run") {
153
+ lines.push(`Verification=${result.summary.verificationState}, failures=${result.summary.verificationFailures ?? 0}`);
154
+ }
140
155
  if (result.summary.runId) {
141
156
  lines.push(`RunId=${result.summary.runId}, artifactManifest=${result.summary.artifactManifest ?? "none"}, blockedPackages=${result.summary.blockedPackages ?? 0}, reviewPackages=${result.summary.reviewPackages ?? 0}, monitorPackages=${result.summary.monitorPackages ?? 0}`);
142
157
  }
@@ -38,6 +38,12 @@ export async function writeGitHubOutput(filePath, result) {
38
38
  `primary_finding_code=${result.summary.primaryFindingCode ?? ""}`,
39
39
  `primary_finding_category=${result.summary.primaryFindingCategory ?? ""}`,
40
40
  `next_action_reason=${result.summary.nextActionReason ?? ""}`,
41
+ `suggested_command=${result.summary.suggestedCommand ?? ""}`,
42
+ `decision_plan=${result.summary.decisionPlan ?? ""}`,
43
+ `interactive_surface=${result.summary.interactiveSurface ?? ""}`,
44
+ `queue_focus=${result.summary.queueFocus ?? ""}`,
45
+ `verification_state=${result.summary.verificationState ?? "not-run"}`,
46
+ `verification_failures=${result.summary.verificationFailures ?? 0}`,
41
47
  `fix_pr_applied=${result.summary.fixPrApplied === true ? "1" : "0"}`,
42
48
  `fix_pr_branches_created=${result.summary.fixPrBranchesCreated}`,
43
49
  `fix_pr_branch=${result.summary.fixBranchName ?? ""}`,
@@ -1,6 +1,4 @@
1
- import { readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
1
+ import { CLI_VERSION } from "../generated/version.js";
4
2
  export function createSarifReport(result) {
5
3
  const dependencyRuleId = "rainy-updates/dependency-update";
6
4
  const runtimeRuleId = "rainy-updates/runtime-error";
@@ -46,7 +44,9 @@ export function createSarifReport(result) {
46
44
  runId: result.summary.runId,
47
45
  },
48
46
  }));
49
- const errorResults = [...result.errors].sort((a, b) => a.localeCompare(b)).map((error) => ({
47
+ const errorResults = [...result.errors]
48
+ .sort((a, b) => a.localeCompare(b))
49
+ .map((error) => ({
50
50
  ruleId: runtimeRuleId,
51
51
  level: "error",
52
52
  message: {
@@ -66,12 +66,16 @@ export function createSarifReport(result) {
66
66
  {
67
67
  id: dependencyRuleId,
68
68
  shortDescription: { text: "Dependency update available" },
69
- fullDescription: { text: "A dependency has a newer version according to configured target." },
69
+ fullDescription: {
70
+ text: "A dependency has a newer version according to configured target.",
71
+ },
70
72
  },
71
73
  {
72
74
  id: runtimeRuleId,
73
75
  shortDescription: { text: "Dependency resolution error" },
74
- fullDescription: { text: "The resolver could not fetch or parse package metadata." },
76
+ fullDescription: {
77
+ text: "The resolver could not fetch or parse package metadata.",
78
+ },
75
79
  },
76
80
  ],
77
81
  },
@@ -114,16 +118,6 @@ let TOOL_VERSION_CACHE = null;
114
118
  function getToolVersion() {
115
119
  if (TOOL_VERSION_CACHE)
116
120
  return TOOL_VERSION_CACHE;
117
- try {
118
- const currentFile = fileURLToPath(import.meta.url);
119
- const packageJsonPath = path.resolve(path.dirname(currentFile), "../../package.json");
120
- const content = readFileSync(packageJsonPath, "utf8");
121
- const parsed = JSON.parse(content);
122
- TOOL_VERSION_CACHE = parsed.version ?? "0.0.0";
123
- return TOOL_VERSION_CACHE;
124
- }
125
- catch {
126
- TOOL_VERSION_CACHE = "0.0.0";
127
- return TOOL_VERSION_CACHE;
128
- }
121
+ TOOL_VERSION_CACHE = CLI_VERSION;
122
+ return TOOL_VERSION_CACHE;
129
123
  }
@@ -1,4 +1,3 @@
1
- import { promises as fs } from "node:fs";
2
1
  import path from "node:path";
3
2
  const DEPENDENCY_KINDS = [
4
3
  "dependencies",
@@ -11,13 +10,12 @@ export function getPackageJsonPath(cwd) {
11
10
  }
12
11
  export async function readManifest(cwd) {
13
12
  const filePath = getPackageJsonPath(cwd);
14
- const content = await fs.readFile(filePath, "utf8");
15
- return JSON.parse(content);
13
+ return (await Bun.file(filePath).json());
16
14
  }
17
15
  export async function writeManifest(cwd, manifest) {
18
16
  const filePath = getPackageJsonPath(cwd);
19
17
  const content = JSON.stringify(manifest, null, 2) + "\n";
20
- await fs.writeFile(filePath, content, "utf8");
18
+ await Bun.write(filePath, content);
21
19
  }
22
20
  export function collectDependencies(manifest, includeKinds) {
23
21
  const deps = [];
@@ -1 +1,40 @@
1
- export declare function detectPackageManager(cwd: string): Promise<"npm" | "pnpm" | "unknown">;
1
+ import type { DetectedPackageManager, SelectedPackageManager, SupportedPackageManager } from "../types/index.js";
2
+ export type PackageManagerDetectionSource = "packageManager-field" | "lockfile" | "fallback";
3
+ export type YarnFlavor = "classic" | "berry" | "unknown";
4
+ export interface PackageManagerDetection {
5
+ manager: DetectedPackageManager;
6
+ source: PackageManagerDetectionSource;
7
+ lockfile?: string;
8
+ packageManagerField?: string;
9
+ yarnFlavor?: YarnFlavor;
10
+ }
11
+ export interface PackageManagerProfile {
12
+ manager: SupportedPackageManager;
13
+ command: SupportedPackageManager;
14
+ source: PackageManagerDetectionSource;
15
+ lockfile?: string;
16
+ packageManagerField?: string;
17
+ yarnFlavor?: YarnFlavor;
18
+ }
19
+ export declare function detectPackageManager(cwd: string): Promise<DetectedPackageManager>;
20
+ export declare function detectPackageManagerDetails(cwd: string): Promise<PackageManagerDetection>;
21
+ export declare function resolvePackageManager(requested: SelectedPackageManager, detected: DetectedPackageManager, fallback?: SupportedPackageManager): SupportedPackageManager;
22
+ export declare function resolvePackageManagerProfile(cwd: string, requested: SelectedPackageManager, fallback?: SupportedPackageManager): Promise<PackageManagerProfile>;
23
+ export declare function createPackageManagerProfile(requested: SelectedPackageManager, detected: PackageManagerDetection, fallback?: SupportedPackageManager): PackageManagerProfile;
24
+ export declare function buildInstallInvocation(profile: PackageManagerProfile, options?: {
25
+ frozen?: boolean;
26
+ ci?: boolean;
27
+ }): {
28
+ command: string;
29
+ args: string[];
30
+ display: string;
31
+ };
32
+ export declare function buildAddInvocation(profile: PackageManagerProfile, packages: string[], options?: {
33
+ exact?: boolean;
34
+ noSave?: boolean;
35
+ }): {
36
+ command: string;
37
+ args: string[];
38
+ display: string;
39
+ };
40
+ export declare function buildTestCommand(profile: PackageManagerProfile): string;
package/dist/pm/detect.js CHANGED
@@ -1,20 +1,163 @@
1
- import { access } from "node:fs/promises";
2
1
  import path from "node:path";
2
+ const PACKAGE_MANAGER_LOCKFILES = [
3
+ ["bun.lock", "bun"],
4
+ ["bun.lockb", "bun"],
5
+ ["pnpm-lock.yaml", "pnpm"],
6
+ ["package-lock.json", "npm"],
7
+ ["npm-shrinkwrap.json", "npm"],
8
+ ["yarn.lock", "yarn"],
9
+ ];
3
10
  export async function detectPackageManager(cwd) {
4
- const pnpmLock = path.join(cwd, "pnpm-lock.yaml");
5
- const npmLock = path.join(cwd, "package-lock.json");
11
+ return (await detectPackageManagerDetails(cwd)).manager;
12
+ }
13
+ export async function detectPackageManagerDetails(cwd) {
14
+ const packageManagerField = await readPackageManagerField(cwd);
15
+ if (packageManagerField) {
16
+ const parsed = parsePackageManagerField(packageManagerField);
17
+ if (parsed) {
18
+ return {
19
+ manager: parsed.manager,
20
+ source: "packageManager-field",
21
+ packageManagerField,
22
+ yarnFlavor: parsed.yarnFlavor,
23
+ };
24
+ }
25
+ }
26
+ for (const [lockfile, packageManager] of PACKAGE_MANAGER_LOCKFILES) {
27
+ if (await fileExists(path.join(cwd, lockfile))) {
28
+ return {
29
+ manager: packageManager,
30
+ source: "lockfile",
31
+ lockfile,
32
+ yarnFlavor: packageManager === "yarn" ? "unknown" : undefined,
33
+ };
34
+ }
35
+ }
36
+ return {
37
+ manager: "unknown",
38
+ source: "fallback",
39
+ packageManagerField: packageManagerField ?? undefined,
40
+ };
41
+ }
42
+ export function resolvePackageManager(requested, detected, fallback = "npm") {
43
+ if (requested !== "auto")
44
+ return requested;
45
+ if (detected !== "unknown")
46
+ return detected;
47
+ return fallback;
48
+ }
49
+ export async function resolvePackageManagerProfile(cwd, requested, fallback = "npm") {
50
+ const detected = await detectPackageManagerDetails(cwd);
51
+ return createPackageManagerProfile(requested, detected, fallback);
52
+ }
53
+ export function createPackageManagerProfile(requested, detected, fallback = "npm") {
54
+ const manager = resolvePackageManager(requested, detected.manager, fallback);
55
+ return {
56
+ manager,
57
+ command: manager,
58
+ source: requested === "auto" && detected.manager !== "unknown"
59
+ ? detected.source
60
+ : "fallback",
61
+ lockfile: detected.lockfile,
62
+ packageManagerField: detected.packageManagerField,
63
+ yarnFlavor: manager === "yarn" ? detected.yarnFlavor ?? inferYarnFlavor(undefined) : undefined,
64
+ };
65
+ }
66
+ export function buildInstallInvocation(profile, options = {}) {
67
+ const args = profile.manager === "bun"
68
+ ? ["install", ...(options.frozen ? ["--frozen-lockfile"] : [])]
69
+ : profile.manager === "pnpm"
70
+ ? ["install", ...(options.frozen ? ["--frozen-lockfile"] : [])]
71
+ : profile.manager === "yarn"
72
+ ? [
73
+ "install",
74
+ ...(options.frozen
75
+ ? [
76
+ profile.yarnFlavor === "berry"
77
+ ? "--immutable"
78
+ : "--frozen-lockfile",
79
+ ]
80
+ : []),
81
+ ]
82
+ : options.ci || options.frozen
83
+ ? ["ci"]
84
+ : ["install"];
85
+ return {
86
+ command: profile.command,
87
+ args,
88
+ display: [profile.command, ...args].join(" "),
89
+ };
90
+ }
91
+ export function buildAddInvocation(profile, packages, options = {}) {
92
+ const args = profile.manager === "bun"
93
+ ? [
94
+ "add",
95
+ ...(options.exact ? ["--exact"] : []),
96
+ ...(options.noSave ? ["--no-save"] : []),
97
+ ...packages,
98
+ ]
99
+ : profile.manager === "pnpm"
100
+ ? [
101
+ "add",
102
+ ...(options.exact ? ["--save-exact"] : []),
103
+ ...(options.noSave ? ["--no-save"] : []),
104
+ ...packages,
105
+ ]
106
+ : profile.manager === "yarn"
107
+ ? ["add", ...(options.exact ? ["--exact"] : []), ...packages]
108
+ : [
109
+ "install",
110
+ ...(options.noSave ? ["--no-save"] : []),
111
+ ...(options.exact ? ["--save-exact"] : []),
112
+ ...packages,
113
+ ];
114
+ return {
115
+ command: profile.command,
116
+ args,
117
+ display: [profile.command, ...args].join(" "),
118
+ };
119
+ }
120
+ export function buildTestCommand(profile) {
121
+ return `${profile.command} test`;
122
+ }
123
+ async function fileExists(filePath) {
6
124
  try {
7
- await access(pnpmLock);
8
- return "pnpm";
125
+ return await Bun.file(filePath).exists();
9
126
  }
10
127
  catch {
11
- // noop
128
+ return false;
12
129
  }
130
+ }
131
+ async function readPackageManagerField(cwd) {
132
+ const packageJsonPath = path.join(cwd, "package.json");
13
133
  try {
14
- await access(npmLock);
15
- return "npm";
134
+ const manifest = (await Bun.file(packageJsonPath).json());
135
+ return typeof manifest.packageManager === "string"
136
+ ? manifest.packageManager
137
+ : null;
16
138
  }
17
139
  catch {
18
- return "unknown";
140
+ return null;
141
+ }
142
+ }
143
+ function parsePackageManagerField(value) {
144
+ const trimmed = value.trim();
145
+ if (trimmed.length === 0)
146
+ return null;
147
+ const [name, version] = trimmed.split("@", 2);
148
+ if (name !== "bun" && name !== "npm" && name !== "pnpm" && name !== "yarn") {
149
+ return null;
19
150
  }
151
+ return {
152
+ manager: name,
153
+ yarnFlavor: name === "yarn" ? inferYarnFlavor(version) : undefined,
154
+ };
155
+ }
156
+ function inferYarnFlavor(version) {
157
+ if (!version)
158
+ return "unknown";
159
+ const match = version.match(/^(\d+)/);
160
+ if (!match)
161
+ return "unknown";
162
+ return Number(match[1]) >= 2 ? "berry" : "classic";
20
163
  }
@@ -1 +1,3 @@
1
- export declare function installDependencies(cwd: string, packageManager: "auto" | "npm" | "pnpm", detected: "npm" | "pnpm" | "unknown"): Promise<void>;
1
+ import type { DetectedPackageManager, SelectedPackageManager } from "../types/index.js";
2
+ import { type PackageManagerDetection } from "./detect.js";
3
+ export declare function installDependencies(cwd: string, packageManager: SelectedPackageManager, detected: DetectedPackageManager | PackageManagerDetection): Promise<void>;
@@ -1,21 +1,22 @@
1
- import { spawn } from "node:child_process";
1
+ import { buildInstallInvocation, createPackageManagerProfile, } from "./detect.js";
2
2
  export async function installDependencies(cwd, packageManager, detected) {
3
- const selected = packageManager === "auto" ? (detected === "unknown" ? "npm" : detected) : packageManager;
4
- const command = selected;
5
- const args = ["install"];
6
- await new Promise((resolve, reject) => {
7
- const child = spawn(command, args, {
3
+ const detection = typeof detected === "string"
4
+ ? { manager: detected, source: "fallback" }
5
+ : detected;
6
+ const invocation = buildInstallInvocation(createPackageManagerProfile(packageManager, detection));
7
+ try {
8
+ const proc = Bun.spawn([invocation.command, ...invocation.args], {
8
9
  cwd,
9
- stdio: "inherit",
10
- shell: process.platform === "win32",
10
+ stdin: "inherit",
11
+ stdout: "inherit",
12
+ stderr: "inherit",
11
13
  });
12
- child.on("exit", (code) => {
13
- if (code === 0) {
14
- resolve();
15
- return;
16
- }
17
- reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code ?? "unknown"}`));
18
- });
19
- child.on("error", reject);
20
- });
14
+ const code = await proc.exited;
15
+ if (code !== 0) {
16
+ throw new Error(`${invocation.display} failed with exit code ${code}`);
17
+ }
18
+ }
19
+ catch (err) {
20
+ throw err instanceof Error ? err : new Error(String(err));
21
+ }
21
22
  }