bun-doctor 0.0.1 → 0.0.2

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/README.md CHANGED
@@ -1,26 +1,15 @@
1
1
  # Bun Doctor
2
2
 
3
- Diagnose Node-to-Bun migration readiness for JavaScript and TypeScript projects.
3
+ [![version](https://img.shields.io/npm/v/bun-doctor?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bun-doctor)
4
+ [![downloads](https://img.shields.io/npm/dt/bun-doctor.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/bun-doctor)
4
5
 
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
6
+ Scan a Node.js project to see how ready it is to move to Bun.
12
7
 
13
8
  ```bash
14
9
  npx -y bun-doctor@latest .
15
10
  ```
16
11
 
17
- Local development from this repo:
18
-
19
- ```bash
20
- bun install
21
- bun run build
22
- node dist/cli.mjs . --verbose
23
- ```
12
+ You get a 0-100 Bun Readiness score and a grouped list of Blockers, Risks, Migration work, and Bun wins. The scanner inspects package manager state, lockfiles, `bunfig.toml`, `tsconfig.json`, GitHub Actions workflows, dependency compatibility, and Bun-specific code risks.
24
13
 
25
14
  ## CLI
26
15
 
@@ -43,21 +32,41 @@ Commands:
43
32
 
44
33
  ## Scoring
45
34
 
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.
35
+ Each unique rule triggered subtracts from a starting score of 100:
36
+
37
+ | Level | Penalty |
38
+ | --- | ---: |
39
+ | Blocker | 12 |
40
+ | Risk | 5 |
41
+ | Migration | 2 |
42
+ | Win | 0 |
43
+
44
+ Wins are surfaced as optional Bun-native simplifications and never lower the score.
45
+
46
+ ## GitHub Action
47
+
48
+ Drop this into `.github/workflows/bun-doctor.yml`:
49
+
50
+ ```yaml
51
+ name: Bun Doctor
52
+ on:
53
+ pull_request:
54
+ push:
55
+ branches: [main]
56
+ jobs:
57
+ bun-doctor:
58
+ runs-on: ubuntu-latest
59
+ steps:
60
+ - uses: actions/checkout@v5
61
+ - uses: kylegrahammatzen/bun-doctor@main
62
+ ```
54
63
 
55
- ## Rule Sources
64
+ ## Sources
56
65
 
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.
66
+ Every diagnostic and compatibility entry carries at least one verifiable source: Bun documentation, the Node compatibility table, or an issue/test link. No source, no rule.
58
67
 
59
68
  ## Roadmap
60
69
 
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.
70
+ - MVP: CLI, JSON, score, package/config rules, code-risk scans, compatibility DB v0.
71
+ - v1.0: public eval harness that verifies compatibility entries across Bun versions and platforms ([docs/eval-harness.md](docs/eval-harness.md)).
63
72
  - Later: editor/linter plugin once rules prove useful in real migrations.
package/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { a as VERSION, i as toRelativePath, t as scan } from "./scan-BVcJTreL.mjs";
2
+ import { a as VERSION, i as toRelativePath, t as scan } from "./scan-BYClwRyn.mjs";
3
3
  import { parseArgs } from "node:util";
4
4
  import path from "node:path";
5
5
  import fs from "node:fs";
@@ -47,7 +47,7 @@ const groupByCategory = (diagnostics) => {
47
47
  };
48
48
  const formatLocation = (diagnostic, rootDirectory) => {
49
49
  const relativePath = toRelativePath(path.resolve(diagnostic.filePath), rootDirectory);
50
- return diagnostic.line > 0 ? `${relativePath}:${diagnostic.line}` : relativePath;
50
+ return diagnostic.line > 1 ? `${relativePath}:${diagnostic.line}` : relativePath;
51
51
  };
52
52
  const formatTextReport = (result, verbose) => {
53
53
  const lines = [];
@@ -69,8 +69,13 @@ const formatTextReport = (result, verbose) => {
69
69
  for (const diagnostic of shownDiagnostics) {
70
70
  lines.push(` ${LEVEL_SYMBOL[diagnostic.level]} ${diagnostic.title} [${diagnostic.ruleId}]`);
71
71
  lines.push(` ${diagnostic.message}`);
72
+ if (diagnostic.replacement) lines.push(` Replacement: ${diagnostic.replacement}`);
72
73
  if (diagnostic.help) lines.push(` ${diagnostic.help}`);
73
74
  lines.push(` ${formatLocation(diagnostic, result.project.rootDirectory)}`);
75
+ if (diagnostic.alsoIn && diagnostic.alsoIn.length > 0) {
76
+ const aggregated = diagnostic.alsoIn.map((alsoPath) => toRelativePath(path.resolve(alsoPath), result.project.rootDirectory)).join(", ");
77
+ lines.push(` Also in: ${aggregated}`);
78
+ }
74
79
  lines.push(` Source: ${diagnostic.sources[0]}`);
75
80
  }
76
81
  if (!verbose && diagnostics.length > shownDiagnostics.length) lines.push(` ... ${diagnostics.length - shownDiagnostics.length} more. Re-run with --verbose.`);
package/dist/cli.mjs.map CHANGED
@@ -1 +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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"}
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 > 1 ? `${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.replacement) lines.push(` Replacement: ${diagnostic.replacement}`);\n if (diagnostic.help) lines.push(` ${diagnostic.help}`);\n lines.push(` ${formatLocation(diagnostic, result.project.rootDirectory)}`);\n if (diagnostic.alsoIn && diagnostic.alsoIn.length > 0) {\n const aggregated = diagnostic.alsoIn\n .map((alsoPath) => toRelativePath(path.resolve(alsoPath), result.project.rootDirectory))\n .join(\", \");\n lines.push(` Also in: ${aggregated}`);\n }\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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"}
package/dist/index.d.mts CHANGED
@@ -32,13 +32,13 @@ interface BunDoctorConfig {
32
32
  interface CompatEntry {
33
33
  packageName: string;
34
34
  severity: FindingLevel;
35
- affectedRanges: string[];
36
- bunVersions: string[];
37
- platforms: Array<"darwin" | "linux" | "win32" | "all">;
38
35
  confidence: "high" | "medium" | "low";
39
36
  reason: string;
40
37
  sources: string[];
41
38
  lastVerified: string;
39
+ affectedRanges?: string[];
40
+ bunVersions?: string[];
41
+ platforms?: Array<"darwin" | "linux" | "win32" | "all">;
42
42
  replacement?: string;
43
43
  workaround?: string;
44
44
  migrationHint?: string;
@@ -73,9 +73,11 @@ interface ProjectInfo {
73
73
  bunfig: BunfigInfo | null;
74
74
  tsconfigPath: string | null;
75
75
  tsconfig: Record<string, unknown> | null;
76
+ tsconfigContent: string | null;
76
77
  workflows: WorkflowFile[];
77
78
  sourceFiles: SourceFile[];
78
79
  pnpmWorkspacePath: string | null;
80
+ pnpmWorkspaceContent: string | null;
79
81
  }
80
82
  interface PackageManifest {
81
83
  packageJsonPath: string;
@@ -83,6 +85,7 @@ interface PackageManifest {
83
85
  packageName: string;
84
86
  dependencies: Record<string, string>;
85
87
  trustedDependencies: Set<string>;
88
+ manifestContent: string;
86
89
  }
87
90
  interface Diagnostic {
88
91
  ruleId: string;
@@ -95,6 +98,8 @@ interface Diagnostic {
95
98
  sources: string[];
96
99
  help?: string;
97
100
  packageName?: string;
101
+ replacement?: string;
102
+ alsoIn?: string[];
98
103
  }
99
104
  interface ScoreResult {
100
105
  score: number;
package/dist/index.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as calculateScore, r as summarizeDiagnostics, t as scan } from "./scan-BVcJTreL.mjs";
1
+ import { n as calculateScore, r as summarizeDiagnostics, t as scan } from "./scan-BYClwRyn.mjs";
2
2
  export { calculateScore, scan, summarizeDiagnostics };