From f987e8a49ec4bdcb5a7b13a188db23933677690d Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 21:47:07 +0100 Subject: [PATCH 01/20] feat: mvp --- .../tests/plugin-create-nodes.e2e.test.ts | 3 +- nx.json | 3 ++ packages/nx-plugin/src/plugin/plugin.ts | 41 ++++++++++++++++--- .../src/plugin/target/configuration-target.ts | 6 +-- .../src/plugin/target/executor-target.ts | 4 +- .../nx-plugin/src/plugin/target/targets.ts | 10 ++--- packages/nx-plugin/src/plugin/types.ts | 19 +++++++-- packages/nx-plugin/src/plugin/utils.ts | 6 +-- 8 files changed, 69 insertions(+), 23 deletions(-) diff --git a/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts b/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts index 11db54c22..e8d6f895d 100644 --- a/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts +++ b/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts @@ -14,7 +14,6 @@ import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR, removeColorCodes, - teardownTestFolder, } from '@code-pushup/test-utils'; import { executeProcess, readTextFile } from '@code-pushup/utils'; import { INLINE_PLUGIN } from './inline-plugin.js'; @@ -35,7 +34,7 @@ describe('nx-plugin', () => { }); afterEach(async () => { - await teardownTestFolder(testFileDir); + // await teardownTestFolder(testFileDir); }); it('should add configuration target dynamically', async () => { diff --git a/nx.json b/nx.json index 84fbf0790..0c1cd400d 100644 --- a/nx.json +++ b/nx.json @@ -94,6 +94,9 @@ "filterByTags": ["publishable"] } } + }, + { + "plugin": "./packages/nx-plugin/src/plugin/plugin.ts" } ] } diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index 9129f1bd7..00f511b5a 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -1,14 +1,17 @@ -import type { +import { CreateNodes, CreateNodesContext, CreateNodesResult, + CreateNodesV2, + createNodesFromFiles, } from '@nx/devkit'; -import { PROJECT_JSON_FILE_NAME } from '../internal/constants.js'; -import { createTargets } from './target/targets.js'; -import type { CreateNodesOptions } from './types.js'; -import { normalizedCreateNodesContext } from './utils.js'; +import { PROJECT_JSON_FILE_NAME } from '../internal/constants'; +import { createTargets } from './target/targets'; +import type { CreateNodesOptions } from './types'; +import { normalizedCreateNodesContext } from './utils'; // name has to be "createNodes" to get picked up by Nx + export const createNodes: CreateNodes = [ `**/${PROJECT_JSON_FILE_NAME}`, async ( @@ -32,3 +35,31 @@ export const createNodes: CreateNodes = [ }; }, ]; + +export const createNodesV2: CreateNodesV2 = [ + `**/${PROJECT_JSON_FILE_NAME}`, + + async (configFiles, options, context) => { + return await createNodesFromFiles( + async (globMatchingFile, internalOptions) => { + const parsedCreateNodesOptions = internalOptions as CreateNodesOptions; + + const normalizedContext = await normalizedCreateNodesContext( + context, + globMatchingFile, + parsedCreateNodesOptions, + ); + return { + projects: { + [normalizedContext.projectRoot]: { + targets: await createTargets(normalizedContext), + }, + }, + }; + }, + configFiles, + options, + context, + ); + }, +]; diff --git a/packages/nx-plugin/src/plugin/target/configuration-target.ts b/packages/nx-plugin/src/plugin/target/configuration-target.ts index d19b9325b..f9b16c985 100644 --- a/packages/nx-plugin/src/plugin/target/configuration-target.ts +++ b/packages/nx-plugin/src/plugin/target/configuration-target.ts @@ -1,8 +1,8 @@ import type { TargetConfiguration } from '@nx/devkit'; import type { RunCommandsOptions } from 'nx/src/executors/run-commands/run-commands.impl'; -import { objectToCliArgs } from '../../executors/internal/cli.js'; -import { PACKAGE_NAME } from '../../internal/constants.js'; -import { CP_TARGET_NAME } from '../constants.js'; +import { objectToCliArgs } from '../../executors/internal/cli'; +import { PACKAGE_NAME } from '../../internal/constants'; +import { CP_TARGET_NAME } from '../constants'; export function createConfigurationTarget(options?: { targetName?: string; diff --git a/packages/nx-plugin/src/plugin/target/executor-target.ts b/packages/nx-plugin/src/plugin/target/executor-target.ts index e8b52eb8f..aeba82ad8 100644 --- a/packages/nx-plugin/src/plugin/target/executor-target.ts +++ b/packages/nx-plugin/src/plugin/target/executor-target.ts @@ -1,6 +1,6 @@ import type { TargetConfiguration } from '@nx/devkit'; -import { PACKAGE_NAME } from '../../internal/constants.js'; -import type { ProjectPrefixOptions } from '../types.js'; +import { PACKAGE_NAME } from '../../internal/constants'; +import type { ProjectPrefixOptions } from '../types'; export function createExecutorTarget(options?: { bin?: string; diff --git a/packages/nx-plugin/src/plugin/target/targets.ts b/packages/nx-plugin/src/plugin/target/targets.ts index 3e659688b..e6d62e03a 100644 --- a/packages/nx-plugin/src/plugin/target/targets.ts +++ b/packages/nx-plugin/src/plugin/target/targets.ts @@ -1,9 +1,9 @@ import { readdir } from 'node:fs/promises'; -import { CP_TARGET_NAME } from '../constants.js'; -import type { NormalizedCreateNodesContext } from '../types.js'; -import { createConfigurationTarget } from './configuration-target.js'; -import { CODE_PUSHUP_CONFIG_REGEX } from './constants.js'; -import { createExecutorTarget } from './executor-target.js'; +import { CP_TARGET_NAME } from '../constants'; +import type { NormalizedCreateNodesContext } from '../types'; +import { createConfigurationTarget } from './configuration-target'; +import { CODE_PUSHUP_CONFIG_REGEX } from './constants'; +import { createExecutorTarget } from './executor-target'; export async function createTargets( normalizedContext: NormalizedCreateNodesContext, diff --git a/packages/nx-plugin/src/plugin/types.ts b/packages/nx-plugin/src/plugin/types.ts index 5e8d59db7..0e96f0e21 100644 --- a/packages/nx-plugin/src/plugin/types.ts +++ b/packages/nx-plugin/src/plugin/types.ts @@ -1,6 +1,10 @@ -import type { CreateNodesContext, ProjectConfiguration } from '@nx/devkit'; +import type { + CreateNodesContext, + CreateNodesContextV2, + ProjectConfiguration, +} from '@nx/devkit'; import type { WithRequired } from '@code-pushup/utils'; -import type { DynamicTargetOptions } from '../internal/types.js'; +import type { DynamicTargetOptions } from '../internal/types'; export type ProjectPrefixOptions = { projectPrefix?: string; @@ -13,7 +17,16 @@ export type ProjectConfigurationWithName = WithRequired< 'name' >; -export type NormalizedCreateNodesContext = CreateNodesContext & { +export type NormalizedCreateNodesContext = ( + | CreateNodesContext + | CreateNodesContextV2 +) & { + projectJson: ProjectConfigurationWithName; + projectRoot: string; + createOptions: CreateNodesOptions; +}; + +export type NormalizedCreateNodesContextV2 = CreateNodesContextV2 & { projectJson: ProjectConfigurationWithName; projectRoot: string; createOptions: CreateNodesOptions; diff --git a/packages/nx-plugin/src/plugin/utils.ts b/packages/nx-plugin/src/plugin/utils.ts index e7a819f8d..b21eb765a 100644 --- a/packages/nx-plugin/src/plugin/utils.ts +++ b/packages/nx-plugin/src/plugin/utils.ts @@ -1,7 +1,7 @@ -import type { CreateNodesContext } from '@nx/devkit'; +import type { CreateNodesContext, CreateNodesContextV2 } from '@nx/devkit'; import { readFile } from 'node:fs/promises'; import * as path from 'node:path'; -import { CP_TARGET_NAME } from './constants.js'; +import { CP_TARGET_NAME } from './constants'; import type { CreateNodesOptions, NormalizedCreateNodesContext, @@ -9,7 +9,7 @@ import type { } from './types.js'; export async function normalizedCreateNodesContext( - context: CreateNodesContext, + context: CreateNodesContext | CreateNodesContextV2, projectConfigurationFile: string, createOptions: CreateNodesOptions = {}, ): Promise { From 9eeca80d05d9eb7dfdcf08747fe3bd5ca0267925 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 21:49:18 +0100 Subject: [PATCH 02/20] chore: wip --- e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts b/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts index e8d6f895d..11db54c22 100644 --- a/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts +++ b/e2e/nx-plugin-e2e/tests/plugin-create-nodes.e2e.test.ts @@ -14,6 +14,7 @@ import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR, removeColorCodes, + teardownTestFolder, } from '@code-pushup/test-utils'; import { executeProcess, readTextFile } from '@code-pushup/utils'; import { INLINE_PLUGIN } from './inline-plugin.js'; @@ -34,7 +35,7 @@ describe('nx-plugin', () => { }); afterEach(async () => { - // await teardownTestFolder(testFileDir); + await teardownTestFolder(testFileDir); }); it('should add configuration target dynamically', async () => { From a2cfed66ad8725a93f50a7e909c9e497719dfe05 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 21:52:34 +0100 Subject: [PATCH 03/20] chore: wip --- nx.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/nx.json b/nx.json index 0c1cd400d..84fbf0790 100644 --- a/nx.json +++ b/nx.json @@ -94,9 +94,6 @@ "filterByTags": ["publishable"] } } - }, - { - "plugin": "./packages/nx-plugin/src/plugin/plugin.ts" } ] } From 9a5c13815ff4c49d7359435c6150e86b7e76fe3f Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 21:56:05 +0100 Subject: [PATCH 04/20] chore: wip --- packages/nx-plugin/eslint.config.cjs | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nx-plugin/eslint.config.cjs b/packages/nx-plugin/eslint.config.cjs index e732748e6..71a018585 100644 --- a/packages/nx-plugin/eslint.config.cjs +++ b/packages/nx-plugin/eslint.config.cjs @@ -15,6 +15,7 @@ module.exports = tseslint.config( { files: ['**/*.ts'], rules: { + 'n/file-extension-in-import': 'off', // Nx plugins don't yet support ESM: https://github.com/nrwl/nx/issues/15682 'unicorn/prefer-module': 'off', // used instead of verbatimModuleSyntax tsconfig flag (requires ESM) From d8641bb9dbb366a32aa18ba47a4d0c77d9aded7b Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 21:57:57 +0100 Subject: [PATCH 05/20] chore: wip --- packages/nx-plugin/src/plugin/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index 00f511b5a..d07d56ea7 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -36,7 +36,7 @@ export const createNodes: CreateNodes = [ }, ]; -export const createNodesV2: CreateNodesV2 = [ +export const createNodesV2: CreateNodesV2 = [ `**/${PROJECT_JSON_FILE_NAME}`, async (configFiles, options, context) => { From cf572798824b76aced77b61c6a736eb2b3175452 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 22:01:44 +0100 Subject: [PATCH 06/20] chore: wip --- packages/nx-plugin/src/plugin/plugin.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index d07d56ea7..68c00b054 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -1,10 +1,10 @@ -import { +import type { CreateNodes, CreateNodesContext, CreateNodesResult, CreateNodesV2, - createNodesFromFiles, } from '@nx/devkit'; +import { createNodesFromFiles } from '@nx/devkit'; import { PROJECT_JSON_FILE_NAME } from '../internal/constants'; import { createTargets } from './target/targets'; import type { CreateNodesOptions } from './types'; @@ -38,9 +38,8 @@ export const createNodes: CreateNodes = [ export const createNodesV2: CreateNodesV2 = [ `**/${PROJECT_JSON_FILE_NAME}`, - - async (configFiles, options, context) => { - return await createNodesFromFiles( + async (configFiles, options, context) => + createNodesFromFiles( async (globMatchingFile, internalOptions) => { const parsedCreateNodesOptions = internalOptions as CreateNodesOptions; @@ -60,6 +59,5 @@ export const createNodesV2: CreateNodesV2 = [ configFiles, options, context, - ); - }, + ) ]; From b39e1ebd4beec4efe2dc35e8c51e59642e203825 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 22:09:34 +0100 Subject: [PATCH 07/20] chore: wip --- packages/nx-plugin/src/plugin/plugin.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index 68c00b054..fdaf75187 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -1,10 +1,10 @@ -import type { - CreateNodes, - CreateNodesContext, - CreateNodesResult, - CreateNodesV2, +import { + type CreateNodes, + type CreateNodesContext, + type CreateNodesResult, + type CreateNodesV2, + createNodesFromFiles } from '@nx/devkit'; -import { createNodesFromFiles } from '@nx/devkit'; import { PROJECT_JSON_FILE_NAME } from '../internal/constants'; import { createTargets } from './target/targets'; import type { CreateNodesOptions } from './types'; From db535affdc7d81c3da9daf4bd2c337a779952f96 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Sun, 9 Mar 2025 22:10:59 +0100 Subject: [PATCH 08/20] chore: wip --- packages/nx-plugin/src/plugin/plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index fdaf75187..433fbb0df 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -3,7 +3,7 @@ import { type CreateNodesContext, type CreateNodesResult, type CreateNodesV2, - createNodesFromFiles + createNodesFromFiles, } from '@nx/devkit'; import { PROJECT_JSON_FILE_NAME } from '../internal/constants'; import { createTargets } from './target/targets'; @@ -59,5 +59,5 @@ export const createNodesV2: CreateNodesV2 = [ configFiles, options, context, - ) + ), ]; From ed2dae97a61db5d77cb87ea87217388bff662337 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 10 Mar 2025 22:39:02 +0100 Subject: [PATCH 09/20] chore: create v1-v2 function helpers and tests --- packages/nx-plugin/src/plugin/plugin.ts | 13 +- .../nx-plugin/src/plugin/plugin.unit.test.ts | 232 ++++++++++-------- packages/nx-plugin/src/plugin/utils.ts | 7 + .../nx-plugin/src/plugin/utils.unit.test.ts | 16 +- .../test-nx-utils/src/lib/utils/nx-plugin.ts | 42 +++- .../src/lib/utils/nx-plugin.unit.test.ts | 168 +++++++++---- 6 files changed, 313 insertions(+), 165 deletions(-) diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index 433fbb0df..0afdf1f25 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -5,13 +5,12 @@ import { type CreateNodesV2, createNodesFromFiles, } from '@nx/devkit'; -import { PROJECT_JSON_FILE_NAME } from '../internal/constants'; -import { createTargets } from './target/targets'; -import type { CreateNodesOptions } from './types'; -import { normalizedCreateNodesContext } from './utils'; - -// name has to be "createNodes" to get picked up by Nx +import { PROJECT_JSON_FILE_NAME } from '../internal/constants.js'; +import { createTargets } from './target/targets.js'; +import type { CreateNodesOptions } from './types.js'; +import { normalizedCreateNodesContext } from './utils.js'; +/** Create the nodes for a V1 Plugin. The name `createNodes` is required by Nx in order to be picked up as a plugin. */ export const createNodes: CreateNodes = [ `**/${PROJECT_JSON_FILE_NAME}`, async ( @@ -36,6 +35,7 @@ export const createNodes: CreateNodes = [ }, ]; +/** Create the nodes for a V2 Plugin. The name `createNodesV2` is required by Nx in order to be picked up as a plugin. */ export const createNodesV2: CreateNodesV2 = [ `**/${PROJECT_JSON_FILE_NAME}`, async (configFiles, options, context) => @@ -48,6 +48,7 @@ export const createNodesV2: CreateNodesV2 = [ globMatchingFile, parsedCreateNodesOptions, ); + return { projects: { [normalizedContext.projectRoot]: { diff --git a/packages/nx-plugin/src/plugin/plugin.unit.test.ts b/packages/nx-plugin/src/plugin/plugin.unit.test.ts index c51ebf570..cc8c77d0e 100644 --- a/packages/nx-plugin/src/plugin/plugin.unit.test.ts +++ b/packages/nx-plugin/src/plugin/plugin.unit.test.ts @@ -1,138 +1,160 @@ -import type { CreateNodesContext } from '@nx/devkit'; +import type { CreateNodesContext, CreateNodesContextV2 } from '@nx/devkit'; import { vol } from 'memfs'; import { describe, expect } from 'vitest'; -import { invokeCreateNodesOnVirtualFiles } from '@code-pushup/test-nx-utils'; +import { + createNodesContextV1, + createNodesContextV2, + invokeCreateNodesOnVirtualFilesV1, + invokeCreateNodesOnVirtualFilesV2, +} from '@code-pushup/test-nx-utils'; import { PACKAGE_NAME, PROJECT_JSON_FILE_NAME } from '../internal/constants.js'; import { CP_TARGET_NAME } from './constants.js'; -import { createNodes } from './plugin.js'; +import { createNodes, createNodesV2 } from './plugin.js'; describe('@code-pushup/nx-plugin/plugin', () => { - let context: CreateNodesContext; + describe('V1', () => { + let context: CreateNodesContext; - beforeEach(() => { - context = { - nxJsonConfiguration: {}, - workspaceRoot: '', - }; - }); + beforeEach(() => { + context = createNodesContextV1({ + nxJsonConfiguration: {}, + workspaceRoot: '', + }); + }); - afterEach(() => { - vol.reset(); - }); + afterEach(() => { + vol.reset(); + }); - it('should normalize context and use it to create the configuration target on ROOT project', async () => { - const projectRoot = '.'; - const matchingFilesData = { - [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ - name: '@org/empty-root', - })}`, - }; + it('should normalize context and use it to create the configuration target on ROOT project', async () => { + const projectRoot = '.'; + const matchingFilesData = { + [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ + name: '@org/empty-root', + })}`, + }; - await expect( - invokeCreateNodesOnVirtualFiles( - createNodes, - context, - {}, - { matchingFilesData }, - ), - ).resolves.toStrictEqual({ - [projectRoot]: { - targets: { - [`${CP_TARGET_NAME}--configuration`]: { - command: `nx g ${PACKAGE_NAME}:configuration --skipTarget --targetName="code-pushup" --project="@org/empty-root"`, + await expect( + invokeCreateNodesOnVirtualFilesV1( + createNodes, + context, + {}, + { matchingFilesData }, + ), + ).resolves.toStrictEqual({ + [projectRoot]: { + targets: { + [`${CP_TARGET_NAME}--configuration`]: { + command: `nx g ${PACKAGE_NAME}:configuration --skipTarget --targetName="code-pushup" --project="@org/empty-root"`, + }, }, }, - }, + }); }); - }); - it('should normalize context and use it to create the configuration target on PACKAGE project', async () => { - const projectRoot = 'apps/my-app'; - const matchingFilesData = { - [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ - name: '@org/empty-root', - })}`, - }; + it('should create the executor target on PACKAGE project if configured', async () => { + const projectRoot = 'apps/my-app'; + const matchingFilesData = { + [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ + name: '@org/empty-root', + })}`, + [`${projectRoot}/code-pushup.config.ts`]: '{}', + }; - await expect( - invokeCreateNodesOnVirtualFiles( - createNodes, - context, - {}, - { matchingFilesData }, - ), - ).resolves.toStrictEqual({ - [projectRoot]: { - targets: { - [`${CP_TARGET_NAME}--configuration`]: { - command: `nx g ${PACKAGE_NAME}:configuration --skipTarget --targetName="code-pushup" --project="@org/empty-root"`, + await expect( + invokeCreateNodesOnVirtualFilesV1( + createNodes, + context, + { + projectPrefix: 'cli', + }, + { matchingFilesData }, + ), + ).resolves.toStrictEqual({ + [projectRoot]: { + targets: { + [CP_TARGET_NAME]: { + executor: `${PACKAGE_NAME}:cli`, + options: { + projectPrefix: 'cli', + }, + }, }, }, - }, + }); }); }); - it('should create the executor target on ROOT project if configured', async () => { - const projectRoot = '.'; - const matchingFilesData = { - [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ - name: '@org/empty-root', - })}`, - [`${projectRoot}/code-pushup.config.ts`]: '{}', - }; + describe('V2', () => { + let context: CreateNodesContextV2; - await expect( - invokeCreateNodesOnVirtualFiles( - createNodes, - context, - { - projectPrefix: 'cli', - }, - { matchingFilesData }, - ), - ).resolves.toStrictEqual({ - [projectRoot]: { - targets: { - [CP_TARGET_NAME]: { - executor: `${PACKAGE_NAME}:cli`, - options: { - projectPrefix: 'cli', + beforeEach(() => { + context = createNodesContextV2({ + nxJsonConfiguration: {}, + workspaceRoot: '', + }); + }); + + afterEach(() => { + vol.reset(); + }); + + it('should normalize context and use it to create the configuration target on ROOT project', async () => { + const projectRoot = '.'; + const matchingFilesData = { + [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ + name: '@org/empty-root', + })}`, + }; + + await expect( + invokeCreateNodesOnVirtualFilesV2( + createNodesV2, + context, + {}, + { matchingFilesData }, + ), + ).resolves.toStrictEqual({ + [projectRoot]: { + targets: { + [`${CP_TARGET_NAME}--configuration`]: { + command: `nx g ${PACKAGE_NAME}:configuration --skipTarget --targetName="code-pushup" --project="@org/empty-root"`, }, }, }, - }, + }); }); - }); - it('should create the executor target on PACKAGE project if configured', async () => { - const projectRoot = 'apps/my-app'; - const matchingFilesData = { - [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ - name: '@org/empty-root', - })}`, - [`${projectRoot}/code-pushup.config.ts`]: '{}', - }; + it('should create the executor target on PACKAGE project if configured', async () => { + const projectRoot = 'apps/my-app'; + const matchingFilesData = { + [`${projectRoot}/${PROJECT_JSON_FILE_NAME}`]: `${JSON.stringify({ + name: '@org/empty-root', + })}`, + [`${projectRoot}/code-pushup.config.ts`]: '{}', + }; - await expect( - invokeCreateNodesOnVirtualFiles( - createNodes, - context, - { - projectPrefix: 'cli', - }, - { matchingFilesData }, - ), - ).resolves.toStrictEqual({ - [projectRoot]: { - targets: { - [CP_TARGET_NAME]: { - executor: `${PACKAGE_NAME}:cli`, - options: { - projectPrefix: 'cli', + await expect( + invokeCreateNodesOnVirtualFilesV2( + createNodesV2, + context, + { + projectPrefix: 'cli', + }, + { matchingFilesData }, + ), + ).resolves.toStrictEqual({ + [projectRoot]: { + targets: { + [CP_TARGET_NAME]: { + executor: `${PACKAGE_NAME}:cli`, + options: { + projectPrefix: 'cli', + }, }, }, }, - }, + }); }); }); }); diff --git a/packages/nx-plugin/src/plugin/utils.ts b/packages/nx-plugin/src/plugin/utils.ts index b21eb765a..356f773cb 100644 --- a/packages/nx-plugin/src/plugin/utils.ts +++ b/packages/nx-plugin/src/plugin/utils.ts @@ -8,6 +8,13 @@ import type { ProjectConfigurationWithName, } from './types.js'; +/** + * Normalize the context for a V1 or V2 Plugin. + * @param context - The context for a V1 or V2 Plugin. + * @param projectConfigurationFile - The project configuration file. + * @param createOptions - The create options. + * @returns The normalized context. + */ export async function normalizedCreateNodesContext( context: CreateNodesContext | CreateNodesContextV2, projectConfigurationFile: string, diff --git a/packages/nx-plugin/src/plugin/utils.unit.test.ts b/packages/nx-plugin/src/plugin/utils.unit.test.ts index edf2bf1cb..439b72445 100644 --- a/packages/nx-plugin/src/plugin/utils.unit.test.ts +++ b/packages/nx-plugin/src/plugin/utils.unit.test.ts @@ -1,6 +1,6 @@ import { vol } from 'memfs'; import { describe, expect } from 'vitest'; -import { createNodesContext } from '@code-pushup/test-nx-utils'; +import { createNodesContextV2 } from '@code-pushup/test-nx-utils'; import { MEMFS_VOLUME } from '@code-pushup/test-utils'; import { normalizedCreateNodesContext } from './utils.js'; @@ -15,7 +15,7 @@ describe('normalizedCreateNodesContext', () => { await expect( normalizedCreateNodesContext( - createNodesContext({ workspaceRoot: MEMFS_VOLUME }), + createNodesContextV2({ workspaceRoot: MEMFS_VOLUME }), 'project.json', ), ).resolves.toStrictEqual( @@ -37,7 +37,7 @@ describe('normalizedCreateNodesContext', () => { await expect( normalizedCreateNodesContext( - createNodesContext(), + createNodesContextV2(), 'packages/utils/project.json', ), ).resolves.toStrictEqual( @@ -59,7 +59,7 @@ describe('normalizedCreateNodesContext', () => { await expect( normalizedCreateNodesContext( - createNodesContext({ + createNodesContextV2({ nxJsonConfiguration: { workspaceLayout: { libsDir: 'libs', @@ -90,7 +90,7 @@ describe('normalizedCreateNodesContext', () => { ); await expect( - normalizedCreateNodesContext(createNodesContext(), 'project.json'), + normalizedCreateNodesContext(createNodesContextV2(), 'project.json'), ).resolves.toStrictEqual( expect.objectContaining({ projectJson: { @@ -109,7 +109,7 @@ describe('normalizedCreateNodesContext', () => { ); await expect( - normalizedCreateNodesContext(createNodesContext(), 'project.json'), + normalizedCreateNodesContext(createNodesContextV2(), 'project.json'), ).rejects.toThrow('Error parsing project.json file project.json.'); }); @@ -124,7 +124,7 @@ describe('normalizedCreateNodesContext', () => { ); await expect( - normalizedCreateNodesContext(createNodesContext(), 'project.json'), + normalizedCreateNodesContext(createNodesContextV2(), 'project.json'), ).resolves.toStrictEqual( expect.objectContaining({ createOptions: { @@ -145,7 +145,7 @@ describe('normalizedCreateNodesContext', () => { ); await expect( - normalizedCreateNodesContext(createNodesContext(), 'project.json', { + normalizedCreateNodesContext(createNodesContextV2(), 'project.json', { projectPrefix: 'cli', }), ).resolves.toStrictEqual( diff --git a/testing/test-nx-utils/src/lib/utils/nx-plugin.ts b/testing/test-nx-utils/src/lib/utils/nx-plugin.ts index 30d9706ba..5a4b29506 100644 --- a/testing/test-nx-utils/src/lib/utils/nx-plugin.ts +++ b/testing/test-nx-utils/src/lib/utils/nx-plugin.ts @@ -3,6 +3,7 @@ import type { CreateNodesContext, CreateNodesContextV2, CreateNodesResult, + CreateNodesV2, } from '@nx/devkit'; import { vol } from 'memfs'; import { MEMFS_VOLUME } from '@code-pushup/test-utils'; @@ -28,10 +29,9 @@ import { MEMFS_VOLUME } from '@code-pushup/test-utils'; * @param createNodeOptions * @param mockData */ -export async function invokeCreateNodesOnVirtualFiles< +export async function invokeCreateNodesOnVirtualFilesV1< T extends Record | undefined, >( - // FIXME: refactor this to use the V2 api & remove the eslint disable on the whole file createNodes: CreateNodes, context: CreateNodesContext, createNodeOptions: T, @@ -55,7 +55,43 @@ export async function invokeCreateNodesOnVirtualFiles< ); } -export function createNodesContext( +export async function invokeCreateNodesOnVirtualFilesV2< + T extends Record | undefined, +>( + createNodes: CreateNodesV2, + context: CreateNodesContextV2, + createNodeOptions: T, + mockData: { + matchingFilesData: Record; + }, +) { + const { matchingFilesData } = mockData; + vol.fromJSON(matchingFilesData, MEMFS_VOLUME); + + const files = Object.keys(matchingFilesData); + + const results = await createNodes[1](files, createNodeOptions, context); + + const result: NonNullable = {}; + return results.reduce( + (acc, [_, { projects }]) => ({ ...acc, ...projects }), + result, + ); +} + +export function createNodesContextV1( + options?: Partial, +): CreateNodesContext { + const { workspaceRoot = process.cwd(), nxJsonConfiguration = {} } = + options ?? {}; + return { + workspaceRoot, + nxJsonConfiguration, + configFiles: [], + }; +} + +export function createNodesContextV2( options?: Partial, ): CreateNodesContextV2 { const { workspaceRoot = process.cwd(), nxJsonConfiguration = {} } = diff --git a/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts b/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts index 68a9d5daa..5c92eae0d 100644 --- a/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts +++ b/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts @@ -1,61 +1,143 @@ import * as process from 'node:process'; import { describe, expect } from 'vitest'; import { - createNodesContext, - invokeCreateNodesOnVirtualFiles, + createNodesContextV1, + createNodesContextV2, + invokeCreateNodesOnVirtualFilesV1, + invokeCreateNodesOnVirtualFilesV2, } from './nx-plugin.js'; -describe('createNodesContext', () => { - it('should return a context with the provided options', () => { - const context = createNodesContext({ - workspaceRoot: 'root', - nxJsonConfiguration: { plugins: [] }, +describe('V1', () => { + describe('createNodesContextV1', () => { + it('should return a context with the provided options', () => { + const context = createNodesContextV1({ + workspaceRoot: 'root', + nxJsonConfiguration: { plugins: [] }, + }); + expect(context).toEqual({ + workspaceRoot: 'root', + nxJsonConfiguration: { plugins: [] }, + configFiles: [], + }); }); - expect(context).toEqual({ - workspaceRoot: 'root', - nxJsonConfiguration: { plugins: [] }, + + it('should return a context with defaults', () => { + const context = createNodesContextV1(); + expect(context).toEqual({ + workspaceRoot: process.cwd(), + nxJsonConfiguration: {}, + configFiles: [], + }); }); }); - it('should return a context with defaults', () => { - const context = createNodesContext(); - expect(context).toEqual({ - workspaceRoot: process.cwd(), - nxJsonConfiguration: {}, + describe('invokeCreateNodesOnVirtualFilesV1', () => { + it('should invoke passed function if matching file is given', async () => { + const createNodesFnSpy = vi + .fn() + .mockResolvedValue({ projects: { 'my-lib': {} } }); + await expect( + invokeCreateNodesOnVirtualFilesV1( + [`**/project.json`, createNodesFnSpy], + createNodesContextV1(), + {}, + { + matchingFilesData: { + '**/project.json': JSON.stringify({ + name: 'my-lib', + }), + }, + }, + ), + ).resolves.toStrictEqual({ 'my-lib': {} }); + expect(createNodesFnSpy).toHaveBeenCalledTimes(1); + expect(createNodesFnSpy).toHaveBeenCalledWith( + '**/project.json', + {}, + expect.any(Object), + ); + }); + + it('should NOT invoke passed function if matching file is NOT given', async () => { + const createNodesFnSpy = vi.fn().mockResolvedValue({}); + await expect( + invokeCreateNodesOnVirtualFilesV1( + [`**/project.json`, createNodesFnSpy], + createNodesContextV1(), + {}, + { matchingFilesData: {} }, + ), + ).resolves.toStrictEqual({}); + expect(createNodesFnSpy).not.toHaveBeenCalled(); }); }); }); -describe('invokeCreateNodesOnVirtualFiles', () => { - it('should invoke passed function if matching file is given', async () => { - const createNodesFnSpy = vi.fn().mockResolvedValue({}); - await expect( - invokeCreateNodesOnVirtualFiles( - [`**/project.json`, createNodesFnSpy], - createNodesContext(), - {}, - { - matchingFilesData: { - '**/project.json': JSON.stringify({ - name: 'my-lib', - }), - }, - }, - ), - ).resolves.toStrictEqual({}); - expect(createNodesFnSpy).toHaveBeenCalledTimes(1); +describe('V2', () => { + describe('createNodesContext', () => { + it('should return a context with the provided options', () => { + const context = createNodesContextV2({ + workspaceRoot: 'root', + nxJsonConfiguration: { plugins: [] }, + }); + expect(context).toEqual({ + workspaceRoot: 'root', + nxJsonConfiguration: { plugins: [] }, + }); + }); + + it('should return a context with defaults', () => { + const context = createNodesContextV2(); + expect(context).toEqual({ + workspaceRoot: process.cwd(), + nxJsonConfiguration: {}, + }); + }); }); - it('should NOT invoke passed function if matching file is NOT given', async () => { - const createNodesFnSpy = vi.fn().mockResolvedValue({}); - await expect( - invokeCreateNodesOnVirtualFiles( - [`**/project.json`, createNodesFnSpy], - createNodesContext(), + describe('invokeCreateNodesOnVirtualFilesV2', () => { + it('should invoke passed function if matching file is given', async () => { + const createNodesFnSpy = vi + .fn() + .mockResolvedValue([ + ['**/project.json', { projects: { 'my-lib': {} } }], + ]); + + await expect( + invokeCreateNodesOnVirtualFilesV2( + [`**/project.json`, createNodesFnSpy], + createNodesContextV2(), + {}, + { + matchingFilesData: { + '**/project.json': JSON.stringify({ + name: 'my-lib', + }), + }, + }, + ), + ).resolves.toStrictEqual({ 'my-lib': {} }); + + expect(createNodesFnSpy).toHaveBeenCalledTimes(1); + expect(createNodesFnSpy).toHaveBeenCalledWith( + ['**/project.json'], {}, - { matchingFilesData: {} }, - ), - ).resolves.toStrictEqual({}); - expect(createNodesFnSpy).not.toHaveBeenCalled(); + expect.any(Object), + ); + }); + + it('should NOT invoke passed function if matching file is NOT given', async () => { + const createNodesFnSpy = vi.fn().mockResolvedValue([]); + await expect( + invokeCreateNodesOnVirtualFilesV2( + [`**/project.json`, createNodesFnSpy], + createNodesContextV2(), + {}, + { matchingFilesData: {} }, + ), + ).resolves.toStrictEqual({}); + expect(createNodesFnSpy).toHaveBeenCalledTimes(1); + expect(createNodesFnSpy).toHaveBeenCalledWith([], {}, expect.any(Object)); + }); }); }); From 30410824ed5a2c0f2cbd553a0fddaa973c674126 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 10 Mar 2025 22:40:19 +0100 Subject: [PATCH 10/20] chore: remove unused lint rule --- packages/nx-plugin/eslint.config.cjs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/nx-plugin/eslint.config.cjs b/packages/nx-plugin/eslint.config.cjs index 71a018585..e732748e6 100644 --- a/packages/nx-plugin/eslint.config.cjs +++ b/packages/nx-plugin/eslint.config.cjs @@ -15,7 +15,6 @@ module.exports = tseslint.config( { files: ['**/*.ts'], rules: { - 'n/file-extension-in-import': 'off', // Nx plugins don't yet support ESM: https://github.com/nrwl/nx/issues/15682 'unicorn/prefer-module': 'off', // used instead of verbatimModuleSyntax tsconfig flag (requires ESM) From b942550a339609d002afd21eac10b615fe966a8b Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 10 Mar 2025 22:42:28 +0100 Subject: [PATCH 11/20] Update packages/nx-plugin/src/plugin/utils.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/nx-plugin/src/plugin/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nx-plugin/src/plugin/utils.ts b/packages/nx-plugin/src/plugin/utils.ts index 356f773cb..17376cec8 100644 --- a/packages/nx-plugin/src/plugin/utils.ts +++ b/packages/nx-plugin/src/plugin/utils.ts @@ -1,7 +1,7 @@ import type { CreateNodesContext, CreateNodesContextV2 } from '@nx/devkit'; import { readFile } from 'node:fs/promises'; import * as path from 'node:path'; -import { CP_TARGET_NAME } from './constants'; +import { CP_TARGET_NAME } from './constants.js'; import type { CreateNodesOptions, NormalizedCreateNodesContext, From 4c2c20d45f6d8b66e5a8574796d5b33d49f11288 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Mon, 10 Mar 2025 22:42:35 +0100 Subject: [PATCH 12/20] Update packages/nx-plugin/src/plugin/types.ts Co-authored-by: Michael Hladky <10064416+BioPhoton@users.noreply.github.com> --- packages/nx-plugin/src/plugin/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nx-plugin/src/plugin/types.ts b/packages/nx-plugin/src/plugin/types.ts index 0e96f0e21..623537fbe 100644 --- a/packages/nx-plugin/src/plugin/types.ts +++ b/packages/nx-plugin/src/plugin/types.ts @@ -4,7 +4,7 @@ import type { ProjectConfiguration, } from '@nx/devkit'; import type { WithRequired } from '@code-pushup/utils'; -import type { DynamicTargetOptions } from '../internal/types'; +import type { DynamicTargetOptions } from '../internal/types.js'; export type ProjectPrefixOptions = { projectPrefix?: string; From a59ee1410ce1b99332470e0cdc0a1886118de427 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Mon, 10 Mar 2025 22:47:18 +0100 Subject: [PATCH 13/20] chore: add js --- .../src/plugin/target/configuration-target.ts | 8 ++++---- .../nx-plugin/src/plugin/target/executor-target.ts | 4 ++-- packages/nx-plugin/src/plugin/target/targets.ts | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/nx-plugin/src/plugin/target/configuration-target.ts b/packages/nx-plugin/src/plugin/target/configuration-target.ts index f9b16c985..dce38ff54 100644 --- a/packages/nx-plugin/src/plugin/target/configuration-target.ts +++ b/packages/nx-plugin/src/plugin/target/configuration-target.ts @@ -1,8 +1,8 @@ import type { TargetConfiguration } from '@nx/devkit'; -import type { RunCommandsOptions } from 'nx/src/executors/run-commands/run-commands.impl'; -import { objectToCliArgs } from '../../executors/internal/cli'; -import { PACKAGE_NAME } from '../../internal/constants'; -import { CP_TARGET_NAME } from '../constants'; +import type { RunCommandsOptions } from 'nx/src/executors/run-commands/run-commands.impl.js'; +import { objectToCliArgs } from '../../executors/internal/cli.js'; +import { PACKAGE_NAME } from '../../internal/constants.js'; +import { CP_TARGET_NAME } from '../constants.js'; export function createConfigurationTarget(options?: { targetName?: string; diff --git a/packages/nx-plugin/src/plugin/target/executor-target.ts b/packages/nx-plugin/src/plugin/target/executor-target.ts index aeba82ad8..e8b52eb8f 100644 --- a/packages/nx-plugin/src/plugin/target/executor-target.ts +++ b/packages/nx-plugin/src/plugin/target/executor-target.ts @@ -1,6 +1,6 @@ import type { TargetConfiguration } from '@nx/devkit'; -import { PACKAGE_NAME } from '../../internal/constants'; -import type { ProjectPrefixOptions } from '../types'; +import { PACKAGE_NAME } from '../../internal/constants.js'; +import type { ProjectPrefixOptions } from '../types.js'; export function createExecutorTarget(options?: { bin?: string; diff --git a/packages/nx-plugin/src/plugin/target/targets.ts b/packages/nx-plugin/src/plugin/target/targets.ts index e6d62e03a..3e659688b 100644 --- a/packages/nx-plugin/src/plugin/target/targets.ts +++ b/packages/nx-plugin/src/plugin/target/targets.ts @@ -1,9 +1,9 @@ import { readdir } from 'node:fs/promises'; -import { CP_TARGET_NAME } from '../constants'; -import type { NormalizedCreateNodesContext } from '../types'; -import { createConfigurationTarget } from './configuration-target'; -import { CODE_PUSHUP_CONFIG_REGEX } from './constants'; -import { createExecutorTarget } from './executor-target'; +import { CP_TARGET_NAME } from '../constants.js'; +import type { NormalizedCreateNodesContext } from '../types.js'; +import { createConfigurationTarget } from './configuration-target.js'; +import { CODE_PUSHUP_CONFIG_REGEX } from './constants.js'; +import { createExecutorTarget } from './executor-target.js'; export async function createTargets( normalizedContext: NormalizedCreateNodesContext, From 7264e6bd14a169f6a0a9bb9c8528ab01dc4e7dc2 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 11 Mar 2025 00:25:54 +0100 Subject: [PATCH 14/20] test: add e2e for nx-plugin config derivation --- .../tests/plugin-derive-config.e2e.test.ts | 167 ++++++++++++++++++ testing/test-nx-utils/src/lib/utils/nx.ts | 20 ++- 2 files changed, 184 insertions(+), 3 deletions(-) create mode 100644 e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts diff --git a/e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts b/e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts new file mode 100644 index 000000000..f554fa1bc --- /dev/null +++ b/e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts @@ -0,0 +1,167 @@ +import { type Tree, writeJson } from '@nx/devkit'; +import path from 'node:path'; +import { readProjectConfiguration } from 'nx/src/generators/utils/project-configuration'; +import { afterEach, expect } from 'vitest'; +import { generateCodePushupConfig } from '@code-pushup/nx-plugin'; +import { + generateProject, + generateWorkspaceAndProject, + materializeTree, + nxShowProjectJson, + nxTargetProject, + registerPluginInWorkspace, +} from '@code-pushup/test-nx-utils'; +import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR } from '@code-pushup/test-utils'; + +describe('nx-plugin-derived-config', () => { + let root: string; + let tree: Tree; + const projectName = 'pkg'; + const testFileDir = path.join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'plugin-create-nodes', + ); + + beforeEach(async () => { + tree = await generateWorkspaceAndProject(); + registerPluginInWorkspace(tree, '@code-pushup/nx-plugin'); + await generateProject(tree, projectName); + root = readProjectConfiguration(tree, projectName).root; + generateCodePushupConfig(tree, root); + }); + + afterEach(async () => { + // await teardownTestFolder(testFileDir); + }); + + it('should derive config from project.json', async () => { + const cwd = path.join(testFileDir, 'project-config'); + const projectJsonPath = path.join('libs', projectName, 'project.json'); + const packageJsonPath = path.join('libs', projectName, 'package.json'); + tree.delete(projectJsonPath); + tree.delete(packageJsonPath); + writeJson(tree, projectJsonPath, { + root, + name: projectName, + targets: { + 'code-pushup': { + executor: `@code-pushup/nx-plugin:cli`, + options: { + 'persist.filename': 'my-report', + }, + }, + }, + }); + await materializeTree(tree, cwd); + + const { code, projectJson } = await nxShowProjectJson(cwd, projectName); + expect(code).toBe(0); + + expect(projectJson.targets).toStrictEqual( + expect.objectContaining({ + 'code-pushup': { + configurations: {}, + executor: `@code-pushup/nx-plugin:cli`, + options: { + 'persist.filename': 'my-report', + }, + parallelism: true, + }, + }), + ); + }); + + it('should derive config from package.json', async () => { + const cwd = path.join(testFileDir, 'package-config'); + const projectJsonPath = path.join('libs', projectName, 'project.json'); + const packageJsonPath = path.join('libs', projectName, 'package.json'); + tree.delete(projectJsonPath); + tree.delete(packageJsonPath); + writeJson(tree, packageJsonPath, { + name: `@code-pushup/${projectName}`, + nx: { + root, + name: projectName, + targets: { + 'code-pushup': { + executor: `@code-pushup/nx-plugin:cli`, + options: { + 'persist.filename': 'my-report', + }, + }, + }, + }, + }); + await materializeTree(tree, cwd); + + const { code, projectJson } = await nxShowProjectJson(cwd, projectName); + expect(code).toBe(0); + + expect(projectJson.targets).toStrictEqual( + expect.objectContaining({ + 'code-pushup': { + configurations: {}, + executor: `@code-pushup/nx-plugin:cli`, + options: { + 'persist.filename': 'my-report', + }, + parallelism: true, + }, + }), + ); + }); + + it('should derive config from mixed', async () => { + const cwd = path.join(testFileDir, 'mixed-config'); + const projectJsonPath = path.join('libs', projectName, 'project.json'); + const packageJsonPath = path.join('libs', projectName, 'package.json'); + + writeJson(tree, projectJsonPath, { + root, + name: projectName, + targets: { + 'code-pushup': { + executor: `@code-pushup/nx-plugin:cli`, + options: { + 'persist.filename': 'my-report', + }, + }, + }, + }); + writeJson(tree, packageJsonPath, { + name: `@code-pushup/${projectName}`, + nx: { + root, + name: projectName, + targets: { + 'code-pushup': { + executor: `@code-pushup/nx-plugin:cli`, + options: { + 'persist.outputPath': 'my-dir', + }, + }, + }, + }, + }); + await materializeTree(tree, cwd); + + const { code, projectJson } = await nxShowProjectJson(cwd, projectName); + expect(code).toBe(0); + + expect(projectJson.targets).toStrictEqual( + expect.objectContaining({ + 'code-pushup': { + configurations: {}, + executor: `@code-pushup/nx-plugin:cli`, + options: { + 'persist.filename': 'my-report', + 'persist.outputPath': 'my-dir', + }, + parallelism: true, + }, + }), + ); + }); +}); diff --git a/testing/test-nx-utils/src/lib/utils/nx.ts b/testing/test-nx-utils/src/lib/utils/nx.ts index e6e700068..252df5913 100644 --- a/testing/test-nx-utils/src/lib/utils/nx.ts +++ b/testing/test-nx-utils/src/lib/utils/nx.ts @@ -34,16 +34,16 @@ export function executorContext< }; } -export async function generateWorkspaceAndProject( +export async function generateProject( + tree: Tree, options: | string | (Omit, 'name'> & { name: string; }), ) { - const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); const { name, ...normalizedOptions } = - typeof options === 'string' ? { name: options } : options; + typeof options === 'string' ? { name: options } : (options ?? {}); await libraryGenerator(tree, { name, directory: path.join('libs', name), @@ -56,6 +56,20 @@ export async function generateWorkspaceAndProject( projectNameAndRootFormat: 'as-provided', ...normalizedOptions, }); +} +export async function generateWorkspaceAndProject( + options?: + | string + | (Omit, 'name'> & { + name: string; + }), +) { + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + const { name, ...opts } = + typeof options === 'string' ? { name: options } : (options ?? {}); + if (name) { + await generateProject(tree, { ...opts, name }); + } return tree; } From 67ff51dd5be0a25a74ea6fd59ed04c42cc3836e1 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Tue, 11 Mar 2025 18:44:49 +0100 Subject: [PATCH 15/20] docs: add v1v2 reference --- packages/nx-plugin/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/nx-plugin/README.md b/packages/nx-plugin/README.md index a1fc57551..e347e466e 100644 --- a/packages/nx-plugin/README.md +++ b/packages/nx-plugin/README.md @@ -66,3 +66,5 @@ Examples: - `nx run :code-pushup` - `nx run :code-pushup print-config --persist.filename=custom-report` + +> ℹ️ This plugin supports both V1 and V2 plugin strategies. When installed in an Nx workspace using Nx 18 or later, the executors will be automatically inferred. Learn more about inferred tasks [here](https://nx.dev/concepts/inferred-tasks). From 6a96b9c4d13254c0d1f11d480c2e611ea9fc3b4e Mon Sep 17 00:00:00 2001 From: Alejandro Date: Wed, 19 Mar 2025 20:03:40 +0100 Subject: [PATCH 16/20] chore: fix type name --- packages/nx-plugin/src/plugin/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/nx-plugin/src/plugin/utils.ts b/packages/nx-plugin/src/plugin/utils.ts index 60bfafc3c..3a473c587 100644 --- a/packages/nx-plugin/src/plugin/utils.ts +++ b/packages/nx-plugin/src/plugin/utils.ts @@ -5,7 +5,7 @@ import { CP_TARGET_NAME } from './constants.js'; import type { CreateNodesOptions, NormalizedCreateNodesContext, - NormalizedCreateNodesV2Context, + NormalizedCreateNodesContextV2, ProjectConfigurationWithName, } from './types.js'; @@ -49,7 +49,7 @@ export async function normalizedCreateNodesV2Context( context: CreateNodesContextV2, projectConfigurationFile: string, createOptions: CreateNodesOptions = {}, -): Promise { +): Promise { const projectRoot = path.dirname(projectConfigurationFile); try { From 38efd10106d85bc9507c36b3326ba72960913376 Mon Sep 17 00:00:00 2001 From: Alejandro <49059458+aramirezj@users.noreply.github.com> Date: Thu, 20 Mar 2025 17:44:52 +0100 Subject: [PATCH 17/20] Update testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vojtech Mašek --- testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts b/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts index 5c92eae0d..ea672ac11 100644 --- a/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts +++ b/testing/test-nx-utils/src/lib/utils/nx-plugin.unit.test.ts @@ -1,4 +1,4 @@ -import * as process from 'node:process'; +import process from 'node:process'; import { describe, expect } from 'vitest'; import { createNodesContextV1, From d96d239e121f7fe9ea572583ddc6b2d8648f175f Mon Sep 17 00:00:00 2001 From: Alejandro Date: Thu, 20 Mar 2025 17:48:46 +0100 Subject: [PATCH 18/20] chore: uncomment code --- e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts b/e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts index f554fa1bc..0535964c0 100644 --- a/e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts +++ b/e2e/nx-plugin-e2e/tests/plugin-derive-config.e2e.test.ts @@ -11,7 +11,11 @@ import { nxTargetProject, registerPluginInWorkspace, } from '@code-pushup/test-nx-utils'; -import { E2E_ENVIRONMENTS_DIR, TEST_OUTPUT_DIR } from '@code-pushup/test-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + teardownTestFolder, +} from '@code-pushup/test-utils'; describe('nx-plugin-derived-config', () => { let root: string; @@ -33,7 +37,7 @@ describe('nx-plugin-derived-config', () => { }); afterEach(async () => { - // await teardownTestFolder(testFileDir); + await teardownTestFolder(testFileDir); }); it('should derive config from project.json', async () => { From bc523bb3e3acefe1c9e5abff7f998925bc1e8e02 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 25 Mar 2025 00:33:32 +0100 Subject: [PATCH 19/20] fix: update packages --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 57caf40ff..bce11cfde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "@nx/react": "19.8.13", "@nx/vite": "19.8.13", "@nx/workspace": "19.8.13", - "@push-based/nx-verdaccio": "0.0.0-alpha.30", + "@push-based/nx-verdaccio": "^0.0.0-alpha.30", "@swc-node/register": "1.9.2", "@swc/cli": "0.3.14", "@swc/core": "1.5.7", diff --git a/package.json b/package.json index 735a5c8a1..ffd44ee72 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "@nx/react": "19.8.13", "@nx/vite": "19.8.13", "@nx/workspace": "19.8.13", - "@push-based/nx-verdaccio": "0.0.0-alpha.30", + "@push-based/nx-verdaccio": "^0.0.0-alpha.31", "@swc-node/register": "1.9.2", "@swc/cli": "0.3.14", "@swc/core": "1.5.7", From 8973d23548b7f51c6bf66fa7ec8665b4dc9d44cf Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 25 Mar 2025 00:37:19 +0100 Subject: [PATCH 20/20] fix: update packages 2 --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index bce11cfde..362d35d3e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,7 +51,7 @@ "@nx/react": "19.8.13", "@nx/vite": "19.8.13", "@nx/workspace": "19.8.13", - "@push-based/nx-verdaccio": "^0.0.0-alpha.30", + "@push-based/nx-verdaccio": "^0.0.0-alpha.31", "@swc-node/register": "1.9.2", "@swc/cli": "0.3.14", "@swc/core": "1.5.7", @@ -5761,9 +5761,9 @@ } }, "node_modules/@push-based/nx-verdaccio": { - "version": "0.0.0-alpha.30", - "resolved": "https://registry.npmjs.org/@push-based/nx-verdaccio/-/nx-verdaccio-0.0.0-alpha.30.tgz", - "integrity": "sha512-PB/WpfcqmyypyXkWKJBVinIgvOgYJV3ckXdQXKBHJuEKyUnfiUObmgnGqmlQqCgFlv5tvPkNGEFXDPTGL9JyGw==", + "version": "0.0.0-alpha.31", + "resolved": "https://registry.npmjs.org/@push-based/nx-verdaccio/-/nx-verdaccio-0.0.0-alpha.31.tgz", + "integrity": "sha512-+wWLvwTVwjsS/9CsuJY68us6+vcD06sKQm7/OtWNnxmGIZ3bpTqv5qor/BSixvMRde9AaGAwwROvTb4Z4Dsdyg==", "dev": true, "dependencies": { "@nx/devkit": "19.8.0",