首页
/ Astro Monorepo 的 main→next 合并冲突解决规范:resolve-conflicts 技能全解

Astro Monorepo 的 main→next 合并冲突解决规范:resolve-conflicts 技能全解

2026-09-05 09:20:20作者:彭桢灵Jeremy

本文基于 Astro 仓库中 resolve-conflicts.md 技能文档,系统讲解 Astro monorepo 将 main 分支合并进 next 分支时的冲突解决流程:如何枚举带冲突标记的文件、按 package.json / pnpm-lock.yaml / 源码 / 文档等不同文件类型套用解决规则,以及如何验证并提交给后续编排器。读完后你将掌握这套“版本保留 next、bug 修复吸收 main、锁文件整体弃用”的分支合并方法论,并能理解其背后 Changesets 预发布版本管理的机制依据。

背景:main→next 合并流水线中的定位

Astro 仓库采用双分支发布模式:main 承载稳定版本(stable release),next 承载预发布版本(pre-release,如 6.0.0-beta.4)。定期需要将 main 的修复合并进 next,这一过程由 merge 技能编排,拆分为三个串行子技能(见 merge/SKILL.md):

  1. resolve-conflicts(本文主题)— 解决 git merge origin/main 产生的所有合并冲突;
  2. clean-changesets — 清理已在 main 上发布过的陈旧 changeset 文件(见 clean-changesets.md);
  3. fix-ci — 修复合并后的构建错误、类型错误与测试失败(见 fix-ci.md)。

关键的职责边界是:resolve-conflicts 阶段不安装依赖、不提交、不构建。文档明确规定 “SCOPE: Do not spawn tasks/sub-agents”,且 pnpm install 由编排器(orchestrator)在解决完成后执行。这个边界设计的目的是让冲突解决这一高确定性任务保持纯粹,把不确定性高的依赖树重解析推迟到统一节点进行。

前置条件:任务启动时的工作区状态

文档列出的前置条件(Prerequisites)定义了任务合法启动的全部假设:

条件 说明
branch 分支名参数,例如 ci/merge-main-to-next,即本次合并使用的临时分支
hasConflicts 布尔参数,标识本次合并是否产生了冲突
工作目录 仓库根目录,且已检出(checkout)在合并分支上
合并状态 git merge origin/main 已执行但未提交——冲突标记(<<<<<<< 等)存在于工作树中
依赖状态 依赖尚未安装,禁止运行 pnpm install——安装由编排器在解决完成后负责

其中两条是最容易被违反的:不要运行 pnpm install(依赖树会干扰后续编排),不要自己 git commit(提交权在编排器)。

Step 1:枚举所有带冲突标记的文件

解决冲突的第一步是全量扫描,而不是逐个看 git status 的输出。文档给出的命令按扩展名白名单过滤(TS/JS 源码、Astro 组件、JSON、YAML、Markdown),并排除 node_modules

# List all files with conflict markers
grep -rl "<<<<<<< " . --include="*.ts" --include="*.js" --include="*.mjs" --include="*.cjs" --include="*.md" --include="*.astro" --include="*.json" --include="*.yaml" --include="*.yml" | grep -v node_modules | sort

这条命令覆盖的扩展名集合与 Astro monorepo 的实际构成一致:核心包位于 packages/astro/src.ts 源码、构建配置 .mjs.astro 组件(如 components)、各包 package.jsonpnpm-lock.yaml 以及大量 .md 文档。使用 grep -rl 而非 git diff --name-only 的好处是它同时能捕捉到二进制冲突以外、因内容重叠产生的标记。

Step 2:按文件类型套用解决规则

这是文档的核心。Astro 的规则不是笼统的“以某一侧为准”,而是按文件类型分治

package.json:版本取 next,依赖看源码 import

对每个产生冲突的 package.json

  • version 字段——永远保留 next 的预发布版本(如 5.0.0-beta.1),绝不使用 main 的稳定版本。这是整个规则体系的第一原则:next 分支的身份就是预发布线,若误取 main 的稳定版本号,后续 Changesets 预发布计算会错乱。
  • 依赖项——共享依赖保留 next 的版本。若 main 新增了一个 next 上不存在的依赖,则纳入;若 main 把某个依赖换成了另一个(文档举的例子:get-tsconfigtsconfck),则next 分支源码实际 import 的那个为准——依赖清单必须服从源码引用,而非分支历史。
  • scripts——两侧合并:保留 next 的 scripts,并加入 main 新增的 script。
  • 其他字段——优先 next,除非 main 侧是明显的 bug 修复。

