Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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', () => {

Check failure on line 20 in e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
let tree: Tree;
const project = 'my-lib';
const testFileDir = path.join(
E2E_ENVIRONMENTS_DIR,
nxTargetProject(),
TEST_OUTPUT_DIR,
'plugin-plugins-config',
);

beforeEach(async () => {

Check failure on line 30 in e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2304: Cannot find name 'beforeEach'.
tree = await generateWorkspaceAndProject(project);
});

afterEach(async () => {
await teardownTestFolder(testFileDir);
});

it('should apply pluginsConfig options to executor target', async () => {

Check failure on line 38 in e2e/nx-plugin-e2e/tests/plugin-plugins-config.e2e.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
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, // @TODO do not forward to CLI. Handle in 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 @@ -4,7 +4,7 @@
import { globalConfig, persistConfig, uploadConfig } from './config.js';

describe('globalConfig', () => {
it('should provide default global verbose options', () => {

Check failure on line 7 in packages/nx-plugin/src/executors/internal/config.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
expect(
globalConfig(
{},
Expand All @@ -19,7 +19,7 @@
).toEqual(expect.objectContaining({ verbose: false }));
});

it('should parse global verbose options', () => {

Check failure on line 22 in packages/nx-plugin/src/executors/internal/config.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
expect(
globalConfig(
{ verbose: true },
Expand All @@ -34,7 +34,7 @@
).toEqual(expect.objectContaining({ verbose: true }));
});

it('should provide default global config options', () => {

Check failure on line 37 in packages/nx-plugin/src/executors/internal/config.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
const { config } = globalConfig(
{},
{
Expand All @@ -52,7 +52,7 @@
);
});

it('should parse global config options', () => {

Check failure on line 55 in packages/nx-plugin/src/executors/internal/config.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
expect(
globalConfig(
{ config: 'my.config.ts' },
Expand All @@ -67,7 +67,7 @@
).toEqual(expect.objectContaining({ config: 'my.config.ts' }));
});

it('should include the command options', () => {

Check failure on line 70 in packages/nx-plugin/src/executors/internal/config.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
const { command } = globalConfig(
{ command: 'collect' },
{
Expand All @@ -81,7 +81,7 @@
expect(command).toBe('collect');
});

it('should work with empty projectConfig', () => {

Check failure on line 84 in packages/nx-plugin/src/executors/internal/config.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
expect(
globalConfig(
{},
Expand All @@ -92,7 +92,7 @@
).toEqual(expect.objectContaining({ config: 'code-pushup.config.ts' }));
});

it('should exclude other options', () => {

Check failure on line 95 in packages/nx-plugin/src/executors/internal/config.unit.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2582: Cannot find name 'it'. Do you need to install type definitions for a test runner? Try `npm i --save-dev @types/jest` or `npm i --save-dev @types/mocha`.
expect(
globalConfig(
{ test: 42, verbose: true },
Expand Down Expand Up @@ -333,6 +333,39 @@
},
{ 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',
},
},
}),
);
});
});