feat(testing): e2e-ci should use serve-static or vite preview for playwright and cypress (#27240)
- fix(vite): preview should dependOn build - fix(react): playwright should use vite preview - fix(vue): playwright should use vite preview - fix(web): playwright should use vite preview - chore(testing): add e2e test <!-- 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 --> Currently, `playwright` uses the `vite serve` command when setting up the web server to run the e2e tests against. The `vite preview` command/target should also depend on `vite build`. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> `playwright` should use the `vite preview` command when setting up the web server `vite preview` targets add a `dependsOn["build"]` Ensure `serve-static` has a dependsOn: ['build'] Cypress should use the `ciBaseUrl` if it exists when running the `e2e-ci` targets Migrations for Playwright and Cypress to use serve-static and preview correctly ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
97d4184709
commit
1dcfbeeeee
@ -18,7 +18,7 @@ describe('React Playwright e2e tests', () => {
|
|||||||
packages: ['@nx/react'],
|
packages: ['@nx/react'],
|
||||||
});
|
});
|
||||||
runCLI(
|
runCLI(
|
||||||
`generate @nx/react:app ${appName} --e2eTestRunner=playwright --projectNameAndRootFormat=as-provided --no-interactive`
|
`generate @nx/react:app ${appName} --e2eTestRunner=playwright --bundler=vite --projectNameAndRootFormat=as-provided --no-interactive`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -88,6 +88,14 @@ exports[`Webpack Plugin (legacy) ConvertConfigToWebpackPlugin, should convert wi
|
|||||||
"lint": {
|
"lint": {
|
||||||
"executor": "@nx/eslint:lint"
|
"executor": "@nx/eslint:lint"
|
||||||
},
|
},
|
||||||
|
"serve-static": {
|
||||||
|
"executor": "@nx/web:file-server",
|
||||||
|
"dependsOn": ["build"],
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "app3224373:build",
|
||||||
|
"spa": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"executor": "@nx/jest:jest",
|
"executor": "@nx/jest:jest",
|
||||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||||
|
|||||||
@ -113,17 +113,17 @@ describe('Webpack Plugin (legacy)', () => {
|
|||||||
updateFile(
|
updateFile(
|
||||||
`${appName}/src/main.ts`,
|
`${appName}/src/main.ts`,
|
||||||
`
|
`
|
||||||
document.querySelector('proj-root').innerHTML = '<h1>Welcome</h1>';
|
document.querySelector('proj-root')!.innerHTML = '<h1>Welcome</h1>';
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
updateFile(
|
updateFile(
|
||||||
`${appName}/webpack.config.js`,
|
`${appName}/webpack.config.js`,
|
||||||
`
|
`
|
||||||
const { join } = require('path');
|
const { join } = require('path');
|
||||||
const {NxWebpackPlugin} = require('@nx/webpack');
|
const {NxAppWebpackPlugin} = require('@nx/webpack/app-plugin');
|
||||||
module.exports = {
|
module.exports = {
|
||||||
output: {
|
output: {
|
||||||
path: join(__dirname, '../dist/app9524918'),
|
path: join(__dirname, '../dist/${appName}'),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new NxAppWebpackPlugin({
|
new NxAppWebpackPlugin({
|
||||||
|
|||||||
@ -29,6 +29,12 @@
|
|||||||
"version": "18.1.0-beta.3",
|
"version": "18.1.0-beta.3",
|
||||||
"description": "Update to Cypress ^13.6.6 if the workspace is using Cypress v13 to ensure workspaces don't use v13.6.5 which has an issue when verifying Cypress.",
|
"description": "Update to Cypress ^13.6.6 if the workspace is using Cypress v13 to ensure workspaces don't use v13.6.5 which has an issue when verifying Cypress.",
|
||||||
"implementation": "./src/migrations/update-18-1-0/update-cypress-version-13-6-6"
|
"implementation": "./src/migrations/update-18-1-0/update-cypress-version-13-6-6"
|
||||||
|
},
|
||||||
|
"update-19-6-0-update-ci-webserver-for-vite": {
|
||||||
|
"cli": "nx",
|
||||||
|
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageJsonUpdates": {
|
"packageJsonUpdates": {
|
||||||
|
|||||||
@ -131,6 +131,7 @@ export function nxE2EPreset(
|
|||||||
webServerCommand: options?.webServerCommands?.default,
|
webServerCommand: options?.webServerCommands?.default,
|
||||||
webServerCommands: options?.webServerCommands,
|
webServerCommands: options?.webServerCommands,
|
||||||
ciWebServerCommand: options?.ciWebServerCommand,
|
ciWebServerCommand: options?.ciWebServerCommand,
|
||||||
|
ciBaseUrl: options?.ciBaseUrl,
|
||||||
},
|
},
|
||||||
|
|
||||||
async setupNodeEvents(on, config) {
|
async setupNodeEvents(on, config) {
|
||||||
@ -268,6 +269,11 @@ export type NxCypressE2EPresetOptions = {
|
|||||||
*/
|
*/
|
||||||
ciWebServerCommand?: string;
|
ciWebServerCommand?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The url of the web server for ciWebServerCommand
|
||||||
|
*/
|
||||||
|
ciBaseUrl?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures how the web server command is started and monitored.
|
* Configures how the web server command is started and monitored.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -47,6 +47,7 @@ export interface CypressE2EConfigSchema {
|
|||||||
|
|
||||||
webServerCommands?: Record<string, string>;
|
webServerCommands?: Record<string, string>;
|
||||||
ciWebServerCommand?: string;
|
ciWebServerCommand?: string;
|
||||||
|
ciBaseUrl?: string;
|
||||||
addPlugin?: boolean;
|
addPlugin?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,10 +219,12 @@ async function addFiles(
|
|||||||
let webServerCommands: Record<string, string>;
|
let webServerCommands: Record<string, string>;
|
||||||
|
|
||||||
let ciWebServerCommand: string;
|
let ciWebServerCommand: string;
|
||||||
|
let ciBaseUrl: string;
|
||||||
|
|
||||||
if (hasPlugin && options.webServerCommands && options.ciWebServerCommand) {
|
if (hasPlugin && options.webServerCommands && options.ciWebServerCommand) {
|
||||||
webServerCommands = options.webServerCommands;
|
webServerCommands = options.webServerCommands;
|
||||||
ciWebServerCommand = options.ciWebServerCommand;
|
ciWebServerCommand = options.ciWebServerCommand;
|
||||||
|
ciBaseUrl = options.ciBaseUrl;
|
||||||
} else if (hasPlugin && options.devServerTarget) {
|
} else if (hasPlugin && options.devServerTarget) {
|
||||||
webServerCommands = {};
|
webServerCommands = {};
|
||||||
|
|
||||||
@ -253,6 +256,7 @@ async function addFiles(
|
|||||||
bundler: options.bundler === 'vite' ? 'vite' : undefined,
|
bundler: options.bundler === 'vite' ? 'vite' : undefined,
|
||||||
webServerCommands,
|
webServerCommands,
|
||||||
ciWebServerCommand: ciWebServerCommand,
|
ciWebServerCommand: ciWebServerCommand,
|
||||||
|
ciBaseUrl,
|
||||||
},
|
},
|
||||||
options.baseUrl
|
options.baseUrl
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,262 @@
|
|||||||
|
import updateCiWebserverForVite from './update-ci-webserver-for-vite';
|
||||||
|
import {
|
||||||
|
type Tree,
|
||||||
|
type ProjectGraph,
|
||||||
|
readNxJson,
|
||||||
|
updateNxJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||||
|
|
||||||
|
let projectGraph: ProjectGraph;
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual<any>('@nx/devkit'),
|
||||||
|
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
|
||||||
|
return projectGraph;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('updateCiWebserverForVite', () => {
|
||||||
|
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 if vite is not found for application', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins = [
|
||||||
|
{
|
||||||
|
plugin: '@nx/cypress/plugin',
|
||||||
|
options: {
|
||||||
|
targetName: 'e2e',
|
||||||
|
ciTargetName: 'e2e-ci',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
|
||||||
|
addProject(tree, tempFs, {
|
||||||
|
buildTargetName: 'build',
|
||||||
|
ciTargetName: 'e2e-ci',
|
||||||
|
appName: 'app',
|
||||||
|
noVite: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await updateCiWebserverForVite(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('app-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"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 app:serve',
|
||||||
|
production: 'nx run app:preview',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'nx run app:serve-static',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update ciWebServerCommand to preview for vite app', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins = [
|
||||||
|
{
|
||||||
|
plugin: '@nx/cypress/plugin',
|
||||||
|
options: {
|
||||||
|
targetName: 'e2e',
|
||||||
|
ciTargetName: 'e2e-ci',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
plugin: '@nx/vite/plugin',
|
||||||
|
options: {
|
||||||
|
buildTargetName: 'build',
|
||||||
|
previewTargetName: 'preview',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
|
||||||
|
addProject(tree, tempFs);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await updateCiWebserverForVite(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('app-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"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 app:serve',
|
||||||
|
production: 'nx run app:preview',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'nx run app:preview',
|
||||||
|
ciBaseUrl: 'http://localhost:4300',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function addProject(
|
||||||
|
tree: Tree,
|
||||||
|
tempFs: TempFs,
|
||||||
|
overrides: {
|
||||||
|
ciTargetName: string;
|
||||||
|
buildTargetName: string;
|
||||||
|
appName: string;
|
||||||
|
noCi?: boolean;
|
||||||
|
noVite?: 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',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (!overrides.noVite) {
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
if (!overrides.noVite) {
|
||||||
|
tempFs.createFile(`${overrides.appName}/vite.config.ts`, viteConfig);
|
||||||
|
}
|
||||||
|
tempFs.createFilesSync({
|
||||||
|
[`${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,165 @@
|
|||||||
|
import {
|
||||||
|
type Tree,
|
||||||
|
CreateNodesV2,
|
||||||
|
createProjectGraphAsync,
|
||||||
|
readNxJson,
|
||||||
|
parseTargetString,
|
||||||
|
joinPathFragments,
|
||||||
|
PluginConfiguration,
|
||||||
|
CreateNodes,
|
||||||
|
formatFiles,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import {
|
||||||
|
retrieveProjectConfigurations,
|
||||||
|
LoadedNxPlugin,
|
||||||
|
ProjectConfigurationsError,
|
||||||
|
findMatchingConfigFiles,
|
||||||
|
} from 'nx/src/devkit-internals';
|
||||||
|
import { ConfigurationResult } from 'nx/src/project-graph/utils/project-configuration-utils';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
import { CypressPluginOptions } from '../../plugins/plugin';
|
||||||
|
|
||||||
|
export default async function (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
|
||||||
|
);
|
||||||
|
|
||||||
|
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 pathToViteConfig = [
|
||||||
|
joinPathFragments(graph.nodes[project].data.root, 'vite.config.ts'),
|
||||||
|
joinPathFragments(graph.nodes[project].data.root, 'vite.config.js'),
|
||||||
|
].find((p) => tree.exists(p));
|
||||||
|
|
||||||
|
if (!pathToViteConfig) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const viteConfigContents = tree.read(pathToViteConfig, 'utf-8');
|
||||||
|
if (!viteConfigContents.includes('preview:')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const matchingVitePlugin = await findPluginForConfigFile(
|
||||||
|
tree,
|
||||||
|
'@nx/vite/plugin',
|
||||||
|
pathToViteConfig
|
||||||
|
);
|
||||||
|
const previewTargetName = matchingVitePlugin
|
||||||
|
? typeof matchingVitePlugin === 'string'
|
||||||
|
? 'preview'
|
||||||
|
: (matchingVitePlugin.options as any)?.previewTargetName ?? 'preview'
|
||||||
|
: 'preview';
|
||||||
|
|
||||||
|
tree.write(
|
||||||
|
configFile,
|
||||||
|
`${configFileContents.slice(
|
||||||
|
0,
|
||||||
|
nodes[0].getStart()
|
||||||
|
)}'nx run ${project}:${previewTargetName}',
|
||||||
|
ciBaseUrl: "http://localhost:4300"${configFileContents.slice(
|
||||||
|
nodes[0].getEnd()
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -257,6 +257,8 @@ async function buildCypressTargets(
|
|||||||
excludeSpecPatterns
|
excludeSpecPatterns
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const ciBaseUrl = pluginPresetOptions?.ciBaseUrl;
|
||||||
|
|
||||||
const dependsOn: TargetConfiguration['dependsOn'] = [];
|
const dependsOn: TargetConfiguration['dependsOn'] = [];
|
||||||
const outputs = getOutputs(projectRoot, cypressConfig, 'e2e');
|
const outputs = getOutputs(projectRoot, cypressConfig, 'e2e');
|
||||||
const inputs = getInputs(namedInputs);
|
const inputs = getInputs(namedInputs);
|
||||||
@ -273,7 +275,9 @@ async function buildCypressTargets(
|
|||||||
outputs,
|
outputs,
|
||||||
inputs,
|
inputs,
|
||||||
cache: true,
|
cache: true,
|
||||||
command: `cypress run --env webServerCommand="${ciWebServerCommand}" --spec ${relativeSpecFilePath}`,
|
command: `cypress run --env webServerCommand="${ciWebServerCommand}" --spec ${relativeSpecFilePath}${
|
||||||
|
ciBaseUrl ? ` --config='{"baseUrl": "${ciBaseUrl}"}'` : ''
|
||||||
|
}`,
|
||||||
options: {
|
options: {
|
||||||
cwd: projectRoot,
|
cwd: projectRoot,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -8,7 +8,10 @@ export { getExecutorInformation } from './command-line/run/executor-utils';
|
|||||||
export { readNxJson as readNxJsonFromDisk } from './config/nx-json';
|
export { readNxJson as readNxJsonFromDisk } from './config/nx-json';
|
||||||
export { calculateDefaultProjectName } from './config/calculate-default-project-name';
|
export { calculateDefaultProjectName } from './config/calculate-default-project-name';
|
||||||
export { retrieveProjectConfigurationsWithAngularProjects } from './project-graph/utils/retrieve-workspace-files';
|
export { retrieveProjectConfigurationsWithAngularProjects } from './project-graph/utils/retrieve-workspace-files';
|
||||||
export { mergeTargetConfigurations } from './project-graph/utils/project-configuration-utils';
|
export {
|
||||||
|
mergeTargetConfigurations,
|
||||||
|
findMatchingConfigFiles,
|
||||||
|
} from './project-graph/utils/project-configuration-utils';
|
||||||
export { readProjectConfigurationsFromRootMap } from './project-graph/utils/project-configuration-utils';
|
export { readProjectConfigurationsFromRootMap } from './project-graph/utils/project-configuration-utils';
|
||||||
export { splitTarget } from './utils/split-target';
|
export { splitTarget } from './utils/split-target';
|
||||||
export { combineOptionsForExecutor } from './utils/params';
|
export { combineOptionsForExecutor } from './utils/params';
|
||||||
|
|||||||
@ -505,12 +505,12 @@ function mergeCreateNodesResults(
|
|||||||
return { projectRootMap, externalNodes, rootMap, configurationSourceMaps };
|
return { projectRootMap, externalNodes, rootMap, configurationSourceMaps };
|
||||||
}
|
}
|
||||||
|
|
||||||
function findMatchingConfigFiles(
|
export function findMatchingConfigFiles(
|
||||||
projectFiles: string[],
|
projectFiles: string[],
|
||||||
pattern: string,
|
pattern: string,
|
||||||
include: string[],
|
include: string[],
|
||||||
exclude: string[]
|
exclude: string[]
|
||||||
) {
|
): string[] {
|
||||||
const matchingConfigFiles: string[] = [];
|
const matchingConfigFiles: string[] = [];
|
||||||
|
|
||||||
for (const file of projectFiles) {
|
for (const file of projectFiles) {
|
||||||
|
|||||||
@ -11,6 +11,12 @@
|
|||||||
"version": "18.1.0-beta.3",
|
"version": "18.1.0-beta.3",
|
||||||
"description": "Remove invalid baseUrl option from @nx/playwright:playwright targets in project.json.",
|
"description": "Remove invalid baseUrl option from @nx/playwright:playwright targets in project.json.",
|
||||||
"implementation": "./src/migrations/update-18-1-0/remove-baseUrl-from-project-json"
|
"implementation": "./src/migrations/update-18-1-0/remove-baseUrl-from-project-json"
|
||||||
|
},
|
||||||
|
"19-6-0-use-serve-static-preview-for-command": {
|
||||||
|
"cli": "nx",
|
||||||
|
"version": "19.6.0-beta.0",
|
||||||
|
"description": "Use serve-static or preview for webServerCommand.",
|
||||||
|
"implementation": "./src/migrations/update-19-6-0/use-serve-static-preview-for-command"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,234 @@
|
|||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { ProjectGraph, type Tree } from '@nx/devkit';
|
||||||
|
import useServeStaticPreviewForCommand from './use-serve-static-preview-for-command';
|
||||||
|
import { TempFs } from 'nx/src/internal-testing-utils/temp-fs';
|
||||||
|
|
||||||
|
let projectGraph: ProjectGraph;
|
||||||
|
jest.mock('@nx/devkit', () => ({
|
||||||
|
...jest.requireActual<any>('@nx/devkit'),
|
||||||
|
createProjectGraphAsync: jest.fn().mockImplementation(async () => {
|
||||||
|
return projectGraph;
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('useServeStaticPreviewForCommand', () => {
|
||||||
|
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 update when it does not use serve-static for non-vite', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
addProject(tree, tempFs, { noVite: true });
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await useServeStaticPreviewForCommand(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('app-e2e/playwright.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"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 app:serve-static',
|
||||||
|
url: 'http://localhost:4200',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
it('should update when it does not use preview for vite', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
addProject(tree, tempFs);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await useServeStaticPreviewForCommand(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(tree.read('app-e2e/playwright.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"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:4300';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||||
|
use: {
|
||||||
|
baseURL,
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
webServer: {
|
||||||
|
command: 'npx nx run app:preview',
|
||||||
|
url: 'http://localhost:4300',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const basePlaywrightConfig = (
|
||||||
|
appName: string
|
||||||
|
) => `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 ${appName}:serve',
|
||||||
|
url: 'http://localhost:4200',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});`;
|
||||||
|
|
||||||
|
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/app',
|
||||||
|
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/app',
|
||||||
|
emptyOutDir: true,
|
||||||
|
reportCompressedSize: true,
|
||||||
|
commonjsOptions: {
|
||||||
|
transformMixedEsModules: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});`;
|
||||||
|
|
||||||
|
function addProject(
|
||||||
|
tree: Tree,
|
||||||
|
tempFs: TempFs,
|
||||||
|
overrides: {
|
||||||
|
noVite?: boolean;
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const appProjectConfig = {
|
||||||
|
name: 'app',
|
||||||
|
root: 'app',
|
||||||
|
sourceRoot: `${'app'}/src`,
|
||||||
|
projectType: 'application',
|
||||||
|
};
|
||||||
|
|
||||||
|
const e2eProjectConfig = {
|
||||||
|
name: `app-e2e`,
|
||||||
|
root: `app-e2e`,
|
||||||
|
sourceRoot: `app-e2e/src`,
|
||||||
|
projectType: 'application',
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!overrides.noVite) {
|
||||||
|
tree.write(`app/vite.config.ts`, viteConfig);
|
||||||
|
} else {
|
||||||
|
tree.write(`app/webpack.config.ts`, ``);
|
||||||
|
}
|
||||||
|
|
||||||
|
tree.write(`app/project.json`, JSON.stringify(appProjectConfig));
|
||||||
|
tree.write(`app-e2e/playwright.config.ts`, basePlaywrightConfig('app'));
|
||||||
|
tree.write(`app-e2e/project.json`, JSON.stringify(e2eProjectConfig));
|
||||||
|
if (!overrides.noVite) {
|
||||||
|
tempFs.createFile(`app/vite.config.ts`, viteConfig);
|
||||||
|
} else {
|
||||||
|
tempFs.createFile(`app/webpack.config.ts`, ``);
|
||||||
|
}
|
||||||
|
tempFs.createFilesSync({
|
||||||
|
[`app/project.json`]: JSON.stringify(appProjectConfig),
|
||||||
|
[`app-e2e/playwright.config.ts`]: basePlaywrightConfig('app'),
|
||||||
|
[`app-e2e/project.json`]: JSON.stringify(e2eProjectConfig),
|
||||||
|
});
|
||||||
|
|
||||||
|
projectGraph.nodes['app'] = {
|
||||||
|
name: 'app',
|
||||||
|
type: 'app',
|
||||||
|
data: {
|
||||||
|
projectType: 'application',
|
||||||
|
root: 'app',
|
||||||
|
targets: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
projectGraph.nodes[`app-e2e`] = {
|
||||||
|
name: `app-e2e`,
|
||||||
|
type: 'app',
|
||||||
|
data: {
|
||||||
|
projectType: 'application',
|
||||||
|
root: `app-e2e`,
|
||||||
|
targets: {
|
||||||
|
e2e: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,142 @@
|
|||||||
|
import {
|
||||||
|
createProjectGraphAsync,
|
||||||
|
formatFiles,
|
||||||
|
getPackageManagerCommand,
|
||||||
|
joinPathFragments,
|
||||||
|
parseTargetString,
|
||||||
|
type Tree,
|
||||||
|
visitNotIgnoredFiles,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { tsquery } from '@phenomnomnominal/tsquery';
|
||||||
|
|
||||||
|
export default async function (tree: Tree) {
|
||||||
|
const graph = await createProjectGraphAsync();
|
||||||
|
visitNotIgnoredFiles(tree, '', (path) => {
|
||||||
|
if (!path.endsWith('playwright.config.ts')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let playwrightConfigFileContents = tree.read(path, 'utf-8');
|
||||||
|
|
||||||
|
const WEBSERVER_COMMAND_SELECTOR =
|
||||||
|
'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=command]) > StringLiteral';
|
||||||
|
let ast = tsquery.ast(playwrightConfigFileContents);
|
||||||
|
const nodes = tsquery(ast, WEBSERVER_COMMAND_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
if (!nodes.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandValueNode = nodes[0];
|
||||||
|
const command = commandValueNode.getText();
|
||||||
|
let project: string;
|
||||||
|
if (command.includes('nx run')) {
|
||||||
|
const NX_TARGET_REGEX = "(?<=nx run )[^']+";
|
||||||
|
const matches = command.match(NX_TARGET_REGEX);
|
||||||
|
if (!matches) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const targetString = matches[0];
|
||||||
|
const parsedTargetString = parseTargetString(targetString, graph);
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedTargetString.target === 'serve-static' ||
|
||||||
|
parsedTargetString.target === 'preview'
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
project = parsedTargetString.project;
|
||||||
|
} else {
|
||||||
|
const NX_PROJECT_REGEX = "(?<=nx [^ ]+ )[^']+";
|
||||||
|
const matches = command.match(NX_PROJECT_REGEX);
|
||||||
|
if (!matches) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
project = matches[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathToViteConfig = [
|
||||||
|
joinPathFragments(graph.nodes[project].data.root, 'vite.config.ts'),
|
||||||
|
joinPathFragments(graph.nodes[project].data.root, 'vite.config.js'),
|
||||||
|
].find((p) => tree.exists(p));
|
||||||
|
|
||||||
|
if (!pathToViteConfig) {
|
||||||
|
const newCommand = `${
|
||||||
|
getPackageManagerCommand().exec
|
||||||
|
} nx run ${project}:serve-static`;
|
||||||
|
tree.write(
|
||||||
|
path,
|
||||||
|
`${playwrightConfigFileContents.slice(
|
||||||
|
0,
|
||||||
|
commandValueNode.getStart()
|
||||||
|
)}"${newCommand}"${playwrightConfigFileContents.slice(
|
||||||
|
commandValueNode.getEnd()
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const newCommand = `${
|
||||||
|
getPackageManagerCommand().exec
|
||||||
|
} nx run ${project}:preview`;
|
||||||
|
tree.write(
|
||||||
|
path,
|
||||||
|
`${playwrightConfigFileContents.slice(
|
||||||
|
0,
|
||||||
|
commandValueNode.getStart()
|
||||||
|
)}"${newCommand}"${playwrightConfigFileContents.slice(
|
||||||
|
commandValueNode.getEnd()
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
playwrightConfigFileContents = tree.read(path, 'utf-8');
|
||||||
|
ast = tsquery.ast(playwrightConfigFileContents);
|
||||||
|
|
||||||
|
const BASE_URL_SELECTOR =
|
||||||
|
'VariableDeclaration:has(Identifier[name=baseURL])';
|
||||||
|
const baseUrlNodes = tsquery(ast, BASE_URL_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
if (!baseUrlNodes.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const baseUrlNode = baseUrlNodes[0];
|
||||||
|
const newBaseUrlVariableDeclaration =
|
||||||
|
"baseURL = process.env['BASE_URL'] || 'http://localhost:4300';";
|
||||||
|
tree.write(
|
||||||
|
path,
|
||||||
|
`${playwrightConfigFileContents.slice(
|
||||||
|
0,
|
||||||
|
baseUrlNode.getStart()
|
||||||
|
)}${newBaseUrlVariableDeclaration}${playwrightConfigFileContents.slice(
|
||||||
|
baseUrlNode.getEnd()
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
|
||||||
|
playwrightConfigFileContents = tree.read(path, 'utf-8');
|
||||||
|
ast = tsquery.ast(playwrightConfigFileContents);
|
||||||
|
const WEB_SERVER_URL_SELECTOR =
|
||||||
|
'PropertyAssignment:has(Identifier[name=webServer]) PropertyAssignment:has(Identifier[name=url]) > StringLiteral';
|
||||||
|
const webServerUrlNodes = tsquery(ast, WEB_SERVER_URL_SELECTOR, {
|
||||||
|
visitAllChildren: true,
|
||||||
|
});
|
||||||
|
if (!webServerUrlNodes.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const webServerUrlNode = webServerUrlNodes[0];
|
||||||
|
const newWebServerUrl = "'http://localhost:4300'";
|
||||||
|
tree.write(
|
||||||
|
path,
|
||||||
|
`${playwrightConfigFileContents.slice(
|
||||||
|
0,
|
||||||
|
webServerUrlNode.getStart()
|
||||||
|
)}${newWebServerUrl}${playwrightConfigFileContents.slice(
|
||||||
|
webServerUrlNode.getEnd()
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
@ -18,6 +18,8 @@ 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, {
|
||||||
@ -27,6 +29,8 @@ 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<
|
||||||
|
|||||||
@ -210,6 +210,60 @@ nxViteTsPaths()],
|
|||||||
});"
|
});"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`app not nested should add vite types to tsconfigs 1`] = `
|
||||||
|
"
|
||||||
|
/// <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/my-app',
|
||||||
|
|
||||||
|
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/my-app',
|
||||||
|
emptyOutDir: true,
|
||||||
|
reportCompressedSize: true,
|
||||||
|
commonjsOptions: {
|
||||||
|
transformMixedEsModules: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
test: {
|
||||||
|
watch: false,
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
|
||||||
|
reporters: ['default'],
|
||||||
|
coverage: {
|
||||||
|
reportsDirectory: '../coverage/my-app',
|
||||||
|
provider: 'v8',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app not nested should generate files 1`] = `
|
exports[`app not nested should generate files 1`] = `
|
||||||
"// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
"// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
import styles from './app.module.css';
|
import styles from './app.module.css';
|
||||||
@ -228,6 +282,133 @@ export default App;
|
|||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`app not nested should setup playwright correctly for vite 1`] = `
|
||||||
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
const baseURL = process.env['BASE_URL'] || 'http://localhost:4300';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
baseURL,
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: 'npx nx run my-app:preview',
|
||||||
|
url: 'http://localhost:4300',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: "chromium",
|
||||||
|
use: { ...devices["Desktop Chrome"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "firefox",
|
||||||
|
use: { ...devices["Desktop Firefox"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "webkit",
|
||||||
|
use: { ...devices["Desktop Safari"] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Uncomment for mobile browsers support
|
||||||
|
/* {
|
||||||
|
name: 'Mobile Chrome',
|
||||||
|
use: { ...devices['Pixel 5'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mobile Safari',
|
||||||
|
use: { ...devices['iPhone 12'] },
|
||||||
|
}, */
|
||||||
|
|
||||||
|
// Uncomment for branded browsers
|
||||||
|
/* {
|
||||||
|
name: 'Microsoft Edge',
|
||||||
|
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
} */
|
||||||
|
],
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`app not nested should use preview vite types to tsconfigs 1`] = `
|
||||||
|
"
|
||||||
|
/// <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/my-app',
|
||||||
|
|
||||||
|
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/my-app',
|
||||||
|
emptyOutDir: true,
|
||||||
|
reportCompressedSize: true,
|
||||||
|
commonjsOptions: {
|
||||||
|
transformMixedEsModules: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
test: {
|
||||||
|
watch: false,
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
|
||||||
|
reporters: ['default'],
|
||||||
|
coverage: {
|
||||||
|
reportsDirectory: '../coverage/my-app',
|
||||||
|
provider: 'v8',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app setup React app with --bundler=vite should setup targets with vite configuration 1`] = `null`;
|
exports[`app setup React app with --bundler=vite should setup targets with vite configuration 1`] = `null`;
|
||||||
|
|
||||||
exports[`app should add custom webpack config 1`] = `
|
exports[`app should add custom webpack config 1`] = `
|
||||||
|
|||||||
@ -69,6 +69,78 @@ describe('app', () => {
|
|||||||
'@nx/react/typings/cssmodule.d.ts',
|
'@nx/react/typings/cssmodule.d.ts',
|
||||||
'@nx/react/typings/image.d.ts',
|
'@nx/react/typings/image.d.ts',
|
||||||
]);
|
]);
|
||||||
|
expect(appTree.read('my-app/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setup cypress correctly for vite', async () => {
|
||||||
|
await applicationGenerator(appTree, {
|
||||||
|
...schema,
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
addPlugin: true,
|
||||||
|
});
|
||||||
|
expect(appTree.read('my-app-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"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 my-app:serve","production":"nx run my-app:preview"},"ciWebServerCommand":"nx run my-app:preview","ciBaseUrl":"http://localhost:4300"}),
|
||||||
|
baseUrl: 'http://localhost:4200' }
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setup playwright correctly for vite', async () => {
|
||||||
|
const nxJson = readNxJson(appTree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: '@nx/vite/plugin',
|
||||||
|
options: {
|
||||||
|
buildTargetName: 'build',
|
||||||
|
previewTargetName: 'preview',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
updateNxJson(appTree, nxJson);
|
||||||
|
|
||||||
|
await applicationGenerator(appTree, {
|
||||||
|
...schema,
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
e2eTestRunner: 'playwright',
|
||||||
|
addPlugin: true,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
appTree.read('my-app-e2e/playwright.config.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should use preview vite types to tsconfigs', async () => {
|
||||||
|
await applicationGenerator(appTree, {
|
||||||
|
...schema,
|
||||||
|
bundler: 'vite',
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
});
|
||||||
|
const tsconfigApp = readJson(appTree, 'my-app/tsconfig.app.json');
|
||||||
|
expect(tsconfigApp.compilerOptions.types).toEqual([
|
||||||
|
'node',
|
||||||
|
'@nx/react/typings/cssmodule.d.ts',
|
||||||
|
'@nx/react/typings/image.d.ts',
|
||||||
|
'vite/client',
|
||||||
|
]);
|
||||||
|
const tsconfigSpec = readJson(appTree, 'my-app/tsconfig.spec.json');
|
||||||
|
expect(tsconfigSpec.compilerOptions.types).toEqual([
|
||||||
|
'vitest/globals',
|
||||||
|
'vitest/importMeta',
|
||||||
|
'vite/client',
|
||||||
|
'node',
|
||||||
|
'vitest',
|
||||||
|
'@nx/react/typings/cssmodule.d.ts',
|
||||||
|
'@nx/react/typings/image.d.ts',
|
||||||
|
]);
|
||||||
|
expect(appTree.read('my-app/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not overwrite default project if already set', async () => {
|
it('should not overwrite default project if already set', async () => {
|
||||||
|
|||||||
@ -16,19 +16,18 @@ export async function addE2e(
|
|||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: NormalizedSchema
|
options: NormalizedSchema
|
||||||
): Promise<GeneratorCallback> {
|
): Promise<GeneratorCallback> {
|
||||||
|
const hasNxBuildPlugin =
|
||||||
|
(options.bundler === 'webpack' && hasWebpackPlugin(tree)) ||
|
||||||
|
(options.bundler === 'vite' && hasVitePlugin(tree));
|
||||||
|
if (!hasNxBuildPlugin) {
|
||||||
|
await webStaticServeGenerator(tree, {
|
||||||
|
buildTarget: `${options.projectName}:build`,
|
||||||
|
targetName: 'serve-static',
|
||||||
|
spa: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
switch (options.e2eTestRunner) {
|
switch (options.e2eTestRunner) {
|
||||||
case 'cypress': {
|
case 'cypress': {
|
||||||
const hasNxBuildPlugin =
|
|
||||||
(options.bundler === 'webpack' && hasWebpackPlugin(tree)) ||
|
|
||||||
(options.bundler === 'vite' && hasVitePlugin(tree));
|
|
||||||
if (!hasNxBuildPlugin) {
|
|
||||||
await webStaticServeGenerator(tree, {
|
|
||||||
buildTarget: `${options.projectName}:build`,
|
|
||||||
targetName: 'serve-static',
|
|
||||||
spa: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const { configurationGenerator } = ensurePackage<
|
const { configurationGenerator } = ensurePackage<
|
||||||
typeof import('@nx/cypress')
|
typeof import('@nx/cypress')
|
||||||
>('@nx/cypress', nxVersion);
|
>('@nx/cypress', nxVersion);
|
||||||
@ -60,8 +59,10 @@ export async function addE2e(
|
|||||||
}
|
}
|
||||||
: undefined,
|
: undefined,
|
||||||
ciWebServerCommand: hasNxBuildPlugin
|
ciWebServerCommand: hasNxBuildPlugin
|
||||||
? `nx run ${options.projectName}:serve-static`
|
? `nx run ${options.projectName}:${options.e2eCiWebServerTarget}`
|
||||||
: undefined,
|
: undefined,
|
||||||
|
ciBaseUrl:
|
||||||
|
options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'playwright': {
|
case 'playwright': {
|
||||||
@ -83,10 +84,10 @@ 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: `${getPackageManagerCommand().exec} nx run ${
|
||||||
options.e2eWebServerTarget
|
options.projectName
|
||||||
} ${options.name}`,
|
}:${options.e2eCiWebServerTarget}`,
|
||||||
webServerAddress: options.e2eWebServerAddress,
|
webServerAddress: options.e2eCiBaseUrl,
|
||||||
rootProject: options.rootProject,
|
rootProject: options.rootProject,
|
||||||
addPlugin: options.addPlugin,
|
addPlugin: options.addPlugin,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -46,32 +46,43 @@ 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 e2eWebServerTarget = 'serve';
|
||||||
|
let e2eCiWebServerTarget =
|
||||||
|
options.bundler === 'vite' ? 'preview' : 'serve-static';
|
||||||
if (options.addPlugin) {
|
if (options.addPlugin) {
|
||||||
if (nxJson.plugins) {
|
if (nxJson.plugins) {
|
||||||
for (const plugin of nxJson.plugins) {
|
for (const plugin of nxJson.plugins) {
|
||||||
if (
|
if (
|
||||||
options.bundler === 'vite' &&
|
options.bundler === 'vite' &&
|
||||||
typeof plugin === 'object' &&
|
typeof plugin === 'object' &&
|
||||||
plugin.plugin === '@nx/vite/plugin' &&
|
plugin.plugin === '@nx/vite/plugin'
|
||||||
(plugin.options as VitePluginOptions).serveTargetName
|
|
||||||
) {
|
) {
|
||||||
e2eWebServerTarget = (plugin.options as VitePluginOptions)
|
e2eCiWebServerTarget =
|
||||||
.serveTargetName;
|
(plugin.options as VitePluginOptions)?.previewTargetName ??
|
||||||
|
e2eCiWebServerTarget;
|
||||||
|
|
||||||
|
e2eWebServerTarget =
|
||||||
|
(plugin.options as VitePluginOptions)?.serveTargetName ??
|
||||||
|
e2eWebServerTarget;
|
||||||
} else if (
|
} else if (
|
||||||
options.bundler === 'webpack' &&
|
options.bundler === 'webpack' &&
|
||||||
typeof plugin === 'object' &&
|
typeof plugin === 'object' &&
|
||||||
plugin.plugin === '@nx/webpack/plugin' &&
|
plugin.plugin === '@nx/webpack/plugin'
|
||||||
(plugin.options as WebpackPluginOptions).serveTargetName
|
|
||||||
) {
|
) {
|
||||||
e2eWebServerTarget = (plugin.options as WebpackPluginOptions)
|
e2eCiWebServerTarget =
|
||||||
.serveTargetName;
|
(plugin.options as WebpackPluginOptions)?.serveStaticTargetName ??
|
||||||
|
e2eCiWebServerTarget;
|
||||||
|
|
||||||
|
e2eWebServerTarget =
|
||||||
|
(plugin.options as WebpackPluginOptions)?.serveTargetName ??
|
||||||
|
e2eWebServerTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let e2ePort = options.devServerPort ?? 4200;
|
|
||||||
if (
|
if (
|
||||||
nxJson.targetDefaults?.[e2eWebServerTarget] &&
|
nxJson.targetDefaults?.[e2eWebServerTarget] &&
|
||||||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
|
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
|
||||||
@ -82,6 +93,10 @@ export async function normalizeOptions<T extends Schema = Schema>(
|
|||||||
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 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())
|
||||||
@ -104,6 +119,8 @@ export async function normalizeOptions<T extends Schema = Schema>(
|
|||||||
e2eProjectRoot,
|
e2eProjectRoot,
|
||||||
e2eWebServerAddress,
|
e2eWebServerAddress,
|
||||||
e2eWebServerTarget,
|
e2eWebServerTarget,
|
||||||
|
e2eCiWebServerTarget,
|
||||||
|
e2eCiBaseUrl,
|
||||||
e2ePort,
|
e2ePort,
|
||||||
parsedTags,
|
parsedTags,
|
||||||
fileName,
|
fileName,
|
||||||
|
|||||||
@ -38,6 +38,8 @@ export interface NormalizedSchema<T extends Schema = Schema> extends T {
|
|||||||
e2eProjectRoot: string;
|
e2eProjectRoot: string;
|
||||||
e2eWebServerAddress: string;
|
e2eWebServerAddress: string;
|
||||||
e2eWebServerTarget: string;
|
e2eWebServerTarget: string;
|
||||||
|
e2eCiWebServerTarget: string;
|
||||||
|
e2eCiBaseUrl: string;
|
||||||
e2ePort: number;
|
e2ePort: number;
|
||||||
parsedTags: string[];
|
parsedTags: string[];
|
||||||
fileName: string;
|
fileName: string;
|
||||||
|
|||||||
@ -32,6 +32,11 @@
|
|||||||
"version": "17.3.0-beta.0",
|
"version": "17.3.0-beta.0",
|
||||||
"description": "Move the vitest coverage thresholds in their own object if exists and add reporters.",
|
"description": "Move the vitest coverage thresholds in their own object if exists and add reporters.",
|
||||||
"implementation": "./src/migrations/update-17-3-0/vitest-coverage-and-reporters"
|
"implementation": "./src/migrations/update-17-3-0/vitest-coverage-and-reporters"
|
||||||
|
},
|
||||||
|
"update-19-6-0-add-depends-on-for-preview-server": {
|
||||||
|
"version": "19.6.0-beta.0",
|
||||||
|
"description": "Add dependsOn: [build] to preview targets using preview-server",
|
||||||
|
"implementation": "./src/migrations/update-19-6-0/add-depends-on-for-preview"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packageJsonUpdates": {
|
"packageJsonUpdates": {
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
import addDependsOnForPreview from './add-depends-on-for-preview';
|
||||||
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
import { readJson } from '@nx/devkit';
|
||||||
|
|
||||||
|
describe('addDependsOnForPreview', () => {
|
||||||
|
it('should update when preview target exists in project.json', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/app/project.json',
|
||||||
|
JSON.stringify({
|
||||||
|
name: 'app',
|
||||||
|
root: 'apps/app',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
preview: {
|
||||||
|
executor: '@nx/vite:preview-server',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await addDependsOnForPreview(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(readJson(tree, 'apps/app/project.json').targets)
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"preview": {
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"executor": "@nx/vite:preview-server",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not update when preview target exists in project.json and has a dependsOn already', async () => {
|
||||||
|
// ARRANGE
|
||||||
|
const tree = createTreeWithEmptyWorkspace();
|
||||||
|
tree.write(
|
||||||
|
'apps/app/project.json',
|
||||||
|
JSON.stringify({
|
||||||
|
name: 'app',
|
||||||
|
root: 'apps/app',
|
||||||
|
projectType: 'application',
|
||||||
|
targets: {
|
||||||
|
preview: {
|
||||||
|
dependsOn: ['build'],
|
||||||
|
executor: '@nx/vite:preview-server',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// ACT
|
||||||
|
await addDependsOnForPreview(tree);
|
||||||
|
|
||||||
|
// ASSERT
|
||||||
|
expect(readJson(tree, 'apps/app/project.json').targets)
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"preview": {
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"executor": "@nx/vite:preview-server",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
import {
|
||||||
|
type Tree,
|
||||||
|
formatFiles,
|
||||||
|
readProjectConfiguration,
|
||||||
|
updateProjectConfiguration,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
import { forEachExecutorOptions } from '@nx/devkit/src/generators/executor-options-utils';
|
||||||
|
import { VitePreviewServerExecutorOptions } from '../../executors/preview-server/schema';
|
||||||
|
|
||||||
|
export default async function (tree: Tree) {
|
||||||
|
forEachExecutorOptions<VitePreviewServerExecutorOptions>(
|
||||||
|
tree,
|
||||||
|
'@nx/vite:preview-server',
|
||||||
|
(_, projectName, targetName) => {
|
||||||
|
const project = readProjectConfiguration(tree, projectName);
|
||||||
|
project.targets[targetName].dependsOn ??= [];
|
||||||
|
if (project.targets[targetName].dependsOn.includes('build')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
project.targets[targetName].dependsOn.push('build');
|
||||||
|
updateProjectConfiguration(tree, projectName, project);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await formatFiles(tree);
|
||||||
|
}
|
||||||
@ -126,6 +126,9 @@ exports[`@nx/vite/plugin not root project should create nodes 1`] = `
|
|||||||
},
|
},
|
||||||
"preview-site": {
|
"preview-site": {
|
||||||
"command": "vite preview",
|
"command": "vite preview",
|
||||||
|
"dependsOn": [
|
||||||
|
"build-something",
|
||||||
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Locally preview Vite production build",
|
"description": "Locally preview Vite production build",
|
||||||
"help": {
|
"help": {
|
||||||
@ -209,6 +212,9 @@ exports[`@nx/vite/plugin root project should create nodes 1`] = `
|
|||||||
},
|
},
|
||||||
"preview": {
|
"preview": {
|
||||||
"command": "vite preview",
|
"command": "vite preview",
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Locally preview Vite production build",
|
"description": "Locally preview Vite production build",
|
||||||
"help": {
|
"help": {
|
||||||
|
|||||||
@ -193,7 +193,10 @@ async function buildViteTargets(
|
|||||||
// If running in library mode, then there is nothing to serve.
|
// If running in library mode, then there is nothing to serve.
|
||||||
if (!viteConfig.build?.lib) {
|
if (!viteConfig.build?.lib) {
|
||||||
targets[options.serveTargetName] = serveTarget(projectRoot);
|
targets[options.serveTargetName] = serveTarget(projectRoot);
|
||||||
targets[options.previewTargetName] = previewTarget(projectRoot);
|
targets[options.previewTargetName] = previewTarget(
|
||||||
|
projectRoot,
|
||||||
|
options.buildTargetName
|
||||||
|
);
|
||||||
targets[options.serveStaticTargetName] = serveStaticTarget(options) as {};
|
targets[options.serveStaticTargetName] = serveStaticTarget(options) as {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,9 +275,10 @@ function serveTarget(projectRoot: string) {
|
|||||||
return targetConfig;
|
return targetConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
function previewTarget(projectRoot: string) {
|
function previewTarget(projectRoot: string, buildTargetName) {
|
||||||
const targetConfig: TargetConfiguration = {
|
const targetConfig: TargetConfiguration = {
|
||||||
command: `vite preview`,
|
command: `vite preview`,
|
||||||
|
dependsOn: [buildTargetName],
|
||||||
options: {
|
options: {
|
||||||
cwd: joinPathFragments(projectRoot),
|
cwd: joinPathFragments(projectRoot),
|
||||||
},
|
},
|
||||||
|
|||||||
@ -204,6 +204,7 @@ export function addPreviewTarget(
|
|||||||
|
|
||||||
// Adds a preview target.
|
// Adds a preview target.
|
||||||
project.targets.preview = {
|
project.targets.preview = {
|
||||||
|
dependsOn: ['build'],
|
||||||
executor: '@nx/vite:preview-server',
|
executor: '@nx/vite:preview-server',
|
||||||
defaultConfiguration: 'development',
|
defaultConfiguration: 'development',
|
||||||
options: previewOptions,
|
options: previewOptions,
|
||||||
|
|||||||
@ -1,5 +1,157 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly for cypress 1`] = `
|
||||||
|
"{
|
||||||
|
"root": true,
|
||||||
|
"ignorePatterns": ["**/*"],
|
||||||
|
"plugins": ["@nx"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
||||||
|
"rules": {
|
||||||
|
"@nx/enforce-module-boundaries": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"enforceBuildableLibDependency": true,
|
||||||
|
"allow": [],
|
||||||
|
"depConstraints": [
|
||||||
|
{
|
||||||
|
"sourceTag": "*",
|
||||||
|
"onlyDependOnLibsWithTags": ["*"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx"],
|
||||||
|
"extends": ["plugin:@nx/typescript"],
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"files": ["*.js", "*.jsx"],
|
||||||
|
"extends": ["plugin:@nx/javascript"],
|
||||||
|
"rules": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly for cypress 2`] = `
|
||||||
|
"/// <reference types='vitest' />
|
||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
root: __dirname,
|
||||||
|
cacheDir: '../node_modules/.vite/test',
|
||||||
|
|
||||||
|
server: {
|
||||||
|
port: 4200,
|
||||||
|
host: 'localhost',
|
||||||
|
},
|
||||||
|
|
||||||
|
preview: {
|
||||||
|
port: 4300,
|
||||||
|
host: 'localhost',
|
||||||
|
},
|
||||||
|
|
||||||
|
plugins: [vue(), nxViteTsPaths()],
|
||||||
|
|
||||||
|
// Uncomment this if you are using workers.
|
||||||
|
// worker: {
|
||||||
|
// plugins: [ nxViteTsPaths() ],
|
||||||
|
// },
|
||||||
|
|
||||||
|
build: {
|
||||||
|
outDir: '../dist/test',
|
||||||
|
emptyOutDir: true,
|
||||||
|
reportCompressedSize: true,
|
||||||
|
commonjsOptions: {
|
||||||
|
transformMixedEsModules: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
test: {
|
||||||
|
watch: false,
|
||||||
|
globals: true,
|
||||||
|
environment: 'jsdom',
|
||||||
|
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||||
|
|
||||||
|
reporters: ['default'],
|
||||||
|
coverage: {
|
||||||
|
reportsDirectory: '../coverage/test',
|
||||||
|
provider: 'v8',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly for cypress 3`] = `
|
||||||
|
"{
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/vue3-essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/eslint-config-typescript",
|
||||||
|
"@vue/eslint-config-prettier/skip-formatting",
|
||||||
|
"../.eslintrc.json"
|
||||||
|
],
|
||||||
|
"ignorePatterns": ["!**/*"],
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": ["*.ts", "*.tsx", "*.js", "*.jsx", "*.vue"],
|
||||||
|
"rules": {
|
||||||
|
"vue/multi-word-component-names": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly for cypress 4`] = `
|
||||||
|
"import { describe, it, expect } from 'vitest';
|
||||||
|
|
||||||
|
import { mount } from '@vue/test-utils';
|
||||||
|
import App from './App.vue';
|
||||||
|
|
||||||
|
describe('App', () => {
|
||||||
|
it('renders properly', async () => {
|
||||||
|
const wrapper = mount(App, {});
|
||||||
|
|
||||||
|
expect(wrapper.text()).toContain('Welcome test 👋');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly for cypress 5`] = `
|
||||||
|
"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 test:serve',
|
||||||
|
production: 'nx run test:preview',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'nx run test:preview',
|
||||||
|
ciBaseUrl: 'http://localhost:4300',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`application generator should set up project correctly with PascalCase name 1`] = `
|
exports[`application generator should set up project correctly with PascalCase name 1`] = `
|
||||||
"{
|
"{
|
||||||
"root": true,
|
"root": true,
|
||||||
@ -290,6 +442,79 @@ describe('App', () => {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`application generator should set up project correctly with given options 5`] = `
|
exports[`application generator should set up project correctly with given options 5`] = `
|
||||||
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
const baseURL = process.env['BASE_URL'] || 'http://localhost:4300';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
baseURL,
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: 'npx nx run test:preview',
|
||||||
|
url: 'http://localhost:4300',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { ...devices['Desktop Firefox'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { ...devices['Desktop Safari'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Uncomment for mobile browsers support
|
||||||
|
/* {
|
||||||
|
name: 'Mobile Chrome',
|
||||||
|
use: { ...devices['Pixel 5'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mobile Safari',
|
||||||
|
use: { ...devices['iPhone 12'] },
|
||||||
|
}, */
|
||||||
|
|
||||||
|
// Uncomment for branded browsers
|
||||||
|
/* {
|
||||||
|
name: 'Microsoft Edge',
|
||||||
|
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
} */
|
||||||
|
],
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`application generator should set up project correctly with given options 6`] = `
|
||||||
[
|
[
|
||||||
".eslintignore",
|
".eslintignore",
|
||||||
".eslintrc.json",
|
".eslintrc.json",
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
import { Tree, readProjectConfiguration } from '@nx/devkit';
|
import {
|
||||||
|
Tree,
|
||||||
|
readProjectConfiguration,
|
||||||
|
readNxJson,
|
||||||
|
updateNxJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
|
|
||||||
import { applicationGenerator } from './application';
|
import { applicationGenerator } from './application';
|
||||||
import { Schema } from './schema';
|
import { Schema } from './schema';
|
||||||
@ -21,14 +26,55 @@ describe('application generator', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should set up project correctly with given options', async () => {
|
it('should set up project correctly with given options', async () => {
|
||||||
await applicationGenerator(tree, { ...options, unitTestRunner: 'vitest' });
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: '@nx/vite/plugin',
|
||||||
|
options: {
|
||||||
|
buildTargetName: 'build',
|
||||||
|
previewTargetName: 'preview',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
...options,
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
e2eTestRunner: 'playwright',
|
||||||
|
});
|
||||||
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
expect(tree.read('test/.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('test/.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
expect(tree.read('test/src/app/App.spec.ts', 'utf-8')).toMatchSnapshot();
|
expect(tree.read('test/src/app/App.spec.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(
|
||||||
|
tree.read('test-e2e/playwright.config.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
expect(listFiles(tree)).toMatchSnapshot();
|
expect(listFiles(tree)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should set up project correctly for cypress', async () => {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: '@nx/vite/plugin',
|
||||||
|
options: {
|
||||||
|
buildTargetName: 'build',
|
||||||
|
previewTargetName: 'preview',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
...options,
|
||||||
|
addPlugin: true,
|
||||||
|
unitTestRunner: 'vitest',
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
});
|
||||||
|
expect(tree.read('.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/.eslintrc.json', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test/src/app/App.spec.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
expect(tree.read('test-e2e/cypress.config.ts', 'utf-8')).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should set up project correctly with PascalCase name', async () => {
|
it('should set up project correctly with PascalCase name', async () => {
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
...options,
|
...options,
|
||||||
|
|||||||
@ -15,14 +15,24 @@ export async function addE2e(
|
|||||||
tree: Tree,
|
tree: Tree,
|
||||||
options: NormalizedSchema
|
options: NormalizedSchema
|
||||||
): Promise<GeneratorCallback> {
|
): Promise<GeneratorCallback> {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
const hasPlugin = nxJson.plugins?.find((p) =>
|
||||||
|
typeof p === 'string'
|
||||||
|
? p === '@nx/vite/plugin'
|
||||||
|
: p.plugin === '@nx/vite/plugin'
|
||||||
|
);
|
||||||
|
const e2eWebServerTarget = hasPlugin
|
||||||
|
? typeof hasPlugin === 'string'
|
||||||
|
? 'serve'
|
||||||
|
: (hasPlugin.options as any)?.serveTargetName ?? 'serve'
|
||||||
|
: 'serve';
|
||||||
|
const e2eCiWebServerTarget = hasPlugin
|
||||||
|
? typeof hasPlugin === 'string'
|
||||||
|
? 'preview'
|
||||||
|
: (hasPlugin.options as any)?.previewTargetName ?? 'preview'
|
||||||
|
: 'preview';
|
||||||
switch (options.e2eTestRunner) {
|
switch (options.e2eTestRunner) {
|
||||||
case 'cypress': {
|
case 'cypress': {
|
||||||
const nxJson = readNxJson(tree);
|
|
||||||
const hasPlugin = nxJson.plugins?.some((p) =>
|
|
||||||
typeof p === 'string'
|
|
||||||
? p === '@nx/vite/plugin'
|
|
||||||
: p.plugin === '@nx/vite/plugin'
|
|
||||||
);
|
|
||||||
if (!hasPlugin) {
|
if (!hasPlugin) {
|
||||||
await webStaticServeGenerator(tree, {
|
await webStaticServeGenerator(tree, {
|
||||||
buildTarget: `${options.projectName}:build`,
|
buildTarget: `${options.projectName}:build`,
|
||||||
@ -48,9 +58,17 @@ export async function addE2e(
|
|||||||
directory: 'src',
|
directory: 'src',
|
||||||
bundler: 'vite',
|
bundler: 'vite',
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
devServerTarget: `${options.projectName}:serve`,
|
devServerTarget: `${options.projectName}:${e2eWebServerTarget}`,
|
||||||
baseUrl: 'http://localhost:4200',
|
baseUrl: 'http://localhost:4200',
|
||||||
jsx: true,
|
jsx: true,
|
||||||
|
webServerCommands: hasPlugin
|
||||||
|
? {
|
||||||
|
default: `nx run ${options.projectName}:${e2eWebServerTarget}`,
|
||||||
|
production: `nx run ${options.projectName}:preview`,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
ciWebServerCommand: `nx run ${options.projectName}:${e2eCiWebServerTarget}`,
|
||||||
|
ciBaseUrl: 'http://localhost:4300',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'playwright': {
|
case 'playwright': {
|
||||||
@ -73,10 +91,10 @@ export async function addE2e(
|
|||||||
js: false,
|
js: false,
|
||||||
linter: options.linter,
|
linter: options.linter,
|
||||||
setParserOptionsProject: options.setParserOptionsProject,
|
setParserOptionsProject: options.setParserOptionsProject,
|
||||||
webServerCommand: `${getPackageManagerCommand().exec} nx serve ${
|
webServerCommand: `${getPackageManagerCommand().exec} nx run ${
|
||||||
options.name
|
options.projectName
|
||||||
}`,
|
}:${e2eCiWebServerTarget}`,
|
||||||
webServerAddress: 'http://localhost:4200',
|
webServerAddress: 'http://localhost:4300',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
case 'none':
|
case 'none':
|
||||||
|
|||||||
@ -1,5 +1,224 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`app not nested should generate files if bundler is vite 1`] = `
|
||||||
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
const baseURL = process.env['BASE_URL'] || 'http://localhost:4300';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
baseURL,
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: 'npx nx run my-app:preview',
|
||||||
|
url: 'http://localhost:4300',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { ...devices['Desktop Firefox'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { ...devices['Desktop Safari'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Uncomment for mobile browsers support
|
||||||
|
/* {
|
||||||
|
name: 'Mobile Chrome',
|
||||||
|
use: { ...devices['Pixel 5'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mobile Safari',
|
||||||
|
use: { ...devices['iPhone 12'] },
|
||||||
|
}, */
|
||||||
|
|
||||||
|
// Uncomment for branded browsers
|
||||||
|
/* {
|
||||||
|
name: 'Microsoft Edge',
|
||||||
|
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
} */
|
||||||
|
],
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`app not nested should setup playwright e2e project correctly for webpack 1`] = `
|
||||||
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
baseURL,
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: 'npx nx run cool-app:serve-static',
|
||||||
|
url: 'http://localhost:4200',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { ...devices['Desktop Firefox'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { ...devices['Desktop Safari'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Uncomment for mobile browsers support
|
||||||
|
/* {
|
||||||
|
name: 'Mobile Chrome',
|
||||||
|
use: { ...devices['Pixel 5'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mobile Safari',
|
||||||
|
use: { ...devices['iPhone 12'] },
|
||||||
|
}, */
|
||||||
|
|
||||||
|
// Uncomment for branded browsers
|
||||||
|
/* {
|
||||||
|
name: 'Microsoft Edge',
|
||||||
|
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
} */
|
||||||
|
],
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`app not nested should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false 1`] = `
|
||||||
|
"import { defineConfig, devices } from '@playwright/test';
|
||||||
|
import { nxE2EPreset } from '@nx/playwright/preset';
|
||||||
|
|
||||||
|
import { workspaceRoot } from '@nx/devkit';
|
||||||
|
|
||||||
|
// For CI, you may want to set BASE_URL to the deployed application.
|
||||||
|
const baseURL = process.env['BASE_URL'] || 'http://localhost:4300';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read environment variables from file.
|
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/
|
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
...nxE2EPreset(__filename, { testDir: './src' }),
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
baseURL,
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: 'npx nx run my-app:preview',
|
||||||
|
url: 'http://localhost:4300',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: { ...devices['Desktop Chrome'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { ...devices['Desktop Firefox'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { ...devices['Desktop Safari'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
// Uncomment for mobile browsers support
|
||||||
|
/* {
|
||||||
|
name: 'Mobile Chrome',
|
||||||
|
use: { ...devices['Pixel 5'] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Mobile Safari',
|
||||||
|
use: { ...devices['iPhone 12'] },
|
||||||
|
}, */
|
||||||
|
|
||||||
|
// Uncomment for branded browsers
|
||||||
|
/* {
|
||||||
|
name: 'Microsoft Edge',
|
||||||
|
use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Google Chrome',
|
||||||
|
use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
} */
|
||||||
|
],
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`app setup web app with --bundler=vite should setup vite configuration 1`] = `null`;
|
exports[`app setup web app with --bundler=vite should setup vite configuration 1`] = `null`;
|
||||||
|
|
||||||
exports[`app should setup eslint 1`] = `
|
exports[`app should setup eslint 1`] = `
|
||||||
|
|||||||
@ -106,6 +106,16 @@ describe('web app generator (legacy)', () => {
|
|||||||
"buildTarget": "my-app:build",
|
"buildTarget": "my-app:build",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"serve-static": {
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"executor": "@nx/web:file-server",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "my-app:build",
|
||||||
|
"spa": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"executor": "@nx/jest:jest",
|
"executor": "@nx/jest:jest",
|
||||||
"options": {
|
"options": {
|
||||||
@ -179,6 +189,9 @@ describe('web app generator (legacy)', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"defaultConfiguration": "development",
|
"defaultConfiguration": "development",
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
"executor": "@nx/vite:preview-server",
|
"executor": "@nx/vite:preview-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "my-vite-app:build",
|
"buildTarget": "my-vite-app:build",
|
||||||
@ -201,6 +214,16 @@ describe('web app generator (legacy)', () => {
|
|||||||
"buildTarget": "my-vite-app:build",
|
"buildTarget": "my-vite-app:build",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"serve-static": {
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
|
"executor": "@nx/web:file-server",
|
||||||
|
"options": {
|
||||||
|
"buildTarget": "my-vite-app:build",
|
||||||
|
"spa": true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"executor": "@nx/jest:jest",
|
"executor": "@nx/jest:jest",
|
||||||
"options": {
|
"options": {
|
||||||
|
|||||||
@ -1,7 +1,12 @@
|
|||||||
import 'nx/src/internal-testing-utils/mock-project-graph';
|
import 'nx/src/internal-testing-utils/mock-project-graph';
|
||||||
|
|
||||||
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
import { installedCypressVersion } from '@nx/cypress/src/utils/cypress-version';
|
||||||
import { readProjectConfiguration, Tree } from '@nx/devkit';
|
import {
|
||||||
|
readNxJson,
|
||||||
|
readProjectConfiguration,
|
||||||
|
Tree,
|
||||||
|
updateNxJson,
|
||||||
|
} from '@nx/devkit';
|
||||||
import { getProjects, readJson } from '@nx/devkit';
|
import { getProjects, readJson } from '@nx/devkit';
|
||||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||||
|
|
||||||
@ -158,11 +163,103 @@ describe('app', () => {
|
|||||||
expect(tree.exists('cool-app-e2e/playwright.config.ts')).toBeTruthy();
|
expect(tree.exists('cool-app-e2e/playwright.config.ts')).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should setup cypress e2e project correctly for vite', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'cool-app',
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
bundler: 'vite',
|
||||||
|
addPlugin: true,
|
||||||
|
});
|
||||||
|
expect(tree.read('cool-app-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"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 cool-app:serve',
|
||||||
|
production: 'nx run cool-app:preview',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'nx run cool-app:preview',
|
||||||
|
ciBaseUrl: 'http://localhost:4300',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setup cypress e2e project correctly for webpack', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'cool-app',
|
||||||
|
e2eTestRunner: 'cypress',
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
bundler: 'webpack',
|
||||||
|
addPlugin: true,
|
||||||
|
});
|
||||||
|
expect(tree.read('cool-app-e2e/cypress.config.ts', 'utf-8'))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||||
|
|
||||||
|
import { defineConfig } from 'cypress';
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
e2e: {
|
||||||
|
...nxE2EPreset(__filename, {
|
||||||
|
cypressDir: 'src',
|
||||||
|
webServerCommands: {
|
||||||
|
default: 'nx run cool-app:serve',
|
||||||
|
production: 'nx run cool-app:preview',
|
||||||
|
},
|
||||||
|
ciWebServerCommand: 'nx run cool-app:serve-static',
|
||||||
|
}),
|
||||||
|
baseUrl: 'http://localhost:4200',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
"
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should setup playwright e2e project correctly for webpack', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'cool-app',
|
||||||
|
e2eTestRunner: 'playwright',
|
||||||
|
unitTestRunner: 'none',
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
bundler: 'webpack',
|
||||||
|
addPlugin: true,
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
tree.read('cool-app-e2e/playwright.config.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should generate files if bundler is vite', async () => {
|
it('should generate files if bundler is vite', async () => {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
nxJson.plugins ??= [];
|
||||||
|
nxJson.plugins.push({
|
||||||
|
plugin: '@nx/vite/plugin',
|
||||||
|
options: {
|
||||||
|
buildTargetName: 'build',
|
||||||
|
previewTargetName: 'preview',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
updateNxJson(tree, nxJson);
|
||||||
await applicationGenerator(tree, {
|
await applicationGenerator(tree, {
|
||||||
name: 'my-app',
|
name: 'my-app',
|
||||||
bundler: 'vite',
|
bundler: 'vite',
|
||||||
projectNameAndRootFormat: 'as-provided',
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
e2eTestRunner: 'playwright',
|
||||||
|
addPlugin: true,
|
||||||
});
|
});
|
||||||
expect(tree.exists('my-app/src/main.ts')).toBeTruthy();
|
expect(tree.exists('my-app/src/main.ts')).toBeTruthy();
|
||||||
expect(tree.exists('my-app/src/app/app.element.ts')).toBeTruthy();
|
expect(tree.exists('my-app/src/app/app.element.ts')).toBeTruthy();
|
||||||
@ -179,7 +276,9 @@ describe('app', () => {
|
|||||||
path: './tsconfig.spec.json',
|
path: './tsconfig.spec.json',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
expect(tree.exists('my-app-e2e/playwright.config.ts')).toBeTruthy();
|
expect(
|
||||||
|
tree.read('my-app-e2e/playwright.config.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
expect(tree.exists('my-app/index.html')).toBeTruthy();
|
expect(tree.exists('my-app/index.html')).toBeTruthy();
|
||||||
expect(tree.exists('my-app/vite.config.ts')).toBeTruthy();
|
expect(tree.exists('my-app/vite.config.ts')).toBeTruthy();
|
||||||
expect(tree.exists(`my-app/environments/environment.ts`)).toBeFalsy();
|
expect(tree.exists(`my-app/environments/environment.ts`)).toBeFalsy();
|
||||||
@ -188,6 +287,18 @@ describe('app', () => {
|
|||||||
).toBeFalsy();
|
).toBeFalsy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should use serve target and port if bundler=vite, e2eTestRunner=playwright, addPlugin=false', async () => {
|
||||||
|
await applicationGenerator(tree, {
|
||||||
|
name: 'my-app',
|
||||||
|
bundler: 'vite',
|
||||||
|
projectNameAndRootFormat: 'as-provided',
|
||||||
|
e2eTestRunner: 'playwright',
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
tree.read('my-app-e2e/playwright.config.ts', 'utf-8')
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
it('should extend from root tsconfig.json when no tsconfig.base.json', async () => {
|
it('should extend from root tsconfig.json when no tsconfig.base.json', async () => {
|
||||||
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
||||||
|
|
||||||
|
|||||||
@ -41,6 +41,8 @@ import { addBuildTargetDefaults } from '@nx/devkit/src/generators/add-build-targ
|
|||||||
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command';
|
||||||
import { VitePluginOptions } from '@nx/vite/src/plugins/plugin';
|
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 { hasVitePlugin } from '../../utils/has-vite-plugin';
|
||||||
|
import staticServeConfiguration from '../static-serve/static-serve-configuration';
|
||||||
|
|
||||||
interface NormalizedSchema extends Schema {
|
interface NormalizedSchema extends Schema {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
@ -49,6 +51,8 @@ interface NormalizedSchema extends Schema {
|
|||||||
e2eProjectRoot: string;
|
e2eProjectRoot: string;
|
||||||
e2eWebServerAddress: string;
|
e2eWebServerAddress: string;
|
||||||
e2eWebServerTarget: string;
|
e2eWebServerTarget: string;
|
||||||
|
e2eCiWebServerTarget: string;
|
||||||
|
e2eCiBaseUrl: string;
|
||||||
e2ePort: number;
|
e2ePort: number;
|
||||||
parsedTags: string[];
|
parsedTags: string[];
|
||||||
}
|
}
|
||||||
@ -364,6 +368,15 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
tasks.push(lintTask);
|
tasks.push(lintTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasNxBuildPlugin =
|
||||||
|
(options.bundler === 'webpack' && hasWebpackPlugin(host)) ||
|
||||||
|
(options.bundler === 'vite' && hasVitePlugin(host));
|
||||||
|
if (!hasNxBuildPlugin) {
|
||||||
|
await staticServeConfiguration(host, {
|
||||||
|
buildTarget: `${options.projectName}:build`,
|
||||||
|
spa: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
if (options.e2eTestRunner === 'cypress') {
|
if (options.e2eTestRunner === 'cypress') {
|
||||||
const { configurationGenerator } = ensurePackage<
|
const { configurationGenerator } = ensurePackage<
|
||||||
typeof import('@nx/cypress')
|
typeof import('@nx/cypress')
|
||||||
@ -383,6 +396,16 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) {
|
|||||||
baseUrl: options.e2eWebServerAddress,
|
baseUrl: options.e2eWebServerAddress,
|
||||||
directory: 'src',
|
directory: 'src',
|
||||||
skipFormat: true,
|
skipFormat: true,
|
||||||
|
webServerCommands: hasNxBuildPlugin
|
||||||
|
? {
|
||||||
|
default: `nx run ${options.projectName}:${options.e2eWebServerTarget}`,
|
||||||
|
production: `nx run ${options.projectName}:preview`,
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
ciWebServerCommand: hasNxBuildPlugin
|
||||||
|
? `nx run ${options.projectName}:${options.e2eCiWebServerTarget}`
|
||||||
|
: undefined,
|
||||||
|
ciBaseUrl: options.bundler === 'vite' ? options.e2eCiBaseUrl : undefined,
|
||||||
});
|
});
|
||||||
tasks.push(cypressTask);
|
tasks.push(cypressTask);
|
||||||
} else if (options.e2eTestRunner === 'playwright') {
|
} else if (options.e2eTestRunner === 'playwright') {
|
||||||
@ -405,10 +428,10 @@ 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 ${
|
webServerCommand: `${getPackageManagerCommand().exec} nx run ${
|
||||||
options.e2eWebServerTarget
|
options.projectName
|
||||||
} ${options.name}`,
|
}:${options.e2eCiWebServerTarget}`,
|
||||||
webServerAddress: options.e2eWebServerAddress,
|
webServerAddress: options.e2eCiBaseUrl,
|
||||||
addPlugin: options.addPlugin,
|
addPlugin: options.addPlugin,
|
||||||
});
|
});
|
||||||
tasks.push(playwrightTask);
|
tasks.push(playwrightTask);
|
||||||
@ -493,32 +516,43 @@ async function normalizeOptions(
|
|||||||
nxJson.useInferencePlugins !== false;
|
nxJson.useInferencePlugins !== false;
|
||||||
options.addPlugin ??= addPluginDefault;
|
options.addPlugin ??= addPluginDefault;
|
||||||
|
|
||||||
|
let e2ePort = 4200;
|
||||||
|
|
||||||
let e2eWebServerTarget = 'serve';
|
let e2eWebServerTarget = 'serve';
|
||||||
|
let e2eCiWebServerTarget =
|
||||||
|
options.bundler === 'vite' ? 'preview' : 'serve-static';
|
||||||
if (options.addPlugin) {
|
if (options.addPlugin) {
|
||||||
if (nxJson.plugins) {
|
if (nxJson.plugins) {
|
||||||
for (const plugin of nxJson.plugins) {
|
for (const plugin of nxJson.plugins) {
|
||||||
if (
|
if (
|
||||||
options.bundler === 'vite' &&
|
options.bundler === 'vite' &&
|
||||||
typeof plugin === 'object' &&
|
typeof plugin === 'object' &&
|
||||||
plugin.plugin === '@nx/vite/plugin' &&
|
plugin.plugin === '@nx/vite/plugin'
|
||||||
(plugin.options as VitePluginOptions).serveTargetName
|
|
||||||
) {
|
) {
|
||||||
e2eWebServerTarget = (plugin.options as VitePluginOptions)
|
e2eCiWebServerTarget =
|
||||||
.serveTargetName;
|
(plugin.options as VitePluginOptions)?.previewTargetName ??
|
||||||
|
e2eCiWebServerTarget;
|
||||||
|
|
||||||
|
e2eWebServerTarget =
|
||||||
|
(plugin.options as VitePluginOptions)?.serveTargetName ??
|
||||||
|
e2eWebServerTarget;
|
||||||
} else if (
|
} else if (
|
||||||
options.bundler === 'webpack' &&
|
options.bundler === 'webpack' &&
|
||||||
typeof plugin === 'object' &&
|
typeof plugin === 'object' &&
|
||||||
plugin.plugin === '@nx/webpack/plugin' &&
|
plugin.plugin === '@nx/webpack/plugin'
|
||||||
(plugin.options as WebpackPluginOptions).serveTargetName
|
|
||||||
) {
|
) {
|
||||||
e2eWebServerTarget = (plugin.options as WebpackPluginOptions)
|
e2eCiWebServerTarget =
|
||||||
.serveTargetName;
|
(plugin.options as WebpackPluginOptions)?.serveStaticTargetName ??
|
||||||
|
e2eCiWebServerTarget;
|
||||||
|
|
||||||
|
e2eWebServerTarget =
|
||||||
|
(plugin.options as WebpackPluginOptions)?.serveTargetName ??
|
||||||
|
e2eWebServerTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let e2ePort = 4200;
|
|
||||||
if (
|
if (
|
||||||
nxJson.targetDefaults?.[e2eWebServerTarget] &&
|
nxJson.targetDefaults?.[e2eWebServerTarget] &&
|
||||||
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
|
nxJson.targetDefaults?.[e2eWebServerTarget].options?.port
|
||||||
@ -529,6 +563,10 @@ async function normalizeOptions(
|
|||||||
const e2eProjectName = `${appProjectName}-e2e`;
|
const e2eProjectName = `${appProjectName}-e2e`;
|
||||||
const e2eProjectRoot = `${appProjectRoot}-e2e`;
|
const e2eProjectRoot = `${appProjectRoot}-e2e`;
|
||||||
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
|
const e2eWebServerAddress = `http://localhost:${e2ePort}`;
|
||||||
|
const e2eCiBaseUrl =
|
||||||
|
options.bundler === 'vite'
|
||||||
|
? 'http://localhost:4300'
|
||||||
|
: `http://localhost:${e2ePort}`;
|
||||||
|
|
||||||
const npmScope = getNpmScope(host);
|
const npmScope = getNpmScope(host);
|
||||||
|
|
||||||
@ -554,6 +592,8 @@ async function normalizeOptions(
|
|||||||
e2eProjectName,
|
e2eProjectName,
|
||||||
e2eWebServerAddress,
|
e2eWebServerAddress,
|
||||||
e2eWebServerTarget,
|
e2eWebServerTarget,
|
||||||
|
e2eCiWebServerTarget,
|
||||||
|
e2eCiBaseUrl,
|
||||||
e2ePort,
|
e2ePort,
|
||||||
parsedTags,
|
parsedTags,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -25,6 +25,9 @@ describe('Static serve configuration generator', () => {
|
|||||||
expect(readProjectConfiguration(tree, 'react-app').targets['serve-static'])
|
expect(readProjectConfiguration(tree, 'react-app').targets['serve-static'])
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
"executor": "@nx/web:file-server",
|
"executor": "@nx/web:file-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "react-app:build",
|
"buildTarget": "react-app:build",
|
||||||
@ -40,6 +43,9 @@ describe('Static serve configuration generator', () => {
|
|||||||
readProjectConfiguration(tree, 'angular-app').targets['serve-static']
|
readProjectConfiguration(tree, 'angular-app').targets['serve-static']
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
"executor": "@nx/web:file-server",
|
"executor": "@nx/web:file-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "angular-app:build",
|
"buildTarget": "angular-app:build",
|
||||||
@ -54,6 +60,9 @@ describe('Static serve configuration generator', () => {
|
|||||||
expect(readProjectConfiguration(tree, 'storybook').targets['serve-static'])
|
expect(readProjectConfiguration(tree, 'storybook').targets['serve-static'])
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
"dependsOn": [
|
||||||
|
"build-storybook",
|
||||||
|
],
|
||||||
"executor": "@nx/web:file-server",
|
"executor": "@nx/web:file-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "storybook:build-storybook",
|
"buildTarget": "storybook:build-storybook",
|
||||||
@ -75,6 +84,9 @@ describe('Static serve configuration generator', () => {
|
|||||||
readProjectConfiguration(tree, 'react-app').targets['serve-static-custom']
|
readProjectConfiguration(tree, 'react-app').targets['serve-static-custom']
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
"executor": "@nx/web:file-server",
|
"executor": "@nx/web:file-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "react-app:build",
|
"buildTarget": "react-app:build",
|
||||||
@ -101,6 +113,9 @@ describe('Static serve configuration generator', () => {
|
|||||||
readProjectConfiguration(tree, 'angular-app').targets['serve-static']
|
readProjectConfiguration(tree, 'angular-app').targets['serve-static']
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
{
|
{
|
||||||
|
"dependsOn": [
|
||||||
|
"build",
|
||||||
|
],
|
||||||
"executor": "@nx/web:file-server",
|
"executor": "@nx/web:file-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "angular-app:build",
|
"buildTarget": "angular-app:build",
|
||||||
|
|||||||
@ -23,6 +23,7 @@ interface NormalizedWebStaticServeSchema extends WebStaticServeSchema {
|
|||||||
projectName: string;
|
projectName: string;
|
||||||
targetName: string;
|
targetName: string;
|
||||||
spa: boolean;
|
spa: boolean;
|
||||||
|
parsedBuildTarget: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function webStaticServeGenerator(
|
export async function webStaticServeGenerator(
|
||||||
@ -49,6 +50,7 @@ async function normalizeOptions(
|
|||||||
targetName: options.targetName || 'serve-static',
|
targetName: options.targetName || 'serve-static',
|
||||||
projectName: target.project,
|
projectName: target.project,
|
||||||
spa: options.spa ?? true,
|
spa: options.spa ?? true,
|
||||||
|
parsedBuildTarget: target.target,
|
||||||
};
|
};
|
||||||
|
|
||||||
const projectConfig = readProjectConfiguration(tree, target.project);
|
const projectConfig = readProjectConfiguration(tree, target.project);
|
||||||
@ -109,6 +111,7 @@ function addStaticConfig(tree: Tree, opts: NormalizedWebStaticServeSchema) {
|
|||||||
Partial<FileServerExecutorSchema>
|
Partial<FileServerExecutorSchema>
|
||||||
> = {
|
> = {
|
||||||
executor: '@nx/web:file-server',
|
executor: '@nx/web:file-server',
|
||||||
|
dependsOn: [opts.parsedBuildTarget],
|
||||||
options: {
|
options: {
|
||||||
buildTarget: opts.buildTarget,
|
buildTarget: opts.buildTarget,
|
||||||
staticFilePath: opts.outputPath,
|
staticFilePath: opts.outputPath,
|
||||||
|
|||||||
10
packages/web/src/utils/has-vite-plugin.ts
Normal file
10
packages/web/src/utils/has-vite-plugin.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { readNxJson, Tree } from '@nx/devkit';
|
||||||
|
|
||||||
|
export function hasVitePlugin(tree: Tree) {
|
||||||
|
const nxJson = readNxJson(tree);
|
||||||
|
return !!nxJson.plugins?.some((p) =>
|
||||||
|
typeof p === 'string'
|
||||||
|
? p === '@nx/vite/plugin'
|
||||||
|
: p.plugin === '@nx/vite/plugin'
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -105,6 +105,9 @@ exports[`@nx/webpack/plugin should create nodes 1`] = `
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"serve-static": {
|
"serve-static": {
|
||||||
|
"dependsOn": [
|
||||||
|
"build-something",
|
||||||
|
],
|
||||||
"executor": "@nx/web:file-server",
|
"executor": "@nx/web:file-server",
|
||||||
"options": {
|
"options": {
|
||||||
"buildTarget": "build-something",
|
"buildTarget": "build-something",
|
||||||
|
|||||||
@ -235,6 +235,7 @@ async function createWebpackTargets(
|
|||||||
};
|
};
|
||||||
|
|
||||||
targets[options.serveStaticTargetName] = {
|
targets[options.serveStaticTargetName] = {
|
||||||
|
dependsOn: [options.buildTargetName],
|
||||||
executor: '@nx/web:file-server',
|
executor: '@nx/web:file-server',
|
||||||
options: {
|
options: {
|
||||||
buildTarget: options.buildTargetName,
|
buildTarget: options.buildTargetName,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user