Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 24 additions & 40 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,18 @@ jobs:
name: Run tests
steps:
- name: Cache Deno dependencies
uses: actions/cache@v4
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ env.DENO_DIR }}
key: my_cache_key
- name: Checkout capgo
uses: actions/checkout@v6
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Setup bun
uses: oven-sh/setup-bun@v2
uses: oven-sh/setup-bun@3d267786b128fe76c2f16a390aa2448b815359f3 # v2
with:
bun-version: latest
- name: Check for typos
uses: crate-ci/typos@v1.42.3
uses: crate-ci/typos@06d010dfe4c84fdab1a25ea02b57b3585018ba80 # v1.42.3
- name: Show bun version
run: bun --version
- name: Show capgo version
Expand All @@ -53,7 +53,7 @@ jobs:
# - name: Lint I18n
# run: bunx @inlang/cli lint --project project.inlang
- name: Install Supabase CLI
uses: supabase/setup-cli@v1
uses: supabase/setup-cli@b60b5899c73b63a2d2d651b1e90db8d4c9392f51 # v1
with:
version: latest
- name: Show Supabase CLI version
Expand All @@ -64,7 +64,7 @@ jobs:
run: supabase test db
- name: Lint SQL
run: supabase db lint -s public --fail-on warning
- uses: JarvusInnovations/background-action@v1
- uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1
name: Bootstrap Edge server
with:
run: supabase functions serve &
Expand All @@ -89,39 +89,23 @@ jobs:
working-directory: .
- name: Run all backend and CLI tests
run: bun run test:all
# TODO: enable these tests when they are stable
# - uses: JarvusInnovations/background-action@v1
# name: Start Cloudflare Workers for testing
# with:
# run: |
# chmod +x scripts/start-cloudflare-workers.sh
# ./scripts/start-cloudflare-workers.sh
# wait-on: |
# http-get://127.0.0.1:8790/ok
# http-get://127.0.0.1:8787/ok
# http-get://127.0.0.1:8788/ok
# http-get://127.0.0.1:8789/ok
# tail: stderr,stdout
# log-output-resume: stderr,stdout
# wait-for: 2m
# log-output: stderr,stdout
# log-output-if: true
# working-directory: .
# - name: Verify D1 sync is ready
# run: |
# echo "Verifying D1 sync worker is operational..."
# for i in {1..30}; do
# response=$(curl -s -w "%{http_code}" -X POST http://127.0.0.1:8790/sync -H "x-webhook-signature: testsecret" -o /dev/null)
# if [ "$response" = "200" ]; then
# echo "✓ D1 sync is ready"
# exit 0
# fi
# echo "Waiting for D1 sync... ($i/30) - HTTP $response"
# sleep 2
# done
# echo "✗ D1 sync failed to become ready"
# exit 1
# - name: Run Cloudflare Workers plugin tests
# run: bun run test:cloudflare:plugin
- uses: JarvusInnovations/background-action@2428e7b970a846423095c79d43f759abf979a635 # v1
name: Start Cloudflare Workers for testing
with:
run: |
chmod +x scripts/start-cloudflare-workers.sh
./scripts/start-cloudflare-workers.sh
wait-on: |
http-get://127.0.0.1:8787/ok
http-get://127.0.0.1:8788/ok
http-get://127.0.0.1:8789/ok
tail: stderr,stdout
log-output-resume: stderr,stdout
wait-for: 2m
log-output: stderr,stdout
log-output-if: true
working-directory: .
- name: Run Cloudflare Workers backend tests
run: bun run test:cloudflare:backend
# - name: Run playwright tests
# run: bun run test:front
5 changes: 3 additions & 2 deletions CLOUDFLARE_TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ The application has three Cloudflare Workers:

1. Supabase must be running: `supabase start`
2. Database must be seeded: `supabase db reset`
3. Environment variables must be configured in `internal/cloudflare/.env.local`
3. Environment variables must be configured in `cloudflare_workers/.env.local`
Note: `./scripts/start-cloudflare-workers.sh` overrides the Supabase keys at runtime using `supabase status` to match your local instance, and also sets Cloudflare-local defaults like `CLOUDFLARE_FUNCTION_URL`.
4. (Optional) For V2/D1 testing: Local D1 database must be synced (see V2/D1
Testing section)

Expand Down Expand Up @@ -112,7 +113,7 @@ correct worker based on the endpoint path.

