mirror of
https://github.com/dessant/lock-threads.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=
|
||||
14
.github/workflows/lock.yml
vendored
Normal file
14
.github/workflows/lock.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: 'Lock threads'
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: '180'
|
||||
24
.github/workflows/release.yml
vendored
Normal file
24
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
name: Create release
|
||||
|
||||
on:
|
||||
create:
|
||||
tags:
|
||||
- 'v*.*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
name: Release on GitHub
|
||||
runs-on: ubuntu-18.04
|
||||
if: contains(github.ref, '.')
|
||||
steps:
|
||||
- name: Create GitHub release
|
||||
id: create_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/lock-threads/blob/master/CHANGELOG.md#changelog).
|
||||
draft: true
|
||||
12
.gitignore
vendored
12
.gitignore
vendored
@@ -1,11 +1 @@
|
||||
.assets/
|
||||
.data/
|
||||
.env
|
||||
*.pem
|
||||
|
||||
node_modules/
|
||||
|
||||
npm-debug.log
|
||||
yarn-debug.log
|
||||
yarn-error.log
|
||||
.yarn-integrity
|
||||
node_modules
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
package.json
|
||||
.travis.yml
|
||||
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: "nQ42MQ1fwlYabJQE1Wpcpers6AXCsweXcsrEUV0UJd7WTU/ejllUtVk7TMg+Qe4USzxj7pOwZohju6PLsI1tlkzZlSSg0UZ7xC4zVYSiK4mZgBWZltRuvxYBX0WNlcmrWnbr2rQVnNdZdylIlnWyo4x4OO0vqoOumHI8/dVZHsF66wgdhVCR9mVM49WqkE05Yjev2f5Zlq8EmSeTJ++7T4eV84VaP3JS7y6TImDKM8BTNInAW+sdxdAmjWO8HPDmw1wyH6Zk+5Qs78PShGNIoBC5SdaUz9nzcI8P69aGs90Dc1a+mx8PdQxQaQvvJYomPBT3rl/VUazelWZ6yBkOPCIMqu0QAVYr4SP6gqiJzRrKHr99AnIRmb4fP/dY0JD6vedFZ1ipn6AHKXS9CmcpF3WmIE/XjoS0IGLmPrNAF8oS9SPRF+B8iLw5BIUhwwWikgcbKKykkMGnqAs+kglE1rxEAWDhJ2b8Rnn0JvogLl/8r+kw50N/BsqKFwCkNdLeY93mDsGe0cAsvETN03mepMGg+eeAc4/98O6dKzTyaplyl2UnC8tiUtcNMEgqaOkVL8bWZ3vkhHE4ncN2+0MRqxlo0F/jxeIV3bX3K2CdQPU5O/CbVGmK4XM3y4Z/r8gydVxA2Bm3bvmUNW0XAgGxXerR9OEKySiUI2G0ICga8NQ="
|
||||
skip_cleanup: true
|
||||
on:
|
||||
tags: true
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017-2019 Armin Sebastian
|
||||
Copyright (c) 2017-2020 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
|
||||
|
||||
234
README.md
234
README.md
@@ -1,11 +1,10 @@
|
||||
# Lock Threads
|
||||
|
||||
[](https://travis-ci.com/dessant/lock-threads)
|
||||
[](https://www.npmjs.com/package/lock-threads)
|
||||
Lock Threads is a GitHub Action that locks closed issues
|
||||
and pull requests after a period of inactivity.
|
||||
|
||||
Lock Threads is a GitHub App inspired by [Stale](https://github.com/probot/stale)
|
||||
and built with [Probot](https://github.com/probot/probot)
|
||||
that locks closed issues and pull requests after a period of inactivity.
|
||||
> The legacy version of this project can be found
|
||||
[here](https://github.com/dessant/lock-threads-app).
|
||||
|
||||

|
||||
|
||||
@@ -20,90 +19,213 @@ please consider contributing with
|
||||
|
||||
## Usage
|
||||
|
||||
1. **[Install the GitHub App](https://github.com/apps/lock)**
|
||||
for the intended repositories
|
||||
2. Create `.github/lock.yml` based on the template below
|
||||
3. It will start scanning for closed issues and/or pull requests within an hour
|
||||
Create a `lock.yml` workflow file in the `.github/workflows` directory,
|
||||
use one of the [example workflows](#examples) to get started.
|
||||
|
||||
**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
|
||||
The action 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`.
|
||||
|
||||
#### Configuration
|
||||
- **`github-token`**
|
||||
- GitHub access token, value must be `${{ github.token }}`
|
||||
- Required
|
||||
- **`issue-lock-inactive-days`**
|
||||
- Number of days of inactivity before a closed issue is locked
|
||||
- Optional, defaults to `365`
|
||||
- **`issue-exclude-created-before`**
|
||||
- Do not lock issues created before a given timestamp,
|
||||
value must follow ISO 8601
|
||||
- Optional, defaults to `''`
|
||||
- **`issue-exclude-labels`**
|
||||
- Do not lock issues with these labels, value must be
|
||||
a comma separated list of labels or `''`
|
||||
- Optional, defaults to `''`
|
||||
- **`issue-lock-labels`**
|
||||
- Labels to add before locking an issue, value must be
|
||||
a comma separated list of labels or `''`
|
||||
- Optional, defaults to `''`
|
||||
- **`issue-lock-comment`**
|
||||
- Comment to post before locking an issue
|
||||
- Optional, defaults to `''`
|
||||
- **`issue-lock-reason`**
|
||||
- Reason for locking an issue, value must be one
|
||||
of `resolved`, `off-topic`, `too heated`, `spam` or `''`
|
||||
- Optional, defaults to `resolved`
|
||||
- **`pr-lock-inactive-days`**
|
||||
- Number of days of inactivity before a closed pull request is locked
|
||||
- Optional, defaults to `365`
|
||||
- **`pr-exclude-created-before`**
|
||||
- Do not lock pull requests created before a given timestamp,
|
||||
value must follow ISO 8601
|
||||
- Optional, defaults to `''`
|
||||
- **`pr-exclude-labels`**
|
||||
- Do not lock pull requests with these labels, value must
|
||||
be a comma separated list of labels or `''`
|
||||
- Optional, defaults to `''`
|
||||
- **`pr-lock-labels`**
|
||||
- Labels to add before locking a pull request, value must be
|
||||
a comma separated list of labels or `''`
|
||||
- Optional, defaults to `''`
|
||||
- **`pr-lock-comment`**
|
||||
- Comment to post before locking a pull request
|
||||
- Optional, defaults to `''`
|
||||
- **`pr-lock-reason`**
|
||||
- Reason for locking a pull request, value must be one
|
||||
of `resolved`, `off-topic`, `too heated`, `spam` or `''`
|
||||
- Optional, defaults to `resolved`
|
||||
- **`process-only`**
|
||||
- Limit locking to only issues or pull requests, value must be
|
||||
one of `issues`, `prs` or `''`
|
||||
- Optional, defaults to `''`
|
||||
|
||||
Create `.github/lock.yml` in the default branch to enable the app,
|
||||
or add it at the same file path to a repository named `.github`.
|
||||
The file can be empty, or it can override any of these default settings:
|
||||
### Outputs
|
||||
|
||||
- **`issues`**
|
||||
- Issues that have been locked, value is a JSON string in the form
|
||||
of `[{"owner": "actions", "repo": "toolkit", "issue_number": 1}]`
|
||||
- Defaults to `''`
|
||||
- **`prs`**
|
||||
- Pull requests that have been locked, value is a JSON string in the form
|
||||
of `[{"owner": "actions", "repo": "toolkit", "issue_number": 1}]`
|
||||
- Defaults to `''`
|
||||
|
||||
## Examples
|
||||
|
||||
The following workflow will search once an hour for closed issues
|
||||
and pull requests that can be locked.
|
||||
|
||||
```yaml
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
name: 'Lock threads'
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 365
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 * * * *'
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
```
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
Edit the workflow after the initial backlog of issues and pull requests
|
||||
has been processed to reduce the frequency of scheduled runs.
|
||||
Running the workflow only once a day helps reduce resource usage.
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
```yaml
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
```
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs.
|
||||
### Available input parameters
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
This workflow declares all the available input parameters of the action
|
||||
and their default values. Any of the parameters can be omitted,
|
||||
except `github-token`.
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
```yaml
|
||||
name: 'Lock threads'
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
jobs:
|
||||
lock:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-inactive-days: '365'
|
||||
issue-exclude-created-before: ''
|
||||
issue-exclude-labels: ''
|
||||
issue-lock-labels: ''
|
||||
issue-lock-comment: ''
|
||||
issue-lock-reason: 'resolved'
|
||||
pr-lock-inactive-days: '365'
|
||||
pr-exclude-created-before: ''
|
||||
pr-exclude-labels: ''
|
||||
pr-lock-labels: ''
|
||||
pr-lock-comment: ''
|
||||
pr-lock-reason: 'resolved'
|
||||
process-only: ''
|
||||
```
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
### Excluding issues and pull requests
|
||||
|
||||
This step will lock only issues, and exclude issues created before 2018,
|
||||
or those with the `upstream` or `help-wanted` labels applied.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-exclude-created-before: '2018-01-01T00:00:00Z'
|
||||
issue-exclude-labels: 'upstream, help-wanted'
|
||||
process-only: 'issues'
|
||||
```
|
||||
|
||||
This step will lock only pull requests, and exclude those
|
||||
with the `wip` label applied.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
pr-exclude-labels: 'wip'
|
||||
process-only: 'prs'
|
||||
```
|
||||
|
||||
### Commenting and labeling
|
||||
|
||||
This step will post a comment on issues and pull requests before locking them,
|
||||
and apply the `outdated` label to issues.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: dessant/lock-threads@v2
|
||||
with:
|
||||
github-token: ${{ github.token }}
|
||||
issue-lock-labels: 'outdated'
|
||||
issue-lock-comment: >
|
||||
This issue has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
pr-lock-comment: >
|
||||
This pull request has been automatically locked since there
|
||||
has not been any recent activity after it was closed.
|
||||
Please open a new issue for related bugs.
|
||||
```
|
||||
|
||||
## How are issues and pull requests determined to be inactive?
|
||||
|
||||
The app uses GitHub's [updated](https://git.io/fhRnE) search qualifier
|
||||
to determine inactivity. Any change to an issue or pull request
|
||||
The action uses GitHub's [updated](https://help.github.com/en/github/searching-for-information-on-github/searching-issues-and-pull-requests#search-by-when-an-issue-or-pull-request-was-created-or-last-updated)
|
||||
search qualifier to determine inactivity. Any change to an issue or pull request
|
||||
is considered an update, including comments, changing labels,
|
||||
applying or removing milestones, or pushing commits.
|
||||
|
||||
An easy way to check and see which issues or pull requests will initially
|
||||
be locked is to add the `updated` search qualifier to either the issue
|
||||
or pull request page filter for your repository:
|
||||
`is:closed is:unlocked updated:<2016-12-20`.
|
||||
Adjust the date to be 365 days ago (or whatever you set for `daysUntilLock`)
|
||||
`is:closed is:unlocked updated:<2018-12-20`.
|
||||
Adjust the date to be 365 days ago (or whatever you set for `*-lock-inactive-days`)
|
||||
to see which issues or pull requests will be locked.
|
||||
|
||||
## Why are only some issues and pull requests processed?
|
||||
|
||||
To avoid triggering abuse prevention mechanisms on GitHub, only 30 issues
|
||||
and pull requests will be handled per hour. If your repository has more
|
||||
To avoid triggering abuse prevention mechanisms on GitHub, only 50 issues
|
||||
and pull requests will be handled at once. If your repository has more
|
||||
than that, it will just take a few hours or days to process them all.
|
||||
|
||||
## Deployment
|
||||
|
||||
See [docs/deploy.md](docs/deploy.md) if you would like to run your own
|
||||
instance of this app.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2017-2019 Armin Sebastian
|
||||
Copyright (c) 2017-2020 Armin Sebastian
|
||||
|
||||
This software is released under the terms of the MIT License.
|
||||
See the [LICENSE](LICENSE) file for further information.
|
||||
|
||||
57
action.yml
Normal file
57
action.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
name: 'Lock Threads'
|
||||
description: 'Lock closed issues and pull requests after a period of inactivity'
|
||||
author: 'Armin Sebastian'
|
||||
inputs:
|
||||
github-token:
|
||||
description: 'GitHub access token'
|
||||
required: true
|
||||
issue-lock-inactive-days:
|
||||
description: 'Number of days of inactivity before a closed issue is locked'
|
||||
default: '365'
|
||||
issue-exclude-created-before:
|
||||
description: 'Do not lock issues created before a given timestamp, value must follow ISO 8601'
|
||||
default: ''
|
||||
issue-exclude-labels:
|
||||
description: 'Do not lock issues with these labels, value must be a comma separated list of labels'
|
||||
default: ''
|
||||
issue-lock-labels:
|
||||
description: 'Labels to add before locking an issue, value must be a comma separated list of labels'
|
||||
default: ''
|
||||
issue-lock-comment:
|
||||
description: 'Comment to post before locking an issue'
|
||||
default: ''
|
||||
issue-lock-reason:
|
||||
description: 'Reason for locking an issue, value must be one of `resolved`, `off-topic`, `too heated` or `spam`'
|
||||
default: 'resolved'
|
||||
pr-lock-inactive-days:
|
||||
description: 'Number of days of inactivity before a closed pull request is locked'
|
||||
default: '365'
|
||||
pr-exclude-created-before:
|
||||
description: 'Do not lock pull requests created before a given timestamp, value must follow ISO 8601'
|
||||
default: ''
|
||||
pr-exclude-labels:
|
||||
description: 'Do not lock pull requests with these labels, value must be a comma separated list of labels'
|
||||
default: ''
|
||||
pr-lock-labels:
|
||||
description: 'Labels to add before locking a pull request, value must be a comma separated list of labels'
|
||||
default: ''
|
||||
pr-lock-comment:
|
||||
description: 'Comment to post before locking a pull request'
|
||||
default: ''
|
||||
pr-lock-reason:
|
||||
description: 'Reason for locking a pull request, value must be one of `resolved`, `off-topic`, `too heated` or `spam`'
|
||||
default: 'resolved'
|
||||
process-only:
|
||||
description: 'Limit locking to only issues or pull requests, value must be one of `issues` or `prs`'
|
||||
default: ''
|
||||
outputs:
|
||||
issues:
|
||||
description: 'Issues that have been locked, value is a JSON string'
|
||||
prs:
|
||||
description: 'Pull requests that have been locked, value is a JSON string'
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
branding:
|
||||
icon: 'lock'
|
||||
color: 'gray-dark'
|
||||
@@ -1,60 +0,0 @@
|
||||
A GitHub App that locks closed issues and pull requests after a period of inactivity.
|
||||
|
||||

|
||||
|
||||
## Supporting the Project
|
||||
|
||||
The continued development of Lock Threads 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=lock-threads&src=app), [PayPal](https://armin.dev/go/paypal?pr=lock-threads&src=app) or [Bitcoin](https://armin.dev/go/bitcoin?pr=lock-threads&src=app).
|
||||
|
||||
## Usage
|
||||
|
||||
1. **[Install the GitHub App](https://github.com/apps/lock)** for the intended repositories
|
||||
2. Create `.github/lock.yml` based on the template below
|
||||
3. It will start scanning for closed issues and/or pull requests within an hour
|
||||
|
||||
**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/lock.yml` in the default branch to enable the app, or add it at the same file path to a repository named `.github`. The file can be empty, or it can override any of these default settings:
|
||||
|
||||
```yaml
|
||||
# Configuration for Lock Threads - https://github.com/dessant/lock-threads
|
||||
|
||||
# Number of days of inactivity before a closed issue or pull request is locked
|
||||
daysUntilLock: 365
|
||||
|
||||
# Skip issues and pull requests created before a given timestamp. Timestamp must
|
||||
# follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable
|
||||
skipCreatedBefore: false
|
||||
|
||||
# Issues and pull requests with these labels will be ignored. Set to `[]` to disable
|
||||
exemptLabels: []
|
||||
|
||||
# Label to add before locking, such as `outdated`. Set to `false` to disable
|
||||
lockLabel: false
|
||||
|
||||
# Comment to post before locking. Set to `false` to disable
|
||||
lockComment: >
|
||||
This thread has been automatically locked since there has not been
|
||||
any recent activity after it was closed. Please open a new issue for
|
||||
related bugs.
|
||||
|
||||
# Assign `resolved` as the reason for locking. Set to `false` to disable
|
||||
setLockReason: true
|
||||
|
||||
# Limit to only `issues` or `pulls`
|
||||
# only: issues
|
||||
|
||||
# Optionally, specify configuration settings just for `issues` or `pulls`
|
||||
# issues:
|
||||
# exemptLabels:
|
||||
# - help-wanted
|
||||
# lockLabel: outdated
|
||||
|
||||
# pulls:
|
||||
# daysUntilLock: 30
|
||||
|
||||
# Repository to extend settings from
|
||||
# _extends: repo
|
||||
```
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 24 KiB |
@@ -1,12 +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**
|
||||
- Pull requests - **Read & Write**
|
||||
- Repository metadata - **Read-only**
|
||||
- Single File - **Read-only**
|
||||
- Path: `.github/lock.yml`
|
||||
4145
package-lock.json
generated
Normal file
4145
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
47
package.json
47
package.json
@@ -1,42 +1,38 @@
|
||||
{
|
||||
"name": "lock-threads",
|
||||
"version": "1.0.1",
|
||||
"description": "A GitHub App built with Probot that locks closed issues and pull requests after a period of inactivity.",
|
||||
"description": "A GitHub Action that locks closed issues and pull requests after a period of inactivity.",
|
||||
"author": "Armin Sebastian",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/dessant/lock-threads",
|
||||
"repository": {
|
||||
"url": "https://github.com/dessant/lock-threads.git",
|
||||
"type": "git"
|
||||
},
|
||||
"bugs": "https://github.com/dessant/lock-threads/issues",
|
||||
"bugs": {
|
||||
"url": "https://github.com/dessant/lock-threads/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",
|
||||
"update": "ncu --upgrade && npm install",
|
||||
"package": "ncc build src/index.js -o dist",
|
||||
"push": "git push --tags origin master",
|
||||
"release": "standard-version"
|
||||
},
|
||||
"files": [
|
||||
"docs",
|
||||
"src"
|
||||
],
|
||||
"dependencies": {
|
||||
"@hapi/joi": "^16.1.7",
|
||||
"probot": "^9.6.0",
|
||||
"probot-messages": "^1.0.1",
|
||||
"probot-scheduler": "dessant/scheduler#cbb84f6",
|
||||
"uuid": "^3.3.3"
|
||||
"@actions/core": "^1.2.2",
|
||||
"@actions/github": "^2.1.0",
|
||||
"@hapi/joi": "^17.1.0",
|
||||
"decamelize": "^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"
|
||||
"@zeit/ncc": "^0.21.1",
|
||||
"npm-check-updates": "^4.0.1",
|
||||
"prettier": "^1.19.1",
|
||||
"standard-version": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.12.0"
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"keywords": [
|
||||
"github",
|
||||
@@ -44,12 +40,9 @@
|
||||
"pull requests",
|
||||
"lock",
|
||||
"automation",
|
||||
"github app",
|
||||
"probot",
|
||||
"probot app",
|
||||
"github actions",
|
||||
"project management",
|
||||
"bot"
|
||||
],
|
||||
"resolutions": {
|
||||
"**/@octokit/endpoint": "^5.3.6"
|
||||
}
|
||||
"private": true
|
||||
}
|
||||
|
||||
181
src/index.js
181
src/index.js
@@ -1,57 +1,152 @@
|
||||
const uuidV4 = require('uuid/v4');
|
||||
const createScheduler = require('probot-scheduler');
|
||||
const sendMessage = require('probot-messages');
|
||||
const core = require('@actions/core');
|
||||
const github = require('@actions/github');
|
||||
const decamelize = require('decamelize');
|
||||
|
||||
const App = require('./lock');
|
||||
const schema = require('./schema');
|
||||
|
||||
module.exports = async robot => {
|
||||
const github = await robot.auth();
|
||||
const appName = (await github.apps.getAuthenticated()).data.name;
|
||||
const scheduler = createScheduler(robot);
|
||||
async function run() {
|
||||
try {
|
||||
const config = getConfig();
|
||||
const client = new github.GitHub(config.githubToken);
|
||||
|
||||
robot.on('schedule.repository', async context => {
|
||||
const app = await getApp(context);
|
||||
if (app) {
|
||||
await app.lockThreads();
|
||||
}
|
||||
});
|
||||
const app = new App(config, client);
|
||||
await app.lockThreads();
|
||||
} catch (err) {
|
||||
core.setFailed(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
class App {
|
||||
constructor(config, client) {
|
||||
this.config = config;
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
async lockThreads() {
|
||||
const type = this.config.processOnly;
|
||||
const threadTypes = type ? [type] : ['issue', 'pr'];
|
||||
|
||||
for (const item of threadTypes) {
|
||||
const threads = await this.lock(item);
|
||||
core.debug(`Setting output (${item}s)`);
|
||||
core.setOutput(`${item}s`, threads.length ? JSON.stringify(threads) : '');
|
||||
}
|
||||
}
|
||||
|
||||
async function getConfig(context, log, file = 'lock.yml') {
|
||||
let config;
|
||||
const repo = context.repo();
|
||||
try {
|
||||
let repoConfig = await context.config(file);
|
||||
if (!repoConfig) {
|
||||
repoConfig = {perform: false};
|
||||
async lock(type) {
|
||||
const repo = github.context.repo;
|
||||
const lockLabels = this.config[type + 'LockLabels'];
|
||||
const lockComment = this.config[type + 'LockComment'];
|
||||
const lockReason = this.config[type + 'LockReason'];
|
||||
|
||||
const threads = [];
|
||||
|
||||
const results = await this.search(type);
|
||||
for (const result of results) {
|
||||
const issue = {...repo, issue_number: result.number};
|
||||
|
||||
if (lockComment) {
|
||||
core.debug(`Commenting (${type}: ${issue.issue_number})`);
|
||||
await this.client.issues.createComment({
|
||||
...issue,
|
||||
body: lockComment
|
||||
});
|
||||
}
|
||||
const {error, value} = schema.validate(repoConfig);
|
||||
if (error) {
|
||||
throw error;
|
||||
|
||||
if (lockLabels) {
|
||||
core.debug(`Labeling (${type}: ${issue.issue_number})`);
|
||||
await this.client.issues.addLabels({
|
||||
...issue,
|
||||
labels: lockLabels
|
||||
});
|
||||
}
|
||||
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.'}
|
||||
);
|
||||
|
||||
core.debug(`Locking (${type}: ${issue.issue_number})`);
|
||||
let params;
|
||||
if (lockReason) {
|
||||
params = {
|
||||
...issue,
|
||||
lock_reason: lockReason,
|
||||
headers: {
|
||||
accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
params = issue;
|
||||
}
|
||||
await this.client.issues.lock(params);
|
||||
|
||||
threads.push(issue);
|
||||
}
|
||||
|
||||
return config;
|
||||
return threads;
|
||||
}
|
||||
};
|
||||
|
||||
async search(type) {
|
||||
const {owner, repo} = github.context.repo;
|
||||
const timestamp = this.getUpdatedTimestamp(
|
||||
this.config[type + 'LockInactiveDays']
|
||||
);
|
||||
let query = `repo:${owner}/${repo} updated:<${timestamp} is:closed is:unlocked`;
|
||||
|
||||
const excludeLabels = this.config[type + 'ExcludeLabels'];
|
||||
if (excludeLabels) {
|
||||
const queryPart = excludeLabels
|
||||
.map(label => `-label:"${label}"`)
|
||||
.join(' ');
|
||||
query += ` ${queryPart}`;
|
||||
}
|
||||
|
||||
const excludeCreatedBefore = this.config[type + 'ExcludeCreatedBefore'];
|
||||
if (excludeCreatedBefore) {
|
||||
query += ` created:>${this.getISOTimestamp(excludeCreatedBefore)}`;
|
||||
}
|
||||
|
||||
if (type === 'issue') {
|
||||
query += ' is:issue';
|
||||
} else {
|
||||
query += ' is:pr';
|
||||
}
|
||||
|
||||
core.debug(`Searching (${type}s)`);
|
||||
const results = (
|
||||
await this.client.search.issuesAndPullRequests({
|
||||
q: query,
|
||||
sort: 'updated',
|
||||
order: 'desc',
|
||||
per_page: 50
|
||||
})
|
||||
).data.items;
|
||||
|
||||
// results may include locked issues
|
||||
return results.filter(issue => !issue.locked);
|
||||
}
|
||||
|
||||
getUpdatedTimestamp(days) {
|
||||
const ttl = days * 24 * 60 * 60 * 1000;
|
||||
const date = new Date(new Date() - ttl);
|
||||
return this.getISOTimestamp(date);
|
||||
}
|
||||
|
||||
getISOTimestamp(date) {
|
||||
return date.toISOString().split('.')[0] + 'Z';
|
||||
}
|
||||
}
|
||||
|
||||
function getConfig() {
|
||||
const input = Object.fromEntries(
|
||||
Object.keys(schema.describe().keys).map(item => [
|
||||
item,
|
||||
core.getInput(decamelize(item, '-'))
|
||||
])
|
||||
);
|
||||
|
||||
const {error, value} = schema.validate(input, {abortEarly: false});
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
116
src/lock.js
116
src/lock.js
@@ -1,116 +0,0 @@
|
||||
module.exports = class Lock {
|
||||
constructor(context, config, logger) {
|
||||
this.context = context;
|
||||
this.config = config;
|
||||
this.log = logger;
|
||||
}
|
||||
|
||||
async lockThreads() {
|
||||
const {only: type} = this.config;
|
||||
if (type) {
|
||||
await this.lock(type);
|
||||
} else {
|
||||
await this.lock('issues');
|
||||
await this.lock('pulls');
|
||||
}
|
||||
}
|
||||
|
||||
async lock(type) {
|
||||
const repo = this.context.repo();
|
||||
const lockLabel = this.getConfigValue(type, 'lockLabel');
|
||||
const lockComment = this.getConfigValue(type, 'lockComment');
|
||||
const setLockReason = this.getConfigValue(type, 'setLockReason');
|
||||
|
||||
const results = await this.search(type);
|
||||
for (const result of results) {
|
||||
const issue = {...repo, issue_number: result.number};
|
||||
|
||||
if (lockComment) {
|
||||
this.log.info({issue}, 'Commenting');
|
||||
await this.context.github.issues.createComment({
|
||||
...issue,
|
||||
body: lockComment
|
||||
});
|
||||
}
|
||||
|
||||
if (lockLabel) {
|
||||
this.log.info({issue}, 'Labeling');
|
||||
await this.context.github.issues.addLabels({
|
||||
...issue,
|
||||
labels: [lockLabel]
|
||||
});
|
||||
}
|
||||
|
||||
this.log.info({issue}, 'Locking');
|
||||
let params;
|
||||
if (setLockReason) {
|
||||
params = {
|
||||
...issue,
|
||||
lock_reason: 'resolved',
|
||||
headers: {
|
||||
accept: 'application/vnd.github.sailor-v-preview+json'
|
||||
}
|
||||
};
|
||||
} else {
|
||||
params = issue;
|
||||
}
|
||||
await this.context.github.issues.lock(params);
|
||||
}
|
||||
}
|
||||
|
||||
async search(type) {
|
||||
const {owner, repo} = this.context.repo();
|
||||
const daysUntilLock = this.getConfigValue(type, 'daysUntilLock');
|
||||
const exemptLabels = this.getConfigValue(type, 'exemptLabels');
|
||||
const skipCreatedBefore = this.getConfigValue(type, 'skipCreatedBefore');
|
||||
|
||||
const timestamp = this.getUpdatedTimestamp(daysUntilLock);
|
||||
|
||||
let query = `repo:${owner}/${repo} updated:<${timestamp} is:closed is:unlocked`;
|
||||
|
||||
if (exemptLabels.length) {
|
||||
const queryPart = exemptLabels
|
||||
.map(label => `-label:"${label}"`)
|
||||
.join(' ');
|
||||
query += ` ${queryPart}`;
|
||||
}
|
||||
|
||||
if (skipCreatedBefore) {
|
||||
query += ` created:>${this.getISOTimestamp(skipCreatedBefore)}`;
|
||||
}
|
||||
|
||||
if (type === 'issues') {
|
||||
query += ' is:issue';
|
||||
} else {
|
||||
query += ' is:pr';
|
||||
}
|
||||
|
||||
this.log.info({repo: {owner, repo}}, `Searching ${type}`);
|
||||
const results = (await this.context.github.search.issuesAndPullRequests({
|
||||
q: query,
|
||||
sort: 'updated',
|
||||
order: 'desc',
|
||||
per_page: 30
|
||||
})).data.items;
|
||||
|
||||
// `is:unlocked` search qualifier is undocumented, skip locked issues
|
||||
return results.filter(issue => !issue.locked);
|
||||
}
|
||||
|
||||
getUpdatedTimestamp(days) {
|
||||
const ttl = days * 24 * 60 * 60 * 1000;
|
||||
const date = new Date(new Date() - ttl);
|
||||
return this.getISOTimestamp(date);
|
||||
}
|
||||
|
||||
getISOTimestamp(date) {
|
||||
return date.toISOString().split('.')[0] + 'Z';
|
||||
}
|
||||
|
||||
getConfigValue(type, key) {
|
||||
if (this.config[type] && typeof this.config[type][key] !== 'undefined') {
|
||||
return this.config[type][key];
|
||||
}
|
||||
return this.config[key];
|
||||
}
|
||||
};
|
||||
207
src/schema.js
207
src/schema.js
@@ -1,84 +1,175 @@
|
||||
const Joi = require('@hapi/joi');
|
||||
|
||||
const fields = {
|
||||
daysUntilLock: Joi.number()
|
||||
.min(1)
|
||||
.max(3650)
|
||||
.description(
|
||||
'Number of days of inactivity before a closed issue or pull request is locked'
|
||||
),
|
||||
const extendedJoi = Joi.extend({
|
||||
type: 'stringList',
|
||||
base: Joi.array(),
|
||||
coerce: {
|
||||
from: 'string',
|
||||
method(value) {
|
||||
value = value.trim();
|
||||
if (value) {
|
||||
value = value
|
||||
.split(',')
|
||||
.map(item => item.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
skipCreatedBefore: Joi.alternatives()
|
||||
return {value};
|
||||
}
|
||||
}
|
||||
}).extend({
|
||||
type: 'processOnly',
|
||||
base: Joi.string(),
|
||||
coerce: {
|
||||
from: 'string',
|
||||
method(value) {
|
||||
value = value.trim();
|
||||
if (['issues', 'prs'].includes(value)) {
|
||||
value = value.slice(0, -1);
|
||||
}
|
||||
|
||||
return {value};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const schema = Joi.object({
|
||||
githubToken: Joi.string()
|
||||
.trim()
|
||||
.max(100),
|
||||
|
||||
issueLockInactiveDays: Joi.number()
|
||||
.min(0)
|
||||
.max(3650)
|
||||
.precision(9)
|
||||
.default(365),
|
||||
|
||||
issueExcludeCreatedBefore: Joi.alternatives()
|
||||
.try(
|
||||
Joi.date()
|
||||
.iso()
|
||||
// .iso()
|
||||
.min('1970-01-01T00:00:00Z')
|
||||
.max('2970-12-31T23:59:59Z'),
|
||||
Joi.boolean().only(false)
|
||||
)
|
||||
.description(
|
||||
'Skip issues and pull requests created before a given timestamp. Timestamp ' +
|
||||
'must follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable'
|
||||
),
|
||||
|
||||
exemptLabels: Joi.array()
|
||||
.single()
|
||||
.items(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(50)
|
||||
.valid('')
|
||||
)
|
||||
.description(
|
||||
'Issues and pull requests with these labels will not be locked. Set to `[]` to disable'
|
||||
),
|
||||
.default(''),
|
||||
|
||||
lockLabel: Joi.alternatives()
|
||||
issueExcludeLabels: Joi.alternatives()
|
||||
.try(
|
||||
extendedJoi
|
||||
.stringList()
|
||||
.items(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(50)
|
||||
)
|
||||
.min(1)
|
||||
.max(30)
|
||||
.unique(),
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(50),
|
||||
Joi.boolean().only(false)
|
||||
.valid('')
|
||||
)
|
||||
.description(
|
||||
'Label to add before locking, such as `outdated`. Set to `false` to disable'
|
||||
),
|
||||
.default(''),
|
||||
|
||||
lockComment: Joi.alternatives()
|
||||
issueLockLabels: Joi.alternatives()
|
||||
.try(
|
||||
extendedJoi
|
||||
.stringList()
|
||||
.items(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(50)
|
||||
)
|
||||
.min(1)
|
||||
.max(30)
|
||||
.unique(),
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(10000),
|
||||
Joi.boolean().only(false)
|
||||
.valid('')
|
||||
)
|
||||
.description('Comment to post before locking. Set to `false` to disable'),
|
||||
.default(''),
|
||||
|
||||
setLockReason: Joi.boolean().description(
|
||||
'Assign `resolved` as the reason for locking. Set to `false` to disable'
|
||||
)
|
||||
};
|
||||
issueLockComment: Joi.string()
|
||||
.trim()
|
||||
.max(10000)
|
||||
.allow('')
|
||||
.default(''),
|
||||
|
||||
const schema = Joi.object().keys({
|
||||
daysUntilLock: fields.daysUntilLock.default(365),
|
||||
skipCreatedBefore: fields.skipCreatedBefore.default(false),
|
||||
exemptLabels: fields.exemptLabels.default([]),
|
||||
lockLabel: fields.lockLabel.default(false),
|
||||
lockComment: fields.lockComment.default(
|
||||
'This thread has been automatically locked since there has not been ' +
|
||||
'any recent activity after it was closed. Please open a new issue for ' +
|
||||
'related bugs.'
|
||||
),
|
||||
setLockReason: fields.setLockReason.default(true),
|
||||
only: Joi.string()
|
||||
issueLockReason: Joi.string()
|
||||
.valid('resolved', 'off-topic', 'too heated', 'spam', '')
|
||||
.default(''),
|
||||
|
||||
prLockInactiveDays: Joi.number()
|
||||
.min(0)
|
||||
.max(3650)
|
||||
.precision(9)
|
||||
.default(365),
|
||||
|
||||
prExcludeCreatedBefore: Joi.alternatives()
|
||||
.try(
|
||||
Joi.date()
|
||||
// .iso()
|
||||
.min('1970-01-01T00:00:00Z')
|
||||
.max('2970-12-31T23:59:59Z'),
|
||||
Joi.string()
|
||||
.trim()
|
||||
.valid('')
|
||||
)
|
||||
.default(''),
|
||||
|
||||
prExcludeLabels: Joi.alternatives()
|
||||
.try(
|
||||
extendedJoi
|
||||
.stringList()
|
||||
.items(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(50)
|
||||
)
|
||||
.min(1)
|
||||
.max(30)
|
||||
.unique(),
|
||||
Joi.string()
|
||||
.trim()
|
||||
.valid('')
|
||||
)
|
||||
.default(''),
|
||||
|
||||
prLockLabels: Joi.alternatives()
|
||||
.try(
|
||||
extendedJoi
|
||||
.stringList()
|
||||
.items(
|
||||
Joi.string()
|
||||
.trim()
|
||||
.max(50)
|
||||
)
|
||||
.min(1)
|
||||
.max(30)
|
||||
.unique(),
|
||||
Joi.string()
|
||||
.trim()
|
||||
.valid('')
|
||||
)
|
||||
.default(''),
|
||||
|
||||
prLockComment: 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)
|
||||
.max(10000)
|
||||
.allow('')
|
||||
.default(''),
|
||||
|
||||
prLockReason: Joi.string()
|
||||
.valid('resolved', 'off-topic', 'too heated', 'spam', '')
|
||||
.default(''),
|
||||
|
||||
processOnly: extendedJoi
|
||||
.processOnly()
|
||||
.valid('issue', 'pr', '')
|
||||
.default('')
|
||||
});
|
||||
|
||||
module.exports = schema;
|
||||
|
||||
Reference in New Issue
Block a user