feat: move to GitHub Actions

BREAKING CHANGE: The deployment method and configuration options have changed.
This commit is contained in:
dessant
2020-02-17 08:56:39 +02:00
parent 4f8b60f58f
commit eb01523c24
21 changed files with 4728 additions and 5376 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=

14
.github/workflows/lock.yml vendored Normal file
View 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
View 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
View File

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

2
.nvmrc
View File

@@ -1 +1 @@
12.13.0
12.14.1

View File

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

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: "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

View File

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

@@ -1,11 +1,10 @@
# Lock Threads
[![Build Status](https://img.shields.io/travis/com/dessant/lock-threads/master.svg)](https://travis-ci.com/dessant/lock-threads)
[![Version](https://img.shields.io/npm/v/lock-threads.svg?colorB=007EC6)](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).
![](assets/screenshot.png)
@@ -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
View 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'

View File

@@ -1,60 +0,0 @@
A GitHub App that locks closed issues and pull requests after a period of inactivity.
![](https://raw.githubusercontent.com/dessant/lock-threads/master/assets/screenshot.png)
## 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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

4947
yarn.lock

File diff suppressed because it is too large Load Diff