NocoBase nb scaffold migration 命令深度解析:从脚手架生成到插件迁移脚本原理
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
nb scaffold migration是 NocoBase CLI(nb命令)中用于快速生成插件数据库迁移(migration)脚本的脚手架命令。本文基于命令参考文档 migration.md 展开,并结合当前仓库中 CLI 的实际源码实现,完整覆盖命令的用法、参数约束、底层执行链路,以及生成的迁移脚本应遵循的up/down结构与插件生命周期挂载时机(beforeLoad/afterSync/afterLoad)。读完后你可以:直接复制命令生成迁移文件、理解 CLI 参数校验与委托执行的实现细节、并掌握在插件中正确挂载迁移的时机选择。
1. 命令用途与快速上手
nb scaffold migration的核心职责只有一件事:生成插件迁移脚本文件(Generate a plugin migration file)。当你为一个 NocoBase 插件新增数据表、调整字段结构或初始化种子数据时,迁移脚本是标准做法——它让数据库变更以可编程、可回滚(down方法)的方式随插件加载执行。
标准用法:
nb scaffold migration <name> --pkg <pkg> [flags]文档中的两个典型示例(与命令源码中内置的 examples 完全一致):
nb scaffold migration migration-name --pkg @nocobase/plugin-acl nb scaffold migration migration-name --pkg @nocobase/plugin-acl --on afterLoad参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
<name> | string | 迁移脚本名称,必填(位置参数) |
--pkg | string | 所属插件包名,必填 |
--on | string | 执行时机:beforeLoad、afterSync或afterLoad,可选,且仅限这三个取值 |
需要强调的是,--name与--pkg都是硬性约束:<name>为必填的位置参数,--pkg为必填 flag,缺省会直接触发 oclif 的参数校验失败;而--on是枚举型 flag,传入beforeLoad/afterSync/afterLoad之外的值同样会被拒绝。
2. 命令的源码实现:一个基于 oclif 的薄封装
该命令的实际实现位于 scaffold/migration.ts,它是一个标准的 oclifCommand子类,源码清晰地展示了参数定义与文档的一致性:
export default class ScaffoldMigration extends Command { static override args = { name: Args.string({description: 'migration name', required: true}), } static override description = 'Generate a plugin migration file.'; static override flags = { pkg: Flags.string({description: 'plugin package name', required: true}), on: Flags.string({description: 'on', required: false, options: ['beforeLoad', 'afterSync', 'afterLoad']}), } public async run(): Promise<void> { const { args, flags } = await this.parse(ScaffoldMigration) const npmArgs = ['create-migration', args.name, '--pkg', flags.pkg]; if (flags.on) { npmArgs.push('--on', flags.on); } try { await runNocoBaseCommand(npmArgs, { env: { LOGGER_SILENT: 'true' } }); } catch (error: unknown) { const message = error instanceof Error ? error.message : String(error); this.error(message); } } }从源码结构可以看出三个关键实现点:
- 参数定义即文档:
Args.string({required: true})对应必填的<name>;Flags.string({required: true})对应必填的--pkg;--on通过options数组实现白名单校验,即文档中beforeLoad | afterSync | afterLoad三选一的来源。 - 错误处理:任何执行异常都会被捕获并通过
this.error(message)抛出,因此命令失败时你看到的错误信息会直接来自底层执行过程。 - 无本地模板:该命令自身不渲染任何模板文件,真正的文件生成逻辑发生在它委托调用的底层命令中(见下一节)。
3. 执行链路:委托给 nocobase-v1 的 create-migration
run()方法将参数组装为['create-migration', args.name, '--pkg', flags.pkg, ...],然后调用 run-npm.ts 中的runNocoBaseCommand。从 runNocoBaseCommand 的实现 看,这条委托链路有三个值得注意的行为:
export function runNocoBaseCommand(args: string[], options?: Omit<RunProcessOptions, 'errorName'>): Promise<void> { const cwd = resolveProjectCwd(options?.cwd); const localBin = path.join(cwd, 'node_modules', '.bin'); return run('nocobase-v1', [...args], { ...options, cwd, errorName: 'nocobase command', env: { PATH: `${localBin}${path.delimiter}${process.env.PATH ?? ''}`, ...options?.env, }, }); }- 定位项目目录:
resolveProjectCwd会从当前目录(或--cwd指定目录)逐级向上查找node_modules/.bin/nocobase-v1二进制(见 resolveProjectCwd),找到才将其作为子进程工作目录。这意味着:该命令必须在安装了 NocoBase 的项目目录下执行,否则找不到目标工程会报错Couldn't find a NocoBase source project。 - PATH 注入:子进程的
PATH前缀注入了node_modules/.bin,保证nocobase-v1能被正确解析。 - 静默日志:
nb scaffold migration调用时额外注入环境变量LOGGER_SILENT: 'true',用于压制底层命令的冗余日志输出。
因此完整的执行链条为:
nb scaffold migration <name> --pkg <pkg> [--on <timing>] → ScaffoldMigration.run() (oclif 参数解析与校验) → runNocoBaseCommand(...) (定位 NocoBase 项目、注入 PATH) → spawn nocobase-v1 create-migration <name> --pkg <pkg> [--on <timing>]即nb scaffold migration本质上是 v1 CLI(cli-v1 包)中create-migration命令的面向开发者封装。
4. 迁移执行时机:beforeLoad / afterSync / afterLoad
--on参数决定迁移在插件生命周期中的挂载时机,三个取值分别对应插件加载流程中的不同阶段:
| 取值 | 含义(结合插件加载流程) |
|---|---|
beforeLoad | 插件load之前执行。适合在插件主体逻辑(如注册模型/资源)之前就需要数据库结构就绪的场景 |
afterSync | 数据库同步(schema sync)完成后执行。适合依赖其他插件/核心模型已同步完成、再补充额外结构的场景 |
afterLoad | 插件load完成之后执行。适合不阻塞插件加载、可延后执行的变更 |
由于文档未逐项展开语义细节,以上阶段划分是结合插件加载流程的通行约定推断出的;实际项目中建议以所用插件server/index.ts中迁移的注册位置为准。如果你不指定--on,则使用底层create-migration的默认行为。
5. 生成的迁移脚本长什么样:参考示例与 API
生成的脚本遵循 NocoBase 的标准Migration结构。仓库中提供了一个可直接运行的完整示例 add-migration.ts,其核心结构如下:
import { DataTypes } from '@nocobase/database'; import { Application, Migration } from '@nocobase/server'; class MyMigration extends Migration { async up() { /* 可用的属性 this.app; this.db; this.queryInterface; this.sequelize; */ await this.queryInterface.createTable('test', { name: DataTypes.STRING, }); } async down() { await this.queryInterface.dropTable('test'); } } app.db.addMigration({ name: 'my-migration', migration: MyMigration, });从该示例可确认迁移脚本的关键要素:
- 继承自
@nocobase/server导出的Migration类,实现up()与down()两个异步方法; this上下文中可用this.app、this.db、this.queryInterface、this.sequelize四个属性,其中queryInterface是操作表结构的常用入口(建表、删表、加字段等);- 通过
app.db.addMigration({ name, migration })将迁移注册到应用数据库管理器; - 数据表结构定义复用
@nocobase/database(Sequelize)的DataTypes。
示例文件头部注释还给出了该示例的运行方式(yarn run:example app/migrations/add-migration migrator up/migrator down),可作为理解迁移如何被驱动的参考。对于插件场景,nb scaffold migration生成的文件会放入--pkg指定插件的迁移目录,并在插件加载流程中按--on指定的时机被调度执行。
6. 实操建议与相关命令
- 先建插件再生成迁移:
nb scaffold migration依赖--pkg指向的插件包,若插件尚不存在,可先用 nb scaffold plugin 命令生成插件脚手架。对照其源码 scaffold/plugin.ts,该命令同样是对底层pm create <pkg>的封装,并支持--force-recreate强制重建。 - 在正确的目录下执行:务必在安装了 NocoBase、且
node_modules/.bin下存在nocobase-v1的项目根目录(或其子目录)中执行命令,否则resolveProjectCwd的向上查找会失败。 - 明确迁移时机:为
--on选择时机时,考虑迁移对其他插件已同步模型的依赖关系——依赖外部结构就用afterSync,希望尽早建表就用beforeLoad。 - 生成后务必复核:脚手架生成的只是骨架,
up/down的具体 DDL 需要按插件需求补全,并确保down与up可互逆,以便安全回滚。
相关文档可进一步阅读:插件开发 与命令参考 nb scaffold plugin。
【免费下载链接】nocobaseNocoBase is an open-source AI + no-code platform for building business systems fast. Instead of generating everything from scratch, AI works on top of production-proven infrastructure and a WYSIWYG no-code interface, so you get both speed and reliability.项目地址: https://gitcode.com/GitHub_Trending/no/nocobase
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考