前言
- 本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
- 这是源码共读的第20期,链接:第20期 | install-pkg
install-pkg 是什么
以编程方式安装包。自动检测包管理器(npm、yarn 和 pnpm)
源码地址 install-pkg
用法
import { installPackage } from '@antfu/install-pkg'
await installPackage('vite', { silent: true })
代码结构
源码分为查找包管理器和安装包两部分
// detect.ts
export async function detectPackageManager(cwd = process.cwd()) {
// ...根据lock文件识别当前使用的包管理器名称并返回
}
// install.ts
import execa from 'execa'
export async function installPackage(names: string | string[], options: InstallPackageOptions = {}) {
// ...整理执行命令用 execa 执行
}
查找包管理器模块
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',
}
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
}
在detect.ts文件文件中使用 find-up查找目录下包管理器的lock文件,然后根据对应的映射得到当前项目使用的包管理器
find-up
import path from 'node:path';
import {fileURLToPath} from 'node:url';
import {locatePath, locatePathSync} from 'locate-path';
const toPath = urlOrPath => urlOrPath instanceof URL ? fileURLToPath(urlOrPath) : urlOrPath;
export const findUpStop = Symbol('findUpStop');
export async function findUpMultiple(name, options = {}) {
let directory = path.resolve(toPath(options.cwd) || '');
const {root} = path.parse(directory);
const stopAt = path.resolve(directory, options.stopAt || root);
const limit = options.limit || Number.POSITIVE_INFINITY;
const paths = [name].flat();
const runMatcher = async locateOptions => {
if (typeof name !== 'function') {
return locatePath(paths, locateOptions);
}
const foundPath = await name(locateOptions.cwd);
if (typeof foundPath === 'string') {
return locatePath([foundPath], locateOptions);
}
return foundPath;
};
const matches = [];
// eslint-disable-next-line no-constant-condition
while (true) {
// eslint-disable-next-line no-await-in-loop
const foundPath = await runMatcher({...options, cwd: directory});
if (foundPath === findUpStop) {
break;
}
if (foundPath) {
matches.push(path.resolve(directory, foundPath));
}
if (directory === stopAt || matches.length >= limit) {
break;
}
directory = path.dirname(directory);
}
return matches;
}
export async function findUp(name, options = {}) {
const matches = await findUpMultiple(name, {...options, limit: 1});
return matches[0];
}
查找目录下包管理器的lock文件名
安装包模块
通过 execa 来执行安装脚本
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 = {}) {
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',
cwd: options.cwd,
},
)
}