Nexus搭建npm私库(角色管理、上传脚本)

安装Nexus

官网下载

https://www.sonatype.com/products/sonatype-nexus-oss-download

进入官网下载,最新下载方式需要输入个人信息才能下载了

image-20231206140429489

选择对应的系统进行下载

  • Windows 推荐也下载 UNIX 版本(Windows 版本配置比较难改)

image-20231206135901989

如果没有下载,点一下 click here 重新下载,下载还是很快的

image-20231206140624901

Windows安装

linux 安装步骤与启动方式跟 windows 是一样的,解压后如下图

image-20231206141655045

$ tar -zxvf nexus-3.63.0-01-unix.tar.gz
$ cd nexus-3.63.0-01/bin
$ ./nexus /run

注意:nexus 必须使用 Java1.8,我现在用的是 Java17,提示信息让咱们配置 INSTALL4J_JAVA_HOME

image-20231206145305323

$ vim nexus
INSTALL4J_JAVA_HOME_OVERRIDE=/d/Develop/Java/jdk1.8.0_161

image-20231206150123385

显示这个之后访问:http://localhost:8081/,点击 Sign In

  • 账号为 admin,密码粘贴 sonatype-work/nexus3/admin.password 里面的密码,登录成功会提示你改密码

image-20231206150205067

如果想更改启动端口,可以修改 etc/nexus-default.properties 文件

image-20231206150804664

docker安装nexus

nexus3 安装参考:

  • docker-nexus3 GitHub

镜像说明:

  • sonatype/nexus 是 nexus2 版本
  • sonatype/nexus3 是 nexus3 版本
$ docker pull sonatype/nexus3
$ docker run -d -p 8081:8081 --name nexus sonatype/nexus# 查看 nexus 日志
$ docker logs -f nexus

注意:已经有的容器,直接 docker start 即可

  • -d:在 docker 守护线程运行这个镜像
  • -p:绑定端口,前面的端口表示宿主机端口,后面的表示容器端口
  • --restart=always:指定 docker 重启启动容器,当服务器或者 docker 进程重启之后,nexus 容器会在 docker 守护进程启动后由 docker 守护进程启动容器
  • --name <container-name>:这里是指定了容器建立后的名称
  • sonatype/nexus3 是镜像名

查看是否启动成功

docker ps

报内存不够的话输入这个

Memory: 4k page, physical 1006968k(877280k free), swap 0k(0k free)$ docker run -d -p 8081:8081 --name nexus3 --restart=always --platform linux/amd64 -e INSTALL4J_ADD_VM_PARAMS="-Xms256M -Xmx256M -XX:MaxDirectMemorySize=2048M" sonatype/nexus3

用户权限不够的话输入如下内容,之后即可启动成功

WARNING: ************************************************************
WARNING: Detected execution as "root" user.  This is NOT recommended!
WARNING: ************************************************************
$ cd /opt/sonatype/nexus/bin
$ vi nexus.rc
run_as_user=root
$ vi /etc/profile
export RUN_AS_USER=root
$ vi nexus
run_as_root=true -> run_as_root=false

创建仓库

仓库管理

Nexus 仓库类型分为如下几种

  1. hosted:本地仓库,通常我们会部署自己的构件到这一类型的仓库,如公司的第二方库
  2. proxy:代理仓库,它们被用来代理远程的公共仓库,如 Maven 中央仓库
  3. group:仓库组,用来合并多个 hosted/proxy 仓库,当你的项目希望在多个 repository 使用资源时就不需要多次引用了,只需要引用一个 group 即可

在创建 repository 之前,最好再创建 Blob Stores 便于统一管理,默认的是 default

image-20231206151333577

选择 File,名字填写为 npm

image-20231206151428449

创建仓库

之后即可创建仓库

image-20231206151933100

一会儿会创建 npm(group)、npm(hosted)、npm(proxy) 这几个仓库

image-20231206151758355

创建npm(hosted)

hosted 宿主仓库:主要用于部署无法从公共仓库获取的构件以及自己或第三方的项目构件

  • 如果是内网情况(无法访问互联网)即可当 npm 总仓库
  • 是否允许重复推送可以根据自己项目情况来考虑

image-20231206153443838

创建npm(proxy)

proxy 代理仓库:代理公共的远程仓库

image-20231206152954330

创建npm(group)

