一、为什么要做平滑过渡:从Sinopia转Verdaccio的核心逻辑

很多做前端工程的团队,早期会选Sinopia搭私有npm仓库——毕竟它轻量、部署简单,适合小团队快速起步。但随着项目变多、成员变杂,Sinopia的缺点会越来越明显:比如对npm新特性支持慢、权限管控太粗糙、插件生态差,甚至偶尔会出现包索引丢失的小问题。这时候Verdaccio就成了更合适的选择:它是Sinopia的继任者,不仅兼容Sinopia的大部分核心功能,还补了很多实用能力,比如细粒度权限、多存储后端、插件化扩展。但直接换仓库的风险很高:比如迁移时丢包、切换后旧配置不生效、新老仓库衔接时出现依赖下载失败。所以必须做“平滑过渡”——就是让用户感知不到切换,整个过程不影响正常开发。

1.1 平滑过渡的核心目标

平滑过渡不是“把旧仓库的包拷到新仓库,然后直接切域名”这么简单,核心要满足三个要求:

  1. 迁移过程中,开发、CI/CD流程都能正常拉包、发包;
  2. 新仓库的配置、数据和旧仓库完全兼容,不会出现之前能下载的包现在下不了;
  3. 切换过程可控,出问题能快速回滚到旧仓库。

二、过渡前的准备工作:环境与兼容性校验

过渡前必须先做校验,不然迁移到一半发现配置不兼容,会耽误大量时间。准备工作分两步:先搭新仓库的测试环境,再做配置和数据的兼容性校验。

2.1 搭建Verdaccio测试环境(完整示例)

先搭一个和生产环境配置结构一致的测试仓库,用来验证所有操作。这里用Docker搭建(生产环境也推荐用Docker,管理方便),技术栈为Docker + Node.js(因为Verdaccio是Node.js写的,生产环境需要Node.js运行)。

首先拉Verdaccio的官方镜像,选和生产环境版本尽量接近的版本(比如生产用4.x,测试也用4.x,避免版本差异带来的兼容性问题):

# 拉取Verdaccio 4.x版本镜像,避免版本差异
docker pull verdaccio/verdaccio:4

然后创建本地目录用来持久化存储配置、数据、插件(Docker容器重启后数据会丢,所以要映射到本地):

# 创建三个目录:存储数据、配置、插件
mkdir -p ./verdaccio/data ./verdaccio/conf ./verdaccio/plugins
# 给目录授权,避免容器内进程没有读写权限(Linux/macOS需要,Windows可跳过)
chmod -R 777 ./verdaccio

接着启动测试容器,映射本地目录到容器内的对应路径:

# 启动Verdaccio测试容器,端口映射为4873(默认端口)
docker run -d \
  --name verdaccio-test \
  -p 4873:4873 \
  -v $(pwd)/verdaccio/data:/verdaccio/storage \
  -v $(pwd)/verdaccio/conf:/verdaccio/conf \
  -v $(pwd)/verdaccio/plugins:/verdaccio/plugins \
  verdaccio/verdaccio:4

启动后访问http://localhost:4873,能看到Verdaccio的首页,说明测试环境搭好了。

2.2 配置文件兼容性校验(核心步骤)

Sinopia和Verdaccio的配置文件结构几乎一样,但有几个细节差异,必须提前校验,不然新仓库启动不了或者功能异常。配置文件的核心是config.yaml,我们先把Sinopia生产环境的config.yaml拷到测试环境的./verdaccio/conf目录下,然后做校验。

2.2.1 校验的核心项

  1. 存储路径配置:Sinopia的存储路径是storage: ./storage,Verdaccio的默认存储路径是/verdaccio/storage,所以要把拷过来的config.yaml里的storage字段改成容器内的路径,或者保持相对路径(只要映射正确)。比如原来的Sinopia配置是:

    # Sinopia的旧配置
    storage: ./storage
    auth:
      htpasswd:
        file: ./htpasswd
    

    拷到Verdaccio后,要改成容器内的绝对路径,因为容器内的工作目录和本地不同:

    # Verdaccio的兼容配置
    storage: /verdaccio/storage
    auth:
      htpasswd:
        file: /verdaccio/conf/htpasswd # 把密码文件放到配置目录,方便管理
    
  2. 权限配置:Sinopia的权限配置是packages字段,Verdaccio完全兼容,但要注意一个细节:Sinopia里的allow_access如果是$all,Verdaccio里要改成all,不然会报错。比如原来的Sinopia配置:

    # Sinopia的旧权限配置
    packages:
      '@my-team/*':
        allow_access: $all
        allow_publish: my-team
    

    要改成:

    # Verdaccio的兼容权限配置
    packages:
      '@my-team/*':
        allow_access: all # 把$all改成all
        allow_publish: my-team
    
  3. 代理配置:如果Sinopia配置了代理公共npm(比如proxy: npmjs),Verdaccio完全兼容,不需要改。

