summaryrefslogtreecommitdiff
path: root/std/node/_fs
diff options
context:
space:
mode:
Diffstat (limited to 'std/node/_fs')
-rw-r--r--std/node/_fs/_fs_lstat.ts59
-rw-r--r--std/node/_fs/_fs_lstat_test.ts56
-rw-r--r--std/node/_fs/_fs_open.ts103
-rw-r--r--std/node/_fs/_fs_open_test.ts209
-rw-r--r--std/node/_fs/_fs_readdir.ts117
-rw-r--r--std/node/_fs/_fs_readdir_test.ts71
-rw-r--r--std/node/_fs/_fs_rename.ts23
-rw-r--r--std/node/_fs/_fs_rename_test.ts38
-rw-r--r--std/node/_fs/_fs_rmdir.ts36
-rw-r--r--std/node/_fs/_fs_rmdir_test.ts88
-rw-r--r--std/node/_fs/_fs_stat.ts289
-rw-r--r--std/node/_fs/_fs_stat_test.ts107
-rw-r--r--std/node/_fs/_fs_unlink.ts10
-rw-r--r--std/node/_fs/_fs_unlink_test.ts30
-rw-r--r--std/node/_fs/_fs_watch.ts111
-rw-r--r--std/node/_fs/_fs_watch_test.ts32
16 files changed, 1379 insertions, 0 deletions
diff --git a/std/node/_fs/_fs_lstat.ts b/std/node/_fs/_fs_lstat.ts
new file mode 100644
index 000000000..0b79fb665
--- /dev/null
+++ b/std/node/_fs/_fs_lstat.ts
@@ -0,0 +1,59 @@
+import {
+ BigIntStats,
+ CFISBIS,
+ statCallback,
+ statCallbackBigInt,
+ statOptions,
+ Stats,
+} from "./_fs_stat.ts";
+
+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 (
+ err: Error | undefined,
+ stat: 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(undefined, CFISBIS(stat, options.bigint)))
+ .catch((err) => callback(err, err));
+}
+
+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/std/node/_fs/_fs_lstat_test.ts b/std/node/_fs/_fs_lstat_test.ts
new file mode 100644
index 000000000..1da0a562f
--- /dev/null
+++ b/std/node/_fs/_fs_lstat_test.ts
@@ -0,0 +1,56 @@
+import { lstat, lstatSync } from "./_fs_lstat.ts";
+import { fail } from "../../testing/asserts.ts";
+import { assertStats, assertStatsBigInt } from "./_fs_stat_test.ts";
+import type { BigIntStats, Stats } from "./_fs_stat.ts";
+
+Deno.test({
+ name: "ASYNC: get a file Stats (lstat)",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<Stats>((resolve, reject) => {
+ lstat(file, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then((stat) => {
+ assertStats(stat, Deno.lstatSync(file));
+ })
+ .catch(() => fail())
+ .finally(() => {
+ Deno.removeSync(file);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file Stats (lstat)",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStats(lstatSync(file), Deno.lstatSync(file));
+ },
+});
+
+Deno.test({
+ name: "ASYNC: get a file BigInt Stats (lstat)",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<BigIntStats>((resolve, reject) => {
+ lstat(file, { bigint: true }, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then((stat) => assertStatsBigInt(stat, Deno.lstatSync(file)))
+ .catch(() => fail())
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "SYNC: BigInt Stats (lstat)",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStatsBigInt(lstatSync(file, { bigint: true }), Deno.lstatSync(file));
+ },
+});
diff --git a/std/node/_fs/_fs_open.ts b/std/node/_fs/_fs_open.ts
new file mode 100644
index 000000000..bf53115de
--- /dev/null
+++ b/std/node/_fs/_fs_open.ts
@@ -0,0 +1,103 @@
+import { existsSync } from "../../fs/mod.ts";
+import { fromFileUrl } from "../path.ts";
+import { getOpenOptions } from "./_fs_common.ts";
+
+type openFlags =
+ | "a"
+ | "ax"
+ | "a+"
+ | "ax+"
+ | "as"
+ | "as+"
+ | "r"
+ | "r+"
+ | "rs+"
+ | "w"
+ | "wx"
+ | "w+"
+ | "wx+";
+
+type openCallback = (err: Error | undefined, 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 | URL, callback: openCallback): void;
+export function open(
+ path: string | URL,
+ flags: openFlags,
+ callback: openCallback,
+): void;
+export function open(
+ path: string | URL,
+ flags: openFlags,
+ mode: number,
+ callback: openCallback,
+): void;
+export function open(
+ path: string | URL,
+ flagsOrCallback: openCallback | openFlags,
+ callbackOrMode?: openCallback | number,
+ maybeCallback?: openCallback,
+) {
+ const flags = typeof flagsOrCallback === "string"
+ ? flagsOrCallback
+ : undefined;
+ const callback = typeof flagsOrCallback === "function"
+ ? flagsOrCallback
+ : typeof callbackOrMode === "function"
+ ? callbackOrMode
+ : maybeCallback;
+ const mode = typeof callbackOrMode === "number" ? callbackOrMode : undefined;
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ if (["ax", "ax+", "wx", "wx+"].includes(flags || "") && existsSync(path)) {
+ const err = new Error(`EEXIST: file already exists, open '${path}'`);
+ callback(err, 0);
+ } else {
+ if (flags === "as" || flags === "as+") {
+ try {
+ const res = openSync(path, flags, mode);
+ callback(undefined, res);
+ } catch (error) {
+ callback(error, error);
+ }
+ return;
+ }
+ Deno.open(path, convertFlagAndModeToOptions(flags, mode))
+ .then((file) => callback(undefined, file.rid))
+ .catch((err) => callback(err, err));
+ }
+}
+
+export function openSync(path: string | URL): number;
+export function openSync(path: string | URL, flags?: openFlags): number;
+export function openSync(path: string | URL, mode?: number): number;
+export function openSync(
+ path: string | URL,
+ flags?: openFlags,
+ mode?: number,
+): number;
+export function openSync(
+ path: string | URL,
+ flagsOrMode?: openFlags | number,
+ maybeMode?: number,
+) {
+ const flags = typeof flagsOrMode === "string" ? flagsOrMode : undefined;
+ const mode = typeof flagsOrMode === "number" ? flagsOrMode : maybeMode;
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ if (["ax", "ax+", "wx", "wx+"].includes(flags || "") && existsSync(path)) {
+ throw new Error(`EEXIST: file already exists, open '${path}'`);
+ }
+
+ return Deno.openSync(path, convertFlagAndModeToOptions(flags, mode)).rid;
+}
diff --git a/std/node/_fs/_fs_open_test.ts b/std/node/_fs/_fs_open_test.ts
new file mode 100644
index 000000000..4cbd58d02
--- /dev/null
+++ b/std/node/_fs/_fs_open_test.ts
@@ -0,0 +1,209 @@
+import {
+ assert,
+ assertEquals,
+ assertThrows,
+ fail,
+} from "../../testing/asserts.ts";
+import { open, openSync } from "./_fs_open.ts";
+import { join, parse } from "../../path/mod.ts";
+import { existsSync } from "../../fs/mod.ts";
+import { closeSync } from "./_fs_close.ts";
+
+const temp_dir = parse(Deno.makeTempFileSync()).dir;
+
+Deno.test({
+ name: "ASYNC: open file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ let fd1: number;
+ await new Promise<number>((resolve, reject) => {
+ open(file, (err, fd) => {
+ if (err) reject(err);
+ resolve(fd);
+ });
+ })
+ .then((fd) => {
+ fd1 = fd;
+ assert(Deno.resources()[fd], `${fd}`);
+ })
+ .catch(() => fail())
+ .finally(() => closeSync(fd1));
+ },
+});
+
+Deno.test({
+ name: "SYNC: open file",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ const fd = openSync(file);
+ assert(Deno.resources()[fd]);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'a'",
+ fn() {
+ const file = join(temp_dir, "some_random_file");
+ const fd = openSync(file, "a");
+ assertEquals(typeof fd, "number");
+ assertEquals(existsSync(file), true);
+ assert(Deno.resources()[fd]);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'ax'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, "ax");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'a+'",
+ fn() {
+ const file = join(temp_dir, "some_random_file2");
+ const fd = openSync(file, "a+");
+ assertEquals(typeof fd, "number");
+ assertEquals(existsSync(file), true);
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'ax+'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, "ax+");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'as'",
+ fn() {
+ const file = join(temp_dir, "some_random_file10");
+ const fd = openSync(file, "as");
+ assertEquals(existsSync(file), true);
+ assertEquals(typeof fd, "number");
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'as+'",
+ fn() {
+ const file = join(temp_dir, "some_random_file10");
+ const fd = openSync(file, "as+");
+ assertEquals(existsSync(file), true);
+ assertEquals(typeof fd, "number");
+ closeSync(fd);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'r'",
+ fn() {
+ const file = join(temp_dir, "some_random_file3");
+ assertThrows(() => {
+ openSync(file, "r");
+ }, Error);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'r+'",
+ fn() {
+ const file = join(temp_dir, "some_random_file4");
+ assertThrows(() => {
+ openSync(file, "r+");
+ }, Error);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'w'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "w");
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = join(temp_dir, "some_random_file5");
+ const fd2 = openSync(file2, "w");
+ assertEquals(typeof fd2, "number");
+ assertEquals(existsSync(file2), true);
+ closeSync(fd2);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'wx'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "w");
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file2, "wx");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file2}'`,
+ );
+ },
+});
+
+Deno.test({
+ name: "open with flag 'w+'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ Deno.writeTextFileSync(file, "hi there");
+ const fd = openSync(file, "w+");
+ assertEquals(typeof fd, "number");
+ assertEquals(Deno.readTextFileSync(file), "");
+ closeSync(fd);
+
+ const file2 = join(temp_dir, "some_random_file6");
+ const fd2 = openSync(file2, "w+");
+ assertEquals(typeof fd2, "number");
+ assertEquals(existsSync(file2), true);
+ closeSync(fd2);
+ },
+});
+
+Deno.test({
+ name: "open with flag 'wx+'",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertThrows(
+ () => {
+ openSync(file, "wx+");
+ },
+ Error,
+ `EEXIST: file already exists, open '${file}'`,
+ );
+ Deno.removeSync(file);
+ },
+});
diff --git a/std/node/_fs/_fs_readdir.ts b/std/node/_fs/_fs_readdir.ts
new file mode 100644
index 000000000..9034eccf8
--- /dev/null
+++ b/std/node/_fs/_fs_readdir.ts
@@ -0,0 +1,117 @@
+import { asyncIterableToCallback } from "./_fs_watch.ts";
+import Dirent from "./_fs_dirent.ts";
+import { fromFileUrl } from "../path.ts";
+
+function toDirent(val: Deno.DirEntry): Dirent {
+ return new Dirent(val);
+}
+
+type readDirOptions = {
+ encoding?: string;
+ withFileTypes?: boolean;
+};
+
+type readDirCallback = (err: Error | undefined, files: string[]) => void;
+
+type readDirCallbackDirent = (err: Error | undefined, files: Dirent[]) => void;
+
+type readDirBoth = (
+ err: Error | undefined,
+ files: string[] | Dirent[] | Array<string | Dirent>,
+) => void;
+
+export function readdir(
+ path: string | URL,
+ options: { withFileTypes?: false; encoding?: string },
+ callback: readDirCallback,
+): void;
+export function readdir(
+ path: string | URL,
+ options: { withFileTypes: true; encoding?: string },
+ callback: readDirCallbackDirent,
+): void;
+export function readdir(path: string | URL, callback: readDirCallback): void;
+export function readdir(
+ path: string | 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 = path instanceof URL ? fromFileUrl(path) : path;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ if (options?.encoding) {
+ try {
+ new TextDecoder(options.encoding);
+ } catch (error) {
+ throw new Error(
+ `TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`,
+ );
+ }
+ }
+
+ try {
+ asyncIterableToCallback(Deno.readDir(path), (val, done) => {
+ if (typeof path !== "string") return;
+ if (done) {
+ callback(undefined, result);
+ return;
+ }
+ if (options?.withFileTypes) {
+ result.push(toDirent(val));
+ } else result.push(decode(val.name));
+ });
+ } catch (error) {
+ callback(error, result);
+ }
+}
+
+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 function readdirSync(
+ path: string | URL,
+ options: { withFileTypes: true; encoding?: string },
+): Dirent[];
+export function readdirSync(
+ path: string | URL,
+ options?: { withFileTypes?: false; encoding?: string },
+): string[];
+export function readdirSync(
+ path: string | URL,
+ options?: readDirOptions,
+): Array<string | Dirent> {
+ const result = [];
+ path = path instanceof URL ? fromFileUrl(path) : path;
+
+ if (options?.encoding) {
+ try {
+ new TextDecoder(options.encoding);
+ } catch (error) {
+ throw new Error(
+ `TypeError [ERR_INVALID_OPT_VALUE_ENCODING]: The value "${options.encoding}" is invalid for option "encoding"`,
+ );
+ }
+ }
+
+ for (const file of Deno.readDirSync(path)) {
+ if (options?.withFileTypes) {
+ result.push(toDirent(file));
+ } else result.push(decode(file.name));
+ }
+ return result;
+}
diff --git a/std/node/_fs/_fs_readdir_test.ts b/std/node/_fs/_fs_readdir_test.ts
new file mode 100644
index 000000000..169e10245
--- /dev/null
+++ b/std/node/_fs/_fs_readdir_test.ts
@@ -0,0 +1,71 @@
+import { assertEquals, assertNotEquals, fail } from "../../testing/asserts.ts";
+import { readdir, readdirSync } from "./_fs_readdir.ts";
+import { join } from "../../path/mod.ts";
+
+Deno.test({
+ name: "ASYNC: reading empty directory",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ await new Promise<string[]>((resolve, reject) => {
+ readdir(dir, (err, files) => {
+ if (err) reject(err);
+ resolve(files);
+ });
+ })
+ .then((files) => assertEquals(files, []))
+ .catch(() => fail())
+ .finally(() => Deno.removeSync(dir));
+ },
+});
+
+function assertEqualsArrayAnyOrder<T>(actual: T[], expected: T[]) {
+ assertEquals(actual.length, expected.length);
+ for (const item of expected) {
+ const index = actual.indexOf(item);
+ assertNotEquals(index, -1);
+ expected = expected.splice(index, 1);
+ }
+}
+
+Deno.test({
+ name: "ASYNC: reading non-empty directory",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ Deno.writeTextFileSync(join(dir, "file1.txt"), "hi");
+ Deno.writeTextFileSync(join(dir, "file2.txt"), "hi");
+ Deno.mkdirSync(join(dir, "some_dir"));
+ await new Promise<string[]>((resolve, reject) => {
+ readdir(dir, (err, files) => {
+ if (err) reject(err);
+ resolve(files);
+ });
+ })
+ .then((files) =>
+ assertEqualsArrayAnyOrder(files, ["file1.txt", "some_dir", "file2.txt"])
+ )
+ .catch(() => fail())
+ .finally(() => Deno.removeSync(dir, { recursive: true }));
+ },
+});
+
+Deno.test({
+ name: "SYNC: reading empty the directory",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ assertEquals(readdirSync(dir), []);
+ },
+});
+
+Deno.test({
+ name: "SYNC: reading non-empty directory",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ Deno.writeTextFileSync(join(dir, "file1.txt"), "hi");
+ Deno.writeTextFileSync(join(dir, "file2.txt"), "hi");
+ Deno.mkdirSync(join(dir, "some_dir"));
+ assertEqualsArrayAnyOrder(
+ readdirSync(dir),
+ ["file1.txt", "some_dir", "file2.txt"],
+ );
+ },
+});
diff --git a/std/node/_fs/_fs_rename.ts b/std/node/_fs/_fs_rename.ts
new file mode 100644
index 000000000..ee7c00977
--- /dev/null
+++ b/std/node/_fs/_fs_rename.ts
@@ -0,0 +1,23 @@
+import { fromFileUrl } from "../path.ts";
+
+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())
+ .catch(callback);
+}
+
+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/std/node/_fs/_fs_rename_test.ts b/std/node/_fs/_fs_rename_test.ts
new file mode 100644
index 000000000..d0084d0d1
--- /dev/null
+++ b/std/node/_fs/_fs_rename_test.ts
@@ -0,0 +1,38 @@
+import { assertEquals, fail } from "../../testing/asserts.ts";
+import { rename, renameSync } from "./_fs_rename.ts";
+import { existsSync } from "../../fs/mod.ts";
+import { join, parse } from "../../path/mod.ts";
+
+Deno.test({
+ name: "ASYNC: renaming a file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ const newPath = join(parse(file).dir, `${parse(file).base}_renamed`);
+ await new Promise((resolve, reject) => {
+ rename(file, newPath, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => {
+ assertEquals(existsSync(newPath), true);
+ assertEquals(existsSync(file), false);
+ })
+ .catch(() => fail())
+ .finally(() => {
+ if (existsSync(file)) Deno.removeSync(file);
+ if (existsSync(newPath)) Deno.removeSync(newPath);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: renaming a file",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ const newPath = join(parse(file).dir, `${parse(file).base}_renamed`);
+ renameSync(file, newPath);
+ assertEquals(existsSync(newPath), true);
+ assertEquals(existsSync(file), false);
+ },
+});
diff --git a/std/node/_fs/_fs_rmdir.ts b/std/node/_fs/_fs_rmdir.ts
new file mode 100644
index 000000000..2035a1e71
--- /dev/null
+++ b/std/node/_fs/_fs_rmdir.ts
@@ -0,0 +1,36 @@
+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,
+) {
+ const callback = typeof optionsOrCallback === "function"
+ ? optionsOrCallback
+ : maybeCallback;
+ const options = typeof optionsOrCallback === "object"
+ ? optionsOrCallback
+ : undefined;
+
+ if (!callback) throw new Error("No callback function supplied");
+
+ Deno.remove(path, { recursive: options?.recursive })
+ .then((_) => callback())
+ .catch(callback);
+}
+
+export function rmdirSync(path: string | URL, options?: rmdirOptions) {
+ Deno.removeSync(path, { recursive: options?.recursive });
+}
diff --git a/std/node/_fs/_fs_rmdir_test.ts b/std/node/_fs/_fs_rmdir_test.ts
new file mode 100644
index 000000000..884d4912a
--- /dev/null
+++ b/std/node/_fs/_fs_rmdir_test.ts
@@ -0,0 +1,88 @@
+import { assertEquals, fail } from "../../testing/asserts.ts";
+import { rmdir, rmdirSync } from "./_fs_rmdir.ts";
+import { closeSync } from "./_fs_close.ts";
+import { existsSync } from "../../fs/mod.ts";
+import { join } from "../../path/mod.ts";
+
+Deno.test({
+ name: "ASYNC: removing empty folder",
+ async fn() {
+ const dir = Deno.makeTempDirSync();
+ await new Promise((resolve, reject) => {
+ rmdir(dir, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(dir), false))
+ .catch(() => fail())
+ .finally(() => {
+ if (existsSync(dir)) Deno.removeSync(dir);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: removing empty folder",
+ fn() {
+ const dir = Deno.makeTempDirSync();
+ rmdirSync(dir);
+ assertEquals(existsSync(dir), false);
+ },
+});
+
+function closeRes(before: Deno.ResourceMap, after: Deno.ResourceMap) {
+ for (const key in after) {
+ if (!before[key]) {
+ try {
+ closeSync(Number(key));
+ } catch (error) {
+ return error;
+ }
+ }
+ }
+}
+
+Deno.test({
+ name: "ASYNC: removing non-empty folder",
+ async fn() {
+ const rBefore = Deno.resources();
+ const dir = Deno.makeTempDirSync();
+ Deno.createSync(join(dir, "file1.txt"));
+ Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ Deno.createSync(join(dir, "some_dir", "file.txt"));
+ await new Promise((resolve, reject) => {
+ rmdir(dir, { recursive: true }, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(dir), false))
+ .catch(() => fail())
+ .finally(() => {
+ if (existsSync(dir)) Deno.removeSync(dir, { recursive: true });
+ const rAfter = Deno.resources();
+ closeRes(rBefore, rAfter);
+ });
+ },
+ ignore: Deno.build.os === "windows",
+});
+
+Deno.test({
+ name: "SYNC: removing non-empty folder",
+ fn() {
+ const rBefore = Deno.resources();
+ const dir = Deno.makeTempDirSync();
+ Deno.createSync(join(dir, "file1.txt"));
+ Deno.createSync(join(dir, "file2.txt"));
+ Deno.mkdirSync(join(dir, "some_dir"));
+ Deno.createSync(join(dir, "some_dir", "file.txt"));
+ rmdirSync(dir, { recursive: true });
+ assertEquals(existsSync(dir), false);
+ // closing resources
+ const rAfter = Deno.resources();
+ closeRes(rBefore, rAfter);
+ },
+ ignore: Deno.build.os === "windows",
+});
diff --git a/std/node/_fs/_fs_stat.ts b/std/node/_fs/_fs_stat.ts
new file mode 100644
index 000000000..d823f7ddb
--- /dev/null
+++ b/std/node/_fs/_fs_stat.ts
@@ -0,0 +1,289 @@
+export type statOptions = {
+ bigint: 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 to_BigInt(number?: number | null) {
+ if (number === null || number === undefined) return null;
+ return BigInt(number);
+}
+
+export function convertFileInfoToBigIntStats(
+ origin: Deno.FileInfo,
+): BigIntStats {
+ return {
+ dev: to_BigInt(origin.dev),
+ ino: to_BigInt(origin.ino),
+ mode: to_BigInt(origin.mode),
+ nlink: to_BigInt(origin.nlink),
+ uid: to_BigInt(origin.uid),
+ gid: to_BigInt(origin.gid),
+ rdev: to_BigInt(origin.rdev),
+ size: to_BigInt(origin.size) || 0n,
+ blksize: to_BigInt(origin.blksize),
+ blocks: to_BigInt(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 | undefined,
+ stat: BigIntStats,
+) => void;
+
+export type statCallback = (err: Error | undefined, 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 (
+ err: Error | undefined,
+ stat: 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(undefined, CFISBIS(stat, options.bigint)))
+ .catch((err) => callback(err, err));
+}
+
+export function statSync(path: string | URL): Stats;
+export function statSync(path: string | URL, options: { bigint: false }): Stats;
+export function statSync(
+ path: string | URL,
+ options: { bigint: true },
+): BigIntStats;
+export function statSync(
+ path: string | URL,
+ options: statOptions = { bigint: false },
+): Stats | BigIntStats {
+ const origin = Deno.statSync(path);
+ return CFISBIS(origin, options.bigint);
+}
diff --git a/std/node/_fs/_fs_stat_test.ts b/std/node/_fs/_fs_stat_test.ts
new file mode 100644
index 000000000..925a79be2
--- /dev/null
+++ b/std/node/_fs/_fs_stat_test.ts
@@ -0,0 +1,107 @@
+import { BigIntStats, stat, Stats, statSync } from "./_fs_stat.ts";
+import { assertEquals, fail } from "../../testing/asserts.ts";
+
+export function assertStats(actual: Stats, expected: Deno.FileInfo) {
+ assertEquals(actual.dev, expected.dev);
+ assertEquals(actual.gid, expected.gid);
+ assertEquals(actual.size, expected.size);
+ assertEquals(actual.blksize, expected.blksize);
+ assertEquals(actual.blocks, expected.blocks);
+ assertEquals(actual.ino, expected.ino);
+ assertEquals(actual.gid, expected.gid);
+ assertEquals(actual.mode, expected.mode);
+ assertEquals(actual.nlink, expected.nlink);
+ assertEquals(actual.rdev, expected.rdev);
+ assertEquals(actual.uid, expected.uid);
+ assertEquals(actual.atime?.getTime(), expected.atime?.getTime());
+ assertEquals(actual.mtime?.getTime(), expected.mtime?.getTime());
+ assertEquals(actual.birthtime?.getTime(), expected.birthtime?.getTime());
+ assertEquals(actual.atimeMs, expected.atime?.getTime());
+ assertEquals(actual.mtimeMs, expected.mtime?.getTime());
+ assertEquals(actual.birthtimeMs, expected.birthtime?.getTime());
+ assertEquals(actual.isFile(), expected.isFile);
+ assertEquals(actual.isDirectory(), expected.isDirectory);
+ assertEquals(actual.isSymbolicLink(), expected.isSymlink);
+}
+
+function to_BigInt(num?: number | null) {
+ if (num === undefined || num === null) return null;
+ return BigInt(num);
+}
+
+export function assertStatsBigInt(
+ actual: BigIntStats,
+ expected: Deno.FileInfo,
+) {
+ assertEquals(actual.dev, to_BigInt(expected.dev));
+ assertEquals(actual.gid, to_BigInt(expected.gid));
+ assertEquals(actual.size, to_BigInt(expected.size));
+ assertEquals(actual.blksize, to_BigInt(expected.blksize));
+ assertEquals(actual.blocks, to_BigInt(expected.blocks));
+ assertEquals(actual.ino, to_BigInt(expected.ino));
+ assertEquals(actual.gid, to_BigInt(expected.gid));
+ assertEquals(actual.mode, to_BigInt(expected.mode));
+ assertEquals(actual.nlink, to_BigInt(expected.nlink));
+ assertEquals(actual.rdev, to_BigInt(expected.rdev));
+ assertEquals(actual.uid, to_BigInt(expected.uid));
+ assertEquals(actual.atime?.getTime(), expected.atime?.getTime());
+ assertEquals(actual.mtime?.getTime(), expected.mtime?.getTime());
+ assertEquals(actual.birthtime?.getTime(), expected.birthtime?.getTime());
+ assertEquals(Number(actual.atimeMs), expected.atime?.getTime());
+ assertEquals(Number(actual.mtimeMs), expected.mtime?.getTime());
+ assertEquals(Number(actual.birthtimeMs), expected.birthtime?.getTime());
+ assertEquals(actual.atimeNs === null, actual.atime === null);
+ assertEquals(actual.mtimeNs === null, actual.mtime === null);
+ assertEquals(actual.birthtimeNs === null, actual.birthtime === null);
+ assertEquals(actual.isFile(), expected.isFile);
+ assertEquals(actual.isDirectory(), expected.isDirectory);
+ assertEquals(actual.isSymbolicLink(), expected.isSymlink);
+}
+
+Deno.test({
+ name: "ASYNC: get a file Stats",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<Stats>((resolve, reject) => {
+ stat(file, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then((stat) => assertStats(stat, Deno.statSync(file)))
+ .catch(() => fail())
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file Stats",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStats(statSync(file), Deno.statSync(file));
+ },
+});
+
+Deno.test({
+ name: "ASYNC: get a file BigInt Stats",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise<BigIntStats>((resolve, reject) => {
+ stat(file, { bigint: true }, (err, stat) => {
+ if (err) reject(err);
+ resolve(stat);
+ });
+ })
+ .then((stat) => assertStatsBigInt(stat, Deno.statSync(file)))
+ .catch(() => fail())
+ .finally(() => Deno.removeSync(file));
+ },
+});
+
+Deno.test({
+ name: "SYNC: get a file BigInt Stats",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ assertStatsBigInt(statSync(file, { bigint: true }), Deno.statSync(file));
+ },
+});
diff --git a/std/node/_fs/_fs_unlink.ts b/std/node/_fs/_fs_unlink.ts
new file mode 100644
index 000000000..aba734fe1
--- /dev/null
+++ b/std/node/_fs/_fs_unlink.ts
@@ -0,0 +1,10 @@
+export function unlink(path: string | URL, callback: (err?: Error) => void) {
+ if (!callback) throw new Error("No callback function supplied");
+ Deno.remove(path)
+ .then((_) => callback())
+ .catch(callback);
+}
+
+export function unlinkSync(path: string | URL) {
+ Deno.removeSync(path);
+}
diff --git a/std/node/_fs/_fs_unlink_test.ts b/std/node/_fs/_fs_unlink_test.ts
new file mode 100644
index 000000000..922a1a703
--- /dev/null
+++ b/std/node/_fs/_fs_unlink_test.ts
@@ -0,0 +1,30 @@
+import { assertEquals, fail } from "../../testing/asserts.ts";
+import { existsSync } from "../../fs/mod.ts";
+import { unlink, unlinkSync } from "./_fs_unlink.ts";
+
+Deno.test({
+ name: "ASYNC: deleting a file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ await new Promise((resolve, reject) => {
+ unlink(file, (err) => {
+ if (err) reject(err);
+ resolve();
+ });
+ })
+ .then(() => assertEquals(existsSync(file), false))
+ .catch(() => fail())
+ .finally(() => {
+ if (existsSync(file)) Deno.removeSync(file);
+ });
+ },
+});
+
+Deno.test({
+ name: "SYNC: Test deleting a file",
+ fn() {
+ const file = Deno.makeTempFileSync();
+ unlinkSync(file);
+ assertEquals(existsSync(file), false);
+ },
+});
diff --git a/std/node/_fs/_fs_watch.ts b/std/node/_fs/_fs_watch.ts
new file mode 100644
index 000000000..a5f3bb9c1
--- /dev/null
+++ b/std/node/_fs/_fs_watch.ts
@@ -0,0 +1,111 @@
+import { fromFileUrl } from "../path.ts";
+import { EventEmitter } from "../events.ts";
+import { notImplemented } from "../_utils.ts";
+
+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,
+) {
+ const iterator = iter[Symbol.asyncIterator]();
+ function next() {
+ iterator.next().then((obj) => {
+ if (obj.done) {
+ callback(obj.value, true);
+ return;
+ }
+ callback(obj.value);
+ next();
+ });
+ }
+ 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;
+ filename = filename instanceof URL ? fromFileUrl(filename) : filename;
+
+ const iterator = Deno.watchFs(filename, {
+ recursive: options?.recursive || false,
+ });
+
+ if (!listener) throw new Error("No callback function supplied");
+
+ const fsWatcher = new FSWatcher(() => {
+ if (iterator.return) iterator.return();
+ });
+
+ fsWatcher.on("change", listener);
+
+ asyncIterableIteratorToCallback<Deno.FsEvent>(iterator, (val, done) => {
+ if (done) return;
+ fsWatcher.emit("change", val.kind, val.paths[0]);
+ });
+
+ return fsWatcher;
+}
+
+class FSWatcher extends EventEmitter {
+ close: () => void;
+ constructor(closer: () => void) {
+ super();
+ this.close = closer;
+ }
+ ref() {
+ notImplemented("FSWatcher.ref() is not implemented");
+ }
+ unref() {
+ notImplemented("FSWatcher.unref() is not implemented");
+ }
+}
diff --git a/std/node/_fs/_fs_watch_test.ts b/std/node/_fs/_fs_watch_test.ts
new file mode 100644
index 000000000..e85b4c9bc
--- /dev/null
+++ b/std/node/_fs/_fs_watch_test.ts
@@ -0,0 +1,32 @@
+import { watch } from "./_fs_watch.ts";
+import { assertEquals, fail } from "../../testing/asserts.ts";
+
+function wait(time: number) {
+ return new Promise((resolve) => {
+ setTimeout(resolve, time);
+ });
+}
+
+Deno.test({
+ name: "watching a file",
+ async fn() {
+ const file = Deno.makeTempFileSync();
+ const result: Array<[string, string]> = [];
+ await new Promise((resolve) => {
+ const watcher = watch(
+ file,
+ (eventType, filename) => result.push([eventType, filename]),
+ );
+ wait(100)
+ .then(() => Deno.writeTextFileSync(file, "something"))
+ .then(() => wait(100))
+ .then(() => watcher.close())
+ .then(() => wait(100))
+ .then(resolve);
+ })
+ .then(() => {
+ assertEquals(result.length >= 1, true);
+ })
+ .catch(() => fail());
+ },
+});