Monorepo Beta 版本发布问题排查与解决方案

问题背景

在使用 Lerna + Yarn 管理的 monorepo 项目中,执行 yarn pub-alpha 发布 beta 测试包时,遇到以下问题:

  1. Yarn 警告 :提示项目中存在 package-lock.json,建议不要混用包管理器
  2. 版本不匹配:循环重试 5 次后依然无法安装正确的 beta 版本,最终发布失败
bash 复制代码
尝试安装 @tyc/tyc-sdk@2.0.101-beta.202511280653 (第 5/5 次)
✗ 版本不匹配,等待 5 秒后重试...
发布过程中出错: Error: 无法安装正确版本的 @tyc/tyc-sdk@2.0.101-beta.202511280653

问题分析

问题 1:包管理器混用

现象:

复制代码
package-lock.json found. Your project contains lock files generated by
tools other than Yarn. It is advised not to mix package managers in order
to avoid resolution inconsistencies caused by unsynchronized lock files.

原因:

项目中同时存在 package-lock.json(npm 生成)和 yarn.lock(yarn 生成):

  • 发布脚本使用的是 yarn 命令
  • 但项目中同时维护两种锁文件,导致依赖解析不一致

影响:

  • 不同开发者可能安装不同版本的依赖
  • CI/CD 环境可能安装错误的依赖版本
  • 发布流程中的依赖版本难以保证一致性

问题 2:Beta 版本安装失败

现象:
package.json 中依赖版本写的是 ^2.0.101-beta.202511280653,但实际安装的是正式版 2.0.101。

原因分析:

通过检查安装的实际版本:

bash 复制代码
$ cat packages/adapter/node_modules/@tyc/tyc-sdk/package.json | grep version
  "version": "2.0.101",  # 安装了正式版

$ yarn list --pattern @tyc/tyc-sdk
└─ @tyc/tyc-sdk@2.0.101  # 不是期望的 beta 版本

问题出在两个地方:

  1. 版本范围符号 ^ 的语义问题
    javascript // scripts/publish-alpha.js:137 adapterPackageJson.dependencies["@tyc/tyc-sdk"] = `^${sdkPublishedVersion}`; // 生成: "^2.0.101-beta.202511280653"

使用 ^ 前缀时,Yarn 在解析 semver 范围时:

  • 会寻找满足 ^2.0.101 的最新版本

  • 可能会忽略 -beta.xxx 的 prerelease 标签

  • 优先选择仓库中的正式版 2.0.101

  1. 安装方式不够明确
    javascript // scripts/publish-alpha.js:72 (旧代码) execSync("yarn install --force", { cwd: installPath, stdio: "inherit" });

使用 yarn install 会根据 package.json 中的版本范围进行解析,而不是强制安装指定的精确版本。

解决方案

解决方案 1:统一包管理器

删除所有 package-lock.json 文件,只保留 yarn.lock:

bash 复制代码
# 删除根目录的锁文件
rm package-lock.json

# 删除所有子包的锁文件
find packages -name "package-lock.json" -type f -delete

# 或者手动删除
rm packages/adapter/package-lock.json
rm packages/tyc-sdk/package-lock.json

防止再次生成:

方案 A - 添加到 .gitignore:

gitignore 复制代码
package-lock.json
**/package-lock.json

方案 B - 配置 .npmrc:

ini 复制代码
package-lock=false

解决方案 2:修复版本安装逻辑

修改 scripts/publish-alpha.js:

修改点 1:使用精确版本(移除 ^)

diff 复制代码
  // 发布 adapter beta版本
  const adapterPackageJsonPath = path.join(adapterPath, "package.json");
  const adapterPackageJson = require(adapterPackageJsonPath);
- adapterPackageJson.dependencies["@tyc/tyc-sdk"] = `^${sdkPublishedVersion}`;
+ adapterPackageJson.dependencies["@tyc/tyc-sdk"] = sdkPublishedVersion;
  fs.writeFileSync(
    adapterPackageJsonPath,
    JSON.stringify(adapterPackageJson, null, 2) + "\n"
  );

修改点 2:使用 yarn add 精确安装

