将本地前端工程中的npm依赖上传到Nexus

【问题背景】

用Nexus搭建了内网的依赖仓库,需要将前端工程中node_modules中的依赖上传到Nexus上,但是node_modules中的依赖已经是解压后的状态,如果直接机械地将其简单地打包上传到Nexus,那么无法通过npm install下载使用。故有此文。

【解决思路】

前端工程中的所有npm依赖信息已经记录在package-lock.json文件中,包括依赖的下载地址,所以可以直接根据package-lock.json中记录的依赖地址逐个下载依赖的原始包,然后再上传到Nexus对应的仓库中。

【前置条件】

  1. Nexus上已创建好hosted类型的npm仓库,假设起名为:npm-local
  2. 本地前端工程所在PC可以连接互联网
  3. 本地前端工程已执行npm install且已生成package-lock.json

【编写脚本】

1.依赖下载脚本。下述的NodeJS脚本可以根据前端源码工程下的package-lock.json文件中的每个依赖信息的resolved字段下载该依赖对应的原始tgz压缩包,只有tgz格式的原始依赖包才能被Nexus作为npm依赖管理。将该脚本保存到一个名为downloadNpmPackage.js的文件中:

process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
const fs = require('fs')
const path = require('path')
const request = require('request')
// 指定根据package-lock.json中记录的信息下载依赖
const packageLock = require('./package-lock.json')
// 指定将依赖下载到当前目录下的npm-dependencies-tgz目录
const downUrl = './npm-dependencies-tgz'

if (!fs.existsSync(downUrl)) {
  fs.mkdirSync(downUrl)
}

// 收集依赖的下载路径
const tgz = []
// 当前下载索引
let currentDownIndex = 0
// 下载失败时,重试次数
const retryTimes = 3
// 当前重试计数
let currentTryTime = 0

// 重试次数内仍旧下载失败的链接
const downloadFailTgz = []

for (const pkg in packageLock.packages) {
  if (!packageLock.packages[pkg].resolved) continue
  const tgzUrl = packageLock.packages[pkg].resolved.split('?')[0]
  tgz.push(tgzUrl)
}
// 下载依赖
function doDownload (url) {
  const outUrl = url.split('/').pop()
  let outUrl2 = [outUrl]
  if (outUrl.indexOf('?') !== -1) {
    outUrl2 = outUrl.split('?')
  }
  const outputDir = path.join(downUrl, outUrl2[0])
  let receivedBytes = 0
  let totalBytes = 0
  const req = request({
    method: 'GET',
    uri: url,
    timeout: 60000
  })
  req.on('response', function (data) {
    totalBytes = parseInt(data.headers['content-length'])
  })
  req.on('data', function (chunk) {
    receivedBytes += chunk.length
    showProgress(receivedBytes, totalBytes, outUrl2[0])
    // 当前文件下载完成
    if (receivedBytes >= totalBytes) {
      currentDownIndex++
      currentTryTime = 0
      if (currentDownIndex < tgz.length) {
        doDownload(tgz[currentDownIndex])
      } else {
        if (downloadFailTgz.length === 0) {
          console.log('【完成】所有依赖均下载成功!')
        } else {
          console.warn('【完成】初步处理完成,但部分依赖多次重试后仍旧下载失败,请手动下载:', downloadFailTgz)
        }
      }
    }
  })
  req.on('error', e => {
    console.log(`第${currentDownIndex + 1}/${tgz.length}个依赖${outUrl2[0]}下载失败:`, JSON.stringify(e))
    if (currentTryTime < retryTimes) {
      currentTryTime++
      console.log(`【第${currentTryTime}次】尝试重新下载第${currentDownIndex + 1}/${tgz.length}个依赖${outUrl2[0]}`)
      doDownload(tgz[currentDownIndex])
    } else {
      // 存入下载失败数组中
      downloadFailTgz.push(tgz[currentDownIndex])
      currentDownIndex++
      currentTryTime = 0
      if (currentDownIndex < tgz.length) {
        doDownload(tgz[currentDownIndex])
      }
    }
  })
  req.pipe(fs.createWriteStream(outputDir))
}

// 依赖下载进度显示
function showProgress (received, total, filePath) {
  const percentage = ((received * 100) / total).toFixed(2)
  process.stdout.write(`${filePath} 下载进度:${percentage}% (${received}/${total} 字节)\r`)
  if (received === total) {
    console.log(`\n第${currentDownIndex + 1}/${tgz.length}个依赖${filePath} 下载完成!`)
  }
}

// 串行下载
doDownload(tgz[currentDownIndex])

上述脚本使用的是串行下载方式,下载速度较慢,但优势是当依赖下载失败时能够更好地定位问题。您可以根据自己实际情况对上述程序进行优化。

2.依赖批量上传脚本。为了方便集成和管理,Nexus提供了大量API,如下图所示为Nexus的设置-系统-API页面。我们可以看到Components分组下有一个上传单个组件的接口。

