Files
nrwl--nx/packages/node/src/generators/application/application.ts
T
2026-07-13 12:38:36 +08:00

270 lines
7.3 KiB
TypeScript

import { logShowProjectCommand } from '@nx/devkit/internal';
import {
ensurePackage,
formatFiles,
GeneratorCallback,
joinPathFragments,
readProjectConfiguration,
runTasksInSerial,
Tree,
updateJson,
updateProjectConfiguration,
updateTsConfigsToJs,
} from '@nx/devkit';
import { configurationGenerator } from '@nx/jest';
import { initGenerator as jsInitGenerator } from '@nx/js';
import {
addProjectToTsSolutionWorkspace,
getTsConfigBaseOptions,
shouldConfigureTsSolutionSetup,
updateTsconfigFiles,
sortPackageJsonFields,
} from '@nx/js/internal';
import { assertSupportedFrameworkVersion } from '../../utils/assert-supported-framework-version';
import { nxVersion } from '../../utils/versions';
import { e2eProjectGenerator } from '../e2e-project/e2e-project';
import { initGenerator } from '../init/init';
import { setupDockerGenerator } from '../setup-docker/setup-docker';
import { Schema } from './schema';
import {
addAppFiles,
addLintingToApplication,
addProject,
addProjectDependencies,
addProxy,
normalizeOptions,
NormalizedSchema,
} from './lib';
function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) {
if (options.isUsingTsSolutionConfig) {
return;
}
updateJson(tree, `${options.appProjectRoot}/tsconfig.json`, (json) => {
if (options.rootProject) {
return {
compilerOptions: {
...getTsConfigBaseOptions(tree),
...json.compilerOptions,
esModuleInterop: true,
},
...json,
extends: undefined,
exclude: ['node_modules', 'tmp'],
};
} else {
return {
...json,
compilerOptions: {
...json.compilerOptions,
esModuleInterop: true,
},
};
}
});
}
export async function applicationGenerator(tree: Tree, schema: Schema) {
return await applicationGeneratorInternal(tree, {
addPlugin: false,
useProjectJson: true,
...schema,
});
}
export async function applicationGeneratorInternal(tree: Tree, schema: Schema) {
assertSupportedFrameworkVersion(tree, schema.framework);
const tasks: GeneratorCallback[] = [];
const addTsPlugin = shouldConfigureTsSolutionSetup(
tree,
schema.addPlugin,
schema.useTsSolution
);
const jsInitTask = await jsInitGenerator(tree, {
...schema,
tsConfigName: schema.rootProject ? 'tsconfig.json' : 'tsconfig.base.json',
skipFormat: true,
addTsPlugin,
});
tasks.push(jsInitTask);
const options = await normalizeOptions(tree, schema);
if (options.framework === 'nest') {
// nx-ignore-next-line
const { applicationGenerator } = ensurePackage('@nx/nest', nxVersion);
const nestTasks = await applicationGenerator(tree, {
...options,
skipFormat: true,
});
tasks.push(nestTasks);
if (options.docker) {
const dockerTask = await setupDockerGenerator(tree, {
...options,
project: options.name,
skipFormat: true,
});
tasks.push(dockerTask);
}
return runTasksInSerial(
...[
...tasks,
() => {
logShowProjectCommand(options.name);
},
]
);
}
const initTask = await initGenerator(tree, {
...schema,
skipFormat: true,
});
tasks.push(initTask);
const { installTask, frameworkDependencies } = addProjectDependencies(
tree,
options
);
tasks.push(installTask);
if (options.bundler === 'webpack') {
const { webpackInitGenerator } = ensurePackage<
typeof import('@nx/webpack')
>('@nx/webpack', nxVersion);
const webpackInitTask = await webpackInitGenerator(tree, {
skipPackageJson: options.skipPackageJson,
skipFormat: true,
addPlugin: options.addPlugin,
});
tasks.push(webpackInitTask);
if (!options.skipPackageJson) {
// Use CommonJS `require` rather than a dynamic ESM `import`:
// `ensurePackage` makes the on-demand-installed package available via
// `Module._initPaths`, which `require()` honors but ESM resolution does
// not. Under nodenext, a dynamic `import()` is preserved as a true ESM
// dynamic import, so it can't see the temp install.
const {
ensureDependencies,
}: typeof import('@nx/webpack/internal') = require('@nx/webpack/internal');
tasks.push(
ensureDependencies(tree, {
uiFramework: options.isNest ? 'none' : 'react',
})
);
}
}
addAppFiles(tree, options);
addProject(tree, options, frameworkDependencies);
// 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 addProjectToTsSolutionWorkspace(tree, options.appProjectRoot);
}
updateTsConfigOptions(tree, options);
if (options.linter === 'eslint') {
const lintTask = await addLintingToApplication(tree, options);
tasks.push(lintTask);
}
if (options.unitTestRunner === 'jest') {
const jestTask = await 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 = readProjectConfiguration(tree, options.name);
projectConfig.targets ??= {};
projectConfig.targets.test = {
...projectConfig.targets.test,
options: {
...projectConfig.targets.test?.options,
passWithNoTests: true,
},
};
updateProjectConfiguration(tree, options.name, projectConfig);
} else {
// No need for default spec file if unit testing is not setup.
tree.delete(
joinPathFragments(options.appProjectRoot, 'src/app/app.spec.ts')
);
}
if (options.e2eTestRunner === 'jest') {
const e2eTask = await 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) {
updateTsConfigsToJs(tree, { projectRoot: options.appProjectRoot });
}
if (options.frontendProject) {
addProxy(tree, options);
}
if (options.docker) {
const dockerTask = await setupDockerGenerator(tree, {
...options,
project: options.name,
skipFormat: true,
skipDockerPlugin: options.skipDockerPlugin ?? false,
});
tasks.push(dockerTask);
}
if (options.isUsingTsSolutionConfig) {
updateTsconfigFiles(
tree,
options.appProjectRoot,
'tsconfig.app.json',
{
module: 'nodenext',
moduleResolution: 'nodenext',
},
options.linter === 'eslint'
? ['eslint.config.js', 'eslint.config.cjs', 'eslint.config.mjs']
: undefined
);
}
sortPackageJsonFields(tree, options.appProjectRoot);
if (!options.skipFormat) {
await formatFiles(tree);
}
tasks.push(() => {
logShowProjectCommand(options.name);
});
return runTasksInSerial(...tasks);
}
export default applicationGenerator;