├── __tests__ ├── test.ts └── makeStatusRequestTests.ts ├── .github └── workflows │ ├── smoke-screen-tests.yml │ ├── build.yml │ └── test.yml ├── src ├── inputNames.ts ├── main.ts └── makeStatusRequest.ts ├── tsconfig.json ├── LICENSE ├── package.json ├── action.yml ├── .gitignore └── README.md /__tests__/test.ts: -------------------------------------------------------------------------------- 1 | import test from "ava" 2 | 3 | test("TODO", t=>t.pass()); -------------------------------------------------------------------------------- /.github/workflows/smoke-screen-tests.yml: -------------------------------------------------------------------------------- 1 | name: "smoke-screen-tests" 2 | on: 3 | push: 4 | branches-ignore: 5 | - master 6 | 7 | jobs: 8 | sst: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - run: | 13 | npm install 14 | npm test -------------------------------------------------------------------------------- /src/inputNames.ts: -------------------------------------------------------------------------------- 1 | 2 | export const INPUT_NAMES = { 3 | authToken: "authToken", 4 | owner: "owner", 5 | repo: "repository", 6 | state: "state", 7 | context: "context", 8 | sha: "sha", 9 | desc: "description", 10 | target_url: "target_url" 11 | } 12 | 13 | export default INPUT_NAMES; -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: "build" 2 | on: # rebuild any PRs and main branch changes 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: # make sure build/ci work properly 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - run: | 14 | npm install 15 | npm run all -------------------------------------------------------------------------------- /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", "__tests__/**/*"] 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": "github-status-action", 3 | "version": "1.1.0", 4 | "private": true, 5 | "description": "Github status action", 6 | "main": "lib/main.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "test": "ava", 10 | "pack": "ncc build", 11 | "all": "npm run build && npm test", 12 | "push": "npm run build && npm run pack && git add * && git commit -m \"ci: update built file\" && git push" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/sibz/github-status-action.git" 17 | }, 18 | "keywords": [ 19 | "actions", 20 | "node", 21 | "setup" 22 | ], 23 | "author": "Sibz@EntityZero", 24 | "license": "MIT", 25 | "dependencies": { 26 | "@actions/core": "^1.2.6", 27 | "@octokit/rest": "^17.6.0" 28 | }, 29 | "devDependencies": { 30 | "@octokit/types": "^2.12.2", 31 | "@types/node": "^12.7.12", 32 | "@zeit/ncc": "^0.20.5", 33 | "ava": "^3.8.1", 34 | "ts-node": "^8.9.1", 35 | "typescript": "^3.6.4" 36 | }, 37 | "ava": { 38 | "extensions": [ 39 | "ts" 40 | ], 41 | "require": [ 42 | "ts-node/register" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'github-status-action' 2 | description: 'Adds a check status on a commit, GitHub reports the last status added for a particular context.' 3 | author: 'Sibz@EntityZero' 4 | branding: 5 | icon: 'check' 6 | color: 'green' 7 | inputs: 8 | authToken: 9 | description: 'Use secrets.GITHUB_TOKEN or your own token if you need to trigger other workflows the use "on: status"' 10 | required: true 11 | state: 12 | description: 'The status of the check: success, error, failure or pending' 13 | required: true 14 | context: 15 | description: 'The context, this is displayed as the name of the check' 16 | default: 'default' 17 | required: false 18 | description: 19 | description: 'Short text explaining the status of the check' 20 | default: '' 21 | required: false 22 | owner: 23 | description: 'Repostory owner, defaults to context github.repository_owner if omited' 24 | default: ${{ github.repository_owner }} 25 | required: false 26 | repository: 27 | description: 'Repository, default to context github.repository if omited' 28 | default: ${{ github.repository }} 29 | required: false 30 | sha: 31 | description: 'SHA of commit to update status on, defaults to context github.sha' 32 | default: ${{ github.sha }} 33 | required: false 34 | target_url: 35 | description: 'URL/URI to use for further details.' 36 | required: false 37 | runs: 38 | using: 'node12' 39 | main: 'dist/index.js' 40 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: "test" 2 | on: # rebuild any PRs and main branch changes 3 | pull_request: 4 | push: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | test: # make sure the action works on a clean machine without building 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Test min args 14 | uses: ./ 15 | with: 16 | authToken: ${{secrets.GITHUB_TOKEN}} 17 | state: 'success' 18 | - name: Test with all args except owner/repo (success) 19 | uses: ./ 20 | with: 21 | authToken: ${{secrets.GITHUB_TOKEN}} 22 | context: "Test run" 23 | description: "Test with all args" 24 | target_url: "https://github.com/Sibz/github-status-action" 25 | sha: ${{github.event.pull_request.head.sha || github.sha}} 26 | state: 'success' 27 | - name: Test failing action 28 | uses: ./ 29 | with: 30 | authToken: ${{secrets.GITHUB_TOKEN}} 31 | context: "Test run failed" 32 | description: "Failed test" 33 | sha: ${{github.event.pull_request.head.sha || github.sha}} 34 | state: 'failure' 35 | - name: Test failing action now succeeded 36 | uses: ./ 37 | with: 38 | authToken: ${{secrets.GITHUB_TOKEN}} 39 | context: "Test run failed" 40 | description: "Failed test now succeeded" 41 | sha: ${{github.event.pull_request.head.sha || github.sha}} 42 | state: 'success' 43 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import { Octokit } from '@octokit/rest' 3 | import makeStatus, { StatusRequest } from './makeStatusRequest' 4 | import makeStatusRequest from './makeStatusRequest'; 5 | import { RequestParameters } from '@octokit/types'; 6 | 7 | async function run(): Promise { 8 | const authToken: string = core.getInput('authToken'); 9 | let octokit: Octokit | null = null; 10 | 11 | try { 12 | octokit = new Octokit({ 13 | auth: authToken, 14 | userAgent: "github-status-action", 15 | baseUrl: 'https://api.github.com', 16 | log: { 17 | debug: () => { }, 18 | info: () => { }, 19 | warn: console.warn, 20 | error: console.error 21 | }, 22 | request: { 23 | agent: undefined, 24 | fetch: undefined, 25 | timeout: 0 26 | } 27 | }); 28 | } catch (error) { 29 | core.setFailed("Error creating octokit:\n" + error.message); 30 | return; 31 | } 32 | 33 | if (octokit == null) { 34 | core.setFailed("Error creating octokit:\noctokit was null"); 35 | return; 36 | } 37 | 38 | let statusRequest: StatusRequest 39 | try { 40 | statusRequest = makeStatusRequest(); 41 | } 42 | catch (error) { 43 | core.setFailed(`Error creating status request object: ${error.message}`); 44 | return; 45 | } 46 | 47 | try { 48 | await octokit.repos.createStatus(statusRequest); 49 | } catch (error) { 50 | core.setFailed(`Error setting status:\n${error.message}\nRequest object:\n${JSON.stringify(statusRequest, null, 2)}`); 51 | } 52 | } 53 | 54 | run(); 55 | -------------------------------------------------------------------------------- /.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/makeStatusRequest.ts: -------------------------------------------------------------------------------- 1 | import * as actionsCore from '@actions/core' 2 | import inputNames from './inputNames'; 3 | import { RequestParameters } from '@octokit/types'; 4 | 5 | export type CommitState = "success" | "error" | "failure" | "pending"; 6 | export type StatusRequest = RequestParameters & Pick; 7 | export const ERR_INVALID_OWNER = "Input 'owner' must be a valid GitHub username"; 8 | export const ERR_INVALID_STATE = "Input 'state' must be one of success | error | failure | pending"; 9 | 10 | const regExUsername = /^[a-z\d](?:[a-z\d]|-(?=[a-z\d])){0,38}$/i; 11 | 12 | export default function makeStatusRequest(testCore: any | null = null): StatusRequest { 13 | let core: CoreActionsForTesting = 14 | testCore as CoreActionsForTesting ?? actionsCore as CoreActionsForTesting; 15 | 16 | let request: StatusRequest = {} as StatusRequest; 17 | 18 | request.context = core.getInput(inputNames.context); 19 | request.description = core.getInput(inputNames.desc); 20 | request.state = core.getInput(inputNames.state) as CommitState; 21 | request.owner = core.getInput(inputNames.owner); 22 | request.repo = core.getInput(inputNames.repo); 23 | request.sha = core.getInput(inputNames.sha); 24 | request.target_url = core.getInput(inputNames.target_url); 25 | 26 | if (!regExUsername.test(request.owner)) { 27 | throw new Error(ERR_INVALID_OWNER); 28 | } 29 | 30 | if (!validateState(request.state)) { 31 | throw new Error(ERR_INVALID_STATE); 32 | } 33 | 34 | if (request.repo.startsWith(`${request.owner}/`)) { 35 | request.repo = request.repo.replace(`${request.owner}/`, ''); 36 | } 37 | 38 | return request; 39 | } 40 | 41 | function validateState(state: any): boolean { 42 | return (state == "success" 43 | || state == "error" 44 | || state == "failure" 45 | || state == "pending"); 46 | } 47 | export interface CoreActionsForTesting { 48 | getInput: (arg: string) => string 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## This is no longer being maintained, a working fork is available here: 4 | 5 | https://github.com/guibranco/github-status-action-v2 6 | 7 | ## Marketplace: 8 | 9 | https://github.com/marketplace/actions/github-status-action-v2 10 | 11 | ---- 12 | 13 |

