Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(experimental): Add additional prompts to create redwood app #6366

Closed
wants to merge 8 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
249 changes: 206 additions & 43 deletions packages/create-redwood-app/src/create-redwood-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import checkNodeVersion from 'check-node-version'
import execa from 'execa'
import fs from 'fs-extra'
import Listr from 'listr'
import { paramCase } from 'param-case'
import prompts from 'prompts'
import { hideBin } from 'yargs/helpers'
import yargs from 'yargs/yargs'
Expand Down Expand Up @@ -61,6 +62,9 @@ import { name, version } from '../package'
)}`
)

// Specify template dir
const templateDir = path.resolve(__dirname, '../template')

// Extract the args as provided by the user in the command line
const {
_: args,
Expand All @@ -69,6 +73,8 @@ import { name, version } from '../package'
overwrite,
telemetry: telemetry,
yarn1,
'git-init': gitInit,
'language-target': languageTarget,
} = yargs(hideBin(process.argv))
.scriptName(name)
.usage('Usage: $0 <project directory> [option]')
Expand Down Expand Up @@ -101,13 +107,35 @@ import { name, version } from '../package'
type: 'boolean',
describe: 'Use yarn 1. yarn 3 by default',
})
.option('git-init', {
alias: 'git',
default: null,
type: 'boolean',
describe: 'Initialize a new git repository.',
})
.option('language-target', {
alias: 'language',
choices: ['ts', 'ts-strict', 'js'],
default: 'ts',
type: 'string',
describe: 'Choose your preferred development language.',
})
.version(version)
.parse()

// Variable to hold the user args as an object that can be used for prompt overides
// This gets more useful as there are more prompts to override
let userArgs = {}

// Handle if a project name/path is specified in command line
// E.g. yarn create redwood-app my-awesome-app
if (typeof args[0] === 'string') {
Object.assign(userArgs, {
'project-name': args[0],
'project-dir': args[0],
})
}

// Handle if typescript is selected via --ts
if (typescript === true) {
Object.assign(userArgs, { typescript: true })
Expand All @@ -117,13 +145,119 @@ import { name, version } from '../package'
Object.assign(userArgs, { typescript: false })
}

// Handle if git init is selected via --git-init
if (gitInit === true) {
Object.assign(userArgs, { 'git-init': true })
}
// Handle if git init is skipped via --no-git-init
if (gitInit === false) {
Object.assign(userArgs, { 'git-init': false })
}

// Check Node/Yarn/Engines Compatability
// This checks all engine requirements, including Node.js and Yarn
let hasPassedEngineCheck = null
let engineErrorLog = []

await new Listr(
[
{
title: 'Checking node and yarn compatibility',
skip: () => {
if (yarnInstall === false) {
return 'Warning: skipping check on request'
}
},
task: () => {
return new Promise((resolve) => {
const { engines } = require(path.join(templateDir, 'package.json'))

// this checks all engine requirements, including Node.js and Yarn
checkNodeVersion(engines, (_error, result) => {
if (result.isSatisfied) {
hasPassedEngineCheck = true
return resolve()
}

const logStatements = Object.keys(result.versions)
.filter((name) => !result.versions[name].isSatisfied)
.map((name) => {
const { version, wanted } = result.versions[name]
return style.error(
`${name} ${wanted} required, but you have ${version}`
)
})
engineErrorLog = [...logStatements]
hasPassedEngineCheck = false
return resolve()
})
})
},
},
],
{ clearOutput: true }
).run()

// Show a success message if required engines are present
if (hasPassedEngineCheck === true) {
console.log(style.success(`✔️ Compatability checks passed.`))
}

// Show an error and prompt if failed engines
if (hasPassedEngineCheck === false) {
console.log(style.error(`✖️ Compatability checks failed.`))
console.log(`${engineErrorLog.join('\n')}`)
console.log(style.header(`\nVisit requirements documentation:`))
console.log(
style.warning(
`/docs/tutorial/chapter1/prerequisites/#nodejs-and-yarn-versions\n`
)
)
const response = await prompts({
type: 'select',
name: 'override-engine-error',
message: 'How would you like to proceed?',
choices: [
{ title: 'Override error and continue install', value: true },
{ title: 'Quit install', value: false },
],
initial: 0,
})
if (response['override-engine-error'] === false) {
process.exit(1)
}
}

