Skip to content

Commit b22060d

Browse files
authored
refactor: modernize code (#120)
BREAKING CHANGE: Project is now ESM BREAKING CHANGE: Now requires Probot v14 or later BREAKING CHANGE: Requires Node 20, or 22 and later
1 parent 47a1963 commit b22060d

File tree

11 files changed

+5098
-7775
lines changed

11 files changed

+5098
-7775
lines changed

.github/workflows/release.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: Release
2-
"on":
2+
on:
33
push:
44
branches:
55
- master
@@ -11,13 +11,17 @@ jobs:
1111
name: release
1212
runs-on: ubuntu-latest
1313
steps:
14-
- uses: actions/checkout@v2
15-
- uses: actions/setup-node@v2
14+
- uses: actions/checkout@v5
15+
with:
16+
persist-credentials: true
17+
- uses: actions/setup-node@v6
1618
with:
1719
node-version: lts/*
1820
cache: npm
19-
- run: npm ci
20-
- run: npx semantic-release
21+
- name: Install dependencies
22+
run: npm ci
23+
- name: Release
24+
run: npx semantic-release
2125
env:
2226
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2327
NPM_TOKEN: ${{ secrets.PROBOTBOT_NPM_TOKEN }}

.github/workflows/test.yml

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: Test
2-
"on":
2+
on:
33
push:
44
branches:
55
- master
@@ -8,13 +8,37 @@ name: Test
88
- opened
99
- synchronize
1010
jobs:
11-
build:
11+
test:
12+
name: Test on Node.js ${{ matrix.node-version }}
1213
runs-on: ubuntu-latest
14+
strategy:
15+
matrix:
16+
node-version: [20, 22, 24]
1317
steps:
14-
- uses: actions/checkout@v2
15-
- uses: actions/setup-node@v2
18+
- uses: actions/checkout@v5
1619
with:
17-
node-version: 16
20+
persist-credentials: false
21+
- uses: actions/setup-node@v6
22+
with:
23+
node-version: ${{ matrix.node-version }}
24+
cache: npm
25+
- name: Install dependencies
26+
run: npm ci
27+
- name: Run tests
28+
run: npm run test
29+
30+
lint:
31+
name: Lint
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v5
35+
with:
36+
persist-credentials: false
37+
- uses: actions/setup-node@v6
38+
with:
39+
node-version: 24
1840
cache: npm
19-
- run: npm install
20-
- run: npm test
41+
- name: Install dependencies
42+
run: npm install
43+
- name: Lint
44+
run: npm run lint

README.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,26 @@ A [Probot](https://github.com/probot/probot) extension to store metadata on Issu
55
## Usage
66

77
```js
8-
const metadata = require('probot-metadata');
8+
import metadata from "probot-metadata";
99

1010
// where `context` is a Probot `Context`
11-
await metadata(context).set(key, value)
11+
await metadata(context).set(key, value);
1212

13-
const value = await metadata(context).get(key)
13+
const value = await metadata(context).get(key);
1414
```
1515

1616
## Example
1717

1818
```js
19-
const metadata = require('probot-metadata');
19+
import metadata from "probot-metadata";
2020

21-
module.exports = robot => {
22-
robot.on('issue_comment.created', async context => {
23-
match = context.payload.comment.body.match('/snooze (.*)')
24-
if(match) {
25-
metadata(context).set('snooze', match[1])
21+
export default function (robot) {
22+
robot.on("issue_comment.created", async (context) => {
23+
match = context.payload.comment.body.match("/snooze (.*)");
24+
if (match) {
25+
metadata(context).set("snooze", match[1]);
2626
}
27-
})
27+
});
2828
}
2929
```
3030

eslint.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import neostandard from 'neostandard'
2+
3+
export default neostandard({})

index.d.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
1-
import { Context } from "probot";
2-
3-
type StringOrNumber = number | string;
4-
type Key =
5-
| { [key: string]: StringOrNumber }
6-
| StringOrNumber[]
7-
| StringOrNumber;
8-
type Value = Key;
9-
10-
declare function metadata(
11-
context: Context,
12-
issue?: { body: string; [key: string]: any }
13-
): {
14-
get(key?: Key): Promise<any>;
15-
set(key: Key, value: Value): Promise<any>;
1+
export default metadata;
2+
export type StringOrNumber = string | number;
3+
export type Key = Record<string, StringOrNumber> | StringOrNumber | StringOrNumber[];
4+
export type Value = Key;
5+
export type IssueOption = {
6+
owner: string;
7+
repo: string;
8+
issue_number: number;
9+
body?: string;
1610
};
17-
18-
export = metadata;
11+
export type ProbotMetadata = {
12+
get: (key?: Key) => Promise<Value | undefined>;
13+
set: (key?: Key, value?: Value) => Promise<void>;
14+
};
15+
export type ProbotMetadataConstructor = (context: import("probot").Context<"issue_comment">, issue?: IssueOption) => ProbotMetadata;
16+
export const metadata: ProbotMetadataConstructor;

index.js

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,89 @@
1-
const regex = /(\n\n|\r\n)<!-- probot = (.*) -->/
1+
const probotMetadataRegex = /(?:\n\n|\r\n)<!-- probot = (.*) -->/
2+
const prototypePollutionKeys = /** @type {const} */(['__proto__', 'constructor', 'prototype'])
23

3-
module.exports = (context, issue = null) => {
4-
const github = context.octokit || context.github
5-
const prefix = context.payload.installation.id
4+
const metadata = /** @type {ProbotMetadataConstructor} */ (context, issue) => {
5+
const octokit = context.octokit
6+
const prefix = /** @type {{ id: number; node_id: string; }} */ (context.payload.installation).id
67

78
if (!issue) issue = context.issue()
89

910
return {
10-
async get (key = null) {
11+
async get (key) {
1112
let body = issue.body
1213

1314
if (!body) {
14-
body = (await github.issues.get(issue)).data.body || ''
15+
body = (await octokit.rest.issues.get(issue)).data.body || ''
1516
}
1617

17-
const match = body.match(regex)
18+
const match = body.match(probotMetadataRegex)
1819

1920
if (match) {
20-
const data = JSON.parse(match[2])[prefix]
21-
return key ? data && data[key] : data
21+
const probotMetadata = JSON.parse(match[1])
22+
const data = probotMetadata[prefix]
23+
return typeof key === 'string' || typeof key === 'number'
24+
? data && data[key]
25+
: data
2226
}
2327
},
2428

2529
async set (key, value) {
2630
let body = issue.body
27-
let data = {}
31+
/** @type {Record<number, Record<number|string, any>>} */
32+
let data = Object.create(null)
2833

29-
if (!body) body = (await github.issues.get(issue)).data.body || ''
34+
if (!body) body = (await octokit.rest.issues.get(issue)).data.body || ''
3035

31-
const match = body.match(regex)
36+
const match = body.match(probotMetadataRegex)
3237

3338
if (match) {
34-
data = JSON.parse(match[2])
39+
data = JSON.parse(match[1])
3540
}
3641

37-
body = body.replace(regex, '')
42+
body = body.replace(probotMetadataRegex, '')
3843

39-
if (!data[prefix]) data[prefix] = {}
44+
if (!data[prefix]) data[prefix] = Object.create(null)
4045

41-
if (typeof key === 'object') {
42-
Object.assign(data[prefix], key)
43-
} else {
46+
// should never happen, but just in case
47+
if (typeof prefix === 'string' && prototypePollutionKeys.includes(prefix)) {
48+
throw new TypeError('Invalid prefix value')
49+
}
50+
if (typeof key === 'string' || typeof key === 'number') {
4451
data[prefix][key] = value
52+
} else {
53+
Object.assign(data[prefix], key)
4554
}
4655

47-
body = `${body}\n\n<!-- probot = ${JSON.stringify(data)} -->`
56+
await octokit.rest.issues.update({
57+
owner: issue.owner,
58+
repo: issue.repo,
59+
issue_number: issue.issue_number,
60+
body: `${body}\n\n<!-- probot = ${JSON.stringify(data)} -->`
61+
})
4862

49-
const { owner, repo, issue_number } = issue
50-
return github.issues.update({ owner, repo, issue_number, body })
63+
issue.body = `${body}\n\n<!-- probot = ${JSON.stringify(data)} -->`
5164
}
5265
}
5366
}
67+
68+
export default metadata
69+
export { metadata }
70+
71+
/** @typedef {string|number} StringOrNumber */
72+
/** @typedef {Record<string, StringOrNumber>|StringOrNumber|StringOrNumber[]} Key */
73+
/** @typedef {Key} Value */
74+
75+
/**
76+
* @typedef {object} IssueOption
77+
* @property {string} owner
78+
* @property {string} repo
79+
* @property {number} issue_number
80+
* @property {string} [body]
81+
*/
82+
83+
/**
84+
* @typedef {object} ProbotMetadata
85+
* @property {(key?: Key)=>Promise<Value|undefined>} get
86+
* @property {(key?: Key, value?: Value)=>Promise<void>} set
87+
*/
88+
89+
/** @typedef {(context: import('probot').Context<'issue_comment'>, issue?: IssueOption)=>ProbotMetadata} ProbotMetadataConstructor */

0 commit comments

Comments
 (0)