UNPKG

create-nx-workspace

Version:

Smart Repos · Fast Builds

1,271 lines (1,270 loc) • 43.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.commandsObject = void 0; exports.validateWorkspaceName = validateWorkspaceName; const enquirer = require("enquirer"); const yargs = require("yargs"); const chalk = require("chalk"); const create_workspace_1 = require("../src/create-workspace"); const preset_1 = require("../src/utils/preset/preset"); const output_1 = require("../src/utils/output"); const nx_version_1 = require("../src/utils/nx/nx-version"); const decorator_1 = require("./decorator"); const get_third_party_preset_1 = require("../src/utils/preset/get-third-party-preset"); const prompts_1 = require("../src/internal-utils/prompts"); const yargs_options_1 = require("../src/internal-utils/yargs-options"); const ab_testing_1 = require("../src/utils/nx/ab-testing"); const error_utils_1 = require("../src/utils/error-utils"); const fs_1 = require("fs"); const is_ci_1 = require("../src/utils/ci/is-ci"); exports.commandsObject = yargs .wrap(yargs.terminalWidth()) .parserConfiguration({ 'strip-dashed': true, 'dot-notation': true, }) .command( // this is the default and only command '$0 [name] [options]', 'Create a new Nx workspace', (yargs) => (0, yargs_options_1.withOptions)(yargs .option('name', { describe: chalk.dim `Workspace name (e.g. org name).`, type: 'string', }) .option('preset', { // This describe is hard to auto-fix because of the loop in the code. // eslint-disable-next-line @nx/workspace/valid-command-object describe: chalk.dim `Customizes the initial content of your workspace. Default presets include: [${Object.values(preset_1.Preset) .map((p) => `"${p}"`) .join(', ')}]. To build your own see https://nx.dev/extending-nx/recipes/create-preset.`, type: 'string', }) .option('interactive', { describe: chalk.dim `Enable interactive mode with presets.`, type: 'boolean', default: true, }) .option('workspaceType', { describe: chalk.dim `The type of workspace to create.`, choices: ['integrated', 'package-based', 'standalone'], type: 'string', }) .option('appName', { describe: chalk.dim `The name of the app when using a monorepo with certain stacks.`, type: 'string', }) .option('style', { describe: chalk.dim `Stylesheet type to be used with certain stacks.`, type: 'string', }) .option('standaloneApi', { describe: chalk.dim `Use Standalone Components if generating an Angular app.`, type: 'boolean', default: true, }) .option('routing', { describe: chalk.dim `Add a routing setup for an Angular or React app.`, type: 'boolean', default: true, }) .option('useReactRouter', { describe: chalk.dim `Generate a Server-Side Rendered (SSR) React app using React Router.`, type: 'boolean', }) .option('bundler', { describe: chalk.dim `Bundler to be used to build the app.`, type: 'string', }) .option('workspaces', { describe: chalk.dim `Use package manager workspaces.`, type: 'boolean', default: true, }) .option('useProjectJson', { describe: chalk.dim `Use a 'project.json' file for the Nx configuration instead of a 'package.json' file. This defaults to 'true' when '--no-workspaces' is used. Otherwise, it defaults to 'false'.`, type: 'boolean', }) .option('formatter', { describe: chalk.dim `Code formatter to use.`, type: 'string', }) .option('framework', { describe: chalk.dim `Framework option to be used with certain stacks.`, type: 'string', }) .option('docker', { describe: chalk.dim `Generate a Dockerfile for the Node API.`, type: 'boolean', }) .option('nextAppDir', { describe: chalk.dim `Enable the App Router for Next.js.`, type: 'boolean', }) .option('nextSrcDir', { describe: chalk.dim `Generate a 'src/' directory for Next.js.`, type: 'boolean', }) .option('e2eTestRunner', { describe: chalk.dim `Test runner to use for end to end (E2E) tests.`, choices: ['playwright', 'cypress', 'none'], type: 'string', }) .option('unitTestRunner', { describe: chalk.dim `Test runner to use for unit tests.`, choices: ['jest', 'vitest', 'none'], type: 'string', }) .option('ssr', { describe: chalk.dim `Enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering) for the Angular application.`, type: 'boolean', }) .option('prefix', { describe: chalk.dim `Prefix to use for Angular component and directive selectors.`, type: 'string', }), yargs_options_1.withNxCloud, yargs_options_1.withUseGitHub, yargs_options_1.withAllPrompts, yargs_options_1.withPackageManager, yargs_options_1.withGitOptions), async function handler(argv) { await main(argv).catch((error) => { const { version } = require('../package.json'); output_1.output.error({ title: `Something went wrong! v${version}`, }); throw error; }); }, [normalizeArgsMiddleware]) .help('help', chalk.dim `Show help`) .updateLocale(decorator_1.yargsDecorator) .version('version', chalk.dim `Show version`, nx_version_1.nxVersion); let rawArgs; async function main(parsedArgs) { output_1.output.log({ title: `Creating your v${nx_version_1.nxVersion} workspace.`, }); const workspaceInfo = await (0, create_workspace_1.createWorkspace)(parsedArgs.preset, parsedArgs, rawArgs); await (0, ab_testing_1.recordStat)({ nxVersion: nx_version_1.nxVersion, command: 'create-nx-workspace', useCloud: parsedArgs.nxCloud !== 'skip', meta: [ ab_testing_1.messages.codeOfSelectedPromptMessage('setupCI'), ab_testing_1.messages.codeOfSelectedPromptMessage('setupNxCloud'), parsedArgs.nxCloud, rawArgs.nxCloud, workspaceInfo.pushedToVcs, ], }); if (parsedArgs.nxCloud && workspaceInfo.nxCloudInfo) { process.stdout.write(workspaceInfo.nxCloudInfo); } // if (isKnownPreset(parsedArgs.preset)) { // printSocialInformation(); // } else { // output.log({ // title: `Successfully applied preset: ${parsedArgs.preset}`, // }); // } } /** * This function is used to normalize the arguments passed to the command. * It would: * - normalize the preset. * @param argv user arguments */ async function normalizeArgsMiddleware(argv) { rawArgs = { ...argv }; output_1.output.log({ title: "Let's create a new workspace [https://nx.dev/getting-started/intro]", }); argv.workspaces ??= true; argv.useProjectJson ??= !argv.workspaces; try { argv.name = await determineFolder(argv); if (!argv.preset || (0, preset_1.isKnownPreset)(argv.preset)) { argv.stack = await determineStack(argv); const presetOptions = await determinePresetOptions(argv); Object.assign(argv, presetOptions); } else { try { (0, get_third_party_preset_1.getPackageNameFromThirdPartyPreset)(argv.preset); } catch (e) { if (e instanceof Error) { output_1.output.error({ title: `Could not find preset "${argv.preset}"`, bodyLines: (0, error_utils_1.mapErrorToBodyLines)(e), }); } else { console.error(e); } process.exit(1); } } const packageManager = await (0, prompts_1.determinePackageManager)(argv); const defaultBase = await (0, prompts_1.determineDefaultBase)(argv); const nxCloud = argv.skipGit === true ? 'skip' : await (0, prompts_1.determineNxCloud)(argv); const useGitHub = nxCloud === 'skip' ? undefined : nxCloud === 'github' || (await (0, prompts_1.determineIfGitHubWillBeUsed)(argv)); Object.assign(argv, { nxCloud, useGitHub, packageManager, defaultBase, }); } catch (e) { console.error(e); process.exit(1); } } function invariant(predicate, message) { if (!predicate) { output_1.output.error(message); process.exit(1); } } function validateWorkspaceName(name) { const pattern = /^[a-zA-Z]/; if (!pattern.test(name)) { output_1.output.error({ title: 'Invalid workspace name', bodyLines: [ `The workspace name "${name}" is invalid.`, `Workspace names must start with a letter.`, `Examples of valid names: myapp, MyApp, my-app, my_app`, ], }); process.exit(1); } } async function determineFolder(parsedArgs) { const folderName = parsedArgs._[0] ? parsedArgs._[0].toString() : parsedArgs.name; if (folderName) { validateWorkspaceName(folderName); return folderName; } const reply = await enquirer.prompt([ { name: 'folderName', message: `Where would you like to create your workspace?`, initial: 'org', type: 'input', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), }, ]); invariant(reply.folderName, { title: 'Invalid folder name', bodyLines: [`Folder name cannot be empty`], }); validateWorkspaceName(reply.folderName); invariant(!(0, fs_1.existsSync)(reply.folderName), { title: 'That folder is already taken', }); return reply.folderName; } async function determineStack(parsedArgs) { if (parsedArgs.preset) { switch (parsedArgs.preset) { case preset_1.Preset.Angular: case preset_1.Preset.AngularStandalone: case preset_1.Preset.AngularMonorepo: return 'angular'; case preset_1.Preset.React: case preset_1.Preset.ReactStandalone: case preset_1.Preset.ReactMonorepo: case preset_1.Preset.NextJs: case preset_1.Preset.NextJsStandalone: case preset_1.Preset.ReactNative: case preset_1.Preset.Expo: return 'react'; case preset_1.Preset.Vue: case preset_1.Preset.VueStandalone: case preset_1.Preset.VueMonorepo: case preset_1.Preset.Nuxt: case preset_1.Preset.NuxtStandalone: return 'vue'; case preset_1.Preset.Nest: case preset_1.Preset.NodeStandalone: case preset_1.Preset.NodeMonorepo: case preset_1.Preset.Express: return 'node'; case preset_1.Preset.Apps: case preset_1.Preset.NPM: case preset_1.Preset.TS: case preset_1.Preset.TsStandalone: return 'none'; case preset_1.Preset.WebComponents: default: return 'unknown'; } } const { stack } = await enquirer.prompt([ { name: 'stack', message: `Which stack do you want to use?`, type: 'autocomplete', choices: [ { name: `none`, message: process.env.NX_ADD_PLUGINS !== 'false' && parsedArgs.workspaces ? `None: Configures a TypeScript/JavaScript monorepo.` : `None: Configures a TypeScript/JavaScript project with minimal structure.`, }, { name: `react`, message: `React: Configures a React application with your framework of choice.`, }, { name: `vue`, message: `Vue: Configures a Vue application with your framework of choice.`, }, { name: `angular`, message: `Angular: Configures a Angular application with modern tooling.`, }, { name: `node`, message: `Node: Configures a Node API application with your framework of choice.`, }, ], }, ]); return stack; } async function determinePresetOptions(parsedArgs) { switch (parsedArgs.stack) { case 'none': return determineNoneOptions(parsedArgs); case 'react': return determineReactOptions(parsedArgs); case 'angular': return determineAngularOptions(parsedArgs); case 'vue': return determineVueOptions(parsedArgs); case 'node': return determineNodeOptions(parsedArgs); default: return parsedArgs; } } async function determineFormatterOptions(args, opts) { if (args.formatter) return args.formatter; const reply = await enquirer.prompt([ { name: 'prettier', message: `Would you like to use Prettier for code formatting?`, type: 'autocomplete', choices: [ { name: 'Yes', }, { name: 'No', }, ], initial: opts?.preferPrettier ? 0 : 1, skip: !args.interactive || (0, is_ci_1.isCI)(), }, ]); return reply.prettier === 'Yes' ? 'prettier' : 'none'; } async function determineLinterOptions(args, opts) { const reply = await enquirer.prompt([ { name: 'eslint', message: `Would you like to use ESLint?`, type: 'autocomplete', choices: [ { name: 'Yes', }, { name: 'No', }, ], initial: opts?.preferEslint ? 0 : 1, skip: !args.interactive || (0, is_ci_1.isCI)(), }, ]); return reply.eslint === 'Yes' ? 'eslint' : 'none'; } async function determineNoneOptions(parsedArgs) { if ((!parsedArgs.preset || parsedArgs.preset === preset_1.Preset.TS) && process.env.NX_ADD_PLUGINS !== 'false' && parsedArgs.workspaces) { return { preset: preset_1.Preset.TS, formatter: await determineFormatterOptions(parsedArgs), }; } else { let preset; let workspaceType = undefined; let appName = undefined; let js; if (parsedArgs.preset) { preset = parsedArgs.preset; } else { workspaceType = await determinePackageBasedOrIntegratedOrStandalone(); if (workspaceType === 'standalone') { preset = preset_1.Preset.TsStandalone; } else if (workspaceType === 'integrated') { preset = preset_1.Preset.Apps; } else { preset = preset_1.Preset.NPM; } } if (preset === preset_1.Preset.TS) { return { preset, formatter: 'prettier' }; } if (parsedArgs.js !== undefined) { js = parsedArgs.js; } else if (preset === preset_1.Preset.TsStandalone) { // Only standalone TS preset generates a default package, so we need to provide --js and --appName options. appName = parsedArgs.name; const reply = await enquirer.prompt([ { name: 'ts', message: `Would you like to use TypeScript with this project?`, type: 'autocomplete', choices: [ { name: 'Yes', }, { name: 'No', }, ], initial: 0, skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), }, ]); js = reply.ts === 'No'; } return { preset, js, appName }; } } async function determineReactOptions(parsedArgs) { let preset; let style = undefined; let appName; let bundler = undefined; let unitTestRunner = undefined; let e2eTestRunner = undefined; let useReactRouter = false; let routing = true; let nextAppDir = false; let nextSrcDir = false; let linter; let formatter; const workspaces = parsedArgs.workspaces; if (parsedArgs.preset && parsedArgs.preset !== preset_1.Preset.React) { preset = parsedArgs.preset; if (preset === preset_1.Preset.ReactStandalone || preset === preset_1.Preset.NextJsStandalone) { appName = parsedArgs.appName ?? parsedArgs.name; } else { appName = await determineAppName(parsedArgs); } } else { const framework = await determineReactFramework(parsedArgs); const isStandalone = workspaces || framework === 'react-native' || framework === 'expo' ? false : (await determineStandaloneOrMonorepo()) === 'standalone'; if (isStandalone) { appName = parsedArgs.name; } else { appName = await determineAppName(parsedArgs); } if (framework === 'nextjs') { if (isStandalone) { preset = preset_1.Preset.NextJsStandalone; } else { preset = preset_1.Preset.NextJs; } } else if (framework === 'react-native') { preset = preset_1.Preset.ReactNative; } else if (framework === 'expo') { preset = preset_1.Preset.Expo; } else { useReactRouter = await determineReactRouter(parsedArgs); if (isStandalone) { preset = preset_1.Preset.ReactStandalone; } else { preset = preset_1.Preset.ReactMonorepo; } } } if (preset === preset_1.Preset.ReactStandalone || preset === preset_1.Preset.ReactMonorepo) { bundler = useReactRouter ? 'vite' : await determineReactBundler(parsedArgs); unitTestRunner = await determineUnitTestRunner(parsedArgs, { preferVitest: bundler === 'vite', }); e2eTestRunner = await determineE2eTestRunner(parsedArgs); } else if (preset === preset_1.Preset.NextJs || preset === preset_1.Preset.NextJsStandalone) { nextAppDir = await determineNextAppDir(parsedArgs); nextSrcDir = await determineNextSrcDir(parsedArgs); unitTestRunner = await determineUnitTestRunner(parsedArgs, { exclude: 'vitest', }); e2eTestRunner = await determineE2eTestRunner(parsedArgs); } else if (preset === preset_1.Preset.ReactNative || preset === preset_1.Preset.Expo) { unitTestRunner = await determineUnitTestRunner(parsedArgs, { exclude: 'vitest', }); e2eTestRunner = await determineE2eTestRunner(parsedArgs); } if (parsedArgs.style) { style = parsedArgs.style; } else if (preset === preset_1.Preset.ReactStandalone || preset === preset_1.Preset.ReactMonorepo || preset === preset_1.Preset.NextJs || preset === preset_1.Preset.NextJsStandalone) { const reply = await enquirer.prompt([ { name: 'style', message: `Default stylesheet format`, initial: 0, type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'css', message: 'CSS', }, { name: 'scss', message: 'SASS(.scss) [ https://sass-lang.com ]', }, { name: 'less', message: 'LESS [ https://lesscss.org ]', }, { name: 'tailwind', message: 'tailwind [ https://tailwindcss.com ]', }, { name: 'styled-components', message: 'styled-components [ https://styled-components.com ]', }, { name: '@emotion/styled', message: 'emotion [ https://emotion.sh ]', }, { name: 'styled-jsx', message: 'styled-jsx [ https://www.npmjs.com/package/styled-jsx ]', }, ], }, ]); style = reply.style; } if (workspaces) { linter = await determineLinterOptions(parsedArgs, { preferEslint: true }); formatter = await determineFormatterOptions(parsedArgs, { preferPrettier: true, }); } else { linter = 'eslint'; formatter = 'prettier'; } return { preset, style, appName, bundler, nextAppDir, nextSrcDir, unitTestRunner, e2eTestRunner, useReactRouter, routing, linter, formatter, workspaces, }; } async function determineVueOptions(parsedArgs) { let preset; let style = undefined; let appName; let unitTestRunner = undefined; let e2eTestRunner = undefined; let linter; let formatter; const workspaces = parsedArgs.workspaces; if (parsedArgs.preset && parsedArgs.preset !== preset_1.Preset.Vue) { preset = parsedArgs.preset; if (preset === preset_1.Preset.VueStandalone || preset === preset_1.Preset.NuxtStandalone) { appName = parsedArgs.appName ?? parsedArgs.name; } else { appName = await determineAppName(parsedArgs); } } else { const framework = await determineVueFramework(parsedArgs); const workspaceType = workspaces ? 'monorepo' : await determineStandaloneOrMonorepo(); if (workspaceType === 'standalone') { appName = parsedArgs.appName ?? parsedArgs.name; } else { appName = await determineAppName(parsedArgs); } if (framework === 'nuxt') { if (workspaceType === 'standalone') { preset = preset_1.Preset.NuxtStandalone; } else { preset = preset_1.Preset.Nuxt; } } else { if (workspaceType === 'standalone') { preset = preset_1.Preset.VueStandalone; } else { preset = preset_1.Preset.VueMonorepo; } } } unitTestRunner = await determineUnitTestRunner(parsedArgs, { exclude: 'jest', }); e2eTestRunner = await determineE2eTestRunner(parsedArgs); if (parsedArgs.style) { style = parsedArgs.style; } else { const reply = await enquirer.prompt([ { name: 'style', message: `Default stylesheet format`, initial: 0, type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'css', message: 'CSS', }, { name: 'scss', message: 'SASS(.scss) [ https://sass-lang.com ]', }, { name: 'less', message: 'LESS [ https://lesscss.org ]', }, { name: 'none', message: 'None', }, ], }, ]); style = reply.style; } if (workspaces) { linter = await determineLinterOptions(parsedArgs, { preferEslint: true }); formatter = await determineFormatterOptions(parsedArgs, { preferPrettier: true, }); } else { linter = 'eslint'; formatter = 'prettier'; } return { preset, style, appName, unitTestRunner, e2eTestRunner, linter, formatter, workspaces, }; } async function determineAngularOptions(parsedArgs) { let preset; let style; let appName; let unitTestRunner = undefined; let e2eTestRunner = undefined; let bundler = undefined; let ssr = undefined; const standaloneApi = parsedArgs.standaloneApi; const routing = parsedArgs.routing; const prefix = parsedArgs.prefix; if (prefix) { // https://github.com/angular/angular-cli/blob/main/packages/schematics/angular/utility/validation.ts#L11-L14 const htmlSelectorRegex = /^[a-zA-Z][.0-9a-zA-Z]*((:?-[0-9]+)*|(:?-[a-zA-Z][.0-9a-zA-Z]*(:?-[0-9]+)*)*)$/; // validate whether component/directive selectors will be valid with the provided prefix if (!htmlSelectorRegex.test(`${prefix}-placeholder`)) { output_1.output.error({ title: `Failed to create a workspace.`, bodyLines: [ `The provided "${prefix}" prefix is invalid. It must be a valid HTML selector.`, ], }); process.exit(1); } } if (parsedArgs.preset && parsedArgs.preset !== preset_1.Preset.Angular) { preset = parsedArgs.preset; if (preset === preset_1.Preset.AngularStandalone) { appName = parsedArgs.name; } else { appName = await determineAppName(parsedArgs); } } else { const workspaceType = await determineStandaloneOrMonorepo(); if (workspaceType === 'standalone') { preset = preset_1.Preset.AngularStandalone; appName = parsedArgs.name; } else { preset = preset_1.Preset.AngularMonorepo; appName = await determineAppName(parsedArgs); } } if (parsedArgs.bundler) { bundler = parsedArgs.bundler; } else { const reply = await enquirer.prompt([ { name: 'bundler', message: `Which bundler would you like to use?`, type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'esbuild', message: 'esbuild [ https://esbuild.github.io/ ]', }, { name: 'rspack', message: 'Rspack [ https://rspack.dev/ ]', }, { name: 'webpack', message: 'Webpack [ https://webpack.js.org/ ]', }, ], initial: 0, }, ]); bundler = reply.bundler; } if (parsedArgs.style) { style = parsedArgs.style; } else { const reply = await enquirer.prompt([ { name: 'style', message: `Default stylesheet format`, initial: 0, type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'css', message: 'CSS', }, { name: 'scss', message: 'SASS(.scss) [ https://sass-lang.com ]', }, { name: 'less', message: 'LESS [ https://lesscss.org ]', }, ], }, ]); style = reply.style; } if (parsedArgs.ssr !== undefined) { ssr = parsedArgs.ssr; } else { const reply = await enquirer.prompt([ { name: 'ssr', message: `Do you want to enable Server-Side Rendering (SSR)${bundler !== 'rspack' ? ' and Static Site Generation (SSG/Prerendering)?' : '?'}`, type: 'autocomplete', choices: [{ name: 'Yes' }, { name: 'No' }], initial: 1, skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), }, ]); ssr = reply.ssr === 'Yes'; } unitTestRunner = await determineUnitTestRunner(parsedArgs); e2eTestRunner = await determineE2eTestRunner(parsedArgs); return { preset, style, appName, standaloneApi, routing, unitTestRunner, e2eTestRunner, bundler, ssr, prefix, }; } async function determineNodeOptions(parsedArgs) { let preset; let appName; let framework; let docker; let linter; let formatter; let unitTestRunner = undefined; const workspaces = parsedArgs.workspaces; if (parsedArgs.preset) { preset = parsedArgs.preset; if (preset === preset_1.Preset.Nest || preset === preset_1.Preset.Express || preset === preset_1.Preset.NodeMonorepo) { appName = await determineAppName(parsedArgs); } else { appName = parsedArgs.name; } if (preset === preset_1.Preset.NodeStandalone || preset === preset_1.Preset.NodeMonorepo) { framework = await determineNodeFramework(parsedArgs); } else { framework = 'none'; } } else { framework = await determineNodeFramework(parsedArgs); const workspaceType = workspaces ? 'monorepo' : await determineStandaloneOrMonorepo(); if (workspaceType === 'standalone') { preset = preset_1.Preset.NodeStandalone; appName = parsedArgs.name; } else { preset = preset_1.Preset.NodeMonorepo; appName = await determineAppName(parsedArgs); } } if (parsedArgs.docker !== undefined) { docker = parsedArgs.docker; } else { const reply = await enquirer.prompt([ { name: 'docker', message: 'Would you like to generate a Dockerfile? [https://docs.docker.com/]', type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'Yes', hint: 'I want to generate a Dockerfile', }, { name: 'No', }, ], initial: 1, }, ]); docker = reply.docker === 'Yes'; } unitTestRunner = await determineUnitTestRunner(parsedArgs, { exclude: 'vitest', }); if (workspaces) { linter = await determineLinterOptions(parsedArgs, { preferEslint: true }); formatter = await determineFormatterOptions(parsedArgs, { preferPrettier: true, }); } else { linter = 'eslint'; formatter = 'prettier'; } return { preset, appName, framework, docker, linter, formatter, workspaces, unitTestRunner, }; } async function determinePackageBasedOrIntegratedOrStandalone() { const { workspaceType } = await enquirer.prompt([ { type: 'autocomplete', name: 'workspaceType', message: `Package-based monorepo, integrated monorepo, or standalone project?`, initial: 0, choices: [ { name: 'package-based', message: 'Package-based Monorepo: Nx makes it fast, but lets you run things your way.', }, { name: 'integrated', message: 'Integrated Monorepo: Nx creates a monorepo that contains multiple projects.', }, { name: 'standalone', message: 'Standalone: Nx creates a single project and makes it fast.', }, ], }, ]); invariant(workspaceType, { title: 'Invalid workspace type', bodyLines: [ `It must be one of the following: standalone, integrated. Got ${workspaceType}`, ], }); return workspaceType; } async function determineStandaloneOrMonorepo() { const { workspaceType } = await enquirer.prompt([ { type: 'autocomplete', name: 'workspaceType', message: `Integrated monorepo, or standalone project?`, initial: 1, choices: [ { name: 'integrated', message: 'Integrated Monorepo: Nx creates a monorepo that contains multiple projects.', }, { name: 'standalone', message: 'Standalone: Nx creates a single project and makes it fast.', }, ], }, ]); invariant(workspaceType, { title: 'Invalid workspace type', bodyLines: [ `It must be one of the following: standalone, integrated. Got ${workspaceType}`, ], }); return workspaceType; } async function determineAppName(parsedArgs) { if (parsedArgs.appName) return parsedArgs.appName; const { appName } = await enquirer.prompt([ { name: 'appName', message: `Application name`, type: 'input', initial: parsedArgs.name, skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), }, ]); invariant(appName, { title: 'Invalid name', bodyLines: [`Name cannot be empty`], }); return appName; } async function determineReactFramework(parsedArgs) { const reply = await enquirer.prompt([ { name: 'framework', message: 'What framework would you like to use?', type: 'autocomplete', choices: [ { name: 'none', message: 'None', hint: ' I only want react, react-dom or react-router', }, { name: 'nextjs', message: 'Next.js [ https://nextjs.org/ ]', }, { name: 'expo', message: 'Expo [ https://expo.io/ ]', }, { name: 'react-native', message: 'React Native [ https://reactnative.dev/ ]', }, ], initial: 0, }, ]); return reply.framework; } async function determineReactBundler(parsedArgs) { if (parsedArgs.bundler) return parsedArgs.bundler; const reply = await enquirer.prompt([ { name: 'bundler', message: `Which bundler would you like to use?`, type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'vite', message: 'Vite [ https://vitejs.dev/ ]', }, { name: 'webpack', message: 'Webpack [ https://webpack.js.org/ ]', }, { name: 'rspack', message: 'Rspack [ https://www.rspack.dev/ ]', }, ], initial: 0, }, ]); return reply.bundler; } async function determineNextAppDir(parsedArgs) { if (parsedArgs.nextAppDir !== undefined) return parsedArgs.nextAppDir; const reply = await enquirer.prompt([ { name: 'nextAppDir', message: 'Would you like to use the App Router (recommended)?', type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'Yes', }, { name: 'No', }, ], initial: 0, }, ]); return reply.nextAppDir === 'Yes'; } async function determineNextSrcDir(parsedArgs) { if (parsedArgs.nextSrcDir !== undefined) return parsedArgs.nextSrcDir; const reply = await enquirer.prompt([ { name: 'nextSrcDir', message: 'Would you like to use the src/ directory?', type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'Yes', }, { name: 'No', }, ], initial: 0, }, ]); return reply.nextSrcDir === 'Yes'; } async function determineVueFramework(parsedArgs) { if (!!parsedArgs.framework) return parsedArgs.framework; const reply = await enquirer.prompt([ { name: 'framework', message: 'What framework would you like to use?', type: 'autocomplete', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'none', message: 'None', hint: ' I only want Vue', }, { name: 'nuxt', message: 'Nuxt [ https://nuxt.com/ ]', }, ], initial: 0, }, ]); return reply.framework; } async function determineNodeFramework(parsedArgs) { if (!!parsedArgs.framework) return parsedArgs.framework; const reply = await enquirer.prompt([ { message: 'What framework should be used?', type: 'autocomplete', name: 'framework', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'none', message: 'None', }, { name: 'express', message: 'Express [ https://expressjs.com/ ]', }, { name: 'fastify', message: 'Fastify [ https://www.fastify.dev/ ]', }, { name: 'koa', message: 'Koa [ https://koajs.com/ ]', }, { name: 'nest', message: 'NestJs [ https://nestjs.com/ ]', }, ], initial: 0, }, ]); return reply.framework; } async function determineUnitTestRunner(parsedArgs, options) { if (parsedArgs.unitTestRunner) { return parsedArgs.unitTestRunner; } else if (!parsedArgs.workspaces) { return undefined; } const reply = await enquirer.prompt([ { message: 'Which unit test runner would you like to use?', type: 'autocomplete', name: 'unitTestRunner', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'none', message: 'None', }, { name: 'jest', message: 'Jest [ https://jestjs.io/ ]', }, { name: 'vitest', message: 'Vitest [ https://vitest.dev/ ]', }, ] .filter((t) => !options?.exclude || options.exclude !== t.name) .sort((a, b) => { if (a.name === 'none') return 1; if (b.name === 'none') return -1; if (options?.preferVitest && a.name === 'vitest') return -1; if (options?.preferVitest && b.name === 'vitest') return 1; return 0; }), initial: 0, // This should be either vite or jest }, ]); return reply.unitTestRunner; } async function determineE2eTestRunner(parsedArgs) { if (parsedArgs.e2eTestRunner) return parsedArgs.e2eTestRunner; const reply = await enquirer.prompt([ { message: 'Test runner to use for end to end (E2E) tests', type: 'autocomplete', name: 'e2eTestRunner', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'playwright', message: 'Playwright [ https://playwright.dev/ ]', }, { name: 'cypress', message: 'Cypress [ https://www.cypress.io/ ]', }, { name: 'none', message: 'None', }, ], initial: 0, }, ]); return reply.e2eTestRunner; } async function determineReactRouter(parsedArgs) { if (parsedArgs.routing !== undefined && parsedArgs.routing === false) return false; if (parsedArgs.useReactRouter !== undefined) return parsedArgs.useReactRouter; const reply = await enquirer.prompt([ { message: 'Would you like to use React Router for server-side rendering [https://reactrouter.com/]?', type: 'autocomplete', name: 'response', skip: !parsedArgs.interactive || (0, is_ci_1.isCI)(), choices: [ { name: 'Yes', hint: 'I want to use React Router. (Vite will be selected as the bundler)', }, { name: 'No', }, ], initial: 0, }, ]); return reply.response === 'Yes'; }