|
1 |
| -import * as core from '@actions/core' |
2 |
| -import { wait } from './wait.js' |
3 |
| - |
4 |
| -/** |
5 |
| - * The main function for the action. |
6 |
| - * |
7 |
| - * @returns Resolves when the action is complete. |
8 |
| - */ |
9 |
| -export async function run(): Promise<void> { |
10 |
| - try { |
11 |
| - const ms: string = core.getInput('milliseconds') |
| 1 | +import * as core from '@actions/core'; |
| 2 | +import * as github from '@actions/github'; |
| 3 | +import * as glob from "@actions/glob"; |
| 4 | +import { readFileSync, existsSync } from 'fs'; |
| 5 | + |
| 6 | +interface Input { |
| 7 | + token: string; |
| 8 | + 'include-gitignore': boolean; |
| 9 | + 'ignore-default': boolean; |
| 10 | + files: string; |
| 11 | +} |
| 12 | + |
| 13 | +export function getInputs(): Input { |
| 14 | + const result = {} as Input; |
| 15 | + result.token = core.getInput('github-token'); |
| 16 | + result['include-gitignore'] = core.getBooleanInput('include-gitignore'); |
| 17 | + result['ignore-default'] = core.getBooleanInput('ignore-default'); |
| 18 | + result.files = core.getInput('files'); |
| 19 | + return result; |
| 20 | +} |
| 21 | + |
| 22 | +export const runAction = async (octokit: ReturnType<typeof github.getOctokit>, input: Input): Promise<void> => { |
| 23 | + let allFiles: string[] = []; |
| 24 | + if (input.files) { |
| 25 | + allFiles = input.files.split(' '); |
| 26 | + allFiles = await (await glob.create(allFiles.join('\n'))).glob(); |
| 27 | + } else { |
| 28 | + allFiles = await (await glob.create('*')).glob(); |
| 29 | + } |
| 30 | + core.startGroup(`All Files: ${allFiles.length}`); |
| 31 | + core.info(JSON.stringify(allFiles)); |
| 32 | + core.endGroup(); |
| 33 | + |
| 34 | + const codeownerContent = getCodeownerContent(); |
| 35 | + core.startGroup('Reading CODEOWNERS File'); |
| 36 | + core.endGroup(); |
| 37 | + let codeownerFileGlobs = codeownerContent.split('\n') |
| 38 | + .map(line => line.split(' ')[0]) |
| 39 | + .filter(file => !file.startsWith('#')) |
| 40 | + .map(file => file.replace(/^\//, '')); |
| 41 | + if (input['ignore-default'] === true) { |
| 42 | + codeownerFileGlobs = codeownerFileGlobs.filter(file => file !== '*'); |
| 43 | + } |
| 44 | + |
| 45 | + const codeownersGlob = await glob.create(codeownerFileGlobs.join('\n')); |
| 46 | + let codeownersFiles = await codeownersGlob.glob(); |
| 47 | + core.startGroup(`CODEOWNERS Files: ${codeownersFiles.length}`); |
| 48 | + core.info(JSON.stringify(codeownersFiles)); |
| 49 | + core.endGroup(); |
| 50 | + codeownersFiles = codeownersFiles.filter(file => allFiles.includes(file)); |
| 51 | + core.info(`CODEOWNER Files in All Files: ${codeownersFiles.length}`); |
| 52 | + core.startGroup('CODEOWNERS'); |
| 53 | + core.info(JSON.stringify(codeownersFiles)); |
| 54 | + core.endGroup(); |
| 55 | + |
12 | 56 |
|
13 |
| - // Debug logs are only output if the `ACTIONS_STEP_DEBUG` secret is true |
14 |
| - core.debug(`Waiting ${ms} milliseconds ...`) |
15 | 57 |
|
16 |
| - // Log the current timestamp, wait, then log the new timestamp |
17 |
| - core.debug(new Date().toTimeString()) |
18 |
| - await wait(parseInt(ms, 10)) |
19 |
| - core.debug(new Date().toTimeString()) |
| 58 | + let filesCovered = codeownersFiles; |
| 59 | + let allFilesClean = allFiles; |
| 60 | + if (input['include-gitignore'] === true) { |
| 61 | + let gitIgnoreFiles: string[] = []; |
| 62 | + if(!existsSync('.gitignore')){ |
| 63 | + core.warning('No .gitignore file found'); |
| 64 | + } else { |
| 65 | + const gitIgnoreBuffer = readFileSync('.gitignore', 'utf8'); |
| 66 | + const gitIgnoreGlob = await glob.create(gitIgnoreBuffer); |
| 67 | + gitIgnoreFiles = await gitIgnoreGlob.glob(); |
| 68 | + core.info(`.gitignore Files: ${gitIgnoreFiles.length}`); |
| 69 | + } |
| 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)); |
| 75 | + } |
| 76 | + const coveragePercent = (filesCovered.length / allFilesClean.length) * 100; |
| 77 | + const coverageMessage = `${filesCovered.length}/${allFilesClean.length}(${coveragePercent.toFixed(2)}%) files covered by CODEOWNERS`; |
| 78 | + core.notice(coverageMessage, { |
| 79 | + title: 'Coverage', |
| 80 | + file: 'CODEOWNERS' |
| 81 | + }); |
| 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); |
| 108 | + } |
| 109 | +} |
20 | 110 |
|
21 |
| - // Set outputs for other workflow steps to use |
22 |
| - core.setOutput('time', new Date().toTimeString()) |
| 111 | +export async function run(): Promise<void> { |
| 112 | + try { |
| 113 | + const input = getInputs(); |
| 114 | + const octokit: ReturnType<typeof github.getOctokit> = github.getOctokit(input.token); |
| 115 | + return runAction(octokit, input); |
23 | 116 | } catch (error) {
|
24 |
| - // Fail the workflow run if an error occurs |
25 |
| - if (error instanceof Error) core.setFailed(error.message) |
| 117 | + core.startGroup(error instanceof Error ? error.message : JSON.stringify(error)); |
| 118 | + core.info(JSON.stringify(error, null, 2)); |
| 119 | + core.endGroup(); |
| 120 | + } |
| 121 | +}; |
| 122 | + |
| 123 | + |
| 124 | +function getCodeownerContent(): string { |
| 125 | + if(existsSync('CODEOWNERS')) { |
| 126 | + return readFileSync('CODEOWNERS', 'utf8') |
| 127 | + } |
| 128 | + if(existsSync('.github/CODEOWNERS')){ |
| 129 | + return readFileSync('.github/CODEOWNERS', 'utf8'); |
26 | 130 | }
|
| 131 | + throw new Error('No CODEOWNERS file found'); |
27 | 132 | }
|
0 commit comments