style(17): apply prettier formatting to satisfy CI format:check
CI / changes (pull_request) Successful in 6s
CI / api (pull_request) Successful in 2m9s
CI / fast-checks (pull_request) Successful in 2m30s
CI / security (pull_request) Successful in 59s
CI / harness (pull_request) Failing after 12m0s
CI / gate (pull_request) Failing after 2s

Reformats 4 phase-17 files (SettingsSheet.tsx, tokens.css, vite.config.ts,
pwa-assets.config.ts) plus 11 pre-existing non-conformant docs/READMEs that
the repo-wide format:check also flags.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Lucas Berger
2026-06-18 15:23:35 -04:00
co-authored by Claude Opus 4.8
parent 6c2c6f24a9
commit 24bc8d2c32
15 changed files with 290 additions and 276 deletions
+1 -1
View File
@@ -260,7 +260,7 @@ Do not make direct repo edits outside a GSD workflow unless the user explicitly
> Generated by GSD from session_analysis. Run `/gsd-profile-user` to update.
| Dimension | Rating | Confidence |
|-----------|--------|------------|
| -------------- | --------------------- | ---------- |
| Communication | conversational | MEDIUM |
| Decisions | fast-intuitive | MEDIUM |
| Explanations | concise | MEDIUM |
+1 -1
View File
@@ -133,7 +133,7 @@ See [`docs/deployment.md`](docs/deployment.md) for Unraid/Docker Compose deploym
Every PR to `main` must pass four jobs before it can merge:
| Job | What it runs |
| -------------------- | -------------------------------------------------------------------------- |
| ------------------ | ---------------------------------------------------------------------------------- |
| `CI / fast-checks` | `pnpm lint`, `pnpm format:check`, `pnpm md:lint`, `pnpm typecheck`, PWA unit tests |
| `CI / api` | DB migrations + API test suite against a real MariaDB 11 service container |
| `CI / harness` | Playwright end-to-end harness (WebKit iPhone + Chromium Pixel) |
+2 -2
View File
@@ -126,7 +126,7 @@ Migration files are written to `src/db/migrations/` and checked into source cont
## Environment variables
| Variable | Required | Description |
| ----------------------------- | ------------------- | ---------------------------------------------------------------------------------- |
| ----------------------------- | -------------------- | ---------------------------------------------------------------------------------- |
| `DB_HOST` | Yes | MariaDB host |
| `DB_USER` | Yes | MariaDB user |
| `DB_PASSWORD` | Yes | MariaDB password |
@@ -156,7 +156,7 @@ See [../../docs/CONFIGURATION.md](../../docs/CONFIGURATION.md) for the full refe
The API supports two non-exclusive auth modes, determined at startup:
| Mode | When active | How it works |
| ---- | ----------- | ------------ |
| -------------- | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Local** | Always (default) | `POST /api/auth/local/login` with username + password; issues an HS256 JWT `local-session` cookie. Requires `LOCAL_SESSION_SECRET`. |
| **OIDC** | When `OIDC_ISSUER` + `OIDC_CLIENT_ID` are set (env or app_config) | `@hono/oidc-auth` authorization-code + PKCE against Authelia. Local users can upgrade to OIDC via `POST /api/me/link-oidc`. |
| **Dev bypass** | `DEV_AUTH_BYPASS=true` in non-production | Skips both guards and injects a synthetic dev user. Blocked in `NODE_ENV=production` by boot guard. |
+1 -1
View File
@@ -33,7 +33,7 @@ The API backend must also be running for most features. See [GETTING-STARTED.md]
## Scripts
| Command | What it does |
| ------------------------------------------------ | ------------------------------------------------------------------------- |
| ----------------------------------------------- | ------------------------------------------------------------- |
| `pnpm --filter @familysync/pwa dev` | Start Vite dev server (HMR) |
| `pnpm --filter @familysync/pwa build` | Type-check then build production bundle (`tsc && vite build`) |
| `pnpm --filter @familysync/pwa preview` | Serve the production build locally |
+2 -2
View File
@@ -1,4 +1,4 @@
import { defineConfig, minimal2023Preset } from '@vite-pwa/assets-generator/config'
import { defineConfig, minimal2023Preset } from '@vite-pwa/assets-generator/config';
export default defineConfig({
preset: {
@@ -7,4 +7,4 @@ export default defineConfig({
images: ['public/logo.svg'],
// Do NOT set overrideManifestIcons: true — the manifest is maintained by hand
// in vite.config.ts (plan 17-04); auto-override would stomp the explicit entries.
})
});
+7 -1
View File
@@ -29,7 +29,13 @@ import { usePushSubscription } from '../hooks/usePushSubscription.js';
import { useIsPhone } from '../hooks/useIsPhone.js';
import { useFocusTrap } from '../hooks/useFocusTrap.js';
import { InstructionSheet } from './InstructionSheet.js';
import { fetchMe, fetchAuthMode, fetchChangePassword, fetchLinkOidc, fetchLocalLogout } from '../api/client.js';
import {
fetchMe,
fetchAuthMode,
fetchChangePassword,
fetchLinkOidc,
fetchLocalLogout,
} from '../api/client.js';
// CR-04: fetch VAPID key (from sessionStorage cache if available) for the
// tap-gated subscribe() path. Same logic as PushPermissionPrompt.
+1 -1
View File
@@ -14,7 +14,7 @@
*/
:root,
[data-theme="light"] {
[data-theme='light'] {
/*
* BASE SURFACE / BORDER / TEXT PALETTE
* */
+6 -1
View File
@@ -42,7 +42,12 @@ export default defineConfig({
icons: [
{ src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
{ src: '/icon-512.png', sizes: '512x512', type: 'image/png' },
{ src: '/icon-maskable-512.png', sizes: '512x512', type: 'image/png', purpose: 'maskable' },
{
src: '/icon-maskable-512.png',
sizes: '512x512',
type: 'image/png',
purpose: 'maskable',
},
],
},
}),
+12 -11
View File
@@ -26,7 +26,7 @@ No API key or `Authorization` header is used. Credentials are never included in
## Endpoints Overview
| Method | Path | Auth | Description |
| ------ | ------------------------------------- | --------- | -------------------------------------------------- |
| ------ | --------------------------------- | ------- | --------------------------------------------------- |
| GET | `/health` | None | DB liveness check |
| GET | `/callback` | None | OIDC authorization-code exchange |
| GET | `/api/setup/status` | None | Setup wizard completion status |
@@ -130,7 +130,7 @@ Stores non-secret OIDC and VAPID configuration into `app_config`. Returns `423`
```
| Field | Type | Required | Constraints |
| ---------------- | ------ | -------- | ------------------------------ |
| ---------------- | ------ | -------- | ----------------------------- |
| `oidcIssuer` | string | Yes | HTTPS URL |
| `oidcClientId` | string | Yes | 1256 characters |
| `vapidPublicKey` | string | Yes | 1512 characters |
@@ -188,7 +188,7 @@ Creates the first admin user (no OIDC identity yet, `claimed: false`) and valida
```
| Field | Type | Required | Constraints |
| --------------- | ------ | -------- | ---------------- |
| --------------- | ------ | -------- | ------------------------------- |
| `fastmailEmail` | string | Yes | Valid email, max 256 characters |
| `appPassword` | string | Yes | 1500 characters |
@@ -233,6 +233,7 @@ Pre-auth endpoint. Returns which authentication methods are currently enabled. U
Validates a username + password against `local_credentials` and issues a signed `local-session` JWT cookie. Pre-auth — reachable without a session.
Rate limiting is per-username (not per-IP):
- 5 failures within 60 seconds → `429 Too Many Requests`
- 10 cumulative failures → `423 Account Locked` (auto-expires after 15 minutes or on admin password reset)
@@ -245,7 +246,7 @@ Timing-oracle defense: `verifyPassword` (scrypt) is always called, even for unkn
```
| Field | Type | Required | Constraints |
| ---------- | ------ | -------- | ---------------- |
| ---------- | ------ | -------- | -------------------------- |
| `username` | string | Yes | 1128 characters (trimmed) |
| `password` | string | Yes | 11000 characters |
@@ -297,7 +298,7 @@ Display name is derived from OIDC claims in priority order: `name` → `preferre
```
| Field | Type | Description |
| -------------------- | ------- | ---------------------------------------------------------------- |
| -------------------- | ------- | ---------------------------------------------------------- |
| `id` | integer | Stable member ID |
| `displayName` | string | Derived from OIDC claims or set by admin |
| `color` | string | Member's assigned color (hex) |
@@ -324,7 +325,7 @@ Member self-service endpoint to set or rotate their own Fastmail CalDAV app pass
```
| Field | Type | Required | Constraints |
| --------------- | ------ | -------- | ---------------- |
| --------------- | ------ | -------- | ------------------------------- |
| `providerType` | string | Yes | Must be `"caldav"` |
| `fastmailEmail` | string | Yes | Valid email, max 256 characters |
| `appPassword` | string | Yes | 1500 characters |
@@ -349,7 +350,7 @@ Self-service password change for local-auth members. Requires the current passwo
```
| Field | Type | Required | Constraints |
| ----------------- | ------ | -------- | ---------------- |
| ----------------- | ------ | -------- | -------------------- |
| `currentPassword` | string | Yes | 1+ characters |
| `newPassword` | string | Yes | Minimum 8 characters |
@@ -943,7 +944,7 @@ Creates a new local-auth member: inserts a `users` row and a `local_credentials`
```
| Field | Type | Required | Constraints |
| ----------------- | ------ | -------- | ---------------- |
| ----------------- | ------ | -------- | -------------------- |
| `displayName` | string | Yes | 1256 characters |
| `username` | string | Yes | 1128 characters |
| `initialPassword` | string | Yes | Minimum 8 characters |
@@ -998,7 +999,7 @@ Validates and stores a Fastmail CalDAV app password for any household member. Pe
```
| Field | Type | Required | Constraints |
| --------------- | ------- | -------- | ---------------- |
| --------------- | ------- | -------- | ------------------------------- |
| `userId` | integer | Yes | Positive integer |
| `providerType` | string | Yes | Must be `"caldav"` |
| `fastmailEmail` | string | Yes | Valid email, max 256 characters |
@@ -1066,7 +1067,7 @@ Validates and upserts the household IANA timezone into `app_config`.
```
| Field | Type | Required | Constraints |
| ---------- | ------ | -------- | -------------------------- |
| ---------- | ------ | -------- | ------------------------------------ |
| `timezone` | string | Yes | Valid IANA timezone, 164 characters |
**Response 200** — `{ "ok": true }`
@@ -1106,7 +1107,7 @@ All error responses use a consistent JSON envelope.
```
| HTTP Status | Meaning |
| ----------- | ------------------------------------------------------------------------------ |
| ----------- | -------------------------------------------------------------------------------------------------------- |
| `400` | Invalid request parameters (e.g., malformed date window) |
| `401` | Session missing or invalid |
| `403` | Authenticated but not authorized (wrong owner, sharee attempted owner-only op, non-admin on admin route) |
+3 -3
View File
@@ -89,7 +89,7 @@ familysync/
### Directory Rationale
| Directory | Purpose |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `apps/api/src/routes/` | One file per resource — `events.ts`, `lists.ts`, `me.ts`, `push.ts`, `sse.ts`, `health.ts`, `admin.ts`, `setup.ts`, `authMode.ts`, `localAuth.ts` |
| `apps/api/src/broker/` | All CalDAV I/O: `client.ts` (tsdav factory), `sync.ts` (REPORT→DB), `poller.ts` (5-min ctag check), `outboxWorker.ts` (async write-back), `expand.ts` (RRULE expansion), `write.ts` (PUT/DELETE), `vevent.ts` (ICS builder), `crypto.ts` (AES-256-GCM for app passwords) |
| `apps/api/src/auth/` | `middleware.ts` (re-exports `@hono/oidc-auth`), `devBypass.ts` (DEV_AUTH_BYPASS inject), `localAuthMiddleware.ts` (local-session cookie → user), `localCredentials.ts` (scrypt hash/verify), `localSession.ts` (JWT cookie issue/verify/clear), `oidcConfig.ts` (env+DB fallback for OIDC config), `linkNonceStore.ts` (single-use OIDC-link nonces), `linkOidc.ts` (bind OIDC identity to local user), `persistSessionCookie.ts` (session lifetime extension), `user.ts` (upsert on first OIDC login) |
@@ -104,7 +104,7 @@ familysync/
## Key Abstractions
| Abstraction | File | Description |
| ------------------------------------------ | ------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ------------------------------------------------------ | ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `app` (Hono) | `apps/api/src/index.ts` | Root Hono app; mounts all routes and serves the PWA static build |
| Drizzle schema | `apps/api/src/db/schema.ts` | Single source of truth for all table definitions (`users`, `memberCredentials`, `localCredentials`, `calendars`, `calendarEvents`, `calendarOutbox`, `lists`, `listShares`, `listItems`, `pushSubscriptions`, `appConfig`) |
| `syncCalendar` | `apps/api/src/broker/sync.ts` | REPORT → ical.js parse → `onDuplicateKeyUpdate` upsert into MariaDB |
@@ -235,7 +235,7 @@ routes/setup.ts ──→ db (app_config)
## Infrastructure
| Component | Technology |
| -------------------- | ------------------------------------------------------------------------------------------------------------------- |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| Runtime | Node.js 22 LTS |
| HTTP framework | Hono 4.x (`@hono/node-server`) |
| Database | MariaDB 11 (Docker volume) |
+4 -4
View File
@@ -11,7 +11,7 @@ All runtime configuration is supplied via environment variables. There are no JS
### Database
| Variable | Required | Default | Description |
| ------------------ | ------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------ |
| ------------------ | ------------ | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DB_HOST` | Yes | `localhost` | MariaDB hostname. Use `mariadb` inside Docker Compose; use `localhost` (or `127.0.0.1`) for host-side dev runs. |
| `DB_PORT` | No | `3306` | MariaDB port. |
| `DB_USER` | No | `familysync` | Database user. |
@@ -56,7 +56,7 @@ Five of these variables — `DB_HOST`, `DB_USER`, `DB_PASSWORD`, `DB_NAME`, `DB_
These variables govern the stateless local-auth path introduced in Phase 19. Local auth issues a separate `local-session` JWT cookie (distinct from `oidc-auth`) signed with `LOCAL_SESSION_SECRET`.
| Variable | Required | Default | Description |
| ----------------------- | ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ----------------------- | ------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `LOCAL_SESSION_SECRET` | **Required** (non-bypass) | _(none)_ | 32+ character secret used to sign and verify `local-session` JWT cookies (HS256). Generate with `openssl rand -base64 32`. The API refuses to start with a fatal error if this is absent or shorter than 32 characters, unless `DEV_AUTH_BYPASS=true`. |
| `LOCAL_SESSION_EXPIRES` | No | `86400` | `local-session` cookie `Max-Age` in seconds (default 1 day). Mirrors `OIDC_AUTH_EXPIRES` but applies to the local-auth cookie. Malformed (non-numeric) values silently fall back to the default. Source: `apps/api/src/auth/localSession.ts`. |
@@ -91,7 +91,7 @@ npx web-push generate-vapid-keys --json
### Runtime Mode
| Variable | Required | Default | Description |
| ----------------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ----------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `NODE_ENV` | No | _(not set)_ | Set to `production` in the production Docker Compose. When `production`, the dev-auth bypass is unconditionally disabled regardless of `DEV_AUTH_BYPASS`. |
| `DEV_AUTH_BYPASS` | No | _(not set)_ | Set to `true` to bypass OIDC authentication for local development without a live Authelia instance. **Only active when `NODE_ENV !== 'production'`.** The production `docker-compose.yml` must never include this variable. |
| `TZ` | No | _(not set)_ | IANA timezone identifier (e.g. `America/Toronto`) used as the server-side fallback for the household timezone when no value is stored in `app_config`. The full fallback chain is: stored DB value → `TZ` env → `Intl.DateTimeFormat().resolvedOptions().timeZone`. Empty or whitespace values are ignored. Source: `apps/api/src/lib/householdTimezone.ts`. |
@@ -103,7 +103,7 @@ npx web-push generate-vapid-keys --json
These variables are never needed in production and should not appear in the production `.env`.
| Variable | Scope | Default | Description |
| ----------------------- | ---------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ----------------------- | --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FASTMAIL_EMAIL` | Dev spike script only | _(none)_ | Fastmail account email. Read only by `apps/api/src/broker/spike.ts`, a standalone dev script for enumerating CalDAV collections. Not imported by the API or Docker image. |
| `FASTMAIL_APP_PASSWORD` | Dev spike script only | _(none)_ | Fastmail app password. Read only by `apps/api/src/broker/spike.ts`. **Never logged.** Not used by the API in any environment. |
| `PLAYWRIGHT_BASE_URL` | E2E tests only | `http://localhost:5173` | Base URL for Playwright e2e tests. Overridden to `http://127.0.0.1:5173` in CI to avoid IPv6 resolution failures. Source: `apps/pwa/playwright.config.ts`. |
+5 -5
View File
@@ -101,7 +101,7 @@ Vite serves the PWA with HMR on the configured dev port. The PWA's API calls tar
### Root workspace scripts
| Command | Description |
| ------------------------ | ------------------------------------------------------------- |
| ----------------------- | --------------------------------------------------------------------------- |
| `pnpm dev:api` | Start API dev watcher (`node --watch dist/index.js`) |
| `pnpm dev:pwa` | Start Vite dev server for the PWA |
| `pnpm build` | Build both `apps/api` (tsc) and `apps/pwa` (tsc + vite build) |
@@ -157,9 +157,9 @@ pnpm md:lint # Markdown lint (also runs in CI fast-checks)
Config: `eslint.config.js` (root, flat ESLint 9 format). The config covers:
- **All `apps/**/*.{ts,tsx}`** — `js.configs.recommended` + `tseslint.configs.recommendedTypeChecked` with `projectService: true` (type-aware rules, auto-discovers all `tsconfig.json` files)
- **`apps/pwa/**/*.{ts,tsx}` additionally** — `eslint-plugin-react` + `eslint-plugin-react-hooks` (React 19 flat config; React Compiler rules disabled — this codebase does not use the Compiler)
- **All `apps/**/*.{ts,tsx}`** — `eslint-plugin-security` (14 of 15 rules at error; `detect-object-injection` disabled due to high false-positive rate on schema-derived numeric keys)
- **All `apps/**/\*.{ts,tsx}`** — `js.configs.recommended`+`tseslint.configs.recommendedTypeChecked`with`projectService: true`(type-aware rules, auto-discovers all`tsconfig.json` files)
- **`apps/pwa/**/\*.{ts,tsx}`additionally** —`eslint-plugin-react`+`eslint-plugin-react-hooks` (React 19 flat config; React Compiler rules disabled — this codebase does not use the Compiler)
- **All `apps/**/\*.{ts,tsx}`** — `eslint-plugin-security`(14 of 15 rules at error;`detect-object-injection` disabled due to high false-positive rate on schema-derived numeric keys)
- **Tool configs + test dirs** (`drizzle.config.ts`, `vitest.config.ts`, `apps/api/tests/**`, `apps/pwa/e2e/**`) — type-aware rules disabled via `disableTypeChecked` (these files are outside the main tsconfig projects)
- **Prettier integration**`eslint-config-prettier` last in the config disables all formatting rules that conflict with Prettier
@@ -196,7 +196,7 @@ Run `pnpm typecheck` before opening a PR to catch errors that vitest and Vite bu
Every PR to `main` runs through `.gitea/workflows/ci.yml`. A `changes` path-filter job determines whether code files changed; the `api` and `harness` jobs are skipped entirely for doc-only PRs (changes only to `.planning/**`, `.gitea/**`, or `*.md` files).
| Job | Runs on | Checks |
| ------------- | ------------------- | ----------------------------------------------------------------------------------------------------------- |
| ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------ |
| `fast-checks` | Every PR | `pnpm lint``pnpm format:check``pnpm md:lint``pnpm typecheck``pnpm --filter @familysync/pwa test` |
| `api` | Code-change PRs only | DB migrations + `pnpm --filter @familysync/api test` (vitest against a MariaDB 11 service container) |
| `harness` | Code-change PRs only | DB migrations + seed dev user + API build + Playwright e2e (WebKit + Chromium) with `DEV_AUTH_BYPASS=true` |
+1
View File
@@ -134,6 +134,7 @@ Or set `DB_HOST=localhost` directly in your `.env` for host-side dev.
**`[FATAL] LOCAL_SESSION_SECRET is not set or is shorter than 32 characters`**
The API refuses to start in non-bypass mode without a valid `LOCAL_SESSION_SECRET`. Either:
- Set `DEV_AUTH_BYPASS=true` in `.env` for local dev (bypass mode exempts the requirement), or
- Run `pnpm generate-secrets` and add the generated `LOCAL_SESSION_SECRET` value to `.env`.
+4 -4
View File
@@ -7,7 +7,7 @@
Both apps use **Vitest** (`^4.1.8`).
| App | Environment | Global setup | Per-file setup |
| ---------- | ----------- | ----------------------------------- | ---------------------------- |
| ---------- | ----------- | ------------------------------- | ---------------------------- |
| `apps/api` | `node` | `apps/api/test/global-setup.ts` | `apps/api/test/setup.ts` |
| `apps/pwa` | `jsdom` | — | `apps/pwa/src/test-setup.ts` |
@@ -54,7 +54,7 @@ pnpm --filter @familysync/api exec vitest run tests/routes/lists.test.ts
The PWA has a Playwright harness configured in `apps/pwa/playwright.config.ts` with three device profiles:
| Profile | Viewport | Engine | User-Agent |
| --------- | --------- | -------- | ------------------------- |
| --------- | -------- | -------- | ------------------------- |
| `iphone` | 390×844 | WebKit | Mobile Safari (iPhone 14) |
| `pixel` | 412×915 | Chromium | Chrome Android (Pixel 7) |
| `desktop` | 1280×720 | Chromium | Desktop Chrome |
@@ -233,7 +233,7 @@ The throwaway credentials (`DB_USER=familysync`, `DB_PASSWORD=testpass`) are sco
Runs the Playwright mobile and desktop e2e harness (iphone + pixel + desktop) against a runner-hosted dev stack. Skipped for doc-only PRs.
| Step | Detail |
| ---------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| MariaDB service | Same `mariadb:11` setup as the `api` job |
| Schema migrations | `pnpm --filter @familysync/api db:migrate` |
| Dev user seed | Inserts `users` row id=1 (`INSERT IGNORE`) for `DEV_AUTH_BYPASS` |
@@ -251,7 +251,7 @@ The API process is started and the Playwright suite invoked within a single CI s
Runs secret scanning and dependency audits. Always runs regardless of the `changes` filter (secrets can appear in doc-only commits). Dependency audit and outdated checks run only when code changes are detected.
| Step | Tool/Command | Detail |
| ------------------- | ------------------------------- | -------------------------------------------------------------- |
| ---------------- | --------------------------------- | ------------------------------------------------------ |
| Secret scan | `gitleaks` (v8.30.1) | Scans the PR diff range; blocks on any finding |
| Dependency audit | `node scripts/check-audit.mjs` | Blocks on High or Critical severity vulnerabilities |
| Outdated report | `node scripts/check-outdated.mjs` | Advisory only — always exits 0, logged but never gates |
+2 -1
View File
@@ -33,7 +33,7 @@ FamilySync uses a self-hosted Gitea Actions runner. Two workflows govern the rel
Triggered on every pull request targeting `main`. The workflow runs a `changes` filter job first, then launches the following jobs in parallel:
| Job | Runs when | What it checks |
| ------------- | -------------------- | --------------------------------------------------------------------------------------------------------- |
| ------------- | ---------------------- | ----------------------------------------------------------------------------------------------------------------- |
| `fast-checks` | Always | Lint (`pnpm lint`), format check (`pnpm format:check`), markdown lint (`pnpm md:lint`), typecheck, PWA unit tests |
| `api` | Code-changing PRs only | DB migrations + API integration tests against a live MariaDB service container |
| `harness` | Code-changing PRs only | Full Playwright E2E suite (iPhone + Pixel + desktop profiles) against the compiled API |
@@ -62,6 +62,7 @@ The current milestone prefix (`v1.1`) is set in the `MILESTONE` env var at the t
The immutable `:<milestone>-<sha>` tag is pushed first. `:latest` is only moved after the immutable tag has landed, so a failed second push can never leave `:latest` advanced without a corresponding rollback tag.
Before pushing, the workflow runs two image hygiene assertions:
1. **Static assertions** — verifies `.dockerignore` contains all required exclusion patterns and that the build targets `--target production`.
2. **Boot-smoke** — starts the image with `NODE_ENV=production` and `DEV_AUTH_BYPASS=true` and asserts that it refuses to start (confirming the D-08 guard fires in the shipped image).