首页
/ Ghost 内部包的 TypeScript 迁移:三提交历史保真与验证工作流实战

Ghost 内部包的 TypeScript 迁移:三提交历史保真与验证工作流实战

2026-09-05 19:29:53作者:范垣楠Rhoda

本文以 Ghost 仓库中 Agent 技能包 convert-internal-package-to-typescript 的配套参考文档《History and verification reference》为主体,讲解在把 packages/ 下的遗留内部包从 JavaScript/CommonJS 迁移到 TypeScript + ESM「黄金路径」时,如何设计可被 Git 追踪的提交序列,以及提交、打包、打包产物三个层面各自的验证手段。读完本篇,你将掌握一套可直接套用的提交拆分方案(含真实提交案例)、git diff --summarygit log --follow 的历史核对命令,以及针对 source 条件、编译产物与运行时资源的完整检查清单,并能对照 Ghost 仓库内已完成的 admin-api-schema 迁移实例逐项印证。

一、背景:黄金路径契约与参考文档的定位

Ghost 的 Node.js 内部库以 packages/README.md 为权威契约:新内部包必须是 private0.0.0 版本、@tryghost/<name> 命名、"type": "module"、源码位于 src/**/*.ts、由 tsc 编译到 build/,且不做独立 npm 发布。入口通过 sourcetypesdefault 三个导出条件暴露,让开发和测试免构建直读原始 TypeScript,而普通 Node 则加载 build/ 下的编译产物。

.agents/skills/convert-internal-package-to-typescript/SKILL.md 定义了完整的迁移工作流:先读透 packages/README.md,再摸清所有 import/require/资产引用与基线测试,然后按「移库 → 改扩展名 → 语义转换」三步提交,并以生产标准完成实现转换。本文聚焦的 history-and-verification.md 正是该技能在规划提交前要求"完整阅读"的参考文档,它回答两个问题:已知的良好提交长什么样,以及每一步如何验证历史与兼容性没有受损

二、Known-good commit shape:三个刻意分离的提交

参考文档给出的基准案例是 admin-api-schema 包的迁移,它使用了三个刻意独立的提交:

  1. 688807ca052 Moved Admin API schema sources from lib to src
  2. be31b36ffab Changed Admin API schema file extensions to TypeScript
  3. b825dafdc19 Converted Admin API schemas to TypeScript

这个顺序的意义在于:让 Git 和审阅者能够区分文件搬迁机械性改名语义转换三类性质完全不同的变更。文档特别强调,这只是"形状"的范例,不是要求复制某个包的实现细节。

对照 SKILL.md 的三步定义,每一步的边界都很严格:

  • 第一步(lib → src):用 git mv 移动源码树,只更新"不改就无法工作"的引用(测试导入、构建输入、包元数据),不改模块语法、不改扩展名、不动实现;
  • 第二步(.js → .ts):同样用 git mv 改名,只添加让改名后的源码能解析、该提交自洽所需的最小配置,行为保持不变,有意义的类型标注与 ESM 转换留给下一步;
  • 第三步(语义转换):应用 packages/README.md 的包契约——共享配置包、最小化的包内配置、ESM 元数据与 exports、标准脚本、单一编译产物——并通过全部机械检查后,才能把包的 migration 状态替换为 ghostPackage.goldenPath: compliant

这三个提交在当前仓库的 git 历史中均可查到,且与文档描述完全吻合。以第一个提交 688807ca052 为例,其 git show --stat 显示全部为 {lib => src}/... 形式的零内容改动纯改名(30+ 个 JSON schema、admin-api-schema.jsschemas/index.js 等),仅根目录转发文件 index.js(2 行改动)和 package.json(4 行改动)有实质修改——这正是"只更新必须变更的引用"的落样。

三、History checks:用 git mv 保住文件谱系

参考文档给出的两条核对命令,在每次机械提交之后都应执行:

git diff --summary HEAD^
git log --follow -- packages/<name>/src/<representative-file>.ts

第一条查看上一个提交的文件级摘要(新增/删除/改名),第二条沿一条代表性文件回溯完整生命周期。文档解释了为什么要如此在意:

Git 记录的是快照而非显式的 rename 操作,因此改名检测依赖相似度。一个在同一次提交里既被移动又被大幅重写的文件,很可能被显示为一次删除加一次新增。要把这些操作拆开,历史才可读。

这里有一个值得记住的合法例外:一个很小的 CommonJS 转发文件,当新的 ESM 入口取代它时,合理地以"删除"形式出现是正常的;此时应验证实现代码的谱系,而不是强行制造一个误导性的 rename。这一点在第三个提交 b825dafdc1(Converted Admin API schemas to TypeScript)中得到了真实印证:其变更统计里 packages/admin-api-schema/index.js 标记为 1 -(该文件被删除),语义转换完成后,原本指向旧实现的 CommonJS 转发壳自然退场,无需也不应伪造它的改名记录。

四、Compatibility checks:source 条件与打包产物双向验证

文档要求"在决定输出格式之前先检查真实的消费者",并给出两阶段验证流程:

阶段一:worktree 验证。 先确认一个使用 source 导出条件的消费者,能在开发/测试中解析并加载原始 TypeScript。对照 packages/admin-api-schema/package.json,其 exports 声明为:

