Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nx-plugin): add project prefix to plugin #792

Merged
merged 16 commits into from
Aug 21, 2024
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
27 changes: 17 additions & 10 deletions e2e/create-cli-e2e/mocks/create-npm-workshpace.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { mkdir, writeFile } from 'node:fs/promises';
import { join } from 'node:path';

export async function createNpmWorkspace(cwd: string) {
await mkdir(cwd, { recursive: true });
await writeFile(join(cwd, 'package.json'), JSON.stringify({
name: 'create-npm-workspace',
version: '0.0.1',
scripts: {
test: 'echo "Error: no test specified" && exit 1',
},
keywords: [],
}, null, 2));
await writeFile(
join(cwd, 'package.json'),
JSON.stringify(
{
name: 'create-npm-workspace',
version: '0.0.1',
scripts: {
test: 'echo "Error: no test specified" && exit 1',
},
keywords: [],
},
null,
2,
),
);
}
2 changes: 1 addition & 1 deletion e2e/nx-plugin-e2e/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
}
}
},
"implicitDependencies": ["nx-plugin"],
matejchalk marked this conversation as resolved.
Show resolved Hide resolved
"implicitDependencies": ["nx-plugin", "test-utils"],
"tags": ["scope:tooling", "type:e2e"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,30 +189,24 @@ describe('nx-plugin', () => {
)}";`,
plugins: [
{
fileImports: `import jsPackagesPlugin from "${join(
fileImports: `import {customPlugin} from "${join(
relativePathToCwd(cwd),
pathRelativeToPackage,
'dist/packages/plugin-js-packages',
'dist/testing/test-utils',
)}";`,
// @TODO improve formatObjectToJsString to get rid of the "`" hack
codeStrings: 'await jsPackagesPlugin({packageManager: `npm`})',
codeStrings: 'customPlugin()',
},
],
});

await materializeTree(tree, cwd);

const { stdout, stderr } = await executeProcess({
const { stdout } = await executeProcess({
command: 'npx',
args: ['nx', 'run', `${project}:code-pushup -- --dryRun`],
args: ['nx', 'run', `${project}:code-pushup`, '--dryRun'],
cwd,
});

const cleanStderr = removeColorCodes(stderr);
// @TODO create test environment for working plugin. This here misses package-lock.json to execute correctly
expect(cleanStderr).toContain(
'DryRun execution of: npx @code-pushup/cli autorun',
);

const cleanStdout = removeColorCodes(stdout);
expect(cleanStdout).toContain(
'NX Successfully ran target code-pushup for project my-lib',
Expand Down Expand Up @@ -242,7 +236,33 @@ describe('nx-plugin', () => {
});
});

it('should NOT add targets dynamically if plugin is NOT registered', async () => {
it('should consider plugin option projectPrefix in executor target', async () => {
const cwd = join(baseDir, 'configuration-option-bin');
registerPluginInWorkspace(tree, {
plugin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
options: {
projectPrefix: 'cli',
},
});
const { root } = readProjectConfiguration(tree, project);
generateCodePushupConfig(tree, root);
await materializeTree(tree, cwd);

const { code, projectJson } = await nxShowProjectJson(cwd, project);

expect(code).toBe(0);

expect(projectJson.targets).toStrictEqual({
['code-pushup']: expect.objectContaining({
executor: `@code-pushup/nx-plugin:autorun`,
options: {
projectPrefix: 'cli',
},
}),
});
});

it('should NOT add targets dynamically if plugin is not registered', async () => {
const cwd = join(baseDir, 'plugin-not-registered');
await materializeTree(tree, cwd);

Expand Down
85 changes: 85 additions & 0 deletions e2e/nx-plugin-e2e/tests/executor-autorun.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Tree, updateProjectConfiguration } from '@nx/devkit';
import { rm } from 'node:fs/promises';
import { join, relative } 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,
} from '@code-pushup/test-nx-utils';
import { removeColorCodes } from '@code-pushup/test-utils';
import { executeProcess } from '@code-pushup/utils';

function relativePathToCwd(testDir: string): string {
return relative(join(process.cwd(), testDir), process.cwd());
}

async function addTargetToWorkspace(
tree: Tree,
options: { cwd: string; project: string },
) {
const { cwd, project } = options;
const pathRelativeToPackage = relative(join(cwd, 'libs', project), cwd);
const projectCfg = readProjectConfiguration(tree, project);
updateProjectConfiguration(tree, project, {
...projectCfg,
targets: {
...projectCfg.targets,
['code-pushup']: {
executor: `${join(
relativePathToCwd(cwd),
'dist/packages/nx-plugin',
)}:autorun`,
},
},
});
const { root } = projectCfg;
generateCodePushupConfig(tree, root, {
fileImports: `import type {CoreConfig} from "${join(
relativePathToCwd(cwd),
pathRelativeToPackage,
'dist/packages/models',
)}";`,
plugins: [
{
fileImports: `import {customPlugin} from "${join(
relativePathToCwd(cwd),
pathRelativeToPackage,
'dist/testing/test-utils',
)}";`,
codeStrings: 'customPlugin()',
},
],
});
await materializeTree(tree, cwd);
}

describe('executor autorun', () => {
let tree: Tree;
const project = 'my-lib';
const baseDir = 'tmp/nx-plugin-e2e/executor';

beforeEach(async () => {
tree = await generateWorkspaceAndProject(project);
});

afterEach(async () => {
await rm(baseDir, { recursive: true, force: true });
});

it('should execute autorun executor', async () => {
const cwd = join(baseDir, 'execute-dynamic-executor');
await addTargetToWorkspace(tree, { cwd, project });

const { stdout, code } = await executeProcess({
command: 'npx',
args: ['nx', 'run', `${project}:code-pushup`, '--dryRun'],
cwd,
});

expect(code).toBe(0);
const cleanStdout = removeColorCodes(stdout);
expect(cleanStdout).toContain('nx run my-lib:code-pushup --dryRun');
});
});
1 change: 0 additions & 1 deletion packages/nx-plugin/src/internal/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export type DynamicTargetOptions = {
// @TODO add prefix https://github.com/code-pushup/cli/issues/619
targetName?: string;
bin?: string;
};
68 changes: 66 additions & 2 deletions packages/nx-plugin/src/plugin/plugin.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe('@code-pushup/nx-plugin/plugin', () => {
vol.reset();
});

it('should normalize context and use it to create target on ROOT project', async () => {
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({
Expand All @@ -46,7 +46,7 @@ describe('@code-pushup/nx-plugin/plugin', () => {
});
});

it('should normalize context and use it to create target on PACKAGE project', async () => {
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({
Expand All @@ -71,4 +71,68 @@ describe('@code-pushup/nx-plugin/plugin', () => {
},
});
});

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`]: '{}',
};

await expect(
invokeCreateNodesOnVirtualFiles(
createNodes,
context,
{
projectPrefix: 'cli',
},
{ matchingFilesData },
),
).resolves.toStrictEqual({
[projectRoot]: {
targets: {
[CP_TARGET_NAME]: {
executor: `${PACKAGE_NAME}:autorun`,
options: {
projectPrefix: 'cli',
},
},
},
},
});
});

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}:autorun`,
options: {
projectPrefix: 'cli',
},
},
},
},
});
});
});
2 changes: 2 additions & 0 deletions packages/nx-plugin/src/plugin/target/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const CODE_PUSHUP_CONFIG_REGEX =

