diff --git a/src/cli.ts b/src/cli.ts index 0ba8094..90ecb2e 100755 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,7 +4,10 @@ import { resolve } from "pathe"; import { consola } from "consola"; import { name, version, description } from "../package.json"; import { addDependency, installDependencies, removeDependency } from "./api"; -import { detectPackageManager } from "./package-manager"; +import { + detectPackageManager, + detectRuntimePackageManager, +} from "./package-manager"; const operationArgs = { cwd: { @@ -94,6 +97,22 @@ const detect = defineCommand({ }, }); +const detectRunner = defineCommand({ + meta: { + description: "Detect the current package runner using runtime checks", + }, + run: () => { + const packageManager = detectRuntimePackageManager(); + if (!packageManager) { + consola.error(`Cannot detect package runner`); + return process.exit(1); + } + consola.log( + `Detected package runner \`${packageManager.name}@${packageManager.version}\``, + ); + }, +}); + const main = defineCommand({ meta: { name, @@ -109,6 +128,7 @@ const main = defineCommand({ uninstall: remove, un: remove, detect, + "detect-runner": detectRunner, }, }); diff --git a/src/package-manager.ts b/src/package-manager.ts index 95a9860..2d707c8 100644 --- a/src/package-manager.ts +++ b/src/package-manager.ts @@ -1,6 +1,7 @@ import { existsSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { join, resolve } from "pathe"; +import { env, process } from "std-env"; import { findup } from "./_utils"; import type { PackageManager } from "./types"; @@ -27,7 +28,12 @@ export type DetectPackageManagerOptions = { includeParentDirs?: boolean; /** - * Weather to ignore argv[1] to detect script + * Weather to ignore runtime checks to detect package manager or script runner + */ + ignoreRuntime?: boolean; + + /** + * Whether to ignore argv[1] to detect package manager or script runner, implied if `ignoreRuntimeChecks` is `true` */ ignoreArgv?: boolean; }; @@ -37,23 +43,27 @@ export const packageManagers: PackageManager[] = [ name: "npm", command: "npm", lockFile: "package-lock.json", + packageRunner: "npx", }, { name: "pnpm", command: "pnpm", lockFile: "pnpm-lock.yaml", files: ["pnpm-workspace.yaml"], + packageRunner: "pnpm dlx", }, { name: "bun", command: "bun", lockFile: ["bun.lockb", "bun.lock"], + packageRunner: "bunx", }, { name: "yarn", command: "yarn", majorVersion: "1", lockFile: "yarn.lock", + packageRunner: undefined, }, { name: "yarn", @@ -61,6 +71,7 @@ export const packageManagers: PackageManager[] = [ majorVersion: "3", lockFile: "yarn.lock", files: [".yarnrc.yml"], + packageRunner: "yarn dlx", }, { name: "deno", @@ -138,10 +149,41 @@ export async function detectPackageManager( }, ); - if (!detected && !options.ignoreArgv) { - // 3. Try to detect based on dlx/exec command + if (!detected && !options.ignoreRuntime) { + return detectRuntimePackageManager(options); + } + + return detected; +} + +export function detectRuntimePackageManager( + options: DetectPackageManagerOptions = {}, +): PackageManager | undefined { + const userAgent = env["npm_config_user_agent"]; + + const engine = userAgent?.split(" ")?.[0] ?? ""; + + const [name, version = "0.0.0"] = engine.split("/"); + + if (name) { + const majorVersion = version.split(".")[0]; + const packageManager = + packageManagers.find( + (pm) => pm.name === name && pm.majorVersion === majorVersion, + ) || packageManagers.find((pm) => pm.name === name); + if (packageManager) { + return { + ...packageManager, + command: name, + version, + majorVersion, + }; + } + } + if (!options.ignoreArgv) { + // Fallback to detecting based on argv // https://github.com/unjs/nypm/issues/116 - const scriptArg = process.argv[1]; + const scriptArg = process.argv?.[1]; if (scriptArg) { for (const packageManager of packageManagers) { // Check /.[name] or /[name] in path @@ -152,6 +194,4 @@ export async function detectPackageManager( } } } - - return detected; } diff --git a/src/types.ts b/src/types.ts index c088cb1..78bc35e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,6 +3,7 @@ export type PackageManagerName = "npm" | "yarn" | "pnpm" | "bun" | "deno"; export type PackageManager = { name: PackageManagerName; command: string; + packageRunner?: string; version?: string; majorVersion?: string; lockFile?: string | string[];