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.
- package/dist/lib/path-class/Path.d.ts +7 -1
- package/dist/lib/path-class/chunks/{chunk-PF7ITQCP.js → chunk-HPGQLKA5.js} +17 -1
- package/dist/lib/path-class/chunks/chunk-HPGQLKA5.js.map +7 -0
- package/dist/lib/path-class/index.js +1 -1
- package/dist/lib/path-class/sync/PathSync.d.ts +46 -0
- package/dist/lib/path-class/sync/index.d.ts +1 -32
- package/dist/lib/path-class/sync/index.js +180 -116
- package/dist/lib/path-class/sync/index.js.map +3 -3
- package/dist/lib/path-class/sync/modifiedNodeTypes.d.ts +7 -1
- package/package.json +6 -7
- package/src/Path.test.ts +39 -0
- package/src/Path.ts +23 -1
- package/src/sync/{sync.test.ts → PathSync.test.ts} +68 -28
- package/src/sync/PathSync.ts +271 -0
- package/src/sync/index.ts +1 -253
- package/src/sync/modifiedNodeTypes.ts +11 -0
- package/dist/lib/path-class/chunks/chunk-PF7ITQCP.js.map +0 -7
- package/dist/lib/path-class/sync/static.d.ts +0 -6
- package/src/sync/static.ts +0 -14
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../../src/sync/
|
|
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 {
|
|
5
|
-
"mappings": ";;;;;;AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK
|
|
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 {
|
|
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.
|
|
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": "
|
|
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": "^
|
|
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
|
|
5
|
-
import "
|
|
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 =
|
|
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
|
-
/
|
|
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 =
|
|
28
|
+
const filePath = PathSync.makeTempDirSync();
|
|
27
29
|
expect(filePath.existsSync()).toBe(true);
|
|
28
30
|
expect(() => filePath.existsSync({ mustBe: "file" })).toThrow(
|
|
29
|
-
/
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
+
});
|