Skip to content

Commit ecb0e91

Browse files
author
dessant
committed
feat: validate config and set custom options for issues and pulls
1 parent 7a737c7 commit ecb0e91

8 files changed

Lines changed: 169 additions & 48 deletions

File tree

README.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,30 @@ The file can be empty, or it can override any of these default settings:
3030

3131
# Number of days of inactivity before a closed issue or pull request is locked
3232
daysUntilLock: 365
33+
34+
# Issues and pull requests with these labels will not be locked. Set to `[]` to disable
35+
exemptLabels: []
36+
37+
# Label to add before locking, such as `outdated`. Set to `false` to disable
38+
lockLabel: false
39+
3340
# Comment to post before locking. Set to `false` to disable
3441
lockComment: >
35-
This thread has been automatically locked because it has not had recent
36-
activity. Please open a new issue for related bugs and link to relevant
37-
comments in this thread.
38-
# Issues or pull requests with these labels will not be locked
39-
# exemptLabels:
40-
# - no-locking
42+
This thread has been automatically locked since there has not been
43+
any recent activity after it was closed. Please open a new issue for
44+
related bugs.
45+
4146
# Limit to only `issues` or `pulls`
4247
# only: issues
43-
# Add a label when locking. Set to `false` to disable
44-
# lockLabel: outdated
48+
49+
# Optionally, specify configuration settings just for `issues` or `pulls`
50+
# issues:
51+
# exemptLabels:
52+
# - help-wanted
53+
# lockLabel: outdated
54+
55+
# pulls:
56+
# daysUntilLock: 30
4557
```
4658

4759
## How are issues and pull requests determined to be inactive?

assets/app-description.md

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,30 @@ Create `.github/lock.yml` in the default branch to enable the app. The file can
1717

1818
# Number of days of inactivity before a closed issue or pull request is locked
1919
daysUntilLock: 365
20+
21+
# Issues and pull requests with these labels will not be locked. Set to `[]` to disable
22+
exemptLabels: []
23+
24+
# Label to add before locking, such as `outdated`. Set to `false` to disable
25+
lockLabel: false
26+
2027
# Comment to post before locking. Set to `false` to disable
2128
lockComment: >
22-
This thread has been automatically locked because it has not had recent
23-
activity. Please open a new issue for related bugs and link to relevant
24-
comments in this thread.
25-
# Issues or pull requests with these labels will not be locked
26-
# exemptLabels:
27-
# - no-locking
29+
This thread has been automatically locked since there has not been
30+
any recent activity after it was closed. Please open a new issue for
31+
related bugs.
32+
2833
# Limit to only `issues` or `pulls`
2934
# only: issues
30-
# Add a label when locking. Set to `false` to disable
31-
# lockLabel: outdated
35+
36+
# Optionally, specify configuration settings just for `issues` or `pulls`
37+
# issues:
38+
# exemptLabels:
39+
# - help-wanted
40+
# lockLabel: outdated
41+
42+
# pulls:
43+
# daysUntilLock: 30
3244
```
3345

3446
## Supporting the Project

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"release": "standard-version && yarn run push"
1414
},
1515
"dependencies": {
16+
"joi": "^13.2.0",
1617
"probot": "^6.1.0",
1718
"probot-scheduler": "^1.1.0"
1819
},

src/defaults.js

Lines changed: 0 additions & 9 deletions
This file was deleted.

src/index.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,47 @@
11
const createScheduler = require('probot-scheduler');
22

33
const App = require('./lock');
4+
const schema = require('./schema');
45

