Properly deprecate the `Linter` enum in favor of the `LinterType` union type and remove unneeded internal usages.
332 lines
9.2 KiB
TypeScript
332 lines
9.2 KiB
TypeScript
import {
|
|
addDependenciesToPackageJson,
|
|
addProjectConfiguration,
|
|
formatFiles,
|
|
generateFiles,
|
|
GeneratorCallback,
|
|
joinPathFragments,
|
|
names,
|
|
offsetFromRoot,
|
|
readNxJson,
|
|
readProjectConfiguration,
|
|
runTasksInSerial,
|
|
Tree,
|
|
updateJson,
|
|
writeJson,
|
|
} from '@nx/devkit';
|
|
import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
|
import { lintProjectGenerator } from '@nx/eslint';
|
|
import {
|
|
javaScriptOverride,
|
|
typeScriptOverride,
|
|
} from '@nx/eslint/src/generators/init/global-eslint-config';
|
|
import * as path from 'path';
|
|
import { axiosVersion } from '../../utils/versions';
|
|
import { Schema } from './schema';
|
|
import {
|
|
addPluginsToLintConfig,
|
|
isEslintConfigSupported,
|
|
replaceOverridesInLintConfig,
|
|
} from '@nx/eslint/src/generators/utils/eslint-file';
|
|
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
|
import { findRootJestPreset } from '@nx/jest/src/utils/config/config-file';
|
|
import {
|
|
addProjectToTsSolutionWorkspace,
|
|
isUsingTsSolutionSetup,
|
|
} from '@nx/js/src/utils/typescript/ts-solution-setup';
|
|
import { relative } from 'node:path/posix';
|
|
import { addSwcTestConfig } from '@nx/js/src/utils/swc/add-swc-config';
|
|
import type { PackageJson } from 'nx/src/utils/package-json';
|
|
|
|
export async function e2eProjectGenerator(host: Tree, options: Schema) {
|
|
return await e2eProjectGeneratorInternal(host, {
|
|
addPlugin: false,
|
|
useProjectJson: true,
|
|
...options,
|
|
});
|
|
}
|
|
|
|
export async function e2eProjectGeneratorInternal(
|
|
host: Tree,
|
|
_options: Schema
|
|
) {
|
|
const tasks: GeneratorCallback[] = [];
|
|
const options = await normalizeOptions(host, _options);
|
|
const appProject = readProjectConfiguration(host, options.project);
|
|
|
|
// TODO(@ndcunningham): This is broken.. the outputs are wrong.. and this isn't using the jest generator
|
|
const packageJson: PackageJson = {
|
|
name: options.importPath,
|
|
version: '0.0.1',
|
|
private: true,
|
|
};
|
|
|
|
if (!options.useProjectJson) {
|
|
packageJson.nx = {
|
|
name:
|
|
options.e2eProjectName !== options.importPath
|
|
? options.e2eProjectName
|
|
: undefined,
|
|
implicitDependencies: [options.project],
|
|
targets: {
|
|
e2e: {
|
|
executor: '@nx/jest:jest',
|
|
outputs: ['{projectRoot}/test-output/jest/coverage'],
|
|
options: {
|
|
jestConfig: `${options.e2eProjectRoot}/jest.config.ts`,
|
|
passWithNoTests: true,
|
|
},
|
|
dependsOn: [`${options.project}:build`, `${options.project}:serve`],
|
|
},
|
|
},
|
|
};
|
|
} else {
|
|
addProjectConfiguration(host, options.e2eProjectName, {
|
|
root: options.e2eProjectRoot,
|
|
implicitDependencies: [options.project],
|
|
projectType: 'application',
|
|
targets: {
|
|
e2e: {
|
|
executor: '@nx/jest:jest',
|
|
outputs: ['{workspaceRoot}/coverage/{e2eProjectRoot}'],
|
|
options: {
|
|
jestConfig: `${options.e2eProjectRoot}/jest.config.ts`,
|
|
passWithNoTests: true,
|
|
},
|
|
dependsOn: [`${options.project}:build`, `${options.project}:serve`],
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
if (!options.useProjectJson || options.isUsingTsSolutionConfig) {
|
|
writeJson(
|
|
host,
|
|
joinPathFragments(options.e2eProjectRoot, 'package.json'),
|
|
packageJson
|
|
);
|
|
}
|
|
|
|
// TODO(@nicholas): Find a better way to get build target
|
|
|
|
// We remove the 'test' target from the e2e project because it is not needed
|
|
// The 'e2e' target is the one that should run the tests for the e2e project
|
|
const nxJson = readNxJson(host);
|
|
const hasPlugin = nxJson.plugins?.some((p) => {
|
|
if (typeof p !== 'string' && p.plugin === '@nx/jest/plugin') {
|
|
return true;
|
|
}
|
|
});
|
|
|
|
if (hasPlugin) {
|
|
updateJson(host, 'nx.json', (json) => {
|
|
return {
|
|
...json,
|
|
plugins: json.plugins?.map((p) => {
|
|
if (typeof p !== 'string' && p.plugin === '@nx/jest/plugin') {
|
|
return {
|
|
...p,
|
|
exclude: [...(p.exclude || []), `${options.e2eProjectRoot}/**/*`],
|
|
};
|
|
}
|
|
return p;
|
|
}),
|
|
};
|
|
});
|
|
}
|
|
|
|
const jestPreset = findRootJestPreset(host) ?? 'jest.preset.js';
|
|
const tsConfigFile = options.isUsingTsSolutionConfig
|
|
? 'tsconfig.json'
|
|
: 'tsconfig.spec.json';
|
|
const rootOffset = offsetFromRoot(options.e2eProjectRoot);
|
|
const coverageDirectory = options.isUsingTsSolutionConfig
|
|
? 'test-output/jest/coverage'
|
|
: joinPathFragments(rootOffset, 'coverage', options.e2eProjectName);
|
|
const projectSimpleName = options.project.split('/').pop();
|
|
if (options.projectType === 'server') {
|
|
generateFiles(
|
|
host,
|
|
path.join(__dirname, 'files/server/common'),
|
|
options.e2eProjectRoot,
|
|
{
|
|
...options,
|
|
...names(options.rootProject ? 'server' : projectSimpleName),
|
|
tsConfigFile,
|
|
offsetFromRoot: rootOffset,
|
|
jestPreset,
|
|
coverageDirectory,
|
|
tmpl: '',
|
|
}
|
|
);
|
|
|
|
if (options.isNest) {
|
|
generateFiles(
|
|
host,
|
|
path.join(__dirname, 'files/server/nest'),
|
|
options.e2eProjectRoot,
|
|
{
|
|
...options,
|
|
...names(options.rootProject ? 'server' : projectSimpleName),
|
|
tsConfigFile,
|
|
offsetFromRoot: rootOffset,
|
|
tmpl: '',
|
|
}
|
|
);
|
|
}
|
|
} else if (options.projectType === 'cli') {
|
|
const mainFile = appProject.targets.build?.options?.outputPath;
|
|
generateFiles(
|
|
host,
|
|
path.join(__dirname, 'files/cli'),
|
|
options.e2eProjectRoot,
|
|
{
|
|
...options,
|
|
...names(options.rootProject ? 'cli' : projectSimpleName),
|
|
mainFile,
|
|
tsConfigFile,
|
|
offsetFromRoot: rootOffset,
|
|
jestPreset,
|
|
coverageDirectory,
|
|
tmpl: '',
|
|
}
|
|
);
|
|
}
|
|
|
|
if (options.isUsingTsSolutionConfig) {
|
|
addSwcTestConfig(host, options.e2eProjectRoot, 'es6');
|
|
generateFiles(
|
|
host,
|
|
path.join(__dirname, 'files/ts-solution'),
|
|
options.e2eProjectRoot,
|
|
{
|
|
...options,
|
|
relativeProjectReferencePath: relative(
|
|
options.e2eProjectRoot,
|
|
appProject.root
|
|
),
|
|
offsetFromRoot: rootOffset,
|
|
tmpl: '',
|
|
}
|
|
);
|
|
} else {
|
|
generateFiles(
|
|
host,
|
|
path.join(__dirname, 'files/non-ts-solution'),
|
|
options.e2eProjectRoot,
|
|
{
|
|
...options,
|
|
offsetFromRoot: rootOffset,
|
|
tmpl: '',
|
|
}
|
|
);
|
|
}
|
|
|
|
// axios is more than likely used in the application code, so install it as a regular dependency.
|
|
const installTask = addDependenciesToPackageJson(
|
|
host,
|
|
{ axios: axiosVersion },
|
|
{}
|
|
);
|
|
tasks.push(installTask);
|
|
|
|
if (options.linter === 'eslint') {
|
|
const linterTask = await lintProjectGenerator(host, {
|
|
project: options.e2eProjectName,
|
|
linter: 'eslint',
|
|
skipFormat: true,
|
|
tsConfigPaths: [
|
|
joinPathFragments(options.e2eProjectRoot, 'tsconfig.json'),
|
|
],
|
|
setParserOptionsProject: false,
|
|
skipPackageJson: false,
|
|
rootProject: options.rootProject,
|
|
addPlugin: options.addPlugin,
|
|
});
|
|
tasks.push(linterTask);
|
|
|
|
if (options.rootProject && isEslintConfigSupported(host)) {
|
|
addPluginsToLintConfig(host, options.e2eProjectRoot, '@nx');
|
|
replaceOverridesInLintConfig(host, options.e2eProjectRoot, [
|
|
typeScriptOverride,
|
|
javaScriptOverride,
|
|
]);
|
|
}
|
|
}
|
|
|
|
if (options.isUsingTsSolutionConfig) {
|
|
updateJson(host, 'tsconfig.json', (json) => {
|
|
json.references ??= [];
|
|
const e2eRef = `./${options.e2eProjectRoot}`;
|
|
if (!json.references.find((ref) => ref.path === e2eRef)) {
|
|
json.references.push({ path: e2eRef });
|
|
}
|
|
return json;
|
|
});
|
|
}
|
|
|
|
// 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(host, options.e2eProjectRoot);
|
|
}
|
|
|
|
if (!options.skipFormat) {
|
|
await formatFiles(host);
|
|
}
|
|
|
|
tasks.push(() => {
|
|
logShowProjectCommand(options.e2eProjectName);
|
|
});
|
|
|
|
return runTasksInSerial(...tasks);
|
|
}
|
|
|
|
async function normalizeOptions(
|
|
tree: Tree,
|
|
options: Schema
|
|
): Promise<
|
|
Omit<Schema, 'name'> & {
|
|
e2eProjectRoot: string;
|
|
e2eProjectName: string;
|
|
importPath: string;
|
|
isUsingTsSolutionConfig: boolean;
|
|
}
|
|
> {
|
|
let directory = options.rootProject ? 'e2e' : options.directory;
|
|
if (!directory) {
|
|
const projectConfig = readProjectConfiguration(tree, options.project);
|
|
directory = `${projectConfig.root}-e2e`;
|
|
}
|
|
const {
|
|
projectName: e2eProjectName,
|
|
projectRoot: e2eProjectRoot,
|
|
importPath,
|
|
} = await determineProjectNameAndRootOptions(tree, {
|
|
name: options.name,
|
|
projectType: 'application',
|
|
directory,
|
|
});
|
|
|
|
const nxJson = readNxJson(tree);
|
|
const addPlugin =
|
|
process.env.NX_ADD_PLUGINS !== 'false' &&
|
|
nxJson.useInferencePlugins !== false;
|
|
|
|
const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree);
|
|
|
|
return {
|
|
addPlugin,
|
|
...options,
|
|
e2eProjectRoot,
|
|
e2eProjectName,
|
|
importPath,
|
|
port: options.port ?? 3000,
|
|
rootProject: !!options.rootProject,
|
|
isUsingTsSolutionConfig,
|
|
useProjectJson: options.useProjectJson ?? !isUsingTsSolutionConfig,
|
|
};
|
|
}
|
|
|
|
export default e2eProjectGenerator;
|