升级

用 git 把新版 ShipKit 合并进你的项目:把发布仓库加为 remote,合并版本 tag,解决冲突,并把迁移一起带过来。

你的项目和 ShipKit 共享代码,所以新版本的到来方式和任何上游更新一样:一次 git 合并。你的提交历史原样保留,模板的改动叠加在上面,两边改到同一处时 git 会明确指出来。

版本是怎么发布的

购买后,你的 GitHub 账号会被邀请为 ShipKit 发布仓库的协作者。仓库里每个版本都是 main 上的一个提交,打着 v<version> 的 tag(v0.5.0、v0.6.0……),并在仓库的 Releases 页面附有发布说明,列出新增的迁移、新增或改名的环境变量,以及破坏性变更。版本号遵循 semver:minor 版本可能带迁移和可选的环境变量;major 版本意味着有破坏性变更,或新增了必需的环境变量。

合并之前先读发布说明。它会告诉你冲突大概出现在哪里,合并后还要做什么。

添加 remote(只需一次)

用你受邀的那个发布仓库的 SSH 或 HTTPS 地址:

git remote add shipkit <release-repo-url>
git fetch shipkit --tags
git tag -l 'v*'

如果你的项目是直接克隆发布仓库得来的,origin 已经指向它了。把它改名为 shipkit,再把你自己的仓库加为 origin:

git remote rename origin shipkit
git remote add origin <your-repo-url>

合并前想先看看某个版本改了什么:

git diff v0.5.0 v0.6.0 --stat

合并一个版本

从干净的工作区出发,单独开一个分支,这样升级出了问题也没有任何损失:

git switch -c upgrade-v0.6.0

第一次合并怎么做,取决于你的项目是怎么开始的。

项目是克隆来的

你的历史里已经包含了起步时的那个模板提交,git 有共同祖先,只会给你看那之后的变化:

git merge v0.6.0

以后每次升级都是同一条命令,换成下一个 tag 即可。

项目是复制来的

如果你是下载了文件,或者把文件复制进一个新的 git init 仓库,那你的历史和模板的历史毫无关联,直接合并会报 “refusing to merge unrelated histories”。需要显式允许:

git merge v0.6.0 --allow-unrelated-histories

没有共同祖先,git 就分不清哪些是你的修改、哪些是模板的变化,两边只要有差异的文件都会变成冲突。如果你知道自己复制的是哪个版本,有更好的办法:先把那个版本记为“已合并”,不改动你的任何文件,再合并新版本:

git merge v0.5.0 --allow-unrelated-histories -s ours -m "Record ShipKit v0.5.0 as merged"
git merge v0.6.0

第一条命令只是把两段历史连起来(-s ours 让你的文件保持原样)。第二条就成了普通的三方合并,只带进 v0.5.0 到 v0.6.0 之间的变化。经过这一次,你的项目就和克隆来的一样了:以后每次升级都是 git merge <tag>。

解决冲突

冲突集中在你改过模板的地方。几种常见情况:

  • src/config/*(app-config.ts、plans.ts、landing.ts):几乎总是要保留你的值,再加上模板新增的字段。

  • messages/en.json、messages/zh.json:两边都加了键。两组都保留,注意别漏了逗号。

  • bun.lock:不要手改。取模板的版本,再让 bun 根据 package.json 把你自己的依赖加回来:

    git checkout --theirs bun.lock
    bun install
    
  • 你删掉的模块:如果新版本改了你已删除的功能里的文件,git 会报 “deleted by us”。用 git rm 让它保持删除状态,再搜一下新版本有没有往共享文件里加新的 [feature: <name>] 块,一并删掉。每个模块牵涉哪些文件,见删除功能。

  • .dev.vars.example:取模板的版本,再把新增的键抄到你自己的 .dev.vars 里。

所有文件都解决后,git add 再 git commit。中途想放弃,git merge --abort 会回到合并前的状态。

迁移

迁移文件在 drizzle/ 里,按编号排列,drizzle/meta/ 里有对应的 snapshot 和 journal 记录。D1 按文件名记住哪些迁移已经执行过。

如果你没有自己的迁移,drizzle/ 目录直接用模板的,然后执行:

bun run db:migrate:local

如果你加过自己的迁移,新版本的迁移文件会和你的撞车:两边都占了下一个编号,drizzle/meta/_journal.json 也会冲突。你的迁移已经在自己的数据库上执行过了,所以保留它们,把模板的改动重新生成为你的下一个迁移:

  1. drizzle/meta 里两边都改过的文件保留你的版本:git checkout --ours -- drizzle/meta/_journal.json,git status 里显示为 “both added” 的 snapshot 也照此处理。
  2. 找出新版本新增的迁移文件——drizzle/ 里的 .sql 文件,以及 drizzle/meta/ 里只有新版本才有的 snapshot(git status 会把它们列为新文件,发布说明里也会写出迁移名)。先读一遍那些 .sql,再用 git rm 全部删掉。
  3. 确认 src/ 里是合并后的 schema——各功能的 schema.ts 就是普通源码,和其他文件一样参与合并。
  4. 运行 bun run db:generate。它会拿合并后的 schema 和你最新的 snapshot 比较,把模板的改动写成排在你之后的新迁移。
  5. 如果删掉的文件里有数据变更(用 UPDATE 或 INSERT 做的回填),db:generate 是看不到的。用 bunx drizzle-kit generate --custom --name <name> 建一个空迁移,按新版本里的顺序把语句粘进去。

然后执行 bun run db:migrate:local。迁移如何生成和执行,见数据库。

上线前检查

安装依赖,重新生成生成物,跑完整的测试:

bun install
bun run cf-typegen          # wrangler.jsonc 有变化时
bun run build               # 顺带生成 src/paraglide,typecheck 需要它
bun run typecheck
bun run check
bun run test
bun run e2e

之后重启开发服务器:环境变量只在启动时读取。把你改过的页面都点一遍——测试覆盖的是模板本身的行为,而不是你的改动。

部署升级

把分支合并到 main,然后:

bunx wrangler secret put <NEW_KEY>   # 发布说明里每个新增的键
bun run db:migrate:remote
bun run deploy

和任何 schema 变更一样,先迁移再部署。如果由 CI 负责部署,远程有未执行的迁移时它会拒绝部署。见部署。

这类工作交给编程 agent 做得不错。把本页和发布说明给它,并要求它遇到拿不准的冲突时停下来问你,而不是自己猜。见配合 AI 编程助手。