feat: move to GitHub Actions

BREAKING CHANGE: The deployment method and configuration options have changed.
This commit is contained in:
dessant
2021-01-17 14:54:12 +02:00
parent 3f2e20f26a
commit 7996649692
21 changed files with 5235 additions and 5406 deletions

View File

@@ -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"]

View File

@@ -1,4 +0,0 @@
#!/usr/bin/env bash
set -e
yarn

View File

@@ -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
View 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
View File

@@ -1,10 +1,2 @@
.data/
.env
*.pem
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
.yarn-integrity
node_modules
.assets

2
.nvmrc
View File

@@ -1 +1 @@
12.13.0
12.20.0

View File

@@ -1,2 +0,0 @@
package.json
.travis.yml

View File

@@ -1,2 +1,4 @@
singleQuote: true
bracketSpacing: false
arrowParens: 'avoid'
trailingComma: 'none'

View File

@@ -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

View File

@@ -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
View File

@@ -1,10 +1,12 @@
# Label Actions
[![Build Status](https://img.shields.io/travis/com/dessant/label-actions/master.svg)](https://travis-ci.com/dessant/label-actions)
[![Version](https://img.shields.io/npm/v/label-actions.svg?colorB=007EC6)](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: >
![](https://i.imgur.com/WuduJNk.jpg)
# 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:
- '![](https://i.imgur.com/WuduJNk.jpg)'
- '![](https://i.imgur.com/1D8yxOo.gif)'
```
## 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
View 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'

View File

@@ -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: >
![](https://i.imgur.com/WuduJNk.jpg)
# Limit to only `issues` or `pulls`
# only: issues
# Repository to extend settings from
# _extends: repo
```

BIN
assets/screenshot.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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
}

View File

@@ -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];
}
};

View File

@@ -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();

View File

@@ -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};

4941
yarn.lock

File diff suppressed because it is too large Load Diff