Files
enteros-landing/.gitea/workflows/ci.yml
T
hongming a4e2a112ac
ci / brand-drift (pull_request) Successful in 12s
ci / lint (pull_request) Successful in 17s
ci / build (pull_request) Successful in 32s
refactor(brand): source brand assets from @enteros/brand, and gate the drift
Rebased onto main. The original commit (e480c96) was written against d645210
and main has moved 30 commits since, including b72aedb/77b8c00 which ported
the static redesign and replaced the whole design system. Every file that
version touched is gone: src/styles/tokens.css, src/lib/fonts.ts and
src/components/Logo.tsx were deleted, main's token vocabulary shares exactly
two names with the old one (--font-display, --font-mono), and fonts moved to
public/assets/fonts/<family>/<family>-<weight>.woff2. Replaying it verbatim
would have re-added ~200KB of unreferenced duplicate fonts, a tokens.css
nothing imports, and a resurrected Logo.tsx.

The intent survives the rebase, and is now worth more than it was: main
hand-vendored its brand assets and src/app/fonts.css claims they are "per
@molecule/brand (the visual SSOT)" — a package name that does not exist, on a
claim that is only two-thirds true.

scripts/sync-brand.mjs targets what the app actually serves:

  npm run brand:sync    copy the SSOT's assets to their app paths
  npm run brand:check   fail if any committed asset differs from the SSOT

and CI gains a `brand-drift` context that runs both, so a re-vendored or
hand-edited brand asset is a red build rather than something noticed a year
later. molecule-ai/molecule-ai-sdk is public, so the job needs no token.

The manifest is derived, not typed out. The script parses the @font-face rules
from the SSOT's css/fonts.css and from src/app/fonts.css and reconciles them by
(family, weight); logo/icon filenames come from brand.json. Add a weight to the
SSOT and the manifest follows. It fails loudly and specifically rather than
producing a partial brand: no package found (lists every path tried), a
half-present package directory, an unparseable or empty @font-face set, a
missing source file, an app-declared weight the SSOT does not ship, or an empty
manifest — each is exit 1 with the reason.

Six assets are byte-identical to @enteros/brand@0.1.0 and now hard-gated:
enteros-logo.svg, enteros-icon.png, inter-tight-{400,500}.woff2,
jetbrains-mono-{400,500}.woff2.

Chillax is a declared divergence (#22): this repo ships the free-font-pack
400/500/600/700 cut, the SSOT ships 300/400/500/600, and they are differently
subset so no weight matches byte-for-byte. Reconciling changes rendered type —
five `font-weight: 620` rules would drop from Bold to Semibold — so it is a
design decision, not a sync, and it is recorded in DECLARED_DIVERGENCE with a
reason and a tracking issue rather than quietly excluded. That list is the only
escape hatch in the gate.

No rendered output changes: `next build` emits the same CSS
(2b768740753229e4.css / bb4644fbaa9e8a3b.css, both byte-identical to main),
`npm run brand:sync` rewrites nothing, tsc is clean and 7/7 tests pass.
2026-07-24 23:27:37 -07:00

132 lines
4.5 KiB
YAML

# Enter OS landing site — CI gate.
#
# Everything a Next.js static-ish marketing site can be wrong about, checked
# before merge. Two independent contexts so a red one names its own failure:
#
# build npm ci -> next build -> tsc --noEmit -> node:test
# `next build` runs FIRST because the content-collections Next plugin
# is what emits .content-collections/generated (the `content-collections`
# module alias) and .next/types — without it `tsc --noEmit` reports
# ~20 phantom TS2307/TS7006 errors. Build, then typecheck.
# lint npm ci -> eslint
# `next lint` is deprecated in Next 15.5 and removed in 16, and was
# never configured in this repo (no eslint config, no eslint dep), so
# it silently did nothing. ESLint now runs directly off
# eslint.config.mjs (next/core-web-vitals + next/typescript).
# Errors fail the job; the repo's pre-existing `no-img-element`
# findings stay warnings (this is a verbatim DOM port of a static
# design that intentionally uses plain <img>).
# brand-drift
# re-run the @enteros/brand sync and fail if any committed brand asset
# differs from the SSOT. Mirrors molecule-ai-sdk's contracts-codegen-
# drift pattern: generated/copied artifacts are committed, so the only
# thing keeping them honest is a job that regenerates them and diffs.
# Node-only, no npm install, ~2s.
#
# Branch protection on main requires every context here to be green.
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
env:
NEXT_TELEMETRY_DISABLED: "1"
jobs:
build:
name: build
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
# Matches the Dockerfile's node:22-bookworm-slim build stage.
node-version: "22"
cache: npm
- name: Install from lockfile
run: npm ci
- name: Build (emits content-collections bindings + .next/types)
run: npm run build
- name: Typecheck
run: npx tsc --noEmit
- name: Tests
run: npm test
lint:
name: lint
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
cache: npm
- name: Install from lockfile
run: npm ci
- name: ESLint
run: npm run lint
brand-drift:
name: brand-drift
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
# The brand SSOT. molecule-ai/molecule-ai-sdk is a public repo, so this
# needs no token — nothing to leak, nothing to rotate.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
repository: molecule-ai/molecule-ai-sdk
path: .brand-ssot
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
# No `npm ci`: sync-brand.mjs is deliberately node-stdlib only, so this
# context stays fast and cannot be broken by a dependency.
- name: Brand assets match @enteros/brand byte-for-byte
env:
ENTEROS_BRAND_DIR: ${{ github.workspace }}/.brand-ssot/brand
run: npm run brand:check
# `brand:check` compares; `brand:sync` writes. Running the writer too
# proves the two agree — a check that passed while the sync would have
# rewritten files would be a check that means nothing.
- name: Re-running the sync produces no diff
env:
ENTEROS_BRAND_DIR: ${{ github.workspace }}/.brand-ssot/brand
run: |
set -euo pipefail
npm run brand:sync
if ! git diff --exit-code --stat; then
echo "::error::npm run brand:sync changed committed files. Commit the result."
exit 1
fi