fix(testing): adding e2e projects should register e2e-ci targetDefaults (#27185)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
e74db498ca
commit
dfd7241ed5
@ -569,7 +569,8 @@ describe('app', () => {
|
||||
it('should add eslint plugin and no lint target to e2e project', async () => {
|
||||
await generateApp(appTree, 'my-app', { linter: Linter.EsLint });
|
||||
|
||||
expect(readNxJson(appTree).plugins).toMatchInlineSnapshot(`
|
||||
const nxJson = readNxJson(appTree);
|
||||
expect(nxJson.plugins).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"options": {
|
||||
@ -588,6 +589,13 @@ describe('app', () => {
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(nxJson.targetDefaults['e2e-ci--**/*']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(
|
||||
readProjectConfiguration(appTree, 'my-app-e2e').targets.lint
|
||||
).toBeUndefined();
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
|
||||
import type { NormalizedSchema } from './normalized-schema';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function addE2e(tree: Tree, options: NormalizedSchema) {
|
||||
// since e2e are separate projects, default to adding plugins
|
||||
@ -45,6 +46,14 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
|
||||
rootProject: options.rootProject,
|
||||
addPlugin,
|
||||
});
|
||||
if (addPlugin) {
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
joinPathFragments(options.e2eProjectRoot, 'cypress.config.ts')
|
||||
);
|
||||
}
|
||||
} else if (options.e2eTestRunner === 'playwright') {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/playwright')
|
||||
@ -71,6 +80,14 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
|
||||
rootProject: options.rootProject,
|
||||
addPlugin,
|
||||
});
|
||||
if (addPlugin) {
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/playwright/plugin',
|
||||
'^build',
|
||||
joinPathFragments(options.e2eProjectRoot, 'playwright.config.ts')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import { addProjectConfiguration, joinPathFragments, Tree } from '@nx/devkit';
|
||||
import type { AngularProjectConfiguration } from '../../../utils/types';
|
||||
import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
|
||||
import type { NormalizedSchema } from './normalized-schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export function createProject(tree: Tree, options: NormalizedSchema) {
|
||||
const { major: angularMajorVersion } = getInstalledAngularVersionInfo(tree);
|
||||
|
||||
@ -2,7 +2,7 @@ import type { Tree } from '@nx/devkit';
|
||||
import { addProjectConfiguration, joinPathFragments } from '@nx/devkit';
|
||||
import type { AngularProjectConfiguration } from '../../../utils/types';
|
||||
import type { NormalizedSchema } from './normalized-schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export function addProject(
|
||||
tree: Tree,
|
||||
|
||||
@ -35,6 +35,12 @@
|
||||
"version": "19.6.0-beta.0",
|
||||
"description": "Update ciWebServerCommand to use previewTargetName if Vite is detected for the application.",
|
||||
"implementation": "./src/migrations/update-19-6-0/update-ci-webserver-for-vite"
|
||||
},
|
||||
"update-19-6-0-add-e2e-ci-target-defaults": {
|
||||
"cli": "nx",
|
||||
"version": "19.6.0-beta.0",
|
||||
"description": "Add inferred ciTargetNames to targetDefaults with dependsOn to ensure dependent application builds are scheduled before atomized tasks.",
|
||||
"implementation": "./src/migrations/update-19-6-0/add-e2e-ci-target-defaults"
|
||||
}
|
||||
},
|
||||
"packageJsonUpdates": {
|
||||
|
||||
@ -0,0 +1,366 @@
|
||||
import { ProjectGraph, readNxJson, type Tree, updateNxJson } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||
import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults';
|
||||
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual<any>('@nx/devkit'),
|
||||
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
|
||||
return projectGraph;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('add-e2e-ci-target-defaults', () => {
|
||||
let tree: Tree;
|
||||
let tempFs: TempFs;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tempFs = new TempFs('add-e2e-ci');
|
||||
tree.root = tempFs.tempDir;
|
||||
projectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tempFs.reset();
|
||||
});
|
||||
|
||||
it('should do nothing when the plugin is not registered', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add the targetDefaults with the correct ciTargetName and buildTarget when there is one plugin', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
addProject(tree, tempFs);
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add the targetDefaults with the correct ciTargetNames and buildTargets when there is more than one plugin', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
include: ['app-e2e/**'],
|
||||
},
|
||||
{
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
include: ['shop-e2e/**'],
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
addProject(tree, tempFs);
|
||||
addProject(tree, tempFs, {
|
||||
buildTargetName: 'build',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
appName: 'shop',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"cypress:e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should only add the targetDefaults with the correct ciTargetName and buildTargets when there is more than one plugin with only one matching multiple projects', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
include: ['cart-e2e/**'],
|
||||
},
|
||||
{
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
addProject(tree, tempFs);
|
||||
addProject(tree, tempFs, {
|
||||
buildTargetName: 'bundle',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
appName: 'shop',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"cypress:e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"^bundle",
|
||||
],
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not add the targetDefaults when the ciWebServerCommand is not present', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
addProject(tree, tempFs, {
|
||||
appName: 'app',
|
||||
buildTargetName: 'build',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
noCi: true,
|
||||
});
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
function addProject(
|
||||
tree: Tree,
|
||||
tempFs: TempFs,
|
||||
overrides: {
|
||||
ciTargetName: string;
|
||||
buildTargetName: string;
|
||||
appName: string;
|
||||
noCi?: boolean;
|
||||
} = { ciTargetName: 'e2e-ci', buildTargetName: 'build', appName: 'app' }
|
||||
) {
|
||||
const appProjectConfig = {
|
||||
name: overrides.appName,
|
||||
root: overrides.appName,
|
||||
sourceRoot: `${overrides.appName}/src`,
|
||||
projectType: 'application',
|
||||
};
|
||||
const viteConfig = `/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/${overrides.appName}',
|
||||
|
||||
server: {
|
||||
port: 4200,
|
||||
host: 'localhost',
|
||||
},
|
||||
|
||||
preview: {
|
||||
port: 4300,
|
||||
host: 'localhost',
|
||||
},
|
||||
|
||||
plugins: [react(), nxViteTsPaths()],
|
||||
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
|
||||
build: {
|
||||
outDir: '../../dist/${overrides.appName}',
|
||||
emptyOutDir: true,
|
||||
reportCompressedSize: true,
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
},
|
||||
});`;
|
||||
|
||||
const e2eProjectConfig = {
|
||||
name: `${overrides.appName}-e2e`,
|
||||
root: `${overrides.appName}-e2e`,
|
||||
sourceRoot: `${overrides.appName}-e2e/src`,
|
||||
projectType: 'application',
|
||||
};
|
||||
|
||||
const cypressConfig = `import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
|
||||
import { defineConfig } from 'cypress';
|
||||
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__filename, {
|
||||
cypressDir: 'src',
|
||||
bundler: 'vite',
|
||||
webServerCommands: {
|
||||
default: 'nx run ${overrides.appName}:serve',
|
||||
production: 'nx run ${overrides.appName}:preview',
|
||||
},
|
||||
${
|
||||
!overrides.noCi
|
||||
? `ciWebServerCommand: 'nx run ${overrides.appName}:serve-static',`
|
||||
: ''
|
||||
}
|
||||
}),
|
||||
baseUrl: 'http://localhost:4200',
|
||||
},
|
||||
});
|
||||
`;
|
||||
|
||||
tree.write(`${overrides.appName}/vite.config.ts`, viteConfig);
|
||||
tree.write(
|
||||
`${overrides.appName}/project.json`,
|
||||
JSON.stringify(appProjectConfig)
|
||||
);
|
||||
tree.write(`${overrides.appName}-e2e/cypress.config.ts`, cypressConfig);
|
||||
tree.write(
|
||||
`${overrides.appName}-e2e/project.json`,
|
||||
JSON.stringify(e2eProjectConfig)
|
||||
);
|
||||
tempFs.createFilesSync({
|
||||
[`${overrides.appName}/vite.config.ts`]: viteConfig,
|
||||
[`${overrides.appName}/project.json`]: JSON.stringify(appProjectConfig),
|
||||
[`${overrides.appName}-e2e/cypress.config.ts`]: cypressConfig,
|
||||
[`${overrides.appName}-e2e/project.json`]: JSON.stringify(e2eProjectConfig),
|
||||
});
|
||||
|
||||
projectGraph.nodes[overrides.appName] = {
|
||||
name: overrides.appName,
|
||||
type: 'app',
|
||||
data: {
|
||||
projectType: 'application',
|
||||
root: overrides.appName,
|
||||
targets: {
|
||||
[overrides.buildTargetName]: {},
|
||||
'serve-static': {
|
||||
options: {
|
||||
buildTarget: overrides.buildTargetName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
projectGraph.nodes[`${overrides.appName}-e2e`] = {
|
||||
name: `${overrides.appName}-e2e`,
|
||||
type: 'app',
|
||||
data: {
|
||||
projectType: 'application',
|
||||
root: `${overrides.appName}-e2e`,
|
||||
targets: {
|
||||
e2e: {},
|
||||
[overrides.ciTargetName]: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,102 @@
|
||||
import {
|
||||
type Tree,
|
||||
type CreateNodesV2,
|
||||
formatFiles,
|
||||
readNxJson,
|
||||
createProjectGraphAsync,
|
||||
parseTargetString,
|
||||
} from '@nx/devkit';
|
||||
import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api';
|
||||
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||
import {
|
||||
ProjectConfigurationsError,
|
||||
retrieveProjectConfigurations,
|
||||
} from 'nx/src/devkit-internals';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import { type CypressPluginOptions } from '../../plugins/plugin';
|
||||
|
||||
export default async function addE2eCiTargetDefaults(tree: Tree) {
|
||||
const pluginName = '@nx/cypress/plugin';
|
||||
const graph = await createProjectGraphAsync();
|
||||
const nxJson = readNxJson(tree);
|
||||
const matchingPluginRegistrations = nxJson.plugins?.filter((p) =>
|
||||
typeof p === 'string' ? p === pluginName : p.plugin === pluginName
|
||||
);
|
||||
|
||||
if (!matchingPluginRegistrations) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
createNodesV2,
|
||||
}: { createNodesV2: CreateNodesV2<CypressPluginOptions> } = await import(
|
||||
pluginName
|
||||
);
|
||||
|
||||
for (const plugin of matchingPluginRegistrations) {
|
||||
let projectConfigs: ConfigurationResult;
|
||||
try {
|
||||
const loadedPlugin = new LoadedNxPlugin(
|
||||
{ createNodesV2, name: pluginName },
|
||||
plugin
|
||||
);
|
||||
projectConfigs = await retrieveProjectConfigurations(
|
||||
[loadedPlugin],
|
||||
tree.root,
|
||||
nxJson
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof ProjectConfigurationsError) {
|
||||
projectConfigs = e.partialProjectConfigurationsResult;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
for (const configFile of projectConfigs.matchingProjectFiles) {
|
||||
const configFileContents = tree.read(configFile, 'utf-8');
|
||||
if (!configFileContents.includes('ciWebServerCommand')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ast = tsquery.ast(configFileContents);
|
||||
const CI_WEBSERVER_COMMAND_SELECTOR =
|
||||
'ObjectLiteralExpression PropertyAssignment:has(Identifier[name=ciWebServerCommand]) > StringLiteral';
|
||||
const nodes = tsquery(ast, CI_WEBSERVER_COMMAND_SELECTOR, {
|
||||
visitAllChildren: true,
|
||||
});
|
||||
if (!nodes.length) {
|
||||
continue;
|
||||
}
|
||||
const ciWebServerCommand = nodes[0].getText();
|
||||
const NX_TARGET_REGEX = "(?<=nx run )[^']+";
|
||||
const matches = ciWebServerCommand.match(NX_TARGET_REGEX);
|
||||
if (!matches) {
|
||||
continue;
|
||||
}
|
||||
const targetString = matches[0];
|
||||
const { project, target, configuration } = parseTargetString(
|
||||
targetString,
|
||||
graph
|
||||
);
|
||||
|
||||
const serveStaticTarget = graph.nodes[project].data.targets[target];
|
||||
let resolvedBuildTarget: string;
|
||||
if (serveStaticTarget.dependsOn) {
|
||||
resolvedBuildTarget = serveStaticTarget.dependsOn.join(',');
|
||||
} else {
|
||||
resolvedBuildTarget =
|
||||
(configuration
|
||||
? serveStaticTarget.configurations[configuration].buildTarget
|
||||
: serveStaticTarget.options.buildTarget) ?? 'build';
|
||||
}
|
||||
|
||||
const buildTarget = `^${resolvedBuildTarget}`;
|
||||
|
||||
await _addE2eCiTargetDefaults(tree, pluginName, buildTarget, configFile);
|
||||
}
|
||||
}
|
||||
|
||||
await formatFiles(tree);
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
import { readNxJson, Tree, updateNxJson } from 'nx/src/devkit-exports';
|
||||
|
||||
export function addBuildTargetDefaults(
|
||||
tree: Tree,
|
||||
executorName: string,
|
||||
buildTargetName = 'build'
|
||||
): void {
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.targetDefaults ??= {};
|
||||
nxJson.targetDefaults[executorName] ??= {
|
||||
cache: true,
|
||||
dependsOn: [`^${buildTargetName}`],
|
||||
inputs:
|
||||
nxJson.namedInputs && 'production' in nxJson.namedInputs
|
||||
? ['production', '^production']
|
||||
: ['default', '^default'],
|
||||
};
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
377
packages/devkit/src/generators/target-defaults-utils.spec.ts
Normal file
377
packages/devkit/src/generators/target-defaults-utils.spec.ts
Normal file
@ -0,0 +1,377 @@
|
||||
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
|
||||
import { readNxJson, updateNxJson, type Tree } from 'nx/src/devkit-exports';
|
||||
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||
import { addE2eCiTargetDefaults } from './target-defaults-utils';
|
||||
describe('target-defaults-utils', () => {
|
||||
describe('addE2eCiTargetDefaults', () => {
|
||||
let tree: Tree;
|
||||
let tempFs: TempFs;
|
||||
beforeEach(() => {
|
||||
tempFs = new TempFs('target-defaults-utils');
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.root = tempFs.tempDir;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tempFs.cleanup();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should add e2e-ci--**/* target default for e2e plugin for specified build target when it does not exist', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults['e2e-ci--**/*']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should update existing e2e-ci--**/* target default for e2e plugin for specified build target when it does not exist in dependsOn', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
nxJson.targetDefaults ??= {};
|
||||
nxJson.targetDefaults['e2e-ci--**/*'] = {
|
||||
dependsOn: ['^build'],
|
||||
};
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build-base',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults['e2e-ci--**/*']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"^build-base",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should read the ciTargetName and add a new entry when it does not exist', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
});
|
||||
nxJson.targetDefaults ??= {};
|
||||
nxJson.targetDefaults['e2e-ci--**/*'] = {
|
||||
dependsOn: ['^build'],
|
||||
};
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build-base',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults['e2e-ci--**/*']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(newNxJson.targetDefaults['cypress:e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build-base",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should not add additional e2e-ci--**/* target default for e2e plugin when it already exists with build target', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
nxJson.targetDefaults ??= {};
|
||||
nxJson.targetDefaults['e2e-ci--**/*'] = {
|
||||
dependsOn: ['^build'],
|
||||
};
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should do nothing when there are no nxJson.plugins does not exist', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = undefined;
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should do nothing when there are nxJson.plugins but e2e plugin is not registered', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/playwright/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should choose the correct plugin when there are includes', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
include: ['libs/**'],
|
||||
});
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
include: ['apps/**'],
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults['cypress:e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should choose the correct plugin when there are excludes', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
exclude: ['apps/**'],
|
||||
});
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
exclude: ['libs/**'],
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults['cypress:e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should use the default name when the plugin registration is a string', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push('@nx/cypress/plugin');
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'^build',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
const newNxJson = readNxJson(tree);
|
||||
expect(newNxJson.targetDefaults['e2e-ci--**/*']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
98
packages/devkit/src/generators/target-defaults-utils.ts
Normal file
98
packages/devkit/src/generators/target-defaults-utils.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import {
|
||||
type CreateNodes,
|
||||
type CreateNodesV2,
|
||||
type PluginConfiguration,
|
||||
type Tree,
|
||||
readNxJson,
|
||||
updateNxJson,
|
||||
} from 'nx/src/devkit-exports';
|
||||
import { findMatchingConfigFiles } from 'nx/src/devkit-internals';
|
||||
|
||||
export function addBuildTargetDefaults(
|
||||
tree: Tree,
|
||||
executorName: string,
|
||||
buildTargetName = 'build'
|
||||
): void {
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.targetDefaults ??= {};
|
||||
nxJson.targetDefaults[executorName] ??= {
|
||||
cache: true,
|
||||
dependsOn: [`^${buildTargetName}`],
|
||||
inputs:
|
||||
nxJson.namedInputs && 'production' in nxJson.namedInputs
|
||||
? ['production', '^production']
|
||||
: ['default', '^default'],
|
||||
};
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
|
||||
export async function addE2eCiTargetDefaults(
|
||||
tree: Tree,
|
||||
e2ePlugin: string,
|
||||
buildTarget: string,
|
||||
pathToE2EConfigFile: string
|
||||
): Promise<void> {
|
||||
const nxJson = readNxJson(tree);
|
||||
if (!nxJson.plugins) {
|
||||
return;
|
||||
}
|
||||
|
||||
const e2ePluginRegistrations = nxJson.plugins.filter((p) =>
|
||||
typeof p === 'string' ? p === e2ePlugin : p.plugin === e2ePlugin
|
||||
);
|
||||
if (!e2ePluginRegistrations.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const resolvedE2ePlugin: {
|
||||
createNodes?: CreateNodes;
|
||||
createNodesV2?: CreateNodesV2;
|
||||
} = await import(e2ePlugin);
|
||||
const e2ePluginGlob =
|
||||
resolvedE2ePlugin.createNodesV2?.[0] ?? resolvedE2ePlugin.createNodes?.[0];
|
||||
|
||||
let foundPluginForApplication: PluginConfiguration;
|
||||
for (let i = 0; i < e2ePluginRegistrations.length; i++) {
|
||||
let candidatePluginForApplication = e2ePluginRegistrations[i];
|
||||
if (typeof candidatePluginForApplication === 'string') {
|
||||
foundPluginForApplication = candidatePluginForApplication;
|
||||
break;
|
||||
}
|
||||
|
||||
const matchingConfigFiles = findMatchingConfigFiles(
|
||||
[pathToE2EConfigFile],
|
||||
e2ePluginGlob,
|
||||
candidatePluginForApplication.include,
|
||||
candidatePluginForApplication.exclude
|
||||
);
|
||||
|
||||
if (matchingConfigFiles.length) {
|
||||
foundPluginForApplication = candidatePluginForApplication;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundPluginForApplication) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ciTargetName =
|
||||
typeof foundPluginForApplication === 'string'
|
||||
? 'e2e-ci'
|
||||
: (foundPluginForApplication.options as any)?.ciTargetName ?? 'e2e-ci';
|
||||
|
||||
const ciTargetNameGlob = `${ciTargetName}--**/*`;
|
||||
nxJson.targetDefaults ??= {};
|
||||
const e2eCiTargetDefaults = nxJson.targetDefaults[ciTargetNameGlob];
|
||||
if (!e2eCiTargetDefaults) {
|
||||
nxJson.targetDefaults[ciTargetNameGlob] = {
|
||||
dependsOn: [buildTarget],
|
||||
};
|
||||
} else {
|
||||
e2eCiTargetDefaults.dependsOn ??= [];
|
||||
if (!e2eCiTargetDefaults.dependsOn.includes(buildTarget)) {
|
||||
e2eCiTargetDefaults.dependsOn.push(buildTarget);
|
||||
}
|
||||
}
|
||||
updateNxJson(tree, nxJson);
|
||||
}
|
||||
173
packages/devkit/src/utils/find-plugin-for-config-file.spec.ts
Normal file
173
packages/devkit/src/utils/find-plugin-for-config-file.spec.ts
Normal file
@ -0,0 +1,173 @@
|
||||
import { type Tree, readNxJson, updateNxJson } from 'nx/src/devkit-exports';
|
||||
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
|
||||
import { findPluginForConfigFile } from './find-plugin-for-config-file';
|
||||
|
||||
describe('find-plugin-for-config-file', () => {
|
||||
let tree: Tree;
|
||||
let tempFs: TempFs;
|
||||
beforeEach(() => {
|
||||
tempFs = new TempFs('target-defaults-utils');
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tree.root = tempFs.tempDir;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tempFs.cleanup();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it('should return the plugin when its registered as just a string', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push('@nx/cypress/plugin');
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
const plugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(plugin).toBeTruthy();
|
||||
expect(plugin).toEqual('@nx/cypress/plugin');
|
||||
});
|
||||
|
||||
it('should return the plugin when it does not have an include or exclude', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
const plugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(plugin).toBeTruthy();
|
||||
expect(plugin).toMatchInlineSnapshot(`
|
||||
{
|
||||
"options": {
|
||||
"ciTargetName": "e2e-ci",
|
||||
"targetName": "e2e",
|
||||
},
|
||||
"plugin": "@nx/cypress/plugin",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return the plugin when it the includes finds the config file', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
include: ['libs/**'],
|
||||
});
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
include: ['apps/**'],
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
const plugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(plugin).toBeTruthy();
|
||||
expect(plugin).toMatchInlineSnapshot(`
|
||||
{
|
||||
"include": [
|
||||
"apps/**",
|
||||
],
|
||||
"options": {
|
||||
"ciTargetName": "cypress:e2e-ci",
|
||||
"targetName": "e2e",
|
||||
},
|
||||
"plugin": "@nx/cypress/plugin",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should return a valid plugin when it the excludes does not include the config file', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'cypress:e2e-ci',
|
||||
},
|
||||
exclude: ['apps/**'],
|
||||
});
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/cypress/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
exclude: ['libs/**'],
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
tree.write('apps/myapp-e2e/cypress.config.ts', '');
|
||||
await tempFs.createFile('apps/myapp-e2e/cypress.config.ts', '');
|
||||
|
||||
// ACT
|
||||
const plugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
'apps/myapp-e2e/cypress.config.ts'
|
||||
);
|
||||
|
||||
// ASSERT
|
||||
expect(plugin).toBeTruthy();
|
||||
expect(plugin).toMatchInlineSnapshot(`
|
||||
{
|
||||
"exclude": [
|
||||
"libs/**",
|
||||
],
|
||||
"options": {
|
||||
"ciTargetName": "e2e-ci",
|
||||
"targetName": "e2e",
|
||||
},
|
||||
"plugin": "@nx/cypress/plugin",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
50
packages/devkit/src/utils/find-plugin-for-config-file.ts
Normal file
50
packages/devkit/src/utils/find-plugin-for-config-file.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {
|
||||
type Tree,
|
||||
type PluginConfiguration,
|
||||
readNxJson,
|
||||
CreateNodes,
|
||||
CreateNodesV2,
|
||||
} from 'nx/src/devkit-exports';
|
||||
import { findMatchingConfigFiles } from 'nx/src/devkit-internals';
|
||||
export async function findPluginForConfigFile(
|
||||
tree: Tree,
|
||||
pluginName: string,
|
||||
pathToConfigFile: string
|
||||
): Promise<PluginConfiguration> {
|
||||
const nxJson = readNxJson(tree);
|
||||
if (!nxJson.plugins) {
|
||||
return;
|
||||
}
|
||||
|
||||
const pluginRegistrations: PluginConfiguration[] = nxJson.plugins.filter(
|
||||
(p) => (typeof p === 'string' ? p === pluginName : p.plugin === pluginName)
|
||||
);
|
||||
|
||||
for (const plugin of pluginRegistrations) {
|
||||
if (typeof plugin === 'string') {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
if (!plugin.include && !plugin.exclude) {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
if (plugin.include || plugin.exclude) {
|
||||
const resolvedPlugin: {
|
||||
createNodes?: CreateNodes;
|
||||
createNodesV2?: CreateNodesV2;
|
||||
} = await import(pluginName);
|
||||
const pluginGlob =
|
||||
resolvedPlugin.createNodesV2?.[0] ?? resolvedPlugin.createNodes?.[0];
|
||||
const matchingConfigFile = findMatchingConfigFiles(
|
||||
[pathToConfigFile],
|
||||
pluginGlob,
|
||||
plugin.include,
|
||||
plugin.exclude
|
||||
);
|
||||
if (matchingConfigFile.length) {
|
||||
return plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@ import { getImportPath } from '@nx/js/src/utils/get-import-path';
|
||||
import { esbuildInitGenerator } from '../init/init';
|
||||
import { EsBuildExecutorOptions } from '../../executors/esbuild/schema';
|
||||
import { EsBuildProjectSchema } from './schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function configurationGenerator(
|
||||
tree: Tree,
|
||||
|
||||
@ -3,6 +3,7 @@ import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||
import {
|
||||
getProjects,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
@ -282,4 +283,56 @@ describe('app', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('cypress', () => {
|
||||
it('should create e2e app with e2e-ci targetDefaults', async () => {
|
||||
await expoApplicationGenerator(appTree, {
|
||||
name: 'my-app',
|
||||
directory: 'my-dir',
|
||||
linter: Linter.EsLint,
|
||||
e2eTestRunner: 'cypress',
|
||||
js: false,
|
||||
skipFormat: false,
|
||||
unitTestRunner: 'none',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const nxJson = readNxJson(appTree);
|
||||
expect(nxJson.targetDefaults['e2e-ci--**/*']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^export",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('playwright', () => {
|
||||
it('should create e2e app with e2e-ci targetDefaults', async () => {
|
||||
await expoApplicationGenerator(appTree, {
|
||||
name: 'my-app',
|
||||
directory: 'my-dir',
|
||||
linter: Linter.EsLint,
|
||||
e2eTestRunner: 'playwright',
|
||||
js: false,
|
||||
skipFormat: false,
|
||||
unitTestRunner: 'none',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
const nxJson = readNxJson(appTree);
|
||||
expect(nxJson.targetDefaults['e2e-ci--**/*']).toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^export",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,12 +4,15 @@ import {
|
||||
ensurePackage,
|
||||
getPackageManagerCommand,
|
||||
joinPathFragments,
|
||||
readNxJson,
|
||||
} from '@nx/devkit';
|
||||
import { webStaticServeGenerator } from '@nx/web';
|
||||
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { hasExpoPlugin } from '../../../utils/has-expo-plugin';
|
||||
import { NormalizedSchema } from './normalize-options';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
|
||||
export async function addE2e(
|
||||
tree: Tree,
|
||||
@ -18,8 +21,7 @@ export async function addE2e(
|
||||
const hasPlugin = hasExpoPlugin(tree);
|
||||
switch (options.e2eTestRunner) {
|
||||
case 'cypress': {
|
||||
const hasNxExpoPlugin = hasExpoPlugin(tree);
|
||||
if (!hasNxExpoPlugin) {
|
||||
if (!hasPlugin) {
|
||||
await webStaticServeGenerator(tree, {
|
||||
buildTarget: `${options.projectName}:export`,
|
||||
targetName: 'serve-static',
|
||||
@ -39,7 +41,7 @@ export async function addE2e(
|
||||
tags: [],
|
||||
});
|
||||
|
||||
return await configurationGenerator(tree, {
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
...options,
|
||||
project: options.e2eProjectName,
|
||||
directory: 'src',
|
||||
@ -49,12 +51,46 @@ export async function addE2e(
|
||||
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`,
|
||||
port: options.e2ePort,
|
||||
baseUrl: options.e2eWebServerAddress,
|
||||
ciWebServerCommand: hasNxExpoPlugin
|
||||
ciWebServerCommand: hasPlugin
|
||||
? `nx run ${options.projectName}:serve-static`
|
||||
: undefined,
|
||||
jsx: true,
|
||||
rootProject: options.rootProject,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/cypress/plugin'
|
||||
: p.plugin === '@nx/cypress/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^export';
|
||||
if (hasPlugin) {
|
||||
const matchingExpoPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
'@nx/expo/plugin',
|
||||
joinPathFragments(options.appProjectRoot, 'app.json')
|
||||
);
|
||||
if (matchingExpoPlugin && typeof matchingExpoPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingExpoPlugin.options as any)?.exportTargetName ?? 'export'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(
|
||||
options.e2eProjectRoot,
|
||||
`cypress.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
case 'playwright': {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
@ -67,7 +103,8 @@ export async function addE2e(
|
||||
targets: {},
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
return configurationGenerator(tree, {
|
||||
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
project: options.e2eProjectName,
|
||||
skipFormat: true,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
@ -80,7 +117,39 @@ export async function addE2e(
|
||||
} ${options.name}`,
|
||||
webServerAddress: options.e2eWebServerAddress,
|
||||
rootProject: options.rootProject,
|
||||
addPlugin: options.addPlugin,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/playwright/plugin'
|
||||
: p.plugin === '@nx/playwright/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^export';
|
||||
if (hasPlugin) {
|
||||
const matchingExpoPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
'@nx/expo/plugin',
|
||||
joinPathFragments(options.appProjectRoot, 'app.json')
|
||||
);
|
||||
if (matchingExpoPlugin && typeof matchingExpoPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingExpoPlugin.options as any)?.exportTargetName ?? 'export'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/playwright/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
case 'detox':
|
||||
const { detoxApplicationGenerator } = ensurePackage<
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
|
||||
import { hasExpoPlugin } from '../../../utils/has-expo-plugin';
|
||||
import { NormalizedSchema } from './normalize-options';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export function addProject(host: Tree, options: NormalizedSchema) {
|
||||
const nxJson = readNxJson(host);
|
||||
|
||||
@ -32,7 +32,7 @@ import { NormalizedSchema, normalizeOptions } from './lib/normalize-options';
|
||||
import { Schema } from './schema';
|
||||
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||
import { initRootBabelConfig } from '../../utils/init-root-babel-config';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||
|
||||
export async function expoLibraryGenerator(
|
||||
|
||||
@ -25,7 +25,7 @@ import {
|
||||
type ProjectNameAndRootOptions,
|
||||
} from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||
import { findMatchingProjects } from 'nx/src/utils/find-matching-projects';
|
||||
import { type PackageJson } from 'nx/src/utils/package-json';
|
||||
|
||||
@ -12,7 +12,7 @@ import { addSwcConfig } from '../../utils/swc/add-swc-config';
|
||||
import { addSwcDependencies } from '../../utils/swc/add-swc-dependencies';
|
||||
import { nxVersion } from '../../utils/versions';
|
||||
import { SetupBuildGeneratorSchema } from './schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function setupBuildGenerator(
|
||||
tree: Tree,
|
||||
|
||||
@ -2,6 +2,7 @@ import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import {
|
||||
getProjects,
|
||||
readJson,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
@ -359,28 +360,9 @@ describe('app', () => {
|
||||
const indexContent = tree.read(`${name}/src/app/page.tsx`, 'utf-8');
|
||||
expect(indexContent).not.toContain(`import styles from './page.module`);
|
||||
expect(indexContent).toContain(`import styled from '@emotion/styled'`);
|
||||
expect(tree.read(`${name}/src/app/layout.tsx`, 'utf-8'))
|
||||
.toMatchInlineSnapshot(`
|
||||
"import './global.css';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Welcome to ${name}',
|
||||
description: 'Generated by create-nx-workspace',
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
"
|
||||
`);
|
||||
expect(
|
||||
tree.read(`${name}/src/app/layout.tsx`, 'utf-8')
|
||||
).toMatchInlineSnapshot(``);
|
||||
});
|
||||
|
||||
it('should add jsxImportSource in tsconfig.json', async () => {
|
||||
@ -559,6 +541,50 @@ describe('app', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('--e2e-test-runner cypress', () => {
|
||||
it('should generate e2e-ci targetDefaults', async () => {
|
||||
const name = uniq();
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
name,
|
||||
style: 'css',
|
||||
e2eTestRunner: 'cypress',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: true,
|
||||
});
|
||||
expect(readNxJson(tree).targetDefaults['e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('--e2e-test-runner playwright', () => {
|
||||
it('should generate e2e-ci targetDefaults', async () => {
|
||||
const name = uniq();
|
||||
|
||||
await applicationGenerator(tree, {
|
||||
name,
|
||||
style: 'css',
|
||||
e2eTestRunner: 'playwright',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
addPlugin: true,
|
||||
});
|
||||
expect(readNxJson(tree).targetDefaults['e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
it('should generate functional components by default', async () => {
|
||||
const name = uniq();
|
||||
|
||||
|
||||
@ -11,6 +11,8 @@ import { Linter } from '@nx/eslint';
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { NormalizedSchema } from './normalize-options';
|
||||
import { webStaticServeGenerator } from '@nx/web';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
const nxJson = readNxJson(host);
|
||||
@ -42,7 +44,7 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
|
||||
return configurationGenerator(host, {
|
||||
const e2eTask = await configurationGenerator(host, {
|
||||
...options,
|
||||
linter: Linter.EsLint,
|
||||
project: options.e2eProjectName,
|
||||
@ -60,6 +62,40 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
? `nx run ${options.projectName}:serve-static`
|
||||
: undefined,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(host).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/cypress/plugin'
|
||||
: p.plugin === '@nx/cypress/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
host,
|
||||
'@nx/next/plugin',
|
||||
joinPathFragments(options.appProjectRoot, 'next.config.js')
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
host,
|
||||
'@nx/cypress/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(
|
||||
options.e2eProjectRoot,
|
||||
`cypress.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
} else if (options.e2eTestRunner === 'playwright') {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/playwright')
|
||||
@ -71,7 +107,7 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
tags: [],
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
return configurationGenerator(host, {
|
||||
const e2eTask = await configurationGenerator(host, {
|
||||
rootProject: options.rootProject,
|
||||
project: options.e2eProjectName,
|
||||
skipFormat: true,
|
||||
@ -86,6 +122,37 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
} ${options.projectName}`,
|
||||
addPlugin: options.addPlugin,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(host).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/playwright/plugin'
|
||||
: p.plugin === '@nx/playwright/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
host,
|
||||
'@nx/next/plugin',
|
||||
joinPathFragments(options.appProjectRoot, 'next.config.js')
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
host,
|
||||
'@nx/playwright/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ import {
|
||||
readNxJson,
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export function addProject(host: Tree, options: NormalizedSchema) {
|
||||
const targets: Record<string, any> = {};
|
||||
|
||||
@ -48,7 +48,7 @@ import { initGenerator } from '../init/init';
|
||||
import { setupDockerGenerator } from '../setup-docker/setup-docker';
|
||||
import { Schema } from './schema';
|
||||
import { hasWebpackPlugin } from '../../utils/has-webpack-plugin';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||
|
||||
export interface NormalizedSchema extends Schema {
|
||||
|
||||
@ -22,7 +22,7 @@ import { join } from 'path';
|
||||
import { tslibVersion, typesNodeVersion } from '../../utils/versions';
|
||||
import { initGenerator } from '../init/init';
|
||||
import { Schema } from './schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export interface NormalizedSchema extends Schema {
|
||||
fileName: string;
|
||||
|
||||
@ -10,6 +10,14 @@ exports[`app generated files content - as-provided - my-app general application
|
||||
.cache"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - my-app general application should add the nuxt and vitest plugins 1`] = `
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build-static",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - my-app general application should configure eslint correctly 1`] = `
|
||||
"{
|
||||
"extends": ["@nuxt/eslint-config", "../.eslintrc.json"],
|
||||
@ -341,6 +349,14 @@ exports[`app generated files content - as-provided - myApp general application s
|
||||
.cache"
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - myApp general application should add the nuxt and vitest plugins 1`] = `
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build-static",
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`app generated files content - as-provided - myApp general application should configure eslint correctly 1`] = `
|
||||
"{
|
||||
"extends": ["@nuxt/eslint-config", "../.eslintrc.json"],
|
||||
|
||||
@ -96,6 +96,7 @@ describe('app', () => {
|
||||
nxJson.plugins.find((p) => p.plugin === '@nx/vite/plugin')
|
||||
)
|
||||
);
|
||||
expect(nxJson.targetDefaults['e2e-ci--**/*']).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -7,6 +7,8 @@ import {
|
||||
} from '@nx/devkit';
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
if (options.e2eTestRunner === 'cypress') {
|
||||
@ -21,7 +23,7 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
tags: [],
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
return await configurationGenerator(host, {
|
||||
const e2eTask = await configurationGenerator(host, {
|
||||
...options,
|
||||
project: options.e2eProjectName,
|
||||
directory: 'src',
|
||||
@ -38,6 +40,33 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
jsx: true,
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
let buildTarget = '^build-static';
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
host,
|
||||
'@nx/nuxt/plugin',
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
`nuxt.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildStaticTargetName ?? 'build-static'
|
||||
}`;
|
||||
}
|
||||
|
||||
await addE2eCiTargetDefaults(
|
||||
host,
|
||||
'@nx/cypress/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(
|
||||
options.e2eProjectRoot,
|
||||
`cypress.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
|
||||
return e2eTask;
|
||||
} else if (options.e2eTestRunner === 'playwright') {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/playwright')
|
||||
@ -48,7 +77,7 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
targets: {},
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
return configurationGenerator(host, {
|
||||
const e2eTask = await configurationGenerator(host, {
|
||||
project: options.e2eProjectName,
|
||||
skipFormat: true,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
@ -62,6 +91,30 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
|
||||
} ${options.projectName}`,
|
||||
addPlugin: true,
|
||||
});
|
||||
|
||||
let buildTarget = '^build-static';
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
host,
|
||||
'@nx/nuxt/plugin',
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
`nuxt.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildStaticTargetName ?? 'build-static'
|
||||
}`;
|
||||
}
|
||||
|
||||
await addE2eCiTargetDefaults(
|
||||
host,
|
||||
'@nx/playwright/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
|
||||
);
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
return () => {};
|
||||
}
|
||||
|
||||
@ -8,11 +8,11 @@ export { getExecutorInformation } from './command-line/run/executor-utils';
|
||||
export { readNxJson as readNxJsonFromDisk } from './config/nx-json';
|
||||
export { calculateDefaultProjectName } from './config/calculate-default-project-name';
|
||||
export { retrieveProjectConfigurationsWithAngularProjects } from './project-graph/utils/retrieve-workspace-files';
|
||||
export { mergeTargetConfigurations } from './project-graph/utils/project-configuration-utils';
|
||||
export {
|
||||
mergeTargetConfigurations,
|
||||
readProjectConfigurationsFromRootMap,
|
||||
findMatchingConfigFiles,
|
||||
} from './project-graph/utils/project-configuration-utils';
|
||||
export { readProjectConfigurationsFromRootMap } from './project-graph/utils/project-configuration-utils';
|
||||
export { splitTarget } from './utils/split-target';
|
||||
export { combineOptionsForExecutor } from './utils/params';
|
||||
export { sortObjectByKeys } from './utils/object-sort';
|
||||
|
||||
@ -0,0 +1,333 @@
|
||||
import { ProjectGraph, readNxJson, type Tree, updateNxJson } from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||
import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults';
|
||||
|
||||
let projectGraph: ProjectGraph;
|
||||
jest.mock('@nx/devkit', () => ({
|
||||
...jest.requireActual<any>('@nx/devkit'),
|
||||
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
|
||||
return projectGraph;
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('add-e2e-ci-target-defaults', () => {
|
||||
let tree: Tree;
|
||||
let tempFs: TempFs;
|
||||
|
||||
beforeEach(() => {
|
||||
tree = createTreeWithEmptyWorkspace();
|
||||
tempFs = new TempFs('add-e2e-ci');
|
||||
tree.root = tempFs.tempDir;
|
||||
projectGraph = {
|
||||
nodes: {},
|
||||
dependencies: {},
|
||||
externalNodes: {},
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
tempFs.reset();
|
||||
});
|
||||
|
||||
it('should do nothing when the plugin is not registered', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add the targetDefaults with the correct ciTargetName and buildTarget when there is one plugin', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/playwright/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
addProject(tree, tempFs);
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add the targetDefaults with the correct ciTargetNames and buildTargets when there is more than one plugin', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/playwright/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
include: ['app-e2e/**'],
|
||||
},
|
||||
{
|
||||
plugin: '@nx/playwright/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'playwright:e2e-ci',
|
||||
},
|
||||
include: ['shop-e2e/**'],
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
addProject(tree, tempFs);
|
||||
addProject(tree, tempFs, {
|
||||
buildTargetName: 'build',
|
||||
ciTargetName: 'playwright:e2e-ci',
|
||||
appName: 'shop',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
"playwright:e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should only add the targetDefaults with the correct ciTargetName and buildTargets when there is more than one plugin with only one matching multiple projects', async () => {
|
||||
// ARRANGE
|
||||
const nxJson = readNxJson(tree);
|
||||
nxJson.plugins = [
|
||||
{
|
||||
plugin: '@nx/playwright/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'e2e-ci',
|
||||
},
|
||||
include: ['cart-e2e/**'],
|
||||
},
|
||||
{
|
||||
plugin: '@nx/playwright/plugin',
|
||||
options: {
|
||||
targetName: 'e2e',
|
||||
ciTargetName: 'playwright:e2e-ci',
|
||||
},
|
||||
},
|
||||
];
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
addProject(tree, tempFs);
|
||||
addProject(tree, tempFs, {
|
||||
buildTargetName: 'bundle',
|
||||
ciTargetName: 'playwright:e2e-ci',
|
||||
appName: 'shop',
|
||||
});
|
||||
|
||||
// ACT
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
|
||||
// ASSERT
|
||||
expect(readNxJson(tree).targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"build": {
|
||||
"cache": true,
|
||||
},
|
||||
"lint": {
|
||||
"cache": true,
|
||||
},
|
||||
"playwright:e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"^bundle",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
function addProject(
|
||||
tree: Tree,
|
||||
tempFs: TempFs,
|
||||
overrides: {
|
||||
ciTargetName: string;
|
||||
buildTargetName: string;
|
||||
appName: string;
|
||||
noCi?: boolean;
|
||||
} = { ciTargetName: 'e2e-ci', buildTargetName: 'build', appName: 'app' }
|
||||
) {
|
||||
const appProjectConfig = {
|
||||
name: overrides.appName,
|
||||
root: overrides.appName,
|
||||
sourceRoot: `${overrides.appName}/src`,
|
||||
projectType: 'application',
|
||||
};
|
||||
const viteConfig = `/// <reference types='vitest' />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||
|
||||
export default defineConfig({
|
||||
root: __dirname,
|
||||
cacheDir: '../../node_modules/.vite/${overrides.appName}',
|
||||
|
||||
server: {
|
||||
port: 4200,
|
||||
host: 'localhost',
|
||||
},
|
||||
|
||||
preview: {
|
||||
port: 4300,
|
||||
host: 'localhost',
|
||||
},
|
||||
|
||||
plugins: [react(), nxViteTsPaths()],
|
||||
|
||||
// Uncomment this if you are using workers.
|
||||
// worker: {
|
||||
// plugins: [ nxViteTsPaths() ],
|
||||
// },
|
||||
|
||||
build: {
|
||||
outDir: '../../dist/${overrides.appName}',
|
||||
emptyOutDir: true,
|
||||
reportCompressedSize: true,
|
||||
commonjsOptions: {
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
},
|
||||
});`;
|
||||
|
||||
const e2eProjectConfig = {
|
||||
name: `${overrides.appName}-e2e`,
|
||||
root: `${overrides.appName}-e2e`,
|
||||
sourceRoot: `${overrides.appName}-e2e/src`,
|
||||
projectType: 'application',
|
||||
};
|
||||
|
||||
const playwrightConfig = `import { defineConfig, devices } from '@playwright/test';
|
||||
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||
|
||||
import { workspaceRoot } from '@nx/devkit';
|
||||
|
||||
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
|
||||
|
||||
export default defineConfig({
|
||||
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||
use: {
|
||||
baseURL,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
webServer: {
|
||||
command: 'npx nx run ${overrides.appName}:serve-static',
|
||||
url: 'http://localhost:4200',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
cwd: workspaceRoot,
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
],
|
||||
});`;
|
||||
|
||||
tree.write(`${overrides.appName}/vite.config.ts`, viteConfig);
|
||||
tree.write(
|
||||
`${overrides.appName}/project.json`,
|
||||
JSON.stringify(appProjectConfig)
|
||||
);
|
||||
tree.write(`${overrides.appName}-e2e/playwright.config.ts`, playwrightConfig);
|
||||
tree.write(
|
||||
`${overrides.appName}-e2e/project.json`,
|
||||
JSON.stringify(e2eProjectConfig)
|
||||
);
|
||||
tempFs.createFilesSync({
|
||||
[`${overrides.appName}/vite.config.ts`]: viteConfig,
|
||||
[`${overrides.appName}/project.json`]: JSON.stringify(appProjectConfig),
|
||||
[`${overrides.appName}-e2e/playwright.config.ts`]: playwrightConfig,
|
||||
[`${overrides.appName}-e2e/project.json`]: JSON.stringify(e2eProjectConfig),
|
||||
});
|
||||
|
||||
projectGraph.nodes[overrides.appName] = {
|
||||
name: overrides.appName,
|
||||
type: 'app',
|
||||
data: {
|
||||
projectType: 'application',
|
||||
root: overrides.appName,
|
||||
targets: {
|
||||
[overrides.buildTargetName]: {},
|
||||
'serve-static': {
|
||||
dependsOn: [overrides.buildTargetName],
|
||||
options: {
|
||||
buildTarget: overrides.buildTargetName,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
projectGraph.nodes[`${overrides.appName}-e2e`] = {
|
||||
name: `${overrides.appName}-e2e`,
|
||||
type: 'app',
|
||||
data: {
|
||||
projectType: 'application',
|
||||
root: `${overrides.appName}-e2e`,
|
||||
targets: {
|
||||
e2e: {},
|
||||
[overrides.ciTargetName]: {},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
import {
|
||||
type Tree,
|
||||
type CreateNodesV2,
|
||||
formatFiles,
|
||||
readNxJson,
|
||||
createProjectGraphAsync,
|
||||
parseTargetString,
|
||||
} from '@nx/devkit';
|
||||
import { addE2eCiTargetDefaults as _addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { LoadedNxPlugin } from 'nx/src/project-graph/plugins/internal-api';
|
||||
import type { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||
import {
|
||||
ProjectConfigurationsError,
|
||||
retrieveProjectConfigurations,
|
||||
} from 'nx/src/devkit-internals';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import { type PlaywrightPluginOptions } from '../../plugins/plugin';
|
||||
|
||||
export default async function addE2eCiTargetDefaults(tree: Tree) {
|
||||
const pluginName = '@nx/playwright/plugin';
|
||||
const graph = await createProjectGraphAsync();
|
||||
const nxJson = readNxJson(tree);
|
||||
const matchingPluginRegistrations = nxJson.plugins?.filter((p) =>
|
||||
typeof p === 'string' ? p === pluginName : p.plugin === pluginName
|
||||
);
|
||||
|
||||
if (!matchingPluginRegistrations) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
createNodesV2,
|
||||
}: { createNodesV2: CreateNodesV2<PlaywrightPluginOptions> } = await import(
|
||||
pluginName
|
||||
);
|
||||
|
||||
for (const plugin of matchingPluginRegistrations) {
|
||||
let projectConfigs: ConfigurationResult;
|
||||
try {
|
||||
const loadedPlugin = new LoadedNxPlugin(
|
||||
{ createNodesV2, name: pluginName },
|
||||
plugin
|
||||
);
|
||||
projectConfigs = await retrieveProjectConfigurations(
|
||||
[loadedPlugin],
|
||||
tree.root,
|
||||
nxJson
|
||||
);
|
||||
} catch (e) {
|
||||
if (e instanceof ProjectConfigurationsError) {
|
||||
projectConfigs = e.partialProjectConfigurationsResult;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
for (const configFile of projectConfigs.matchingProjectFiles) {
|
||||
const configFileContents = tree.read(configFile, 'utf-8');
|
||||
|
||||
const ast = tsquery.ast(configFileContents);
|
||||
const CI_WEBSERVER_COMMAND_SELECTOR =
|
||||
'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=command]) > StringLiteral';
|
||||
const nodes = tsquery(ast, CI_WEBSERVER_COMMAND_SELECTOR, {
|
||||
visitAllChildren: true,
|
||||
});
|
||||
if (!nodes.length) {
|
||||
continue;
|
||||
}
|
||||
const ciWebServerCommand = nodes[0].getText();
|
||||
let serveStaticProject: string;
|
||||
let serveStaticTarget: string;
|
||||
let serveStaticConfiguration: string;
|
||||
if (ciWebServerCommand.includes('nx run')) {
|
||||
const NX_TARGET_REGEX = "(?<=nx run )[^']+";
|
||||
const matches = ciWebServerCommand.match(NX_TARGET_REGEX);
|
||||
if (!matches) {
|
||||
continue;
|
||||
}
|
||||
const targetString = matches[0];
|
||||
const { project, target, configuration } = parseTargetString(
|
||||
targetString,
|
||||
graph
|
||||
);
|
||||
serveStaticProject = project;
|
||||
serveStaticTarget = target;
|
||||
serveStaticConfiguration = configuration;
|
||||
} else {
|
||||
const NX_PROJECT_REGEX = 'nx\\s+([^ ]+)\\s+([^ ]+)';
|
||||
const matches = ciWebServerCommand.match(NX_PROJECT_REGEX);
|
||||
if (!matches) {
|
||||
return;
|
||||
}
|
||||
serveStaticTarget = matches[1];
|
||||
serveStaticProject = matches[2];
|
||||
}
|
||||
|
||||
const resolvedServeStaticTarget =
|
||||
graph.nodes[serveStaticProject].data.targets[serveStaticTarget];
|
||||
|
||||
let resolvedBuildTarget: string;
|
||||
if (resolvedServeStaticTarget.dependsOn) {
|
||||
resolvedBuildTarget = resolvedServeStaticTarget.dependsOn.join(',');
|
||||
} else {
|
||||
resolvedBuildTarget =
|
||||
(serveStaticConfiguration
|
||||
? resolvedServeStaticTarget.configurations[serveStaticConfiguration]
|
||||
.buildTarget
|
||||
: resolvedServeStaticTarget.options.buildTarget) ?? 'build';
|
||||
}
|
||||
|
||||
const buildTarget = `^${resolvedBuildTarget}`;
|
||||
|
||||
await _addE2eCiTargetDefaults(tree, pluginName, buildTarget, configFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -8,6 +8,7 @@ import {
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nx/devkit';
|
||||
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||
import addE2eCiTargetDefaults from './add-e2e-ci-target-defaults';
|
||||
|
||||
export default async function (tree: Tree) {
|
||||
const graph = await createProjectGraphAsync();
|
||||
@ -138,5 +139,6 @@ export default async function (tree: Tree) {
|
||||
}
|
||||
});
|
||||
|
||||
await addE2eCiTargetDefaults(tree);
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
@ -1086,4 +1086,99 @@ describe('app', () => {
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add e2e-ci targetDefaults to nxJson when addPlugin=true with playwright', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
let nxJson = readNxJson(tree);
|
||||
delete nxJson.targetDefaults;
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myapp',
|
||||
addPlugin: true,
|
||||
linter: Linter.None,
|
||||
style: 'none',
|
||||
e2eTestRunner: 'playwright',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
nxJson = readNxJson(tree);
|
||||
expect(nxJson.targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add e2e-ci targetDefaults to nxJson when addPlugin=true with cypress', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
let nxJson = readNxJson(tree);
|
||||
delete nxJson.targetDefaults;
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myapp',
|
||||
addPlugin: true,
|
||||
linter: Linter.None,
|
||||
style: 'none',
|
||||
e2eTestRunner: 'cypress',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
nxJson = readNxJson(tree);
|
||||
expect(nxJson.targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should add e2e-ci targetDefaults to nxJson when addPlugin=true with cypress and use the defined webpack buildTargetName', async () => {
|
||||
// ARRANGE
|
||||
const tree = createTreeWithEmptyWorkspace();
|
||||
let nxJson = readNxJson(tree);
|
||||
delete nxJson.targetDefaults;
|
||||
nxJson.plugins ??= [];
|
||||
nxJson.plugins.push({
|
||||
plugin: '@nx/webpack/plugin',
|
||||
options: {
|
||||
buildTargetName: 'build-base',
|
||||
},
|
||||
});
|
||||
updateNxJson(tree, nxJson);
|
||||
|
||||
// ACT
|
||||
await applicationGenerator(tree, {
|
||||
name: 'myapp',
|
||||
addPlugin: true,
|
||||
linter: Linter.None,
|
||||
style: 'none',
|
||||
bundler: 'webpack',
|
||||
e2eTestRunner: 'cypress',
|
||||
});
|
||||
|
||||
// ASSERT
|
||||
nxJson = readNxJson(tree);
|
||||
expect(nxJson.targetDefaults).toMatchInlineSnapshot(`
|
||||
{
|
||||
"e2e-ci--**/*": {
|
||||
"dependsOn": [
|
||||
"^build-base",
|
||||
],
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
ensurePackage,
|
||||
getPackageManagerCommand,
|
||||
joinPathFragments,
|
||||
readNxJson,
|
||||
} from '@nx/devkit';
|
||||
import { webStaticServeGenerator } from '@nx/web';
|
||||
|
||||
@ -11,6 +12,8 @@ import { nxVersion } from '../../../utils/versions';
|
||||
import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin';
|
||||
import { hasVitePlugin } from '../../../utils/has-vite-plugin';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function addE2e(
|
||||
tree: Tree,
|
||||
@ -41,7 +44,7 @@ export async function addE2e(
|
||||
tags: [],
|
||||
});
|
||||
|
||||
return await configurationGenerator(tree, {
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
...options,
|
||||
project: options.e2eProjectName,
|
||||
directory: 'src',
|
||||
@ -64,6 +67,46 @@ export async function addE2e(
|
||||
ciBaseUrl:
|
||||
options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/cypress/plugin'
|
||||
: p.plugin === '@nx/cypress/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasNxBuildPlugin) {
|
||||
const configFile =
|
||||
options.bundler === 'webpack'
|
||||
? 'webpack.config.js'
|
||||
: options.bundler === 'vite'
|
||||
? `vite.config.${options.js ? 'js' : 'ts'}`
|
||||
: 'webpack.config.js';
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
`@nx/${options.bundler}/plugin`,
|
||||
joinPathFragments(options.appProjectRoot, configFile)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(
|
||||
options.e2eProjectRoot,
|
||||
`cypress.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
case 'playwright': {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
@ -76,7 +119,7 @@ export async function addE2e(
|
||||
targets: {},
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
return configurationGenerator(tree, {
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
project: options.e2eProjectName,
|
||||
skipFormat: true,
|
||||
skipPackageJson: options.skipPackageJson,
|
||||
@ -91,6 +134,43 @@ export async function addE2e(
|
||||
rootProject: options.rootProject,
|
||||
addPlugin: options.addPlugin,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/playwright/plugin'
|
||||
: p.plugin === '@nx/playwright/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasNxBuildPlugin) {
|
||||
const configFile =
|
||||
options.bundler === 'webpack'
|
||||
? 'webpack.config.js'
|
||||
: options.bundler === 'vite'
|
||||
? `vite.config.${options.js ? 'js' : 'ts'}`
|
||||
: 'webpack.config.js';
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
`@nx/${options.bundler}/plugin`,
|
||||
joinPathFragments(options.appProjectRoot, configFile)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/playwright/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
case 'none':
|
||||
default:
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||
|
||||
import { joinPathFragments, readJson, type Tree } from '@nx/devkit';
|
||||
import { joinPathFragments, readJson, readNxJson, type Tree } from '@nx/devkit';
|
||||
import * as devkit from '@nx/devkit';
|
||||
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
@ -129,6 +129,14 @@ describe('Remix Application', () => {
|
||||
expectTargetsToBeCorrect(tree, '.');
|
||||
|
||||
expect(tree.read('e2e/cypress.config.ts', 'utf-8')).toMatchSnapshot();
|
||||
expect(readNxJson(tree).targetDefaults['e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
@ -148,6 +156,14 @@ describe('Remix Application', () => {
|
||||
expectTargetsToBeCorrect(tree, '.');
|
||||
|
||||
expect(tree.read('e2e/playwright.config.ts', 'utf-8')).toMatchSnapshot();
|
||||
expect(readNxJson(tree).targetDefaults['e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -33,7 +33,7 @@ import { NxRemixGeneratorSchema } from './schema';
|
||||
import { updateDependencies } from '../utils/update-dependencies';
|
||||
import initGenerator from '../init/init';
|
||||
import { initGenerator as jsInitGenerator } from '@nx/js';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||
import { updateJestTestMatch } from '../../utils/testing-config-utils';
|
||||
|
||||
|
||||
@ -6,11 +6,19 @@ import {
|
||||
updateProjectConfiguration,
|
||||
ensurePackage,
|
||||
getPackageManagerCommand,
|
||||
readNxJson,
|
||||
} from '@nx/devkit';
|
||||
import { type NormalizedSchema } from './normalize-options';
|
||||
import { getPackageVersion } from '../../../utils/versions';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function addE2E(tree: Tree, options: NormalizedSchema) {
|
||||
const hasRemixPlugin = readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/remix/plugin'
|
||||
: p.plugin === '@nx/remix/plugin'
|
||||
);
|
||||
if (options.e2eTestRunner === 'cypress') {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/cypress')
|
||||
@ -25,7 +33,7 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
|
||||
return await configurationGenerator(tree, {
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
project: options.e2eProjectName,
|
||||
directory: 'src',
|
||||
skipFormat: true,
|
||||
@ -33,6 +41,40 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
|
||||
baseUrl: options.e2eWebServerAddress,
|
||||
addPlugin: options.addPlugin,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/cypress/plugin'
|
||||
: p.plugin === '@nx/cypress/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasRemixPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
`@nx/remix/plugin`,
|
||||
joinPathFragments(options.projectRoot, 'remix.config.js')
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(
|
||||
options.e2eProjectRoot,
|
||||
`cypress.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
} else if (options.e2eTestRunner === 'playwright') {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
typeof import('@nx/playwright')
|
||||
@ -47,7 +89,7 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
|
||||
return configurationGenerator(tree, {
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
project: options.e2eProjectName,
|
||||
skipFormat: true,
|
||||
skipPackageJson: false,
|
||||
@ -62,6 +104,37 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
|
||||
rootProject: options.rootProject,
|
||||
addPlugin: options.addPlugin,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/playwright/plugin'
|
||||
: p.plugin === '@nx/playwright/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasRemixPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
`@nx/remix/plugin`,
|
||||
joinPathFragments(options.projectRoot, 'remix.config.js')
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/playwright/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
} else {
|
||||
return () => {};
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@ import { getImportPath } from '@nx/js/src/utils/get-import-path';
|
||||
import { rollupInitGenerator } from '../init/init';
|
||||
import { RollupExecutorOptions } from '../../executors/rollup/schema';
|
||||
import { RollupProjectSchema } from './schema';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||
import { hasPlugin } from '../../utils/has-plugin';
|
||||
import { RollupWithNxPluginOptions } from '../../plugins/with-nx/with-nx-options';
|
||||
|
||||
@ -14,7 +14,7 @@ import { VitePreviewServerExecutorOptions } from '../executors/preview-server/sc
|
||||
import { VitestExecutorOptions } from '../executors/test/schema';
|
||||
import { ViteConfigurationGeneratorSchema } from '../generators/configuration/schema';
|
||||
import { ensureViteConfigIsCorrect } from './vite-config-edit-utils';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export type Target = 'build' | 'serve' | 'test' | 'preview';
|
||||
export type TargetFlags = Partial<Record<Target, boolean>>;
|
||||
|
||||
@ -40,6 +40,7 @@ describe('application generator', () => {
|
||||
...options,
|
||||
unitTestRunner: 'vitest',
|
||||
e2eTestRunner: 'playwright',
|
||||
addPlugin: true,
|
||||
});
|
||||
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||
expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||
@ -49,6 +50,14 @@ describe('application generator', () => {
|
||||
tree.read('test-e2e/playwright.config.ts', 'utf-8')
|
||||
).toMatchSnapshot();
|
||||
expect(listFiles(tree)).toMatchSnapshot();
|
||||
expect(readNxJson(tree).targetDefaults['e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('should set up project correctly for cypress', async () => {
|
||||
|
||||
@ -10,6 +10,8 @@ import { webStaticServeGenerator } from '@nx/web';
|
||||
|
||||
import { nxVersion } from '../../../utils/versions';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
|
||||
export async function addE2e(
|
||||
tree: Tree,
|
||||
@ -52,7 +54,7 @@ export async function addE2e(
|
||||
tags: [],
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
return await configurationGenerator(tree, {
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
...options,
|
||||
project: options.e2eProjectName,
|
||||
directory: 'src',
|
||||
@ -70,6 +72,43 @@ export async function addE2e(
|
||||
ciWebServerCommand: `nx run ${options.projectName}:${e2eCiWebServerTarget}`,
|
||||
ciBaseUrl: 'http://localhost:4300',
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/cypress/plugin'
|
||||
: p.plugin === '@nx/cypress/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
`@nx/vite/plugin`,
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
`vite.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/cypress/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(
|
||||
options.e2eProjectRoot,
|
||||
`cypress.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
case 'playwright': {
|
||||
const { configurationGenerator } = ensurePackage<
|
||||
@ -82,7 +121,7 @@ export async function addE2e(
|
||||
targets: {},
|
||||
implicitDependencies: [options.projectName],
|
||||
});
|
||||
return configurationGenerator(tree, {
|
||||
const e2eTask = await configurationGenerator(tree, {
|
||||
...options,
|
||||
project: options.e2eProjectName,
|
||||
skipFormat: true,
|
||||
@ -96,6 +135,40 @@ export async function addE2e(
|
||||
}:${e2eCiWebServerTarget}`,
|
||||
webServerAddress: 'http://localhost:4300',
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(tree).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/playwright/plugin'
|
||||
: p.plugin === '@nx/playwright/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
tree,
|
||||
`@nx/vite/plugin`,
|
||||
joinPathFragments(
|
||||
options.appProjectRoot,
|
||||
`vite.config.${options.js ? 'js' : 'ts'}`
|
||||
)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
tree,
|
||||
'@nx/playwright/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
return e2eTask;
|
||||
}
|
||||
case 'none':
|
||||
default:
|
||||
|
||||
@ -43,6 +43,14 @@ describe('app', () => {
|
||||
expect(readProjectConfiguration(tree, 'my-app-e2e').root).toEqual(
|
||||
'my-app-e2e'
|
||||
);
|
||||
expect(readNxJson(tree).targetDefaults['e2e-ci--**/*'])
|
||||
.toMatchInlineSnapshot(`
|
||||
{
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
],
|
||||
}
|
||||
`);
|
||||
}, 60_000);
|
||||
|
||||
it('should update tags and implicit dependencies', async () => {
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
joinPathFragments,
|
||||
names,
|
||||
offsetFromRoot,
|
||||
type PluginConfiguration,
|
||||
readNxJson,
|
||||
readProjectConfiguration,
|
||||
runTasksInSerial,
|
||||
@ -37,12 +38,15 @@ import { webInitGenerator } from '../init/init';
|
||||
import { Schema } from './schema';
|
||||
import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope';
|
||||
import { hasWebpackPlugin } from '../../utils/has-webpack-plugin';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import {
|
||||
addBuildTargetDefaults,
|
||||
addE2eCiTargetDefaults,
|
||||
} from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||
import { VitePluginOptions } from '@nx/vite/src/plugins/plugin';
|
||||
import { WebpackPluginOptions } from '@nx/webpack/src/plugins/plugin';
|
||||
import { hasVitePlugin } from '../../utils/has-vite-plugin';
|
||||
import staticServeConfiguration from '../static-serve/static-serve-configuration';
|
||||
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
|
||||
|
||||
interface NormalizedSchema extends Schema {
|
||||
projectName: string;
|
||||
@ -368,10 +372,20 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
||||
tasks.push(lintTask);
|
||||
}
|
||||
|
||||
const hasNxBuildPlugin =
|
||||
(options.bundler === 'webpack' && hasWebpackPlugin(host)) ||
|
||||
(options.bundler === 'vite' && hasVitePlugin(host));
|
||||
if (!hasNxBuildPlugin) {
|
||||
const nxJson = readNxJson(host);
|
||||
let hasPlugin: PluginConfiguration | undefined;
|
||||
let buildPlugin: string;
|
||||
let buildConfigFile: string;
|
||||
if (options.bundler === 'webpack' || options.bundler === 'vite') {
|
||||
buildPlugin = `@nx/${options.bundler}/plugin`;
|
||||
buildConfigFile =
|
||||
options.bundler === 'webpack' ? 'webpack.config.js' : `vite.config.ts`;
|
||||
hasPlugin = nxJson.plugins?.find((p) =>
|
||||
typeof p === 'string' ? p === buildPlugin : p.plugin === buildPlugin
|
||||
);
|
||||
}
|
||||
|
||||
if (!hasPlugin) {
|
||||
await staticServeConfiguration(host, {
|
||||
buildTarget: `${options.projectName}:build`,
|
||||
spa: true,
|
||||
@ -396,17 +410,47 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
||||
baseUrl: options.e2eWebServerAddress,
|
||||
directory: 'src',
|
||||
skipFormat: true,
|
||||
webServerCommands: hasNxBuildPlugin
|
||||
webServerCommands: hasPlugin
|
||||
? {
|
||||
default: `nx run ${options.projectName}:${options.e2eWebServerTarget}`,
|
||||
production: `nx run ${options.projectName}:preview`,
|
||||
}
|
||||
: undefined,
|
||||
ciWebServerCommand: hasNxBuildPlugin
|
||||
ciWebServerCommand: hasPlugin
|
||||
? `nx run ${options.projectName}:${options.e2eCiWebServerTarget}`
|
||||
: undefined,
|
||||
ciBaseUrl: options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(host).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/cypress/plugin'
|
||||
: p.plugin === '@nx/cypress/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
host,
|
||||
buildPlugin,
|
||||
joinPathFragments(options.appProjectRoot, buildConfigFile)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
host,
|
||||
'@nx/cypress/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `cypress.config.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
tasks.push(cypressTask);
|
||||
} else if (options.e2eTestRunner === 'playwright') {
|
||||
const { configurationGenerator: playwrightConfigGenerator } = ensurePackage<
|
||||
@ -434,6 +478,36 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
||||
webServerAddress: options.e2eCiBaseUrl,
|
||||
addPlugin: options.addPlugin,
|
||||
});
|
||||
|
||||
if (
|
||||
options.addPlugin ||
|
||||
readNxJson(host).plugins?.find((p) =>
|
||||
typeof p === 'string'
|
||||
? p === '@nx/playwright/plugin'
|
||||
: p.plugin === '@nx/playwright/plugin'
|
||||
)
|
||||
) {
|
||||
let buildTarget = '^build';
|
||||
if (hasPlugin) {
|
||||
const matchingPlugin = await findPluginForConfigFile(
|
||||
host,
|
||||
buildPlugin,
|
||||
joinPathFragments(options.appProjectRoot, buildConfigFile)
|
||||
);
|
||||
if (matchingPlugin && typeof matchingPlugin !== 'string') {
|
||||
buildTarget = `^${
|
||||
(matchingPlugin.options as any)?.buildTargetName ?? 'build'
|
||||
}`;
|
||||
}
|
||||
}
|
||||
await addE2eCiTargetDefaults(
|
||||
host,
|
||||
'@nx/playwright/plugin',
|
||||
buildTarget,
|
||||
joinPathFragments(options.e2eProjectRoot, `playwright.config.ts`)
|
||||
);
|
||||
}
|
||||
|
||||
tasks.push(playwrightTask);
|
||||
}
|
||||
if (options.unitTestRunner === 'jest') {
|
||||
|
||||
@ -15,7 +15,7 @@ import { webpackInitGenerator } from '../init/init';
|
||||
import { ConfigurationGeneratorSchema } from './schema';
|
||||
import { WebpackExecutorOptions } from '../../executors/webpack/schema';
|
||||
import { hasPlugin } from '../../utils/has-plugin';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-target-defaults';
|
||||
import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
|
||||
import { ensureDependencies } from '../../utils/ensure-dependencies';
|
||||
|
||||
export function configurationGenerator(
|
||||
|
||||
@ -115,6 +115,7 @@
|
||||
"@nx/nx-dev/ui-theme": ["nx-dev/ui-theme/src/index.ts"],
|
||||
"@nx/nx-dev/util-ai": ["nx-dev/util-ai/src/index.ts"],
|
||||
"@nx/playwright": ["packages/playwright/index.ts"],
|
||||
"@nx/playwright/*": ["packages/playwright/*"],
|
||||
"@nx/plugin": ["packages/plugin"],
|
||||
"@nx/plugin/*": ["packages/plugin/*"],
|
||||
"@nx/react": ["packages/react"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user