path-class 0.10.13 → 0.11.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
- "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 rmdirSync,\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 rmDirSync(options?: Parameters<typeof rmdirSync>[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 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(`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.rmDirSync = function (): void {\n rmdirSync(this.path);\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": "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",
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 ): PathSync {\n cpSync(this.path, new Path(destination).path, options);\n return new PathSync(destination);\n }\n\n renameSync(destination: string | URL | Path): void {\n renameSync(this.path, new Path(destination).path);\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 |\n constants.S_IRWXU |\n constants.S_IXUSR |\n constants.S_IXGRP |\n constants.S_IXOTH,\n );\n return this;\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,4 +1,5 @@
1
- import type { BigIntStats, Dirent, ObjectEncodingOptions, StatSyncOptions, Stats } from "node:fs";
1
+ import type { Abortable } from "node:events";
2
+ import type { BigIntStats, Dirent, Mode, ObjectEncodingOptions, StatSyncOptions, Stats } from "node:fs";
2
3
  export declare function readFileSyncType(options?: {
3
4
  encoding?: null | undefined;
4
5
  flag?: string | undefined;
@@ -10,6 +11,11 @@ export declare function readFileSyncType(options: {
10
11
  export declare function readFileSyncType(options?: (ObjectEncodingOptions & {
11
12
  flag?: string | undefined;
12
13
  }) | BufferEncoding | null): string | NonSharedBuffer;
14
+ export type WriteFileOptions = (ObjectEncodingOptions & Abortable & {
15
+ mode?: Mode | undefined;
16
+ flag?: string | undefined;
17
+ flush?: boolean | undefined;
18
+ }) | BufferEncoding | null;
13
19
  export declare function readDirSyncType(options?: {
14
20
  encoding: BufferEncoding | null;
15
21
  withFileTypes?: false | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "path-class",
3
- "version": "0.10.13",
3
+ "version": "0.11.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",
@@ -22,20 +22,19 @@
22
22
  }
23
23
  },
24
24
  "dependencies": {
25
- "xdg-basedir": "^5.1.0"
25
+ "xdg-basedir": "^5.1.0",
26
+ "@types/node": ">=24.7.1"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@biomejs/biome": "^2.2.5",
29
- "@cubing/dev-config": "^0.3.6",
30
+ "@cubing/dev-config": ">=0.8.1",
30
31
  "@types/bun": "^1.3.0",
32
+ "bun-dx": ">=0.1.3",
31
33
  "esbuild": "^0.25.10",
32
- "printable-shell-command": "^2.6.3",
34
+ "printable-shell-command": "^4.0.4",
33
35
  "readme-cli-help": ">=0.4.9",
34
36
  "typescript": "^5.9.3"
35
37
  },
36
- "peerDependencies": {
37
- "@types/node": ">=24.7.1"
38
- },
39
38
  "files": [
40
39
  "./dist/",
41
40
  "./src"
package/src/Path.test.ts CHANGED
@@ -2,6 +2,7 @@ import { expect, spyOn, test } from "bun:test";
2
2
  import { readFile, realpath } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import { chdir } from "node:process";
5
+ import { PrintableShellCommand } from "printable-shell-command";
5
6
  import { Path, ResolutionPrefix, stringifyIfPath } from "./Path";
6
7
 
7
8
  test("constructor", async () => {
@@ -597,6 +598,44 @@ test(".lstat(…)", async () => {
597
598
  expect(await target.readText()).toEqual("hello");
598
599
  });
599
600
 
601
+ test(".chmod(…)", async () => {
602
+ const binPath = (await Path.makeTempDir()).join("nonexistent.bin");
603
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
604
+ /ENOENT|Premature close/,
605
+ );
606
+ await binPath.write(`#!/usr/bin/env bash
607
+
608
+ echo hi`);
609
+ // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
610
+ // await binPath.write(`#!/usr/bin/env -S bun run --
611
+
612
+ // console.log("hi");`);
613
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
614
+ /EACCES|Premature close/,
615
+ );
616
+ await binPath.chmod(0o755);
617
+ expect(await new PrintableShellCommand(binPath, []).text()).toEqual("hi\n");
618
+ });
619
+
620
+ test(".chmodX(…)", async () => {
621
+ const binPath = (await Path.makeTempDir()).join("nonexistent.bin");
622
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
623
+ /ENOENT|Premature close/,
624
+ );
625
+ await binPath.write(`#!/usr/bin/env bash
626
+
627
+ echo hi`);
628
+ // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
629
+ // await binPath.write(`#!/usr/bin/env -S bun run --
630
+
631
+ // console.log("hi");`);
632
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
633
+ /EACCES|Premature close/,
634
+ );
635
+ await binPath.chmodX();
636
+ expect(await new PrintableShellCommand(binPath, []).text()).toEqual("hi\n");
637
+ });
638
+
600
639
  test(".homedir", async () => {
601
640
  expect(Path.homedir.path).toEqual("/mock/home/dir");
602
641
  });
package/src/Path.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  appendFile,
3
+ chmod,
4
+ constants,
3
5
  cp,
4
6
  lstat,
5
7
  mkdir,
@@ -57,7 +59,7 @@ export enum ResolutionPrefix {
57
59
  Bare = "bare",
58
60
  }
59
61
 
60
- function resolutionPrefix(pathString: string): ResolutionPrefix {
62
+ export function resolutionPrefix(pathString: string): ResolutionPrefix {
61
63
  if (pathString.startsWith("/")) {
62
64
  return ResolutionPrefix.Absolute;
63
65
  } else if (pathString.startsWith("./")) {
@@ -607,6 +609,26 @@ export class Path {
607
609
  // biome-ignore lint/suspicious/noExplicitAny: Needed to wrangle the types.
608
610
  lstat(this.#path, ...options) as any;
609
611
 
612
+ async chmod(mode: Parameters<typeof chmod>[1]): Promise<Path> {
613
+ await chmod(this.#path, mode);
614
+ return this;
615
+ }
616
+
617
+ /**
618
+ * Add the executable bit (for everyone) to the given path without modifying other bits (`chmod +x`).
619
+ */
620
+ async chmodX(): Promise<Path> {
621
+ const { mode } = await this.stat();
622
+ await this.chmod(
623
+ mode |
624
+ constants.S_IRWXU |
625
+ constants.S_IXUSR |
626
+ constants.S_IXGRP |
627
+ constants.S_IXOTH,
628
+ );
629
+ return this;
630
+ }
631
+
610
632
  static get homedir(): Path {
611
633
  return new Path(homedir());
612
634
  }
@@ -1,11 +1,13 @@
1
1
  import { expect, test } from "bun:test";
2
2
  import { readFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
- import { Path } from "../Path";
5
- import "./index";
4
+ import "./PathSync";
5
+ import { PrintableShellCommand } from "printable-shell-command";
6
+
7
+ import { PathSync } from "./PathSync";
6
8
 
7
9
  test(".existsAsFileSync()", () => {
8
- const filePath = Path.makeTempDirSync().join("file.txt");
10
+ const filePath = PathSync.makeTempDirSync().join("file.txt");
9
11
  expect(filePath.existsSync()).toBe(false);
10
12
  expect(filePath.existsSync({ mustBe: "file" })).toBe(false);
11
13
  expect(filePath.existsSync({ mustBe: "directory" })).toBe(false);
@@ -17,16 +19,16 @@ test(".existsAsFileSync()", () => {
17
19
  expect(filePath.existsSync()).toBe(true);
18
20
  expect(filePath.existsSync({ mustBe: "file" })).toBe(true);
19
21
  expect(() => filePath.existsSync({ mustBe: "directory" })).toThrow(
20
- /Path exists but is not a directory/,
22
+ /PathSync exists but is not a directory/,
21
23
  );
22
24
  expect(filePath.existsAsFileSync()).toBe(true);
23
25
  });
24
26
 
25
27
  test(".existsAsDir()", () => {
26
- const filePath = Path.makeTempDirSync();
28
+ const filePath = PathSync.makeTempDirSync();
27
29
  expect(filePath.existsSync()).toBe(true);
28
30
  expect(() => filePath.existsSync({ mustBe: "file" })).toThrow(
29
- /Path exists but is not a file/,
31
+ /PathSync exists but is not a file/,
30
32
  );
31
33
  expect(filePath.existsSync({ mustBe: "directory" })).toBe(true);
32
34
  expect(filePath.existsAsDirSync()).toBe(true);
@@ -38,14 +40,14 @@ test(".existsAsDir()", () => {
38
40
  });
39
41
 
40
42
  test(".mkdirSync(…) (un-nested)", () => {
41
- const dir = Path.makeTempDirSync().join("mkdir-test");
43
+ const dir = PathSync.makeTempDirSync().join("mkdir-test");
42
44
  expect(dir.existsSync()).toBe(false);
43
45
  dir.mkdirSync();
44
46
  expect(dir.existsSync()).toBe(true);
45
47
  });
46
48
 
47
49
  test(".mkdirSync(…) (nested)", () => {
48
- const dir = Path.makeTempDirSync().join("mkdir-test/nested");
50
+ const dir = PathSync.makeTempDirSync().join("mkdir-test/nested");
49
51
  expect(dir.existsSync()).toBe(false);
50
52
  expect(() => dir.mkdirSync({ recursive: false })).toThrow("no such file");
51
53
  dir.mkdirSync();
@@ -53,7 +55,7 @@ test(".mkdirSync(…) (nested)", () => {
53
55
  });
54
56
 
55
57
  test(".cpSync(…)", () => {
56
- const parentDir = Path.makeTempDirSync();
58
+ const parentDir = PathSync.makeTempDirSync();
57
59
  const file1 = parentDir.join("file1.txt");
58
60
  const file2 = parentDir.join("file2.txt");
59
61
 
@@ -67,7 +69,7 @@ test(".cpSync(…)", () => {
67
69
  });
68
70
 
69
71
  test(".renameSync(…)", () => {
70
- const parentDir = Path.makeTempDirSync();
72
+ const parentDir = PathSync.makeTempDirSync();
71
73
  const file1 = parentDir.join("file1.txt");
72
74
  const file2 = parentDir.join("file2.txt");
73
75
 
@@ -81,18 +83,18 @@ test(".renameSync(…)", () => {
81
83
  });
82
84
 
83
85
  test(".makeTempDirSync(…)", () => {
84
- const tempDir = Path.makeTempDirSync();
86
+ const tempDir = PathSync.makeTempDirSync();
85
87
  expect(tempDir.path).toContain("/js-temp-");
86
88
  expect(tempDir.basename.path).toStartWith("js-temp-");
87
89
  expect(tempDir.existsAsDirSync()).toBe(true);
88
90
 
89
- const tempDir2 = Path.makeTempDirSync("foo");
91
+ const tempDir2 = PathSync.makeTempDirSync("foo");
90
92
  expect(tempDir2.path).not.toContain("/js-temp-");
91
93
  expect(tempDir2.basename.path).toStartWith("foo");
92
94
  });
93
95
 
94
96
  test(".rmSync(…) (file)", () => {
95
- const file = Path.makeTempDirSync().join("file.txt");
97
+ const file = PathSync.makeTempDirSync().join("file.txt");
96
98
  file.writeSync("");
97
99
  expect(file.existsAsFileSync()).toBe(true);
98
100
  file.rmSync();
@@ -102,7 +104,7 @@ test(".rmSync(…) (file)", () => {
102
104
  });
103
105
 
104
106
  test(".rmSync(…) (folder)", () => {
105
- const tempDir = Path.makeTempDirSync();
107
+ const tempDir = PathSync.makeTempDirSync();
106
108
  const file = tempDir.join("file.txt");
107
109
  file.writeSync("");
108
110
  expect(tempDir.existsAsDirSync()).toBe(true);
@@ -114,7 +116,7 @@ test(".rmSync(…) (folder)", () => {
114
116
  });
115
117
 
116
118
  test(".rmDirSync(…) (folder)", () => {
117
- const tempDir = Path.makeTempDirSync();
119
+ const tempDir = PathSync.makeTempDirSync();
118
120
  const file = tempDir.join("file.txt");
119
121
  file.writeSync("");
120
122
  expect(tempDir.existsAsDirSync()).toBe(true);
@@ -126,7 +128,7 @@ test(".rmDirSync(…) (folder)", () => {
126
128
  });
127
129
 
128
130
  test(".rm_rfSync(…) (file)", () => {
129
- const file = Path.makeTempDirSync().join("file.txt");
131
+ const file = PathSync.makeTempDirSync().join("file.txt");
130
132
  file.writeSync("");
131
133
  expect(file.existsAsFileSync()).toBe(true);
132
134
  file.rm_rfSync();
@@ -137,7 +139,7 @@ test(".rm_rfSync(…) (file)", () => {
137
139
  });
138
140
 
139
141
  test(".rm_rfSync(…) (folder)", () => {
140
- const tempDir = Path.makeTempDirSync();
142
+ const tempDir = PathSync.makeTempDirSync();
141
143
  tempDir.join("file.txt").writeSync("");
142
144
  expect(tempDir.path).toContain("/js-temp-");
143
145
  expect(tempDir.existsSync()).toBe(true);
@@ -148,7 +150,7 @@ test(".rm_rfSync(…) (folder)", () => {
148
150
  });
149
151
 
150
152
  test(".readTextSync()", () => {
151
- const file = Path.makeTempDirSync().join("file.txt");
153
+ const file = PathSync.makeTempDirSync().join("file.txt");
152
154
  file.writeSync("hi");
153
155
  file.writeSync("bye");
154
156
 
@@ -157,7 +159,7 @@ test(".readTextSync()", () => {
157
159
  });
158
160
 
159
161
  test(".readJSONSync()", () => {
160
- const file = Path.makeTempDirSync().join("file.json");
162
+ const file = PathSync.makeTempDirSync().join("file.json");
161
163
  file.writeSync(JSON.stringify({ foo: "bar" }));
162
164
 
163
165
  expect(file.readJSONSync()).toEqual<Record<string, string>>({ foo: "bar" });
@@ -168,7 +170,7 @@ test(".readJSONSync()", () => {
168
170
  });
169
171
 
170
172
  test(".readJSONSync(…) with fallback", () => {
171
- const tempDir = Path.makeTempDirSync();
173
+ const tempDir = PathSync.makeTempDirSync();
172
174
  const file = tempDir.join("file.json");
173
175
  const json: { foo?: number } = file.readJSONSync({ fallback: { foo: 4 } });
174
176
  expect(json).toEqual({ foo: 4 });
@@ -186,7 +188,7 @@ test(".readJSONSync(…) with fallback", () => {
186
188
  });
187
189
 
188
190
  test(".writeSync(…)", () => {
189
- const tempDir = Path.makeTempDirSync();
191
+ const tempDir = PathSync.makeTempDirSync();
190
192
  const file = tempDir.join("file.json");
191
193
  expect(file.writeSync("foo")).toBe(file);
192
194
 
@@ -202,14 +204,14 @@ test(".writeSync(…)", () => {
202
204
  });
203
205
 
204
206
  test(".writeJSONSync(…)", () => {
205
- const file = Path.makeTempDirSync().join("file.json");
207
+ const file = PathSync.makeTempDirSync().join("file.json");
206
208
  expect(file.writeJSONSync({ foo: "bar" })).toBe(file);
207
209
 
208
210
  expect(file.readJSONSync()).toEqual<Record<string, string>>({ foo: "bar" });
209
211
  });
210
212
 
211
213
  test(".appendFileSync(…)", () => {
212
- const file = Path.makeTempDirSync().join("file.txt");
214
+ const file = PathSync.makeTempDirSync().join("file.txt");
213
215
  file.appendFileSync("test\n");
214
216
  expect(file.readTextSync()).toEqual("test\n");
215
217
  file.appendFileSync("more\n");
@@ -217,7 +219,7 @@ test(".appendFileSync(…)", () => {
217
219
  });
218
220
 
219
221
  test(".readDirSync(…)", () => {
220
- const dir = Path.makeTempDirSync();
222
+ const dir = PathSync.makeTempDirSync();
221
223
  dir.join("file.txt").writeSync("hello");
222
224
  dir.join("dir/file.json").writeSync("hello");
223
225
 
@@ -231,7 +233,7 @@ test(".readDirSync(…)", () => {
231
233
  });
232
234
 
233
235
  test(".symlinkSync(…)", () => {
234
- const tempDir = Path.makeTempDirSync();
236
+ const tempDir = PathSync.makeTempDirSync();
235
237
  const source = tempDir.join("foo.txt");
236
238
  const target = tempDir.join("bar.txt");
237
239
  source.symlinkSync(target);
@@ -243,7 +245,7 @@ test(".symlinkSync(…)", () => {
243
245
  });
244
246
 
245
247
  test(".realpathSync(…)", () => {
246
- const tempDir = Path.makeTempDirSync();
248
+ const tempDir = PathSync.makeTempDirSync();
247
249
  const source = tempDir.join("foo.txt");
248
250
  source.writeSync("hello world!");
249
251
  const target = tempDir.join("bar.txt");
@@ -252,7 +254,7 @@ test(".realpathSync(…)", () => {
252
254
  });
253
255
 
254
256
  test(".statSync(…)", () => {
255
- const file = Path.makeTempDirSync().join("foo.txt");
257
+ const file = PathSync.makeTempDirSync().join("foo.txt");
256
258
  file.writeSync("hello");
257
259
 
258
260
  expect(file.statSync()?.size).toEqual(5);
@@ -261,7 +263,7 @@ test(".statSync(…)", () => {
261
263
  });
262
264
 
263
265
  test(".lstatSync(…)", () => {
264
- const tempDir = Path.makeTempDirSync();
266
+ const tempDir = PathSync.makeTempDirSync();
265
267
  const source = tempDir.join("foo.txt");
266
268
  const target = tempDir.join("bar.txt");
267
269
  source.symlinkSync(target);
@@ -272,3 +274,41 @@ test(".lstatSync(…)", () => {
272
274
 
273
275
  expect(target.readTextSync()).toEqual("hello");
274
276
  });
277
+
278
+ test(".chmodSync(…)", () => {
279
+ const binPath = PathSync.makeTempDirSync().join("nonexistent.bin");
280
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
281
+ /ENOENT|Premature close/,
282
+ );
283
+ binPath.writeSync(`#!/usr/bin/env bash
284
+
285
+ echo hi`);
286
+ // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
287
+ // binPath.writeSync(`#!/usr/bin/env -S bun run --
288
+
289
+ // console.log("hi");`);
290
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
291
+ /EACCES|Premature close/,
292
+ );
293
+ binPath.chmodSync(0o755);
294
+ expect(() => new PrintableShellCommand(binPath, []).text()).not.toThrow();
295
+ });
296
+
297
+ test(".chmodXSync(…)", () => {
298
+ const binPath = PathSync.makeTempDirSync().join("nonexistent.bin");
299
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
300
+ /ENOENT|Premature close/,
301
+ );
302
+ binPath.writeSync(`#!/usr/bin/env bash
303
+
304
+ echo hi`);
305
+ // TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
306
+ // binPath.writeSync(`#!/usr/bin/env -S bun run --
307
+
308
+ // console.log("hi");`);
309
+ expect(() => new PrintableShellCommand(binPath, []).text()).toThrow(
310
+ /EACCES|Premature close/,
311
+ );
312
+ binPath.chmodXSync();
313
+ expect(() => new PrintableShellCommand(binPath, []).text()).not.toThrow();
314
+ });