-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfiles.js
264 lines (233 loc) · 6.11 KB
/
files.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
// The best filesystem for promises and array manipulation
import run from "atocha";
import fs from "node:fs";
import fsp from "node:fs/promises";
import { homedir, tmpdir } from "node:os";
import path from "node:path";
import { Readable } from "node:stream";
import swear from "swear";
// Find whether it's Linux or Mac, where we can use `find`
const mac = () => process.platform === "darwin";
const linux = () => process.platform === "linux";
// Retrieve the full, absolute path for the path
const abs = swear(async (name = ".", base = process.cwd()) => {
name = await name;
base = await base;
// Absolute paths do not need more absolutism
if (path.isAbsolute(name)) return name;
if (name.slice(0, 2) === "~/") {
base = await home();
name = name.slice(2);
}
// We are off-base here; recover the viable base option
if (!base || typeof base !== "string") {
base = process.cwd();
}
// Return the file/folder within the base
return join(base, name);
});
const copy = swear(async (src, dst) => {
src = await abs(src);
dst = await abs(dst);
await mkdir(dir(dst));
await fsp.copyFile(src, dst);
return dst;
});
// Get the directory from path
const dir = swear(async (name = ".") => {
name = await abs(name);
return path.dirname(name);
});
// Check whether a filename exists or not
const exists = swear(async (name) => {
name = await abs(name);
return fsp.access(name).then(
() => true,
() => false
);
});
// Get the home directory: https://stackoverflow.com/a/9081436/938236
const home = swear((...args) => join(homedir(), ...args).then(mkdir));
// Put several path segments together
const join = swear((...parts) => abs(path.join(...parts)));
// List all the files in the folder
const list = swear(async (dir) => {
dir = await abs(dir);
return swear(fsp.readdir(dir)).map((file) => abs(file, dir));
});
// Create a new directory in the specified path
// Note: `recursive` flag on Node.js is ONLY for Mac and Windows (not Linux), so
// it's totally worthless for us
const mkdir = swear(async (name) => {
name = await abs(name);
// Create a recursive list of paths to create, from the highest to the lowest
const list = name
.split(path.sep)
.map((part, i, all) => all.slice(0, i + 1).join(path.sep))
.filter(Boolean);
// Build each nested path sequentially
for (let path of list) {
if (await exists(path)) continue;
await fsp.mkdir(path).catch(() => null);
}
return name;
});
const move = swear(async (src, dst) => {
try {
src = await abs(src);
dst = await abs(dst);
await mkdir(dir(dst));
await fsp.rename(src, dst);
return dst;
} catch (error) {
// Some OS/environments don't allow move, so copy it first
// and then remove the original
if (error.code === "EXDEV") {
await copy(src, dst);
await remove(src);
return dst;
} else {
throw error;
}
}
});
// Get the path's filename
const name = swear((file) => path.basename(file));
// Read the contents of a single file
const read = swear(async (name, options = {}) => {
name = await abs(name);
const type = options && options.type ? options.type : "text";
if (type === "text") {
return fsp.readFile(name, "utf-8").catch(() => null);
}
if (type === "json") {
return read(name).then(JSON.parse);
}
if (type === "raw" || type === "buffer") {
return fsp.readFile(name).catch(() => null);
}
if (type === "stream" || type === "web" || type === "webStream") {
const file = await fsp.open(name);
return file.readableWebStream();
}
if (type === "node" || type === "nodeStream") {
return fs.createReadStream(name);
}
});
// Delete a file or directory (recursively)
const remove = swear(async (name) => {
name = await abs(name);
if (name === "/") throw new Error("Cannot remove the root folder `/`");
if (!(await exists(name))) return name;
if (await stat(name).isDirectory()) {
// Remove all content recursively
await list(name).map(remove);
await fsp.rmdir(name).catch(() => null);
} else {
await fsp.unlink(name).catch(() => null);
}
return name;
});
const sep = path.sep;
// Get some interesting info from the path
const stat = swear(async (name) => {
name = await abs(name);
return fsp.lstat(name).catch(() => null);
});
// Get a temporary folder
const tmp = swear(async (...args) => {
const path = await join(tmpdir(), ...args);
return mkdir(path);
});
// Perform a recursive walk
const rWalk = (name) => {
const file = abs(name);
const deeper = async (file) => {
if (await stat(file).isDirectory()) {
return rWalk(file);
}
return [file];
};
// Note: list() already wraps the promise
return list(file)
.map(deeper)
.reduce((all, arr) => all.concat(arr), []);
};
// Attempt to make an OS walk, and fallback to the recursive one
const walk = swear(async (name) => {
name = await abs(name);
if (!(await exists(name))) return [];
if (linux() || mac()) {
try {
// Attempt to invoke run (command may fail for large directories)
return await run(`find ${name} -type f`).split("\n").filter(Boolean);
} catch (error) {
// Fall back to rWalk() below
}
}
return rWalk(name).filter(Boolean);
});
// Create a new file with the specified contents
const write = swear(async (name, body = "") => {
name = await abs(name);
// If it's a WebStream, convert it to a normal node stream
if (body && body.pipeTo) {
body = Readable.fromWeb(body);
}
// If it's a type that is not a string nor a stream, convert it
// into plain text with JSON.stringify
if (
body &&
typeof body !== "string" &&
!body.pipe &&
!(body instanceof Buffer)
) {
body = JSON.stringify(body);
}
await mkdir(dir(name));
await fsp.writeFile(name, body, "utf-8");
return name;
});
const files = {
abs,
copy,
dir,
exists,
home,
join,
list,
mkdir,
move,
name,
read,
remove,
rename: move,
sep,
stat,
swear,
tmp,
walk,
write,
};
export {
abs,
copy,
dir,
exists,
home,
join,
list,
mkdir,
move,
name,
read,
remove,
move as rename,
sep,
stat,
swear,
tmp,
walk,
write,
};
export default files;