Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -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',
},
}),
}),
}),
}),
);
});
});
3 changes: 2 additions & 1 deletion packages/nx-plugin/src/executors/cli/executor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export default async function runCliExecutor(
dryRun,
env: executorEnv,
bin,
projectPrefix, // Do not forward to CLI, it is handled plugin logic only
...restArgs
} = parseCliExecutorOptions(terminalAndExecutorOptions, normalizedContext);
// this sets `CP_VERBOSE=true` on process.env
Expand All @@ -48,7 +49,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,
Expand Down
10 changes: 3 additions & 7 deletions packages/nx-plugin/src/executors/internal/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
35 changes: 34 additions & 1 deletion packages/nx-plugin/src/executors/internal/config.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' }));
});
});
3 changes: 2 additions & 1 deletion packages/nx-plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
1 change: 1 addition & 0 deletions packages/nx-plugin/src/plugin/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const CP_TARGET_NAME = 'code-pushup';
export const PLUGIN_NAME = '@code-pushup/nx-plugin';
15 changes: 13 additions & 2 deletions packages/nx-plugin/src/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -15,6 +16,9 @@ import {
} from './utils.js';

// name has to be "createNodes" to get picked up by Nx <v20
/**
* @deprecated
*/
export const createNodes: CreateNodes = [
`**/${PROJECT_JSON_FILE_NAME}`,
async (
Expand All @@ -23,10 +27,14 @@ export const createNodes: CreateNodes = [
context: CreateNodesContext,
): Promise<CreateNodesResult> => {
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 {
Expand All @@ -47,13 +55,16 @@ export const createNodesV2: CreateNodesV2<CreateNodesOptions> = [
context: CreateNodesContextV2,
): Promise<CreateNodesResultV2> => {
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 = {
Expand Down
13 changes: 13 additions & 0 deletions testing/test-nx-utils/src/lib/utils/nx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,29 @@ export async function generateWorkspaceAndProject(
export function registerPluginInWorkspace(
tree: Tree,
configuration: PluginConfiguration,
pluginConfig?: Record<string, unknown>,
) {
const normalizedPluginConfiguration =
typeof configuration === 'string'
? {
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,
},
}
: {}),
}));
}

Expand Down
34 changes: 34 additions & 0 deletions testing/test-nx-utils/src/lib/utils/nx.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
}),
);
});
});