diff 复制代码
  for (let i = 0; i < maxRetries; i++) {
    console.log(`\n尝试安装 ${packageName}@${expectedVersion} (第 ${i + 1}/${maxRetries} 次)`);

    // 清除缓存并安装
    try {
      execSync(`yarn cache clean ${packageName}`, { cwd: installPath, stdio: "inherit" });
    } catch (error) {
      // 忽略清除缓存的错误
    }

-   execSync("yarn install --force", { cwd: installPath, stdio: "inherit" });
+   // 使用 yarn add 直接安装指定版本,而不是依赖 yarn install 的解析
+   execSync(`yarn add ${packageName}@${expectedVersion}`, { cwd: installPath, stdio: "inherit" });

    // 验证安装的版本
    if (verifyInstalledVersion(packageName, expectedVersion, nodeModulesPath)) {
      console.log(`✓ 成功安装 ${packageName}@${expectedVersion}`);

关键区别:

方式 命令 行为
旧方式 yarn install --force 根据 package.json 中的版本范围解析,可能选择错误版本
新方式 yarn add @pkg@1.0.0-beta.x 强制安装指定的精确版本,忽略 semver 范围

技术原理

Semver 中的 Prerelease 版本

根据 Semver 规范:

  • ^2.0.101 匹配 >=2.0.101 <3.0.0
  • ^2.0.101-beta.202511280653 的语义较为复杂:
    • -beta.xxx 是 prerelease 标识符
    • 在版本比较时,2.0.101 > 2.0.101-beta.xxx(正式版优先级更高)
    • ^ 范围运算符可能会跳过 prerelease 版本,选择最新的正式版

Yarn 的版本解析策略

Yarn 在执行 yarn install 时:

  1. 读取 package.json 中的依赖声明
  2. 根据 semver 范围查询 registry 中的可用版本
  3. 选择满足范围的"最佳"版本(通常是最新的稳定版)
  4. prerelease 版本通常不会被自动选中(除非显式指定或使用 tag)

而 yarn add @pkg@exact-version:

  • 直接下载指定的精确版本
  • 不进行 semver 范围解析
  • 更新 package.json 为精确版本

最佳实践

1. 包管理器选择

在团队中统一使用一种包管理器:

推荐方式:

  • 使用 Yarn(v1.x 或 v3+)或 pnpm
  • 在 package.json 中添加 packageManager 字段(Node 16.9+):
    json { "packageManager": "yarn@1.22.21" }
  • 使用 Corepack 强制包管理器版本

Git 配置:

gitignore 复制代码
# 只保留一种锁文件
package-lock.json
pnpm-lock.yaml

2. Monorepo Beta 版本发布

依赖版本管理:

javascript 复制代码
// 使用精确版本,而非范围版本
dependencies["@scope/pkg"] = exactVersion;  // ✅ 推荐
dependencies["@scope/pkg"] = `^${version}`; // ❌ 避免(对 prerelease 版本)

安装策略:

javascript 复制代码
// 对于 beta/alpha 版本,使用显式安装
execSync(`yarn add ${pkg}@${version}`);     // ✅ 推荐
execSync(`yarn install --force`);            // ❌ 可能解析错误

发布流程:

  1. 发布 SDK beta 版本到 registry
  2. 清除 yarn cache 确保获取最新版本
  3. 使用 yarn add pkg@version 精确安装
  4. 验证安装的版本是否正确
  5. 构建并发布依赖该 SDK 的包

3. 版本验证

在发布脚本中添加版本验证:

javascript 复制代码
const verifyInstalledVersion = (packageName, expectedVersion, nodeModulesPath) => {
  try {
    const installedPackagePath = path.join(nodeModulesPath, packageName, "package.json");
    if (!fs.existsSync(installedPackagePath)) {
      return false;
    }
    // 清除 require 缓存
    delete require.cache[require.resolve(installedPackagePath)];
    const installedPackage = require(installedPackagePath);
    return installedPackage.version === expectedVersion;
  } catch (error) {
    return false;
  }
};

4. CI/CD 环境配置

确保 CI/CD 环境中:

  • 使用统一的包管理器版本
  • 配置正确的 registry URL
  • 清除缓存后再安装依赖
  • 验证关键依赖的版本

总结

这个问题涉及两个核心点:

  1. 包管理器混用导致锁文件不一致,进而引发依赖解析问题
  2. Semver prerelease 版本的特殊性 :使用范围符号(^/~)+ yarn install 可能无法正确解析 beta 版本

解决思路:

  • 统一包管理工具,只保留一种锁文件
  • 对于 prerelease 版本,使用精确版本 + yarn add 显式安装
  • 添加版本验证机制,确保安装的版本符合预期

通过这些改进,可以确保 monorepo 项目中 beta 版本的发布流程稳定可靠。

参考资料

相关推荐
陈随易40 分钟前
Vue 编译器换掉 source map,快了三四成
前端·后端·程序员
ZhengEnCi2 小时前
12 种高级 UI 交互效果及对应 AI 提示词:从按压回弹到底部抽屉的物理感教学
前端·css·交互设计
IT_陈寒5 小时前
Redis大key删除为什么会导致服务卡顿?我的线上事故复盘
前端·人工智能·后端
用户2705077725245 小时前
MCP 最小 Server 实测:3 个 JSON-RPC 报错与修法
前端
用户499335025075 小时前
一人VIP账号多人共用:Web 代理里的会话共享怎么实现
前端
用户老李5 小时前
无线电自学教程 05:第一台收音机——从矿石机到 SDR
前端
IPIPD6 小时前
Node.js请求遇到503怎么重试?先解析Retry-After,再做有上限退避
前端
葡萄城技术团队6 小时前
dsh-spreadjs-editor 的集成链路:把 SpreadJS Designer 嵌进 Agent 工作台
前端
FlyWIHTSKY6 小时前
vue2和vue3有什么区别,用法上有哪些区别
前端·javascript·vue.js
码云之上6 小时前
10 颗一模一样的「Edit」:中后台 Web Agent 怎么找到对的那一行
前端·人工智能·浏览器