2.2.2 校验方法

把修改后的config.yaml放到测试环境的./verdaccio/conf目录下,然后重启测试容器:

# 重启测试容器,加载新的配置
docker restart verdaccio-test

然后用docker logs verdaccio-test查看容器日志,如果没有报错,说明配置兼容。如果有报错,比如“Unknown key: packages.@my-team.allow_access.$all”,就说明是权限配置的问题,按照上面的方法修改即可。

三、数据存储结构转换:从Sinopia到Verdaccio

Sinopia和Verdaccio的存储结构(也就是包的存储方式)几乎一样,但有两个小差异,必须处理,不然新仓库识别不了旧包。

3.1 存储结构的核心差异

Sinopia的存储结构是:

storage/
  @my-team/
    pkg1/
      1.0.0/
        pkg1-1.0.0.tgz
      index.json
    pkg2/
      ...
  pkg3/
    ...

Verdaccio的存储结构和Sinopia完全一致,只有一个差异:Verdaccio会在每个包的目录下生成一个.verdaccio的隐藏文件,用来记录包的元数据(比如发布时间、发布者)。这个文件Sinopia没有,所以迁移后,Verdaccio会自动生成,但如果是旧包,可能会出现元数据丢失的问题。

3.2 数据转换的完整步骤(带示例)

这里用Node.js写一个简单的转换脚本(技术栈:Node.js v16+),用来给所有旧包生成.verdaccio文件,确保元数据完整。

首先,创建一个convert.js脚本,内容如下:

const fs = require('fs');
const path = require('path');

// 配置:Sinopia的存储路径(旧数据),Verdaccio的存储路径(新数据)
const OLD_STORAGE = path.resolve('./sinopia-storage'); // 旧仓库的存储目录
const NEW_STORAGE = path.resolve('./verdaccio-storage'); // 新仓库的存储目录

// 遍历所有包的目录
function traversePackages(dir) {
  // 读取目录下的所有文件和子目录
  const items = fs.readdirSync(dir, { withFileTypes: true });
  items.forEach(item => {
    const itemPath = path.join(dir, item.name);
    if (item.isDirectory()) {
      // 如果是目录,继续遍历
      traversePackages(itemPath);
    } else if (item.name === 'index.json') {
      // 如果是包的索引文件,处理这个包
      processPackage(itemPath);
    }
  });
}

// 处理单个包
function processPackage(indexPath) {
  // 读取旧包的index.json
  const indexContent = fs.readFileSync(indexPath, 'utf8');
  const indexData = JSON.parse(indexContent);
  // 提取包的元数据:最新版本的发布者、发布时间
  const latestVersion = indexData['dist-tags'].latest;
  const latestMeta = indexData.versions[latestVersion];
  const meta = {
    name: indexData.name,
    latest: latestVersion,
    publisher: latestMeta._npmUser, // 发布者信息
    publishTime: latestMeta.time, // 发布时间
    versions: Object.keys(indexData.versions) // 所有版本列表
  };
  // 生成.verdaccio文件的路径:包目录下
  const verdaccioPath = path.join(path.dirname(indexPath), '.verdaccio');
  // 写入.verdaccio文件
  fs.writeFileSync(verdaccioPath, JSON.stringify(meta, null, 2));
  console.log(`处理包:${indexData.name} 完成`);
}

// 开始转换
traversePackages(OLD_STORAGE);
console.log('所有包转换完成');

然后准备旧数据:把Sinopia生产环境的storage目录(比如叫sinopia-storage)拷到本地,放到和convert.js同目录下。

接着运行脚本:

# 运行转换脚本,Node.js版本必须是16+(Verdaccio 4.x要求)
node convert.js

运行完成后,旧的sinopia-storage目录下的每个包目录都会生成.verdaccio文件。然后把转换后的sinopia-storage目录拷到Verdaccio测试环境的./verdaccio/data目录下,覆盖原来的空目录。

最后重启测试容器,访问http://localhost:4873,能看到所有旧包,说明数据转换成功。

四、平滑过渡的完整流程:新老仓库衔接

数据和配置都校验通过后,就可以开始过渡了。过渡的核心是“先双仓库并行,再逐步切换”,避免直接切换带来的风险。

4.1 双仓库并行阶段(核心步骤)

