Repository Wiki
ChanIok/SpinningMomo

代码生成与格式化流水线

SpinningMomo 仓库中的代码生成与格式化流水线由三部分组成:Node 驱动的代码生成脚本(迁移代码、内嵌本地化、地图注入代码)、pnpm 顶层构建/发布脚本链(C++ / Web / Android / 安装包),以及基于 husky + lint-staged 的提交前格式化钩子(C++ 与 Web 各自的格式化入口)。三者共同保证「源文件变更 → 重新生成派生产物 → 提交时自动格式化 → 构建发布」这条链路可重复执行。

Purpose and Scope(目的与范围)

本页面覆盖以下内容:

  • 三个代码生成脚本的职责、触发条件与源/目标文件关系:scripts/generate-migrations.js、scripts/generate-embedded-locales.js、scripts/generate-map-injection-cpp.js。
  • 仓库根 package.json 中的顶层构建、发布、格式化脚本及其相互调用关系(build → build:cpp / build:web / build:android / build:dist,release → 完整发布链)。
  • 提交前格式化机制:husky + lint-staged 如何在 commit 时对 C++ 源码与 Web 前端源码分别调用格式化脚本。
  • AGENTS.md 中与生成/格式化强相关的工程约定:C++ 头文件包含顺序、命名规范、注释规范——这些正是格式化与生成产物需要遵守的约束。

有意留给兄弟页面的内容(本页不展开):

  • C++ 后端的具体 xmake 构建/链接细节与 tasks/ 自定义任务 → 见「构建与发布」相关页面。
  • Android 捕获守护进程(momo-capture)的源码架构 → 见 android/capture/README.md 所在的 Android 页面。
  • 安装包(MSI / WiX bundle)打包细节 → 见安装器相关页面。
  • 代码生成脚本读取的领域内容本身(数据库迁移 SQL、i18n 词条、地图注入 JS 的业务逻辑)→ 见各自的功能页面。

Overview(概述)

SpinningMomo 是一个 Win32 C++ 后端 + Vue 3 Web 前端的双进程应用,另含 Android 捕获守护进程。这个多语言组合带来了一个典型问题:部分 C++/编译期产物并非手写,而是由 Node 脚本从源头文件生成的。如果开发者修改了源文件却忘记重新生成,就会出现「运行时数据与源码不一致」的隐蔽 bug。因此 AGENTS.md 明确列出必须重新运行的生成脚本清单。

仓库通过以下命令组织整个流水线:

1# C++ backend — debug 2xmake build 3 4# C++ backend — release 5xmake release 6 7# Web frontend 8pnpm run build:web 9 10# Android capture service 11pnpm run build:android

Source: AGENTS.md

格式化方面,仓库采用「手动全量格式化命令 + 提交时增量格式化」的双层策略:根目录提供 format:cpp 与 format:web 两个脚本入口,同时通过 husky 挂载的 lint-staged 在每次 git commit 时对本次改动的文件按语言分别格式化。

Architecture(架构)

Loading diagram...

图中各层职责说明:

  • sg_Sources → sg_Codegen → sg_Generated:单向数据流。三个生成脚本分别消费 SQL 迁移、本地化 JSON、地图注入 JS 源文件,产出供 C++ 编译使用的生成物。generate-map-injection-cpp.js 会同时「重新生成压缩后的 JS 和对应的 C++ 头文件」。
  • sg_Build:pnpm run build 是一个聚合脚本,按 build:cpp → build:web → build:android → build:dist 顺序串联;build:cpp 内部先以 release 模式配置再切回 debug 模式。
  • sg_Format:husky 提供 git 钩子基础设施,lint-staged 负责「只格式化本次暂存区命中的文件」,分别路由到 C++ 格式化脚本与 Web 格式化脚本。
  • sg_Release:pnpm run release 在 build 基础上叠加便携版、安装包与校验和生成。

构建脚本链的依赖关系

构建脚本链定义在仓库根 package.json 中:

