ci: add contributor workflow automation
- Add PR template with description, type of change, and contributing checklist - Enforce target branch: label + comment + 24h auto-close for PRs targeting main - Flag bad issue titles: label + comment + 24h auto-close instead of instant close - Redirect feature requests to Discussions (instant close, unchanged) - Add two scheduled workflows to close stale labeled issues and PRs after 24h - Update CONTRIBUTING.md with tests and branch up-to-date requirements
This commit is contained in:
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
21
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
## Description
|
||||
<!-- What does this PR do? Why? -->
|
||||
|
||||
## Related Issue or Discussion
|
||||
<!-- This project requires an issue or an approved feature request before submitting a PR. -->
|
||||
<!-- For bug fixes: Closes #ISSUE_NUMBER -->
|
||||
<!-- For features: Addresses discussion #DISCUSSION_NUMBER -->
|
||||
|
||||
## Type of Change
|
||||
- [ ] Bug fix
|
||||
- [ ] New feature
|
||||
- [ ] Breaking change
|
||||
- [ ] Documentation update
|
||||
|
||||
## Checklist
|
||||
- [ ] I have read the [Contributing Guidelines](https://github.com/mauriceboe/TREK/wiki/Contributing)
|
||||
- [ ] My branch is [up to date with `dev`](https://github.com/mauriceboe/TREK/wiki/Development-environment#3-keep-your-fork-up-to-date)
|
||||
- [ ] This PR targets the `dev` branch, not `main`
|
||||
- [ ] I have tested my changes locally
|
||||
- [ ] I have added/updated tests that prove my fix is effective or that my feature works
|
||||
- [ ] I have updated documentation if needed
|
||||
71
.github/workflows/close-stale-invalid-titles.yml
vendored
Normal file
71
.github/workflows/close-stale-invalid-titles.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Close issues with unchanged bad titles
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */6 * * *' # Every 6 hours
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
close-stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close stale invalid-title issues
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const badTitles = [
|
||||
"[bug]", "bug report", "bug", "issue",
|
||||
"help", "question", "test", "...", "untitled"
|
||||
];
|
||||
|
||||
const { data: issues } = await github.rest.issues.listForRepo({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: 'invalid-title',
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
|
||||
for (const issue of issues) {
|
||||
const createdAt = new Date(issue.created_at);
|
||||
if (createdAt > twentyFourHoursAgo) continue; // grace period not over yet
|
||||
|
||||
const titleLower = issue.title.trim().toLowerCase();
|
||||
|
||||
if (!badTitles.includes(titleLower)) {
|
||||
// Title was fixed — remove the label and move on
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
name: 'invalid-title',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Still a bad title after 24h — close it
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
body: [
|
||||
'## Issue closed',
|
||||
'',
|
||||
'This issue has been automatically closed because the title was not updated within 24 hours.',
|
||||
'',
|
||||
'Feel free to open a new issue with a descriptive title that summarizes the problem.',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: issue.number,
|
||||
state: 'closed',
|
||||
state_reason: 'not_planned',
|
||||
});
|
||||
}
|
||||
66
.github/workflows/close-stale-wrong-branch.yml
vendored
Normal file
66
.github/workflows/close-stale-wrong-branch.yml
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Close PRs with unchanged wrong base branch
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */6 * * *' # Every 6 hours
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
close-stale:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Close stale wrong-base-branch PRs
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const { data: pulls } = await github.rest.pulls.list({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
|
||||
|
||||
for (const pull of pulls) {
|
||||
const hasLabel = pull.labels.some(l => l.name === 'wrong-base-branch');
|
||||
if (!hasLabel) continue;
|
||||
|
||||
const createdAt = new Date(pull.created_at);
|
||||
if (createdAt > twentyFourHoursAgo) continue; // grace period not over yet
|
||||
|
||||
// Base was fixed — remove label and move on
|
||||
if (pull.base.ref !== 'main') {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pull.number,
|
||||
name: 'wrong-base-branch',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Still targeting main after 24h — close it
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: pull.number,
|
||||
body: [
|
||||
'## PR closed',
|
||||
'',
|
||||
'This PR has been automatically closed because the base branch was not updated to `dev` within 24 hours.',
|
||||
'',
|
||||
'Feel free to open a new PR targeting `dev`.',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pull.number,
|
||||
state: 'closed',
|
||||
});
|
||||
}
|
||||
77
.github/workflows/close-untitled-issues.yml
vendored
77
.github/workflows/close-untitled-issues.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Close untitled issues
|
||||
name: Flag issues with bad titles
|
||||
|
||||
on:
|
||||
issues:
|
||||
@@ -10,58 +10,83 @@ permissions:
|
||||
jobs:
|
||||
check-title:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Close if title is empty or generic
|
||||
- name: Flag or redirect issue
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const title = context.payload.issue.title.trim();
|
||||
const titleLower = title.toLowerCase();
|
||||
|
||||
const badTitles = [
|
||||
"[bug]",
|
||||
"bug report",
|
||||
"bug",
|
||||
"issue",
|
||||
"[bug]", "bug report", "bug", "issue",
|
||||
"help", "question", "test", "...", "untitled"
|
||||
];
|
||||
|
||||
const featureRequestTitles = [
|
||||
"feature request",
|
||||
"[feature]",
|
||||
"[feature request]",
|
||||
"[enhancement]"
|
||||
]
|
||||
|
||||
const titleLower = title.toLowerCase();
|
||||
"feature request", "[feature]", "[feature request]", "[enhancement]"
|
||||
];
|
||||
|
||||
if (badTitles.includes(titleLower)) {
|
||||
// Ensure the label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'invalid-title',
|
||||
});
|
||||
} catch {
|
||||
await github.rest.issues.createLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'invalid-title',
|
||||
color: 'e4e669',
|
||||
description: 'Issue title does not meet quality standards',
|
||||
});
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
labels: ['invalid-title'],
|
||||
});
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
body: "This issue was closed because no title was provided. Please re-open with a descriptive title that summarizes the problem."
|
||||
body: [
|
||||
'## Invalid title',
|
||||
'',
|
||||
`Your issue title \`${title}\` is too generic to be actionable.`,
|
||||
'',
|
||||
'Please edit the title to something descriptive that summarizes the problem — for example:',
|
||||
'> _Map view crashes when zooming in on Safari 17_',
|
||||
'',
|
||||
'**This issue will be automatically closed in 24 hours if the title has not been updated.**',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned"
|
||||
});
|
||||
} else if (featureRequestTitles.some(t => titleLower.startsWith(t))) {
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
body: "Feature requests should be made in the [Discussions](https://github.com/mauriceboe/TREK/discussions/new?category=feature-requests) — not as issues. This issue has been closed."
|
||||
body: [
|
||||
'## Wrong place for feature requests',
|
||||
'',
|
||||
'Feature requests should be submitted in [Discussions](https://github.com/mauriceboe/TREK/discussions/new?category=feature-requests), not as issues.',
|
||||
'',
|
||||
'This issue has been closed. Feel free to re-submit your idea in the right place!',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
await github.rest.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.issue.number,
|
||||
state: "closed",
|
||||
state_reason: "not_planned"
|
||||
state: 'closed',
|
||||
state_reason: 'not_planned',
|
||||
});
|
||||
}
|
||||
100
.github/workflows/enforce-target-branch.yml
vendored
Normal file
100
.github/workflows/enforce-target-branch.yml
vendored
Normal file
@@ -0,0 +1,100 @@
|
||||
name: Enforce PR Target Branch
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
check-target:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- name: Flag or clear wrong base branch
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const base = context.payload.pull_request.base.ref;
|
||||
const labels = context.payload.pull_request.labels.map(l => l.name);
|
||||
const prNumber = context.payload.pull_request.number;
|
||||
|
||||
// If the base was fixed, remove the label and let it through
|
||||
if (base !== 'main') {
|
||||
if (labels.includes('wrong-base-branch')) {
|
||||
await github.rest.issues.removeLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
name: 'wrong-base-branch',
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Base is main — check if this user is a maintainer
|
||||
let permission = 'none';
|
||||
try {
|
||||
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
username: context.payload.pull_request.user.login,
|
||||
});
|
||||
permission = data.permission;
|
||||
} catch (_) {
|
||||
// User is not a collaborator — treat as 'none'
|
||||
}
|
||||
|
||||
if (['admin', 'write'].includes(permission)) {
|
||||
console.log(`User has '${permission}' permission, skipping.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Already labeled — avoid spamming on every push
|
||||
if (labels.includes('wrong-base-branch')) {
|
||||
core.setFailed("PR must target `dev`, not `main`.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the label exists
|
||||
try {
|
||||
await github.rest.issues.getLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'wrong-base-branch',
|
||||
});
|
||||
} catch {
|
||||
await github.rest.issues.createLabel({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: 'wrong-base-branch',
|
||||
color: 'd73a4a',
|
||||
description: 'PR is targeting the wrong base branch',
|
||||
});
|
||||
}
|
||||
|
||||
await github.rest.issues.addLabels({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
labels: ['wrong-base-branch'],
|
||||
});
|
||||
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: prNumber,
|
||||
body: [
|
||||
'## Wrong target branch',
|
||||
'',
|
||||
'This PR targets `main`, but contributions must go through `dev` first.',
|
||||
'',
|
||||
'To fix this, click **Edit** next to the PR title and change the base branch to `dev`.',
|
||||
'',
|
||||
'**This PR will be automatically closed in 24 hours if the base branch has not been updated.**',
|
||||
'',
|
||||
'> _If you need to merge directly to `main`, contact a maintainer._',
|
||||
].join('\n'),
|
||||
});
|
||||
|
||||
core.setFailed("PR must target `dev`, not `main`.");
|
||||
@@ -9,6 +9,8 @@ Thanks for your interest in contributing! Please read these guidelines before op
|
||||
3. **No breaking changes** — Backwards compatibility is non-negotiable
|
||||
4. **Target the `dev` branch** — All PRs must be opened against `dev`, not `main`
|
||||
5. **Match the existing style** — No reformatting, no linter config changes, no "while I'm here" cleanups
|
||||
6. **Tests** — Your changes must include tests. The project maintains 80%+ coverage; PRs that drop it will be closed
|
||||
7. **Branch up to date** — Your branch must be [up to date with `dev`](https://github.com/mauriceboe/TREK/wiki/Development-environment#3-keep-your-fork-up-to-date) before submitting a PR
|
||||
|
||||
## Pull Requests
|
||||
|
||||
@@ -35,22 +37,9 @@ fix(maps): correct zoom level on Safari
|
||||
feat(budget): add CSV export for expenses
|
||||
```
|
||||
|
||||
## Development Setup
|
||||
## Development Environment
|
||||
|
||||
```bash
|
||||
git clone https://github.com/mauriceboe/TREK.git
|
||||
cd TREK
|
||||
|
||||
# Server
|
||||
cd server && npm install && npm run dev
|
||||
|
||||
# Client (separate terminal)
|
||||
cd client && npm install && npm run dev
|
||||
```
|
||||
|
||||
Server: `http://localhost:3001` | Client: `http://localhost:5173`
|
||||
|
||||
On first run, check the server logs for the auto-generated admin credentials.
|
||||
See the [Developer Environment page](https://github.com/mauriceboe/TREK/wiki/Development-environment) for more information on setting up your development environment.
|
||||
|
||||
## More Details
|
||||
|
||||
|
||||
Reference in New Issue
Block a user