mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
Validate / changes (push) Successful in 52s
Validate / process-gate (push) Failing after 1m17s
Validate / provenance (push) Successful in 1m20s
Validate / hacs (push) Failing after 16s
Validate / hassfest (push) Failing after 13s
Validate / frontend (push) Successful in 7m30s
Validate / backend (push) Failing after 8m41s
Validate / performance_smoke (push) Failing after 9m45s
Validate / golden (push) Failing after 14m25s
Validate / smoke (push) Failing after 28m53s
Every push to every branch ran 128 browser smokes, 50 golden scenes, a Home Assistant install and a performance pass — including a push that added one spec file. The pipeline made such pushes routine: every spec revision and every review document is a push to a task branch and used to cost the full suite. A changes job classifies the push range; frontend, smoke, golden, performance and backend now run only when their paths moved, and hacs and hassfest only for manifests, translations or Python. provenance and process-gate always run — they judge commits, not code. The exception carries the design. On dev everything runs, always, unfiltered: the beta gate accepts "green Validate at the exact SHA", and if the volume of a run depends on the diff, green stops meaning one thing — a release candidate touches manifests and changelogs, would skip the browser suites under filtering, and a run with skipped jobs still concludes success. That would be the sixth silent success of the week. Filters save time on task branches, where Validate is an early signal and the real acceptance is the code review running gates itself. A new branch with a zero before-sha is classified from the merge-base with dev, not from the root of history. Issue: #136 User-Visible: no
265 lines
11 KiB
YAML
265 lines
11 KiB
YAML
name: Validate
|
||
|
||
on:
|
||
push:
|
||
# The branch commit is the release-gate authority. An annotated tag points
|
||
# to the same SHA and must not duplicate the browser validation jobs.
|
||
branches:
|
||
- '**'
|
||
pull_request:
|
||
|
||
# A new push supersedes an unfinished validation for the same branch or PR.
|
||
# Exact-SHA release gates never depend on an obsolete commit.
|
||
concurrency:
|
||
group: validate-${{ github.event.pull_request.number || github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
jobs:
|
||
provenance:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with: { fetch-depth: 0 }
|
||
- uses: actions/setup-node@v4
|
||
with: { node-version: 22 }
|
||
- name: Validate commit trailers and hook mode
|
||
env:
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
BEFORE_SHA: ${{ github.event.before }}
|
||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||
HEAD_SHA: ${{ github.sha }}
|
||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||
run: |
|
||
node scripts/validate-commit-provenance.mjs --check-hook-mode --github-range
|
||
|
||
# Догоняющая проверка процесса (PROCESS.md §10.3). Хуки ловят нарушение на
|
||
# машине автора, но их можно обойти `--no-verify`, а коммиты идут прямо в dev
|
||
# без PR — GitHub на своей стороне не блокирует ничего. Это последнее место,
|
||
# где нарушение правила №1 ловится машиной. Job независимый: краснеет сам и
|
||
# не роняет остальные, откат — удалить его отсюда, скрипт остаётся рабочим.
|
||
process-gate:
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with: { fetch-depth: 0 }
|
||
- uses: actions/setup-node@v4
|
||
with: { node-version: 22 }
|
||
- name: Process gate
|
||
env:
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
BEFORE_SHA: ${{ github.event.before }}
|
||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||
HEAD_SHA: ${{ github.sha }}
|
||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||
TARGET_REF: ${{ github.ref }}
|
||
# Публичный репозиторий: штатного токена хватает на чтение issue.
|
||
GH_TOKEN: ${{ github.token }}
|
||
run: |
|
||
node scripts/process-gate.mjs --github-range --issues
|
||
|
||
# Классификация изменённых путей: тяжёлые job идут только там, где менялось
|
||
# относящееся к ним. НА DEV ФИЛЬТРОВ НЕТ: гейт беты принимает «зелёный Validate
|
||
# на точном SHA», и если объём прогона зависит от diff, «зелёный» перестаёт
|
||
# значить одно и то же — кандидат релиза (манифесты + changelog) пропустил бы
|
||
# браузерные тесты, а прогон с пропущенными job всё равно success. Фильтры
|
||
# экономят на ветках задач, где Validate — ранний сигнал: настоящую приёмку
|
||
# там делает код-ревью, которое гоняет гейты само (#127).
|
||
changes:
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
frontend: ${{ steps.classify.outputs.frontend }}
|
||
backend: ${{ steps.classify.outputs.backend }}
|
||
integration: ${{ steps.classify.outputs.integration }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with: { fetch-depth: 0 }
|
||
- id: classify
|
||
env:
|
||
EVENT_NAME: ${{ github.event_name }}
|
||
BEFORE_SHA: ${{ github.event.before }}
|
||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||
HEAD_SHA: ${{ github.sha }}
|
||
REF: ${{ github.ref }}
|
||
run: |
|
||
if [ "$REF" = "refs/heads/dev" ]; then
|
||
echo "dev: без фильтров, всё true"
|
||
printf 'frontend=true\nbackend=true\nintegration=true\n' >> "$GITHUB_OUTPUT"
|
||
exit 0
|
||
fi
|
||
zero=$(printf '%040d' 0)
|
||
base="$BEFORE_SHA"
|
||
if [ "$EVENT_NAME" = "pull_request" ]; then base="$BASE_SHA"; fi
|
||
# Новая ветка: before нулевой, диапазон считается от merge-base с dev,
|
||
# иначе классифицировалась бы вся история.
|
||
if [ -z "$base" ] || [ "$base" = "$zero" ] \
|
||
|| ! git cat-file -e "$base" 2>/dev/null; then
|
||
git fetch -q origin dev
|
||
base=$(git merge-base origin/dev "$HEAD_SHA" || echo "$HEAD_SHA~1")
|
||
fi
|
||
files=$(git diff --name-only "$base" "$HEAD_SHA")
|
||
printf '%s\n' "$files" | head -50
|
||
has() { printf '%s\n' "$files" | grep -qE "$1" && echo true || echo false; }
|
||
{
|
||
echo "frontend=$(has '^(src/|demo/|test/|dist/|custom_components/houseplan/frontend/|package(-lock)?\.json$|rollup\.config\.mjs$|tsconfig)')"
|
||
echo "backend=$(has '^(custom_components/.*\.py$|tests_backend/|pytest\.ini$)')"
|
||
echo "integration=$(has '^(custom_components/houseplan/manifest\.json$|hacs\.json$|custom_components/.*\.py$|custom_components/.*/translations/)')"
|
||
} >> "$GITHUB_OUTPUT"
|
||
|
||
hacs:
|
||
needs: changes
|
||
if: needs.changes.outputs.integration == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- name: HACS validation
|
||
uses: hacs/action@main
|
||
with:
|
||
category: integration
|
||
|
||
hassfest:
|
||
needs: changes
|
||
if: needs.changes.outputs.integration == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- name: Hassfest validation
|
||
uses: home-assistant/actions/hassfest@master
|
||
|
||
frontend:
|
||
needs: changes
|
||
if: needs.changes.outputs.frontend == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
- run: npm ci
|
||
- name: Typecheck
|
||
run: npm run typecheck
|
||
- name: Unit tests
|
||
run: npm test
|
||
- name: Build
|
||
run: npm run build
|
||
- name: Card bundle snapshots in sync
|
||
run: |
|
||
cmp dist/houseplan-card.js custom_components/houseplan/frontend/houseplan-card.js
|
||
cmp dist/houseplan-card.js demo/srv/assets/houseplan-card.js
|
||
|
||
smoke:
|
||
# Gated on `frontend` so a typecheck failure does not burn browser minutes.
|
||
needs: frontend
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
- run: npm ci
|
||
- name: Install Chromium for Playwright
|
||
run: npx playwright install --with-deps chromium
|
||
- name: Build a fresh bundle for the smokes
|
||
run: npm run build && cp dist/houseplan-card.js demo/srv/assets/houseplan-card.js
|
||
- name: Smoke suite
|
||
run: |
|
||
fail=0
|
||
mkdir -p /tmp/smoke-logs
|
||
for f in demo/smoke_*.mjs; do
|
||
name=$(basename "$f" .mjs)
|
||
if node "$f" > "/tmp/smoke-logs/$name.log" 2>&1; then
|
||
echo "ok $name"
|
||
else
|
||
echo "FAIL $name"
|
||
tail -20 "/tmp/smoke-logs/$name.log"
|
||
fail=1
|
||
fi
|
||
done
|
||
exit $fail
|
||
- name: Upload smoke logs
|
||
if: failure()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: smoke-logs
|
||
path: /tmp/smoke-logs
|
||
|
||
golden:
|
||
# Deterministic visual correctness stays in every prerelease gate: it is
|
||
# inexpensive and catches a different class of regressions than timings.
|
||
needs: frontend
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
- run: npm ci
|
||
- name: Install pinned Chromium
|
||
run: npx playwright install --with-deps chromium
|
||
- name: Build the exact source under review
|
||
run: npm run build && cp dist/houseplan-card.js demo/srv/assets/houseplan-card.js
|
||
- name: Capture or verify golden matrix
|
||
id: golden
|
||
run: |
|
||
if find demo/golden/baselines -maxdepth 1 -name '*.png' -print -quit | grep -q .; then
|
||
echo "has_baselines=true" >> "$GITHUB_OUTPUT"
|
||
npm run golden:verify
|
||
else
|
||
echo "has_baselines=false" >> "$GITHUB_OUTPUT"
|
||
npm run golden:capture
|
||
fi
|
||
- name: Upload golden candidates/diffs
|
||
if: failure() || steps.golden.outputs.has_baselines == 'false'
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: golden-images
|
||
path: artifacts/golden
|
||
|
||
performance_smoke:
|
||
# Candidate-only catastrophic-regression guard for ordinary pushes and
|
||
# prereleases. The expensive same-runner comparison lives in performance.yml.
|
||
needs: frontend
|
||
runs-on: ubuntu-latest
|
||
timeout-minutes: 15
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- uses: actions/setup-node@v4
|
||
with:
|
||
node-version: 22
|
||
cache: npm
|
||
- run: npm ci
|
||
- name: Install pinned Chromium
|
||
run: npx playwright install --with-deps chromium
|
||
- name: Build the exact candidate source
|
||
run: npm run build && cp dist/houseplan-card.js demo/srv/assets/houseplan-card.js
|
||
- name: Capture the heaviest Glow state
|
||
run: |
|
||
npm run benchmark:glow -- --profile=large-house-glow-overlay-v1 --variants=60 --samples=3 --warmups=1 --output=artifacts/performance-smoke/candidate.json
|
||
- name: Enforce absolute smoke ceilings
|
||
run: |
|
||
npm run benchmark:compare -- --absolute-only --budgets=demo/performance/budgets-glow-smoke.json --candidate=artifacts/performance-smoke/candidate.json --output=artifacts/performance-smoke/comparison.json
|
||
- name: Upload performance smoke report
|
||
if: always()
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: performance-smoke
|
||
path: artifacts/performance-smoke
|
||
|
||
backend:
|
||
needs: changes
|
||
if: needs.changes.outputs.backend == 'true'
|
||
runs-on: ubuntu-latest
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
# Browser fixtures are generated by their real ESM factories and then
|
||
# validated through the Python CONFIG_SCHEMA/LAYOUT_SCHEMA in the same test.
|
||
- uses: actions/setup-node@v4
|
||
with: { node-version: 22 }
|
||
- uses: actions/setup-python@v5
|
||
with: { python-version: "3.13" }
|
||
- run: pip install pytest voluptuous pytest-homeassistant-custom-component home-assistant-frontend
|
||
- name: Backend unit tests (pure + HA harness)
|
||
run: python -m pytest tests_backend/ -q
|