npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

prisma-strong-migrations

v1.1.0

Published

Lint Prisma migrations for unsafe SQL before it reaches production

Readme

Prisma Strong Migrations

Lint the SQL in your Prisma migrations for patterns that are dangerous to apply to a live production database — before they merge.

Prisma generates a migration.sql file for every migration. Most are harmless, but a few patterns can take your database down, lose data, or break the app mid-deploy. This tool reads those SQL files, understands them via a real SQL parser (not string matching), and flags the dangerous ones with an explanation and a safer alternative. It runs as a GitHub Action on your pull requests, or as a CLI anywhere.

It's the strong_migrations / Squawk idea, tuned to the SQL Prisma emits and to how Prisma applies migrations.

Scope: PostgreSQL today. The architecture is dialect-aware so more engines can be added; until then non-Postgres projects aren't supported.

What it catches

Every rule maps to a real way a migration hurts you in production:

| Failure mode | Example | Why it's dangerous | | --- | --- | --- | | Destructive | DROP TABLE, DROP COLUMN | Data is gone, and old code still reading it errors. | | Backwards-incompatible | renames, column type changes | The previously-deployed app breaks during the rolling deploy. | | Locking | CREATE INDEX, SET NOT NULL, validating constraints | Heavy locks or full-table scans stall reads/writes. | | Correctness | ADD COLUMN ... NOT NULL with no default | The migration fails outright on a non-empty table. |

See the full rule list below.

Quick start (GitHub Action)

Add a workflow that runs on pull requests. Findings show up as inline annotations on the diff.

# .github/workflows/migration-safety.yml
name: Migration Safety
on: pull_request

permissions:
  contents: read

jobs:
  lint-migrations:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0 # needed for changed-only detection
      - uses: mileszim/prisma-strong-migrations@v1
        with:
          changed-only: true # only lint migrations added in this PR

Action inputs

| Input | Default | Description | | --- | --- | --- | | migrations-dir | ./prisma/migrations | Where your migrations live. | | changed-only | false | Only lint migrations this branch changed versus its base — the whole branch diff, like a PR, not just the latest commit. On a PR it compares against the base branch; on a push, the repo's default branch. The action deepens shallow checkouts automatically. | | base | auto | Branch to compare against for changed-only. Defaults to the PR base branch (PRs) or the default branch (pushes). | | fail-on | error | Severity that fails the check: error or warning. | | reporter | github | github (inline annotations), stylish, or json. | | config | auto | Path to a config file. | | working-directory | . | Directory containing the Prisma project. | | node-version | 20 | Node.js version used to run the linter. |

Quick start (CLI)

npm install --save-dev prisma-strong-migrations

# Lint every migration
npx prisma-strong-migrations lint

# Lint only what changed versus a base branch (great locally and in CI)
npx prisma-strong-migrations lint --changed --base origin/main

# Lint specific files
npx prisma-strong-migrations lint prisma/migrations/20240101_init/migration.sql

# Other reporters
npx prisma-strong-migrations lint --reporter json

# See every rule and its default
npx prisma-strong-migrations list-rules

# Write a starter config
npx prisma-strong-migrations init

The CLI exits non-zero when it finds an issue at or above --fail-on (default error), so it fails CI on its own.

Example output:

prisma/migrations/20240201_risky/migration.sql
  2:20  error    Dropping column "bio" from "User" is destructive.  no-drop-column
                 why: The column data is lost, and any deployed code that still selects it will error until redeployed.
                 fix: Remove the field from your Prisma schema and deploy that first, then drop the column in a follow-up migration.

✖ 1 problem (1 error)

Configuration

Configuration is optional — the defaults are sensible. To customize, run prisma-strong-migrations init or create a prisma-strong-migrations.config.js (also supports .cjs, .json, an .prisma-strong-migrationsrc, or a "prisma-strong-migrations" key in package.json):

/** @type {import('prisma-strong-migrations').UserConfig} */
module.exports = {
  migrationsDir: './prisma/migrations',
  dialect: 'postgresql',
  failOn: 'error', // 'error' | 'warning'
  rules: {
    // Set a severity...
    'no-data-manipulation': 'error',
    // ...turn a rule off...
    'require-concurrent-index': 'off',
    // ...or enable an opt-in rule.
    'require-pii-comment': 'warning',
  },
};

Each rule can be set to 'error', 'warning', 'off', true (its default severity), false (off), or { severity, enabled }.

Suppressing a finding

Sometimes a flagged statement really is safe — you've shipped the code that stops using a column before dropping it, for example. Silence a finding with an inline SQL comment in the migration.sql file, right where the statement is:

-- psm-disable-next-line no-drop-table -- removed all readers of this table in #482
DROP TABLE "legacy_sessions";

