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.
- package/dist/lib/path-class/Path.d.ts +66 -10
- package/dist/lib/path-class/chunks/{chunk-7GEFMWZY.js → chunk-IZ3HOMWW.js} +144 -32
- package/dist/lib/path-class/chunks/chunk-IZ3HOMWW.js.map +7 -0
- package/dist/lib/path-class/index.d.ts +2 -1
- package/dist/lib/path-class/index.js +1 -1
- package/dist/lib/path-class/stringifyfIfPath.d.ts +11 -0
- package/dist/lib/path-class/sync/PathSync.d.ts +35 -1
- package/dist/lib/path-class/sync/index.js +59 -3
- package/dist/lib/path-class/sync/index.js.map +2 -2
- package/package.json +2 -1
- package/src/Path.test.ts +165 -98
- package/src/Path.ts +172 -43
- package/src/index.ts +2 -1
- package/src/stringifyIfPath.test.ts +9 -0
- package/src/stringifyfIfPath.ts +17 -0
- package/src/sync/PathSync.test.ts +111 -64
- package/src/sync/PathSync.ts +73 -3
- package/src/test.preload.ts +41 -2
- package/dist/lib/path-class/chunks/chunk-7GEFMWZY.js.map +0 -7
package/src/Path.ts
CHANGED
|
@@ -36,6 +36,7 @@ import type {
|
|
|
36
36
|
readFileType,
|
|
37
37
|
statType,
|
|
38
38
|
} from "./modifiedNodeTypes";
|
|
39
|
+
import { stringifyIfPath } from "./stringifyfIfPath";
|
|
39
40
|
|
|
40
41
|
// Note that (non-static) functions in this file are defined using `function(…)
|
|
41
42
|
// { … }` rather than arrow functions, specifically because we want `this` to
|
|
@@ -76,6 +77,30 @@ export function resolutionPrefix(pathString: string): ResolutionPrefix {
|
|
|
76
77
|
return ResolutionPrefix.Bare;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
const DEFAULT_TEMP_PREFIX = "js-temp-";
|
|
81
|
+
const DEFAULT_TEMP_FILE_NAME = "file";
|
|
82
|
+
|
|
83
|
+
function preserveRelativeResolutionPrefix(
|
|
84
|
+
pathString: string,
|
|
85
|
+
from: string,
|
|
86
|
+
): string {
|
|
87
|
+
if (
|
|
88
|
+
resolutionPrefix(from) === ResolutionPrefix.Relative &&
|
|
89
|
+
resolutionPrefix(pathString) !== ResolutionPrefix.Relative
|
|
90
|
+
) {
|
|
91
|
+
// We don't have to handle the case of `"."`, as it already starts with `"."`
|
|
92
|
+
return `./${pathString}`;
|
|
93
|
+
}
|
|
94
|
+
return pathString;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function joinPreservingRelativeResolutionPrefix(
|
|
98
|
+
base: string,
|
|
99
|
+
relative: string[],
|
|
100
|
+
): string {
|
|
101
|
+
return preserveRelativeResolutionPrefix(join(base, ...relative), base);
|
|
102
|
+
}
|
|
103
|
+
|
|
79
104
|
export class Path {
|
|
80
105
|
// @ts-expect-error ts(2564): False positive. https://github.com/microsoft/TypeScript/issues/32194
|
|
81
106
|
#path: string;
|
|
@@ -87,6 +112,27 @@ export class Path {
|
|
|
87
112
|
this.#setNormalizedPath(s);
|
|
88
113
|
}
|
|
89
114
|
|
|
115
|
+
static #pathlikeToString(path: string | URL | Path): string {
|
|
116
|
+
if (path instanceof Path) {
|
|
117
|
+
return path.#path;
|
|
118
|
+
}
|
|
119
|
+
if (path instanceof URL) {
|
|
120
|
+
return fileURLToPath(path);
|
|
121
|
+
}
|
|
122
|
+
if (typeof path === "string") {
|
|
123
|
+
// TODO: allow turning off this heuristic?
|
|
124
|
+
if (path.startsWith("file:///")) {
|
|
125
|
+
return fileURLToPath(path);
|
|
126
|
+
}
|
|
127
|
+
return path;
|
|
128
|
+
}
|
|
129
|
+
throw new Error("Invalid path");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Preserves the `ResolutionPrefix` status when possible.
|
|
133
|
+
#setNormalizedPath(path: string): void {
|
|
134
|
+
this.#path = joinPreservingRelativeResolutionPrefix(path, []);
|
|
135
|
+
}
|
|
90
136
|
static fromString(s: string): Path {
|
|
91
137
|
if (typeof s !== "string") {
|
|
92
138
|
throw new Error(
|
|
@@ -126,31 +172,80 @@ export class Path {
|
|
|
126
172
|
return new Path(new URL(Path.#pathlikeToString(path), baseURL));
|
|
127
173
|
}
|
|
128
174
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Convenience function. The following are equivalent:
|
|
177
|
+
*
|
|
178
|
+
* B.resolve(A);
|
|
179
|
+
* Path.resolve(A, B);
|
|
180
|
+
*
|
|
181
|
+
*/
|
|
182
|
+
resolve(path: string | URL | Path): Path {
|
|
183
|
+
return Path.resolve(path, this);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Computes the relative path from an ancestor (this) to another path.
|
|
188
|
+
*
|
|
189
|
+
* - If the path is a descendant *or is the same path*, this returns a
|
|
190
|
+
* relative path. Call {@link Path.asBare `.asBare()`} on the output if
|
|
191
|
+
* needed.
|
|
192
|
+
* - The output is `null` unless the input paths are:
|
|
193
|
+
* - Both absolute paths.
|
|
194
|
+
* - Both relative paths. Resolve the paths before passing to this function
|
|
195
|
+
* if needed.
|
|
196
|
+
* - Trailing slashes:
|
|
197
|
+
* - By default, the ancestor path must have a trailing slash for the
|
|
198
|
+
* function to be called. Pass `{ requireTrailingSlashForAncestor: false
|
|
199
|
+
* }` if needed. Note that recombining the ancestor path with the output
|
|
200
|
+
* using {@link Path.prototype.resolve `.resolve(…)` } does not result in
|
|
201
|
+
* the original input path if the ancestor path did not have a trailing
|
|
202
|
+
* slash.
|
|
203
|
+
* - For the descendant/same path check, trailing slashes are ignored. In
|
|
204
|
+
* particular, if the ancestor path has a trailing slash and the
|
|
205
|
+
* descendant path is the same path without a trailing slash, this is
|
|
206
|
+
* still considered to be the same path.
|
|
207
|
+
* - The output has trailing slash if and only if:
|
|
208
|
+
* - the input descendant does, or
|
|
209
|
+
* - the output is the absolute path `/`.
|
|
210
|
+
*/
|
|
211
|
+
descendantRelativePath(
|
|
212
|
+
potentialDescendant: string | URL | Path,
|
|
213
|
+
options?: { requireTrailingSlashForAncestor: boolean },
|
|
214
|
+
): Path | null {
|
|
215
|
+
const requireTrailingSlashForAncestor =
|
|
216
|
+
options?.requireTrailingSlashForAncestor ?? true;
|
|
217
|
+
if (requireTrailingSlashForAncestor && !this.hasTrailingSlash()) {
|
|
218
|
+
throw new Error(
|
|
219
|
+
"Ancestor must have a trailing slash. Pass `{ requireTrailingSlashForAncestor: false }` if needed.",
|
|
220
|
+
);
|
|
132
221
|
}
|
|
133
|
-
|
|
134
|
-
|
|
222
|
+
|
|
223
|
+
const other = new Path(potentialDescendant);
|
|
224
|
+
if (this.isAbsolutePath() !== other.isAbsolutePath()) {
|
|
225
|
+
return null;
|
|
135
226
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
227
|
+
|
|
228
|
+
// Leading slashes are okay, as they will result in a `""` component for
|
|
229
|
+
// absolute paths (and we don't compare absolute paths to relative paths.)
|
|
230
|
+
const thisParts = this.toggleTrailingSlash(false).#path.split("/");
|
|
231
|
+
const otherParts = other.toggleTrailingSlash(false).#path.split("/");
|
|
232
|
+
|
|
233
|
+
if (otherParts.length < thisParts.length) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
for (let i = 0; i < thisParts.length; i++) {
|
|
237
|
+
console.log(i, thisParts[i], otherParts[i]);
|
|
238
|
+
if (thisParts[i] !== otherParts[i]) {
|
|
239
|
+
return null;
|
|
140
240
|
}
|
|
141
|
-
return path;
|
|
142
241
|
}
|
|
143
|
-
|
|
242
|
+
return new Path("./")
|
|
243
|
+
.join(...otherParts.slice(thisParts.length))
|
|
244
|
+
.toggleTrailingSlash(other.hasTrailingSlash());
|
|
144
245
|
}
|
|
145
246
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
const prefix = resolutionPrefix(path);
|
|
149
|
-
this.#path = join(path);
|
|
150
|
-
if (prefix === ResolutionPrefix.Relative && !this.#path.startsWith(".")) {
|
|
151
|
-
// We don't have to handle the case of `"."`, as it already starts with `"."`
|
|
152
|
-
this.#path = `./${this.#path}`;
|
|
153
|
-
}
|
|
247
|
+
unresolve(path: string | URL | Path): Path {
|
|
248
|
+
return Path.resolve(path, this);
|
|
154
249
|
}
|
|
155
250
|
|
|
156
251
|
isAbsolutePath(): boolean {
|
|
@@ -237,7 +332,9 @@ export class Path {
|
|
|
237
332
|
}
|
|
238
333
|
return s;
|
|
239
334
|
});
|
|
240
|
-
return new Path(
|
|
335
|
+
return new Path(
|
|
336
|
+
joinPreservingRelativeResolutionPrefix(this.#path, segmentStrings),
|
|
337
|
+
);
|
|
241
338
|
}
|
|
242
339
|
|
|
243
340
|
/**
|
|
@@ -496,7 +593,47 @@ export class Path {
|
|
|
496
593
|
* */
|
|
497
594
|
static async makeTempDir(prefix?: string): Promise<AsyncDisposablePath> {
|
|
498
595
|
return new AsyncDisposablePath(
|
|
499
|
-
await mkdtemp(
|
|
596
|
+
await mkdtemp(
|
|
597
|
+
new Path(tmpdir()).join(prefix ?? DEFAULT_TEMP_PREFIX).toString(),
|
|
598
|
+
),
|
|
599
|
+
);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Return a path:
|
|
604
|
+
*
|
|
605
|
+
* - whose parent dir is a temp dir that *has* been created, but
|
|
606
|
+
* - which has itself not yet been created.
|
|
607
|
+
*
|
|
608
|
+
* Note that this path can actually also be used to create dir, but it is most
|
|
609
|
+
* convenient to get a path for a temporary file that can be written to, while
|
|
610
|
+
* having a disposal implementation that cleans everything up:
|
|
611
|
+
*
|
|
612
|
+
* await using tempFile = await Path.tempFilePath({ basename: "foo.txt" });
|
|
613
|
+
* await tempFile.write("hello world!");
|
|
614
|
+
* // …
|
|
615
|
+
*
|
|
616
|
+
* Note that that the following are equivalent when *not* using `await using`:
|
|
617
|
+
*
|
|
618
|
+
* await Path.tempFilePath({ basename: "foo.txt" });
|
|
619
|
+
* (await Path.makeTempDir()).join("file.txt");
|
|
620
|
+
*
|
|
621
|
+
* However, it is recommended to use `await using` to ensure cleanup.
|
|
622
|
+
*/
|
|
623
|
+
static async tempFilePath(options: {
|
|
624
|
+
tempDirPrefix?: string;
|
|
625
|
+
basename?: string | Path;
|
|
626
|
+
}): Promise<AsyncDisposablePath> {
|
|
627
|
+
const tempDir = new Path(
|
|
628
|
+
await mkdtemp(
|
|
629
|
+
new Path(tmpdir())
|
|
630
|
+
.join(options?.tempDirPrefix ?? DEFAULT_TEMP_PREFIX)
|
|
631
|
+
.toString(),
|
|
632
|
+
),
|
|
633
|
+
);
|
|
634
|
+
return new AsyncDisposablePath(
|
|
635
|
+
tempDir.join(options?.basename ?? DEFAULT_TEMP_FILE_NAME),
|
|
636
|
+
{ disposePathInstead: tempDir },
|
|
500
637
|
);
|
|
501
638
|
}
|
|
502
639
|
|
|
@@ -711,25 +848,22 @@ export class Path {
|
|
|
711
848
|
}
|
|
712
849
|
|
|
713
850
|
export class AsyncDisposablePath extends Path {
|
|
714
|
-
|
|
715
|
-
|
|
851
|
+
#options?: { disposePathInstead: Path };
|
|
852
|
+
constructor(
|
|
853
|
+
path: ConstructorParameters<typeof Path>[0],
|
|
854
|
+
options?: { disposePathInstead: Path | string },
|
|
855
|
+
) {
|
|
856
|
+
super(path);
|
|
857
|
+
if (options) {
|
|
858
|
+
this.#options = {
|
|
859
|
+
disposePathInstead: new Path(options.disposePathInstead),
|
|
860
|
+
};
|
|
861
|
+
}
|
|
716
862
|
}
|
|
717
|
-
}
|
|
718
863
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
*
|
|
723
|
-
* function process(args: (string | Path)[]) {
|
|
724
|
-
* const argsAsStrings = args.map(stringifyIfPath);
|
|
725
|
-
* }
|
|
726
|
-
*
|
|
727
|
-
*/
|
|
728
|
-
export function stringifyIfPath<T>(value: T | Path): T | string {
|
|
729
|
-
if (value instanceof Path) {
|
|
730
|
-
return value.toString();
|
|
731
|
-
}
|
|
732
|
-
return value;
|
|
864
|
+
async [Symbol.asyncDispose]() {
|
|
865
|
+
await (this.#options?.disposePathInstead ?? this).rm_rf();
|
|
866
|
+
}
|
|
733
867
|
}
|
|
734
868
|
|
|
735
869
|
export function mustNotHaveTrailingSlash(path: Path): void {
|
|
@@ -739,8 +873,3 @@ export function mustNotHaveTrailingSlash(path: Path): void {
|
|
|
739
873
|
);
|
|
740
874
|
}
|
|
741
875
|
}
|
|
742
|
-
|
|
743
|
-
const tmp = await Path.makeTempDir();
|
|
744
|
-
(await tmp.join("foo.json").write("foo")).rename(
|
|
745
|
-
tmp.join("sdfsD", "sdfsdfsdf", "sdfsdf.json"),
|
|
746
|
-
);
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { Path } from "./Path";
|
|
3
|
+
import { stringifyIfPath } from "./stringifyfIfPath";
|
|
4
|
+
|
|
5
|
+
test.concurrent(".stringifyIfPath(…)", async () => {
|
|
6
|
+
expect(stringifyIfPath(Path.homedir)).toBe("/mock/home/dir");
|
|
7
|
+
expect(stringifyIfPath("/mock/home/dir")).toBe("/mock/home/dir");
|
|
8
|
+
expect(stringifyIfPath(4)).toBe(4);
|
|
9
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Path } from "./Path";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* This function is useful to serialize any `Path`s in a structure to pass on to
|
|
5
|
+
* functions that do not know about the `Path` class, e.g.
|
|
6
|
+
*
|
|
7
|
+
* function process(args: (string | Path)[]) {
|
|
8
|
+
* const argsAsStrings = args.map(stringifyIfPath);
|
|
9
|
+
* }
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
export function stringifyIfPath<T>(value: Exclude<T, Path> | Path): T | string {
|
|
13
|
+
if (value instanceof Path) {
|
|
14
|
+
return value.toString();
|
|
15
|
+
}
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
@@ -2,12 +2,46 @@ import { expect, test } from "bun:test";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import "./PathSync";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
5
6
|
import { constants } from "node:fs/promises";
|
|
6
|
-
import { PrintableShellCommand } from "printable-shell-command";
|
|
7
7
|
import { PathSync } from "./PathSync";
|
|
8
8
|
|
|
9
|
+
test.concurrent("PathSync.resolve(…)", () => {
|
|
10
|
+
expect(PathSync.resolve("foo/lish", new PathSync("/bar/baz")).path).toEqual(
|
|
11
|
+
"/bar/foo/lish",
|
|
12
|
+
);
|
|
13
|
+
expect(PathSync.resolve("foo/lish", new PathSync("/bar/baz/")).path).toEqual(
|
|
14
|
+
"/bar/baz/foo/lish",
|
|
15
|
+
);
|
|
16
|
+
expect(
|
|
17
|
+
() => PathSync.resolve("foo/lish", new PathSync("bar/baz")).path,
|
|
18
|
+
).toThrow(/must be an absolute path/);
|
|
19
|
+
expect(PathSync.resolve("foo/lish", import.meta.url).path).toEqual(
|
|
20
|
+
new PathSync(import.meta.url).parent.join("foo/lish").path,
|
|
21
|
+
);
|
|
22
|
+
expect(PathSync.resolve("foo", "file:///hello/world").path).toEqual(
|
|
23
|
+
"/hello/foo",
|
|
24
|
+
);
|
|
25
|
+
expect(PathSync.resolve("foo", "file:///hello/world/").path).toEqual(
|
|
26
|
+
"/hello/world/foo",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test.concurrent(".resolve(…)", () => {
|
|
31
|
+
expect(new PathSync("/bar/baz").resolve("foo/lish").path).toEqual(
|
|
32
|
+
"/bar/foo/lish",
|
|
33
|
+
);
|
|
34
|
+
expect(new PathSync("/bar/baz/").resolve("foo/lish").path).toEqual(
|
|
35
|
+
"/bar/baz/foo/lish",
|
|
36
|
+
);
|
|
37
|
+
expect(() => new PathSync("bar/baz").resolve("foo/lish").path).toThrow(
|
|
38
|
+
/must be an absolute path/,
|
|
39
|
+
);
|
|
40
|
+
expect(new PathSync("/bar/baz").resolve("foo/lish")).toBeInstanceOf(PathSync);
|
|
41
|
+
});
|
|
42
|
+
|
|
9
43
|
test.concurrent(".existsAsFileSync()", () => {
|
|
10
|
-
|
|
44
|
+
using filePath = PathSync.tempFilePathSync({ basename: "file.txt" });
|
|
11
45
|
expect(filePath.existsSync()).toBe(false);
|
|
12
46
|
expect(filePath.existsSync({ mustBe: "file" })).toBe(false);
|
|
13
47
|
expect(filePath.existsSync({ mustBe: "directory" })).toBe(false);
|
|
@@ -25,29 +59,31 @@ test.concurrent(".existsAsFileSync()", () => {
|
|
|
25
59
|
});
|
|
26
60
|
|
|
27
61
|
test.concurrent(".existsAsDir()", () => {
|
|
28
|
-
|
|
29
|
-
expect(
|
|
30
|
-
expect(() =>
|
|
62
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
63
|
+
expect(tempDir.existsSync()).toBe(true);
|
|
64
|
+
expect(() => tempDir.existsSync({ mustBe: "file" })).toThrow(
|
|
31
65
|
/PathSync exists but is not a file/,
|
|
32
66
|
);
|
|
33
|
-
expect(
|
|
34
|
-
expect(
|
|
35
|
-
|
|
36
|
-
expect(
|
|
37
|
-
expect(
|
|
38
|
-
expect(
|
|
39
|
-
expect(
|
|
67
|
+
expect(tempDir.existsSync({ mustBe: "directory" })).toBe(true);
|
|
68
|
+
expect(tempDir.existsAsDirSync()).toBe(true);
|
|
69
|
+
tempDir.rm_rfSync();
|
|
70
|
+
expect(tempDir.existsSync()).toBe(false);
|
|
71
|
+
expect(tempDir.existsSync({ mustBe: "file" })).toBe(false);
|
|
72
|
+
expect(tempDir.existsSync({ mustBe: "directory" })).toBe(false);
|
|
73
|
+
expect(tempDir.existsAsDirSync()).toBe(false);
|
|
40
74
|
});
|
|
41
75
|
|
|
42
76
|
test.concurrent(".mkdirSync(…) (un-nested)", () => {
|
|
43
|
-
|
|
77
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
78
|
+
const dir = tempDir.join("mkdir-test");
|
|
44
79
|
expect(dir.existsSync()).toBe(false);
|
|
45
80
|
dir.mkdirSync();
|
|
46
81
|
expect(dir.existsSync()).toBe(true);
|
|
47
82
|
});
|
|
48
83
|
|
|
49
84
|
test.concurrent(".mkdirSync(…) (nested)", () => {
|
|
50
|
-
|
|
85
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
86
|
+
const dir = tempDir.join("mkdir-test/nested");
|
|
51
87
|
expect(dir.existsSync()).toBe(false);
|
|
52
88
|
expect(() => dir.mkdirSync({ recursive: false })).toThrow("no such file");
|
|
53
89
|
dir.mkdirSync();
|
|
@@ -55,7 +91,7 @@ test.concurrent(".mkdirSync(…) (nested)", () => {
|
|
|
55
91
|
});
|
|
56
92
|
|
|
57
93
|
test.concurrent(".cpSync(…)", () => {
|
|
58
|
-
|
|
94
|
+
using parentDir = PathSync.makeTempDirSync();
|
|
59
95
|
const file1 = parentDir.join("file1.txt");
|
|
60
96
|
const file2 = parentDir.join("file2.txt");
|
|
61
97
|
const file3 = parentDir.join("nonexistent/dirs/file3.txt");
|
|
@@ -80,7 +116,7 @@ test.concurrent(".cpSync(…)", () => {
|
|
|
80
116
|
});
|
|
81
117
|
|
|
82
118
|
test.concurrent(".renameSync(…)", () => {
|
|
83
|
-
|
|
119
|
+
using parentDir = PathSync.makeTempDirSync();
|
|
84
120
|
const file1 = parentDir.join("file1.txt");
|
|
85
121
|
const file2 = parentDir.join("file2.txt");
|
|
86
122
|
const file3 = parentDir.join("nonexistent/dirs/file3.txt");
|
|
@@ -105,18 +141,28 @@ test.concurrent(".renameSync(…)", () => {
|
|
|
105
141
|
});
|
|
106
142
|
|
|
107
143
|
test.concurrent(".makeTempDirSync(…)", () => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
144
|
+
let disposablePathSyncString: string;
|
|
145
|
+
{
|
|
146
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
147
|
+
disposablePathSyncString = tempDir.path;
|
|
148
|
+
expect(tempDir.path).toContain("/js-temp-");
|
|
149
|
+
expect(tempDir.basename.path).toStartWith("js-temp-");
|
|
150
|
+
expect(tempDir.existsAsDirSync()).toBe(true);
|
|
151
|
+
}
|
|
152
|
+
expect(new PathSync(disposablePathSyncString).existsAsDirSync()).toBe(false);
|
|
153
|
+
|
|
154
|
+
let disposablePathSyncString2: string;
|
|
155
|
+
{
|
|
156
|
+
using tempDir2 = PathSync.makeTempDirSync("foo");
|
|
157
|
+
disposablePathSyncString2 = tempDir2.path;
|
|
158
|
+
expect(tempDir2.path).not.toContain("/js-temp-");
|
|
159
|
+
expect(tempDir2.basename.path).toStartWith("foo");
|
|
160
|
+
}
|
|
161
|
+
expect(new PathSync(disposablePathSyncString2).existsAsDirSync()).toBe(false);
|
|
116
162
|
});
|
|
117
163
|
|
|
118
164
|
test.concurrent(".rmSync(…) (file)", () => {
|
|
119
|
-
|
|
165
|
+
using file = PathSync.tempFilePathSync({ basename: "file.txt" });
|
|
120
166
|
file.writeSync("");
|
|
121
167
|
expect(file.existsAsFileSync()).toBe(true);
|
|
122
168
|
file.rmSync();
|
|
@@ -126,7 +172,7 @@ test.concurrent(".rmSync(…) (file)", () => {
|
|
|
126
172
|
});
|
|
127
173
|
|
|
128
174
|
test.concurrent(".rmSync(…) (folder)", () => {
|
|
129
|
-
|
|
175
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
130
176
|
const file = tempDir.join("file.txt");
|
|
131
177
|
file.writeSync("");
|
|
132
178
|
expect(tempDir.existsAsDirSync()).toBe(true);
|
|
@@ -138,7 +184,7 @@ test.concurrent(".rmSync(…) (folder)", () => {
|
|
|
138
184
|
});
|
|
139
185
|
|
|
140
186
|
test.concurrent(".rmDirSync(…) (folder)", () => {
|
|
141
|
-
|
|
187
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
142
188
|
const file = tempDir.join("file.txt");
|
|
143
189
|
file.writeSync("");
|
|
144
190
|
expect(tempDir.existsAsDirSync()).toBe(true);
|
|
@@ -150,7 +196,7 @@ test.concurrent(".rmDirSync(…) (folder)", () => {
|
|
|
150
196
|
});
|
|
151
197
|
|
|
152
198
|
test.concurrent(".rm_rfSync(…) (file)", () => {
|
|
153
|
-
|
|
199
|
+
using file = PathSync.tempFilePathSync({ basename: "file.txt" });
|
|
154
200
|
file.writeSync("");
|
|
155
201
|
expect(file.existsAsFileSync()).toBe(true);
|
|
156
202
|
file.rm_rfSync();
|
|
@@ -161,7 +207,7 @@ test.concurrent(".rm_rfSync(…) (file)", () => {
|
|
|
161
207
|
});
|
|
162
208
|
|
|
163
209
|
test.concurrent(".rm_rfSync(…) (folder)", () => {
|
|
164
|
-
|
|
210
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
165
211
|
tempDir.join("file.txt").writeSync("");
|
|
166
212
|
expect(tempDir.path).toContain("/js-temp-");
|
|
167
213
|
expect(tempDir.existsSync()).toBe(true);
|
|
@@ -172,7 +218,7 @@ test.concurrent(".rm_rfSync(…) (folder)", () => {
|
|
|
172
218
|
});
|
|
173
219
|
|
|
174
220
|
test.concurrent(".readTextSync()", () => {
|
|
175
|
-
|
|
221
|
+
using file = PathSync.tempFilePathSync({ basename: "file.txt" });
|
|
176
222
|
file.writeSync("hi");
|
|
177
223
|
file.writeSync("bye");
|
|
178
224
|
|
|
@@ -181,7 +227,7 @@ test.concurrent(".readTextSync()", () => {
|
|
|
181
227
|
});
|
|
182
228
|
|
|
183
229
|
test.concurrent(".readJSONSync()", () => {
|
|
184
|
-
|
|
230
|
+
using file = PathSync.tempFilePathSync({ basename: "file.json" });
|
|
185
231
|
file.writeSync(JSON.stringify({ foo: "bar" }));
|
|
186
232
|
|
|
187
233
|
expect(file.readJSONSync()).toEqual<Record<string, string>>({ foo: "bar" });
|
|
@@ -192,7 +238,7 @@ test.concurrent(".readJSONSync()", () => {
|
|
|
192
238
|
});
|
|
193
239
|
|
|
194
240
|
test.concurrent(".readJSONSync(…) with fallback", () => {
|
|
195
|
-
|
|
241
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
196
242
|
const file = tempDir.join("file.json");
|
|
197
243
|
const json: { foo?: number } = file.readJSONSync({ fallback: { foo: 4 } });
|
|
198
244
|
expect(json).toEqual({ foo: 4 });
|
|
@@ -210,7 +256,7 @@ test.concurrent(".readJSONSync(…) with fallback", () => {
|
|
|
210
256
|
});
|
|
211
257
|
|
|
212
258
|
test.concurrent(".writeSync(…)", () => {
|
|
213
|
-
|
|
259
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
214
260
|
const file = tempDir.join("file.json");
|
|
215
261
|
expect(file.writeSync("foo")).toBe(file);
|
|
216
262
|
|
|
@@ -226,14 +272,14 @@ test.concurrent(".writeSync(…)", () => {
|
|
|
226
272
|
});
|
|
227
273
|
|
|
228
274
|
test.concurrent(".writeJSONSync(…)", () => {
|
|
229
|
-
|
|
275
|
+
using file = PathSync.tempFilePathSync({ basename: "file.json" });
|
|
230
276
|
expect(file.writeJSONSync({ foo: "bar" })).toBe(file);
|
|
231
277
|
|
|
232
278
|
expect(file.readJSONSync()).toEqual<Record<string, string>>({ foo: "bar" });
|
|
233
279
|
});
|
|
234
280
|
|
|
235
281
|
test.concurrent(".appendFileSync(…)", () => {
|
|
236
|
-
|
|
282
|
+
using file = PathSync.tempFilePathSync({ basename: "file.txt" });
|
|
237
283
|
file.appendFileSync("test\n");
|
|
238
284
|
expect(file.readTextSync()).toEqual("test\n");
|
|
239
285
|
file.appendFileSync("more\n");
|
|
@@ -241,7 +287,7 @@ test.concurrent(".appendFileSync(…)", () => {
|
|
|
241
287
|
});
|
|
242
288
|
|
|
243
289
|
test.concurrent(".readDirSync(…)", () => {
|
|
244
|
-
|
|
290
|
+
using dir = PathSync.makeTempDirSync();
|
|
245
291
|
dir.join("file.txt").writeSync("hello");
|
|
246
292
|
dir.join("dir/file.json").writeSync("hello");
|
|
247
293
|
|
|
@@ -255,7 +301,7 @@ test.concurrent(".readDirSync(…)", () => {
|
|
|
255
301
|
});
|
|
256
302
|
|
|
257
303
|
test.concurrent(".symlinkSync(…)", () => {
|
|
258
|
-
|
|
304
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
259
305
|
const source = tempDir.join("foo.txt");
|
|
260
306
|
const target = tempDir.join("bar.txt");
|
|
261
307
|
source.symlinkSync(target);
|
|
@@ -267,7 +313,7 @@ test.concurrent(".symlinkSync(…)", () => {
|
|
|
267
313
|
});
|
|
268
314
|
|
|
269
315
|
test.concurrent(".realpathSync(…)", () => {
|
|
270
|
-
|
|
316
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
271
317
|
const source = tempDir.join("foo.txt");
|
|
272
318
|
source.writeSync("hello world!");
|
|
273
319
|
const target = tempDir.join("bar.txt");
|
|
@@ -276,7 +322,7 @@ test.concurrent(".realpathSync(…)", () => {
|
|
|
276
322
|
});
|
|
277
323
|
|
|
278
324
|
test.concurrent(".statSync(…)", () => {
|
|
279
|
-
|
|
325
|
+
using file = PathSync.tempFilePathSync({ basename: "foo.txt" });
|
|
280
326
|
file.writeSync("hello");
|
|
281
327
|
|
|
282
328
|
expect(file.statSync()?.size).toEqual(5);
|
|
@@ -285,7 +331,7 @@ test.concurrent(".statSync(…)", () => {
|
|
|
285
331
|
});
|
|
286
332
|
|
|
287
333
|
test.concurrent(".lstatSync(…)", () => {
|
|
288
|
-
|
|
334
|
+
using tempDir = PathSync.makeTempDirSync();
|
|
289
335
|
const source = tempDir.join("foo.txt");
|
|
290
336
|
const target = tempDir.join("bar.txt");
|
|
291
337
|
source.symlinkSync(target);
|
|
@@ -297,46 +343,47 @@ test.concurrent(".lstatSync(…)", () => {
|
|
|
297
343
|
expect(target.readTextSync()).toEqual("hello");
|
|
298
344
|
});
|
|
299
345
|
|
|
346
|
+
// Note: this test uses `execSync(…)` because it runs the binary and returns
|
|
347
|
+
// expected error messages correctly. Further, it helps keep this entire test
|
|
348
|
+
// file sync (which we have some basic checks for in `lint-sync-code.ts` that
|
|
349
|
+
// don't seem like a great idea to work around).
|
|
300
350
|
test.concurrent(".chmodSync(…)", () => {
|
|
301
|
-
|
|
302
|
-
expect(() =>
|
|
303
|
-
/
|
|
351
|
+
using binPath = PathSync.tempFilePathSync({ basename: "bin.bash" });
|
|
352
|
+
expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
|
|
353
|
+
/No such file or directory|not found/,
|
|
304
354
|
);
|
|
305
|
-
binPath.writeSync(`#!/usr/bin/env
|
|
306
|
-
|
|
307
|
-
echo hi`);
|
|
308
|
-
// TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
|
|
309
|
-
// binPath.writeSync(`#!/usr/bin/env -S bun run --
|
|
355
|
+
binPath.writeSync(`#!/usr/bin/env -S bun run --
|
|
310
356
|
|
|
311
|
-
|
|
312
|
-
expect(() =>
|
|
313
|
-
/
|
|
357
|
+
console.log("hi");`);
|
|
358
|
+
expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
|
|
359
|
+
/Permission denied/,
|
|
314
360
|
);
|
|
315
361
|
binPath.chmodSync(0o755);
|
|
316
|
-
expect((
|
|
362
|
+
expect(execSync(binPath.path, { encoding: "utf-8" })).toEqual("hi\n");
|
|
317
363
|
});
|
|
318
364
|
|
|
365
|
+
// Note: this test uses `execSync(…)` because it runs the binary and returns
|
|
366
|
+
// expected error messages correctly. Further, it helps keep this entire test
|
|
367
|
+
// file sync (which we have some basic checks for in `lint-sync-code.ts` that
|
|
368
|
+
// don't seem like a great idea to work around).
|
|
319
369
|
test.concurrent(".chmodXSync(…)", () => {
|
|
320
|
-
|
|
321
|
-
expect(() =>
|
|
322
|
-
/
|
|
370
|
+
using binPath = PathSync.tempFilePathSync({ basename: "bin.bash" });
|
|
371
|
+
expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
|
|
372
|
+
/No such file or directory|not found/,
|
|
323
373
|
);
|
|
324
|
-
binPath.writeSync(`#!/usr/bin/env
|
|
325
|
-
|
|
326
|
-
echo hi`);
|
|
327
|
-
// TODO: why doesn't this work here instead (but works in `printable-shell-comand`)?
|
|
328
|
-
// binPath.writeSync(`#!/usr/bin/env -S bun run --
|
|
374
|
+
binPath.writeSync(`#!/usr/bin/env -S bun run --
|
|
329
375
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
376
|
+
console.log("hi");`);
|
|
377
|
+
// TODO: Should not be `ENOENT`? Probably `EACCES`.
|
|
378
|
+
expect(() => execSync(binPath.path, { stdio: ["ignore"] })).toThrow(
|
|
379
|
+
/Permission denied/,
|
|
333
380
|
);
|
|
334
381
|
expect(binPath.statSync().mode & constants.S_IWUSR).toBeTruthy();
|
|
335
382
|
binPath.chmodSync(0o444);
|
|
336
383
|
expect(binPath.statSync().mode & constants.S_IWUSR).toBeFalsy();
|
|
337
384
|
expect(binPath.statSync().mode & constants.S_IXUSR).toBeFalsy();
|
|
338
385
|
binPath.chmodXSync();
|
|
339
|
-
expect((
|
|
386
|
+
expect(execSync(binPath.path, { encoding: "utf-8" })).toEqual("hi\n");
|
|
340
387
|
expect(binPath.statSync().mode & constants.S_IWUSR).toBeFalsy();
|
|
341
388
|
expect(binPath.statSync().mode & constants.S_IXUSR).toBeTruthy();
|
|
342
389
|
});
|