|
1 | 1 | import * as core from '@actions/core';
|
2 |
| -import * as github from '@actions/github'; |
3 | 2 | import * as glob from "@actions/glob";
|
4 | 3 | import { readFileSync, existsSync } from 'fs';
|
5 | 4 |
|
6 | 5 | interface Input {
|
7 |
| - token: string; |
8 | 6 | 'include-gitignore': boolean;
|
9 | 7 | 'ignore-default': boolean;
|
10 | 8 | files: string;
|
11 | 9 | }
|
12 | 10 |
|
13 | 11 | export function getInputs(): Input {
|
14 | 12 | const result = {} as Input;
|
15 |
| - result.token = core.getInput('github-token'); |
16 | 13 | result['include-gitignore'] = core.getBooleanInput('include-gitignore');
|
17 | 14 | result['ignore-default'] = core.getBooleanInput('ignore-default');
|
18 | 15 | result.files = core.getInput('files');
|
19 | 16 | return result;
|
20 | 17 | }
|
21 | 18 |
|
22 |
| -export const runAction = async (octokit: ReturnType<typeof github.getOctokit>, input: Input): Promise<void> => { |
23 |
| - let allFiles: string[] = []; |
| 19 | +export const runAction = async (input: Input): Promise<void> => { |
| 20 | + let filesToCheck: string[] = []; |
| 21 | + core.startGroup(`Loading files to check.`); |
24 | 22 | if (input.files) {
|
25 |
| - allFiles = input.files.split(' '); |
26 |
| - allFiles = await (await glob.create(allFiles.join('\n'))).glob(); |
| 23 | + filesToCheck = input.files.split(' '); |
| 24 | + filesToCheck = await (await glob.create(filesToCheck.join('\n'))).glob(); |
27 | 25 | } else {
|
28 |
| - allFiles = await (await glob.create('*')).glob(); |
| 26 | + filesToCheck = await (await glob.create('*')).glob(); |
29 | 27 | }
|
30 |
| - core.startGroup(`All Files: ${allFiles.length}`); |
31 |
| - core.info(JSON.stringify(allFiles)); |
| 28 | + // core.info(JSON.stringify(filesToCheck)); |
32 | 29 | core.endGroup();
|
33 | 30 |
|
34 |
| - const codeownerContent = getCodeownerContent(); |
35 | 31 | core.startGroup('Reading CODEOWNERS File');
|
36 |
| - core.endGroup(); |
| 32 | + const codeownerContent = getCodeownerContent(); |
37 | 33 | let codeownerFileGlobs = codeownerContent.split('\n')
|
38 | 34 | .map(line => line.split(' ')[0])
|
39 | 35 | .filter(file => !file.startsWith('#'))
|
40 | 36 | .map(file => file.replace(/^\//, ''));
|
41 | 37 | if (input['ignore-default'] === true) {
|
42 | 38 | codeownerFileGlobs = codeownerFileGlobs.filter(file => file !== '*');
|
43 | 39 | }
|
44 |
| - |
45 | 40 | const codeownersGlob = await glob.create(codeownerFileGlobs.join('\n'));
|
46 | 41 | let codeownersFiles = await codeownersGlob.glob();
|
47 |
| - core.startGroup(`CODEOWNERS Files: ${codeownersFiles.length}`); |
48 |
| - core.info(JSON.stringify(codeownersFiles)); |
| 42 | + // core.info(JSON.stringify(codeownersFiles)); |
49 | 43 | core.endGroup();
|
50 |
| - codeownersFiles = codeownersFiles.filter(file => allFiles.includes(file)); |
| 44 | + |
| 45 | + core.startGroup('Matching CODEOWNER Files with found files'); |
| 46 | + codeownersFiles = codeownersFiles.filter(file => filesToCheck.includes(file)); |
51 | 47 | core.info(`CODEOWNER Files in All Files: ${codeownersFiles.length}`);
|
52 |
| - core.startGroup('CODEOWNERS'); |
53 | 48 | core.info(JSON.stringify(codeownersFiles));
|
54 | 49 | core.endGroup();
|
55 | 50 |
|
56 |
| - |
57 |
| - |
58 |
| - let filesCovered = codeownersFiles; |
59 |
| - let allFilesClean = allFiles; |
60 | 51 | if (input['include-gitignore'] === true) {
|
61 |
| - let gitIgnoreFiles: string[] = []; |
| 52 | + core.startGroup('Ignoring .gitignored files'); |
| 53 | + let gitIgnoreFiles: string[] = []; |
62 | 54 | if(!existsSync('.gitignore')){
|
63 | 55 | core.warning('No .gitignore file found');
|
64 | 56 | } else {
|
65 | 57 | const gitIgnoreBuffer = readFileSync('.gitignore', 'utf8');
|
66 | 58 | const gitIgnoreGlob = await glob.create(gitIgnoreBuffer);
|
67 | 59 | gitIgnoreFiles = await gitIgnoreGlob.glob();
|
68 | 60 | core.info(`.gitignore Files: ${gitIgnoreFiles.length}`);
|
| 61 | + const lengthBefore = filesToCheck.length; |
| 62 | + filesToCheck = filesToCheck.filter(file => !gitIgnoreFiles.includes(file)); |
| 63 | + const filesIgnored = lengthBefore - filesToCheck.length; |
| 64 | + core.info(`Files Ignored: ${filesIgnored}`); |
69 | 65 | }
|
70 |
| - allFilesClean = allFiles.filter(file => !gitIgnoreFiles.includes(file)); |
71 |
| - filesCovered = filesCovered.filter(file => !gitIgnoreFiles.includes(file)); |
72 |
| - } |
73 |
| - if (input.files) { |
74 |
| - filesCovered = filesCovered.filter(file => allFilesClean.includes(file)); |
| 66 | + core.endGroup(); |
75 | 67 | }
|
76 |
| - const coveragePercent = (filesCovered.length / allFilesClean.length) * 100; |
77 |
| - const coverageMessage = `${filesCovered.length}/${allFilesClean.length}(${coveragePercent.toFixed(2)}%) files covered by CODEOWNERS`; |
| 68 | + |
| 69 | + core.startGroup('Checking CODEOWNERS Coverage'); |
| 70 | + const filesNotCovered = filesToCheck.filter(file => !codeownersFiles.includes(file)); |
| 71 | + const amountCovered = filesToCheck.length - filesNotCovered.length |
| 72 | + |
| 73 | + |
| 74 | + const coveragePercent = filesToCheck.length === 0 ? 100 : (amountCovered / filesToCheck.length) * 100; |
| 75 | + const coverageMessage = `${amountCovered}/${filesToCheck.length}(${coveragePercent.toFixed(2)}%) files covered by CODEOWNERS`; |
78 | 76 | core.notice(coverageMessage, {
|
79 | 77 | title: 'Coverage',
|
80 | 78 | file: 'CODEOWNERS'
|
81 | 79 | });
|
82 |
| - |
83 |
| - const filesNotCovered = allFilesClean.filter(f => !filesCovered.includes(f)); |
84 |
| - core.info(`Files not covered: ${filesNotCovered.length}`); |
85 |
| - |
86 |
| - if (github.context.eventName === 'pull_request') { |
87 |
| - const checkResponse = await octokit.rest.checks.create({ |
88 |
| - owner: github.context.repo.owner, |
89 |
| - repo: github.context.repo.repo, |
90 |
| - name: 'Changed Files have CODEOWNERS', |
91 |
| - head_sha: github.context.payload.pull_request?.head.sha || github.context.payload.after || github.context.sha, |
92 |
| - status: 'completed', |
93 |
| - completed_at: (new Date()).toISOString(), |
94 |
| - output: { |
95 |
| - title: 'Codeowners check!', |
96 |
| - summary: `Summary`, |
97 |
| - annotations: filesNotCovered.map(file => ({ |
98 |
| - path: file, |
99 |
| - annotation_level: 'failure' as 'failure', |
100 |
| - message: 'File not covered by CODEOWNERS', |
101 |
| - start_line: 0, |
102 |
| - end_line: 1, |
103 |
| - })).slice(0, 50), |
104 |
| - }, |
105 |
| - conclusion: coveragePercent < 100 ? 'failure' : 'success', |
106 |
| - }); |
107 |
| - console.log('Check Response OK: ', checkResponse.status); |
| 80 | + core.endGroup(); |
| 81 | + core.startGroup('Annotating files'); |
| 82 | + filesNotCovered.forEach(file => core.error(`File not covered by CODEOWNERS: ${file}`, { |
| 83 | + title: 'File mssing in CODEOWNERS', |
| 84 | + file: file, |
| 85 | + })); |
| 86 | + core.endGroup(); |
| 87 | + if(filesNotCovered.length > 0){ |
| 88 | + core.setFailed(`${filesNotCovered.length}/${filesToCheck.length} files not covered in CODEOWNERS`); |
108 | 89 | }
|
109 | 90 | }
|
110 | 91 |
|
111 | 92 | export async function run(): Promise<void> {
|
112 | 93 | try {
|
113 | 94 | const input = getInputs();
|
114 |
| - const octokit: ReturnType<typeof github.getOctokit> = github.getOctokit(input.token); |
115 |
| - return runAction(octokit, input); |
| 95 | + return runAction(input); |
116 | 96 | } catch (error) {
|
117 | 97 | core.startGroup(error instanceof Error ? error.message : JSON.stringify(error));
|
118 | 98 | core.info(JSON.stringify(error, null, 2));
|
|
0 commit comments