Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/close-stale-issues.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: Close stale closing-soon issues

on:
schedule:
- cron: '0 9 * * *' # daily at 09:00 UTC
workflow_dispatch:

jobs:
close-stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const label = 'closing-soon';
const staleDays = 3;
const cutoff = new Date(Date.now() - staleDays * 24 * 60 * 60 * 1000);

const issues = await github.rest.issues.listForRepo({
...context.repo,
state: 'open',
labels: label,
per_page: 100
});

for (const issue of issues.data) {
if (issue.pull_request) continue;

const { data: events } = await github.rest.issues.listEvents({
...context.repo,
issue_number: issue.number,
per_page: 100
});

const labelEvent = events
.filter(e => e.event === 'labeled' && e.label?.name === label)
.pop();

if (!labelEvent) continue;

const labeledAt = new Date(labelEvent.created_at);
if (labeledAt > cutoff) continue;

await github.rest.issues.createComment({
...context.repo,
issue_number: issue.number,
body: `🔒 Auto-closing: this issue has had the \`${label}\` label for more than ${staleDays} days without a response.\n\nFeel free to reopen if you have additional information to share.`
});

await github.rest.issues.update({
...context.repo,
issue_number: issue.number,
state: 'closed',
state_reason: 'not_planned'
});

console.log(`Closed issue #${issue.number} (labeled ${labeledAt.toISOString()})`);
}
81 changes: 81 additions & 0 deletions .github/workflows/issue-pending-maintainer.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Issue pending-maintainer flip

on:
schedule:
- cron: '0 * * * *' # hourly safety net
issue_comment:
types: [created]
workflow_dispatch:

jobs:
check-pending:
if: >-
github.event_name == 'workflow_dispatch'
|| github.event_name == 'schedule'
|| (github.event_name == 'issue_comment' && !github.event.issue.pull_request)
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const MAINTAINER = 'pending-maintainer';
const CONTRIBUTOR = 'pending-contributor';

let issueNumbers = [];

if (context.eventName === 'workflow_dispatch' || context.eventName === 'schedule') {
const issues = await github.rest.issues.listForRepo({
...context.repo,
state: 'open',
per_page: 100
});
issueNumbers = issues.data
.filter(i => !i.pull_request)
.map(i => i.number);
} else if (context.eventName === 'issue_comment') {
issueNumbers = [context.payload.issue.number];
}

for (const issueNumber of issueNumbers) {
const { data: issue } = await github.rest.issues.get({
...context.repo,
issue_number: issueNumber
});

const labels = issue.labels.map(l => l.name);

// Skip if closing-soon or wontfix/not-planned
if (labels.includes('closing-soon')) continue;
if (labels.includes('wontfix') || labels.includes('not-planned')) continue;

// Check last human comment is from issue author
const { data: allComments } = await github.rest.issues.listComments({
...context.repo,
issue_number: issueNumber,
per_page: 100
});
const humanComments = allComments.filter(c => c.user.type !== 'Bot');
if (humanComments.length === 0) continue;

const lastCommenter = humanComments[humanComments.length - 1].user.login;
if (lastCommenter !== issue.user.login) continue;

// Flip: +pending-maintainer, -pending-contributor
if (!labels.includes(MAINTAINER)) {
await github.rest.issues.addLabels({
...context.repo,
issue_number: issueNumber,
labels: [MAINTAINER]
});
}
if (labels.includes(CONTRIBUTOR)) {
await github.rest.issues.removeLabel({
...context.repo,
issue_number: issueNumber,
name: CONTRIBUTOR
}).catch(() => {});
}
console.log(`#${issueNumber} — author replied, set ${MAINTAINER}`);
}
56 changes: 56 additions & 0 deletions .github/workflows/stale-issue.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Label stale pending-contributor issues

on:
schedule:
- cron: '0 * * * *' # hourly
workflow_dispatch:

jobs:
check-stale:
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- uses: actions/github-script@v7
with:
script: |
const CONTRIBUTOR = 'pending-contributor';
const CLOSING = 'closing-soon';
const STALE_DAYS = 7;
const cutoff = new Date(Date.now() - STALE_DAYS * 24 * 60 * 60 * 1000);

const issues = await github.rest.issues.listForRepo({
...context.repo,
state: 'open',
labels: CONTRIBUTOR,
per_page: 100
});

for (const issue of issues.data) {
if (issue.pull_request) continue;
const labels = issue.labels.map(l => l.name);
if (labels.includes(CLOSING)) continue;

// Find when pending-contributor was last applied
const { data: events } = await github.rest.issues.listEvents({
...context.repo,
issue_number: issue.number,
per_page: 100
});

const labelEvent = events
.filter(e => e.event === 'labeled' && e.label?.name === CONTRIBUTOR)
.pop();

if (!labelEvent) continue;

const labeledAt = new Date(labelEvent.created_at);
if (labeledAt > cutoff) continue;

await github.rest.issues.addLabels({
...context.repo,
issue_number: issue.number,
labels: [CLOSING]
});
console.log(`#${issue.number} — ${CONTRIBUTOR} since ${labeledAt.toISOString()}, added ${CLOSING}`);
}
Loading