Gestire le migrazioni del database core

In questa pagina

Le migrazioni core di EmDash aggiornano le tabelle proprie di EmDash e le colonne standard sulle tabelle di contenuto. Non creano, rimuovono o rinominano le tue collection e i tuoi campi; vedi Evolvere un sito deployato per le modifiche al modello di contenuto.

La modalità di migrazione runtime predefinita è auto, così i deployment esistenti continuano ad applicare le migrazioni core in sospeso all’avvio. Le migrazioni gestite dal deployment consentono a una build di migrare il proprio database prima che il nuovo codice applicativo riceva traffico, e poi al runtime di verificare o fidarsi di quel passo di deployment.

Le migrazioni core sono solo in avanti. Sono scritte in modo che un comando possa essere ritentato dopo istruzioni che hanno definitivamente completato, ma un comando remoto interrotto può lasciare un risultato ambiguo. La risposta sicura è ispezionare lo stesso database con emdash migrate --status, non presumere che sia stata eseguita l’intera migrazione o nessuna.

Compilare, migrare, deployare, verificare

Una build o sync Astro scrive .emdash/migrations.json. Questo manifesto senza segreti registra la versione esatta di EmDash, l’insieme di migrazioni ordinato, la configurazione di locale e l’esecutore di migrazioni dell’adapter usato da quella build.

Esegui questi comandi dal progetto le cui dipendenze hanno prodotto il manifesto. Prima compila e ispeziona il target.

pnpm build
pnpm emdash migrate --status

Dopo aver confermato che il target segnalato è il database previsto, avvia la migrazione interattiva. Rivedi di nuovo il target al prompt prima di confermare. Poi fai il deploy della stessa build e verifica lo schema deployato.

pnpm emdash migrate
pnpm wrangler deploy
pnpm emdash migrate --check

emdash migrate --status segnala migrazioni applicate, in sospeso e sconosciute senza modificare il database. Il comando semplice emdash migrate mostra il target e chiede conferma prima di applicare le migrazioni in sospeso.

--check non applica mai migrazioni e termina con codice diverso da zero quando sono in sospeso migrazioni note o il database contiene record di migrazione sconosciuti alla build. Usa --status quando vuoi ispezionare gli stessi insiemi di migrazione senza lo stato di uscita diverso da zero «lavoro richiesto» di check. Il riferimento CLI distingue i codici di uscita in sospeso, sconosciuti, di conferma, di interruzione e operativi.

L’applicazione non interattiva e ogni applicazione --json richiedono --expected-target-fingerprint; il comando fallisce se il target risolto non corrisponde. Usa queste opzioni nei job di deployment automatizzati, non per il flusso interattivo sopra.

Usa --manifest path/to/migrations.json per un manifesto memorizzato altrove. Per indagini locali, --from-config [--config astro.config.mjs] valuta esplicitamente la configurazione di progetto attendibile senza eseguire hook Astro né avviare un server. Le pipeline di deployment devono consumare il manifesto della build.

Selezionare il database esplicitamente

L’adapter configurato contribuisce informazioni di target senza segreti al manifesto. Le credenziali restano nelle variabili d’ambiente e sono lette solo dal comando di migrazione.

AdapterManifest targetDefault credential variableUseful override
SQLiteDatabase path or file: URL—--database <path>
libSQLPublic URLTURSO_AUTH_TOKENConfigure migrationAuthTokenEnv
PostgreSQLConnection variable nameDATABASE_URL--database-url-env <name>
Cloudflare D1Wrangler binding nameCLOUDFLARE_API_TOKEN--d1, --account-id, --wrangler-config, --wrangler-env
HyperdrivePrimary binding and origin variable nameBinding-specific direct-origin variableConfigure migrationConnectionStringEnv

I percorsi SQLite relativi si risolvono dalla root del progetto, non dal pacchetto EmDash installato né dalla sottodirectory corrente della shell. Le etichette di target PostgreSQL, libSQL e Hyperdrive omettono credenziali e parametri URL.

Provisionare D1 prima di migrarlo