// User prompts
// See https://github.com/terkelg/prompts
const questions = [
{
type: 'text',
name: 'project-name',
message: 'Project name?',
initial: 'my-redwood-app',
},
{
type: 'text',
name: 'project-dir',
message: 'Project directory?',
initial: (prev) => paramCase(prev),
},
{
type: 'select',
name: 'language-target',
message: 'Choose your preferred development language.',
choices: [
{ title: 'TypeScript', value: 'ts' },
{ title: 'TypeScript (strict mode)', value: 'ts-strict' },
{ title: 'JavaScript', value: 'js' },
],
initial: 0,
},
{
type: 'confirm',
name: 'typescript',
message: 'Use TypeScript?',
name: 'git-init',
message: 'Should we initialize a new git repository?',
initial: true,
active: 'Yes',
inactive: 'No',
Expand All @@ -137,7 +271,9 @@ import { name, version } from '../package'
const answers = await prompts(questions)

// Get the directory for installation from the args
const targetDir = String(args).replace(/,/g, '-')
// Because of the override we can just use the value from the answers which will
// either be the overide or it will be the provided prompt.
const targetDir = String(answers['project-dir']).replace(/,/g, '-')

// Throw an error if there is no target directory specified
if (!targetDir) {
Expand All @@ -159,48 +295,47 @@ import { name, version } from '../package'

const newAppDir = path.resolve(process.cwd(), targetDir)
const appDirExists = fs.existsSync(newAppDir)
const templateDir = path.resolve(__dirname, '../template')

const createProjectTasks = ({ newAppDir, overwrite }) => {
return [
{
title: 'Checking node and yarn compatibility',
skip: () => {
if (yarnInstall === false) {
return 'Warning: skipping check on request'
}
},
task: () => {
return new Promise((resolve, reject) => {
const { engines } = require(path.join(templateDir, 'package.json'))
// {
// title: 'Checking node and yarn compatibility',
// skip: () => {
// if (yarnInstall === false) {
// return 'Warning: skipping check on request'
// }
// },
// task: () => {
// return new Promise((resolve, reject) => {
// const { engines } = require(path.join(templateDir, 'package.json'))

// this checks all engine requirements, including Node.js and Yarn
checkNodeVersion(engines, (_error, result) => {
if (result.isSatisfied) {
return resolve()
}
// // this checks all engine requirements, including Node.js and Yarn
// checkNodeVersion(engines, (_error, result) => {
// if (result.isSatisfied) {
// return resolve()
// }

const logStatements = Object.keys(result.versions)
.filter((name) => !result.versions[name].isSatisfied)
.map((name) => {
const { version, wanted } = result.versions[name]
return style.error(
`${name} ${wanted} required, but you have ${version}`
)
})
logStatements.push(
style.header(`\nVisit requirements documentation:`)
)
logStatements.push(
style.warning(
`/docs/tutorial/chapter1/prerequisites/#nodejs-and-yarn-versions\n`
)
)
return reject(new Error(logStatements.join('\n')))
})
})
},
},
// const logStatements = Object.keys(result.versions)
// .filter((name) => !result.versions[name].isSatisfied)
// .map((name) => {
// const { version, wanted } = result.versions[name]
// return style.error(
// `${name} ${wanted} required, but you have ${version}`
// )
// })
// logStatements.push(
// style.header(`\nVisit requirements documentation:`)
// )
// logStatements.push(
// style.warning(
// `/docs/tutorial/chapter1/prerequisites/#nodejs-and-yarn-versions\n`
// )
// )
// return reject(new Error(logStatements.join('\n')))
// })
// })
// },
// },
{
title: `${
appDirExists ? 'Using' : 'Creating'
Expand Down Expand Up @@ -327,11 +462,11 @@ import { name, version } from '../package'
},
{
title: 'Convert TypeScript files to JavaScript',
// Enabled if user selects no to typescript prompt
// Enabled if user specified --no-ts via command line
// Enabled if user selects javascript prompt
// Enabled if user specified javascript via commandline
enabled: () =>
yarnInstall === true &&
(typescript === false || answers.typescript === false),
(languageTarget === 'js' || answers['language-target'] === 'js'),
task: () => {
return execa('yarn rw ts-to-js', {
shell: true,
Expand All @@ -349,6 +484,31 @@ import { name, version } from '../package'
})
},
},
// TODO this doesn't actually work
{
title: 'Enable TypeScript strict mode',

enabled: () =>
yarnInstall === true &&
(languageTarget === 'ts-strict' ||
answers['language-target'] === 'ts-strict'),
task: () => {
return console.log('TODO')
},
},
{
title: 'Initializing new git repo',
enabled: () => gitInit === true || answers['git-init'] === true,
task: () => {
return execa(
'git init && git add . && git commit -m "Initial commit" && git branch -M main',
{
shell: true,
cwd: newAppDir,
}
)
},
},
],
{ collapse: false, exitOnError: true }
)
Expand All @@ -360,6 +520,9 @@ import { name, version } from '../package'
// https://prettier.io/docs/en/rationale.html#semicolons
;[
'',
style.success(
`🎉🎉Successfully created ${answers['project-name']}🎉🎉`
),
style.success('Thanks for trying out Redwood!'),
'',
` ⚡️ ${style.redwood(
Expand Down