Skip to content

Commit 6c7277a

Browse files
authored
Merge fc411fa into 2f8d78e
2 parents 2f8d78e + fc411fa commit 6c7277a

File tree

28 files changed

+1148
-92
lines changed

28 files changed

+1148
-92
lines changed

packages/core/src/lib/upload.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export type UploadOptions = { upload?: UploadConfig } & {
1414
/**
1515
* Uploads collected audits to the portal
1616
* @param options
17+
* @param uploadFn
1718
*/
1819
export async function upload(
1920
options: UploadOptions,

packages/nx-plugin/README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
#### Init
66

7-
Install JS packages and register plugin
7+
Install JS packages and register plugin.
8+
See [init docs](./src/generators/init/README.md) for details
89

910
Examples:
1011

@@ -13,7 +14,8 @@ Examples:
1314

1415
#### Configuration
1516

16-
Adds a `code-pushup` target to your `project.json`
17+
Adds a `code-pushup` target to your `project.json`.
18+
See [configuration docs](./src/generators/configuration/README.md) for details
1719

1820
Examples:
1921

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const ENV = {
2+
CP_SERVER: 'https://portal.code.pushup.dev',
3+
CP_ORGANIZATION: 'code-pushup',
4+
CP_PROJECT: 'utils',
5+
CP_API_KEY: '23456789098765432345678909876543',
6+
CP_TIMEOUT: '9',
7+
};

packages/nx-plugin/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
"dependencies": {
66
"@nx/devkit": "^17.1.3",
77
"tslib": "2.6.2",
8-
"nx": "^17.1.3"
8+
"nx": "^17.1.3",
9+
"@code-pushup/models": "*",
10+
"zod": "^3.22.4"
911
},
1012
"type": "commonjs",
1113
"main": "./src/index.js",
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
export function createCliCommand(
2+
command: string,
3+
args: Record<string, unknown>,
4+
): string {
5+
return `npx @code-pushup/cli ${command} ${objectToCliArgs(args).join(' ')}`;
6+
}
7+
8+
type ArgumentValue = number | string | boolean | string[];
9+
export type CliArgsObject<T extends object = Record<string, ArgumentValue>> =
10+
T extends never
11+
? // eslint-disable-next-line @typescript-eslint/naming-convention
12+
Record<string, ArgumentValue | undefined> | { _: string }
13+
: T;
14+
// @TODO import from @code-pushup/utils => get rid of poppins for cjs support
15+
// eslint-disable-next-line sonarjs/cognitive-complexity
16+
export function objectToCliArgs<
17+
T extends object = Record<string, ArgumentValue>,
18+
>(params?: CliArgsObject<T>): string[] {
19+
if (!params) {
20+
return [];
21+
}
22+
23+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
24+
return Object.entries(params).flatMap(([key, value]) => {
25+
// process/file/script
26+
if (key === '_') {
27+
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
28+
return Array.isArray(value) ? value : [`${value}`];
29+
}
30+
const prefix = key.length === 1 ? '-' : '--';
31+
// "-*" arguments (shorthands)
32+
if (Array.isArray(value)) {
33+
return value.map(v => `${prefix}${key}="${v}"`);
34+
}
35+
36+
if (typeof value === 'object') {
37+
return Object.entries(value as Record<string, unknown>).map(
38+
([k, v]) => `${prefix}${key}.${k}="${v?.toString()}"`,
39+
);
40+
}
41+
42+
if (typeof value === 'string') {
43+
return [`${prefix}${key}="${value}"`];
44+
}
45+
46+
if (typeof value === 'number') {
47+
return [`${prefix}${key}=${value}`];
48+
}
49+
50+
if (typeof value === 'boolean') {
51+
return [`${prefix}${value ? '' : 'no-'}${key}`];
52+
}
53+
54+
if (value === undefined) {
55+
return [];
56+
}
57+
58+
throw new Error(`Unsupported type ${typeof value} for key ${key}`);
59+
});
60+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { createCliCommand, objectToCliArgs } from './cli';
3+
4+
describe('objectToCliArgs', () => {
5+
it('should empty params', () => {
6+
const result = objectToCliArgs();
7+
expect(result).toEqual([]);
8+
});
9+
10+
it('should handle the "_" argument as script', () => {
11+
const params = { _: 'bin.js' };
12+
const result = objectToCliArgs(params);
13+
expect(result).toEqual(['bin.js']);
14+
});
15+
16+
it('should handle the "_" argument with multiple values', () => {
17+
const params = { _: ['bin.js', '--help'] };
18+
const result = objectToCliArgs(params);
19+
expect(result).toEqual(['bin.js', '--help']);
20+
});
21+
22+
it('should handle shorthands arguments', () => {
23+
const params = {
24+
e: `test`,
25+
};
26+
const result = objectToCliArgs(params);
27+
expect(result).toEqual([`-e="${params.e}"`]);
28+
});
29+
30+
it('should handle string arguments', () => {
31+
const params = { name: 'Juanita' };
32+
const result = objectToCliArgs(params);
33+
expect(result).toEqual(['--name="Juanita"']);
34+
});
35+
36+
it('should handle number arguments', () => {
37+
const params = { parallel: 5 };
38+
const result = objectToCliArgs(params);
39+
expect(result).toEqual(['--parallel=5']);
40+
});
41+
42+
it('should handle boolean arguments', () => {
43+
const params = { progress: true };
44+
const result = objectToCliArgs(params);
45+
expect(result).toEqual(['--progress']);
46+
});
47+
48+
it('should handle negated boolean arguments', () => {
49+
const params = { progress: false };
50+
const result = objectToCliArgs(params);
51+
expect(result).toEqual(['--no-progress']);
52+
});
53+
54+
it('should handle array of string arguments', () => {
55+
const params = { format: ['json', 'md'] };
56+
const result = objectToCliArgs(params);
57+
expect(result).toEqual(['--format="json"', '--format="md"']);
58+
});
59+
60+
it('should handle objects', () => {
61+
const params = { format: { json: 'simple' } };
62+
const result = objectToCliArgs(params);
63+
expect(result).toStrictEqual(['--format.json="simple"']);
64+
});
65+
66+
it('should handle objects with undefined', () => {
67+
const params = { format: undefined };
68+
const result = objectToCliArgs(params);
69+
expect(result).toStrictEqual([]);
70+
});
71+
72+
it('should throw error for unsupported type', () => {
73+
expect(() => objectToCliArgs({ param: Symbol('') })).toThrow(
74+
'Unsupported type',
75+
);
76+
});
77+
});
78+
79+
describe('createCliCommand', () => {
80+
it('should create command out of command name and an object for arguments', () => {
81+
const result = createCliCommand('autorun', { verbose: true });
82+
expect(result).toBe('npx @code-pushup/cli autorun --verbose');
83+
});
84+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { MockInstance, describe, expect } from 'vitest';
2+
import { ENV } from '../../../mock/fixtures/env';
3+
import { uploadConfig } from './config';
4+
import * as env from './env';
5+
6+
describe('uploadConfig', () => {
7+
let parseEnvSpy: MockInstance<[], NodeJS.ProcessEnv>;
8+
let processEnvSpy: MockInstance<[], NodeJS.ProcessEnv>;
9+
10+
beforeAll(() => {
11+
processEnvSpy = vi.spyOn(process, 'env', 'get').mockReturnValue({});
12+
parseEnvSpy = vi.spyOn(env, 'parseEnv');
13+
});
14+
15+
afterAll(() => {
16+
processEnvSpy.mockRestore();
17+
parseEnvSpy.mockRestore();
18+
});
19+
20+
it('should call parseEnv function with values from process.env', () => {
21+
processEnvSpy.mockReturnValue(ENV);
22+
expect(
23+
uploadConfig(
24+
{},
25+
{
26+
workspaceRoot: 'workspaceRoot',
27+
projectConfig: {
28+
name: 'my-app',
29+
root: 'root',
30+
},
31+
},
32+
),
33+
).toEqual(
34+
expect.objectContaining({
35+
server: ENV.CP_SERVER,
36+
apiKey: ENV.CP_API_KEY,
37+
organization: ENV.CP_ORGANIZATION,
38+
project: ENV.CP_PROJECT,
39+
}),
40+
);
41+
42+
expect(parseEnvSpy).toHaveBeenCalledTimes(1);
43+
expect(parseEnvSpy).toHaveBeenCalledWith(ENV);
44+
});
45+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { join } from 'node:path';
2+
import type { PersistConfig, UploadConfig } from '@code-pushup/models';
3+
import { parseEnv } from './env';
4+
import {
5+
BaseNormalizedExecutorContext,
6+
GlobalExecutorOptions,
7+
ProjectExecutorOnlyOptions,
8+
} from './types';
9+
10+
export function globalConfig(
11+
options: Partial<GlobalExecutorOptions>,
12+
context: BaseNormalizedExecutorContext,
13+
): Required<GlobalExecutorOptions> {
14+
const { projectConfig } = context;
15+
const { root: projectRoot = '' } = projectConfig ?? {};
16+
// For better debugging use `--verbose --no-progress` as default
17+
const { verbose, progress, config } = options;
18+
return {
19+
verbose: !!verbose,
20+
progress: !!progress,
21+
config: config ?? join(projectRoot, 'code-pushup.config.ts'),
22+
};
23+
}
24+
25+
export function persistConfig(
26+
options: Partial<PersistConfig & ProjectExecutorOnlyOptions>,
27+
context: BaseNormalizedExecutorContext,
28+
): Partial<PersistConfig> {
29+
const { projectConfig, workspaceRoot } = context;
30+
31+
const { name: projectName = '' } = projectConfig ?? {};
32+
const {
33+
format,
34+
outputDir = join(workspaceRoot, '.code-pushup', projectName), // always in <root>/.code-pushup/<project-name>,
35+
filename,
36+
} = options;
37+
38+
return {
39+
outputDir,
40+
...(format ? { format } : {}),
41+
...(filename ? { filename } : {}),
42+
};
43+
}
44+
45+
export function uploadConfig(
46+
options: Partial<UploadConfig & ProjectExecutorOnlyOptions>,
47+
context: BaseNormalizedExecutorContext,
48+
): Partial<UploadConfig> {
49+
const { projectConfig, workspaceRoot } = context;
50+
51+
const { name: projectName } = projectConfig ?? {};
52+
const { projectPrefix, server, apiKey, organization, project, timeout } =
53+
options;
54+
const applyPrefix = workspaceRoot === '.';
55+
const prefix = projectPrefix ? `${projectPrefix}-` : '';
56+
return {
57+
...(projectName
58+
? {
59+
project: applyPrefix ? `${prefix}${projectName}` : projectName, // provide correct project
60+
}
61+
: {}),
62+
...parseEnv(process.env),
63+
...Object.fromEntries(
64+
Object.entries({ server, apiKey, organization, project, timeout }).filter(
65+
([_, v]) => v !== undefined,
66+
),
67+
),
68+
};
69+
}

0 commit comments

Comments
 (0)