Skip to content
Open
Show file tree
Hide file tree
Changes from 10 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
40 changes: 35 additions & 5 deletions packages/nx-plugin/src/plugin/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type {
CreateNodes,
CreateNodesContext,
CreateNodesResult,
import {
type CreateNodes,
type CreateNodesContext,
type CreateNodesResult,
type 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';

// name has to be "createNodes" to get picked up by Nx
/** 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 (
Expand All @@ -32,3 +34,31 @@ 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) =>
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,
),
];
232 changes: 127 additions & 105 deletions packages/nx-plugin/src/plugin/plugin.unit.test.ts
Original file line number Diff line number Diff line change
@@ -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',
},
},
},
},
},
});
});
});
});
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
4 changes: 2 additions & 2 deletions packages/nx-plugin/src/plugin/target/executor-target.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
10 changes: 5 additions & 5 deletions packages/nx-plugin/src/plugin/target/targets.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
19 changes: 16 additions & 3 deletions packages/nx-plugin/src/plugin/types.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down
Loading
Loading