mirror of
https://github.com/dessant/label-actions.git
synced 2026-03-13 01:27:03 -04:00
chore: add content
This commit is contained in:
9
.env.example
Normal file
9
.env.example
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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=
|
||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
.data/
|
||||
.env
|
||||
*.pem
|
||||
|
||||
node_modules/
|
||||
|
||||
npm-debug.log
|
||||
yarn-debug.log
|
||||
yarn-error.log
|
||||
.yarn-integrity
|
||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
||||
package.json
|
||||
2
.prettierrc
Normal file
2
.prettierrc
Normal file
@@ -0,0 +1,2 @@
|
||||
singleQuote: true
|
||||
bracketSpacing: false
|
||||
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
language: node_js
|
||||
|
||||
cache: yarn
|
||||
|
||||
script: ""
|
||||
|
||||
deploy:
|
||||
- provider: npm
|
||||
email: $NPM_EMAIL
|
||||
api_key: $NPM_TOKEN
|
||||
on:
|
||||
tags: true
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 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
|
||||
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.
|
||||
102
README.md
Normal file
102
README.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# Label Actions
|
||||
|
||||
[](https://travis-ci.com/dessant/label-actions)
|
||||
[](https://www.npmjs.com/package/label-actions)
|
||||
|
||||
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.
|
||||
|
||||
## 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://www.patreon.com/dessant),
|
||||
[PayPal](https://www.paypal.me/ArminSebastian) or [Bitcoin](https://goo.gl/uJUAaU).
|
||||
|
||||
## 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 special 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
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
This software is released under the terms of the MIT License.
|
||||
See the [LICENSE](LICENSE) file for further information.
|
||||
74
assets/app-description.md
Normal file
74
assets/app-description.md
Normal file
@@ -0,0 +1,74 @@
|
||||
A GitHub App that performs actions when issues or pull requests are labeled or unlabeled.
|
||||
|
||||
## 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 special 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
|
||||
```
|
||||
|
||||
## 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://www.patreon.com/dessant), [PayPal](https://www.paypal.me/ArminSebastian) or [Bitcoin](https://goo.gl/uJUAaU).
|
||||
14
docs/deploy.md
Normal file
14
docs/deploy.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# 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`
|
||||
58
package.json
Normal file
58
package.json
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"name": "label-actions",
|
||||
"version": "0.1.0",
|
||||
"description": "A GitHub App built with Probot that performs actions when issues or pull requests are labeled or unlabeled.",
|
||||
"author": "Armin Sebastian",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"url": "https://github.com/dessant/label-actions.git",
|
||||
"type": "git"
|
||||
},
|
||||
"bugs": "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 --upgradeAll && yarn",
|
||||
"push": "git push --follow-tags origin master",
|
||||
"release": "standard-version && yarn run push"
|
||||
},
|
||||
"files": [
|
||||
"docs",
|
||||
"src"
|
||||
],
|
||||
"dependencies": {
|
||||
"joi": "^14.3.1",
|
||||
"probot": "^7.5.0",
|
||||
"probot-config": "^1.0.1",
|
||||
"probot-messages": "^0.1.2",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^1.18.9",
|
||||
"npm-check-updates": "^2.15.0",
|
||||
"prettier": "^1.16.0",
|
||||
"smee-client": "^1.0.2",
|
||||
"standard-version": "^4.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.12.0"
|
||||
},
|
||||
"keywords": [
|
||||
"github",
|
||||
"issues",
|
||||
"pull requests",
|
||||
"label",
|
||||
"comment",
|
||||
"close",
|
||||
"lock",
|
||||
"label issues",
|
||||
"close issues",
|
||||
"lock issues",
|
||||
"automation",
|
||||
"github app",
|
||||
"probot",
|
||||
"probot app",
|
||||
"bot"
|
||||
]
|
||||
}
|
||||
113
src/action.js
Normal file
113
src/action.js
Normal file
@@ -0,0 +1,113 @@
|
||||
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.issue();
|
||||
|
||||
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.edit({...issue, state: 'open'});
|
||||
}
|
||||
|
||||
if (close && targetPayload.state === 'open') {
|
||||
this.log.info(issue, 'Closing');
|
||||
await github.issues.edit({...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];
|
||||
}
|
||||
};
|
||||
64
src/index.js
Normal file
64
src/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
const uuidV4 = require('uuid/v4');
|
||||
const getMergedConfig = require('probot-config');
|
||||
const sendMessage = require('probot-messages');
|
||||
|
||||
const App = require('./action');
|
||||
const schema = require('./schema');
|
||||
|
||||
module.exports = async robot => {
|
||||
const github = await robot.auth();
|
||||
const appName = (await github.apps.get({})).data.name;
|
||||
|
||||
robot.on(
|
||||
[
|
||||
'issues.labeled',
|
||||
'issues.unlabeled',
|
||||
'pull_request.labeled',
|
||||
'pull_request.unlabeled'
|
||||
],
|
||||
async context => {
|
||||
const app = await getApp(context);
|
||||
if (app) {
|
||||
await app.action();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfig(context, log, file = 'label-actions.yml') {
|
||||
let config;
|
||||
const repo = context.repo();
|
||||
try {
|
||||
let repoConfig = await getMergedConfig(context, 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;
|
||||
}
|
||||
};
|
||||
65
src/schema.js
Normal file
65
src/schema.js
Normal file
@@ -0,0 +1,65 @@
|
||||
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 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)
|
||||
});
|
||||
|
||||
module.exports = schema;
|
||||
Reference in New Issue
Block a user