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

fix(nx-plugin): adjust upload config handling #937

Merged
merged 33 commits into from
Mar 11, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
f653376
fix(nx-plugin): adjust upload config handling
BioPhoton Feb 14, 2025
ccce7b1
fix(nx-plugin): remove file extensions in imports
BioPhoton Feb 22, 2025
d10f1be
fix(nx-plugin): add createNodesV2
BioPhoton Feb 22, 2025
ff94e39
fix(nx-plugin): add createNodesV2
BioPhoton Feb 22, 2025
45c8667
ci: setup plugin
BioPhoton Feb 22, 2025
a3316bc
ci: setup cp for models
BioPhoton Feb 22, 2025
48143dc
wip
BioPhoton Feb 22, 2025
42f0bd4
wip
BioPhoton Mar 1, 2025
233cfd7
wip 1
BioPhoton Mar 3, 2025
c2ec88c
fix(nx-plugin): fix lint
BioPhoton Mar 5, 2025
15983d8
fix(nx-plugin): fix lint
BioPhoton Mar 5, 2025
3596149
fix: wip
BioPhoton Mar 5, 2025
d375686
docs: add js docs
BioPhoton Mar 5, 2025
bf87631
Merge branch 'main' into nx-plugin/fix-upload-config
BioPhoton Mar 5, 2025
a754393
refactor(nx-plugin): explicit import extensions
matejchalk Mar 10, 2025
2bcfcc7
Update packages/nx-plugin/src/plugin/target/targets.ts
BioPhoton Mar 10, 2025
385d77c
fix: update nx-verdaccio pkg (#954)
BioPhoton Mar 5, 2025
fa5635e
fix(utils): ignore non-json lines in fromJsonLines utility
matejchalk Mar 5, 2025
9e68eb1
fix(plugin-js-packages): ignore non-empty stderr
matejchalk Mar 5, 2025
88fd037
release: 0.64.2 [skip ci]
matejchalk Mar 5, 2025
00aae50
feat(utils): add score filter to md report generation (#956)
BioPhoton Mar 5, 2025
76c2b47
release: 0.65.0 [skip ci]
matejchalk Mar 5, 2025
2437497
fix: adjust logic
BioPhoton Mar 10, 2025
8de3562
Update packages/nx-plugin/eslint.config.js
BioPhoton Mar 10, 2025
f5cf751
fix: wip
BioPhoton Mar 10, 2025
fb846ff
fix: wip
BioPhoton Mar 10, 2025
59145b7
Merge branch 'main' into nx-plugin/fix-upload-config
BioPhoton Mar 10, 2025
90daca5
fix: e2e tests
BioPhoton Mar 10, 2025
acbd3ab
fix: e2e tests
BioPhoton Mar 10, 2025
3e32e37
fix: wip
BioPhoton Mar 10, 2025
7cf9479
fix: wip
BioPhoton Mar 10, 2025
74c39cb
fix: wip e2e
BioPhoton Mar 10, 2025
f17ce38
fix: wip e2e 2
BioPhoton Mar 10, 2025
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
44 changes: 43 additions & 1 deletion e2e/nx-plugin-e2e/tests/executor-cli.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type Tree, updateProjectConfiguration } from '@nx/devkit';
import path from 'node:path';
import { readProjectConfiguration } from 'nx/src/generators/utils/project-configuration';
import { afterEach, expect } from 'vitest';
import { afterAll, afterEach, beforeEach, expect, vi } from 'vitest';
import {
type AutorunCommandExecutorOptions,
generateCodePushupConfig,
Expand Down Expand Up @@ -58,15 +58,31 @@ describe('executor command', () => {
TEST_OUTPUT_DIR,
'executor-cli',
);
const processEnvCP = Object.fromEntries(
Object.entries(process.env).filter(([k]) => k.startsWith('CP_')),
);

/* eslint-disable functional/immutable-data, @typescript-eslint/no-dynamic-delete */
beforeAll(() => {
Object.entries(process.env)
.filter(([k]) => k.startsWith('CP_'))
.forEach(([k]) => delete process.env[k]);
});

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

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

afterAll(() => {
Object.entries(processEnvCP).forEach(([k, v]) => (process.env[k] = v));
});
/* eslint-enable functional/immutable-data, @typescript-eslint/no-dynamic-delete */

it('should execute no specific command by default', async () => {
const cwd = path.join(testFileDir, 'execute-default-command');
await addTargetToWorkspace(tree, { cwd, project });
Expand Down Expand Up @@ -100,6 +116,32 @@ describe('executor command', () => {
).rejects.toThrow('');
});

it('should execute print-config executor with api key', async () => {
const cwd = path.join(testFileDir, 'execute-print-config-command');
await addTargetToWorkspace(tree, { cwd, project });

const { stdout, code } = await executeProcess({
command: 'npx',
args: [
'nx',
'run',
`${project}:code-pushup`,
'print-config',
'--upload.apiKey=a123a',
],
cwd,
});

expect(code).toBe(0);
const cleanStdout = removeColorCodes(stdout);
expect(cleanStdout).toContain('nx run my-lib:code-pushup print-config');
expect(cleanStdout).toContain('a123a');

await expect(() =>
readJsonFile(path.join(cwd, '.code-pushup', project, 'report.json')),
).rejects.toThrow('');
});

it('should execute collect executor and merge target and command-line options', async () => {
const cwd = path.join(testFileDir, 'execute-collect-with-merged-options');
await addTargetToWorkspace(
Expand Down
1 change: 1 addition & 0 deletions packages/models/tsconfig.lib.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"exclude": [
"vite.config.unit.ts",
"vite.config.integration.ts",
"code-pushup.config.ts",
"zod2md.config.ts",
"src/**/*.test.ts",
"src/**/*.mock.ts",
Expand Down
1 change: 1 addition & 0 deletions packages/models/tsconfig.test.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"outDir": "../../dist/out-tsc",
"types": ["vitest/globals", "vitest/importMeta", "vite/client", "node"]
},
"exclude": ["**/code-pushup.config.ts"],
"include": [
"vite.config.unit.ts",
"vite.config.integration.ts",
Expand Down
37 changes: 35 additions & 2 deletions packages/nx-plugin/src/executors/cli/executor.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { logger } from '@nx/devkit';
import { execSync } from 'node:child_process';
import { afterEach, expect, vi } from 'vitest';
import { afterAll, afterEach, beforeEach, expect, vi } from 'vitest';
import { executorContext } from '@code-pushup/test-nx-utils';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import runAutorunExecutor from './executor.js';
Expand All @@ -19,14 +19,33 @@ vi.mock('node:child_process', async () => {
});

describe('runAutorunExecutor', () => {
const processEnvCP = Object.fromEntries(
Object.entries(process.env).filter(([k]) => k.startsWith('CP_')),
);
const loggerInfoSpy = vi.spyOn(logger, 'info');
const loggerWarnSpy = vi.spyOn(logger, 'warn');

/* eslint-disable functional/immutable-data, @typescript-eslint/no-dynamic-delete */
beforeAll(() => {
Object.entries(process.env)
.filter(([k]) => k.startsWith('CP_'))
.forEach(([k]) => delete process.env[k]);
});

beforeEach(() => {
vi.unstubAllEnvs();
});

afterEach(() => {
loggerWarnSpy.mockReset();
loggerInfoSpy.mockReset();
});

afterAll(() => {
Object.entries(processEnvCP).forEach(([k, v]) => (process.env[k] = v));
});
/* eslint-enable functional/immutable-data, @typescript-eslint/no-dynamic-delete */

it('should call execSync with return result', async () => {
const output = await runAutorunExecutor({}, executorContext('utils'));
expect(output.success).toBe(true);
Expand Down Expand Up @@ -63,7 +82,20 @@ describe('runAutorunExecutor', () => {
expect(output.command).toMatch('--persist.filename="REPORT"');
});

it('should create command from context, options and arguments', async () => {
it('should create command from context and options if no api key is set', async () => {
vi.stubEnv('CP_PROJECT', 'CLI');
const output = await runAutorunExecutor(
{ persist: { filename: 'REPORT', format: ['md', 'json'] } },
executorContext('core'),
);
expect(output.command).toMatch('--persist.filename="REPORT"');
expect(output.command).toMatch(
'--persist.format="md" --persist.format="json"',
);
});

it('should create command from context, options and arguments if api key is set', async () => {
vi.stubEnv('CP_API_KEY', 'cp_1234567');
vi.stubEnv('CP_PROJECT', 'CLI');
const output = await runAutorunExecutor(
{ persist: { filename: 'REPORT', format: ['md', 'json'] } },
Expand All @@ -73,6 +105,7 @@ describe('runAutorunExecutor', () => {
expect(output.command).toMatch(
'--persist.format="md" --persist.format="json"',
);
expect(output.command).toMatch('--upload.apiKey="cp_1234567"');
expect(output.command).toMatch('--upload.project="CLI"');
});

Expand Down
9 changes: 6 additions & 3 deletions packages/nx-plugin/src/executors/cli/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,19 @@
const { projectPrefix, persist, upload, command } = options;
const needsUploadParams =
command === 'upload' || command === 'autorun' || command === undefined;
const uploadCfg = uploadConfig(
{ projectPrefix, ...upload },
normalizedContext,
);
const hasApiToken = uploadCfg?.apiKey != null;
return {
...parseAutorunExecutorOnlyOptions(options),
...globalConfig(options, normalizedContext),
persist: persistConfig({ projectPrefix, ...persist }, normalizedContext),
// @TODO This is a hack to avoid validation errors of upload config for commands that dont need it.
// Fix: use utils and execute the core logic directly
// Blocked by Nx plugins can't compile to es6
upload: needsUploadParams
? uploadConfig({ projectPrefix, ...upload }, normalizedContext)
: undefined,
...(needsUploadParams && hasApiToken ? { upload: uploadCfg } : {}),

Check failure on line 42 in packages/nx-plugin/src/executors/cli/utils.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Branch coverage

1st branch is not taken in any test case.
};
}

Expand Down
9 changes: 4 additions & 5 deletions packages/nx-plugin/src/executors/cli/utils.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,13 @@ describe('parseAutorunExecutorOptions', () => {
},
},
);
expect(osAgnosticPath(executorOptions.config)).toBe(
expect(osAgnosticPath(executorOptions.config ?? '')).toBe(
osAgnosticPath('root/code-pushup.config.ts'),
);
expect(executorOptions).toEqual(
expect.objectContaining({
progress: false,
verbose: false,
upload: { project: projectName },
}),
);

Expand All @@ -92,20 +91,20 @@ describe('parseAutorunExecutorOptions', () => {
}),
);

expect(osAgnosticPath(executorOptions.persist?.outputDir)).toBe(
expect(osAgnosticPath(executorOptions.persist?.outputDir ?? '')).toBe(
osAgnosticPath('workspaceRoot/.code-pushup/my-app'),
);
});

it.each<Command | undefined>(['upload', 'autorun', undefined])(
'should include upload config for command %s',
'should include upload config for command %s if API key is provided',
command => {
const projectName = 'my-app';
const executorOptions = parseAutorunExecutorOptions(
{
command,
upload: {
organization: 'code-pushup',
apiKey: '123456789',
},
},
{
Expand Down
16 changes: 8 additions & 8 deletions packages/nx-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ import { createNodes } from './plugin/index.js';
// default export for nx.json#plugins
export default createNodes;

export * from './internal/versions.js';
export { type InitGeneratorSchema } from './generators/init/schema.js';
export { initGenerator, initSchematic } from './generators/init/generator.js';
export type { ConfigurationGeneratorOptions } from './generators/configuration/schema.js';
export { configurationGenerator } from './generators/configuration/generator.js';
export type { AutorunCommandExecutorOptions } from './executors/cli/schema.js';
export { objectToCliArgs } from './executors/internal/cli.js';
export { generateCodePushupConfig } from './generators/configuration/code-pushup-config.js';
export { createNodes } from './plugin/index.js';
export { configurationGenerator } from './generators/configuration/generator.js';
export type { ConfigurationGeneratorOptions } from './generators/configuration/schema.js';
export { initGenerator, initSchematic } from './generators/init/generator.js';
export { type InitGeneratorSchema } from './generators/init/schema.js';
export {
executeProcess,
type ProcessConfig,
} from './internal/execute-process.js';
export { objectToCliArgs } from './executors/internal/cli.js';
export type { AutorunCommandExecutorOptions } from './executors/cli/schema.js';
export * from './internal/versions.js';
export { createNodes } from './plugin/index.js';
5 changes: 2 additions & 3 deletions packages/nx-plugin/src/internal/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { name } from '../../package.json';

export const PROJECT_JSON_FILE_NAME = 'project.json';
export const PACKAGE_NAME = name;
export const CODE_PUSHUP_CONFIG_REGEX = /^code-pushup(?:\.[\w-]+)?\.ts$/;
export const PACKAGE_NAME = '@code-pushup/nx-plugin';
export const DEFAULT_TARGET_NAME = 'code-pushup';
2 changes: 1 addition & 1 deletion packages/nx-plugin/src/internal/execute-process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export type ProcessObserver = {
* // async process execution
* const result = await executeProcess({
* command: 'node',
* args: ['download-data.js'],
* args: ['download-data'],
* observer: {
* onStdout: updateProgress,
* error: handleError,
Expand Down
4 changes: 4 additions & 0 deletions packages/nx-plugin/src/internal/versions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ export const cpCliVersion = loadPackageJson(
path.join(projectsFolder, 'models'),
).version;

/**
* Load the package.json file from the given folder path.
* @param folderPath
*/
function loadPackageJson(folderPath: string): PackageJson {
return readJsonFile<PackageJson>(path.join(folderPath, 'package.json'));
}
2 changes: 1 addition & 1 deletion packages/nx-plugin/src/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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
// name has to be "createNodes" to get picked up by Nx <v20
export const createNodes: CreateNodes = [
`**/${PROJECT_JSON_FILE_NAME}`,
async (
Expand Down
1 change: 1 addition & 0 deletions packages/nx-plugin/src/plugin/plugin.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('@code-pushup/nx-plugin/plugin', () => {
context = {
nxJsonConfiguration: {},
workspaceRoot: '',
configFiles: [],
};
});

Expand Down
15 changes: 11 additions & 4 deletions packages/nx-plugin/src/plugin/target/targets.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
import { readdir } from 'node:fs/promises';
import { CP_TARGET_NAME } from '../constants.js';
import type { NormalizedCreateNodesContext } from '../types.js';
import type {
CreateNodesOptions,
ProjectConfigurationWithName,
} from '../types.js';
import { createConfigurationTarget } from './configuration-target.js';
import { CODE_PUSHUP_CONFIG_REGEX } from './constants.js';
import { createExecutorTarget } from './executor-target.js';

export async function createTargets(
normalizedContext: NormalizedCreateNodesContext,
) {
export type CreateTargetsOptions = {

Check warning on line 11 in packages/nx-plugin/src/plugin/target/targets.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> JSDoc coverage | Types coverage

Missing types documentation for CreateTargetsOptions
projectJson: ProjectConfigurationWithName;
projectRoot: string;
createOptions: CreateNodesOptions;
};

export async function createTargets(normalizedContext: CreateTargetsOptions) {

Check warning on line 17 in packages/nx-plugin/src/plugin/target/targets.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> JSDoc coverage | Functions coverage

Missing functions documentation for createTargets
const {
targetName = CP_TARGET_NAME,
bin,
Expand Down
17 changes: 11 additions & 6 deletions packages/nx-plugin/src/plugin/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
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 { CreateTargetsOptions } from './target/targets.js';

export type ProjectPrefixOptions = {
projectPrefix?: string;
Expand All @@ -13,8 +18,8 @@
'name'
>;

export type NormalizedCreateNodesContext = CreateNodesContext & {
projectJson: ProjectConfigurationWithName;
projectRoot: string;
createOptions: CreateNodesOptions;
};
export type NormalizedCreateNodesContext = CreateNodesContext &
CreateTargetsOptions;

export type NormalizedCreateNodesV2Context = CreateNodesContextV2 &

Check warning on line 24 in packages/nx-plugin/src/plugin/types.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> JSDoc coverage | Types coverage

Missing types documentation for NormalizedCreateNodesV2Context
CreateTargetsOptions;
29 changes: 28 additions & 1 deletion packages/nx-plugin/src/plugin/utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { CreateNodesContext } from '@nx/devkit';
import type { CreateNodesContext, CreateNodesContextV2 } from '@nx/devkit';
import { readFile } from 'node:fs/promises';
import * as path from 'node:path';
import { CP_TARGET_NAME } from './constants.js';
import type {
CreateNodesOptions,
NormalizedCreateNodesContext,
NormalizedCreateNodesV2Context,
ProjectConfigurationWithName,
} from './types.js';

Expand Down Expand Up @@ -36,3 +37,29 @@
);
}
}

export async function normalizedCreateNodesV2Context(

Check failure on line 41 in packages/nx-plugin/src/plugin/utils.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Function coverage

Function normalizedCreateNodesV2Context is not called in any test case.

Check warning on line 41 in packages/nx-plugin/src/plugin/utils.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> JSDoc coverage | Functions coverage

Missing functions documentation for normalizedCreateNodesV2Context
context: CreateNodesContextV2,
projectConfigurationFile: string,
createOptions: CreateNodesOptions = {},
): Promise<NormalizedCreateNodesV2Context> {
const projectRoot = path.dirname(projectConfigurationFile);

try {
const projectJson = JSON.parse(
(await readFile(projectConfigurationFile)).toString(),
) as ProjectConfigurationWithName;

const { targetName = CP_TARGET_NAME } = createOptions;
return {
...context,
projectJson,
projectRoot,
createOptions: { ...createOptions, targetName },
};
} catch {
throw new Error(
`Error parsing project.json file ${projectConfigurationFile}.`,
);
}
}

Check warning on line 65 in packages/nx-plugin/src/plugin/utils.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Line coverage

Lines 41-65 are not covered in any test case.
Loading