diff --git a/README.md b/README.md index 42853e08..c4ef8520 100644 --- a/README.md +++ b/README.md @@ -233,6 +233,8 @@ For repositories with many packages, comments might get too long. In that case, pkg.pr.new uses `npm pack --json` under the hood, in case you face issues, you can also use the `--pnpm`, `--yarn`, or `--bun` flag so it starts using `pnpm pack`, `yarn pack`, or `bun pm pack`. This is not necessary in most cases. +If you want to add comments to the associated issue simultaneously, you can set `--syncCommentWithIssue`. + pkg.pr.new is not available in your local environment and it only works in workflows. diff --git a/packages/app/server/routes/publish.post.ts b/packages/app/server/routes/publish.post.ts index d8d08ba7..c10ec973 100644 --- a/packages/app/server/routes/publish.post.ts +++ b/packages/app/server/routes/publish.post.ts @@ -23,6 +23,7 @@ export default eventHandler(async (event) => { "sb-only-templates": onlyTemplatesHeader, "sb-comment-with-sha": commentWithShaHeader, "sb-comment-with-dev": commentWithDevHeader, + "sb-sync-comment-with-issue": syncCommentWithIssueHeader, } = getHeaders(event); const compact = compactHeader === "true"; const onlyTemplates = onlyTemplatesHeader === "true"; @@ -32,6 +33,7 @@ export default eventHandler(async (event) => { (packageManagerHeader as PackageManager) || "npm"; const commentWithSha = commentWithShaHeader === "true"; const commentWithDev = commentWithDevHeader === "true"; + const syncCommentWithIssue = syncCommentWithIssueHeader === "true"; if (!key || !runIdHeader || !shasumsHeader) { throw createError({ @@ -246,7 +248,17 @@ export default eventHandler(async (event) => { isPullRequest(workflowData.ref) && (await getPullRequestState(installation, workflowData)) === "open" ) { - let prevComment: OctokitComponents["schemas"]["issue-comment"]; + let prevComment: + | OctokitComponents["schemas"]["issue-comment"] + | undefined; + const prevIssueComments: OctokitComponents["schemas"]["issue-comment"][] = + []; + const relatedIssueNumbers: number[] = []; + const matchIssueNumber = /(fix(es)?|closes?|resolves?)\s*(\d+)/gi; + const fullAddressMatchIssueNumber = new RegExp( + `(fix(es)|closes?|resolves?)\\s*https://github.com/${workflowData.owner}/${workflowData.repo}/issues/(\\d+)`, + "gi", + ); await installation.paginate( "GET /repos/{owner}/{repo}/issues/{issue_number}/comments", @@ -261,12 +273,65 @@ export default eventHandler(async (event) => { prevComment = c; done(); break; + } else { + const body = c.body || ""; + let match; + matchIssueNumber.lastIndex = 0; + while ((match = matchIssueNumber.exec(body)) !== null) { + const issueNumber = Number(match[2]); + if (!isNaN(issueNumber)) { + relatedIssueNumbers.push(issueNumber); + } + } + if (!relatedIssueNumbers.length) { + fullAddressMatchIssueNumber.lastIndex = 0; + while ( + (match = fullAddressMatchIssueNumber.exec(body)) !== null + ) { + const issueNumber = Number(match[2]); + if (!isNaN(issueNumber)) { + relatedIssueNumbers.push(issueNumber); + } + } + } + } + if (prevComment) { + if (!syncCommentWithIssue) { + done(); + break; + } else if (relatedIssueNumbers.length) { + done(); + break; + } } } return []; }, ); + if (syncCommentWithIssue && relatedIssueNumbers.length) { + for (const issueNumber of relatedIssueNumbers) { + await installation.paginate( + "GET /repos/{owner}/{repo}/issues/{issue_number}/comments", + { + owner: workflowData.owner, + repo: workflowData.repo, + issue_number: issueNumber, + }, + ({ data }, done) => { + for (const c of data) { + if (c.performed_via_github_app?.id === Number(appId)) { + prevIssueComments.push(c); + done(); + break; + } + } + return []; + }, + ); + } + } + if (comment !== "off") { const { data: { permissions }, @@ -280,49 +345,85 @@ export default eventHandler(async (event) => { try { if (comment === "update" && prevComment!) { + const commentBody = generatePullRequestPublishMessage( + origin, + templatesHtmlMap, + packagesWithoutPrefix, + workflowData, + compact, + onlyTemplates, + checkRunUrl, + packageManager, + commentWithSha ? "sha" : "ref", + bin, + commentWithDev, + ); + await installation.request( "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", { owner: workflowData.owner, repo: workflowData.repo, comment_id: prevComment.id, - body: generatePullRequestPublishMessage( - origin, - templatesHtmlMap, - packagesWithoutPrefix, - workflowData, - compact, - onlyTemplates, - checkRunUrl, - packageManager, - commentWithSha ? "sha" : "ref", - bin, - commentWithDev, - ), + body: commentBody, }, ); + if ( + syncCommentWithIssue && + relatedIssueNumbers.length && + prevIssueComments.length + ) { + for (let i = 0; i < relatedIssueNumbers.length; i++) { + const prevIssueComment = prevIssueComments[i]; + if (!prevIssueComment) continue; + + await installation.request( + "PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}", + { + owner: workflowData.owner, + repo: workflowData.repo, + comment_id: prevIssueComment.id, + body: commentBody, + }, + ); + } + } } else { + const commentBody = generatePullRequestPublishMessage( + origin, + templatesHtmlMap, + packagesWithoutPrefix, + workflowData, + compact, + onlyTemplates, + checkRunUrl, + packageManager, + comment === "update" ? "ref" : "sha", + bin, + commentWithDev, + ); await installation.request( "POST /repos/{owner}/{repo}/issues/{issue_number}/comments", { owner: workflowData.owner, repo: workflowData.repo, issue_number: Number(workflowData.ref), - body: generatePullRequestPublishMessage( - origin, - templatesHtmlMap, - packagesWithoutPrefix, - workflowData, - compact, - onlyTemplates, - checkRunUrl, - packageManager, - comment === "update" ? "ref" : "sha", - bin, - commentWithDev, - ), + body: commentBody, }, ); + if (syncCommentWithIssue && relatedIssueNumbers.length) { + for (const relatedIssueNumber of relatedIssueNumbers) { + await installation.request( + "POST /repos/{owner}/{repo}/issues/{issue_number}/comments", + { + owner: workflowData.owner, + repo: workflowData.repo, + issue_number: relatedIssueNumber, + body: commentBody, + }, + ); + } + } } } catch (error) { console.error("failed to create/update comment", error, permissions); diff --git a/packages/cli/index.ts b/packages/cli/index.ts index 470763e1..ad816cb8 100644 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -100,6 +100,12 @@ const main = defineCommand({ "should install the packages with the 'dev' tag in the comment links", default: false, }, + syncCommentWithIssue: { + type: "boolean", + description: + "sync the comment with the related issue if any (issue number is extracted from the comment body)", + default: false, + }, "only-templates": { type: "boolean", description: `generate only stackblitz templates`, @@ -156,6 +162,7 @@ const main = defineCommand({ const isBinaryApplication = !!args.bin; const isCommentWithSha = !!args.commentWithSha; const isCommentWithDev = !!args.commentWithDev; + const isSyncCommentWithIssue = !!args.syncCommentWithIssue; const comment: Comment = args.comment as Comment; const selectedPackageManager = [ ...new Set( @@ -559,6 +566,7 @@ const main = defineCommand({ "sb-only-templates": `${isOnlyTemplates}`, "sb-comment-with-sha": `${isCommentWithSha}`, "sb-comment-with-dev": `${isCommentWithDev}`, + "sb-sync-comment-with-issue": `${isSyncCommentWithIssue}`, }, body: formData, });