From 7d156ccb5969ce4b899c7b10223df9b96c6d7b01 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Tue, 22 Sep 2026 18:35:45 -0700 Subject: [PATCH 1/2] vfs: normalize RealFSProvider mkdir return paths RealFSProvider.mkdirSync()/mkdir() forwarded the raw fs.mkdirSync() result, which for { recursive: true } is the absolute backing-directory path of the first created directory. VirtualFileSystem#toMountedPath() then joined the mount point with it, producing //a instead of /a, so the returned path failed fs.existsSync(). Reuse the existing #resolvedToVfsPath() helper (as realpath does) to translate the result back to a provider-relative VFS path, matching MemoryProvider. Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com> Assisted-by: opencode --- lib/internal/vfs/providers/real.js | 8 +++-- .../test-vfs-real-provider-promises.js | 13 ++++++++ test/parallel/test-vfs-real-provider.js | 31 +++++++++++++++++++ 3 files changed, 50 insertions(+), 2 deletions(-) diff --git a/lib/internal/vfs/providers/real.js b/lib/internal/vfs/providers/real.js index 2f25f582244c..5624d1d92371 100644 --- a/lib/internal/vfs/providers/real.js +++ b/lib/internal/vfs/providers/real.js @@ -458,12 +458,16 @@ class RealFSProvider extends VirtualProvider { mkdirSync(vfsPath, options) { const realPath = this.#resolvePath(vfsPath); - return fs.mkdirSync(realPath, options); + const created = fs.mkdirSync(realPath, options); + if (created === undefined) return undefined; + return this.#resolvedToVfsPath(created, vfsPath, 'mkdir'); } async mkdir(vfsPath, options) { const realPath = this.#resolvePath(vfsPath); - return fs.promises.mkdir(realPath, options); + const created = await fs.promises.mkdir(realPath, options); + if (created === undefined) return undefined; + return this.#resolvedToVfsPath(created, vfsPath, 'mkdir'); } rmdirSync(vfsPath) { diff --git a/test/parallel/test-vfs-real-provider-promises.js b/test/parallel/test-vfs-real-provider-promises.js index 932a30fd086d..a7e8d3f7d6d1 100644 --- a/test/parallel/test-vfs-real-provider-promises.js +++ b/test/parallel/test-vfs-real-provider-promises.js @@ -35,6 +35,19 @@ const myVfs = vfs.create(new vfs.RealFSProvider(root)); assert.deepStrictEqual(entries.sort(), ['sub']); await myVfs.promises.rmdir('/d/sub'); + // Recursive mkdir returns the first directory created, as a VFS-relative + // path (never the backing-directory path), matching MemoryProvider. + const createdSub = await myVfs.promises.mkdir('/d/sub', { recursive: true }); + assert.strictEqual(createdSub, '/d/sub'); + assert.strictEqual(await myVfs.promises.mkdir('/d/sub', { recursive: true }), + undefined); + const created = await myVfs.promises.mkdir('/d/deep/nested', { recursive: true }); + assert.strictEqual(created, '/d/deep'); + assert.strictEqual(myVfs.existsSync(created), true); + await myVfs.promises.rmdir('/d/sub'); + await myVfs.promises.rmdir('/d/deep/nested'); + await myVfs.promises.rmdir('/d/deep'); + // rename await myVfs.promises.writeFile('/old.txt', 'x'); await myVfs.promises.rename('/old.txt', '/new.txt'); diff --git a/test/parallel/test-vfs-real-provider.js b/test/parallel/test-vfs-real-provider.js index a54181d8f6dc..dd37194d4ae6 100644 --- a/test/parallel/test-vfs-real-provider.js +++ b/test/parallel/test-vfs-real-provider.js @@ -106,6 +106,37 @@ fs.mkdirSync(testDir, { recursive: true }); fs.rmdirSync(path.join(testDir, 'deep/nested/dir')); fs.rmdirSync(path.join(testDir, 'deep/nested')); fs.rmdirSync(path.join(testDir, 'deep')); + + // Recursive mkdir returns the first directory created, as a VFS-relative + // path (never the backing-directory path), matching MemoryProvider. + const created = realVfs.mkdirSync('/returned/nested/dir', { recursive: true }); + assert.strictEqual(created, '/returned'); + assert.strictEqual(realVfs.existsSync(created), true); + assert.strictEqual( + realVfs.mkdirSync('/returned/nested/dir', { recursive: true }), + undefined, + ); + assert.strictEqual(realVfs.mkdirSync('/returned/new-dir'), + undefined); + fs.rmdirSync(path.join(testDir, 'returned/nested/dir')); + fs.rmdirSync(path.join(testDir, 'returned/nested')); + fs.rmdirSync(path.join(testDir, 'returned/new-dir')); + fs.rmdirSync(path.join(testDir, 'returned')); +} + +// Mounted recursive mkdir returns a path that exists on the real filesystem. +{ + const realVfs = vfs.create(new vfs.RealFSProvider(testDir)); + const mountPoint = realVfs.mount(); + try { + const created = realVfs.mkdirSync(path.join(mountPoint, 'm/nested/dir'), + { recursive: true }); + assert.strictEqual(created, path.join(mountPoint, 'm')); + assert.strictEqual(fs.existsSync(created), true); + } finally { + realVfs.unmount(); + fs.rmSync(path.join(testDir, 'm'), { recursive: true, force: true }); + } } // unlink From 3b52939a2c1de2d47d6fbedba5e7740e0727c0f9 Mon Sep 17 00:00:00 2001 From: "Kamat, Trivikram" <16024985+trivikr@users.noreply.github.com> Date: Sun, 27 Sep 2026 07:02:05 -0700 Subject: [PATCH 2/2] fixup! vfs: normalize RealFSProvider mkdir return paths --- lib/internal/vfs/providers/real.js | 8 +++++++- test/parallel/test-vfs-real-provider.js | 14 +++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/lib/internal/vfs/providers/real.js b/lib/internal/vfs/providers/real.js index 5624d1d92371..de18cda14941 100644 --- a/lib/internal/vfs/providers/real.js +++ b/lib/internal/vfs/providers/real.js @@ -570,7 +570,13 @@ class RealFSProvider extends VirtualProvider { // because fs.realpathSync (a JS impl) preserves case but fs.promises.realpath // (native) canonicalizes the drive letter and other components. #resolvedToVfsPath(resolved, vfsPath, syscall) { - const rel = path.relative(this.#rootPath, resolved); + // Both sides are put in the Windows long-path form first, because the + // native bindings answer in it (\\?\C:\dir) and path.relative() reads + // that as a different root than a plain rootPath, which would make every + // comparison look like an escape. A no-op elsewhere, and idempotent, so + // a rootPath already in that form is covered too. + const rel = path.relative(path.toNamespacedPath(this.#rootPath), + path.toNamespacedPath(resolved)); if (rel === '') return '/'; if (rel === '..' || StringPrototypeStartsWith(rel, '..' + path.sep) || diff --git a/test/parallel/test-vfs-real-provider.js b/test/parallel/test-vfs-real-provider.js index dd37194d4ae6..9a6077cd9d6f 100644 --- a/test/parallel/test-vfs-real-provider.js +++ b/test/parallel/test-vfs-real-provider.js @@ -7,7 +7,7 @@ // test-vfs-real-provider-handle.js, and symlinks/path-escape live in // test-vfs-real-provider-symlinks.js. -require('../common'); +const common = require('../common'); const tmpdir = require('../common/tmpdir'); const assert = require('assert'); const fs = require('fs'); @@ -139,6 +139,18 @@ fs.mkdirSync(testDir, { recursive: true }); } } +// Recursive mkdir works when the root is given in the Windows long-path +// form, which is what the native bindings hand back: the returned path must +// still be translated relative to the root, not rejected as an escape. +if (common.isWindows) { + const longVfs = vfs.create(new vfs.RealFSProvider( + path.toNamespacedPath(testDir))); + const created = longVfs.mkdirSync('/long/deep/dir', { recursive: true }); + assert.strictEqual(created, '/long'); + assert.strictEqual(longVfs.existsSync('/long/deep/dir'), true); + fs.rmSync(path.join(testDir, 'long'), { recursive: true, force: true }); +} + // unlink { const realVfs = vfs.create(new vfs.RealFSProvider(testDir));