only-allow 源码学习

246 阅读2分钟

前言

npm 命令钩子

钩子官方文档

{
  "scripts": {
    "preinstall": "npx only-allow pnpm"
  }
}

源码地址: only-allow

源码分析

#!/usr/bin/env node
const whichPMRuns = require('which-pm-runs')
const boxen = require('boxen')

const argv = process.argv.slice(2)
if (argv.length === 0) {
  console.log('Please specify the wanted package manager: only-allow <npm|cnpm|pnpm|yarn>')
  process.exit(1)
}
// 读取用户传入的包管理器名称
const wantedPM = argv[0]
// 当前支持的包管理器
if (wantedPM !== 'npm' && wantedPM !== 'cnpm' && wantedPM !== 'pnpm' && wantedPM !== 'yarn') {
  console.log(`"${wantedPM}" is not a valid package manager. Available package managers are: npm, cnpm, pnpm, or yarn.`)
  process.exit(1)
}
// 当前进程使用的和期望的包管理器对比,不同则报错退出进程
const usedPM = whichPMRuns()
if (usedPM && usedPM.name !== wantedPM) {
  const boxenOpts = { borderColor: 'red', borderStyle: 'double', padding: 1 }
  switch (wantedPM) {
    case 'npm':
      console.log(boxen('Use "npm install" for installation in this project', boxenOpts))
      break
    case 'cnpm':
      console.log(boxen('Use "cnpm install" for installation in this project', boxenOpts))
      break
    case 'pnpm':
      console.log(boxen(`Use "pnpm install" for installation in this project.

If you don't have pnpm, install it via "npm i -g pnpm".
For more details, go to https://pnpm.js.org/`, boxenOpts))
      break
    case 'yarn':
      console.log(boxen(`Use "yarn" for installation in this project.

If you don't have Yarn, install it via "npm i -g yarn".
For more details, go to https://yarnpkg.com/`, boxenOpts))
      break
  }
  process.exit(1)
}

only-allow 代码不到40行,核心就两件事:

  1. 判断命令是否指定了包管理器,且指定的包管理器符合规定 <npm|cnpm|pnpm|yarn>
  2. 通过 which-pm-runs 获取当前执行命令的包管理器,如果当前使用的包管理器跟指定的包管理器不同,则发出警告并退出

which-pm-runs 读取包管理器运行时的环境变量

which-pm-runs 的代码不多

'use strict'
module.exports = function () {
  if (!process.env.npm_config_user_agent) {
    return undefined
  }
  return pmFromUserAgent(process.env.npm_config_user_agent)
}

function pmFromUserAgent (userAgent) {
  const pmSpec = userAgent.split(' ')[0]
  const separatorPos = pmSpec.lastIndexOf('/')
  return {
    name: pmSpec.substr(0, separatorPos),
    version: pmSpec.substr(separatorPos + 1)
  }
}

判断 process.env.npm_config_user_agent 这个环境变量在 <npm|cnpm|pnpm|yarn> 执行时会被写入,然后解析该变量内容,取出包管理器名称和版本并返回。