am-supabase-ddl-deploy
Deploy DDL Supabase (CREATE/ALTER FUNCTION, migrations RPC, row-level security) sans jamais demander a l'utilisateur d'executer le SQL dans le Dashboard. Utilise psql avec la connection string Supabase pour un deploy end-to-end autonome. A declencher des qu'une migration Supabase ou un RPC doit etre deploye sur un projet AM, Nelvo ou tout projet utilisant Supabase.
Deploy DDL Supabase autonome
Workflow pour deployer du DDL Supabase (migrations, fonctions RPC, triggers, policies RLS) de facon autonome via psql, sans friction utilisateur.
Core principle : ne JAMAIS demander a l'utilisateur d'executer le SQL dans le Dashboard Supabase. Tout passe par psql avec la connection string.
Announce at start : "Je declenche la skill am-supabase-ddl-deploy pour appliquer le DDL en autonomie."
Prerequis
- psql CLI installe (
brew install libpqsi absent) - Connection string Supabase dans le
.env:DATABASE_URLouSUPABASE_DB_URLouSUPABASE_DB_CONNECTION_STRING
- Project ref Supabase (visible dans l'URL du dashboard)
- SERVICE_ROLE_KEY (pour tester les RPC apres deploy)
Checklist
Step 1 — Localiser la connection string
# Chercher dans le .env du projet
grep -E "DATABASE_URL|SUPABASE_DB_URL|SUPABASE_DB_CONNECTION" .env .env.local 2>/dev/null
Format attendu :
postgresql://postgres.<project-ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres
Si absent :
- Aller sur le Dashboard Supabase > Project Settings > Database > Connection string (URI mode)
- Recuperer la string, l'ajouter au
.envlocal et au.env.example(avec valeur masquee) - Verifier qu'elle est bien dans les secrets Railway/Fly/Vercel selon le hosting
- Si le password manque, le demander a l'utilisateur UNE SEULE FOIS et le stocker dans le scratchpad session
Step 2 — Preparer le fichier de migration
Convention de nommage : supabase/migrations/<timestamp>_<feature>.sql ou migrations/<NNN>-<feature>.sql.
Pattern idempotent obligatoire :
-- migrations/008-rpc-customer-stats.sql
-- Date : 2026-04-14
-- Fonction RPC pour stats client (idempotent via CREATE OR REPLACE)
CREATE OR REPLACE FUNCTION public.get_customer_stats(p_customer_id bigint)
RETURNS TABLE (
total_orders int,
total_spent numeric,
last_order_at timestamptz
)
LANGUAGE sql
SECURITY DEFINER
SET search_path = public
AS $$
SELECT
COUNT(*)::int,
COALESCE(SUM(amount), 0),
MAX(created_at)
FROM orders
WHERE customer_id = p_customer_id;
$$;
-- Grant execution au role anon/authenticated
GRANT EXECUTE ON FUNCTION public.get_customer_stats(bigint) TO anon, authenticated;
-- Policy RLS idempotente
DROP POLICY IF EXISTS "customers_own_stats" ON customers;
CREATE POLICY "customers_own_stats"
ON customers FOR SELECT
USING (auth.uid() = user_id);
Patterns idempotents a privilegier :
CREATE OR REPLACE FUNCTIONCREATE TABLE IF NOT EXISTSDROP POLICY IF EXISTS+CREATE POLICYALTER TABLE ... ADD COLUMN IF NOT EXISTSCREATE INDEX IF NOT EXISTS
Step 3 — Executer via psql
# Charger le .env
set -a && source .env && set +a
# Deploy
psql "$SUPABASE_DB_URL" -f migrations/008-rpc-customer-stats.sql
Verifier la sortie :
CREATE FUNCTIONGRANTDROP POLICY/CREATE POLICY- Pas d'erreur
permission denied→ si oui, verifier que l'URL pointe surpostgres(role admin) et pasanon.
Step 4 — Valider via un appel RPC
Tester immediatement la fonction avec SERVICE_ROLE_KEY :
curl -s -X POST "$SUPABASE_URL/rest/v1/rpc/get_customer_stats" \
-H "apikey: $SUPABASE_SERVICE_ROLE_KEY" \
-H "Authorization: Bearer $SUPABASE_SERVICE_ROLE_KEY" \
-H "Content-Type: application/json" \
-d '{"p_customer_id": 1}' | jq
Ou via le client JS du projet :
const { data, error } = await supabase.rpc('get_customer_stats', { p_customer_id: 1 });
Attendu : JSON avec total_orders, total_spent, last_order_at. Si erreur function does not exist → le DDL n'a pas ete applique, relire la sortie psql.
Step 5 — Commit + push
git add migrations/008-*.sql
git commit -m "supabase(ddl): get_customer_stats RPC + rls policy"
git push origin <branche>
Red Flags
Ne JAMAIS :
- Demander a l'utilisateur "Tu peux executer ce SQL dans Supabase ?" — casse le flow autonome
- Utiliser
supabase db pushsi c'est pour du DDL custom (reserve aux migrations Drizzle/Prisma alignees) - Laisser des
DROP FUNCTIONsansIF EXISTS - Commit les passwords en clair dans le .env.example
- Utiliser
SECURITY DEFINERsans definirSET search_path = public(CVE-2018-1058)
Toujours :
- Patterns idempotents (re-executable sans casser)
- Tester immediatement via RPC apres deploy
- Grant explicite a
anonet/ouauthenticatedpour les fonctions publiques - RLS policies pour tout table accessible via l'API REST Supabase
Integration
Agents concernes :
- SEBASTIEN (backend, creation RPC)
- MORGAN (architecture, design des fonctions)
- CASEY (audit secu des policies RLS et SECURITY DEFINER)
Pairs avec :
am-sql-preprod-deploy(pour les tables postgres classiques, pas Supabase-specific)- Hook
guard.sh(protection contre DROP destructifs)
Reference
- Source :
feedback_supabase_ddl.md - Supabase docs : https://supabase.com/docs/guides/database/functions
- RLS best practices : https://supabase.com/docs/guides/database/postgres/row-level-security
- Security advisor CVE-2018-1058 : toujours
SET search_path = publicdansSECURITY DEFINER