create-app.ts 6.1 KB
Newer Older
J
Joe Haddad 已提交
1
/* eslint-disable import/no-extraneous-dependencies */
2
import retry from 'async-retry'
J
Joe Haddad 已提交
3 4 5 6 7 8
import chalk from 'chalk'
import cpy from 'cpy'
import fs from 'fs'
import makeDir from 'make-dir'
import os from 'os'
import path from 'path'
9 10 11
import {
  downloadAndExtractExample,
  downloadAndExtractRepo,
12 13 14
  getRepoInfo,
  hasExample,
  hasRepo,
15 16
  RepoInfo,
} from './helpers/examples'
17
import { tryGitInit } from './helpers/git'
J
Joe Haddad 已提交
18 19 20 21 22
import { install } from './helpers/install'
import { isFolderEmpty } from './helpers/is-folder-empty'
import { getOnline } from './helpers/is-online'
import { shouldUseYarn } from './helpers/should-use-yarn'

23 24
export class DownloadError extends Error {}

J
Joe Haddad 已提交
25 26 27 28
export async function createApp({
  appPath,
  useNpm,
  example,
29
  examplePath,
J
Joe Haddad 已提交
30 31 32 33
}: {
  appPath: string
  useNpm: boolean
  example?: string
34
  examplePath?: string
35
}): Promise<void> {
36 37
  let repoInfo: RepoInfo | undefined

J
Joe Haddad 已提交
38
  if (example) {
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
    let repoUrl: URL | undefined

    try {
      repoUrl = new URL(example)
    } catch (error) {
      if (error.code !== 'ERR_INVALID_URL') {
        console.error(error)
        process.exit(1)
      }
    }

    if (repoUrl) {
      if (repoUrl.origin !== 'https://github.com') {
        console.error(
          `Invalid URL: ${chalk.red(
            `"${example}"`
          )}. Only GitHub repositories are supported. Please use a GitHub URL and try again.`
        )
        process.exit(1)
      }

60
      repoInfo = await getRepoInfo(repoUrl, examplePath)
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80

      if (!repoInfo) {
        console.error(
          `Found invalid GitHub URL: ${chalk.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 ${chalk.red(
            `"${example}"`
          )}. Please check that the repository exists and try again.`
        )
        process.exit(1)
      }
81
    } else if (example !== '__internal-testing-retry') {
82 83 84 85 86 87 88 89 90 91
      const found = await hasExample(example)

      if (!found) {
        console.error(
          `Could not locate an example named ${chalk.red(
            `"${example}"`
          )}. Please check your spelling and try again.`
        )
        process.exit(1)
      }
J
Joe Haddad 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114
    }
  }

  const root = path.resolve(appPath)
  const appName = path.basename(root)

  await makeDir(root)
  if (!isFolderEmpty(root, appName)) {
    process.exit(1)
  }

  const useYarn = useNpm ? false : shouldUseYarn()
  const isOnline = !useYarn || (await getOnline())
  const originalDirectory = process.cwd()

  const displayedCommand = useYarn ? 'yarn' : 'npm'
  console.log(`Creating a new Next.js app in ${chalk.green(root)}.`)
  console.log()

  await makeDir(root)
  process.chdir(root)

  if (example) {
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
    try {
      if (repoInfo) {
        const repoInfo2 = repoInfo
        console.log(
          `Downloading files from repo ${chalk.cyan(
            example
          )}. This might take a moment.`
        )
        console.log()
        await retry(() => downloadAndExtractRepo(root, repoInfo2), {
          retries: 3,
        })
      } else {
        console.log(
          `Downloading files for example ${chalk.cyan(
            example
          )}. This might take a moment.`
        )
        console.log()
        await retry(() => downloadAndExtractExample(root, example), {
          retries: 3,
        })
      }
    } catch (reason) {
      throw new DownloadError(reason)
140
    }
141 142 143 144 145 146 147 148 149
    // Copy our default `.gitignore` if the application did not provide one
    const ignorePath = path.join(root, '.gitignore')
    if (!fs.existsSync(ignorePath)) {
      fs.copyFileSync(
        path.join(__dirname, 'templates', 'default', 'gitignore'),
        ignorePath
      )
    }

J
Joe Haddad 已提交
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179
    console.log('Installing packages. This might take a couple of minutes.')
    console.log()

    await install(root, null, { useYarn, isOnline })
    console.log()
  } else {
    const packageJson = {
      name: appName,
      version: '0.1.0',
      private: true,
      scripts: { dev: 'next dev', build: 'next build', start: 'next start' },
    }
    fs.writeFileSync(
      path.join(root, 'package.json'),
      JSON.stringify(packageJson, null, 2) + os.EOL
    )

    console.log(
      `Installing ${chalk.cyan('react')}, ${chalk.cyan(
        'react-dom'
      )}, and ${chalk.cyan('next')} using ${displayedCommand}...`
    )
    console.log()

    await install(root, ['react', 'react-dom', 'next'], { useYarn, isOnline })
    console.log()

    await cpy('**', root, {
      parents: true,
      cwd: path.join(__dirname, 'templates', 'default'),
J
Joe Haddad 已提交
180
      rename: (name) => {
J
Joe Haddad 已提交
181 182 183 184
        switch (name) {
          case 'gitignore': {
            return '.'.concat(name)
          }
185 186 187 188 189
          // README.md is ignored by webpack-asset-relocator-loader used by ncc:
          // https://github.com/zeit/webpack-asset-relocator-loader/blob/e9308683d47ff507253e37c9bcbb99474603192b/src/asset-relocator.js#L227
          case 'README-template.md': {
            return 'README.md'
          }
J
Joe Haddad 已提交
190 191 192 193 194 195 196 197
          default: {
            return name
          }
        }
      },
    })
  }

198 199 200 201 202
  if (tryGitInit(root)) {
    console.log('Initialized a git repository.')
    console.log()
  }

J
Joe Haddad 已提交
203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229
  let cdpath: string
  if (path.join(originalDirectory, appName) === appPath) {
    cdpath = appName
  } else {
    cdpath = appPath
  }

  console.log(`${chalk.green('Success!')} Created ${appName} at ${appPath}`)
  console.log('Inside that directory, you can run several commands:')
  console.log()
  console.log(chalk.cyan(`  ${displayedCommand} ${useYarn ? '' : 'run '}dev`))
  console.log('    Starts the development server.')
  console.log()
  console.log(chalk.cyan(`  ${displayedCommand} ${useYarn ? '' : 'run '}build`))
  console.log('    Builds the app for production.')
  console.log()
  console.log(chalk.cyan(`  ${displayedCommand} 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(chalk.cyan('  cd'), cdpath)
  console.log(
    `  ${chalk.cyan(`${displayedCommand} ${useYarn ? '' : 'run '}dev`)}`
  )
  console.log()
}