Repository navigation
feat(skills): asking to refresh a stale Drizzle migration now runs one checked procedure #109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| --- | ||
| name: refresh-drizzle-migration | ||
| description: "Refresh a branch's Drizzle migration after its base gained newer ones: merge the base, drop the stale migration, regenerate, check, push behind a preflight. Use when asked to merge main into a PR or branch and refresh or regenerate its migration, or when its journal entry or migration number trails the base's." | ||
| --- | ||
|
|
||
| # Refresh a Drizzle migration | ||
|
|
||
| A migration is **stale** when the base branch gained migrations after the head branch generated its own, so the head's journal entry, snapshot chain, or folder timestamp no longer follows the base's latest. Every refresh drops the head's migration and regenerates it on top of the base. | ||
|
|
||
| `drizzle-kit generate` is the only drizzle-kit command this skill runs. The user's refresh request is the per-run permission for that one generate step. `migrate`, `push`, `pull`, `up`, `studio`, and any script that wraps them stay out of this run, because they touch a database or rewrite the migrations layout. | ||
|
|
||
| ## 1. Record the starting point | ||
|
|
||
| Resolve the base branch the way `resolving-merge-conflicts` step 1 does. Fetch it, and use the remote-tracking ref `refs/remotes/<remote>/<base>` as `<base-ref>` in every command below; a local branch of the same name can lag the remote. When the base comes from the PR, require `<base-ref>` to equal the PR's `baseRefOid`. Require a clean worktree with the head branch checked out, as that skill does. | ||
|
|
||
| Find every Drizzle config: `git ls-files '*drizzle.config.*'`, plus every path a `package.json` script passes to `drizzle-kit` through `--config`. Read each config's `out` folder (default `./drizzle`) and resolve it against the config's own directory, so it works as a repo-root pathspec. Identify the layout per `out` folder: | ||
|
|
||
| - **Journal layout** (drizzle-kit 0.x): `meta/_journal.json`, `<idx>_<name>.sql`, `meta/<idx>_snapshot.json`. | ||
| - **Folder layout** (drizzle-kit v1): one `<timestamp>_<name>/` folder per migration holding `migration.sql` and `snapshot.json`, and no journal. | ||
|
|
||
| List the head's own migrations per `out` folder, before anything merges, with `git diff --name-only --diff-filter=A <base-ref>...HEAD -- <out>`. The stale SQL is readable at `HEAD` as it stands now, so record that SHA as the **stale commit**. When this lists nothing, an earlier merge may have dropped the files, so fall back to `git log --full-history --no-merges --diff-filter=A --name-only --format= <base-ref>..HEAD -- <out>`, and take each file's stale commit from `git log -1 --no-merges --format=%H --full-history --diff-filter=AM <base-ref>..HEAD -- <path>`, the last commit that wrote it, so a hand edit made after generate is the version step 5 compares. | ||
|
bhagyamudgal marked this conversation as resolved.
Resolved
|
||
|
|
||
| Then run `git diff --name-only --no-renames --diff-filter=MD <base-ref>...HEAD -- <out>`. Git prints these paths from the repo root. Any path other than `<out>/meta/_journal.json` and the head migration's own `<out>/meta/<idx>_snapshot.json`, which an earlier hand merge may have resolved to the head side, means the head edited, renamed, or deleted a migration the base already has, which a refresh cannot repair, so stop and show those paths to the user. | ||
|
|
||
| Count migrations, not files: `.sql` files in the journal layout, distinct `<timestamp>_<name>/` folders in the folder layout. Leave alone every config whose `out` folder holds none. When one `out` folder holds two or more, stop: show them to the user and ask how to proceed, since one generate run can only produce one migration. | ||
|
|
||
| The step is done when every config is classified as untouched or as holding exactly one head migration, with its layout, stale SQL path, and stale commit recorded. | ||
|
|
||
| ## 2. Merge the base | ||
|
|
||
| Skip this step when `git merge-base --is-ancestor <base-ref> HEAD` exits 0. Otherwise merge the base into the head branch through `resolving-merge-conflicts`, whose step 1 owns how a conflicted Drizzle `meta/` file resolves. That skill ends the merge with its own commit and readiness card, and leaves the push to step 6 here. | ||
|
|
||
| The step is done when `git merge-base --is-ancestor <base-ref> HEAD` exits 0 and `git status --porcelain` is empty. | ||
|
|
||
| ## 3. Drop the stale migration | ||
|
|
||
| For each touched config, return every path the head's migration occupies to the base's version, using `git restore --source=<base-ref> -- <path>` when the base has that path and deleting the path otherwise. A path both branches added belongs to the base. | ||
|
|
||
| - **Journal layout**: `meta/_journal.json`, which always comes back from the base, plus the SQL file and its `meta/<idx>_snapshot.json`. | ||
| - **Folder layout**: the migration folder. | ||
|
|
||
| The step is done when `git diff --name-only <base-ref> -- <out>`, which compares the working tree with the base, lists nothing. | ||
|
|
||
| ## 4. Regenerate | ||
|
|
||
| Read the `package.json` of the config's package and find the script that runs `drizzle-kit generate`. Run that script through the project's package manager only when it runs nothing but `drizzle-kit generate` and targets this config, through a `--config` that resolves to it or, with no `--config`, because this config is the package's default `drizzle.config.*`. Otherwise run the installed binary directly from the config's own directory, since drizzle-kit resolves `schema`, `out`, and `.env` from where it runs: `<bin> generate --config <absolute config path>`, where `<bin>` is the nearest `node_modules/.bin/drizzle-kit` from the package directory up to the repo root. When no such binary exists, stop and tell the user to install dependencies. Fetching drizzle-kit on the fly, through `npx`, `bunx`, or `npm exec`, pulls unreviewed code. | ||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
|
|
||
| Run generate with stdin from `/dev/null` and a two-minute limit. drizzle-kit asks "created or renamed?" whenever a diff drops one column or table and adds another, and an agent shell has no TTY to answer it. When generate errors on a prompt or TTY, or hits the limit, stop and hand the user: | ||
|
|
||
| - the exact command, to run in their own terminal; | ||
| - the answer for each prompt, read from the stale SQL. A `RENAME` statement there means "renamed", and a matching `DROP` with `ADD` means "created". | ||
|
|
||
| Resume at step 5 once the user reports the new files exist. | ||
|
|
||
| The step is done when the `out` folder holds exactly one migration the base lacks, or generate reported no schema changes. With no changes, step 5 compares the stale SQL against an empty set. | ||
|
|
||
| ## 5. Check the regenerated migration | ||
|
|
||
| Both checks must pass before anything is committed. A failure stops the run and shows the user the values or statements that failed. | ||
|
|
||
| **Order.** In the journal layout, the new entry's `idx` is one past the base's highest, its `when` is greater than every base entry's, and its snapshot's `prevId` equals the `id` of the base's latest snapshot. In the folder layout, the new folder's timestamp sorts after every base folder, and its snapshot's `prevIds` names the base's latest snapshot `id`. | ||
|
|
||
| **Same change.** Compare the stale SQL (`git show <stale commit>:<path>`) with the regenerated SQL as statement sets. Normalize both: split on `--> statement-breakpoint`, collapse whitespace, trim each trailing `;`. Order does not matter. A statement on only one side fails the check. A statement only in the stale file is hand-written SQL, a backfill, or a `--custom` migration that generate cannot reproduce. A statement only in the new file means the merged schema drifted from what the PR intended. | ||
|
bhagyamudgal marked this conversation as resolved.
Resolved
bhagyamudgal marked this conversation as resolved.
Resolved
|
||
|
|
||
| ## 6. Verify, commit, push | ||
|
|
||
| Hand off to `done`, then commit the regenerated files through `git-commit`, separate from the merge commit. | ||
|
bhagyamudgal marked this conversation as resolved.
Resolved
|
||
|
|
||
| Pushing updates a shared ref, and the branch may belong to a teammate. Push to `<head-remote>`, the remote that resolves to the PR's `headRepository`, or the branch's upstream when there is no PR; on a fork PR it differs from the base's remote. When no configured remote resolves to it, stop and ask. Invoke `preflight-mutations` immediately before the push, with the branch, `<head-remote>`, the local and remote SHAs, and the branch owner (the PR author, or the head repository's owner) in its ownership field. Apply its result. Push without force, then read the remote ref back with `git ls-remote <head-remote> refs/heads/<branch>` and confirm it equals local `HEAD`. | ||
|
|
||
| In the final report, tell the user what happens to a database that already applied the stale migration. Its next migrate runs the regenerated migration's DDL again. In the journal layout, the 0.x migrator also silently skips every base migration whose journal `when` is at or before the stale entry's, because it only applies entries newer than the last one it recorded. For the folder layout, say this skill has not established which base migrations such a database skips. Which databases those are, and how to repair them, is the user's call. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export default { | ||
| schema: "./src/schema.ts", | ||
| out: "./drizzle", | ||
| dialect: "postgresql", | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| CREATE TABLE "users" ( | ||
| "id" text PRIMARY KEY NOT NULL, | ||
| "email" text NOT NULL, | ||
| "nickname" text | ||
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| { | ||
| "id": "5f1c2a9e-3b7d-4c41-9a0e-8d2f6b1e7c34", | ||
| "prevId": "00000000-0000-0000-0000-000000000000", | ||
| "version": "7", | ||
| "dialect": "postgresql", | ||
| "tables": { | ||
| "public.users": { | ||
| "name": "users", | ||
| "schema": "", | ||
| "columns": { | ||
| "id": { "name": "id", "type": "text", "primaryKey": true, "notNull": true }, | ||
| "email": { "name": "email", "type": "text", "primaryKey": false, "notNull": true }, | ||
| "nickname": { "name": "nickname", "type": "text", "primaryKey": false, "notNull": false } | ||
| }, | ||
| "indexes": {}, | ||
| "foreignKeys": {}, | ||
| "compositePrimaryKeys": {}, | ||
| "uniqueConstraints": {}, | ||
| "policies": {}, | ||
| "checkConstraints": {}, | ||
| "isRLSEnabled": false | ||
| } | ||
| }, | ||
| "enums": {}, | ||
| "schemas": {}, | ||
| "sequences": {}, | ||
| "roles": {}, | ||
| "policies": {}, | ||
| "views": {}, | ||
| "_meta": { "columns": {}, "schemas": {}, "tables": {} } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| { | ||
| "version": "7", | ||
| "dialect": "postgresql", | ||
| "entries": [ | ||
| { | ||
| "idx": 0, | ||
| "version": "7", | ||
| "when": 1761955200000, | ||
| "tag": "0000_create_users", | ||
| "breakpoints": true | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { pgTable, text } from "drizzle-orm/pg-core"; | ||
|
|
||
| export const users = pgTable("users", { | ||
| id: text("id").primaryKey(), | ||
| email: text("email").notNull(), | ||
| nickname: text("nickname"), | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.