Gestionar migraciones de base de datos del núcleo

En esta página

Las migraciones del núcleo de EmDash actualizan las tablas propias de EmDash y las columnas estándar de las tablas de contenido. No crean, eliminan ni renombran tus colecciones y campos; consulta Evolucionar un sitio desplegado para cambios del modelo de contenido.

El modo de migración en tiempo de ejecución por defecto es auto, de modo que los despliegues existentes siguen aplicando migraciones del núcleo pendientes al arrancar. Las migraciones gestionadas por el despliegue permiten que un build migre su base de datos antes de que el código nuevo de la aplicación reciba tráfico, y luego que el tiempo de ejecución verifique o confíe en ese paso de despliegue.

Las migraciones del núcleo son solo hacia adelante. Están escritas para que un comando pueda reintentarse tras sentencias que definitivamente completaron, pero un comando remoto interrumpido puede dejar un resultado ambiguo. La respuesta segura es inspeccionar la misma base de datos con emdash migrate --status, no asumir que se ejecutó toda la migración o ninguna.

Compilar, migrar, desplegar, comprobar

Una compilación o sincronización de Astro escribe .emdash/migrations.json. Este manifiesto sin secretos registra la versión exacta de EmDash, el conjunto de migraciones ordenado, la configuración de locale y el ejecutor de migraciones del adaptador usado por esa compilación.

Ejecuta estos comandos desde el proyecto cuyas dependencias produjeron el manifiesto. Primero compila e inspecciona el destino.

pnpm build
pnpm emdash migrate --status

Tras confirmar que el destino informado es la base de datos prevista, inicia la migración interactiva. Revisa el destino de nuevo en el prompt antes de confirmar. Luego despliega la misma compilación y comprueba el esquema desplegado.

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

emdash migrate --status informa migraciones aplicadas, pendientes y desconocidas sin cambiar la base de datos. El comando simple emdash migrate muestra el destino y pide confirmación antes de aplicar migraciones pendientes.

--check nunca aplica migraciones y sale con código distinto de cero cuando hay migraciones conocidas pendientes o la base de datos contiene registros de migración desconocidos para la compilación. Usa --status cuando quieras inspeccionar los mismos conjuntos de migración sin el estado de salida distinto de cero «trabajo requerido» de check. La referencia CLI distingue códigos de salida pendientes, desconocidos, de confirmación, de interrupción y operativos.

La aplicación no interactiva y cada aplicación --json requieren --expected-target-fingerprint; el comando falla si el destino resuelto no coincide. Usa estas opciones en trabajos de despliegue automatizados, no para el flujo interactivo anterior.

Usa --manifest path/to/migrations.json para un manifiesto almacenado en otro lugar. Para investigación local, --from-config [--config astro.config.mjs] evalúa explícitamente la configuración de proyecto de confianza sin ejecutar hooks de Astro ni iniciar un servidor. Las canalizaciones de despliegue deben consumir el manifiesto de la compilación.

Seleccionar la base de datos explícitamente

El adaptador configurado aporta información de destino sin secretos al manifiesto. Las credenciales permanecen en variables de entorno y solo las lee el comando de migración.

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

Las rutas relativas de SQLite se resuelven desde la raíz del proyecto, no desde el paquete EmDash instalado ni desde el subdirectorio actual del shell. Las etiquetas de destino de PostgreSQL, libSQL e Hyperdrive omiten credenciales y parámetros de URL.

Aprovisionar D1 antes de migrarlo

Crear una base de datos D1 y migrar su esquema son operaciones separadas. emdash migrate nunca crea una base de datos ausente.

  1. Aprovisiona la base de datos y registra su UUID de producción.

    pnpm wrangler d1 create my-site-production
  2. Añade ese UUID al binding y entorno previstos en wrangler.jsonc.

  3. Compila el sitio para que el binding D1 quede registrado en .emdash/migrations.json.

  4. Establece el ID de cuenta y un token de API con alcance y permiso D1 Edit. Inspecciona el destino seleccionado y luego ejecuta la migración interactiva. Confirma el prompt solo cuando la cuenta y la base de datos coincidan con la base de datos de producción prevista.

    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

En su lugar puedes proporcionar --account-id con --d1 <database-uuid-or-name>. La búsqueda por nombre debe resolverse a exactamente una base de datos. Los ID de vista previa, ID de marcador de posición, cuentas en conflicto y bindings ambiguos fallan de forma cerrada.

Configurar migraciones D1 en CI

