Skip to content
Snippets Groups Projects
Commit 32592457 authored by Marcus Schiesser's avatar Marcus Schiesser
Browse files

add create-next-app v13.5.6

parent ced35552
No related branches found
No related tags found
No related merge requests found
Showing
with 2744 additions and 125 deletions
# Create Next App
The easiest way to get started with Next.js is by using `create-next-app`. This CLI tool enables you to quickly start building a new Next.js application, with everything set up for you. You can create a new app using the default Next.js template, or by using one of the [official Next.js examples](https://github.com/vercel/next.js/tree/canary/examples). To get started, use the following command:
### Interactive
You can create a new project interactively by running:
```bash
npx create-next-app@latest
# or
yarn create next-app
# or
pnpm create next-app
# or
bunx create-next-app
```
You will be asked for the name of your project, and then whether you want to
create a TypeScript project:
```bash
✔ Would you like to use TypeScript? … No / Yes
```
Select **Yes** to install the necessary types/dependencies and create a new TS project.
### Non-interactive
You can also pass command line arguments to set up a new project
non-interactively. See `create-next-app --help`:
```bash
create-next-app <project-directory> [options]
Options:
-V, --version output the version number
--ts, --typescript
Initialize as a TypeScript project. (default)
--js, --javascript
Initialize as a JavaScript project.
--use-npm
Explicitly tell the CLI to bootstrap the app using npm
--use-pnpm
Explicitly tell the CLI to bootstrap the app using pnpm
--use-yarn
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
from the official Next.js repo or a GitHub URL. The URL can use
any branch and/or subdirectory
--example-path <path-to-example>
In a rare case, your GitHub URL might contain a branch name with
a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar).
In this case, you must specify the path to the example separately:
--example-path foo/bar
```
### Why use Create Next App?
`create-next-app` allows you to create a new Next.js app within seconds. It is officially maintained by the creators of Next.js, and includes a number of benefits:
- **Interactive Experience**: Running `npx create-next-app@latest` (with no arguments) launches an interactive experience that guides you through setting up a project.
- **Zero Dependencies**: Initializing a project is as quick as one second. Create Next App has zero dependencies.
- **Offline Support**: Create Next App will automatically detect if you're offline and bootstrap your project using your local package cache.
- **Support for Examples**: Create Next App can bootstrap your application using an example from the Next.js examples collection (e.g. `npx create-next-app --example api-routes`).
- **Tested**: The package is part of the Next.js monorepo and tested using the same integration test suite as Next.js itself, ensuring it works as expected with every release.
/* eslint-disable import/no-extraneous-dependencies */
import retry from 'async-retry'
import { red, green, cyan } from 'picocolors'
import fs from 'fs'
import path from 'path'
import type { RepoInfo } from './helpers/examples'
import {
downloadAndExtractExample,
downloadAndExtractRepo,
getRepoInfo,
existsInRepo,
hasRepo,
} from './helpers/examples'
import { makeDir } from './helpers/make-dir'
import { tryGitInit } from './helpers/git'
import { install } from './helpers/install'
import { isFolderEmpty } from './helpers/is-folder-empty'
import { getOnline } from './helpers/is-online'
import { isWriteable } from './helpers/is-writeable'
import type { PackageManager } from './helpers/get-pkg-manager'
import type { TemplateMode, TemplateType } from './templates'
import { getTemplateFile, installTemplate } from './templates'
export class DownloadError extends Error {}
export async function createApp({
appPath,
packageManager,
example,
examplePath,
typescript,
tailwind,
eslint,
appRouter,
srcDir,
importAlias,
}: {
appPath: string
packageManager: PackageManager
example?: string
examplePath?: string
typescript: boolean
tailwind: boolean
eslint: boolean
appRouter: boolean
srcDir: boolean
importAlias: string
}): Promise<void> {
let repoInfo: RepoInfo | undefined
const mode: TemplateMode = typescript ? 'ts' : 'js'
const template: TemplateType = appRouter
? tailwind
? 'app-tw'
: 'app'
: tailwind
? 'default-tw'
: 'default'
if (example) {
let repoUrl: URL | undefined
try {
repoUrl = new URL(example)
} catch (error: any) {
if (error.code !== 'ERR_INVALID_URL') {
console.error(error)
process.exit(1)
}
}
if (repoUrl) {
if (repoUrl.origin !== 'https://github.com') {
console.error(
`Invalid URL: ${red(
`"${example}"`
)}. Only GitHub repositories are supported. Please use a GitHub URL and try again.`
)
process.exit(1)
}
repoInfo = await getRepoInfo(repoUrl, examplePath)
if (!repoInfo) {
console.error(
`Found invalid GitHub URL: ${red(
`"${example}"`
)}. Please fix the URL and try again.`
)
process.exit(1)
}
const found = await hasRepo(repoInfo)
if (!found) {
console.error(
`Could not locate the repository for ${red(
`"${example}"`
)}. Please check that the repository exists and try again.`
)
process.exit(1)
}
} else if (example !== '__internal-testing-retry') {
const found = await existsInRepo(example)
if (!found) {
console.error(
`Could not locate an example named ${red(
`"${example}"`
)}. It could be due to the following:\n`,
`1. Your spelling of example ${red(
`"${example}"`
)} might be incorrect.\n`,
`2. You might not be connected to the internet or you are behind a proxy.`
)
process.exit(1)
}
}
}
const root = path.resolve(appPath)
if (!(await isWriteable(path.dirname(root)))) {
console.error(
'The application path is not writable, please check folder permissions and try again.'
)
console.error(
'It is likely you do not have write permissions for this folder.'
)
process.exit(1)
}
const appName = path.basename(root)
await makeDir(root)
if (!isFolderEmpty(root, appName)) {
process.exit(1)
}
const useYarn = packageManager === 'yarn'
const isOnline = !useYarn || (await getOnline())
const originalDirectory = process.cwd()
console.log(`Creating a new Next.js app in ${green(root)}.`)
console.log()
process.chdir(root)
const packageJsonPath = path.join(root, 'package.json')
let hasPackageJson = false
if (example) {
/**
* If an example repository is provided, clone it.
*/
try {
if (repoInfo) {
const repoInfo2 = repoInfo
console.log(
`Downloading files from repo ${cyan(
example
)}. This might take a moment.`
)
console.log()
await retry(() => downloadAndExtractRepo(root, repoInfo2), {
retries: 3,
})
} else {
console.log(
`Downloading files for example ${cyan(
example
)}. This might take a moment.`
)
console.log()
await retry(() => downloadAndExtractExample(root, example), {
retries: 3,
})
}
} catch (reason) {
function isErrorLike(err: unknown): err is { message: string } {
return (
typeof err === 'object' &&
err !== null &&
typeof (err as { message?: unknown }).message === 'string'
)
}
throw new DownloadError(
isErrorLike(reason) ? reason.message : reason + ''
)
}
// Copy `.gitignore` if the application did not provide one
const ignorePath = path.join(root, '.gitignore')
if (!fs.existsSync(ignorePath)) {
fs.copyFileSync(
getTemplateFile({ template, mode, file: 'gitignore' }),
ignorePath
)
}
// Copy `next-env.d.ts` to any example that is typescript
const tsconfigPath = path.join(root, 'tsconfig.json')
if (fs.existsSync(tsconfigPath)) {
fs.copyFileSync(
getTemplateFile({ template, mode: 'ts', file: 'next-env.d.ts' }),
path.join(root, 'next-env.d.ts')
)
}
hasPackageJson = fs.existsSync(packageJsonPath)
if (hasPackageJson) {
console.log('Installing packages. This might take a couple of minutes.')
console.log()
await install(packageManager, isOnline)
console.log()
}
} else {
/**
* If an example repository is not provided for cloning, proceed
* by installing from a template.
*/
await installTemplate({
appName,
root,
template,
mode,
packageManager,
isOnline,
tailwind,
eslint,
srcDir,
importAlias,
})
}
if (tryGitInit(root)) {
console.log('Initialized a git repository.')
console.log()
}
let cdpath: string
if (path.join(originalDirectory, appName) === appPath) {
cdpath = appName
} else {
cdpath = appPath
}
console.log(`${green('Success!')} Created ${appName} at ${appPath}`)
if (hasPackageJson) {
console.log('Inside that directory, you can run several commands:')
console.log()
console.log(cyan(` ${packageManager} ${useYarn ? '' : 'run '}dev`))
console.log(' Starts the development server.')
console.log()
console.log(cyan(` ${packageManager} ${useYarn ? '' : 'run '}build`))
console.log(' Builds the app for production.')
console.log()
console.log(cyan(` ${packageManager} start`))
console.log(' Runs the built app in production mode.')
console.log()
console.log('We suggest that you begin by typing:')
console.log()
console.log(cyan(' cd'), cdpath)
console.log(` ${cyan(`${packageManager} ${useYarn ? '' : 'run '}dev`)}`)
}
console.log()
}
/* eslint-disable import/no-extraneous-dependencies */
import { async as glob } from 'fast-glob'
import path from 'path'
import fs from 'fs'
interface CopyOption {
cwd?: string
rename?: (basename: string) => string
parents?: boolean
}
const identity = (x: string) => x
export const copy = async (
src: string | string[],
dest: string,
{ cwd, rename = identity, parents = true }: CopyOption = {}
) => {
const source = typeof src === 'string' ? [src] : src
if (source.length === 0 || !dest) {
throw new TypeError('`src` and `dest` are required')
}
const sourceFiles = await glob(source, {
cwd,
dot: true,
absolute: false,
stats: false,
})
const destRelativeToCwd = cwd ? path.resolve(cwd, dest) : dest
return Promise.all(
sourceFiles.map(async (p) => {
const dirname = path.dirname(p)
const basename = rename(path.basename(p))
const from = cwd ? path.resolve(cwd, p) : p
const to = parents
? path.join(destRelativeToCwd, dirname, basename)
: path.join(destRelativeToCwd, basename)
// Ensure the destination directory exists
await fs.promises.mkdir(path.dirname(to), { recursive: true })
return fs.promises.copyFile(from, to)
})
)
}
/* eslint-disable import/no-extraneous-dependencies */
import got from 'got'
import tar from 'tar'
import { Stream } from 'stream'
import { promisify } from 'util'
import { join } from 'path'
import { tmpdir } from 'os'
import { createWriteStream, promises as fs } from 'fs'
const pipeline = promisify(Stream.pipeline)
export type RepoInfo = {
username: string
name: string
branch: string
filePath: string
}
export async function isUrlOk(url: string): Promise<boolean> {
const res = await got.head(url).catch((e) => e)
return res.statusCode === 200
}
export async function getRepoInfo(
url: URL,
examplePath?: string
): Promise<RepoInfo | undefined> {
const [, username, name, t, _branch, ...file] = url.pathname.split('/')
const filePath = examplePath ? examplePath.replace(/^\//, '') : file.join('/')
if (
// Support repos whose entire purpose is to be a Next.js example, e.g.
// https://github.com/:username/:my-cool-nextjs-example-repo-name.
t === undefined ||
// Support GitHub URL that ends with a trailing slash, e.g.
// https://github.com/:username/:my-cool-nextjs-example-repo-name/
// In this case "t" will be an empty string while the next part "_branch" will be undefined
(t === '' && _branch === undefined)
) {
const infoResponse = await got(
`https://api.github.com/repos/${username}/${name}`
).catch((e) => e)
if (infoResponse.statusCode !== 200) {
return
}
const info = JSON.parse(infoResponse.body)
return { username, name, branch: info['default_branch'], filePath }
}
// If examplePath is available, the branch name takes the entire path
const branch = examplePath
? `${_branch}/${file.join('/')}`.replace(new RegExp(`/${filePath}|/$`), '')
: _branch
if (username && name && branch && t === 'tree') {
return { username, name, branch, filePath }
}
}
export function hasRepo({
username,
name,
branch,
filePath,
}: RepoInfo): Promise<boolean> {
const contentsUrl = `https://api.github.com/repos/${username}/${name}/contents`
const packagePath = `${filePath ? `/${filePath}` : ''}/package.json`
return isUrlOk(contentsUrl + packagePath + `?ref=${branch}`)
}
export function existsInRepo(nameOrUrl: string): Promise<boolean> {
try {
const url = new URL(nameOrUrl)
return isUrlOk(url.href)
} catch {
return isUrlOk(
`https://api.github.com/repos/vercel/next.js/contents/examples/${encodeURIComponent(
nameOrUrl
)}`
)
}
}
async function downloadTar(url: string) {
const tempFile = join(tmpdir(), `next.js-cna-example.temp-${Date.now()}`)
await pipeline(got.stream(url), createWriteStream(tempFile))
return tempFile
}
export async function downloadAndExtractRepo(
root: string,
{ username, name, branch, filePath }: RepoInfo
) {
const tempFile = await downloadTar(
`https://codeload.github.com/${username}/${name}/tar.gz/${branch}`
)
await tar.x({
file: tempFile,
cwd: root,
strip: filePath ? filePath.split('/').length + 1 : 1,
filter: (p) =>
p.startsWith(
`${name}-${branch.replace(/\//g, '-')}${
filePath ? `/${filePath}/` : '/'
}`
),
})
await fs.unlink(tempFile)
}
export async function downloadAndExtractExample(root: string, name: string) {
if (name === '__internal-testing-retry') {
throw new Error('This is an internal example for testing the CLI.')
}
const tempFile = await downloadTar(
'https://codeload.github.com/vercel/next.js/tar.gz/canary'
)
await tar.x({
file: tempFile,
cwd: root,
strip: 2 + name.split('/').length,
filter: (p) => p.includes(`next.js-canary/examples/${name}/`),
})
await fs.unlink(tempFile)
}
export type PackageManager = 'npm' | 'pnpm' | 'yarn' | 'bun'
export function getPkgManager(): PackageManager {
const userAgent = process.env.npm_config_user_agent || ''
if (userAgent.startsWith('yarn')) {
return 'yarn'
}
if (userAgent.startsWith('pnpm')) {
return 'pnpm'
}
if (userAgent.startsWith('bun')) {
return 'bun'
}
return 'npm'
}
/* eslint-disable import/no-extraneous-dependencies */
import { execSync } from 'child_process'
import path from 'path'
import fs from 'fs'
function isInGitRepository(): boolean {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
return true
} catch (_) {}
return false
}
function isInMercurialRepository(): boolean {
try {
execSync('hg --cwd . root', { stdio: 'ignore' })
return true
} catch (_) {}
return false
}
function isDefaultBranchSet(): boolean {
try {
execSync('git config init.defaultBranch', { stdio: 'ignore' })
return true
} catch (_) {}
return false
}
export function tryGitInit(root: string): boolean {
let didInit = false
try {
execSync('git --version', { stdio: 'ignore' })
if (isInGitRepository() || isInMercurialRepository()) {
return false
}
execSync('git init', { stdio: 'ignore' })
didInit = true
if (!isDefaultBranchSet()) {
execSync('git checkout -b main', { stdio: 'ignore' })
}
execSync('git add -A', { stdio: 'ignore' })
execSync('git commit -m "Initial commit from Create Next App"', {
stdio: 'ignore',
})
return true
} catch (e) {
if (didInit) {
try {
fs.rmSync(path.join(root, '.git'), { recursive: true, force: true })
} catch (_) {}
}
return false
}
}
/* eslint-disable import/no-extraneous-dependencies */
import { yellow } from 'picocolors'
import spawn from 'cross-spawn'
import type { PackageManager } from './get-pkg-manager'
/**
* Spawn a package manager installation based on user preference.
*
* @returns A Promise that resolves once the installation is finished.
*/
export async function install(
/** Indicate which package manager to use. */
packageManager: PackageManager,
/** Indicate whether there is an active Internet connection.*/
isOnline: boolean
): Promise<void> {
let args: string[] = ['install']
if (!isOnline) {
console.log(
yellow('You appear to be offline.\nFalling back to the local cache.')
)
args.push('--offline')
}
/**
* Return a Promise that resolves once the installation is finished.
*/
return new Promise((resolve, reject) => {
/**
* Spawn the installation process.
*/
const child = spawn(packageManager, args, {
stdio: 'inherit',
env: {
...process.env,
ADBLOCK: '1',
// we set NODE_ENV to development as pnpm skips dev
// dependencies when production
NODE_ENV: 'development',
DISABLE_OPENCOLLECTIVE: '1',
},
})
child.on('close', (code) => {
if (code !== 0) {
reject({ command: `${packageManager} ${args.join(' ')}` })
return
}
resolve()
})
})
}
/* eslint-disable import/no-extraneous-dependencies */
import { green, blue } from 'picocolors'
import fs from 'fs'
import path from 'path'
export function isFolderEmpty(root: string, name: string): boolean {
const validFiles = [
'.DS_Store',
'.git',
'.gitattributes',
'.gitignore',
'.gitlab-ci.yml',
'.hg',
'.hgcheck',
'.hgignore',
'.idea',
'.npmignore',
'.travis.yml',
'LICENSE',
'Thumbs.db',
'docs',
'mkdocs.yml',
'npm-debug.log',
'yarn-debug.log',
'yarn-error.log',
'yarnrc.yml',
'.yarn',
]
const conflicts = fs
.readdirSync(root)
.filter((file) => !validFiles.includes(file))
// Support IntelliJ IDEA-based editors
.filter((file) => !/\.iml$/.test(file))
if (conflicts.length > 0) {
console.log(
`The directory ${green(name)} contains files that could conflict:`
)
console.log()
for (const file of conflicts) {
try {
const stats = fs.lstatSync(path.join(root, file))
if (stats.isDirectory()) {
console.log(` ${blue(file)}/`)
} else {
console.log(` ${file}`)
}
} catch {
console.log(` ${file}`)
}
}
console.log()
console.log(
'Either try using a new directory name, or remove the files listed above.'
)
console.log()
return false
}
return true
}
import { execSync } from 'child_process'
import dns from 'dns'
import url from 'url'
function getProxy(): string | undefined {
if (process.env.https_proxy) {
return process.env.https_proxy
}
try {
const httpsProxy = execSync('npm config get https-proxy').toString().trim()
return httpsProxy !== 'null' ? httpsProxy : undefined
} catch (e) {
return
}
}
export function getOnline(): Promise<boolean> {
return new Promise((resolve) => {
dns.lookup('registry.yarnpkg.com', (registryErr) => {
if (!registryErr) {
return resolve(true)
}
const proxy = getProxy()
if (!proxy) {
return resolve(false)
}
const { hostname } = url.parse(proxy)
if (!hostname) {
return resolve(false)
}
dns.lookup(hostname, (proxyErr) => {
resolve(proxyErr == null)
})
})
})
}
import fs from 'fs'
export async function isWriteable(directory: string): Promise<boolean> {
try {
await fs.promises.access(directory, (fs.constants || fs).W_OK)
return true
} catch (err) {
return false
}
}
import fs from 'fs'
export function makeDir(
root: string,
options = { recursive: true }
): Promise<string | undefined> {
return fs.promises.mkdir(root, options)
}
// eslint-disable-next-line import/no-extraneous-dependencies
import validateProjectName from 'validate-npm-package-name'
export function validateNpmName(name: string): {
valid: boolean
problems?: string[]
} {
const nameValidation = validateProjectName(name)
if (nameValidation.validForNewPackages) {
return { valid: true }
}
return {
valid: false,
problems: [
...(nameValidation.errors || []),
...(nameValidation.warnings || []),
],
}
}
#!/usr/bin/env node
/* eslint-disable import/no-extraneous-dependencies */
import { cyan, green, red, yellow, bold, blue } from 'picocolors'
import Commander from 'commander'
import Conf from 'conf'
import path from 'path'
import prompts from 'prompts'
import checkForUpdate from 'update-check'
import { createApp, DownloadError } from './create-app'
import { getPkgManager } from './helpers/get-pkg-manager'
import { validateNpmName } from './helpers/validate-pkg'
import packageJson from './package.json'
import ciInfo from 'ci-info'
import { isFolderEmpty } from './helpers/is-folder-empty'
import fs from 'fs'
let projectPath: string = ''
const handleSigTerm = () => process.exit(0)
process.on('SIGINT', handleSigTerm)
process.on('SIGTERM', handleSigTerm)
const onPromptState = (state: any) => {
if (state.aborted) {
// If we don't re-enable the terminal cursor before exiting
// the program, the cursor will remain hidden
process.stdout.write('\x1B[?25h')
process.stdout.write('\n')
process.exit(1)
}
}
const program = new Commander.Command(packageJson.name)
.version(packageJson.version)
.arguments('<project-directory>')
.usage(`${green('<project-directory>')} [options]`)
.action((name) => {
projectPath = name
})
.option(
'--ts, --typescript',
`
Initialize as a TypeScript project. (default)
`
)
.option(
'--js, --javascript',
`
Initialize as a JavaScript project.
`
)
.option(
'--tailwind',
`
Initialize with Tailwind CSS config. (default)
`
)
.option(
'--eslint',
`
Initialize with eslint config.
`
)
.option(
'--app',
`
Initialize as an App Router project.
`
)
.option(
'--src-dir',
`
Initialize inside a \`src/\` directory.
`
)
.option(
'--import-alias <alias-to-configure>',
`
Specify import alias to use (default "@/*").
`
)
.option(
'--use-npm',
`
Explicitly tell the CLI to bootstrap the application using npm
`
)
.option(
'--use-pnpm',
`
Explicitly tell the CLI to bootstrap the application using pnpm
`
)
.option(
'--use-yarn',
`
Explicitly tell the CLI to bootstrap the application using Yarn
`
)
.option(
'--use-bun',
`
Explicitly tell the CLI to bootstrap the application using Bun
`
)
.option(
'-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 GitHub URL. The URL can use
any branch and/or subdirectory
`
)
.option(
'--example-path <path-to-example>',
`
In a rare case, your GitHub URL might contain a branch name with
a slash (e.g. bug/fix-1) and the path to the example (e.g. foo/bar).
In this case, you must specify the path to the example separately:
--example-path foo/bar
`
)
.option(
'--reset-preferences',
`
Explicitly tell the CLI to reset any stored preferences
`
)
.allowUnknownOption()
.parse(process.argv)
const packageManager = !!program.useNpm
? 'npm'
: !!program.usePnpm
? 'pnpm'
: !!program.useYarn
? 'yarn'
: !!program.useBun
? 'bun'
: getPkgManager()
async function run(): Promise<void> {
const conf = new Conf({ projectName: 'create-next-app' })
if (program.resetPreferences) {
conf.clear()
console.log(`Preferences reset successfully`)
return
}
if (typeof projectPath === 'string') {
projectPath = projectPath.trim()
}
if (!projectPath) {
const res = await prompts({
onState: onPromptState,
type: 'text',
name: 'path',
message: 'What is your project named?',
initial: 'my-app',
validate: (name) => {
const validation = validateNpmName(path.basename(path.resolve(name)))
if (validation.valid) {
return true
}
return 'Invalid project name: ' + validation.problems![0]
},
})
if (typeof res.path === 'string') {
projectPath = res.path.trim()
}
}
if (!projectPath) {
console.log(
'\nPlease specify the project directory:\n' +
` ${cyan(program.name())} ${green('<project-directory>')}\n` +
'For example:\n' +
` ${cyan(program.name())} ${green('my-next-app')}\n\n` +
`Run ${cyan(`${program.name()} --help`)} to see all options.`
)
process.exit(1)
}
const resolvedProjectPath = path.resolve(projectPath)
const projectName = path.basename(resolvedProjectPath)
const { valid, problems } = validateNpmName(projectName)
if (!valid) {
console.error(
`Could not create a project called ${red(
`"${projectName}"`
)} because of npm naming restrictions:`
)
problems!.forEach((p) => console.error(` ${red(bold('*'))} ${p}`))
process.exit(1)
}
if (program.example === true) {
console.error(
'Please provide an example name or url, otherwise remove the example option.'
)
process.exit(1)
}
/**
* Verify the project dir is empty or doesn't exist
*/
const root = path.resolve(resolvedProjectPath)
const appName = path.basename(root)
const folderExists = fs.existsSync(root)
if (folderExists && !isFolderEmpty(root, appName)) {
process.exit(1)
}
const example = typeof program.example === 'string' && program.example.trim()
const preferences = (conf.get('preferences') || {}) as Record<
string,
boolean | string
>
/**
* If the user does not provide the necessary flags, prompt them for whether
* to use TS or JS.
*/
if (!example) {
const defaults: typeof preferences = {
typescript: true,
eslint: true,
tailwind: true,
app: true,
srcDir: false,
importAlias: '@/*',
customizeImportAlias: false,
}
const getPrefOrDefault = (field: string) =>
preferences[field] ?? defaults[field]
if (!program.typescript && !program.javascript) {
if (ciInfo.isCI) {
// default to TypeScript in CI as we can't prompt to
// prevent breaking setup flows
program.typescript = getPrefOrDefault('typescript')
} else {
const styledTypeScript = blue('TypeScript')
const { typescript } = await prompts(
{
type: 'toggle',
name: 'typescript',
message: `Would you like to use ${styledTypeScript}?`,
initial: getPrefOrDefault('typescript'),
active: 'Yes',
inactive: 'No',
},
{
/**
* User inputs Ctrl+C or Ctrl+D to exit the prompt. We should close the
* process and not write to the file system.
*/
onCancel: () => {
console.error('Exiting.')
process.exit(1)
},
}
)
/**
* Depending on the prompt response, set the appropriate program flags.
*/
program.typescript = Boolean(typescript)
program.javascript = !Boolean(typescript)
preferences.typescript = Boolean(typescript)
}
}
if (
!process.argv.includes('--eslint') &&
!process.argv.includes('--no-eslint')
) {
if (ciInfo.isCI) {
program.eslint = getPrefOrDefault('eslint')
} else {
const styledEslint = blue('ESLint')
const { eslint } = await prompts({
onState: onPromptState,
type: 'toggle',
name: 'eslint',
message: `Would you like to use ${styledEslint}?`,
initial: getPrefOrDefault('eslint'),
active: 'Yes',
inactive: 'No',
})
program.eslint = Boolean(eslint)
preferences.eslint = Boolean(eslint)
}
}
if (
!process.argv.includes('--tailwind') &&
!process.argv.includes('--no-tailwind')
) {
if (ciInfo.isCI) {
program.tailwind = getPrefOrDefault('tailwind')
} else {
const tw = blue('Tailwind CSS')
const { tailwind } = await prompts({
onState: onPromptState,
type: 'toggle',
name: 'tailwind',
message: `Would you like to use ${tw}?`,
initial: getPrefOrDefault('tailwind'),
active: 'Yes',
inactive: 'No',
})
program.tailwind = Boolean(tailwind)
preferences.tailwind = Boolean(tailwind)
}
}
if (
!process.argv.includes('--src-dir') &&
!process.argv.includes('--no-src-dir')
) {
if (ciInfo.isCI) {
program.srcDir = getPrefOrDefault('srcDir')
} else {
const styledSrcDir = blue('`src/` directory')
const { srcDir } = await prompts({
onState: onPromptState,
type: 'toggle',
name: 'srcDir',
message: `Would you like to use ${styledSrcDir}?`,
initial: getPrefOrDefault('srcDir'),
active: 'Yes',
inactive: 'No',
})
program.srcDir = Boolean(srcDir)
preferences.srcDir = Boolean(srcDir)
}
}
if (!process.argv.includes('--app') && !process.argv.includes('--no-app')) {
if (ciInfo.isCI) {
program.app = getPrefOrDefault('app')
} else {
const styledAppDir = blue('App Router')
const { appRouter } = await prompts({
onState: onPromptState,
type: 'toggle',
name: 'appRouter',
message: `Would you like to use ${styledAppDir}? (recommended)`,
initial: getPrefOrDefault('app'),
active: 'Yes',
inactive: 'No',
})
program.app = Boolean(appRouter)
}
}
if (
typeof program.importAlias !== 'string' ||
!program.importAlias.length
) {
if (ciInfo.isCI) {
// We don't use preferences here because the default value is @/* regardless of existing preferences
program.importAlias = defaults.importAlias
} else {
const styledImportAlias = blue('import alias')
const { customizeImportAlias } = await prompts({
onState: onPromptState,
type: 'toggle',
name: 'customizeImportAlias',
message: `Would you like to customize the default ${styledImportAlias} (${defaults.importAlias})?`,
initial: getPrefOrDefault('customizeImportAlias'),
active: 'Yes',
inactive: 'No',
})
if (!customizeImportAlias) {
// We don't use preferences here because the default value is @/* regardless of existing preferences
program.importAlias = defaults.importAlias
} else {
const { importAlias } = await prompts({
onState: onPromptState,
type: 'text',
name: 'importAlias',
message: `What ${styledImportAlias} would you like configured?`,
initial: getPrefOrDefault('importAlias'),
validate: (value) =>
/.+\/\*/.test(value)
? true
: 'Import alias must follow the pattern <prefix>/*',
})
program.importAlias = importAlias
preferences.importAlias = importAlias
}
}
}
}
try {
await createApp({
appPath: resolvedProjectPath,
packageManager,
example: example && example !== 'default' ? example : undefined,
examplePath: program.examplePath,
typescript: program.typescript,
tailwind: program.tailwind,
eslint: program.eslint,
appRouter: program.app,
srcDir: program.srcDir,
importAlias: program.importAlias,
})
} catch (reason) {
if (!(reason instanceof DownloadError)) {
throw reason
}
const res = await prompts({
onState: onPromptState,
type: 'confirm',
name: 'builtin',
message:
`Could not download "${example}" because of a connectivity issue between your machine and GitHub.\n` +
`Do you want to use the default template instead?`,
initial: true,
})
if (!res.builtin) {
throw reason
}
await createApp({
appPath: resolvedProjectPath,
packageManager,
typescript: program.typescript,
eslint: program.eslint,
tailwind: program.tailwind,
appRouter: program.app,
srcDir: program.srcDir,
importAlias: program.importAlias,
})
}
conf.set('preferences', preferences)
}
const update = checkForUpdate(packageJson).catch(() => null)
async function notifyUpdate(): Promise<void> {
try {
const res = await update
if (res?.latest) {
const updateMessage =
packageManager === 'yarn'
? '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(
yellow(bold('A new version of `create-next-app` is available!')) +
'\n' +
'You can update by running: ' +
cyan(updateMessage) +
'\n'
)
}
process.exit()
} catch {
// ignore error
}
}
run()
.then(notifyUpdate)
.catch(async (reason) => {
console.log()
console.log('Aborting installation.')
if (reason.command) {
console.log(` ${cyan(reason.command)} has failed.`)
} else {
console.log(
red('Unexpected error. Please report it as a bug:') + '\n',
reason
)
}
console.log()
await notifyUpdate()
process.exit(1)
})
{
"name": "create-next-app",
"version": "13.5.6",
"keywords": [
"react",
"next",
"next.js"
],
"description": "Create Next.js-powered React apps with one command",
"repository": {
"type": "git",
"url": "https://github.com/vercel/next.js",
"directory": "packages/create-next-app"
},
"author": "Next.js Team <support@vercel.com>",
"license": "MIT",
"bin": {
"create-next-app": "./dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"dev": "ncc build ./index.ts -w -o dist/",
"prerelease": "node ../../scripts/rm.mjs dist",
"release": "ncc build ./index.ts -o ./dist/ --minify --no-cache --no-source-map-register",
"prepublishOnly": "cd ../../ && turbo run build",
"build": "pnpm release",
"lint-fix": "pnpm prettier -w --plugin prettier-plugin-tailwindcss 'templates/*-tw/{ts,js}/{app,pages}/**/*.{js,ts,tsx}'"
},
"devDependencies": {
"@types/async-retry": "1.4.2",
"@types/ci-info": "2.0.0",
"@types/cross-spawn": "6.0.0",
"@types/node": "^20.2.5",
"@types/prompts": "2.0.1",
"@types/tar": "6.1.5",
"@types/validate-npm-package-name": "3.0.0",
"@vercel/ncc": "0.34.0",
"async-retry": "1.3.1",
"ci-info": "watson/ci-info#f43f6a1cefff47fb361c88cf4b943fdbcaafe540",
"commander": "2.20.0",
"conf": "10.2.0",
"cross-spawn": "7.0.3",
"fast-glob": "3.3.1",
"got": "10.7.0",
"picocolors": "1.0.0",
"prettier-plugin-tailwindcss": "0.3.0",
"prompts": "2.1.0",
"tar": "6.1.15",
"update-check": "1.5.4",
"validate-npm-package-name": "3.0.0"
},
"engines": {
"node": ">=16.14.0"
}
}
\ No newline at end of file
This diff is collapsed.
{
"compilerOptions": {
"target": "es2019",
"moduleResolution": "node",
"strict": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"skipLibCheck": false
},
"exclude": ["templates", "dist"]
}
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment