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
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:
co-authored by
Claude Opus 4.8
parent
6c2c6f24a9
commit
24bc8d2c32
@@ -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 |
|
||||
|
||||
@@ -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) |
|
||||
|
||||
+3
-3
@@ -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 |
|
||||
@@ -142,7 +142,7 @@ Migration files are written to `src/db/migrations/` and checked into source cont
|
||||
| `VAPID_PRIVATE_KEY` | Yes (push) | VAPID private key |
|
||||
| `APP_PASSWORD_ENCRYPTION_KEY` | Yes | AES-256-GCM key (64-char hex) for stored Fastmail app passwords |
|
||||
| `LOCAL_SESSION_SECRET` | Yes (local auth) | HS256 signing key for local-session JWT cookies (min 32 chars) |
|
||||
| `LOCAL_SESSION_EXPIRES` | No (default `86400`)| Local session lifetime in seconds |
|
||||
| `LOCAL_SESSION_EXPIRES` | No (default `86400`) | Local session lifetime in seconds |
|
||||
| `DEV_AUTH_BYPASS` | No | Set to `true` (non-production only) to skip OIDC and inject a dev user |
|
||||
| `NODE_ENV` | No | Set to `production` to enforce OIDC unconditionally |
|
||||
| `TZ` | No | IANA timezone fallback when household_timezone is not set in app_config |
|
||||
@@ -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
@@ -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 |
|
||||
|
||||
@@ -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.
|
||||
})
|
||||
});
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
*/
|
||||
|
||||
:root,
|
||||
[data-theme="light"] {
|
||||
[data-theme='light'] {
|
||||
/* ─────────────────────────────────────────────────────────────────────────
|
||||
* BASE SURFACE / BORDER / TEXT PALETTE
|
||||
* ───────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
+14
-13
@@ -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 |
|
||||
@@ -39,7 +39,7 @@ No API key or `Authorization` header is used. Credentials are never included in
|
||||
| GET | `/api/auth/mode` | None | Auth mode discovery (local vs OIDC enabled) |
|
||||
| POST | `/api/auth/local/login` | None | Local username+password login |
|
||||
| POST | `/api/auth/local/logout` | None | Clear local session cookie |
|
||||
| GET | `/api/auth/local/logout` | None | Clear local session cookie (browser redirect alias)|
|
||||
| GET | `/api/auth/local/logout` | None | Clear local session cookie (browser redirect alias) |
|
||||
| GET | `/api/login` | OIDC | OIDC login entry point, redirects to `/` |
|
||||
| GET | `/api/me` | Session | Current user identity, role, and setup status |
|
||||
| POST | `/api/me/credential` | Session | Member self-service Fastmail credential update |
|
||||
@@ -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 | 1–256 characters |
|
||||
| `vapidPublicKey` | string | Yes | 1–512 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 | 1–500 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 | 1–128 characters (trimmed) |
|
||||
| `password` | string | Yes | 1–1000 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 | 1–500 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 | 1–256 characters |
|
||||
| `username` | string | Yes | 1–128 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, 1–64 characters |
|
||||
|
||||
**Response 200** — `{ "ok": true }`
|
||||
@@ -1106,14 +1107,14 @@ 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) |
|
||||
| `404` | Resource not found |
|
||||
| `409` | Conflict (e.g., duplicate username) |
|
||||
| `422` | Valid request but cannot be fulfilled (e.g., user has no calendar configured) |
|
||||
| `423` | Locked (setup already complete, or account locked after too many failed logins)|
|
||||
| `423` | Locked (setup already complete, or account locked after too many failed logins) |
|
||||
| `429` | Too many requests (login rate limit exceeded for this username) |
|
||||
| `503` | DB or downstream service unavailable |
|
||||
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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
@@ -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` |
|
||||
|
||||
@@ -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
@@ -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
@@ -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).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user