EmDash mantiene un bloqueo de migración en la base de datos D1 mientras aplica migraciones, tanto desde emdash migrate como desde migraciones en tiempo de ejecución en modo auto. Una segunda ejecución que comienza mientras el bloqueo está retenido espera hasta 10 segundos. Si la primera ejecución termina en ese tiempo, la segunda tiene éxito sin aplicar nada; de lo contrario, falla sin aplicar migraciones. Ejecuta un trabajo de migración a la vez por cuenta y UUID de base de datos para que un segundo trabajo espere en la cola de CI en lugar de fallar.

Establece el siguiente secreto y variables en el entorno de CI:

  • Secreto CLOUDFLARE_API_TOKEN: un token con alcance y permiso D1 Edit.
  • Variable CLOUDFLARE_ACCOUNT_ID: el ID de cuenta de Cloudflare que posee la base de datos.
  • Variable D1_DATABASE_ID: el UUID de la base de datos D1 de producción.
  • Variable EMDASH_TARGET_FINGERPRINT: la huella impresa por emdash migrate --status después de revisar la cuenta y la base de datos localmente.

El siguiente flujo de trabajo de GitHub Actions usa esos valores y agrupa la concurrencia por ambos identificadores D1 inmutables. Su paso de aplicación no es interactivo, por lo que suministra explícitamente la huella del destino revisada.

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 }}"

Actualiza EMDASH_TARGET_FINGERPRINT solo después de revisar un destino cambiado localmente. La huella no contiene credenciales, pero cambiarla sin comprobar la cuenta y la base de datos elimina la protección contra migrar la base de datos incorrecta.

Liberar un bloqueo de migración atascado

Una ejecución de migración D1 que se detiene antes de liberar el bloqueo de migración deja el bloqueo retenido. Esto ocurre cuando se cancela un trabajo de CI durante emdash migrate, cuando un Worker en modo auto se detiene durante una migración en tiempo de ejecución, o cuando se detiene un servidor de desarrollo mientras aplica migraciones. Una ejecución que falla con un error de migración libera el bloqueo. EmDash no libera un bloqueo que no posee, porque el poseedor puede seguir aplicando migraciones o haberse detenido a mitad de una. Hasta que se libere el bloqueo, el sitio no puede aplicar sus migraciones pendientes y, en modo auto, EmDash no inicializa.

Después de que el bloqueo haya estado retenido más de un minuto, emdash migrate y las migraciones en tiempo de ejecución dejan de esperarlo e informan el siguiente error:

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

Liberar el bloqueo de una base de datos D1 remota usa emdash migrate, así que necesita una compilación que haya escrito .emdash/migrations.json y un token de API con permiso D1 Edit, como se describe en Aprovisionar D1 antes de migrarlo.

  1. Confirma que ningún trabajo de migración, despliegue u otro comando emdash migrate se está ejecutando contra la base de datos.

  2. Inspecciona el bloqueo y los conjuntos de migración con las mismas opciones de destino que usó la migración.

    pnpm emdash migrate --status

    El informe comienza con el bloqueo y su id. Si la ejecución detenida estaba aplicando una migración, esa fue la primera migración pendiente y puede estar parcialmente aplicada.

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

    Un Worker en modo auto también puede retener el bloqueo mientras aplica migraciones. Ejecuta el comando de nuevo un minuto o más después, y espera en lugar de liberar el bloqueo mientras las migraciones aplicadas conocidas siguen cambiando.

  3. Libera el bloqueo con ese id. El comando te pide confirmar el destino y solo libera el bloqueo mientras el bloqueo aún tiene ese id. En un shell no interactivo, añade --expected-target-fingerprint con la huella del destino que imprimió --status.

    pnpm emdash migrate --release-lock 1788264000000
  4. Aplica de nuevo las migraciones pendientes.

    pnpm emdash migrate

    Si la aplicación falla en la primera migración pendiente, trata esa migración como dejada a medias y sigue la entrada para una escritura D1 ambigua en Solución de problemas.

emdash migrate solo alcanza bases de datos D1 remotas. Cuando el bloqueo está retenido en la base de datos D1 local de un servidor de desarrollo, detén el servidor y limpia el bloqueo con Wrangler, reemplazando DB por el nombre del binding y el número por el id del bloqueo del error.

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

Hyperdrive se conecta al origen

El ejecutor de migraciones de Hyperdrive abre una conexión PostgreSQL directa al origen. No envía tráfico de migración a través de Hyperdrive, no usa el binding en caché opcional ni hereda la alcanzabilidad de red privada del Worker.

