{"_id":"@soartec-lab/prisma-strong-migrations","_rev":"3-64ab9c67a84df38100f10134416e2d8f","name":"@soartec-lab/prisma-strong-migrations","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@soartec-lab/prisma-strong-migrations","version":"0.1.0","keywords":["prisma","migrations","postgresql","database","safety"],"author":{"name":"Shodai Suzuki"},"license":"MIT","_id":"@soartec-lab/prisma-strong-migrations@0.1.0","maintainers":[{"name":"soartec-lab","email":"shodaiconnection@gmail.com"}],"homepage":"https://github.com/soartec-lab/prisma-strong-migrations#readme","bugs":{"url":"https://github.com/soartec-lab/prisma-strong-migrations/issues"},"bin":{"psm":"bin/cli.js","prisma-strong-migrations":"bin/cli.js"},"dist":{"shasum":"523a9e3ca83499c1c910e20b492a4d46f4c03254","tarball":"https://registry.npmjs.org/@soartec-lab/prisma-strong-migrations/-/prisma-strong-migrations-0.1.0.tgz","fileCount":9,"integrity":"sha512-7Lk4vtieYerp99PP6G8YdS9dscxmNraMdUQd4M/8nQbPqeKnu0rhY7BfyZKUw73cfD3PVYQn4SYf3bLrW5NCaw==","signatures":[{"sig":"MEYCIQCeEFHslhJ8UFoGcpeYUnThW4yZ8R4xwgxk5m6VWDxsNAIhALb1TYLBzK+iX9wLzO0h1fsuouTvReBF6+JeYPNCOH3q","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":125863},"main":"./dist/index.mjs","type":"module","_from":"file:soartec-lab-prisma-strong-migrations-0.1.0.tgz","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"scripts":{"knip":"knip","test":"vp test","build":"vp pack","check":"vp check"},"_npmUser":{"name":"soartec-lab","email":"shodaiconnection@gmail.com"},"_resolved":"/tmp/2a6573eb0cd4fcb1e96010860b4fd20c/soartec-lab-prisma-strong-migrations-0.1.0.tgz","_integrity":"sha512-7Lk4vtieYerp99PP6G8YdS9dscxmNraMdUQd4M/8nQbPqeKnu0rhY7BfyZKUw73cfD3PVYQn4SYf3bLrW5NCaw==","repository":{"url":"git+https://github.com/soartec-lab/prisma-strong-migrations.git","type":"git"},"_npmVersion":"11.9.0","description":"Detect dangerous operations in Prisma migrations","directories":{},"_nodeVersion":"24.14.0","dependencies":{"chalk":"^5.0.0","commander":"^11.0.0","pgsql-ast-parser":"^12.0.0"},"_hasShrinkwrap":false,"devDependencies":{"knip":"^6.0.1","vite-plus":"latest","@types/node":"^25.5.0","@typescript/native-preview":"latest","@voidzero-dev/vite-plus-core":"latest"},"_npmOperationalInternal":{"tmp":"tmp/prisma-strong-migrations_0.1.0_1774171905117_0.3047624673085334","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@soartec-lab/prisma-strong-migrations","version":"0.2.0","keywords":["database","migrations","postgresql","prisma","safety"],"author":{"name":"Shodai Suzuki"},"license":"MIT","_id":"@soartec-lab/prisma-strong-migrations@0.2.0","maintainers":[{"name":"soartec-lab","email":"shodaiconnection@gmail.com"}],"homepage":"https://github.com/soartec-lab/prisma-strong-migrations#readme","bugs":{"url":"https://github.com/soartec-lab/prisma-strong-migrations/issues"},"bin":{"psm":"bin/cli.js","prisma-strong-migrations":"bin/cli.js"},"dist":{"shasum":"f20019cdc251102129414014d351b7e7ee8f4e58","tarball":"https://registry.npmjs.org/@soartec-lab/prisma-strong-migrations/-/prisma-strong-migrations-0.2.0.tgz","fileCount":9,"integrity":"sha512-++0RFep8NQTfinprE7USBTuuyg8Om6xAvhdktRGp+h7YxByKUkoa40ViMhMgdKGQQ1pgg0bGXkDhL6xHc2+3ng==","signatures":[{"sig":"MEUCICr6bSetVa3z8ZKb9tJhycNa5aL1UAHYjxMuCK+v0M3mAiEAvXOT7FL5kTMelNI5mO+7aSEfacHAy8B6rPjTVG3iGbE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":131734},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"b2c4095517d0785ab7b6805a6e303dbbb8091a5a","scripts":{"knip":"knip","test":"vp test","build":"vp pack","check":"vp check","prepublishOnly":"vp pack"},"_npmUser":{"name":"soartec-lab","email":"shodaiconnection@gmail.com"},"repository":{"url":"git+https://github.com/soartec-lab/prisma-strong-migrations.git","type":"git"},"_npmVersion":"11.11.0","description":"Detect dangerous operations in Prisma migrations","directories":{},"_nodeVersion":"25.8.1","dependencies":{"chalk":"^5.0.0","commander":"^11.0.0","pgsql-ast-parser":"^12.0.0"},"_hasShrinkwrap":false,"packageManager":"pnpm@10.32.1","devDependencies":{"knip":"^6.0.1","vite-plus":"latest","@types/node":"^25.5.0","@typescript/native-preview":"latest","@voidzero-dev/vite-plus-core":"latest"},"_npmOperationalInternal":{"tmp":"tmp/prisma-strong-migrations_0.2.0_1774181085095_0.09317853459560199","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@soartec-lab/prisma-strong-migrations","version":"0.3.0","description":"Detect dangerous operations in Prisma migrations","keywords":["database","migrations","postgresql","prisma","safety"],"license":"MIT","author":{"name":"Shodai Suzuki"},"repository":{"type":"git","url":"git+https://github.com/soartec-lab/prisma-strong-migrations.git"},"bin":{"prisma-strong-migrations":"bin/cli.js","psm":"bin/cli.js"},"type":"module","main":"./dist/index.mjs","types":"./dist/index.d.mts","exports":{".":{"import":"./dist/index.mjs","types":"./dist/index.d.mts"}},"scripts":{"test":"vp test","check":"vp check","build":"vp pack","prepublishOnly":"vp pack","knip":"knip"},"dependencies":{"chalk":"5.6.2","commander":"15.0.0","pgsql-ast-parser":"12.0.2"},"devDependencies":{"@types/node":"25.9.1","@typescript/native-preview":"7.0.0-dev.20260527.2","@voidzero-dev/vite-plus-core":"0.1.23","knip":"6.14.2","vite-plus":"0.1.23"},"packageManager":"pnpm@10.32.1","gitHead":"00363167054317eabbe8dfdd9718197201a6bcfa","_id":"@soartec-lab/prisma-strong-migrations@0.3.0","bugs":{"url":"https://github.com/soartec-lab/prisma-strong-migrations/issues"},"homepage":"https://github.com/soartec-lab/prisma-strong-migrations#readme","_nodeVersion":"25.8.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-UNqg5YY29tJOh3FWISLIqWZnM/fq2lHKTqTIgkPbMxZIR6zaFSy2wJduJPIBr/tq4HGnzkkPQHFJNB0/sR9k0Q==","shasum":"d036b756fbf8567f93fde34084e3a03668062077","tarball":"https://registry.npmjs.org/@soartec-lab/prisma-strong-migrations/-/prisma-strong-migrations-0.3.0.tgz","fileCount":9,"unpackedSize":144234,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDC8QVnwg6pZPzXBitci2PLIpcO77GrRI/tYCbu76vTiAIhAL8ZrQ6ina+tggOpW1BCB9J6hw1O9MGMsIsQt5/ophd2"}]},"_npmUser":{"name":"soartec-lab","email":"shodaiconnection@gmail.com"},"directories":{},"maintainers":[{"name":"soartec-lab","email":"shodaiconnection@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/prisma-strong-migrations_0.3.0_1780447572395_0.4377936321982394"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-22T09:31:45.013Z","modified":"2026-06-03T00:46:12.650Z","0.1.0":"2026-03-22T09:31:45.267Z","0.2.0":"2026-03-22T12:04:45.243Z","0.3.0":"2026-06-03T00:46:12.542Z"},"bugs":{"url":"https://github.com/soartec-lab/prisma-strong-migrations/issues"},"author":{"name":"Shodai Suzuki"},"license":"MIT","homepage":"https://github.com/soartec-lab/prisma-strong-migrations#readme","keywords":["database","migrations","postgresql","prisma","safety"],"repository":{"type":"git","url":"git+https://github.com/soartec-lab/prisma-strong-migrations.git"},"description":"Detect dangerous operations in Prisma migrations","maintainers":[{"name":"soartec-lab","email":"shodaiconnection@gmail.com"}],"readme":"# prisma-strong-migrations\n\nCatch unsafe migrations in development for Prisma + PostgreSQL\n\n✓ Detects potentially dangerous operations  \n✓ Prevents them from being applied by default  \n✓ Provides instructions on safer ways to do what you want\n\nInspired by [strong_migrations](https://github.com/ankane/strong_migrations) for Ruby on Rails.\n\n## Installation\n\n```bash\nnpm install prisma-strong-migrations --save-dev\n# or\nyarn add prisma-strong-migrations --dev\n# or\npnpm add prisma-strong-migrations --save-dev\n# or\nbun add prisma-strong-migrations --dev\n# or (vite-plus)\nvp add -D prisma-strong-migrations\n```\n\n## How It Works\n\nWhen you create a migration that's potentially dangerous, you'll see an error message like:\n\n```\nprisma/migrations/20240320_remove_user_name/migration.sql\n\nerror [removeColumn] line 1\n  Removing column \"name\" from table \"users\"\n\n  ❌ Bad: Removing a column may cause application errors.\n         If you deploy the migration before updating your application code,\n         requests handled by old instances will fail.\n\n  ✅ Good: Follow these steps:\n     1. Remove all usages of 'name' field from your code\n     2. Run 'npx prisma generate' to update Prisma Client\n     3. Deploy the code changes\n     4. Then apply this migration with a disable comment\n\n  To skip this check, add above the statement:\n     -- prisma-strong-migrations-disable-next-line removeColumn\n────────────────────────────────────────────────────────────\n\n✗ 1 error\n\n❌ Migration check failed.\n```\n\n## Comparison\n\n| Feature                                      | prisma-strong-migrations | squawk | Prisma built-in    |\n| -------------------------------------------- | ------------------------ | ------ | ------------------ |\n| Prisma-specific rules                        | ✅ 13 rules              | ❌     | ❌                 |\n| Auto-fix (`--fix`)                           | ✅ 6 rules               | ❌     | ❌                 |\n| Custom rules (JS/TS)                         | ✅                       | ❌     | ❌                 |\n| `migrate dev` / `migrate deploy` integration | ✅                       | ❌     | ✅                 |\n| Inline skip with audit trail                 | ✅                       | ❌     | ❌                 |\n| Total rules                                  | 38                       | ~26    | syntax errors only |\n\n[squawk](https://squawkhq.com/) is a general-purpose PostgreSQL SQL linter. It catches common dangerous patterns but has no awareness of Prisma's migration conventions — such as the implicit transaction wrapper, `CONCURRENTLY` requirements, or Prisma-managed tables like `_AToB` join tables.\n\n## Usage\n\n### Recommended Workflow\n\n```bash\n# 1. Modify your schema\nvim prisma/schema.prisma\n\n# 2. Create migration without applying\nnpx prisma migrate dev --create-only --name add_feature\n\n# 3. Check the generated SQL\nnpx prisma-strong-migrations check\n\n# 4a. If safe, apply the migration\nnpx prisma migrate dev\n\n# 4b. If issues found:\n#     - Fix using the suggested safe approach, OR\n#     - Add disable comment if intentional\n```\n\n### Adopting in an Existing Project\n\nWhen introducing this tool to an already-running application, the first local environment setup can be painful: all existing migration files will be pending in the fresh database, and they'll trigger a flood of errors even though those migrations are already safely running in production.\n\nUse `--force` to skip safety checks and apply all migrations as-is:\n\n```bash\n# Apply all existing migrations without safety checks (local setup only)\nnpx prisma-strong-migrations migrate dev --force\n# or\nnpx prisma-strong-migrations migrate deploy --force\n```\n\n> **Warning:** `--force` disables all safety checks. Use it only for local development environment setup, never in production CI/CD pipelines.\n\n## Commands\n\nAll commands are available as `prisma-strong-migrations <command>` or the `psm` shorthand (e.g. `npx psm migrate dev`).\n\n### `check [migration]`\n\nCheck migration files for dangerous operations.\n\n```bash\n# Check all migrations\nnpx prisma-strong-migrations check\n\n# Check a specific file\nnpx prisma-strong-migrations check prisma/migrations/20240320_add_index/migration.sql\n```\n\n| Option                  | Description                                                         |\n| ----------------------- | ------------------------------------------------------------------- |\n| `-f, --format <format>` | Output format: `console` (default) or `json`                        |\n| `-c, --config <path>`   | Path to config file (default: `prisma-strong-migrations.config.js`) |\n| `--no-fail`             | Always exit with code 0, even if errors are found                   |\n| `--fix`                 | Automatically rewrite auto-fixable issues in the SQL files          |\n\nThe `--format json` output shape:\n\n```json\n{\n  \"errors\": [\n    {\n      \"ruleName\": \"addIndex\",\n      \"severity\": \"error\",\n      \"migrationPath\": \"prisma/migrations/...\",\n      \"line\": 3,\n      \"message\": \"...\",\n      \"suggestion\": \"...\",\n      \"fixable\": true\n    }\n  ],\n  \"warnings\": [],\n  \"totalErrors\": 1,\n  \"totalWarnings\": 0\n}\n```\n\n### `migrate dev`\n\nCreate a migration with `--create-only`, check it, then apply if safe. Wraps `prisma migrate dev`.\n\n```bash\nnpx prisma-strong-migrations migrate dev\n```\n\n| Option                | Description                                                |\n| --------------------- | ---------------------------------------------------------- |\n| `--name <name>`       | Migration name, passed to Prisma                           |\n| `--schema <path>`     | Path to `schema.prisma`, passed to Prisma                  |\n| `-c, --config <path>` | Path to config file                                        |\n| `--fix`               | Auto-fix issues and exit — re-run without `--fix` to apply |\n| `--force`             | Skip all safety checks (local dev setup only)              |\n\n**`--fix` workflow:** `--fix` rewrites SQL files only — it does not apply the migration. Re-run without `--fix` to apply after reviewing the changes.\n\n```bash\n# Step 1: auto-fix the SQL\nnpx psm migrate dev --fix\n# ✔ Auto-fixed 1 issue(s) in prisma/migrations/.../migration.sql\n# ✅ Auto-fix applied. Run the same command again (without --fix) to apply the migration.\n\n# Step 2: review the rewritten SQL, then apply\nnpx psm migrate dev\n```\n\n### `migrate deploy`\n\nCheck all migrations, then run `prisma migrate deploy` if all checks pass. Wraps `prisma migrate deploy`.\n\n```bash\nnpx prisma-strong-migrations migrate deploy\n```\n\n| Option                | Description                                   |\n| --------------------- | --------------------------------------------- |\n| `-c, --config <path>` | Path to config file                           |\n| `--force`             | Skip all safety checks (local dev setup only) |\n\n**`migrate dev` vs `migrate deploy`:**\n\n|                        | `migrate dev`                  | `migrate deploy`         |\n| ---------------------- | ------------------------------ | ------------------------ |\n| Intended environment   | Local development              | Production / staging     |\n| Creates migration file | Yes (via `--create-only`)      | No (apply only)          |\n| Interactive prompts    | Yes                            | No                       |\n| Checks                 | Newly generated migration      | All pending migrations   |\n| Typical usage          | `npx psm migrate dev --name …` | `npx psm migrate deploy` |\n\n### `init`\n\nInteractive setup wizard. Run once when introducing the tool to a project.\n\n```bash\nnpx prisma-strong-migrations init\n```\n\n1. Creates `prisma-strong-migrations.config.js` with all options and defaults\n2. Scans `package.json` scripts and interactively offers to replace `prisma migrate dev` / `prisma migrate deploy` with the wrapped commands\n\n### `init-rule <name>`\n\nGenerate a custom rule template in `./prisma-strong-migrations-rules/<name>.js`.\n\n```bash\nnpx prisma-strong-migrations init-rule my-rule\n```\n\n## Why Prisma-Specific Rules?\n\nGeneral-purpose SQL linters catch many dangerous patterns, but Prisma has conventions that a generic tool cannot know about:\n\n- **Implicit transactions** — Prisma wraps every migration file in `BEGIN/COMMIT` by default. Operations that cannot run inside a transaction (e.g. `CREATE INDEX CONCURRENTLY`) require a `-- prisma-migrate-disable-next-transaction` header, and mixing multiple statements in such a file removes rollback protection.\n- **Prisma-managed tables** — Join tables like `_CategoryToPost` are fully controlled by Prisma. Direct modifications break its relation management.\n- **ENUM recreation** — PostgreSQL has no `ALTER TYPE … DROP VALUE`. Prisma works around this by recreating the type, which fails if existing rows still hold the removed value.\n- **`@updatedAt` management** — Adding a DB-level `DEFAULT` or trigger on an `@updatedAt` column conflicts with Prisma Client's own update logic.\n\nThese 13 Prisma-specific rules cover what generic tools leave undetected.\n\n## Checks\n\nPotentially dangerous operations:\n\n- [prisma-strong-migrations](#prisma-strong-migrations)\n  - [Installation](#installation)\n  - [How It Works](#how-it-works)\n  - [Usage](#usage)\n    - [Recommended Workflow](#recommended-workflow)\n  - [Checks](#checks)\n    - [Removing a column](#removing-a-column)\n      - [Bad](#bad)\n      - [Good](#good)\n    - [Renaming a column](#renaming-a-column)\n      - [Bad](#bad-1)\n      - [Good](#good-1)\n    - [Renaming a table](#renaming-a-table)\n      - [Bad](#bad-2)\n      - [Good](#good-2)\n    - [Changing the type of a column](#changing-the-type-of-a-column)\n      - [Bad](#bad-3)\n      - [Good](#good-3)\n    - [Adding an index non-concurrently](#adding-an-index-non-concurrently)\n      - [Bad](#bad-4)\n      - [Good](#good-4)\n    - [Removing an index non-concurrently](#removing-an-index-non-concurrently)\n      - [Bad](#bad-5)\n      - [Good](#good-5)\n    - [Adding a foreign key](#adding-a-foreign-key)\n      - [Bad](#bad-6)\n      - [Good](#good-6)\n    - [Adding a check constraint](#adding-a-check-constraint)\n      - [Bad](#bad-7)\n      - [Good](#good-7)\n    - [Adding a unique constraint](#adding-a-unique-constraint)\n      - [Bad](#bad-8)\n      - [Good](#good-8)\n    - [Adding an exclusion constraint](#adding-an-exclusion-constraint)\n      - [Bad](#bad-9)\n      - [Good](#good-9)\n    - [Setting NOT NULL on an existing column](#setting-not-null-on-an-existing-column)\n      - [Bad](#bad-10)\n      - [Good](#good-10)\n    - [Adding a json column](#adding-a-json-column)\n      - [Bad](#bad-11)\n      - [Good](#good-11)\n    - [Adding an array column without NOT NULL](#adding-an-array-column-without-not-null)\n      - [Bad](#bad-12)\n      - [Good](#good-12)\n    - [Adding a column with a volatile default value](#adding-a-column-with-a-volatile-default-value)\n      - [Bad](#bad-13)\n      - [Good](#good-13)\n    - [Adding an auto-incrementing column](#adding-an-auto-incrementing-column)\n      - [Bad](#bad-14)\n      - [Good](#good-14)\n    - [Adding a stored generated column](#adding-a-stored-generated-column)\n      - [Bad](#bad-15)\n      - [Good](#good-15)\n    - [Renaming a schema](#renaming-a-schema)\n      - [Bad](#bad-16)\n      - [Good](#good-16)\n    - [Keeping non-unique indexes to three columns or less](#keeping-non-unique-indexes-to-three-columns-or-less)\n      - [Bad](#bad-17)\n      - [Good](#good-17)\n  - [Skipping Checks](#skipping-checks)\n    - [Skip multiple rules](#skip-multiple-rules)\n    - [Skip all rules for a statement](#skip-all-rules-for-a-statement)\n  - [Configuration](#configuration)\n  - [Custom Rules](#custom-rules)\n  - [CI Integration](#ci-integration)\n  - [Development](#development)\n    - [Using devcontainer (Recommended)](#using-devcontainer-recommended)\n    - [Available Commands](#available-commands)\n  - [Documentation](#documentation)\n  - [Credits](#credits)\n  - [License](#license)\n\nBest practices:\n\n- [Keeping non-unique indexes to three columns or less](#keeping-non-unique-indexes-to-three-columns-or-less)\n\n---\n\n### Removing a column\n\n#### Bad\n\nRemoving a column may cause application errors. If you deploy the migration before updating your application code, requests handled by old application instances will fail.\n\n```sql\nALTER TABLE \"users\" DROP COLUMN \"name\";\n```\n\n#### Good\n\n1. Add `@ignore` to the `name` field in `schema.prisma` so Prisma Client stops accessing it, and remove remaining usages from your code\n2. Run `npx prisma generate` and deploy the code changes\n3. Then apply this migration:\n\n```prisma\nmodel User {\n  name String @ignore // excluded from Prisma Client before the column is dropped\n}\n```\n\n```sql\n-- prisma-strong-migrations-disable-next-line removeColumn\n-- Reason: All references removed in PR #123\nALTER TABLE \"users\" DROP COLUMN \"name\";\n```\n\n---\n\n### Renaming a column\n\n#### Bad\n\nRenaming a column that's in use will cause errors in your application.\n\n```sql\nALTER TABLE \"users\" RENAME COLUMN \"name\" TO \"full_name\";\n```\n\n#### Good\n\nA safer approach is to:\n\n1. Create a new column\n2. Write to both columns in your application\n3. Backfill data from the old column to the new column\n4. Move reads from the old column to the new column\n5. Stop writing to the old column\n6. Add `@ignore` to the old field, then drop the old column\n\n---\n\n### Renaming a table\n\n#### Bad\n\nRenaming a table that's in use will cause errors in your application.\n\n```sql\nALTER TABLE \"users\" RENAME TO \"customers\";\n```\n\n#### Good\n\nA safer approach is to:\n\n1. Create a new table\n2. Write to both tables in your application\n3. Backfill data from the old table to the new table\n4. Move reads from the old table to the new table\n5. Stop writing to the old table\n6. Add `@@ignore` to the old model, then drop the old table\n\n---\n\n### Changing the type of a column\n\n#### Bad\n\nChanging the type of a column causes the entire table to be rewritten. During this time, reads and writes are blocked.\n\n```sql\nALTER TABLE \"users\" ALTER COLUMN \"age\" TYPE bigint;\n```\n\nSome changes don't require a table rewrite and are safe in Postgres:\n\n| Type           | Safe Changes                                            |\n| -------------- | ------------------------------------------------------- |\n| `varchar(n)`   | Increasing or removing limit, changing to `text`        |\n| `text`         | Changing to `varchar` with no limit                     |\n| `numeric(p,s)` | Increasing precision at same scale                      |\n| `timestamp`    | Changing to `timestamptz` when session time zone is UTC |\n\n#### Good\n\nFor other type changes, a safer approach is to:\n\n1. Create a new column\n2. Write to both columns in your application\n3. Backfill data from the old column to the new column\n4. Move reads from the old column to the new column\n5. Stop writing to the old column\n6. Add `@ignore` to the old field, then drop the old column\n\n---\n\n### Adding an index non-concurrently\n\n#### Bad\n\nAdding an index non-concurrently blocks writes.\n\n```sql\nCREATE INDEX \"users_email_idx\" ON \"users\"(\"email\");\n```\n\n#### Good\n\nAdd indexes concurrently. Because Prisma wraps migrations in transactions by default, you must disable the transaction for this migration file.\n\n1. Generate migration file only:\n   ```bash\n   npx prisma migrate dev --create-only --name add_users_email_index\n   ```\n2. Edit the generated file — add `-- prisma-migrate-disable-next-transaction` as the first line, then add `CONCURRENTLY`:\n   ```sql\n   -- prisma-migrate-disable-next-transaction\n   CREATE INDEX CONCURRENTLY \"users_email_idx\" ON \"users\"(\"email\");\n   ```\n3. Apply the migration:\n   ```bash\n   npx prisma migrate dev\n   ```\n\n> **Note:** `-- prisma-migrate-disable-next-transaction` disables transaction protection for the **entire file**. Keep this migration file minimal — ideally one statement only.\n\n---\n\n### Removing an index non-concurrently\n\n#### Bad\n\nRemoving an index non-concurrently blocks writes.\n\n```sql\nDROP INDEX \"users_email_idx\";\n```\n\n#### Good\n\nRemove indexes concurrently.\n\n```sql\nDROP INDEX CONCURRENTLY \"users_email_idx\";\n```\n\n---\n\n### Adding a foreign key\n\n#### Bad\n\nAdding a foreign key blocks writes on both tables.\n\n```sql\nALTER TABLE \"posts\"\nADD CONSTRAINT \"posts_user_id_fkey\"\nFOREIGN KEY (\"user_id\") REFERENCES \"users\"(\"id\");\n```\n\n#### Good\n\nAdd the foreign key without validating existing rows, then validate in a separate migration.\n\n**Migration 1:**\n\n```sql\nALTER TABLE \"posts\"\nADD CONSTRAINT \"posts_user_id_fkey\"\nFOREIGN KEY (\"user_id\") REFERENCES \"users\"(\"id\")\nNOT VALID;\n```\n\n**Migration 2:**\n\n```sql\nALTER TABLE \"posts\"\nVALIDATE CONSTRAINT \"posts_user_id_fkey\";\n```\n\n---\n\n### Adding a check constraint\n\n#### Bad\n\nAdding a check constraint blocks reads and writes while every row is checked.\n\n```sql\nALTER TABLE \"products\"\nADD CONSTRAINT \"products_price_check\"\nCHECK (price > 0);\n```\n\n#### Good\n\nAdd the check constraint without validating existing rows, then validate in a separate migration.\n\n**Migration 1:**\n\n```sql\nALTER TABLE \"products\"\nADD CONSTRAINT \"products_price_check\"\nCHECK (price > 0)\nNOT VALID;\n```\n\n**Migration 2:**\n\n```sql\nALTER TABLE \"products\"\nVALIDATE CONSTRAINT \"products_price_check\";\n```\n\n---\n\n### Adding a unique constraint\n\n#### Bad\n\nAdding a unique constraint creates a unique index, which blocks reads and writes.\n\n```sql\nALTER TABLE \"users\"\nADD CONSTRAINT \"users_email_unique\"\nUNIQUE (\"email\");\n```\n\n#### Good\n\nCreate a unique index concurrently, then use it for the constraint.\n\n**Migration 1:**\n\n```sql\nCREATE UNIQUE INDEX CONCURRENTLY \"users_email_idx\" ON \"users\"(\"email\");\n```\n\n**Migration 2:**\n\n```sql\nALTER TABLE \"users\"\nADD CONSTRAINT \"users_email_unique\"\nUNIQUE USING INDEX \"users_email_idx\";\n```\n\n---\n\n### Adding an exclusion constraint\n\n#### Bad\n\nAdding an exclusion constraint blocks reads and writes while every row is checked.\n\n```sql\nALTER TABLE \"reservations\"\nADD CONSTRAINT \"reservations_no_overlap\"\nEXCLUDE USING gist (room_id WITH =, tsrange(start_time, end_time) WITH &&);\n```\n\n#### Good\n\nThere's no safe way to add an exclusion constraint (they cannot be marked `NOT VALID`). Consider:\n\n- Running during a maintenance window\n- Using application-level validation instead\n\n---\n\n### Setting NOT NULL on an existing column\n\n#### Bad\n\nSetting `NOT NULL` on an existing column blocks reads and writes while every row is checked.\n\n```sql\nALTER TABLE \"users\" ALTER COLUMN \"email\" SET NOT NULL;\n```\n\n#### Good\n\nAdd a check constraint first, then set `NOT NULL`.\n\n**Migration 1:**\n\n```sql\nALTER TABLE \"users\"\nADD CONSTRAINT \"users_email_not_null\"\nCHECK (\"email\" IS NOT NULL)\nNOT VALID;\n```\n\n**Migration 2:**\n\n```sql\nALTER TABLE \"users\" VALIDATE CONSTRAINT \"users_email_not_null\";\nALTER TABLE \"users\" ALTER COLUMN \"email\" SET NOT NULL;\nALTER TABLE \"users\" DROP CONSTRAINT \"users_email_not_null\";\n```\n\nAlso give the column a default value if it has none — otherwise you cannot drop it safely later: once its field is marked `@ignore`, Prisma Client omits it from INSERTs and those inserts fail without a default.\n\n---\n\n### Adding a json column\n\n#### Bad\n\nIn Postgres, there's no equality operator for the `json` column type, which can cause errors for `SELECT DISTINCT` queries.\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"metadata\" json;\n```\n\n#### Good\n\nUse `jsonb` instead.\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"metadata\" jsonb;\n```\n\nIn Prisma schema:\n\n```prisma\nmodel User {\n  metadata Json @db.JsonB\n}\n```\n\n---\n\n### Adding an array column without NOT NULL\n\n#### Bad\n\nPrisma list fields like `savedColors String[]` are always non-nullable, but the generated SQL omits `NOT NULL` (with or without a default). The column ends up nullable in the database while Prisma treats it as non-nullable, so `NULL` can slip in.\n\n```sql\nALTER TABLE \"LandingPage\" ADD COLUMN \"savedColors\" TEXT[] DEFAULT ARRAY[]::TEXT[];\nALTER TABLE \"Post\" ADD COLUMN \"tags\" TEXT[];\n```\n\n#### Good\n\nAdd `NOT NULL`. When a default exists, keep it; otherwise add an empty-array default so existing rows stay valid. This rule is auto-fixable (`--fix`).\n\n```sql\nALTER TABLE \"LandingPage\" ADD COLUMN \"savedColors\" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[];\nALTER TABLE \"Post\" ADD COLUMN \"tags\" TEXT[] NOT NULL DEFAULT '{}';\n```\n\n---\n\n### Adding a column with a volatile default value\n\n#### Bad\n\nAdding a column with a volatile default value (like `gen_random_uuid()` or `now()`) causes the entire table to be rewritten.\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"uuid\" uuid DEFAULT gen_random_uuid();\n```\n\n#### Good\n\nAdd the column without a default value, then change the default.\n\n**Migration 1:**\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"uuid\" uuid;\nALTER TABLE \"users\" ALTER COLUMN \"uuid\" SET DEFAULT gen_random_uuid();\n```\n\nThen backfill existing rows in batches (outside a transaction):\n\n```sql\nUPDATE \"users\" SET \"uuid\" = gen_random_uuid() WHERE \"uuid\" IS NULL;\n```\n\n---\n\n### Adding an auto-incrementing column\n\n#### Bad\n\nAdding an auto-incrementing column (`SERIAL` or `BIGSERIAL`) causes the entire table to be rewritten.\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"id\" SERIAL;\n```\n\n#### Good\n\nCreate a new table and migrate the data with the same steps as [renaming a table](#renaming-a-table).\n\n---\n\n### Adding a stored generated column\n\n#### Bad\n\nAdding a stored generated column causes the entire table to be rewritten.\n\n```sql\nALTER TABLE \"users\"\nADD COLUMN \"full_name\" text\nGENERATED ALWAYS AS (first_name || ' ' || last_name) STORED;\n```\n\n#### Good\n\nAdd a non-generated column and use triggers or application logic instead.\n\n---\n\n### Renaming a schema\n\n#### Bad\n\nRenaming a schema that's in use will cause errors in your application.\n\n```sql\nALTER SCHEMA \"old_schema\" RENAME TO \"new_schema\";\n```\n\n#### Good\n\nA safer approach is to:\n\n1. Create a new schema\n2. Write to both schemas in your application\n3. Backfill data from the old schema to the new schema\n4. Move reads from the old schema to the new schema\n5. Stop writing to the old schema\n6. Drop the old schema\n\n---\n\n### Dropping a table\n\n#### Bad\n\nDropping a table that's still referenced by application code will cause errors. All data is permanently lost.\n\n```sql\nDROP TABLE \"users\";\n```\n\n#### Good\n\n1. Add `@@ignore` to the model in `schema.prisma` so Prisma Client stops using it, and remove remaining references from your code\n2. Run `npx prisma generate` and deploy the application\n3. Then apply this migration:\n\n```sql\n-- prisma-strong-migrations-disable-next-line dropTable\n-- Reason: Model removed in PR #456, all references cleaned up\nDROP TABLE \"users\";\n```\n\n---\n\n### Disabling transaction protection\n\n#### Bad\n\nUsing `-- prisma-migrate-disable-next-transaction` disables rollback for the entire file. Mixing other DDL statements risks a partial state on failure.\n\n```sql\n-- prisma-migrate-disable-next-transaction\nCREATE INDEX CONCURRENTLY \"users_email_idx\" ON \"users\"(\"email\");\nALTER TABLE \"users\" ADD COLUMN \"bio\" text;\n```\n\n#### Good\n\nKeep the file to one statement only when disabling transactions.\n\n```sql\n-- prisma-migrate-disable-next-transaction\nCREATE INDEX CONCURRENTLY \"users_email_idx\" ON \"users\"(\"email\");\n```\n\n---\n\n### Adding a NOT NULL column without a default value\n\n#### Bad\n\nAdding a NOT NULL column without a default value fails if the table has existing rows. It also blocks a safe removal later: once you mark its field `@ignore` to drop it, Prisma Client INSERTs that omit the column will fail without a default.\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"status\" text NOT NULL;\n```\n\n#### Good\n\nGive the column a default value (or add `@default(...)` in the Prisma schema before generating the migration).\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"status\" text NOT NULL DEFAULT 'active';\n```\n\n---\n\n### Truncating a table\n\n#### Bad\n\n`TRUNCATE` acquires an `AccessExclusiveLock` and deletes all rows. Locks propagate to foreign-key-referencing tables, and accidental execution in production is catastrophic.\n\n```sql\nTRUNCATE TABLE \"users\";\n```\n\n#### Good\n\nDelete rows in application code where scope is controlled:\n\n```typescript\nawait prisma.users.deleteMany({});\n```\n\n---\n\n### Disabling triggers\n\n#### Bad\n\n`DISABLE TRIGGER` turns off foreign key and other constraint triggers, which can silently corrupt data integrity. If the migration fails after disabling, triggers remain off.\n\n```sql\nALTER TABLE \"users\" DISABLE TRIGGER ALL;\n```\n\n#### Good\n\nDo not disable triggers in migrations. If unavoidable, always re-enable before the migration ends.\n\n---\n\n### Running VACUUM inside a migration\n\n#### Bad\n\n`VACUUM` cannot execute inside a transaction block. Prisma wraps migrations in `BEGIN/COMMIT`, so this always fails and marks the migration as broken.\n\n```sql\nVACUUM ANALYZE \"users\";\n```\n\n#### Good\n\nRun VACUUM as a separate maintenance task outside migrations:\n\n```bash\npsql -c \"VACUUM ANALYZE \\\"users\\\";\"\n```\n\n---\n\n### Moving a table to another tablespace\n\n#### Bad\n\n`SET TABLESPACE` physically relocates the table, holding an `AccessExclusiveLock` for the entire duration. On large tables this can block production for minutes.\n\n```sql\nALTER TABLE \"users\" SET TABLESPACE pg_default;\n```\n\n#### Good\n\nRun this operation in a scheduled maintenance window, not in a regular migration.\n\n---\n\n### Clustering a table\n\n#### Bad\n\n`CLUSTER` physically rewrites the table in index order, holding an `AccessExclusiveLock` throughout. On large tables this blocks all reads and writes for a long time.\n\n```sql\nCLUSTER \"users\" USING \"users_pkey\";\n```\n\n#### Good\n\nUse **pg_repack** to reorder rows without an exclusive lock, or run `CLUSTER` during a low-traffic maintenance window.\n\n---\n\n### Creating a table from a SELECT query\n\n#### Bad\n\n`CREATE TABLE AS SELECT` copies all rows inside the migration. On large tables this takes a long time and can cause timeouts.\n\n```sql\nCREATE TABLE \"users_backup\" AS SELECT * FROM \"users\";\n```\n\n#### Good\n\nRun data copies outside migrations using `pg_dump` or a separate backfill job.\n\n---\n\n### Keeping non-unique indexes to three columns or less\n\n#### Bad\n\nAdding a non-unique index with more than three columns rarely improves performance.\n\n```sql\nCREATE INDEX \"users_multi_idx\" ON \"users\"(\"a\", \"b\", \"c\", \"d\");\n```\n\n#### Good\n\nStart an index with columns that narrow down the results the most.\n\n```sql\nCREATE INDEX CONCURRENTLY \"users_idx\" ON \"users\"(\"d\", \"b\");\n```\n\n---\n\n### Using CONCURRENTLY without disabling the transaction\n\n#### Bad\n\nPrisma wraps every migration in a transaction. PostgreSQL does not allow `CONCURRENTLY` operations inside a transaction block, so the migration will fail at runtime.\n\n```sql\nCREATE INDEX CONCURRENTLY \"idx_users_email\" ON \"users\"(\"email\");\n```\n\n#### Good\n\nAdd `-- prisma-migrate-disable-next-transaction` as the first line of the file and keep the file to one statement:\n\n```sql\n-- prisma-migrate-disable-next-transaction\nCREATE INDEX CONCURRENTLY \"idx_users_email\" ON \"users\"(\"email\");\n```\n\n---\n\n### Adding NOT VALID and VALIDATE CONSTRAINT in the same file\n\n#### Bad\n\n`NOT VALID` is meant to defer the expensive table scan to a later `VALIDATE CONSTRAINT` step. Putting both in the same file negates the optimization — the full table scan still occurs.\n\n```sql\nALTER TABLE \"orders\" ADD CONSTRAINT \"orders_user_id_fkey\"\n  FOREIGN KEY (\"user_id\") REFERENCES \"users\"(\"id\") NOT VALID;\nALTER TABLE \"orders\" VALIDATE CONSTRAINT \"orders_user_id_fkey\";\n```\n\n#### Good\n\nSplit into two separate migration files:\n\n```sql\n-- migration_1.sql\nALTER TABLE \"orders\" ADD CONSTRAINT \"orders_user_id_fkey\"\n  FOREIGN KEY (\"user_id\") REFERENCES \"users\"(\"id\") NOT VALID;\n\n-- migration_2.sql (deploy after migration_1)\nALTER TABLE \"orders\" VALIDATE CONSTRAINT \"orders_user_id_fkey\";\n```\n\n---\n\n### Multiple statements with disabled transaction\n\n#### Bad\n\nWhen `-- prisma-migrate-disable-next-transaction` is present, the whole file runs without a transaction. If any statement fails, earlier statements cannot be rolled back.\n\n```sql\n-- prisma-migrate-disable-next-transaction\nCREATE INDEX CONCURRENTLY \"idx_a\" ON \"users\"(\"email\");\nALTER TABLE \"users\" ADD COLUMN \"name\" text;\n```\n\n#### Good\n\nKeep files with a disabled transaction to one SQL statement only:\n\n```sql\n-- prisma-migrate-disable-next-transaction\nCREATE INDEX CONCURRENTLY \"idx_a\" ON \"users\"(\"email\");\n```\n\n---\n\n### UPDATE without WHERE clause\n\n#### Bad\n\nAn `UPDATE` without a `WHERE` clause updates every row in the table, which can lock the table for a long time on large datasets.\n\n```sql\nUPDATE \"users\" SET \"status\" = 'active';\n```\n\n#### Good\n\nAdd a `WHERE` clause to limit the affected rows:\n\n```sql\nUPDATE \"users\" SET \"status\" = 'active' WHERE \"status\" IS NULL;\n```\n\n---\n\n### DELETE FROM without WHERE clause\n\n#### Bad\n\nA `DELETE FROM` without a `WHERE` clause deletes every row in the table.\n\n```sql\nDELETE FROM \"sessions\";\n```\n\n#### Good\n\nAdd a `WHERE` clause to limit the deleted rows:\n\n```sql\nDELETE FROM \"sessions\" WHERE \"expires_at\" < NOW();\n```\n\n---\n\n### Mixing schema changes and data backfill\n\n#### Bad\n\nCombining `ALTER TABLE` schema changes with `UPDATE` backfill in one migration can cause long-running locks on large tables.\n\n```sql\nALTER TABLE \"users\" ADD COLUMN \"full_name\" text;\nUPDATE \"users\" SET \"full_name\" = first_name || ' ' || last_name;\n```\n\n#### Good\n\nSplit into two separate migration files:\n\n```sql\n-- migration_1.sql: schema change only\nALTER TABLE \"users\" ADD COLUMN \"full_name\" text;\n\n-- migration_2.sql: backfill only\nUPDATE \"users\" SET \"full_name\" = first_name || ' ' || last_name;\n```\n\n---\n\n### Removing an ENUM value\n\nPrisma recreates the ENUM type when removing a value. If existing data contains the removed value, the migration will fail.\n\n```sql\n-- ❌ Bad: will fail if existing rows have the removed value\nALTER TYPE \"Role\" RENAME TO \"Role_old\";\nCREATE TYPE \"Role\" AS ENUM ('ADMIN', 'USER');\nALTER TABLE \"User\" ALTER COLUMN \"role\" TYPE \"Role\" USING \"role\"::text::\"Role\";\nDROP TYPE \"Role_old\";\n```\n\n```sql\n-- ✅ Good: backfill data before removing the enum value\nUPDATE \"User\" SET \"role\" = 'ADMIN' WHERE \"role\" = 'MEMBER';\n-- then apply the migration after deploying code that no longer references MEMBER\n```\n\n---\n\n### Creating an implicit M2M join table\n\nPrisma generates `_XToY` tables for implicit M2M relations. These tables are limited to columns `A` and `B`, cannot hold extra fields, and use opaque naming. Explicit M2M models are clearer and more flexible.\n\n#### Bad\n\n```sql\n-- Prisma generates this for implicit M2M — columns A and B only\nCREATE TABLE \"_CategoryToPost\" (\n    \"A\" integer NOT NULL,\n    \"B\" integer NOT NULL\n);\n```\n\n#### Good\n\n```prisma\n-- ✅ Convert to explicit M2M in schema.prisma\nmodel CategoryOnPost {\n  post       Post     @relation(fields: [postId], references: [id])\n  postId     Int\n  category   Category @relation(fields: [categoryId], references: [id])\n  categoryId Int\n  assignedAt DateTime @default(now())\n\n  @@id([postId, categoryId])\n}\n```\n\n---\n\n### Directly modifying an implicit M2M table\n\nPrisma auto-manages join tables named `_AToB`. Direct modifications break Prisma's relation management.\n\n```sql\n-- ❌ Bad: bypasses Prisma's M2M management\nALTER TABLE \"_CategoryToPost\" ADD COLUMN \"extra\" TEXT;\n```\n\n```prisma\n-- ✅ Good: convert to explicit M2M in schema.prisma\nmodel CategoriesOnPosts {\n  postId     Int\n  categoryId Int\n  post       Post     @relation(fields: [postId], references: [id])\n  category   Category @relation(fields: [categoryId], references: [id])\n  @@id([postId, categoryId])\n}\n```\n\n---\n\n### Using SERIAL (32-bit) for a primary key\n\n`SERIAL` has a maximum of ~2.1 billion rows. Migrating to `BigInt` later is nearly impossible in production.\n\n```sql\n-- ❌ Bad: 32-bit integer, max ~2.1 billion rows\nCREATE TABLE \"User\" (\n    \"id\" SERIAL NOT NULL,\n    ...\n);\n```\n\n```prisma\n-- ✅ Good: use BigInt or UUID v7 in schema.prisma\nmodel User {\n  id BigInt @id @default(autoincrement())\n  -- or\n  id String @id @default(uuid(7))\n}\n```\n\n---\n\n### Dropping the default from an id column\n\nDropping a database-level default from the `id` column can break ID generation for inserts that bypass Prisma Client.\n\n```sql\n-- ❌ Bad: breaks ID generation if the column relies on a DB default\nALTER TABLE \"User\" ALTER COLUMN \"id\" DROP DEFAULT;\n```\n\n```prisma\n-- ✅ Good: change ID strategy in schema.prisma and let Prisma regenerate\nmodel User {\n  id String @id @default(uuid(7))\n}\n```\n\n---\n\n### Setting a DB-level default or trigger on @updatedAt\n\nPrisma's `@updatedAt` manages the column automatically. Adding a DB-level default or trigger conflicts with Prisma's updates.\n\n```sql\n-- ❌ Bad: conflicts with Prisma's @updatedAt management\nALTER TABLE \"User\" ALTER COLUMN \"updatedAt\" SET DEFAULT NOW();\nCREATE TRIGGER set_updated_at BEFORE UPDATE ON \"User\"\n  FOR EACH ROW EXECUTE FUNCTION trigger_set_updated_at();\n```\n\n```prisma\n-- ✅ Good: let Prisma manage it exclusively via @updatedAt\nmodel User {\n  updatedAt DateTime @updatedAt\n}\n```\n\n---\n\n## Skipping Checks\n\nIf you've reviewed the warning and want to proceed anyway, add a disable comment:\n\n```sql\n-- prisma-strong-migrations-disable-next-line removeColumn\n-- Reason: Column deprecated, no references found\nALTER TABLE \"users\" DROP COLUMN \"name\";\n```\n\n### Skip multiple rules\n\n```sql\n-- prisma-strong-migrations-disable-next-line removeColumn renameColumn\nALTER TABLE \"users\" DROP COLUMN \"name\";\n```\n\n### Skip all rules for a statement\n\n```sql\n-- prisma-strong-migrations-disable-next-line\nALTER TABLE \"users\" DROP COLUMN \"name\";\n```\n\n## Approving Checks\n\nWhen you have reviewed an operation and handled it properly, mark it with\n`approve-next-line` to record that it was approved:\n\n```sql\n-- prisma-strong-migrations-approve-next-line removeColumn\n-- Reason: field already removed from the app and deployed\nALTER TABLE \"users\" DROP COLUMN \"name\";\n```\n\nApproved findings are counted in the console summary (e.g. `1 approved`) and do\nnot fail the run (and are not auto-fixed by `--fix`). The JSON output\n(`--format json`) reports only real errors and warnings. You can approve a\nspecific rule, multiple rules, or all rules for the next statement:\n\n```sql\n-- Approve multiple rules\n-- prisma-strong-migrations-approve-next-line removeColumn renameColumn\nALTER TABLE \"users\" DROP COLUMN \"name\";\n\n-- Approve all rules for a statement\n-- prisma-strong-migrations-approve-next-line\nALTER TABLE \"users\" DROP COLUMN \"name\";\n```\n\n## Configuration\n\nCreate `prisma-strong-migrations.config.js` in your project root:\n\n```javascript\nmodule.exports = {\n  // Disable specific rules globally\n  disabledRules: [\"indexColumnsCount\"],\n\n  // Skip specific migrations (matched by substring)\n  ignoreMigrations: [\"20240101_initial\"],\n\n  // Custom rules directory\n  customRulesDir: \"./prisma-strong-migrations-rules\",\n\n  // Directory to scan for migration files\n  migrationsDir: \"./prisma/migrations\",\n\n  // Treat warnings as errors\n  warningsAsErrors: false,\n\n  // Exit non-zero when warnings are found\n  failOnWarning: false,\n\n  // Exit non-zero when errors are found\n  failOnError: true,\n};\n```\n\n### Configuration options\n\n| Option             | Type       | Default                              | Description                               |\n| ------------------ | ---------- | ------------------------------------ | ----------------------------------------- |\n| `disabledRules`    | `string[]` | `[]`                                 | Rule names to disable globally            |\n| `ignoreMigrations` | `string[]` | `[]`                                 | Migration names to skip (substring match) |\n| `customRulesDir`   | `string`   | `\"./prisma-strong-migrations-rules\"` | Directory containing custom rule files    |\n| `migrationsDir`    | `string`   | `\"./prisma/migrations\"`              | Directory to scan for migration files     |\n| `warningsAsErrors` | `boolean`  | `false`                              | Treat warnings as errors (exit non-zero)  |\n| `failOnWarning`    | `boolean`  | `false`                              | Exit non-zero when warnings are found     |\n| `failOnError`      | `boolean`  | `true`                               | Exit non-zero when errors are found       |\n\n## Custom Rules\n\nCreate custom rules for your project-specific needs. See [docs/RULES.md](./docs/RULES.md) for details.\n\n## CI Integration\n\nAdd a check step to your pull request workflow so unsafe migrations are caught before they reach production.\n\n### GitHub Actions\n\n```yaml\nname: Migration Check\n\non:\n  pull_request:\n    paths:\n      - \"prisma/schema.prisma\"\n      - \"prisma/migrations/**\"\n\njobs:\n  check:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/setup-node@v4\n        with:\n          node-version: \"22\"\n      - run: npm ci\n      - run: npx psm check\n```\n\nUse `--format json` to parse results in custom scripts or post findings as pull request comments. See [docs/WORKFLOW.md](./docs/WORKFLOW.md) for more CI/CD integration examples.\n\n## Development\n\n### Using devcontainer (Recommended)\n\nThis project uses Docker + devcontainer for development.\n\n```bash\n# Open in VSCode\n# 1. Open the project in VSCode\n# 2. Command Palette (Cmd+Shift+P) → \"Dev Containers: Reopen in Container\"\n```\n\n### Available Commands\n\n```bash\nvp install  # Install dependencies\nvp test     # Run tests\nvp check    # Run lint, format, and type checks\nvp pack     # Build library for publishing\n```\n\n## Documentation\n\n- [Design Document](./docs/DESIGN.md) - Architecture and implementation details\n- [Rules Reference](./docs/RULES.md) - Detailed explanation of each rule\n- [Testing Strategy](./docs/TESTING.md) - Unit and integration testing\n- [Workflow Guide](./docs/WORKFLOW.md) - Development workflow and CI/CD\n- [Agent Guidelines](./AGENTS.md) - Guidelines for AI agents\n\n## Credits\n\nInspired by [strong_migrations](https://github.com/ankane/strong_migrations) by Andrew Kane.\n\n## License\n\nMIT\n","readmeFilename":"README.md"}