Skip to content

Commit bdeab54

Browse files
authored
feat(models): add report filename option (#174)
1 parent 7d11d81 commit bdeab54

File tree

13 files changed

+135
-47
lines changed

13 files changed

+135
-47
lines changed

e2e/cli-e2e/mocks/code-pushup.config.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
// TODO: import plugins using NPM package names using local registry: https://github.com/flowup/quality-metrics-cli/issues/33
22
// import eslintPlugin from '../../../dist/packages/plugin-eslint';
3+
import { join } from 'path';
34
import lighthousePlugin from '../../../dist/packages/plugin-lighthouse';
45

56
export default {
6-
persist: { outputDir: 'tmp/js' },
7+
persist: { outputDir: join('tmp', 'js') },
78
upload: {
89
organization: 'code-pushup',
910
project: 'cli-js',

e2e/cli-e2e/mocks/code-pushup.config.mjs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { join } from 'path';
12
// TODO: import plugins using NPM package names using local registry: https://github.com/flowup/quality-metrics-cli/issues/33
23
// import eslintPlugin from '../../../dist/packages/plugin-eslint';
34
import lighthousePlugin from '../../../dist/packages/plugin-lighthouse';
45

56
export default {
6-
persist: { outputDir: 'tmp/mjs' },
7+
persist: { outputDir: join('tmp', 'mjs') },
78
upload: {
89
organization: 'code-pushup',
910
project: 'cli-mjs',

e2e/cli-e2e/mocks/code-pushup.config.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
// TODO: import plugins using NPM package names using local registry: https://github.com/flowup/quality-metrics-cli/issues/33
22
// import eslintPlugin from '../../../dist/packages/plugin-eslint';
3+
import { join } from 'path';
34
import lighthousePlugin from '../../../dist/packages/plugin-lighthouse';
45
import { CoreConfig } from '../../../packages/models/src';
56

67
export default {
7-
persist: { outputDir: 'tmp/ts' },
8+
persist: { outputDir: join('tmp', 'ts') },
89
upload: {
910
organization: 'code-pushup',
1011
project: 'cli-ts',

e2e/cli-e2e/tests/print-config.spec.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ describe('print-config', () => {
4141
apiKey: 'e2e-api-key',
4242
server: 'https://e2e.com/api',
4343
},
44-
persist: { outputDir: `tmp/${ext}` },
44+
persist: {
45+
outputDir: join('tmp', ext),
46+
filename: 'report',
47+
},
4548
plugins: expect.any(Array),
4649
categories: expect.any(Array),
4750
});
@@ -65,7 +68,8 @@ describe('print-config', () => {
6568
server: 'https://e2e.com/api',
6669
},
6770
persist: {
68-
outputDir: `tmp/ts`,
71+
outputDir: join('tmp', 'ts'),
72+
filename: 'report',
6973
},
7074
plugins: expect.any(Array),
7175
categories: expect.any(Array),

package-lock.json

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/core/src/lib/implementation/persist.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export async function persistReport(
3030
): Promise<PersistResult> {
3131
const { persist } = config;
3232
const outputDir = persist.outputDir;
33+
const filename = persist?.filename || reportName(report);
3334
let { format } = persist;
3435
format = format && format.length !== 0 ? format : ['stdout'];
3536
let scoredReport;
@@ -61,7 +62,7 @@ export async function persistReport(
6162
// write relevant format outputs to file system
6263
return Promise.allSettled(
6364
results.map(({ format, content }) => {
64-
const reportPath = join(outputDir, `report.${format}`);
65+
const reportPath = join(outputDir, `${filename}.${format}`);
6566

6667
return (
6768
writeFile(reportPath, content)
@@ -104,3 +105,7 @@ export function logPersistedResults(persistResult: PersistResult) {
104105
});
105106
}
106107
}
108+
109+
function reportName(report: Report): string {
110+
return `report.${report.date}`;
111+
}

packages/models/src/lib/implementation/schemas.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { z } from 'zod';
2-
import { slugRegex } from './utils';
2+
import { filenameRegex, slugRegex } from './utils';
33

44
/**
55
* Schema for execution meta date
@@ -102,6 +102,20 @@ export function filePathSchema(description: string) {
102102
.min(1, { message: 'path is invalid' });
103103
}
104104

105+
/**
106+
* Schema for a fileNameSchema
107+
* @param description
108+
*/
109+
export function fileNameSchema(description: string) {
110+
return z
111+
.string({ description })
112+
.trim()
113+
.regex(filenameRegex, {
114+
message: `The filename has to be valid`,
115+
})
116+
.min(1, { message: 'file name is invalid' });
117+
}
118+
105119
/**
106120
* Schema for a weight
107121
* @param description

packages/models/src/lib/implementation/utils.spec.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
import { describe, expect, it } from 'vitest';
2-
import { hasDuplicateStrings, hasMissingStrings, slugRegex } from './utils';
2+
import {
3+
filenameRegex,
4+
hasDuplicateStrings,
5+
hasMissingStrings,
6+
slugRegex,
7+
} from './utils';
38

49
describe('slugRegex', () => {
510
// test valid and array of strings against slugRegex with it blocks
@@ -32,6 +37,32 @@ describe('slugRegex', () => {
3237
});
3338
});
3439

40+
describe('filenameRegex', () => {
41+
// test valid and array of strings against filenameRegex with it blocks
42+
it.each(['report', 'report.mock', 'report-test.mock'])(
43+
`should match valid %p`,
44+
filename => {
45+
expect(filename).toMatch(filenameRegex);
46+
},
47+
);
48+
49+
// test invalid and array of strings against filenameRegex with it blocks
50+
it.each([
51+
'',
52+
' ',
53+
'file/name',
54+
'file:name',
55+
'file*name',
56+
'file?name',
57+
'file"name',
58+
'file<name',
59+
'file>name',
60+
'file|name',
61+
])(`should not match invalid file name %p`, invalidFilename => {
62+
expect(invalidFilename).not.toMatch(filenameRegex);
63+
});
64+
});
65+
3566
describe('stringUnique', () => {
3667
it('should return true for a list of unique strings', () => {
3768
expect(hasDuplicateStrings(['a', 'b'])).toBe(false);

packages/models/src/lib/implementation/utils.ts

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@
66
*/
77
export const slugRegex = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
88

9+
/**
10+
* Regular expression to validate a filename.
11+
*/
12+
export const filenameRegex = /^(?!.*[ \\/:*?"<>|]).+$/;
13+
914
/**
1015
* helper function to validate string arrays
1116
*

packages/models/src/lib/persist-config.spec.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,26 @@ describe('persistConfigSchema', () => {
88
expect(() => persistConfigSchema.parse(persistConfigMock)).not.toThrow();
99
});
1010

11+
it('should fill defaults', () => {
12+
const persistConfigMock = persistConfigSchema.parse(persistConfig());
13+
expect(persistConfigMock.filename).toBe('report');
14+
});
15+
1116
it('should throw if outputDir is invalid', () => {
12-
const persistConfigMock = persistConfig({ outputDir: ' ' });
17+
const persistConfigMock = persistConfig();
18+
persistConfigMock.outputDir = ' ';
19+
persistConfigMock.filename = 'valid-filename';
1320

1421
expect(() => persistConfigSchema.parse(persistConfigMock)).toThrow(
1522
`path is invalid`,
1623
);
1724
});
25+
26+
it('should throw if filename is invalid', () => {
27+
const persistConfigMock = persistConfig();
28+
persistConfigMock.filename = ' ';
29+
expect(() => persistConfigSchema.parse(persistConfigMock)).toThrow(
30+
'The filename has to be valid',
31+
);
32+
});
1833
});
+5-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import { z } from 'zod';
2-
import { filePathSchema } from './implementation/schemas';
2+
import { fileNameSchema, filePathSchema } from './implementation/schemas';
33

44
export const formatSchema = z.enum(['json', 'stdout', 'md']);
55
export type Format = z.infer<typeof formatSchema>;
66

77
export const persistConfigSchema = z.object({
88
outputDir: filePathSchema('Artifacts folder'),
9-
format: z.array(formatSchema).default(['stdout']).optional(),
9+
filename: fileNameSchema('Artifacts file name (without extension)').default(
10+
'report',
11+
),
12+
format: z.array(formatSchema).default(['stdout']).optional(), // @TODO remove default or optional value and otherwise it will not set defaults
1013
});
1114

1215
export type PersistConfig = z.infer<typeof persistConfigSchema>;

packages/models/test/fixtures/config.mock.ts

+40-33
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
import { CoreConfig, PluginConfig, PluginReport, Report } from '../../src';
1+
import {
2+
CoreConfig,
3+
PluginConfig,
4+
PluginReport,
5+
Report,
6+
coreConfigSchema,
7+
} from '../../src';
28
import { categoryConfigs } from './categories.mock';
39
import { eslintPluginConfig } from './eslint-plugin.mock';
410
import { lighthousePluginConfig } from './lighthouse-plugin.mock';
11+
import { persistConfig } from './persist-config.mock';
512
import { auditReport, pluginConfig } from './plugin-config.mock';
613

714
export function config(outputDir = 'tmp'): CoreConfig {
815
return {
9-
persist: { outputDir },
16+
persist: persistConfig({ outputDir }),
1017
upload: {
1118
organization: 'code-pushup',
1219
project: 'cli',
@@ -25,38 +32,38 @@ export function minimalConfig(
2532
const AUDIT_1_SLUG = 'audit-1';
2633
const outputFile = `${PLUGIN_1_SLUG}.${Date.now()}.json`;
2734

28-
return JSON.parse(
29-
JSON.stringify({
30-
persist: { outputDir },
31-
upload: {
32-
organization: 'code-pushup',
33-
project: 'cli',
34-
apiKey: 'dummy-api-key',
35-
server: 'https://example.com/api',
35+
const cfg = coreConfigSchema.parse({
36+
persist: persistConfig({ outputDir }),
37+
upload: {
38+
organization: 'code-pushup',
39+
project: 'cli',
40+
apiKey: 'dummy-api-key',
41+
server: 'https://example.com/api',
42+
},
43+
categories: [
44+
{
45+
slug: 'category-1',
46+
title: 'Category 1',
47+
refs: [
48+
{
49+
type: 'audit',
50+
plugin: PLUGIN_1_SLUG,
51+
slug: AUDIT_1_SLUG,
52+
weight: 1,
53+
},
54+
],
3655
},
37-
categories: [
38-
{
39-
slug: 'category-1',
40-
title: 'Category 1',
41-
refs: [
42-
{
43-
type: 'audit',
44-
plugin: PLUGIN_1_SLUG,
45-
slug: AUDIT_1_SLUG,
46-
weight: 1,
47-
},
48-
],
49-
},
50-
],
51-
plugins: [
52-
pluginConfig([auditReport({ slug: AUDIT_1_SLUG })], {
53-
slug: PLUGIN_1_SLUG,
54-
outputDir,
55-
outputFile,
56-
}),
57-
],
58-
} satisfies Omit<CoreConfig, 'upload'> & Required<Pick<CoreConfig, 'upload'>>),
59-
);
56+
],
57+
plugins: [
58+
pluginConfig([auditReport({ slug: AUDIT_1_SLUG })], {
59+
slug: PLUGIN_1_SLUG,
60+
outputDir,
61+
outputFile,
62+
}),
63+
],
64+
});
65+
66+
return JSON.parse(JSON.stringify(cfg));
6067
}
6168

6269
export function minimalReport(outputDir = 'tmp'): Report {
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { PersistConfig } from '../../src';
1+
import { PersistConfig, persistConfigSchema } from '../../src';
22

33
export function persistConfig(opt?: Partial<PersistConfig>): PersistConfig {
4-
return {
4+
return persistConfigSchema.parse({
55
outputDir: 'tmp',
66
...opt,
7-
};
7+
});
88
}

0 commit comments

Comments
 (0)