-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Fix package manager detection #7655
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "hardhat": patch | ||
| --- | ||
|
|
||
| Fix package manager detection |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,35 +1,98 @@ | ||
| import { execSync } from "node:child_process"; | ||
| import path from "node:path"; | ||
|
|
||
| import { exists } from "@nomicfoundation/hardhat-utils/fs"; | ||
| import semver from "semver"; | ||
|
|
||
| type PackageManager = "npm" | "yarn" | "pnpm"; | ||
| type PackageManager = "npm" | "yarn" | "pnpm" | "bun" | "deno"; | ||
|
|
||
| /** | ||
| * getPackageManager returns the name of the package manager used in the workspace. | ||
| * It determines this by checking the presence of package manager specific lock files. | ||
| * getPackageManager returns the name of the package manager used to run Hardhat | ||
| * | ||
| * @param workspace The path to the workspace where the package manager should be checked. | ||
| * @returns The name of the package manager used in the workspace. | ||
| * This logic is based on the env variable `npm_config_user_agent`, which is set | ||
| * by all major package manager, both when running a package that has been | ||
| * installed, and when it hasn't. | ||
| * | ||
| * Here's how to reproduce it, with the value of the env var: | ||
| * | ||
| * npm: | ||
| * | ||
| * uninstalled: npx -y print-environment | ||
| * "npm/11.6.1 node/v24.10.0 linux arm64 workspaces/false" | ||
| * | ||
| * installed: npm init -y && npm i print-environment && npx print-environment | ||
| * "npm/11.6.1 node/v24.10.0 linux arm64 workspaces/false" | ||
| * | ||
| * | ||
| * pnpm: | ||
| * | ||
| * uninstalled: pnpm dlx print-environment | ||
| * "pnpm/10.18.3 npm/? node/v24.10.0 linux arm64" | ||
| * | ||
| * installed: pnpm init && pnpm add print-environment && pnpm print-environment | ||
| * "pnpm/10.18.3 npm/? node/v24.10.0 linux arm64" | ||
| * | ||
| * | ||
| * yarn classic: | ||
| * uninstalled: unsupported | ||
| * | ||
| * installed: yarn init -y && yarn add print-environment && yarn print-environment | ||
| * "yarn/1.22.22 npm/? node/v24.10.0 linux arm64" | ||
| * | ||
| * yarn berry: | ||
| * | ||
| * uninstalled: yarn set version berry && yarn dlx print-environment | ||
| * "yarn/4.10.3 npm/? node/v24.10.0 linux arm64" | ||
| * | ||
| * installed: yarn set version berry && yarn add print-environment && yarn print-environment | ||
| * "yarn/4.10.3 npm/? node/v24.10.0 linux arm64" | ||
| * | ||
| * bun: | ||
| * | ||
| * uninstalled: bunx print-environment | ||
| * "bun/1.3.1 npm/? node/v24.3.0 linux arm64" | ||
| * | ||
| * installed: bun init -y && bun add print-environment && bun print-environment | ||
| * "bun/1.3.1 npm/? node/v24.3.0 linux arm64" | ||
| * | ||
| * deno: | ||
| * | ||
| * uninstall: deno run -A npm:print-environment | ||
| * "deno/2.5.6 npm/? deno/2.5.6 linux aarch64" | ||
| * | ||
| * installed: deno init && deno add npm:print-environment && deno --allow-env print-environment | ||
| * "deno/2.5.6 npm/? deno/2.5.6 linux aarch64" | ||
| * | ||
| * @returns The name of the package manager used to run hardhat. | ||
| */ | ||
| export async function getPackageManager( | ||
| workspace: string, | ||
| ): Promise<PackageManager> { | ||
| const pathToYarnLock = path.join(workspace, "yarn.lock"); | ||
| const pathToPnpmLock = path.join(workspace, "pnpm-lock.yaml"); | ||
| export function getPackageManager(): PackageManager { | ||
| const DEFAULT = "npm"; | ||
|
|
||
| const invokedFromPnpm = (process.env.npm_config_user_agent ?? "").includes( | ||
| "pnpm", | ||
| ); | ||
| const userAgent = process.env.npm_config_user_agent; | ||
|
|
||
| if (await exists(pathToYarnLock)) { | ||
| return "yarn"; | ||
| if (userAgent === undefined) { | ||
| return DEFAULT; | ||
| } | ||
| if ((await exists(pathToPnpmLock)) || invokedFromPnpm) { | ||
| return "pnpm"; | ||
|
|
||
| const firstSlashIndex = userAgent.indexOf("/"); | ||
| if (firstSlashIndex === -1) { | ||
| return DEFAULT; | ||
| } | ||
|
|
||
| const packageManager = userAgent.substring(0, firstSlashIndex); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do the package managers provide any guarantees on the message they use?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above |
||
|
|
||
| switch (packageManager) { | ||
| case "npm": | ||
| return "npm"; | ||
| case "yarn": | ||
| return "yarn"; | ||
| case "pnpm": | ||
| return "pnpm"; | ||
| case "bun": | ||
| return "bun"; | ||
| case "deno": | ||
| return "deno"; | ||
| default: | ||
| return DEFAULT; | ||
| } | ||
| return "npm"; | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -49,11 +112,21 @@ export function getDevDependenciesInstallationCommand( | |
| npm: ["npm", "install", "--save-dev"], | ||
| yarn: ["yarn", "add", "--dev"], | ||
| pnpm: ["pnpm", "add", "--save-dev"], | ||
| deno: ["deno", "add"], | ||
| bun: ["bun", "add", "--dev"], | ||
| }; | ||
| const command = packageManagerToCommand[packageManager]; | ||
| // We quote all the dependency identifiers so that they can be run on a shell | ||
| // without semver symbols interfering with the command | ||
| command.push(...dependencies.map((d) => `"${d}"`)); | ||
| command.push( | ||
| ...dependencies.map((d) => { | ||
| if (packageManager === "deno") { | ||
| return `"npm:${d}"`; | ||
| } | ||
|
|
||
| return `"${d}"`; | ||
| }), | ||
| ); | ||
| return command; | ||
| } | ||
|
|
||
|
|
@@ -118,6 +191,15 @@ export async function installsPeerDependenciesByDefault( | |
| } | ||
| } | ||
| return false; | ||
| case "bun": | ||
| // Bun has installed peer dependencies for over 2 years, so that's fine | ||
| // https://github.com/oven-sh/bun/releases/tag/bun-v1.0.5 | ||
| // This can be disabled, and there's no easy way to check that, so we | ||
| // assume true for now | ||
| return true; | ||
| case "deno": | ||
| // Deno doesn't autoinstall peers | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,6 @@ import assert from "node:assert/strict"; | |||||||||
| import { afterEach, beforeEach, describe, it } from "node:test"; | ||||||||||
|
|
||||||||||
| import { useTmpDir } from "@nomicfoundation/hardhat-test-utils"; | ||||||||||
| import { writeUtf8File } from "@nomicfoundation/hardhat-utils/fs"; | ||||||||||
|
|
||||||||||
| import { | ||||||||||
| getDevDependenciesInstallationCommand, | ||||||||||
|
|
@@ -16,35 +15,69 @@ describe("getPackageManager", () => { | |||||||||
| let originalUserAgent: string | undefined; | ||||||||||
| beforeEach(() => { | ||||||||||
| originalUserAgent = process.env.npm_config_user_agent; | ||||||||||
| process.env.npm_config_user_agent = "npm"; // assume we are running npm by default | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| afterEach(() => { | ||||||||||
| process.env.npm_config_user_agent = originalUserAgent; | ||||||||||
| }); | ||||||||||
|
|
||||||||||
| it("should return pnpm if pnpm-lock.yaml exists", async () => { | ||||||||||
| await writeUtf8File("pnpm-lock.yaml", ""); | ||||||||||
| assert.equal(await getPackageManager(process.cwd()), "pnpm"); | ||||||||||
| }); | ||||||||||
| const fixtureValues = [ | ||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have end-to-end tests to alert us in case any of the package managers change this behaviour?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, but I think the chances of that changing are remote, as they would break the entire ecosystem |
||||||||||
| { | ||||||||||
| agentString: "npm/11.6.1 node/v24.10.0 linux arm64 workspaces/false", | ||||||||||
| expected: "npm", | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| agentString: "npm/11.6.1 node/v24.10.0 linux arm64 workspaces/false", | ||||||||||
| expected: "npm", | ||||||||||
| }, | ||||||||||
alcuadrado marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| { | ||||||||||
| agentString: "pnpm/10.18.3 npm/? node/v24.10.0 linux arm64", | ||||||||||
| expected: "pnpm", | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| agentString: "pnpm/10.18.3 npm/? node/v24.10.0 linux arm64", | ||||||||||
| expected: "pnpm", | ||||||||||
| }, | ||||||||||
| { | ||||||||||
alcuadrado marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
| agentString: "yarn/1.22.22 npm/? node/v24.10.0 linux arm64", | ||||||||||
| expected: "yarn", | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| agentString: "yarn/4.10.3 npm/? node/v24.10.0 linux arm64", | ||||||||||
| expected: "yarn", | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| agentString: "yarn/4.10.3 npm/? node/v24.10.0 linux arm64", | ||||||||||
| expected: "yarn", | ||||||||||
| }, | ||||||||||
alcuadrado marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||
| { | ||||||||||
| agentString: "bun/1.3.1 npm/? node/v24.3.0 linux arm64", | ||||||||||
| expected: "bun", | ||||||||||
| }, | ||||||||||
| { | ||||||||||
| agentString: "bun/1.3.1 npm/? node/v24.3.0 linux arm64", | ||||||||||
| expected: "bun", | ||||||||||
| }, | ||||||||||
| { | ||||||||||
|
Comment on lines
38
to
41
|
||||||||||
| agentString: "bun/1.3.1 npm/? node/v24.3.0 linux arm64", | |
| expected: "bun", | |
| }, | |
| { |
Uh oh!
There was an error while loading. Please reload this page.