fix(testing): application generators should accurately configure e2e projects (#27453)

- feat(devkit): add util for determining the e2e web server info
- feat(vite): add util for determining the e2e web server info
- feat(webpack): add util for determining the e2e web server info
- fix(webpack): allow port override
- fix(devkit): e2e web server info util should handle target defaults
- feat(webpack): export the e2e web server info utils
- fix(vite): rename util
- fix(devkit): util should determine the devTarget for cypress
- fix(react): improve accuracy of e2e project generation

<!-- 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 -->
The logic for finding the correct targets and web addresses to use when
setting up e2e projects is flawed and missing some key considerations.


## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The logic is accurate and usage is simplified across plugins 

Projects:
- [x] Angular
- [x] Expo
- [x] Next
- [x] Nuxt
- [x] Vue
- [x] Web
- [x] Remix
- [x] React
- [x] React Native


## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Colum Ferry 2024-08-27 15:00:43 +01:00 committed by GitHub
parent 57f3701372
commit 320d9f223f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 1220 additions and 454 deletions

View File

@ -76,7 +76,7 @@
"description": "Adds the specified e2e test runner", "description": "Adds the specified e2e test runner",
"type": "string", "type": "string",
"enum": ["playwright", "cypress", "detox", "none"], "enum": ["playwright", "cypress", "detox", "none"],
"default": "playwright" "default": "none"
}, },
"standaloneConfig": { "standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.", "description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",

View File

@ -35,14 +35,6 @@ describe('file-server', () => {
}, },
} }
); );
runCLI(
`generate @nx/web:static-config --buildTarget=${ngAppName}:build --outputPath=dist/apps/${ngAppName}/browser --no-interactive`,
{
env: {
NX_ADD_PLUGINS: 'false',
},
}
);
runCLI( runCLI(
`generate @nx/web:static-config --buildTarget=${reactAppName}:build --targetName=custom-serve-static --no-interactive`, `generate @nx/web:static-config --buildTarget=${reactAppName}:build --targetName=custom-serve-static --no-interactive`,
{ {

View File

@ -1,4 +1,4 @@
import type { Tree } from '@nx/devkit'; import { Tree } from '@nx/devkit';
import { import {
addDependenciesToPackageJson, addDependenciesToPackageJson,
addProjectConfiguration, addProjectConfiguration,
@ -13,6 +13,7 @@ import { nxVersion } from '../../../utils/versions';
import { getInstalledAngularVersionInfo } from '../../utils/version-utils'; import { getInstalledAngularVersionInfo } from '../../utils/version-utils';
import type { NormalizedSchema } from './normalized-schema'; import type { NormalizedSchema } from './normalized-schema';
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function addE2e(tree: Tree, options: NormalizedSchema) { export async function addE2e(tree: Tree, options: NormalizedSchema) {
// since e2e are separate projects, default to adding plugins // since e2e are separate projects, default to adding plugins
@ -21,12 +22,19 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
process.env.NX_ADD_PLUGINS !== 'false' && process.env.NX_ADD_PLUGINS !== 'false' &&
nxJson.useInferencePlugins !== false; nxJson.useInferencePlugins !== false;
const e2eWebServerInfo = getAngularE2EWebServerInfo(
tree,
options.name,
options.port
);
// TODO: This can call `@nx/web:static-config` generator when ready
addFileServerTarget(tree, options, 'serve-static', e2eWebServerInfo.e2ePort);
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
// TODO: This can call `@nx/web:static-config` generator when ready
addFileServerTarget(tree, options, 'serve-static');
addProjectConfiguration(tree, options.e2eProjectName, { addProjectConfiguration(tree, options.e2eProjectName, {
projectType: 'application', projectType: 'application',
root: options.e2eProjectRoot, root: options.e2eProjectRoot,
@ -41,8 +49,14 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
linter: options.linter, linter: options.linter,
skipPackageJson: options.skipPackageJson, skipPackageJson: options.skipPackageJson,
skipFormat: true, skipFormat: true,
devServerTarget: `${options.name}:${options.e2eWebServerTarget}:development`, devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
baseUrl: options.e2eWebServerAddress, baseUrl: e2eWebServerInfo.e2eWebServerAddress,
webServerCommands: {
default: e2eWebServerInfo.e2eWebServerCommand,
production: e2eWebServerInfo.e2eCiWebServerCommand,
},
ciWebServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
rootProject: options.rootProject, rootProject: options.rootProject,
addPlugin, addPlugin,
}); });
@ -73,10 +87,8 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx ${ webServerCommand: e2eWebServerInfo.e2eWebServerCommand,
options.e2eWebServerTarget webServerAddress: e2eWebServerInfo.e2eWebServerAddress,
} ${options.name}`,
webServerAddress: options.e2eWebServerAddress,
rootProject: options.rootProject, rootProject: options.rootProject,
addPlugin, addPlugin,
}); });
@ -94,7 +106,8 @@ export async function addE2e(tree: Tree, options: NormalizedSchema) {
function addFileServerTarget( function addFileServerTarget(
tree: Tree, tree: Tree,
options: NormalizedSchema, options: NormalizedSchema,
targetName: string targetName: string,
e2ePort: number
) { ) {
if (!options.skipPackageJson) { if (!options.skipPackageJson) {
addDependenciesToPackageJson(tree, {}, { '@nx/web': nxVersion }); addDependenciesToPackageJson(tree, {}, { '@nx/web': nxVersion });
@ -109,7 +122,7 @@ function addFileServerTarget(
executor: '@nx/web:file-server', executor: '@nx/web:file-server',
options: { options: {
buildTarget: `${options.name}:build`, buildTarget: `${options.name}:build`,
port: options.e2ePort, port: e2ePort,
staticFilePath: isUsingApplicationBuilder staticFilePath: isUsingApplicationBuilder
? joinPathFragments(options.outputPath, 'browser') ? joinPathFragments(options.outputPath, 'browser')
: undefined, : undefined,
@ -118,3 +131,32 @@ function addFileServerTarget(
}; };
updateProjectConfiguration(tree, options.name, projectConfig); updateProjectConfiguration(tree, options.name, projectConfig);
} }
function getAngularE2EWebServerInfo(
tree: Tree,
projectName: string,
portOverride: number
): E2EWebServerDetails & { e2ePort: number } {
const nxJson = readNxJson(tree);
let e2ePort = portOverride ?? 4200;
if (
nxJson.targetDefaults?.['serve'] &&
(nxJson.targetDefaults?.['serve'].options?.port ||
nxJson.targetDefaults?.['serve'].options?.env?.PORT)
) {
e2ePort =
nxJson.targetDefaults?.['serve'].options?.port ||
nxJson.targetDefaults?.['serve'].options?.env?.PORT;
}
const pm = getPackageManagerCommand();
return {
e2eCiBaseUrl: 'http://localhost:4200',
e2eCiWebServerCommand: `${pm.exec} nx run ${projectName}:serve-static`,
e2eWebServerCommand: `${pm.exec} nx run ${projectName}:serve`,
e2eWebServerAddress: `http://localhost:${e2ePort}`,
e2eDevServerTarget: `${projectName}:serve`,
e2ePort,
};
}

View File

@ -26,21 +26,9 @@ export async function normalizeOptions(
options.projectNameAndRootFormat = projectNameAndRootFormat; options.projectNameAndRootFormat = projectNameAndRootFormat;
const nxJson = readNxJson(host); const nxJson = readNxJson(host);
let e2eWebServerTarget = 'serve';
let e2ePort = options.port ?? 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
(nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT)
) {
e2ePort =
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
@ -72,9 +60,6 @@ export async function normalizeOptions(
appProjectSourceRoot: `${appProjectRoot}/src`, appProjectSourceRoot: `${appProjectRoot}/src`,
e2eProjectRoot, e2eProjectRoot,
e2eProjectName, e2eProjectName,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
parsedTags, parsedTags,
bundler, bundler,
outputPath: joinPathFragments( outputPath: joinPathFragments(

View File

@ -12,9 +12,6 @@ export interface NormalizedSchema extends Schema {
appProjectSourceRoot: string; appProjectSourceRoot: string;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
parsedTags: string[]; parsedTags: string[];
outputPath: string; outputPath: string;
} }

View File

@ -0,0 +1,248 @@
import { createTreeWithEmptyWorkspace } from 'nx/src/devkit-testing-exports';
import { type Tree, readNxJson, updateNxJson } from 'nx/src/devkit-exports';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { getE2EWebServerInfo } from './e2e-web-server-info-utils';
describe('getE2EWebServerInfo', () => {
let tree: Tree;
let tempFs: TempFs;
beforeEach(() => {
tempFs = new TempFs('e2e-webserver-info');
tree = createTreeWithEmptyWorkspace();
tree.root = tempFs.tempDir;
tree.write(`app/vite.config.ts`, ``);
tempFs.createFileSync(`app/vite.config.ts`, ``);
});
afterEach(() => {
tempFs.cleanup();
jest.resetModules();
});
it('should use the default values when no plugin is registered and plugins are not being used', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getE2EWebServerInfo(
tree,
'app',
{
plugin: '@nx/vite/plugin',
configFilePath: 'app/vite.config.ts',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'previewTargetName',
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'preview',
defaultE2EWebServerAddress: 'http://localhost:4200',
defaultE2ECiBaseUrl: 'http://localhost:4300',
defaultE2EPort: 4200,
},
false
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:preview",
"e2eDevServerTarget": "app:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:serve",
}
`);
});
it('should use the default values of the plugin when the plugin is just a string', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = ['@nx/vite/plugin'];
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getE2EWebServerInfo(
tree,
'app',
{
plugin: '@nx/vite/plugin',
configFilePath: 'app/vite.config.ts',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'previewTargetName',
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'preview',
defaultE2EWebServerAddress: 'http://localhost:4200',
defaultE2ECiBaseUrl: 'http://localhost:4300',
defaultE2EPort: 4200,
},
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:preview",
"e2eDevServerTarget": "app:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:serve",
}
`);
});
it('should use the values of the registered plugin when there is no includes or excludes defined', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
serveTargetName: 'vite:serve',
previewTargetName: 'vite:preview',
},
});
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getE2EWebServerInfo(
tree,
'app',
{
plugin: '@nx/vite/plugin',
configFilePath: 'app/vite.config.ts',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'previewTargetName',
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'preview',
defaultE2EWebServerAddress: 'http://localhost:4200',
defaultE2ECiBaseUrl: 'http://localhost:4300',
defaultE2EPort: 4200,
},
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:vite:preview",
"e2eDevServerTarget": "app:vite:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:vite:serve",
}
`);
});
it('should handle targetDefaults', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
serveTargetName: 'vite:serve',
previewTargetName: 'vite:preview',
},
});
nxJson.targetDefaults ??= {};
nxJson.targetDefaults['vite:serve'] = {
options: {
port: 4400,
},
};
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getE2EWebServerInfo(
tree,
'app',
{
plugin: '@nx/vite/plugin',
configFilePath: 'app/vite.config.ts',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'previewTargetName',
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'preview',
defaultE2EWebServerAddress: 'http://localhost:4200',
defaultE2ECiBaseUrl: 'http://localhost:4300',
defaultE2EPort: 4200,
},
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:vite:preview",
"e2eDevServerTarget": "app:vite:serve",
"e2eWebServerAddress": "http://localhost:4400",
"e2eWebServerCommand": "npx nx run app:vite:serve",
}
`);
});
it('should use the values of the correct registered plugin when there are includes or excludes defined', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
serveTargetName: 'vite:serve',
previewTargetName: 'vite:preview',
},
include: ['libs/**'],
});
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
serveTargetName: 'vite-serve',
previewTargetName: 'vite-preview',
},
include: ['app/**'],
});
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getE2EWebServerInfo(
tree,
'app',
{
plugin: '@nx/vite/plugin',
configFilePath: 'app/vite.config.ts',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'previewTargetName',
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'preview',
defaultE2EWebServerAddress: 'http://localhost:4200',
defaultE2ECiBaseUrl: 'http://localhost:4300',
defaultE2EPort: 4400,
},
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:vite-preview",
"e2eDevServerTarget": "app:vite-serve",
"e2eWebServerAddress": "http://localhost:4400",
"e2eWebServerCommand": "npx nx run app:vite-serve",
}
`);
});
});

View File

@ -0,0 +1,126 @@
import {
type Tree,
getPackageManagerCommand,
readNxJson,
} from 'nx/src/devkit-exports';
import type { PackageManagerCommands } from 'nx/src/utils/package-manager';
import { findPluginForConfigFile } from '../utils/find-plugin-for-config-file';
interface E2EWebServerDefaultValues {
defaultServeTargetName: string;
defaultServeStaticTargetName: string;
defaultE2EWebServerAddress: string;
defaultE2ECiBaseUrl: string;
defaultE2EPort: number;
}
interface E2EWebServerPluginOptions {
plugin: string;
configFilePath: string;
serveTargetName: string;
serveStaticTargetName: string;
}
export interface E2EWebServerDetails {
e2eWebServerAddress: string;
e2eWebServerCommand: string;
e2eCiWebServerCommand: string;
e2eCiBaseUrl: string;
e2eDevServerTarget: string;
}
export async function getE2EWebServerInfo(
tree: Tree,
projectName: string,
pluginOptions: E2EWebServerPluginOptions,
defaultValues: E2EWebServerDefaultValues,
isPluginBeingAdded: boolean
): Promise<E2EWebServerDetails> {
const pm = getPackageManagerCommand();
if (isPluginBeingAdded) {
return await getE2EWebServerInfoForPlugin(
tree,
projectName,
pluginOptions,
defaultValues,
pm
);
} else {
return {
e2eWebServerAddress: defaultValues.defaultE2EWebServerAddress,
e2eWebServerCommand: `${pm.exec} nx run ${projectName}:${defaultValues.defaultServeTargetName}`,
e2eCiWebServerCommand: `${pm.exec} nx run ${projectName}:${defaultValues.defaultServeStaticTargetName}`,
e2eCiBaseUrl: defaultValues.defaultE2ECiBaseUrl,
e2eDevServerTarget: `${projectName}:${defaultValues.defaultServeTargetName}`,
};
}
}
async function getE2EWebServerInfoForPlugin(
tree: Tree,
projectName: string,
pluginOptions: E2EWebServerPluginOptions,
defaultValues: E2EWebServerDefaultValues,
pm: PackageManagerCommands
): Promise<E2EWebServerDetails> {
const foundPlugin = await findPluginForConfigFile(
tree,
pluginOptions.plugin,
pluginOptions.configFilePath
);
if (
!foundPlugin ||
typeof foundPlugin === 'string' ||
!foundPlugin?.options
) {
return {
e2eWebServerAddress: defaultValues.defaultE2EWebServerAddress,
e2eWebServerCommand: `${pm.exec} nx run ${projectName}:${defaultValues.defaultServeTargetName}`,
e2eCiWebServerCommand: `${pm.exec} nx run ${projectName}:${defaultValues.defaultServeStaticTargetName}`,
e2eCiBaseUrl: defaultValues.defaultE2ECiBaseUrl,
e2eDevServerTarget: `${projectName}:${defaultValues.defaultServeTargetName}`,
};
}
const nxJson = readNxJson(tree);
let e2ePort = defaultValues.defaultE2EPort ?? 4200;
if (
nxJson.targetDefaults?.[
foundPlugin.options[pluginOptions.serveTargetName] ??
defaultValues.defaultServeTargetName
] &&
nxJson.targetDefaults?.[
foundPlugin.options[pluginOptions.serveTargetName] ??
defaultValues.defaultServeTargetName
].options?.port
) {
e2ePort =
nxJson.targetDefaults?.[
foundPlugin.options[pluginOptions.serveTargetName] ??
defaultValues.defaultServeTargetName
].options?.port;
}
const e2eWebServerAddress = defaultValues.defaultE2EWebServerAddress.replace(
/:\d+/,
`:${e2ePort}`
);
return {
e2eWebServerAddress,
e2eWebServerCommand: `${pm.exec} nx run ${projectName}:${
foundPlugin.options[pluginOptions.serveTargetName] ??
defaultValues.defaultServeTargetName
}`,
e2eCiWebServerCommand: `${pm.exec} nx run ${projectName}:${
foundPlugin.options[pluginOptions.serveStaticTargetName] ??
defaultValues.defaultServeStaticTargetName
}`,
e2eCiBaseUrl: defaultValues.defaultE2ECiBaseUrl,
e2eDevServerTarget: `${projectName}:${
foundPlugin.options[pluginOptions.serveTargetName] ??
defaultValues.defaultServeTargetName
}`,
};
}

View File

@ -1,4 +1,4 @@
import type { GeneratorCallback, Tree } from '@nx/devkit'; import { GeneratorCallback, Tree } from '@nx/devkit';
import { import {
addProjectConfiguration, addProjectConfiguration,
ensurePackage, ensurePackage,
@ -13,21 +13,29 @@ import { hasExpoPlugin } from '../../../utils/has-expo-plugin';
import { NormalizedSchema } from './normalize-options'; import { NormalizedSchema } from './normalize-options';
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function addE2e( export async function addE2e(
tree: Tree, tree: Tree,
options: NormalizedSchema options: NormalizedSchema
): Promise<GeneratorCallback> { ): Promise<GeneratorCallback> {
const hasPlugin = hasExpoPlugin(tree); const hasPlugin = hasExpoPlugin(tree);
if (!hasPlugin) {
await webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:export`,
targetName: 'serve-static',
});
}
const e2eWebServerInfo = await getExpoE2EWebServerInfo(
tree,
options.projectName,
joinPathFragments(options.appProjectRoot, 'app.json'),
options.addPlugin
);
switch (options.e2eTestRunner) { switch (options.e2eTestRunner) {
case 'cypress': { case 'cypress': {
if (!hasPlugin) {
await webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:export`,
targetName: 'serve-static',
});
}
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
>('@nx/cypress', nxVersion); >('@nx/cypress', nxVersion);
@ -48,12 +56,14 @@ export async function addE2e(
// the name and root are already normalized, instruct the generator to use them as is // the name and root are already normalized, instruct the generator to use them as is
bundler: 'none', bundler: 'none',
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`, devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
port: options.e2ePort, baseUrl: e2eWebServerInfo.e2eWebServerAddress,
baseUrl: options.e2eWebServerAddress, ciWebServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
ciWebServerCommand: hasPlugin webServerCommands: {
? `nx run ${options.projectName}:serve-static` default: e2eWebServerInfo.e2eWebServerCommand,
: undefined, production: e2eWebServerInfo.e2eCiWebServerCommand,
},
ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
jsx: true, jsx: true,
rootProject: options.rootProject, rootProject: options.rootProject,
}); });
@ -112,10 +122,8 @@ export async function addE2e(
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx ${ webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
options.e2eWebServerTarget webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
} ${options.name}`,
webServerAddress: options.e2eWebServerAddress,
rootProject: options.rootProject, rootProject: options.rootProject,
addPlugin: options.addPlugin, addPlugin: options.addPlugin,
}); });
@ -172,3 +180,39 @@ export async function addE2e(
return () => {}; return () => {};
} }
} }
async function getExpoE2EWebServerInfo(
tree: Tree,
projectName: string,
configFilePath: string,
isPluginBeingAdded: boolean
) {
const nxJson = readNxJson(tree);
let e2ePort = isPluginBeingAdded ? 8081 : 4200;
if (
nxJson.targetDefaults?.['serve'] &&
nxJson.targetDefaults?.['serve'].options?.port
) {
e2ePort = nxJson.targetDefaults?.['serve'].options?.port;
}
return getE2EWebServerInfo(
tree,
projectName,
{
plugin: '@nx/expo/plugin',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'serveTargetName',
configFilePath,
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'serve-static',
defaultE2EWebServerAddress: `http://localhost:${e2ePort}`,
defaultE2ECiBaseUrl: 'http://localhost:4200',
defaultE2EPort: e2ePort,
},
isPluginBeingAdded
);
}

