Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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: 7 additions & 7 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,21 +100,21 @@ runs:
if [[ $flag && ${{ github.event.action }} != 'deleted' ]]; then
if [ $bot_comment_id ]; then
if [[ $type = pr_comment ]] || [[ $type = pr_description ]]; then
gh api repos/${{ github.repository }}/issues/comments/$bot_comment_id -X PATCH -f body="$message"
postCommentWithFallback "$message" gh api repos/${{ github.repository }}/issues/comments/$bot_comment_id -X PATCH -f body="$message"
elif [[ $type = issue_comment ]] || [[ $type = issue_description ]]; then
gh api repos/${{ github.repository }}/issues/comments/$bot_comment_id -X PATCH -f body="$message"
postCommentWithFallback "$message" gh api repos/${{ github.repository }}/issues/comments/$bot_comment_id -X PATCH -f body="$message"
elif [[ $type = discussion_description ]] || [[ $type = discussion_comment ]]; then
gh api graphql -f query='mutation($commentId: ID!, $body: String!) { updateDiscussionComment(input: {commentId: $commentId, body: $body}) { comment { id body }}}' -f commentId=$bot_comment_id -f body="$message"
postCommentWithFallback "$message" gh api graphql -f query='mutation($commentId: ID!, $body: String!) { updateDiscussionComment(input: {commentId: $commentId, body: $body}) { comment { id body }}}' -f commentId=$bot_comment_id -f body="$message"
fi
else
if [[ $type = pr_comment ]] || [[ $type = pr_description ]]; then
gh pr comment $issue_url --body "$message"
postCommentWithFallback "$message" gh pr comment $issue_url --body "$message"
elif [[ $type = issue_comment ]] || [[ $type = issue_description ]]; then
gh issue comment $issue_url --body "$message"
postCommentWithFallback "$message" gh issue comment $issue_url --body "$message"
elif [[ $type = discussion_description ]]; then
addDiscussionComment $discussion_node_id "$message"
postCommentWithFallback "$message" addDiscussionComment $discussion_node_id "$message"
elif [[ $type = discussion_comment ]]; then
addDiscussionComment $discussion_node_id "$message" $reply_to_id
postCommentWithFallback "$message" addDiscussionComment $discussion_node_id "$message" $reply_to_id
fi
fi
else
Expand Down
39 changes: 38 additions & 1 deletion queries.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
#!/bin/bash

# Runs a comment command and falls back to the log and job summary when a fork token cannot write comments.
function postCommentWithFallback() {
local MESSAGE=$1
shift

local OUTPUT
local EXIT_CODE

if OUTPUT=$("$@" 2>&1); then
if [ -n "$OUTPUT" ]; then
printf '%s\n' "$OUTPUT"
fi
return 0
else
EXIT_CODE=$?
fi

if [[ "$OUTPUT" == *"Resource not accessible by integration"* ]]; then
printf '%s\n' "::warning::Unable to post the accessibility alt text comment because the workflow token cannot write comments. The message is included below and in the job summary."
printf '\n%s\n' "$MESSAGE"

if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
{
printf '# Accessibility alt text bot\n\n'
printf '%s\n' "$MESSAGE"
} >> "$GITHUB_STEP_SUMMARY"
fi

return 0
fi

if [ -n "$OUTPUT" ]; then
printf '%s\n' "$OUTPUT" >&2
fi
return "$EXIT_CODE"
}

# Given a node_id for a discussion comment that is a reply in thread, return the parent comment's node ID.
function getDiscussionReplyToId() {
local NODE_ID=$1
Expand Down Expand Up @@ -33,7 +70,7 @@ function addDiscussionComment() {
}
'
else
gh api graphql -F discussionId="$discussion_node_id" -F body="$message" -f query='
gh api graphql -F discussionId="$DISCUSSION_NODE_ID" -F body="$MESSAGE" -f query='
mutation($discussionId: ID!, $body: String!) {
addDiscussionComment(input: {discussionId: $discussionId, body: $body}) {
comment {
Expand Down
116 changes: 116 additions & 0 deletions src/queries.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { spawnSync } from "node:child_process";
import {
existsSync,
mkdtempSync,
readFileSync,
rmSync,
} from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";

import { describe, expect, test } from "vitest";

const queriesPath = join(
dirname(fileURLToPath(import.meta.url)),
"..",
"queries.sh",
);

const runShell = (body) => {
const temporaryDirectory = mkdtempSync(
join(tmpdir(), "accessibility-alt-text-bot-"),
);
const summaryPath = join(temporaryDirectory, "summary.md");

try {
const result = spawnSync(
"bash",
[
"-c",
`set -e -o pipefail\nsource "$1"\n${body}`,
"queries-test",
queriesPath,
],
{
encoding: "utf8",
env: {
...process.env,
GITHUB_STEP_SUMMARY: summaryPath,
TEST_MESSAGE: "Complete fallback message\nwith a second line.",
},
},
);

return {
result,
summary: existsSync(summaryPath)
? readFileSync(summaryPath, "utf8")
: undefined,
};
} finally {
rmSync(temporaryDirectory, { recursive: true, force: true });
}
};

describe("postCommentWithFallback", () => {
test("preserves successful command output without a fallback", () => {
const { result, summary } = runShell(`
postCommentWithFallback "$TEST_MESSAGE" bash -c \
'printf "%s\\n" "$1"' comment-command "$TEST_MESSAGE"
`);

expect(result.status).toBe(0);
expect(result.stdout).toBe("Complete fallback message\nwith a second line.\n");
expect(result.stderr).toBe("");
expect(summary).toBeUndefined();
});

test("reports the full message when fork permissions prevent a comment", () => {
const { result, summary } = runShell(`
postCommentWithFallback "$TEST_MESSAGE" bash -c \
'printf "GraphQL: Resource not accessible by integration (addComment)\\n" >&2; exit 1'
`);

expect(result.status).toBe(0);
expect(result.stdout).toContain(
"::warning::Unable to post the accessibility alt text comment",
);
expect(result.stdout).toContain("Complete fallback message\nwith a second line.");
expect(result.stderr).toBe("");
expect(summary).toContain("# Accessibility alt text bot");
expect(summary).toContain("Complete fallback message\nwith a second line.");
});

test("preserves unexpected failures", () => {
const { result, summary } = runShell(`
postCommentWithFallback "$TEST_MESSAGE" bash -c \
'printf "unexpected API failure\\n" >&2; exit 23'
`);

expect(result.status).toBe(23);
expect(result.stdout).toBe("");
expect(result.stderr).toBe("unexpected API failure\n");
expect(summary).toBeUndefined();
});
});

describe("addDiscussionComment", () => {
test("uses its explicit arguments for a top-level comment", () => {
const { result } = runShell(`
discussion_node_id="global-id"
message="global message"
gh() { printf '<%s>\\n' "$@"; }

addDiscussionComment "argument-id" "$TEST_MESSAGE"
`);

expect(result.status).toBe(0);
expect(result.stdout).toContain("<discussionId=argument-id>");
expect(result.stdout).toContain(
"<body=Complete fallback message\nwith a second line.>",
);
expect(result.stdout).not.toContain("global-id");
expect(result.stdout).not.toContain("global message");
});
});