Skip to content

Commit 2da6f3f

Browse files
authored
Merge pull request #143 from engmung/dev
ci: GitHub Actions — web build, Discord notify, roadmap snapshot
2 parents 8bf1d3e + 26ad355 commit 2da6f3f

9 files changed

Lines changed: 517 additions & 2 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!-- Keep it short. Delete any line that doesn't apply. -->
2+
3+
## What
4+
<!-- One or two sentences: what does this change do? -->
5+
6+
## Why
7+
<!-- The reason / the issue it fixes. Link issues like: Fixes #123 -->
8+
9+
## Tested
10+
<!-- How did you check it works? (built locally, flashed the board, ran the site…) -->
11+
12+
---
13+
- [ ] Touches `web/` → it builds (`npm run build`)
14+
- [ ] Sharing a pattern? It's CC-BY-SA 4.0 with an `// Author:` header (see CONTRIBUTING)

.github/release.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Groups merged PRs into sections when you click "Generate release notes"
2+
# on a new GitHub release. Categories match by PR label, top to bottom.
3+
changelog:
4+
exclude:
5+
labels:
6+
- skip-changelog
7+
categories:
8+
- title: 🚀 New features
9+
labels:
10+
- "type:feature"
11+
- title: ✨ Improvements
12+
labels:
13+
- "type:improvement"
14+
- title: 🐛 Fixes
15+
labels:
16+
- "type:bug"
17+
- title: 📖 Docs
18+
labels:
19+
- "area:docs"
20+
- title: 🧩 Other changes
21+
labels:
22+
- "*"

