Skip to content

Commit d2033db

Browse files
Create a facade for node:fs (#2224)
Localize the import surface with `node:fs` with an explicit facade. Currently the facade API is 1-1 the (used part of) original API with the exception of some simplifications. In the future, this facade can help simplify migration as the original API changes or (perhaps more likely in the case of a built-in library) aid in writing code that supports both older and newer version of the API when multiple runtime versions are supported.
1 parent ff2d661 commit d2033db

5 files changed

Lines changed: 120 additions & 2 deletions

File tree

src/internal/fs.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* @overview Provides an interface for interacting with the file system.
3+
* @license MPL-2.0
4+
*/
5+
6+
import * as fs from "node:fs";
7+
8+
/**
9+
* Synchronously look up if the `path` exists.
10+
*
11+
* @param {string} path The path to look up.
12+
* @param {object} [deps] Node.js' fs module.
13+
* @returns {boolean} `true` if the path exists, `false` otherwise.
14+
*/
15+
export function existsSync(path, deps = fs) {
16+
return deps.existsSync(path);
17+
}
18+
19+
/**
20+
* Synchronously look up the (target) location of a symbolic link.
21+
*
22+
* @param {string} path The path to look up.
23+
* @param {object} [deps] Node.js' fs module.
24+
* @returns {string} The contents of `path`.
25+
* @throws {Error} If `path` is not a symbolic link.
26+
*/
27+
export function readlinkSync(path, deps = fs) {
28+
return deps.readlinkSync(path);
29+
}

src/internal/unix.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
* @license MPL-2.0
44
*/
55

6-
import * as fs from "node:fs";
76
import * as path from "node:path";
87

98
import which from "which";
109

10+
import * as fs from "./fs.js";
1111
import { noShell } from "./options.js";
1212
import * as bash from "./unix/bash.js";
1313
import * as busybox from "./unix/busybox.js";

src/internal/win.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
* @license MPL-2.0
44
*/
55

6-
import * as fs from "node:fs";
76
import * as path from "node:path";
87

98
import which from "which";
109

10+
import * as fs from "./fs.js";
1111
import { noShell } from "./options.js";
1212
import { hasOwn } from "./reflection.js";
1313
import * as cmd from "./win/cmd.js";

test/unit/fs/exists.test.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @overview Contains unit tests for the functionality to check if a file
3+
* exists.
4+
* @license MIT
5+
*/
6+
7+
import { testProp } from "@fast-check/ava";
8+
import test from "ava";
9+
import * as fc from "fast-check";
10+
import * as sinon from "sinon";
11+
12+
import { existsSync } from "../../../src/internal/fs.js";
13+
14+
test.before((t) => {
15+
const existsSync = sinon.mock();
16+
17+
t.context = {
18+
deps: { existsSync },
19+
};
20+
});
21+
22+
testProp(
23+
"return value",
24+
[fc.string(), fc.boolean()],
25+
(t, path, returnValue) => {
26+
t.context.deps.existsSync.resetHistory();
27+
t.context.deps.existsSync.returns(returnValue);
28+
29+
const result = existsSync(path, t.context.deps);
30+
t.is(result, returnValue);
31+
},
32+
);
33+
34+
testProp("fs usage", [fc.string(), fc.boolean()], (t, path, returnValue) => {
35+
t.context.deps.existsSync.resetHistory();
36+
t.context.deps.existsSync.returns(returnValue);
37+
38+
existsSync(path, t.context.deps);
39+
t.is(t.context.deps.existsSync.callCount, 1);
40+
t.true(t.context.deps.existsSync.calledWithExactly(path));
41+
});

test/unit/fs/readlink.test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* @overview Contains unit tests for the functionality to read a file system
3+
* link.
4+
* @license MIT
5+
*/
6+
7+
import { testProp } from "@fast-check/ava";
8+
import test from "ava";
9+
import * as fc from "fast-check";
10+
import * as sinon from "sinon";
11+
12+
import { readlinkSync } from "../../../src/internal/fs.js";
13+
14+
test.before((t) => {
15+
const readlinkSync = sinon.mock();
16+
17+
t.context = {
18+
deps: { readlinkSync },
19+
};
20+
});
21+
22+
testProp("return value", [fc.string(), fc.string()], (t, path, returnValue) => {
23+
t.context.deps.readlinkSync.resetHistory();
24+
t.context.deps.readlinkSync.returns(returnValue);
25+
26+
const result = readlinkSync(path, t.context.deps);
27+
t.is(result, returnValue);
28+
});
29+
30+
testProp(
31+
"error",
32+
[fc.string(), fc.string().map((message) => new Error(message))],
33+
(t, path, error) => {
34+
t.context.deps.readlinkSync.resetHistory();
35+
t.context.deps.readlinkSync.throws(error);
36+
37+
t.throws(() => readlinkSync(path, t.context.deps), { is: error });
38+
},
39+
);
40+
41+
testProp("fs usage", [fc.string(), fc.string()], (t, path, returnValue) => {
42+
t.context.deps.readlinkSync.resetHistory();
43+
t.context.deps.readlinkSync.returns(returnValue);
44+
45+
readlinkSync(path, t.context.deps);
46+
t.is(t.context.deps.readlinkSync.callCount, 1);
47+
t.true(t.context.deps.readlinkSync.calledWithExactly(path));
48+
});

0 commit comments

Comments
 (0)