Skip to content

feat(db): migrations create, list, and apply - #136

Open
jamie-at-bunny wants to merge 12 commits into
mainfrom
db-migrations
Open

feat(db): migrations create, list, and apply#136
jamie-at-bunny wants to merge 12 commits into
mainfrom
db-migrations

Conversation

@jamie-at-bunny

Copy link
Copy Markdown
Member

Adds bunny db migrations for running plain SQL migration files against a Bunny Database.

Adds `bunny db migrations` for running plain SQL migration files against a
Bunny Database. Files live in `migrations/` (falling back to `drizzle/`) and
are named `NNNN_<slug>.sql`; the filename is the migration's identity and its
numeric prefix is the apply order. Applied migrations are recorded in
`__bunny_migrations`, which existing introspection excludes already, so it
stays out of `db studio` and the REST layer.

Each file runs through `client.migrate()` together with its tracking row, so a
migration either lands and is recorded or neither happens, and foreign keys
stay deferred for table rebuilds. `list` reports applied, pending, modified,
and missing state without creating the tracking table. `apply` stops at the
first failure and confirms only when a TTY is attached.

`splitStatements` now keeps `CREATE TRIGGER ... BEGIN ... END;` bodies intact,
which `db shell <file>.sql` benefits from too.

Credential resolution moves to `db/credentials.ts`, shared by shell, studio,
and migrations apply instead of a third copy.
@bogdan-at-bunny

Copy link
Copy Markdown

@codex review

@jamie-at-bunny jamie-at-bunny self-assigned this Jul 28, 2026
@changeset-bot

changeset-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 01b1c91

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 12 packages
Name Type
@bunny.net/cli Minor
@bunny.net/database-shell Patch
@bunny.net/cli-linux-x64 Minor
@bunny.net/cli-linux-arm64 Minor
@bunny.net/cli-darwin-x64 Minor
@bunny.net/cli-darwin-arm64 Minor
@bunny.net/cli-windows-x64 Minor
@bunny.net/database-shell-linux-x64 Patch
@bunny.net/database-shell-linux-arm64 Patch
@bunny.net/database-shell-darwin-x64 Patch
@bunny.net/database-shell-darwin-arm64 Patch
@bunny.net/database-shell-windows-x64 Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Comment thread packages/cli/src/commands/db/credentials.ts Outdated
Comment thread packages/database-shell/src/parser.ts Outdated
@greptile-apps

greptile-apps Bot commented Jul 28, 2026

Copy link
Copy Markdown

Greptile Summary

Adds a database migration workflow and hardens shared database tooling.

  • Adds commands to create, list, and atomically apply ordered SQL migrations with checksum drift detection.
  • Centralizes encrypted database credential resolution and endpoint-bound token handling for shell, studio, and migration commands.
  • Extends SQL splitting to preserve trigger bodies, recognize SQLite quoting forms, remove comments, and reject truncated input.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
packages/cli/src/commands/db/credentials.ts Centralizes credential resolution, enforces encrypted URLs, and binds ambient or generated tokens to the expected endpoint.
packages/cli/src/commands/db/migrations/engine.ts Implements migration discovery, checksums, history classification, SQL preparation, and atomic application with a tracking row.
packages/cli/src/commands/db/migrations/apply.ts Orchestrates migration preflight, drift checks, confirmation, application, and structured reporting.
packages/database-shell/src/parser.ts Expands SQL splitting to support trigger blocks, comments, SQLite quote forms, and explicit truncated-input errors.
packages/database-shell/src/shell.test.ts Adds regression coverage for the previously reported trigger parsing and lexical edge cases.

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI as bunny db migrations
    participant Credentials as Credential resolver
    participant Parser as SQL splitter
    participant DB as Bunny Database
    User->>CLI: apply migration files
    CLI->>Credentials: resolve URL and token
    Credentials-->>CLI: encrypted endpoint-bound credentials
    CLI->>DB: read migration history
    CLI->>Parser: validate and split pending SQL
    Parser-->>CLI: prepared statements
    loop Each pending migration
        CLI->>DB: atomic migrate(statements + tracking row)
        DB-->>CLI: applied or rolled back
    end
    CLI-->>User: migration report
