bun-doctor 0.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kyle
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # Bun Doctor
2
+
3
+ Diagnose Node-to-Bun migration readiness for JavaScript and TypeScript projects.
4
+
5
+ `bun-doctor` answers one question:
6
+
7
+ > Can this repo safely move to Bun, and what exact changes get it there?
8
+
9
+ It scans package manager state, lockfiles, Bun config, TypeScript config, CI workflows, dependency risk, and a first pass of Bun-specific code risks. The output is a 0-100 Bun Readiness score grouped into Blockers, Risks, Migration work, and Bun wins.
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ npx -y bun-doctor@latest .
15
+ ```
16
+
17
+ Local development from this repo:
18
+
19
+ ```bash
20
+ bun install
21
+ bun run build
22
+ node dist/cli.mjs . --verbose
23
+ ```
24
+
25
+ ## CLI
26
+
27
+ ```txt
28
+ Usage: bun-doctor [directory] [options]
29
+
30
+ Options:
31
+ --json output a structured JSON report
32
+ --score output only the numeric score
33
+ --verbose show every diagnostic
34
+ --no-package skip package/config/dependency checks
35
+ --no-code skip source code checks
36
+ --fail-on <level> exit non-zero on blocker, risk, migration, or none
37
+ -v, --version print version
38
+ -h, --help print help
39
+
40
+ Commands:
41
+ bun-doctor install install the bun-doctor agent skill
42
+ ```
43
+
44
+ ## Scoring
45
+
46
+ The score starts at 100 and subtracts for unique triggered rules:
47
+
48
+ - Blocker: 12 points
49
+ - Risk: 5 points
50
+ - Migration work: 2 points
51
+ - Bun win: 0 points
52
+
53
+ Bun wins are shown as opportunities but do not lower readiness.
54
+
55
+ ## Rule Sources
56
+
57
+ Every diagnostic has at least one verifiable source: Bun docs, compatibility docs, or an issue/test link in the compatibility database. No source means no rule.
58
+
59
+ ## Roadmap
60
+
61
+ - MVP: CLI, JSON, score, config/package rules, code-risk scans, compatibility DB v0.
62
+ - v1.0: public eval harness that verifies compatibility DB entries across Bun versions and platforms.
63
+ - Later: editor/linter plugin once rules prove useful in real migrations.
package/action.yml ADDED
@@ -0,0 +1,52 @@
1
+ name: "Bun Doctor"
2
+ description: "Scan JavaScript and TypeScript projects for Bun migration readiness"
3
+ branding:
4
+ icon: "activity"
5
+ color: "orange"
6
+
7
+ inputs:
8
+ directory:
9
+ description: "Project directory to scan"
10
+ default: "."
11
+ verbose:
12
+ description: "Show all diagnostics"
13
+ default: "true"
14
+ fail-on:
15
+ description: "Exit with error code on diagnostics: blocker, risk, migration, none"
16
+ default: "blocker"
17
+ node-version:
18
+ description: "Node.js version to use for running bun-doctor"
19
+ default: "22"
20
+
21
+ outputs:
22
+ score:
23
+ description: "Bun Readiness score (0-100)"
24
+ value: ${{ steps.score.outputs.score }}
25
+
26
+ runs:
27
+ using: "composite"
28
+ steps:
29
+ - uses: actions/setup-node@v4
30
+ with:
31
+ node-version: ${{ inputs.node-version }}
32
+
33
+ - shell: bash
34
+ env:
35
+ INPUT_DIRECTORY: ${{ inputs.directory }}
36
+ INPUT_VERBOSE: ${{ inputs.verbose }}
37
+ INPUT_FAIL_ON: ${{ inputs.fail-on }}
38
+ run: |
39
+ FLAGS=("--fail-on" "$INPUT_FAIL_ON")
40
+ if [ "$INPUT_VERBOSE" = "true" ]; then FLAGS+=("--verbose"); fi
41
+ npx -y bun-doctor@latest "$INPUT_DIRECTORY" "${FLAGS[@]}"
42
+
43
+ - id: score
44
+ if: always()
45
+ shell: bash
46
+ env:
47
+ INPUT_DIRECTORY: ${{ inputs.directory }}
48
+ run: |
49
+ SCORE=$(npx -y bun-doctor@latest "$INPUT_DIRECTORY" --score --fail-on none 2>/dev/null | tail -1 | tr -d '[:space:]') || true
50
+ if [[ -n "$SCORE" && "$SCORE" =~ ^[0-9]+$ ]]; then
51
+ echo "score=$SCORE" >> "$GITHUB_OUTPUT"
52
+ fi
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import "../dist/cli.mjs";
package/dist/cli.d.mts ADDED
@@ -0,0 +1 @@
1
+ export { };
package/dist/cli.mjs ADDED
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+ import { a as VERSION, i as toRelativePath, t as scan } from "./scan-BVcJTreL.mjs";
3
+ import { parseArgs } from "node:util";
4
+ import path from "node:path";
5
+ import fs from "node:fs";
6
+ //#region src/install-skill.ts
7
+ const SKILL_CONTENT = `---
8
+ name: bun-doctor
9
+ description: Use after making dependency, CI, package manager, test runner, or Node runtime changes in a project that uses or is migrating to Bun. Checks Bun readiness and migration risk.
10
+ version: "1.0.0"
11
+ ---
12
+
13
+ # Bun Doctor
14
+
15
+ Run \`npx -y bun-doctor@latest . --verbose\` after Bun migration changes and fix blockers before switching CI or runtime.
16
+ `;
17
+ const installSkill = (options) => {
18
+ const skillDirectory = path.join(options.directory, ".agents", "skills", "bun-doctor");
19
+ const skillPath = path.join(skillDirectory, "SKILL.md");
20
+ if (options.dryRun) return skillPath;
21
+ fs.mkdirSync(skillDirectory, { recursive: true });
22
+ fs.writeFileSync(skillPath, SKILL_CONTENT, "utf8");
23
+ return skillPath;
24
+ };
25
+ //#endregion
26
+ //#region src/report.ts
27
+ const CATEGORY_ORDER = [
28
+ "Blockers",
29
+ "Risks",
30
+ "Migration work",
31
+ "Bun wins"
32
+ ];
33
+ const LEVEL_SYMBOL = {
34
+ blocker: "x",
35
+ risk: "!",
36
+ migration: "~",
37
+ win: "+"
38
+ };
39
+ const groupByCategory = (diagnostics) => {
40
+ const groups = /* @__PURE__ */ new Map();
41
+ for (const diagnostic of diagnostics) {
42
+ const existing = groups.get(diagnostic.category) ?? [];
43
+ existing.push(diagnostic);
44
+ groups.set(diagnostic.category, existing);
45
+ }
46
+ return groups;
47
+ };
48
+ const formatLocation = (diagnostic, rootDirectory) => {
49
+ const relativePath = toRelativePath(path.resolve(diagnostic.filePath), rootDirectory);
50
+ return diagnostic.line > 0 ? `${relativePath}:${diagnostic.line}` : relativePath;
51
+ };
52
+ const formatTextReport = (result, verbose) => {
53
+ const lines = [];
54
+ lines.push(`bun-doctor`);
55
+ lines.push(`Project: ${result.project.packageName}`);
56
+ lines.push(`Bun Readiness: ${result.score.score}/100 (${result.score.label})`);
57
+ lines.push(`Findings: ${result.summary.blockers} blockers, ${result.summary.risks} risks, ${result.summary.migrations} migration, ${result.summary.wins} wins`);
58
+ lines.push("");
59
+ if (result.diagnostics.length === 0) {
60
+ lines.push("No Bun migration findings.");
61
+ return lines.join("\n");
62
+ }
63
+ const groups = groupByCategory(result.diagnostics);
64
+ for (const category of CATEGORY_ORDER) {
65
+ const diagnostics = groups.get(category) ?? [];
66
+ if (diagnostics.length === 0) continue;
67
+ lines.push(`${category} (${diagnostics.length})`);
68
+ const shownDiagnostics = verbose ? diagnostics : diagnostics.slice(0, 3);
69
+ for (const diagnostic of shownDiagnostics) {
70
+ lines.push(` ${LEVEL_SYMBOL[diagnostic.level]} ${diagnostic.title} [${diagnostic.ruleId}]`);
71
+ lines.push(` ${diagnostic.message}`);
72
+ if (diagnostic.help) lines.push(` ${diagnostic.help}`);
73
+ lines.push(` ${formatLocation(diagnostic, result.project.rootDirectory)}`);
74
+ lines.push(` Source: ${diagnostic.sources[0]}`);
75
+ }
76
+ if (!verbose && diagnostics.length > shownDiagnostics.length) lines.push(` ... ${diagnostics.length - shownDiagnostics.length} more. Re-run with --verbose.`);
77
+ lines.push("");
78
+ }
79
+ return lines.join("\n").trimEnd();
80
+ };
81
+ const toJsonReport = (result) => ({
82
+ schemaVersion: 1,
83
+ ok: true,
84
+ score: result.score,
85
+ summary: result.summary,
86
+ project: {
87
+ name: result.project.packageName,
88
+ rootDirectory: result.project.rootDirectory,
89
+ packageJsonPath: result.project.packageJsonPath,
90
+ lockfiles: result.project.lockfiles,
91
+ legacyLockfiles: result.project.legacyLockfiles
92
+ },
93
+ diagnostics: result.diagnostics
94
+ });
95
+ //#endregion
96
+ //#region src/cli.ts
97
+ const VALID_FAIL_ON_LEVELS = new Set([
98
+ "blocker",
99
+ "risk",
100
+ "migration",
101
+ "win",
102
+ "none"
103
+ ]);
104
+ const HELP_TEXT = `Usage: bun-doctor [directory] [options]
105
+
106
+ Options:
107
+ --json output a structured JSON report
108
+ --score output only the numeric score
109
+ --verbose show every diagnostic
110
+ --no-package skip package/config/dependency checks
111
+ --no-code skip source code checks
112
+ --fail-on <level> exit non-zero on blocker, risk, migration, or none
113
+ -v, --version print version
114
+ -h, --help print help
115
+
116
+ Commands:
117
+ bun-doctor install [directory] [--dry-run]
118
+ `;
119
+ const shouldFail = (levels, failOn) => {
120
+ if (failOn === "none") return false;
121
+ if (failOn === "win") return levels.size > 0;
122
+ if (failOn === "migration") return levels.has("blocker") || levels.has("risk") || levels.has("migration");
123
+ if (failOn === "risk") return levels.has("blocker") || levels.has("risk");
124
+ return levels.has("blocker");
125
+ };
126
+ const parseFailOn = (value) => {
127
+ if (!value) return "blocker";
128
+ if (VALID_FAIL_ON_LEVELS.has(value)) return value;
129
+ throw new Error(`Invalid --fail-on value: ${value}. Expected blocker, risk, migration, win, or none.`);
130
+ };
131
+ const parseCli = (argv) => {
132
+ const parsed = parseArgs({
133
+ args: argv,
134
+ allowPositionals: true,
135
+ options: {
136
+ json: {
137
+ type: "boolean",
138
+ default: false
139
+ },
140
+ score: {
141
+ type: "boolean",
142
+ default: false
143
+ },
144
+ verbose: {
145
+ type: "boolean",
146
+ default: false
147
+ },
148
+ "no-package": {
149
+ type: "boolean",
150
+ default: false
151
+ },
152
+ "no-code": {
153
+ type: "boolean",
154
+ default: false
155
+ },
156
+ "fail-on": {
157
+ type: "string",
158
+ default: "blocker"
159
+ },
160
+ version: {
161
+ type: "boolean",
162
+ short: "v",
163
+ default: false
164
+ },
165
+ help: {
166
+ type: "boolean",
167
+ short: "h",
168
+ default: false
169
+ }
170
+ }
171
+ });
172
+ if (parsed.values.help) {
173
+ process.stdout.write(`${HELP_TEXT}\n`);
174
+ process.exit(0);
175
+ }
176
+ if (parsed.values.version) {
177
+ process.stdout.write(`${VERSION}\n`);
178
+ process.exit(0);
179
+ }
180
+ return {
181
+ directory: path.resolve(parsed.positionals[0] ?? "."),
182
+ flags: {
183
+ json: Boolean(parsed.values.json),
184
+ score: Boolean(parsed.values.score),
185
+ verbose: Boolean(parsed.values.verbose),
186
+ packageChecks: !parsed.values["no-package"],
187
+ codeChecks: !parsed.values["no-code"],
188
+ failOn: parseFailOn(parsed.values["fail-on"])
189
+ }
190
+ };
191
+ };
192
+ const runInstallCommand = (argv) => {
193
+ const parsed = parseArgs({
194
+ args: argv,
195
+ allowPositionals: true,
196
+ options: {
197
+ "dry-run": {
198
+ type: "boolean",
199
+ default: false
200
+ },
201
+ help: {
202
+ type: "boolean",
203
+ short: "h",
204
+ default: false
205
+ }
206
+ }
207
+ });
208
+ if (parsed.values.help) {
209
+ process.stdout.write("Usage: bun-doctor install [directory] [--dry-run]\n");
210
+ return;
211
+ }
212
+ const skillPath = installSkill({
213
+ directory: path.resolve(parsed.positionals[0] ?? "."),
214
+ dryRun: Boolean(parsed.values["dry-run"])
215
+ });
216
+ const action = parsed.values["dry-run"] ? "Would install" : "Installed";
217
+ process.stdout.write(`${action} bun-doctor skill at ${skillPath}\n`);
218
+ };
219
+ const main = async () => {
220
+ const argv = process.argv.slice(2);
221
+ if (argv[0] === "install") {
222
+ runInstallCommand(argv.slice(1));
223
+ return;
224
+ }
225
+ const { directory, flags } = parseCli(argv);
226
+ const result = await scan(directory, {
227
+ packageChecks: flags.packageChecks,
228
+ codeChecks: flags.codeChecks
229
+ });
230
+ if (flags.score) process.stdout.write(`${result.score.score}\n`);
231
+ else if (flags.json) process.stdout.write(`${JSON.stringify(toJsonReport(result), null, 2)}\n`);
232
+ else process.stdout.write(`${formatTextReport(result, flags.verbose)}\n`);
233
+ if (shouldFail(new Set(result.diagnostics.map((diagnostic) => diagnostic.level)), flags.failOn)) process.exitCode = 1;
234
+ };
235
+ main().catch((error) => {
236
+ const message = error instanceof Error ? error.message : String(error);
237
+ process.stderr.write(`bun-doctor: ${message}\n`);
238
+ process.exitCode = 1;
239
+ });
240
+ //#endregion
241
+ export {};
242
+
243
+ //# sourceMappingURL=cli.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.mjs","names":[],"sources":["../src/install-skill.ts","../src/report.ts","../src/cli.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\ninterface InstallSkillOptions {\n directory: string;\n dryRun?: boolean;\n}\n\nconst SKILL_CONTENT = `---\nname: bun-doctor\ndescription: Use after making dependency, CI, package manager, test runner, or Node runtime changes in a project that uses or is migrating to Bun. Checks Bun readiness and migration risk.\nversion: \"1.0.0\"\n---\n\n# Bun Doctor\n\nRun \\`npx -y bun-doctor@latest . --verbose\\` after Bun migration changes and fix blockers before switching CI or runtime.\n`;\n\nexport const installSkill = (options: InstallSkillOptions): string => {\n const skillDirectory = path.join(options.directory, \".agents\", \"skills\", \"bun-doctor\");\n const skillPath = path.join(skillDirectory, \"SKILL.md\");\n\n if (options.dryRun) return skillPath;\n\n fs.mkdirSync(skillDirectory, { recursive: true });\n fs.writeFileSync(skillPath, SKILL_CONTENT, \"utf8\");\n return skillPath;\n};\n","import path from \"node:path\";\nimport type { Diagnostic, FindingCategory, ScanResult } from \"./types.js\";\nimport { toRelativePath } from \"./utils.js\";\n\nconst CATEGORY_ORDER: FindingCategory[] = [\"Blockers\", \"Risks\", \"Migration work\", \"Bun wins\"];\n\nconst LEVEL_SYMBOL: Record<Diagnostic[\"level\"], string> = {\n blocker: \"x\",\n risk: \"!\",\n migration: \"~\",\n win: \"+\",\n};\n\nconst groupByCategory = (diagnostics: Diagnostic[]): Map<FindingCategory, Diagnostic[]> => {\n const groups = new Map<FindingCategory, Diagnostic[]>();\n for (const diagnostic of diagnostics) {\n const existing = groups.get(diagnostic.category) ?? [];\n existing.push(diagnostic);\n groups.set(diagnostic.category, existing);\n }\n return groups;\n};\n\nconst formatLocation = (diagnostic: Diagnostic, rootDirectory: string): string => {\n const relativePath = toRelativePath(path.resolve(diagnostic.filePath), rootDirectory);\n return diagnostic.line > 0 ? `${relativePath}:${diagnostic.line}` : relativePath;\n};\n\nexport const formatTextReport = (result: ScanResult, verbose: boolean): string => {\n const lines: string[] = [];\n lines.push(`bun-doctor`);\n lines.push(`Project: ${result.project.packageName}`);\n lines.push(`Bun Readiness: ${result.score.score}/100 (${result.score.label})`);\n lines.push(\n `Findings: ${result.summary.blockers} blockers, ${result.summary.risks} risks, ${result.summary.migrations} migration, ${result.summary.wins} wins`,\n );\n lines.push(\"\");\n\n if (result.diagnostics.length === 0) {\n lines.push(\"No Bun migration findings.\");\n return lines.join(\"\\n\");\n }\n\n const groups = groupByCategory(result.diagnostics);\n for (const category of CATEGORY_ORDER) {\n const diagnostics = groups.get(category) ?? [];\n if (diagnostics.length === 0) continue;\n lines.push(`${category} (${diagnostics.length})`);\n const shownDiagnostics = verbose ? diagnostics : diagnostics.slice(0, 3);\n for (const diagnostic of shownDiagnostics) {\n lines.push(` ${LEVEL_SYMBOL[diagnostic.level]} ${diagnostic.title} [${diagnostic.ruleId}]`);\n lines.push(` ${diagnostic.message}`);\n if (diagnostic.help) lines.push(` ${diagnostic.help}`);\n lines.push(` ${formatLocation(diagnostic, result.project.rootDirectory)}`);\n lines.push(` Source: ${diagnostic.sources[0]}`);\n }\n if (!verbose && diagnostics.length > shownDiagnostics.length) {\n lines.push(` ... ${diagnostics.length - shownDiagnostics.length} more. Re-run with --verbose.`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trimEnd();\n};\n\nexport const toJsonReport = (result: ScanResult): object => ({\n schemaVersion: 1,\n ok: true,\n score: result.score,\n summary: result.summary,\n project: {\n name: result.project.packageName,\n rootDirectory: result.project.rootDirectory,\n packageJsonPath: result.project.packageJsonPath,\n lockfiles: result.project.lockfiles,\n legacyLockfiles: result.project.legacyLockfiles,\n },\n diagnostics: result.diagnostics,\n});\n","#!/usr/bin/env node\nimport { parseArgs } from \"node:util\";\nimport path from \"node:path\";\nimport { VERSION } from \"./constants.js\";\nimport { installSkill } from \"./install-skill.js\";\nimport { formatTextReport, toJsonReport } from \"./report.js\";\nimport { scan } from \"./scan.js\";\nimport type { FailOnLevel, ScanOptions } from \"./types.js\";\n\ninterface CliFlags {\n json: boolean;\n score: boolean;\n verbose: boolean;\n packageChecks: boolean;\n codeChecks: boolean;\n failOn: FailOnLevel;\n}\n\nconst VALID_FAIL_ON_LEVELS = new Set<FailOnLevel>([\"blocker\", \"risk\", \"migration\", \"win\", \"none\"]);\n\nconst HELP_TEXT = `Usage: bun-doctor [directory] [options]\n\nOptions:\n --json output a structured JSON report\n --score output only the numeric score\n --verbose show every diagnostic\n --no-package skip package/config/dependency checks\n --no-code skip source code checks\n --fail-on <level> exit non-zero on blocker, risk, migration, or none\n -v, --version print version\n -h, --help print help\n\nCommands:\n bun-doctor install [directory] [--dry-run]\n`;\n\nconst shouldFail = (levels: Set<string>, failOn: FailOnLevel): boolean => {\n if (failOn === \"none\") return false;\n if (failOn === \"win\") return levels.size > 0;\n if (failOn === \"migration\") return levels.has(\"blocker\") || levels.has(\"risk\") || levels.has(\"migration\");\n if (failOn === \"risk\") return levels.has(\"blocker\") || levels.has(\"risk\");\n return levels.has(\"blocker\");\n};\n\nconst parseFailOn = (value: string | undefined): FailOnLevel => {\n if (!value) return \"blocker\";\n if (VALID_FAIL_ON_LEVELS.has(value as FailOnLevel)) return value as FailOnLevel;\n throw new Error(`Invalid --fail-on value: ${value}. Expected blocker, risk, migration, win, or none.`);\n};\n\nconst parseCli = (argv: string[]): { directory: string; flags: CliFlags } => {\n const parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n json: { type: \"boolean\", default: false },\n score: { type: \"boolean\", default: false },\n verbose: { type: \"boolean\", default: false },\n \"no-package\": { type: \"boolean\", default: false },\n \"no-code\": { type: \"boolean\", default: false },\n \"fail-on\": { type: \"string\", default: \"blocker\" },\n version: { type: \"boolean\", short: \"v\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (parsed.values.help) {\n process.stdout.write(`${HELP_TEXT}\\n`);\n process.exit(0);\n }\n\n if (parsed.values.version) {\n process.stdout.write(`${VERSION}\\n`);\n process.exit(0);\n }\n\n return {\n directory: path.resolve(parsed.positionals[0] ?? \".\"),\n flags: {\n json: Boolean(parsed.values.json),\n score: Boolean(parsed.values.score),\n verbose: Boolean(parsed.values.verbose),\n packageChecks: !parsed.values[\"no-package\"],\n codeChecks: !parsed.values[\"no-code\"],\n failOn: parseFailOn(parsed.values[\"fail-on\"]),\n },\n };\n};\n\nconst runInstallCommand = (argv: string[]): void => {\n const parsed = parseArgs({\n args: argv,\n allowPositionals: true,\n options: {\n \"dry-run\": { type: \"boolean\", default: false },\n help: { type: \"boolean\", short: \"h\", default: false },\n },\n });\n\n if (parsed.values.help) {\n process.stdout.write(\"Usage: bun-doctor install [directory] [--dry-run]\\n\");\n return;\n }\n\n const directory = path.resolve(parsed.positionals[0] ?? \".\");\n const skillPath = installSkill({ directory, dryRun: Boolean(parsed.values[\"dry-run\"]) });\n const action = parsed.values[\"dry-run\"] ? \"Would install\" : \"Installed\";\n process.stdout.write(`${action} bun-doctor skill at ${skillPath}\\n`);\n};\n\nconst main = async (): Promise<void> => {\n const argv = process.argv.slice(2);\n if (argv[0] === \"install\") {\n runInstallCommand(argv.slice(1));\n return;\n }\n\n const { directory, flags } = parseCli(argv);\n const options: ScanOptions = {\n packageChecks: flags.packageChecks,\n codeChecks: flags.codeChecks,\n };\n const result = await scan(directory, options);\n\n if (flags.score) {\n process.stdout.write(`${result.score.score}\\n`);\n } else if (flags.json) {\n process.stdout.write(`${JSON.stringify(toJsonReport(result), null, 2)}\\n`);\n } else {\n process.stdout.write(`${formatTextReport(result, flags.verbose)}\\n`);\n }\n\n const levels = new Set(result.diagnostics.map((diagnostic) => diagnostic.level));\n if (shouldFail(levels, flags.failOn)) {\n process.exitCode = 1;\n }\n};\n\nmain().catch((error: unknown) => {\n const message = error instanceof Error ? error.message : String(error);\n process.stderr.write(`bun-doctor: ${message}\\n`);\n process.exitCode = 1;\n});\n"],"mappings":"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"}
@@ -0,0 +1,129 @@
1
+ //#region src/types.d.ts
2
+ type FindingLevel = "blocker" | "risk" | "migration" | "win";
3
+ type FindingCategory = "Blockers" | "Risks" | "Migration work" | "Bun wins";
4
+ type FailOnLevel = FindingLevel | "none";
5
+ interface PackageJson {
6
+ name?: string;
7
+ version?: string;
8
+ packageManager?: string;
9
+ scripts?: Record<string, string>;
10
+ dependencies?: Record<string, string>;
11
+ devDependencies?: Record<string, string>;
12
+ peerDependencies?: Record<string, string>;
13
+ optionalDependencies?: Record<string, string>;
14
+ trustedDependencies?: string[];
15
+ workspaces?: string[] | {
16
+ packages?: string[];
17
+ catalog?: unknown;
18
+ catalogs?: unknown;
19
+ };
20
+ catalog?: unknown;
21
+ catalogs?: unknown;
22
+ bunDoctor?: BunDoctorConfig;
23
+ }
24
+ interface BunDoctorConfig {
25
+ ignore?: {
26
+ rules?: string[];
27
+ files?: string[];
28
+ };
29
+ package?: boolean;
30
+ code?: boolean;
31
+ }
32
+ interface CompatEntry {
33
+ packageName: string;
34
+ severity: FindingLevel;
35
+ affectedRanges: string[];
36
+ bunVersions: string[];
37
+ platforms: Array<"darwin" | "linux" | "win32" | "all">;
38
+ confidence: "high" | "medium" | "low";
39
+ reason: string;
40
+ sources: string[];
41
+ lastVerified: string;
42
+ replacement?: string;
43
+ workaround?: string;
44
+ migrationHint?: string;
45
+ requiresTrustedDependency?: boolean;
46
+ }
47
+ interface SourceFile {
48
+ filePath: string;
49
+ content: string;
50
+ }
51
+ interface WorkflowFile {
52
+ filePath: string;
53
+ content: string;
54
+ }
55
+ interface BunfigInfo {
56
+ filePath: string;
57
+ content: string;
58
+ installIgnoreScripts?: boolean;
59
+ installFrozenLockfile?: boolean;
60
+ installAuto?: string;
61
+ installSecurityScanner?: string;
62
+ }
63
+ interface ProjectInfo {
64
+ rootDirectory: string;
65
+ packageJsonPath: string;
66
+ packageJson: PackageJson;
67
+ packageName: string;
68
+ dependencies: Record<string, string>;
69
+ trustedDependencies: Set<string>;
70
+ packageManifests: PackageManifest[];
71
+ lockfiles: string[];
72
+ legacyLockfiles: string[];
73
+ bunfig: BunfigInfo | null;
74
+ tsconfigPath: string | null;
75
+ tsconfig: Record<string, unknown> | null;
76
+ workflows: WorkflowFile[];
77
+ sourceFiles: SourceFile[];
78
+ pnpmWorkspacePath: string | null;
79
+ }
80
+ interface PackageManifest {
81
+ packageJsonPath: string;
82
+ packageJson: PackageJson;
83
+ packageName: string;
84
+ dependencies: Record<string, string>;
85
+ trustedDependencies: Set<string>;
86
+ }
87
+ interface Diagnostic {
88
+ ruleId: string;
89
+ title: string;
90
+ level: FindingLevel;
91
+ category: FindingCategory;
92
+ message: string;
93
+ filePath: string;
94
+ line: number;
95
+ sources: string[];
96
+ help?: string;
97
+ packageName?: string;
98
+ }
99
+ interface ScoreResult {
100
+ score: number;
101
+ label: "Ready" | "Close" | "Risky" | "Blocked";
102
+ }
103
+ interface ScanSummary {
104
+ blockers: number;
105
+ risks: number;
106
+ migrations: number;
107
+ wins: number;
108
+ }
109
+ interface ScanOptions {
110
+ packageChecks?: boolean;
111
+ codeChecks?: boolean;
112
+ configOverride?: BunDoctorConfig | null;
113
+ }
114
+ interface ScanResult {
115
+ project: ProjectInfo;
116
+ diagnostics: Diagnostic[];
117
+ score: ScoreResult;
118
+ summary: ScanSummary;
119
+ }
120
+ //#endregion
121
+ //#region src/scan.d.ts
122
+ declare const scan: (directory: string, options?: ScanOptions) => Promise<ScanResult>;
123
+ //#endregion
124
+ //#region src/score.d.ts
125
+ declare const calculateScore: (diagnostics: Diagnostic[]) => ScoreResult;
126
+ declare const summarizeDiagnostics: (diagnostics: Diagnostic[]) => ScanSummary;
127
+ //#endregion
128
+ export { type BunDoctorConfig, type CompatEntry, type Diagnostic, type FailOnLevel, type FindingCategory, type FindingLevel, type ScanOptions, type ScanResult, calculateScore, scan, summarizeDiagnostics };
129
+ //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs ADDED
@@ -0,0 +1,2 @@
1
+ import { n as calculateScore, r as summarizeDiagnostics, t as scan } from "./scan-BVcJTreL.mjs";
2
+ export { calculateScore, scan, summarizeDiagnostics };