├── .eslintignore ├── .prettierignore ├── tsconfig.eslint.json ├── .github ├── FUNDING.yml └── workflows │ ├── tag-latest.yml │ ├── test.yml │ └── example.yml ├── .prettierrc.json ├── jest.config.js ├── src ├── wait.ts ├── namespaces │ └── Inputs.ts ├── checks.ts ├── main.ts └── inputs.ts ├── .editorconfig ├── tsconfig.json ├── LICENSE ├── package.json ├── action.yml ├── .gitignore ├── .eslintrc.json ├── README.md └── __tests__ └── main.test.ts /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [] 4 | } 5 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [simensen, dflydev] 2 | open_collective: dflydev 3 | custom: https://enjoy.gitstore.app/repositories/dflydev/check-runs-action 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": false, 9 | "arrowParens": "avoid", 10 | "parser": "typescript" 11 | } 12 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: ['**/*.test.ts'], 6 | testRunner: 'jest-circus/runner', 7 | transform: { 8 | '^.+\\.ts$': 'ts-jest' 9 | }, 10 | verbose: true 11 | } -------------------------------------------------------------------------------- /src/wait.ts: -------------------------------------------------------------------------------- 1 | export async function wait(milliseconds: number): Promise { 2 | return new Promise(resolve => { 3 | if (isNaN(milliseconds)) { 4 | throw new Error('milliseconds not a number') 5 | } 6 | 7 | setTimeout(() => resolve('done!'), milliseconds) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_size = 2 16 | 17 | [*.{js,ts}] 18 | indent_size = 2 19 | 20 | [*.vue] 21 | indent_size = 2 22 | -------------------------------------------------------------------------------- /.github/workflows/tag-latest.yml: -------------------------------------------------------------------------------- 1 | name: 'Tag latest release of GitHub Check Runs' 2 | 3 | on: 4 | release: 5 | types: 6 | - published 7 | - edited 8 | 9 | jobs: 10 | run-tag-latest: 11 | runs-on: windows-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - uses: Actions-R-Us/actions-tagger@latest 15 | with: 16 | publish_latest_tag: true 17 | env: 18 | GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' 19 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "build-test" 2 | on: # rebuild any PRs and main branch changes 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | - 'releases/*' 8 | 9 | jobs: 10 | env: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - run: | 14 | echo "${{ toJson(runner) }}" 15 | - run: | 16 | env 17 | - run: | 18 | echo $GITHUB_EVENT_PATH 19 | cat $GITHUB_EVENT_PATH 20 | echo "done" 21 | 22 | build: # make sure build/ci work properly 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v1 26 | - run: | 27 | npm install 28 | npm run all 29 | test: # make sure the action works on a clean machine without building 30 | runs-on: ubuntu-latest 31 | steps: 32 | - uses: actions/checkout@v1 33 | - uses: ./ 34 | with: 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 4 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 5 | "outDir": "./lib", /* Redirect output structure to the directory. */ 6 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 7 | "strict": true, /* Enable all strict type-checking options. */ 8 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 9 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 10 | }, 11 | "exclude": ["node_modules", "**/*.test.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 GitHub, Inc. and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "check-runs-action", 3 | "version": "1.0.6", 4 | "description": "Create and manage one or more GitHub Check Runs per job.", 5 | "main": "lib/main.js", 6 | "scripts": { 7 | "build": "tsc", 8 | "format": "prettier --write **/*.ts", 9 | "format-check": "prettier --check **/*.ts", 10 | "lint": "eslint src/**/*.ts", 11 | "pack": "ncc build", 12 | "test": "jest", 13 | "all": "npm run build && npm run format && npm run lint && npm run pack && npm test", 14 | "prod": "npm run build && npm run format && npm run lint && npm run pack" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/dflydev/check-runs-action.git" 19 | }, 20 | "keywords": [ 21 | "checks", 22 | "Check Run", 23 | "Check Runs" 24 | ], 25 | "author": "dflydev", 26 | "license": "MIT", 27 | "dependencies": { 28 | "@actions/core": "^1.2.6", 29 | "@actions/github": "^2.1.1" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "^24.0.23", 33 | "@types/node": "^12.7.12", 34 | "@typescript-eslint/eslint-plugin": "^2.30.0", 35 | "@typescript-eslint/parser": "^2.8.0", 36 | "@zeit/ncc": "^0.20.5", 37 | "eslint": "^5.16.0", 38 | "eslint-plugin-github": "^2.0.0", 39 | "eslint-plugin-jest": "^22.21.0", 40 | "jest": "^24.9.0", 41 | "jest-circus": "^24.9.0", 42 | "js-yaml": "^3.13.1", 43 | "kind-of": "^6.0.3", 44 | "minimist": "^1.2.5", 45 | "prettier": "^1.19.1", 46 | "ts-jest": "^24.2.0", 47 | "typescript": "^3.6.4" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/namespaces/Inputs.ts: -------------------------------------------------------------------------------- 1 | import {Octokit} from '@octokit/rest' 2 | 3 | export interface Input { 4 | globalContext: GlobalContext 5 | runContext: RunContext 6 | } 7 | 8 | export interface GlobalContext { 9 | collections: NamedCheckRunCollection[] 10 | } 11 | 12 | export interface RunContext { 13 | token: string 14 | collectedCheckRuns: CollectedCheckRun[] 15 | fail: boolean 16 | } 17 | 18 | export interface CollectedCheckRun { 19 | collection: string 20 | checkRun: CheckRun 21 | } 22 | 23 | export interface NamedCheckRunCollection { 24 | collection: string 25 | checkRuns: CheckRun[] 26 | } 27 | 28 | export interface CheckRun { 29 | id: string 30 | name: string 31 | status: Status 32 | 33 | gitHubCheckRunId?: number 34 | 35 | conclusion?: Conclusion 36 | actionURL?: string 37 | 38 | output?: Output 39 | annotations?: Annotations 40 | images?: Images 41 | actions?: Actions 42 | } 43 | 44 | export type Annotations = Octokit.ChecksCreateParamsOutputAnnotations[] 45 | 46 | export type Images = Octokit.ChecksCreateParamsOutputImages[] 47 | 48 | export type Actions = Octokit.ChecksCreateParamsActions[] 49 | 50 | export interface Output { 51 | summary: string 52 | text_description?: string 53 | } 54 | 55 | export enum Conclusion { 56 | Success = 'success', 57 | Failure = 'failure', 58 | Neutral = 'neutral', 59 | Cancelled = 'cancelled', 60 | TimedOut = 'timed_out', 61 | ActionRequired = 'action_required', 62 | Skipped = 'skipped', 63 | } 64 | 65 | export enum Status { 66 | Queued = 'queued', 67 | InProgress = 'in_progress', 68 | Completed = 'completed', 69 | } 70 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'GitHub Check Runs Action' 2 | description: 'Create and manage one or more GitHub check runs per job.' 3 | author: 'dflydev' 4 | inputs: 5 | token: 6 | description: 'your GITHUB_TOKEN' 7 | required: true 8 | collection: 9 | description: 'the name of your check collection' 10 | required: false 11 | id: 12 | description: 'the step ID related to this check' 13 | required: false 14 | checks: 15 | description: 'the checks you want to manage' 16 | required: false 17 | name: 18 | description: 'the name of your check' 19 | required: true 20 | conclusion: 21 | description: 'the conclusion of your check' 22 | required: true 23 | status: 24 | description: 'the status of your check' 25 | required: false 26 | action_url: 27 | description: 'the url to call back to when using `action_required` as conclusion or with `actions`' 28 | required: false 29 | output: 30 | description: 'the output of your check' 31 | required: false 32 | annotations: 33 | description: 'the annotations of your check' 34 | required: false 35 | images: 36 | description: 'the images of your check' 37 | required: false 38 | actions: 39 | description: 'the actions of your check' 40 | required: false 41 | fail-on-error: 42 | description: 'step will fail if specified `conclusion` is not `success` or `neutral`' 43 | required: false 44 | fail-on-neutral: 45 | description: 'step will fail if specified `conclusion` is `neutral`' 46 | required: false 47 | runs: 48 | using: 'node12' 49 | main: 'dist/index.js' 50 | branding: 51 | icon: 'check-square' 52 | color: 'green' 53 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | node_modules 3 | 4 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | jspm_packages/ 46 | 47 | # TypeScript v1 declaration files 48 | typings/ 49 | 50 | # TypeScript cache 51 | *.tsbuildinfo 52 | 53 | # Optional npm cache directory 54 | .npm 55 | 56 | # Optional eslint cache 57 | .eslintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variables file 69 | .env 70 | .env.test 71 | 72 | # parcel-bundler cache (https://parceljs.org/) 73 | .cache 74 | 75 | # next.js build output 76 | .next 77 | 78 | # nuxt.js build output 79 | .nuxt 80 | 81 | # vuepress build output 82 | .vuepress/dist 83 | 84 | # Serverless directories 85 | .serverless/ 86 | 87 | # FuseBox cache 88 | .fusebox/ 89 | 90 | # DynamoDB Local files 91 | .dynamodb/ 92 | 93 | # OS metadata 94 | .DS_Store 95 | Thumbs.db 96 | 97 | # Ignore built ts files 98 | __tests__/runner/* 99 | lib/**/* -------------------------------------------------------------------------------- /src/checks.ts: -------------------------------------------------------------------------------- 1 | import {GitHub} from '@actions/github' 2 | import * as Inputs from './namespaces/Inputs' 3 | 4 | interface Ownership { 5 | owner: string 6 | repo: string 7 | } 8 | 9 | const unpackInputs = (inputs: Inputs.CheckRun): object => { 10 | const output = inputs.output 11 | ? { 12 | output: { 13 | ...(inputs.name ? {title: inputs.name} : {}), 14 | ...(inputs.output.summary ? {summary: inputs.output.summary} : {}), 15 | ...(inputs.output.text_description 16 | ? {text: inputs.output.text_description} 17 | : {}), 18 | ...(inputs.actions ? {actions: inputs.actions} : {}), 19 | ...(inputs.images ? {images: inputs.images} : {}), 20 | }, 21 | } 22 | : {} 23 | 24 | const more: {details_url?: string} = {} 25 | if ( 26 | inputs.conclusion === Inputs.Conclusion.ActionRequired || 27 | inputs.actions 28 | ) { 29 | more.details_url = inputs.actionURL 30 | } 31 | 32 | const status = inputs.status ? {status: inputs.status.toString()} : {} 33 | const conclusion = inputs.conclusion 34 | ? {conclusion: inputs.conclusion.toString()} 35 | : {} 36 | 37 | return { 38 | ...output, 39 | ...(inputs.actions ? {actions: inputs.actions} : {}), 40 | ...more, 41 | ...status, 42 | ...conclusion, 43 | } 44 | } 45 | 46 | const formatDate = (): string => { 47 | return new Date().toISOString() 48 | } 49 | 50 | export const createRun = async ( 51 | octokit: GitHub, 52 | sha: string, 53 | ownership: Ownership, 54 | inputs: Inputs.CheckRun, 55 | ): Promise => { 56 | const payload = { 57 | ...ownership, 58 | head_sha: sha, 59 | name: inputs.name, 60 | started_at: formatDate(), 61 | ...unpackInputs(inputs), 62 | } 63 | 64 | const {data} = await octokit.checks.create(payload) 65 | return data.id 66 | } 67 | 68 | export const updateRun = async ( 69 | octokit: GitHub, 70 | id: number, 71 | ownership: Ownership, 72 | inputs: Inputs.CheckRun, 73 | ): Promise => { 74 | await octokit.checks.update({ 75 | ...ownership, 76 | check_run_id: id, 77 | ...unpackInputs(inputs), 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | import {parseInputs} from './inputs' 4 | import {createRun, updateRun} from './checks' 5 | 6 | import {wait} from './wait' 7 | 8 | export const envVariableName = 'DFLYDEV_CHECK_RUN_COLLECTIONS' 9 | 10 | async function run(): Promise { 11 | try { 12 | core.debug( 13 | `GITHUB_SHA: ${process.env['GITHUB_SHA']} (${github.context.sha} from context)`, 14 | ) 15 | 16 | core.debug(`GITHUB_EVENT_PATH: ${process.env['GITHUB_EVENT_PATH']}`) 17 | 18 | core.debug(`Parsing inputs`) 19 | const inputs = parseInputs(core.getInput) 20 | 21 | core.debug(JSON.stringify(inputs)) 22 | 23 | core.debug(`Setting up OctoKit`) 24 | const octokit = new github.GitHub(inputs.runContext.token) 25 | 26 | const ownership = { 27 | owner: github.context.repo.owner, 28 | repo: github.context.repo.repo, 29 | } 30 | 31 | const sha = github.context.sha 32 | 33 | core.debug(JSON.stringify(ownership)) 34 | 35 | for (const collectedCheckRun of inputs.runContext.collectedCheckRuns) { 36 | const collection = collectedCheckRun.collection 37 | const checkRun = collectedCheckRun.checkRun 38 | 39 | if (checkRun.gitHubCheckRunId) { 40 | core.debug(`Updating a Run (${checkRun.gitHubCheckRunId})`) 41 | 42 | updateRun(octokit, checkRun.gitHubCheckRunId, ownership, checkRun) 43 | } else { 44 | core.debug(`Create a Run`) 45 | 46 | const gitHubCheckRunId = await createRun( 47 | octokit, 48 | sha, 49 | ownership, 50 | checkRun, 51 | ) 52 | 53 | const foundCollectedCheckRun = inputs.globalContext.collections.find( 54 | thisCollectedCheckRun => 55 | thisCollectedCheckRun.collection === collection, 56 | ) 57 | 58 | if (!foundCollectedCheckRun) { 59 | throw Error(`Could not find collection named '${collection}'`) 60 | } 61 | 62 | const foundCheckRun = foundCollectedCheckRun.checkRuns.find( 63 | thisCheckRun => thisCheckRun.id === checkRun.id, 64 | ) 65 | 66 | if (!foundCheckRun) { 67 | throw Error( 68 | `Could not find id '${checkRun.id}' in collection named '${collection}'`, 69 | ) 70 | } 71 | 72 | foundCheckRun.gitHubCheckRunId = gitHubCheckRunId 73 | } 74 | } 75 | 76 | core.exportVariable(envVariableName, JSON.stringify(inputs.globalContext)) 77 | 78 | if (inputs.runContext.fail) { 79 | core.setFailed(`Related step conclusion treated as a failure`) 80 | } 81 | } catch (error) { 82 | core.setFailed(error.message) 83 | } 84 | } 85 | 86 | run() 87 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["jest", "@typescript-eslint"], 3 | "extends": [ 4 | "plugin:github/es6", 5 | "plugin:@typescript-eslint/recommended", 6 | "prettier/@typescript-eslint" 7 | ], 8 | "parser": "@typescript-eslint/parser", 9 | "parserOptions": { 10 | "ecmaVersion": 9, 11 | "sourceType": "module", 12 | "project": "./tsconfig.eslint.json" 13 | }, 14 | "rules": { 15 | "eslint-comments/no-use": "off", 16 | "import/no-namespace": "off", 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": "error", 19 | "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}], 20 | "@typescript-eslint/no-require-imports": "error", 21 | "@typescript-eslint/array-type": "error", 22 | "@typescript-eslint/await-thenable": "error", 23 | "@typescript-eslint/ban-ts-ignore": "error", 24 | "camelcase": "off", 25 | "@typescript-eslint/camelcase": "error", 26 | "@typescript-eslint/class-name-casing": "error", 27 | "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], 28 | "@typescript-eslint/func-call-spacing": ["error", "never"], 29 | "@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"], 30 | "@typescript-eslint/no-array-constructor": "error", 31 | "@typescript-eslint/no-empty-interface": "error", 32 | "@typescript-eslint/no-explicit-any": "error", 33 | "@typescript-eslint/no-extraneous-class": "error", 34 | "@typescript-eslint/no-for-in-array": "error", 35 | "@typescript-eslint/no-inferrable-types": "error", 36 | "@typescript-eslint/no-misused-new": "error", 37 | "@typescript-eslint/no-namespace": "error", 38 | "@typescript-eslint/no-non-null-assertion": "warn", 39 | //"@typescript-eslint/no-object-literal-type-assertion": "error", 40 | "@typescript-eslint/no-unnecessary-qualifier": "error", 41 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 42 | "@typescript-eslint/no-useless-constructor": "error", 43 | "@typescript-eslint/no-var-requires": "error", 44 | "@typescript-eslint/prefer-for-of": "warn", 45 | "@typescript-eslint/prefer-function-type": "warn", 46 | "@typescript-eslint/prefer-includes": "error", 47 | //"@typescript-eslint/prefer-interface": "error", 48 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 49 | "@typescript-eslint/promise-function-async": "error", 50 | "@typescript-eslint/require-array-sort-compare": "error", 51 | "@typescript-eslint/restrict-plus-operands": "error", 52 | "semi": "off", 53 | "@typescript-eslint/semi": ["error", "never"], 54 | "@typescript-eslint/type-annotation-spacing": "error", 55 | "@typescript-eslint/unbound-method": "error" 56 | }, 57 | "env": { 58 | "node": true, 59 | "es6": true, 60 | "jest/globals": true 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/workflows/example.yml: -------------------------------------------------------------------------------- 1 | name: Build Status 2 | on: push 3 | jobs: 4 | build: 5 | name: Build 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v2 10 | #- name: Set up PHP, with Composer and extensions 11 | # uses: shivammathur/setup-php@v2 12 | # with: 13 | # php-version: 7.4 14 | # coverage: pcov 15 | - name: Prepare PHP Checks 16 | uses: ./ 17 | with: 18 | token: ${{ secrets.GITHUB_TOKEN }} 19 | collection: php 20 | checks: | 21 | [ 22 | { "id": "phpunit", "name": "PHPUnit", "status": "queued" }, 23 | { "id": "psalm", "name": "Psalm", "status": "queued" }, 24 | { "id": "phpcs", "name": "PHP_CodeSniffer", "status": "queued" }, 25 | { "id": "dusk", "name": "Dusk", "status": "queued" } 26 | ] 27 | - name: Prepare Node Checks 28 | uses: ./ 29 | with: 30 | token: ${{ secrets.GITHUB_TOKEN }} 31 | collection: node 32 | checks: | 33 | [ 34 | { "id": "eslint", "name": "ESLint", "status": "queued" }, 35 | { "id": "build", "name": "npm build", "status": "queued" } 36 | ] 37 | 38 | - name: Report PHPUnit Starting 39 | uses: ./ 40 | with: 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | collection: php 43 | id: phpunit 44 | status: in_progress 45 | 46 | - name: PHPUnit 47 | id: phpunit 48 | continue-on-error: true 49 | run: | 50 | sleep 5 && false 51 | 52 | - name: Report PHPUnit Conclusion 53 | if: always() 54 | uses: ./ 55 | with: 56 | token: ${{ secrets.GITHUB_TOKEN }} 57 | collection: php 58 | id: phpunit 59 | conclusion: ${{ steps.phpunit.outcome }} 60 | 61 | - name: Report PHP_CodeSniffer Starting 62 | uses: ./ 63 | with: 64 | token: ${{ secrets.GITHUB_TOKEN }} 65 | collection: php 66 | id: phpcs 67 | status: in_progress 68 | 69 | - name: PHP_CodeSniffer 70 | id: phpcs 71 | continue-on-error: true 72 | run: | 73 | sleep 2 74 | 75 | - name: Report PHP_CodeSniffer Conclusion 76 | if: always() 77 | uses: ./ 78 | with: 79 | token: ${{ secrets.GITHUB_TOKEN }} 80 | collection: php 81 | id: phpcs 82 | conclusion: ${{ steps.phpcs.outcome }} 83 | 84 | - name: Report ESLint Starting 85 | uses: ./ 86 | with: 87 | token: ${{ secrets.GITHUB_TOKEN }} 88 | collection: node 89 | id: eslint 90 | status: in_progress 91 | 92 | - name: ESLint 93 | id: eslint 94 | continue-on-error: true 95 | run: | 96 | sleep 1 && false 97 | 98 | - name: Report ESLint Conclusion 99 | if: always() 100 | uses: ./ 101 | with: 102 | token: ${{ secrets.GITHUB_TOKEN }} 103 | collection: node 104 | id: eslint 105 | conclusion: ${{ steps.eslint.outcome }} 106 | 107 | 108 | - name: Report Build Starting 109 | uses: ./ 110 | with: 111 | token: ${{ secrets.GITHUB_TOKEN }} 112 | collection: node 113 | id: build 114 | status: in_progress 115 | 116 | - name: Build 117 | id: build 118 | continue-on-error: true 119 | run: | 120 | sleep 5 121 | 122 | - name: Report Build Conclusion 123 | if: always() 124 | uses: ./ 125 | with: 126 | token: ${{ secrets.GITHUB_TOKEN }} 127 | collection: node 128 | id: build 129 | conclusion: ${{ steps.build.outcome }} 130 | 131 | 132 | - name: Report Dusk Starting 133 | uses: ./ 134 | with: 135 | token: ${{ secrets.GITHUB_TOKEN }} 136 | collection: php 137 | id: dusk 138 | status: in_progress 139 | 140 | - name: Dusk 141 | id: dusk 142 | run: | 143 | sleep 2 && false 144 | 145 | - name: Report Dusk Conclusion 146 | if: always() 147 | uses: ./ 148 | with: 149 | token: ${{ secrets.GITHUB_TOKEN }} 150 | collection: php 151 | id: dusk 152 | conclusion: ${{ steps.dusk.outcome }} 153 | 154 | - name: Report Psalm Starting 155 | uses: ./ 156 | with: 157 | token: ${{ secrets.GITHUB_TOKEN }} 158 | collection: php 159 | id: psalm 160 | status: in_progress 161 | 162 | - name: Psalm 163 | id: psalm 164 | run: | 165 | sleep 15 && false 166 | 167 | - name: Report Psalm Conclusion 168 | if: always() 169 | uses: ./ 170 | with: 171 | token: ${{ secrets.GITHUB_TOKEN }} 172 | collection: php 173 | id: psalm 174 | conclusion: ${{ steps.psalm.outcome }} 175 | 176 | - name: Clean up checks 177 | if: always() 178 | uses: ./ 179 | with: 180 | token: ${{ secrets.GITHUB_TOKEN }} 181 | conclusion: cancelled 182 | -------------------------------------------------------------------------------- /src/inputs.ts: -------------------------------------------------------------------------------- 1 | import {InputOptions} from '@actions/core' 2 | import {envVariableName} from './main' 3 | import { 4 | Actions, 5 | Annotations, 6 | CheckRun, 7 | CollectedCheckRun, 8 | Conclusion, 9 | GlobalContext, 10 | Images, 11 | Input, 12 | NamedCheckRunCollection, 13 | Output, 14 | Status, 15 | } from './namespaces/Inputs' 16 | 17 | type GetInput = (name: string, options?: InputOptions | undefined) => string 18 | 19 | const parseJSON = (getInput: GetInput, property: string): T | undefined => { 20 | const value = getInput(property) 21 | if (!value) { 22 | return 23 | } 24 | try { 25 | const obj = JSON.parse(value) 26 | return obj as T 27 | } catch (e) { 28 | throw new Error(`invalid format for '${property}: ${e.toString()}`) 29 | } 30 | } 31 | 32 | const parseCheckRun = (getInput: GetInput): CheckRun => { 33 | const id = getInput('id') 34 | const name = getInput('name') 35 | const statusFromInput = getInput('status') 36 | const conclusionFromInput = getInput('conclusion') 37 | const actionURL = getInput('action_url') 38 | 39 | const status = statusFromInput 40 | ? (statusFromInput.toLowerCase() as Status) 41 | : null 42 | 43 | const conclusion = conclusionFromInput 44 | ? (conclusionFromInput.toLowerCase() as Conclusion) 45 | : null 46 | 47 | if (status && !Object.values(Status).includes(status)) { 48 | throw new Error(`invalid value for 'status': '${status}'`) 49 | } 50 | 51 | if (conclusion && !Object.values(Conclusion).includes(conclusion)) { 52 | throw new Error(`invalid value for 'conclusion': '${conclusion}'`) 53 | } 54 | 55 | const output = parseJSON(getInput, 'output') 56 | const annotations = parseJSON(getInput, 'annotations') 57 | const images = parseJSON(getInput, 'images') 58 | const actions = parseJSON(getInput, 'actions') 59 | 60 | if (!actionURL && (conclusion === Conclusion.ActionRequired || actions)) { 61 | throw new Error(`missing value for 'action_url'`) 62 | } 63 | 64 | if ((!output || !output.summary) && (annotations || images)) { 65 | throw new Error(`missing value for 'output.summary'`) 66 | } 67 | 68 | return Object.entries({ 69 | id, 70 | name, 71 | status, 72 | conclusion, 73 | actionURL, 74 | output, 75 | annotations, 76 | images, 77 | actions, 78 | }).reduce((a, [k, v]) => (v ? {...a, [k]: v} : a), {}) as CheckRun 79 | } 80 | 81 | export const parseInputs = (getInput: GetInput): Input => { 82 | const globalContext: GlobalContext = process.env[envVariableName] 83 | ? JSON.parse(process.env[envVariableName] as string) 84 | : {collections: []} 85 | 86 | const collectionFromInput = getInput('collection') 87 | const collection = collectionFromInput || 'default' 88 | 89 | const token = getInput('token', {required: true}) 90 | const failOnError = getInput('fail-on-error') === 'true' || false 91 | const failOnNeutral = getInput('fail-on-neutral') === 'true' || false 92 | 93 | const defaults = parseCheckRun(getInput) 94 | 95 | const incomingCheckRuns = parseJSON(getInput, 'checks') || [] 96 | 97 | if (incomingCheckRuns.length === 0 && defaults.id) { 98 | incomingCheckRuns.push(defaults) 99 | } 100 | 101 | const push = (newCollection: string): NamedCheckRunCollection => { 102 | const newNamedCheckRunCollection: NamedCheckRunCollection = { 103 | collection: newCollection, 104 | checkRuns: [], 105 | } 106 | 107 | globalContext.collections.push(newNamedCheckRunCollection) 108 | 109 | return newNamedCheckRunCollection 110 | } 111 | 112 | const namedCheckRunCollection: NamedCheckRunCollection = 113 | globalContext.collections.find( 114 | existingCollection => existingCollection.collection === collection, 115 | ) ?? push(collection) 116 | 117 | const collectedCheckRuns: CollectedCheckRun[] = [] 118 | 119 | let fail = false 120 | 121 | if (incomingCheckRuns.length > 0) { 122 | for (const incomingCheckRun of incomingCheckRuns) { 123 | if (incomingCheckRun.conclusion === Conclusion.Skipped) { 124 | // We skip this conclusion as we do not actually have a way to deal 125 | // with the Skipped conclusion. (API limitation) 126 | continue 127 | } 128 | 129 | if (incomingCheckRun.conclusion === Conclusion.Neutral && failOnNeutral) { 130 | fail = true 131 | } 132 | 133 | if ( 134 | incomingCheckRun.conclusion && 135 | ![Conclusion.Success, Conclusion.Neutral].includes( 136 | incomingCheckRun.conclusion, 137 | ) && 138 | failOnError 139 | ) { 140 | fail = true 141 | } 142 | 143 | const existingCheckRun = namedCheckRunCollection.checkRuns.find( 144 | existing => existing.id === incomingCheckRun.id, 145 | ) 146 | 147 | if (existingCheckRun) { 148 | const updatedCheckRun = Object.assign( 149 | existingCheckRun, 150 | incomingCheckRun, 151 | ) 152 | 153 | collectedCheckRuns.push({ 154 | collection, 155 | checkRun: updatedCheckRun, 156 | }) 157 | } else { 158 | const newCheckRun = Object.assign({}, defaults, incomingCheckRun) 159 | 160 | collectedCheckRuns.push({ 161 | collection, 162 | checkRun: newCheckRun, 163 | }) 164 | 165 | namedCheckRunCollection.checkRuns.push(newCheckRun) 166 | } 167 | } 168 | } else { 169 | if (!defaults.conclusion) { 170 | throw new Error(`missing conclusion for global cleanup operation`) 171 | } 172 | 173 | if (!defaults.status) { 174 | defaults.status = Status.Completed 175 | } 176 | 177 | if (defaults.status !== Status.Completed) { 178 | throw new Error( 179 | `invalid status '${defaults.status}'; must be empty or must be '${Status.Completed}`, 180 | ) 181 | } 182 | 183 | if (collectionFromInput) { 184 | for (const checkRun of namedCheckRunCollection.checkRuns) { 185 | if (checkRun.conclusion) { 186 | continue 187 | } 188 | 189 | Object.assign(checkRun, defaults) 190 | 191 | delete checkRun.status 192 | 193 | collectedCheckRuns.push({ 194 | collection, 195 | checkRun, 196 | }) 197 | } 198 | } else { 199 | for (const namedCheckRunCollectionOfAll of globalContext.collections) { 200 | for (const checkRun of namedCheckRunCollectionOfAll.checkRuns) { 201 | if (checkRun.conclusion) { 202 | continue 203 | } 204 | 205 | Object.assign(checkRun, defaults) 206 | 207 | delete checkRun.status 208 | 209 | collectedCheckRuns.push({ 210 | collection: namedCheckRunCollectionOfAll.collection, 211 | checkRun, 212 | }) 213 | } 214 | } 215 | } 216 | } 217 | 218 | return { 219 | globalContext, 220 | runContext: { 221 | token, 222 | collectedCheckRuns, 223 | fail, 224 | }, 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

