Replace native browser alerts with a Toast/notification component in Login #128
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Issue Assignment Manager | |
| on: | |
| issues: | |
| types: [assigned] | |
| issue_comment: | |
| types: [created] | |
| schedule: | |
| - cron: '0 */6 * * *' # every 6 hours (UTC) | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| contents: read | |
| jobs: | |
| manual-assignment-welcome: | |
| if: ${{ github.event_name == 'issues' && github.event.action == 'assigned' && !github.event.issue.pull_request }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Welcome manually assigned contributor | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const core = require("@actions/core"); | |
| const { context, github } = require("@actions/github"); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const issue = context.payload.issue; | |
| const assignee = context.payload.assignee; | |
| const assigner = context.payload.assigner || context.payload.sender; | |
| if (!issue || !assignee) { | |
| core.info("Missing issue or assignee. Skipping."); | |
| return; | |
| } | |
| const issueNumber = issue.number; | |
| const marker = `<!-- learnhub-manual-assign:${assignee.login.toLowerCase()} -->`; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| per_page: 100 | |
| }); | |
| const alreadyCommented = comments.some(comment => | |
| String(comment.body || "").includes(marker) | |
| ); | |
| if (alreadyCommented) { | |
| core.info(`Manual assignment welcome already exists for @${assignee.login}.`); | |
| return; | |
| } | |
| const intro = | |
| `👋 Hi @${assignee.login}, you have been assigned to this issue by @${assigner?.login || "a maintainer"}.\n\n`; | |
| const checklist = | |
| `Welcome to LearnHub. Please make sure you:\n` + | |
| `- Star the repository\n` + | |
| `- Fork the repository\n` + | |
| `- Read \`CONTRIBUTING.md\`\n` + | |
| `- Read \`CODE_OF_CONDUCT.md\`\n` + | |
| `- Understand the issue clearly before starting\n\n`; | |
| const timing = | |
| `Please share meaningful progress within 3 days, or the issue may be reassigned.\n\n`; | |
| const support = | |
| `If you need help or may be delayed, please ping @udaycodespace or join Discord and message there with your GitHub username.\n\n`; | |
| const note = | |
| `Note: PRs with fewer than 20 lines are not accepted.\n`; | |
| const body = `${marker}\n${intro}${checklist}${timing}${support}${note}`; | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body | |
| }); | |
| handle-commands: | |
| if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Handle /assign, /work, /unassign commands | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const core = require("@actions/core"); | |
| const { context, github } = require("@actions/github"); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const issue = context.payload.issue; | |
| const comment = context.payload.comment; | |
| const issueNumber = issue.number; | |
| const issueState = String(issue.state || "").toLowerCase(); | |
| const commentBody = String(comment.body || "").trim(); | |
| const commenter = comment.user.login; | |
| const issueAuthor = issue.user.login; | |
| const isAssignCommand = /^\/assign(?:\s+(.+))?$/i.test(commentBody); | |
| const assignMatch = commentBody.match(/^\/assign(?:\s+(.+))?$/i); | |
| const isWorkCommand = /^\/work\s+(.+)$/i.test(commentBody); | |
| const workMatch = commentBody.match(/^\/work\s+(.+)$/i); | |
| const isUnassignCommand = /^\/unassign\s*$/i.test(commentBody); | |
| if (!isAssignCommand && !isWorkCommand && !isUnassignCommand) { | |
| return; | |
| } | |
| if (issueState === "closed") { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, this issue is already closed and cannot be assigned, worked, or unassigned.` | |
| }); | |
| return; | |
| } | |
| async function isMaintainer(username) { | |
| try { | |
| const { data: permission } = await github.rest.repos.getCollaboratorPermissionLevel({ | |
| owner, | |
| repo, | |
| username | |
| }); | |
| return ( | |
| ["admin", "write"].includes(permission.permission) || | |
| ["admin", "maintain", "write"].includes(permission.role_name) | |
| ); | |
| } catch (e) { | |
| return false; | |
| } | |
| } | |
| const currentAssignees = (issue.assignees || []).map(a => a.login); | |
| const lowerCurrentAssignees = currentAssignees.map(login => login.toLowerCase()); | |
| const commenterLower = commenter.toLowerCase(); | |
| const issueAuthorLower = issueAuthor.toLowerCase(); | |
| async function hasTooManyOpenAssignments(username) { | |
| const { data: assignedItems } = await github.rest.issues.listForRepo({ | |
| owner, | |
| repo, | |
| assignee: username, | |
| state: "open", | |
| per_page: 100 | |
| }); | |
| const assignedOpenIssuesOnly = assignedItems.filter(item => !item.pull_request); | |
| return assignedOpenIssuesOnly.length >= 2; | |
| } | |
| const limitBody = | |
| `Easy there, legend — @${commenter} already has 2 open issues. Finish one first and let others cook too.\n`; | |
| async function replyLimit(username) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: limitBody.replace(commenter, username) | |
| }); | |
| } | |
| const QUEUE_MARKER = "<!-- learnhub-queue-state"; | |
| async function getQueueComment() { | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| per_page: 100 | |
| }); | |
| return comments.find(c => String(c.body || "").includes(QUEUE_MARKER)) || null; | |
| } | |
| function parseQueueState(body) { | |
| const state = { | |
| primary: "", | |
| primary_assigned_at: "", | |
| backup: "", | |
| backup_approach: "", | |
| last_progress_at: "" | |
| }; | |
| if (!body) return state; | |
| const lines = String(body).split("\n"); | |
| for (const line of lines) { | |
| const m = line.match(/^\s*([a-z_]+):\s*(.*)\s*$/i); | |
| if (m) { | |
| const key = m[1].trim(); | |
| const val = m[2].trim(); | |
| if (Object.prototype.hasOwnProperty.call(state, key)) { | |
| state[key] = val; | |
| } | |
| } | |
| } | |
| return state; | |
| } | |
| function buildQueueBody(state) { | |
| return ( | |
| `${QUEUE_MARKER}\n` + | |
| `primary: ${state.primary || ""}\n` + | |
| `primary_assigned_at: ${state.primary_assigned_at || ""}\n` + | |
| `backup: ${state.backup || ""}\n` + | |
| `backup_approach: ${state.backup_approach || ""}\n` + | |
| `last_progress_at: ${state.last_progress_at || ""}\n` + | |
| `-->` | |
| ); | |
| } | |
| async function ensureQueueComment() { | |
| let qc = await getQueueComment(); | |
| if (!qc) { | |
| const initialState = { | |
| primary: "", | |
| primary_assigned_at: "", | |
| backup: "", | |
| backup_approach: "", | |
| last_progress_at: "" | |
| }; | |
| const body = buildQueueBody(initialState); | |
| const { data: created } = await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body | |
| }); | |
| qc = created; | |
| } | |
| return qc; | |
| } | |
| const queueComment = await ensureQueueComment(); | |
| let queueState = parseQueueState(queueComment.body || ""); | |
| const commenterIsMaintainer = await isMaintainer(commenter); | |
| const isSelfIssue = commenterLower === issueAuthorLower; | |
| // /assign (self or @someone) | |
| if (isAssignCommand) { | |
| const rawArg = (assignMatch[1] || "").trim(); | |
| const isForceAssign = /^@([a-zA-Z0-9-]+)$/i.test(rawArg); | |
| const forceMatch = rawArg.match(/^@([a-zA-Z0-9-]+)$/i); | |
| const targetUser = isForceAssign ? forceMatch[1] : commenter; | |
| if (issue.pull_request) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, assignment commands are only for issues, not pull requests.` | |
| }); | |
| return; | |
| } | |
| // /assign @someone (maintainer-only) | |
| if (isForceAssign) { | |
| if (!commenterIsMaintainer) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, only maintainers can assign other users directly.` | |
| }); | |
| return; | |
| } | |
| if (await hasTooManyOpenAssignments(targetUser)) { | |
| await replyLimit(targetUser); | |
| return; | |
| } | |
| const isAlreadyPrimary = queueState.primary.toLowerCase() === targetUser.toLowerCase(); | |
| if (!isAlreadyPrimary && currentAssignees.length > 0) { | |
| await github.rest.issues.removeAssignees({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| assignees: currentAssignees | |
| }); | |
| } | |
| if (!lowerCurrentAssignees.includes(targetUser.toLowerCase())) { | |
| await github.rest.issues.addAssignees({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| assignees: [targetUser] | |
| }); | |
| } | |
| const now = new Date().toISOString(); | |
| queueState.primary = targetUser; | |
| queueState.primary_assigned_at = now; | |
| queueState.backup = ""; | |
| queueState.backup_approach = ""; | |
| queueState.last_progress_at = now; | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: queueComment.id, | |
| body: buildQueueBody(queueState) | |
| }); | |
| const labels = (issue.labels || []).map(l => String(l.name || "").toLowerCase()); | |
| const labelsToAdd = ["assigned"].filter(label => !labels.includes(label)); | |
| if (labelsToAdd.length > 0) { | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| labels: labelsToAdd | |
| }); | |
| } catch (e) {} | |
| } | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| name: "available" | |
| }); | |
| } catch (e) {} | |
| const forceBody = | |
| `🎯 @${targetUser} has been assigned to this issue by a maintainer. ` + | |
| `Please share meaningful progress within 3 days, or it may be opened to others.\n`; | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: forceBody | |
| }); | |
| return; | |
| } | |
| // Plain /assign (self-claim) | |
| if (await hasTooManyOpenAssignments(commenter)) { | |
| await replyLimit(commenter); | |
| return; | |
| } | |
| const hasPrimaryAssignee = currentAssignees.length > 0; | |
| if (!hasPrimaryAssignee) { | |
| if (!lowerCurrentAssignees.includes(commenterLower)) { | |
| await github.rest.issues.addAssignees({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| assignees: [commenter] | |
| }); | |
| } | |
| const now = new Date().toISOString(); | |
| queueState.primary = commenter; | |
| queueState.primary_assigned_at = now; | |
| queueState.backup = ""; | |
| queueState.backup_approach = ""; | |
| queueState.last_progress_at = now; | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: queueComment.id, | |
| body: buildQueueBody(queueState) | |
| }); | |
| const labels = (issue.labels || []).map(l => String(l.name || "").toLowerCase()); | |
| const labelsToAdd = ["assigned"].filter(label => !labels.includes(label)); | |
| if (labelsToAdd.length > 0) { | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| labels: labelsToAdd | |
| }); | |
| } catch (e) {} | |
| } | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| name: "available" | |
| }); | |
| } catch (e) {} | |
| const authorWelcome = | |
| `🎉 @${commenter} has been assigned to this issue. Welcome to LearnHub.\n\n` + | |
| `Hope you do well on this issue. Please make sure you:\n` + | |
| `- Star the repository\n` + | |
| `- Fork the repository\n` + | |
| `- Read \`CONTRIBUTING.md\`\n` + | |
| `- Read \`CODE_OF_CONDUCT.md\`\n` + | |
| `- Understand the issue clearly before starting\n` + | |
| `- Share meaningful progress within 3 days\n\n` + | |
| `If you need help or may be delayed, please ping @udaycodespace.\n\n` + | |
| `Note: PRs with fewer than 20 lines are not accepted.\n`; | |
| const nonAuthorWelcome = | |
| `🎉 @${commenter} has been assigned to this issue. Welcome to LearnHub.\n\n` + | |
| `Please make sure you:\n` + | |
| `- Star the repository\n` + | |
| `- Fork the repository\n` + | |
| `- Read \`CONTRIBUTING.md\`\n` + | |
| `- Read \`CODE_OF_CONDUCT.md\`\n` + | |
| `- Understand the issue clearly before starting\n\n` + | |
| `Please share meaningful progress within 3 days, or it may be made available to others.\n`; | |
| const welcomeBody = isSelfIssue ? authorWelcome : nonAuthorWelcome; | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: welcomeBody | |
| }); | |
| } else { | |
| if (lowerCurrentAssignees.includes(commenterLower)) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, you are already assigned to this issue. Please focus on making progress and sharing updates within 3 days.` | |
| }); | |
| } else { | |
| const primary = currentAssignees[0]; | |
| const alreadyBody = | |
| `@${commenter}, first priority for this issue is with @${primary}.\n\n` + | |
| `If you’d like to be next in queue, please use \`/work <approach>\` and describe how you plan to solve it. ` + | |
| `After the 3-day window, you may be auto-promoted if there is no meaningful progress from the primary assignee.\n`; | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: alreadyBody | |
| }); | |
| } | |
| } | |
| return; | |
| } | |
| // /work <approach> | |
| if (isWorkCommand) { | |
| if (issue.pull_request) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, work queue commands are only for issues, not pull requests.` | |
| }); | |
| return; | |
| } | |
| const approach = workMatch[1].trim(); | |
| if (!approach) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, please provide your approach after \`/work\`. Example: \`/work I will reproduce the issue locally, trace the flow, and submit a tested fix.\`` | |
| }); | |
| return; | |
| } | |
| if (await hasTooManyOpenAssignments(commenter)) { | |
| await replyLimit(commenter); | |
| return; | |
| } | |
| const primary = queueState.primary || (currentAssignees[0] || ""); | |
| if (!primary) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, this issue is currently unassigned. You can claim it directly with \`/assign\`.` | |
| }); | |
| return; | |
| } | |
| if (!queueState.backup) { | |
| queueState.backup = commenter; | |
| queueState.backup_approach = approach; | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: queueComment.id, | |
| body: buildQueueBody(queueState) | |
| }); | |
| const workBody = | |
| `@${commenter}, first priority is with @${primary}. You are now next in the queue for this issue with the following approach:\n\n` + | |
| `\`${approach}\`\n\n` + | |
| `Please make sure you:\n` + | |
| `- Star the repository\n` + | |
| `- Fork the repository\n` + | |
| `- Understand the issue clearly\n\n` + | |
| `If @${primary} doesn’t show meaningful progress within 3 days, you may be auto-promoted to primary assignee.\n`; | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: workBody | |
| }); | |
| } else if (queueState.backup.toLowerCase() === commenterLower) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, you are already in the backup queue for this issue. Please focus on your described approach and wait for the 3-day window.` | |
| }); | |
| } else { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, the backup slot for this issue is already taken by @${queueState.backup}. Please pick another issue to work on.` | |
| }); | |
| } | |
| return; | |
| } | |
| // /unassign | |
| if (isUnassignCommand) { | |
| const isPrimary = lowerCurrentAssignees.includes(commenterLower); | |
| if (!isPrimary && !commenterIsMaintainer) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, you cannot unassign this issue because it is not assigned to you.` | |
| }); | |
| return; | |
| } | |
| const usersToRemove = commenterIsMaintainer ? currentAssignees : [commenter]; | |
| if (usersToRemove.length === 0) { | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `@${commenter}, this issue is already unassigned.` | |
| }); | |
| return; | |
| } | |
| await github.rest.issues.removeAssignees({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| assignees: usersToRemove | |
| }); | |
| queueState.primary = ""; | |
| queueState.primary_assigned_at = ""; | |
| queueState.backup = ""; | |
| queueState.backup_approach = ""; | |
| queueState.last_progress_at = ""; | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: queueComment.id, | |
| body: buildQueueBody(queueState) | |
| }); | |
| try { | |
| await github.rest.issues.removeLabel({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| name: "assigned" | |
| }); | |
| } catch (e) {} | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| labels: ["available"] | |
| }); | |
| } catch (e) {} | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: `🧹 This issue is now unassigned and open for other contributors.` | |
| }); | |
| return; | |
| } | |
| auto-promotion: | |
| if: ${{ github.event_name == 'schedule' }} | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Auto-promote backup claimants after 3 days | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| script: | | |
| const core = require("@actions/core"); | |
| const { github, context } = require("@actions/github"); | |
| const owner = context.repo.owner; | |
| const repo = context.repo.repo; | |
| const { data: issues } = await github.rest.issues.listForRepo({ | |
| owner, | |
| repo, | |
| state: "open", | |
| labels: "assigned", | |
| per_page: 100 | |
| }); | |
| const QUEUE_MARKER = "<!-- learnhub-queue-state"; | |
| function parseQueueState(body) { | |
| const state = { | |
| primary: "", | |
| primary_assigned_at: "", | |
| backup: "", | |
| backup_approach: "", | |
| last_progress_at: "" | |
| }; | |
| if (!body) return state; | |
| const lines = String(body).split("\n"); | |
| for (const line of lines) { | |
| const m = line.match(/^\s*([a-z_]+):\s*(.*)\s*$/i); | |
| if (m) { | |
| const key = m[1].trim(); | |
| const val = m[2].trim(); | |
| if (Object.prototype.hasOwnProperty.call(state, key)) { | |
| state[key] = val; | |
| } | |
| } | |
| } | |
| return state; | |
| } | |
| function buildQueueBody(state) { | |
| return ( | |
| `${QUEUE_MARKER}\n` + | |
| `primary: ${state.primary || ""}\n` + | |
| `primary_assigned_at: ${state.primary_assigned_at || ""}\n` + | |
| `backup: ${state.backup || ""}\n` + | |
| `backup_approach: ${state.backup_approach || ""}\n` + | |
| `last_progress_at: ${state.last_progress_at || ""}\n` + | |
| `-->` | |
| ); | |
| } | |
| const now = Date.now(); | |
| const threeDaysMs = 3 * 24 * 60 * 60 * 1000; | |
| for (const issue of issues) { | |
| if (issue.pull_request) continue; | |
| const issueNumber = issue.number; | |
| const comments = await github.paginate(github.rest.issues.listComments, { | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| per_page: 100 | |
| }); | |
| const queueComment = comments.find(c => | |
| String(c.body || "").includes(QUEUE_MARKER) | |
| ); | |
| if (!queueComment) { | |
| continue; | |
| } | |
| let queueState = parseQueueState(queueComment.body || ""); | |
| const primary = queueState.primary; | |
| const backup = queueState.backup; | |
| if (!primary || !backup) { | |
| continue; | |
| } | |
| const activityIso = queueState.last_progress_at || queueState.primary_assigned_at; | |
| if (!activityIso) { | |
| continue; | |
| } | |
| const activityTime = Date.parse(activityIso); | |
| if (Number.isNaN(activityTime)) { | |
| continue; | |
| } | |
| const inactiveForMs = now - activityTime; | |
| if (inactiveForMs < threeDaysMs) { | |
| continue; | |
| } | |
| const currentAssignees = (issue.assignees || []).map(a => a.login); | |
| if (currentAssignees.length > 0) { | |
| await github.rest.issues.removeAssignees({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| assignees: currentAssignees | |
| }); | |
| } | |
| await github.rest.issues.addAssignees({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| assignees: [backup] | |
| }); | |
| const promotionTimeIso = new Date().toISOString(); | |
| queueState.primary = backup; | |
| queueState.primary_assigned_at = promotionTimeIso; | |
| queueState.backup = ""; | |
| queueState.backup_approach = ""; | |
| queueState.last_progress_at = promotionTimeIso; | |
| await github.rest.issues.updateComment({ | |
| owner, | |
| repo, | |
| comment_id: queueComment.id, | |
| body: buildQueueBody(queueState) | |
| }); | |
| try { | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| labels: ["auto-promoted"] | |
| }); | |
| } catch (e) {} | |
| const promotionBody = | |
| `⏳ The primary claimant did not show enough progress in 3 days. @${backup} is now the new priority assignee for this issue.\n\n` + | |
| `Please:\n` + | |
| `- Star the repository\n` + | |
| `- Fork the repository\n` + | |
| `- Understand the issue clearly\n` + | |
| `- Share meaningful progress as soon as you can.\n`; | |
| await github.rest.issues.createComment({ | |
| owner, | |
| repo, | |
| issue_number: issueNumber, | |
| body: promotionBody | |
| }); | |
| } |