434 lines
11 KiB
TypeScript
434 lines
11 KiB
TypeScript
import {
|
|
addDependenciesToPackageJson,
|
|
addProjectConfiguration,
|
|
convertNxGenerator,
|
|
extractLayoutDirectory,
|
|
formatFiles,
|
|
generateFiles,
|
|
GeneratorCallback,
|
|
getWorkspaceLayout,
|
|
joinPathFragments,
|
|
logger,
|
|
names,
|
|
offsetFromRoot,
|
|
ProjectConfiguration,
|
|
readProjectConfiguration,
|
|
TargetConfiguration,
|
|
toJS,
|
|
Tree,
|
|
updateJson,
|
|
updateProjectConfiguration,
|
|
updateTsConfigsToJs,
|
|
} from '@nrwl/devkit';
|
|
|
|
import { join } from 'path';
|
|
|
|
import { Linter, lintProjectGenerator } from '@nrwl/linter';
|
|
import { jestProjectGenerator } from '@nrwl/jest';
|
|
import { runTasksInSerial } from '@nrwl/workspace/src/utilities/run-tasks-in-serial';
|
|
|
|
import { Schema } from './schema';
|
|
import { initGenerator } from '../init/init';
|
|
import { getRelativePathToRootTsConfig } from '@nrwl/workspace/src/utilities/typescript';
|
|
import {
|
|
connectTypingsVersion,
|
|
connectVersion,
|
|
esbuildVersion,
|
|
expressTypingsVersion,
|
|
expressVersion,
|
|
fastifyVersion,
|
|
koaTypingsVersion,
|
|
koaVersion,
|
|
nxVersion,
|
|
} from '../../utils/versions';
|
|
|
|
import * as shared from '@nrwl/workspace/src/utils/create-ts-config';
|
|
import { e2eProjectGenerator } from '../e2e-project/e2e-project';
|
|
import { setupDockerGenerator } from '../setup-docker/setup-docker';
|
|
|
|
export interface NormalizedSchema extends Schema {
|
|
appProjectRoot: string;
|
|
parsedTags: string[];
|
|
}
|
|
|
|
function getWebpackBuildConfig(
|
|
project: ProjectConfiguration,
|
|
options: NormalizedSchema
|
|
): TargetConfiguration {
|
|
return {
|
|
executor: `@nrwl/webpack:webpack`,
|
|
outputs: ['{options.outputPath}'],
|
|
options: {
|
|
target: 'node',
|
|
compiler: 'tsc',
|
|
outputPath: joinPathFragments('dist', options.appProjectRoot),
|
|
main: joinPathFragments(
|
|
project.sourceRoot,
|
|
'main' + (options.js ? '.js' : '.ts')
|
|
),
|
|
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
|
assets: [joinPathFragments(project.sourceRoot, 'assets')],
|
|
webpackConfig: joinPathFragments(
|
|
options.appProjectRoot,
|
|
'webpack.config.js'
|
|
),
|
|
},
|
|
configurations: {
|
|
production: {
|
|
optimization: true,
|
|
extractLicenses: true,
|
|
inspect: false,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function getEsBuildConfig(
|
|
project: ProjectConfiguration,
|
|
options: NormalizedSchema
|
|
): TargetConfiguration {
|
|
return {
|
|
executor: '@nrwl/esbuild:esbuild',
|
|
outputs: ['{options.outputPath}'],
|
|
options: {
|
|
outputPath: joinPathFragments('dist', options.appProjectRoot),
|
|
format: ['cjs'],
|
|
main: joinPathFragments(
|
|
project.sourceRoot,
|
|
'main' + (options.js ? '.js' : '.ts')
|
|
),
|
|
tsConfig: joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
|
assets: [joinPathFragments(project.sourceRoot, 'assets')],
|
|
},
|
|
};
|
|
}
|
|
|
|
function getServeConfig(options: NormalizedSchema): TargetConfiguration {
|
|
return {
|
|
executor: '@nrwl/js:node',
|
|
options: {
|
|
buildTarget: `${options.name}:build`,
|
|
},
|
|
configurations: {
|
|
production: {
|
|
buildTarget: `${options.name}:build:production`,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
function addProject(tree: Tree, options: NormalizedSchema) {
|
|
const project: ProjectConfiguration = {
|
|
root: options.appProjectRoot,
|
|
sourceRoot: joinPathFragments(options.appProjectRoot, 'src'),
|
|
projectType: 'application',
|
|
targets: {},
|
|
tags: options.parsedTags,
|
|
};
|
|
|
|
project.targets.build =
|
|
options.bundler === 'esbuild'
|
|
? getEsBuildConfig(project, options)
|
|
: getWebpackBuildConfig(project, options);
|
|
project.targets.serve = getServeConfig(options);
|
|
|
|
addProjectConfiguration(
|
|
tree,
|
|
options.name,
|
|
project,
|
|
options.standaloneConfig
|
|
);
|
|
}
|
|
|
|
function addAppFiles(tree: Tree, options: NormalizedSchema) {
|
|
generateFiles(
|
|
tree,
|
|
join(__dirname, './files/common'),
|
|
options.appProjectRoot,
|
|
{
|
|
...options,
|
|
tmpl: '',
|
|
name: options.name,
|
|
root: options.appProjectRoot,
|
|
offset: offsetFromRoot(options.appProjectRoot),
|
|
rootTsConfigPath: getRelativePathToRootTsConfig(
|
|
tree,
|
|
options.appProjectRoot
|
|
),
|
|
}
|
|
);
|
|
|
|
if (options.bundler !== 'webpack') {
|
|
tree.delete(joinPathFragments(options.appProjectRoot, 'webpack.config.js'));
|
|
}
|
|
|
|
if (options.framework && options.framework !== 'none') {
|
|
generateFiles(
|
|
tree,
|
|
join(__dirname, `./files/${options.framework}`),
|
|
options.appProjectRoot,
|
|
{
|
|
...options,
|
|
tmpl: '',
|
|
name: options.name,
|
|
root: options.appProjectRoot,
|
|
offset: offsetFromRoot(options.appProjectRoot),
|
|
rootTsConfigPath: getRelativePathToRootTsConfig(
|
|
tree,
|
|
options.appProjectRoot
|
|
),
|
|
}
|
|
);
|
|
}
|
|
if (options.js) {
|
|
toJS(tree);
|
|
}
|
|
if (options.pascalCaseFiles) {
|
|
logger.warn('NOTE: --pascalCaseFiles is a noop');
|
|
}
|
|
}
|
|
|
|
function addProxy(tree: Tree, options: NormalizedSchema) {
|
|
const projectConfig = 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:3333',
|
|
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:3333',
|
|
secure: false,
|
|
},
|
|
};
|
|
|
|
tree.write(pathToProxyFile, JSON.stringify(proxyModified, null, 2));
|
|
}
|
|
|
|
updateProjectConfiguration(tree, options.frontendProject, projectConfig);
|
|
}
|
|
}
|
|
|
|
export async function addLintingToApplication(
|
|
tree: Tree,
|
|
options: NormalizedSchema
|
|
): Promise<GeneratorCallback> {
|
|
const lintTask = await lintProjectGenerator(tree, {
|
|
linter: options.linter,
|
|
project: options.name,
|
|
tsConfigPaths: [
|
|
joinPathFragments(options.appProjectRoot, 'tsconfig.app.json'),
|
|
],
|
|
eslintFilePatterns: [
|
|
`${options.appProjectRoot}/**/*.${options.js ? 'js' : 'ts'}`,
|
|
],
|
|
unitTestRunner: options.unitTestRunner,
|
|
skipFormat: true,
|
|
setParserOptionsProject: options.setParserOptionsProject,
|
|
});
|
|
|
|
return lintTask;
|
|
}
|
|
|
|
function addProjectDependencies(
|
|
tree: Tree,
|
|
options: NormalizedSchema
|
|
): GeneratorCallback {
|
|
const bundlers = {
|
|
webpack: {
|
|
'@nrwl/webpack': nxVersion,
|
|
},
|
|
esbuild: {
|
|
'@nrwl/esbuild': nxVersion,
|
|
esbuild: esbuildVersion,
|
|
},
|
|
};
|
|
|
|
const frameworkDependencies = {
|
|
express: {
|
|
express: expressVersion,
|
|
'@types/express': expressTypingsVersion,
|
|
},
|
|
koa: {
|
|
koa: koaVersion,
|
|
'@types/koa': koaTypingsVersion,
|
|
},
|
|
fastify: {
|
|
fastify: fastifyVersion,
|
|
},
|
|
connect: {
|
|
connect: connectVersion,
|
|
'@types/connect': connectTypingsVersion,
|
|
},
|
|
};
|
|
return addDependenciesToPackageJson(
|
|
tree,
|
|
{},
|
|
{
|
|
...frameworkDependencies[options.framework],
|
|
...bundlers[options.bundler],
|
|
}
|
|
);
|
|
}
|
|
|
|
function updateTsConfigOptions(tree: Tree, options: NormalizedSchema) {
|
|
updateJson(tree, `${options.appProjectRoot}/tsconfig.json`, (json) => {
|
|
if (options.rootProject) {
|
|
return {
|
|
compilerOptions: {
|
|
...shared.tsConfigBaseOptions,
|
|
...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) {
|
|
const options = normalizeOptions(tree, schema);
|
|
const tasks: GeneratorCallback[] = [];
|
|
|
|
const initTask = await initGenerator(tree, {
|
|
...options,
|
|
skipFormat: true,
|
|
});
|
|
tasks.push(initTask);
|
|
|
|
const installTask = addProjectDependencies(tree, options);
|
|
tasks.push(installTask);
|
|
addAppFiles(tree, options);
|
|
addProject(tree, options);
|
|
|
|
updateTsConfigOptions(tree, options);
|
|
|
|
if (options.linter !== Linter.None) {
|
|
const lintTask = await addLintingToApplication(tree, {
|
|
...options,
|
|
skipFormat: true,
|
|
});
|
|
tasks.push(lintTask);
|
|
}
|
|
|
|
if (options.unitTestRunner === 'jest') {
|
|
const jestTask = await jestProjectGenerator(tree, {
|
|
...options,
|
|
project: options.name,
|
|
setupFile: 'none',
|
|
skipSerializers: true,
|
|
supportTsx: options.js,
|
|
testEnvironment: 'node',
|
|
compiler: 'tsc',
|
|
skipFormat: true,
|
|
});
|
|
tasks.push(jestTask);
|
|
}
|
|
|
|
if (options.e2eTestRunner === 'jest') {
|
|
const e2eTask = await e2eProjectGenerator(tree, {
|
|
...options,
|
|
projectType: options.framework === 'none' ? 'cli' : 'server',
|
|
name: options.rootProject ? 'e2e' : `${options.name}-e2e`,
|
|
project: options.name,
|
|
port: options.port,
|
|
});
|
|
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,
|
|
projectName: options.name,
|
|
});
|
|
|
|
tasks.push(dockerTask);
|
|
}
|
|
|
|
if (!options.skipFormat) {
|
|
await formatFiles(tree);
|
|
}
|
|
|
|
return runTasksInSerial(...tasks);
|
|
}
|
|
|
|
function normalizeOptions(host: Tree, options: Schema): NormalizedSchema {
|
|
const { layoutDirectory, projectDirectory } = extractLayoutDirectory(
|
|
options.directory
|
|
);
|
|
const appsDir = layoutDirectory ?? getWorkspaceLayout(host).appsDir;
|
|
|
|
const appDirectory = projectDirectory
|
|
? `${names(projectDirectory).fileName}/${names(options.name).fileName}`
|
|
: names(options.name).fileName;
|
|
|
|
const appProjectName = appDirectory.replace(new RegExp('/', 'g'), '-');
|
|
|
|
const appProjectRoot = options.rootProject
|
|
? '.'
|
|
: joinPathFragments(appsDir, appDirectory);
|
|
|
|
options.bundler = options.bundler ?? 'esbuild';
|
|
options.e2eTestRunner = options.e2eTestRunner ?? 'jest';
|
|
|
|
const parsedTags = options.tags
|
|
? options.tags.split(',').map((s) => s.trim())
|
|
: [];
|
|
|
|
return {
|
|
...options,
|
|
name: names(appProjectName).fileName,
|
|
frontendProject: options.frontendProject
|
|
? names(options.frontendProject).fileName
|
|
: undefined,
|
|
appProjectRoot,
|
|
parsedTags,
|
|
linter: options.linter ?? Linter.EsLint,
|
|
unitTestRunner: options.unitTestRunner ?? 'jest',
|
|
rootProject: options.rootProject ?? false,
|
|
port: options.port ?? 3000,
|
|
};
|
|
}
|
|
|
|
export default applicationGenerator;
|
|
export const applicationSchematic = convertNxGenerator(applicationGenerator);
|