使用 Nexus3 搭建 npm 私库,超详细的图文教程
安装运行 Nexus3
1. 下载
2. 安装
- 将
nexus-3.46.0-01-win64.zip文件解压到D:\Program Files\nexus3目录下,如图所示:
其中:
nexus-3.46.0-01 为 nexus 服务器相关文件目录
sonatype-work 为 nexus 数据存储目录
-
新增全局变量。在
path变量中新增D:\Program Files\nexus3\nexus-3.46.0-01\bin -
注册服务。以管理员身份打开命令行窗口。运行
nexus /install nexus3。 即可在系统同注册一个名为nexus3的服务。 -
windows + r打开运行窗口,输入services.msc找到nexus3服务,点击启动,即可启动服务,如图所示:
- 更多命令。
# 启动服务
$ nexus /run nexus3
# 停止服务
$ nexus /stop nexus3
# 删除服务
$ nexus /uninstall nexus3
- 稍等片刻后,你可以通过在浏览器中输入
http://localhost:8081/,进入服务,如图所示:
-
点击
Sign in进行登录。其中用户名为admin,密码在文件D:\Program Files\nexus3\sonatype-work\nexus3\admin.password中查看,第一次登录需要修改密码,如修改成admin123, 修改密码后admin.password⽂件会⾃动删除。 -
至此,nexus 安装运行已全部完成。
创建新仓库
- 创建一个名为
vue-cms的仓库,如图所示:
-
完成后,点击
Create repository完成。 -
以上操作完成后,现在我们就可以在浏览列表看到
vue-cms仓库了,如图所示:
上传 npm 依赖到自定义仓库
接下来我们测试一下,上传项目中的 npm 依赖到我们的自定义仓库
1. 创建项目
- 创建目录
test-project - 执行
npm init -y
- 安装依赖
npm install chart.js
2. 下载 tgz 包
接下来我们要做的就是,将package.json 中的依赖 chart.js 进行下载。
方式1:
- 打开
package-lock.json文件,找到packages属性,将其下的所有依赖包,进行下载。
-
复制每个插件
resolved属性后的链接到浏览器进行下载。 -
将下载的
.tgz包放到与package.json同级目录的node(需创建)文件夹下。
方式2(推荐!!!自己写的,如果有bug,请及时联系我):
通过 node.js 下载依赖包,需要本地有 node 环境
# 完整目录结构
.
└── npm-publish 根目录
├── node // 依赖包存储目录
├── index.js // 入口文件
├── node_modules
├── package.json
└── package-lock.json
步骤如下:
# 1. 创建并进入一个新目录
mkdir npm-publish && cd npm-publish
# 2. 初始化项目,生成 package.json 文件
$ npm init -y
# 3. 创建 index.js,并复制代码(见下)
# 4. 安装 axios,因为我是通过axios下载文件的,所以该插件必备
$ npm i axios
# 3. 安装自己所需依赖包,如 chart.js
$ npm i chart.js
# 4. 打开控制台,执行index.js文件
$ node index.js
# 5. 我想此时已经下载完成了,所有下载依赖放置在同级目录 node 文件夹下
- index.js 内容
const fs = require("fs");
const path = require("path");
// 读取 package-lock.json 文件
const filePath = path.join(__dirname, "package-lock.json");
let data;
try {
data = JSON.parse(fs.readFileSync(filePath, "utf8"));
} catch (error) {
console.log("Error reading or parsing JSON file:", filePath);
}
if (!data) {
return;
}
const packages = data.packages;
const packageList = Object.keys(packages);
console.log(`======= 共计 ${packageList.length - 1} 个依赖包 =======`);
// 创建 axios 请求
const axios = require("axios");
const targetDir = path.join(__dirname, "node");
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir);
}
async function downloadFile(url, outputPath) {
const writer = fs.createWriteStream(outputPath);
const response = await axios({
url,
method: "GET",
responseType: "stream",
});
response.data.pipe(writer);
return new Promise((resolve, reject) => {
writer.on("finish", resolve);
writer.on("error", reject);
});
}
// 下载文件
let successCount = 0;
let failCount = 0;
packageList.forEach(async (key, index) => {
if (key.indexOf("node_modules") > -1) {
const filePath = packages[key].resolved;
const fileName = path.basename(filePath);
const outputPath = path.join(targetDir, fileName);
try {
if (fs.existsSync(outputPath)) {
successCount++;
console.log(`${fileName}: 文件已存在,已下载 ${successCount} 个文件`);
return;
}
console.log(`正在下载: ${filePath}`);
await downloadFile(filePath, outputPath);
successCount++;
console.log(`${filePath}: 下载完成,已下载 ${successCount} 个文件`);
} catch (error) {
failCount++;
console.log(`${filePath}: 下载失败,已失败 ${failCount} 个文件`);
fs.unlinkSync(outputPath); // 删除未完成的文件
}
}
});
方式3:
通过 python 脚本进行下载,需要有 python 环境
- 在
package.json同级目录下创建download.py文件, 脚本如下:
# -*-coding:utf-8-*-
import json
import os
import urllib.request
from pathlib import Path
def node_modules(file_dir):
# 通过递归遍历 node_modules 每个子包的 package.json 解析下载链接
links = []
for root, dirs, files in os.walk(file_dir):
if 'package.json' in files:
package_json_file = os.path.join(root, 'package.json')
try:
with open(package_json_file, 'r', encoding='UTF-8') as load_f:
load_dict = json.load(load_f)
if '_resolved' in load_dict.keys():
links.append(load_dict['_resolved'])
except Exception as e:
print(package_json_file)
print('Error:', e)
return links
def package_lock(package_lock_path):
# 通过递归遍历 package-lock.json 解析下载链接
links = []
with open(package_lock_path, 'r', encoding='UTF-8') as load_f:
load_dict = json.load(load_f)
search(load_dict, "resolved", links)
return links
def yarn_lock(package_lock_path):
# 通过递归遍历 xxx-yarn.lock 解析下载链接
links = []
with open(package_lock_path, 'r', encoding='UTF-8') as load_f:
for line in load_f:
if line.find('resolved') >= 0:
line = line.replace('resolved', '')
url = line.strip().strip('"')
links.append(url)
return links
def search(json_object, key, links):
# 遍历查找指定的key
for k in json_object:
if k == key:
links.append(json_object[k])
if isinstance(json_object[k], dict):
search(json_object[k], key, links)
if isinstance(json_object[k], list):
for item in json_object[k]:
if isinstance(item, dict):
search(item, key, links)
def download_file(path, store_path, flag):
# 根据下载链接下载
if not Path(store_path).exists():
os.makedirs(store_path, int('0755'))
links = []
if path.endswith("package-lock.json"):
links = package_lock(path)
elif path.endswith("yarn.lock"):
links = yarn_lock(path)
else:
links = node_modules(path)
print("link resolved number:" + str(len(links)))
for url in links:
try:
filename = url.split('/')[-1]
index = filename.find('?')
# 去掉 ? 参数和 # 哈希
if index > 0:
filename = filename[:index]
index = filename.find('#')
if index > 0:
filename = filename[:index]
filepath = os.path.join(store_path, filename)
if not Path(filepath).exists():
print("download:" + url)
# 以防以后对请求头做限制
opener = urllib.request.build_opener()
opener.addheaders = [('User-agent', 'Mozilla/5.0')]
urllib.request.install_opener(opener)
if flag:
new_path = os.path.join(os.getcwd(), 'tgz')
if not Path(new_path).exists():
os.makedirs(new_path, int('0755'))
filepath = os.path.join(new_path, filename)
urllib.request.urlretrieve(url, filepath)
# else:
# print("file already exists:", filename)
except Exception as e:
print('Error Url:' + url)
print('Error:', e)
if __name__ == '__main__':
# 通过 xxx 文件解析对应依赖树
download_link = os.path.join(os.getcwd(), 'package-lock.json')
# 下载文件存放的路径
download_path = os.path.join(os.getcwd(), 'node')
# 下载文件是否存放到一个新的路径里,默认存放到 node 目录下, download_flag 为 True 时 存放到 tgz 目录下
download_flag = False
download_file(download_link, download_path, download_flag)
print("ok")
- 运行脚本,执行
python download.py,也可以修改scripts命令来执行。 执行后我们可以看到在node目录下有了两个.tgz包。
- 如有报错,请将当前目录下的
node和tgz文件夹删除后重新执行脚本。
3. 上传依赖
- 在
package.json同级目录下创建publish.sh。
#!/bin/bash
PACKAGE_PATH=./node
REPOSITORY=http://localhost:8081/repository/vue-cms/
npm login --registry=$REPOSITORY
for file in $PACKAGE_PATH/*.tgz; do
npm publish --registry=$REPOSITORY $file
done
如图所示:
- 在当前项目下,打开
Git Bash,运行./publish.sh。如图所示:
- 输入用户名
admin和 密码admin123以及邮箱123@qq.com(随意),回车。
- 靠,报错了401!,如图:
- 在 nexus 平台上添加
npm Bearer Token Realm
-
再次执行
./publish.sh, 成功! -
我们可以去 nexus 平台上找到 vue-cms 仓库点进去看到我们已经上传的依赖。如图所示:
测试 npm install
既然 npm 依赖已经上传到了私库上了接下来我们测试一下是否可以成功下载。
- 删除
vue-cms项目的node_modules。 - 创建
.npmrc文件,指定下载路径。
registry=http://localhost:8081/repository/vue-cms/
如图所示:
- 打开终端,执行
npm install,成功!
如图所示:
常遇问题
1. 内网上传的 tgz 包无法下载成功
原因:
个别包(这里以 nw@0.36.4-sdk 为例),下载时会自动去它的官网下载安装,内网无法访问导致下载失败。
解决方案:
将下载的 nw-0.36.4-sdk-tgz 解压,其内是 package 文件夹。
阅读其 package.json 文件,发现 "scripts.postinstall": "node scripts/install.js" 走的是 scripts 下的install.js 文件。
阅读 install.js 文件, 发现其 urlBase 为 官网源,将其修改为 我们 nginx 文件下载的服务器,如 http://IP:Port/nwjs/nwjs-sdk-v0.36.4-win-x64.zip, 这里的压缩包是 nw-0.36.4-sdk 的可执行包,点击下载,并将 url = urlBase,注释其平台类型判断代码。
随后在 package 文件夹下新增 .npmrc 文件, 设置 registry 源为内网的 nexus 库, 设置完毕后执行 npm pulish进行推送。
推送成功后即可测试下载。