Creare un database D1 e migrarne lo schema sono operazioni separate. emdash migrate non crea mai un database mancante.

  1. Provisiona il database e registra il suo UUID di produzione.

    pnpm wrangler d1 create my-site-production
  2. Aggiungi quel UUID al binding e all’ambiente previsti in wrangler.jsonc.

  3. Compila il sito così che il binding D1 sia registrato in .emdash/migrations.json.

  4. Imposta l’ID account e un token API con ambito e permesso D1 Edit. Ispeziona il target selezionato, poi esegui la migrazione interattiva. Conferma il prompt solo quando account e database corrispondono al database di produzione previsto.

    export CLOUDFLARE_ACCOUNT_ID="..."
    export CLOUDFLARE_API_TOKEN="..."
    pnpm emdash migrate \
      --status \
      --wrangler-config wrangler.jsonc \
      --wrangler-env production
    pnpm emdash migrate \
      --wrangler-config wrangler.jsonc \
      --wrangler-env production

In alternativa puoi fornire --account-id con --d1 <database-uuid-or-name>. La ricerca per nome deve risolvere esattamente un database. ID di anteprima, ID segnaposto, account in conflitto e binding ambigui falliscono in modo chiuso.

Configurare le migrazioni D1 in CI

EmDash mantiene un lock di migrazione nel database D1 mentre applica le migrazioni, sia da emdash migrate sia dalle migrazioni runtime in modalità auto. Una seconda esecuzione che inizia mentre il lock è tenuto attende fino a 10 secondi. Se la prima esecuzione termina in quel tempo, la seconda riesce senza applicare nulla; altrimenti fallisce senza applicare migrazioni. Esegui un job di migrazione alla volta per account e UUID di database così che un secondo job attenda in coda CI invece di fallire.

Imposta il seguente secret e le seguenti variabili nell’ambiente CI:

  • Secret CLOUDFLARE_API_TOKEN: un token con ambito e permesso D1 Edit.
  • Variabile CLOUDFLARE_ACCOUNT_ID: l’ID account Cloudflare che possiede il database.
  • Variabile D1_DATABASE_ID: l’UUID del database D1 di produzione.
  • Variabile EMDASH_TARGET_FINGERPRINT: l’impronta stampata da emdash migrate --status dopo aver rivisto account e database localmente.

Il seguente workflow GitHub Actions usa quei valori e raggruppa la concurrency per entrambi gli identificatori D1 immutabili. Il suo passo di apply non è interattivo, quindi fornisce esplicitamente l’impronta del target rivista.

name: Deploy

on:
  workflow_dispatch:

concurrency:
  group: emdash-migrations-${{ vars.CLOUDFLARE_ACCOUNT_ID }}-${{ vars.D1_DATABASE_ID }}
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - name: Inspect EmDash migration target
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        run: |
          pnpm emdash migrate --status --json \
            --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
            --d1 "${{ vars.D1_DATABASE_ID }}"
      - name: Apply EmDash migrations
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          EMDASH_TARGET_FINGERPRINT: ${{ vars.EMDASH_TARGET_FINGERPRINT }}
        run: |
          pnpm emdash migrate \
            --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
            --d1 "${{ vars.D1_DATABASE_ID }}" \
            --expected-target-fingerprint "$EMDASH_TARGET_FINGERPRINT"
      - run: pnpm wrangler deploy
      - name: Check EmDash migrations
        env:
          CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
        run: |
          pnpm emdash migrate --check \
            --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \
            --d1 "${{ vars.D1_DATABASE_ID }}"

Aggiorna EMDASH_TARGET_FINGERPRINT solo dopo aver rivisto un target modificato localmente. L’impronta non contiene credenziali, ma cambiarla senza controllare account e database rimuove la protezione contro la migrazione del database sbagliato.

Rilasciare un lock di migrazione bloccato

Un’esecuzione di migrazione D1 che si ferma prima di rilasciare il lock di migrazione lascia il lock tenuto. Succede quando un job CI viene annullato durante emdash migrate, quando un Worker in modalità auto si ferma durante una migrazione runtime, o quando un server di sviluppo viene fermato mentre applica migrazioni. Un’esecuzione che fallisce con un errore di migrazione rilascia il lock. EmDash non rilascia un lock che non detiene, perché il detentore potrebbe ancora applicare migrazioni o essersi fermato a metà di una. Finché il lock non viene rilasciato, il sito non può applicare le sue migrazioni in sospeso e, in modalità auto, EmDash non riesce a inizializzare.

Dopo che il lock è stato tenuto per più di un minuto, emdash migrate e le migrazioni runtime smettono di aspettarlo e segnalano il seguente errore:

The migration lock has been held since 2026-09-01T12:00:00.000Z (lock 1788264000000). A migration may still be running; if none is, check the database and release the lock: https://docs.emdashcms.com/deployment/core-migrations/#release-a-stuck-migration-lock

