# @antfu/install-pkg

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

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

  • Github (opens new window)

# Usage

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

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

# 源码

  • 入口 index.ts
// index.ts
export * from './detect'
export * from './install'
1
2
3
  • 自动检测包管理器 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
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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'
1
2
3
4
5
6
7

find-up (opens new window)
path.basename (opens new window)

  • 编程式安装 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,
    },
  )
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

execa (opens new window)

installPackage('vite', {
  dev: true,
  silent: true,
  packageManager: 'npm',
  preferOffline: true,
})
// npm install -D --prefer-offline vite
1
2
3
4
5
6
7

filter(Boolean) 去除无效值

# 依赖

{
  "dependencies": {
    "execa": "^5.1.1",
    "find-up": "^5.0.0"
  }
}
1
2
3
4
5
6

# 收获

  • 了解 find-up execa 使用

  • path.basename()

  • options.stdio