View File

@ -40,9 +40,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema); } as NormalizedSchema);
}); });
@ -75,9 +72,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'myApp-e2e', e2eProjectName: 'myApp-e2e',
e2eProjectRoot: 'myApp-e2e', e2eProjectRoot: 'myApp-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema); } as NormalizedSchema);
}); });
@ -112,9 +106,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'directory-e2e', e2eProjectRoot: 'directory-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema); } as NormalizedSchema);
}); });
@ -147,9 +138,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'directory/my-app-e2e', e2eProjectRoot: 'directory/my-app-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema); } as NormalizedSchema);
}); });
@ -183,9 +171,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e',
e2ePort: 8081,
e2eWebServerAddress: 'http://localhost:8081',
e2eWebServerTarget: 'serve',
} as NormalizedSchema); } as NormalizedSchema);
}); });
}); });

View File

@ -12,9 +12,6 @@ export interface NormalizedSchema extends Schema {
rootProject: boolean; rootProject: boolean;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
} }
export async function normalizeOptions( export async function normalizeOptions(
@ -46,38 +43,13 @@ export async function normalizeOptions(
: []; : [];
const rootProject = appProjectRoot === '.'; const rootProject = appProjectRoot === '.';
let e2eWebServerTarget = 'serve';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'object' &&
plugin.plugin === '@nx/expo/plugin' &&
(plugin.options as ExpoPluginOptions).serveTargetName
) {
e2eWebServerTarget = (plugin.options as ExpoPluginOptions)
.serveTargetName;
}
}
}
}
let e2ePort = options.addPlugin ? 8081 : 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[e2eWebServerTarget].options.port;
}
const e2eProjectName = rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectName = rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`; const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
return { return {
...options, ...options,
unitTestRunner: options.unitTestRunner || 'jest', unitTestRunner: options.unitTestRunner || 'jest',
e2eTestRunner: options.e2eTestRunner, e2eTestRunner: options.e2eTestRunner || 'none',
name: projectNames.projectSimpleName, name: projectNames.projectSimpleName,
className, className,
lowerCaseName: className.toLowerCase(), lowerCaseName: className.toLowerCase(),
@ -88,8 +60,5 @@ export async function normalizeOptions(
rootProject, rootProject,
e2eProjectName, e2eProjectName,
e2eProjectRoot, e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
}; };
} }

View File

@ -76,7 +76,7 @@
"description": "Adds the specified e2e test runner", "description": "Adds the specified e2e test runner",
"type": "string", "type": "string",
"enum": ["playwright", "cypress", "detox", "none"], "enum": ["playwright", "cypress", "detox", "none"],
"default": "playwright" "default": "none"
}, },
"standaloneConfig": { "standaloneConfig": {
"description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.", "description": "Split the project configuration into `<projectRoot>/project.json` rather than including it inside `workspace.json`.",

View File

@ -13,6 +13,7 @@ import { NormalizedSchema } from './normalize-options';
import { webStaticServeGenerator } from '@nx/web'; import { webStaticServeGenerator } from '@nx/web';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function addE2e(host: Tree, options: NormalizedSchema) { export async function addE2e(host: Tree, options: NormalizedSchema) {
const nxJson = readNxJson(host); const nxJson = readNxJson(host);
@ -22,6 +23,13 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
: p.plugin === '@nx/next/plugin' : p.plugin === '@nx/next/plugin'
); );
const e2eWebServerInfo = await getNextE2EWebServerInfo(
host,
options.projectName,
joinPathFragments(options.appProjectRoot, 'next.config.js'),
options.addPlugin
);
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
@ -50,17 +58,16 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
project: options.e2eProjectName, project: options.e2eProjectName,
directory: 'src', directory: 'src',
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`, devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
baseUrl: options.e2eWebServerAddress, baseUrl: e2eWebServerInfo.e2eWebServerAddress,
jsx: true, jsx: true,
webServerCommands: hasPlugin webServerCommands: hasPlugin
? { ? {
default: `nx run ${options.projectName}:${options.e2eWebServerTarget}`, default: e2eWebServerInfo.e2eWebServerCommand,
} }
: undefined, : undefined,
ciWebServerCommand: hasPlugin ciWebServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
? `nx run ${options.projectName}:serve-static` ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
: undefined,
}); });
if ( if (
@ -116,10 +123,8 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
webServerAddress: `http://127.0.0.1:${options.e2ePort}`, webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
webServerCommand: `${getPackageManagerCommand().exec} nx ${ webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
options.e2eWebServerTarget
} ${options.projectName}`,
addPlugin: options.addPlugin, addPlugin: options.addPlugin,
}); });
@ -156,3 +161,41 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
} }
return () => {}; return () => {};
} }
async function getNextE2EWebServerInfo(
tree: Tree,
projectName: string,
configFilePath: string,
isPluginBeingAdded: boolean
) {
const nxJson = readNxJson(tree);
let e2ePort = isPluginBeingAdded ? 3000 : 4200;
const defaultServeTarget = isPluginBeingAdded ? 'dev' : 'serve';
if (
nxJson.targetDefaults?.[defaultServeTarget] &&
nxJson.targetDefaults?.[defaultServeTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[defaultServeTarget].options?.port;
}
return getE2EWebServerInfo(
tree,
projectName,
{
plugin: '@nx/next/plugin',
serveTargetName: 'devTargetName',
serveStaticTargetName: 'serveStaticTargetName',
configFilePath,
},
{
defaultServeTargetName: defaultServeTarget,
defaultServeStaticTargetName: 'serve-static',
defaultE2EWebServerAddress: `http://127.0.0.1:${e2ePort}`,
defaultE2ECiBaseUrl: `http://localhost:${e2ePort}`,
defaultE2EPort: e2ePort,
},
isPluginBeingAdded
);
}

View File

@ -21,9 +21,6 @@ describe('updateEslint', () => {
unitTestRunner: 'jest', unitTestRunner: 'jest',
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e',
e2ePort: 3000,
e2eWebServerTarget: 'start',
e2eWebServerAddress: 'http://localhost:4200',
outputPath: 'dist/my-app', outputPath: 'dist/my-app',
name: 'my-app', name: 'my-app',
parsedTags: [], parsedTags: [],

View File

@ -11,9 +11,6 @@ export interface NormalizedSchema extends Schema {
outputPath: string; outputPath: string;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
parsedTags: string[]; parsedTags: string[];
fileName: string; fileName: string;
styledModule: null | string; styledModule: null | string;
@ -46,36 +43,8 @@ export async function normalizeOptions(
options.addPlugin ??= addPlugin; options.addPlugin ??= addPlugin;
let e2eWebServerTarget = options.addPlugin ? 'start' : 'serve';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'object' &&
plugin.plugin === '@nx/next/plugin' &&
(plugin.options as NextPluginOptions).startTargetName
) {
e2eWebServerTarget = (plugin.options as NextPluginOptions)
.startTargetName;
}
}
}
}
let e2ePort = options.addPlugin ? 3000 : 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
(nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT)
) {
e2ePort =
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const name = names(options.name).fileName; const name = names(options.name).fileName;
@ -107,9 +76,6 @@ export async function normalizeOptions(
appProjectRoot, appProjectRoot,
e2eProjectName, e2eProjectName,
e2eProjectRoot, e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
e2eTestRunner: options.e2eTestRunner || 'playwright', e2eTestRunner: options.e2eTestRunner || 'playwright',
fileName, fileName,
linter: options.linter || Linter.EsLint, linter: options.linter || Linter.EsLint,

View File

@ -3,14 +3,24 @@ import {
ensurePackage, ensurePackage,
getPackageManagerCommand, getPackageManagerCommand,
joinPathFragments, joinPathFragments,
readNxJson,
Tree, Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
import { nxVersion } from '../../../utils/versions'; import { nxVersion } from '../../../utils/versions';
import { NormalizedSchema } from '../schema'; import { NormalizedSchema } from '../schema';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
export async function addE2e(host: Tree, options: NormalizedSchema) { export async function addE2e(host: Tree, options: NormalizedSchema) {
const e2eWebServerInfo = await getNuxtE2EWebServerInfo(
host,
options.projectName,
joinPathFragments(
options.appProjectRoot,
`nuxt.config.${options.js ? 'js' : 'ts'}`
)
);
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
@ -29,14 +39,13 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
directory: 'src', directory: 'src',
bundler: 'vite', bundler: 'vite',
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`, devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
webServerCommands: { webServerCommands: {
default: `${getPackageManagerCommand().exec} nx ${ default: e2eWebServerInfo.e2eWebServerCommand,
options.e2eWebServerTarget
} ${options.projectName}`,
}, },
ciWebServerCommand: `nx run ${options.projectName}:serve-static`, ciWebServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
baseUrl: options.e2eWebServerAddress, baseUrl: e2eWebServerInfo.e2eWebServerAddress,
ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
jsx: true, jsx: true,
addPlugin: true, addPlugin: true,
}); });
@ -85,10 +94,8 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
webServerAddress: options.e2eWebServerAddress, webServerAddress: e2eWebServerInfo.e2eCiWebServerCommand,
webServerCommand: `${getPackageManagerCommand().exec} nx ${ webServerCommand: e2eWebServerInfo.e2eCiBaseUrl,
options.e2eWebServerTarget
} ${options.projectName}`,
addPlugin: true, addPlugin: true,
}); });
@ -118,3 +125,38 @@ export async function addE2e(host: Tree, options: NormalizedSchema) {
} }
return () => {}; return () => {};
} }
async function getNuxtE2EWebServerInfo(
tree: Tree,
projectName: string,
configFilePath: string
) {
const nxJson = readNxJson(tree);
let e2ePort = 4200;
if (
nxJson.targetDefaults?.['serve'] &&
nxJson.targetDefaults?.['serve'].options?.port
) {
e2ePort = nxJson.targetDefaults?.['serve'].options?.port;
}
return getE2EWebServerInfo(
tree,
projectName,
{
plugin: '@nx/nuxt/plugin',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'serveStaticTargetName',
configFilePath,
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'serve-static',
defaultE2EWebServerAddress: `http://localhost:${e2ePort}`,
defaultE2ECiBaseUrl: 'http://localhost:4200',
defaultE2EPort: e2ePort,
},
true
);
}

View File

@ -32,30 +32,9 @@ export async function normalizeOptions(
options.projectNameAndRootFormat = projectNameAndRootFormat; options.projectNameAndRootFormat = projectNameAndRootFormat;
const nxJson = readNxJson(host); const nxJson = readNxJson(host);
let e2eWebServerTarget = 'serve';
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'object' &&
plugin.plugin === '@nx/nuxt/plugin' &&
(plugin.options as NuxtPluginOptions).serveTargetName
) {
e2eWebServerTarget = (plugin.options as NuxtPluginOptions)
.serveTargetName;
}
}
}
let e2ePort = 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[e2eWebServerTarget].options?.port;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
@ -68,9 +47,6 @@ export async function normalizeOptions(
appProjectRoot, appProjectRoot,
e2eProjectName, e2eProjectName,
e2eProjectRoot, e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
parsedTags, parsedTags,
style: options.style ?? 'none', style: options.style ?? 'none',
} as NormalizedSchema; } as NormalizedSchema;

