path-class 0.12.1 → 0.13.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.
@@ -2,12 +2,46 @@ import { expect, test } from "bun:test";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
4
  import "./PathSync";
5
+ import { execSync } from "node:child_process";
5
6
  import { constants } from "node:fs/promises";
6
- import { PrintableShellCommand } from "printable-shell-command";
7
7
  import { PathSync } from "./PathSync";
8
8
 
9
+ test.concurrent("PathSync.resolve(…)", () => {
10
+ expect(PathSync.resolve("foo/lish", new PathSync("/bar/baz")).path).toEqual(
11
+ "/bar/foo/lish",
12
+ );
13
+ expect(PathSync.resolve("foo/lish", new PathSync("/bar/baz/")).path).toEqual(
14
+ "/bar/baz/foo/lish",
15
+ );
16
+ expect(
17
+ () => PathSync.resolve("foo/lish", new PathSync("bar/baz")).path,
18
+ ).toThrow(/must be an absolute path/);
19
+ expect(PathSync.resolve("foo/lish", import.meta.url).path).toEqual(
20
+ new PathSync(import.meta.url).parent.join("foo/lish").path,
21
+ );
22
+ expect(PathSync.resolve("foo", "file:///hello/world").path).toEqual(
23
+ "/hello/foo",
24
+ );
25
+ expect(PathSync.resolve("foo", "file:///hello/world/").path).toEqual(
26
+ "/hello/world/foo",
27
+ );
28
+ });
29
+
30
+ test.concurrent(".resolve(…)", () => {
31
+ expect(new PathSync("/bar/baz").resolve("foo/lish").path).toEqual(
32
+ "/bar/foo/lish",
33
+ );
34
+ expect(new PathSync("/bar/baz/").resolve("foo/lish").path).toEqual(
35
+ "/bar/baz/foo/lish",
36
+ );
37
+ expect(() => new PathSync("bar/baz").resolve("foo/lish").path).toThrow(
38
+ /must be an absolute path/,
39
+ );
40
+ expect(new PathSync("/bar/baz").resolve("foo/lish")).toBeInstanceOf(PathSync);
41
+ });
42
+
9
43
  test.concurrent(".existsAsFileSync()", () => {
10
- const filePath = PathSync.makeTempDirSync().join("file.txt");
44
+ using filePath = PathSync.tempFilePathSync({ basename: "file.txt" });
11
45
  expect(filePath.existsSync()).toBe(false);
12
46
  expect(filePath.existsSync({ mustBe: "file" })).toBe(false);
13
47
  expect(filePath.existsSync({ mustBe: "directory" })).toBe(false);
@@ -25,29 +59,31 @@ test.concurrent(".existsAsFileSync()", () => {
25
59
  });
26
60
 
27
61
  test.concurrent(".existsAsDir()", () => {
28
- const filePath = PathSync.makeTempDirSync();
29
- expect(filePath.existsSync()).toBe(true);
30
- expect(() => filePath.existsSync({ mustBe: "file" })).toThrow(
62
+ using tempDir = PathSync.makeTempDirSync();
63
+ expect(tempDir.existsSync()).toBe(true);
64
+ expect(() => tempDir.existsSync({ mustBe: "file" })).toThrow(
31
65
  /PathSync exists but is not a file/,
32
66
  );
33
- expect(filePath.existsSync({ mustBe: "directory" })).toBe(true);
34
- expect(filePath.existsAsDirSync()).toBe(true);
35
- filePath.rm_rfSync();
36
- expect(filePath.existsSync()).toBe(false);
37
- expect(filePath.existsSync({ mustBe: "file" })).toBe(false);
38
- expect(filePath.existsSync({ mustBe: "directory" })).toBe(false);
39
- expect(filePath.existsAsDirSync()).toBe(false);
67
+ expect(tempDir.existsSync({ mustBe: "directory" })).toBe(true);
68
+ expect(tempDir.existsAsDirSync()).toBe(true);
69
+ tempDir.rm_rfSync();
70
+ expect(tempDir.existsSync()).toBe(false);
71
+ expect(tempDir.existsSync({ mustBe: "file" })).toBe(false);
72
+ expect(tempDir.existsSync({ mustBe: "directory" })).toBe(false);
73
+ expect(tempDir.existsAsDirSync()).toBe(false);
40
74
  });
41
75
 
42
76
  test.concurrent(".mkdirSync(…) (un-nested)", () => {
43
- const dir = PathSync.makeTempDirSync().join("mkdir-test");
77
+ using tempDir = PathSync.makeTempDirSync();
78
+ const dir = tempDir.join("mkdir-test");
44
79
  expect(dir.existsSync()).toBe(false);
45
80
  dir.mkdirSync();
46
81
  expect(dir.existsSync()).toBe(true);
47
82
  });
48
83
 
49
84
  test.concurrent(".mkdirSync(…) (nested)", () => {
50
- const dir = PathSync.makeTempDirSync().join("mkdir-test/nested");
85
+ using tempDir = PathSync.makeTempDirSync();
86
+ const dir = tempDir.join("mkdir-test/nested");
51
87
  expect(dir.existsSync()).toBe(false);
52
88
  expect(() => dir.mkdirSync({ recursive: false })).toThrow("no such file");
53
89
  dir.mkdirSync();
@@ -55,7 +91,7 @@ test.concurrent(".mkdirSync(…) (nested)", () => {
55
91
  });
56
92
 
57
93
  test.concurrent(".cpSync(…)", () => {
58
- const parentDir = PathSync.makeTempDirSync();
94
+ using parentDir = PathSync.makeTempDirSync();
59
95
  const file1 = parentDir.join("file1.txt");
60
96
  const file2 = parentDir.join("file2.txt");
61
97
  const file3 = parentDir.join("nonexistent/dirs/file3.txt");
@@ -80,7 +116,7 @@ test.concurrent(".cpSync(…)", () => {
80
116
  });
81
117
 
82
118
  test.concurrent(".renameSync(…)", () => {
83
- const parentDir = PathSync.makeTempDirSync();
119
+ using parentDir = PathSync.makeTempDirSync();
84
120
  const file1 = parentDir.join("file1.txt");
85
121
  const file2 = parentDir.join("file2.txt");
86
122
  const file3 = parentDir.join("nonexistent/dirs/file3.txt");
@@ -105,18 +141,28 @@ test.concurrent(".renameSync(…)", () => {
105
141
  });
106
142
 
107
143
  test.concurrent(".makeTempDirSync(…)", () => {
108
- const tempDir = PathSync.makeTempDirSync();
109
- expect(tempDir.path).toContain("/js-temp-");
110
- expect(tempDir.basename.path).toStartWith("js-temp-");
111
- expect(tempDir.existsAsDirSync()).toBe(true);
112
-
113
- const tempDir2 = PathSync.makeTempDirSync("foo");
114
- expect(tempDir2.path).not.toContain("/js-temp-");
115
- expect(tempDir2.basename.path).toStartWith("foo");
144
+ let disposablePathSyncString: string;
145
+ {
146
+ using tempDir = PathSync.makeTempDirSync();
147
+ disposablePathSyncString = tempDir.path;
148
+ expect(tempDir.path).toContain("/js-temp-");
149
+ expect(tempDir.basename.path).toStartWith("js-temp-");
150
+ expect(tempDir.existsAsDirSync()).toBe(true);
151
+ }
152
+ expect(new PathSync(disposablePathSyncString).existsAsDirSync()).toBe(false);
153
+
154
+ let disposablePathSyncString2: string;
155
+ {
156
+ using tempDir2 = PathSync.makeTempDirSync("foo");
157
+ disposablePathSyncString2 = tempDir2.path;
158
+ expect(tempDir2.path).not.toContain("/js-temp-");
159
+ expect(tempDir2.basename.path).toStartWith("foo");
160
+ }
161
+ expect(new PathSync(disposablePathSyncString2).existsAsDirSync()).toBe(false);
116
162
  });
117
163
 
118
164
  test.concurrent(".rmSync(…) (file)", () => {
119
- const file = PathSync.makeTempDirSync().join("file.txt");
165
+ using file = PathSync.tempFilePathSync({ basename: "file.txt" });
120
166
  file.writeSync("");
121
167
  expect(file.existsAsFileSync()).toBe(true);
122
168
  file.rmSync();
@@ -126,7 +172,7 @@ test.concurrent(".rmSync(…) (file)", () => {
126
172
  });
127
173
 
128
174
  test.concurrent(".rmSync(…) (folder)", () => {
129
- const tempDir = PathSync.makeTempDirSync();
175
+ using tempDir = PathSync.makeTempDirSync();
130
176
  const file = tempDir.join("file.txt");
131
177
  file.writeSync("");
132
178
  expect(tempDir.existsAsDirSync()).toBe(true);
@@ -138,7 +184,7 @@ test.concurrent(".rmSync(…) (folder)", () => {
138
184
  });
139
185
 
140
186
  test.concurrent(".rmDirSync(…) (folder)", () => {
141
- const tempDir = PathSync.makeTempDirSync();
187
+ using tempDir = PathSync.makeTempDirSync();
142
188
  const file = tempDir.join("file.txt");
143
189
  file.writeSync("");
144
190
  expect(tempDir.existsAsDirSync()).toBe(true);
@@ -150,7 +196,7 @@ test.concurrent(".rmDirSync(…) (folder)", () => {
150
196
  });
151
197
 
152
198
  test.concurrent(".rm_rfSync(…) (file)", () => {
153
- const file = PathSync.makeTempDirSync().join("file.txt");
199
+ using file = PathSync.tempFilePathSync({ basename: "file.txt" });
154
200
  file.writeSync("");
155
201
  expect(file.existsAsFileSync()).toBe(true);
156
202
  file.rm_rfSync();
@@ -161,7 +207,7 @@ test.concurrent(".rm_rfSync(…) (file)", () => {
161
207
  });
162
208
 
163
209
  test.concurrent(".rm_rfSync(…) (folder)", () => {
164
- const tempDir = PathSync.makeTempDirSync();
210
+ using tempDir = PathSync.makeTempDirSync();
165
211
  tempDir.join("file.txt").writeSync("");
166
212
  expect(tempDir.path).toContain("/js-temp-");
167
213
  expect(tempDir.existsSync()).toBe(true);
@@ -172,7 +218,7 @@ test.concurrent(".rm_rfSync(…) (folder)", () => {
172
218
  });
173
219
 
174
220
  test.concurrent(".readTextSync()", () => {
175
- const file = PathSync.makeTempDirSync().join("file.txt");
221
+ using file = PathSync.tempFilePathSync({ basename: "file.txt" });
176
222
  file.writeSync("hi");
177
223
  file.writeSync("bye");
178
224
 
@@ -181,7 +227,7 @@ test.concurrent(".readTextSync()", () => {
181
227
  });
182
228
 
183
229
  test.concurrent(".readJSONSync()", () => {
184
- const file = PathSync.makeTempDirSync().join("file.json");
230
+ using file = PathSync.tempFilePathSync({ basename: "file.json" });
185
231
  file.writeSync(JSON.stringify({ foo: "bar" }));
186
232
 
187
233
  expect(file.readJSONSync()).toEqual<Record<string, string>>({ foo: "bar" });
@@ -192,7 +238,7 @@ test.concurrent(".readJSONSync()", () => {
192
238
  });
193
239
 
194
240
  test.concurrent(".readJSONSync(…) with fallback", () => {
195
- const tempDir = PathSync.makeTempDirSync();
241
+ using tempDir = PathSync.makeTempDirSync();
196
242
  const file = tempDir.join("file.json");
197
243
  const json: { foo?: number } = file.readJSONSync({ fallback: { foo: 4 } });
198
244
  expect(json).toEqual({ foo: 4 });
@@ -210,7 +256,7 @@ test.concurrent(".readJSONSync(…) with fallback", () => {
210
256
  });
211
257
 
212
258
  test.concurrent(".writeSync(…)", () => {
213
- const tempDir = PathSync.makeTempDirSync();
259
+ using tempDir = PathSync.makeTempDirSync();
214
260
  const file = tempDir.join("file.json");
215
261
  expect(file.writeSync("foo")).toBe(file);
216
262
 
@@ -226,14 +272,14 @@ test.concurrent(".writeSync(…)", () => {
226
272
  });
227
273
 
228
274
  test.concurrent(".writeJSONSync(…)", () => {
229
- const file = PathSync.makeTempDirSync().join("file.json");
275
+ using file = PathSync.tempFilePathSync({ basename: "file.json" });
230
276
  expect(file.writeJSONSync({ foo: "bar" })).toBe(file);
231
277
 
232
278
  expect(file.readJSONSync()).toEqual<Record<string, string>>({ foo: "bar" });
233
279
  });
234
280
 
235
281
  test.concurrent(".appendFileSync(…)", () => {
236
- const file = PathSync.makeTempDirSync().join("file.txt");
282
+ using file = PathSync.tempFilePathSync({ basename: "file.txt" });
237
283
  file.appendFileSync("test\n");
238
284
  expect(file.readTextSync()).toEqual("test\n");
239
285
  file.appendFileSync("more\n");
@@ -241,7 +287,7 @@ test.concurrent(".appendFileSync(…)", () => {
241
287
  });
242
288
 
243
289
  test.concurrent(".readDirSync(…)", () => {
244
- const dir = PathSync.makeTempDirSync();
290
+ using dir = PathSync.makeTempDirSync();
245
291
  dir.join("file.txt").writeSync("hello");
246
292
  dir.join("dir/file.json").writeSync("hello");
247
293
 
@@ -255,7 +301,7 @@ test.concurrent(".readDirSync(…)", () => {
255
301
  });
256
302
 
257
303
  test.concurrent(".symlinkSync(…)", () => {
258
- const tempDir = PathSync.makeTempDirSync();
304
+ using tempDir = PathSync.makeTempDirSync();
259
305
  const source = tempDir.join("foo.txt");
260
306
  const target = tempDir.join("bar.txt");
261
307
  source.symlinkSync(target);
@@ -267,7 +313,7 @@ test.concurrent(".symlinkSync(…)", () => {
267
313
  });
268
314
 
269
315
  test.concurrent(".realpathSync(…)", () => {
270
- const tempDir = PathSync.makeTempDirSync();
316
+ using tempDir = PathSync.makeTempDirSync();
271
317
  const source = tempDir.join("foo.txt");
272
318
  source.writeSync("hello world!");
273
319
  const target = tempDir.join("bar.txt");
@@ -276,7 +322,7 @@ test.concurrent(".realpathSync(…)", () => {
276
322
  });
277
323
 
278
324
  test.concurrent(".statSync(…)", () => {
279
- const file = PathSync.makeTempDirSync().join("foo.txt");
325
+ using file = PathSync.tempFilePathSync({ basename: "foo.txt" });
280
326
  file.writeSync("hello");
281
327
 
282
328
  expect(file.statSync()?.size).toEqual(5);
@@ -285,7 +331,7 @@ test.concurrent(".statSync(…)", () => {
285
331
  });
286
332
 
287
333
  test.concurrent(".lstatSync(…)", () => {
288
- const tempDir = PathSync.makeTempDirSync();
334
+ using tempDir = PathSync.makeTempDirSync();
289
335
  const source = tempDir.join("foo.txt");
290
336
  const target = tempDir.join("bar.txt");
291
337
  source.symlinkSync(target);
@@ -297,46 +343,47 @@ test.concurrent(".lstatSync(…)", () => {
297
343
  expect(target.readTextSync()).toEqual("hello");
298
344
  });
299
345
 
346
+ // Note: this test uses `execSync(…)` because it runs the binary and returns
347
+ // expected error messages correctly. Further, it helps keep this entire test
348
+ // file sync (which we have some basic checks for in `lint-sync-code.ts` that
349
+ // don't seem like a great idea to work around).
300
350
  test.concurrent(".chmodSync(…)", () => {
301
- const binPath = PathSync.makeTempDirSync().join("nonexistent.bin");
302
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
303
- /ENOENT|Premature close/,
351
+ using binPath = PathSync.tempFilePathSync({ basename: "bin.bash" });
352
+ expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
353
+ /No such file or directory|not found/,
304
354
  );
305
- binPath.writeSync(`#!/usr/bin/env bash
306
-
307
- echo hi`);
308
- // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
309
- // binPath.writeSync(`#!/usr/bin/env -S bun run --
355
+ binPath.writeSync(`#!/usr/bin/env -S bun run --
310
356
 
311
- // console.log("hi");`);
312
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
313
- /EACCES|Premature close/,
357
+ console.log("hi");`);
358
+ expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
359
+ /Permission denied/,
314
360
  );
315
361
  binPath.chmodSync(0o755);
316
- expect(() => new PrintableShellCommand(binPath, []).text()).not.toThrow();
362
+ expect(execSync(binPath.path, { encoding: "utf-8" })).toEqual("hi\n");
317
363
  });
318
364
 
365
+ // Note: this test uses `execSync(…)` because it runs the binary and returns
366
+ // expected error messages correctly. Further, it helps keep this entire test
367
+ // file sync (which we have some basic checks for in `lint-sync-code.ts` that
368
+ // don't seem like a great idea to work around).
319
369
  test.concurrent(".chmodXSync(…)", () => {
320
- const binPath = PathSync.makeTempDirSync().join("nonexistent.bin");
321
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
322
- /ENOENT|Premature close/,
370
+ using binPath = PathSync.tempFilePathSync({ basename: "bin.bash" });
371
+ expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
372
+ /No such file or directory|not found/,
323
373
  );
324
- binPath.writeSync(`#!/usr/bin/env bash
325
-
326
- echo hi`);
327
- // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
328
- // binPath.writeSync(`#!/usr/bin/env -S bun run --
374
+ binPath.writeSync(`#!/usr/bin/env -S bun run --
329
375
 
330
- // console.log("hi");`);
331
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
332
- /EACCES|Premature close/,
376
+ console.log("hi");`);
377
+ // TODO: Should not be `ENOENT`? Probably `EACCES`.
378
+ expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
379
+ /Permission denied/,
333
380
  );
334
381
  expect(binPath.statSync().mode & constants.S_IWUSR).toBeTruthy();
335
382
  binPath.chmodSync(0o444);
336
383
  expect(binPath.statSync().mode & constants.S_IWUSR).toBeFalsy();
337
384
  expect(binPath.statSync().mode & constants.S_IXUSR).toBeFalsy();
338
385
  binPath.chmodXSync();
339
- expect(() => new PrintableShellCommand(binPath, []).text()).not.toThrow();
386
+ expect(execSync(binPath.path, { encoding: "utf-8" })).toEqual("hi\n");
340
387
  expect(binPath.statSync().mode & constants.S_IWUSR).toBeFalsy();
341
388
  expect(binPath.statSync().mode & constants.S_IXUSR).toBeTruthy();
342
389
  });
@@ -25,6 +25,9 @@ import type {
25
25
  statSyncType,
26
26
  } from "./modifiedNodeTypes";
27
27
 
28
+ const DEFAULT_TEMP_PREFIX = "js-temp-sync-";
29
+ const DEFAULT_TEMP_FILE_NAME = "file";
30
+
28
31
  export class PathSync extends Path {
29
32
  static override fromString(s: string): PathSync {
30
33
  return new PathSync(s);
@@ -34,6 +37,20 @@ export class PathSync extends Path {
34
37
  return new PathSync(Path.resolve(...args));
35
38
  }
36
39
 
40
+ override resolve(...args: Parameters<Path["resolve"]>): PathSync {
41
+ return new PathSync(super.resolve(...args));
42
+ }
43
+
44
+ override descendantRelativePath(
45
+ ...args: Parameters<Path["descendantRelativePath"]>
46
+ ): PathSync | null {
47
+ const v = super.descendantRelativePath(...args);
48
+ if (v === null) {
49
+ return null;
50
+ }
51
+ return new PathSync(v);
52
+ }
53
+
37
54
  override toggleTrailingSlash(
38
55
  ...args: Parameters<Path["toggleTrailingSlash"]>
39
56
  ): PathSync {
@@ -169,9 +186,43 @@ export class PathSync extends Path {
169
186
  return destinationPath;
170
187
  }
171
188
 
172
- static makeTempDirSync(prefix?: string): PathSync {
173
- return new PathSync(
174
- mkdtempSync(new Path(tmpdir()).join(prefix ?? "js-temp-").toString()),
189
+ static makeTempDirSync(prefix?: string): DisposablePathSync {
190
+ return new DisposablePathSync(
191
+ mkdtempSync(
192
+ new Path(tmpdir()).join(prefix ?? DEFAULT_TEMP_PREFIX).toString(),
193
+ ),
194
+ );
195
+ }
196
+
197
+ /**
198
+ * Return a path:
199
+ *
200
+ * - whose parent dir is a temp dir that *has* been created, but
201
+ * - which has itself not yet been created.
202
+ *
203
+ * Note that this path can actually also be used to create dir, but it is most
204
+ * convenient to get a path for a temporary file that can be written to, while
205
+ * having a disposal implementation that cleans everything up:
206
+ *
207
+ * using tempFile = PathSync.tempFilePathSync({ basename: "foo.txt" });
208
+ * tempFile.writeSync("hello world!");
209
+ * // …
210
+ *
211
+ * Note that that the following are equivalent when *not* using `using`:
212
+ *
213
+ * PathSync.tempFilePathSync({ basename: "foo.txt" });
214
+ * PathSync.makeTempDirSync().join("file.txt");
215
+ *
216
+ * However, it is recommended to use `using` to ensure cleanup.
217
+ */
218
+ static tempFilePathSync(options: {
219
+ tempDirPrefix?: string;
220
+ basename?: string | Path;
221
+ }): DisposablePathSync {
222
+ const tempDir = PathSync.makeTempDirSync(options?.tempDirPrefix);
223
+ return new DisposablePathSync(
224
+ tempDir.join(options?.basename ?? DEFAULT_TEMP_FILE_NAME),
225
+ { disposePathInstead: tempDir },
175
226
  );
176
227
  }
177
228
 
@@ -280,3 +331,22 @@ export class PathSync extends Path {
280
331
  return this;
281
332
  }
282
333
  }
334
+
335
+ export class DisposablePathSync extends PathSync {
336
+ #options?: { disposePathInstead: PathSync };
337
+ constructor(
338
+ path: ConstructorParameters<typeof Path>[0],
339
+ options?: { disposePathInstead: Path | string },
340
+ ) {
341
+ super(path);
342
+ if (options) {
343
+ this.#options = {
344
+ disposePathInstead: new PathSync(options.disposePathInstead),
345
+ };
346
+ }
347
+ }
348
+
349
+ [Symbol.dispose]() {
350
+ (this.#options?.disposePathInstead ?? this).rm_rfSync();
351
+ }
352
+ }
@@ -1,10 +1,49 @@
1
- import { mock } from "bun:test";
2
- import nodeOS from "node:os";
1
+ // Note: we do not import from the library implementation here, because we need
2
+ // our setup to occur before the code loads.
3
+
4
+ import { afterAll, mock } from "bun:test";
5
+ import assert from "node:assert";
6
+ import { mkdtemp, rmdir } from "node:fs/promises";
7
+ import nodeOS, { tmpdir } from "node:os";
8
+ import { join } from "node:path";
9
+ import { env } from "node:process";
10
+ import { ErgonomicDate } from "ergonomic-date";
11
+
12
+ const MOCK_TEMP_DIR_ENV_VAR = "MOCK_TEMP_DIR";
13
+
14
+ const mockTempDir = await (async () => {
15
+ if (MOCK_TEMP_DIR_ENV_VAR in env) {
16
+ const tempDir = env[MOCK_TEMP_DIR_ENV_VAR];
17
+ assert(tempDir);
18
+ console.log(
19
+ `Using the specificed mock temp dir from the \`${MOCK_TEMP_DIR_ENV_VAR}\` env var: ${tempDir}`,
20
+ );
21
+ return tempDir;
22
+ } else {
23
+ const mockTempDir = await mkdtemp(
24
+ join(
25
+ tmpdir(),
26
+ `path-class-mock-temp-dir-${new ErgonomicDate().multipurposeTimestamp}-`,
27
+ ),
28
+ );
29
+ console.log(
30
+ `Using a mock temp dir inside the main temp dir: ${mockTempDir}`,
31
+ );
32
+
33
+ afterAll(async () => {
34
+ // If this fails, one of the tests hasn't cleaned up after itself properly.
35
+ await rmdir(mockTempDir);
36
+ });
37
+ return mockTempDir;
38
+ }
39
+ })();
3
40
 
4
41
  const nodeOSMocked = {
5
42
  ...nodeOS,
6
43
  homedir: () => "/mock/home/dir",
44
+ tmpdir: () => mockTempDir,
7
45
  };
46
+
8
47
  // biome-ignore lint/suspicious/noExplicitAny: This isn't worth wrangling types for.
9
48
  (nodeOSMocked as any).default = nodeOSMocked; // Needed because `xdg-basedir` imports the default.
10
49
 
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../../../src/Path.ts"],
4
- "sourcesContent": ["import {\n appendFile,\n chmod,\n constants,\n cp,\n lstat,\n mkdir,\n mkdtemp,\n readdir,\n readFile,\n realpath,\n rename,\n rm,\n rmdir,\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 { cwd } from \"node:process\";\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\nexport function 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.startsWith(\"../\")) {\n return ResolutionPrefix.Relative;\n } else if (pathString === \".\") {\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 static fromString(s: string): Path {\n if (typeof s !== \"string\") {\n throw new Error(\n \"Invalid argument to `Path.fromString(\u2026)` \u2014 expected a string.\",\n );\n }\n return new Path(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 * Toggles or sets a trailing slash as specified.\n * If the path is `/`, it will always be left as-is.\n */\n // Similar convention to:\n // https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle Most\n // use cases will probably use the `force` parameters, but supporting the\n // \"toggle\" use case is easy to implement and hopefully will make the name\n // and API more familiar to web devs.\n toggleTrailingSlash(force?: boolean): Path {\n if (this.#path === \"/\") {\n return this;\n }\n const wantTrailingSlash = force ?? !this.hasTrailingSlash();\n if (wantTrailingSlash) {\n return this.hasTrailingSlash() ? this : this.join(\"./\");\n } else {\n return this.hasTrailingSlash() ? new Path(this.#path.slice(0, -1)) : this;\n }\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 /**\n * Format this with the escape codes for printing to the shell in bold blue.\n *\n * Example usage:\n *\n * console.log(`Processing: ${path.blue}`)\n *\n */\n get blue() {\n const { styleText } = globalThis.process.getBuiltinModule(\"node:util\");\n return styleText([\"bold\", \"blue\"], 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 if (constraints?.mustBe === \"file\") {\n mustNotHaveTrailingSlash(this);\n }\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 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] & { createIntermediateDirs?: boolean },\n ): Promise<Path> {\n const { createIntermediateDirs, ...cpOptions } = options ?? {};\n const destinationPath = new Path(destination);\n if (createIntermediateDirs ?? true) {\n await destinationPath.parent.mkdir();\n }\n await cp(this.#path, destinationPath.#path, cpOptions);\n return destinationPath;\n }\n\n // TODO: check idempotency semantics when the destination exists and is a folder.\n async rename(\n destination: string | URL | Path,\n options?: { createIntermediateDirs?: boolean },\n ): Promise<Path> {\n const destinationPath = new Path(destination);\n if (options?.createIntermediateDirs ?? true) {\n await destinationPath.parent.mkdir();\n }\n await rename(this.#path, destinationPath.#path);\n return destinationPath;\n }\n\n /**\n * Create a temporary dir inside the global temp dir for the current user.\n *\n * This can be used with [`await\n * using`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/await_using)\n * to automatically delete the dir when it goes out of scope.\n *\n * {\n * await using tempDir = await Path.makeTempDir();\n * // Temporary dir exists while we're inside this block.\n * }\n * // Temporary dir has now been deleted.\n *\n * Note that (due to the semantics of JS runtime implementations) this does\n * not delete the temp dir if the process calls `exit(\u2026)` before the `using`\n * goes out of scope.\n *\n * */\n static async makeTempDir(prefix?: string): Promise<AsyncDisposablePath> {\n return new AsyncDisposablePath(\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 async rmDir(): Promise<void> {\n await rmdir(this.#path);\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 `realpath` 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 async chmod(mode: Parameters<typeof chmod>[1]): Promise<Path> {\n await chmod(this.#path, mode);\n return this;\n }\n\n /**\n * Add the executable bit (for everyone) to the given path without modifying other bits (`chmod +x`).\n */\n async chmodX(): Promise<Path> {\n const { mode } = await this.stat();\n await this.chmod(\n mode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH,\n );\n return this;\n }\n\n static get homedir(): Path {\n return new Path(homedir());\n }\n\n /**\n * Get the current working directory as a path. Always includes a trailing slash.\n * Note that this computes the `cwd` from fresh every time, in case it has changed for the current process.\n */\n static get cwd(): Path {\n return new Path(cwd()).toggleTrailingSlash(true);\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\nexport class AsyncDisposablePath extends Path {\n async [Symbol.asyncDispose]() {\n await this.rm_rf();\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\nconst tmp = await Path.makeTempDir();\n(await tmp.join(\"foo.json\").write(\"foo\")).rename(\n tmp.join(\"sdfsD\", \"sdfsdfsdf\", \"sdfsdf.json\"),\n);\n"],
5
- "mappings": "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",
6
- "names": ["ResolutionPrefix"]
7
- }