Skip to content

Commit

Permalink
Confirm if user wants example when creating app (#10543)
Browse files Browse the repository at this point in the history
* Confirm if user wants example when creating app

* Only ask user to choose example if they provide --example flag

* Simplify the search

* Added test

* Use a select to pick the template

* Updated test

* check for lowercase and updated no example message

* Add message if no template is selected

* Updated tests

* Handle error if fetching the examples fails

* Fixed tests

* Updated test timeout

* Update examples.ts

Co-authored-by: merelinguist <merelinguist@users.noreply.github.com>
Co-authored-by: Luis Alvarez <luis@zeit.co>
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
Co-authored-by: Joe Haddad <joe.haddad@zeit.co>
  • Loading branch information
5 people authored Apr 7, 2020
1 parent bf184fc commit bc7d183
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 5 deletions.
7 changes: 7 additions & 0 deletions packages/create-next-app/helpers/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,10 @@ export function downloadAndExtractExample(
tar.extract({ cwd: root, strip: 3 }, [`next.js-canary/examples/${name}`])
)
}

export async function listExamples(): Promise<any> {
const res = await got(
'https://api.github.com/repositories/70107786/contents/examples'
)
return JSON.parse(res.body)
}
70 changes: 69 additions & 1 deletion packages/create-next-app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createApp } from './create-app'
import { validateNpmName } from './helpers/validate-pkg'
import packageJson from './package.json'
import { shouldUseYarn } from './helpers/should-use-yarn'
import { listExamples } from './helpers/examples'

let projectPath: string = ''

Expand All @@ -21,7 +22,7 @@ const program = new Commander.Command(packageJson.name)
})
.option('--use-npm')
.option(
'-e, --example <name>|<github-url>',
'-e, --example [name]|[github-url]',
`
An example to bootstrap the app with. You can use an example name
Expand Down Expand Up @@ -98,6 +99,72 @@ async function run() {
process.exit(1)
}

if (!program.example) {
const template = await prompts({
type: 'select',
name: 'value',
message: 'Pick a template',
choices: [
{ title: 'Default starter app', value: 'default' },
{ title: 'Example from the Next.js repo', value: 'example' },
],
})

if (!template.value) {
console.log()
console.log('Please specify the template')
process.exit(1)
}

if (template.value === 'example') {
let examplesJSON: any

try {
examplesJSON = await listExamples()
} catch (error) {
console.log()
console.log(
'Failed to fetch the list of examples with the following error:'
)
console.error(error)
console.log()
console.log('Switching to the default starter app')
console.log()
}

if (examplesJSON) {
const choices = examplesJSON.map((example: any) => ({
title: example.name,
value: example.name,
}))
// The search function built into `prompts` isn’t very helpful:
// someone searching for `styled-components` would get no results since
// the example is called `with-styled-components`, and `prompts` searches
// the beginnings of titles.
const nameRes = await prompts({
type: 'autocomplete',
name: 'exampleName',
message: 'Pick an example',
choices,
suggest: (input: any, choices: any) => {
const regex = new RegExp(input, 'i')
return choices.filter((choice: any) => regex.test(choice.title))
},
})

if (!nameRes.exampleName) {
console.log()
console.log(
'Please specify an example or use the default starter app.'
)
process.exit(1)
}

program.example = nameRes.exampleName
}
}
}

await createApp({
appPath: resolvedProjectPath,
useNpm: !!program.useNpm,
Expand Down Expand Up @@ -130,6 +197,7 @@ async function notifyUpdate() {
)
console.log()
}
process.exit()
} catch {
// ignore error
}
Expand Down
58 changes: 54 additions & 4 deletions test/integration/create-next-app/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,43 @@ const cwd = path.join(
)

const run = (...args) => execa('node', [cli, ...args], { cwd })
const runStarter = (...args) => {
const res = run(...args)

res.stdout.on('data', data => {
const stdout = data.toString()

if (/Pick a template/.test(stdout)) {
res.stdin.write('\n')
}
})

return res
}

describe('create next app', () => {
beforeAll(async () => {
jest.setTimeout(1000 * 60)
jest.setTimeout(1000 * 60 * 2)
await fs.mkdirp(cwd)
})

it('non-empty directory', async () => {
const projectName = 'non-empty-directory'

await fs.mkdirp(path.join(cwd, projectName))
const pkg = path.join(cwd, projectName, 'package.json')
fs.writeFileSync(pkg, '{ "foo": "bar" }')

expect.assertions(1)
try {
await run(projectName)
await runStarter(projectName)
} catch (e) {
expect(e.stdout).toMatch(/contains files that could conflict/)
}
})

it('empty directory', async () => {
const projectName = 'empty-directory'
const res = await run(projectName)
const res = await runStarter(projectName)

expect(res.exitCode).toBe(0)
expect(
Expand Down Expand Up @@ -150,4 +162,42 @@ describe('create next app', () => {
fs.existsSync(path.join(cwd, projectName, '.gitignore'))
).toBeTruthy()
})

it('should allow to manually select an example', async () => {
const runExample = (...args) => {
const res = run(...args)

function pickExample(data) {
if (/hello-world/.test(data.toString())) {
res.stdout.removeListener('data', pickExample)
res.stdin.write('\n')
}
}

function searchExample(data) {
if (/Pick an example/.test(data.toString())) {
res.stdout.removeListener('data', searchExample)
res.stdin.write('hello-world')
res.stdout.on('data', pickExample)
}
}

function selectExample(data) {
if (/Pick a template/.test(data.toString())) {
res.stdout.removeListener('data', selectExample)
res.stdin.write('\u001b[B\n') // Down key and enter
res.stdout.on('data', searchExample)
}
}

res.stdout.on('data', selectExample)

return res
}

const res = await runExample('no-example')

expect(res.exitCode).toBe(0)
expect(res.stdout).toMatch(/Downloading files for example hello-world/)
})
})

0 comments on commit bc7d183

Please sign in to comment.