diff --git a/lib/ensure/__tests__/symlink.test.js b/lib/ensure/__tests__/symlink.test.js index cec7e9dd..e0850065 100644 --- a/lib/ensure/__tests__/symlink.test.js +++ b/lib/ensure/__tests__/symlink.test.js @@ -399,4 +399,27 @@ describe('fse-ensure-symlink', () => { assert.strictEqual(content, 'content') }) }) + + // https://github.com/jprichardson/node-fs-extra/issues/925 + describe('ensureSymlink() when dest is an existing broken symlink (issue #925)', () => { + it('should fail with EEXIST (not ENOENT) when dest is a broken symlink', async () => { + const srcpath = path.join(TEST_DIR, 'src-925.txt') + const dstpath = path.join(TEST_DIR, 'broken-link-925') + fs.writeFileSync(srcpath, 'src\n') + fs.symlinkSync(path.join(TEST_DIR, 'missing-925.txt'), dstpath) + assert.strictEqual(fs.lstatSync(dstpath).isSymbolicLink(), true) + + await assert.rejects(ensureSymlink(srcpath, dstpath), { code: 'EEXIST' }) + }) + + it('should fail with EEXIST (not ENOENT) when dest is a broken symlink (sync)', () => { + const srcpath = path.join(TEST_DIR, 'src-925-sync.txt') + const dstpath = path.join(TEST_DIR, 'broken-link-925-sync') + fs.writeFileSync(srcpath, 'src\n') + fs.symlinkSync(path.join(TEST_DIR, 'missing-925-sync.txt'), dstpath) + assert.strictEqual(fs.lstatSync(dstpath).isSymbolicLink(), true) + + assert.throws(() => ensureSymlinkSync(srcpath, dstpath), { code: 'EEXIST' }) + }) + }) }) diff --git a/lib/ensure/symlink.js b/lib/ensure/symlink.js index a7de8cad..f283672e 100644 --- a/lib/ensure/symlink.js +++ b/lib/ensure/symlink.js @@ -35,8 +35,13 @@ async function createSymlink (srcpath, dstpath, type) { } } - const dstStat = await fs.stat(dstpath, { bigint: true }) - if (areIdentical(srcStat, dstStat)) return + // Following the existing symlink fails if it's broken; in that case fall + // through so fs.symlink reports EEXIST, rather than leaking the stat ENOENT. + let dstStat + try { + dstStat = await fs.stat(dstpath, { bigint: true }) + } catch { } + if (dstStat && areIdentical(srcStat, dstStat)) return } const relative = await symlinkPaths(srcpath, dstpath) @@ -72,8 +77,13 @@ function createSymlinkSync (srcpath, dstpath, type) { } } - const dstStat = fs.statSync(dstpath, { bigint: true }) - if (areIdentical(srcStat, dstStat)) return + // Following the existing symlink fails if it's broken; in that case fall + // through so fs.symlinkSync reports EEXIST, rather than leaking the stat ENOENT. + let dstStat + try { + dstStat = fs.statSync(dstpath, { bigint: true }) + } catch { } + if (dstStat && areIdentical(srcStat, dstStat)) return } const relative = symlinkPathsSync(srcpath, dstpath)