Skip to content

Commit

Permalink
feat(fs/unstable): add readDir (#6338)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbronder authored Jan 14, 2025
1 parent 3c1b8e9 commit 55b1c7e
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 0 deletions.
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(),
};
}
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);
}
}
}
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 {
/** 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;
}

0 comments on commit 55b1c7e

Please sign in to comment.