Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
5212312
feat: self-host rick assets with WebP support and harden HTML pages
rlespinasse Mar 19, 2026
494e239
refactor(karaoke): extract inline JavaScript to dedicated module
rlespinasse Mar 19, 2026
8747351
feat: add link expiration, blocker detection, and generator enhancements
rlespinasse Mar 19, 2026
52b41ea
build: add cache-busting, doc linting, and test infrastructure
rlespinasse Mar 19, 2026
440f07f
ci: add Lighthouse, broken links, and preview build workflows
rlespinasse Mar 19, 2026
9bbc281
test: add comprehensive Playwright test suite
rlespinasse Mar 19, 2026
948d89b
docs: expand contributing guide and add testing reference
rlespinasse Mar 19, 2026
7c0955a
docs: restructure documentation following Diataxis method
rlespinasse Mar 19, 2026
cb295bb
fix(ci): correct pinned SHAs and add npm to dependabot
rlespinasse Mar 19, 2026
de49e91
refactor(ci): extract PR comment markdown into template files
rlespinasse Mar 19, 2026
4272e69
chore: add editorconfig, gitattributes, and node version config
rlespinasse Mar 19, 2026
02d32f5
fix(deps): use correct playwright and axe packages
rlespinasse Mar 19, 2026
688af8a
fix(test): correct webServer config and remove TypeScript syntax
rlespinasse Mar 19, 2026
37ce9b8
build: add test recipes to justfile
rlespinasse Mar 19, 2026
e84a9be
fix(ci): update lighthouse PR comment instead of creating duplicates
rlespinasse Mar 19, 2026
f6d8567
fix(ci): resolve build failure and URL check on binary files
rlespinasse Mar 19, 2026
448f933
feat: add SRI integrity hash verification workflow and document trade…
rlespinasse Mar 19, 2026
935094d
docs: add CI, SRI, and security documentation
rlespinasse Mar 19, 2026
eabf455
feat: add logo branding and unify accent color
rlespinasse Mar 19, 2026
892f0c6
fix(ci): resolve URL check and Lighthouse failures
rlespinasse Mar 19, 2026
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
32 changes: 32 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8

[*.{html,css,js}]
indent_style = space
indent_size = 4

[*.py]
indent_style = space
indent_size = 4

[*.{yml,yaml}]
indent_style = space
indent_size = 2

[*.md]
indent_style = space
indent_size = 2
trim_trailing_whitespace = false

[*.json]
indent_style = space
indent_size = 2

[justfile]
indent_style = space
indent_size = 4
24 changes: 24 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Normalize line endings
* text=auto

# Source files
*.html text
*.css text
*.js text
*.json text
*.md text
*.yml text
*.yaml text
*.py text
*.txt text

# Binary files
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.webp binary
*.avif binary
*.ico binary
*.woff binary
*.woff2 binary
9 changes: 9 additions & 0 deletions .github/comment-templates/lighthouse-report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Lighthouse Performance Report

{{status}}

## Audit Results

{{results}}

All categories must score 90 or higher.
21 changes: 21 additions & 0 deletions .github/comment-templates/preview-build.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Preview Build

✅ Build completed successfully!

## Download Preview

The built site is available as an artifact: **{{artifactName}}**

To preview locally:

1. Download the artifact from the Actions tab
2. Unzip the archive
3. Open `index.html` in your browser

Or run a simple HTTP server:

```bash
python3 -m http.server 8000
```

Then visit <http://localhost:8000>
9 changes: 9 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,12 @@ updates:
dependencies:
patterns:
- '*'

- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'weekly'
groups:
dependencies:
patterns:
- '*'
66 changes: 66 additions & 0 deletions .github/workflows/check-sri.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Check SRI Hash

on:
schedule:
# Run daily at 6 AM UTC
- cron: '0 6 * * *'
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
check-sri:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- name: Compute remote SRI hash
id: remote
run: |
HASH=$(curl -sf https://gc.zgo.at/count.js | openssl dgst -sha384 -binary | openssl base64 -A)
echo "hash=$HASH" >> "$GITHUB_OUTPUT"
echo "Remote hash: $HASH"

- name: Extract current SRI hash
id: current
run: |
HASH=$(grep -oP 'integrity="sha384-\K[^"]+' site/index.html)
echo "hash=$HASH" >> "$GITHUB_OUTPUT"
echo "Current hash: $HASH"

- name: Compare hashes
if: steps.remote.outputs.hash == steps.current.outputs.hash
run: echo "SRI hash is up to date"

- name: Update SRI hash in all HTML files
if: steps.remote.outputs.hash != steps.current.outputs.hash
run: |
OLD="${{ steps.current.outputs.hash }}"
NEW="${{ steps.remote.outputs.hash }}"
echo "Updating SRI hash: $OLD -> $NEW"
sed -i "s|integrity=\"sha384-${OLD}\"|integrity=\"sha384-${NEW}\"|g" \
site/index.html \
site/404.html \
site/about/index.html \
site/karaoke/index.html \
site/generate/index.html

- name: Create Pull Request
if: steps.remote.outputs.hash != steps.current.outputs.hash
uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
with:
branch: fix/update-goatcounter-sri
commit-message: 'fix: update GoatCounter SRI hash'
title: 'fix: update GoatCounter SRI hash'
body: |
## GoatCounter SRI hash update

The `count.js` script at `gc.zgo.at` has changed, causing the SRI integrity hash to become stale.

**Old hash:** `sha384-${{ steps.current.outputs.hash }}`
**New hash:** `sha384-${{ steps.remote.outputs.hash }}`

This was detected automatically by the `check-sri` workflow.
labels: sri-update
36 changes: 31 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
- uses: taiki-e/install-action@42721ded7ddc3cd90f687527e8602066e4e1ff3a # v2.69.2
with:
tool: just

- name: Lint HTML, CSS, and JS
id: lint
Expand All @@ -27,14 +29,18 @@ jobs:
run: |
# Extract external URLs from site files (src, href, content, @import, data-goatcounter)
# Then normalize protocol-relative URLs and deduplicate
urls=$(grep -roEh '(https?://|//)[^"'"'"' <>)]+' site/ \
urls=$(grep -roEhI --include='*.html' --include='*.css' --include='*.js' --include='*.svg' \
'(https?://|//)[^"'"'"' <>)]+' site/ \
| sed 's/^\/\//https:\/\//' \
| sed 's/;$//' \
| grep -v 'xmlns' \
| grep -v 'w3\.org' \
| grep -v 'wraas\.github\.io' \
| grep -v 'youtube\.com' \
| grep -v 'lyricsondemand\.com' \
| grep -v 'goatcounter\.com' \
| sort -u)
| grep -v 'gc\.zgo\.at' \
| sort -u || true)

if [ -z "$urls" ]; then
echo "No external URLs found"
Expand Down Expand Up @@ -113,9 +119,29 @@ jobs:

const body = `## CI Failures\n\n${sections.join('\n\n')}\n\nPlease fix before merging.`;

await github.rest.issues.createComment({
const marker = "<!-- ci-report -->";
const bodyWithMarker = marker + "\n" + body;

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});

const existing = comments.find(c => c.body.includes(marker));

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: bodyWithMarker,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: bodyWithMarker,
});
}
8 changes: 5 additions & 3 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ jobs:
- name: Setup Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5.0.0

