Skip to content

fix tests

fix tests #162

Workflow file for this run

name: Status Labels
on:
issues:
types: [opened, reopened, assigned, unassigned, labeled, unlabeled, closed]
pull_request:
types: [opened, reopened, synchronize, ready_for_review, converted_to_draft, review_requested, review_request_removed, closed]
pull_request_review:
types: [submitted, dismissed]
permissions:
issues: write
pull-requests: read
jobs:
issue-status-labels:
if: github.event_name == 'issues'
runs-on: ubuntu-latest
steps:
- name: Sync issue status labels
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const issue = context.payload.issue;
const issueNumber = issue.number;
const state = issue.state;
const assignees = issue.assignees || [];
const labels = (issue.labels || []).map(l => l.name.toLowerCase());
const workingLabel = 'in progress';
const blockedLabels = ['blocked', 'needs-triage', 'do-not-assign'];
const STATUS_TRIAGE = 'status: triage';
const STATUS_IN_PROGRESS = 'status: in-progress';
const STATUS_BLOCKED = 'status: blocked';
const STATUS_CLOSED = 'status: closed';
const statusLabels = [STATUS_TRIAGE, STATUS_IN_PROGRESS, STATUS_BLOCKED, STATUS_CLOSED];
async function safeWrite(fn, op) {
try {
return await fn();
} catch (e) {
if (e.status === 403) {
core.warning(`Skipping ${op}: token cannot write in this context (403).`);
return null;
}
throw e;
}
}
async function ensureLabel(name) {
const existing = await github.rest.issues.getLabel({ owner, repo, name }).catch(e => {
if (e.status === 404) return null;
throw e;
});
if (existing) return;
await safeWrite(
() => github.rest.issues.createLabel({
owner,
repo,
name,
color: 'BFDADC',
description: `Issue status label: ${name}`
}),
`create label ${name}`
);
}
for (const label of statusLabels) await ensureLabel(label);
const shouldBe = new Set();
const isBlocked = labels.some(l => blockedLabels.includes(l));
const hasAssignee = assignees.length > 0;
const hasWorking = labels.includes(workingLabel);
if (state === 'closed') {
shouldBe.add(STATUS_CLOSED);
} else if (isBlocked) {
shouldBe.add(STATUS_BLOCKED);
} else if (hasAssignee || hasWorking) {
shouldBe.add(STATUS_IN_PROGRESS);
} else {
shouldBe.add(STATUS_TRIAGE);
}
const remove = statusLabels.filter(l => !shouldBe.has(l) && labels.includes(l.toLowerCase()));
if (remove.length > 0) {
for (const name of remove) {
try {
await safeWrite(
() => github.rest.issues.removeLabel({ owner, repo, issue_number: issueNumber, name }),
`remove label ${name} from issue #${issueNumber}`
);
} catch (e) {
if (e.status !== 404) throw e;
}
}
}
const add = [...shouldBe].filter(l => !labels.includes(l.toLowerCase()));
if (add.length > 0) {
await safeWrite(
() => github.rest.issues.addLabels({
owner,
repo,
issue_number: issueNumber,
labels: add
}),
`add labels to issue #${issueNumber}`
);
}
pr-status-labels:
if: github.event_name == 'pull_request' || github.event_name == 'pull_request_review'
runs-on: ubuntu-latest
steps:
- name: Sync PR status labels
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const pullNumber = context.payload.pull_request.number;
const prPayload = context.payload.pull_request;
const fromFork = !!prPayload.head?.repo?.fork || prPayload.head?.repo?.full_name !== `${owner}/${repo}`;
const pr = await github.rest.pulls.get({
owner,
repo,
pull_number: pullNumber
});
const STATUS_DRAFT = 'status: draft';
const STATUS_REVIEW = 'status: in-review';
const STATUS_APPROVED = 'status: approved';
const STATUS_CHANGES = 'status: changes-requested';
const STATUS_MERGED = 'status: merged';
const STATUS_CLOSED = 'status: closed';
const statusLabels = [STATUS_DRAFT, STATUS_REVIEW, STATUS_APPROVED, STATUS_CHANGES, STATUS_MERGED, STATUS_CLOSED];
async function safeWrite(fn, op) {
try {
return await fn();
} catch (e) {
if (e.status === 403) {
core.warning(`Skipping ${op}: token cannot write in this context (403).`);
return null;
}
throw e;
}
}
if (fromFork) {
core.warning('Skipping PR status label writes for fork-origin PR context.');
return;
}
async function ensureLabel(name) {
const existing = await github.rest.issues.getLabel({ owner, repo, name }).catch(e => {
if (e.status === 404) return null;
throw e;
});
if (existing) return;
await safeWrite(
() => github.rest.issues.createLabel({
owner,
repo,
name,
color: 'D4C5F9',
description: `PR status label: ${name}`
}),
`create label ${name}`
);
}
for (const label of statusLabels) await ensureLabel(label);
const currentLabels = (pr.data.labels || []).map(l => l.name.toLowerCase());
const shouldBe = new Set();
if (pr.data.state === 'closed') {
if (pr.data.merged_at) shouldBe.add(STATUS_MERGED);
else shouldBe.add(STATUS_CLOSED);
} else if (pr.data.draft) {
shouldBe.add(STATUS_DRAFT);
} else {
const reviews = await github.paginate(github.rest.pulls.listReviews, {
owner,
repo,
pull_number: pullNumber,
per_page: 100
});
const latestByUser = new Map();
for (const r of reviews) {
const login = r.user?.login;
if (!login) continue;
latestByUser.set(login, r.state);
}
const states = [...latestByUser.values()];
if (states.includes('CHANGES_REQUESTED')) shouldBe.add(STATUS_CHANGES);
else if (states.includes('APPROVED')) shouldBe.add(STATUS_APPROVED);
else shouldBe.add(STATUS_REVIEW);
}
const remove = statusLabels.filter(l => !shouldBe.has(l) && currentLabels.includes(l.toLowerCase()));
for (const name of remove) {
try {
await safeWrite(
() => github.rest.issues.removeLabel({
owner,
repo,
issue_number: pullNumber,
name
}),
`remove label ${name} from PR #${pullNumber}`
);
} catch (e) {
if (e.status !== 404) throw e;
}
}
const add = [...shouldBe].filter(l => !currentLabels.includes(l.toLowerCase()));
if (add.length > 0) {
await safeWrite(
() => github.rest.issues.addLabels({
owner,
repo,
issue_number: pullNumber,
labels: add
}),
`add labels to PR #${pullNumber}`
);
}