背景

作者:滴滴-乐扬
一切从Chameleon开始,目前项目组的所有项目都是使用跨多端框架Chameleon,因为某些原因,内部项目使用的Chameleon版本为内源版本,那么就需要用到NPM内部镜像,我司有两个 npm 源,一个叫杭州源,另一个叫北京源。本来各用各的相安无事,直到有一天我们想依赖另一个源的 npm 包时,就相互拉取不到对方的源了。杭州源这边也有做过兼容,当拉取的源不存在时,就尝试去北京源拉取,看似解决了这个问题。但是这只解决了我们依赖北京源的 npm 包的问题,而我们有大量的包需要推广到其它部门,因此我们决定切换到使用人数更多的北京源,目前团队正处于集体转向使用北京源的阶段,不过经常会遇到如下问题:

  1. 项目拉取下来,执行 npm install,提示某些包不存在,然后查看当前源,切换到另一种源,继续 npm install;

  2. 老项目使用杭州源,但是依赖了北京源的 npm 包,虽然杭州源会在拉取不到包时主动向北京源拉取,但是这个机制经常出问题,有时会阻塞服务的构建,得解决这个依赖问题才能重新构建项目;

  3. 新项目使用北京源,但是依赖了杭州源的 npm 包,这个场景比较棘手,北京源没有向杭州源拉取包的机制,因此需要将杭州源的包重新在北京源上发布,如果采用手动发布,版本多的包会很繁琐还容易出错;如果只发布部分包,会导致两边不一致,导致无法拉取到未发布的包;

  4. 发布一个包,只向杭州源发布,那使用北京源的项目很可能更新不到你的新版本,反之亦然;

咋整?

解决思路

为了加快结束这个过渡期,需要我们全面切换到北京源,有两个问题需要解决:

  1. 团队内的杭州源 npm 包的各个版本需要在北京源重新发布一次,除了版本外,dist-tag 也要同步到北京源;

  2. 通知团队成员将项目切换到北京源,并不再使用杭州源,如有遗漏未同步到北京源的 npm 包告知我将其同步;

那么,我们团队有多少个包呢?已知的 @kd scope 下的包有 49 个,而更多的包都在 @dd scope 下,目前收集到的有 50 个。假设平均一个包有 10 个版本,那么粗略估计有上千个版本需要重新发布,所以手动是不可能手动的,得想法子通过自动化的方式批量同步。

来看看到底咋整吧!

实现方式

作为一个前端开发,当然选择 Node.js 来编写脚本或命令行工具解决这种重复劳动了!先来回顾下如何发布一个 npm 包:只要进入 npm 包的根目录,执行 npm publish 即可。所以我们只要找到一个方式能获取到每个包的每个版本的所有代码即可!那有这种方式吗?

有的,有的!回忆下你使用 npm view 查询一个 npm 包时的情形:

聪明的你肯定发现 .tarball: https://registry.npmjs.org/koa/-/koa-2.13.0.tgz,这个包正是当前最新版本 2.13.0 的 koa 包。下载解压后发现这正是我们要的,只要进入该目录执行 npm publish 即可:

那我们能获取指定版本的 tgz 包吗?当然,使用 npm view koa@1.0.0 即可查看指定版本。等等,我没法知道当前 koa 包有哪些版本诶?别慌,试试这个命令 npm view koa --json,答案就藏在 versions 字段里:

此外 dist-tags 字段也需要同步( 默认不同步 tag,这有可能导致杭州源的 tag 覆盖了北京源的 tag,而杭州源的 tag 可能不是最新的)。

目前万事俱备,离搞定只差一个程序猿了!核心逻辑即是根据包名拉取所有版本的 tgz 压缩包,同时解压所有 tgz 压缩包,进入对应目录执行 npm publish:

