源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
@@ -0,0 +1,308 @@
|
||||
name: Den DB Migrate
|
||||
|
||||
# Applies Drizzle migrations to the production PlanetScale database.
|
||||
#
|
||||
# Required GitHub Actions secrets (Settings -> Secrets and variables -> Actions):
|
||||
# DATABASE_HOST PlanetScale host (e.g. aws.connect.psdb.cloud)
|
||||
# DATABASE_USERNAME PlanetScale branch password username
|
||||
# DATABASE_PASSWORD PlanetScale branch password
|
||||
# DATABASE_NAME PlanetScale database name (drizzle-kit migrate connects over the MySQL protocol and needs it)
|
||||
# PLANETSCALE_SERVICE_TOKEN_ID PlanetScale service token id (used to toggle safe migrations)
|
||||
# PLANETSCALE_SERVICE_TOKEN PlanetScale service token secret
|
||||
#
|
||||
# Safe migrations stays enabled on the production branch at all times. This
|
||||
# workflow briefly disables it (direct DDL) while applying reviewed migration
|
||||
# files, then re-enables it in an always() step — so ad-hoc manual DDL stays
|
||||
# blocked outside CI runs.
|
||||
#
|
||||
# Runs automatically when migration files land on dev, and manually via
|
||||
# workflow_dispatch. For a database previously managed with db:push (no
|
||||
# __drizzle_migrations table yet), run once manually with baseline=true
|
||||
# to record existing migrations as applied without executing them.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
paths:
|
||||
- "ee/packages/den-db/drizzle/**"
|
||||
# Also run when this workflow itself changes: a broken migrate run fixed
|
||||
# by a workflow-only PR must re-apply pending migrations on merge instead
|
||||
# of waiting for a manual dispatch (migrate is idempotent via the journal).
|
||||
- ".github/workflows/den-db-migrate.yml"
|
||||
schedule:
|
||||
# Self-heal: if a push-triggered run fails (workflow bug, transient infra),
|
||||
# pending migrations get re-applied on the next tick instead of waiting for
|
||||
# a human. The pending-check below makes idle ticks read-only no-ops.
|
||||
- cron: "23,53 * * * *"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
baseline:
|
||||
description: "One-time: mark existing migrations as applied without executing them (for a db previously managed via db:push)"
|
||||
type: boolean
|
||||
default: false
|
||||
baseline_through:
|
||||
description: "Baseline through this migration tag (default: latest, e.g. 0020_breezy_siren)"
|
||||
type: string
|
||||
required: false
|
||||
dry_run:
|
||||
description: "Print what would happen without applying migrations"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: den-db-migrate
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
migrate:
|
||||
name: Apply migrations
|
||||
if: github.repository == 'different-ai/openwork'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2204
|
||||
|
||||
env:
|
||||
DATABASE_HOST: ${{ secrets.DATABASE_HOST }}
|
||||
DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
|
||||
DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
|
||||
DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
|
||||
PLANETSCALE_SERVICE_TOKEN_ID: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_ID }}
|
||||
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}
|
||||
PLANETSCALE_ORG: ben-prologe
|
||||
PLANETSCALE_BRANCH: main
|
||||
|
||||
steps:
|
||||
- name: Verify database secrets are configured
|
||||
run: |
|
||||
set -euo pipefail
|
||||
missing=""
|
||||
[ -z "$DATABASE_HOST" ] && missing="$missing DATABASE_HOST"
|
||||
[ -z "$DATABASE_USERNAME" ] && missing="$missing DATABASE_USERNAME"
|
||||
[ -z "$DATABASE_PASSWORD" ] && missing="$missing DATABASE_PASSWORD"
|
||||
[ -z "$DATABASE_NAME" ] && missing="$missing DATABASE_NAME"
|
||||
[ -z "$PLANETSCALE_SERVICE_TOKEN_ID" ] && missing="$missing PLANETSCALE_SERVICE_TOKEN_ID"
|
||||
[ -z "$PLANETSCALE_SERVICE_TOKEN" ] && missing="$missing PLANETSCALE_SERVICE_TOKEN"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "::error::Missing GitHub Actions secrets:$missing. Add them under Settings -> Secrets and variables -> Actions."
|
||||
exit 1
|
||||
fi
|
||||
echo "Database secrets present."
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 11.4.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install --filter "@openwork-ee/den-db..."
|
||||
|
||||
# Read-only gate: compare the drizzle journal against __drizzle_migrations.
|
||||
# Scheduled/push runs with nothing pending stop here, so safe migrations
|
||||
# are only ever toggled when there is real DDL to apply. workflow_dispatch
|
||||
# always proceeds (baseline mode needs to run before the table exists).
|
||||
- name: Check for pending migrations
|
||||
id: pending
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pending="$(cd ee/packages/den-db && node --input-type=module -e "
|
||||
const { readFile } = await import('node:fs/promises');
|
||||
const { Client } = await import('@planetscale/database');
|
||||
const journal = JSON.parse(await readFile('drizzle/meta/_journal.json', 'utf8'));
|
||||
const latest = Math.max(...journal.entries.map((entry) => entry.when));
|
||||
const client = new Client({ host: process.env.DATABASE_HOST, username: process.env.DATABASE_USERNAME, password: process.env.DATABASE_PASSWORD });
|
||||
let applied = 0;
|
||||
try {
|
||||
const result = await client.execute('SELECT MAX(created_at) AS latest FROM __drizzle_migrations');
|
||||
applied = Number(result.rows[0]?.latest ?? 0);
|
||||
} catch {
|
||||
applied = 0;
|
||||
}
|
||||
console.log(latest > applied ? 'true' : 'false');
|
||||
")"
|
||||
echo "pending=$pending" >> "$GITHUB_OUTPUT"
|
||||
echo "Pending migrations: $pending"
|
||||
|
||||
- name: Setup pscale CLI
|
||||
if: steps.pending.outputs.pending == 'true' || github.event_name == 'workflow_dispatch'
|
||||
id: setup-pscale
|
||||
uses: planetscale/setup-pscale-action@v1
|
||||
|
||||
- name: Disable safe migrations (allow DDL for this run only)
|
||||
if: steps.pending.outputs.pending == 'true' || github.event_name == 'workflow_dispatch'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pscale_retry() {
|
||||
local attempt err_file error_output output status
|
||||
for attempt in $(seq 1 5); do
|
||||
err_file="$(mktemp)"
|
||||
if output="$("$@" 2>"$err_file")"; then
|
||||
error_output="$(<"$err_file")"
|
||||
rm -f "$err_file"
|
||||
if [ -n "$error_output" ]; then
|
||||
printf '%s\n' "$error_output" >&2
|
||||
fi
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
else
|
||||
status=$?
|
||||
error_output="$(<"$err_file")"
|
||||
rm -f "$err_file"
|
||||
if [ -n "$output" ]; then
|
||||
printf '%s\n' "$output" >&2
|
||||
fi
|
||||
if [ -n "$error_output" ]; then
|
||||
printf '%s\n' "$error_output" >&2
|
||||
fi
|
||||
if [ "$attempt" -lt 5 ]; then
|
||||
echo "pscale command failed (attempt $attempt/5, exit $status); retrying in 10s..." >&2
|
||||
sleep 10
|
||||
fi
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
# Read the resulting state from the toggle's own response instead of a
|
||||
# separate `pscale branch show`: `disable` is idempotent, and it saves a
|
||||
# call that needs different API permissions and a different output shape.
|
||||
if ! state="$(pscale_retry pscale branch safe-migrations disable "$DATABASE_NAME" "$PLANETSCALE_BRANCH" --org "$PLANETSCALE_ORG" --format json)"; then
|
||||
echo "::error::Could not disable safe migrations for $DATABASE_NAME/$PLANETSCALE_BRANCH."
|
||||
exit 1
|
||||
fi
|
||||
if ! jq -e '.safe_migrations == false' >/dev/null <<<"$state"; then
|
||||
echo "::error::PlanetScale did not confirm safe migrations were disabled. Response: $state"
|
||||
exit 1
|
||||
fi
|
||||
echo "Safe migrations disabled for the duration of this run."
|
||||
|
||||
# The toggle propagates to the connection gateways asynchronously and
|
||||
# per edge node, so require several consecutive successful DDL probes
|
||||
# before proceeding (a single success can still hit a stale node).
|
||||
consecutive=0
|
||||
for attempt in $(seq 1 36); do
|
||||
if (cd ee/packages/den-db && node --input-type=module -e "
|
||||
const { Client } = await import('@planetscale/database');
|
||||
const client = new Client({ host: process.env.DATABASE_HOST, username: process.env.DATABASE_USERNAME, password: process.env.DATABASE_PASSWORD });
|
||||
await client.execute('CREATE TABLE IF NOT EXISTS __ddl_probe (id int)');
|
||||
await client.execute('DROP TABLE IF EXISTS __ddl_probe');
|
||||
" 2>/tmp/ddl-probe-err); then
|
||||
consecutive=$((consecutive + 1))
|
||||
echo "DDL probe ok (attempt $attempt, consecutive $consecutive)."
|
||||
if [ "$consecutive" -ge 3 ]; then
|
||||
break
|
||||
fi
|
||||
sleep 5
|
||||
else
|
||||
consecutive=0
|
||||
echo "DDL still blocked (attempt $attempt): $(tail -1 /tmp/ddl-probe-err 2>/dev/null || true)"
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
if [ "$consecutive" -lt 3 ]; then
|
||||
echo "::error::DDL window did not open within the probe budget after disabling safe migrations."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Baseline existing migrations (one-time)
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.baseline
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# Retry only when a stale gateway node still reports DDL as disabled.
|
||||
run_with_ddl_retry() {
|
||||
local attempt output
|
||||
for attempt in $(seq 1 8); do
|
||||
if output="$("$@" 2>&1)"; then
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
printf '%s\n' "$output"
|
||||
if ! printf '%s' "$output" | grep -q "direct DDL is disabled"; then
|
||||
return 1
|
||||
fi
|
||||
echo "DDL window not propagated everywhere yet; retrying (attempt $attempt)..."
|
||||
sleep 15
|
||||
done
|
||||
return 1
|
||||
}
|
||||
extra=""
|
||||
if [ -n "${{ inputs.baseline_through }}" ]; then
|
||||
extra="--through ${{ inputs.baseline_through }}"
|
||||
fi
|
||||
if [ "${{ inputs.dry_run }}" = "true" ]; then
|
||||
run_with_ddl_retry pnpm --filter @openwork-ee/den-db db:baseline -- $extra
|
||||
else
|
||||
run_with_ddl_retry pnpm --filter @openwork-ee/den-db db:baseline -- --yes $extra
|
||||
fi
|
||||
|
||||
- name: Apply migrations
|
||||
if: >-
|
||||
(steps.pending.outputs.pending == 'true' || github.event_name == 'workflow_dispatch') &&
|
||||
(github.event_name != 'workflow_dispatch' || !inputs.dry_run)
|
||||
run: |
|
||||
set -euo pipefail
|
||||
run_with_ddl_retry() {
|
||||
local attempt output
|
||||
for attempt in $(seq 1 8); do
|
||||
if output="$("$@" 2>&1)"; then
|
||||
printf '%s\n' "$output"
|
||||
return 0
|
||||
fi
|
||||
printf '%s\n' "$output"
|
||||
if ! printf '%s' "$output" | grep -q "direct DDL is disabled"; then
|
||||
return 1
|
||||
fi
|
||||
echo "DDL window not propagated everywhere yet; retrying (attempt $attempt)..."
|
||||
sleep 15
|
||||
done
|
||||
return 1
|
||||
}
|
||||
run_with_ddl_retry pnpm --filter @openwork-ee/den-db db:migrate
|
||||
|
||||
- name: Re-enable safe migrations
|
||||
if: always() && steps.setup-pscale.conclusion == 'success'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
confirmed="false"
|
||||
for attempt in $(seq 1 5); do
|
||||
echo "Enabling safe migrations (attempt $attempt/5)."
|
||||
# Same as the disable step: trust the toggle's own JSON response, and
|
||||
# always echo it when it does not confirm, so a future API/CLI change
|
||||
# shows up in the log instead of being swallowed.
|
||||
if output="$(pscale branch safe-migrations enable "$DATABASE_NAME" "$PLANETSCALE_BRANCH" --org "$PLANETSCALE_ORG" --format json 2>&1)"; then
|
||||
if jq -e '.safe_migrations == true' >/dev/null <<<"$output"; then
|
||||
confirmed="true"
|
||||
echo "PlanetScale confirmed safe migrations are enabled."
|
||||
break
|
||||
fi
|
||||
echo "PlanetScale enable response did not confirm safe migrations: $output" >&2
|
||||
else
|
||||
status=$?
|
||||
if [ -n "$output" ]; then
|
||||
printf '%s\n' "$output" >&2
|
||||
fi
|
||||
echo "pscale enable failed (attempt $attempt/5, exit $status)." >&2
|
||||
fi
|
||||
|
||||
if [ "$attempt" -lt 5 ]; then
|
||||
echo "Safe migrations not confirmed enabled yet; retrying in 10s..."
|
||||
sleep 10
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$confirmed" != "true" ]; then
|
||||
echo "::error::Safe migrations is NOT re-enabled on $DATABASE_NAME/$PLANETSCALE_BRANCH. Re-enable it manually: pscale branch safe-migrations enable $DATABASE_NAME $PLANETSCALE_BRANCH --org $PLANETSCALE_ORG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Dry run note
|
||||
if: github.event_name == 'workflow_dispatch' && inputs.dry_run
|
||||
run: echo "Dry run requested -- skipped 'db:migrate'. Baseline output above (if enabled) shows the plan."
|
||||
Reference in New Issue
Block a user