pnpm-lock.yaml:整体放弃手工解决

锁文件是合并冲突中最难手工处理的一类——冲突标记可能散布数百处,且手工拼接极易产生不自洽的条目。文档的策略是彻底的:

Do NOT try to manually resolve the lockfile. Just delete it — the orchestrator will regenerate it via pnpm install --no-frozen-lockfile after you're done.

具体命令为:

git checkout --theirs pnpm-lock.yaml 2>/dev/null || true

这里用 --theirs(即 origin/main 侧)只是给文件一个合法的冲突解除状态以便 git add;真正的锁文件会由编排器依据解决后的 package.json 集合重新生成。这与仓库根 package.jsonversion 脚本的做法一致——根脚本在 changeset version 之后同样执行 pnpm install --no-frozen-lockfile 来重算锁文件,说明“改包清单 → 整体重装”是本仓库处理依赖一致性的标准手段。

源码(.ts / .js / .mjs / .cjs / .astro):修复要过桥,API 听 next

对源代码冲突,文档给出两条互补的原则:

  • main 的 bug 修复——应携带到 next,必要时把修复适配到 next 的 API 上。也就是说,不是整段拷贝 main 的代码,而是把“修复语义”移植过去;
  • next 的 API 变更——保留 next 版本,反过来把 main 的代码适配到 next 的新 API;
  • 拿不准时,一律偏向 next——它是面向未来的分支。

这一原则的边界判断依据是仓库 AGENTS.md 描述的 monorepo 结构:所有包位于 packages/node_modules 中的构建产物对应 packages/ 下的 TypeScript 源码(如 node_modules/astro/dist/...packages/astro/src/...)。因此“next 的源码实际 import 什么”是有据可查的:直接检查 packages/ 下对应源码即可。

