This is an automated email from the ASF dual-hosted git repository.
Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new b2bc43376a infra: add PR comment commands for assign, close, and lgtm
(#7770)
b2bc43376a is described below
commit b2bc43376a0146684faf6d45a6832d201fc61f45
Author: shown <[email protected]>
AuthorDate: Sat Oct 17 22:22:42 2026 +0800
infra: add PR comment commands for assign, close, and lgtm (#7770)
---
.github/workflows/assign.yml | 79 ++++++++++++++++++++++++++++++++++++++++
.github/workflows/close.yml | 71 ++++++++++++++++++++++++++++++++++++
.github/workflows/lgtm.yml | 66 +++++++++++++++++++++++++++++++++
actions/scripts/issue-manager.sh | 8 ++++
4 files changed, 224 insertions(+)
diff --git a/.github/workflows/assign.yml b/.github/workflows/assign.yml
new file mode 100644
index 0000000000..a84af41f2d
--- /dev/null
+++ b/.github/workflows/assign.yml
@@ -0,0 +1,79 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Assign
+
+# Post /assign or /assign @user1 @user2 as the entire comment on an open PR.
+# With no arguments, assign the commenter. A rocket confirms success.
+on:
+ issue_comment:
+ types: [created]
+
+permissions: {}
+
+jobs:
+ assign:
+ if: github.event.issue.pull_request && contains(github.event.comment.body,
'/assign')
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ pull-requests: write
+ concurrency:
+ group: assign-${{ github.event.issue.number }}
+ cancel-in-progress: false
+ steps:
+ # Use the default-branch script without checking out or executing PR
code.
+ - name: Assign pull request
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const {issue, comment, action} = context.payload;
+ const command = comment?.body?.trim().match(/^\/assign(?:[
\t]+(@?[a-zA-Z0-9-]+(?:[ \t]+@?[a-zA-Z0-9-]+)*))?$/);
+ if (action !== 'created' || !issue?.pull_request || !command) {
+ return;
+ }
+
+ const repo = context.repo;
+ const {data: pr} = await github.rest.pulls.get({...repo,
pull_number: issue.number});
+ if (pr.state !== 'open') {
+ core.info('Skipping a closed pull request.');
+ return;
+ }
+
+ const users = command[1] ? command[1].split(/[ \t]+/) :
[comment.user.login];
+ const assignees = [...new Set(users.map(user => user.replace(/^@/,
'').toLowerCase()))];
+ if (assignees.length > 10) {
+ core.setFailed('A pull request can have at most 10
assignees.');
+ return;
+ }
+
+ const {data: updated} = await github.rest.issues.addAssignees({
+ ...repo,
+ issue_number: issue.number,
+ assignees,
+ });
+ // GitHub silently ignores users who cannot be assigned; report
partial failures.
+ const assigned = new Set(updated.assignees.map(user =>
user.login.toLowerCase()));
+ const missing = assignees.filter(user => !assigned.has(user));
+ if (missing.length) {
+ core.setFailed(`Could not assign: ${missing.join(', ')}.`);
+ return;
+ }
+
+ await github.rest.reactions.createForIssueComment({
+ ...repo,
+ comment_id: comment.id,
+ content: 'rocket',
+ });
diff --git a/.github/workflows/close.yml b/.github/workflows/close.yml
new file mode 100644
index 0000000000..b906597a1a
--- /dev/null
+++ b/.github/workflows/close.yml
@@ -0,0 +1,71 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: Close
+
+# Post /close as the entire comment on an open PR. The PR author and users
+# with write, maintain, or admin access may close it. A rocket confirms
success.
+on:
+ issue_comment:
+ types: [created]
+
+permissions: {}
+
+jobs:
+ close:
+ if: github.event.issue.pull_request && contains(github.event.comment.body,
'/close')
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ pull-requests: write
+ concurrency:
+ group: close-${{ github.event.issue.number }}
+ cancel-in-progress: false
+ steps:
+ # Use the default-branch script without checking out or executing PR
code.
+ - name: Close pull request
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const {issue, comment, action} = context.payload;
+ if (action !== 'created' || !issue?.pull_request ||
comment?.body?.trim() !== '/close') {
+ return;
+ }
+
+ const repo = context.repo;
+ const pullRequest = {...repo, pull_number: issue.number};
+ const {data: pr} = await github.rest.pulls.get(pullRequest);
+ if (pr.state !== 'open') {
+ core.info('Skipping a closed pull request.');
+ return;
+ }
+
+ if (comment.user.login.toLowerCase() !==
pr.user.login.toLowerCase()) {
+ const {data: access} = await
github.rest.repos.getCollaboratorPermissionLevel({
+ ...repo,
+ username: comment.user.login,
+ });
+ if (!['write', 'maintain',
'admin'].includes(access.permission)) {
+ core.info('Only the pull request author or a user with
write access can close it.');
+ return;
+ }
+ }
+
+ await github.rest.pulls.update({...pullRequest, state: 'closed'});
+ await github.rest.reactions.createForIssueComment({
+ ...repo,
+ comment_id: comment.id,
+ content: 'rocket',
+ });
diff --git a/.github/workflows/lgtm.yml b/.github/workflows/lgtm.yml
new file mode 100644
index 0000000000..5e92727f74
--- /dev/null
+++ b/.github/workflows/lgtm.yml
@@ -0,0 +1,66 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+name: LGTM
+
+# Post /lgtm as the entire comment on an open PR to submit an approving review.
+# The Actions bot reviews the current head commit. A rocket confirms success.
+# Repository settings must allow GitHub Actions to create and approve pull
requests.
+on:
+ issue_comment:
+ types: [created]
+
+permissions: {}
+
+jobs:
+ lgtm:
+ if: github.event.issue.pull_request && contains(github.event.comment.body,
'/lgtm')
+ runs-on: ubuntu-latest
+ timeout-minutes: 5
+ permissions:
+ pull-requests: write
+ concurrency:
+ group: lgtm-${{ github.event.issue.number }}
+ cancel-in-progress: false
+ steps:
+ # Use the default-branch script without checking out or executing PR
code.
+ - name: Approve pull request
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const {issue, comment, action} = context.payload;
+ if (action !== 'created' || !issue?.pull_request ||
comment?.body?.trim() !== '/lgtm') {
+ return;
+ }
+
+ const repo = context.repo;
+ const {data: pr} = await github.rest.pulls.get({...repo,
pull_number: issue.number});
+ if (pr.state !== 'open') {
+ core.info('Skipping a closed pull request.');
+ return;
+ }
+
+ await github.rest.pulls.createReview({
+ ...repo,
+ pull_number: issue.number,
+ commit_id: pr.head.sha,
+ event: 'APPROVE',
+ body: `Approved via /lgtm by @${comment.user.login}.`,
+ });
+ await github.rest.reactions.createForIssueComment({
+ ...repo,
+ comment_id: comment.id,
+ content: 'rocket',
+ });
diff --git a/actions/scripts/issue-manager.sh b/actions/scripts/issue-manager.sh
index 401d635a80..9edc78dcd1 100644
--- a/actions/scripts/issue-manager.sh
+++ b/actions/scripts/issue-manager.sh
@@ -20,6 +20,7 @@
# Minimal issue manager script to handle a subset of prow-style commands
# Reads the GitHub issue_comment event JSON pointed to by GITHUB_EVENT_PATH
# Requires GITHUB_TOKEN env and GITHUB_REPOSITORY env (owner/repo)
+# PR /assign, /close, and /lgtm commands are handled by dedicated workflows.
#
# Supported Commands:
# -------------------
@@ -68,6 +69,7 @@ read_event() {
COMMENT_BODY=$(read_event '.comment.body')
ISSUE_NUMBER=$(read_event '.issue.number')
COMMENT_USER=$(read_event '.comment.user.login')
+IS_PULL_REQUEST=$(read_event '.issue.pull_request != null')
OWNER=$(echo "${GITHUB_REPOSITORY}" | cut -d/ -f1)
REPO=$(echo "${GITHUB_REPOSITORY}" | cut -d/ -f2)
@@ -156,6 +158,12 @@ normalize_user() {
process_command() {
local line="$1"
line="$(echo "$line" | sed -e 's/^\s*//' -e 's/\s*$//')"
+ # Do not replay PR commands owned by the dedicated created-comment workflows.
+ if [ "$IS_PULL_REQUEST" = true ]; then
+ case "$line" in
+ \/assign*|\/close|\/lgtm) return ;;
+ esac
+ fi
case "$line" in
\/assign*)
args="${line#\/assign}"