The directive applies to the statement on the next line (it covers the whole statement, even if it spans multiple lines). Forms:

| Directive | Scope | | --- | --- | | -- psm-disable-next-line [rules] [-- reason] | the statement below the comment | | -- psm-disable-line [rules] [-- reason] | the statement on the same line (trailing comment) | | -- psm-disable-file [rules] [-- reason] | every statement in the file | | -- psm-disable [rules]-- psm-enable [rules] | every statement between the two comments |

  • rules is an optional space/comma-separated list of rule names (e.g. no-drop-table, no-set-not-null). Omit it to suppress all rules for that scope — prefer naming the rule so a different problem on the same statement still gets caught.
  • reason is optional free text after -- or :. It's strongly encouraged: it documents why the statement is safe for the next reader.
  • psm-ignore and psm-ignore-file are accepted as aliases for the next-line and file forms. Block comments (/* psm-disable-next-line */) work too.

Suppressions are never hidden: every reporter notes how many findings were suppressed, and the json reporter lists them (with reasons) under a suppressed key — so they stay auditable in review and can't quietly rot.

Rules

Run prisma-strong-migrations list-rules for the live list. Rules marked opt-in are off by default; enable them in config.

Destructive

  • no-drop-table (error) — Dropping a table deletes its data and breaks code still reading it.
  • no-drop-column (error) — Dropping a column deletes its data; Prisma keeps selecting every scalar field, so old code errors.

Backwards-incompatible

  • no-rename-column (error) — A rename is a drop + add to the running app; old code queries a column that's gone. Use expand-and-contract.
  • no-rename-table (error) — The deployed app keeps querying the old name. Use @@map or expand-and-contract.
  • no-change-column-type (error) — Usually rewrites the table under an exclusive lock and may break the running app. A USING clause doesn't make it safe.

Correctness

  • no-add-not-null-column-without-default (error) — ADD COLUMN ... NOT NULL with no default aborts on a table that already has rows.

Locking

  • no-set-not-null (warning) — SET NOT NULL scans the whole table under an exclusive lock and fails if any value is null. Use a validated CHECK (... IS NOT NULL) first.
  • require-concurrent-index (warning) — CREATE INDEX without CONCURRENTLY blocks writes while it builds.
  • constraint-missing-not-valid (warning) — Adding a CHECK or FOREIGN KEY validates every existing row under a lock. Add it NOT VALID, then VALIDATE separately.
  • no-data-manipulation (warning) — INSERT/UPDATE/DELETE inside a schema migration runs under the migration lock and can stall the deploy.

Opinionated (opt-in)

  • require-explicit-not-null (off) — Require new columns to declare NULL or NOT NULL explicitly.
  • require-pii-comment (off) — Flag columns whose names look like personal data, for a compliance review.
  • no-unindexed-foreign-key (off) — Flag foreign keys whose referencing column has no covering index in the same migration.

A note on CREATE INDEX CONCURRENTLY and Prisma

Prisma wraps each migration in a transaction, and CREATE INDEX CONCURRENTLY cannot run inside one. To follow require-concurrent-index, put the concurrent index in its own migration and apply it outside the transactional batch (for example with prisma db execute). The rule's suggestion text spells this out.

Programmatic API

import { lintProject, shouldFail, stylish } from 'prisma-strong-migrations';

const { result, config } = lintProject({
  overrides: { migrationsDir: './prisma/migrations' },
  changedSince: 'origin/main', // optional
});

console.log(stylish(result));
process.exit(shouldFail(result, config.failOn) ? 1 : 0);

Lower-level building blocks are exported too: parseSql, loadMigrations, lint, loadConfig, ALL_RULES, and the reporters.

How it works

  1. Find every migration.sql under migrationsDir (or just the changed ones).
  2. Parse each file with sql-parser-cst into a concrete syntax tree. Statements that don't parse are reported, never silently skipped.
  3. Run each enabled rule against the tree. Rules read the structure of the SQL (statement kind, table, columns, clauses) — not substrings — so they don't trip over identifiers that happen to contain keywords.
  4. Report findings with the configured severity, sorted by location.

Limitations

  • PostgreSQL only for now.
  • Static analysis can't know table size or row contents, so locking/correctness rules flag the pattern; whether it actually causes pain depends on your data. Tune severities per rule.

Development

npm install
npm run build      # compile TypeScript to dist/
npm test           # run the vitest suite
npm run lint       # eslint
npm run typecheck  # tsc --noEmit

Each rule is a small module in src/rules/ that reads facts from the parsed tree via helpers in src/rules/ast.ts and calls ctx.report(...). Add a rule by creating the module, registering it in src/rules/index.ts, and adding a test in test/rules.test.ts.

License

MIT — see LICENSE.