Skip to content

Commit 7369585

Browse files
nacho-vazquezmatejchalk
authored andcommitted
feat: implement skip-plugins option
1 parent 7e55fd3 commit 7369585

File tree

6 files changed

+285
-0
lines changed

6 files changed

+285
-0
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { filterItemRefsBy } from '@code-pushup/utils';
2+
import { SkipPluginsOptions } from './skip-plugins.model';
3+
import { validateSkipPluginsOption } from './skip-plugins.utils';
4+
5+
export function skipPluginsMiddleware<T extends SkipPluginsOptions>(
6+
originalProcessArgs: T,
7+
): T {
8+
const { categories = [], skipPlugins: originalSkipPlugins } =
9+
originalProcessArgs;
10+
11+
if (originalSkipPlugins && originalSkipPlugins.length > 0) {
12+
const { verbose, plugins, skipPlugins = [] } = originalProcessArgs;
13+
14+
validateSkipPluginsOption(
15+
{ plugins, categories },
16+
{ skipPlugins, verbose },
17+
);
18+
19+
const validSkipPlugins = skipPlugins.filter(sP =>
20+
plugins.find(p => p.slug === sP),
21+
);
22+
23+
const skipPluginsSet = new Set(validSkipPlugins);
24+
25+
return {
26+
...originalProcessArgs,
27+
plugins:
28+
skipPluginsSet.size > 0
29+
? plugins.filter(({ slug }) => !skipPluginsSet.has(slug))
30+
: plugins,
31+
categories:
32+
skipPluginsSet.size > 0
33+
? filterItemRefsBy(
34+
categories,
35+
({ plugin }) => !skipPluginsSet.has(plugin),
36+
)
37+
: categories,
38+
};
39+
}
40+
41+
return {
42+
...originalProcessArgs,
43+
// if undefined fill categories with empty array
44+
categories,
45+
};
46+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { describe, expect, vi } from 'vitest';
2+
import { CategoryConfig, PluginConfig } from '@code-pushup/models';
3+
import { skipPluginsMiddleware } from './skip-plugins.middleware';
4+
5+
vi.mock('@code-pushup/core', async () => {
6+
const { CORE_CONFIG_MOCK }: typeof import('@code-pushup/test-utils') =
7+
await vi.importActual('@code-pushup/test-utils');
8+
const core: object = await vi.importActual('@code-pushup/core');
9+
return {
10+
...core,
11+
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
12+
autoloadRc: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
13+
};
14+
});
15+
16+
describe('skipPluginsMiddleware', () => {
17+
it('should fill undefined categories with empty array', () => {
18+
expect(
19+
skipPluginsMiddleware({
20+
plugins: [{ slug: 'p1' } as PluginConfig],
21+
}),
22+
).toStrictEqual({
23+
plugins: [{ slug: 'p1' }],
24+
categories: [],
25+
});
26+
});
27+
28+
it('should forward equal values if not set', () => {
29+
expect(
30+
skipPluginsMiddleware({
31+
plugins: [{ slug: 'p1' } as PluginConfig],
32+
categories: [
33+
{ slug: 'c1', refs: [{ plugin: 'p1' }] } as CategoryConfig,
34+
],
35+
}),
36+
).toStrictEqual({
37+
plugins: [{ slug: 'p1' }],
38+
categories: [{ slug: 'c1', refs: [{ plugin: 'p1' }] }],
39+
});
40+
});
41+
42+
it('should filter plugins plugins for slug "p1"', () => {
43+
const { plugins } = skipPluginsMiddleware({
44+
skipPlugins: ['p1'],
45+
plugins: [{ slug: 'p1' }, { slug: 'p2' }] as PluginConfig[],
46+
categories: [],
47+
});
48+
expect(plugins).toStrictEqual([expect.objectContaining({ slug: 'p2' })]);
49+
});
50+
51+
it('should forward plugins and categories for a slug not present in plugins', () => {
52+
const originalCategories = [
53+
{
54+
slug: 'c1',
55+
refs: [
56+
{ plugin: 'p1', slug: 'a1-p1' },
57+
{ plugin: 'p2', slug: 'a2-p1' },
58+
],
59+
},
60+
{ slug: 'c2', refs: [{ plugin: 'p2', slug: 'a1-p2' }] },
61+
] as CategoryConfig[];
62+
const originalPlugins = [{ slug: 'p1' }, { slug: 'p2' }] as PluginConfig[];
63+
const { categories, plugins } = skipPluginsMiddleware({
64+
skipPlugins: ['wrong-slug'],
65+
plugins: originalPlugins,
66+
categories: originalCategories,
67+
});
68+
expect(categories).toBe(originalCategories);
69+
expect(plugins).toBe(originalPlugins);
70+
});
71+
72+
it('should filter categories for slug "p1"', () => {
73+
const { categories } = skipPluginsMiddleware({
74+
skipPlugins: ['p1'],
75+
plugins: [{ slug: 'p1' }, { slug: 'p2' }] as PluginConfig[],
76+
categories: [
77+
{
78+
slug: 'c1',
79+
refs: [
80+
{ plugin: 'p1', slug: 'a1-p1' },
81+
{ plugin: 'p2', slug: 'a2-p1' },
82+
],
83+
},
84+
{ slug: 'c2', refs: [{ plugin: 'p2', slug: 'a1-p2' }] },
85+
] as CategoryConfig[],
86+
});
87+
expect(categories).toStrictEqual([
88+
expect.objectContaining({
89+
slug: 'c1',
90+
refs: [{ plugin: 'p2', slug: 'a2-p1' }],
91+
}),
92+
expect.objectContaining({
93+
slug: 'c2',
94+
refs: [{ plugin: 'p2', slug: 'a1-p2' }],
95+
}),
96+
]);
97+
});
98+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { GlobalOptions } from '@code-pushup/core';
2+
import { CoreConfig } from '@code-pushup/models';
3+
4+
export type SkipPluginsCliOptions = {
5+
skipPlugins?: string[];
6+
};
7+
export type SkipPluginsOptions = Partial<GlobalOptions> &
8+
Pick<CoreConfig, 'categories' | 'plugins'> &
9+
SkipPluginsCliOptions;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Options } from 'yargs';
2+
import { coerceArray } from './global.utils';
3+
4+
export const skipPluginsOption: Options = {
5+
describe: 'List of plugins to skip. If not set all plugins are run.',
6+
type: 'array',
7+
default: [],
8+
coerce: coerceArray,
9+
};
10+
11+
export function yargsSkipPluginsOptionsDefinition(): Record<
12+
'skipPlugins',
13+
Options
14+
> {
15+
return {
16+
skipPlugins: skipPluginsOption,
17+
};
18+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import chalk from 'chalk';
2+
import type { CategoryConfig, PluginConfig } from '@code-pushup/models';
3+
import { filterItemRefsBy, ui } from '@code-pushup/utils';
4+
5+
export function validateSkipPluginsOption(
6+
{
7+
plugins,
8+
categories,
9+
}: {
10+
plugins: PluginConfig[];
11+
categories: CategoryConfig[];
12+
},
13+
{
14+
skipPlugins = [],
15+
verbose = false,
16+
}: { skipPlugins?: string[]; verbose?: boolean } = {},
17+
): void {
18+
const skipPluginsSet = new Set(skipPlugins);
19+
const missingPlugins = skipPlugins.filter(
20+
plugin => !plugins.some(({ slug }) => slug === plugin),
21+
);
22+
23+
if (missingPlugins.length > 0 && verbose) {
24+
ui().logger.info(
25+
`${chalk.yellow(
26+
'⚠',
27+
)} The --skipPlugin argument references plugins with "${missingPlugins.join(
28+
'", "',
29+
)}" slugs, but no such plugins are present in the configuration. Expected one of the following plugin slugs: "${plugins
30+
.map(({ slug }) => slug)
31+
.join('", "')}".`,
32+
);
33+
}
34+
35+
if (categories.length > 0) {
36+
const removedCategorieSlugs = filterItemRefsBy(categories, ({ plugin }) =>
37+
skipPluginsSet.has(plugin),
38+
).map(({ slug }) => slug);
39+
ui().logger.info(
40+
`The --skipPlugin argument removed categories with "${removedCategorieSlugs.join(
41+
'", "',
42+
)}" slugs.
43+
`,
44+
);
45+
}
46+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { describe, expect } from 'vitest';
2+
import { CategoryConfig, PluginConfig } from '@code-pushup/models';
3+
import { getLogMessages } from '@code-pushup/test-utils';
4+
import { ui } from '@code-pushup/utils';
5+
import { validateSkipPluginsOption } from './skip-plugins.utils';
6+
7+
describe('validateSkipPluginsOption', () => {
8+
it('should warn if skipPlugins option contains non-existing plugin', () => {
9+
validateSkipPluginsOption(
10+
{
11+
plugins: [
12+
{ slug: 'plugin1', audits: [{ slug: 'a1' }] },
13+
] as PluginConfig[],
14+
categories: [],
15+
},
16+
{
17+
skipPlugins: ['plugin1', 'plugin3', 'plugin4'],
18+
verbose: true,
19+
},
20+
);
21+
const logs = getLogMessages(ui().logger);
22+
expect(logs[0]).toContain(
23+
'The --skipPlugin argument references plugins with "plugin3", "plugin4" slugs',
24+
);
25+
});
26+
27+
it('should not log if skipPlugins option contains only existing plugins', () => {
28+
validateSkipPluginsOption(
29+
{
30+
plugins: [
31+
{ slug: 'plugin1', audits: [{ slug: 'a1-p1' }] },
32+
{ slug: 'plugin2', audits: [{ slug: 'a1-p2' }] },
33+
] as PluginConfig[],
34+
categories: [],
35+
},
36+
{
37+
skipPlugins: ['plugin1'],
38+
verbose: true,
39+
},
40+
);
41+
expect(getLogMessages(ui().logger)).toHaveLength(0);
42+
});
43+
44+
it('should print ignored category and its first violating plugin', () => {
45+
validateSkipPluginsOption(
46+
{
47+
plugins: [
48+
{ slug: 'plugin1', audits: [{ slug: 'a1-p1' }] },
49+
{ slug: 'plugin2', audits: [{ slug: 'a1-p2' }] },
50+
] as PluginConfig[],
51+
categories: [
52+
{ slug: 'c1', refs: [{ plugin: 'plugin2' }] } as CategoryConfig,
53+
{ slug: 'c2', refs: [{ plugin: 'plugin1' }] } as CategoryConfig,
54+
{ slug: 'c3', refs: [{ plugin: 'plugin2' }] } as CategoryConfig,
55+
],
56+
},
57+
{
58+
skipPlugins: ['plugin2'],
59+
verbose: true,
60+
},
61+
);
62+
console.log(getLogMessages(ui().logger));
63+
expect(getLogMessages(ui().logger)).toHaveLength(1);
64+
expect(getLogMessages(ui().logger)[0]).toContain(
65+
'The --skipPlugin argument removed categories with "c1", "c3" slugs',
66+
);
67+
});
68+
});

0 commit comments

Comments
 (0)