From 9359b6b57145a73e9dcf89286a67594cbbf528db Mon Sep 17 00:00:00 2001 From: Mario Campos Date: Thu, 29 Oct 2026 10:34:12 -0500 Subject: [PATCH] Wrap `--marker` argument in HTML comment tags automatically --- .github/actions/post-comment/action.yml | 4 ++-- .github/workflows/pr-checks.yml | 4 ++-- pr-checks/post-comment.test.ts | 30 ++++++++++++------------- pr-checks/post-comment.ts | 19 ++++++++++------ 4 files changed, 31 insertions(+), 26 deletions(-) diff --git a/.github/actions/post-comment/action.yml b/.github/actions/post-comment/action.yml index a71b57d9f..8055bf21d 100644 --- a/.github/actions/post-comment/action.yml +++ b/.github/actions/post-comment/action.yml @@ -3,8 +3,8 @@ description: "Create or update a sticky PR comment identified by a caller-provid inputs: marker: description: > - Non-empty marker identifying the comment. Use a unique, - stable HTML comment for each distinct comment. + Non-empty plain-text identifier for the comment. Use a unique, stable value + for each distinct comment; it is automatically wrapped in a hidden HTML comment. required: true body: description: "Full comment markdown." diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 2193a69c7..eda085049 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -295,7 +295,7 @@ jobs: !startsWith(github.event.pull_request.head.ref, 'update-bundle/') uses: ./.github/actions/post-comment with: - marker: "" + marker: "post-comment:CHANGELOG.md" body: | ⚠️ `CHANGELOG.md` has been modified in this PR. Please do not modify `CHANGELOG.md` directly. Instead, create a change-note file in `unreleased-change-notes/`. The `CHANGELOG.md` @@ -312,7 +312,7 @@ jobs: !startsWith(github.event.pull_request.head.ref, 'update-bundle/') uses: ./.github/actions/post-comment with: - marker: "" + marker: "post-comment:CHANGELOG.md" body: | ⚠️ `CHANGELOG.md` has been modified in this PR. Please do not modify `CHANGELOG.md` directly. Instead, create a change-note file in `unreleased-change-notes/`. The `CHANGELOG.md` diff --git a/pr-checks/post-comment.test.ts b/pr-checks/post-comment.test.ts index d82f5d249..960e7a0f0 100644 --- a/pr-checks/post-comment.test.ts +++ b/pr-checks/post-comment.test.ts @@ -20,7 +20,7 @@ const validFlags = [ "--body", "hello", "--marker", - "", + "marker", "--issue-id", "42", "--repository", @@ -48,7 +48,7 @@ describe("parseOptions", async () => { const options = parseOptions(validFlags); assert.deepEqual(options, { body: "hello", - marker: "", + marker: "marker", issueId: 42, repository: { owner: "github", repo: "codeql-action" }, }); @@ -136,7 +136,7 @@ describe("findExistingComment", async () => { client, { owner: "owner", repo: "repo" }, 1, - "", + "marker", ); assert.equal(id, 2); }); @@ -147,7 +147,7 @@ describe("findExistingComment", async () => { client, { owner: "owner", repo: "repo" }, 1, - "", + "marker", ); assert.equal(id, undefined); }); @@ -156,7 +156,7 @@ describe("findExistingComment", async () => { /** A baseline set of `Options`, for use in `setComment` tests. */ const baseOptions: Options = { body: "hello", - marker: "", + marker: "marker", issueId: 42, repository: { owner: "owner", repo: "repo" }, }; @@ -166,16 +166,16 @@ function assertCommentContent( marker: string, body: string, ): void { - assert.ok(comment.body?.startsWith(marker)); + assert.ok(comment.body?.startsWith(``)); assert.ok(comment.body?.endsWith(body)); } describe("setComment", async () => { await it("uses the supplied marker without affecting other comments", async () => { - const marker = ""; + const marker = "custom-warning"; const { client, comments } = fakeClient([ - { id: 1, body: `${baseOptions.marker}\nunrelated warning` }, - { id: 2, body: `${marker}\nold warning` }, + { id: 1, body: "\nunrelated warning" }, + { id: 2, body: "\nold warning" }, ]); const options = parseOptions(withFlag("--marker", marker)); @@ -185,7 +185,7 @@ describe("setComment", async () => { assert.equal(comments.length, 2); assert.deepEqual(comments[0], { id: 1, - body: `${baseOptions.marker}\nunrelated warning`, + body: "\nunrelated warning", }); assert.equal(comments[1].id, 2); assertCommentContent(comments[1], marker, options.body); @@ -196,7 +196,7 @@ describe("setComment", async () => { // No comment has the marker before posting. assert.equal(comments.length, 0); assert.ok( - !comments.some(({ body }) => body?.startsWith(baseOptions.marker)), + !comments.some(({ body }) => body?.startsWith("")), ); await setComment(client, baseOptions); @@ -214,7 +214,7 @@ describe("setComment", async () => { const { client, comments } = fakeClient(originalComments); // Existing comments do not match the marker. assert.ok( - !comments.some(({ body }) => body?.startsWith(baseOptions.marker)), + !comments.some(({ body }) => body?.startsWith("")), ); await setComment(client, baseOptions); @@ -226,7 +226,7 @@ describe("setComment", async () => { originalComments, ); const markedComments = comments.filter(({ body }) => - body?.startsWith(baseOptions.marker), + body?.startsWith(""), ); // Exactly one comment contains the expected marker and body. assert.equal(markedComments.length, 1); @@ -241,7 +241,7 @@ describe("setComment", async () => { await it("updates the existing comment when one is found", async () => { const { client, comments } = fakeClient([ - { id: 7, body: `${baseOptions.marker}\nold` }, + { id: 7, body: "\nold" }, ]); await setComment(client, baseOptions); @@ -254,7 +254,7 @@ describe("setComment", async () => { await it("replaces a warning with the resolved state using the same marker", async () => { const { client, comments } = fakeClient([ - { id: 9, body: `${baseOptions.marker}\nold` }, + { id: 9, body: "\nold" }, ]); const body = "`CHANGELOG.md` is not modified in this PR. No changelog action is needed."; diff --git a/pr-checks/post-comment.ts b/pr-checks/post-comment.ts index 1b55c13b2..7f805d1ee 100644 --- a/pr-checks/post-comment.ts +++ b/pr-checks/post-comment.ts @@ -3,14 +3,14 @@ /* Creates or updates a pull request comment identified by a marker. The marker is supplied via `--marker` (or the `.github/actions/post-comment` action's `marker` input) -and must be non-empty. The marker is prepended to `--body` and used to find -the comment on later runs. +and must be non-empty. Supply a plain-text identifier; the CLI wraps it in a +hidden HTML comment, prepends it to `--body`, and uses it to find the comment on later runs. Example: ```sh GH_TOKEN= npx tsx ./pr-checks/post-comment.ts \ --body "The changelog needs updating." \ - --marker "" \ + --marker "my-bot" \ --issue-id 123 \ --repository github/codeql-action ``` @@ -27,7 +27,7 @@ import { type ApiClient, getApiClient, resolveToken } from "./api-client"; export interface Options { /** The full comment body. */ body: string; - /** The hidden HTML-comment marker used to find a pre-existing comment. */ + /** Plain-text identifier wrapped in a hidden HTML comment. */ marker: string; /** The issue/pull request number to post/update the comment on. */ issueId: number; @@ -35,6 +35,10 @@ export interface Options { repository: RepositoryNwo; } +function hiddenMarker(marker: string): string { + return ``; +} + /** Parses CLI arguments supplied by the composite action into `Options`. */ export function parseOptions(argv?: string[]): Options { const { values } = parseArgs({ @@ -72,17 +76,18 @@ export async function findExistingComment( issueNumber: number, marker: string, ): Promise { + const formattedMarker = hiddenMarker(marker); const comments = await client.paginate(client.rest.issues.listComments, { ...repository, issue_number: issueNumber, per_page: 100, }); const markedComments = comments.filter((comment) => - comment.body?.startsWith(marker), + comment.body?.startsWith(formattedMarker), ); if (markedComments.length > 1) { console.warn( - `Found ${markedComments.length} comments with marker "${marker}". ` + + `Found ${markedComments.length} comments with marker "${formattedMarker}". ` + "Updating the first one and leaving the others unchanged." + "Use a unique marker for each distinct managed comment to avoid this situation.", ); @@ -98,7 +103,7 @@ export async function setComment( options: Options, ): Promise { const { issueId, marker, repository } = options; - const body = `${marker}\n${options.body}`; + const body = `${hiddenMarker(marker)}\n${options.body}`; const existingCommentId = await findExistingComment( client,