2019-07-19 21:55:30 +02:00
|
|
|
#!/usr/bin/env node
|
2020-05-28 10:23:10 +02:00
|
|
|
/* eslint-disable import/no-extraneous-dependencies */
|
2019-07-19 21:55:30 +02:00
|
|
|
import chalk from 'chalk'
|
|
|
|
import Commander from 'commander'
|
|
|
|
import path from 'path'
|
|
|
|
import prompts from 'prompts'
|
|
|
|
import checkForUpdate from 'update-check'
|
2020-05-26 18:39:18 +02:00
|
|
|
import { createApp, DownloadError } from './create-app'
|
2022-03-04 00:49:24 +01:00
|
|
|
import { getPkgManager } from './helpers/get-pkg-manager'
|
2019-07-19 21:55:30 +02:00
|
|
|
import { validateNpmName } from './helpers/validate-pkg'
|
|
|
|
import packageJson from './package.json'
|
|
|
|
|
|
|
|
let projectPath: string = ''
|
|
|
|
|
|
|
|
const program = new Commander.Command(packageJson.name)
|
|
|
|
.version(packageJson.version)
|
|
|
|
.arguments('<project-directory>')
|
|
|
|
.usage(`${chalk.green('<project-directory>')} [options]`)
|
2020-05-18 21:24:37 +02:00
|
|
|
.action((name) => {
|
2019-07-19 21:55:30 +02:00
|
|
|
projectPath = name
|
|
|
|
})
|
2021-05-07 10:08:16 +02:00
|
|
|
.option(
|
|
|
|
'--ts, --typescript',
|
|
|
|
`
|
|
|
|
|
|
|
|
Initialize as a TypeScript project.
|
|
|
|
`
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'--use-npm',
|
|
|
|
`
|
|
|
|
|
|
|
|
Explicitly tell the CLI to bootstrap the app using npm
|
2022-03-04 00:49:24 +01:00
|
|
|
`
|
|
|
|
)
|
|
|
|
.option(
|
|
|
|
'--use-pnpm',
|
|
|
|
`
|
|
|
|
|
|
|
|
Explicitly tell the CLI to bootstrap the app using pnpm
|
2021-05-07 10:08:16 +02:00
|
|
|
`
|
|
|
|
)
|
2019-07-19 21:55:30 +02:00
|
|
|
.option(
|
2020-04-07 19:11:29 +02:00
|
|
|
'-e, --example [name]|[github-url]',
|
2020-02-27 16:32:33 +01:00
|
|
|
`
|
|
|
|
|
|
|
|
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
|
|
|
|
`
|
2019-07-19 21:55:30 +02:00
|
|
|
)
|
|
|
|
.allowUnknownOption()
|
|
|
|
.parse(process.argv)
|
|
|
|
|
2020-05-10 23:51:47 +02:00
|
|
|
async function run(): Promise<void> {
|
2019-07-19 21:55:30 +02:00
|
|
|
if (typeof projectPath === 'string') {
|
|
|
|
projectPath = projectPath.trim()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!projectPath) {
|
|
|
|
const res = await prompts({
|
|
|
|
type: 'text',
|
|
|
|
name: 'path',
|
|
|
|
message: 'What is your project named?',
|
|
|
|
initial: 'my-app',
|
2020-05-18 21:24:37 +02:00
|
|
|
validate: (name) => {
|
2019-07-19 21:55:30 +02:00
|
|
|
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(
|
2022-05-22 20:50:08 +02:00
|
|
|
'\nPlease specify the project directory:\n' +
|
|
|
|
` ${chalk.cyan(program.name())} ${chalk.green(
|
|
|
|
'<project-directory>'
|
|
|
|
)}\n` +
|
|
|
|
'For example:\n' +
|
|
|
|
` ${chalk.cyan(program.name())} ${chalk.green('my-next-app')}\n\n` +
|
|
|
|
`Run ${chalk.cyan(`${program.name()} --help`)} to see all options.`
|
2019-07-19 21:55:30 +02:00
|
|
|
)
|
|
|
|
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 ${chalk.red(
|
|
|
|
`"${projectName}"`
|
|
|
|
)} because of npm naming restrictions:`
|
|
|
|
)
|
|
|
|
|
2020-05-18 21:24:37 +02:00
|
|
|
problems!.forEach((p) => console.error(` ${chalk.red.bold('*')} ${p}`))
|
2019-07-19 21:55:30 +02:00
|
|
|
process.exit(1)
|
|
|
|
}
|
|
|
|
|
2020-06-10 05:14:24 +02:00
|
|
|
if (program.example === true) {
|
|
|
|
console.error(
|
|
|
|
'Please provide an example name or url, otherwise remove the example option.'
|
|
|
|
)
|
|
|
|
process.exit(1)
|
2020-04-07 19:11:29 +02:00
|
|
|
}
|
|
|
|
|
2022-03-04 00:49:24 +01:00
|
|
|
const packageManager = !!program.useNpm
|
|
|
|
? 'npm'
|
|
|
|
: !!program.usePnpm
|
|
|
|
? 'pnpm'
|
2022-03-26 09:29:52 +01:00
|
|
|
: getPkgManager()
|
2022-03-04 00:49:24 +01:00
|
|
|
|
2020-05-24 00:50:31 +02:00
|
|
|
const example = typeof program.example === 'string' && program.example.trim()
|
2020-05-26 18:39:18 +02:00
|
|
|
try {
|
|
|
|
await createApp({
|
|
|
|
appPath: resolvedProjectPath,
|
2022-03-04 00:49:24 +01:00
|
|
|
packageManager,
|
2020-05-26 18:39:18 +02:00
|
|
|
example: example && example !== 'default' ? example : undefined,
|
|
|
|
examplePath: program.examplePath,
|
2021-05-07 10:08:16 +02:00
|
|
|
typescript: program.typescript,
|
2020-05-26 18:39:18 +02:00
|
|
|
})
|
|
|
|
} catch (reason) {
|
|
|
|
if (!(reason instanceof DownloadError)) {
|
|
|
|
throw reason
|
|
|
|
}
|
|
|
|
|
|
|
|
const res = await prompts({
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2021-05-07 10:08:16 +02:00
|
|
|
await createApp({
|
|
|
|
appPath: resolvedProjectPath,
|
2022-03-04 00:49:24 +01:00
|
|
|
packageManager,
|
2021-05-07 10:08:16 +02:00
|
|
|
typescript: program.typescript,
|
|
|
|
})
|
2020-05-26 18:39:18 +02:00
|
|
|
}
|
2019-07-19 21:55:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const update = checkForUpdate(packageJson).catch(() => null)
|
|
|
|
|
2020-05-10 23:51:47 +02:00
|
|
|
async function notifyUpdate(): Promise<void> {
|
2019-07-19 21:55:30 +02:00
|
|
|
try {
|
|
|
|
const res = await update
|
2020-01-08 17:30:53 +01:00
|
|
|
if (res?.latest) {
|
2022-03-04 00:49:24 +01:00
|
|
|
const pkgManager = getPkgManager()
|
2019-07-19 21:55:30 +02:00
|
|
|
console.log(
|
2022-05-22 20:50:08 +02:00
|
|
|
chalk.yellow.bold('A new version of `create-next-app` is available!') +
|
|
|
|
'\n' +
|
|
|
|
'You can update by running: ' +
|
2019-07-19 21:55:30 +02:00
|
|
|
chalk.cyan(
|
2022-03-04 00:49:24 +01:00
|
|
|
pkgManager === 'yarn'
|
2019-07-19 21:55:30 +02:00
|
|
|
? 'yarn global add create-next-app'
|
2022-03-04 00:49:24 +01:00
|
|
|
: `${pkgManager} install --global create-next-app`
|
2022-05-22 20:50:08 +02:00
|
|
|
) +
|
|
|
|
'\n'
|
2019-07-19 21:55:30 +02:00
|
|
|
)
|
|
|
|
}
|
2020-04-07 19:11:29 +02:00
|
|
|
process.exit()
|
2019-07-19 21:55:30 +02:00
|
|
|
} catch {
|
|
|
|
// ignore error
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
run()
|
|
|
|
.then(notifyUpdate)
|
2020-05-18 21:24:37 +02:00
|
|
|
.catch(async (reason) => {
|
2019-07-19 21:55:30 +02:00
|
|
|
console.log()
|
|
|
|
console.log('Aborting installation.')
|
|
|
|
if (reason.command) {
|
|
|
|
console.log(` ${chalk.cyan(reason.command)} has failed.`)
|
|
|
|
} else {
|
2022-05-22 20:50:08 +02:00
|
|
|
console.log(
|
|
|
|
chalk.red('Unexpected error. Please report it as a bug:') + '\n',
|
|
|
|
reason
|
|
|
|
)
|
2019-07-19 21:55:30 +02:00
|
|
|
}
|
|
|
|
console.log()
|
|
|
|
|
|
|
|
await notifyUpdate()
|
|
|
|
|
|
|
|
process.exit(1)
|
|
|
|
})
|