json-sort-cli 4.2.3 → 4.3.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/process-files.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
- import { lstat, open, realpath, rename, unlink } from "node:fs/promises";
3
+ import * as filesystem from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import { decodeJson, formatParsedJson, parseJson } from "./json-formatter.js";
6
6
 
@@ -18,129 +18,141 @@ function asError(error) {
18
18
  return new Error(description, { cause: error });
19
19
  }
20
20
 
21
- async function openWithoutFollowing(filePath) {
22
- const noFollow = constants.O_NOFOLLOW ?? 0;
23
- return open(filePath, constants.O_RDONLY | noFollow);
24
- }
21
+ export function createFileOperations({
22
+ fs = filesystem,
23
+ noFollow = constants.O_NOFOLLOW,
24
+ } = {}) {
25
+ const { lstat, open, realpath, rename, unlink } = fs;
25
26
 
26
- async function readFileSnapshot(filePath) {
27
- const realPath = await realpath(filePath);
28
- const pathStat = await lstat(filePath, { bigint: true });
29
- if (pathStat.isSymbolicLink()) {
30
- throw new Error(`Refusing to process symbolic link: ${filePath}`);
27
+ async function openWithoutFollowing(filePath) {
28
+ return open(filePath, constants.O_RDONLY | (noFollow ?? 0));
31
29
  }
32
30
 
33
- const handle = await openWithoutFollowing(filePath);
34
- try {
35
- const stat = await handle.stat({ bigint: true });
36
- if (!stat.isFile()) {
37
- throw new Error(`Refusing to process a non-file: ${filePath}`);
38
- }
39
- if (!sameIdentity(pathStat, stat)) {
40
- throw new Error(
41
- `The file changed while it was being opened: ${filePath}`,
42
- );
31
+ async function readFileSnapshot(filePath) {
32
+ const realPath = await realpath(filePath);
33
+ const pathStat = await lstat(filePath, { bigint: true });
34
+ if (pathStat.isSymbolicLink()) {
35
+ throw new Error(`Refusing to process symbolic link: ${filePath}`);
43
36
  }
44
- if ((await realpath(filePath)) !== realPath) {
45
- throw new Error(`The file route changed while opening: ${filePath}`);
37
+
38
+ const handle = await openWithoutFollowing(filePath);
39
+ try {
40
+ const stat = await handle.stat({ bigint: true });
41
+ if (!stat.isFile()) {
42
+ throw new Error(`Refusing to process a non-file: ${filePath}`);
43
+ }
44
+ if (!sameIdentity(pathStat, stat)) {
45
+ throw new Error(
46
+ `The file changed while it was being opened: ${filePath}`,
47
+ );
48
+ }
49
+ if ((await realpath(filePath)) !== realPath) {
50
+ throw new Error(`The file route changed while opening: ${filePath}`);
51
+ }
52
+ return { contents: await handle.readFile(), realPath, stat };
53
+ } finally {
54
+ await handle.close();
46
55
  }
47
- return { contents: await handle.readFile(), realPath, stat };
48
- } finally {
49
- await handle.close();
50
56
  }
51
- }
52
57
 
53
- function sameIdentity(left, right) {
54
- return (
55
- left.dev === right.dev &&
56
- left.ino === right.ino &&
57
- left.size === right.size &&
58
- left.mtimeNs === right.mtimeNs &&
59
- left.ctimeNs === right.ctimeNs
60
- );
61
- }
62
-
63
- async function commitFile(filePath, output, snapshot) {
64
- if (
65
- !snapshot?.stat ||
66
- !Buffer.isBuffer(snapshot.contents) ||
67
- typeof snapshot.realPath !== "string"
68
- ) {
69
- throw new Error("Cannot safely commit without the original file snapshot");
58
+ function sameIdentity(left, right) {
59
+ return (
60
+ left.dev === right.dev &&
61
+ left.ino === right.ino &&
62
+ left.size === right.size &&
63
+ left.mtimeNs === right.mtimeNs &&
64
+ left.ctimeNs === right.ctimeNs
65
+ );
70
66
  }
71
67
 
72
- const commitPath = snapshot.realPath;
73
- const directory = path.dirname(commitPath);
74
- const temporaryPath = path.join(
75
- directory,
76
- `.${path.basename(commitPath)}.${process.pid}.${randomUUID()}.tmp`,
77
- );
78
- let temporaryHandle;
79
-
80
- try {
81
- if ((await realpath(filePath)) !== snapshot.realPath) {
68
+ async function commitFile(filePath, output, snapshot) {
69
+ if (
70
+ !snapshot?.stat ||
71
+ !Buffer.isBuffer(snapshot.contents) ||
72
+ typeof snapshot.realPath !== "string"
73
+ ) {
82
74
  throw new Error(
83
- "The file route changed after it was read; refusing to overwrite it",
75
+ "Cannot safely commit without the original file snapshot",
84
76
  );
85
77
  }
86
- temporaryHandle = await open(
87
- temporaryPath,
88
- "wx",
89
- Number(snapshot.stat.mode & 0o7777n),
78
+
79
+ const commitPath = snapshot.realPath;
80
+ const directory = path.dirname(commitPath);
81
+ const temporaryPath = path.join(
82
+ directory,
83
+ `.${path.basename(commitPath)}.${process.pid}.${randomUUID()}.tmp`,
90
84
  );
91
- await temporaryHandle.writeFile(output, "utf8");
92
- await temporaryHandle.chmod(Number(snapshot.stat.mode & 0o7777n));
85
+ let temporaryHandle;
93
86
 
94
- const temporaryStat = await temporaryHandle.stat({ bigint: true });
95
- if (
96
- temporaryStat.uid !== snapshot.stat.uid ||
97
- temporaryStat.gid !== snapshot.stat.gid
98
- ) {
99
- await temporaryHandle.chown(
100
- Number(snapshot.stat.uid),
101
- Number(snapshot.stat.gid),
87
+ try {
88
+ if ((await realpath(filePath)) !== snapshot.realPath) {
89
+ throw new Error(
90
+ "The file route changed after it was read; refusing to overwrite it",
91
+ );
92
+ }
93
+ temporaryHandle = await open(
94
+ temporaryPath,
95
+ "wx",
96
+ Number(snapshot.stat.mode & 0o7777n),
102
97
  );
103
- }
104
- await temporaryHandle.sync();
105
- await temporaryHandle.close();
106
- temporaryHandle = undefined;
98
+ await temporaryHandle.writeFile(output, "utf8");
99
+ await temporaryHandle.chmod(Number(snapshot.stat.mode & 0o7777n));
107
100
 
108
- const current = await readFileSnapshot(filePath);
109
- if (
110
- !sameIdentity(snapshot.stat, current.stat) ||
111
- !snapshot.contents.equals(current.contents)
112
- ) {
113
- throw new Error(
114
- "The file changed after it was read; refusing to overwrite it",
115
- );
116
- }
101
+ const temporaryStat = await temporaryHandle.stat({ bigint: true });
102
+ if (
103
+ temporaryStat.uid !== snapshot.stat.uid ||
104
+ temporaryStat.gid !== snapshot.stat.gid
105
+ ) {
106
+ await temporaryHandle.chown(
107
+ Number(snapshot.stat.uid),
108
+ Number(snapshot.stat.gid),
109
+ );
110
+ }
111
+ await temporaryHandle.sync();
112
+ await temporaryHandle.close();
113
+ temporaryHandle = undefined;
117
114
 
118
- if ((await realpath(filePath)) !== snapshot.realPath) {
119
- throw new Error(
120
- "The file route changed before commit; refusing to overwrite it",
121
- );
122
- }
123
- await rename(temporaryPath, commitPath);
115
+ const current = await readFileSnapshot(filePath);
116
+ if (
117
+ !sameIdentity(snapshot.stat, current.stat) ||
118
+ !snapshot.contents.equals(current.contents)
119
+ ) {
120
+ throw new Error(
121
+ "The file changed after it was read; refusing to overwrite it",
122
+ );
123
+ }
124
124
 
125
- // Directory syncing is not supported by every platform. The file is
126
- // already durable and atomically visible when this best-effort step runs.
127
- try {
128
- const directoryHandle = await open(directory, "r");
125
+ if ((await realpath(filePath)) !== snapshot.realPath) {
126
+ throw new Error(
127
+ "The file route changed before commit; refusing to overwrite it",
128
+ );
129
+ }
130
+ await rename(temporaryPath, commitPath);
131
+
132
+ // Directory syncing is not supported by every platform. The file is
133
+ // already durable and atomically visible when this best-effort step runs.
129
134
  try {
130
- await directoryHandle.sync();
131
- } finally {
132
- await directoryHandle.close();
135
+ const directoryHandle = await open(directory, "r");
136
+ try {
137
+ await directoryHandle.sync();
138
+ } finally {
139
+ await directoryHandle.close();
140
+ }
141
+ } catch {}
142
+ } catch (error) {
143
+ if (temporaryHandle) {
144
+ await temporaryHandle.close().catch(() => {});
133
145
  }
134
- } catch {}
135
- } catch (error) {
136
- if (temporaryHandle) {
137
- await temporaryHandle.close().catch(() => {});
146
+ await unlink(temporaryPath).catch(() => {});
147
+ throw error;
138
148
  }
139
- await unlink(temporaryPath).catch(() => {});
140
- throw error;
141
149
  }
150
+
151
+ return { read: readFileSnapshot, write: commitFile };
142
152
  }
143
153
 
154
+ const { read: readFileSnapshot, write: commitFile } = createFileOperations();
155
+
144
156
  export class FileProcessingError extends Error {
145
157
  constructor(filePath, stage, error) {
146
158
  const cause = asError(error);