Files
webstudio/apps/builder/backend/lib.sh
Oleg Isonen 4273b7001f refactor: Organize Builder development and test tooling (#6285)
## Outcome

Builder development, backend, E2E, and CI tooling now share one
ownership model: Builder defines named commands and backend modes, while
CI only schedules those commands.

## Technical choices

- Group shared Compose files, PostgreSQL initialization, and backend
helpers under `apps/builder/backend`.
- Resolve `compose.<mode>.yaml` by convention instead of maintaining
Compose filename lists in scripts and workflows.
- Keep persistent development and disposable test Compose projects
separate by default, while allowing the standard `COMPOSE_PROJECT_NAME`
override.
- Make schema snapshots invalidate from discovered migration sources and
backend files.
- Move E2E behavior recipes into named Builder commands; retain concise
root proxies for developer entry points.
- Let Vitest allocate available browser ports instead of maintaining a
cross-package port registry.
- Keep test-suite scheduling explicit in `main.yml`, but move executable
test commands to root package scripts.
- Move fixture-size enforcement from inline GitHub JavaScript into a
locally runnable repository script.

## Todo

- [x] Consolidate Builder backend and Compose ownership.
- [x] Add named development, E2E, schema-cache, Supabase, and CI-shard
commands.
- [x] Remove Compose topology and behavior flags from CI.
- [x] Remove the static Vitest browser-port registry.
- [x] Extract fixture-size checking from the workflow.
- [x] Verify migration bootstrap, snapshot-backed E2E, tests, builds,
types, formatting, lint, generated contracts, and workflow syntax.
- [x] Review documentation impact: updated the E2E developer guide; no
user-facing `docs/` content is affected.

## Verification

- Full package, CLI, Builder core, and Builder app test suites
- Full workspace typecheck
- Builder production build
- Schema-cache generation through the complete migration history
- Supabase migration compatibility
- Snapshot-backed Playwright animation workflow
- Backend regression tests, including demonstrated fail/pass coverage
- All four Compose mode configurations
- `oxfmt`, `oxlint`, package boundaries, generated API, and generated
docs checks
- `actionlint` and Bash syntax checks

The fixture-size checker preserves the existing Linux `du` thresholds.
Its three inputs build locally; macOS allocation sizes are not directly
comparable to the Linux CI thresholds, so CI remains the authoritative
size result.
2026-08-31 21:09:54 +02:00

216 lines
6.2 KiB
Bash

#!/usr/bin/env bash
builder_backend_init() {
local mode="${1:?Builder backend mode is required}"
ROOT_DIR="${ROOT_DIR:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)}"
local backend_dir="$ROOT_DIR/apps/builder/backend"
local compose_override="$backend_dir/compose.${mode}.yaml"
if [ ! -f "$compose_override" ]; then
echo "Unknown Builder backend mode: $mode" >&2
return 1
fi
if [ "$mode" = "development" ]; then
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-builder}"
else
# Disposable modes must never share the persistent development volume.
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-builder-e2e}"
fi
COMPOSE_ENV=(
--env-file "$ROOT_DIR/apps/builder/.env"
)
if [ -f "$ROOT_DIR/apps/builder/.env.development" ]; then
COMPOSE_ENV+=(--env-file "$ROOT_DIR/apps/builder/.env.development")
fi
COMPOSE_FILES=(
-f "$backend_dir/compose.yaml"
-f "$compose_override"
)
}
builder_backend_schema_fingerprint() {
(
cd "$ROOT_DIR"
{
find packages/prisma-client/prisma/migrations \
-mindepth 1 -maxdepth 1 -type d -print
find packages/prisma-client/prisma/migrations \
-type f \( -name migration.sql -o -name migration.ts \) -print
find apps/builder/backend -type f ! -name '*.test.*' -print
} | LC_ALL=C sort | while IFS= read -r file; do
if [ -f "$file" ]; then
printf '%s ' "$file"
git hash-object "$file"
else
printf '%s directory\n' "$file"
fi
done
) | git hash-object --stdin
}
builder_backend_init_schema_snapshot() {
SCHEMA_SNAPSHOT="${SCHEMA_SNAPSHOT:-$ROOT_DIR/.cache/builder-e2e/schema-$(builder_backend_schema_fingerprint).sql}"
}
builder_compose() {
docker compose "${COMPOSE_ENV[@]}" "${COMPOSE_FILES[@]}" "$@"
}
builder_backend_down() {
builder_compose down "$@"
}
builder_backend_pull_db() {
builder_compose pull --policy missing db
}
builder_backend_start_db() {
builder_compose up -d db
}
builder_backend_start_postgrest() {
builder_compose up -d postgrest
}
builder_backend_wait_for_postgrest() {
local timeout_seconds="${1:-60}"
local port
port="$(builder_compose port postgrest 3000)"
local url="http://127.0.0.1:${port##*:}/UserProduct?select=userId&limit=0"
local timeout_at
timeout_at="$(($(date +%s) + timeout_seconds))"
until {
local status
status="$(curl -s -o /dev/null -w "%{http_code}" "$url" || true)"
[ "$status" -ge 200 ] && [ "$status" -lt 300 ]
}; do
if [ "$(date +%s)" -ge "$timeout_at" ]; then
echo "Timed out waiting for PostgREST" >&2
return 1
fi
sleep 0.2
done
}
builder_backend_wait_for_db() {
local timeout_seconds="${1:-60}"
local timeout_at
timeout_at="$(($(date +%s) + timeout_seconds))"
# Connect over TCP, not the unix socket: the postgres image's entrypoint
# runs a temporary socket-only server (listen_addresses='') during init,
# which lets a socket probe pass while host clients (prisma migrate,
# postgrest) still get connection refused.
until builder_compose exec -T db \
sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -q -h 127.0.0.1 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1 -c "SELECT 1"' \
>/dev/null 2>&1; do
if [ "$(date +%s)" -ge "$timeout_at" ]; then
echo "Timed out waiting for database" >&2
return 1
fi
sleep 0.2
done
}
builder_backend_schema_exists() {
[ "$(
builder_compose exec -T db \
sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -q -U "$POSTGRES_USER" -d "$POSTGRES_DB" -tAc "SELECT to_regclass('\''public.\"Project\"'\'') IS NOT NULL"'
)" = "t" ]
}
builder_backend_bootstrap_schema_snapshot() {
if [ ! -f "$SCHEMA_SNAPSHOT" ]; then
return 1
fi
builder_compose exec -T db \
sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" psql -q -U "$POSTGRES_USER" -d "$POSTGRES_DB" -v ON_ERROR_STOP=1' \
<"$SCHEMA_SNAPSHOT" >/dev/null
}
builder_backend_migrate() {
pnpm --dir "$ROOT_DIR" --filter=@webstudio-is/prisma-client migrations migrate --dev --cwd ../../apps/builder
if ! builder_backend_schema_exists; then
echo "Migrations completed without creating the Builder schema" >&2
return 1
fi
}
builder_backend_bootstrap() {
case "${1:-auto}" in
schema)
builder_backend_bootstrap_schema_snapshot
;;
migrations)
builder_backend_migrate
;;
auto)
if [ -f "$SCHEMA_SNAPSHOT" ]; then
builder_backend_bootstrap_schema_snapshot
echo "Bootstrapped database from cached schema $SCHEMA_SNAPSHOT"
else
echo "No cached schema found at $SCHEMA_SNAPSHOT; running migrations"
builder_backend_migrate
builder_backend_write_schema_snapshot
fi
;;
*)
echo "Unknown database bootstrap mode: $1" >&2
exit 1
;;
esac
}
builder_backend_bootstrap_if_empty() {
if builder_backend_schema_exists; then
echo "Database schema already exists; skipping bootstrap"
else
builder_backend_bootstrap auto
fi
}
builder_backend_write_schema_snapshot() {
mkdir -p "$(dirname "$SCHEMA_SNAPSHOT")"
local temporary_snapshot="${SCHEMA_SNAPSHOT}.tmp.$$"
# Dump only the application-owned public schema so the snapshot can be
# restored without replacing database-owned schemas.
if builder_compose exec -T db \
sh -c 'PGPASSWORD="$POSTGRES_PASSWORD" pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --schema=public --schema-only --clean --if-exists --no-owner --no-privileges' \
>"$temporary_snapshot"; then
mv "$temporary_snapshot" "$SCHEMA_SNAPSHOT"
else
rm -f "$temporary_snapshot"
return 1
fi
echo "Wrote $SCHEMA_SNAPSHOT"
}
builder_prisma_client_exists() {
[ -f "$ROOT_DIR/packages/prisma-client/src/__generated__/index.js" ]
}
builder_generate_prisma_client() {
case "${1:-auto}" in
true)
pnpm --dir "$ROOT_DIR" --filter=@webstudio-is/prisma-client generate
;;
false)
echo "Skipping Prisma client generation"
;;
auto)
if builder_prisma_client_exists; then
echo "Skipping Prisma client generation; generated client already exists"
else
pnpm --dir "$ROOT_DIR" --filter=@webstudio-is/prisma-client generate
fi
;;
*)
echo "Unknown Prisma generation mode: $1" >&2
exit 1
;;
esac
}