├── .eslintignore ├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── slack-workflow.yml ├── slack.yml └── workflows │ ├── codeql-analysis.yml │ ├── custom.yml │ ├── manual.yml │ ├── schedule.yml │ ├── test.yml │ └── workflow.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── RELEASE.md ├── __tests__ ├── blocks.test.ts ├── config.test.ts ├── fixtures │ ├── create.json │ ├── delete.json │ ├── merge.json │ ├── pull_request.json │ ├── push.json │ ├── release.env.txt │ ├── release.json │ ├── schedule.env.txt │ ├── schedule.json │ ├── slack-blocks.yml │ ├── slack-legacy.yml │ ├── slack-workflow.yml │ ├── slack.json │ ├── workflow_dispatch.env.txt │ ├── workflow_dispatch.json │ ├── workflow_run.env.txt │ └── workflow_run.json ├── handlebars.test.ts ├── inputs.test.ts ├── job_inputs.test.ts ├── job_matrix.test.ts ├── job_status.test.ts ├── pull_request.test.ts ├── push.test.ts ├── release.test.ts ├── schedule.test.ts ├── workflow_dispatch.test.ts └── workflow_run.test.ts ├── action.yml ├── dist ├── index.js ├── index.js.map ├── licenses.txt └── sourcemap-register.js ├── docs └── images │ ├── example1.png │ ├── example2.png │ ├── example3.png │ ├── example4.png │ ├── example5.png │ └── example6.png ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── handlebars.ts ├── main.ts └── slack.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["jest", "@typescript-eslint", "github"], 3 | "extends": ["plugin:github/recommended"], 4 | "parser": "@typescript-eslint/parser", 5 | "parserOptions": { 6 | "ecmaVersion": 9, 7 | "sourceType": "module", 8 | "project": "./tsconfig.json" 9 | }, 10 | "rules": { 11 | "eslint-comments/no-use": "off", 12 | "import/no-namespace": "off", 13 | "no-unused-vars": "off", 14 | "@typescript-eslint/no-unused-vars": "error", 15 | "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}], 16 | "@typescript-eslint/no-require-imports": "error", 17 | "@typescript-eslint/array-type": "error", 18 | "@typescript-eslint/await-thenable": "error", 19 | "@typescript-eslint/ban-ts-comment": "error", 20 | "camelcase": "off", 21 | "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}], 22 | "@typescript-eslint/func-call-spacing": ["error", "never"], 23 | "@typescript-eslint/no-array-constructor": "error", 24 | "@typescript-eslint/no-empty-interface": "error", 25 | "@typescript-eslint/no-explicit-any": "error", 26 | "@typescript-eslint/no-extraneous-class": "error", 27 | "@typescript-eslint/no-for-in-array": "error", 28 | "@typescript-eslint/no-inferrable-types": "error", 29 | "@typescript-eslint/no-misused-new": "error", 30 | "@typescript-eslint/no-namespace": "error", 31 | "@typescript-eslint/no-non-null-assertion": "warn", 32 | "@typescript-eslint/consistent-type-assertions": "error", 33 | "@typescript-eslint/no-unnecessary-qualifier": "error", 34 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 35 | "@typescript-eslint/no-useless-constructor": "error", 36 | "@typescript-eslint/no-var-requires": "error", 37 | "@typescript-eslint/prefer-for-of": "warn", 38 | "@typescript-eslint/prefer-function-type": "warn", 39 | "@typescript-eslint/prefer-includes": "error", 40 | "@typescript-eslint/consistent-type-definitions": "error", 41 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 42 | "@typescript-eslint/promise-function-async": "error", 43 | "@typescript-eslint/require-array-sort-compare": "error", 44 | "@typescript-eslint/restrict-plus-operands": "error", 45 | "semi": "off", 46 | "@typescript-eslint/semi": ["error", "never"], 47 | "@typescript-eslint/type-annotation-spacing": "error", 48 | "@typescript-eslint/unbound-method": "error", 49 | "i18n-text/no-en": "off" 50 | }, 51 | "env": { 52 | "node": true, 53 | "es6": true, 54 | "jest/globals": true 55 | } 56 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/slack-workflow.yml: -------------------------------------------------------------------------------- 1 | username: GitHub-CI 2 | icon_url: https://octodex.github.com/images/femalecodertocat.png 3 | 4 | pretext: Triggered via {{eventName}} by {{actor}} {{default action "action"}} {{ref}} `{{diffRef}}` 5 | title: GitHub Actions 6 | title_link: https://support.github.com 7 | 8 | fallback: |- 9 | [GitHub] {{workflow}} #{{runNumber}} is {{jobStatus}} 10 | 11 | blocks: 12 | # author 13 | - type: context 14 | elements: 15 | - type: image 16 | image_url: '{{{sender.avatar_url}}}' 17 | alt_text: '{{sender.login}}' 18 | - type: mrkdwn 19 | text: "*<{{sender.html_url}}|{{sender.login}}>*" 20 | 21 | # text 22 | - type: section 23 | text: 24 | type: mrkdwn 25 | text: >- 26 | Workflow {{payload.workflow.name}} {{payload.workflow_run.status}} 27 | with {{payload.workflow_run.conclusion}} after 28 | {{pluralize payload.workflow_run.run_attempt 'attempt'}} 29 | accessory: 30 | type: button 31 | text: 32 | type: plain_text 33 | text: View 34 | value: workflow_run_{{payload.workflow_run.workflow_id}} 35 | url: '{{payload.workflow_run.html_url}}' 36 | action_id: button-action 37 | 38 | # fields 39 | - type: section 40 | fields: 41 | - type: mrkdwn 42 | text: "*Jobs*\n{{payload.workflow_run.jobs_url}}" 43 | - type: mrkdwn 44 | text: "*Logs*\n{{payload.workflow_run.logs_url}}" 45 | 46 | # footer 47 | - type: context 48 | elements: 49 | - type: image 50 | image_url: '{{footer_icon}}' 51 | alt_text: github 52 | - type: mrkdwn 53 | text: '{{{footer}}} | ' 54 | 55 | footer: >- 56 | <{{repositoryUrl}}|{{repositoryName}}> {{workflow}} #{{runNumber}} 57 | 58 | colors: 59 | success: '#5DADE2' 60 | failure: '#884EA0' 61 | cancelled: '#A569BD' 62 | default: '#7D3C98' 63 | 64 | icons: 65 | success: ':white_check_mark:' 66 | failure: ':grimacing:' 67 | cancelled: ':x:' 68 | skipped: ':heavy_minus_sign:' 69 | default: ':interrobang:' 70 | -------------------------------------------------------------------------------- /.github/slack.yml: -------------------------------------------------------------------------------- 1 | username: GitHub-CI 2 | icon_url: https://octodex.github.com/images/femalecodertocat.png 3 | 4 | pretext: Triggered via {{eventName}} by {{actor}} {{default action "action"}} {{ref}} `{{diffRef}}` 5 | title: GitHub Actions 6 | title_link: https://support.github.com 7 | 8 | text: &text | 9 | *<{{workflowRunUrl}}|Workflow _{{workflow}}_ job _{{jobName}}_ triggered by _{{eventName}}_ is _{{jobStatus}}_>* for <{{refUrl}}|`{{ref}}`> 10 | {{#if description}}<{{diffUrl}}|`{{diffRef}}`> - {{{description}}}{{/if}} 11 | {{#if payload.commits}} 12 | *Commits* 13 | {{#each payload.commits}} 14 | <{{this.url}}|`{{truncate this.id 8}}`> - {{this.message}} 15 | {{/each}} 16 | {{/if}} 17 | 18 | fallback: |- 19 | [GitHub] {{workflow}} #{{runNumber}} {{jobName}} is {{jobStatus}} 20 | 21 | fields: 22 | - title: Job Steps 23 | value: "{{#each jobSteps}}{{icon this.outcome}} {{@key}}\n{{/each}}" 24 | short: false 25 | - title: Job Matrix 26 | value: "{{#each jobMatrix}}{{@key}}: {{this}}\n{{/each}}" 27 | short: false 28 | - title: Job Inputs 29 | value: "{{#each jobInputs}}{{@key}}: {{this}}\n{{/each}}" 30 | short: false 31 | - title: Workflow 32 | value: "<{{{workflowUrl}}}|{{workflow}}>" 33 | short: true 34 | - title: Git Ref 35 | value: "{{ref}} ({{refType}})" 36 | short: true 37 | - title: Run ID 38 | value: |- 39 | <{{workflowRunUrl}}|{{runId}}> 40 | short: true 41 | - title: Run Number 42 | value: "{{runNumber}}" 43 | short: true 44 | - title: Actor 45 | value: "{{actor}}" 46 | short: true 47 | - title: Job Status 48 | value: "{{jobStatus}}" 49 | short: true 50 | 51 | blocks: 52 | # author 53 | - type: context 54 | elements: 55 | - type: image 56 | image_url: '{{{sender.avatar_url}}}' 57 | alt_text: '{{sender.login}}' 58 | - type: mrkdwn 59 | text: "*<{{sender.html_url}}|{{sender.login}}>*" 60 | 61 | # title 62 | - type: section 63 | text: 64 | type: mrkdwn 65 | text: | 66 | *<{{title_link}}|{{title}}>* 67 | 68 | # text 69 | - type: section 70 | text: 71 | type: mrkdwn 72 | text: *text 73 | accessory: 74 | type: button 75 | text: 76 | type: plain_text 77 | text: View 78 | value: workflow_run_{{runId}} 79 | url: '{{workflowRunUrl}}' 80 | action_id: button-action 81 | 82 | # fields 83 | - type: section 84 | fields: 85 | - type: mrkdwn 86 | text: |- 87 | *Job Steps* 88 | {{#each jobSteps}}{{#ifneq this.outcome 'skipped'}}{{icon this.outcome}} {{@key}} 89 | {{/ifneq}}{{/each}} 90 | - type: section 91 | fields: 92 | - type: mrkdwn 93 | text: "*Workflow*\n<{{{workflowUrl}}}|{{workflow}}>" 94 | - type: mrkdwn 95 | text: "*Git Ref*\n{{ref}} ({{refType}})" 96 | - type: mrkdwn 97 | text: |- 98 | *Run ID* 99 | <{{workflowRunUrl}}|{{runId}}> 100 | - type: mrkdwn 101 | text: "*Run Number*\n{{runNumber}}" 102 | - type: mrkdwn 103 | text: "*Actor*\n{{actor}}" 104 | - type: mrkdwn 105 | text: "*Job Status*\n{{jobStatus}}" 106 | 107 | # footer 108 | - type: context 109 | elements: 110 | - type: image 111 | image_url: '{{footer_icon}}' 112 | alt_text: github 113 | - type: mrkdwn 114 | text: '{{{footer}}} | ' 115 | 116 | footer: >- 117 | <{{repositoryUrl}}|{{repositoryName}}> {{workflow}} #{{runNumber}} 118 | 119 | colors: 120 | success: '#5DADE2' 121 | failure: '#884EA0' 122 | cancelled: '#A569BD' 123 | default: '#7D3C98' 124 | 125 | icons: 126 | success: ':white_check_mark:' 127 | failure: ':grimacing:' 128 | cancelled: ':x:' 129 | skipped: ':heavy_minus_sign:' 130 | default: ':interrobang:' 131 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '31 2 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v4 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v3 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v3 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v3 71 | -------------------------------------------------------------------------------- /.github/workflows/custom.yml: -------------------------------------------------------------------------------- 1 | name: "custom-test" 2 | on: 3 | repository_dispatch: 4 | types: [custom] 5 | 6 | jobs: 7 | custom: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - id: checkout 11 | uses: actions/checkout@v4 12 | - id: build 13 | run: | 14 | npm install 15 | npm run all 16 | - uses: ./ 17 | with: 18 | status: ${{ job.status }} 19 | steps: ${{ toJson(steps) }} 20 | channel: '#actions' 21 | if: always() 22 | -------------------------------------------------------------------------------- /.github/workflows/manual.yml: -------------------------------------------------------------------------------- 1 | name: "manual-test" 2 | on: workflow_dispatch 3 | 4 | env: 5 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - id: checkout 12 | uses: actions/checkout@v4 13 | - id: build 14 | run: | 15 | npm install 16 | npm run all 17 | - uses: ./ 18 | with: 19 | status: ${{ job.status }} 20 | steps: ${{ toJson(steps) }} 21 | channel: '#actions' 22 | message: Manual test of {{ env.GITHUB_REF_NAME }} branch... 23 | if: always() 24 | -------------------------------------------------------------------------------- /.github/workflows/schedule.yml: -------------------------------------------------------------------------------- 1 | name: "schedule-test" 2 | on: 3 | schedule: 4 | - cron: '0 10 * * *' 5 | 6 | env: 7 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - id: checkout 14 | uses: actions/checkout@v4 15 | - id: build 16 | run: | 17 | npm install 18 | npm run all 19 | - uses: ./ 20 | with: 21 | status: ${{ job.status }} 22 | steps: ${{ toJson(steps) }} 23 | channel: '#actions' 24 | if: always() 25 | -------------------------------------------------------------------------------- /.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 | tags: 9 | - 'v*' 10 | 11 | env: 12 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 13 | 14 | jobs: 15 | lint: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - id: checkout 19 | uses: actions/checkout@v4 20 | - id: build 21 | run: | 22 | npm install 23 | - id: format-check 24 | run: | 25 | npm run format-check 26 | - id: lint 27 | run: | 28 | npm run lint 29 | - uses: ./ 30 | with: 31 | status: ${{ job.status }} 32 | channel: '#actions' 33 | message: lint error 34 | if: failure() 35 | 36 | test: 37 | runs-on: ubuntu-latest 38 | steps: 39 | - id: checkout 40 | uses: actions/checkout@v4 41 | with: 42 | ref: ${{ github.event.pull_request.head.sha }} 43 | - uses: ./ 44 | with: 45 | status: starting 46 | channel: '#actions' 47 | - id: dump_github 48 | name: Dump GitHub context 49 | env: 50 | GITHUB_CONTEXT: ${{ toJson(github) }} 51 | run: echo "$GITHUB_CONTEXT" 52 | - id: dump_job 53 | name: Dump job context 54 | env: 55 | JOB_CONTEXT: ${{ toJson(job) }} 56 | run: echo "$JOB_CONTEXT" 57 | - id: dump_steps 58 | name: Dump steps context 59 | env: 60 | STEPS_CONTEXT: ${{ toJson(steps) }} 61 | run: echo "$STEPS_CONTEXT" 62 | - id: test 63 | run: | 64 | npm install 65 | npm run test 66 | - uses: ./ 67 | with: 68 | status: ${{ job.status }} 69 | steps: ${{ toJson(steps) }} 70 | channel: '#actions' 71 | config: __tests__/fixtures/slack-legacy.yml 72 | if: always() 73 | - uses: ./ 74 | with: 75 | status: ${{ job.status }} 76 | steps: ${{ toJson(steps) }} 77 | channel: '#actions' 78 | config: __tests__/fixtures/slack-blocks.yml 79 | if: always() 80 | 81 | build: 82 | runs-on: ubuntu-latest 83 | steps: 84 | - id: checkout 85 | uses: actions/checkout@v4 86 | with: 87 | ref: ${{ github.event.pull_request.head.sha }} 88 | - id: install-nodejs 89 | uses: actions/setup-node@v4 90 | with: 91 | node-version: 20 92 | - id: build 93 | run: | 94 | npm install 95 | npm run all 96 | - uses: ./ 97 | with: 98 | status: ${{ job.status }} 99 | steps: ${{ toJson(steps) }} 100 | channel: '#actions' 101 | if: always() 102 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: "workflow-run" 2 | on: 3 | workflow_run: 4 | workflows: ["build-test"] 5 | # branches: [master] 6 | # types: 7 | # - completed 8 | # - requested 9 | 10 | env: 11 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 12 | 13 | jobs: 14 | on-success: 15 | runs-on: ubuntu-latest 16 | if: ${{ github.event.workflow_run.conclusion == 'success' }} 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: ./ 20 | with: 21 | webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} 22 | status: ${{ github.event.workflow_run.conclusion }} 23 | channel: '#actions' 24 | config: .github/slack-workflow.yml 25 | if: always() 26 | 27 | on-failure: 28 | runs-on: ubuntu-latest 29 | if: ${{ github.event.workflow_run.conclusion == 'failure' }} 30 | steps: 31 | - uses: actions/checkout@v4 32 | - uses: ./ 33 | with: 34 | status: ${{ github.event.workflow_run.conclusion }} 35 | channel: '#actions' 36 | config: .github/slack-workflow.yml 37 | if: always() 38 | -------------------------------------------------------------------------------- /.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/**/* 100 | 101 | # IDE files 102 | .idea 103 | .vs 104 | .vscode 105 | 106 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | lib/ 3 | node_modules/ -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": false, 6 | "singleQuote": true, 7 | "trailingComma": "none", 8 | "bracketSpacing": false, 9 | "arrowParens": "avoid", 10 | "parser": "typescript" 11 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 |

2 | typescript-action status 3 |