.github/scripts/build-roadmap.mjs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// Fetches open issues from the public repo and writes a static snapshot to
2+
// web/public/roadmap.json. Shape matches the old /api/roadmap response so the
3+
// /roadmap page can read the static file with no runtime token or rate limit.
4+
//
5+
// Run in CI (uses the built-in GITHUB_TOKEN) or locally:
6+
// GITHUB_TOKEN=ghp_xxx node .github/scripts/build-roadmap.mjs
7+
import { writeFile, mkdir } from "node:fs/promises";
8+
import { dirname, resolve } from "node:path";
9+
10+
const REPO = process.env.ROADMAP_REPO || "engmung/PatternFlow";
11+
const PER_PAGE = 30;
12+
const OUT = resolve(process.cwd(), "web/public/roadmap.json");
13+
14+
const headers = {
15+
Accept: "application/vnd.github+json",
16+
"X-GitHub-Api-Version": "2022-11-28",
17+
"User-Agent": "patternflow-roadmap-snapshot",
18+
};
19+
if (process.env.GITHUB_TOKEN) {
20+
headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`;
21+
}
22+
23+
const res = await fetch(
24+
`https://api.github.com/repos/${REPO}/issues?state=open&per_page=${PER_PAGE}&sort=updated&direction=desc`,
25+
{ headers },
26+
);
27+
if (!res.ok) {
28+
throw new Error(`GitHub responded ${res.status}: ${await res.text()}`);
29+
}
30+
31+
const raw = await res.json();
32+
const issues = raw
33+
// The issues endpoint also returns pull requests — drop them.
34+
.filter((item) => !item.pull_request)
35+
.map((item) => ({
36+
number: item.number,
37+
title: item.title,
38+
url: item.html_url,
39+
state: item.state,
40+
updatedAt: item.updated_at,
41+
comments: item.comments,
42+
labels: (item.labels ?? [])
43+
.map((label) =>
44+
typeof label === "string"
45+
? { name: label, color: "888888" }
46+
: { name: label.name, color: label.color || "888888" },
47+
)
48+
.filter((label) => label.name),
49+
subIssues:
50+
item.sub_issues_summary && item.sub_issues_summary.total > 0
51+
? {
52+
total: item.sub_issues_summary.total,
53+
completed: item.sub_issues_summary.completed,
54+
percent: item.sub_issues_summary.percent_completed,
55+
}
56+
: null,
57+
}));
58+
59+
const payload = { repo: REPO, fetchedAt: new Date().toISOString(), issues };
60+
61+
await mkdir(dirname(OUT), { recursive: true });
62+
await writeFile(OUT, JSON.stringify(payload, null, 2) + "\n");
63+
console.log(`Wrote ${issues.length} issues to ${OUT}`);
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
name: Discord notify
2+
3+
# Posts to Discord via webhooks when:
4+
# - new commits land on main → #dev-log (DISCORD_WEBHOOK_DEV)
5+
# - a GitHub release published → #announcements (DISCORD_WEBHOOK_ANNOUNCE)
6+
# Set both secrets under Settings → Secrets and variables → Actions.
7+
# If a secret is missing, that job no-ops instead of failing.
8+
on:
9+
push:
10+
branches: [main]
11+
release:
12+
types: [published]
13+
14+
jobs:
15+
commits:
16+
if: github.event_name == 'push'
17+
runs-on: ubuntu-latest
18+
steps:
19+
- name: Post commits to #dev-log
20+
uses: actions/github-script@v7
21+
env:
22+
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_DEV }}
23+
with:
24+
script: |
25+
const webhook = process.env.DISCORD_WEBHOOK;
26+
if (!webhook) { core.info("DISCORD_WEBHOOK not set — skipping."); return; }
27+
28+
const commits = context.payload.commits || [];
29+
if (commits.length === 0) { core.info("No commits in payload — skipping."); return; }
30+
31+
const repo = `${context.repo.owner}/${context.repo.repo}`;
32+
const lines = commits.map((c) => {
33+
const sha = c.id.slice(0, 7);
34+
const msg = c.message.split("\n")[0].slice(0, 120);
35+
return `[\`${sha}\`](${c.url}) ${msg} — ${c.author.name}`;
36+
});
37+
38+
const payload = {
39+
embeds: [{
40+
title: `📦 ${commits.length} new commit${commits.length > 1 ? "s" : ""} on main`,
41+
description: lines.join("\n").slice(0, 4000),
42+
url: context.payload.compare,
43+
color: 0x5865f2,
44+
footer: { text: repo },
45+
timestamp: new Date().toISOString(),
46+
}],
47+
};
48+
49+
const res = await fetch(webhook, {
50+
method: "POST",
51+
headers: { "Content-Type": "application/json" },
52+
body: JSON.stringify(payload),
53+
});
54+
if (!res.ok) core.setFailed(`Discord responded ${res.status}: ${await res.text()}`);
55+
56+
release:
57+
if: github.event_name == 'release'
58+
runs-on: ubuntu-latest
59+
steps:
60+
- name: Post release to #announcements
61+
uses: actions/github-script@v7
62+
env:
63+
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK_ANNOUNCE }}
64+
with:
65+
script: |
66+
const webhook = process.env.DISCORD_WEBHOOK;
67+
if (!webhook) { core.info("DISCORD_WEBHOOK not set — skipping."); return; }
68+
69+
const r = context.payload.release;
70+
const repo = `${context.repo.owner}/${context.repo.repo}`;
71+
const body = (r.body || "").trim().slice(0, 3500) || "_No release notes._";
72+
73+
const payload = {
74+
content: "@here",
75+
embeds: [{
76+
title: `🚀 ${r.name || r.tag_name}`,
77+
description: body,
78+
url: r.html_url,
79+
color: 0x57f287,
80+
footer: { text: repo },
81+
timestamp: r.published_at,
82+
}],
83+
};
84+
85+
const res = await fetch(webhook, {
86+
method: "POST",
87+
headers: { "Content-Type": "application/json" },
88+
body: JSON.stringify(payload),
89+
});
90+
if (!res.ok) core.setFailed(`Discord responded ${res.status}: ${await res.text()}`);
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
name: Roadmap snapshot
2+
3+
# Regenerates web/public/roadmap.json from open issues so the /roadmap page
4+
# reads a static file (no runtime GitHub token, no rate limit). Runs every few
5+
# hours, whenever issues change, and on manual dispatch.
6+
on:
7+
schedule:
8+
- cron: "0 */6 * * *" # every 6 hours
9+
issues:
10+
types: [opened, closed, reopened, edited, labeled, unlabeled]
11+
workflow_dispatch:
12+
13+
# Only one snapshot at a time; let the newest win.
14+
concurrency:
15+
group: roadmap-snapshot
16+
cancel-in-progress: true
17+
18+
jobs:
19+
snapshot:
20+
runs-on: ubuntu-latest
21+
permissions:
22+
contents: write
23+
steps:
24+
- uses: actions/checkout@v4
25+
26+
- uses: actions/setup-node@v4
27+
with:
28+
node-version: 20
29+
30+
- name: Build roadmap.json
31+
env:
32+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33+
run: node .github/scripts/build-roadmap.mjs
34+
35+
- name: Commit if changed
36+
run: |
37+
git config user.name "github-actions[bot]"
38+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
39+
if git diff --quiet -- web/public/roadmap.json; then
40+
echo "No changes to roadmap.json."
41+
exit 0
42+
fi
43+
git add web/public/roadmap.json
44+
git commit -m "chore(roadmap): refresh snapshot [skip ci]"
45+
git push