Rilasciare il lock di un database D1 remoto usa emdash migrate, quindi serve una build che abbia scritto .emdash/migrations.json e un token API con permesso D1 Edit, come descritto in Provisionare D1 prima di migrarlo.

  1. Conferma che nessun job di migrazione, deployment o altro comando emdash migrate sia in esecuzione contro il database.

  2. Ispeziona il lock e gli insiemi di migrazione con le stesse opzioni di target usate dalla migrazione.

    pnpm emdash migrate --status

    Il report inizia con il lock e il suo id. Se l’esecuzione fermata stava applicando una migrazione, quella era la prima migrazione in sospeso e può essere parzialmente applicata.

    Migration lock: held since 2026-09-01T12:00:00.000Z (id 1788264000000)

    Un Worker in modalità auto può anche tenere il lock mentre applica migrazioni. Esegui di nuovo il comando un minuto o più dopo, e aspetta invece di rilasciare il lock mentre le migrazioni applicate note continuano a cambiare.

  3. Rilascia il lock con quell’id. Il comando chiede di confermare il target e rilascia il lock solo mentre il lock ha ancora quell’id. In una shell non interattiva, aggiungi --expected-target-fingerprint con l’impronta del target stampata da --status.

    pnpm emdash migrate --release-lock 1788264000000
  4. Applica di nuovo le migrazioni in sospeso.

    pnpm emdash migrate

    Se l’applicazione fallisce nella prima migrazione in sospeso, tratta quella migrazione come lasciata a metà e segui la voce per una scrittura D1 ambigua in Risoluzione dei problemi.

emdash migrate raggiunge solo database D1 remoti. Quando il lock è tenuto nel database D1 locale di un server di sviluppo, ferma il server e cancella il lock con Wrangler, sostituendo DB con il nome del binding e il numero con l’id del lock dall’errore.

pnpm wrangler d1 execute DB --local --command "UPDATE _emdash_migrations_lock SET is_locked = 0 WHERE is_locked = 1788264000000"

Hyperdrive si connette all’origine

L’esecutore di migrazioni di Hyperdrive apre una connessione PostgreSQL diretta all’origine. Non invia traffico di migrazione tramite Hyperdrive, non usa il binding in cache opzionale e non eredita la raggiungibilità di rete privata dal Worker.

Il runner di deployment deve poter raggiungere l’origine. Imposta migrationConnectionStringEnv su hyperdrive() quando la variabile predefinita specifica del binding non è adatta, e fornisci quella variabile solo al job di migrazione. Mantieni separate le credenziali Hyperdrive runtime e le credenziali di deployment all’origine diretta.

Adottare l’enforcement runtime gradualmente

La seguente configurazione di integrazione EmDash abilita l’enforcement runtime mantenendo le migrazioni automatiche in sviluppo.

emdash({
	database,
	migrations: {
		runtime: "check",
		dev: "auto",
	},
});
  • auto è il valore predefinito retrocompatibile. L’avvio runtime controlla e applica le migrazioni in sospeso.
  • check esegue una query di stato direzionale e restituisce 503 prima di servire una richiesta quando sono in sospeso migrazioni note. Tollera record di una build compatibile più recente durante un deployment graduale.
  • manual non esegue migrazione né query di stato runtime. Usalo solo dopo che la pipeline di deployment applica e verifica ogni build in modo affidabile.

EMDASH_MIGRATIONS_MODE può sovrascrivere la modalità runtime quando lo stesso artefatto è promosso attraverso più ambienti. Le route di setup e bypass di sviluppo obbediscono alla modalità effettiva; non possono migrare in silenzio dietro check o manual.

Un rollout conservativo è auto mentre si introduce il job di deployment, poi check dopo che il job è affidabile, poi manual quando un controllo esterno è imposto per ogni deployment.

Compatibilità durante i rolling deploy

Le migrazioni core seguono la sequenza expand/deploy/contract. Un deployment può eseguire temporaneamente isolati applicativi vecchi e nuovi contro il database espanso, e un backfill può essere ancora in corso. Non contrarre uno schema finché ogni versione deployata ha smesso di usarlo.

I record di migrazione applicati sconosciuti sono tollerati dal check runtime solo per questa direzione di rolling deployment. Il controllo esatto della CLI li segnala e apply rifiuta di mutare, perché il database può essere più recente o avere una cronologia di migrazione divergente.

Limite di rollback

