From 22fb43801d26c34898b467a4d63cb88f6634aae7 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Aug 2026 11:06:34 +0100 Subject: [PATCH 1/7] fix: ignore generated migration manifests in templates --- .changeset/deployment-migration-manifest.md | 2 +- templates/blank/.gitignore | 1 + templates/blog-cloudflare/.gitignore | 1 + templates/blog/.gitignore | 1 + templates/marketing-cloudflare/.gitignore | 1 + templates/marketing/.gitignore | 1 + templates/portfolio-cloudflare/.gitignore | 1 + templates/portfolio/.gitignore | 1 + templates/starter-cloudflare/.gitignore | 1 + templates/starter/.gitignore | 1 + 10 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.changeset/deployment-migration-manifest.md b/.changeset/deployment-migration-manifest.md index 5c8efe6b24..80fd1e4d6f 100644 --- a/.changeset/deployment-migration-manifest.md +++ b/.changeset/deployment-migration-manifest.md @@ -2,4 +2,4 @@ "emdash": minor --- -Adds a validated, secret-free deployment migration manifest during Astro build and sync so deployment tooling can run the exact migrations bundled with the site. +Adds deployment-managed core migrations. Astro builds emit a validated, secret-free `.emdash/migrations.json` manifest for running the build's exact migration set before deployment, and EmDash templates ignore the generated file. diff --git a/templates/blank/.gitignore b/templates/blank/.gitignore index 9c5f20046e..2729298c39 100644 --- a/templates/blank/.gitignore +++ b/templates/blank/.gitignore @@ -1,6 +1,7 @@ node_modules dist .astro +.emdash/migrations.json uploads data.db diff --git a/templates/blog-cloudflare/.gitignore b/templates/blog-cloudflare/.gitignore index 973291296b..f507a16c03 100644 --- a/templates/blog-cloudflare/.gitignore +++ b/templates/blog-cloudflare/.gitignore @@ -1,6 +1,7 @@ node_modules dist .astro +.emdash/migrations.json uploads data.db diff --git a/templates/blog/.gitignore b/templates/blog/.gitignore index 9c5f20046e..2729298c39 100644 --- a/templates/blog/.gitignore +++ b/templates/blog/.gitignore @@ -1,6 +1,7 @@ node_modules dist .astro +.emdash/migrations.json uploads data.db diff --git a/templates/marketing-cloudflare/.gitignore b/templates/marketing-cloudflare/.gitignore index 1cc5052a7b..b34b359b8a 100644 --- a/templates/marketing-cloudflare/.gitignore +++ b/templates/marketing-cloudflare/.gitignore @@ -6,6 +6,7 @@ dist/ # Astro .astro/ +.emdash/migrations.json # Data data.db diff --git a/templates/marketing/.gitignore b/templates/marketing/.gitignore index a955e19866..9d2ca4d137 100644 --- a/templates/marketing/.gitignore +++ b/templates/marketing/.gitignore @@ -6,6 +6,7 @@ dist/ # Astro .astro/ +.emdash/migrations.json # Data data.db diff --git a/templates/portfolio-cloudflare/.gitignore b/templates/portfolio-cloudflare/.gitignore index 221e2b04c9..807fd45acf 100644 --- a/templates/portfolio-cloudflare/.gitignore +++ b/templates/portfolio-cloudflare/.gitignore @@ -6,6 +6,7 @@ node_modules/ # astro .astro/ +.emdash/migrations.json # local data data.db diff --git a/templates/portfolio/.gitignore b/templates/portfolio/.gitignore index 3cc2890368..80f3d68b47 100644 --- a/templates/portfolio/.gitignore +++ b/templates/portfolio/.gitignore @@ -6,6 +6,7 @@ node_modules/ # astro .astro/ +.emdash/migrations.json # local data data.db diff --git a/templates/starter-cloudflare/.gitignore b/templates/starter-cloudflare/.gitignore index 973291296b..f507a16c03 100644 --- a/templates/starter-cloudflare/.gitignore +++ b/templates/starter-cloudflare/.gitignore @@ -1,6 +1,7 @@ node_modules dist .astro +.emdash/migrations.json uploads data.db diff --git a/templates/starter/.gitignore b/templates/starter/.gitignore index 9c5f20046e..2729298c39 100644 --- a/templates/starter/.gitignore +++ b/templates/starter/.gitignore @@ -1,6 +1,7 @@ node_modules dist .astro +.emdash/migrations.json uploads data.db From eb26bf81ed64188ed005f9d3c5d23650251e8e74 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Aug 2026 11:10:27 +0100 Subject: [PATCH 2/7] docs: ignore generated migration manifests --- docs/src/content/docs/deployment/core-migrations.mdx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/src/content/docs/deployment/core-migrations.mdx b/docs/src/content/docs/deployment/core-migrations.mdx index 8d31ae4fb4..df373c5474 100644 --- a/docs/src/content/docs/deployment/core-migrations.mdx +++ b/docs/src/content/docs/deployment/core-migrations.mdx @@ -13,6 +13,12 @@ Runtime migration mode defaults to `auto`, so existing deployments keep applying An Astro build or sync writes `.emdash/migrations.json`. This secret-free manifest records the exact EmDash version, ordered migration set, locale configuration, and adapter migration executor used by that build. +Ignore the generated manifest in source control. Deployment jobs consume it from the build workspace: + +```text title=".gitignore" +.emdash/migrations.json +``` + Run these commands from the project whose dependencies produced the manifest. First build and inspect the target. ```bash From 1cdae5c652c5d1d559dbe8c12fa1f7375b698a7e Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Aug 2026 11:23:15 +0100 Subject: [PATCH 3/7] docs: clarify managed migration release note --- .changeset/deployment-migration-manifest.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.changeset/deployment-migration-manifest.md b/.changeset/deployment-migration-manifest.md index 80fd1e4d6f..7bc3bde3ff 100644 --- a/.changeset/deployment-migration-manifest.md +++ b/.changeset/deployment-migration-manifest.md @@ -2,4 +2,6 @@ "emdash": minor --- -Adds deployment-managed core migrations. Astro builds emit a validated, secret-free `.emdash/migrations.json` manifest for running the build's exact migration set before deployment, and EmDash templates ignore the generated file. +Adds deployment-managed core migrations, allowing a deployment pipeline to apply the build's exact database migrations before new application code receives traffic. Astro builds write `.emdash/migrations.json`, which `emdash migrate` uses to inspect the target, apply pending migrations, and verify the deployed schema. Existing sites keep automatic runtime migrations by default. + +Sites created from EmDash templates ignore generated migration manifests. Existing sites should add `.emdash/migrations.json` to `.gitignore`. From 71e0bf58c61d7b88b652eef9c778dd0ffc863e7e Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Aug 2026 11:27:51 +0100 Subject: [PATCH 4/7] docs: expand managed migration release note --- .changeset/deployment-migration-manifest.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.changeset/deployment-migration-manifest.md b/.changeset/deployment-migration-manifest.md index 7bc3bde3ff..eb4a4f81f7 100644 --- a/.changeset/deployment-migration-manifest.md +++ b/.changeset/deployment-migration-manifest.md @@ -2,6 +2,8 @@ "emdash": minor --- -Adds deployment-managed core migrations, allowing a deployment pipeline to apply the build's exact database migrations before new application code receives traffic. Astro builds write `.emdash/migrations.json`, which `emdash migrate` uses to inspect the target, apply pending migrations, and verify the deployed schema. Existing sites keep automatic runtime migrations by default. +Adds deployment-managed core migrations, giving production deployments an explicit build, migrate, deploy, and verify workflow. Each Astro build writes a validated, secret-free manifest containing its exact EmDash version, ordered migration set, locale configuration, and database adapter. `emdash migrate` resolves and fingerprints the target, reports pending or unknown migrations, applies only the build's known migration set, and checks the deployed schema. -Sites created from EmDash templates ignore generated migration manifests. Existing sites should add `.emdash/migrations.json` to `.gitignore`. +SQLite, libSQL, PostgreSQL, D1, and Hyperdrive deployments are supported. Runtime `auto`, `check`, and `manual` modes let sites adopt the workflow gradually; existing sites continue applying migrations automatically by default. + +Builds write the manifest to `.emdash/migrations.json`. EmDash templates ignore this generated file, and existing sites should add it to `.gitignore`. From 631f57d15fd2a970a6999bc2e5319ace914aa1da Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Aug 2026 13:07:05 +0100 Subject: [PATCH 5/7] docs: make migration release note actionable --- .changeset/deployment-migration-manifest.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.changeset/deployment-migration-manifest.md b/.changeset/deployment-migration-manifest.md index eb4a4f81f7..4ca04b223d 100644 --- a/.changeset/deployment-migration-manifest.md +++ b/.changeset/deployment-migration-manifest.md @@ -2,8 +2,8 @@ "emdash": minor --- -Adds deployment-managed core migrations, giving production deployments an explicit build, migrate, deploy, and verify workflow. Each Astro build writes a validated, secret-free manifest containing its exact EmDash version, ordered migration set, locale configuration, and database adapter. `emdash migrate` resolves and fingerprints the target, reports pending or unknown migrations, applies only the build's known migration set, and checks the deployed schema. +Adds deployment-managed core migrations so production databases can be updated before a new version of the site starts serving traffic. -SQLite, libSQL, PostgreSQL, D1, and Hyperdrive deployments are supported. Runtime `auto`, `check`, and `manual` modes let sites adopt the workflow gradually; existing sites continue applying migrations automatically by default. +To adopt the workflow, build the site, inspect and apply its migrations with `emdash migrate`, deploy that same build, then run `emdash migrate --check` to verify the database. Existing sites continue applying migrations automatically by default; switch the runtime to `check` only after the deployment migration job is reliable. -Builds write the manifest to `.emdash/migrations.json`. EmDash templates ignore this generated file, and existing sites should add it to `.gitignore`. +Add `.emdash/migrations.json` to `.gitignore`, then follow [Manage Core Database Migrations](https://docs.emdashcms.com/deployment/core-migrations/) for credentials, target confirmation, CI serialization, and rollout guidance. From 5b3411202c448ebf8a85d640d602f89cceac89cb Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Aug 2026 13:11:44 +0100 Subject: [PATCH 6/7] docs: make migration workflow human-first --- .changeset/deployment-migration-manifest.md | 2 +- .../docs/deployment/core-migrations.mdx | 44 ++++++++++--------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/.changeset/deployment-migration-manifest.md b/.changeset/deployment-migration-manifest.md index 4ca04b223d..b13bf5dd38 100644 --- a/.changeset/deployment-migration-manifest.md +++ b/.changeset/deployment-migration-manifest.md @@ -6,4 +6,4 @@ Adds deployment-managed core migrations so production databases can be updated b To adopt the workflow, build the site, inspect and apply its migrations with `emdash migrate`, deploy that same build, then run `emdash migrate --check` to verify the database. Existing sites continue applying migrations automatically by default; switch the runtime to `check` only after the deployment migration job is reliable. -Add `.emdash/migrations.json` to `.gitignore`, then follow [Manage Core Database Migrations](https://docs.emdashcms.com/deployment/core-migrations/) for credentials, target confirmation, CI serialization, and rollout guidance. +Follow [Manage Core Database Migrations](https://docs.emdashcms.com/deployment/core-migrations/) for setup, credentials, target confirmation, CI configuration, and rollout guidance. diff --git a/docs/src/content/docs/deployment/core-migrations.mdx b/docs/src/content/docs/deployment/core-migrations.mdx index df373c5474..4fe1c8c1e1 100644 --- a/docs/src/content/docs/deployment/core-migrations.mdx +++ b/docs/src/content/docs/deployment/core-migrations.mdx @@ -13,34 +13,30 @@ Runtime migration mode defaults to `auto`, so existing deployments keep applying An Astro build or sync writes `.emdash/migrations.json`. This secret-free manifest records the exact EmDash version, ordered migration set, locale configuration, and adapter migration executor used by that build. -Ignore the generated manifest in source control. Deployment jobs consume it from the build workspace: - -```text title=".gitignore" -.emdash/migrations.json -``` + Run these commands from the project whose dependencies produced the manifest. First build and inspect the target. ```bash pnpm build -pnpm exec emdash migrate --status --json +pnpm exec emdash migrate --status ``` -After reviewing the reported target and recording its fingerprint, apply the migrations, deploy, and check the deployed schema. +After confirming that the reported target is the intended database, start the interactive migration. Review the target again at the prompt before confirming. Then deploy the same build and check the deployed schema. ```bash -pnpm exec emdash migrate --expected-target-fingerprint "$EMDASH_TARGET_FINGERPRINT" +pnpm exec emdash migrate pnpm exec wrangler deploy pnpm exec emdash migrate --check ``` -`emdash migrate` prints the immutable target before issuing SQL. An interactive, human-readable apply asks for confirmation. Non-interactive apply and every `--json` apply require `--expected-target-fingerprint`; the command fails if it does not match the resolved target. +`emdash migrate --status` reports applied, pending, and unknown migrations without changing the database. The plain `emdash migrate` command displays the target and asks for confirmation before applying pending migrations. -`--check` never applies migrations and exits non-zero when known migrations are pending or the database contains migration records unknown to the build. The [CLI reference](/reference/cli/#emdash-migrate) distinguishes pending, unknown, confirmation, interruption, and operational exit codes. The following command inspects all sets without using check's non-zero "work required" exit status. +`--check` never applies migrations and exits non-zero when known migrations are pending or the database contains migration records unknown to the build. Use `--status` when you want to inspect the same migration sets without check's non-zero "work required" exit status. The [CLI reference](/reference/cli/#emdash-migrate) distinguishes pending, unknown, confirmation, interruption, and operational exit codes. -```bash -pnpm exec emdash migrate --status --json -``` +Non-interactive apply and every `--json` apply require `--expected-target-fingerprint`; the command fails if the resolved target does not match. Use these options in automated deployment jobs, not for the interactive workflow above. Use `--manifest path/to/migrations.json` for a manifest stored elsewhere. For local investigation, `--from-config [--config astro.config.mjs]` explicitly evaluates trusted project configuration without running Astro hooks or starting a server. Deployment pipelines should consume the build manifest. @@ -81,28 +77,36 @@ Creating a D1 database and migrating its schema are separate operations. `emdash 3. Build the site so the D1 binding is recorded in `.emdash/migrations.json`. -4. Set the account ID and a scoped API token with D1 Edit permission. Inspect the selected target, record its fingerprint, and then migrate it. +4. Set the account ID and a scoped API token with D1 Edit permission. Inspect the selected target, then run the interactive migration. Confirm the prompt only when the account and database match the intended production database. ```bash export CLOUDFLARE_ACCOUNT_ID="..." export CLOUDFLARE_API_TOKEN="..." pnpm exec emdash migrate \ - --status --json \ + --status \ --wrangler-config wrangler.jsonc \ --wrangler-env production pnpm exec emdash migrate \ --wrangler-config wrangler.jsonc \ - --wrangler-env production \ - --expected-target-fingerprint "$EMDASH_TARGET_FINGERPRINT" + --wrangler-env production ``` You can instead provide `--account-id` with `--d1 `. Name lookup must resolve to exactly one database. Preview IDs, placeholder IDs, conflicting accounts, and ambiguous bindings fail closed. -## Serialize D1 migration jobs +## Configure D1 migrations in CI + +D1 does not provide the advisory migration lock used by PostgreSQL. Run at most one migration job for an account and database UUID. + +Set the following secret and variables in the CI environment: + +- Secret `CLOUDFLARE_API_TOKEN`: a scoped token with D1 Edit permission. +- Variable `CLOUDFLARE_ACCOUNT_ID`: the Cloudflare account ID that owns the database. +- Variable `D1_DATABASE_ID`: the production D1 database UUID. +- Variable `EMDASH_TARGET_FINGERPRINT`: the fingerprint printed by `emdash migrate --status` after you have reviewed the account and database locally. -D1 does not provide the advisory migration lock used by PostgreSQL. Run at most one migration job for an account and database UUID. The following GitHub Actions workflow keys the concurrency group by both immutable identifiers. +The following GitHub Actions workflow uses those values and keys the concurrency group by both immutable D1 identifiers. Its apply step is non-interactive, so it supplies the reviewed target fingerprint explicitly. ```yaml title=".github/workflows/deploy.yml" name: Deploy @@ -152,7 +156,7 @@ jobs: --d1 "${{ vars.D1_DATABASE_ID }}" ``` -Store the target fingerprint only after reviewing the target printed by `--status`. The fingerprint contains no credential, but it is an important deployment guard against migrating the wrong database. +Update `EMDASH_TARGET_FINGERPRINT` only after reviewing a changed target locally. The fingerprint contains no credential, but changing it without checking the account and database removes the guard against migrating the wrong database. ## Hyperdrive connects to the origin From 8c77fed6578291922fba3492208a92b4e60ae722 Mon Sep 17 00:00:00 2001 From: Matt Kane Date: Wed, 19 Aug 2026 13:13:23 +0100 Subject: [PATCH 7/7] docs: simplify migration commands --- .../docs/deployment/core-migrations.mdx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/src/content/docs/deployment/core-migrations.mdx b/docs/src/content/docs/deployment/core-migrations.mdx index 4fe1c8c1e1..e9b8531d19 100644 --- a/docs/src/content/docs/deployment/core-migrations.mdx +++ b/docs/src/content/docs/deployment/core-migrations.mdx @@ -21,15 +21,15 @@ Run these commands from the project whose dependencies produced the manifest. Fi ```bash pnpm build -pnpm exec emdash migrate --status +pnpm emdash migrate --status ``` After confirming that the reported target is the intended database, start the interactive migration. Review the target again at the prompt before confirming. Then deploy the same build and check the deployed schema. ```bash -pnpm exec emdash migrate -pnpm exec wrangler deploy -pnpm exec emdash migrate --check +pnpm emdash migrate +pnpm wrangler deploy +pnpm emdash migrate --check ``` `emdash migrate --status` reports applied, pending, and unknown migrations without changing the database. The plain `emdash migrate` command displays the target and asks for confirmation before applying pending migrations. @@ -70,7 +70,7 @@ Creating a D1 database and migrating its schema are separate operations. `emdash 1. Provision the database and record its production UUID. ```bash - pnpm exec wrangler d1 create my-site-production + pnpm wrangler d1 create my-site-production ``` 2. Add that UUID to the intended binding and environment in `wrangler.jsonc`. @@ -82,11 +82,11 @@ Creating a D1 database and migrating its schema are separate operations. `emdash ```bash export CLOUDFLARE_ACCOUNT_ID="..." export CLOUDFLARE_API_TOKEN="..." - pnpm exec emdash migrate \ + pnpm emdash migrate \ --status \ --wrangler-config wrangler.jsonc \ --wrangler-env production - pnpm exec emdash migrate \ + pnpm emdash migrate \ --wrangler-config wrangler.jsonc \ --wrangler-env production ``` @@ -134,7 +134,7 @@ jobs: env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} run: | - pnpm exec emdash migrate --status --json \ + pnpm emdash migrate --status --json \ --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \ --d1 "${{ vars.D1_DATABASE_ID }}" - name: Apply EmDash migrations @@ -142,16 +142,16 @@ jobs: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} EMDASH_TARGET_FINGERPRINT: ${{ vars.EMDASH_TARGET_FINGERPRINT }} run: | - pnpm exec emdash migrate \ + pnpm emdash migrate \ --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \ --d1 "${{ vars.D1_DATABASE_ID }}" \ --expected-target-fingerprint "$EMDASH_TARGET_FINGERPRINT" - - run: pnpm exec wrangler deploy + - run: pnpm wrangler deploy - name: Check EmDash migrations env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} run: | - pnpm exec emdash migrate --check \ + pnpm emdash migrate --check \ --account-id "${{ vars.CLOUDFLARE_ACCOUNT_ID }}" \ --d1 "${{ vars.D1_DATABASE_ID }}" ```