【若川视野 x 源码共读】第20期 | install-pkg

53 阅读1分钟

@antfu/install-pkg

  • 编程式安装包,自动检测包管理器(npm,yarn,pnpm)

  • 通过lock文件检测包管理器,然后通过 execa 执行安装命令

  • Github install-pkg

Usage

npm i @antfu/install-pkg 
import { installPackage } from '@antfu/install-pkg'

await installPackage('vite', { silent: true })

源码

  • 入口 index.ts
// index.ts
export * from './detect'
export * from './install'
  • 自动检测包管理器 detectPackageManager
// detect.ts
import path from 'path'
import findUp from 'find-up'

export type PackageManager = 'pnpm' | 'yarn' | 'npm'

const LOCKS: Record<string, PackageManager> = {
  'pnpm-lock.yaml': 'pnpm',
  'yarn.lock': 'yarn',
  'package-lock.json': 'npm',
}

// process.cwd() = /User/duzit/code
export async function detectPackageManager(cwd = process.cwd()) {
  const result = await findUp(Object.keys(LOCKS), { cwd })
  const agent = (result ? LOCKS[path.basename(result)] : null)
  return agent
}

detectPackageManager(cwd) 默认传入当前目录,如 process.cwd() = /User/duzit/code

find-upcwd 下索引包管理器的lock文件,返回lock文件目录,再通过 path.basename() 获取对应的lock文件名

// /User/duzit/code/package-lock.json
cwd = '/User/duzit/code'
const result = await findUp(Object.keys(LOCKS), cwd) 
// result = /User/duzit/code/package-lock.json
const agent = (result ? LOCKS[path.basename(result)] : null)
// path.basename(result) = package-lock.json
// agent = 'npm'

find-up
path.basename

  • 编程式安装 execa
// install.ts 
import execa from 'execa'
import { detectPackageManager } from '.'

export interface InstallPackageOptions {
  cwd?: string
  dev?: boolean
  silent?: boolean
  packageManager?: string
  preferOffline?: boolean
  additionalArgs?: string[]
}

export async function installPackage(names: string | string[], options: InstallPackageOptions = {}) {
  // 默认 npm
  const agent = options.packageManager || await detectPackageManager(options.cwd) || 'npm'
  // 单个包名处理为数组形式
  if (!Array.isArray(names))
    names = [names]

  const args = options.additionalArgs || []

  if (options.preferOffline)
    args.unshift('--prefer-offline')

  return execa(
    agent,
    [
      agent === 'yarn'
        ? 'add'
        : 'install',
      options.dev ? '-D' : '',
      ...args,
      ...names,
    ].filter(Boolean),
    {
      stdio: options.silent ? 'ignore' : 'inherit', // http://nodejs.cn/api-v12/child_process/options_stdio.html 配置在父进程和子进程之间建立的管道
      cwd: options.cwd,
    },
  )
}

execa

installPackage('vite', {
  dev: true,
  silent: true,
  packageManager: 'npm',
  preferOffline: true,
})
// npm install -D --prefer-offline vite

filter(Boolean) 去除无效值

依赖

{
  "dependencies": {
    "execa": "^5.1.1",
    "find-up": "^5.0.0"
  }
}

收获

  • 了解 find-up execa 使用

  • path.basename()

  • options.stdio