Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Release
"on":
on:
push:
branches:
- master
Expand All @@ -11,13 +11,17 @@ jobs:
name: release
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v5
with:
persist-credentials: true
- uses: actions/setup-node@v6
with:
node-version: lts/*
cache: npm
- run: npm ci
- run: npx semantic-release
- name: Install dependencies
run: npm ci
- name: Release
run: npx semantic-release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.PROBOTBOT_NPM_TOKEN }}
38 changes: 31 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: Test
"on":
on:
push:
branches:
- master
Expand All @@ -8,13 +8,37 @@ name: Test
- opened
- synchronize
jobs:
build:
test:
name: Test on Node.js ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22, 24]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v5
with:
node-version: 16
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm run test

lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: 24
cache: npm
- run: npm install
- run: npm test
- name: Install dependencies
run: npm install
- name: Lint
run: npm run lint
3 changes: 3 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import neostandard from 'neostandard'

export default neostandard({})
32 changes: 15 additions & 17 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { Context } from "probot";

type StringOrNumber = number | string;
type Key =
| { [key: string]: StringOrNumber }
| StringOrNumber[]
| StringOrNumber;
type Value = Key;

declare function metadata(
context: Context,
issue?: { body: string; [key: string]: any }
): {
get(key?: Key): Promise<any>;
set(key: Key, value: Value): Promise<any>;
export default metadata;
export type StringOrNumber = string | number;
export type Key = Record<string, StringOrNumber> | StringOrNumber | StringOrNumber[];
export type Value = Key;
export type IssueOption = {
owner: string;
repo: string;
issue_number: number;
body?: string;
};

export = metadata;
export type ProbotMetadata = {
get: (key?: Key) => Promise<Value | undefined>;
set: (key?: Key, value?: Value) => Promise<void>;
};
export type ProbotMetadataConstructor = (context: import("probot").Context<"issue_comment">, issue?: IssueOption) => ProbotMetadata;
export const metadata: ProbotMetadataConstructor;
78 changes: 57 additions & 21 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,53 +1,89 @@
const regex = /(\n\n|\r\n)<!-- probot = (.*) -->/
const probotMetadataRegex = /(?:\n\n|\r\n)<!-- probot = (.*) -->/
const prototypePollutionKeys = /** @type {const} */(['__proto__', 'constructor', 'prototype'])

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

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

return {
async get (key = null) {
async get (key) {
let body = issue.body

if (!body) {
body = (await github.issues.get(issue)).data.body || ''
body = (await octokit.rest.issues.get(issue)).data.body || ''
}

const match = body.match(regex)
const match = body.match(probotMetadataRegex)

if (match) {
const data = JSON.parse(match[2])[prefix]
return key ? data && data[key] : data
const probotMetadata = JSON.parse(match[1])
const data = probotMetadata[prefix]
return typeof key === 'string' || typeof key === 'number'
? data && data[key]
: data
}
},

async set (key, value) {
let body = issue.body
let data = {}
/** @type {Record<number, Record<number|string, any>>} */
let data = Object.create(null)

if (!body) body = (await github.issues.get(issue)).data.body || ''
if (!body) body = (await octokit.rest.issues.get(issue)).data.body || ''

const match = body.match(regex)
const match = body.match(probotMetadataRegex)

if (match) {
data = JSON.parse(match[2])
data = JSON.parse(match[1])
}

body = body.replace(regex, '')
body = body.replace(probotMetadataRegex, '')

if (!data[prefix]) data[prefix] = {}
if (!data[prefix]) data[prefix] = Object.create(null)

if (typeof key === 'object') {
Object.assign(data[prefix], key)
} else {
// should never happen, but just in case
if (typeof prefix === 'string' && prototypePollutionKeys.includes(prefix)) {
throw new TypeError('Invalid prefix value')
}
if (typeof key === 'string' || typeof key === 'number') {
data[prefix][key] = value
} else {
Object.assign(data[prefix], key)
}

body = `${body}\n\n<!-- probot = ${JSON.stringify(data)} -->`
await octokit.rest.issues.update({
owner: issue.owner,
repo: issue.repo,
issue_number: issue.issue_number,
body: `${body}\n\n<!-- probot = ${JSON.stringify(data)} -->`
})

const { owner, repo, issue_number } = issue
return github.issues.update({ owner, repo, issue_number, body })
issue.body = `${body}\n\n<!-- probot = ${JSON.stringify(data)} -->`
}
}
}

export default metadata
export { metadata }

/** @typedef {string|number} StringOrNumber */
/** @typedef {Record<string, StringOrNumber>|StringOrNumber|StringOrNumber[]} Key */
/** @typedef {Key} Value */

/**
* @typedef {object} IssueOption
* @property {string} owner
* @property {string} repo
* @property {number} issue_number
* @property {string} [body]
*/

/**
* @typedef {object} ProbotMetadata
* @property {(key?: Key)=>Promise<Value|undefined>} get
* @property {(key?: Key, value?: Value)=>Promise<void>} set
*/

/** @typedef {(context: import('probot').Context<'issue_comment'>, issue?: IssueOption)=>ProbotMetadata} ProbotMetadataConstructor */
Loading