56
module.exports = robot => {
67
scheduler = createScheduler(robot);
78

89
robot.on('schedule.repository', async context => {
910
const app = await getApp(context);
1011
if (app) {
11-
await app.lock();
12+
const {only: type} = app.config;
13+
if (type) {
14+
await app.lock(type);
15+
} else {
16+
await app.lock('issues');
17+
await app.lock('pulls');
18+
}
1219
}
1320
});
1421

1522
async function getApp(context) {
16-
let config = await context.config('lock.yml');
23+
const config = await getConfig(context);
1724
if (config) {
1825
return new App(context, config, robot.log);
1926
}
2027
}
28+
29+
async function getConfig(context) {
30+
const {owner, repo} = context.issue();
31+
let config;
32+
try {
33+
const repoConfig = await context.config('lock.yml');
34+
if (repoConfig) {
35+
const {error, value} = schema.validate(repoConfig);
36+
if (error) {
37+
throw error;
38+
}
39+
config = value;
40+
}
41+
} catch (err) {
42+
robot.log.warn({err: new Error(err), owner, repo}, 'Invalid config');
43+
}
44+
45+
return config;
46+
}
2147
};

src/lock.js

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
1-
const defaults = require('./defaults');
2-
31
module.exports = class Lock {
42
constructor(context, config, logger) {
53
this.context = context;
6-
this.config = Object.assign({}, defaults, config);
4+
this.config = config;
75
this.logger = logger;
86
}
97

10-
async lock() {
8+
async lock(type) {
119
const {owner, repo} = this.context.repo();
12-
const {lockComment, lockLabel} = this.config;
10+
const lockLabel = this.getConfigValue(type, 'lockLabel');
11+
const lockComment = this.getConfigValue(type, 'lockComment');
1312

14-
const issues = await this.getLockableIssues();
13+
const issues = await this.getLockableIssues(type);
1514
for (const issue of issues) {
1615
const issueUrl = `${owner}/${repo}/issues/${issue.number}`;
1716
if (lockComment) {
@@ -47,17 +46,12 @@ module.exports = class Lock {
4746
}
4847
}
4948

50-
async getLockableIssues() {
51-
const results = await this.search();
52-
return results.data.items.filter(issue => !issue.locked);
53-
}
54-
55-
search() {
49+
search(type) {
5650
const {owner, repo} = this.context.repo();
57-
const {exemptLabels, daysUntilLock, only} = this.config;
58-
const timestamp = this.since(daysUntilLock)
59-
.toISOString()
60-
.replace(/\.\d{3}\w$/, '');
51+
const daysUntilLock = this.getConfigValue(type, 'daysUntilLock');
52+
const exemptLabels = this.getConfigValue(type, 'exemptLabels');
53+
54+
const timestamp = this.getUpdatedTimestamp(daysUntilLock);
6155

6256
let query = `repo:${owner}/${repo} is:closed updated:<${timestamp}`;
6357
if (exemptLabels.length) {
@@ -66,13 +60,13 @@ module.exports = class Lock {
6660
.join(' ');
6761
query += ` ${queryPart}`;
6862
}
69-
if (only === 'issues') {
63+
if (type === 'issues') {
7064
query += ' is:issue';
71-
} else if (only === 'pulls') {
65+
} else {
7266
query += ' is:pr';
7367
}
7468

75-
this.logger.info(`[${owner}/${repo}] Searching`);
69+
this.logger.info(`[${owner}/${repo}] Searching ${type}`);
7670
return this.context.github.search.issues({
7771
q: query,
7872
sort: 'updated',
@@ -81,8 +75,21 @@ module.exports = class Lock {
8175
});
8276
}
8377

84-
since(days) {
78+
async getLockableIssues(type) {
79+
const results = await this.search(type);
80+
return results.data.items.filter(issue => !issue.locked);
81+
}
82+
83+
getUpdatedTimestamp(days) {
8584
const ttl = days * 24 * 60 * 60 * 1000;
86-
return new Date(new Date() - ttl);
85+
const date = new Date(new Date() - ttl);
86+
return date.toISOString().replace(/\.\d{3}\w$/, '');
87+
}
88+
89+
getConfigValue(type, key) {
90+
if (this.config[type] && typeof this.config[type][key] !== 'undefined') {
91+
return this.config[type][key];
92+
}
93+
return this.config[key];
8794
}
8895
};

src/schema.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const Joi = require('joi');
2+
3+
const fields = {
4+
daysUntilLock: Joi.number()
5+
.min(1)
6+
.description(
7+
'Number of days of inactivity before a closed issue or pull request is locked'
8+
),
9+
10+
exemptLabels: Joi.array()
11+
.single()
12+
.items(Joi.string())
13+
.description(
14+
'Issues and pull requests with these labels will not be locked. Set to `[]` to disable'
15+
),
16+
17+
lockLabel: Joi.alternatives()
18+
.try(Joi.string(), Joi.boolean().only(false))
19+
.description(
20+
'Label to add before locking, such as `outdated`. Set to `false` to disable'
21+
),
22+
23+
lockComment: Joi.alternatives()
24+
.try(Joi.string(), Joi.boolean().only(false))
25+
.description('Comment to post before locking. Set to `false` to disable')
26+
};
27+
28+
const schema = Joi.object().keys({
29+
daysUntilLock: fields.daysUntilLock.default(365),
30+
exemptLabels: fields.exemptLabels.default([]),
31+
lockLabel: fields.lockLabel.default(false),
32+
lockComment: fields.lockComment.default(
33+
'This thread has been automatically locked since there has not been ' +
34+
'any recent activity after it was closed. Please open a new issue for ' +
35+
'related bugs.'
36+
),
37+
only: Joi.string()
38+
.valid('issues', 'pulls')
39+
.description('Limit to only `issues` or `pulls`'),
40+
pulls: Joi.object().keys(fields),
41+
issues: Joi.object().keys(fields)
42+
});
43+
44+
module.exports = schema;

yarn.lock

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1664,6 +1664,10 @@ hoek@4.x.x:
16641664
version "4.2.0"
16651665
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
16661666

1667+
hoek@5.x.x:
1668+
version "5.0.3"
1669+
resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.3.tgz#b71d40d943d0a95da01956b547f83c4a5b4a34ac"
1670+
16671671
hosted-git-info@^2.1.4, hosted-git-info@^2.1.5, hosted-git-info@^2.4.2:
16681672
version "2.5.0"
16691673
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c"
@@ -1975,6 +1979,12 @@ isarray@1.0.0, isarray@~1.0.0:
19751979
version "1.0.0"
19761980
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
19771981

1982+
isemail@3.x.x:
1983+
version "3.1.2"
1984+
resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.2.tgz#937cf919002077999a73ea8b1951d590e84e01dd"
1985+
dependencies:
1986+
punycode "2.x.x"
1987+
19781988
isexe@^2.0.0:
19791989
version "2.0.0"
19801990
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@@ -1997,6 +2007,14 @@ jju@^1.1.0:
19972007
version "1.3.0"
19982008
resolved "https://registry.yarnpkg.com/jju/-/jju-1.3.0.tgz#dadd9ef01924bc728b03f2f7979bdbd62f7a2aaa"
19992009

2010+
joi@^13.2.0:
2011+
version "13.2.0"
2012+
resolved "https://registry.yarnpkg.com/joi/-/joi-13.2.0.tgz#72307f1765bb40b068361f9368a4ba1092b8478e"
2013+
dependencies:
2014+
hoek "5.x.x"
2015+
isemail "3.x.x"
2016+
topo "3.x.x"
2017+
20002018
js-yaml@^3.6.1:
20012019
version "3.11.0"
20022020
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
@@ -3128,6 +3146,10 @@ pstree.remy@^1.1.0:
31283146
dependencies:
31293147
ps-tree "^1.1.0"
31303148

3149+
punycode@2.x.x:
3150+
version "2.1.0"
3151+
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
3152+
31313153
punycode@^1.4.1:
31323154
version "1.4.1"
31333155
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
@@ -3977,6 +3999,12 @@ to-regex@^3.0.1, to-regex@^3.0.2:
39773999
regex-not "^1.0.2"
39784000
safe-regex "^1.1.0"
39794001

4002+
topo@3.x.x:
4003+
version "3.0.0"
4004+
resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.0.tgz#37e48c330efeac784538e0acd3e62ca5e231fe7a"
4005+
dependencies:
4006+
hoek "5.x.x"
4007+
39804008
touch@^3.1.0:
39814009
version "3.1.0"
39824010
resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b"

0 commit comments

Comments
 (0)