From 5721674276b16194f3031349a85f8c628017c6c2 Mon Sep 17 00:00:00 2001 From: Hamid Reza Ghavami Date: Thu, 9 Jul 2026 23:43:04 +0300 Subject: [PATCH 01/16] fs: return Buffer from mkdtemp when prefix is a Buffer Signed-off-by: Hamid Reza Ghavami --- lib/fs.js | 12 +++++++++--- lib/internal/fs/promises.js | 9 ++++++--- test/parallel/test-fs-mkdtemp-buffer.js | 26 +++++++++++++++++++++++++ test/parallel/test-fs-mkdtemp.js | 13 ++----------- 4 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 test/parallel/test-fs-mkdtemp-buffer.js diff --git a/lib/fs.js b/lib/fs.js index 63312ba19507..d207caaefe1c 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -3679,7 +3679,9 @@ function mkdtemp(prefix, options, callback) { if (h !== null && vfsResult(h.mkdtemp(prefix, typeof options === 'function' ? undefined : options), callback)) return; options = getOptions(options); - + if (BufferIsBuffer(prefix)) { + options = { ...options, encoding: 'buffer' }; + } prefix = getValidatedPath(prefix, 'prefix'); warnOnNonPortableTemplate(prefix); @@ -3702,7 +3704,9 @@ function mkdtempSync(prefix, options) { } options = getOptions(options); - + if (BufferIsBuffer(prefix)) { + options = { ...options, encoding: 'buffer' }; + } prefix = getValidatedPath(prefix, 'prefix'); warnOnNonPortableTemplate(prefix); return binding.mkdtemp(prefix, options.encoding); @@ -3718,7 +3722,9 @@ function mkdtempSync(prefix, options) { */ function mkdtempDisposableSync(prefix, options) { options = getOptions(options); - + if (BufferIsBuffer(prefix)) { + options = { ...options, encoding: 'buffer' }; + } prefix = getValidatedPath(prefix, 'prefix'); warnOnNonPortableTemplate(prefix); diff --git a/lib/internal/fs/promises.js b/lib/internal/fs/promises.js index ec163ccf9068..09654169cac3 100644 --- a/lib/internal/fs/promises.js +++ b/lib/internal/fs/promises.js @@ -2039,9 +2039,10 @@ async function mkdtemp(prefix, options) { const promise = h.mkdtemp(prefix, options); if (promise !== undefined) return await promise; } - options = getOptions(options); - + if (BufferIsBuffer(prefix)) { + options = { ...options, encoding: 'buffer' }; + } prefix = getValidatedPath(prefix, 'prefix'); warnOnNonPortableTemplate(prefix); @@ -2054,7 +2055,9 @@ async function mkdtemp(prefix, options) { async function mkdtempDisposable(prefix, options) { options = getOptions(options); - + if (BufferIsBuffer(prefix)) { + options = { ...options, encoding: 'buffer' }; + } prefix = getValidatedPath(prefix, 'prefix'); warnOnNonPortableTemplate(prefix); diff --git a/test/parallel/test-fs-mkdtemp-buffer.js b/test/parallel/test-fs-mkdtemp-buffer.js new file mode 100644 index 000000000000..4403db03570a --- /dev/null +++ b/test/parallel/test-fs-mkdtemp-buffer.js @@ -0,0 +1,26 @@ +'use strict'; +const common = require('../common'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +const tmpdir = require('../common/tmpdir'); +tmpdir.refresh(); + +const prefixString = path.join(tmpdir.path, 'buffer-'); +const prefixBuffer = Buffer.from(prefixString); + +// 1. Test Sync API +const resultSync = fs.mkdtempSync(prefixBuffer); +assert.strictEqual(Buffer.isBuffer(resultSync), true); + +// 2. Test Callback API +fs.mkdtemp(prefixBuffer, common.mustSucceed((resultCb) => { + assert.strictEqual(Buffer.isBuffer(resultCb), true); +})); + +// 3. Test Promises API +fs.promises.mkdtemp(prefixBuffer) + .then(common.mustCall((resultPromise) => { + assert.strictEqual(Buffer.isBuffer(resultPromise), true); + })); diff --git a/test/parallel/test-fs-mkdtemp.js b/test/parallel/test-fs-mkdtemp.js index e93809d5b445..a7d678d21ee0 100644 --- a/test/parallel/test-fs-mkdtemp.js +++ b/test/parallel/test-fs-mkdtemp.js @@ -64,22 +64,13 @@ function handler(err, folder) { { const tmpFolder = fs.mkdtempSync(Buffer.from(tmpdir.resolve('foo.'))); - assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length); + assert.strictEqual(path.basename(tmpFolder.toString()).length, 'foo.XXXXXX'.length); assert(fs.existsSync(tmpFolder)); const utf8 = fs.mkdtempSync(Buffer.from(tmpdir.resolve('\u0222abc.'))); - assert.strictEqual(Buffer.byteLength(path.basename(utf8)), + assert.strictEqual(Buffer.byteLength(path.basename(utf8.toString())), Buffer.byteLength('\u0222abc.XXXXXX')); assert(fs.existsSync(utf8)); - - fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.')), common.mustCall(handler)); - - // Same test as above, but making sure that passing an options object doesn't - // affect the way the callback function is handled. - fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.')), {}, common.mustCall(handler)); - - // Warning fires only once - fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.X')), common.mustCall(handler)); } // Test with Uint8Array From 4b0381965609515e3a0511dbd6274b345e9a08cf Mon Sep 17 00:00:00 2001 From: Hamid Reza Ghavami Date: Sat, 11 Jul 2026 22:10:38 +0300 Subject: [PATCH 02/16] test: restore missing mkdtemp async buffer tests --- test/parallel/test-fs-mkdtemp.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/parallel/test-fs-mkdtemp.js b/test/parallel/test-fs-mkdtemp.js index a7d678d21ee0..3c2323440e08 100644 --- a/test/parallel/test-fs-mkdtemp.js +++ b/test/parallel/test-fs-mkdtemp.js @@ -71,6 +71,14 @@ function handler(err, folder) { assert.strictEqual(Buffer.byteLength(path.basename(utf8.toString())), Buffer.byteLength('\u0222abc.XXXXXX')); assert(fs.existsSync(utf8)); + fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.')), common.mustCall(handler)); + + // Same test as above, but making sure that passing an options object doesn't + // affect the way the callback function is handled. + fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.')), {}, common.mustCall(handler)); + + // Warning fires only once + fs.mkdtemp(Buffer.from(tmpdir.resolve('bar.X')), common.mustCall(handler)); } // Test with Uint8Array From 5f6fca585d81339756cd220cfaf16d0939cd52f8 Mon Sep 17 00:00:00 2001 From: Hamid Reza Ghavami Date: Sun, 12 Jul 2026 10:27:52 +0300 Subject: [PATCH 03/16] doc: update fs.mkdtemp() return types and history --- doc/api/fs.md | 255 +++++++++----------------------------------------- 1 file changed, 45 insertions(+), 210 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 9be67ded1f50..990a989a5346 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -196,7 +196,7 @@ changes: strings anymore. --> -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `signal` {AbortSignal|undefined} allows aborting an in-progress writeFile. **Default:** `undefined` @@ -323,9 +323,6 @@ fd.createReadStream({ start: 90, end: 99 }); > Stability: 1 - Experimental @@ -405,7 +400,7 @@ added: reached, whichever comes first. **Default:** read until EOF. * `chunkSize` {number} Size in bytes of the buffer allocated for each read operation. **Default:** `131072` (128 KB). -* Returns: {AsyncIterable} whose chunks fulfill with {Uint8Array\[]} +* Returns: {AsyncIterable\} Return the file contents as an async iterable using the [`node:stream/iter`][] pull model. Reads are performed in `chunkSize`-byte @@ -465,7 +460,6 @@ run().catch(console.error); > Stability: 1 - Experimental @@ -481,7 +475,7 @@ added: iterator. **Default:** read until EOF. * `chunkSize` {number} Size in bytes of the buffer allocated for each read operation. **Default:** `131072` (128 KB). -* Returns: {Iterable} whose chunks return {Uint8Array\[]} +* Returns: {Iterable\} Synchronous counterpart of [`filehandle.pull()`][]. Returns a sync iterable that reads the file using synchronous I/O on the main thread. Reads are @@ -703,9 +697,7 @@ close the `FileHandle` automatically. User code must still call the @@ -1015,7 +1007,7 @@ changes: strings anymore. --> -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} * `options` {Object|string} * `encoding` {string|null} The expected character encoding when `data` is a string. **Default:** `'utf8'` @@ -1070,7 +1062,6 @@ the end of the file. > Stability: 1 - Experimental @@ -1194,15 +1185,9 @@ changes: description: No longer experimental. --> -* Returns: {Promise} - Calls `filehandle.close()` and returns a promise that fulfills when the filehandle is closed. -This method enables the filehandle to be used with [`await using`][], which -will automatically close the file when the scope exits. For more information, -see the [MDN documentation on `using` statements][`using`]. - ### `fsPromises.access(path[, mode])` * `path` {string|Buffer|URL|FileHandle} filename or {FileHandle} -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} +* `data` {string|Buffer} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `mode` {integer} **Default:** `0o666` @@ -1271,7 +1251,7 @@ changes: * Returns: {Promise} Fulfills with `undefined` upon success. Asynchronously append data to a file, creating the file if it does not yet -`data` can be a string, a buffer, an {AsyncIterable}, or an {Iterable} object. +exist. `data` can be a string or a {Buffer}. If `options` is a string, then it specifies the `encoding`. @@ -1337,10 +1317,6 @@ changes: Asynchronously copies `src` to `dest`. By default, `dest` is overwritten if it already exists. -Symbolic links are followed. If `src` is a symbolic link, the target file is -copied. If `dest` is a symbolic link, the target file is overwritten unless -`mode` contains `fs.constants.COPYFILE_EXCL`. - No guarantees are made about the atomicity of the copy operation. If an error occurs after the destination file has been opened for writing, an attempt will be made to remove the destination. @@ -1424,9 +1400,6 @@ behavior is similar to `cp dir1/ dir2/`. @@ -1559,10 +1529,6 @@ link(2) documentation for more detail. For detailed information, see the documentation of [`fsPromises.mkdtemp()`][]. @@ -1849,9 +1809,7 @@ try { * `file` {string|Buffer|URL|FileHandle} filename or `FileHandle` -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `mode` {integer} **Default:** `0o666` @@ -2879,10 +2831,6 @@ callback function. Node.js makes no guarantees about the atomicity of the copy operation. If an error occurs after the destination file has been opened for writing, Node.js will attempt to remove the destination. -Symbolic links are followed. If `src` is a symbolic link, the target file is -copied. If `dest` is a symbolic link, the target file is overwritten unless -`mode` contains `fs.constants.COPYFILE_EXCL`. - `mode` is an optional integer that specifies the behavior of the copy operation. It is possible to create a mask consisting of the bitwise OR of two or more values (e.g. @@ -2978,9 +2926,6 @@ behavior is similar to `cp dir1/ dir2/`. > Stability: 0 - Deprecated @@ -6505,11 +6397,9 @@ changes: * `prefix` {string|Buffer|URL} * `options` {string|Object} * `encoding` {string} **Default:** `'utf8'` -* Returns: {string|Buffer} +* Returns: {string} -Returns the created directory path. If `encoding` is `'buffer'`, then the -resulting directory path is returned as a {Buffer}. Otherwise, the path -is returned as a {string} using the specified encoding. +Returns the created directory path. For detailed information, see the documentation of the asynchronous version of this API: [`fs.mkdtemp()`][]. @@ -6527,23 +6417,22 @@ added: v24.4.0 * `options` {string|Object} * `encoding` {string} **Default:** `'utf8'` * Returns: {Object} A disposable object: - * `path` {string|Buffer} The path of the created directory. + * `path` {string} The path of the created directory. * `remove` {Function} A function which removes the created directory. * `[Symbol.dispose]` {Function} The same as `remove`. Returns a disposable object whose `path` property holds the created directory -path. If `encoding` is `'buffer'`, the `path` will be a {Buffer}. When the -object is disposed, the directory and its contents will be removed if it still -exists. If the directory cannot be deleted, disposal will throw an error. The -object has a `remove()` method which will perform the same task. +path. When the object is disposed, the directory and its contents will be +removed if it still exists. If the directory cannot be deleted, disposal will +throw an error. The object has a `remove()` method which will perform the same +task. -See the [MDN documentation on `using` statements][`using`] for more information about -explicit resource management. + For detailed information, see the documentation of [`fs.mkdtemp()`][]. There is no callback-based version of this API because it is designed for use -with the [`using`][] syntax. +with the `using` syntax. The optional `options` argument can be a string specifying an encoding, or an object with an `encoding` property specifying the character encoding to use. @@ -6655,9 +6544,7 @@ If `options.withFileTypes` is set to `true`, the result will contain -* Returns: {Promise} - Calls `dir.close()` if the directory handle is open, and returns a promise that fulfills when disposal is complete. -This method enables the directory to be used with [`await using`][], which -will automatically close the directory when the scope exits. For more -information, see the [MDN documentation on `using` statements][`using`]. - #### `dir[Symbol.dispose]()` From ca78ff40ab692e06a8b7f9bc579a0b9a175b5dfa Mon Sep 17 00:00:00 2001 From: Hamid Reza Ghavami Date: Thu, 23 Jul 2026 08:57:54 +0300 Subject: [PATCH 08/16] doc: wrap lines in fs.md --- doc/api/fs.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 40c62fcb5d67..55e40bb71cac 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -1667,7 +1667,11 @@ added: v24.4.0 * `[Symbol.asyncDispose]` {AsyncFunction} The same as `remove`. The resulting Promise holds an async-disposable object whose `path` property -holds the created directory path. If `prefix` is a `Buffer`, the `path` will also be a `Buffer`. When the object is disposed, the directory and its contents will be removed asynchronously if it still exists. If the directory cannot be deleted, disposal will throw an error. The object has an async `remove()` method which will perform the same task. +holds the created directory path. If `prefix` is a `Buffer`, the `path` will +also be a `Buffer`. When the object is disposed, the directory and its contents +will be removed asynchronously if it still exists. If the directory cannot be +deleted, disposal will throw an error. The object has an async `remove()` +method which will perform the same task. Both this function and the disposal function on the resulting object are async, so it should be used with `await` + `await using` as in @@ -6431,7 +6435,10 @@ added: v24.4.0 * `[Symbol.dispose]` {Function} The same as `remove`. Returns a disposable object whose `path` property holds the created directory -path. If `prefix` is a `Buffer`, the `path` will also be a `Buffer`. When the object is disposed, the directory and its contents will be removed if it still exists. If the directory cannot be deleted, disposal will throw an error. The object has a `remove()` method which will perform the same task. +path. If `prefix` is a `Buffer`, the `path` will also be a `Buffer`. When the +object is disposed, the directory and its contents will be removed if it still +exists. If the directory cannot be deleted, disposal will throw an error. The +object has a `remove()` method which will perform the same task. From a2717082e0e9f7e1662463a4c71bc2ea4c70b027 Mon Sep 17 00:00:00 2001 From: Hamid Reza Ghavami Date: Thu, 23 Jul 2026 15:11:09 +0300 Subject: [PATCH 09/16] doc: add changes blocks to mkdtempDisposable functions --- doc/api/fs.md | 260 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 206 insertions(+), 54 deletions(-) diff --git a/doc/api/fs.md b/doc/api/fs.md index 55e40bb71cac..9be67ded1f50 100644 --- a/doc/api/fs.md +++ b/doc/api/fs.md @@ -196,7 +196,7 @@ changes: strings anymore. --> -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `signal` {AbortSignal|undefined} allows aborting an in-progress writeFile. **Default:** `undefined` @@ -323,6 +323,9 @@ fd.createReadStream({ start: 90, end: 99 }); > Stability: 1 - Experimental @@ -400,7 +405,7 @@ added: reached, whichever comes first. **Default:** read until EOF. * `chunkSize` {number} Size in bytes of the buffer allocated for each read operation. **Default:** `131072` (128 KB). -* Returns: {AsyncIterable\} +* Returns: {AsyncIterable} whose chunks fulfill with {Uint8Array\[]} Return the file contents as an async iterable using the [`node:stream/iter`][] pull model. Reads are performed in `chunkSize`-byte @@ -460,6 +465,7 @@ run().catch(console.error); > Stability: 1 - Experimental @@ -475,7 +481,7 @@ added: iterator. **Default:** read until EOF. * `chunkSize` {number} Size in bytes of the buffer allocated for each read operation. **Default:** `131072` (128 KB). -* Returns: {Iterable\} +* Returns: {Iterable} whose chunks return {Uint8Array\[]} Synchronous counterpart of [`filehandle.pull()`][]. Returns a sync iterable that reads the file using synchronous I/O on the main thread. Reads are @@ -697,7 +703,9 @@ close the `FileHandle` automatically. User code must still call the @@ -1007,7 +1015,7 @@ changes: strings anymore. --> -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} The expected character encoding when `data` is a string. **Default:** `'utf8'` @@ -1062,6 +1070,7 @@ the end of the file. > Stability: 1 - Experimental @@ -1185,9 +1194,15 @@ changes: description: No longer experimental. --> +* Returns: {Promise} + Calls `filehandle.close()` and returns a promise that fulfills when the filehandle is closed. +This method enables the filehandle to be used with [`await using`][], which +will automatically close the file when the scope exits. For more information, +see the [MDN documentation on `using` statements][`using`]. + ### `fsPromises.access(path[, mode])` * `path` {string|Buffer|URL|FileHandle} filename or {FileHandle} -* `data` {string|Buffer} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `mode` {integer} **Default:** `0o666` @@ -1251,7 +1271,7 @@ changes: * Returns: {Promise} Fulfills with `undefined` upon success. Asynchronously append data to a file, creating the file if it does not yet -exist. `data` can be a string or a {Buffer}. +`data` can be a string, a buffer, an {AsyncIterable}, or an {Iterable} object. If `options` is a string, then it specifies the `encoding`. @@ -1317,6 +1337,10 @@ changes: Asynchronously copies `src` to `dest`. By default, `dest` is overwritten if it already exists. +Symbolic links are followed. If `src` is a symbolic link, the target file is +copied. If `dest` is a symbolic link, the target file is overwritten unless +`mode` contains `fs.constants.COPYFILE_EXCL`. + No guarantees are made about the atomicity of the copy operation. If an error occurs after the destination file has been opened for writing, an attempt will be made to remove the destination. @@ -1400,6 +1424,9 @@ behavior is similar to `cp dir1/ dir2/`. @@ -1529,6 +1559,10 @@ link(2) documentation for more detail. +See the [MDN documentation on `using` statements][`using`] for more information about +explicit resource management. For detailed information, see the documentation of [`fsPromises.mkdtemp()`][]. @@ -1816,7 +1849,9 @@ try { * `file` {string|Buffer|URL|FileHandle} filename or `FileHandle` -* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable|Stream} +* `data` {string|Buffer|TypedArray|DataView|AsyncIterable|Iterable} * `options` {Object|string} * `encoding` {string|null} **Default:** `'utf8'` * `mode` {integer} **Default:** `0o666` @@ -2838,6 +2879,10 @@ callback function. Node.js makes no guarantees about the atomicity of the copy operation. If an error occurs after the destination file has been opened for writing, Node.js will attempt to remove the destination. +Symbolic links are followed. If `src` is a symbolic link, the target file is +copied. If `dest` is a symbolic link, the target file is overwritten unless +`mode` contains `fs.constants.COPYFILE_EXCL`. + `mode` is an optional integer that specifies the behavior of the copy operation. It is possible to create a mask consisting of the bitwise OR of two or more values (e.g. @@ -2933,6 +2978,9 @@ behavior is similar to `cp dir1/ dir2/`. > Stability: 0 - Deprecated @@ -6408,11 +6505,11 @@ changes: * `prefix` {string|Buffer|URL} * `options` {string|Object} * `encoding` {string} **Default:** `'utf8'` -* Returns: {string} +* Returns: {string|Buffer} -Returns the created directory path. If `prefix` is a `Buffer`, then the -resulting directory path is returned as a `Buffer`. Otherwise, the path -is returned as a string using the specified encoding. +Returns the created directory path. If `encoding` is `'buffer'`, then the +resulting directory path is returned as a {Buffer}. Otherwise, the path +is returned as a {string} using the specified encoding. For detailed information, see the documentation of the asynchronous version of this API: [`fs.mkdtemp()`][]. @@ -6435,17 +6532,18 @@ added: v24.4.0 * `[Symbol.dispose]` {Function} The same as `remove`. Returns a disposable object whose `path` property holds the created directory -path. If `prefix` is a `Buffer`, the `path` will also be a `Buffer`. When the +path. If `encoding` is `'buffer'`, the `path` will be a {Buffer}. When the object is disposed, the directory and its contents will be removed if it still exists. If the directory cannot be deleted, disposal will throw an error. The object has a `remove()` method which will perform the same task. - +See the [MDN documentation on `using` statements][`using`] for more information about +explicit resource management. For detailed information, see the documentation of [`fs.mkdtemp()`][]. There is no callback-based version of this API because it is designed for use -with the `using` syntax. +with the [`using`][] syntax. The optional `options` argument can be a string specifying an encoding, or an object with an `encoding` property specifying the character encoding to use. @@ -6557,7 +6655,9 @@ If `options.withFileTypes` is set to `true`, the result will contain +* Returns: {Promise} + Calls `dir.close()` if the directory handle is open, and returns a promise that fulfills when disposal is complete. +This method enables the directory to be used with [`await using`][], which +will automatically close the directory when the scope exits. For more +information, see the [MDN documentation on `using` statements][`using`]. + #### `dir[Symbol.dispose]()`