diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 234a4948..937cf474 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: # Connect your workspace on nx.app and uncomment this to enable task distribution. # The "--stop-agents-after" is optional, but allows idle agents to shut down once the "e2e" targets have been requested - - run: pnpm exec nx-cloud start-ci-run --distribute-on=".nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e" --with-env-vars="ANDROID_SDK_VERSION,FLUTTER_VERSION,JDK_VERSION" + - run: pnpm exec nx-cloud start-ci-run --distribute-on=".nx/workflows/dynamic-changesets.yaml" --stop-agents-after="e2e-ci" --with-env-vars="ANDROID_SDK_VERSION,FLUTTER_VERSION,JDK_VERSION" # This line is needed for nx affected to work when CI is running on a PR - run: git branch --track develop origin/develop || exit 0 @@ -46,4 +46,4 @@ jobs: main-branch-name: 'develop' - run: pnpm exec nx-cloud record -- nx format:check - - run: pnpm exec nx affected -t lint test-ci build e2e --parallel=5 --exclude=smoke + - run: pnpm exec nx affected -t lint test-ci build e2e-ci --parallel=5 --exclude=smoke diff --git a/.verdaccio/config.yml b/.verdaccio/config.yml index e9cdce7d..c1018fa2 100644 --- a/.verdaccio/config.yml +++ b/.verdaccio/config.yml @@ -31,4 +31,3 @@ logs: publish: allow_offline: true # set offline to true to allow publish offline -listen: 0.0.0.0:4873 # listen on all addresses (INADDR_ANY) diff --git a/e2e/nx-ktor-e2e/tests/nx-ktor.spec.ts b/e2e/nx-ktor-e2e/tests/nx-ktor.spec.ts index b6037c9b..7eb7a4fd 100644 --- a/e2e/nx-ktor-e2e/tests/nx-ktor.spec.ts +++ b/e2e/nx-ktor-e2e/tests/nx-ktor.spec.ts @@ -1,3 +1,4 @@ +import { getPackageManagerCommand } from '@nx/devkit'; import { uniq } from '@nx/plugin/testing'; import { createTestProject, @@ -19,11 +20,14 @@ describe('nx-ktor e2e', () => { // The plugin has been built and published to a local registry in the jest globalSetup // Install the plugin built with the latest source code into the test repo - execSync(`npm install @nxrocks/nx-ktor@0.0.0-e2e`, { - cwd: projectDirectory, - stdio: 'inherit', - env: process.env, - }); + execSync( + `${getPackageManagerCommand().install} @nxrocks/nx-ktor@0.0.0-e2e`, + { + cwd: projectDirectory, + stdio: 'inherit', + env: process.env, + } + ); }); afterAll(() => { @@ -37,7 +41,7 @@ describe('nx-ktor e2e', () => { it('should be installed', () => { // npm ls will fail if the package is not installed properly - execSync('npm ls @nxrocks/nx-ktor', { + execSync(`${getPackageManagerCommand().list} @nxrocks/nx-ktor`, { cwd: projectDirectory, stdio: 'inherit', }); diff --git a/e2e/nx-melos-e2e/tests/nx-melos.spec.ts b/e2e/nx-melos-e2e/tests/nx-melos.spec.ts index 119e07a3..8e27cd86 100644 --- a/e2e/nx-melos-e2e/tests/nx-melos.spec.ts +++ b/e2e/nx-melos-e2e/tests/nx-melos.spec.ts @@ -1,4 +1,9 @@ -import { checkFilesExist, createTestProject, runNxCommandAsync } from '@nxrocks/common/testing'; +import { getPackageManagerCommand } from '@nx/devkit'; +import { + checkFilesExist, + createTestProject, + runNxCommandAsync, +} from '@nxrocks/common/testing'; import { execSync } from 'child_process'; import { rmSync } from 'fs-extra'; @@ -10,24 +15,28 @@ describe('nx-melos e2e', () => { // The plugin has been built and published to a local registry in the jest globalSetup // Install the plugin built with the latest source code into the test repo - execSync(`npm install @nxrocks/nx-melos@0.0.0-e2e`, { - cwd: projectDirectory, - stdio: 'inherit', - env: process.env, - }); + execSync( + `${getPackageManagerCommand().install} @nxrocks/nx-melos@0.0.0-e2e`, + { + cwd: projectDirectory, + stdio: 'inherit', + env: process.env, + } + ); }); afterAll(() => { // Cleanup the test project - projectDirectory && rmSync(projectDirectory, { - recursive: true, - force: true, - }); + projectDirectory && + rmSync(projectDirectory, { + recursive: true, + force: true, + }); }); it('should be installed', () => { // npm ls will fail if the package is not installed properly - execSync('npm ls @nxrocks/nx-melos', { + execSync(`${getPackageManagerCommand().list} @nxrocks/nx-melos`, { cwd: projectDirectory, stdio: 'inherit', }); diff --git a/e2e/nx-micronaut-e2e/tests/nx-micronaut.spec.ts b/e2e/nx-micronaut-e2e/tests/nx-micronaut.spec.ts index 3473465f..d930efd5 100644 --- a/e2e/nx-micronaut-e2e/tests/nx-micronaut.spec.ts +++ b/e2e/nx-micronaut-e2e/tests/nx-micronaut.spec.ts @@ -1,3 +1,4 @@ +import { getPackageManagerCommand } from '@nx/devkit'; import { uniq } from '@nx/plugin/testing'; import { createTestProject, @@ -20,11 +21,14 @@ describe('nx-micronaut e2e', () => { // The plugin has been built and published to a local registry in the jest globalSetup // Install the plugin built with the latest source code into the test repo - execSync(`npm install @nxrocks/nx-micronaut@0.0.0-e2e`, { - cwd: projectDirectory, - stdio: 'inherit', - env: process.env, - }); + execSync( + `${getPackageManagerCommand().install} @nxrocks/nx-micronaut@0.0.0-e2e`, + { + cwd: projectDirectory, + stdio: 'inherit', + env: process.env, + } + ); }); afterAll(() => { @@ -38,7 +42,7 @@ describe('nx-micronaut e2e', () => { it('should be installed', () => { // npm ls will fail if the package is not installed properly - execSync('npm ls @nxrocks/nx-micronaut', { + execSync(`${getPackageManagerCommand().list} @nxrocks/nx-micronaut`, { cwd: projectDirectory, stdio: 'inherit', }); diff --git a/e2e/nx-quarkus-e2e/tests/nx-quarkus.spec.ts b/e2e/nx-quarkus-e2e/tests/nx-quarkus.spec.ts index 5a121b90..8c6a81ff 100644 --- a/e2e/nx-quarkus-e2e/tests/nx-quarkus.spec.ts +++ b/e2e/nx-quarkus-e2e/tests/nx-quarkus.spec.ts @@ -1,3 +1,4 @@ +import { getPackageManagerCommand } from '@nx/devkit'; import { uniq } from '@nx/plugin/testing'; import { createTestProject, @@ -21,11 +22,14 @@ describe('nx-quarkus e2e', () => { // The plugin has been built and published to a local registry in the jest globalSetup // Install the plugin built with the latest source code into the test repo - execSync(`npm install @nxrocks/nx-quarkus@0.0.0-e2e`, { - cwd: projectDirectory, - stdio: 'inherit', - env: process.env, - }); + execSync( + `${getPackageManagerCommand().install} @nxrocks/nx-quarkus@0.0.0-e2e`, + { + cwd: projectDirectory, + stdio: 'inherit', + env: process.env, + } + ); }); afterAll(() => { @@ -39,7 +43,7 @@ describe('nx-quarkus e2e', () => { it('should be installed', () => { // npm ls will fail if the package is not installed properly - execSync('npm ls @nxrocks/nx-quarkus', { + execSync(`${getPackageManagerCommand().list} @nxrocks/nx-quarkus`, { cwd: projectDirectory, stdio: 'inherit', }); diff --git a/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts b/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts index 3e31c01b..5d1b9623 100644 --- a/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts +++ b/e2e/nx-spring-boot-e2e/tests/nx-spring-boot.spec.ts @@ -9,7 +9,7 @@ import { runNxCommandAsync, tmpProjPath, } from '@nxrocks/common-jvm/testing'; -import { names } from '@nx/devkit'; +import { getPackageManagerCommand, names } from '@nx/devkit'; import { lstatSync, rmSync } from 'fs'; import { execSync } from 'child_process'; @@ -22,11 +22,14 @@ describe('nx-spring-boot e2e', () => { // The plugin has been built and published to a local registry in the jest globalSetup // Install the plugin built with the latest source code into the test repo - execSync(`npm install @nxrocks/nx-spring-boot@0.0.0-e2e`, { - cwd: projectDirectory, - stdio: 'inherit', - env: process.env, - }); + execSync( + `${getPackageManagerCommand().install} @nxrocks/nx-spring-boot@0.0.0-e2e`, + { + cwd: projectDirectory, + stdio: 'inherit', + env: process.env, + } + ); }); afterAll(() => { @@ -39,7 +42,7 @@ describe('nx-spring-boot e2e', () => { it('should be installed', () => { // npm ls will fail if the package is not installed properly - execSync('npm ls @nxrocks/nx-spring-boot', { + execSync(`${getPackageManagerCommand().list} @nxrocks/nx-spring-boot`, { cwd: projectDirectory, stdio: 'inherit', }); diff --git a/e2e/smoke/project.json b/e2e/smoke/project.json index 731047ab..0c9e12c9 100644 --- a/e2e/smoke/project.json +++ b/e2e/smoke/project.json @@ -3,14 +3,13 @@ "$schema": "../../node_modules/nx/schemas/project-schema.json", "sourceRoot": "e2e/smoke/src", "projectType": "application", - "tags": [], + "tags": ["e2e"], "implicitDependencies": [ "nx-spring-boot", "nx-quarkus", "nx-micronaut", "nx-flutter", - "nx-ktor", - "nx-melos" + "nx-ktor" ], "targets": {} } diff --git a/nx.json b/nx.json index ad22e95f..bd3cf399 100644 --- a/nx.json +++ b/nx.json @@ -21,7 +21,8 @@ "generatorOptions": { "packageRoot": "dist/packages/{projectName}", "currentVersionResolver": "git-tag", - "specifierSource": "conventional-commits" + "specifierSource": "conventional-commits", + "updateDependents": "auto" } } }, @@ -56,7 +57,7 @@ "passWithNoTests": true, "runInBand": true }, - "dependsOn": ["^build"], + "dependsOn": ["nxrocks:populate-local-registry"], "configurations": { "ci": { "ci": true, @@ -69,7 +70,7 @@ "passWithNoTests": true, "runInBand": true }, - "dependsOn": ["^build"], + "dependsOn": ["nxrocks:populate-local-registry"], "configurations": { "ci": { "ci": true, @@ -162,5 +163,8 @@ "sharedGlobals": ["{workspaceRoot}/.nx/workflows/**/**"] }, "defaultBase": "develop", - "useLegacyCache": true + "useLegacyCache": true, + "cli": { + "packageManager": "pnpm" + } } diff --git a/packages/common/testing/e2e/index.ts b/packages/common/testing/e2e/index.ts index ebf9c983..74c04a09 100644 --- a/packages/common/testing/e2e/index.ts +++ b/packages/common/testing/e2e/index.ts @@ -1,17 +1,35 @@ -import { JsonParseOptions, PackageManager, detectPackageManager, getPackageManagerCommand, joinPathFragments, parseJson, readJsonFile, workspaceRoot } from '@nx/devkit'; +import { + JsonParseOptions, + PackageManager, + detectPackageManager, + getPackageManagerCommand, + joinPathFragments, + parseJson, + readJsonFile, + workspaceRoot, +} from '@nx/devkit'; import { getPackageLatestNpmVersion } from '../../src/'; import { exec, execSync } from 'child_process'; -import { rmSync, mkdirSync, statSync, readFileSync, existsSync } from 'fs-extra'; +import { + rmSync, + mkdirSync, + statSync, + readFileSync, + existsSync, +} from 'fs-extra'; import { join, dirname, isAbsolute } from 'path'; - export const isWin = process.platform === 'win32'; /** * Creates a test project with create-nx-workspace and installs the plugin * @returns The directory where the test project was created */ -export function createTestProject(pkgManager: PackageManager = 'npm', projectName = 'test-project', workspaceVersion: 'latest' | 'local' = 'local') { +export function createTestProject( + pkgManager: PackageManager = detectPackageManager(), + projectName = 'test-project', + workspaceVersion: 'latest' | 'local' = 'local' +) { const projectDirectory = join(process.cwd(), 'tmp', projectName); // Ensure projectDirectory is empty @@ -23,11 +41,14 @@ export function createTestProject(pkgManager: PackageManager = 'npm', projectNam recursive: true, }); - const nxVersion = workspaceVersion === 'local' ? readLocalNxWorkspaceVersion() : 'latest'; + const nxVersion = + workspaceVersion === 'local' ? readLocalNxWorkspaceVersion() : 'latest'; const confirm = pkgManager === 'npm' ? ' --yes' : ''; execSync( - `${getPackageManagerCommand(pkgManager).dlx}${confirm} create-nx-workspace@${nxVersion} ${projectName} --preset apps --nxCloud=skip --no-interactive --pm ${pkgManager}`, + `${ + getPackageManagerCommand(pkgManager).dlx + }${confirm} create-nx-workspace@${nxVersion} ${projectName} --preset apps --nxCloud=skip --no-interactive --pm ${pkgManager}`, { cwd: dirname(projectDirectory), stdio: 'inherit', @@ -44,8 +65,13 @@ export function createTestProject(pkgManager: PackageManager = 'npm', projectNam * Creates a test project with create-nx-workspace and installs the plugin * @returns The directory where the test project was created */ -export function createCLITestProject(createPkgName: string, extraArgs = '', createPkgVersion = '0.0.0-e2e', pkgManager: PackageManager = 'npm', projectName = 'test-project') { - +export function createCLITestProject( + createPkgName: string, + extraArgs = '', + createPkgVersion = '0.0.0-e2e', + pkgManager: PackageManager = detectPackageManager(), + projectName = 'test-project' +) { const projectDirectory = join(process.cwd(), 'tmp', projectName); // Ensure projectDirectory is empty @@ -57,17 +83,21 @@ export function createCLITestProject(createPkgName: string, extraArgs = '', crea recursive: true, }); - execSync(`${getPackageManagerCommand(pkgManager).dlx} --yes ${createPkgName}@${createPkgVersion} ${projectName} ${extraArgs}`, { - cwd: dirname(projectDirectory), - stdio: 'inherit', - env: process.env, - }); + execSync( + `${ + getPackageManagerCommand(pkgManager).dlx + } --yes ${createPkgName}@${createPkgVersion} ${projectName} ${extraArgs}`, + { + cwd: dirname(projectDirectory), + stdio: 'inherit', + env: process.env, + } + ); console.log(`Created test project in "${projectDirectory}"`); return projectDirectory; } - // Temporary utilities from @nx/plugin/testing package, like `runNxCommandAsync`, `readJson`, etc // Adapted to support the new 'test-project' folder, instead of the default 'nx-e2e' used internally by those methods // (until properly updated/published By Nx ?) @@ -104,7 +134,6 @@ function exists(filePath: string): boolean { return directoryExists(filePath) || fileExists(filePath); } - //https://github.com/nrwl/nx/blob/master/e2e/utils/create-project-utils.ts#L628 /** @@ -150,7 +179,9 @@ export function runNxCommandAsync( ): Promise<{ stdout: string; stderr: string }> { const cwd = opts.cwd ?? tmpProjPath(); if (fileExists(tmpProjPath('package.json'))) { - const pmc = getPackageManagerCommand(pkgManager || detectPackageManager(cwd)); + const pmc = getPackageManagerCommand( + pkgManager || detectPackageManager(cwd) + ); return runCommandAsync(`${pmc.exec} nx ${command}`, opts); } else if (process.platform === 'win32') { return runCommandAsync(`./nx.bat %${command}`, opts); @@ -170,13 +201,14 @@ export async function runNxCommand( opts: { silenceError?: boolean; env?: NodeJS.ProcessEnv; cwd?: string } = { silenceError: false, } -): Promise<{ stdout: string; stderr: string; }> { - - return await runNxCommandAsync(command,pkgManager, opts); - +): Promise<{ stdout: string; stderr: string }> { + return await runNxCommandAsync(command, pkgManager, opts); } -export function readJson(path: string, options?: JsonParseOptions): T { +export function readJson( + path: string, + options?: JsonParseOptions +): T { return parseJson(readFile(path), options); } @@ -186,7 +218,7 @@ export function readFile(path: string): string { } export function getLatestNxWorkspaceVersion(): string { - return getPackageLatestNpmVersion("nx"); + return getPackageLatestNpmVersion('nx'); } export function readLocalNxWorkspaceVersion(): string { @@ -197,7 +229,7 @@ export function readLocalNxWorkspaceVersion(): string { ); } - return readJsonFile(pkgJsonPath).devDependencies["nx"]; + return readJsonFile(pkgJsonPath).devDependencies['nx']; } /** @@ -208,5 +240,8 @@ export function isLocalNxMatchingLatestFeatureVersion() { const localNxVersion = readLocalNxWorkspaceVersion().split('.'); const latestNxVersion = getLatestNxWorkspaceVersion().split('.'); - return localNxVersion[0] === latestNxVersion[0] && localNxVersion[1] === latestNxVersion?.[1]; -} \ No newline at end of file + return ( + localNxVersion[0] === latestNxVersion[0] && + localNxVersion[1] === latestNxVersion?.[1] + ); +} diff --git a/project.json b/project.json index 1888954a..d604cd22 100644 --- a/project.json +++ b/project.json @@ -5,9 +5,17 @@ "local-registry": { "executor": "@nx/js:verdaccio", "options": { + "port": 4873, + "listenAddress": "0.0.0.0", "config": ".verdaccio/config.yml", "storage": "tmp/local-registry/storage" } + }, + "populate-local-registry": { + "cache": true, + "inputs": ["production"], + "command": "ts-node -P ./tools/tsconfig.tools.json ./tools/scripts/populate-local-registry.ts", + "outputs": ["{workspaceRoot}/tmp/local-registry/storage"] } } } diff --git a/tools/scripts/populate-local-registry.ts b/tools/scripts/populate-local-registry.ts new file mode 100644 index 00000000..8e67dd30 --- /dev/null +++ b/tools/scripts/populate-local-registry.ts @@ -0,0 +1,54 @@ +/** + * This script starts a local registry, populates it by installing packages,then stop the registry. + * It is meant to allows reusing that storage when running e2e tasks later on. + */ +import { startLocalRegistry } from '@nx/js/plugins/jest/local-registry'; +import { releasePublish, releaseVersion } from 'nx/release'; + +(async () => { + + // local registry target to run + const localRegistryTarget = 'nxrocks:local-registry'; + // storage folder for the local registry + const storage = './tmp/local-registry/storage'; + + const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true'; + + let stopLocalRegistry; + + try { + stopLocalRegistry = await startLocalRegistry({ + localRegistryTarget, + storage, + verbose: isVerbose, + listenAddress: '0.0.0.0', + }); + + console.log('Publishing packages to local registry to populate storage...'); + await releaseVersion({ + specifier: '0.0.0-e2e', + stageChanges: false, + gitCommit: false, + gitTag: false, + firstRelease: true, + generatorOptionsOverrides: { + skipLockFileUpdate: true, + }, + }); + await releasePublish({ + tag: 'e2e', + firstRelease: true, + }); + + stopLocalRegistry(); + console.log('Killed local registry process'); + } catch (err) { + // Clean up registry if possible after setup related errors + if (typeof stopLocalRegistry === 'function') { + stopLocalRegistry(); + console.log('Killed local registry process due to an error during setup'); + } + console.error('Error:', err); + process.exit(1); + } +})(); diff --git a/tools/scripts/run-all-e2e-tests.ts b/tools/scripts/run-all-e2e-tests.ts index 2bd6fa33..950abfe5 100644 --- a/tools/scripts/run-all-e2e-tests.ts +++ b/tools/scripts/run-all-e2e-tests.ts @@ -4,17 +4,14 @@ import stopRegistry from './stop-local-registry'; (async () => { - + await startLocalRegistry(); const nx = require.resolve('nx'); execFileSync( nx, ['affected', '-t', 'e2e', '--runInBand', '--exclude', 'smoke'], { - env: { - ...process.env, - 'SKIP_LOCAL_REGISTRY_GLOBAL_SETUP': 'true', - }, stdio: 'inherit' + stdio: 'inherit' } ); diff --git a/tools/scripts/start-local-registry.ts b/tools/scripts/start-local-registry.ts index 7a5a3b75..c6f41834 100644 --- a/tools/scripts/start-local-registry.ts +++ b/tools/scripts/start-local-registry.ts @@ -3,42 +3,47 @@ * It is meant to be called in jest's globalSetup. */ import { startLocalRegistry } from '@nx/js/plugins/jest/local-registry'; +import { directoryExists } from '@nx/workspace/src/utilities/fileutils'; import { releasePublish, releaseVersion } from 'nx/release'; export default async () => { - if ( - process.env.SKIP_LOCAL_REGISTRY_GLOBAL_SETUP && - process.env.SKIP_LOCAL_REGISTRY_GLOBAL_SETUP !== 'false' - ) { - console.log( - "Environment variable 'SKIP_LOCAL_REGISTRY_GLOBAL_SETUP' is set. Skipping global setup of Verdaccio's Local Registry..." - ); - return; - } // local registry target to run const localRegistryTarget = 'nxrocks:local-registry'; // storage folder for the local registry const storage = './tmp/local-registry/storage'; + const isVerbose = process.env.NX_VERBOSE_LOGGING === 'true'; + + /** + * For e2e-ci we populate the verdaccio storage up front, but for other workflows we need + * to run the full local release process before running tests. + */ + const requiresLocalRelease = + !process.env.NX_TASK_TARGET_TARGET?.startsWith('e2e-ci') && !directoryExists(storage); + global.stopLocalRegistry = await startLocalRegistry({ localRegistryTarget, storage, - verbose: false, + verbose: isVerbose, + clearStorage: requiresLocalRelease, + listenAddress: '0.0.0.0', }); - await releaseVersion({ - specifier: '0.0.0-e2e', - stageChanges: false, - gitCommit: false, - gitTag: false, - firstRelease: true, - generatorOptionsOverrides: { - skipLockFileUpdate: true, - }, - }); - await releasePublish({ - tag: 'e2e', - firstRelease: true, - }); + if (requiresLocalRelease) { + await releaseVersion({ + specifier: '0.0.0-e2e', + stageChanges: false, + gitCommit: false, + gitTag: false, + firstRelease: true, + generatorOptionsOverrides: { + skipLockFileUpdate: true, + }, + }); + await releasePublish({ + tag: 'e2e', + firstRelease: true, + }); + } }; diff --git a/tools/scripts/stop-local-registry.ts b/tools/scripts/stop-local-registry.ts index 7df2ae80..a2fa0da1 100644 --- a/tools/scripts/stop-local-registry.ts +++ b/tools/scripts/stop-local-registry.ts @@ -4,11 +4,7 @@ */ export default () => { - if(process.env.SKIP_LOCAL_REGISTRY_GLOBAL_SETUP && process.env.SKIP_LOCAL_REGISTRY_GLOBAL_SETUP !== 'false') { - console.log("\nEnvironment variable 'SKIP_LOCAL_REGISTRY_GLOBAL_SETUP' is set. Skipping global teardown of Verdaccio's Local Registry..."); - return; - } - + if (global.stopLocalRegistry) { global.stopLocalRegistry(); }