Skip to content

chore(deps): bump fast-uri from 3.1.2 to 3.1.4 #3942

chore(deps): bump fast-uri from 3.1.2 to 3.1.4

chore(deps): bump fast-uri from 3.1.2 to 3.1.4 #3942

name: Sync Issue Lifecycle State
on:
issues:
types: [assigned, reopened, closed]
pull_request:
types: [closed]
permissions:
issues: write
pull-requests: read
jobs:
sync-state:
runs-on: ubuntu-latest
steps:
- name: Update lifecycle labels
uses: actions/github-script@v9
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const lifecycleLabels = [
{ name: 'ready', color: '0e8a16', description: 'Triaged and ready for an agent to pick up' },
{ name: 'in-progress', color: 'fbca04', description: 'Issue is actively being worked on' },
{ name: 'in-develop', color: '1d76db', description: 'Code is merged to develop, not yet on main' },
{ name: 'in-main', color: '5319e7', description: 'Code is on main and will be included in the next release' },
{ name: 'released', color: '0e8a16', description: 'Included in a published release' },
{ name: 'needs-human', color: 'b60205', description: 'Agent could not safely proceed and needs maintainer help' },
{ name: 'blocked-by-graduation', color: 'd4c5f9', description: 'Ready technically, but blocked by release phase or graduation policy' }
];
async function ensureLabel(label) {
try {
await github.rest.issues.getLabel({ owner, repo, name: label.name });
} catch (error) {
if (error.status !== 404) throw error;
await github.rest.issues.createLabel({ owner, repo, ...label });
}
}
async function addLabels(issueNumber, labels) {
if (!labels.length) return;
try {
await github.rest.issues.addLabels({ owner, repo, issue_number: issueNumber, labels });
} catch (error) {
if (error.status !== 403) throw error;
core.warning(`Label add blocked (403) on issue #${issueNumber} — likely a fork PR with read-only token.`);
}
}
async function removeLabel(issueNumber, label) {
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number: issueNumber, name: label });
} catch (error) {
if (error.status !== 404 && error.status !== 403) throw error;
if (error.status === 403) {
core.warning(`Label remove blocked (403) on issue #${issueNumber} — likely a fork PR with read-only token.`);
}
}
}
function extractLinkedIssueNumbers(text) {
const matches = [...text.matchAll(/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\s+#(\d+)/gi)];
return [...new Set(matches.map((match) => Number(match[1])))];
}
for (const label of lifecycleLabels) {
await ensureLabel(label);
}
if (context.eventName === 'issues') {
const issue = context.payload.issue;
const issueNumber = issue.number;
const action = context.payload.action;
if (action === 'assigned') {
await removeLabel(issueNumber, 'ready');
await addLabels(issueNumber, ['in-progress']);
core.info(`Marked issue #${issueNumber} as in-progress.`);
return;
}
if (action === 'reopened') {
await removeLabel(issueNumber, 'released');
const assignees = issue.assignees || [];
await addLabels(issueNumber, [assignees.length > 0 ? 'in-progress' : 'ready']);
core.info(`Restored lifecycle state for issue #${issueNumber}.`);
return;
}
if (action === 'closed') {
for (const label of ['ready', 'in-progress', 'in-develop', 'in-main']) {
await removeLabel(issueNumber, label);
}
const currentLabelNames = (issue.labels || []).map((label) => label.name);
const nonReleaseClosure = currentLabelNames.some((label) => ['duplicate', 'invalid', 'wontfix'].includes(label));
if (issue.state_reason === 'completed' && !nonReleaseClosure) {
await addLabels(issueNumber, ['released']);
}
core.info(`Finalized lifecycle labels for issue #${issueNumber}.`);
}
return;
}
if (context.eventName === 'pull_request') {
const pr = context.payload.pull_request;
if (!pr.merged) {
core.info('Pull request was closed without merge; no lifecycle update needed.');
return;
}
const baseRef = pr.base.ref;
if (!['develop', 'main'].includes(baseRef)) {
core.info(`PR merged into ${baseRef}; lifecycle sync only applies to develop/main.`);
return;
}
const issueNumbers = extractLinkedIssueNumbers(`${pr.title}\n${pr.body || ''}`);
if (issueNumbers.length === 0) {
core.info('No linked issues found in PR body/title.');
return;
}
const targetLabel = baseRef === 'develop' ? 'in-develop' : 'in-main';
for (const issueNumber of issueNumbers) {
await removeLabel(issueNumber, 'ready');
await removeLabel(issueNumber, 'in-progress');
if (targetLabel === 'in-main') {
await removeLabel(issueNumber, 'in-develop');
}
await addLabels(issueNumber, [targetLabel]);
}
core.info(`Updated linked issues for PR #${pr.number} -> ${targetLabel}.`);
}