@jerryan/pi-hashline-edit 0.7.4 → 0.8.1

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/src/fs-write.ts CHANGED
@@ -1,76 +1,76 @@
1
- import { randomUUID } from "crypto";
2
- import { lstat, mkdir, readlink, rename, stat, writeFile } from "fs/promises";
3
- import { dirname, join, parse, resolve, sep } from "path";
4
-
5
- export async function resolveMutationTargetPath(path: string): Promise<string> {
6
- const absolutePath = resolve(path);
7
- const { root } = parse(absolutePath);
8
- const parts = absolutePath.slice(root.length).split(sep).filter((part) => part.length > 0);
9
- const visitedSymlinks = new Set<string>();
10
-
11
- async function resolveFromParts(currentPath: string, remainingParts: string[]): Promise<string> {
12
- if (remainingParts.length === 0) {
13
- return currentPath;
14
- }
15
-
16
- const [nextPart, ...tail] = remainingParts;
17
- const candidatePath = join(currentPath, nextPart);
18
-
19
- try {
20
- const candidateStats = await lstat(candidatePath);
21
- if (!candidateStats.isSymbolicLink()) {
22
- return resolveFromParts(candidatePath, tail);
23
- }
24
-
25
- if (visitedSymlinks.has(candidatePath)) {
26
- const error = new Error(`Too many symbolic links while resolving ${path}`) as NodeJS.ErrnoException;
27
- error.code = "ELOOP";
28
- throw error;
29
- }
30
- visitedSymlinks.add(candidatePath);
31
-
32
- const linkTargetPath = resolve(dirname(candidatePath), await readlink(candidatePath));
33
- const targetParts = linkTargetPath
34
- .slice(parse(linkTargetPath).root.length)
35
- .split(sep)
36
- .filter((part) => part.length > 0);
37
- return resolveFromParts(parse(linkTargetPath).root, [...targetParts, ...tail]);
38
- } catch (error: unknown) {
39
- if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
40
- return join(candidatePath, ...tail);
41
- }
42
- throw error;
43
- }
44
- }
45
-
46
- return resolveFromParts(root, parts);
47
- }
48
-
49
- export async function writeFileAtomically(
50
- path: string,
51
- content: string,
52
- ): Promise<void> {
53
- const targetPath = await resolveMutationTargetPath(path);
54
-
55
- let existingStats: Awaited<ReturnType<typeof stat>> | null = null;
56
- try {
57
- existingStats = await stat(targetPath);
58
- } catch (error: unknown) {
59
- if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") {
60
- throw error;
61
- }
62
- }
63
-
64
- if (existingStats && existingStats.nlink > 1) {
65
- await writeFile(targetPath, content, "utf-8");
66
- return;
67
- }
68
-
69
- const dir = dirname(targetPath);
70
- const tempPath = join(dir, `.tmp-${randomUUID()}`);
71
- await mkdir(dir, { recursive: true });
72
- const mode = existingStats ? existingStats.mode & 0o7777 : 0o600;
73
- await writeFile(tempPath, content, { encoding: "utf-8", flag: "wx", mode });
74
-
75
- await rename(tempPath, targetPath);
76
- }
1
+ import { randomUUID } from "crypto";
2
+ import { lstat, mkdir, readlink, rename, stat, writeFile } from "fs/promises";
3
+ import { dirname, join, parse, resolve, sep } from "path";
4
+
5
+ export async function resolveMutationTargetPath(path: string): Promise<string> {
6
+ const absolutePath = resolve(path);
7
+ const { root } = parse(absolutePath);
8
+ const parts = absolutePath.slice(root.length).split(sep).filter((part) => part.length > 0);
9
+ const visitedSymlinks = new Set<string>();
10
+
11
+ async function resolveFromParts(currentPath: string, remainingParts: string[]): Promise<string> {
12
+ if (remainingParts.length === 0) {
13
+ return currentPath;
14
+ }
15
+
16
+ const [nextPart, ...tail] = remainingParts;
17
+ const candidatePath = join(currentPath, nextPart);
18
+
19
+ try {
20
+ const candidateStats = await lstat(candidatePath);
21
+ if (!candidateStats.isSymbolicLink()) {
22
+ return resolveFromParts(candidatePath, tail);
23
+ }
24
+
25
+ if (visitedSymlinks.has(candidatePath)) {
26
+ const error = new Error(`Too many symbolic links while resolving ${path}`) as NodeJS.ErrnoException;
27
+ error.code = "ELOOP";
28
+ throw error;
29
+ }
30
+ visitedSymlinks.add(candidatePath);
31
+
32
+ const linkTargetPath = resolve(dirname(candidatePath), await readlink(candidatePath));
33
+ const targetParts = linkTargetPath
34
+ .slice(parse(linkTargetPath).root.length)
35
+ .split(sep)
36
+ .filter((part) => part.length > 0);
37
+ return resolveFromParts(parse(linkTargetPath).root, [...targetParts, ...tail]);
38
+ } catch (error: unknown) {
39
+ if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
40
+ return join(candidatePath, ...tail);
41
+ }
42
+ throw error;
43
+ }
44
+ }
45
+
46
+ return resolveFromParts(root, parts);
47
+ }
48
+
49
+ export async function writeFileAtomically(
50
+ path: string,
51
+ content: string,
52
+ ): Promise<void> {
53
+ const targetPath = await resolveMutationTargetPath(path);
54
+
55
+ let existingStats: Awaited<ReturnType<typeof stat>> | null = null;
56
+ try {
57
+ existingStats = await stat(targetPath);
58
+ } catch (error: unknown) {
59
+ if ((error as NodeJS.ErrnoException)?.code !== "ENOENT") {
60
+ throw error;
61
+ }
62
+ }
63
+
64
+ if (existingStats && existingStats.nlink > 1) {
65
+ await writeFile(targetPath, content, "utf-8");
66
+ return;
67
+ }
68
+
69
+ const dir = dirname(targetPath);
70
+ const tempPath = join(dir, `.tmp-${randomUUID()}`);
71
+ await mkdir(dir, { recursive: true });
72
+ const mode = existingStats ? existingStats.mode & 0o7777 : 0o600;
73
+ await writeFile(tempPath, content, { encoding: "utf-8", flag: "wx", mode });
74
+
75
+ await rename(tempPath, targetPath);
76
+ }