{
  "source": "./src/index.ts",
  "types": "./build/index.d.ts",
  "default": "./build/index.js"
}

source 条件在条件顺序上最先,供 monorepo 内免构建开发使用;普通 Node 忽略它、走 default 加载编译产物。

阶段二:构建并打包(build & pack),针对打包产物验证四件事:

  • 普通 Node 能 import 编译后的 ESM 入口;
  • 既有的 CommonJS 消费者能在 Ghost 支持的 Node 版本上 require() 编译入口;
  • 编译后的模块图中不含顶层 await
  • 打包件中的 typesdefaultmain 三个目标都指向产物中真实存在的文件。

为什么顶层 await 是硬性约束?packages/README.md 的"TypeScript and ESM"一节解释了底层机制:Ghost Core 本身是 CommonJS,但运行在支持 require(esm) 的 Node 版本上,因此一套 ESM 产物即可同时服务 importrequire() 两类消费者——前提是整条被引入的模块图都没有顶层 await,且该限制由 ESLint 强制执行。文档同时明确:不要投机性地增加第二套 CommonJS 构建;确有必要时,把例外记录进包 README 和配置并加以测试。

五、Assets and packaging checks:build/ 是运行时唯一事实源

文档要求从干净的包输出开始构建,并逐个检查 build/ 中的运行时文件。对 JSON schema 这类资产,策略是"编译器能输出就尽量从 TypeScript 直接导入;否则使用显式的、可移植的复制步骤"。

admin-api-schema 是前者的现成范例。src/schemas/index.ts 通过 import attributes 从 TypeScript 直接导入 34 个 JSON schema 文件(如 import posts from './posts.json' with { type: 'json' };),配合 tsconfig.jsonrootDir: src / outDir: build 的设置,tsc 会把 JSON 一并输出到 build/schemas/,无需额外复制步骤。

同一个文件还展示了 SKILL.md 中"用显式的类型化注册表替代动态 CommonJS 发现"的落地方式:schemas 对象以 as const 固定形状,SchemaName = keyof typeof schemas 派生出精确的键类型,actionSchemaNames 数组用 as const satisfies readonly SchemaName[] 做双重校验——注册表即 API,运行时不存在"扫目录找文件"的猜测逻辑。上层入口 src/index.tsget()/list()/validate() 全部构建在这一显式注册表之上,未知名称走类型守卫返回 null 或抛出结构化的 IncorrectUsageError

文档给出的最终打包核对清单:

  • 生产执行不得从 src/ 读取
  • files 字段包含构建产物,排除手写源码(除非包契约明确另有规定)——admin-api-schema"files": ["build"] 即符合此条;
  • 消费者行为要针对打包件测试,而不只是 worktree;
  • 有现成的 pack 或 release-archive 检查时,优先复用仓库已有手段。

六、Review the final diff:PR 提交前的逐项自查

参考文档的最后一节要求:开 PR 之前,每个提交独立看一遍,总 diff 也看一遍,并专门排查以下六类问题:

  1. 意外的格式变更(formatting churn);
  2. 被放松的编译器或 lint 规则;
  3. 无解释的 unknown 或类型断言;
  4. 过期的 CommonJS 配置残留;
  5. 缺失的运行时资产;
  6. 仍然绕过包 exports 的消费者。

这份清单与 SKILL.md 的"Hold the conversion to production standards"标准互为表里:后者要求建模真实的输入输出与注册表形状、缺失类型不得用 any 兜底、unknown 只允许出现在真正的不可信边界并尽快收窄、禁用宽泛断言 / 非空断言 / @ts-ignore / lint 关闭、除非显式在范围内否则保持运行时 API 不变、以及不得为了通过转换而放松共享的 TypeScript / ESLint / Vitest 规则

完成自查后的验证动作包括:运行 packages/README.md 要求的 pnpm build / pnpm test / pnpm lint;让代表性消费者走它们生产环境真实的 import/require() 路径;若 source 与编译产物两种解析方式都被使用,则两条都测;导出、运行时资产或归档包含关系发生变化时,检查打包包或 Ghost release 组件;工作区构建图变化时运行仓库级构建。最后,SKILL.md 补充了合并层面的约定:当这些提交各自独立有效且顺序刻意时,现代化 PR 可以采用 rebase merge;这不改变其前置 subtree 历史导入仍需 merge-commit 的要求。

七、小结:机械变更与语义变更严格分层的工程价值

history-and-verification.md 放回 Ghost 的迁移语境,它的核心思想可以浓缩为一句话:让每一类变更在 Git 历史里各归其位。Git 的 rename 检测基于快照相似度,任何"移动 + 重写"的混合提交都会破坏谱系;而 admin-api-schema 三个提交(688807ca052be31b36ffab825dafdc1)证明了这种拆分是可执行、可验证的——纯改名的 git diff --summary、CommonJS 转发文件的合理删除、JSON 资产经 import attributes 进入 build/source/types/default 三条件在打包件中的落地,都能从当前仓库源码与 git 历史中直接复核。对于任何想把遗留包迁到 TS + ESM 黄金路径的团队,这套"三提交形状 + 历史核对 + 双向兼容验证 + 资产打包清单 + diff 自查"的组合,本身就是可直接移植的方法论。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384