diff --git a/docs/generated/manifests/nx-api.json b/docs/generated/manifests/nx-api.json index ed22d993a2..aa23983c06 100644 --- a/docs/generated/manifests/nx-api.json +++ b/docs/generated/manifests/nx-api.json @@ -444,6 +444,16 @@ } }, "migrations": { + "/nx-api/angular/migrations/set-continuous-option": { + "description": "Set the `continuous` option to `true` for continuous tasks.", + "file": "generated/packages/angular/migrations/set-continuous-option.json", + "hidden": false, + "name": "set-continuous-option", + "version": "21.0.0-beta.3", + "originalFilePath": "/packages/angular", + "path": "/nx-api/angular/migrations/set-continuous-option", + "type": "migration" + }, "/nx-api/angular/migrations/20.5.0-angular-eslint-package-updates": { "description": "", "file": "generated/packages/angular/migrations/20.5.0-angular-eslint-package-updates.json", diff --git a/docs/generated/packages-metadata.json b/docs/generated/packages-metadata.json index 00e4c6e78f..2449c88983 100644 --- a/docs/generated/packages-metadata.json +++ b/docs/generated/packages-metadata.json @@ -439,6 +439,16 @@ } ], "migrations": [ + { + "description": "Set the `continuous` option to `true` for continuous tasks.", + "file": "generated/packages/angular/migrations/set-continuous-option.json", + "hidden": false, + "name": "set-continuous-option", + "version": "21.0.0-beta.3", + "originalFilePath": "/packages/angular", + "path": "angular/migrations/set-continuous-option", + "type": "migration" + }, { "description": "", "file": "generated/packages/angular/migrations/20.5.0-angular-eslint-package-updates.json", diff --git a/docs/generated/packages/angular/migrations/set-continuous-option.json b/docs/generated/packages/angular/migrations/set-continuous-option.json new file mode 100644 index 0000000000..2da29a5959 --- /dev/null +++ b/docs/generated/packages/angular/migrations/set-continuous-option.json @@ -0,0 +1,14 @@ +{ + "name": "set-continuous-option", + "cli": "nx", + "version": "21.0.0-beta.3", + "description": "Set the `continuous` option to `true` for continuous tasks.", + "factory": "./src/migrations/update-21-0-0/set-continuous-option", + "implementation": "/packages/angular/src/migrations/update-21-0-0/set-continuous-option.ts", + "aliases": [], + "hidden": false, + "path": "/packages/angular", + "schema": null, + "type": "migration", + "examplesFile": "#### Set `continuous` Option for Continuous Tasks\n\nThis migration sets the `continuous` option to `true` for tasks that are known to run continuously, and only if the option is not already explicitly set.\n\nSpecifically, it updates Angular targets using the following executors:\n\n- `@angular-devkit/build-angular:dev-server`\n- `@angular-devkit/build-angular:ssr-dev-server`\n- `@nx/angular:dev-server`\n- `@nx/angular:module-federation-dev-server`\n- `@nx/angular:module-federation-dev-ssr`\n\n#### Examples\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"apps/app1/project.json\" %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"executor\": \"@angular-devkit/build-angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"apps/app1/project.json\" highlightLines=[6] %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"continuous\": true,\n \"executor\": \"@angular-devkit/build-angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n\nWhen a target is already explicitly configured with a `continuous` option, the migration will not modify it:\n\n{% tabs %}\n{% tab label=\"Before\" %}\n\n```json {% fileName=\"apps/app1/project.json\" highlightLines=[6] %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"continuous\": false,\n \"executor\": \"@nx/angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n\n{% tab label=\"After\" %}\n\n```json {% fileName=\"apps/app1/project.json\" highlightLines=[6] %}\n{\n // ...\n \"targets\": {\n // ...\n \"serve\": {\n \"continuous\": false,\n \"executor\": \"@nx/angular:dev-server\",\n \"options\": {\n \"buildTarget\": \"my-app:build\",\n \"port\": 4200\n }\n }\n }\n}\n```\n\n{% /tab %}\n{% /tabs %}\n" +} diff --git a/packages/angular/migrations.json b/packages/angular/migrations.json index 4a9d58ae58..84c32ff159 100644 --- a/packages/angular/migrations.json +++ b/packages/angular/migrations.json @@ -356,6 +356,12 @@ }, "description": "Update the @angular/cli package version to ~19.2.0.", "factory": "./src/migrations/update-20-5-0/update-angular-cli" + }, + "set-continuous-option": { + "cli": "nx", + "version": "21.0.0-beta.3", + "description": "Set the `continuous` option to `true` for continuous tasks.", + "factory": "./src/migrations/update-21-0-0/set-continuous-option" } }, "packageJsonUpdates": { diff --git a/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap index 8695e7805f..52f76fbc54 100644 --- a/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/angular/src/generators/application/__snapshots__/application.spec.ts.snap @@ -737,10 +737,12 @@ exports[`app nested should create project configs 1`] = ` "buildTarget": "my-app:build:production", }, }, + "continuous": true, "defaultConfiguration": "development", "executor": "@angular-devkit/build-angular:dev-server", }, "serve-static": { + "continuous": true, "executor": "@nx/web:file-server", "options": { "buildTarget": "my-app:build", @@ -853,10 +855,12 @@ exports[`app not nested should create project configs 1`] = ` "buildTarget": "my-app:build:production", }, }, + "continuous": true, "defaultConfiguration": "development", "executor": "@angular-devkit/build-angular:dev-server", }, "serve-static": { + "continuous": true, "executor": "@nx/web:file-server", "options": { "buildTarget": "my-app:build", diff --git a/packages/angular/src/generators/application/lib/add-serve-static-target.ts b/packages/angular/src/generators/application/lib/add-serve-static-target.ts index d3b01441bd..aa7bdcbe54 100644 --- a/packages/angular/src/generators/application/lib/add-serve-static-target.ts +++ b/packages/angular/src/generators/application/lib/add-serve-static-target.ts @@ -30,6 +30,7 @@ function addFileServerTarget( const projectConfig = readProjectConfiguration(tree, options.name); projectConfig.targets[targetName] = { + continuous: true, executor: '@nx/web:file-server', options: { buildTarget: `${options.name}:build`, diff --git a/packages/angular/src/generators/application/lib/create-project.ts b/packages/angular/src/generators/application/lib/create-project.ts index 352630a27f..1155e15b1b 100644 --- a/packages/angular/src/generators/application/lib/create-project.ts +++ b/packages/angular/src/generators/application/lib/create-project.ts @@ -93,6 +93,7 @@ export function createProject(tree: Tree, options: NormalizedSchema) { defaultConfiguration: 'production', }, serve: { + continuous: true, executor: '@angular-devkit/build-angular:dev-server', options: options.port ? { diff --git a/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap b/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap index 92d9ebe598..a1c03bfb80 100644 --- a/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap +++ b/packages/angular/src/generators/host/__snapshots__/host.spec.ts.snap @@ -251,6 +251,7 @@ exports[`Host App Generator --ssr should generate the correct files 10`] = ` "serverTarget": "test:server:production", }, }, + "continuous": true, "defaultConfiguration": "development", "executor": "@nx/angular:module-federation-dev-ssr", } @@ -475,6 +476,7 @@ exports[`Host App Generator --ssr should generate the correct files for standalo "serverTarget": "test:server:production", }, }, + "continuous": true, "defaultConfiguration": "development", "executor": "@nx/angular:module-federation-dev-ssr", } @@ -700,6 +702,7 @@ exports[`Host App Generator --ssr should generate the correct files for standalo "serverTarget": "test:server:production", }, }, + "continuous": true, "defaultConfiguration": "development", "executor": "@nx/angular:module-federation-dev-ssr", } @@ -910,6 +913,7 @@ exports[`Host App Generator --ssr should generate the correct files when --types "serverTarget": "test:server:production", }, }, + "continuous": true, "defaultConfiguration": "development", "executor": "@nx/angular:module-federation-dev-ssr", } diff --git a/packages/angular/src/generators/setup-mf/lib/setup-serve-target.ts b/packages/angular/src/generators/setup-mf/lib/setup-serve-target.ts index 2308567865..f75db63881 100644 --- a/packages/angular/src/generators/setup-mf/lib/setup-serve-target.ts +++ b/packages/angular/src/generators/setup-mf/lib/setup-serve-target.ts @@ -23,6 +23,7 @@ export function setupServeTarget(host: Tree, options: Schema) { if (options.mfType === 'remote') { appConfig.targets['serve-static'] = { + continuous: true, executor: '@nx/web:file-server', defaultConfiguration: 'production', options: { diff --git a/packages/angular/src/generators/setup-ssr/lib/update-project-config.ts b/packages/angular/src/generators/setup-ssr/lib/update-project-config.ts index 793c654fd0..e3d1953b35 100644 --- a/packages/angular/src/generators/setup-ssr/lib/update-project-config.ts +++ b/packages/angular/src/generators/setup-ssr/lib/update-project-config.ts @@ -118,6 +118,7 @@ export function updateProjectConfigForBrowserBuilder( }; projectConfig.targets['serve-ssr'] = { + continuous: true, executor: '@angular-devkit/build-angular:ssr-dev-server', configurations: { development: { diff --git a/packages/angular/src/migrations/update-21-0-0/set-continuous-option.md b/packages/angular/src/migrations/update-21-0-0/set-continuous-option.md new file mode 100644 index 0000000000..5241ef3dff --- /dev/null +++ b/packages/angular/src/migrations/update-21-0-0/set-continuous-option.md @@ -0,0 +1,102 @@ +#### Set `continuous` Option for Continuous Tasks + +This migration sets the `continuous` option to `true` for tasks that are known to run continuously, and only if the option is not already explicitly set. + +Specifically, it updates Angular targets using the following executors: + +- `@angular-devkit/build-angular:dev-server` +- `@angular-devkit/build-angular:ssr-dev-server` +- `@nx/angular:dev-server` +- `@nx/angular:module-federation-dev-server` +- `@nx/angular:module-federation-dev-ssr` + +#### Examples + +{% tabs %} +{% tab label="Before" %} + +```json {% fileName="apps/app1/project.json" %} +{ + // ... + "targets": { + // ... + "serve": { + "executor": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "my-app:build", + "port": 4200 + } + } + } +} +``` + +{% /tab %} + +{% tab label="After" %} + +```json {% fileName="apps/app1/project.json" highlightLines=[6] %} +{ + // ... + "targets": { + // ... + "serve": { + "continuous": true, + "executor": "@angular-devkit/build-angular:dev-server", + "options": { + "buildTarget": "my-app:build", + "port": 4200 + } + } + } +} +``` + +{% /tab %} +{% /tabs %} + +When a target is already explicitly configured with a `continuous` option, the migration will not modify it: + +{% tabs %} +{% tab label="Before" %} + +```json {% fileName="apps/app1/project.json" highlightLines=[6] %} +{ + // ... + "targets": { + // ... + "serve": { + "continuous": false, + "executor": "@nx/angular:dev-server", + "options": { + "buildTarget": "my-app:build", + "port": 4200 + } + } + } +} +``` + +{% /tab %} + +{% tab label="After" %} + +```json {% fileName="apps/app1/project.json" highlightLines=[6] %} +{ + // ... + "targets": { + // ... + "serve": { + "continuous": false, + "executor": "@nx/angular:dev-server", + "options": { + "buildTarget": "my-app:build", + "port": 4200 + } + } + } +} +``` + +{% /tab %} +{% /tabs %} diff --git a/packages/angular/src/migrations/update-21-0-0/set-continuous-option.spec.ts b/packages/angular/src/migrations/update-21-0-0/set-continuous-option.spec.ts new file mode 100644 index 0000000000..e6fb8788e8 --- /dev/null +++ b/packages/angular/src/migrations/update-21-0-0/set-continuous-option.spec.ts @@ -0,0 +1,78 @@ +import { + addProjectConfiguration, + readProjectConfiguration, + type Tree, +} from '@nx/devkit'; +import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; +import migration, { continuousExecutors } from './set-continuous-option'; + +jest.mock('@nx/devkit', () => ({ + ...jest.requireActual('@nx/devkit'), + formatFiles: jest.fn(), +})); + +describe('set-continuous-option migration', () => { + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); + }); + + it.each([...continuousExecutors])( + 'should set continuous option to true for targets using "%s" executor', + async (executor) => { + addProjectConfiguration(tree, 'app1', { + root: 'apps/app1', + projectType: 'application', + targets: { + serve: { + executor, + options: {}, + }, + }, + }); + + await migration(tree); + + const project = readProjectConfiguration(tree, 'app1'); + expect(project.targets.serve.continuous).toBe(true); + } + ); + + it('should not change continuous option when it is already set', async () => { + addProjectConfiguration(tree, 'app1', { + root: 'apps/app1', + projectType: 'application', + targets: { + serve: { + executor: '@angular-devkit/build-angular:dev-server', + continuous: false, + options: {}, + }, + }, + }); + + await migration(tree); + + const project = readProjectConfiguration(tree, 'app1'); + expect(project.targets.serve.continuous).toBe(false); + }); + + it('should not modify targets using other executors', async () => { + addProjectConfiguration(tree, 'app1', { + root: 'apps/app1', + projectType: 'application', + targets: { + build: { + executor: '@angular-devkit/build-angular:browser', + options: {}, + }, + }, + }); + + await migration(tree); + + const project = readProjectConfiguration(tree, 'app1'); + expect(project.targets.build.continuous).toBeUndefined(); + }); +}); diff --git a/packages/angular/src/migrations/update-21-0-0/set-continuous-option.ts b/packages/angular/src/migrations/update-21-0-0/set-continuous-option.ts new file mode 100644 index 0000000000..2b9aee29b6 --- /dev/null +++ b/packages/angular/src/migrations/update-21-0-0/set-continuous-option.ts @@ -0,0 +1,38 @@ +import { + formatFiles, + getProjects, + type Tree, + updateProjectConfiguration, +} from '@nx/devkit'; + +export const continuousExecutors = new Set([ + '@angular-devkit/build-angular:dev-server', + '@angular-devkit/build-angular:ssr-dev-server', + '@nx/angular:dev-server', + '@nx/angular:module-federation-dev-server', + '@nx/angular:module-federation-dev-ssr', +]); + +export default async function (tree: Tree) { + const projects = getProjects(tree); + + for (const [projectName, projectConfig] of projects) { + let updated = false; + + for (const targetConfig of Object.values(projectConfig.targets ?? {})) { + if ( + continuousExecutors.has(targetConfig.executor) && + targetConfig.continuous === undefined + ) { + targetConfig.continuous = true; + updated = true; + } + } + + if (updated) { + updateProjectConfiguration(tree, projectName, projectConfig); + } + } + + await formatFiles(tree); +} diff --git a/packages/angular/src/plugins/plugin.spec.ts b/packages/angular/src/plugins/plugin.spec.ts index 122b8bd668..2d8dc8f67e 100644 --- a/packages/angular/src/plugins/plugin.spec.ts +++ b/packages/angular/src/plugins/plugin.spec.ts @@ -137,6 +137,7 @@ describe('@nx/angular/plugin', () => { "command": "ng run my-app:serve:production", }, }, + "continuous": true, "metadata": { "description": "Run the "serve" target for "my-app".", "help": { @@ -435,6 +436,7 @@ describe('@nx/angular/plugin', () => { "command": "ng run org1-app1:serve:production", }, }, + "continuous": true, "metadata": { "description": "Run the "serve" target for "org1-app1".", "help": { @@ -619,6 +621,7 @@ describe('@nx/angular/plugin', () => { "command": "ng run org2-app1:serve:production", }, }, + "continuous": true, "metadata": { "description": "Run the "serve" target for "org2-app1".", "help": { diff --git a/packages/angular/src/plugins/plugin.ts b/packages/angular/src/plugins/plugin.ts index 92f02135c7..6025bf0f93 100644 --- a/packages/angular/src/plugins/plugin.ts +++ b/packages/angular/src/plugins/plugin.ts @@ -208,6 +208,7 @@ async function buildAngularProjects( namedInputs ); } else if (knownExecutors.devServer.has(angularTarget.builder)) { + targets[nxTargetName].continuous = true; targets[nxTargetName].metadata.help.example.options = { port: 4201 }; } else if (knownExecutors.extractI18n.has(angularTarget.builder)) { targets[nxTargetName].metadata.help.example.options = { @@ -233,6 +234,7 @@ async function buildAngularProjects( namedInputs ); } else if (knownExecutors.serveSsr.has(angularTarget.builder)) { + targets[nxTargetName].continuous = true; targets[nxTargetName].metadata.help.example.options = { port: 4201 }; } else if (knownExecutors.prerender.has(angularTarget.builder)) { prerenderTargets.push({ target: nxTargetName, project: projectName }); diff --git a/packages/cypress/plugins/cypress-preset.ts b/packages/cypress/plugins/cypress-preset.ts index c279e1aa89..adc50cdd57 100644 --- a/packages/cypress/plugins/cypress-preset.ts +++ b/packages/cypress/plugins/cypress-preset.ts @@ -139,6 +139,7 @@ export function nxE2EPreset( webServerCommands: options?.webServerCommands, ciWebServerCommand: options?.ciWebServerCommand, ciBaseUrl: options?.ciBaseUrl, + reuseExistingServer: options?.webServerConfig?.reuseExistingServer, }, async setupNodeEvents(on, config) { diff --git a/packages/cypress/src/plugins/plugin.spec.ts b/packages/cypress/src/plugins/plugin.spec.ts index 71f68af85c..0237515759 100644 --- a/packages/cypress/src/plugins/plugin.spec.ts +++ b/packages/cypress/src/plugins/plugin.spec.ts @@ -11,6 +11,8 @@ describe('@nx/cypress/plugin', () => { let createNodesFunction = createNodesV2[1]; let context: CreateNodesContext; let tempFs: TempFs; + let cwd = process.cwd(); + let originalCacheProjectGraph: string | undefined; beforeEach(async () => { tempFs = new TempFs('cypress-plugin'); @@ -37,12 +39,18 @@ describe('@nx/cypress/plugin', () => { workspaceRoot: tempFs.tempDir, configFiles: [], }; + + process.chdir(tempFs.tempDir); + originalCacheProjectGraph = process.env.NX_CACHE_PROJECT_GRAPH; + process.env.NX_CACHE_PROJECT_GRAPH = 'false'; }); afterEach(() => { jest.resetModules(); tempFs.cleanup(); tempFs = null; + process.chdir(cwd); + process.env.NX_CACHE_PROJECT_GRAPH = originalCacheProjectGraph; }); afterAll(() => { @@ -90,6 +98,14 @@ describe('@nx/cypress/plugin', () => { "command": "cypress run --env webServerCommand="nx run my-app:serve:production"", }, }, + "dependsOn": [ + { + "projects": [ + "my-app", + ], + "target": "serve", + }, + ], "inputs": [ "default", "^production", @@ -124,7 +140,6 @@ describe('@nx/cypress/plugin', () => { "{projectRoot}/dist/videos", "{projectRoot}/dist/screenshots", ], - "parallelism": false, }, "open-cypress": { "command": "cypress open", @@ -451,6 +466,399 @@ describe('@nx/cypress/plugin', () => { `); }); + it('should infer dependsOn using the task run in the webServerCommands.default and ciWebServerCommand for the e2e and atomized e2e-ci targets respectively and not set parallelism to false', async () => { + mockCypressConfig( + defineConfig({ + e2e: { + ...nxE2EPreset(join(tempFs.tempDir, 'cypress.config.js'), { + webServerCommands: { + default: 'npx nx run my-app:serve', + production: 'npx nx run my-app:serve:production', + }, + ciWebServerCommand: 'npx nx run my-app:serve-static', + }), + specPattern: '**/*.cy.ts', + videosFolder: './dist/videos', + screenshotsFolder: './dist/screenshots', + }, + }) + ); + const nodes = await createNodesFunction( + ['cypress.config.js'], + { targetName: 'e2e' }, + context + ); + + expect(nodes).toMatchInlineSnapshot(` + [ + [ + "cypress.config.js", + { + "projects": { + ".": { + "metadata": { + "targetGroups": { + "E2E (CI)": [ + "e2e-ci--src/test.cy.ts", + "e2e-ci", + ], + }, + }, + "projectType": "application", + "targets": { + "e2e": { + "cache": true, + "command": "cypress run", + "configurations": { + "production": { + "command": "cypress run --env webServerCommand="npx nx run my-app:serve:production"", + }, + }, + "dependsOn": [ + { + "projects": [ + "my-app", + ], + "target": "serve", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "cypress", + ], + }, + ], + "metadata": { + "description": "Runs Cypress Tests", + "help": { + "command": "npx cypress run --help", + "example": { + "args": [ + "--dev", + "--headed", + ], + }, + }, + "technologies": [ + "cypress", + ], + }, + "options": { + "cwd": ".", + }, + "outputs": [ + "{projectRoot}/dist/videos", + "{projectRoot}/dist/screenshots", + ], + }, + "e2e-ci": { + "cache": true, + "dependsOn": [ + { + "params": "forward", + "projects": "self", + "target": "e2e-ci--src/test.cy.ts", + }, + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "cypress", + ], + }, + ], + "metadata": { + "description": "Runs Cypress Tests in CI", + "help": { + "command": "npx cypress run --help", + "example": { + "args": [ + "--dev", + "--headed", + ], + }, + }, + "nonAtomizedTarget": "e2e", + "technologies": [ + "cypress", + ], + }, + "outputs": [ + "{projectRoot}/dist/videos", + "{projectRoot}/dist/screenshots", + ], + "parallelism": false, + }, + "e2e-ci--src/test.cy.ts": { + "cache": true, + "command": "cypress run --env webServerCommand="npx nx run my-app:serve-static" --spec src/test.cy.ts --config="{\\"e2e\\":{\\"videosFolder\\":\\"dist/videos/src-test-cy-ts\\",\\"screenshotsFolder\\":\\"dist/screenshots/src-test-cy-ts\\"}}"", + "dependsOn": [ + { + "projects": [ + "my-app", + ], + "target": "serve-static", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "cypress", + ], + }, + ], + "metadata": { + "description": "Runs Cypress Tests in src/test.cy.ts in CI", + "help": { + "command": "npx cypress run --help", + "example": { + "args": [ + "--dev", + "--headed", + ], + }, + }, + "technologies": [ + "cypress", + ], + }, + "options": { + "cwd": ".", + }, + "outputs": [ + "{projectRoot}/dist/videos/src-test-cy-ts", + "{projectRoot}/dist/screenshots/src-test-cy-ts", + ], + }, + "open-cypress": { + "command": "cypress open", + "metadata": { + "description": "Opens Cypress", + "help": { + "command": "npx cypress open --help", + "example": { + "args": [ + "--dev", + "--e2e", + ], + }, + }, + "technologies": [ + "cypress", + ], + }, + "options": { + "cwd": ".", + }, + }, + }, + }, + }, + }, + ], + ] + `); + }); + + it('should set parallelism to false and not infer commands in dependsOn if reuseExistingServer is false', async () => { + mockCypressConfig( + defineConfig({ + e2e: { + ...nxE2EPreset(join(tempFs.tempDir, 'cypress.config.js'), { + webServerCommands: { + default: 'npx nx run my-app:serve', + production: 'npx nx run my-app:serve:production', + }, + ciWebServerCommand: 'npx nx run my-app:serve-static', + webServerConfig: { + reuseExistingServer: false, + }, + }), + specPattern: '**/*.cy.ts', + videosFolder: './dist/videos', + screenshotsFolder: './dist/screenshots', + }, + }) + ); + const nodes = await createNodesFunction( + ['cypress.config.js'], + { targetName: 'e2e' }, + context + ); + + expect(nodes).toMatchInlineSnapshot(` + [ + [ + "cypress.config.js", + { + "projects": { + ".": { + "metadata": { + "targetGroups": { + "E2E (CI)": [ + "e2e-ci--src/test.cy.ts", + "e2e-ci", + ], + }, + }, + "projectType": "application", + "targets": { + "e2e": { + "cache": true, + "command": "cypress run", + "configurations": { + "production": { + "command": "cypress run --env webServerCommand="npx nx run my-app:serve:production"", + }, + }, + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "cypress", + ], + }, + ], + "metadata": { + "description": "Runs Cypress Tests", + "help": { + "command": "npx cypress run --help", + "example": { + "args": [ + "--dev", + "--headed", + ], + }, + }, + "technologies": [ + "cypress", + ], + }, + "options": { + "cwd": ".", + }, + "outputs": [ + "{projectRoot}/dist/videos", + "{projectRoot}/dist/screenshots", + ], + "parallelism": false, + }, + "e2e-ci": { + "cache": true, + "dependsOn": [ + { + "params": "forward", + "projects": "self", + "target": "e2e-ci--src/test.cy.ts", + }, + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "cypress", + ], + }, + ], + "metadata": { + "description": "Runs Cypress Tests in CI", + "help": { + "command": "npx cypress run --help", + "example": { + "args": [ + "--dev", + "--headed", + ], + }, + }, + "nonAtomizedTarget": "e2e", + "technologies": [ + "cypress", + ], + }, + "outputs": [ + "{projectRoot}/dist/videos", + "{projectRoot}/dist/screenshots", + ], + "parallelism": false, + }, + "e2e-ci--src/test.cy.ts": { + "cache": true, + "command": "cypress run --env webServerCommand="npx nx run my-app:serve-static" --spec src/test.cy.ts --config="{\\"e2e\\":{\\"videosFolder\\":\\"dist/videos/src-test-cy-ts\\",\\"screenshotsFolder\\":\\"dist/screenshots/src-test-cy-ts\\"}}"", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "cypress", + ], + }, + ], + "metadata": { + "description": "Runs Cypress Tests in src/test.cy.ts in CI", + "help": { + "command": "npx cypress run --help", + "example": { + "args": [ + "--dev", + "--headed", + ], + }, + }, + "technologies": [ + "cypress", + ], + }, + "options": { + "cwd": ".", + }, + "outputs": [ + "{projectRoot}/dist/videos/src-test-cy-ts", + "{projectRoot}/dist/screenshots/src-test-cy-ts", + ], + "parallelism": false, + }, + "open-cypress": { + "command": "cypress open", + "metadata": { + "description": "Opens Cypress", + "help": { + "command": "npx cypress open --help", + "example": { + "args": [ + "--dev", + "--e2e", + ], + }, + }, + "technologies": [ + "cypress", + ], + }, + "options": { + "cwd": ".", + }, + }, + }, + }, + }, + }, + ], + ] + `); + }); + function mockCypressConfig(cypressConfig: Cypress.ConfigOptions) { // This isn't JS, but all that really matters here // is that the hash is different after updating the diff --git a/packages/cypress/src/plugins/plugin.ts b/packages/cypress/src/plugins/plugin.ts index 1fd47227c7..ecb0cfedcb 100644 --- a/packages/cypress/src/plugins/plugin.ts +++ b/packages/cypress/src/plugins/plugin.ts @@ -36,7 +36,13 @@ export interface CypressPluginOptions { } function readTargetsCache(cachePath: string): Record { - return existsSync(cachePath) ? readJsonFile(cachePath) : {}; + try { + return process.env.NX_CACHE_PROJECT_GRAPH !== 'false' + ? readJsonFile(cachePath) + : {}; + } catch { + return {}; + } } function writeTargetsToCache(cachePath: string, results: CypressTargets) { @@ -257,6 +263,8 @@ async function buildCypressTargets( const webServerCommands: Record = pluginPresetOptions?.webServerCommands; + const shouldReuseExistingServer = + pluginPresetOptions?.reuseExistingServer ?? true; const namedInputs = getNamedInputs(projectRoot, context); @@ -274,7 +282,6 @@ async function buildCypressTargets( cache: true, inputs: getInputs(namedInputs), outputs: getOutputs(projectRoot, cypressConfig, 'e2e'), - parallelism: false, metadata: { technologies: ['cypress'], description: 'Runs Cypress Tests', @@ -288,7 +295,23 @@ async function buildCypressTargets( }; if (webServerCommands?.default) { + const webServerCommandTask = shouldReuseExistingServer + ? parseTaskFromCommand(webServerCommands.default) + : null; + if (webServerCommandTask) { + targets[options.targetName].dependsOn = [ + { + projects: [webServerCommandTask.project], + target: webServerCommandTask.target, + }, + ]; + } else { + targets[options.targetName].parallelism = false; + } + delete webServerCommands.default; + } else { + targets[options.targetName].parallelism = false; } if (Object.keys(webServerCommands ?? {}).length > 0) { @@ -329,6 +352,9 @@ async function buildCypressTargets( const groupName = 'E2E (CI)'; metadata = { targetGroups: { [groupName]: [] } }; const ciTargetGroup = metadata.targetGroups[groupName]; + const ciWebServerCommandTask = shouldReuseExistingServer + ? parseTaskFromCommand(ciWebServerCommand) + : null; for (const file of specFiles) { const relativeSpecFilePath = normalizePath(relative(projectRoot, file)); @@ -351,7 +377,6 @@ async function buildCypressTargets( cwd: projectRoot, env: { TS_NODE_COMPILER_OPTIONS: tsNodeCompilerOptions }, }, - parallelism: false, metadata: { technologies: ['cypress'], description: `Runs Cypress Tests in ${relativeSpecFilePath} in CI`, @@ -368,6 +393,17 @@ async function buildCypressTargets( projects: 'self', params: 'forward', }); + + if (ciWebServerCommandTask) { + targets[targetName].dependsOn = [ + { + target: ciWebServerCommandTask.target, + projects: [ciWebServerCommandTask.project], + }, + ]; + } else { + targets[targetName].parallelism = false; + } } targets[options.ciTargetName] = { @@ -457,3 +493,26 @@ function getInputs( }, ]; } + +function parseTaskFromCommand(command: string): { + project: string; + target: string; +} | null { + const nxRunRegex = + /^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx run (\S+:\S+)$/; + const infixRegex = /^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx (\S+ \S+)$/; + + const nxRunMatch = command.match(nxRunRegex); + if (nxRunMatch) { + const [project, target] = nxRunMatch[1].split(':'); + return { project, target }; + } + + const infixMatch = command.match(infixRegex); + if (infixMatch) { + const [target, project] = infixMatch[1].split(' '); + return { project, target }; + } + + return null; +} diff --git a/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap b/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap index 1f867376d3..2ac6415b4f 100644 --- a/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap +++ b/packages/next/src/plugins/__snapshots__/plugin.spec.ts.snap @@ -40,12 +40,14 @@ exports[`@nx/next/plugin integrated projects should create nodes 1`] = ` }, "my-serve": { "command": "next dev", + "continuous": true, "options": { "cwd": "my-app", }, }, "my-serve-static": { "command": "next start", + "continuous": true, "dependsOn": [ "my-build", ], @@ -55,6 +57,7 @@ exports[`@nx/next/plugin integrated projects should create nodes 1`] = ` }, "my-start": { "command": "next start", + "continuous": true, "dependsOn": [ "my-build", ], @@ -117,12 +120,14 @@ exports[`@nx/next/plugin root projects should create nodes 1`] = ` }, "dev": { "command": "next dev", + "continuous": true, "options": { "cwd": ".", }, }, "serve-static": { "command": "next start", + "continuous": true, "dependsOn": [ "build", ], @@ -132,6 +137,7 @@ exports[`@nx/next/plugin root projects should create nodes 1`] = ` }, "start": { "command": "next start", + "continuous": true, "dependsOn": [ "build", ], diff --git a/packages/next/src/plugins/plugin.ts b/packages/next/src/plugins/plugin.ts index 5c817dcbdd..851ac1f5ea 100644 --- a/packages/next/src/plugins/plugin.ts +++ b/packages/next/src/plugins/plugin.ts @@ -219,6 +219,7 @@ async function getBuildTargetConfig( function getDevTargetConfig(projectRoot: string) { const targetConfig: TargetConfiguration = { + continuous: true, command: `next dev`, options: { cwd: projectRoot, @@ -230,6 +231,7 @@ function getDevTargetConfig(projectRoot: string) { function getStartTargetConfig(options: NextPluginOptions, projectRoot: string) { const targetConfig: TargetConfiguration = { + continuous: true, command: `next start`, options: { cwd: projectRoot, diff --git a/packages/playwright/src/generators/configuration/files/playwright.config.ts.template b/packages/playwright/src/generators/configuration/files/playwright.config.ts.template index 4fe960bed7..e09b2b00d6 100644 --- a/packages/playwright/src/generators/configuration/files/playwright.config.ts.template +++ b/packages/playwright/src/generators/configuration/files/playwright.config.ts.template @@ -26,13 +26,13 @@ export default defineConfig({ webServer: { command: '<%= webServerCommand %>', url: '<%= webServerAddress %>', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot },<% } else {%> // webServer: { // command: 'npm run start', // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, + // reuseExistingServer: true, // cwd: workspaceRoot, // },<% } %> projects: [ diff --git a/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.spec.ts b/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.spec.ts index d581674d4f..848ebd6475 100644 --- a/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.spec.ts +++ b/packages/playwright/src/generators/convert-to-inferred/convert-to-inferred.spec.ts @@ -137,7 +137,7 @@ function createTestProject( webServer: { command: 'npx nx serve myapp', url: 'http://localhost:4200', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot, }, projects: [ diff --git a/packages/playwright/src/plugins/plugin.spec.ts b/packages/playwright/src/plugins/plugin.spec.ts index c8de130c61..635f1ee24b 100644 --- a/packages/playwright/src/plugins/plugin.spec.ts +++ b/packages/playwright/src/plugins/plugin.spec.ts @@ -8,6 +8,8 @@ describe('@nx/playwright/plugin', () => { let createNodesFunction = createNodesV2[1]; let context: CreateNodesContext; let tempFs: TempFs; + let cwd = process.cwd(); + let originalCacheProjectGraph: string | undefined; beforeEach(async () => { tempFs = new TempFs('playwright-plugin'); @@ -26,11 +28,16 @@ describe('@nx/playwright/plugin', () => { workspaceRoot: tempFs.tempDir, configFiles: [], }; + + process.chdir(tempFs.tempDir); + originalCacheProjectGraph = process.env.NX_CACHE_PROJECT_GRAPH; + process.env.NX_CACHE_PROJECT_GRAPH = 'false'; }); afterEach(() => { - // tempFs.cleanup(); jest.resetModules(); + process.chdir(cwd); + process.env.NX_CACHE_PROJECT_GRAPH = originalCacheProjectGraph; }); it('should create nodes with default playwright configuration', async () => { @@ -431,6 +438,451 @@ describe('@nx/playwright/plugin', () => { expect(targets['e2e-ci--tests/ignored/run-me.spec.ts']).not.toBeDefined(); expect(targets['e2e-ci--not-tests/run-me.spec.ts']).not.toBeDefined(); }); + + it('should infer dependsOn using the task run in the webServer.command and not set parallelism to false', async () => { + await mockPlaywrightConfig(tempFs, { + testDir: 'tests', + webServer: { + command: 'npx nx run app1:serve', + reuseExistingServer: true, + }, + }); + await tempFs.createFiles({ + 'tests/run-me.spec.ts': '', + 'tests/run-me-2.spec.ts': '', + }); + + const results = await createNodesFunction( + ['playwright.config.js'], + { + targetName: 'e2e', + ciTargetName: 'e2e-ci', + }, + context + ); + const project = results[0][1].projects['.']; + const { targets } = project; + expect(targets['e2e']).toMatchInlineSnapshot(` + { + "cache": true, + "command": "playwright test", + "dependsOn": [ + { + "projects": [ + "app1", + ], + "target": "serve", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "technologies": [ + "playwright", + ], + }, + "options": { + "cwd": "{projectRoot}", + }, + "outputs": [ + "{projectRoot}/test-results", + ], + } + `); + expect(targets['e2e-ci']).toMatchInlineSnapshot(` + { + "cache": true, + "dependsOn": [ + { + "params": "forward", + "projects": "self", + "target": "e2e-ci--tests/run-me-2.spec.ts", + }, + { + "params": "forward", + "projects": "self", + "target": "e2e-ci--tests/run-me.spec.ts", + }, + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests in CI", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "nonAtomizedTarget": "e2e", + "technologies": [ + "playwright", + ], + }, + "outputs": [ + "{projectRoot}/test-results", + ], + "parallelism": false, + } + `); + expect(project.metadata.targetGroups).toMatchInlineSnapshot(` + { + "E2E (CI)": [ + "e2e-ci--tests/run-me-2.spec.ts", + "e2e-ci--tests/run-me.spec.ts", + "e2e-ci", + ], + } + `); + expect(targets['e2e-ci--tests/run-me.spec.ts']).toMatchInlineSnapshot(` + { + "cache": true, + "command": "playwright test tests/run-me.spec.ts --output=test-results/tests-run-me-spec-ts", + "dependsOn": [ + { + "projects": [ + "app1", + ], + "target": "serve", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests in tests/run-me.spec.ts in CI", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "technologies": [ + "playwright", + ], + }, + "options": { + "cwd": "{projectRoot}", + "env": {}, + }, + "outputs": [ + "{projectRoot}/test-results/tests-run-me-spec-ts", + ], + } + `); + expect(targets['e2e-ci--tests/run-me-2.spec.ts']).toMatchInlineSnapshot(` + { + "cache": true, + "command": "playwright test tests/run-me-2.spec.ts --output=test-results/tests-run-me-2-spec-ts", + "dependsOn": [ + { + "projects": [ + "app1", + ], + "target": "serve", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests in tests/run-me-2.spec.ts in CI", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "technologies": [ + "playwright", + ], + }, + "options": { + "cwd": "{projectRoot}", + "env": {}, + }, + "outputs": [ + "{projectRoot}/test-results/tests-run-me-2-spec-ts", + ], + } + `); + }); + + it('should not set parallelism to false and should infer dependsOn using the tasks run in the different webServer.command that have reuseExistingServer set to true', async () => { + await mockPlaywrightConfig(tempFs, { + testDir: 'tests', + webServer: [ + { command: 'npx nx run app1:serve', reuseExistingServer: true }, + { command: 'npx nx run api1:serve', reuseExistingServer: true }, + { command: 'npx nx run api2:dev', reuseExistingServer: true }, + { command: 'npx nx run api3:serve', reuseExistingServer: false }, // this one should not be included in dependsOn + ], + }); + await tempFs.createFiles({ + 'tests/run-me.spec.ts': '', + 'tests/run-me-2.spec.ts': '', + }); + + const results = await createNodesFunction( + ['playwright.config.js'], + { + targetName: 'e2e', + ciTargetName: 'e2e-ci', + }, + context + ); + const project = results[0][1].projects['.']; + const { targets } = project; + expect(targets['e2e']).toMatchInlineSnapshot(` + { + "cache": true, + "command": "playwright test", + "dependsOn": [ + { + "projects": [ + "app1", + "api1", + ], + "target": "serve", + }, + { + "projects": [ + "api2", + ], + "target": "dev", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "technologies": [ + "playwright", + ], + }, + "options": { + "cwd": "{projectRoot}", + }, + "outputs": [ + "{projectRoot}/test-results", + ], + } + `); + expect(targets['e2e-ci']).toMatchInlineSnapshot(` + { + "cache": true, + "dependsOn": [ + { + "params": "forward", + "projects": "self", + "target": "e2e-ci--tests/run-me-2.spec.ts", + }, + { + "params": "forward", + "projects": "self", + "target": "e2e-ci--tests/run-me.spec.ts", + }, + ], + "executor": "nx:noop", + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests in CI", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "nonAtomizedTarget": "e2e", + "technologies": [ + "playwright", + ], + }, + "outputs": [ + "{projectRoot}/test-results", + ], + "parallelism": false, + } + `); + expect(project.metadata.targetGroups).toMatchInlineSnapshot(` + { + "E2E (CI)": [ + "e2e-ci--tests/run-me-2.spec.ts", + "e2e-ci--tests/run-me.spec.ts", + "e2e-ci", + ], + } + `); + expect(targets['e2e-ci--tests/run-me.spec.ts']).toMatchInlineSnapshot(` + { + "cache": true, + "command": "playwright test tests/run-me.spec.ts --output=test-results/tests-run-me-spec-ts", + "dependsOn": [ + { + "projects": [ + "app1", + "api1", + ], + "target": "serve", + }, + { + "projects": [ + "api2", + ], + "target": "dev", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests in tests/run-me.spec.ts in CI", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "technologies": [ + "playwright", + ], + }, + "options": { + "cwd": "{projectRoot}", + "env": {}, + }, + "outputs": [ + "{projectRoot}/test-results/tests-run-me-spec-ts", + ], + } + `); + expect(targets['e2e-ci--tests/run-me-2.spec.ts']).toMatchInlineSnapshot(` + { + "cache": true, + "command": "playwright test tests/run-me-2.spec.ts --output=test-results/tests-run-me-2-spec-ts", + "dependsOn": [ + { + "projects": [ + "app1", + "api1", + ], + "target": "serve", + }, + { + "projects": [ + "api2", + ], + "target": "dev", + }, + ], + "inputs": [ + "default", + "^production", + { + "externalDependencies": [ + "@playwright/test", + ], + }, + ], + "metadata": { + "description": "Runs Playwright Tests in tests/run-me-2.spec.ts in CI", + "help": { + "command": "npx playwright test --help", + "example": { + "options": { + "workers": 1, + }, + }, + }, + "technologies": [ + "playwright", + ], + }, + "options": { + "cwd": "{projectRoot}", + "env": {}, + }, + "outputs": [ + "{projectRoot}/test-results/tests-run-me-2-spec-ts", + ], + } + `); + }); }); async function mockPlaywrightConfig( diff --git a/packages/playwright/src/plugins/plugin.ts b/packages/playwright/src/plugins/plugin.ts index c6727da346..d43496ec1a 100644 --- a/packages/playwright/src/plugins/plugin.ts +++ b/packages/playwright/src/plugins/plugin.ts @@ -44,7 +44,13 @@ type PlaywrightTargets = Pick; function readTargetsCache( cachePath: string ): Record { - return existsSync(cachePath) ? readJsonFile(cachePath) : {}; + try { + return process.env.NX_CACHE_PROJECT_GRAPH !== 'false' + ? readJsonFile(cachePath) + : {}; + } catch { + return {}; + } } function writeTargetsToCache( @@ -159,12 +165,12 @@ async function buildPlaywrightTargets( const testOutput = getTestOutput(playwrightConfig); const reporterOutputs = getReporterOutputs(playwrightConfig); + const webserverCommandTasks = getWebserverCommandTasks(playwrightConfig); const baseTargetConfig: TargetConfiguration = { command: 'playwright test', options: { cwd: '{projectRoot}', }, - parallelism: false, metadata: { technologies: ['playwright'], description: 'Runs Playwright Tests', @@ -179,6 +185,12 @@ async function buildPlaywrightTargets( }, }; + if (webserverCommandTasks.length) { + baseTargetConfig.dependsOn = getDependsOn(webserverCommandTasks); + } else { + baseTargetConfig.parallelism = false; + } + targets[options.targetName] = { ...baseTargetConfig, cache: true, @@ -438,6 +450,74 @@ function addSubfolderToOutput(output: string, subfolder?: string): string { return join(output, subfolder); } +function getWebserverCommandTasks( + playwrightConfig: PlaywrightTestConfig +): Array<{ project: string; target: string }> { + if (!playwrightConfig.webServer) { + return []; + } + + const tasks: Array<{ project: string; target: string }> = []; + + const webServer = Array.isArray(playwrightConfig.webServer) + ? playwrightConfig.webServer + : [playwrightConfig.webServer]; + + for (const server of webServer) { + if (!server.reuseExistingServer) { + continue; + } + + const task = parseTaskFromCommand(server.command); + if (task) { + tasks.push(task); + } + } + + return tasks; +} + +function parseTaskFromCommand(command: string): { + project: string; + target: string; +} | null { + const nxRunRegex = + /^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx run (\S+:\S+)$/; + const infixRegex = /^(?:(?:npx|yarn|bun|pnpm|pnpm exec|pnpx) )?nx (\S+ \S+)$/; + + const nxRunMatch = command.match(nxRunRegex); + if (nxRunMatch) { + const [project, target] = nxRunMatch[1].split(':'); + return { project, target }; + } + + const infixMatch = command.match(infixRegex); + if (infixMatch) { + const [target, project] = infixMatch[1].split(' '); + return { project, target }; + } + + return null; +} + +function getDependsOn( + tasks: Array<{ project: string; target: string }> +): TargetConfiguration['dependsOn'] { + const projectsPerTask = new Map(); + + for (const { project, target } of tasks) { + if (!projectsPerTask.has(target)) { + projectsPerTask.set(target, []); + } + projectsPerTask.get(target).push(project); + } + + return Array.from(projectsPerTask.entries()).map(([target, projects]) => ({ + projects, + target, + })); +} + function normalizeOutput( path: string, workspaceRoot: string, diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 4fcd787fe2..3de64f465c 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -180,7 +180,7 @@ describe('app', () => { webServer: { command: '${packageCmd} nx run my-app:preview', url: 'http://localhost:4300', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot }, projects: [ diff --git a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap index 5dda4330e7..1379560758 100644 --- a/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap +++ b/packages/remix/src/generators/application/__snapshots__/application.impl.spec.ts.snap @@ -182,7 +182,7 @@ export default defineConfig({ webServer: { command: 'npx nx run test:serve-static', url: 'http://localhost:3000', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot, }, projects: [ @@ -712,7 +712,7 @@ export default defineConfig({ webServer: { command: 'npx nx run test:serve-static', url: 'http://localhost:3000', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot, }, projects: [ diff --git a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap index 4f70851fa7..4ae5de7252 100644 --- a/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/vue/src/generators/application/__snapshots__/application.spec.ts.snap @@ -364,7 +364,7 @@ export default defineConfig({ webServer: { command: 'npx nx run test:preview', url: 'http://localhost:4300', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot, }, projects: [ diff --git a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap index f1269df4c0..6ac5758f27 100644 --- a/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap +++ b/packages/web/src/generators/application/__snapshots__/application.spec.ts.snap @@ -29,7 +29,7 @@ export default defineConfig({ webServer: { command: 'npx nx run my-app:preview', url: 'http://localhost:4300', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot, }, projects: [ @@ -101,7 +101,7 @@ export default defineConfig({ webServer: { command: 'npx nx run cool-app:serve-static', url: 'http://localhost:4200', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot, }, projects: [ @@ -173,7 +173,7 @@ export default defineConfig({ webServer: { command: 'npx nx run my-app:preview', url: 'http://localhost:4300', - reuseExistingServer: !process.env.CI, + reuseExistingServer: true, cwd: workspaceRoot, }, projects: [