Skip to content

Commit 51d99cf

Browse files
committed
test(utils): move objectToCliArgs to transform, remove fs from executeProcess
1 parent 91ca6cf commit 51d99cf

11 files changed

Lines changed: 182 additions & 205 deletions

global-setup.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,3 @@
1-
import { mkdir, rm } from 'fs/promises';
2-
31
export async function setup() {
4-
// ensure clean tmp/ directory
5-
await rm('tmp', { recursive: true, force: true });
6-
await mkdir('tmp', { recursive: true });
72
process.env.TZ = 'UTC';
83
}

packages/utils/src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
export {
2-
CliArgsObject,
32
ProcessConfig,
43
ProcessError,
54
ProcessObserver,
65
ProcessResult,
76
executeProcess,
8-
objectToCliArgs,
97
} from './lib/execute-process';
108
export {
9+
CrawlFileSystemOptions,
1110
FileResult,
1211
MultipleFileResults,
13-
CrawlFileSystemOptions,
1412
crawlFileSystem,
1513
ensureDirectoryExists,
1614
fileExists,
@@ -52,11 +50,13 @@ export { reportToMd } from './lib/report-to-md';
5250
export { reportToStdout } from './lib/report-to-stdout';
5351
export { ScoredReport, scoreReport } from './lib/scoring';
5452
export {
53+
CliArgsObject,
5554
countOccurrences,
5655
distinct,
5756
factorOf,
57+
objectToCliArgs,
5858
objectToEntries,
5959
objectToKeys,
6060
toArray,
61-
} from './lib/transformation';
61+
} from './lib/transform';
6262
export { verboseUtils } from './lib/verbose-utils';

packages/utils/src/lib/execute-process.ts

Lines changed: 0 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -168,66 +168,3 @@ export function executeProcess(cfg: ProcessConfig): Promise<ProcessResult> {
168168
});
169169
});
170170
}
171-
172-
type ArgumentValue = number | string | boolean | string[];
173-
export type CliArgsObject<T extends object = Record<string, ArgumentValue>> =
174-
T extends never
175-
? Record<string, ArgumentValue | undefined> | { _: string }
176-
: T;
177-
178-
/**
179-
* Converts an object with different types of values into an array of command-line arguments.
180-
*
181-
* @example
182-
* const args = objectToProcessArgs({
183-
* _: ['node', 'index.js'], // node index.js
184-
* name: 'Juanita', // --name=Juanita
185-
* formats: ['json', 'md'] // --format=json --format=md
186-
* });
187-
*/
188-
export function objectToCliArgs<
189-
T extends object = Record<string, ArgumentValue>,
190-
>(params?: CliArgsObject<T>): string[] {
191-
if (!params) {
192-
return [];
193-
}
194-
195-
return Object.entries(params).flatMap(([key, value]) => {
196-
// process/file/script
197-
if (key === '_') {
198-
if (Array.isArray(value)) {
199-
return value;
200-
} else {
201-
return [value + ''];
202-
}
203-
}
204-
const prefix = key.length === 1 ? '-' : '--';
205-
// "-*" arguments (shorthands)
206-
if (Array.isArray(value)) {
207-
return value.map(v => `${prefix}${key}="${v}"`);
208-
}
209-
// "--*" arguments ==========
210-
211-
if (Array.isArray(value)) {
212-
return value.map(v => `${prefix}${key}="${v}"`);
213-
}
214-
215-
if (typeof value === 'string') {
216-
return [`${prefix}${key}="${value}"`];
217-
}
218-
219-
if (typeof value === 'number') {
220-
return [`${prefix}${key}=${value}`];
221-
}
222-
223-
if (typeof value === 'boolean') {
224-
return [`${prefix}${value ? '' : 'no-'}${key}`];
225-
}
226-
227-
// @TODO add support for nested objects `persist.filename`
228-
229-
throw new Error(`Unsupported type ${typeof value} for key ${key}`);
230-
});
231-
}
232-
233-
objectToCliArgs<{ z: number }>({ z: 5 });
Lines changed: 4 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,12 @@
1-
import { join } from 'path';
21
import { describe, expect, it, vi } from 'vitest';
32
import { getAsyncProcessRunnerConfig, mockProcessConfig } from '../../test';
4-
import { executeProcess, objectToCliArgs } from './execute-process';
5-
6-
const outFolder = '';
7-
const outName = 'tmp/out-async-runner.json';
8-
const outputFile = join(outFolder, outName);
3+
import { executeProcess } from './execute-process';
94

