From bacb64fa9626a7dc504f8a9e146617c20548250d Mon Sep 17 00:00:00 2001 From: Hendrik Liebau Date: Tue, 23 Jul 2024 22:23:50 +0200 Subject: [PATCH] Add `--yes` option to create-next-app This is a small improvement for power users that run `create-next-app` frequently, using the same preferences. By specifying the `--yes` option along with the project name, they won't be prompted for any preferences. Instead, the previously used preferences are automatically chosen, using the defaults as fallback. --- .../02-api-reference/06-create-next-app.mdx | 17 +++++--- packages/create-next-app/README.md | 27 ++++++++---- packages/create-next-app/index.ts | 32 +++++++++----- .../create-next-app/prompts.test.ts | 42 +++++++++++++++++++ 4 files changed, 95 insertions(+), 23 deletions(-) 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 852363fa01b0fd..c394a55a9954af 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 @@ -95,24 +95,24 @@ Options: --use-npm - Explicitly tell the CLI to bootstrap the app using npm + Explicitly tell the CLI to bootstrap the application using npm --use-pnpm - Explicitly tell the CLI to bootstrap the app using pnpm + Explicitly tell the CLI to bootstrap the application using pnpm --use-yarn - Explicitly tell the CLI to bootstrap the app using Yarn + Explicitly tell the CLI to bootstrap the application using Yarn --use-bun - Explicitly tell the CLI to bootstrap the app using Bun + Explicitly tell the CLI to bootstrap the application using Bun -e, --example [name]|[github-url] An example to bootstrap the app with. You can use an example name - from the official Next.js repo or a public GitHub URL. The URL can use + from the official Next.js repo or a GitHub URL. The URL can use any branch and/or subdirectory --example-path @@ -130,7 +130,12 @@ Options: Explicitly tell the CLI to skip installing packages - -h, --help output usage information + --yes + + Use previous preferences or defaults for all options that were not + explicitly specified, without prompting. + + -h, --help display help for command ``` ### Why use Create Next App? diff --git a/packages/create-next-app/README.md b/packages/create-next-app/README.md index d6488cb8b699b4..3f9df85288f282 100644 --- a/packages/create-next-app/README.md +++ b/packages/create-next-app/README.md @@ -31,7 +31,7 @@ You can also pass command line arguments to set up a new project non-interactively. See `create-next-app --help`: ```bash -Usage: create-next-app [options] +Usage: create-next-app [project-directory] [options] Options: -V, --version output the version number @@ -67,26 +67,30 @@ Options: Specify import alias to use (default "@/*"). + --empty + + Initialize an empty project. + --use-npm - Explicitly tell the CLI to bootstrap the app using npm + Explicitly tell the CLI to bootstrap the application using npm --use-pnpm - Explicitly tell the CLI to bootstrap the app using pnpm + Explicitly tell the CLI to bootstrap the application using pnpm --use-yarn - Explicitly tell the CLI to bootstrap the app using Yarn + Explicitly tell the CLI to bootstrap the application using Yarn --use-bun - Explicitly tell the CLI to bootstrap the app using Bun + Explicitly tell the CLI to bootstrap the application using Bun -e, --example [name]|[github-url] An example to bootstrap the app with. You can use an example name - from the official Next.js repo or a public GitHub URL. The URL can use + from the official Next.js repo or a GitHub URL. The URL can use any branch and/or subdirectory --example-path @@ -100,7 +104,16 @@ Options: Explicitly tell the CLI to reset any stored preferences - -h, --help output usage information + --skip-install + + Explicitly tell the CLI to skip installing packages + + --yes + + Use previous preferences or defaults for all options that were not + explicitly specified, without prompting. + + -h, --help display help for command ``` ### Why use Create Next App? diff --git a/packages/create-next-app/index.ts b/packages/create-next-app/index.ts index 05cf5c15f7402d..511a6fea8ceaee 100644 --- a/packages/create-next-app/index.ts +++ b/packages/create-next-app/index.ts @@ -73,7 +73,7 @@ const program = new Command(packageJson.name) '--eslint', ` - Initialize with eslint config. + Initialize with ESLint config. ` ) .option( @@ -170,6 +170,14 @@ const program = new Command(packageJson.name) ` Explicitly tell the CLI to skip installing packages +` + ) + .option( + '--yes', + ` + + Use previous preferences or defaults for all options that were not + explicitly specified, without prompting. ` ) .allowUnknownOption() @@ -271,10 +279,13 @@ async function run(): Promise { string, boolean | string > + /** - * If the user does not provide the necessary flags, prompt them for whether - * to use TS or JS. + * If the user does not provide the necessary flags, prompt them for their + * preferences, unless `--yes` option was specified, or when running in CI. */ + const skipPrompt = ciInfo.isCI || program.yes + if (!example) { const defaults: typeof preferences = { typescript: true, @@ -291,7 +302,7 @@ async function run(): Promise { preferences[field] ?? defaults[field] if (!program.typescript && !program.javascript) { - if (ciInfo.isCI) { + if (skipPrompt) { // default to TypeScript in CI as we can't prompt to // prevent breaking setup flows program.typescript = getPrefOrDefault('typescript') @@ -330,7 +341,7 @@ async function run(): Promise { !process.argv.includes('--eslint') && !process.argv.includes('--no-eslint') ) { - if (ciInfo.isCI) { + if (skipPrompt) { program.eslint = getPrefOrDefault('eslint') } else { const styledEslint = blue('ESLint') @@ -352,7 +363,7 @@ async function run(): Promise { !process.argv.includes('--tailwind') && !process.argv.includes('--no-tailwind') ) { - if (ciInfo.isCI) { + if (skipPrompt) { program.tailwind = getPrefOrDefault('tailwind') } else { const tw = blue('Tailwind CSS') @@ -374,7 +385,7 @@ async function run(): Promise { !process.argv.includes('--src-dir') && !process.argv.includes('--no-src-dir') ) { - if (ciInfo.isCI) { + if (skipPrompt) { program.srcDir = getPrefOrDefault('srcDir') } else { const styledSrcDir = blue('`src/` directory') @@ -393,7 +404,7 @@ async function run(): Promise { } if (!process.argv.includes('--app') && !process.argv.includes('--no-app')) { - if (ciInfo.isCI) { + if (skipPrompt) { program.app = getPrefOrDefault('app') } else { const styledAppDir = blue('App Router') @@ -407,11 +418,12 @@ async function run(): Promise { inactive: 'No', }) program.app = Boolean(appRouter) + preferences.app = Boolean(appRouter) } } if (!program.turbo && !process.argv.includes('--no-turbo')) { - if (ciInfo.isCI) { + if (skipPrompt) { program.turbo = getPrefOrDefault('turbo') } else { const styledTurbo = blue('Turbopack') @@ -434,7 +446,7 @@ async function run(): Promise { typeof program.importAlias !== 'string' || !importAliasPattern.test(program.importAlias) ) { - if (ciInfo.isCI) { + if (skipPrompt) { // We don't use preferences here because the default value is @/* regardless of existing preferences program.importAlias = defaults.importAlias } else if (process.argv.includes('--no-import-alias')) { diff --git a/test/integration/create-next-app/prompts.test.ts b/test/integration/create-next-app/prompts.test.ts index 56e745ea8ac1f5..102ce4472b4d76 100644 --- a/test/integration/create-next-app/prompts.test.ts +++ b/test/integration/create-next-app/prompts.test.ts @@ -174,4 +174,46 @@ describe('create-next-app prompts', () => { `) }) }) + + it('should not prompt user for choice and use defaults if --yes is defined', async () => { + await useTempDir(async (cwd) => { + const projectName = 'yes-we-can' + const childProcess = createNextApp( + [projectName, '--yes'], + { + cwd, + }, + nextTgzFilename + ) + + await new Promise((resolve) => { + childProcess.on('exit', async (exitCode) => { + expect(exitCode).toBe(0) + projectFilesShouldExist({ + cwd, + projectName, + files: [ + 'app', + '.eslintrc.json', + 'package.json', + 'tailwind.config.ts', + 'tsconfig.json', + ], + }) + resolve() + }) + }) + + const pkg = require(join(cwd, projectName, 'package.json')) + expect(pkg.name).toBe(projectName) + const tsConfig = require(join(cwd, projectName, 'tsconfig.json')) + expect(tsConfig.compilerOptions.paths).toMatchInlineSnapshot(` + { + "@/*": [ + "./*", + ], + } + `) + }) + }) })