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 el esquema de un sitio desplegado para los cambios del modelo de contenido.
El modo de migración en tiempo de ejecución es auto por defecto, así que los despliegues existentes siguen aplicando al arrancar las migraciones pendientes del núcleo. Las migraciones gestionadas por el despliegue permiten que una compilación migre su base de datos antes de que el nuevo código de la aplicación reciba tráfico, y que después el tiempo de ejecución verifique ese paso del despliegue o confíe en él.
Las migraciones del núcleo solo avanzan. Están escritas para que un comando pueda reintentarse después de sentencias que sin duda se completaron, pero un comando remoto interrumpido puede dejar un resultado ambiguo. La respuesta segura es inspeccionar esa misma base de datos con emdash migrate --status, en lugar de suponer que se ejecutó la migración completa o que no se ejecutó nada.
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 ordenado de migraciones, la configuración de locales y el ejecutor de migraciones del adaptador que usó 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
Después de confirmar que el destino indicado es la base de datos prevista, inicia la migración interactiva. Revisa el destino de nuevo en el aviso 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 de las migraciones aplicadas, pendientes y desconocidas sin modificar la base de datos. El comando emdash migrate a secas muestra el destino y pide confirmación antes de aplicar las migraciones pendientes.
--check nunca aplica migraciones y termina con un código distinto de cero cuando hay migraciones conocidas pendientes o cuando la base de datos contiene registros de migración que la compilación desconoce. Usa --status cuando quieras inspeccionar los mismos conjuntos de migraciones sin el estado de salida distinto de cero de check, que indica “hay trabajo pendiente”. La referencia de la CLI distingue los códigos de salida de pendiente, desconocido, confirmación, interrupción y error operativo.
La aplicación no interactiva y toda aplicación con --json requieren --expected-target-fingerprint; el comando falla si el destino resuelto no coincide. Usa estas opciones en trabajos de despliegue automatizados, no en el flujo interactivo anterior.
Usa --manifest path/to/migrations.json para un manifiesto almacenado en otro lugar. Para investigaciones locales, --from-config [--config astro.config.mjs] evalúa de forma explícita la configuración de un proyecto de confianza, sin ejecutar los hooks de Astro ni iniciar un servidor. Los pipelines de despliegue deben consumir el manifiesto de la compilación.
Seleccionar la base de datos explícitamente
El adaptador configurado aporta al manifiesto información del destino sin secretos. Las credenciales permanecen en variables de entorno y solo las lee el comando de migración.
| Adaptador | Destino del manifiesto | Variable de credencial predeterminada | Anulación útil |
|---|---|---|---|
| SQLite | Ruta de la base de datos o URL file: | — | --database <path> |
| libSQL | URL pública | TURSO_AUTH_TOKEN | Configura migrationAuthTokenEnv |
| PostgreSQL | Nombre de la variable de conexión | DATABASE_URL | --database-url-env <name> |
| Cloudflare D1 | Nombre del binding de Wrangler | CLOUDFLARE_API_TOKEN | --d1, --account-id, --wrangler-config, --wrangler-env |
| Hyperdrive | Binding principal y nombre de la variable del origen | Variable de origen directo específica del binding | Configura migrationConnectionStringEnv |
Las rutas relativas de SQLite se resuelven desde la raíz del proyecto, no desde el paquete de EmDash instalado ni desde el subdirectorio actual del shell. Las etiquetas de destino de PostgreSQL, libSQL y Hyperdrive omiten las credenciales y los parámetros de la URL.
Aprovisionar D1 antes de migrarlo
Crear una base de datos D1 y migrar su esquema son operaciones distintas. emdash migrate nunca crea una base de datos que falte.
-
Aprovisiona la base de datos y anota su UUID de producción.
pnpm wrangler d1 create my-site-production -
Añade ese UUID al binding y al entorno previstos en
wrangler.jsonc. -
Compila el sitio para que el binding de D1 quede registrado en
.emdash/migrations.json. -
Define el ID de la cuenta y un token de API de alcance limitado con permiso D1 Edit. Inspecciona el destino seleccionado y luego ejecuta la migración interactiva. Confirma el aviso 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
También puedes proporcionar --account-id junto con --d1 <database-uuid-or-name>. La búsqueda por nombre debe resolverse a exactamente una base de datos. Los ID de vista previa, los ID de marcador de posición, las cuentas en conflicto y los bindings ambiguos fallan de forma segura (fail closed).
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 las migraciones en tiempo de ejecución en modo auto. Una segunda ejecución que empiece 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; en caso contrario, falla sin aplicar migraciones. Ejecuta un solo 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.
Define el siguiente secreto y las siguientes variables en el entorno de CI:
- Secreto
CLOUDFLARE_API_TOKEN: un token de alcance limitado con permiso D1 Edit. - Variable
CLOUDFLARE_ACCOUNT_ID: el ID de la cuenta de Cloudflare propietaria de 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 que imprimeemdash migrate --statusdespués de que hayas revisado localmente la cuenta y la base de datos.
El siguiente flujo de trabajo de GitHub Actions usa esos valores y define la clave del grupo de concurrencia con los dos identificadores inmutables de D1. Su paso de aplicación no es interactivo, así que proporciona 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 localmente un destino modificado. La huella no contiene ninguna credencial, pero cambiarla sin comprobar la cuenta y la base de datos elimina la salvaguarda contra migrar la base de datos equivocada.
Liberar un bloqueo de migración atascado
Una ejecución de migración de 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 retiene, porque el titular puede seguir aplicando migraciones o puede 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 consigue inicializarse.
Cuando el bloqueo lleva retenido más de un minuto, emdash migrate y las migraciones en tiempo de ejecución dejan de esperarlo e informan del 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.
-
Confirma que ningún trabajo de migración, despliegue ni otro comando
emdash migratese está ejecutando contra la base de datos. -
Inspecciona el bloqueo y los conjuntos de migraciones con las mismas opciones de destino que usó la migración.
pnpm emdash migrate --statusEl informe empieza 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 aplicada parcialmente.
Migration lock: held since 2026-09-01T12:00:00.000Z (id 1788264000000)Un Worker en modo
autotambié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 sigan cambiando. -
Libera el bloqueo con ese id. El comando te pide que confirmes el destino y solo libera el bloqueo mientras este siga teniendo ese id. En un shell no interactivo, añade
--expected-target-fingerprintcon la huella del destino que imprimió--status.pnpm emdash migrate --release-lock 1788264000000 -
Vuelve a aplicar las migraciones pendientes.
pnpm emdash migrateSi la aplicación falla en la primera migración pendiente, trata esa migración como dejada a medias y sigue la entrada sobre una escritura ambigua en D1 en Solución de problemas.
emdash migrate solo llega a 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 borra el bloqueo con Wrangler, sustituyendo DB por el nombre del binding y el número por el id del bloqueo que aparece en el 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 directa de PostgreSQL con el origen. No envía el tráfico de migración a través de Hyperdrive, no usa el binding de caché opcional y no hereda la accesibilidad de red privada del Worker.
El ejecutor del despliegue debe poder alcanzar el origen. Define 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 gradualmente la exigencia en tiempo de ejecución
La siguiente configuración de la integración de EmDash habilita la exigencia en tiempo de ejecución y conserva las migraciones automáticas en desarrollo.
emdash({
database,
migrations: {
runtime: "check",
dev: "auto",
},
});
autoes el valor predeterminado retrocompatible. El arranque en tiempo de ejecución comprueba y aplica las migraciones pendientes.checkrealiza una consulta de estado direccional y devuelve 503 antes de atender una solicitud cuando hay migraciones conocidas pendientes. Tolera los registros de una compilación compatible más reciente durante un despliegue gradual. Las solicitudes al endpoint de imágenes que instala EmDash se siguen atendiendo, porque omiten el arranque en tiempo de ejecución que ejecuta esta comprobación.manualno realiza ninguna migración ni consulta de estado en tiempo de ejecución. Úsalo solo después de que el pipeline 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 promueve por varios entornos. Las rutas de omisión de configuración y desarrollo respetan el modo efectivo; no pueden migrar en silencio por detrás de check o manual.
Una introducción prudente es auto mientras introduces el trabajo de despliegue, después check cuando el trabajo sea fiable y por último manual cuando se imponga una comprobación externa en cada despliegue.
Compatibilidad durante despliegues graduales
Las migraciones del núcleo siguen la secuencia expandir/desplegar/contraer (expand/deploy/contract). Un despliegue puede ejecutar temporalmente aislados (isolates) antiguos y nuevos de la aplicación contra la base de datos expandida, y puede que un relleno (backfill) siga en curso. No contraigas un esquema hasta que todas las versiones desplegadas hayan dejado de usarlo.
Los registros de migraciones aplicadas desconocidos se toleran en check en tiempo de ejecución solo para esta dirección del despliegue gradual. La comprobación exacta de la CLI los informa y la aplicación se niega a modificar nada, porque la base de datos puede ser más reciente o puede tener un historial de migraciones divergente.
Límite de reversión
Desplegar el artefacto anterior de la aplicación no revierte una migración del núcleo. Antes de aplicar las migraciones pendientes, haz una copia de seguridad restaurable de la base de datos y anota el artefacto de la aplicación que le corresponde. Si la aplicación anterior no puede ejecutarse contra el esquema migrado, restaura juntas 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 procedimiento cuando un sitio PostgreSQL existente haya creado objetos de EmDash con más de un propietario y las migraciones posteriores fallen con errores como must be owner of table. Elige el rol canónico que la conexión principal 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 del 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 de EmDash incluyen las tablas de sistema _emdash_* y _plugin_*, las tablas de colección ec_* y tablas sin prefijo como content_taxonomies, media, options, revisions y taxonomies. En un esquema de EmDash dedicado, todas las tablas de la aplicación deberían tener el propietario canónico.
EmDash también crea funciones de PostgreSQL que usan los triggers 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 con propietario distinto usando un superusuario o un rol del proveedor que pueda cambiar su propiedad. Usa el esquema, el objeto, el rol y la firma de función reales del inventario en lugar de copiar sin cambios los nombres del ejemplo:
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 abarca sus índices, restricciones y triggers asociados, pero no las funciones de trigger independientes. Repite ambas consultas de inventario hasta que cada tabla y función de EmDash indique el propietario canónico. Después conéctate con ese rol y verifica current_database(), current_schema() y el estado de las migraciones antes de reanudar el tráfico.
Para que un usuario que no es superusuario transfiera la propiedad, debe ser propietario del objeto o heredar su propiedad, poder hacer SET ROLE al nuevo propietario, y el nuevo propietario debe tener CREATE en el esquema. Los proveedores de PostgreSQL gestionado pueden exigir que la transferencia la realice su rol administrativo.
Solución de problemas
- No se encontró ningún manifiesto de migración. Compila o sincroniza primero el proyecto. Usa
--manifestpara una ubicación de artefacto no estándar o elige explícitamente--from-configpara una investigación local. - El artefacto no coincide con el EmDash del proyecto. Vuelve a compilar y desplegar juntos la aplicación y el manifiesto. Ejecuta la CLI del proyecto en lugar de una instalación global.
- El destino falta o es ambiguo. Aprovisiónalo primero y después proporciona una ruta de base de datos explícita, el nombre de la variable de conexión, un selector de D1 o la configuración de Wrangler y el entorno seleccionados. EmDash no adivina a partir de variables de entorno o bindings no relacionados.
- La huella del destino cambió. Detente y revisa la cuenta, el entorno, el nombre de la base de datos, el UUID o la ruta que se muestran. Actualiza la huella esperada solo después de confirmar el destino previsto.
- Hay registros de migración desconocidos. No elimines los registros ni vuelvas a ejecutar la aplicación. Confirma que el artefacto de la aplicación es la versión prevista e investiga si una compilación más reciente o divergente migró la base de datos.
- El resultado de una escritura en D1 es ambiguo. No vuelvas a ejecutar el comando de migración. Ejecuta
emdash migrate --statuscontra la misma cuenta y el mismo UUID de base de datos, inspecciona el resultado y escala el problema si la migración se detuvo a medias. - La normalización de fecha y hora requiere revisión manual. Una fecha y hora heredada cae en una hora repetida u omitida por el horario de verano en la zona horaria configurada del sitio. El error enumera cada fila de contenido o revisión afectada. Corrige esos valores con un desfase UTC explícito y vuelve a intentar la migración. La comprobación previa de la migración no escribe ninguna fecha y hora hasta que todos los valores almacenados puedan resolverse.
- Hyperdrive no puede conectarse. Prueba la accesibilidad desde el ejecutor del despliegue hasta el origen de PostgreSQL y verifica la variable de origen directo. La conectividad entre el Worker y Hyperdrive no demuestra que el ejecutor pueda alcanzar el origen.