diff --git a/docs/02-app/02-api-reference/06-create-next-app.mdx b/docs/02-app/02-api-reference/06-create-next-app.mdx index 2ee652e108dcc7..15480de510c31b 100644 --- a/docs/02-app/02-api-reference/06-create-next-app.mdx +++ b/docs/02-app/02-api-reference/06-create-next-app.mdx @@ -92,6 +92,10 @@ Options: Explicitly tell the CLI to bootstrap the app using Yarn + --use-bun + + Explicitly tell the CLI to bootstrap the app using Bun + -e, --example [name]|[github-url] An example to bootstrap the app with. You can use an example name diff --git a/packages/create-next-app/README.md b/packages/create-next-app/README.md index 2c4dccfcae355d..423c39bdfc7399 100644 --- a/packages/create-next-app/README.md +++ b/packages/create-next-app/README.md @@ -53,6 +53,10 @@ Options: Explicitly tell the CLI to bootstrap the app using Yarn + --use-bun + + Explicitly tell the CLI to bootstrap the app using Bun + -e, --example [name]|[github-url] An example to bootstrap the app with. You can use an example name diff --git a/packages/create-next-app/helpers/get-pkg-manager.ts b/packages/create-next-app/helpers/get-pkg-manager.ts index 4bb66a08c62ac6..20900ebcb9ede9 100644 --- a/packages/create-next-app/helpers/get-pkg-manager.ts +++ b/packages/create-next-app/helpers/get-pkg-manager.ts @@ -1,4 +1,4 @@ -export type PackageManager = 'npm' | 'pnpm' | 'yarn' +export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun' export function getPkgManager(): PackageManager { const userAgent = process.env.npm_config_user_agent || '' @@ -11,5 +11,9 @@ export function getPkgManager(): PackageManager { return 'pnpm' } + if (userAgent.startsWith('bun')) { + return 'bun' + } + return 'npm' } diff --git a/packages/create-next-app/helpers/install.ts b/packages/create-next-app/helpers/install.ts index bfe12b2b2cd44f..a3d2545ac1d6ee 100644 --- a/packages/create-next-app/helpers/install.ts +++ b/packages/create-next-app/helpers/install.ts @@ -43,6 +43,7 @@ export function install( let args: string[] let command = packageManager const useYarn = packageManager === 'yarn' + const useBun = packageManager === 'bun' if (dependencies && dependencies.length) { /** @@ -57,6 +58,11 @@ export function install( args.push('--cwd', root) if (devDependencies) args.push('--dev') args.push(...dependencies) + } else if (useBun) { + args = ['add', '--exact'] + args.push('--cwd', root) + if (devDependencies) args.push('--development') + args.push(...dependencies) } else { /** * Call `(p)npm install [--save|--save-dev] ...`. diff --git a/packages/create-next-app/index.ts b/packages/create-next-app/index.ts index ed8358b487ca60..3c12a7469b0af2 100644 --- a/packages/create-next-app/index.ts +++ b/packages/create-next-app/index.ts @@ -106,6 +106,13 @@ const program = new Commander.Command(packageJson.name) ` Explicitly tell the CLI to bootstrap the application using Yarn +` + ) + .option( + '--use-bun', + ` + + Explicitly tell the CLI to bootstrap the application using Bun ` ) .option( @@ -143,6 +150,8 @@ const packageManager = !!program.useNpm ? 'pnpm' : !!program.useYarn ? 'yarn' + : !!program.useBun + ? 'bun' : getPkgManager() async function run(): Promise { @@ -461,6 +470,8 @@ async function notifyUpdate(): Promise { ? 'yarn global add create-next-app' : packageManager === 'pnpm' ? 'pnpm add -g create-next-app' + : packageManager === 'bun' + ? 'bun add -g create-next-app' : 'npm i -g create-next-app' console.log( diff --git a/test/integration/create-next-app/package-manager.test.ts b/test/integration/create-next-app/package-manager.test.ts index d0c1e12b3e7f6c..ee582a9087b887 100644 --- a/test/integration/create-next-app/package-manager.test.ts +++ b/test/integration/create-next-app/package-manager.test.ts @@ -226,6 +226,79 @@ it('should use pnpm as the package manager on supplying --use-pnpm with example' }) }) +it('should use Bun as the package manager on supplying --use-bun', async () => { + await useTempDir(async (cwd) => { + const projectName = 'use-bun' + const res = await run( + [ + projectName, + '--js', + '--no-tailwind', + '--eslint', + '--use-bun', + '--no-src-dir', + '--app', + `--import-alias=@/*`, + ], + { + cwd, + } + ) + + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files: [ + 'package.json', + 'app/page.js', + '.gitignore', + '.eslintrc.json', + 'bun.lockb', + 'node_modules/next', + ], + }) + }) +}) + +it('should use Bun as the package manager on supplying --use-bun with example', async () => { + try { + await execa('bun', ['--version']) + } catch (_) { + // install Bun if not available + await execa('npm', ['i', '-g', 'bun']) + } + + await useTempDir(async (cwd) => { + const projectName = 'use-bun' + const res = await run( + [ + projectName, + '--js', + '--no-tailwind', + '--eslint', + '--use-bun', + '--example', + `${exampleRepo}/${examplePath}`, + ], + { cwd } + ) + + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files: [ + 'package.json', + 'pages/index.tsx', + '.gitignore', + 'bun.lockb', + 'node_modules/next', + ], + }) + }) +}) + it('should infer npm as the package manager', async () => { await useTempDir(async (cwd) => { const projectName = 'infer-package-manager-npm' @@ -436,3 +509,78 @@ it('should infer pnpm as the package manager with example', async () => { projectFilesShouldExist({ cwd, projectName, files }) }) }) + +it('should infer Bun as the package manager', async () => { + try { + await execa('bun', ['--version']) + } catch (_) { + // install Bun if not available + await execa('npm', ['i', '-g', 'bun']) + } + + await useTempDir(async (cwd) => { + const projectName = 'infer-package-manager' + const res = await run( + [ + projectName, + '--js', + '--no-tailwind', + '--eslint', + '--no-src-dir', + '--app', + `--import-alias=@/*`, + ], + { + cwd, + env: { ...process.env, npm_config_user_agent: 'bun' }, + } + ) + + const files = [ + 'package.json', + 'app/page.js', + '.gitignore', + '.eslintrc.json', + 'bun.lockb', + 'node_modules/next', + ] + + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ cwd, projectName, files }) + }) +}) + +it('should infer Bun as the package manager with example', async () => { + try { + await execa('bun', ['--version']) + } catch (_) { + // install Bun if not available + await execa('npm', ['i', '-g', 'bun']) + } + + await useTempDir(async (cwd) => { + const projectName = 'infer-package-manager-npm' + const res = await run( + [ + projectName, + '--js', + '--no-tailwind', + '--eslint', + '--example', + `${exampleRepo}/${examplePath}`, + ], + { cwd, env: { ...process.env, npm_config_user_agent: 'bun' } } + ) + + const files = [ + 'package.json', + 'pages/index.tsx', + '.gitignore', + 'bun.lockb', + 'node_modules/next', + ] + + expect(res.exitCode).toBe(0) + projectFilesShouldExist({ cwd, projectName, files }) + }) +})