Skip to content

Commit 148107e

Browse files
authored
feat(fs/unstable): add statSync and lstatSync (#6300)
1 parent 9b86f0f commit 148107e

File tree

8 files changed

+162
-45
lines changed

8 files changed

+162
-45
lines changed

_tools/check_docs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ const ENTRY_POINTS = [
6262
"../front_matter/mod.ts",
6363
"../front_matter/unstable_yaml.ts",
6464
"../fs/mod.ts",
65+
"../fs/unstable_lstat.ts",
66+
"../fs/unstable_stat.ts",
67+
"../fs/unstable_types.ts",
6568
"../html/mod.ts",
6669
"../html/unstable_is_valid_custom_element_name.ts",
6770
"../http/mod.ts",

fs/_utils.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ function checkWindows(): boolean {
2121
return false;
2222
}
2323

24-
export function getNodeFsPromises() {
25-
return (globalThis as any).process.getBuiltinModule("node:fs/promises");
24+
/**
25+
* @returns The Node.js `fs` module.
26+
*/
27+
export function getNodeFs() {
28+
return (globalThis as any).process.getBuiltinModule("node:fs");
2629
}

fs/deno.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
"./exists": "./exists.ts",
1414
"./expand-glob": "./expand_glob.ts",
1515
"./move": "./move.ts",
16-
"./unstable-stat": "./unstable_stat.ts",
1716
"./unstable-lstat": "./unstable_lstat.ts",
17+
"./unstable-stat": "./unstable_stat.ts",
18+
"./unstable-types": "./unstable_types.ts",
1819
"./walk": "./walk.ts"
1920
}
2021
}

fs/unstable_lstat.ts

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,68 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3-
import { getNodeFsPromises, isDeno } from "./_utils.ts";
3+
import { getNodeFs, isDeno } from "./_utils.ts";
44
import { mapError } from "./_map_error.ts";
55
import { toFileInfo } from "./_to_file_info.ts";
66
import type { FileInfo } from "./unstable_types.ts";
77

8-
/** Resolves to a {@linkcode FileInfo} for the specified `path`. If `path` is a symlink, information for the symlink will be returned instead of what it points to.
8+
/**
9+
* Resolves to a {@linkcode FileInfo} for the specified `path`. If `path` is a symlink, information for the symlink will be returned instead of what it points to.
910
*
11+
* Requires `allow-read` permission in Deno.
12+
*
13+
* @example Usage
1014
* ```ts
1115
* import { assert } from "@std/assert";
1216
* import { lstat } from "@std/fs/unstable-lstat";
1317
* const fileInfo = await lstat("README.md");
1418
* assert(fileInfo.isFile);
1519
* ```
1620
*
17-
* Requires `allow-read` permission.
18-
*
1921
* @tags allow-read
20-
* @category File System
22+
*
23+
* @param path The path to the file or directory.
24+
* @returns A promise that resolves to a {@linkcode FileInfo} for the specified `path`.
2125
*/
2226
export async function lstat(path: string | URL): Promise<FileInfo> {
2327
if (isDeno) {
2428
return Deno.lstat(path);
2529
} else {
26-
const fsPromises = getNodeFsPromises();
2730
try {
28-
const stat = await fsPromises.lstat(path);
29-
return toFileInfo(stat);
31+
return toFileInfo(await getNodeFs().promises.lstat(path));
32+
} catch (error) {
33+
throw mapError(error);
34+
}
35+
}
36+
}
37+
38+
/**
39+
* Synchronously returns a {@linkcode FileInfo} for the specified
40+
* `path`. If `path` is a symlink, information for the symlink will be
41+
* returned instead of what it points to.
42+
*
43+
* Requires `allow-read` permission in Deno.
44+
*
45+
* @example Usage
46+
*
47+
* ```ts
48+
* import { assert } from "@std/assert";
49+
* import { lstatSync } from "@std/fs/unstable-lstat";
50+
*
51+
* const fileInfo = lstatSync("README.md");
52+
* assert(fileInfo.isFile);
53+
* ```
54+
*
55+
* @tags allow-read
56+
*
57+
* @param path The path to the file or directory.
58+
* @returns A {@linkcode FileInfo} for the specified `path`.
59+
*/
60+
export function lstatSync(path: string | URL): FileInfo {
61+
if (isDeno) {
62+
return Deno.lstatSync(path);
63+
} else {
64+
try {
65+
return toFileInfo(getNodeFs().lstatSync(path));
3066
} catch (error) {
3167
throw mapError(error);
3268
}

fs/unstable_lstat_test.ts

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,48 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3-
import { assert, assertRejects } from "@std/assert";
4-
import { lstat } from "./unstable_lstat.ts";
3+
import { assert, assertRejects, assertThrows } from "@std/assert";
4+
import { lstat, lstatSync } from "./unstable_lstat.ts";
55
import { NotFound } from "./unstable_errors.js";
66

7-
Deno.test("lstat() returns FileInfo for a file", async () => {
8-
const fileInfo = await lstat("README.md");
9-
10-
assert(fileInfo.isFile);
7+
Deno.test("lstat() and lstatSync() return FileInfo for a file", async () => {
8+
{
9+
const fileInfo = await lstat("README.md");
10+
assert(fileInfo.isFile);
11+
}
12+
{
13+
const fileInfo = lstatSync("README.md");
14+
assert(fileInfo.isFile);
15+
}
1116
});
1217

13-
Deno.test("lstat() does not follow symlinks", async () => {
14-
const linkFile = `${import.meta.dirname}/testdata/0-link`;
15-
const fileInfo = await lstat(linkFile);
16-
17-
assert(fileInfo.isSymlink);
18+
Deno.test("lstat() and lstatSync() do not follow symlinks", async () => {
19+
const linkFile = new URL("testdata/0-link", import.meta.url);
20+
{
21+
const fileInfo = await lstat(linkFile);
22+
assert(fileInfo.isSymlink);
23+
}
24+
{
25+
const fileInfo = lstatSync(linkFile);
26+
assert(fileInfo.isSymlink);
27+
}
1828
});
1929

20-
Deno.test("lstat() returns FileInfo for a directory", async () => {
21-
const fileInfo = await lstat("fs");
22-
23-
assert(fileInfo.isDirectory);
30+
Deno.test("lstat() and lstatSync() return FileInfo for a directory", async () => {
31+
{
32+
const fileInfo = await lstat("fs");
33+
assert(fileInfo.isDirectory);
34+
}
35+
{
36+
const fileInfo = lstatSync("fs");
37+
assert(fileInfo.isDirectory);
38+
}
2439
});
2540

26-
Deno.test("lstat() rejects with NotFound for a non-existent file", async () => {
41+
Deno.test("lstat() and lstatSync() throw with NotFound for a non-existent file", async () => {
2742
await assertRejects(async () => {
2843
await lstat("non_existent_file");
2944
}, NotFound);
45+
assertThrows(() => {
46+
lstatSync("non_existent_file");
47+
}, NotFound);
3048
});

fs/unstable_stat.ts

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,67 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3-
import { getNodeFsPromises, isDeno } from "./_utils.ts";
3+
import { getNodeFs, isDeno } from "./_utils.ts";
44
import { mapError } from "./_map_error.ts";
55
import { toFileInfo } from "./_to_file_info.ts";
66
import type { FileInfo } from "./unstable_types.ts";
77

8-
/** Resolves to a {@linkcode FileInfo} for the specified `path`. Will
8+
/**
9+
* Resolves to a {@linkcode FileInfo} for the specified `path`. Will
910
* always follow symlinks.
1011
*
12+
* Requires `allow-read` permission in Deno.
13+
*
14+
* @example Usage
1115
* ```ts
1216
* import { assert } from "@std/assert";
1317
* import { stat } from "@std/fs/unstable-stat";
1418
* const fileInfo = await stat("README.md");
1519
* assert(fileInfo.isFile);
1620
* ```
1721
*
18-
* Requires `allow-read` permission.
19-
*
2022
* @tags allow-read
21-
* @category File System
23+
*
24+
* @param path The path to the file or directory.
25+
* @returns A promise that resolves to a {@linkcode FileInfo} for the specified `path`.
2226
*/
2327
export async function stat(path: string | URL): Promise<FileInfo> {
2428
if (isDeno) {
2529
return Deno.stat(path);
2630
} else {
27-
const fsPromises = getNodeFsPromises();
2831
try {
29-
const stat = await fsPromises.stat(path);
30-
return toFileInfo(stat);
32+
return toFileInfo(await getNodeFs().promises.stat(path));
33+
} catch (error) {
34+
throw mapError(error);
35+
}
36+
}
37+
}
38+
39+
/**
40+
* Synchronously returns a {@linkcode FileInfo} for the specified
41+
* `path`. Will always follow symlinks.
42+
*
43+
* Requires `allow-read` permission in Deno.
44+
*
45+
* @example Usage
46+
* ```ts
47+
* import { assert } from "@std/assert";
48+
* import { statSync } from "@std/fs/unstable-stat";
49+
*
50+
* const fileInfo = statSync("README.md");
51+
* assert(fileInfo.isFile);
52+
* ```
53+
*
54+
* @tags allow-read
55+
*
56+
* @param path The path to the file or directory.
57+
* @returns A {@linkcode FileInfo} for the specified `path`.
58+
*/
59+
export function statSync(path: string | URL): FileInfo {
60+
if (isDeno) {
61+
return Deno.statSync(path);
62+
} else {
63+
try {
64+
return toFileInfo(getNodeFs().statSync(path));
3165
} catch (error) {
3266
throw mapError(error);
3367
}

fs/unstable_stat_test.ts

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,37 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3-
import { assert, assertRejects } from "@std/assert";
4-
import { stat } from "./unstable_stat.ts";
3+
import { assert, assertRejects, assertThrows } from "@std/assert";
4+
import { stat, statSync } from "./unstable_stat.ts";
55
import { NotFound } from "./unstable_errors.js";
66

7-
Deno.test("stat() returns FileInfo for a file", async () => {
8-
const fileInfo = await stat("README.md");
7+
Deno.test("stat() and statSync() return FileInfo for a file", async () => {
8+
{
9+
const fileInfo = await stat("README.md");
10+
assert(fileInfo.isFile);
11+
}
912

10-
assert(fileInfo.isFile);
13+
{
14+
const fileInfo = statSync("README.md");
15+
assert(fileInfo.isFile);
16+
}
1117
});
1218

13-
Deno.test("stat() returns FileInfo for a directory", async () => {
14-
const fileInfo = await stat("fs");
15-
16-
assert(fileInfo.isDirectory);
19+
Deno.test("stat() and statSync() return FileInfo for a directory", async () => {
20+
{
21+
const fileInfo = await stat("fs");
22+
assert(fileInfo.isDirectory);
23+
}
24+
{
25+
const fileInfo = statSync("fs");
26+
assert(fileInfo.isDirectory);
27+
}
1728
});
1829

19-
Deno.test("stat() rejects with NotFound for a non-existent file", async () => {
30+
Deno.test("stat() and statSync() throw with NotFound for a non-existent file", async () => {
2031
await assertRejects(async () => {
2132
await stat("non_existent_file");
2233
}, NotFound);
34+
assertThrows(() => {
35+
statSync("non_existent_file");
36+
}, NotFound);
2337
});

fs/unstable_types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
// Copyright 2018-2025 the Deno authors. MIT license.
22

3+
/**
4+
* Provides information about a file and is returned by
5+
* {@linkcode stat}, {@linkcode lstat}, {@linkcode statSync},
6+
* and {@linkcode lstatSync} or from calling `stat()` and `statSync()`
7+
* on an {@linkcode FsFile} instance.
8+
*
9+
* @category File System
10+
*/
311
export interface FileInfo {
412
/** True if this is info for a regular file. Mutually exclusive to
513
* `FileInfo.isDirectory` and `FileInfo.isSymlink`. */

0 commit comments

Comments
 (0)