@jsenv/filesystem 2.3.1 → 2.6.0

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