Skip to content

ci(den-db): automated Drizzle migrations for PlanetScale#2152

Open
benjaminshafii wants to merge 2 commits into
devfrom
ci/den-db-auto-migrations
Open

ci(den-db): automated Drizzle migrations for PlanetScale#2152
benjaminshafii wants to merge 2 commits into
devfrom
ci/den-db-auto-migrations

Conversation

@benjaminshafii

Copy link
Copy Markdown
Member

What

Automated database migrations for the Den stack (PlanetScale prod). No telemetry/analytics changes in this PR — clean off dev.

New workflows

  • .github/workflows/den-db-check.yml — on every PR touching ee/packages/den-db/**: runs db:generate and fails if it produces changes (schema edited without a committed migration). No secrets needed.
  • .github/workflows/den-db-migrate.yml — applies db:migrate to production:
    • automatically on push to dev when ee/packages/den-db/drizzle/** changes
    • manually via workflow_dispatch, with baseline / baseline_through / dry_run inputs
    • guarded: fails fast with a clear message if secrets are missing; concurrency group prevents overlapping runs; skipped on forks

Required GitHub Actions secrets (fill these in)

Same names as the local env vars in ee/packages/den-db/.env.example:

Secret Value
DATABASE_HOST PlanetScale host (e.g. aws.connect.psdb.cloud)
DATABASE_USERNAME PlanetScale branch password username
DATABASE_PASSWORD PlanetScale branch password

Supporting changes

  • db:baseline script (ee/packages/den-db/scripts/baseline-migrations.ts): prod was historically managed with db:push, so it has no __drizzle_migrations table — the first db:migrate would replay all 20 migrations and fail. The baseline records existing migrations as applied without executing them (matches drizzle's exact tracking semantics: sha256 hash + journal when timestamp). Dry-run by default, --yes to write, --through <tag> to baseline partially.
  • drizzle.config.ts: the DATABASE_HOST/USERNAME/PASSWORD credential path now sets ssl: { rejectUnauthorized: true } — PlanetScale requires TLS for MySQL-protocol connections, previously this path would fail from CI.
  • README: workflow docs, baseline guide, expand/contract migration policy (migrations run before code deploys).

Rollout (after merging)

  1. Fill the three secrets in repo settings.
  2. Run Den DB Migrate manually once with baseline: true, dry_run: true → check the printed plan (should list 0001–0020).
  3. Re-run with baseline: true, dry_run: false → records the baseline and runs db:migrate (no-op).
  4. Done — future migration files landing on dev apply automatically. (PR feat(den): analytics event model + /v1/telemetry/analytics endpoint (Layer 1+2) #2148's 0021 telemetry migration would be the first real consumer.)

Tests

Commands run (all locally, since prod secrets are intentionally not mine to set):

  • pnpm --filter @openwork-ee/den-db db:generate on a clean tree → "No schema changes", git status --porcelain -- ee/packages/den-db/drizzle empty → check workflow logic verified
  • Simulated prod (local Docker MySQL openwork_den, created via db:push, no __drizzle_migrations table — same state as prod):
    • db:baseline (dry run) → printed plan listing all 20 migrations
    • db:baseline -- --yes → recorded 20 rows (count=20, max(created_at)=1780426749385 = journal when of 0020)
    • db:migrate → "migrations applied successfully" with nothing to replay ✓
  • Negative test: full replay on an empty DB fails at 0001 (ALTER TABLE worker ... doesn't exist) — confirms the chain has no 0000 baseline; documented in the README (fresh DBs use db:push, existing DBs use baseline + migrate).

Not tested (requires the PlanetScale secrets): the TLS connection from CI to PlanetScale itself. The dry_run dispatch input exists exactly so the first prod run can be validated safely — happy to run it once the secrets are filled.

No video: CLI/CI-only change; full command transcripts above reproduce everything.

- den-db-check.yml: PR gate that fails when the schema changes without
  a committed migration (db:generate must be a no-op)
- den-db-migrate.yml: applies db:migrate to production PlanetScale when
  migration files land on dev; manual dispatch supports a one-time
  baseline (with dry-run) for the push-managed prod database
- db:baseline script: records existing migrations as applied in
  __drizzle_migrations without executing them
- drizzle.config.ts: enable TLS for the PlanetScale host/user/password
  credential path (required for MySQL-protocol connections from CI)
- .env.example documents DATABASE_HOST/DATABASE_USERNAME/DATABASE_PASSWORD
  (same names as the GitHub Actions secrets)
- README: CI workflow docs, baseline guide, expand/contract policy
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
openwork-app Ready Ready Preview, Comment Jun 10, 2026 4:38pm
openwork-den Ready Ready Preview, Comment Jun 10, 2026 4:38pm
openwork-den-worker-proxy Ready Ready Preview, Comment Jun 10, 2026 4:38pm
openwork-landing Ready Ready Preview, Comment, Open in v0 Jun 10, 2026 4:38pm

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="ee/packages/den-db/scripts/baseline-migrations.ts">

<violation number="1" location="ee/packages/den-db/scripts/baseline-migrations.ts:41">
P2: `--through` is not validated, so missing/empty values silently baseline through the latest migration.</violation>
</file>

<file name=".github/workflows/den-db-migrate.yml">

<violation number="1" location=".github/workflows/den-db-migrate.yml:90">
P2: `workflow_dispatch` input is directly interpolated in a `run` script, creating a shell-injection path for `baseline_through`.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

function parseArgs() {
const args = process.argv.slice(2)
const apply = args.includes("--yes")
const throughIndex = args.indexOf("--through")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: --through is not validated, so missing/empty values silently baseline through the latest migration.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At ee/packages/den-db/scripts/baseline-migrations.ts, line 41:

<comment>`--through` is not validated, so missing/empty values silently baseline through the latest migration.</comment>

<file context>
@@ -0,0 +1,143 @@
+function parseArgs() {
+  const args = process.argv.slice(2)
+  const apply = args.includes("--yes")
+  const throughIndex = args.indexOf("--through")
+  const through = throughIndex >= 0 ? args[throughIndex + 1] : undefined
+  return { apply, through }
</file context>

set -euo pipefail
extra=""
if [ -n "${{ inputs.baseline_through }}" ]; then
extra="--through ${{ inputs.baseline_through }}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: workflow_dispatch input is directly interpolated in a run script, creating a shell-injection path for baseline_through.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .github/workflows/den-db-migrate.yml, line 90:

<comment>`workflow_dispatch` input is directly interpolated in a `run` script, creating a shell-injection path for `baseline_through`.</comment>

<file context>
@@ -0,0 +1,104 @@
+          set -euo pipefail
+          extra=""
+          if [ -n "${{ inputs.baseline_through }}" ]; then
+            extra="--through ${{ inputs.baseline_through }}"
+          fi
+          if [ "${{ inputs.dry_run }}" = "true" ]; then
</file context>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant