Automatic compilation

This commit is contained in:
github-actions[bot]
2024-11-14 20:30:45 +00:00
committed by GitHub
parent 74ae656892
commit fcfb566f8b
38 changed files with 50980 additions and 8788 deletions
-2
View File
@@ -1,2 +0,0 @@
node_modules
dist
-132
View File
@@ -1,132 +0,0 @@
//
// SlackAPI JavaScript style
// ---
// This style helps maintainers enforce safe and consistent programming practices in this project. It is not meant to be
// comprehensive on its own or vastly different from existing styles. The goal is to inherit and aggregate as many of
// the communities' recommended styles for the technologies used as we can. When, and only when, we have a stated need
// to differentiate, we add more rules (or modify options). Therefore, the fewer rules directly defined in this file,
// the better.
module.exports = {
// This is a root of the project, ESLint should not look through parent directories to find more config
root: true,
ignorePatterns: [
// Ignore all build outputs and artifacts (node_modules, dotfiles, and dot directories are implicitly ignored)
'/dist',
'/coverage',
],
// These environments contain lists of global variables which are allowed to be accessed
env: {
// According to https://node.green, the target node version (v10) supports all important ES2018 features. But es2018
// is not an option since it presumably doesn't introduce any new globals over ES2017.
es2017: true,
node: true,
},
extends: [
// ESLint's recommended built-in rules: https://eslint.org/docs/rules/
'eslint:recommended',
// Node plugin's recommended rules: https://github.com/mysticatea/eslint-plugin-node
'plugin:node/recommended',
// AirBnB style guide (without React) rules: https://github.com/airbnb/javascript.
'airbnb-base',
// JSDoc plugin's recommended rules
'plugin:jsdoc/recommended',
],
rules: {
// JavaScript rules
// ---
// The top level of this configuration contains rules which apply to JavaScript (and will also be inherited for
// TypeScript). This section does not contain rules meant to override options or disable rules in the base
// configurations (ESLint, Node, AirBnb). Those rules are added in the final override.
// Eliminate tabs to standardize on spaces for indentation. If you want to use tabs for something other than
// indentation, you may need to turn this rule off using an inline config comments.
'no-tabs': 'error',
// Bans use of comma as an operator because it can obscure side effects and is often an accident.
'no-sequences': 'error',
// This repo uses console.log, which is fine to do in GitHub actions
'no-console': 'off',
// Disallow the use of process.exit()
'node/no-process-exit': 'error',
// Allow safe references to functions before the declaration. Overrides AirBnB config. Not located in the override
// section below because a distinct override is necessary in TypeScript files.
'no-use-before-define': ['error', 'nofunc'],
},
overrides: [
{
files: ['**/*.js'],
rules: {
// Override rules
// ---
// This level of this configuration contains rules which override options or disable rules in the base
// configurations in JavaScript.
// Increase the max line length to 120. The rest of this setting is copied from the AirBnB config.
'max-len': ['error', 120, 2, {
ignoreUrls: true,
ignoreComments: false,
ignoreRegExpLiterals: true,
ignoreStrings: true,
ignoreTemplateLiterals: true,
}],
// Restrict the use of backticks to declare a normal string. Template literals should only be used when the
// template string contains placeholders. The rest of this setting is copied from the AirBnb config.
quotes: ['error', 'single', { avoidEscape: true, allowTemplateLiterals: false }],
// the server side Slack API uses snake_case for parameters often
// for mocking and override support, we need to allow snake_case
// Allow leading underscores for parameter names, which is used to acknowledge unused variables in TypeScript.
// Also, enforce camelCase naming for variables. Ideally, the leading underscore could be restricted to only
// unused parameter names, but this rule isn't capable of knowing when a variable is unused. The camelcase and
// no-underscore-dangle rules are replaced with the naming-convention rule because this single rule can serve
// both purposes, and it works fine on non-TypeScript code.
camelcase: 'off',
'no-underscore-dangle': 'off',
// Remove the minProperties option for enforcing line breaks between braces. The AirBnB config sets this to 4,
// which is arbitrary and not backed by anything specific in the style guide. If we just remove it, we can
// rely on the max-len rule to determine if the line is too long and then enforce line breaks. Overrides AirBnB
// styles.
'object-curly-newline': ['error', { multiline: true, consistent: true }],
},
},
{
files: ['src/test/*.js'],
rules: {
// Test-specific rules
// ---
// Rules that only apply to JavaScript _test_ source files
// With Mocha as a test framework, it is sometimes helpful to assign
// shared state to Mocha's Context object, for example in setup and
// teardown test methods. Assigning stub/mock objects to the Context
// object via `this` is a common pattern in Mocha. As such, using
// `function` over the the arrow notation binds `this` appropriately and
// should be used in tests. So: we turn off the prefer-arrow-callback
// rule.
// See https://github.com/slackapi/bolt-js/pull/1012#pullrequestreview-711232738
// for a case of arrow-vs-function syntax coming up for the team
'prefer-arrow-callback': 'off',
// Using ununamed functions (e.g., null logger) in tests is fine
'func-names': 'off',
// In tests, don't force constructing a Symbol with a descriptor, as
// it's probably just for tests
'symbol-description': 'off',
},
},
],
};
-59
View File
@@ -1,59 +0,0 @@
# Contributors Guide
Interested in contributing? Awesome! Before you do though, please read our
[Code of Conduct](https://slackhq.github.io/code-of-conduct). We take it very seriously, and expect that you will as
well.
There are many ways you can contribute! :heart:
### Bug Reports and Fixes :bug:
- If you find a bug, please [create a new issue](https://github.com/slackapi/slack-github-action/issues/new). Fill out the "Bug Report" section of the issue template. Even if an Issue is closed, feel free to comment and add details, it will still
be reviewed.
- Issues that have already been identified as a bug (note: able to reproduce) will be labelled `bug`.
- If you'd like to submit a fix for a bug, [send a Pull Request](#creating_a_pull_request) and mention the Issue number.
- Include tests that isolate the bug and verifies that it was fixed.
### New Features :bulb:
- If you'd like to add new functionality to this project, describe the problem you want to solve in a [new Issue](https://github.com/slackapi/slack-github-action/issues/new).
- Issues that have been identified as a feature request will be labelled `enhancement`.
- If you'd like to implement the new feature, please wait for feedback from the project
maintainers before spending too much time writing the code. In some cases, `enhancement`s may
not align well with the project objectives at the time.
### Tests :mag:, Documentation :books:, Miscellaneous :sparkles:
- If you'd like to improve the tests, you want to make the documentation clearer, you have an
alternative implementation of something that may have advantages over the way its currently
done, or you have any other change, we would be happy to hear about it!
- If its a trivial change, go ahead and [send a Pull Request](#creating_a_pull_request) with the changes you have in mind.
- If not, [open an Issue](https://github.com/slackapi/slack-github-actiondcjtfkgitihftbgeungvnhgbvkhjtdbiilknucucrfek/issues/new) to discuss the idea first.
If you're new to our project and looking for some way to make your first contribution, look for
Issues labelled `good first contribution`.
## Requirements
For your contribution to be accepted:
- [x] You must have signed the [Contributor License Agreement (CLA)](https://cla.salesforce.com/sign-cla).
- [x] The test suite must be complete and pass.
- [x] The changes must be approved by code review.
- [x] Commits should be atomic and messages must be descriptive. Related issues should be mentioned by Issue number.
If the contribution doesn't meet the above criteria, you may fail our automated checks or a maintainer will discuss it with you. You can continue to improve a Pull Request by adding commits to the branch from which the PR was created.
[Interested in knowing more about about pull requests at Slack?](https://slack.engineering/on-empathy-pull-requests-979e4257d158#.awxtvmb2z)
## Creating a Pull Request
1. :fork_and_knife: Fork the repository on GitHub.
2. :runner: Clone/fetch your fork to your local development machine. It's a good idea to run the tests just
to make sure everything is in order.
3. :herb: Create a new branch and check it out.
4. :crystal_ball: Make your changes and commit them locally. Magic happens here!
5. :arrow_heading_up: Push your new branch to your fork. (e.g. `git push username fix-issue-16`).
6. :inbox_tray: Open a Pull Request on github.com from your new branch on your fork to `master` in this
repository.
## Maintainers
There are more details about processes and workflow in the [Maintainer's Guide](./maintainers_guide.md).
-11
View File
@@ -1,11 +0,0 @@
version: 2
updates:
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'monthly'
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'monthly'
-49
View File
@@ -1,49 +0,0 @@
### Description
Describe your issue here.
### What type of issue is this? (place an `x` in one of the `[ ]`)
- [ ] bug
- [ ] enhancement (feature request)
- [ ] question
- [ ] documentation related
- [ ] example code related
- [ ] testing related
- [ ] discussion
### Requirements (place an `x` in each of the `[ ]`)
* [ ] I've read and understood the [Contributing guidelines](https://github.com/slackapi/slack-github-action/blob/main/.github/contributing.md) and have done my best effort to follow them.
* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
* [ ] I've searched for any related issues and avoided creating a duplicate issue.
---
### Bug Report
Filling out the following details about bugs will help us solve your issue sooner.
#### Reproducible in:
package version:
node version:
OS version(s):
#### Steps to reproduce:
1.
2.
3.
#### Expected result:
What you expected to happen
#### Actual result:
What actually happened
#### Attachments:
Logs, screenshots, screencast, sample project, funny gif, etc.
-93
View File
@@ -1,93 +0,0 @@
# Maintainers Guide
This document describes tools, tasks and workflow that one needs to be familiar with in order to effectively maintain
this project. If you use this package within your own software as is but don't plan on modifying it, this guide is
**not** for you.
## Tools
All you need to work with this project is a supported version of [Node.js](https://nodejs.org/en/)
(see `package.json` field "engines") and npm (which is distributed with Node.js).
## Tasks
### Developing
Iterate quickly by developing and testing a local version of this action using `npm run local`.
Information on setting up and configuring mocked events can be found in [`.github/workflows/local/README.md`](./workflows/local/README.md).
### Testing
When testing locally, ensure at least linting and unit tests pass by running `npm test`.
Additionally, sending a PR is highly recommended with every change as there are several GitHub
Actions jobs that execute what are effectively integration tests for this GitHub Action.
#### Checks on PRs
Actions that run the integration tests on PRs from a fork will require approval before running.
These checks use stored secrets so the changes should be reviewed before approving the workflow to
avoid accidently leaking tokens!
### Releasing
* Check the status of this project's GitHub Milestone to be released for issues that should be shipped with the release.
- If all issues have been closed, continue with the release.
- If issues are still open, discuss with the team about whether the open issues should be moved to a future release or if the release should be held off until the issues are resolved.
- Take a look at all issues under the Milestone to make sure that the type of issues included aligns with the Milestone name based on [semantic versioning](https://semver.org/). If the issues do not align with the naming of the Milestone (ex: if the issues are all bug fixes, but the Milestone is labeled as a minor release), then you can tweak the Milestone name to reflect the correct versioning.
* Update the version in `package.json`.
* Update all references to versions in the README and in the workflow files under `example-workflows/` to refer to the latest release.
* Run all tests using `npm test` to make sure the tests pass.
* Commit the changes on your `main` branch.
* Create a git tag for the new version. Should be in the format `v1.4.0`. `git tag v1.4.0`.
* Push changes up to GitHub `git push origin main --tags`.
* Create a GitHub Release based on the tag you just pushed up - this will trigger the publishing
GitHub workflow.
* Once released, make sure to close the relevant GitHub Milestone for the version you released.
## Workflow
### Versioning and Tags
This project is versioned using [Semantic Versioning](http://semver.org/), particularly in the
[npm flavor](https://docs.npmjs.com/getting-started/semantic-versioning). Each release is tagged
using git.
### Fork
As a maintainer, the development you do will be almost entirely off of your forked version of this repository. The exception to this rule pertains to multiple collaborators working on the same feature, which is detailed in the **Branches** section below.
### Branches
`main` is where active development occurs.
When developing, branches should be created off of your fork and not directly off of this repository. If working on a long-running feature and in collaboration with others, a corresponding branch of the same name is permitted. This makes collaboration on a single branch possible, as contributors working on the same feature cannot push commits to others' open Pull Requests.
After a major version increment, there also may be maintenance branches created specifically for supporting older major versions.
### Issue Management
Labels are used to run issues through an organized workflow. Here are the basic definitions:
* `bug`: A confirmed bug report. A bug is considered confirmed when reproduction steps have been
documented and the issue has been reproduced.
* `enhancement`: A feature request for something this package might not already do.
* `docs`: An issue that is purely about documentation work.
* `tests`: An issue that is purely about testing work.
* `needs feedback`: An issue that may have claimed to be a bug but was not reproducible, or was otherwise missing some information.
* `discussion`: An issue that is purely meant to hold a discussion. Typically the maintainers are looking for feedback in this issues.
* `question`: An issue that is like a support request because the user's usage was not correct.
* `semver:major|minor|patch`: Metadata about how resolving this issue would affect the version number.
* `security`: An issue that has special consideration for security reasons.
* `good first contribution`: An issue that has a well-defined relatively-small scope, with clear expectations. It helps when the testing approach is also known.
* `duplicate`: An issue that is functionally the same as another issue. Apply this only if you've linked the other issue by number.
**Triage** is the process of taking new issues that aren't yet "seen" and marking them with a basic
level of information with labels. An issue should have **one** of the following labels applied:
`bug`, `enhancement`, `question`, `needs feedback`, `docs`, `tests`, or `discussion`.
Issues are closed when a resolution has been reached. If for any reason a closed issue seems
relevant once again, reopening is great and better than creating a duplicate issue.
## Everything else
When in doubt, find the other maintainers and ask.
-8
View File
@@ -1,8 +0,0 @@
### Summary
Describe the goal of this PR. Mention any related Issue numbers.
### Requirements (place an `x` in each `[ ]`)
* [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/slack-github-action/blob/main/.github/contributing.md) and have done my best effort to follow them.
* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
@@ -1,27 +0,0 @@
{
"text": "A GitHub Action Event ${{ github.eventName }} has ${{ env.JOB_STATUS }}",
"attachments": [{
"color": "${{ env.ATTACHMENT_COLOR }}",
"fields": [{
"title": "Repository",
"short": false,
"value": "<${{ github.payload.repository.html_url }}|${{ github.payload.repository.full_name }}>"
}, {
"title": "Ref",
"short": false,
"value": "${{ github.ref }}"
}, {
"title": "Commit",
"short": false,
"value": "<${{ github.payload.repository.html_url }}/commit/${{ github.sha }}|${{ github.sha }}>"
}, {
"title": "Author",
"short": false,
"value": "<https://github.com/${{ github.actor }}|${{ github.actor }}>"
}, {
"title": "Workflow",
"short": false,
"value": "<${{ github.payload.repository.html_url }}/actions/runs/${{ github.runId }}|${{ github.workflow }}>"
}]
}]
}
-4
View File
@@ -1,4 +0,0 @@
# Rename this file to .env and update any variables to get started
SLACK_BOT_TOKEN=xoxb-01010101-abcdefgh
SLACK_WEBHOOK_URL=https://hooks.slack.com/services/T0123456789/B0123456789/abcdefghijklmnopqrstuvwxyz
-2
View File
@@ -1,2 +0,0 @@
# Secret variables
.env
-25
View File
@@ -1,25 +0,0 @@
# Local runs
For a simple development experience, a local version of this action can be used in experiments.
**Requirements**:
- An installation of [nektos/act](https://github.com/nektos/act)
## Configuring secrets
To use `${{ secrets.* }}` in the workflow, move `.env.example` to `.env` and update any variables.
## Mocking event payloads
Different event payloads can be mocked directly with changes to the `event.json` file.
Reference: https://docs.github.com/en/webhooks/webhook-events-and-payloads
## Updating the workflow
The `local.yml` file contains the workflow used for testing. Updates to these steps can be made to test various functionalities.
## Running an experiment
Run the workflow using `npm run local`. The above configurations will be used to simulate an actual workflow run.
-9
View File
@@ -1,9 +0,0 @@
{
"pull_request": {
"user": {
"login": "dependabot"
},
"title": "Bump actions/checkout from 3 to 4",
"html_url": "https://github.com/slackapi/slack-github-action/pull/238"
}
}
-28
View File
@@ -1,28 +0,0 @@
name: Local run
# Requires mocking the "public" event to begin this workflow and avoid actual runs
# https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
on:
public:
jobs:
run:
runs-on: ubuntu-latest
steps:
- name: Checkout action
uses: actions/checkout@v4
- name: Build action
run: npm install && npm run build
- name: Send a message into channel
id: slack
uses: ./.
with:
payload: |
{
"repository": "${{ github.repository }}",
"pull_request_username": "${{ github.event.pull_request.user.login }}",
"pull_request_title": ${{ toJSON(github.event.pull_request.title) }},
"pull_request_url": "${{ github.event.pull_request.html_url }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
-126
View File
@@ -1,126 +0,0 @@
name: Tests
on:
pull_request_target:
push:
branches:
- main
jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
environment: staging
steps:
- name: "build: checkout the latest changes"
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- name: "build: install the required dependencies"
run: npm ci
- name: "build: package the latest changes"
run: npm run build
- name: "unit(test): perform unit test checks"
run: npm test
- name: "unit(test): check unit test coverage"
run: npm run test:gen-cov
- name: "unit(test): upload coverage to CodeCov"
uses: codecov/[email protected]
with:
directory: ./coverage
token: ${{ secrets.CODECOV_TOKEN }}
- name: "integration(botToken): post a message to channel"
id: slackToken
uses: ./
with:
channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
slack-message: 'CI Post from slack-send GitHub Action! Succeeded!!'
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- name: "integration(botToken): confirm a message was posted"
run: test -n "${{ steps.slackToken.outputs.ts }}"
- name: "integration(botToken): post a threaded response"
id: slackThreadResponse
uses: ./
with:
channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
payload: |
{
"text": "This message should be posted as a response in thread",
"thread_ts": "${{ steps.slackToken.outputs.thread_ts }}"
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- name: "integration(botToken): confirm a response was posted"
run: test -n "${{ steps.slackThreadResponse.outputs.ts }}"
- name: "integration(wfb): save the push event trigger commit URL"
if: "contains(github.event_name, 'push')"
run: |
url=${{ github.event.head_commit.url }}
echo "URL=$url" >> "$GITHUB_ENV"
- name: "integration(wfb): save the pull request event trigger commit URL"
if: "contains(github.event_name, 'pull_request')"
run: |
url=${{ github.event.pull_request.url }}
echo "URL=$url" >> "$GITHUB_ENV"
- name: "integration(wfb): send a payload via workflow builder webhook"
id: slackWorkflow
uses: ./
with:
# Workflow builder webhooks need to know the name of the keys in the payload in advance. Without normalizing, the github context payload keys can differ based on the GitHub trigger event type
# Normalized payload with info pulled out from GitHub trigger event
payload: "{\"author\":\"${{ github.event.sender.login }}\",\"url\":\"${{ env.URL}}\", \"repoName\":\"${{ github.event.repository.full_name }}\", \"status\":\"${{ job.status }}\"}"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
- name: "integration(wfb): confirm a payload was sent"
run: test -n "${{ steps.slackWorkflow.outputs.time }}"
- name: "integration(incoming): post a message via incoming webhook"
id: slackIncoming
uses: ./
with:
payload: "{\"text\":\"Incoming Webhook test for slack send\", \"blocks\":[{\"type\":\"section\",\"text\":{\"type\":\"plain_text\",\"text\":\"A post by Slack Send GitHub Action. Testing Incoming webhooks\",\"emoji\":true}}]}"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
- name: "integration(incoming): confirm a webhook was posted"
run: test -n "${{ steps.slackIncoming.outputs.time }}"
- name: "integration(incoming): reveal contents of the github payload"
run: echo $JSON
env:
JSON: ${{ toJSON(github) }}
- name: "integration(incoming): post a message via payload file"
id: slackPayloadFile
uses: ./
with:
payload-file-path: ./.github/resources/payload-notification.json
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_INCOMING_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
JOB_STATUS: ${{ job.status }}
ATTACHMENT_COLOR: ${{ (job.status == 'success' && 'good') || (job.status == 'failure' && 'danger') || 'warning' }}
- name: "integration(incoming): confirm a payload file was posted"
run: test -n "${{ steps.slackPayloadFile.outputs.time }}"
- name: "chore(health): check up on recent changes to the health score"
uses: slackapi/[email protected]
with:
codecov_token: ${{ secrets.CODECOV_TOKEN }}
github_token: ${{ secrets.GITHUB_TOKEN }}
extension: js
-21
View File
@@ -1,21 +0,0 @@
name: Publish
on:
release:
types: [published, edited]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name }}
- name: Install deps and build
run: npm ci && npm run build
- uses: JasonEtco/build-and-tag-action@v2
env:
GITHUB_TOKEN: ${{ github.token }}
with:
tag_name: ${{ github.event.release.tag_name }}
-5
View File
@@ -1,5 +0,0 @@
node_modules
.DS_Store
.nyc_output
dist
coverage
-3
View File
@@ -1,3 +0,0 @@
{
"timeout": 3000
}
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2019 Slack Technologies, LLC
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-328
View File
@@ -1,328 +0,0 @@
# Slack Send GitHub Action
[![codecov](https://codecov.io/gh/slackapi/slack-github-action/graph/badge.svg?token=OZNX7FHN78)](https://codecov.io/gh/slackapi/slack-github-action)
Send data into Slack using this GitHub Action!
## Example Workflows
For examples on how to leverage this in your workflows, check out the [example workflows we have](https://github.com/slackapi/slack-github-action/tree/main/example-workflows).
## Sending Variables
You can provide data to send with this GitHub Action from various sources:
* The default event [context][event-context] with a [payload][event-payload] matching the GitHub event.
* A custom JSON payload with optional [variables][variables] provided in the GitHub Action step.
The custom payload can be provided inline in your GitHub workflow or from a file, [detailed with technique 1](#technique-1-slack-workflow-builder), and will be used instead of the default event context if included.
## How to Send Data to Slack
This package has three different techniques to send data to Slack:
1) Send data to Slack's Workflow Builder (requires a paid Slack instance).
2) Send data via a Slack app to post to a specific channel (use an existing custom app or create a new one).
3) Send data via a Slack Incoming Webhook URL (use an existing custom app or create a new one).
The recommended way to use this action is with Slack's Workflow Builder (if you're on a paid Slack plan).
### Technique 1: Slack Workflow Builder
> ❗️ This approach requires a paid Slack plan; it also doesn't support any text formatting
This technique sends data into Slack via a webhook URL created using [Slack's Workflow builder](https://slack.com/features/workflow-automation). Follow [these steps to create a Slack workflow using webhooks][create-webhook]. The Slack workflow webhook URL will be in the form `https://hooks.slack.com/workflows/....`.
As part of the [workflow setup](https://slack.com/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks#workflow-setup),
you will need to define expected variables in the payload the webhook will receive (described in the "Create custom variables" section of the docs). If these variables are missing in the payload, an error is returned.
To match the webhook input format expected by Workflow Builder, the payload will be flattened and stringified (all nested keys are moved to the top level) before being sent. The default delimiter used to flatten payloads is a period (".") but should be changed to an underscore ("_") using the `payload-delimiter` parameter if you're using nested payloads as input values in your own workflows.
#### Setup
* [Create a Slack workflow webhook][create-webhook].
* Copy the webhook URL (`https://hooks.slack.com/workflows/....`) and [add it as a secret in your repo settings][repo-secret] named `SLACK_WEBHOOK_URL`.
* Add a step to your GitHub action to send data to your Webhook.
* Configure your Slack workflow to use variables from the incoming payload from the GitHub Action. You can select where you want to post the data and how you want to format it in Slack's workflow builder interface.
#### Usage
Add this Action as a [step][job-step] to your project's GitHub Action Workflow file:
```yaml
- name: Send GitHub Action trigger data to Slack workflow
id: slack
uses: slackapi/[email protected]
with:
payload-delimiter: "_"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```
or
```yaml
- name: Send custom JSON data to Slack workflow
id: slack
uses: slackapi/[email protected]
with:
# This data can be any valid JSON from a previous step in the GitHub Action
payload: |
{
"key": "value",
"foo": "bar"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```
or
> If the `payload` is provided it will take preference over `payload-file-path`
```yaml
- name: Send custom JSON data to Slack workflow
id: slack
uses: slackapi/[email protected]
with:
payload-file-path: "./payload-slack-content.json"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```
> To send the payload file JSON as is, without replacing templated values with
> `github.context` or `github.env`, set `payload-file-path-parsed` to `false`.
> Default: `true`.
```yaml
- name: Send custom JSON data to Slack workflow
id: slack
uses: slackapi/[email protected]
with:
payload-file-path: "./payload-slack-content.json"
payload-file-path-parsed: false
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
```
### Technique 2: Slack App
By creating a new Slack app or using an existing one, this approach allows your GitHub Actions job to post a message in a Slack channel or direct message by utilizing the [chat.postMessage](https://api.slack.com/methods/chat.postMessage) API method. Using this approach you can instantly post a message without setting up Slack workflows.
#### Setup
* [Create a Slack App][apps] for your workspace (alternatively use an existing app you have already created and installed).
* Add the [`chat:write`](https://api.slack.com/scopes/chat:write) bot scope under **OAuth & Permissions**.
* Install the app to your workspace.
* Copy the app's Bot Token from the **OAuth & Permissions** page and [add it as a secret in your repo settings][repo-secret] named `SLACK_BOT_TOKEN`.
* Invite the bot user into the channel you wish to post messages to (`/invite @bot_user_name`).
#### Usage
Add this Action as a [step][job-step] to your project's GitHub Action Workflow file:
```yaml
- name: Post to a Slack channel
id: slack
uses: slackapi/[email protected]
with:
# Slack channel id, channel name, or user id to post message.
# See also: https://api.slack.com/methods/chat.postMessage#channels
# You can pass in multiple channels to post to by providing a comma-delimited list of channel IDs.
channel-id: 'CHANNEL_ID,ANOTHER_CHANNEL_ID'
# For posting a simple plain text message
slack-message: "GitHub build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
```
Using JSON payload for constructing a message is also available:
```yaml
- name: Post to a Slack channel
id: slack
uses: slackapi/[email protected]
with:
# Slack channel id, channel name, or user id to post message.
# See also: https://api.slack.com/methods/chat.postMessage#channels
channel-id: 'CHANNEL_ID'
# For posting a rich message using Block Kit
payload: |
{
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
}
}
]
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
```
#### Update the message
If you would like to notify the real-time updates on a build status, you can modify the message your build job posted in the subsequent steps. In order to do this, the steps after the first message posting can have `update-ts: ${{ steps.slack.outputs.ts }}` in their settings. With this, the step updates the already posted channel message instead of posting a new one.
Please note that **the message update step does not accept a channel name.** Set a channel ID for the steps for the actions that update messages.
```yaml
- id: slack
uses: slackapi/[email protected]
with:
# The following message update step does not accept a channel name.
# Setting a channel ID here for consistency is highly recommended.
channel-id: "CHANNEL_ID"
payload: |
{
"text": "Deployment started (In Progress)",
"attachments": [
{
"pretext": "Deployment started",
"color": "dbab09",
"fields": [
{
"title": "Status",
"short": true,
"value": "In Progress"
}
]
}
]
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- uses: slackapi/[email protected]
with:
# Unlike the step posting a new message, this step does not accept a channel name.
# Please use a channel ID, not a name here.
channel-id: "CHANNEL_ID"
update-ts: ${{ steps.slack.outputs.ts }}
payload: |
{
"text": "Deployment finished (Completed)",
"attachments": [
{
"pretext": "Deployment finished",
"color": "28a745",
"fields": [
{
"title": "Status",
"short": true,
"value": "Completed"
}
]
}
]
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
```
#### Reply to a message
If you want to post a message as a threaded reply, you can populate the `payload` with a `thread_ts` field. This field should equal the `ts` value of the parent message of the thread. If you want to reply to a message previously posted by this Action, you can use the `ts` output provided as the `thread_ts` of a consequent threaded reply, e.g. `"thread_ts": "${{ steps.deployment_message.outputs.ts }}"`.
Please note that **reply to a message does not accept a channel name.** Set a channel ID for the actions that reply to messages in thread.
```yaml
- id: deployment_message
uses: slackapi/[email protected]
with:
channel-id: "CHANNEL_ID"
payload: |
{
"text": "Deployment started (In Progress)"
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
- uses: slackapi/[email protected]
with:
# Unlike the step posting a new message, this step does not accept a channel name.
# Please use a channel ID, not a name here.
channel-id: "CHANNEL_ID"
payload: |
{
"thread_ts": "${{ steps.deployment_message.outputs.ts }}",
"text": "Deployment finished (Completed)"
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
```
### Technique 3: Slack Incoming Webhook
This approach allows your GitHub Actions job to post a message to a Slack channel or direct message by utilizing [Incoming Webhooks](https://api.slack.com/messaging/webhooks).
Incoming Webhooks conform to the same rules and functionality as any of Slack's other messaging APIs. You can make your posted messages as simple as a single line of text, or make them really useful with [interactive components](https://api.slack.com/messaging/interactivity). To make the message more expressive and useful use [Block Kit](https://api.slack.com/block-kit) to build and test visual components.
#### Setup
* [Create a Slack App][apps] for your workspace (alternatively use an existing app you have already created and installed).
* Add the [`incoming-webhook`](https://api.slack.com/scopes/incoming-webhook) bot scope under **OAuth & Permissions**.
* Install the app to your workspace (you will select a channel to notify).
* Activate and create a new webhook under **Incoming Webhooks**.
* Copy the Webhook URL from the Webhook you just generated [add it as a secret in your repo settings][repo-secret] named `SLACK_WEBHOOK_URL`.
#### Usage
```yaml
- name: Send custom JSON data to Slack workflow
id: slack
uses: slackapi/[email protected]
with:
# For posting a rich message using Block Kit
payload: |
{
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "GitHub Action build result: ${{ job.status }}\n${{ github.event.pull_request.html_url || github.event.head_commit.url }}"
}
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
```
### HTTPS Proxy
If you need to use a proxy to connect with Slack, you can use the `HTTPS_PROXY` (or `https_proxy`) environment variable. In this example we use the Slack App technique, but configuring a proxy works the same way for all of them:
```yaml
- name: Post to a Slack channel via a proxy
id: slack
uses: slackapi/[email protected]
with:
channel-id: 'CHANNEL_ID'
slack-message: 'This message was sent through a proxy'
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
# Set the HTTPS_PROXY environment variable to whatever your policy requires
HTTPS_PROXY: 'http://proxy.example.org:8080'
```
## Contributing
See [CONTRIBUTING](.github/contributing.md).
## License
See [LICENSE](LICENSE).
[apps]: https://api.slack.com/apps
[create-webhook]: https://slack.com/intl/en-ca/help/articles/360041352714-Create-more-advanced-workflows-using-webhooks
[event-context]: https://github.com/actions/toolkit/blob/main/packages/github/src/context.ts#L6
[event-payload]: https://docs.github.com/en/webhooks/webhook-events-and-payloads
[job-step]: https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idsteps
[repo-secret]: https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository
[variables]: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables
+50980
View File
File diff suppressed because one or more lines are too long
@@ -1,18 +0,0 @@
on: [push]
jobs:
new_push_job:
runs-on: ubuntu-latest
name: New push to repo
steps:
- name: Send GitHub trigger payload to Slack Workflow Builder
id: slack
uses: slackapi/[email protected]
with:
payload: |
{
"key": "value",
"foo": "bar"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
@@ -1,14 +0,0 @@
on: [push]
jobs:
new_push_job:
runs-on: ubuntu-latest
name: New push to repo
steps:
- name: Send GitHub trigger payload to Slack Workflow Builder
id: slack
uses: slackapi/[email protected]
with:
payload-file-path: "./example-workflows/Technique_1_Slack_Workflow_Builder/payloads/example.json"
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
@@ -1,12 +0,0 @@
on: [push]
jobs:
new_push_job:
runs-on: ubuntu-latest
name: New push to repo
steps:
- name: Send GitHub trigger payload to Slack Workflow Builder
id: slack
uses: slackapi/[email protected]
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
@@ -1,3 +0,0 @@
{
"text": "${{ github.workflow }} workflow failed on ${{ env.DEFAULT_BRANCH }} branch in the ${{ github.event.repository.name }} repository!"
}
@@ -1,44 +0,0 @@
on: [push]
jobs:
new_push_job:
runs-on: ubuntu-latest
name: New push to repo
steps:
- name: Send GitHub trigger payload to Slack Workflow Builder
id: slack
uses: slackapi/[email protected]
with:
channel-id: 'SLACK_CHANNEL_ID' # ID of Slack Channel you want to post to
payload: |
{
"blocks": [
{ "type": "divider" },
{
"type": "image",
"title": {
"type": "plain_text",
"text": "Slack Slack Slack",
"emoji": true
},
"image_url": "https://media.makeameme.org/created/a-slack-this.jpg",
"alt_text": "marg"
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": {
"type": "plain_text",
"text": "${{ github.sha }}"
},
"url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
]
}
],
"channel-id": "NEW_TARGET_CHANNEL_ID"
}
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
@@ -1,15 +0,0 @@
on: [push]
jobs:
new_push_job:
runs-on: ubuntu-latest
name: New push to repo
steps:
- name: Send GitHub trigger payload to Slack Workflow Builder
id: slack
uses: slackapi/[email protected]
with:
channel-id: 'SLACK_CHANNEL_ID' # ID of Slack Channel you want to post to
payload: "{\"text\": \"posting from a github action\"}"
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
@@ -1,15 +0,0 @@
on: [push]
jobs:
new_push_job:
runs-on: ubuntu-latest
name: New push to repo
steps:
- name: Publish to slack channel via bot token
id: slack
uses: slackapi/[email protected]
with:
channel-id: 'SLACK_CHANNEL_ID' # ID of Slack Channel you want to post to
slack-message: 'posting from a github action!' # The message you want to post
env:
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
@@ -1,51 +0,0 @@
on: [push]
jobs:
new_push_job:
runs-on: ubuntu-latest
name: New push to repo
steps:
- name: Send GitHub trigger payload to Slack Workflow Builder
id: slack
uses: slackapi/[email protected]
with:
payload: |
{
"text": "Danny Torrence left a 1 star review for your property.",
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "Danny Torrence left the following review for your property:"
}
},
{
"type": "section",
"block_id": "section567",
"text": {
"type": "mrkdwn",
"text": "<https://example.com|Overlook Hotel> \\n :star: \\n Doors had too many axe holes, guest in room 237 was far too rowdy, whole place felt stuck in the 1920s."
},
"accessory": {
"type": "image",
"image_url": "https://is5-ssl.mzstatic.com/image/thumb/Purple3/v4/d3/72/5c/d3725c8f-c642-5d69-1904-aa36e4297885/source/256x256bb.jpg",
"alt_text": "Haunted hotel image"
}
},
{
"type": "section",
"block_id": "section789",
"fields": [
{
"type": "mrkdwn",
"text": "*Average Rating*\\n1.0"
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
-7035
View File
File diff suppressed because it is too large Load Diff
-57
View File
@@ -1,57 +0,0 @@
{
"name": "slack-github-action",
"version": "1.27.1",
"description": "The official slack github action. Use this to send data into your Slack workspace",
"main": "dist/index.js",
"scripts": {
"lint": "eslint .",
"local": "act public --eventpath .github/workflows/local/event.json --secret-file .github/workflows/local/.env --platform ubuntu-latest=node:20-buster",
"test:mocha": "mocha --config .mocharc.json test/*-test.js",
"test:gen-cov": "nyc --reporter=lcov npm run test:mocha",
"test": "npm run lint && nyc npm run test:mocha",
"build": "npx @vercel/ncc build src/index.js --license licenses.txt"
},
"repository": {
"type": "git",
"url": "git+https://github.com/slackapi/slack-github-action.git"
},
"keywords": [
"slack",
"github",
"action",
"workflow"
],
"author": "Slack Technologies, LLC",
"license": "MIT",
"bugs": {
"url": "https://github.com/slackapi/slack-github-action/issues"
},
"engines": {
"node": ">=20.0.0",
"npm": ">=10.2.0"
},
"homepage": "https://github.com/slackapi/slack-github-action#readme",
"dependencies": {
"@actions/core": "^1.11.1",
"@actions/github": "^6.0.0",
"@slack/web-api": "^7.7.0",
"axios": "^1.7.7",
"flat": "^5.0.2",
"https-proxy-agent": "^7.0.5",
"markup-js": "^1.5.21",
"whatwg-url": "^14.0.0"
},
"devDependencies": {
"@vercel/ncc": "^0.38.1",
"chai": "^4.5.0",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsdoc": "^50.4.3",
"eslint-plugin-node": "^11.1.0",
"mocha": "^10.8.2",
"nyc": "^17.1.0",
"rewiremock": "^3.14.5",
"sinon": "^19.0.2"
}
}
-4
View File
@@ -1,4 +0,0 @@
const core = require('@actions/core');
const slackSend = require('./slack-send');
slackSend(core);
-165
View File
@@ -1,165 +0,0 @@
const { promises: fs } = require('fs');
const path = require('path');
const github = require('@actions/github');
const flatten = require('flat');
const axios = require('axios');
const markup = require('markup-js');
const { HttpsProxyAgent } = require('https-proxy-agent');
const { parseURL } = require('whatwg-url');
const { createWebClient } = require('./web-client');
const SLACK_WEBHOOK_TYPES = {
WORKFLOW_TRIGGER: 'WORKFLOW_TRIGGER',
INCOMING_WEBHOOK: 'INCOMING_WEBHOOK',
};
module.exports = async function slackSend(core) {
try {
const botToken = process.env.SLACK_BOT_TOKEN;
const webhookUrl = process.env.SLACK_WEBHOOK_URL;
let webhookType = SLACK_WEBHOOK_TYPES.WORKFLOW_TRIGGER;
if (process.env.SLACK_WEBHOOK_TYPE) {
// The default type is for Workflow Builder triggers. If you want to use this action for Incoming Webhooks, use
// the corresponding type instead.
webhookType = process.env.SLACK_WEBHOOK_TYPE.toUpperCase();
}
if ((botToken === undefined || botToken.length <= 0) && (webhookUrl === undefined || webhookUrl.length <= 0)) {
throw new Error('Need to provide at least one botToken or webhookUrl');
}
let payload = core.getInput('payload');
const payloadFilePath = core.getInput('payload-file-path');
/** Option to replace templated context variables in the payload file JSON */
const payloadFilePathParsed = core.getBooleanInput('payload-file-path-parsed');
let webResponse;
if (payloadFilePath && !payload) {
try {
payload = await fs.readFile(path.resolve(payloadFilePath), 'utf-8');
if (payloadFilePathParsed) {
const context = { github: github.context, env: process.env };
const payloadString = payload.replaceAll('${{', '{{');
payload = markup.up(payloadString, context);
}
} catch (error) {
// passed in payload file path was invalid
console.error(error);
throw new Error(`The payload-file-path may be incorrect. Failed to load the file: ${payloadFilePath}`);
}
}
if (payload) {
try {
// confirm it is valid json
payload = JSON.parse(payload);
} catch (e) {
// passed in payload wasn't valid json
console.error('passed in payload was invalid JSON');
throw new Error('Need to provide valid JSON payload');
}
}
if (typeof botToken !== 'undefined' && botToken.length > 0) {
const message = core.getInput('slack-message') || '';
const channelIds = core.getInput('channel-id') || '';
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy || '';
const web = createWebClient(botToken, httpsProxy);
if (channelIds.length <= 0) {
console.log('Channel ID is required to run this action. An empty one has been provided');
throw new Error('Channel ID is required to run this action. An empty one has been provided');
}
if (message.length > 0 || payload) {
const ts = core.getInput('update-ts');
await Promise.all(channelIds.split(',').map(async (channelId) => {
if (ts) {
// update message
webResponse = await web.chat.update({ ts, channel: channelId.trim(), text: message, ...(payload || {}) });
} else {
// post message
webResponse = await web.chat.postMessage({ channel: channelId.trim(), text: message, ...(payload || {}) });
}
}));
} else {
console.log('Missing slack-message or payload! Did not send a message via chat.postMessage with botToken', { channel: channelIds, text: message, ...(payload) });
throw new Error('Missing message content, please input a valid payload or message to send. No Message has been send.');
}
}
if (typeof webhookUrl !== 'undefined' && webhookUrl.length > 0) {
if (!payload) {
// No Payload was passed in
console.log('no custom payload was passed in, using default payload that triggered the GitHub Action');
// Get the JSON webhook payload for the event that triggered the workflow
payload = github.context.payload;
}
if (webhookType === SLACK_WEBHOOK_TYPES.WORKFLOW_TRIGGER) {
// flatten JSON payload (no nested attributes).
const payloadDelimiter = core.getInput('payload-delimiter') || '.';
const flatPayload = flatten(payload, { delimiter: payloadDelimiter });
// workflow builder requires values to be strings
// iterate over every value and convert it to string
Object.keys(flatPayload).forEach((key) => {
flatPayload[key] = `${flatPayload[key]}`;
});
payload = flatPayload;
}
const axiosOpts = {};
try {
if (parseURL(webhookUrl).scheme === 'https') {
const httpsProxy = process.env.HTTPS_PROXY || process.env.https_proxy || '';
if (httpsProxy && parseURL(httpsProxy).scheme === 'http') {
const httpsProxyAgent = new HttpsProxyAgent(httpsProxy);
axiosOpts.httpsAgent = httpsProxyAgent;
// Use configured tunnel above instead of default axios proxy setup from env vars
axiosOpts.proxy = false;
}
}
} catch (err) {
console.log('failed to configure https proxy agent for http proxy. Using default axios configuration', err);
}
try {
await axios.post(webhookUrl, payload, axiosOpts);
} catch (err) {
console.log('axios post failed, double check the payload being sent includes the keys Slack expects');
if ('toJSON' in err) {
console.error(JSON.stringify(err.toJSON()));
}
console.error(`Attempted to POST payload: ${JSON.stringify(payload)}`);
if (err.response) {
core.setFailed(err.response.data);
} else {
core.setFailed(err.message);
}
return;
}
}
if (webResponse && webResponse.ok) {
core.setOutput('ts', webResponse.ts);
// return the thread_ts if it exists, if not return the ts
const thread_ts = webResponse.thread_ts ? webResponse.thread_ts : webResponse.ts;
core.setOutput('thread_ts', thread_ts);
// return id of the channel from the response
core.setOutput('channel_id', webResponse.channel);
}
const time = (new Date()).toTimeString();
core.setOutput('time', time);
} catch (error) {
core.setFailed(error);
}
};
-20
View File
@@ -1,20 +0,0 @@
const { WebClient } = require('@slack/web-api');
const { HttpsProxyAgent } = require('https-proxy-agent');
/**
*
* @param {string} botToken token used to authenticate as the Slack Bot user
* @param {string?} httpsProxy (optional) URL for the proxy to use for HTTPS requests
* @returns { WebClient } a WebClient configured with the bot token and proxy agent (if needed)
*/
function createWebClient(botToken, httpsProxy) {
if (httpsProxy) {
const httpsProxyAgent = new HttpsProxyAgent(httpsProxy);
return new WebClient(botToken, { agent: httpsProxyAgent });
}
return new WebClient(botToken);
}
module.exports = {
createWebClient,
};
-15
View File
@@ -1,15 +0,0 @@
module.exports = {
// These environments contain lists of global variables which are allowed to be accessed
env: {
// According to https://node.green, the target node version (v10) supports all important ES2018 features. But es2018
// is not an option since it presumably doesn't introduce any new globals over ES2017.
es2017: true,
node: true,
mocha: true,
},
rules: {
// These rules dont like the use of devDependencies - which test code uses often.
'node/no-unpublished-require': 0,
'node/no-missing-require': 0,
},
};
-1
View File
@@ -1 +0,0 @@
{not-valid-json
-5
View File
@@ -1,5 +0,0 @@
{
"oliver": "benji",
"bonny": "clyde",
"actor":"${{github.actor}}"
}
-328
View File
@@ -1,328 +0,0 @@
const { assert } = require('chai');
const sinon = require('sinon');
const core = require('@actions/core');
const github = require('@actions/github');
const rewiremock = require('rewiremock/node');
const ChatStub = {
postMessage: sinon.fake.resolves({ ok: true, ts: '1503435957.111111', thread_ts: '1503435956.000247' }),
update: sinon.fake.resolves({ ok: true, thread_ts: '1503435956.000247' }),
};
/* eslint-disable-next-line global-require */
rewiremock(() => require('@slack/web-api')).with({
WebClient: class {
constructor(token) {
this.token = token;
this.chat = ChatStub;
}
},
});
const AxiosMock = {
post: sinon.stub().resolves(),
};
/* eslint-disable-next-line global-require */
rewiremock(() => require('axios')).with(AxiosMock);
rewiremock.enable();
const slackSend = require('../src/slack-send');
rewiremock.disable();
const ORIG_TOKEN_VAR = process.env.SLACK_BOT_TOKEN;
const ORIG_WEBHOOK_VAR = process.env.SLACK_WEBHOOK_URL;
describe('slack-send', () => {
const fakeCore = sinon.stub(core);
const fakeGithub = sinon.stub(github);
beforeEach(() => {
sinon.reset();
});
after(() => {
process.env.SLACK_BOT_TOKEN = ORIG_TOKEN_VAR;
process.env.SLACK_WEBHOOK_URL = ORIG_WEBHOOK_VAR;
});
it('should set an error if no webhook URL or token is provided', async () => {
delete process.env.SLACK_BOT_TOKEN;
delete process.env.SLACK_WEBHOOK_URL;
await slackSend(fakeCore);
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide at least one botToken or webhook', 'Error set specifying what env vars need to be set.');
});
describe('using a bot token', () => {
beforeEach(() => {
process.env.SLACK_BOT_TOKEN = 'xoxb-xxxxx';
delete process.env.SLACK_WEBHOOK_URL;
});
describe('happy path', () => {
it('should send a message using the postMessage API', async () => {
fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?');
fakeCore.getInput.withArgs('channel-id').returns('C123456');
await slackSend(fakeCore);
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage');
});
it('should send a message using the update API', async () => {
fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?');
fakeCore.getInput.withArgs('channel-id').returns('C123456');
fakeCore.getInput.withArgs('update-ts').returns('123456');
await slackSend(fakeCore);
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
const chatArgs = ChatStub.update.lastCall.firstArg;
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage');
});
it('should send payload-file-path values with replaced context variables', async () => {
// Prepare
fakeCore.getInput.withArgs('channel-id').returns('C123456');
fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/valid-payload.json');
fakeCore.getBooleanInput.withArgs('payload-file-path-parsed').returns(true);
fakeGithub.context.actor = 'user123';
// Run
await slackSend(fakeCore);
// Assert
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
assert.equal(chatArgs.text, '', 'Correct message provided to postMessage');
assert.equal(chatArgs.bonny, 'clyde', 'Correct message provided to postMessage');
assert.equal(chatArgs.oliver, 'benji', 'Correct message provided to postMessage');
assert.equal(chatArgs.actor, 'user123', 'Correct message provided to postMessage');
});
it('should send payload-file-path values without replacing context variables', async () => {
// Prepare
fakeCore.getInput.withArgs('channel-id').returns('C123456');
fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/valid-payload.json');
fakeCore.getBooleanInput.withArgs('payload-file-path-parsed').returns(false);
fakeGithub.context.actor = 'user123';
// Run
await slackSend(fakeCore);
// Assert
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
assert.equal(chatArgs.text, '', 'Correct message provided to postMessage');
assert.equal(chatArgs.bonny, 'clyde', 'Correct message provided to postMessage');
assert.equal(chatArgs.oliver, 'benji', 'Correct message provided to postMessage');
/* eslint-disable-next-line no-template-curly-in-string */
assert.equal(chatArgs.actor, '${{github.actor}}', 'Correct message provided to postMessage');
});
it('should send the same message to multiple channels', async () => {
fakeCore.getInput.withArgs('slack-message').returns('who let the dogs out?');
fakeCore.getInput.withArgs('channel-id').returns('C123456,C987654');
await slackSend(fakeCore);
const firstChatArgs = ChatStub.postMessage.firstCall.firstArg;
const secondChatArgs = ChatStub.postMessage.lastCall.firstArg;
assert.oneOf('C123456', [firstChatArgs.channel, secondChatArgs.channel], 'First comma-separated channel provided to postMessage');
assert.oneOf('C987654', [firstChatArgs.channel, secondChatArgs.channel], 'Second comma-separated channel provided to postMessage');
assert.equal(firstChatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage with first comma-separated channel');
assert.equal(secondChatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage with second comma-separated channel');
});
it("should send a reply-message using the postMessage API if thread_ts payload field is used'", async () => {
fakeCore.getInput
.withArgs('payload')
.returns('{"thread_ts":"123456","text":"who let the dogs out?"}');
fakeCore.getInput.withArgs('channel-id').returns('C123456');
await slackSend(fakeCore);
assert.equal(fakeCore.setOutput.firstCall.firstArg, 'ts', 'Output name set to ts');
assert.equal(fakeCore.setOutput.secondCall.firstArg, 'thread_ts', 'Output name set to thread_ts');
assert(fakeCore.setOutput.secondCall.lastArg.length > 0, 'Time output a non-zero-length string');
assert.equal(fakeCore.setOutput.lastCall.firstArg, 'time', 'Output name set to time');
assert(fakeCore.setOutput.lastCall.lastArg.length > 0, 'Time output a non-zero-length string');
const chatArgs = ChatStub.postMessage.lastCall.firstArg;
assert.equal(chatArgs.channel, 'C123456', 'Correct channel provided to postMessage');
assert.equal(chatArgs.thread_ts, '123456', 'Correct thread_ts provided to postMessage');
assert.equal(chatArgs.text, 'who let the dogs out?', 'Correct message provided to postMessage');
});
});
describe('sad path', () => {
it('should set an error if payload cannot be JSON parsed', async () => {
fakeCore.getInput.withArgs('payload').returns('{not-valid-json');
await slackSend(fakeCore);
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON', 'Error set specifying JSON was invalid.');
});
it('should fail if an invalid payload-file-path is provided', async () => {
// Prepare
fakeCore.getInput.withArgs('channel-id').returns('C123456');
fakeCore.getInput.withArgs('payload-file-path').returns('non-existing-path.json');
// Run
await slackSend(fakeCore);
// Assert
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'The payload-file-path may be incorrect. Failed to load the file: non-existing-path.json', 'Error set specifying JSON was invalid.');
});
it('should fail if a valid payload-file-path with an invalid JSON is provided', async () => {
// Prepare
fakeCore.getInput.withArgs('channel-id').returns('C123456');
fakeCore.getInput.withArgs('payload-file-path').returns('./test/resources/invalid-payload.json');
// Run
await slackSend(fakeCore);
// Assert
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Need to provide valid JSON payload', 'Error set specifying JSON was invalid.');
});
it('should fail if Channel ID is missing', async () => {
// Run
await slackSend(fakeCore);
// Assert
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Channel ID is required to run this action. An empty one has been provided', 'Error set specifying JSON was invalid.');
});
it('should fail if payload is missing or empty', async () => {
// Prepare
fakeCore.getInput.withArgs('channel-id').returns('C123456');
// Run
await slackSend(fakeCore);
// Assert
assert.include(fakeCore.setFailed.lastCall.firstArg.message, 'Missing message content, please input a valid payload or message to send. No Message has been send.', 'Error set specifying JSON was invalid.');
});
});
});
describe('using a webhook URL', () => {
beforeEach(() => {
process.env.SLACK_WEBHOOK_URL = 'https://someurl';
delete process.env.SLACK_BOT_TOKEN;
});
describe('happy path', () => {
const payload = {
batman: 'robin',
thor: 'loki',
};
beforeEach(() => {
fakeCore.getInput.withArgs('payload').returns(JSON.stringify(payload));
});
it('should post the payload to the webhook URL', async () => {
await slackSend(fakeCore);
assert(AxiosMock.post.calledWith('https://someurl', payload));
});
describe('proxy config', () => {
beforeEach(() => {
delete process.env.https_proxy;
delete process.env.HTTPS_PROXY;
});
it('should use https proxy agent when proxy uses HTTP', async () => {
process.env.HTTPS_PROXY = 'http://test.proxy:8080/';
await slackSend(fakeCore);
assert(AxiosMock.post.calledWith('https://someurl', payload, sinon.match.has('httpsAgent').and(sinon.match.has('proxy'))));
});
it('should use default axios config when no proxy set', async () => {
await slackSend(fakeCore);
assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {}));
});
it('should use default axios config when proxy uses HTTPS', async () => {
process.env.HTTPS_PROXY = 'https://test.proxy:8080/';
await slackSend(fakeCore);
assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {}));
});
it('should use default axios config when proxy URL is invalid', async () => {
process.env.HTTPS_PROXY = 'invalid string';
const consoleSpy = sinon.spy(console, 'log');
await slackSend(fakeCore);
assert(consoleSpy.calledWith('failed to configure https proxy agent for http proxy. Using default axios configuration'));
assert(AxiosMock.post.calledWithExactly('https://someurl', payload, {}));
});
});
describe('flatten', () => {
const mockPayload = {
apples: 'tree',
bananas: { truthiness: true },
};
beforeEach(() => {
fakeCore.getInput.withArgs('payload').returns(JSON.stringify(mockPayload));
});
afterEach(() => {
delete process.env.SLACK_WEBHOOK_TYPE;
});
it('defaults to using a period to flatten nested paylods', async () => {
process.env.SLACK_WEBHOOK_TYPE = 'WORKFLOW_TRIGGER';
await slackSend(fakeCore);
const expected = {
apples: 'tree',
'bananas.truthiness': 'true',
};
const count = AxiosMock.post.callCount;
assert.equal(count, 1);
const post = AxiosMock.post.getCall(0);
const [url, actual] = post.args;
assert.equal(url, 'https://someurl');
assert.deepEqual(actual, expected);
});
it('replaces delimiter with provided payload settings', async () => {
fakeCore.getInput.withArgs('payload-delimiter').returns('_');
process.env.SLACK_WEBHOOK_TYPE = 'WORKFLOW_TRIGGER';
await slackSend(fakeCore);
const expected = {
apples: 'tree',
bananas_truthiness: 'true',
};
const count = AxiosMock.post.callCount;
assert.equal(count, 1);
const post = AxiosMock.post.getCall(0);
const [url, actual] = post.args;
assert.equal(url, 'https://someurl');
assert.deepEqual(actual, expected);
});
it('does not flatten nested values of incoming webhook', async () => {
process.env.SLACK_WEBHOOK_TYPE = 'INCOMING_WEBHOOK';
await slackSend(fakeCore);
const expected = {
apples: 'tree',
bananas: { truthiness: true },
};
const count = AxiosMock.post.callCount;
assert.equal(count, 1);
const post = AxiosMock.post.getCall(0);
const [url, actual] = post.args;
assert.equal(url, 'https://someurl');
assert.deepEqual(actual, expected);
});
});
});
describe('sad path', () => {
it('should set an error if the POST to the webhook fails without a response', async () => {
AxiosMock.post.rejects(new Error('boom'));
await slackSend(fakeCore);
assert.include(fakeCore.setFailed.lastCall.firstArg, 'boom', 'Error set to whatever axios reports as error.');
});
});
});
});
-33
View File
@@ -1,33 +0,0 @@
const { assert } = require('chai');
const sinon = require('sinon');
const rewiremock = require('rewiremock/node');
/* eslint-disable-next-line global-require */
rewiremock(() => require('@slack/web-api')).with({
WebClient: class {
constructor(token, options) {
this.token = token;
this.options = options || {};
}
},
});
rewiremock.enable();
const { createWebClient } = require('../src/web-client');
rewiremock.disable();
describe('web-client', () => {
beforeEach(() => {
sinon.reset();
});
it('should create WebClient with an https proxy agent if given a proxy URL', async () => {
const web = createWebClient('xoxb-xxxxx', 'http://test.proxy:8080/');
assert.equal(typeof web.options.agent, 'object');
});
it('should create WebClient with default settings if not given a proxy URL', async () => {
const web = createWebClient('xoxb-xxxxx');
assert.equal(web.options.agent, undefined);
});
});