@nx/node
Version:
500 lines (499 loc) • 20.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addLintingToApplication = addLintingToApplication;
exports.applicationGenerator = applicationGenerator;
exports.applicationGeneratorInternal = applicationGeneratorInternal;
const devkit_1 = require("@nx/devkit");
const project_name_and_root_utils_1 = require("@nx/devkit/src/generators/project-name-and-root-utils");
const jest_1 = require("@nx/jest");
const js_1 = require("@nx/js");
const versions_1 = require("@nx/js/src/utils/versions");
const eslint_1 = require("@nx/eslint");
const path_1 = require("path");
const versions_2 = require("../../utils/versions");
const e2e_project_1 = require("../e2e-project/e2e-project");
const init_1 = require("../init/init");
const setup_docker_1 = require("../setup-docker/setup-docker");
const has_webpack_plugin_1 = require("../../utils/has-webpack-plugin");
const target_defaults_utils_1 = require("@nx/devkit/src/generators/target-defaults-utils");
const log_show_project_command_1 = require("@nx/devkit/src/utils/log-show-project-command");
const ts_solution_setup_1 = require("@nx/js/src/utils/typescript/ts-solution-setup");
const sort_fields_1 = require("@nx/js/src/utils/package-json/sort-fields");
function getWebpackBuildConfig(project, options) {
return {
executor: `@nx/webpack:webpack`,
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
target: 'node',
compiler: 'tsc',
outputPath: options.outputPath,
main: (0, devkit_1.joinPathFragments)(project.sourceRoot, 'main' + (options.js ? '.js' : '.ts')),
tsConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json'),
assets: [(0, devkit_1.joinPathFragments)(project.sourceRoot, 'assets')],
webpackConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.js'),
generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true,
},
configurations: {
development: {},
production: {
...(options.docker && { generateLockfile: true }),
},
},
};
}
function getEsBuildConfig(project, options) {
return {
executor: '@nx/esbuild:esbuild',
outputs: ['{options.outputPath}'],
defaultConfiguration: 'production',
options: {
platform: 'node',
outputPath: options.outputPath,
// Use CJS for Node apps for widest compatibility.
format: ['cjs'],
bundle: false,
main: (0, devkit_1.joinPathFragments)(project.sourceRoot, 'main' + (options.js ? '.js' : '.ts')),
tsConfig: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json'),
assets: [(0, devkit_1.joinPathFragments)(project.sourceRoot, 'assets')],
generatePackageJson: options.isUsingTsSolutionConfig ? undefined : true,
esbuildOptions: {
sourcemap: true,
// Generate CJS files as .js so imports can be './foo' rather than './foo.cjs'.
outExtension: { '.js': '.js' },
},
},
configurations: {
development: {},
production: {
...(options.docker && { generateLockfile: true }),
esbuildOptions: {
sourcemap: false,
// Generate CJS files as .js so imports can be './foo' rather than './foo.cjs'.
outExtension: { '.js': '.js' },
},
},
},
};
}
function getServeConfig(options) {
return {
executor: '@nx/js:node',
defaultConfiguration: 'development',
// Run build, which includes dependency on "^build" by default, so the first run
// won't error out due to missing build artifacts.
dependsOn: ['build'],
options: {
buildTarget: `${options.name}:build`,
// Even though `false` is the default, set this option so users know it
// exists if they want to always run dependencies during each rebuild.
runBuildTargetDependencies: false,
},
configurations: {
development: {
buildTarget: `${options.name}:build:development`,
},
production: {
buildTarget: `${options.name}:build:production`,
},
},
};
}
function getNestWebpackBuildConfig() {
return {
executor: 'nx:run-commands',
options: {
command: 'webpack-cli build',
args: ['node-env=production'],
},
configurations: {
development: {
args: ['node-env=development'],
},
},
};
}
function addProject(tree, options) {
const project = {
root: options.appProjectRoot,
sourceRoot: (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src'),
projectType: 'application',
targets: {},
tags: options.parsedTags,
};
if (options.bundler === 'esbuild') {
(0, target_defaults_utils_1.addBuildTargetDefaults)(tree, '@nx/esbuild:esbuild');
project.targets.build = getEsBuildConfig(project, options);
}
else if (options.bundler === 'webpack') {
if (!(0, has_webpack_plugin_1.hasWebpackPlugin)(tree)) {
(0, target_defaults_utils_1.addBuildTargetDefaults)(tree, `@nx/webpack:webpack`);
project.targets.build = getWebpackBuildConfig(project, options);
}
else if (options.isNest) {
// If we are using Nest that has the webpack plugin we need to override the
// build target so that node-env can be set to production or development so the serve target can be run in development mode
project.targets.build = getNestWebpackBuildConfig();
}
}
project.targets.serve = getServeConfig(options);
const packageJson = {
name: options.importPath,
version: '0.0.1',
private: true,
};
if (!options.useProjectJson) {
packageJson.nx = {
name: options.name !== options.importPath ? options.name : undefined,
targets: project.targets,
tags: project.tags?.length ? project.tags : undefined,
};
}
else {
(0, devkit_1.addProjectConfiguration)(tree, options.name, project, options.standaloneConfig);
}
if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
(0, devkit_1.writeJson)(tree, (0, devkit_1.joinPathFragments)(options.appProjectRoot, 'package.json'), packageJson);
}
}
function addAppFiles(tree, options) {
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, './files/common'), options.appProjectRoot, {
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: (0, devkit_1.offsetFromRoot)(options.appProjectRoot),
rootTsConfigPath: (0, js_1.getRelativePathToRootTsConfig)(tree, options.appProjectRoot),
webpackPluginOptions: (0, has_webpack_plugin_1.hasWebpackPlugin)(tree)
? {
outputPath: options.isUsingTsSolutionConfig
? 'dist'
: (0, devkit_1.joinPathFragments)((0, devkit_1.offsetFromRoot)(options.appProjectRoot), 'dist', options.rootProject ? options.name : options.appProjectRoot),
main: './src/main' + (options.js ? '.js' : '.ts'),
tsConfig: './tsconfig.app.json',
assets: ['./src/assets'],
}
: null,
});
if (options.bundler !== 'webpack') {
tree.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'webpack.config.js'));
}
if (options.framework && options.framework !== 'none') {
(0, devkit_1.generateFiles)(tree, (0, path_1.join)(__dirname, `./files/${options.framework}`), options.appProjectRoot, {
...options,
tmpl: '',
name: options.name,
root: options.appProjectRoot,
offset: (0, devkit_1.offsetFromRoot)(options.appProjectRoot),
rootTsConfigPath: (0, js_1.getRelativePathToRootTsConfig)(tree, options.appProjectRoot),
});
}
if (options.js) {
(0, devkit_1.toJS)(tree);
}
}
function addProxy(tree, options) {
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.frontendProject);
if (projectConfig.targets && projectConfig.targets.serve) {
const pathToProxyFile = `${projectConfig.root}/proxy.conf.json`;
projectConfig.targets.serve.options = {
...projectConfig.targets.serve.options,
proxyConfig: pathToProxyFile,
};
if (!tree.exists(pathToProxyFile)) {
tree.write(pathToProxyFile, JSON.stringify({
'/api': {
target: `http://localhost:${options.port}`,
secure: false,
},
}, null, 2));
}
else {
//add new entry to existing config
const proxyFileContent = tree.read(pathToProxyFile).toString();
const proxyModified = {
...JSON.parse(proxyFileContent),
[`/${options.name}-api`]: {
target: `http://localhost:${options.port}`,
secure: false,
},
};
tree.write(pathToProxyFile, JSON.stringify(proxyModified, null, 2));
}
(0, devkit_1.updateProjectConfiguration)(tree, options.frontendProject, projectConfig);
}
else {
devkit_1.logger.warn(`Skip updating proxy for frontend project "${options.frontendProject}" since "serve" target is not found in project.json. For more information, see: https://nx.dev/recipes/node/application-proxies.`);
}
}
async function addLintingToApplication(tree, options) {
const lintTask = await (0, eslint_1.lintProjectGenerator)(tree, {
linter: options.linter,
project: options.name,
tsConfigPaths: [
(0, devkit_1.joinPathFragments)(options.appProjectRoot, 'tsconfig.app.json'),
],
unitTestRunner: options.unitTestRunner,
skipFormat: true,
setParserOptionsProject: options.setParserOptionsProject,
rootProject: options.rootProject,
addPlugin: options.addPlugin,
});
return lintTask;
}
function addProjectDependencies(tree, options) {
const bundlers = {
webpack: {
'@nx/webpack': versions_2.nxVersion,
},
esbuild: {
'@nx/esbuild': versions_2.nxVersion,
esbuild: versions_1.esbuildVersion,
},
};
const frameworkDependencies = {
express: {
express: versions_2.expressVersion,
},
koa: {
koa: versions_2.koaVersion,
},
fastify: {
fastify: versions_2.fastifyVersion,
'fastify-plugin': versions_2.fastifyPluginVersion,
'@fastify/autoload': versions_2.fastifyAutoloadVersion,
'@fastify/sensible': versions_2.fastifySensibleVersion,
},
};
const frameworkDevDependencies = {
express: {
'@types/express': versions_2.expressTypingsVersion,
},
koa: {
'@types/koa': versions_2.koaTypingsVersion,
},
fastify: {},
};
return (0, devkit_1.addDependenciesToPackageJson)(tree, {
...frameworkDependencies[options.framework],
tslib: versions_2.tslibVersion,
}, {
...frameworkDevDependencies[options.framework],
...bundlers[options.bundler],
'@types/node': versions_2.typesNodeVersion,
});
}
function updateTsConfigOptions(tree, options) {
if (options.isUsingTsSolutionConfig) {
return;
}
(0, devkit_1.updateJson)(tree, `${options.appProjectRoot}/tsconfig.json`, (json) => {
if (options.rootProject) {
return {
compilerOptions: {
...js_1.tsConfigBaseOptions,
...json.compilerOptions,
esModuleInterop: true,
},
...json,
extends: undefined,
exclude: ['node_modules', 'tmp'],
};
}
else {
return {
...json,
compilerOptions: {
...json.compilerOptions,
esModuleInterop: true,
},
};
}
});
}
async function applicationGenerator(tree, schema) {
return await applicationGeneratorInternal(tree, {
addPlugin: false,
useProjectJson: true,
...schema,
});
}
async function applicationGeneratorInternal(tree, schema) {
const tasks = [];
const jsInitTask = await (0, js_1.initGenerator)(tree, {
...schema,
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
skipFormat: true,
addTsPlugin: schema.useTsSolution,
});
tasks.push(jsInitTask);
const options = await normalizeOptions(tree, schema);
if (options.framework === 'nest') {
// nx-ignore-next-line
const { applicationGenerator } = (0, devkit_1.ensurePackage)('@nx/nest', versions_2.nxVersion);
const nestTasks = await applicationGenerator(tree, {
...options,
skipFormat: true,
});
tasks.push(nestTasks);
if (options.docker) {
const dockerTask = await (0, setup_docker_1.setupDockerGenerator)(tree, {
...options,
project: options.name,
skipFormat: true,
});
tasks.push(dockerTask);
}
return (0, devkit_1.runTasksInSerial)(...[
...tasks,
() => {
(0, log_show_project_command_1.logShowProjectCommand)(options.name);
},
]);
}
const initTask = await (0, init_1.initGenerator)(tree, {
...schema,
skipFormat: true,
});
tasks.push(initTask);
const installTask = addProjectDependencies(tree, options);
tasks.push(installTask);
if (options.bundler === 'webpack') {
const { webpackInitGenerator } = (0, devkit_1.ensurePackage)('@nx/webpack', versions_2.nxVersion);
const webpackInitTask = await webpackInitGenerator(tree, {
skipPackageJson: options.skipPackageJson,
skipFormat: true,
addPlugin: options.addPlugin,
});
tasks.push(webpackInitTask);
if (!options.skipPackageJson) {
const { ensureDependencies } = await Promise.resolve().then(() => require('@nx/webpack/src/utils/ensure-dependencies'));
tasks.push(ensureDependencies(tree, {
uiFramework: options.isNest ? 'none' : 'react',
}));
}
}
addAppFiles(tree, options);
addProject(tree, options);
// If we are using the new TS solution
// We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project
if (options.isUsingTsSolutionConfig) {
await (0, ts_solution_setup_1.addProjectToTsSolutionWorkspace)(tree, options.appProjectRoot);
}
updateTsConfigOptions(tree, options);
if (options.linter === eslint_1.Linter.EsLint) {
const lintTask = await addLintingToApplication(tree, options);
tasks.push(lintTask);
}
if (options.unitTestRunner === 'jest') {
const jestTask = await (0, jest_1.configurationGenerator)(tree, {
...options,
project: options.name,
setupFile: 'none',
skipSerializers: true,
supportTsx: options.js,
testEnvironment: 'node',
compiler: options.swcJest ? 'swc' : 'tsc',
skipFormat: true,
});
tasks.push(jestTask);
// There are no tests by default, so set `--passWithNoTests` to avoid test failure on new project.
const projectConfig = (0, devkit_1.readProjectConfiguration)(tree, options.name);
projectConfig.targets ??= {};
projectConfig.targets.test = {
options: { passWithNoTests: true },
};
(0, devkit_1.updateProjectConfiguration)(tree, options.name, projectConfig);
}
else {
// No need for default spec file if unit testing is not setup.
tree.delete((0, devkit_1.joinPathFragments)(options.appProjectRoot, 'src/app/app.spec.ts'));
}
if (options.e2eTestRunner === 'jest') {
const e2eTask = await (0, e2e_project_1.e2eProjectGenerator)(tree, {
...options,
projectType: options.framework === 'none' ? 'cli' : 'server',
name: options.rootProject ? 'e2e' : `${options.name}-e2e`,
directory: options.rootProject ? 'e2e' : `${options.appProjectRoot}-e2e`,
project: options.name,
port: options.port,
isNest: options.isNest,
skipFormat: true,
});
tasks.push(e2eTask);
}
if (options.js) {
(0, devkit_1.updateTsConfigsToJs)(tree, { projectRoot: options.appProjectRoot });
}
if (options.frontendProject) {
addProxy(tree, options);
}
if (options.docker) {
const dockerTask = await (0, setup_docker_1.setupDockerGenerator)(tree, {
...options,
project: options.name,
skipFormat: true,
});
tasks.push(dockerTask);
}
if (options.isUsingTsSolutionConfig) {
(0, ts_solution_setup_1.updateTsconfigFiles)(tree, options.appProjectRoot, 'tsconfig.app.json', {
module: 'nodenext',
moduleResolution: 'nodenext',
}, options.linter === 'eslint'
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
: undefined);
}
(0, sort_fields_1.sortPackageJsonFields)(tree, options.appProjectRoot);
if (!options.skipFormat) {
await (0, devkit_1.formatFiles)(tree);
}
tasks.push(() => {
(0, log_show_project_command_1.logShowProjectCommand)(options.name);
});
return (0, devkit_1.runTasksInSerial)(...tasks);
}
async function normalizeOptions(host, options) {
await (0, project_name_and_root_utils_1.ensureRootProjectName)(options, 'application');
const { projectName, projectRoot: appProjectRoot, importPath, } = await (0, project_name_and_root_utils_1.determineProjectNameAndRootOptions)(host, {
name: options.name,
projectType: 'application',
directory: options.directory,
rootProject: options.rootProject,
});
options.rootProject = appProjectRoot === '.';
options.bundler = options.bundler ?? 'esbuild';
options.e2eTestRunner = options.e2eTestRunner ?? 'jest';
const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim())
: [];
const nxJson = (0, devkit_1.readNxJson)(host);
const addPlugin = process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false;
const isUsingTsSolutionConfig = (0, ts_solution_setup_1.isUsingTsSolutionSetup)(host);
const swcJest = options.swcJest ?? isUsingTsSolutionConfig;
const appProjectName = !isUsingTsSolutionConfig || options.name ? projectName : importPath;
const useProjectJson = options.useProjectJson ?? !isUsingTsSolutionConfig;
return {
addPlugin,
...options,
name: appProjectName,
frontendProject: options.frontendProject
? (0, devkit_1.names)(options.frontendProject).fileName
: undefined,
appProjectRoot,
importPath,
parsedTags,
linter: options.linter ?? eslint_1.Linter.EsLint,
unitTestRunner: options.unitTestRunner ?? 'jest',
rootProject: options.rootProject ?? false,
port: options.port ?? 3000,
outputPath: isUsingTsSolutionConfig
? (0, devkit_1.joinPathFragments)(appProjectRoot, 'dist')
: (0, devkit_1.joinPathFragments)('dist', options.rootProject ? appProjectName : appProjectRoot),
isUsingTsSolutionConfig,
swcJest,
useProjectJson,
};
}
exports.default = applicationGenerator;