group 仓库组:通过仓库组统一管理多个仓库,这样我们在项目中直接请求仓库组即可请求到仓库组管理的多个仓库

  • hosted 和 proxy 这两个都弄好了之后,在通过 group 聚合提供统一的访问地址

image-20231206153604374

测试

指定 npm 的 registry 为自己的 group 地址,安装 express 依赖

image-20231206155638015

proxy 地址由于没有对应依赖会去镜像源地址下载,之后 proxy 仓库就有对应依赖了

image-20231206155807939

创建推送包角色

添加角色用户

点击 Roles,添加角色权限,搜索 npm- 之后点击 Transfer All 把所有权限都附上即可

  • 可以根据对应需求赋对应权限,比如只让上传不让修改只赋 edit 即可

image-20231206161750869

之后创建对应用户,并设置对应权限

image-20231206161935295

点击 Realms,添加 npm Bearer Token Realm,不然 npm publish 会报 401

image-20231206164136875

添加推送账号

https://blog.sonatype.com/using-sonatype-nexus-repository-3-part-2-npm-packages

添加推送账号,常用如下两种方法

npm adduser

示例:使用 npm adduser 方法

$ npm adduser
Username: admin
Password: admin123
Email: (this IS public): any@email.com

推荐:加 --scope 限制作用域,加 --registry 限制仓库地址

$ npm adduser --registry=http://localhost:8081/repository/npm_hosted/ --scope=@mynpm
npm notice Log in on http://localhost:8081/repository/npm_hosted/
Username: npm
Email: (this IS public) ll@123.com
Logged in to scope @mynpm on http://localhost:8081/repository/npm_hosted/.
$ npm publish --scope=@mynpm

image-20231206170241797

使用.npmrc

不添加推送账号也可以使用 .npmrc,创建 .npmrc 文件,将私库地址粘贴过来

  • 如果想免密登陆推送可以加上 _auth,加密规则:user:password -> base64
registry=http://localhost:8081/repository/npm_hosted/
# 只是举例,不推荐使用 admin 用户
_auth=YWRtaW46YWRtaW4xMjM=
email=any@email.com

_auth 加密方式:

  • 可以使用浏览器自带的方法 window.btoawindow.atob

    image-20230613101343312

  • 还可以使用 linux base64 命令

    image-20230613101349194

registry 也可以通过配置 package.json 来实现

{"publishConfig": {"registry": "http://localhost:8081/repository/npm_hosted/"}
}

下载依赖包

python下载

  • 根据 resolved 字段进行下载
# -*-coding:utf-8-*-
import json
import os
import urllib.request
from pathlib import Pathdef 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 linksdef 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 linksdef search(json_object, key, links):# 遍历查找指定的keyfor 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)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(), 'nodes')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')# 下载文件是否存放到一个新的路径里,默认存放到 nodes 里download_flag = Truedownload_file(download_link, download_path, download_flag)print("ok")

node下载

  • 使用 npm pack 命令,下载 .tgz 文件
const shell = require('shelljs')
const fs = require('fs')function download(fileNames = []) {shell.cd('download')let count = 0fileNames.forEach(fileName => {const fileExec = shell.exec(`npm pack ${fileName}`, { async: true, silent: true })fileExec.stdout.on('data', () => {++countshell.echo(`>>> ${fileName} 下载完成...`)if (count === fileNames.length) {shell.cd('..')shell.exit(0)}}).on('err', () => {++countshell.echo(`>>> ${fileName} 下载失败!!!...`)if (count === fileNames.length) {shell.cd('..')shell.exit(0)}})})
}function downloadByPackageJsonLockFile(depLockJsonFile = {}) {const nMap = new Map()const NotMap = new Map()// 总的nodes文件夹,方便下次避免重复下载const downloadedDir = './nodes'const downloadedArr = fs.readdirSync(downloadedDir)function getAllList(depJson) {if (depJson) {Object.keys(depJson).forEach(dep => {const depWithVersion = `${dep}@${depJson[dep].version}`let tgzFormat = `${dep}-${depJson[dep].version}.tgz`// eg: @babel/code-frame-7.14.5.tgz -> babel-code-frame-7.14.5.tgztgzFormat = dep.startsWith('@')? tgzFormat.split('/').join('-').slice(1): tgzFormatif (!nMap.has(depWithVersion) && !downloadedArr.includes(tgzFormat)) {nMap.set(depWithVersion, true)getAllList(depJson[dep].dependencies)} else if (downloadedArr.includes(tgzFormat) && !NotMap.has(tgzFormat)) {NotMap.set(tgzFormat, true)}})}}getAllList(depLockJsonFile.dependencies)shell.echo(`一共${Array.from(NotMap.keys()).length}个依赖包已在${downloadedDir}目录下存在,不需要重复下载:\n`)shell.echo(`>>> 无需下载列表: \n - ${Array.from(NotMap.keys()).join('\n - ')}...\n`)shell.echo(`一共${Array.from(nMap.keys()).length}个依赖包待下载\n`)shell.echo(`>>> 待下载列表: \n - ${Array.from(nMap.keys()).join('\n - ')}...`)download(Array.from(nMap.keys()))
}const pkgLock = require('./package-lock')
downloadByPackageJsonLockFile(pkgLock)

