关于 orm-preflight
orm-preflight 是用于检查 TypeORM 迁移的命令行工具,可在合并前发现数据丢失、表锁和部署风险。它无需连接数据库,并提供问题原因、安全修改建议及 CI 输出格式。
主要功能
- 01检查迁移中的数据丢失风险
- 02识别可能阻塞的数据库操作
- 03发现滚动部署相关问题
- 04提供问题原因和安全替代方案
- 05支持 JSON、GitHub 注释及 SARIF 输出
适用场景
- 01在合并前审查 TypeORM 迁移
- 02在拉取请求中检查新增或修改的迁移
- 03将迁移检查接入 GitHub Actions
- 04排查可能重写数据或锁表的语句
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
3收录的工具列表 · 更新于 2 周前
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| files | array | 否 | 迁移文件或 glob,相对于 projectDir,例如 ["src/migrations/1727200000000-AddEmail.ts"]。 |
| dialect | enum | 否 | 数据库类型。默认值:配置中的值,或 postgres。postgres | mysql |
| projectDir | string | 否 | 项目根目录的绝对路径,即 package.json 所在目录。默认值:服务启动时所在的目录。 |
| changedSince | string | 否 | Git 引用,例如 origin/main。仅检查自分支从该引用分出后新增或更改的迁移。 |
| postgresVersion | integer | 否 | PostgreSQL 主版本。默认值:配置中的值,或 16。 |
详细介绍
orm-preflight
TypeORM 迁移的预检安全检查:在合并前发现数据丢失和锁表风险。
文档:https://sikandar100.github.io/orm-preflight/
在 TypeORM 中修改列的长度可能会删除该列的所有数据。当列的类型或长度发生变化时,TypeORM 的迁移生成器会先删除该列,再重新添加 (typeorm/typeorm#3357)。这类迁移在代码审查时看似无害,而且在空数据库上能通过所有测试。orm-preflight 会在合并前发现这类问题,以及其他会导致数据丢失、锁定繁忙表或破坏滚动部署的迁移。
src/migrations/1727100000000-WidenUserName.ts
7:30 error no-drop-and-recreate-column
"users"."name" is dropped and re-added in the same migration. Every existing value in this column will be lost.
Why: TypeORM drops and re-adds a column when its type or length changes, instead of changing it in place. The new column starts empty.
Safe: Change the type in place: ALTER TABLE "users" ALTER COLUMN "name" TYPE varchar(255). Widening a varchar, or changing varchar to text, does not rewrite the table. ...
Docs: https://github.com/sikandar100/orm-preflight/blob/main/docs/rules/no-drop-and-recreate-column.md
8:30 error no-add-not-null-without-default
"users"."name" is added as NOT NULL without a default. The migration fails as soon as "users" has any rows.
...
2 errors, 0 warnings in 1 migration
安装和运行
npm install --save-dev orm-preflight
npx orm-preflight "src/migrations/*.ts"
需要 Node.js 20 或更高版本。不带参数运行时,orm-preflight 会检查其配置中的 migrations glob,或项目中所有名为 migrations 的文件夹。
如果要在现有项目中使用,但不修复项目历史记录,请先运行 npx orm-preflight init(参见在现有项目中采用)。
能发现的问题
| 规则 | 报告 |
|---|---|
| 数据丢失 | |
no-drop-and-recreate-column | 在同一次迁移中删除并重新添加列 |
possible-rename-data-loss | 删除一列并添加另一列,但未复制数据(警告) |
no-drop-column | 删除列 |
no-drop-table | 删除表 |
no-truncate | TRUNCATE 或 clearTable() |
no-truncating-type-change | 类型变更中的 USING 仅保留每个值的一部分,例如 left()(警告) |
no-delete-rows | 对现有表执行 DELETE(警告) |
no-null-out-column | 仅清除列、将其设为 NULL 的 UPDATE(警告) |
rewrites-existing-rows | 对现有表执行的每个 UPDATE、upsert 或 INSERT ... SELECT,供审核(提示) |
typeorm/no-changecolumn-recreate | 删除并重新添加列的 changeColumn() |
| 部署安全 | |
no-rename-column | 重命名列 |
no-rename-table | 重命名表 |
| 锁定(PostgreSQL) | |
require-concurrent-index | 不带 CONCURRENTLY 的 CREATE INDEX |
concurrent-index-transaction | CONCURRENTLY 在事务中运行 |
no-add-not-null-without-default | 添加未设置默认值的 NOT NULL 列 |
no-volatile-default | 添加带有易变默认值的列,或添加为 serial、identity 或 generated |
no-unsafe-column-type-change | 会重写表的列类型变更 |
require-not-valid-foreign-key | 添加外键时未使用 NOT VALID |
no-set-not-null | 对现有列执行 SET NOT NULL |
require-not-valid-check | 添加 CHECK 约束时未使用 NOT VALID |
validate-in-same-transaction | 在添加 NOT VALID 的同一事务中执行 VALIDATE CONSTRAINT,此举毫无益处(警告) |
require-concurrent-unique | 原地添加唯一约束或主键 |
require-concurrent-index-drop | 不带 CONCURRENTLY 的 DROP INDEX(警告) |
no-blocking-maintenance | 不带 CONCURRENTLY 的 VACUUM FULL、CLUSTER 或 REINDEX |
enum-value-used-in-same-transaction | 在添加新枚举值的事务提交前使用该值 |
require-lock-timeout | 修改现有表时未设置 lock_timeout(默认关闭,需主动启用) |
typeorm/no-enum-recreate | TypeORM 0.3 通过重建类型来变更枚举(警告) |
| 正确性 | |
typeorm/transaction-override-forbidden | 在事务模式为 "all" 时,为迁移设置 transaction |
typeorm/invalid-migration-name | 迁移名称不含 13 位时间戳 |
unanalyzable-statement | orm-preflight 无法读取的语句(警告) |
invalid-suppression | 抑制注释缺少原因或引用了未知规则 |
no-edit-applied-migration | 使用 --changed-since 时:修改了基准分支中已存在的迁移 |
require-down | down() 为空或缺失(警告) |
每条发现都会说明发生了什么、原因是什么,以及如何安全地进行更改。每条规则的页面都引用了支持其结论的 PostgreSQL、MySQL 或 TypeORM 源码。运行 npx orm-preflight explain <rule> 可在终端中阅读相关内容,或运行 npx orm-preflight rules 查看规则列表。 |
同一迁移中创建的表所发生的更改不会被报告:此时表中还没有行。
命令行
orm-preflight [files or globs...] [options]
-c, --config <path> Config file (default: orm-preflight.config.json, then the
"ormPreflight" key in package.json)
--dialect <postgres|mysql> Database dialect (default: postgres)
--postgres-version <n> PostgreSQL major version the migrations run on (default: 16)
--orm <typeorm> ORM adapter (default: typeorm)
--changed-since <git-ref> Check only migrations added or changed since the merge base
with <git-ref>, such as origin/main
--format <format> pretty, json, github (annotations), or sarif. Default:
github in GitHub Actions, pretty elsewhere
--max-warnings <n> Fail when there are more than n warnings (default: no limit)
--execute Run each migration's up() to see SQL built at run time. This
runs your code: only use it on code you trust
--allow-untrusted-execute Allow --execute under GitHub's pull_request_target event
--no-color Print without colors
orm-preflight rules List every rule
orm-preflight explain <rule> Print a rule's documentation
orm-preflight init [files or globs...] Write a starter config
orm-preflight mcp Run as an MCP server for AI coding agents
| 退出代码 | 含义 |
|---|---|
0 | 没有错误,且警告数不超过 --max-warnings |
1 | 存在错误,或警告数超过 --max-warnings |
2 | 用法、配置或内部错误 |
| 格式 | 输出 |
|---|---|
pretty | 按文件分组显示发现,方便阅读。仅统计已抑制的发现数量。 |
json | 所有发现,包括已抑制的发现。其结构属于公开 API,详见 docs/json-output.md。 |
github | 在拉取请求的对应行添加 GitHub Actions 注释,然后输出一行摘要。 |
sarif | SARIF 2.1.0 格式,适用于 GitHub 代码扫描及其他 SARIF 查看器。保留已抑制的发现及其原因。 |
配置
将配置放在 orm-preflight.config.json 中,或放在 package.json 的 "ormPreflight" 键下。仅支持 JSON,因此拉取请求无法通过配置运行代码。
{
"$schema": "./node_modules/orm-preflight/schema.json",
"orm": "typeorm",
"dialect": "postgres",
"postgresVersion": 16,
"migrations": ["src/migrations/*.ts"],
"startAfter": 1727000000000,
"typeorm": { "transactionMode": "each" },
"rules": { "no-rename-column": "warn" }
}
| 键 | 含义 |
|---|---|
migrations | 迁移文件的 glob 模式。默认:所有 migrations 文件夹。 |
startAfter | 迁移时间戳。此时间戳及之前的迁移不会接受检查。 |
postgresVersion | 你所运行的 PostgreSQL 主版本,需为 12 或更高版本。某些安全替代方案取决于此版本。默认:16。 |
defaultSchema | 未限定表名所属的 schema。默认:PostgreSQL 中的 public。 |
typeorm.transactionMode | 必须与 DataSource 中的 migrationsTransactionMode 一致:all(TypeORM 默认值)、each 或 none。 |
rules | 每条规则的严重级别:error、warn、info 或 off。 |
命令行标志会覆盖配置。
各严重级别的作用:
| 严重级别 | 含义 |
|---|---|
error | 实际问题。运行失败(退出代码 1)。 |
warn | 可能存在问题。仅当警告数超过 --max-warnings 时,运行才会失败。 |
info | 值得了解,但不构成问题,例如会重写现有行的语句。运行永远不会因此失败。 |
off | 不运行此规则。 |
在现有项目中采用
npx orm-preflight init
init 会写入 orm-preflight.config.json,并将 startAfter 设为最新迁移,因此只会检查你从现在起新增的迁移。它会读取迁移文件,但绝不会运行它们。
在拉取请求中,你也可以只检查分支新增或更改的迁移:
npx orm-preflight --changed-since origin/main
它会与 origin/main 的合并基准进行比较,并包含未提交和未跟踪的文件。任何已更改迁移创建的表,对这些迁移来说都算作新表。在 origin/main 上已存在的迁移如果被编辑,会由 no-edit-applied-migration 报告。在 GitHub Actions 中,请使用 fetch-depth: 0 检出代码,以确保合并基准可用。
持续集成
orm-preflight 无需数据库和密钥,因此可安全用于来自复刻仓库的拉取请求。
GitHub Action
问题会以注释的形式显示在拉取请求的对应行上:
name: Migrations
on: pull_request
permissions:
contents: read
jobs:
orm-preflight:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # for changed-since
persist-credentials: false
- uses: sikandar100/orm-preflight@v1
with:
changed-since: origin/${{ github.base_ref }}
版本选择:
@v1会跟随每个 1.x 版本,因此无需更改任何内容即可获得修复。- 如果策略要求固定版本,请使用
@v1.0.0或提交 SHA,这样版本就会保持不变。
该 Action 会通过 npm 安装与其标签对应的 orm-preflight 版本并运行它,因此 GitHub 托管的运行器无需进行其他设置。在自托管运行器上,请先设置 Node.js 20 或更高版本。
| 输入 | 含义 |
|---|---|
changed-since | 只检查自与此 git 引用的合并基准以来新增或更改的迁移。 |
files | 要检查的文件或 glob,以空格分隔。默认值:配置文件,或所有 migrations 文件夹。 |
config | 配置文件的路径。 |
working-directory | 要运行的目录,相对于仓库根目录。默认值为 .。 |
max-warnings | 警告数量超过此值时失败。 |
mysql | 迁移目标为 MySQL 时设为 true,以安装 MySQL 解析器。 |
sarif | 设为 true 时,还会将问题上传到 GitHub 代码扫描。见下文。 |
version | 要运行的 orm-preflight 版本。默认值:Action 标签对应的版本。 |
请使用 pull_request 触发器。切勿为此使用 pull_request_target:它会向来自复刻仓库的拉取请求授予具有写入权限的令牌和你的密钥,而 orm-preflight 两者都不需要。
GitHub 每个步骤最多显示 10 条各级别的注释。日志的最后一行始终会统计所有问题。
若要在仓库的代码扫描警报中也查看问题,请设置 sarif: true 并授予 security-events: write 权限。GitHub 不会向来自复刻仓库的拉取请求授予此权限,因此 Action 会跳过上传,但仍会添加注释:
permissions:
contents: read
security-events: write
其他 CI 系统
运行 CLI。出现错误时,退出代码为 1:
npx orm-preflight --changed-since origin/main
检出足够的历史记录以获取合并基准(在 GitHub Actions 中使用 fetch-depth: 0)。
在 AI 编码 Agent 中使用
AI 编码 Agent 能快速编写迁移,也可能编写出危险的迁移。连接 orm-preflight 后,Agent 会检查自己编写的每个迁移,并修复发现的问题,然后你再进行审查。
Claude Code 插件
这个插件能为你做最多的事。只需在 Claude Code 中安装一次:
/plugin marketplace add sikandar100/orm-preflight
/plugin install orm-preflight@orm-preflight
它会添加三项内容:
- 自动检查。 每当 Claude 编写或编辑迁移时,orm-preflight 都会检查,并将结果直接反馈给 Claude。随后 Claude 会修复迁移,或告诉你为什么这样做是安全的。你无需亲自阅读任何警告。其他文件会立即跳过。
- 一项技能,教 Claude 如何安全地更改架构,例如就地更改列,而不是删除它。
- 下文介绍的 MCP 工具。
该插件运行发布时所用的确切 orm-preflight 版本,并在首次使用时从 npm 下载。你项目中的 orm-preflight.config.json 仍然有效。若要让自动检查改为运行项目中安装的 orm-preflight(例如使用其 MySQL 解析器),请在 /config 中启用插件设置 Use the project's own orm-preflight。
任何 Agent,均可通过 MCP 使用
orm-preflight mcp 会将 orm-preflight 作为 MCP 服务运行。MCP 是为 Agent 提供新工具的标准方式。只需添加一次。
在 Claude Code 中(不使用插件):
claude mcp add orm-preflight -- npx -y orm-preflight mcp
在其他 Agent 中,例如 Cursor、VS Code、Codex 或 Gemini CLI,在 MCP 设置中添加一个运行此命令的服务。每个应用都有自己的设置文件,但命令始终相同:
npx -y orm-preflight mcp
每个 Agent 的具体设置方法,以及适用于 AGENTS.md 的指令片段,请参阅与 AI Agent 配合使用。
Agent 可使用三种工具:
| 工具 | Agent 可获取的内容 |
|---|---|
check_migrations | 某些文件、整个项目,或自 origin/main 等 git 引用以来更改的迁移文件的检查结果。每项结果都说明了会发生什么、原因,以及安全的处理方式。 |
explain_rule | 某条规则的完整文档。 |
list_rules | 所有规则及其类别和默认严重级别。 |
须知:
- 这些工具只会读取文件。它们绝不会运行迁移或连接数据库,并且无法通过 MCP 使用
--execute。 - 服务会检查启动时所在目录中的项目。在其他位置启动服务的 Agent 可以传入
projectDir。 mcp后的标志会设置默认值,例如npx -y orm-preflight mcp --dialect mysql。对于 MySQL,请在项目中安装orm-preflight和node-sql-parser,并使用不带-y的npx orm-preflight mcp,以便找到解析器。- 服务会告知 Agent,未经你事先同意,绝不能添加抑制注释。
- 你的 Agent 可能会询问你是否允许使用这些工具。
抑制检查结果
当某项检查结果属于预期情况时,请在相关语句上方的注释中说明原因:
// preflight safety-assured no-drop-column -- bio removed from the entity in 2.3, deployed everywhere
await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "bio"`)
必须提供原因,这样抑制记录才能形成审计跟踪。对于整个文件,可在文件任意位置添加
/* preflight safety-assured-file <rule> -- <reason> */。缺少原因的抑制或未知规则本身都会报错。
工作原理
orm-preflight 使用 Babel 解析每个迁移文件,并读取传递给
queryRunner.query() 的 SQL 字符串,以及 addColumn() 和 createIndex() 等构建器调用。它还会在同一文件中值已知时填充 SQL 中的 ${...},例如常量、使用固定参数调用的本地函数,或对已知数组调用 .join();对于遍历已知数组的循环,它也会按每个数组项读取一次。它使用 PostgreSQL 解析器(libpg-query)或 MySQL 解析器解析
SQL,然后对解析结果运行规则。
它绝不会导入或运行你的迁移,不会连接数据库,也不会加载 TypeORM。对于它无法读取的内容,例如运行时构建的 SQL,它会报告为
unanalyzable-statement,而不是悄悄跳过。
使用 --execute 运行迁移
某些迁移会使用仅在运行时才知道的值构建 SQL,例如从数据库读取的名称,或从其他文件导入的辅助函数。静态分析无法读取这些 SQL。--execute 可以:
npx orm-preflight --execute
它会加载每个迁移文件,并使用 TypeORM 查询运行器的替身运行 up();该替身会记录每条语句,而不会将其发送到数据库。因此:
- 不需要数据库,也不会连接任何数据库。
- 对
typeorm的导入会使用一个轻量替身;绝不会加载真正的 TypeORM。 - 如果
up()读取数据库,例如调用getTable(),它会得到空结果,而 orm-preflight 会报告真实数据库可能会产生不同的语句。 - 如果
up()运行失败或超过 10 秒,系统会报告这一情况,并继续执行 lint。
--execute 会运行你的代码。 只能用于你信任的代码:
- 绝不要在带有
pull_request_target触发器的 fork 拉取请求上使用它,因为该触发器会向其提供你的机密信息。除非你添加--allow-untrusted-execute,否则 orm-preflight 会拒绝执行。 - GitHub Action 绝不会使用
--execute。它会保持静态分析,确保 fork 安全。
检查通过意味着什么
检查通过意味着没有发现任何已记录的风险。但这并不能保证迁移是安全的。orm-preflight 不知道你的表大小或流量,因此会假设每个现有表都很大且繁忙。
限制:
- 仅支持 TypeORM。核心与 ORM 无关,因此可以为其他 ORM 开发适配器。
- 支持 PostgreSQL 和 MySQL。在 MySQL 上,数据丢失、部署安全和正确性规则均适用。
MySQL 的锁定分析(
ALGORITHM=INSTANT、INPLACE、COPY)计划在 1.0 之后推出。 MySQL 需要npm install --save-dev node-sql-parser。 - 仅分析
up()。down()只会检查是否为空(require-down)。 - 运行时构建的 SQL 会被报告,而不会被分析,除非你使用
--execute。
以编程方式使用
import { formatJson, lint } from 'orm-preflight'
const result = await lint({ cwd: process.cwd(), patterns: ['src/migrations/*.ts'] })
process.stdout.write(formatJson(result))
if (result.summary.errors > 0) process.exitCode = 1
参与贡献
请参阅 CONTRIBUTING.md。如需报告安全问题,请参阅 SECURITY.md。
隐私
orm-preflight 不会收集或发送任何信息:没有遥测、分析数据或账户。它在你的计算机或 CI 运行器上运行,绝不会连接你的数据库。详情请见隐私政策。
致谢
orm-preflight 的灵感来自 Rails 的 strong_migrations 和 PostgreSQL 的 squawk。
许可证
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。