@form8ion/codecov 7.6.0 → 7.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -2
- package/src/badge/index.js +1 -0
- package/src/badge/repository-details-fetcher.js +10 -0
- package/src/badge/repository-details-fetcher.test.js +33 -0
- package/src/badge/scaffolder.js +21 -0
- package/src/badge/scaffolder.test.js +53 -0
- package/src/config/index.js +1 -0
- package/src/config/scaffolder.js +10 -0
- package/src/config/scaffolder.test.js +23 -0
- package/src/index.js +4 -0
- package/src/lifter.js +15 -0
- package/src/lifter.test.js +33 -0
- package/src/predicates.js +3 -0
- package/src/predicates.test.js +31 -0
- package/src/remover.js +9 -0
- package/src/remover.test.js +27 -0
- package/src/reporter/ci-providers/github-workflows/action/constants.js +1 -0
- package/src/reporter/ci-providers/github-workflows/action/index.js +4 -0
- package/src/reporter/ci-providers/github-workflows/action/lifter.js +12 -0
- package/src/reporter/ci-providers/github-workflows/action/lifter.test.js +21 -0
- package/src/reporter/ci-providers/github-workflows/action/remover.js +6 -0
- package/src/reporter/ci-providers/github-workflows/action/remover.test.js +22 -0
- package/src/reporter/ci-providers/github-workflows/action/scaffolder.js +14 -0
- package/src/reporter/ci-providers/github-workflows/action/scaffolder.test.js +19 -0
- package/src/reporter/ci-providers/github-workflows/action/tester.js +9 -0
- package/src/reporter/ci-providers/github-workflows/action/tester.test.js +23 -0
- package/src/reporter/ci-providers/github-workflows/index.js +3 -0
- package/src/reporter/ci-providers/github-workflows/lifter.js +25 -0
- package/src/reporter/ci-providers/github-workflows/lifter.test.js +47 -0
- package/src/reporter/ci-providers/github-workflows/predicate.js +5 -0
- package/src/reporter/ci-providers/github-workflows/predicate.test.js +20 -0
- package/src/reporter/ci-providers/github-workflows/remover.js +9 -0
- package/src/reporter/ci-providers/github-workflows/remover.test.js +30 -0
- package/src/reporter/ci-providers/github-workflows/steps/index.js +2 -0
- package/src/reporter/ci-providers/github-workflows/steps/lifter.js +16 -0
- package/src/reporter/ci-providers/github-workflows/steps/lifter.test.js +48 -0
- package/src/reporter/ci-providers/github-workflows/steps/tester.js +5 -0
- package/src/reporter/ci-providers/github-workflows/steps/tester.test.js +25 -0
- package/src/reporter/ci-providers/index.js +1 -0
- package/src/reporter/index.js +1 -0
- package/src/reporter/lifter.js +38 -0
- package/src/reporter/lifter.test.js +70 -0
- package/src/scaffolder.js +7 -0
- package/src/scaffolder.test.js +18 -0
- package/src/tester.js +5 -0
- package/src/tester.test.js +25 -0
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@form8ion/codecov",
|
|
3
3
|
"description": "code coverage service plugin for form8ion",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "7.6.
|
|
5
|
+
"version": "7.6.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": "^20.19.0 || >=22.14.0"
|
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"example.js",
|
|
50
|
-
"lib/"
|
|
50
|
+
"lib/",
|
|
51
|
+
"src/"
|
|
51
52
|
],
|
|
52
53
|
"publishConfig": {
|
|
53
54
|
"access": "public",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {scaffold} from './scaffolder.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import got from 'got';
|
|
2
|
+
|
|
3
|
+
export default async function fetchRepositoryDetails({vcs, apiAccessToken}) {
|
|
4
|
+
const {body: {repo}} = await got(
|
|
5
|
+
`https://codecov.io/api/gh/${vcs.owner}/${vcs.name}`,
|
|
6
|
+
{headers: {Authorization: apiAccessToken}, responseType: 'json'}
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
return repo;
|
|
10
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import got from 'got';
|
|
2
|
+
|
|
3
|
+
import {expect, describe, it, vi} from 'vitest';
|
|
4
|
+
import {when} from 'vitest-when';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import fetchRepositoryDetails from './repository-details-fetcher.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('got');
|
|
10
|
+
|
|
11
|
+
describe('repository-details-fetcher', () => {
|
|
12
|
+
it('should fetch repository details from the codecov api', async () => {
|
|
13
|
+
const apiAccessToken = any.string();
|
|
14
|
+
const vcsHost = any.word();
|
|
15
|
+
const vcsOwner = any.word();
|
|
16
|
+
const vcsName = any.word();
|
|
17
|
+
const vcs = {
|
|
18
|
+
...any.simpleObject(),
|
|
19
|
+
host: vcsHost,
|
|
20
|
+
owner: vcsOwner,
|
|
21
|
+
name: vcsName
|
|
22
|
+
};
|
|
23
|
+
const repoDetails = any.simpleObject();
|
|
24
|
+
when(got)
|
|
25
|
+
.calledWith(
|
|
26
|
+
`https://codecov.io/api/gh/${vcsOwner}/${vcsName}`,
|
|
27
|
+
{headers: {Authorization: apiAccessToken}, responseType: 'json'}
|
|
28
|
+
)
|
|
29
|
+
.thenResolve({body: {repo: repoDetails}});
|
|
30
|
+
|
|
31
|
+
expect(await fetchRepositoryDetails({vcs, apiAccessToken})).toEqual(repoDetails);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fetchRepositoryDetails from './repository-details-fetcher.js';
|
|
2
|
+
|
|
3
|
+
export async function scaffold({vcs, apiAccessToken}) {
|
|
4
|
+
return {
|
|
5
|
+
...['github', 'gitlab', 'bitbucket'].includes(vcs?.host) && {
|
|
6
|
+
badges: {
|
|
7
|
+
status: {
|
|
8
|
+
coverage: {
|
|
9
|
+
img: `https://img.shields.io/codecov/c/${vcs.host}/${vcs.owner}/${vcs.name}?logo=codecov${
|
|
10
|
+
apiAccessToken
|
|
11
|
+
? `&token=${(await fetchRepositoryDetails({vcs, apiAccessToken})).image_token}`
|
|
12
|
+
: ''
|
|
13
|
+
}`,
|
|
14
|
+
link: `https://codecov.io/${vcs.host}/${vcs.owner}/${vcs.name}`,
|
|
15
|
+
text: 'Codecov'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import {it, vi, describe, expect} from 'vitest';
|
|
2
|
+
import {when} from 'vitest-when';
|
|
3
|
+
import any from '@travi/any';
|
|
4
|
+
|
|
5
|
+
import fetchRepositoryDetails from './repository-details-fetcher.js';
|
|
6
|
+
import {scaffold} from './scaffolder.js';
|
|
7
|
+
|
|
8
|
+
vi.mock('./repository-details-fetcher.js');
|
|
9
|
+
|
|
10
|
+
describe('badge scaffolder', () => {
|
|
11
|
+
const vcsHost = any.fromList(['github', 'gitlab', 'bitbucket']);
|
|
12
|
+
const vcsOwner = any.word();
|
|
13
|
+
const vcsName = any.word();
|
|
14
|
+
const vcs = {
|
|
15
|
+
...any.simpleObject(),
|
|
16
|
+
host: vcsHost,
|
|
17
|
+
owner: vcsOwner,
|
|
18
|
+
name: vcsName
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
it('should scaffold badge details for supported vcs hosts', async () => {
|
|
22
|
+
const {badges} = await scaffold({vcs});
|
|
23
|
+
|
|
24
|
+
expect(badges.status.coverage).toEqual({
|
|
25
|
+
img: `https://img.shields.io/codecov/c/${vcsHost}/${vcsOwner}/${vcsName}?logo=codecov`,
|
|
26
|
+
link: `https://codecov.io/${vcsHost}/${vcsOwner}/${vcsName}`,
|
|
27
|
+
text: 'Codecov'
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should include the image token in the img url', async () => {
|
|
32
|
+
const apiAccessToken = any.string();
|
|
33
|
+
const token = any.word();
|
|
34
|
+
when(fetchRepositoryDetails).calledWith({vcs, apiAccessToken}).thenResolve({image_token: token});
|
|
35
|
+
|
|
36
|
+
const {badges} = await scaffold({vcs, apiAccessToken});
|
|
37
|
+
|
|
38
|
+
expect(badges.status.coverage.img)
|
|
39
|
+
.toEqual(`https://img.shields.io/codecov/c/${vcsHost}/${vcsOwner}/${vcsName}?logo=codecov&token=${token}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should not define the badge if shields.io badge does not support the host', async () => {
|
|
43
|
+
const {badges} = await scaffold({vcs: {host: any.word()}});
|
|
44
|
+
|
|
45
|
+
expect(badges).toBe(undefined);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should not define the badge if vcs details are not defined', async () => {
|
|
49
|
+
const {badges} = await scaffold({});
|
|
50
|
+
|
|
51
|
+
expect(badges).toBe(undefined);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default as scaffold} from './scaffolder.js';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import {fileTypes, writeConfigFile} from '@form8ion/core';
|
|
2
|
+
|
|
3
|
+
export default async function scaffoldConfig({projectRoot}) {
|
|
4
|
+
await writeConfigFile({
|
|
5
|
+
format: fileTypes.YAML,
|
|
6
|
+
path: projectRoot,
|
|
7
|
+
name: '.codecov',
|
|
8
|
+
config: {comment: {layout: 'reach,diff,flags,tree'}}
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {fileTypes, writeConfigFile} from '@form8ion/core';
|
|
2
|
+
|
|
3
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
4
|
+
import any from '@travi/any';
|
|
5
|
+
|
|
6
|
+
import scaffoldConfig from './scaffolder.js';
|
|
7
|
+
|
|
8
|
+
vi.mock('@form8ion/core');
|
|
9
|
+
|
|
10
|
+
describe('config scaffolder', () => {
|
|
11
|
+
it('should create the config file', async () => {
|
|
12
|
+
const projectRoot = any.string();
|
|
13
|
+
|
|
14
|
+
await scaffoldConfig({projectRoot});
|
|
15
|
+
|
|
16
|
+
expect(writeConfigFile).toHaveBeenCalledWith({
|
|
17
|
+
format: fileTypes.YAML,
|
|
18
|
+
path: projectRoot,
|
|
19
|
+
name: '.codecov',
|
|
20
|
+
config: {comment: {layout: 'reach,diff,flags,tree'}}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
});
|
package/src/index.js
ADDED
package/src/lifter.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import deepmerge from 'deepmerge';
|
|
2
|
+
|
|
3
|
+
import {scaffold as scaffoldConfig} from './config/index.js';
|
|
4
|
+
import {scaffold as scaffoldBadge} from './badge/index.js';
|
|
5
|
+
import {lift as liftReporting} from './reporter/index.js';
|
|
6
|
+
|
|
7
|
+
export async function lift({projectRoot, packageManager, vcs}) {
|
|
8
|
+
const [reportingResults, badgeResults] = await Promise.all([
|
|
9
|
+
liftReporting({projectRoot, packageManager}),
|
|
10
|
+
scaffoldBadge({vcs}),
|
|
11
|
+
scaffoldConfig({projectRoot})
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
return deepmerge.all([reportingResults, badgeResults]);
|
|
15
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import deepmerge from 'deepmerge';
|
|
2
|
+
|
|
3
|
+
import {expect, it, vi, describe} from 'vitest';
|
|
4
|
+
import {when} from 'vitest-when';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import {scaffold as scaffoldConfig} from './config/index.js';
|
|
8
|
+
import {scaffold as scaffoldBadge} from './badge/index.js';
|
|
9
|
+
import {lift as liftReporting} from './reporter/index.js';
|
|
10
|
+
import {lift} from './lifter.js';
|
|
11
|
+
|
|
12
|
+
vi.mock('deepmerge');
|
|
13
|
+
vi.mock('./config/index.js');
|
|
14
|
+
vi.mock('./badge/index.js');
|
|
15
|
+
vi.mock('./reporter/index.js');
|
|
16
|
+
|
|
17
|
+
describe('lifter', () => {
|
|
18
|
+
it('should lift the reporting process', async () => {
|
|
19
|
+
const projectRoot = any.string();
|
|
20
|
+
const packageManager = any.word();
|
|
21
|
+
const vcs = any.simpleObject();
|
|
22
|
+
const reportingResults = any.simpleObject();
|
|
23
|
+
const badgeResults = any.simpleObject();
|
|
24
|
+
const mergedResults = any.simpleObject();
|
|
25
|
+
when(liftReporting).calledWith({projectRoot, packageManager}).thenResolve(reportingResults);
|
|
26
|
+
when(scaffoldBadge).calledWith({vcs}).thenResolve(badgeResults);
|
|
27
|
+
when(deepmerge.all).calledWith([reportingResults, badgeResults]).thenReturn(mergedResults);
|
|
28
|
+
|
|
29
|
+
expect(await lift({projectRoot, packageManager, vcs})).toEqual(mergedResults);
|
|
30
|
+
|
|
31
|
+
expect(scaffoldConfig).toHaveBeenCalledWith({projectRoot});
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import {expect, it, describe} from 'vitest';
|
|
2
|
+
import any from '@travi/any';
|
|
3
|
+
|
|
4
|
+
import {coverageShouldBeReportedToCodecov} from './predicates.js';
|
|
5
|
+
|
|
6
|
+
describe('predicates', () => {
|
|
7
|
+
it('should determine that coverage should be reported when the project is public', () => {
|
|
8
|
+
expect(coverageShouldBeReportedToCodecov({visibility: 'Public'})).toBe(true);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it(
|
|
12
|
+
'should determine that coverage should be reported when an API token is provided and GitHub is the VCS host',
|
|
13
|
+
async () => {
|
|
14
|
+
expect(coverageShouldBeReportedToCodecov({apiAccessToken: any.word(), vcs: {host: 'github'}})).toBe(true);
|
|
15
|
+
}
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
it(
|
|
19
|
+
'should determine that coverage should not be reported when the project is not public and no API token is provided',
|
|
20
|
+
async () => {
|
|
21
|
+
expect(coverageShouldBeReportedToCodecov({})).toBe(false);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
it(
|
|
26
|
+
"should determine that coverage should not be reported when an API token is provided but the VCS host isn't GitHub",
|
|
27
|
+
async () => {
|
|
28
|
+
expect(coverageShouldBeReportedToCodecov({apiAccessToken: any.word(), vcs: {host: any.word()}})).toBe(false);
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
});
|
package/src/remover.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {remove as removeAction, test as githubWorkflowExists} from './reporter/ci-providers/github-workflows/index.js';
|
|
2
|
+
|
|
3
|
+
export default async function removeCodecov({projectRoot}) {
|
|
4
|
+
if (await githubWorkflowExists({projectRoot})) {
|
|
5
|
+
await removeAction({projectRoot});
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return {};
|
|
9
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
2
|
+
import {when} from 'vitest-when';
|
|
3
|
+
import any from '@travi/any';
|
|
4
|
+
|
|
5
|
+
import {remove as removeAction, test as githubWorkflowExists} from './reporter/ci-providers/github-workflows/index.js';
|
|
6
|
+
import remove from './remover.js';
|
|
7
|
+
|
|
8
|
+
vi.mock(('./reporter/ci-providers/github-workflows/index.js'));
|
|
9
|
+
|
|
10
|
+
describe('remover', () => {
|
|
11
|
+
const projectRoot = any.string();
|
|
12
|
+
|
|
13
|
+
it('should remove the various parts', async () => {
|
|
14
|
+
when(githubWorkflowExists).calledWith({projectRoot}).thenResolve(true);
|
|
15
|
+
|
|
16
|
+
expect(await remove({projectRoot})).toEqual({});
|
|
17
|
+
expect(removeAction).toHaveBeenCalledWith({projectRoot});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should not remove the action if there is no GitHub Actions workflow', async () => {
|
|
21
|
+
when(githubWorkflowExists).calledWith({projectRoot}).thenResolve(false);
|
|
22
|
+
|
|
23
|
+
await remove({projectRoot});
|
|
24
|
+
|
|
25
|
+
expect(removeAction).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ACTION_NAME = 'codecov/codecov-action';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export default function liftAction(action) {
|
|
2
|
+
return {
|
|
3
|
+
name: 'Upload unit test coverage to Codecov',
|
|
4
|
+
...action,
|
|
5
|
+
with: {
|
|
6
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
7
|
+
token: '${{ secrets.CODECOV_TOKEN }}',
|
|
8
|
+
flags: 'unit',
|
|
9
|
+
report_type: 'coverage'
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import {describe, it, expect} from 'vitest';
|
|
2
|
+
import any from '@travi/any';
|
|
3
|
+
|
|
4
|
+
import liftAction from './lifter.js';
|
|
5
|
+
|
|
6
|
+
describe('action lifter', () => {
|
|
7
|
+
it('should expose the token secret to the action', async () => {
|
|
8
|
+
const existingAction = any.simpleObject();
|
|
9
|
+
|
|
10
|
+
expect(liftAction(existingAction)).toEqual({
|
|
11
|
+
name: 'Upload unit test coverage to Codecov',
|
|
12
|
+
...existingAction,
|
|
13
|
+
with: {
|
|
14
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
15
|
+
token: '${{ secrets.CODECOV_TOKEN }}',
|
|
16
|
+
flags: 'unit',
|
|
17
|
+
report_type: 'coverage'
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {removeActionFromJobs} from '@form8ion/github-workflows-core';
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
4
|
+
import {when} from 'vitest-when';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import removeCodecovActionFrom from './remover.js';
|
|
8
|
+
import {ACTION_NAME} from './constants.js';
|
|
9
|
+
|
|
10
|
+
vi.mock('@form8ion/github-workflows-core');
|
|
11
|
+
|
|
12
|
+
describe('codecov action', () => {
|
|
13
|
+
describe('remove from jobs', () => {
|
|
14
|
+
it('should remove the codecov action from the provided jobs', async () => {
|
|
15
|
+
const jobs = any.listOf(any.simpleObject);
|
|
16
|
+
const updatedJobs = any.listOf(any.simpleObject);
|
|
17
|
+
when(removeActionFromJobs).calledWith(jobs, ACTION_NAME).thenReturn(updatedJobs);
|
|
18
|
+
|
|
19
|
+
expect(removeCodecovActionFrom(jobs)).toEqual(updatedJobs);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {ACTION_NAME} from './constants.js';
|
|
2
|
+
|
|
3
|
+
export default function scaffoldAction() {
|
|
4
|
+
return {
|
|
5
|
+
name: 'Upload unit test coverage to Codecov',
|
|
6
|
+
uses: `${ACTION_NAME}@v5.5.2`,
|
|
7
|
+
with: {
|
|
8
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
9
|
+
token: '${{ secrets.CODECOV_TOKEN }}',
|
|
10
|
+
flags: 'unit',
|
|
11
|
+
report_type: 'coverage'
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {describe, it, expect} from 'vitest';
|
|
2
|
+
|
|
3
|
+
import scaffoldAction from './scaffolder.js';
|
|
4
|
+
import {ACTION_NAME} from './constants.js';
|
|
5
|
+
|
|
6
|
+
describe('codecov action scaffolder', () => {
|
|
7
|
+
it('should scaffold the codecov action', async () => {
|
|
8
|
+
expect(scaffoldAction()).toEqual({
|
|
9
|
+
name: 'Upload unit test coverage to Codecov',
|
|
10
|
+
uses: `${ACTION_NAME}@v5.5.2`,
|
|
11
|
+
with: {
|
|
12
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
13
|
+
token: '${{ secrets.CODECOV_TOKEN }}',
|
|
14
|
+
flags: 'unit',
|
|
15
|
+
report_type: 'coverage'
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import {describe, it, expect} from 'vitest';
|
|
2
|
+
import any from '@travi/any';
|
|
3
|
+
|
|
4
|
+
import stepIsCodecovAction from './tester.js';
|
|
5
|
+
import {ACTION_NAME} from './constants.js';
|
|
6
|
+
|
|
7
|
+
describe('action tester', () => {
|
|
8
|
+
it('should return `false` if the step does not use an action', async () => {
|
|
9
|
+
expect(stepIsCodecovAction(any.simpleObject())).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should return `false` if the step does not use the CodeCov action', async () => {
|
|
13
|
+
const otherAction = {...any.simpleObject(), uses: 'something-else@v1.2.3'};
|
|
14
|
+
|
|
15
|
+
expect(stepIsCodecovAction(otherAction)).toBe(false);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should return `true` if the step uses the CodeCov action', async () => {
|
|
19
|
+
const codecovAction = {...any.simpleObject(), uses: `${ACTION_NAME}@v5.5.2`};
|
|
20
|
+
|
|
21
|
+
expect(stepIsCodecovAction(codecovAction)).toBe(true);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {loadWorkflowFile, writeWorkflowFile} from '@form8ion/github-workflows-core';
|
|
2
|
+
|
|
3
|
+
import {lift as liftSteps} from './steps/index.js';
|
|
4
|
+
|
|
5
|
+
export async function lift({projectRoot}) {
|
|
6
|
+
const ciWorkflowName = 'node-ci';
|
|
7
|
+
|
|
8
|
+
const workflowDetails = await loadWorkflowFile({projectRoot, name: ciWorkflowName});
|
|
9
|
+
const {jobs: {verify: {steps}}} = workflowDetails;
|
|
10
|
+
|
|
11
|
+
await writeWorkflowFile({
|
|
12
|
+
projectRoot,
|
|
13
|
+
name: ciWorkflowName,
|
|
14
|
+
config: {
|
|
15
|
+
...workflowDetails,
|
|
16
|
+
jobs: {
|
|
17
|
+
...workflowDetails.jobs,
|
|
18
|
+
verify: {
|
|
19
|
+
...workflowDetails.jobs.verify,
|
|
20
|
+
steps: liftSteps(steps)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import {loadWorkflowFile, writeWorkflowFile} from '@form8ion/github-workflows-core';
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
4
|
+
import {when} from 'vitest-when';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import {lift as liftSteps} from './steps/index.js';
|
|
8
|
+
import {lift as configureGithubWorkflow} from './lifter.js';
|
|
9
|
+
|
|
10
|
+
vi.mock('@form8ion/github-workflows-core');
|
|
11
|
+
vi.mock('./steps/index.js');
|
|
12
|
+
|
|
13
|
+
describe('github workflow lifter', () => {
|
|
14
|
+
const projectRoot = any.string();
|
|
15
|
+
const ciWorkflowName = 'node-ci';
|
|
16
|
+
const liftedSteps = any.listOf(any.simpleObject);
|
|
17
|
+
|
|
18
|
+
it('should add the codecov action to the verify job', async () => {
|
|
19
|
+
const otherTopLevelProperties = any.simpleObject();
|
|
20
|
+
const otherJobs = any.simpleObject();
|
|
21
|
+
const otherVerifyProperties = any.simpleObject();
|
|
22
|
+
const existingVerifySteps = any.listOf(any.simpleObject);
|
|
23
|
+
const existingWorkflowContents = {
|
|
24
|
+
...otherTopLevelProperties,
|
|
25
|
+
jobs: {...otherJobs, verify: {...otherVerifyProperties, steps: existingVerifySteps}}
|
|
26
|
+
};
|
|
27
|
+
when(loadWorkflowFile).calledWith({projectRoot, name: ciWorkflowName}).thenResolve(existingWorkflowContents);
|
|
28
|
+
when(liftSteps).calledWith(existingVerifySteps).thenReturn(liftedSteps);
|
|
29
|
+
|
|
30
|
+
await configureGithubWorkflow({projectRoot});
|
|
31
|
+
|
|
32
|
+
expect(writeWorkflowFile).toHaveBeenCalledWith({
|
|
33
|
+
projectRoot,
|
|
34
|
+
name: ciWorkflowName,
|
|
35
|
+
config: {
|
|
36
|
+
...otherTopLevelProperties,
|
|
37
|
+
jobs: {
|
|
38
|
+
...otherJobs,
|
|
39
|
+
verify: {
|
|
40
|
+
...otherVerifyProperties,
|
|
41
|
+
steps: liftedSteps
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {workflowFileExists} from '@form8ion/github-workflows-core';
|
|
2
|
+
|
|
3
|
+
import {expect, describe, it, vi} from 'vitest';
|
|
4
|
+
import {when} from 'vitest-when';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import ciWorkflowExists from './predicate.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('@form8ion/github-workflows-core');
|
|
10
|
+
|
|
11
|
+
describe('github workflows predicate', () => {
|
|
12
|
+
const projectRoot = any.string();
|
|
13
|
+
|
|
14
|
+
it('should determine whether the verification workflow exists', async () => {
|
|
15
|
+
const workflowExists = any.boolean();
|
|
16
|
+
when(workflowFileExists).calledWith({projectRoot, name: 'node-ci'}).thenResolve(workflowExists);
|
|
17
|
+
|
|
18
|
+
expect(await ciWorkflowExists({projectRoot})).toEqual(workflowExists);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import {loadWorkflowFile, writeWorkflowFile} from '@form8ion/github-workflows-core';
|
|
2
|
+
import {remove as removeCodecovActionFrom} from './action/index.js';
|
|
3
|
+
|
|
4
|
+
export default async function removeCodecovAction({projectRoot}) {
|
|
5
|
+
const existingConfig = await loadWorkflowFile({projectRoot, name: 'node-ci'});
|
|
6
|
+
existingConfig.jobs = removeCodecovActionFrom(existingConfig.jobs);
|
|
7
|
+
|
|
8
|
+
await writeWorkflowFile({projectRoot, name: 'node-ci', config: existingConfig});
|
|
9
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {loadWorkflowFile, writeWorkflowFile} from '@form8ion/github-workflows-core';
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
4
|
+
import {when} from 'vitest-when';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import {remove as removeCodecovActionFrom} from './action/index.js';
|
|
8
|
+
import remove from './remover.js';
|
|
9
|
+
|
|
10
|
+
vi.mock('@form8ion/github-workflows-core');
|
|
11
|
+
vi.mock('./action/index.js');
|
|
12
|
+
|
|
13
|
+
describe('action remover', () => {
|
|
14
|
+
it('should remove the action from the workflow', async () => {
|
|
15
|
+
const projectRoot = any.string();
|
|
16
|
+
const updatedJobs = any.simpleObject();
|
|
17
|
+
const ciWorkflowName = 'node-ci';
|
|
18
|
+
const existingWorkflowDefinition = {...any.simpleObject(), jobs: any.simpleObject()};
|
|
19
|
+
when(loadWorkflowFile).calledWith({projectRoot, name: ciWorkflowName}).thenResolve(existingWorkflowDefinition);
|
|
20
|
+
when(removeCodecovActionFrom).calledWith(existingWorkflowDefinition.jobs).thenReturn(updatedJobs);
|
|
21
|
+
|
|
22
|
+
await remove({projectRoot});
|
|
23
|
+
|
|
24
|
+
expect(writeWorkflowFile).toHaveBeenCalledWith({
|
|
25
|
+
projectRoot,
|
|
26
|
+
name: ciWorkflowName,
|
|
27
|
+
config: {...existingWorkflowDefinition, jobs: updatedJobs}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {scaffold as scaffoldAction, lift as liftAction, test as stepIsCodecovAction} from '../action/index.js';
|
|
2
|
+
import codecovActionExistsInSteps from './tester.js';
|
|
3
|
+
|
|
4
|
+
export default function liftSteps(steps) {
|
|
5
|
+
if (!codecovActionExistsInSteps(steps)) {
|
|
6
|
+
const stepsWithLegacyReportingRemoved = steps.filter(({run}) => 'npm run coverage:report' !== run);
|
|
7
|
+
|
|
8
|
+
return [...stepsWithLegacyReportingRemoved, scaffoldAction()];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return steps.map(step => {
|
|
12
|
+
if (stepIsCodecovAction(step)) return liftAction(step);
|
|
13
|
+
|
|
14
|
+
return step;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import {describe, it, expect, vi} from 'vitest';
|
|
2
|
+
import any from '@travi/any';
|
|
3
|
+
import {when} from 'vitest-when';
|
|
4
|
+
|
|
5
|
+
import {scaffold as scaffoldAction, lift as liftAction, test as stepIsCodecovAction} from '../action/index.js';
|
|
6
|
+
import codecovActionExistsInSteps from './tester.js';
|
|
7
|
+
import liftSteps from './lifter.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('../action/index.js');
|
|
10
|
+
vi.mock('./tester.js');
|
|
11
|
+
|
|
12
|
+
describe('steps lifter', () => {
|
|
13
|
+
const codecovAction = any.simpleObject();
|
|
14
|
+
|
|
15
|
+
it('should append the CodeCov step if it doesnt already exist', async () => {
|
|
16
|
+
const steps = any.listOf(any.simpleObject);
|
|
17
|
+
when(codecovActionExistsInSteps).calledWith(steps).thenReturn(false);
|
|
18
|
+
when(scaffoldAction).calledWith().thenReturn(codecovAction);
|
|
19
|
+
|
|
20
|
+
expect(liftSteps(steps)).toEqual([...steps, codecovAction]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should remove the legacy reporting step, if present', async () => {
|
|
24
|
+
const stepsBeforeLegacy = any.listOf(any.simpleObject);
|
|
25
|
+
const stepsAfterLegacy = any.listOf(any.simpleObject);
|
|
26
|
+
const steps = [
|
|
27
|
+
...stepsBeforeLegacy,
|
|
28
|
+
{...any.simpleObject(), run: 'npm run coverage:report'},
|
|
29
|
+
...stepsAfterLegacy
|
|
30
|
+
];
|
|
31
|
+
when(codecovActionExistsInSteps).calledWith(steps).thenReturn(false);
|
|
32
|
+
when(scaffoldAction).calledWith().thenReturn(codecovAction);
|
|
33
|
+
|
|
34
|
+
expect(liftSteps(steps)).toEqual([...stepsBeforeLegacy, ...stepsAfterLegacy, codecovAction]);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should lift the existing action, if present', async () => {
|
|
38
|
+
const stepsBeforeExisting = any.listOf(any.simpleObject);
|
|
39
|
+
const stepsAfterExisting = any.listOf(any.simpleObject);
|
|
40
|
+
const existingAction = any.simpleObject();
|
|
41
|
+
const steps = [...stepsBeforeExisting, existingAction, ...stepsAfterExisting];
|
|
42
|
+
when(codecovActionExistsInSteps).calledWith(steps).thenReturn(true);
|
|
43
|
+
when(stepIsCodecovAction).calledWith(existingAction).thenReturn(true);
|
|
44
|
+
when(liftAction).calledWith(existingAction).thenReturn(codecovAction);
|
|
45
|
+
|
|
46
|
+
expect(liftSteps(steps)).toEqual([...stepsBeforeExisting, codecovAction, ...stepsAfterExisting]);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
2
|
+
import {when} from 'vitest-when';
|
|
3
|
+
import any from '@travi/any';
|
|
4
|
+
|
|
5
|
+
import {test as stepIsCodecovAction} from '../action/index.js';
|
|
6
|
+
import codecovActionExistsInSteps from './tester.js';
|
|
7
|
+
|
|
8
|
+
vi.mock('../action/index.js');
|
|
9
|
+
|
|
10
|
+
describe('codecov action tester', () => {
|
|
11
|
+
it('should return `true` if the codecov action is found in the list of provided steps', async () => {
|
|
12
|
+
const codecovAction = any.simpleObject();
|
|
13
|
+
when(stepIsCodecovAction).calledWith(codecovAction).thenReturn(true);
|
|
14
|
+
|
|
15
|
+
expect(codecovActionExistsInSteps([
|
|
16
|
+
...any.listOf(any.simpleObject),
|
|
17
|
+
codecovAction,
|
|
18
|
+
...any.listOf(any.simpleObject)
|
|
19
|
+
])).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('should return `false` if no codecov action is found in the list of provided steps', async () => {
|
|
23
|
+
expect(codecovActionExistsInSteps(any.listOf(any.simpleObject))).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {lift, test} from './github-workflows/index.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {default as lift} from './lifter.js';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import {promises as fs} from 'node:fs';
|
|
2
|
+
import {writePackageJson} from '@form8ion/javascript-core';
|
|
3
|
+
|
|
4
|
+
import {execa} from 'execa';
|
|
5
|
+
import {lift as liftCiProvider, test as ciProviderIsLiftable} from './ci-providers/index.js';
|
|
6
|
+
|
|
7
|
+
export default async function liftReporter({projectRoot, packageManager}) {
|
|
8
|
+
const pathToPackageJson = `${projectRoot}/package.json`;
|
|
9
|
+
|
|
10
|
+
const [ciProviderCanBeLifted, existingPackageContents] = await Promise.all([
|
|
11
|
+
ciProviderIsLiftable({projectRoot}),
|
|
12
|
+
fs.readFile(pathToPackageJson, 'utf-8')
|
|
13
|
+
]);
|
|
14
|
+
const parsedPackageContents = JSON.parse(existingPackageContents);
|
|
15
|
+
const {scripts} = parsedPackageContents;
|
|
16
|
+
const {'coverage:report': reportCoverageScript, ...otherScripts} = scripts;
|
|
17
|
+
|
|
18
|
+
if (ciProviderCanBeLifted) await liftCiProvider({projectRoot});
|
|
19
|
+
|
|
20
|
+
if (scripts['coverage:report']) {
|
|
21
|
+
parsedPackageContents.scripts = otherScripts;
|
|
22
|
+
await writePackageJson({projectRoot, config: parsedPackageContents});
|
|
23
|
+
|
|
24
|
+
await execa(packageManager, ['remove', 'codecov']);
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...!ciProviderCanBeLifted && {
|
|
28
|
+
nextSteps: [{
|
|
29
|
+
summary: 'Configure modern reporting to Codecov on your CI service',
|
|
30
|
+
description: 'Configure the [Codecov Uploader](https://docs.codecov.com/docs/codecov-uploader) appropriately'
|
|
31
|
+
+ ' for your CI Provider. If available for your provider, prefer one of the dedicated wrappers.'
|
|
32
|
+
}]
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {promises as fs} from 'node:fs';
|
|
2
|
+
import {execa} from 'execa';
|
|
3
|
+
import {writePackageJson} from '@form8ion/javascript-core';
|
|
4
|
+
|
|
5
|
+
import {it, describe, vi, expect} from 'vitest';
|
|
6
|
+
import {when} from 'vitest-when';
|
|
7
|
+
import any from '@travi/any';
|
|
8
|
+
|
|
9
|
+
import {lift as liftCiProvider, test as ciProviderIsLiftable} from './ci-providers/index.js';
|
|
10
|
+
import liftReporting from './lifter.js';
|
|
11
|
+
|
|
12
|
+
vi.mock('node:fs');
|
|
13
|
+
vi.mock('execa');
|
|
14
|
+
vi.mock('@form8ion/javascript-core');
|
|
15
|
+
vi.mock('./ci-providers/index.js');
|
|
16
|
+
|
|
17
|
+
describe('reporting lifter', () => {
|
|
18
|
+
const projectRoot = any.string();
|
|
19
|
+
const packageManager = any.word();
|
|
20
|
+
const pathToPackageJson = `${projectRoot}/package.json`;
|
|
21
|
+
|
|
22
|
+
it('should remove the legacy node reporter', async () => {
|
|
23
|
+
const otherScripts = any.simpleObject();
|
|
24
|
+
const otherTopLevelProperties = any.simpleObject();
|
|
25
|
+
const existingPackageContents = {
|
|
26
|
+
...otherTopLevelProperties,
|
|
27
|
+
scripts: {...otherScripts, 'coverage:report': any.string()}
|
|
28
|
+
};
|
|
29
|
+
when(fs.readFile).calledWith(pathToPackageJson, 'utf-8').thenResolve(JSON.stringify(existingPackageContents));
|
|
30
|
+
when(ciProviderIsLiftable).calledWith({projectRoot}).thenResolve(false);
|
|
31
|
+
|
|
32
|
+
const {nextSteps} = await liftReporting({projectRoot, packageManager});
|
|
33
|
+
|
|
34
|
+
expect(nextSteps).toEqual([{
|
|
35
|
+
summary: 'Configure modern reporting to Codecov on your CI service',
|
|
36
|
+
description: 'Configure the [Codecov Uploader](https://docs.codecov.com/docs/codecov-uploader) appropriately'
|
|
37
|
+
+ ' for your CI Provider. If available for your provider, prefer one of the dedicated wrappers.'
|
|
38
|
+
}]);
|
|
39
|
+
|
|
40
|
+
expect(execa).toHaveBeenCalledWith(packageManager, ['remove', 'codecov']);
|
|
41
|
+
expect(writePackageJson).toHaveBeenCalledWith({
|
|
42
|
+
projectRoot,
|
|
43
|
+
config: {...otherTopLevelProperties, scripts: otherScripts}
|
|
44
|
+
});
|
|
45
|
+
expect(liftCiProvider).not.toHaveBeenCalled();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should lift the ci provider when supported', async () => {
|
|
49
|
+
when(fs.readFile)
|
|
50
|
+
.calledWith(pathToPackageJson, 'utf-8')
|
|
51
|
+
.thenResolve(JSON.stringify({
|
|
52
|
+
...any.simpleObject(),
|
|
53
|
+
scripts: {...any.simpleObject(), 'coverage:report': any.string()}
|
|
54
|
+
}));
|
|
55
|
+
when(ciProviderIsLiftable).calledWith({projectRoot}).thenResolve(true);
|
|
56
|
+
|
|
57
|
+
const {nextSteps} = await liftReporting({projectRoot, packageManager});
|
|
58
|
+
|
|
59
|
+
expect(liftCiProvider).toHaveBeenCalledWith({projectRoot});
|
|
60
|
+
expect(nextSteps).toBeUndefined();
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should not update the `package.json` if it did not contain a `coverage:report` script', async () => {
|
|
64
|
+
const existingPackageContents = {...any.simpleObject(), scripts: any.simpleObject()};
|
|
65
|
+
when(fs.readFile).calledWith(pathToPackageJson, 'utf-8').thenResolve(JSON.stringify(existingPackageContents));
|
|
66
|
+
|
|
67
|
+
expect(await liftReporting({projectRoot})).toEqual({});
|
|
68
|
+
expect(writePackageJson).not.toHaveBeenCalled();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
2
|
+
import any from '@travi/any';
|
|
3
|
+
|
|
4
|
+
import {scaffold as scaffoldConfig} from './config/index.js';
|
|
5
|
+
import {scaffold} from './scaffolder.js';
|
|
6
|
+
|
|
7
|
+
vi.mock('./config/index.js');
|
|
8
|
+
|
|
9
|
+
describe('scaffolder', () => {
|
|
10
|
+
it('should simply return empty results', async () => {
|
|
11
|
+
const projectRoot = any.string();
|
|
12
|
+
|
|
13
|
+
const results = await scaffold({projectRoot});
|
|
14
|
+
|
|
15
|
+
expect(results).toEqual({});
|
|
16
|
+
expect(scaffoldConfig).toHaveBeenCalledWith({projectRoot});
|
|
17
|
+
});
|
|
18
|
+
});
|
package/src/tester.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {fileExists} from '@form8ion/core';
|
|
2
|
+
|
|
3
|
+
import {describe, expect, it, vi} from 'vitest';
|
|
4
|
+
import {when} from 'vitest-when';
|
|
5
|
+
import any from '@travi/any';
|
|
6
|
+
|
|
7
|
+
import codecovInUse from './tester.js';
|
|
8
|
+
|
|
9
|
+
vi.mock('@form8ion/core');
|
|
10
|
+
|
|
11
|
+
describe('codecov predicate', () => {
|
|
12
|
+
const projectRoot = any.string();
|
|
13
|
+
|
|
14
|
+
it('should return `true` if the config file exists', async () => {
|
|
15
|
+
when(fileExists).calledWith(`${projectRoot}/.codecov.yml`).thenResolve(true);
|
|
16
|
+
|
|
17
|
+
expect(await codecovInUse({projectRoot})).toBe(true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should return `false` if the config file exists', async () => {
|
|
21
|
+
when(fileExists).calledWith(`${projectRoot}/.codecov.yml`).thenResolve(false);
|
|
22
|
+
|
|
23
|
+
expect(await codecovInUse({projectRoot})).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
});
|