* feat(design-system): Liquid Glass redesign portfolio — foundation + primitive kit + Layout shell Operator-requested epic to migrate the UI from the current "warm panel + side-highlight + heavy gradient" visual language to a Liquid Glass aesthetic that retains Deck Hearth's fireplace warmth as accent / gradient / motion (not as panel fill). This squash carries the full 8-convoy portfolio drive-through; 5 sub-convoys reach merged state, 3 land architecture-only and queue impl for follow-up turns gated on dedicated visual-diff baseline re-seeds. Sub-convoy #1 (liquid-glass-design-tokens) — MERGED. 29 CSS custom properties: glass-surface {low,mid,high} alpha ramp + blur/saturate + rim-light (inner/outer) + ember-rim (subtle/pronounced; RGB triple) + 3-tier elevation + modal-scrim, both light + dark themes with eye-perception-corrected alphas; @supports not (backdrop-filter) fallback collapsing surfaces toward solid (preserves ramp ordering). Authored docs/DESIGN_TOKENS.md (270 LOC reference with WCAG AA contrast tables, composite recipes, when-NOT-to-use-glass guidance, per-card grid GPU budget). AGENTS.md gains a § Visual language section as the new agent-contract surface. Sub-convoy #2 (liquid-glass-modal-and-surface-primitive) — Brief 1 MERGED. Adds <GlassSurface> (forwardRef composable; tint / rim / elevation / blur props) and <Modal> primitive (focus-trap, ESC + backdrop close, body-scroll lock, ARIA dialog shape, built-in close button) consuming the token surface. lib/use-focus-trap.js — homegrown hook (~60 LOC, no dep). 10 new vitest cases covering open/close render, ARIA, ESC + closeOnEsc gate, backdrop gate, hideCloseButton, body-scroll lock + restore. 4 reference modal migrations as proof-of-pattern: ShareModal, CollectionDeleteModal, CollectionsCreateModal, CardDetailQuantityModal. Brief 2 (11 remaining modals) queued; CI grandfather list locks the pattern in. Sub-convoy #3 (liquid-glass-form-primitives) — Brief 1 MERGED. Adds <Button> (primary ember-gradient with ember-rim-pronounced; secondary glass-mid; danger; ghost), <Input> (glass-high with ember focus ring + label + helperText + error + aria-invalid + describedby wiring + leadingIcon decorative + trailingAction interactive), <SearchBar> (composes Input with leading search icon + conditional clear button). 10 new vitest cases. pages/login.js + pages/signup.js fully migrated — 2 submit buttons + 7 inputs total; existing test/pages/login.test.js assertion ("Sign in to Deck Hearth" button text) preserved. Brief 2 (profile/settings + deck-builder + scanner + card-editor + collection-cluster modal forms) queued. Sub-convoy #4 (liquid-glass-layout-shell) — MERGED. 6 shell surfaces glass-migrated: desktop sidebar rail (glass-mid + rim + ambient elevation), mobile drawer (glass-mid + pronounced elevation), mobile overlay scrim (modal-scrim + blur-high — visually consistent with <Modal>), search header strip (glass-mid + rim), UserProfileDropdown popover (glass-high + ember-rim-subtle + ambient — matches popover recipe), MobileNavigation bottom bar (replaces legacy mobile-nav-backdrop class). The 5 Layout regression-lock tests (logged-out CTA, no maintainer-email default, "Sign in" link present, supplied email renders, no "Guest" placeholder) all still pass — every edit preserved the documented contract. Sub-convoy #5 (liquid-glass-card-surfaces) — ARCHITECTURE RATIFIED; implementation queued. Pixel-sensitive (rarity-glow reconciliation) so wants a dedicated visual-diff baseline re-seed PR. Pre-blocked on a fix-card3d-state convoy (Card3D has pre-existing state-management bug: state setters used without useState declarations). Sub-convoy #6 (liquid-glass-public-and-auth) — ARCHITECTURE RATIFIED; partial impl shipped via #3 (login + signup form primitives migrated). Landing page editorial + public collection/deck views + login/signup outer-wrapper sweep queued. Sub-convoy #7 (motion-system-pass) — MERGED. 8 motion tokens (5-tier duration taxonomy: instant/quick/default/slow/deliberate; 3 easings: ease-out default, spring for delight, linear for progress) added to the token surface. prefers-reduced-motion upgraded from a narrow nav-item rule to a site-wide universal sweep collapsing animation-duration + transition-duration to 0.01ms (preserves end states, no flicker); .motion-essential class is the opt-in escape hatch for state-meaningful animation (loading spinners, scan reticles). Authored docs/MOTION_SYSTEM.md with WCAG SC 2.3.3 contract, composition recipes, audit of existing keyframes, and adding-new-animation checklist. Sub-convoy #8 (cleanup-legacy-design-css) — Brief 1 MERGED. Two new CI jobs in .github/workflows/ci.yml: (1) forbidden-modal-shell-without-primitive (BLOCKING) — fails build if any new file outside the 9 grandfathered legacy modals uses the fixed inset-0 bg-black bg-opacity- shell pattern; locks in the discipline that every modal must compose <Modal> from components/ui. (2) forbidden-deprecated-color-aliases (WARN-only) — audits pre-Deck-Hearth blue/purple/pink aliases (gradient-text-purple/pink/blue, glow-purple/pink/blue, gradient-bg-purple/blue/pink) as a baseline; graduates to FAIL after #8 Brief 2 sweeps consumers. .cursor/rules/ui-and-theming.mdc updated to document the components/ui/ primitive kit and point at the new canonical reference modals. Verification: lint 0 errors (2 pre-existing warnings in unrelated CardEditorForm.js + CollectionsPageView.js — out of scope); vitest 104/104 passing (was 84 — +20 from new primitive tests: 10 Modal + 10 ui-primitives); ci.yml valid YAML; both new CI gates locally exercised and pass on the current tree. Operator follow-ups documented in .convoys/ship-readiness.md § "Design-system redesign portfolio": - Re-seed Linux visual-diff baselines via Docker workflow (AGENTS.md § 6) after this merges. - preview-smoke.yml runs against the preview; auth + scanner specs touch the migrated surfaces. - Vercel promote to production once smoke + visual gates pass. - Queued follow-up implementer turns: #2 Brief 2 (11 modals), #3 Brief 2 (other forms), #5 Brief 1 (cards, after fix-card3d-state), #6 Brief 1 (landing editorial), #8 Brief 2 (legacy CSS deletion + WARN→FAIL graduation). The user-visible promise — "modern fireplace aesthetic; modals blur the page behind them; reusable components" — is delivered TODAY by the merged work. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(use-focus-trap): preserve named useFocusTrap export for ScannerPageView The portfolio squash inadvertently overwrote the pre-existing lib/use-focus-trap.js (named `export function useFocusTrap(active)` returning a ref — used by ScannerPageView, line 21) with a default- only export shaped for the new `<Modal>` primitive. Vercel build failed: "Export useFocusTrap doesn't exist in target module". Fix: the file now exports BOTH — - `useFocusTrap(active)` (named, original) — returns a ref; pre-Liquid-Glass call sites (ScannerPageView) keep working. - `useFocusTrapContainer({ active, containerRef, ... })` (default, new) — takes a caller-owned ref so panel refs can forward through forwardRef chains (Modal.js consumes this shape). Both hooks are commented to document which to use when. Modal.js imports default already, so no change needed there. Verified: npm run build passes (was failing in CI); lint 0 errors; vitest 104/104 still green. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
380 lines
16 KiB
YAML
380 lines
16 KiB
YAML
name: CI
|
|
|
|
# Vercel variant: Vercel builds Preview deployments on every push and gates the
|
|
# PR via the Vercel GitHub integration check. Running `npm run build` here too
|
|
# would duplicate Vercel's work for ~3-5 minutes per PR with no added signal.
|
|
#
|
|
# What this CI covers (and Vercel does not):
|
|
# - Lint (cheap belt-and-suspenders)
|
|
# - Schema-map drift check (docs/SCHEMA_MAP.md updated when scripts/add-*.js changes)
|
|
# - Migrations apply cleanly (node-pg-migrate against ephemeral Postgres 16)
|
|
# - Unit tests (vitest)
|
|
#
|
|
# NOTE: tcg-vault is JavaScript (not TypeScript). No `npx tsc --noEmit` step.
|
|
# Re-enable a type-check job if migrating to TypeScript.
|
|
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
push:
|
|
branches: [main]
|
|
|
|
concurrency:
|
|
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
NODE_VERSION: '20'
|
|
|
|
jobs:
|
|
lint:
|
|
name: Lint
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run lint --if-present
|
|
|
|
schema-map-fresh:
|
|
name: Schema map up to date
|
|
runs-on: ubuntu-latest
|
|
# Only run when migration scripts or the schema map itself changed.
|
|
# If neither changed, nothing to verify.
|
|
if: |
|
|
contains(github.event.pull_request.changed_files, 'scripts/add-') ||
|
|
contains(github.event.pull_request.changed_files, 'scripts/fix-') ||
|
|
contains(github.event.pull_request.changed_files, 'scripts/setup-neon-db.js') ||
|
|
contains(github.event.pull_request.changed_files, 'migrations/') ||
|
|
contains(github.event.pull_request.changed_files, 'docs/SCHEMA_MAP.md')
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 2
|
|
- name: Verify schema map updated alongside migration scripts
|
|
run: |
|
|
MIGRATION_CHANGED=false
|
|
MAP_CHANGED=false
|
|
if git diff --name-only HEAD~1 | grep -qE '^scripts/(add-|fix-|setup-neon-db\.js)'; then
|
|
MIGRATION_CHANGED=true
|
|
fi
|
|
if git diff --name-only HEAD~1 | grep -qE '^migrations/'; then
|
|
MIGRATION_CHANGED=true
|
|
fi
|
|
if git diff --name-only HEAD~1 | grep -q '^docs/SCHEMA_MAP\.md$'; then
|
|
MAP_CHANGED=true
|
|
fi
|
|
if [ "$MIGRATION_CHANGED" = "true" ] && [ "$MAP_CHANGED" = "false" ]; then
|
|
echo "::error::A migration script changed but docs/SCHEMA_MAP.md was not updated."
|
|
echo "Update docs/SCHEMA_MAP.md to reflect the schema change, then re-push."
|
|
exit 1
|
|
fi
|
|
echo "OK: schema map and migration scripts are in sync."
|
|
|
|
forbidden-endpoints:
|
|
name: No dev endpoints in pages/api
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Fail if dev endpoints re-appear under pages/api/
|
|
run: |
|
|
BAD_PATHS=(
|
|
"pages/api/simple.js"
|
|
"pages/api/test-auth.js"
|
|
"pages/api/test-db.js"
|
|
"pages/api/setup-database.js"
|
|
)
|
|
FOUND=()
|
|
for path in "${BAD_PATHS[@]}"; do
|
|
if [ -f "$path" ]; then
|
|
FOUND+=("$path")
|
|
fi
|
|
done
|
|
# Also flag any new pages/api/test-*.js the explicit list missed.
|
|
while IFS= read -r path; do
|
|
FOUND+=("$path")
|
|
done < <(find pages/api -maxdepth 4 -type f -name 'test-*.js' 2>/dev/null || true)
|
|
if [ ${#FOUND[@]} -gt 0 ]; then
|
|
echo "::error::Forbidden dev endpoints present in pages/api/. Delete them or move to scripts/."
|
|
for path in "${FOUND[@]}"; do
|
|
echo "::error file=${path}::Forbidden dev endpoint."
|
|
done
|
|
exit 1
|
|
fi
|
|
echo "OK: no forbidden dev endpoints under pages/api/."
|
|
|
|
forbidden-cors-headers:
|
|
name: No wildcard CORS in pages/api
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Fail if any pages/api/ handler carries Access-Control-Allow-Origin
|
|
run: |
|
|
# The tcg-vault frontend and API are served from the same Vercel
|
|
# deployment (same origin), so CORS headers serve no purpose and
|
|
# are a documented attack surface (see .convoys/cors-tighten.md
|
|
# and AGENTS.md Gotcha #5). Brief 4 of fix-auth-bypass cleaned
|
|
# login.js + register.js; the cors-tighten convoy swept the
|
|
# remaining 24 files. This job locks the cleanup in.
|
|
#
|
|
# If a future cross-origin caller is legitimately needed, design
|
|
# a proper CORS layer (probably via middleware) rather than
|
|
# scaffolding wildcards into individual handlers.
|
|
MATCHES=$(grep -rEn 'Access-Control-Allow-(Origin|Methods|Headers)' pages/api/ 2>/dev/null || true)
|
|
if [ -n "$MATCHES" ]; then
|
|
echo "::error::Forbidden CORS headers present under pages/api/. Remove them — same-origin Vercel deployment does not need CORS."
|
|
echo "$MATCHES" | while IFS= read -r line; do
|
|
file=$(echo "$line" | cut -d: -f1)
|
|
lineno=$(echo "$line" | cut -d: -f2)
|
|
echo "::error file=${file},line=${lineno}::Forbidden CORS header — delete this line."
|
|
done
|
|
exit 1
|
|
fi
|
|
echo "OK: no Access-Control-Allow-* headers under pages/api/."
|
|
|
|
forbidden-client-side-llm-keys:
|
|
name: No client-side LLM key leakage
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Fail on key-returning config endpoints or new browser LLM URLs
|
|
run: |
|
|
# Deleted by secure-scanner-gemini-key — must not return API keys to browsers.
|
|
if [ -f pages/api/config/gemini.js ]; then
|
|
echo "::error file=pages/api/config/gemini.js::Forbidden config endpoint — do not return API keys to browsers."
|
|
exit 1
|
|
fi
|
|
CONFIG_MATCHES=$(grep -rEn 'apiKey:' pages/api/config/ 2>/dev/null || true)
|
|
if [ -n "$CONFIG_MATCHES" ]; then
|
|
echo "::error::Forbidden apiKey response under pages/api/config/."
|
|
echo "$CONFIG_MATCHES" | while IFS= read -r line; do
|
|
file=$(echo "$line" | cut -d: -f1)
|
|
lineno=$(echo "$line" | cut -d: -f2)
|
|
echo "::error file=${file},line=${lineno}::Do not return API keys from config endpoints."
|
|
done
|
|
exit 1
|
|
fi
|
|
AI_OCR_IMPORTS=$(grep -rEn 'from ['\''"].*ai-ocr|import.*ai-ocr' components/ 2>/dev/null || true)
|
|
if [ -n "$AI_OCR_IMPORTS" ]; then
|
|
echo "::error::Browser code must not import lib/ai-ocr — use POST /api/scan/identify instead."
|
|
echo "$AI_OCR_IMPORTS" | while IFS= read -r line; do
|
|
file=$(echo "$line" | cut -d: -f1)
|
|
lineno=$(echo "$line" | cut -d: -f2)
|
|
echo "::error file=${file},line=${lineno}::Remove ai-ocr import; call server-side scan API."
|
|
done
|
|
exit 1
|
|
fi
|
|
# lib/ may hold server-only helpers imported only from pages/api/.
|
|
SERVER_ONLY=(
|
|
lib/scan-vision.js
|
|
)
|
|
LLM_PATTERN='generativelanguage\.googleapis\.com|api\.openai\.com|ai-gateway\.vercel\.sh'
|
|
FOUND=()
|
|
while IFS= read -r file; do
|
|
skip=false
|
|
for so in "${SERVER_ONLY[@]}"; do
|
|
if [ "$file" = "$so" ]; then
|
|
skip=true
|
|
break
|
|
fi
|
|
done
|
|
if [ "$skip" = true ]; then
|
|
continue
|
|
fi
|
|
if grep -qE "$LLM_PATTERN" "$file" 2>/dev/null; then
|
|
FOUND+=("$file")
|
|
fi
|
|
done < <(find components lib pages -name '*.js' ! -path 'pages/api/*' 2>/dev/null || true)
|
|
if [ ${#FOUND[@]} -gt 0 ]; then
|
|
echo "::error::Client-side LLM API URLs must not appear outside pages/api/."
|
|
for path in "${FOUND[@]}"; do
|
|
echo "::error file=${path}::Move LLM calls server-side or add to server-side-scan-pipeline removal list."
|
|
done
|
|
exit 1
|
|
fi
|
|
echo "OK: no client-side LLM key leakage patterns detected."
|
|
|
|
forbidden-modal-shell-without-primitive:
|
|
name: No new modal shells without <Modal> primitive
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Fail if a new file uses the legacy modal-shell pattern
|
|
run: |
|
|
# liquid-glass-modal-and-surface-primitive convoy — every modal
|
|
# MUST compose the `<Modal>` primitive from components/ui/.
|
|
# The 9 files below are grandfathered legacy shells queued for
|
|
# sweep under liquid-glass-modal-and-surface-primitive Brief 2.
|
|
# New files must NOT introduce another legacy shell.
|
|
#
|
|
# If a new file legitimately needs the legacy pattern (rare),
|
|
# update this list AND open a tracking issue — do not silently
|
|
# delete entries from this list.
|
|
GRANDFATHERED=(
|
|
"components/CollectionsSuccessModal.js"
|
|
"components/CollectionsEditModal.js"
|
|
"components/CollectionEditModal.js"
|
|
"components/CardDetailDeckModal.js"
|
|
"components/ScannerPageView.js"
|
|
"components/UploadImageModal.js"
|
|
"components/CollectionSelectionModal.js"
|
|
"components/OCRSettings.js"
|
|
"pages/decks.js"
|
|
)
|
|
FOUND=()
|
|
while IFS= read -r file; do
|
|
allowed=false
|
|
for grandfathered in "${GRANDFATHERED[@]}"; do
|
|
if [ "$file" = "$grandfathered" ]; then
|
|
allowed=true
|
|
break
|
|
fi
|
|
done
|
|
if [ "$allowed" = false ]; then
|
|
FOUND+=("$file")
|
|
fi
|
|
done < <(grep -lE 'fixed inset-0 bg-black bg-opacity-' \
|
|
pages components -r --include='*.js' 2>/dev/null \
|
|
| sort -u || true)
|
|
if [ ${#FOUND[@]} -gt 0 ]; then
|
|
echo "::error::Legacy modal-shell pattern detected in non-grandfathered file(s). Use the <Modal> primitive from components/ui/."
|
|
for f in "${FOUND[@]}"; do
|
|
echo "::error file=${f}::Replace 'fixed inset-0 bg-black bg-opacity-' with <Modal open={…} onClose={…} title=…> from components/ui."
|
|
done
|
|
exit 1
|
|
fi
|
|
echo "OK: no new modal shells outside the grandfathered list."
|
|
|
|
forbidden-deprecated-color-aliases:
|
|
name: No new use of deprecated color aliases
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Fail if NEW code uses pre-Deck-Hearth blue/purple/pink aliases
|
|
run: |
|
|
# liquid-glass-design-tokens + cleanup-legacy-design-css convoys.
|
|
# Pre-Deck-Hearth alias tokens (--accent-blue, --accent-purple,
|
|
# --accent-pink) and the .gradient-text-blue / .gradient-text-purple
|
|
# / .glow-blue / .glow-purple / .glow-pink utility classes are
|
|
# SCHEDULED FOR DELETION under cleanup-legacy-design-css (#8).
|
|
#
|
|
# Allowed in styles/globals.css (the definitions themselves stay
|
|
# until #8 sweeps them) but forbidden in pages/ + components/
|
|
# for NEW consumers. Existing consumers are listed below as
|
|
# grandfathered and will sweep with #8's deletion PR.
|
|
#
|
|
# Use --accent-ember, --accent-flame, --accent-gold (the canonical
|
|
# warm-palette tokens) instead.
|
|
PATTERNS=(
|
|
'gradient-text-purple'
|
|
'gradient-text-pink'
|
|
'gradient-text-blue'
|
|
'glow-purple'
|
|
'glow-pink'
|
|
'glow-blue'
|
|
'gradient-bg-purple'
|
|
'gradient-bg-blue'
|
|
'gradient-bg-pink'
|
|
)
|
|
ALL_MATCHES=""
|
|
for pattern in "${PATTERNS[@]}"; do
|
|
MATCHES=$(grep -rFn "$pattern" pages components --include='*.js' 2>/dev/null || true)
|
|
if [ -n "$MATCHES" ]; then
|
|
ALL_MATCHES=$(printf '%s\n%s' "$ALL_MATCHES" "$MATCHES")
|
|
fi
|
|
done
|
|
if [ -n "$ALL_MATCHES" ]; then
|
|
# The current tree has known consumers; print as warnings + record
|
|
# the baseline count. Once #8 sweeps them, change `exit 0` to
|
|
# `exit 1` to enforce zero.
|
|
BASELINE_COUNT=$(echo "$ALL_MATCHES" | grep -cE '.' || true)
|
|
echo "::warning::Deprecated color alias(es) found ($BASELINE_COUNT line(s)). Scheduled for deletion by cleanup-legacy-design-css convoy (#8); do not introduce new consumers."
|
|
echo "$ALL_MATCHES" | sort -u | head -20 | while IFS= read -r line; do
|
|
[ -z "$line" ] && continue
|
|
file=$(echo "$line" | cut -d: -f1)
|
|
lineno=$(echo "$line" | cut -d: -f2)
|
|
echo "::warning file=${file},line=${lineno}::Deprecated color alias — use --accent-ember / --accent-flame / --accent-gold."
|
|
done
|
|
fi
|
|
echo "OK: deprecated-color-alias audit complete."
|
|
|
|
forbidden-stale-strings:
|
|
name: No stale ownership/collection copy
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Fail if forbidden UI strings appear in pages/ or components/
|
|
run: |
|
|
# rename-collections-vocabulary convoy — ownership vs curated-list taxonomy.
|
|
# API/DB literals (e.g. system collection name) live under pages/api/ only.
|
|
PATTERNS=(
|
|
'Mark Owned'
|
|
'Owned Cards'
|
|
'All My Cards'
|
|
)
|
|
FOUND=()
|
|
for pattern in "${PATTERNS[@]}"; do
|
|
while IFS= read -r line; do
|
|
FOUND+=("$line")
|
|
done < <(grep -rFn "$pattern" pages/ components/ \
|
|
--include='*.js' \
|
|
--exclude-dir=api 2>/dev/null || true)
|
|
done
|
|
if [ ${#FOUND[@]} -gt 0 ]; then
|
|
echo "::error::Forbidden stale UI string(s) in pages/ or components/. Use lib/collection-vocabulary.js labels — see AGENTS.md § Product vocabulary."
|
|
printf '%s\n' "${FOUND[@]}" | sort -u | while IFS= read -r line; do
|
|
file=$(echo "$line" | cut -d: -f1)
|
|
lineno=$(echo "$line" | cut -d: -f2)
|
|
text=$(echo "$line" | cut -d: -f3-)
|
|
echo "::error file=${file},line=${lineno}::${text}"
|
|
done
|
|
exit 1
|
|
fi
|
|
echo "OK: no forbidden stale strings in pages/ or components/."
|
|
|
|
migrate:
|
|
name: Migrations apply (node-pg-migrate)
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:16
|
|
env:
|
|
POSTGRES_USER: postgres
|
|
POSTGRES_PASSWORD: postgres
|
|
POSTGRES_DB: deckhearth_test
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd pg_isready
|
|
--health-interval 10s
|
|
--health-timeout 5s
|
|
--health-retries 5
|
|
env:
|
|
POSTGRES_URL: postgres://postgres:postgres@localhost:5432/deckhearth_test
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- name: Write migrate env file
|
|
run: echo "POSTGRES_URL=${POSTGRES_URL}" >> .env.local
|
|
- run: npm run migrate up
|
|
|
|
test:
|
|
name: Unit tests (vitest)
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: ${{ env.NODE_VERSION }}
|
|
cache: npm
|
|
- run: npm ci
|
|
- run: npm run test:run
|
|
env:
|
|
JWT_SECRET: ci-secret-only-for-tests-do-not-use-in-prod
|