@jsenv/filesystem 2.5.1 → 2.7.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,2703 +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 abort = require('@jsenv/abort');
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
- let sourceUrl = assertAndNormalizeFileUrl(source);
304
- if (sourceUrl.endsWith("/")) sourceUrl = sourceUrl.slice(0, -1);
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
- signal = new AbortController().signal,
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
- const collectOperation = abort.Abort.startOperation();
640
- collectOperation.addAbortSignal(signal);
641
-
642
- const visitDirectory = async directoryUrl => {
643
- collectOperation.throwIfAborted();
644
- const directoryItems = await readDirectory(directoryUrl);
645
- await Promise.all(directoryItems.map(async directoryItem => {
646
- const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`;
647
- const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl);
648
- collectOperation.throwIfAborted();
649
- const directoryChildNodeStats = await readFileSystemNodeStat(directoryChildNodeUrl, {
650
- // we ignore symlink because recursively traversed
651
- // so symlinked file will be discovered.
652
- // Moreover if they lead outside of directoryPath it can become a problem
653
- // like infinite recursion of whatever.
654
- // that we could handle using an object of pathname already seen but it will be useless
655
- // because directoryPath is recursively traversed
656
- followLink: false
657
- });
658
-
659
- if (directoryChildNodeStats.isDirectory()) {
660
- const subDirectoryUrl = `${directoryChildNodeUrl}/`;
661
-
662
- if (!urlMeta.urlCanContainsMetaMatching({
663
- url: subDirectoryUrl,
664
- structuredMetaMap: structuredMetaMapNormalized,
665
- predicate
666
- })) {
667
- ignoredArray.push({
668
- relativeUrl: ensureUrlTrailingSlash(relativeUrl),
669
- fileStats: directoryChildNodeStats
670
- });
671
- return;
672
- }
673
-
674
- await visitDirectory(subDirectoryUrl);
675
- return;
676
- }
677
-
678
- if (directoryChildNodeStats.isFile()) {
679
- const meta = urlMeta.urlToMeta({
680
- url: directoryChildNodeUrl,
681
- structuredMetaMap: structuredMetaMapNormalized
682
- });
683
-
684
- if (!predicate(meta)) {
685
- ignoredArray.push({
686
- relativeUrl,
687
- meta,
688
- fileStats: directoryChildNodeStats
689
- });
690
- return;
691
- }
692
-
693
- matchingArray.push({
694
- relativeUrl,
695
- meta,
696
- fileStats: directoryChildNodeStats
697
- });
698
- return;
699
- }
700
- }));
701
- };
702
-
703
- try {
704
- await visitDirectory(rootDirectoryUrl);
705
- return {
706
- matchingArray: sortByRelativeUrl(matchingArray),
707
- ignoredArray: sortByRelativeUrl(ignoredArray)
708
- };
709
- } finally {
710
- await collectOperation.end();
711
- }
712
- };
713
-
714
- const sortByRelativeUrl = array => array.sort((left, right) => {
715
- return comparePathnames(left.relativeUrl, right.relativeUrl);
716
- });
717
-
718
- const collectFiles = async ({
719
- signal = new AbortController().signal,
720
- directoryUrl,
721
- structuredMetaMap,
722
- predicate
723
- }) => {
724
- const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
725
-
726
- if (typeof predicate !== "function") {
727
- throw new TypeError(`predicate must be a function, got ${predicate}`);
728
- }
729
-
730
- const structuredMetaMapNormalized = urlMeta.normalizeStructuredMetaMap(structuredMetaMap, rootDirectoryUrl);
731
- const collectOperation = abort.Abort.startOperation();
732
- collectOperation.addAbortSignal(signal);
733
- const matchingFileResultArray = [];
734
-
735
- const visitDirectory = async directoryUrl => {
736
- collectOperation.throwIfAborted();
737
- const directoryItems = await readDirectory(directoryUrl);
738
- await Promise.all(directoryItems.map(async directoryItem => {
739
- const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`;
740
- collectOperation.throwIfAborted();
741
- const directoryChildNodeStats = await readFileSystemNodeStat(directoryChildNodeUrl, {
742
- // we ignore symlink because recursively traversed
743
- // so symlinked file will be discovered.
744
- // Moreover if they lead outside of directoryPath it can become a problem
745
- // like infinite recursion of whatever.
746
- // that we could handle using an object of pathname already seen but it will be useless
747
- // because directoryPath is recursively traversed
748
- followLink: false
749
- });
750
-
751
- if (directoryChildNodeStats.isDirectory()) {
752
- const subDirectoryUrl = `${directoryChildNodeUrl}/`;
753
-
754
- if (!urlMeta.urlCanContainsMetaMatching({
755
- url: subDirectoryUrl,
756
- structuredMetaMap: structuredMetaMapNormalized,
757
- predicate
758
- })) {
759
- return;
760
- }
761
-
762
- await visitDirectory(subDirectoryUrl);
763
- return;
764
- }
765
-
766
- if (directoryChildNodeStats.isFile()) {
767
- const meta = urlMeta.urlToMeta({
768
- url: directoryChildNodeUrl,
769
- structuredMetaMap: structuredMetaMapNormalized
770
- });
771
- if (!predicate(meta)) return;
772
- const relativeUrl = urlToRelativeUrl(directoryChildNodeUrl, rootDirectoryUrl);
773
- matchingFileResultArray.push({
774
- url: new URL(relativeUrl, rootDirectoryUrl).href,
775
- relativeUrl,
776
- meta,
777
- fileStats: directoryChildNodeStats
778
- });
779
- return;
780
- }
781
- }));
782
- };
783
-
784
- try {
785
- await visitDirectory(rootDirectoryUrl); // When we operate on thoose files later it feels more natural
786
- // to perform operation in the same order they appear in the filesystem.
787
- // It also allow to get a predictable return value.
788
- // For that reason we sort matchingFileResultArray
789
-
790
- matchingFileResultArray.sort((leftFile, rightFile) => {
791
- return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl);
792
- });
793
- return matchingFileResultArray;
794
- } finally {
795
- await collectOperation.end();
796
- }
797
- };
798
-
799
- const {
800
- mkdir
801
- } = fs.promises;
802
- const writeDirectory = async (destination, {
803
- recursive = true,
804
- allowUseless = false
805
- } = {}) => {
806
- const destinationUrl = assertAndNormalizeDirectoryUrl(destination);
807
- const destinationPath = urlToFileSystemPath(destinationUrl);
808
- const destinationStats = await readFileSystemNodeStat(destinationUrl, {
809
- nullIfNotFound: true,
810
- followLink: false
811
- });
812
-
813
- if (destinationStats) {
814
- if (destinationStats.isDirectory()) {
815
- if (allowUseless) {
816
- return;
817
- }
818
-
819
- throw new Error(`directory already exists at ${destinationPath}`);
820
- }
821
-
822
- const destinationType = statsToType(destinationStats);
823
- throw new Error(`cannot write directory at ${destinationPath} because there is a ${destinationType}`);
824
- }
825
-
826
- try {
827
- await mkdir(destinationPath, {
828
- recursive
829
- });
830
- } catch (error) {
831
- if (allowUseless && error.code === "EEXIST") {
832
- return;
833
- }
834
-
835
- throw error;
836
- }
837
- };
838
-
839
- const resolveUrl = (specifier, baseUrl) => {
840
- if (typeof baseUrl === "undefined") {
841
- throw new TypeError(`baseUrl missing to resolve ${specifier}`);
842
- }
843
-
844
- return String(new URL(specifier, baseUrl));
845
- };
846
-
847
- const removeFileSystemNode = async (source, {
848
- signal = new AbortController().signal,
849
- allowUseless = false,
850
- recursive = false,
851
- maxRetries = 3,
852
- retryDelay = 100,
853
- onlyContent = false
854
- } = {}) => {
855
- const sourceUrl = assertAndNormalizeFileUrl(source);
856
- const removeOperation = abort.Abort.startOperation();
857
- removeOperation.addAbortSignal(signal);
858
-
859
- try {
860
- removeOperation.throwIfAborted();
861
- const sourceStats = await readFileSystemNodeStat(sourceUrl, {
862
- nullIfNotFound: true,
863
- followLink: false
864
- });
865
-
866
- if (!sourceStats) {
867
- if (allowUseless) {
868
- return;
869
- }
870
-
871
- throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`);
872
- } // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats
873
- // FIFO and socket are ignored, not sure what they are exactly and what to do with them
874
- // other libraries ignore them, let's do the same.
875
-
876
-
877
- if (sourceStats.isFile() || sourceStats.isSymbolicLink() || sourceStats.isCharacterDevice() || sourceStats.isBlockDevice()) {
878
- await removeNonDirectory(sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl, {
879
- maxRetries,
880
- retryDelay
881
- });
882
- } else if (sourceStats.isDirectory()) {
883
- await removeDirectory(ensureUrlTrailingSlash(sourceUrl), {
884
- signal: removeOperation.signal,
885
- recursive,
886
- maxRetries,
887
- retryDelay,
888
- onlyContent
889
- });
890
- }
891
- } finally {
892
- await removeOperation.end();
893
- }
894
- };
895
-
896
- const removeNonDirectory = (sourceUrl, {
897
- maxRetries,
898
- retryDelay
899
- }) => {
900
- const sourcePath = urlToFileSystemPath(sourceUrl);
901
- let retryCount = 0;
902
-
903
- const attempt = () => {
904
- return unlinkNaive(sourcePath, { ...(retryCount >= maxRetries ? {} : {
905
- handleTemporaryError: async () => {
906
- retryCount++;
907
- return new Promise(resolve => {
908
- setTimeout(() => {
909
- resolve(attempt());
910
- }, retryCount * retryDelay);
911
- });
912
- }
913
- })
914
- });
915
- };
916
-
917
- return attempt();
918
- };
919
-
920
- const unlinkNaive = (sourcePath, {
921
- handleTemporaryError = null
922
- } = {}) => {
923
- return new Promise((resolve, reject) => {
924
- node_fs.unlink(sourcePath, error => {
925
- if (error) {
926
- if (error.code === "ENOENT") {
927
- resolve();
928
- } else if (handleTemporaryError && (error.code === "EBUSY" || error.code === "EMFILE" || error.code === "ENFILE" || error.code === "ENOENT")) {
929
- resolve(handleTemporaryError(error));
930
- } else {
931
- reject(error);
932
- }
933
- } else {
934
- resolve();
935
- }
936
- });
937
- });
938
- };
939
-
940
- const removeDirectory = async (rootDirectoryUrl, {
941
- signal,
942
- maxRetries,
943
- retryDelay,
944
- recursive,
945
- onlyContent
946
- }) => {
947
- const removeDirectoryOperation = abort.Abort.startOperation();
948
- removeDirectoryOperation.addAbortSignal(signal);
949
-
950
- const visit = async sourceUrl => {
951
- removeDirectoryOperation.throwIfAborted();
952
- const sourceStats = await readFileSystemNodeStat(sourceUrl, {
953
- nullIfNotFound: true,
954
- followLink: false
955
- }); // file/directory not found
956
-
957
- if (sourceStats === null) {
958
- return;
959
- }
960
-
961
- if (sourceStats.isFile() || sourceStats.isCharacterDevice() || sourceStats.isBlockDevice()) {
962
- await visitFile(sourceUrl);
963
- } else if (sourceStats.isSymbolicLink()) {
964
- await visitSymbolicLink(sourceUrl);
965
- } else if (sourceStats.isDirectory()) {
966
- await visitDirectory(`${sourceUrl}/`);
967
- }
968
- };
969
-
970
- const visitDirectory = async directoryUrl => {
971
- const directoryPath = urlToFileSystemPath(directoryUrl);
972
- const optionsFromRecursive = recursive ? {
973
- handleNotEmptyError: async () => {
974
- await removeDirectoryContent(directoryUrl);
975
- await visitDirectory(directoryUrl);
976
- }
977
- } : {};
978
- removeDirectoryOperation.throwIfAborted();
979
- await removeDirectoryNaive(directoryPath, { ...optionsFromRecursive,
980
- // Workaround for https://github.com/joyent/node/issues/4337
981
- ...(process.platform === "win32" ? {
982
- handlePermissionError: async error => {
983
- console.error(`trying to fix windows EPERM after readir on ${directoryPath}`);
984
- let openOrCloseError;
985
-
986
- try {
987
- const fd = node_fs.openSync(directoryPath);
988
- node_fs.closeSync(fd);
989
- } catch (e) {
990
- openOrCloseError = e;
991
- }
992
-
993
- if (openOrCloseError) {
994
- if (openOrCloseError.code === "ENOENT") {
995
- return;
996
- }
997
-
998
- console.error(`error while trying to fix windows EPERM after readir on ${directoryPath}: ${openOrCloseError.stack}`);
999
- throw error;
1000
- }
1001
-
1002
- await removeDirectoryNaive(directoryPath, { ...optionsFromRecursive
1003
- });
1004
- }
1005
- } : {})
1006
- });
1007
- };
1008
-
1009
- const removeDirectoryContent = async directoryUrl => {
1010
- removeDirectoryOperation.throwIfAborted();
1011
- const names = await readDirectory(directoryUrl);
1012
- await Promise.all(names.map(async name => {
1013
- const url = resolveUrl(name, directoryUrl);
1014
- await visit(url);
1015
- }));
1016
- };
1017
-
1018
- const visitFile = async fileUrl => {
1019
- await removeNonDirectory(fileUrl, {
1020
- maxRetries,
1021
- retryDelay
1022
- });
1023
- };
1024
-
1025
- const visitSymbolicLink = async symbolicLinkUrl => {
1026
- await removeNonDirectory(symbolicLinkUrl, {
1027
- maxRetries,
1028
- retryDelay
1029
- });
1030
- };
1031
-
1032
- try {
1033
- if (onlyContent) {
1034
- await removeDirectoryContent(rootDirectoryUrl);
1035
- } else {
1036
- await visitDirectory(rootDirectoryUrl);
1037
- }
1038
- } finally {
1039
- await removeDirectoryOperation.end();
1040
- }
1041
- };
1042
-
1043
- const removeDirectoryNaive = (directoryPath, {
1044
- handleNotEmptyError = null,
1045
- handlePermissionError = null
1046
- } = {}) => {
1047
- return new Promise((resolve, reject) => {
1048
- node_fs.rmdir(directoryPath, (error, lstatObject) => {
1049
- if (error) {
1050
- if (handlePermissionError && error.code === "EPERM") {
1051
- resolve(handlePermissionError(error));
1052
- } else if (error.code === "ENOENT") {
1053
- resolve();
1054
- } else if (handleNotEmptyError && ( // linux os
1055
- error.code === "ENOTEMPTY" || // SunOS
1056
- error.code === "EEXIST")) {
1057
- resolve(handleNotEmptyError(error));
1058
- } else {
1059
- reject(error);
1060
- }
1061
- } else {
1062
- resolve(lstatObject);
1063
- }
1064
- });
1065
- });
1066
- };
1067
-
1068
- const ensureEmptyDirectory = async source => {
1069
- const stats = await readFileSystemNodeStat(source, {
1070
- nullIfNotFound: true,
1071
- followLink: false
1072
- });
1073
-
1074
- if (stats === null) {
1075
- // if there is nothing, create a directory
1076
- return writeDirectory(source, {
1077
- allowUseless: true
1078
- });
1079
- }
1080
-
1081
- if (stats.isDirectory()) {
1082
- // if there is a directory remove its content and done
1083
- return removeFileSystemNode(source, {
1084
- allowUseless: true,
1085
- recursive: true,
1086
- onlyContent: true
1087
- });
1088
- }
1089
-
1090
- const sourceType = statsToType(stats);
1091
- const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source));
1092
- throw new Error(`ensureEmptyDirectory expect directory at ${sourcePath}, found ${sourceType} instead`);
1093
- };
1094
-
1095
- const ensureParentDirectories = async destination => {
1096
- const destinationUrl = assertAndNormalizeFileUrl(destination);
1097
- const destinationPath = urlToFileSystemPath(destinationUrl);
1098
- const destinationParentPath = path.dirname(destinationPath);
1099
- return writeDirectory(destinationParentPath, {
1100
- recursive: true,
1101
- allowUseless: true
1102
- });
1103
- };
1104
-
1105
- const isWindows$2 = process.platform === "win32";
1106
- const baseUrlFallback = fileSystemPathToUrl(process.cwd());
1107
- /**
1108
- * Some url might be resolved or remapped to url without the windows drive letter.
1109
- * For instance
1110
- * new URL('/foo.js', 'file:///C:/dir/file.js')
1111
- * resolves to
1112
- * 'file:///foo.js'
1113
- *
1114
- * But on windows it becomes a problem because we need the drive letter otherwise
1115
- * url cannot be converted to a filesystem path.
1116
- *
1117
- * ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.
1118
- */
1119
-
1120
- const ensureWindowsDriveLetter = (url, baseUrl) => {
1121
- try {
1122
- url = String(new URL(url));
1123
- } catch (e) {
1124
- throw new Error(`absolute url expected but got ${url}`);
1125
- }
1126
-
1127
- if (!isWindows$2) {
1128
- return url;
1129
- }
1130
-
1131
- try {
1132
- baseUrl = String(new URL(baseUrl));
1133
- } catch (e) {
1134
- throw new Error(`absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`);
1135
- }
1136
-
1137
- if (!url.startsWith("file://")) {
1138
- return url;
1139
- }
1140
-
1141
- const afterProtocol = url.slice("file://".length); // we still have the windows drive letter
1142
-
1143
- if (extractDriveLetter(afterProtocol)) {
1144
- return url;
1145
- } // drive letter was lost, restore it
1146
-
1147
-
1148
- const baseUrlOrFallback = baseUrl.startsWith("file://") ? baseUrl : baseUrlFallback;
1149
- const driveLetter = extractDriveLetter(baseUrlOrFallback.slice("file://".length));
1150
-
1151
- if (!driveLetter) {
1152
- throw new Error(`drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`);
1153
- }
1154
-
1155
- return `file:///${driveLetter}:${afterProtocol}`;
1156
- };
1157
-
1158
- const extractDriveLetter = ressource => {
1159
- // we still have the windows drive letter
1160
- if (/[a-zA-Z]/.test(ressource[1]) && ressource[2] === ":") {
1161
- return ressource[1];
1162
- }
1163
-
1164
- return null;
1165
- };
1166
-
1167
- const urlTargetsSameFileSystemPath = (leftUrl, rightUrl) => {
1168
- if (leftUrl.endsWith("/")) leftUrl = leftUrl.slice(0, -1);
1169
- if (rightUrl.endsWith("/")) rightUrl = rightUrl.slice(0, -1);
1170
- return leftUrl === rightUrl;
1171
- };
1172
-
1173
- const writeFileSystemNodeModificationTime = (source, mtime) => {
1174
- const sourceUrl = assertAndNormalizeFileUrl(source);
1175
- const sourcePath = urlToFileSystemPath(sourceUrl);
1176
- 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
1177
- // where we want to modify it
1178
-
1179
- const atimeValue = mtimeValue;
1180
- return new Promise((resolve, reject) => {
1181
- fs.utimes(sourcePath, atimeValue, mtimeValue, error => {
1182
- if (error) {
1183
- reject(error);
1184
- } else {
1185
- resolve();
1186
- }
1187
- });
1188
- });
1189
- };
1190
-
1191
- const replaceBackSlashesWithSlashes = string => string.replace(/\\/g, "/");
1192
-
1193
- /*
1194
- * - symlink documentation on Node.js:
1195
- * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_fs_symlink_target_path_type_callback
1196
- */
1197
- const readSymbolicLink = url => {
1198
- const symbolicLinkUrl = assertAndNormalizeFileUrl(url);
1199
- const symbolicLinkPath = urlToFileSystemPath(symbolicLinkUrl);
1200
- return new Promise((resolve, reject) => {
1201
- node_fs.readlink(symbolicLinkPath, (error, resolvedPath) => {
1202
- if (error) {
1203
- reject(error);
1204
- } else {
1205
- resolve(isFileSystemPath(resolvedPath) ? fileSystemPathToUrl(resolvedPath) : replaceBackSlashesWithSlashes(resolvedPath));
1206
- }
1207
- });
1208
- });
1209
- };
1210
-
1211
- const {
1212
- symlink
1213
- } = node_fs.promises;
1214
- const isWindows$1 = process.platform === "win32";
1215
- /**
1216
- * Writes a symbolic link pointing from a filesystem node to an other
1217
- * @param {string} from Where symlink is written
1218
- * @param {string} to The symlink target
1219
- * @param {"file" | "dir"} [type] Symlink type if you know it before hand
1220
- * @param {boolean} [allowUseless=false] Prevent error when symlink already exists with the same target
1221
- * @param {boolean} [allowOverwrite=false] Will replace any existing symlink
1222
- */
1223
-
1224
- const writeSymbolicLink = async ({
1225
- from,
1226
- to,
1227
- type,
1228
- allowUseless = false,
1229
- allowOverwrite = false
1230
- }) => {
1231
- const fromUrl = assertAndNormalizeFileUrl(from);
1232
- const toInfo = getToInfo(to, fromUrl);
1233
-
1234
- if (isWindows$1 && typeof type === "undefined") {
1235
- // without this if you write a symbolic link without specifying the type on windows
1236
- // you later get EPERM when doing stat on the symlink
1237
- const toStats = await readFileSystemNodeStat(toInfo.url, {
1238
- nullIfNotFound: true
1239
- });
1240
- type = toStats && toStats.isDirectory() ? "dir" : "file";
1241
- }
1242
-
1243
- const symbolicLinkPath = urlToFileSystemPath(fromUrl);
1244
-
1245
- try {
1246
- await symlink(toInfo.value, symbolicLinkPath, type);
1247
- } catch (error) {
1248
- if (error.code === "ENOENT") {
1249
- await ensureParentDirectories(fromUrl);
1250
- await symlink(toInfo.value, symbolicLinkPath, type);
1251
- return;
1252
- }
1253
-
1254
- if (error.code === "EEXIST") {
1255
- if (allowUseless) {
1256
- const existingSymbolicLinkUrl = await readSymbolicLink(fromUrl);
1257
-
1258
- if (existingSymbolicLinkUrl === toInfo.url) {
1259
- return;
1260
- }
1261
- }
1262
-
1263
- if (allowOverwrite) {
1264
- await removeFileSystemNode(fromUrl);
1265
- await symlink(toInfo.value, symbolicLinkPath, type);
1266
- return;
1267
- }
1268
- }
1269
-
1270
- throw error;
1271
- }
1272
- };
1273
-
1274
- const getToInfo = (to, fromUrl) => {
1275
- if (typeof to === "string") {
1276
- // absolute filesystem path
1277
- if (isFileSystemPath(to)) {
1278
- const url = fileSystemPathToUrl(to);
1279
- const value = to;
1280
- return {
1281
- url,
1282
- value
1283
- };
1284
- } // relative url
1285
-
1286
-
1287
- if (to.startsWith("./") || to.startsWith("../")) {
1288
- const url = resolveUrl(to, fromUrl);
1289
- const value = to;
1290
- return {
1291
- url,
1292
- value
1293
- };
1294
- } // absolute url
1295
-
1296
-
1297
- const url = resolveUrl(to, fromUrl);
1298
- const value = urlToFileSystemPath(url);
1299
- return {
1300
- url,
1301
- value
1302
- };
1303
- }
1304
-
1305
- if (to instanceof URL) {
1306
- const url = String(to);
1307
- const value = urlToFileSystemPath(url);
1308
- return {
1309
- url,
1310
- value
1311
- };
1312
- }
1313
-
1314
- throw new TypeError(`symbolic link to must be a string or an url, received ${to}`);
1315
- };
1316
-
1317
- const urlIsInsideOf = (url, otherUrl) => {
1318
- const urlObject = new URL(url);
1319
- const otherUrlObject = new URL(otherUrl);
1320
-
1321
- if (urlObject.origin !== otherUrlObject.origin) {
1322
- return false;
1323
- }
1324
-
1325
- const urlPathname = urlObject.pathname;
1326
- const otherUrlPathname = otherUrlObject.pathname;
1327
-
1328
- if (urlPathname === otherUrlPathname) {
1329
- return false;
1330
- }
1331
-
1332
- const isInside = urlPathname.startsWith(otherUrlPathname);
1333
- return isInside;
1334
- };
1335
-
1336
- const copyFileSystemNode = async ({
1337
- signal = new AbortController().signal,
1338
- from,
1339
- to,
1340
- overwrite = false,
1341
- preserveStat = true,
1342
- preserveMtime = preserveStat,
1343
- preservePermissions = preserveStat,
1344
- allowUseless = false,
1345
- followLink = true
1346
- }) => {
1347
- const fromUrl = assertAndNormalizeFileUrl(from);
1348
- const fromPath = urlToFileSystemPath(fromUrl);
1349
- let toUrl = assertAndNormalizeFileUrl(to);
1350
- let toPath = urlToFileSystemPath(toUrl);
1351
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1352
- nullIfNotFound: true,
1353
- followLink: false
1354
- });
1355
-
1356
- if (!sourceStats) {
1357
- throw new Error(`nothing to copy at ${fromPath}`);
1358
- }
1359
-
1360
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1361
- nullIfNotFound: true,
1362
- // we force false here but in fact we will follow the destination link
1363
- // to know where we will actually move and detect useless move overrite etc..
1364
- followLink: false
1365
- });
1366
-
1367
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1368
- const linkTarget = await readSymbolicLink(toUrl);
1369
- toUrl = resolveUrl(linkTarget, toUrl);
1370
- toPath = urlToFileSystemPath(toUrl);
1371
- destinationStats = await readFileSystemNodeStat(toUrl, {
1372
- nullIfNotFound: true
1373
- });
1374
- }
1375
-
1376
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1377
- if (allowUseless) {
1378
- return;
1379
- }
1380
-
1381
- throw new Error(`cannot copy ${fromPath} because destination and source are the same`);
1382
- }
1383
-
1384
- if (sourceStats.isDirectory()) {
1385
- toUrl = ensureUrlTrailingSlash(toUrl);
1386
- }
1387
-
1388
- const copyOperation = abort.Abort.startOperation();
1389
- copyOperation.addAbortSignal(signal);
1390
-
1391
- const visit = async (url, stats) => {
1392
- copyOperation.throwIfAborted();
1393
-
1394
- if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
1395
- await visitFile(url, stats);
1396
- } else if (stats.isSymbolicLink()) {
1397
- await visitSymbolicLink(url);
1398
- } else if (stats.isDirectory()) {
1399
- await visitDirectory(ensureUrlTrailingSlash(url), stats);
1400
- }
1401
- };
1402
-
1403
- const visitFile = async (fileUrl, fileStats) => {
1404
- const fileRelativeUrl = urlToRelativeUrl(fileUrl, fromUrl);
1405
- const fileCopyUrl = resolveUrl(fileRelativeUrl, toUrl);
1406
- await copyFileContentNaive(urlToFileSystemPath(fileUrl), urlToFileSystemPath(fileCopyUrl));
1407
- await copyStats(fileCopyUrl, fileStats);
1408
- };
1409
-
1410
- const visitSymbolicLink = async symbolicLinkUrl => {
1411
- const symbolicLinkRelativeUrl = urlToRelativeUrl(symbolicLinkUrl, fromUrl);
1412
- const symbolicLinkTarget = await readSymbolicLink(symbolicLinkUrl);
1413
- const symbolicLinkTargetUrl = resolveUrl(symbolicLinkTarget, symbolicLinkUrl);
1414
- const linkIsRelative = symbolicLinkTarget.startsWith("./") || symbolicLinkTarget.startsWith("../");
1415
- let symbolicLinkCopyTarget;
1416
-
1417
- if (symbolicLinkTargetUrl === fromUrl) {
1418
- symbolicLinkCopyTarget = linkIsRelative ? symbolicLinkTarget : toUrl;
1419
- } else if (urlIsInsideOf(symbolicLinkTargetUrl, fromUrl)) {
1420
- // symbolic link targets something inside the directory we want to copy
1421
- // reflects it inside the copied directory structure
1422
- const linkCopyTargetRelative = urlToRelativeUrl(symbolicLinkTargetUrl, fromUrl);
1423
- symbolicLinkCopyTarget = linkIsRelative ? `./${linkCopyTargetRelative}` : resolveUrl(linkCopyTargetRelative, toUrl);
1424
- } else {
1425
- // symbolic link targets something outside the directory we want to copy
1426
- symbolicLinkCopyTarget = symbolicLinkTarget;
1427
- } // we must guess ourself the type of the symlink
1428
- // because the destination might not exists because not yet copied
1429
- // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fs_symlink_target_path_type_callback
1430
-
1431
-
1432
- const targetStats = await readFileSystemNodeStat(symbolicLinkTargetUrl, {
1433
- nullIfNotFound: true,
1434
- followLink: false
1435
- });
1436
- const linkType = targetStats && targetStats.isDirectory() ? "dir" : "file";
1437
- const symbolicLinkCopyUrl = resolveUrl(symbolicLinkRelativeUrl, toUrl);
1438
- await writeSymbolicLink({
1439
- from: symbolicLinkCopyUrl,
1440
- to: symbolicLinkCopyTarget,
1441
- type: linkType
1442
- });
1443
- };
1444
-
1445
- const copyStats = async (toUrl, stats) => {
1446
- if (preservePermissions || preserveMtime) {
1447
- const {
1448
- mode,
1449
- mtimeMs
1450
- } = stats;
1451
-
1452
- if (preservePermissions) {
1453
- await writeFileSystemNodePermissions(toUrl, binaryFlagsToPermissions(mode));
1454
- }
1455
-
1456
- if (preserveMtime) {
1457
- await writeFileSystemNodeModificationTime(toUrl, mtimeMs);
1458
- }
1459
- }
1460
- };
1461
-
1462
- const visitDirectory = async (directoryUrl, directoryStats) => {
1463
- const directoryRelativeUrl = urlToRelativeUrl(directoryUrl, fromUrl);
1464
- const directoryCopyUrl = resolveUrl(directoryRelativeUrl, toUrl);
1465
- await writeDirectory(directoryCopyUrl);
1466
- await copyDirectoryContent(directoryUrl);
1467
- await copyStats(directoryCopyUrl, directoryStats);
1468
- };
1469
-
1470
- const copyDirectoryContent = async directoryUrl => {
1471
- const names = await readDirectory(directoryUrl);
1472
- await Promise.all(names.map(async name => {
1473
- const fileSystemNodeUrl = resolveUrl(name, directoryUrl);
1474
- const stats = await readFileSystemNodeStat(fileSystemNodeUrl, {
1475
- followLink: false
1476
- });
1477
- await visit(fileSystemNodeUrl, stats);
1478
- }));
1479
- };
1480
-
1481
- try {
1482
- if (destinationStats) {
1483
- const sourceType = statsToType(sourceStats);
1484
- const destinationType = statsToType(destinationStats);
1485
-
1486
- if (sourceType !== destinationType) {
1487
- throw new Error(`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`);
1488
- }
1489
-
1490
- if (!overwrite) {
1491
- throw new Error(`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`);
1492
- } // remove file, link, directory...
1493
-
1494
-
1495
- await removeFileSystemNode(toUrl, {
1496
- signal: copyOperation.signal,
1497
- recursive: true,
1498
- allowUseless: true
1499
- });
1500
- } else {
1501
- await ensureParentDirectories(toUrl);
1502
- }
1503
-
1504
- copyOperation.throwIfAborted();
1505
- await visit(fromUrl, sourceStats);
1506
- } finally {
1507
- await copyOperation.end();
1508
- }
1509
- };
1510
-
1511
- const copyFileContentNaive = (filePath, fileDestinationPath) => {
1512
- return new Promise((resolve, reject) => {
1513
- node_fs.copyFile(filePath, fileDestinationPath, error => {
1514
- if (error) {
1515
- reject(error);
1516
- } else {
1517
- resolve();
1518
- }
1519
- });
1520
- });
1521
- };
1522
-
1523
- const copyDirectoryContent = async ({
1524
- signal = new AbortController().signal,
1525
- from,
1526
- to,
1527
- overwrite,
1528
- followLink = true
1529
- } = {}) => {
1530
- const fromUrl = assertAndNormalizeDirectoryUrl(from);
1531
- const fromPath = urlToFileSystemPath(fromUrl);
1532
- let toUrl = assertAndNormalizeDirectoryUrl(to);
1533
- let toPath = urlToFileSystemPath(toUrl);
1534
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1535
- nullIfNotFound: true,
1536
- followLink: false
1537
- });
1538
-
1539
- if (!sourceStats) {
1540
- throw new Error(`no directory to copy content from at ${fromPath}`);
1541
- }
1542
-
1543
- if (!sourceStats.isDirectory()) {
1544
- const sourceType = statsToType(sourceStats);
1545
- throw new Error(`found a ${sourceType} instead of a directory at ${fromPath}`);
1546
- }
1547
-
1548
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1549
- nullIfNotFound: true,
1550
- // we force false here but in fact we will follow the destination link
1551
- // to know where we will actually move and detect useless move overrite etc..
1552
- followLink: false
1553
- });
1554
-
1555
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1556
- const linkTarget = await readSymbolicLink(toUrl);
1557
- toUrl = resolveUrl(linkTarget, toUrl);
1558
- toPath = urlToFileSystemPath(toUrl);
1559
- destinationStats = await readFileSystemNodeStat(toUrl, {
1560
- nullIfNotFound: true
1561
- });
1562
- }
1563
-
1564
- if (destinationStats === null) {
1565
- throw new Error(`no directory to copy content into at ${toPath}`);
1566
- }
1567
-
1568
- if (!destinationStats.isDirectory()) {
1569
- const destinationType = statsToType(destinationStats);
1570
- throw new Error(`destination leads to a ${destinationType} instead of a directory at ${toPath}`);
1571
- }
1572
-
1573
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1574
- throw new Error(`cannot copy directory content, source and destination are the same (${fromPath})`);
1575
- }
1576
-
1577
- const copyOperation = abort.Abort.startOperation();
1578
- copyOperation.addAbortSignal(signal);
1579
-
1580
- try {
1581
- copyOperation.throwIfAborted();
1582
- const directoryEntries = await readDirectory(fromUrl);
1583
- await Promise.all(directoryEntries.map(async directoryEntry => {
1584
- const from = resolveUrl(directoryEntry, fromUrl);
1585
- const to = resolveUrl(directoryEntry, toUrl);
1586
- await copyOperation.withSignal(async signal => {
1587
- await copyFileSystemNode({
1588
- signal,
1589
- from,
1590
- to,
1591
- overwrite,
1592
- followLink
1593
- });
1594
- });
1595
- }));
1596
- } finally {
1597
- await copyOperation.end();
1598
- }
1599
- };
1600
-
1601
- /*
1602
- * - file modes documentation on Node.js
1603
- * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_file_modes
1604
- */
1605
- const {
1606
- stat
1607
- } = node_fs.promises;
1608
- const readFileSystemNodePermissions = async source => {
1609
- const sourceUrl = assertAndNormalizeFileUrl(source);
1610
- const sourcePath = urlToFileSystemPath(sourceUrl);
1611
- const {
1612
- mode
1613
- } = await stat(sourcePath);
1614
- return binaryFlagsToPermissions(mode);
1615
- };
1616
-
1617
- const grantPermissionsOnFileSystemNode = async (source, {
1618
- read = false,
1619
- write = false,
1620
- execute = false
1621
- }) => {
1622
- const sourceUrl = assertAndNormalizeFileUrl(source);
1623
- const filePermissions = await readFileSystemNodePermissions(sourceUrl);
1624
- await writeFileSystemNodePermissions(sourceUrl, {
1625
- owner: {
1626
- read,
1627
- write,
1628
- execute
1629
- },
1630
- group: {
1631
- read,
1632
- write,
1633
- execute
1634
- },
1635
- others: {
1636
- read,
1637
- write,
1638
- execute
1639
- }
1640
- });
1641
- return async () => {
1642
- await writeFileSystemNodePermissions(sourceUrl, filePermissions);
1643
- };
1644
- };
1645
-
1646
- const listFilesMatching = async (patterns, directoryUrl = process.cwd(), signal) => {
1647
- if (typeof patterns === "string") {
1648
- patterns = [patterns];
1649
- } else if (Array.isArray(patterns)) {
1650
- throw new TypeError(`patterns must be a string or an array of strings, got ${patterns}`);
1651
- }
1652
-
1653
- const patternMap = {};
1654
- patterns.forEach((pattern, index) => {
1655
- if (typeof pattern !== "string") {
1656
- throw new TypeError(`every pattern must be a string, found ${pattern} at index ${index}`);
1657
- }
1658
-
1659
- patternMap[pattern] = true;
1660
- });
1661
- const fileDatas = await collectFiles({
1662
- signal,
1663
- directoryUrl,
1664
- structuredMetaMap: {
1665
- matches: patternMap
1666
- },
1667
- predicate: ({
1668
- matches
1669
- }) => matches
1670
- });
1671
- return fileDatas.map(({
1672
- url
1673
- }) => url);
1674
- };
1675
-
1676
- const memoize = compute => {
1677
- let memoized = false;
1678
- let memoizedValue;
1679
-
1680
- const fnWithMemoization = (...args) => {
1681
- if (memoized) {
1682
- return memoizedValue;
1683
- } // if compute is recursive wait for it to be fully done before storing the value
1684
- // so set memoized boolean after the call
1685
-
1686
-
1687
- memoizedValue = compute(...args);
1688
- memoized = true;
1689
- return memoizedValue;
1690
- };
1691
-
1692
- fnWithMemoization.forget = () => {
1693
- const value = memoizedValue;
1694
- memoized = false;
1695
- memoizedValue = undefined;
1696
- return value;
1697
- };
1698
-
1699
- return fnWithMemoization;
1700
- };
1701
-
1702
- const moveFileSystemNode = async ({
1703
- signal = new AbortController().signal,
1704
- from,
1705
- to,
1706
- overwrite = false,
1707
- allowUseless = false,
1708
- followLink = true
1709
- }) => {
1710
- const fromUrl = assertAndNormalizeFileUrl(from);
1711
- const fromPath = urlToFileSystemPath(fromUrl);
1712
- let toUrl = assertAndNormalizeFileUrl(to);
1713
- let toPath = urlToFileSystemPath(toUrl);
1714
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1715
- nullIfNotFound: true,
1716
- followLink: false
1717
- });
1718
-
1719
- if (!sourceStats) {
1720
- throw new Error(`nothing to move from ${fromPath}`);
1721
- }
1722
-
1723
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1724
- nullIfNotFound: true,
1725
- // we force false here but in fact we will follow the destination link
1726
- // to know where we will actually move and detect useless move overrite etc..
1727
- followLink: false
1728
- });
1729
-
1730
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1731
- const linkTarget = await readSymbolicLink(toUrl);
1732
- toUrl = resolveUrl(linkTarget, toUrl);
1733
- toPath = urlToFileSystemPath(toUrl);
1734
- destinationStats = await readFileSystemNodeStat(toUrl, {
1735
- nullIfNotFound: true
1736
- });
1737
- }
1738
-
1739
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1740
- if (allowUseless) {
1741
- return;
1742
- }
1743
-
1744
- throw new Error(`no move needed for ${fromPath} because destination and source are the same`);
1745
- }
1746
-
1747
- const moveOperation = abort.Abort.startOperation();
1748
- moveOperation.addAbortSignal(signal);
1749
-
1750
- try {
1751
- if (destinationStats) {
1752
- const sourceType = statsToType(sourceStats);
1753
- const destinationType = statsToType(destinationStats);
1754
-
1755
- if (sourceType !== destinationType) {
1756
- throw new Error(`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`);
1757
- }
1758
-
1759
- if (!overwrite) {
1760
- throw new Error(`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`);
1761
- } // remove file, link, directory...
1762
-
1763
-
1764
- await removeFileSystemNode(toUrl, {
1765
- signal: moveOperation.signal,
1766
- recursive: true
1767
- });
1768
- } else {
1769
- await ensureParentDirectories(toUrl);
1770
- }
1771
-
1772
- moveOperation.throwIfAborted();
1773
- await moveNaive(fromPath, toPath, {
1774
- handleCrossDeviceError: async () => {
1775
- await copyFileSystemNode({
1776
- from: fromUrl,
1777
- to: toUrl,
1778
- preserveStat: true
1779
- });
1780
- await removeFileSystemNode(fromUrl, {
1781
- signal: moveOperation.signal,
1782
- recursive: true
1783
- });
1784
- }
1785
- });
1786
- } finally {
1787
- await moveOperation.end();
1788
- }
1789
- };
1790
-
1791
- const moveNaive = (fromPath, destinationPath, {
1792
- handleCrossDeviceError = null
1793
- } = {}) => {
1794
- return new Promise((resolve, reject) => {
1795
- node_fs.rename(fromPath, destinationPath, error => {
1796
- if (error) {
1797
- if (handleCrossDeviceError && error.code === "EXDEV") {
1798
- resolve(handleCrossDeviceError(error));
1799
- } else {
1800
- reject(error);
1801
- }
1802
- } else {
1803
- resolve();
1804
- }
1805
- });
1806
- });
1807
- };
1808
-
1809
- const moveDirectoryContent = async ({
1810
- signal = new AbortController().signal,
1811
- from,
1812
- to,
1813
- overwrite,
1814
- followLink = true
1815
- } = {}) => {
1816
- const fromUrl = assertAndNormalizeDirectoryUrl(from);
1817
- const fromPath = urlToFileSystemPath(fromUrl);
1818
- let toUrl = assertAndNormalizeDirectoryUrl(to);
1819
- let toPath = urlToFileSystemPath(toUrl);
1820
- const sourceStats = await readFileSystemNodeStat(fromUrl, {
1821
- nullIfNotFound: true,
1822
- followLink: false
1823
- });
1824
-
1825
- if (!sourceStats) {
1826
- throw new Error(`no directory to move content from at ${fromPath}`);
1827
- }
1828
-
1829
- if (!sourceStats.isDirectory()) {
1830
- const sourceType = statsToType(sourceStats);
1831
- throw new Error(`found a ${sourceType} instead of a directory at ${fromPath}`);
1832
- }
1833
-
1834
- let destinationStats = await readFileSystemNodeStat(toUrl, {
1835
- nullIfNotFound: true,
1836
- // we force false here but in fact we will follow the destination link
1837
- // to know where we will actually move and detect useless move overrite etc..
1838
- followLink: false
1839
- });
1840
-
1841
- if (followLink && destinationStats && destinationStats.isSymbolicLink()) {
1842
- const linkTarget = await readSymbolicLink(toUrl);
1843
- toUrl = resolveUrl(linkTarget, toUrl);
1844
- toPath = urlToFileSystemPath(toUrl);
1845
- destinationStats = await readFileSystemNodeStat(toUrl, {
1846
- nullIfNotFound: true
1847
- });
1848
- }
1849
-
1850
- if (destinationStats === null) {
1851
- throw new Error(`no directory to move content into at ${toPath}`);
1852
- }
1853
-
1854
- if (!destinationStats.isDirectory()) {
1855
- const destinationType = statsToType(destinationStats);
1856
- throw new Error(`destination leads to a ${destinationType} instead of a directory at ${toPath}`);
1857
- }
1858
-
1859
- if (urlTargetsSameFileSystemPath(fromUrl, toUrl)) {
1860
- throw new Error(`cannot move directory content, source and destination are the same (${fromPath})`);
1861
- }
1862
-
1863
- const moveOperation = abort.Abort.startOperation();
1864
- moveOperation.addAbortSignal(signal);
1865
-
1866
- try {
1867
- moveOperation.throwIfAborted();
1868
- const directoryEntries = await readDirectory(fromUrl);
1869
- await Promise.all(directoryEntries.map(async directoryEntry => {
1870
- const from = resolveUrl(directoryEntry, fromUrl);
1871
- const to = resolveUrl(directoryEntry, toUrl);
1872
- await moveOperation.withSignal(async signal => {
1873
- await moveFileSystemNode({
1874
- signal,
1875
- from,
1876
- to,
1877
- overwrite,
1878
- followLink
1879
- });
1880
- });
1881
- }));
1882
- } finally {
1883
- await moveOperation.end();
1884
- }
1885
- };
1886
-
1887
- const readFilePromisified = util.promisify(fs.readFile);
1888
- const readFile = async (value, {
1889
- as = "string"
1890
- } = {}) => {
1891
- const fileUrl = assertAndNormalizeFileUrl(value);
1892
- const filePath = urlToFileSystemPath(fileUrl);
1893
- const buffer = await readFilePromisified(filePath);
1894
-
1895
- if (as === "buffer") {
1896
- return buffer;
1897
- }
1898
-
1899
- if (as === "string") {
1900
- return buffer.toString();
1901
- }
1902
-
1903
- if (as === "json") {
1904
- return JSON.parse(buffer.toString());
1905
- }
1906
-
1907
- throw new Error(`as must be one of buffer,string,json, received ${as}.`);
1908
- };
1909
-
1910
- const readFileSystemNodeModificationTime = async source => {
1911
- const stats = await readFileSystemNodeStat(source);
1912
- return Math.floor(stats.mtimeMs);
1913
- };
1914
-
1915
- const fileSystemNodeToTypeOrNull = url => {
1916
- const path = urlToFileSystemPath(url);
1917
-
1918
- try {
1919
- const stats = fs.statSync(path);
1920
- return statsToType(stats);
1921
- } catch (e) {
1922
- if (e.code === "ENOENT") {
1923
- return null;
1924
- }
1925
-
1926
- throw e;
1927
- }
1928
- };
1929
-
1930
- const isWindows = process.platform === "win32";
1931
- const createWatcher = (sourcePath, options) => {
1932
- const watcher = fs.watch(sourcePath, options);
1933
-
1934
- if (isWindows) {
1935
- watcher.on("error", async error => {
1936
- // https://github.com/joyent/node/issues/4337
1937
- if (error.code === "EPERM") {
1938
- try {
1939
- const fd = fs.openSync(sourcePath, "r");
1940
- fs.closeSync(fd);
1941
- } catch (e) {
1942
- if (e.code === "ENOENT") {
1943
- return;
1944
- }
1945
-
1946
- console.error(`error while fixing windows eperm: ${e.stack}`);
1947
- throw error;
1948
- }
1949
- } else {
1950
- throw error;
1951
- }
1952
- });
1953
- }
1954
-
1955
- return watcher;
1956
- };
1957
-
1958
- const trackRessources = () => {
1959
- const callbackArray = [];
1960
-
1961
- const registerCleanupCallback = callback => {
1962
- if (typeof callback !== "function") throw new TypeError(`callback must be a function
1963
- callback: ${callback}`);
1964
- callbackArray.push(callback);
1965
- return () => {
1966
- const index = callbackArray.indexOf(callback);
1967
- if (index > -1) callbackArray.splice(index, 1);
1968
- };
1969
- };
1970
-
1971
- const cleanup = async reason => {
1972
- const localCallbackArray = callbackArray.slice();
1973
- await Promise.all(localCallbackArray.map(callback => callback(reason)));
1974
- };
1975
-
1976
- return {
1977
- registerCleanupCallback,
1978
- cleanup
1979
- };
1980
- };
1981
-
1982
- /* eslint-disable import/max-dependencies */
1983
- const isLinux = process.platform === "linux"; // linux does not support recursive option
1984
-
1985
- const fsWatchSupportsRecursive = !isLinux;
1986
- const registerDirectoryLifecycle = (source, {
1987
- added,
1988
- updated,
1989
- removed,
1990
- watchDescription = {
1991
- "./**/*": true
1992
- },
1993
- notifyExistent = false,
1994
- keepProcessAlive = true,
1995
- recursive = false
1996
- }) => {
1997
- const sourceUrl = ensureUrlTrailingSlash(assertAndNormalizeFileUrl(source));
1998
-
1999
- if (!undefinedOrFunction$1(added)) {
2000
- throw new TypeError(`added must be a function or undefined, got ${added}`);
2001
- }
2002
-
2003
- if (!undefinedOrFunction$1(updated)) {
2004
- throw new TypeError(`updated must be a function or undefined, got ${updated}`);
2005
- }
2006
-
2007
- if (!undefinedOrFunction$1(removed)) {
2008
- throw new TypeError(`removed must be a function or undefined, got ${removed}`);
2009
- }
2010
-
2011
- const structuredMetaMap = urlMeta.normalizeStructuredMetaMap({
2012
- watch: watchDescription
2013
- }, sourceUrl);
2014
-
2015
- const entryShouldBeWatched = ({
2016
- relativeUrl,
2017
- type
2018
- }) => {
2019
- const entryUrl = resolveUrl(relativeUrl, sourceUrl);
2020
-
2021
- if (type === "directory") {
2022
- const canContainEntryToWatch = urlMeta.urlCanContainsMetaMatching({
2023
- url: `${entryUrl}/`,
2024
- structuredMetaMap,
2025
- predicate: ({
2026
- watch
2027
- }) => watch
2028
- });
2029
- return canContainEntryToWatch;
2030
- }
2031
-
2032
- const entryMeta = urlMeta.urlToMeta({
2033
- url: entryUrl,
2034
- structuredMetaMap
2035
- });
2036
- return entryMeta.watch;
2037
- };
2038
-
2039
- const tracker = trackRessources();
2040
- const contentMap = new Map();
2041
-
2042
- const handleDirectoryEvent = ({
2043
- directoryRelativeUrl,
2044
- filename,
2045
- eventType
2046
- }) => {
2047
- if (filename) {
2048
- if (directoryRelativeUrl) {
2049
- handleChange(`${directoryRelativeUrl}/${filename}`);
2050
- } else {
2051
- handleChange(`${filename}`);
2052
- }
2053
- } else if ((removed || added) && eventType === "rename") {
2054
- // we might receive `rename` without filename
2055
- // in that case we try to find ourselves which file was removed.
2056
- let relativeUrlCandidateArray = Array.from(contentMap.keys());
2057
-
2058
- if (recursive && !fsWatchSupportsRecursive) {
2059
- relativeUrlCandidateArray = relativeUrlCandidateArray.filter(relativeUrlCandidate => {
2060
- if (!directoryRelativeUrl) {
2061
- // ensure entry is top level
2062
- if (relativeUrlCandidate.includes("/")) return false;
2063
- return true;
2064
- } // entry not inside this directory
2065
-
2066
-
2067
- if (!relativeUrlCandidate.startsWith(directoryRelativeUrl)) return false;
2068
- const afterDirectory = relativeUrlCandidate.slice(directoryRelativeUrl.length + 1); // deep inside this directory
2069
-
2070
- if (afterDirectory.includes("/")) return false;
2071
- return true;
2072
- });
2073
- }
2074
-
2075
- const removedEntryRelativeUrl = relativeUrlCandidateArray.find(relativeUrlCandidate => {
2076
- const entryUrl = resolveUrl(relativeUrlCandidate, sourceUrl);
2077
- const type = fileSystemNodeToTypeOrNull(entryUrl);
2078
- return type === null;
2079
- });
2080
-
2081
- if (removedEntryRelativeUrl) {
2082
- handleEntryLost({
2083
- relativeUrl: removedEntryRelativeUrl,
2084
- type: contentMap.get(removedEntryRelativeUrl)
2085
- });
2086
- }
2087
- }
2088
- };
2089
-
2090
- const handleChange = relativeUrl => {
2091
- const entryUrl = resolveUrl(relativeUrl, sourceUrl);
2092
- const previousType = contentMap.get(relativeUrl);
2093
- const type = fileSystemNodeToTypeOrNull(entryUrl);
2094
-
2095
- if (!entryShouldBeWatched({
2096
- relativeUrl,
2097
- type
2098
- })) {
2099
- return;
2100
- } // it's something new
2101
-
2102
-
2103
- if (!previousType) {
2104
- if (type !== null) {
2105
- handleEntryFound({
2106
- relativeUrl,
2107
- type,
2108
- existent: false
2109
- });
2110
- }
2111
-
2112
- return;
2113
- } // it existed but now it's not here anymore
2114
-
2115
-
2116
- if (type === null) {
2117
- handleEntryLost({
2118
- relativeUrl,
2119
- type: previousType
2120
- });
2121
- return;
2122
- } // it existed and was replaced by something else
2123
- // we don't handle this as an update. We rather say the ressource
2124
- // is lost and something else is found (call removed() then added())
2125
-
2126
-
2127
- if (previousType !== type) {
2128
- handleEntryLost({
2129
- relativeUrl,
2130
- type: previousType
2131
- });
2132
- handleEntryFound({
2133
- relativeUrl,
2134
- type
2135
- });
2136
- return;
2137
- } // a directory cannot really be updated in way that matters for us
2138
- // filesystem is trying to tell us the directory content have changed
2139
- // but we don't care about that
2140
- // we'll already be notified about what has changed
2141
-
2142
-
2143
- if (type === "directory") {
2144
- return;
2145
- } // something has changed at this relativeUrl (the file existed and was not deleted)
2146
- // it's possible to get there and there is no real update
2147
- // (file content is the same and file mtime is the same).
2148
- // In short filesystem is sometimes "lying"
2149
- // Not trying to guard against that because:
2150
- // - hurt perfs a lot
2151
- // - it happens very rarely
2152
- // - it's not really a concern in practice
2153
- // - filesystem did not send an event out of nowhere:
2154
- // something occured but we don't know what with the information we have.
2155
-
2156
-
2157
- if (updated) {
2158
- updated({
2159
- relativeUrl,
2160
- type
2161
- });
2162
- }
2163
- };
2164
-
2165
- const handleEntryFound = ({
2166
- relativeUrl,
2167
- type,
2168
- existent
2169
- }) => {
2170
- if (!entryShouldBeWatched({
2171
- relativeUrl,
2172
- type
2173
- })) {
2174
- return;
2175
- }
2176
-
2177
- contentMap.set(relativeUrl, type);
2178
- const entryUrl = resolveUrl(relativeUrl, sourceUrl);
2179
-
2180
- if (type === "directory") {
2181
- visitDirectory({
2182
- directoryUrl: `${entryUrl}/`,
2183
- entryFound: entry => {
2184
- handleEntryFound({
2185
- relativeUrl: `${relativeUrl}/${entry.relativeUrl}`,
2186
- type: entry.type,
2187
- existent
2188
- });
2189
- }
2190
- });
2191
- }
2192
-
2193
- if (added) {
2194
- if (existent) {
2195
- if (notifyExistent) {
2196
- added({
2197
- relativeUrl,
2198
- type,
2199
- existent: true
2200
- });
2201
- }
2202
- } else {
2203
- added({
2204
- relativeUrl,
2205
- type
2206
- });
2207
- }
2208
- } // we must watch manually every directory we find
2209
-
2210
-
2211
- if (!fsWatchSupportsRecursive && type === "directory") {
2212
- const watcher = createWatcher(urlToFileSystemPath(entryUrl), {
2213
- persistent: keepProcessAlive
2214
- });
2215
- tracker.registerCleanupCallback(() => {
2216
- watcher.close();
2217
- });
2218
- watcher.on("change", (eventType, filename) => {
2219
- handleDirectoryEvent({
2220
- directoryRelativeUrl: relativeUrl,
2221
- filename: filename ? replaceBackSlashesWithSlashes(filename) : "",
2222
- eventType
2223
- });
2224
- });
2225
- }
2226
- };
2227
-
2228
- const handleEntryLost = ({
2229
- relativeUrl,
2230
- type
2231
- }) => {
2232
- contentMap.delete(relativeUrl);
2233
-
2234
- if (removed) {
2235
- removed({
2236
- relativeUrl,
2237
- type
2238
- });
2239
- }
2240
- };
2241
-
2242
- visitDirectory({
2243
- directoryUrl: sourceUrl,
2244
- entryFound: ({
2245
- relativeUrl,
2246
- type
2247
- }) => {
2248
- handleEntryFound({
2249
- relativeUrl,
2250
- type,
2251
- existent: true
2252
- });
2253
- }
2254
- });
2255
- const watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
2256
- recursive: recursive && fsWatchSupportsRecursive,
2257
- persistent: keepProcessAlive
2258
- });
2259
- tracker.registerCleanupCallback(() => {
2260
- watcher.close();
2261
- });
2262
- watcher.on("change", (eventType, fileSystemPath) => {
2263
- handleDirectoryEvent({ ...fileSystemPathToDirectoryRelativeUrlAndFilename(fileSystemPath),
2264
- eventType
2265
- });
2266
- });
2267
- return tracker.cleanup;
2268
- };
2269
-
2270
- const undefinedOrFunction$1 = value => typeof value === "undefined" || typeof value === "function";
2271
-
2272
- const visitDirectory = ({
2273
- directoryUrl,
2274
- entryFound
2275
- }) => {
2276
- const directoryPath = urlToFileSystemPath(directoryUrl);
2277
- fs.readdirSync(directoryPath).forEach(entry => {
2278
- const entryUrl = resolveUrl(entry, directoryUrl);
2279
- const type = fileSystemNodeToTypeOrNull(entryUrl);
2280
-
2281
- if (type === null) {
2282
- return;
2283
- }
2284
-
2285
- const relativeUrl = urlToRelativeUrl(entryUrl, directoryUrl);
2286
- entryFound({
2287
- relativeUrl,
2288
- type
2289
- });
2290
- });
2291
- };
2292
-
2293
- const fileSystemPathToDirectoryRelativeUrlAndFilename = path => {
2294
- if (!path) {
2295
- return {
2296
- directoryRelativeUrl: "",
2297
- filename: ""
2298
- };
2299
- }
2300
-
2301
- const normalizedPath = replaceBackSlashesWithSlashes(path);
2302
- const slashLastIndex = normalizedPath.lastIndexOf("/");
2303
-
2304
- if (slashLastIndex === -1) {
2305
- return {
2306
- directoryRelativeUrl: "",
2307
- filename: normalizedPath
2308
- };
2309
- }
2310
-
2311
- const directoryRelativeUrl = normalizedPath.slice(0, slashLastIndex);
2312
- const filename = normalizedPath.slice(slashLastIndex + 1);
2313
- return {
2314
- directoryRelativeUrl,
2315
- filename
2316
- };
2317
- };
2318
-
2319
- const registerFileLifecycle = (source, {
2320
- added,
2321
- updated,
2322
- removed,
2323
- notifyExistent = false,
2324
- keepProcessAlive = true
2325
- }) => {
2326
- const sourceUrl = assertAndNormalizeFileUrl(source);
2327
-
2328
- if (!undefinedOrFunction(added)) {
2329
- throw new TypeError(`added must be a function or undefined, got ${added}`);
2330
- }
2331
-
2332
- if (!undefinedOrFunction(updated)) {
2333
- throw new TypeError(`updated must be a function or undefined, got ${updated}`);
2334
- }
2335
-
2336
- if (!undefinedOrFunction(removed)) {
2337
- throw new TypeError(`removed must be a function or undefined, got ${removed}`);
2338
- }
2339
-
2340
- const tracker = trackRessources();
2341
-
2342
- const handleFileFound = ({
2343
- existent
2344
- }) => {
2345
- const fileMutationStopWatching = watchFileMutation(sourceUrl, {
2346
- updated,
2347
- removed: () => {
2348
- fileMutationStopTracking();
2349
- watchFileAdded();
2350
-
2351
- if (removed) {
2352
- removed();
2353
- }
2354
- },
2355
- keepProcessAlive
2356
- });
2357
- const fileMutationStopTracking = tracker.registerCleanupCallback(fileMutationStopWatching);
2358
-
2359
- if (added) {
2360
- if (existent) {
2361
- if (notifyExistent) {
2362
- added({
2363
- existent: true
2364
- });
2365
- }
2366
- } else {
2367
- added({});
2368
- }
2369
- }
2370
- };
2371
-
2372
- const watchFileAdded = () => {
2373
- const fileCreationStopWatching = watchFileCreation(sourceUrl, () => {
2374
- fileCreationgStopTracking();
2375
- handleFileFound({
2376
- existent: false
2377
- });
2378
- }, keepProcessAlive);
2379
- const fileCreationgStopTracking = tracker.registerCleanupCallback(fileCreationStopWatching);
2380
- };
2381
-
2382
- const sourceType = fileSystemNodeToTypeOrNull(sourceUrl);
2383
-
2384
- if (sourceType === null) {
2385
- if (added) {
2386
- watchFileAdded();
2387
- } else {
2388
- throw new Error(`${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`);
2389
- }
2390
- } else if (sourceType === "file") {
2391
- handleFileFound({
2392
- existent: true
2393
- });
2394
- } else {
2395
- throw new Error(`${urlToFileSystemPath(sourceUrl)} must lead to a file, type found instead`);
2396
- }
2397
-
2398
- return tracker.cleanup;
2399
- };
2400
-
2401
- const undefinedOrFunction = value => typeof value === "undefined" || typeof value === "function";
2402
-
2403
- const watchFileCreation = (source, callback, keepProcessAlive) => {
2404
- const sourcePath = urlToFileSystemPath(source);
2405
- const sourceFilename = path.basename(sourcePath);
2406
- const directoryPath = path.dirname(sourcePath);
2407
- let directoryWatcher = createWatcher(directoryPath, {
2408
- persistent: keepProcessAlive
2409
- });
2410
- directoryWatcher.on("change", (eventType, filename) => {
2411
- if (filename && filename !== sourceFilename) return;
2412
- const type = fileSystemNodeToTypeOrNull(source); // ignore if something else with that name gets created
2413
- // we are only interested into files
2414
-
2415
- if (type !== "file") return;
2416
- directoryWatcher.close();
2417
- directoryWatcher = undefined;
2418
- callback();
2419
- });
2420
- return () => {
2421
- if (directoryWatcher) {
2422
- directoryWatcher.close();
2423
- }
2424
- };
2425
- };
2426
-
2427
- const watchFileMutation = (sourceUrl, {
2428
- updated,
2429
- removed,
2430
- keepProcessAlive
2431
- }) => {
2432
- let watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
2433
- persistent: keepProcessAlive
2434
- });
2435
- watcher.on("change", () => {
2436
- const sourceType = fileSystemNodeToTypeOrNull(sourceUrl);
2437
-
2438
- if (sourceType === null) {
2439
- watcher.close();
2440
- watcher = undefined;
2441
-
2442
- if (removed) {
2443
- removed();
2444
- }
2445
- } else if (sourceType === "file") {
2446
- if (updated) {
2447
- updated();
2448
- }
2449
- }
2450
- });
2451
- return () => {
2452
- if (watcher) {
2453
- watcher.close();
2454
- }
2455
- };
2456
- };
2457
-
2458
- const resolveDirectoryUrl = (specifier, baseUrl) => {
2459
- const url = resolveUrl(specifier, baseUrl);
2460
- return ensureUrlTrailingSlash(url);
2461
- };
2462
-
2463
- const {
2464
- access
2465
- } = fs.promises;
2466
- const {
2467
- // F_OK,
2468
- R_OK,
2469
- W_OK,
2470
- X_OK
2471
- } = fs.constants;
2472
- const testFileSystemNodePermissions = async (source, {
2473
- read = false,
2474
- write = false,
2475
- execute = false,
2476
- allowedIfNotFound = false
2477
- } = {}) => {
2478
- const sourceUrl = assertAndNormalizeFileUrl(source);
2479
- const sourcePath = urlToFileSystemPath(sourceUrl);
2480
- let binaryFlags = 0; // if (visible) binaryFlags |= F_OK
2481
-
2482
- if (read) binaryFlags |= R_OK;
2483
- if (write) binaryFlags |= W_OK;
2484
- if (execute) binaryFlags |= X_OK;
2485
-
2486
- try {
2487
- await access(sourcePath, binaryFlags);
2488
- return true;
2489
- } catch (error) {
2490
- if (error.code === "ENOENT") {
2491
- if (allowedIfNotFound) {
2492
- return true;
2493
- }
2494
-
2495
- throw error;
2496
- }
2497
-
2498
- return false;
2499
- }
2500
- };
2501
-
2502
- const urlToScheme = url => {
2503
- const urlString = String(url);
2504
- const colonIndex = urlString.indexOf(":");
2505
-
2506
- if (colonIndex === -1) {
2507
- return "";
2508
- }
2509
-
2510
- const scheme = urlString.slice(0, colonIndex);
2511
- return scheme;
2512
- };
2513
-
2514
- const urlToRessource = url => {
2515
- const scheme = urlToScheme(url);
2516
-
2517
- if (scheme === "file") {
2518
- const urlAsStringWithoutFileProtocol = String(url).slice("file://".length);
2519
- return urlAsStringWithoutFileProtocol;
2520
- }
2521
-
2522
- if (scheme === "https" || scheme === "http") {
2523
- // remove origin
2524
- const afterProtocol = String(url).slice(scheme.length + "://".length);
2525
- const pathnameSlashIndex = afterProtocol.indexOf("/", "://".length);
2526
- const urlAsStringWithoutOrigin = afterProtocol.slice(pathnameSlashIndex);
2527
- return urlAsStringWithoutOrigin;
2528
- }
2529
-
2530
- const urlAsStringWithoutProtocol = String(url).slice(scheme.length + 1);
2531
- return urlAsStringWithoutProtocol;
2532
- };
2533
-
2534
- const urlToPathname = url => {
2535
- const ressource = urlToRessource(url);
2536
- const pathname = ressourceToPathname(ressource);
2537
- return pathname;
2538
- };
2539
-
2540
- const ressourceToPathname = ressource => {
2541
- const searchSeparatorIndex = ressource.indexOf("?");
2542
- return searchSeparatorIndex === -1 ? ressource : ressource.slice(0, searchSeparatorIndex);
2543
- };
2544
-
2545
- const urlToFilename = url => {
2546
- const pathname = urlToPathname(url);
2547
- const pathnameBeforeLastSlash = pathname.endsWith("/") ? pathname.slice(0, -1) : pathname;
2548
- const slashLastIndex = pathnameBeforeLastSlash.lastIndexOf("/");
2549
- const filename = slashLastIndex === -1 ? pathnameBeforeLastSlash : pathnameBeforeLastSlash.slice(slashLastIndex + 1);
2550
- return filename;
2551
- };
2552
-
2553
- const urlToBasename = url => {
2554
- const filename = urlToFilename(url);
2555
- const dotLastIndex = filename.lastIndexOf(".");
2556
- const basename = dotLastIndex === -1 ? filename : filename.slice(0, dotLastIndex);
2557
- return basename;
2558
- };
2559
-
2560
- const urlToExtension = url => {
2561
- const pathname = urlToPathname(url);
2562
- return pathnameToExtension(pathname);
2563
- };
2564
-
2565
- const pathnameToExtension = pathname => {
2566
- const slashLastIndex = pathname.lastIndexOf("/");
2567
-
2568
- if (slashLastIndex !== -1) {
2569
- pathname = pathname.slice(slashLastIndex + 1);
2570
- }
2571
-
2572
- const dotLastIndex = pathname.lastIndexOf(".");
2573
- if (dotLastIndex === -1) return ""; // if (dotLastIndex === pathname.length - 1) return ""
2574
-
2575
- const extension = pathname.slice(dotLastIndex);
2576
- return extension;
2577
- };
2578
-
2579
- const urlToOrigin = url => {
2580
- const urlString = String(url);
2581
-
2582
- if (urlString.startsWith("file://")) {
2583
- return `file://`;
2584
- }
2585
-
2586
- return new URL(urlString).origin;
2587
- };
2588
-
2589
- const urlToParentUrl = url => {
2590
- const ressource = urlToRessource(url);
2591
- const slashLastIndex = ressource.lastIndexOf("/");
2592
-
2593
- if (slashLastIndex === -1) {
2594
- const urlAsString = String(url);
2595
- return urlAsString;
2596
- }
2597
-
2598
- const lastCharacterIndex = ressource.length - 1;
2599
-
2600
- if (slashLastIndex === lastCharacterIndex) {
2601
- const slashPreviousIndex = ressource.lastIndexOf("/", lastCharacterIndex - 1);
2602
-
2603
- if (slashPreviousIndex === -1) {
2604
- const urlAsString = String(url);
2605
- return urlAsString;
2606
- }
2607
-
2608
- const origin = urlToOrigin(url);
2609
- const parentUrl = `${origin}${ressource.slice(0, slashPreviousIndex + 1)}`;
2610
- return parentUrl;
2611
- }
2612
-
2613
- const origin = urlToOrigin(url);
2614
- const parentUrl = `${origin}${ressource.slice(0, slashLastIndex + 1)}`;
2615
- return parentUrl;
2616
- };
2617
-
2618
- const {
2619
- writeFile: writeFileNode
2620
- } = fs.promises;
2621
- const writeFile = async (destination, content = "") => {
2622
- const destinationUrl = assertAndNormalizeFileUrl(destination);
2623
- const destinationPath = urlToFileSystemPath(destinationUrl);
2624
-
2625
- try {
2626
- await writeFileNode(destinationPath, content);
2627
- } catch (error) {
2628
- if (error.code === "ENOENT") {
2629
- await ensureParentDirectories(destinationUrl);
2630
- await writeFileNode(destinationPath, content);
2631
- return;
2632
- }
2633
-
2634
- throw error;
2635
- }
2636
- };
2637
-
2638
- Object.defineProperty(exports, 'applyPatternMatching', {
2639
- enumerable: true,
2640
- get: function () { return urlMeta.applyPatternMatching; }
2641
- });
2642
- Object.defineProperty(exports, 'normalizeStructuredMetaMap', {
2643
- enumerable: true,
2644
- get: function () { return urlMeta.normalizeStructuredMetaMap; }
2645
- });
2646
- Object.defineProperty(exports, 'urlCanContainsMetaMatching', {
2647
- enumerable: true,
2648
- get: function () { return urlMeta.urlCanContainsMetaMatching; }
2649
- });
2650
- Object.defineProperty(exports, 'urlToMeta', {
2651
- enumerable: true,
2652
- get: function () { return urlMeta.urlToMeta; }
2653
- });
2654
- exports.assertAndNormalizeDirectoryUrl = assertAndNormalizeDirectoryUrl;
2655
- exports.assertAndNormalizeFileUrl = assertAndNormalizeFileUrl;
2656
- exports.assertDirectoryPresence = assertDirectoryPresence;
2657
- exports.assertFilePresence = assertFilePresence;
2658
- exports.bufferToEtag = bufferToEtag;
2659
- exports.collectDirectoryMatchReport = collectDirectoryMatchReport;
2660
- exports.collectFiles = collectFiles;
2661
- exports.comparePathnames = comparePathnames;
2662
- exports.copyDirectoryContent = copyDirectoryContent;
2663
- exports.copyFileSystemNode = copyFileSystemNode;
2664
- exports.ensureEmptyDirectory = ensureEmptyDirectory;
2665
- exports.ensureParentDirectories = ensureParentDirectories;
2666
- exports.ensureWindowsDriveLetter = ensureWindowsDriveLetter;
2667
- exports.fileSystemPathToUrl = fileSystemPathToUrl;
2668
- exports.grantPermissionsOnFileSystemNode = grantPermissionsOnFileSystemNode;
2669
- exports.isFileSystemPath = isFileSystemPath;
2670
- exports.listFilesMatching = listFilesMatching;
2671
- exports.memoize = memoize;
2672
- exports.moveDirectoryContent = moveDirectoryContent;
2673
- exports.moveFileSystemNode = moveFileSystemNode;
2674
- exports.readDirectory = readDirectory;
2675
- exports.readFile = readFile;
2676
- exports.readFileSystemNodeModificationTime = readFileSystemNodeModificationTime;
2677
- exports.readFileSystemNodePermissions = readFileSystemNodePermissions;
2678
- exports.readFileSystemNodeStat = readFileSystemNodeStat;
2679
- exports.readSymbolicLink = readSymbolicLink;
2680
- exports.registerDirectoryLifecycle = registerDirectoryLifecycle;
2681
- exports.registerFileLifecycle = registerFileLifecycle;
2682
- exports.removeFileSystemNode = removeFileSystemNode;
2683
- exports.resolveDirectoryUrl = resolveDirectoryUrl;
2684
- exports.resolveUrl = resolveUrl;
2685
- exports.testFileSystemNodePermissions = testFileSystemNodePermissions;
2686
- exports.urlIsInsideOf = urlIsInsideOf;
2687
- exports.urlToBasename = urlToBasename;
2688
- exports.urlToExtension = urlToExtension;
2689
- exports.urlToFileSystemPath = urlToFileSystemPath;
2690
- exports.urlToFilename = urlToFilename;
2691
- exports.urlToOrigin = urlToOrigin;
2692
- exports.urlToParentUrl = urlToParentUrl;
2693
- exports.urlToPathname = urlToPathname;
2694
- exports.urlToRelativeUrl = urlToRelativeUrl;
2695
- exports.urlToRessource = urlToRessource;
2696
- exports.urlToScheme = urlToScheme;
2697
- exports.writeDirectory = writeDirectory;
2698
- exports.writeFile = writeFile;
2699
- exports.writeFileSystemNodeModificationTime = writeFileSystemNodeModificationTime;
2700
- exports.writeFileSystemNodePermissions = writeFileSystemNodePermissions;
2701
- exports.writeSymbolicLink = writeSymbolicLink;
2702
-
2703
- //# sourceMappingURL=jsenv_filesystem.cjs.map