✅ GitHub Check Runs Action

2 |

3 | 4 | Documentation 5 | 6 | 7 | Maintenance 8 | 9 | 10 | License: MIT 11 | 12 | 13 | Twitter: dflydev 14 | 15 | 16 | Twitter: beausimensen 17 | 18 |

19 | 20 | > Create and manage one or more GitHub [check runs](https://developer.github.com/v3/checks/runs/) per job. 21 | 22 | [GitHub Actions](https://github.com/features/actions) already creates a check run for every job. This is great if you can break your workflow into smaller jobs. But if any one job is doing several things for which you'd like feedback as it progresses you are out of luck. 23 | 24 | This action allows you to create and manage GitHub check runs throughout the course of a single job. 25 | 26 | ## 🚀 Quick start 27 | 28 | ### Just one, please 29 | 30 | The following is a quick example showing how `dflydev/check-runs-action` can be used to report a [check run](https://developer.github.com/v3/checks/runs/). 31 | 32 | ```yml 33 | jobs: 34 | build: 35 | name: Build PHP Library 36 | runs-on: ubuntu-latest 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v2 40 | - name: Set up PHP, with Composer and extensions 41 | uses: shivammathur/setup-php@v2 42 | with: 43 | php-version: 7.4 44 | coverage: pcov 45 | 46 | - name: PHPUnit 47 | id: phpunit 48 | continue-on-error: true 49 | run: ./vendor/bin/phpunit --coverage-text 50 | 51 | - name: Report PHPUnit Conclusion 52 | if: always() 53 | uses: dflydev/check-runs-action@v1 54 | with: 55 | token: ${{ secrets.GITHUB_TOKEN }} 56 | id: phpunit 57 | conclusion: ${{ steps.phpunit.outcome }} 58 | ``` 59 | 60 | ### Expect a bunch of things to happen 61 | 62 | The following is a quick example showing how `dflydev/check-runs-action` can be used to create multiple check runs in advance with the status `queued`. 63 | 64 | ```yml 65 | jobs: 66 | build: 67 | name: Build Laravel Application 68 | runs-on: ubuntu-latest 69 | steps: 70 | - name: Prepare Check Runs 71 | uses: dflydev/check-runs-action@v1 72 | with: 73 | token: ${{ secrets.GITHUB_TOKEN }} 74 | checks: | 75 | [ 76 | { "id": "phpunit", "name": "PHPUnit" }, 77 | { "id": "psalm", "name": "Psalm" }, 78 | { "id": "phpcs", "name": "PHP_CodeSniffer" }, 79 | { "id": "eslint", "name": "ESLint" }, 80 | { "id": "assets", "name": "npm run production" }, 81 | { "id": "dusk", "name": "Dusk" } 82 | ] 83 | status: "queued" 84 | ``` 85 | 86 | Before running an individual step (like `phpunit`, `psalm`, `phpcs`, `eslint`, `assets` or `dusk`), the status can be updated to `in_progress`. 87 | 88 | ```yml 89 | jobs: 90 | build: 91 | steps: 92 | - name: Report PHPUnit Starting 93 | uses: dflydev/check-runs-action@v1 94 | with: 95 | token: ${{ secrets.GITHUB_TOKEN }} 96 | id: phpunit 97 | status: in_progress 98 | ``` 99 | 100 | Execute something that may pass or fail. 101 | 102 | In order to actually do something meaningful with fails, you need to pass `true` for `continue-on-error`. It is also important to give these steps an `id` so that following actions (like `check-runs-action`) can capture the outcome. 103 | 104 | ```yml 105 | jobs: 106 | build: 107 | steps: 108 | - name: PHPUnit 109 | id: phpunit 110 | continue-on-error: true 111 | run: ./vendor/bin/phpunit --coverage-text 112 | ``` 113 | 114 | After individual steps have been completed, they can be marked with their conclusion. 115 | 116 | To work with `continue-on-error`, it is recommended to use `steps..outcome`. 117 | 118 | Set `fail-on-error` to `true` if the job should fail after reporting a failed conclusion. 119 | 120 | ```yml 121 | jobs: 122 | build: 123 | steps: 124 | - name: Report PHPUnit Conclusion 125 | if: always() 126 | uses: dflydev/check-runs-action@v1 127 | with: 128 | token: ${{ secrets.GITHUB_TOKEN }} 129 | id: phpunit 130 | conclusion: ${{ steps.phpunit.outcome }} 131 | fail-on-error: true 132 | ``` 133 | 134 | Finally, steps that have not been marked with a conclusion can be finalized to a specifed conclusion. This ensures there will be no checks defined upfront stuck in a non-final state when the job ends. 135 | 136 | ```yml 137 | jobs: 138 | build: 139 | steps: 140 | - name: Clean up checks 141 | if: always() 142 | uses: dflydev/check-runs-action@v1 143 | with: 144 | token: ${{ secrets.GITHUB_TOKEN }} 145 | conclusion: cancelled 146 | ``` 147 | 148 | 149 | ## Author 150 | 151 | 👥 **dflydev** 152 | 153 | * Website: [dflydev.com](https://dflydev.com?utm_source=check-runs-action&utm_medium=readme-author) 154 | * Twitter: [@dflydev](https://twitter.com/dflydev) 155 | * Github: [@dflydev](https://github.com/dflydev) 156 | 157 | 👤 **Beau Simensen** 158 | 159 | * Website: [beausimensen.com](https://beausimensen.com?utm_source=check-runs-action&utm_medium=readme-author) 160 | * Twitter: [@dflydev](https://twitter.com/beausimensen) 161 | * Github: [@simensen](https://github.com/simensen) 162 | 163 | 164 | ## 💡 Inspiration 165 | 166 | This project was heavily inspired by [GitHub Checks](https://github.com/LouisBrunner/checks-action) action. If you only need one check run per job and do not want to specify an arbitrary `id`, check out that action instead! 167 | 168 | 169 | ## 🤝 Contributing 170 | 171 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/dflydev/check-runs-action/issues). 172 | 173 | 174 | ## ❤️ Support the development 175 | 176 | Give a ⭐️ if this project helped you! 177 | 178 | Did this project save you time? Did this project increase your productivity? Did this project solve a problem for you? Did this project make your life easier? Please also consider donating or buying a license! 179 | 180 | * [Sponsor Beau Simensen on GitHub](https://github.com/sponsors/simensen) 181 | * [Sponsor dflydev on GitHub](https://github.com/sponsors/dflydev) 182 | * [Sponsor dflydev on OpenCollecctive](https://opencollective.com/dflydev) 183 | * [License or sponsor on Gitstore](https://enjoy.gitstore.app/repositories/dflydev/check-runs-action) 184 | 185 | 186 | ## 📝 License 187 | 188 | Copyright © 2020 [dflydev](https://github.com/dflydev).
189 | This project is [MIT](https://github.com/dflydev/check-runs-action/blob/master/LICENSE) licensed. 190 | -------------------------------------------------------------------------------- /__tests__/main.test.ts: -------------------------------------------------------------------------------- 1 | import * as cp from 'child_process' 2 | import * as path from 'path' 3 | import { 4 | CheckRun, 5 | Conclusion, 6 | GlobalContext, 7 | Status, 8 | } from '../src/namespaces/Inputs' 9 | 10 | const ip = path.join(__dirname, '..', 'lib', 'main.js') 11 | 12 | // eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires 13 | const action = require(ip) 14 | 15 | function run(env?: NodeJS.ProcessEnv | undefined): Buffer { 16 | const options: cp.ExecSyncOptions = { 17 | env: {...process.env, ...(env ? env : {})}, 18 | } 19 | 20 | try { 21 | const buffer = cp.execSync(`node ${ip}`, options) 22 | 23 | console.log(buffer.toString()) 24 | 25 | return buffer 26 | } catch (e) { 27 | console.log(e.output.toString()) 28 | 29 | throw e 30 | } 31 | } 32 | 33 | const testScenarioJustPhp: GlobalContext = { 34 | collections: [ 35 | { 36 | collection: 'php', 37 | checkRuns: [ 38 | {id: 'phpunit', name: 'PHPUnit', status: Status.Queued}, 39 | {id: 'psalm', name: 'Psalm', status: Status.Queued}, 40 | {id: 'phpcs', name: 'PHP_CodeSniffer', status: Status.Queued}, 41 | {id: 'dusk', name: 'Dusk', status: Status.Queued}, 42 | ], 43 | }, 44 | ], 45 | } 46 | 47 | const testScenarioPhpAndNode: GlobalContext = { 48 | collections: [ 49 | { 50 | collection: 'php', 51 | checkRuns: [ 52 | {id: 'phpunit', name: 'PHPUnit', status: Status.Queued}, 53 | {id: 'psalm', name: 'Psalm', status: Status.Queued}, 54 | {id: 'phpcs', name: 'PHP_CodeSniffer', status: Status.Queued}, 55 | {id: 'dusk', name: 'Dusk', status: Status.Queued}, 56 | ], 57 | }, 58 | { 59 | collection: 'node', 60 | checkRuns: [ 61 | {id: 'eslint', name: 'ESLint', status: Status.Queued}, 62 | {id: 'build', name: 'npm build', status: Status.Queued}, 63 | ], 64 | }, 65 | ], 66 | } 67 | 68 | function loadScenario( 69 | scenario: GlobalContext, 70 | cb: (scenario: GlobalContext) => GlobalContext | void, 71 | ): string { 72 | const rv = cb(scenario) 73 | 74 | return JSON.stringify(rv ? rv : scenario) 75 | } 76 | 77 | function actionInput(input: NodeJS.ProcessEnv): NodeJS.ProcessEnv { 78 | return Object.entries(input).reduce( 79 | (a, [k, v]) => ({...a, [`INPUT_${k.replace(/ /g, '_').toUpperCase()}`]: v}), 80 | {}, 81 | ) 82 | } 83 | 84 | function findCheckRun( 85 | globalContext: GlobalContext, 86 | collection: string, 87 | id: string, 88 | ): CheckRun { 89 | const foundCollectedCheckRun = globalContext.collections.find( 90 | thisCollectedCheckRun => thisCollectedCheckRun.collection === collection, 91 | ) 92 | 93 | if (!foundCollectedCheckRun) { 94 | throw Error(`Could not find collection named '${collection}'`) 95 | } 96 | 97 | const foundCheckRun = foundCollectedCheckRun.checkRuns.find( 98 | thisCheckRun => thisCheckRun.id === id, 99 | ) 100 | 101 | if (!foundCheckRun) { 102 | throw Error(`Could not find id '${id} in collection named '${collection}'`) 103 | } 104 | 105 | return foundCheckRun 106 | } 107 | 108 | test('it cannot run without a token', () => { 109 | expect(() => { 110 | run() 111 | }).toThrow() 112 | }) 113 | 114 | test('it starts a collection', () => { 115 | run({ 116 | ...actionInput({ 117 | token: 'FOO', 118 | checks: JSON.stringify([ 119 | {id: 'phpunit', name: 'PHPUnit'}, 120 | {id: 'psalm', name: 'Psalm'}, 121 | {id: 'phpcs', name: 'PHP_CodeSniffer'}, 122 | {id: 'dusk', name: 'Dusk'}, 123 | ]), 124 | status: 'queued', 125 | }), 126 | }) 127 | }) 128 | 129 | test('it adds a new check to a collection', () => { 130 | run({ 131 | [action.envVariableName]: JSON.stringify(testScenarioJustPhp), 132 | ...actionInput({ 133 | token: 'FOO', 134 | collection: 'php', 135 | checks: JSON.stringify([ 136 | {id: 'phpunit2', name: 'PHPUnit Too!', status: 'in_progress'}, 137 | ]), 138 | status: 'queued', 139 | }), 140 | }) 141 | }) 142 | 143 | test('it updates new check from a collection with checks json', () => { 144 | run({ 145 | [action.envVariableName]: JSON.stringify(testScenarioJustPhp), 146 | ...actionInput({ 147 | token: 'FOO', 148 | collection: 'php', 149 | checks: JSON.stringify([{id: 'phpunit', status: 'in_progress'}]), 150 | }), 151 | }) 152 | }) 153 | 154 | test('it updates new check from a collection with top-level keys', () => { 155 | run({ 156 | [action.envVariableName]: JSON.stringify(testScenarioJustPhp), 157 | ...actionInput({ 158 | token: 'FOO', 159 | collection: 'php', 160 | id: 'phpunit', 161 | status: 'in_progress', 162 | }), 163 | }) 164 | }) 165 | 166 | test('it updates all check from a collection if no id is set', () => { 167 | run({ 168 | [action.envVariableName]: JSON.stringify(testScenarioJustPhp), 169 | ...actionInput({ 170 | token: 'FOO', 171 | collection: 'php', 172 | conclusion: 'cancelled', 173 | }), 174 | }) 175 | }) 176 | 177 | test('it updates all check from a collection if no id is set', () => { 178 | run({ 179 | [action.envVariableName]: loadScenario(testScenarioJustPhp, function( 180 | scenario: GlobalContext, 181 | ): GlobalContext | void { 182 | const checkRun = findCheckRun(scenario, 'php', 'phpcs') 183 | 184 | checkRun.conclusion = Conclusion.Success 185 | delete checkRun.status 186 | }), 187 | ...actionInput({ 188 | token: 'FOO', 189 | collection: 'php', 190 | conclusion: 'cancelled', 191 | }), 192 | }) 193 | }) 194 | 195 | test('it updates all check from a collection if no id is set with multiple collections but only impacts one', () => { 196 | run({ 197 | [action.envVariableName]: loadScenario(testScenarioPhpAndNode, function( 198 | scenario: GlobalContext, 199 | ): GlobalContext | void { 200 | const phpcs = findCheckRun(scenario, 'php', 'phpcs') 201 | 202 | phpcs.conclusion = Conclusion.Success 203 | delete phpcs.status 204 | 205 | const eslint = findCheckRun(scenario, 'node', 'eslint') 206 | 207 | eslint.conclusion = Conclusion.Success 208 | delete eslint.status 209 | }), 210 | ...actionInput({ 211 | token: 'FOO', 212 | collection: 'php', 213 | conclusion: 'cancelled', 214 | }), 215 | }) 216 | }) 217 | 218 | test('it updates all check from a collection if no id is set with multiple collections and impacts all', () => { 219 | run({ 220 | [action.envVariableName]: loadScenario(testScenarioPhpAndNode, function( 221 | scenario: GlobalContext, 222 | ): GlobalContext | void { 223 | const phpcs = findCheckRun(scenario, 'php', 'phpcs') 224 | 225 | phpcs.conclusion = Conclusion.Success 226 | delete phpcs.status 227 | 228 | const eslint = findCheckRun(scenario, 'node', 'eslint') 229 | 230 | eslint.conclusion = Conclusion.Success 231 | delete eslint.status 232 | }), 233 | ...actionInput({ 234 | token: 'FOO', 235 | conclusion: 'cancelled', 236 | }), 237 | }) 238 | }) 239 | 240 | test('it does not fail on failure by default', () => { 241 | run({ 242 | [action.envVariableName]: loadScenario(testScenarioPhpAndNode, function( 243 | scenario: GlobalContext, 244 | ): GlobalContext | void { 245 | // noop 246 | }), 247 | ...actionInput({ 248 | token: 'FOO', 249 | id: 'phpunit', 250 | conclusion: 'failure', 251 | }), 252 | }) 253 | }) 254 | 255 | test('it does not fail on neutral by default', () => { 256 | run({ 257 | [action.envVariableName]: loadScenario(testScenarioPhpAndNode, function( 258 | scenario: GlobalContext, 259 | ): GlobalContext | void { 260 | // noop 261 | }), 262 | ...actionInput({ 263 | token: 'FOO', 264 | id: 'phpunit', 265 | conclusion: 'neutral', 266 | }), 267 | }) 268 | }) 269 | 270 | test('it fails on failure if requested', () => { 271 | run({ 272 | [action.envVariableName]: loadScenario(testScenarioPhpAndNode, function( 273 | scenario: GlobalContext, 274 | ): GlobalContext | void { 275 | // noop 276 | }), 277 | ...actionInput({ 278 | token: 'FOO', 279 | id: 'phpunit', 280 | conclusion: 'failure', 281 | 'fail-on-error': 'true', 282 | }), 283 | }) 284 | }) 285 | 286 | test('it fails on neutral if requested', () => { 287 | run({ 288 | [action.envVariableName]: loadScenario(testScenarioPhpAndNode, function( 289 | scenario: GlobalContext, 290 | ): GlobalContext | void { 291 | // noop 292 | }), 293 | ...actionInput({ 294 | token: 'FOO', 295 | id: 'phpunit', 296 | conclusion: 'neutral', 297 | 'fail-on-neutral': 'true', 298 | }), 299 | }) 300 | }) 301 | --------------------------------------------------------------------------------