Skip to content

Commit

Permalink
feat: prompt user if no --js, --ts flag provided
Browse files Browse the repository at this point in the history
  • Loading branch information
ctjlewis committed Aug 17, 2022
1 parent 05e29cf commit 215957b
Show file tree
Hide file tree
Showing 6 changed files with 254 additions and 122 deletions.
35 changes: 35 additions & 0 deletions packages/create-next-app/index.ts
Expand Up @@ -136,6 +136,41 @@ async function run(): Promise<void> {
: getPkgManager()

const example = typeof program.example === 'string' && program.example.trim()

/**
* If the user does not provide a --js or --ts flag set, ask them whether or
* not they'd like to use TypeScript.
*/
if (!program.typescript && !program.javascript) {
const { typescript } = await prompts(
{
type: 'toggle',
name: 'typescript',
message: 'Would you like to use TypeScript with this project?',
initial: true,
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)
},
}
)

if (typescript) {
program.typescript = true
} else {
program.javascript = false
program.javascript = true
}
}

try {
await createApp({
appPath: resolvedProjectPath,
Expand Down
29 changes: 29 additions & 0 deletions test/integration/create-next-app/lib/projectFiles.ts
@@ -0,0 +1,29 @@
/**
* Files common to all Next.js templates.
*/
export const allProjectFiles = [
'package.json',
'.eslintrc.json',
'node_modules/next',
'.gitignore',
]

/**
* Files specific to Next.js JS-only templates.
*/
export const jsProjectFiles = [
'pages/index.js',
'pages/_app.js',
'pages/api/hello.js',
]

/**
* Files specific to Next.js TypeScript-only templates.
*/
export const tsProjectFiles = [
'pages/index.tsx',
'pages/_app.tsx',
'pages/api/hello.ts',
'tsconfig.json',
'next-env.d.ts',
]
64 changes: 64 additions & 0 deletions test/integration/create-next-app/lib/utils.ts
@@ -0,0 +1,64 @@
/**
* @fileoverview
*
* This file contains utilities for `create-next-app` testing.
*/

import { ChildProcess, spawn, SpawnOptions } from 'child_process'
import { existsSync } from 'fs'
import { resolve } from 'path'

const cli = require.resolve('create-next-app/dist/index.js')

/**
* Run the built version of `create-next-app` with the given arguments.
*/
export const createNextApp = (args: string[], options?: SpawnOptions) => {
console.log(`[TEST] $ ${cli} ${args.join(' ')}`, { options })
return spawn('node', [cli].concat(args), options ?? {})
}

/**
* Return a Promise that resolves when the process exits with code 0 and rejects
* otherwise.
*/
export const spawnExitPromise = (childProcess: ChildProcess) => {
return new Promise((resolve, reject) => {
childProcess
.on('exit', (code) => {
if (code === 0) {
resolve(code)
} else {
reject(code)
}
})
.on('error', reject)
})
}

export const projectFilesShouldExist = (
projectRoot: string,
files: string[]
) => {
for (const file of files) {
expect(existsSync(resolve(projectRoot, file))).toBe(true)
}
}

export const projectFilesShouldntExist = (
projectRoot: string,
files: string[]
) => {
for (const file of files) {
expect(existsSync(resolve(projectRoot, file))).toBe(false)
}
}

export const projectDepsShouldBe = (
projectRoot: string,
depType: 'dependencies' | 'devDependencies',
value: any
) => {
const pkgJson = require(resolve(projectRoot, 'package.json'))
expect(Object.keys(pkgJson[depType])).toEqual(value)
}
122 changes: 0 additions & 122 deletions test/integration/create-next-app/templates.test.js

This file was deleted.

109 changes: 109 additions & 0 deletions test/integration/create-next-app/templates.test.ts
@@ -0,0 +1,109 @@
/* eslint-env jest */

import { resolve } from 'path'
import {
createNextApp,
projectDepsShouldBe,
projectFilesShouldExist,
projectFilesShouldntExist,
spawnExitPromise,
} from './lib/utils'
import {
allProjectFiles,
jsProjectFiles,
tsProjectFiles,
} from './lib/projectFiles'

import { useTempDir } from '../../../test/lib/use-temp-dir'

describe('create-next-app templates', () => {
it('should prompt user to choose if --ts or --js is not provided', async () => {
useTempDir(async (cwd) => {
const projectName = 'choose-ts-js'
const projectRoot = resolve(cwd, projectName)

/**
* Start the create-next-app call.
*/
const childProcess = createNextApp([projectName], { cwd })
/**
* Wait for the prompt to display.
*/
// await new Promise((resolve) => setTimeout(resolve, 1000));
/**
* Bind the exit listener.
*/
childProcess.on('exit', (exitCode) => {
expect(exitCode).toBe(0)
/**
* Verify it correctly emitted a TS project by looking for tsconfig.
*/
projectFilesShouldntExist(projectRoot, ['tsconfig.json'])
})
/**
* Simulate "Y" for TypeScript.
*/
childProcess.stdin.write('N\n')
})
})

it('should create TS projects with --ts, --typescript', async () => {
await useTempDir(async (cwd) => {
const projectName = 'typescript-test'
const projectRoot = resolve(cwd, projectName)

const childProcess = createNextApp([projectName, '--ts'], { cwd })
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)

projectFilesShouldntExist(projectRoot, jsProjectFiles)
projectFilesShouldExist(projectRoot, [
...allProjectFiles,
...tsProjectFiles,
])

projectDepsShouldBe(projectRoot, 'dependencies', [
'next',
'react',
'react-dom',
])

projectDepsShouldBe(projectRoot, 'devDependencies', [
'@types/node',
'@types/react',
'@types/react-dom',
'eslint',
'eslint-config-next',
'typescript',
])
})
})

it('should create JS projects with --js, --javascript', async () => {
await useTempDir(async (cwd) => {
const projectName = 'javascript-test'
const projectRoot = resolve(cwd, projectName)

const childProcess = createNextApp([projectName, '--js'], { cwd })
const exitCode = await spawnExitPromise(childProcess)
expect(exitCode).toBe(0)

projectFilesShouldntExist(projectRoot, tsProjectFiles)
projectFilesShouldExist(projectRoot, [
...allProjectFiles,
...jsProjectFiles,
])

projectDepsShouldBe(projectRoot, 'dependencies', [
'next',
'react',
'react-dom',
])

projectDepsShouldBe(projectRoot, 'devDependencies', [
'eslint',
'eslint-config-next',
])
})
})
})
17 changes: 17 additions & 0 deletions test/lib/use-temp-dir.ts
@@ -0,0 +1,17 @@
import fs from 'fs-extra'
import os from 'os'
import path from 'path'

/**
* Create a temporary directory and run a callback function inside it, deleting
* it when finished.
*/
export async function useTempDir(fn: (folder: string) => void | Promise<void>) {
const folder = path.join(os.tmpdir(), Math.random().toString(36).slice(2))
fs.mkdirp(folder)
try {
await fn(folder)
} finally {
await fs.remove(folder)
}
}

0 comments on commit 215957b

Please sign in to comment.