path-class 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/path-class/index.d.ts +33 -1
- package/dist/lib/path-class/index.js +20 -1
- package/dist/lib/path-class/index.js.map +2 -2
- package/package.json +1 -1
- package/src/index.test.ts +24 -3
- package/src/index.ts +69 -2
|
@@ -1,7 +1,34 @@
|
|
|
1
|
+
import type { Dirent, ObjectEncodingOptions } from "node:fs";
|
|
1
2
|
import { cp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
declare function readDirType(options?: (ObjectEncodingOptions & {
|
|
4
|
+
withFileTypes?: false | undefined;
|
|
5
|
+
recursive?: boolean | undefined;
|
|
6
|
+
}) | BufferEncoding | null): Promise<string[]>;
|
|
7
|
+
declare function readDirType(options: {
|
|
8
|
+
encoding: "buffer";
|
|
9
|
+
withFileTypes?: false | undefined;
|
|
10
|
+
recursive?: boolean | undefined;
|
|
11
|
+
} | "buffer"): Promise<Buffer[]>;
|
|
12
|
+
declare function readDirType(options?: (ObjectEncodingOptions & {
|
|
13
|
+
withFileTypes?: false | undefined;
|
|
14
|
+
recursive?: boolean | undefined;
|
|
15
|
+
}) | BufferEncoding | null): Promise<string[] | Buffer[]>;
|
|
16
|
+
declare function readDirType(options: ObjectEncodingOptions & {
|
|
17
|
+
withFileTypes: true;
|
|
18
|
+
recursive?: boolean | undefined;
|
|
19
|
+
}): Promise<Dirent[]>;
|
|
20
|
+
declare function readDirType(options: {
|
|
21
|
+
encoding: "buffer";
|
|
22
|
+
withFileTypes: true;
|
|
23
|
+
recursive?: boolean | undefined;
|
|
24
|
+
}): Promise<Dirent<Buffer>[]>;
|
|
2
25
|
export declare class Path {
|
|
3
26
|
#private;
|
|
4
27
|
constructor(path: string | URL | Path);
|
|
28
|
+
/**
|
|
29
|
+
* Same as `.toString()`, but more concise.
|
|
30
|
+
*/
|
|
31
|
+
get path(): string;
|
|
5
32
|
toString(): string;
|
|
6
33
|
join(...segments: string[]): Path;
|
|
7
34
|
extendBasename(suffix: string): Path;
|
|
@@ -35,7 +62,10 @@ export declare class Path {
|
|
|
35
62
|
rm_rf(options?: Parameters<typeof rm>[1]): Promise<void>;
|
|
36
63
|
fileText(): Promise<string>;
|
|
37
64
|
fileJSON<T>(): Promise<T>;
|
|
38
|
-
/**
|
|
65
|
+
/** Creates intermediate directories if they do not exist.
|
|
66
|
+
*
|
|
67
|
+
* Returns the original `Path` (for chaining).
|
|
68
|
+
*/
|
|
39
69
|
write(data: Parameters<typeof writeFile>[1], options?: Parameters<typeof writeFile>[2]): Promise<Path>;
|
|
40
70
|
/**
|
|
41
71
|
* If only `data` is provided, this is equivalent to:
|
|
@@ -49,6 +79,7 @@ export declare class Path {
|
|
|
49
79
|
* Returns the original `Path` (for chaining).
|
|
50
80
|
*/
|
|
51
81
|
writeJSON<T>(data: T, replacer?: Parameters<typeof JSON.stringify>[1], space?: Parameters<typeof JSON.stringify>[2]): Promise<Path>;
|
|
82
|
+
readDir: typeof readDirType;
|
|
52
83
|
static get homedir(): Path;
|
|
53
84
|
static xdg: {
|
|
54
85
|
cache: Path;
|
|
@@ -57,3 +88,4 @@ export declare class Path {
|
|
|
57
88
|
state: Path;
|
|
58
89
|
};
|
|
59
90
|
}
|
|
91
|
+
export {};
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
cp,
|
|
4
4
|
mkdir,
|
|
5
5
|
mkdtemp,
|
|
6
|
+
readdir,
|
|
6
7
|
readFile,
|
|
7
8
|
rename,
|
|
8
9
|
rm,
|
|
@@ -35,6 +36,12 @@ var Path = class _Path {
|
|
|
35
36
|
#setNormalizedPath(path) {
|
|
36
37
|
this.#path = join(path);
|
|
37
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Same as `.toString()`, but more concise.
|
|
41
|
+
*/
|
|
42
|
+
get path() {
|
|
43
|
+
return this.#path;
|
|
44
|
+
}
|
|
38
45
|
toString() {
|
|
39
46
|
return this.#path;
|
|
40
47
|
}
|
|
@@ -170,8 +177,12 @@ var Path = class _Path {
|
|
|
170
177
|
async fileJSON() {
|
|
171
178
|
return JSON.parse(await this.fileText());
|
|
172
179
|
}
|
|
173
|
-
/**
|
|
180
|
+
/** Creates intermediate directories if they do not exist.
|
|
181
|
+
*
|
|
182
|
+
* Returns the original `Path` (for chaining).
|
|
183
|
+
*/
|
|
174
184
|
async write(data, options) {
|
|
185
|
+
await this.parent.mkdir();
|
|
175
186
|
await writeFile(this.#path, data, options);
|
|
176
187
|
return this;
|
|
177
188
|
}
|
|
@@ -190,6 +201,14 @@ var Path = class _Path {
|
|
|
190
201
|
await this.write(JSON.stringify(data, replacer, space));
|
|
191
202
|
return this;
|
|
192
203
|
}
|
|
204
|
+
// Normally we'd add a `@deprecated` alias named `.readdir`, but that would
|
|
205
|
+
// differ only by capitalization of a single non-leading character. This can
|
|
206
|
+
// be a bit confusing, especially when autocompleting. So for this function in
|
|
207
|
+
// particular we don't include an alias.
|
|
208
|
+
readDir = (options) => (
|
|
209
|
+
// biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.
|
|
210
|
+
readdir(this.#path, options)
|
|
211
|
+
);
|
|
193
212
|
static get homedir() {
|
|
194
213
|
return new _Path(homedir());
|
|
195
214
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["import {\n cp,\n mkdir,\n mkdtemp,\n readFile,\n rename,\n rm,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { default as trash } from \"trash\";\nimport { xdgCache, xdgConfig, xdgData, xdgState } from \"xdg-basedir\";\n\n//
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["import type { Dirent, ObjectEncodingOptions } from \"node:fs\";\nimport {\n cp,\n mkdir,\n mkdtemp,\n readdir,\n readFile,\n rename,\n rm,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, extname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { default as trash } from \"trash\";\nimport { xdgCache, xdgConfig, xdgData, xdgState } from \"xdg-basedir\";\n\n// Modifying the type of `readdir(\u2026)` from `node:fs/promises` to remove the\n// first parameter is difficult, if not impossible. So we give up and duplicate\n// the types manually. This ensures ergonomic types, such as an inferred return\n// type of `string[]` when `options` is not passed.\n\ndeclare function readDirType(\n options?:\n | (ObjectEncodingOptions & {\n withFileTypes?: false | undefined;\n recursive?: boolean | undefined;\n })\n | BufferEncoding\n | null,\n): Promise<string[]>;\n\ndeclare function readDirType(\n options:\n | {\n encoding: \"buffer\";\n withFileTypes?: false | undefined;\n recursive?: boolean | undefined;\n }\n | \"buffer\",\n): Promise<Buffer[]>;\n\ndeclare function readDirType(\n options?:\n | (ObjectEncodingOptions & {\n withFileTypes?: false | undefined;\n recursive?: boolean | undefined;\n })\n | BufferEncoding\n | null,\n): Promise<string[] | Buffer[]>;\n\ndeclare function readDirType(\n options: ObjectEncodingOptions & {\n withFileTypes: true;\n recursive?: boolean | undefined;\n },\n): Promise<Dirent[]>;\n\ndeclare function readDirType(options: {\n encoding: \"buffer\";\n withFileTypes: true;\n recursive?: boolean | undefined;\n}): Promise<Dirent<Buffer>[]>;\n\nexport class Path {\n // @ts-expect-error ts(2564): False positive. https://github.com/microsoft/TypeScript/issues/32194\n #path: string;\n constructor(path: string | URL | Path) {\n if (path instanceof Path) {\n this.#setNormalizedPath(path.#path);\n return;\n }\n if (path instanceof URL) {\n this.#setNormalizedPath(fileURLToPath(path));\n return;\n }\n if (typeof path === \"string\") {\n this.#setNormalizedPath(path);\n return;\n }\n throw new Error(\"Invalid path\");\n }\n\n #setNormalizedPath(path: string): void {\n this.#path = join(path);\n }\n\n /**\n * Same as `.toString()`, but more concise.\n */\n get path() {\n return this.#path;\n }\n\n toString(): string {\n return this.#path;\n }\n\n /// Constructs a new path by appending the given path segments.\n // TODO: accept `Path` inputs?\n join(...segments: string[]): Path {\n return new Path(join(this.#path, ...segments));\n }\n\n extendBasename(suffix: string): Path {\n const joinedSuffix = join(suffix);\n if (joinedSuffix !== basename(joinedSuffix)) {\n throw new Error(\"Invalid suffix to extend file name.\");\n }\n // TODO: join basename and dirname instead?\n return new Path(this.#path + joinedSuffix);\n }\n\n get parent(): Path {\n return new Path(dirname(this.#path));\n }\n\n // Normally I'd stick with `node`'s name, but I think `.dirname` is a\n // particularly poor name. So we support `.dirname` for discovery but mark it\n // as deprecated, even if it will never be removed.\n /** @deprecated Alias for `.parent`. */\n get dirname(): Path {\n return this.parent;\n }\n\n get basename(): Path {\n return new Path(basename(this.#path));\n }\n\n get extension(): string {\n this.#mustNotHaveTrailingSlash();\n return extname(this.#path);\n }\n\n // Normally I'd stick with `node`'s name, but I think `.extname` is a\n // particularly poor name. So we support `.extname` for discovery but mark it\n // as deprecated, even if it will never be removed.\n /** @deprecated Alias for `.extension`. */\n get extname(): string {\n return this.extension;\n }\n\n #mustNotHaveTrailingSlash(): void {\n if (this.#path.endsWith(\"/\")) {\n throw new Error(\n \"Path ends with a slash, which cannot be treated as a file.\",\n );\n }\n }\n\n async exists(constraints?: {\n mustBe: \"file\" | \"directory\";\n }): Promise<boolean> {\n let stats: Awaited<ReturnType<typeof stat>>;\n try {\n stats = await stat(this.#path);\n // biome-ignore lint/suspicious/noExplicitAny: TypeScript limitation\n } catch (e: any) {\n if (e.code === \"ENOENT\") {\n return false;\n }\n throw e;\n }\n if (!constraints?.mustBe) {\n return true;\n }\n switch (constraints?.mustBe) {\n case \"file\": {\n this.#mustNotHaveTrailingSlash();\n if (stats.isFile()) {\n return true;\n }\n throw new Error(`Path exists but is not a file: ${this.#path}`);\n }\n case \"directory\": {\n if (stats.isDirectory()) {\n return true;\n }\n throw new Error(`Path exists but is not a directory: ${this.#path}`);\n }\n default: {\n throw new Error(\"Invalid path type constraint\");\n }\n }\n }\n\n async existsAsFile(): Promise<boolean> {\n return this.exists({ mustBe: \"file\" });\n }\n\n async existsAsDir(): Promise<boolean> {\n return this.exists({ mustBe: \"directory\" });\n }\n\n // I don't think `mkdir` is a great name, but it does match the\n // well-established canonical commandline name. So in this case we keep the\n // awkward abbreviation.\n /** Defaults to `recursive: true`. */\n async mkdir(options?: Parameters<typeof mkdir>[1]): Promise<Path> {\n const optionsObject = (() => {\n if (typeof options === \"string\" || typeof options === \"number\") {\n return { mode: options };\n }\n return options ?? {};\n })();\n await mkdir(this.#path, { recursive: true, ...optionsObject });\n return this;\n }\n\n // TODO: check idempotency semantics when the destination exists and is a folder.\n /** Returns the destination path. */\n async cp(\n destination: string | URL | Path,\n options?: Parameters<typeof cp>[2],\n ): Promise<Path> {\n await cp(this.#path, new Path(destination).#path, options);\n return new Path(destination);\n }\n\n // TODO: check idempotency semantics when the destination exists and is a folder.\n async rename(destination: string | URL | Path): Promise<void> {\n await rename(this.#path, new Path(destination).#path);\n }\n\n /** Create a temporary dir inside the global temp dir for the current user. */\n static async makeTempDir(prefix?: string): Promise<Path> {\n return new Path(\n await mkdtemp(new Path(tmpdir()).join(prefix ?? \"js-temp-\").toString()),\n );\n }\n\n async trash(): Promise<void> {\n await trash(this.#path, { glob: false });\n }\n\n async rm(options?: Parameters<typeof rm>[1]): Promise<void> {\n await rm(this.#path, options);\n }\n\n /**\n * Equivalent to:\n *\n * .rm({ recursive: true, force: true, ...(options ?? {}) })\n *\n */\n async rm_rf(options?: Parameters<typeof rm>[1]): Promise<void> {\n await this.rm({ recursive: true, force: true, ...(options ?? {}) });\n }\n\n async fileText(): Promise<string> {\n return readFile(this.#path, \"utf-8\");\n }\n\n async fileJSON<T>(): Promise<T> {\n return JSON.parse(await this.fileText());\n }\n\n /** Creates intermediate directories if they do not exist.\n *\n * Returns the original `Path` (for chaining).\n */\n async write(\n data: Parameters<typeof writeFile>[1],\n options?: Parameters<typeof writeFile>[2],\n ): Promise<Path> {\n await this.parent.mkdir();\n await writeFile(this.#path, data, options);\n return this;\n }\n\n /**\n * If only `data` is provided, this is equivalent to:\n *\n * .write(JSON.stringify(data, null, \" \"));\n *\n * `replacer` and `space` can also be specified, making this equivalent to:\n *\n * .write(JSON.stringify(data, replacer, space));\n *\n * Returns the original `Path` (for chaining).\n */\n async writeJSON<T>(\n data: T,\n replacer: Parameters<typeof JSON.stringify>[1] = null,\n space: Parameters<typeof JSON.stringify>[2] = \" \",\n ): Promise<Path> {\n await this.write(JSON.stringify(data, replacer, space));\n return this;\n }\n\n // Normally we'd add a `@deprecated` alias named `.readdir`, but that would\n // differ only by capitalization of a single non-leading character. This can\n // be a bit confusing, especially when autocompleting. So for this function in\n // particular we don't include an alias.\n readDir: typeof readDirType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n readdir(this.#path, options as any) as any;\n\n static get homedir(): Path {\n return new Path(homedir());\n }\n\n static xdg = {\n cache: new Path(xdgCache ?? Path.homedir.join(\".cache\")),\n config: new Path(xdgConfig ?? Path.homedir.join(\".config\")),\n data: new Path(xdgData ?? Path.homedir.join(\".local/share\")),\n state: new Path(xdgState ?? Path.homedir.join(\".local/state\")),\n };\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
package/src/index.test.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { expect, test } from "bun:test";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
import { Path } from ".";
|
|
4
5
|
|
|
5
6
|
test("constructor", async () => {
|
|
@@ -174,7 +175,7 @@ test("rm (folder)", async () => {
|
|
|
174
175
|
const file = tempDir.join("file.txt");
|
|
175
176
|
await file.write("");
|
|
176
177
|
expect(await tempDir.existsAsDir()).toBe(true);
|
|
177
|
-
expect(async () => tempDir.rm()).toThrowError(/EACCES/);
|
|
178
|
+
expect(async () => tempDir.rm()).toThrowError(/EACCES|EFAULT/);
|
|
178
179
|
await file.rm();
|
|
179
180
|
await tempDir.rm({ recursive: true });
|
|
180
181
|
expect(await tempDir.existsAsDir()).toBe(false);
|
|
@@ -224,10 +225,19 @@ test(".fileJSON()", async () => {
|
|
|
224
225
|
});
|
|
225
226
|
|
|
226
227
|
test(".write(…)", async () => {
|
|
227
|
-
const
|
|
228
|
+
const tempDir = await Path.makeTempDir();
|
|
229
|
+
const file = tempDir.join("file.json");
|
|
228
230
|
await file.write("foo");
|
|
229
231
|
|
|
230
|
-
expect(
|
|
232
|
+
expect(
|
|
233
|
+
await readFile(join(tempDir.toString(), "./file.json"), "utf-8"),
|
|
234
|
+
).toEqual("foo");
|
|
235
|
+
|
|
236
|
+
const file2 = tempDir.join("nested/file2.json");
|
|
237
|
+
await file2.write("bar");
|
|
238
|
+
expect(
|
|
239
|
+
await readFile(join(tempDir.toString(), "./nested/file2.json"), "utf-8"),
|
|
240
|
+
).toEqual("bar");
|
|
231
241
|
});
|
|
232
242
|
|
|
233
243
|
test(".writeJSON(…)", async () => {
|
|
@@ -237,6 +247,17 @@ test(".writeJSON(…)", async () => {
|
|
|
237
247
|
expect(await file.fileJSON()).toEqual<Record<string, string>>({ foo: "bar" });
|
|
238
248
|
});
|
|
239
249
|
|
|
250
|
+
test(".readDir(…)", async () => {
|
|
251
|
+
const dir = await Path.makeTempDir();
|
|
252
|
+
await dir.join("file.txt").write("hello");
|
|
253
|
+
await dir.join("dir/file.json").write("hello");
|
|
254
|
+
|
|
255
|
+
const contentsAsStrings = await dir.readDir();
|
|
256
|
+
expect(new Set(contentsAsStrings)).toEqual(new Set(["file.txt", "dir"]));
|
|
257
|
+
|
|
258
|
+
// const contentsAsEntries = await dir.readDir({ withFileTypes: true });
|
|
259
|
+
});
|
|
260
|
+
|
|
240
261
|
test("homedir", async () => {
|
|
241
262
|
expect(Path.homedir.toString()).toEqual("/mock/home/dir");
|
|
242
263
|
});
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { Dirent, ObjectEncodingOptions } from "node:fs";
|
|
1
2
|
import {
|
|
2
3
|
cp,
|
|
3
4
|
mkdir,
|
|
4
5
|
mkdtemp,
|
|
6
|
+
readdir,
|
|
5
7
|
readFile,
|
|
6
8
|
rename,
|
|
7
9
|
rm,
|
|
@@ -14,7 +16,53 @@ import { fileURLToPath } from "node:url";
|
|
|
14
16
|
import { default as trash } from "trash";
|
|
15
17
|
import { xdgCache, xdgConfig, xdgData, xdgState } from "xdg-basedir";
|
|
16
18
|
|
|
17
|
-
//
|
|
19
|
+
// Modifying the type of `readdir(…)` from `node:fs/promises` to remove the
|
|
20
|
+
// first parameter is difficult, if not impossible. So we give up and duplicate
|
|
21
|
+
// the types manually. This ensures ergonomic types, such as an inferred return
|
|
22
|
+
// type of `string[]` when `options` is not passed.
|
|
23
|
+
|
|
24
|
+
declare function readDirType(
|
|
25
|
+
options?:
|
|
26
|
+
| (ObjectEncodingOptions & {
|
|
27
|
+
withFileTypes?: false | undefined;
|
|
28
|
+
recursive?: boolean | undefined;
|
|
29
|
+
})
|
|
30
|
+
| BufferEncoding
|
|
31
|
+
| null,
|
|
32
|
+
): Promise<string[]>;
|
|
33
|
+
|
|
34
|
+
declare function readDirType(
|
|
35
|
+
options:
|
|
36
|
+
| {
|
|
37
|
+
encoding: "buffer";
|
|
38
|
+
withFileTypes?: false | undefined;
|
|
39
|
+
recursive?: boolean | undefined;
|
|
40
|
+
}
|
|
41
|
+
| "buffer",
|
|
42
|
+
): Promise<Buffer[]>;
|
|
43
|
+
|
|
44
|
+
declare function readDirType(
|
|
45
|
+
options?:
|
|
46
|
+
| (ObjectEncodingOptions & {
|
|
47
|
+
withFileTypes?: false | undefined;
|
|
48
|
+
recursive?: boolean | undefined;
|
|
49
|
+
})
|
|
50
|
+
| BufferEncoding
|
|
51
|
+
| null,
|
|
52
|
+
): Promise<string[] | Buffer[]>;
|
|
53
|
+
|
|
54
|
+
declare function readDirType(
|
|
55
|
+
options: ObjectEncodingOptions & {
|
|
56
|
+
withFileTypes: true;
|
|
57
|
+
recursive?: boolean | undefined;
|
|
58
|
+
},
|
|
59
|
+
): Promise<Dirent[]>;
|
|
60
|
+
|
|
61
|
+
declare function readDirType(options: {
|
|
62
|
+
encoding: "buffer";
|
|
63
|
+
withFileTypes: true;
|
|
64
|
+
recursive?: boolean | undefined;
|
|
65
|
+
}): Promise<Dirent<Buffer>[]>;
|
|
18
66
|
|
|
19
67
|
export class Path {
|
|
20
68
|
// @ts-expect-error ts(2564): False positive. https://github.com/microsoft/TypeScript/issues/32194
|
|
@@ -39,6 +87,13 @@ export class Path {
|
|
|
39
87
|
this.#path = join(path);
|
|
40
88
|
}
|
|
41
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Same as `.toString()`, but more concise.
|
|
92
|
+
*/
|
|
93
|
+
get path() {
|
|
94
|
+
return this.#path;
|
|
95
|
+
}
|
|
96
|
+
|
|
42
97
|
toString(): string {
|
|
43
98
|
return this.#path;
|
|
44
99
|
}
|
|
@@ -202,11 +257,15 @@ export class Path {
|
|
|
202
257
|
return JSON.parse(await this.fileText());
|
|
203
258
|
}
|
|
204
259
|
|
|
205
|
-
/**
|
|
260
|
+
/** Creates intermediate directories if they do not exist.
|
|
261
|
+
*
|
|
262
|
+
* Returns the original `Path` (for chaining).
|
|
263
|
+
*/
|
|
206
264
|
async write(
|
|
207
265
|
data: Parameters<typeof writeFile>[1],
|
|
208
266
|
options?: Parameters<typeof writeFile>[2],
|
|
209
267
|
): Promise<Path> {
|
|
268
|
+
await this.parent.mkdir();
|
|
210
269
|
await writeFile(this.#path, data, options);
|
|
211
270
|
return this;
|
|
212
271
|
}
|
|
@@ -231,6 +290,14 @@ export class Path {
|
|
|
231
290
|
return this;
|
|
232
291
|
}
|
|
233
292
|
|
|
293
|
+
// Normally we'd add a `@deprecated` alias named `.readdir`, but that would
|
|
294
|
+
// differ only by capitalization of a single non-leading character. This can
|
|
295
|
+
// be a bit confusing, especially when autocompleting. So for this function in
|
|
296
|
+
// particular we don't include an alias.
|
|
297
|
+
readDir: typeof readDirType = (options) =>
|
|
298
|
+
// biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.
|
|
299
|
+
readdir(this.#path, options as any) as any;
|
|
300
|
+
|
|
234
301
|
static get homedir(): Path {
|
|
235
302
|
return new Path(homedir());
|
|
236
303
|
}
|