diff --git a/.yarn/versions/fix-7210.yml b/.yarn/versions/fix-7210.yml new file mode 100644 index 00000000000..6c24923445d --- /dev/null +++ b/.yarn/versions/fix-7210.yml @@ -0,0 +1,36 @@ +releases: + "@yarnpkg/core": patch + +declined: + - "@yarnpkg/plugin-catalog" + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-exec" + - "@yarnpkg/plugin-file" + - "@yarnpkg/plugin-git" + - "@yarnpkg/plugin-github" + - "@yarnpkg/plugin-http" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-jsr" + - "@yarnpkg/plugin-link" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/cli" + - "@yarnpkg/doctor" + - "@yarnpkg/extensions" + - "@yarnpkg/nm" + - "@yarnpkg/pnpify" + - "@yarnpkg/sdks" diff --git a/packages/yarnpkg-core/sources/scriptUtils.ts b/packages/yarnpkg-core/sources/scriptUtils.ts index e0c3fb0f012..0a0893e65bf 100644 --- a/packages/yarnpkg-core/sources/scriptUtils.ts +++ b/packages/yarnpkg-core/sources/scriptUtils.ts @@ -37,16 +37,27 @@ interface PackageManagerSelection { reason: string; } -async function makePathWrapper(location: PortablePath, name: Filename, argv0: NativePath, args: Array = []) { - if (process.platform === `win32`) { - // https://github.com/microsoft/terminal/issues/217#issuecomment-737594785 - const cmdScript = `@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @"${argv0}" ${args.map(arg => `"${arg.replace(`"`, `""`)}"`).join(` `)} %*`; - await xfs.writeFilePromise(ppath.format({dir: location, name, ext: `.cmd`}), cmdScript); - } +async function makePathWrapper(location: PortablePath, name: Filename, argv0: NativePath, args: Array = [], nodeWrapperPath?: NativePath) { + if (nodeWrapperPath && (argv0 === process.execPath || npath.toPortablePath(argv0) === npath.toPortablePath(process.execPath))) { + if (process.platform === `win32`) { + const cmdScript = `@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @set "WRAPPER_NODE_OPTIONS=%NODE_OPTIONS%" & @set "NODE_OPTIONS=" & @"${process.execPath}" "${nodeWrapperPath}" "${argv0}" ${args.map(arg => `"${arg.replace(`"`, `""`)}"`).join(` `)} %*`; + await xfs.writeFilePromise(ppath.format({dir: location, name, ext: `.cmd`}), cmdScript); + } - await xfs.writeFilePromise(ppath.join(location, name), `#!/bin/sh\nexec "${argv0}" ${args.map(arg => `'${arg.replace(/'/g, `'"'"'`)}'`).join(` `)} "$@"\n`, { - mode: 0o755, - }); + await xfs.writeFilePromise(ppath.join(location, name), `#!/bin/sh\nexport WRAPPER_NODE_OPTIONS="$NODE_OPTIONS"\nexport NODE_OPTIONS=""\nexec "${process.execPath}" "${nodeWrapperPath}" "${argv0}" ${args.map(arg => `'${arg.replace(/'/g, `'"'"'`)}'`).join(` `)} "$@"\n`, { + mode: 0o755, + }); + } else { + if (process.platform === `win32`) { + // https://github.com/microsoft/terminal/issues/217#issuecomment-737594785 + const cmdScript = `@goto #_undefined_# 2>NUL || @title %COMSPEC% & @setlocal & @"${argv0}" ${args.map(arg => `"${arg.replace(`"`, `""`)}"`).join(` `)} %*`; + await xfs.writeFilePromise(ppath.format({dir: location, name, ext: `.cmd`}), cmdScript); + } + + await xfs.writeFilePromise(ppath.join(location, name), `#!/bin/sh\nexec "${argv0}" ${args.map(arg => `'${arg.replace(/'/g, `'"'"'`)}'`).join(` `)} "$@"\n`, { + mode: 0o755, + }); + } } /** @@ -119,6 +130,87 @@ export async function makeScriptEnv({project, locator, binFolder, ignoreCorepack // binaries for the dependencies of the active package scriptEnv.BERRY_BIN_FOLDER = npath.fromPortablePath(nBinFolder); + let nodeWrapperNativePath: string | undefined; + if (project) { + scriptEnv.BERRY_PROJECT_ROOT = npath.fromPortablePath(project.cwd); + + const nodeWrapperPath = ppath.join(binFolder, `node-wrapper.js` as Filename); + const nodeWrapperContent = `const child_process = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const realNode = process.argv[2]; +const args = process.argv.slice(3); + +let strip = false; +const projectRoot = process.env.BERRY_PROJECT_ROOT; +if (projectRoot) { + const resolvedProjectRoot = path.resolve(projectRoot); + for (const arg of args) { + if (arg.startsWith('-')) + continue; + + try { + if (fs.existsSync(arg) && fs.statSync(arg).isFile()) { + const resolvedPath = path.resolve(arg); + const relative = path.relative(resolvedProjectRoot, resolvedPath); + const isOutside = relative.startsWith('..') || path.isAbsolute(relative); + if (isOutside) { + strip = true; + break; + } + } + } catch {} + } +} + +const env = { ...process.env }; +let nodeOptions = env.WRAPPER_NODE_OPTIONS || ''; +delete env.WRAPPER_NODE_OPTIONS; + +if (strip && nodeOptions) { + nodeOptions = nodeOptions + .replace(/--experimental-package-map(?:="[^"]*"|='[^']*'|=\\S+)/g, '') + .trim(); +} + +env.NODE_OPTIONS = nodeOptions; + +const cp = child_process.spawn(realNode, args, { + stdio: 'inherit', + env, +}); + +cp.on('error', err => { + console.error(err); + process.exit(1); +}); + +cp.on('exit', (code, signal) => { + if (signal) { + process.kill(process.pid, signal); + } else { + process.exit(code ?? 0); + } +}); + +const signals = ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGBREAK']; +for (const sig of signals) { + try { + process.on(sig, () => { + if (cp.pid) { + try { + process.kill(cp.pid, sig); + } catch {} + } + }); + } catch {} +} +`; + await xfs.writeFilePromise(nodeWrapperPath, nodeWrapperContent); + nodeWrapperNativePath = npath.fromPortablePath(nodeWrapperPath); + } + // Otherwise we'd override the Corepack binaries, and thus break the detection // of the `packageManager` field when running Yarn in other directories. const yarnBin = process.env.COREPACK_ROOT && !ignoreCorepack @@ -128,12 +220,12 @@ export async function makeScriptEnv({project, locator, binFolder, ignoreCorepack // Register some binaries that must be made available in all subprocesses // spawned by Yarn (we thus ensure that they always use the right version) await Promise.all([ - makePathWrapper(binFolder, `node` as Filename, process.execPath), + makePathWrapper(binFolder, `node` as Filename, process.execPath, [], nodeWrapperNativePath), ...YarnVersion !== null ? [ - makePathWrapper(binFolder, `run` as Filename, process.execPath, [yarnBin, `run`]), - makePathWrapper(binFolder, `yarn` as Filename, process.execPath, [yarnBin]), - makePathWrapper(binFolder, `yarnpkg` as Filename, process.execPath, [yarnBin]), - makePathWrapper(binFolder, `node-gyp` as Filename, process.execPath, [yarnBin, `run`, `--top-level`, `node-gyp`]), + makePathWrapper(binFolder, `run` as Filename, process.execPath, [yarnBin, `run`], nodeWrapperNativePath), + makePathWrapper(binFolder, `yarn` as Filename, process.execPath, [yarnBin], nodeWrapperNativePath), + makePathWrapper(binFolder, `yarnpkg` as Filename, process.execPath, [yarnBin], nodeWrapperNativePath), + makePathWrapper(binFolder, `node-gyp` as Filename, process.execPath, [yarnBin, `run`, `--top-level`, `node-gyp`], nodeWrapperNativePath), ] : [], ]); @@ -729,11 +821,16 @@ export async function getWorkspaceAccessibleBinaries(workspace: Workspace) { } async function installBinaries(target: PortablePath, binaries: PackageAccessibleBinaries) { + const nodeWrapperPath = ppath.join(target, `node-wrapper.js` as Filename); + const nodeWrapperNativePath = xfs.existsSync(nodeWrapperPath) + ? npath.fromPortablePath(nodeWrapperPath) + : undefined; + await Promise.all( Array.from(binaries, ([binaryName, [, binaryPath, isScript]]) => { return isScript - ? makePathWrapper(target, binaryName as Filename, process.execPath, [binaryPath]) - : makePathWrapper(target, binaryName as Filename, binaryPath, []); + ? makePathWrapper(target, binaryName as Filename, process.execPath, [binaryPath], nodeWrapperNativePath) + : makePathWrapper(target, binaryName as Filename, binaryPath, [], nodeWrapperNativePath); }), ); }