create-nx-workspace
Version:
1,271 lines (1,270 loc) • 43.1 kB
JavaScript
"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';
}