diff --git a/.nx/workflows/dynamic-changesets.yaml b/.nx/workflows/dynamic-changesets.yaml index da23d01c379944..c3fd5cff51e27c 100644 --- a/.nx/workflows/dynamic-changesets.yaml +++ b/.nx/workflows/dynamic-changesets.yaml @@ -5,31 +5,6 @@ distribute-on: large-changeset: 6 linux-large, 6 linux-extra-large extra-large-changeset: 8 linux-large, 8 linux-extra-large assignment-rules: - - projects: - - e2e-gradle - targets: - - e2e-ci** - run-on: - - agent: linux-extra-large - parallelism: 1 - - projects: - - e2e-next - - e2e-plugin - targets: - - e2e-ci** - run-on: - - agent: linux-extra-large - parallelism: 2 - - projects: - - e2e-angular - - e2e-node - - e2e-react - targets: - - e2e-ci** - run-on: - - agent: linux-extra-large - parallelism: 1 - - projects: - nx - workspace @@ -44,36 +19,13 @@ assignment-rules: - agent: linux-extra-large parallelism: 1 - - projects: - - e2e-release - - e2e-nuxt - - e2e-web - - e2e-eslint - - e2e-remix - - e2e-cypress - - e2e-docker - - e2e-js - - e2e-nx - - e2e-nx-init - - e2e-dotnet - - e2e-workspace-create - - e2e-rollup - targets: - - e2e-ci** - run-on: - - agent: linux-large - parallelism: 1 - - agent: linux-extra-large - parallelism: 2 - - # All other e2e tests can run in parallel - targets: - e2e-ci** run-on: - agent: linux-large parallelism: 2 - agent: linux-extra-large - parallelism: 3 + parallelism: 4 - targets: - bench:* diff --git a/e2e/angular/src/module-federation-host-remote.test.ts b/e2e/angular/src/module-federation-host-remote.test.ts index aed112be11a0b8..39e3966e0018f7 100644 --- a/e2e/angular/src/module-federation-host-remote.test.ts +++ b/e2e/angular/src/module-federation-host-remote.test.ts @@ -2,6 +2,7 @@ import { names } from '@nx/devkit'; import { checkFilesExist, killProcessAndPorts, + reservePort, runCLI, runCommandUntil, uniq, @@ -30,12 +31,12 @@ describe('Angular Module Federation - Host and Remote', () => { const sharedLib = uniq('shared-lib'); const wildcardLib = uniq('wildcard-lib'); const secondaryEntry = uniq('secondary'); - const hostPort = 4300; - const remotePort = 4301; + const hostPort = await reservePort(); + const remotePort = await reservePort(); // generate host app runCLI( - `generate @nx/angular:host ${hostApp} --style=css --no-standalone --unitTestRunner=jest --no-interactive` + `generate @nx/angular:host ${hostApp} --port=${hostPort} --style=css --no-standalone --unitTestRunner=jest --no-interactive` ); // generate remote app runCLI( @@ -152,7 +153,7 @@ describe('Angular Module Federation - Host and Remote', () => { `serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`, (output) => !output.includes(`Remote '${remoteApp1}' failed to serve correctly`) && - output.includes(`listening on localhost:${hostPort}`) + output.includes(`server ready at http://localhost:${hostPort}`) ); await killProcessAndPorts(processSwc.pid, hostPort, remotePort); @@ -160,7 +161,7 @@ describe('Angular Module Federation - Host and Remote', () => { `serve ${hostApp} --port=${hostPort} --dev-remotes=${remoteApp1}`, (output) => !output.includes(`Remote '${remoteApp1}' failed to serve correctly`) && - output.includes(`listening on localhost:${hostPort}`), + output.includes(`server ready at http://localhost:${hostPort}`), { env: { NX_PREFER_TS_NODE: 'true' } } ); @@ -170,8 +171,8 @@ describe('Angular Module Federation - Host and Remote', () => { it('should convert apps to MF successfully', async () => { const app1 = uniq('app1'); const app2 = uniq('app2'); - const app1Port = 4400; - const app2Port = 4401; + const app1Port = await reservePort(); + const app2Port = await reservePort(); // generate apps runCLI( @@ -193,7 +194,7 @@ describe('Angular Module Federation - Host and Remote', () => { `serve ${app1} --dev-remotes=${app2}`, (output) => !output.includes(`Remote '${app2}' failed to serve correctly`) && - output.includes(`listening on localhost:${app1Port}`) + output.includes(`server ready at http://localhost:${app1Port}`) ); await killProcessAndPorts(processSwc.pid, app1Port, app2Port); @@ -202,7 +203,7 @@ describe('Angular Module Federation - Host and Remote', () => { `serve ${app1} --dev-remotes=${app2}`, (output) => !output.includes(`Remote '${app2}' failed to serve correctly`) && - output.includes(`listening on localhost:${app1Port}`), + output.includes(`server ready at http://localhost:${app1Port}`), { env: { NX_PREFER_TS_NODE: 'true' } } ); diff --git a/e2e/angular/src/module-federation-lib.test.ts b/e2e/angular/src/module-federation-lib.test.ts index 29ee6d4553be9f..7623a0f27a8441 100644 --- a/e2e/angular/src/module-federation-lib.test.ts +++ b/e2e/angular/src/module-federation-lib.test.ts @@ -1,5 +1,6 @@ import { killProcessAndPorts, + reservePort, runCLI, runCommandUntil, runE2ETests, @@ -27,10 +28,10 @@ describe('Angular Module Federation - Federated Libraries', () => { const module = uniq('module'); const host = uniq('host'); - const hostPort = 4200; + const hostPort = await reservePort(); runCLI( - `generate @nx/angular:host ${host} --remotes=${remote} --e2eTestRunner=cypress --no-interactive` + `generate @nx/angular:host ${host} --port=${hostPort} --remotes=${remote} --e2eTestRunner=cypress --no-interactive` ); runCLI(`generate @nx/js:lib ${lib} --no-interactive`); @@ -101,10 +102,10 @@ describe('Angular Module Federation - Federated Libraries', () => { const childRemote = uniq('childremote'); const module = uniq('module'); const host = uniq('host'); - const hostPort = 4200; + const hostPort = await reservePort(); runCLI( - `generate @nx/angular:host ${host} --remotes=${remote} --e2eTestRunner=cypress --no-interactive` + `generate @nx/angular:host ${host} --port=${hostPort} --remotes=${remote} --e2eTestRunner=cypress --no-interactive` ); runCLI(`generate @nx/js:lib ${lib} --no-interactive`); diff --git a/e2e/angular/src/module-federation-ssr.test.ts b/e2e/angular/src/module-federation-ssr.test.ts index 26f01d210ee7da..9374310bebc658 100644 --- a/e2e/angular/src/module-federation-ssr.test.ts +++ b/e2e/angular/src/module-federation-ssr.test.ts @@ -3,6 +3,7 @@ import { killPorts, killProcessAndPorts, readJson, + reservePort, runCLI, runCommandUntil, runE2ETests, @@ -40,13 +41,14 @@ describe('Angular Module Federation - SSR', () => { const remote1 = uniq('remote1'); const remote2 = uniq('remote2'); + // ports + const hostPort = await reservePort(); + // generate remote apps runCLI( - `generate @nx/angular:host ${host} --ssr --remotes=${remote1},${remote2} --no-interactive` + `generate @nx/angular:host ${host} --port=${hostPort} --ssr --remotes=${remote1},${remote2} --no-interactive` ); - // ports - const hostPort = 4500; const remote1Port = readJson(join(remote1, 'project.json')).targets.serve .options.port; const remote2Port = readJson(join(remote2, 'project.json')).targets.serve diff --git a/e2e/angular/src/module-federation.rspack.test.ts b/e2e/angular/src/module-federation.rspack.test.ts index 1780be5c954762..788a15249842dc 100644 --- a/e2e/angular/src/module-federation.rspack.test.ts +++ b/e2e/angular/src/module-federation.rspack.test.ts @@ -5,6 +5,7 @@ import { killProcessAndPorts, newProject, readFile, + reservePort, runCLI, runCommandUntil, runE2ETests, @@ -34,12 +35,12 @@ describe('Angular Module Federation', () => { const sharedLib = uniq('shared-lib'); const wildcardLib = uniq('wildcard-lib'); const secondaryEntry = uniq('secondary'); - const hostPort = 4300; - const remotePort = 4301; + const hostPort = await reservePort(); + const remotePort = await reservePort(); // generate host app runCLI( - `generate @nx/angular:host ${hostApp} --style=css --bundler=rspack --no-standalone --no-interactive` + `generate @nx/angular:host ${hostApp} --port=${hostPort} --style=css --bundler=rspack --no-standalone --no-interactive` ); let rspackConfigFileContents = readFile(join(hostApp, 'rspack.config.ts')); let updatedConfigFileContents = rspackConfigFileContents.replace( @@ -179,12 +180,12 @@ describe('Angular Module Federation', () => { it('should load remote app in the browser via ESM module federation', async () => { const hostApp = uniq('host'); const remoteApp = uniq('remote'); - const hostPort = 4200; - const remotePort = 4401; + const hostPort = await reservePort(); + const remotePort = await reservePort(); // generate host with playwright e2e runner runCLI( - `generate @nx/angular:host ${hostApp} --style=css --bundler=rspack --e2eTestRunner=playwright --no-interactive` + `generate @nx/angular:host ${hostApp} --port=${hostPort} --style=css --bundler=rspack --e2eTestRunner=playwright --no-interactive` ); // generate remote diff --git a/e2e/angular/src/projects-build-and-test.test.ts b/e2e/angular/src/projects-build-and-test.test.ts index 883e08aa0c9b60..6e1b0edb54eec6 100644 --- a/e2e/angular/src/projects-build-and-test.test.ts +++ b/e2e/angular/src/projects-build-and-test.test.ts @@ -5,6 +5,7 @@ import { killPort, killProcessAndPorts, readFile, + reservePort, runCLI, runCommandUntil, runE2ETests, @@ -95,7 +96,7 @@ describe('Angular Projects - Build and Test', () => { expect(await killPort(4200)).toBeTruthy(); } - const appPort = 4207; + const appPort = await reservePort(); const process = await runCommandUntil( `serve ${app1} -- --port=${appPort}`, (output) => output.includes(`listening on localhost:${appPort}`) diff --git a/e2e/gradle/src/utils/create-gradle-project.ts b/e2e/gradle/src/utils/create-gradle-project.ts index 6013fde21db7fc..d09d51c19b6b1d 100644 --- a/e2e/gradle/src/utils/create-gradle-project.ts +++ b/e2e/gradle/src/utils/create-gradle-project.ts @@ -28,14 +28,21 @@ export function createGradleProject( cwd, }) ); + // Run setup-time gradle commands with --no-daemon so we don't leave a + // long-lived daemon holding inotify watches on the project dir. A daemon + // spawned here outlives the setup phase and interferes with later + // non-gradle filesystem operations (e.g. `nx import` runs `git + // filter-branch --tree-filter`, which fails with "Unable to read current + // working directory" when the daemon is concurrently watching the same + // tree). The actual test-body gradle calls keep daemons enabled. e2eConsoleLogger( - execSync(`${gradleCommand} help --task :init`, { + execSync(`${gradleCommand} help --task :init --no-daemon`, { cwd, }).toString() ); e2eConsoleLogger( runCommand( - `${gradleCommand} init --type ${type}-application --dsl ${type} --project-name ${projectName} --package ${packageName} --no-incubating --split-project --overwrite`, + `${gradleCommand} init --type ${type}-application --dsl ${type} --project-name ${projectName} --package ${packageName} --no-incubating --split-project --overwrite --no-daemon`, { cwd, } @@ -53,12 +60,7 @@ export function createGradleProject( try { e2eConsoleLogger( - runCommand(`${gradleCommand} --stop`, { - cwd, - }) - ); - e2eConsoleLogger( - runCommand(`${gradleCommand} clean`, { + runCommand(`${gradleCommand} clean --no-daemon`, { cwd, }) ); diff --git a/e2e/next/src/next-legacy.test.ts b/e2e/next/src/next-legacy.test.ts index 6821ef3a4f8c92..bcb87958557b59 100644 --- a/e2e/next/src/next-legacy.test.ts +++ b/e2e/next/src/next-legacy.test.ts @@ -9,6 +9,7 @@ import { newProject, packageManagerLockFile, readFile, + reservePort, runCLI, runCommand, runCommandUntil, @@ -77,7 +78,7 @@ describe('@nx/next (legacy)', () => { checkFilesExist(`dist/${appName}/nested/headers-2.js`); }, 120_000); - it('should build and install pruned lock file', () => { + it('should build and install pruned lock file', async () => { const appName = uniq('app'); runCLI(`generate @nx/next:app ${appName} --no-interactive --style=css`, { env: { NX_ADD_PLUGINS: 'false' }, @@ -250,7 +251,7 @@ describe('@nx/next (legacy)', () => { expect(nextConfigPath).not.toContain(`require("@nx/`); // dev-only packages // Check that `nx serve --prod` works with previous production build (e.g. `nx build `). - const prodServePort = 4001; + const prodServePort = await reservePort(); const prodServeProcess = await runCommandUntil( `run ${appName}:serve --prod --port=${prodServePort}`, (output) => { @@ -259,7 +260,7 @@ describe('@nx/next (legacy)', () => { ); // Check that the output is self-contained (i.e. can run with its own package.json + node_modules) - const selfContainedPort = 3000; + const selfContainedPort = await reservePort(); runCLI( `generate @nx/workspace:run-commands serve-prod --project ${appName} --cwd=dist/packages/${appName} --command="npx next start --port=${selfContainedPort}"`, { diff --git a/e2e/node/src/node-esm-support.test.ts b/e2e/node/src/node-esm-support.test.ts index 53ed1f51813d64..1c47c1a4d2f3e8 100644 --- a/e2e/node/src/node-esm-support.test.ts +++ b/e2e/node/src/node-esm-support.test.ts @@ -4,6 +4,7 @@ import { cleanupProject, getPackageManagerCommand, newProject, + reservePort, runCLI, runCLIAsync, runCommand, @@ -55,6 +56,7 @@ describe('Node.js Framework ESM Support', () => { describe('Express Framework with ESM', () => { it('should build and run Express app with ESM modules', async () => { + const port = await reservePort(); const expressApp = uniq('express-esm'); runCLI( `generate @nx/node:app apps/${expressApp} --framework=express --linter=eslint --unitTestRunner=jest` @@ -87,7 +89,7 @@ describe('Node.js Framework ESM Support', () => { console.log('Express ESM app starting'); console.log('fetch type:', typeof fetch); - const port = process.env.PORT || 3000; + const port = process.env.PORT || ${port}; const server = app.listen(port, () => { console.log('Express ESM server ready on port ' + port); }); @@ -111,6 +113,7 @@ describe('Node.js Framework ESM Support', () => { }, 600000); it('should serve Express app with ESM modules', async () => { + const port = await reservePort(); const expressApp = uniq('express-esm-serve'); runCLI( `generate @nx/node:app apps/${expressApp} --framework=express --linter=eslint --unitTestRunner=jest` @@ -139,7 +142,7 @@ describe('Node.js Framework ESM Support', () => { res.json({ message: 'Express ESM serve working' }); }); - const port = process.env.PORT || 3000; + const port = process.env.PORT || ${port}; const server = app.listen(port, () => { console.log('Express ESM serve ready on port ' + port); }); @@ -161,6 +164,7 @@ describe('Node.js Framework ESM Support', () => { describe('Fastify Framework with ESM', () => { it('should build and run Fastify app with ESM modules', async () => { + const port = await reservePort(); const fastifyApp = uniq('fastify-esm'); runCLI( `generate @nx/node:app apps/${fastifyApp} --framework=fastify --linter=eslint --unitTestRunner=jest` @@ -197,7 +201,7 @@ describe('Node.js Framework ESM Support', () => { const start = async () => { try { - const port = +(process.env.PORT || 3000); + const port = +(process.env.PORT || ${port}); await fastify.listen({ port }); console.log('Fastify ESM server ready on port ' + port); } catch (err) { @@ -225,6 +229,7 @@ describe('Node.js Framework ESM Support', () => { }, 600000); it('should serve Fastify app with ESM modules', async () => { + const port = await reservePort(); const fastifyApp = uniq('fastify-esm-serve'); runCLI( `generate @nx/node:app apps/${fastifyApp} --framework=fastify --linter=eslint --unitTestRunner=jest` @@ -255,7 +260,7 @@ describe('Node.js Framework ESM Support', () => { const start = async () => { try { - const port = +(process.env.PORT || 3000); + const port = +(process.env.PORT || ${port}); await fastify.listen({ port }); console.log('Fastify ESM serve ready on port ' + port); } catch (err) { @@ -280,6 +285,7 @@ describe('Node.js Framework ESM Support', () => { describe('Koa Framework with ESM', () => { it('should build and run Koa app with ESM modules', async () => { + const port = await reservePort(); const koaApp = uniq('koa-esm'); runCLI( `generate @nx/node:app apps/${koaApp} --framework=koa --linter=eslint --unitTestRunner=jest` @@ -318,7 +324,7 @@ describe('Node.js Framework ESM Support', () => { console.log('Koa ESM app starting'); console.log('fetch type:', typeof fetch); - const port = +(process.env.PORT || 3000); + const port = +(process.env.PORT || ${port}); const server = app.listen(port, () => { console.log('Koa ESM server ready on port ' + port); }); @@ -342,6 +348,7 @@ describe('Node.js Framework ESM Support', () => { }, 600000); it('should serve Koa app with ESM modules', async () => { + const port = await reservePort(); const koaApp = uniq('koa-esm-serve'); runCLI( `generate @nx/node:app apps/${koaApp} --framework=koa --linter=eslint --unitTestRunner=jest` @@ -376,7 +383,7 @@ describe('Node.js Framework ESM Support', () => { app.use(router.routes()).use(router.allowedMethods()); - const port = +(process.env.PORT || 3000); + const port = +(process.env.PORT || ${port}); const server = app.listen(port, () => { console.log('Koa ESM serve ready on port ' + port); }); @@ -398,6 +405,7 @@ describe('Node.js Framework ESM Support', () => { describe('Nest Framework with ESM', () => { it('should build and run Nest app with ESM modules', async () => { + const port = await reservePort(); const nestApp = uniq('nest-esm'); runCLI( `generate @nx/node:app apps/${nestApp} --framework=nest --linter=eslint --unitTestRunner=jest` @@ -431,7 +439,7 @@ describe('Node.js Framework ESM Support', () => { const app = await NestFactory.create(AppModule); const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); - const port = process.env.PORT || 3000; + const port = process.env.PORT || ${port}; await app.listen(port); console.log('Nest ESM server ready on port ' + port); } @@ -454,6 +462,7 @@ describe('Node.js Framework ESM Support', () => { }, 600000); it('should serve Nest app with ESM modules', async () => { + const port = await reservePort(); const nestApp = uniq('nest-esm-serve'); runCLI( `generate @nx/node:app apps/${nestApp} --framework=nest --linter=eslint --unitTestRunner=jest` @@ -487,7 +496,7 @@ describe('Node.js Framework ESM Support', () => { const app = await NestFactory.create(AppModule); const globalPrefix = 'api'; app.setGlobalPrefix(globalPrefix); - const port = process.env.PORT || 3000; + const port = process.env.PORT || ${port}; await app.listen(port); console.log('Nest ESM serve ready on port ' + port); } @@ -510,6 +519,7 @@ describe('Node.js Framework ESM Support', () => { describe('Mixed Imports across Node.js Frameworks', () => { it('should handle CommonJS and ESM packages together', async () => { + const port = await reservePort(); const nodeApp = uniq('node-mixed-imports'); runCLI( `generate @nx/node:app apps/${nodeApp} --framework=express --linter=eslint --unitTestRunner=jest` @@ -552,7 +562,7 @@ describe('Node.js Framework ESM Support', () => { console.log('Mixed imports Node.js app starting'); console.log('lodash.pick type:', typeof lodash.pick); - const port = process.env.PORT || 3000; + const port = process.env.PORT || ${port}; const server = app.listen(port, () => { console.log('Mixed imports server ready on port ' + port); }); diff --git a/e2e/node/src/node-server.test.ts b/e2e/node/src/node-server.test.ts index 1ef4d8f7f0042d..41dd2752228132 100644 --- a/e2e/node/src/node-server.test.ts +++ b/e2e/node/src/node-server.test.ts @@ -7,6 +7,7 @@ import { newProject, promisifiedTreeKill, readFile, + reservePort, runCLI, runCommandUntil, uniq, @@ -56,7 +57,7 @@ describe('Node Applications + webpack', () => { } } - async function runE2eTests(appName: string, port: number = 5000) { + async function runE2eTests(appName: string, port: number) { process.env.PORT = `${port}`; const result = runCLI(`e2e ${appName}-e2e --verbose`); expect(result).toContain('Setting up...'); @@ -74,8 +75,16 @@ describe('Node Applications + webpack', () => { const fastifyApp = uniq('fastifyapp'); const koaApp = uniq('koaapp'); const nestApp = uniq('nest'); - - beforeAll(() => { + let expressPort: number; + let fastifyPort: number; + let koaPort: number; + let nestPort: number; + + beforeAll(async () => { + expressPort = await reservePort(); + fastifyPort = await reservePort(); + koaPort = await reservePort(); + nestPort = await reservePort(); runCLI( `generate @nx/node:lib libs/${testLib1} --linter=eslint --unitTestRunner=jest --buildable=false` ); @@ -83,16 +92,16 @@ describe('Node Applications + webpack', () => { `generate @nx/node:lib libs/${testLib2} --importPath=@acme/test2 --linter=eslint --unitTestRunner=jest --buildable=false` ); runCLI( - `generate @nx/node:app apps/${expressApp} --framework=express --port=7000 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest` + `generate @nx/node:app apps/${expressApp} --framework=express --port=${expressPort} --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest` ); runCLI( - `generate @nx/node:app apps/${fastifyApp} --framework=fastify --port=7001 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest` + `generate @nx/node:app apps/${fastifyApp} --framework=fastify --port=${fastifyPort} --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest` ); runCLI( - `generate @nx/node:app apps/${koaApp} --framework=koa --port=7002 --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest` + `generate @nx/node:app apps/${koaApp} --framework=koa --port=${koaPort} --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest` ); runCLI( - `generate @nx/node:app apps/${nestApp} --framework=nest --port=7003 --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest --verbose` + `generate @nx/node:app apps/${nestApp} --framework=nest --port=${nestPort} --bundler=webpack --no-interactive --linter=eslint --unitTestRunner=jest --e2eTestRunner=jest --verbose` ); addLibImport(expressApp, testLib1); @@ -145,19 +154,19 @@ describe('Node Applications + webpack', () => { }, 300_000); it('should e2e test express app', async () => { - await runE2eTests(expressApp, 7000); + await runE2eTests(expressApp, expressPort); }); it('should e2e test fastify app', async () => { - await runE2eTests(fastifyApp, 7001); + await runE2eTests(fastifyApp, fastifyPort); }); it('should e2e test koa app', async () => { - await runE2eTests(koaApp, 7002); + await runE2eTests(koaApp, koaPort); }); it('should e2e test nest app', async () => { - await runE2eTests(nestApp, 7003); + await runE2eTests(nestApp, nestPort); }); }); @@ -174,13 +183,15 @@ describe('Node Applications + webpack', () => { it('should support waitUntilTargets for serve target', async () => { const nodeApp1 = uniq('nodeapp1'); const nodeApp2 = uniq('nodeapp2'); + const nodeApp1Port = await reservePort(); + const nodeApp2Port = await reservePort(); // Set ports to avoid conflicts with other tests that might run in parallel runCLI( - `generate @nx/node:app apps/${nodeApp1} --framework=none --no-interactive --port=4444 --linter=eslint --unitTestRunner=jest` + `generate @nx/node:app apps/${nodeApp1} --framework=none --no-interactive --port=${nodeApp1Port} --linter=eslint --unitTestRunner=jest` ); runCLI( - `generate @nx/node:app apps/${nodeApp2} --framework=none --no-interactive --port=4445 --linter=eslint --unitTestRunner=jest` + `generate @nx/node:app apps/${nodeApp2} --framework=none --no-interactive --port=${nodeApp2Port} --linter=eslint --unitTestRunner=jest` ); updateJson(join('apps', nodeApp1, 'project.json'), (config) => { config.targets.serve.options.waitUntilTargets = [`${nodeApp2}:build`]; diff --git a/e2e/nx/src/__fixtures__/remote-cache.js b/e2e/nx/src/__fixtures__/remote-cache.js index 9b8eaa53bc6458..a5251774427d7e 100644 --- a/e2e/nx/src/__fixtures__/remote-cache.js +++ b/e2e/nx/src/__fixtures__/remote-cache.js @@ -63,7 +63,7 @@ const server = http.createServer((req, res) => { } }); -const PORT = 3000; +const PORT = Number(process.env.PORT ?? 3000); server.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}/`); }); diff --git a/e2e/nx/src/cache-no-daemon.test.ts b/e2e/nx/src/cache-no-daemon.test.ts index 1fa8fbad6af81c..fea2a7fc41e0f6 100644 --- a/e2e/nx/src/cache-no-daemon.test.ts +++ b/e2e/nx/src/cache-no-daemon.test.ts @@ -6,6 +6,7 @@ import { newProject, readFile, removeFile, + reservePort, rmDist, runCLI as _runCLI, RunCmdOpts, @@ -647,9 +648,16 @@ console.log('Build complete'); describe('http remote cache', () => { let cacheServer: any; - beforeAll(() => { + let cachePort: number; + let unboundPort: number; + beforeAll(async () => { + cachePort = await reservePort(); + // A second port we deliberately don't bind, used by the + // "should error if server is not running" test. + unboundPort = await reservePort(); cacheServer = fork(join(__dirname, '__fixtures__', 'remote-cache.js'), { stdio: 'inherit', + env: { ...process.env, PORT: String(cachePort) }, }); }); @@ -675,7 +683,7 @@ console.log('Build complete'); ); runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3000', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${cachePort}`, NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: 'test-token', }, }); @@ -686,7 +694,7 @@ console.log('Build complete'); runCLI(`reset`); const output = runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3000', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${cachePort}`, NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: 'test-token', }, }); @@ -712,7 +720,7 @@ console.log('Build complete'); ); const output = runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3000', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${cachePort}`, }, silenceError: true, }); @@ -740,13 +748,13 @@ console.log('Build complete'); ); const output = runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3001', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${unboundPort}`, NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: 'test-token', }, silenceError: true, }); - expect(output).toContain('http://localhost:3001'); + expect(output).toContain(`http://localhost:${unboundPort}`); }); }); diff --git a/e2e/nx/src/cache.test.ts b/e2e/nx/src/cache.test.ts index 2193bd52ab3b9f..b7be1d031e1512 100644 --- a/e2e/nx/src/cache.test.ts +++ b/e2e/nx/src/cache.test.ts @@ -6,6 +6,7 @@ import { newProject, readFile, removeFile, + reservePort, rmDist, runCLI, tmpProjPath, @@ -638,9 +639,15 @@ console.log('Build complete'); describe('http remote cache', () => { let cacheServer: any; - beforeAll(() => { + let cachePort: number; + let unusedPort: number; + beforeAll(async () => { + cachePort = await reservePort(); + // Reserved but never bound — used to simulate a server-not-running error. + unusedPort = await reservePort(); cacheServer = fork(join(__dirname, '__fixtures__', 'remote-cache.js'), { stdio: 'inherit', + env: { ...process.env, PORT: String(cachePort) }, }); }); @@ -666,7 +673,7 @@ console.log('Build complete'); ); runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3000', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${cachePort}`, NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: 'test-token', }, }); @@ -677,7 +684,7 @@ console.log('Build complete'); runCLI(`reset`); const output = runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3000', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${cachePort}`, NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: 'test-token', }, }); @@ -703,7 +710,7 @@ console.log('Build complete'); ); const output = runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3000', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${cachePort}`, }, silenceError: true, }); @@ -731,13 +738,13 @@ console.log('Build complete'); ); const output = runCLI(`build ${projectName}`, { env: { - NX_SELF_HOSTED_REMOTE_CACHE_SERVER: 'http://localhost:3001', + NX_SELF_HOSTED_REMOTE_CACHE_SERVER: `http://localhost:${unusedPort}`, NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: 'test-token', }, silenceError: true, }); - expect(output).toContain('http://localhost:3001'); + expect(output).toContain(`http://localhost:${unusedPort}`); }); }); diff --git a/e2e/nx/src/spread.test.ts b/e2e/nx/src/spread.test.ts index d62b1cbf8615c0..591aaa5c90a4dc 100644 --- a/e2e/nx/src/spread.test.ts +++ b/e2e/nx/src/spread.test.ts @@ -23,6 +23,12 @@ describe('Spread Token Merging', () => { }); afterEach(() => { updateFile('nx.json', JSON.stringify(existingNxJson, null, 2)); + // Reset daemon cache so the next test does not see stale plugin-inferred + // project graph data. The PR enabling NX_DAEMON=true in runCLI means the + // daemon persists across tests; without a reset, re-adding a previously + // removed plugin to nx.json can cause the daemon to return a cached graph + // that is missing inferred targets. + runCLI('reset'); }); function getResolvedProject(name: string) { diff --git a/e2e/nx/src/workspace-legacy.test.ts b/e2e/nx/src/workspace-legacy.test.ts index bdacae592e962f..7e944797df6a63 100644 --- a/e2e/nx/src/workspace-legacy.test.ts +++ b/e2e/nx/src/workspace-legacy.test.ts @@ -2,6 +2,7 @@ import { checkFilesExist, cleanupProject, newProject, + reservePort, runCLI, runE2ETests, uniq, @@ -18,8 +19,9 @@ describe('@nx/workspace:convert-to-monorepo', () => { it('should convert a standalone webpack and jest react project to a monorepo (legacy)', async () => { const reactApp = uniq('reactapp'); + const appPort = await reservePort(); runCLI( - `generate @nx/react:app --name=${reactApp} --directory="." --bundler=webpack --unitTestRunner=jest --e2eTestRunner=cypress --no-interactive --linter=eslint`, + `generate @nx/react:app --name=${reactApp} --directory="." --bundler=webpack --unitTestRunner=jest --e2eTestRunner=cypress --devServerPort=${appPort} --no-interactive --linter=eslint`, { env: { NX_ADD_PLUGINS: 'false', diff --git a/e2e/playwright/src/playwright.test.ts b/e2e/playwright/src/playwright.test.ts index bab5ea0b6a7ce9..183870b7da92c7 100644 --- a/e2e/playwright/src/playwright.test.ts +++ b/e2e/playwright/src/playwright.test.ts @@ -1,12 +1,13 @@ import { cleanupProject, - newProject, - uniq, - runCLI, ensurePlaywrightBrowsersInstallation, getPackageManagerCommand, getSelectedPackageManager, + newProject, readJson, + reservePort, + runCLI, + uniq, } from '@nx/e2e-utils'; const TEN_MINS_MS = 600_000; @@ -28,14 +29,15 @@ describe('Playwright E2E Test runner', () => { it( 'should test and lint example app', - () => { + async () => { ensurePlaywrightBrowsersInstallation(); + const port = await reservePort(); runCLI( `g @nx/web:app demo-e2e --unitTestRunner=none --bundler=vite --e2eTestRunner=none --style=css --no-interactive` ); runCLI( - `g @nx/playwright:configuration --project demo-e2e --webServerCommand="${pmc.runNx} serve demo-e2e" --webServerAddress="http://localhost:4200"` + `g @nx/playwright:configuration --project demo-e2e --webServerCommand="${pmc.runNx} serve demo-e2e --port=${port}" --webServerAddress="http://localhost:${port}"` ); const e2eResults = runCLI(`e2e demo-e2e`); @@ -49,14 +51,15 @@ describe('Playwright E2E Test runner', () => { it( 'should test and lint example app with js', - () => { + async () => { ensurePlaywrightBrowsersInstallation(); + const port = await reservePort(); runCLI( `g @nx/web:app demo-js-e2e --unitTestRunner=none --bundler=vite --e2eTestRunner=none --style=css --no-interactive` ); runCLI( - `g @nx/playwright:configuration --project demo-js-e2e --js --webServerCommand="${pmc.runNx} serve demo-e2e" --webServerAddress="http://localhost:4200"` + `g @nx/playwright:configuration --project demo-js-e2e --js --webServerCommand="${pmc.runNx} serve demo-e2e --port=${port}" --webServerAddress="http://localhost:${port}"` ); const e2eResults = runCLI(`e2e demo-js-e2e`); @@ -91,16 +94,17 @@ describe('Playwright E2E Test Runner - legacy', () => { it( 'should test and lint example app', - () => { + async () => { ensurePlaywrightBrowsersInstallation(); const pmc = getPackageManagerCommand(); + const port = await reservePort(); runCLI( `g @nx/web:app demo-e2e --directory apps/demo-e2e --unitTestRunner=none --bundler=vite --e2eTestRunner=none --style=css --no-interactive` ); runCLI( - `g @nx/playwright:configuration --project demo-e2e --webServerCommand="${pmc.runNx} serve demo-e2e" --webServerAddress="http://localhost:4200"` + `g @nx/playwright:configuration --project demo-e2e --webServerCommand="${pmc.runNx} serve demo-e2e --port=${port}" --webServerAddress="http://localhost:${port}"` ); const e2eResults = runCLI(`e2e demo-e2e`); @@ -114,16 +118,17 @@ describe('Playwright E2E Test Runner - legacy', () => { it( 'should test and lint example app with js', - () => { + async () => { ensurePlaywrightBrowsersInstallation(); const pmc = getPackageManagerCommand(); + const port = await reservePort(); runCLI( `g @nx/web:app demo-js-e2e --directory apps/demo-js-e2e --unitTestRunner=none --bundler=vite --e2eTestRunner=none --style=css --no-interactive` ); runCLI( - `g @nx/playwright:configuration --project demo-js-e2e --js --webServerCommand="${pmc.runNx} serve demo-e2e" --webServerAddress="http://localhost:4200"` + `g @nx/playwright:configuration --project demo-js-e2e --js --webServerCommand="${pmc.runNx} serve demo-e2e --port=${port}" --webServerAddress="http://localhost:${port}"` ); const e2eResults = runCLI(`e2e demo-js-e2e`); diff --git a/e2e/react/src/module-federation/core-rspack-basic-host-remote-generation.test.ts b/e2e/react/src/module-federation/core-rspack-basic-host-remote-generation.test.ts index 0483c6d49cafff..aadfc8497858d6 100644 --- a/e2e/react/src/module-federation/core-rspack-basic-host-remote-generation.test.ts +++ b/e2e/react/src/module-federation/core-rspack-basic-host-remote-generation.test.ts @@ -1,13 +1,14 @@ import { stripIndents } from '@nx/devkit'; import { checkFilesExist, - getAvailablePort, + reservePorts, killProcessAndPorts, runCLIAsync, runCommandUntil, runE2ETests, uniq, updateFile, + updateJson, } from '@nx/e2e-utils'; import { readPort, runCLI } from './utils'; import { @@ -33,12 +34,25 @@ describe('React Rspack Module Federation - Basic - Host Remote Generation', () = const remote1 = uniq('remote1'); const remote2 = uniq('remote2'); const remote3 = uniq('remote3'); - const shellPort = await getAvailablePort(); + const [shellPort, remote1Port, remote2Port, remote3Port] = + await reservePorts(4); runCLI( `generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1},${remote2},${remote3} --devServerPort=${shellPort} --bundler=rspack --e2eTestRunner=cypress --style=css --no-interactive --skipFormat --js=${js}` ); + const remotePorts: Array<[string, number]> = [ + [remote1, remote1Port], + [remote2, remote2Port], + [remote3, remote3Port], + ]; + for (const [remote, port] of remotePorts) { + updateJson(`apps/${remote}/project.json`, (project) => { + project.targets.serve.options.port = port; + return project; + }); + } + checkFilesExist( `apps/${shell}/module-federation.config.${js ? 'js' : 'ts'}` ); diff --git a/e2e/react/src/module-federation/core-webpack-basic-host-remote-generation.test.ts b/e2e/react/src/module-federation/core-webpack-basic-host-remote-generation.test.ts index 982f94a205e9ab..7db71140fe4aad 100644 --- a/e2e/react/src/module-federation/core-webpack-basic-host-remote-generation.test.ts +++ b/e2e/react/src/module-federation/core-webpack-basic-host-remote-generation.test.ts @@ -1,13 +1,14 @@ import { stripIndents } from '@nx/devkit'; import { checkFilesExist, - getAvailablePort, + reservePorts, killProcessAndPorts, runCLIAsync, runCommandUntil, runE2ETests, uniq, updateFile, + updateJson, } from '@nx/e2e-utils'; import { readPort, runCLI } from './utils'; import { @@ -33,12 +34,25 @@ describe('React Module Federation - Webpack Basic - Host Remote Generation', () const remote1 = uniq('remote1'); const remote2 = uniq('remote2'); const remote3 = uniq('remote3'); - const shellPort = await getAvailablePort(); + const [shellPort, remote1Port, remote2Port, remote3Port] = + await reservePorts(4); runCLI( `generate @nx/react:host ${shell} --remotes=${remote1},${remote2},${remote3} --devServerPort=${shellPort} --bundler=webpack --e2eTestRunner=cypress --style=css --no-interactive --skipFormat --js=${js}` ); + const remotePorts: Array<[string, number]> = [ + [remote1, remote1Port], + [remote2, remote2Port], + [remote3, remote3Port], + ]; + for (const [remote, port] of remotePorts) { + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = port; + return project; + }); + } + checkFilesExist(`${shell}/module-federation.config.${js ? 'js' : 'ts'}`); checkFilesExist( `${remote1}/module-federation.config.${js ? 'js' : 'ts'}` diff --git a/e2e/react/src/module-federation/core-webpack-ssr.test.ts b/e2e/react/src/module-federation/core-webpack-ssr.test.ts index c5db4da4c316f3..37ddd45a2c6a4b 100644 --- a/e2e/react/src/module-federation/core-webpack-ssr.test.ts +++ b/e2e/react/src/module-federation/core-webpack-ssr.test.ts @@ -3,11 +3,13 @@ import { killPorts, killProcessAndPorts, readJson, + reservePorts, runCLIAsync, runCommandUntil, runE2ETests, uniq, updateFile, + updateJson, } from '@nx/e2e-utils'; import { readPort, runCLI } from './utils'; import { @@ -32,11 +34,34 @@ describe('React Module Federation - Webpack SSR', () => { `generate @nx/react:host ${shell} --bundler=webpack --ssr --remotes=${remote1},${remote2},${remote3} --style=css --no-interactive --skipFormat` ); + // The generator should default ports to 4200..4203. expect(readPort(shell)).toEqual(4200); expect(readPort(remote1)).toEqual(4201); expect(readPort(remote2)).toEqual(4202); expect(readPort(remote3)).toEqual(4203); + // Override defaults with reserved ports so parallel tests don't collide + // when the SSR `server:*` targets bind sockets. + const [shellPort, remote1Port, remote2Port, remote3Port] = + await reservePorts(4); + const portMap: Array<[string, number]> = [ + [shell, shellPort], + [remote1, remote1Port], + [remote2, remote2Port], + [remote3, remote3Port], + ]; + for (const [app, port] of portMap) { + updateJson(`${app}/project.json`, (project) => { + project.targets.serve.options.port = port; + // The SSR `server` target binds its own socket via @nx/{webpack,rspack}:ssr-dev-server + // and defaults to 4200. Pin it so parallel tests don't collide. + if (project.targets.server?.options) { + project.targets.server.options.port = port; + } + return project; + }); + } + [shell, remote1, remote2, remote3].forEach((app) => { checkFilesExist( `${app}/module-federation.config.ts`, @@ -63,6 +88,26 @@ describe('React Module Federation - Webpack SSR', () => { `generate @nx/react:host ${shell} --ssr --bundler=webpack --remotes=${remote1},${remote2},${remote3} --style=css --e2eTestRunner=cypress --no-interactive --skipFormat` ); + const [shellPort, remote1Port, remote2Port, remote3Port] = + await reservePorts(4); + const portMap: Array<[string, number]> = [ + [shell, shellPort], + [remote1, remote1Port], + [remote2, remote2Port], + [remote3, remote3Port], + ]; + for (const [app, port] of portMap) { + updateJson(`${app}/project.json`, (project) => { + project.targets.serve.options.port = port; + // The SSR `server` target binds its own socket via @nx/{webpack,rspack}:ssr-dev-server + // and defaults to 4200. Pin it so parallel tests don't collide. + if (project.targets.server?.options) { + project.targets.server.options.port = port; + } + return project; + }); + } + const serveResult = await runCommandUntil( `serve ${shell}`, (output) => @@ -83,6 +128,26 @@ describe('React Module Federation - Webpack SSR', () => { `generate @nx/react:host ${shell} --bundler=webpack --ssr --remotes=${remote1},${remote2},${remote3} --style=css --e2eTestRunner=cypress --no-interactive --skipFormat` ); + const [shellPort, remote1Port, remote2Port, remote3Port] = + await reservePorts(4); + const portMap: Array<[string, number]> = [ + [shell, shellPort], + [remote1, remote1Port], + [remote2, remote2Port], + [remote3, remote3Port], + ]; + for (const [app, port] of portMap) { + updateJson(`${app}/project.json`, (project) => { + project.targets.serve.options.port = port; + // The SSR `server` target binds its own socket via @nx/{webpack,rspack}:ssr-dev-server + // and defaults to 4200. Pin it so parallel tests don't collide. + if (project.targets.server?.options) { + project.targets.server.options.port = port; + } + return project; + }); + } + const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1); updateFile(`${shell}-e2e/src/e2e/app.cy.ts`, (content) => { diff --git a/e2e/react/src/module-federation/dynamic-federation.rspack.test.ts b/e2e/react/src/module-federation/dynamic-federation.rspack.test.ts index 88847747e3d471..37208b1327b5b5 100644 --- a/e2e/react/src/module-federation/dynamic-federation.rspack.test.ts +++ b/e2e/react/src/module-federation/dynamic-federation.rspack.test.ts @@ -1,8 +1,8 @@ import { cleanupProject, fileExists, - getAvailablePort, - getAvailablePorts, + reservePort, + reservePorts, killProcessAndPorts, newProject, readJson, @@ -24,7 +24,7 @@ describe('Dynamic Module Federation', () => { it('should load remote dynamic module', async () => { const shell = uniq('shell'); const remote = uniq('remote'); - const [shellPort, remotePort] = await getAvailablePorts(2); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${shell} --remotes=${remote} --devServerPort=${shellPort} --bundler=rspack --e2eTestRunner=cypress --dynamic=true --no-interactive --skipFormat` diff --git a/e2e/react/src/module-federation/dynamic-federation.webpack.test.ts b/e2e/react/src/module-federation/dynamic-federation.webpack.test.ts index cdf761e9641b2b..f44b6d8e41194e 100644 --- a/e2e/react/src/module-federation/dynamic-federation.webpack.test.ts +++ b/e2e/react/src/module-federation/dynamic-federation.webpack.test.ts @@ -1,7 +1,7 @@ import { cleanupProject, fileExists, - getAvailablePorts, + reservePorts, killProcessAndPorts, newProject, readJson, @@ -23,7 +23,7 @@ describe('Dynamic Module Federation', () => { it('should load remote dynamic module', async () => { const shell = uniq('shell'); const remote = uniq('remote'); - const [shellPort, remotePort] = await getAvailablePorts(2); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${shell} --remotes=${remote} --devServerPort=${shellPort} --bundler=webpack --e2eTestRunner=cypress --dynamic=true --no-interactive --skipFormat` diff --git a/e2e/react/src/module-federation/federate-module.rspack.test.ts b/e2e/react/src/module-federation/federate-module.rspack.test.ts index b4bfaf85d9e422..01f21e08d5d69a 100644 --- a/e2e/react/src/module-federation/federate-module.rspack.test.ts +++ b/e2e/react/src/module-federation/federate-module.rspack.test.ts @@ -1,12 +1,13 @@ import { cleanupProject, - getAvailablePort, + reservePorts, killProcessAndPorts, newProject, runCommandUntil, runE2ETests, uniq, updateFile, + updateJson, } from '@nx/e2e-utils'; import { readPort, runCLI } from './utils'; @@ -24,12 +25,17 @@ describe('Federate Module', () => { const module = uniq('module'); const host = uniq('host'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${host} --remotes=${remote} --bundler=rspack --e2eTestRunner=cypress --devServerPort=${shellPort} --no-interactive --skipFormat` ); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); + runCLI(`generate @nx/js:lib ${lib} --no-interactive --skipFormat`); // Federate Module @@ -95,7 +101,6 @@ describe('Federate Module', () => { ); const hostPort = readPort(host); - const remotePort = readPort(remote); // Build host and remote const buildOutput = runCLI(`build ${host}`); @@ -125,12 +130,17 @@ describe('Federate Module', () => { const module = uniq('module'); const host = uniq('host'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort, childRemotePort] = await reservePorts(3); runCLI( `generate @nx/react:host ${host} --remotes=${remote} --bundler=rspack --e2eTestRunner=cypress --devServerPort=${shellPort} --no-interactive --skipFormat` ); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); + runCLI(`generate @nx/js:lib ${lib} --no-interactive --skipFormat`); // Federate Module @@ -138,6 +148,11 @@ describe('Federate Module', () => { `generate @nx/react:federate-module ${lib}/src/index.ts --name=${module} --remote=${childRemote} --remoteDirectory=${childRemote} --bundler=rspack --no-interactive --skipFormat` ); + updateJson(`${childRemote}/project.json`, (project) => { + project.targets.serve.options.port = childRemotePort; + return project; + }); + updateFile( `${lib}/src/index.ts`, `export { default } from './lib/${lib}';` @@ -188,8 +203,6 @@ describe('Federate Module', () => { ); const hostPort = readPort(host); - const remotePort = readPort(remote); - const childRemotePort = readPort(childRemote); // Build host and remote const buildOutput = runCLI(`build ${host}`); diff --git a/e2e/react/src/module-federation/federate-module.webpack.test.ts b/e2e/react/src/module-federation/federate-module.webpack.test.ts index a29deef72c5dfe..c8de81351f1011 100644 --- a/e2e/react/src/module-federation/federate-module.webpack.test.ts +++ b/e2e/react/src/module-federation/federate-module.webpack.test.ts @@ -1,12 +1,13 @@ import { cleanupProject, - getAvailablePort, + reservePorts, killProcessAndPorts, newProject, runCommandUntil, runE2ETests, uniq, updateFile, + updateJson, } from '@nx/e2e-utils'; import { readPort, runCLI } from './utils'; @@ -24,12 +25,17 @@ describe('Federate Module', () => { const module = uniq('module'); const host = uniq('host'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${host} --bundler=webpack --remotes=${remote} --devServerPort=${shellPort} --e2eTestRunner=cypress --no-interactive --skipFormat` ); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); + runCLI(`generate @nx/js:lib ${lib} --no-interactive --skipFormat`); // Federate Module @@ -95,7 +101,6 @@ describe('Federate Module', () => { ); const hostPort = readPort(host); - const remotePort = readPort(remote); // Build host and remote const buildOutput = runCLI(`build ${host}`); @@ -125,12 +130,17 @@ describe('Federate Module', () => { const module = uniq('module'); const host = uniq('host'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort, childRemotePort] = await reservePorts(3); runCLI( `generate @nx/react:host ${host} --remotes=${remote} --devServerPort=${shellPort} --bundler=webpack --e2eTestRunner=cypress --no-interactive --skipFormat` ); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); + runCLI(`generate @nx/js:lib ${lib} --no-interactive --skipFormat`); // Federate Module @@ -138,6 +148,11 @@ describe('Federate Module', () => { `generate @nx/react:federate-module ${lib}/src/index.ts --bundler=webpack --name=${module} --remote=${childRemote} --remoteDirectory=${childRemote} --no-interactive --skipFormat` ); + updateJson(`${childRemote}/project.json`, (project) => { + project.targets.serve.options.port = childRemotePort; + return project; + }); + updateFile( `${lib}/src/index.ts`, `export { default } from './lib/${lib}';` @@ -188,8 +203,6 @@ describe('Federate Module', () => { ); const hostPort = readPort(host); - const remotePort = readPort(remote); - const childRemotePort = readPort(childRemote); // Build host and remote const buildOutput = runCLI(`build ${host}`); diff --git a/e2e/react/src/module-federation/independent-deployability.rspack.test.ts b/e2e/react/src/module-federation/independent-deployability.rspack.test.ts index 60af1c5e65d5e2..cc7f659ad0dbf6 100644 --- a/e2e/react/src/module-federation/independent-deployability.rspack.test.ts +++ b/e2e/react/src/module-federation/independent-deployability.rspack.test.ts @@ -1,6 +1,6 @@ import { cleanupProject, - getAvailablePort, + reservePorts, killProcessAndPorts, newProject, runCommandUntil, @@ -26,13 +26,16 @@ describe('Independent Deployability', () => { it('should support promised based remotes', async () => { const remote = uniq('remote'); const host = uniq('host'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${host} --remotes=${remote} --devServerPort=${shellPort} --bundler=rspack --e2eTestRunner=cypress --no-interactive --typescriptConfiguration=false --skipFormat` ); - const remotePort = readPort(remote); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); // Update remote to be loaded via script updateFile( `${remote}/module-federation.config.js`, @@ -143,18 +146,21 @@ describe('Independent Deployability', () => { const remote = uniq('remote'); const lib = uniq('lib'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${shell} --remotes=${remote} --devServerPort=${shellPort} --bundler=rspack --e2eTestRunner=cypress --no-interactive --skipFormat` ); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); + runCLI( `generate @nx/js:lib ${lib} --importPath=@acme/${lib} --publishable=true --no-interactive --skipFormat` ); - const remotePort = readPort(remote); - updateFile( `${lib}/src/lib/${lib}.ts`, stripIndents` @@ -292,13 +298,16 @@ describe('Independent Deployability', () => { const shell = uniq('shell'); const remote = uniq('remote'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${shell} --remotes=${remote} --bundler=rspack --devServerPort=${shellPort} --e2eTestRunner=cypress --no-interactive --skipFormat` ); - const remotePort = readPort(remote); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); // update host and remote to use library type var updateFile( @@ -379,7 +388,8 @@ describe('Independent Deployability', () => { const hostE2eResultsSwc = await runCommandUntil( `e2e ${shell}-e2e --verbose`, (output) => - output.includes('NX Successfully ran target e2e for project') + output.includes('NX Successfully ran target e2e for project'), + { timeout: 120000 } ); await killProcessAndPorts( hostE2eResultsSwc.pid, @@ -391,7 +401,8 @@ describe('Independent Deployability', () => { const remoteE2eResultsSwc = await runCommandUntil( `e2e ${remote}-e2e --verbose`, (output) => - output.includes('NX Successfully ran target e2e for project') + output.includes('NX Successfully ran target e2e for project'), + { timeout: 120000 } ); await killProcessAndPorts(remoteE2eResultsSwc.pid, remotePort); diff --git a/e2e/react/src/module-federation/independent-deployability.webpack.test.ts b/e2e/react/src/module-federation/independent-deployability.webpack.test.ts index 4488fdf47343c1..6fbb94fcf679cd 100644 --- a/e2e/react/src/module-federation/independent-deployability.webpack.test.ts +++ b/e2e/react/src/module-federation/independent-deployability.webpack.test.ts @@ -1,6 +1,6 @@ import { cleanupProject, - getAvailablePort, + reservePorts, killProcessAndPorts, newProject, runCommandUntil, @@ -28,13 +28,16 @@ describe('Independent Deployability', () => { const remote = uniq('remote'); const host = uniq('host'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${host} --remotes=${remote} --devServerPort=${shellPort} --bundler=webpack --e2eTestRunner=cypress --no-interactive --typescriptConfiguration=false --skipFormat` ); - const remotePort = readPort(remote); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); // Update remote to be loaded via script updateFile( @@ -165,18 +168,21 @@ describe('Independent Deployability', () => { const remote = uniq('remote'); const lib = uniq('lib'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${shell} --remotes=${remote} --devServerPort=${shellPort} --bundler=webpack --e2eTestRunner=cypress --no-interactive --skipFormat` ); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); + runCLI( `generate @nx/js:lib ${lib} --importPath=@acme/${lib} --publishable=true --no-interactive --skipFormat` ); - const remotePort = readPort(remote); - updateFile( `${lib}/src/lib/${lib}.ts`, stripIndents` @@ -315,13 +321,16 @@ describe('Independent Deployability', () => { it('should support host and remote with library type var', async () => { const shell = uniq('shell'); const remote = uniq('remote'); - const shellPort = await getAvailablePort(); + const [shellPort, remotePort] = await reservePorts(2); runCLI( `generate @nx/react:host ${shell} --devServerPort=${shellPort} --remotes=${remote} --bundler=webpack --e2eTestRunner=cypress --no-interactive --skipFormat` ); - const remotePort = readPort(remote); + updateJson(`${remote}/project.json`, (project) => { + project.targets.serve.options.port = remotePort; + return project; + }); // update host and remote to use library type var updateFile( diff --git a/e2e/react/src/module-federation/misc-rspack-convert-to-rspack.test.ts b/e2e/react/src/module-federation/misc-rspack-convert-to-rspack.test.ts index 383756b6c7d77a..efc522a206d9c7 100644 --- a/e2e/react/src/module-federation/misc-rspack-convert-to-rspack.test.ts +++ b/e2e/react/src/module-federation/misc-rspack-convert-to-rspack.test.ts @@ -6,7 +6,8 @@ import { runE2ETests, uniq, updateFile, - getAvailablePort, + updateJson, + reservePorts, } from '@nx/e2e-utils'; import { readPort, runCLI } from './utils'; import { stripIndents } from 'nx/src/utils/strip-indents'; @@ -24,12 +25,17 @@ describe('React Rspack Module Federation Misc - Convert To Rspack', () => { it('should generate host and remote apps in webpack, convert to rspack and use playwright for e2es', async () => { const shell = uniq('shell'); const remote1 = uniq('remote1'); - const shellPort = await getAvailablePort(); + const [shellPort, remote1Port] = await reservePorts(2); runCLI( `generate @nx/react:host ${shell} --remotes=${remote1} --bundler=webpack --devServerPort=${shellPort} --e2eTestRunner=playwright --style=css --no-interactive --skipFormat` ); + updateJson(`${remote1}/project.json`, (project) => { + project.targets.serve.options.port = remote1Port; + return project; + }); + runCLI( `generate @nx/rspack:convert-webpack ${shell} --skipFormat --no-interactive` ); diff --git a/e2e/react/src/module-federation/misc-rspack-interoperability.test.ts b/e2e/react/src/module-federation/misc-rspack-interoperability.test.ts index b4116aa7276f13..60bad4f084f310 100644 --- a/e2e/react/src/module-federation/misc-rspack-interoperability.test.ts +++ b/e2e/react/src/module-federation/misc-rspack-interoperability.test.ts @@ -6,7 +6,8 @@ import { runE2ETests, uniq, updateFile, - getAvailablePort, + updateJson, + reservePorts, } from '@nx/e2e-utils'; import { readPort, runCLI } from './utils'; import { stripIndents } from 'nx/src/utils/strip-indents'; @@ -25,16 +26,26 @@ describe('React Rspack Module Federation Misc - Interoperability', () => { const shell = uniq('shell'); const remote1 = uniq('remote1'); const remote2 = uniq('remote2'); - const shellPort = await getAvailablePort(); + const [shellPort, remote1Port, remote2Port] = await reservePorts(3); runCLI( `generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1} --bundler=webpack --devServerPort=${shellPort} --e2eTestRunner=cypress --style=css --no-interactive --skipFormat` ); + updateJson(`apps/${remote1}/project.json`, (project) => { + project.targets.serve.options.port = remote1Port; + return project; + }); + runCLI( `generate @nx/react:remote apps/${remote2} --name=${remote2} --host=${shell} --bundler=rspack --style=css --no-interactive --skipFormat` ); + updateJson(`apps/${remote2}/project.json`, (project) => { + project.targets.serve.options.port = remote2Port; + return project; + }); + updateFile( `apps/${shell}-e2e/src/integration/app.spec.ts`, stripIndents` @@ -66,8 +77,10 @@ describe('React Rspack Module Federation Misc - Interoperability', () => { }); }); - const serveResult = await runCommandUntil(`serve ${shell}`, (output) => - output.includes(`http://localhost:${readPort(shell)}`) + const serveResult = await runCommandUntil( + `serve ${shell}`, + (output) => output.includes(`http://localhost:${readPort(shell)}`), + { timeout: 120000 } ); await killProcessAndPorts(serveResult.pid, readPort(shell)); @@ -75,7 +88,8 @@ describe('React Rspack Module Federation Misc - Interoperability', () => { if (runE2ETests()) { const e2eResultsSwc = await runCommandUntil( `e2e ${shell}-e2e --verbose`, - (output) => output.includes('All specs passed!') + (output) => output.includes('All specs passed!'), + { timeout: 120000 } ); await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell)); @@ -86,16 +100,26 @@ describe('React Rspack Module Federation Misc - Interoperability', () => { const shell = uniq('shell'); const remote1 = uniq('remote1'); const remote2 = uniq('remote2'); - const shellPort = await getAvailablePort(); + const [shellPort, remote1Port, remote2Port] = await reservePorts(3); runCLI( `generate @nx/react:host apps/${shell} --name=${shell} --remotes=${remote1} --bundler=rspack --devServerPort=${shellPort} --e2eTestRunner=cypress --style=css --no-interactive --skipFormat` ); + updateJson(`apps/${remote1}/project.json`, (project) => { + project.targets.serve.options.port = remote1Port; + return project; + }); + runCLI( `generate @nx/react:remote apps/${remote2} --name=${remote2} --host=${shell} --bundler=webpack --style=css --no-interactive --skipFormat` ); + updateJson(`apps/${remote2}/project.json`, (project) => { + project.targets.serve.options.port = remote2Port; + return project; + }); + updateFile( `apps/${shell}-e2e/src/integration/app.cy.ts`, stripIndents` @@ -124,7 +148,8 @@ describe('React Rspack Module Federation Misc - Interoperability', () => { if (runE2ETests()) { const e2eResultsSwc = await runCommandUntil( `e2e ${shell}-e2e --verbose`, - (output) => output.includes('Successfully ran target e2e') + (output) => output.includes('Successfully ran target e2e'), + { timeout: 120000 } ); await killProcessAndPorts(e2eResultsSwc.pid, readPort(shell)); diff --git a/e2e/react/src/module-federation/ts-solution-mf.test.ts b/e2e/react/src/module-federation/ts-solution-mf.test.ts index b151fae516d250..d11e042305dcb7 100644 --- a/e2e/react/src/module-federation/ts-solution-mf.test.ts +++ b/e2e/react/src/module-federation/ts-solution-mf.test.ts @@ -4,7 +4,7 @@ import { cleanupProject, checkFilesDoNotExist, checkFilesExist, - getAvailablePort, + reservePort, killProcessAndPorts, readFile, readJson, @@ -45,7 +45,7 @@ describe('React Rspack Module Federation - TS Solution + PM Workspaces', () => { const shell = uniq('shell'); const remote1 = uniq('remote1'); const remote2 = uniq('remote2'); - const shellPort = await getAvailablePort(); + const shellPort = await reservePort(); // Generate host with remotes runCLI( @@ -192,7 +192,7 @@ describe('React Rspack Module Federation - TS Solution + PM Workspaces', () => { const shell = uniq('shell'); const remote1 = uniq('remote1'); const remote2 = uniq('remote2'); - const shellPort = await getAvailablePort(); + const shellPort = await reservePort(); // Generate host with one remote runCLI( @@ -226,7 +226,7 @@ describe('React Rspack Module Federation - TS Solution + PM Workspaces', () => { const shell = uniq('shell'); const remote = uniq('remote'); const lib = uniq('lib'); - const shellPort = await getAvailablePort(); + const shellPort = await reservePort(); // Generate a library runCLI( diff --git a/e2e/react/src/react-rsbuild.test.ts b/e2e/react/src/react-rsbuild.test.ts index 92f45a59e72be5..eccc444cf0932b 100644 --- a/e2e/react/src/react-rsbuild.test.ts +++ b/e2e/react/src/react-rsbuild.test.ts @@ -1,7 +1,7 @@ import { checkFilesExist, cleanupProject, - getAvailablePort, + reservePort, newProject, runCLI, runCLIAsync, @@ -67,7 +67,7 @@ describe('Build React applications and libraries with Rsbuild', () => { it('should support bundling with Rsbuild and Jest', async () => { const rsbuildApp = uniq('rsbuildapp'); - const port = await getAvailablePort(); + const port = await reservePort(); runCLI( `generate @nx/react:app apps/${rsbuildApp} --port=${port} --bundler=rsbuild --unitTestRunner=jest --no-interactive --linter=eslint` diff --git a/e2e/react/src/react-vite.test.ts b/e2e/react/src/react-vite.test.ts index 91e742e9b65a5f..cb89fb60ee283b 100644 --- a/e2e/react/src/react-vite.test.ts +++ b/e2e/react/src/react-vite.test.ts @@ -1,7 +1,7 @@ import { checkFilesExist, cleanupProject, - getAvailablePort, + reservePort, killPorts, newProject, readFile, @@ -66,7 +66,7 @@ describe('Build React applications and libraries with Vite', () => { it('should generate app with custom port', async () => { const viteApp = uniq('viteapp'); - const customPort = await getAvailablePort(); + const customPort = await reservePort(); runCLI( `generate @nx/react:app apps/${viteApp} --bundler=vite --port=${customPort} --unitTestRunner=vitest --no-interactive --linter=eslint --e2eTestRunner=playwright` diff --git a/e2e/release/src/release-publishable-libraries-ts-solution.test.ts b/e2e/release/src/release-publishable-libraries-ts-solution.test.ts index 4779e717437b8d..10bbadafd963e8 100644 --- a/e2e/release/src/release-publishable-libraries-ts-solution.test.ts +++ b/e2e/release/src/release-publishable-libraries-ts-solution.test.ts @@ -69,7 +69,7 @@ describe('release publishable libraries in workspace with ts solution setup', () // This is the verdaccio instance that the e2e tests themselves are working from e2eRegistryUrl = execSync('npm config get registry').toString().trim(); - }, 100000); + }, 300_000); beforeEach(() => { try { diff --git a/e2e/release/src/release-publishable-libraries.test.ts b/e2e/release/src/release-publishable-libraries.test.ts index b3e5fc955473a4..ad6dc3826fddc3 100644 --- a/e2e/release/src/release-publishable-libraries.test.ts +++ b/e2e/release/src/release-publishable-libraries.test.ts @@ -76,7 +76,7 @@ describe('release publishable libraries', () => { // This is the verdaccio instance that the e2e tests themselves are working from e2eRegistryUrl = execSync('npm config get registry').toString().trim(); - }, 100000); + }, 300_000); beforeEach(() => { try { diff --git a/e2e/storybook/src/storybook-angular.test.ts b/e2e/storybook/src/storybook-angular.test.ts index b0a4f525115bd0..85666188cc1de8 100644 --- a/e2e/storybook/src/storybook-angular.test.ts +++ b/e2e/storybook/src/storybook-angular.test.ts @@ -3,6 +3,7 @@ import { cleanupProject, killPorts, newProject, + reservePort, runCLI, runCommandUntil, uniq, @@ -25,12 +26,14 @@ describe('Storybook executors for Angular', () => { }); describe('serve and build storybook', () => { - afterAll(() => killPorts(4400)); + let storybookPort: number; + afterAll(() => storybookPort && killPorts(storybookPort)); // TODO(jack): re-enable when lodash@4.18.0 assignWith bug is resolved it.skip('should serve an Angular based Storybook setup', async () => { + storybookPort = await reservePort(); const p = await runCommandUntil( - `run ${angularStorybookLib}:storybook --port 4400`, + `run ${angularStorybookLib}:storybook --port ${storybookPort}`, (output) => { return /Storybook.*(started|ready)/gi.test(output); } diff --git a/e2e/storybook/src/storybook-nested.test.ts b/e2e/storybook/src/storybook-nested.test.ts index 3a6babc41aa3a6..696005542b0fe8 100644 --- a/e2e/storybook/src/storybook-nested.test.ts +++ b/e2e/storybook/src/storybook-nested.test.ts @@ -4,6 +4,7 @@ import { getSelectedPackageManager, killPorts, readJson, + reservePort, runCLI, runCommandUntil, runCreateWorkspace, @@ -49,11 +50,13 @@ describe('Storybook generators and executors for standalone workspaces - using R }); describe('serve storybook', () => { - afterEach(() => killPorts(4400)); + let storybookPort: number; + afterEach(() => storybookPort && killPorts(storybookPort)); it('should serve a React based Storybook setup that uses Vite', async () => { + storybookPort = await reservePort(); const p = await runCommandUntil( - `run ${appName}:storybook --port 4400`, + `run ${appName}:storybook --port ${storybookPort}`, (output) => { return /Storybook.*(started|ready)/gi.test(output); } diff --git a/e2e/utils/command-utils.ts b/e2e/utils/command-utils.ts index fdd461def46e00..16d9e060fa50da 100644 --- a/e2e/utils/command-utils.ts +++ b/e2e/utils/command-utils.ts @@ -303,6 +303,7 @@ export function runCommandUntil( encoding: 'utf-8', env: { CI: 'true', + NX_DAEMON: 'true', // Use new versioning by default in e2e tests NX_INTERNAL_USE_LEGACY_VERSIONING: 'false', ...getStrippedEnvironmentVariables(), diff --git a/e2e/utils/port-utils.ts b/e2e/utils/port-utils.ts index f28a03b95de0bf..961a7581fc2ce9 100644 --- a/e2e/utils/port-utils.ts +++ b/e2e/utils/port-utils.ts @@ -1,8 +1,64 @@ +import * as fs from 'fs'; import * as net from 'net'; +import * as os from 'os'; +import * as path from 'path'; /** - * Finds an available port by creating a temporary server on port 0 - * and letting the OS assign a free port. + * Reserves a port across parallel processes on the same host via an atomic + * lock file. Avoids the TOCTOU race of probing port 0 and then binding it + * seconds later — another parallel process could be handed the same port in + * between. + * + * Starts at 6100 (outside the framework-default zone of 3000/4200/5173/8080/etc.) + * so reserved ports never collide with parallel tests that generate apps + * without explicitly pinning the dev-server port. + */ +const LOCK_DIR = path.join(os.tmpdir(), 'nx-e2e-port-locks'); +fs.mkdirSync(LOCK_DIR, { recursive: true }); + +export async function reservePort(start = 6100): Promise { + for (let port = start; port < 65000; port++) { + const lock = path.join(LOCK_DIR, `${port}.lock`); + try { + fs.writeFileSync(lock, '', { flag: 'wx' }); + } catch (err) { + if ((err as NodeJS.ErrnoException).code !== 'EEXIST') throw err; + continue; + } + // Lock claimed; now verify the OS port is actually free. Another e2e test + // on the same agent may be using the OS port via the generator's default + // (i.e. without participating in the lock-file scheme), so an exclusive + // lock is not enough. + if (!(await isPortAvailable(port))) { + try { + fs.unlinkSync(lock); + } catch {} + continue; + } + process.on('exit', () => { + try { + fs.unlinkSync(lock); + } catch {} + }); + return port; + } + throw new Error('No available ports'); +} + +/** + * Reserves `count` ports. + */ +export async function reservePorts(count: number): Promise { + const ports: number[] = []; + for (let i = 0; i < count; i++) { + ports.push(await reservePort()); + } + return ports; +} + +/** + * @deprecated Use {@link reservePort} — probing the OS for port 0 opens a + * TOCTOU race across parallel e2e processes. Kept for backwards compatibility. */ export async function getAvailablePort(): Promise { return new Promise((resolve, reject) => { @@ -25,10 +81,7 @@ export async function getAvailablePort(): Promise { } /** - * Finds multiple available ports for running multiple services - * - * @param count - Number of ports needed - * @returns Promise - Array of available port numbers + * @deprecated Use {@link reservePorts}. */ export async function getAvailablePorts(count: number): Promise { const ports: number[] = []; diff --git a/e2e/vite/src/vite.test.ts b/e2e/vite/src/vite.test.ts index 2d3859ee67c0cb..0d74d661f26940 100644 --- a/e2e/vite/src/vite.test.ts +++ b/e2e/vite/src/vite.test.ts @@ -4,6 +4,7 @@ import { killProcessAndPorts, newProject, readJson, + reservePort, runCLI, runCommand, runCommandUntil, @@ -135,7 +136,7 @@ describe('@nx/vite/plugin', () => { it('should run serve-static', async () => { let process: ChildProcess; - const port = 8081; + const port = await reservePort(); try { process = await runCommandUntil( diff --git a/e2e/vue/src/vue.test.ts b/e2e/vue/src/vue.test.ts index 55b3c9be557acb..e2a10f54f55b04 100644 --- a/e2e/vue/src/vue.test.ts +++ b/e2e/vue/src/vue.test.ts @@ -2,6 +2,7 @@ import { cleanupProject, killPorts, newProject, + reservePort, runCLI, runE2ETests, uniq, @@ -34,7 +35,7 @@ describe('Vue Plugin', () => { ); if (runE2ETests('playwright')) { - const availablePort = await getAvailablePort(); + const availablePort = await reservePort(); updateFile(`${app}-e2e/playwright.config.ts`, (content) => { return content @@ -76,7 +77,7 @@ describe('Vue Plugin', () => { ); if (runE2ETests('playwright')) { - const availablePort = await getAvailablePort(); + const availablePort = await reservePort(); updateFile(`${app}-e2e/playwright.config.ts`, (content) => { return content @@ -115,25 +116,3 @@ describe('Vue Plugin', () => { ); }); }); - -async function getAvailablePort(): Promise { - const net = require('net'); - - return new Promise((resolve, reject) => { - const server = net.createServer(); - server.unref(); - server.on('error', reject); - - server.listen(0, () => { - const addressInfo = server.address(); - if (!addressInfo) { - reject(new Error('Failed to get server address')); - return; - } - const port = addressInfo.port; - server.close(() => { - resolve(port); - }); - }); - }); -} diff --git a/e2e/web/src/web-webpack.test.ts b/e2e/web/src/web-webpack.test.ts index 47add23cbf710b..72fac69aaa4da9 100644 --- a/e2e/web/src/web-webpack.test.ts +++ b/e2e/web/src/web-webpack.test.ts @@ -2,6 +2,7 @@ import { cleanupProject, killProcessAndPorts, newProject, + reservePort, runCLI, runCommandUntil, uniq, @@ -22,13 +23,14 @@ describe('Web Components Applications with bundler set as webpack', () => { } ); + const port = await reservePort(); const childProcess = await runCommandUntil( - `serve ${appName} --port=5000 --ssl`, + `serve ${appName} --port=${port} --ssl`, (output) => { - return output.includes('listening at https://localhost:5000'); + return output.includes(`listening at https://localhost:${port}`); } ); - await killProcessAndPorts(childProcess.pid, 5000); + await killProcessAndPorts(childProcess.pid, port); }, 300_000); }); diff --git a/e2e/webpack/src/webpack.legacy.test.ts b/e2e/webpack/src/webpack.legacy.test.ts index 2d88d6a7899223..70fb04c9630bb6 100644 --- a/e2e/webpack/src/webpack.legacy.test.ts +++ b/e2e/webpack/src/webpack.legacy.test.ts @@ -1,10 +1,10 @@ import { checkFilesExist, cleanupProject, - getAvailablePort, killProcessAndPorts, newProject, readFile, + reservePort, runCLI, runCommandUntil, runE2ETests, @@ -51,7 +51,7 @@ describe('Webpack Plugin (legacy)', () => { it('should run serve-static', async () => { let process: ChildProcess; - const port = await getAvailablePort(); + const port = await reservePort(); try { process = await runCommandUntil( diff --git a/e2e/webpack/src/webpack.test.ts b/e2e/webpack/src/webpack.test.ts index 09ec1281430fec..934c1bd66a2b1e 100644 --- a/e2e/webpack/src/webpack.test.ts +++ b/e2e/webpack/src/webpack.test.ts @@ -332,7 +332,7 @@ describe('Webpack Plugin', () => { const myPkg = uniq('my-pkg'); runCLI( - `generate @nx/web:application ${appName} --directory=apps/${appName}` + `generate @nx/web:application ${appName} --directory=apps/${appName} --bundler=webpack` ); runCLI( diff --git a/nx.json b/nx.json index 527d7d0b350474..1375dd4e848562 100644 --- a/nx.json +++ b/nx.json @@ -287,7 +287,7 @@ "nxCloudId": "62d013ea0852fe0a2df74438", "nxCloudUrl": "https://staging.nx.app", "parallel": 1, - "bust": 322, + "bust": 331, "defaultBase": "master", "sync": { "applyChanges": true }, "conformance": { diff --git a/packages/angular/src/generators/application/lib/add-e2e.ts b/packages/angular/src/generators/application/lib/add-e2e.ts index 306b32d036033e..4caaa225f98721 100644 --- a/packages/angular/src/generators/application/lib/add-e2e.ts +++ b/packages/angular/src/generators/application/lib/add-e2e.ts @@ -102,7 +102,7 @@ function getAngularE2EWebServerInfo( const pm = getPackageManagerCommand(); return { - e2eCiBaseUrl: 'http://localhost:4200', + e2eCiBaseUrl: `http://localhost:${e2ePort}`, e2eCiWebServerCommand: `${pm.exec} nx run ${projectName}:serve-static`, e2eWebServerCommand: `${pm.exec} nx run ${projectName}:serve`, e2eWebServerAddress: `http://localhost:${e2ePort}`, diff --git a/packages/angular/src/generators/host/host.ts b/packages/angular/src/generators/host/host.ts index 5bdb3028130509..307b077ee8b6b8 100644 --- a/packages/angular/src/generators/host/host.ts +++ b/packages/angular/src/generators/host/host.ts @@ -73,11 +73,13 @@ export async function host(tree: Tree, schema: Schema) { directory: options.directory, }); + const hostPort = options.port ?? 4200; + const appInstallTask = await applicationGenerator(tree, { ...options, standalone: options.standalone, routing: true, - port: 4200, + port: hostPort, skipFormat: true, bundler: 'webpack', }); @@ -88,7 +90,7 @@ export async function host(tree: Tree, schema: Schema) { appName: hostProjectName, mfType: 'host', routing: true, - port: 4200, + port: hostPort, remotes: remotesToIntegrate ?? [], federationType: options.dynamic ? 'dynamic' : 'static', skipPackageJson: options.skipPackageJson, @@ -124,7 +126,7 @@ export async function host(tree: Tree, schema: Schema) { name: remote, directory: remoteDirectory, host: hostProjectName, - port: isRspack ? 4200 + i + 1 : undefined, + port: isRspack ? hostPort + i + 1 : undefined, skipFormat: true, standalone: options.standalone, typescriptConfiguration, @@ -144,7 +146,7 @@ export async function host(tree: Tree, schema: Schema) { const project = readProjectConfiguration(tree, hostProjectName); project.targets.serve ??= {}; project.targets.serve.options ??= {}; - project.targets.serve.options.port = 4200; + project.targets.serve.options.port = hostPort; updateProjectConfiguration(tree, hostProjectName, project); if (!options.skipFormat) { diff --git a/packages/angular/src/generators/host/schema.d.ts b/packages/angular/src/generators/host/schema.d.ts index 489117b41ae385..17b9a2adfb30b6 100644 --- a/packages/angular/src/generators/host/schema.d.ts +++ b/packages/angular/src/generators/host/schema.d.ts @@ -6,6 +6,7 @@ export interface Schema { directory: string; name?: string; bundler?: 'webpack' | 'rspack'; + port?: number; remotes?: string[]; dynamic?: boolean; setParserOptionsProject?: boolean; diff --git a/packages/angular/src/generators/host/schema.json b/packages/angular/src/generators/host/schema.json index ba31c8ddf9abb9..fa279e05356055 100644 --- a/packages/angular/src/generators/host/schema.json +++ b/packages/angular/src/generators/host/schema.json @@ -43,6 +43,10 @@ "default": "webpack", "enum": ["webpack", "rspack"] }, + "port": { + "type": "number", + "description": "The port at which the Consumer (host) application should be served. Defaults to 4200." + }, "dynamic": { "type": "boolean", "description": "Should the host application use dynamic federation?",