From 312bc1708514dcf0ad2fa683cbc6526b3a1928df Mon Sep 17 00:00:00 2001 From: Joni Vainio-Kaila Date: Fri, 13 Feb 2026 10:15:45 +0200 Subject: [PATCH] fix(package): Spawn child process scripts with same package manager as user uses --- .../src/plugin/detectPackageManagerRunner.ts | 36 +++++++++++++++++++ package/src/plugin/plugin.test.ts | 2 +- package/src/plugin/plugin.ts | 24 ++++++++----- 3 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 package/src/plugin/detectPackageManagerRunner.ts diff --git a/package/src/plugin/detectPackageManagerRunner.ts b/package/src/plugin/detectPackageManagerRunner.ts new file mode 100644 index 0000000..2948d1a --- /dev/null +++ b/package/src/plugin/detectPackageManagerRunner.ts @@ -0,0 +1,36 @@ +import { existsSync } from "node:fs"; +import path from "node:path"; + +type PackageManagerRunner = "npx" | "pnpm exec" | "yarn" | "bunx"; + +export function detectPackageManagerRunner(): PackageManagerRunner { + // 1. Check environment variable + const packageManagerFromEnv = detectPackageManagerRunnerFromEnv(); + if (packageManagerFromEnv) return packageManagerFromEnv; + + // 2. Check lock files + const packageManagerFromLockFile = detectPackageManagerRunnerFromLockFiles(); + if (packageManagerFromLockFile) return packageManagerFromLockFile; + + // 3. Fallback to npx + return "npx"; +} + +function detectPackageManagerRunnerFromEnv(): PackageManagerRunner | undefined { + const userAgent = process.env.npm_config_user_agent; + if (!userAgent) return; + if (userAgent.startsWith("pnpm")) return "pnpm exec"; + if (userAgent.startsWith("yarn")) return "yarn"; + if (userAgent.startsWith("bun")) return "bunx"; + if (userAgent.startsWith("npm")) return "npx"; +} + +function detectPackageManagerRunnerFromLockFiles(): + | PackageManagerRunner + | undefined { + const cwd = process.cwd(); + if (existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm exec"; + if (existsSync(path.join(cwd, "yarn.lock"))) return "yarn"; + if (existsSync(path.join(cwd, "bun.lockb"))) return "bunx"; + if (existsSync(path.join(cwd, "package-lock.json"))) return "npx"; +} diff --git a/package/src/plugin/plugin.test.ts b/package/src/plugin/plugin.test.ts index cc02669..03b6e24 100644 --- a/package/src/plugin/plugin.test.ts +++ b/package/src/plugin/plugin.test.ts @@ -96,7 +96,7 @@ describe("plugin", () => { await withNextGlobeGenPlugin(CONFIG_PATH)({})("phase-production-build"); expect(spawn).not.toBeCalled(); expect(spawnSync).toHaveBeenCalledWith( - "npx next-globe-gen --plugin --config ./src/__mocks__/i18n.config.ts", + "pnpm exec next-globe-gen --plugin --config ./src/__mocks__/i18n.config.ts", expect.objectContaining({ stdio: "inherit", shell: true, diff --git a/package/src/plugin/plugin.ts b/package/src/plugin/plugin.ts index 044752d..f798b27 100644 --- a/package/src/plugin/plugin.ts +++ b/package/src/plugin/plugin.ts @@ -13,6 +13,7 @@ import { createRequire } from "node:module"; import { resolve } from "node:path"; import type { Config, DomainConfig } from "~/config/types"; import { compile } from "~/utils/ts-utils"; +import { detectPackageManagerRunner } from "./detectPackageManagerRunner"; type Phase = | typeof PHASE_EXPORT @@ -56,9 +57,10 @@ async function useGenerator(configPath: string, phase: Phase) { if (nextjsVersion.major >= 16 && phase === "phase-development-server" && !process.env.NEXT_PRIVATE_WORKER) return; if (nextjsVersion.major < 16 && process.env.NEXT_PRIVATE_WORKER) return; if (process.env.NEXT_DEPLOYMENT_ID !== undefined) return; + const runner = detectPackageManagerRunner(); try { if (phase !== "phase-production-server") { - spawnSync(`npx next-globe-gen --plugin --config ${configPath}`, { + spawnSync(`${runner} next-globe-gen --plugin --config ${configPath}`, { cwd: process.cwd(), stdio: "inherit", shell: true, @@ -77,16 +79,20 @@ async function useGenerator(configPath: string, phase: Phase) { abortController.abort(); process.exit(); }); - spawn(`npx next-globe-gen --plugin --watch --config ${configPath}`, { - cwd: process.cwd(), - stdio: "inherit", - shell: true, - detached: false, - signal: abortController.signal, - }); + spawn( + `${runner} next-globe-gen --plugin --watch --config ${configPath}`, + { + cwd: process.cwd(), + stdio: "inherit", + shell: true, + detached: false, + signal: abortController.signal, + }, + ); } - } catch (_e) { + } catch (error) { console.error("Failed to spawn the NextGlobeGen compiler process"); + throw error; } }