搭建NPM私有仓库服务
一、背景
1.阿里云效NPM仓库不稳定,流水线莫名出现第三方包找不到的问题,阿里云长时间未能解决。
2.云效仓库突然对yarn的支持不友好,使用yarn装包频繁出现打包失败的问题,个别流水线必现。
3.目前的npm包缺乏包相关的使用文档(类似于npm仓库,搜索包时会有包内的README使用说明)。
二、私有仓库技术选型
常用的NPM私有服务一般基于三种框架:Verdaccio、Cnpm和Nenus。各个框架有如下特点
Nenus
- Java社区的一个方案,可以用于Maven、npm多重类型的仓库
- 界面比较丑
- 配置相对比较复杂
Cnpm
- 支持静态配置型用户管理机制
- 分层模块权限设置
- 公共模块镜像更新
- 私有模块管理
- 支持拓展多重存储形式
- 需要配置数据库,部署过程比较复杂
Verdaccio
- 基于Sinopia仓库进行改造,Sinopia是一个很老的NPM仓库,目前已经停止维护,最后一次代码更新是7年前
- 偏向于零配置、轻量型
- 不需要额外的数据库配置,内部自带小型数据库
- 支持私有模块管理
- 支持缓存使用过的公共模块
- 发布及缓存的模块以静态资源的形式本地存储
基于我们的情况,
1:哈哈,其实有三条,这里保密
2:······
3:······
结论:基于Verdaccio搭建我们的私服系统
三、私服架构图

四、搭建实操
// 全局安装verdaccio包,这样就能在任意路径执行verdaccio指令 yarn add verdaccio -g
verdaccio -v // 安装成功的话会输出版本号信息
创建私服目录,一般是在Linux服务器上,找到你认为合适的工作目录,我在服务器上的某个文件夹中创建了如下文件
/config.yaml // verdaccio服务配置 /password // 密码存放文件 /npmServer.sh // 服务启动脚本 /storage // 数据库文件夹 /verdaccio.log //仓库运行日志记录文件
这里面最重要的是config.yaml,因为这个文件决定了我们的私服是以怎样的模式运行的。初次运行verdaccio时,会生成一个默认配置文件,这里我就给大家展示了,直接看我是怎么根据我们的需求进行个性化配置的。
以下代码已经过脱敏处理
#
# This is the default config file. It allows all users to do anything,
# so don't use it on production systems.
#
# Look here for more config file examples:
# https://github.com/verdaccio/verdaccio/tree/master/conf
#
# path to a directory with all packages
#配置npm网站
web:
#是否开启网站配置
enable: true
#网站的title
title: NPM仓库
#网站的语言
i18n: zh-CN
#网站的logo,可以是本地静态文件也可以是静态服务器地址
logo: xxx
#网站的主题色
primary_color: "#32ca99"
#网站的favicon
favicon: xxx
#数据库地址,注意这里要和文件目录对应
storage: ./storage
#这也就是我们的NPM私服的IP地址和端口号
#NPM网站的访问地址也是这个端口号
listen: 0.0.0.0:4876
#我们肯定不希望所有的人都能往我们的私服上发包
#所以我们要进行相应的权限控制
auth:
#配置密码相关
htpasswd:
#密码文件地址
file: ./password
# Maximum amount of users allowed to register, defaults to "+inf".
# You can set this to -1 to disable registration.
#max_users: 1000
# a list of other known repositories we can talk to
# 其他的我们的私服可以访问的地址,当某个包在私服上找不到时,会尝试从下面配置的地址中读取
uplinks:
# 仓库别名
alinpm:
# 仓库地址
url: https://packages.aliyun.com/***/npm/npm-registry/
# 是否缓存该仓库的包
cache: true
# 认真信息
auth:
type: bearer
token: "***"
# 同上
taobaonpm:
url: https://registry.npmmirror.com/
backup:
url: https://registry.npmjs.org/
# 关于NPM包的配置
packages:
#包名前缀
'@nc/*':
# scoped packages
访问权限
access: $all
是否允许发布
publish: undefined
找不到的话代理到哪里寻找,名称和上面的仓库配置名称要对应上
proxy: alinpm
'@ncfe/*':
#scoped packages
access: $all
publish: undefined
proxy: alinpm
'@ncwp/*':
#scoped packages
access: $all
publish: undefined
proxy: alinpm
'**':
# allow all users (including non-authenticated users) to read and
# publish all packages
#
# you can specify usernames/groupnames (depending on your auth plugin)
# and three keywords: "$all", "$anonymous", "$authenticated"
access: $all
# allow all known users to publish packages
# (anyone can register by default, remember?)
publish: $authenticated
# if package is not available locally, proxy requests to 'npmjs' registry
proxy: taobaonpm
# log settings
# 日志文件配置,这里就不多解释了,一看就懂
logs: {type: file, format: pretty-timestamped, path: './verdaccio.log', level: error}
服务启动脚本内容
verdaccio --config ./config.yaml
最后通过pm2命令即可使我们的服务持续的运行在服务器上
pm2 start npmServer.sh
五、阿里云仓库的包迁移到Verdaccio
私服目前就已经算是搭建完了,但是还有一个很重要的问题需要解决,就是我们已有的一些私有包,怎么同步到我们的私服上?因为像我们在有私服之前,我们的私有包已经存放到了阿里云的仓库上,而阿里云好像并没有提供导出私有包的功能,Verdaccio似乎也没有提供导入的功能,这怎么办?
在搭建私服的时候,我有个发现,就是通过我们的私服代理访问阿里云NPM仓库之后,在私服上我们的storage里面,也会有我访问的包的版本的一个备份。那么我们是不是可以写一个爬虫通过私服代理访问一下阿里云上所有的私有包的所有版本来达到私有仓库迁移的目的呢?说干就干。
以下代码已经过脱敏处理
const https = require('https');
const fs = require('fs');
const exec = require('child_process').execSync;
/**
* @description npm包爬虫
* @param {String} targetUrl 爬取地址
* @param {Object} requestHeader 必要的请求头信息
*/
function npmCrawler(targetUrl,requestHeader){
https.get(targetUrl,{header:requestHeader},function(response){
// 缓存接口返回的信息
let result = '';
// 缓存的包列表和版本信息
let packageList = null;
// 缓存已爬取的包名
let packageNames = []
// 拼接完成
response.on('data',function(data){
result+=data;
}).on('end',function(){
packageList = JSON.parse(result).object.dataList;
// 防止并发量太高导致私服访问崩溃,这里使用for of同步遍历
for(let package of packageList){
packageNames.push(`"${package.name}"`);
for(let version of package.versions){
console.log(`导入包:${package.name}@${version}`);
// 将爬取的包同步到私服上(通过访问私服上的包的地址,达到私服缓存对应包的效果)
exec(`curl https://ip:4876/${package.name}/-/${package.name}-${version}.tgz -s`, { stdio: 'ignore' })
}
}
fs.writeFileSync('names.json',packageNames.join())
console.log(`本次一共导入${packageList.length}个包`);
})
}).on('error',(e)=>{
console.log('错误信息',e);
})
}
// 调用爬虫
npmCrawler(
'https://packages.aliyun.com/api/repo/npm/npm-registry/moduleGroups?queryString=****',
{
'Cookie': '***',
'referer': '***',
'x-requested-with': 'XMLHttpRequest',
'x-xsrf-token': '**',
'content-type': 'application/x-www-form-urlencoded'
}
)
完美!到这里,我们私服的搭建到私服的迁移的工作就全部完成了。
最后上一张图看看我们的NPM私服系统吧。

参考链接
#前端工程师#