View File

@ -23,8 +23,5 @@ export interface NormalizedSchema extends Schema {
appProjectRoot: string; appProjectRoot: string;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
parsedTags: string[]; parsedTags: string[];
} }

View File

@ -18,8 +18,6 @@ export async function addE2e(
styledModule: null, styledModule: null,
hasStyles: false, hasStyles: false,
unitTestRunner: 'none', unitTestRunner: 'none',
e2eCiWebServerTarget: options.e2eWebServerTarget,
e2eCiBaseUrl: options.e2eWebServerAddress,
}); });
case 'playwright': case 'playwright':
return addE2eReact(host, { return addE2eReact(host, {
@ -29,8 +27,6 @@ export async function addE2e(
styledModule: null, styledModule: null,
hasStyles: false, hasStyles: false,
unitTestRunner: 'none', unitTestRunner: 'none',
e2eCiWebServerTarget: options.e2eWebServerTarget,
e2eCiBaseUrl: options.e2eWebServerAddress,
}); });
case 'detox': case 'detox':
const { detoxApplicationGenerator } = ensurePackage< const { detoxApplicationGenerator } = ensurePackage<

View File

@ -44,9 +44,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e',
e2ePort: 4200,
e2eWebServerAddress: 'http://localhost:4200',
e2eWebServerTarget: 'serve',
}); });
}); });
@ -83,9 +80,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'myApp-e2e', e2eProjectRoot: 'myApp-e2e',
e2ePort: 4200,
e2eWebServerAddress: 'http://localhost:4200',
e2eWebServerTarget: 'serve',
}); });
}); });
@ -124,9 +118,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'directory/my-app-e2e', e2eProjectRoot: 'directory/my-app-e2e',
e2ePort: 4200,
e2eWebServerAddress: 'http://localhost:4200',
e2eWebServerTarget: 'serve',
}); });
}); });
@ -163,9 +154,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'directory/my-app-e2e', e2eProjectName: 'directory/my-app-e2e',
e2eProjectRoot: 'directory/my-app-e2e', e2eProjectRoot: 'directory/my-app-e2e',
e2ePort: 4200,
e2eWebServerAddress: 'http://localhost:4200',
e2eWebServerTarget: 'serve',
}); });
}); });
@ -203,9 +191,6 @@ describe('Normalize Options', () => {
rootProject: false, rootProject: false,
e2eProjectName: 'my-app-e2e', e2eProjectName: 'my-app-e2e',
e2eProjectRoot: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e',
e2ePort: 4200,
e2eWebServerAddress: 'http://localhost:4200',
e2eWebServerTarget: 'serve',
}); });
}); });
}); });