json
1{ 2 "scripts": { 3 "prepare": "husky", 4 "build": "pnpm run build:cpp && pnpm run build:web && pnpm run build:android && pnpm run build:dist", 5 "build:cpp": "xmake config -m release && xmake build && xmake config -m debug", 6 "build:android": "node scripts/build-android.js", 7 "build:web": "pnpm --filter web run build", 8 "build:dist": "node scripts/prepare-dist.js", 9 "build:portable": "node scripts/build-portable.js", 10 "build:installer": "node scripts/build-installer.js", 11 "build:checksums": "node scripts/generate-checksums.js", 12 "release": "pnpm run build && pnpm run build:portable && pnpm run build:installer && pnpm run build:checksums", 13 "release:version": "node scripts/release-version.js", 14 "format:cpp": "node scripts/format-cpp.js", 15 "format:web": "pnpm --filter web exec prettier --write ." 16 } 17}

Source: package.json

要点:

  • build 使用 && 串联而非并行,保证 C++ 后端先行编译——Web 产物与 dist 准备在其后进行,任一环节失败即中止整链。
  • build:cpp 在一条命令内完成「release 编译 + 恢复 debug 配置」,避免开发者在 release 构建后忘记切回,导致后续调试构建模式错乱。这是一个有意的状态复原设计。
  • format:web 通过 pnpm --filter web exec 在 web/ 子包上下文中执行 prettier,而不是在仓库根执行——格式化规则归属前端工作区,与根目录的 C++ 格式化互不干扰。
  • prepare 脚本在 pnpm install 时安装 husky,是格式化钩子生效的前提。

代码生成脚本

AGENTS.md 中「Code Generation Scripts」一节是本流水线的核心约束:必须在其源文件变更后重新运行对应脚本。

These must be re-run when their source files change:

  • node scripts/generate-migrations.js — after modifying src/migrations/*.sql
  • node scripts/generate-embedded-locales.js — after modifying src/locales/*.json (zh-CN / en-US)
  • node scripts/generate-map-injection-cpp.js — after modifying web/src/features/map/injection/source/*.js (regenerates minified JS and its C++ header)
脚本触发源文件生成产物说明
scripts/generate-migrations.jssrc/migrations/*.sql自动生成的数据库迁移代码AGENTS.md 提到数据库迁移系统为「auto-generated migration system」
scripts/generate-embedded-locales.jssrc/locales/*.json内嵌本地化(zh-CN / en-US)数据将 JSON 词条编译进 C++ 侧
scripts/generate-map-injection-cpp.jsweb/src/features/map/injection/source/*.js压缩(minified) JS + 对应 C++ 头文件前端注入脚本被嵌入后端

Source: AGENTS.md

为什么需要「源变更即重新生成」

SpinningMomo 的数据库层使用「SQLite + SQLiteCpp + 线程本地连接 + DataMapper(ORM 风格行映射)+ 自动生成迁移系统」的组合;本地化数据与地图注入脚本同样被嵌入 C++ 编译产物。这些派生文件一旦与源文件脱节,编译仍会成功,但运行时行为(数据库 schema、界面语言、地图注入逻辑)与仓库中手写的源不一致,属于难以察觉的一致性缺陷。因此流水线把「重新生成」定义为开发者的硬性义务,而不是构建系统自动完成的步骤——AGENTS.md 同时声明了 Build Policy:不要自动运行构建,由用户确认或手动执行,这与生成脚本的显式触发策略保持一致。

生成脚本在构建中的位置

三个生成脚本不在 pnpm run build 聚合链中,它们是构建前的前置步骤。build:cpp(xmake)编译时消费其产物:

Loading diagram...

提交前格式化(husky + lint-staged)

仓库使用 husky 承载 git 钩子,lint-staged 决定「哪些暂存文件交给哪个格式化器」:

json
1{ 2 "devDependencies": { 3 "esbuild": "^0.28.2", 4 "fast-glob": "^3.3.2", 5 "husky": "^9.1.7", 6 "lint-staged": "^16.2.4" 7 }, 8 "lint-staged": { 9 "src/**/*.{cpp,h,hpp}": [ 10 "node scripts/format-cpp.js --files" 11 ], 12 "web/**/*.{js,ts,vue,json,css,md}": [ 13 "node scripts/format-web.js" 14 ] 15 } 16}

Source: package.json

两条格式化路由

