Skip to content

Pipeline

Pipeline #83

Workflow file for this run

name: Pipeline
on:
push:
branches:
- "**"
pull_request:
branches:
- "**"
schedule:
- cron: "0 6 * * *" # notify discoveries (daily)
- cron: "0 0 * * *" # unlock solarhealth (daily)
- cron: "0 14 * * 6" # delete anomaly log (weekly)
- cron: "0 21 * * 0" # codeql (weekly)
workflow_dispatch:
inputs:
run_notify_discoveries:
description: "Run discovery notification maintenance job"
required: false
default: false
type: boolean
run_unlock_solarhealth:
description: "Run solarhealth unlock maintenance job"
required: false
default: false
type: boolean
run_delete_anomaly_log:
description: "Run anomaly log cleanup maintenance job"
required: false
default: false
type: boolean
run_community_event_notify:
description: "Run community event push notification job"
required: false
default: false
type: boolean
community_event_title:
description: "Community event notification title"
required: false
default: "Community event"
type: string
community_event_message:
description: "Community event notification message"
required: false
default: "A new community event is live."
type: string
community_event_url:
description: "URL to open from notification"
required: false
default: "/game"
type: string
community_event_dry_run:
description: "If true, do not send notifications; only produce a report."
required: false
default: true
type: boolean
permissions:
contents: read
concurrency:
group: pipeline-${{ github.ref }}
cancel-in-progress: true
jobs:
test_suite:
if: github.event_name != 'schedule'
runs-on: ubuntu-latest
env:
NODE_ENV: test
SKIP_USER_CREATION_TESTS: "true"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
- name: Install dependencies
run: |
yarn lock:check
yarn install --frozen-lockfile
- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
with:
version: latest
- name: Start local Supabase
run: supabase start
- name: Normalize local test env defaults
run: |
SUPABASE_URL="${SUPABASE_URL:-${{ vars.SUPABASE_URL }}}"
NEXT_PUBLIC_SUPABASE_URL="${NEXT_PUBLIC_SUPABASE_URL:-${{ vars.NEXT_PUBLIC_SUPABASE_URL }}}"
NEXT_PUBLIC_SUPABASE_ANON_KEY="${NEXT_PUBLIC_SUPABASE_ANON_KEY:-${{ vars.NEXT_PUBLIC_SUPABASE_ANON_KEY }}}"
SUPABASE_SERVICE_ROLE_KEY="${SUPABASE_SERVICE_ROLE_KEY:-${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}}"
DATABASE_URL="${DATABASE_URL:-}"
SUPABASE_URL="${SUPABASE_URL:-http://127.0.0.1:54321}"
NEXT_PUBLIC_SUPABASE_URL="${NEXT_PUBLIC_SUPABASE_URL:-http://127.0.0.1:54321}"
STATUS_ENV="$(supabase status -o env 2>/dev/null || true)"
if [ -n "${STATUS_ENV}" ]; then
LOCAL_DB_URL="$(printf '%s\n' "${STATUS_ENV}" | sed -n 's/^DB_URL=//p' | tail -n1 | sed 's/^"//; s/"$//')"
LOCAL_PUBLISHABLE="$(printf '%s\n' "${STATUS_ENV}" | sed -n 's/^ANON_KEY=//p' | tail -n1 | sed 's/^"//; s/"$//')"
LOCAL_SECRET="$(printf '%s\n' "${STATUS_ENV}" | sed -n 's/^SERVICE_ROLE_KEY=//p' | tail -n1 | sed 's/^"//; s/"$//')"
if [ -z "${LOCAL_PUBLISHABLE}" ]; then
LOCAL_PUBLISHABLE="$(printf '%s\n' "${STATUS_ENV}" | sed -n 's/^PUBLISHABLE_KEY=//p' | tail -n1 | sed 's/^"//; s/"$//')"
fi
if [ -z "${LOCAL_SECRET}" ]; then
LOCAL_SECRET="$(printf '%s\n' "${STATUS_ENV}" | sed -n 's/^SECRET_KEY=//p' | tail -n1 | sed 's/^"//; s/"$//')"
fi
fi
if [ -z "${LOCAL_PUBLISHABLE}" ] || [ -z "${LOCAL_SECRET}" ]; then
STATUS_TEXT="$(supabase status)"
LOCAL_PUBLISHABLE="$(printf '%s\n' "${STATUS_TEXT}" | sed -n 's/^| Publishable | \(.*\) |$/\1/p' | tail -n1)"
LOCAL_SECRET="$(printf '%s\n' "${STATUS_TEXT}" | sed -n 's/^| Secret | \(.*\) |$/\1/p' | tail -n1)"
fi
DATABASE_URL="${DATABASE_URL:-$LOCAL_DB_URL}"
NEXT_PUBLIC_SUPABASE_ANON_KEY="${NEXT_PUBLIC_SUPABASE_ANON_KEY:-$LOCAL_PUBLISHABLE}"
SUPABASE_SERVICE_ROLE_KEY="${SUPABASE_SERVICE_ROLE_KEY:-$LOCAL_SECRET}"
if [ -z "${DATABASE_URL}" ] || [ -z "${NEXT_PUBLIC_SUPABASE_ANON_KEY}" ] || [ -z "${SUPABASE_SERVICE_ROLE_KEY}" ]; then
echo "Unable to resolve local Supabase database/auth env from supabase status." >&2
exit 1
fi
echo "DATABASE_URL=${DATABASE_URL}" >> "$GITHUB_ENV"
echo "SUPABASE_URL=${SUPABASE_URL}" >> "$GITHUB_ENV"
echo "NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL}" >> "$GITHUB_ENV"
echo "NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY}" >> "$GITHUB_ENV"
echo "SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}" >> "$GITHUB_ENV"
- name: Run unit tests
run: yarn test:unit
- name: Run unit tests with coverage
run: yarn test:unit --coverage
- name: Run lint
run: yarn lint
- name: Run E2E tests (minimal smoke for regular pushes)
if: github.event_name == 'push' && github.ref_name != 'main'
run: yarn test:e2e:smoke:minimal
- name: Run E2E tests (full suite for main branch pushes)
if: github.event_name == 'push' && github.ref_name == 'main'
run: yarn test:e2e
- name: Run E2E tests (full suite for PR to main)
if: >
github.event_name == 'pull_request' && github.base_ref == 'main'
run: yarn test:e2e
- name: Upload e2e coverage
if: >
always() && (
(github.event_name == 'pull_request' && github.base_ref == 'main')
)
uses: actions/upload-artifact@v4
with:
name: e2e-coverage
path: |
coverage-e2e
.nyc_output
- name: Upload Cypress artifacts
if: >
always() && (
(github.event_name == 'push' && github.ref_name == 'main') ||
(github.event_name == 'pull_request' && github.base_ref == 'main')
)
uses: actions/upload-artifact@v4
with:
name: cypress-artifacts
path: |
cypress/videos
cypress/screenshots
build_app:
if: github.event_name != 'schedule'
needs: test_suite
runs-on: ubuntu-latest
env:
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
VAPID_PUBLIC_KEY: ${{ secrets.VAPID_PUBLIC_KEY }}
VAPID_PRIVATE_KEY: ${{ secrets.VAPID_PRIVATE_KEY }}
DATABASE_URL: "postgresql://placeholder:placeholder@localhost:5432/placeholder"
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
- name: Install dependencies
run: |
yarn lock:check
yarn install --frozen-lockfile
- name: Build app
run: |
rm -rf .next
yarn build
- name: Check bundle budgets
run: yarn bundle:check
deploy_preview:
if: github.event_name == 'push' && github.ref_name != 'main'
needs: build_app
runs-on: ubuntu-latest
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
- name: Pull Vercel Environment Information
run: npx vercel@latest pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ secrets.VERCEL_ORG_ID }}
- name: Build Project Artifacts
run: |
ATTEMPTS=3
for ATTEMPT in $(seq 1 "$ATTEMPTS"); do
echo "Vercel preview build attempt ${ATTEMPT}/${ATTEMPTS}"
if npx vercel@latest build --yes --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ secrets.VERCEL_ORG_ID }}; then
exit 0
fi
if [ "$ATTEMPT" -lt "$ATTEMPTS" ]; then
echo "Build failed (likely transient). Retrying in 20s..."
sleep 20
fi
done
echo "Vercel preview build failed after ${ATTEMPTS} attempts."
exit 1
- name: Deploy Project Artifacts to Vercel Staging
run: |
ATTEMPTS=3
for ATTEMPT in $(seq 1 "$ATTEMPTS"); do
echo "Vercel preview deploy attempt ${ATTEMPT}/${ATTEMPTS}"
if npx vercel@latest deploy --prebuilt --yes --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ secrets.VERCEL_ORG_ID }}; then
exit 0
fi
if [ "$ATTEMPT" -lt "$ATTEMPTS" ]; then
echo "Deploy failed (likely transient). Retrying in 20s..."
sleep 20
fi
done
echo "Vercel preview deploy failed after ${ATTEMPTS} attempts."
exit 1
deploy_production:
if: github.event_name == 'push' && github.ref_name == 'main'
needs: build_app
runs-on: ubuntu-latest
env:
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22"
cache: "yarn"
- name: Pull Vercel Environment Information
run: npx vercel@latest pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ secrets.VERCEL_ORG_ID }}
- name: Build Project Artifacts
run: |
ATTEMPTS=3
for ATTEMPT in $(seq 1 "$ATTEMPTS"); do
echo "Vercel production build attempt ${ATTEMPT}/${ATTEMPTS}"
if npx vercel@latest build --prod --yes --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ secrets.VERCEL_ORG_ID }}; then
exit 0
fi
if [ "$ATTEMPT" -lt "$ATTEMPTS" ]; then
echo "Build failed (likely transient). Retrying in 20s..."
sleep 20
fi
done
echo "Vercel production build failed after ${ATTEMPTS} attempts."
exit 1
- name: Deploy Project Artifacts to Vercel
run: |
ATTEMPTS=3
for ATTEMPT in $(seq 1 "$ATTEMPTS"); do
echo "Vercel production deploy attempt ${ATTEMPT}/${ATTEMPTS}"
if npx vercel@latest deploy --prebuilt --prod --yes --token=${{ secrets.VERCEL_TOKEN }} --scope=${{ secrets.VERCEL_ORG_ID }}; then
exit 0
fi
if [ "$ATTEMPT" -lt "$ATTEMPTS" ]; then
echo "Deploy failed (likely transient). Retrying in 20s..."
sleep 20
fi
done
echo "Vercel production deploy failed after ${ATTEMPTS} attempts."
exit 1
notify_unclassified_discoveries:
if: >
(github.event_name == 'schedule' && github.event.schedule == '0 6 * * *') ||
(github.event_name == 'workflow_dispatch' && inputs.run_notify_discoveries == true)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
- name: Install dependencies
run: |
yarn lock:check
yarn install --frozen-lockfile
- name: Send discovery notifications
env:
SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}
NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${{ secrets.VAPID_PUBLIC_KEY }}
VAPID_PRIVATE_KEY: ${{ secrets.VAPID_PRIVATE_KEY }}
SUPABASE_DB_URL: ${{ secrets.SUPABASE_DB_URL }}
run: node --experimental-strip-types scripts/notify-unclassified-discoveries.ts
unlock_solarhealth_anomalies:
if: >
(github.event_name == 'schedule' && github.event.schedule == '0 0 * * *') ||
(github.event_name == 'workflow_dispatch' && inputs.run_unlock_solarhealth == true) ||
(github.event_name == 'push' && contains(github.event.head_commit.message, 'SSM-257'))
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
- name: Install dependencies
run: |
yarn lock:check
yarn install --frozen-lockfile
- name: Unlock SolarHealth anomalies
env:
SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
run: node --experimental-strip-types scripts/unlock-solarhealth-anomalies.ts
delete_push_anomaly_log:
if: >
(github.event_name == 'schedule' && github.event.schedule == '0 14 * * 6') ||
(github.event_name == 'workflow_dispatch' && inputs.run_delete_anomaly_log == true)
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: "1.21"
- name: Install dependencies
run: go mod tidy
- name: Delete push anomaly log
env:
SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
run: go run scripts/delete_push_anomaly_log.go
community_event_notify:
if: github.event_name == 'workflow_dispatch' && inputs.run_community_event_notify == true
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "yarn"
- name: Install dependencies
run: |
yarn lock:check
yarn install --frozen-lockfile
- name: Send community event push notification (or dry run)
env:
SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}
SUPABASE_SERVICE_ROLE_KEY: ${{ secrets.SUPABASE_SERVICE_ROLE_KEY }}
NEXT_PUBLIC_VAPID_PUBLIC_KEY: ${{ secrets.VAPID_PUBLIC_KEY }}
VAPID_PRIVATE_KEY: ${{ secrets.VAPID_PRIVATE_KEY }}
COMMUNITY_EVENT_TITLE: ${{ inputs.community_event_title }}
COMMUNITY_EVENT_MESSAGE: ${{ inputs.community_event_message }}
COMMUNITY_EVENT_URL: ${{ inputs.community_event_url }}
DRY_RUN: ${{ inputs.community_event_dry_run }}
REPORT_PATH: community-event-report.json
run: node --experimental-strip-types scripts/notify-community-event.ts
- name: Upload community event report
if: always()
uses: actions/upload-artifact@v4
with:
name: community-event-report
path: community-event-report.json