@megic/easy-dev
v1.0.0
Published
一个专注 OpenAPI-only 的命令行工具: - 以 OpenAPI 作为唯一真源(Single Source of Truth) - 版本快照直接保存 OpenAPI 文件到 `versions/<semver>/` - 直接从两个 OpenAPI 文件生成数据库增量 SQL(Postgres) - 可选:保留解析到 JSON 的能力,但默认流程不依赖 JSON 元数据
Readme
easy-dev CLI (edev)
一个专注 OpenAPI-only 的命令行工具:
- 以 OpenAPI 作为唯一真源(Single Source of Truth)
- 版本快照直接保存 OpenAPI 文件到
versions/<semver>/ - 直接从两个 OpenAPI 文件生成数据库增量 SQL(Postgres)
- 可选:保留解析到 JSON 的能力,但默认流程不依赖 JSON 元数据
快速开始(OpenAPI-only 默认流程)
要求:Node.js >= 18
npm run build
npm link # 将 edev 命令链接到全局
# 保存 OpenAPI 到版本目录(推荐)
edev version create -v 0.1.1 -m "保存 JSON 快照" --openapi example/openapi.ns.json
# 直接从两个 OpenAPI 文件对比并生成 SQL(不落 JSON)
edev db diff --from-openapi example/openapi-old.json --to-openapi example/openapi.ns.json -d postgres -o migrations/openapi_old_to_new.sql
# 从 latest 版本的 OpenAPI 对比到新 OpenAPI
edev db diff --from-latest --to-openapi example/openapi.ns.json -d postgres -o migrations/latest_to_new.sql
# 指定某个版本作为旧版(versions/<v>/openapi.*)
edev db diff --from-version 0.1.1 --to-openapi example/openapi.ns.json -d postgres -o migrations/0.1.1_to_new.sql
# 从 Git tag/commit 读取旧版 OpenAPI 并对比当前 OpenAPI
edev db diff --from-git v0.1.1 --to-openapi example/openapi.ns.json -d postgres -o migrations/git_v0.1.1_to_new.sql以上命令均默认优先使用 OpenAPI;仅当未找到 OpenAPI 时才回退到 JSON 物理模型(例如 --from versions/0.1.1/physical.json 与 --to meta/physical.json)。
说明:--from-git <ref> 将尝试从 <ref> 提交下的以下路径中定位 OpenAPI(依次):versions/*/openapi.json(选择最高版本)、example/openapi.json、openapi.json,并写入缓存临时文件用于解析。
发布与安装
发布到 npm
- 确保
package.json中的name是唯一的(建议使用 scope 包,如@your-username/easy-dev)。 - 登录 npm:
npm login - 构建并发布:
# 自动运行 build 脚本 npm publish --access public
全局安装
用户可以通过以下命令全局安装:
npm install -g easy-dev
# 或者如果是 scope 包
npm install -g @your-username/easy-dev安装后即可在任意目录下使用 edev 命令。
OpenAPI 扩展约定
- 物理模型标记:在
components.schemas.*中使用x-physical: true或x-db.table: "表名"标记为物理模型实体 - 列属性扩展:
x-primary-key: true设置主键列x-unique: true设置唯一约束nullable: true/false与default作为列属性description: string列描述(用于 Postgres 的COMMENT ON COLUMN)
- 表描述:表的
description: string用于 Postgres 的COMMENT ON TABLE - 额外 SQL:
x-db.sql: string[]支持宏替换:{{table}}-> 实际表名(启用命名空间后如app_members)- 列级内联片段:在列上使用
x-db.sql: string | string[](例如DEFAULT now())将直接附加到列定义 - 建议写成幂等语句(
IF NOT EXISTS)以适配重复执行场景
- 列类型覆盖:
- 使用
x-db.sqlType(或x-db: { sqlType })指定列 SQL 类型(如JSONB、TEXT[]、TIMESTAMPTZ) - 未指定时
object/array默认映射为TEXT;指定后按给定类型输出(不校验兼容性) - 迁移提示(Postgres):从
TEXT/VARCHAR转为JSON/JSONB时,自动加入USING强制转换;请确保现有数据可解析为合法 JSON
- 使用
- 命名空间行为:
- 启用
--namespace时,x-db.table自动加上下划线前缀(如app_members);schemas名称前缀为<目录名><分隔符>并自动重写$ref
- 启用
- 示例:参见
example/modules/*/openapi.json与合并后的example/openapi.ns.json。
目录约定
versions/<semver>/:版本快照(openapi.json)CHANGELOG.md:版本记录migrations/:diff 输出的 SQL 文件meta/:可选的 JSON 快照(如果需要)
可选功能(非默认)
- 解析 OpenAPI 为 JSON 元数据快照:
edev parse -i example/openapi.ns.json -o meta -d postgres - 基于 JSON 快照的数据库增量(历史
versions/<v>/physical.json到当前meta/physical.json):edev db diff -d postgres -o migrations/0.1.0_to_current.sql
注意
- 当前 diff 主要支持表创建/删除、列新增/删除、列类型/可空/默认值/唯一约束变更;更复杂约束(复合主键、外键、索引等)可后续扩展。
- OpenAPI-only 模式下,你可以完全跳过 JSON 快照与元数据版本化,直接以 OpenAPI 文件作为版本基线与对比来源。
合并模块为总 OpenAPI(JSON)
- 说明:遍历子目录中的
openapi.json,合并为一个总的 OpenAPI JSON;按子目录名为每个接口添加tags分组。 - 用法:
edev openapi merge -d example/modules -o example/openapi.json -t "Example Modules" -v 0.1.0 - 参数:
--dir <modulesDir>模块目录(子目录中包含openapi.json)--out <file>输出的合并后 JSON 文件路径--title <text>顶部info.title(默认Merged API)--version <ver>顶部info.version(默认0.1.0)--namespace启用命名空间:paths前缀加子目录名,schemas加子目录名前缀--ns-sep <sep>命名空间分隔符(默认.,例如system.User)
- 示例(启用命名空间):
edev openapi merge -d example/modules -o example/openapi.ns.json --namespace - 合并策略:
paths:合并各模块的路由与操作;每个操作的tags自动追加子目录名;启用命名空间时将路径前缀为/<目录名>components.schemas与components.securitySchemes:简单合并(同名覆盖,最后出现的生效);启用命名空间时schemas名称前缀为<目录名><分隔符>并自动重写文档中的$refx-db.table:启用命名空间时自动加上下划线前缀(如app_members、system_users)
合并模块初始化脚本 (init.sql)
- 说明:扫描模块目录下的所有子目录,寻找
init.sql文件,将其合并为一个总的初始化 SQL 脚本。 - 特性:
- 自动移除各模块
init.sql中的事务控制语句 (BEGIN/COMMIT/ROLLBACK),避免嵌套事务错误。 - 在最终生成的 SQL 文件外层包裹统一的事务 (
BEGIN; ... COMMIT;)。 - 按模块目录名称排序合并顺序。
- 自动移除各模块
- 用法:
edev merge-init-sql --dir example/modules --out example/full_init.sql - 参数:
--dir <dir>模块根目录(包含多个子模块目录)--out <file>输出的合并后 SQL 文件路径
生成完整 SQL(建表 + 额外 SQL + 描述)
- 目的:从单一 OpenAPI 直接生成完整物理建表与扩展 SQL,且支持宏替换。
- 命令:
edev db generate --from-openapi example/openapi.ns.json --out example/full.sql --dialect postgres - 行为:
- 生成
CREATE TABLE语句(根据x-physical/x-db.table与列定义) - 生成 Postgres 的
COMMENT ON TABLE与COMMENT ON COLUMN语句(来自description) - 读取
x-db.sql: string[]并执行宏替换:{{table}}-> 实际表名(命名空间后,如app_members) - 输出按顺序拼接:建表 -> 注释 -> 额外 SQL
- 生成
- 示例(在模块 OpenAPI 中声明额外 SQL,并使用宏):
{ "components": { "schemas": { "Member": { "x-physical": true, "description": "会员表", "x-db": { "table": "members", "sql": [ "CREATE UNIQUE INDEX IF NOT EXISTS uq_app_members_email ON {{table}} (email)" ]}, "type": "object", "required": ["id", "email"], "properties": { "id": { "type": "string", "x-primary-key": true, "description": "主键ID" }, "email": { "type": "string", "x-unique": true, "description": "邮箱" }, "nickname": { "type": "string", "description": "昵称" }, "createdAt": { "type": "string", "format": "date-time", "description": "创建时间" } } } } } } - 说明:
- 描述注释目前支持 Postgres(
--dialect postgres),其他方言可后续按需补充。 - 建议将额外 SQL写成幂等语句(
IF NOT EXISTS)。
- 描述注释目前支持 Postgres(
对比现有数据库并生成差异 SQL(Postgres)
- 目的:将 OpenAPI 物理模型与线上 Postgres 实际表结构进行对比,输出迁移 SQL。
- 命令:
edev db diff-db --from-openapi example/openapi.ns.json --url postgres://user:pass@host:5432/db --out migrations/diff_from_db.sql - 行为:
- 新增表:生成
CREATE TABLE,追加 Postgres 的COMMENT ON TABLE/COMMENT ON COLUMN,并拼接x-db.sql(支持{{table}}宏) - 既有表:生成列/可空/默认值/唯一约束/自增等差异的
ALTER TABLE语句(不重复追加x-db.sql) - 输出顺序(新增表部分):建表 -> 注释 -> 额外 SQL
- 新增表:生成
- 说明:
- 描述注释目前支持 Postgres(
diff-db),其他方言后续按需补充 - 建议将额外 SQL 写成幂等语句(
IF NOT EXISTS)以适配重复执行场景 - 生成
CREATE TABLE语句(根据x-physical/x-db.table与列定义) - 生成 Postgres 的
COMMENT ON TABLE与COMMENT ON COLUMN语句(来自description) - 读取
x-db.sql: string[]并执行宏替换:{{table}}-> 实际表名(命名空间后,如app_members) - 输出按顺序拼接:建表 -> 注释 -> 额外 SQL
- 描述注释目前支持 Postgres(
- 示例(在模块 OpenAPI 中声明额外 SQL,并使用宏):
{ "components": { "schemas": { "Member": { "x-physical": true, "description": "会员表", "x-db": { "table": "members", "sql": [ "CREATE UNIQUE INDEX IF NOT EXISTS uq_app_members_email ON {{table}} (email)" ]}, "type": "object", "required": ["id", "email"], "properties": { "id": { "type": "string", "x-primary-key": true, "description": "主键ID" }, "email": { "type": "string", "x-unique": true, "description": "邮箱" }, "nickname": { "type": "string", "description": "昵称" }, "createdAt": { "type": "string", "format": "date-time", "description": "创建时间" } } } } } } - 说明:
- 描述注释目前支持 Postgres(
--dialect postgres),其他方言可后续按需补充。 - 建议将额外 SQL写成幂等语句(
IF NOT EXISTS)。
- 描述注释目前支持 Postgres(
执行 SQL 文件
- 命令:
edev db exec --url postgres://user:pass@host:5432/db --file path/to.sql - 行为:一次性执行整份 SQL;不自动开启事务;遇到错误会停止并返回非零退出码。
- 多语句:按分号
;分隔。请确保每条语句以;结尾(生成器会自动补上)。 - 示例:
- 执行生成的完整建表与注释:
edev db exec --url postgres://user:pass@host:5432/db --file example/full.sql - 执行差异迁移:
edev db exec --url postgres://user:pass@host:5432/db --file migrations/diff_from_db.sql
- 执行生成的完整建表与注释:
- 提示:
- 若 SQL 中包含 Postgres 专属语句(如
COMMENT ON、INHERITS),请使用--dialect postgres生成。 - 生产库前建议在测试库验证;必要时将关键步骤包裹在手动事务(
BEGIN; ... COMMIT;)中。
- 若 SQL 中包含 Postgres 专属语句(如
数据库管理(Postgres 模板与测试库)
提供了一组命令用于 CI/CD 流程中快速创建、克隆和清理测试数据库。支持并发安全检测、强制覆盖以及标准化 JSON 日志输出。
命令概览
- 创建模板库:将现有数据库(source)克隆为模板库(template),并标记为模板(禁止连接)。
- 克隆测试库:基于模板库(template)快速克隆出新的测试库(target)。
- 删除数据库:清理不再需要的数据库。
连接参数
支持两种连接方式(二选一):
--url:完整的连接字符串,例如postgres://user:pass@host:5432/db?sslmode=require- 分解参数:
--host <host>(必填)--user <user>(必填)--password <pass>(可选)--port <port>(可选,默认 5432)--sslmode <mode>(可选,支持disable|allow|prefer|require|verify-ca|verify-full)
1. 创建模板库 (create-template)
# 使用 URL
edev db create-template --source-db my_source --template-db my_template --url postgres://u:p@localhost:5432/postgres --force
# 使用分解参数
edev db create-template --source-db my_source --template-db my_template --host localhost --user postgres --password secret --force--source-db:源数据库名(必须存在)。--template-db:目标模板数据库名。--force:如果模板库已存在,强制删除重建;如果源库有连接,强制终止连接。
2. 克隆测试库 (clone-template)
edev db clone-template --template-db my_template --target-db test_db_123 --host localhost --user postgres --force--template-db:模板数据库名(必须存在)。--target-db:新数据库名。--force:如果目标库已存在,强制删除重建。
3. 删除数据库 (drop-db)
edev db drop-db --target-db test_db_123 --host localhost --user postgres --if-exists --force--target-db:要删除的数据库名。--if-exists:如果数据库不存在,不报错(返回成功)。--force:如果数据库有连接,强制终止连接并删除。
CI/CD 集成 (JSON 日志与错误码)
所有数据库管理命令的标准输出(stdout/stderr)均为 JSON 格式(单行),便于机器解析。
- 格式:
{"ts": "...", "level": "info|error", "code": "...", "message": "...", "data": {...}} - 错误码 (code):
OK: 成功DB_NOT_FOUND: 数据库不存在DB_ALREADY_EXISTS: 数据库已存在(且未指定 force)DB_IN_USE: 数据库正被占用(且未指定 force)PG_CONNECT_FAILED: 连接失败(密码/网络等)INVALID_ARGS: 参数错误
- 退出码:进程退出码与错误类型对应(0=成功, 1=常规错误, 2=参数错误 等)。
- 项目结构
src/:CLI 源码(db/diff.ts、db/types.ts、openapi.ts、index.ts等)example/:示例输出与合并后的 OpenAPI(openapi.json、openapi.ns.json、full.sql)example/modules/:模块化 OpenAPI(如app/openapi.json、system/openapi.json)migrations/:通过 diff 命令生成的迁移 SQLversions/:OpenAPI 版本快照(推荐保留)
对齐 example/full.sql
- 使用合并后的 OpenAPI 生成完整 SQL:
edev db generate --from-openapi example/openapi.ns.json --dialect postgres -o example/full.sql
full.sql示例包含:- 建表(
app_members、system_users)与主键 - 唯一索引与普通索引(可在模块 OpenAPI 的
x-db.sql中声明,支持{{table}}宏) - 表与列注释(Postgres)
- 其他额外 SQL(如分区/继承等 Postgres 特性)建议放入
x-db.sql
- 建表(
类型变更注意事项(Postgres)
- 从
TEXT/VARCHAR变更到INTEGER时,Postgres 不会自动转换,需要显式USING:- 生成的语句将包含:
ALTER TABLE "tbl" ALTER COLUMN "id" TYPE INTEGER USING NULLIF(TRIM("id"), '')::INTEGER; - 若存在非数字内容(例如
abc),上述转换仍会失败。请先清洗数据或分步迁移:- 增加新整型列、回填合法数据、调整主键/外键、再移除旧列;或在事务内批量更新非数字为
NULL/占位。
- 增加新整型列、回填合法数据、调整主键/外键、再移除旧列;或在事务内批量更新非数字为
- 生成的语句将包含:
- 变更到自增(IDENTITY)建议在类型稳定后再追加:
ALTER TABLE "tbl" ALTER COLUMN "id" ADD GENERATED BY DEFAULT AS IDENTITY;
(已合并至上文“OpenAPI 扩展约定”)
