summaryrefslogtreecommitdiff
path: root/ext/node/polyfills/_fs
diff options
context:
space:
mode:
Diffstat (limited to 'ext/node/polyfills/_fs')
-rw-r--r--ext/node/polyfills/_fs/_fs_access.ts127
-rw-r--r--ext/node/polyfills/_fs/_fs_appendFile.ts73
-rw-r--r--ext/node/polyfills/_fs/_fs_chmod.ts69
-rw-r--r--ext/node/polyfills/_fs/_fs_chown.ts56
-rw-r--r--ext/node/polyfills/_fs/_fs_close.ts21
-rw-r--r--ext/node/polyfills/_fs/_fs_common.ts233
-rw-r--r--ext/node/polyfills/_fs/_fs_constants.ts39
-rw-r--r--ext/node/polyfills/_fs/_fs_copy.ts88
-rw-r--r--ext/node/polyfills/_fs/_fs_dir.ts104
-rw-r--r--ext/node/polyfills/_fs/_fs_dirent.ts46
-rw-r--r--ext/node/polyfills/_fs/_fs_exists.ts40
-rw-r--r--ext/node/polyfills/_fs/_fs_fdatasync.ts13
-rw-r--r--ext/node/polyfills/_fs/_fs_fstat.ts60
-rw-r--r--ext/node/polyfills/_fs/_fs_fsync.ts13
-rw-r--r--ext/node/polyfills/_fs/_fs_ftruncate.ts23
-rw-r--r--ext/node/polyfills/_fs/_fs_futimes.ts50
-rw-r--r--ext/node/polyfills/_fs/_fs_link.ts46
-rw-r--r--ext/node/polyfills/_fs/_fs_lstat.ts67
-rw-r--r--ext/node/polyfills/_fs/_fs_mkdir.ts77
-rw-r--r--ext/node/polyfills/_fs/_fs_mkdtemp.ts115
-rw-r--r--ext/node/polyfills/_fs/_fs_open.ts198
-rw-r--r--ext/node/polyfills/_fs/_fs_opendir.ts89
-rw-r--r--ext/node/polyfills/_fs/_fs_read.ts197
-rw-r--r--ext/node/polyfills/_fs/_fs_readFile.ts108
-rw-r--r--ext/node/polyfills/_fs/_fs_readdir.ts142
-rw-r--r--ext/node/polyfills/_fs/_fs_readlink.ts89
-rw-r--r--ext/node/polyfills/_fs/_fs_realpath.ts35
-rw-r--r--ext/node/polyfills/_fs/_fs_rename.ts28
-rw-r--r--ext/node/polyfills/_fs/_fs_rm.ts81
-rw-r--r--ext/node/polyfills/_fs/_fs_rmdir.ts108
-rw-r--r--ext/node/polyfills/_fs/_fs_stat.ts314
-rw-r--r--ext/node/polyfills/_fs/_fs_symlink.ts46
-rw-r--r--ext/node/polyfills/_fs/_fs_truncate.ts33
-rw-r--r--ext/node/polyfills/_fs/_fs_unlink.ts15
-rw-r--r--ext/node/polyfills/_fs/_fs_utimes.ts61
-rw-r--r--ext/node/polyfills/_fs/_fs_watch.ts346
-rw-r--r--ext/node/polyfills/_fs/_fs_write.d.ts207
-rw-r--r--ext/node/polyfills/_fs/_fs_write.mjs132
-rw-r--r--ext/node/polyfills/_fs/_fs_writeFile.ts193
-rw-r--r--ext/node/polyfills/_fs/_fs_writev.d.ts65
-rw-r--r--ext/node/polyfills/_fs/_fs_writev.mjs81
-rw-r--r--ext/node/polyfills/_fs/testdata/hello.txt1
42 files changed, 3929 insertions, 0 deletions
diff --git a/ext/node/polyfills/_fs/_fs_access.ts b/ext/node/polyfills/_fs/_fs_access.ts
new file mode 100644
index 000000000..9450c2f01
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_access.ts
@@ -0,0 +1,127 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import {
+ type CallbackWithError,
+ makeCallback,
+} from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { fs } from "internal:deno_node/polyfills/internal_binding/constants.ts";
+import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts";
+import {
+ getValidatedPath,
+ getValidMode,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import type { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export function access(
+ path: string | Buffer | URL,
+ mode: number | CallbackWithError,
+ callback?: CallbackWithError,
+) {
+ if (typeof mode === "function") {
+ callback = mode;
+ mode = fs.F_OK;
+ }
+
+ path = getValidatedPath(path).toString();
+ mode = getValidMode(mode, "access");
+ const cb = makeCallback(callback);
+
+ Deno.lstat(path).then((info) => {
+ if (info.mode === null) {
+ // If the file mode is unavailable, we pretend it has
+ // the permission
+ cb(null);
+ return;
+ }
+ const m = +mode || 0;
+ let fileMode = +info.mode || 0;
+ if (Deno.build.os !== "windows" && info.uid === Deno.uid()) {
+ // If the user is the owner of the file, then use the owner bits of
+ // the file permission
+ fileMode >>= 6;
+ }
+ // TODO(kt3k): Also check the case when the user belong to the group
+ // of the file
+ if ((m & fileMode) === m) {
+ // all required flags exist
+ cb(null);
+ } else {
+ // some required flags don't
+ // deno-lint-ignore no-explicit-any
+ const e: any = new Error(`EACCES: permission denied, access '${path}'`);
+ e.path = path;
+ e.syscall = "access";
+ e.errno = codeMap.get("EACCES");
+ e.code = "EACCES";
+ cb(e);
+ }
+ }, (err) => {
+ if (err instanceof Deno.errors.NotFound) {
+ // deno-lint-ignore no-explicit-any
+ const e: any = new Error(
+ `ENOENT: no such file or directory, access '${path}'`,
+ );
+ e.path = path;
+ e.syscall = "access";
+ e.errno = codeMap.get("ENOENT");
+ e.code = "ENOENT";
+ cb(e);
+ } else {
+ cb(err);
+ }
+ });
+}
+
+export const accessPromise = promisify(access) as (
+ path: string | Buffer | URL,
+ mode?: number,
+) => Promise<void>;
+
+export function accessSync(path: string | Buffer | URL, mode?: number) {
+ path = getValidatedPath(path).toString();
+ mode = getValidMode(mode, "access");
+ try {
+ const info = Deno.lstatSync(path.toString());
+ if (info.mode === null) {
+ // If the file mode is unavailable, we pretend it has
+ // the permission
+ return;
+ }
+ const m = +mode! || 0;
+ let fileMode = +info.mode! || 0;
+ if (Deno.build.os !== "windows" && info.uid === Deno.uid()) {
+ // If the user is the owner of the file, then use the owner bits of
+ // the file permission
+ fileMode >>= 6;
+ }
+ // TODO(kt3k): Also check the case when the user belong to the group
+ // of the file
+ if ((m & fileMode) === m) {
+ // all required flags exist
+ } else {
+ // some required flags don't
+ // deno-lint-ignore no-explicit-any
+ const e: any = new Error(`EACCES: permission denied, access '${path}'`);
+ e.path = path;
+ e.syscall = "access";
+ e.errno = codeMap.get("EACCES");
+ e.code = "EACCES";
+ throw e;
+ }
+ } catch (err) {
+ if (err instanceof Deno.errors.NotFound) {
+ // deno-lint-ignore no-explicit-any
+ const e: any = new Error(
+ `ENOENT: no such file or directory, access '${path}'`,
+ );
+ e.path = path;
+ e.syscall = "access";
+ e.errno = codeMap.get("ENOENT");
+ e.code = "ENOENT";
+ throw e;
+ } else {
+ throw err;
+ }
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_appendFile.ts b/ext/node/polyfills/_fs/_fs_appendFile.ts
new file mode 100644
index 000000000..d47afe81b
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_appendFile.ts
@@ -0,0 +1,73 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ CallbackWithError,
+ isFd,
+ maybeCallback,
+ WriteFileOptions,
+} from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { Encodings } from "internal:deno_node/polyfills/_utils.ts";
+import {
+ copyObject,
+ getOptions,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import {
+ writeFile,
+ writeFileSync,
+} from "internal:deno_node/polyfills/_fs/_fs_writeFile.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+/**
+ * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+export function appendFile(
+ path: string | number | URL,
+ data: string | Uint8Array,
+ options: Encodings | WriteFileOptions | CallbackWithError,
+ callback?: CallbackWithError,
+) {
+ callback = maybeCallback(callback || options);
+ options = getOptions(options, { encoding: "utf8", mode: 0o666, flag: "a" });
+
+ // Don't make changes directly on options object
+ options = copyObject(options);
+
+ // Force append behavior when using a supplied file descriptor
+ if (!options.flag || isFd(path)) {
+ options.flag = "a";
+ }
+
+ writeFile(path, data, options, callback);
+}
+
+/**
+ * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+export const appendFilePromise = promisify(appendFile) as (
+ path: string | number | URL,
+ data: string | Uint8Array,
+ options?: Encodings | WriteFileOptions,
+) => Promise<void>;
+
+/**
+ * TODO: Also accept 'data' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+export function appendFileSync(
+ path: string | number | URL,
+ data: string | Uint8Array,
+ options?: Encodings | WriteFileOptions,
+) {
+ options = getOptions(options, { encoding: "utf8", mode: 0o666, flag: "a" });
+
+ // Don't make changes directly on options object
+ options = copyObject(options);
+
+ // Force append behavior when using a supplied file descriptor
+ if (!options.flag || isFd(path)) {
+ options.flag = "a";
+ }
+
+ writeFileSync(path, data, options);
+}
diff --git a/ext/node/polyfills/_fs/_fs_chmod.ts b/ext/node/polyfills/_fs/_fs_chmod.ts
new file mode 100644
index 000000000..3a19e5622
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_chmod.ts
@@ -0,0 +1,69 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import * as pathModule from "internal:deno_node/polyfills/path.ts";
+import { parseFileMode } from "internal:deno_node/polyfills/internal/validators.mjs";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export function chmod(
+ path: string | Buffer | URL,
+ mode: string | number,
+ callback: CallbackWithError,
+) {
+ path = getValidatedPath(path).toString();
+
+ try {
+ mode = parseFileMode(mode, "mode");
+ } catch (error) {
+ // TODO(PolarETech): Errors should not be ignored when Deno.chmod is supported on Windows.
+ // https://github.com/denoland/deno_std/issues/2916
+ if (Deno.build.os === "windows") {
+ mode = 0; // set dummy value to avoid type checking error at Deno.chmod
+ } else {
+ throw error;
+ }
+ }
+
+ Deno.chmod(pathModule.toNamespacedPath(path), mode).catch((error) => {
+ // Ignore NotSupportedError that occurs on windows
+ // https://github.com/denoland/deno_std/issues/2995
+ if (!(error instanceof Deno.errors.NotSupported)) {
+ throw error;
+ }
+ }).then(
+ () => callback(null),
+ callback,
+ );
+}
+
+export const chmodPromise = promisify(chmod) as (
+ path: string | Buffer | URL,
+ mode: string | number,
+) => Promise<void>;
+
+export function chmodSync(path: string | URL, mode: string | number) {
+ path = getValidatedPath(path).toString();
+
+ try {
+ mode = parseFileMode(mode, "mode");
+ } catch (error) {
+ // TODO(PolarETech): Errors should not be ignored when Deno.chmodSync is supported on Windows.
+ // https://github.com/denoland/deno_std/issues/2916
+ if (Deno.build.os === "windows") {
+ mode = 0; // set dummy value to avoid type checking error at Deno.chmodSync
+ } else {
+ throw error;
+ }
+ }
+
+ try {
+ Deno.chmodSync(pathModule.toNamespacedPath(path), mode);
+ } catch (error) {
+ // Ignore NotSupportedError that occurs on windows
+ // https://github.com/denoland/deno_std/issues/2995
+ if (!(error instanceof Deno.errors.NotSupported)) {
+ throw error;
+ }
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_chown.ts b/ext/node/polyfills/_fs/_fs_chown.ts
new file mode 100644
index 000000000..55a469fba
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_chown.ts
@@ -0,0 +1,56 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ type CallbackWithError,
+ makeCallback,
+} from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import {
+ getValidatedPath,
+ kMaxUserId,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import * as pathModule from "internal:deno_node/polyfills/path.ts";
+import { validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs";
+import type { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+/**
+ * Asynchronously changes the owner and group
+ * of a file.
+ */
+export function chown(
+ path: string | Buffer | URL,
+ uid: number,
+ gid: number,
+ callback: CallbackWithError,
+) {
+ callback = makeCallback(callback);
+ path = getValidatedPath(path).toString();
+ validateInteger(uid, "uid", -1, kMaxUserId);
+ validateInteger(gid, "gid", -1, kMaxUserId);
+
+ Deno.chown(pathModule.toNamespacedPath(path), uid, gid).then(
+ () => callback(null),
+ callback,
+ );
+}
+
+export const chownPromise = promisify(chown) as (
+ path: string | Buffer | URL,
+ uid: number,
+ gid: number,
+) => Promise<void>;
+
+/**
+ * Synchronously changes the owner and group
+ * of a file.
+ */
+export function chownSync(
+ path: string | Buffer | URL,
+ uid: number,
+ gid: number,
+) {
+ path = getValidatedPath(path).toString();
+ validateInteger(uid, "uid", -1, kMaxUserId);
+ validateInteger(gid, "gid", -1, kMaxUserId);
+
+ Deno.chownSync(pathModule.toNamespacedPath(path), uid, gid);
+}
diff --git a/ext/node/polyfills/_fs/_fs_close.ts b/ext/node/polyfills/_fs/_fs_close.ts
new file mode 100644
index 000000000..ff6082980
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_close.ts
@@ -0,0 +1,21 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { getValidatedFd } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+
+export function close(fd: number, callback: CallbackWithError) {
+ fd = getValidatedFd(fd);
+ setTimeout(() => {
+ let error = null;
+ try {
+ Deno.close(fd);
+ } catch (err) {
+ error = err instanceof Error ? err : new Error("[non-error thrown]");
+ }
+ callback(error);
+ }, 0);
+}
+
+export function closeSync(fd: number) {
+ fd = getValidatedFd(fd);
+ Deno.close(fd);
+}
diff --git a/ext/node/polyfills/_fs/_fs_common.ts b/ext/node/polyfills/_fs/_fs_common.ts
new file mode 100644
index 000000000..1e9f0f266
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_common.ts
@@ -0,0 +1,233 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ O_APPEND,
+ O_CREAT,
+ O_EXCL,
+ O_RDONLY,
+ O_RDWR,
+ O_TRUNC,
+ O_WRONLY,
+} from "internal:deno_node/polyfills/_fs/_fs_constants.ts";
+import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs";
+import type { ErrnoException } from "internal:deno_node/polyfills/_global.d.ts";
+import {
+ BinaryEncodings,
+ Encodings,
+ notImplemented,
+ TextEncodings,
+} from "internal:deno_node/polyfills/_utils.ts";
+
+export type CallbackWithError = (err: ErrnoException | null) => void;
+
+export interface FileOptions {
+ encoding?: Encodings;
+ flag?: string;
+ signal?: AbortSignal;
+}
+
+export type TextOptionsArgument =
+ | TextEncodings
+ | ({ encoding: TextEncodings } & FileOptions);
+export type BinaryOptionsArgument =
+ | BinaryEncodings
+ | ({ encoding: BinaryEncodings } & FileOptions);
+export type FileOptionsArgument = Encodings | FileOptions;
+
+export interface WriteFileOptions extends FileOptions {
+ mode?: number;
+}
+
+export function isFileOptions(
+ fileOptions: string | WriteFileOptions | undefined,
+): fileOptions is FileOptions {
+ if (!fileOptions) return false;
+
+ return (
+ (fileOptions as FileOptions).encoding != undefined ||
+ (fileOptions as FileOptions).flag != undefined ||
+ (fileOptions as FileOptions).signal != undefined ||
+ (fileOptions as WriteFileOptions).mode != undefined
+ );
+}
+
+export function getEncoding(
+ optOrCallback?:
+ | FileOptions
+ | WriteFileOptions
+ // deno-lint-ignore no-explicit-any
+ | ((...args: any[]) => any)
+ | Encodings
+ | null,
+): Encodings | null {
+ if (!optOrCallback || typeof optOrCallback === "function") {
+ return null;
+ }
+
+ const encoding = typeof optOrCallback === "string"
+ ? optOrCallback
+ : optOrCallback.encoding;
+ if (!encoding) return null;
+ return encoding;
+}
+
+export function checkEncoding(encoding: Encodings | null): Encodings | null {
+ if (!encoding) return null;
+
+ encoding = encoding.toLowerCase() as Encodings;
+ if (["utf8", "hex", "base64"].includes(encoding)) return encoding;
+
+ if (encoding === "utf-8") {
+ return "utf8";
+ }
+ if (encoding === "binary") {
+ return "binary";
+ // before this was buffer, however buffer is not used in Node
+ // node -e "require('fs').readFile('../world.txt', 'buffer', console.log)"
+ }
+
+ const notImplementedEncodings = ["utf16le", "latin1", "ascii", "ucs2"];
+
+ if (notImplementedEncodings.includes(encoding as string)) {
+ notImplemented(`"${encoding}" encoding`);
+ }
+
+ throw new Error(`The value "${encoding}" is invalid for option "encoding"`);
+}
+
+export function getOpenOptions(
+ flag: string | number | undefined,
+): Deno.OpenOptions {
+ if (!flag) {
+ return { create: true, append: true };
+ }
+
+ let openOptions: Deno.OpenOptions = {};
+
+ if (typeof flag === "string") {
+ switch (flag) {
+ case "a": {
+ // 'a': Open file for appending. The file is created if it does not exist.
+ openOptions = { create: true, append: true };
+ break;
+ }
+ case "ax":
+ case "xa": {
+ // 'ax', 'xa': Like 'a' but fails if the path exists.
+ openOptions = { createNew: true, write: true, append: true };
+ break;
+ }
+ case "a+": {
+ // 'a+': Open file for reading and appending. The file is created if it does not exist.
+ openOptions = { read: true, create: true, append: true };
+ break;
+ }
+ case "ax+":
+ case "xa+": {
+ // 'ax+', 'xa+': Like 'a+' but fails if the path exists.
+ openOptions = { read: true, createNew: true, append: true };
+ break;
+ }
+ case "r": {
+ // 'r': Open file for reading. An exception occurs if the file does not exist.
+ openOptions = { read: true };
+ break;
+ }
+ case "r+": {
+ // 'r+': Open file for reading and writing. An exception occurs if the file does not exist.
+ openOptions = { read: true, write: true };
+ break;
+ }
+ case "w": {
+ // 'w': Open file for writing. The file is created (if it does not exist) or truncated (if it exists).
+ openOptions = { create: true, write: true, truncate: true };
+ break;
+ }
+ case "wx":
+ case "xw": {
+ // 'wx', 'xw': Like 'w' but fails if the path exists.
+ openOptions = { createNew: true, write: true };
+ break;
+ }
+ case "w+": {
+ // 'w+': Open file for reading and writing. The file is created (if it does not exist) or truncated (if it exists).
+ openOptions = { create: true, write: true, truncate: true, read: true };
+ break;
+ }
+ case "wx+":
+ case "xw+": {
+ // 'wx+', 'xw+': Like 'w+' but fails if the path exists.
+ openOptions = { createNew: true, write: true, read: true };
+ break;
+ }
+ case "as":
+ case "sa": {
+ // 'as', 'sa': Open file for appending in synchronous mode. The file is created if it does not exist.
+ openOptions = { create: true, append: true };
+ break;
+ }
+ case "as+":
+ case "sa+": {
+ // 'as+', 'sa+': Open file for reading and appending in synchronous mode. The file is created if it does not exist.
+ openOptions = { create: true, read: true, append: true };
+ break;
+ }
+ case "rs+":
+ case "sr+": {
+ // 'rs+', 'sr+': Open file for reading and writing in synchronous mode. Instructs the operating system to bypass the local file system cache.
+ openOptions = { create: true, read: true, write: true };
+ break;
+ }
+ default: {
+ throw new Error(`Unrecognized file system flag: ${flag}`);
+ }
+ }
+ } else if (typeof flag === "number") {
+ if ((flag & O_APPEND) === O_APPEND) {
+ openOptions.append = true;
+ }
+ if ((flag & O_CREAT) === O_CREAT) {
+ openOptions.create = true;
+ openOptions.write = true;
+ }
+ if ((flag & O_EXCL) === O_EXCL) {
+ openOptions.createNew = true;
+ openOptions.read = true;
+ openOptions.write = true;
+ }
+ if ((flag & O_TRUNC) === O_TRUNC) {
+ openOptions.truncate = true;
+ }
+ if ((flag & O_RDONLY) === O_RDONLY) {
+ openOptions.read = true;
+ }
+ if ((flag & O_WRONLY) === O_WRONLY) {
+ openOptions.write = true;
+ }
+ if ((flag & O_RDWR) === O_RDWR) {
+ openOptions.read = true;
+ openOptions.write = true;
+ }
+ }
+
+ return openOptions;
+}
+
+export { isUint32 as isFd } from "internal:deno_node/polyfills/internal/validators.mjs";
+
+export function maybeCallback(cb: unknown) {
+ validateFunction(cb, "cb");
+
+ return cb as CallbackWithError;
+}
+
+// Ensure that callbacks run in the global context. Only use this function
+// for callbacks that are passed to the binding layer, callbacks that are
+// invoked from JS already run in the proper scope.
+export function makeCallback(
+ this: unknown,
+ cb?: (err: Error | null, result?: unknown) => void,
+) {
+ validateFunction(cb, "cb");
+
+ return (...args: unknown[]) => Reflect.apply(cb!, this, args);
+}
diff --git a/ext/node/polyfills/_fs/_fs_constants.ts b/ext/node/polyfills/_fs/_fs_constants.ts
new file mode 100644
index 000000000..761f6a9b7
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_constants.ts
@@ -0,0 +1,39 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import { fs } from "internal:deno_node/polyfills/internal_binding/constants.ts";
+
+export const {
+ F_OK,
+ R_OK,
+ W_OK,
+ X_OK,
+ S_IRUSR,
+ S_IWUSR,
+ S_IXUSR,
+ S_IRGRP,
+ S_IWGRP,
+ S_IXGRP,
+ S_IROTH,
+ S_IWOTH,
+ S_IXOTH,
+ COPYFILE_EXCL,
+ COPYFILE_FICLONE,
+ COPYFILE_FICLONE_FORCE,
+ UV_FS_COPYFILE_EXCL,
+ UV_FS_COPYFILE_FICLONE,
+ UV_FS_COPYFILE_FICLONE_FORCE,
+ O_RDONLY,
+ O_WRONLY,
+ O_RDWR,
+ O_NOCTTY,
+ O_TRUNC,
+ O_APPEND,
+ O_DIRECTORY,
+ O_NOFOLLOW,
+ O_SYNC,
+ O_DSYNC,
+ O_SYMLINK,
+ O_NONBLOCK,
+ O_CREAT,
+ O_EXCL,
+} = fs;
diff --git a/ext/node/polyfills/_fs/_fs_copy.ts b/ext/node/polyfills/_fs/_fs_copy.ts
new file mode 100644
index 000000000..0971da1eb
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_copy.ts
@@ -0,0 +1,88 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { makeCallback } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import {
+ getValidatedPath,
+ getValidMode,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { fs } from "internal:deno_node/polyfills/internal_binding/constants.ts";
+import { codeMap } from "internal:deno_node/polyfills/internal_binding/uv.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export function copyFile(
+ src: string | Buffer | URL,
+ dest: string | Buffer | URL,
+ callback: CallbackWithError,
+): void;
+export function copyFile(
+ src: string | Buffer | URL,
+ dest: string | Buffer | URL,
+ mode: number,
+ callback: CallbackWithError,
+): void;
+export function copyFile(
+ src: string | Buffer | URL,
+ dest: string | Buffer | URL,
+ mode: number | CallbackWithError,
+ callback?: CallbackWithError,
+) {
+ if (typeof mode === "function") {
+ callback = mode;
+ mode = 0;
+ }
+ const srcStr = getValidatedPath(src, "src").toString();
+ const destStr = getValidatedPath(dest, "dest").toString();
+ const modeNum = getValidMode(mode, "copyFile");
+ const cb = makeCallback(callback);
+
+ if ((modeNum & fs.COPYFILE_EXCL) === fs.COPYFILE_EXCL) {
+ Deno.lstat(destStr).then(() => {
+ // deno-lint-ignore no-explicit-any
+ const e: any = new Error(
+ `EEXIST: file already exists, copyfile '${srcStr}' -> '${destStr}'`,
+ );
+ e.syscall = "copyfile";
+ e.errno = codeMap.get("EEXIST");
+ e.code = "EEXIST";
+ cb(e);
+ }, (e) => {
+ if (e instanceof Deno.errors.NotFound) {
+ Deno.copyFile(srcStr, destStr).then(() => cb(null), cb);
+ }
+ cb(e);
+ });
+ } else {
+ Deno.copyFile(srcStr, destStr).then(() => cb(null), cb);
+ }
+}
+
+export const copyFilePromise = promisify(copyFile) as (
+ src: string | Buffer | URL,
+ dest: string | Buffer | URL,
+ mode?: number,
+) => Promise<void>;
+
+export function copyFileSync(
+ src: string | Buffer | URL,
+ dest: string | Buffer | URL,
+ mode?: number,
+) {
+ const srcStr = getValidatedPath(src, "src").toString();
+ const destStr = getValidatedPath(dest, "dest").toString();
+ const modeNum = getValidMode(mode, "copyFile");
+
+ if ((modeNum & fs.COPYFILE_EXCL) === fs.COPYFILE_EXCL) {
+ try {
+ Deno.lstatSync(destStr);
+ throw new Error(`A file exists at the destination: ${destStr}`);
+ } catch (e) {
+ if (e instanceof Deno.errors.NotFound) {
+ Deno.copyFileSync(srcStr, destStr);
+ }
+ throw e;
+ }
+ } else {
+ Deno.copyFileSync(srcStr, destStr);
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_dir.ts b/ext/node/polyfills/_fs/_fs_dir.ts
new file mode 100644
index 000000000..e13547241
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_dir.ts
@@ -0,0 +1,104 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import Dirent from "internal:deno_node/polyfills/_fs/_fs_dirent.ts";
+import { assert } from "internal:deno_node/polyfills/_util/asserts.ts";
+import { ERR_MISSING_ARGS } from "internal:deno_node/polyfills/internal/errors.ts";
+import { TextDecoder } from "internal:deno_web/08_text_encoding.js";
+
+export default class Dir {
+ #dirPath: string | Uint8Array;
+ #syncIterator!: Iterator<Deno.DirEntry, undefined> | null;
+ #asyncIterator!: AsyncIterator<Deno.DirEntry, undefined> | null;
+
+ constructor(path: string | Uint8Array) {
+ if (!path) {
+ throw new ERR_MISSING_ARGS("path");
+ }
+ this.#dirPath = path;
+ }
+
+ get path(): string {
+ if (this.#dirPath instanceof Uint8Array) {
+ return new TextDecoder().decode(this.#dirPath);
+ }
+ return this.#dirPath;
+ }
+
+ // deno-lint-ignore no-explicit-any
+ read(callback?: (...args: any[]) => void): Promise<Dirent | null> {
+ return new Promise((resolve, reject) => {
+ if (!this.#asyncIterator) {
+ this.#asyncIterator = Deno.readDir(this.path)[Symbol.asyncIterator]();
+ }
+ assert(this.#asyncIterator);
+ this.#asyncIterator
+ .next()
+ .then((iteratorResult) => {
+ resolve(
+ iteratorResult.done ? null : new Dirent(iteratorResult.value),
+ );
+ if (callback) {
+ callback(
+ null,
+ iteratorResult.done ? null : new Dirent(iteratorResult.value),
+ );
+ }
+ }, (err) => {
+ if (callback) {
+ callback(err);
+ }
+ reject(err);
+ });
+ });
+ }
+
+ readSync(): Dirent | null {
+ if (!this.#syncIterator) {
+ this.#syncIterator = Deno.readDirSync(this.path)![Symbol.iterator]();
+ }
+
+ const iteratorResult = this.#syncIterator.next();
+ if (iteratorResult.done) {
+ return null;
+ } else {
+ return new Dirent(iteratorResult.value);
+ }
+ }
+
+ /**
+ * Unlike Node, Deno does not require managing resource ids for reading
+ * directories, and therefore does not need to close directories when
+ * finished reading.
+ */
+ // deno-lint-ignore no-explicit-any
+ close(callback?: (...args: any[]) => void): Promise<void> {
+ return new Promise((resolve) => {
+ if (callback) {
+ callback(null);
+ }
+ resolve();
+ });
+ }
+
+ /**
+ * Unlike Node, Deno does not require managing resource ids for reading
+ * directories, and therefore does not need to close directories when
+ * finished reading
+ */
+ closeSync() {
+ //No op
+ }
+
+ async *[Symbol.asyncIterator](): AsyncIterableIterator<Dirent> {
+ try {
+ while (true) {
+ const dirent: Dirent | null = await this.read();
+ if (dirent === null) {
+ break;
+ }
+ yield dirent;
+ }
+ } finally {
+ await this.close();
+ }
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_dirent.ts b/ext/node/polyfills/_fs/_fs_dirent.ts
new file mode 100644
index 000000000..5a7c243bf
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_dirent.ts
@@ -0,0 +1,46 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { notImplemented } from "internal:deno_node/polyfills/_utils.ts";
+
+export default class Dirent {
+ constructor(private entry: Deno.DirEntry) {}
+
+ isBlockDevice(): boolean {
+ notImplemented("Deno does not yet support identification of block devices");
+ return false;
+ }
+
+ isCharacterDevice(): boolean {
+ notImplemented(
+ "Deno does not yet support identification of character devices",
+ );
+ return false;
+ }
+
+ isDirectory(): boolean {
+ return this.entry.isDirectory;
+ }
+
+ isFIFO(): boolean {
+ notImplemented(
+ "Deno does not yet support identification of FIFO named pipes",
+ );
+ return false;
+ }
+
+ isFile(): boolean {
+ return this.entry.isFile;
+ }
+
+ isSocket(): boolean {
+ notImplemented("Deno does not yet support identification of sockets");
+ return false;
+ }
+
+ isSymbolicLink(): boolean {
+ return this.entry.isSymlink;
+ }
+
+ get name(): string | null {
+ return this.entry.name;
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_exists.ts b/ext/node/polyfills/_fs/_fs_exists.ts
new file mode 100644
index 000000000..9b0f18303
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_exists.ts
@@ -0,0 +1,40 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+
+type ExistsCallback = (exists: boolean) => void;
+
+/**
+ * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ * Deprecated in node api
+ */
+export function exists(path: string | URL, callback: ExistsCallback) {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+ Deno.lstat(path).then(() => callback(true), () => callback(false));
+}
+
+// The callback of fs.exists doesn't have standard callback signature.
+// We need to provide special implementation for promisify.
+// See https://github.com/nodejs/node/pull/13316
+const kCustomPromisifiedSymbol = Symbol.for("nodejs.util.promisify.custom");
+Object.defineProperty(exists, kCustomPromisifiedSymbol, {
+ value: (path: string | URL) => {
+ return new Promise((resolve) => {
+ exists(path, (exists) => resolve(exists));
+ });
+ },
+});
+
+/**
+ * TODO: Also accept 'path' parameter as a Node polyfill Buffer or URL type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+export function existsSync(path: string | URL): boolean {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+ try {
+ Deno.lstatSync(path);
+ return true;
+ } catch (_err) {
+ return false;
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_fdatasync.ts b/ext/node/polyfills/_fs/_fs_fdatasync.ts
new file mode 100644
index 000000000..325ac30da
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_fdatasync.ts
@@ -0,0 +1,13 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+
+export function fdatasync(
+ fd: number,
+ callback: CallbackWithError,
+) {
+ Deno.fdatasync(fd).then(() => callback(null), callback);
+}
+
+export function fdatasyncSync(fd: number) {
+ Deno.fdatasyncSync(fd);
+}
diff --git a/ext/node/polyfills/_fs/_fs_fstat.ts b/ext/node/polyfills/_fs/_fs_fstat.ts
new file mode 100644
index 000000000..ab9cbead4
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_fstat.ts
@@ -0,0 +1,60 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ BigIntStats,
+ CFISBIS,
+ statCallback,
+ statCallbackBigInt,
+ statOptions,
+ Stats,
+} from "internal:deno_node/polyfills/_fs/_fs_stat.ts";
+
+export function fstat(fd: number, callback: statCallback): void;
+export function fstat(
+ fd: number,
+ options: { bigint: false },
+ callback: statCallback,
+): void;
+export function fstat(
+ fd: number,
+ options: { bigint: true },
+ callback: statCallbackBigInt,
+): void;
+export function fstat(
+ fd: number,
+ optionsOrCallback: statCallback | statCallbackBigInt | statOptions,
+ maybeCallback?: statCallback | statCallbackBigInt,
+) {
+ const callback =
+ (typeof optionsOrCallback === "function"
+ ? optionsOrCallback
+ : maybeCallback) as (
+ ...args: [Error] | [null, BigIntStats | Stats]
+ ) => void;
+ const options = typeof optionsOrCallback === "object"
+ ? optionsOrCallback
+ : { bigint: false };
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.fstat(fd).then(
+ (stat) => callback(null, CFISBIS(stat, options.bigint)),
+ (err) => callback(err),
+ );
+}
+
+export function fstatSync(fd: number): Stats;
+export function fstatSync(
+ fd: number,
+ options: { bigint: false },
+): Stats;
+export function fstatSync(
+ fd: number,
+ options: { bigint: true },
+): BigIntStats;
+export function fstatSync(
+ fd: number,
+ options?: statOptions,
+): Stats | BigIntStats {
+ const origin = Deno.fstatSync(fd);
+ return CFISBIS(origin, options?.bigint || false);
+}
diff --git a/ext/node/polyfills/_fs/_fs_fsync.ts b/ext/node/polyfills/_fs/_fs_fsync.ts
new file mode 100644
index 000000000..02be24abc
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_fsync.ts
@@ -0,0 +1,13 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+
+export function fsync(
+ fd: number,
+ callback: CallbackWithError,
+) {
+ Deno.fsync(fd).then(() => callback(null), callback);
+}
+
+export function fsyncSync(fd: number) {
+ Deno.fsyncSync(fd);
+}
diff --git a/ext/node/polyfills/_fs/_fs_ftruncate.ts b/ext/node/polyfills/_fs/_fs_ftruncate.ts
new file mode 100644
index 000000000..9c7bfbd01
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_ftruncate.ts
@@ -0,0 +1,23 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+
+export function ftruncate(
+ fd: number,
+ lenOrCallback: number | CallbackWithError,
+ maybeCallback?: CallbackWithError,
+) {
+ const len: number | undefined = typeof lenOrCallback === "number"
+ ? lenOrCallback
+ : undefined;
+ const callback: CallbackWithError = typeof lenOrCallback === "function"
+ ? lenOrCallback
+ : maybeCallback as CallbackWithError;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.ftruncate(fd, len).then(() => callback(null), callback);
+}
+
+export function ftruncateSync(fd: number, len?: number) {
+ Deno.ftruncateSync(fd, len);
+}
diff --git a/ext/node/polyfills/_fs/_fs_futimes.ts b/ext/node/polyfills/_fs/_fs_futimes.ts
new file mode 100644
index 000000000..60f06bc34
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_futimes.ts
@@ -0,0 +1,50 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+
+function getValidTime(
+ time: number | string | Date,
+ name: string,
+): number | Date {
+ if (typeof time === "string") {
+ time = Number(time);
+ }
+
+ if (
+ typeof time === "number" &&
+ (Number.isNaN(time) || !Number.isFinite(time))
+ ) {
+ throw new Deno.errors.InvalidData(
+ `invalid ${name}, must not be infinity or NaN`,
+ );
+ }
+
+ return time;
+}
+
+export function futimes(
+ fd: number,
+ atime: number | string | Date,
+ mtime: number | string | Date,
+ callback: CallbackWithError,
+) {
+ if (!callback) {
+ throw new Deno.errors.InvalidData("No callback function supplied");
+ }
+
+ atime = getValidTime(atime, "atime");
+ mtime = getValidTime(mtime, "mtime");
+
+ Deno.futime(fd, atime, mtime).then(() => callback(null), callback);
+}
+
+export function futimesSync(
+ fd: number,
+ atime: number | string | Date,
+ mtime: number | string | Date,
+) {
+ atime = getValidTime(atime, "atime");
+ mtime = getValidTime(mtime, "mtime");
+
+ Deno.futimeSync(fd, atime, mtime);
+}
diff --git a/ext/node/polyfills/_fs/_fs_link.ts b/ext/node/polyfills/_fs/_fs_link.ts
new file mode 100644
index 000000000..eb95a10f6
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_link.ts
@@ -0,0 +1,46 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+/**
+ * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+export function link(
+ existingPath: string | URL,
+ newPath: string | URL,
+ callback: CallbackWithError,
+) {
+ existingPath = existingPath instanceof URL
+ ? fromFileUrl(existingPath)
+ : existingPath;
+ newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath;
+
+ Deno.link(existingPath, newPath).then(() => callback(null), callback);
+}
+
+/**
+ * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+export const linkPromise = promisify(link) as (
+ existingPath: string | URL,
+ newPath: string | URL,
+) => Promise<void>;
+
+/**
+ * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+export function linkSync(
+ existingPath: string | URL,
+ newPath: string | URL,
+) {
+ existingPath = existingPath instanceof URL
+ ? fromFileUrl(existingPath)
+ : existingPath;
+ newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath;
+
+ Deno.linkSync(existingPath, newPath);
+}
diff --git a/ext/node/polyfills/_fs/_fs_lstat.ts b/ext/node/polyfills/_fs/_fs_lstat.ts
new file mode 100644
index 000000000..c85f82a11
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_lstat.ts
@@ -0,0 +1,67 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ BigIntStats,
+ CFISBIS,
+ statCallback,
+ statCallbackBigInt,
+ statOptions,
+ Stats,
+} from "internal:deno_node/polyfills/_fs/_fs_stat.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export function lstat(path: string | URL, callback: statCallback): void;
+export function lstat(
+ path: string | URL,
+ options: { bigint: false },
+ callback: statCallback,
+): void;
+export function lstat(
+ path: string | URL,
+ options: { bigint: true },
+ callback: statCallbackBigInt,
+): void;
+export function lstat(
+ path: string | URL,
+ optionsOrCallback: statCallback | statCallbackBigInt | statOptions,
+ maybeCallback?: statCallback | statCallbackBigInt,
+) {
+ const callback =
+ (typeof optionsOrCallback === "function"
+ ? optionsOrCallback
+ : maybeCallback) as (
+ ...args: [Error] | [null, BigIntStats | Stats]
+ ) => void;
+ const options = typeof optionsOrCallback === "object"
+ ? optionsOrCallback
+ : { bigint: false };
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.lstat(path).then(
+ (stat) => callback(null, CFISBIS(stat, options.bigint)),
+ (err) => callback(err),
+ );
+}
+
+export const lstatPromise = promisify(lstat) as (
+ & ((path: string | URL) => Promise<Stats>)
+ & ((path: string | URL, options: { bigint: false }) => Promise<Stats>)
+ & ((path: string | URL, options: { bigint: true }) => Promise<BigIntStats>)
+);
+
+export function lstatSync(path: string | URL): Stats;
+export function lstatSync(
+ path: string | URL,
+ options: { bigint: false },
+): Stats;
+export function lstatSync(
+ path: string | URL,
+ options: { bigint: true },
+): BigIntStats;
+export function lstatSync(
+ path: string | URL,
+ options?: statOptions,
+): Stats | BigIntStats {
+ const origin = Deno.lstatSync(path);
+ return CFISBIS(origin, options?.bigint || false);
+}
diff --git a/ext/node/polyfills/_fs/_fs_mkdir.ts b/ext/node/polyfills/_fs/_fs_mkdir.ts
new file mode 100644
index 000000000..ac4b78259
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_mkdir.ts
@@ -0,0 +1,77 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts";
+import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { validateBoolean } from "internal:deno_node/polyfills/internal/validators.mjs";
+
+/**
+ * TODO: Also accept 'path' parameter as a Node polyfill Buffer type once these
+ * are implemented. See https://github.com/denoland/deno/issues/3403
+ */
+type MkdirOptions =
+ | { recursive?: boolean; mode?: number | undefined }
+ | number
+ | boolean;
+
+export function mkdir(
+ path: string | URL,
+ options?: MkdirOptions | CallbackWithError,
+ callback?: CallbackWithError,
+) {
+ path = getValidatedPath(path) as string;
+
+ let mode = 0o777;
+ let recursive = false;
+
+ if (typeof options == "function") {
+ callback = options;
+ } else if (typeof options === "number") {
+ mode = options;
+ } else if (typeof options === "boolean") {
+ recursive = options;
+ } else if (options) {
+ if (options.recursive !== undefined) recursive = options.recursive;
+ if (options.mode !== undefined) mode = options.mode;
+ }
+ validateBoolean(recursive, "options.recursive");
+
+ Deno.mkdir(path, { recursive, mode })
+ .then(() => {
+ if (typeof callback === "function") {
+ callback(null);
+ }
+ }, (err) => {
+ if (typeof callback === "function") {
+ callback(err);
+ }
+ });
+}
+
+export const mkdirPromise = promisify(mkdir) as (
+ path: string | URL,
+ options?: MkdirOptions,
+) => Promise<void>;
+
+export function mkdirSync(path: string | URL, options?: MkdirOptions) {
+ path = getValidatedPath(path) as string;
+
+ let mode = 0o777;
+ let recursive = false;
+
+ if (typeof options === "number") {
+ mode = options;
+ } else if (typeof options === "boolean") {
+ recursive = options;
+ } else if (options) {
+ if (options.recursive !== undefined) recursive = options.recursive;
+ if (options.mode !== undefined) mode = options.mode;
+ }
+ validateBoolean(recursive, "options.recursive");
+
+ try {
+ Deno.mkdirSync(path, { recursive, mode });
+ } catch (err) {
+ throw denoErrorToNodeError(err as Error, { syscall: "mkdir", path });
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_mkdtemp.ts b/ext/node/polyfills/_fs/_fs_mkdtemp.ts
new file mode 100644
index 000000000..de227b216
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_mkdtemp.ts
@@ -0,0 +1,115 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright Node.js contributors. All rights reserved. MIT License.
+
+import {
+ TextDecoder,
+ TextEncoder,
+} from "internal:deno_web/08_text_encoding.js";
+import { existsSync } from "internal:deno_node/polyfills/_fs/_fs_exists.ts";
+import {
+ mkdir,
+ mkdirSync,
+} from "internal:deno_node/polyfills/_fs/_fs_mkdir.ts";
+import {
+ ERR_INVALID_ARG_TYPE,
+ ERR_INVALID_OPT_VALUE_ENCODING,
+} from "internal:deno_node/polyfills/internal/errors.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export type mkdtempCallback = (
+ err: Error | null,
+ directory?: string,
+) => void;
+
+// https://nodejs.org/dist/latest-v15.x/docs/api/fs.html#fs_fs_mkdtemp_prefix_options_callback
+export function mkdtemp(prefix: string, callback: mkdtempCallback): void;
+export function mkdtemp(
+ prefix: string,
+ options: { encoding: string } | string,
+ callback: mkdtempCallback,
+): void;
+export function mkdtemp(
+ prefix: string,
+ optionsOrCallback: { encoding: string } | string | mkdtempCallback,
+ maybeCallback?: mkdtempCallback,
+) {
+ const callback: mkdtempCallback | undefined =
+ typeof optionsOrCallback == "function" ? optionsOrCallback : maybeCallback;
+ if (!callback) {
+ throw new ERR_INVALID_ARG_TYPE("callback", "function", callback);
+ }
+
+ const encoding: string | undefined = parseEncoding(optionsOrCallback);
+ const path = tempDirPath(prefix);
+
+ mkdir(
+ path,
+ { recursive: false, mode: 0o700 },
+ (err: Error | null | undefined) => {
+ if (err) callback(err);
+ else callback(null, decode(path, encoding));
+ },
+ );
+}
+
+export const mkdtempPromise = promisify(mkdtemp) as (
+ prefix: string,
+ options?: { encoding: string } | string,
+) => Promise<string>;
+
+// https://nodejs.org/dist/latest-v15.x/docs/api/fs.html#fs_fs_mkdtempsync_prefix_options
+export function mkdtempSync(
+ prefix: string,
+ options?: { encoding: string } | string,
+): string {
+ const encoding: string | undefined = parseEncoding(options);
+ const path = tempDirPath(prefix);
+
+ mkdirSync(path, { recursive: false, mode: 0o700 });
+ return decode(path, encoding);
+}
+
+function parseEncoding(
+ optionsOrCallback?: { encoding: string } | string | mkdtempCallback,
+): string | undefined {
+ let encoding: string | undefined;
+ if (typeof optionsOrCallback == "function") encoding = undefined;
+ else if (optionsOrCallback instanceof Object) {
+ encoding = optionsOrCallback?.encoding;
+ } else encoding = optionsOrCallback;
+
+ if (encoding) {
+ try {
+ new TextDecoder(encoding);
+ } catch {
+ throw new ERR_INVALID_OPT_VALUE_ENCODING(encoding);
+ }
+ }
+
+ return encoding;
+}
+
+function decode(str: string, encoding?: string): string {
+ if (!encoding) return str;
+ else {
+ const decoder = new TextDecoder(encoding);
+ const encoder = new TextEncoder();
+ return decoder.decode(encoder.encode(str));
+ }
+}
+
+const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+function randomName(): string {
+ return [...Array(6)].map(() =>
+ CHARS[Math.floor(Math.random() * CHARS.length)]
+ ).join("");
+}
+
+function tempDirPath(prefix: string): string {
+ let path: string;
+ do {
+ path = prefix + randomName();
+ } while (existsSync(path));
+
+ return path;
+}
diff --git a/ext/node/polyfills/_fs/_fs_open.ts b/ext/node/polyfills/_fs/_fs_open.ts
new file mode 100644
index 000000000..e703da56f
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_open.ts
@@ -0,0 +1,198 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ O_APPEND,
+ O_CREAT,
+ O_EXCL,
+ O_RDWR,
+ O_TRUNC,
+ O_WRONLY,
+} from "internal:deno_node/polyfills/_fs/_fs_constants.ts";
+import { getOpenOptions } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+import { parseFileMode } from "internal:deno_node/polyfills/internal/validators.mjs";
+import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts";
+import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import type { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+
+function existsSync(filePath: string | URL): boolean {
+ try {
+ Deno.lstatSync(filePath);
+ return true;
+ } catch (error) {
+ if (error instanceof Deno.errors.NotFound) {
+ return false;
+ }
+ throw error;
+ }
+}
+
+const FLAGS_AX = O_APPEND | O_CREAT | O_WRONLY | O_EXCL;
+const FLAGS_AX_PLUS = O_APPEND | O_CREAT | O_RDWR | O_EXCL;
+const FLAGS_WX = O_TRUNC | O_CREAT | O_WRONLY | O_EXCL;
+const FLAGS_WX_PLUS = O_TRUNC | O_CREAT | O_RDWR | O_EXCL;
+
+export type openFlags =
+ | "a"
+ | "ax"
+ | "a+"
+ | "ax+"
+ | "as"
+ | "as+"
+ | "r"
+ | "r+"
+ | "rs+"
+ | "w"
+ | "wx"
+ | "w+"
+ | "wx+"
+ | number;
+
+type openCallback = (err: Error | null, fd: number) => void;
+
+function convertFlagAndModeToOptions(
+ flag?: openFlags,
+ mode?: number,
+): Deno.OpenOptions | undefined {
+ if (!flag && !mode) return undefined;
+ if (!flag && mode) return { mode };
+ return { ...getOpenOptions(flag), mode };
+}
+
+export function open(path: string | Buffer | URL, callback: openCallback): void;
+export function open(
+ path: string | Buffer | URL,
+ flags: openFlags,
+ callback: openCallback,
+): void;
+export function open(
+ path: string | Buffer | URL,
+ flags: openFlags,
+ mode: number,
+ callback: openCallback,
+): void;
+export function open(
+ path: string | Buffer | URL,
+ flags: openCallback | openFlags,
+ mode?: openCallback | number,
+ callback?: openCallback,
+) {
+ if (flags === undefined) {
+ throw new ERR_INVALID_ARG_TYPE(
+ "flags or callback",
+ ["string", "function"],
+ flags,
+ );
+ }
+ path = getValidatedPath(path);
+ if (arguments.length < 3) {
+ // deno-lint-ignore no-explicit-any
+ callback = flags as any;
+ flags = "r";
+ mode = 0o666;
+ } else if (typeof mode === "function") {
+ callback = mode;
+ mode = 0o666;
+ } else {
+ mode = parseFileMode(mode, "mode", 0o666);
+ }
+
+ if (typeof callback !== "function") {
+ throw new ERR_INVALID_ARG_TYPE(
+ "callback",
+ "function",
+ callback,
+ );
+ }
+
+ if (flags === undefined) {
+ flags = "r";
+ }
+
+ if (
+ existenceCheckRequired(flags as openFlags) &&
+ existsSync(path as string)
+ ) {
+ const err = new Error(`EEXIST: file already exists, open '${path}'`);
+ (callback as (err: Error) => void)(err);
+ } else {
+ if (flags === "as" || flags === "as+") {
+ let err: Error | null = null, res: number;
+ try {
+ res = openSync(path, flags, mode);
+ } catch (error) {
+ err = error instanceof Error ? error : new Error("[non-error thrown]");
+ }
+ if (err) {
+ (callback as (err: Error) => void)(err);
+ } else {
+ callback(null, res!);
+ }
+ return;
+ }
+ Deno.open(
+ path as string,
+ convertFlagAndModeToOptions(flags as openFlags, mode),
+ ).then(
+ (file) => callback!(null, file.rid),
+ (err) => (callback as (err: Error) => void)(err),
+ );
+ }
+}
+
+export const openPromise = promisify(open) as (
+ & ((path: string | Buffer | URL) => Promise<number>)
+ & ((path: string | Buffer | URL, flags: openFlags) => Promise<number>)
+ & ((path: string | Buffer | URL, mode?: number) => Promise<number>)
+ & ((
+ path: string | Buffer | URL,
+ flags?: openFlags,
+ mode?: number,
+ ) => Promise<number>)
+);
+
+export function openSync(path: string | Buffer | URL): number;
+export function openSync(
+ path: string | Buffer | URL,
+ flags?: openFlags,
+): number;
+export function openSync(path: string | Buffer | URL, mode?: number): number;
+export function openSync(
+ path: string | Buffer | URL,
+ flags?: openFlags,
+ mode?: number,
+): number;
+export function openSync(
+ path: string | Buffer | URL,
+ flags?: openFlags,
+ maybeMode?: number,
+) {
+ const mode = parseFileMode(maybeMode, "mode", 0o666);
+ path = getValidatedPath(path);
+
+ if (flags === undefined) {
+ flags = "r";
+ }
+
+ if (
+ existenceCheckRequired(flags) &&
+ existsSync(path as string)
+ ) {
+ throw new Error(`EEXIST: file already exists, open '${path}'`);
+ }
+
+ return Deno.openSync(path as string, convertFlagAndModeToOptions(flags, mode))
+ .rid;
+}
+
+function existenceCheckRequired(flags: openFlags | number) {
+ return (
+ (typeof flags === "string" &&
+ ["ax", "ax+", "wx", "wx+"].includes(flags)) ||
+ (typeof flags === "number" && (
+ ((flags & FLAGS_AX) === FLAGS_AX) ||
+ ((flags & FLAGS_AX_PLUS) === FLAGS_AX_PLUS) ||
+ ((flags & FLAGS_WX) === FLAGS_WX) ||
+ ((flags & FLAGS_WX_PLUS) === FLAGS_WX_PLUS)
+ ))
+ );
+}
diff --git a/ext/node/polyfills/_fs/_fs_opendir.ts b/ext/node/polyfills/_fs/_fs_opendir.ts
new file mode 100644
index 000000000..5ee13f951
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_opendir.ts
@@ -0,0 +1,89 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import Dir from "internal:deno_node/polyfills/_fs/_fs_dir.ts";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import {
+ getOptions,
+ getValidatedPath,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts";
+import {
+ validateFunction,
+ validateInteger,
+} from "internal:deno_node/polyfills/internal/validators.mjs";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+/** These options aren't funcitonally used right now, as `Dir` doesn't yet support them.
+ * However, these values are still validated.
+ */
+type Options = {
+ encoding?: string;
+ bufferSize?: number;
+};
+type Callback = (err?: Error | null, dir?: Dir) => void;
+
+function _validateFunction(callback: unknown): asserts callback is Callback {
+ validateFunction(callback, "callback");
+}
+
+/** @link https://nodejs.org/api/fs.html#fsopendirsyncpath-options */
+export function opendir(
+ path: string | Buffer | URL,
+ options: Options | Callback,
+ callback?: Callback,
+) {
+ callback = typeof options === "function" ? options : callback;
+ _validateFunction(callback);
+
+ path = getValidatedPath(path).toString();
+
+ let err, dir;
+ try {
+ const { bufferSize } = getOptions(options, {
+ encoding: "utf8",
+ bufferSize: 32,
+ });
+ validateInteger(bufferSize, "options.bufferSize", 1, 4294967295);
+
+ /** Throws if path is invalid */
+ Deno.readDirSync(path);
+
+ dir = new Dir(path);
+ } catch (error) {
+ err = denoErrorToNodeError(error as Error, { syscall: "opendir" });
+ }
+ if (err) {
+ callback(err);
+ } else {
+ callback(null, dir);
+ }
+}
+
+/** @link https://nodejs.org/api/fs.html#fspromisesopendirpath-options */
+export const opendirPromise = promisify(opendir) as (
+ path: string | Buffer | URL,
+ options?: Options,
+) => Promise<Dir>;
+
+export function opendirSync(
+ path: string | Buffer | URL,
+ options?: Options,
+): Dir {
+ path = getValidatedPath(path).toString();
+
+ const { bufferSize } = getOptions(options, {
+ encoding: "utf8",
+ bufferSize: 32,
+ });
+
+ validateInteger(bufferSize, "options.bufferSize", 1, 4294967295);
+
+ try {
+ /** Throws if path is invalid */
+ Deno.readDirSync(path);
+
+ return new Dir(path);
+ } catch (err) {
+ throw denoErrorToNodeError(err as Error, { syscall: "opendir" });
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_read.ts b/ext/node/polyfills/_fs/_fs_read.ts
new file mode 100644
index 000000000..d74445829
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_read.ts
@@ -0,0 +1,197 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { ERR_INVALID_ARG_TYPE } from "internal:deno_node/polyfills/internal/errors.ts";
+import {
+ validateOffsetLengthRead,
+ validatePosition,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import {
+ validateBuffer,
+ validateInteger,
+} from "internal:deno_node/polyfills/internal/validators.mjs";
+
+type readOptions = {
+ buffer: Buffer | Uint8Array;
+ offset: number;
+ length: number;
+ position: number | null;
+};
+
+type readSyncOptions = {
+ offset: number;
+ length: number;
+ position: number | null;
+};
+
+type BinaryCallback = (
+ err: Error | null,
+ bytesRead: number | null,
+ data?: Buffer,
+) => void;
+type Callback = BinaryCallback;
+
+export function read(fd: number, callback: Callback): void;
+export function read(
+ fd: number,
+ options: readOptions,
+ callback: Callback,
+): void;
+export function read(
+ fd: number,
+ buffer: Buffer | Uint8Array,
+ offset: number,
+ length: number,
+ position: number | null,
+ callback: Callback,
+): void;
+export function read(
+ fd: number,
+ optOrBufferOrCb?: Buffer | Uint8Array | readOptions | Callback,
+ offsetOrCallback?: number | Callback,
+ length?: number,
+ position?: number | null,
+ callback?: Callback,
+) {
+ let cb: Callback | undefined;
+ let offset = 0,
+ buffer: Buffer | Uint8Array;
+
+ if (typeof fd !== "number") {
+ throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
+ }
+
+ if (length == null) {
+ length = 0;
+ }
+
+ if (typeof offsetOrCallback === "function") {
+ cb = offsetOrCallback;
+ } else if (typeof optOrBufferOrCb === "function") {
+ cb = optOrBufferOrCb;
+ } else {
+ offset = offsetOrCallback as number;
+ validateInteger(offset, "offset", 0);
+ cb = callback;
+ }
+
+ if (
+ optOrBufferOrCb instanceof Buffer || optOrBufferOrCb instanceof Uint8Array
+ ) {
+ buffer = optOrBufferOrCb;
+ } else if (typeof optOrBufferOrCb === "function") {
+ offset = 0;
+ buffer = Buffer.alloc(16384);
+ length = buffer.byteLength;
+ position = null;
+ } else {
+ const opt = optOrBufferOrCb as readOptions;
+ if (
+ !(opt.buffer instanceof Buffer) && !(opt.buffer instanceof Uint8Array)
+ ) {
+ if (opt.buffer === null) {
+ // @ts-ignore: Intentionally create TypeError for passing test-fs-read.js#L87
+ length = opt.buffer.byteLength;
+ }
+ throw new ERR_INVALID_ARG_TYPE("buffer", [
+ "Buffer",
+ "TypedArray",
+ "DataView",
+ ], optOrBufferOrCb);
+ }
+ offset = opt.offset ?? 0;
+ buffer = opt.buffer ?? Buffer.alloc(16384);
+ length = opt.length ?? buffer.byteLength;
+ position = opt.position ?? null;
+ }
+
+ if (position == null) {
+ position = -1;
+ }
+
+ validatePosition(position);
+ validateOffsetLengthRead(offset, length, buffer.byteLength);
+
+ if (!cb) throw new ERR_INVALID_ARG_TYPE("cb", "Callback", cb);
+
+ (async () => {
+ try {
+ let nread: number | null;
+ if (typeof position === "number" && position >= 0) {
+ const currentPosition = await Deno.seek(fd, 0, Deno.SeekMode.Current);
+ // We use sync calls below to avoid being affected by others during
+ // these calls.
+ Deno.seekSync(fd, position, Deno.SeekMode.Start);
+ nread = Deno.readSync(fd, buffer);
+ Deno.seekSync(fd, currentPosition, Deno.SeekMode.Start);
+ } else {
+ nread = await Deno.read(fd, buffer);
+ }
+ cb(null, nread ?? 0, Buffer.from(buffer.buffer, offset, length));
+ } catch (error) {
+ cb(error as Error, null);
+ }
+ })();
+}
+
+export function readSync(
+ fd: number,
+ buffer: Buffer | Uint8Array,
+ offset: number,
+ length: number,
+ position: number | null,
+): number;
+export function readSync(
+ fd: number,
+ buffer: Buffer | Uint8Array,
+ opt: readSyncOptions,
+): number;
+export function readSync(
+ fd: number,
+ buffer: Buffer | Uint8Array,
+ offsetOrOpt?: number | readSyncOptions,
+ length?: number,
+ position?: number | null,
+): number {
+ let offset = 0;
+
+ if (typeof fd !== "number") {
+ throw new ERR_INVALID_ARG_TYPE("fd", "number", fd);
+ }
+
+ validateBuffer(buffer);
+
+ if (length == null) {
+ length = 0;
+ }
+
+ if (typeof offsetOrOpt === "number") {
+ offset = offsetOrOpt;
+ validateInteger(offset, "offset", 0);
+ } else {
+ const opt = offsetOrOpt as readSyncOptions;
+ offset = opt.offset ?? 0;
+ length = opt.length ?? buffer.byteLength;
+ position = opt.position ?? null;
+ }
+
+ if (position == null) {
+ position = -1;
+ }
+
+ validatePosition(position);
+ validateOffsetLengthRead(offset, length, buffer.byteLength);
+
+ let currentPosition = 0;
+ if (typeof position === "number" && position >= 0) {
+ currentPosition = Deno.seekSync(fd, 0, Deno.SeekMode.Current);
+ Deno.seekSync(fd, position, Deno.SeekMode.Start);
+ }
+
+ const numberOfBytesRead = Deno.readSync(fd, buffer);
+
+ if (typeof position === "number" && position >= 0) {
+ Deno.seekSync(fd, currentPosition, Deno.SeekMode.Start);
+ }
+
+ return numberOfBytesRead ?? 0;
+}
diff --git a/ext/node/polyfills/_fs/_fs_readFile.ts b/ext/node/polyfills/_fs/_fs_readFile.ts
new file mode 100644
index 000000000..6c5e9fb8b
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_readFile.ts
@@ -0,0 +1,108 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ BinaryOptionsArgument,
+ FileOptionsArgument,
+ getEncoding,
+ TextOptionsArgument,
+} from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import {
+ BinaryEncodings,
+ Encodings,
+ TextEncodings,
+} from "internal:deno_node/polyfills/_utils.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+function maybeDecode(data: Uint8Array, encoding: TextEncodings): string;
+function maybeDecode(
+ data: Uint8Array,
+ encoding: BinaryEncodings | null,
+): Buffer;
+function maybeDecode(
+ data: Uint8Array,
+ encoding: Encodings | null,
+): string | Buffer {
+ const buffer = Buffer.from(data.buffer, data.byteOffset, data.byteLength);
+ if (encoding && encoding !== "binary") return buffer.toString(encoding);
+ return buffer;
+}
+
+type TextCallback = (err: Error | null, data?: string) => void;
+type BinaryCallback = (err: Error | null, data?: Buffer) => void;
+type GenericCallback = (err: Error | null, data?: string | Buffer) => void;
+type Callback = TextCallback | BinaryCallback | GenericCallback;
+
+export function readFile(
+ path: string | URL,
+ options: TextOptionsArgument,
+ callback: TextCallback,
+): void;
+export function readFile(
+ path: string | URL,
+ options: BinaryOptionsArgument,
+ callback: BinaryCallback,
+): void;
+export function readFile(
+ path: string | URL,
+ options: null | undefined | FileOptionsArgument,
+ callback: BinaryCallback,
+): void;
+export function readFile(path: string | URL, callback: BinaryCallback): void;
+export function readFile(
+ path: string | URL,
+ optOrCallback?: FileOptionsArgument | Callback | null | undefined,
+ callback?: Callback,
+) {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+ let cb: Callback | undefined;
+ if (typeof optOrCallback === "function") {
+ cb = optOrCallback;
+ } else {
+ cb = callback;
+ }
+
+ const encoding = getEncoding(optOrCallback);
+
+ const p = Deno.readFile(path);
+
+ if (cb) {
+ p.then((data: Uint8Array) => {
+ if (encoding && encoding !== "binary") {
+ const text = maybeDecode(data, encoding);
+ return (cb as TextCallback)(null, text);
+ }
+ const buffer = maybeDecode(data, encoding);
+ (cb as BinaryCallback)(null, buffer);
+ }, (err) => cb && cb(err));
+ }
+}
+
+export const readFilePromise = promisify(readFile) as (
+ & ((path: string | URL, opt: TextOptionsArgument) => Promise<string>)
+ & ((path: string | URL, opt?: BinaryOptionsArgument) => Promise<Buffer>)
+ & ((path: string | URL, opt?: FileOptionsArgument) => Promise<Buffer>)
+);
+
+export function readFileSync(
+ path: string | URL,
+ opt: TextOptionsArgument,
+): string;
+export function readFileSync(
+ path: string | URL,
+ opt?: BinaryOptionsArgument,
+): Buffer;
+export function readFileSync(
+ path: string | URL,
+ opt?: FileOptionsArgument,
+): string | Buffer {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+ const data = Deno.readFileSync(path);
+ const encoding = getEncoding(opt);
+ if (encoding && encoding !== "binary") {
+ const text = maybeDecode(data, encoding);
+ return text;
+ }
+ const buffer = maybeDecode(data, encoding);
+ return buffer;
+}
diff --git a/ext/node/polyfills/_fs/_fs_readdir.ts b/ext/node/polyfills/_fs/_fs_readdir.ts
new file mode 100644
index 000000000..f6cfae4f7
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_readdir.ts
@@ -0,0 +1,142 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import {
+ TextDecoder,
+ TextEncoder,
+} from "internal:deno_web/08_text_encoding.js";
+import { asyncIterableToCallback } from "internal:deno_node/polyfills/_fs/_fs_watch.ts";
+import Dirent from "internal:deno_node/polyfills/_fs/_fs_dirent.ts";
+import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts";
+import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+function toDirent(val: Deno.DirEntry): Dirent {
+ return new Dirent(val);
+}
+
+type readDirOptions = {
+ encoding?: string;
+ withFileTypes?: boolean;
+};
+
+type readDirCallback = (err: Error | null, files: string[]) => void;
+
+type readDirCallbackDirent = (err: Error | null, files: Dirent[]) => void;
+
+type readDirBoth = (
+ ...args: [Error] | [null, string[] | Dirent[] | Array<string | Dirent>]
+) => void;
+
+export function readdir(
+ path: string | Buffer | URL,
+ options: { withFileTypes?: false; encoding?: string },
+ callback: readDirCallback,
+): void;
+export function readdir(
+ path: string | Buffer | URL,
+ options: { withFileTypes: true; encoding?: string },
+ callback: readDirCallbackDirent,
+): void;
+export function readdir(path: string | URL, callback: readDirCallback): void;
+export function readdir(
+ path: string | Buffer | URL,
+ optionsOrCallback: readDirOptions | readDirCallback | readDirCallbackDirent,
+ maybeCallback?: readDirCallback | readDirCallbackDirent,
+) {
+ const callback =
+ (typeof optionsOrCallback === "function"
+ ? optionsOrCallback
+ : maybeCallback) as readDirBoth | undefined;
+ const options = typeof optionsOrCallback === "object"
+ ? optionsOrCallback
+ : null;
+ const result: Array<string | Dirent> = [];
+ path = getValidatedPath(path);
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ if (options?.encoding) {
+ try {
+ new TextDecoder(options.encoding);
+ } catch {
+ throw new Error(
+ `TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`,
+ );
+ }
+ }
+
+ try {
+ asyncIterableToCallback(Deno.readDir(path.toString()), (val, done) => {
+ if (typeof path !== "string") return;
+ if (done) {
+ callback(null, result);
+ return;
+ }
+ if (options?.withFileTypes) {
+ result.push(toDirent(val));
+ } else result.push(decode(val.name));
+ }, (e) => {
+ callback(denoErrorToNodeError(e as Error, { syscall: "readdir" }));
+ });
+ } catch (e) {
+ callback(denoErrorToNodeError(e as Error, { syscall: "readdir" }));
+ }
+}
+
+function decode(str: string, encoding?: string): string {
+ if (!encoding) return str;
+ else {
+ const decoder = new TextDecoder(encoding);
+ const encoder = new TextEncoder();
+ return decoder.decode(encoder.encode(str));
+ }
+}
+
+export const readdirPromise = promisify(readdir) as (
+ & ((path: string | Buffer | URL, options: {
+ withFileTypes: true;
+ encoding?: string;
+ }) => Promise<Dirent[]>)
+ & ((path: string | Buffer | URL, options?: {
+ withFileTypes?: false;
+ encoding?: string;
+ }) => Promise<string[]>)
+);
+
+export function readdirSync(
+ path: string | Buffer | URL,
+ options: { withFileTypes: true; encoding?: string },
+): Dirent[];
+export function readdirSync(
+ path: string | Buffer | URL,
+ options?: { withFileTypes?: false; encoding?: string },
+): string[];
+export function readdirSync(
+ path: string | Buffer | URL,
+ options?: readDirOptions,
+): Array<string | Dirent> {
+ const result = [];
+ path = getValidatedPath(path);
+
+ if (options?.encoding) {
+ try {
+ new TextDecoder(options.encoding);
+ } catch {
+ throw new Error(
+ `TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`,
+ );
+ }
+ }
+
+ try {
+ for (const file of Deno.readDirSync(path.toString())) {
+ if (options?.withFileTypes) {
+ result.push(toDirent(file));
+ } else result.push(decode(file.name));
+ }
+ } catch (e) {
+ throw denoErrorToNodeError(e as Error, { syscall: "readdir" });
+ }
+ return result;
+}
diff --git a/ext/node/polyfills/_fs/_fs_readlink.ts b/ext/node/polyfills/_fs/_fs_readlink.ts
new file mode 100644
index 000000000..07d1b6f6f
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_readlink.ts
@@ -0,0 +1,89 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import { TextEncoder } from "internal:deno_web/08_text_encoding.js";
+import {
+ intoCallbackAPIWithIntercept,
+ MaybeEmpty,
+ notImplemented,
+} from "internal:deno_node/polyfills/_utils.ts";
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+type ReadlinkCallback = (
+ err: MaybeEmpty<Error>,
+ linkString: MaybeEmpty<string | Uint8Array>,
+) => void;
+
+interface ReadlinkOptions {
+ encoding?: string | null;
+}
+
+function maybeEncode(
+ data: string,
+ encoding: string | null,
+): string | Uint8Array {
+ if (encoding === "buffer") {
+ return new TextEncoder().encode(data);
+ }
+ return data;
+}
+
+function getEncoding(
+ optOrCallback?: ReadlinkOptions | ReadlinkCallback,
+): string | null {
+ if (!optOrCallback || typeof optOrCallback === "function") {
+ return null;
+ } else {
+ if (optOrCallback.encoding) {
+ if (
+ optOrCallback.encoding === "utf8" ||
+ optOrCallback.encoding === "utf-8"
+ ) {
+ return "utf8";
+ } else if (optOrCallback.encoding === "buffer") {
+ return "buffer";
+ } else {
+ notImplemented(`fs.readlink encoding=${optOrCallback.encoding}`);
+ }
+ }
+ return null;
+ }
+}
+
+export function readlink(
+ path: string | URL,
+ optOrCallback: ReadlinkCallback | ReadlinkOptions,
+ callback?: ReadlinkCallback,
+) {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ let cb: ReadlinkCallback | undefined;
+ if (typeof optOrCallback === "function") {
+ cb = optOrCallback;
+ } else {
+ cb = callback;
+ }
+
+ const encoding = getEncoding(optOrCallback);
+
+ intoCallbackAPIWithIntercept<string, Uint8Array | string>(
+ Deno.readLink,
+ (data: string): string | Uint8Array => maybeEncode(data, encoding),
+ cb,
+ path,
+ );
+}
+
+export const readlinkPromise = promisify(readlink) as (
+ path: string | URL,
+ opt?: ReadlinkOptions,
+) => Promise<string | Uint8Array>;
+
+export function readlinkSync(
+ path: string | URL,
+ opt?: ReadlinkOptions,
+): string | Uint8Array {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ return maybeEncode(Deno.readLinkSync(path), getEncoding(opt));
+}
diff --git a/ext/node/polyfills/_fs/_fs_realpath.ts b/ext/node/polyfills/_fs/_fs_realpath.ts
new file mode 100644
index 000000000..5892b2c0f
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_realpath.ts
@@ -0,0 +1,35 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+type Options = { encoding: string };
+type Callback = (err: Error | null, path?: string) => void;
+
+export function realpath(
+ path: string,
+ options?: Options | Callback,
+ callback?: Callback,
+) {
+ if (typeof options === "function") {
+ callback = options;
+ }
+ if (!callback) {
+ throw new Error("No callback function supplied");
+ }
+ Deno.realPath(path).then(
+ (path) => callback!(null, path),
+ (err) => callback!(err),
+ );
+}
+
+realpath.native = realpath;
+
+export const realpathPromise = promisify(realpath) as (
+ path: string,
+ options?: Options,
+) => Promise<string>;
+
+export function realpathSync(path: string): string {
+ return Deno.realPathSync(path);
+}
+
+realpathSync.native = realpathSync;
diff --git a/ext/node/polyfills/_fs/_fs_rename.ts b/ext/node/polyfills/_fs/_fs_rename.ts
new file mode 100644
index 000000000..3f8b5bd7e
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_rename.ts
@@ -0,0 +1,28 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export function rename(
+ oldPath: string | URL,
+ newPath: string | URL,
+ callback: (err?: Error) => void,
+) {
+ oldPath = oldPath instanceof URL ? fromFileUrl(oldPath) : oldPath;
+ newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.rename(oldPath, newPath).then((_) => callback(), callback);
+}
+
+export const renamePromise = promisify(rename) as (
+ oldPath: string | URL,
+ newPath: string | URL,
+) => Promise<void>;
+
+export function renameSync(oldPath: string | URL, newPath: string | URL) {
+ oldPath = oldPath instanceof URL ? fromFileUrl(oldPath) : oldPath;
+ newPath = newPath instanceof URL ? fromFileUrl(newPath) : newPath;
+
+ Deno.renameSync(oldPath, newPath);
+}
diff --git a/ext/node/polyfills/_fs/_fs_rm.ts b/ext/node/polyfills/_fs/_fs_rm.ts
new file mode 100644
index 000000000..80ba0b5f8
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_rm.ts
@@ -0,0 +1,81 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ validateRmOptions,
+ validateRmOptionsSync,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+type rmOptions = {
+ force?: boolean;
+ maxRetries?: number;
+ recursive?: boolean;
+ retryDelay?: number;
+};
+
+type rmCallback = (err: Error | null) => void;
+
+export function rm(path: string | URL, callback: rmCallback): void;
+export function rm(
+ path: string | URL,
+ options: rmOptions,
+ callback: rmCallback,
+): void;
+export function rm(
+ path: string | URL,
+ optionsOrCallback: rmOptions | rmCallback,
+ maybeCallback?: rmCallback,
+) {
+ const callback = typeof optionsOrCallback === "function"
+ ? optionsOrCallback
+ : maybeCallback;
+ const options = typeof optionsOrCallback === "object"
+ ? optionsOrCallback
+ : undefined;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ validateRmOptions(
+ path,
+ options,
+ false,
+ (err: Error | null, options: rmOptions) => {
+ if (err) {
+ return callback(err);
+ }
+ Deno.remove(path, { recursive: options?.recursive })
+ .then((_) => callback(null), (err: unknown) => {
+ if (options?.force && err instanceof Deno.errors.NotFound) {
+ callback(null);
+ } else {
+ callback(
+ err instanceof Error
+ ? denoErrorToNodeError(err, { syscall: "rm" })
+ : err,
+ );
+ }
+ });
+ },
+ );
+}
+
+export const rmPromise = promisify(rm) as (
+ path: string | URL,
+ options?: rmOptions,
+) => Promise<void>;
+
+export function rmSync(path: string | URL, options?: rmOptions) {
+ options = validateRmOptionsSync(path, options, false);
+ try {
+ Deno.removeSync(path, { recursive: options?.recursive });
+ } catch (err: unknown) {
+ if (options?.force && err instanceof Deno.errors.NotFound) {
+ return;
+ }
+ if (err instanceof Error) {
+ throw denoErrorToNodeError(err, { syscall: "stat" });
+ } else {
+ throw err;
+ }
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_rmdir.ts b/ext/node/polyfills/_fs/_fs_rmdir.ts
new file mode 100644
index 000000000..ba753a743
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_rmdir.ts
@@ -0,0 +1,108 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import {
+ emitRecursiveRmdirWarning,
+ getValidatedPath,
+ validateRmdirOptions,
+ validateRmOptions,
+ validateRmOptionsSync,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { toNamespacedPath } from "internal:deno_node/polyfills/path.ts";
+import {
+ denoErrorToNodeError,
+ ERR_FS_RMDIR_ENOTDIR,
+} from "internal:deno_node/polyfills/internal/errors.ts";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+type rmdirOptions = {
+ maxRetries?: number;
+ recursive?: boolean;
+ retryDelay?: number;
+};
+
+type rmdirCallback = (err?: Error) => void;
+
+export function rmdir(path: string | URL, callback: rmdirCallback): void;
+export function rmdir(
+ path: string | URL,
+ options: rmdirOptions,
+ callback: rmdirCallback,
+): void;
+export function rmdir(
+ path: string | URL,
+ optionsOrCallback: rmdirOptions | rmdirCallback,
+ maybeCallback?: rmdirCallback,
+) {
+ path = toNamespacedPath(getValidatedPath(path) as string);
+
+ const callback = typeof optionsOrCallback === "function"
+ ? optionsOrCallback
+ : maybeCallback;
+ const options = typeof optionsOrCallback === "object"
+ ? optionsOrCallback
+ : undefined;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ if (options?.recursive) {
+ emitRecursiveRmdirWarning();
+ validateRmOptions(
+ path,
+ { ...options, force: false },
+ true,
+ (err: Error | null | false, options: rmdirOptions) => {
+ if (err === false) {
+ return callback(new ERR_FS_RMDIR_ENOTDIR(path.toString()));
+ }
+ if (err) {
+ return callback(err);
+ }
+
+ Deno.remove(path, { recursive: options?.recursive })
+ .then((_) => callback(), callback);
+ },
+ );
+ } else {
+ validateRmdirOptions(options);
+ Deno.remove(path, { recursive: options?.recursive })
+ .then((_) => callback(), (err: unknown) => {
+ callback(
+ err instanceof Error
+ ? denoErrorToNodeError(err, { syscall: "rmdir" })
+ : err,
+ );
+ });
+ }
+}
+
+export const rmdirPromise = promisify(rmdir) as (
+ path: string | Buffer | URL,
+ options?: rmdirOptions,
+) => Promise<void>;
+
+export function rmdirSync(path: string | Buffer | URL, options?: rmdirOptions) {
+ path = getValidatedPath(path);
+ if (options?.recursive) {
+ emitRecursiveRmdirWarning();
+ const optionsOrFalse: rmdirOptions | false = validateRmOptionsSync(path, {
+ ...options,
+ force: false,
+ }, true);
+ if (optionsOrFalse === false) {
+ throw new ERR_FS_RMDIR_ENOTDIR(path.toString());
+ }
+ options = optionsOrFalse;
+ } else {
+ validateRmdirOptions(options);
+ }
+
+ try {
+ Deno.removeSync(toNamespacedPath(path as string), {
+ recursive: options?.recursive,
+ });
+ } catch (err: unknown) {
+ throw (err instanceof Error
+ ? denoErrorToNodeError(err, { syscall: "rmdir" })
+ : err);
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_stat.ts b/ext/node/polyfills/_fs/_fs_stat.ts
new file mode 100644
index 000000000..3a006084d
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_stat.ts
@@ -0,0 +1,314 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { denoErrorToNodeError } from "internal:deno_node/polyfills/internal/errors.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export type statOptions = {
+ bigint: boolean;
+ throwIfNoEntry?: boolean;
+};
+
+export type Stats = {
+ /** ID of the device containing the file.
+ *
+ * _Linux/Mac OS only._ */
+ dev: number | null;
+ /** Inode number.
+ *
+ * _Linux/Mac OS only._ */
+ ino: number | null;
+ /** **UNSTABLE**: Match behavior with Go on Windows for `mode`.
+ *
+ * The underlying raw `st_mode` bits that contain the standard Unix
+ * permissions for this file/directory. */
+ mode: number | null;
+ /** Number of hard links pointing to this file.
+ *
+ * _Linux/Mac OS only._ */
+ nlink: number | null;
+ /** User ID of the owner of this file.
+ *
+ * _Linux/Mac OS only._ */
+ uid: number | null;
+ /** Group ID of the owner of this file.
+ *
+ * _Linux/Mac OS only._ */
+ gid: number | null;
+ /** Device ID of this file.
+ *
+ * _Linux/Mac OS only._ */
+ rdev: number | null;
+ /** The size of the file, in bytes. */
+ size: number;
+ /** Blocksize for filesystem I/O.
+ *
+ * _Linux/Mac OS only._ */
+ blksize: number | null;
+ /** Number of blocks allocated to the file, in 512-byte units.
+ *
+ * _Linux/Mac OS only._ */
+ blocks: number | null;
+ /** The last modification time of the file. This corresponds to the `mtime`
+ * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This
+ * may not be available on all platforms. */
+ mtime: Date | null;
+ /** The last access time of the file. This corresponds to the `atime`
+ * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
+ * be available on all platforms. */
+ atime: Date | null;
+ /** The creation time of the file. This corresponds to the `birthtime`
+ * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may
+ * not be available on all platforms. */
+ birthtime: Date | null;
+ /** change time */
+ ctime: Date | null;
+ /** atime in milliseconds */
+ atimeMs: number | null;
+ /** atime in milliseconds */
+ mtimeMs: number | null;
+ /** atime in milliseconds */
+ ctimeMs: number | null;
+ /** atime in milliseconds */
+ birthtimeMs: number | null;
+ isBlockDevice: () => boolean;
+ isCharacterDevice: () => boolean;
+ isDirectory: () => boolean;
+ isFIFO: () => boolean;
+ isFile: () => boolean;
+ isSocket: () => boolean;
+ isSymbolicLink: () => boolean;
+};
+
+export type BigIntStats = {
+ /** ID of the device containing the file.
+ *
+ * _Linux/Mac OS only._ */
+ dev: bigint | null;
+ /** Inode number.
+ *
+ * _Linux/Mac OS only._ */
+ ino: bigint | null;
+ /** **UNSTABLE**: Match behavior with Go on Windows for `mode`.
+ *
+ * The underlying raw `st_mode` bits that contain the standard Unix
+ * permissions for this file/directory. */
+ mode: bigint | null;
+ /** Number of hard links pointing to this file.
+ *
+ * _Linux/Mac OS only._ */
+ nlink: bigint | null;
+ /** User ID of the owner of this file.
+ *
+ * _Linux/Mac OS only._ */
+ uid: bigint | null;
+ /** Group ID of the owner of this file.
+ *
+ * _Linux/Mac OS only._ */
+ gid: bigint | null;
+ /** Device ID of this file.
+ *
+ * _Linux/Mac OS only._ */
+ rdev: bigint | null;
+ /** The size of the file, in bytes. */
+ size: bigint;
+ /** Blocksize for filesystem I/O.
+ *
+ * _Linux/Mac OS only._ */
+ blksize: bigint | null;
+ /** Number of blocks allocated to the file, in 512-byte units.
+ *
+ * _Linux/Mac OS only._ */
+ blocks: bigint | null;
+ /** The last modification time of the file. This corresponds to the `mtime`
+ * field from `stat` on Linux/Mac OS and `ftLastWriteTime` on Windows. This
+ * may not be available on all platforms. */
+ mtime: Date | null;
+ /** The last access time of the file. This corresponds to the `atime`
+ * field from `stat` on Unix and `ftLastAccessTime` on Windows. This may not
+ * be available on all platforms. */
+ atime: Date | null;
+ /** The creation time of the file. This corresponds to the `birthtime`
+ * field from `stat` on Mac/BSD and `ftCreationTime` on Windows. This may
+ * not be available on all platforms. */
+ birthtime: Date | null;
+ /** change time */
+ ctime: Date | null;
+ /** atime in milliseconds */
+ atimeMs: bigint | null;
+ /** atime in milliseconds */
+ mtimeMs: bigint | null;
+ /** atime in milliseconds */
+ ctimeMs: bigint | null;
+ /** atime in nanoseconds */
+ birthtimeMs: bigint | null;
+ /** atime in nanoseconds */
+ atimeNs: bigint | null;
+ /** atime in nanoseconds */
+ mtimeNs: bigint | null;
+ /** atime in nanoseconds */
+ ctimeNs: bigint | null;
+ /** atime in nanoseconds */
+ birthtimeNs: bigint | null;
+ isBlockDevice: () => boolean;
+ isCharacterDevice: () => boolean;
+ isDirectory: () => boolean;
+ isFIFO: () => boolean;
+ isFile: () => boolean;
+ isSocket: () => boolean;
+ isSymbolicLink: () => boolean;
+};
+
+export function convertFileInfoToStats(origin: Deno.FileInfo): Stats {
+ return {
+ dev: origin.dev,
+ ino: origin.ino,
+ mode: origin.mode,
+ nlink: origin.nlink,
+ uid: origin.uid,
+ gid: origin.gid,
+ rdev: origin.rdev,
+ size: origin.size,
+ blksize: origin.blksize,
+ blocks: origin.blocks,
+ mtime: origin.mtime,
+ atime: origin.atime,
+ birthtime: origin.birthtime,
+ mtimeMs: origin.mtime?.getTime() || null,
+ atimeMs: origin.atime?.getTime() || null,
+ birthtimeMs: origin.birthtime?.getTime() || null,
+ isFile: () => origin.isFile,
+ isDirectory: () => origin.isDirectory,
+ isSymbolicLink: () => origin.isSymlink,
+ // not sure about those
+ isBlockDevice: () => false,
+ isFIFO: () => false,
+ isCharacterDevice: () => false,
+ isSocket: () => false,
+ ctime: origin.mtime,
+ ctimeMs: origin.mtime?.getTime() || null,
+ };
+}
+
+function toBigInt(number?: number | null) {
+ if (number === null || number === undefined) return null;
+ return BigInt(number);
+}
+
+export function convertFileInfoToBigIntStats(
+ origin: Deno.FileInfo,
+): BigIntStats {
+ return {
+ dev: toBigInt(origin.dev),
+ ino: toBigInt(origin.ino),
+ mode: toBigInt(origin.mode),
+ nlink: toBigInt(origin.nlink),
+ uid: toBigInt(origin.uid),
+ gid: toBigInt(origin.gid),
+ rdev: toBigInt(origin.rdev),
+ size: toBigInt(origin.size) || 0n,
+ blksize: toBigInt(origin.blksize),
+ blocks: toBigInt(origin.blocks),
+ mtime: origin.mtime,
+ atime: origin.atime,
+ birthtime: origin.birthtime,
+ mtimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null,
+ atimeMs: origin.atime ? BigInt(origin.atime.getTime()) : null,
+ birthtimeMs: origin.birthtime ? BigInt(origin.birthtime.getTime()) : null,
+ mtimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null,
+ atimeNs: origin.atime ? BigInt(origin.atime.getTime()) * 1000000n : null,
+ birthtimeNs: origin.birthtime
+ ? BigInt(origin.birthtime.getTime()) * 1000000n
+ : null,
+ isFile: () => origin.isFile,
+ isDirectory: () => origin.isDirectory,
+ isSymbolicLink: () => origin.isSymlink,
+ // not sure about those
+ isBlockDevice: () => false,
+ isFIFO: () => false,
+ isCharacterDevice: () => false,
+ isSocket: () => false,
+ ctime: origin.mtime,
+ ctimeMs: origin.mtime ? BigInt(origin.mtime.getTime()) : null,
+ ctimeNs: origin.mtime ? BigInt(origin.mtime.getTime()) * 1000000n : null,
+ };
+}
+
+// shortcut for Convert File Info to Stats or BigIntStats
+export function CFISBIS(fileInfo: Deno.FileInfo, bigInt: boolean) {
+ if (bigInt) return convertFileInfoToBigIntStats(fileInfo);
+ return convertFileInfoToStats(fileInfo);
+}
+
+export type statCallbackBigInt = (err: Error | null, stat: BigIntStats) => void;
+
+export type statCallback = (err: Error | null, stat: Stats) => void;
+
+export function stat(path: string | URL, callback: statCallback): void;
+export function stat(
+ path: string | URL,
+ options: { bigint: false },
+ callback: statCallback,
+): void;
+export function stat(
+ path: string | URL,
+ options: { bigint: true },
+ callback: statCallbackBigInt,
+): void;
+export function stat(
+ path: string | URL,
+ optionsOrCallback: statCallback | statCallbackBigInt | statOptions,
+ maybeCallback?: statCallback | statCallbackBigInt,
+) {
+ const callback =
+ (typeof optionsOrCallback === "function"
+ ? optionsOrCallback
+ : maybeCallback) as (
+ ...args: [Error] | [null, BigIntStats | Stats]
+ ) => void;
+ const options = typeof optionsOrCallback === "object"
+ ? optionsOrCallback
+ : { bigint: false };
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.stat(path).then(
+ (stat) => callback(null, CFISBIS(stat, options.bigint)),
+ (err) => callback(denoErrorToNodeError(err, { syscall: "stat" })),
+ );
+}
+
+export const statPromise = promisify(stat) as (
+ & ((path: string | URL) => Promise<Stats>)
+ & ((path: string | URL, options: { bigint: false }) => Promise<Stats>)
+ & ((path: string | URL, options: { bigint: true }) => Promise<BigIntStats>)
+);
+
+export function statSync(path: string | URL): Stats;
+export function statSync(
+ path: string | URL,
+ options: { bigint: false; throwIfNoEntry?: boolean },
+): Stats;
+export function statSync(
+ path: string | URL,
+ options: { bigint: true; throwIfNoEntry?: boolean },
+): BigIntStats;
+export function statSync(
+ path: string | URL,
+ options: statOptions = { bigint: false, throwIfNoEntry: true },
+): Stats | BigIntStats | undefined {
+ try {
+ const origin = Deno.statSync(path);
+ return CFISBIS(origin, options.bigint);
+ } catch (err) {
+ if (
+ options?.throwIfNoEntry === false &&
+ err instanceof Deno.errors.NotFound
+ ) {
+ return;
+ }
+ if (err instanceof Error) {
+ throw denoErrorToNodeError(err, { syscall: "stat" });
+ } else {
+ throw err;
+ }
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_symlink.ts b/ext/node/polyfills/_fs/_fs_symlink.ts
new file mode 100644
index 000000000..c8652885f
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_symlink.ts
@@ -0,0 +1,46 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+type SymlinkType = "file" | "dir";
+
+export function symlink(
+ target: string | URL,
+ path: string | URL,
+ typeOrCallback: SymlinkType | CallbackWithError,
+ maybeCallback?: CallbackWithError,
+) {
+ target = target instanceof URL ? fromFileUrl(target) : target;
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ const type: SymlinkType = typeof typeOrCallback === "string"
+ ? typeOrCallback
+ : "file";
+
+ const callback: CallbackWithError = typeof typeOrCallback === "function"
+ ? typeOrCallback
+ : (maybeCallback as CallbackWithError);
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.symlink(target, path, { type }).then(() => callback(null), callback);
+}
+
+export const symlinkPromise = promisify(symlink) as (
+ target: string | URL,
+ path: string | URL,
+ type?: SymlinkType,
+) => Promise<void>;
+
+export function symlinkSync(
+ target: string | URL,
+ path: string | URL,
+ type?: SymlinkType,
+) {
+ target = target instanceof URL ? fromFileUrl(target) : target;
+ path = path instanceof URL ? fromFileUrl(path) : path;
+ type = type || "file";
+
+ Deno.symlinkSync(target, path, { type });
+}
diff --git a/ext/node/polyfills/_fs/_fs_truncate.ts b/ext/node/polyfills/_fs/_fs_truncate.ts
new file mode 100644
index 000000000..105555abc
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_truncate.ts
@@ -0,0 +1,33 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export function truncate(
+ path: string | URL,
+ lenOrCallback: number | CallbackWithError,
+ maybeCallback?: CallbackWithError,
+) {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+ const len: number | undefined = typeof lenOrCallback === "number"
+ ? lenOrCallback
+ : undefined;
+ const callback: CallbackWithError = typeof lenOrCallback === "function"
+ ? lenOrCallback
+ : maybeCallback as CallbackWithError;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.truncate(path, len).then(() => callback(null), callback);
+}
+
+export const truncatePromise = promisify(truncate) as (
+ path: string | URL,
+ len?: number,
+) => Promise<void>;
+
+export function truncateSync(path: string | URL, len?: number) {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ Deno.truncateSync(path, len);
+}
diff --git a/ext/node/polyfills/_fs/_fs_unlink.ts b/ext/node/polyfills/_fs/_fs_unlink.ts
new file mode 100644
index 000000000..ed43bb1b3
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_unlink.ts
@@ -0,0 +1,15 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+export function unlink(path: string | URL, callback: (err?: Error) => void) {
+ if (!callback) throw new Error("No callback function supplied");
+ Deno.remove(path).then((_) => callback(), callback);
+}
+
+export const unlinkPromise = promisify(unlink) as (
+ path: string | URL,
+) => Promise<void>;
+
+export function unlinkSync(path: string | URL) {
+ Deno.removeSync(path);
+}
diff --git a/ext/node/polyfills/_fs/_fs_utimes.ts b/ext/node/polyfills/_fs/_fs_utimes.ts
new file mode 100644
index 000000000..7423a1060
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_utimes.ts
@@ -0,0 +1,61 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+
+import type { CallbackWithError } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+function getValidTime(
+ time: number | string | Date,
+ name: string,
+): number | Date {
+ if (typeof time === "string") {
+ time = Number(time);
+ }
+
+ if (
+ typeof time === "number" &&
+ (Number.isNaN(time) || !Number.isFinite(time))
+ ) {
+ throw new Deno.errors.InvalidData(
+ `invalid ${name}, must not be infinity or NaN`,
+ );
+ }
+
+ return time;
+}
+
+export function utimes(
+ path: string | URL,
+ atime: number | string | Date,
+ mtime: number | string | Date,
+ callback: CallbackWithError,
+) {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ if (!callback) {
+ throw new Deno.errors.InvalidData("No callback function supplied");
+ }
+
+ atime = getValidTime(atime, "atime");
+ mtime = getValidTime(mtime, "mtime");
+
+ Deno.utime(path, atime, mtime).then(() => callback(null), callback);
+}
+
+export const utimesPromise = promisify(utimes) as (
+ path: string | URL,
+ atime: number | string | Date,
+ mtime: number | string | Date,
+) => Promise<void>;
+
+export function utimesSync(
+ path: string | URL,
+ atime: number | string | Date,
+ mtime: number | string | Date,
+) {
+ path = path instanceof URL ? fromFileUrl(path) : path;
+ atime = getValidTime(atime, "atime");
+ mtime = getValidTime(mtime, "mtime");
+
+ Deno.utimeSync(path, atime, mtime);
+}
diff --git a/ext/node/polyfills/_fs/_fs_watch.ts b/ext/node/polyfills/_fs/_fs_watch.ts
new file mode 100644
index 000000000..79f226126
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_watch.ts
@@ -0,0 +1,346 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { basename } from "internal:deno_node/polyfills/path.ts";
+import { EventEmitter } from "internal:deno_node/polyfills/events.ts";
+import { notImplemented } from "internal:deno_node/polyfills/_utils.ts";
+import { promisify } from "internal:deno_node/polyfills/util.ts";
+import { getValidatedPath } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { validateFunction } from "internal:deno_node/polyfills/internal/validators.mjs";
+import { stat, Stats } from "internal:deno_node/polyfills/_fs/_fs_stat.ts";
+import { Stats as StatsClass } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { delay } from "internal:deno_node/polyfills/_util/async.ts";
+
+const statPromisified = promisify(stat);
+const statAsync = async (filename: string): Promise<Stats | null> => {
+ try {
+ return await statPromisified(filename);
+ } catch {
+ return emptyStats;
+ }
+};
+const emptyStats = new StatsClass(
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ Date.UTC(1970, 0, 1, 0, 0, 0),
+ Date.UTC(1970, 0, 1, 0, 0, 0),
+ Date.UTC(1970, 0, 1, 0, 0, 0),
+ Date.UTC(1970, 0, 1, 0, 0, 0),
+) as unknown as Stats;
+
+export function asyncIterableIteratorToCallback<T>(
+ iterator: AsyncIterableIterator<T>,
+ callback: (val: T, done?: boolean) => void,
+) {
+ function next() {
+ iterator.next().then((obj) => {
+ if (obj.done) {
+ callback(obj.value, true);
+ return;
+ }
+ callback(obj.value);
+ next();
+ });
+ }
+ next();
+}
+
+export function asyncIterableToCallback<T>(
+ iter: AsyncIterable<T>,
+ callback: (val: T, done?: boolean) => void,
+ errCallback: (e: unknown) => void,
+) {
+ const iterator = iter[Symbol.asyncIterator]();
+ function next() {
+ iterator.next().then((obj) => {
+ if (obj.done) {
+ callback(obj.value, true);
+ return;
+ }
+ callback(obj.value);
+ next();
+ }, errCallback);
+ }
+ next();
+}
+
+type watchOptions = {
+ persistent?: boolean;
+ recursive?: boolean;
+ encoding?: string;
+};
+
+type watchListener = (eventType: string, filename: string) => void;
+
+export function watch(
+ filename: string | URL,
+ options: watchOptions,
+ listener: watchListener,
+): FSWatcher;
+export function watch(
+ filename: string | URL,
+ listener: watchListener,
+): FSWatcher;
+export function watch(
+ filename: string | URL,
+ options: watchOptions,
+): FSWatcher;
+export function watch(filename: string | URL): FSWatcher;
+export function watch(
+ filename: string | URL,
+ optionsOrListener?: watchOptions | watchListener,
+ optionsOrListener2?: watchOptions | watchListener,
+) {
+ const listener = typeof optionsOrListener === "function"
+ ? optionsOrListener
+ : typeof optionsOrListener2 === "function"
+ ? optionsOrListener2
+ : undefined;
+ const options = typeof optionsOrListener === "object"
+ ? optionsOrListener
+ : typeof optionsOrListener2 === "object"
+ ? optionsOrListener2
+ : undefined;
+
+ const watchPath = getValidatedPath(filename).toString();
+
+ let iterator: Deno.FsWatcher;
+ // Start the actual watcher a few msec later to avoid race condition
+ // error in test case in compat test case
+ // (parallel/test-fs-watch.js, parallel/test-fs-watchfile.js)
+ const timer = setTimeout(() => {
+ iterator = Deno.watchFs(watchPath, {
+ recursive: options?.recursive || false,
+ });
+
+ asyncIterableToCallback<Deno.FsEvent>(iterator, (val, done) => {
+ if (done) return;
+ fsWatcher.emit(
+ "change",
+ convertDenoFsEventToNodeFsEvent(val.kind),
+ basename(val.paths[0]),
+ );
+ }, (e) => {
+ fsWatcher.emit("error", e);
+ });
+ }, 5);
+
+ const fsWatcher = new FSWatcher(() => {
+ clearTimeout(timer);
+ try {
+ iterator?.close();
+ } catch (e) {
+ if (e instanceof Deno.errors.BadResource) {
+ // already closed
+ return;
+ }
+ throw e;
+ }
+ });
+
+ if (listener) {
+ fsWatcher.on("change", listener.bind({ _handle: fsWatcher }));
+ }
+
+ return fsWatcher;
+}
+
+export const watchPromise = promisify(watch) as (
+ & ((
+ filename: string | URL,
+ options: watchOptions,
+ listener: watchListener,
+ ) => Promise<FSWatcher>)
+ & ((
+ filename: string | URL,
+ listener: watchListener,
+ ) => Promise<FSWatcher>)
+ & ((
+ filename: string | URL,
+ options: watchOptions,
+ ) => Promise<FSWatcher>)
+ & ((filename: string | URL) => Promise<FSWatcher>)
+);
+
+type WatchFileListener = (curr: Stats, prev: Stats) => void;
+type WatchFileOptions = {
+ bigint?: boolean;
+ persistent?: boolean;
+ interval?: number;
+};
+
+export function watchFile(
+ filename: string | Buffer | URL,
+ listener: WatchFileListener,
+): StatWatcher;
+export function watchFile(
+ filename: string | Buffer | URL,
+ options: WatchFileOptions,
+ listener: WatchFileListener,
+): StatWatcher;
+export function watchFile(
+ filename: string | Buffer | URL,
+ listenerOrOptions: WatchFileListener | WatchFileOptions,
+ listener?: WatchFileListener,
+): StatWatcher {
+ const watchPath = getValidatedPath(filename).toString();
+ const handler = typeof listenerOrOptions === "function"
+ ? listenerOrOptions
+ : listener!;
+ validateFunction(handler, "listener");
+ const {
+ bigint = false,
+ persistent = true,
+ interval = 5007,
+ } = typeof listenerOrOptions === "object" ? listenerOrOptions : {};
+
+ let stat = statWatchers.get(watchPath);
+ if (stat === undefined) {
+ stat = new StatWatcher(bigint);
+ stat[kFSStatWatcherStart](watchPath, persistent, interval);
+ statWatchers.set(watchPath, stat);
+ }
+
+ stat.addListener("change", listener!);
+ return stat;
+}
+
+export function unwatchFile(
+ filename: string | Buffer | URL,
+ listener?: WatchFileListener,
+) {
+ const watchPath = getValidatedPath(filename).toString();
+ const stat = statWatchers.get(watchPath);
+
+ if (!stat) {
+ return;
+ }
+
+ if (typeof listener === "function") {
+ const beforeListenerCount = stat.listenerCount("change");
+ stat.removeListener("change", listener);
+ if (stat.listenerCount("change") < beforeListenerCount) {
+ stat[kFSStatWatcherAddOrCleanRef]("clean");
+ }
+ } else {
+ stat.removeAllListeners("change");
+ stat[kFSStatWatcherAddOrCleanRef]("cleanAll");
+ }
+
+ if (stat.listenerCount("change") === 0) {
+ stat.stop();
+ statWatchers.delete(watchPath);
+ }
+}
+
+const statWatchers = new Map<string, StatWatcher>();
+
+const kFSStatWatcherStart = Symbol("kFSStatWatcherStart");
+const kFSStatWatcherAddOrCleanRef = Symbol("kFSStatWatcherAddOrCleanRef");
+
+class StatWatcher extends EventEmitter {
+ #bigint: boolean;
+ #refCount = 0;
+ #abortController = new AbortController();
+ constructor(bigint: boolean) {
+ super();
+ this.#bigint = bigint;
+ }
+ [kFSStatWatcherStart](
+ filename: string,
+ persistent: boolean,
+ interval: number,
+ ) {
+ if (persistent) {
+ this.#refCount++;
+ }
+
+ (async () => {
+ let prev = await statAsync(filename);
+
+ if (prev === emptyStats) {
+ this.emit("change", prev, prev);
+ }
+
+ try {
+ while (true) {
+ await delay(interval, { signal: this.#abortController.signal });
+ const curr = await statAsync(filename);
+ if (curr?.mtime !== prev?.mtime) {
+ this.emit("change", curr, prev);
+ prev = curr;
+ }
+ }
+ } catch (e) {
+ if (e instanceof DOMException && e.name === "AbortError") {
+ return;
+ }
+ this.emit("error", e);
+ }
+ })();
+ }
+ [kFSStatWatcherAddOrCleanRef](addOrClean: "add" | "clean" | "cleanAll") {
+ if (addOrClean === "add") {
+ this.#refCount++;
+ } else if (addOrClean === "clean") {
+ this.#refCount--;
+ } else {
+ this.#refCount = 0;
+ }
+ }
+ stop() {
+ if (this.#abortController.signal.aborted) {
+ return;
+ }
+ this.#abortController.abort();
+ this.emit("stop");
+ }
+ ref() {
+ notImplemented("FSWatcher.ref() is not implemented");
+ }
+ unref() {
+ notImplemented("FSWatcher.unref() is not implemented");
+ }
+}
+
+class FSWatcher extends EventEmitter {
+ #closer: () => void;
+ #closed = false;
+ constructor(closer: () => void) {
+ super();
+ this.#closer = closer;
+ }
+ close() {
+ if (this.#closed) {
+ return;
+ }
+ this.#closed = true;
+ this.emit("close");
+ this.#closer();
+ }
+ ref() {
+ notImplemented("FSWatcher.ref() is not implemented");
+ }
+ unref() {
+ notImplemented("FSWatcher.unref() is not implemented");
+ }
+}
+
+type NodeFsEventType = "rename" | "change";
+
+function convertDenoFsEventToNodeFsEvent(
+ kind: Deno.FsEvent["kind"],
+): NodeFsEventType {
+ if (kind === "create" || kind === "remove") {
+ return "rename";
+ } else {
+ return "change";
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_write.d.ts b/ext/node/polyfills/_fs/_fs_write.d.ts
new file mode 100644
index 000000000..eb6dbcc95
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_write.d.ts
@@ -0,0 +1,207 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Forked from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d9df51e34526f48bef4e2546a006157b391ad96c/types/node/fs.d.ts
+
+import {
+ BufferEncoding,
+ ErrnoException,
+} from "internal:deno_node/polyfills/_global.d.ts";
+
+/**
+ * Write `buffer` to the file specified by `fd`. If `buffer` is a normal object, it
+ * must have an own `toString` function property.
+ *
+ * `offset` determines the part of the buffer to be written, and `length` is
+ * an integer specifying the number of bytes to write.
+ *
+ * `position` refers to the offset from the beginning of the file where this data
+ * should be written. If `typeof position !== 'number'`, the data will be written
+ * at the current position. See [`pwrite(2)`](http://man7.org/linux/man-pages/man2/pwrite.2.html).
+ *
+ * The callback will be given three arguments `(err, bytesWritten, buffer)` where`bytesWritten` specifies how many _bytes_ were written from `buffer`.
+ *
+ * If this method is invoked as its `util.promisify()` ed version, it returns
+ * a promise for an `Object` with `bytesWritten` and `buffer` properties.
+ *
+ * It is unsafe to use `fs.write()` multiple times on the same file without waiting
+ * for the callback. For this scenario, {@link createWriteStream} is
+ * recommended.
+ *
+ * On Linux, positional writes don't work when the file is opened in append mode.
+ * The kernel ignores the position argument and always appends the data to
+ * the end of the file.
+ * @since v0.0.2
+ */
+export function write<TBuffer extends ArrayBufferView>(
+ fd: number,
+ buffer: TBuffer,
+ offset: number | undefined | null,
+ length: number | undefined | null,
+ position: number | undefined | null,
+ callback: (
+ err: ErrnoException | null,
+ written: number,
+ buffer: TBuffer,
+ ) => void,
+): void;
+/**
+ * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ * @param offset The part of the buffer to be written. If not supplied, defaults to `0`.
+ * @param length The number of bytes to write. If not supplied, defaults to `buffer.length - offset`.
+ */
+export function write<TBuffer extends ArrayBufferView>(
+ fd: number,
+ buffer: TBuffer,
+ offset: number | undefined | null,
+ length: number | undefined | null,
+ callback: (
+ err: ErrnoException | null,
+ written: number,
+ buffer: TBuffer,
+ ) => void,
+): void;
+/**
+ * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ * @param offset The part of the buffer to be written. If not supplied, defaults to `0`.
+ */
+export function write<TBuffer extends ArrayBufferView>(
+ fd: number,
+ buffer: TBuffer,
+ offset: number | undefined | null,
+ callback: (
+ err: ErrnoException | null,
+ written: number,
+ buffer: TBuffer,
+ ) => void,
+): void;
+/**
+ * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ */
+export function write<TBuffer extends ArrayBufferView>(
+ fd: number,
+ buffer: TBuffer,
+ callback: (
+ err: ErrnoException | null,
+ written: number,
+ buffer: TBuffer,
+ ) => void,
+): void;
+/**
+ * Asynchronously writes `string` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ * @param string A string to write.
+ * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position.
+ * @param encoding The expected string encoding.
+ */
+export function write(
+ fd: number,
+ string: string,
+ position: number | undefined | null,
+ encoding: BufferEncoding | undefined | null,
+ callback: (err: ErrnoException | null, written: number, str: string) => void,
+): void;
+/**
+ * Asynchronously writes `string` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ * @param string A string to write.
+ * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position.
+ */
+export function write(
+ fd: number,
+ string: string,
+ position: number | undefined | null,
+ callback: (err: ErrnoException | null, written: number, str: string) => void,
+): void;
+/**
+ * Asynchronously writes `string` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ * @param string A string to write.
+ */
+export function write(
+ fd: number,
+ string: string,
+ callback: (err: ErrnoException | null, written: number, str: string) => void,
+): void;
+export namespace write {
+ /**
+ * Asynchronously writes `buffer` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ * @param offset The part of the buffer to be written. If not supplied, defaults to `0`.
+ * @param length The number of bytes to write. If not supplied, defaults to `buffer.length - offset`.
+ * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position.
+ */
+ function __promisify__<TBuffer extends ArrayBufferView>(
+ fd: number,
+ buffer?: TBuffer,
+ offset?: number,
+ length?: number,
+ position?: number | null,
+ ): Promise<{
+ bytesWritten: number;
+ buffer: TBuffer;
+ }>;
+ /**
+ * Asynchronously writes `string` to the file referenced by the supplied file descriptor.
+ * @param fd A file descriptor.
+ * @param string A string to write.
+ * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position.
+ * @param encoding The expected string encoding.
+ */
+ function __promisify__(
+ fd: number,
+ string: string,
+ position?: number | null,
+ encoding?: BufferEncoding | null,
+ ): Promise<{
+ bytesWritten: number;
+ buffer: string;
+ }>;
+}
+/**
+ * If `buffer` is a plain object, it must have an own (not inherited) `toString`function property.
+ *
+ * For detailed information, see the documentation of the asynchronous version of
+ * this API: {@link write}.
+ * @since v0.1.21
+ * @return The number of bytes written.
+ */
+export function writeSync(
+ fd: number,
+ buffer: ArrayBufferView,
+ offset?: number | null,
+ length?: number | null,
+ position?: number | null,
+): number;
+/**
+ * Synchronously writes `string` to the file referenced by the supplied file descriptor, returning the number of bytes written.
+ * @param fd A file descriptor.
+ * @param string A string to write.
+ * @param position The offset from the beginning of the file where this data should be written. If not supplied, defaults to the current position.
+ * @param encoding The expected string encoding.
+ */
+export function writeSync(
+ fd: number,
+ string: string,
+ position?: number | null,
+ encoding?: BufferEncoding | null,
+): number;
+export type ReadPosition = number | bigint;
+/**
+ * Read data from the file specified by `fd`.
+ *
+ * The callback is given the three arguments, `(err, bytesRead, buffer)`.
+ *
+ * If the file is not modified concurrently, the end-of-file is reached when the
+ * number of bytes read is zero.
+ *
+ * If this method is invoked as its `util.promisify()` ed version, it returns
+ * a promise for an `Object` with `bytesRead` and `buffer` properties.
+ * @since v0.0.2
+ * @param buffer The buffer that the data will be written to.
+ * @param offset The position in `buffer` to write the data to.
+ * @param length The number of bytes to read.
+ * @param position Specifies where to begin reading from in the file. If `position` is `null` or `-1 `, data will be read from the current file position, and the file position will be updated. If
+ * `position` is an integer, the file position will be unchanged.
+ */
diff --git a/ext/node/polyfills/_fs/_fs_write.mjs b/ext/node/polyfills/_fs/_fs_write.mjs
new file mode 100644
index 000000000..d44a72921
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_write.mjs
@@ -0,0 +1,132 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { validateEncoding, validateInteger } from "internal:deno_node/polyfills/internal/validators.mjs";
+import {
+ getValidatedFd,
+ showStringCoercionDeprecation,
+ validateOffsetLengthWrite,
+ validateStringAfterArrayBufferView,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { isArrayBufferView } from "internal:deno_node/polyfills/internal/util/types.ts";
+import { maybeCallback } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+
+export function writeSync(fd, buffer, offset, length, position) {
+ fd = getValidatedFd(fd);
+
+ const innerWriteSync = (fd, buffer, offset, length, position) => {
+ if (buffer instanceof DataView) {
+ buffer = new Uint8Array(buffer.buffer);
+ }
+ if (typeof position === "number") {
+ Deno.seekSync(fd, position, Deno.SeekMode.Start);
+ }
+ let currentOffset = offset;
+ const end = offset + length;
+ while (currentOffset - offset < length) {
+ currentOffset += Deno.writeSync(fd, buffer.subarray(currentOffset, end));
+ }
+ return currentOffset - offset;
+ };
+
+ if (isArrayBufferView(buffer)) {
+ if (position === undefined) {
+ position = null;
+ }
+ if (offset == null) {
+ offset = 0;
+ } else {
+ validateInteger(offset, "offset", 0);
+ }
+ if (typeof length !== "number") {
+ length = buffer.byteLength - offset;
+ }
+ validateOffsetLengthWrite(offset, length, buffer.byteLength);
+ return innerWriteSync(fd, buffer, offset, length, position);
+ }
+ validateStringAfterArrayBufferView(buffer, "buffer");
+ validateEncoding(buffer, length);
+ if (offset === undefined) {
+ offset = null;
+ }
+ buffer = Buffer.from(buffer, length);
+ return innerWriteSync(fd, buffer, 0, buffer.length, position);
+}
+
+/** Writes the buffer to the file of the given descriptor.
+ * https://nodejs.org/api/fs.html#fswritefd-buffer-offset-length-position-callback
+ * https://github.com/nodejs/node/blob/42ad4137aadda69c51e1df48eee9bc2e5cebca5c/lib/fs.js#L797
+ */
+export function write(fd, buffer, offset, length, position, callback) {
+ fd = getValidatedFd(fd);
+
+ const innerWrite = async (fd, buffer, offset, length, position) => {
+ if (buffer instanceof DataView) {
+ buffer = new Uint8Array(buffer.buffer);
+ }
+ if (typeof position === "number") {
+ await Deno.seek(fd, position, Deno.SeekMode.Start);
+ }
+ let currentOffset = offset;
+ const end = offset + length;
+ while (currentOffset - offset < length) {
+ currentOffset += await Deno.write(
+ fd,
+ buffer.subarray(currentOffset, end),
+ );
+ }
+ return currentOffset - offset;
+ };
+
+ if (isArrayBufferView(buffer)) {
+ callback = maybeCallback(callback || position || length || offset);
+ if (offset == null || typeof offset === "function") {
+ offset = 0;
+ } else {
+ validateInteger(offset, "offset", 0);
+ }
+ if (typeof length !== "number") {
+ length = buffer.byteLength - offset;
+ }
+ if (typeof position !== "number") {
+ position = null;
+ }
+ validateOffsetLengthWrite(offset, length, buffer.byteLength);
+ innerWrite(fd, buffer, offset, length, position).then(
+ (nwritten) => {
+ callback(null, nwritten, buffer);
+ },
+ (err) => callback(err),
+ );
+ return;
+ }
+
+ // Here the call signature is
+ // `fs.write(fd, string[, position[, encoding]], callback)`
+
+ validateStringAfterArrayBufferView(buffer, "buffer");
+ if (typeof buffer !== "string") {
+ showStringCoercionDeprecation();
+ }
+
+ if (typeof position !== "function") {
+ if (typeof offset === "function") {
+ position = offset;
+ offset = null;
+ } else {
+ position = length;
+ }
+ length = "utf-8";
+ }
+
+ const str = String(buffer);
+ validateEncoding(str, length);
+ callback = maybeCallback(position);
+ buffer = Buffer.from(str, length);
+ innerWrite(fd, buffer, 0, buffer.length, offset, callback).then(
+ (nwritten) => {
+ callback(null, nwritten, buffer);
+ },
+ (err) => callback(err),
+ );
+}
diff --git a/ext/node/polyfills/_fs/_fs_writeFile.ts b/ext/node/polyfills/_fs/_fs_writeFile.ts
new file mode 100644
index 000000000..3cad5f947
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_writeFile.ts
@@ -0,0 +1,193 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+import { Encodings } from "internal:deno_node/polyfills/_utils.ts";
+import { fromFileUrl } from "internal:deno_node/polyfills/path.ts";
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import {
+ CallbackWithError,
+ checkEncoding,
+ getEncoding,
+ getOpenOptions,
+ isFileOptions,
+ WriteFileOptions,
+} from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+import { isWindows } from "internal:deno_node/polyfills/_util/os.ts";
+import {
+ AbortError,
+ denoErrorToNodeError,
+} from "internal:deno_node/polyfills/internal/errors.ts";
+import {
+ showStringCoercionDeprecation,
+ validateStringAfterArrayBufferView,
+} from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { promisify } from "internal:deno_node/polyfills/internal/util.mjs";
+
+interface Writer {
+ write(p: Uint8Array): Promise<number>;
+}
+
+export function writeFile(
+ pathOrRid: string | number | URL,
+ // deno-lint-ignore ban-types
+ data: string | Uint8Array | Object,
+ optOrCallback: Encodings | CallbackWithError | WriteFileOptions | undefined,
+ callback?: CallbackWithError,
+) {
+ const callbackFn: CallbackWithError | undefined =
+ optOrCallback instanceof Function ? optOrCallback : callback;
+ const options: Encodings | WriteFileOptions | undefined =
+ optOrCallback instanceof Function ? undefined : optOrCallback;
+
+ if (!callbackFn) {
+ throw new TypeError("Callback must be a function.");
+ }
+
+ pathOrRid = pathOrRid instanceof URL ? fromFileUrl(pathOrRid) : pathOrRid;
+
+ const flag: string | undefined = isFileOptions(options)
+ ? options.flag
+ : undefined;
+
+ const mode: number | undefined = isFileOptions(options)
+ ? options.mode
+ : undefined;
+
+ const encoding = checkEncoding(getEncoding(options)) || "utf8";
+ const openOptions = getOpenOptions(flag || "w");
+
+ if (!ArrayBuffer.isView(data)) {
+ validateStringAfterArrayBufferView(data, "data");
+ if (typeof data !== "string") {
+ showStringCoercionDeprecation();
+ }
+ data = Buffer.from(String(data), encoding);
+ }
+
+ const isRid = typeof pathOrRid === "number";
+ let file;
+
+ let error: Error | null = null;
+ (async () => {
+ try {
+ file = isRid
+ ? new Deno.FsFile(pathOrRid as number)
+ : await Deno.open(pathOrRid as string, openOptions);
+
+ // ignore mode because it's not supported on windows
+ // TODO(@bartlomieju): remove `!isWindows` when `Deno.chmod` is supported
+ if (!isRid && mode && !isWindows) {
+ await Deno.chmod(pathOrRid as string, mode);
+ }
+
+ const signal: AbortSignal | undefined = isFileOptions(options)
+ ? options.signal
+ : undefined;
+ await writeAll(file, data as Uint8Array, { signal });
+ } catch (e) {
+ error = e instanceof Error
+ ? denoErrorToNodeError(e, { syscall: "write" })
+ : new Error("[non-error thrown]");
+ } finally {
+ // Make sure to close resource
+ if (!isRid && file) file.close();
+ callbackFn(error);
+ }
+ })();
+}
+
+export const writeFilePromise = promisify(writeFile) as (
+ pathOrRid: string | number | URL,
+ // deno-lint-ignore ban-types
+ data: string | Uint8Array | Object,
+ options?: Encodings | WriteFileOptions,
+) => Promise<void>;
+
+export function writeFileSync(
+ pathOrRid: string | number | URL,
+ // deno-lint-ignore ban-types
+ data: string | Uint8Array | Object,
+ options?: Encodings | WriteFileOptions,
+) {
+ pathOrRid = pathOrRid instanceof URL ? fromFileUrl(pathOrRid) : pathOrRid;
+
+ const flag: string | undefined = isFileOptions(options)
+ ? options.flag
+ : undefined;
+
+ const mode: number | undefined = isFileOptions(options)
+ ? options.mode
+ : undefined;
+
+ const encoding = checkEncoding(getEncoding(options)) || "utf8";
+ const openOptions = getOpenOptions(flag || "w");
+
+ if (!ArrayBuffer.isView(data)) {
+ validateStringAfterArrayBufferView(data, "data");
+ if (typeof data !== "string") {
+ showStringCoercionDeprecation();
+ }
+ data = Buffer.from(String(data), encoding);
+ }
+
+ const isRid = typeof pathOrRid === "number";
+ let file;
+
+ let error: Error | null = null;
+ try {
+ file = isRid
+ ? new Deno.FsFile(pathOrRid as number)
+ : Deno.openSync(pathOrRid as string, openOptions);
+
+ // ignore mode because it's not supported on windows
+ // TODO(@bartlomieju): remove `!isWindows` when `Deno.chmod` is supported
+ if (!isRid && mode && !isWindows) {
+ Deno.chmodSync(pathOrRid as string, mode);
+ }
+
+ // TODO(crowlKats): duplicate from runtime/js/13_buffer.js
+ let nwritten = 0;
+ while (nwritten < (data as Uint8Array).length) {
+ nwritten += file.writeSync((data as Uint8Array).subarray(nwritten));
+ }
+ } catch (e) {
+ error = e instanceof Error
+ ? denoErrorToNodeError(e, { syscall: "write" })
+ : new Error("[non-error thrown]");
+ } finally {
+ // Make sure to close resource
+ if (!isRid && file) file.close();
+ }
+
+ if (error) throw error;
+}
+
+interface WriteAllOptions {
+ offset?: number;
+ length?: number;
+ signal?: AbortSignal;
+}
+async function writeAll(
+ w: Writer,
+ arr: Uint8Array,
+ options: WriteAllOptions = {},
+) {
+ const { offset = 0, length = arr.byteLength, signal } = options;
+ checkAborted(signal);
+
+ const written = await w.write(arr.subarray(offset, offset + length));
+
+ if (written === length) {
+ return;
+ }
+
+ await writeAll(w, arr, {
+ offset: offset + written,
+ length: length - written,
+ signal,
+ });
+}
+
+function checkAborted(signal?: AbortSignal) {
+ if (signal?.aborted) {
+ throw new AbortError();
+ }
+}
diff --git a/ext/node/polyfills/_fs/_fs_writev.d.ts b/ext/node/polyfills/_fs/_fs_writev.d.ts
new file mode 100644
index 000000000..d828bf677
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_writev.d.ts
@@ -0,0 +1,65 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Forked from https://github.com/DefinitelyTyped/DefinitelyTyped/blob/d9df51e34526f48bef4e2546a006157b391ad96c/types/node/fs.d.ts
+
+import { ErrnoException } from "internal:deno_node/polyfills/_global.d.ts";
+
+/**
+ * Write an array of `ArrayBufferView`s to the file specified by `fd` using`writev()`.
+ *
+ * `position` is the offset from the beginning of the file where this data
+ * should be written. If `typeof position !== 'number'`, the data will be written
+ * at the current position.
+ *
+ * The callback will be given three arguments: `err`, `bytesWritten`, and`buffers`. `bytesWritten` is how many bytes were written from `buffers`.
+ *
+ * If this method is `util.promisify()` ed, it returns a promise for an`Object` with `bytesWritten` and `buffers` properties.
+ *
+ * It is unsafe to use `fs.writev()` multiple times on the same file without
+ * waiting for the callback. For this scenario, use {@link createWriteStream}.
+ *
+ * On Linux, positional writes don't work when the file is opened in append mode.
+ * The kernel ignores the position argument and always appends the data to
+ * the end of the file.
+ * @since v12.9.0
+ */
+export function writev(
+ fd: number,
+ buffers: ReadonlyArray<ArrayBufferView>,
+ cb: (
+ err: ErrnoException | null,
+ bytesWritten: number,
+ buffers: ArrayBufferView[],
+ ) => void,
+): void;
+export function writev(
+ fd: number,
+ buffers: ReadonlyArray<ArrayBufferView>,
+ position: number | null,
+ cb: (
+ err: ErrnoException | null,
+ bytesWritten: number,
+ buffers: ArrayBufferView[],
+ ) => void,
+): void;
+export interface WriteVResult {
+ bytesWritten: number;
+ buffers: ArrayBufferView[];
+}
+export namespace writev {
+ function __promisify__(
+ fd: number,
+ buffers: ReadonlyArray<ArrayBufferView>,
+ position?: number,
+ ): Promise<WriteVResult>;
+}
+/**
+ * For detailed information, see the documentation of the asynchronous version of
+ * this API: {@link writev}.
+ * @since v12.9.0
+ * @return The number of bytes written.
+ */
+export function writevSync(
+ fd: number,
+ buffers: ReadonlyArray<ArrayBufferView>,
+ position?: number,
+): number;
diff --git a/ext/node/polyfills/_fs/_fs_writev.mjs b/ext/node/polyfills/_fs/_fs_writev.mjs
new file mode 100644
index 000000000..ffc67c81a
--- /dev/null
+++ b/ext/node/polyfills/_fs/_fs_writev.mjs
@@ -0,0 +1,81 @@
+// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
+// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license.
+import { Buffer } from "internal:deno_node/polyfills/buffer.ts";
+import { validateBufferArray } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { getValidatedFd } from "internal:deno_node/polyfills/internal/fs/utils.mjs";
+import { maybeCallback } from "internal:deno_node/polyfills/_fs/_fs_common.ts";
+
+export function writev(fd, buffers, position, callback) {
+ const innerWritev = async (fd, buffers, position) => {
+ const chunks = [];
+ const offset = 0;
+ for (let i = 0; i < buffers.length; i++) {
+ if (Buffer.isBuffer(buffers[i])) {
+ chunks.push(buffers[i]);
+ } else {
+ chunks.push(Buffer.from(buffers[i]));
+ }
+ }
+ if (typeof position === "number") {
+ await Deno.seekSync(fd, position, Deno.SeekMode.Start);
+ }
+ const buffer = Buffer.concat(chunks);
+ let currentOffset = 0;
+ while (currentOffset < buffer.byteLength) {
+ currentOffset += await Deno.writeSync(fd, buffer.subarray(currentOffset));
+ }
+ return currentOffset - offset;
+ };
+
+ fd = getValidatedFd(fd);
+ validateBufferArray(buffers);
+ callback = maybeCallback(callback || position);
+
+ if (buffers.length === 0) {
+ process.nextTick(callback, null, 0, buffers);
+ return;
+ }
+
+ if (typeof position !== "number") position = null;
+
+ innerWritev(fd, buffers, position).then(
+ (nwritten) => {
+ callback(null, nwritten, buffers);
+ },
+ (err) => callback(err),
+ );
+}
+
+export function writevSync(fd, buffers, position) {
+ const innerWritev = (fd, buffers, position) => {
+ const chunks = [];
+ const offset = 0;
+ for (let i = 0; i < buffers.length; i++) {
+ if (Buffer.isBuffer(buffers[i])) {
+ chunks.push(buffers[i]);
+ } else {
+ chunks.push(Buffer.from(buffers[i]));
+ }
+ }
+ if (typeof position === "number") {
+ Deno.seekSync(fd, position, Deno.SeekMode.Start);
+ }
+ const buffer = Buffer.concat(chunks);
+ let currentOffset = 0;
+ while (currentOffset < buffer.byteLength) {
+ currentOffset += Deno.writeSync(fd, buffer.subarray(currentOffset));
+ }
+ return currentOffset - offset;
+ };
+
+ fd = getValidatedFd(fd);
+ validateBufferArray(buffers);
+
+ if (buffers.length === 0) {
+ return 0;
+ }
+
+ if (typeof position !== "number") position = null;
+
+ return innerWritev(fd, buffers, position);
+}
diff --git a/ext/node/polyfills/_fs/testdata/hello.txt b/ext/node/polyfills/_fs/testdata/hello.txt
new file mode 100644
index 000000000..95d09f2b1
--- /dev/null
+++ b/ext/node/polyfills/_fs/testdata/hello.txt
@@ -0,0 +1 @@
+hello world \ No newline at end of file