Repository navigation
ci(neon): preview migrate, PR schema diff, production migrate on merge #74
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,8 @@ | ||
| { | ||
| "version": 1, | ||
| "lastRunAtMs": 1774205384265, | ||
| "turnsSinceLastRun": 6, | ||
| "lastTranscriptMtimeMs": 1774205384073, | ||
| "lastProcessedGenerationId": "be5a8da6-6a66-4dd0-abdf-1d8ebc0d9762", | ||
| "lastRunAtMs": 1774380014755, | ||
| "turnsSinceLastRun": 1, | ||
| "lastTranscriptMtimeMs": 1774380014716, | ||
| "lastProcessedGenerationId": "2cceeb7e-0721-4f00-84d1-eb6ca0c7d720", | ||
| "trialStartedAtMs": null | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -24,18 +24,16 @@ jobs: | |
|
|
||
| create_neon_branch: | ||
| name: Create Neon Branch | ||
| permissions: | ||
| contents: read | ||
| outputs: | ||
| db_url: ${{ steps.create_neon_branch.outputs.db_url }} | ||
| db_url_pooled: ${{ steps.create_neon_branch.outputs.db_url_pooled }} | ||
| needs: setup | ||
| if: | | ||
| github.event_name == 'pull_request' && ( | ||
| github.event.action == 'synchronize' | ||
| || github.event.action == 'opened' | ||
| || github.event.action == 'reopened') | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v4 | ||
|
|
@@ -65,50 +63,55 @@ jobs: | |
| - name: Install dependencies | ||
| run: yarn install --immutable | ||
|
|
||
| # The step above creates a new Neon branch. | ||
| # You may want to do something with the new branch, such as run migrations, run tests | ||
| # on it, or send the connection details to a hosting platform environment. | ||
| # The branch DATABASE_URL is available to you via: | ||
| # "${{ steps.create_neon_branch.outputs.db_url_pooled }}". | ||
| # It's important you don't log the DATABASE_URL as output as it contains a username and | ||
| # password for your database. | ||
| - name: Run Migrations | ||
| run: yarn db:push | ||
| # Apply committed migrations only (run `yarn db:generate` locally when schema changes). | ||
| - name: Run migrations on preview branch | ||
| run: yarn db:migrate | ||
| env: | ||
| # to use pooled connection | ||
| #DATABASE_URL: "${{ steps.create_neon_branch.outputs.db_url_pooled }}" | ||
| # OR to use unpooled connection | ||
| DATABASE_URL: "${{ steps.create_neon_branch.outputs.db_url }}" | ||
|
|
||
| # Following the step above, which runs database migrations, you may want to check | ||
| # for schema changes in your database. We recommend using the following action to | ||
| # post a comment to your pull request with the schema diff. For this action to work, | ||
| # you also need to give permissions to the workflow job to be able to post comments | ||
| # and read your repository contents. Add the following permissions to the workflow job: | ||
| # | ||
| # permissions: | ||
| # contents: read | ||
| # pull-requests: write | ||
| # | ||
| # You can also check out https://github.com/neondatabase/schema-diff-action for more | ||
| # information on how to use the schema diff action. | ||
| # You can uncomment the lines below to enable the schema diff action. | ||
| # - name: Post Schema Diff Comment to PR | ||
| # uses: neondatabase/schema-diff-action@v1 | ||
| # with: | ||
| # project_id: ${{ vars.NEON_PROJECT_ID }} | ||
| # compare_branch: preview/pr-${{ github.event.number }}-${{ needs.setup.outputs.branch }} | ||
| # api_key: ${{ secrets.NEON_API_KEY }} | ||
| - name: Post schema diff comment to PR | ||
| uses: neondatabase/schema-diff-action@v1 | ||
| with: | ||
| project_id: ${{ vars.NEON_PROJECT_ID }} | ||
| compare_branch: preview/pr-${{ github.event.number }}-${{ needs.setup.outputs.branch }} | ||
| api_key: ${{ secrets.NEON_API_KEY }} | ||
|
|
||
| delete_neon_branch: | ||
| name: Delete Neon Branch | ||
| name: Delete Neon Branch and apply production migrations | ||
| needs: setup | ||
| if: github.event_name == 'pull_request' && github.event.action == 'closed' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - name: Delete Neon Branch | ||
| uses: neondatabase/delete-branch-action@v3 | ||
| with: | ||
| project_id: ${{ vars.NEON_PROJECT_ID }} | ||
| branch: preview/pr-${{ github.event.number }}-${{ needs.setup.outputs.branch }} | ||
| api_key: ${{ secrets.NEON_API_KEY }} | ||
|
|
||
| - name: Checkout | ||
| if: github.event.pull_request.merged == true | ||
| uses: actions/checkout@v4 | ||
|
|
||
|
Comment on lines
+94
to
+97
|
||
| - name: Setup Node.js | ||
| if: github.event.pull_request.merged == true | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version: 22 | ||
| cache: yarn | ||
|
|
||
| - name: Enable Corepack (Yarn from packageManager) | ||
| if: github.event.pull_request.merged == true | ||
| run: corepack enable | ||
|
|
||
| - name: Install dependencies | ||
| if: github.event.pull_request.merged == true | ||
| run: yarn install --immutable | ||
|
|
||
| - name: Apply migrations to production | ||
| if: github.event.pull_request.merged == true | ||
| run: yarn db:migrate | ||
| env: | ||
| DATABASE_URL: "${{ secrets.DATABASE_URL }}" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| -- One-time fixes for drizzle-kit migrate + Neon branching: | ||
| -- 1) Empty __drizzle_migrations while tables already exist (e.g. after db:push). | ||
| -- 2) A row for 0002 with created_at lower than 0001’s (breaks Drizzle’s “max(created_at)” check). | ||
| -- | ||
| -- Hashes are SHA-256 of each db/migrations/<tag>.sql file. created_at must match | ||
| -- db/migrations/meta/_journal.json "when" and increase with migration index. | ||
|
|
||
| UPDATE drizzle.__drizzle_migrations | ||
| SET created_at = 1774322729407::bigint | ||
| WHERE hash = 'e7e0da21bd642dd224ef85b89f8d4f29b349525901fdcad9cde454792ae26f8d' | ||
| AND created_at < 1774322729406::bigint; | ||
|
|
||
| INSERT INTO drizzle.__drizzle_migrations ("hash", "created_at") | ||
| SELECT 'd15f8da1594d56975cb8c0cac5d8d944f546ffe38fdedaa9d15a75e48826b348', 1774123011028::bigint | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 FROM drizzle.__drizzle_migrations WHERE hash = 'd15f8da1594d56975cb8c0cac5d8d944f546ffe38fdedaa9d15a75e48826b348' | ||
| ); | ||
|
|
||
| INSERT INTO drizzle.__drizzle_migrations ("hash", "created_at") | ||
| SELECT 'c2fbf77da07e7b8d5d0f53b8b2687a14f5d6d61fd300118f8eceb416d4d55973', 1774322729406::bigint | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 FROM drizzle.__drizzle_migrations WHERE hash = 'c2fbf77da07e7b8d5d0f53b8b2687a14f5d6d61fd300118f8eceb416d4d55973' | ||
| ); | ||
|
|
||
| INSERT INTO drizzle.__drizzle_migrations ("hash", "created_at") | ||
| SELECT 'e7e0da21bd642dd224ef85b89f8d4f29b349525901fdcad9cde454792ae26f8d', 1774322729407::bigint | ||
| WHERE NOT EXISTS ( | ||
| SELECT 1 FROM drizzle.__drizzle_migrations WHERE hash = 'e7e0da21bd642dd224ef85b89f8d4f29b349525901fdcad9cde454792ae26f8d' | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { readFileSync } from 'node:fs'; | ||
| import { resolve } from 'node:path'; | ||
| import { describe, expect, it } from 'vitest'; | ||
|
|
||
| describe('Drizzle migration journal', () => { | ||
| it('keeps "when" monotonic in journal order (required by drizzle-kit migrate)', () => { | ||
| const raw = readFileSync(resolve(process.cwd(), 'db/migrations/meta/_journal.json'), 'utf8'); | ||
| const journal = JSON.parse(raw) as { entries: { when: number }[] }; | ||
| let prev = 0; | ||
| for (const e of journal.entries) { | ||
| expect(e.when, 'each entry.when must be > previous (PgDialect.migrate uses max(created_at))').toBeGreaterThan( | ||
| prev | ||
| ); | ||
| prev = e.when; | ||
| } | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this job, the preview branch is deleted before production migrations run. If the delete-branch action fails (e.g., branch already deleted/expired or transient Neon API error), subsequent steps will be skipped (default
if: success()), so production migrations won’t run on merge. Consider running production migrations before deleting the preview branch, and/or make the delete step non-blocking (continue-on-error: true) and useif: always()for the migration steps so they still execute when merged.