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 +21 -0
- package/README.md +63 -0
- package/action.yml +52 -0
- package/bin/bun-doctor.js +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +243 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/index.d.mts +129 -0
- package/dist/index.mjs +2 -0
- package/dist/scan-BVcJTreL.mjs +749 -0
- package/dist/scan-BVcJTreL.mjs.map +1 -0
- package/docs/compat-db.md +30 -0
- package/docs/rule-spec.md +57 -0
- package/package.json +61 -0
- package/skills/bun-doctor/SKILL.md +30 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-BVcJTreL.mjs","names":[],"sources":["../src/constants.ts","../src/utils.ts","../src/config.ts","../src/project.ts","../src/compat-db.ts","../src/rules.ts","../src/score.ts","../src/scan.ts"],"sourcesContent":["export const VERSION = process.env.VERSION ?? \"0.0.0\";\n\nexport const PERFECT_SCORE = 100;\nexport const BLOCKER_RULE_PENALTY = 12;\nexport const RISK_RULE_PENALTY = 5;\nexport const MIGRATION_RULE_PENALTY = 2;\n\nexport const SCORE_READY_THRESHOLD = 90;\nexport const SCORE_CLOSE_THRESHOLD = 75;\nexport const SCORE_RISKY_THRESHOLD = 50;\n\nexport const BUN_DOCS = {\n autoInstall: \"https://bun.com/docs/runtime/auto-install\",\n bunfig: \"https://bun.com/docs/runtime/bunfig\",\n ci: \"https://bun.com/docs/guides/install/cicd\",\n catalogs: \"https://bun.com/docs/pm/catalogs\",\n environmentVariables: \"https://bun.com/docs/runtime/environment-variables\",\n hashing: \"https://bun.com/docs/runtime/hashing\",\n lifecycle: \"https://bun.com/docs/pm/lifecycle\",\n lockfile: \"https://bun.com/docs/pm/lockfile\",\n nodeCompatibility: \"https://bun.com/docs/runtime/nodejs-compat\",\n securityScanner: \"https://bun.com/docs/pm/security-scanner-api\",\n sqlite: \"https://bun.com/docs/runtime/sqlite\",\n testConfiguration: \"https://bun.com/docs/test/configuration\",\n testRunner: \"https://bun.com/docs/test\",\n typescript: \"https://bun.com/docs/typescript\",\n workspaces: \"https://bun.com/docs/pm/workspaces\",\n};\n\nexport const IGNORED_DIRECTORIES = new Set([\n \".git\",\n \".next\",\n \".turbo\",\n \"build\",\n \"coverage\",\n \"dist\",\n \"node_modules\",\n \"vendor\",\n]);\n\nexport const SOURCE_FILE_EXTENSIONS = new Set([\n \".cjs\",\n \".cts\",\n \".js\",\n \".jsx\",\n \".mjs\",\n \".mts\",\n \".ts\",\n \".tsx\",\n]);\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { IGNORED_DIRECTORIES, SOURCE_FILE_EXTENSIONS } from \"./constants.js\";\n\nexport const isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nexport const fileExists = (filePath: string): boolean => {\n try {\n return fs.statSync(filePath).isFile();\n } catch {\n return false;\n }\n};\n\nexport const directoryExists = (directoryPath: string): boolean => {\n try {\n return fs.statSync(directoryPath).isDirectory();\n } catch {\n return false;\n }\n};\n\nexport const readJsonFile = <T>(filePath: string): T | null => {\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\")) as T;\n } catch {\n return null;\n }\n};\n\nexport const collectFiles = (rootDirectory: string, predicate: (filePath: string) => boolean): string[] => {\n const files: string[] = [];\n const stack = [rootDirectory];\n\n while (stack.length > 0) {\n const currentDirectory = stack.pop();\n if (!currentDirectory) continue;\n\n let entries: fs.Dirent[] = [];\n try {\n entries = fs.readdirSync(currentDirectory, { withFileTypes: true });\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n const entryPath = path.join(currentDirectory, entry.name);\n if (entry.isDirectory()) {\n if (!entry.name.startsWith(\".\") && !IGNORED_DIRECTORIES.has(entry.name)) {\n stack.push(entryPath);\n }\n continue;\n }\n if (entry.isFile() && predicate(entryPath)) {\n files.push(entryPath);\n }\n }\n }\n\n return files.sort();\n};\n\nexport const isSourceFilePath = (filePath: string): boolean => {\n if (filePath.endsWith(\".d.ts\")) return false;\n return SOURCE_FILE_EXTENSIONS.has(path.extname(filePath));\n};\n\nexport const toRelativePath = (filePath: string, rootDirectory: string): string =>\n path.relative(rootDirectory, filePath).replaceAll(path.sep, \"/\") || \".\";\n\nexport const findLineNumber = (content: string, pattern: RegExp): number => {\n const lines = content.split(/\\r?\\n/);\n const flags = pattern.flags.replace(\"g\", \"\");\n const linePattern = new RegExp(pattern.source, flags);\n\n for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {\n if (linePattern.test(lines[lineIndex] ?? \"\")) return lineIndex + 1;\n }\n\n return 1;\n};\n\nexport const wildcardToRegExp = (pattern: string): RegExp => {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\")\n .replaceAll(\"**\", \"__DOUBLE_STAR__\")\n .replaceAll(\"*\", \"[^/]*\")\n .replaceAll(\"__DOUBLE_STAR__\", \".*\");\n return new RegExp(`^${escaped}$`);\n};\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type { BunDoctorConfig, Diagnostic, PackageJson } from \"./types.js\";\nimport { readJsonFile, toRelativePath, wildcardToRegExp } from \"./utils.js\";\n\nconst CONFIG_FILE_NAME = \"bun-doctor.config.json\";\n\nexport const loadConfig = (rootDirectory: string, packageJson: PackageJson): BunDoctorConfig => {\n const configPath = path.join(rootDirectory, CONFIG_FILE_NAME);\n if (fs.existsSync(configPath)) {\n return readJsonFile<BunDoctorConfig>(configPath) ?? {};\n }\n return packageJson.bunDoctor ?? {};\n};\n\nexport const filterIgnoredDiagnostics = (\n diagnostics: Diagnostic[],\n config: BunDoctorConfig,\n rootDirectory: string,\n): Diagnostic[] => {\n const ignoredRules = new Set(config.ignore?.rules ?? []);\n const ignoredFilePatterns = (config.ignore?.files ?? []).map(wildcardToRegExp);\n\n return diagnostics.filter((diagnostic) => {\n if (ignoredRules.has(diagnostic.ruleId)) return false;\n const relativePath = toRelativePath(diagnostic.filePath, rootDirectory);\n return !ignoredFilePatterns.some((pattern) => pattern.test(relativePath));\n });\n};\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type {\n BunfigInfo,\n PackageJson,\n PackageManifest,\n ProjectInfo,\n SourceFile,\n WorkflowFile,\n} from \"./types.js\";\nimport { collectFiles, fileExists, isSourceFilePath, readJsonFile } from \"./utils.js\";\n\nconst LOCKFILE_NAMES = [\"bun.lock\", \"bun.lockb\"];\nconst LEGACY_LOCKFILE_NAMES = [\"package-lock.json\", \"pnpm-lock.yaml\", \"yarn.lock\"];\nconst WORKFLOW_EXTENSIONS = new Set([\".yml\", \".yaml\"]);\n\nconst collectDependencies = (packageJson: PackageJson): Record<string, string> => ({\n ...packageJson.optionalDependencies,\n ...packageJson.peerDependencies,\n ...packageJson.devDependencies,\n ...packageJson.dependencies,\n});\n\nconst toPackageManifest = (packageJsonPath: string, packageJson: PackageJson): PackageManifest => ({\n packageJsonPath,\n packageJson,\n packageName: packageJson.name ?? path.basename(path.dirname(packageJsonPath)),\n dependencies: collectDependencies(packageJson),\n trustedDependencies: new Set(packageJson.trustedDependencies ?? []),\n});\n\nconst collectPackageManifests = (\n rootDirectory: string,\n rootPackageJsonPath: string,\n rootPackageJson: PackageJson,\n): PackageManifest[] => {\n const rootManifest = toPackageManifest(rootPackageJsonPath, rootPackageJson);\n const manifestPaths = collectFiles(\n rootDirectory,\n (filePath) => path.basename(filePath) === \"package.json\" && filePath !== rootPackageJsonPath,\n );\n const workspaceManifests = manifestPaths.flatMap((manifestPath) => {\n const packageJson = readJsonFile<PackageJson>(manifestPath);\n if (!packageJson || typeof packageJson !== \"object\" || Array.isArray(packageJson)) return [];\n return [toPackageManifest(manifestPath, packageJson)];\n });\n return [rootManifest, ...workspaceManifests];\n};\n\nconst mergeDependencies = (manifests: PackageManifest[]): Record<string, string> => {\n const dependencies: Record<string, string> = {};\n for (const manifest of manifests) {\n Object.assign(dependencies, manifest.dependencies);\n }\n return dependencies;\n};\n\nconst mergeTrustedDependencies = (manifests: PackageManifest[]): Set<string> => {\n const trustedDependencies = new Set<string>();\n for (const manifest of manifests) {\n for (const packageName of manifest.trustedDependencies) {\n trustedDependencies.add(packageName);\n }\n }\n return trustedDependencies;\n};\n\nconst parseBooleanTomlValue = (content: string, key: string): boolean | undefined => {\n const match = content.match(new RegExp(`^\\\\s*${key}\\\\s*=\\\\s*(true|false)\\\\s*$`, \"m\"));\n if (!match?.[1]) return undefined;\n return match[1] === \"true\";\n};\n\nconst parseStringTomlValue = (content: string, key: string): string | undefined => {\n const match = content.match(new RegExp(`^\\\\s*${key}\\\\s*=\\\\s*[\"']([^\"']+)[\"']\\\\s*$`, \"m\"));\n return match?.[1];\n};\n\nconst parseBunfig = (rootDirectory: string): BunfigInfo | null => {\n const filePath = path.join(rootDirectory, \"bunfig.toml\");\n if (!fileExists(filePath)) return null;\n const content = fs.readFileSync(filePath, \"utf8\");\n return {\n filePath,\n content,\n installIgnoreScripts: parseBooleanTomlValue(content, \"ignoreScripts\"),\n installFrozenLockfile: parseBooleanTomlValue(content, \"frozenLockfile\"),\n installAuto: parseStringTomlValue(content, \"auto\"),\n installSecurityScanner: parseStringTomlValue(content, \"scanner\"),\n };\n};\n\nconst readSourceFiles = (rootDirectory: string): SourceFile[] =>\n collectFiles(rootDirectory, isSourceFilePath).map((filePath) => ({\n filePath,\n content: fs.readFileSync(filePath, \"utf8\"),\n }));\n\nconst readWorkflowFiles = (rootDirectory: string): WorkflowFile[] => {\n const workflowDirectory = path.join(rootDirectory, \".github\", \"workflows\");\n if (!fs.existsSync(workflowDirectory)) return [];\n return collectFiles(workflowDirectory, (filePath) => WORKFLOW_EXTENSIONS.has(path.extname(filePath))).map(\n (filePath) => ({ filePath, content: fs.readFileSync(filePath, \"utf8\") }),\n );\n};\n\nconst readTsconfig = (rootDirectory: string): { path: string | null; config: Record<string, unknown> | null } => {\n const tsconfigPath = path.join(rootDirectory, \"tsconfig.json\");\n if (!fileExists(tsconfigPath)) return { path: null, config: null };\n const config = readJsonFile<Record<string, unknown>>(tsconfigPath);\n return { path: tsconfigPath, config };\n};\n\nconst findPackageJsonPath = (startDirectory: string): string => {\n const packageJsonPath = path.join(startDirectory, \"package.json\");\n if (fileExists(packageJsonPath)) return packageJsonPath;\n throw new Error(`No package.json found in ${startDirectory}`);\n};\n\nexport const discoverProject = (directory: string): ProjectInfo => {\n const rootDirectory = path.resolve(directory);\n const packageJsonPath = findPackageJsonPath(rootDirectory);\n const packageJson = readJsonFile<PackageJson>(packageJsonPath);\n if (!packageJson || typeof packageJson !== \"object\" || Array.isArray(packageJson)) {\n throw new Error(`Could not parse ${packageJsonPath}`);\n }\n\n const lockfiles = LOCKFILE_NAMES.filter((lockfileName) => fileExists(path.join(rootDirectory, lockfileName)));\n const legacyLockfiles = LEGACY_LOCKFILE_NAMES.filter((lockfileName) =>\n fileExists(path.join(rootDirectory, lockfileName)),\n );\n const packageManifests = collectPackageManifests(rootDirectory, packageJsonPath, packageJson);\n const tsconfig = readTsconfig(rootDirectory);\n const pnpmWorkspacePath = path.join(rootDirectory, \"pnpm-workspace.yaml\");\n\n return {\n rootDirectory,\n packageJsonPath,\n packageJson,\n packageName: packageJson.name ?? path.basename(rootDirectory),\n dependencies: mergeDependencies(packageManifests),\n trustedDependencies: mergeTrustedDependencies(packageManifests),\n packageManifests,\n lockfiles,\n legacyLockfiles,\n bunfig: parseBunfig(rootDirectory),\n tsconfigPath: tsconfig.path,\n tsconfig: tsconfig.config,\n workflows: readWorkflowFiles(rootDirectory),\n sourceFiles: readSourceFiles(rootDirectory),\n pnpmWorkspacePath: fileExists(pnpmWorkspacePath) ? pnpmWorkspacePath : null,\n };\n};\n","import { BUN_DOCS } from \"./constants.js\";\nimport type { CompatEntry } from \"./types.js\";\n\nexport const COMPAT_DB: CompatEntry[] = [\n {\n packageName: \"sqlite3\",\n severity: \"risk\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"medium\",\n reason: \"Native SQLite packages can depend on lifecycle scripts and Node native addon behavior. Bun ships a native SQLite driver.\",\n sources: [BUN_DOCS.sqlite, BUN_DOCS.lifecycle],\n lastVerified: \"2026-05-12\",\n replacement: \"bun:sqlite\",\n migrationHint: \"Evaluate replacing sqlite3 usage with Database from bun:sqlite.\",\n requiresTrustedDependency: true,\n },\n {\n packageName: \"better-sqlite3\",\n severity: \"risk\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"medium\",\n reason: \"Native SQLite packages can be sensitive to lifecycle script and Node-API behavior. Bun ships a native SQLite driver.\",\n sources: [BUN_DOCS.sqlite, BUN_DOCS.lifecycle, BUN_DOCS.nodeCompatibility],\n lastVerified: \"2026-05-12\",\n replacement: \"bun:sqlite\",\n workaround: \"If you keep better-sqlite3, verify install and runtime on every target platform.\",\n migrationHint: \"Evaluate replacing better-sqlite3 usage with Database from bun:sqlite.\",\n requiresTrustedDependency: true,\n },\n {\n packageName: \"node-sass\",\n severity: \"risk\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"high\",\n reason: \"node-sass commonly relies on native install/build behavior. Bun does not run arbitrary lifecycle scripts by default.\",\n sources: [BUN_DOCS.lifecycle],\n lastVerified: \"2026-05-12\",\n replacement: \"sass\",\n workaround: \"Prefer Dart Sass. If you keep node-sass, explicitly trust it and verify platform installs.\",\n migrationHint: \"Replace node-sass with sass where possible.\",\n requiresTrustedDependency: true,\n },\n {\n packageName: \"dotenv\",\n severity: \"win\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"high\",\n reason: \"Bun automatically loads .env files, so many dotenv usages can be removed.\",\n sources: [BUN_DOCS.environmentVariables],\n lastVerified: \"2026-05-12\",\n replacement: \"Bun built-in .env loading\",\n migrationHint: \"Remove dotenv/config bootstrap code when Bun's automatic .env loading covers the same files.\",\n },\n {\n packageName: \"node-fetch\",\n severity: \"win\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"high\",\n reason: \"Bun provides a native fetch implementation through Web APIs.\",\n sources: [BUN_DOCS.nodeCompatibility],\n lastVerified: \"2026-05-12\",\n replacement: \"global fetch\",\n migrationHint: \"Prefer global fetch for new Bun-targeted code.\",\n },\n {\n packageName: \"cross-fetch\",\n severity: \"win\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"high\",\n reason: \"Bun provides native Request, Response, Headers, and fetch Web APIs.\",\n sources: [BUN_DOCS.nodeCompatibility],\n lastVerified: \"2026-05-12\",\n replacement: \"global fetch\",\n migrationHint: \"Prefer global fetch when all target runtimes provide it.\",\n },\n {\n packageName: \"bcrypt\",\n severity: \"win\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"medium\",\n reason: \"Bun ships password hashing APIs, which can remove native bcrypt install complexity in some apps.\",\n sources: [BUN_DOCS.hashing, BUN_DOCS.lifecycle],\n lastVerified: \"2026-05-12\",\n replacement: \"Bun.password\",\n migrationHint: \"Evaluate Bun.password for new Bun-only password hashing code.\",\n requiresTrustedDependency: true,\n },\n {\n packageName: \"ts-node\",\n severity: \"migration\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"high\",\n reason: \"Bun runs TypeScript files directly, so ts-node is often unnecessary after migration.\",\n sources: [BUN_DOCS.typescript],\n lastVerified: \"2026-05-12\",\n replacement: \"bun run file.ts\",\n migrationHint: \"Replace ts-node scripts with bun run or direct bun execution where possible.\",\n },\n {\n packageName: \"tsx\",\n severity: \"migration\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"high\",\n reason: \"Bun runs TypeScript files directly and has watch/hot modes.\",\n sources: [BUN_DOCS.typescript],\n lastVerified: \"2026-05-12\",\n replacement: \"bun run file.ts\",\n migrationHint: \"Replace tsx scripts with bun run where Bun is the target runtime.\",\n },\n {\n packageName: \"nodemon\",\n severity: \"migration\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"high\",\n reason: \"Bun has built-in watch and hot reload modes for many runtime workflows.\",\n sources: [\"https://bun.com/docs/runtime/watch-mode\"],\n lastVerified: \"2026-05-12\",\n replacement: \"bun --watch or bun --hot\",\n migrationHint: \"Replace nodemon development scripts with bun --watch or bun --hot when behavior matches.\",\n },\n {\n packageName: \"jest\",\n severity: \"migration\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"medium\",\n reason: \"Bun ships a Jest-compatible test runner, but config/setup behavior needs migration review.\",\n sources: [BUN_DOCS.testRunner, BUN_DOCS.testConfiguration],\n lastVerified: \"2026-05-12\",\n replacement: \"bun test\",\n workaround: \"Keep Jest if you rely on unsupported Jest-specific behavior.\",\n migrationHint: \"Audit jest.config and setup files before replacing test scripts with bun test.\",\n },\n {\n packageName: \"webpack\",\n severity: \"win\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"medium\",\n reason: \"Bun includes a native bundler for many application and library workflows.\",\n sources: [\"https://bun.com/docs/bundler\"],\n lastVerified: \"2026-05-12\",\n replacement: \"bun build\",\n migrationHint: \"Evaluate bun build for simple library or app bundles before keeping webpack by default.\",\n },\n {\n packageName: \"esbuild\",\n severity: \"win\",\n affectedRanges: [\"*\"],\n bunVersions: [\">=1.0.0\"],\n platforms: [\"all\"],\n confidence: \"medium\",\n reason: \"Bun includes a native bundler and provides an esbuild migration guide.\",\n sources: [\"https://bun.com/docs/bundler/esbuild\"],\n lastVerified: \"2026-05-12\",\n replacement: \"bun build\",\n migrationHint: \"Evaluate bun build for build scripts that only need basic esbuild behavior.\",\n },\n];\n","import path from \"node:path\";\nimport { BUN_DOCS } from \"./constants.js\";\nimport { COMPAT_DB } from \"./compat-db.js\";\nimport type {\n Diagnostic,\n FindingCategory,\n FindingLevel,\n PackageManifest,\n ProjectInfo,\n} from \"./types.js\";\nimport { findLineNumber, isPlainObject } from \"./utils.js\";\n\nconst CATEGORY_BY_LEVEL: Record<FindingLevel, FindingCategory> = {\n blocker: \"Blockers\",\n risk: \"Risks\",\n migration: \"Migration work\",\n win: \"Bun wins\",\n};\n\ninterface DiagnosticInput {\n ruleId: string;\n title: string;\n level: FindingLevel;\n message: string;\n filePath: string;\n sources: string[];\n line?: number;\n help?: string;\n packageName?: string;\n}\n\ninterface CodeRule {\n ruleId: string;\n title: string;\n level: FindingLevel;\n pattern: RegExp;\n message: string;\n sources: string[];\n help?: string;\n}\n\nconst createDiagnostic = (input: DiagnosticInput): Diagnostic => {\n if (input.sources.length === 0) {\n throw new Error(`Rule ${input.ruleId} is missing a source`);\n }\n return {\n ruleId: input.ruleId,\n title: input.title,\n level: input.level,\n category: CATEGORY_BY_LEVEL[input.level],\n message: input.message,\n filePath: input.filePath,\n line: input.line ?? 1,\n sources: input.sources,\n help: input.help,\n packageName: input.packageName,\n };\n};\n\nconst getCompilerOptions = (project: ProjectInfo): Record<string, unknown> => {\n const compilerOptions = project.tsconfig?.compilerOptions;\n return isPlainObject(compilerOptions) ? compilerOptions : {};\n};\n\nconst usesBunGlobal = (project: ProjectInfo): boolean =>\n project.sourceFiles.some((sourceFile) => /\\bBun\\.|from\\s+[\"']bun[\"']|require\\([\"']bun[\"']\\)/.test(sourceFile.content));\n\nconst hasDependency = (project: ProjectInfo, packageName: string): boolean =>\n Boolean(project.dependencies[packageName]);\n\nconst findDependencyManifests = (project: ProjectInfo, packageName: string): PackageManifest[] =>\n project.packageManifests.filter((manifest) => Boolean(manifest.dependencies[packageName]));\n\nconst hasPackageJsonWorkspaces = (project: ProjectInfo): boolean => Boolean(project.packageJson.workspaces);\n\nconst hasCatalogReference = (project: ProjectInfo): boolean =>\n Object.values(project.dependencies).some((version) => version.startsWith(\"catalog:\"));\n\nconst hasPackageJsonCatalog = (project: ProjectInfo): boolean =>\n Boolean(project.packageJson.catalog) ||\n Boolean(project.packageJson.catalogs) ||\n (isPlainObject(project.packageJson.workspaces) &&\n (Boolean(project.packageJson.workspaces.catalog) || Boolean(project.packageJson.workspaces.catalogs)));\n\nconst workflowUsesBun = (content: string): boolean => /\\bbun\\s+(install|run|test|build|x)\\b/.test(content);\nconst workflowUsesSetupBun = (content: string): boolean => /oven-sh\\/setup-bun@/.test(content);\nconst workflowUsesLegacyInstall = (content: string): boolean =>\n /\\b(npm ci|npm install|pnpm install|yarn install|yarn --frozen-lockfile)\\b/.test(content);\nconst workflowUsesUnfrozenBunInstall = (content: string): boolean =>\n /^\\s*(-\\s*)?run:\\s*bun install\\s*$/m.test(content) || /\\bbun install\\b(?![^\\n]*--frozen-lockfile)/.test(content);\n\nexport const runPackageRules = (project: ProjectInfo): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n const packageJsonPath = project.packageJsonPath;\n const hasBunLock = project.lockfiles.includes(\"bun.lock\");\n const hasBunLockb = project.lockfiles.includes(\"bun.lockb\");\n const hasAnyBunLock = hasBunLock || hasBunLockb;\n\n if (!hasAnyBunLock) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/lockfile-missing\",\n title: \"Missing Bun lockfile\",\n level: \"migration\",\n message: \"This project has no bun.lock. Commit Bun's lockfile before treating installs or CI as reproducible under Bun.\",\n filePath: packageJsonPath,\n sources: [BUN_DOCS.lockfile],\n help: \"Run bun install and commit bun.lock.\",\n }),\n );\n }\n\n if (hasBunLockb) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/legacy-lockb\",\n title: \"Legacy binary Bun lockfile\",\n level: \"migration\",\n message: \"bun.lockb is the legacy binary lockfile format. Bun v1.2 defaults to the text-based bun.lock.\",\n filePath: path.join(project.rootDirectory, \"bun.lockb\"),\n sources: [BUN_DOCS.lockfile],\n help: \"Migrate with bun install --save-text-lockfile --frozen-lockfile --lockfile-only, then remove bun.lockb after verification.\",\n }),\n );\n }\n\n if (hasAnyBunLock && project.legacyLockfiles.length > 0) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/mixed-lockfiles\",\n title: \"Mixed package-manager lockfiles\",\n level: \"risk\",\n message: `Bun lockfile exists alongside ${project.legacyLockfiles.join(\", \")}. Mixed lockfiles make it unclear which package manager owns dependency resolution.`,\n filePath: packageJsonPath,\n sources: [BUN_DOCS.lockfile],\n help: \"Keep legacy lockfiles only if another supported workflow still owns them; otherwise remove them after validating bun.lock.\",\n }),\n );\n }\n\n if (!project.packageJson.packageManager?.startsWith(\"bun@\")) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/package-manager-field\",\n title: \"packageManager does not pin Bun\",\n level: \"migration\",\n message: \"package.json does not pin Bun in packageManager, so contributors and CI may use different package managers.\",\n filePath: packageJsonPath,\n sources: [BUN_DOCS.lockfile],\n help: \"Set packageManager to the Bun version used by the project, for example bun@1.3.11.\",\n }),\n );\n }\n\n if (project.pnpmWorkspacePath && !hasPackageJsonWorkspaces(project)) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/pnpm-workspace-only\",\n title: \"Workspaces only declared for pnpm\",\n level: \"blocker\",\n message: \"Bun reads workspaces from package.json. A pnpm-workspace.yaml without package.json workspaces will not define Bun workspaces.\",\n filePath: project.pnpmWorkspacePath,\n sources: [BUN_DOCS.workspaces],\n help: \"Move workspace globs into package.json workspaces before relying on bun install at the repo root.\",\n }),\n );\n }\n\n if (hasCatalogReference(project) && !hasPackageJsonCatalog(project)) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/catalog-without-package-json-catalog\",\n title: \"Catalog references need Bun catalog definitions\",\n level: \"blocker\",\n message: \"This project uses catalog: dependency references, but no Bun catalog or catalogs definition was found in package.json.\",\n filePath: packageJsonPath,\n sources: [BUN_DOCS.catalogs],\n help: \"Define catalog or catalogs in package.json, preferably under workspaces for monorepos.\",\n }),\n );\n }\n\n for (const workflow of project.workflows) {\n if (workflowUsesBun(workflow.content) && !workflowUsesSetupBun(workflow.content)) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/ci-missing-setup-bun\",\n title: \"CI uses Bun without setup-bun\",\n level: \"blocker\",\n message: \"This workflow runs bun commands but does not install Bun with oven-sh/setup-bun.\",\n filePath: workflow.filePath,\n line: findLineNumber(workflow.content, /\\bbun\\s+(install|run|test|build|x)\\b/),\n sources: [BUN_DOCS.ci],\n help: \"Add oven-sh/setup-bun before bun commands in GitHub Actions.\",\n }),\n );\n }\n\n if (hasAnyBunLock && workflowUsesLegacyInstall(workflow.content)) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/ci-uses-legacy-package-manager\",\n title: \"CI still installs with another package manager\",\n level: \"migration\",\n message: \"This workflow uses npm, pnpm, or yarn install even though the project has a Bun lockfile.\",\n filePath: workflow.filePath,\n line: findLineNumber(workflow.content, /\\b(npm ci|npm install|pnpm install|yarn install|yarn --frozen-lockfile)\\b/),\n sources: [BUN_DOCS.ci],\n help: \"Switch Bun-owned CI jobs to bun install --frozen-lockfile.\",\n }),\n );\n }\n\n if (workflowUsesUnfrozenBunInstall(workflow.content)) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/ci-install-not-frozen\",\n title: \"CI Bun install is not frozen\",\n level: \"risk\",\n message: \"This workflow runs bun install without --frozen-lockfile, so CI can update bun.lock instead of verifying it.\",\n filePath: workflow.filePath,\n line: findLineNumber(workflow.content, /\\bbun install\\b/),\n sources: [BUN_DOCS.bunfig, BUN_DOCS.ci],\n help: \"Use bun install --frozen-lockfile in CI.\",\n }),\n );\n }\n }\n\n if (usesBunGlobal(project) && !hasDependency(project, \"@types/bun\")) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/types-package-missing\",\n title: \"Bun types are missing\",\n level: \"migration\",\n message: \"Source files reference Bun APIs, but @types/bun is not installed.\",\n filePath: packageJsonPath,\n sources: [BUN_DOCS.typescript],\n help: \"Install @types/bun as a dev dependency.\",\n }),\n );\n }\n\n const compilerOptions = getCompilerOptions(project);\n const compilerTypes = compilerOptions.types;\n if (hasDependency(project, \"@types/bun\") && Array.isArray(compilerTypes) && !compilerTypes.includes(\"bun\")) {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/tsconfig-types-missing-bun\",\n title: \"tsconfig types excludes Bun\",\n level: \"risk\",\n message: \"@types/bun is installed, but compilerOptions.types does not include bun. TypeScript 6+ requires explicit Bun types in this mode.\",\n filePath: project.tsconfigPath ?? packageJsonPath,\n sources: [BUN_DOCS.typescript],\n help: \"Add \\\"bun\\\" to compilerOptions.types or remove types if you do not need to restrict global type packages.\",\n }),\n );\n }\n\n if (project.bunfig?.installAuto && project.bunfig.installAuto !== \"disable\") {\n diagnostics.push(\n createDiagnostic({\n ruleId: \"bun/auto-install-enabled\",\n title: \"Bun auto-install is enabled\",\n level: \"risk\",\n message: `bunfig.toml sets install.auto to ${project.bunfig.installAuto}. Auto-install can fetch packages during execution when node_modules is absent.`,\n filePath: project.bunfig.filePath,\n line: findLineNumber(project.bunfig.content, /auto\\s*=/),\n sources: [BUN_DOCS.autoInstall, BUN_DOCS.bunfig],\n help: \"For application repos and CI, consider install.auto = \\\"disable\\\" for more predictable execution.\",\n }),\n );\n }\n\n for (const entry of COMPAT_DB) {\n for (const manifest of findDependencyManifests(project, entry.packageName)) {\n diagnostics.push(\n createDiagnostic({\n ruleId: `compat/${entry.packageName}`,\n title: `${entry.packageName} compatibility note`,\n level: entry.severity,\n message: entry.reason,\n filePath: manifest.packageJsonPath,\n sources: entry.sources,\n packageName: entry.packageName,\n help: [entry.replacement ? `Replacement: ${entry.replacement}.` : \"\", entry.workaround, entry.migrationHint]\n .filter(Boolean)\n .join(\" \"),\n }),\n );\n\n if (entry.requiresTrustedDependency && !manifest.trustedDependencies.has(entry.packageName)) {\n diagnostics.push(\n createDiagnostic({\n ruleId: `bun/trusted-dependency/${entry.packageName}`,\n title: `${entry.packageName} may need trustedDependencies`,\n level: \"risk\",\n message: `${entry.packageName} can require lifecycle scripts, but it is not listed in trustedDependencies. Bun does not run arbitrary lifecycle scripts by default.`,\n filePath: manifest.packageJsonPath,\n sources: [BUN_DOCS.lifecycle],\n packageName: entry.packageName,\n help: `If you keep ${entry.packageName}, verify whether it needs install scripts and add it to trustedDependencies only after review.`,\n }),\n );\n }\n }\n }\n\n return diagnostics;\n};\n\nconst CODE_RULES: CodeRule[] = [\n {\n ruleId: \"code/node-repl\",\n title: \"node:repl is not implemented in Bun\",\n level: \"blocker\",\n pattern: /(?:from\\s+[\"']node:repl[\"']|require\\([\"']node:repl[\"']\\))/,\n message: \"Bun's Node compatibility table marks node:repl as not implemented.\",\n sources: [BUN_DOCS.nodeCompatibility],\n },\n {\n ruleId: \"code/node-trace-events\",\n title: \"node:trace_events is not implemented in Bun\",\n level: \"blocker\",\n pattern: /(?:from\\s+[\"']node:trace_events[\"']|require\\([\"']node:trace_events[\"']\\))/,\n message: \"Bun's Node compatibility table marks node:trace_events as not implemented.\",\n sources: [BUN_DOCS.nodeCompatibility],\n },\n {\n ruleId: \"code/node-sqlite\",\n title: \"node:sqlite is not implemented in Bun\",\n level: \"blocker\",\n pattern: /(?:from\\s+[\"']node:sqlite[\"']|require\\([\"']node:sqlite[\"']\\))/,\n message: \"Bun does not implement node:sqlite. Bun provides bun:sqlite instead.\",\n sources: [BUN_DOCS.nodeCompatibility, BUN_DOCS.sqlite],\n help: \"Use bun:sqlite for Bun-targeted SQLite code.\",\n },\n {\n ruleId: \"code/process-binding\",\n title: \"process.binding usage is compatibility-sensitive\",\n level: \"risk\",\n pattern: /\\bprocess\\.binding\\s*\\(/,\n message: \"process.binding is an internal Node API and only partially implemented by Bun.\",\n sources: [BUN_DOCS.nodeCompatibility],\n help: \"Replace internal Node binding access with public APIs before migrating.\",\n },\n {\n ruleId: \"code/process-missing-api\",\n title: \"Node process API is not implemented in Bun\",\n level: \"blocker\",\n pattern: /\\bprocess\\.(loadEnvFile|getBuiltinModule)\\s*\\(/,\n message: \"Bun's compatibility docs list process.loadEnvFile and process.getBuiltinModule as not implemented.\",\n sources: [BUN_DOCS.nodeCompatibility],\n },\n {\n ruleId: \"code/module-register\",\n title: \"module.register is not implemented in Bun\",\n level: \"blocker\",\n pattern: /\\bmodule\\.register\\s*\\(/,\n message: \"Bun's compatibility docs list module.register as not implemented and recommend Bun.plugin in the meantime.\",\n sources: [BUN_DOCS.nodeCompatibility, \"https://bun.com/docs/runtime/plugins\"],\n help: \"Evaluate Bun.plugin or avoid runtime module loader hooks in Bun-targeted code.\",\n },\n {\n ruleId: \"code/node-test\",\n title: \"node:test is only partly implemented in Bun\",\n level: \"migration\",\n pattern: /(?:from\\s+[\"']node:test[\"']|require\\([\"']node:test[\"']\\))/,\n message: \"Bun's compatibility docs mark node:test as partly implemented. Bun has its own bun:test runner.\",\n sources: [BUN_DOCS.nodeCompatibility, BUN_DOCS.testRunner],\n help: \"Prefer bun:test when migrating the test runner to Bun.\",\n },\n {\n ruleId: \"code/v8-specific-api\",\n title: \"V8-specific APIs are compatibility-sensitive\",\n level: \"risk\",\n pattern: /(?:from\\s+[\"']node:v8[\"']|require\\([\"']node:v8[\"']\\)|\\bv8\\.(serialize|deserialize|setFlagsFromString|cachedDataVersionTag)\\s*\\()/,\n message: \"Bun runs on JavaScriptCore, and its Node v8 compatibility is partial.\",\n sources: [BUN_DOCS.nodeCompatibility],\n help: \"Avoid V8-specific runtime assumptions in Bun-targeted code.\",\n },\n {\n ruleId: \"code/worker-resource-limits\",\n title: \"worker_threads resource limits are unsupported\",\n level: \"risk\",\n pattern: /\\bresourceLimits\\s*:/,\n message: \"Bun's worker_threads compatibility notes list resourceLimits as unsupported.\",\n sources: [BUN_DOCS.nodeCompatibility],\n help: \"Verify worker behavior under Bun or avoid Node-specific Worker options.\",\n },\n];\n\nexport const runCodeRules = (project: ProjectInfo): Diagnostic[] => {\n const diagnostics: Diagnostic[] = [];\n\n for (const sourceFile of project.sourceFiles) {\n for (const rule of CODE_RULES) {\n if (!rule.pattern.test(sourceFile.content)) continue;\n diagnostics.push(\n createDiagnostic({\n ruleId: rule.ruleId,\n title: rule.title,\n level: rule.level,\n message: rule.message,\n filePath: sourceFile.filePath,\n line: findLineNumber(sourceFile.content, rule.pattern),\n sources: rule.sources,\n help: rule.help,\n }),\n );\n }\n }\n\n return diagnostics;\n};\n","import {\n BLOCKER_RULE_PENALTY,\n MIGRATION_RULE_PENALTY,\n PERFECT_SCORE,\n RISK_RULE_PENALTY,\n SCORE_CLOSE_THRESHOLD,\n SCORE_READY_THRESHOLD,\n SCORE_RISKY_THRESHOLD,\n} from \"./constants.js\";\nimport type { Diagnostic, FindingLevel, ScanSummary, ScoreResult } from \"./types.js\";\n\nconst getScoreLabel = (score: number): ScoreResult[\"label\"] => {\n if (score >= SCORE_READY_THRESHOLD) return \"Ready\";\n if (score >= SCORE_CLOSE_THRESHOLD) return \"Close\";\n if (score >= SCORE_RISKY_THRESHOLD) return \"Risky\";\n return \"Blocked\";\n};\n\nconst collectUniqueRuleCounts = (diagnostics: Diagnostic[]): Record<FindingLevel, Set<string>> => {\n const counts: Record<FindingLevel, Set<string>> = {\n blocker: new Set(),\n risk: new Set(),\n migration: new Set(),\n win: new Set(),\n };\n\n for (const diagnostic of diagnostics) {\n counts[diagnostic.level].add(diagnostic.ruleId);\n }\n\n return counts;\n};\n\nexport const calculateScore = (diagnostics: Diagnostic[]): ScoreResult => {\n const counts = collectUniqueRuleCounts(diagnostics);\n const penalty =\n counts.blocker.size * BLOCKER_RULE_PENALTY +\n counts.risk.size * RISK_RULE_PENALTY +\n counts.migration.size * MIGRATION_RULE_PENALTY;\n const score = Math.max(0, Math.round(PERFECT_SCORE - penalty));\n return { score, label: getScoreLabel(score) };\n};\n\nexport const summarizeDiagnostics = (diagnostics: Diagnostic[]): ScanSummary => ({\n blockers: diagnostics.filter((diagnostic) => diagnostic.level === \"blocker\").length,\n risks: diagnostics.filter((diagnostic) => diagnostic.level === \"risk\").length,\n migrations: diagnostics.filter((diagnostic) => diagnostic.level === \"migration\").length,\n wins: diagnostics.filter((diagnostic) => diagnostic.level === \"win\").length,\n});\n","import { loadConfig, filterIgnoredDiagnostics } from \"./config.js\";\nimport { discoverProject } from \"./project.js\";\nimport { runCodeRules, runPackageRules } from \"./rules.js\";\nimport { calculateScore, summarizeDiagnostics } from \"./score.js\";\nimport type { ScanOptions, ScanResult } from \"./types.js\";\n\nexport const scan = async (directory: string, options: ScanOptions = {}): Promise<ScanResult> => {\n const project = discoverProject(directory);\n const loadedConfig = options.configOverride ?? loadConfig(project.rootDirectory, project.packageJson);\n const shouldRunPackageChecks = options.packageChecks ?? loadedConfig.package ?? true;\n const shouldRunCodeChecks = options.codeChecks ?? loadedConfig.code ?? true;\n\n const diagnostics = [\n ...(shouldRunPackageChecks ? runPackageRules(project) : []),\n ...(shouldRunCodeChecks ? runCodeRules(project) : []),\n ];\n const filteredDiagnostics = filterIgnoredDiagnostics(\n diagnostics,\n loadedConfig,\n project.rootDirectory,\n );\n\n return {\n project,\n diagnostics: filteredDiagnostics,\n score: calculateScore(filteredDiagnostics),\n summary: summarizeDiagnostics(filteredDiagnostics),\n };\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Compatibility DB Schema
|
|
2
|
+
|
|
3
|
+
The compatibility database powers dependency-level findings. Entries must be conservative, sourced, and reviewable.
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
interface CompatEntry {
|
|
7
|
+
packageName: string;
|
|
8
|
+
severity: "blocker" | "risk" | "migration" | "win";
|
|
9
|
+
affectedRanges: string[];
|
|
10
|
+
bunVersions: string[];
|
|
11
|
+
platforms: Array<"darwin" | "linux" | "win32" | "all">;
|
|
12
|
+
confidence: "high" | "medium" | "low";
|
|
13
|
+
reason: string;
|
|
14
|
+
sources: string[];
|
|
15
|
+
lastVerified: string;
|
|
16
|
+
replacement?: string;
|
|
17
|
+
workaround?: string;
|
|
18
|
+
migrationHint?: string;
|
|
19
|
+
requiresTrustedDependency?: boolean;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Field Notes
|
|
24
|
+
|
|
25
|
+
- `sources` is mandatory and must not be empty.
|
|
26
|
+
- `severity` should be the lowest severity that remains truthful.
|
|
27
|
+
- `win` entries are allowed for optional Bun-native replacements and never reduce the score.
|
|
28
|
+
- `workaround` is free text for mitigation when no replacement exists.
|
|
29
|
+
- `migrationHint` is structured enough to power future codemods or autofixes.
|
|
30
|
+
- `lastVerified` must be refreshed by the v1.0 eval harness.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Bun Doctor Rule Spec
|
|
2
|
+
|
|
3
|
+
## Product Frame
|
|
4
|
+
|
|
5
|
+
`bun-doctor` is a Bun-readiness scanner for Node-to-Bun migrations. It is not a generic code-style linter.
|
|
6
|
+
|
|
7
|
+
The primary output is a 0-100 Bun Readiness score with findings grouped by user intent:
|
|
8
|
+
|
|
9
|
+
- Blockers: likely to fail under Bun or in Bun-based CI.
|
|
10
|
+
- Risks: likely to work in common cases, but affected by unsupported Node APIs, platform differences, native packages, or lifecycle behavior.
|
|
11
|
+
- Migration work: repo/config/tooling changes needed for a clean Bun migration.
|
|
12
|
+
- Bun wins: optional simplifications or performance improvements. These never reduce the score.
|
|
13
|
+
|
|
14
|
+
## Severity Model
|
|
15
|
+
|
|
16
|
+
| Level | Score penalty | Use when |
|
|
17
|
+
| --- | ---: | --- |
|
|
18
|
+
| `blocker` | 12 | The repo is likely to break under Bun or Bun CI. |
|
|
19
|
+
| `risk` | 5 | The repo may work, but uses unsupported or compatibility-sensitive behavior. |
|
|
20
|
+
| `migration` | 2 | The repo has incomplete Bun migration state or avoidable toolchain drift. |
|
|
21
|
+
| `win` | 0 | Bun has a native replacement or simplification, but current code can still be valid. |
|
|
22
|
+
|
|
23
|
+
The score counts unique triggered rules, not every occurrence. Fixing 49 of 50 occurrences of one rule should not change the score until the rule is fully resolved.
|
|
24
|
+
|
|
25
|
+
## Source Requirement
|
|
26
|
+
|
|
27
|
+
Every rule and compatibility DB entry must include at least one verifiable source:
|
|
28
|
+
|
|
29
|
+
- Bun documentation page.
|
|
30
|
+
- Bun compatibility documentation.
|
|
31
|
+
- Public GitHub issue or upstream issue.
|
|
32
|
+
- A repository test fixture or eval harness result.
|
|
33
|
+
|
|
34
|
+
No source, no diagnostic. This keeps false positives under control and makes each finding auditable.
|
|
35
|
+
|
|
36
|
+
## MVP Rule Split
|
|
37
|
+
|
|
38
|
+
The first rule pack should stay around 20 high-confidence rules:
|
|
39
|
+
|
|
40
|
+
- Around 12 package/config/dependency/CI rules.
|
|
41
|
+
- Around 8 source-code risk rules.
|
|
42
|
+
|
|
43
|
+
Package/config rules carry most early migration value. Code rules exist to prove the scanner sees real runtime risk, but should stay conservative until the eval harness exists.
|
|
44
|
+
|
|
45
|
+
## v1.0 Eval Harness
|
|
46
|
+
|
|
47
|
+
The compatibility database is the project moat. It must be backed by a public eval harness for v1.0.
|
|
48
|
+
|
|
49
|
+
The harness should:
|
|
50
|
+
|
|
51
|
+
- Install packages across Bun versions.
|
|
52
|
+
- Run package-specific smoke tests.
|
|
53
|
+
- Test relevant OS/platform combinations.
|
|
54
|
+
- Emit machine-readable results used to update `lastVerified` and `confidence`.
|
|
55
|
+
- Link findings back to test cases from the compatibility DB.
|
|
56
|
+
|
|
57
|
+
MVP can ship before the harness, but schema and docs should assume it from day one.
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bun-doctor",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Diagnose Node-to-Bun migration readiness for JavaScript and TypeScript projects",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bun",
|
|
7
|
+
"bun-doctor",
|
|
8
|
+
"migration",
|
|
9
|
+
"diagnostics",
|
|
10
|
+
"nodejs",
|
|
11
|
+
"typescript",
|
|
12
|
+
"cli"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/kylegrahammatzen/bun-doctor#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/kylegrahammatzen/bun-doctor/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/kylegrahammatzen/bun-doctor.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"type": "module",
|
|
24
|
+
"bin": {
|
|
25
|
+
"bun-doctor": "bin/bun-doctor.js"
|
|
26
|
+
},
|
|
27
|
+
"exports": {
|
|
28
|
+
".": {
|
|
29
|
+
"types": "./dist/index.d.mts",
|
|
30
|
+
"default": "./dist/index.mjs"
|
|
31
|
+
},
|
|
32
|
+
"./package.json": "./package.json"
|
|
33
|
+
},
|
|
34
|
+
"files": [
|
|
35
|
+
"dist",
|
|
36
|
+
"bin",
|
|
37
|
+
"skills",
|
|
38
|
+
"docs",
|
|
39
|
+
"README.md",
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"action.yml"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsdown",
|
|
45
|
+
"dev": "tsdown --watch",
|
|
46
|
+
"prepack": "bun run build",
|
|
47
|
+
"test": "bun test",
|
|
48
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
49
|
+
"check": "bun run typecheck && bun test && bun run build"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=20"
|
|
53
|
+
},
|
|
54
|
+
"packageManager": "bun@1.3.11",
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/bun": "^1.3.3",
|
|
57
|
+
"@types/node": "^24.10.1",
|
|
58
|
+
"tsdown": "^0.22.0",
|
|
59
|
+
"typescript": "^5.9.3"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bun-doctor
|
|
3
|
+
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.
|
|
4
|
+
version: "1.0.0"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Bun Doctor
|
|
8
|
+
|
|
9
|
+
Scans JavaScript and TypeScript projects for Bun migration readiness. Outputs a 0-100 Bun Readiness score.
|
|
10
|
+
|
|
11
|
+
## After migration-related changes
|
|
12
|
+
|
|
13
|
+
Run `npx -y bun-doctor@latest . --verbose` and check for blockers first, then risks.
|
|
14
|
+
|
|
15
|
+
## Before switching CI to Bun
|
|
16
|
+
|
|
17
|
+
Run `npx -y bun-doctor@latest . --verbose --fail-on blocker`.
|
|
18
|
+
|
|
19
|
+
## Command
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npx -y bun-doctor@latest . --verbose
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
| Flag | Purpose |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `.` | Scan current directory |
|
|
28
|
+
| `--verbose` | Show every diagnostic |
|
|
29
|
+
| `--score` | Output only the numeric score |
|
|
30
|
+
| `--json` | Output structured JSON |
|