El runner de despliegue debe poder alcanzar el origen. Establece migrationConnectionStringEnv en hyperdrive() cuando la variable predeterminada específica del binding no sea adecuada, y proporciona esa variable solo al trabajo de migración. Mantén separadas las credenciales de Hyperdrive en tiempo de ejecución y las credenciales de despliegue de origen directo.

Adoptar la aplicación en tiempo de ejecución gradualmente

La siguiente configuración de integración de EmDash habilita la aplicación en tiempo de ejecución y conserva las migraciones automáticas en desarrollo.

emdash({
	database,
	migrations: {
		runtime: "check",
		dev: "auto",
	},
});
  • auto es el valor predeterminado compatible hacia atrás. El arranque en tiempo de ejecución comprueba y aplica migraciones pendientes.
  • check realiza una consulta de estado direccional y devuelve 503 antes de servir una solicitud cuando hay migraciones conocidas pendientes. Tolera registros de una compilación compatible más reciente durante un despliegue gradual.
  • manual no realiza migración ni consulta de estado en tiempo de ejecución. Úsalo solo después de que la canalización de despliegue aplique y compruebe cada compilación de forma fiable.

EMDASH_MIGRATIONS_MODE puede anular el modo en tiempo de ejecución cuando el mismo artefacto se promociona por varios entornos. Las rutas de configuración y bypass de desarrollo obedecen el modo efectivo; no pueden migrar en silencio detrás de check o manual.

Una implantación conservadora es auto mientras se introduce el trabajo de despliegue, luego check cuando el trabajo es fiable, luego manual cuando se aplica una comprobación externa a cada despliegue.

Compatibilidad durante despliegues graduales

Las migraciones del núcleo siguen la secuencia expandir/desplegar/contraer. Un despliegue puede ejecutar temporalmente aislados de aplicación antiguos y nuevos contra la base de datos expandida, y un relleno puede seguir en curso. No contraigas un esquema hasta que cada versión desplegada haya dejado de usarlo.

Los registros de migración aplicados desconocidos son tolerados por el check en tiempo de ejecución solo para esta dirección de despliegue gradual. La comprobación exacta de la CLI los informa y apply se niega a mutar, porque la base de datos puede ser más reciente o tener un historial de migración divergente.

Límite de reversión

Desplegar el artefacto de aplicación anterior no revierte una migración del núcleo. Antes de aplicar migraciones pendientes, haz una copia de seguridad restaurable de la base de datos y registra el artefacto de aplicación que coincida. Si la aplicación anterior no puede ejecutarse contra el esquema migrado, restaura juntos la base de datos previa a la migración y la aplicación. No elimines filas de _emdash_migrations ni ejecutes la función interna down() de una migración como reversión operativa.

Reparar propiedad mixta de PostgreSQL

Usa este runbook cuando un sitio PostgreSQL existente haya creado objetos EmDash con más de un propietario y migraciones posteriores fallen con errores como must be owner of table. Elige el rol canónico que la conexión primaria de EmDash seguirá usando. Haz una copia de seguridad restaurable de la base de datos y detén el tráfico de la aplicación y los cambios de esquema antes de cambiar la propiedad.

Inspecciona cada tabla en el esquema activo:

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;

Los objetos EmDash incluyen tablas del sistema _emdash_* y _plugin_*, tablas de colección ec_* y tablas sin prefijo como content_taxonomies, media, options, revisions y taxonomies. En un esquema EmDash dedicado, cada tabla de aplicación debe tener el propietario canónico.

EmDash también crea funciones PostgreSQL usadas por disparadores de uso de medios. Inspecciona la propiedad de las funciones y conserva la firma de argumentos de cada función para el comando de reparación:

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;

Transfiere cada objeto no coincidente con un superusuario o un rol del proveedor que pueda cambiar su propiedad. Usa el esquema, objeto, rol y firma de función reales del inventario en lugar de copiar los nombres de ejemplo sin cambios:

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;

Cambiar el propietario de una tabla también cubre sus índices, restricciones y disparadores adjuntos, pero no las funciones de disparador independientes. Repite ambas consultas de inventario hasta que cada tabla y función EmDash informe el propietario canónico. Luego conéctate como ese rol y verifica current_database(), current_schema() y el estado de migración antes de reiniciar el tráfico.

Para que un no superusuario transfiera la propiedad, debe poseer o heredar la propiedad del objeto, poder hacer SET ROLE al nuevo propietario, y el nuevo propietario debe tener CREATE en el esquema. Los proveedores de PostgreSQL gestionados pueden exigir su rol administrativo para realizar la transferencia.

Solución de problemas

  • 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.