Bump the npm-dependencies group with 2 updates #43
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Runs lint, unit tests, and E2E suites to gate deployments | |
| name: Test | |
| on: | |
| push: | |
| branches: | |
| - main | |
| pull_request: | |
| branches: | |
| - main | |
| env: | |
| CONVERTKIT_API_KEY: ${{ secrets.CONVERTKIT_API_KEY }} | |
| CONVERTKIT_FORM_ID: ${{ secrets.CONVERTKIT_FORM_ID }} | |
| CRON_SECRET: ${{ secrets.CRON_SECRET }} | |
| RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SUPABASE_URL: ${{ secrets.SUPABASE_URL }} | |
| SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} | |
| SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }} | |
| SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }} | |
| KV_URL: ${{ secrets.KV_URL }} | |
| KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }} | |
| KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }} | |
| KV_REST_API_READ_ONLY_TOKEN: ${{ secrets.KV_REST_API_READ_ONLY_TOKEN }} | |
| REDIS_URL: ${{ secrets.REDIS_URL }} | |
| WEBMENTION_IO_TOKEN: ${{ secrets.WEBMENTION_IO_TOKEN }} | |
| jobs: | |
| lint: | |
| name: Lint | |
| runs-on: ubuntu-latest | |
| if: ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci --legacy-peer-deps | |
| - name: Sync Astro types | |
| run: npm run sync | |
| - name: Run Astro check | |
| run: npm run check | |
| - name: Run lint | |
| run: npm run lint:base | |
| - name: Run Actions lint | |
| run: npm run lint:actions | |
| unit-test: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| needs: lint | |
| if: ${{ !startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci --legacy-peer-deps | |
| - name: Sync Astro types | |
| run: npm run sync | |
| - name: Run Astro check | |
| run: npm run check | |
| - name: Run unit tests (Vitest — coverage with GitHub Actions reporter) | |
| id: vitest | |
| run: npm run test:coverage | |
| - name: Report Coverage | |
| if: steps.vitest.outcome == 'success' && hashFiles('coverage/coverage-summary.json') != '' | |
| uses: davelosert/vitest-coverage-report-action@v2.9.0 | |
| with: | |
| json-summary-path: './coverage/coverage-summary.json' | |
| json-final-path: './coverage/coverage-final.json' | |
| - name: Upload test coverage | |
| if: always() && hashFiles('coverage/coverage-summary.json') != '' | |
| uses: actions/upload-artifact@v5 | |
| with: | |
| name: test-coverage | |
| path: coverage/ | |
| retention-days: 30 | |
| e2e-test: | |
| name: E2E Tests | |
| runs-on: ubuntu-latest | |
| needs: unit-test | |
| # Temporarily disabled while Supabase migrations are fixed; set this expression back to the previous condition when ready | |
| if: ${{ false && !startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| env: | |
| CONVERTKIT_API_KEY: ${{ secrets.CONVERTKIT_API_KEY }} | |
| CONVERTKIT_FORM_ID: ${{ secrets.CONVERTKIT_FORM_ID }} | |
| CRON_SECRET: ${{ secrets.CRON_SECRET }} | |
| RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} | |
| SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} | |
| SENTRY_DSN: ${{ secrets.SENTRY_DSN }} | |
| SUPABASE_URL: ${{ secrets.SUPABASE_URL }} | |
| SUPABASE_KEY: ${{ secrets.SUPABASE_KEY }} | |
| SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }} | |
| SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }} | |
| SUPABASE_PROJECT_REF: ${{ secrets.SUPABASE_PROJECT_REF }} | |
| SUPABASE_DB_PASSWORD: ${{ secrets.SUPABASE_DB_PASSWORD }} | |
| KV_URL: ${{ secrets.KV_URL }} | |
| KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }} | |
| KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }} | |
| KV_REST_API_READ_ONLY_TOKEN: ${{ secrets.KV_REST_API_READ_ONLY_TOKEN }} | |
| REDIS_URL: ${{ secrets.REDIS_URL }} | |
| WEBMENTION_IO_TOKEN: ${{ secrets.WEBMENTION_IO_TOKEN }} | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22.x' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm ci --legacy-peer-deps | |
| - name: Install Playwright browsers | |
| run: npx playwright install --with-deps | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3.11.1 | |
| - name: Build Upstash mock image | |
| run: docker buildx build --load -t wb/upstash-redis-local:test test/containers/upstash/local-proxy | |
| - name: Generate container env file | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mkdir -p test/containers | |
| : "${SUPABASE_SERVICE_ROLE_KEY:?SUPABASE_SERVICE_ROLE_KEY secret is required}" | |
| cat <<EOF_ENV > test/containers/.env | |
| COMPOSE_PROJECT_NAME=wb-e2e | |
| CONVERTKIT_HTTP_PORT=9010 | |
| RESEND_HTTP_PORT=9011 | |
| UPSTASH_HTTP_PORT=${UPSTASH_HTTP_PORT:-8079} | |
| UPSTASH_REDIS_PORT=${UPSTASH_REDIS_PORT:-6380} | |
| UPSTASH_TOKEN=${UPSTASH_TOKEN:-local-dev-token} | |
| SUPABASE_HEALTH_TIMEOUT=${SUPABASE_HEALTH_TIMEOUT:-240} | |
| SUPABASE_URL=${SUPABASE_URL:-http://127.0.0.1:54321} | |
| SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY} | |
| EOF_ENV | |
| - name: Start mock containers | |
| run: npm run containers:up | |
| - name: Wait for mock services | |
| run: npm run containers:wait | |
| - name: Pre-pull Supabase storage image | |
| run: | | |
| set -euo pipefail | |
| image="public.ecr.aws/supabase/storage-api:v1.32.1" | |
| for attempt in 1 2 3 4 5 6; do | |
| echo "Attempt ${attempt}: pulling ${image}" | |
| if docker pull "${image}"; then | |
| echo "Pulled ${image} successfully" | |
| exit 0 | |
| fi | |
| sleep_seconds=$((2 ** attempt)) | |
| echo "Pull failed; sleeping ${sleep_seconds}s before retry" | |
| sleep "${sleep_seconds}" | |
| done | |
| echo "Failed to pull ${image} after multiple attempts" >&2 | |
| exit 1 | |
| - name: Start Supabase stack | |
| run: npm run containers:supabase:start | |
| - name: Apply Supabase migrations (local container) | |
| run: | | |
| set -euo pipefail | |
| echo "::group::Supabase directory layout" | |
| ls -al suprabase || true | |
| find suprabase -maxdepth 2 -type f -print | sort || true | |
| echo "::endgroup::" | |
| echo "::group::Supabase migrations catalog check" | |
| bash <<'BASH' | |
| set -euo pipefail | |
| find_db_container() { | |
| docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$3 ~ /^supabase\/postgres(:|$)/ {print $1" "$2; exit}' | |
| } | |
| db_container_line=$(find_db_container) | |
| if [ -z "${db_container_line}" ]; then | |
| db_container_line=$(docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$2 ~ /_db_/ {print $1" "$2; exit}') | |
| fi | |
| if [ -z "${db_container_line}" ]; then | |
| echo "Supabase database container not found; cannot verify catalog" >&2 | |
| exit 1 | |
| fi | |
| container_id=$(echo "${db_container_line}" | awk '{print $1}') | |
| catalog_present=$(docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -At -c "select to_regclass('supabase_migrations.schema_migrations');" || true) | |
| if [ -z "${catalog_present}" ] || [ "${catalog_present}" = "" ] || [ "${catalog_present}" = "NULL" ]; then | |
| echo "supabase_migrations.schema_migrations missing; creating manually" | |
| docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c "CREATE SCHEMA IF NOT EXISTS supabase_migrations;" | |
| docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -v ON_ERROR_STOP=1 -c "CREATE TABLE IF NOT EXISTS supabase_migrations.schema_migrations (version text PRIMARY KEY, inserted_at timestamptz DEFAULT now());" | |
| echo "Catalog created; verifying contents" | |
| docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -c "\\d supabase_migrations.schema_migrations" | |
| docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -c "table supabase_migrations.schema_migrations" | |
| else | |
| echo "Catalog present: ${catalog_present}" | |
| fi | |
| BASH | |
| echo "::endgroup::" | |
| echo "::group::Supabase migrations" | |
| npm run containers:supabase:db-push | |
| echo "::endgroup::" | |
| echo "::group::Supabase migration status" | |
| FORCE_COLOR=1 npx supabase migration list --local --workdir suprabase | |
| echo "::endgroup::" | |
| echo "::group::Supabase migration status (JSON)" | |
| FORCE_COLOR=1 npx supabase migration list --local --workdir suprabase --output json || true | |
| echo "::endgroup::" | |
| echo "::group::Supabase schema snapshot" | |
| schema_dir="artifacts/supabase" | |
| schema_path="${schema_dir}/schema.sql" | |
| mkdir -p "${schema_dir}" | |
| FORCE_COLOR=1 npx dotenv-cli -e test/containers/.env -- bash -c 'npx supabase db dump --local --schema "public,graphql_public,storage" --workdir suprabase' > "${schema_path}" | |
| head -n 200 "${schema_path}" || true | |
| echo "(full schema saved to ${schema_path})" | |
| echo "::endgroup::" | |
| echo "::group::Supabase table inventory" | |
| bash <<'BASH' | |
| set -euo pipefail | |
| find_db_container() { | |
| docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$3 ~ /^supabase\/postgres(:|$)/ {print $1" "$2; exit}' | |
| } | |
| db_container_line=$(find_db_container) | |
| if [ -z "${db_container_line}" ]; then | |
| db_container_line=$(docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$2 ~ /_db_/ {print $1" "$2; exit}') | |
| fi | |
| if [ -z "${db_container_line}" ]; then | |
| echo "Supabase database container not found; skipping inventory" >&2 | |
| exit 0 | |
| fi | |
| container_id=$(echo "${db_container_line}" | awk '{print $1}') | |
| echo "Listing public schema tables via container ${container_id}" | |
| docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -c '\dt public.*' || true | |
| docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -At -c "select table_schema || '.' || table_name from information_schema.tables where table_schema='public' order by 1;" || true | |
| BASH | |
| echo "::endgroup::" | |
| echo "::group::Supabase schema guard" | |
| bash <<'BASH' | |
| set -euo pipefail | |
| find_db_container() { | |
| docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$3 ~ /^supabase\/postgres(:|$)/ {print $1" "$2; exit}' | |
| } | |
| db_container_line=$(find_db_container) | |
| if [ -z "${db_container_line}" ]; then | |
| db_container_line=$(docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$2 ~ /_db_/ {print $1" "$2; exit}') | |
| fi | |
| if [ -z "${db_container_line}" ]; then | |
| echo "Supabase database container not found; cannot verify migrations" >&2 | |
| exit 1 | |
| fi | |
| container_id=$(echo "${db_container_line}" | awk '{print $1}') | |
| container_name=$(echo "${db_container_line}" | awk '{print $2}') | |
| echo "Inspecting tables inside ${container_name} (${container_id})" | |
| required_tables=(newsletter_confirmations consent_records dsar_requests) | |
| missing=0 | |
| for table in "${required_tables[@]}"; do | |
| if ! docker exec -e PGPASSWORD=postgres "${container_id}" psql -U postgres -d postgres -At -c "select to_regclass('public.${table}');" | grep -q "public.${table}"; then | |
| echo "❌ Missing table public.${table}" | |
| missing=1 | |
| else | |
| echo "✅ Found table public.${table}" | |
| fi | |
| done | |
| if [ "${missing}" -ne 0 ]; then | |
| echo "Required tables missing after Supabase migrations" >&2 | |
| exit 1 | |
| fi | |
| BASH | |
| echo "::endgroup::" | |
| echo "::group::Restart Supabase REST container" | |
| bash <<'BASH' | |
| set -euo pipefail | |
| rest_container_line=$(docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$3 ~ /postgrest/ {print $1" "$2; exit}') | |
| if [ -z "${rest_container_line}" ]; then | |
| echo "Supabase REST container not found; skipping restart" >&2 | |
| exit 0 | |
| fi | |
| container_id=$(echo "${rest_container_line}" | awk '{print $1}') | |
| container_name=$(echo "${rest_container_line}" | awk '{print $2}') | |
| echo "Restarting ${container_name} (${container_id}) to refresh schema cache" | |
| docker restart "${container_id}" | |
| BASH | |
| echo "::endgroup::" | |
| echo "::group::Supabase container logs (last 200 lines)" | |
| bash <<'BASH' | |
| set -euo pipefail | |
| containers=$(docker ps --format '{{.ID}} {{.Names}} {{.Image}}' | awk '$3 ~ /supabase\// {print $1" "$2}') | |
| if [ -z "${containers}" ]; then | |
| echo "No Supabase containers found" >&2 | |
| exit 0 | |
| fi | |
| while read -r container_id container_name; do | |
| [ -z "${container_id}" ] && continue | |
| echo "--- ${container_name} (tail -n 200) ---" | |
| docker logs --tail 200 "${container_id}" || true | |
| done <<< "${containers}" | |
| BASH | |
| echo "::endgroup::" | |
| - name: Upload Supabase schema snapshot | |
| if: ${{ always() && hashFiles('artifacts/supabase/schema.sql') != '' }} | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: supabase-schema | |
| path: artifacts/supabase/schema.sql | |
| retention-days: 14 | |
| - name: Start Astro dev server | |
| run: | | |
| npm run dev -- --host 0.0.0.0 > /tmp/astro-dev.log 2>&1 & | |
| echo $! > /tmp/astro-dev.pid | |
| - name: Wait for dev server | |
| run: | | |
| for attempt in $(seq 1 60); do | |
| if curl -fsS http://127.0.0.1:4321 >/dev/null; then | |
| echo "✅ Dev server is responding" | |
| exit 0 | |
| fi | |
| sleep 2 | |
| done | |
| echo "❌ Dev server failed to start" >&2 | |
| if [ -f /tmp/astro-dev.log ]; then | |
| echo '--- Astro dev server log ---' | |
| cat /tmp/astro-dev.log | |
| fi | |
| exit 1 | |
| - name: Run Playwright E2E tests | |
| run: npx playwright test | |
| env: | |
| CI: '1' | |
| FORCE_COLOR: '1' | |
| E2E_MOCKS: '1' | |
| - name: Stop Astro dev server | |
| if: always() | |
| run: | | |
| if [ -f /tmp/astro-dev.pid ]; then | |
| kill $(cat /tmp/astro-dev.pid) || true | |
| rm /tmp/astro-dev.pid | |
| fi | |
| - name: Upload Playwright report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 30 | |
| - name: Stop Supabase stack | |
| if: always() | |
| run: npm run containers:supabase:stop || true | |
| - name: Stop mock containers | |
| if: always() | |
| run: npm run containers:down || true | |
| hotfix-bypass: | |
| name: Hotfix Bypass Notice | |
| runs-on: ubuntu-latest | |
| if: ${{ startsWith(github.head_ref || github.ref_name, 'hotfix/') }} | |
| steps: | |
| - name: Skip testing for hotfix branch | |
| run: echo "hotfix/* branch detected; skipping lint, unit, and e2e workflows but allowing deployments." |