.github/workflows/web-ci.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Web CI
2+
3+
# Lint + build the Next.js site so broken code can't reach main.
4+
# Only runs when something under web/ (or this workflow) changes.
5+
on:
6+
pull_request:
7+
paths:
8+
- "web/**"
9+
- ".github/workflows/web-ci.yml"
10+
push:
11+
branches: [main]
12+
paths:
13+
- "web/**"
14+
- ".github/workflows/web-ci.yml"
15+
16+
# Cancel an in-progress run if newer commits are pushed to the same ref.
17+
concurrency:
18+
group: web-ci-${{ github.ref }}
19+
cancel-in-progress: true
20+
21+
jobs:
22+
build:
23+
runs-on: ubuntu-latest
24+
defaults:
25+
run:
26+
working-directory: web
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- uses: actions/setup-node@v4
31+
with:
32+
node-version: 20
33+
cache: npm
34+
cache-dependency-path: web/package-lock.json
35+
36+
- name: Install dependencies
37+
run: npm ci
38+
39+
# Lint is reported but does not block the merge yet — the repo still has
40+
# pre-existing lint errors. Drop continue-on-error once those are cleaned
41+
# up to make lint a hard gate too.
42+
- name: Lint
43+
run: npm run lint
44+
continue-on-error: true
45+
46+
# Build is the hard gate: broken code can't reach main.
47+
- name: Build
48+
run: npm run build

CONTRIBUTING.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,23 @@ Custom patterns are welcome as community work, but official bundled firmware pat
1919

2020
**Licensing — inbound = outbound.** By sharing a pattern (Discord, issue, or PR) you agree to license it under **CC-BY-SA 4.0** — the same commons as the rest of Patternflow — with attribution kept in the code header (`// Author:` and `// SPDX-License-Identifier: CC-BY-SA-4.0`). There is no copyright assignment (no CLA): you keep authorship, and the project just gets the right to bundle and redistribute it. You may set a different license in the header as long as it still lets the project bundle and redistribute the pattern.
2121

22+
## Commit messages
23+
24+
Keep it simple: start with the area, then a short summary in plain present tense.
25+
26+
```
27+
area: short summary
28+
29+
web: collapse the preset list on mobile
30+
firmware: fix reversed encoder direction
31+
docs: clarify the breadboard wiring step
32+
```
33+
34+
Common areas: `web`, `firmware`, `pcb`, `enclosure`, `docs`, `pattern`. Use
35+
`wip(area): …` for work that isn't finished yet. That's the whole rule — no
36+
tooling enforces it, it just keeps the history (and the Discord dev-log)
37+
readable.
38+
2239
## Project Rules
2340

2441
Project rules and guidelines are currently under consideration and will be finalized in the future.

0 commit comments

Comments
 (0)