下述Bash脚本便是根据Nexus提供的上传单个依赖接口进行编写的,可以将原始的tgz格式的前端依赖上传到Nexus上,将该脚本保存到名为UploadnpmPackage.sh的文件中。

#!/bin/bash

# 获取命令行参数
while getopts ":r:u:p:" opt; do
    case $opt in
        r) REPO_URL="$OPTARG"
        ;;
        u) USERNAME="$OPTARG"
        ;;
        p) PASSWORD="$OPTARG"
        ;;
    esac
done
 
# find 并批量上传
find . -type f -name '*.tgz'  | sed "s|^\./||" | xargs -I '{}' \
curl -u "$USERNAME:$PASSWORD" -X 'POST' -v \
  ${REPO_URL} \
  -H 'accept: application/json' \
  -H 'Content-Type: multipart/form-data' \
  -F 'npm.asset=@{};type=application/x-compressed' ;

【下载原始依赖】

1.将NodeJs脚本downloadNpmPackage.js置于前端工程目录下且与package-lock.json文件同级:

2.在当前目录运行Node命令:

node downloadNpmPackage.js

等待程序下载依赖包:

下载完成后会在package-lock.json同级目录生成一个名为npm-dependencies-tgz的文件夹:

里面存放的是当前工程所有依赖的tgz文件:

将npm-dependencies-tgz文件夹打包成tar压缩包。

【上传依赖】

1.至此上一步含有tgz格式的原始依赖的tar包和名为UploadnpmPackage.sh的shell脚本如下,可以将这俩发给负责依赖上传的同事,由他们完成后面的上传操作。

2.上传依赖的同事收到上述两个文件后,解压npm-dependencies-tgz.tar压缩包,得到npm-dependencies-tgz目录,将UploadnpmPackage.sh剪切到npm-dependencies-tgz目录下与所有tgz依赖包同级:

3.若是linux操作系统,则需要使用如下命令将脚本中的换行符进行转换:

dos2unix UploadnpmPackage.sh

若没有dos2unix则需要安装一下:sudo yum install dos2unix。若是windows操作系统则跳过本步。

4.在npm-dependencies-tgz目录下使用如下命令运行脚本UploadnpmPackage.sh将依赖包上传到nexus上,注意红字部分根据Nexus的实际情况填写(建议使用Nexus的admin用户):

sh UploadnpmPackage.sh -u admin -p nexusAdmin2023 -r

http://172.24.105.249:8089/service/rest/v1/components?repository=npm-local

5.登录Nexus网页,查看到npm-local仓库下已经存在刚才上传的依赖了(您看到这里,您是否觉得这和您本地node_modules中的目录结构很像呢?还存在有@符号的目录?这是因为以@开头的目录是npm默认的布局结构。这种结构允许npm以模块化的方式组织依赖项。当您上传npm依赖的tgz包到Nexus时,这些包会被自动放置在对应的@目录下,这种方式使得在部署或管理项目时npm更容易找到所需的特定版本的依赖项。您逐层点开后会发现Nexus最终存放的是一个tgz格式的原始依赖包,而不像本地开发环境中node_modules那样会将依赖解压成js文件,这也是Nexus和本地开发环境下node_modules在依赖管理上的区别。正是有这样的区别,所以才不能直接将本地node_modules中的目录直接机械地搬动到Nexus仓库中。 ):

【使用依赖】

1.完成上述上传操作后,查看并复制npm-local仓库地址:

2.CI工具中配置依赖拉取地址为上述复制的地址:http://172.24.105.249:8089/repository/npm-local/。或者离线编译前端源码时可以使用如下命令设置npm仓库地址:

npm config set registry=http://172.24.105.249:8089/repository/npm-local/

3.到此便可正常运行npm install安装依赖了。

【常见问题】

1.上传依赖(或者下载依赖时)报错信息显示not authorized for requested path....
答:此提示表示您当前使用的账号不具备访问Nexus服务的权限,属于权限配置问题。请使用Nexus的admin账号执行上传操作或在Nexus中设置允许匿名用户(Anonymous user)访问服务(“设置-安全-匿名访问”页面勾选允许匿名用户访问服务。)亦或者根据请求链路排查用户-角色-权限相关配置是否正常。


2.我想直接将前端源码工程中node_modules目录下的所有依赖上传到Nexus,尝试后失败了。
答:千万不要简单的以为您本地已经下载了依赖到了node_modules目录,然后您就单纯的觉得将node_modules目录下的依赖一股脑地上传到nexus就行了。不是这样的,您本地node_modules中的依赖是开发环境下已经解压后的依赖,不是原始依赖,不能被nexus作为依赖包识别并管理(具体原因请参看本文【上传依赖】第5步内容)。建议使用本文中的【编写脚本】章节的downloadNpmPackage.js脚本下载原始的npm依赖的tgz压缩包。然后再通过上传脚本批量上传依赖。

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值