项目统一规范包管理器

一般来说每个团队都会统一规定项目内只使用一个包管理器,譬如:npm、yarn、pnpm等,我们可以在文档中或者项目根目录REDEM.md中进行描述来形成共识,但毕竟是文档,并不能真正的进行约束,如果有项目成员没有看文档描述,而使用了不同的包管理器,则可能会因为lock file 失效而导致项目无法正常运行,针对这种情况就有了 only-allow 这款解决该问题的工具

这篇不说该工具的使用,而是去实现一个改包的源码,因为改包比较小,代码量也不是很大,知道核心就可以了。源码GitHub地址:pnpm/only-allow

我们想要统一使用同一个包管理器,就是在成员拉下代码后,npm install 之前需要做一些事情,比如团队统一使用pnpm,如果成员使用 npm install 就给他抛出错误,并且终止npm install

首先我们要知道一个钩子:preinstall, preinstall可以在运行npm install之前执行某个命令,node原生中的 process.exit(code)  code非0时终止运行。

首先第一步我们在package.json中添加以下代码

"scripts": {
  "preinstall": "node check-npm.js pnpm"
}

也就是当我们npm install 之前 会去执行 check-npm.js脚本 ,后面的pnpm是需要我们必须传递的参数,也就是我们渴望该项目统一使用什么包管理器

我们可以通过nodejs中的process.argv获取到我们传递的参数(包管理器),process.argv属性返回一个数组,这个数组包含了启动Node.js进程时的命令行参数,如下 

process.argv.slice(2)[0] //pnpm

如果没有传递参数我们,我们应该打印出一条日志进行提示,并终止运行

const argv = process.argv.slice(2)

if(argv.length === 0) {
  console.log(`Please specify the wanted package manager: only-allow <npm|cnpm|yarn|pnpm>`)
  process.exit(1)
}

如果用户乱传参数显然也是不行的,我们也应打印出一条日志给与提示,并终止运行

const PACKAGE_MANAGER_LIST = ['npm', 'cnpm', 'yarn', 'pnpm']

const argv = process.argv.slice(2)

const wantedPM = argv[0]

if(!PACKAGE_MANAGER_LIST.includes(wantedPM)) {
  const name = PACKAGE_MANAGER_LIST.join(',')
  console.log(`"${wantedPM}" is not a valid package manager. Available package managers are: ${name}.`)
  process.exit(1)
}

上面两种情况考虑完之后,就是要考虑我们如何才能知道用户是使用的什么包管理器进行安装依赖的呢,答案就是我们可以通过 process.env.npm_config_user_agent 来获取,下面给大家看下该值到底是个什么东西

它其实就是个字符串,我们使用空格进行split切割取第一个 就可以拿到我们使用的包管理器和对应包管理器的版本 ,这样我们就可以写个方法简单封装一下,来返回当前使用的包管理器和当前包管理器的版本 

function getPackageManagerByUserAgent(userAgent) {
  if(!userAgent) {
    throw new Error(`'userAgent' arguments required`)
  }
  const spec = userAgent.split(' ')[0]
  const [name, version] = spec.split('/')

  return { name, version }
}

最后就是我们的完整代码

check-npm.js 

const PACKAGE_MANAGER_LIST = ['npm', 'cnpm', 'yarn', 'pnpm']

// 获取我们传递的参数<[npm|cnpm|yarn|pnpm]>
const argv = process.argv.slice(2)

// 没有传递参数给与提示并终止运行
if(argv.length === 0) {
  const name = PACKAGE_MANAGER_LIST.join('|')
  console.log(`Please specify the wanted package manager: only-allow <${name}>`)
  process.exit(1)
}

// 我们传递的参数值<npm|cnpm|yarn|pnpm>
const wantedPM = argv[0]

// 乱传参数给与提示并终止运行
if(!PACKAGE_MANAGER_LIST.includes(wantedPM)) {
  const name = PACKAGE_MANAGER_LIST.join(',')
  console.log(`"${wantedPM}" is not a valid package manager. Available package managers are: ${name}.`)
  process.exit(1)
}

// 当前使用的包管理器
const usedPM = getPackageManagerByUserAgent(process.env?.npm_config_user_agent).name

// 当前使用的包管理器和我们约束的不一样 抛出一条错误日志并终止运行
if(usedPM !== wantedPM) {
  console.error(`You are using ${usedPM} but wanted ${wantedPM}`)
  process.exit(1)
}

function getPackageManagerByUserAgent(userAgent) {
  if(!userAgent) {
    throw new Error(`'userAgent' arguments required`)
  }
  const spec = userAgent.split(' ')[0]
  const [name, version] = spec.split('/')

  return { name, version }
}

好了,我们已经实现了only-allow的功能了

问题: 

preinstall 钩子理论上应该在安装依赖前触发,但是经验证 ,npm和yarn调用preinstall的时机并不一样,npm 仅仅在 npm install 时运行,而 npm insatll <package-name> 则不会,但yarn则会在 yarn install 和 yarn add <pk-name>时都会运行,所以如果想用这种方式限制npm使用,可能无法达到预期

preinstall 从字面意思理解是在install之前执行。但是到了 NPM7.0版本之后就有所不同,preinstall就有了bug,实际上是先install再执行的preinstall。pnpm不知道从哪个版本开始也跟NPM7.0+一样。但是pnpm6.21版本开始新增了 pnpm:devPreinstall 脚本,所以使用pnpm可以考虑将preinstall换成pnpm:devPreinstall 即可达到NPM7.0以下版本的效果。具体参考:NPM preinstall 不同版本的差异

猜你喜欢

转载自blog.csdn.net/m0_51431448/article/details/128218027