4 | 5 | # Create a JavaScript Action using TypeScript 6 | 7 | Use this template to bootstrap the creation of a TypeScript action.:rocket: 8 | 9 | This template includes compilation support, tests, a validation workflow, publishing, and versioning guidance. 10 | 11 | If you are new, there's also a simpler introduction. See the [Hello World JavaScript Action](https://github.com/actions/hello-world-javascript-action) 12 | 13 | ## Create an action from this template 14 | 15 | Click the `Use this Template` and provide the new repo details for your action 16 | 17 | ## Code in Main 18 | 19 | > First, you'll need to have a reasonably modern version of `node` handy. This won't work with versions older than 9, for instance. 20 | 21 | Install the dependencies 22 | ```bash 23 | $ npm install 24 | ``` 25 | 26 | Build the typescript and package it for distribution 27 | ```bash 28 | $ npm run build && npm run package 29 | ``` 30 | 31 | Run the tests :heavy_check_mark: 32 | ```bash 33 | $ npm test 34 | 35 | PASS ./index.test.js 36 | ✓ throws invalid number (3ms) 37 | ✓ wait 500 ms (504ms) 38 | ✓ test runs (95ms) 39 | 40 | ... 41 | ``` 42 | 43 | ## Change action.yml 44 | 45 | The action.yml defines the inputs and output for your action. 46 | 47 | Update the action.yml with your name, description, inputs and outputs for your action. 48 | 49 | See the [documentation](https://help.github.com/en/articles/metadata-syntax-for-github-actions) 50 | 51 | ## Change the Code 52 | 53 | Most toolkit and CI/CD operations involve async operations so the action is run in an async function. 54 | 55 | ```javascript 56 | import * as core from '@actions/core'; 57 | ... 58 | 59 | async function run() { 60 | try { 61 | ... 62 | } 63 | catch (error) { 64 | core.setFailed(error.message); 65 | } 66 | } 67 | 68 | run() 69 | ``` 70 | 71 | See the [toolkit documentation](https://github.com/actions/toolkit/blob/master/README.md#packages) for the various packages. 72 | 73 | ## Publish to a distribution branch 74 | 75 | Actions are run from GitHub repos so we will checkin the packed dist folder. 76 | 77 | Then run [ncc](https://github.com/zeit/ncc) and push the results: 78 | ```bash 79 | $ npm run package 80 | $ git add dist 81 | $ git commit -a -m "prod dependencies" 82 | $ git push origin releases/v1 83 | ``` 84 | 85 | Note: We recommend using the `--license` option for ncc, which will create a license file for all of the production node modules used in your project. 86 | 87 | Your action is now published! :rocket: 88 | 89 | See the [versioning documentation](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) 90 | 91 | ## Validate 92 | 93 | You can now validate the action by referencing `./` in a workflow in your repo (see [test.yml](.github/workflows/test.yml)) 94 | 95 | ```yaml 96 | uses: ./ 97 | with: 98 | milliseconds: 1000 99 | ``` 100 | 101 | See the [actions tab](https://github.com/actions/typescript-action/actions) for runs of this action! :rocket: 102 | 103 | ## Usage: 104 | 105 | After testing you can [create a v1 tag](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) to reference the stable and latest V1 action -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2020-2024 Nick Satterly 5 | Copyright (c) 2018 GitHub, Inc. and contributors 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in 15 | all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #!make 2 | 3 | TEST_REGEX ?= *.ts 4 | 5 | .DEFAULT_GOAL:=help 6 | 7 | all: 8 | npm run all 9 | 10 | ## install - Install dependencies. 11 | install: 12 | npm install 13 | 14 | ## format - Code formatter. 15 | format: 16 | npm run format 17 | 18 | ## lint - Source code linter. 19 | lint: 20 | npm run lint:fix 21 | 22 | ## test - Run unit tests. 23 | test: 24 | npm test 25 | 26 | ## test.only - Only run defined unit tests. 27 | test.only: 28 | npm test -- $(TEST_REGEX) 29 | 30 | ## test.package - Run ncc compiled code. 31 | test.package: 32 | npm run package:test 33 | 34 | ## build - Build and package. 35 | build: 36 | npm run build && npm run package 37 | 38 | ## help - Show this help. 39 | help: Makefile 40 | @echo '' 41 | @echo 'Usage:' 42 | @echo ' make [TARGET]' 43 | @echo '' 44 | @echo 'Targets:' 45 | @sed -n 's/^##//p' $< 46 | @echo '' 47 | 48 | @echo 'Add project-specific env variables to .env file:' 49 | @echo 'PROJECT=$(PROJECT)' 50 | 51 | .PHONY: help format lint test build all 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build-test](https://github.com/act10ns/slack/actions/workflows/test.yml/badge.svg)](https://github.com/act10ns/slack/actions/workflows/test.yml) 2 | 3 | # Slack messages for GitHub Actions workflows, jobs and steps 4 | 5 | A simple and flexible Slack integration with GitHub Actions. 6 | 7 | 8 | 9 | ## Features 10 | 11 | * Advanced users can use a [configuration file](#config-optional) and Handlebars templates to configure every aspect of the Slack message. 12 | 13 | * In addition to "legacy" attachments, rich messages can be created using [layout blocks](https://api.slack.com/messaging/composing/layouts) for flexible message visualisation and interactivity. 14 | 15 | ## Configuration 16 | 17 | ### Environment Variables (`env`) 18 | 19 | #### `SLACK_WEBHOOK_URL` (required) 20 | 21 | Create a Slack Webhook URL using either the 22 | [Incoming Webhooks App](https://slack.com/apps/A0F7XDUAZ-incoming-webhooks?next_id=0) 23 | (preferred) or by attaching an incoming webhook to an existing 24 | [Slack App](https://api.slack.com/apps) (beware, channel override not possible 25 | when using a Slack App): 26 | 27 | env: 28 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 29 | 30 | ### Input Parameters (`with`) 31 | 32 | #### `webhook-url` (optional) 33 | 34 | Only required if the `SLACK_WEBHOOK_URL` environment variable is not set. 35 | 36 | with: 37 | webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} 38 | 39 | #### `status` (required) 40 | 41 | The `status` must be defined. It can either be the current job status 42 | using: 43 | 44 | with: 45 | status: ${{ job.status }} 46 | 47 | or a hardcoded custom status such as "starting" or "in progress": 48 | 49 | with: 50 | status: in progress 51 | 52 | #### `steps` (optional) 53 | 54 | The individual status of job steps can be included in the Slack 55 | message using: 56 | 57 | with: 58 | status: ${{ job.status }} 59 | steps: ${{ toJson(steps) }} 60 | 61 | **Note: Only steps that have a "step id" will be reported on. See example below.** 62 | 63 | #### `matrix` (optional) 64 | Parameters for [matrix jobs](https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs) can be included in Slack messages: 65 | 66 | with: 67 | status: ${{ job.status }} 68 | matrix: ${{ toJson(matrix) }} 69 | 70 | 71 | 72 | #### `inputs` (optional) 73 | Parameters for [`workflow_call`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_call) or [`workflow_dispatch`](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch) events can be included in Slack messages: 74 | 75 | with: 76 | status: ${{ job.status }} 77 | inputs: ${{ toJson(inputs) }} 78 | 79 | 80 | 81 | 82 | 83 | #### `channel` (optional) 84 | 85 | To override the channel or to send the Slack message to an individual 86 | use: 87 | 88 | with: 89 | status: ${{ job.status }} 90 | channel: '#workflows' 91 | 92 | **Note: To override the channel the Slack webhook URL must be an 93 | Incoming Webhook URL. See https://api.slack.com/faq#incoming_webhooks** 94 | 95 | ### `message` (optional) 96 | 97 | To override the slack message use: 98 | 99 | with: 100 | status: ${{ job.status }} 101 | channel: '#workflows' 102 | message: Deploying {{ env.GITHUB_REF_NAME }} branch 103 | 104 | ### `config` (optional) 105 | 106 | A configuration file can be used to customise the following Slack message fields: 107 | 108 | - `username` 109 | - `icon_url` 110 | - `pretext` 111 | - `title` and `title_link` 112 | - `text` 113 | - `fallback` plain text summary used for dumb clients and notifications 114 | - `fields` title, value and short/long 115 | - `blocks` including `actions`, `context`, `divider`, `file`, `header`, `image`, `input` and `section` blocks 116 | - message `footer` 117 | - border `colors` based job status `success`, `failure`, `cancelled`. valid colors are `good` (green), `warning` (yellow), `danger` (red) or any hex color code eg. `#439FE0` 118 | - `icons` for step status `success`, `failure`, `cancelled`, `skipped`, and a default 119 | 120 | Default: `.github/slack.yml` 121 | 122 | with: 123 | status: ${{ job.status }} 124 | config: .github/config/slack.yml 125 | 126 | The following Slack [message fields](https://api.slack.com/reference/messaging/attachments) and 127 | [block layouts](https://api.slack.com/reference/block-kit/blocks) support templating using 128 | [Handlebars.js](https://handlebarsjs.com/guide/) format: 129 | 130 | - `pretext` 131 | - `title` 132 | - `text` and `message` 133 | - `fallback` 134 | - `fields` `title` and `value` 135 | - `blocks` 136 | 137 | **Supported Template variables** 138 | 139 | `env.*`, `payload.*`, `jobName`, `jobStatus`, `jobSteps`, `jobMatrix`, 140 | `eventName`, `workflow`, `workflowUrl`, `workflowRunUrl`, `repositoryName`, `repositoryUrl`, `runId`, `runNumber`, `sha`, `shortSha`, `branch`, `actor`, `action`, `ref`, `refType`, `refUrl`, `diffRef`, `diffUrl`, `description`, `sender` 141 | 142 | **Helper Functions** 143 | 144 | Apart from the [standard helper functions](https://handlebarsjs.com/guide/builtin-helpers.html#if) such as `#if` and `#each` there are also a few custom 145 | ones: 146 | 147 | - `icon` converts a job status into an icon eg. `{{icon jobStatus}}` 148 | - `json` dumps the value as a JSON string eg. `{{json payload.commits}}` 149 | - `truncate` cuts the string at the limit eg. `{{truncate sha 8}}` 150 | - `default` allows a alternative or default value eg. `{{default headRef "master"}}` 151 | - `pluralize` outputs different text based on item count eg. `{{pluralize requested_reviewers "reviewer" "reviewers"}}` (if only singular form is given plural is derived by adding an "s") 152 | 153 | - `eq`, `neq`, `not`, `and`, and `or` can be used as logical operators eg. `{{#if (and (not has_issues) (or has_pages has_wiki))}}yes{{else}}no{{/if}}` 154 | - `#ifeq` and `#ifneq` test for variable equality or not eg. `{{#ifneq event_name "create"}}yes{{else}}no{{/ifneq}}` 155 | 156 | **Example Using Config File** 157 | 158 | To generate the message format below use the `slack.yml` configuration file that follows. 159 | 160 | 161 | 162 | *Example Configuration File: slack.yml* 163 | 164 | ``` 165 | username: GitHub-CI 166 | icon_url: https://octodex.github.com/images/mona-the-rivetertocat.png 167 | 168 | pretext: Triggered via {{eventName}} by {{actor}} {{or action "action"}} {{ref}} `{{diffRef}}` 169 | title: GitHub Actions 170 | title_link: https://support.github.com 171 | 172 | text: | 173 | *<{{workflowRunUrl}}|Workflow _{{workflow}}_ job _{{jobName}}_ triggered by _{{eventName}}_ is _{{jobStatus}}_>* for <{{refUrl}}|`{{ref}}`> 174 | {{#if description}}<{{diffUrl}}|`{{diffRef}}`> - {{description}}{{/if}} 175 | {{#if payload.commits}} 176 | *Commits* 177 | {{#each payload.commits}} 178 | <{{this.url}}|`{{truncate this.id 8}}`> - {{this.message}} 179 | {{/each}} 180 | {{/if}} 181 | 182 | fallback: |- 183 | [GitHub] {{workflow}} #{{runNumber}} {{jobName}} is {{jobStatus}} 184 | 185 | fields: 186 | - title: Job Steps 187 | value: "{{#each jobSteps}}{{icon this.outcome}} {{@key}}\n{{/each}}" 188 | short: false 189 | - title: Job Matrix 190 | value: "{{#each jobMatrix}}{{@key}}: {{this}}\n{{/each}}" 191 | short: false 192 | - title: Workflow 193 | value: "<{{workflowUrl}}|{{workflow}}>" 194 | short: true 195 | - title: Git Ref 196 | value: "{{ref}} ({{refType}})" 197 | short: true 198 | - title: Run ID 199 | value: |- 200 | <{{workflowRunUrl}}|{{runId}}> 201 | short: true 202 | - title: Run Number 203 | value: "{{runNumber}}" 204 | short: true 205 | - title: Actor 206 | value: "{{actor}}" 207 | short: true 208 | - title: Job Status 209 | value: "{{jobStatus}}" 210 | short: true 211 | 212 | footer: >- 213 | <{{repositoryUrl}}|{{repositoryName}}> {{workflow}} #{{runNumber}} 214 | 215 | colors: 216 | success: '#5DADE2' 217 | failure: '#884EA0' 218 | cancelled: '#A569BD' 219 | default: '#7D3C98' 220 | 221 | icons: 222 | success: ':white_check_mark:' 223 | failure: ':grimacing:' 224 | cancelled: ':x:' 225 | skipped: ':heavy_minus_sign:' 226 | default: ':interrobang:' 227 | ``` 228 | 229 | *Notes:* 230 | 231 | * If template expressions occur at the start of a string the string must be double-quoted eg. `pretext: "{{eventName}} triggered by {{actor}}"` 232 | * Use [YAML multiline string formats](https://yaml-multiline.info/) `|`, `>`, `|-` and `>-` or double-quotes `"\n"` to control new lines 233 | * Use `~` (tilde) character to control whitepace when looping see [Whitespace control](https://handlebarsjs.com/guide/expressions.html#whitespace-control) 234 | 235 | ### Conditionals (`if`) 236 | 237 | To ensure the Slack message is sent even if the job fails add the 238 | `always()` function: 239 | 240 | if: always() 241 | 242 | or use a specific status function to only run when the job status 243 | matches. All possible status check functions are: 244 | 245 | * `success()` (default) 246 | * `always()` 247 | * `cancelled()` 248 | * `failure()` 249 | 250 | ## Examples 251 | 252 | To send a Slack message when a workflow job has completed add the 253 | following as the last step of the job: 254 | 255 | - uses: act10ns/slack@v2 256 | with: 257 | status: ${{ job.status }} 258 | if: always() 259 | 260 | To include statuses for each Job Step in the message include the 261 | `steps` input (making sure to use the `toJSON` function): 262 | 263 | - uses: act10ns/slack@v2 264 | with: 265 | status: ${{ job.status }} 266 | steps: ${{ toJson(steps) }} 267 | if: always() 268 | 269 | Only steps that have a "step id" assigned to them will be reported on: 270 | 271 | - name: Build 272 | id: build 273 | run: | 274 | npm install 275 | npm run build 276 | 277 | The default Slack channel for the configured webhook can be overridden 278 | using either another channel name `#channel` or a username `@username`. 279 | 280 | - uses: act10ns/slack@v2 281 | with: 282 | status: ${{ job.status }} 283 | channel: '#workflows' 284 | 285 | or 286 | 287 | - uses: act10ns/slack@v2 288 | with: 289 | status: ${{ job.status }} 290 | channel: '@nick' 291 | 292 | ### Complete example 293 | 294 | name: Docker Build and Push 295 | 296 | on: 297 | push: 298 | branches: [ master, release/* ] 299 | 300 | jobs: 301 | build: 302 | runs-on: ubuntu-latest 303 | env: 304 | REPOSITORY_URL: docker.pkg.github.com 305 | IMAGE_NAME: ${{ github.repository }}/alerta-cli 306 | SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} 307 | steps: 308 | - uses: act10ns/slack@v2 309 | with: 310 | status: starting 311 | channel: '#workflows' 312 | message: Starting Docker Build and Push... 313 | if: always() 314 | - name: Checkout 315 | uses: actions/checkout@v4 316 | - name: Variables 317 | id: vars 318 | run: echo "::set-output name=SHORT_COMMIT_ID::$(git rev-parse --short HEAD)" 319 | - name: Build image 320 | id: docker-build 321 | run: >- 322 | docker build 323 | -t $IMAGE_NAME 324 | -t $REPOSITORY_URL/$IMAGE_NAME:${{ steps.vars.outputs.SHORT_COMMIT_ID }} 325 | -t $REPOSITORY_URL/$IMAGE_NAME:latest . 326 | - name: Docker Login 327 | env: 328 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 329 | DOCKER_PASSWORD: ${{ secrets.GITHUB_TOKEN }} 330 | run: docker login $REPOSITORY_URL --username "$DOCKER_USERNAME" --password "$DOCKER_PASSWORD" 331 | - name: Publish Image 332 | id: docker-push 333 | run: docker push $REPOSITORY_URL/$IMAGE_NAME 334 | 335 | - uses: act10ns/slack@v2 336 | with: 337 | status: ${{ job.status }} 338 | steps: ${{ toJson(steps) }} 339 | channel: '#workflows' 340 | if: always() 341 | 342 | The above "Docker Build and Push" workflow will appear in Slack as: 343 | 344 | 345 | 346 | ## Troubleshooting 347 | 348 | To enable runner diagnostic logging set the `ACTIONS_RUNNER_DEBUG` secret to `true`. 349 | 350 | To enable step debug logging set the `ACTIONS_STEP_DEBUG` secret to `true`. 351 | 352 | See https://docs.github.com/en/free-pro-team@latest/actions/managing-workflow-runs/enabling-debug-logging 353 | 354 | ## References 355 | 356 | * GitHub Actions Toolkit https://github.com/actions/toolkit/tree/main/packages/github 357 | * GitHub Actions Starter Workflows https://github.com/actions/starter-workflows 358 | * Slack Incoming Webhooks https://slack.com/apps/A0F7XDUAZ-incoming-webhooks?next_id=0 359 | * Env vars https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables 360 | * Webhook Payloads https://docs.github.com/en/free-pro-team@latest/developers/webhooks-and-events/webhook-events-and-payloads#webhook-payload-object-common-properties 361 | * GitHub Actions Cheat Sheet https://github.github.io/actions-cheat-sheet/actions-cheat-sheet.html 362 | * Slack Secondary message attachments https://api.slack.com/reference/messaging/attachments 363 | * Handlebars Language Guide https://handlebarsjs.com/guide/ 364 | * YAML multiline string formats https://yaml-multiline.info/ 365 | * Migrate your legacy message compositions to blocks https://api.slack.com/messaging/attachments-to-blocks 366 | 367 | ## License 368 | 369 | Copyright (c) 2020-2024 Nick Satterly. Available under the MIT License. 370 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | ## Setup 4 | 5 | ```bash 6 | $ brew install node@20 7 | $ make install 8 | ``` 9 | 10 | ## Develop 11 | 12 | ```bash 13 | $ make format 14 | $ make lint 15 | $ make test 16 | ``` 17 | 18 | ## Publish 19 | 20 | ```bash 21 | $ vi package.json 22 | $ vi package-lock.json 23 | $ make build 24 | $ git add . 25 | $ git commit -m 'Bump version 1.0.13 -> 1.1.0' 26 | $ git tag -a v1.1.0 -m 'version 1.1.0' 27 | $ git push --follow-tags 28 | $ git tag -fa v1 -m "Update v1 tag" 29 | $ git push origin v1 --force 30 | ``` 31 | 32 | Go to [GitHub Releases](https://github.com/act10ns/slack/releases) and create a new release 33 | 34 | ## References 35 | 36 | See [Versioning](https://github.com/actions/toolkit/blob/master/docs/action-versioning.md) to understand version strategy. 37 | 38 | See [Publishing actions in GitHub Marketplace](https://docs.github.com/en/actions/creating-actions/publishing-actions-in-github-marketplace) for general information about how to make actions available on GitHub. 39 | -------------------------------------------------------------------------------- /__tests__/blocks.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send, ConfigOptions} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | import * as yaml from 'js-yaml' 7 | 8 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 9 | const jobName = 'CI Tests' 10 | const jobStatus = 'failure' 11 | const jobSteps = { 12 | 'install-deps': { 13 | outputs: {}, 14 | outcome: 'success', 15 | conclusion: 'success' 16 | }, 17 | hooks: { 18 | outputs: {}, 19 | outcome: 'cancelled', 20 | conclusion: 'cancelled' 21 | }, 22 | lint: { 23 | outputs: {}, 24 | outcome: 'failure', 25 | conclusion: 'failure' 26 | }, 27 | types: { 28 | outputs: {}, 29 | outcome: 'skipped', 30 | conclusion: 'skipped' 31 | }, 32 | 'unit-test': { 33 | outputs: {}, 34 | outcome: 'skipped', 35 | conclusion: 'skipped' 36 | }, 37 | 'integration-test': { 38 | outputs: {}, 39 | outcome: 'failure', 40 | conclusion: 'failure' 41 | } 42 | } 43 | const jobMatrix = {} 44 | const jobInputs = {} 45 | const channel = '#github-ci' 46 | 47 | // mock github context 48 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/push.json', 'utf-8')) 49 | 50 | github.context.payload = dump.event 51 | github.context.eventName = dump.event_name 52 | github.context.sha = dump.sha 53 | github.context.ref = dump.ref 54 | github.context.workflow = dump.workflow 55 | github.context.action = dump.action 56 | github.context.actor = dump.actor 57 | 58 | process.env.CI = 'true' 59 | process.env.GITHUB_WORKFLOW = 'build-test' 60 | process.env.GITHUB_RUN_ID = '100143423' 61 | process.env.GITHUB_RUN_NUMBER = '8' 62 | process.env.GITHUB_ACTION = 'self2' 63 | process.env.GITHUB_ACTIONS = 'true' 64 | process.env.GITHUB_ACTOR = 'satterly' 65 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 66 | process.env.GITHUB_EVENT_NAME = 'push' 67 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 68 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 69 | process.env.GITHUB_SHA = '68d48876e0794fba714cb331a1624af6b20942d8' 70 | process.env.GITHUB_REF = 'refs/heads/master' 71 | process.env.GITHUB_REF_TYPE = 'branch' 72 | process.env.GITHUB_REF_NAME = 'master' 73 | process.env.GITHUB_HEAD_REF = '' 74 | process.env.GITHUB_BASE_REF = '' 75 | process.env.GITHUB_SERVER_URL = 'https://github.com' 76 | process.env.GITHUB_API_URL = 'https://github.com' 77 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 78 | 79 | test('custom config of slack action using legacy and blocks', async () => { 80 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 81 | 82 | mockAxios 83 | .onPost() 84 | .reply(config => { 85 | console.log(config.data) 86 | return [200, {status: 'ok'}] 87 | }) 88 | .onAny() 89 | .reply(500) 90 | 91 | let message = undefined 92 | 93 | let config = yaml.load(readFileSync('./__tests__/fixtures/slack-blocks.yml', 'utf-8'), { 94 | schema: yaml.FAILSAFE_SCHEMA 95 | }) as ConfigOptions 96 | 97 | let res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message, config) 98 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 99 | 100 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 101 | username: 'GitHub-CI', 102 | icon_url: 'https://octodex.github.com/images/mona-the-rivetertocat.png', 103 | channel: '#github-ci', 104 | timeout: 0, 105 | attachments: [ 106 | { 107 | mrkdwn_in: ['pretext', 'text', 'fields'], 108 | color: '#884EA0', 109 | pretext: 'Triggered via push by satterly action master `68d48876`', 110 | author_name: 'satterly', 111 | author_link: 'https://github.com/satterly', 112 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 113 | title: 'GitHub Actions', 114 | title_link: 'https://support.github.com', 115 | text: '** for \n - 4 commits\n*Commits*\n - wip\n - wip\n - wip\n - wip\n', 116 | fields: [ 117 | { 118 | title: 'Job Steps', 119 | value: ':white_check_mark: install-deps\n:x: hooks\n:grimacing: lint\n:grimacing: integration-test\n', 120 | short: false 121 | } 122 | ], 123 | fallback: '[GitHub] build-test #8 CI Tests is failure', 124 | footer: ' build-test #8', 125 | footer_icon: 'https://github.githubassets.com/favicon.ico', 126 | ts: expect.stringMatching(/[0-9]+/) 127 | }, 128 | { 129 | color: '#884EA0', 130 | fallback: '[GitHub] build-test #8 CI Tests is failure', 131 | blocks: [ 132 | { 133 | type: 'context', 134 | elements: [ 135 | {type: 'image', image_url: 'https://avatars0.githubusercontent.com/u/615057?v=4', alt_text: 'satterly'}, 136 | {type: 'mrkdwn', text: '**'} 137 | ] 138 | }, 139 | {type: 'section', text: {type: 'mrkdwn', text: '**\n'}}, 140 | { 141 | type: 'section', 142 | text: { 143 | type: 'mrkdwn', 144 | text: '** for \n - 4 commits\n\n*Commits*\n\n - wip\n\n - wip\n\n - wip\n\n - wip\n\n\n' 145 | } 146 | }, 147 | { 148 | type: 'section', 149 | fields: [ 150 | { 151 | type: 'mrkdwn', 152 | text: '*Job Steps*\n:white_check_mark: install-deps\n:x: hooks\n:grimacing: lint\n:grimacing: integration-test\n' 153 | } 154 | ] 155 | }, 156 | { 157 | type: 'section', 158 | fields: [ 159 | { 160 | type: 'mrkdwn', 161 | text: '*Workflow*\n' 162 | }, 163 | {type: 'mrkdwn', text: '*Git Ref*\nmaster (branch)'}, 164 | {type: 'mrkdwn', text: '*Run ID*\n'}, 165 | {type: 'mrkdwn', text: '*Run Number*\n8'}, 166 | {type: 'mrkdwn', text: '*Actor*\nsatterly'}, 167 | {type: 'mrkdwn', text: '*Job Status*\nfailure'} 168 | ] 169 | }, 170 | { 171 | type: 'context', 172 | elements: [ 173 | {type: 'image', image_url: 'https://github.githubassets.com/favicon.ico', alt_text: 'satterly'}, 174 | { 175 | type: 'mrkdwn', 176 | text: expect.stringMatching( 177 | / build-test #8 | / 178 | ) 179 | } 180 | ] 181 | } 182 | ] 183 | } 184 | ] 185 | }) 186 | 187 | mockAxios.resetHistory() 188 | mockAxios.reset() 189 | }) 190 | -------------------------------------------------------------------------------- /__tests__/config.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send, ConfigOptions} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | import * as yaml from 'js-yaml' 7 | 8 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 9 | const jobName = 'CI Tests' 10 | const jobStatus = 'failure' 11 | const jobSteps = { 12 | 'install-deps': { 13 | outputs: {}, 14 | outcome: 'success', 15 | conclusion: 'success' 16 | }, 17 | hooks: { 18 | outputs: {}, 19 | outcome: 'cancelled', 20 | conclusion: 'cancelled' 21 | }, 22 | lint: { 23 | outputs: {}, 24 | outcome: 'failure', 25 | conclusion: 'failure' 26 | }, 27 | types: { 28 | outputs: {}, 29 | outcome: 'skipped', 30 | conclusion: 'skipped' 31 | }, 32 | 'unit-test': { 33 | outputs: {}, 34 | outcome: 'skipped', 35 | conclusion: 'skipped' 36 | }, 37 | 'integration-test': { 38 | outputs: {}, 39 | outcome: 'failure', 40 | conclusion: 'failure' 41 | } 42 | } 43 | const jobMatrix = {} 44 | const jobInputs = {} 45 | const channel = '#github-ci' 46 | 47 | // mock github context 48 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/push.json', 'utf-8')) 49 | 50 | github.context.payload = dump.event 51 | github.context.eventName = dump.event_name 52 | github.context.sha = dump.sha 53 | github.context.ref = dump.ref 54 | github.context.workflow = dump.workflow 55 | github.context.action = dump.action 56 | github.context.actor = dump.actor 57 | 58 | process.env.CI = 'true' 59 | process.env.GITHUB_WORKFLOW = 'build-test' 60 | process.env.GITHUB_RUN_ID = '100143423' 61 | process.env.GITHUB_RUN_NUMBER = '8' 62 | process.env.GITHUB_ACTION = 'self2' 63 | process.env.GITHUB_ACTIONS = 'true' 64 | process.env.GITHUB_ACTOR = 'satterly' 65 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 66 | process.env.GITHUB_EVENT_NAME = 'push' 67 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 68 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 69 | process.env.GITHUB_SHA = '68d48876e0794fba714cb331a1624af6b20942d8' 70 | process.env.GITHUB_REF = 'refs/heads/master' 71 | process.env.GITHUB_REF_TYPE = 'branch' 72 | process.env.GITHUB_REF_NAME = 'master' 73 | process.env.GITHUB_HEAD_REF = '' 74 | process.env.GITHUB_BASE_REF = '' 75 | process.env.GITHUB_SERVER_URL = 'https://github.com' 76 | process.env.GITHUB_API_URL = 'https://github.com' 77 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 78 | 79 | test('custom config of slack action using legacy attachments', async () => { 80 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 81 | 82 | mockAxios 83 | .onPost() 84 | .reply(config => { 85 | console.log(config.data) 86 | return [200, {status: 'ok'}] 87 | }) 88 | .onAny() 89 | .reply(500) 90 | 91 | let message = undefined 92 | 93 | let config = yaml.load(readFileSync('./__tests__/fixtures/slack-legacy.yml', 'utf-8'), { 94 | schema: yaml.FAILSAFE_SCHEMA 95 | }) as ConfigOptions 96 | 97 | let res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message, config) 98 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 99 | 100 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 101 | username: 'GitHub-CI', 102 | icon_url: 'https://octodex.github.com/images/mona-the-rivetertocat.png', 103 | channel: '#github-ci', 104 | timeout: 0, 105 | attachments: [ 106 | { 107 | fallback: '[GitHub] build-test #8 CI Tests is failure', 108 | color: '#884EA0', 109 | author_name: 'satterly', 110 | author_link: 'https://github.com/satterly', 111 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 112 | mrkdwn_in: ['pretext', 'text', 'fields'], 113 | pretext: 'Triggered via push by satterly action master `68d48876`', 114 | text: 115 | '** for \n' + 116 | ' - 4 commits\n' + 117 | '*Commits*\n' + 118 | ' - wip\n' + 119 | ' - wip\n' + 120 | ' - wip\n' + 121 | ' - wip\n', 122 | title: 'GitHub Actions', 123 | title_link: 'https://support.github.com', 124 | fields: [ 125 | { 126 | short: false, 127 | title: 'Job Steps', 128 | value: ':white_check_mark: install-deps\n:x: hooks\n:grimacing: lint\n:grimacing: integration-test\n' 129 | }, 130 | { 131 | short: true, 132 | title: 'Workflow', 133 | value: '' 134 | }, 135 | { 136 | short: true, 137 | title: 'Git Ref', 138 | value: 'master (branch)' 139 | }, 140 | { 141 | short: true, 142 | title: 'Run ID', 143 | value: '' 144 | }, 145 | { 146 | short: true, 147 | title: 'Run Number', 148 | value: '8' 149 | }, 150 | { 151 | short: true, 152 | title: 'Actor', 153 | value: 'satterly' 154 | }, 155 | { 156 | short: true, 157 | title: 'Job Status', 158 | value: 'failure' 159 | } 160 | ], 161 | footer: ' build-test #8', 162 | footer_icon: 'https://github.githubassets.com/favicon.ico', 163 | ts: expect.stringMatching(/[0-9]+/) 164 | } 165 | ] 166 | }) 167 | 168 | mockAxios.resetHistory() 169 | mockAxios.reset() 170 | }) 171 | -------------------------------------------------------------------------------- /__tests__/fixtures/create.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "***", 3 | "job": "test", 4 | "ref": "refs/heads/fix-undefined-url", 5 | "sha": "d0d4530a505a87990b764d11f207ea0e8c6e93f7", 6 | "repository": "act10ns/slack", 7 | "repository_owner": "act10ns", 8 | "repositoryUrl": "git://github.com/act10ns/slack.git", 9 | "run_id": "110200164", 10 | "run_number": "134", 11 | "actor": "satterly", 12 | "workflow": "build-test", 13 | "head_ref": "", 14 | "base_ref": "", 15 | "event_name": "create", 16 | "event": { 17 | "description": "Slack messages for GitHub Actions workflows, jobs and steps", 18 | "master_branch": "master", 19 | "organization": { 20 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 21 | "description": "Automate your GitHub workflows with custom actions", 22 | "events_url": "https://api.github.com/orgs/act10ns/events", 23 | "hooks_url": "https://api.github.com/orgs/act10ns/hooks", 24 | "id": 65077766, 25 | "issues_url": "https://api.github.com/orgs/act10ns/issues", 26 | "login": "act10ns", 27 | "members_url": "https://api.github.com/orgs/act10ns/members{/member}", 28 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 29 | "public_members_url": "https://api.github.com/orgs/act10ns/public_members{/member}", 30 | "repos_url": "https://api.github.com/orgs/act10ns/repos", 31 | "url": "https://api.github.com/orgs/act10ns" 32 | }, 33 | "pusher_type": "user", 34 | "ref": "fix-undefined-url", 35 | "ref_type": "branch", 36 | "repository": { 37 | "archive_url": "https://api.github.com/repos/act10ns/slack/{archive_format}{/ref}", 38 | "archived": false, 39 | "assignees_url": "https://api.github.com/repos/act10ns/slack/assignees{/user}", 40 | "blobs_url": "https://api.github.com/repos/act10ns/slack/git/blobs{/sha}", 41 | "branches_url": "https://api.github.com/repos/act10ns/slack/branches{/branch}", 42 | "clone_url": "https://github.com/act10ns/slack.git", 43 | "collaborators_url": "https://api.github.com/repos/act10ns/slack/collaborators{/collaborator}", 44 | "comments_url": "https://api.github.com/repos/act10ns/slack/comments{/number}", 45 | "commits_url": "https://api.github.com/repos/act10ns/slack/commits{/sha}", 46 | "compare_url": "https://api.github.com/repos/act10ns/slack/compare/{base}...{head}", 47 | "contents_url": "https://api.github.com/repos/act10ns/slack/contents/{+path}", 48 | "contributors_url": "https://api.github.com/repos/act10ns/slack/contributors", 49 | "created_at": "2020-05-09T14:04:36Z", 50 | "default_branch": "master", 51 | "deployments_url": "https://api.github.com/repos/act10ns/slack/deployments", 52 | "description": "Slack messages for GitHub Actions workflows, jobs and steps", 53 | "disabled": false, 54 | "downloads_url": "https://api.github.com/repos/act10ns/slack/downloads", 55 | "events_url": "https://api.github.com/repos/act10ns/slack/events", 56 | "fork": false, 57 | "forks": 0, 58 | "forks_count": 0, 59 | "forks_url": "https://api.github.com/repos/act10ns/slack/forks", 60 | "full_name": "act10ns/slack", 61 | "git_commits_url": "https://api.github.com/repos/act10ns/slack/git/commits{/sha}", 62 | "git_refs_url": "https://api.github.com/repos/act10ns/slack/git/refs{/sha}", 63 | "git_tags_url": "https://api.github.com/repos/act10ns/slack/git/tags{/sha}", 64 | "git_url": "git://github.com/act10ns/slack.git", 65 | "has_downloads": true, 66 | "has_issues": true, 67 | "has_pages": false, 68 | "has_projects": true, 69 | "has_wiki": true, 70 | "homepage": "https://github.com/marketplace/actions/slack-github-actions-slack-integration", 71 | "hooks_url": "https://api.github.com/repos/act10ns/slack/hooks", 72 | "html_url": "https://github.com/act10ns/slack", 73 | "id": 262583918, 74 | "issue_comment_url": "https://api.github.com/repos/act10ns/slack/issues/comments{/number}", 75 | "issue_events_url": "https://api.github.com/repos/act10ns/slack/issues/events{/number}", 76 | "issues_url": "https://api.github.com/repos/act10ns/slack/issues{/number}", 77 | "keys_url": "https://api.github.com/repos/act10ns/slack/keys{/key_id}", 78 | "labels_url": "https://api.github.com/repos/act10ns/slack/labels{/name}", 79 | "language": "TypeScript", 80 | "languages_url": "https://api.github.com/repos/act10ns/slack/languages", 81 | "license": { 82 | "key": "mit", 83 | "name": "MIT License", 84 | "node_id": "MDc6TGljZW5zZTEz", 85 | "spdx_id": "MIT", 86 | "url": "https://api.github.com/licenses/mit" 87 | }, 88 | "merges_url": "https://api.github.com/repos/act10ns/slack/merges", 89 | "milestones_url": "https://api.github.com/repos/act10ns/slack/milestones{/number}", 90 | "mirror_url": null, 91 | "name": "slack", 92 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjI1ODM5MTg=", 93 | "notifications_url": "https://api.github.com/repos/act10ns/slack/notifications{?since,all,participating}", 94 | "open_issues": 5, 95 | "open_issues_count": 5, 96 | "owner": { 97 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 98 | "events_url": "https://api.github.com/users/act10ns/events{/privacy}", 99 | "followers_url": "https://api.github.com/users/act10ns/followers", 100 | "following_url": "https://api.github.com/users/act10ns/following{/other_user}", 101 | "gists_url": "https://api.github.com/users/act10ns/gists{/gist_id}", 102 | "gravatar_id": "", 103 | "html_url": "https://github.com/act10ns", 104 | "id": 65077766, 105 | "login": "act10ns", 106 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 107 | "organizations_url": "https://api.github.com/users/act10ns/orgs", 108 | "received_events_url": "https://api.github.com/users/act10ns/received_events", 109 | "repos_url": "https://api.github.com/users/act10ns/repos", 110 | "site_admin": false, 111 | "starred_url": "https://api.github.com/users/act10ns/starred{/owner}{/repo}", 112 | "subscriptions_url": "https://api.github.com/users/act10ns/subscriptions", 113 | "type": "Organization", 114 | "url": "https://api.github.com/users/act10ns" 115 | }, 116 | "private": false, 117 | "pulls_url": "https://api.github.com/repos/act10ns/slack/pulls{/number}", 118 | "pushed_at": "2020-05-20T08:57:09Z", 119 | "releases_url": "https://api.github.com/repos/act10ns/slack/releases{/id}", 120 | "size": 1276, 121 | "ssh_url": "git@github.com:act10ns/slack.git", 122 | "stargazers_count": 1, 123 | "stargazers_url": "https://api.github.com/repos/act10ns/slack/stargazers", 124 | "statuses_url": "https://api.github.com/repos/act10ns/slack/statuses/{sha}", 125 | "subscribers_url": "https://api.github.com/repos/act10ns/slack/subscribers", 126 | "subscription_url": "https://api.github.com/repos/act10ns/slack/subscription", 127 | "svn_url": "https://github.com/act10ns/slack", 128 | "tags_url": "https://api.github.com/repos/act10ns/slack/tags", 129 | "teams_url": "https://api.github.com/repos/act10ns/slack/teams", 130 | "trees_url": "https://api.github.com/repos/act10ns/slack/git/trees{/sha}", 131 | "updated_at": "2020-05-20T08:50:29Z", 132 | "url": "https://api.github.com/repos/act10ns/slack", 133 | "watchers": 1, 134 | "watchers_count": 1 135 | }, 136 | "sender": { 137 | "avatar_url": "https://avatars0.githubusercontent.com/u/615057?v=4", 138 | "events_url": "https://api.github.com/users/satterly/events{/privacy}", 139 | "followers_url": "https://api.github.com/users/satterly/followers", 140 | "following_url": "https://api.github.com/users/satterly/following{/other_user}", 141 | "gists_url": "https://api.github.com/users/satterly/gists{/gist_id}", 142 | "gravatar_id": "", 143 | "html_url": "https://github.com/satterly", 144 | "id": 615057, 145 | "login": "satterly", 146 | "node_id": "MDQ6VXNlcjYxNTA1Nw==", 147 | "organizations_url": "https://api.github.com/users/satterly/orgs", 148 | "received_events_url": "https://api.github.com/users/satterly/received_events", 149 | "repos_url": "https://api.github.com/users/satterly/repos", 150 | "site_admin": false, 151 | "starred_url": "https://api.github.com/users/satterly/starred{/owner}{/repo}", 152 | "subscriptions_url": "https://api.github.com/users/satterly/subscriptions", 153 | "type": "User", 154 | "url": "https://api.github.com/users/satterly" 155 | } 156 | }, 157 | "workspace": "/home/runner/work/slack/slack", 158 | "action": "run1", 159 | "event_path": "/home/runner/work/_temp/_github_workflow/event.json" 160 | } 161 | -------------------------------------------------------------------------------- /__tests__/fixtures/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "***", 3 | "job": "test", 4 | "ref": "refs/heads/master", 5 | "sha": "6730ca0708dcf207e6090d8e721fa80749321ac3", 6 | "repository": "act10ns/slack", 7 | "repository_owner": "act10ns", 8 | "repositoryUrl": "git://github.com/act10ns/slack.git", 9 | "run_id": "110240292", 10 | "run_number": "143", 11 | "actor": "satterly", 12 | "workflow": "build-test", 13 | "head_ref": "", 14 | "base_ref": "", 15 | "event_name": "delete", 16 | "event": { 17 | "organization": { 18 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 19 | "description": "Automate your GitHub workflows with custom actions", 20 | "events_url": "https://api.github.com/orgs/act10ns/events", 21 | "hooks_url": "https://api.github.com/orgs/act10ns/hooks", 22 | "id": 65077766, 23 | "issues_url": "https://api.github.com/orgs/act10ns/issues", 24 | "login": "act10ns", 25 | "members_url": "https://api.github.com/orgs/act10ns/members{/member}", 26 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 27 | "public_members_url": "https://api.github.com/orgs/act10ns/public_members{/member}", 28 | "repos_url": "https://api.github.com/orgs/act10ns/repos", 29 | "url": "https://api.github.com/orgs/act10ns" 30 | }, 31 | "pusher_type": "user", 32 | "ref": "test-branch", 33 | "ref_type": "branch", 34 | "repository": { 35 | "archive_url": "https://api.github.com/repos/act10ns/slack/{archive_format}{/ref}", 36 | "archived": false, 37 | "assignees_url": "https://api.github.com/repos/act10ns/slack/assignees{/user}", 38 | "blobs_url": "https://api.github.com/repos/act10ns/slack/git/blobs{/sha}", 39 | "branches_url": "https://api.github.com/repos/act10ns/slack/branches{/branch}", 40 | "clone_url": "https://github.com/act10ns/slack.git", 41 | "collaborators_url": "https://api.github.com/repos/act10ns/slack/collaborators{/collaborator}", 42 | "comments_url": "https://api.github.com/repos/act10ns/slack/comments{/number}", 43 | "commits_url": "https://api.github.com/repos/act10ns/slack/commits{/sha}", 44 | "compare_url": "https://api.github.com/repos/act10ns/slack/compare/{base}...{head}", 45 | "contents_url": "https://api.github.com/repos/act10ns/slack/contents/{+path}", 46 | "contributors_url": "https://api.github.com/repos/act10ns/slack/contributors", 47 | "created_at": "2020-05-09T14:04:36Z", 48 | "default_branch": "master", 49 | "deployments_url": "https://api.github.com/repos/act10ns/slack/deployments", 50 | "description": "Slack messages for GitHub Actions workflows, jobs and steps", 51 | "disabled": false, 52 | "downloads_url": "https://api.github.com/repos/act10ns/slack/downloads", 53 | "events_url": "https://api.github.com/repos/act10ns/slack/events", 54 | "fork": false, 55 | "forks": 0, 56 | "forks_count": 0, 57 | "forks_url": "https://api.github.com/repos/act10ns/slack/forks", 58 | "full_name": "act10ns/slack", 59 | "git_commits_url": "https://api.github.com/repos/act10ns/slack/git/commits{/sha}", 60 | "git_refs_url": "https://api.github.com/repos/act10ns/slack/git/refs{/sha}", 61 | "git_tags_url": "https://api.github.com/repos/act10ns/slack/git/tags{/sha}", 62 | "git_url": "git://github.com/act10ns/slack.git", 63 | "has_downloads": true, 64 | "has_issues": true, 65 | "has_pages": false, 66 | "has_projects": true, 67 | "has_wiki": true, 68 | "homepage": "https://github.com/marketplace/actions/slack-github-actions-slack-integration", 69 | "hooks_url": "https://api.github.com/repos/act10ns/slack/hooks", 70 | "html_url": "https://github.com/act10ns/slack", 71 | "id": 262583918, 72 | "issue_comment_url": "https://api.github.com/repos/act10ns/slack/issues/comments{/number}", 73 | "issue_events_url": "https://api.github.com/repos/act10ns/slack/issues/events{/number}", 74 | "issues_url": "https://api.github.com/repos/act10ns/slack/issues{/number}", 75 | "keys_url": "https://api.github.com/repos/act10ns/slack/keys{/key_id}", 76 | "labels_url": "https://api.github.com/repos/act10ns/slack/labels{/name}", 77 | "language": "TypeScript", 78 | "languages_url": "https://api.github.com/repos/act10ns/slack/languages", 79 | "license": { 80 | "key": "mit", 81 | "name": "MIT License", 82 | "node_id": "MDc6TGljZW5zZTEz", 83 | "spdx_id": "MIT", 84 | "url": "https://api.github.com/licenses/mit" 85 | }, 86 | "merges_url": "https://api.github.com/repos/act10ns/slack/merges", 87 | "milestones_url": "https://api.github.com/repos/act10ns/slack/milestones{/number}", 88 | "mirror_url": null, 89 | "name": "slack", 90 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjI1ODM5MTg=", 91 | "notifications_url": "https://api.github.com/repos/act10ns/slack/notifications{?since,all,participating}", 92 | "open_issues": 5, 93 | "open_issues_count": 5, 94 | "owner": { 95 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 96 | "events_url": "https://api.github.com/users/act10ns/events{/privacy}", 97 | "followers_url": "https://api.github.com/users/act10ns/followers", 98 | "following_url": "https://api.github.com/users/act10ns/following{/other_user}", 99 | "gists_url": "https://api.github.com/users/act10ns/gists{/gist_id}", 100 | "gravatar_id": "", 101 | "html_url": "https://github.com/act10ns", 102 | "id": 65077766, 103 | "login": "act10ns", 104 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 105 | "organizations_url": "https://api.github.com/users/act10ns/orgs", 106 | "received_events_url": "https://api.github.com/users/act10ns/received_events", 107 | "repos_url": "https://api.github.com/users/act10ns/repos", 108 | "site_admin": false, 109 | "starred_url": "https://api.github.com/users/act10ns/starred{/owner}{/repo}", 110 | "subscriptions_url": "https://api.github.com/users/act10ns/subscriptions", 111 | "type": "Organization", 112 | "url": "https://api.github.com/users/act10ns" 113 | }, 114 | "private": false, 115 | "pulls_url": "https://api.github.com/repos/act10ns/slack/pulls{/number}", 116 | "pushed_at": "2020-05-20T09:41:17Z", 117 | "releases_url": "https://api.github.com/repos/act10ns/slack/releases{/id}", 118 | "size": 1493, 119 | "ssh_url": "git@github.com:act10ns/slack.git", 120 | "stargazers_count": 1, 121 | "stargazers_url": "https://api.github.com/repos/act10ns/slack/stargazers", 122 | "statuses_url": "https://api.github.com/repos/act10ns/slack/statuses/{sha}", 123 | "subscribers_url": "https://api.github.com/repos/act10ns/slack/subscribers", 124 | "subscription_url": "https://api.github.com/repos/act10ns/slack/subscription", 125 | "svn_url": "https://github.com/act10ns/slack", 126 | "tags_url": "https://api.github.com/repos/act10ns/slack/tags", 127 | "teams_url": "https://api.github.com/repos/act10ns/slack/teams", 128 | "trees_url": "https://api.github.com/repos/act10ns/slack/git/trees{/sha}", 129 | "updated_at": "2020-05-20T09:40:55Z", 130 | "url": "https://api.github.com/repos/act10ns/slack", 131 | "watchers": 1, 132 | "watchers_count": 1 133 | }, 134 | "sender": { 135 | "avatar_url": "https://avatars0.githubusercontent.com/u/615057?v=4", 136 | "events_url": "https://api.github.com/users/satterly/events{/privacy}", 137 | "followers_url": "https://api.github.com/users/satterly/followers", 138 | "following_url": "https://api.github.com/users/satterly/following{/other_user}", 139 | "gists_url": "https://api.github.com/users/satterly/gists{/gist_id}", 140 | "gravatar_id": "", 141 | "html_url": "https://github.com/satterly", 142 | "id": 615057, 143 | "login": "satterly", 144 | "node_id": "MDQ6VXNlcjYxNTA1Nw==", 145 | "organizations_url": "https://api.github.com/users/satterly/orgs", 146 | "received_events_url": "https://api.github.com/users/satterly/received_events", 147 | "repos_url": "https://api.github.com/users/satterly/repos", 148 | "site_admin": false, 149 | "starred_url": "https://api.github.com/users/satterly/starred{/owner}{/repo}", 150 | "subscriptions_url": "https://api.github.com/users/satterly/subscriptions", 151 | "type": "User", 152 | "url": "https://api.github.com/users/satterly" 153 | } 154 | }, 155 | "workspace": "/home/runner/work/slack/slack", 156 | "action": "run1", 157 | "event_path": "/home/runner/work/_temp/_github_workflow/event.json" 158 | } 159 | -------------------------------------------------------------------------------- /__tests__/fixtures/merge.json: -------------------------------------------------------------------------------- 1 | { 2 | "after": "eef54c472eb55a5ab07a08121d8fdd021d226ea0", 3 | "base_ref": null, 4 | "before": "332b8416cd15a8f77816a5d3df21423b16b46756", 5 | "commits": [ 6 | { 7 | "author": { 8 | "email": "nfsatterly@gmail.com", 9 | "name": "Nick Satterly", 10 | "username": "satterly" 11 | }, 12 | "committer": { 13 | "email": "nfsatterly@gmail.com", 14 | "name": "Nick Satterly", 15 | "username": "satterly" 16 | }, 17 | "distinct": false, 18 | "id": "29b55976ca55b37bfe71506c4f17de93d66b7ede", 19 | "message": "Use env vars mostly, complement with event payload data if available", 20 | "timestamp": "2020-11-14T00:55:33+01:00", 21 | "tree_id": "d21ba173fa20be7f6fb973b26cf804695359bd0c", 22 | "url": "https://github.com/act10ns/slack/commit/29b55976ca55b37bfe71506c4f17de93d66b7ede" 23 | }, 24 | { 25 | "author": { 26 | "email": "nfsatterly@gmail.com", 27 | "name": "Nick Satterly", 28 | "username": "satterly" 29 | }, 30 | "committer": { 31 | "email": "noreply@github.com", 32 | "name": "GitHub", 33 | "username": "web-flow" 34 | }, 35 | "distinct": true, 36 | "id": "eef54c472eb55a5ab07a08121d8fdd021d226ea0", 37 | "message": "Merge pull request #165 from act10ns/minor-refactor\n\nMinor refactor", 38 | "timestamp": "2020-11-14T00:56:53+01:00", 39 | "tree_id": "d21ba173fa20be7f6fb973b26cf804695359bd0c", 40 | "url": "https://github.com/act10ns/slack/commit/eef54c472eb55a5ab07a08121d8fdd021d226ea0" 41 | } 42 | ], 43 | "compare": "https://github.com/act10ns/slack/compare/332b8416cd15...eef54c472eb5", 44 | "created": false, 45 | "deleted": false, 46 | "forced": false, 47 | "head_commit": { 48 | "author": { 49 | "email": "nfsatterly@gmail.com", 50 | "name": "Nick Satterly", 51 | "username": "satterly" 52 | }, 53 | "committer": { 54 | "email": "noreply@github.com", 55 | "name": "GitHub", 56 | "username": "web-flow" 57 | }, 58 | "distinct": true, 59 | "id": "eef54c472eb55a5ab07a08121d8fdd021d226ea0", 60 | "message": "Merge pull request #165 from act10ns/minor-refactor\n\nMinor refactor", 61 | "timestamp": "2020-11-14T00:56:53+01:00", 62 | "tree_id": "d21ba173fa20be7f6fb973b26cf804695359bd0c", 63 | "url": "https://github.com/act10ns/slack/commit/eef54c472eb55a5ab07a08121d8fdd021d226ea0" 64 | }, 65 | "organization": { 66 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 67 | "description": "Automate your GitHub workflows with custom actions", 68 | "events_url": "https://api.github.com/orgs/act10ns/events", 69 | "hooks_url": "https://api.github.com/orgs/act10ns/hooks", 70 | "id": 65077766, 71 | "issues_url": "https://api.github.com/orgs/act10ns/issues", 72 | "login": "act10ns", 73 | "members_url": "https://api.github.com/orgs/act10ns/members{/member}", 74 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 75 | "public_members_url": "https://api.github.com/orgs/act10ns/public_members{/member}", 76 | "repos_url": "https://api.github.com/orgs/act10ns/repos", 77 | "url": "https://api.github.com/orgs/act10ns" 78 | }, 79 | "pusher": { 80 | "email": "nfsatterly@gmail.com", 81 | "name": "satterly" 82 | }, 83 | "ref": "refs/heads/master", 84 | "repository": { 85 | "archive_url": "https://api.github.com/repos/act10ns/slack/{archive_format}{/ref}", 86 | "archived": false, 87 | "assignees_url": "https://api.github.com/repos/act10ns/slack/assignees{/user}", 88 | "blobs_url": "https://api.github.com/repos/act10ns/slack/git/blobs{/sha}", 89 | "branches_url": "https://api.github.com/repos/act10ns/slack/branches{/branch}", 90 | "clone_url": "https://github.com/act10ns/slack.git", 91 | "collaborators_url": "https://api.github.com/repos/act10ns/slack/collaborators{/collaborator}", 92 | "comments_url": "https://api.github.com/repos/act10ns/slack/comments{/number}", 93 | "commits_url": "https://api.github.com/repos/act10ns/slack/commits{/sha}", 94 | "compare_url": "https://api.github.com/repos/act10ns/slack/compare/{base}...{head}", 95 | "contents_url": "https://api.github.com/repos/act10ns/slack/contents/{+path}", 96 | "contributors_url": "https://api.github.com/repos/act10ns/slack/contributors", 97 | "created_at": 1589033076, 98 | "default_branch": "master", 99 | "deployments_url": "https://api.github.com/repos/act10ns/slack/deployments", 100 | "description": "Slack messages for GitHub Actions workflows, jobs and steps", 101 | "disabled": false, 102 | "downloads_url": "https://api.github.com/repos/act10ns/slack/downloads", 103 | "events_url": "https://api.github.com/repos/act10ns/slack/events", 104 | "fork": false, 105 | "forks": 1, 106 | "forks_count": 1, 107 | "forks_url": "https://api.github.com/repos/act10ns/slack/forks", 108 | "full_name": "act10ns/slack", 109 | "git_commits_url": "https://api.github.com/repos/act10ns/slack/git/commits{/sha}", 110 | "git_refs_url": "https://api.github.com/repos/act10ns/slack/git/refs{/sha}", 111 | "git_tags_url": "https://api.github.com/repos/act10ns/slack/git/tags{/sha}", 112 | "git_url": "git://github.com/act10ns/slack.git", 113 | "has_downloads": true, 114 | "has_issues": true, 115 | "has_pages": false, 116 | "has_projects": true, 117 | "has_wiki": true, 118 | "homepage": "https://github.com/marketplace/actions/slack-github-actions-slack-integration", 119 | "hooks_url": "https://api.github.com/repos/act10ns/slack/hooks", 120 | "html_url": "https://github.com/act10ns/slack", 121 | "id": 262583918, 122 | "issue_comment_url": "https://api.github.com/repos/act10ns/slack/issues/comments{/number}", 123 | "issue_events_url": "https://api.github.com/repos/act10ns/slack/issues/events{/number}", 124 | "issues_url": "https://api.github.com/repos/act10ns/slack/issues{/number}", 125 | "keys_url": "https://api.github.com/repos/act10ns/slack/keys{/key_id}", 126 | "labels_url": "https://api.github.com/repos/act10ns/slack/labels{/name}", 127 | "language": "TypeScript", 128 | "languages_url": "https://api.github.com/repos/act10ns/slack/languages", 129 | "license": { 130 | "key": "mit", 131 | "name": "MIT License", 132 | "node_id": "MDc6TGljZW5zZTEz", 133 | "spdx_id": "MIT", 134 | "url": "https://api.github.com/licenses/mit" 135 | }, 136 | "master_branch": "master", 137 | "merges_url": "https://api.github.com/repos/act10ns/slack/merges", 138 | "milestones_url": "https://api.github.com/repos/act10ns/slack/milestones{/number}", 139 | "mirror_url": null, 140 | "name": "slack", 141 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjI1ODM5MTg=", 142 | "notifications_url": "https://api.github.com/repos/act10ns/slack/notifications{?since,all,participating}", 143 | "open_issues": 10, 144 | "open_issues_count": 10, 145 | "organization": "act10ns", 146 | "owner": { 147 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 148 | "email": null, 149 | "events_url": "https://api.github.com/users/act10ns/events{/privacy}", 150 | "followers_url": "https://api.github.com/users/act10ns/followers", 151 | "following_url": "https://api.github.com/users/act10ns/following{/other_user}", 152 | "gists_url": "https://api.github.com/users/act10ns/gists{/gist_id}", 153 | "gravatar_id": "", 154 | "html_url": "https://github.com/act10ns", 155 | "id": 65077766, 156 | "login": "act10ns", 157 | "name": "act10ns", 158 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 159 | "organizations_url": "https://api.github.com/users/act10ns/orgs", 160 | "received_events_url": "https://api.github.com/users/act10ns/received_events", 161 | "repos_url": "https://api.github.com/users/act10ns/repos", 162 | "site_admin": false, 163 | "starred_url": "https://api.github.com/users/act10ns/starred{/owner}{/repo}", 164 | "subscriptions_url": "https://api.github.com/users/act10ns/subscriptions", 165 | "type": "Organization", 166 | "url": "https://api.github.com/users/act10ns" 167 | }, 168 | "private": false, 169 | "pulls_url": "https://api.github.com/repos/act10ns/slack/pulls{/number}", 170 | "pushed_at": 1605311814, 171 | "releases_url": "https://api.github.com/repos/act10ns/slack/releases{/id}", 172 | "size": 1595, 173 | "ssh_url": "git@github.com:act10ns/slack.git", 174 | "stargazers": 22, 175 | "stargazers_count": 22, 176 | "stargazers_url": "https://api.github.com/repos/act10ns/slack/stargazers", 177 | "statuses_url": "https://api.github.com/repos/act10ns/slack/statuses/{sha}", 178 | "subscribers_url": "https://api.github.com/repos/act10ns/slack/subscribers", 179 | "subscription_url": "https://api.github.com/repos/act10ns/slack/subscription", 180 | "svn_url": "https://github.com/act10ns/slack", 181 | "tags_url": "https://api.github.com/repos/act10ns/slack/tags", 182 | "teams_url": "https://api.github.com/repos/act10ns/slack/teams", 183 | "trees_url": "https://api.github.com/repos/act10ns/slack/git/trees{/sha}", 184 | "updated_at": "2020-11-13T10:13:40Z", 185 | "url": "https://github.com/act10ns/slack", 186 | "watchers": 22, 187 | "watchers_count": 22 188 | }, 189 | "sender": { 190 | "avatar_url": "https://avatars0.githubusercontent.com/u/615057?v=4", 191 | "events_url": "https://api.github.com/users/satterly/events{/privacy}", 192 | "followers_url": "https://api.github.com/users/satterly/followers", 193 | "following_url": "https://api.github.com/users/satterly/following{/other_user}", 194 | "gists_url": "https://api.github.com/users/satterly/gists{/gist_id}", 195 | "gravatar_id": "", 196 | "html_url": "https://github.com/satterly", 197 | "id": 615057, 198 | "login": "satterly", 199 | "node_id": "MDQ6VXNlcjYxNTA1Nw==", 200 | "organizations_url": "https://api.github.com/users/satterly/orgs", 201 | "received_events_url": "https://api.github.com/users/satterly/received_events", 202 | "repos_url": "https://api.github.com/users/satterly/repos", 203 | "site_admin": false, 204 | "starred_url": "https://api.github.com/users/satterly/starred{/owner}{/repo}", 205 | "subscriptions_url": "https://api.github.com/users/satterly/subscriptions", 206 | "type": "User", 207 | "url": "https://api.github.com/users/satterly" 208 | } 209 | } -------------------------------------------------------------------------------- /__tests__/fixtures/push.json: -------------------------------------------------------------------------------- 1 | { 2 | "token": "***", 3 | "job": "test", 4 | "ref": "refs/heads/master", 5 | "sha": "68d48876e0794fba714cb331a1624af6b20942d8", 6 | "repository": "act10ns/slack", 7 | "repository_owner": "act10ns", 8 | "repositoryUrl": "git://github.com/act10ns/slack.git", 9 | "run_id": "100143423", 10 | "run_number": "8", 11 | "actor": "satterly", 12 | "workflow": "build-test", 13 | "head_ref": "", 14 | "base_ref": "", 15 | "event_name": "push", 16 | "event": { 17 | "after": "68d48876e0794fba714cb331a1624af6b20942d8", 18 | "base_ref": null, 19 | "before": "db9fe60430a62879a0c8381d20796e5701585ec0", 20 | "commits": [ 21 | { 22 | "author": { 23 | "email": "nfsatterly@gmail.com", 24 | "name": "Nick Satterly", 25 | "username": "satterly" 26 | }, 27 | "committer": { 28 | "email": "nfsatterly@gmail.com", 29 | "name": "Nick Satterly", 30 | "username": "satterly" 31 | }, 32 | "distinct": true, 33 | "id": "b1f512300ea6e925e095c51a441fcf30104523aa", 34 | "message": "wip", 35 | "timestamp": "2020-05-09T20:06:03+02:00", 36 | "tree_id": "e70aed14891f875801b4541b35e4c06069186d4b", 37 | "url": "https://github.com/act10ns/slack/commit/b1f512300ea6e925e095c51a441fcf30104523aa" 38 | }, 39 | { 40 | "author": { 41 | "email": "nfsatterly@gmail.com", 42 | "name": "Nick Satterly", 43 | "username": "satterly" 44 | }, 45 | "committer": { 46 | "email": "nfsatterly@gmail.com", 47 | "name": "Nick Satterly", 48 | "username": "satterly" 49 | }, 50 | "distinct": true, 51 | "id": "b246b5fdcc2722909503d5a43eb635885aa5fd25", 52 | "message": "wip", 53 | "timestamp": "2020-05-09T20:19:21+02:00", 54 | "tree_id": "007d1729360fcf106e373edbae9aaba6a6572b3b", 55 | "url": "https://github.com/act10ns/slack/commit/b246b5fdcc2722909503d5a43eb635885aa5fd25" 56 | }, 57 | { 58 | "author": { 59 | "email": "nfsatterly@gmail.com", 60 | "name": "Nick Satterly", 61 | "username": "satterly" 62 | }, 63 | "committer": { 64 | "email": "nfsatterly@gmail.com", 65 | "name": "Nick Satterly", 66 | "username": "satterly" 67 | }, 68 | "distinct": true, 69 | "id": "553c22356fadc36947653de987dabd8da40cb06b", 70 | "message": "wip", 71 | "timestamp": "2020-05-09T20:28:41+02:00", 72 | "tree_id": "3ef6bc1e2205aa021f727891899f7a29186eb1ec", 73 | "url": "https://github.com/act10ns/slack/commit/553c22356fadc36947653de987dabd8da40cb06b" 74 | }, 75 | { 76 | "author": { 77 | "email": "nfsatterly@gmail.com", 78 | "name": "Nick Satterly", 79 | "username": "satterly" 80 | }, 81 | "committer": { 82 | "email": "nfsatterly@gmail.com", 83 | "name": "Nick Satterly", 84 | "username": "satterly" 85 | }, 86 | "distinct": true, 87 | "id": "68d48876e0794fba714cb331a1624af6b20942d8", 88 | "message": "wip", 89 | "timestamp": "2020-05-09T21:22:21+02:00", 90 | "tree_id": "3ffbe48e4de9a6272ff12a74661f87d99a422e0d", 91 | "url": "https://github.com/act10ns/slack/commit/68d48876e0794fba714cb331a1624af6b20942d8" 92 | } 93 | ], 94 | "compare": "https://github.com/act10ns/slack/compare/db9fe60430a6...68d48876e079", 95 | "created": false, 96 | "deleted": false, 97 | "forced": false, 98 | "head_commit": { 99 | "author": { 100 | "email": "nfsatterly@gmail.com", 101 | "name": "Nick Satterly", 102 | "username": "satterly" 103 | }, 104 | "committer": { 105 | "email": "nfsatterly@gmail.com", 106 | "name": "Nick Satterly", 107 | "username": "satterly" 108 | }, 109 | "distinct": true, 110 | "id": "68d48876e0794fba714cb331a1624af6b20942d8", 111 | "message": "wip", 112 | "timestamp": "2020-05-09T21:22:21+02:00", 113 | "tree_id": "3ffbe48e4de9a6272ff12a74661f87d99a422e0d", 114 | "url": "https://github.com/act10ns/slack/commit/68d48876e0794fba714cb331a1624af6b20942d8" 115 | }, 116 | "organization": { 117 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 118 | "description": "Automate your GitHub workflows with custom actions", 119 | "events_url": "https://api.github.com/orgs/act10ns/events", 120 | "hooks_url": "https://api.github.com/orgs/act10ns/hooks", 121 | "id": 65077766, 122 | "issues_url": "https://api.github.com/orgs/act10ns/issues", 123 | "login": "act10ns", 124 | "members_url": "https://api.github.com/orgs/act10ns/members{/member}", 125 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 126 | "public_members_url": "https://api.github.com/orgs/act10ns/public_members{/member}", 127 | "repos_url": "https://api.github.com/orgs/act10ns/repos", 128 | "url": "https://api.github.com/orgs/act10ns" 129 | }, 130 | "pusher": { 131 | "email": "nfsatterly@gmail.com", 132 | "name": "satterly" 133 | }, 134 | "ref": "refs/heads/master", 135 | "repository": { 136 | "archive_url": "https://api.github.com/repos/act10ns/slack/{archive_format}{/ref}", 137 | "archived": false, 138 | "assignees_url": "https://api.github.com/repos/act10ns/slack/assignees{/user}", 139 | "blobs_url": "https://api.github.com/repos/act10ns/slack/git/blobs{/sha}", 140 | "branches_url": "https://api.github.com/repos/act10ns/slack/branches{/branch}", 141 | "clone_url": "https://github.com/act10ns/slack.git", 142 | "collaborators_url": "https://api.github.com/repos/act10ns/slack/collaborators{/collaborator}", 143 | "comments_url": "https://api.github.com/repos/act10ns/slack/comments{/number}", 144 | "commits_url": "https://api.github.com/repos/act10ns/slack/commits{/sha}", 145 | "compare_url": "https://api.github.com/repos/act10ns/slack/compare/{base}...{head}", 146 | "contents_url": "https://api.github.com/repos/act10ns/slack/contents/{+path}", 147 | "contributors_url": "https://api.github.com/repos/act10ns/slack/contributors", 148 | "created_at": 1589033076, 149 | "default_branch": "master", 150 | "deployments_url": "https://api.github.com/repos/act10ns/slack/deployments", 151 | "description": "GitHub Actions for Slack", 152 | "disabled": false, 153 | "downloads_url": "https://api.github.com/repos/act10ns/slack/downloads", 154 | "events_url": "https://api.github.com/repos/act10ns/slack/events", 155 | "fork": false, 156 | "forks": 0, 157 | "forks_count": 0, 158 | "forks_url": "https://api.github.com/repos/act10ns/slack/forks", 159 | "full_name": "act10ns/slack", 160 | "git_commits_url": "https://api.github.com/repos/act10ns/slack/git/commits{/sha}", 161 | "git_refs_url": "https://api.github.com/repos/act10ns/slack/git/refs{/sha}", 162 | "git_tags_url": "https://api.github.com/repos/act10ns/slack/git/tags{/sha}", 163 | "git_url": "git://github.com/act10ns/slack.git", 164 | "has_downloads": true, 165 | "has_issues": true, 166 | "has_pages": false, 167 | "has_projects": true, 168 | "has_wiki": true, 169 | "homepage": null, 170 | "hooks_url": "https://api.github.com/repos/act10ns/slack/hooks", 171 | "html_url": "https://github.com/act10ns/slack", 172 | "id": 262583918, 173 | "issue_comment_url": "https://api.github.com/repos/act10ns/slack/issues/comments{/number}", 174 | "issue_events_url": "https://api.github.com/repos/act10ns/slack/issues/events{/number}", 175 | "issues_url": "https://api.github.com/repos/act10ns/slack/issues{/number}", 176 | "keys_url": "https://api.github.com/repos/act10ns/slack/keys{/key_id}", 177 | "labels_url": "https://api.github.com/repos/act10ns/slack/labels{/name}", 178 | "language": "TypeScript", 179 | "languages_url": "https://api.github.com/repos/act10ns/slack/languages", 180 | "license": { 181 | "key": "mit", 182 | "name": "MIT License", 183 | "node_id": "MDc6TGljZW5zZTEz", 184 | "spdx_id": "MIT", 185 | "url": "https://api.github.com/licenses/mit" 186 | }, 187 | "master_branch": "master", 188 | "merges_url": "https://api.github.com/repos/act10ns/slack/merges", 189 | "milestones_url": "https://api.github.com/repos/act10ns/slack/milestones{/number}", 190 | "mirror_url": null, 191 | "name": "slack", 192 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjI1ODM5MTg=", 193 | "notifications_url": "https://api.github.com/repos/act10ns/slack/notifications{?since,all,participating}", 194 | "open_issues": 0, 195 | "open_issues_count": 0, 196 | "organization": "act10ns", 197 | "owner": { 198 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 199 | "email": null, 200 | "events_url": "https://api.github.com/users/act10ns/events{/privacy}", 201 | "followers_url": "https://api.github.com/users/act10ns/followers", 202 | "following_url": "https://api.github.com/users/act10ns/following{/other_user}", 203 | "gists_url": "https://api.github.com/users/act10ns/gists{/gist_id}", 204 | "gravatar_id": "", 205 | "html_url": "https://github.com/act10ns", 206 | "id": 65077766, 207 | "login": "act10ns", 208 | "name": "act10ns", 209 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 210 | "organizations_url": "https://api.github.com/users/act10ns/orgs", 211 | "received_events_url": "https://api.github.com/users/act10ns/received_events", 212 | "repos_url": "https://api.github.com/users/act10ns/repos", 213 | "site_admin": false, 214 | "starred_url": "https://api.github.com/users/act10ns/starred{/owner}{/repo}", 215 | "subscriptions_url": "https://api.github.com/users/act10ns/subscriptions", 216 | "type": "Organization", 217 | "url": "https://api.github.com/users/act10ns" 218 | }, 219 | "private": false, 220 | "pulls_url": "https://api.github.com/repos/act10ns/slack/pulls{/number}", 221 | "pushed_at": 1589052147, 222 | "releases_url": "https://api.github.com/repos/act10ns/slack/releases{/id}", 223 | "size": 623, 224 | "ssh_url": "git@github.com:act10ns/slack.git", 225 | "stargazers": 0, 226 | "stargazers_count": 0, 227 | "stargazers_url": "https://api.github.com/repos/act10ns/slack/stargazers", 228 | "statuses_url": "https://api.github.com/repos/act10ns/slack/statuses/{sha}", 229 | "subscribers_url": "https://api.github.com/repos/act10ns/slack/subscribers", 230 | "subscription_url": "https://api.github.com/repos/act10ns/slack/subscription", 231 | "svn_url": "https://github.com/act10ns/slack", 232 | "tags_url": "https://api.github.com/repos/act10ns/slack/tags", 233 | "teams_url": "https://api.github.com/repos/act10ns/slack/teams", 234 | "trees_url": "https://api.github.com/repos/act10ns/slack/git/trees{/sha}", 235 | "updated_at": "2020-05-09T17:51:49Z", 236 | "url": "https://github.com/act10ns/slack", 237 | "watchers": 0, 238 | "watchers_count": 0 239 | }, 240 | "sender": { 241 | "avatar_url": "https://avatars0.githubusercontent.com/u/615057?v=4", 242 | "events_url": "https://api.github.com/users/satterly/events{/privacy}", 243 | "followers_url": "https://api.github.com/users/satterly/followers", 244 | "following_url": "https://api.github.com/users/satterly/following{/other_user}", 245 | "gists_url": "https://api.github.com/users/satterly/gists{/gist_id}", 246 | "gravatar_id": "", 247 | "html_url": "https://github.com/satterly", 248 | "id": 615057, 249 | "login": "satterly", 250 | "node_id": "MDQ6VXNlcjYxNTA1Nw==", 251 | "organizations_url": "https://api.github.com/users/satterly/orgs", 252 | "received_events_url": "https://api.github.com/users/satterly/received_events", 253 | "repos_url": "https://api.github.com/users/satterly/repos", 254 | "site_admin": false, 255 | "starred_url": "https://api.github.com/users/satterly/starred{/owner}{/repo}", 256 | "subscriptions_url": "https://api.github.com/users/satterly/subscriptions", 257 | "type": "User", 258 | "url": "https://api.github.com/users/satterly" 259 | } 260 | }, 261 | "workspace": "/home/runner/work/slack/slack", 262 | "action": "run1", 263 | "event_path": "/home/runner/work/_temp/_github_workflow/event.json" 264 | } -------------------------------------------------------------------------------- /__tests__/fixtures/release.env.txt: -------------------------------------------------------------------------------- 1 | AGENT_TOOLSDIRECTORY = /opt/hostedtoolcache 2 | ANDROID_HOME = /usr/local/lib/android/sdk 3 | ANDROID_SDK_ROOT = /usr/local/lib/android/sdk 4 | ANT_HOME = /usr/share/ant 5 | AZURE_EXTENSION_DIR = /opt/az/azcliextensions 6 | BOOST_ROOT_1_72_0 = /opt/hostedtoolcache/boost/1.72.0/x64 7 | CHROME_BIN = /usr/bin/google-chrome 8 | CHROMEWEBDRIVER = /usr/local/share/chrome_driver 9 | CI = true 10 | CONDA = /usr/share/miniconda 11 | DEBIAN_FRONTEND = noninteractive 12 | DEPLOYMENT_BASEPATH = /opt/runner 13 | DOTNET_MULTILEVEL_LOOKUP = "0" 14 | DOTNET_NOLOGO = "1" 15 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE = "1" 16 | GECKOWEBDRIVER = /usr/local/share/gecko_driver 17 | GITHUB_ACTION = self 18 | GITHUB_ACTION_REF = v2 19 | GITHUB_ACTION_REPOSITORY = actions/checkout 20 | GITHUB_ACTIONS = true 21 | GITHUB_ACTOR = satterly 22 | GITHUB_API_URL = https://api.github.com 23 | GITHUB_BASE_REF = 24 | GITHUB_ENV = /home/runner/work/_temp/_runner_file_commands/set_env_750f945e-cb2f-4f80-8c99-3c63257aa034 25 | GITHUB_EVENT_NAME = release 26 | GITHUB_EVENT_PATH = /home/runner/work/_temp/_github_workflow/event.json 27 | GITHUB_GRAPHQL_URL = https://api.github.com/graphql 28 | GITHUB_HEAD_REF = 29 | GITHUB_JOB = build 30 | GITHUB_PATH = /home/runner/work/_temp/_runner_file_commands/add_path_750f945e-cb2f-4f80-8c99-3c63257aa034 31 | GITHUB_REF = refs/tags/v1.0.13 32 | GITHUB_REPOSITORY = act10ns/slack 33 | GITHUB_REPOSITORY_OWNER = act10ns 34 | GITHUB_RETENTION_DAYS = 90 35 | GITHUB_RUN_ID = 361391443 36 | GITHUB_RUN_NUMBER = 817 37 | GITHUB_SERVER_URL = https://github.com 38 | GITHUB_SHA = 332b8416cd15a8f77816a5d3df21423b16b46756 39 | GITHUB_WORKFLOW = build-test 40 | GITHUB_WORKSPACE = /home/runner/work/slack/slack 41 | GOROOT = /opt/hostedtoolcache/go/1.14.11/x64 42 | GOROOT_1_13_X64 = /opt/hostedtoolcache/go/1.13.15/x64 43 | GOROOT_1_14_X64 = /opt/hostedtoolcache/go/1.14.11/x64 44 | GOROOT_1_15_X64 = /opt/hostedtoolcache/go/1.15.4/x64 45 | GRADLE_HOME = /usr/share/gradle 46 | HOME = /home/runner 47 | HOMEBREW_CELLAR = "/home/linuxbrew/.linuxbrew/Cellar" 48 | HOMEBREW_PREFIX = "/home/linuxbrew/.linuxbrew" 49 | HOMEBREW_REPOSITORY = "/home/linuxbrew/.linuxbrew/Homebrew" 50 | ImageOS = ubuntu18 51 | ImageVersion = 20201108.1 52 | INPUT_CHANNEL = #actions 53 | INPUT_STATUS = success 54 | INPUT_STEPS = { 55 | "checkout": { 56 | "outputs": {}, 57 | "outcome": "success", 58 | "conclusion": "success" 59 | }, 60 | "build": { 61 | "outputs": {}, 62 | "outcome": "success", 63 | "conclusion": "success" 64 | } 65 | } 66 | INVOCATION_ID = c2d4df9eb4ec4de9826f7eff7ab4cfa8 67 | JAVA_HOME = /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64 68 | JAVA_HOME_11_X64 = /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64 69 | JAVA_HOME_12_X64 = /usr/lib/jvm/adoptopenjdk-12-hotspot-amd64 70 | JAVA_HOME_7_X64 = /usr/lib/jvm/zulu-7-azure-amd64 71 | JAVA_HOME_8_X64 = /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64 72 | JOURNAL_STREAM = 9:21721 73 | LANG = C.UTF-8 74 | LEIN_HOME = /usr/local/lib/lein 75 | LEIN_JAR = /usr/local/lib/lein/self-installs/leiningen-2.9.4-standalone.jar 76 | M2_HOME = /usr/share/apache-maven-3.6.3 77 | PATH = /home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/opt/pipx_bin:/usr/share/rust/.cargo/bin:/home/runner/.config/composer/vendor/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 78 | PERFLOG_LOCATION_SETTING = RUNNER_PERFLOG 79 | PIPX_BIN_DIR = "/opt/pipx_bin" 80 | PIPX_HOME = "/opt/pipx" 81 | POWERSHELL_DISTRIBUTION_CHANNEL = GitHub-Actions-ubuntu18 82 | RUNNER_DEBUG = 1 83 | RUNNER_OS = Linux 84 | RUNNER_PERFLOG = /home/runner/perflog 85 | RUNNER_TEMP = /home/runner/work/_temp 86 | RUNNER_TOOL_CACHE = /opt/hostedtoolcache 87 | RUNNER_TRACKING_ID = github_1a8b79b2-7ca6-4b46-aea3-6c3f5fcb3607 88 | RUNNER_USER = runner 89 | RUNNER_WORKSPACE = /home/runner/work/slack 90 | SELENIUM_JAR_PATH = /usr/share/java/selenium-server-standalone.jar 91 | SLACK_WEBHOOK_URL = *** 92 | SWIFT_PATH = /usr/share/swift/usr/bin 93 | USER = runner 94 | VCPKG_INSTALLATION_ROOT = /usr/local/share/vcpkg 95 | -------------------------------------------------------------------------------- /__tests__/fixtures/release.json: -------------------------------------------------------------------------------- 1 | { 2 | "action": "created", 3 | "organization": { 4 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 5 | "description": "Automate your GitHub workflows with custom actions", 6 | "events_url": "https://api.github.com/orgs/act10ns/events", 7 | "hooks_url": "https://api.github.com/orgs/act10ns/hooks", 8 | "id": 65077766, 9 | "issues_url": "https://api.github.com/orgs/act10ns/issues", 10 | "login": "act10ns", 11 | "members_url": "https://api.github.com/orgs/act10ns/members{/member}", 12 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 13 | "public_members_url": "https://api.github.com/orgs/act10ns/public_members{/member}", 14 | "repos_url": "https://api.github.com/orgs/act10ns/repos", 15 | "url": "https://api.github.com/orgs/act10ns" 16 | }, 17 | "release": { 18 | "assets": [], 19 | "assets_url": "https://api.github.com/repos/act10ns/slack/releases/33906956/assets", 20 | "author": { 21 | "avatar_url": "https://avatars0.githubusercontent.com/u/615057?v=4", 22 | "events_url": "https://api.github.com/users/satterly/events{/privacy}", 23 | "followers_url": "https://api.github.com/users/satterly/followers", 24 | "following_url": "https://api.github.com/users/satterly/following{/other_user}", 25 | "gists_url": "https://api.github.com/users/satterly/gists{/gist_id}", 26 | "gravatar_id": "", 27 | "html_url": "https://github.com/satterly", 28 | "id": 615057, 29 | "login": "satterly", 30 | "node_id": "MDQ6VXNlcjYxNTA1Nw==", 31 | "organizations_url": "https://api.github.com/users/satterly/orgs", 32 | "received_events_url": "https://api.github.com/users/satterly/received_events", 33 | "repos_url": "https://api.github.com/users/satterly/repos", 34 | "site_admin": false, 35 | "starred_url": "https://api.github.com/users/satterly/starred{/owner}{/repo}", 36 | "subscriptions_url": "https://api.github.com/users/satterly/subscriptions", 37 | "type": "User", 38 | "url": "https://api.github.com/users/satterly" 39 | }, 40 | "body": "", 41 | "created_at": "2020-11-13T10:13:22Z", 42 | "draft": false, 43 | "html_url": "https://github.com/act10ns/slack/releases/tag/v1.0.13", 44 | "id": 33906956, 45 | "name": "v1.0.13", 46 | "node_id": "MDc6UmVsZWFzZTMzOTA2OTU2", 47 | "prerelease": false, 48 | "published_at": "2020-11-13T10:14:00Z", 49 | "tag_name": "v1.0.13", 50 | "tarball_url": "https://api.github.com/repos/act10ns/slack/tarball/v1.0.13", 51 | "target_commitish": "master", 52 | "upload_url": "https://uploads.github.com/repos/act10ns/slack/releases/33906956/assets{?name,label}", 53 | "url": "https://api.github.com/repos/act10ns/slack/releases/33906956", 54 | "zipball_url": "https://api.github.com/repos/act10ns/slack/zipball/v1.0.13" 55 | }, 56 | "repository": { 57 | "archive_url": "https://api.github.com/repos/act10ns/slack/{archive_format}{/ref}", 58 | "archived": false, 59 | "assignees_url": "https://api.github.com/repos/act10ns/slack/assignees{/user}", 60 | "blobs_url": "https://api.github.com/repos/act10ns/slack/git/blobs{/sha}", 61 | "branches_url": "https://api.github.com/repos/act10ns/slack/branches{/branch}", 62 | "clone_url": "https://github.com/act10ns/slack.git", 63 | "collaborators_url": "https://api.github.com/repos/act10ns/slack/collaborators{/collaborator}", 64 | "comments_url": "https://api.github.com/repos/act10ns/slack/comments{/number}", 65 | "commits_url": "https://api.github.com/repos/act10ns/slack/commits{/sha}", 66 | "compare_url": "https://api.github.com/repos/act10ns/slack/compare/{base}...{head}", 67 | "contents_url": "https://api.github.com/repos/act10ns/slack/contents/{+path}", 68 | "contributors_url": "https://api.github.com/repos/act10ns/slack/contributors", 69 | "created_at": "2020-05-09T14:04:36Z", 70 | "default_branch": "master", 71 | "deployments_url": "https://api.github.com/repos/act10ns/slack/deployments", 72 | "description": "Slack messages for GitHub Actions workflows, jobs and steps", 73 | "disabled": false, 74 | "downloads_url": "https://api.github.com/repos/act10ns/slack/downloads", 75 | "events_url": "https://api.github.com/repos/act10ns/slack/events", 76 | "fork": false, 77 | "forks": 1, 78 | "forks_count": 1, 79 | "forks_url": "https://api.github.com/repos/act10ns/slack/forks", 80 | "full_name": "act10ns/slack", 81 | "git_commits_url": "https://api.github.com/repos/act10ns/slack/git/commits{/sha}", 82 | "git_refs_url": "https://api.github.com/repos/act10ns/slack/git/refs{/sha}", 83 | "git_tags_url": "https://api.github.com/repos/act10ns/slack/git/tags{/sha}", 84 | "git_url": "git://github.com/act10ns/slack.git", 85 | "has_downloads": true, 86 | "has_issues": true, 87 | "has_pages": false, 88 | "has_projects": true, 89 | "has_wiki": true, 90 | "homepage": "https://github.com/marketplace/actions/slack-github-actions-slack-integration", 91 | "hooks_url": "https://api.github.com/repos/act10ns/slack/hooks", 92 | "html_url": "https://github.com/act10ns/slack", 93 | "id": 262583918, 94 | "issue_comment_url": "https://api.github.com/repos/act10ns/slack/issues/comments{/number}", 95 | "issue_events_url": "https://api.github.com/repos/act10ns/slack/issues/events{/number}", 96 | "issues_url": "https://api.github.com/repos/act10ns/slack/issues{/number}", 97 | "keys_url": "https://api.github.com/repos/act10ns/slack/keys{/key_id}", 98 | "labels_url": "https://api.github.com/repos/act10ns/slack/labels{/name}", 99 | "language": "TypeScript", 100 | "languages_url": "https://api.github.com/repos/act10ns/slack/languages", 101 | "license": { 102 | "key": "mit", 103 | "name": "MIT License", 104 | "node_id": "MDc6TGljZW5zZTEz", 105 | "spdx_id": "MIT", 106 | "url": "https://api.github.com/licenses/mit" 107 | }, 108 | "merges_url": "https://api.github.com/repos/act10ns/slack/merges", 109 | "milestones_url": "https://api.github.com/repos/act10ns/slack/milestones{/number}", 110 | "mirror_url": null, 111 | "name": "slack", 112 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjI1ODM5MTg=", 113 | "notifications_url": "https://api.github.com/repos/act10ns/slack/notifications{?since,all,participating}", 114 | "open_issues": 10, 115 | "open_issues_count": 10, 116 | "owner": { 117 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 118 | "events_url": "https://api.github.com/users/act10ns/events{/privacy}", 119 | "followers_url": "https://api.github.com/users/act10ns/followers", 120 | "following_url": "https://api.github.com/users/act10ns/following{/other_user}", 121 | "gists_url": "https://api.github.com/users/act10ns/gists{/gist_id}", 122 | "gravatar_id": "", 123 | "html_url": "https://github.com/act10ns", 124 | "id": 65077766, 125 | "login": "act10ns", 126 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 127 | "organizations_url": "https://api.github.com/users/act10ns/orgs", 128 | "received_events_url": "https://api.github.com/users/act10ns/received_events", 129 | "repos_url": "https://api.github.com/users/act10ns/repos", 130 | "site_admin": false, 131 | "starred_url": "https://api.github.com/users/act10ns/starred{/owner}{/repo}", 132 | "subscriptions_url": "https://api.github.com/users/act10ns/subscriptions", 133 | "type": "Organization", 134 | "url": "https://api.github.com/users/act10ns" 135 | }, 136 | "private": false, 137 | "pulls_url": "https://api.github.com/repos/act10ns/slack/pulls{/number}", 138 | "pushed_at": "2020-11-13T10:13:45Z", 139 | "releases_url": "https://api.github.com/repos/act10ns/slack/releases{/id}", 140 | "size": 1350, 141 | "ssh_url": "git@github.com:act10ns/slack.git", 142 | "stargazers_count": 22, 143 | "stargazers_url": "https://api.github.com/repos/act10ns/slack/stargazers", 144 | "statuses_url": "https://api.github.com/repos/act10ns/slack/statuses/{sha}", 145 | "subscribers_url": "https://api.github.com/repos/act10ns/slack/subscribers", 146 | "subscription_url": "https://api.github.com/repos/act10ns/slack/subscription", 147 | "svn_url": "https://github.com/act10ns/slack", 148 | "tags_url": "https://api.github.com/repos/act10ns/slack/tags", 149 | "teams_url": "https://api.github.com/repos/act10ns/slack/teams", 150 | "trees_url": "https://api.github.com/repos/act10ns/slack/git/trees{/sha}", 151 | "updated_at": "2020-11-13T10:13:40Z", 152 | "url": "https://api.github.com/repos/act10ns/slack", 153 | "watchers": 22, 154 | "watchers_count": 22 155 | }, 156 | "sender": { 157 | "avatar_url": "https://avatars0.githubusercontent.com/u/615057?v=4", 158 | "events_url": "https://api.github.com/users/satterly/events{/privacy}", 159 | "followers_url": "https://api.github.com/users/satterly/followers", 160 | "following_url": "https://api.github.com/users/satterly/following{/other_user}", 161 | "gists_url": "https://api.github.com/users/satterly/gists{/gist_id}", 162 | "gravatar_id": "", 163 | "html_url": "https://github.com/satterly", 164 | "id": 615057, 165 | "login": "satterly", 166 | "node_id": "MDQ6VXNlcjYxNTA1Nw==", 167 | "organizations_url": "https://api.github.com/users/satterly/orgs", 168 | "received_events_url": "https://api.github.com/users/satterly/received_events", 169 | "repos_url": "https://api.github.com/users/satterly/repos", 170 | "site_admin": false, 171 | "starred_url": "https://api.github.com/users/satterly/starred{/owner}{/repo}", 172 | "subscriptions_url": "https://api.github.com/users/satterly/subscriptions", 173 | "type": "User", 174 | "url": "https://api.github.com/users/satterly" 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /__tests__/fixtures/schedule.env.txt: -------------------------------------------------------------------------------- 1 | ACTIONS_CACHE_URL = https://artifactcache.actions.githubusercontent.com/vYbmtabU4akFCdAnXD3NmnfhNFUM6zder6WPTPGVPndBPW2Pxw/ 2 | ACTIONS_RUNTIME_TOKEN = *** 3 | ACTIONS_RUNTIME_URL = https://pipelines.actions.githubusercontent.com/vYbmtabU4akFCdAnXD3NmnfhNFUM6zder6WPTPGVPndBPW2Pxw/ 4 | AGENT_TOOLSDIRECTORY = /opt/hostedtoolcache 5 | ANDROID_HOME = /usr/local/lib/android/sdk 6 | ANDROID_SDK_ROOT = /usr/local/lib/android/sdk 7 | ANT_HOME = /usr/share/ant 8 | AZURE_EXTENSION_DIR = /opt/az/azcliextensions 9 | BOOST_ROOT_1_72_0 = /opt/hostedtoolcache/boost/1.72.0/x64 10 | CHROME_BIN = /usr/bin/google-chrome 11 | CHROMEWEBDRIVER = /usr/local/share/chrome_driver 12 | CI = true 13 | CONDA = /usr/share/miniconda 14 | DEBIAN_FRONTEND = noninteractive 15 | DEPLOYMENT_BASEPATH = /opt/runner 16 | DOTNET_MULTILEVEL_LOOKUP = "0" 17 | DOTNET_NOLOGO = "1" 18 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE = "1" 19 | GECKOWEBDRIVER = /usr/local/share/gecko_driver 20 | GITHUB_ACTION = self 21 | GITHUB_ACTION_REF = v2 22 | GITHUB_ACTION_REPOSITORY = actions/checkout 23 | GITHUB_ACTIONS = true 24 | GITHUB_ACTOR = satterly 25 | GITHUB_API_URL = https://api.github.com 26 | GITHUB_BASE_REF = 27 | GITHUB_ENV = /home/runner/work/_temp/_runner_file_commands/set_env_211d6fde-e2d2-4c6e-99a2-8a40e51ffebe 28 | GITHUB_EVENT_NAME = schedule 29 | GITHUB_EVENT_PATH = /home/runner/work/_temp/_github_workflow/event.json 30 | GITHUB_GRAPHQL_URL = https://api.github.com/graphql 31 | GITHUB_HEAD_REF = 32 | GITHUB_JOB = build 33 | GITHUB_PATH = /home/runner/work/_temp/_runner_file_commands/add_path_211d6fde-e2d2-4c6e-99a2-8a40e51ffebe 34 | GITHUB_REF = refs/heads/master 35 | GITHUB_REPOSITORY = act10ns/slack 36 | GITHUB_REPOSITORY_OWNER = act10ns 37 | GITHUB_RETENTION_DAYS = 90 38 | GITHUB_RUN_ID = 363600556 39 | GITHUB_RUN_NUMBER = 179 40 | GITHUB_SERVER_URL = https://github.com 41 | GITHUB_SHA = 09a6b2c984766efb19eb39c97bc8be5d352a102f 42 | GITHUB_WORKFLOW = schedule-test 43 | GITHUB_WORKSPACE = /home/runner/work/slack/slack 44 | GOROOT = /opt/hostedtoolcache/go/1.14.11/x64 45 | GOROOT_1_13_X64 = /opt/hostedtoolcache/go/1.13.15/x64 46 | GOROOT_1_14_X64 = /opt/hostedtoolcache/go/1.14.11/x64 47 | GOROOT_1_15_X64 = /opt/hostedtoolcache/go/1.15.4/x64 48 | GRADLE_HOME = /usr/share/gradle 49 | HOME = /home/runner 50 | HOMEBREW_CELLAR = "/home/linuxbrew/.linuxbrew/Cellar" 51 | HOMEBREW_PREFIX = "/home/linuxbrew/.linuxbrew" 52 | HOMEBREW_REPOSITORY = "/home/linuxbrew/.linuxbrew/Homebrew" 53 | ImageOS = ubuntu18 54 | ImageVersion = 20201108.1 55 | INPUT_CHANNEL = #actions 56 | INPUT_STATUS = success 57 | INPUT_STEPS = { 58 | "checkout": { 59 | "outputs": {}, 60 | "outcome": "success", 61 | "conclusion": "success" 62 | }, 63 | "build": { 64 | "outputs": {}, 65 | "outcome": "success", 66 | "conclusion": "success" 67 | } 68 | } 69 | INVOCATION_ID = 1a1f065e457f48ea96eb5d289fa1bb9f 70 | JAVA_HOME = /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64 71 | JAVA_HOME_11_X64 = /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64 72 | JAVA_HOME_12_X64 = /usr/lib/jvm/adoptopenjdk-12-hotspot-amd64 73 | JAVA_HOME_7_X64 = /usr/lib/jvm/zulu-7-azure-amd64 74 | JAVA_HOME_8_X64 = /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64 75 | JOURNAL_STREAM = 9:20950 76 | LANG = C.UTF-8 77 | LEIN_HOME = /usr/local/lib/lein 78 | LEIN_JAR = /usr/local/lib/lein/self-installs/leiningen-2.9.4-standalone.jar 79 | M2_HOME = /usr/share/apache-maven-3.6.3 80 | PATH = /home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/opt/pipx_bin:/usr/share/rust/.cargo/bin:/home/runner/.config/composer/vendor/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 81 | PERFLOG_LOCATION_SETTING = RUNNER_PERFLOG 82 | PIPX_BIN_DIR = "/opt/pipx_bin" 83 | PIPX_HOME = "/opt/pipx" 84 | POWERSHELL_DISTRIBUTION_CHANNEL = GitHub-Actions-ubuntu18 85 | RUNNER_DEBUG = 1 86 | RUNNER_OS = Linux 87 | RUNNER_PERFLOG = /home/runner/perflog 88 | RUNNER_TEMP = /home/runner/work/_temp 89 | RUNNER_TOOL_CACHE = /opt/hostedtoolcache 90 | RUNNER_TRACKING_ID = github_e70122fb-b5ec-42b5-be7d-a8c8403b1b7c 91 | RUNNER_USER = runner 92 | RUNNER_WORKSPACE = /home/runner/work/slack 93 | SELENIUM_JAR_PATH = /usr/share/java/selenium-server-standalone.jar 94 | SLACK_WEBHOOK_URL = *** 95 | SWIFT_PATH = /usr/share/swift/usr/bin 96 | USER = runner 97 | VCPKG_INSTALLATION_ROOT = /usr/local/share/vcpkg 98 | -------------------------------------------------------------------------------- /__tests__/fixtures/schedule.json: -------------------------------------------------------------------------------- 1 | { 2 | "schedule": "*/15 * * * *" 3 | } -------------------------------------------------------------------------------- /__tests__/fixtures/slack-blocks.yml: -------------------------------------------------------------------------------- 1 | username: GitHub-CI 2 | icon_url: https://octodex.github.com/images/mona-the-rivetertocat.png 3 | 4 | pretext: Triggered via {{eventName}} by {{actor}} {{default action "action"}} {{ref}} `{{diffRef}}` 5 | title: GitHub Actions 6 | title_link: https://support.github.com 7 | 8 | text: &text | 9 | *<{{workflowRunUrl}}|Workflow _{{workflow}}_ job _{{jobName}}_ triggered by _{{eventName}}_ is _{{jobStatus}}_>* for <{{refUrl}}|`{{ref}}`> 10 | {{#if description}}<{{diffUrl}}|`{{diffRef}}`> - {{{description}}}{{/if}} 11 | {{#if payload.commits}} 12 | *Commits* 13 | {{#each payload.commits}} 14 | <{{this.url}}|`{{truncate this.id 8}}`> - {{this.message}} 15 | {{/each}} 16 | {{/if}} 17 | 18 | fallback: |- 19 | [GitHub] {{workflow}} #{{runNumber}} {{jobName}} is {{jobStatus}} 20 | 21 | fields: 22 | - title: Job Steps 23 | value: |- 24 | {{#each jobSteps}}{{#ifneq this.outcome 'skipped'}}{{icon this.outcome}} {{@key}} 25 | {{/ifneq}}{{/each}} 26 | short: false 27 | 28 | blocks: 29 | # author 30 | - type: context 31 | elements: 32 | - type: image 33 | image_url: '{{{sender.avatar_url}}}' 34 | alt_text: '{{sender.login}}' 35 | - type: mrkdwn 36 | text: "*<{{sender.html_url}}|{{sender.login}}>*" 37 | 38 | # title 39 | - type: section 40 | text: 41 | type: mrkdwn 42 | text: | 43 | *<{{title_link}}|{{title}}>* 44 | 45 | # text 46 | - type: section 47 | text: 48 | type: mrkdwn 49 | text: *text 50 | 51 | # fields 52 | - type: section 53 | fields: 54 | - type: mrkdwn 55 | text: |- 56 | *Job Steps* 57 | {{#each jobSteps}}{{#ifneq this.outcome 'skipped'}}{{icon this.outcome}} {{@key}} 58 | {{/ifneq}}{{/each}} 59 | - type: section 60 | fields: 61 | - type: mrkdwn 62 | text: "*Workflow*\n<{{{workflowUrl}}}|{{workflow}}>" 63 | - type: mrkdwn 64 | text: "*Git Ref*\n{{ref}} ({{refType}})" 65 | - type: mrkdwn 66 | text: |- 67 | *Run ID* 68 | <{{workflowRunUrl}}|{{runId}}> 69 | - type: mrkdwn 70 | text: "*Run Number*\n{{runNumber}}" 71 | - type: mrkdwn 72 | text: "*Actor*\n{{actor}}" 73 | - type: mrkdwn 74 | text: "*Job Status*\n{{jobStatus}}" 75 | 76 | # footer 77 | - type: context 78 | elements: 79 | - type: image 80 | image_url: '{{footer_icon}}' 81 | alt_text: satterly 82 | - type: mrkdwn 83 | text: '{{{footer}}} | ' 84 | 85 | footer: >- 86 | <{{repositoryUrl}}|{{repositoryName}}> {{workflow}} #{{runNumber}} 87 | 88 | colors: 89 | success: '#5DADE2' 90 | failure: '#884EA0' 91 | cancelled: '#A569BD' 92 | default: '#7D3C98' 93 | 94 | icons: 95 | success: ':white_check_mark:' 96 | failure: ':grimacing:' 97 | cancelled: ':x:' 98 | skipped: ':heavy_minus_sign:' 99 | default: ':interrobang:' 100 | -------------------------------------------------------------------------------- /__tests__/fixtures/slack-legacy.yml: -------------------------------------------------------------------------------- 1 | username: GitHub-CI 2 | icon_url: https://octodex.github.com/images/mona-the-rivetertocat.png 3 | 4 | pretext: Triggered via {{eventName}} by {{actor}} {{default action "action"}} {{ref}} `{{diffRef}}` 5 | title: GitHub Actions 6 | title_link: https://support.github.com 7 | 8 | text: | 9 | *<{{workflowRunUrl}}|Workflow _{{workflow}}_ job _{{jobName}}_ triggered by _{{eventName}}_ is _{{jobStatus}}_>* for <{{refUrl}}|`{{ref}}`> 10 | {{#if description}}<{{diffUrl}}|`{{diffRef}}`> - {{{description}}}{{/if}} 11 | {{#if payload.commits}} 12 | *Commits* 13 | {{#each payload.commits}} 14 | <{{this.url}}|`{{truncate this.id 8}}`> - {{this.message}} 15 | {{/each}} 16 | {{/if}} 17 | 18 | fallback: |- 19 | [GitHub] {{workflow}} #{{runNumber}} {{jobName}} is {{jobStatus}} 20 | 21 | fields: 22 | - title: Job Steps 23 | value: |- 24 | {{#each jobSteps}}{{#ifneq this.outcome 'skipped'}}{{icon this.outcome}} {{@key}} 25 | {{/ifneq}}{{/each}} 26 | short: false 27 | - title: Workflow 28 | value: "<{{{workflowUrl}}}|{{workflow}}>" 29 | short: true 30 | - title: Git Ref 31 | value: "{{ref}} ({{refType}})" 32 | short: true 33 | - title: Run ID 34 | value: |- 35 | <{{workflowRunUrl}}|{{runId}}> 36 | short: true 37 | - title: Run Number 38 | value: "{{runNumber}}" 39 | short: true 40 | - title: Actor 41 | value: "{{actor}}" 42 | short: true 43 | - title: Job Status 44 | value: "{{jobStatus}}" 45 | short: true 46 | 47 | footer: >- 48 | <{{repositoryUrl}}|{{repositoryName}}> {{workflow}} #{{runNumber}} 49 | 50 | colors: 51 | success: '#5DADE2' 52 | failure: '#884EA0' 53 | cancelled: '#A569BD' 54 | default: '#7D3C98' 55 | 56 | icons: 57 | success: ':white_check_mark:' 58 | failure: ':grimacing:' 59 | cancelled: ':x:' 60 | skipped: ':heavy_minus_sign:' 61 | default: ':interrobang:' 62 | -------------------------------------------------------------------------------- /__tests__/fixtures/slack-workflow.yml: -------------------------------------------------------------------------------- 1 | username: GitHub-CI 2 | icon_url: https://octodex.github.com/images/femalecodertocat.png 3 | 4 | pretext: Triggered via {{eventName}} by {{actor}} {{default action "action"}} {{ref}} `{{diffRef}}` 5 | title: GitHub Actions 6 | title_link: https://support.github.com 7 | 8 | fallback: |- 9 | [GitHub] {{workflow}} #{{runNumber}} is {{jobStatus}} 10 | 11 | blocks: 12 | # author 13 | - type: context 14 | elements: 15 | - type: image 16 | image_url: '{{{sender.avatar_url}}}' 17 | alt_text: '{{sender.login}}' 18 | - type: mrkdwn 19 | text: "*<{{sender.html_url}}|{{sender.login}}>*" 20 | 21 | # text 22 | - type: section 23 | text: 24 | type: mrkdwn 25 | text: >- 26 | Workflow {{payload.workflow.name}} {{payload.workflow_run.status}} 27 | with {{payload.workflow_run.conclusion}} after 28 | {{pluralize payload.workflow_run.run_attempt 'attempt'}} 29 | accessory: 30 | type: button 31 | text: 32 | type: plain_text 33 | text: View 34 | value: workflow_run_{{payload.workflow_run.workflow_id}} 35 | url: '{{payload.workflow_run.html_url}}' 36 | action_id: button-action 37 | 38 | # fields 39 | - type: section 40 | fields: 41 | - type: mrkdwn 42 | text: "*Jobs*\n{{payload.workflow_run.jobs_url}}" 43 | - type: mrkdwn 44 | text: "*Logs*\n{{payload.workflow_run.logs_url}}" 45 | 46 | # footer 47 | - type: context 48 | elements: 49 | - type: image 50 | image_url: '{{footer_icon}}' 51 | alt_text: github 52 | - type: mrkdwn 53 | text: '{{{footer}}} | ' 54 | 55 | footer: >- 56 | <{{repositoryUrl}}|{{repositoryName}}> {{workflow}} #{{runNumber}} 57 | 58 | colors: 59 | success: '#5DADE2' 60 | failure: '#884EA0' 61 | cancelled: '#A569BD' 62 | default: '#7D3C98' 63 | 64 | icons: 65 | success: ':white_check_mark:' 66 | failure: ':grimacing:' 67 | cancelled: ':x:' 68 | skipped: ':heavy_minus_sign:' 69 | default: ':interrobang:' 70 | -------------------------------------------------------------------------------- /__tests__/fixtures/slack.json: -------------------------------------------------------------------------------- 1 | { 2 | "username": "GitHub Actions", 3 | "icon_url": "https://octodex.github.com/images/original.png", 4 | "channel": "#actions", 5 | "attachments": [ 6 | { 7 | "mrkdwn_in": [ 8 | "pretext", 9 | "text", 10 | "fields" 11 | ], 12 | "color": "danger", 13 | "pretext": "", 14 | "author_name": "satterly", 15 | "author_link": "https://github.com/satterly", 16 | "author_icon": "https://avatars0.githubusercontent.com/u/615057?v=4", 17 | "title": "", 18 | "text": "** for \n - 4 commits", 19 | "fields": [ 20 | { 21 | "title": "Job Steps", 22 | "value": ":no_entry_sign: install-deps\n:no_entry_sign: hooks\n:no_entry_sign: lint\n:no_entry_sign: types\n:no_entry_sign: unit-test\n:no_entry_sign: integration-test\n", 23 | "short": false 24 | } 25 | ], 26 | "fallback": "[GitHub]: [act10ns/slack] build-test push failure", 27 | "footer": " #8", 28 | "footer_icon": "https://github.githubassets.com/favicon.ico", 29 | "ts": "1636625243" 30 | }, 31 | { 32 | "blocks": [ 33 | { 34 | "type": "section", 35 | "fields": [ 36 | { 37 | "type": "plain_text", 38 | "text": "*this is plain text*" 39 | } 40 | ] 41 | } 42 | ] 43 | } 44 | ] 45 | } -------------------------------------------------------------------------------- /__tests__/fixtures/workflow_dispatch.env.txt: -------------------------------------------------------------------------------- 1 | ACTIONS_CACHE_URL = https://artifactcache.actions.githubusercontent.com/vYbmtabU4akFCdAnXD3NmnfhNFUM6zder6WPTPGVPndBPW2Pxw/ 2 | ACTIONS_RUNTIME_TOKEN = *** 3 | ACTIONS_RUNTIME_URL = https://pipelines.actions.githubusercontent.com/vYbmtabU4akFCdAnXD3NmnfhNFUM6zder6WPTPGVPndBPW2Pxw/ 4 | AGENT_TOOLSDIRECTORY = /opt/hostedtoolcache 5 | ANDROID_HOME = /usr/local/lib/android/sdk 6 | ANDROID_SDK_ROOT = /usr/local/lib/android/sdk 7 | ANT_HOME = /usr/share/ant 8 | AZURE_EXTENSION_DIR = /opt/az/azcliextensions 9 | BOOST_ROOT_1_72_0 = /opt/hostedtoolcache/boost/1.72.0/x64 10 | CHROMEWEBDRIVER = /usr/local/share/chrome_driver 11 | CHROME_BIN = /usr/bin/google-chrome 12 | CI = true 13 | CONDA = /usr/share/miniconda 14 | DEBIAN_FRONTEND = noninteractive 15 | DEPLOYMENT_BASEPATH = /opt/runner 16 | DOTNET_MULTILEVEL_LOOKUP = "0" 17 | DOTNET_NOLOGO = "1" 18 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE = "1" 19 | GECKOWEBDRIVER = /usr/local/share/gecko_driver 20 | GITHUB_ACTION = self 21 | GITHUB_ACTIONS = true 22 | GITHUB_ACTION_REF = v2 23 | GITHUB_ACTION_REPOSITORY = actions/checkout 24 | GITHUB_ACTOR = satterly 25 | GITHUB_API_URL = https://api.github.com 26 | GITHUB_BASE_REF = 27 | GITHUB_ENV = /home/runner/work/_temp/_runner_file_commands/set_env_eb077d80-2a63-476c-9929-afe6a4e57cd3 28 | GITHUB_EVENT_NAME = workflow_dispatch 29 | GITHUB_EVENT_PATH = /home/runner/work/_temp/_github_workflow/event.json 30 | GITHUB_GRAPHQL_URL = https://api.github.com/graphql 31 | GITHUB_HEAD_REF = 32 | GITHUB_JOB = build 33 | GITHUB_PATH = /home/runner/work/_temp/_runner_file_commands/add_path_eb077d80-2a63-476c-9929-afe6a4e57cd3 34 | GITHUB_REF = refs/heads/master 35 | GITHUB_REPOSITORY = act10ns/slack 36 | GITHUB_REPOSITORY_OWNER = act10ns 37 | GITHUB_RETENTION_DAYS = 90 38 | GITHUB_RUN_ID = 360767681 39 | GITHUB_RUN_NUMBER = 6 40 | GITHUB_SERVER_URL = https://github.com 41 | GITHUB_SHA = a333f8bdd400c778b71e4bb645b745c7a9ac23cd 42 | GITHUB_WORKFLOW = manual-test 43 | GITHUB_WORKSPACE = /home/runner/work/slack/slack 44 | GOROOT = /opt/hostedtoolcache/go/1.14.11/x64 45 | GOROOT_1_13_X64 = /opt/hostedtoolcache/go/1.13.15/x64 46 | GOROOT_1_14_X64 = /opt/hostedtoolcache/go/1.14.11/x64 47 | GOROOT_1_15_X64 = /opt/hostedtoolcache/go/1.15.4/x64 48 | GRADLE_HOME = /usr/share/gradle 49 | HOME = /home/runner 50 | HOMEBREW_CELLAR = "/home/linuxbrew/.linuxbrew/Cellar" 51 | HOMEBREW_PREFIX = "/home/linuxbrew/.linuxbrew" 52 | HOMEBREW_REPOSITORY = "/home/linuxbrew/.linuxbrew/Homebrew" 53 | INPUT_CHANNEL = #actions 54 | INPUT_STATUS = failure 55 | INPUT_STEPS = { 56 | "checkout": { 57 | "outputs": {}, 58 | "outcome": "success", 59 | "conclusion": "success" 60 | }, 61 | "build": { 62 | "outputs": {}, 63 | "outcome": "failure", 64 | "conclusion": "failure" 65 | } 66 | } 67 | INVOCATION_ID = 4ba0c5c7bdb443dcb0a5c1543d8c3e96 68 | ImageOS = ubuntu18 69 | ImageVersion = 20201108.1 70 | JAVA_HOME = /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64 71 | JAVA_HOME_11_X64 = /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64 72 | JAVA_HOME_12_X64 = /usr/lib/jvm/adoptopenjdk-12-hotspot-amd64 73 | JAVA_HOME_7_X64 = /usr/lib/jvm/zulu-7-azure-amd64 74 | JAVA_HOME_8_X64 = /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64 75 | JOURNAL_STREAM = 9:21922 76 | LANG = C.UTF-8 77 | LEIN_HOME = /usr/local/lib/lein 78 | LEIN_JAR = /usr/local/lib/lein/self-installs/leiningen-2.9.4-standalone.jar 79 | M2_HOME = /usr/share/apache-maven-3.6.3 80 | PATH = /home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/opt/pipx_bin:/usr/share/rust/.cargo/bin:/home/runner/.config/composer/vendor/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 81 | PERFLOG_LOCATION_SETTING = RUNNER_PERFLOG 82 | PIPX_BIN_DIR = "/opt/pipx_bin" 83 | PIPX_HOME = "/opt/pipx" 84 | POWERSHELL_DISTRIBUTION_CHANNEL = GitHub-Actions-ubuntu18 85 | RUNNER_DEBUG = 1 86 | RUNNER_OS = Linux 87 | RUNNER_PERFLOG = /home/runner/perflog 88 | RUNNER_TEMP = /home/runner/work/_temp 89 | RUNNER_TOOL_CACHE = /opt/hostedtoolcache 90 | RUNNER_TRACKING_ID = github_43daf53c-43fa-45d9-954c-5c024685ce2e 91 | RUNNER_USER = runner 92 | RUNNER_WORKSPACE = /home/runner/work/slack 93 | SELENIUM_JAR_PATH = /usr/share/java/selenium-server-standalone.jar 94 | SLACK_WEBHOOK_URL = *** 95 | SWIFT_PATH = /usr/share/swift/usr/bin 96 | USER = runner 97 | VCPKG_INSTALLATION_ROOT = /usr/local/share/vcpkg -------------------------------------------------------------------------------- /__tests__/fixtures/workflow_dispatch.json: -------------------------------------------------------------------------------- 1 | { 2 | "inputs": null, 3 | "organization": { 4 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 5 | "description": "Automate your GitHub workflows with custom actions", 6 | "events_url": "https://api.github.com/orgs/act10ns/events", 7 | "hooks_url": "https://api.github.com/orgs/act10ns/hooks", 8 | "id": 65077766, 9 | "issues_url": "https://api.github.com/orgs/act10ns/issues", 10 | "login": "act10ns", 11 | "members_url": "https://api.github.com/orgs/act10ns/members{/member}", 12 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 13 | "public_members_url": "https://api.github.com/orgs/act10ns/public_members{/member}", 14 | "repos_url": "https://api.github.com/orgs/act10ns/repos", 15 | "url": "https://api.github.com/orgs/act10ns" 16 | }, 17 | "ref": "refs/heads/master", 18 | "repository": { 19 | "archive_url": "https://api.github.com/repos/act10ns/slack/{archive_format}{/ref}", 20 | "archived": false, 21 | "assignees_url": "https://api.github.com/repos/act10ns/slack/assignees{/user}", 22 | "blobs_url": "https://api.github.com/repos/act10ns/slack/git/blobs{/sha}", 23 | "branches_url": "https://api.github.com/repos/act10ns/slack/branches{/branch}", 24 | "clone_url": "https://github.com/act10ns/slack.git", 25 | "collaborators_url": "https://api.github.com/repos/act10ns/slack/collaborators{/collaborator}", 26 | "comments_url": "https://api.github.com/repos/act10ns/slack/comments{/number}", 27 | "commits_url": "https://api.github.com/repos/act10ns/slack/commits{/sha}", 28 | "compare_url": "https://api.github.com/repos/act10ns/slack/compare/{base}...{head}", 29 | "contents_url": "https://api.github.com/repos/act10ns/slack/contents/{+path}", 30 | "contributors_url": "https://api.github.com/repos/act10ns/slack/contributors", 31 | "created_at": "2020-05-09T14:04:36Z", 32 | "default_branch": "master", 33 | "deployments_url": "https://api.github.com/repos/act10ns/slack/deployments", 34 | "description": "Slack messages for GitHub Actions workflows, jobs and steps", 35 | "disabled": false, 36 | "downloads_url": "https://api.github.com/repos/act10ns/slack/downloads", 37 | "events_url": "https://api.github.com/repos/act10ns/slack/events", 38 | "fork": false, 39 | "forks": 1, 40 | "forks_count": 1, 41 | "forks_url": "https://api.github.com/repos/act10ns/slack/forks", 42 | "full_name": "act10ns/slack", 43 | "git_commits_url": "https://api.github.com/repos/act10ns/slack/git/commits{/sha}", 44 | "git_refs_url": "https://api.github.com/repos/act10ns/slack/git/refs{/sha}", 45 | "git_tags_url": "https://api.github.com/repos/act10ns/slack/git/tags{/sha}", 46 | "git_url": "git://github.com/act10ns/slack.git", 47 | "has_downloads": true, 48 | "has_issues": true, 49 | "has_pages": false, 50 | "has_projects": true, 51 | "has_wiki": true, 52 | "homepage": "https://github.com/marketplace/actions/slack-github-actions-slack-integration", 53 | "hooks_url": "https://api.github.com/repos/act10ns/slack/hooks", 54 | "html_url": "https://github.com/act10ns/slack", 55 | "id": 262583918, 56 | "issue_comment_url": "https://api.github.com/repos/act10ns/slack/issues/comments{/number}", 57 | "issue_events_url": "https://api.github.com/repos/act10ns/slack/issues/events{/number}", 58 | "issues_url": "https://api.github.com/repos/act10ns/slack/issues{/number}", 59 | "keys_url": "https://api.github.com/repos/act10ns/slack/keys{/key_id}", 60 | "labels_url": "https://api.github.com/repos/act10ns/slack/labels{/name}", 61 | "language": "TypeScript", 62 | "languages_url": "https://api.github.com/repos/act10ns/slack/languages", 63 | "license": { 64 | "key": "mit", 65 | "name": "MIT License", 66 | "node_id": "MDc6TGljZW5zZTEz", 67 | "spdx_id": "MIT", 68 | "url": "https://api.github.com/licenses/mit" 69 | }, 70 | "merges_url": "https://api.github.com/repos/act10ns/slack/merges", 71 | "milestones_url": "https://api.github.com/repos/act10ns/slack/milestones{/number}", 72 | "mirror_url": null, 73 | "name": "slack", 74 | "node_id": "MDEwOlJlcG9zaXRvcnkyNjI1ODM5MTg=", 75 | "notifications_url": "https://api.github.com/repos/act10ns/slack/notifications{?since,all,participating}", 76 | "open_issues": 12, 77 | "open_issues_count": 12, 78 | "owner": { 79 | "avatar_url": "https://avatars1.githubusercontent.com/u/65077766?v=4", 80 | "events_url": "https://api.github.com/users/act10ns/events{/privacy}", 81 | "followers_url": "https://api.github.com/users/act10ns/followers", 82 | "following_url": "https://api.github.com/users/act10ns/following{/other_user}", 83 | "gists_url": "https://api.github.com/users/act10ns/gists{/gist_id}", 84 | "gravatar_id": "", 85 | "html_url": "https://github.com/act10ns", 86 | "id": 65077766, 87 | "login": "act10ns", 88 | "node_id": "MDEyOk9yZ2FuaXphdGlvbjY1MDc3NzY2", 89 | "organizations_url": "https://api.github.com/users/act10ns/orgs", 90 | "received_events_url": "https://api.github.com/users/act10ns/received_events", 91 | "repos_url": "https://api.github.com/users/act10ns/repos", 92 | "site_admin": false, 93 | "starred_url": "https://api.github.com/users/act10ns/starred{/owner}{/repo}", 94 | "subscriptions_url": "https://api.github.com/users/act10ns/subscriptions", 95 | "type": "Organization", 96 | "url": "https://api.github.com/users/act10ns" 97 | }, 98 | "private": false, 99 | "pulls_url": "https://api.github.com/repos/act10ns/slack/pulls{/number}", 100 | "pushed_at": "2020-11-13T01:07:48Z", 101 | "releases_url": "https://api.github.com/repos/act10ns/slack/releases{/id}", 102 | "size": 1556, 103 | "ssh_url": "git@github.com:act10ns/slack.git", 104 | "stargazers_count": 22, 105 | "stargazers_url": "https://api.github.com/repos/act10ns/slack/stargazers", 106 | "statuses_url": "https://api.github.com/repos/act10ns/slack/statuses/{sha}", 107 | "subscribers_url": "https://api.github.com/repos/act10ns/slack/subscribers", 108 | "subscription_url": "https://api.github.com/repos/act10ns/slack/subscription", 109 | "svn_url": "https://github.com/act10ns/slack", 110 | "tags_url": "https://api.github.com/repos/act10ns/slack/tags", 111 | "teams_url": "https://api.github.com/repos/act10ns/slack/teams", 112 | "trees_url": "https://api.github.com/repos/act10ns/slack/git/trees{/sha}", 113 | "updated_at": "2020-11-13T01:07:51Z", 114 | "url": "https://api.github.com/repos/act10ns/slack", 115 | "watchers": 22, 116 | "watchers_count": 22 117 | }, 118 | "sender": { 119 | "avatar_url": "https://avatars0.githubusercontent.com/u/615057?v=4", 120 | "events_url": "https://api.github.com/users/satterly/events{/privacy}", 121 | "followers_url": "https://api.github.com/users/satterly/followers", 122 | "following_url": "https://api.github.com/users/satterly/following{/other_user}", 123 | "gists_url": "https://api.github.com/users/satterly/gists{/gist_id}", 124 | "gravatar_id": "", 125 | "html_url": "https://github.com/satterly", 126 | "id": 615057, 127 | "login": "satterly", 128 | "node_id": "MDQ6VXNlcjYxNTA1Nw==", 129 | "organizations_url": "https://api.github.com/users/satterly/orgs", 130 | "received_events_url": "https://api.github.com/users/satterly/received_events", 131 | "repos_url": "https://api.github.com/users/satterly/repos", 132 | "site_admin": false, 133 | "starred_url": "https://api.github.com/users/satterly/starred{/owner}{/repo}", 134 | "subscriptions_url": "https://api.github.com/users/satterly/subscriptions", 135 | "type": "User", 136 | "url": "https://api.github.com/users/satterly" 137 | }, 138 | "workflow": ".github/workflows/manual.yml" 139 | } 140 | -------------------------------------------------------------------------------- /__tests__/fixtures/workflow_run.env.txt: -------------------------------------------------------------------------------- 1 | ACCEPT_EULA = Y 2 | ACTIONS_CACHE_URL = https://artifactcache.actions.githubusercontent.com/vYbmtabU4akFCdAnXD3NmnfhNFUM6zder6WPTPGVPndBPW2Pxw/ 3 | ACTIONS_RUNTIME_TOKEN = *** 4 | ACTIONS_RUNTIME_URL = https://pipelines.actions.githubusercontent.com/vYbmtabU4akFCdAnXD3NmnfhNFUM6zder6WPTPGVPndBPW2Pxw/ 5 | AGENT_TOOLSDIRECTORY = /opt/hostedtoolcache 6 | ANDROID_HOME = /usr/local/lib/android/sdk 7 | ANDROID_NDK_HOME = /usr/local/lib/android/sdk/ndk-bundle 8 | ANDROID_NDK_LATEST_HOME = /usr/local/lib/android/sdk/ndk/23.1.7779620 9 | ANDROID_NDK_ROOT = /usr/local/lib/android/sdk/ndk-bundle 10 | ANDROID_SDK_ROOT = /usr/local/lib/android/sdk 11 | ANT_HOME = /usr/share/ant 12 | AZURE_EXTENSION_DIR = /opt/az/azcliextensions 13 | BOOTSTRAP_HASKELL_NONINTERACTIVE = 1 14 | CHROME_BIN = /usr/bin/google-chrome 15 | CHROMEWEBDRIVER = /usr/local/share/chrome_driver 16 | CI = true 17 | CONDA = /usr/share/miniconda 18 | DEBIAN_FRONTEND = noninteractive 19 | DEPLOYMENT_BASEPATH = /opt/runner 20 | DOTNET_MULTILEVEL_LOOKUP = 0 21 | DOTNET_NOLOGO = 1 22 | DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 23 | GECKOWEBDRIVER = /usr/local/share/gecko_driver 24 | GITHUB_ACTION = __act10ns_slack 25 | GITHUB_ACTION_REF = v1 26 | GITHUB_ACTION_REPOSITORY = act10ns/slack 27 | GITHUB_ACTIONS = true 28 | GITHUB_ACTOR = satterly 29 | GITHUB_API_URL = https://api.github.com 30 | GITHUB_BASE_REF = 31 | GITHUB_ENV = /home/runner/work/_temp/_runner_file_commands/set_env_52dbe441-ed87-4c3c-8c24-5dd994f257dd 32 | GITHUB_EVENT_NAME = workflow_run 33 | GITHUB_EVENT_PATH = /home/runner/work/_temp/_github_workflow/event.json 34 | GITHUB_GRAPHQL_URL = https://api.github.com/graphql 35 | GITHUB_HEAD_REF = 36 | GITHUB_JOB = on-success 37 | GITHUB_PATH = /home/runner/work/_temp/_runner_file_commands/add_path_52dbe441-ed87-4c3c-8c24-5dd994f257dd 38 | GITHUB_REF = refs/heads/master 39 | GITHUB_REF_NAME = master 40 | GITHUB_REF_PROTECTED = false 41 | GITHUB_REF_TYPE = branch 42 | GITHUB_REPOSITORY = act10ns/slack 43 | GITHUB_REPOSITORY_OWNER = act10ns 44 | GITHUB_RETENTION_DAYS = 90 45 | GITHUB_RUN_ATTEMPT = 1 46 | GITHUB_RUN_ID = 1452345894 47 | GITHUB_RUN_NUMBER = 4 48 | GITHUB_SERVER_URL = https://github.com 49 | GITHUB_SHA = 0d05b90e3bf469738248c462d36be1a78520a02e 50 | GITHUB_WORKFLOW = workflow-run 51 | GITHUB_WORKSPACE = /home/runner/work/slack/slack 52 | GOROOT_1_15_X64 = /opt/hostedtoolcache/go/1.15.15/x64 53 | GOROOT_1_16_X64 = /opt/hostedtoolcache/go/1.16.10/x64 54 | GOROOT_1_17_X64 = /opt/hostedtoolcache/go/1.17.3/x64 55 | GRAALVM_11_ROOT = /usr/local/graalvm/graalvm-ce-java11-21.3.0 56 | GRADLE_HOME = /usr/share/gradle-7.2 57 | HOME = /home/runner 58 | HOMEBREW_CELLAR = /home/linuxbrew/.linuxbrew/Cellar 59 | HOMEBREW_CLEANUP_PERIODIC_FULL_DAYS = 3650 60 | HOMEBREW_NO_AUTO_UPDATE = 1 61 | HOMEBREW_PREFIX = /home/linuxbrew/.linuxbrew 62 | HOMEBREW_REPOSITORY = /home/linuxbrew/.linuxbrew/Homebrew 63 | HOMEBREW_SHELLENV_PREFIX = /home/linuxbrew/.linuxbrew 64 | ImageOS = ubuntu20 65 | ImageVersion = 20211108.1 66 | INPUT_CHANNEL = #actions 67 | INPUT_CONFIG = .github/slack.yml 68 | INPUT_STATUS = success 69 | INPUT_STEPS = 70 | INVOCATION_ID = d34823fd1c3f43398818027d9c3e7c70 71 | JAVA_HOME = /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64 72 | JAVA_HOME_11_X64 = /usr/lib/jvm/adoptopenjdk-11-hotspot-amd64 73 | JAVA_HOME_8_X64 = /usr/lib/jvm/adoptopenjdk-8-hotspot-amd64 74 | JOURNAL_STREAM = 8:23120 75 | LANG = C.UTF-8 76 | LEIN_HOME = /usr/local/lib/lein 77 | LEIN_JAR = /usr/local/lib/lein/self-installs/leiningen-2.9.7-standalone.jar 78 | NVM_DIR = /home/runner/.nvm 79 | PATH = /home/linuxbrew/.linuxbrew/bin:/home/linuxbrew/.linuxbrew/sbin:/home/runner/.local/bin:/opt/pipx_bin:/usr/share/rust/.cargo/bin:/home/runner/.config/composer/vendor/bin:/usr/local/.ghcup/bin:/home/runner/.dotnet/tools:/snap/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin 80 | PERFLOG_LOCATION_SETTING = RUNNER_PERFLOG 81 | PIPX_BIN_DIR = /opt/pipx_bin 82 | PIPX_HOME = /opt/pipx 83 | POWERSHELL_DISTRIBUTION_CHANNEL = GitHub-Actions-ubuntu20 84 | RUNNER_ARCH = X64 85 | RUNNER_DEBUG = 1 86 | RUNNER_NAME = Hosted Agent 87 | RUNNER_OS = Linux 88 | RUNNER_PERFLOG = /home/runner/perflog 89 | RUNNER_TEMP = /home/runner/work/_temp 90 | RUNNER_TOOL_CACHE = /opt/hostedtoolcache 91 | RUNNER_TRACKING_ID = github_9de6a5f4-e7a0-4c0f-bc11-f983e9a814e1 92 | RUNNER_USER = runner 93 | RUNNER_WORKSPACE = /home/runner/work/slack 94 | SELENIUM_JAR_PATH = /usr/share/java/selenium-server-standalone.jar 95 | SGX_AESM_ADDR = 1 96 | SLACK_WEBHOOK_URL = *** 97 | STATS_KEEPALIVE = true 98 | SWIFT_PATH = /usr/share/swift/usr/bin 99 | USER = runner 100 | VCPKG_INSTALLATION_ROOT = /usr/local/share/vcpkg 101 | XDG_CONFIG_HOME = /home/runner/.config -------------------------------------------------------------------------------- /__tests__/handlebars.test.ts: -------------------------------------------------------------------------------- 1 | import Handlebars from '../src/handlebars' 2 | 3 | const data = { 4 | foo: 'bar', 5 | fu: 'bar', 6 | baz: 'quux', 7 | obj: { 8 | a: 'b', 9 | c: 123, 10 | d: ['x', 'y', 'z'] 11 | }, 12 | text: 'this is a long line of text', 13 | uuid: 'CFE20509-E7CF-4401-9733-7615EF3E8A25', 14 | want: 0, 15 | t: true, 16 | f: false, 17 | empty: [], 18 | empty_str: '', 19 | commits: ['088fcd5a5bc73a6733edcc58d0d30869ddbe2e2f'], 20 | numbers: [1, 2, 3, 4], 21 | letters: ['abc', 'def', 'ghi'], 22 | foobars: [ 23 | {foo: true, bar: 1}, 24 | {foo: false, bar: 3}, 25 | {foo: true, bar: 3} 26 | ], 27 | attendees: ['dave', 'mike', 'jane', 'betty'], 28 | items: [1, 2, 3], 29 | payload: { 30 | workflow_run: { 31 | run_attempt: 7 32 | } 33 | } 34 | } 35 | 36 | // utilities 37 | test('json stringify', () => { 38 | const template = Handlebars.compile('{{{json obj}}}') 39 | const text = template(data) 40 | expect(text).toStrictEqual('{"a":"b","c":123,"d":["x","y","z"]}') 41 | }) 42 | 43 | test('truncate uuid', () => { 44 | const template = Handlebars.compile('{{truncate uuid 8}}') 45 | const text = template(data) 46 | expect(text).toStrictEqual('CFE20509') 47 | }) 48 | 49 | test('want when exists', () => { 50 | const template = Handlebars.compile('{{default foo "fallback"}}') 51 | const text = template(data) 52 | expect(text).toStrictEqual('bar') 53 | }) 54 | 55 | test('want when false', () => { 56 | const template = Handlebars.compile('{{default f "fallback"}}') 57 | const text = template(data) 58 | expect(text).toStrictEqual('false') 59 | }) 60 | 61 | test('want when 0', () => { 62 | const template = Handlebars.compile('{{default want "fallback"}}') 63 | const text = template(data) 64 | expect(text).toStrictEqual('0') 65 | }) 66 | 67 | test('default value when not exists', () => { 68 | const template = Handlebars.compile('{{default fallback baz}}') 69 | const text = template(data) 70 | expect(text).toStrictEqual('quux') 71 | }) 72 | 73 | test('default string when not exists', () => { 74 | const template = Handlebars.compile('{{default fallback "fallback"}}') 75 | const text = template(data) 76 | expect(text).toStrictEqual('fallback') 77 | }) 78 | 79 | test('default when empty string', () => { 80 | const template = Handlebars.compile('{{default empty_str "fallback"}}') 81 | const text = template(data) 82 | expect(text).toStrictEqual('fallback') 83 | }) 84 | 85 | test('pluralize empty list', () => { 86 | const template = Handlebars.compile('{{pluralize empty}}') 87 | const text = template(data) 88 | expect(text).toStrictEqual('no items') 89 | }) 90 | 91 | test('pluralize numeric value', () => { 92 | const template = Handlebars.compile('{{pluralize payload.workflow_run.run_attempt "attempt"}}') 93 | const text = template(data) 94 | expect(text).toStrictEqual('7 attempts') 95 | }) 96 | 97 | test('pluralize commits', () => { 98 | const template = Handlebars.compile('{{pluralize commits "commit"}}') 99 | const text = template(data) 100 | expect(text).toStrictEqual('1 commit') 101 | }) 102 | 103 | test('pluralize attendees', () => { 104 | const template = Handlebars.compile('{{pluralize attendees "attendee"}}') 105 | const text = template(data) 106 | expect(text).toStrictEqual('4 attendees') 107 | }) 108 | 109 | test('pluralize people', () => { 110 | const template = Handlebars.compile('{{pluralize attendees "person" "people"}}') 111 | const text = template(data) 112 | expect(text).toStrictEqual('4 people') 113 | }) 114 | 115 | // equality 116 | test('eq is true', () => { 117 | const template = Handlebars.compile('{{#if (eq foo fu)}}yes{{else}}no{{/if}}') 118 | const text = template(data) 119 | expect(text).toStrictEqual('yes') 120 | }) 121 | 122 | test('eq is false', () => { 123 | const template = Handlebars.compile('{{#if (eq foo "foo")}}yes{{else}}no{{/if}}') 124 | const text = template(data) 125 | expect(text).toStrictEqual('no') 126 | }) 127 | 128 | test('neq is true', () => { 129 | const template = Handlebars.compile('{{#if (neq foo fu)}}yes{{else}}no{{/if}}') 130 | const text = template(data) 131 | expect(text).toStrictEqual('no') 132 | }) 133 | 134 | test('neq is false', () => { 135 | const template = Handlebars.compile('{{#if (neq foo "foo")}}yes{{else}}no{{/if}}') 136 | const text = template(data) 137 | expect(text).toStrictEqual('yes') 138 | }) 139 | 140 | // boolean operators 141 | test('not false', () => { 142 | const template = Handlebars.compile('{{#if (not f)}}yes{{else}}no{{/if}}') 143 | const text = template(data) 144 | expect(text).toStrictEqual('yes') 145 | }) 146 | 147 | test('not true', () => { 148 | const template = Handlebars.compile('{{#if (not t)}}yes{{else}}no{{/if}}') 149 | const text = template(data) 150 | expect(text).toStrictEqual('no') 151 | }) 152 | 153 | test('not false and (true or false)', () => { 154 | const template = Handlebars.compile('{{#if (and (not f) (or t f))}}yes{{else}}no{{/if}}') 155 | const text = template(data) 156 | expect(text).toStrictEqual('yes') 157 | }) 158 | 159 | test('or is true', () => { 160 | const template = Handlebars.compile('{{#if (or t f)}}yes{{else}}no{{/if}}') 161 | const text = template(data) 162 | expect(text).toStrictEqual('yes') 163 | }) 164 | 165 | test('and is false', () => { 166 | const template = Handlebars.compile('{{#if (and t f)}}yes{{else}}no{{/if}}') 167 | const text = template(data) 168 | expect(text).toStrictEqual('no') 169 | }) 170 | 171 | // conditionals 172 | test('#ifeq is true', () => { 173 | const template = Handlebars.compile('{{#ifeq foo fu}}yes{{else}}no{{/ifeq}}') 174 | const text = template(data) 175 | expect(text).toStrictEqual('yes') 176 | }) 177 | 178 | test('#ifeq is false', () => { 179 | const template = Handlebars.compile('{{#ifeq foo "foo"}}yes{{else}}no{{/ifeq}}') 180 | const text = template(data) 181 | expect(text).toStrictEqual('no') 182 | }) 183 | 184 | test('#ifneq is true', () => { 185 | const template = Handlebars.compile('{{#ifneq foo fu}}yes{{else}}no{{/ifneq}}') 186 | const text = template(data) 187 | expect(text).toStrictEqual('no') 188 | }) 189 | 190 | test('#ifneq is false', () => { 191 | const template = Handlebars.compile('{{#ifneq foo "foo"}}yes{{else}}no{{/ifneq}}') 192 | const text = template(data) 193 | expect(text).toStrictEqual('yes') 194 | }) 195 | -------------------------------------------------------------------------------- /__tests__/inputs.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send, ConfigOptions} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | import * as yaml from 'js-yaml' 7 | 8 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 9 | const jobName = 'CI Tests' 10 | const jobStatus = 'in progress' 11 | const jobSteps = { 12 | 'install-deps': { 13 | outputs: {}, 14 | outcome: 'success', 15 | conclusion: 'success' 16 | }, 17 | hooks: { 18 | outputs: {}, 19 | outcome: 'skipped', 20 | conclusion: 'skipped' 21 | }, 22 | lint: { 23 | outputs: {}, 24 | outcome: 'skipped', 25 | conclusion: 'skipped' 26 | }, 27 | types: { 28 | outputs: {}, 29 | outcome: 'skipped', 30 | conclusion: 'skipped' 31 | }, 32 | 'unit-test': { 33 | outputs: {}, 34 | outcome: 'failure', 35 | conclusion: 'failure' 36 | }, 37 | 'integration-test': { 38 | outputs: {}, 39 | outcome: 'cancelled', 40 | conclusion: 'cancelled' 41 | } 42 | } 43 | const jobMatrix = {} 44 | const jobInputs = {} 45 | const channel = '#deploy' 46 | let message = 'Successfully deployed to {{ env.ENVIRONMENT }}!' 47 | 48 | // mock github context 49 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/push.json', 'utf-8')) 50 | 51 | github.context.payload = dump.event 52 | github.context.eventName = dump.event_name 53 | github.context.sha = dump.sha 54 | github.context.ref = dump.ref 55 | github.context.workflow = dump.workflow 56 | github.context.action = dump.action 57 | github.context.actor = dump.actor 58 | 59 | process.env.ENVIRONMENT = 'dev' 60 | process.env.CI = 'true' 61 | process.env.GITHUB_WORKFLOW = 'build-test' 62 | process.env.GITHUB_RUN_ID = '100143423' 63 | process.env.GITHUB_RUN_NUMBER = '8' 64 | process.env.GITHUB_ACTION = 'self2' 65 | process.env.GITHUB_ACTIONS = 'true' 66 | process.env.GITHUB_ACTOR = 'satterly' 67 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 68 | process.env.GITHUB_EVENT_NAME = 'push' 69 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 70 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 71 | process.env.GITHUB_SHA = '68d48876e0794fba714cb331a1624af6b20942d8' 72 | process.env.GITHUB_REF = 'refs/heads/master' 73 | process.env.GITHUB_REF_TYPE = 'branch' 74 | process.env.GITHUB_REF_NAME = 'master' 75 | process.env.GITHUB_HEAD_REF = '' 76 | process.env.GITHUB_BASE_REF = '' 77 | process.env.GITHUB_SERVER_URL = 'https://github.com' 78 | process.env.GITHUB_API_URL = 'https://github.com' 79 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 80 | 81 | test('custom config of slack action using inputs for channel and message', async () => { 82 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 83 | 84 | mockAxios 85 | .onPost() 86 | .reply(config => { 87 | console.log(config.data) 88 | return [200, {status: 'ok'}] 89 | }) 90 | .onAny() 91 | .reply(500) 92 | 93 | let config = yaml.load(readFileSync('./__tests__/fixtures/slack-legacy.yml', 'utf-8'), { 94 | schema: yaml.FAILSAFE_SCHEMA 95 | }) as ConfigOptions 96 | 97 | let res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message, config) 98 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 99 | 100 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 101 | username: 'GitHub-CI', 102 | icon_url: 'https://octodex.github.com/images/mona-the-rivetertocat.png', 103 | channel: '#deploy', 104 | timeout: 0, 105 | attachments: [ 106 | { 107 | fallback: '[GitHub] build-test #8 CI Tests is in progress', 108 | color: '#7D3C98', 109 | author_name: 'satterly', 110 | author_link: 'https://github.com/satterly', 111 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 112 | mrkdwn_in: ['pretext', 'text', 'fields'], 113 | pretext: 'Triggered via push by satterly action master `68d48876`', 114 | text: 'Successfully deployed to dev!', 115 | title: 'GitHub Actions', 116 | title_link: 'https://support.github.com', 117 | fields: [ 118 | { 119 | short: false, 120 | title: 'Job Steps', 121 | value: ':white_check_mark: install-deps\n:grimacing: unit-test\n:x: integration-test\n' 122 | }, 123 | { 124 | short: true, 125 | title: 'Workflow', 126 | value: '' 127 | }, 128 | { 129 | short: true, 130 | title: 'Git Ref', 131 | value: 'master (branch)' 132 | }, 133 | { 134 | short: true, 135 | title: 'Run ID', 136 | value: '' 137 | }, 138 | { 139 | short: true, 140 | title: 'Run Number', 141 | value: '8' 142 | }, 143 | { 144 | short: true, 145 | title: 'Actor', 146 | value: 'satterly' 147 | }, 148 | { 149 | short: true, 150 | title: 'Job Status', 151 | value: 'in progress' 152 | } 153 | ], 154 | footer: ' build-test #8', 155 | footer_icon: 'https://github.githubassets.com/favicon.ico', 156 | ts: expect.stringMatching(/[0-9]+/) 157 | } 158 | ] 159 | }) 160 | 161 | mockAxios.resetHistory() 162 | mockAxios.reset() 163 | }) 164 | -------------------------------------------------------------------------------- /__tests__/job_inputs.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'Build and Test' 9 | const jobStatus = 'Success' 10 | const jobSteps = {} 11 | const jobMatrix = {} 12 | const jobInputs = { 13 | environment: 'staging', 14 | logLevel: 'warning', 15 | print_tags: true 16 | } 17 | const channel = '@override' 18 | const message = undefined 19 | 20 | // mock github context 21 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/push.json', 'utf-8')) 22 | 23 | github.context.payload = dump.event 24 | github.context.eventName = dump.event_name 25 | github.context.sha = dump.sha 26 | github.context.ref = dump.ref 27 | github.context.workflow = dump.workflow 28 | github.context.action = dump.action 29 | github.context.actor = dump.actor 30 | 31 | process.env.CI = 'true' 32 | process.env.GITHUB_WORKFLOW = 'build-test' 33 | process.env.GITHUB_RUN_ID = '100143423' 34 | process.env.GITHUB_RUN_NUMBER = '8' 35 | process.env.GITHUB_ACTION = 'self2' 36 | process.env.GITHUB_ACTIONS = 'true' 37 | process.env.GITHUB_ACTOR = 'satterly' 38 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 39 | process.env.GITHUB_EVENT_NAME = 'push' 40 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 41 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 42 | process.env.GITHUB_SHA = '68d48876e0794fba714cb331a1624af6b20942d8' 43 | process.env.GITHUB_REF = 'refs/heads/master' 44 | process.env.GITHUB_HEAD_REF = '' 45 | process.env.GITHUB_BASE_REF = '' 46 | process.env.GITHUB_SERVER_URL = 'https://github.com' 47 | process.env.GITHUB_API_URL = 'https://github.com' 48 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 49 | 50 | test('push event to slack', async () => { 51 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 52 | 53 | mockAxios 54 | .onPost() 55 | .reply(config => { 56 | console.log(config.data) 57 | return [200, {status: 'ok'}] 58 | }) 59 | .onAny() 60 | .reply(500) 61 | 62 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message) 63 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 64 | 65 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 66 | username: 'GitHub Actions', 67 | icon_url: 'https://octodex.github.com/images/original.png', 68 | channel: '@override', 69 | timeout: 0, 70 | attachments: [ 71 | { 72 | fallback: '[GitHub]: [act10ns/slack] build-test push Success', 73 | color: 'good', 74 | author_name: 'satterly', 75 | author_link: 'https://github.com/satterly', 76 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 77 | mrkdwn_in: ['pretext', 'text', 'fields'], 78 | pretext: '', 79 | text: '** for \n - 4 commits', 80 | title: '', 81 | fields: [ 82 | { 83 | title: 'Job Inputs', 84 | value: 'environment: staging\nlogLevel: warning\nprint_tags: true\n', 85 | short: false 86 | } 87 | ], 88 | footer: ' #8', 89 | footer_icon: 'https://github.githubassets.com/favicon.ico', 90 | ts: expect.stringMatching(/[0-9]+/) 91 | } 92 | ] 93 | }) 94 | 95 | mockAxios.resetHistory() 96 | mockAxios.reset() 97 | }) 98 | -------------------------------------------------------------------------------- /__tests__/job_matrix.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'Build and Test' 9 | const jobStatus = 'Success' 10 | const jobSteps = {} 11 | const jobMatrix = { 12 | name1: 'value1', 13 | name2: 'value2' 14 | } 15 | const jobInputs = {} 16 | const channel = '@override' 17 | const message = undefined 18 | 19 | // mock github context 20 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/push.json', 'utf-8')) 21 | 22 | github.context.payload = dump.event 23 | github.context.eventName = dump.event_name 24 | github.context.sha = dump.sha 25 | github.context.ref = dump.ref 26 | github.context.workflow = dump.workflow 27 | github.context.action = dump.action 28 | github.context.actor = dump.actor 29 | 30 | process.env.CI = 'true' 31 | process.env.GITHUB_WORKFLOW = 'build-test' 32 | process.env.GITHUB_RUN_ID = '100143423' 33 | process.env.GITHUB_RUN_NUMBER = '8' 34 | process.env.GITHUB_ACTION = 'self2' 35 | process.env.GITHUB_ACTIONS = 'true' 36 | process.env.GITHUB_ACTOR = 'satterly' 37 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 38 | process.env.GITHUB_EVENT_NAME = 'push' 39 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 40 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 41 | process.env.GITHUB_SHA = '68d48876e0794fba714cb331a1624af6b20942d8' 42 | process.env.GITHUB_REF = 'refs/heads/master' 43 | process.env.GITHUB_HEAD_REF = '' 44 | process.env.GITHUB_BASE_REF = '' 45 | process.env.GITHUB_SERVER_URL = 'https://github.com' 46 | process.env.GITHUB_API_URL = 'https://github.com' 47 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 48 | 49 | test('push event to slack', async () => { 50 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 51 | 52 | mockAxios 53 | .onPost() 54 | .reply(config => { 55 | console.log(config.data) 56 | return [200, {status: 'ok'}] 57 | }) 58 | .onAny() 59 | .reply(500) 60 | 61 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message) 62 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 63 | 64 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 65 | username: 'GitHub Actions', 66 | icon_url: 'https://octodex.github.com/images/original.png', 67 | channel: '@override', 68 | timeout: 0, 69 | attachments: [ 70 | { 71 | fallback: '[GitHub]: [act10ns/slack] build-test push Success', 72 | color: 'good', 73 | author_name: 'satterly', 74 | author_link: 'https://github.com/satterly', 75 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 76 | mrkdwn_in: ['pretext', 'text', 'fields'], 77 | pretext: '', 78 | text: '** for \n - 4 commits', 79 | title: '', 80 | fields: [ 81 | { 82 | title: 'Job Matrix', 83 | value: 'name1: value1\nname2: value2\n', 84 | short: false 85 | } 86 | ], 87 | footer: ' #8', 88 | footer_icon: 'https://github.githubassets.com/favicon.ico', 89 | ts: expect.stringMatching(/[0-9]+/) 90 | } 91 | ] 92 | }) 93 | 94 | mockAxios.resetHistory() 95 | mockAxios.reset() 96 | }) 97 | -------------------------------------------------------------------------------- /__tests__/job_status.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send, ConfigOptions} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'CI Tests' 9 | const jobStatus = 'failure' 10 | const jobSteps = { 11 | 'install-deps': { 12 | outputs: {}, 13 | outcome: 'skipped', 14 | conclusion: 'skipped' 15 | }, 16 | hooks: { 17 | outputs: {}, 18 | outcome: 'skipped', 19 | conclusion: 'skipped' 20 | }, 21 | lint: { 22 | outputs: {}, 23 | outcome: 'skipped', 24 | conclusion: 'skipped' 25 | }, 26 | types: { 27 | outputs: {}, 28 | outcome: 'skipped', 29 | conclusion: 'skipped' 30 | }, 31 | 'unit-test': { 32 | outputs: {}, 33 | outcome: 'skipped', 34 | conclusion: 'skipped' 35 | }, 36 | 'integration-test': { 37 | outputs: {}, 38 | outcome: 'skipped', 39 | conclusion: 'skipped' 40 | } 41 | } 42 | const jobMatrix = {} 43 | const jobInputs = {} 44 | const channel = '#github-ci' 45 | const message = undefined 46 | 47 | // mock github context 48 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/push.json', 'utf-8')) 49 | 50 | github.context.payload = dump.event 51 | github.context.eventName = dump.event_name 52 | github.context.sha = dump.sha 53 | github.context.ref = dump.ref 54 | github.context.workflow = dump.workflow 55 | github.context.action = dump.action 56 | github.context.actor = dump.actor 57 | 58 | process.env.CI = 'true' 59 | process.env.GITHUB_WORKFLOW = 'build-test' 60 | process.env.GITHUB_RUN_ID = '100143423' 61 | process.env.GITHUB_RUN_NUMBER = '8' 62 | process.env.GITHUB_ACTION = 'self2' 63 | process.env.GITHUB_ACTIONS = 'true' 64 | process.env.GITHUB_ACTOR = 'satterly' 65 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 66 | process.env.GITHUB_EVENT_NAME = 'push' 67 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 68 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 69 | process.env.GITHUB_SHA = '68d48876e0794fba714cb331a1624af6b20942d8' 70 | process.env.GITHUB_REF = 'refs/heads/master' 71 | process.env.GITHUB_HEAD_REF = '' 72 | process.env.GITHUB_BASE_REF = '' 73 | process.env.GITHUB_SERVER_URL = 'https://github.com' 74 | process.env.GITHUB_API_URL = 'https://github.com' 75 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 76 | 77 | test('push event to slack', async () => { 78 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 79 | 80 | mockAxios 81 | .onPost() 82 | .reply(config => { 83 | console.log(config.data) 84 | return [200, {status: 'ok'}] 85 | }) 86 | .onAny() 87 | .reply(500) 88 | 89 | const config: ConfigOptions = {} 90 | 91 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message, config) 92 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 93 | 94 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 95 | username: 'GitHub Actions', 96 | icon_url: 'https://octodex.github.com/images/original.png', 97 | channel: '#github-ci', 98 | timeout: 0, 99 | attachments: [ 100 | { 101 | fallback: '[GitHub]: [act10ns/slack] build-test push failure', 102 | color: 'danger', 103 | author_name: 'satterly', 104 | author_link: 'https://github.com/satterly', 105 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 106 | mrkdwn_in: ['pretext', 'text', 'fields'], 107 | pretext: '', 108 | text: '** for \n - 4 commits', 109 | title: '', 110 | fields: [ 111 | { 112 | short: false, 113 | title: 'Job Steps', 114 | value: 115 | ':no_entry_sign: install-deps\n:no_entry_sign: hooks\n:no_entry_sign: lint\n:no_entry_sign: types\n:no_entry_sign: unit-test\n:no_entry_sign: integration-test\n' 116 | } 117 | ], 118 | footer: ' #8', 119 | footer_icon: 'https://github.githubassets.com/favicon.ico', 120 | ts: expect.stringMatching(/[0-9]+/) 121 | } 122 | ] 123 | }) 124 | 125 | mockAxios.resetHistory() 126 | mockAxios.reset() 127 | }) 128 | -------------------------------------------------------------------------------- /__tests__/pull_request.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'Build and Test' 9 | const jobStatus = 'Success' 10 | const jobSteps = {} 11 | const jobMatrix = {} 12 | const jobInputs = {} 13 | const channel = '@override' 14 | const message = undefined 15 | 16 | // mock github context 17 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/pull_request.json', 'utf-8')) 18 | 19 | github.context.payload = dump.event 20 | github.context.eventName = dump.event_name 21 | github.context.sha = dump.sha 22 | github.context.ref = dump.ref 23 | github.context.workflow = dump.workflow 24 | github.context.action = dump.action 25 | github.context.actor = dump.actor 26 | 27 | process.env.CI = 'true' 28 | process.env.GITHUB_WORKFLOW = 'build-test' 29 | process.env.GITHUB_RUN_ID = '360703544' 30 | process.env.GITHUB_RUN_NUMBER = '760' 31 | process.env.GITHUB_ACTION = 'self2' 32 | process.env.GITHUB_ACTIONS = 'true' 33 | process.env.GITHUB_ACTOR = 'satterly' 34 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 35 | process.env.GITHUB_EVENT_NAME = 'pull_request' 36 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 37 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 38 | process.env.GITHUB_SHA = 'f4c103c8121b97a235791468fd31ce98e89a5e9e' 39 | process.env.GITHUB_REF = 'refs/pull/17/merge' 40 | process.env.GITHUB_HEAD_REF = 'rename-to-slack' 41 | process.env.GITHUB_BASE_REF = 'master' 42 | process.env.GITHUB_SERVER_URL = 'https://github.com' 43 | process.env.GITHUB_API_URL = 'https://github.com' 44 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 45 | 46 | test('pull request event to slack', async () => { 47 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 48 | 49 | mockAxios 50 | .onPost() 51 | .reply(config => { 52 | console.log(config.data) 53 | return [200, {status: 'ok'}] 54 | }) 55 | .onAny() 56 | .reply(500) 57 | 58 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message) 59 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 60 | 61 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 62 | username: 'GitHub Actions', 63 | icon_url: 'https://octodex.github.com/images/original.png', 64 | channel: '@override', 65 | timeout: 0, 66 | attachments: [ 67 | { 68 | fallback: '[GitHub]: [act10ns/slack] build-test pull_request opened Success', 69 | color: 'good', 70 | author_name: 'satterly', 71 | author_link: 'https://github.com/satterly', 72 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 73 | mrkdwn_in: ['pretext', 'text', 'fields'], 74 | pretext: '', 75 | text: '** for \n - Rename to slack', 76 | title: '', 77 | fields: [], 78 | footer: ' #760', 79 | footer_icon: 'https://github.githubassets.com/favicon.ico', 80 | ts: expect.stringMatching(/[0-9]+/) 81 | } 82 | ] 83 | }) 84 | 85 | mockAxios.resetHistory() 86 | mockAxios.reset() 87 | }) 88 | -------------------------------------------------------------------------------- /__tests__/push.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'Build and Test' 9 | const jobStatus = 'Success' 10 | const jobSteps = {} 11 | const jobMatrix = {} 12 | const jobInputs = {} 13 | const channel = '@override' 14 | const message = undefined 15 | 16 | // mock github context 17 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/push.json', 'utf-8')) 18 | 19 | github.context.payload = dump.event 20 | github.context.eventName = dump.event_name 21 | github.context.sha = dump.sha 22 | github.context.ref = dump.ref 23 | github.context.workflow = dump.workflow 24 | github.context.action = dump.action 25 | github.context.actor = dump.actor 26 | 27 | process.env.CI = 'true' 28 | process.env.GITHUB_WORKFLOW = 'build-test' 29 | process.env.GITHUB_RUN_ID = '100143423' 30 | process.env.GITHUB_RUN_NUMBER = '8' 31 | process.env.GITHUB_ACTION = 'self2' 32 | process.env.GITHUB_ACTIONS = 'true' 33 | process.env.GITHUB_ACTOR = 'satterly' 34 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 35 | process.env.GITHUB_EVENT_NAME = 'push' 36 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 37 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 38 | process.env.GITHUB_SHA = '68d48876e0794fba714cb331a1624af6b20942d8' 39 | process.env.GITHUB_REF = 'refs/heads/master' 40 | process.env.GITHUB_HEAD_REF = '' 41 | process.env.GITHUB_BASE_REF = '' 42 | process.env.GITHUB_SERVER_URL = 'https://github.com' 43 | process.env.GITHUB_API_URL = 'https://github.com' 44 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 45 | 46 | test('push event to slack', async () => { 47 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 48 | 49 | mockAxios 50 | .onPost() 51 | .reply(config => { 52 | console.log(config.data) 53 | return [200, {status: 'ok'}] 54 | }) 55 | .onAny() 56 | .reply(500) 57 | 58 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message) 59 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 60 | 61 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 62 | username: 'GitHub Actions', 63 | icon_url: 'https://octodex.github.com/images/original.png', 64 | channel: '@override', 65 | timeout: 0, 66 | attachments: [ 67 | { 68 | fallback: '[GitHub]: [act10ns/slack] build-test push Success', 69 | color: 'good', 70 | author_name: 'satterly', 71 | author_link: 'https://github.com/satterly', 72 | author_icon: 'https://avatars0.githubusercontent.com/u/615057?v=4', 73 | mrkdwn_in: ['pretext', 'text', 'fields'], 74 | pretext: '', 75 | text: '** for \n - 4 commits', 76 | title: '', 77 | fields: [], 78 | footer: ' #8', 79 | footer_icon: 'https://github.githubassets.com/favicon.ico', 80 | ts: expect.stringMatching(/[0-9]+/) 81 | } 82 | ] 83 | }) 84 | 85 | mockAxios.resetHistory() 86 | mockAxios.reset() 87 | }) 88 | -------------------------------------------------------------------------------- /__tests__/release.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'Build and Test' 9 | const jobStatus = 'Success' 10 | const jobSteps = {} 11 | const jobMatrix = {} 12 | const jobInputs = {} 13 | const channel = '@override' 14 | const message = undefined 15 | 16 | // mock github context 17 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/release.json', 'utf-8')) 18 | 19 | github.context.payload = dump.event 20 | github.context.eventName = dump.event_name 21 | github.context.sha = dump.sha 22 | github.context.ref = dump.ref 23 | github.context.workflow = dump.workflow 24 | github.context.action = dump.action 25 | github.context.actor = dump.actor 26 | console.log(github.context) 27 | 28 | process.env.CI = 'true' 29 | process.env.GITHUB_WORKFLOW = 'build-test' 30 | process.env.GITHUB_JOB = 'build' 31 | process.env.GITHUB_RUN_ID = '361391443' 32 | process.env.GITHUB_RUN_NUMBER = '817' 33 | process.env.GITHUB_ACTION = 'self' 34 | process.env.GITHUB_ACTION_REF = 'v2' 35 | process.env.GITHUB_ACTIONS = 'true' 36 | process.env.GITHUB_ACTOR = 'satterly' 37 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 38 | process.env.GITHUB_EVENT_NAME = 'release' 39 | process.env.GITHUB_EVENT_PATH = 'fixtures/release.json' 40 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 41 | process.env.GITHUB_SHA = '332b8416cd15a8f77816a5d3df21423b16b46756' 42 | process.env.GITHUB_REF = 'refs/tags/v1.0.13' 43 | process.env.GITHUB_HEAD_REF = '' 44 | process.env.GITHUB_BASE_REF = '' 45 | process.env.GITHUB_SERVER_URL = 'https://github.com' 46 | process.env.GITHUB_API_URL = 'https://api.github.com' 47 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 48 | 49 | test('release event to slack', async () => { 50 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 51 | 52 | mockAxios 53 | .onPost() 54 | .reply(config => { 55 | console.log(config.data) 56 | return [200, {status: 'ok'}] 57 | }) 58 | .onAny() 59 | .reply(500) 60 | 61 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message) 62 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 63 | 64 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 65 | username: 'GitHub Actions', 66 | icon_url: 'https://octodex.github.com/images/original.png', 67 | timeout: 0, 68 | channel: '@override', 69 | attachments: [ 70 | { 71 | fallback: '[GitHub]: [act10ns/slack] build-test release Success', 72 | color: 'good', 73 | author_name: 'satterly', 74 | author_link: 'https://github.com/satterly', 75 | author_icon: '', 76 | mrkdwn_in: ['pretext', 'text', 'fields'], 77 | pretext: '', 78 | text: '** for \n', 79 | title: '', 80 | fields: [], 81 | footer: ' #817', 82 | footer_icon: 'https://github.githubassets.com/favicon.ico', 83 | ts: expect.stringMatching(/[0-9]+/) 84 | } 85 | ] 86 | }) 87 | 88 | mockAxios.resetHistory() 89 | mockAxios.reset() 90 | }) 91 | -------------------------------------------------------------------------------- /__tests__/schedule.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'Build and Test' 9 | const jobStatus = 'Success' 10 | const jobSteps = {} 11 | const jobMatrix = {} 12 | const jobInputs = {} 13 | const channel = '@override' 14 | const message = undefined 15 | 16 | // mock github context 17 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/schedule.json', 'utf-8')) 18 | 19 | github.context.payload = dump 20 | 21 | process.env.CI = 'true' 22 | process.env.GITHUB_WORKFLOW = 'schedule-test' 23 | process.env.GITHUB_JOB = 'build' 24 | process.env.GITHUB_RUN_ID = '363600556' 25 | process.env.GITHUB_RUN_NUMBER = '179' 26 | process.env.GITHUB_ACTION = 'self2' 27 | process.env.GITHUB_ACTIONS = 'true' 28 | process.env.GITHUB_ACTOR = 'satterly' 29 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 30 | process.env.GITHUB_EVENT_NAME = 'schedule' 31 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 32 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 33 | process.env.GITHUB_SHA = '09a6b2c984766efb19eb39c97bc8be5d352a102f' 34 | process.env.GITHUB_REF = 'refs/heads/master' 35 | process.env.GITHUB_HEAD_REF = '' 36 | process.env.GITHUB_BASE_REF = '' 37 | process.env.GITHUB_SERVER_URL = 'https://github.com' 38 | process.env.GITHUB_API_URL = 'https://github.com' 39 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 40 | process.env.INVOCATION_ID = '1a1f065e457f48ea96eb5d289fa1bb9f' 41 | 42 | test('schedule event to slack', async () => { 43 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 44 | 45 | mockAxios 46 | .onPost() 47 | .reply(config => { 48 | console.log(config.data) 49 | return [200, {status: 'ok'}] 50 | }) 51 | .onAny() 52 | .reply(500) 53 | 54 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message) 55 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 56 | 57 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 58 | username: 'GitHub Actions', 59 | icon_url: 'https://octodex.github.com/images/original.png', 60 | channel: '@override', 61 | timeout: 0, 62 | attachments: [ 63 | { 64 | fallback: '[GitHub]: [act10ns/slack] schedule-test schedule Success', 65 | color: 'good', 66 | author_name: 'github', 67 | author_link: 'https://github.com/github', 68 | author_icon: 'https://avatars1.githubusercontent.com/u/9919?s=200&v=4', 69 | mrkdwn_in: ['pretext', 'text', 'fields'], 70 | pretext: '', 71 | text: '** for \n - Schedule `*/15 * * * *`', 72 | title: '', 73 | fields: [], 74 | footer: ' #179', 75 | footer_icon: 'https://github.githubassets.com/favicon.ico', 76 | ts: expect.stringMatching(/[0-9]+/) 77 | } 78 | ] 79 | }) 80 | 81 | mockAxios.resetHistory() 82 | mockAxios.reset() 83 | }) 84 | -------------------------------------------------------------------------------- /__tests__/workflow_dispatch.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | 7 | const url = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 8 | const jobName = 'Build and Test' 9 | const jobStatus = 'Success' 10 | const jobSteps = {} 11 | const jobMatrix = {} 12 | const jobInputs = {} 13 | const channel = '@override' 14 | const message = undefined 15 | 16 | // mock github context 17 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/workflow_dispatch.json', 'utf-8')) 18 | 19 | github.context.payload = dump.event 20 | github.context.eventName = dump.event_name 21 | github.context.sha = dump.sha 22 | github.context.ref = dump.ref 23 | github.context.workflow = dump.workflow 24 | github.context.action = dump.action 25 | github.context.actor = dump.actor 26 | 27 | process.env.CI = 'true' 28 | process.env.GITHUB_WORKFLOW = 'manual-test' 29 | process.env.GITHUB_JOB = 'build' 30 | process.env.GITHUB_RUN_ID = '360767681' 31 | process.env.GITHUB_RUN_NUMBER = '6' 32 | process.env.GITHUB_ACTION = 'self2' 33 | process.env.GITHUB_ACTIONS = 'true' 34 | process.env.GITHUB_ACTOR = 'satterly' 35 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 36 | process.env.GITHUB_EVENT_NAME = 'workflow_dispatch' 37 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 38 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 39 | process.env.GITHUB_SHA = 'f4c103c8121b97a235791468fd31ce98e89a5e9e' 40 | process.env.GITHUB_REF = 'refs/heads/master' 41 | process.env.GITHUB_HEAD_REF = '' 42 | process.env.GITHUB_BASE_REF = '' 43 | process.env.GITHUB_SERVER_URL = 'https://github.com' 44 | process.env.GITHUB_API_URL = 'https://github.com' 45 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 46 | 47 | test('workflow_dispatch event to slack', async () => { 48 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 49 | 50 | mockAxios 51 | .onPost() 52 | .reply(config => { 53 | console.log(config.data) 54 | return [200, {status: 'ok'}] 55 | }) 56 | .onAny() 57 | .reply(500) 58 | 59 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message) 60 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 61 | 62 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 63 | username: 'GitHub Actions', 64 | icon_url: 'https://octodex.github.com/images/original.png', 65 | channel: '@override', 66 | timeout: 0, 67 | attachments: [ 68 | { 69 | fallback: '[GitHub]: [act10ns/slack] manual-test workflow_dispatch Success', 70 | color: 'good', 71 | author_name: 'satterly', 72 | author_link: 'https://github.com/satterly', 73 | author_icon: '', 74 | mrkdwn_in: ['pretext', 'text', 'fields'], 75 | pretext: '', 76 | text: '** for \n', 77 | title: '', 78 | fields: [], 79 | footer: ' #6', 80 | footer_icon: 'https://github.githubassets.com/favicon.ico', 81 | ts: expect.stringMatching(/[0-9]+/) 82 | } 83 | ] 84 | }) 85 | 86 | mockAxios.resetHistory() 87 | mockAxios.reset() 88 | }) 89 | -------------------------------------------------------------------------------- /__tests__/workflow_run.test.ts: -------------------------------------------------------------------------------- 1 | import * as github from '@actions/github' 2 | import axios from 'axios' 3 | import MockAdapter from 'axios-mock-adapter' 4 | import {send, ConfigOptions} from '../src/slack' 5 | import {readFileSync} from 'fs' 6 | import * as yaml from 'js-yaml' 7 | 8 | // mock github context 9 | const dump = JSON.parse(readFileSync('./__tests__/fixtures/workflow_run.json', 'utf-8')) 10 | 11 | github.context.payload = dump 12 | github.context.eventName = dump.event_name 13 | github.context.sha = dump.sha 14 | github.context.ref = dump.ref 15 | github.context.workflow = dump.workflow 16 | github.context.action = dump.action 17 | github.context.actor = dump.actor 18 | 19 | process.env.CI = 'true' 20 | process.env.GITHUB_WORKFLOW = 'workflow-run' 21 | process.env.GITHUB_JOB = 'on-success' 22 | process.env.GITHUB_RUN_ID = '1452345894' 23 | process.env.GITHUB_RUN_NUMBER = '4' 24 | process.env.GITHUB_ACTION = '__act10ns_slack' 25 | process.env.GITHUB_ACTIONS = 'true' 26 | process.env.GITHUB_ACTOR = 'satterly' 27 | process.env.GITHUB_REPOSITORY = 'act10ns/slack' 28 | process.env.GITHUB_EVENT_NAME = 'workflow_run' 29 | process.env.GITHUB_EVENT_PATH = '/home/runner/work/_temp/_github_workflow/event.json' 30 | process.env.GITHUB_WORKSPACE = '/home/runner/work/slack/slack' 31 | process.env.GITHUB_SHA = '0d05b90e3bf469738248c462d36be1a78520a02e' 32 | process.env.GITHUB_REF = 'refs/heads/master' 33 | process.env.GITHUB_HEAD_REF = '' 34 | process.env.GITHUB_BASE_REF = '' 35 | process.env.GITHUB_SERVER_URL = 'https://github.com' 36 | process.env.GITHUB_API_URL = 'https://github.com' 37 | process.env.GITHUB_GRAPHQL_URL = 'https://api.github.com/graphql' 38 | 39 | process.env.INPUT_CHANNEL = '#actions' 40 | process.env.INPUT_CONFIG = '__tests__/fixtures/slack-workflow.yml' 41 | process.env.INPUT_STATUS = 'success' 42 | process.env.INPUT_STEPS = '' 43 | 44 | process.env.SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX' 45 | 46 | const url = process.env.SLACK_WEBHOOK_URL as string 47 | const jobName = process.env.GITHUB_JOB as string 48 | const jobStatus = (process.env.INPUT_STATUS as string).toUpperCase() 49 | const jobSteps = process.env.INPUT_STEPS || {} 50 | const jobMatrix = {} 51 | const jobInputs = {} 52 | const channel = process.env.INPUT_CHANNEL as string 53 | const message = process.env.INPUT_MESSAGE as string 54 | 55 | test('workflow_run event to slack', async () => { 56 | const mockAxios = new MockAdapter(axios, {delayResponse: 200}) 57 | 58 | mockAxios 59 | .onPost() 60 | .reply(config => { 61 | console.log(config.data) 62 | return [200, {status: 'ok'}] 63 | }) 64 | .onAny() 65 | .reply(500) 66 | 67 | let config = yaml.load(readFileSync('./__tests__/fixtures/slack-workflow.yml', 'utf-8'), { 68 | schema: yaml.FAILSAFE_SCHEMA 69 | }) as ConfigOptions 70 | 71 | const res = await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message, config) 72 | await expect(res).toStrictEqual({text: {status: 'ok'}}) 73 | 74 | expect(JSON.parse(mockAxios.history.post[0].data)).toStrictEqual({ 75 | username: 'GitHub-CI', 76 | icon_url: 'https://octodex.github.com/images/femalecodertocat.png', 77 | channel: '#actions', 78 | timeout: 0, 79 | attachments: [ 80 | { 81 | mrkdwn_in: ['pretext', 'text', 'fields'], 82 | color: '#5DADE2', 83 | pretext: 'Triggered via workflow_run by satterly __act10ns_slack master `0d05b90e`', 84 | author_name: 'satterly', 85 | author_link: 'https://github.com/satterly', 86 | author_icon: 'https://avatars.githubusercontent.com/u/615057?v=4', 87 | title: 'GitHub Actions', 88 | title_link: 'https://support.github.com', 89 | text: '** for \n', 90 | fields: [], 91 | fallback: '[GitHub] workflow-run #4 is SUCCESS', 92 | footer: ' workflow-run #4', 93 | footer_icon: 'https://github.githubassets.com/favicon.ico', 94 | ts: expect.stringMatching(/[0-9]+/) 95 | }, 96 | { 97 | color: '#5DADE2', 98 | fallback: '[GitHub] workflow-run #4 is SUCCESS', 99 | blocks: [ 100 | { 101 | type: 'context', 102 | elements: [ 103 | {type: 'image', image_url: 'https://avatars.githubusercontent.com/u/615057?v=4', alt_text: 'satterly'}, 104 | {type: 'mrkdwn', text: '**'} 105 | ] 106 | }, 107 | { 108 | type: 'section', 109 | text: {type: 'mrkdwn', text: 'Workflow build-test completed with success after 1 attempt'}, 110 | accessory: { 111 | type: 'button', 112 | text: {type: 'plain_text', text: 'View'}, 113 | value: 'workflow_run_1237076', 114 | url: 'https://github.com/act10ns/slack/actions/runs/1452342609', 115 | action_id: 'button-action' 116 | } 117 | }, 118 | { 119 | type: 'section', 120 | fields: [ 121 | {type: 'mrkdwn', text: '*Jobs*\nhttps://api.github.com/repos/act10ns/slack/actions/runs/1452342609/jobs'}, 122 | {type: 'mrkdwn', text: '*Logs*\nhttps://api.github.com/repos/act10ns/slack/actions/runs/1452342609/logs'} 123 | ] 124 | }, 125 | { 126 | type: 'context', 127 | elements: [ 128 | {type: 'image', image_url: 'https://github.githubassets.com/favicon.ico', alt_text: 'github'}, 129 | { 130 | type: 'mrkdwn', 131 | text: expect.stringMatching( 132 | / build-test #8 | / 133 | ) 134 | } 135 | ] 136 | } 137 | ] 138 | } 139 | ] 140 | }) 141 | 142 | mockAxios.resetHistory() 143 | mockAxios.reset() 144 | }) 145 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: slack - GitHub Actions Slack integration 2 | description: Notify Slack of GitHub Actions workflows, jobs and step status. 3 | author: satterly 4 | inputs: 5 | webhook-url: 6 | description: Specify Slack Incoming Webhook URL 7 | required: false 8 | config: 9 | description: Configuration file 10 | required: false 11 | default: .github/slack.yml 12 | status: 13 | description: Specify success, failure, cancelled or a custom status 14 | required: true 15 | steps: 16 | description: Report on the status of individual steps 17 | required: false 18 | matrix: 19 | description: matrix properties 20 | required: false 21 | inputs: 22 | description: Report input values passed by workflow_call or workflow_dispatch events 23 | required: false 24 | channel: 25 | description: Override default channel with different channel or username 26 | required: false 27 | message: 28 | description: Override message format for step 29 | required: false 30 | runs: 31 | using: 'node20' 32 | main: 'dist/index.js' 33 | branding: 34 | icon: alert-circle 35 | color: red 36 | -------------------------------------------------------------------------------- /docs/images/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/act10ns/slack/d2d8b231aa25a8f36ede1a2dccfedf505593ff4c/docs/images/example1.png -------------------------------------------------------------------------------- /docs/images/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/act10ns/slack/d2d8b231aa25a8f36ede1a2dccfedf505593ff4c/docs/images/example2.png -------------------------------------------------------------------------------- /docs/images/example3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/act10ns/slack/d2d8b231aa25a8f36ede1a2dccfedf505593ff4c/docs/images/example3.png -------------------------------------------------------------------------------- /docs/images/example4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/act10ns/slack/d2d8b231aa25a8f36ede1a2dccfedf505593ff4c/docs/images/example4.png -------------------------------------------------------------------------------- /docs/images/example5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/act10ns/slack/d2d8b231aa25a8f36ede1a2dccfedf505593ff4c/docs/images/example5.png -------------------------------------------------------------------------------- /docs/images/example6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/act10ns/slack/d2d8b231aa25a8f36ede1a2dccfedf505593ff4c/docs/images/example6.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | clearMocks: true, 3 | moduleFileExtensions: ['js', 'ts'], 4 | testEnvironment: 'node', 5 | testMatch: [ 6 | '**/*.test.ts', 7 | ], 8 | testRunner: 'jest-circus/runner', 9 | transform: { 10 | '^.+\\.ts$': 'ts-jest' 11 | }, 12 | verbose: true 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "slack", 3 | "version": "2.1.0", 4 | "private": true, 5 | "description": "Notify Slack of GitHub Actions job and step status.", 6 | "main": "lib/main.js", 7 | "scripts": { 8 | "build": "tsc", 9 | "format": "prettier --write '**/*.ts'", 10 | "format-check": "prettier --check --loglevel debug '**/*.ts'", 11 | "lint": "eslint src/**/*.ts", 12 | "lint:fix": "eslint --fix src/**/*.ts", 13 | "package": "NODE_OPTIONS=--openssl-legacy-provider ncc build --source-map --license licenses.txt", 14 | "package:test": "ncc run dist/index.js", 15 | "test": "jest", 16 | "all": "npm run build && npm run format && npm run lint && npm run package && npm test" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/act10ns/slack.git" 21 | }, 22 | "keywords": [ 23 | "actions", 24 | "slack", 25 | "notify" 26 | ], 27 | "author": "satterly", 28 | "license": "MIT", 29 | "dependencies": { 30 | "@actions/core": "^1.9.1", 31 | "@actions/github": "^4.0.0", 32 | "@octokit/webhooks-definitions": "^3.0.0", 33 | "@slack/webhook": "^7.0.2", 34 | "@types/js-yaml": "^4.0.4", 35 | "flow-bin": "^0.138.0", 36 | "graphql": "^15.4.0", 37 | "handlebars": "^4.7.7", 38 | "js-yaml": "^4.1.0" 39 | }, 40 | "devDependencies": { 41 | "@types/istanbul-lib-report": "^3.0.0", 42 | "@types/jest": "^29.5.12", 43 | "@types/node": "^16.10.5", 44 | "@typescript-eslint/eslint-plugin": "^5.3.0", 45 | "@typescript-eslint/parser": "^5.0.0", 46 | "@vercel/ncc": "^0.31.1", 47 | "axios-mock-adapter": "^1.22.0", 48 | "eslint": "^7.32.0", 49 | "eslint-plugin-github": "^4.3.2", 50 | "eslint-plugin-jest": "^25.0.5", 51 | "jest": "^29.7.0", 52 | "jest-circus": "^29.7.0", 53 | "js-yaml": "^4.1.0", 54 | "prettier": "^2.4.1", 55 | "ts-jest": "^29.1.2", 56 | "typescript": "^4.4.4" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/handlebars.ts: -------------------------------------------------------------------------------- 1 | import Handlebars from 'handlebars' 2 | 3 | // utilities 4 | Handlebars.registerHelper('json', value => new Handlebars.SafeString(JSON.stringify(value))) 5 | 6 | Handlebars.registerHelper('truncate', (text, size) => text.substring(0, size)) 7 | 8 | Handlebars.registerHelper('default', (want, fallback) => (want || want === 0 || want === false ? want : fallback)) 9 | 10 | Handlebars.registerHelper('pluralize', (items, ...args) => { 11 | items = items ?? [] 12 | const count = typeof items === 'number' ? items : items.length 13 | const singular = args.length === 1 ? 'item' : args[0] 14 | const plural = args.length === 3 ? args[1] : `${singular}s` 15 | 16 | if (count === 0) return `no ${plural}` 17 | if (count === 1) return `1 ${singular}` 18 | return `${count} ${plural}` 19 | }) 20 | 21 | // equality 22 | Handlebars.registerHelper('eq', (a, b) => a === b) 23 | 24 | Handlebars.registerHelper('neq', (a, b) => a !== b) 25 | 26 | // logical operators 27 | Handlebars.registerHelper('not', a => !a) 28 | 29 | Handlebars.registerHelper('and', (a, b) => a && b) 30 | 31 | Handlebars.registerHelper('or', (a, b) => a || b) 32 | 33 | // conditionals 34 | Handlebars.registerHelper('ifeq', function (this: Handlebars.HelperDelegate, a, b, options) { 35 | return a === b ? options.fn(this) : options.inverse(this) // eslint-disable-line no-invalid-this 36 | }) 37 | 38 | Handlebars.registerHelper('ifneq', function (this: Handlebars.HelperDelegate, a, b, options) { 39 | return a !== b ? options.fn(this) : options.inverse(this) // eslint-disable-line no-invalid-this 40 | }) 41 | 42 | export default Handlebars 43 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as yaml from 'js-yaml' 3 | import {ConfigOptions, send} from './slack' 4 | import {existsSync, readFileSync} from 'fs' 5 | 6 | async function run(): Promise { 7 | try { 8 | // debug output of environment variables and event payload 9 | for (const k of Object.keys(process.env).sort((a, b) => a.localeCompare(b))) { 10 | core.debug(`${k} = ${process.env[k]}`) 11 | } 12 | const event = process.env.GITHUB_EVENT_PATH as string 13 | const readEvent = (): object => JSON.parse(readFileSync(event, 'utf8')) 14 | core.debug(JSON.stringify(readEvent())) 15 | 16 | const configFile = core.getInput('config', {required: false}) 17 | let config: ConfigOptions = {} 18 | try { 19 | core.info(`Reading config file ${configFile}...`) 20 | if (existsSync(configFile)) { 21 | config = yaml.load(readFileSync(configFile, 'utf-8'), {schema: yaml.FAILSAFE_SCHEMA}) as ConfigOptions 22 | } 23 | } catch (error) { 24 | if (error instanceof Error) core.info(error.message) 25 | } 26 | core.debug(yaml.dump(config)) 27 | 28 | const url = core.getInput('webhook-url', {required: false}) || (process.env.SLACK_WEBHOOK_URL as string) 29 | const jobName = process.env.GITHUB_JOB as string 30 | const jobStatus = core.getInput('status', {required: true}).toUpperCase() 31 | const jobSteps = JSON.parse(core.getInput('steps', {required: false}) || '{}') 32 | const jobMatrix = JSON.parse(core.getInput('matrix', {required: false}) || '{}') 33 | const jobInputs = JSON.parse(core.getInput('inputs', {required: false}) || '{}') 34 | const channel = core.getInput('channel', {required: false}) 35 | const message = core.getInput('message', {required: false}) 36 | core.debug(`jobName: ${jobName}, jobStatus: ${jobStatus}`) 37 | core.debug(`channel: ${channel}, message: ${message}`) 38 | core.debug(`jobMatrix: ${JSON.stringify(jobMatrix)}`) 39 | core.debug(`jobInputs: ${JSON.stringify(jobInputs)}`) 40 | 41 | if (url) { 42 | await send(url, jobName, jobStatus, jobSteps, jobMatrix, jobInputs, channel, message, config) 43 | core.info(`Sent ${jobName} status of ${jobStatus} to Slack!`) 44 | } else { 45 | core.warning('No "SLACK_WEBHOOK_URL"s env or "webhook-url" input configured. Skip.') 46 | } 47 | } catch (error) { 48 | if (error instanceof Error) core.setFailed(error.message) 49 | } 50 | } 51 | 52 | run() 53 | -------------------------------------------------------------------------------- /src/slack.ts: -------------------------------------------------------------------------------- 1 | import * as core from '@actions/core' 2 | import * as github from '@actions/github' 3 | import {Block, KnownBlock, MessageAttachment} from '@slack/types' 4 | import {IncomingWebhook, IncomingWebhookResult} from '@slack/webhook' 5 | import {IssueCommentEvent, IssuesEvent, PullRequestEvent, PushEvent} from '@octokit/webhooks-definitions/schema' // eslint-disable-line import/no-unresolved 6 | import Handlebars from './handlebars' 7 | 8 | const DEFAULT_USERNAME = 'GitHub Actions' 9 | const DEFAULT_ICON_URL = 'https://octodex.github.com/images/original.png' 10 | const DEFAULT_FOOTER_ICON = 'https://github.githubassets.com/favicon.ico' 11 | 12 | interface ColorOptions { 13 | success?: string 14 | failure?: string 15 | cancelled?: string 16 | default?: string 17 | } 18 | 19 | function jobColor(status: string, opts?: ColorOptions): string { 20 | if (status.toLowerCase() === 'success') return opts?.success || 'good' 21 | if (status.toLowerCase() === 'failure') return opts?.failure || 'danger' 22 | if (status.toLowerCase() === 'cancelled') return opts?.cancelled || 'warning' 23 | return opts?.default || '#C0C0C0' // silver 24 | } 25 | 26 | interface IconOptions { 27 | success?: string 28 | failure?: string 29 | cancelled?: string 30 | skipped?: string 31 | default?: string 32 | } 33 | 34 | function stepIcon(status: string, opts?: IconOptions): string { 35 | if (status.toLowerCase() === 'success') return opts?.success || ':heavy_check_mark:' 36 | if (status.toLowerCase() === 'failure') return opts?.failure || ':x:' 37 | if (status.toLowerCase() === 'cancelled') return opts?.cancelled || ':exclamation:' 38 | if (status.toLowerCase() === 'skipped') return opts?.skipped || ':no_entry_sign:' 39 | return `:grey_question: ${status}` 40 | } 41 | 42 | interface Field { 43 | title: string 44 | value: string 45 | short: boolean 46 | if?: string 47 | } 48 | 49 | interface Actions { 50 | type: string 51 | elements: object[] 52 | block_id?: string 53 | if?: string 54 | } 55 | 56 | interface Context { 57 | type: string 58 | elements: object[] 59 | block_id?: string 60 | if?: string 61 | } 62 | 63 | interface Divider { 64 | type: string 65 | block_id?: string 66 | if?: string 67 | } 68 | 69 | interface File { 70 | type: string 71 | external_id: string 72 | source: string 73 | block_id?: string 74 | if?: string 75 | } 76 | 77 | interface Header { 78 | type: string 79 | text: object 80 | block_id?: string 81 | if?: string 82 | } 83 | 84 | interface Image { 85 | type: string 86 | image_url: string 87 | alt_text: string 88 | title?: object 89 | block_id?: string 90 | if?: string 91 | } 92 | 93 | interface Input { 94 | type: string 95 | label: object 96 | element: object 97 | dispatch_action?: boolean 98 | block_id?: string 99 | hint?: object 100 | optional?: boolean 101 | if?: string 102 | } 103 | 104 | interface Section { 105 | type: string 106 | text?: object 107 | block_id?: string 108 | fields?: object[] 109 | accessory?: object 110 | if?: string 111 | } 112 | 113 | export interface ConfigOptions { 114 | username?: string 115 | icon_url?: string 116 | pretext?: string 117 | title?: string 118 | title_link?: string 119 | text?: string 120 | fallback?: string 121 | fields?: Field[] 122 | blocks?: (Actions | Context | Divider | File | Header | Image | Input | Section)[] 123 | footer?: string 124 | colors?: object 125 | icons?: object 126 | unfurl_links?: boolean 127 | unfurl_media?: boolean 128 | } 129 | 130 | export async function send( 131 | url: string, 132 | jobName: string, 133 | jobStatus: string, 134 | jobSteps: object, 135 | jobMatrix: object, 136 | jobInputs: object, 137 | channel?: string, 138 | message?: string, 139 | opts?: ConfigOptions 140 | ): Promise { 141 | const eventName = process.env.GITHUB_EVENT_NAME 142 | const workflow = process.env.GITHUB_WORKFLOW 143 | const repositoryName = process.env.GITHUB_REPOSITORY 144 | const repositoryUrl = `${process.env.GITHUB_SERVER_URL}/${process.env.GITHUB_REPOSITORY}` 145 | 146 | const runId = process.env.GITHUB_RUN_ID 147 | const runNumber = process.env.GITHUB_RUN_NUMBER 148 | const workflowUrl = `${repositoryUrl}/actions?query=workflow:%22${workflow}%22` 149 | const workflowRunUrl = `${repositoryUrl}/actions/runs/${runId}` 150 | 151 | const sha = process.env.GITHUB_SHA as string 152 | const shortSha = sha.slice(0, 8) 153 | const branch = process.env.GITHUB_HEAD_REF || (process.env.GITHUB_REF?.replace('refs/heads/', '') as string) 154 | const refType = process.env.GITHUB_REF_TYPE 155 | const actor = process.env.GITHUB_ACTOR 156 | 157 | let payload 158 | let action 159 | let ref = branch 160 | let refUrl = `${repositoryUrl}/commits/${branch}` 161 | let diffRef = shortSha 162 | let diffUrl = `${repositoryUrl}/commit/${shortSha}` 163 | let description 164 | let sender 165 | const ts = Math.round(new Date().getTime() / 1000) 166 | 167 | switch (eventName) { 168 | case 'issues': 169 | payload = github.context.payload as IssuesEvent 170 | // falls through 171 | case 'issue_comment': { 172 | payload = github.context.payload as IssueCommentEvent 173 | action = payload.action 174 | ref = `#${payload.issue.number}` 175 | refUrl = payload.issue.html_url 176 | diffUrl = payload.issue.comments_url 177 | description = payload.issue.title 178 | sender = payload.sender 179 | // ts = new Date(payload.issue.updated_at).getTime() / 1000 180 | break 181 | } 182 | case 'pull_request': { 183 | payload = github.context.payload as PullRequestEvent 184 | action = payload.action 185 | ref = `#${payload.number}` 186 | refUrl = payload.pull_request.html_url 187 | diffUrl = `${payload.pull_request.html_url}/files` 188 | diffRef = payload.pull_request.head.ref 189 | description = payload.pull_request.title 190 | sender = payload.sender 191 | // ts = new Date(payload.pull_request.updated_at).getTime() / 1000 192 | break 193 | } 194 | case 'push': { 195 | payload = github.context.payload as PushEvent 196 | action = null 197 | ref = payload.ref.replace('refs/heads/', '') 198 | diffUrl = payload.compare 199 | description = `${payload.commits.length} commits` 200 | sender = payload.sender 201 | // ts = new Date(payload.commits[0].timestamp).getTime() / 1000 202 | break 203 | } 204 | case 'schedule': 205 | action = null 206 | ref = (process.env.GITHUB_REF as string).replace('refs/heads/', '') 207 | description = `Schedule \`${github.context.payload.schedule}\`` 208 | sender = { 209 | login: 'github', 210 | html_url: 'https://github.com/github', 211 | avatar_url: 'https://avatars1.githubusercontent.com/u/9919?s=200&v=4' 212 | } 213 | break 214 | default: { 215 | core.info('Unsupported webhook event type. Using environment variables.') 216 | payload = github.context.payload 217 | action = process.env.GITHUB_ACTION?.startsWith('self') ? '' : process.env.GITHUB_ACTION 218 | ref = (process.env.GITHUB_REF as string).replace('refs/heads/', '') 219 | sender = payload?.sender 220 | ? payload.sender 221 | : { 222 | login: actor, 223 | html_url: `https://github.com/${actor}`, 224 | avatar_url: '' 225 | } 226 | } 227 | } 228 | 229 | Handlebars.registerHelper('icon', status => stepIcon(status, opts?.icons)) 230 | 231 | const pretextTemplate = Handlebars.compile(opts?.pretext || '') 232 | const titleTemplate = Handlebars.compile(opts?.title || '') 233 | 234 | const defaultText = `${ 235 | '*<{{{workflowUrl}}}|Workflow _{{workflow}}_ ' + 236 | 'job _{{jobName}}_ triggered by _{{eventName}}_ is _{{jobStatus}}_>* ' + 237 | 'for <{{refUrl}}|`{{ref}}`>\n' 238 | }${description ? '<{{diffUrl}}|`{{diffRef}}`> - {{{description}}}' : ''}` 239 | const textTemplate = Handlebars.compile(message || opts?.text || defaultText) 240 | 241 | const defaultFallback = `[GitHub]: [{{repositoryName}}] {{workflow}} {{eventName}} ${ 242 | action ? '{{action}} ' : '' 243 | }{{jobStatus}}` 244 | const fallbackTemplate = Handlebars.compile(opts?.fallback || defaultFallback) 245 | 246 | const defaultFields = [] 247 | if (Object.entries(jobSteps).length) { 248 | defaultFields.push({ 249 | title: 'Job Steps', 250 | value: '{{#each jobSteps}}{{icon this.outcome}} {{@key}}\n{{~/each}}', 251 | short: false, 252 | if: 'always()' 253 | }) 254 | } 255 | if (Object.entries(jobMatrix).length) { 256 | defaultFields.push({ 257 | title: 'Job Matrix', 258 | value: '{{#each jobMatrix}}{{@key}}: {{this}}\n{{~/each}}', 259 | short: false, 260 | if: 'always()' 261 | }) 262 | } 263 | if (Object.entries(jobInputs).length) { 264 | defaultFields.push({ 265 | title: 'Job Inputs', 266 | value: '{{#each jobInputs}}{{@key}}: {{this}}\n{{~/each}}', 267 | short: false, 268 | if: 'always()' 269 | }) 270 | } 271 | 272 | const filteredFields: object[] = [] 273 | for (const field of opts?.fields || defaultFields) { 274 | const field_if = field?.if || 'always()' 275 | if (field_if === 'always()' || field_if.startsWith(jobStatus.toLowerCase())) { 276 | filteredFields.push({ 277 | title: field.title, 278 | value: field.value, 279 | short: JSON.parse(field.short.toString()) 280 | }) 281 | } 282 | } 283 | const fieldsTemplate = Handlebars.compile(JSON.stringify(filteredFields)) 284 | 285 | const defaultFooter = '<{{repositoryUrl}}|{{repositoryName}}> #{{runNumber}}' 286 | const footerTemplate = Handlebars.compile(opts?.footer || defaultFooter) 287 | 288 | const data = { 289 | env: process.env, 290 | payload: payload || {}, 291 | jobName, 292 | jobStatus, 293 | jobSteps, 294 | jobMatrix, 295 | jobInputs, 296 | eventName, 297 | workflow, 298 | workflowUrl, 299 | workflowRunUrl, 300 | repositoryName, 301 | repositoryUrl, 302 | runId, 303 | runNumber, 304 | sha, 305 | shortSha, 306 | branch, 307 | actor, 308 | action, 309 | ref, 310 | refType, 311 | refUrl, 312 | diffRef, 313 | diffUrl, 314 | description, 315 | sender, 316 | ts 317 | } 318 | 319 | const pretext = pretextTemplate(data) 320 | const title = titleTemplate(data) 321 | const text = textTemplate(data) 322 | const fallback = fallbackTemplate(data) 323 | const fieldsJson = fieldsTemplate(data) 324 | core.debug(fieldsJson.toString()) 325 | const fields = JSON.parse(fieldsTemplate(data)) 326 | const footer = footerTemplate(data) 327 | 328 | const filteredBlocks: object[] = [] 329 | for (const block of opts?.blocks || []) { 330 | const block_if = block?.if || 'always()' 331 | if (block_if === 'always()' || block_if.startsWith(jobStatus.toLowerCase())) { 332 | /* eslint-disable @typescript-eslint/no-unused-vars */ 333 | const {if: string, ...blockWithoutIf} = block 334 | filteredBlocks.push(blockWithoutIf as KnownBlock | Block) 335 | } 336 | } 337 | const blocksTemplate = Handlebars.compile(JSON.stringify(filteredBlocks)) 338 | 339 | // allow blocks to reference templated fields 340 | const blockContext = { 341 | pretext, 342 | title, 343 | title_link: opts?.title_link, 344 | text, 345 | fallback, 346 | footer, 347 | footer_icon: DEFAULT_FOOTER_ICON 348 | } 349 | const blocksJson = blocksTemplate({...data, ...blockContext}) 350 | core.debug(blocksJson.toString()) 351 | const blocks = JSON.parse(blocksTemplate({...data, ...blockContext})) 352 | 353 | const attachments: MessageAttachment[] = [ 354 | { 355 | mrkdwn_in: ['pretext' as const, 'text' as const, 'fields' as const], 356 | color: jobColor(jobStatus, opts?.colors), 357 | pretext, 358 | author_name: sender?.login, 359 | author_link: sender?.html_url, 360 | author_icon: sender?.avatar_url, 361 | title, 362 | title_link: opts?.title_link, 363 | text, 364 | fields, 365 | fallback, 366 | footer, 367 | footer_icon: DEFAULT_FOOTER_ICON, 368 | ts: ts.toString() 369 | } 370 | ] 371 | 372 | if (opts?.blocks) { 373 | attachments.push({ 374 | color: jobColor(jobStatus, opts?.colors), 375 | fallback, 376 | blocks 377 | }) 378 | } 379 | 380 | const postMessage = { 381 | username: opts?.username || DEFAULT_USERNAME, 382 | icon_url: opts?.icon_url || DEFAULT_ICON_URL, 383 | channel, 384 | attachments 385 | } 386 | core.debug(JSON.stringify(postMessage)) 387 | 388 | const webhook = new IncomingWebhook(url) 389 | return await webhook.send(postMessage) 390 | } 391 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------