mirror of
https://github.com/civitai/civitai.git
synced 2026-09-20 22:08:18 +08:00
ci(lighthouse): report-only Lighthouse CI on PR previews (Tekton) (#2516)
* ci(lighthouse): report-only Lighthouse CI on PR previews (Tekton) Adds the civitai-side config for a new report-only `lighthouse` task in the datapacket-talos pr-preview pipeline. On each preview build it runs Lighthouse (5 runs, desktop, median-aggregated) against the deployed preview at pr-<N>.civitaic.com and posts a report-only PR comment with perf/a11y scores + LCP/CLS/TBT/INP and a public report link. - lighthouserc.json: 3 routes (/ /models /generate), numberOfRuns 5, desktop preset, --no-sandbox, temporary-public-storage upload. All assertions are `warn` (report-only) — promotion path documented (deterministic CLS/byte budgets to `error` first, noisy timing metrics last, after a soak). - tests/lighthouse-mint-cookie.cjs: mints the gate-passing ci-smoke-gold NextAuth session cookie (same next-auth/jwt encode + NEXTAUTH_SECRET as tests/preview-auth.setup.ts) and injects it into collect.settings.extraHeaders -> lighthouserc.runtime.json, so headless Chrome clears the preview-auth middleware /login gate instead of measuring the login page. Report-only first; structured to flip to gating later. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: retrigger preview for lighthouse upload fix * ci: retrigger preview for lighthouse manifest fix --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,3 +134,7 @@ tmpclaude-*
|
||||
|
||||
# Turborepo local task cache
|
||||
.turbo/
|
||||
|
||||
# Lighthouse CI — runtime config (cookie-injected) + report output, never committed
|
||||
lighthouserc.runtime.json
|
||||
.lighthouseci/
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"//": "Lighthouse CI config for the PR-preview pipeline (datapacket-talos pr-preview-pipeline.yaml `lighthouse` task). REPORT-ONLY: no assertions are set to `error`, so a regression never fails the build. The Tekton task mints a gold-tester NextAuth session cookie (the preview is gated by preview-auth.middleware -> unauthenticated requests 302 to /login) and injects it into collect.settings.extraHeaders at runtime, producing lighthouserc.runtime.json from this base. See tests/lighthouse-mint-cookie.cjs.",
|
||||
"ci": {
|
||||
"collect": {
|
||||
"//": "5 runs + LHCI median aggregation is the single biggest stabilizer on a capacity-constrained build pool — lab noise is real, which is exactly why this is report-only and why timing metrics (LCP/TBT/INP) must NOT be promoted to a gating assertion until a soak proves the threshold sits above the noise band. URLs are filled in per-PR by the task (BASE_URL + these paths). All routes are hit AUTHENTICATED as ci-smoke-gold (a gate-passing paid member) — an anonymous GET would 302 to /login and measure the login page, not the app.",
|
||||
"numberOfRuns": 5,
|
||||
"settings": {
|
||||
"preset": "desktop",
|
||||
"//preset": "Desktop preset (not mobile). Desktop is the lower-variance profile on a noisy shared runner and matches the primary civitai surface; revisit mobile once the desktop signal is trusted.",
|
||||
"chromeFlags": "--no-sandbox --disable-gpu --disable-dev-shm-usage --headless=new",
|
||||
"//chromeFlags": "--no-sandbox is required: the Tekton step runs Chrome without the kernel sandbox privileges. --disable-dev-shm-usage avoids /dev/shm exhaustion in the small container.",
|
||||
"maxWaitForLoad": 60000
|
||||
}
|
||||
},
|
||||
"assert": {
|
||||
"//": "REPORT-ONLY. Every assertion is `warn` — LHCI surfaces the diff but exits 0, so the build never fails. Promotion path (mirrors the bundle-budget soak->gate philosophy): promote the DETERMINISTIC, low-variance metrics to `error` FIRST once a soak confirms a stable baseline — cumulative-layout-shift (geometry, near-zero variance) and total byte/resource budgets. Promote the NOISY timing metrics (largest-contentful-paint, total-blocking-time, interactive, interaction-to-next-paint) LAST, and only with a threshold set comfortably above the observed 5-run noise band. To gate: change the relevant `warn` to `error`.",
|
||||
"assertions": {
|
||||
"categories:performance": ["warn", { "minScore": 0.5 }],
|
||||
"categories:accessibility": ["warn", { "minScore": 0.9 }],
|
||||
"cumulative-layout-shift": ["warn", { "maxNumericValue": 0.1 }],
|
||||
"largest-contentful-paint": ["warn", { "maxNumericValue": 4000 }],
|
||||
"total-blocking-time": ["warn", { "maxNumericValue": 600 }]
|
||||
}
|
||||
},
|
||||
"upload": {
|
||||
"//": "temporary-public-storage gives a public, 7-day report URL with zero infra (no @lhci/server stood up). The task ALSO reads .lighthouseci/manifest.json (median-run summary scores) and the representative lhr-*.json (LCP/CLS/TBT/INP audits) for the PR comment, and .lighthouseci/links.json for the public URLs.",
|
||||
"target": "temporary-public-storage"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Mint a gate-passing NextAuth session cookie for a deployed PR preview and
|
||||
* produce the runtime Lighthouse CI config that injects it.
|
||||
*
|
||||
* Why this exists: a deployed PR preview runs IS_PREVIEW=true and is gated by
|
||||
* preview-auth.middleware — an UNAUTHENTICATED request 302s to /login, so a
|
||||
* naive Lighthouse run would measure the login page, not the app. We mint the
|
||||
* same `__Secure-civitai-token` JWE the app signs with (next-auth/jwt encode +
|
||||
* the preview's shared NEXTAUTH_SECRET), as ci-smoke-gold (a paid member in the
|
||||
* flipt `preview-site-access` testers allowlist — id mirrors preview-fixtures /
|
||||
* the datapacket-talos seed-smoke-test-users CronJob), and put it in
|
||||
* collect.settings.extraHeaders.Cookie so headless Chrome clears the gate.
|
||||
*
|
||||
* This is the SAME mechanism tests/preview-auth.setup.ts uses for the smoke
|
||||
* suite; kept as a standalone .cjs so the Tekton lighthouse task can run it
|
||||
* with plain `node` (no ts/playwright) using the next-auth/jwt + uuid already
|
||||
* installed into the shared workspace node_modules by the typecheck task.
|
||||
*
|
||||
* Usage:
|
||||
* NEXTAUTH_SECRET=... BASE_URL=https://pr-123.civitaic.com \
|
||||
* node tests/lighthouse-mint-cookie.cjs
|
||||
* Writes lighthouserc.runtime.json (cwd) with extraHeaders + per-PR URLs.
|
||||
* Prints nothing sensitive (never the cookie value) to stdout.
|
||||
*/
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { encode } = require('next-auth/jwt');
|
||||
const { v4: uuid } = require('uuid');
|
||||
|
||||
const SECRET = process.env.NEXTAUTH_SECRET;
|
||||
const BASE_URL = process.env.BASE_URL;
|
||||
const COOKIE_NAME = '__Secure-civitai-token'; // libs/auth.ts — https => __Secure- prefix
|
||||
const MAX_AGE_S = 30 * 24 * 60 * 60;
|
||||
|
||||
// Representative authed routes. Kept small (3) so 5 runs x 3 routes stays inside
|
||||
// the build pool's time budget. All are reachable by ci-smoke-gold and are
|
||||
// present regardless of the preview's DB content (they don't depend on a
|
||||
// specific seeded entity). `/` = SSR home, `/models` = heavy list/feed,
|
||||
// `/generate` = client-heavy generator. Edit here to change what's measured.
|
||||
const ROUTES = ['/', '/models', '/generate'];
|
||||
|
||||
// ci-smoke-gold — must mirror tests/preview-fixtures.ts PREVIEW_USERS.gold.
|
||||
const GOLD = {
|
||||
id: 2000000004,
|
||||
username: 'ci-smoke-gold',
|
||||
email: 'ci-smoke-gold@civitai.test',
|
||||
isModerator: false,
|
||||
tier: 'gold',
|
||||
showNsfw: true,
|
||||
blurNsfw: false,
|
||||
browsingLevel: 1,
|
||||
onboarding: 15,
|
||||
muted: false,
|
||||
};
|
||||
|
||||
async function main() {
|
||||
if (!SECRET) throw new Error('NEXTAUTH_SECRET is required to mint the preview cookie');
|
||||
if (!BASE_URL) throw new Error('BASE_URL is required (e.g. https://pr-123.civitaic.com)');
|
||||
|
||||
const token = { user: GOLD, sub: String(GOLD.id), id: uuid(), signedAt: Date.now() };
|
||||
const value = await encode({ token, secret: SECRET, maxAge: MAX_AGE_S });
|
||||
|
||||
const base = JSON.parse(
|
||||
fs.readFileSync(path.join(process.cwd(), 'lighthouserc.json'), 'utf8')
|
||||
);
|
||||
|
||||
base.ci.collect.url = ROUTES.map((r) => `${BASE_URL.replace(/\/$/, '')}${r}`);
|
||||
base.ci.collect.settings = base.ci.collect.settings || {};
|
||||
// extraHeaders must be a JSON STRING per the LHCI schema.
|
||||
base.ci.collect.settings.extraHeaders = JSON.stringify({
|
||||
Cookie: `${COOKIE_NAME}=${value}`,
|
||||
});
|
||||
|
||||
fs.writeFileSync('lighthouserc.runtime.json', JSON.stringify(base, null, 2));
|
||||
console.log(
|
||||
`Wrote lighthouserc.runtime.json — ${ROUTES.length} routes x ${base.ci.collect.numberOfRuns} runs, authed as ${GOLD.username}`
|
||||
);
|
||||
for (const u of base.ci.collect.url) console.log(` - ${u}`);
|
||||
}
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(`mint-cookie failed: ${e.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user