EmDash 核心迁移会更新 EmDash 自己的表以及内容表上的标准列。它们不会创建、删除或重命名你的集合与字段;内容模型变更请参见演进已部署站点。
运行时迁移模式默认为 auto,因此现有部署在启动时会继续应用待处理的核心迁移。部署管理的迁移让构建在新应用代码接收流量之前迁移其数据库,然后让运行时验证或信任该部署步骤。
核心迁移仅向前。它们的编写方式允许在确定已完成的语句之后重试命令,但中断的远程命令可能留下模糊结果。安全的做法是用 emdash migrate --status 检查同一数据库,而不是假定整次迁移已运行或完全未运行。
构建、迁移、部署、检查
Astro 构建或同步会写入 .emdash/migrations.json。此无密钥清单记录该构建使用的精确 EmDash 版本、有序迁移集、语言环境配置以及适配器迁移执行器。
从生成该清单的依赖所在项目运行这些命令。先构建并检查目标。
pnpm build
pnpm emdash migrate --status
确认报告的目标是预期数据库后,启动交互式迁移。在提示处再次核对目标后再确认。然后部署同一构建并检查已部署的架构。
pnpm emdash migrate
pnpm wrangler deploy
pnpm emdash migrate --check
emdash migrate --status 报告已应用、待处理与未知迁移,且不更改数据库。普通的 emdash migrate 命令显示目标,并在应用待处理迁移前请求确认。
--check 从不应用迁移,并在已知迁移待处理或数据库包含构建未知的迁移记录时以非零退出。若要检查同一迁移集且不要 check 的非零「需要工作」退出状态,请使用 --status。CLI 参考 区分待处理、未知、确认、中断与操作退出码。
非交互应用与每次 --json 应用都需要 --expected-target-fingerprint;若解析的目标不匹配,命令会失败。在自动化部署作业中使用这些选项,不要用于上面的交互工作流。
对存储在其他位置的清单使用 --manifest path/to/migrations.json。对于本地调查,--from-config [--config astro.config.mjs] 会显式评估受信任的项目配置,而不运行 Astro 钩子或启动服务器。部署流水线应消费构建清单。
显式选择数据库
已配置的适配器向清单贡献无密钥目标信息。凭据保留在环境变量中,仅由迁移命令读取。
| Adapter | Manifest target | Default credential variable | Useful override |
|---|---|---|---|
| SQLite | Database path or file: URL | — | --database <path> |
| libSQL | Public URL | TURSO_AUTH_TOKEN | Configure migrationAuthTokenEnv |
| PostgreSQL | Connection variable name | DATABASE_URL | --database-url-env <name> |
| Cloudflare D1 | Wrangler binding name | CLOUDFLARE_API_TOKEN | --d1, --account-id, --wrangler-config, --wrangler-env |
| Hyperdrive | Primary binding and origin variable name | Binding-specific direct-origin variable | Configure migrationConnectionStringEnv |
相对 SQLite 路径从项目根解析,而不是从已安装的 EmDash 包或 shell 的当前子目录。PostgreSQL、libSQL 与 Hyperdrive 目标标签会省略凭据与 URL 参数。
迁移前预置 D1
创建 D1 数据库与迁移其架构是分开的操作。emdash migrate 从不创建缺失的数据库。
-
预置数据库并记录其生产 UUID。
pnpm wrangler d1 create my-site-production -
将该 UUID 添加到
wrangler.jsonc中预期的绑定与环境。 -
构建站点,使 D1 绑定记录到
.emdash/migrations.json。 -
设置账户 ID 与具有 D1 Edit 权限的限定范围 API 令牌。检查所选目标,然后运行交互式迁移。仅当账户与数据库匹配预期生产数据库时才确认提示。
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
你也可以改为提供 --account-id 与 --d1 <database-uuid-or-name>。名称查找必须解析为恰好一个数据库。预览 ID、占位符 ID、冲突账户与模糊绑定会 fail closed。
在 CI 中配置 D1 迁移
EmDash 在应用迁移时会在 D1 数据库中持有迁移锁,包括 emdash migrate 与 auto 模式下的运行时迁移。在锁被持有时启动的第二次运行最多等待 10 秒。若第一次运行在该时间内结束,第二次运行会成功且不应用任何内容;否则会失败且不应用迁移。对每个账户与数据库 UUID 一次只运行一个迁移作业,使第二个作业在 CI 队列中等待而不是失败。
在 CI 环境中设置以下密钥与变量:
- 密钥
CLOUDFLARE_API_TOKEN:具有 D1 Edit 权限的限定范围令牌。 - 变量
CLOUDFLARE_ACCOUNT_ID:拥有该数据库的 Cloudflare 账户 ID。 - 变量
D1_DATABASE_ID:生产 D1 数据库 UUID。 - 变量
EMDASH_TARGET_FINGERPRINT:在本地核对账户与数据库后由emdash migrate --status打印的指纹。
以下 GitHub Actions 工作流使用这些值,并按两个不可变 D1 标识符设置并发组。其应用步骤是非交互的,因此显式提供已核对的目标指纹。
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 }}"
仅在本地核对已变更目标后才更新 EMDASH_TARGET_FINGERPRINT。指纹不含凭据,但未检查账户与数据库就更改它会移除防止迁移错误数据库的保护。
释放卡住的迁移锁
在释放迁移锁之前停止的 D1 迁移运行会使锁保持持有。当 CI 作业在 emdash migrate 期间被取消、auto 模式下的 Worker 在运行时迁移期间停止,或开发服务器在应用迁移时被停止时会发生这种情况。因迁移错误而失败的运行会释放锁。EmDash 不会释放它未持有的锁,因为持有者可能仍在应用迁移,或可能已在某次迁移中途停止。在锁被释放之前,站点无法应用其待处理迁移,且在 auto 模式下 EmDash 会初始化失败。
锁被持有超过一分钟后,emdash migrate 与运行时迁移会停止等待并报告以下错误:
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
释放远程 D1 数据库的锁使用 emdash migrate,因此需要写入 .emdash/migrations.json 的构建以及具有 D1 Edit 权限的 API 令牌,如迁移前预置 D1所述。
-
确认没有迁移作业、部署或其他
emdash migrate命令正在对该数据库运行。 -
使用迁移所用的相同目标选项检查锁与迁移集。
pnpm emdash migrate --status报告以锁及其 id 开头。若已停止的运行正在应用迁移,那是第一个待处理迁移,且可能已部分应用。
Migration lock: held since 2026-09-01T12:00:00.000Z (id 1788264000000)auto模式下的 Worker 在应用迁移时也可能持有锁。一分钟或更久后再运行该命令,并在已知已应用迁移仍在变化时等待,而不是释放锁。 -
用该 id 释放锁。命令会要求你确认目标,且仅在锁仍具有该 id 时释放。在非交互 shell 中,添加带有
--status打印的目标指纹的--expected-target-fingerprint。pnpm emdash migrate --release-lock 1788264000000 -
再次应用待处理迁移。
pnpm emdash migrate若在第一个待处理迁移中应用失败,将该迁移视为中途留下,并遵循故障排除中关于模糊 D1 写入的条目。
emdash migrate 仅到达远程 D1 数据库。当锁在开发服务器的本地 D1 数据库中被持有时,停止服务器并用 Wrangler 清除锁,将 DB 替换为绑定名称,将数字替换为错误中的锁 id。
pnpm wrangler d1 execute DB --local --command "UPDATE _emdash_migrations_lock SET is_locked = 0 WHERE is_locked = 1788264000000"
Hyperdrive 连接到源
Hyperdrive 的迁移执行器会打开到源的直接 PostgreSQL 连接。它不会通过 Hyperdrive 发送迁移流量,不使用可选的缓存绑定,也不从 Worker 继承私有网络可达性。
部署运行器必须能够到达源。当默认的绑定特定变量不合适时,在 hyperdrive() 上设置 migrationConnectionStringEnv,并将该变量仅提供给迁移作业。将运行时 Hyperdrive 凭据与直接源部署凭据分开。
逐步采用运行时强制
以下 EmDash 集成配置在开发中保留自动迁移的同时启用运行时强制。
emdash({
database,
migrations: {
runtime: "check",
dev: "auto",
},
});
auto是向后兼容的默认值。运行时启动会检查并应用待处理迁移。check执行单向状态查询,并在已知迁移待处理时在提供请求前返回 503。它在滚动部署期间容忍来自较新兼容构建的记录。manual不执行运行时迁移或状态查询。仅在部署流水线可靠地应用并检查每个构建之后使用。
当同一产物通过多个环境提升时,EMDASH_MIGRATIONS_MODE 可以覆盖运行时模式。设置与开发绕过路由遵守有效模式;它们不能在 check 或 manual 背后静默迁移。
保守推出是:引入部署作业时用 auto,作业可靠后用 check,对每次部署强制外部检查时用 manual。
滚动部署期间的兼容性
核心迁移遵循 expand/deploy/contract 顺序。部署可能暂时对扩展后的数据库运行新旧应用隔离区,回填可能仍在进行。在每个已部署版本停止使用某架构之前,不要收缩该架构。
未知的已应用迁移记录仅在此滚动部署方向上被运行时 check 容忍。CLI 的精确检查会报告它们,且 apply 拒绝变更,因为数据库可能更新,或可能有分歧的迁移历史。
回滚边界
部署先前的应用产物不会逆转核心迁移。在应用待处理迁移之前,进行可恢复的数据库备份,并记录与之匹配的应用产物。若先前的应用无法在已迁移架构上运行,请一起恢复迁移前的数据库与应用。不要从 _emdash_migrations 删除行,也不要将迁移的内部 down() 函数作为操作回滚运行。
修复混合的 PostgreSQL 所有权
当现有 PostgreSQL 站点用多个所有者创建了 EmDash 对象,且后续迁移因 must be owner of table 等错误失败时,使用此运行手册。选择主 EmDash 连接将继续使用的规范角色。进行可恢复的数据库备份,并在更改所有权之前停止应用流量与架构更改。
检查活动架构中的每个表:
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;
EmDash 对象包括 _emdash_* 与 _plugin_* 系统表、ec_* 集合表,以及无前缀表如 content_taxonomies、media、options、revisions 与 taxonomies。在专用 EmDash 架构中,每个应用表都应有规范所有者。
EmDash 还会创建媒体使用触发器所用的 PostgreSQL 函数。检查函数所有权,并为修复命令保留每个函数的参数签名:
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;
用可以更改所有权的超级用户或提供商角色转移每个不匹配的对象。使用清单中的真实架构、对象、角色与函数签名,而不是原样复制示例名称:
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;
更改表的所有者也会覆盖其附加的索引、约束与触发器,但不包括独立的触发器函数。重复两个清单查询,直到每个 EmDash 表与函数都报告规范所有者。然后以该角色连接,并在重启流量之前验证 current_database()、current_schema() 与迁移状态。
非超级用户要转移所有权,必须拥有或继承该对象的所有权,能够对新所有者 SET ROLE,且新所有者必须对架构有 CREATE。托管 PostgreSQL 提供商可能要求其管理角色执行转移。
故障排除
- No migration manifest found. Build or sync the project first. Use
--manifestfor a non-standard artifact location or explicitly choose--from-configfor 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 --statusagainst 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.