这个阶段,新老仓库同时运行,所有开发、CI/CD流程都能正常拉包、发包,具体步骤如下:

  1. 新仓库上线:把测试环境的配置、数据拷到生产环境,启动Verdaccio生产仓库,端口还是用原来的4873(或者用新的域名,比如verdaccio.my-team.com)。
  2. 配置代理:在Verdaccio生产仓库的config.yaml里配置代理Sinopia生产仓库,这样如果Verdaccio里没有的包,会自动从Sinopia拉取:
    # 代理Sinopia旧仓库
    proxy:
      - http://sinopia.my-team.com:4873
    
  3. 开发端配置:给所有开发人员发通知,让他们把本地的npm仓库地址改成Verdaccio的地址(如果是新域名的话):
    # 把本地npm仓库地址改成Verdaccio的地址
    npm set registry http://verdaccio.my-team.com:4873
    
    同时保留Sinopia的地址作为回滚方案,如果Verdaccio出问题,开发人员可以改回:
    # 回滚到Sinopia旧仓库
    npm set registry http://sinopia.my-team.com:4873
    
  4. CI/CD配置:把CI/CD流程里的npm仓库地址改成Verdaccio的地址,同时在CI/CD里配置回滚逻辑:如果下载包失败,自动切换到Sinopia。比如用GitHub Actions的话,可以这么配置:
    # GitHub Actions的CI配置
    jobs:
      build:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: actions/setup-node@v4
            with:
              node-version: 16
              registry-url: http://verdaccio.my-team.com:4873
          - name: 安装依赖,失败则回滚
            run: |
              npm install || npm set registry http://sinopia.my-team.com:4873 && npm install
    

4.2 逐步切换阶段

双仓库并行运行一周后,确认没有问题,就可以逐步切换了:

  1. 停止Sinopia的发包:在Sinopia的config.yaml里修改权限,禁止所有人发包,这样所有新包都会发到Verdaccio:
    # Sinopia的旧配置,禁止发包
    packages:
      '@my-team/*':
        allow_publish: nobody
    
  2. 停止Sinopia的拉包:运行一周后,确认所有新包都在Verdaccio里,就可以停止Sinopia的拉包服务,然后下线Sinopia仓库。

五、验证与回滚:确保过渡万无一失

过渡过程中,必须做验证,出问题要能快速回滚。

5.1 验证的核心项

  1. 包的完整性验证:拉取所有常用的旧包和新包,确认能正常下载、安装:
    # 拉取一个旧包
    npm install @my-team/pkg1@1.0.0
    # 拉取一个新包
    npm install @my-team/pkg2@2.0.0
    
  2. 权限验证:不同角色的开发人员(普通成员、管理员)分别拉包、发包,确认权限符合预期:
    # 普通成员发包,确认能成功
    npm publish
    # 管理员拉取包,确认能成功
    npm install @my-team/pkg3
    
  3. CI/CD验证:运行所有CI/CD流程,确认能正常构建、部署。

5.2 回滚方案

如果过渡过程中出现问题,比如Verdaccio里的包下载失败、权限异常,要能快速回滚:

  1. 开发端回滚:让开发人员把本地的npm仓库地址改回Sinopia。
  2. CI/CD回滚:把CI/CD流程里的npm仓库地址改回Sinopia。
  3. 新仓库下线:停止Verdaccio生产仓库,下线新仓库,排查问题后再重新过渡。

六、场景分析与注意事项

6.1 适用场景

这个过渡方案适合所有从Sinopia迁移到Verdaccio的团队,尤其是:

  1. 有大量历史包的团队(需要确保旧包能正常使用);
  2. 对开发流程稳定性要求高的团队(不能因为迁移影响正常开发);
  3. 有CI/CD流程的团队(需要确保构建部署正常)。

6.2 过渡方案的优缺点

优点

  1. 平滑无感知:过渡过程中开发、CI/CD流程都能正常运行,不会影响业务;
  2. 风险可控:双仓库并行阶段,出问题能快速回滚;
  3. 兼容性好:提前做了配置和数据的校验,避免了新仓库识别不了旧包的问题。

缺点

  1. 过渡周期长:需要双仓库并行运行一周,才能逐步切换;
  2. 维护成本高:过渡阶段需要同时维护两个仓库,增加了维护工作量。

6.3 注意事项

  1. 版本一致性:测试环境和生产环境的Verdaccio版本必须一致,避免版本差异带来的兼容性问题;
  2. 数据备份:过渡前必须备份Sinopia的所有数据,避免迁移过程中丢包;
  3. 通知到位:提前给所有开发人员发通知,告知过渡的时间、步骤、注意事项;
  4. 监控到位:过渡阶段要监控两个仓库的运行状态,比如包的下载量、错误率,及时发现问题。

七、总结

从Sinopia迁移到Verdaccio的平滑过渡,核心是“提前校验、双仓库并行、逐步切换、快速回滚”。整个过程需要提前搭测试环境,做配置和数据的兼容性校验,然后通过双仓库并行阶段让新老仓库衔接,最后逐步切换到新仓库。过渡过程中要注意版本一致性、数据备份、通知和监控,确保过渡万无一失。这个方案不仅适合Sinopia转Verdaccio,也适合其他私有npm仓库的迁移,只要核心逻辑不变,就能根据具体情况调整。