diff --git a/README.md b/README.md index c3a910b0..54521de2 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,13 @@ yarn 2+ have native support for patching dependencies via [`yarn patch`](https://yarnpkg.com/cli/patch). You do not need to use patch-package on these projects. +### bun + + bun add patch-package + +You can use `--dev` if you don't need to run bun in production, e.g. if you're +making a web frontend. + ### pnpm pnpm has native support for patching dependencies via @@ -152,9 +159,15 @@ team. - `--use-yarn` - By default, patch-package checks whether you use npm or yarn based on which - lockfile you have. If you have both, it uses npm by default. Set this option - to override that default and always use yarn. + By default, patch-package checks whether you use npm, yarn or bun based on + which lockfile you have. If you have multiple lockfiles, it uses npm by + default (in cases where npm is not available, it will resort to yarn). Set + this option to override that default and always use yarn. + +- `--use-bun` + + Similar to --use-yarn, but for bun. If both --use-yarn and --use-bun are + specified, --use-yarn takes precedence. - `--exclude ` diff --git a/src/detectPackageManager.ts b/src/detectPackageManager.ts index c7bb7559..5f2ae183 100644 --- a/src/detectPackageManager.ts +++ b/src/detectPackageManager.ts @@ -4,7 +4,7 @@ import chalk from "chalk" import process from "process" import findWorkspaceRoot from "find-yarn-workspace-root" -export type PackageManager = "yarn" | "npm" | "npm-shrinkwrap" +export type PackageManager = "yarn" | "npm" | "npm-shrinkwrap" | "bun" function printNoYarnLockfileError() { console.log(` @@ -14,12 +14,20 @@ ${chalk.red.bold("**ERROR**")} ${chalk.red( `) } +function printNoBunLockfileError() { + console.log(` +${chalk.red.bold("**ERROR**")} ${chalk.red( + `The --use-bun option was specified but there is no bun.lockb file`, + )} +`) +} + function printNoLockfilesError() { console.log(` ${chalk.red.bold("**ERROR**")} ${chalk.red( - `No package-lock.json, npm-shrinkwrap.json, or yarn.lock file. + `No package-lock.json, npm-shrinkwrap.json, yarn.lock, or bun.lockb file. -You must use either npm@>=5, yarn, or npm-shrinkwrap to manage this project's +You must use either npm@>=5, yarn, npm-shrinkwrap, or bun to manage this project's dependencies.`, )} `) @@ -29,14 +37,40 @@ function printSelectingDefaultMessage() { console.info( `${chalk.bold( "patch-package", - )}: you have both yarn.lock and package-lock.json + )}: you have multiple lockfiles, e.g. yarn.lock and package-lock.json Defaulting to using ${chalk.bold("npm")} -You can override this setting by passing --use-yarn or deleting -package-lock.json if you don't need it +You can override this setting by passing --use-yarn, --use-bun, or +deleting the conflicting lockfile if you don't need it +`, + ) +} + +function printSelectingDefaultYarnMessage() { + console.info( + `${chalk.bold( + "patch-package", + )}: you have both yarn.lock and bun.lockb lockfiles +Defaulting to using ${chalk.bold("yarn")} +You can override this setting by passing --use-bun, or +deleting yarn.lock if you don't need it `, ) } +function checkForYarnOverride(overridePackageManager: PackageManager | null) { + if (overridePackageManager === "yarn") { + printNoYarnLockfileError() + process.exit(1) + } +} + +function checkForBunOverride(overridePackageManager: PackageManager | null) { + if (overridePackageManager === "bun") { + printNoBunLockfileError() + process.exit(1) + } +} + export const detectPackageManager = ( appRootPath: string, overridePackageManager: PackageManager | null, @@ -47,23 +81,40 @@ export const detectPackageManager = ( const shrinkWrapExists = fs.existsSync( join(appRootPath, "npm-shrinkwrap.json"), ) - const yarnLockExists = fs.existsSync(join(appRootPath, "yarn.lock")) - if ((packageLockExists || shrinkWrapExists) && yarnLockExists) { + const yarnLockExists = fs.existsSync( + join(findWorkspaceRoot() ?? appRootPath, "yarn.lock"), + ) + // Bun workspaces seem to work the same as yarn workspaces - https://bun.sh/docs/install/workspaces + const bunLockbExists = fs.existsSync( + join(findWorkspaceRoot() ?? appRootPath, "bun.lockb"), + ) + if ( + [ + packageLockExists || shrinkWrapExists, + yarnLockExists, + bunLockbExists, + ].filter(Boolean).length > 1 + ) { if (overridePackageManager) { return overridePackageManager - } else { - printSelectingDefaultMessage() - return shrinkWrapExists ? "npm-shrinkwrap" : "npm" } - } else if (packageLockExists || shrinkWrapExists) { - if (overridePackageManager === "yarn") { - printNoYarnLockfileError() - process.exit(1) - } else { - return shrinkWrapExists ? "npm-shrinkwrap" : "npm" + if (!packageLockExists && !shrinkWrapExists) { + // The only case where we don't want to default to npm is when we have both yarn and bun lockfiles. + printSelectingDefaultYarnMessage() + return "yarn" } - } else if (yarnLockExists || findWorkspaceRoot()) { + printSelectingDefaultMessage() + return shrinkWrapExists ? "npm-shrinkwrap" : "npm" + } else if (packageLockExists || shrinkWrapExists) { + checkForYarnOverride(overridePackageManager) + checkForBunOverride(overridePackageManager) + return shrinkWrapExists ? "npm-shrinkwrap" : "npm" + } else if (yarnLockExists) { + checkForBunOverride(overridePackageManager) return "yarn" + } else if (bunLockbExists) { + checkForYarnOverride(overridePackageManager) + return "bun" } else { printNoLockfilesError() process.exit(1) diff --git a/src/getPackageResolution.ts b/src/getPackageResolution.ts index a046e7f9..2d740070 100644 --- a/src/getPackageResolution.ts +++ b/src/getPackageResolution.ts @@ -7,6 +7,7 @@ import yaml from "yaml" import findWorkspaceRoot from "find-yarn-workspace-root" import { getPackageVersion } from "./getPackageVersion" import { coerceSemVer } from "./coerceSemVer" +import { parseBunLockfile } from "./parseBunLockfile" export function getPackageResolution({ packageDetails, @@ -17,24 +18,32 @@ export function getPackageResolution({ packageManager: PackageManager appPath: string }) { - if (packageManager === "yarn") { - let lockFilePath = "yarn.lock" + if (packageManager === "yarn" || packageManager === "bun") { + const isBun = packageManager === "bun" + const lockFileName = isBun ? "bun.lockb" : "yarn.lock" + let lockFilePath = lockFileName if (!existsSync(lockFilePath)) { const workspaceRoot = findWorkspaceRoot() if (!workspaceRoot) { - throw new Error("Can't find yarn.lock file") + throw new Error(`Can't find ${lockFileName} file`) } - lockFilePath = join(workspaceRoot, "yarn.lock") + lockFilePath = join(workspaceRoot, lockFilePath) } if (!existsSync(lockFilePath)) { - throw new Error("Can't find yarn.lock file") + throw new Error(`Can't find ${lockFileName} file`) } - const lockFileString = readFileSync(lockFilePath).toString() + const lockFileString = isBun + ? parseBunLockfile(lockFilePath) + : readFileSync(lockFilePath).toString() let appLockFile if (lockFileString.includes("yarn lockfile v1")) { const parsedYarnLockFile = parseYarnLockFile(lockFileString) if (parsedYarnLockFile.type !== "success") { - throw new Error("Could not parse yarn v1 lock file") + throw new Error( + `Could not parse yarn v1 lock file ${ + isBun ? "- was originally a bun.lockb file" : "" + }`, + ) } else { appLockFile = parsedYarnLockFile.object } @@ -43,7 +52,11 @@ export function getPackageResolution({ appLockFile = yaml.parse(lockFileString) } catch (e) { console.log(e) - throw new Error("Could not parse yarn v2 lock file") + throw new Error( + `Could not parse yarn v2 lock file ${ + isBun ? "- was originally a bun.lockb file (should not happen)" : "" + }`, + ) } } diff --git a/src/index.ts b/src/index.ts index 8ee449a9..31a92fec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -84,7 +84,7 @@ if (argv.version || argv.v) { ) const packageManager = detectPackageManager( appPath, - argv["use-yarn"] ? "yarn" : null, + argv["use-yarn"] ? "yarn" : argv["use-bun"] ? "bun" : null, ) const createIssue = argv["create-issue"] packageNames.forEach((packagePathSpecifier: string) => { @@ -198,9 +198,15 @@ Usage: ${chalk.bold("--use-yarn")} - By default, patch-package checks whether you use npm or yarn based on - which lockfile you have. If you have both, it uses npm by default. - Set this option to override that default and always use yarn. + By default, patch-package checks whether you use npm, yarn or bun based on + which lockfile you have. If you have multiple lockfiles, it uses npm by + default (in cases where npm is not available, it will resort to yarn). Set + this option to override that default and always use yarn. + + ${chalk.bold("--use-bun")} + + Similar to --use-yarn, but for bun. If both --use-yarn and --use-bun are + specified, --use-yarn takes precedence. ${chalk.bold("--exclude ")} diff --git a/src/parseBunLockfile.ts b/src/parseBunLockfile.ts new file mode 100644 index 00000000..2ebed2d0 --- /dev/null +++ b/src/parseBunLockfile.ts @@ -0,0 +1,15 @@ +import { spawnSync } from "child_process" + +// Adapted from https://github.com/oven-sh/bun/blob/ffe4f561a3af53b9f5a41c182de55d7199b5d692/packages/bun-vscode/src/features/lockfile.ts#L39, +// rewritten to use spawnSync instead of spawn. +export function parseBunLockfile(lockFilePath: string): string { + const process = spawnSync("bun", [lockFilePath], { + stdio: ["ignore", "pipe", "pipe"], + }) + if (process.status !== 0) { + throw new Error( + `Bun exited with code: ${process.status}\n${process.stderr.toString()}`, + ) + } + return process.stdout.toString() +}