chore: reorganize Azure DevOps pipeline samples #15
Workflow file for this run
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: Label Fixed Issues on Merge | |
| # Trigger: a pull request is closed. | |
| # When the PR was actually MERGED, every issue it closed via a linking keyword | |
| # (Closes #N / Fixes #N / Resolves #N) is labeled `close:fixed` so the close | |
| # reason is visible at a glance and consistent with the manual triage taxonomy. | |
| # | |
| # ─── WHY pull_request_target ──────────────────────────────────────────────── | |
| # `pull_request` grants a read-only GITHUB_TOKEN to PRs opened from forks, which | |
| # would make `issues: write` unavailable and silently skip fork contributions. | |
| # `pull_request_target` runs with the base repository's trusted workflow file and | |
| # a read/write token. This workflow NEVER checks out or executes PR head code — | |
| # it only calls the GitHub API via github-script — so there is no untrusted-code | |
| # execution risk from using pull_request_target. | |
| # ──────────────────────────────────────────────────────────────────────────── | |
| on: | |
| pull_request_target: | |
| types: [closed] | |
| permissions: | |
| issues: write | |
| contents: read | |
| # The close:fixed label is defined and created by issue-labels-sync.yml | |
| # (CLOSE_LABELS) — that workflow is the single source of truth for its | |
| # color/description. This workflow only applies the existing label. | |
| env: | |
| FIXED_LABEL: 'close:fixed' | |
| jobs: | |
| label-fixed: | |
| name: Label issues closed by merge | |
| runs-on: ubuntu-latest | |
| # Only act on merged PRs — a closed-but-unmerged PR fixes nothing. | |
| if: github.event.pull_request.merged == true | |
| steps: | |
| - name: Verify close:fixed label exists | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const FIXED_LABEL = process.env.FIXED_LABEL; | |
| const { owner, repo } = context.repo; | |
| // The close:fixed label is owned by issue-labels-sync.yml. If it's | |
| // missing, fail the run rather than letting a later addLabels call | |
| // silently create a default-colored variant — that signals the | |
| // label taxonomy is out of sync and needs the sync workflow to run. | |
| try { | |
| await github.rest.issues.getLabel({ owner, repo, name: FIXED_LABEL }); | |
| core.info(`Label "${FIXED_LABEL}" exists`); | |
| } catch (err) { | |
| if (err.status === 404) { | |
| core.setFailed( | |
| `Label "${FIXED_LABEL}" does not exist. Run the "Sync Labels" ` + | |
| `workflow (issue-labels-sync.yml) to create it, then re-run.` | |
| ); | |
| return; | |
| } | |
| throw err; | |
| } | |
| - name: Apply close:fixed to auto-closed issues | |
| uses: actions/github-script@v8 | |
| with: | |
| script: | | |
| const FIXED_LABEL = process.env.FIXED_LABEL; | |
| const { owner, repo } = context.repo; | |
| const prNumber = context.payload.pull_request.number; | |
| // Resolve the issues this PR closes via linking keywords. GraphQL's | |
| // closingIssuesReferences is the authoritative source — it mirrors | |
| // exactly what GitHub itself closed on merge, so it stays correct | |
| // even if the PR body wording is unusual. | |
| const query = ` | |
| query($owner: String!, $repo: String!, $number: Int!) { | |
| repository(owner: $owner, name: $repo) { | |
| pullRequest(number: $number) { | |
| closingIssuesReferences(first: 50) { | |
| nodes { number } | |
| } | |
| } | |
| } | |
| }`; | |
| const result = await github.graphql(query, { | |
| owner, | |
| repo, | |
| number: prNumber | |
| }); | |
| const issues = result.repository.pullRequest.closingIssuesReferences.nodes || []; | |
| if (issues.length === 0) { | |
| core.info(`PR #${prNumber} closed no linked issues — nothing to label`); | |
| return; | |
| } | |
| core.info(`PR #${prNumber} closed issue(s): ${issues.map(i => '#' + i.number).join(', ')}`); | |
| let labeled = 0; | |
| for (const { number } of issues) { | |
| // Read live labels so we don't post a redundant label event | |
| // (which would needlessly re-trigger the enforce-unique workflow). | |
| const fresh = await github.rest.issues.get({ | |
| owner, | |
| repo, | |
| issue_number: number | |
| }); | |
| const labels = fresh.data.labels.map(l => (typeof l === 'string' ? l : l.name)); | |
| if (labels.includes(FIXED_LABEL)) { | |
| core.info(`#${number} already has ${FIXED_LABEL} — skipping`); | |
| continue; | |
| } | |
| await github.rest.issues.addLabels({ | |
| owner, | |
| repo, | |
| issue_number: number, | |
| labels: [FIXED_LABEL] | |
| }); | |
| core.info(`Labeled #${number} with ${FIXED_LABEL}`); | |
| labeled++; | |
| } | |
| core.info(`Done — applied ${FIXED_LABEL} to ${labeled} issue(s)`); |