@jsenv/filesystem 2.4.0 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2647 +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 && ( // linux os
1045
- error.code === "ENOTEMPTY" || // SunOS
1046
- error.code === "EEXIST")) {
1047
- resolve(handleNotEmptyError(error));
1048
- } else {
1049
- reject(error);
1050
- }
1051
- } else {
1052
- resolve(lstatObject);
1053
- }
1054
- });
1055
- });
1056
- };
1057
-
1058
- const ensureEmptyDirectory = async source => {
1059
- const stats = await readFileSystemNodeStat(source, {
1060
- nullIfNotFound: true,
1061
- followLink: false
1062
- });
1063
-
1064
- if (stats === null) {
1065
- // if there is nothing, create a directory
1066
- return writeDirectory(source, {
1067
- allowUseless: true
1068
- });
1069
- }
1070
-
1071
- if (stats.isDirectory()) {
1072
- // if there is a directory remove its content and done
1073
- return removeFileSystemNode(source, {
1074
- allowUseless: true,
1075
- recursive: true,
1076
- onlyContent: true
1077
- });
1078
- }
1079
-
1080
- const sourceType = statsToType(stats);
1081
- const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source));
1082
- throw new Error(`ensureEmptyDirectory expect directory at ${sourcePath}, found ${sourceType} instead`);
1083
- };
1084
-
1085
- const ensureParentDirectories = async destination => {
1086
- const destinationUrl = assertAndNormalizeFileUrl(destination);
1087
- const destinationPath = urlToFileSystemPath(destinationUrl);
1088
- const destinationParentPath = path.dirname(destinationPath);
1089
- return writeDirectory(destinationParentPath, {
1090
- recursive: true,
1091
- allowUseless: true
1092
- });
1093
- };
1094
-
1095
- const isWindows$2 = process.platform === "win32";
1096
- const baseUrlFallback = fileSystemPathToUrl(process.cwd());
1097
- /**
1098
- * Some url might be resolved or remapped to url without the windows drive letter.
1099
- * For instance
1100
- * new URL('/foo.js', 'file:///C:/dir/file.js')
1101
- * resolves to
1102
- * 'file:///foo.js'
1103
- *
1104
- * But on windows it becomes a problem because we need the drive letter otherwise
1105
- * url cannot be converted to a filesystem path.
1106
- *
1107
- * ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.
1108
- */
1109
-
1110
- const ensureWindowsDriveLetter = (url, baseUrl) => {
1111
- try {
1112
- url = String(new URL(url));
1113
- } catch (e) {
1114
- throw new Error(`absolute url expected but got ${url}`);
1115
- }
1116
-
1117
- if (!isWindows$2) {
1118
- return url;
1119
- }
1120
-
1121
- try {
1122
- baseUrl = String(new URL(baseUrl));
1123
- } catch (e) {
1124
- throw new Error(`absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`);
1125
- }
1126
-
1127
- if (!url.startsWith("file://")) {
1128
- return url;
1129
- }
1130
-
1131
- const afterProtocol = url.slice("file://".length); // we still have the windows drive letter
1132
-
1133
- if (extractDriveLetter(afterProtocol)) {
1134
- return url;
1135
- } // drive letter was lost, restore it
1136
-
1137
-
1138
- const baseUrlOrFallback = baseUrl.startsWith("file://") ? baseUrl : baseUrlFallback;
1139
- const driveLetter = extractDriveLetter(baseUrlOrFallback.slice("file://".length));
1140
-
1141
- if (!driveLetter) {
1142
- throw new Error(`drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`);
1143
- }
1144
-
1145
- return `file:///${driveLetter}:${afterProtocol}`;
1146
- };
1147
-
1148
- const extractDriveLetter = ressource => {
1149
- // we still have the windows drive letter
1150
- if (/[a-zA-Z]/.test(ressource[1]) && ressource[2] === ":") {
1151
- return ressource[1];
1152
- }
1153
-
1154
- return null;
1155
- };
1156
-
1157
- const urlTargetsSameFileSystemPath = (leftUrl, rightUrl) => {
1158
- if (leftUrl.endsWith("/")) leftUrl = leftUrl.slice(0, -1);
1159
- if (rightUrl.endsWith("/")) rightUrl = rightUrl.slice(0, -1);
1160
- return leftUrl === rightUrl;
1161
- };
1162
-
1163
- const writeFileSystemNodeModificationTime = (source, mtime) => {
1164
- const sourceUrl = assertAndNormalizeFileUrl(source);
1165
- const sourcePath = urlToFileSystemPath(sourceUrl);
1166
- 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
1167
- // where we want to modify it
1168
-
1169
- const atimeValue = mtimeValue;
1170
- return new Promise((resolve, reject) => {
1171
- fs.utimes(sourcePath, atimeValue, mtimeValue, error => {
1172
- if (error) {
1173
- reject(error);
1174
- } else {
1175
- resolve();
1176
- }
1177
- });
1178
- });
1179
- };
1180
-
1181
- const replaceBackSlashesWithSlashes = string => string.replace(/\\/g, "/");
1182
-
1183
- /*
1184
- * - symlink documentation on Node.js:
1185
- * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_fs_symlink_target_path_type_callback
1186
- */
1187
- const readSymbolicLink = url => {
1188
- const symbolicLinkUrl = assertAndNormalizeFileUrl(url);
1189
- const symbolicLinkPath = urlToFileSystemPath(symbolicLinkUrl);
1190
- return new Promise((resolve, reject) => {
1191
- node_fs.readlink(symbolicLinkPath, (error, resolvedPath) => {
1192
- if (error) {
1193
- reject(error);
1194
- } else {
1195
- resolve(isFileSystemPath(resolvedPath) ? fileSystemPathToUrl(resolvedPath) : replaceBackSlashesWithSlashes(resolvedPath));
1196
- }
1197
- });
1198
- });
1199
- };
1200
-
1201
- const {
1202
- symlink
1203
- } = node_fs.promises;
1204
- const isWindows$1 = process.platform === "win32";
1205
- /**
1206
- * Writes a symbolic link pointing from a filesystem node to an other
1207
- * @param {string} from Where symlink is written
1208
- * @param {string} to The symlink target
1209
- * @param {"file" | "dir"} [type] Symlink type if you know it before hand
1210
- * @param {boolean} [allowUseless=false] Prevent error when symlink already exists with the same target
1211
- * @param {boolean} [allowOverwrite=false] Will replace any existing symlink
1212
- */
1213
-
1214
- const writeSymbolicLink = async ({
1215
- from,
1216
- to,
1217
- type,
1218
- allowUseless = false,
1219
- allowOverwrite = false
1220
- }) => {
1221
- const fromUrl = assertAndNormalizeFileUrl(from);
1222
- const toInfo = getToInfo(to, fromUrl);
1223
-
1224
- if (isWindows$1 && typeof type === "undefined") {
1225
- // without this if you write a symbolic link without specifying the type on windows
1226
- // you later get EPERM when doing stat on the symlink
1227
- const toStats = await readFileSystemNodeStat(toInfo.url, {
1228
- nullIfNotFound: true
1229
- });
1230
- type = toStats && toStats.isDirectory() ? "dir" : "file";
1231
- }
1232
-
1233
- const symbolicLinkPath = urlToFileSystemPath(fromUrl);
1234
-
1235
- try {
1236
- await symlink(toInfo.value, symbolicLinkPath, type);
1237
- } catch (error) {
1238
- if (error.code === "ENOENT") {
1239
- await ensureParentDirectories(fromUrl);
1240
- await symlink(toInfo.value, symbolicLinkPath, type);
1241
- return;
1242
- }
1243
-
1244
- if (error.code === "EEXIST") {
1245
- if (allowUseless) {
1246
- const existingSymbolicLinkUrl = await readSymbolicLink(fromUrl);
1247
-
1248
- if (existingSymbolicLinkUrl === toInfo.url) {
1249
- return;
1250
- }
1251
- }
1252
-
1253
- if (allowOverwrite) {
1254
- await removeFileSystemNode(fromUrl);
1255
- await symlink(toInfo.value, symbolicLinkPath, type);
1256
- return;
1257
- }
1258
- }
1259
-
1260
- throw error;
1261
- }
1262
- };
1263
-
1264
- const getToInfo = (to, fromUrl) => {
1265
- if (typeof to === "string") {
1266
- // absolute filesystem path
1267
- if (isFileSystemPath(to)) {
1268
- const url = fileSystemPathToUrl(to);
1269
- const value = to;
1270
- return {
1271
- url,
1272
- value
1273
- };
1274
- } // relative url
1275
-
1276
-
1277
- if (to.startsWith("./") || to.startsWith("../")) {
1278
- const url = resolveUrl(to, fromUrl);
1279
- const value = to;
1280
- return {
1281
- url,
1282
- value
1283
- };
1284
- } // absolute url
1285
-
1286
-
1287
- const url = resolveUrl(to, fromUrl);
1288
- const value = urlToFileSystemPath(url);
1289
- return {
1290
- url,
1291
- value
1292
- };
1293
- }
1294
-
1295
- if (to instanceof URL) {
1296
- const url = String(to);
1297
- const value = urlToFileSystemPath(url);
1298
- return {
1299
- url,
1300
- value
1301
- };
1302
- }
1303
-
1304
- throw new TypeError(`symbolic link to must be a string or an url, received ${to}`);
1305
- };
1306
-
1307
- const urlIsInsideOf = (url, otherUrl) => {
1308
- const urlObject = new URL(url);
1309
- const otherUrlObject = new URL(otherUrl);
1310
-
1311
- if (urlObject.origin !== otherUrlObject.origin) {
1312
- return false;
1313
- }
1314
-
1315
- const urlPathname = urlObject.pathname;
1316
- const otherUrlPathname = otherUrlObject.pathname;
1317
-
1318
- if (urlPathname === otherUrlPathname) {
1319
- return false;
1320
- }
1321
-
1322
- const isInside = urlPathname.startsWith(otherUrlPathname);
1323
- return isInside;
1324
- };
1325
-
1326
- /* eslint-disable import/max-dependencies */
1327
- const copyFileSystemNode = async ({
1328
- from,
1329
- to,
1330
- overwrite = false,
1331
- preserveStat = true,
1332
- preserveMtime = preserveStat,
1333
- preservePermissions = preserveStat,
1334
- allowUseless = false,
1335
- followLink = true
1336
- }) => {
1337
- const fromUrl = assertAndNormalizeFileUrl(from);
1338
- const fromPath = urlToFileSystemPath(fromUrl);
1339
- let toUrl = assertAndNormalizeFileUrl(to);
1340
- let toPath = urlToFileSystemPath(toUrl);
1341
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1342
- nullIfNotFound: true,
1343
- followLink: false
1344
- });
1345
-
1346
- if (!sourceStats) {
1347
- throw new Error(`nothing to copy at ${fromPath}`);
1348
- }
1349
-
1350
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1351
- nullIfNotFound: true,
1352
- // we force false here but in fact we will follow the destination link
1353
- // to know where we will actually move and detect useless move overrite etc..
1354
- followLink: false
1355
- });
1356
-
1357
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1358
- const linkTarget = await readSymbolicLink(toUrl);
1359
- toUrl = resolveUrl(linkTarget, toUrl);
1360
- toPath = urlToFileSystemPath(toUrl);
1361
- destinationStats = await readFileSystemNodeStat(toUrl, {
1362
- nullIfNotFound: true
1363
- });
1364
- }
1365
-
1366
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1367
- if (allowUseless) {
1368
- return;
1369
- }
1370
-
1371
- throw new Error(`cannot copy ${fromPath} because destination and source are the same`);
1372
- }
1373
-
1374
- if (destinationStats) {
1375
- const sourceType = statsToType(sourceStats);
1376
- const destinationType = statsToType(destinationStats);
1377
-
1378
- if (sourceType !== destinationType) {
1379
- throw new Error(`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`);
1380
- }
1381
-
1382
- if (!overwrite) {
1383
- throw new Error(`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`);
1384
- } // remove file, link, directory...
1385
-
1386
-
1387
- await removeFileSystemNode(toUrl, {
1388
- recursive: true,
1389
- allowUseless: true
1390
- });
1391
- } else {
1392
- await ensureParentDirectories(toUrl);
1393
- }
1394
-
1395
- if (sourceStats.isDirectory()) {
1396
- toUrl = ensureUrlTrailingSlash(toUrl);
1397
- }
1398
-
1399
- const visit = async (url, stats) => {
1400
- if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
1401
- await visitFile(url, stats);
1402
- } else if (stats.isSymbolicLink()) {
1403
- await visitSymbolicLink(url);
1404
- } else if (stats.isDirectory()) {
1405
- await visitDirectory(ensureUrlTrailingSlash(url), stats);
1406
- }
1407
- };
1408
-
1409
- const visitFile = async (fileUrl, fileStats) => {
1410
- const fileRelativeUrl = urlToRelativeUrl(fileUrl, fromUrl);
1411
- const fileCopyUrl = resolveUrl(fileRelativeUrl, toUrl);
1412
- await copyFileContentNaive(urlToFileSystemPath(fileUrl), urlToFileSystemPath(fileCopyUrl));
1413
- await copyStats(fileCopyUrl, fileStats);
1414
- };
1415
-
1416
- const visitSymbolicLink = async symbolicLinkUrl => {
1417
- const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, fromUrl);
1418
- const symbolicLinkTarget = await readSymbolicLink(symbolicLinkUrl);
1419
- const symbolicLinkTargetUrl = resolveUrl(symbolicLinkTarget, symbolicLinkUrl);
1420
- const linkIsRelative = symbolicLinkTarget.startsWith("./") || symbolicLinkTarget.startsWith("../");
1421
- let symbolicLinkCopyTarget;
1422
-
1423
- if (symbolicLinkTargetUrl === fromUrl) {
1424
- symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : toUrl;
1425
- } else if (urlIsInsideOf(symbolicLinkTargetUrl, fromUrl)) {
1426
- // symbolic link targets something inside the directory we want to copy
1427
- // reflects it inside the copied directory structure
1428
- const linkCopyTargetRelative = urlToRelativeUrl(symbolicLinkTargetUrl, fromUrl);
1429
- symbolicLinkCopyTarget = linkIsRelative ? `./${linkCopyTargetRelative}` : resolveUrl(linkCopyTargetRelative, toUrl);
1430
- } else {
1431
- // symbolic link targets something outside the directory we want to copy
1432
- symbolicLinkCopyTarget = symbolicLinkTarget;
1433
- } // we must guess ourself the type of the symlink
1434
- // because the destination might not exists because not yet copied
1435
- // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fs_symlink_target_path_type_callback
1436
-
1437
-
1438
- const targetStats = await readFileSystemNodeStat(symbolicLinkTargetUrl, {
1439
- nullIfNotFound: true,
1440
- followLink: false
1441
- });
1442
- const linkType = targetStats && targetStats.isDirectory() ? "dir" : "file";
1443
- const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, toUrl);
1444
- await writeSymbolicLink({
1445
- from: symbolicLinkCopyUrl,
1446
- to: symbolicLinkCopyTarget,
1447
- type: linkType
1448
- });
1449
- };
1450
-
1451
- const copyStats = async (toUrl, stats) => {
1452
- if (preservePermissions || preserveMtime) {
1453
- const {
1454
- mode,
1455
- mtimeMs
1456
- } = stats;
1457
-
1458
- if (preservePermissions) {
1459
- await writeFileSystemNodePermissions(toUrl, binaryFlagsToPermissions(mode));
1460
- }
1461
-
1462
- if (preserveMtime) {
1463
- await writeFileSystemNodeModificationTime(toUrl, mtimeMs);
1464
- }
1465
- }
1466
- };
1467
-
1468
- const visitDirectory = async (directoryUrl, directoryStats) => {
1469
- const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, fromUrl);
1470
- const directoryCopyUrl = resolveUrl(directoryRelativeUrl, toUrl);
1471
- await writeDirectory(directoryCopyUrl);
1472
- await copyDirectoryContent(directoryUrl);
1473
- await copyStats(directoryCopyUrl, directoryStats);
1474
- };
1475
-
1476
- const copyDirectoryContent = async directoryUrl => {
1477
- const names = await readDirectory(directoryUrl);
1478
- await Promise.all(names.map(async name => {
1479
- const fileSystemNodeUrl = resolveUrl(name, directoryUrl);
1480
- const stats = await readFileSystemNodeStat(fileSystemNodeUrl, {
1481
- followLink: false
1482
- });
1483
- await visit(fileSystemNodeUrl, stats);
1484
- }));
1485
- };
1486
-
1487
- await visit(fromUrl, sourceStats);
1488
- };
1489
-
1490
- const copyFileContentNaive = (filePath, fileDestinationPath) => {
1491
- return new Promise((resolve, reject) => {
1492
- node_fs.copyFile(filePath, fileDestinationPath, error => {
1493
- if (error) {
1494
- reject(error);
1495
- } else {
1496
- resolve();
1497
- }
1498
- });
1499
- });
1500
- };
1501
-
1502
- const copyDirectoryContent = async ({
1503
- from,
1504
- to,
1505
- overwrite,
1506
- followLink = true
1507
- } = {}) => {
1508
- const fromUrl = assertAndNormalizeDirectoryUrl(from);
1509
- const fromPath = urlToFileSystemPath(fromUrl);
1510
- let toUrl = assertAndNormalizeDirectoryUrl(to);
1511
- let toPath = urlToFileSystemPath(toUrl);
1512
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1513
- nullIfNotFound: true,
1514
- followLink: false
1515
- });
1516
-
1517
- if (!sourceStats) {
1518
- throw new Error(`no directory to copy content from at ${fromPath}`);
1519
- }
1520
-
1521
- if (!sourceStats.isDirectory()) {
1522
- const sourceType = statsToType(sourceStats);
1523
- throw new Error(`found a ${sourceType} instead of a directory at ${fromPath}`);
1524
- }
1525
-
1526
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1527
- nullIfNotFound: true,
1528
- // we force false here but in fact we will follow the destination link
1529
- // to know where we will actually move and detect useless move overrite etc..
1530
- followLink: false
1531
- });
1532
-
1533
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1534
- const linkTarget = await readSymbolicLink(toUrl);
1535
- toUrl = resolveUrl(linkTarget, toUrl);
1536
- toPath = urlToFileSystemPath(toUrl);
1537
- destinationStats = await readFileSystemNodeStat(toUrl, {
1538
- nullIfNotFound: true
1539
- });
1540
- }
1541
-
1542
- if (destinationStats === null) {
1543
- throw new Error(`no directory to copy content into at ${toPath}`);
1544
- }
1545
-
1546
- if (!destinationStats.isDirectory()) {
1547
- const destinationType = statsToType(destinationStats);
1548
- throw new Error(`destination leads to a ${destinationType} instead of a directory at ${toPath}`);
1549
- }
1550
-
1551
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1552
- throw new Error(`cannot copy directory content, source and destination are the same (${fromPath})`);
1553
- }
1554
-
1555
- const directoryEntries = await readDirectory(fromUrl);
1556
- await Promise.all(directoryEntries.map(async directoryEntry => {
1557
- const from = resolveUrl(directoryEntry, fromUrl);
1558
- const to = resolveUrl(directoryEntry, toUrl);
1559
- await copyFileSystemNode({
1560
- from,
1561
- to,
1562
- overwrite,
1563
- followLink
1564
- });
1565
- }));
1566
- };
1567
-
1568
- /*
1569
- * - file modes documentation on Node.js
1570
- * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_file_modes
1571
- */
1572
- const {
1573
- stat
1574
- } = node_fs.promises;
1575
- const readFileSystemNodePermissions = async source => {
1576
- const sourceUrl = assertAndNormalizeFileUrl(source);
1577
- const sourcePath = urlToFileSystemPath(sourceUrl);
1578
- const {
1579
- mode
1580
- } = await stat(sourcePath);
1581
- return binaryFlagsToPermissions(mode);
1582
- };
1583
-
1584
- const grantPermissionsOnFileSystemNode = async (source, {
1585
- read = false,
1586
- write = false,
1587
- execute = false
1588
- }) => {
1589
- const sourceUrl = assertAndNormalizeFileUrl(source);
1590
- const filePermissions = await readFileSystemNodePermissions(sourceUrl);
1591
- await writeFileSystemNodePermissions(sourceUrl, {
1592
- owner: {
1593
- read,
1594
- write,
1595
- execute
1596
- },
1597
- group: {
1598
- read,
1599
- write,
1600
- execute
1601
- },
1602
- others: {
1603
- read,
1604
- write,
1605
- execute
1606
- }
1607
- });
1608
- return async () => {
1609
- await writeFileSystemNodePermissions(sourceUrl, filePermissions);
1610
- };
1611
- };
1612
-
1613
- const listFilesMatching = async (patterns, directoryUrl = process.cwd()) => {
1614
- if (typeof patterns === "string") {
1615
- patterns = [patterns];
1616
- } else if (Array.isArray(patterns)) {
1617
- throw new TypeError(`patterns must be a string or an array of strings, got ${patterns}`);
1618
- }
1619
-
1620
- const patternMap = {};
1621
- patterns.forEach((pattern, index) => {
1622
- if (typeof pattern !== "string") {
1623
- throw new TypeError(`every pattern must be a string, found ${pattern} at index ${index}`);
1624
- }
1625
-
1626
- patternMap[pattern] = true;
1627
- });
1628
- const fileDatas = await collectFiles({
1629
- directoryUrl,
1630
- structuredMetaMap: {
1631
- matches: patternMap
1632
- },
1633
- predicate: ({
1634
- matches
1635
- }) => matches
1636
- });
1637
- return fileDatas.map(({
1638
- url
1639
- }) => url);
1640
- };
1641
-
1642
- const memoize = compute => {
1643
- let memoized = false;
1644
- let memoizedValue;
1645
-
1646
- const fnWithMemoization = (...args) => {
1647
- if (memoized) {
1648
- return memoizedValue;
1649
- } // if compute is recursive wait for it to be fully done before storing the value
1650
- // so set memoized boolean after the call
1651
-
1652
-
1653
- memoizedValue = compute(...args);
1654
- memoized = true;
1655
- return memoizedValue;
1656
- };
1657
-
1658
- fnWithMemoization.forget = () => {
1659
- const value = memoizedValue;
1660
- memoized = false;
1661
- memoizedValue = undefined;
1662
- return value;
1663
- };
1664
-
1665
- return fnWithMemoization;
1666
- };
1667
-
1668
- /* eslint-disable import/max-dependencies */
1669
- const moveFileSystemNode = async ({
1670
- from,
1671
- to,
1672
- overwrite = false,
1673
- allowUseless = false,
1674
- followLink = true
1675
- }) => {
1676
- const fromUrl = assertAndNormalizeFileUrl(from);
1677
- const fromPath = urlToFileSystemPath(fromUrl);
1678
- let toUrl = assertAndNormalizeFileUrl(to);
1679
- let toPath = urlToFileSystemPath(toUrl);
1680
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1681
- nullIfNotFound: true,
1682
- followLink: false
1683
- });
1684
-
1685
- if (!sourceStats) {
1686
- throw new Error(`nothing to move from ${fromPath}`);
1687
- }
1688
-
1689
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1690
- nullIfNotFound: true,
1691
- // we force false here but in fact we will follow the destination link
1692
- // to know where we will actually move and detect useless move overrite etc..
1693
- followLink: false
1694
- });
1695
-
1696
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1697
- const linkTarget = await readSymbolicLink(toUrl);
1698
- toUrl = resolveUrl(linkTarget, toUrl);
1699
- toPath = urlToFileSystemPath(toUrl);
1700
- destinationStats = await readFileSystemNodeStat(toUrl, {
1701
- nullIfNotFound: true
1702
- });
1703
- }
1704
-
1705
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1706
- if (allowUseless) {
1707
- return;
1708
- }
1709
-
1710
- throw new Error(`no move needed for ${fromPath} because destination and source are the same`);
1711
- }
1712
-
1713
- if (destinationStats) {
1714
- const sourceType = statsToType(sourceStats);
1715
- const destinationType = statsToType(destinationStats);
1716
-
1717
- if (sourceType !== destinationType) {
1718
- throw new Error(`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`);
1719
- }
1720
-
1721
- if (!overwrite) {
1722
- throw new Error(`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`);
1723
- } // remove file, link, directory...
1724
-
1725
-
1726
- await removeFileSystemNode(toUrl, {
1727
- recursive: true
1728
- });
1729
- } else {
1730
- await ensureParentDirectories(toUrl);
1731
- }
1732
-
1733
- await moveNaive(fromPath, toPath, {
1734
- handleCrossDeviceError: async () => {
1735
- await copyFileSystemNode({
1736
- from: fromUrl,
1737
- to: toUrl,
1738
- preserveStat: true
1739
- });
1740
- await removeFileSystemNode(fromUrl, {
1741
- recursive: true
1742
- });
1743
- }
1744
- });
1745
- };
1746
-
1747
- const moveNaive = (fromPath, destinationPath, {
1748
- handleCrossDeviceError = null
1749
- } = {}) => {
1750
- return new Promise((resolve, reject) => {
1751
- node_fs.rename(fromPath, destinationPath, error => {
1752
- if (error) {
1753
- if (handleCrossDeviceError && error.code === "EXDEV") {
1754
- resolve(handleCrossDeviceError(error));
1755
- } else {
1756
- reject(error);
1757
- }
1758
- } else {
1759
- resolve();
1760
- }
1761
- });
1762
- });
1763
- };
1764
-
1765
- const moveDirectoryContent = async ({
1766
- from,
1767
- to,
1768
- overwrite,
1769
- followLink = true
1770
- } = {}) => {
1771
- const fromUrl = assertAndNormalizeDirectoryUrl(from);
1772
- const fromPath = urlToFileSystemPath(fromUrl);
1773
- let toUrl = assertAndNormalizeDirectoryUrl(to);
1774
- let toPath = urlToFileSystemPath(toUrl);
1775
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1776
- nullIfNotFound: true,
1777
- followLink: false
1778
- });
1779
-
1780
- if (!sourceStats) {
1781
- throw new Error(`no directory to move content from at ${fromPath}`);
1782
- }
1783
-
1784
- if (!sourceStats.isDirectory()) {
1785
- const sourceType = statsToType(sourceStats);
1786
- throw new Error(`found a ${sourceType} instead of a directory at ${fromPath}`);
1787
- }
1788
-
1789
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1790
- nullIfNotFound: true,
1791
- // we force false here but in fact we will follow the destination link
1792
- // to know where we will actually move and detect useless move overrite etc..
1793
- followLink: false
1794
- });
1795
-
1796
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1797
- const linkTarget = await readSymbolicLink(toUrl);
1798
- toUrl = resolveUrl(linkTarget, toUrl);
1799
- toPath = urlToFileSystemPath(toUrl);
1800
- destinationStats = await readFileSystemNodeStat(toUrl, {
1801
- nullIfNotFound: true
1802
- });
1803
- }
1804
-
1805
- if (destinationStats === null) {
1806
- throw new Error(`no directory to move content into at ${toPath}`);
1807
- }
1808
-
1809
- if (!destinationStats.isDirectory()) {
1810
- const destinationType = statsToType(destinationStats);
1811
- throw new Error(`destination leads to a ${destinationType} instead of a directory at ${toPath}`);
1812
- }
1813
-
1814
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1815
- throw new Error(`cannot move directory content, source and destination are the same (${fromPath})`);
1816
- }
1817
-
1818
- const directoryEntries = await readDirectory(fromUrl);
1819
- await Promise.all(directoryEntries.map(async directoryEntry => {
1820
- const from = resolveUrl(directoryEntry, fromUrl);
1821
- const to = resolveUrl(directoryEntry, toUrl);
1822
- await moveFileSystemNode({
1823
- from,
1824
- to,
1825
- overwrite,
1826
- followLink
1827
- });
1828
- }));
1829
- };
1830
-
1831
- const readFilePromisified = util.promisify(fs.readFile);
1832
- const readFile = async (value, {
1833
- as = "string"
1834
- } = {}) => {
1835
- const fileUrl = assertAndNormalizeFileUrl(value);
1836
- const filePath = urlToFileSystemPath(fileUrl);
1837
- const buffer = await readFilePromisified(filePath);
1838
-
1839
- if (as === "buffer") {
1840
- return buffer;
1841
- }
1842
-
1843
- if (as === "string") {
1844
- return buffer.toString();
1845
- }
1846
-
1847
- if (as === "json") {
1848
- return JSON.parse(buffer.toString());
1849
- }
1850
-
1851
- throw new Error(`as must be one of buffer,string,json, received ${as}.`);
1852
- };
1853
-
1854
- const readFileSystemNodeModificationTime = async source => {
1855
- const stats = await readFileSystemNodeStat(source);
1856
- return Math.floor(stats.mtimeMs);
1857
- };
1858
-
1859
- const fileSystemNodeToTypeOrNull = url => {
1860
- const path = urlToFileSystemPath(url);
1861
-
1862
- try {
1863
- const stats = fs.statSync(path);
1864
- return statsToType(stats);
1865
- } catch (e) {
1866
- if (e.code === "ENOENT") {
1867
- return null;
1868
- }
1869
-
1870
- throw e;
1871
- }
1872
- };
1873
-
1874
- const isWindows = process.platform === "win32";
1875
- const createWatcher = (sourcePath, options) => {
1876
- const watcher = fs.watch(sourcePath, options);
1877
-
1878
- if (isWindows) {
1879
- watcher.on("error", async error => {
1880
- // https://github.com/joyent/node/issues/4337
1881
- if (error.code === "EPERM") {
1882
- try {
1883
- const fd = fs.openSync(sourcePath, "r");
1884
- fs.closeSync(fd);
1885
- } catch (e) {
1886
- if (e.code === "ENOENT") {
1887
- return;
1888
- }
1889
-
1890
- console.error(`error while fixing windows eperm: ${e.stack}`);
1891
- throw error;
1892
- }
1893
- } else {
1894
- throw error;
1895
- }
1896
- });
1897
- }
1898
-
1899
- return watcher;
1900
- };
1901
-
1902
- const trackRessources = () => {
1903
- const callbackArray = [];
1904
-
1905
- const registerCleanupCallback = callback => {
1906
- if (typeof callback !== "function") throw new TypeError(`callback must be a function
1907
- callback: ${callback}`);
1908
- callbackArray.push(callback);
1909
- return () => {
1910
- const index = callbackArray.indexOf(callback);
1911
- if (index > -1) callbackArray.splice(index, 1);
1912
- };
1913
- };
1914
-
1915
- const cleanup = async reason => {
1916
- const localCallbackArray = callbackArray.slice();
1917
- await Promise.all(localCallbackArray.map(callback => callback(reason)));
1918
- };
1919
-
1920
- return {
1921
- registerCleanupCallback,
1922
- cleanup
1923
- };
1924
- };
1925
-
1926
- /* eslint-disable import/max-dependencies */
1927
- const isLinux = process.platform === "linux"; // linux does not support recursive option
1928
-
1929
- const fsWatchSupportsRecursive = !isLinux;
1930
- const registerDirectoryLifecycle = (source, {
1931
- added,
1932
- updated,
1933
- removed,
1934
- watchDescription = {
1935
- "./**/*": true
1936
- },
1937
- notifyExistent = false,
1938
- keepProcessAlive = true,
1939
- recursive = false
1940
- }) => {
1941
- const sourceUrl = ensureUrlTrailingSlash(assertAndNormalizeFileUrl(source));
1942
-
1943
- if (!undefinedOrFunction$1(added)) {
1944
- throw new TypeError(`added must be a function or undefined, got ${added}`);
1945
- }
1946
-
1947
- if (!undefinedOrFunction$1(updated)) {
1948
- throw new TypeError(`updated must be a function or undefined, got ${updated}`);
1949
- }
1950
-
1951
- if (!undefinedOrFunction$1(removed)) {
1952
- throw new TypeError(`removed must be a function or undefined, got ${removed}`);
1953
- }
1954
-
1955
- const structuredMetaMap = urlMeta.normalizeStructuredMetaMap({
1956
- watch: watchDescription
1957
- }, sourceUrl);
1958
-
1959
- const entryShouldBeWatched = ({
1960
- relativeUrl,
1961
- type
1962
- }) => {
1963
- const entryUrl = resolveUrl(relativeUrl, sourceUrl);
1964
-
1965
- if (type === "directory") {
1966
- const canContainEntryToWatch = urlMeta.urlCanContainsMetaMatching({
1967
- url: `${entryUrl}/`,
1968
- structuredMetaMap,
1969
- predicate: ({
1970
- watch
1971
- }) => watch
1972
- });
1973
- return canContainEntryToWatch;
1974
- }
1975
-
1976
- const entryMeta = urlMeta.urlToMeta({
1977
- url: entryUrl,
1978
- structuredMetaMap
1979
- });
1980
- return entryMeta.watch;
1981
- };
1982
-
1983
- const tracker = trackRessources();
1984
- const contentMap = new Map();
1985
-
1986
- const handleDirectoryEvent = ({
1987
- directoryRelativeUrl,
1988
- filename,
1989
- eventType
1990
- }) => {
1991
- if (filename) {
1992
- if (directoryRelativeUrl) {
1993
- handleChange(`${directoryRelativeUrl}/${filename}`);
1994
- } else {
1995
- handleChange(`${filename}`);
1996
- }
1997
- } else if ((removed || added) && eventType === "rename") {
1998
- // we might receive `rename` without filename
1999
- // in that case we try to find ourselves which file was removed.
2000
- let relativeUrlCandidateArray = Array.from(contentMap.keys());
2001
-
2002
- if (recursive && !fsWatchSupportsRecursive) {
2003
- relativeUrlCandidateArray = relativeUrlCandidateArray.filter(relativeUrlCandidate => {
2004
- if (!directoryRelativeUrl) {
2005
- // ensure entry is top level
2006
- if (relativeUrlCandidate.includes("/")) return false;
2007
- return true;
2008
- } // entry not inside this directory
2009
-
2010
-
2011
- if (!relativeUrlCandidate.startsWith(directoryRelativeUrl)) return false;
2012
- const afterDirectory = relativeUrlCandidate.slice(directoryRelativeUrl.length + 1); // deep inside this directory
2013
-
2014
- if (afterDirectory.includes("/")) return false;
2015
- return true;
2016
- });
2017
- }
2018
-
2019
- const removedEntryRelativeUrl = relativeUrlCandidateArray.find(relativeUrlCandidate => {
2020
- const entryUrl = resolveUrl(relativeUrlCandidate, sourceUrl);
2021
- const type = fileSystemNodeToTypeOrNull(entryUrl);
2022
- return type === null;
2023
- });
2024
-
2025
- if (removedEntryRelativeUrl) {
2026
- handleEntryLost({
2027
- relativeUrl: removedEntryRelativeUrl,
2028
- type: contentMap.get(removedEntryRelativeUrl)
2029
- });
2030
- }
2031
- }
2032
- };
2033
-
2034
- const handleChange = relativeUrl => {
2035
- const entryUrl = resolveUrl(relativeUrl, sourceUrl);
2036
- const previousType = contentMap.get(relativeUrl);
2037
- const type = fileSystemNodeToTypeOrNull(entryUrl);
2038
-
2039
- if (!entryShouldBeWatched({
2040
- relativeUrl,
2041
- type
2042
- })) {
2043
- return;
2044
- } // it's something new
2045
-
2046
-
2047
- if (!previousType) {
2048
- if (type !== null) {
2049
- handleEntryFound({
2050
- relativeUrl,
2051
- type,
2052
- existent: false
2053
- });
2054
- }
2055
-
2056
- return;
2057
- } // it existed but now it's not here anymore
2058
-
2059
-
2060
- if (type === null) {
2061
- handleEntryLost({
2062
- relativeUrl,
2063
- type: previousType
2064
- });
2065
- return;
2066
- } // it existed and was replaced by something else
2067
- // we don't handle this as an update. We rather say the ressource
2068
- // is lost and something else is found (call removed() then added())
2069
-
2070
-
2071
- if (previousType !== type) {
2072
- handleEntryLost({
2073
- relativeUrl,
2074
- type: previousType
2075
- });
2076
- handleEntryFound({
2077
- relativeUrl,
2078
- type
2079
- });
2080
- return;
2081
- } // a directory cannot really be updated in way that matters for us
2082
- // filesystem is trying to tell us the directory content have changed
2083
- // but we don't care about that
2084
- // we'll already be notified about what has changed
2085
-
2086
-
2087
- if (type === "directory") {
2088
- return;
2089
- } // something has changed at this relativeUrl (the file existed and was not deleted)
2090
- // it's possible to get there and there is no real update
2091
- // (file content is the same and file mtime is the same).
2092
- // In short filesystem is sometimes "lying"
2093
- // Not trying to guard against that because:
2094
- // - hurt perfs a lot
2095
- // - it happens very rarely
2096
- // - it's not really a concern in practice
2097
- // - filesystem did not send an event out of nowhere:
2098
- // something occured but we don't know what with the information we have.
2099
-
2100
-
2101
- if (updated) {
2102
- updated({
2103
- relativeUrl,
2104
- type
2105
- });
2106
- }
2107
- };
2108
-
2109
- const handleEntryFound = ({
2110
- relativeUrl,
2111
- type,
2112
- existent
2113
- }) => {
2114
- if (!entryShouldBeWatched({
2115
- relativeUrl,
2116
- type
2117
- })) {
2118
- return;
2119
- }
2120
-
2121
- contentMap.set(relativeUrl, type);
2122
- const entryUrl = resolveUrl(relativeUrl, sourceUrl);
2123
-
2124
- if (type === "directory") {
2125
- visitDirectory({
2126
- directoryUrl: `${entryUrl}/`,
2127
- entryFound: entry => {
2128
- handleEntryFound({
2129
- relativeUrl: `${relativeUrl}/${entry.relativeUrl}`,
2130
- type: entry.type,
2131
- existent
2132
- });
2133
- }
2134
- });
2135
- }
2136
-
2137
- if (added) {
2138
- if (existent) {
2139
- if (notifyExistent) {
2140
- added({
2141
- relativeUrl,
2142
- type,
2143
- existent: true
2144
- });
2145
- }
2146
- } else {
2147
- added({
2148
- relativeUrl,
2149
- type
2150
- });
2151
- }
2152
- } // we must watch manually every directory we find
2153
-
2154
-
2155
- if (!fsWatchSupportsRecursive && type === "directory") {
2156
- const watcher = createWatcher(urlToFileSystemPath(entryUrl), {
2157
- persistent: keepProcessAlive
2158
- });
2159
- tracker.registerCleanupCallback(() => {
2160
- watcher.close();
2161
- });
2162
- watcher.on("change", (eventType, filename) => {
2163
- handleDirectoryEvent({
2164
- directoryRelativeUrl: relativeUrl,
2165
- filename: filename ? replaceBackSlashesWithSlashes(filename) : "",
2166
- eventType
2167
- });
2168
- });
2169
- }
2170
- };
2171
-
2172
- const handleEntryLost = ({
2173
- relativeUrl,
2174
- type
2175
- }) => {
2176
- contentMap.delete(relativeUrl);
2177
-
2178
- if (removed) {
2179
- removed({
2180
- relativeUrl,
2181
- type
2182
- });
2183
- }
2184
- };
2185
-
2186
- visitDirectory({
2187
- directoryUrl: sourceUrl,
2188
- entryFound: ({
2189
- relativeUrl,
2190
- type
2191
- }) => {
2192
- handleEntryFound({
2193
- relativeUrl,
2194
- type,
2195
- existent: true
2196
- });
2197
- }
2198
- });
2199
- const watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
2200
- recursive: recursive && fsWatchSupportsRecursive,
2201
- persistent: keepProcessAlive
2202
- });
2203
- tracker.registerCleanupCallback(() => {
2204
- watcher.close();
2205
- });
2206
- watcher.on("change", (eventType, fileSystemPath) => {
2207
- handleDirectoryEvent({ ...fileSystemPathToDirectoryRelativeUrlAndFilename(fileSystemPath),
2208
- eventType
2209
- });
2210
- });
2211
- return tracker.cleanup;
2212
- };
2213
-
2214
- const undefinedOrFunction$1 = value => typeof value === "undefined" || typeof value === "function";
2215
-
2216
- const visitDirectory = ({
2217
- directoryUrl,
2218
- entryFound
2219
- }) => {
2220
- const directoryPath = urlToFileSystemPath(directoryUrl);
2221
- fs.readdirSync(directoryPath).forEach(entry => {
2222
- const entryUrl = resolveUrl(entry, directoryUrl);
2223
- const type = fileSystemNodeToTypeOrNull(entryUrl);
2224
-
2225
- if (type === null) {
2226
- return;
2227
- }
2228
-
2229
- const relativeUrl = urlToRelativeUrl(entryUrl, directoryUrl);
2230
- entryFound({
2231
- relativeUrl,
2232
- type
2233
- });
2234
- });
2235
- };
2236
-
2237
- const fileSystemPathToDirectoryRelativeUrlAndFilename = path => {
2238
- if (!path) {
2239
- return {
2240
- directoryRelativeUrl: "",
2241
- filename: ""
2242
- };
2243
- }
2244
-
2245
- const normalizedPath = replaceBackSlashesWithSlashes(path);
2246
- const slashLastIndex = normalizedPath.lastIndexOf("/");
2247
-
2248
- if (slashLastIndex === -1) {
2249
- return {
2250
- directoryRelativeUrl: "",
2251
- filename: normalizedPath
2252
- };
2253
- }
2254
-
2255
- const directoryRelativeUrl = normalizedPath.slice(0, slashLastIndex);
2256
- const filename = normalizedPath.slice(slashLastIndex + 1);
2257
- return {
2258
- directoryRelativeUrl,
2259
- filename
2260
- };
2261
- };
2262
-
2263
- const registerFileLifecycle = (source, {
2264
- added,
2265
- updated,
2266
- removed,
2267
- notifyExistent = false,
2268
- keepProcessAlive = true
2269
- }) => {
2270
- const sourceUrl = assertAndNormalizeFileUrl(source);
2271
-
2272
- if (!undefinedOrFunction(added)) {
2273
- throw new TypeError(`added must be a function or undefined, got ${added}`);
2274
- }
2275
-
2276
- if (!undefinedOrFunction(updated)) {
2277
- throw new TypeError(`updated must be a function or undefined, got ${updated}`);
2278
- }
2279
-
2280
- if (!undefinedOrFunction(removed)) {
2281
- throw new TypeError(`removed must be a function or undefined, got ${removed}`);
2282
- }
2283
-
2284
- const tracker = trackRessources();
2285
-
2286
- const handleFileFound = ({
2287
- existent
2288
- }) => {
2289
- const fileMutationStopWatching = watchFileMutation(sourceUrl, {
2290
- updated,
2291
- removed: () => {
2292
- fileMutationStopTracking();
2293
- watchFileAdded();
2294
-
2295
- if (removed) {
2296
- removed();
2297
- }
2298
- },
2299
- keepProcessAlive
2300
- });
2301
- const fileMutationStopTracking = tracker.registerCleanupCallback(fileMutationStopWatching);
2302
-
2303
- if (added) {
2304
- if (existent) {
2305
- if (notifyExistent) {
2306
- added({
2307
- existent: true
2308
- });
2309
- }
2310
- } else {
2311
- added({});
2312
- }
2313
- }
2314
- };
2315
-
2316
- const watchFileAdded = () => {
2317
- const fileCreationStopWatching = watchFileCreation(sourceUrl, () => {
2318
- fileCreationgStopTracking();
2319
- handleFileFound({
2320
- existent: false
2321
- });
2322
- }, keepProcessAlive);
2323
- const fileCreationgStopTracking = tracker.registerCleanupCallback(fileCreationStopWatching);
2324
- };
2325
-
2326
- const sourceType = fileSystemNodeToTypeOrNull(sourceUrl);
2327
-
2328
- if (sourceType === null) {
2329
- if (added) {
2330
- watchFileAdded();
2331
- } else {
2332
- throw new Error(`${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`);
2333
- }
2334
- } else if (sourceType === "file") {
2335
- handleFileFound({
2336
- existent: true
2337
- });
2338
- } else {
2339
- throw new Error(`${urlToFileSystemPath(sourceUrl)} must lead to a file, type found instead`);
2340
- }
2341
-
2342
- return tracker.cleanup;
2343
- };
2344
-
2345
- const undefinedOrFunction = value => typeof value === "undefined" || typeof value === "function";
2346
-
2347
- const watchFileCreation = (source, callback, keepProcessAlive) => {
2348
- const sourcePath = urlToFileSystemPath(source);
2349
- const sourceFilename = path.basename(sourcePath);
2350
- const directoryPath = path.dirname(sourcePath);
2351
- let directoryWatcher = createWatcher(directoryPath, {
2352
- persistent: keepProcessAlive
2353
- });
2354
- directoryWatcher.on("change", (eventType, filename) => {
2355
- if (filename && filename !== sourceFilename) return;
2356
- const type = fileSystemNodeToTypeOrNull(source); // ignore if something else with that name gets created
2357
- // we are only interested into files
2358
-
2359
- if (type !== "file") return;
2360
- directoryWatcher.close();
2361
- directoryWatcher = undefined;
2362
- callback();
2363
- });
2364
- return () => {
2365
- if (directoryWatcher) {
2366
- directoryWatcher.close();
2367
- }
2368
- };
2369
- };
2370
-
2371
- const watchFileMutation = (sourceUrl, {
2372
- updated,
2373
- removed,
2374
- keepProcessAlive
2375
- }) => {
2376
- let watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
2377
- persistent: keepProcessAlive
2378
- });
2379
- watcher.on("change", () => {
2380
- const sourceType = fileSystemNodeToTypeOrNull(sourceUrl);
2381
-
2382
- if (sourceType === null) {
2383
- watcher.close();
2384
- watcher = undefined;
2385
-
2386
- if (removed) {
2387
- removed();
2388
- }
2389
- } else if (sourceType === "file") {
2390
- if (updated) {
2391
- updated();
2392
- }
2393
- }
2394
- });
2395
- return () => {
2396
- if (watcher) {
2397
- watcher.close();
2398
- }
2399
- };
2400
- };
2401
-
2402
- const resolveDirectoryUrl = (specifier, baseUrl) => {
2403
- const url = resolveUrl(specifier, baseUrl);
2404
- return ensureUrlTrailingSlash(url);
2405
- };
2406
-
2407
- const {
2408
- access
2409
- } = fs.promises;
2410
- const {
2411
- // F_OK,
2412
- R_OK,
2413
- W_OK,
2414
- X_OK
2415
- } = fs.constants;
2416
- const testFileSystemNodePermissions = async (source, {
2417
- read = false,
2418
- write = false,
2419
- execute = false,
2420
- allowedIfNotFound = false
2421
- } = {}) => {
2422
- const sourceUrl = assertAndNormalizeFileUrl(source);
2423
- const sourcePath = urlToFileSystemPath(sourceUrl);
2424
- let binaryFlags = 0; // if (visible) binaryFlags |= F_OK
2425
-
2426
- if (read) binaryFlags |= R_OK;
2427
- if (write) binaryFlags |= W_OK;
2428
- if (execute) binaryFlags |= X_OK;
2429
-
2430
- try {
2431
- await access(sourcePath, binaryFlags);
2432
- return true;
2433
- } catch (error) {
2434
- if (error.code === "ENOENT") {
2435
- if (allowedIfNotFound) {
2436
- return true;
2437
- }
2438
-
2439
- throw error;
2440
- }
2441
-
2442
- return false;
2443
- }
2444
- };
2445
-
2446
- const urlToScheme = url => {
2447
- const urlString = String(url);
2448
- const colonIndex = urlString.indexOf(":");
2449
-
2450
- if (colonIndex === -1) {
2451
- return "";
2452
- }
2453
-
2454
- const scheme = urlString.slice(0, colonIndex);
2455
- return scheme;
2456
- };
2457
-
2458
- const urlToRessource = url => {
2459
- const scheme = urlToScheme(url);
2460
-
2461
- if (scheme === "file") {
2462
- const urlAsStringWithoutFileProtocol = String(url).slice("file://".length);
2463
- return urlAsStringWithoutFileProtocol;
2464
- }
2465
-
2466
- if (scheme === "https" || scheme === "http") {
2467
- // remove origin
2468
- const afterProtocol = String(url).slice(scheme.length + "://".length);
2469
- const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
2470
- const urlAsStringWithoutOrigin = afterProtocol.slice(pathnameSlashIndex);
2471
- return urlAsStringWithoutOrigin;
2472
- }
2473
-
2474
- const urlAsStringWithoutProtocol = String(url).slice(scheme.length + 1);
2475
- return urlAsStringWithoutProtocol;
2476
- };
2477
-
2478
- const urlToPathname = url => {
2479
- const ressource = urlToRessource(url);
2480
- const pathname = ressourceToPathname(ressource);
2481
- return pathname;
2482
- };
2483
-
2484
- const ressourceToPathname = ressource => {
2485
- const searchSeparatorIndex = ressource.indexOf("?");
2486
- return searchSeparatorIndex === -1 ? ressource : ressource.slice(0, searchSeparatorIndex);
2487
- };
2488
-
2489
- const urlToFilename = url => {
2490
- const pathname = urlToPathname(url);
2491
- const pathnameBeforeLastSlash = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
2492
- const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/");
2493
- const filename = slashLastIndex === -1 ? pathnameBeforeLastSlash : pathnameBeforeLastSlash.slice(slashLastIndex + 1);
2494
- return filename;
2495
- };
2496
-
2497
- const urlToBasename = url => {
2498
- const filename = urlToFilename(url);
2499
- const dotLastIndex = filename.lastIndexOf(".");
2500
- const basename = dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex);
2501
- return basename;
2502
- };
2503
-
2504
- const urlToExtension = url => {
2505
- const pathname = urlToPathname(url);
2506
- return pathnameToExtension(pathname);
2507
- };
2508
-
2509
- const pathnameToExtension = pathname => {
2510
- const slashLastIndex = pathname.lastIndexOf("/");
2511
-
2512
- if (slashLastIndex !== -1) {
2513
- pathname = pathname.slice(slashLastIndex + 1);
2514
- }
2515
-
2516
- const dotLastIndex = pathname.lastIndexOf(".");
2517
- if (dotLastIndex === -1) return ""; // if (dotLastIndex === pathname.length - 1) return ""
2518
-
2519
- const extension = pathname.slice(dotLastIndex);
2520
- return extension;
2521
- };
2522
-
2523
- const urlToOrigin = url => {
2524
- const urlString = String(url);
2525
-
2526
- if (urlString.startsWith("file://")) {
2527
- return `file://`;
2528
- }
2529
-
2530
- return new URL(urlString).origin;
2531
- };
2532
-
2533
- const urlToParentUrl = url => {
2534
- const ressource = urlToRessource(url);
2535
- const slashLastIndex = ressource.lastIndexOf("/");
2536
-
2537
- if (slashLastIndex === -1) {
2538
- const urlAsString = String(url);
2539
- return urlAsString;
2540
- }
2541
-
2542
- const lastCharacterIndex = ressource.length - 1;
2543
-
2544
- if (slashLastIndex === lastCharacterIndex) {
2545
- const slashPreviousIndex = ressource.lastIndexOf("/", lastCharacterIndex - 1);
2546
-
2547
- if (slashPreviousIndex === -1) {
2548
- const urlAsString = String(url);
2549
- return urlAsString;
2550
- }
2551
-
2552
- const origin = urlToOrigin(url);
2553
- const parentUrl = `${origin}${ressource.slice(0, slashPreviousIndex + 1)}`;
2554
- return parentUrl;
2555
- }
2556
-
2557
- const origin = urlToOrigin(url);
2558
- const parentUrl = `${origin}${ressource.slice(0, slashLastIndex + 1)}`;
2559
- return parentUrl;
2560
- };
2561
-
2562
- const {
2563
- writeFile: writeFileNode
2564
- } = fs.promises;
2565
- const writeFile = async (destination, content = "") => {
2566
- const destinationUrl = assertAndNormalizeFileUrl(destination);
2567
- const destinationPath = urlToFileSystemPath(destinationUrl);
2568
-
2569
- try {
2570
- await writeFileNode(destinationPath, content);
2571
- } catch (error) {
2572
- if (error.code === "ENOENT") {
2573
- await ensureParentDirectories(destinationUrl);
2574
- await writeFileNode(destinationPath, content);
2575
- return;
2576
- }
2577
-
2578
- throw error;
2579
- }
2580
- };
2581
-
2582
- Object.defineProperty(exports, 'applyPatternMatching', {
2583
- enumerable: true,
2584
- get: function () { return urlMeta.applyPatternMatching; }
2585
- });
2586
- Object.defineProperty(exports, 'normalizeStructuredMetaMap', {
2587
- enumerable: true,
2588
- get: function () { return urlMeta.normalizeStructuredMetaMap; }
2589
- });
2590
- Object.defineProperty(exports, 'urlCanContainsMetaMatching', {
2591
- enumerable: true,
2592
- get: function () { return urlMeta.urlCanContainsMetaMatching; }
2593
- });
2594
- Object.defineProperty(exports, 'urlToMeta', {
2595
- enumerable: true,
2596
- get: function () { return urlMeta.urlToMeta; }
2597
- });
2598
- exports.assertAndNormalizeDirectoryUrl = assertAndNormalizeDirectoryUrl;
2599
- exports.assertAndNormalizeFileUrl = assertAndNormalizeFileUrl;
2600
- exports.assertDirectoryPresence = assertDirectoryPresence;
2601
- exports.assertFilePresence = assertFilePresence;
2602
- exports.bufferToEtag = bufferToEtag;
2603
- exports.collectDirectoryMatchReport = collectDirectoryMatchReport;
2604
- exports.collectFiles = collectFiles;
2605
- exports.comparePathnames = comparePathnames;
2606
- exports.copyDirectoryContent = copyDirectoryContent;
2607
- exports.copyFileSystemNode = copyFileSystemNode;
2608
- exports.ensureEmptyDirectory = ensureEmptyDirectory;
2609
- exports.ensureParentDirectories = ensureParentDirectories;
2610
- exports.ensureWindowsDriveLetter = ensureWindowsDriveLetter;
2611
- exports.fileSystemPathToUrl = fileSystemPathToUrl;
2612
- exports.grantPermissionsOnFileSystemNode = grantPermissionsOnFileSystemNode;
2613
- exports.isFileSystemPath = isFileSystemPath;
2614
- exports.listFilesMatching = listFilesMatching;
2615
- exports.memoize = memoize;
2616
- exports.moveDirectoryContent = moveDirectoryContent;
2617
- exports.moveFileSystemNode = moveFileSystemNode;
2618
- exports.readDirectory = readDirectory;
2619
- exports.readFile = readFile;
2620
- exports.readFileSystemNodeModificationTime = readFileSystemNodeModificationTime;
2621
- exports.readFileSystemNodePermissions = readFileSystemNodePermissions;
2622
- exports.readFileSystemNodeStat = readFileSystemNodeStat;
2623
- exports.readSymbolicLink = readSymbolicLink;
2624
- exports.registerDirectoryLifecycle = registerDirectoryLifecycle;
2625
- exports.registerFileLifecycle = registerFileLifecycle;
2626
- exports.removeFileSystemNode = removeFileSystemNode;
2627
- exports.resolveDirectoryUrl = resolveDirectoryUrl;
2628
- exports.resolveUrl = resolveUrl;
2629
- exports.testFileSystemNodePermissions = testFileSystemNodePermissions;
2630
- exports.urlIsInsideOf = urlIsInsideOf;
2631
- exports.urlToBasename = urlToBasename;
2632
- exports.urlToExtension = urlToExtension;
2633
- exports.urlToFileSystemPath = urlToFileSystemPath;
2634
- exports.urlToFilename = urlToFilename;
2635
- exports.urlToOrigin = urlToOrigin;
2636
- exports.urlToParentUrl = urlToParentUrl;
2637
- exports.urlToPathname = urlToPathname;
2638
- exports.urlToRelativeUrl = urlToRelativeUrl;
2639
- exports.urlToRessource = urlToRessource;
2640
- exports.urlToScheme = urlToScheme;
2641
- exports.writeDirectory = writeDirectory;
2642
- exports.writeFile = writeFile;
2643
- exports.writeFileSystemNodeModificationTime = writeFileSystemNodeModificationTime;
2644
- exports.writeFileSystemNodePermissions = writeFileSystemNodePermissions;
2645
- exports.writeSymbolicLink = writeSymbolicLink;
2646
-
2647
- //# sourceMappingURL=jsenv_filesystem.cjs.map