Loading

Fix All in Greploop

Reviews (9): Last reviewed commit: "Merge branch 'main' into db-migrations" | Re-trigger Greptile

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e496926392

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/cli/src/commands/db/migrations/apply.ts Outdated
Comment thread packages/database-shell/src/parser.ts Outdated
Comment thread packages/cli/src/commands/db/migrations/apply.ts Outdated
Comment thread packages/cli/src/commands/db/credentials.ts Outdated
Parser: a trigger body statement ending in `CASE ... END;` was mistaken for the
trigger's own terminator, shredding a valid trigger into three fragments.
Nesting is now counted across `BEGIN` and `CASE` openers, with quoted strings
and identifiers scrubbed first so a column named `end` doesn't skew the count.

Credentials: an explicit database ID no longer falls through to `.env`, which
could target a different database than the one named on the command line. A
generated token is now bound to the resolved database's host, so `--url`
without `--token` is refused on mismatch instead of sending a full-access token
to an unverified endpoint, and the check runs before the token is created.

Apply: `ensureMigrationsTable()` moved after the dry-run exit and the
confirmation, so a preview or a declined run writes nothing. The failed
migration is now counted as still pending, since its tracking row rolls back
with it.

Also wraps the pre-confirmation read in `readApplied()`, turning a bad URL or
token into a hinted error instead of an unexpected-error exit.
Comment thread packages/cli/src/commands/db/credentials.ts Outdated
Comment thread packages/database-shell/src/parser.ts
Parser: block comments were not tokenized at all, so `/* END */` inside a
trigger body counted as a structural closer, and more broadly a `;` or a quote
inside any block comment split or corrupted the statement around it. Block
comments are now skipped like `--` comments, which fixes both.

Credentials: a hostname match let a plaintext URL receive a token. A token the
user did not pass on the command line now requires an encrypted target,
rejecting `http:`, `ws:`, and `libsql://host:port?tls=0`, which the libSQL
client downgrades to plaintext. This covers the token read from `.env` as well
as a generated one, since neither was paired with the URL by the user. The
scheme check runs before any lookup or prompt, so an unusable URL fails
immediately rather than after picking a database.

An explicit `--token` alongside a plaintext `--url` is still allowed: that
pairing is deliberate, and it covers a local sqld over http.
Comment thread packages/cli/src/commands/db/credentials.ts Outdated
An encrypted `--url` on a foreign host still received the token from `.env`,
because the plaintext guard added in the previous commit was the only check on
that path. Last round I assumed host ownership couldn't be verified there
without an API call, which was wrong: the `.env` URL is the pairing the user
established, so comparing against it is a local check.

The `.env` token is now reused only for a `--url` on the same host as the `.env`
URL. Anything else falls through to the API path, where a fresh token is created
and checked against the database's canonical URL, so the stored credential is
never the one that travels. Comparing against `.env` rather than the API keeps
the offline case working: both values in `.env` with `--url` naming the same
host still needs no network call.

Folding the encryption check into the same predicate removes the separate
`.env`-specific error path; a plaintext override now falls through to the
existing "must be encrypted" refusal.
Comment thread packages/cli/src/commands/db/credentials.ts Outdated
Comment thread packages/cli/src/commands/db/credentials.ts Outdated
@@ -1,42 +1,114 @@
/** Statements whose body is a `BEGIN ... END` block, so inner semicolons don't terminate them. */
const BLOCK_BODY_START = /^CREATE\s+(?:TEMP\s+|TEMPORARY\s+)?TRIGGER\b/i;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 IF NOT EXISTS trigger splits

When a migration or SQL file contains CREATE TRIGGER IF NOT EXISTS ... BEGIN ...; END;, trigger-body detection does not preserve the body, so the first inner semicolon creates malformed fragments that fail in client.migrate or client.batch.

Knowledge Base Used: Database Shell

Fix in Claude Code

Docs-only conflicts in AGENTS.md and README.md: keep main's content
additions (sandbox stat, openapi-client subpath entrypoints, agent-skill
core module, LICENSE in the published files list, skills command
examples) in this branch's punctuation style.
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.

2 participants