@jsenv/filesystem 2.3.1 → 2.6.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/README.md +7 -4
- package/package.json +18 -30
- package/src/collectDirectoryMatchReport.js +29 -23
- package/src/collectFiles.js +34 -45
- package/src/copyDirectoryContent.js +26 -14
- package/src/copyFileSystemNode.js +37 -23
- package/src/listFilesMatching.js +8 -20
- package/src/moveDirectoryContent.js +27 -14
- package/src/moveFileSystemNode.js +43 -28
- package/src/readFileSystemNodeStat.js +2 -2
- package/src/removeFileSystemNode.js +56 -35
- package/dist/commonjs/jsenv_filesystem.cjs +0 -2654
- package/dist/commonjs/jsenv_filesystem.cjs.map +0 -604
- package/dist/dev/template_node_package.dev.cjs +0 -20
- package/dist/dev/template_node_package.dev.cjs.map +0 -16
- package/dist/prod/template_node_package.prod.cjs +0 -20
- package/dist/prod/template_node_package.prod.cjs.map +0 -16
|
@@ -1,2654 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
-
|
|
5
|
-
var urlMeta = require('@jsenv/url-meta');
|
|
6
|
-
var url = require('url');
|
|
7
|
-
var node_fs = require('fs');
|
|
8
|
-
var fs = require('fs');
|
|
9
|
-
var node_crypto = require('crypto');
|
|
10
|
-
var cancellation = require('@jsenv/cancellation');
|
|
11
|
-
var path = require('path');
|
|
12
|
-
var util = require('util');
|
|
13
|
-
|
|
14
|
-
const ensureUrlTrailingSlash = url => {
|
|
15
|
-
return url.endsWith("/") ? url : `${url}/`;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const isFileSystemPath = value => {
|
|
19
|
-
if (typeof value !== "string") {
|
|
20
|
-
throw new TypeError(`isFileSystemPath first arg must be a string, got ${value}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (value[0] === "/") {
|
|
24
|
-
return true;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return startsWithWindowsDriveLetter(value);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const startsWithWindowsDriveLetter = string => {
|
|
31
|
-
const firstChar = string[0];
|
|
32
|
-
if (!/[a-zA-Z]/.test(firstChar)) return false;
|
|
33
|
-
const secondChar = string[1];
|
|
34
|
-
if (secondChar !== ":") return false;
|
|
35
|
-
return true;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const fileSystemPathToUrl = value => {
|
|
39
|
-
if (!isFileSystemPath(value)) {
|
|
40
|
-
throw new Error(`received an invalid value for fileSystemPath: ${value}`);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return String(url.pathToFileURL(value));
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const assertAndNormalizeDirectoryUrl = value => {
|
|
47
|
-
let urlString;
|
|
48
|
-
|
|
49
|
-
if (value instanceof URL) {
|
|
50
|
-
urlString = value.href;
|
|
51
|
-
} else if (typeof value === "string") {
|
|
52
|
-
if (isFileSystemPath(value)) {
|
|
53
|
-
urlString = fileSystemPathToUrl(value);
|
|
54
|
-
} else {
|
|
55
|
-
try {
|
|
56
|
-
urlString = String(new URL(value));
|
|
57
|
-
} catch (e) {
|
|
58
|
-
throw new TypeError(`directoryUrl must be a valid url, received ${value}`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
} else {
|
|
62
|
-
throw new TypeError(`directoryUrl must be a string or an url, received ${value}`);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!urlString.startsWith("file://")) {
|
|
66
|
-
throw new Error(`directoryUrl must starts with file://, received ${value}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return ensureUrlTrailingSlash(urlString);
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
const assertAndNormalizeFileUrl = (value, baseUrl) => {
|
|
73
|
-
let urlString;
|
|
74
|
-
|
|
75
|
-
if (value instanceof URL) {
|
|
76
|
-
urlString = value.href;
|
|
77
|
-
} else if (typeof value === "string") {
|
|
78
|
-
if (isFileSystemPath(value)) {
|
|
79
|
-
urlString = fileSystemPathToUrl(value);
|
|
80
|
-
} else {
|
|
81
|
-
try {
|
|
82
|
-
urlString = String(new URL(value, baseUrl));
|
|
83
|
-
} catch (e) {
|
|
84
|
-
throw new TypeError(`fileUrl must be a valid url, received ${value}`);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
throw new TypeError(`fileUrl must be a string or an url, received ${value}`);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (!urlString.startsWith("file://")) {
|
|
92
|
-
throw new Error(`fileUrl must starts with file://, received ${value}`);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return urlString;
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const statsToType = stats => {
|
|
99
|
-
if (stats.isFile()) return "file";
|
|
100
|
-
if (stats.isDirectory()) return "directory";
|
|
101
|
-
if (stats.isSymbolicLink()) return "symbolic-link";
|
|
102
|
-
if (stats.isFIFO()) return "fifo";
|
|
103
|
-
if (stats.isSocket()) return "socket";
|
|
104
|
-
if (stats.isCharacterDevice()) return "character-device";
|
|
105
|
-
if (stats.isBlockDevice()) return "block-device";
|
|
106
|
-
return undefined;
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const urlToFileSystemPath = url$1 => {
|
|
110
|
-
let urlString = String(url$1);
|
|
111
|
-
|
|
112
|
-
if (urlString[urlString.length - 1] === "/") {
|
|
113
|
-
// remove trailing / so that nodejs path becomes predictable otherwise it logs
|
|
114
|
-
// the trailing slash on linux but does not on windows
|
|
115
|
-
urlString = urlString.slice(0, -1);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const fileSystemPath = url.fileURLToPath(urlString);
|
|
119
|
-
return fileSystemPath;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
// https://github.com/coderaiser/cloudcmd/issues/63#issuecomment-195478143
|
|
123
|
-
// https://nodejs.org/api/fs.html#fs_file_modes
|
|
124
|
-
// https://github.com/TooTallNate/stat-mode
|
|
125
|
-
// cannot get from fs.constants because they are not available on windows
|
|
126
|
-
const S_IRUSR = 256;
|
|
127
|
-
/* 0000400 read permission, owner */
|
|
128
|
-
|
|
129
|
-
const S_IWUSR = 128;
|
|
130
|
-
/* 0000200 write permission, owner */
|
|
131
|
-
|
|
132
|
-
const S_IXUSR = 64;
|
|
133
|
-
/* 0000100 execute/search permission, owner */
|
|
134
|
-
|
|
135
|
-
const S_IRGRP = 32;
|
|
136
|
-
/* 0000040 read permission, group */
|
|
137
|
-
|
|
138
|
-
const S_IWGRP = 16;
|
|
139
|
-
/* 0000020 write permission, group */
|
|
140
|
-
|
|
141
|
-
const S_IXGRP = 8;
|
|
142
|
-
/* 0000010 execute/search permission, group */
|
|
143
|
-
|
|
144
|
-
const S_IROTH = 4;
|
|
145
|
-
/* 0000004 read permission, others */
|
|
146
|
-
|
|
147
|
-
const S_IWOTH = 2;
|
|
148
|
-
/* 0000002 write permission, others */
|
|
149
|
-
|
|
150
|
-
const S_IXOTH = 1;
|
|
151
|
-
/* 0000001 execute/search permission, others */
|
|
152
|
-
|
|
153
|
-
/*
|
|
154
|
-
here we could warn that on windows only 0o444 or 0o666 will work
|
|
155
|
-
|
|
156
|
-
0o444 (readonly)
|
|
157
|
-
{
|
|
158
|
-
owner: {read: true, write: false, execute: false},
|
|
159
|
-
group: {read: true, write: false, execute: false},
|
|
160
|
-
others: {read: true, write: false, execute: false},
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
0o666 (read and write)
|
|
164
|
-
{
|
|
165
|
-
owner: {read: true, write: true, execute: false},
|
|
166
|
-
group: {read: true, write: true, execute: false},
|
|
167
|
-
others: {read: true, write: true, execute: false},
|
|
168
|
-
}
|
|
169
|
-
*/
|
|
170
|
-
|
|
171
|
-
const binaryFlagsToPermissions = binaryFlags => {
|
|
172
|
-
const owner = {
|
|
173
|
-
read: Boolean(binaryFlags & S_IRUSR),
|
|
174
|
-
write: Boolean(binaryFlags & S_IWUSR),
|
|
175
|
-
execute: Boolean(binaryFlags & S_IXUSR)
|
|
176
|
-
};
|
|
177
|
-
const group = {
|
|
178
|
-
read: Boolean(binaryFlags & S_IRGRP),
|
|
179
|
-
write: Boolean(binaryFlags & S_IWGRP),
|
|
180
|
-
execute: Boolean(binaryFlags & S_IXGRP)
|
|
181
|
-
};
|
|
182
|
-
const others = {
|
|
183
|
-
read: Boolean(binaryFlags & S_IROTH),
|
|
184
|
-
write: Boolean(binaryFlags & S_IWOTH),
|
|
185
|
-
execute: Boolean(binaryFlags & S_IXOTH)
|
|
186
|
-
};
|
|
187
|
-
return {
|
|
188
|
-
owner,
|
|
189
|
-
group,
|
|
190
|
-
others
|
|
191
|
-
};
|
|
192
|
-
};
|
|
193
|
-
const permissionsToBinaryFlags = ({
|
|
194
|
-
owner,
|
|
195
|
-
group,
|
|
196
|
-
others
|
|
197
|
-
}) => {
|
|
198
|
-
let binaryFlags = 0;
|
|
199
|
-
if (owner.read) binaryFlags |= S_IRUSR;
|
|
200
|
-
if (owner.write) binaryFlags |= S_IWUSR;
|
|
201
|
-
if (owner.execute) binaryFlags |= S_IXUSR;
|
|
202
|
-
if (group.read) binaryFlags |= S_IRGRP;
|
|
203
|
-
if (group.write) binaryFlags |= S_IWGRP;
|
|
204
|
-
if (group.execute) binaryFlags |= S_IXGRP;
|
|
205
|
-
if (others.read) binaryFlags |= S_IROTH;
|
|
206
|
-
if (others.write) binaryFlags |= S_IWOTH;
|
|
207
|
-
if (others.execute) binaryFlags |= S_IXOTH;
|
|
208
|
-
return binaryFlags;
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const writeFileSystemNodePermissions = async (source, permissions) => {
|
|
212
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
213
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
214
|
-
let binaryFlags;
|
|
215
|
-
|
|
216
|
-
if (typeof permissions === "object") {
|
|
217
|
-
permissions = {
|
|
218
|
-
owner: {
|
|
219
|
-
read: getPermissionOrComputeDefault("read", "owner", permissions),
|
|
220
|
-
write: getPermissionOrComputeDefault("write", "owner", permissions),
|
|
221
|
-
execute: getPermissionOrComputeDefault("execute", "owner", permissions)
|
|
222
|
-
},
|
|
223
|
-
group: {
|
|
224
|
-
read: getPermissionOrComputeDefault("read", "group", permissions),
|
|
225
|
-
write: getPermissionOrComputeDefault("write", "group", permissions),
|
|
226
|
-
execute: getPermissionOrComputeDefault("execute", "group", permissions)
|
|
227
|
-
},
|
|
228
|
-
others: {
|
|
229
|
-
read: getPermissionOrComputeDefault("read", "others", permissions),
|
|
230
|
-
write: getPermissionOrComputeDefault("write", "others", permissions),
|
|
231
|
-
execute: getPermissionOrComputeDefault("execute", "others", permissions)
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
binaryFlags = permissionsToBinaryFlags(permissions);
|
|
235
|
-
} else {
|
|
236
|
-
binaryFlags = permissions;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
return chmodNaive(sourcePath, binaryFlags);
|
|
240
|
-
};
|
|
241
|
-
|
|
242
|
-
const chmodNaive = (fileSystemPath, binaryFlags) => {
|
|
243
|
-
return new Promise((resolve, reject) => {
|
|
244
|
-
fs.chmod(fileSystemPath, binaryFlags, error => {
|
|
245
|
-
if (error) {
|
|
246
|
-
reject(error);
|
|
247
|
-
} else {
|
|
248
|
-
resolve();
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
});
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const actionLevels = {
|
|
255
|
-
read: 0,
|
|
256
|
-
write: 1,
|
|
257
|
-
execute: 2
|
|
258
|
-
};
|
|
259
|
-
const subjectLevels = {
|
|
260
|
-
others: 0,
|
|
261
|
-
group: 1,
|
|
262
|
-
owner: 2
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
const getPermissionOrComputeDefault = (action, subject, permissions) => {
|
|
266
|
-
if (subject in permissions) {
|
|
267
|
-
const subjectPermissions = permissions[subject];
|
|
268
|
-
|
|
269
|
-
if (action in subjectPermissions) {
|
|
270
|
-
return subjectPermissions[action];
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
const actionLevel = actionLevels[action];
|
|
274
|
-
const actionFallback = Object.keys(actionLevels).find(actionFallbackCandidate => actionLevels[actionFallbackCandidate] > actionLevel && actionFallbackCandidate in subjectPermissions);
|
|
275
|
-
|
|
276
|
-
if (actionFallback) {
|
|
277
|
-
return subjectPermissions[actionFallback];
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
const subjectLevel = subjectLevels[subject]; // do we have a subject with a stronger level (group or owner)
|
|
282
|
-
// where we could read the action permission ?
|
|
283
|
-
|
|
284
|
-
const subjectFallback = Object.keys(subjectLevels).find(subjectFallbackCandidate => subjectLevels[subjectFallbackCandidate] > subjectLevel && subjectFallbackCandidate in permissions);
|
|
285
|
-
|
|
286
|
-
if (subjectFallback) {
|
|
287
|
-
const subjectPermissions = permissions[subjectFallback];
|
|
288
|
-
return action in subjectPermissions ? subjectPermissions[action] : getPermissionOrComputeDefault(action, subjectFallback, permissions);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
return false;
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
/*
|
|
295
|
-
* - stats object documentation on Node.js
|
|
296
|
-
* https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
|
|
297
|
-
*/
|
|
298
|
-
const isWindows$3 = process.platform === "win32";
|
|
299
|
-
const readFileSystemNodeStat = async (source, {
|
|
300
|
-
nullIfNotFound = false,
|
|
301
|
-
followLink = true
|
|
302
|
-
} = {}) => {
|
|
303
|
-
if (source.endsWith("/")) source = source.slice(0, -1);
|
|
304
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
305
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
306
|
-
const handleNotFoundOption = nullIfNotFound ? {
|
|
307
|
-
handleNotFoundError: () => null
|
|
308
|
-
} : {};
|
|
309
|
-
return readStat(sourcePath, {
|
|
310
|
-
followLink,
|
|
311
|
-
...handleNotFoundOption,
|
|
312
|
-
...(isWindows$3 ? {
|
|
313
|
-
// Windows can EPERM on stat
|
|
314
|
-
handlePermissionDeniedError: async error => {
|
|
315
|
-
console.error(`trying to fix windows EPERM after stats on ${sourcePath}`);
|
|
316
|
-
|
|
317
|
-
try {
|
|
318
|
-
// unfortunately it means we mutate the permissions
|
|
319
|
-
// without being able to restore them to the previous value
|
|
320
|
-
// (because reading current permission would also throw)
|
|
321
|
-
await writeFileSystemNodePermissions(sourceUrl, 0o666);
|
|
322
|
-
const stats = await readStat(sourcePath, {
|
|
323
|
-
followLink,
|
|
324
|
-
...handleNotFoundOption,
|
|
325
|
-
// could not fix the permission error, give up and throw original error
|
|
326
|
-
handlePermissionDeniedError: () => {
|
|
327
|
-
console.error(`still got EPERM after stats on ${sourcePath}`);
|
|
328
|
-
throw error;
|
|
329
|
-
}
|
|
330
|
-
});
|
|
331
|
-
return stats;
|
|
332
|
-
} catch (e) {
|
|
333
|
-
console.error(`error while trying to fix windows EPERM after stats on ${sourcePath}: ${e.stack}`);
|
|
334
|
-
throw error;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
} : {})
|
|
338
|
-
});
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
const readStat = (sourcePath, {
|
|
342
|
-
followLink,
|
|
343
|
-
handleNotFoundError = null,
|
|
344
|
-
handlePermissionDeniedError = null
|
|
345
|
-
} = {}) => {
|
|
346
|
-
const nodeMethod = followLink ? node_fs.stat : node_fs.lstat;
|
|
347
|
-
return new Promise((resolve, reject) => {
|
|
348
|
-
nodeMethod(sourcePath, (error, statsObject) => {
|
|
349
|
-
if (error) {
|
|
350
|
-
if (handleNotFoundError && error.code === "ENOENT") {
|
|
351
|
-
resolve(handleNotFoundError(error));
|
|
352
|
-
} else if (handlePermissionDeniedError && (error.code === "EPERM" || error.code === "EACCES")) {
|
|
353
|
-
resolve(handlePermissionDeniedError(error));
|
|
354
|
-
} else {
|
|
355
|
-
reject(error);
|
|
356
|
-
}
|
|
357
|
-
} else {
|
|
358
|
-
resolve(statsObject);
|
|
359
|
-
}
|
|
360
|
-
});
|
|
361
|
-
});
|
|
362
|
-
};
|
|
363
|
-
|
|
364
|
-
const assertDirectoryPresence = async source => {
|
|
365
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
366
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
367
|
-
const sourceStats = await readFileSystemNodeStat(sourceUrl, {
|
|
368
|
-
nullIfNotFound: true
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
if (!sourceStats) {
|
|
372
|
-
throw new Error(`directory not found at ${sourcePath}`);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
if (!sourceStats.isDirectory()) {
|
|
376
|
-
throw new Error(`directory expected at ${sourcePath} and found ${statsToType(sourceStats)} instead`);
|
|
377
|
-
}
|
|
378
|
-
};
|
|
379
|
-
|
|
380
|
-
const assertFilePresence = async source => {
|
|
381
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
382
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
383
|
-
const sourceStats = await readFileSystemNodeStat(sourceUrl, {
|
|
384
|
-
nullIfNotFound: true
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
if (!sourceStats) {
|
|
388
|
-
throw new Error(`file not found at ${sourcePath}`);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
if (!sourceStats.isFile()) {
|
|
392
|
-
throw new Error(`file expected at ${sourcePath} and found ${statsToType(sourceStats)} instead`);
|
|
393
|
-
}
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
/*
|
|
397
|
-
* - Buffer documentation on Node.js
|
|
398
|
-
* https://nodejs.org/docs/latest-v13.x/api/buffer.html
|
|
399
|
-
* - eTag documentation on MDN
|
|
400
|
-
* https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
|
|
401
|
-
*/
|
|
402
|
-
const ETAG_FOR_EMPTY_CONTENT = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
|
|
403
|
-
const bufferToEtag = buffer => {
|
|
404
|
-
if (!Buffer.isBuffer(buffer)) {
|
|
405
|
-
throw new TypeError(`buffer expected, got ${buffer}`);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
if (buffer.length === 0) {
|
|
409
|
-
return ETAG_FOR_EMPTY_CONTENT;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const hash = node_crypto.createHash("sha1");
|
|
413
|
-
hash.update(buffer, "utf8");
|
|
414
|
-
const hashBase64String = hash.digest("base64");
|
|
415
|
-
const hashBase64StringSubset = hashBase64String.slice(0, 27);
|
|
416
|
-
const length = buffer.length;
|
|
417
|
-
return `"${length.toString(16)}-${hashBase64StringSubset}"`;
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
const readDirectory = async (url, {
|
|
421
|
-
emfileMaxWait = 1000
|
|
422
|
-
} = {}) => {
|
|
423
|
-
const directoryUrl = assertAndNormalizeDirectoryUrl(url);
|
|
424
|
-
const directoryPath = urlToFileSystemPath(directoryUrl);
|
|
425
|
-
const startMs = Date.now();
|
|
426
|
-
let attemptCount = 0;
|
|
427
|
-
|
|
428
|
-
const attempt = () => {
|
|
429
|
-
return readdirNaive(directoryPath, {
|
|
430
|
-
handleTooManyFilesOpenedError: async error => {
|
|
431
|
-
attemptCount++;
|
|
432
|
-
const nowMs = Date.now();
|
|
433
|
-
const timeSpentWaiting = nowMs - startMs;
|
|
434
|
-
|
|
435
|
-
if (timeSpentWaiting > emfileMaxWait) {
|
|
436
|
-
throw error;
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
return new Promise(resolve => {
|
|
440
|
-
setTimeout(() => {
|
|
441
|
-
resolve(attempt());
|
|
442
|
-
}, attemptCount);
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
};
|
|
447
|
-
|
|
448
|
-
return attempt();
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
const readdirNaive = (directoryPath, {
|
|
452
|
-
handleTooManyFilesOpenedError = null
|
|
453
|
-
} = {}) => {
|
|
454
|
-
return new Promise((resolve, reject) => {
|
|
455
|
-
fs.readdir(directoryPath, (error, names) => {
|
|
456
|
-
if (error) {
|
|
457
|
-
// https://nodejs.org/dist/latest-v13.x/docs/api/errors.html#errors_common_system_errors
|
|
458
|
-
if (handleTooManyFilesOpenedError && (error.code === "EMFILE" || error.code === "ENFILE")) {
|
|
459
|
-
resolve(handleTooManyFilesOpenedError(error));
|
|
460
|
-
} else {
|
|
461
|
-
reject(error);
|
|
462
|
-
}
|
|
463
|
-
} else {
|
|
464
|
-
resolve(names);
|
|
465
|
-
}
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
};
|
|
469
|
-
|
|
470
|
-
const getCommonPathname = (pathname, otherPathname) => {
|
|
471
|
-
const firstDifferentCharacterIndex = findFirstDifferentCharacterIndex(pathname, otherPathname); // pathname and otherpathname are exactly the same
|
|
472
|
-
|
|
473
|
-
if (firstDifferentCharacterIndex === -1) {
|
|
474
|
-
return pathname;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const commonString = pathname.slice(0, firstDifferentCharacterIndex + 1); // the first different char is at firstDifferentCharacterIndex
|
|
478
|
-
|
|
479
|
-
if (pathname.charAt(firstDifferentCharacterIndex) === "/") {
|
|
480
|
-
return commonString;
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
if (otherPathname.charAt(firstDifferentCharacterIndex) === "/") {
|
|
484
|
-
return commonString;
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
const firstDifferentSlashIndex = commonString.lastIndexOf("/");
|
|
488
|
-
return pathname.slice(0, firstDifferentSlashIndex + 1);
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
const findFirstDifferentCharacterIndex = (string, otherString) => {
|
|
492
|
-
const maxCommonLength = Math.min(string.length, otherString.length);
|
|
493
|
-
let i = 0;
|
|
494
|
-
|
|
495
|
-
while (i < maxCommonLength) {
|
|
496
|
-
const char = string.charAt(i);
|
|
497
|
-
const otherChar = otherString.charAt(i);
|
|
498
|
-
|
|
499
|
-
if (char !== otherChar) {
|
|
500
|
-
return i;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
i++;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
if (string.length === otherString.length) {
|
|
507
|
-
return -1;
|
|
508
|
-
} // they differ at maxCommonLength
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
return maxCommonLength;
|
|
512
|
-
};
|
|
513
|
-
|
|
514
|
-
const pathnameToParentPathname = pathname => {
|
|
515
|
-
const slashLastIndex = pathname.lastIndexOf("/");
|
|
516
|
-
|
|
517
|
-
if (slashLastIndex === -1) {
|
|
518
|
-
return "/";
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
return pathname.slice(0, slashLastIndex + 1);
|
|
522
|
-
};
|
|
523
|
-
|
|
524
|
-
const urlToRelativeUrl = (url, baseUrl) => {
|
|
525
|
-
const urlObject = new URL(url);
|
|
526
|
-
const baseUrlObject = new URL(baseUrl);
|
|
527
|
-
|
|
528
|
-
if (urlObject.protocol !== baseUrlObject.protocol) {
|
|
529
|
-
const urlAsString = String(url);
|
|
530
|
-
return urlAsString;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
if (urlObject.username !== baseUrlObject.username || urlObject.password !== baseUrlObject.password || urlObject.host !== baseUrlObject.host) {
|
|
534
|
-
const afterUrlScheme = String(url).slice(urlObject.protocol.length);
|
|
535
|
-
return afterUrlScheme;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const {
|
|
539
|
-
pathname,
|
|
540
|
-
hash,
|
|
541
|
-
search
|
|
542
|
-
} = urlObject;
|
|
543
|
-
|
|
544
|
-
if (pathname === "/") {
|
|
545
|
-
const baseUrlRessourceWithoutLeadingSlash = baseUrlObject.pathname.slice(1);
|
|
546
|
-
return baseUrlRessourceWithoutLeadingSlash;
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const basePathname = baseUrlObject.pathname;
|
|
550
|
-
const commonPathname = getCommonPathname(pathname, basePathname);
|
|
551
|
-
|
|
552
|
-
if (!commonPathname) {
|
|
553
|
-
const urlAsString = String(url);
|
|
554
|
-
return urlAsString;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
const specificPathname = pathname.slice(commonPathname.length);
|
|
558
|
-
const baseSpecificPathname = basePathname.slice(commonPathname.length);
|
|
559
|
-
|
|
560
|
-
if (baseSpecificPathname.includes("/")) {
|
|
561
|
-
const baseSpecificParentPathname = pathnameToParentPathname(baseSpecificPathname);
|
|
562
|
-
const relativeDirectoriesNotation = baseSpecificParentPathname.replace(/.*?\//g, "../");
|
|
563
|
-
const relativeUrl = `${relativeDirectoriesNotation}${specificPathname}${search}${hash}`;
|
|
564
|
-
return relativeUrl;
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
const relativeUrl = `${specificPathname}${search}${hash}`;
|
|
568
|
-
return relativeUrl;
|
|
569
|
-
};
|
|
570
|
-
|
|
571
|
-
const comparePathnames = (leftPathame, rightPathname) => {
|
|
572
|
-
const leftPartArray = leftPathame.split("/");
|
|
573
|
-
const rightPartArray = rightPathname.split("/");
|
|
574
|
-
const leftLength = leftPartArray.length;
|
|
575
|
-
const rightLength = rightPartArray.length;
|
|
576
|
-
const maxLength = Math.max(leftLength, rightLength);
|
|
577
|
-
let i = 0;
|
|
578
|
-
|
|
579
|
-
while (i < maxLength) {
|
|
580
|
-
const leftPartExists = (i in leftPartArray);
|
|
581
|
-
const rightPartExists = (i in rightPartArray); // longer comes first
|
|
582
|
-
|
|
583
|
-
if (!leftPartExists) {
|
|
584
|
-
return +1;
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
if (!rightPartExists) {
|
|
588
|
-
return -1;
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
const leftPartIsLast = i === leftPartArray.length - 1;
|
|
592
|
-
const rightPartIsLast = i === rightPartArray.length - 1; // folder comes first
|
|
593
|
-
|
|
594
|
-
if (leftPartIsLast && !rightPartIsLast) {
|
|
595
|
-
return +1;
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
if (!leftPartIsLast && rightPartIsLast) {
|
|
599
|
-
return -1;
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
const leftPart = leftPartArray[i];
|
|
603
|
-
const rightPart = rightPartArray[i];
|
|
604
|
-
i++; // local comparison comes first
|
|
605
|
-
|
|
606
|
-
const comparison = leftPart.localeCompare(rightPart);
|
|
607
|
-
|
|
608
|
-
if (comparison !== 0) {
|
|
609
|
-
return comparison;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
|
|
613
|
-
if (leftLength < rightLength) {
|
|
614
|
-
return +1;
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
if (leftLength > rightLength) {
|
|
618
|
-
return -1;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
return 0;
|
|
622
|
-
};
|
|
623
|
-
|
|
624
|
-
const collectDirectoryMatchReport = async ({
|
|
625
|
-
cancellationToken = cancellation.createCancellationToken(),
|
|
626
|
-
directoryUrl,
|
|
627
|
-
structuredMetaMap,
|
|
628
|
-
predicate
|
|
629
|
-
}) => {
|
|
630
|
-
const matchingArray = [];
|
|
631
|
-
const ignoredArray = [];
|
|
632
|
-
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
|
|
633
|
-
|
|
634
|
-
if (typeof predicate !== "function") {
|
|
635
|
-
throw new TypeError(`predicate must be a function, got ${predicate}`);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
const structuredMetaMapNormalized = urlMeta.normalizeStructuredMetaMap(structuredMetaMap, rootDirectoryUrl);
|
|
639
|
-
|
|
640
|
-
const visitDirectory = async directoryUrl => {
|
|
641
|
-
const directoryItems = await cancellation.createOperation({
|
|
642
|
-
cancellationToken,
|
|
643
|
-
start: () => readDirectory(directoryUrl)
|
|
644
|
-
});
|
|
645
|
-
await Promise.all(directoryItems.map(async directoryItem => {
|
|
646
|
-
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`;
|
|
647
|
-
const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl);
|
|
648
|
-
const directoryChildNodeStats = await cancellation.createOperation({
|
|
649
|
-
cancellationToken,
|
|
650
|
-
start: () => readFileSystemNodeStat(directoryChildNodeUrl, {
|
|
651
|
-
// we ignore symlink because recursively traversed
|
|
652
|
-
// so symlinked file will be discovered.
|
|
653
|
-
// Moreover if they lead outside of directoryPath it can become a problem
|
|
654
|
-
// like infinite recursion of whatever.
|
|
655
|
-
// that we could handle using an object of pathname already seen but it will be useless
|
|
656
|
-
// because directoryPath is recursively traversed
|
|
657
|
-
followLink: false
|
|
658
|
-
})
|
|
659
|
-
});
|
|
660
|
-
|
|
661
|
-
if (directoryChildNodeStats.isDirectory()) {
|
|
662
|
-
const subDirectoryUrl = `${directoryChildNodeUrl}/`;
|
|
663
|
-
|
|
664
|
-
if (!urlMeta.urlCanContainsMetaMatching({
|
|
665
|
-
url: subDirectoryUrl,
|
|
666
|
-
structuredMetaMap: structuredMetaMapNormalized,
|
|
667
|
-
predicate
|
|
668
|
-
})) {
|
|
669
|
-
ignoredArray.push({
|
|
670
|
-
relativeUrl: ensureUrlTrailingSlash(relativeUrl),
|
|
671
|
-
fileStats: directoryChildNodeStats
|
|
672
|
-
});
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
await visitDirectory(subDirectoryUrl);
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
if (directoryChildNodeStats.isFile()) {
|
|
681
|
-
const meta = urlMeta.urlToMeta({
|
|
682
|
-
url: directoryChildNodeUrl,
|
|
683
|
-
structuredMetaMap: structuredMetaMapNormalized
|
|
684
|
-
});
|
|
685
|
-
|
|
686
|
-
if (!predicate(meta)) {
|
|
687
|
-
ignoredArray.push({
|
|
688
|
-
relativeUrl,
|
|
689
|
-
meta,
|
|
690
|
-
fileStats: directoryChildNodeStats
|
|
691
|
-
});
|
|
692
|
-
return;
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
matchingArray.push({
|
|
696
|
-
relativeUrl,
|
|
697
|
-
meta,
|
|
698
|
-
fileStats: directoryChildNodeStats
|
|
699
|
-
});
|
|
700
|
-
return;
|
|
701
|
-
}
|
|
702
|
-
}));
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
await visitDirectory(rootDirectoryUrl);
|
|
706
|
-
return {
|
|
707
|
-
matchingArray: sortByRelativeUrl(matchingArray),
|
|
708
|
-
ignoredArray: sortByRelativeUrl(ignoredArray)
|
|
709
|
-
};
|
|
710
|
-
};
|
|
711
|
-
|
|
712
|
-
const sortByRelativeUrl = array => array.sort((left, right) => {
|
|
713
|
-
return comparePathnames(left.relativeUrl, right.relativeUrl);
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
const collectFiles = async ({
|
|
717
|
-
cancellationToken = cancellation.createCancellationToken(),
|
|
718
|
-
directoryUrl,
|
|
719
|
-
structuredMetaMap,
|
|
720
|
-
predicate,
|
|
721
|
-
matchingFileOperation = () => null
|
|
722
|
-
}) => {
|
|
723
|
-
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
|
|
724
|
-
|
|
725
|
-
if (typeof predicate !== "function") {
|
|
726
|
-
throw new TypeError(`predicate must be a function, got ${predicate}`);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
if (typeof matchingFileOperation !== "function") {
|
|
730
|
-
throw new TypeError(`matchingFileOperation must be a function, got ${matchingFileOperation}`);
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
const structuredMetaMapNormalized = urlMeta.normalizeStructuredMetaMap(structuredMetaMap, rootDirectoryUrl);
|
|
734
|
-
const matchingFileResultArray = [];
|
|
735
|
-
|
|
736
|
-
const visitDirectory = async directoryUrl => {
|
|
737
|
-
const directoryItems = await cancellation.createOperation({
|
|
738
|
-
cancellationToken,
|
|
739
|
-
start: () => readDirectory(directoryUrl)
|
|
740
|
-
});
|
|
741
|
-
await Promise.all(directoryItems.map(async directoryItem => {
|
|
742
|
-
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`;
|
|
743
|
-
const directoryChildNodeStats = await cancellation.createOperation({
|
|
744
|
-
cancellationToken,
|
|
745
|
-
start: () => readFileSystemNodeStat(directoryChildNodeUrl, {
|
|
746
|
-
// we ignore symlink because recursively traversed
|
|
747
|
-
// so symlinked file will be discovered.
|
|
748
|
-
// Moreover if they lead outside of directoryPath it can become a problem
|
|
749
|
-
// like infinite recursion of whatever.
|
|
750
|
-
// that we could handle using an object of pathname already seen but it will be useless
|
|
751
|
-
// because directoryPath is recursively traversed
|
|
752
|
-
followLink: false
|
|
753
|
-
})
|
|
754
|
-
});
|
|
755
|
-
|
|
756
|
-
if (directoryChildNodeStats.isDirectory()) {
|
|
757
|
-
const subDirectoryUrl = `${directoryChildNodeUrl}/`;
|
|
758
|
-
|
|
759
|
-
if (!urlMeta.urlCanContainsMetaMatching({
|
|
760
|
-
url: subDirectoryUrl,
|
|
761
|
-
structuredMetaMap: structuredMetaMapNormalized,
|
|
762
|
-
predicate
|
|
763
|
-
})) {
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
await visitDirectory(subDirectoryUrl);
|
|
768
|
-
return;
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
if (directoryChildNodeStats.isFile()) {
|
|
772
|
-
const meta = urlMeta.urlToMeta({
|
|
773
|
-
url: directoryChildNodeUrl,
|
|
774
|
-
structuredMetaMap: structuredMetaMapNormalized
|
|
775
|
-
});
|
|
776
|
-
if (!predicate(meta)) return;
|
|
777
|
-
const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl);
|
|
778
|
-
const operationResult = await cancellation.createOperation({
|
|
779
|
-
cancellationToken,
|
|
780
|
-
start: () => matchingFileOperation({
|
|
781
|
-
cancellationToken,
|
|
782
|
-
relativeUrl,
|
|
783
|
-
meta,
|
|
784
|
-
fileStats: directoryChildNodeStats
|
|
785
|
-
})
|
|
786
|
-
});
|
|
787
|
-
matchingFileResultArray.push({
|
|
788
|
-
url: new URL(relativeUrl, rootDirectoryUrl).href,
|
|
789
|
-
relativeUrl,
|
|
790
|
-
meta,
|
|
791
|
-
fileStats: directoryChildNodeStats,
|
|
792
|
-
operationResult
|
|
793
|
-
});
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
}));
|
|
797
|
-
};
|
|
798
|
-
|
|
799
|
-
await visitDirectory(rootDirectoryUrl); // When we operate on thoose files later it feels more natural
|
|
800
|
-
// to perform operation in the same order they appear in the filesystem.
|
|
801
|
-
// It also allow to get a predictable return value.
|
|
802
|
-
// For that reason we sort matchingFileResultArray
|
|
803
|
-
|
|
804
|
-
matchingFileResultArray.sort((leftFile, rightFile) => {
|
|
805
|
-
return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl);
|
|
806
|
-
});
|
|
807
|
-
return matchingFileResultArray;
|
|
808
|
-
};
|
|
809
|
-
|
|
810
|
-
const {
|
|
811
|
-
mkdir
|
|
812
|
-
} = fs.promises;
|
|
813
|
-
const writeDirectory = async (destination, {
|
|
814
|
-
recursive = true,
|
|
815
|
-
allowUseless = false
|
|
816
|
-
} = {}) => {
|
|
817
|
-
const destinationUrl = assertAndNormalizeDirectoryUrl(destination);
|
|
818
|
-
const destinationPath = urlToFileSystemPath(destinationUrl);
|
|
819
|
-
const destinationStats = await readFileSystemNodeStat(destinationUrl, {
|
|
820
|
-
nullIfNotFound: true,
|
|
821
|
-
followLink: false
|
|
822
|
-
});
|
|
823
|
-
|
|
824
|
-
if (destinationStats) {
|
|
825
|
-
if (destinationStats.isDirectory()) {
|
|
826
|
-
if (allowUseless) {
|
|
827
|
-
return;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
throw new Error(`directory already exists at ${destinationPath}`);
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
const destinationType = statsToType(destinationStats);
|
|
834
|
-
throw new Error(`cannot write directory at ${destinationPath} because there is a ${destinationType}`);
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
try {
|
|
838
|
-
await mkdir(destinationPath, {
|
|
839
|
-
recursive
|
|
840
|
-
});
|
|
841
|
-
} catch (error) {
|
|
842
|
-
if (allowUseless && error.code === "EEXIST") {
|
|
843
|
-
return;
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
throw error;
|
|
847
|
-
}
|
|
848
|
-
};
|
|
849
|
-
|
|
850
|
-
const resolveUrl = (specifier, baseUrl) => {
|
|
851
|
-
if (typeof baseUrl === "undefined") {
|
|
852
|
-
throw new TypeError(`baseUrl missing to resolve ${specifier}`);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
return String(new URL(specifier, baseUrl));
|
|
856
|
-
};
|
|
857
|
-
|
|
858
|
-
const removeFileSystemNode = async (source, {
|
|
859
|
-
allowUseless = false,
|
|
860
|
-
recursive = false,
|
|
861
|
-
maxRetries = 3,
|
|
862
|
-
retryDelay = 100,
|
|
863
|
-
onlyContent = false
|
|
864
|
-
} = {}) => {
|
|
865
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
866
|
-
const sourceStats = await readFileSystemNodeStat(sourceUrl, {
|
|
867
|
-
nullIfNotFound: true,
|
|
868
|
-
followLink: false
|
|
869
|
-
});
|
|
870
|
-
|
|
871
|
-
if (!sourceStats) {
|
|
872
|
-
if (allowUseless) {
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`);
|
|
877
|
-
} // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats
|
|
878
|
-
// FIFO and socket are ignored, not sure what they are exactly and what to do with them
|
|
879
|
-
// other libraries ignore them, let's do the same.
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
if (sourceStats.isFile() || sourceStats.isSymbolicLink() || sourceStats.isCharacterDevice() || sourceStats.isBlockDevice()) {
|
|
883
|
-
await removeNonDirectory(sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl, {
|
|
884
|
-
maxRetries,
|
|
885
|
-
retryDelay
|
|
886
|
-
});
|
|
887
|
-
} else if (sourceStats.isDirectory()) {
|
|
888
|
-
await removeDirectory(ensureUrlTrailingSlash(sourceUrl), {
|
|
889
|
-
recursive,
|
|
890
|
-
maxRetries,
|
|
891
|
-
retryDelay,
|
|
892
|
-
onlyContent
|
|
893
|
-
});
|
|
894
|
-
}
|
|
895
|
-
};
|
|
896
|
-
|
|
897
|
-
const removeNonDirectory = (sourceUrl, {
|
|
898
|
-
maxRetries,
|
|
899
|
-
retryDelay
|
|
900
|
-
}) => {
|
|
901
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
902
|
-
let retryCount = 0;
|
|
903
|
-
|
|
904
|
-
const attempt = () => {
|
|
905
|
-
return unlinkNaive(sourcePath, { ...(retryCount >= maxRetries ? {} : {
|
|
906
|
-
handleTemporaryError: async () => {
|
|
907
|
-
retryCount++;
|
|
908
|
-
return new Promise(resolve => {
|
|
909
|
-
setTimeout(() => {
|
|
910
|
-
resolve(attempt());
|
|
911
|
-
}, retryCount * retryDelay);
|
|
912
|
-
});
|
|
913
|
-
}
|
|
914
|
-
})
|
|
915
|
-
});
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
return attempt();
|
|
919
|
-
};
|
|
920
|
-
|
|
921
|
-
const unlinkNaive = (sourcePath, {
|
|
922
|
-
handleTemporaryError = null
|
|
923
|
-
} = {}) => {
|
|
924
|
-
return new Promise((resolve, reject) => {
|
|
925
|
-
node_fs.unlink(sourcePath, error => {
|
|
926
|
-
if (error) {
|
|
927
|
-
if (error.code === "ENOENT") {
|
|
928
|
-
resolve();
|
|
929
|
-
} else if (handleTemporaryError && (error.code === "EBUSY" || error.code === "EMFILE" || error.code === "ENFILE" || error.code === "ENOENT")) {
|
|
930
|
-
resolve(handleTemporaryError(error));
|
|
931
|
-
} else {
|
|
932
|
-
reject(error);
|
|
933
|
-
}
|
|
934
|
-
} else {
|
|
935
|
-
resolve();
|
|
936
|
-
}
|
|
937
|
-
});
|
|
938
|
-
});
|
|
939
|
-
};
|
|
940
|
-
|
|
941
|
-
const removeDirectory = async (rootDirectoryUrl, {
|
|
942
|
-
maxRetries,
|
|
943
|
-
retryDelay,
|
|
944
|
-
recursive,
|
|
945
|
-
onlyContent
|
|
946
|
-
}) => {
|
|
947
|
-
const visit = async sourceUrl => {
|
|
948
|
-
const sourceStats = await readFileSystemNodeStat(sourceUrl, {
|
|
949
|
-
nullIfNotFound: true,
|
|
950
|
-
followLink: false
|
|
951
|
-
}); // file/directory not found
|
|
952
|
-
|
|
953
|
-
if (sourceStats === null) {
|
|
954
|
-
return;
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
if (sourceStats.isFile() || sourceStats.isCharacterDevice() || sourceStats.isBlockDevice()) {
|
|
958
|
-
await visitFile(sourceUrl);
|
|
959
|
-
} else if (sourceStats.isSymbolicLink()) {
|
|
960
|
-
await visitSymbolicLink(sourceUrl);
|
|
961
|
-
} else if (sourceStats.isDirectory()) {
|
|
962
|
-
await visitDirectory(`${sourceUrl}/`);
|
|
963
|
-
}
|
|
964
|
-
};
|
|
965
|
-
|
|
966
|
-
const visitDirectory = async directoryUrl => {
|
|
967
|
-
const directoryPath = urlToFileSystemPath(directoryUrl);
|
|
968
|
-
const optionsFromRecursive = recursive ? {
|
|
969
|
-
handleNotEmptyError: async () => {
|
|
970
|
-
await removeDirectoryContent(directoryUrl);
|
|
971
|
-
await visitDirectory(directoryUrl);
|
|
972
|
-
}
|
|
973
|
-
} : {};
|
|
974
|
-
await removeDirectoryNaive(directoryPath, { ...optionsFromRecursive,
|
|
975
|
-
// Workaround for https://github.com/joyent/node/issues/4337
|
|
976
|
-
...(process.platform === "win32" ? {
|
|
977
|
-
handlePermissionError: async error => {
|
|
978
|
-
console.error(`trying to fix windows EPERM after readir on ${directoryPath}`);
|
|
979
|
-
let openOrCloseError;
|
|
980
|
-
|
|
981
|
-
try {
|
|
982
|
-
const fd = node_fs.openSync(directoryPath);
|
|
983
|
-
node_fs.closeSync(fd);
|
|
984
|
-
} catch (e) {
|
|
985
|
-
openOrCloseError = e;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
if (openOrCloseError) {
|
|
989
|
-
if (openOrCloseError.code === "ENOENT") {
|
|
990
|
-
return;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
console.error(`error while trying to fix windows EPERM after readir on ${directoryPath}: ${openOrCloseError.stack}`);
|
|
994
|
-
throw error;
|
|
995
|
-
}
|
|
996
|
-
|
|
997
|
-
await removeDirectoryNaive(directoryPath, { ...optionsFromRecursive
|
|
998
|
-
});
|
|
999
|
-
}
|
|
1000
|
-
} : {})
|
|
1001
|
-
});
|
|
1002
|
-
};
|
|
1003
|
-
|
|
1004
|
-
const removeDirectoryContent = async directoryUrl => {
|
|
1005
|
-
const names = await readDirectory(directoryUrl);
|
|
1006
|
-
await Promise.all(names.map(async name => {
|
|
1007
|
-
const url = resolveUrl(name, directoryUrl);
|
|
1008
|
-
await visit(url);
|
|
1009
|
-
}));
|
|
1010
|
-
};
|
|
1011
|
-
|
|
1012
|
-
const visitFile = async fileUrl => {
|
|
1013
|
-
await removeNonDirectory(fileUrl, {
|
|
1014
|
-
maxRetries,
|
|
1015
|
-
retryDelay
|
|
1016
|
-
});
|
|
1017
|
-
};
|
|
1018
|
-
|
|
1019
|
-
const visitSymbolicLink = async symbolicLinkUrl => {
|
|
1020
|
-
await removeNonDirectory(symbolicLinkUrl, {
|
|
1021
|
-
maxRetries,
|
|
1022
|
-
retryDelay
|
|
1023
|
-
});
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
if (onlyContent) {
|
|
1027
|
-
await removeDirectoryContent(rootDirectoryUrl);
|
|
1028
|
-
} else {
|
|
1029
|
-
await visitDirectory(rootDirectoryUrl);
|
|
1030
|
-
}
|
|
1031
|
-
};
|
|
1032
|
-
|
|
1033
|
-
const removeDirectoryNaive = (directoryPath, {
|
|
1034
|
-
handleNotEmptyError = null,
|
|
1035
|
-
handlePermissionError = null
|
|
1036
|
-
} = {}) => {
|
|
1037
|
-
return new Promise((resolve, reject) => {
|
|
1038
|
-
node_fs.rmdir(directoryPath, (error, lstatObject) => {
|
|
1039
|
-
if (error) {
|
|
1040
|
-
if (handlePermissionError && error.code === "EPERM") {
|
|
1041
|
-
resolve(handlePermissionError(error));
|
|
1042
|
-
} else if (error.code === "ENOENT") {
|
|
1043
|
-
resolve();
|
|
1044
|
-
} else if (handleNotEmptyError && (error.code === "ENOTEMPTY" || // SunOS
|
|
1045
|
-
error.code === "EEXIST")) {
|
|
1046
|
-
resolve(handleNotEmptyError(error));
|
|
1047
|
-
} else {
|
|
1048
|
-
reject(error);
|
|
1049
|
-
}
|
|
1050
|
-
} else {
|
|
1051
|
-
resolve(lstatObject);
|
|
1052
|
-
}
|
|
1053
|
-
});
|
|
1054
|
-
});
|
|
1055
|
-
};
|
|
1056
|
-
|
|
1057
|
-
const ensureEmptyDirectory = async source => {
|
|
1058
|
-
const stats = await readFileSystemNodeStat(source, {
|
|
1059
|
-
nullIfNotFound: true,
|
|
1060
|
-
followLink: false
|
|
1061
|
-
});
|
|
1062
|
-
|
|
1063
|
-
if (stats === null) {
|
|
1064
|
-
// if there is nothing, create a directory
|
|
1065
|
-
return writeDirectory(source, {
|
|
1066
|
-
allowUseless: true
|
|
1067
|
-
});
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
if (stats.isDirectory()) {
|
|
1071
|
-
// if there is a directory remove its content and done
|
|
1072
|
-
return removeFileSystemNode(source, {
|
|
1073
|
-
allowUseless: true,
|
|
1074
|
-
recursive: true,
|
|
1075
|
-
onlyContent: true
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
const sourceType = statsToType(stats);
|
|
1080
|
-
const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source));
|
|
1081
|
-
throw new Error(`ensureEmptyDirectory expect directory at ${sourcePath}, found ${sourceType} instead`);
|
|
1082
|
-
};
|
|
1083
|
-
|
|
1084
|
-
const ensureParentDirectories = async destination => {
|
|
1085
|
-
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
1086
|
-
const destinationPath = urlToFileSystemPath(destinationUrl);
|
|
1087
|
-
const destinationParentPath = path.dirname(destinationPath);
|
|
1088
|
-
return writeDirectory(destinationParentPath, {
|
|
1089
|
-
recursive: true,
|
|
1090
|
-
allowUseless: true
|
|
1091
|
-
});
|
|
1092
|
-
};
|
|
1093
|
-
|
|
1094
|
-
const isWindows$2 = process.platform === "win32";
|
|
1095
|
-
const baseUrlFallback = fileSystemPathToUrl(process.cwd());
|
|
1096
|
-
/**
|
|
1097
|
-
* Some url might be resolved or remapped to url without the windows drive letter.
|
|
1098
|
-
* For instance
|
|
1099
|
-
* new URL('/foo.js', 'file:///C:/dir/file.js')
|
|
1100
|
-
* resolves to
|
|
1101
|
-
* 'file:///foo.js'
|
|
1102
|
-
*
|
|
1103
|
-
* But on windows it becomes a problem because we need the drive letter otherwise
|
|
1104
|
-
* url cannot be converted to a filesystem path.
|
|
1105
|
-
*
|
|
1106
|
-
* ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.
|
|
1107
|
-
*/
|
|
1108
|
-
|
|
1109
|
-
const ensureWindowsDriveLetter = (url, baseUrl) => {
|
|
1110
|
-
try {
|
|
1111
|
-
url = String(new URL(url));
|
|
1112
|
-
} catch (e) {
|
|
1113
|
-
throw new Error(`absolute url expected but got ${url}`);
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
if (!isWindows$2) {
|
|
1117
|
-
return url;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
try {
|
|
1121
|
-
baseUrl = String(new URL(baseUrl));
|
|
1122
|
-
} catch (e) {
|
|
1123
|
-
throw new Error(`absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`);
|
|
1124
|
-
}
|
|
1125
|
-
|
|
1126
|
-
if (!url.startsWith("file://")) {
|
|
1127
|
-
return url;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
const afterProtocol = url.slice("file://".length); // we still have the windows drive letter
|
|
1131
|
-
|
|
1132
|
-
if (extractDriveLetter(afterProtocol)) {
|
|
1133
|
-
return url;
|
|
1134
|
-
} // drive letter was lost, restore it
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
const baseUrlOrFallback = baseUrl.startsWith("file://") ? baseUrl : baseUrlFallback;
|
|
1138
|
-
const driveLetter = extractDriveLetter(baseUrlOrFallback.slice("file://".length));
|
|
1139
|
-
|
|
1140
|
-
if (!driveLetter) {
|
|
1141
|
-
throw new Error(`drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`);
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
return `file:///${driveLetter}:${afterProtocol}`;
|
|
1145
|
-
};
|
|
1146
|
-
|
|
1147
|
-
const extractDriveLetter = ressource => {
|
|
1148
|
-
// we still have the windows drive letter
|
|
1149
|
-
if (/[a-zA-Z]/.test(ressource[1]) && ressource[2] === ":") {
|
|
1150
|
-
return ressource[1];
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
return null;
|
|
1154
|
-
};
|
|
1155
|
-
|
|
1156
|
-
const urlTargetsSameFileSystemPath = (leftUrl, rightUrl) => {
|
|
1157
|
-
if (leftUrl.endsWith("/")) leftUrl = leftUrl.slice(0, -1);
|
|
1158
|
-
if (rightUrl.endsWith("/")) rightUrl = rightUrl.slice(0, -1);
|
|
1159
|
-
return leftUrl === rightUrl;
|
|
1160
|
-
};
|
|
1161
|
-
|
|
1162
|
-
const writeFileSystemNodeModificationTime = (source, mtime) => {
|
|
1163
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
1164
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
1165
|
-
const mtimeValue = typeof mtime === "number" ? new Date(Math.floor(mtime)) : mtime; // reading atime mutates its value so there is no use case I can think of
|
|
1166
|
-
// where we want to modify it
|
|
1167
|
-
|
|
1168
|
-
const atimeValue = mtimeValue;
|
|
1169
|
-
return new Promise((resolve, reject) => {
|
|
1170
|
-
fs.utimes(sourcePath, atimeValue, mtimeValue, error => {
|
|
1171
|
-
if (error) {
|
|
1172
|
-
reject(error);
|
|
1173
|
-
} else {
|
|
1174
|
-
resolve();
|
|
1175
|
-
}
|
|
1176
|
-
});
|
|
1177
|
-
});
|
|
1178
|
-
};
|
|
1179
|
-
|
|
1180
|
-
const replaceBackSlashesWithSlashes = string => string.replace(/\\/g, "/");
|
|
1181
|
-
|
|
1182
|
-
/*
|
|
1183
|
-
* - symlink documentation on Node.js:
|
|
1184
|
-
* https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_fs_symlink_target_path_type_callback
|
|
1185
|
-
*/
|
|
1186
|
-
const readSymbolicLink = url => {
|
|
1187
|
-
const symbolicLinkUrl = assertAndNormalizeFileUrl(url);
|
|
1188
|
-
const symbolicLinkPath = urlToFileSystemPath(symbolicLinkUrl);
|
|
1189
|
-
return new Promise((resolve, reject) => {
|
|
1190
|
-
node_fs.readlink(symbolicLinkPath, (error, resolvedPath) => {
|
|
1191
|
-
if (error) {
|
|
1192
|
-
reject(error);
|
|
1193
|
-
} else {
|
|
1194
|
-
resolve(isFileSystemPath(resolvedPath) ? fileSystemPathToUrl(resolvedPath) : replaceBackSlashesWithSlashes(resolvedPath));
|
|
1195
|
-
}
|
|
1196
|
-
});
|
|
1197
|
-
});
|
|
1198
|
-
};
|
|
1199
|
-
|
|
1200
|
-
const {
|
|
1201
|
-
symlink
|
|
1202
|
-
} = node_fs.promises;
|
|
1203
|
-
const isWindows$1 = process.platform === "win32";
|
|
1204
|
-
/**
|
|
1205
|
-
* Writes a symbolic link pointing from a filesystem node to an other
|
|
1206
|
-
* @param {string} from Where symlink is written
|
|
1207
|
-
* @param {string} to The symlink target
|
|
1208
|
-
* @param {"file" | "dir"} [type] Symlink type if you know it before hand
|
|
1209
|
-
* @param {boolean} [allowUseless=false] Prevent error when symlink already exists with the same target
|
|
1210
|
-
* @param {boolean} [allowOverwrite=false] Will replace any existing symlink
|
|
1211
|
-
*/
|
|
1212
|
-
|
|
1213
|
-
const writeSymbolicLink = async ({
|
|
1214
|
-
from,
|
|
1215
|
-
to,
|
|
1216
|
-
type,
|
|
1217
|
-
allowUseless = false,
|
|
1218
|
-
allowOverwrite = false
|
|
1219
|
-
}) => {
|
|
1220
|
-
const fromUrl = assertAndNormalizeFileUrl(from);
|
|
1221
|
-
const toInfo = getToInfo(to, fromUrl);
|
|
1222
|
-
|
|
1223
|
-
if (isWindows$1 && typeof type === "undefined") {
|
|
1224
|
-
// without this if you write a symbolic link without specifying the type on windows
|
|
1225
|
-
// you later get EPERM when doing stat on the symlink
|
|
1226
|
-
const toStats = await readFileSystemNodeStat(toInfo.url, {
|
|
1227
|
-
nullIfNotFound: true
|
|
1228
|
-
});
|
|
1229
|
-
type = toStats && toStats.isDirectory() ? "dir" : "file";
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
const symbolicLinkPath = urlToFileSystemPath(fromUrl);
|
|
1233
|
-
|
|
1234
|
-
try {
|
|
1235
|
-
await symlink(toInfo.value, symbolicLinkPath, type);
|
|
1236
|
-
} catch (error) {
|
|
1237
|
-
if (error.code === "ENOENT") {
|
|
1238
|
-
await ensureParentDirectories(fromUrl);
|
|
1239
|
-
await symlink(toInfo.value, symbolicLinkPath, type);
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
|
|
1243
|
-
if (error.code === "EEXIST") {
|
|
1244
|
-
if (allowUseless) {
|
|
1245
|
-
const existingSymbolicLinkUrl = await readSymbolicLink(fromUrl);
|
|
1246
|
-
|
|
1247
|
-
if (existingSymbolicLinkUrl === toInfo.url) {
|
|
1248
|
-
return;
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
if (allowOverwrite) {
|
|
1253
|
-
await removeFileSystemNode(fromUrl);
|
|
1254
|
-
await symlink(toInfo.value, symbolicLinkPath, type);
|
|
1255
|
-
return;
|
|
1256
|
-
}
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
throw error;
|
|
1260
|
-
}
|
|
1261
|
-
};
|
|
1262
|
-
|
|
1263
|
-
const getToInfo = (to, fromUrl) => {
|
|
1264
|
-
if (typeof to === "string") {
|
|
1265
|
-
// absolute filesystem path
|
|
1266
|
-
if (isFileSystemPath(to)) {
|
|
1267
|
-
const url = fileSystemPathToUrl(to);
|
|
1268
|
-
const value = to;
|
|
1269
|
-
return {
|
|
1270
|
-
url,
|
|
1271
|
-
value
|
|
1272
|
-
};
|
|
1273
|
-
} // relative url
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
if (to.startsWith("./") || to.startsWith("../")) {
|
|
1277
|
-
const url = resolveUrl(to, fromUrl);
|
|
1278
|
-
const value = to;
|
|
1279
|
-
return {
|
|
1280
|
-
url,
|
|
1281
|
-
value
|
|
1282
|
-
};
|
|
1283
|
-
} // absolute url
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
const url = resolveUrl(to, fromUrl);
|
|
1287
|
-
const value = urlToFileSystemPath(url);
|
|
1288
|
-
return {
|
|
1289
|
-
url,
|
|
1290
|
-
value
|
|
1291
|
-
};
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
if (to instanceof URL) {
|
|
1295
|
-
const url = String(to);
|
|
1296
|
-
const value = urlToFileSystemPath(url);
|
|
1297
|
-
return {
|
|
1298
|
-
url,
|
|
1299
|
-
value
|
|
1300
|
-
};
|
|
1301
|
-
}
|
|
1302
|
-
|
|
1303
|
-
throw new TypeError(`symbolic link to must be a string or an url, received ${to}`);
|
|
1304
|
-
};
|
|
1305
|
-
|
|
1306
|
-
const urlIsInsideOf = (url, otherUrl) => {
|
|
1307
|
-
const urlObject = new URL(url);
|
|
1308
|
-
const otherUrlObject = new URL(otherUrl);
|
|
1309
|
-
|
|
1310
|
-
if (urlObject.origin !== otherUrlObject.origin) {
|
|
1311
|
-
return false;
|
|
1312
|
-
}
|
|
1313
|
-
|
|
1314
|
-
const urlPathname = urlObject.pathname;
|
|
1315
|
-
const otherUrlPathname = otherUrlObject.pathname;
|
|
1316
|
-
|
|
1317
|
-
if (urlPathname === otherUrlPathname) {
|
|
1318
|
-
return false;
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
const isInside = urlPathname.startsWith(otherUrlPathname);
|
|
1322
|
-
return isInside;
|
|
1323
|
-
};
|
|
1324
|
-
|
|
1325
|
-
/* eslint-disable import/max-dependencies */
|
|
1326
|
-
const copyFileSystemNode = async ({
|
|
1327
|
-
from,
|
|
1328
|
-
to,
|
|
1329
|
-
overwrite = false,
|
|
1330
|
-
preserveStat = true,
|
|
1331
|
-
preserveMtime = preserveStat,
|
|
1332
|
-
preservePermissions = preserveStat,
|
|
1333
|
-
allowUseless = false,
|
|
1334
|
-
followLink = true
|
|
1335
|
-
}) => {
|
|
1336
|
-
const fromUrl = assertAndNormalizeFileUrl(from);
|
|
1337
|
-
const fromPath = urlToFileSystemPath(fromUrl);
|
|
1338
|
-
let toUrl = assertAndNormalizeFileUrl(to);
|
|
1339
|
-
let toPath = urlToFileSystemPath(toUrl);
|
|
1340
|
-
const sourceStats = await readFileSystemNodeStat(fromUrl, {
|
|
1341
|
-
nullIfNotFound: true,
|
|
1342
|
-
followLink: false
|
|
1343
|
-
});
|
|
1344
|
-
|
|
1345
|
-
if (!sourceStats) {
|
|
1346
|
-
throw new Error(`nothing to copy at ${fromPath}`);
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
let destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1350
|
-
nullIfNotFound: true,
|
|
1351
|
-
// we force false here but in fact we will follow the destination link
|
|
1352
|
-
// to know where we will actually move and detect useless move overrite etc..
|
|
1353
|
-
followLink: false
|
|
1354
|
-
});
|
|
1355
|
-
|
|
1356
|
-
if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
|
|
1357
|
-
const linkTarget = await readSymbolicLink(toUrl);
|
|
1358
|
-
toUrl = resolveUrl(linkTarget, toUrl);
|
|
1359
|
-
toPath = urlToFileSystemPath(toUrl);
|
|
1360
|
-
destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1361
|
-
nullIfNotFound: true
|
|
1362
|
-
});
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
|
|
1366
|
-
if (allowUseless) {
|
|
1367
|
-
return;
|
|
1368
|
-
}
|
|
1369
|
-
|
|
1370
|
-
throw new Error(`cannot copy ${fromPath} because destination and source are the same`);
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
if (destinationStats) {
|
|
1374
|
-
const sourceType = statsToType(sourceStats);
|
|
1375
|
-
const destinationType = statsToType(destinationStats);
|
|
1376
|
-
|
|
1377
|
-
if (sourceType !== destinationType) {
|
|
1378
|
-
throw new Error(`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`);
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
if (!overwrite) {
|
|
1382
|
-
throw new Error(`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`);
|
|
1383
|
-
} // remove file, link, directory...
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
await removeFileSystemNode(toUrl, {
|
|
1387
|
-
recursive: true,
|
|
1388
|
-
allowUseless: true
|
|
1389
|
-
});
|
|
1390
|
-
} else {
|
|
1391
|
-
await ensureParentDirectories(toUrl);
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
if (sourceStats.isDirectory()) {
|
|
1395
|
-
toUrl = ensureUrlTrailingSlash(toUrl);
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
const visit = async (url, stats) => {
|
|
1399
|
-
if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
|
|
1400
|
-
await visitFile(url, stats);
|
|
1401
|
-
} else if (stats.isSymbolicLink()) {
|
|
1402
|
-
await visitSymbolicLink(url);
|
|
1403
|
-
} else if (stats.isDirectory()) {
|
|
1404
|
-
await visitDirectory(ensureUrlTrailingSlash(url), stats);
|
|
1405
|
-
}
|
|
1406
|
-
};
|
|
1407
|
-
|
|
1408
|
-
const visitFile = async (fileUrl, fileStats) => {
|
|
1409
|
-
const fileRelativeUrl = urlToRelativeUrl(fileUrl, fromUrl);
|
|
1410
|
-
const fileCopyUrl = resolveUrl(fileRelativeUrl, toUrl);
|
|
1411
|
-
await copyFileContentNaive(urlToFileSystemPath(fileUrl), urlToFileSystemPath(fileCopyUrl));
|
|
1412
|
-
await copyStats(fileCopyUrl, fileStats);
|
|
1413
|
-
};
|
|
1414
|
-
|
|
1415
|
-
const visitSymbolicLink = async symbolicLinkUrl => {
|
|
1416
|
-
const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, fromUrl);
|
|
1417
|
-
const symbolicLinkTarget = await readSymbolicLink(symbolicLinkUrl);
|
|
1418
|
-
const symbolicLinkTargetUrl = resolveUrl(symbolicLinkTarget, symbolicLinkUrl);
|
|
1419
|
-
const linkIsRelative = symbolicLinkTarget.startsWith("./") || symbolicLinkTarget.startsWith("../");
|
|
1420
|
-
let symbolicLinkCopyTarget;
|
|
1421
|
-
|
|
1422
|
-
if (symbolicLinkTargetUrl === fromUrl) {
|
|
1423
|
-
symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : toUrl;
|
|
1424
|
-
} else if (urlIsInsideOf(symbolicLinkTargetUrl, fromUrl)) {
|
|
1425
|
-
// symbolic link targets something inside the directory we want to copy
|
|
1426
|
-
// reflects it inside the copied directory structure
|
|
1427
|
-
const linkCopyTargetRelative = urlToRelativeUrl(symbolicLinkTargetUrl, fromUrl);
|
|
1428
|
-
symbolicLinkCopyTarget = linkIsRelative ? `./${linkCopyTargetRelative}` : resolveUrl(linkCopyTargetRelative, toUrl);
|
|
1429
|
-
} else {
|
|
1430
|
-
// symbolic link targets something outside the directory we want to copy
|
|
1431
|
-
symbolicLinkCopyTarget = symbolicLinkTarget;
|
|
1432
|
-
} // we must guess ourself the type of the symlink
|
|
1433
|
-
// because the destination might not exists because not yet copied
|
|
1434
|
-
// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fs_symlink_target_path_type_callback
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
const targetStats = await readFileSystemNodeStat(symbolicLinkTargetUrl, {
|
|
1438
|
-
nullIfNotFound: true,
|
|
1439
|
-
followLink: false
|
|
1440
|
-
});
|
|
1441
|
-
const linkType = targetStats && targetStats.isDirectory() ? "dir" : "file";
|
|
1442
|
-
const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, toUrl);
|
|
1443
|
-
await writeSymbolicLink({
|
|
1444
|
-
from: symbolicLinkCopyUrl,
|
|
1445
|
-
to: symbolicLinkCopyTarget,
|
|
1446
|
-
type: linkType
|
|
1447
|
-
});
|
|
1448
|
-
};
|
|
1449
|
-
|
|
1450
|
-
const copyStats = async (toUrl, stats) => {
|
|
1451
|
-
if (preservePermissions || preserveMtime) {
|
|
1452
|
-
const {
|
|
1453
|
-
mode,
|
|
1454
|
-
mtimeMs
|
|
1455
|
-
} = stats;
|
|
1456
|
-
|
|
1457
|
-
if (preservePermissions) {
|
|
1458
|
-
await writeFileSystemNodePermissions(toUrl, binaryFlagsToPermissions(mode));
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
|
-
if (preserveMtime) {
|
|
1462
|
-
await writeFileSystemNodeModificationTime(toUrl, mtimeMs);
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
};
|
|
1466
|
-
|
|
1467
|
-
const visitDirectory = async (directoryUrl, directoryStats) => {
|
|
1468
|
-
const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, fromUrl);
|
|
1469
|
-
const directoryCopyUrl = resolveUrl(directoryRelativeUrl, toUrl);
|
|
1470
|
-
await writeDirectory(directoryCopyUrl);
|
|
1471
|
-
await copyDirectoryContent(directoryUrl);
|
|
1472
|
-
await copyStats(directoryCopyUrl, directoryStats);
|
|
1473
|
-
};
|
|
1474
|
-
|
|
1475
|
-
const copyDirectoryContent = async directoryUrl => {
|
|
1476
|
-
const names = await readDirectory(directoryUrl);
|
|
1477
|
-
await Promise.all(names.map(async name => {
|
|
1478
|
-
const fileSystemNodeUrl = resolveUrl(name, directoryUrl);
|
|
1479
|
-
const stats = await readFileSystemNodeStat(fileSystemNodeUrl, {
|
|
1480
|
-
followLink: false
|
|
1481
|
-
});
|
|
1482
|
-
await visit(fileSystemNodeUrl, stats);
|
|
1483
|
-
}));
|
|
1484
|
-
};
|
|
1485
|
-
|
|
1486
|
-
await visit(fromUrl, sourceStats);
|
|
1487
|
-
};
|
|
1488
|
-
|
|
1489
|
-
const copyFileContentNaive = (filePath, fileDestinationPath) => {
|
|
1490
|
-
return new Promise((resolve, reject) => {
|
|
1491
|
-
node_fs.copyFile(filePath, fileDestinationPath, error => {
|
|
1492
|
-
if (error) {
|
|
1493
|
-
reject(error);
|
|
1494
|
-
} else {
|
|
1495
|
-
resolve();
|
|
1496
|
-
}
|
|
1497
|
-
});
|
|
1498
|
-
});
|
|
1499
|
-
};
|
|
1500
|
-
|
|
1501
|
-
const copyDirectoryContent = async ({
|
|
1502
|
-
from,
|
|
1503
|
-
to,
|
|
1504
|
-
overwrite,
|
|
1505
|
-
followLink = true
|
|
1506
|
-
} = {}) => {
|
|
1507
|
-
const fromUrl = assertAndNormalizeDirectoryUrl(from);
|
|
1508
|
-
const fromPath = urlToFileSystemPath(fromUrl);
|
|
1509
|
-
let toUrl = assertAndNormalizeDirectoryUrl(to);
|
|
1510
|
-
let toPath = urlToFileSystemPath(toUrl);
|
|
1511
|
-
const sourceStats = await readFileSystemNodeStat(fromUrl, {
|
|
1512
|
-
nullIfNotFound: true,
|
|
1513
|
-
followLink: false
|
|
1514
|
-
});
|
|
1515
|
-
|
|
1516
|
-
if (!sourceStats) {
|
|
1517
|
-
throw new Error(`no directory to copy content from at ${fromPath}`);
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
if (!sourceStats.isDirectory()) {
|
|
1521
|
-
const sourceType = statsToType(sourceStats);
|
|
1522
|
-
throw new Error(`found a ${sourceType} instead of a directory at ${fromPath}`);
|
|
1523
|
-
}
|
|
1524
|
-
|
|
1525
|
-
let destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1526
|
-
nullIfNotFound: true,
|
|
1527
|
-
// we force false here but in fact we will follow the destination link
|
|
1528
|
-
// to know where we will actually move and detect useless move overrite etc..
|
|
1529
|
-
followLink: false
|
|
1530
|
-
});
|
|
1531
|
-
|
|
1532
|
-
if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
|
|
1533
|
-
const linkTarget = await readSymbolicLink(toUrl);
|
|
1534
|
-
toUrl = resolveUrl(linkTarget, toUrl);
|
|
1535
|
-
toPath = urlToFileSystemPath(toUrl);
|
|
1536
|
-
destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1537
|
-
nullIfNotFound: true
|
|
1538
|
-
});
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
if (destinationStats === null) {
|
|
1542
|
-
throw new Error(`no directory to copy content into at ${toPath}`);
|
|
1543
|
-
}
|
|
1544
|
-
|
|
1545
|
-
if (!destinationStats.isDirectory()) {
|
|
1546
|
-
const destinationType = statsToType(destinationStats);
|
|
1547
|
-
throw new Error(`destination leads to a ${destinationType} instead of a directory at ${toPath}`);
|
|
1548
|
-
}
|
|
1549
|
-
|
|
1550
|
-
if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
|
|
1551
|
-
throw new Error(`cannot copy directory content, source and destination are the same (${fromPath})`);
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
const directoryEntries = await readDirectory(fromUrl);
|
|
1555
|
-
await Promise.all(directoryEntries.map(async directoryEntry => {
|
|
1556
|
-
const from = resolveUrl(directoryEntry, fromUrl);
|
|
1557
|
-
const to = resolveUrl(directoryEntry, toUrl);
|
|
1558
|
-
await copyFileSystemNode({
|
|
1559
|
-
from,
|
|
1560
|
-
to,
|
|
1561
|
-
overwrite,
|
|
1562
|
-
followLink
|
|
1563
|
-
});
|
|
1564
|
-
}));
|
|
1565
|
-
};
|
|
1566
|
-
|
|
1567
|
-
/*
|
|
1568
|
-
* - file modes documentation on Node.js
|
|
1569
|
-
* https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_file_modes
|
|
1570
|
-
*/
|
|
1571
|
-
const {
|
|
1572
|
-
stat
|
|
1573
|
-
} = node_fs.promises;
|
|
1574
|
-
const readFileSystemNodePermissions = async source => {
|
|
1575
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
1576
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
1577
|
-
const {
|
|
1578
|
-
mode
|
|
1579
|
-
} = await stat(sourcePath);
|
|
1580
|
-
return binaryFlagsToPermissions(mode);
|
|
1581
|
-
};
|
|
1582
|
-
|
|
1583
|
-
const grantPermissionsOnFileSystemNode = async (source, {
|
|
1584
|
-
read = false,
|
|
1585
|
-
write = false,
|
|
1586
|
-
execute = false
|
|
1587
|
-
}) => {
|
|
1588
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
1589
|
-
const filePermissions = await readFileSystemNodePermissions(sourceUrl);
|
|
1590
|
-
await writeFileSystemNodePermissions(sourceUrl, {
|
|
1591
|
-
owner: {
|
|
1592
|
-
read,
|
|
1593
|
-
write,
|
|
1594
|
-
execute
|
|
1595
|
-
},
|
|
1596
|
-
group: {
|
|
1597
|
-
read,
|
|
1598
|
-
write,
|
|
1599
|
-
execute
|
|
1600
|
-
},
|
|
1601
|
-
others: {
|
|
1602
|
-
read,
|
|
1603
|
-
write,
|
|
1604
|
-
execute
|
|
1605
|
-
}
|
|
1606
|
-
});
|
|
1607
|
-
return async () => {
|
|
1608
|
-
await writeFileSystemNodePermissions(sourceUrl, filePermissions);
|
|
1609
|
-
};
|
|
1610
|
-
};
|
|
1611
|
-
|
|
1612
|
-
const listFilesMatching = async (patterns, directoryUrl = process.cwd()) => {
|
|
1613
|
-
if (typeof patterns === "string") {
|
|
1614
|
-
patterns = [patterns];
|
|
1615
|
-
} else if (Array.isArray(patterns)) {
|
|
1616
|
-
throw new TypeError(`patterns must be a string or an array of strings, got ${patterns}`);
|
|
1617
|
-
}
|
|
1618
|
-
|
|
1619
|
-
const patternMap = {};
|
|
1620
|
-
patterns.forEach((pattern, index) => {
|
|
1621
|
-
if (typeof pattern !== "string") {
|
|
1622
|
-
throw new TypeError(`every pattern must be a string, found ${pattern} at index ${index}`);
|
|
1623
|
-
}
|
|
1624
|
-
|
|
1625
|
-
patternMap[pattern] = true;
|
|
1626
|
-
});
|
|
1627
|
-
const fileDatas = await collectFiles({
|
|
1628
|
-
directoryUrl,
|
|
1629
|
-
structuredMetaMap: {
|
|
1630
|
-
matches: patternMap
|
|
1631
|
-
},
|
|
1632
|
-
predicate: ({
|
|
1633
|
-
matches
|
|
1634
|
-
}) => matches
|
|
1635
|
-
});
|
|
1636
|
-
return fileDatas.map(({
|
|
1637
|
-
url
|
|
1638
|
-
}) => url);
|
|
1639
|
-
};
|
|
1640
|
-
|
|
1641
|
-
const memoize = compute => {
|
|
1642
|
-
let memoized = false;
|
|
1643
|
-
let memoizedValue;
|
|
1644
|
-
|
|
1645
|
-
const fnWithMemoization = (...args) => {
|
|
1646
|
-
if (memoized) {
|
|
1647
|
-
return memoizedValue;
|
|
1648
|
-
} // if compute is recursive wait for it to be fully done before storing the value
|
|
1649
|
-
// so set memoized boolean after the call
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
memoizedValue = compute(...args);
|
|
1653
|
-
memoized = true;
|
|
1654
|
-
return memoizedValue;
|
|
1655
|
-
};
|
|
1656
|
-
|
|
1657
|
-
fnWithMemoization.forget = () => {
|
|
1658
|
-
const value = memoizedValue;
|
|
1659
|
-
memoized = false;
|
|
1660
|
-
memoizedValue = undefined;
|
|
1661
|
-
return value;
|
|
1662
|
-
};
|
|
1663
|
-
|
|
1664
|
-
return fnWithMemoization;
|
|
1665
|
-
};
|
|
1666
|
-
|
|
1667
|
-
/* eslint-disable import/max-dependencies */
|
|
1668
|
-
const moveFileSystemNode = async ({
|
|
1669
|
-
from,
|
|
1670
|
-
to,
|
|
1671
|
-
overwrite = false,
|
|
1672
|
-
allowUseless = false,
|
|
1673
|
-
followLink = true
|
|
1674
|
-
}) => {
|
|
1675
|
-
const fromUrl = assertAndNormalizeFileUrl(from);
|
|
1676
|
-
const fromPath = urlToFileSystemPath(fromUrl);
|
|
1677
|
-
let toUrl = assertAndNormalizeFileUrl(to);
|
|
1678
|
-
let toPath = urlToFileSystemPath(toUrl);
|
|
1679
|
-
const sourceStats = await readFileSystemNodeStat(fromUrl, {
|
|
1680
|
-
nullIfNotFound: true,
|
|
1681
|
-
followLink: false
|
|
1682
|
-
});
|
|
1683
|
-
|
|
1684
|
-
if (!sourceStats) {
|
|
1685
|
-
throw new Error(`nothing to move from ${fromPath}`);
|
|
1686
|
-
}
|
|
1687
|
-
|
|
1688
|
-
let destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1689
|
-
nullIfNotFound: true,
|
|
1690
|
-
// we force false here but in fact we will follow the destination link
|
|
1691
|
-
// to know where we will actually move and detect useless move overrite etc..
|
|
1692
|
-
followLink: false
|
|
1693
|
-
});
|
|
1694
|
-
|
|
1695
|
-
if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
|
|
1696
|
-
const linkTarget = await readSymbolicLink(toUrl);
|
|
1697
|
-
toUrl = resolveUrl(linkTarget, toUrl);
|
|
1698
|
-
toPath = urlToFileSystemPath(toUrl);
|
|
1699
|
-
destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1700
|
-
nullIfNotFound: true
|
|
1701
|
-
});
|
|
1702
|
-
}
|
|
1703
|
-
|
|
1704
|
-
if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
|
|
1705
|
-
if (allowUseless) {
|
|
1706
|
-
return;
|
|
1707
|
-
}
|
|
1708
|
-
|
|
1709
|
-
throw new Error(`no move needed for ${fromPath} because destination and source are the same`);
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
|
-
if (destinationStats) {
|
|
1713
|
-
const sourceType = statsToType(sourceStats);
|
|
1714
|
-
const destinationType = statsToType(destinationStats);
|
|
1715
|
-
|
|
1716
|
-
if (sourceType !== destinationType) {
|
|
1717
|
-
throw new Error(`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`);
|
|
1718
|
-
}
|
|
1719
|
-
|
|
1720
|
-
if (!overwrite) {
|
|
1721
|
-
throw new Error(`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`);
|
|
1722
|
-
} // remove file, link, directory...
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
await removeFileSystemNode(toUrl, {
|
|
1726
|
-
recursive: true
|
|
1727
|
-
});
|
|
1728
|
-
} else {
|
|
1729
|
-
await ensureParentDirectories(toUrl);
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
await moveNaive(fromPath, toPath, {
|
|
1733
|
-
handleCrossDeviceError: async () => {
|
|
1734
|
-
await copyFileSystemNode({
|
|
1735
|
-
from: fromUrl,
|
|
1736
|
-
to: toUrl,
|
|
1737
|
-
preserveStat: true
|
|
1738
|
-
});
|
|
1739
|
-
await removeFileSystemNode(fromUrl, {
|
|
1740
|
-
recursive: true
|
|
1741
|
-
});
|
|
1742
|
-
}
|
|
1743
|
-
});
|
|
1744
|
-
};
|
|
1745
|
-
|
|
1746
|
-
const moveNaive = (fromPath, destinationPath, {
|
|
1747
|
-
handleCrossDeviceError = null
|
|
1748
|
-
} = {}) => {
|
|
1749
|
-
return new Promise((resolve, reject) => {
|
|
1750
|
-
node_fs.rename(fromPath, destinationPath, error => {
|
|
1751
|
-
if (error) {
|
|
1752
|
-
if (handleCrossDeviceError && error.code === "EXDEV") {
|
|
1753
|
-
resolve(handleCrossDeviceError(error));
|
|
1754
|
-
} else {
|
|
1755
|
-
reject(error);
|
|
1756
|
-
}
|
|
1757
|
-
} else {
|
|
1758
|
-
resolve();
|
|
1759
|
-
}
|
|
1760
|
-
});
|
|
1761
|
-
});
|
|
1762
|
-
};
|
|
1763
|
-
|
|
1764
|
-
const moveDirectoryContent = async ({
|
|
1765
|
-
from,
|
|
1766
|
-
to,
|
|
1767
|
-
overwrite,
|
|
1768
|
-
followLink = true
|
|
1769
|
-
} = {}) => {
|
|
1770
|
-
const fromUrl = assertAndNormalizeDirectoryUrl(from);
|
|
1771
|
-
const fromPath = urlToFileSystemPath(fromUrl);
|
|
1772
|
-
let toUrl = assertAndNormalizeDirectoryUrl(to);
|
|
1773
|
-
let toPath = urlToFileSystemPath(toUrl);
|
|
1774
|
-
const sourceStats = await readFileSystemNodeStat(fromUrl, {
|
|
1775
|
-
nullIfNotFound: true,
|
|
1776
|
-
followLink: false
|
|
1777
|
-
});
|
|
1778
|
-
|
|
1779
|
-
if (!sourceStats) {
|
|
1780
|
-
throw new Error(`no directory to move content from at ${fromPath}`);
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
if (!sourceStats.isDirectory()) {
|
|
1784
|
-
const sourceType = statsToType(sourceStats);
|
|
1785
|
-
throw new Error(`found a ${sourceType} instead of a directory at ${fromPath}`);
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
let destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1789
|
-
nullIfNotFound: true,
|
|
1790
|
-
// we force false here but in fact we will follow the destination link
|
|
1791
|
-
// to know where we will actually move and detect useless move overrite etc..
|
|
1792
|
-
followLink: false
|
|
1793
|
-
});
|
|
1794
|
-
|
|
1795
|
-
if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
|
|
1796
|
-
const linkTarget = await readSymbolicLink(toUrl);
|
|
1797
|
-
toUrl = resolveUrl(linkTarget, toUrl);
|
|
1798
|
-
toPath = urlToFileSystemPath(toUrl);
|
|
1799
|
-
destinationStats = await readFileSystemNodeStat(toUrl, {
|
|
1800
|
-
nullIfNotFound: true
|
|
1801
|
-
});
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
if (destinationStats === null) {
|
|
1805
|
-
throw new Error(`no directory to move content into at ${toPath}`);
|
|
1806
|
-
}
|
|
1807
|
-
|
|
1808
|
-
if (!destinationStats.isDirectory()) {
|
|
1809
|
-
const destinationType = statsToType(destinationStats);
|
|
1810
|
-
throw new Error(`destination leads to a ${destinationType} instead of a directory at ${toPath}`);
|
|
1811
|
-
}
|
|
1812
|
-
|
|
1813
|
-
if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
|
|
1814
|
-
throw new Error(`cannot move directory content, source and destination are the same (${fromPath})`);
|
|
1815
|
-
}
|
|
1816
|
-
|
|
1817
|
-
const directoryEntries = await readDirectory(fromUrl);
|
|
1818
|
-
await Promise.all(directoryEntries.map(async directoryEntry => {
|
|
1819
|
-
const from = resolveUrl(directoryEntry, fromUrl);
|
|
1820
|
-
const to = resolveUrl(directoryEntry, toUrl);
|
|
1821
|
-
await moveFileSystemNode({
|
|
1822
|
-
from,
|
|
1823
|
-
to,
|
|
1824
|
-
overwrite,
|
|
1825
|
-
followLink
|
|
1826
|
-
});
|
|
1827
|
-
}));
|
|
1828
|
-
};
|
|
1829
|
-
|
|
1830
|
-
const readFilePromisified = util.promisify(fs.readFile);
|
|
1831
|
-
const readFile = async (value, {
|
|
1832
|
-
as = "string"
|
|
1833
|
-
} = {}) => {
|
|
1834
|
-
const fileUrl = assertAndNormalizeFileUrl(value);
|
|
1835
|
-
const filePath = urlToFileSystemPath(fileUrl);
|
|
1836
|
-
const buffer = await readFilePromisified(filePath);
|
|
1837
|
-
|
|
1838
|
-
if (as === "buffer") {
|
|
1839
|
-
return buffer;
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
|
-
if (as === "string") {
|
|
1843
|
-
return buffer.toString();
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
if (as === "json") {
|
|
1847
|
-
return JSON.parse(buffer.toString());
|
|
1848
|
-
}
|
|
1849
|
-
|
|
1850
|
-
throw new Error(`as must be one of buffer,string,json, received ${as}.`);
|
|
1851
|
-
};
|
|
1852
|
-
|
|
1853
|
-
const readFileSystemNodeModificationTime = async source => {
|
|
1854
|
-
const stats = await readFileSystemNodeStat(source);
|
|
1855
|
-
return Math.floor(stats.mtimeMs);
|
|
1856
|
-
};
|
|
1857
|
-
|
|
1858
|
-
const fileSystemNodeToTypeOrNull = url => {
|
|
1859
|
-
const path = urlToFileSystemPath(url);
|
|
1860
|
-
|
|
1861
|
-
try {
|
|
1862
|
-
const stats = fs.statSync(path);
|
|
1863
|
-
return statsToType(stats);
|
|
1864
|
-
} catch (e) {
|
|
1865
|
-
if (e.code === "ENOENT") {
|
|
1866
|
-
return null;
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
throw e;
|
|
1870
|
-
}
|
|
1871
|
-
};
|
|
1872
|
-
|
|
1873
|
-
const isWindows = process.platform === "win32";
|
|
1874
|
-
const createWatcher = (sourcePath, options) => {
|
|
1875
|
-
const watcher = fs.watch(sourcePath, options);
|
|
1876
|
-
|
|
1877
|
-
if (isWindows) {
|
|
1878
|
-
watcher.on("error", async error => {
|
|
1879
|
-
// https://github.com/joyent/node/issues/4337
|
|
1880
|
-
if (error.code === "EPERM") {
|
|
1881
|
-
try {
|
|
1882
|
-
const fd = fs.openSync(sourcePath, "r");
|
|
1883
|
-
fs.closeSync(fd);
|
|
1884
|
-
} catch (e) {
|
|
1885
|
-
if (e.code === "ENOENT") {
|
|
1886
|
-
return;
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
console.error(`error while fixing windows eperm: ${e.stack}`);
|
|
1890
|
-
throw error;
|
|
1891
|
-
}
|
|
1892
|
-
} else {
|
|
1893
|
-
throw error;
|
|
1894
|
-
}
|
|
1895
|
-
});
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
return watcher;
|
|
1899
|
-
};
|
|
1900
|
-
|
|
1901
|
-
const trackRessources = () => {
|
|
1902
|
-
const callbackArray = [];
|
|
1903
|
-
|
|
1904
|
-
const registerCleanupCallback = callback => {
|
|
1905
|
-
if (typeof callback !== "function") throw new TypeError(`callback must be a function
|
|
1906
|
-
callback: ${callback}`);
|
|
1907
|
-
callbackArray.push(callback);
|
|
1908
|
-
return () => {
|
|
1909
|
-
const index = callbackArray.indexOf(callback);
|
|
1910
|
-
if (index > -1) callbackArray.splice(index, 1);
|
|
1911
|
-
};
|
|
1912
|
-
};
|
|
1913
|
-
|
|
1914
|
-
const cleanup = async reason => {
|
|
1915
|
-
const localCallbackArray = callbackArray.slice();
|
|
1916
|
-
await Promise.all(localCallbackArray.map(callback => callback(reason)));
|
|
1917
|
-
};
|
|
1918
|
-
|
|
1919
|
-
return {
|
|
1920
|
-
registerCleanupCallback,
|
|
1921
|
-
cleanup
|
|
1922
|
-
};
|
|
1923
|
-
};
|
|
1924
|
-
|
|
1925
|
-
/* eslint-disable import/max-dependencies */
|
|
1926
|
-
const isLinux = process.platform === "linux"; // linux does not support recursive option
|
|
1927
|
-
|
|
1928
|
-
const fsWatchSupportsRecursive = !isLinux;
|
|
1929
|
-
const registerDirectoryLifecycle = (source, {
|
|
1930
|
-
added,
|
|
1931
|
-
updated,
|
|
1932
|
-
removed,
|
|
1933
|
-
watchDescription = {
|
|
1934
|
-
"./**/*": true
|
|
1935
|
-
},
|
|
1936
|
-
notifyExistent = false,
|
|
1937
|
-
keepProcessAlive = true,
|
|
1938
|
-
recursive = false
|
|
1939
|
-
}) => {
|
|
1940
|
-
const sourceUrl = ensureUrlTrailingSlash(assertAndNormalizeFileUrl(source));
|
|
1941
|
-
|
|
1942
|
-
if (!undefinedOrFunction$1(added)) {
|
|
1943
|
-
throw new TypeError(`added must be a function or undefined, got ${added}`);
|
|
1944
|
-
}
|
|
1945
|
-
|
|
1946
|
-
if (!undefinedOrFunction$1(updated)) {
|
|
1947
|
-
throw new TypeError(`updated must be a function or undefined, got ${updated}`);
|
|
1948
|
-
}
|
|
1949
|
-
|
|
1950
|
-
if (!undefinedOrFunction$1(removed)) {
|
|
1951
|
-
throw new TypeError(`removed must be a function or undefined, got ${removed}`);
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
const structuredMetaMap = urlMeta.normalizeStructuredMetaMap({
|
|
1955
|
-
watch: watchDescription
|
|
1956
|
-
}, sourceUrl);
|
|
1957
|
-
|
|
1958
|
-
const entryShouldBeWatched = ({
|
|
1959
|
-
relativeUrl,
|
|
1960
|
-
type
|
|
1961
|
-
}) => {
|
|
1962
|
-
const entryUrl = resolveUrl(relativeUrl, sourceUrl);
|
|
1963
|
-
|
|
1964
|
-
if (type === "directory") {
|
|
1965
|
-
const canContainEntryToWatch = urlMeta.urlCanContainsMetaMatching({
|
|
1966
|
-
url: `${entryUrl}/`,
|
|
1967
|
-
structuredMetaMap,
|
|
1968
|
-
predicate: ({
|
|
1969
|
-
watch
|
|
1970
|
-
}) => watch
|
|
1971
|
-
});
|
|
1972
|
-
return canContainEntryToWatch;
|
|
1973
|
-
}
|
|
1974
|
-
|
|
1975
|
-
const entryMeta = urlMeta.urlToMeta({
|
|
1976
|
-
url: entryUrl,
|
|
1977
|
-
structuredMetaMap
|
|
1978
|
-
});
|
|
1979
|
-
return entryMeta.watch;
|
|
1980
|
-
};
|
|
1981
|
-
|
|
1982
|
-
const tracker = trackRessources();
|
|
1983
|
-
const contentMap = new Map();
|
|
1984
|
-
|
|
1985
|
-
const handleDirectoryEvent = ({
|
|
1986
|
-
directoryRelativeUrl,
|
|
1987
|
-
filename,
|
|
1988
|
-
eventType
|
|
1989
|
-
}) => {
|
|
1990
|
-
if (filename) {
|
|
1991
|
-
if (directoryRelativeUrl) {
|
|
1992
|
-
handleChange(`${directoryRelativeUrl}/${filename}`);
|
|
1993
|
-
} else {
|
|
1994
|
-
handleChange(`${filename}`);
|
|
1995
|
-
}
|
|
1996
|
-
} else if ((removed || added) && eventType === "rename") {
|
|
1997
|
-
// we might receive `rename` without filename
|
|
1998
|
-
// in that case we try to find ourselves which file was removed.
|
|
1999
|
-
let relativeUrlCandidateArray = Array.from(contentMap.keys());
|
|
2000
|
-
|
|
2001
|
-
if (recursive && !fsWatchSupportsRecursive) {
|
|
2002
|
-
relativeUrlCandidateArray = relativeUrlCandidateArray.filter(relativeUrlCandidate => {
|
|
2003
|
-
if (!directoryRelativeUrl) {
|
|
2004
|
-
// ensure entry is top level
|
|
2005
|
-
if (relativeUrlCandidate.includes("/")) return false;
|
|
2006
|
-
return true;
|
|
2007
|
-
} // entry not inside this directory
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
if (!relativeUrlCandidate.startsWith(directoryRelativeUrl)) return false;
|
|
2011
|
-
const afterDirectory = relativeUrlCandidate.slice(directoryRelativeUrl.length + 1); // deep inside this directory
|
|
2012
|
-
|
|
2013
|
-
if (afterDirectory.includes("/")) return false;
|
|
2014
|
-
return true;
|
|
2015
|
-
});
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
const removedEntryRelativeUrl = relativeUrlCandidateArray.find(relativeUrlCandidate => {
|
|
2019
|
-
const entryUrl = resolveUrl(relativeUrlCandidate, sourceUrl);
|
|
2020
|
-
const type = fileSystemNodeToTypeOrNull(entryUrl);
|
|
2021
|
-
return type === null;
|
|
2022
|
-
});
|
|
2023
|
-
|
|
2024
|
-
if (removedEntryRelativeUrl) {
|
|
2025
|
-
handleEntryLost({
|
|
2026
|
-
relativeUrl: removedEntryRelativeUrl,
|
|
2027
|
-
type: contentMap.get(removedEntryRelativeUrl)
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
};
|
|
2032
|
-
|
|
2033
|
-
const handleChange = relativeUrl => {
|
|
2034
|
-
const entryUrl = resolveUrl(relativeUrl, sourceUrl);
|
|
2035
|
-
const previousType = contentMap.get(relativeUrl);
|
|
2036
|
-
const type = fileSystemNodeToTypeOrNull(entryUrl);
|
|
2037
|
-
|
|
2038
|
-
if (!entryShouldBeWatched({
|
|
2039
|
-
relativeUrl,
|
|
2040
|
-
type
|
|
2041
|
-
})) {
|
|
2042
|
-
return;
|
|
2043
|
-
} // it's something new
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
if (!previousType) {
|
|
2047
|
-
if (type !== null) {
|
|
2048
|
-
handleEntryFound({
|
|
2049
|
-
relativeUrl,
|
|
2050
|
-
type,
|
|
2051
|
-
existent: false
|
|
2052
|
-
});
|
|
2053
|
-
}
|
|
2054
|
-
|
|
2055
|
-
return;
|
|
2056
|
-
} // it existed but now it's not here anymore
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
if (type === null) {
|
|
2060
|
-
handleEntryLost({
|
|
2061
|
-
relativeUrl,
|
|
2062
|
-
type: previousType
|
|
2063
|
-
});
|
|
2064
|
-
return;
|
|
2065
|
-
} // it existed and was replaced by something else
|
|
2066
|
-
// we don't handle this as an update. We rather say the ressource
|
|
2067
|
-
// is lost and something else is found (call removed() then added())
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
if (previousType !== type) {
|
|
2071
|
-
handleEntryLost({
|
|
2072
|
-
relativeUrl,
|
|
2073
|
-
type: previousType
|
|
2074
|
-
});
|
|
2075
|
-
handleEntryFound({
|
|
2076
|
-
relativeUrl,
|
|
2077
|
-
type
|
|
2078
|
-
});
|
|
2079
|
-
return;
|
|
2080
|
-
} // a directory cannot really be updated in way that matters for us
|
|
2081
|
-
// filesystem is trying to tell us the directory content have changed
|
|
2082
|
-
// but we don't care about that
|
|
2083
|
-
// we'll already be notified about what has changed
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
if (type === "directory") {
|
|
2087
|
-
return;
|
|
2088
|
-
} // something has changed at this relativeUrl (the file existed and was not deleted)
|
|
2089
|
-
// it's possible to get there and there is no real update
|
|
2090
|
-
// (file content is the same and file mtime is the same).
|
|
2091
|
-
// In short filesystem is sometimes "lying"
|
|
2092
|
-
// Not trying to guard against that because:
|
|
2093
|
-
// - hurt perfs a lot
|
|
2094
|
-
// - it happens very rarely
|
|
2095
|
-
// - it's not really a concern in practice
|
|
2096
|
-
// - filesystem did not send an event out of nowhere:
|
|
2097
|
-
// something occured but we don't know what with the information we have.
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
if (updated) {
|
|
2101
|
-
updated({
|
|
2102
|
-
relativeUrl,
|
|
2103
|
-
type
|
|
2104
|
-
});
|
|
2105
|
-
}
|
|
2106
|
-
};
|
|
2107
|
-
|
|
2108
|
-
const handleEntryFound = ({
|
|
2109
|
-
relativeUrl,
|
|
2110
|
-
type,
|
|
2111
|
-
existent
|
|
2112
|
-
}) => {
|
|
2113
|
-
if (!entryShouldBeWatched({
|
|
2114
|
-
relativeUrl,
|
|
2115
|
-
type
|
|
2116
|
-
})) {
|
|
2117
|
-
return;
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
contentMap.set(relativeUrl, type);
|
|
2121
|
-
const entryUrl = resolveUrl(relativeUrl, sourceUrl);
|
|
2122
|
-
|
|
2123
|
-
if (type === "directory") {
|
|
2124
|
-
visitDirectory({
|
|
2125
|
-
directoryUrl: `${entryUrl}/`,
|
|
2126
|
-
entryFound: entry => {
|
|
2127
|
-
handleEntryFound({
|
|
2128
|
-
relativeUrl: `${relativeUrl}/${entry.relativeUrl}`,
|
|
2129
|
-
type: entry.type,
|
|
2130
|
-
existent
|
|
2131
|
-
});
|
|
2132
|
-
}
|
|
2133
|
-
});
|
|
2134
|
-
}
|
|
2135
|
-
|
|
2136
|
-
if (added) {
|
|
2137
|
-
if (existent) {
|
|
2138
|
-
if (notifyExistent) {
|
|
2139
|
-
added({
|
|
2140
|
-
relativeUrl,
|
|
2141
|
-
type,
|
|
2142
|
-
existent: true
|
|
2143
|
-
});
|
|
2144
|
-
}
|
|
2145
|
-
} else {
|
|
2146
|
-
added({
|
|
2147
|
-
relativeUrl,
|
|
2148
|
-
type
|
|
2149
|
-
});
|
|
2150
|
-
}
|
|
2151
|
-
} // we must watch manually every directory we find
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
if (!fsWatchSupportsRecursive && type === "directory") {
|
|
2155
|
-
const watcher = createWatcher(urlToFileSystemPath(entryUrl), {
|
|
2156
|
-
persistent: keepProcessAlive
|
|
2157
|
-
});
|
|
2158
|
-
tracker.registerCleanupCallback(() => {
|
|
2159
|
-
watcher.close();
|
|
2160
|
-
});
|
|
2161
|
-
watcher.on("change", (eventType, filename) => {
|
|
2162
|
-
handleDirectoryEvent({
|
|
2163
|
-
directoryRelativeUrl: relativeUrl,
|
|
2164
|
-
filename: filename ? replaceBackSlashesWithSlashes(filename) : "",
|
|
2165
|
-
eventType
|
|
2166
|
-
});
|
|
2167
|
-
});
|
|
2168
|
-
}
|
|
2169
|
-
};
|
|
2170
|
-
|
|
2171
|
-
const handleEntryLost = ({
|
|
2172
|
-
relativeUrl,
|
|
2173
|
-
type
|
|
2174
|
-
}) => {
|
|
2175
|
-
contentMap.delete(relativeUrl);
|
|
2176
|
-
|
|
2177
|
-
if (removed) {
|
|
2178
|
-
removed({
|
|
2179
|
-
relativeUrl,
|
|
2180
|
-
type
|
|
2181
|
-
});
|
|
2182
|
-
}
|
|
2183
|
-
};
|
|
2184
|
-
|
|
2185
|
-
visitDirectory({
|
|
2186
|
-
directoryUrl: sourceUrl,
|
|
2187
|
-
entryFound: ({
|
|
2188
|
-
relativeUrl,
|
|
2189
|
-
type
|
|
2190
|
-
}) => {
|
|
2191
|
-
handleEntryFound({
|
|
2192
|
-
relativeUrl,
|
|
2193
|
-
type,
|
|
2194
|
-
existent: true
|
|
2195
|
-
});
|
|
2196
|
-
}
|
|
2197
|
-
});
|
|
2198
|
-
const watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
|
|
2199
|
-
recursive: recursive && fsWatchSupportsRecursive,
|
|
2200
|
-
persistent: keepProcessAlive
|
|
2201
|
-
});
|
|
2202
|
-
tracker.registerCleanupCallback(() => {
|
|
2203
|
-
watcher.close();
|
|
2204
|
-
});
|
|
2205
|
-
watcher.on("change", (eventType, fileSystemPath) => {
|
|
2206
|
-
handleDirectoryEvent({ ...fileSystemPathToDirectoryRelativeUrlAndFilename(fileSystemPath),
|
|
2207
|
-
eventType
|
|
2208
|
-
});
|
|
2209
|
-
});
|
|
2210
|
-
return tracker.cleanup;
|
|
2211
|
-
};
|
|
2212
|
-
|
|
2213
|
-
const undefinedOrFunction$1 = value => typeof value === "undefined" || typeof value === "function";
|
|
2214
|
-
|
|
2215
|
-
const visitDirectory = ({
|
|
2216
|
-
directoryUrl,
|
|
2217
|
-
entryFound
|
|
2218
|
-
}) => {
|
|
2219
|
-
const directoryPath = urlToFileSystemPath(directoryUrl);
|
|
2220
|
-
fs.readdirSync(directoryPath).forEach(entry => {
|
|
2221
|
-
const entryUrl = resolveUrl(entry, directoryUrl);
|
|
2222
|
-
const type = fileSystemNodeToTypeOrNull(entryUrl);
|
|
2223
|
-
|
|
2224
|
-
if (type === null) {
|
|
2225
|
-
return;
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
const relativeUrl = urlToRelativeUrl(entryUrl, directoryUrl);
|
|
2229
|
-
entryFound({
|
|
2230
|
-
relativeUrl,
|
|
2231
|
-
type
|
|
2232
|
-
});
|
|
2233
|
-
});
|
|
2234
|
-
};
|
|
2235
|
-
|
|
2236
|
-
const fileSystemPathToDirectoryRelativeUrlAndFilename = path => {
|
|
2237
|
-
if (!path) {
|
|
2238
|
-
return {
|
|
2239
|
-
directoryRelativeUrl: "",
|
|
2240
|
-
filename: ""
|
|
2241
|
-
};
|
|
2242
|
-
}
|
|
2243
|
-
|
|
2244
|
-
const normalizedPath = replaceBackSlashesWithSlashes(path);
|
|
2245
|
-
const slashLastIndex = normalizedPath.lastIndexOf("/");
|
|
2246
|
-
|
|
2247
|
-
if (slashLastIndex === -1) {
|
|
2248
|
-
return {
|
|
2249
|
-
directoryRelativeUrl: "",
|
|
2250
|
-
filename: normalizedPath
|
|
2251
|
-
};
|
|
2252
|
-
}
|
|
2253
|
-
|
|
2254
|
-
const directoryRelativeUrl = normalizedPath.slice(0, slashLastIndex);
|
|
2255
|
-
const filename = normalizedPath.slice(slashLastIndex + 1);
|
|
2256
|
-
return {
|
|
2257
|
-
directoryRelativeUrl,
|
|
2258
|
-
filename
|
|
2259
|
-
};
|
|
2260
|
-
};
|
|
2261
|
-
|
|
2262
|
-
const registerFileLifecycle = (source, {
|
|
2263
|
-
added,
|
|
2264
|
-
updated,
|
|
2265
|
-
removed,
|
|
2266
|
-
notifyExistent = false,
|
|
2267
|
-
keepProcessAlive = true
|
|
2268
|
-
}) => {
|
|
2269
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
2270
|
-
|
|
2271
|
-
if (!undefinedOrFunction(added)) {
|
|
2272
|
-
throw new TypeError(`added must be a function or undefined, got ${added}`);
|
|
2273
|
-
}
|
|
2274
|
-
|
|
2275
|
-
if (!undefinedOrFunction(updated)) {
|
|
2276
|
-
throw new TypeError(`updated must be a function or undefined, got ${updated}`);
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
if (!undefinedOrFunction(removed)) {
|
|
2280
|
-
throw new TypeError(`removed must be a function or undefined, got ${removed}`);
|
|
2281
|
-
}
|
|
2282
|
-
|
|
2283
|
-
const tracker = trackRessources();
|
|
2284
|
-
|
|
2285
|
-
const handleFileFound = ({
|
|
2286
|
-
existent
|
|
2287
|
-
}) => {
|
|
2288
|
-
const fileMutationStopWatching = watchFileMutation(sourceUrl, {
|
|
2289
|
-
updated,
|
|
2290
|
-
removed: () => {
|
|
2291
|
-
fileMutationStopTracking();
|
|
2292
|
-
watchFileAdded();
|
|
2293
|
-
|
|
2294
|
-
if (removed) {
|
|
2295
|
-
removed();
|
|
2296
|
-
}
|
|
2297
|
-
},
|
|
2298
|
-
keepProcessAlive
|
|
2299
|
-
});
|
|
2300
|
-
const fileMutationStopTracking = tracker.registerCleanupCallback(fileMutationStopWatching);
|
|
2301
|
-
|
|
2302
|
-
if (added) {
|
|
2303
|
-
if (existent) {
|
|
2304
|
-
if (notifyExistent) {
|
|
2305
|
-
added({
|
|
2306
|
-
existent: true
|
|
2307
|
-
});
|
|
2308
|
-
}
|
|
2309
|
-
} else {
|
|
2310
|
-
added({});
|
|
2311
|
-
}
|
|
2312
|
-
}
|
|
2313
|
-
};
|
|
2314
|
-
|
|
2315
|
-
const watchFileAdded = () => {
|
|
2316
|
-
const fileCreationStopWatching = watchFileCreation(sourceUrl, () => {
|
|
2317
|
-
fileCreationgStopTracking();
|
|
2318
|
-
handleFileFound({
|
|
2319
|
-
existent: false
|
|
2320
|
-
});
|
|
2321
|
-
}, keepProcessAlive);
|
|
2322
|
-
const fileCreationgStopTracking = tracker.registerCleanupCallback(fileCreationStopWatching);
|
|
2323
|
-
};
|
|
2324
|
-
|
|
2325
|
-
const sourceType = fileSystemNodeToTypeOrNull(sourceUrl);
|
|
2326
|
-
|
|
2327
|
-
if (sourceType === null) {
|
|
2328
|
-
if (added) {
|
|
2329
|
-
watchFileAdded();
|
|
2330
|
-
} else {
|
|
2331
|
-
throw new Error(`${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`);
|
|
2332
|
-
}
|
|
2333
|
-
} else if (sourceType === "file") {
|
|
2334
|
-
handleFileFound({
|
|
2335
|
-
existent: true
|
|
2336
|
-
});
|
|
2337
|
-
} else {
|
|
2338
|
-
throw new Error(`${urlToFileSystemPath(sourceUrl)} must lead to a file, type found instead`);
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
return tracker.cleanup;
|
|
2342
|
-
};
|
|
2343
|
-
|
|
2344
|
-
const undefinedOrFunction = value => typeof value === "undefined" || typeof value === "function";
|
|
2345
|
-
|
|
2346
|
-
const watchFileCreation = (source, callback, keepProcessAlive) => {
|
|
2347
|
-
const sourcePath = urlToFileSystemPath(source);
|
|
2348
|
-
const sourceFilename = path.basename(sourcePath);
|
|
2349
|
-
const directoryPath = path.dirname(sourcePath);
|
|
2350
|
-
let directoryWatcher = createWatcher(directoryPath, {
|
|
2351
|
-
persistent: keepProcessAlive
|
|
2352
|
-
});
|
|
2353
|
-
directoryWatcher.on("change", (eventType, filename) => {
|
|
2354
|
-
if (filename && filename !== sourceFilename) return;
|
|
2355
|
-
const type = fileSystemNodeToTypeOrNull(source); // ignore if something else with that name gets created
|
|
2356
|
-
// we are only interested into files
|
|
2357
|
-
|
|
2358
|
-
if (type !== "file") return;
|
|
2359
|
-
directoryWatcher.close();
|
|
2360
|
-
directoryWatcher = undefined;
|
|
2361
|
-
callback();
|
|
2362
|
-
});
|
|
2363
|
-
return () => {
|
|
2364
|
-
if (directoryWatcher) {
|
|
2365
|
-
directoryWatcher.close();
|
|
2366
|
-
}
|
|
2367
|
-
};
|
|
2368
|
-
};
|
|
2369
|
-
|
|
2370
|
-
const watchFileMutation = (sourceUrl, {
|
|
2371
|
-
updated,
|
|
2372
|
-
removed,
|
|
2373
|
-
keepProcessAlive
|
|
2374
|
-
}) => {
|
|
2375
|
-
let watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
|
|
2376
|
-
persistent: keepProcessAlive
|
|
2377
|
-
});
|
|
2378
|
-
watcher.on("change", () => {
|
|
2379
|
-
const sourceType = fileSystemNodeToTypeOrNull(sourceUrl);
|
|
2380
|
-
|
|
2381
|
-
if (sourceType === null) {
|
|
2382
|
-
watcher.close();
|
|
2383
|
-
watcher = undefined;
|
|
2384
|
-
|
|
2385
|
-
if (removed) {
|
|
2386
|
-
removed();
|
|
2387
|
-
}
|
|
2388
|
-
} else if (sourceType === "file") {
|
|
2389
|
-
if (updated) {
|
|
2390
|
-
updated();
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
});
|
|
2394
|
-
return () => {
|
|
2395
|
-
if (watcher) {
|
|
2396
|
-
watcher.close();
|
|
2397
|
-
}
|
|
2398
|
-
};
|
|
2399
|
-
};
|
|
2400
|
-
|
|
2401
|
-
const resolveDirectoryUrl = (specifier, baseUrl) => {
|
|
2402
|
-
const url = resolveUrl(specifier, baseUrl);
|
|
2403
|
-
return ensureUrlTrailingSlash(url);
|
|
2404
|
-
};
|
|
2405
|
-
|
|
2406
|
-
const {
|
|
2407
|
-
access
|
|
2408
|
-
} = fs.promises;
|
|
2409
|
-
const {
|
|
2410
|
-
// F_OK,
|
|
2411
|
-
R_OK,
|
|
2412
|
-
W_OK,
|
|
2413
|
-
X_OK
|
|
2414
|
-
} = fs.constants;
|
|
2415
|
-
const testFileSystemNodePermissions = async (source, {
|
|
2416
|
-
read = false,
|
|
2417
|
-
write = false,
|
|
2418
|
-
execute = false,
|
|
2419
|
-
allowedIfNotFound = false
|
|
2420
|
-
} = {}) => {
|
|
2421
|
-
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
2422
|
-
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
2423
|
-
let binaryFlags = 0; // if (visible) binaryFlags |= F_OK
|
|
2424
|
-
|
|
2425
|
-
if (read) binaryFlags |= R_OK;
|
|
2426
|
-
if (write) binaryFlags |= W_OK;
|
|
2427
|
-
if (execute) binaryFlags |= X_OK;
|
|
2428
|
-
|
|
2429
|
-
try {
|
|
2430
|
-
await access(sourcePath, binaryFlags);
|
|
2431
|
-
return true;
|
|
2432
|
-
} catch (error) {
|
|
2433
|
-
if (error.code === "ENOENT") {
|
|
2434
|
-
if (allowedIfNotFound) {
|
|
2435
|
-
return true;
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
|
-
throw error;
|
|
2439
|
-
}
|
|
2440
|
-
|
|
2441
|
-
return false;
|
|
2442
|
-
}
|
|
2443
|
-
};
|
|
2444
|
-
|
|
2445
|
-
const urlToScheme = url => {
|
|
2446
|
-
const urlString = String(url);
|
|
2447
|
-
const colonIndex = urlString.indexOf(":");
|
|
2448
|
-
|
|
2449
|
-
if (colonIndex === -1) {
|
|
2450
|
-
return "";
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
const scheme = urlString.slice(0, colonIndex);
|
|
2454
|
-
return scheme;
|
|
2455
|
-
};
|
|
2456
|
-
|
|
2457
|
-
const urlToRessource = url => {
|
|
2458
|
-
const scheme = urlToScheme(url);
|
|
2459
|
-
|
|
2460
|
-
if (scheme === "file") {
|
|
2461
|
-
const urlAsStringWithoutFileProtocol = String(url).slice("file://".length);
|
|
2462
|
-
return urlAsStringWithoutFileProtocol;
|
|
2463
|
-
}
|
|
2464
|
-
|
|
2465
|
-
if (scheme === "https" || scheme === "http") {
|
|
2466
|
-
// remove origin
|
|
2467
|
-
const afterProtocol = String(url).slice(scheme.length + "://".length);
|
|
2468
|
-
const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
|
|
2469
|
-
const urlAsStringWithoutOrigin = afterProtocol.slice(pathnameSlashIndex);
|
|
2470
|
-
return urlAsStringWithoutOrigin;
|
|
2471
|
-
}
|
|
2472
|
-
|
|
2473
|
-
const urlAsStringWithoutProtocol = String(url).slice(scheme.length + 1);
|
|
2474
|
-
return urlAsStringWithoutProtocol;
|
|
2475
|
-
};
|
|
2476
|
-
|
|
2477
|
-
const urlToPathname = url => {
|
|
2478
|
-
const ressource = urlToRessource(url);
|
|
2479
|
-
const pathname = ressourceToPathname(ressource);
|
|
2480
|
-
return pathname;
|
|
2481
|
-
};
|
|
2482
|
-
|
|
2483
|
-
const ressourceToPathname = ressource => {
|
|
2484
|
-
const searchSeparatorIndex = ressource.indexOf("?");
|
|
2485
|
-
return searchSeparatorIndex === -1 ? ressource : ressource.slice(0, searchSeparatorIndex);
|
|
2486
|
-
};
|
|
2487
|
-
|
|
2488
|
-
const urlToFilename = url => {
|
|
2489
|
-
const pathname = urlToPathname(url);
|
|
2490
|
-
const pathnameBeforeLastSlash = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
|
|
2491
|
-
const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/");
|
|
2492
|
-
const filename = slashLastIndex === -1 ? pathnameBeforeLastSlash : pathnameBeforeLastSlash.slice(slashLastIndex + 1);
|
|
2493
|
-
return filename;
|
|
2494
|
-
};
|
|
2495
|
-
|
|
2496
|
-
const urlToBasename = url => {
|
|
2497
|
-
const filename = urlToFilename(url);
|
|
2498
|
-
const dotLastIndex = filename.lastIndexOf(".");
|
|
2499
|
-
const basename = dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex);
|
|
2500
|
-
return basename;
|
|
2501
|
-
};
|
|
2502
|
-
|
|
2503
|
-
const urlToExtension = url => {
|
|
2504
|
-
const pathname = urlToPathname(url);
|
|
2505
|
-
return pathnameToExtension(pathname);
|
|
2506
|
-
};
|
|
2507
|
-
|
|
2508
|
-
const pathnameToExtension = pathname => {
|
|
2509
|
-
const slashLastIndex = pathname.lastIndexOf("/");
|
|
2510
|
-
|
|
2511
|
-
if (slashLastIndex !== -1) {
|
|
2512
|
-
pathname = pathname.slice(slashLastIndex + 1);
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
|
-
const dotLastIndex = pathname.lastIndexOf(".");
|
|
2516
|
-
if (dotLastIndex === -1) return ""; // if (dotLastIndex === pathname.length - 1) return ""
|
|
2517
|
-
|
|
2518
|
-
const extension = pathname.slice(dotLastIndex);
|
|
2519
|
-
return extension;
|
|
2520
|
-
};
|
|
2521
|
-
|
|
2522
|
-
const urlToOrigin = url => {
|
|
2523
|
-
const urlString = String(url);
|
|
2524
|
-
|
|
2525
|
-
if (urlString.startsWith("file://")) {
|
|
2526
|
-
return `file://`;
|
|
2527
|
-
}
|
|
2528
|
-
|
|
2529
|
-
return new URL(urlString).origin;
|
|
2530
|
-
};
|
|
2531
|
-
|
|
2532
|
-
const urlToParentUrl = url => {
|
|
2533
|
-
const ressource = urlToRessource(url);
|
|
2534
|
-
const slashLastIndex = ressource.lastIndexOf("/");
|
|
2535
|
-
|
|
2536
|
-
if (slashLastIndex === -1) {
|
|
2537
|
-
const urlAsString = String(url);
|
|
2538
|
-
return urlAsString;
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
const lastCharacterIndex = ressource.length - 1;
|
|
2542
|
-
|
|
2543
|
-
if (slashLastIndex === lastCharacterIndex) {
|
|
2544
|
-
const slashPreviousIndex = ressource.lastIndexOf("/", lastCharacterIndex - 1);
|
|
2545
|
-
|
|
2546
|
-
if (slashPreviousIndex === -1) {
|
|
2547
|
-
const urlAsString = String(url);
|
|
2548
|
-
return urlAsString;
|
|
2549
|
-
}
|
|
2550
|
-
|
|
2551
|
-
const origin = urlToOrigin(url);
|
|
2552
|
-
const parentUrl = `${origin}${ressource.slice(0, slashPreviousIndex + 1)}`;
|
|
2553
|
-
return parentUrl;
|
|
2554
|
-
}
|
|
2555
|
-
|
|
2556
|
-
const origin = urlToOrigin(url);
|
|
2557
|
-
const parentUrl = `${origin}${ressource.slice(0, slashLastIndex + 1)}`;
|
|
2558
|
-
return parentUrl;
|
|
2559
|
-
};
|
|
2560
|
-
|
|
2561
|
-
const {
|
|
2562
|
-
writeFile: writeFileNode
|
|
2563
|
-
} = fs.promises;
|
|
2564
|
-
const writeFile = async (destination, content = "") => {
|
|
2565
|
-
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
2566
|
-
const destinationPath = urlToFileSystemPath(destinationUrl);
|
|
2567
|
-
|
|
2568
|
-
try {
|
|
2569
|
-
await writeFileNode(destinationPath, content);
|
|
2570
|
-
} catch (error) {
|
|
2571
|
-
if (error.code === "ENOENT") {
|
|
2572
|
-
await ensureParentDirectories(destinationUrl);
|
|
2573
|
-
await writeFileNode(destinationPath, content);
|
|
2574
|
-
return;
|
|
2575
|
-
}
|
|
2576
|
-
|
|
2577
|
-
throw error;
|
|
2578
|
-
}
|
|
2579
|
-
};
|
|
2580
|
-
|
|
2581
|
-
Object.defineProperty(exports, 'applyPatternMatching', {
|
|
2582
|
-
enumerable: true,
|
|
2583
|
-
get: function () {
|
|
2584
|
-
return urlMeta.applyPatternMatching;
|
|
2585
|
-
}
|
|
2586
|
-
});
|
|
2587
|
-
Object.defineProperty(exports, 'normalizeStructuredMetaMap', {
|
|
2588
|
-
enumerable: true,
|
|
2589
|
-
get: function () {
|
|
2590
|
-
return urlMeta.normalizeStructuredMetaMap;
|
|
2591
|
-
}
|
|
2592
|
-
});
|
|
2593
|
-
Object.defineProperty(exports, 'urlCanContainsMetaMatching', {
|
|
2594
|
-
enumerable: true,
|
|
2595
|
-
get: function () {
|
|
2596
|
-
return urlMeta.urlCanContainsMetaMatching;
|
|
2597
|
-
}
|
|
2598
|
-
});
|
|
2599
|
-
Object.defineProperty(exports, 'urlToMeta', {
|
|
2600
|
-
enumerable: true,
|
|
2601
|
-
get: function () {
|
|
2602
|
-
return urlMeta.urlToMeta;
|
|
2603
|
-
}
|
|
2604
|
-
});
|
|
2605
|
-
exports.assertAndNormalizeDirectoryUrl = assertAndNormalizeDirectoryUrl;
|
|
2606
|
-
exports.assertAndNormalizeFileUrl = assertAndNormalizeFileUrl;
|
|
2607
|
-
exports.assertDirectoryPresence = assertDirectoryPresence;
|
|
2608
|
-
exports.assertFilePresence = assertFilePresence;
|
|
2609
|
-
exports.bufferToEtag = bufferToEtag;
|
|
2610
|
-
exports.collectDirectoryMatchReport = collectDirectoryMatchReport;
|
|
2611
|
-
exports.collectFiles = collectFiles;
|
|
2612
|
-
exports.comparePathnames = comparePathnames;
|
|
2613
|
-
exports.copyDirectoryContent = copyDirectoryContent;
|
|
2614
|
-
exports.copyFileSystemNode = copyFileSystemNode;
|
|
2615
|
-
exports.ensureEmptyDirectory = ensureEmptyDirectory;
|
|
2616
|
-
exports.ensureParentDirectories = ensureParentDirectories;
|
|
2617
|
-
exports.ensureWindowsDriveLetter = ensureWindowsDriveLetter;
|
|
2618
|
-
exports.fileSystemPathToUrl = fileSystemPathToUrl;
|
|
2619
|
-
exports.grantPermissionsOnFileSystemNode = grantPermissionsOnFileSystemNode;
|
|
2620
|
-
exports.isFileSystemPath = isFileSystemPath;
|
|
2621
|
-
exports.listFilesMatching = listFilesMatching;
|
|
2622
|
-
exports.memoize = memoize;
|
|
2623
|
-
exports.moveDirectoryContent = moveDirectoryContent;
|
|
2624
|
-
exports.moveFileSystemNode = moveFileSystemNode;
|
|
2625
|
-
exports.readDirectory = readDirectory;
|
|
2626
|
-
exports.readFile = readFile;
|
|
2627
|
-
exports.readFileSystemNodeModificationTime = readFileSystemNodeModificationTime;
|
|
2628
|
-
exports.readFileSystemNodePermissions = readFileSystemNodePermissions;
|
|
2629
|
-
exports.readFileSystemNodeStat = readFileSystemNodeStat;
|
|
2630
|
-
exports.readSymbolicLink = readSymbolicLink;
|
|
2631
|
-
exports.registerDirectoryLifecycle = registerDirectoryLifecycle;
|
|
2632
|
-
exports.registerFileLifecycle = registerFileLifecycle;
|
|
2633
|
-
exports.removeFileSystemNode = removeFileSystemNode;
|
|
2634
|
-
exports.resolveDirectoryUrl = resolveDirectoryUrl;
|
|
2635
|
-
exports.resolveUrl = resolveUrl;
|
|
2636
|
-
exports.testFileSystemNodePermissions = testFileSystemNodePermissions;
|
|
2637
|
-
exports.urlIsInsideOf = urlIsInsideOf;
|
|
2638
|
-
exports.urlToBasename = urlToBasename;
|
|
2639
|
-
exports.urlToExtension = urlToExtension;
|
|
2640
|
-
exports.urlToFileSystemPath = urlToFileSystemPath;
|
|
2641
|
-
exports.urlToFilename = urlToFilename;
|
|
2642
|
-
exports.urlToOrigin = urlToOrigin;
|
|
2643
|
-
exports.urlToParentUrl = urlToParentUrl;
|
|
2644
|
-
exports.urlToPathname = urlToPathname;
|
|
2645
|
-
exports.urlToRelativeUrl = urlToRelativeUrl;
|
|
2646
|
-
exports.urlToRessource = urlToRessource;
|
|
2647
|
-
exports.urlToScheme = urlToScheme;
|
|
2648
|
-
exports.writeDirectory = writeDirectory;
|
|
2649
|
-
exports.writeFile = writeFile;
|
|
2650
|
-
exports.writeFileSystemNodeModificationTime = writeFileSystemNodeModificationTime;
|
|
2651
|
-
exports.writeFileSystemNodePermissions = writeFileSystemNodePermissions;
|
|
2652
|
-
exports.writeSymbolicLink = writeSymbolicLink;
|
|
2653
|
-
|
|
2654
|
-
//# sourceMappingURL=jsenv_filesystem.cjs.map
|