Gerenciar migrações do banco de dados do núcleo

Nesta página

As migrações do núcleo do EmDash atualizam as tabelas próprias do EmDash e as colunas padrão nas tabelas de conteúdo. Elas não criam, removem nem renomeiam suas coleções e campos; veja Evoluir um site implantado para alterações no modelo de conteúdo.

O modo de migração em tempo de execução tem o padrão auto, de modo que implantações existentes continuam aplicando migrações do núcleo pendentes na inicialização. Migrações gerenciadas pela implantação permitem que um build migre seu banco de dados antes que o novo código da aplicação receba tráfego, e então que o tempo de execução verifique ou confie nesse passo de implantação.

As migrações do núcleo são apenas para frente. São escritas para que um comando possa ser retentado após instruções que definitivamente concluíram, mas um comando remoto interrompido pode deixar um resultado ambíguo. A resposta segura é inspecionar o mesmo banco de dados com emdash migrate --status, não presumir que toda a migração ou nenhuma foi executada.

Compilar, migrar, implantar, verificar

Uma compilação ou sincronização Astro escreve .emdash/migrations.json. Este manifesto sem segredos registra a versão exata do EmDash, o conjunto de migrações ordenado, a configuração de locale e o executor de migração do adaptador usado por essa compilação.

Execute estes comandos a partir do projeto cujas dependências produziram o manifesto. Primeiro compile e inspecione o destino.

pnpm build
pnpm emdash migrate --status

Após confirmar que o destino relatado é o banco de dados pretendido, inicie a migração interativa. Revise o destino novamente no prompt antes de confirmar. Depois implante a mesma compilação e verifique o esquema implantado.

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

emdash migrate --status relata migrações aplicadas, pendentes e desconhecidas sem alterar o banco de dados. O comando simples emdash migrate exibe o destino e pede confirmação antes de aplicar migrações pendentes.

--check nunca aplica migrações e sai com código diferente de zero quando há migrações conhecidas pendentes ou o banco de dados contém registros de migração desconhecidos da compilação. Use --status quando quiser inspecionar os mesmos conjuntos de migração sem o status de saída diferente de zero «trabalho necessário» do check. A referência CLI distingue códigos de saída pendentes, desconhecidos, de confirmação, de interrupção e operacionais.

A aplicação não interativa e toda aplicação --json exigem --expected-target-fingerprint; o comando falha se o destino resolvido não corresponder. Use estas opções em trabalhos de implantação automatizados, não no fluxo interativo acima.

Use --manifest path/to/migrations.json para um manifesto armazenado em outro lugar. Para investigação local, --from-config [--config astro.config.mjs] avalia explicitamente a configuração de projeto confiável sem executar hooks Astro nem iniciar um servidor. Pipelines de implantação devem consumir o manifesto da compilação.

Selecionar o banco de dados explicitamente

O adaptador configurado contribui informações de destino sem segredos para o manifesto. As credenciais permanecem em variáveis de ambiente e são lidas apenas pelo comando de migração.

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

Caminhos SQLite relativos resolvem a partir da raiz do projeto, não do pacote EmDash instalado nem do subdiretório atual do shell. Rótulos de destino PostgreSQL, libSQL e Hyperdrive omitem credenciais e parâmetros de URL.

Provisionar D1 antes de migrá-lo

Criar um banco de dados D1 e migrar seu esquema são operações separadas. emdash migrate nunca cria um banco de dados ausente.

  1. Provisione o banco de dados e registre seu UUID de produção.

    pnpm wrangler d1 create my-site-production
  2. Adicione esse UUID ao binding e ambiente pretendidos em wrangler.jsonc.

  3. Compile o site para que o binding D1 seja registrado em .emdash/migrations.json.

  4. Defina o ID da conta e um token de API com escopo e permissão D1 Edit. Inspecione o destino selecionado e então execute a migração interativa. Confirme o prompt apenas quando a conta e o banco de dados corresponderem ao banco de dados de produção pretendido.

    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

Em vez disso, você pode fornecer --account-id com --d1 <database-uuid-or-name>. A busca por nome deve resolver exatamente um banco de dados. IDs de preview, IDs placeholder, contas em conflito e bindings ambíguos falham de forma fechada.

Configurar migrações D1 no CI

