mirror of
https://github.com/soconnor0919/beenpad.git
synced 2026-02-05 08:16:37 -05:00
214 lines
6.1 KiB
JavaScript
214 lines
6.1 KiB
JavaScript
import process from 'node:process';
|
|
import fs from 'node:fs/promises';
|
|
import path, { resolve } from 'node:path';
|
|
import { existsSync } from 'node:fs';
|
|
import { x } from 'tinyexec';
|
|
|
|
const AGENTS = [
|
|
"npm",
|
|
"yarn",
|
|
"yarn@berry",
|
|
"pnpm",
|
|
"pnpm@6",
|
|
"bun",
|
|
"deno"
|
|
];
|
|
const LOCKS = {
|
|
"bun.lock": "bun",
|
|
"bun.lockb": "bun",
|
|
"deno.lock": "deno",
|
|
"pnpm-lock.yaml": "pnpm",
|
|
"pnpm-workspace.yaml": "pnpm",
|
|
"yarn.lock": "yarn",
|
|
"package-lock.json": "npm",
|
|
"npm-shrinkwrap.json": "npm"
|
|
};
|
|
const INSTALL_METADATA = {
|
|
"node_modules/.deno/": "deno",
|
|
"node_modules/.pnpm/": "pnpm",
|
|
"node_modules/.yarn-state.yml": "yarn",
|
|
// yarn v2+ (node-modules)
|
|
"node_modules/.yarn_integrity": "yarn",
|
|
// yarn v1
|
|
"node_modules/.package-lock.json": "npm",
|
|
".pnp.cjs": "yarn",
|
|
// yarn v3+ (pnp)
|
|
".pnp.js": "yarn",
|
|
// yarn v2 (pnp)
|
|
"bun.lock": "bun",
|
|
"bun.lockb": "bun"
|
|
};
|
|
|
|
async function pathExists(path2, type) {
|
|
try {
|
|
const stat = await fs.stat(path2);
|
|
return type === "file" ? stat.isFile() : stat.isDirectory();
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
function* lookup(cwd = process.cwd()) {
|
|
let directory = path.resolve(cwd);
|
|
const { root } = path.parse(directory);
|
|
while (directory && directory !== root) {
|
|
yield directory;
|
|
directory = path.dirname(directory);
|
|
}
|
|
}
|
|
async function parsePackageJson(filepath, onUnknown) {
|
|
return !filepath || !pathExists(filepath, "file") ? null : await handlePackageManager(filepath, onUnknown);
|
|
}
|
|
async function detect(options = {}) {
|
|
const {
|
|
cwd,
|
|
strategies = ["lockfile", "packageManager-field", "devEngines-field"],
|
|
onUnknown
|
|
} = options;
|
|
let stopDir;
|
|
if (typeof options.stopDir === "string") {
|
|
const resolved = path.resolve(options.stopDir);
|
|
stopDir = (dir) => dir === resolved;
|
|
} else {
|
|
stopDir = options.stopDir;
|
|
}
|
|
for (const directory of lookup(cwd)) {
|
|
for (const strategy of strategies) {
|
|
switch (strategy) {
|
|
case "lockfile": {
|
|
for (const lock of Object.keys(LOCKS)) {
|
|
if (await pathExists(path.join(directory, lock), "file")) {
|
|
const name = LOCKS[lock];
|
|
const result = await parsePackageJson(path.join(directory, "package.json"), onUnknown);
|
|
if (result)
|
|
return result;
|
|
else
|
|
return { name, agent: name };
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case "packageManager-field":
|
|
case "devEngines-field": {
|
|
const result = await parsePackageJson(path.join(directory, "package.json"), onUnknown);
|
|
if (result)
|
|
return result;
|
|
break;
|
|
}
|
|
case "install-metadata": {
|
|
for (const metadata of Object.keys(INSTALL_METADATA)) {
|
|
const fileOrDir = metadata.endsWith("/") ? "dir" : "file";
|
|
if (await pathExists(path.join(directory, metadata), fileOrDir)) {
|
|
const name = INSTALL_METADATA[metadata];
|
|
const agent = name === "yarn" ? isMetadataYarnClassic(metadata) ? "yarn" : "yarn@berry" : name;
|
|
return { name, agent };
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (stopDir?.(directory))
|
|
break;
|
|
}
|
|
return null;
|
|
}
|
|
function getNameAndVer(pkg) {
|
|
const handelVer = (version) => version?.match(/\d+(\.\d+){0,2}/)?.[0] ?? version;
|
|
if (typeof pkg.packageManager === "string") {
|
|
const [name, ver] = pkg.packageManager.replace(/^\^/, "").split("@");
|
|
return { name, ver: handelVer(ver) };
|
|
}
|
|
if (typeof pkg.devEngines?.packageManager?.name === "string") {
|
|
return {
|
|
name: pkg.devEngines.packageManager.name,
|
|
ver: handelVer(pkg.devEngines.packageManager.version)
|
|
};
|
|
}
|
|
return void 0;
|
|
}
|
|
async function handlePackageManager(filepath, onUnknown) {
|
|
try {
|
|
const pkg = JSON.parse(await fs.readFile(filepath, "utf8"));
|
|
let agent;
|
|
const nameAndVer = getNameAndVer(pkg);
|
|
if (nameAndVer) {
|
|
const name = nameAndVer.name;
|
|
const ver = nameAndVer.ver;
|
|
let version = ver;
|
|
if (name === "yarn" && ver && Number.parseInt(ver) > 1) {
|
|
agent = "yarn@berry";
|
|
version = "berry";
|
|
return { name, agent, version };
|
|
} else if (name === "pnpm" && ver && Number.parseInt(ver) < 7) {
|
|
agent = "pnpm@6";
|
|
return { name, agent, version };
|
|
} else if (AGENTS.includes(name)) {
|
|
agent = name;
|
|
return { name, agent, version };
|
|
} else {
|
|
return onUnknown?.(pkg.packageManager) ?? null;
|
|
}
|
|
}
|
|
} catch {
|
|
}
|
|
return null;
|
|
}
|
|
function isMetadataYarnClassic(metadataPath) {
|
|
return metadataPath.endsWith(".yarn_integrity");
|
|
}
|
|
|
|
// src/detect.ts
|
|
async function detectPackageManager(cwd = process.cwd()) {
|
|
const result = await detect({
|
|
cwd,
|
|
onUnknown(packageManager) {
|
|
console.warn("[@antfu/install-pkg] Unknown packageManager:", packageManager);
|
|
return void 0;
|
|
}
|
|
});
|
|
return result?.agent || null;
|
|
}
|
|
async function installPackage(names, options = {}) {
|
|
const detectedAgent = options.packageManager || await detectPackageManager(options.cwd) || "npm";
|
|
const [agent] = detectedAgent.split("@");
|
|
if (!Array.isArray(names))
|
|
names = [names];
|
|
const args = (typeof options.additionalArgs === "function" ? options.additionalArgs(agent, detectedAgent) : options.additionalArgs) || [];
|
|
if (options.preferOffline) {
|
|
if (detectedAgent === "yarn@berry")
|
|
args.unshift("--cached");
|
|
else
|
|
args.unshift("--prefer-offline");
|
|
}
|
|
if (agent === "pnpm") {
|
|
args.unshift(
|
|
/**
|
|
* Prevent pnpm from removing installed devDeps while `NODE_ENV` is `production`
|
|
* @see https://pnpm.io/cli/install#--prod--p
|
|
*/
|
|
"--prod=false"
|
|
);
|
|
if (existsSync(resolve(options.cwd ?? process.cwd(), "pnpm-workspace.yaml"))) {
|
|
args.unshift("-w");
|
|
}
|
|
}
|
|
return x(
|
|
agent,
|
|
[
|
|
agent === "yarn" ? "add" : "install",
|
|
options.dev ? "-D" : "",
|
|
...args,
|
|
...names
|
|
].filter(Boolean),
|
|
{
|
|
nodeOptions: {
|
|
stdio: options.silent ? "ignore" : "inherit",
|
|
cwd: options.cwd
|
|
},
|
|
throwOnError: true
|
|
}
|
|
);
|
|
}
|
|
|
|
export { detectPackageManager, installPackage };
|