diff --git a/build/cli.cjs b/build/cli.cjs index e494590738..88ab56eae7 100755 --- a/build/cli.cjs +++ b/build/cli.cjs @@ -356,7 +356,16 @@ function getFilepath(cwd = ".", name = "zx", _ext) { return [ name + ext, name + "-" + (0, import_util2.randomId)() + ext - ].map((f) => import_index.path.resolve(import_node_process2.default.cwd(), cwd, f)).find((f) => !import_index.fs.existsSync(f)); + ].map((f) => import_index.path.resolve(import_node_process2.default.cwd(), cwd, f)).find((f) => !existsNoFollow(f)); +} +function existsNoFollow(p) { + try { + import_index.fs.lstatSync(p); + return true; + } catch (err) { + if (err?.code === "ENOENT") return false; + throw err; + } } /* c8 ignore next 100 */ // Annotate the CommonJS export names for ESM import in node: @@ -369,4 +378,4 @@ function getFilepath(cwd = ".", name = "zx", _ext) { normalizeExt, printUsage, transformMarkdown -}); \ No newline at end of file +}); diff --git a/src/cli.ts b/src/cli.ts index df1ea719f0..cf3b5a2a42 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -294,5 +294,15 @@ function getFilepath(cwd = '.', name = 'zx', _ext?: string): string { name + '-' + randomId() + ext, ] .map(f => path.resolve(process.cwd(), cwd, f)) - .find(f => !fs.existsSync(f))! + .find(f => !existsNoFollow(f))! +} + +function existsNoFollow(p: string): boolean { + try { + fs.lstatSync(p) + return true + } catch (err: any) { + if (err?.code === 'ENOENT') return false + throw err + } } diff --git a/test/cli.test.js b/test/cli.test.js index 848cade891..4c29c85d33 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -367,6 +367,31 @@ console.log(a); assert.equal((await $`node build/cli.js -e='echo(69)'`).stdout, '69\n') }) + test('eval does not write through dangling symlink temp path', async (t) => { + const cwd = tmpdir() + const target = path.join(cwd, 'created-through-symlink') + const link = path.join(cwd, 'zx.mjs') + + try { + fs.symlinkSync(target, link) + } catch { + await fs.remove(cwd) + t.skip('symlink creation is unavailable') + return + } + + try { + const out = + await $`node build/cli.js --cwd=${cwd} --eval 'console.log("ok")'` + + assert.equal(out.stdout, 'ok\n') + assert.equal(fs.existsSync(target), false) + assert.equal(fs.lstatSync(link).isSymbolicLink(), true) + } finally { + await fs.remove(cwd) + } + }) + test('eval works with stdin', async () => { const p = $`(printf foo; sleep 0.1; printf bar) | node build/cli.js --eval 'echo(await stdin())'` assert.equal((await p).stdout, 'foobar\n')