Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
127 changes: 98 additions & 29 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
name: Build & release
name: Build, compatibility & release

# Every push / PR builds the theme and uploads a drop-in package as a workflow artifact.
# Pushing a tag v* also creates a GitHub Release with the .zip / .tar.gz attached.
on:
push:
branches: [main]
Expand All @@ -10,7 +8,7 @@ on:
workflow_dispatch:

permissions:
contents: write
contents: read

jobs:
visual:
Expand All @@ -21,50 +19,121 @@ jobs:
build:
needs: visual
runs-on: ubuntu-24.04
outputs:
matrix: ${{ steps.versions.outputs.matrix }}
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
steps:
- uses: actions/checkout@v7

- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm

- run: npm ci

- name: Lint (no literal colors, tokens only, selector ownership)
run: npm run lint

- name: Build theme files
# Release tags must meet the 300 KB budget; branch builds only report it.
run: |
if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
node build/build.mjs --strict
else
node build/build.mjs --budget-warn
fi

- name: Package for Gitea CUSTOM_PATH
run: node build/package.mjs

- id: versions
name: Read the shared Gitea version matrix
run: echo "matrix=$(node build/gitea-versions.mjs)" >> "$GITHUB_OUTPUT"
- run: npm test
- uses: actions/cache@v6
with:
path: .cache/gitea-sources
key: gitea-sources-v1-${{ hashFiles('build/gitea-versions.json') }}
- name: Fetch all supported upstream source trees
run: npm run upstream
- name: Lint and check page scopes on every supported version
run: npm run lint -- --strict
- name: Build once (strict checks and 340 KB budget)
run: npm run build -- --strict
- name: Package once for the entire matrix
run: npm run package
- name: Summary
run: |
echo "### Theme files" >> "$GITHUB_STEP_SUMMARY"
node -e "const r=require('./dist/build-report.json');for(const [n,t] of Object.entries(r.themes))console.log('- theme-'+n+'.css: '+t.kb+' KB'+(t.overBudget?' (over 300 KB budget)':''))" >> "$GITHUB_STEP_SUMMARY"

node --input-type=module <<'JS' >> "$GITHUB_STEP_SUMMARY"
import fs from 'node:fs';
const r = JSON.parse(fs.readFileSync('dist/build-report.json'));
console.log('### One package for Gitea ' + r.giteaVersions.join(', '));
for (const [name, theme] of Object.entries(r.themes)) console.log(`- theme-${name}.css: ${theme.kb} KB`);
JS
- uses: actions/upload-artifact@v7
with:
name: gitea-theme-github
path: |
dist/gitea-theme-github-*.zip
dist/gitea-theme-github-*.tar.gz
dist/gitea-theme-github-*.zip
dist/theme-github-*.css
dist/build-report.json
if-no-files-found: error