105
describe('executeProcess', () => {
116
it('should work with node command `node -v`', async () => {
127
const cfg = mockProcessConfig({ command: `node`, args: ['-v'] });
138
const processResult = await executeProcess(cfg);
14-
expect(processResult.stdout).toContain('v');
9+
expect(processResult.stdout).toMatch(/v[0-9]{1,2}(\.[0-9]{1,2}){0,2}/);
1510
});
1611

1712
it('should work with npx command `npx --help`', async () => {
@@ -20,12 +15,12 @@ describe('executeProcess', () => {
2015
const processResult = await executeProcess(cfg);
2116
expect(observer?.onStdout).toHaveBeenCalledTimes(1);
2217
expect(observer?.onComplete).toHaveBeenCalledTimes(1);
23-
expect(processResult.stdout).toContain('Options');
18+
expect(processResult.stdout).toContain('npm exec');
2419
});
2520

2621
it('should work with script `node custom-script.js`', async () => {
2722
const cfg = mockProcessConfig(
28-
getAsyncProcessRunnerConfig({ interval: 10, outputFile }),
23+
getAsyncProcessRunnerConfig({ interval: 10 }),
2924
);
3025
const { observer } = cfg;
3126
const errorSpy = vi.fn();
@@ -51,60 +46,3 @@ describe('executeProcess', () => {
5146
expect(observer?.onError).toHaveBeenCalledTimes(1);
5247
});
5348
});
54-
55-
describe('objectToCliArgs', () => {
56-
it('should handle the "_" argument as script', () => {
57-
const params = { _: 'bin.js' };
58-
const result = objectToCliArgs(params);
59-
expect(result).toEqual(['bin.js']);
60-
});
61-
62-
it('should handle the "_" argument with multiple values', () => {
63-
const params = { _: ['bin.js', '--help'] };
64-
const result = objectToCliArgs(params);
65-
expect(result).toEqual(['bin.js', '--help']);
66-
});
67-
68-
it('should handle shorthands arguments', () => {
69-
const params = {
70-
e: `test`,
71-
};
72-
const result = objectToCliArgs(params);
73-
expect(result).toEqual([`-e="${params.e}"`]);
74-
});
75-
76-
it('should handle string arguments', () => {
77-
const params = { name: 'Juanita' };
78-
const result = objectToCliArgs(params);
79-
expect(result).toEqual(['--name="Juanita"']);
80-
});
81-
82-
it('should handle number arguments', () => {
83-
const params = { parallel: 5 };
84-
const result = objectToCliArgs(params);
85-
expect(result).toEqual(['--parallel=5']);
86-
});
87-
88-
it('should handle boolean arguments', () => {
89-
const params = { progress: true };
90-
const result = objectToCliArgs(params);
91-
expect(result).toEqual(['--progress']);
92-
});
93-
94-
it('should handle negated boolean arguments', () => {
95-
const params = { progress: false };
96-
const result = objectToCliArgs(params);
97-
expect(result).toEqual(['--no-progress']);
98-
});
99-
100-
it('should handle array of string arguments', () => {
101-
const params = { format: ['json', 'md'] };
102-
const result = objectToCliArgs(params);
103-
expect(result).toEqual(['--format="json"', '--format="md"']);
104-
});
105-
106-
it('should throw error for unsupported type', () => {
107-
const params = { unsupported: undefined as any };
108-
expect(() => objectToCliArgs(params)).toThrow('Unsupported type');
109-
});
110-
});

packages/utils/src/lib/scoring.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
PluginReport,
88
Report,
99
} from '@code-pushup/models';
10-
import { deepClone } from './transformation';
10+
import { deepClone } from './transform';
1111

1212
export type EnrichedAuditReport = AuditReport & { plugin: string };
1313
export type WeighedAuditReport = EnrichedAuditReport & { weight: number };
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
export function toArray<T>(val: T | T[]): T[] {
2+
return Array.isArray(val) ? val : [val];
3+
}
4+
5+
export function objectToKeys<T extends object>(obj: T) {
6+
return Object.keys(obj) as (keyof T)[];
7+
}
8+
9+
export function objectToEntries<T extends object>(obj: T) {
10+
return Object.entries(obj) as [keyof T, T[keyof T]][];
11+
}
12+
13+
export function countOccurrences<T extends PropertyKey>(
14+
values: T[],
15+
): Partial<Record<T, number>> {
16+
return values.reduce<Partial<Record<T, number>>>(
17+
(acc, value) => ({ ...acc, [value]: (acc[value] ?? 0) + 1 }),
18+
{},
19+
);
20+
}
21+
22+
export function distinct<T extends string | number | boolean>(array: T[]): T[] {
23+
return Array.from(new Set(array));
24+
}
25+
26+
export function deepClone<T>(obj: T): T {
27+
if (obj == null || typeof obj !== 'object') {
28+
return obj;
29+
}
30+
31+
const cloned: T = Array.isArray(obj) ? ([] as T) : ({} as T);
32+
// eslint-disable-next-line functional/no-loop-statements
33+
for (const key in obj) {
34+
if (Object.prototype.hasOwnProperty.call(obj, key)) {
35+
cloned[key as keyof T] = deepClone(obj[key]);
36+
}
37+
}
38+
return cloned;
39+
}
40+
41+
export function factorOf<T>(items: T[], filterFn: (i: T) => boolean): number {
42+
const itemCount = items.length;
43+
// early exit for empty items
44+
if (!itemCount) {
45+
return 1;
46+
}
47+
const filterCount = items.filter(filterFn).length;
48+
// if no items result from the filter fn we forward return 1 as factor
49+
return filterCount === 0 ? 1 : (itemCount - filterCount) / itemCount;
50+
}
51+
52+
type ArgumentValue = number | string | boolean | string[];
53+
export type CliArgsObject<T extends object = Record<string, ArgumentValue>> =
54+
T extends never
55+
? Record<string, ArgumentValue | undefined> | { _: string }
56+
: T;
57+
58+
/**
59+
* Converts an object with different types of values into an array of command-line arguments.
60+
*
61+
* @example
62+
* const args = objectToProcessArgs({
63+
* _: ['node', 'index.js'], // node index.js
64+
* name: 'Juanita', // --name=Juanita
65+
* formats: ['json', 'md'] // --format=json --format=md
66+
* });
67+
*/
68+
export function objectToCliArgs<
69+
T extends object = Record<string, ArgumentValue>,
70+
>(params?: CliArgsObject<T>): string[] {
71+
if (!params) {
72+
return [];
73+
}
74+
75+
return Object.entries(params).flatMap(([key, value]) => {
76+
// process/file/script
77+
if (key === '_') {
78+
if (Array.isArray(value)) {
79+
return value;
80+
} else {
81+
return [value + ''];
82+
}
83+
}
84+
const prefix = key.length === 1 ? '-' : '--';
85+
// "-*" arguments (shorthands)
86+
if (Array.isArray(value)) {
87+
return value.map(v => `${prefix}${key}="${v}"`);
88+
}
89+
// "--*" arguments ==========
90+
91+
if (Array.isArray(value)) {
92+
return value.map(v => `${prefix}${key}="${v}"`);
93+
}
94+
95+
if (typeof value === 'string') {
96+
return [`${prefix}${key}="${value}"`];
97+
}
98+
99+
if (typeof value === 'number') {
100+
return [`${prefix}${key}=${value}`];
101+
}
102+
103+
if (typeof value === 'boolean') {
104+
return [`${prefix}${value ? '' : 'no-'}${key}`];
105+
}
106+
107+
// @TODO add support for nested objects `persist.filename`
108+
109+
throw new Error(`Unsupported type ${typeof value} for key ${key}`);
110+
});
111+
}

0 commit comments

Comments
 (0)