Markdown 与配置文件

  • Changesets(.changeset/*.md——本阶段先接受两侧(accept both sides),留给下一阶段 clean-changesets 统一处理;
  • 其他 .md 文件——优先 next

之所以把 changeset 单独摘出来延后处理,是因为它涉及发布版本判断(详见下文的“机制佐证”),在冲突解决阶段做不划算。仓库中确实存在大量此类文件,例如 .changeset/better-lines-show.md,其 frontmatter 形如 '@astrojs/cloudflare': patch

Step 3:逐个暂存已解决的文件

每解决完一个文件立即暂存:

git add <resolved-file>

暂存后 git status 能清晰区分“已解决”与“未解决”的文件,也为 Step 4 的全量校验提供干净的基线。

Step 4:校验无冲突标记残留

文档给出的验证命令同时匹配三种标记(起始标记、分隔线、结束标记),与 Step 1 相同的扩展名过滤逻辑:

grep -r "<<<<<<< \|=======$\|>>>>>>> " . --include="*.ts" --include="*.js" --include="*.mjs" --include="*.cjs" --include="*.md" --include="*.astro" --include="*.json" --include="*.yaml" --include="*.yml" | grep -v node_modules

注意分隔线用的是 =======$(行尾锚定),避免误伤 Markdown 标题分隔等合法用法。若仍有残留输出,回到 Step 2 继续解决。“零残留”是本阶段的完成判据——它保证编排器随后的 pnpm install / pnpm build 不会在任何文件中遇到未解析的冲突标记。

Step 5:止步——不提交、不安装

文档把这一条单列为一步,强调编排器接管后续:提交、pnpm install --no-frozen-lockfile、构建全部发生在此阶段之后。resolve-conflicts 的输出物是已暂存的、无冲突标记的工作树,加上“解决了哪些文件”的清单。

规则背后的机制佐证:来自仓库证据

上述规则并非随意约定,仓库中有多处互相印证的证据。

1. Evals 干跑用例完整演示了一条规则链。 merge 技能的评测集 中第一条用例构造了 packages/astro/package.json 的合成冲突:next 侧为 "version": "6.0.0-beta.4" + tsconfckmain 侧为 "version": "5.14.2" + get-tsconfig + 额外依赖 kleur 与 script test:smoke。预期解法与文档规则逐条对应:保留 6.0.0-beta.4tsconfck(源码 import 的那个),纳入 kleurtest:smoke(新增项),但不引入 get-tsconfig(被换掉的旧依赖)。同一用例还包含 packages/astro/src/core/config/read.ts 的源码冲突:main 侧多了一个空值守卫(if (!result) return undefined),但 next 侧 API 已变为 result.tsconfig——预期解法是移植守卫、保留 next 的字段名,正是“bug 修复要过桥、API 听 next”的教科书式落地。评测断言还明确要求:不执行 pnpm install、不执行 git commit,锁文件走 git checkout --theirs

2. Changesets 机制解释了“先接受、后清理”的原因。 clean-changesets.md 的背景说明指出:当 mainnext 分叉之后发布时,main 已消费掉的 changeset 会以“新文件”形式出现在 next 上;若不清理,next 的预发布会包含已按稳定版发布的版本号,导致类似 @astrojs/sitemap@3.6.1-beta.33.7.0 之后被发布的版本倒挂问题。这与本阶段“changeset 冲突先接受两侧”的约定形成完整闭环。仓库的 .changeset/config.jsonbaseBranchorigin/main,也印证了 main 作为稳定基线的地位。

3. 根目录工具链解释了“install/build 由谁负责”。package.json 显示这是一个 pnpm workspace(工作区定义见 pnpm-workspace.yaml),build 脚本经由 Turbo 按包过滤执行(turbo run build --filter=astro ...),要求 Node >=22.12.0packageManagerpnpm@11.13.1。因此“冲突解决后 pnpm install 和 pnpm build 必须能成功”是有明确执行入口的;而 fix-ci.md 更进一步规定 CI 修复阶段反而禁止再跑 pnpm install--no-frozen-lockfile 会重解析整棵依赖树、破坏传递依赖)——两个阶段的 install 禁令方向相反,恰好说明 resolve-conflicts 阶段“不装”是为了把唯一一次重装收敛到编排器节点。

4. AGENTS.md 提供了命令语境的佐证。 AGENTS.md 中“Running Tests”一节说明单包测试经由 astro-scripts test 执行(如 pnpm -C packages/astro exec astro-scripts test "test/xxx.test.ts"),且修改 packages/ 源码后需 pnpm build 才能生效——这正是后续 fix-ci 阶段验证手段的基础,也说明冲突解决阶段刻意回避这些重命令是合理的。

关键规则速查

文件类型 解决策略 判定依据
package.jsonversion 永远取 next 的预发布版本 next 分支的版本线身份
package.json 依赖 共享依赖取 nextmain 新增依赖纳入;被替换的依赖看 next 源码 import 依赖清单服从源码引用
package.json scripts 两侧合并,main 新 script 追加 功能叠加不冲突
pnpm-lock.yaml 不手工解决,git checkout --theirs 后交编排器重装 手工拼接不可靠
源码 bug 修复移植到 next API;API 变更取 next 拿不准偏向 next
.changeset/*.md 接受两侧,交给 clean-changesets 涉及发布版本判断
其他 .md 优先 next 面向未来的分支
完成判据 全量 grep 零冲突标记 + 全部 git add 保障后续 install/build

输出与移交

阶段完成时的交付物有两部分:一是已解决文件的清单(文档 Output 一节的要求);二是干净的工作树状态——所有冲突标记清除、全部解决文件已暂存、未提交、未安装。编排器随后执行提交与 pnpm install --no-frozen-lockfile,并进入 clean-changesets 与 fix-ci 阶段;若构建或测试仍有失败,将由 fix-ci.md 的“先 diff、后读码、最小修复、目标化验证”流程接管。

掌握本文规则后,你不仅能执行 Astro 仓库的 main→next 合并任务,更能把其中“预发布分支版本保护、锁文件整体弃用、修复语义移植而非整段拷贝”的策略泛化到任何采用 Changesets 预发布工作流的 monorepo 合并场景中。

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