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 版本的发布流程稳定可靠。

参考资料

相关推荐
猫头虎-前端技术42 分钟前
小白也能做AI产品?我用 MateChat 给学生做了一个会“拍照解题 + 分步教学”的AI智能老师
前端·javascript·vue.js·前端框架·ecmascript·devui·matechat
b***666143 分钟前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
栀秋66644 分钟前
ES6+新增语法特性:重塑JavaScript的开发范式
前端·javascript
爱分享的鱼鱼1 小时前
Vue动态路由详解:从基础到实践
前端
未来之窗软件服务1 小时前
幽冥大陆(三十七)文件系统路径格式化——东方仙盟筑基期
前端·javascript·文件系统·仙盟创梦ide·东方仙盟
维维酱1 小时前
Vite 构建中的两个典型问题:代码分割命名与循环依赖
前端
VaJoy1 小时前
Cocos Creator Shader 入门 (21) —— 高斯模糊的高性能实现
前端·cocos creator
前端加油站1 小时前
使劲折腾Element Plus的Table组件
前端·javascript·vue.js
ze_juejin1 小时前
Angular的Service创建多个实例的总结
前端