From 892c3d75b76dcdad5b27fdf88b9245d5ccb4a509 Mon Sep 17 00:00:00 2001 From: Abhishek Chauhan Date: Wed, 30 Sep 2026 10:28:50 -0400 Subject: [PATCH] Check srcpath with lstat in ensureSymlinkSync, like ensureSymlink symlinkPathsSync checked srcpath with fs.existsSync, which follows symlinks, while the async symlinkPaths uses fs.lstat. So ensureSymlinkSync rejected a srcpath that is itself a broken symlink, which ensureSymlink accepts, and it threw a plain "absolute srcpath does not exist" error without a code where ensureSymlink rejects with ENOENT. Use lstatSync with the same error handling as the async version. --- lib/ensure/__tests__/symlink.test.js | 36 ++++++++++++++++++++++++++++ lib/ensure/symlink-paths.js | 16 +++++++++---- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/lib/ensure/__tests__/symlink.test.js b/lib/ensure/__tests__/symlink.test.js index 92222738..72ca417c 100644 --- a/lib/ensure/__tests__/symlink.test.js +++ b/lib/ensure/__tests__/symlink.test.js @@ -472,4 +472,40 @@ describe('fse-ensure-symlink', () => { assert.throws(() => createSymlinkSync(srcpath, dstpath), { code: 'EACCES' }) }) }) + + describe('ensureSymlinkSync() when src is a broken symlink', () => { + it('should create the symlink like ensureSymlink() does', async () => { + const brokenpath = path.join(TEST_DIR, 'broken-src') + fs.symlinkSync(path.join(TEST_DIR, 'missing-target'), brokenpath) + + await ensureSymlink(brokenpath, path.join(TEST_DIR, 'link-async')) + ensureSymlinkSync(brokenpath, path.join(TEST_DIR, 'link-sync')) + + assert.strictEqual(fs.readlinkSync(path.join(TEST_DIR, 'link-async')), brokenpath) + assert.strictEqual(fs.readlinkSync(path.join(TEST_DIR, 'link-sync')), brokenpath) + }) + + it('should create the symlink like ensureSymlink() does with a relative src', async () => { + // `broken-rel-src` does not exist relative to `dir-foo`, so it is resolved from the cwd + fs.symlinkSync('missing-target', 'broken-rel-src') + + await ensureSymlink('broken-rel-src', 'dir-foo/link-rel-async') + ensureSymlinkSync('broken-rel-src', 'dir-foo/link-rel-sync') + + assert.strictEqual(fs.readlinkSync('dir-foo/link-rel-async'), path.join('..', 'broken-rel-src')) + assert.strictEqual(fs.readlinkSync('dir-foo/link-rel-sync'), path.join('..', 'broken-rel-src')) + }) + }) + + describe('ensureSymlinkSync() when src does not exist', () => { + it('should throw ENOENT like ensureSymlink() does', async () => { + const srcpath = path.join(TEST_DIR, 'missing-src') + const relpath = 'missing-rel-src' + + await assert.rejects(ensureSymlink(srcpath, path.join(TEST_DIR, 'link-abs')), { code: 'ENOENT' }) + assert.throws(() => ensureSymlinkSync(srcpath, path.join(TEST_DIR, 'link-abs-sync')), { code: 'ENOENT' }) + await assert.rejects(ensureSymlink(relpath, path.join(TEST_DIR, 'link-rel')), { code: 'ENOENT' }) + assert.throws(() => ensureSymlinkSync(relpath, path.join(TEST_DIR, 'link-rel-sync')), { code: 'ENOENT' }) + }) + }) }) diff --git a/lib/ensure/symlink-paths.js b/lib/ensure/symlink-paths.js index 85dda475..3f7765ce 100644 --- a/lib/ensure/symlink-paths.js +++ b/lib/ensure/symlink-paths.js @@ -69,8 +69,12 @@ async function symlinkPaths (srcpath, dstpath) { function symlinkPathsSync (srcpath, dstpath) { if (path.isAbsolute(srcpath)) { - const exists = fs.existsSync(srcpath) - if (!exists) throw new Error('absolute srcpath does not exist') + try { + fs.lstatSync(srcpath) + } catch (err) { + err.message = err.message.replace('lstat', 'ensureSymlink') + throw err + } return { toCwd: srcpath, toDst: srcpath @@ -87,8 +91,12 @@ function symlinkPathsSync (srcpath, dstpath) { } } - const srcExists = fs.existsSync(srcpath) - if (!srcExists) throw new Error('relative srcpath does not exist') + try { + fs.lstatSync(srcpath) + } catch (err) { + err.message = err.message.replace('lstat', 'ensureSymlink') + throw err + } return { toCwd: srcpath, toDst: path.relative(dstdir, srcpath)