14 | github-status-action status 16 | github-status-action status 17 |

18 | 19 | # GitHub Status Action 20 | 21 | Adds a status update to a commit. GitHub will always show the latest state of a context. 22 | 23 | ## Usage 24 | 25 | ### Inputs 26 | 27 | * `authToken` (required) 28 | Use secrets.GITHUB_TOKEN or your own token if you need to trigger other workflows that use "on: status"' 29 | * `state` (required) 30 | The status of the check should only be `success`, `error`, `failure` or `pending` 31 | * `context` 32 | The context, this is displayed as the name of the check 33 | * `description` 34 | Short text explaining the status of the check 35 | * `owner` 36 | Repostory onwer, defaults to context github.repository_owner if omited 37 | * `repository` 38 | Repository, default to context github.repository if omited 39 | * `sha` 40 | SHA of commit to update status on, defaults to context github.sha 41 | *If using `on: pull_request` use `github.event.pull_request.head.sha`* 42 | * `target_url` 43 | Url to use for the details link. If omited no link is shown. 44 | 45 | ### Outputs 46 | None. 47 | 48 | ## Example 49 | ```yml 50 | name: "test" 51 | on: # run on any PRs and main branch changes 52 | pull_request: 53 | push: 54 | branches: 55 | - master 56 | 57 | jobs: 58 | test: # make sure the action works on a clean machine without building 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v2 62 | - name: Run the action # You would run your tests before this using the output to set state/desc 63 | uses: Sibz/github-status-action@v1 64 | with: 65 | authToken: ${{secrets.GITHUB_TOKEN}} 66 | context: 'Test run' 67 | description: 'Passed' 68 | state: 'success' 69 | sha: ${{github.event.pull_request.head.sha || github.sha}} 70 | ``` 71 | -------------------------------------------------------------------------------- /__tests__/makeStatusRequestTests.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import makeStatusRequest, { CoreActionsForTesting, CommitState, ERR_INVALID_OWNER, ERR_INVALID_STATE} from '../src/makeStatusRequest' 3 | import inputNames from '../src/inputNames' 4 | 5 | const INPUT_CONTEXT = "test context"; 6 | const INPUT_OWNER = "TestOwner"; 7 | const INPUT_OWNER_INVALID = "-TestOwner"; 8 | const INPUT_REPOSITORY = "Test.Repository-1"; 9 | const INPUT_REPOSITORY_WITHOWNER = INPUT_OWNER + "/Test.Repository-1"; 10 | const INPUT_STATE: CommitState = "success"; 11 | const INPUT_STATE_INVALID: CommitState = "failed" as CommitState; 12 | const INPUT_DESC = "Test Description"; 13 | const INPUT_SHA = "TestSHA"; 14 | const INPUT_TARGETURL = "test/uri"; 15 | 16 | const actionsCore: CoreActionsForTesting = { 17 | getInput: (arg:string) => { 18 | switch(arg) { 19 | case inputNames.context: 20 | return INPUT_CONTEXT; 21 | case inputNames.owner: 22 | return INPUT_OWNER; 23 | case inputNames.repo: 24 | return INPUT_REPOSITORY; 25 | case inputNames.state: 26 | return INPUT_STATE; 27 | case inputNames.desc: 28 | return INPUT_DESC; 29 | case inputNames.sha: 30 | return INPUT_SHA; 31 | case inputNames.target_url: 32 | return INPUT_TARGETURL; 33 | default: 34 | return "input not in test mock"; 35 | } 36 | } 37 | } 38 | const actionsCoreAlt1: CoreActionsForTesting = { 39 | getInput: (arg:string) => { 40 | switch(arg) { 41 | case inputNames.repo: 42 | return INPUT_REPOSITORY_WITHOWNER; 43 | default: 44 | return actionsCore.getInput(arg); 45 | } 46 | } 47 | } 48 | 49 | const actionsCoreAlt2: CoreActionsForTesting = { 50 | getInput: (arg:string) => { 51 | switch(arg) { 52 | case inputNames.owner: 53 | return INPUT_OWNER_INVALID; 54 | default: 55 | return actionsCore.getInput(arg); 56 | } 57 | } 58 | } 59 | 60 | const actionsCoreAlt3: CoreActionsForTesting = { 61 | getInput: (arg:string) => { 62 | switch(arg) { 63 | case inputNames.state: 64 | return INPUT_STATE_INVALID; 65 | default: 66 | return actionsCore.getInput(arg); 67 | } 68 | } 69 | } 70 | 71 | 72 | test("should getInput context", t=> { 73 | t.is(makeStatusRequest(actionsCore).context, INPUT_CONTEXT); 74 | }); 75 | test("should getInput owner", t=> { 76 | t.is(makeStatusRequest(actionsCore).owner, INPUT_OWNER); 77 | }); 78 | test("should getInput repo", t=> { 79 | t.is(makeStatusRequest(actionsCore).repo, INPUT_REPOSITORY); 80 | }); 81 | test("should getInput state", t=> { 82 | t.is(makeStatusRequest(actionsCore).state, INPUT_STATE); 83 | }); 84 | test("should getInput description", t=> { 85 | t.is(makeStatusRequest(actionsCore).description, INPUT_DESC); 86 | }); 87 | test("should getInput sha", t=> { 88 | t.is(makeStatusRequest(actionsCore).sha, INPUT_SHA); 89 | }); 90 | test("should getInput target_url", t=> { 91 | t.is(makeStatusRequest(actionsCore).target_url, INPUT_TARGETURL); 92 | }); 93 | 94 | test("should getInput repo and remove leading owner name", t=> { 95 | t.is(makeStatusRequest(actionsCoreAlt1).repo, INPUT_REPOSITORY); 96 | }); 97 | 98 | test("when owner is not a valid GitHub username, should throw", t=> { 99 | let err = t.throws(()=> makeStatusRequest(actionsCoreAlt2)); 100 | t.is(err.message, ERR_INVALID_OWNER) 101 | }); 102 | 103 | test("should validate state", t=> { 104 | let err = t.throws(()=> makeStatusRequest(actionsCoreAlt3)); 105 | t.is(err.message, ERR_INVALID_STATE) 106 | }); --------------------------------------------------------------------------------