From ff2a0180da1b45c88afb6dae5eeda5a43464bb5e Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Sun, 16 Nov 2025 16:43:42 +0100 Subject: [PATCH 1/6] feat(ci): add `/fmt` command for maintainers --- .github/workflows/commentator.yml | 115 ++++++++++++++++++++++++++++++ .github/workflows/linter.yml | 51 +++++++------ docs.json | 2 +- 3 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 .github/workflows/commentator.yml diff --git a/.github/workflows/commentator.yml b/.github/workflows/commentator.yml new file mode 100644 index 000000000..fa29d8cf6 --- /dev/null +++ b/.github/workflows/commentator.yml @@ -0,0 +1,115 @@ +# Listens for issue comments and emoji reactions in PRs +# and creates appropriate review comments +name: 💬 Commentator + +env: + HUSKY: 0 + NODE_VERSION: 20 + +on: + issue_comment: + types: [created] # deleted, edited + pull_request_review_comment: + types: [created] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-suggester + cancel-in-progress: true + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + fmt: + name: "Formatting changes" + runs-on: ubuntu-latest + # /fmt comment from a trusted commenter in a PR + if: | + ( + ( + (github.event_name == 'issue_comment' && github.event.issue.pull_request != null) || + (github.event_name == 'pull_request_review_comment') + ) && + (github.event.comment.body == '/fmt' || startsWith(github.event.comment.body, '/fmt ')) && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) + ) + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: "npm" + + - name: Install dependencies + run: | + corepack enable + npm ci + + - name: Get changed MDX and Markdown files + id: changed-files + uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47 + with: + files: | + **.md + **.mdx + + - name: Create review comments for each formatted file + env: + ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + run: | + set -euo pipefail + if [ -z $(echo -e "${ALL_CHANGED_FILES[@]}" | tr -d '[:space:]') ]; then + echo -e 'No relevant files affected!' + exit 0 + fi + echo -e 'Creating review comments for each formatted (modified) MDX and Markdown file affected by this PR:' + # Preparations + jq_inplace() { jq "${@:2}" "$1" > "${1}.tmp" && mv "${1}.tmp" "$1"; } + # Sidecar must exist (selection may be empty when approving clean PRs) + SIDECAR="$RUN_DIR/review/index.json" + if [ ! -f "$SIDECAR" ]; then + echo "Sidecar not found: $SIDECAR" >/dev/null 2>&1 + exit 1 + fi + COMMIT_ID="$(jq -r '.commit_id // empty' "$SIDECAR")" + if [ -z "$COMMIT_ID" ]; then + echo "commit_id missing in sidecar; aborting." >/dev/null 2>&1 + exit 1 + fi + echo '{"body": "Please apply the formatting suggestions.", "event": "COMMENT", "comments": []}' > payload.json + for file in ${ALL_CHANGED_FILES}; do + npx remark --quiet --frail --silently-ignore -o "$file" + # npx remark --quiet --frail --silently-ignore "$file" -o "${file}-output" + ( # try + git diff --quiet "$file" + # diff -q "$file" "${file}-output" + ) || ( # catch — formatter there are differences and formatting did something + jq_inplace payload.json ".comments += [{path: \"${file}\", subject_type: \"file\", side: \"RIGHT\", start_side: \"RIGHT\", commit_id: \"${COMMIT_ID}\", body: \"$(cat "$file" | sed ':a;N;$!ba;s/\n/\\n/g')\"}]" + # jq_inplace payload.json ".comments += [{path: \"${file}\", subject_type: \"file\", side: \"RIGHT\", start_side: \"RIGHT\", commit_id: \"${COMMIT_ID}\", body: \"$(cat "${file}-output" | sed ':a;N;$!ba;s/\n/\\n/g')\"}]" + ) + done + COMMENTS_COUNT=$(jq -r '.comments | length' payload.json) + if [ "$COMMENTS_COUNT" -eq 0 ]; then + echo -e '\nNo comments' + exit 0 + fi + echo -e '\nSubmitting comments' + API="https://api.github.com/repos/${{ github.repository }}/pulls/${{ github.event.number }}/reviews" + HTTP_CODE=$(curl -sS -o /dev/null -w "%{http_code}" -L -X POST "$API" \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ github.token }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "Content-Type: application/json" \ + -d @payload.json || true) + echo -e "GitHub API HTTP: ${HTTP_CODE:-}" + if ! [[ "$HTTP_CODE" =~ ^[0-9]{3}$ ]] || [ "$HTTP_CODE" -lt 200 ] || [ "$HTTP_CODE" -ge 300 ]; then + echo -e "Failed to submit a PR review" + exit 1 + fi diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 8c1f90ab6..60d74d884 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}-linter cancel-in-progress: true permissions: @@ -52,6 +52,7 @@ jobs: env: ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} run: | + set -euo pipefail if [ -z $(echo -e "${ALL_CHANGED_FILES[@]}" | tr -d '[:space:]') ]; then echo -e '\nNo such files affected!' exit 0 @@ -61,26 +62,34 @@ jobs: echo "- $file" done echo - npx remark --no-stdout --quiet --frail --silently-ignore $(echo -e "${ALL_CHANGED_FILES[@]}" | tr '\n' ' ') - - - name: ${{ steps.check-fmt.conclusion == 'failure' && '👀 How to fix the formatting? See these suggestions!' || '...' }} - env: - ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} - if: failure() - run: | - # Preparations - FILES="$(echo -e "${ALL_CHANGED_FILES[@]}" | tr '\n' ' ' | sed 's/.*/\"&\"/')" - BODY="{\"body\":\"To fix the **formatting** issues:\n\n1. Install necessary dependencies: \`npm ci\`\n2. Then, run this command:\n\`\`\`shell\nnpx remark -o --silent --silently-ignore ${FILES}\n\`\`\`\"}" - # Comment on the PR - curl -s -o /dev/null -L -X POST \ - -H "Accept: application/vnd.github+json" \ - -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ - -H "X-GitHub-Api-Version: 2022-11-28" \ - -d "$BODY" \ - https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.number }}/comments - # Comment right in the actions output - echo -e "\nInstall necessary dependencies: \033[31mnpm ci\033[0m" - echo -e "Then, run this to fix formatting: \033[31mnpx remark -o --silent --silently-ignore ${FILES}\033[0m" + FILES="$(echo -e "${ALL_CHANGED_FILES[@]}" | sed 's/.*/\"&\"/' | tr '\n' ' ')" + ( # try + npx remark --no-stdout --quiet --frail --silently-ignore $FILES + ) || ( # catch + ERR="$(echo $?)" + # Comment right in the actions output + echo -e "\nInstall necessary dependencies: \033[31mnpm ci\033[0m" + echo -e "Then, run this to fix formatting: \033[31mnpx remark -o --silent --silently-ignore ${FILES}\033[0m" + # Comment on the PR (if there is one) + if [ ${{ (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') && 'true' || 'false' }} = "false" ]; then + exit $ERR # exit code of the failed formatting check + fi + API="https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" + COMMENT="To fix the **formatting** issues:\n\n1. Install necessary dependencies: \`npm ci\`\n2. Then, run this command:\n\`\`\`shell\nnpx remark -o --silent --silently-ignore ${FILES}\n\`\`\`" + BY_MAINTAINER=${{ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.author_association) && 'true' || 'false' }} + if [ "$BY_MAINTAINER" = "true" ]; then + COMMENT="${COMMENT}\n\n**Alternatively**, request automatic changes by commenting /fmt in this PR." + else + COMMENT="${COMMENT}\n\n**Alternatively**, a maintainer can request automatic changes by commenting /fmt in this PR." + fi + curl -sS -o /dev/null -L -X POST "$API" \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ github.token }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "Content-Type: application/json" \ + -d "{\"body\":\"${COMMENT}\"}" + exit $ERR # exit code of the failed formatting check + ) spell-check: name: "Spelling" diff --git a/docs.json b/docs.json index acf2fe770..4bae6bdd1 100644 --- a/docs.json +++ b/docs.json @@ -2121,4 +2121,4 @@ "permanent": true } ] -} \ No newline at end of file +} From c9e0d873b1991126645d3e4b1931aa7bbff8c220 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:13:57 +0100 Subject: [PATCH 2/6] intentional formatter trigger --- start-here.mdx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/start-here.mdx b/start-here.mdx index b5501ee02..db8f6716e 100644 --- a/start-here.mdx +++ b/start-here.mdx @@ -6,3 +6,7 @@ icon: rocket import { Stub } from '/snippets/stub.jsx'; + + + + From 50609c6905d5ee8e6f96743384a7dfa658412c47 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:16:04 +0100 Subject: [PATCH 3/6] fix --- .github/workflows/commentator.yml | 2 +- .github/workflows/linter.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/commentator.yml b/.github/workflows/commentator.yml index fa29d8cf6..cbed80f6d 100644 --- a/.github/workflows/commentator.yml +++ b/.github/workflows/commentator.yml @@ -91,7 +91,7 @@ jobs: git diff --quiet "$file" # diff -q "$file" "${file}-output" ) || ( # catch — formatter there are differences and formatting did something - jq_inplace payload.json ".comments += [{path: \"${file}\", subject_type: \"file\", side: \"RIGHT\", start_side: \"RIGHT\", commit_id: \"${COMMIT_ID}\", body: \"$(cat "$file" | sed ':a;N;$!ba;s/\n/\\n/g')\"}]" + jq_inplace payload.json ".comments += [{path: \"${file}\", subject_type: \"file\", side: \"RIGHT\", start_side: \"RIGHT\", commit_id: \"${COMMIT_ID}\", body: \"\`\`\`\`\`suggestion\\n$(cat "$file")\\n\`\`\`\`\`\"}]" # jq_inplace payload.json ".comments += [{path: \"${file}\", subject_type: \"file\", side: \"RIGHT\", start_side: \"RIGHT\", commit_id: \"${COMMIT_ID}\", body: \"$(cat "${file}-output" | sed ':a;N;$!ba;s/\n/\\n/g')\"}]" ) done diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 60d74d884..d6693c6cb 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -62,7 +62,7 @@ jobs: echo "- $file" done echo - FILES="$(echo -e "${ALL_CHANGED_FILES[@]}" | sed 's/.*/\"&\"/' | tr '\n' ' ')" + FILES="$(echo -e "${ALL_CHANGED_FILES[@]}" | tr '\n' ' ')" ( # try npx remark --no-stdout --quiet --frail --silently-ignore $FILES ) || ( # catch From 4daa90f8754a9d041dc461d06bbd9069241c3a1a Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:28:31 +0100 Subject: [PATCH 4/6] another trigger --- .github/workflows/commentator.yml | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/commentator.yml b/.github/workflows/commentator.yml index cbed80f6d..d562071d3 100644 --- a/.github/workflows/commentator.yml +++ b/.github/workflows/commentator.yml @@ -8,7 +8,7 @@ env: on: issue_comment: - types: [created] # deleted, edited + types: [created] pull_request_review_comment: types: [created] @@ -28,13 +28,11 @@ jobs: # /fmt comment from a trusted commenter in a PR if: | ( - ( - (github.event_name == 'issue_comment' && github.event.issue.pull_request != null) || - (github.event_name == 'pull_request_review_comment') - ) && - (github.event.comment.body == '/fmt' || startsWith(github.event.comment.body, '/fmt ')) && - contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) - ) + (github.event_name == 'issue_comment' && github.event.issue.pull_request != null) || + (github.event_name == 'pull_request_review_comment') + ) && + (github.event.comment.body == '/fmt' || startsWith(github.event.comment.body, '/fmt ')) && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) steps: - name: Checkout repository uses: actions/checkout@v4 @@ -86,13 +84,10 @@ jobs: echo '{"body": "Please apply the formatting suggestions.", "event": "COMMENT", "comments": []}' > payload.json for file in ${ALL_CHANGED_FILES}; do npx remark --quiet --frail --silently-ignore -o "$file" - # npx remark --quiet --frail --silently-ignore "$file" -o "${file}-output" ( # try git diff --quiet "$file" - # diff -q "$file" "${file}-output" - ) || ( # catch — formatter there are differences and formatting did something + ) || ( # catch — there are differences and formatting did something jq_inplace payload.json ".comments += [{path: \"${file}\", subject_type: \"file\", side: \"RIGHT\", start_side: \"RIGHT\", commit_id: \"${COMMIT_ID}\", body: \"\`\`\`\`\`suggestion\\n$(cat "$file")\\n\`\`\`\`\`\"}]" - # jq_inplace payload.json ".comments += [{path: \"${file}\", subject_type: \"file\", side: \"RIGHT\", start_side: \"RIGHT\", commit_id: \"${COMMIT_ID}\", body: \"$(cat "${file}-output" | sed ':a;N;$!ba;s/\n/\\n/g')\"}]" ) done COMMENTS_COUNT=$(jq -r '.comments | length' payload.json) From de1244af7a3cc154df72e7c0effa8a8c5634a0a0 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:39:56 +0100 Subject: [PATCH 5/6] ping --- .github/workflows/commentator.yml | 26 ++++++++++++++++++++++++++ .github/workflows/linter.yml | 1 + 2 files changed, 27 insertions(+) diff --git a/.github/workflows/commentator.yml b/.github/workflows/commentator.yml index d562071d3..cb4c54e35 100644 --- a/.github/workflows/commentator.yml +++ b/.github/workflows/commentator.yml @@ -22,6 +22,32 @@ permissions: issues: write jobs: + ping: + runs-on: ubuntu-latest + if: | + ( + (github.event_name == 'issue_comment' && github.event.issue.pull_request != null) || + (github.event_name == 'pull_request_review_comment') + ) && + (github.event.comment.body == 'ping' || startsWith(github.event.comment.body, 'ping ')) && + contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.author_association) + steps: + - run: | + API="https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" + COMMENT="pong" + BY_MAINTAINER=${{ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.author_association) && 'true' || 'false' }} + if [ "$BY_MAINTAINER" = "true" ]; then + COMMENT="${COMMENT}\n\nYou are either owner, member, or collaborator" + else + COMMENT="${COMMENT}\n\nYou are not an owner, member, or collaborator." + fi + curl -sS -o /dev/null -L -X POST "$API" \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ github.token }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + -H "Content-Type: application/json" \ + -d "{\"body\":\"${COMMENT}\"}" + fmt: name: "Formatting changes" runs-on: ubuntu-latest diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index d6693c6cb..1697da1b3 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -22,6 +22,7 @@ jobs: format-check: name: "Formatting" runs-on: ubuntu-latest + if: false # TODO: enable it back steps: - name: Checkout repository uses: actions/checkout@v4 From b583b0c85d4ae3f6a1c67e8cba36d15ae6904db7 Mon Sep 17 00:00:00 2001 From: Novus Nota <68142933+novusnota@users.noreply.github.com> Date: Mon, 17 Nov 2025 14:52:56 +0100 Subject: [PATCH 6/6] idk --- .github/workflows/commentator.yml | 8 +++----- .github/workflows/linter.yml | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/commentator.yml b/.github/workflows/commentator.yml index cb4c54e35..0155e30ac 100644 --- a/.github/workflows/commentator.yml +++ b/.github/workflows/commentator.yml @@ -13,7 +13,7 @@ on: types: [created] concurrency: - group: ${{ github.workflow }}-${{ github.ref }}-suggester + group: ${{ github.workflow }}-${{ github.ref }}-commentator cancel-in-progress: true permissions: @@ -25,13 +25,11 @@ jobs: ping: runs-on: ubuntu-latest if: | - ( - (github.event_name == 'issue_comment' && github.event.issue.pull_request != null) || - (github.event_name == 'pull_request_review_comment') - ) && + ((github.event_name == 'issue_comment' && github.event.issue.pull_request != null) || (github.event_name == 'pull_request_review_comment')) && (github.event.comment.body == 'ping' || startsWith(github.event.comment.body, 'ping ')) && contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.author_association) steps: + - uses: actions/checkout@v4 - run: | API="https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" COMMENT="pong" diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 1697da1b3..f653f1a00 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -77,7 +77,7 @@ jobs: fi API="https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.number }}/comments" COMMENT="To fix the **formatting** issues:\n\n1. Install necessary dependencies: \`npm ci\`\n2. Then, run this command:\n\`\`\`shell\nnpx remark -o --silent --silently-ignore ${FILES}\n\`\`\`" - BY_MAINTAINER=${{ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.author_association) && 'true' || 'false' }} + BY_MAINTAINER=${{ contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.pull_request.author_association) && 'true' || 'false' }} if [ "$BY_MAINTAINER" = "true" ]; then COMMENT="${COMMENT}\n\n**Alternatively**, request automatic changes by commenting /fmt in this PR." else