1. **Port Configuration**: Cloudflare Workers run on different ports (8787,
8788, 8789)
2. **Environment Loading**: Uses `internal/cloudflare/.env.local` instead of
2. **Environment Loading**: Uses `cloudflare_workers/.env.local` instead of
Supabase secrets
3. **Runtime**: Uses Cloudflare Workers runtime instead of Deno
4. **Worker Separation**: API and Plugin endpoints are handled by separate
Expand Down
13 changes: 13 additions & 0 deletions cloudflare_workers/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { env } from 'node:process'
import { app as accept_invitation } from '../../supabase/functions/_backend/private/accept_invitation.ts'
import { app as admin_credits } from '../../supabase/functions/_backend/private/admin_credits.ts'
import { app as admin_stats } from '../../supabase/functions/_backend/private/admin_stats.ts'
import { app as channel_stats } from '../../supabase/functions/_backend/private/channel_stats.ts'
Expand All @@ -9,13 +10,17 @@ import { app as deleted_failed_version } from '../../supabase/functions/_backend
import { app as devices_priv } from '../../supabase/functions/_backend/private/devices.ts'
import { app as events } from '../../supabase/functions/_backend/private/events.ts'
import { app as groups } from '../../supabase/functions/_backend/private/groups.ts'
import { app as invite_new_user_to_org } from '../../supabase/functions/_backend/private/invite_new_user_to_org.ts'
import { app as latency } from '../../supabase/functions/_backend/private/latency.ts'
import { app as log_as } from '../../supabase/functions/_backend/private/log_as.ts'
import { app as plans } from '../../supabase/functions/_backend/private/plans.ts'
import { app as publicStats } from '../../supabase/functions/_backend/private/public_stats.ts'
import { app as set_org_email } from '../../supabase/functions/_backend/private/set_org_email.ts'
import { app as stats_priv } from '../../supabase/functions/_backend/private/stats.ts'
import { app as storeTop } from '../../supabase/functions/_backend/private/store_top.ts'
import { app as stripe_checkout } from '../../supabase/functions/_backend/private/stripe_checkout.ts'
import { app as stripe_portal } from '../../supabase/functions/_backend/private/stripe_portal.ts'
import { app as validate_password_compliance } from '../../supabase/functions/_backend/private/validate_password_compliance.ts'
import { app as verify_email_otp } from '../../supabase/functions/_backend/private/verify_email_otp.ts'
import { app as apikey } from '../../supabase/functions/_backend/public/apikey/index.ts'
import { app as appEndpoint } from '../../supabase/functions/_backend/public/app/index.ts'
Expand All @@ -27,6 +32,7 @@ import { app as ok } from '../../supabase/functions/_backend/public/ok.ts'
import { app as organization } from '../../supabase/functions/_backend/public/organization/index.ts'
import { app as replication } from '../../supabase/functions/_backend/public/replication.ts'
import { app as statistics } from '../../supabase/functions/_backend/public/statistics/index.ts'
import { app as webhooks } from '../../supabase/functions/_backend/public/webhooks/index.ts'
import { app as cron_clean_orphan_images } from '../../supabase/functions/_backend/triggers/cron_clean_orphan_images.ts'
import { app as cron_clear_versions } from '../../supabase/functions/_backend/triggers/cron_clear_versions.ts'
import { app as cron_email } from '../../supabase/functions/_backend/triggers/cron_email.ts'
Expand Down Expand Up @@ -60,6 +66,7 @@ app.route('/channel', channel)
app.route('/device', device)
app.route('/organization', organization)
app.route('/statistics', statistics)
app.route('/webhooks', webhooks)
app.route('/app', appEndpoint)
app.route('/build', build)
app.route('/replication', replication)
Expand All @@ -72,8 +79,12 @@ appPrivate.route('/credits', credits)
appPrivate.route('/store_top', storeTop)
appPrivate.route('/website_stats', publicStats)
appPrivate.route('/config', config)
appPrivate.route('/accept_invitation', accept_invitation)
appPrivate.route('/devices', devices_priv)
appPrivate.route('/log_as', log_as)
appPrivate.route('/invite_new_user_to_org', invite_new_user_to_org)
appPrivate.route('/set_org_email', set_org_email)
appPrivate.route('/validate_password_compliance', validate_password_compliance)
appPrivate.route('/admin_credits', admin_credits)
appPrivate.route('/admin_stats', admin_stats)
appPrivate.route('/stats', stats_priv)
Expand All @@ -83,12 +94,14 @@ appPrivate.route('/stripe_portal', stripe_portal)
appPrivate.route('/verify_email_otp', verify_email_otp)
appPrivate.route('/delete_failed_version', deleted_failed_version)
appPrivate.route('/create_device', create_device)
appPrivate.route('/latency', latency)
appPrivate.route('/events', events)
appPrivate.route('/groups', groups)

// Triggers
const functionNameTriggers = 'triggers'
const appTriggers = createHono(functionNameTriggers, version)
appTriggers.route('/ok', ok)
appTriggers.route('/cron_email', cron_email)
appTriggers.route('/cron_clear_versions', cron_clear_versions)
appTriggers.route('/cron_clean_orphan_images', cron_clean_orphan_images)
Expand Down
99 changes: 70 additions & 29 deletions scripts/start-cloudflare-workers.sh
Original file line number Diff line number Diff line change
@@ -1,81 +1,122 @@
#!/usr/bin/env bash

