#!/usr/bin/env bash # ============================================================================= # schema-snapshot.sh — TRM directus schema snapshot (host-side, dev-time) # # Captures the current Directus schema from the running dev compose stack and # writes it to ./snapshots/schema.yaml for git commit. # # Usage # Run from the repo root (where compose.dev.yaml lives): # pnpm run schema:snapshot # Or directly: # bash scripts/schema-snapshot.sh # # Prerequisites # - docker CLI must be on PATH. # - The dev compose stack must be running: # pnpm run dev (or: docker compose -f compose.dev.yaml up -d) # - The directus service must be healthy (bootstrapped, not just started). # # What it does # 1. Verifies docker is on PATH. # 2. Verifies the compose directus service is in a running state. # 3. Runs `directus schema snapshot --yes /tmp/schema-snapshot.yaml` inside # the container (the container already has all required env vars for DB # access). # 4. Copies the generated file out to ./snapshots/schema.yaml. # 5. Prints a one-line success log with the file size. # # Exit codes # 0 Snapshot written successfully. # 1 docker CLI not found, or directus service is not running, or snapshot # command failed inside the container, or copy failed. # # Notes # - The compose service name is `directus`; the compose file is # `compose.dev.yaml`, resolved relative to the working directory. # - Do NOT run this from inside the container — it is a host-side script. # - Do NOT hand-edit snapshots/schema.yaml. Run this script after making # schema changes via the Directus admin UI. # # Wired into package.json as `schema:snapshot`. # ============================================================================= set -euo pipefail # ----------------------------------------------------------------------------- # Logging helpers # ----------------------------------------------------------------------------- log_info() { printf '[schema-snapshot] %s\n' "$*" } log_error() { printf '[schema-snapshot] ERROR: %s\n' "$*" >&2 } # ----------------------------------------------------------------------------- # Configuration # ----------------------------------------------------------------------------- COMPOSE_FILE="compose.dev.yaml" COMPOSE_SERVICE="directus" # Temporary path inside the container — chosen to be writable by the `node` # user that the directus image runs as. CONTAINER_TMP_PATH="/tmp/schema-snapshot.yaml" # Destination on the host (relative to the repo root, where this script runs). HOST_SNAPSHOT_PATH="./snapshots/schema.yaml" # ----------------------------------------------------------------------------- # Step 1 — Verify docker CLI is available # ----------------------------------------------------------------------------- if ! command -v docker > /dev/null 2>&1; then log_error "docker CLI not found on PATH" log_error "Install Docker Desktop or Docker Engine and ensure 'docker' is in your PATH." exit 1 fi log_info "docker CLI found: $(docker --version)" # ----------------------------------------------------------------------------- # Step 2 — Verify the directus compose service is running # ----------------------------------------------------------------------------- log_info "checking compose stack (${COMPOSE_FILE}) for service '${COMPOSE_SERVICE}'" if [[ ! -f "${COMPOSE_FILE}" ]]; then log_error "compose file '${COMPOSE_FILE}' not found." log_error "Run this script from the directus/ repo root (where compose.dev.yaml lives)." exit 1 fi # `docker compose ps --status running --services` lists only services that are # in the running state. We check whether our target service appears in that list. running_services="$(docker compose -f "${COMPOSE_FILE}" ps --status running --services 2>&1)" || { log_error "docker compose ps failed — is Docker running?" log_error "Output: ${running_services}" exit 1 } if ! printf '%s\n' "${running_services}" | grep -qx "${COMPOSE_SERVICE}"; then log_error "Directus container is not running." log_error "Start the stack first: pnpm run dev" log_error "(Services currently running: ${running_services:-})" exit 1 fi log_info "service '${COMPOSE_SERVICE}' is running" # ----------------------------------------------------------------------------- # Step 3 — Run `directus schema snapshot` inside the container # ----------------------------------------------------------------------------- log_info "running 'directus schema snapshot' inside the container..." snapshot_output="" snapshot_exit=0 snapshot_output="$( docker compose -f "${COMPOSE_FILE}" exec -T \ "${COMPOSE_SERVICE}" \ node /directus/cli.js schema snapshot --yes "${CONTAINER_TMP_PATH}" 2>&1 )" || snapshot_exit=$? if [[ "${snapshot_exit}" -ne 0 ]]; then log_error "directus schema snapshot failed (exit ${snapshot_exit})" log_error "Container output:" # Print each line prefixed so it's clearly from the container. while IFS= read -r line; do log_error " > ${line}" done <<< "${snapshot_output}" exit 1 fi # ----------------------------------------------------------------------------- # Step 4 — Copy the snapshot out of the container # ----------------------------------------------------------------------------- log_info "copying snapshot from container to ${HOST_SNAPSHOT_PATH}" copy_exit=0 copy_output="$( docker compose -f "${COMPOSE_FILE}" cp \ "${COMPOSE_SERVICE}:${CONTAINER_TMP_PATH}" \ "${HOST_SNAPSHOT_PATH}" 2>&1 )" || copy_exit=$? if [[ "${copy_exit}" -ne 0 ]]; then log_error "docker compose cp failed (exit ${copy_exit}): ${copy_output}" exit 1 fi # ----------------------------------------------------------------------------- # Step 5 — Report success # ----------------------------------------------------------------------------- # Compute the size of the written file for the one-line success log. if command -v stat > /dev/null 2>&1; then # GNU stat (Linux) and BSD stat (macOS) use different flags; try both. snapshot_bytes="$(stat -c%s "${HOST_SNAPSHOT_PATH}" 2>/dev/null \ || stat -f%z "${HOST_SNAPSHOT_PATH}" 2>/dev/null \ || echo "?")" else snapshot_bytes="?" fi log_info "snapshot written to snapshots/schema.yaml (${snapshot_bytes} bytes)"