Skip to content

chore: reorganize Azure DevOps pipeline samples #15

chore: reorganize Azure DevOps pipeline samples

chore: reorganize Azure DevOps pipeline samples #15

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)`);