diff --git a/.changeset/clerk-update-yarn-hang.md b/.changeset/clerk-update-yarn-hang.md new file mode 100644 index 00000000..904b8314 --- /dev/null +++ b/.changeset/clerk-update-yarn-hang.md @@ -0,0 +1,5 @@ +--- +"clerk": patch +--- + +Fix `clerk update` hanging when a corepack-shimmed package manager (e.g. yarn) prompts on stdin to download itself on first use. Package-manager probes now run with stdin detached, `COREPACK_ENABLE_DOWNLOAD_PROMPT=0`, and a 1.5s timeout, so a missing or uninitialized PM is treated as not installed instead of blocking the command. diff --git a/packages/cli-core/src/lib/installer.ts b/packages/cli-core/src/lib/installer.ts index cac81051..ae59eb30 100644 --- a/packages/cli-core/src/lib/installer.ts +++ b/packages/cli-core/src/lib/installer.ts @@ -139,27 +139,69 @@ export async function getInstallerPackageDirs(): Promise { + let proc: ReturnType | undefined; + try { + proc = Bun.spawn(args, { + stdin: "ignore", + stdout: "pipe", + stderr: "ignore", + env: { ...process.env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" }, + }); + } catch { + return null; + } + let timedOut = false; + const timer = setTimeout(() => { + timedOut = true; + proc?.kill(); + }, PM_PROBE_TIMEOUT_MS); + try { + const exitCode = await proc.exited; + if (timedOut || exitCode !== 0) return null; + // `stdout: "pipe"` always yields a ReadableStream; the union in the type + // covers other stdout modes we don't use here. + const stdout = await new Response(proc.stdout as ReadableStream).text(); + return stdout.trim() || null; + } catch { + return null; + } finally { + clearTimeout(timer); + } +} + async function queryNpmPackageDir(): Promise { // `npm root -g` reports the actual global node_modules dir on both platforms // (POSIX: `/lib/node_modules`; Windows: `\node_modules`, no // `lib` segment). Constructing the path manually breaks on Windows. - const result = await Bun.$`npm root -g`.quiet().nothrow(); - if (result.exitCode !== 0) return null; - const dir = result.stdout.toString().trim(); + const dir = await probePmDir(["npm", "root", "-g"]); return dir ? await safeRealpath(dir) : null; } async function queryPnpmPackageDir(): Promise { - const result = await Bun.$`pnpm root -g`.quiet().nothrow(); - if (result.exitCode !== 0) return null; - const dir = result.stdout.toString().trim(); + const dir = await probePmDir(["pnpm", "root", "-g"]); return dir ? await safeRealpath(dir) : null; } async function queryYarnPackageDir(): Promise { - const result = await Bun.$`yarn global dir`.quiet().nothrow(); - if (result.exitCode !== 0) return null; - const dir = result.stdout.toString().trim(); + const dir = await probePmDir(["yarn", "global", "dir"]); return dir ? await safeRealpath(join(dir, "node_modules")) : null; }