-
Notifications
You must be signed in to change notification settings - Fork 974
docs: add Prisma Next Migrations section #8025
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 1 commit
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
d2ca62d
docs: add Prisma Next Migrations section
sorenbs 41e2dd0
docs: first review pass on migrations pages
sorenbs e1f14f3
docs: verification pass and full review of migrations pages
sorenbs 1c32f78
Merge origin/main into docs/prisma-next-migrations
sorenbs d9ed6b2
docs: address review; fix preset merge seam
sorenbs 04964e8
docs: five-minute path, forward-reference pass, backlog polish
sorenbs 5b54aac
docs: fix two list-count mismatches from later additions
sorenbs 8f1fc8f
docs: fix animation color bleed and narrow step-1 emphasis
sorenbs 9f0c93c
docs: make migration-loop step 3 self-explanatory
sorenbs 20b35b7
docs: clearer wording for the three-command walkthrough
sorenbs 0831b02
docs: explain why the graph matters, and that it is ignorable
sorenbs 9e0e4d3
docs: add 30s migration-loop video to How migrations work
sorenbs d7d1113
docs: link Agent Skills from the coding-agent sections
sorenbs b699109
docs(migrations): one concept per diagram, teachable file anatomy, sk…
ankur-arch 9c7c561
docs(migrations): fold in live-validation findings
ankur-arch 32007a5
docs(migrations): terminology aligned with the data contract page
ankur-arch 3381f40
Merge remote-tracking branch 'origin/main' into docs/prisma-next-migr…
nurul3101 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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| { | ||
| "version": "0.0.1", | ||
| "configurations": [ | ||
| { | ||
| "name": "docs", | ||
| "runtimeExecutable": "pnpm", | ||
| "runtimeArgs": ["--filter", "docs", "exec", "next", "dev", "--port", "3105"], | ||
| "port": 3105 | ||
| } | ||
| ] | ||
| } |
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
167 changes: 167 additions & 0 deletions
167
apps/docs/content/docs/orm/next/migrations/applying-a-migration.mdx
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,167 @@ | ||
| --- | ||
| title: Applying a migration | ||
| description: prisma-next migrate walks the graph from where your database is to where you want it — with a preview, a checkpoint at every step, and safe retries. | ||
| url: /orm/next/migrations/applying-a-migration | ||
| metaTitle: Applying a migration in Prisma Next | ||
| metaDescription: How prisma-next migrate applies migrations — targeting refs and hashes, previewing with --show, checking status, and running safely in development and production. | ||
| badge: early-access | ||
| --- | ||
|
|
||
| Applying is the one step that touches a database, and it has one command: | ||
|
|
||
| ```bash | ||
| npx prisma-next migrate | ||
| ``` | ||
|
|
||
| Note the verb: it's `migrate`, not `migration apply`. The `migration ...` subcommands manage files on disk; `migrate` moves a database. It reads where the database currently is (its **marker**), finds a path through the [migration graph](/orm/next/migrations/the-migration-graph) to the target, and applies each migration along the path — running every operation's precheck, execute, and postcheck as it goes. | ||
|
|
||
| The connection comes from `db.connection` in `prisma-next.config.ts`, or `--db` to override: | ||
|
|
||
| ```bash | ||
| npx prisma-next migrate --db $DATABASE_URL | ||
| ``` | ||
|
|
||
| A successful run reports every operation it performed and the marker it left behind: | ||
|
|
||
| ```text | ||
| ✔ Applied 1 migration(s) (3 operation(s)) across 1 contract space(s) | ||
|
|
||
| App space | ||
| ├─ Add column "displayName" to "user" | ||
| ├─ Data transform: backfill-user-displayName | ||
| └─ Set NOT NULL on "user"."displayName" (destructive) | ||
| marker: sha256:e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63 | ||
|
|
||
| Next: prisma-next migration status | ||
| ``` | ||
|
|
||
| ## Check before, preview, then apply | ||
|
|
||
| The habit worth building — especially against shared databases — is a three-step rhythm: | ||
|
|
||
| ```bash | ||
| # 1. Where is the database, what's pending? | ||
| npx prisma-next migration status --db $DATABASE_URL | ||
|
|
||
| # 2. What exactly would run? | ||
| npx prisma-next migrate --show --db $DATABASE_URL | ||
|
|
||
| # 3. Run it. | ||
| npx prisma-next migrate --db $DATABASE_URL | ||
| ``` | ||
|
|
||
| `migration status` draws the path between the database's marker and the target, flagging each migration as applied or pending: | ||
|
|
||
| ```text | ||
| * 925198f @contract | ||
| |^ 20260707T1006_add_user_phone 705b1a6 -> 925198f 1 ops > pending | ||
| * 705b1a6 @db (db) | ||
| |^ 20260707T1005_init - -> 705b1a6 2 ops + applied | ||
| * - | ||
|
|
||
| 1 pending — run `prisma-next migrate --to 925198f3cc27` | ||
| ``` | ||
|
|
||
| Read the markers on the right: `@db` is where the database is, `@contract` is where your emitted contract is, and `(db)` is the [ref](/orm/next/migrations/the-migration-graph#refs-naming-places-in-the-graph) of that name pointing at the same node. | ||
|
|
||
| `migrate --show` is the read-only dry run: it prints the migrations that would execute and stops. Nothing touches the database. | ||
|
|
||
| After applying, `migration log` shows the database's own record of what ran — an append-only **ledger** the runner writes alongside the marker: | ||
|
|
||
| ```text | ||
| Applied at Migration Change Ops | ||
| ---------------------- ------------------------------- -------------------- ------ | ||
| 2026-07-07 10:05:32Z 20260707T1005_init - -> 705b1a6 2 ops | ||
| 2026-07-07 10:09:55Z 20260707T1006_add_user_phone 705b1a6 -> 925198f 1 ops | ||
| ``` | ||
|
|
||
| ## Choosing a target | ||
|
|
||
| With no `--to`, `migrate` advances toward your emitted contract. To aim somewhere specific, `--to` accepts the same reference grammar as everywhere else — a ref name, a contract hash or prefix, a migration directory name, or `<dir>^` for the state before a migration: | ||
|
|
||
| ```bash | ||
| npx prisma-next migrate --to prod --db $DATABASE_URL # a ref | ||
| npx prisma-next migrate --to sha256:e6b5c28 --db $DATABASE_URL # a hash prefix | ||
| npx prisma-next migrate --to 20260707T1005_init --db $DATABASE_URL | ||
| ``` | ||
|
|
||
| If the graph has branched and more than one tip is reachable, `migrate` refuses to guess and asks for an explicit `--to`. That's the graph protecting you: two feature branches may both be valid futures, and picking one is a human decision. | ||
|
|
||
| `--advance-ref` moves a named ref to the post-apply state in the same step. Advancing one called `db` is what keeps [`migration plan`](/orm/next/migrations/generating-a-migration#skipping---from-the-db-ref) incremental: | ||
|
|
||
| ```bash | ||
| npx prisma-next migrate --advance-ref db | ||
| ``` | ||
|
|
||
| ## When something goes wrong | ||
|
|
||
| The runner stops at the first failing operation and tells you which one, why, and what to do: | ||
|
|
||
| ```text | ||
| ✖ Operation pgvector.install-vector-extension failed during execution: create extension "vector" (PN-RUN-3000) | ||
| Why: extension "vector" is not available | ||
| Fix: Fix the issue and re-run `prisma-next migrate --to <contract>` — previously applied migrations are preserved. | ||
| ``` | ||
|
|
||
| Three properties make failure boring instead of terrifying: | ||
|
|
||
| - **On PostgreSQL, a failed run leaves nothing behind.** The entire `migrate` run executes inside one transaction, so when an operation fails, everything from that run rolls back and the database is exactly where it was before you started. Migrations applied in *earlier* runs are untouched — that's what "previously applied migrations are preserved" means. | ||
| - **The error is specific.** It names the operation, the phase (precheck, execute, or postcheck), and the check that failed — enough to fix the cause without spelunking. | ||
| - **Re-running is safe.** Operations are idempotent: before running one, the runner evaluates its postcheck and skips it if the database already satisfies it. A change that snuck in out-of-band doesn't break the run — it just becomes a skip. On MongoDB, where cross-collection transactions don't exist, this same mechanism is what makes a partially-applied run converge on retry. See [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery) for the full failure playbook. | ||
|
|
||
| Before running any DDL, `migrate` also verifies the database's marker is a state the graph knows. A database that was changed outside of migrations fails fast with a marker mismatch instead of getting SQL applied on top of unknown drift. | ||
|
|
||
| ## Development vs. production | ||
|
|
||
| The commands are the same everywhere; what changes is where the files come from and who runs them. | ||
|
|
||
| **In development**, you're the one planning and editing, and you apply immediately: | ||
|
|
||
| ```bash | ||
| npx prisma-next migration plan --name my_change && npx prisma-next migrate --advance-ref db | ||
| ``` | ||
|
|
||
| **In CI and production**, migrations arrive via your repo, already planned, reviewed, and merged. The deploy step is: | ||
|
|
||
| ```bash | ||
| npx prisma-next migration check # files intact, graph well-formed (offline) | ||
| npx prisma-next migrate --show --db $DATABASE_URL # log what's about to run | ||
| npx prisma-next migrate --db $DATABASE_URL | ||
| ``` | ||
|
|
||
| A production nicety that falls out of the design: the runner executes only `ops.json` — plain data. Your `migration.ts` files, and any TypeScript they import, are never executed with production credentials. | ||
|
|
||
| Concurrent deploys are safe: on PostgreSQL the whole apply runs inside a transaction guarded by an advisory lock, so two `migrate` runs serialize instead of interleaving. On MongoDB — where cross-collection DDL transactions don't exist — each migration advances the marker with compare-and-swap and the runner verifies the resulting schema before committing the marker, so a re-run converges rather than double-applying. | ||
|
|
||
| ## Extension spaces | ||
|
|
||
| If your project uses database extensions (say pgvector), you'll see more than one *contract space* in the output: extensions ship their own migrations (for example `CREATE EXTENSION vector`), tracked in `migrations/<extension>/` next to your app's. One `migrate` run walks them all — extensions first, then your app — and reports each space separately: | ||
|
|
||
| ```text | ||
| ✔ Applied 2 migration(s) (20 operation(s)) across 2 contract space(s) | ||
|
|
||
| Extension space: pgvector | ||
| └─ Enable extension "vector" | ||
|
|
||
| App space | ||
| ├─ Create table "user" | ||
| └─ ... | ||
| ``` | ||
|
|
||
| :::note[What's early] | ||
|
|
||
| Apply, targeting, preview, refs, the ledger, and multi-space runs all work today. Not built yet: a shadow-database rehearsal (`migrate` runs against the real target; use `--show` and a staging database), and an apply-time check that `ops.json` still matches `migration.ts` (today that's `migration check`'s job — run it in CI). | ||
|
|
||
| ::: | ||
|
|
||
| ## Prompt your coding agent | ||
|
|
||
| - "Check migration status against staging and apply whatever is pending." | ||
| - "Preview what `migrate --to prod` would run and summarize the destructive operations." | ||
| - "Apply the pending migrations and advance the `db` ref." | ||
|
|
||
| ## See also | ||
|
|
||
| - [Rollbacks and recovery](/orm/next/migrations/rollbacks-and-recovery): when you need to go back instead of forward | ||
| - [The migration graph](/orm/next/migrations/the-migration-graph): markers, refs, and pathfinding | ||
| - [Generating a migration](/orm/next/migrations/generating-a-migration): producing what `migrate` runs |
207 changes: 207 additions & 0 deletions
207
apps/docs/content/docs/orm/next/migrations/editing-a-migration.mdx
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,207 @@ | ||
| --- | ||
| title: Editing a migration | ||
| description: A migration is TypeScript you own. Fill in backfills, reorder steps, or drop to raw SQL — then recompile it with one command. | ||
| url: /orm/next/migrations/editing-a-migration | ||
| metaTitle: Editing a migration in Prisma Next | ||
| metaDescription: How to edit a Prisma Next migration.ts — fill placeholder data transforms, write typed backfills, use rawSql, and recompile ops.json with node migration.ts. | ||
| badge: early-access | ||
| --- | ||
|
|
||
| The planner writes a good first draft, but plenty of real migrations need a human (or agent) decision: what to put in existing rows when a column becomes required, which order two steps must run in, one statement the planner has no factory for. In Prisma Next you make those changes by editing `migration.ts` — ordinary TypeScript with autocomplete and type checking — and recompiling it. You never hand-edit SQL files, and you never hand-edit `ops.json`. | ||
|
|
||
| The rule that makes this safe: | ||
|
|
||
| > **You edit `migration.ts`. Running it regenerates `ops.json`. The runner only ever executes `ops.json`.** | ||
|
|
||
| After any edit, recompile from inside your project: | ||
|
|
||
| ```bash | ||
| node migrations/app/20260707T1008_add_display_name/migration.ts | ||
| ``` | ||
|
|
||
| ```text | ||
| Wrote ops.json + migration.json to migrations/app/20260707T1008_add_display_name | ||
| ``` | ||
|
|
||
| The recompile also re-attests the package: `migration.json` gets a fresh `migrationHash` covering the compiled output. If someone edits `ops.json` directly — or edits `migration.ts` and forgets to recompile — `migration check` fails in CI with a hash mismatch. Commit `migration.ts` and `ops.json` together, like a lockfile and its manifest. | ||
|
|
||
| ## Worked example: making a column required | ||
|
|
||
| This is the classic case. `User` gets a required `displayName`, but the table already has rows, and those rows have no `displayName`. Plan it: | ||
|
|
||
| ```bash | ||
| npx prisma-next migration plan --name add_display_name | ||
| ``` | ||
|
|
||
| ```text | ||
| ⚠ Planned migration with placeholder(s) — edit migration.ts then run `node migration.ts` to self-emit | ||
|
|
||
| Open migration.ts and replace each `placeholder(...)` call with your actual query. | ||
| ``` | ||
|
|
||
| The planner knew the shape of the fix — add the column nullable, backfill, then tighten — and scaffolded exactly that, leaving the backfill query to you: | ||
|
|
||
| ```ts title="migration.ts (as planned)" | ||
| override get operations() { | ||
| return [ | ||
| this.addColumn({ | ||
| schema: 'public', | ||
| table: 'user', | ||
| column: col('displayName', 'text', { codecRef: { codecId: 'pg/text@1' } }), | ||
| }), | ||
| this.dataTransform(endContract, 'backfill-user-displayName', { | ||
| check: () => placeholder('backfill-user-displayName:check'), | ||
| run: () => placeholder('backfill-user-displayName:run'), | ||
| }), | ||
| this.setNotNull({ schema: 'public', table: 'user', column: 'displayName' }), | ||
| ]; | ||
| } | ||
| ``` | ||
|
|
||
| A `dataTransform` takes two closures. `check` asks "are there rows that still need this?" — it must be a row-returning query where *any row* means work remains (the conventional shape is `select('id').where(<violation>).limit(1)`). `run` performs the change. Fill them with the typed SQL query builder, wired to the contract snapshot sitting next to the migration: | ||
|
|
||
| ```ts title="migration.ts (filled in)" | ||
| import type { Contract as End } from './end-contract'; | ||
| import endContractJson from './end-contract.json' with { type: 'json' }; | ||
| import type { Contract as Start } from './start-contract'; | ||
| import startContract from './start-contract.json' with { type: 'json' }; | ||
| import { Migration, MigrationCLI, col } from '@prisma-next/postgres/migration'; | ||
| import postgresAdapter from '@prisma-next/adapter-postgres/runtime'; | ||
| import { sql } from '@prisma-next/sql-builder/runtime'; | ||
| import { createExecutionContext, createSqlExecutionStack } from '@prisma-next/sql-runtime'; | ||
| import postgresTarget, { PostgresContractSerializer } from '@prisma-next/target-postgres/runtime'; | ||
|
|
||
| const endContract = new PostgresContractSerializer().deserializeContract(endContractJson); | ||
|
|
||
| const db = sql<End>({ | ||
| context: createExecutionContext({ | ||
| contract: endContract, | ||
| stack: createSqlExecutionStack({ target: postgresTarget, adapter: postgresAdapter }), | ||
| }), | ||
| }); | ||
|
|
||
| export default class M extends Migration<Start, End> { | ||
| override readonly endContractJson = endContract; | ||
| override readonly startContractJson = startContract; | ||
|
|
||
| override get operations() { | ||
| return [ | ||
| this.addColumn({ | ||
| schema: 'public', | ||
| table: 'user', | ||
| column: col('displayName', 'text', { codecRef: { codecId: 'pg/text@1' } }), | ||
| }), | ||
| this.dataTransform(endContract, 'backfill-user-displayName', { | ||
| check: () => | ||
| db.public.user | ||
| .select('id') | ||
| .where((f, fns) => fns.eq(f.displayName, null)) | ||
| .limit(1), | ||
| run: () => | ||
| db.public.user | ||
| .update({ displayName: 'Anonymous' }) | ||
| .where((f, fns) => fns.eq(f.displayName, null)), | ||
| }), | ||
| this.setNotNull({ schema: 'public', table: 'user', column: 'displayName' }), | ||
| ]; | ||
| } | ||
| } | ||
|
|
||
| MigrationCLI.run(import.meta.url, M); | ||
| ``` | ||
|
|
||
| Two details worth pausing on: | ||
|
|
||
| - **The types come from the migration's own contract snapshot** (`./end-contract`), not your live app contract. You're type-checking against the schema *as it will exist when this step runs* — which is why the builder happily references `displayName` even though the column doesn't exist yet, and why a migration written months ago keeps compiling after your contract moves on. | ||
| - **The query is real application-grade TypeScript.** You can import shared constants, and typos in column names fail the type check instead of failing in production. | ||
|
|
||
| Recompile with `node migration.ts` and inspect what the backfill became: | ||
|
|
||
| ```json title="ops.json (the compiled dataTransform)" | ||
| { | ||
| "id": "data_migration.backfill-user-displayName", | ||
| "label": "Data transform: backfill-user-displayName", | ||
| "operationClass": "data", | ||
| "precheck": [ | ||
| { | ||
| "description": "Check backfill-user-displayName has work to do", | ||
| "sql": "SELECT EXISTS (SELECT \"id\" AS \"id\" FROM \"public\".\"user\" WHERE \"displayName\" IS NULL LIMIT 1) AS ok", | ||
| "params": [] | ||
| } | ||
| ], | ||
| "execute": [ | ||
| { | ||
| "description": "Run backfill-user-displayName", | ||
| "sql": "UPDATE \"public\".\"user\" SET \"displayName\" = $1 WHERE \"displayName\" IS NULL", | ||
| "params": ["Anonymous"] | ||
| } | ||
| ], | ||
| "postcheck": [ | ||
| { | ||
| "description": "Verify backfill-user-displayName resolved all violations", | ||
| "sql": "SELECT NOT EXISTS (SELECT \"id\" AS \"id\" FROM \"public\".\"user\" WHERE \"displayName\" IS NULL LIMIT 1) AS ok", | ||
| "params": [] | ||
| } | ||
| ] | ||
| } | ||
| ``` | ||
|
|
||
| Your `check` closure became both the precheck (`EXISTS` — is there work?) and the postcheck (`NOT EXISTS` — is it done?). Your `run` became a parameterized `UPDATE` — note `"Anonymous"` travels in `params`, through the driver's parameter binder, never spliced into SQL text. The reviewer reading your PR sees intent in `migration.ts` and the exact statements in `ops.json`, side by side. | ||
|
|
||
| ## Escape hatch: raw SQL | ||
|
|
||
| For anything the operation factories don't cover — enabling an extension, `CREATE INDEX CONCURRENTLY`, a vendor-specific statement — use `rawSql`, and keep the same three-phase safety if you can: | ||
|
|
||
| ```ts | ||
| rawSql({ | ||
| id: 'extension.pgcrypto', | ||
| label: 'Enable extension "pgcrypto"', | ||
| operationClass: 'additive', | ||
| target: { id: 'postgres' }, | ||
| precheck: [ | ||
| { description: 'not yet enabled', sql: "SELECT NOT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto')" }, | ||
| ], | ||
| execute: [{ description: 'enable it', sql: 'CREATE EXTENSION IF NOT EXISTS pgcrypto' }], | ||
| postcheck: [ | ||
| { description: 'now enabled', sql: "SELECT EXISTS (SELECT 1 FROM pg_extension WHERE extname = 'pgcrypto')" }, | ||
| ], | ||
| }), | ||
| ``` | ||
|
|
||
| The prechecks and postchecks are optional — but they're what makes a failed run resumable and a mistake diagnosable, so skipping them trades away most of what this system gives you. If you write the same `rawSql` twice, lift it into a function; the built-in factories are plain functions doing exactly this. | ||
|
|
||
| ## Starting from a blank migration | ||
|
|
||
| Sometimes there's no contract change at all — you want a data-only migration, or you'd rather write the whole thing by hand. `migration new` scaffolds an empty, attested package: | ||
|
|
||
| ```bash | ||
| npx prisma-next migration new --name backfill_scores | ||
| ``` | ||
|
|
||
| Write your operations in the generated `migration.ts`, then compile it the same way: `node migration.ts`. | ||
|
|
||
| ## Editing checklist | ||
|
|
||
| 1. Edit `migration.ts` — never `ops.json`. | ||
| 2. Recompile: `node <migration-dir>/migration.ts`. | ||
| 3. Review the diff of `ops.json` — that's what will run. | ||
| 4. Verify: `npx prisma-next migration check`. | ||
| 5. Commit `migration.ts`, `ops.json`, and `migration.json` together. | ||
|
|
||
| :::note[What's early] | ||
|
|
||
| The typed-builder wiring at the top of the filled example (deserializing the contract, building the `db` handle) is more ceremony than we want; expect it to shrink. On MongoDB, hand-authored data transforms currently use raw Mongo command shapes rather than the full typed builder. `rawSql` and the scaffolded placeholder flow shown here work today, end to end. | ||
|
|
||
| ::: | ||
|
|
||
| ## Prompt your coding agent | ||
|
|
||
| - "Fill in the placeholder in the latest migration: backfill `displayName` with the user's email prefix." | ||
| - "Add a data transform to this migration that normalizes existing `phone` values before the unique constraint." | ||
| - "Recompile the migration I just edited and show me the ops.json diff." | ||
|
|
||
| ## See also | ||
|
|
||
| - [Generating a migration](/orm/next/migrations/generating-a-migration): where the scaffold comes from | ||
| - [Applying a migration](/orm/next/migrations/applying-a-migration): run the edited migration | ||
| - [Data Migrations in Prisma Next](https://www.prisma.io/blog/data-migrations-in-prisma-next): the design story for `dataTransform` | ||
Oops, something went wrong.
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.