mirror of
https://github.com/dessant/label-actions.git
synced 2026-03-13 01:27:03 -04:00
feat: move to GitHub Actions
BREAKING CHANGE: The deployment method and configuration options have changed.
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
FROM node:10.16.0-stretch
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY build.sh /cont/script/
|
||||
RUN set -ex \
|
||||
&& chmod +x /cont/script/build.sh
|
||||
|
||||
CMD ["/cont/script/build.sh"]
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
yarn
|
||||
@@ -1,9 +0,0 @@
|
||||
# The ID of your GitHub App
|
||||
APP_ID=
|
||||
WEBHOOK_SECRET=development
|
||||
|
||||
# Use `trace` to get verbose logging or `info` to show less
|
||||
LOG_LEVEL=debug
|
||||
|
||||
# Go to https://smee.io/new and set this to the URL that you are redirected to
|
||||
# WEBHOOK_PROXY_URL=
|
||||
20
.github/workflows/release.yml
vendored
Normal file
20
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: Create release
|
||||
|
||||
on: create
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release on GitHub
|
||||
runs-on: ubuntu-18.04
|
||||
if: startsWith(github.ref, 'refs/tags/v') && contains(github.ref, '.')
|
||||
steps:
|
||||
- name: Create GitHub release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
with:
|
||||
tag_name: ${{ github.ref }}
|
||||
release_name: ${{ github.ref }}
|
||||
body: >
|
||||
Learn more about this release from the [changelog](https://github.com/dessant/label-actions/blob/master/CHANGELOG.md#changelog).
|
||||
draft: true
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,10 +1,2 @@
|
||||
.data/
|
||||
.env
|
||||
*.pem
|
||||
|
||||
node_modules/
|
||||
|
||||
npm-debug.log
|
||||
yarn-debug.log
|
||||
yarn-error.log
|
||||
.yarn-integrity
|
||||
node_modules
|
||||
.assets
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
package.json
|
||||
.travis.yml
|
||||
@@ -1,2 +1,4 @@
|
||||
singleQuote: true
|
||||
bracketSpacing: false
|
||||
arrowParens: 'avoid'
|
||||
trailingComma: 'none'
|
||||
|
||||
20
.travis.yml
20
.travis.yml
@@ -1,20 +0,0 @@
|
||||
jobs:
|
||||
fast_finish: true
|
||||
include:
|
||||
- stage: build
|
||||
install: docker build --tag ci .ci
|
||||
script: docker run --init --rm --mount type=bind,source="${PWD}",target=/app ci
|
||||
os: linux
|
||||
dist: xenial
|
||||
language: generic
|
||||
services:
|
||||
- docker
|
||||
|
||||
deploy:
|
||||
- provider: npm
|
||||
email: $NPM_EMAIL
|
||||
api_key:
|
||||
secure: "BVEm8aSAjeys0vgUztEbIcJSnab7BssI6HQVNHeuhj4St7FWecPayZiMCWc8CzgmK/ws+CcNdHhLVAb9s5XJnguKddLG8MnLegd/F7Fe5pgJkgXMhS0mBbTGOD0QcfVS/CiJbrmFnGnBRMc2vM15sNkx3C9ekUnU8nGsZZF5yuwi+UR/B7AakN/Tv2N4eEAcWId3J4N3ez3N70umJwsV7WB2RvrOOs/5y7H4miZzKUC7llr45qFtDJNopcZbrEZ3noV8g0Vcwh5z+ykQMAl6kVpZxByT1fL8IPGW+7sIEX8oR8FKDUJvJ7aGrUwlqicmFjERcNzpT8RHKaaAZT8wlnOT75bhy7Onbpyn/RQgB0UI6GAjd+LHbK4dqbw3T6sG4BakT4cO2qr/kAJm6/mclMYGD02PZkV7i+WpGjghzOtYZmOeeD3i7XKlAtc0NKu9S7K8p5reDyLXVXVV76NPE8R38i7J2NUCancysdWZZ6+jPvM20aZ9CnqjQstZxetFKtoWcem4d0dEJF/pElMKka3ucLjrg9rVwk4i5Rou7LlaIjSCLPsThTtAYuQwffu4vOWMbxeQ/do+HoalRaeyjTMyJm+hyOZ30pf0CmPOU6iAghxuo9BfSXkL954l6NhOomTMNZlnqwthCrhohfzQTQ63udxexfQyOhUYwdH1z1o="
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Armin Sebastian
|
||||
Copyright (c) 2019-2021 Armin Sebastian
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
274
README.md
274
README.md
@@ -1,10 +1,12 @@
|
||||
# Label Actions
|
||||
|
||||
[](https://travis-ci.com/dessant/label-actions)
|
||||
[](https://www.npmjs.com/package/label-actions)
|
||||
Label Actions is a GitHub bot that performs certain actions when issues
|
||||
or pull requests are labeled or unlabeled.
|
||||
|
||||
Label Actions is a GitHub App built with [Probot](https://github.com/probot/probot)
|
||||
that performs actions when issues or pull requests are labeled or unlabeled.
|
||||
> The legacy version of this project can be found
|
||||
[here](https://github.com/dessant/label-actions-app).
|
||||
|
||||
<img width="800" src="https://raw.githubusercontent.com/dessant/label-actions/master/assets/screenshot.png">
|
||||
|
||||
## Supporting the Project
|
||||
|
||||
@@ -17,88 +19,230 @@ please consider contributing with
|
||||
|
||||
## How It Works
|
||||
|
||||
The app performs certain actions when an issue or pull request
|
||||
is labeled or unlabeled. No action is taken by default and the app
|
||||
The bot performs certain actions when an issue or pull request
|
||||
is labeled or unlabeled. No action is taken by default and the bot
|
||||
must be configured. The following actions are supported:
|
||||
|
||||
* Post a comment (`comment` option)
|
||||
* Close (`close` option)
|
||||
* Reopen (`open` option)
|
||||
* Lock with an optional lock reason (`lock` and `lockReason` options)
|
||||
* Unlock (`unlock` option)
|
||||
* Post comments
|
||||
* Add labels
|
||||
* Remove labels
|
||||
* Close threads
|
||||
* Reopen threads
|
||||
* Lock threads with an optional lock reason
|
||||
* Unlock threads
|
||||
|
||||
## Usage
|
||||
|
||||
1. **[Install the GitHub App](https://github.com/apps/label-actions)**
|
||||
for the intended repositories
|
||||
2. Create `.github/label-actions.yml` based on the template below
|
||||
1. Create the `.github/workflows/label-actions.yml` workflow file,
|
||||
use one of the [example workflows](#examples) to get started
|
||||
2. Create the `.github/label-actions.yml` configuration file
|
||||
based on the [example](#configuring-labels-and-actions) below
|
||||
3. Start labeling issues and pull requests
|
||||
|
||||
**If possible, install the app only for select repositories.
|
||||
Do not leave the `All repositories` option selected, unless you intend
|
||||
to use the app for all current and future repositories.**
|
||||
### Inputs
|
||||
|
||||
#### Configuration
|
||||
The bot can be configured using [input parameters](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepswith).
|
||||
All parameters are optional, except `github-token`.
|
||||
|
||||
Create `.github/label-actions.yml` in the default branch to enable the app,
|
||||
or add it at the same file path to a repository named `.github`.
|
||||
Configure the app by editing the following template:
|
||||
- **`github-token`**
|
||||
- GitHub access token, value must be `${{ github.token }}`
|
||||
- Required
|
||||
- **`config-path`**
|
||||
- Configuration file path
|
||||
- Optional, defaults to `.github/label-actions.yml`
|
||||
- **`process-only`**
|
||||
- Process label events only for issues or pull requests, value must be
|
||||
either `issues` or `prs`
|
||||
- Optional, defaults to `''`
|
||||
|
||||
### Configuration
|
||||
|
||||
Labels and actions are specified in a configuration file.
|
||||
Actions are grouped under label names, and a label name can be prepended
|
||||
with a `-` sign to declare actions taken when a label is removed
|
||||
from a thread. Actions can be overridden or declared only for issues
|
||||
or pull requests by grouping them under the `issues` or `prs` key.
|
||||
|
||||
#### Actions
|
||||
|
||||
- **`comment`**
|
||||
- Post comments, value must be either a comment or a list of comments,
|
||||
`{issue-author}` is an optional placeholder
|
||||
- Optional, defaults to `''`
|
||||
- **`label`**
|
||||
- Add labels, value must be either a label or a list of labels
|
||||
- Optional, defaults to `''`
|
||||
- **`unlabel`**
|
||||
- Remove labels, value must be either a label or a list of labels
|
||||
- Optional, defaults to `''`
|
||||
- **`close`**
|
||||
- Close threads, value must be either `true` or `false`
|
||||
- Optional, defaults to `false`
|
||||
- **`reopen`**
|
||||
- Reopen threads, value must be either `true` or `false`
|
||||
- Optional, defaults to `false`
|
||||
- **`lock`**
|
||||
- Lock threads, value must be either `true` or `false`
|
||||
- Optional, defaults to `false`
|
||||
- **`lock-reason`**
|
||||
- Reason for locking threads, value must be one
|
||||
of `resolved`, `off-topic`, `too heated` or `spam`
|
||||
- Optional, defaults to `''`
|
||||
- **`unlock`**
|
||||
- Unlock threads, value must be either `true` or `false`
|
||||
- Optional, defaults to `false`
|
||||
|
||||
## Examples
|
||||
|
||||
The following workflow will perform the actions specified
|
||||
in the `.github/label-actions.yml` configuration file when an issue
|
||||
or pull request is labeled or unlabeled.
|
||||
|
||||
```yaml
|
||||
name: 'Label Actions'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled]
|
||||
pull_request:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
reaction:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/label-actions@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
```
|
||||
|
||||
### Available input parameters
|
||||
|
||||
This workflow declares all the available input parameters of the app
|
||||
and their default values. Any of the parameters can be omitted,
|
||||
except `github-token`.
|
||||
|
||||
```yaml
|
||||
name: 'Label Actions'
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled, unlabeled]
|
||||
pull_request:
|
||||
types: [labeled, unlabeled]
|
||||
|
||||
jobs:
|
||||
reaction:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/label-actions@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
config-path: '.github/label-actions.yml'
|
||||
process-only: ''
|
||||
```
|
||||
|
||||
### Ignoring label events
|
||||
|
||||
This step will process label events only for issues.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: dessant/label-actions@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
process-only: 'issues'
|
||||
```
|
||||
|
||||
This step will process label events only for pull requests.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: dessant/label-actions@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
process-only: 'prs'
|
||||
```
|
||||
|
||||
Unnecessary workflow runs can be avoided by removing the events
|
||||
that trigger workflows from the workflow file instead.
|
||||
|
||||
```yaml
|
||||
on:
|
||||
issues:
|
||||
types: labeled
|
||||
```
|
||||
|
||||
### Configuring labels and actions
|
||||
|
||||
Labels and actions are specified in a configuration file.
|
||||
The following example showcases how desired actions may be declared:
|
||||
|
||||
```yaml
|
||||
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||
|
||||
# Specify actions for issues and pull requests
|
||||
actions:
|
||||
# Actions taken when the `heated` label is added
|
||||
heated:
|
||||
# Post a comment
|
||||
# Actions taken when the `heated` label is added to issues or pull requests
|
||||
heated:
|
||||
# Post a comment
|
||||
comment: >
|
||||
The thread has been temporarily locked.
|
||||
Please follow our community guidelines.
|
||||
# Lock the thread
|
||||
lock: true
|
||||
# Set a lock reason
|
||||
lock-reason: 'too heated'
|
||||
# Additionally, add a label to pull requests
|
||||
prs:
|
||||
label: 'on hold'
|
||||
|
||||
# Actions taken when the `heated` label is removed from issues or pull requests
|
||||
-heated:
|
||||
# Unlock the thread
|
||||
unlock: true
|
||||
|
||||
# Actions taken when the `wontfix` label is removed from issues or pull requests
|
||||
-wontfix:
|
||||
# Reopen the thread
|
||||
reopen: true
|
||||
|
||||
# Actions taken when the `feature` label is added to issues
|
||||
feature:
|
||||
issues:
|
||||
# Post a comment, `{issue-author}` is an optional placeholder
|
||||
comment: >
|
||||
The thread has been temporarily locked.
|
||||
Please follow our community guidelines.
|
||||
# Lock the thread
|
||||
lock: true
|
||||
# Set a lock reason, such as `off-topic`, `too heated`, `resolved` or `spam`
|
||||
lockReason: "too heated"
|
||||
# Actions taken when the `heated` label is removed
|
||||
-heated:
|
||||
# Unlock the thread
|
||||
unlock: true
|
||||
:wave: @{issue-author}, please use our idea board to request new features.
|
||||
# Close the issue
|
||||
close: true
|
||||
|
||||
# Optionally, specify configuration settings just for issues
|
||||
issues:
|
||||
actions:
|
||||
feature:
|
||||
# Post a comment, `{issue-author}` is an optional placeholder
|
||||
comment: >
|
||||
:wave: @{issue-author}, please use our idea board to request new features.
|
||||
# Close the issue
|
||||
close: true
|
||||
-wontfix:
|
||||
# Reopen the issue
|
||||
open: true
|
||||
# Actions taken when the `wip` label is added to pull requests
|
||||
wip:
|
||||
prs:
|
||||
# Add labels
|
||||
label:
|
||||
- 'on hold'
|
||||
- 'needs feedback'
|
||||
|
||||
# Optionally, specify configuration settings just for pull requests
|
||||
pulls:
|
||||
actions:
|
||||
pizzazz:
|
||||
comment: >
|
||||

|
||||
# Actions taken when the `wip` label is removed from pull requests
|
||||
-wip:
|
||||
prs:
|
||||
# Add label
|
||||
label: 'needs QA'
|
||||
# Remove labels
|
||||
unlabel:
|
||||
- 'on hold'
|
||||
- 'needs feedback'
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
# Actions taken when the `pizzazz` label is added to issues or pull requests
|
||||
pizzazz:
|
||||
# Post comments
|
||||
comment:
|
||||
- ''
|
||||
- ''
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
See [docs/deploy.md](docs/deploy.md) if you would like to run your own
|
||||
instance of this app.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2019 Armin Sebastian
|
||||
Copyright (c) 2019-2021 Armin Sebastian
|
||||
|
||||
This software is released under the terms of the MIT License.
|
||||
See the [LICENSE](LICENSE) file for further information.
|
||||
|
||||
19
action.yml
Normal file
19
action.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
name: 'Label Actions'
|
||||
description: 'Perform actions when issues or pull requests are labeled or unlabeled'
|
||||
author: 'Armin Sebastian'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'GitHub access token'
|
||||
required: true
|
||||
config-path:
|
||||
description: 'Configuration file path'
|
||||
default: '.github/label-actions.yml'
|
||||
process-only:
|
||||
description: 'Process label events only for issues or pull requests, value must be either `issues` or `prs`'
|
||||
default: ''
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'tag'
|
||||
color: 'purple'
|
||||
@@ -1,74 +0,0 @@
|
||||
A GitHub App that performs actions when issues or pull requests are labeled or unlabeled.
|
||||
|
||||
## Supporting the Project
|
||||
|
||||
The continued development of Label Actions is made possible thanks to the support of awesome backers. If you'd like to join them, please consider contributing with [Patreon](https://armin.dev/go/patreon?pr=label-actions&src=app), [PayPal](https://armin.dev/go/paypal?pr=label-actions&src=app) or [Bitcoin](https://armin.dev/go/bitcoin?pr=label-actions&src=app).
|
||||
|
||||
## How It Works
|
||||
|
||||
The app performs certain actions when an issue or pull request is labeled or unlabeled. No action is taken by default and the app must be configured. The following actions are supported:
|
||||
|
||||
* Post a comment (`comment` option)
|
||||
* Close (`close` option)
|
||||
* Reopen (`open` option)
|
||||
* Lock with an optional lock reason (`lock` and `lockReason` options)
|
||||
* Unlock (`unlock` option)
|
||||
|
||||
## Usage
|
||||
|
||||
1. **[Install the GitHub App](https://github.com/apps/label-actions)** for the intended repositories
|
||||
2. Create `.github/label-actions.yml` based on the template below
|
||||
3. Start labeling issues and pull requests
|
||||
|
||||
**If possible, install the app only for select repositories. Do not leave the `All repositories` option selected, unless you intend to use the app for all current and future repositories.**
|
||||
|
||||
#### Configuration
|
||||
|
||||
Create `.github/label-actions.yml` in the default branch to enable the app, or add it at the same file path to a repository named `.github`. Configure the app by editing the following template:
|
||||
|
||||
```yaml
|
||||
# Configuration for Label Actions - https://github.com/dessant/label-actions
|
||||
|
||||
# Specify actions for issues and pull requests
|
||||
actions:
|
||||
# Actions taken when the `heated` label is added
|
||||
heated:
|
||||
# Post a comment
|
||||
comment: >
|
||||
The thread has been temporarily locked.
|
||||
Please follow our community guidelines.
|
||||
# Lock the thread
|
||||
lock: true
|
||||
# Set a lock reason, such as `off-topic`, `too heated`, `resolved` or `spam`
|
||||
lockReason: "too heated"
|
||||
# Actions taken when the `heated` label is removed
|
||||
-heated:
|
||||
# Unlock the thread
|
||||
unlock: true
|
||||
|
||||
# Optionally, specify configuration settings just for issues
|
||||
issues:
|
||||
actions:
|
||||
feature:
|
||||
# Post a comment, `{issue-author}` is an optional placeholder
|
||||
comment: >
|
||||
:wave: @{issue-author}, please use our idea board to request new features.
|
||||
# Close the issue
|
||||
close: true
|
||||
-wontfix:
|
||||
# Reopen the issue
|
||||
open: true
|
||||
|
||||
# Optionally, specify configuration settings just for pull requests
|
||||
pulls:
|
||||
actions:
|
||||
pizzazz:
|
||||
comment: >
|
||||

|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
```
|
||||
BIN
assets/screenshot.png
Executable file
BIN
assets/screenshot.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 58 KiB |
@@ -1,14 +0,0 @@
|
||||
# Deploying
|
||||
|
||||
If you would like to run your own instance of this app, see the
|
||||
[docs for deployment](https://probot.github.io/docs/deployment/).
|
||||
|
||||
This app requires these **Permissions & events** for the GitHub App:
|
||||
|
||||
- Issues - **Read & Write**
|
||||
- [x] Check the box for **Issues** events
|
||||
- Pull requests - **Read & Write**
|
||||
- [x] Check the box for **Pull request** events
|
||||
- Repository metadata - **Read-only**
|
||||
- Single File - **Read-only**
|
||||
- Path: `.github/label-actions.yml`
|
||||
4639
package-lock.json
generated
Normal file
4639
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
51
package.json
51
package.json
@@ -1,60 +1,55 @@
|
||||
{
|
||||
"name": "label-actions",
|
||||
"version": "1.0.1",
|
||||
"description": "A GitHub App built with Probot that performs actions when issues or pull requests are labeled or unlabeled.",
|
||||
"description": "A GitHub Action that performs actions when issues or pull requests are labeled or unlabeled.",
|
||||
"author": "Armin Sebastian",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/dessant/label-actions",
|
||||
"repository": {
|
||||
"url": "https://github.com/dessant/label-actions.git",
|
||||
"type": "git"
|
||||
},
|
||||
"bugs": "https://github.com/dessant/label-actions/issues",
|
||||
"bugs": {
|
||||
"url": "https://github.com/dessant/label-actions/issues"
|
||||
},
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "probot run ./src/index.js",
|
||||
"watch": "nodemon --exec 'yarn run start'",
|
||||
"update": "ncu --upgrade && yarn",
|
||||
"push": "git push --follow-tags origin master",
|
||||
"release": "standard-version"
|
||||
"build": "ncc build src/index.js -o dist",
|
||||
"update": "ncu --upgrade",
|
||||
"release": "standard-version",
|
||||
"push": "git push --tags origin master"
|
||||
},
|
||||
"files": [
|
||||
"docs",
|
||||
"src"
|
||||
],
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^16.1.7",
|
||||
"probot": "^9.6.0",
|
||||
"probot-messages": "^1.0.1",
|
||||
"uuid": "^3.3.3"
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/github": "^4.0.0",
|
||||
"joi": "^17.3.0",
|
||||
"js-yaml": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.19.4",
|
||||
"npm-check-updates": "^3.1.25",
|
||||
"prettier": "^1.18.2",
|
||||
"smee-client": "^1.1.0",
|
||||
"standard-version": "^7.0.0"
|
||||
"@vercel/ncc": "^0.27.0",
|
||||
"npm-check-updates": "^10.2.5",
|
||||
"prettier": "^2.2.1",
|
||||
"standard-version": "^9.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.12.0"
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"github",
|
||||
"issues",
|
||||
"pull requests",
|
||||
"label",
|
||||
"github labels",
|
||||
"comment",
|
||||
"close",
|
||||
"lock",
|
||||
"label issues",
|
||||
"close issues",
|
||||
"lock issues",
|
||||
"comment",
|
||||
"automation",
|
||||
"github app",
|
||||
"probot",
|
||||
"probot app",
|
||||
"github actions",
|
||||
"project management",
|
||||
"bot"
|
||||
],
|
||||
"resolutions": {
|
||||
"**/@octokit/endpoint": "^5.3.6"
|
||||
}
|
||||
"private": true
|
||||
}
|
||||
|
||||
115
src/action.js
115
src/action.js
@@ -1,115 +0,0 @@
|
||||
module.exports = class Action {
|
||||
constructor(context, config, logger) {
|
||||
this.context = context;
|
||||
this.config = config;
|
||||
this.log = logger;
|
||||
}
|
||||
|
||||
async action() {
|
||||
const {payload, github} = this.context;
|
||||
const issue = this.context.repo({
|
||||
issue_number: this.context.issue().number
|
||||
});
|
||||
|
||||
const {only} = this.config;
|
||||
const type = payload.issue ? 'issues' : 'pulls';
|
||||
if (only && only !== type) {
|
||||
return;
|
||||
}
|
||||
|
||||
let label = payload.label.name;
|
||||
if (payload.action === 'unlabeled') {
|
||||
label = `-${label}`;
|
||||
}
|
||||
const actions = this.getLabelActions(type, label);
|
||||
if (!actions) {
|
||||
return;
|
||||
}
|
||||
const {comment, open, close, lock, unlock, lockReason} = actions;
|
||||
|
||||
const targetPayload = payload.issue || payload.pull_request;
|
||||
|
||||
if (comment) {
|
||||
this.log.info(issue, 'Commenting');
|
||||
const commentBody = comment.replace(
|
||||
/{issue-author}/,
|
||||
targetPayload.user.login
|
||||
);
|
||||
await this.ensureUnlock(issue, {active: targetPayload.locked}, () =>
|
||||
github.issues.createComment({...issue, body: commentBody})
|
||||
);
|
||||
}
|
||||
|
||||
if (open && targetPayload.state === 'closed') {
|
||||
this.log.info(issue, 'Reopening');
|
||||
await github.issues.update({...issue, state: 'open'});
|
||||
}
|
||||
|
||||
if (close && targetPayload.state === 'open') {
|
||||
this.log.info(issue, 'Closing');
|
||||
await github.issues.update({...issue, state: 'closed'});
|
||||
}
|
||||
|
||||
if (lock && !targetPayload.locked) {
|
||||
this.log.info(issue, 'Locking');
|
||||
let params;
|
||||
if (lockReason) {
|
||||
params = {
|
||||
...issue,
|
||||
lock_reason: lockReason,
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
params = issue;
|
||||
}
|
||||
await github.issues.lock(params);
|
||||
}
|
||||
|
||||
if (unlock && targetPayload.locked) {
|
||||
this.log.info(issue, 'Unlocking');
|
||||
github.issues.unlock(issue);
|
||||
}
|
||||
}
|
||||
|
||||
async ensureUnlock(issue, lock, action) {
|
||||
const github = this.context.github;
|
||||
if (lock.active) {
|
||||
if (!lock.hasOwnProperty('reason')) {
|
||||
const {data: issueData} = await github.issues.get({
|
||||
...issue,
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
});
|
||||
lock.reason = issueData.active_lock_reason;
|
||||
}
|
||||
await github.issues.unlock(issue);
|
||||
await action();
|
||||
if (lock.reason) {
|
||||
issue = {
|
||||
...issue,
|
||||
lock_reason: lock.reason,
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
};
|
||||
}
|
||||
await github.issues.lock(issue);
|
||||
} else {
|
||||
await action();
|
||||
}
|
||||
}
|
||||
|
||||
getLabelActions(type, label) {
|
||||
if (
|
||||
this.config[type] &&
|
||||
this.config[type].actions &&
|
||||
this.config[type].actions[label]
|
||||
) {
|
||||
return this.config[type].actions[label];
|
||||
}
|
||||
return this.config.actions[label];
|
||||
}
|
||||
};
|
||||
282
src/index.js
282
src/index.js
@@ -1,63 +1,241 @@
|
||||
const uuidV4 = require('uuid/v4');
|
||||
const sendMessage = require('probot-messages');
|
||||
const core = require('@actions/core');
|
||||
const github = require('@actions/github');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const App = require('./action');
|
||||
const schema = require('./schema');
|
||||
const {configSchema, actionSchema} = require('./schema');
|
||||
|
||||
module.exports = async robot => {
|
||||
const github = await robot.auth();
|
||||
const appName = (await github.apps.getAuthenticated()).data.name;
|
||||
async function run() {
|
||||
try {
|
||||
const config = getConfig();
|
||||
const client = github.getOctokit(config['github-token']);
|
||||
|
||||
robot.on(
|
||||
[
|
||||
'issues.labeled',
|
||||
'issues.unlabeled',
|
||||
'pull_request.labeled',
|
||||
'pull_request.unlabeled'
|
||||
],
|
||||
async context => {
|
||||
const app = await getApp(context);
|
||||
if (app) {
|
||||
await app.action();
|
||||
const actions = await getActionConfig(client, config['config-path']);
|
||||
|
||||
const app = new App(config, client, actions);
|
||||
|
||||
await app.performActions();
|
||||
} catch (err) {
|
||||
core.setFailed(err);
|
||||
}
|
||||
}
|
||||
|
||||
class App {
|
||||
constructor(config, client, actions) {
|
||||
this.config = config;
|
||||
this.client = client;
|
||||
this.actions = actions;
|
||||
}
|
||||
|
||||
async performActions() {
|
||||
const payload = github.context.payload;
|
||||
|
||||
if (payload.sender.type === 'Bot') {
|
||||
return;
|
||||
}
|
||||
|
||||
const threadType = payload.issue ? 'issue' : 'pr';
|
||||
|
||||
const processOnly = this.config['process-only'];
|
||||
if (processOnly && processOnly !== threadType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions = this.getLabelActions(
|
||||
payload.label.name,
|
||||
payload.action,
|
||||
threadType
|
||||
);
|
||||
if (!actions) {
|
||||
return;
|
||||
}
|
||||
|
||||
const threadData = payload.issue || payload.pull_request;
|
||||
|
||||
const {owner, repo} = github.context.repo;
|
||||
const issue = {owner, repo, issue_number: threadData.number};
|
||||
|
||||
const lock = {
|
||||
active: threadData.locked,
|
||||
reason: threadData.active_lock_reason
|
||||
};
|
||||
|
||||
if (actions.comment) {
|
||||
core.debug('Commenting');
|
||||
await this.ensureUnlock(issue, lock, async () => {
|
||||
for (let commentBody of actions.comment) {
|
||||
commentBody = commentBody.replace(
|
||||
/{issue-author}/,
|
||||
threadData.user.login
|
||||
);
|
||||
|
||||
await this.client.issues.createComment({...issue, body: commentBody});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (actions.label) {
|
||||
const currentLabels = threadData.labels.map(label => label.name);
|
||||
const newLabels = actions.label.filter(
|
||||
label => !currentLabels.includes(label)
|
||||
);
|
||||
|
||||
if (newLabels.length) {
|
||||
core.debug('Labeling');
|
||||
await this.client.issues.addLabels({
|
||||
...issue,
|
||||
labels: newLabels
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.unlabel) {
|
||||
const currentLabels = threadData.labels.map(label => label.name);
|
||||
const matchingLabels = currentLabels.filter(label =>
|
||||
actions.unlabel.includes(label)
|
||||
);
|
||||
|
||||
for (const label of matchingLabels) {
|
||||
core.debug('Unlabeling');
|
||||
await this.client.issues.removeLabel({
|
||||
...issue,
|
||||
name: label
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (actions.reopen && threadData.state === 'closed' && !threadData.merged) {
|
||||
core.debug('Reopening');
|
||||
await this.client.issues.update({...issue, state: 'open'});
|
||||
}
|
||||
|
||||
if (actions.close && threadData.state === 'open') {
|
||||
core.debug('Closing');
|
||||
await this.client.issues.update({...issue, state: 'closed'});
|
||||
}
|
||||
|
||||
if (actions.lock && !threadData.locked) {
|
||||
core.debug('Locking');
|
||||
const params = {...issue};
|
||||
const lockReason = actions['lock-reason'];
|
||||
if (lockReason) {
|
||||
Object.assign(params, {
|
||||
lock_reason: lockReason,
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
});
|
||||
}
|
||||
await this.client.issues.lock(params);
|
||||
}
|
||||
|
||||
if (actions.unlock && threadData.locked) {
|
||||
core.debug('Unlocking');
|
||||
await this.client.issues.unlock(issue);
|
||||
}
|
||||
}
|
||||
|
||||
getLabelActions(label, event, threadType) {
|
||||
if (event === 'unlabeled') {
|
||||
label = `-${label}`;
|
||||
}
|
||||
threadType = threadType === 'issue' ? 'issues' : 'prs';
|
||||
|
||||
const actions = this.actions[label];
|
||||
|
||||
if (actions) {
|
||||
const threadActions = actions[threadType];
|
||||
if (threadActions) {
|
||||
Object.assign(actions, threadActions);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
}
|
||||
|
||||
async ensureUnlock(issue, lock, action) {
|
||||
if (lock.active) {
|
||||
if (!lock.hasOwnProperty('reason')) {
|
||||
const {data: issueData} = await this.client.issues.get({
|
||||
...issue,
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
});
|
||||
lock.reason = issueData.active_lock_reason;
|
||||
}
|
||||
await this.client.issues.unlock(issue);
|
||||
|
||||
let actionError;
|
||||
try {
|
||||
await action();
|
||||
} catch (err) {
|
||||
actionError = err;
|
||||
}
|
||||
|
||||
if (lock.reason) {
|
||||
issue = {
|
||||
...issue,
|
||||
lock_reason: lock.reason,
|
||||
headers: {
|
||||
Accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
};
|
||||
}
|
||||
await this.client.issues.lock(issue);
|
||||
|
||||
if (actionError) {
|
||||
throw actionError;
|
||||
}
|
||||
} else {
|
||||
await action();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
const input = Object.fromEntries(
|
||||
Object.keys(configSchema.describe().keys).map(item => [
|
||||
item,
|
||||
core.getInput(item)
|
||||
])
|
||||
);
|
||||
|
||||
async function getApp(context) {
|
||||
const logger = context.log.child({appName, session: uuidV4()});
|
||||
const config = await getConfig(context, logger);
|
||||
if (config && config.perform) {
|
||||
return new App(context, config, logger);
|
||||
const {error, value} = configSchema.validate(input, {abortEarly: false});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
async function getActionConfig(client, configPath) {
|
||||
let configData;
|
||||
try {
|
||||
({
|
||||
data: {content: configData}
|
||||
} = await client.repos.getContent({
|
||||
...github.context.repo,
|
||||
path: configPath
|
||||
}));
|
||||
} catch (err) {
|
||||
if (err.status === 404) {
|
||||
throw new Error(`Missing configuration file (${configPath})`);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfig(context, log, file = 'label-actions.yml') {
|
||||
let config;
|
||||
const repo = context.repo();
|
||||
try {
|
||||
let repoConfig = await context.config(file);
|
||||
if (!repoConfig) {
|
||||
repoConfig = {perform: false};
|
||||
}
|
||||
const {error, value} = schema.validate(repoConfig);
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
config = value;
|
||||
} catch (err) {
|
||||
log.warn({err: new Error(err), repo, file}, 'Invalid config');
|
||||
if (['YAMLException', 'ValidationError'].includes(err.name)) {
|
||||
await sendMessage(
|
||||
robot,
|
||||
context,
|
||||
'[{appName}] Configuration error',
|
||||
'[{appName}]({appUrl}) has encountered a configuration error in ' +
|
||||
`\`${file}\`.\n\`\`\`\n${err.toString()}\n\`\`\``,
|
||||
{update: 'The configuration error is still occurring.'}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return config;
|
||||
const input = yaml.load(Buffer.from(configData, 'base64').toString());
|
||||
if (!input) {
|
||||
throw new Error(`Empty configuration file (${configPath})`);
|
||||
}
|
||||
};
|
||||
|
||||
const {error, value} = actionSchema.validate(input, {abortEarly: false});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
150
src/schema.js
150
src/schema.js
@@ -1,65 +1,93 @@
|
||||
const Joi = require('@hapi/joi');
|
||||
const Joi = require('joi');
|
||||
|
||||
const fields = {
|
||||
actions: Joi.object()
|
||||
.pattern(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(50),
|
||||
Joi.object().keys({
|
||||
comment: Joi.alternatives()
|
||||
.try(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(10000),
|
||||
Joi.boolean().only(false)
|
||||
)
|
||||
.default(false)
|
||||
.description(
|
||||
'Post a comment, `{issue-author}` is an optional placeholder'
|
||||
),
|
||||
close: Joi.boolean()
|
||||
.default(false)
|
||||
.description('Close the thread'),
|
||||
open: Joi.boolean()
|
||||
.default(false)
|
||||
.description('Reopen the thread'),
|
||||
lock: Joi.boolean()
|
||||
.default(false)
|
||||
.description('Lock the thread'),
|
||||
unlock: Joi.boolean()
|
||||
.default(false)
|
||||
.description('Unlock the thread'),
|
||||
lockReason: Joi.alternatives()
|
||||
.try(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.valid('off-topic', 'too heated', 'resolved', 'spam'),
|
||||
Joi.boolean().only(false)
|
||||
)
|
||||
.default(false)
|
||||
.description(
|
||||
'Set a lock reason, such as `off-topic`, `too heated`, `resolved` or `spam`'
|
||||
)
|
||||
})
|
||||
)
|
||||
.max(200)
|
||||
.description('Specify actions for issues and pull requests')
|
||||
};
|
||||
const extendedJoi = Joi.extend({
|
||||
type: 'processOnly',
|
||||
base: Joi.string(),
|
||||
coerce: {
|
||||
from: 'string',
|
||||
method(value) {
|
||||
value = value.trim();
|
||||
if (['issues', 'prs'].includes(value)) {
|
||||
value = value.slice(0, -1);
|
||||
}
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
actions: fields.actions.default({}),
|
||||
only: Joi.string()
|
||||
.trim()
|
||||
.valid('issues', 'pulls')
|
||||
.description('Limit to only `issues` or `pulls`'),
|
||||
pulls: Joi.object().keys(fields),
|
||||
issues: Joi.object().keys(fields),
|
||||
_extends: Joi.string()
|
||||
.trim()
|
||||
.max(260)
|
||||
.description('Repository to extend settings from'),
|
||||
perform: Joi.boolean().default(!process.env.DRY_RUN)
|
||||
return {value};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = schema;
|
||||
const configSchema = Joi.object({
|
||||
'github-token': Joi.string().trim().max(100),
|
||||
|
||||
'config-path': Joi.string()
|
||||
.trim()
|
||||
.max(200)
|
||||
.default('.github/label-actions.yml'),
|
||||
|
||||
'process-only': extendedJoi.processOnly().valid('issue', 'pr', '').default('')
|
||||
});
|
||||
|
||||
const actions = {
|
||||
close: Joi.boolean(),
|
||||
|
||||
reopen: Joi.boolean(),
|
||||
|
||||
lock: Joi.boolean(),
|
||||
|
||||
unlock: Joi.boolean(),
|
||||
|
||||
'lock-reason': Joi.alternatives().try(
|
||||
Joi.boolean().only(false),
|
||||
Joi.string().trim().valid('resolved', 'off-topic', 'too heated', 'spam', '')
|
||||
),
|
||||
|
||||
comment: Joi.alternatives().try(
|
||||
Joi.boolean().only(false),
|
||||
Joi.string().trim().valid(''),
|
||||
Joi.array().items(Joi.string().trim().max(65536)).min(1).max(10).single()
|
||||
),
|
||||
|
||||
label: Joi.alternatives().try(
|
||||
Joi.boolean().only(false),
|
||||
Joi.string().trim().valid(''),
|
||||
Joi.array()
|
||||
.items(Joi.string().trim().max(50))
|
||||
.min(1)
|
||||
.max(30)
|
||||
.unique()
|
||||
.single()
|
||||
),
|
||||
|
||||
unlabel: Joi.alternatives().try(
|
||||
Joi.boolean().only(false),
|
||||
Joi.string().trim().valid(''),
|
||||
Joi.array()
|
||||
.items(Joi.string().trim().max(50))
|
||||
.min(1)
|
||||
.max(30)
|
||||
.unique()
|
||||
.single()
|
||||
)
|
||||
};
|
||||
|
||||
const actionSchema = Joi.object()
|
||||
.pattern(
|
||||
Joi.string().trim().max(51),
|
||||
Joi.object().keys({
|
||||
close: actions.close.default(false),
|
||||
reopen: actions.reopen.default(false),
|
||||
lock: actions.lock.default(false),
|
||||
unlock: actions.unlock.default(false),
|
||||
'lock-reason': actions['lock-reason'].default(''),
|
||||
comment: actions.comment.default(''),
|
||||
label: actions.label.default(''),
|
||||
unlabel: actions.unlabel.default(''),
|
||||
|
||||
issues: Joi.object().keys(actions),
|
||||
prs: Joi.object().keys(actions)
|
||||
})
|
||||
)
|
||||
.min(1)
|
||||
.max(200);
|
||||
|
||||
module.exports = {configSchema, actionSchema};
|
||||
|
||||
Reference in New Issue
Block a user