- uses: taiki-e/install-action@42721ded7ddc3cd90f687527e8602066e4e1ff3a # v2.69.2
with:
tool: just

- name: Build site
run: |
mkdir -p _site
cp -r site/* _site/
run: just build

- name: Upload artifact
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
Expand Down
132 changes: 132 additions & 0 deletions .github/workflows/lighthouse.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
name: Lighthouse CI

on:
pull_request:
push:
branches: [main]

permissions:
contents: read
pull-requests: write

jobs:
lighthouse:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

- uses: taiki-e/install-action@42721ded7ddc3cd90f687527e8602066e4e1ff3a # v2.69.2
with:
tool: just

- name: Build site
run: just build

- name: Start local server
id: server
run: |
cd _site
python3 -m http.server 8000 > /dev/null 2>&1 &
echo "pid=$!" >> $GITHUB_OUTPUT
sleep 2

- name: Install Lighthouse
run: npm install -g lighthouse

- name: Run Lighthouse on key pages
id: lighthouse
run: |
set +e

pages=("/" "/about/" "/generate/" "/karaoke/" "/404.html")
results_json=""
all_passed=true

for page in "${pages[@]}"; do
url="http://localhost:8000${page}"
echo "Testing $url..."

lighthouse "$url" \
--chrome-flags="--headless=new --no-sandbox" \
--output=json \
--output-path=/tmp/lighthouse-${page//\//-}.json \
--disable-full-page-screenshot \
--throttling-method=devtools \
> /dev/null 2>&1 || true

if [ -f "/tmp/lighthouse-${page//\//-}.json" ]; then
perf=$(jq '.categories.performance.score * 100' "/tmp/lighthouse-${page//\//-}.json")
access=$(jq '.categories.accessibility.score * 100' "/tmp/lighthouse-${page//\//-}.json")
bp=$(jq '.categories."best-practices".score * 100' "/tmp/lighthouse-${page//\//-}.json")
seo=$(jq '.categories.seo.score * 100' "/tmp/lighthouse-${page//\//-}.json")

printf "Page: %-20s | Performance: %3.0f | Accessibility: %3.0f | Best Practices: %3.0f | SEO: %3.0f\n" "$page" "$perf" "$access" "$bp" "$seo"

if (( $(echo "$perf < 90" | bc -l) )); then
all_passed=false
fi
if (( $(echo "$access < 90" | bc -l) )); then
all_passed=false
fi
if (( $(echo "$bp < 90" | bc -l) )); then
all_passed=false
fi

results_json+="
- **$page**: Performance $perf, Accessibility $access, Best Practices $bp, SEO $seo"
fi
done

if [ "$all_passed" = false ]; then
echo "LIGHTHOUSE_RESULTS<<EOF" >> $GITHUB_ENV
echo "$results_json" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
echo "All Lighthouse checks must have scores >= 90"
exit 1
else
echo "LIGHTHOUSE_RESULTS<<EOF" >> $GITHUB_ENV
echo "$results_json" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
fi

- name: Comment on pull request with results
if: always() && github.event_name == 'pull_request'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const fs = require("fs");
const results = process.env.LIGHTHOUSE_RESULTS || "No results available";
const status = '${{ steps.lighthouse.outcome }}' === 'success' ? '✅ PASSED' : '⚠️ NEEDS IMPROVEMENT';
const template = fs.readFileSync(".github/comment-templates/lighthouse-report.md", "utf8");
const body = template.replace("{{status}}", status).replace("{{results}}", results);

const marker = "<!-- lighthouse-report -->";
const bodyWithMarker = marker + "\n" + body;

const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});

const existing = comments.find(c => c.body.includes(marker));

if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: bodyWithMarker,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: bodyWithMarker,
});
}

- name: Cleanup
if: always()
run: kill "${{ steps.server.outputs.pid }}" 2>/dev/null || true
Loading
Loading