From 107ab45b779fd2e067ae8280372c847b18cd4b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=A9rence=20Hollander?= Date: Wed, 9 Oct 2024 22:10:45 +0200 Subject: [PATCH] feat: manage delete modes in a better way BREAKING CHANGE: delete now deletes a comment immediately. To delete the comment at the end of the job, use `delete-on-completion` Co-authored-by: mlahargou --- .github/workflows/ci.yaml | 40 +++++++++++++------- README.md | 51 +++++++++++++++---------- action.yml | 14 +++---- lib/cleanup/index.js | 26 ++++++------- lib/index.js | 75 +++++++++++++++++++++---------------- package-lock.json | 4 +- package.json | 2 +- src/cleanup.ts | 27 +++++++------ src/main.ts | 79 ++++++++++++++++++++++----------------- 9 files changed, 181 insertions(+), 137 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index e82eec0c..e080100c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -15,37 +15,51 @@ jobs: - name: Comment PR with message uses: ./ - id: nrt_message + id: nrt-message with: message: | Current branch is `${{ github.head_ref }}`. _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ - comment_tag: nrt_message + comment-tag: nrt-message reactions: eyes, rocket mode: recreate + - name: Create a comment to delete it after + uses: ./ + with: + message: | + This message will be deleted + comment-tag: nrt-message-delete + reactions: eyes + + - name: Delete comment + uses: ./ + with: + comment-tag: nrt-message-delete + mode: delete + - name: Comment PR with message that will be deleted uses: ./ with: message: | This PR is being built... - comment_tag: nrt_message_delete + comment-tag: nrt-message-delete-on-completion reactions: eyes - mode: delete + mode: delete-on-completion - name: Comment PR with file uses: ./ with: - filePath: README.md - comment_tag: nrt_file + file-path: README.md + comment-tag: nrt-file reactions: eyes, rocket mode: recreate - name: Comment PR with file (absolute path) uses: ./ with: - filePath: /tmp/foobar.txt - comment_tag: nrt_file_absolute + file-path: /tmp/foobar.txt + comment-tag: nrt-file-absolute reactions: eyes, rocket mode: recreate @@ -53,14 +67,14 @@ jobs: uses: ./ with: message: Should not be printed - comment_tag: nrt_create_if_not_exists - create_if_not_exists: false + comment-tag: nrt-create-if-not-exists + create-if-not-exists: false - name: Check outputs run: | - echo "id : ${{ steps.nrt_message.outputs.id }}" - echo "body : ${{ steps.nrt_message.outputs.body }}" - echo "html_url : ${{ steps.nrt_message.outputs.html_url }}" + echo "id : ${{ steps.nrt-message.outputs.id }}" + echo "body : ${{ steps.nrt-message.outputs.body }}" + echo "html-url : ${{ steps.nrt-message.outputs.html-url }}" - name: (AFTER) Setup test cases run: rm /tmp/foobar.txt \ No newline at end of file diff --git a/README.md b/README.md index 83b7dd62..e1e2b0f7 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v3 - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: message: | Hello world ! :wave: @@ -35,7 +35,7 @@ You can either pass an absolute filePath or a relative one that will be by defau ```yml - name: PR comment with file - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: filePath: /path/to/file.txt ``` @@ -48,7 +48,7 @@ It takes only valid reactions and adds it to the comment you've just created. (S ```yml - name: PR comment with reactions - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: message: | Hello world ! :wave: @@ -63,7 +63,7 @@ That is particularly useful for manual workflow for instance (`workflow_run`). ```yml ... - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: message: | Hello world ! :wave: @@ -83,7 +83,7 @@ _That is particularly interesting while committing multiple times in a PR and th ```yml ... - name: Comment PR with execution number - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: message: | _(execution **${{ github.run_id }}** / attempt **${{ github.run_attempt }}**)_ @@ -94,20 +94,33 @@ Note: the input `mode` can be used to either `upsert` (by default) or `recreate` ### Delete a comment -Deleting an existing comment is also possible thanks to the `comment_tag` input combined with `mode: delete`. + +Deleting a comment with a specific `comment_tag` is possible with the `mode: delete`. If a comment with the `comment_tag` exists, it will be deleted when ran. + +```yml +... +- name: Delete a comment + uses: thollander/actions-comment-pull-request@v3 + with: + comment_tag: to_delete + mode: delete +``` + +### Delete a comment on job completion + +Deleting an existing comment on job completion is also possible thanks to the `comment_tag` input combined with `mode: delete-on-completion`. This will delete the comment at the end of the job. ```yml ... - name: Write a comment that will be deleted at the end of the job - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 with: message: | The PR is being built... - comment_tag: to_delete - mode: delete - + comment_tag: to_delete_on_completion + mode: delete-on-completion ``` ## Inputs @@ -116,14 +129,14 @@ This will delete the comment at the end of the job. | Name | Description | Required | Default | | --- | --- | --- | --- | -| `GITHUB_TOKEN` | Token that is used to create comments. Defaults to ${{ github.token }} | ✅ | | +| `github-token` | Token that is used to create comments. Defaults to ${{ github.token }} | ✅ | | | `message` | Comment body | | | -| `filePath` | Path of the file that should be commented | | | +| `file-path` | Path of the file that should be commented | | | | `reactions` | List of reactions for the comment (comma separated). See https://docs.github.com/en/rest/reactions#reaction-types | | | -| `pr_number` | The number of the pull request where to create the comment | | current pull-request/issue number (deduced from context) | -| `comment_tag` | A tag on your comment that will be used to identify a comment in case of replacement | | | -| `mode` | Mode that will be used to update comment (upsert/recreate/delete) | | upsert | -| `create_if_not_exists` | Whether a comment should be created even if `comment_tag` is not found | | true | +| `pr-number` | The number of the pull request where to create the comment | | current pull-request/issue number (deduced from context) | +| `comment-tag` | A tag on your comment that will be used to identify a comment in case of replacement | | | +| `mode` | Mode that will be used to update comment (upsert/recreate/delete/delete-on-completion) | | upsert | +| `create-if-not-exists` | Whether a comment should be created even if `comment-tag` is not found | | true | ## Outputs @@ -136,13 +149,13 @@ You can get some outputs from this actions : | --- | --- | | `id` | Comment id that was created or updated | | `body` | Comment body | -| `html_url` | URL of the comment created or updated | +| `html-url` | URL of the comment created or updated | ### Example output ```yaml - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 + uses: thollander/actions-comment-pull-request@v3 id: hello with: message: | @@ -151,7 +164,7 @@ You can get some outputs from this actions : run: | echo "id : ${{ steps.hello.outputs.id }}" echo "body : ${{ steps.hello.outputs.body }}" - echo "html_url : ${{ steps.hello.outputs.html_url }}" + echo "html-url : ${{ steps.hello.outputs.html-url }}" ``` ## Permissions diff --git a/action.yml b/action.yml index 29d39034..afb38def 100644 --- a/action.yml +++ b/action.yml @@ -6,23 +6,23 @@ description: 'Comments a pull request with the provided message' inputs: message: description: 'Message that should be printed in the pull request' - filePath: + file-path: description: 'Path of the file that should be commented' - GITHUB_TOKEN: + github-token: description: 'Github token of the repository (automatically created by Github)' default: ${{ github.token }} required: false reactions: description: 'You can set some reactions on your comments through the `reactions` input.' - pr_number: + pr-number: description: 'Manual pull request number' - comment_tag: + comment-tag: description: 'A tag on your comment that will be used to identify a comment in case of replacement.' mode: - description: 'Mode that will be used to update comment (upsert/recreate)' + description: 'Mode that will be used (upsert/recreate/delete/delete-on-completion)' default: 'upsert' - create_if_not_exists: - description: 'Whether a comment should be created even if comment_tag is not found.' + create-if-not-exists: + description: 'Whether a comment should be created even if comment-tag is not found.' default: 'true' runs: using: 'node20' diff --git a/lib/cleanup/index.js b/lib/cleanup/index.js index 49162872..59d77886 100644 --- a/lib/cleanup/index.js +++ b/lib/cleanup/index.js @@ -9540,32 +9540,32 @@ const github = __importStar(__nccwpck_require__(5438)); const core = __importStar(__nccwpck_require__(2186)); async function run() { try { - const github_token = core.getInput('GITHUB_TOKEN'); - const pr_number = core.getInput('pr_number'); - const comment_tag = core.getInput('comment_tag'); + const githubToken = core.getInput('github-token'); + const prNumber = core.getInput('pr-number'); + const commentTag = core.getInput('comment-tag'); const mode = core.getInput('mode'); - if (mode !== 'delete') { - core.debug('This comment was not to be deleted. Skipping'); + if (mode !== 'delete-on-completion') { + core.debug('This comment was not to be deleted on completion. Skipping'); return; } - if (!comment_tag) { + if (!commentTag) { core.debug("No 'comment_tag' parameter passed in. Cannot search for something to delete."); return; } const context = github.context; - const issue_number = parseInt(pr_number) || context.payload.pull_request?.number || context.payload.issue?.number; - const octokit = github.getOctokit(github_token); - if (!issue_number) { + const issueNumber = parseInt(prNumber) || context.payload.pull_request?.number || context.payload.issue?.number; + const octokit = github.getOctokit(githubToken); + if (!issueNumber) { core.setFailed('No issue/pull request in input neither in current context.'); return; } - const comment_tag_pattern = ``; - if (comment_tag_pattern) { + const commentTagPattern = ``; + if (commentTagPattern) { for await (const { data: comments } of octokit.paginate.iterator(octokit.rest.issues.listComments, { ...context.repo, - issue_number, + issue_number: issueNumber, })) { - const commentsToDelete = comments.filter((comment) => comment?.body?.includes(comment_tag_pattern)); + const commentsToDelete = comments.filter((comment) => comment?.body?.includes(commentTagPattern)); for (const commentToDelete of commentsToDelete) { core.info(`Deleting comment ${commentToDelete.id}.`); await octokit.rest.issues.deleteComment({ diff --git a/lib/index.js b/lib/index.js index c7410c4d..8fc7efac 100644 --- a/lib/index.js +++ b/lib/index.js @@ -9547,15 +9547,15 @@ const REACTIONS = ['+1', '-1', 'laugh', 'confused', 'heart', 'hooray', 'rocket', async function run() { try { const message = core.getInput('message'); - const filePath = core.getInput('filePath'); - const github_token = core.getInput('GITHUB_TOKEN'); - const pr_number = core.getInput('pr_number'); - const comment_tag = core.getInput('comment_tag'); + const filePath = core.getInput('file-path'); + const githubToken = core.getInput('github-token'); + const prNumber = core.getInput('pr-number'); + const commentTag = core.getInput('comment-tag'); const reactions = core.getInput('reactions'); const mode = core.getInput('mode'); - const create_if_not_exists = core.getInput('create_if_not_exists') === 'true'; - if (!message && !filePath) { - core.setFailed('Either "filePath" or "message" should be provided as input'); + const createIfNotExists = core.getInput('create-if-not-exists') === 'true'; + if (!message && !filePath && mode !== 'delete') { + core.setFailed('Either "filePath" or "message" should be provided as input unless running as "delete".'); return; } let content = message; @@ -9563,13 +9563,13 @@ async function run() { content = fs_1.default.readFileSync(filePath, 'utf8'); } const context = github.context; - const issue_number = parseInt(pr_number) || context.payload.pull_request?.number || context.payload.issue?.number; - const octokit = github.getOctokit(github_token); - if (!issue_number) { + const issueNumber = parseInt(prNumber) || context.payload.pull_request?.number || context.payload.issue?.number; + const octokit = github.getOctokit(githubToken); + if (!issueNumber) { core.setFailed('No issue/pull request in input neither in current context.'); return; } - async function addReactions(comment_id, reactions) { + async function addReactions(commentId, reactions) { const validReactions = reactions .replace(/\s/g, '') .split(',') @@ -9577,56 +9577,54 @@ async function run() { await Promise.allSettled(validReactions.map(async (content) => { await octokit.rest.reactions.createForIssueComment({ ...context.repo, - comment_id, + comment_id: commentId, content, }); })); } - async function createComment({ owner, repo, issue_number, body, }) { + async function createComment({ owner, repo, issueNumber, body, }) { const { data: comment } = await octokit.rest.issues.createComment({ owner, repo, - issue_number, + issue_number: issueNumber, body, }); core.setOutput('id', comment.id); core.setOutput('body', comment.body); - core.setOutput('html_url', comment.html_url); + core.setOutput('html-url', comment.html_url); await addReactions(comment.id, reactions); return comment; } - async function updateComment({ owner, repo, comment_id, body, }) { + async function updateComment({ owner, repo, commentId, body, }) { const { data: comment } = await octokit.rest.issues.updateComment({ owner, repo, - comment_id, + comment_id: commentId, body, }); core.setOutput('id', comment.id); core.setOutput('body', comment.body); - core.setOutput('html_url', comment.html_url); + core.setOutput('html-url', comment.html_url); await addReactions(comment.id, reactions); return comment; } - async function deleteComment({ owner, repo, comment_id }) { + async function deleteComment({ owner, repo, commentId }) { const { data: comment } = await octokit.rest.issues.deleteComment({ owner, repo, - comment_id, + comment_id: commentId, }); return comment; } - const comment_tag_pattern = comment_tag - ? `` - : null; - const body = comment_tag_pattern ? `${content}\n${comment_tag_pattern}` : content; - if (comment_tag_pattern) { + const commentTagPattern = commentTag ? `` : null; + const body = commentTagPattern ? `${content}\n${commentTagPattern}` : content; + if (commentTagPattern) { let comment; for await (const { data: comments } of octokit.paginate.iterator(octokit.rest.issues.listComments, { ...context.repo, - issue_number, + issue_number: issueNumber, })) { - comment = comments.find((comment) => comment?.body?.includes(comment_tag_pattern)); + comment = comments.find((comment) => comment?.body?.includes(commentTagPattern)); if (comment) break; } @@ -9634,7 +9632,7 @@ async function run() { if (mode === 'upsert') { await updateComment({ ...context.repo, - comment_id: comment.id, + commentId: comment.id, body, }); return; @@ -9642,24 +9640,35 @@ async function run() { else if (mode === 'recreate') { await deleteComment({ ...context.repo, - comment_id: comment.id, + commentId: comment.id, }); await createComment({ ...context.repo, - issue_number, + issueNumber, body, }); return; } else if (mode === 'delete') { + await deleteComment({ + ...context.repo, + commentId: comment.id, + }); + return; + } + else if (mode === 'delete-on-completion') { core.debug('Registering this comment to be deleted.'); } else { - core.setFailed(`Mode ${mode} is unknown. Please use 'upsert', 'recreate' or 'delete'.`); + core.setFailed(`Mode ${mode} is unknown. Please use 'upsert', 'recreate', 'delete' or 'delete-on-completion'.`); return; } } - else if (create_if_not_exists) { + else if (mode === 'delete') { + core.info('No comment has been found with asked pattern. Nothing to delete.'); + return; + } + else if (createIfNotExists) { core.info('No comment has been found with asked pattern. Creating a new comment.'); } else { @@ -9669,7 +9678,7 @@ async function run() { } await createComment({ ...context.repo, - issue_number, + issueNumber, body, }); } diff --git a/package-lock.json b/package-lock.json index df826ae0..e6c4f240 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "actions-comment-pull-request", - "version": "2.5.0", + "version": "3.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "actions-comment-pull-request", - "version": "2.5.0", + "version": "3.0.0", "license": "MIT", "dependencies": { "@actions/core": "^1.10.1", diff --git a/package.json b/package.json index 08be9be5..763e77b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "actions-comment-pull-request", - "version": "2.5.0", + "version": "3.0.0", "description": "GitHub action for commenting pull-request", "main": "lib/main.js", "scripts": { diff --git a/src/cleanup.ts b/src/cleanup.ts index 9ef2bd51..bd54aaf9 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -1,42 +1,41 @@ import * as github from '@actions/github'; import * as core from '@actions/core'; -import { GetResponseDataTypeFromEndpointMethod } from '@octokit/types'; async function run() { try { - const github_token: string = core.getInput('GITHUB_TOKEN'); - const pr_number: string = core.getInput('pr_number'); - const comment_tag: string = core.getInput('comment_tag'); + const githubToken: string = core.getInput('github-token'); + const prNumber: string = core.getInput('pr-number'); + const commentTag: string = core.getInput('comment-tag'); const mode: string = core.getInput('mode'); - if (mode !== 'delete') { - core.debug('This comment was not to be deleted. Skipping'); + if (mode !== 'delete-on-completion') { + core.debug('This comment was not to be deleted on completion. Skipping'); return; } - if (!comment_tag) { + if (!commentTag) { core.debug("No 'comment_tag' parameter passed in. Cannot search for something to delete."); return; } const context = github.context; - const issue_number = parseInt(pr_number) || context.payload.pull_request?.number || context.payload.issue?.number; + const issueNumber = parseInt(prNumber) || context.payload.pull_request?.number || context.payload.issue?.number; - const octokit = github.getOctokit(github_token); + const octokit = github.getOctokit(githubToken); - if (!issue_number) { + if (!issueNumber) { core.setFailed('No issue/pull request in input neither in current context.'); return; } - const comment_tag_pattern = ``; + const commentTagPattern = ``; - if (comment_tag_pattern) { + if (commentTagPattern) { for await (const { data: comments } of octokit.paginate.iterator(octokit.rest.issues.listComments, { ...context.repo, - issue_number, + issue_number: issueNumber, })) { - const commentsToDelete = comments.filter((comment) => comment?.body?.includes(comment_tag_pattern)); + const commentsToDelete = comments.filter((comment) => comment?.body?.includes(commentTagPattern)); for (const commentToDelete of commentsToDelete) { core.info(`Deleting comment ${commentToDelete.id}.`); await octokit.rest.issues.deleteComment({ diff --git a/src/main.ts b/src/main.ts index 62e1ee81..6afd917f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -10,16 +10,16 @@ type Reaction = (typeof REACTIONS)[number]; async function run() { try { const message: string = core.getInput('message'); - const filePath: string = core.getInput('filePath'); - const github_token: string = core.getInput('GITHUB_TOKEN'); - const pr_number: string = core.getInput('pr_number'); - const comment_tag: string = core.getInput('comment_tag'); + const filePath: string = core.getInput('file-path'); + const githubToken: string = core.getInput('github-token'); + const prNumber: string = core.getInput('pr-number'); + const commentTag: string = core.getInput('comment-tag'); const reactions: string = core.getInput('reactions'); const mode: string = core.getInput('mode'); - const create_if_not_exists: boolean = core.getInput('create_if_not_exists') === 'true'; + const createIfNotExists: boolean = core.getInput('create-if-not-exists') === 'true'; - if (!message && !filePath) { - core.setFailed('Either "filePath" or "message" should be provided as input'); + if (!message && !filePath && mode !== 'delete') { + core.setFailed('Either "filePath" or "message" should be provided as input unless running as "delete".'); return; } @@ -29,16 +29,16 @@ async function run() { } const context = github.context; - const issue_number = parseInt(pr_number) || context.payload.pull_request?.number || context.payload.issue?.number; + const issueNumber = parseInt(prNumber) || context.payload.pull_request?.number || context.payload.issue?.number; - const octokit = github.getOctokit(github_token); + const octokit = github.getOctokit(githubToken); - if (!issue_number) { + if (!issueNumber) { core.setFailed('No issue/pull request in input neither in current context.'); return; } - async function addReactions(comment_id: number, reactions: string) { + async function addReactions(commentId: number, reactions: string) { const validReactions = reactions .replace(/\s/g, '') .split(',') @@ -48,7 +48,7 @@ async function run() { validReactions.map(async (content) => { await octokit.rest.reactions.createForIssueComment({ ...context.repo, - comment_id, + comment_id: commentId, content, }); }), @@ -58,24 +58,24 @@ async function run() { async function createComment({ owner, repo, - issue_number, + issueNumber, body, }: { owner: string; repo: string; - issue_number: number; + issueNumber: number; body: string; }) { const { data: comment } = await octokit.rest.issues.createComment({ owner, repo, - issue_number, + issue_number: issueNumber, body, }); core.setOutput('id', comment.id); core.setOutput('body', comment.body); - core.setOutput('html_url', comment.html_url); + core.setOutput('html-url', comment.html_url); await addReactions(comment.id, reactions); @@ -85,55 +85,53 @@ async function run() { async function updateComment({ owner, repo, - comment_id, + commentId, body, }: { owner: string; repo: string; - comment_id: number; + commentId: number; body: string; }) { const { data: comment } = await octokit.rest.issues.updateComment({ owner, repo, - comment_id, + comment_id: commentId, body, }); core.setOutput('id', comment.id); core.setOutput('body', comment.body); - core.setOutput('html_url', comment.html_url); + core.setOutput('html-url', comment.html_url); await addReactions(comment.id, reactions); return comment; } - async function deleteComment({ owner, repo, comment_id }: { owner: string; repo: string; comment_id: number }) { + async function deleteComment({ owner, repo, commentId }: { owner: string; repo: string; commentId: number }) { const { data: comment } = await octokit.rest.issues.deleteComment({ owner, repo, - comment_id, + comment_id: commentId, }); return comment; } - const comment_tag_pattern = comment_tag - ? `` - : null; - const body = comment_tag_pattern ? `${content}\n${comment_tag_pattern}` : content; + const commentTagPattern = commentTag ? `` : null; + const body = commentTagPattern ? `${content}\n${commentTagPattern}` : content; - if (comment_tag_pattern) { + if (commentTagPattern) { type ListCommentsResponseDataType = GetResponseDataTypeFromEndpointMethod< typeof octokit.rest.issues.listComments >; let comment: ListCommentsResponseDataType[0] | undefined; for await (const { data: comments } of octokit.paginate.iterator(octokit.rest.issues.listComments, { ...context.repo, - issue_number, + issue_number: issueNumber, })) { - comment = comments.find((comment) => comment?.body?.includes(comment_tag_pattern)); + comment = comments.find((comment) => comment?.body?.includes(commentTagPattern)); if (comment) break; } @@ -141,29 +139,40 @@ async function run() { if (mode === 'upsert') { await updateComment({ ...context.repo, - comment_id: comment.id, + commentId: comment.id, body, }); return; } else if (mode === 'recreate') { await deleteComment({ ...context.repo, - comment_id: comment.id, + commentId: comment.id, }); await createComment({ ...context.repo, - issue_number, + issueNumber, body, }); return; } else if (mode === 'delete') { + await deleteComment({ + ...context.repo, + commentId: comment.id, + }); + return; + } else if (mode === 'delete-on-completion') { core.debug('Registering this comment to be deleted.'); } else { - core.setFailed(`Mode ${mode} is unknown. Please use 'upsert', 'recreate' or 'delete'.`); + core.setFailed( + `Mode ${mode} is unknown. Please use 'upsert', 'recreate', 'delete' or 'delete-on-completion'.`, + ); return; } - } else if (create_if_not_exists) { + } else if (mode === 'delete') { + core.info('No comment has been found with asked pattern. Nothing to delete.'); + return; + } else if (createIfNotExists) { core.info('No comment has been found with asked pattern. Creating a new comment.'); } else { core.info( @@ -175,7 +184,7 @@ async function run() { await createComment({ ...context.repo, - issue_number, + issueNumber, body, }); } catch (error) {