path-class 0.8.3 → 0.9.0

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.
@@ -1,12 +1,18 @@
1
- import { appendFile, cp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
1
+ import { cp, mkdir, rm, symlink, writeFile } from "node:fs/promises";
2
2
  import type { lstatType, readDirType, readFileType, statType } from "./modifiedNodeTypes";
3
3
  type WritableData = Parameters<typeof writeFile>[1] | ReadableStream | Response;
4
+ export declare enum ResolutionPrefix {
5
+ Absolute = "absolute",
6
+ Relative = "relative",
7
+ Bare = "bare"
8
+ }
4
9
  export declare class Path {
5
10
  #private;
6
11
  /**
7
12
  * If `path` is a string starting with `file:///`, it will be parsed as a file URL.
8
13
  */
9
14
  constructor(path: string | URL | Path);
15
+ get resolutionPrefix(): ResolutionPrefix;
10
16
  /**
11
17
  * Similar to `new URL(path, base)`, but accepting and returning `Path` objects.
12
18
  * Note that `base` must be one of:
@@ -36,6 +42,67 @@ export declare class Path {
36
42
  * This follows `node` semantics for absolute paths: leading slashes in the given descendant segments are ignored.
37
43
  */
38
44
  join(...segments: (string | Path)[]): Path;
45
+ /**
46
+ * Adjust the prefix to construct a relative path.
47
+ *
48
+ * | Example input | Output |
49
+ * |-----------------|-----------------|
50
+ * | `"bare"` | `"./bare"` |
51
+ * | `"./relative"` | `"./relative"` |
52
+ * | `"../up-first"` | `"../up-first"` |
53
+ * | `"/absolute"` | `"./absolute"` |
54
+ *
55
+ */
56
+ asRelative(): Path;
57
+ /**
58
+ * Adjust the prefix to construct an absolute path.
59
+ *
60
+ * | Example input | Output |
61
+ * |-----------------|---------------|
62
+ * | `"bare"` | `"/bare"` |
63
+ * | `"./relative"` | `"/relative"` |
64
+ * | `"../up-first"` | `"/up-first"` |
65
+ * | `"/absolute"` | `"/absolute"` |
66
+ *
67
+ */
68
+ asAbsolute(): Path;
69
+ /**
70
+ * Adjust the prefix to construct a bare path. Note that this returns `"."` if
71
+ * there are no named paths left.
72
+ *
73
+ * | Example input | Output |
74
+ * |-------------------|--------------|
75
+ * | `"bare"` | `"bare"` |
76
+ * | `"./relative" ` | `"relative"` |
77
+ * | `"/absolute"` | `"absolute"` |
78
+ * | `"."` | `"."` |
79
+ * | `"down-first/.."` | `"."` |
80
+ * | `"../up-first"` | (error) |
81
+ * | `".."` | (error) |
82
+ *
83
+ * Specify `parentTraversalPrefixHandling` in the `options` if you would like
84
+ * to strip or keep resolution prefixes like `../` rather than erroring.
85
+ *
86
+ * | Example input | Output with `{ parentTraversalPrefixHandling: "strip" }` |
87
+ * |----------------------|----------------------------------------------------------|
88
+ * | `"../up-first"` | `"up-first"` |
89
+ * | `".."` | `"."` |
90
+ *
91
+ * | Example input | Output with `{ parentTraversalPrefixHandling: "keep" }` |
92
+ * |----------------------|---------------------------------------------------------|
93
+ * | `"../up-first"` | `"../up-first"` |
94
+ * | `".."` | `".."` |
95
+ *
96
+ * If you need the output to start with a named component and return values
97
+ * like `.`, `..`, `../`, or `../…` are not okay, pass
98
+ * `requireNamedComponentPrefix: true`. This is useful if the path represents
99
+ * an `npm`-style package name (e.g. `"typescript"`, `"@biomejs/biome"`).
100
+ *
101
+ */
102
+ asBare(options?: {
103
+ parentTraversalPrefixHandling?: "error" | "strip" | "keep";
104
+ requireNamedComponentPrefix?: boolean;
105
+ }): Path;
39
106
  extendBasename(suffix: string): Path;
40
107
  get parent(): Path;
41
108
  /** @deprecated Alias for `.parent`. */
@@ -77,10 +144,6 @@ export declare class Path {
77
144
  readJSON<T = any>(options?: {
78
145
  fallback?: T;
79
146
  }): Promise<T>;
80
- /**
81
- * Returns the original `Path` (for chaining).
82
- */
83
- appendFile(data: Parameters<typeof appendFile>[1], options?: Parameters<typeof appendFile>[2]): Promise<Path>;
84
147
  /** Creates intermediate directories if they do not exist.
85
148
  *
86
149
  * Returns the original `Path` (for chaining).
@@ -101,7 +164,6 @@ export declare class Path {
101
164
  readDir: typeof readDirType;
102
165
  /** Returns the destination path. */
103
166
  symlink(target: string | URL | Path, type?: Parameters<typeof symlink>[2]): Promise<Path>;
104
- realpath(): Promise<Path>;
105
167
  stat: typeof statType;
106
168
  lstat: typeof lstatType;
107
169
  static get homedir(): Path;
@@ -1,13 +1,11 @@
1
1
  // src/Path.ts
2
2
  import {
3
- appendFile,
4
3
  cp,
5
4
  lstat,
6
5
  mkdir,
7
6
  mkdtemp,
8
7
  readdir,
9
8
  readFile,
10
- realpath,
11
9
  rename,
12
10
  rm,
13
11
  stat,
@@ -35,6 +33,22 @@ async function wrangleWritableData(data) {
35
33
  }
36
34
  return data;
37
35
  }
36
+ var ResolutionPrefix = /* @__PURE__ */ ((ResolutionPrefix2) => {
37
+ ResolutionPrefix2["Absolute"] = "absolute";
38
+ ResolutionPrefix2["Relative"] = "relative";
39
+ ResolutionPrefix2["Bare"] = "bare";
40
+ return ResolutionPrefix2;
41
+ })(ResolutionPrefix || {});
42
+ function resolutionPrefix(pathString) {
43
+ if (pathString.startsWith("/")) {
44
+ return "absolute" /* Absolute */;
45
+ } else if (pathString.startsWith("./")) {
46
+ return "relative" /* Relative */;
47
+ } else if (pathString === ".") {
48
+ return "relative" /* Relative */;
49
+ }
50
+ return "bare" /* Bare */;
51
+ }
38
52
  var Path = class _Path {
39
53
  // @ts-expect-error ts(2564): False positive. https://github.com/microsoft/TypeScript/issues/32194
40
54
  #path;
@@ -42,7 +56,11 @@ var Path = class _Path {
42
56
  * If `path` is a string starting with `file:///`, it will be parsed as a file URL.
43
57
  */
44
58
  constructor(path) {
45
- this.#setNormalizedPath(_Path.#pathlikeToString(path));
59
+ const s = _Path.#pathlikeToString(path);
60
+ this.#setNormalizedPath(s);
61
+ }
62
+ get resolutionPrefix() {
63
+ return resolutionPrefix(this.#path);
46
64
  }
47
65
  /**
48
66
  * Similar to `new URL(path, base)`, but accepting and returning `Path` objects.
@@ -55,6 +73,9 @@ var Path = class _Path {
55
73
  static resolve(path, base) {
56
74
  const baseURL = (() => {
57
75
  if (!(base instanceof _Path)) {
76
+ if (typeof base === "string" && !base.startsWith("file://")) {
77
+ return pathToFileURL(base);
78
+ }
58
79
  return base;
59
80
  }
60
81
  if (!base.isAbsolutePath()) {
@@ -81,11 +102,16 @@ var Path = class _Path {
81
102
  }
82
103
  throw new Error("Invalid path");
83
104
  }
105
+ // Preserves the `ResolutionPrefix` status when possible.
84
106
  #setNormalizedPath(path) {
107
+ const prefix = resolutionPrefix(path);
85
108
  this.#path = join(path);
109
+ if (prefix === "relative" /* Relative */ && !this.#path.startsWith(".")) {
110
+ this.#path = `./${this.#path}`;
111
+ }
86
112
  }
87
113
  isAbsolutePath() {
88
- return this.#path.startsWith("/");
114
+ return this.resolutionPrefix === "absolute" /* Absolute */;
89
115
  }
90
116
  toFileURL() {
91
117
  if (!this.isAbsolutePath()) {
@@ -119,11 +145,112 @@ var Path = class _Path {
119
145
  * This follows `node` semantics for absolute paths: leading slashes in the given descendant segments are ignored.
120
146
  */
121
147
  join(...segments) {
122
- const segmentStrings = segments.map(
123
- (segment) => segment instanceof _Path ? segment.path : segment
124
- );
148
+ const segmentStrings = segments.map((segment) => {
149
+ const s = stringifyIfPath(segment);
150
+ if (resolutionPrefix(s) === "absolute" /* Absolute */) {
151
+ throw new Error(
152
+ "Arguments to `.join(\u2026)` cannot be absolute. Use `.asRelative()` to convert them first if needed."
153
+ );
154
+ }
155
+ return s;
156
+ });
125
157
  return new _Path(join(this.#path, ...segmentStrings));
126
158
  }
159
+ /**
160
+ * Adjust the prefix to construct a relative path.
161
+ *
162
+ * | Example input | Output |
163
+ * |-----------------|-----------------|
164
+ * | `"bare"` | `"./bare"` |
165
+ * | `"./relative"` | `"./relative"` |
166
+ * | `"../up-first"` | `"../up-first"` |
167
+ * | `"/absolute"` | `"./absolute"` |
168
+ *
169
+ */
170
+ asRelative() {
171
+ return new _Path(`./${this.#path}`);
172
+ }
173
+ /**
174
+ * Adjust the prefix to construct an absolute path.
175
+ *
176
+ * | Example input | Output |
177
+ * |-----------------|---------------|
178
+ * | `"bare"` | `"/bare"` |
179
+ * | `"./relative"` | `"/relative"` |
180
+ * | `"../up-first"` | `"/up-first"` |
181
+ * | `"/absolute"` | `"/absolute"` |
182
+ *
183
+ */
184
+ asAbsolute() {
185
+ return new _Path(join("/", this.#path));
186
+ }
187
+ /**
188
+ * Adjust the prefix to construct a bare path. Note that this returns `"."` if
189
+ * there are no named paths left.
190
+ *
191
+ * | Example input | Output |
192
+ * |-------------------|--------------|
193
+ * | `"bare"` | `"bare"` |
194
+ * | `"./relative" ` | `"relative"` |
195
+ * | `"/absolute"` | `"absolute"` |
196
+ * | `"."` | `"."` |
197
+ * | `"down-first/.."` | `"."` |
198
+ * | `"../up-first"` | (error) |
199
+ * | `".."` | (error) |
200
+ *
201
+ * Specify `parentTraversalPrefixHandling` in the `options` if you would like
202
+ * to strip or keep resolution prefixes like `../` rather than erroring.
203
+ *
204
+ * | Example input | Output with `{ parentTraversalPrefixHandling: "strip" }` |
205
+ * |----------------------|----------------------------------------------------------|
206
+ * | `"../up-first"` | `"up-first"` |
207
+ * | `".."` | `"."` |
208
+ *
209
+ * | Example input | Output with `{ parentTraversalPrefixHandling: "keep" }` |
210
+ * |----------------------|---------------------------------------------------------|
211
+ * | `"../up-first"` | `"../up-first"` |
212
+ * | `".."` | `".."` |
213
+ *
214
+ * If you need the output to start with a named component and return values
215
+ * like `.`, `..`, `../`, or `../…` are not okay, pass
216
+ * `requireNamedComponentPrefix: true`. This is useful if the path represents
217
+ * an `npm`-style package name (e.g. `"typescript"`, `"@biomejs/biome"`).
218
+ *
219
+ */
220
+ asBare(options) {
221
+ const path = new _Path(join(".", this.#path));
222
+ if (!path.#path.startsWith("../") && path.#path !== "..") {
223
+ if (options?.requireNamedComponentPrefix && path.resolutionPrefix === "relative" /* Relative */) {
224
+ throw new Error("Output does not start with a named component.");
225
+ }
226
+ return path;
227
+ }
228
+ const parentTraversalHandling = options?.parentTraversalPrefixHandling ?? "error";
229
+ switch (parentTraversalHandling) {
230
+ case "error": {
231
+ throw new Error(
232
+ 'Converting path to a bare path resulted in a `..` traversal prefix. Pass `"strip"` or `"keep"` as the `parentTraversalHandling` option to avoid an error.'
233
+ );
234
+ }
235
+ case "strip": {
236
+ let newPath = path.#path.replace(/^(\.\.\/)+/, "");
237
+ if (["", ".."].includes(newPath)) {
238
+ newPath = ".";
239
+ }
240
+ const output = new _Path(newPath);
241
+ if (options?.requireNamedComponentPrefix && output.resolutionPrefix === "relative" /* Relative */) {
242
+ throw new Error("Output does not start with a named component.");
243
+ }
244
+ return new _Path(newPath);
245
+ }
246
+ case "keep": {
247
+ if (options?.requireNamedComponentPrefix) {
248
+ throw new Error("Output does not start with a named component.");
249
+ }
250
+ return path;
251
+ }
252
+ }
253
+ }
127
254
  extendBasename(suffix) {
128
255
  const joinedSuffix = join(suffix);
129
256
  if (joinedSuffix !== basename(joinedSuffix)) {
@@ -262,13 +389,6 @@ var Path = class _Path {
262
389
  throw e;
263
390
  }
264
391
  }
265
- /**
266
- * Returns the original `Path` (for chaining).
267
- */
268
- async appendFile(data, options) {
269
- await appendFile(this.#path, data, options);
270
- return this;
271
- }
272
392
  /** Creates intermediate directories if they do not exist.
273
393
  *
274
394
  * Returns the original `Path` (for chaining).
@@ -312,15 +432,6 @@ var Path = class _Path {
312
432
  );
313
433
  return targetPath;
314
434
  }
315
- // I don't think `lstat` is a great name, but it does match the
316
- // well-established canonical commandline name. So in this case we keep the
317
- // name instead of using `realPath`.
318
- //
319
- // Note: There are no options in our API, because the only option is an
320
- // encoding. We set the encoding to construct the returned `Path`.
321
- async realpath() {
322
- return new _Path(await realpath(this.#path, "utf-8"));
323
- }
324
435
  // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.
325
436
  stat = (...options) => stat(this.#path, ...options);
326
437
  // I don't think `lstat` is a great name, but it does match the
@@ -377,8 +488,9 @@ function mustNotHaveTrailingSlash(path) {
377
488
  }
378
489
 
379
490
  export {
491
+ ResolutionPrefix,
380
492
  Path,
381
493
  stringifyIfPath,
382
494
  mustNotHaveTrailingSlash
383
495
  };
384
- //# sourceMappingURL=chunk-3W742CSF.js.map
496
+ //# sourceMappingURL=chunk-AB3VUARG.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../src/Path.ts"],
4
+ "sourcesContent": ["import {\n cp,\n lstat,\n mkdir,\n mkdtemp,\n readdir,\n readFile,\n rename,\n rm,\n stat,\n symlink,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, extname, join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport {\n xdgCache,\n xdgConfig,\n xdgData,\n xdgRuntime,\n xdgState,\n} from \"xdg-basedir\";\nimport type {\n lstatType,\n readDirType,\n readFileType,\n statType,\n} from \"./modifiedNodeTypes\";\n\n// Note that (non-static) functions in this file are defined using `function(\u2026)\n// { \u2026 }` rather than arrow functions, specifically because we want `this` to\n// operate on the `Path` instance.\n\ntype WritableData = Parameters<typeof writeFile>[1] | ReadableStream | Response;\nasync function wrangleWritableData(\n data: WritableData | Promise<WritableData>,\n): Promise<Parameters<typeof writeFile>[1]> {\n data = await data;\n if (data instanceof Response) {\n data = data.body ? Readable.fromWeb(data.body) : new Uint8Array(0);\n }\n if (data instanceof ReadableStream) {\n data = Readable.fromWeb(data);\n }\n return data;\n}\n\nexport enum ResolutionPrefix {\n Absolute = \"absolute\",\n Relative = \"relative\",\n Bare = \"bare\",\n}\n\nfunction resolutionPrefix(pathString: string): ResolutionPrefix {\n if (pathString.startsWith(\"/\")) {\n return ResolutionPrefix.Absolute;\n } else if (pathString.startsWith(\"./\")) {\n return ResolutionPrefix.Relative;\n } else if (pathString === \".\") {\n return ResolutionPrefix.Relative;\n }\n return ResolutionPrefix.Bare;\n}\n\nexport class Path {\n // @ts-expect-error ts(2564): False positive. https://github.com/microsoft/TypeScript/issues/32194\n #path: string;\n /**\n * If `path` is a string starting with `file:///`, it will be parsed as a file URL.\n */\n constructor(path: string | URL | Path) {\n const s = Path.#pathlikeToString(path);\n this.#setNormalizedPath(s);\n }\n\n get resolutionPrefix(): ResolutionPrefix {\n return resolutionPrefix(this.#path);\n }\n\n /**\n * Similar to `new URL(path, base)`, but accepting and returning `Path` objects.\n * Note that `base` must be one of:\n *\n * - a valid second argument to `new URL(\u2026)`.\n * - a `Path` representing an absolute path.\n *\n */\n static resolve(path: string | URL | Path, base: string | URL | Path): Path {\n const baseURL = (() => {\n if (!(base instanceof Path)) {\n if (typeof base === \"string\" && !base.startsWith(\"file://\")) {\n return pathToFileURL(base);\n }\n return base;\n }\n if (!base.isAbsolutePath()) {\n throw new Error(\n \"The `base` arg to `Path.resolve(\u2026)` must be an absolute path.\",\n );\n }\n return pathToFileURL(base.#path);\n })();\n return new Path(new URL(Path.#pathlikeToString(path), baseURL));\n }\n\n static #pathlikeToString(path: string | URL | Path): string {\n if (path instanceof Path) {\n return path.#path;\n }\n if (path instanceof URL) {\n return fileURLToPath(path);\n }\n if (typeof path === \"string\") {\n // TODO: allow turning off this heuristic?\n if (path.startsWith(\"file:///\")) {\n return fileURLToPath(path);\n }\n return path;\n }\n throw new Error(\"Invalid path\");\n }\n\n // Preserves the `ResolutionPrefix` status when possible.\n #setNormalizedPath(path: string): void {\n const prefix = resolutionPrefix(path);\n this.#path = join(path);\n if (prefix === ResolutionPrefix.Relative && !this.#path.startsWith(\".\")) {\n // We don't have to handle the case of `\".\"`, as it already starts with `\".\"`\n this.#path = `./${this.#path}`;\n }\n }\n\n isAbsolutePath(): boolean {\n return this.resolutionPrefix === ResolutionPrefix.Absolute;\n }\n\n toFileURL(): URL {\n if (!this.isAbsolutePath()) {\n throw new Error(\n \"Tried to convert to file URL when the path is not absolute.\",\n );\n }\n return pathToFileURL(this.#path);\n }\n\n /**\n * The `Path` can have a trailing slash, indicating that it represents a\n * directory. (If there is no trailing slash, it can represent either a file\n * or a directory.)\n *\n * Some operations will refuse to treat a directory path as a file path. This\n * function identifies such paths.\n */\n hasTrailingSlash(): boolean {\n // TODO: handle Windows semantically\n return this.#path.endsWith(\"/\");\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 * This follows `node` semantics for absolute paths: leading slashes in the given descendant segments are ignored.\n */\n join(...segments: (string | Path)[]): Path {\n const segmentStrings = segments.map((segment) => {\n const s = stringifyIfPath(segment);\n if (resolutionPrefix(s) === ResolutionPrefix.Absolute) {\n throw new Error(\n \"Arguments to `.join(\u2026)` cannot be absolute. Use `.asRelative()` to convert them first if needed.\",\n );\n }\n return s;\n });\n return new Path(join(this.#path, ...segmentStrings));\n }\n\n /**\n * Adjust the prefix to construct a relative path.\n *\n * | Example input | Output |\n * |-----------------|-----------------|\n * | `\"bare\"` | `\"./bare\"` |\n * | `\"./relative\"` | `\"./relative\"` |\n * | `\"../up-first\"` | `\"../up-first\"` |\n * | `\"/absolute\"` | `\"./absolute\"` |\n *\n */\n asRelative(): Path {\n return new Path(`./${this.#path}`);\n }\n\n /**\n * Adjust the prefix to construct an absolute path.\n *\n * | Example input | Output |\n * |-----------------|---------------|\n * | `\"bare\"` | `\"/bare\"` |\n * | `\"./relative\"` | `\"/relative\"` |\n * | `\"../up-first\"` | `\"/up-first\"` |\n * | `\"/absolute\"` | `\"/absolute\"` |\n *\n */\n asAbsolute(): Path {\n return new Path(join(\"/\", this.#path));\n }\n\n /**\n * Adjust the prefix to construct a bare path. Note that this returns `\".\"` if\n * there are no named paths left.\n *\n * | Example input | Output |\n * |-------------------|--------------|\n * | `\"bare\"` | `\"bare\"` |\n * | `\"./relative\" ` | `\"relative\"` |\n * | `\"/absolute\"` | `\"absolute\"` |\n * | `\".\"` | `\".\"` |\n * | `\"down-first/..\"` | `\".\"` |\n * | `\"../up-first\"` | (error) |\n * | `\"..\"` | (error) |\n *\n * Specify `parentTraversalPrefixHandling` in the `options` if you would like\n * to strip or keep resolution prefixes like `../` rather than erroring.\n *\n * | Example input | Output with `{ parentTraversalPrefixHandling: \"strip\" }` |\n * |----------------------|----------------------------------------------------------|\n * | `\"../up-first\"` | `\"up-first\"` |\n * | `\"..\"` | `\".\"` |\n *\n * | Example input | Output with `{ parentTraversalPrefixHandling: \"keep\" }` |\n * |----------------------|---------------------------------------------------------|\n * | `\"../up-first\"` | `\"../up-first\"` |\n * | `\"..\"` | `\"..\"` |\n *\n * If you need the output to start with a named component and return values\n * like `.`, `..`, `../`, or `../\u2026` are not okay, pass\n * `requireNamedComponentPrefix: true`. This is useful if the path represents\n * an `npm`-style package name (e.g. `\"typescript\"`, `\"@biomejs/biome\"`).\n *\n */\n asBare(options?: {\n parentTraversalPrefixHandling?: \"error\" | \"strip\" | \"keep\";\n requireNamedComponentPrefix?: boolean;\n }): Path {\n const path = new Path(join(\".\", this.#path));\n if (!path.#path.startsWith(\"../\") && path.#path !== \"..\") {\n if (\n options?.requireNamedComponentPrefix &&\n path.resolutionPrefix === ResolutionPrefix.Relative\n ) {\n throw new Error(\"Output does not start with a named component.\");\n }\n return path;\n }\n const parentTraversalHandling =\n options?.parentTraversalPrefixHandling ?? \"error\";\n switch (parentTraversalHandling) {\n case \"error\": {\n throw new Error(\n 'Converting path to a bare path resulted in a `..` traversal prefix. Pass `\"strip\"` or `\"keep\"` as the `parentTraversalHandling` option to avoid an error.',\n );\n }\n case \"strip\": {\n let newPath = path.#path.replace(/^(\\.\\.\\/)+/, \"\");\n if ([\"\", \"..\"].includes(newPath)) {\n newPath = \".\";\n }\n const output = new Path(newPath);\n if (\n options?.requireNamedComponentPrefix &&\n output.resolutionPrefix === ResolutionPrefix.Relative\n ) {\n throw new Error(\"Output does not start with a named component.\");\n }\n return new Path(newPath);\n }\n case \"keep\": {\n if (options?.requireNamedComponentPrefix) {\n throw new Error(\"Output does not start with a named component.\");\n }\n return path;\n }\n }\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 mustNotHaveTrailingSlash(this);\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 // TODO: find a neat way to dedup with the sync version?\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 mustNotHaveTrailingSlash(this);\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 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 read: typeof readFileType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n readFile(this.#path, options as any) as any;\n\n async readText(): Promise<string> {\n return readFile(this.#path, \"utf-8\");\n }\n\n /**\n * Reads JSON from the given file and parses it. No validation is performed\n * (beyond JSON parsing).\n *\n * An optional `fallback` value can be specified. It will be used if (and only\n * if) the file does not exist.\n *\n */\n\n // biome-ignore lint/suspicious/noExplicitAny: Allow a default of `any` to match `JSON.parse(\u2026)`.\n async readJSON<T = any>(options?: { fallback?: T }): Promise<T> {\n try {\n return JSON.parse(await this.readText());\n } catch (e) {\n if (\n (e as { code?: string }).code === \"ENOENT\" &&\n options &&\n \"fallback\" in options\n ) {\n return options.fallback as T;\n }\n throw e;\n }\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: WritableData | Promise<WritableData>,\n options?: Parameters<typeof writeFile>[2],\n ): Promise<Path> {\n await this.parent.mkdir();\n await writeFile(this.#path, await wrangleWritableData(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 /** Returns the destination path. */\n async symlink(\n target: string | URL | Path,\n type?: Parameters<typeof symlink>[2],\n ): Promise<Path> {\n const targetPath = new Path(target);\n await symlink(\n this.path,\n targetPath.path,\n type as Exclude<Parameters<typeof symlink>[2], undefined>, // \uD83E\uDD37\n );\n return targetPath;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n stat: typeof statType = (...options) => stat(this.#path, ...options) as any;\n\n // I don't think `lstat` is a great name, but it does match the\n // well-established canonical system call. So in this case we keep the\n // awkward abbreviation.\n lstat: typeof lstatType = (...options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n lstat(this.#path, ...options) 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 * {@link Path.xdg.runtime} does not have a default value. Consider\n * {@link Path.xdg.runtimeWithStateFallback} if you need a fallback but do not have a particular fallback in mind.\n */\n runtime: xdgRuntime ? new Path(xdgRuntime) : undefined,\n runtimeWithStateFallback: xdgRuntime\n ? new Path(xdgRuntime)\n : new Path(xdgState ?? Path.homedir.join(\".local/state\")),\n };\n\n /** Chainable function to print the path. Prints the same as:\n *\n * if (args.length > 0) {\n * console.log(...args);\n * }\n * console.log(this.path);\n *\n */\n // biome-ignore lint/suspicious/noExplicitAny: This is the correct type, based on `console.log(\u2026)`.\n debugPrint(...args: any[]): Path {\n if (args.length > 0) {\n console.log(...args);\n }\n console.log(this.#path);\n return this;\n }\n}\n\n/**\n * This function is useful to serialize any `Path`s in a structure to pass on to\n * functions that do not know about the `Path` class, e.g.\n *\n * function process(args: (string | Path)[]) {\n * const argsAsStrings = args.map(stringifyIfPath);\n * }\n *\n */\nexport function stringifyIfPath<T>(value: T | Path): T | string {\n if (value instanceof Path) {\n return value.toString();\n }\n return value;\n}\n\nexport function mustNotHaveTrailingSlash(path: Path): void {\n if (path.hasTrailingSlash()) {\n throw new Error(\n \"Path ends with a slash, which cannot be treated as a file.\",\n );\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["ResolutionPrefix"]
7
+ }
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  Path,
3
+ ResolutionPrefix,
3
4
  mustNotHaveTrailingSlash,
4
5
  stringifyIfPath
5
- } from "./chunks/chunk-3W742CSF.js";
6
+ } from "./chunks/chunk-AB3VUARG.js";
6
7
  export {
7
8
  Path,
9
+ ResolutionPrefix,
8
10
  mustNotHaveTrailingSlash,
9
11
  stringifyIfPath
10
12
  };
@@ -33,6 +33,13 @@ export declare function readFileType(options: ({
33
33
  export declare function readFileType(options?: (ObjectEncodingOptions & Abortable & {
34
34
  flag?: OpenMode | undefined;
35
35
  }) | BufferEncoding | null): Promise<string | Buffer>;
36
+ export declare function lstatType(opts?: StatOptions & {
37
+ bigint?: false | undefined;
38
+ }): Promise<Stats>;
39
+ export declare function lstatType(opts: StatOptions & {
40
+ bigint: true;
41
+ }): Promise<BigIntStats>;
42
+ export declare function lstatType(opts?: StatOptions): Promise<Stats | BigIntStats>;
36
43
  export declare function statType(opts?: StatOptions & {
37
44
  bigint?: false | undefined;
38
45
  }): Promise<Stats>;
@@ -40,4 +47,3 @@ export declare function statType(opts: StatOptions & {
40
47
  bigint: true;
41
48
  }): Promise<BigIntStats>;
42
49
  export declare function statType(opts?: StatOptions): Promise<Stats | BigIntStats>;
43
- export declare const lstatType: typeof statType;
@@ -1,4 +1,4 @@
1
- import { appendFileSync, cpSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
1
+ import { cpSync, mkdirSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
2
2
  import "./static";
3
3
  import type { lstatSyncType, readDirSyncType, readFileSyncType, statSyncType } from "./modifiedNodeTypes";
4
4
  declare module "../Path" {
@@ -18,13 +18,11 @@ declare module "../Path" {
18
18
  readJSONSync<T>(options?: {
19
19
  fallback?: T;
20
20
  }): T;
21
- appendFileSync(data: Parameters<typeof appendFileSync>[1], options?: Parameters<typeof appendFileSync>[2]): Path;
22
21
  writeSync(data: Parameters<typeof writeFileSync>[1], options?: Parameters<typeof writeFileSync>[2] | undefined): Path;
23
22
  writeJSONSync<T>(data: T, replacer?: Parameters<typeof JSON.stringify>[1], space?: Parameters<typeof JSON.stringify>[2]): Path;
24
23
  readDirSync: typeof readDirSyncType;
25
24
  /** Returns the destination path. */
26
25
  symlinkSync(target: string | URL | Path, type?: Parameters<typeof symlinkSync>[2]): Path;
27
- realpathSync(): Path;
28
26
  statSync: typeof statSyncType;
29
27
  lstatSync: typeof lstatSyncType;
30
28
  }
@@ -1,17 +1,15 @@
1
1
  import {
2
2
  Path,
3
3
  mustNotHaveTrailingSlash
4
- } from "../chunks/chunk-3W742CSF.js";
4
+ } from "../chunks/chunk-AB3VUARG.js";
5
5
 
6
6
  // src/sync/index.ts
7
7
  import {
8
- appendFileSync,
9
8
  cpSync,
10
9
  lstatSync,
11
10
  mkdirSync,
12
11
  readdirSync,
13
12
  readFileSync,
14
- realpathSync,
15
13
  renameSync,
16
14
  rmSync,
17
15
  statSync,
@@ -104,10 +102,6 @@ Path.prototype.readJSONSync = function(options) {
104
102
  throw e;
105
103
  }
106
104
  };
107
- Path.prototype.appendFileSync = function(data, options) {
108
- appendFileSync(this.path, data, options);
109
- return this;
110
- };
111
105
  Path.prototype.writeSync = function(data, options) {
112
106
  this.parent.mkdirSync();
113
107
  writeFileSync(this.path, data, options);
@@ -121,7 +115,7 @@ Path.prototype.writeJSONSync = function(data, replacer = null, space = " ") {
121
115
  Path.prototype.readDirSync = function(options) {
122
116
  return readdirSync(this.path, options);
123
117
  };
124
- Path.prototype.symlinkSync = function(target, type) {
118
+ Path.prototype.symlinkSync = function symlink(target, type) {
125
119
  const targetPath = new Path(target);
126
120
  symlinkSync(
127
121
  this.path,
@@ -131,9 +125,6 @@ Path.prototype.symlinkSync = function(target, type) {
131
125
  );
132
126
  return targetPath;
133
127
  };
134
- Path.prototype.realpathSync = function() {
135
- return new Path(realpathSync(this.path));
136
- };
137
128
  Path.prototype.statSync = function(options) {
138
129
  return statSync(this.path, options);
139
130
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/sync/index.ts", "../../../../src/sync/static.ts"],
4
- "sourcesContent": ["import {\n appendFileSync,\n cpSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n realpathSync,\n renameSync,\n rmSync,\n statSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { mustNotHaveTrailingSlash, Path } from \"../Path\";\nimport \"./static\";\nimport type {\n lstatSyncType,\n readDirSyncType,\n readFileSyncType,\n statSyncType,\n} from \"./modifiedNodeTypes\";\n\n// Note that (non-static) functions in this file are defined using `function(\u2026)\n// { \u2026 }` rather than arrow functions, specifically because we want `this` to\n// operate on the `Path` instance.\n\ndeclare module \"../Path\" {\n interface Path {\n existsSync(constraints?: { mustBe: \"file\" | \"directory\" }): boolean;\n existsAsFileSync(): boolean;\n existsAsDirSync(): boolean;\n\n mkdirSync(options?: Parameters<typeof mkdirSync>[1]): Path;\n cpSync(\n destination: string | URL | Path,\n options?: Parameters<typeof cpSync>[2],\n ): Path;\n renameSync(destination: string | URL | Path): void;\n\n rmSync(options?: Parameters<typeof rmSync>[1]): void;\n rm_rfSync(options?: Parameters<typeof rmSync>[1]): void;\n\n readSync: typeof readFileSyncType;\n readTextSync(): string;\n readJSONSync<T>(options?: { fallback?: T }): T;\n\n appendFileSync(\n data: Parameters<typeof appendFileSync>[1],\n options?: Parameters<typeof appendFileSync>[2],\n ): Path;\n\n writeSync(\n data: Parameters<typeof writeFileSync>[1],\n options?: Parameters<typeof writeFileSync>[2] | undefined,\n ): Path;\n writeJSONSync<T>(\n data: T,\n replacer?: Parameters<typeof JSON.stringify>[1],\n space?: Parameters<typeof JSON.stringify>[2],\n ): Path;\n\n readDirSync: typeof readDirSyncType;\n\n /** Returns the destination path. */\n symlinkSync(\n target: string | URL | Path,\n type?: Parameters<typeof symlinkSync>[2],\n ): Path;\n realpathSync(): Path;\n\n statSync: typeof statSyncType;\n lstatSync: typeof lstatSyncType;\n }\n}\n\n// TODO: find a neat way to dedup with the async version? // lint-sync-code-expect-error\nPath.prototype.existsSync = function (constraints?: {\n mustBe: \"file\" | \"directory\";\n}): boolean {\n let stats: ReturnType<typeof statSync>;\n try {\n stats = statSync(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 mustNotHaveTrailingSlash(this);\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\nPath.prototype.existsAsFileSync = function (): boolean {\n return this.existsSync({ mustBe: \"file\" });\n};\n\nPath.prototype.existsAsDirSync = function (): boolean {\n return this.existsSync({ mustBe: \"directory\" });\n};\n\nPath.prototype.mkdirSync = function (\n options?: Parameters<typeof mkdirSync>[1],\n): Path {\n const optionsObject = (() => {\n if (typeof options === \"string\" || typeof options === \"number\") {\n return { mode: options };\n }\n return options ?? {};\n })();\n mkdirSync(this.path, { recursive: true, ...optionsObject });\n return this;\n};\n\nPath.prototype.cpSync = function (\n destination: string | URL | Path,\n options?: Parameters<typeof cpSync>[2],\n): Path {\n cpSync(this.path, new Path(destination).path, options);\n return new Path(destination);\n};\n\nPath.prototype.renameSync = function (destination: string | URL | Path): void {\n renameSync(this.path, new Path(destination).path);\n};\n\nPath.prototype.rmSync = function (\n options?: Parameters<typeof rmSync>[1],\n): void {\n rmSync(this.path, options);\n};\n\nPath.prototype.rm_rfSync = function (\n options?: Parameters<typeof rmSync>[1],\n): void {\n this.rmSync({ recursive: true, force: true, ...(options ?? {}) });\n};\n\nPath.prototype.readSync = function () {\n /** @ts-expect-error ts(2683) */\n return readFileSync(this.path);\n} as typeof readFileSyncType;\n\nPath.prototype.readTextSync = function (): string {\n return readFileSync(this.path, \"utf-8\");\n};\n\nPath.prototype.readJSONSync = function <T>(options?: { fallback?: T }): T {\n try {\n return JSON.parse(this.readTextSync());\n } catch (e) {\n if (\n (e as { code?: string }).code === \"ENOENT\" &&\n options &&\n \"fallback\" in options\n ) {\n return options.fallback as T;\n }\n throw e;\n }\n};\n\nPath.prototype.appendFileSync = function (\n data: Parameters<typeof appendFileSync>[1],\n options?: Parameters<typeof appendFileSync>[2],\n): Path {\n appendFileSync(this.path, data, options);\n return this;\n};\n\nPath.prototype.writeSync = function (\n data: Parameters<typeof writeFileSync>[1],\n options?: Parameters<typeof writeFileSync>[2],\n): Path {\n this.parent.mkdirSync();\n writeFileSync(this.path, data, options);\n return this;\n};\n\nPath.prototype.writeJSONSync = function <T>(\n data: T,\n replacer: Parameters<typeof JSON.stringify>[1] = null,\n space: Parameters<typeof JSON.stringify>[2] = \" \",\n): Path {\n this.parent.mkdirSync();\n this.writeSync(JSON.stringify(data, replacer, space));\n return this;\n};\n\n/** @ts-expect-error ts(2322): Wrangle types */\nPath.prototype.readDirSync = function (options) {\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n return readdirSync(this.path, options as any);\n};\n\nPath.prototype.symlinkSync = function (\n target: string | URL | Path,\n type?: Parameters<typeof symlinkSync>[2],\n): Path {\n const targetPath = new Path(target);\n symlinkSync(\n this.path,\n targetPath.path,\n type as Exclude<Parameters<typeof symlinkSync>[2], undefined>, // \uD83E\uDD37\n );\n return targetPath;\n};\n\nPath.prototype.realpathSync = function (): Path {\n return new Path(realpathSync(this.path));\n};\n\n/** @ts-expect-error ts(2322): Wrangle types */\nPath.prototype.statSync = function (\n options?: Parameters<typeof statSync>[1],\n): ReturnType<typeof statSync> {\n return statSync(this.path, options);\n};\n\n/** @ts-expect-error ts(2322): Wrangle types */\nPath.prototype.lstatSync = function (\n options?: Parameters<typeof lstatSync>[1],\n): ReturnType<typeof lstatSync> {\n return lstatSync(this.path, options);\n};\n", "import { mkdtempSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { Path } from \"../Path\";\n\ndeclare module \"../Path\" {\n namespace Path {\n export function makeTempDirSync(prefix?: string): Path;\n }\n}\n\nPath.makeTempDirSync = (prefix?: string): Path =>\n new Path(\n mkdtempSync(new Path(tmpdir()).join(prefix ?? \"js-temp-\").toString()),\n );\n"],
5
- "mappings": ";;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACbP,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AASvB,KAAK,kBAAkB,CAAC,WACtB,IAAI;AAAA,EACF,YAAY,IAAI,KAAK,OAAO,CAAC,EAAE,KAAK,UAAU,UAAU,EAAE,SAAS,CAAC;AACtE;;;ADgEF,KAAK,UAAU,aAAa,SAAU,aAE1B;AACV,MAAI;AACJ,MAAI;AACF,YAAQ,SAAS,KAAK,IAAI;AAAA,EAE5B,SAAS,GAAQ;AACf,QAAI,EAAE,SAAS,UAAU;AACvB,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AACA,MAAI,CAAC,aAAa,QAAQ;AACxB,WAAO;AAAA,EACT;AACA,UAAQ,aAAa,QAAQ;AAAA,IAC3B,KAAK,QAAQ;AACX,+BAAyB,IAAI;AAC7B,UAAI,MAAM,OAAO,GAAG;AAClB,eAAO;AAAA,MACT;AACA,YAAM,IAAI,MAAM,kCAAkC,KAAK,IAAI,EAAE;AAAA,IAC/D;AAAA,IACA,KAAK,aAAa;AAChB,UAAI,MAAM,YAAY,GAAG;AACvB,eAAO;AAAA,MACT;AACA,YAAM,IAAI,MAAM,uCAAuC,KAAK,IAAI,EAAE;AAAA,IACpE;AAAA,IACA,SAAS;AACP,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAAA,EACF;AACF;AAEA,KAAK,UAAU,mBAAmB,WAAqB;AACrD,SAAO,KAAK,WAAW,EAAE,QAAQ,OAAO,CAAC;AAC3C;AAEA,KAAK,UAAU,kBAAkB,WAAqB;AACpD,SAAO,KAAK,WAAW,EAAE,QAAQ,YAAY,CAAC;AAChD;AAEA,KAAK,UAAU,YAAY,SACzB,SACM;AACN,QAAM,iBAAiB,MAAM;AAC3B,QAAI,OAAO,YAAY,YAAY,OAAO,YAAY,UAAU;AAC9D,aAAO,EAAE,MAAM,QAAQ;AAAA,IACzB;AACA,WAAO,WAAW,CAAC;AAAA,EACrB,GAAG;AACH,YAAU,KAAK,MAAM,EAAE,WAAW,MAAM,GAAG,cAAc,CAAC;AAC1D,SAAO;AACT;AAEA,KAAK,UAAU,SAAS,SACtB,aACA,SACM;AACN,SAAO,KAAK,MAAM,IAAI,KAAK,WAAW,EAAE,MAAM,OAAO;AACrD,SAAO,IAAI,KAAK,WAAW;AAC7B;AAEA,KAAK,UAAU,aAAa,SAAU,aAAwC;AAC5E,aAAW,KAAK,MAAM,IAAI,KAAK,WAAW,EAAE,IAAI;AAClD;AAEA,KAAK,UAAU,SAAS,SACtB,SACM;AACN,SAAO,KAAK,MAAM,OAAO;AAC3B;AAEA,KAAK,UAAU,YAAY,SACzB,SACM;AACN,OAAK,OAAO,EAAE,WAAW,MAAM,OAAO,MAAM,GAAI,WAAW,CAAC,EAAG,CAAC;AAClE;AAEA,KAAK,UAAU,WAAW,WAAY;AAEpC,SAAO,aAAa,KAAK,IAAI;AAC/B;AAEA,KAAK,UAAU,eAAe,WAAoB;AAChD,SAAO,aAAa,KAAK,MAAM,OAAO;AACxC;AAEA,KAAK,UAAU,eAAe,SAAa,SAA+B;AACxE,MAAI;AACF,WAAO,KAAK,MAAM,KAAK,aAAa,CAAC;AAAA,EACvC,SAAS,GAAG;AACV,QACG,EAAwB,SAAS,YAClC,WACA,cAAc,SACd;AACA,aAAO,QAAQ;AAAA,IACjB;AACA,UAAM;AAAA,EACR;AACF;AAEA,KAAK,UAAU,iBAAiB,SAC9B,MACA,SACM;AACN,iBAAe,KAAK,MAAM,MAAM,OAAO;AACvC,SAAO;AACT;AAEA,KAAK,UAAU,YAAY,SACzB,MACA,SACM;AACN,OAAK,OAAO,UAAU;AACtB,gBAAc,KAAK,MAAM,MAAM,OAAO;AACtC,SAAO;AACT;AAEA,KAAK,UAAU,gBAAgB,SAC7B,MACA,WAAiD,MACjD,QAA8C,MACxC;AACN,OAAK,OAAO,UAAU;AACtB,OAAK,UAAU,KAAK,UAAU,MAAM,UAAU,KAAK,CAAC;AACpD,SAAO;AACT;AAGA,KAAK,UAAU,cAAc,SAAU,SAAS;AAE9C,SAAO,YAAY,KAAK,MAAM,OAAc;AAC9C;AAEA,KAAK,UAAU,cAAc,SAC3B,QACA,MACM;AACN,QAAM,aAAa,IAAI,KAAK,MAAM;AAClC;AAAA,IACE,KAAK;AAAA,IACL,WAAW;AAAA,IACX;AAAA;AAAA,EACF;AACA,SAAO;AACT;AAEA,KAAK,UAAU,eAAe,WAAkB;AAC9C,SAAO,IAAI,KAAK,aAAa,KAAK,IAAI,CAAC;AACzC;AAGA,KAAK,UAAU,WAAW,SACxB,SAC6B;AAC7B,SAAO,SAAS,KAAK,MAAM,OAAO;AACpC;AAGA,KAAK,UAAU,YAAY,SACzB,SAC8B;AAC9B,SAAO,UAAU,KAAK,MAAM,OAAO;AACrC;",
4
+ "sourcesContent": ["import {\n cpSync,\n lstatSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n renameSync,\n rmSync,\n statSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { mustNotHaveTrailingSlash, Path } from \"../Path\";\nimport \"./static\";\nimport type {\n lstatSyncType,\n readDirSyncType,\n readFileSyncType,\n statSyncType,\n} from \"./modifiedNodeTypes\";\n\n// Note that (non-static) functions in this file are defined using `function(\u2026)\n// { \u2026 }` rather than arrow functions, specifically because we want `this` to\n// operate on the `Path` instance.\n\ndeclare module \"../Path\" {\n interface Path {\n existsSync(constraints?: { mustBe: \"file\" | \"directory\" }): boolean;\n existsAsFileSync(): boolean;\n existsAsDirSync(): boolean;\n\n mkdirSync(options?: Parameters<typeof mkdirSync>[1]): Path;\n cpSync(\n destination: string | URL | Path,\n options?: Parameters<typeof cpSync>[2],\n ): Path;\n renameSync(destination: string | URL | Path): void;\n\n rmSync(options?: Parameters<typeof rmSync>[1]): void;\n rm_rfSync(options?: Parameters<typeof rmSync>[1]): void;\n\n readSync: typeof readFileSyncType;\n readTextSync(): string;\n readJSONSync<T>(options?: { fallback?: T }): T;\n\n writeSync(\n data: Parameters<typeof writeFileSync>[1],\n options?: Parameters<typeof writeFileSync>[2] | undefined,\n ): Path;\n writeJSONSync<T>(\n data: T,\n replacer?: Parameters<typeof JSON.stringify>[1],\n space?: Parameters<typeof JSON.stringify>[2],\n ): Path;\n\n readDirSync: typeof readDirSyncType;\n\n /** Returns the destination path. */\n symlinkSync(\n target: string | URL | Path,\n type?: Parameters<typeof symlinkSync>[2],\n ): Path;\n\n statSync: typeof statSyncType;\n lstatSync: typeof lstatSyncType;\n }\n}\n\n// TODO: find a neat way to dedup with the async version?\nPath.prototype.existsSync = function (constraints?: {\n mustBe: \"file\" | \"directory\";\n}): boolean {\n let stats: ReturnType<typeof statSync>;\n try {\n stats = statSync(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 mustNotHaveTrailingSlash(this);\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\nPath.prototype.existsAsFileSync = function (): boolean {\n return this.existsSync({ mustBe: \"file\" });\n};\n\nPath.prototype.existsAsDirSync = function (): boolean {\n return this.existsSync({ mustBe: \"directory\" });\n};\n\nPath.prototype.mkdirSync = function (\n options?: Parameters<typeof mkdirSync>[1],\n): Path {\n const optionsObject = (() => {\n if (typeof options === \"string\" || typeof options === \"number\") {\n return { mode: options };\n }\n return options ?? {};\n })();\n mkdirSync(this.path, { recursive: true, ...optionsObject });\n return this;\n};\n\nPath.prototype.cpSync = function (\n destination: string | URL | Path,\n options?: Parameters<typeof cpSync>[2],\n): Path {\n cpSync(this.path, new Path(destination).path, options);\n return new Path(destination);\n};\n\nPath.prototype.renameSync = function (destination: string | URL | Path): void {\n renameSync(this.path, new Path(destination).path);\n};\n\nPath.prototype.rmSync = function (\n options?: Parameters<typeof rmSync>[1],\n): void {\n rmSync(this.path, options);\n};\n\nPath.prototype.rm_rfSync = function (\n options?: Parameters<typeof rmSync>[1],\n): void {\n this.rmSync({ recursive: true, force: true, ...(options ?? {}) });\n};\n\nPath.prototype.readSync = function () {\n /** @ts-expect-error ts(2683) */\n return readFileSync(this.path);\n} as typeof readFileSyncType;\n\nPath.prototype.readTextSync = function (): string {\n return readFileSync(this.path, \"utf-8\");\n};\n\nPath.prototype.readJSONSync = function <T>(options?: { fallback?: T }): T {\n try {\n return JSON.parse(this.readTextSync());\n } catch (e) {\n if (\n (e as { code?: string }).code === \"ENOENT\" &&\n options &&\n \"fallback\" in options\n ) {\n return options.fallback as T;\n }\n throw e;\n }\n};\n\nPath.prototype.writeSync = function (\n data: Parameters<typeof writeFileSync>[1],\n options?: Parameters<typeof writeFileSync>[2],\n): Path {\n this.parent.mkdirSync();\n writeFileSync(this.path, data, options);\n return this;\n};\n\nPath.prototype.writeJSONSync = function <T>(\n data: T,\n replacer: Parameters<typeof JSON.stringify>[1] = null,\n space: Parameters<typeof JSON.stringify>[2] = \" \",\n): Path {\n this.parent.mkdirSync();\n this.writeSync(JSON.stringify(data, replacer, space));\n return this;\n};\n\n/** @ts-expect-error ts(2322): Wrangle types */\nPath.prototype.readDirSync = function (options) {\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n return readdirSync(this.path, options as any);\n};\n\nPath.prototype.symlinkSync = function symlink(\n target: string | URL | Path,\n type?: Parameters<typeof symlinkSync>[2],\n): Path {\n const targetPath = new Path(target);\n symlinkSync(\n this.path,\n targetPath.path,\n type as Exclude<Parameters<typeof symlinkSync>[2], undefined>, // \uD83E\uDD37\n );\n return targetPath;\n};\n\n/** @ts-expect-error ts(2322): Wrangle types */\nPath.prototype.statSync = function (\n options?: Parameters<typeof statSync>[1],\n): ReturnType<typeof statSync> {\n return statSync(this.path, options);\n};\n\n/** @ts-expect-error ts(2322): Wrangle types */\nPath.prototype.lstatSync = function (\n options?: Parameters<typeof lstatSync>[1],\n): ReturnType<typeof lstatSync> {\n return lstatSync(this.path, options);\n};\n", "import { mkdtempSync } from \"node:fs\";\nimport { tmpdir } from \"node:os\";\nimport { Path } from \"../Path\";\n\ndeclare module \"../Path\" {\n namespace Path {\n export function makeTempDirSync(prefix?: string): Path;\n }\n}\n\nPath.makeTempDirSync = (prefix?: string): Path =>\n new Path(\n mkdtempSync(new Path(tmpdir()).join(prefix ?? \"js-temp-\").toString()),\n );\n"],
5
+ "mappings": ";;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;ACXP,SAAS,mBAAmB;AAC5B,SAAS,cAAc;AASvB,KAAK,kBAAkB,CAAC,WACtB,IAAI;AAAA,EACF,YAAY,IAAI,KAAK,OAAO,CAAC,EAAE,KAAK,UAAU,UAAU,EAAE,SAAS,CAAC;AACtE;;;ADwDF,KAAK,UAAU,aAAa,SAAU,aAE1B;AACV,MAAI;AACJ,MAAI;AACF,YAAQ,SAAS,KAAK,IAAI;AAAA,EAE5B,SAAS,GAAQ;AACf,QAAI,EAAE,SAAS,UAAU;AACvB,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AACA,MAAI,CAAC,aAAa,QAAQ;AACxB,WAAO;AAAA,EACT;AACA,UAAQ,aAAa,QAAQ;AAAA,IAC3B,KAAK,QAAQ;AACX,+BAAyB,IAAI;AAC7B,UAAI,MAAM,OAAO,GAAG;AAClB,eAAO;AAAA,MACT;AACA,YAAM,IAAI,MAAM,kCAAkC,KAAK,IAAI,EAAE;AAAA,IAC/D;AAAA,IACA,KAAK,aAAa;AAChB,UAAI,MAAM,YAAY,GAAG;AACvB,eAAO;AAAA,MACT;AACA,YAAM,IAAI,MAAM,uCAAuC,KAAK,IAAI,EAAE;AAAA,IACpE;AAAA,IACA,SAAS;AACP,YAAM,IAAI,MAAM,8BAA8B;AAAA,IAChD;AAAA,EACF;AACF;AAEA,KAAK,UAAU,mBAAmB,WAAqB;AACrD,SAAO,KAAK,WAAW,EAAE,QAAQ,OAAO,CAAC;AAC3C;AAEA,KAAK,UAAU,kBAAkB,WAAqB;AACpD,SAAO,KAAK,WAAW,EAAE,QAAQ,YAAY,CAAC;AAChD;AAEA,KAAK,UAAU,YAAY,SACzB,SACM;AACN,QAAM,iBAAiB,MAAM;AAC3B,QAAI,OAAO,YAAY,YAAY,OAAO,YAAY,UAAU;AAC9D,aAAO,EAAE,MAAM,QAAQ;AAAA,IACzB;AACA,WAAO,WAAW,CAAC;AAAA,EACrB,GAAG;AACH,YAAU,KAAK,MAAM,EAAE,WAAW,MAAM,GAAG,cAAc,CAAC;AAC1D,SAAO;AACT;AAEA,KAAK,UAAU,SAAS,SACtB,aACA,SACM;AACN,SAAO,KAAK,MAAM,IAAI,KAAK,WAAW,EAAE,MAAM,OAAO;AACrD,SAAO,IAAI,KAAK,WAAW;AAC7B;AAEA,KAAK,UAAU,aAAa,SAAU,aAAwC;AAC5E,aAAW,KAAK,MAAM,IAAI,KAAK,WAAW,EAAE,IAAI;AAClD;AAEA,KAAK,UAAU,SAAS,SACtB,SACM;AACN,SAAO,KAAK,MAAM,OAAO;AAC3B;AAEA,KAAK,UAAU,YAAY,SACzB,SACM;AACN,OAAK,OAAO,EAAE,WAAW,MAAM,OAAO,MAAM,GAAI,WAAW,CAAC,EAAG,CAAC;AAClE;AAEA,KAAK,UAAU,WAAW,WAAY;AAEpC,SAAO,aAAa,KAAK,IAAI;AAC/B;AAEA,KAAK,UAAU,eAAe,WAAoB;AAChD,SAAO,aAAa,KAAK,MAAM,OAAO;AACxC;AAEA,KAAK,UAAU,eAAe,SAAa,SAA+B;AACxE,MAAI;AACF,WAAO,KAAK,MAAM,KAAK,aAAa,CAAC;AAAA,EACvC,SAAS,GAAG;AACV,QACG,EAAwB,SAAS,YAClC,WACA,cAAc,SACd;AACA,aAAO,QAAQ;AAAA,IACjB;AACA,UAAM;AAAA,EACR;AACF;AAEA,KAAK,UAAU,YAAY,SACzB,MACA,SACM;AACN,OAAK,OAAO,UAAU;AACtB,gBAAc,KAAK,MAAM,MAAM,OAAO;AACtC,SAAO;AACT;AAEA,KAAK,UAAU,gBAAgB,SAC7B,MACA,WAAiD,MACjD,QAA8C,MACxC;AACN,OAAK,OAAO,UAAU;AACtB,OAAK,UAAU,KAAK,UAAU,MAAM,UAAU,KAAK,CAAC;AACpD,SAAO;AACT;AAGA,KAAK,UAAU,cAAc,SAAU,SAAS;AAE9C,SAAO,YAAY,KAAK,MAAM,OAAc;AAC9C;AAEA,KAAK,UAAU,cAAc,SAAS,QACpC,QACA,MACM;AACN,QAAM,aAAa,IAAI,KAAK,MAAM;AAClC;AAAA,IACE,KAAK;AAAA,IACL,WAAW;AAAA,IACX;AAAA;AAAA,EACF;AACA,SAAO;AACT;AAGA,KAAK,UAAU,WAAW,SACxB,SAC6B;AAC7B,SAAO,SAAS,KAAK,MAAM,OAAO;AACpC;AAGA,KAAK,UAAU,YAAY,SACzB,SAC8B;AAC9B,SAAO,UAAU,KAAK,MAAM,OAAO;AACrC;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "path-class",
3
- "version": "0.8.3",
3
+ "version": "0.9.0",
4
4
  "author": "Lucas Garron <code@garron.net>",
5
5
  "type": "module",
6
6
  "main": "./dist/lib/path-class/index.js",
@@ -29,7 +29,7 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@types/node": "^24.7.1",
32
- "readme-cli-help": "^0.1.10",
32
+ "readme-cli-help": ">=0.3.1",
33
33
  "xdg-basedir": "^5.1.0"
34
34
  },
35
35
  "files": [
package/src/Path.test.ts CHANGED
@@ -4,12 +4,21 @@ import { join } from "node:path";
4
4
  import { Path, stringifyIfPath } from "./Path";
5
5
 
6
6
  test("constructor", async () => {
7
- expect(new Path("foo").path).toEqual("foo");
8
- expect(new Path("./relative").path).toEqual("relative");
9
- expect(new Path("./relative/nested").path).toEqual("relative/nested");
7
+ expect(new Path("bare").path).toEqual("bare");
8
+ expect(new Path("bare/").path).toEqual("bare/");
9
+ expect(new Path("bare/path").path).toEqual("bare/path");
10
+ expect(new Path("bare/path/").path).toEqual("bare/path/");
11
+ expect(new Path("./relative").path).toEqual("./relative");
12
+ expect(new Path("./relative/").path).toEqual("./relative/");
13
+ expect(new Path("./relative/nested").path).toEqual("./relative/nested");
14
+ expect(new Path("./relative/nested/").path).toEqual("./relative/nested/");
10
15
  expect(new Path("/absolute").path).toEqual("/absolute");
16
+ expect(new Path("/absolute/").path).toEqual("/absolute/");
11
17
  expect(new Path("/absolute/nested").path).toEqual("/absolute/nested");
12
- expect(new Path("trailing/slash/").path).toEqual("trailing/slash/");
18
+ expect(new Path("/absolute/nested/").path).toEqual("/absolute/nested/");
19
+ expect(new Path("./down/../again").path).toEqual("./again");
20
+ expect(new Path("down/../again").path).toEqual("again");
21
+ expect(new Path("down/..").path).toEqual(".");
13
22
  });
14
23
 
15
24
  test("Path.resolve(…)", async () => {
@@ -50,8 +59,10 @@ test(".hasTrailingSlash()", async () => {
50
59
  expect(new Path("foo/bar").hasTrailingSlash()).toBe(false);
51
60
  expect(new Path("foo/bar/").hasTrailingSlash()).toBe(true);
52
61
  expect(new Path(import.meta.url).hasTrailingSlash()).toBe(false);
53
- expect(new Path(import.meta.url).join("/").hasTrailingSlash()).toBe(true);
54
- expect(new Path(import.meta.url).join("/.").hasTrailingSlash()).toBe(false);
62
+ expect(new Path(import.meta.url).join("foo/").hasTrailingSlash()).toBe(true);
63
+ expect(new Path(import.meta.url).join("bar/.").hasTrailingSlash()).toBe(
64
+ false,
65
+ );
55
66
  expect(new Path(import.meta.url).join(".").hasTrailingSlash()).toBe(false);
56
67
  });
57
68
 
@@ -72,7 +83,114 @@ test(".join(…)", async () => {
72
83
  expect(
73
84
  new Path("foo/bar").join("bath", new Path("kitchen/sink")).path,
74
85
  ).toEqual("foo/bar/bath/kitchen/sink");
75
- expect(new Path("foo").join(new Path("/bar")).path).toEqual("foo/bar");
86
+ expect(() => new Path("foo").join(new Path("/bar")).path).toThrow(
87
+ "Arguments to `.join(…)` cannot be absolute. Use `.asRelative()` to convert them first if needed.",
88
+ );
89
+ });
90
+
91
+ test("asRelative()", async () => {
92
+ // From doc comment
93
+ expect(new Path("bare").asRelative().path).toEqual("./bare");
94
+ expect(new Path("./relative").asRelative().path).toEqual("./relative");
95
+ expect(new Path("../up-first").asRelative().path).toEqual("../up-first");
96
+ expect(new Path("/absolute").asRelative().path).toEqual("./absolute");
97
+ // Other
98
+ expect(new Path("./bar/../foo").asRelative().path).toEqual("./foo");
99
+ expect(new Path("./bar/../../").asRelative().path).toEqual("../");
100
+ expect(new Path("././").asRelative().path).toEqual("./");
101
+ expect(new Path("..").asRelative().path).toEqual("..");
102
+ expect(new Path("../").asRelative().path).toEqual("../");
103
+ expect(new Path("/abs/").asRelative().path).toEqual("./abs/");
104
+ expect(new Path("bare/").asRelative().path).toEqual("./bare/");
105
+ expect(new Path("./rel/").asRelative().path).toEqual("./rel/");
106
+ expect(new Path("../up/").asRelative().path).toEqual("../up/");
107
+ });
108
+
109
+ test("asAbsolute()", async () => {
110
+ // From doc comment
111
+ expect(new Path("bare").asAbsolute().path).toEqual("/bare");
112
+ expect(new Path("./relative").asAbsolute().path).toEqual("/relative");
113
+ expect(new Path("../up-first").asAbsolute().path).toEqual("/up-first");
114
+ expect(new Path("/absolute").asAbsolute().path).toEqual("/absolute");
115
+ // Other
116
+ expect(new Path("/abs/").asAbsolute().path).toEqual("/abs/");
117
+ expect(new Path("bare/").asAbsolute().path).toEqual("/bare/");
118
+ expect(new Path("../up/").asAbsolute().path).toEqual("/up/");
119
+ });
120
+
121
+ test("asBare(…)", async () => {
122
+ const ERROR_1 =
123
+ 'Converting path to a bare path resulted in a `..` traversal prefix. Pass `"strip"` or `"keep"` as the `parentTraversalHandling` option to avoid an error.';
124
+ const ERROR_2 = "Output does not start with a named component.";
125
+ // From doc comment (default)
126
+ expect(new Path("bare").asBare().path).toEqual("bare");
127
+ expect(new Path("./relative").asBare().path).toEqual("relative");
128
+ expect(new Path(".").asBare().path).toEqual(".");
129
+ expect(new Path("down-first/..").asBare().path).toEqual(".");
130
+ expect(() => new Path("../up-first").asBare().path).toThrow(ERROR_1);
131
+ expect(() => new Path("..").asBare().path).toThrow(ERROR_1);
132
+ expect(new Path("/absolute").asBare().path).toEqual("absolute");
133
+ // From doc comment (strip)
134
+ expect(
135
+ new Path("../up-first").asBare({ parentTraversalPrefixHandling: "strip" })
136
+ .path,
137
+ ).toEqual("up-first");
138
+ expect(
139
+ new Path("..").asBare({ parentTraversalPrefixHandling: "strip" }).path,
140
+ ).toEqual(".");
141
+ // From doc comment (keep)
142
+ expect(
143
+ new Path("../up-first").asBare({ parentTraversalPrefixHandling: "keep" })
144
+ .path,
145
+ ).toEqual("../up-first");
146
+ expect(
147
+ new Path("..").asBare({ parentTraversalPrefixHandling: "keep" }).path,
148
+ ).toEqual("..");
149
+ // Other
150
+ expect(new Path(".").asBare().asBare().path).toEqual(".");
151
+ expect(new Path("./").asBare().asBare().path).toEqual("./");
152
+ expect(new Path("/abs/").asBare().asBare().path).toEqual("abs/");
153
+ expect(new Path("bare/").asBare().asBare().path).toEqual("bare/");
154
+ expect(() => new Path("../up/").asBare().path).toThrow(ERROR_1);
155
+ expect(() => new Path("./down/down/../../..").asBare().path).toThrow(ERROR_1);
156
+ expect(() => new Path("..").asBare().path).toThrow(ERROR_1);
157
+ expect(() => new Path("../../up/").asBare().path).toThrow(ERROR_1);
158
+ // parentTraversalPrefixHandling
159
+ expect(
160
+ new Path("../../up/").asBare({ parentTraversalPrefixHandling: "strip" })
161
+ .path,
162
+ ).toEqual("up/");
163
+ expect(
164
+ new Path("../../up/").asBare({ parentTraversalPrefixHandling: "keep" })
165
+ .path,
166
+ ).toEqual("../../up/");
167
+ expect(
168
+ new Path("../..").asBare({ parentTraversalPrefixHandling: "strip" }).path,
169
+ ).toEqual(".");
170
+ expect(
171
+ new Path("../../").asBare({ parentTraversalPrefixHandling: "strip" }).path,
172
+ ).toEqual(".");
173
+ // requireNamedComponentPrefix
174
+ expect(
175
+ () =>
176
+ new Path(".").asBare({
177
+ requireNamedComponentPrefix: true,
178
+ }).path,
179
+ ).toThrow(ERROR_2);
180
+ expect(
181
+ () =>
182
+ new Path("../../").asBare({
183
+ parentTraversalPrefixHandling: "strip",
184
+ requireNamedComponentPrefix: true,
185
+ }).path,
186
+ ).toThrow(ERROR_2);
187
+ expect(
188
+ () =>
189
+ new Path("./").asBare({
190
+ parentTraversalPrefixHandling: "strip",
191
+ requireNamedComponentPrefix: true,
192
+ }).path,
193
+ ).toThrow(ERROR_2);
76
194
  });
77
195
 
78
196
  test("traverse", async () => {
@@ -320,14 +438,6 @@ test(".writeJSON(…)", async () => {
320
438
  expect(await file.readJSON()).toEqual<Record<string, string>>({ foo: "bar" });
321
439
  });
322
440
 
323
- test(".appendFile(…)", async () => {
324
- const file = (await Path.makeTempDir()).join("file.txt");
325
- await file.appendFile("test\n");
326
- expect(await file.readText()).toEqual("test\n");
327
- await file.appendFile("more\n");
328
- expect(await file.readText()).toEqual("test\nmore\n");
329
- });
330
-
331
441
  test(".readDir(…)", async () => {
332
442
  const dir = await Path.makeTempDir();
333
443
  await dir.join("file.txt").write("hello");
@@ -354,17 +464,6 @@ test(".symlink(…)", async () => {
354
464
  expect(await target.readText()).toEqual("hello");
355
465
  });
356
466
 
357
- test(".realpath(…)", async () => {
358
- const tempDir = await Path.makeTempDir();
359
- const source = tempDir.join("foo.txt");
360
- await source.write("hello world!");
361
- const target = tempDir.join("bar.txt");
362
- await source.symlink(target);
363
- expect((await source.realpath()).path).toEqual(
364
- (await target.realpath()).path,
365
- );
366
- });
367
-
368
467
  test(".stat(…)", async () => {
369
468
  const file = (await Path.makeTempDir()).join("foo.txt");
370
469
  await file.write("hello");
@@ -405,8 +504,11 @@ test(".xdg", async () => {
405
504
  const spy = spyOn(console, "log");
406
505
 
407
506
  test(".debugPrint(…)", async () => {
408
- Path.homedir.debugPrint("foo");
409
- expect(spy.mock.calls).toEqual([["foo"], ["/mock/home/dir"]]);
507
+ Path.homedir.debugPrint("Here is a test log of the mock home directory:");
508
+ expect(spy.mock.calls).toEqual([
509
+ ["Here is a test log of the mock home directory:"],
510
+ ["/mock/home/dir"],
511
+ ]);
410
512
  });
411
513
 
412
514
  test(".stringifyIfPath(…)", async () => {
package/src/Path.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  import {
2
- appendFile,
3
2
  cp,
4
3
  lstat,
5
4
  mkdir,
6
5
  mkdtemp,
7
6
  readdir,
8
7
  readFile,
9
- realpath,
10
8
  rename,
11
9
  rm,
12
10
  stat,
@@ -49,6 +47,23 @@ async function wrangleWritableData(
49
47
  return data;
50
48
  }
51
49
 
50
+ export enum ResolutionPrefix {
51
+ Absolute = "absolute",
52
+ Relative = "relative",
53
+ Bare = "bare",
54
+ }
55
+
56
+ function resolutionPrefix(pathString: string): ResolutionPrefix {
57
+ if (pathString.startsWith("/")) {
58
+ return ResolutionPrefix.Absolute;
59
+ } else if (pathString.startsWith("./")) {
60
+ return ResolutionPrefix.Relative;
61
+ } else if (pathString === ".") {
62
+ return ResolutionPrefix.Relative;
63
+ }
64
+ return ResolutionPrefix.Bare;
65
+ }
66
+
52
67
  export class Path {
53
68
  // @ts-expect-error ts(2564): False positive. https://github.com/microsoft/TypeScript/issues/32194
54
69
  #path: string;
@@ -56,7 +71,12 @@ export class Path {
56
71
  * If `path` is a string starting with `file:///`, it will be parsed as a file URL.
57
72
  */
58
73
  constructor(path: string | URL | Path) {
59
- this.#setNormalizedPath(Path.#pathlikeToString(path));
74
+ const s = Path.#pathlikeToString(path);
75
+ this.#setNormalizedPath(s);
76
+ }
77
+
78
+ get resolutionPrefix(): ResolutionPrefix {
79
+ return resolutionPrefix(this.#path);
60
80
  }
61
81
 
62
82
  /**
@@ -70,6 +90,9 @@ export class Path {
70
90
  static resolve(path: string | URL | Path, base: string | URL | Path): Path {
71
91
  const baseURL = (() => {
72
92
  if (!(base instanceof Path)) {
93
+ if (typeof base === "string" && !base.startsWith("file://")) {
94
+ return pathToFileURL(base);
95
+ }
73
96
  return base;
74
97
  }
75
98
  if (!base.isAbsolutePath()) {
@@ -99,12 +122,18 @@ export class Path {
99
122
  throw new Error("Invalid path");
100
123
  }
101
124
 
125
+ // Preserves the `ResolutionPrefix` status when possible.
102
126
  #setNormalizedPath(path: string): void {
127
+ const prefix = resolutionPrefix(path);
103
128
  this.#path = join(path);
129
+ if (prefix === ResolutionPrefix.Relative && !this.#path.startsWith(".")) {
130
+ // We don't have to handle the case of `"."`, as it already starts with `"."`
131
+ this.#path = `./${this.#path}`;
132
+ }
104
133
  }
105
134
 
106
135
  isAbsolutePath(): boolean {
107
- return this.#path.startsWith("/");
136
+ return this.resolutionPrefix === ResolutionPrefix.Absolute;
108
137
  }
109
138
 
110
139
  toFileURL(): URL {
@@ -144,12 +173,126 @@ export class Path {
144
173
  * This follows `node` semantics for absolute paths: leading slashes in the given descendant segments are ignored.
145
174
  */
146
175
  join(...segments: (string | Path)[]): Path {
147
- const segmentStrings = segments.map((segment) =>
148
- segment instanceof Path ? segment.path : segment,
149
- );
176
+ const segmentStrings = segments.map((segment) => {
177
+ const s = stringifyIfPath(segment);
178
+ if (resolutionPrefix(s) === ResolutionPrefix.Absolute) {
179
+ throw new Error(
180
+ "Arguments to `.join(…)` cannot be absolute. Use `.asRelative()` to convert them first if needed.",
181
+ );
182
+ }
183
+ return s;
184
+ });
150
185
  return new Path(join(this.#path, ...segmentStrings));
151
186
  }
152
187
 
188
+ /**
189
+ * Adjust the prefix to construct a relative path.
190
+ *
191
+ * | Example input | Output |
192
+ * |-----------------|-----------------|
193
+ * | `"bare"` | `"./bare"` |
194
+ * | `"./relative"` | `"./relative"` |
195
+ * | `"../up-first"` | `"../up-first"` |
196
+ * | `"/absolute"` | `"./absolute"` |
197
+ *
198
+ */
199
+ asRelative(): Path {
200
+ return new Path(`./${this.#path}`);
201
+ }
202
+
203
+ /**
204
+ * Adjust the prefix to construct an absolute path.
205
+ *
206
+ * | Example input | Output |
207
+ * |-----------------|---------------|
208
+ * | `"bare"` | `"/bare"` |
209
+ * | `"./relative"` | `"/relative"` |
210
+ * | `"../up-first"` | `"/up-first"` |
211
+ * | `"/absolute"` | `"/absolute"` |
212
+ *
213
+ */
214
+ asAbsolute(): Path {
215
+ return new Path(join("/", this.#path));
216
+ }
217
+
218
+ /**
219
+ * Adjust the prefix to construct a bare path. Note that this returns `"."` if
220
+ * there are no named paths left.
221
+ *
222
+ * | Example input | Output |
223
+ * |-------------------|--------------|
224
+ * | `"bare"` | `"bare"` |
225
+ * | `"./relative" ` | `"relative"` |
226
+ * | `"/absolute"` | `"absolute"` |
227
+ * | `"."` | `"."` |
228
+ * | `"down-first/.."` | `"."` |
229
+ * | `"../up-first"` | (error) |
230
+ * | `".."` | (error) |
231
+ *
232
+ * Specify `parentTraversalPrefixHandling` in the `options` if you would like
233
+ * to strip or keep resolution prefixes like `../` rather than erroring.
234
+ *
235
+ * | Example input | Output with `{ parentTraversalPrefixHandling: "strip" }` |
236
+ * |----------------------|----------------------------------------------------------|
237
+ * | `"../up-first"` | `"up-first"` |
238
+ * | `".."` | `"."` |
239
+ *
240
+ * | Example input | Output with `{ parentTraversalPrefixHandling: "keep" }` |
241
+ * |----------------------|---------------------------------------------------------|
242
+ * | `"../up-first"` | `"../up-first"` |
243
+ * | `".."` | `".."` |
244
+ *
245
+ * If you need the output to start with a named component and return values
246
+ * like `.`, `..`, `../`, or `../…` are not okay, pass
247
+ * `requireNamedComponentPrefix: true`. This is useful if the path represents
248
+ * an `npm`-style package name (e.g. `"typescript"`, `"@biomejs/biome"`).
249
+ *
250
+ */
251
+ asBare(options?: {
252
+ parentTraversalPrefixHandling?: "error" | "strip" | "keep";
253
+ requireNamedComponentPrefix?: boolean;
254
+ }): Path {
255
+ const path = new Path(join(".", this.#path));
256
+ if (!path.#path.startsWith("../") && path.#path !== "..") {
257
+ if (
258
+ options?.requireNamedComponentPrefix &&
259
+ path.resolutionPrefix === ResolutionPrefix.Relative
260
+ ) {
261
+ throw new Error("Output does not start with a named component.");
262
+ }
263
+ return path;
264
+ }
265
+ const parentTraversalHandling =
266
+ options?.parentTraversalPrefixHandling ?? "error";
267
+ switch (parentTraversalHandling) {
268
+ case "error": {
269
+ throw new Error(
270
+ 'Converting path to a bare path resulted in a `..` traversal prefix. Pass `"strip"` or `"keep"` as the `parentTraversalHandling` option to avoid an error.',
271
+ );
272
+ }
273
+ case "strip": {
274
+ let newPath = path.#path.replace(/^(\.\.\/)+/, "");
275
+ if (["", ".."].includes(newPath)) {
276
+ newPath = ".";
277
+ }
278
+ const output = new Path(newPath);
279
+ if (
280
+ options?.requireNamedComponentPrefix &&
281
+ output.resolutionPrefix === ResolutionPrefix.Relative
282
+ ) {
283
+ throw new Error("Output does not start with a named component.");
284
+ }
285
+ return new Path(newPath);
286
+ }
287
+ case "keep": {
288
+ if (options?.requireNamedComponentPrefix) {
289
+ throw new Error("Output does not start with a named component.");
290
+ }
291
+ return path;
292
+ }
293
+ }
294
+ }
295
+
153
296
  extendBasename(suffix: string): Path {
154
297
  const joinedSuffix = join(suffix);
155
298
  if (joinedSuffix !== basename(joinedSuffix)) {
@@ -317,17 +460,6 @@ export class Path {
317
460
  }
318
461
  }
319
462
 
320
- /**
321
- * Returns the original `Path` (for chaining).
322
- */
323
- async appendFile(
324
- data: Parameters<typeof appendFile>[1],
325
- options?: Parameters<typeof appendFile>[2],
326
- ): Promise<Path> {
327
- await appendFile(this.#path, data, options);
328
- return this;
329
- }
330
-
331
463
  /** Creates intermediate directories if they do not exist.
332
464
  *
333
465
  * Returns the original `Path` (for chaining).
@@ -383,16 +515,6 @@ export class Path {
383
515
  return targetPath;
384
516
  }
385
517
 
386
- // I don't think `lstat` is a great name, but it does match the
387
- // well-established canonical commandline name. So in this case we keep the
388
- // name instead of using `realPath`.
389
- //
390
- // Note: There are no options in our API, because the only option is an
391
- // encoding. We set the encoding to construct the returned `Path`.
392
- async realpath(): Promise<Path> {
393
- return new Path(await realpath(this.#path, "utf-8"));
394
- }
395
-
396
518
  // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.
397
519
  stat: typeof statType = (...options) => stat(this.#path, ...options) as any;
398
520
 
@@ -84,6 +84,20 @@ export declare function readFileType(
84
84
  | null,
85
85
  ): Promise<string | Buffer>;
86
86
 
87
+ export declare function lstatType(
88
+ opts?: StatOptions & {
89
+ bigint?: false | undefined;
90
+ },
91
+ ): Promise<Stats>;
92
+ export declare function lstatType(
93
+ opts: StatOptions & {
94
+ bigint: true;
95
+ },
96
+ ): Promise<BigIntStats>;
97
+ export declare function lstatType(
98
+ opts?: StatOptions,
99
+ ): Promise<Stats | BigIntStats>;
100
+
87
101
  export declare function statType(
88
102
  opts?: StatOptions & {
89
103
  bigint?: false | undefined;
@@ -97,5 +111,3 @@ export declare function statType(
97
111
  export declare function statType(
98
112
  opts?: StatOptions,
99
113
  ): Promise<Stats | BigIntStats>;
100
-
101
- export declare const lstatType: typeof statType;
package/src/sync/index.ts CHANGED
@@ -1,11 +1,9 @@
1
1
  import {
2
- appendFileSync,
3
2
  cpSync,
4
3
  lstatSync,
5
4
  mkdirSync,
6
5
  readdirSync,
7
6
  readFileSync,
8
- realpathSync,
9
7
  renameSync,
10
8
  rmSync,
11
9
  statSync,
@@ -45,11 +43,6 @@ declare module "../Path" {
45
43
  readTextSync(): string;
46
44
  readJSONSync<T>(options?: { fallback?: T }): T;
47
45
 
48
- appendFileSync(
49
- data: Parameters<typeof appendFileSync>[1],
50
- options?: Parameters<typeof appendFileSync>[2],
51
- ): Path;
52
-
53
46
  writeSync(
54
47
  data: Parameters<typeof writeFileSync>[1],
55
48
  options?: Parameters<typeof writeFileSync>[2] | undefined,
@@ -67,14 +60,13 @@ declare module "../Path" {
67
60
  target: string | URL | Path,
68
61
  type?: Parameters<typeof symlinkSync>[2],
69
62
  ): Path;
70
- realpathSync(): Path;
71
63
 
72
64
  statSync: typeof statSyncType;
73
65
  lstatSync: typeof lstatSyncType;
74
66
  }
75
67
  }
76
68
 
77
- // TODO: find a neat way to dedup with the async version? // lint-sync-code-expect-error
69
+ // TODO: find a neat way to dedup with the async version?
78
70
  Path.prototype.existsSync = function (constraints?: {
79
71
  mustBe: "file" | "directory";
80
72
  }): boolean {
@@ -180,14 +172,6 @@ Path.prototype.readJSONSync = function <T>(options?: { fallback?: T }): T {
180
172
  }
181
173
  };
182
174
 
183
- Path.prototype.appendFileSync = function (
184
- data: Parameters<typeof appendFileSync>[1],
185
- options?: Parameters<typeof appendFileSync>[2],
186
- ): Path {
187
- appendFileSync(this.path, data, options);
188
- return this;
189
- };
190
-
191
175
  Path.prototype.writeSync = function (
192
176
  data: Parameters<typeof writeFileSync>[1],
193
177
  options?: Parameters<typeof writeFileSync>[2],
@@ -213,7 +197,7 @@ Path.prototype.readDirSync = function (options) {
213
197
  return readdirSync(this.path, options as any);
214
198
  };
215
199
 
216
- Path.prototype.symlinkSync = function (
200
+ Path.prototype.symlinkSync = function symlink(
217
201
  target: string | URL | Path,
218
202
  type?: Parameters<typeof symlinkSync>[2],
219
203
  ): Path {
@@ -226,10 +210,6 @@ Path.prototype.symlinkSync = function (
226
210
  return targetPath;
227
211
  };
228
212
 
229
- Path.prototype.realpathSync = function (): Path {
230
- return new Path(realpathSync(this.path));
231
- };
232
-
233
213
  /** @ts-expect-error ts(2322): Wrangle types */
234
214
  Path.prototype.statSync = function (
235
215
  options?: Parameters<typeof statSync>[1],
@@ -193,14 +193,6 @@ test(".writeJSONSync(…)", () => {
193
193
  expect(file.readJSONSync()).toEqual<Record<string, string>>({ foo: "bar" });
194
194
  });
195
195
 
196
- test(".appendFileSync(…)", () => {
197
- const file = Path.makeTempDirSync().join("file.txt");
198
- file.appendFileSync("test\n");
199
- expect(file.readTextSync()).toEqual("test\n");
200
- file.appendFileSync("more\n");
201
- expect(file.readTextSync()).toEqual("test\nmore\n");
202
- });
203
-
204
196
  test(".readDirSync(…)", () => {
205
197
  const dir = Path.makeTempDirSync();
206
198
  dir.join("file.txt").writeSync("hello");
@@ -227,15 +219,6 @@ test(".symlinkSync(…)", () => {
227
219
  expect(target.readTextSync()).toEqual("hello");
228
220
  });
229
221
 
230
- test(".realpathSync(…)", () => {
231
- const tempDir = Path.makeTempDirSync();
232
- const source = tempDir.join("foo.txt");
233
- source.writeSync("hello world!");
234
- const target = tempDir.join("bar.txt");
235
- source.symlinkSync(target);
236
- expect(source.realpathSync().path).toEqual(target.realpathSync().path);
237
- });
238
-
239
222
  test(".statSync(…)", () => {
240
223
  const file = Path.makeTempDirSync().join("foo.txt");
241
224
  file.writeSync("hello");
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/Path.ts"],
4
- "sourcesContent": ["import {\n appendFile,\n cp,\n lstat,\n mkdir,\n mkdtemp,\n readdir,\n readFile,\n realpath,\n rename,\n rm,\n stat,\n symlink,\n writeFile,\n} from \"node:fs/promises\";\nimport { homedir, tmpdir } from \"node:os\";\nimport { basename, dirname, extname, join } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport {\n xdgCache,\n xdgConfig,\n xdgData,\n xdgRuntime,\n xdgState,\n} from \"xdg-basedir\";\nimport type {\n lstatType,\n readDirType,\n readFileType,\n statType,\n} from \"./modifiedNodeTypes\";\n\n// Note that (non-static) functions in this file are defined using `function(\u2026)\n// { \u2026 }` rather than arrow functions, specifically because we want `this` to\n// operate on the `Path` instance.\n\ntype WritableData = Parameters<typeof writeFile>[1] | ReadableStream | Response;\nasync function wrangleWritableData(\n data: WritableData | Promise<WritableData>,\n): Promise<Parameters<typeof writeFile>[1]> {\n data = await data;\n if (data instanceof Response) {\n data = data.body ? Readable.fromWeb(data.body) : new Uint8Array(0);\n }\n if (data instanceof ReadableStream) {\n data = Readable.fromWeb(data);\n }\n return data;\n}\n\nexport class Path {\n // @ts-expect-error ts(2564): False positive. https://github.com/microsoft/TypeScript/issues/32194\n #path: string;\n /**\n * If `path` is a string starting with `file:///`, it will be parsed as a file URL.\n */\n constructor(path: string | URL | Path) {\n this.#setNormalizedPath(Path.#pathlikeToString(path));\n }\n\n /**\n * Similar to `new URL(path, base)`, but accepting and returning `Path` objects.\n * Note that `base` must be one of:\n *\n * - a valid second argument to `new URL(\u2026)`.\n * - a `Path` representing an absolute path.\n *\n */\n static resolve(path: string | URL | Path, base: string | URL | Path): Path {\n const baseURL = (() => {\n if (!(base instanceof Path)) {\n return base;\n }\n if (!base.isAbsolutePath()) {\n throw new Error(\n \"The `base` arg to `Path.resolve(\u2026)` must be an absolute path.\",\n );\n }\n return pathToFileURL(base.#path);\n })();\n return new Path(new URL(Path.#pathlikeToString(path), baseURL));\n }\n\n static #pathlikeToString(path: string | URL | Path): string {\n if (path instanceof Path) {\n return path.#path;\n }\n if (path instanceof URL) {\n return fileURLToPath(path);\n }\n if (typeof path === \"string\") {\n // TODO: allow turning off this heuristic?\n if (path.startsWith(\"file:///\")) {\n return fileURLToPath(path);\n }\n return path;\n }\n throw new Error(\"Invalid path\");\n }\n\n #setNormalizedPath(path: string): void {\n this.#path = join(path);\n }\n\n isAbsolutePath(): boolean {\n return this.#path.startsWith(\"/\");\n }\n\n toFileURL(): URL {\n if (!this.isAbsolutePath()) {\n throw new Error(\n \"Tried to convert to file URL when the path is not absolute.\",\n );\n }\n return pathToFileURL(this.#path);\n }\n\n /**\n * The `Path` can have a trailing slash, indicating that it represents a\n * directory. (If there is no trailing slash, it can represent either a file\n * or a directory.)\n *\n * Some operations will refuse to treat a directory path as a file path. This\n * function identifies such paths.\n */\n hasTrailingSlash(): boolean {\n // TODO: handle Windows semantically\n return this.#path.endsWith(\"/\");\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 * This follows `node` semantics for absolute paths: leading slashes in the given descendant segments are ignored.\n */\n join(...segments: (string | Path)[]): Path {\n const segmentStrings = segments.map((segment) =>\n segment instanceof Path ? segment.path : segment,\n );\n return new Path(join(this.#path, ...segmentStrings));\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 mustNotHaveTrailingSlash(this);\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 // TODO: find a neat way to dedup with the sync version?\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 mustNotHaveTrailingSlash(this);\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 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 read: typeof readFileType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n readFile(this.#path, options as any) as any;\n\n async readText(): Promise<string> {\n return readFile(this.#path, \"utf-8\");\n }\n\n /**\n * Reads JSON from the given file and parses it. No validation is performed\n * (beyond JSON parsing).\n *\n * An optional `fallback` value can be specified. It will be used if (and only\n * if) the file does not exist.\n *\n */\n\n // biome-ignore lint/suspicious/noExplicitAny: Allow a default of `any` to match `JSON.parse(\u2026)`.\n async readJSON<T = any>(options?: { fallback?: T }): Promise<T> {\n try {\n return JSON.parse(await this.readText());\n } catch (e) {\n if (\n (e as { code?: string }).code === \"ENOENT\" &&\n options &&\n \"fallback\" in options\n ) {\n return options.fallback as T;\n }\n throw e;\n }\n }\n\n /**\n * Returns the original `Path` (for chaining).\n */\n async appendFile(\n data: Parameters<typeof appendFile>[1],\n options?: Parameters<typeof appendFile>[2],\n ): Promise<Path> {\n await appendFile(this.#path, data, options);\n return this;\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: WritableData | Promise<WritableData>,\n options?: Parameters<typeof writeFile>[2],\n ): Promise<Path> {\n await this.parent.mkdir();\n await writeFile(this.#path, await wrangleWritableData(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 /** Returns the destination path. */\n async symlink(\n target: string | URL | Path,\n type?: Parameters<typeof symlink>[2],\n ): Promise<Path> {\n const targetPath = new Path(target);\n await symlink(\n this.path,\n targetPath.path,\n type as Exclude<Parameters<typeof symlink>[2], undefined>, // \uD83E\uDD37\n );\n return targetPath;\n }\n\n // I don't think `lstat` is a great name, but it does match the\n // well-established canonical commandline name. So in this case we keep the\n // name instead of using `realPath`.\n //\n // Note: There are no options in our API, because the only option is an\n // encoding. We set the encoding to construct the returned `Path`.\n async realpath(): Promise<Path> {\n return new Path(await realpath(this.#path, \"utf-8\"));\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n stat: typeof statType = (...options) => stat(this.#path, ...options) as any;\n\n // I don't think `lstat` is a great name, but it does match the\n // well-established canonical system call. So in this case we keep the\n // awkward abbreviation.\n lstat: typeof lstatType = (...options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n lstat(this.#path, ...options) 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 * {@link Path.xdg.runtime} does not have a default value. Consider\n * {@link Path.xdg.runtimeWithStateFallback} if you need a fallback but do not have a particular fallback in mind.\n */\n runtime: xdgRuntime ? new Path(xdgRuntime) : undefined,\n runtimeWithStateFallback: xdgRuntime\n ? new Path(xdgRuntime)\n : new Path(xdgState ?? Path.homedir.join(\".local/state\")),\n };\n\n /** Chainable function to print the path. Prints the same as:\n *\n * if (args.length > 0) {\n * console.log(...args);\n * }\n * console.log(this.path);\n *\n */\n // biome-ignore lint/suspicious/noExplicitAny: This is the correct type, based on `console.log(\u2026)`.\n debugPrint(...args: any[]): Path {\n if (args.length > 0) {\n console.log(...args);\n }\n console.log(this.#path);\n return this;\n }\n}\n\n/**\n * This function is useful to serialize any `Path`s in a structure to pass on to\n * functions that do not know about the `Path` class, e.g.\n *\n * function process(args: (string | Path)[]) {\n * const argsAsStrings = args.map(stringifyIfPath);\n * }\n *\n */\nexport function stringifyIfPath<T>(value: T | Path): T | string {\n if (value instanceof Path) {\n return value.toString();\n }\n return value;\n}\n\nexport function mustNotHaveTrailingSlash(path: Path): void {\n if (path.hasTrailingSlash()) {\n throw new Error(\n \"Path ends with a slash, which cannot be treated as a file.\",\n );\n }\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }