Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(fs/unstable): add readDir #6338

Merged
merged 10 commits into from
Jan 14, 2025
3 changes: 3 additions & 0 deletions _tools/node_test_runner/register_deno_shim.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ import { register } from "node:module";

register(new URL("deno_compat_hooks.mjs", import.meta.url));

// Caches @std/path module before polyfilling globalThis.Deno
await import("@std/path");

globalThis.Deno = Deno;
globalThis.testDefinitions = testDefinitions;
1 change: 1 addition & 0 deletions _tools/node_test_runner/run_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import "../../collections/union_test.ts";
import "../../collections/unzip_test.ts";
import "../../collections/without_all_test.ts";
import "../../collections/zip_test.ts";
import "../../fs/unstable_read_dir_test.ts";
import "../../fs/unstable_stat_test.ts";
import "../../fs/unstable_lstat_test.ts";

Expand Down
12 changes: 12 additions & 0 deletions fs/_to_dir_entry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import type { DirEntry } from "./unstable_types.ts";

export function toDirEntry(s: import("node:fs").Dirent): DirEntry {
return {
name: s.name,
isFile: s.isFile(),
isDirectory: s.isDirectory(),
isSymlink: s.isSymbolicLink(),
};
}

Check warning on line 12 in fs/_to_dir_entry.ts

View check run for this annotation

Codecov / codecov/patch

fs/_to_dir_entry.ts#L5-L12

Added lines #L5 - L12 were not covered by tests
1 change: 1 addition & 0 deletions fs/deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"./expand-glob": "./expand_glob.ts",
"./move": "./move.ts",
"./unstable-lstat": "./unstable_lstat.ts",
"./unstable-read-dir": "./unstable_read_dir.ts",
"./unstable-stat": "./unstable_stat.ts",
"./unstable-types": "./unstable_types.ts",
"./walk": "./walk.ts"
Expand Down
40 changes: 40 additions & 0 deletions fs/unstable_read_dir.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { getNodeFs, isDeno } from "./_utils.ts";
import { mapError } from "./_map_error.ts";
import { toDirEntry } from "./_to_dir_entry.ts";
import type { DirEntry } from "./unstable_types.ts";

/** Reads the directory given by `path` and returns an async iterable of
* {@linkcode DirEntry}. The order of entries is not guaranteed.
*
* @example Usage
* ```ts
* import { readDir } from "@std/fs/unstable-read-dir";
*
* for await (const dirEntry of readDir("/")) {
* console.log(dirEntry.name);
* }
* ```
*
* Throws error if `path` is not a directory.
*
* Requires `allow-read` permission.
*
* @tags allow-read
* @category File System
*/
export async function* readDir(path: string | URL): AsyncIterable<DirEntry> {
if (isDeno) {
yield* Deno.readDir(path);
} else {
try {
const dir = await getNodeFs().promises.opendir(path);
for await (const entry of dir) {
yield toDirEntry(entry);
}
} catch (error) {
throw mapError(error);
}
}

Check warning on line 39 in fs/unstable_read_dir.ts

View check run for this annotation

Codecov / codecov/patch

fs/unstable_read_dir.ts#L31-L39

Added lines #L31 - L39 were not covered by tests
}
41 changes: 41 additions & 0 deletions fs/unstable_read_dir_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2018-2025 the Deno authors. MIT license.

import { assert, assertEquals, assertRejects } from "@std/assert";
import { fromFileUrl, join, resolve } from "@std/path";
import { readDir } from "./unstable_read_dir.ts";
import { NotFound } from "./unstable_errors.js";

const testdataDir = resolve(fromFileUrl(import.meta.url), "../testdata");

Deno.test("readDir() reads from the directory and its subdirectories", async () => {
const files = [];
for await (const e of readDir(testdataDir)) {
files.push(e);
}

let counter = 0;
for (const f of files) {
if (f.name === "walk") {
assert(f.isDirectory);
counter++;
}
}

assertEquals(counter, 1);
});

Deno.test("readDir() rejects when the path is not a directory", async () => {
await assertRejects(async () => {
const testFile = join(testdataDir, "0.ts");
await readDir(testFile)[Symbol.asyncIterator]().next();
}, Error);
});

Deno.test("readDir() rejects when the directory does not exist", async () => {
await assertRejects(
async () => {
await readDir("non_existent_dir")[Symbol.asyncIterator]().next();
},
NotFound,
);
});
19 changes: 19 additions & 0 deletions fs/unstable_types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,22 @@ export interface FileInfo {
* _Linux/Mac OS only._ */
isSocket: boolean | null;
}

/**
* Information about a directory entry returned from {@linkcode readDir}
* and {@linkcode readDirSync}.
*/
export interface DirEntry {
kt3k marked this conversation as resolved.
Show resolved Hide resolved
/** The file name of the entry. It is just the entity name and does not
* include the full path. */
name: string;
/** True if this is info for a regular file. Mutually exclusive to
* `FileInfo.isDirectory` and `FileInfo.isSymlink`. */
isFile: boolean;
/** True if this is info for a regular directory. Mutually exclusive to
* `FileInfo.isFile` and `FileInfo.isSymlink`. */
isDirectory: boolean;
/** True if this is info for a symlink. Mutually exclusive to
* `FileInfo.isFile` and `FileInfo.isDirectory`. */
isSymlink: boolean;
}
Loading