推送依赖包

shell单线程推送

  • 只使用一个线程推送,我比较常用,一般推送的包不会很多
#!/bin/bash
# 待publish文件夹地址
PACKAGE_PATH=./download
# 前端私库地址
REPOSITORY=http://localhost:8081/repository/npm_hosted/
npm login --registry=$REPOSITORY
for file in $PACKAGE_PATH/*.tgz; donpm publish --registry=$REPOSITORY $file
done

node多线程推送

多线程推送,上传包较多可以使用,如果包过多可能会导致电脑卡死

let fs = require('fs')
let path = require('path')
const { exec } = require('child_process')// 前端私库地址
const registry = 'http://localhost:8081/repository/npm_hosted/'
const publishPosition = `npm publish --registry=${registry}`
// 待publish文件夹地址
const filesDir = './download'fs.readdir(filesDir, (errs, files) => {files.forEach(file => {fs.stat(filesDir + file, function (err, stats) {if (stats.isFile()) {const fullFilePath = path.resolve(__dirname, filesDir + file)console.log(fullFilePath + ' publish 开始')exec(publishPosition + ' ' + fullFilePath, function (error, stdout, stderr) {if (error) {console.error(fullFilePath + ' publish 失败')} else {console.error(fullFilePath + ' publish 成功')}})}})})
})

nexus API上传

官网信息见: https://help.sonatype.com/repomanager3/rest-and-integration-api/components-api

#!/bin/bash
# 待publish文件夹地址
PACKAGE_PATH=./download
# 前端私库服务地址
PUBLISH_RESTFUL=http://localhost:8081/service/rest/v1/components?repository=npm_hostedecho ">>> 文件所在目录:$PACKAGE_PATH <<<"
dir=$(ls -l $PACKAGE_PATH | awk '/.tgz$/ {print $NF}')
cd $PACKAGE_PATHfor file in $dir
doecho ">>> $PACKAGE_PATH/$file 上传开始 \n"ret=`curl -u admin:admin123 -X POST "$PUBLISH_RESTFUL" -H "Accept: application/json" -H "Content-Type: multipart/form-data" -F "npm.asset=@$file;type=application/x-compressed"`echo $retecho ">>> $PACKAGE_PATH/$file 上传完成 \n"
done

问题

内网上传问题

  1. 分析依赖锁时,有些包命名重复,导致下载错误

    比如:下载 parse-json@4.0.0 链接,既有可能下载的是 @types/parse-json 也有可能下载就是 parse-json,如果按文件名进行覆盖则会导致少传包

  2. 上传包时,有些包的 package.json 里面配置 publishConfig

    image-20230823160618665

    • 比如:archiver-5.0.0 里面配置了如上 publishConfig。在内网情况下,使用 npm i --registry=xx,是会报错的,内网无法访问到 https://registry.npmjs.org/
      • 目前我想到的解决方案是:把 archiver-5.0.0.tgz 解压,之后解压 archiver-5.0.0.tar,修改 package.json 里面的 publishConfig,之后执行 npm publish
    • 比如:builtins@1.0.3 里面也配置了 publishConfig
    • 比如:ahooks@3.7.8ahooks-v3-count-1.0.0hoist-non-react-statics@3.3.2(react-redux@8 的依赖)

    特殊

    • 比如:simple-update-notifier@2.0.0 也是里面配置了 publishConfig,这个包里 scripts 命令里还会有 prepare 钩子,需要先把它去掉
    • 比如:@ant-design/icons@4.8.1 这个比较有特殊性,它依赖了 @ant-design/icons-svg@4.3.1,这两个包都配置了 publishConfig,所以需要单独推送,但是这个包里 scripts 命令里配置了 prepublishOnly 钩子,需要先把它去掉
    • 比如:antd@5.8.4antd@4.24.13 同时配置了 prepare、prepublishOnly、postpublish 钩子,需要先把它们去掉
  3. 分析依赖锁时,包下载不下来,这个就只能用笨方法(缺什么依赖,npm i 之后把对应包 tgz 包下载下来)

    比如:

    • @vue/cli 需要 @types/inquirer@8.1.3@types/accepts@1.3.5body-parser@1.19.2qs@6.9.7@vitejs/plugin-react@4.0.3@types/html-minifier-terser@6.0.0serve-index@1.9.1
    • 其实这个是和第一个原因是一样的都是名重复了,导致下载不下来

其他问题

E400

仓库不允许重复推送会报 400,改为 allow redeploy

image-20231206165353153

E401

没有权限涉及情况较多:

  1. 账号密码不对
  2. 检查对应角色是否有对应权限
  3. 比如 npm 推送以什么方式校验,npm Bearer Token Realm
  4. token 是否过期,去根目录修改 .npmrc 文件,将过期 token 删除

image-20231206162906061

E403

禁止上传,group 仓库禁止上传,上传到 hosted 即可

image-20231206164947825

E503

仓库离线,改为在线即可

image-20231206165252356

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/209828.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Qt 中的窗口类

目录 QWidget 设置父对象 窗口位置 测试代码 窗口尺寸 窗口标题和图标 信号 槽函数 QDialog 常用API QDialog的子类 QMessageBox QFileDialog QFontDialog QFontDialog类的静态API QColorDialog 颜色类 QColor 静态API函数 测试代码 QInputDialog 静态函数…

GPT4停止订阅付费了怎么办? 怎么升级ChatGPT plus?提供解决方案

11月中旬日OpenAI 暂时关闭所有的升级入口之后&#xff0c;很多小伙伴就真的在排队等待哦。其实有方法可以绕开排队&#xff0c;直接付费订阅升级GPT的。赶紧用起来立马“插队”成功&#xff01;亲测~~~ 一、登录ChatGPT账号 1、没有账号可以直接注册一个&#xff0c;流程超级…

前端使用视频作为背景图的方法

实现思路 通过 video source 引入视频&#xff0c;并对视频播放属性进行设置&#xff0c;再通过 css 使视频覆盖背景即可。 代码 <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>有开发问题可联系作者</title>…

学习git后,真正在项目中如何使用?

文章目录 前言下载和安装Git克隆远程仓库PyCharm链接本地Git创建分支修改项目工程并提交到本地仓库推送到远程仓库小结 前言 网上学习git的教程&#xff0c;甚至还有很多可视化很好的git教程&#xff0c;入门git也不是什么难事。但我发现&#xff0c;当我真的要从网上克隆一个…

SQL事务的开启,提交和回滚

在处理数据库数据的时候会出现一种情况就是我们删除两个关联的表其中一个表的信息&#xff0c;另一个表也需要改动&#xff0c;但是我们SQL语句在同时更改两个表的同时&#xff0c;难免会出现一个表修改成功&#xff0c;另一个出现错误&#xff0c;这时候表与表之间就会出现矛盾…

webrtc网之sip转webrtc

OpenSIP是一个开源的SIP&#xff08;Session Initiation Protocol&#xff09;服务器&#xff0c;它提供了一个可扩展的基础架构&#xff0c;用于建立、终止和管理VoIP&#xff08;Voice over IP&#xff09;通信会话。SIP是一种通信协议&#xff0c;用于建立、修改和终止多媒体…

geolife 笔记:将所有轨迹放入一个DataFrame

单条轨迹的处理&#xff1a;geolife笔记&#xff1a;整理处理单条轨迹-CSDN博客 1 加载数据 import pandas as pd import numpy as np import datetime as dt import osdata_dir Geolife Trajectories 1.3/Data/ 1.1 列出所有文件夹 dirlist os.listdir(data_dir) dirlist…

Esxi登录超时:“由于不活动超时,您已被注销“,修改UserVars.HostClientSessionTimeout为0永不超时

Esxi登录超时:“由于不活动超时&#xff0c;您已被注销”,修改UserVars.HostClientSessionTimeout为0永不超时 UserVars.HostClientSessionTimeout0永不超时 Esxi网页登录后,一段时间不操作就会被注销 提示: 由于不活动超时&#xff0c;您已被注销 主机→管理→系统→高级设…

【linux】查看CPU和内存信息

之前咱们一起学习了查看内存的和CPU的命令。 ​mpstat &#xff1a; 【linux】 mpstat 使用 uptime&#xff1a;【Linux】 uptime命令使用 CPU的使用率&#xff1a;【linux】查看CPU的使用率 nmon &#xff1a;【linux】nmon 工具使用 htop &#xff1a;【linux】htop 命令…

文件格式对齐、自定义快捷键、idea

文件格式对齐 Shift Alt F 自动格式化代码的快捷键&#xff08;如何配置自动格式化&#xff09; 日常编码必备idea快捷键 [VS Code] 入门-自定键盘快捷键 文件格式对齐 文件格式对齐通常是通过编辑器或IDE提供的快捷键或命令完成的。以下是一些常见编辑器和IDE中进行文件…

快速认识什么是:Docker

Docker&#xff0c;一种可以将软件打包到容器中并在任何环境中可靠运行的工具。但什么是容器以及为什么需要容器呢&#xff1f;今天就来一起学快速入门一下Docker吧&#xff01;希望本文对您有所帮助。 假设您使用 Cobol 构建了一个在某种奇怪风格的 Linux 上运行的应用程序。您…

InnoDB Architecture MySQL 5.7 vs 8.0

innodb-architecture-5-7 innodb-architecture-8-0 图片均来源于MySQL官网

【Vue】props与$emit的简单理解

Vue组件 组件是Vue中不可或缺的一个功能&#xff0c;它可以将一个页面划分为多个独立的内部组件&#xff0c;方便代码的管理。 定义组件 <body><div id"App"><bcomp></bcomp></div><script>const app Vue.createApp({})cons…

【2023传智杯-新增场次】第六届传智杯程序设计挑战赛AB组-ABC题复盘解题分析详解【JavaPythonC++解题笔记】

本文仅为【2023传智杯-第二场】第六届传智杯程序设计挑战赛-题目解题分析详解的解题个人笔记,个人解题分析记录。 本文包含:第六届传智杯程序设计挑战赛题目、解题思路分析、解题代码、解题代码详解 文章目录 一.前言二.赛题题目A题题目-B题题目-C题题目-二.赛题题解A题题解-…

Servlet should have a mapping

第一种可能&#xff1a; 你就是没写Servlet <servlet><servlet-name>SpringMVC</servlet-name><servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class><!-- 配置springMVC需要加载的配置文件--><init-par…

Android studio生成二维码

1.遇到的问题 需要生成一个二维码&#xff0c;可以使用zxing第三方组件&#xff0c;增加依赖。 //生成二维码 implementation com.google.zxing:core:3.4.1 2.代码 展示页面 <ImageViewandroid:id"id/qrCodeImageView"android:layout_width"150dp"an…

【Linux】echo命令使用

​echo命令 功能是在显示器上显示一段文字&#xff0c;一般起到一个提示的作用。此外&#xff0c;也可以直接在文件中写入要写的内容。也可以用于脚本编程时显示某一个变量的值&#xff0c;或者直接输出指定的字符串。 ​ 著者 由布莱恩福克斯和切特拉米撰写。 语法 echo […

图像万物分割——Segment Anything算法解析与模型推理

一、概述 在视觉任务中&#xff0c;图像分割任务是一个很广泛的领域&#xff0c;应用于交互式分割&#xff0c;边缘检测&#xff0c;超像素化&#xff0c;感兴趣目标生成&#xff0c;前景分割&#xff0c;语义分割&#xff0c;实例分割&#xff0c;泛视分割等。 交互式分割&am…

设计模式之结构型模式(适配器、桥接、组合、享元、装饰者、外观、代理)

文章目录 一、结构型设计模式二、适配器模式三、桥接模式四、组合模式五、享元模式六、装饰者模式七、外观模式八、代理设计模式 一、结构型设计模式 这篇文章我们来讲解下结构型设计模式&#xff0c;结构型设计模式&#xff0c;主要处理类或对象的组合关系&#xff0c;为如何…

计算机设备管理器如何看内存,怎么查看电脑配置信息?3种方法,让你掌握电脑全部信息!...

转载&#xff1a;https://blog.csdn.net/weixin_35849957/article/details/118512756?spm1001.2014.3001.5502 原标题&#xff1a;怎么查看电脑配置信息&#xff1f;3种方法&#xff0c;让你掌握电脑全部信息&#xff01; 电脑的配置决定了电脑性能高低以及运行速度。而电脑…