-
Notifications
You must be signed in to change notification settings - Fork 298
feat: add label-command trigger (On Label Command) #21118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a1689ef
1039de5
d75f850
d8365f0
36f9f3a
446975f
2e28c38
9f70032
d8c9fe0
000bada
be2a502
38cc6ab
eb5c443
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,139 @@ | ||
| // @ts-check | ||
| /// <reference types="@actions/github-script" /> | ||
|
|
||
| const { ERR_API, ERR_CONFIG } = require("./error_codes.cjs"); | ||
|
|
||
| /** | ||
| * Remove the label that triggered this workflow from the issue, pull request, or discussion. | ||
| * This allows the same label to be applied again later to re-trigger the workflow. | ||
| * | ||
| * Supported events: issues (labeled), pull_request (labeled), discussion (labeled). | ||
| * For workflow_dispatch, the step emits an empty label_name output and exits without error. | ||
| */ | ||
| async function main() { | ||
| const labelNamesJSON = process.env.GH_AW_LABEL_NAMES; | ||
|
|
||
| const { getErrorMessage } = require("./error_helpers.cjs"); | ||
|
|
||
| if (!labelNamesJSON) { | ||
| core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_LABEL_NAMES not specified.`); | ||
| return; | ||
| } | ||
|
|
||
| let labelNames = []; | ||
| try { | ||
| labelNames = JSON.parse(labelNamesJSON); | ||
| if (!Array.isArray(labelNames)) { | ||
| core.setFailed(`${ERR_CONFIG}: Configuration error: GH_AW_LABEL_NAMES must be a JSON array.`); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| return; | ||
| } | ||
| } catch (error) { | ||
| core.setFailed(`${ERR_CONFIG}: Configuration error: Failed to parse GH_AW_LABEL_NAMES: ${getErrorMessage(error)}`); | ||
| return; | ||
| } | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor nit: |
||
| const eventName = context.eventName; | ||
|
|
||
| // For workflow_dispatch and other non-labeled events, nothing to remove. | ||
| if (eventName === "workflow_dispatch") { | ||
| core.info("Event is workflow_dispatch – skipping label removal."); | ||
| core.setOutput("label_name", ""); | ||
| return; | ||
| } | ||
|
|
||
| // Retrieve the label that was added from the event payload. | ||
| const triggerLabel = context.payload?.label?.name; | ||
| if (!triggerLabel) { | ||
| core.info(`Event ${eventName} has no label payload – skipping label removal.`); | ||
| core.setOutput("label_name", ""); | ||
| return; | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good defensive check here — verifying |
||
|
|
||
| // Confirm that this label is one of the configured command labels. | ||
| if (!labelNames.includes(triggerLabel)) { | ||
| core.info(`Trigger label '${triggerLabel}' is not in the configured label-command list [${labelNames.join(", ")}] – skipping removal.`); | ||
| core.setOutput("label_name", triggerLabel); | ||
| return; | ||
| } | ||
|
|
||
| core.info(`Removing trigger label '${triggerLabel}' (event: ${eventName})`); | ||
|
|
||
| const owner = context.repo?.owner; | ||
| const repo = context.repo?.repo; | ||
| if (!owner || !repo) { | ||
| core.setFailed(`${ERR_CONFIG}: Configuration error: Unable to determine repository owner/name from context.`); | ||
| return; | ||
| } | ||
|
|
||
| try { | ||
| if (eventName === "issues") { | ||
| const issueNumber = context.payload?.issue?.number; | ||
| if (!issueNumber) { | ||
| core.warning("No issue number found in payload – skipping label removal."); | ||
| core.setOutput("label_name", triggerLabel); | ||
| return; | ||
| } | ||
| await github.rest.issues.removeLabel({ | ||
| owner, | ||
| repo, | ||
| issue_number: issueNumber, | ||
| name: triggerLabel, | ||
| }); | ||
| core.info(`✓ Removed label '${triggerLabel}' from issue #${issueNumber}`); | ||
| } else if (eventName === "pull_request") { | ||
| // Pull requests share the issues API for labels. | ||
| const prNumber = context.payload?.pull_request?.number; | ||
| if (!prNumber) { | ||
| core.warning("No pull request number found in payload – skipping label removal."); | ||
| core.setOutput("label_name", triggerLabel); | ||
| return; | ||
| } | ||
| await github.rest.issues.removeLabel({ | ||
| owner, | ||
| repo, | ||
| issue_number: prNumber, | ||
| name: triggerLabel, | ||
| }); | ||
| core.info(`✓ Removed label '${triggerLabel}' from pull request #${prNumber}`); | ||
| } else if (eventName === "discussion") { | ||
| // Discussions require the GraphQL API for label management. | ||
| const discussionNodeId = context.payload?.discussion?.node_id; | ||
| const labelNodeId = context.payload?.label?.node_id; | ||
| if (!discussionNodeId || !labelNodeId) { | ||
| core.warning("No discussion or label node_id found in payload – skipping label removal."); | ||
| core.setOutput("label_name", triggerLabel); | ||
| return; | ||
| } | ||
| await github.graphql( | ||
| ` | ||
| mutation RemoveLabelFromDiscussion($labelableId: ID!, $labelIds: [ID!]!) { | ||
| removeLabelsFromLabelable(input: { labelableId: $labelableId, labelIds: $labelIds }) { | ||
| clientMutationId | ||
| } | ||
| } | ||
| `, | ||
| { | ||
| labelableId: discussionNodeId, | ||
| labelIds: [labelNodeId], | ||
| } | ||
| ); | ||
| core.info(`✓ Removed label '${triggerLabel}' from discussion`); | ||
| } else { | ||
| core.info(`Event '${eventName}' does not support label removal – skipping.`); | ||
| } | ||
| } catch (error) { | ||
| // Non-fatal: log a warning but do not fail the step. | ||
| // A 404 status means the label is no longer present on the item (e.g., another concurrent | ||
| // workflow run already removed it), which is an expected outcome in multi-workflow setups. | ||
| const status = /** @type {any} */ error?.status; | ||
| if (status === 404) { | ||
| core.info(`Label '${triggerLabel}' is no longer present on the item – already removed by another run.`); | ||
| } else { | ||
| core.warning(`${ERR_API}: Failed to remove label '${triggerLabel}': ${getErrorMessage(error)}`); | ||
| } | ||
| } | ||
|
|
||
| core.setOutput("label_name", triggerLabel); | ||
| } | ||
|
|
||
| module.exports = { main }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clean simplification — replacing the explicit
issues: types: [labeled]+names:block with a singlelabel_command: clocloline is much more readable. The intent is immediately clear.