# Script to start Cloudflare Workers for testing
# This script starts all workers (D1 Sync, API, Plugin, Files) in the background
# This script starts all workers (API, Plugin, Files) in the background

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"

echo "Starting Cloudflare Workers for testing..."

# Colors for output
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Use the installed Supabase CLI in CI, fall back to bunx locally.
if command -v supabase >/dev/null 2>&1; then
SUPABASE_CLI="supabase"
else
SUPABASE_CLI="bunx supabase"
fi

# Extract a single variable from `supabase status -o env`, preserving any '=' in values (JWT padding).
get_supabase_status_var() {
local key_regex="$1"
# Output looks like: KEY="value" or KEY=value
printf '%s\n' "${SUPA_ENV}" \
| grep -E "^(${key_regex})=" \
| head -n 1 \
| sed -E 's/^[^=]+=//' \
| sed -E 's/^"//; s/"$//'
}

# Build a runtime env file with local Supabase keys so we don't commit secrets.
BASE_ENV_FILE="${ROOT_DIR}/cloudflare_workers/.env.local"
RUNTIME_ENV_FILE="$(mktemp "${TMPDIR:-/tmp}/capgo-cloudflare-env.XXXXXX")"
chmod 600 "${RUNTIME_ENV_FILE}"
if [ -f "${BASE_ENV_FILE}" ]; then
cp "${BASE_ENV_FILE}" "${RUNTIME_ENV_FILE}"
else
echo -e "${YELLOW}Warning: ${BASE_ENV_FILE} not found - starting with empty base env${NC}"
fi

SUPA_ENV="$(${SUPABASE_CLI} status -o env 2>/dev/null || true)"
SUPABASE_URL_FROM_STATUS="$(get_supabase_status_var 'API_URL')"
# Supabase CLI has historically emitted either SERVICE_ROLE_KEY/ANON_KEY or SECRET_KEY/PUBLISHABLE_KEY.
SUPABASE_SERVICE_ROLE_KEY_FROM_STATUS="$(get_supabase_status_var 'SERVICE_ROLE_KEY|SECRET_KEY')"
SUPABASE_ANON_KEY_FROM_STATUS="$(get_supabase_status_var 'ANON_KEY|PUBLISHABLE_KEY')"

# Allow overrides via environment, otherwise use supabase status output.
SUPABASE_URL="${SUPABASE_URL:-${SUPABASE_URL_FROM_STATUS}}"
SUPABASE_SERVICE_ROLE_KEY="${SUPABASE_SERVICE_ROLE_KEY:-${SUPABASE_SERVICE_ROLE_KEY_FROM_STATUS}}"
SUPABASE_ANON_KEY="${SUPABASE_ANON_KEY:-${SUPABASE_ANON_KEY_FROM_STATUS}}"

if [ -z "${SUPABASE_SERVICE_ROLE_KEY}" ] || [ -z "${SUPABASE_ANON_KEY}" ] || [ -z "${SUPABASE_URL}" ]; then
echo -e "${YELLOW}Missing Supabase keys for Cloudflare Workers.${NC}"
echo "Ensure Supabase is running, or set SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY and SUPABASE_ANON_KEY in your environment."
exit 1
fi

# Cloudflare local testing defaults.
CLOUDFLARE_FUNCTION_URL="${CLOUDFLARE_FUNCTION_URL:-http://127.0.0.1:8787}"
STRIPE_WEBHOOK_SECRET="${STRIPE_WEBHOOK_SECRET:-testsecret}"

# In CI/linux, `host.docker.internal` is unreliable. Prefer localhost (mapped ports).
S3_ENDPOINT_TO_USE="${S3_ENDPOINT:-127.0.0.1:9000}"

cat >> "${RUNTIME_ENV_FILE}" <<EOF
SUPABASE_URL=${SUPABASE_URL}
SUPABASE_SERVICE_ROLE_KEY=${SUPABASE_SERVICE_ROLE_KEY}
SUPABASE_ANON_KEY=${SUPABASE_ANON_KEY}
CLOUDFLARE_FUNCTION_URL=${CLOUDFLARE_FUNCTION_URL}
STRIPE_WEBHOOK_SECRET=${STRIPE_WEBHOOK_SECRET}
S3_ENDPOINT=${S3_ENDPOINT_TO_USE}
EOF

# Kill any existing wrangler processes
echo -e "${YELLOW}Cleaning up existing wrangler processes...${NC}"
pkill -f "wrangler dev" || true
sleep 2

# Wait a bit for the sync worker to start
sleep 3

