Repository: denoland/deno
Pull request: fix(ext/node): fs.realpath buffer encoding

Towards #29972

Allows https://github.com/nodejs/node/blob/v24.2.0/test/parallel/test-fs-realpath-buffer-encoding.js test to pass.

Changed files:
filename: ext/node/polyfills/_fs/_fs_realpath.ts
patch: @@ -2,39 +2,71 @@
 
 import { promisify } from "ext:deno_node/internal/util.mjs";
 import { primordials } from "ext:core/mod.js";
+import { Buffer } from "node:buffer";
+import {
+  getOptions,
+  getValidatedPathToString,
+} from "ext:deno_node/internal/fs/utils.mjs";
+import { validateFunction } from "ext:deno_node/internal/validators.mjs";
+import { BufferEncoding } from "ext:deno_node/_global.d.ts";
 
-type Options = { encoding: string };
-type Callback = (err: Error | null, path?: string) => void;
+type Encoding = BufferEncoding | "buffer";
+type EncodingObj = { encoding?: Encoding };
+type Options = Encoding | EncodingObj;
+type Callback = (err: Error | null, path?: string | Buffer) => void;
 
-const { PromisePrototypeThen, Error } = primordials;
+const { PromisePrototypeThen } = primordials;
+
+function encodeRealpathResult(
+  result: string,
+  options?: EncodingObj,
+): string | Buffer {
+  if (!options || !options.encoding || options.encoding === "utf8") {
+    return result;
+  }
+
+  const asBuffer = Buffer.from(result);
+  if (options.encoding === "buffer") {
+    return asBuffer;
+  }
+  // deno-lint-ignore prefer-primordials
+  return asBuffer.toString(options.encoding);
+}
 
 export function realpath(
-  path: string,
-  options?: Options | Callback,
+  path: string | Buffer,
+  options?: Options | Callback | Encoding,
   callback?: Callback,
 ) {
   if (typeof options === "function") {
     callback = options;
   }
-  if (!callback) {
-    throw new Error("No callback function supplied");
-  }
+  validateFunction(callback, "cb");
+  options = getOptions(options) as EncodingObj;
+  path = getValidatedPathToString(path);
+
   PromisePrototypeThen(
     Deno.realPath(path),
-    (path) => callback!(null, path),
+    (path) => callback!(null, encodeRealpathResult(path, options)),
     (err) => callback!(err),
   );
 }
 
 realpath.native = realpath;
 
 export const realpathPromise = promisify(realpath) as (
-  path: string,
+  path: string | Buffer,
   options?: Options,
-) => Promise<string>;
+) => Promise<string | Buffer>;
 
-export function realpathSync(path: string): string {
-  return Deno.realPathSync(path);
+export function realpathSync(
+  path: string,
+  options?: Options | Encoding,
+): string | Buffer {
+  options = getOptions(options) as EncodingObj;
+  path = getValidatedPathToString(path);
+  const realPath = Deno.realPathSync(path);
+  return encodeRealpathResult(realPath, options);
 }
 
 realpathSync.native = realpathSync;

filename: ext/node/polyfills/internal/fs/utils.mjs
patch: @@ -56,7 +56,7 @@ import {
   isDate,
   isUint8Array,
 } from "ext:deno_node/internal/util/types.ts";
-import { once } from "ext:deno_node/internal/util.mjs";
+import { kEmptyObject, once } from "ext:deno_node/internal/util.mjs";
 import { toPathIfFileURL } from "ext:deno_node/internal/url.ts";
 import {
   validateAbortSignal,
@@ -343,7 +343,13 @@ export function getDirent(path, name, type, callback) {
   }
 }
 
-export function getOptions(options, defaultOptions) {
+/**
+ * @template T
+ * @param {unknown} options
+ * @param {T} [defaultOptions]
+ * @returns {T}
+ */
+export function getOptions(options, defaultOptions = kEmptyObject) {
   if (
     options === null || options === undefined ||
     typeof options === "function"

filename: tests/node_compat/config.toml
patch: @@ -478,6 +478,7 @@
 "parallel/test-fs-readlink-type-check.js" = {}
 "parallel/test-fs-readv-promisify.js" = {}
 "parallel/test-fs-ready-event-stream.js" = {}
+"parallel/test-fs-realpath-buffer-encoding.js" = {}
 "parallel/test-fs-rename-type-check.js" = {}
 "parallel/test-fs-rmdir-type-check.js" = {}
 "parallel/test-fs-statfs.js" = {}