Issue number (#64)

* Major code reorg
* Add issue arg
* Paginate existing comment list
This commit is contained in:
Michael Shick 2022-11-08 10:12:22 -08:00 committed by GitHub
parent e8076c64f7
commit 8645f3f0ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 2187 additions and 1968 deletions

71
src/comments.ts Normal file
View file

@ -0,0 +1,71 @@
import { GitHub } from '@actions/github/lib/utils'
import { Endpoints } from '@octokit/types'
export type CreateIssueCommentResponseData =
Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data']
export async function getExistingCommentId(
octokit: InstanceType<typeof GitHub>,
owner: string,
repo: string,
issueNumber: number,
messageId: string,
): Promise<number | undefined> {
const parameters = {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
}
let found
for await (const comments of octokit.paginate.iterator(
octokit.rest.issues.listComments,
parameters,
)) {
found = comments.data.find(({ body }) => {
return (body?.search(messageId) ?? -1) > -1
})
if (found) {
break
}
}
return found?.id
}
export async function updateComment(
octokit: InstanceType<typeof GitHub>,
owner: string,
repo: string,
existingCommentId: number,
body: string,
): Promise<CreateIssueCommentResponseData> {
const updatedComment = await octokit.rest.issues.updateComment({
comment_id: existingCommentId,
owner,
repo,
body,
})
return updatedComment.data
}
export async function createComment(
octokit: InstanceType<typeof GitHub>,
owner: string,
repo: string,
issueNumber: number,
body: string,
): Promise<CreateIssueCommentResponseData> {
const createdComment = await octokit.rest.issues.createComment({
issue_number: issueNumber,
owner,
repo,
body,
})
return createdComment.data
}

89
src/config.ts Normal file
View file

@ -0,0 +1,89 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import fs from 'node:fs/promises'
interface Inputs {
allowRepeats: boolean
message?: string
messageId: string
messagePath?: string
messageSuccess?: string
messageFailure?: string
messageCancelled?: string
proxyUrl?: string
repoToken: string
status?: string
issue?: number
commitSha: string
pullRequestNumber?: number
repo: string
owner: string
}
export async function getInputs(): Promise<Inputs> {
const messageIdInput = core.getInput('message-id', { required: false })
const messageId = messageIdInput === '' ? 'add-pr-comment' : `add-pr-comment:${messageIdInput}`
const messageInput = core.getInput('message', { required: false })
const messagePath = core.getInput('message-path', { required: false })
const repoToken = core.getInput('repo-token', { required: true })
const status = core.getInput('status', { required: true })
const issue = core.getInput('issue', { required: false })
const proxyUrl = core.getInput('proxy-url', { required: false }).replace(/\/$/, '')
const allowRepeats = core.getInput('allow-repeats', { required: true }) === 'true'
if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path')
}
let message
if (messagePath) {
message = await fs.readFile(messagePath, { encoding: 'utf8' })
} else {
message = messageInput
}
const messageSuccess = core.getInput(`message-success`)
const messageFailure = core.getInput(`message-failure`)
const messageCancelled = core.getInput(`message-cancelled`)
if (status === 'success' && messageSuccess) {
message = messageSuccess
}
if (status === 'failure' && messageFailure) {
message = messageFailure
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled
}
if (!message) {
throw new Error('no message, check your message inputs')
}
const { payload } = github.context
const repoFullName = payload.repository?.full_name
if (!repoFullName) {
throw new Error('unable to determine repository from request type')
}
const [owner, repo] = repoFullName.split('/')
return {
allowRepeats,
message,
messageId: `<!-- ${messageId} -->`,
proxyUrl,
repoToken,
status,
issue: issue ? Number(issue) : payload.issue?.number,
pullRequestNumber: payload.pull_request?.number,
commitSha: github.context.sha,
owner,
repo,
}
}

16
src/issues.ts Normal file
View file

@ -0,0 +1,16 @@
import { GitHub } from '@actions/github/lib/utils'
export async function getIssueNumberFromCommitPullsList(
octokit: InstanceType<typeof GitHub>,
owner: string,
repo: string,
commitSha: string,
): Promise<number | null> {
const commitPullsList = await octokit.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: commitSha,
})
return commitPullsList.data.length ? commitPullsList.data?.[0].number : null
}