- name: GitHub Release
if: startsWith(github.ref, 'refs/tags/v')
uses: softprops/action-gh-release@v3
compatibility:
needs: build
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.build.outputs.matrix) }}
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1'
GITEA_VERSION: ${{ matrix.version }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- uses: actions/download-artifact@v8
with:
name: gitea-theme-github
path: dist
- run: npx playwright install --with-deps chromium
- name: Test the unchanged package on Gitea ${{ matrix.version }}
run: |
packages=(dist/gitea-theme-github-*.tar.gz)
test "${#packages[@]}" -eq 1
npm run compat -- --version "$GITEA_VERSION" --package "${packages[0]}" --out "shots/compat/$GITEA_VERSION"
- uses: actions/upload-artifact@v7
if: always()
with:
name: compatibility-${{ matrix.version }}
path: shots/compat/${{ matrix.version }}/
if-no-files-found: warn

release:
needs: [build, compatibility]
if: startsWith(github.ref, 'refs/tags/v')
runs-on: ubuntu-24.04
permissions:
contents: write
env:
GITEA_MATRIX: ${{ needs.build.outputs.matrix }}
steps:
- uses: actions/setup-node@v7
with:
node-version: 24
- uses: actions/download-artifact@v8
with:
name: gitea-theme-github
path: dist
- uses: actions/download-artifact@v8
with:
pattern: compatibility-*
path: compatibility-reports
- name: Verify every matrix result tested exactly this archive
run: |
node --input-type=module <<'JS'
import assert from 'node:assert/strict';
import fs from 'node:fs';
import crypto from 'node:crypto';
const archives = fs.readdirSync('dist').filter(name => name.endsWith('.tar.gz'));
assert.equal(archives.length, 1);
const sha = crypto.createHash('sha256').update(fs.readFileSync(`dist/${archives[0]}`)).digest('hex');
for (const version of JSON.parse(process.env.GITEA_MATRIX).version) {
const report = JSON.parse(fs.readFileSync(`compatibility-reports/compatibility-${version}/report.json`));
assert.equal(report.version, version);
assert.equal(report.ok, true);
assert.equal(report.packageSha256, sha);
}
JS
- uses: softprops/action-gh-release@v3
with:
files: |
dist/gitea-theme-github-*.zip
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/visual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ jobs:
node-version: 24
cache: npm
- run: npm ci
- uses: actions/cache@v6
with:
path: .cache/gitea-sources
key: gitea-sources-v1-${{ hashFiles('build/gitea-versions.json') }}
- name: Install the pinned Chromium browser and its system dependencies
run: npx playwright install --with-deps chromium
- name: Compare the candidate with the approved visual baseline
Expand Down
28 changes: 21 additions & 7 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Environment facts: `docs/CONTEXT.md`. Gitea source research (with file:line cita
| `@primer/primitives` | **11.10.0** | all tokens (`build/gen-tokens.mjs` → `src/tokens/generated/`) |
| `@primer/octicons` | **19.38.0** | icon source (Gitea 1.27.3 bundles 19.28.1) |
| `@primer/css` | **22.3.2** | reference only for component specs (not shipped) |
| Gitea | **1.27.3** and **28.1.0** | sources at `../gitea-src-1.27.3`, `../gitea-src-28.1.0`; one package for both (§7 "Two Gitea versions") |
| Gitea | **1.27.3**, **28.0.0**, **28.1.0**, **28.2.0**, **28.3.0** | CI/source matrix: `build/gitea-versions.json`; one package (§7 "Two Gitea series") |

Dependency versions are pinned in `package.json` and `package-lock.json`.

Expand Down Expand Up @@ -193,10 +193,12 @@ keep the non-GitHub output byte-identical.
Install these files into `CUSTOM_PATH/templates/` and reload templates as described in README.md's upgrade instructions.
`npm run deploy` checks for template drift but does not install template overrides.

**Two Gitea versions (1.27.x and 28.x, one package).** Each template that differs between 1.27.3 and 28.1.0 starts with
**Two Gitea series (1.27.x and 28.x, one package).** Each template that differs between 1.27.3 and 28.1.0 starts with
`{{$gitea1 := StringUtils.HasPrefix AppVer "1."}}` (no output) and branches only where upstream differs, so a non-github
theme gets each version's upstream bytes (checked: anonymous HTML of all routes with vs. without the templates, both
versions, differs only by the documented `hidden` "N Commits" link). Two rules learned the hard way:
versions, differs only by the documented `hidden` "N Commits" link). The native navbar also preserves the old avatar markup on 28.0/28.1 and the flex markup introduced in 28.2;
the GitHub navbar uses a flex layout supported by every target. The disposable compatibility check compares each
version's native forced-password navbar before and after loading the overrides. Two rules learned the hard way:
- Fields and methods are resolved at execution, so an untaken branch may name `.Repository.CanEnableEditor` (1.27) or
`CanContentChange` (28); use `and`/`or` (lazy since Go 1.18) or `{{if $gitea1}}` around them.
- `{{template "x"}}` is checked by html/template's escaper even in a branch that never runs: an undefined name breaks the
Expand Down Expand Up @@ -241,7 +243,9 @@ column colors, repo units, federation, wiki clone box, etc.) get the **closest P
use), −100 KB per file. Loop 2 also merges Primer tokens whose definitions are identical in every scheme into one short
name (91 tokens; only tokens no folder re-declares, which are all resolved on :root; `--no-dedupe`). Gitea's own names (`--color-*`, `--is-dark-theme`, anything in gitea-map.css /
scheme-*.css / folder-local names) and any Primer name mentioned in Gitea's web_src/templates or our templates are
never renamed. `dist/theme-github-*.src.css` keeps the real names; `dist/varmap.json` maps short → Primer name.
never renamed. The build scans the union of every matrix version's templates and frontend sources, also retaining
tokens used only by upstream code. Missing source trees are an error; fetch them with `npm run upstream`.
`dist/theme-github-*.src.css` keeps the real names; `dist/varmap.json` maps short → Primer name.
So in DevTools a computed `--fgColor-muted` reads as `--…` — look it up in varmap.json, or build with `--no-rename`
(identical rendering: pixel-diffed on 8 routes × 2 schemes, only the footer's server-timing text differs).
- Prefix nesting (loop 1, integrator; `build/nest.mjs`): as the last step, in the minified `dist/theme-github-*.css` only,
Expand All @@ -255,10 +259,12 @@ column colors, repo units, federation, wiki clone box, etc.) get the **closest P
(TARGETS): Chrome/Edge 112+ (target 120), Safari 16.5+, Firefox 117+ — all evergreen since 2023. `--no-nest` builds the
flat file for comparison. `*.src.css` stays flat, so coverage and the
lint are unaffected; in DevTools nested rules show as `& …` under their parent.
- Page-scope check (FG2-105, `build/page-scope-check.mjs`): page-folder selector scopes vs every Gitea template's
`.page-content` classes (+ live route classes cached in `shots/page-classes.json`); each page folder declares its intended
- Page-scope check (FG2-105, `build/page-scope-check.mjs`): page-folder selector scopes vs each matrix version's Gitea templates independently
(`.page-content` classes, plus optional instance-specific live caches under `.cache/page-scope/<version>/`); each page folder declares its intended
templates in `src/pages/<group>/scopes.json`; an unintended match is a LEAK (warning in `npm run lint` and the build,
error under `--strict`). Reports: `docs/page-scope-report.{md,json}` (generated locally, ignored by Git). `--live` refetches, `--probe` counts matched elements.
error under `--strict`). Aggregate reports: `docs/page-scope-report.{md,json}`; version reports: `.cache/page-scope/<version>/report.{md,json}` (all ignored by Git).
`--version <version> --live` refetches a matching instance; `--version <version> --probe` verifies the instance, refetches
and counts matched elements. Static checks use upstream + tracked overrides; `--live`/`--probe` include live custom overrides.
- `npm run deploy` — build, then atomically copy the three files to `CUSTOM_PATH/public/assets/css/`, copy icons,
bump `github_revision` in `head_style.tmpl`, `gitea manager reload-templates`, then **fetch each file back from
Gitea and compare SHA-256** (the deploy fails if Gitea serves different bytes). Reports `restartRequired` when theme
Expand Down Expand Up @@ -294,6 +300,14 @@ Mona, Hubot, Copilot marks or other trademarks; the Gitea logo stays. Fonts: sys

## 13. Verification loop

- `npm test` — build regressions: reserve names across upstream versions, retain upstream-only tokens, reject missing
source trees, detect version-specific page leaks, ignore unrelated live caches, and keep package metadata in sync.
- `npm run compat -- --version <version> --package <archive.tar.gz>` — fresh disposable Docker database and users;
original native navbar vs package, forced-password layout, all overridden page structures, light/dark, desktop/mobile,
fixed themes, Mermaid, clipboard, JavaScript errors and CSS layering. The same tarball is used for every CI target.
GitHub Release publication waits for the matrix and validates its report checksums. The older seeded tools below
remain useful for deeper visual reviews and write-heavy functional tests.

- `tools/seed/seed.mjs` — idempotent seed via API (admin token): migrated public GitHub repos (same pages exist on
github.com), users, org + teams, a playground repo with a markdown torture-test README, large multi-file PR, Actions
runs, a package, releases, wiki. Manifest: `docs/seed-manifest.json`. Pre-seed baseline: `docs/baseline-pre-seed.json`.
Expand Down
Loading