O EmDash mantém um bloqueio de migração no banco de dados D1 enquanto aplica migrações, tanto de emdash migrate quanto de migrações em tempo de execução no modo auto. Uma segunda execução que começa enquanto o bloqueio é mantido espera até 10 segundos. Se a primeira execução terminar nesse tempo, a segunda tem sucesso sem aplicar nada; caso contrário, falha sem aplicar migrações. Execute um trabalho de migração por vez por conta e UUID de banco de dados para que um segundo trabalho espere na fila do CI em vez de falhar.

Defina o seguinte segredo e variáveis no ambiente de CI:

  • Segredo CLOUDFLARE_API_TOKEN: um token com escopo e permissão D1 Edit.
  • Variável CLOUDFLARE_ACCOUNT_ID: o ID da conta Cloudflare que possui o banco de dados.
  • Variável D1_DATABASE_ID: o UUID do banco de dados D1 de produção.
  • Variável EMDASH_TARGET_FINGERPRINT: a impressão impressa por emdash migrate --status após você revisar a conta e o banco de dados localmente.

O seguinte fluxo de trabalho do GitHub Actions usa esses valores e agrupa a concurrency por ambos os identificadores D1 imutáveis. Seu passo de apply é não interativo, então fornece explicitamente a impressão do 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 }}"

Atualize EMDASH_TARGET_FINGERPRINT apenas após revisar um destino alterado localmente. A impressão não contém credencial, mas alterá-la sem verificar a conta e o banco de dados remove a proteção contra migrar o banco de dados errado.

Liberar um bloqueio de migração preso

Uma execução de migração D1 que para antes de liberar o bloqueio de migração deixa o bloqueio mantido. Isso acontece quando um trabalho de CI é cancelado durante emdash migrate, quando um Worker no modo auto para durante uma migração em tempo de execução, ou quando um servidor de desenvolvimento é parado enquanto aplica migrações. Uma execução que falha com um erro de migração libera o bloqueio. O EmDash não libera um bloqueio que não mantém, porque o detentor ainda pode estar aplicando migrações ou pode ter parado no meio de uma. Até o bloqueio ser liberado, o site não pode aplicar suas migrações pendentes e, no modo auto, o EmDash falha ao inicializar.

Depois que o bloqueio foi mantido por mais de um minuto, emdash migrate e migrações em tempo de execução param de esperá-lo e relatam o seguinte erro:

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 o bloqueio de um banco de dados D1 remoto usa emdash migrate, então precisa de uma compilação que escreveu .emdash/migrations.json e um token de API com permissão D1 Edit, conforme descrito em Provisionar D1 antes de migrá-lo.

  1. Confirme que nenhum trabalho de migração, implantação ou outro comando emdash migrate está em execução contra o banco de dados.

  2. Inspecione o bloqueio e os conjuntos de migração com as mesmas opções de destino que a migração usou.

    pnpm emdash migrate --status

    O relatório começa com o bloqueio e seu id. Se a execução parada estava aplicando uma migração, essa foi a primeira migração pendente e pode estar parcialmente aplicada.

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

    Um Worker no modo auto também pode manter o bloqueio enquanto aplica migrações. Execute o comando novamente um minuto ou mais depois, e espere em vez de liberar o bloqueio enquanto as migrações aplicadas conhecidas continuam mudando.

  3. Liberte o bloqueio com esse id. O comando pede para confirmar o destino e só libera o bloqueio enquanto o bloqueio ainda tem esse id. Em um shell não interativo, adicione --expected-target-fingerprint com a impressão do destino que --status imprimiu.

    pnpm emdash migrate --release-lock 1788264000000
  4. Aplique novamente as migrações pendentes.

    pnpm emdash migrate

    Se a aplicação falhar na primeira migração pendente, trate essa migração como deixada pela metade e siga a entrada para uma escrita D1 ambígua em Solução de problemas.

emdash migrate alcança apenas bancos de dados D1 remotos. Quando o bloqueio é mantido no banco de dados D1 local de um servidor de desenvolvimento, pare o servidor e limpe o bloqueio com Wrangler, substituindo DB pelo nome do binding e o número pelo id do bloqueio do erro.

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

Hyperdrive conecta-se à origem

O executor de migração do Hyperdrive abre uma conexão PostgreSQL direta à origem. Não envia tráfego de migração pelo Hyperdrive, não usa o binding em cache opcional e não herda alcance de rede privada do Worker.

