From 3fa9bda6f3cecada7e45a40fef3c63810f873fcc Mon Sep 17 00:00:00 2001 From: Shashank Date: Mon, 11 May 2026 23:10:45 +0530 Subject: [PATCH] feat: implement /assign GitHub Action --- .github/workflows/assign.yml | 131 +++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 36 ++++++++-- VERIFY_IMPLEMENTATION.txt | 62 +++++++++++++++++ 3 files changed, 224 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/assign.yml create mode 100644 VERIFY_IMPLEMENTATION.txt diff --git a/.github/workflows/assign.yml b/.github/workflows/assign.yml new file mode 100644 index 0000000..e407673 --- /dev/null +++ b/.github/workflows/assign.yml @@ -0,0 +1,131 @@ +name: Issue / PR Self-Assign + +on: + issue_comment: + types: [created] + +permissions: + issues: write + pull-requests: write + +jobs: + assign: + if: | + !github.event.sender.type == 'Bot' && + contains(github.event.comment.body, '/assign') + runs-on: ubuntu-latest + + steps: + - name: Parse /assign command + id: parse + uses: actions/github-script@v7 + with: + script: | + const body = context.payload.comment.body.trim(); + const commenter = context.payload.comment.user.login; + const senderType = context.payload.comment.user.type; + + if (senderType === 'Bot') { + core.setOutput('skip', 'true'); + return; + } + + // Supports /assign or /assign @username + const match = body.match(/^\/assign(?:\s+@?([\w-]+))?/im); + + if (!match) { + core.setOutput('skip', 'true'); + return; + } + + const targetUser = match[1] ? match[1] : commenter; + + core.setOutput('skip', 'false'); + core.setOutput('target_user', targetUser); + core.setOutput('commenter', commenter); + + - name: Assign user + if: steps.parse.outputs.skip == 'false' + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const targetUser = '${{ steps.parse.outputs.target_user }}'; + const commenter = '${{ steps.parse.outputs.commenter }}'; + const issueNumber = context.payload.issue.number; + const owner = context.repo.owner; + const repo = context.repo.repo; + + async function postComment(body) { + await github.rest.issues.createComment({ + owner, repo, issue_number: issueNumber, body + }); + } + + let userMeta; + try { + const { data } = await github.rest.users.getByUsername({ username: targetUser }); + userMeta = data; + } catch (err) { + if (err.status === 404) { + await postComment( + `> ❌ @${commenter} – \`${targetUser}\` is not a valid GitHub username. ` + + `Please check the spelling and try again.` + ); + core.setFailed(`User '${targetUser}' not found on GitHub.`); + return; + } + throw err; + } + + if (userMeta.type === 'Bot') { + await postComment( + `> ⚠️ @${commenter} – bot accounts cannot be assigned to issues or PRs.` + ); + return; + } + + const { data: issue } = await github.rest.issues.get({ + owner, repo, issue_number: issueNumber + }); + + const currentAssignees = issue.assignees.map(a => a.login.toLowerCase()); + + if (currentAssignees.includes(targetUser.toLowerCase())) { + const isSelf = targetUser.toLowerCase() === commenter.toLowerCase(); + await postComment( + isSelf + ? `> ℹ️ @${commenter} – you are already assigned to this ${issue.pull_request ? 'PR' : 'issue'}.` + : `> ℹ️ @${commenter} – \`${targetUser}\` is already assigned to this ${issue.pull_request ? 'PR' : 'issue'}.` + ); + return; + } + + try { + await github.rest.issues.addAssignees({ + owner, + repo, + issue_number: issueNumber, + assignees: [targetUser] + }); + } catch (err) { + // GitHub returns 422 when a user lacks repo access + if (err.status === 422) { + await postComment( + `> ❌ @${commenter} – \`${targetUser}\` could not be assigned. ` + + `They may not have the necessary repository access. ` + + `Please ask a maintainer for help.` + ); + core.setFailed(`Cannot assign '${targetUser}' – insufficient permissions.`); + return; + } + throw err; + } + + const isSelf = targetUser.toLowerCase() === commenter.toLowerCase(); + const itemType = issue.pull_request ? 'PR' : 'issue'; + await postComment( + isSelf + ? `> ✅ @${commenter} has self-assigned this ${itemType}. Good luck! 🚀` + : `> ✅ @${commenter} has assigned @${targetUser} to this ${itemType}.` + ); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 00cb1e8..38bb9a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,20 +82,46 @@ devcard/ - **Conventional Commits** for commit messages (`feat:`, `fix:`, `docs:`, `chore:`) - Write tests for new features and bug fixes +## Self-Assigning Issues & PRs + +You can assign yourself (or another contributor) to any open issue or pull request by posting a comment with the `/assign` command — no maintainer action required. + +| Command | Effect | +|---|---| +| `/assign` | Assigns **you** (the commenter) to the issue/PR | +| `/assign @username` | Assigns the specified GitHub **@username** | + +**Examples:** + +``` +/assign +``` +``` +/assign @octocat +``` + +The bot will reply with a confirmation comment (or an error message if the username is invalid, the user is already assigned, or the account lacks repo access). + +> **Note:** Bot accounts cannot be assigned. Duplicate assignments are silently prevented. + +--- + ## Pull Request Process 1. Create a feature branch from `main`: `git checkout -b feat/your-feature` -2. Make your changes with clear, descriptive commits -3. Ensure all tests pass: `pnpm test` -4. Ensure linting passes: `pnpm lint` -5. Open a PR against `main` with a clear description of the change -6. Wait for review — maintainers will respond within 48 hours +2. Self-assign the related issue with `/assign` so others know you're working on it +3. Make your changes with clear, descriptive commits +4. Ensure all tests pass: `pnpm test` +5. Ensure linting passes: `pnpm lint` +6. Open a PR against `main` with a clear description of the change +7. Wait for review — maintainers will respond within 48 hours ## Reporting Issues - Use GitHub Issues for bug reports and feature requests - Include reproduction steps for bugs - Search existing issues before creating a new one +- Use `/assign` on an issue you plan to fix so work isn't duplicated ## Code of Conduct diff --git a/VERIFY_IMPLEMENTATION.txt b/VERIFY_IMPLEMENTATION.txt new file mode 100644 index 0000000..89ff2ca --- /dev/null +++ b/VERIFY_IMPLEMENTATION.txt @@ -0,0 +1,62 @@ +# Implementation Verification & Setup Guide + +This guide covers how to set up the project and verify the newly implemented `/assign` GitHub Action. + +## 1. Local Project Setup +To ensure your local environment matches the project structure and all dependencies are present: + +1. **Install pnpm** (if not already installed): + ```bash + npm install -g pnpm + ``` + +2. **Install Dependencies**: + Run this from the root of the repository to install all workspace dependencies: + ```bash + pnpm install + ``` + +3. **Verify Configuration Files**: + - Check that `.github/workflows/assign.yml` exists. + - Check that `CONTRIBUTING.md` includes the "Self-Assigning Issues & PRs" section. + +--- + +## 2. Verifying the GitHub Action +GitHub Actions run on GitHub's infrastructure. To verify the `/assign` command: + +1. **Push Changes**: + Ensure the new `.github/workflows/assign.yml` file is pushed to your GitHub repository. + ```bash + git add .github/workflows/assign.yml CONTRIBUTING.md + git commit -m "feat: implement /assign GitHub Action" + git push origin + ``` + +2. **Trigger the Action**: + - Open any Issue or Pull Request on your GitHub repository. + - Post a comment: `/assign` + - **Expected Result**: The "Issue / PR Self-Assign" workflow should trigger. You can see this under the "Actions" tab. + - **Confirmation**: A bot (github-actions) should reply to your comment and you should be added as an assignee. + +3. **Test Edge Cases**: + - Post: `/assign @nonexistent_user_12345` (Should reply with an error). + - Post: `/assign @another_valid_user` (Should assign that user). + - Post: `/assign` again (Should notify you that you are already assigned). + +--- + +## 3. Optional: Local Workflow Testing +If you want to test the workflow logic without pushing to GitHub, you can use **act**: + +1. **Install act**: + ```bash + brew install nektos/tap/act + ``` + +2. **Run the workflow**: + Note: This requires Docker to be running. + ```bash + act issue_comment + ``` + *(Note: You may need to provide a mock event JSON to simulate a specific comment body like "/assign")*