暂存文件模式格式化命令说明
src/**/*.{cpp,h,hpp}node scripts/format-cpp.js --filesC++ 后端源码;--files 表示以传入文件列表模式运行(与全量 format:cpp 区分)
web/**/*.{js,ts,vue,json,css,md}node scripts/format-web.jsWeb 前端源码,包装脚本在 web 工作区上下文执行 prettier

设计意图:

  • 按语言分治:C++ 与 Web(TS/Vue/JSON/CSS/MD)使用完全不同的工具链,lint-staged 的 glob 把每类文件路由到各自的格式化器,避免在错误的工具上浪费时间或产生不兼容的格式改写。
  • 增量而非全量:只格式化本次提交涉及的文件,保证提交速度快,也避免无关文件被无关紧要的格式变更污染 diff。
  • --files 双模式:scripts/format-cpp.js 同时是全量入口(pnpm run format:cpp,不带参数时全仓库扫描)与增量入口(lint-staged 传入文件列表并附带 --files 标志)。这是同一个脚本服务两种调用方的典型设计。
  • esbuild + fast-glob 作为 devDependencies 出现在根目录,与 Node 脚本族的实现高度相关:fast-glob 用于快速枚举待处理源文件,esbuild 用于 generate-map-injection-cpp.js 中的 JS 压缩环节(生成 minified JS)。

实现细节说明:scripts/format-cpp.js、scripts/format-web.js 及三个生成脚本的内部实现未在本页读取范围内(源文件探索预算已用尽),上文对其行为描述仅基于 package.json 中的脚本定义与 AGENTS.md 中的官方约定。脚本内部的 CLI 参数、格式化规则文件位置等细节请直接查阅对应脚本。

格式化必须遵守的代码约定

格式化脚本输出的是「符合仓库约定」的代码,AGENTS.md 对这些约定有明确定义,理解它们有助于判断格式化结果的正确性:

C++ 头文件包含顺序

The matching header first in .cpp, then vendor/std.hpp, remaining vendor headers, and project headers.

即 .cpp 文件中:① 与之同名的头文件;② vendor/std.hpp;③ 其余 vendor 头;④ 项目头。这一顺序与「每个项目头必须不依赖 PCH 即可自包含」的约束配合工作。

命名规范

类别规范示例
C++ 命名空间lower snake casefeatures::gallery、core::http_server
C++ 类型PascalCaseGalleryState、RpcRequest
C++ 文件/函数snake_casegallery.hpp、initialize()
前端组件PascalCaseGalleryPage.vue
前端模块camelCasegalleryApi.ts

Source: AGENTS.md

注释规范

Comments should describe intent and logic (why / what), not restate what the code already shows (how). When changing code, update related comments so they stay in sync with the implementation.

即:注释描述意图与逻辑(为什么/做什么),而非复述代码已展示的实现方式;改代码时必须同步更新相关注释。这条规范与生成脚本紧密相关——生成的迁移/本地化/注入代码同样不应携带复述式注释。

发布链与生成/格式化的衔接

pnpm run release 定义了完整发布序列:

release = build && build:portable && build:installer && build:checksums

其中:

脚本实现说明
build聚合脚本build:cpp → build:web → build:android → build:dist 顺序执行
build:portablescripts/build-portable.js生成便携版
build:installerscripts/build-installer.js构建 MSI 与 WiX bundle 安装包,输出至 dist/;支持 --msi-only 与 --version X.Y.Z 覆盖 version.json
build:checksumsscripts/generate-checksums.js生成发布产物校验和
release:versionscripts/release-version.js版本号管理独立入口

构建输出目录约定(AGENTS.md「Build Output」节):Release 为 build\windows\x64\release\,Debug 为 build\windows\x64\debug\,Android 守护进程为 build\android\momo-capture.jar,分发产物在 dist/。

Loading diagram...

注意图中的关键点:生成脚本的执行发生在开发阶段而非发布脚本内部。发布链假设生成产物已是最新;这正是 AGENTS.md 把「必须重新运行」写成显式清单的原因——它是流水线中唯一依赖人工纪律的环节。

Failure Modes(失败模式与边界情况)