O runner de implantação deve conseguir alcançar a origem. Defina migrationConnectionStringEnv em hyperdrive() quando a variável padrão específica do binding for inadequada, e forneça essa variável apenas ao trabalho de migração. Mantenha separadas as credenciais Hyperdrive em tempo de execução e as credenciais de implantação de origem direta.

Adotar a aplicação em tempo de execução gradualmente

A seguinte configuração de integração EmDash habilita a aplicação em tempo de execução mantendo migrações automáticas no desenvolvimento.

emdash({
	database,
	migrations: {
		runtime: "check",
		dev: "auto",
	},
});
  • auto é o padrão compatível com versões anteriores. A inicialização em tempo de execução verifica e aplica migrações pendentes.
  • check realiza uma consulta de status direcional e retorna 503 antes de servir uma solicitação quando há migrações conhecidas pendentes. Tolera registros de uma compilação compatível mais recente durante uma implantação gradual.
  • manual não realiza migração nem consulta de status em tempo de execução. Use-o apenas depois que o pipeline de implantação aplica e verifica cada compilação de forma confiável.

EMDASH_MIGRATIONS_MODE pode substituir o modo em tempo de execução quando o mesmo artefato é promovido por vários ambientes. Rotas de configuração e bypass de desenvolvimento obedecem ao modo efetivo; não podem migrar silenciosamente atrás de check ou manual.

Uma implantação conservadora é auto enquanto se introduz o trabalho de implantação, depois check quando o trabalho é confiável, depois manual quando uma verificação externa é aplicada a cada implantação.

Compatibilidade durante implantações graduais

As migrações do núcleo seguem a sequência expand/deploy/contract. Uma implantação pode executar temporariamente isolados de aplicação antigos e novos contra o banco de dados expandido, e um backfill pode ainda estar em andamento. Não contrate um esquema até que cada versão implantada tenha parado de usá-lo.

Registros de migração aplicados desconhecidos são tolerados pelo check em tempo de execução apenas para essa direção de implantação gradual. A verificação exata da CLI os relata e apply se recusa a mutar, porque o banco de dados pode ser mais recente ou ter um histórico de migração divergente.

Limite de reversão

Implantar o artefato de aplicação anterior não reverte uma migração do núcleo. Antes de aplicar migrações pendentes, faça um backup restaurável do banco de dados e registre o artefato de aplicação que corresponde. Se a aplicação anterior não puder executar contra o esquema migrado, restaure juntos o banco de dados pré-migração e a aplicação. Não exclua linhas de _emdash_migrations nem execute a função interna down() de uma migração como reversão operacional.

Reparar propriedade PostgreSQL mista

Use este runbook quando um site PostgreSQL existente criou objetos EmDash com mais de um proprietário e migrações posteriores falham com erros como must be owner of table. Escolha o papel canônico que a conexão primária do EmDash continuará usando. Faça um backup restaurável do banco de dados e pare o tráfego da aplicação e alterações de esquema antes de mudar a propriedade.

Inspecione cada tabela no esquema ativo:

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;

Objetos EmDash incluem tabelas de sistema _emdash_* e _plugin_*, tabelas de coleção ec_* e tabelas sem prefixo como content_taxonomies, media, options, revisions e taxonomies. Em um esquema EmDash dedicado, cada tabela de aplicação deve ter o proprietário canônico.

O EmDash também cria funções PostgreSQL usadas por gatilhos de uso de mídia. Inspecione a propriedade das funções e retenha a assinatura de argumentos de cada função para o comando de reparo:

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;

Transfira cada objeto incompatível com um superusuário ou papel do provedor que possa alterar sua propriedade. Use o esquema, objeto, papel e assinatura de função reais do inventário em vez de copiar os nomes de exemplo inalterados:

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;

Alterar o proprietário de uma tabela também cobre seus índices, restrições e gatilhos anexados, mas não funções de gatilho independentes. Repita ambas as consultas de inventário até que cada tabela e função EmDash relate o proprietário canônico. Depois conecte-se como esse papel e verifique current_database(), current_schema() e o status de migração antes de reiniciar o tráfego.

Para um não superusuário transferir propriedade, ele deve possuir ou herdar a propriedade do objeto, poder SET ROLE para o novo proprietário, e o novo proprietário deve ter CREATE no esquema. Provedores PostgreSQL gerenciados podem exigir seu papel administrativo para realizar a transferência.

Solução 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.