diff --git a/src/index.ts b/src/index.ts index a335b88..4d5a6fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -471,22 +471,35 @@ function generateShShim (src: string, to: string, opts: InternalOptions): string // #!/bin/sh // # Resolve $0 through symlinks so basedir is the shim's real directory. // # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. + // + // # A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and + // # uname go through `command -p`, which searches the system default path instead. + // # A dependency's bin cannot stand in for one of them and take over the shim + // # before it reaches its target. Directories come from `${link%/*}`, which needs + // # no helper at all. // link="$0" + // # `${link%/*}` needs a separator to strip. A bare name came from a PATH lookup + // # and stands for a file in the current directory. + // case "$link" in + // */*|*\\*) ;; + // *) link="./$link" ;; + // esac // hops=0 // while [ -L "$link" ] && [ "$hops" -lt 40 ]; do // hops=$((hops+1)) - // target=$(readlink "$link") + // target=$(command -p readlink "$link") // case "$target" in // /*) link="$target" ;; - // *) link="$(dirname "$link")/$target" ;; + // *) link="${link%/*}/$target" ;; // esac // done - // basedir=$(dirname "$(echo "$link" | sed -e 's,\\,/,g')") + // basedir=$(echo "$link" | command -p sed -e 's,\\,/,g') + // basedir="${basedir%/*}" // basedir_win="$basedir" // exe="" // msys="" // - // case `uname -a` in + // case `command -p uname -a` in // *CYGWIN*|*MINGW*|*MSYS*) // if command -v cygpath > /dev/null 2>&1; then // basedir_win=`cygpath -w "$basedir"` @@ -524,22 +537,35 @@ function generateShShim (src: string, to: string, opts: InternalOptions): string #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` diff --git a/test/e2e.test.js b/test/e2e.test.js index f721f39..a3c9f8c 100644 --- a/test/e2e.test.js +++ b/test/e2e.test.js @@ -198,3 +198,87 @@ describeOnPosix('sh shim invoked through a chain of external symlinks', () => { assert.equal(runShim(hop), 'NODE_BIN_OK') }) }) + +describeOnPosix('sh shim resolves its helpers off the caller\'s PATH', () => { + // A shim runs with node_modules/.bin at the front of PATH, which is where a + // dependency's own bins live, so a helper taken from there could report any + // directory it liked and redirect what the shim finally execs + // (https://github.com/pnpm/pnpm/issues/14837). + const writeExecutable = (file, body) => { + fs.writeFileSync(file, body, 'utf8') + fs.chmodSync(file, 0o755) + } + + // A shimmed tool plus a relative symlink to it in the same directory, so the + // walk composes a directory with the link target instead of taking one + // straight from readlink. + const makeShimmedTool = async (tempDir) => { + const binDir = path.join(tempDir, 'node_modules', '.bin') + const target = path.join(tempDir, 'node_modules', 'typescript', 'bin', 'tsc.js') + fs.mkdirSync(binDir, { recursive: true }) + fs.mkdirSync(path.dirname(target), { recursive: true }) + fs.writeFileSync(target, 'console.log("tsc-output")\n', 'utf8') + // A dependency can declare a bin named node.exe, and the shim's basedir is + // the directory those bins land in. Only a lying uname reaches it. + writeExecutable(path.join(binDir, 'node.exe'), '#!/bin/sh\necho hijacked\n') + + const shim = path.join(binDir, 'tsc') + await cmdShim(target, shim, { createCmdFile: false }) + fs.symlinkSync('tsc', path.join(binDir, 'tsc-link')) + return binDir + } + + // Write the tree the decoys point at, and the decoys, returning the directory + // to put at the front of PATH. Each decoy answers with what its real + // counterpart would be asked for, so any one of them alone is enough to + // redirect the shim. + const plantHijackTreeAndDecoys = (tempDir) => { + const hijack = path.join(tempDir, 'hijack', 'node_modules') + const hijackBin = path.join(hijack, '.bin') + const hijackTarget = path.join(hijack, 'typescript', 'bin', 'tsc.js') + fs.mkdirSync(hijackBin, { recursive: true }) + fs.mkdirSync(path.dirname(hijackTarget), { recursive: true }) + fs.writeFileSync(hijackTarget, 'console.log("hijacked")\n', 'utf8') + + const decoyDir = path.join(tempDir, 'decoy') + fs.mkdirSync(decoyDir) + const answer = (p) => `#!/bin/sh\necho '${p}'\n` + for (const helper of ['readlink', 'sed']) { + writeExecutable(path.join(decoyDir, helper), answer(path.join(hijackBin, 'tsc'))) + } + writeExecutable(path.join(decoyDir, 'dirname'), answer(hijackBin)) + writeExecutable(path.join(decoyDir, 'uname'), '#!/bin/sh\necho MINGW64_NT-10.0\n') + return decoyDir + } + + const runWithDecoys = (tempDir, cmd, args, cwd) => { + const decoyDir = plantHijackTreeAndDecoys(tempDir) + const r = spawnSync(cmd, args, { + cwd, + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + env: { + ...process.env, + PATH: [decoyDir, path.dirname(process.execPath), process.env.PATH].join(path.delimiter), + }, + }) + assert.equal(r.status, 0, `shim exited ${r.status}\nstdout: ${r.stdout}\nstderr: ${r.stderr}`) + assert.equal(r.stdout.trim(), 'tsc-output', 'the shim took a helper from the caller\'s PATH') + } + + test('reaches its target with decoy readlink, dirname, sed, and uname first on PATH', async () => { + const tempDir = tempy.directory() + const binDir = await makeShimmedTool(tempDir) + + runWithDecoys(tempDir, path.join(binDir, 'tsc-link'), []) + }) + + // The kernel and execvp hand the interpreter the path they resolved, so $0 is + // bare only when a shell is given the name itself. + test('reaches its target when sh receives a bare name', async () => { + const tempDir = tempy.directory() + const binDir = await makeShimmedTool(tempDir) + + runWithDecoys(tempDir, 'sh', ['tsc-link'], binDir) + }) +}) diff --git a/test/e2e.test.js.snapshot b/test/e2e.test.js.snapshot index 20db918..9719dd1 100644 --- a/test/e2e.test.js.snapshot +++ b/test/e2e.test.js.snapshot @@ -3,22 +3,35 @@ exports[`create a command shim for a .exe file > shim files 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` diff --git a/test/test.js.snapshot b/test/test.js.snapshot index 9b29f19..d88ffa8 100644 --- a/test/test.js.snapshot +++ b/test/test.js.snapshot @@ -3,22 +3,35 @@ exports[`batch script > shim files > bat.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -118,22 +131,35 @@ exports[`custom node executable > shim files > env.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -191,22 +217,35 @@ exports[`env shebang > shim files > env.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -290,22 +329,35 @@ exports[`env shebang with NODE_PATH > shim files > env.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -412,22 +464,35 @@ exports[`env shebang with PATH extending > shim files > env.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -523,22 +588,35 @@ exports[`env shebang with args > shim files > env.args.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -622,22 +700,35 @@ exports[`env shebang with default args > shim files > env.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -721,22 +812,35 @@ exports[`env shebang with no NODE_PATH > shim files > env.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -820,22 +924,35 @@ exports[`explicit shebang > shim files > sh.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -919,22 +1036,35 @@ exports[`explicit shebang with args > shim files > sh.args.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -1018,22 +1148,35 @@ exports[`explicit shebang with args, linking to another drive on Windows > shim #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -1117,22 +1260,35 @@ exports[`explicit shebang with prog args > shim files > sh.args.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -1216,22 +1372,35 @@ exports[`no cmd file > shim files > exe.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -1282,22 +1451,35 @@ exports[`no shebang > shim files > exe.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\` @@ -1355,22 +1537,35 @@ exports[`shebang with -S > shim files > from.env.s.shim 1`] = ` #!/bin/sh # Resolve $0 through symlinks so basedir is the shim's real directory. # Cap hops at the kernel's ELOOP limit so a cycle cannot hang the shim. +# +# A shim runs with node_modules/.bin at the front of PATH, so readlink, sed, and +# uname go through \`command -p\`, which searches the system default path instead. +# A dependency's bin cannot stand in for one of them and take over the shim +# before it reaches its target. Directories come from \`\${link%/*}\`, which needs +# no helper at all. link="$0" +# \`\${link%/*}\` needs a separator to strip. A bare name came from a PATH lookup +# and stands for a file in the current directory. +case "$link" in + */*|*\\\\*) ;; + *) link="./$link" ;; +esac hops=0 while [ -L "$link" ] && [ "$hops" -lt 40 ]; do hops=$((hops+1)) - target=$(readlink "$link") + target=$(command -p readlink "$link") case "$target" in /*) link="$target" ;; - *) link="$(dirname "$link")/$target" ;; + *) link="\${link%/*}/$target" ;; esac done -basedir=$(dirname "$(echo "$link" | sed -e 's,\\\\,/,g')") +basedir=$(echo "$link" | command -p sed -e 's,\\\\,/,g') +basedir="\${basedir%/*}" basedir_win="$basedir" exe="" msys="" -case \`uname -a\` in +case \`command -p uname -a\` in *CYGWIN*|*MINGW*|*MSYS*) if command -v cygpath > /dev/null 2>&1; then basedir_win=\`cygpath -w "$basedir"\`