Skip to content

Commit d1907af

Browse files
committed
feat: add build pipeline for SVG to PNG rendering
- Playwright/Chromium-based SVG to PNG renderer (build.mjs) - Semantic-release config for automated GitHub releases - CI workflow with PR version preview - Fix wordmark-rounded fill color to match brand palette
1 parent 9c47382 commit d1907af

7 files changed

Lines changed: 5944 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: write
11+
pull-requests: write
12+
13+
jobs:
14+
build:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
with:
19+
fetch-depth: 0
20+
21+
- uses: actions/setup-node@v4
22+
with:
23+
node-version: 22
24+
cache: npm
25+
26+
- run: npm ci
27+
- run: npx playwright install --with-deps chromium
28+
- run: npm run build
29+
30+
- name: Preview release version
31+
if: github.event_name == 'pull_request'
32+
continue-on-error: true
33+
env:
34+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35+
run: |
36+
git checkout ${{ github.event.pull_request.head.sha }}
37+
OUTPUT=$(unset GITHUB_ACTIONS; npx semantic-release --dry-run --no-ci --branches ${{ github.head_ref }} 2>&1) || true
38+
echo "$OUTPUT"
39+
VERSION=$(echo "$OUTPUT" | grep -oP 'The next release version is \K[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "")
40+
if [ -n "$VERSION" ]; then
41+
gh pr comment ${{ github.event.pull_request.number }} \
42+
--body "This PR will release **v${VERSION}**"
43+
fi
44+
45+
- name: Release
46+
if: github.ref == 'refs/heads/main'
47+
env:
48+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49+
run: npx semantic-release

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
/.claude/settings.local.json
33
/notes
44
/.claude.local/
5+
/build/
6+
node_modules/

.releaserc.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"branches": "main",
3+
"plugins": [
4+
"@semantic-release/commit-analyzer",
5+
"@semantic-release/release-notes-generator",
6+
[
7+
"@semantic-release/github",
8+
{
9+
"assets": [
10+
{ "path": "build/brand-assets.tar.gz", "label": "Brand assets (SVG + PNG)" }
11+
]
12+
}
13+
]
14+
]
15+
}

assets/fink-wordmark-rounded.svg

Lines changed: 1 addition & 1 deletion
Loading

build.mjs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { chromium } from "playwright";
2+
import { readFileSync, writeFileSync, cpSync, mkdirSync, readdirSync } from "node:fs";
3+
import { execSync } from "node:child_process";
4+
5+
const assetsDir = "./build/assets";
6+
mkdirSync(assetsDir, { recursive: true });
7+
8+
// ---------------------------------------------------------------------------
9+
// 1. Determine version via semantic-release dry-run
10+
// ---------------------------------------------------------------------------
11+
12+
let version = "unreleased";
13+
try {
14+
const branch = execSync("git branch --show-current", {
15+
encoding: "utf-8",
16+
}).trim();
17+
const output = execSync(
18+
`npx semantic-release --dry-run --no-ci --branches ${branch}`,
19+
{
20+
encoding: "utf-8",
21+
env: { ...process.env, GITHUB_ACTIONS: "" },
22+
stdio: ["pipe", "pipe", "pipe"],
23+
}
24+
);
25+
const match = output.match(/next release version is (\d+\.\d+\.\d+)/);
26+
if (match) version = match[1];
27+
} catch {
28+
// no release pending or no git tags yet
29+
}
30+
console.log(`Version: ${version}`);
31+
32+
const date = new Date().toISOString().split("T")[0];
33+
const commit = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
34+
35+
writeFileSync(
36+
"./build/version.md",
37+
`# fink-brand\nversion: ${version}\nreleased: ${date}\ncommit: ${commit}\n`
38+
);
39+
40+
// ---------------------------------------------------------------------------
41+
// 2. Render SVGs to PNGs
42+
// ---------------------------------------------------------------------------
43+
44+
const svgFiles = readdirSync("assets").filter((f) => f.endsWith(".svg"));
45+
const browser = await chromium.launch();
46+
47+
for (const file of svgFiles) {
48+
cpSync(`assets/${file}`, `${assetsDir}/${file}`);
49+
50+
const svg = readFileSync(`assets/${file}`, "utf-8");
51+
52+
const widthMatch = svg.match(/width="(\d+)"/);
53+
const heightMatch = svg.match(/height="(\d+)"/);
54+
const width = widthMatch ? parseInt(widthMatch[1]) : 512;
55+
const height = heightMatch ? parseInt(heightMatch[1]) : 512;
56+
57+
const page = await browser.newPage({ viewport: { width, height } });
58+
59+
const html = `<!DOCTYPE html>
60+
<html><head><style>
61+
body { margin: 0; background: transparent; }
62+
img { display: block; width: ${width}px; height: ${height}px; }
63+
</style></head>
64+
<body><img src="data:image/svg+xml;base64,${Buffer.from(svg).toString("base64")}"></body></html>`;
65+
66+
await page.setContent(html, { waitUntil: "load" });
67+
68+
const outName = file.replace(".svg", ".png");
69+
await page.screenshot({
70+
path: `${assetsDir}/${outName}`,
71+
omitBackground: true,
72+
});
73+
74+
console.log(` ${file}${outName} (${width}x${height})`);
75+
await page.close();
76+
}
77+
78+
await browser.close();
79+
80+
// ---------------------------------------------------------------------------
81+
// 3. Package tarball
82+
// ---------------------------------------------------------------------------
83+
84+
execSync("tar -czf build/brand-assets.tar.gz -C build version.md assets");
85+
console.log(`\nDone. brand-assets.tar.gz (v${version})`);

0 commit comments

Comments
 (0)