// 获取该包的所有版本
let result
try {result = await execa.command(`npm view ${npmPacakgeName} --json --registry=${from}`)
} catch (error) {// 查询失败的日志写入文件,方便追踪console.log(chalk.red(`获取列表失败:${npmPacakgeName}`))const content = await fs.readFile(errorLogFile)await fs.writeFile(errorLogFile, `${content}\n获取列表失败:${npmPacakgeName}: ${error.stack}`)return
}const json = result.stdout ? eval(`temp = ${result.stdout}`) : {}const tagTaskList = Object.keys(json['dist-tags']).map(npmPackageTag => {return async () => {try {await execa.command(`npm dist-tag add ${npmPacakgeName}@${json['dist-tags'][npmPackageTag]} ${npmPackageTag} --registry=${to}`)console.log(chalk.green(`成功同步 tag:${npmPacakgeName}@${npmPackageTag}`))} catch (error) {// 发布失败的日志写入文件,方便追踪console.log(chalk.red(`tag 添加失败:${npmPacakgeName}@${json['dist-tags']}`))const content = await fs.readFile(errorLogFile)await fs.writeFile(errorLogFile, `${content}\ntag 添加失败:${npmPacakgeName}@${json['dist-tags']}: ${error.stack}`)}}
})const publishTaskList = json.versions && json.versions.map(npmPacakgeVersion => {// 该包每个版本的处理逻辑return async () => {try {// 如果 ${to}已经存在该版本,则不处理// FIXME: 处理 ${to}不存在该包的情况let resulttry {result = await execa.command(`npm view ${npmPacakgeName}@${npmPacakgeVersion} --registry=${to}`)if (result.stdout) {console.log(chalk.green(`已存在于 ${to},无需同步:${npmPacakgeName}@${npmPacakgeVersion}`))return}} catch (error) {if (!error.message.includes('npm ERR! code E404')) {throw error}}// 去除形如 @scope 的字符串const packagePath = path.join(__dirname, npmPacakgeName, `${npmPacakgeName.replace(/@.*\//, '')}-${npmPacakgeVersion}`)const tgzPath = `${packagePath}.tgz`// 下载包await download(`${from}/${npmPacakgeName}/download/${npmPacakgeName}-${npmPacakgeVersion}.tgz`,npmPacakgeName)// 解压包await compressing.tgz.uncompress(tgzPath,packagePath)// 删除压缩包await execa.command(`rm ${tgzPath}`)// 发布await execa.command(`npm publish --tag=sync --registry=${to}`, {cwd: path.join(packagePath, 'package')})console.log(chalk.green(`${npmPacakgeName}@${npmPacakgeVersion} 成功同步 ${to}!`))} catch (error) {// 发布失败的日志写入文件,方便追踪console.log(chalk.red(`version 发布失败:${npmPacakgeName}@${npmPacakgeVersion}`))const content = await fs.readFile(errorLogFile)await fs.writeFile(errorLogFile, `${content}\nversion 发布失败:${npmPacakgeName}@${npmPacakgeVersion}: ${error.stack}`)}}
})
// 限制并行数为 5,防止 npm 网站报错
await Promise.all(publishTaskList.map(pLimit(5)))
// 修复所有 tag,并行会导致不成功,此处改为串行
// 默认不同步 tag,有可能导致${from}的 tag 覆盖了 ${to}的 tag,而${from}的 tag 可能不是最新的
if (options.syncTag) {await Promise.all(tagTaskList.map(pLimit(1)))
}

请留意并行问题,并发量太大 npm 网站会报错(估计被拦截了)。

如何使用

本来仅提供脚本执行方式,为了让大家也能方便使用(不要再叫我同步了)。贴心的我已把这个脚本实现为一个命令行工具,使用方式如下:

内部工具,此处不提供,大伙可自行实现

安装命令行

npm i @dd/npm-sync -g

执行版本同步

npm-sync packageName

执行版本和tag同步

npm-sync packageName --syncTag=true

更多使用方式

npm-sync --help

作者知乎地址

彩蛋 ~~>

【CML开发者互动季】活动来了,关注CML 官方仓库,Airpods、京东卡等豪礼免费送!
滴滴开源跨端项目Chameleon,已经在代驾、跑腿、顺风车、青桔单车的生产中得到了大量的应用
GitHub仓库:https://github.com/didi/chameleon
CML官网:http://cml.didi.cn/

给你心爱的 npm 包上个『北京户口』相关推荐

  1. webpack创建library及从零开始发布一个npm包

    最近公司有个需求,我们部门开发一个平台项目之后,其他兄弟部门开发出的插件我们可以拿来直接用,并且不需要我们再进行打包,只是做静态的文件引入,研究一波后发现,webpack创建library可以实现. ...

  2. 规范升级 NPM 包

    规范升级 NPM 包 前言 在日常工作中,当组件跨项目使用时,我们往往会选择把组件抽成 npm 包.那么在 npm 开发以及发布的过程中有什么需要注意的事项吗?本文将从我自己的角度,来为大家介绍一下我 ...

  3. npm包的上传npm包的步骤,与更新和下载步骤

    官网: ======================================================= 没有账号可以先注册一个,右上角点击"Sign Up",有账号 ...

  4. verdaccio如何修改上传npm包最大值?

    当出现下面问题错误的时候,说明你的npm包太大,超过限制 413 Payload Too Large - PUT http://xxx- request entity too large 此时找到ve ...

  5. npm包开发测试与发布

    NPM 包开发测试与发布 NPM 包开发测试与发布 引言 1. 开发步骤 1.1. 项目创建 1.2. 工具类功能实现 1.3. ts文件编译 2. npm包本地测试 2.1. 将npm包文件引入项目 ...

  6. 聊一聊工作中如何优雅的通过TDD方式来维护一个高质量的NPM包

    背景 最近一直在采用TDD(测试驱动开发)的方式来维护公司内部的一套通用业务逻辑. 既然是应用到公司实际项目中的,那就不能随随便便搭个脚手架来完成了. 调研过程中,发现TypeScript libra ...

  7. 发布一个持续集成的npm包并加上装逼小icon

    前言 随着入坑时间的增长,很多小伙伴在使用第三方轮子时不满足于下载xxx.js,script标签引入xxx.js,然后使用轮子的原始方式.想要高大上一点,于是小伙伴们跟npm邂逅了.后来有的小伙伴又想 ...

  8. 如何自己写一个公用的NPM包

    以 markdown-clear ,创建过程为例,讲解整个NPM包创建和发布流程 1 如何创建一个包 1.1 创建并使用一个工程 在GitHub上新建一个仓库,其名markdown-clear clo ...

  9. 小程序在输入npm命令_微信小程序使用npm包步骤

    这里以npm引入小程序官方UI组件库weui-miniprogram为例 1.在小程序根目录内,初始化npm(官方文档上是没写出这一步,这里做个补充) npm init 2.在小程序中执行命令安装 n ...

最新文章

  1. UI组件之AdapterView及其子类(六)ExpandableListView组件和ExpandableListActivity的使用
  2. iOS 进阶 - RUNTIME 运行时
  3. Android Studio git 版本回退到最新的版本
  4. 手摸手,带你用vue撸后台 系列一(基础篇) - 掘金
  5. Rethinking the smaller-norm-less-infromative assumption in channel pruning of convolution layers
  6. composition API
  7. java 数组和集合的区别
  8. ps 打开失败 提示:暂存盘已满
  9. 《财富》推荐的75部必看书籍
  10. 【ZZULIOJ】1092: 素数表(函数专题)
  11. Map container is already initialized.
  12. [全程动图]解决Offline Explorer崩溃闪退的问题和一些小技巧(如何下载js、100线程下载)
  13. HTTP–Response详解
  14. #华为模拟器eNSP
  15. GNU binutils工具集
  16. Java 爬虫微信公众号详情,并且破解微信图片跨域问题
  17. 联想台式电脑修复计算机,联想台式机一键恢复,教您怎么使用联想电脑一键恢复...
  18. TFN F7 M1 光时域反射仪 多模OTDR 光纤测试仪 高精度 触摸屏 波长850/1300nm
  19. css filter blur 白边,如何解决CSS3毛玻璃效果blur有白边的问题
  20. 什么是云管平台?业界知名的云管平台品牌有哪些?

热门文章

  1. Eclipse转IDEA开发java项目spring+mybaits项目踩坑记录
  2. 力扣 863. 二叉树中所有距离为 K 的结点
  3. 走进Java接口测试之简单快速的Mock Server Moco
  4. OpenAI CEO Sam Altman:巨型 AI 模型时代即将终结!
  5. dom更新到底在javascript事件循环的哪个阶段?「前端每日一题v22.11.17」
  6. 谈谈对Spring IOC(控制反转)的理解
  7. MySQL下载安装详情教程(Windows)
  8. 索尼xz2android设置向导,安卓9.0上线:索尼XZ2 Premium更新喜迎全面屏新操作!
  9. 西班牙语学习、关系代词que的用法
  10. 初学者如何学好Java