View file

@ -1,179 +1,46 @@
import * as core from '@actions/core'
import * as github from '@actions/github'
import { HttpClient } from '@actions/http-client'
import { Endpoints } from '@octokit/types'
import fs from 'node:fs/promises'
type ListCommitPullsResponseData =
Endpoints['GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls']['response']['data']
type CreateIssueCommentResponseData =
Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data']
type IssuesListCommentsResponseData =
Endpoints['GET /repos/{owner}/{repo}/issues/comments']['response']['data']
const getIssueNumberFromCommitPullsList = (
commitPullsList: ListCommitPullsResponseData,
): number | null => (commitPullsList.length ? commitPullsList[0].number : null)
interface CreateCommentProxyParams {
repoToken: string
commentId?: number
body: string
owner: string
repo: string
issueNumber: number
proxyUrl: string
}
async function createCommentProxy(
params: CreateCommentProxyParams,
): Promise<CreateIssueCommentResponseData | null> {
const { repoToken, owner, repo, issueNumber, body, commentId, proxyUrl } = params
const http = new HttpClient('http-client-add-pr-comment')
const response = await http.postJson<CreateIssueCommentResponseData>(
`${proxyUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
{ comment_id: commentId, body },
{
['temporary-github-token']: repoToken,
},
)
return response.result
}
function getExistingCommentId(
comments: IssuesListCommentsResponseData,
messageId: string,
): number | undefined {
const found = comments.find(({ body }) => {
return (body?.search(messageId) ?? -1) > -1
})
return found?.id
}
interface AddPrCommentInputs {
allowRepeats: boolean
message?: string
messageId: string
messagePath?: string
messageSuccess?: string
messageFailure?: string
messageCancelled?: string
proxyUrl?: string
repoToken: string
status?: string
}
async function getInputs(): Promise<AddPrCommentInputs> {
const messageId = core.getInput('message-id')
const messageInput = core.getInput('message')
const messagePath = core.getInput('message-path')
const repoToken = core.getInput('repo-token') || process.env['GITHUB_TOKEN']
const status = core.getInput('status')
if (!repoToken) {
throw new Error(
'no github token provided, set one with the repo-token input or GITHUB_TOKEN env variable',
)
}
if (messageInput && messagePath) {
throw new Error('must specify only one, message or message-path')
}
let message
if (messagePath) {
message = await fs.readFile(messagePath, { encoding: 'utf8' })
} else {
message = messageInput
}
const messageSuccess = core.getInput(`message-success`)
const messageFailure = core.getInput(`message-failure`)
const messageCancelled = core.getInput(`message-cancelled`)
if ((messageSuccess || messageFailure || messageCancelled) && !status) {
throw new Error('to use a status message you must provide a status input')
}
if (status) {
if (status === 'success' && messageSuccess) {
message = messageSuccess
}
if (status === 'failure' && messageFailure) {
message = messageFailure
}
if (status === 'cancelled' && messageCancelled) {
message = messageCancelled
}
}
if (!message) {
throw new Error('no message, check your message inputs')
}
return {
allowRepeats: Boolean(core.getInput('allow-repeats') === 'true'),
message,
messageId: messageId === '' ? 'add-pr-comment' : messageId,
proxyUrl: core.getInput('proxy-url').replace(/\/$/, ''),
repoToken,
status,
}
}
import {
createComment,
CreateIssueCommentResponseData,
getExistingCommentId,
updateComment,
} from './comments'
import { getInputs } from './config'
import { getIssueNumberFromCommitPullsList } from './issues'
import { createCommentProxy } from './proxy'
const run = async (): Promise<void> => {
try {
const { allowRepeats, message, messageId, repoToken, proxyUrl } = await getInputs()
const messageIdComment = `<!-- ${messageId} -->`
const {
payload: { pull_request: pullRequest, issue, repository },
sha: commitSha,
} = github.context
allowRepeats,
message,
messageId,
repoToken,
proxyUrl,
issue,
pullRequestNumber,
commitSha,
repo,
owner,
} = await getInputs()
if (!repository) {
core.info('unable to determine repository from request type')
core.setOutput('comment-created', 'false')
return
}
const { full_name: repoFullName } = repository
if (!repoFullName) {
core.info('repository is missing a full_name property... weird')
core.setOutput('comment-created', 'false')
return
}
const [owner, repo] = repoFullName.split('/')
const octokit = github.getOctokit(repoToken)
let issueNumber
if (issue && issue.number) {
issueNumber = issue.number
} else if (pullRequest && pullRequest.number) {
issueNumber = pullRequest.number
if (issue) {
issueNumber = issue
} else if (pullRequestNumber) {
issueNumber = pullRequestNumber
} else {
// If this is not a pull request, attempt to find a PR matching the sha
const commitPullsList = await octokit.rest.repos.listPullRequestsAssociatedWithCommit({
owner,
repo,
commit_sha: commitSha,
})
issueNumber = commitPullsList.data && getIssueNumberFromCommitPullsList(commitPullsList.data)
issueNumber = await getIssueNumberFromCommitPullsList(octokit, owner, repo, commitSha)
}
if (!issueNumber) {
core.info(
'this action only works on issues and pull_request events or other commits associated with a pull',
'no issue number found, use a pull_request event, a pull event, or provide an issue input',
)
core.setOutput('comment-created', 'false')
return
@ -184,13 +51,7 @@ const run = async (): Promise<void> => {
if (!allowRepeats) {
core.debug('repeat comments are disallowed, checking for existing')
const { data: comments } = await octokit.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber,
})
existingCommentId = getExistingCommentId(comments, messageIdComment)
existingCommentId = await getExistingCommentId(octokit, owner, repo, issueNumber, messageId)
if (existingCommentId) {
core.debug(`existing comment found with id: ${existingCommentId}`)
@ -198,7 +59,8 @@ const run = async (): Promise<void> => {
}
let comment: CreateIssueCommentResponseData | null | undefined
const body = `${messageIdComment}\n\n${message}`
const body = `${messageId}\n\n${message}`
if (proxyUrl) {
comment = await createCommentProxy({
@ -212,22 +74,10 @@ const run = async (): Promise<void> => {
})
core.setOutput(existingCommentId ? 'comment-updated' : 'comment-created', 'true')
} else if (existingCommentId) {
const updatedComment = await octokit.rest.issues.updateComment({
comment_id: existingCommentId,
owner,
repo,
body,
})
comment = updatedComment.data
comment = await updateComment(octokit, owner, repo, existingCommentId, body)
core.setOutput('comment-updated', 'true')
} else {
const createdComment = await octokit.rest.issues.createComment({
issue_number: issueNumber,
owner,
repo,
body,
})
comment = createdComment.data
comment = await createComment(octokit, owner, repo, issueNumber, body)
core.setOutput('comment-created', 'true')
}
@ -240,8 +90,6 @@ const run = async (): Promise<void> => {
} catch (err) {
if (err instanceof Error) {
core.setFailed(err.message)
} else {
core.setFailed('unknown failure')
}
}
}

33
src/proxy.ts Normal file
View file

@ -0,0 +1,33 @@
import { HttpClient } from '@actions/http-client'
import { Endpoints } from '@octokit/types'
type CreateIssueCommentResponseData =
Endpoints['POST /repos/{owner}/{repo}/issues/{issue_number}/comments']['response']['data']
export interface CreateCommentProxyParams {
repoToken: string
commentId?: number
body: string
owner: string
repo: string
issueNumber: number
proxyUrl: string
}
export async function createCommentProxy(
params: CreateCommentProxyParams,
): Promise<CreateIssueCommentResponseData | null> {
const { repoToken, owner, repo, issueNumber, body, commentId, proxyUrl } = params
const http = new HttpClient('http-client-add-pr-comment')
const response = await http.postJson<CreateIssueCommentResponseData>(
`${proxyUrl}/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
{ comment_id: commentId, body },
{
['temporary-github-token']: repoToken,
},
)
return response.result
}