Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 9 additions & 22 deletions .github/workflows/d1-migrate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,17 @@ jobs:

- name: Confirm no migrations remain pending (audit log)
# wrangler d1 migrations list exits 0 even when migrations remain
# unapplied, so parse --json and fail the job explicitly (issue #3024).
# Verified output schema (wrangler 4.x, 2026-08): array of objects with
# `name`, `created_at`, and `applied_at` (null when pending). The jq
# filter also tolerates `applied`/`AppliedAt` variants defensively, and
# if the schema is completely unknown (no recognized field) it fails
# closed rather than silently passing (issue #3075).
# unapplied, so require the current no-pending sentinel explicitly.
# Wrangler 4.144.0 rejects `--json` for this command, so keep the
# audit on the supported text output and fail closed if the sentinel
# disappears or the list command itself fails.
run: |
set -euo pipefail
npm run db:migrations:list -- --json > pending.json || true
if command -v jq >/dev/null; then
pending_count=$(jq '[.[] | select((.applied_at // .applied // .AppliedAt) == null)] | length' pending.json)
echo "Pending migration count: ${pending_count}"
total_count=$(jq 'length' pending.json)
recognized=$(jq '[.[] | has("applied_at") or has("applied") or has("AppliedAt")] | any' pending.json)
if [ "${recognized}" != "true" ] && [ "${total_count}" -gt 0 ]; then
echo "::error::wrangler d1 migrations list --json output schema is unrecognized. Inspect the output above and update this step."
exit 1
fi
if [ "${pending_count}" -gt 0 ]; then
echo "::error::D1 migrations remain unapplied after apply. See logs above."
exit 1
fi
else
npm run db:migrations:list
output="$(npm run db:migrations:list 2>&1)"
printf '%s\n' "${output}"
if ! grep -Fq "No migrations to apply!" <<<"${output}"; then
echo "::error::D1 migrations may remain unapplied after apply. Inspect the list output above."
exit 1
fi
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,34 @@ describe('D1 migration npm toolchain', () => {
const installStep = steps?.find((step) => step.name === 'Install dependencies');
const listStep = steps?.find((step) => step.name === 'List pending migrations');
const applyStep = steps?.find((step) => step.name === 'Apply migrations');
const auditStep = steps?.find((step) => step.name === 'Confirm no migrations remain pending (audit log)');

expect(pinStep).toBeDefined();
expect(installStep).toBeDefined();
expect(listStep).toBeDefined();
expect(applyStep).toBeDefined();
expect(auditStep).toBeDefined();
expect(pinStep?.run).toContain(`npm install --global --ignore-scripts --no-audit --no-fund ${packageManager}`);

const expectedVersion = packageManager?.replace(/^npm@/, '');
expect(pinStep?.run).toContain(`test "$(npm --version)" = "${expectedVersion}"`);
expect(installStep?.run?.trim()).toBe('npm ci');
expect(installStep?.env?.HUSKY).toBe('0');
expect(auditStep?.run).toContain('npm run db:migrations:list 2>&1');
expect(auditStep?.run).toContain('grep -Fq "No migrations to apply!"');
expect(auditStep?.run).not.toContain('--json');
expect(auditStep?.run).not.toContain('|| true');

const pinIndex = steps?.indexOf(pinStep as WorkflowStep) ?? -1;
const installIndex = steps?.indexOf(installStep as WorkflowStep) ?? -1;
const listIndex = steps?.indexOf(listStep as WorkflowStep) ?? -1;
const applyIndex = steps?.indexOf(applyStep as WorkflowStep) ?? -1;
const auditIndex = steps?.indexOf(auditStep as WorkflowStep) ?? -1;

expect(pinIndex).toBeGreaterThanOrEqual(0);
expect(pinIndex).toBeLessThan(installIndex);
expect(installIndex).toBeLessThan(listIndex);
expect(listIndex).toBeLessThan(applyIndex);
expect(applyIndex).toBeLessThan(auditIndex);
});
});
Loading