path-class 0.12.2 → 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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/sync/PathSync.ts"],
4
- "sourcesContent": ["import {\n appendFileSync,\n chmodSync,\n cpSync,\n lstatSync,\n mkdirSync,\n mkdtempSync,\n readdirSync,\n readFileSync,\n realpathSync,\n renameSync,\n rmdirSync,\n rmSync,\n statSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { constants } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { mustNotHaveTrailingSlash, Path } from \"../Path\";\nimport type {\n lstatSyncType,\n readDirSyncType,\n readFileSyncType,\n statSyncType,\n} from \"./modifiedNodeTypes\";\n\nexport class PathSync extends Path {\n static override fromString(s: string): PathSync {\n return new PathSync(s);\n }\n\n static override resolve(...args: Parameters<typeof Path.resolve>): PathSync {\n return new PathSync(Path.resolve(...args));\n }\n\n override toggleTrailingSlash(\n ...args: Parameters<Path[\"toggleTrailingSlash\"]>\n ): PathSync {\n return new PathSync(super.toggleTrailingSlash(...args));\n }\n\n override join(...args: Parameters<Path[\"join\"]>): PathSync {\n return new PathSync(super.join(...args));\n }\n\n override asRelative(...args: Parameters<Path[\"asRelative\"]>): PathSync {\n return new PathSync(super.asRelative(...args));\n }\n\n override asAbsolute(...args: Parameters<Path[\"asAbsolute\"]>): PathSync {\n return new PathSync(super.asAbsolute(...args));\n }\n\n override asBare(...args: Parameters<Path[\"asBare\"]>): PathSync {\n return new PathSync(super.asBare(...args));\n }\n\n override extendBasename(\n ...args: Parameters<Path[\"extendBasename\"]>\n ): PathSync {\n return new PathSync(super.extendBasename(...args));\n }\n\n override get parent(): PathSync {\n return new PathSync(super.parent);\n }\n\n override get dirname(): PathSync {\n return new PathSync(super.dirname);\n }\n\n override get basename(): PathSync {\n return new PathSync(super.basename);\n }\n\n static override get homedir(): PathSync {\n return new PathSync(Path.homedir);\n }\n\n static override get cwd(): PathSync {\n return new PathSync(Path.cwd);\n }\n\n override debugPrint(...args: Parameters<Path[\"debugPrint\"]>): PathSync {\n return new PathSync(super.debugPrint(...args));\n }\n\n // TODO: find a neat way to dedup with the async version? // lint-sync-code-expect-error\n existsSync(constraints?: { mustBe: \"file\" | \"directory\" }): boolean {\n if (constraints?.mustBe === \"file\") {\n mustNotHaveTrailingSlash(this);\n }\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 if (stats.isFile()) {\n return true;\n }\n throw new Error(`PathSync exists but is not a file: ${this.path}`);\n }\n case \"directory\": {\n if (stats.isDirectory()) {\n return true;\n }\n throw new Error(`PathSync exists but is not a directory: ${this.path}`);\n }\n default: {\n throw new Error(\"Invalid path type constraint\");\n }\n }\n }\n\n existsAsFileSync(): boolean {\n return this.existsSync({ mustBe: \"file\" });\n }\n\n existsAsDirSync(): boolean {\n return this.existsSync({ mustBe: \"directory\" });\n }\n\n mkdirSync(options?: Parameters<typeof mkdirSync>[1]): PathSync {\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\n cpSync(\n destination: string | URL | Path,\n options?: Parameters<typeof cpSync>[2] & {\n createIntermediateDirs?: boolean;\n },\n ): PathSync {\n const { createIntermediateDirs, ...cpOptions } = options ?? {};\n const destinationPath = new PathSync(destination);\n if (createIntermediateDirs ?? true) {\n destinationPath.parent.mkdirSync();\n }\n cpSync(this.path, destinationPath.path, cpOptions);\n return destinationPath;\n }\n\n renameSync(\n destination: string | URL | Path,\n options?: { createIntermediateDirs?: boolean },\n ): PathSync {\n const destinationPath = new PathSync(destination);\n if (options?.createIntermediateDirs ?? true) {\n destinationPath.parent.mkdirSync();\n }\n renameSync(this.path, destinationPath.path);\n return destinationPath;\n }\n\n static makeTempDirSync(prefix?: string): PathSync {\n return new PathSync(\n mkdtempSync(new Path(tmpdir()).join(prefix ?? \"js-temp-\").toString()),\n );\n }\n\n rmSync(options?: Parameters<typeof rmSync>[1]): void {\n rmSync(this.path, options);\n }\n\n rmDirSync(): void {\n rmdirSync(this.path);\n }\n\n rm_rfSync(options?: Parameters<typeof rmSync>[1]): void {\n this.rmSync({ recursive: true, force: true, ...(options ?? {}) });\n }\n\n readSync: typeof readFileSyncType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n readFileSync(this.path, options) as any;\n\n readTextSync(): string {\n return readFileSync(this.path, \"utf-8\");\n }\n\n readJSONSync<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\n appendFileSync(\n data: Parameters<typeof appendFileSync>[1],\n options?: Parameters<typeof appendFileSync>[2],\n ): PathSync {\n appendFileSync(this.path, data, options);\n return this;\n }\n\n writeSync(\n data: Parameters<typeof writeFileSync>[1],\n options?: Parameters<typeof writeFileSync>[2],\n ): PathSync {\n this.parent.mkdirSync();\n writeFileSync(this.path, data, options);\n return this;\n }\n\n writeJSONSync<T>(\n data: T,\n replacer: Parameters<typeof JSON.stringify>[1] = null,\n space: Parameters<typeof JSON.stringify>[2] = \" \",\n ): PathSync {\n this.parent.mkdirSync();\n this.writeSync(JSON.stringify(data, replacer, space));\n return this;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling.\n readDirSync: typeof readDirSyncType = (options: any) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n readdirSync(this.path, options) as any;\n\n symlinkSync(\n target: string | URL | Path,\n type?: Parameters<typeof symlinkSync>[2],\n ): PathSync {\n const targetPath = new PathSync(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 realpathSync(): PathSync {\n return new PathSync(realpathSync(this.path));\n }\n\n statSync: typeof statSyncType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n statSync(this.path, options) as any;\n\n lstatSync: typeof lstatSyncType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n lstatSync(this.path, options) as any;\n\n chmodSync(mode: Parameters<typeof chmodSync>[1]): PathSync {\n chmodSync(this.path, mode);\n return this;\n }\n\n chmodXSync(): PathSync {\n const { mode } = this.statSync();\n this.chmodSync(\n mode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH,\n );\n return this;\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import {\n appendFileSync,\n chmodSync,\n cpSync,\n lstatSync,\n mkdirSync,\n mkdtempSync,\n readdirSync,\n readFileSync,\n realpathSync,\n renameSync,\n rmdirSync,\n rmSync,\n statSync,\n symlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { constants } from \"node:fs/promises\";\nimport { tmpdir } from \"node:os\";\nimport { mustNotHaveTrailingSlash, Path } from \"../Path\";\nimport type {\n lstatSyncType,\n readDirSyncType,\n readFileSyncType,\n statSyncType,\n} from \"./modifiedNodeTypes\";\n\nconst DEFAULT_TEMP_PREFIX = \"js-temp-sync-\";\nconst DEFAULT_TEMP_FILE_NAME = \"file\";\n\nexport class PathSync extends Path {\n static override fromString(s: string): PathSync {\n return new PathSync(s);\n }\n\n static override resolve(...args: Parameters<typeof Path.resolve>): PathSync {\n return new PathSync(Path.resolve(...args));\n }\n\n override resolve(...args: Parameters<Path[\"resolve\"]>): PathSync {\n return new PathSync(super.resolve(...args));\n }\n\n override descendantRelativePath(\n ...args: Parameters<Path[\"descendantRelativePath\"]>\n ): PathSync | null {\n const v = super.descendantRelativePath(...args);\n if (v === null) {\n return null;\n }\n return new PathSync(v);\n }\n\n override toggleTrailingSlash(\n ...args: Parameters<Path[\"toggleTrailingSlash\"]>\n ): PathSync {\n return new PathSync(super.toggleTrailingSlash(...args));\n }\n\n override join(...args: Parameters<Path[\"join\"]>): PathSync {\n return new PathSync(super.join(...args));\n }\n\n override asRelative(...args: Parameters<Path[\"asRelative\"]>): PathSync {\n return new PathSync(super.asRelative(...args));\n }\n\n override asAbsolute(...args: Parameters<Path[\"asAbsolute\"]>): PathSync {\n return new PathSync(super.asAbsolute(...args));\n }\n\n override asBare(...args: Parameters<Path[\"asBare\"]>): PathSync {\n return new PathSync(super.asBare(...args));\n }\n\n override extendBasename(\n ...args: Parameters<Path[\"extendBasename\"]>\n ): PathSync {\n return new PathSync(super.extendBasename(...args));\n }\n\n override get parent(): PathSync {\n return new PathSync(super.parent);\n }\n\n override get dirname(): PathSync {\n return new PathSync(super.dirname);\n }\n\n override get basename(): PathSync {\n return new PathSync(super.basename);\n }\n\n static override get homedir(): PathSync {\n return new PathSync(Path.homedir);\n }\n\n static override get cwd(): PathSync {\n return new PathSync(Path.cwd);\n }\n\n override debugPrint(...args: Parameters<Path[\"debugPrint\"]>): PathSync {\n return new PathSync(super.debugPrint(...args));\n }\n\n // TODO: find a neat way to dedup with the async version? // lint-sync-code-expect-error\n existsSync(constraints?: { mustBe: \"file\" | \"directory\" }): boolean {\n if (constraints?.mustBe === \"file\") {\n mustNotHaveTrailingSlash(this);\n }\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 if (stats.isFile()) {\n return true;\n }\n throw new Error(`PathSync exists but is not a file: ${this.path}`);\n }\n case \"directory\": {\n if (stats.isDirectory()) {\n return true;\n }\n throw new Error(`PathSync exists but is not a directory: ${this.path}`);\n }\n default: {\n throw new Error(\"Invalid path type constraint\");\n }\n }\n }\n\n existsAsFileSync(): boolean {\n return this.existsSync({ mustBe: \"file\" });\n }\n\n existsAsDirSync(): boolean {\n return this.existsSync({ mustBe: \"directory\" });\n }\n\n mkdirSync(options?: Parameters<typeof mkdirSync>[1]): PathSync {\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\n cpSync(\n destination: string | URL | Path,\n options?: Parameters<typeof cpSync>[2] & {\n createIntermediateDirs?: boolean;\n },\n ): PathSync {\n const { createIntermediateDirs, ...cpOptions } = options ?? {};\n const destinationPath = new PathSync(destination);\n if (createIntermediateDirs ?? true) {\n destinationPath.parent.mkdirSync();\n }\n cpSync(this.path, destinationPath.path, cpOptions);\n return destinationPath;\n }\n\n renameSync(\n destination: string | URL | Path,\n options?: { createIntermediateDirs?: boolean },\n ): PathSync {\n const destinationPath = new PathSync(destination);\n if (options?.createIntermediateDirs ?? true) {\n destinationPath.parent.mkdirSync();\n }\n renameSync(this.path, destinationPath.path);\n return destinationPath;\n }\n\n static makeTempDirSync(prefix?: string): DisposablePathSync {\n return new DisposablePathSync(\n mkdtempSync(\n new Path(tmpdir()).join(prefix ?? DEFAULT_TEMP_PREFIX).toString(),\n ),\n );\n }\n\n /**\n * Return a path:\n *\n * - whose parent dir is a temp dir that *has* been created, but\n * - which has itself not yet been created.\n *\n * Note that this path can actually also be used to create dir, but it is most\n * convenient to get a path for a temporary file that can be written to, while\n * having a disposal implementation that cleans everything up:\n *\n * using tempFile = PathSync.tempFilePathSync({ basename: \"foo.txt\" });\n * tempFile.writeSync(\"hello world!\");\n * // \u2026\n *\n * Note that that the following are equivalent when *not* using `using`:\n *\n * PathSync.tempFilePathSync({ basename: \"foo.txt\" });\n * PathSync.makeTempDirSync().join(\"file.txt\");\n *\n * However, it is recommended to use `using` to ensure cleanup.\n */\n static tempFilePathSync(options: {\n tempDirPrefix?: string;\n basename?: string | Path;\n }): DisposablePathSync {\n const tempDir = PathSync.makeTempDirSync(options?.tempDirPrefix);\n return new DisposablePathSync(\n tempDir.join(options?.basename ?? DEFAULT_TEMP_FILE_NAME),\n { disposePathInstead: tempDir },\n );\n }\n\n rmSync(options?: Parameters<typeof rmSync>[1]): void {\n rmSync(this.path, options);\n }\n\n rmDirSync(): void {\n rmdirSync(this.path);\n }\n\n rm_rfSync(options?: Parameters<typeof rmSync>[1]): void {\n this.rmSync({ recursive: true, force: true, ...(options ?? {}) });\n }\n\n readSync: typeof readFileSyncType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n readFileSync(this.path, options) as any;\n\n readTextSync(): string {\n return readFileSync(this.path, \"utf-8\");\n }\n\n readJSONSync<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\n appendFileSync(\n data: Parameters<typeof appendFileSync>[1],\n options?: Parameters<typeof appendFileSync>[2],\n ): PathSync {\n appendFileSync(this.path, data, options);\n return this;\n }\n\n writeSync(\n data: Parameters<typeof writeFileSync>[1],\n options?: Parameters<typeof writeFileSync>[2],\n ): PathSync {\n this.parent.mkdirSync();\n writeFileSync(this.path, data, options);\n return this;\n }\n\n writeJSONSync<T>(\n data: T,\n replacer: Parameters<typeof JSON.stringify>[1] = null,\n space: Parameters<typeof JSON.stringify>[2] = \" \",\n ): PathSync {\n this.parent.mkdirSync();\n this.writeSync(JSON.stringify(data, replacer, space));\n return this;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Type wrangling.\n readDirSync: typeof readDirSyncType = (options: any) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n readdirSync(this.path, options) as any;\n\n symlinkSync(\n target: string | URL | Path,\n type?: Parameters<typeof symlinkSync>[2],\n ): PathSync {\n const targetPath = new PathSync(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 realpathSync(): PathSync {\n return new PathSync(realpathSync(this.path));\n }\n\n statSync: typeof statSyncType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n statSync(this.path, options) as any;\n\n lstatSync: typeof lstatSyncType = (options) =>\n // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.\n lstatSync(this.path, options) as any;\n\n chmodSync(mode: Parameters<typeof chmodSync>[1]): PathSync {\n chmodSync(this.path, mode);\n return this;\n }\n\n chmodXSync(): PathSync {\n const { mode } = this.statSync();\n this.chmodSync(\n mode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH,\n );\n return this;\n }\n}\n\nexport class DisposablePathSync extends PathSync {\n #options?: { disposePathInstead: PathSync };\n constructor(\n path: ConstructorParameters<typeof Path>[0],\n options?: { disposePathInstead: Path | string },\n ) {\n super(path);\n if (options) {\n this.#options = {\n disposePathInstead: new PathSync(options.disposePathInstead),\n };\n }\n }\n\n [Symbol.dispose]() {\n (this.#options?.disposePathInstead ?? this).rm_rfSync();\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "path-class",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
4
4
  "description": "A semantic `Path` class for `node` and `bun`. Inspired by `bun`'s [`file(…)`](https://bun.com/docs/runtime/file-io) API, but `node`-compatible.",
5
5
  "author": "Lucas Garron <code@garron.net>",
6
6
  "license": "MIT",
@@ -30,6 +30,7 @@
30
30
  "@cubing/dev-config": ">=0.8.1",
31
31
  "@types/bun": "^1.3.0",
32
32
  "bun-dx": ">=0.1.3",
33
+ "ergonomic-date": "^0.1.3",
33
34
  "esbuild": "^0.25.10",
34
35
  "printable-shell-command": "^4.0.4",
35
36
  "readme-cli-help": ">=0.4.9",
package/src/Path.test.ts CHANGED
@@ -1,9 +1,10 @@
1
- import { expect, spyOn, test } from "bun:test";
1
+ import { expect, jest, spyOn, test } from "bun:test";
2
+ import assert from "node:assert";
3
+ import { execSync } from "node:child_process";
2
4
  import { constants, readFile, realpath } from "node:fs/promises";
3
5
  import { join } from "node:path";
4
6
  import { chdir } from "node:process";
5
- import { PrintableShellCommand } from "printable-shell-command";
6
- import { Path, ResolutionPrefix, stringifyIfPath } from "./Path";
7
+ import { Path, ResolutionPrefix } from "./Path";
7
8
 
8
9
  test.concurrent("constructor", async () => {
9
10
  expect(new Path("bare").path).toEqual("bare");
@@ -25,6 +26,8 @@ test.concurrent("constructor", async () => {
25
26
  expect(new Path("../").path).toEqual("../");
26
27
  expect(new Path(new URL("file:///root/")).path).toEqual("/root/");
27
28
  expect(new Path(new Path("foo")).path).toEqual("foo");
29
+
30
+ expect(new Path("./.foo").path).toEqual("./.foo");
28
31
  });
29
32
 
30
33
  test.concurrent(".fromString(…)", async () => {
@@ -83,6 +86,9 @@ test.concurrent("Path.resolve(…)", async () => {
83
86
  expect(Path.resolve("foo/lish", new Path("/bar/baz")).path).toEqual(
84
87
  "/bar/foo/lish",
85
88
  );
89
+ expect(Path.resolve("foo/lish", new Path("/bar/baz/")).path).toEqual(
90
+ "/bar/baz/foo/lish",
91
+ );
86
92
  expect(() => Path.resolve("foo/lish", new Path("bar/baz")).path).toThrow(
87
93
  /must be an absolute path/,
88
94
  );
@@ -95,6 +101,78 @@ test.concurrent("Path.resolve(…)", async () => {
95
101
  );
96
102
  });
97
103
 
104
+ test.concurrent(".resolve(…)", async () => {
105
+ expect(new Path("/bar/baz").resolve("foo/lish").path).toEqual(
106
+ "/bar/foo/lish",
107
+ );
108
+ expect(new Path("/bar/baz/").resolve("foo/lish").path).toEqual(
109
+ "/bar/baz/foo/lish",
110
+ );
111
+ expect(() => new Path("bar/baz").resolve("foo/lish").path).toThrow(
112
+ /must be an absolute path/,
113
+ );
114
+ });
115
+
116
+ function mustNotBeNull<T>(t: T): Exclude<T, null> {
117
+ assert.notEqual(t, null);
118
+ // @ts-expect-error Cast
119
+ return t;
120
+ }
121
+
122
+ test.concurrent(".descendantRelativePath(…)", async () => {
123
+ expect(
124
+ mustNotBeNull(
125
+ new Path("/Users/lgarron/").descendantRelativePath(
126
+ "/Users/lgarron/Library",
127
+ ),
128
+ ).path,
129
+ ).toEqual("./Library");
130
+ expect(
131
+ mustNotBeNull(
132
+ new Path("/Users/lgarron/").descendantRelativePath(
133
+ "/Users/lgarron/Library/",
134
+ ),
135
+ ).path,
136
+ ).toEqual("./Library/");
137
+ expect(
138
+ () =>
139
+ mustNotBeNull(
140
+ new Path("/Users/lgarron").descendantRelativePath(
141
+ "/Users/lgarron/Library/",
142
+ ),
143
+ ).path,
144
+ ).toThrow("Ancestor must have a trailing slash.");
145
+ expect(
146
+ mustNotBeNull(
147
+ new Path("/Users/lgarron").descendantRelativePath(
148
+ "/Users/lgarron/Library/",
149
+ { requireTrailingSlashForAncestor: false },
150
+ ),
151
+ ).path,
152
+ ).toEqual("./Library/");
153
+ expect(
154
+ mustNotBeNull(
155
+ new Path("/Users/lgarron").descendantRelativePath(
156
+ "/Users/lgarron/Downloads/test.png",
157
+ { requireTrailingSlashForAncestor: false },
158
+ ),
159
+ ).path,
160
+ ).toEqual("./Downloads/test.png");
161
+ expect(
162
+ new Path("/Users/lgarron/").descendantRelativePath("/etc/hosts/"),
163
+ ).toBe(null);
164
+ expect(
165
+ new Path("/Users/lgarron/").descendantRelativePath("/Users/shared/"),
166
+ ).toBe(null);
167
+ expect(
168
+ mustNotBeNull(
169
+ new Path("/Users/lgarron/").descendantRelativePath(
170
+ "/Users/shared/../lgarron",
171
+ ),
172
+ ).path,
173
+ ).toBe(".");
174
+ });
175
+
98
176
  test.concurrent(".isAbsolutePath()", async () => {
99
177
  expect(new Path("/foo/bar").isAbsolutePath()).toBe(true);
100
178
  expect(new Path("foo/bar").isAbsolutePath()).toBe(false);
@@ -132,6 +210,7 @@ test.concurrent(".toggleTrailingSlash(…)", async () => {
132
210
  expect(new Path(".").toggleTrailingSlash().path).toBe("./");
133
211
  expect(new Path("../").toggleTrailingSlash().path).toBe("..");
134
212
  expect(new Path("..").toggleTrailingSlash().path).toBe("../");
213
+ expect(new Path("./foo").toggleTrailingSlash().path).toEqual("./foo/");
135
214
  });
136
215
 
137
216
  test.concurrent(".blue", async () => {
@@ -151,6 +230,7 @@ test.concurrent("normalize", async () => {
151
230
 
152
231
  test.concurrent(".join(…)", async () => {
153
232
  expect(new Path("foo").join("bar").path).toEqual("foo/bar");
233
+ expect(new Path("./foo").join("bar").path).toEqual("./foo/bar");
154
234
  expect(new Path("foo/bar").join("bath", "kitchen/sink").path).toEqual(
155
235
  "foo/bar/bath/kitchen/sink",
156
236
  );
@@ -325,47 +405,49 @@ test.concurrent(".extname", async () => {
325
405
  });
326
406
 
327
407
  test.concurrent(".existsAsFile()", async () => {
328
- const filePath = (await Path.makeTempDir()).join("file.txt");
329
- expect(await filePath.exists()).toBe(false);
330
- expect(await filePath.exists({ mustBe: "file" })).toBe(false);
331
- expect(await filePath.exists({ mustBe: "directory" })).toBe(false);
332
- expect(await filePath.existsAsFile()).toBe(false);
333
- expect(() => filePath.join("./").existsAsFile()).toThrow(
408
+ await using file = await Path.tempFilePath({ basename: "file.txt" });
409
+ expect(await file.exists()).toBe(false);
410
+ expect(await file.exists({ mustBe: "file" })).toBe(false);
411
+ expect(await file.exists({ mustBe: "directory" })).toBe(false);
412
+ expect(await file.existsAsFile()).toBe(false);
413
+ expect(() => file.join("./").existsAsFile()).toThrow(
334
414
  "Path ends with a slash, which cannot be treated as a file.",
335
415
  );
336
- await filePath.write("test");
337
- expect(await filePath.exists()).toBe(true);
338
- expect(await filePath.exists({ mustBe: "file" })).toBe(true);
339
- expect(() => filePath.exists({ mustBe: "directory" })).toThrow(
416
+ await file.write("test");
417
+ expect(await file.exists()).toBe(true);
418
+ expect(await file.exists({ mustBe: "file" })).toBe(true);
419
+ expect(() => file.exists({ mustBe: "directory" })).toThrow(
340
420
  /Path exists but is not a directory/,
341
421
  );
342
- expect(await filePath.existsAsFile()).toBe(true);
422
+ expect(await file.existsAsFile()).toBe(true);
343
423
  });
344
424
 
345
425
  test.concurrent(".existsAsDir()", async () => {
346
- const filePath = await Path.makeTempDir();
347
- expect(await filePath.exists()).toBe(true);
348
- expect(() => filePath.exists({ mustBe: "file" })).toThrow(
426
+ await using tempDir = await Path.makeTempDir();
427
+ expect(await tempDir.exists()).toBe(true);
428
+ expect(() => tempDir.exists({ mustBe: "file" })).toThrow(
349
429
  /Path exists but is not a file/,
350
430
  );
351
- expect(await filePath.exists({ mustBe: "directory" })).toBe(true);
352
- expect(await filePath.existsAsDir()).toBe(true);
353
- await filePath.rm_rf();
354
- expect(await filePath.exists()).toBe(false);
355
- expect(await filePath.exists({ mustBe: "file" })).toBe(false);
356
- expect(await filePath.exists({ mustBe: "directory" })).toBe(false);
357
- expect(await filePath.existsAsDir()).toBe(false);
431
+ expect(await tempDir.exists({ mustBe: "directory" })).toBe(true);
432
+ expect(await tempDir.existsAsDir()).toBe(true);
433
+ await tempDir.rm_rf();
434
+ expect(await tempDir.exists()).toBe(false);
435
+ expect(await tempDir.exists({ mustBe: "file" })).toBe(false);
436
+ expect(await tempDir.exists({ mustBe: "directory" })).toBe(false);
437
+ expect(await tempDir.existsAsDir()).toBe(false);
358
438
  });
359
439
 
360
440
  test.concurrent(".mkdir(…) (un-nested)", async () => {
361
- const dir = (await Path.makeTempDir()).join("mkdir-test");
441
+ await using tempDir = await Path.makeTempDir();
442
+ const dir = tempDir.join("mkdir-test");
362
443
  expect(await dir.exists()).toBe(false);
363
444
  await dir.mkdir();
364
445
  expect(await dir.exists()).toBe(true);
365
446
  });
366
447
 
367
448
  test.concurrent(".mkdir(…) (nested)", async () => {
368
- const dir = (await Path.makeTempDir()).join("mkdir-test/nested");
449
+ await using tempDir = await Path.makeTempDir();
450
+ const dir = tempDir.join("mkdir-test/nested");
369
451
  expect(await dir.exists()).toBe(false);
370
452
  expect(() => dir.mkdir({ recursive: false })).toThrow("no such file");
371
453
  await dir.mkdir();
@@ -373,7 +455,7 @@ test.concurrent(".mkdir(…) (nested)", async () => {
373
455
  });
374
456
 
375
457
  test.concurrent(".cp(…)", async () => {
376
- const parentDir = await Path.makeTempDir();
458
+ await using parentDir = await Path.makeTempDir();
377
459
  const file1 = parentDir.join("file1.txt");
378
460
  const file2 = parentDir.join("file2.txt");
379
461
  const file3 = parentDir.join("nonexistent/dirs/file3.txt");
@@ -398,7 +480,7 @@ test.concurrent(".cp(…)", async () => {
398
480
  });
399
481
 
400
482
  test.concurrent(".rename(…)", async () => {
401
- const parentDir = await Path.makeTempDir();
483
+ await using parentDir = await Path.makeTempDir();
402
484
  const file1 = parentDir.join("file1.txt");
403
485
  const file2 = parentDir.join("file2.txt");
404
486
  const file3 = parentDir.join("nonexistent/dirs/file3.txt");
@@ -444,41 +526,41 @@ test.concurrent(".makeTempDir(…)", async () => {
444
526
  });
445
527
 
446
528
  test.concurrent(".rm(…) (file)", async () => {
447
- const file = (await Path.makeTempDir()).join("file.txt");
448
- await file.write("");
449
- expect(await file.existsAsFile()).toBe(true);
450
- await file.rm();
451
- expect(await file.existsAsFile()).toBe(false);
452
- expect(await file.parent.existsAsDir()).toBe(true);
453
- expect(async () => file.rm()).toThrowError(/ENOENT/);
529
+ await using filePath = await Path.tempFilePath({ basename: "file.txt" });
530
+ await filePath.write("");
531
+ expect(await filePath.existsAsFile()).toBe(true);
532
+ await filePath.rm();
533
+ expect(await filePath.existsAsFile()).toBe(false);
534
+ expect(await filePath.parent.existsAsDir()).toBe(true);
535
+ expect(async () => filePath.rm()).toThrow(/^ENOENT/);
454
536
  });
455
537
 
456
538
  test.concurrent(".rm(…) (folder)", async () => {
457
- const tempDir = await Path.makeTempDir();
539
+ await using tempDir = await Path.makeTempDir();
458
540
  const file = tempDir.join("file.txt");
459
541
  await file.write("");
460
542
  expect(await tempDir.existsAsDir()).toBe(true);
461
- expect(async () => tempDir.rm()).toThrowError(/EACCES|EFAULT/);
543
+ expect(async () => tempDir.rm()).toThrow(/^(EACCES|EFAULT)/);
462
544
  await file.rm();
463
545
  await tempDir.rm({ recursive: true });
464
546
  expect(await tempDir.existsAsDir()).toBe(false);
465
- expect(async () => tempDir.rm()).toThrowError(/ENOENT/);
547
+ expect(async () => tempDir.rm()).toThrow(/^ENOENT/);
466
548
  });
467
549
 
468
550
  test.concurrent(".rmDir(…)", async () => {
469
- const tempDir = await Path.makeTempDir();
551
+ await using tempDir = await Path.makeTempDir();
470
552
  const file = tempDir.join("file.txt");
471
553
  await file.write("");
472
554
  expect(await tempDir.existsAsDir()).toBe(true);
473
- expect(async () => tempDir.rmDir()).toThrowError(/ENOTEMPTY/);
555
+ expect(async () => tempDir.rmDir()).toThrow(/^ENOTEMPTY/);
474
556
  await file.rm();
475
557
  await tempDir.rmDir();
476
558
  expect(await tempDir.existsAsDir()).toBe(false);
477
- expect(async () => tempDir.rmDir()).toThrowError(/ENOENT/);
559
+ expect(async () => tempDir.rmDir()).toThrow(/^ENOENT/);
478
560
  });
479
561
 
480
562
  test.concurrent(".rm_rf(…) (file)", async () => {
481
- const file = (await Path.makeTempDir()).join("file.txt");
563
+ await using file = await Path.tempFilePath({ basename: "file.txt" });
482
564
  await file.write("");
483
565
  expect(await file.existsAsFile()).toBe(true);
484
566
  await file.rm_rf();
@@ -489,7 +571,7 @@ test.concurrent(".rm_rf(…) (file)", async () => {
489
571
  });
490
572
 
491
573
  test.concurrent(".rm_rf(…) (folder)", async () => {
492
- const tempDir = await Path.makeTempDir();
574
+ await using tempDir = await Path.makeTempDir();
493
575
  await tempDir.join("file.txt").write("");
494
576
  expect(tempDir.path).toContain("/js-temp-");
495
577
  expect(await tempDir.exists()).toBe(true);
@@ -500,7 +582,7 @@ test.concurrent(".rm_rf(…) (folder)", async () => {
500
582
  });
501
583
 
502
584
  test.concurrent(".readText()", async () => {
503
- const file = (await Path.makeTempDir()).join("file.txt");
585
+ await using file = await Path.tempFilePath({ basename: "file.txt" });
504
586
  await file.write("hi");
505
587
  await file.write("bye");
506
588
 
@@ -509,14 +591,14 @@ test.concurrent(".readText()", async () => {
509
591
  });
510
592
 
511
593
  test.concurrent(".readLines()", async () => {
512
- const file = (await Path.makeTempDir()).join("file.txt");
594
+ await using file = await Path.tempFilePath({ basename: "file.txt" });
513
595
  await file.write("hi\nbye\n");
514
596
 
515
597
  expect(await Array.fromAsync(file.readLines())).toEqual(["hi", "bye"]);
516
598
  });
517
599
 
518
600
  test.concurrent(".readJSON()", async () => {
519
- const file = (await Path.makeTempDir()).join("file.json");
601
+ await using file = await Path.tempFilePath({ basename: "file.json" });
520
602
  await file.write(JSON.stringify({ foo: "bar" }));
521
603
 
522
604
  expect(await file.readJSON()).toEqual<Record<string, string>>({ foo: "bar" });
@@ -527,7 +609,7 @@ test.concurrent(".readJSON()", async () => {
527
609
  });
528
610
 
529
611
  test.concurrent(".readJSON(…) with fallback", async () => {
530
- const tempDir = await Path.makeTempDir();
612
+ await using tempDir = await Path.makeTempDir();
531
613
  const file = tempDir.join("file.json");
532
614
  const json: { foo?: number } = await file.readJSON({ fallback: { foo: 4 } });
533
615
  expect(json).toEqual({ foo: 4 });
@@ -539,13 +621,11 @@ test.concurrent(".readJSON(…) with fallback", async () => {
539
621
  });
540
622
  expect(json2).toEqual({ foo: 6 });
541
623
 
542
- expect(() => tempDir.readJSON({ fallback: { foo: 4 } })).toThrowError(
543
- /^EISDIR/,
544
- );
624
+ expect(() => tempDir.readJSON({ fallback: { foo: 4 } })).toThrow(/^EISDIR/);
545
625
  });
546
626
 
547
627
  test.concurrent(".write(…)", async () => {
548
- const tempDir = await Path.makeTempDir();
628
+ await using tempDir = await Path.makeTempDir();
549
629
  const file = tempDir.join("file.json");
550
630
  expect(await file.write("foo")).toBe(file);
551
631
 
@@ -561,14 +641,14 @@ test.concurrent(".write(…)", async () => {
561
641
  });
562
642
 
563
643
  test.concurrent(".writeJSON(…)", async () => {
564
- const file = (await Path.makeTempDir()).join("file.json");
644
+ await using file = await Path.tempFilePath({ basename: "file.json" });
565
645
  expect(await file.writeJSON({ foo: "bar" })).toBe(file);
566
646
 
567
647
  expect(await file.readJSON()).toEqual<Record<string, string>>({ foo: "bar" });
568
648
  });
569
649
 
570
650
  test.concurrent(".appendFile(…)", async () => {
571
- const file = (await Path.makeTempDir()).join("file.txt");
651
+ await using file = await Path.tempFilePath({ basename: "file.txt" });
572
652
  await file.appendFile("test\n");
573
653
  expect(await file.readText()).toEqual("test\n");
574
654
  await file.appendFile("more\n");
@@ -576,7 +656,7 @@ test.concurrent(".appendFile(…)", async () => {
576
656
  });
577
657
 
578
658
  test.concurrent(".readDir(…)", async () => {
579
- const dir = await Path.makeTempDir();
659
+ await using dir = await Path.makeTempDir();
580
660
  await dir.join("file.txt").write("hello");
581
661
  await dir.join("dir/file.json").write("hello");
582
662
 
@@ -590,19 +670,19 @@ test.concurrent(".readDir(…)", async () => {
590
670
  });
591
671
 
592
672
  test.concurrent(".symlink(…)", async () => {
593
- const tempDir = await Path.makeTempDir();
673
+ await using tempDir = await Path.makeTempDir();
594
674
  const source = tempDir.join("foo.txt");
595
675
  const target = tempDir.join("bar.txt");
596
676
  await source.symlink(target);
597
677
  expect(await target.existsAsFile()).toBe(false);
598
- expect(() => target.readText()).toThrow(/ENOENT/);
678
+ expect(() => target.readText()).toThrow(/^ENOENT/);
599
679
  await source.write("hello");
600
680
  expect(await target.existsAsFile()).toBe(true);
601
681
  expect(await target.readText()).toEqual("hello");
602
682
  });
603
683
 
604
684
  test.concurrent(".realpath(…)", async () => {
605
- const tempDir = await Path.makeTempDir();
685
+ await using tempDir = await Path.makeTempDir();
606
686
  const source = tempDir.join("foo.txt");
607
687
  await source.write("hello world!");
608
688
  const target = tempDir.join("bar.txt");
@@ -613,7 +693,7 @@ test.concurrent(".realpath(…)", async () => {
613
693
  });
614
694
 
615
695
  test.concurrent(".stat(…)", async () => {
616
- const file = (await Path.makeTempDir()).join("foo.txt");
696
+ await using file = await Path.tempFilePath({ basename: "foo.txt" });
617
697
  await file.write("hello");
618
698
 
619
699
  expect((await file.stat()).size).toEqual(5);
@@ -622,7 +702,7 @@ test.concurrent(".stat(…)", async () => {
622
702
  });
623
703
 
624
704
  test.concurrent(".lstat(…)", async () => {
625
- const tempDir = await Path.makeTempDir();
705
+ await using tempDir = await Path.makeTempDir();
626
706
  const source = tempDir.join("foo.txt");
627
707
  const target = tempDir.join("bar.txt");
628
708
  await source.symlink(target);
@@ -635,45 +715,37 @@ test.concurrent(".lstat(…)", async () => {
635
715
  });
636
716
 
637
717
  test.concurrent(".chmod(…)", async () => {
638
- const binPath = (await Path.makeTempDir()).join("nonexistent.bin");
639
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
640
- /ENOENT|Premature close/,
718
+ await using binPath = await Path.tempFilePath({
719
+ basename: "bin.bash",
720
+ });
721
+ expect(() => execSync(binPath.path)).toThrow(
722
+ /No such file or directory|not found/,
641
723
  );
642
- await binPath.write(`#!/usr/bin/env bash
724
+ await binPath.write(`#!/usr/bin/env -S bun run --
643
725
 
644
- echo hi`);
645
- // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
646
- // await binPath.write(`#!/usr/bin/env -S bun run --
647
-
648
- // console.log("hi");`);
649
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
650
- /EACCES|Premature close/,
651
- );
726
+ console.log("hi");`);
727
+ expect(() => execSync(binPath.path)).toThrow(/Permission denied/);
652
728
  await binPath.chmod(0o755);
653
- expect(await new PrintableShellCommand(binPath, []).text()).toEqual("hi\n");
729
+ expect(execSync(binPath.path, { encoding: "utf-8" })).toEqual("hi\n");
654
730
  });
655
731
 
656
732
  test.concurrent(".chmodX(…)", async () => {
657
- const binPath = (await Path.makeTempDir()).join("nonexistent.bin");
658
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
659
- /ENOENT|Premature close/,
733
+ await using binPath = await Path.tempFilePath({
734
+ basename: "bin.bash",
735
+ });
736
+ expect(() => execSync(binPath.path)).toThrow(
737
+ /No such file or directory|not found/,
660
738
  );
661
- await binPath.write(`#!/usr/bin/env bash
739
+ await binPath.write(`#!/usr/bin/env -S bun run --
662
740
 
663
- echo hi`);
664
- // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
665
- // await binPath.write(`#!/usr/bin/env -S bun run --
666
-
667
- // console.log("hi");`);
668
- expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
669
- /EACCES|Premature close/,
670
- );
741
+ console.log("hi");`);
742
+ expect(() => execSync(binPath.path)).toThrow(/Permission denied/);
671
743
  expect((await binPath.stat()).mode & constants.S_IWUSR).toBeTruthy();
672
744
  await binPath.chmod(0o444);
673
745
  expect((await binPath.stat()).mode & constants.S_IWUSR).toBeFalsy();
674
746
  expect((await binPath.stat()).mode & constants.S_IXUSR).toBeFalsy();
675
747
  await binPath.chmodX();
676
- expect(await new PrintableShellCommand(binPath, []).text()).toEqual("hi\n");
748
+ expect(execSync(binPath.path, { encoding: "utf-8" })).toEqual("hi\n");
677
749
  expect((await binPath.stat()).mode & constants.S_IWUSR).toBeFalsy();
678
750
  expect((await binPath.stat()).mode & constants.S_IXUSR).toBeTruthy();
679
751
  });
@@ -682,11 +754,14 @@ test.concurrent(".homedir", async () => {
682
754
  expect(Path.homedir.path).toEqual("/mock/home/dir");
683
755
  });
684
756
 
685
- test.concurrent(".cwd", async () => {
757
+ // This is serial because it can break binary execution tests elsewhere.
758
+ test.serial(".cwd", async () => {
759
+ const originalCwd = Path.cwd;
686
760
  expect(Path.cwd.basename.path).toEqual("path-class");
687
- const tempDir = await Path.makeTempDir();
761
+ await using tempDir = await Path.makeTempDir();
688
762
  chdir(tempDir.path);
689
763
  expect(await realpath(Path.cwd.path)).toEqual(await realpath(tempDir.path));
764
+ chdir(originalCwd.path);
690
765
  });
691
766
 
692
767
  test.concurrent(".xdg", async () => {
@@ -700,20 +775,12 @@ test.concurrent(".xdg", async () => {
700
775
  );
701
776
  });
702
777
 
703
- const spy = spyOn(console, "log");
704
-
705
- test(".debugPrint(…)", async () => {
706
- spy.mockReset();
778
+ test.concurrent(".debugPrint(…)", async () => {
779
+ const spy = spyOn(console, "log");
707
780
  Path.homedir.debugPrint("Here is a test log of the mock home directory:");
708
781
  expect(spy.mock.calls).toEqual([
709
782
  ["Here is a test log of the mock home directory:"],
710
783
  ["/mock/home/dir"],
711
784
  ]);
712
- });
713
-
714
- test(".stringifyIfPath(…)", async () => {
715
- spy.mockReset();
716
- expect(stringifyIfPath(Path.homedir)).toBe("/mock/home/dir");
717
- expect(stringifyIfPath("/mock/home/dir")).toBe("/mock/home/dir");
718
- expect(stringifyIfPath(4)).toBe(4);
785
+ jest.restoreAllMocks();
719
786
  });