Il deploy dell’artefatto applicativo precedente non inverte una migrazione core. Prima di applicare le migrazioni in sospeso, fai un backup del database ripristinabile e registra l’artefatto applicativo che corrisponde. Se l’applicazione precedente non può eseguire contro lo schema migrato, ripristina insieme il database pre-migrazione e l’applicazione. Non eliminare righe da _emdash_migrations e non eseguire la funzione interna down() di una migrazione come rollback operativo.

Riparare la proprietà PostgreSQL mista

Usa questo runbook quando un sito PostgreSQL esistente ha creato oggetti EmDash con più di un owner e migrazioni successive falliscono con errori come must be owner of table. Scegli il ruolo canonico che la connessione EmDash primaria continuerà a usare. Fai un backup del database ripristinabile e ferma il traffico applicativo e le modifiche allo schema prima di cambiare la proprietà.

Ispeziona ogni tabella nello schema attivo:

SELECT
  n.nspname AS schema_name,
  c.relname AS table_name,
  pg_get_userbyid(c.relowner) AS owner
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE n.nspname = current_schema()
  AND c.relkind IN ('r', 'p')
ORDER BY c.relname;

Gli oggetti EmDash includono tabelle di sistema _emdash_* e _plugin_*, tabelle di collection ec_* e tabelle senza prefisso come content_taxonomies, media, options, revisions e taxonomies. In uno schema EmDash dedicato, ogni tabella applicativa dovrebbe avere l’owner canonico.

EmDash crea anche funzioni PostgreSQL usate dai trigger di utilizzo media. Ispeziona la proprietà delle funzioni e conserva la firma degli argomenti di ciascuna funzione per il comando di riparazione:

SELECT
  n.nspname AS schema_name,
  p.proname AS function_name,
  pg_get_function_identity_arguments(p.oid) AS arguments,
  pg_get_userbyid(p.proowner) AS owner
FROM pg_proc AS p
JOIN pg_namespace AS n ON n.oid = p.pronamespace
WHERE n.nspname = current_schema()
ORDER BY p.proname, arguments;

Trasferisci ogni oggetto non corrispondente con un superuser o un ruolo del provider che possa cambiarne la proprietà. Usa schema, oggetto, ruolo e firma di funzione reali dall’inventario invece di copiare i nomi di esempio invariati:

ALTER TABLE emdash.content_taxonomies OWNER TO emdash_app;
ALTER TABLE emdash.ec_posts OWNER TO emdash_app;
ALTER FUNCTION emdash.emdash_media_usage_capture_work() OWNER TO emdash_app;

Cambiare l’owner di una tabella copre anche i suoi indici, vincoli e trigger allegati, ma non le funzioni di trigger indipendenti. Ripeti entrambe le query di inventario finché ogni tabella e funzione EmDash non riporta l’owner canonico. Poi connettiti come quel ruolo e verifica current_database(), current_schema() e lo stato di migrazione prima di riavviare il traffico.

Affinché un non-superuser trasferisca la proprietà, deve possedere o ereditare la proprietà dell’oggetto, poter fare SET ROLE sul nuovo owner, e il nuovo owner deve avere CREATE sullo schema. I provider PostgreSQL gestiti possono richiedere il loro ruolo amministrativo per eseguire il trasferimento.

Risoluzione dei problemi

  • No migration manifest found. Build or sync the project first. Use --manifest for a non-standard artifact location or explicitly choose --from-config for local investigation.
  • The artifact does not match project EmDash. Rebuild and deploy the application and manifest together. Run the project’s CLI instead of a global installation.
  • The target is missing or ambiguous. Provision it first, then supply an explicit database path, connection-variable name, D1 selector, or selected Wrangler config and environment. EmDash does not guess from unrelated environment variables or bindings.
  • The target fingerprint changed. Stop and review the displayed account, environment, database name, UUID, or path. Update the expected fingerprint only after confirming the intended target.
  • Unknown migration records are present. Do not delete the records or rerun apply. Confirm that the application artifact is the intended version and investigate whether a newer or divergent build migrated the database.
  • A D1 write outcome is ambiguous. Do not replay the migration command. Run emdash migrate --status against the same account and database UUID, inspect the result, and escalate if the migration stopped part-way through.
  • Datetime normalization requires manual review. A legacy datetime falls in a repeated or skipped daylight-saving hour in the site’s configured timezone. The error lists each affected content row or revision. Correct those values with an explicit UTC offset, then retry the migration. The migration preflight does not write any datetime until every stored value can be resolved.
  • Hyperdrive cannot connect. Test reachability from the deployment runner to the PostgreSQL origin and verify the direct-origin variable. Worker-to-Hyperdrive connectivity does not prove the runner can reach the origin.