Check failure on line 1 in packages/nx-plugin/src/plugin/target/constants.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<↗> Code coverage | Branch coverage

1st branch is not taken in any test case.
/^code-pushup\.config\.(\w*\.)*(ts|js|mjs)$/;
14 changes: 11 additions & 3 deletions packages/nx-plugin/src/plugin/target/executor-target.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { TargetConfiguration } from '@nx/devkit';
import { RunCommandsOptions } from 'nx/src/executors/run-commands/run-commands.impl';
import { PACKAGE_NAME } from '../../internal/constants';
import { ProjectPrefixOptions } from '../types';

export function createExecutorTarget(options?: {
bin?: string;
}): TargetConfiguration<RunCommandsOptions> {
const { bin = PACKAGE_NAME } = options ?? {};
projectPrefix?: string;
}): TargetConfiguration<ProjectPrefixOptions> {
const { bin = PACKAGE_NAME, projectPrefix } = options ?? {};
return {
executor: `${bin}:autorun`,
...(projectPrefix
? {
options: {
projectPrefix,
},
}
: {}),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,13 @@ describe('createExecutorTarget', () => {
executor: 'xyz:autorun',
});
});

it('should use projectPrefix if provided', () => {
expect(createExecutorTarget({ projectPrefix: 'cli' })).toStrictEqual({
executor: '@code-pushup/nx-plugin:autorun',
options: {
projectPrefix: 'cli',
},
});
});
});
16 changes: 9 additions & 7 deletions packages/nx-plugin/src/plugin/target/targets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,21 @@ 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';

export async function createTargets(
normalizedContext: NormalizedCreateNodesContext,
) {
const { targetName = CP_TARGET_NAME, bin } = normalizedContext.createOptions;
const {
targetName = CP_TARGET_NAME,
bin,
projectPrefix,
} = normalizedContext.createOptions;
const rootFiles = await readdir(normalizedContext.projectRoot);
return rootFiles.some(filename =>
filename.match(/^code-pushup\.config.(\w*\.)*(ts|js|mjs)$/),
)
? // @TODO return code-pushup cli target https://github.com/code-pushup/cli/issues/619
{
[targetName]: createExecutorTarget({ bin }),
return rootFiles.some(filename => filename.match(CODE_PUSHUP_CONFIG_REGEX))
? {
[targetName]: createExecutorTarget({ bin, projectPrefix }),
}
: // if NO code-pushup.config.*.(ts|js|mjs) is present return configuration target
{
Expand Down
25 changes: 25 additions & 0 deletions packages/nx-plugin/src/plugin/target/targets.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,4 +161,29 @@ describe('createTargets', () => {
},
});
});

it('should include projectPrefix options in executor targets if given', async () => {
const projectName = 'plugin-my-plugin';
vol.fromJSON(
{
[`code-pushup.config.ts`]: `{}`,
},
MEMFS_VOLUME,
);
await expect(
createTargets({
projectRoot: '.',
projectJson: {
name: projectName,
},
createOptions: {
projectPrefix: 'cli',
},
} as NormalizedCreateNodesContext),
).resolves.toStrictEqual({
[DEFAULT_TARGET_NAME]: expect.objectContaining({
options: { projectPrefix: 'cli' },
}),
});
});
});
Loading