# Start API worker on port 8787
echo -e "${GREEN}Starting API worker on port 8787...${NC}"
(cd cloudflare_workers/api && bunx wrangler dev -c wrangler.jsonc --port 8787 --env-file=../.env.local --env=local --persist-to ../../.wrangler-shared) &
(cd "${ROOT_DIR}/cloudflare_workers/api" && bunx wrangler dev -c wrangler.jsonc --port 8787 --env-file="${RUNTIME_ENV_FILE}" --env=local --persist-to "${ROOT_DIR}/.wrangler-shared") &
API_PID=$!

# Wait a bit for the first worker to start
sleep 3

# Start Plugin worker on port 8788
echo -e "${GREEN}Starting Plugin worker on port 8788...${NC}"
(cd cloudflare_workers/plugin && bunx wrangler dev -c wrangler.jsonc --port 8788 --env-file=../.env.local --env=local --persist-to ../../.wrangler-shared) &
(cd "${ROOT_DIR}/cloudflare_workers/plugin" && bunx wrangler dev -c wrangler.jsonc --port 8788 --env-file="${RUNTIME_ENV_FILE}" --env=local --persist-to "${ROOT_DIR}/.wrangler-shared") &
PLUGIN_PID=$!

# Wait a bit for the second worker to start
sleep 3

# Start Files worker on port 8789
echo -e "${GREEN}Starting Files worker on port 8789...${NC}"
(cd cloudflare_workers/files && bunx wrangler dev -c wrangler.jsonc --port 8789 --env-file=../.env.local --env=local --persist-to ../../.wrangler-shared) &
(cd "${ROOT_DIR}/cloudflare_workers/files" && bunx wrangler dev -c wrangler.jsonc --port 8789 --env-file="${RUNTIME_ENV_FILE}" --env=local --persist-to "${ROOT_DIR}/.wrangler-shared") &
FILES_PID=$!

echo -e "${GREEN}All workers started!${NC}"
echo "D1 Sync Worker PID: $SYNC_PID (http://127.0.0.1:8790)"
echo "API Worker PID: $API_PID (http://127.0.0.1:8787)"
echo "Plugin Worker PID: $PLUGIN_PID (http://127.0.0.1:8788)"
echo "Files Worker PID: $FILES_PID (http://127.0.0.1:8789)"
echo ""

# Queue initial data to D1 via PGMQ (production-like approach)
echo -e "${GREEN}Queueing initial data for D1 sync...${NC}"
psql postgresql://postgres:postgres@127.0.0.1:54322/postgres -f scripts/trigger-initial-d1-sync.sql > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ Initial data queued to PGMQ${NC}"

# Trigger sync worker to process the queue
echo -e "${GREEN}Triggering D1 sync worker...${NC}"
curl -s -X POST http://127.0.0.1:8790/sync -H "x-webhook-signature: testsecret" > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo -e "${GREEN}✓ D1 sync triggered successfully${NC}"
sleep 2
echo -e "${GREEN}✓ D1 database is now ready with initial data${NC}"
else
echo -e "${YELLOW}⚠ Warning: Failed to trigger D1 sync${NC}"
fi
else
echo -e "${YELLOW}⚠ Warning: Failed to queue initial data${NC}"
fi

echo ""
echo "Press Ctrl+C to stop all workers"

# Function to cleanup on exit
cleanup() {
echo -e "\n${YELLOW}Stopping workers...${NC}"
kill $SYNC_PID $API_PID $PLUGIN_PID $FILES_PID 2>/dev/null || true
kill $API_PID $PLUGIN_PID $FILES_PID 2>/dev/null || true
pkill -f "wrangler dev" || true
rm -f "${RUNTIME_ENV_FILE}" 2>/dev/null || true
echo -e "${GREEN}All workers stopped${NC}"
}

Expand Down
13 changes: 7 additions & 6 deletions scripts/test-cloudflare-v2.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#!/usr/bin/env bash

# Complete workflow for testing Cloudflare Workers with V2 (D1) enabled
# Complete workflow for testing Cloudflare Workers locally.
#
# Note: Despite the "V2/D1" naming, this script currently validates the Cloudflare
# Workers runtime integration (API/Plugin/Files) against the local Supabase
# database. If/when a D1 sync worker is reintroduced, extend this script to run
# the sync step before tests.

set -e

Expand All @@ -14,11 +19,7 @@ NC='\033[0m'

# 1. Reset and seed database
echo -e "\n${YELLOW}Step 1: Resetting Supabase database...${NC}"
supabase db reset

# 2. Sync to D1
echo -e "\n${YELLOW}Step 2: Syncing data from Postgres to D1...${NC}"
bun run scripts/sync-postgres-to-d1.ts
PAGER=cat bunx supabase db reset

# 3. Start workers
echo -e "\n${YELLOW}Step 3: Starting Cloudflare Workers...${NC}"
Expand Down
Loading
Loading