基于已收集的源证据,本流水线存在以下需要注意的失败模式:

  1. 忘跑生成脚本(最主要风险):修改 src/migrations/*.sql、src/locales/*.json 或 web/src/features/map/injection/source/*.js 后未运行对应脚本。编译不会失败,但运行时行为与源文件不一致。缓解方式是 AGENTS.md 的显式清单 + code review 约定。
  2. 构建模式残留:build:cpp 在 release 编译后自动 xmake config -m debug 复原配置,防止开发者直接跑 xmake build 时仍处于 release 模式。绕过 build:cpp 手动执行 xmake config -m release && xmake build 则可能把配置残留在 release。
  3. && 短路中止:聚合脚本使用 && 串联,任何一个环节非零退出都会中止整链。这是有意的 fail-fast 设计,但对「部分成功状态」(例如 cpp 已构建而 web 未构建)需要使用者自行感知。
  4. 场景测试依赖 Release 构建:AGENTS.md 明确「content-hash 语义只在 Release 下成立」,跑 pnpm run test:scenarios 前必须先 xmake release,否则测试语义不成立。这也解释了为何 build:cpp 先编译 release。
  5. 格式化钩子依赖 pnpm install:husky 通过 prepare 脚本安装;未执行过 install 的克隆仓库不会触发提交前格式化,C++ 与 Web 源码可能绕过格式约束。
  6. lint-staged 文件类型边界:lint-staged 只覆盖 src/**/*.{cpp,h,hpp} 与 web/**/*.{js,ts,vue,json,css,md}。根目录脚本、scripts/*.js、其他目录的代码不会被提交时格式化。

Configuration Options(配置项)

配置项位置默认值/示例说明
Node 版本要求package.json engines.node>=22.13.0所有生成/构建/格式化脚本的运行时前提
包管理器package.json packageManagerpnpm@11.21.0使用 corepack 固定的 pnpm 版本
C++ 格式化 globpackage.json lint-stagedsrc/**/*.{cpp,h,hpp}提交时进入 format-cpp.js --files 的文件集
Web 格式化 globpackage.json lint-stagedweb/**/*.{js,ts,vue,json,css,md}提交时进入 format-web.js 的文件集
husky 启用package.json preparehuskypnpm install 时自动安装钩子
安装包版本覆盖build-installer.js--version X.Y.Z覆盖 version.json;--msi-only 跳过 bundle
生成脚本触发源AGENTS.md Code Generation Scripts 节见上文表格源文件变更 → 必须重跑的对应关系

API / 命令参考

本流水线的对外接口是 pnpm 脚本与 Node CLI 脚本,而非函数 API。

pnpm run build

聚合构建:build:cpp → build:web → build:android → build:dist,&& 串联 fail-fast。

pnpm run release

发布链:build → build:portable → build:installer → build:checksums,产物输出至 dist/。

node scripts/generate-migrations.js

修改 src/migrations/*.sql 后必须重新运行,生成数据库迁移代码。参数与内部行为未在已读取源范围内。

node scripts/generate-embedded-locales.js

修改 src/locales/*.json(zh-CN / en-US)后必须重新运行,生成内嵌本地化数据。

node scripts/generate-map-injection-cpp.js

修改 web/src/features/map/injection/source/*.js 后必须重新运行,同时重新生成压缩后的 JS 与其 C++ 头文件。

pnpm run format:cpp / node scripts/format-cpp.js --files

C++ 格式化双模式入口:不带参数(经 format:cpp)时全量执行;lint-staged 调用时带 --files 并传入暂存文件列表做增量格式化。

pnpm run format:web / node scripts/format-web.js

Web 格式化:format:web 在 web/ 工作区以 prettier 全量写入;format-web.js 为 lint-staged 的增量包装。

说明:各 Node 脚本的完整参数签名(如 format-cpp.js --files 之外是否还有其他标志)在本次未读取的 scripts/*.js 实现中,此处不臆测。

  • AGENTS.md — 仓库工程约定总纲:构建策略、代码生成脚本清单、注释与命名规范、测试策略。
  • package.json — 顶层脚本链与 lint-staged/husky 配置。
  • docs/developer/architecture.md — 完整环境搭建步骤(AGENTS.md 指引的权威来源)。
  • 构建与发布、Android 捕获守护进程、安装器打包等主题由对应兄弟页面展开。

Sources

(2 files)