From 38cd7f2e6dedac54a09f94f54186d10ee97c82ef Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 15 Dec 2025 20:39:55 +0100 Subject: [PATCH 1/8] refactor: adjust prefix logic --- packages/nx-plugin/src/executors/cli/executor.ts | 2 +- packages/nx-plugin/src/executors/internal/config.ts | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/nx-plugin/src/executors/cli/executor.ts b/packages/nx-plugin/src/executors/cli/executor.ts index b7d2772d7..04550f4dd 100644 --- a/packages/nx-plugin/src/executors/cli/executor.ts +++ b/packages/nx-plugin/src/executors/cli/executor.ts @@ -48,7 +48,7 @@ export default async function runCliExecutor( logger.warn(`DryRun execution of: ${commandString}`); } else { try { - logger.debug(`Run CLI with env vars: ${loggedEnvVars}`); + logger.debug(`Run CLI with env vars: ${JSON.stringify(loggedEnvVars)}`); await executeProcess({ command, args, diff --git a/packages/nx-plugin/src/executors/internal/config.ts b/packages/nx-plugin/src/executors/internal/config.ts index 7c285a971..7c0abbe5a 100644 --- a/packages/nx-plugin/src/executors/internal/config.ts +++ b/packages/nx-plugin/src/executors/internal/config.ts @@ -50,15 +50,11 @@ export function uploadConfig( const { projectPrefix, server, apiKey, organization, project, timeout } = options; - const applyPrefix = workspaceRoot === '.'; - const prefix = projectPrefix ? `${projectPrefix}-` : ''; + const applyPrefix = workspaceRoot !== '.'; + const prefix = projectPrefix && applyPrefix ? `${projectPrefix}-` : ''; const derivedProject = - projectName && !project - ? applyPrefix - ? `${prefix}${projectName}` - : projectName - : project; + projectName && !project ? `${prefix}${projectName}` : project; return { ...parseEnv(process.env), From 0ba515510bd0cc01a510c30a2650963f3dc45f1f Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 15 Dec 2025 20:57:02 +0100 Subject: [PATCH 2/8] refactor: test project prefix --- .../executors/internal/config.unit.test.ts | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/nx-plugin/src/executors/internal/config.unit.test.ts b/packages/nx-plugin/src/executors/internal/config.unit.test.ts index cf4077b55..c9d948bdd 100644 --- a/packages/nx-plugin/src/executors/internal/config.unit.test.ts +++ b/packages/nx-plugin/src/executors/internal/config.unit.test.ts @@ -333,6 +333,39 @@ describe('uploadConfig', () => { }, { workspaceRoot: 'workspaceRoot', projectName: 'my-app' }, ), - ).toEqual(expect.objectContaining({ project: 'my-app2' })); + ).toStrictEqual(expect.objectContaining({ project: 'my-app2' })); + }); + + it('should apply projectPrefix when workspaceRoot is not "."', () => { + expect( + uploadConfig( + { + ...baseUploadConfig, + projectPrefix: 'cli', + }, + { workspaceRoot: 'workspace-root', projectName: 'models' }, + ), + ).toStrictEqual(expect.objectContaining({ project: 'cli-models' })); + }); + + it('should NOT apply projectPrefix when workspaceRoot is "."', () => { + expect( + uploadConfig( + { + ...baseUploadConfig, + projectPrefix: 'cli', + }, + { workspaceRoot: '.', projectName: 'models' }, + ), + ).toStrictEqual(expect.objectContaining({ project: 'models' })); + }); + + it('should NOT apply projectPrefix when projectPrefix is not provided', () => { + expect( + uploadConfig(baseUploadConfig, { + workspaceRoot: 'workspace-root', + projectName: 'models', + }), + ).toStrictEqual(expect.objectContaining({ project: 'models' })); }); }); From 4dd4009e16eddbc6fe014b5b83b80e8b312e2882 Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 15 Dec 2025 21:09:44 +0100 Subject: [PATCH 3/8] refactor: exclude param from CLI execution --- packages/nx-plugin/src/executors/cli/executor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/nx-plugin/src/executors/cli/executor.ts b/packages/nx-plugin/src/executors/cli/executor.ts index 04550f4dd..b62fecf43 100644 --- a/packages/nx-plugin/src/executors/cli/executor.ts +++ b/packages/nx-plugin/src/executors/cli/executor.ts @@ -24,6 +24,7 @@ export default async function runCliExecutor( dryRun, env: executorEnv, bin, + projectPrefix, // @TODO do not forward to CLI. Handle in plugin logic only ...restArgs } = parseCliExecutorOptions(terminalAndExecutorOptions, normalizedContext); // this sets `CP_VERBOSE=true` on process.env From 7653f3a09641e59db29ef5828a1010c3b9764722 Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 15 Dec 2025 22:10:22 +0100 Subject: [PATCH 4/8] refactor: add const --- packages/nx-plugin/src/index.ts | 3 ++- packages/nx-plugin/src/plugin/constants.ts | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/nx-plugin/src/index.ts b/packages/nx-plugin/src/index.ts index 31beb6920..653ebad6c 100644 --- a/packages/nx-plugin/src/index.ts +++ b/packages/nx-plugin/src/index.ts @@ -1,8 +1,9 @@ +import { PLUGIN_NAME } from './plugin/constants.js'; import { createNodes, createNodesV2 } from './plugin/index.js'; // default export for nx.json#plugins const plugin = { - name: '@code-pushup/nx-plugin', + name: PLUGIN_NAME, createNodesV2, // Keep for backwards compatibility with Nx < 21 createNodes, diff --git a/packages/nx-plugin/src/plugin/constants.ts b/packages/nx-plugin/src/plugin/constants.ts index bf2e81d9f..5be953791 100644 --- a/packages/nx-plugin/src/plugin/constants.ts +++ b/packages/nx-plugin/src/plugin/constants.ts @@ -1 +1,2 @@ export const CP_TARGET_NAME = 'code-pushup'; +export const PLUGIN_NAME = '@code-pushup/nx-plugin'; From 8749e284f31640fa3e2a8167632b9f621d078ad2 Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 15 Dec 2025 22:15:01 +0100 Subject: [PATCH 5/8] refactor: extend nx.ts helper --- testing/test-nx-utils/src/lib/utils/nx.ts | 13 +++++++ .../src/lib/utils/nx.unit.test.ts | 34 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/testing/test-nx-utils/src/lib/utils/nx.ts b/testing/test-nx-utils/src/lib/utils/nx.ts index 050bdc541..97d565ccf 100644 --- a/testing/test-nx-utils/src/lib/utils/nx.ts +++ b/testing/test-nx-utils/src/lib/utils/nx.ts @@ -65,6 +65,7 @@ export async function generateWorkspaceAndProject( export function registerPluginInWorkspace( tree: Tree, configuration: PluginConfiguration, + pluginConfig?: Record, ) { const normalizedPluginConfiguration = typeof configuration === 'string' @@ -72,9 +73,21 @@ export function registerPluginInWorkspace( plugin: configuration, } : configuration; + + const pluginName = + typeof configuration === 'string' ? configuration : configuration.plugin; + updateJson(tree, 'nx.json', (json: NxJsonConfiguration) => ({ ...json, plugins: [...(json.plugins ?? []), normalizedPluginConfiguration], + ...(pluginConfig + ? { + pluginsConfig: { + ...json.pluginsConfig, + [pluginName]: pluginConfig, + }, + } + : {}), })); } diff --git a/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts b/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts index fd6593875..5da045870 100644 --- a/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts +++ b/testing/test-nx-utils/src/lib/utils/nx.unit.test.ts @@ -91,4 +91,38 @@ describe('registerPluginInWorkspace', () => { }), ); }); + + it('should register pluginsConfig when provided', () => { + const tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); + + registerPluginInWorkspace( + tree, + { + plugin: '@code-pushup/nx-plugin', + options: { targetName: 'code-pushup' }, + }, + { + projectPrefix: 'cli', + bin: 'packages/cli/src/index.ts', + }, + ); + + const nxJson = JSON.parse(tree.read('nx.json')?.toString() ?? '{}'); + expect(nxJson).toStrictEqual( + expect.objectContaining({ + plugins: [ + { + plugin: '@code-pushup/nx-plugin', + options: { targetName: 'code-pushup' }, + }, + ], + pluginsConfig: { + '@code-pushup/nx-plugin': { + projectPrefix: 'cli', + bin: 'packages/cli/src/index.ts', + }, + }, + }), + ); + }); }); From bbb45a5a85bbdc714aaf80f6a49b884eeed3beac Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 15 Dec 2025 22:19:33 +0100 Subject: [PATCH 6/8] refactor: use pluginConfig in plugin --- packages/nx-plugin/src/plugin/plugin.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/nx-plugin/src/plugin/plugin.ts b/packages/nx-plugin/src/plugin/plugin.ts index 1b4e3e2f1..9458374f9 100644 --- a/packages/nx-plugin/src/plugin/plugin.ts +++ b/packages/nx-plugin/src/plugin/plugin.ts @@ -7,6 +7,7 @@ import type { CreateNodesV2, } from '@nx/devkit'; import { PROJECT_JSON_FILE_NAME } from '../internal/constants.js'; +import { PLUGIN_NAME } from './constants.js'; import { createTargets } from './target/targets.js'; import type { CreateNodesOptions } from './types.js'; import { @@ -15,6 +16,9 @@ import { } from './utils.js'; // name has to be "createNodes" to get picked up by Nx => { const parsedCreateNodesOptions = createNodesOptions as CreateNodesOptions; + const pluginsConfig = + context.nxJsonConfiguration.pluginsConfig?.[PLUGIN_NAME] ?? {}; + const mergedOptions = { ...pluginsConfig, ...parsedCreateNodesOptions }; + const normalizedContext = await normalizedCreateNodesContext( context, projectConfigurationFile, - parsedCreateNodesOptions, + mergedOptions, ); return { @@ -47,13 +55,16 @@ export const createNodesV2: CreateNodesV2 = [ context: CreateNodesContextV2, ): Promise => { const parsedCreateNodesOptions = createNodesOptions as CreateNodesOptions; + const { pluginsConfig = {} } = context.nxJsonConfiguration; + const pluginsConfigObj = pluginsConfig[PLUGIN_NAME] ?? {}; + const mergedOptions = { ...pluginsConfigObj, ...parsedCreateNodesOptions }; return await Promise.all( projectConfigurationFiles.map(async projectConfigurationFile => { const normalizedContext = await normalizedCreateNodesV2Context( context, projectConfigurationFile, - parsedCreateNodesOptions, + mergedOptions, ); const result: CreateNodesResult = { From e72156b9fd665dbe3bd4b17b62bdcf039090a49f Mon Sep 17 00:00:00 2001 From: John Doe Date: Mon, 15 Dec 2025 22:23:22 +0100 Subject: [PATCH 7/8] refactor: test pluginConfig in nx-plugin --- .../tests/plugin-plugins-config.e2e.test.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts diff --git a/e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts b/e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts new file mode 100644 index 000000000..6aeb7bb29 --- /dev/null +++ b/e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts @@ -0,0 +1,99 @@ +import type { Tree } 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 { + generateWorkspaceAndProject, + materializeTree, + nxShowProjectJson, + nxTargetProject, + registerPluginInWorkspace, +} from '@code-pushup/test-nx-utils'; +import { + E2E_ENVIRONMENTS_DIR, + TEST_OUTPUT_DIR, + teardownTestFolder, +} from '@code-pushup/test-utils'; +import { INLINE_PLUGIN } from '../mocks/inline-plugin.js'; + +describe('nx-plugin pluginsConfig', () => { + let tree: Tree; + const project = 'my-lib'; + const testFileDir = path.join( + E2E_ENVIRONMENTS_DIR, + nxTargetProject(), + TEST_OUTPUT_DIR, + 'plugin-plugins-config', + ); + + beforeEach(async () => { + tree = await generateWorkspaceAndProject(project); + }); + + afterEach(async () => { + await teardownTestFolder(testFileDir); + }); + + it('should apply pluginsConfig options to executor target', async () => { + const cwd = path.join(testFileDir, 'plugins-config-applied'); + const binPath = 'packages/cli/src/index.ts'; + const configPath = '{projectRoot}/code-pushup.config.ts'; + const projectPrefix = 'cli'; + + // Register plugin with options in the plugins array and pluginsConfig + registerPluginInWorkspace( + tree, + { + plugin: '@code-pushup/nx-plugin', + options: { + config: configPath, + persist: { + outputDir: '.code-pushup/{projectName}', + }, + }, + }, + { + projectPrefix, + bin: binPath, + env: { + NODE_OPTIONS: '--import tsx', + TSX_TSCONFIG_PATH: 'tsconfig.base.json', + }, + }, + ); + + const { root } = readProjectConfiguration(tree, project); + generateCodePushupConfig(tree, root, { + plugins: [ + { + fileImports: '', + codeStrings: INLINE_PLUGIN, + }, + ], + }); + + await materializeTree(tree, cwd); + + const { code, projectJson } = await nxShowProjectJson(cwd, project); + expect(code).toBe(0); + + expect(projectJson).toStrictEqual( + expect.objectContaining({ + targets: expect.objectContaining({ + 'code-pushup': expect.objectContaining({ + executor: '@code-pushup/nx-plugin:cli', + options: expect.objectContaining({ + projectPrefix, + bin: binPath, + env: { + NODE_OPTIONS: '--import tsx', + TSX_TSCONFIG_PATH: 'tsconfig.base.json', + }, + }), + }), + }), + }), + ); + }); +}); From 73e44c5da7d6bd7eef302cc909e290c859dbe1f5 Mon Sep 17 00:00:00 2001 From: John Doe Date: Tue, 16 Dec 2025 19:53:19 +0100 Subject: [PATCH 8/8] refactor: adjust comment --- packages/nx-plugin/src/executors/cli/executor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/nx-plugin/src/executors/cli/executor.ts b/packages/nx-plugin/src/executors/cli/executor.ts index b62fecf43..8db40c418 100644 --- a/packages/nx-plugin/src/executors/cli/executor.ts +++ b/packages/nx-plugin/src/executors/cli/executor.ts @@ -24,7 +24,7 @@ export default async function runCliExecutor( dryRun, env: executorEnv, bin, - projectPrefix, // @TODO do not forward to CLI. Handle in plugin logic only + projectPrefix, // Do not forward to CLI, it is handled plugin logic only ...restArgs } = parseCliExecutorOptions(terminalAndExecutorOptions, normalizedContext); // this sets `CP_VERBOSE=true` on process.env