Skip to content

Add app CI checks

Add app CI checks #6

Workflow file for this run

name: PR Validator
on:
pull_request:
types: [opened, edited, reopened, synchronize]
permissions:
pull-requests: write
issues: write
contents: read
jobs:
validate-pr:
name: validate-pr
runs-on: ubuntu-latest
steps:
- name: Validate PR and enforce contribution rules
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const pr = context.payload.pull_request;
const title = pr.title;
const body = pr.body || "";
const headRef = pr.head.ref;
const baseRef = pr.base.ref;
const author = pr.user.login;
const prNumber = pr.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const failures = [];
// ── Check 1: PR title format ─────────────────────────────────────────
const titleRegex = /^(feat|fix|docs|chore|refactor|test|style|perf)\((app|backend|ml-worker|repo)\): .{10,}$/;
if (!titleRegex.test(title)) {
failures.push(
`**Invalid PR title:** \`${title}\` — must follow \`type(scope): description\` format where type is one of \`feat|fix|docs|chore|refactor|test|style|perf\`, scope is one of \`app|backend|ml-worker|repo\`, and description is at least 10 characters.`
);
}
// ── Check 2: PR body sections ────────────────────────────────────────
const hasWhatChanged = /##\s*what changed/i.test(body);
const hasWhy = /##\s*why/i.test(body);
const hasHowToTest = /##\s*how to test/i.test(body);
if (!body.trim() || !hasWhatChanged || !hasWhy || !hasHowToTest) {
failures.push(
`**Missing PR description sections:** Your PR body must include all three headings: \`## What changed\`, \`## Why\`, and \`## How to test\`. Add them and fill them in.`
);
}
// ── Check 3: Linked issue ────────────────────────────────────────────
const issueRegex = /(?:closes|fixes|resolves|relates to|ref)\s+#\d+/i;
if (!issueRegex.test(body)) {
failures.push(
`**No linked issue:** Reference the issue this PR addresses using \`closes #N\`, \`fixes #N\`, or \`relates to #N\`. Open an issue first if one doesn't exist.`
);
}
// ── Check 4: Target branch ───────────────────────────────────────────
if (baseRef !== "main") {
failures.push(
`**Wrong target branch:** This PR targets \`${baseRef}\` but all PRs must target \`main\`.`
);
}
// ── Check 5: Branch name ─────────────────────────────────────────────
const branchRegex = /^(feat|fix|docs|chore|refactor|test|style|perf)\/(app|backend|ml-worker|repo|shared)\/[a-z0-9-]+$/;
if (!branchRegex.test(headRef)) {
failures.push(
`**Invalid branch name:** \`${headRef}\` — must follow \`type/scope/short-description\` e.g. \`feat/app/timetable-progress\`.`
);
}
// ── Helper: safe label operations ───────────────────────────────────
async function addLabel(name) {
try {
await github.rest.issues.addLabels({
owner, repo, issue_number: prNumber,
labels: [name],
});
} catch (e) {
// Label may not exist in the repo yet — skip silently
core.warning(`Could not add label "${name}": ${e.message}`);
}
}
async function removeLabel(name) {
try {
await github.rest.issues.removeLabel({
owner, repo, issue_number: prNumber,
name,
});
} catch (e) {
// Label not present — ignore 404
}
}
// ── All checks passed ────────────────────────────────────────────────
if (failures.length === 0) {
await addLabel("ready-for-review");
await removeLabel("invalid-pr");
console.log("✅ PR passed all validation checks.");
return;
}
// ── Checks failed — build comment ────────────────────────────────────
const bulletList = failures.map(f => `- ${f}`).join("\n");
const comment = `## ❌ PR Validation Failed
Hi @${author}, this PR was automatically closed because it does not follow Sentri's contribution guidelines. Please read [CONTRIBUTING.md](../blob/main/CONTRIBUTING.md) before re-submitting.

Check failure on line 112 in .github/workflows/pr-validator.yml

View workflow run for this annotation

GitHub Actions / .github/workflows/pr-validator.yml

Invalid workflow file

You have an error in your yaml syntax on line 112
### Issues found:
${bulletList}
### How to fix and reopen
1. Fix all issues listed above
2. Push your changes or edit the PR title/description
3. Reopen the PR — validation will run again automatically
---
_This check is automated. If you believe this is a mistake, ping a maintainer._`;
// Post comment
await github.rest.issues.createComment({
owner, repo,
issue_number: prNumber,
body: comment,
});
// Apply / remove labels
await addLabel("invalid-pr");
await removeLabel("ready-for-review");
// Close the PR
await github.rest.pulls.update({
owner, repo,
pull_number: prNumber,
state: "closed",
});
// Fail the job so the status check is red
core.setFailed(`PR validation failed with ${failures.length} issue(s).`);