View File

@ -17,9 +17,6 @@ export interface NormalizedSchema extends Schema {
rootProject: boolean; rootProject: boolean;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
} }
export async function normalizeOptions( export async function normalizeOptions(
@ -50,34 +47,8 @@ export async function normalizeOptions(
const androidProjectRoot = joinPathFragments(appProjectRoot, 'android'); const androidProjectRoot = joinPathFragments(appProjectRoot, 'android');
const rootProject = appProjectRoot === '.'; const rootProject = appProjectRoot === '.';
let e2eWebServerTarget = 'serve';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
options.bundler === 'vite' &&
typeof plugin === 'object' &&
plugin.plugin === '@nx/vite/plugin' &&
(plugin.options as VitePluginOptions).serveTargetName
) {
e2eWebServerTarget = (plugin.options as ReactNativePluginOptions)
.startTargetName;
}
}
}
}
let e2ePort = 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[e2eWebServerTarget].options?.port;
}
const e2eProjectName = rootProject ? 'e2e' : `${fileName}-e2e`; const e2eProjectName = rootProject ? 'e2e' : `${fileName}-e2e`;
const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`; const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
@ -101,8 +72,5 @@ export async function normalizeOptions(
rootProject, rootProject,
e2eProjectName, e2eProjectName,
e2eProjectRoot, e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
}; };
} }

View File

@ -86,7 +86,7 @@ describe('app', () => {
import { defineConfig } from 'cypress'; import { defineConfig } from 'cypress';
export default defineConfig({ export default defineConfig({
e2e: { ...nxE2EPreset(__filename, {"cypressDir":"src","bundler":"vite","webServerCommands":{"default":"nx run my-app:serve","production":"nx run my-app:preview"},"ciWebServerCommand":"nx run my-app:preview","ciBaseUrl":"http://localhost:4300"}), e2e: { ...nxE2EPreset(__filename, {"cypressDir":"src","bundler":"vite","webServerCommands":{"default":"npx nx run my-app:serve","production":"npx nx run my-app:preview"},"ciWebServerCommand":"npx nx run my-app:preview","ciBaseUrl":"http://localhost:4300"}),
baseUrl: 'http://localhost:4200' } baseUrl: 'http://localhost:4200' }
}); });
" "

View File

@ -14,6 +14,7 @@ import { hasVitePlugin } from '../../../utils/has-vite-plugin';
import { NormalizedSchema } from '../schema'; import { NormalizedSchema } from '../schema';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function addE2e( export async function addE2e(
tree: Tree, tree: Tree,
@ -22,6 +23,49 @@ export async function addE2e(
const hasNxBuildPlugin = const hasNxBuildPlugin =
(options.bundler === 'webpack' && hasWebpackPlugin(tree)) || (options.bundler === 'webpack' && hasWebpackPlugin(tree)) ||
(options.bundler === 'vite' && hasVitePlugin(tree)); (options.bundler === 'vite' && hasVitePlugin(tree));
let e2eWebServerInfo: E2EWebServerDetails = {
e2eWebServerAddress: `http://localhost:${options.devServerPort ?? 4200}`,
e2eWebServerCommand: `${getPackageManagerCommand().exec} nx run ${
options.projectName
}:serve`,
e2eCiWebServerCommand: `${getPackageManagerCommand().exec} nx run ${
options.projectName
}:serve-static`,
e2eCiBaseUrl: `http://localhost:4200`,
e2eDevServerTarget: `${options.projectName}:serve`,
};
if (options.bundler === 'webpack') {
const { getWebpackE2EWebServerInfo } = ensurePackage<
typeof import('@nx/webpack')
>('@nx/webpack', nxVersion);
e2eWebServerInfo = await getWebpackE2EWebServerInfo(
tree,
options.projectName,
joinPathFragments(
options.appProjectRoot,
`webpack.config.${options.js ? 'js' : 'ts'}`
),
options.addPlugin,
options.devServerPort ?? 4200
);
} else if (options.bundler === 'vite') {
const { getViteE2EWebServerInfo } = ensurePackage<
typeof import('@nx/vite')
>('@nx/vite', nxVersion);
e2eWebServerInfo = await getViteE2EWebServerInfo(
tree,
options.projectName,
joinPathFragments(
options.appProjectRoot,
`vite.config.${options.js ? 'js' : 'ts'}`
),
options.addPlugin,
options.devServerPort ?? 4200
);
}
if (!hasNxBuildPlugin) { if (!hasNxBuildPlugin) {
await webStaticServeGenerator(tree, { await webStaticServeGenerator(tree, {
buildTarget: `${options.projectName}:build`, buildTarget: `${options.projectName}:build`,
@ -51,21 +95,20 @@ export async function addE2e(
// the name and root are already normalized, instruct the generator to use them as is // the name and root are already normalized, instruct the generator to use them as is
bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler, bundler: options.bundler === 'rspack' ? 'webpack' : options.bundler,
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`, devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
baseUrl: options.e2eWebServerAddress, baseUrl: e2eWebServerInfo.e2eWebServerAddress,
jsx: true, jsx: true,
rootProject: options.rootProject, rootProject: options.rootProject,
webServerCommands: hasNxBuildPlugin webServerCommands: hasNxBuildPlugin
? { ? {
default: `nx run ${options.projectName}:${options.e2eWebServerTarget}`, default: e2eWebServerInfo.e2eWebServerCommand,
production: `nx run ${options.projectName}:preview`, production: e2eWebServerInfo.e2eCiWebServerCommand,
} }
: undefined, : undefined,
ciWebServerCommand: hasNxBuildPlugin ciWebServerCommand: hasNxBuildPlugin
? `nx run ${options.projectName}:${options.e2eCiWebServerTarget}` ? e2eWebServerInfo.e2eCiWebServerCommand
: undefined, : undefined,
ciBaseUrl: ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined,
}); });
if ( if (
@ -127,10 +170,8 @@ export async function addE2e(
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx run ${ webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
options.projectName webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
}:${options.e2eCiWebServerTarget}`,
webServerAddress: options.e2eCiBaseUrl,
rootProject: options.rootProject, rootProject: options.rootProject,
addPlugin: options.addPlugin, addPlugin: options.addPlugin,
}); });

View File

@ -46,57 +46,8 @@ export async function normalizeOptions<T extends Schema = Schema>(
options.rootProject = appProjectRoot === '.'; options.rootProject = appProjectRoot === '.';
options.projectNameAndRootFormat = projectNameAndRootFormat; options.projectNameAndRootFormat = projectNameAndRootFormat;
let e2ePort = options.devServerPort ?? 4200;
let e2eWebServerTarget = 'serve';
let e2eCiWebServerTarget =
options.bundler === 'vite' ? 'preview' : 'serve-static';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
options.bundler === 'vite' &&
typeof plugin === 'object' &&
plugin.plugin === '@nx/vite/plugin'
) {
e2eCiWebServerTarget =
(plugin.options as VitePluginOptions)?.previewTargetName ??
e2eCiWebServerTarget;
e2eWebServerTarget =
(plugin.options as VitePluginOptions)?.serveTargetName ??
e2eWebServerTarget;
} else if (
options.bundler === 'webpack' &&
typeof plugin === 'object' &&
plugin.plugin === '@nx/webpack/plugin'
) {
e2eCiWebServerTarget =
(plugin.options as WebpackPluginOptions)?.serveStaticTargetName ??
e2eCiWebServerTarget;
e2eWebServerTarget =
(plugin.options as WebpackPluginOptions)?.serveTargetName ??
e2eWebServerTarget;
}
}
}
}
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[e2eWebServerTarget].options?.port;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const e2eCiBaseUrl =
options.bundler === 'vite'
? 'http://localhost:4300'
: `http://localhost:${e2ePort}`;
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
@ -117,11 +68,6 @@ export async function normalizeOptions<T extends Schema = Schema>(
appProjectRoot, appProjectRoot,
e2eProjectName, e2eProjectName,
e2eProjectRoot, e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2eCiWebServerTarget,
e2eCiBaseUrl,
e2ePort,
parsedTags, parsedTags,
fileName, fileName,
styledModule, styledModule,

View File

@ -37,11 +37,6 @@ export interface NormalizedSchema<T extends Schema = Schema> extends T {
appProjectRoot: string; appProjectRoot: string;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2eCiWebServerTarget: string;
e2eCiBaseUrl: string;
e2ePort: number;
parsedTags: string[]; parsedTags: string[];
fileName: string; fileName: string;
styledModule: null | SupportedStyles; styledModule: null | SupportedStyles;

View File

@ -153,7 +153,12 @@ export default defineConfig({
e2e: { e2e: {
...nxE2EPreset(__filename, { ...nxE2EPreset(__filename, {
cypressDir: 'src', cypressDir: 'src',
webServerCommands: { default: 'nx run test:dev:development' }, webServerCommands: {
default: 'npx nx run test:dev',
production: 'npx nx run test:serve-static',
},
ciWebServerCommand: 'npx nx run test:serve-static',
ciBaseUrl: 'http://localhost:3000',
}), }),
baseUrl: 'http://localhost:3000', baseUrl: 'http://localhost:3000',
}, },
@ -189,7 +194,7 @@ export default defineConfig({
}, },
/* Run your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
webServer: { webServer: {
command: 'npx nx dev test', command: 'npx nx run test:serve-static',
url: 'http://localhost:3000', url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
cwd: workspaceRoot, cwd: workspaceRoot,
@ -665,7 +670,12 @@ export default defineConfig({
e2e: { e2e: {
...nxE2EPreset(__filename, { ...nxE2EPreset(__filename, {
cypressDir: 'src', cypressDir: 'src',
webServerCommands: { default: 'nx run test:dev:development' }, webServerCommands: {
default: 'npx nx run test:dev',
production: 'npx nx run test:serve-static',
},
ciWebServerCommand: 'npx nx run test:serve-static',
ciBaseUrl: 'http://localhost:3000',
}), }),
baseUrl: 'http://localhost:3000', baseUrl: 'http://localhost:3000',
}, },
@ -701,7 +711,7 @@ export default defineConfig({
}, },
/* Run your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
webServer: { webServer: {
command: 'npx nx dev test', command: 'npx nx run test:serve-static',
url: 'http://localhost:3000', url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
cwd: workspaceRoot, cwd: workspaceRoot,
@ -1033,7 +1043,12 @@ export default defineConfig({
e2e: { e2e: {
...nxE2EPreset(__filename, { ...nxE2EPreset(__filename, {
cypressDir: 'src', cypressDir: 'src',
webServerCommands: { default: 'nx run test:dev:development' }, webServerCommands: {
default: 'npx nx run test:dev',
production: 'npx nx run test:serve-static',
},
ciWebServerCommand: 'npx nx run test:serve-static',
ciBaseUrl: 'http://localhost:3000',
}), }),
baseUrl: 'http://localhost:3000', baseUrl: 'http://localhost:3000',
}, },
@ -1434,7 +1449,7 @@ export default defineConfig({
}, },
/* Run your local dev server before starting the tests */ /* Run your local dev server before starting the tests */
webServer: { webServer: {
command: 'npx nx dev test', command: 'npx nx run test:serve-static',
url: 'http://localhost:3000', url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI, reuseExistingServer: !process.env.CI,
cwd: workspaceRoot, cwd: workspaceRoot,

View File

@ -97,7 +97,7 @@ export async function remixApplicationGeneratorInternal(
cwd: options.projectRoot, cwd: options.projectRoot,
}, },
}, },
['static-serve']: { ['serve-static']: {
dependsOn: ['build'], dependsOn: ['build'],
command: `remix-serve build/index.js`, command: `remix-serve build/index.js`,
options: { options: {

View File

@ -12,6 +12,7 @@ import { type NormalizedSchema } from './normalize-options';
import { getPackageVersion } from '../../../utils/versions'; import { getPackageVersion } from '../../../utils/versions';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { addE2eCiTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils';
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function addE2E(tree: Tree, options: NormalizedSchema) { export async function addE2E(tree: Tree, options: NormalizedSchema) {
const hasRemixPlugin = readNxJson(tree).plugins?.find((p) => const hasRemixPlugin = readNxJson(tree).plugins?.find((p) =>
@ -19,6 +20,14 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
? p === '@nx/remix/plugin' ? p === '@nx/remix/plugin'
: p.plugin === '@nx/remix/plugin' : p.plugin === '@nx/remix/plugin'
); );
let e2eWebsServerInfo = await getRemixE2EWebServerInfo(
tree,
options.projectName,
joinPathFragments(options.projectRoot, 'remix.config.js'),
options.addPlugin ?? Boolean(hasRemixPlugin)
);
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
@ -37,8 +46,18 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
project: options.e2eProjectName, project: options.e2eProjectName,
directory: 'src', directory: 'src',
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}:development`, devServerTarget: e2eWebsServerInfo.e2eDevServerTarget,
baseUrl: options.e2eWebServerAddress, baseUrl: e2eWebsServerInfo.e2eWebServerAddress,
webServerCommands: hasRemixPlugin
? {
default: e2eWebsServerInfo.e2eWebServerCommand,
production: e2eWebsServerInfo.e2eCiWebServerCommand,
}
: undefined,
ciWebServerCommand: hasRemixPlugin
? e2eWebsServerInfo.e2eCiWebServerCommand
: undefined,
ciBaseUrl: e2eWebsServerInfo.e2eCiBaseUrl,
addPlugin: options.addPlugin, addPlugin: options.addPlugin,
}); });
@ -97,10 +116,8 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: false, setParserOptionsProject: false,
webServerCommand: `${getPackageManagerCommand().exec} nx ${ webServerCommand: e2eWebsServerInfo.e2eCiWebServerCommand,
options.e2eWebServerTarget webServerAddress: e2eWebsServerInfo.e2eCiBaseUrl,
} ${options.name}`,
webServerAddress: options.e2eWebServerAddress,
rootProject: options.rootProject, rootProject: options.rootProject,
addPlugin: options.addPlugin, addPlugin: options.addPlugin,
}); });
@ -139,3 +156,41 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) {
return () => {}; return () => {};
} }
} }
async function getRemixE2EWebServerInfo(
tree: Tree,
projectName: string,
configFilePath: string,
isPluginBeingAdded: boolean
) {
const nxJson = readNxJson(tree);
let e2ePort = isPluginBeingAdded ? 3000 : 4200;
const defaultServeTarget = isPluginBeingAdded ? 'dev' : 'serve';
if (
nxJson.targetDefaults?.[defaultServeTarget] &&
nxJson.targetDefaults?.[defaultServeTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[defaultServeTarget].options?.port;
}
return getE2EWebServerInfo(
tree,
projectName,
{
plugin: '@nx/remix/plugin',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'serveStaticTargetName',
configFilePath,
},
{
defaultServeTargetName: defaultServeTarget,
defaultServeStaticTargetName: 'serve-static',
defaultE2EWebServerAddress: `http://localhost:${e2ePort}`,
defaultE2ECiBaseUrl: 'http://localhost:3000',
defaultE2EPort: e2ePort,
},
isPluginBeingAdded
);
}

View File

@ -9,9 +9,6 @@ export interface NormalizedSchema extends NxRemixGeneratorSchema {
projectRoot: string; projectRoot: string;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2ePort: number;
parsedTags: string[]; parsedTags: string[];
} }
@ -36,35 +33,8 @@ export async function normalizeOptions(
nxJson.useInferencePlugins !== false; nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault; options.addPlugin ??= addPluginDefault;
let e2eWebServerTarget = options.addPlugin ? 'dev' : 'serve';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
typeof plugin === 'object' &&
plugin.plugin === '@nx/remix/plugin' &&
(plugin.options as RemixPluginOptions).devTargetName
) {
e2eWebServerTarget = (plugin.options as RemixPluginOptions)
.devTargetName;
}
}
}
}
let e2ePort = options.addPlugin ? 3000 : 4200;
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
(nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT)
) {
e2ePort =
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port ||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.env?.PORT;
}
const e2eProjectName = options.rootProject ? 'e2e' : `${projectName}-e2e`; const e2eProjectName = options.rootProject ? 'e2e' : `${projectName}-e2e`;
const e2eProjectRoot = options.rootProject ? 'e2e' : `${projectRoot}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${projectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const parsedTags = options.tags const parsedTags = options.tags
? options.tags.split(',').map((s) => s.trim()) ? options.tags.split(',').map((s) => s.trim())
@ -77,9 +47,6 @@ export async function normalizeOptions(
projectRoot, projectRoot,
e2eProjectName, e2eProjectName,
e2eProjectRoot, e2eProjectRoot,
e2eWebServerAddress,
e2eWebServerTarget,
e2ePort,
parsedTags, parsedTags,
}; };
} }

View File

@ -35,6 +35,15 @@ exports[`@nx/remix/plugin non-root project should create nodes 1`] = `
"cwd": "my-app", "cwd": "my-app",
}, },
}, },
"serve-static": {
"command": "remix-serve build/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": "my-app",
},
},
"start": { "start": {
"command": "remix-serve build/index.js", "command": "remix-serve build/index.js",
"dependsOn": [ "dependsOn": [
@ -110,6 +119,15 @@ exports[`@nx/remix/plugin root project should create nodes 1`] = `
"cwd": ".", "cwd": ".",
}, },
}, },
"serve-static": {
"command": "remix-serve build/index.js",
"dependsOn": [
"build",
],
"options": {
"cwd": ".",
},
},
"start": { "start": {
"command": "remix-serve build/index.js", "command": "remix-serve build/index.js",
"dependsOn": [ "dependsOn": [

View File

@ -45,7 +45,11 @@ export interface RemixPluginOptions {
devTargetName?: string; devTargetName?: string;
startTargetName?: string; startTargetName?: string;
typecheckTargetName?: string; typecheckTargetName?: string;
/**
* @deprecated Use serveStaticTargetName instead. This option will be removed in Nx 21.
*/
staticServeTargetName?: string; staticServeTargetName?: string;
serveStaticTargetName?: string;
} }
export const createNodes: CreateNodes<RemixPluginOptions> = [ export const createNodes: CreateNodes<RemixPluginOptions> = [
@ -116,11 +120,17 @@ async function buildRemixTargets(
serverBuildPath, serverBuildPath,
options.buildTargetName options.buildTargetName
); );
// TODO(colum): Remove for Nx 21
targets[options.staticServeTargetName] = startTarget( targets[options.staticServeTargetName] = startTarget(
projectRoot, projectRoot,
serverBuildPath, serverBuildPath,
options.buildTargetName options.buildTargetName
); );
targets[options.serveStaticTargetName] = startTarget(
projectRoot,
serverBuildPath,
options.buildTargetName
);
targets[options.typecheckTargetName] = typecheckTarget( targets[options.typecheckTargetName] = typecheckTarget(
projectRoot, projectRoot,
namedInputs, namedInputs,
@ -233,7 +243,9 @@ function normalizeOptions(options: RemixPluginOptions) {
options.devTargetName ??= 'dev'; options.devTargetName ??= 'dev';
options.startTargetName ??= 'start'; options.startTargetName ??= 'start';
options.typecheckTargetName ??= 'typecheck'; options.typecheckTargetName ??= 'typecheck';
// TODO(colum): remove for Nx 21
options.staticServeTargetName ??= 'static-serve'; options.staticServeTargetName ??= 'static-serve';
options.serveStaticTargetName ??= 'serve-static';
return options; return options;
} }

View File

@ -1,5 +1,6 @@
export * from './src/utils/versions'; export * from './src/utils/versions';
export * from './src/utils/generator-utils'; export * from './src/utils/generator-utils';
export * from './src/utils/e2e-web-server-info-utils';
export { type ViteConfigurationGeneratorSchema } from './src/generators/configuration/schema'; export { type ViteConfigurationGeneratorSchema } from './src/generators/configuration/schema';
export { viteConfigurationGenerator } from './src/generators/configuration/configuration'; export { viteConfigurationGenerator } from './src/generators/configuration/configuration';
export { type VitestGeneratorSchema } from './src/generators/vitest/schema'; export { type VitestGeneratorSchema } from './src/generators/vitest/schema';

View File

@ -0,0 +1,149 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { type Tree, readNxJson, updateNxJson } from 'nx/src/devkit-exports';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { getViteE2EWebServerInfo } from './e2e-web-server-info-utils';
describe('getViteE2EWebServerInfo', () => {
let tree: Tree;
let tempFs: TempFs;
beforeEach(() => {
tempFs = new TempFs('e2e-webserver-info');
tree = createTreeWithEmptyWorkspace();
tree.root = tempFs.tempDir;
tree.write(`app/vite.config.ts`, ``);
tempFs.createFileSync(`app/vite.config.ts`, ``);
});
afterEach(() => {
tempFs.cleanup();
jest.resetModules();
});
it('should use the default values when no plugin is registered and plugins are not being used', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getViteE2EWebServerInfo(
tree,
'app',
'app/vite.config.ts',
false
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:preview",
"e2eDevServerTarget": "app:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:serve",
}
`);
});
it('should use the default values of the plugin when the plugin is just a string', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = ['@nx/vite/plugin'];
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getViteE2EWebServerInfo(
tree,
'app',
'app/vite.config.ts',
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:preview",
"e2eDevServerTarget": "app:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:serve",
}
`);
});
it('should use the values of the registered plugin when there is no includes or excludes defined', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
serveTargetName: 'vite:serve',
previewTargetName: 'vite:preview',
},
});
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getViteE2EWebServerInfo(
tree,
'app',
'app/vite.config.ts',
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:vite:preview",
"e2eDevServerTarget": "app:vite:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:vite:serve",
}
`);
});
it('should use the values of the correct registered plugin when there are includes or excludes defined', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
serveTargetName: 'vite:serve',
previewTargetName: 'vite:preview',
},
include: ['libs/**'],
});
nxJson.plugins.push({
plugin: '@nx/vite/plugin',
options: {
serveTargetName: 'vite-serve',
previewTargetName: 'vite-preview',
},
include: ['app/**'],
});
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getViteE2EWebServerInfo(
tree,
'app',
'app/vite.config.ts',
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4300",
"e2eCiWebServerCommand": "npx nx run app:vite-preview",
"e2eDevServerTarget": "app:vite-serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:vite-serve",
}
`);
});
});

View File

@ -0,0 +1,39 @@
import { type Tree, readNxJson } from '@nx/devkit';
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function getViteE2EWebServerInfo(
tree: Tree,
projectName: string,
configFilePath: string,
isPluginBeingAdded: boolean,
e2ePortOverride?: number
) {
const nxJson = readNxJson(tree);
let e2ePort = e2ePortOverride ?? 4200;
if (
nxJson.targetDefaults?.['serve'] &&
nxJson.targetDefaults?.['serve'].options?.port
) {
e2ePort = nxJson.targetDefaults?.['serve'].options?.port;
}
return getE2EWebServerInfo(
tree,
projectName,
{
plugin: '@nx/vite/plugin',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'previewTargetName',
configFilePath,
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'preview',
defaultE2EWebServerAddress: `http://localhost:${e2ePort}`,
defaultE2ECiBaseUrl: 'http://localhost:4300',
defaultE2EPort: e2ePort,
},
isPluginBeingAdded
);
}

