Merge pull request 'dev → stage: wire Supabase migrations into CI/CD' (#94) from dev into stage
Merge PR #94 from dev into stage
This commit was merged in pull request #94.
This commit is contained in:
@@ -10,6 +10,7 @@ on:
|
||||
- 'src/**'
|
||||
- 'dist/**'
|
||||
- 'runtime/**'
|
||||
- 'migrations/**'
|
||||
- 'Dockerfile.stage'
|
||||
- 'build-stage.sh'
|
||||
- '.gitea/workflows/deploy.yaml'
|
||||
@@ -80,6 +81,66 @@ jobs:
|
||||
with:
|
||||
project_id: neuron-785695
|
||||
|
||||
- name: Run database migrations
|
||||
# Applies any pending migrations in migrations/*.sql to the Supabase DB.
|
||||
# Runs unconditionally (asset-only or full build) so the schema is always
|
||||
# current before the new code is deployed.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 - << 'PYEOF'
|
||||
import json, glob, subprocess, sys, os
|
||||
|
||||
def gcloud_secret(name):
|
||||
return subprocess.check_output([
|
||||
'gcloud', 'secrets', 'versions', 'access', 'latest',
|
||||
f'--secret={name}', '--project=neuron-785695'
|
||||
], text=True).strip()
|
||||
|
||||
access_token = gcloud_secret('supabase-access-token')
|
||||
project_id = 'ocojsghaonltunidkzpw'
|
||||
api_url = f'https://api.supabase.com/v1/projects/{project_id}/database/query'
|
||||
|
||||
def query(sql):
|
||||
r = subprocess.run([
|
||||
'curl', '-sf', '-X', 'POST', api_url,
|
||||
'-H', f'Authorization: Bearer {access_token}',
|
||||
'-H', 'Content-Type: application/json',
|
||||
'-d', json.dumps({'query': sql})
|
||||
], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(f'curl failed: {r.stderr}')
|
||||
resp = json.loads(r.stdout)
|
||||
if isinstance(resp, dict) and resp.get('message') and not isinstance(resp.get('message'), list):
|
||||
raise RuntimeError(f'DB error: {resp}')
|
||||
return resp
|
||||
|
||||
query("""
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
id text PRIMARY KEY,
|
||||
applied_at timestamptz DEFAULT now()
|
||||
)
|
||||
""")
|
||||
|
||||
applied = {row['id'] for row in query('SELECT id FROM schema_migrations')}
|
||||
print(f'Already applied: {sorted(applied)}')
|
||||
|
||||
pending = [p for p in sorted(glob.glob('migrations/*.sql'))
|
||||
if os.path.basename(p) not in applied]
|
||||
|
||||
if not pending:
|
||||
print('No pending migrations.')
|
||||
sys.exit(0)
|
||||
|
||||
for path in pending:
|
||||
name = os.path.basename(path)
|
||||
print(f'Applying {name}...')
|
||||
query(open(path).read())
|
||||
query(f"INSERT INTO schema_migrations (id) VALUES ('{name}')")
|
||||
print(f'Applied {name}')
|
||||
|
||||
print(f'Done. Applied {len(pending)} migration(s).')
|
||||
PYEOF
|
||||
|
||||
- name: Configure docker auth for Artifact Registry
|
||||
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ on:
|
||||
- 'dist/**'
|
||||
- 'runtime/**'
|
||||
- 'tests/**'
|
||||
- 'migrations/**'
|
||||
- 'playwright.config.ts'
|
||||
- 'package.json'
|
||||
- 'Dockerfile.stage'
|
||||
@@ -97,6 +98,66 @@ jobs:
|
||||
with:
|
||||
project_id: neuron-785695
|
||||
|
||||
- name: Run database migrations
|
||||
# Applies any pending migrations in migrations/*.sql to the Supabase DB.
|
||||
# Runs unconditionally (asset-only or full build) so the schema is always
|
||||
# current before the new code is deployed.
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python3 - << 'PYEOF'
|
||||
import json, glob, subprocess, sys, os
|
||||
|
||||
def gcloud_secret(name):
|
||||
return subprocess.check_output([
|
||||
'gcloud', 'secrets', 'versions', 'access', 'latest',
|
||||
f'--secret={name}', '--project=neuron-785695'
|
||||
], text=True).strip()
|
||||
|
||||
access_token = gcloud_secret('supabase-access-token')
|
||||
project_id = 'ocojsghaonltunidkzpw'
|
||||
api_url = f'https://api.supabase.com/v1/projects/{project_id}/database/query'
|
||||
|
||||
def query(sql):
|
||||
r = subprocess.run([
|
||||
'curl', '-sf', '-X', 'POST', api_url,
|
||||
'-H', f'Authorization: Bearer {access_token}',
|
||||
'-H', 'Content-Type: application/json',
|
||||
'-d', json.dumps({'query': sql})
|
||||
], capture_output=True, text=True)
|
||||
if r.returncode != 0:
|
||||
raise RuntimeError(f'curl failed: {r.stderr}')
|
||||
resp = json.loads(r.stdout)
|
||||
if isinstance(resp, dict) and resp.get('message') and not isinstance(resp.get('message'), list):
|
||||
raise RuntimeError(f'DB error: {resp}')
|
||||
return resp
|
||||
|
||||
query("""
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
id text PRIMARY KEY,
|
||||
applied_at timestamptz DEFAULT now()
|
||||
)
|
||||
""")
|
||||
|
||||
applied = {row['id'] for row in query('SELECT id FROM schema_migrations')}
|
||||
print(f'Already applied: {sorted(applied)}')
|
||||
|
||||
pending = [p for p in sorted(glob.glob('migrations/*.sql'))
|
||||
if os.path.basename(p) not in applied]
|
||||
|
||||
if not pending:
|
||||
print('No pending migrations.')
|
||||
sys.exit(0)
|
||||
|
||||
for path in pending:
|
||||
name = os.path.basename(path)
|
||||
print(f'Applying {name}...')
|
||||
query(open(path).read())
|
||||
query(f"INSERT INTO schema_migrations (id) VALUES ('{name}')")
|
||||
print(f'Applied {name}')
|
||||
|
||||
print(f'Done. Applied {len(pending)} migration(s).')
|
||||
PYEOF
|
||||
|
||||
- name: Configure docker auth for Artifact Registry
|
||||
run: gcloud auth configure-docker us-central1-docker.pkg.dev --quiet
|
||||
|
||||
|
||||
Reference in New Issue
Block a user