View File

@ -141,10 +141,10 @@ export default defineConfig({
cypressDir: 'src', cypressDir: 'src',
bundler: 'vite', bundler: 'vite',
webServerCommands: { webServerCommands: {
default: 'nx run test:serve', default: 'npx nx run test:serve',
production: 'nx run test:preview', production: 'npx nx run test:preview',
}, },
ciWebServerCommand: 'nx run test:preview', ciWebServerCommand: 'npx nx run test:preview',
ciBaseUrl: 'http://localhost:4300', ciBaseUrl: 'http://localhost:4300',
}), }),
baseUrl: 'http://localhost:4200', baseUrl: 'http://localhost:4200',

View File

@ -23,16 +23,21 @@ export async function addE2e(
? p === '@nx/vite/plugin' ? p === '@nx/vite/plugin'
: p.plugin === '@nx/vite/plugin' : p.plugin === '@nx/vite/plugin'
); );
const e2eWebServerTarget = hasPlugin const { getViteE2EWebServerInfo } = ensurePackage<typeof import('@nx/vite')>(
? typeof hasPlugin === 'string' '@nx/vite',
? 'serve' nxVersion
: (hasPlugin.options as any)?.serveTargetName ?? 'serve' );
: 'serve'; const e2eWebServerInfo = await getViteE2EWebServerInfo(
const e2eCiWebServerTarget = hasPlugin tree,
? typeof hasPlugin === 'string' options.projectName,
? 'preview' joinPathFragments(
: (hasPlugin.options as any)?.previewTargetName ?? 'preview' options.appProjectRoot,
: 'preview'; `vite.config.${options.js ? 'js' : 'ts'}`
),
options.addPlugin,
options.devServerPort ?? 4200
);
switch (options.e2eTestRunner) { switch (options.e2eTestRunner) {
case 'cypress': { case 'cypress': {
if (!hasPlugin) { if (!hasPlugin) {
@ -60,17 +65,17 @@ export async function addE2e(
directory: 'src', directory: 'src',
bundler: 'vite', bundler: 'vite',
skipFormat: true, skipFormat: true,
devServerTarget: `${options.projectName}:${e2eWebServerTarget}`, devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
baseUrl: 'http://localhost:4200', baseUrl: e2eWebServerInfo.e2eWebServerAddress,
jsx: true, jsx: true,
webServerCommands: hasPlugin webServerCommands: hasPlugin
? { ? {
default: `nx run ${options.projectName}:${e2eWebServerTarget}`, default: e2eWebServerInfo.e2eWebServerCommand,
production: `nx run ${options.projectName}:preview`, production: e2eWebServerInfo.e2eCiWebServerCommand,
} }
: undefined, : undefined,
ciWebServerCommand: `nx run ${options.projectName}:${e2eCiWebServerTarget}`, ciWebServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
ciBaseUrl: 'http://localhost:4300', ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
}); });
if ( if (
@ -130,10 +135,8 @@ export async function addE2e(
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx run ${ webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
options.projectName webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
}:${e2eCiWebServerTarget}`,
webServerAddress: 'http://localhost:4300',
}); });
if ( if (

View File

@ -192,10 +192,10 @@ describe('app', () => {
cypressDir: 'src', cypressDir: 'src',
bundler: 'vite', bundler: 'vite',
webServerCommands: { webServerCommands: {
default: 'nx run cool-app:serve', default: 'npx nx run cool-app:serve',
production: 'nx run cool-app:preview', production: 'npx nx run cool-app:preview',
}, },
ciWebServerCommand: 'nx run cool-app:preview', ciWebServerCommand: 'npx nx run cool-app:preview',
ciBaseUrl: 'http://localhost:4300', ciBaseUrl: 'http://localhost:4300',
}), }),
baseUrl: 'http://localhost:4200', baseUrl: 'http://localhost:4200',
@ -225,10 +225,11 @@ describe('app', () => {
...nxE2EPreset(__filename, { ...nxE2EPreset(__filename, {
cypressDir: 'src', cypressDir: 'src',
webServerCommands: { webServerCommands: {
default: 'nx run cool-app:serve', default: 'npx nx run cool-app:serve',
production: 'nx run cool-app:preview', production: 'npx nx run cool-app:serve-static',
}, },
ciWebServerCommand: 'nx run cool-app:serve-static', ciWebServerCommand: 'npx nx run cool-app:serve-static',
ciBaseUrl: 'http://localhost:4200',
}), }),
baseUrl: 'http://localhost:4200', baseUrl: 'http://localhost:4200',
}, },

View File

@ -47,17 +47,13 @@ import { VitePluginOptions } from '@nx/vite/src/plugins/plugin';
import { WebpackPluginOptions } from '@nx/webpack/src/plugins/plugin'; import { WebpackPluginOptions } from '@nx/webpack/src/plugins/plugin';
import staticServeConfiguration from '../static-serve/static-serve-configuration'; import staticServeConfiguration from '../static-serve/static-serve-configuration';
import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file'; import { findPluginForConfigFile } from '@nx/devkit/src/utils/find-plugin-for-config-file';
import { E2EWebServerDetails } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
interface NormalizedSchema extends Schema { interface NormalizedSchema extends Schema {
projectName: string; projectName: string;
appProjectRoot: string; appProjectRoot: string;
e2eProjectName: string; e2eProjectName: string;
e2eProjectRoot: string; e2eProjectRoot: string;
e2eWebServerAddress: string;
e2eWebServerTarget: string;
e2eCiWebServerTarget: string;
e2eCiBaseUrl: string;
e2ePort: number;
parsedTags: string[]; parsedTags: string[];
} }
@ -391,6 +387,43 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
spa: true, spa: true,
}); });
} }
let e2eWebServerInfo: E2EWebServerDetails = {
e2eWebServerAddress: `http://localhost:4200`,
e2eWebServerCommand: `${getPackageManagerCommand().exec} nx run ${
options.projectName
}:serve`,
e2eCiWebServerCommand: `${getPackageManagerCommand().exec} nx run ${
options.projectName
}:serve-static`,
e2eCiBaseUrl: `http://localhost:4200`,
e2eDevServerTarget: `${options.projectName}:serve`,
};
if (options.bundler === 'webpack') {
const { getWebpackE2EWebServerInfo } = ensurePackage<
typeof import('@nx/webpack')
>('@nx/webpack', nxVersion);
e2eWebServerInfo = await getWebpackE2EWebServerInfo(
host,
options.projectName,
joinPathFragments(options.appProjectRoot, `webpack.config.js`),
options.addPlugin,
4200
);
} else if (options.bundler === 'vite') {
const { getViteE2EWebServerInfo } = ensurePackage<
typeof import('@nx/vite')
>('@nx/vite', nxVersion);
e2eWebServerInfo = await getViteE2EWebServerInfo(
host,
options.projectName,
joinPathFragments(options.appProjectRoot, `vite.config.ts`),
options.addPlugin,
4200
);
}
if (options.e2eTestRunner === 'cypress') { if (options.e2eTestRunner === 'cypress') {
const { configurationGenerator } = ensurePackage< const { configurationGenerator } = ensurePackage<
typeof import('@nx/cypress') typeof import('@nx/cypress')
@ -406,20 +439,20 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
const cypressTask = await configurationGenerator(host, { const cypressTask = await configurationGenerator(host, {
...options, ...options,
project: options.e2eProjectName, project: options.e2eProjectName,
devServerTarget: `${options.projectName}:${options.e2eWebServerTarget}`, devServerTarget: e2eWebServerInfo.e2eDevServerTarget,
baseUrl: options.e2eWebServerAddress, baseUrl: e2eWebServerInfo.e2eWebServerAddress,
directory: 'src', directory: 'src',
skipFormat: true, skipFormat: true,
webServerCommands: hasPlugin webServerCommands: hasPlugin
? { ? {
default: `nx run ${options.projectName}:${options.e2eWebServerTarget}`, default: e2eWebServerInfo.e2eWebServerCommand,
production: `nx run ${options.projectName}:preview`, production: e2eWebServerInfo.e2eCiWebServerCommand,
} }
: undefined, : undefined,
ciWebServerCommand: hasPlugin ciWebServerCommand: hasPlugin
? `nx run ${options.projectName}:${options.e2eCiWebServerTarget}` ? e2eWebServerInfo.e2eCiWebServerCommand
: undefined, : undefined,
ciBaseUrl: options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined, ciBaseUrl: e2eWebServerInfo.e2eCiBaseUrl,
}); });
if ( if (
@ -472,10 +505,8 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
js: false, js: false,
linter: options.linter, linter: options.linter,
setParserOptionsProject: options.setParserOptionsProject, setParserOptionsProject: options.setParserOptionsProject,
webServerCommand: `${getPackageManagerCommand().exec} nx run ${ webServerCommand: e2eWebServerInfo.e2eCiWebServerCommand,
options.projectName webServerAddress: e2eWebServerInfo.e2eCiBaseUrl,
}:${options.e2eCiWebServerTarget}`,
webServerAddress: options.e2eCiBaseUrl,
addPlugin: options.addPlugin, addPlugin: options.addPlugin,
}); });
@ -590,57 +621,8 @@ async function normalizeOptions(
nxJson.useInferencePlugins !== false; nxJson.useInferencePlugins !== false;
options.addPlugin ??= addPluginDefault; options.addPlugin ??= addPluginDefault;
let e2ePort = 4200;
let e2eWebServerTarget = 'serve';
let e2eCiWebServerTarget =
options.bundler === 'vite' ? 'preview' : 'serve-static';
if (options.addPlugin) {
if (nxJson.plugins) {
for (const plugin of nxJson.plugins) {
if (
options.bundler === 'vite' &&
typeof plugin === 'object' &&
plugin.plugin === '@nx/vite/plugin'
) {
e2eCiWebServerTarget =
(plugin.options as VitePluginOptions)?.previewTargetName ??
e2eCiWebServerTarget;
e2eWebServerTarget =
(plugin.options as VitePluginOptions)?.serveTargetName ??
e2eWebServerTarget;
} else if (
options.bundler === 'webpack' &&
typeof plugin === 'object' &&
plugin.plugin === '@nx/webpack/plugin'
) {
e2eCiWebServerTarget =
(plugin.options as WebpackPluginOptions)?.serveStaticTargetName ??
e2eCiWebServerTarget;
e2eWebServerTarget =
(plugin.options as WebpackPluginOptions)?.serveTargetName ??
e2eWebServerTarget;
}
}
}
}
if (
nxJson.targetDefaults?.[e2eWebServerTarget] &&
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
) {
e2ePort = nxJson.targetDefaults?.[e2eWebServerTarget].options?.port;
}
const e2eProjectName = `${appProjectName}-e2e`; const e2eProjectName = `${appProjectName}-e2e`;
const e2eProjectRoot = `${appProjectRoot}-e2e`; const e2eProjectRoot = `${appProjectRoot}-e2e`;
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
const e2eCiBaseUrl =
options.bundler === 'vite'
? 'http://localhost:4300'
: `http://localhost:${e2ePort}`;
const npmScope = getNpmScope(host); const npmScope = getNpmScope(host);
@ -664,11 +646,6 @@ async function normalizeOptions(
appProjectRoot, appProjectRoot,
e2eProjectRoot, e2eProjectRoot,
e2eProjectName, e2eProjectName,
e2eWebServerAddress,
e2eWebServerTarget,
e2eCiWebServerTarget,
e2eCiBaseUrl,
e2ePort,
parsedTags, parsedTags,
}; };
} }

View File

@ -37,3 +37,4 @@ export * from './src/utils/get-css-module-local-ident';
export * from './src/utils/with-nx'; export * from './src/utils/with-nx';
export * from './src/utils/with-web'; export * from './src/utils/with-web';
export * from './src/utils/module-federation/public-api'; export * from './src/utils/module-federation/public-api';
export * from './src/utils/e2e-web-server-info-utils';

View File

@ -0,0 +1,149 @@
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { type Tree, readNxJson, updateNxJson } from 'nx/src/devkit-exports';
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
import { getWebpackE2EWebServerInfo } from './e2e-web-server-info-utils';
describe('getWebpackE2EWebServerInfo', () => {
let tree: Tree;
let tempFs: TempFs;
beforeEach(() => {
tempFs = new TempFs('e2e-webserver-info');
tree = createTreeWithEmptyWorkspace();
tree.root = tempFs.tempDir;
tree.write(`app/webpack.config.ts`, ``);
tempFs.createFileSync(`app/webpack.config.ts`, ``);
});
afterEach(() => {
tempFs.cleanup();
jest.resetModules();
});
it('should use the default values when no plugin is registered and plugins are not being used', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getWebpackE2EWebServerInfo(
tree,
'app',
'app/webpack.config.ts',
false
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4200",
"e2eCiWebServerCommand": "npx nx run app:serve-static",
"e2eDevServerTarget": "app:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:serve",
}
`);
});
it('should use the default values of the plugin when the plugin is just a string', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins = ['@nx/webpack/plugin'];
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getWebpackE2EWebServerInfo(
tree,
'app',
'app/webpack.config.ts',
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4200",
"e2eCiWebServerCommand": "npx nx run app:serve-static",
"e2eDevServerTarget": "app:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:serve",
}
`);
});
it('should use the values of the registered plugin when there is no includes or excludes defined', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/webpack/plugin',
options: {
serveTargetName: 'webpack:serve',
serveStaticTargetName: 'webpack:preview',
},
});
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getWebpackE2EWebServerInfo(
tree,
'app',
'app/webpack.config.ts',
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4200",
"e2eCiWebServerCommand": "npx nx run app:webpack:preview",
"e2eDevServerTarget": "app:webpack:serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:webpack:serve",
}
`);
});
it('should use the values of the correct registered plugin when there are includes or excludes defined', async () => {
// ARRANGE
const nxJson = readNxJson(tree);
nxJson.plugins ??= [];
nxJson.plugins.push({
plugin: '@nx/webpack/plugin',
options: {
serveTargetName: 'webpack:serve',
serveStaticTargetName: 'webpack:preview',
},
include: ['libs/**'],
});
nxJson.plugins.push({
plugin: '@nx/webpack/plugin',
options: {
serveTargetName: 'webpack-serve',
serveStaticTargetName: 'webpack-preview',
},
include: ['app/**'],
});
updateNxJson(tree, nxJson);
// ACT
const e2eWebServerInfo = await getWebpackE2EWebServerInfo(
tree,
'app',
'app/webpack.config.ts',
true
);
// ASSERT
expect(e2eWebServerInfo).toMatchInlineSnapshot(`
{
"e2eCiBaseUrl": "http://localhost:4200",
"e2eCiWebServerCommand": "npx nx run app:webpack-preview",
"e2eDevServerTarget": "app:webpack-serve",
"e2eWebServerAddress": "http://localhost:4200",
"e2eWebServerCommand": "npx nx run app:webpack-serve",
}
`);
});
});

View File

@ -0,0 +1,39 @@
import { type Tree, readNxJson } from '@nx/devkit';
import { getE2EWebServerInfo } from '@nx/devkit/src/generators/e2e-web-server-info-utils';
export async function getWebpackE2EWebServerInfo(
tree: Tree,
projectName: string,
configFilePath: string,
isPluginBeingAdded: boolean,
e2ePortOverride?: number
) {
const nxJson = readNxJson(tree);
let e2ePort = e2ePortOverride ?? 4200;
if (
nxJson.targetDefaults?.['serve'] &&
nxJson.targetDefaults?.['serve'].options?.port
) {
e2ePort = nxJson.targetDefaults?.['serve'].options?.port;
}
return getE2EWebServerInfo(
tree,
projectName,
{
plugin: '@nx/webpack/plugin',
serveTargetName: 'serveTargetName',
serveStaticTargetName: 'serveStaticTargetName',
configFilePath,
},
{
defaultServeTargetName: 'serve',
defaultServeStaticTargetName: 'serve-static',
defaultE2EWebServerAddress: `http://localhost:${e2ePort}`,
defaultE2ECiBaseUrl: 'http://localhost:4200',
defaultE2EPort: e2ePort,
},
isPluginBeingAdded
);
}