@jsenv/core 38.3.7 → 38.3.9

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.
Files changed (2) hide show
  1. package/dist/jsenv_core.js +615 -599
  2. package/package.json +9 -9
@@ -1,7 +1,7 @@
1
- import { chmod, stat, lstat, readdir, promises, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, writeFileSync as writeFileSync$1, mkdirSync, createReadStream, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
1
+ import { readdir, chmod, stat, lstat, promises, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, createReadStream, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
2
2
  import { pathToFileURL, fileURLToPath } from "node:url";
3
- import crypto, { createHash } from "node:crypto";
4
3
  import { extname } from "node:path";
4
+ import crypto, { createHash } from "node:crypto";
5
5
  import process$1 from "node:process";
6
6
  import os, { networkInterfaces } from "node:os";
7
7
  import tty from "node:tty";
@@ -61,6 +61,7 @@ const isWindowsPathnameSpecifier = (specifier) => {
61
61
  const hasScheme$1 = (specifier) => /^[a-zA-Z]+:/.test(specifier);
62
62
 
63
63
  const resolveAssociations = (associations, baseUrl) => {
64
+ if (baseUrl && typeof baseUrl.href === "string") baseUrl = baseUrl.href;
64
65
  assertUrlLike(baseUrl, "baseUrl");
65
66
  const associationsResolved = {};
66
67
  Object.keys(associations).forEach((key) => {
@@ -144,6 +145,7 @@ const asFlatAssociations = (associations) => {
144
145
  */
145
146
  const applyPatternMatching = ({ url, pattern }) => {
146
147
  assertUrlLike(pattern, "pattern");
148
+ if (url && typeof url.href === "string") url = url.href;
147
149
  assertUrlLike(url, "url");
148
150
  const { matched, patternIndex, index, groups } = applyMatching(pattern, url);
149
151
  const matchGroups = [];
@@ -391,6 +393,7 @@ const skipUntilMatch = ({ pattern, string, canSkipSlash }) => {
391
393
  };
392
394
 
393
395
  const applyAssociations = ({ url, associations }) => {
396
+ if (url && typeof url.href === "string") url = url.href;
394
397
  assertUrlLike(url);
395
398
  const flatAssociations = asFlatAssociations(associations);
396
399
  return Object.keys(flatAssociations).reduce((previousValue, pattern) => {
@@ -440,6 +443,7 @@ const applyAliases = ({ url, aliases }) => {
440
443
  };
441
444
 
442
445
  const urlChildMayMatch = ({ url, associations, predicate }) => {
446
+ if (url && typeof url.href === "string") url = url.href;
443
447
  assertUrlLike(url, "url");
444
448
  // the function was meants to be used on url ending with '/'
445
449
  if (!url.endsWith("/")) {
@@ -1273,291 +1277,163 @@ const assertAndNormalizeFileUrl = (
1273
1277
  return value;
1274
1278
  };
1275
1279
 
1276
- const statsToType = (stats) => {
1277
- if (stats.isFile()) return "file";
1278
- if (stats.isDirectory()) return "directory";
1279
- if (stats.isSymbolicLink()) return "symbolic-link";
1280
- if (stats.isFIFO()) return "fifo";
1281
- if (stats.isSocket()) return "socket";
1282
- if (stats.isCharacterDevice()) return "character-device";
1283
- if (stats.isBlockDevice()) return "block-device";
1284
- return undefined;
1285
- };
1286
-
1287
- // https://github.com/coderaiser/cloudcmd/issues/63#issuecomment-195478143
1288
- // https://nodejs.org/api/fs.html#fs_file_modes
1289
- // https://github.com/TooTallNate/stat-mode
1280
+ const comparePathnames = (leftPathame, rightPathname) => {
1281
+ const leftPartArray = leftPathame.split("/");
1282
+ const rightPartArray = rightPathname.split("/");
1290
1283
 
1291
- // cannot get from fs.constants because they are not available on windows
1292
- const S_IRUSR = 256; /* 0000400 read permission, owner */
1293
- const S_IWUSR = 128; /* 0000200 write permission, owner */
1294
- const S_IXUSR = 64; /* 0000100 execute/search permission, owner */
1295
- const S_IRGRP = 32; /* 0000040 read permission, group */
1296
- const S_IWGRP = 16; /* 0000020 write permission, group */
1297
- const S_IXGRP = 8; /* 0000010 execute/search permission, group */
1298
- const S_IROTH = 4; /* 0000004 read permission, others */
1299
- const S_IWOTH = 2; /* 0000002 write permission, others */
1300
- const S_IXOTH = 1; /* 0000001 execute/search permission, others */
1284
+ const leftLength = leftPartArray.length;
1285
+ const rightLength = rightPartArray.length;
1301
1286
 
1302
- const permissionsToBinaryFlags = ({ owner, group, others }) => {
1303
- let binaryFlags = 0;
1287
+ const maxLength = Math.max(leftLength, rightLength);
1288
+ let i = 0;
1289
+ while (i < maxLength) {
1290
+ const leftPartExists = i in leftPartArray;
1291
+ const rightPartExists = i in rightPartArray;
1304
1292
 
1305
- if (owner.read) binaryFlags |= S_IRUSR;
1306
- if (owner.write) binaryFlags |= S_IWUSR;
1307
- if (owner.execute) binaryFlags |= S_IXUSR;
1293
+ // longer comes first
1294
+ if (!leftPartExists) {
1295
+ return +1;
1296
+ }
1297
+ if (!rightPartExists) {
1298
+ return -1;
1299
+ }
1308
1300
 
1309
- if (group.read) binaryFlags |= S_IRGRP;
1310
- if (group.write) binaryFlags |= S_IWGRP;
1311
- if (group.execute) binaryFlags |= S_IXGRP;
1301
+ const leftPartIsLast = i === leftPartArray.length - 1;
1302
+ const rightPartIsLast = i === rightPartArray.length - 1;
1303
+ // folder comes first
1304
+ if (leftPartIsLast && !rightPartIsLast) {
1305
+ return +1;
1306
+ }
1307
+ if (!leftPartIsLast && rightPartIsLast) {
1308
+ return -1;
1309
+ }
1312
1310
 
1313
- if (others.read) binaryFlags |= S_IROTH;
1314
- if (others.write) binaryFlags |= S_IWOTH;
1315
- if (others.execute) binaryFlags |= S_IXOTH;
1311
+ const leftPart = leftPartArray[i];
1312
+ const rightPart = rightPartArray[i];
1313
+ i++;
1314
+ // local comparison comes first
1315
+ const comparison = leftPart.localeCompare(rightPart);
1316
+ if (comparison !== 0) {
1317
+ return comparison;
1318
+ }
1319
+ }
1316
1320
 
1317
- return binaryFlags;
1321
+ if (leftLength < rightLength) {
1322
+ return +1;
1323
+ }
1324
+ if (leftLength > rightLength) {
1325
+ return -1;
1326
+ }
1327
+ return 0;
1318
1328
  };
1319
1329
 
1320
- const writeEntryPermissions = async (source, permissions) => {
1321
- const sourceUrl = assertAndNormalizeFileUrl(source);
1322
-
1323
- let binaryFlags;
1324
- if (typeof permissions === "object") {
1325
- permissions = {
1326
- owner: {
1327
- read: getPermissionOrComputeDefault("read", "owner", permissions),
1328
- write: getPermissionOrComputeDefault("write", "owner", permissions),
1329
- execute: getPermissionOrComputeDefault("execute", "owner", permissions),
1330
- },
1331
- group: {
1332
- read: getPermissionOrComputeDefault("read", "group", permissions),
1333
- write: getPermissionOrComputeDefault("write", "group", permissions),
1334
- execute: getPermissionOrComputeDefault("execute", "group", permissions),
1335
- },
1336
- others: {
1337
- read: getPermissionOrComputeDefault("read", "others", permissions),
1338
- write: getPermissionOrComputeDefault("write", "others", permissions),
1339
- execute: getPermissionOrComputeDefault(
1340
- "execute",
1341
- "others",
1342
- permissions,
1343
- ),
1344
- },
1345
- };
1346
- binaryFlags = permissionsToBinaryFlags(permissions);
1347
- } else {
1348
- binaryFlags = permissions;
1349
- }
1330
+ const isWindows$3 = process.platform === "win32";
1331
+ const baseUrlFallback = fileSystemPathToUrl$1(process.cwd());
1350
1332
 
1351
- return new Promise((resolve, reject) => {
1352
- chmod(new URL(sourceUrl), binaryFlags, (error) => {
1353
- if (error) {
1354
- reject(error);
1355
- } else {
1356
- resolve();
1357
- }
1358
- });
1359
- });
1360
- };
1333
+ /**
1334
+ * Some url might be resolved or remapped to url without the windows drive letter.
1335
+ * For instance
1336
+ * new URL('/foo.js', 'file:///C:/dir/file.js')
1337
+ * resolves to
1338
+ * 'file:///foo.js'
1339
+ *
1340
+ * But on windows it becomes a problem because we need the drive letter otherwise
1341
+ * url cannot be converted to a filesystem path.
1342
+ *
1343
+ * ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.
1344
+ */
1361
1345
 
1362
- const actionLevels = { read: 0, write: 1, execute: 2 };
1363
- const subjectLevels = { others: 0, group: 1, owner: 2 };
1346
+ const ensureWindowsDriveLetter = (url, baseUrl) => {
1347
+ try {
1348
+ url = String(new URL(url));
1349
+ } catch (e) {
1350
+ throw new Error(`absolute url expected but got ${url}`);
1351
+ }
1364
1352
 
1365
- const getPermissionOrComputeDefault = (action, subject, permissions) => {
1366
- if (subject in permissions) {
1367
- const subjectPermissions = permissions[subject];
1368
- if (action in subjectPermissions) {
1369
- return subjectPermissions[action];
1370
- }
1353
+ if (!isWindows$3) {
1354
+ return url;
1355
+ }
1371
1356
 
1372
- const actionLevel = actionLevels[action];
1373
- const actionFallback = Object.keys(actionLevels).find(
1374
- (actionFallbackCandidate) =>
1375
- actionLevels[actionFallbackCandidate] > actionLevel &&
1376
- actionFallbackCandidate in subjectPermissions,
1357
+ try {
1358
+ baseUrl = String(new URL(baseUrl));
1359
+ } catch (e) {
1360
+ throw new Error(
1361
+ `absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`,
1377
1362
  );
1378
- if (actionFallback) {
1379
- return subjectPermissions[actionFallback];
1380
- }
1381
1363
  }
1382
1364
 
1383
- const subjectLevel = subjectLevels[subject];
1384
- // do we have a subject with a stronger level (group or owner)
1385
- // where we could read the action permission ?
1386
- const subjectFallback = Object.keys(subjectLevels).find(
1387
- (subjectFallbackCandidate) =>
1388
- subjectLevels[subjectFallbackCandidate] > subjectLevel &&
1389
- subjectFallbackCandidate in permissions,
1365
+ if (!url.startsWith("file://")) {
1366
+ return url;
1367
+ }
1368
+ const afterProtocol = url.slice("file://".length);
1369
+ // we still have the windows drive letter
1370
+ if (extractDriveLetter(afterProtocol)) {
1371
+ return url;
1372
+ }
1373
+
1374
+ // drive letter was lost, restore it
1375
+ const baseUrlOrFallback = baseUrl.startsWith("file://")
1376
+ ? baseUrl
1377
+ : baseUrlFallback;
1378
+ const driveLetter = extractDriveLetter(
1379
+ baseUrlOrFallback.slice("file://".length),
1390
1380
  );
1391
- if (subjectFallback) {
1392
- const subjectPermissions = permissions[subjectFallback];
1393
- return action in subjectPermissions
1394
- ? subjectPermissions[action]
1395
- : getPermissionOrComputeDefault(action, subjectFallback, permissions);
1381
+ if (!driveLetter) {
1382
+ throw new Error(
1383
+ `drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`,
1384
+ );
1396
1385
  }
1386
+ return `file:///${driveLetter}:${afterProtocol}`;
1387
+ };
1397
1388
 
1398
- return false;
1389
+ const extractDriveLetter = (resource) => {
1390
+ // we still have the windows drive letter
1391
+ if (/[a-zA-Z]/.test(resource[1]) && resource[2] === ":") {
1392
+ return resource[1];
1393
+ }
1394
+ return null;
1399
1395
  };
1400
1396
 
1401
1397
  /*
1402
- * - stats object documentation on Node.js
1403
- * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
1398
+ * See callback_race.md
1404
1399
  */
1405
1400
 
1401
+ const raceCallbacks = (raceDescription, winnerCallback) => {
1402
+ let cleanCallbacks = [];
1403
+ let status = "racing";
1406
1404
 
1407
- const isWindows$3 = process.platform === "win32";
1408
-
1409
- const readEntryStat = async (
1410
- source,
1411
- { nullIfNotFound = false, followLink = true } = {},
1412
- ) => {
1413
- let sourceUrl = assertAndNormalizeFileUrl(source);
1414
- if (sourceUrl.endsWith("/")) sourceUrl = sourceUrl.slice(0, -1);
1405
+ const clean = () => {
1406
+ cleanCallbacks.forEach((clean) => {
1407
+ clean();
1408
+ });
1409
+ cleanCallbacks = null;
1410
+ };
1415
1411
 
1416
- const sourcePath = urlToFileSystemPath(sourceUrl);
1412
+ const cancel = () => {
1413
+ if (status !== "racing") {
1414
+ return;
1415
+ }
1416
+ status = "cancelled";
1417
+ clean();
1418
+ };
1417
1419
 
1418
- const handleNotFoundOption = nullIfNotFound
1419
- ? {
1420
- handleNotFoundError: () => null,
1420
+ Object.keys(raceDescription).forEach((candidateName) => {
1421
+ const register = raceDescription[candidateName];
1422
+ const returnValue = register((data) => {
1423
+ if (status !== "racing") {
1424
+ return;
1421
1425
  }
1422
- : {};
1423
-
1424
- return readStat(sourcePath, {
1425
- followLink,
1426
- ...handleNotFoundOption,
1427
- ...(isWindows$3
1428
- ? {
1429
- // Windows can EPERM on stat
1430
- handlePermissionDeniedError: async (error) => {
1431
- console.error(
1432
- `trying to fix windows EPERM after stats on ${sourcePath}`,
1433
- );
1434
-
1435
- try {
1436
- // unfortunately it means we mutate the permissions
1437
- // without being able to restore them to the previous value
1438
- // (because reading current permission would also throw)
1439
- await writeEntryPermissions(sourceUrl, 0o666);
1440
- const stats = await readStat(sourcePath, {
1441
- followLink,
1442
- ...handleNotFoundOption,
1443
- // could not fix the permission error, give up and throw original error
1444
- handlePermissionDeniedError: () => {
1445
- console.error(`still got EPERM after stats on ${sourcePath}`);
1446
- throw error;
1447
- },
1448
- });
1449
- return stats;
1450
- } catch (e) {
1451
- console.error(
1452
- `error while trying to fix windows EPERM after stats on ${sourcePath}: ${e.stack}`,
1453
- );
1454
- throw error;
1455
- }
1456
- },
1457
- }
1458
- : {}),
1459
- });
1460
- };
1461
-
1462
- const readStat = (
1463
- sourcePath,
1464
- {
1465
- followLink,
1466
- handleNotFoundError = null,
1467
- handlePermissionDeniedError = null,
1468
- } = {},
1469
- ) => {
1470
- const nodeMethod = followLink ? stat : lstat;
1471
-
1472
- return new Promise((resolve, reject) => {
1473
- nodeMethod(sourcePath, (error, statsObject) => {
1474
- if (error) {
1475
- if (handleNotFoundError && error.code === "ENOENT") {
1476
- resolve(handleNotFoundError(error));
1477
- } else if (
1478
- handlePermissionDeniedError &&
1479
- (error.code === "EPERM" || error.code === "EACCES")
1480
- ) {
1481
- resolve(handlePermissionDeniedError(error));
1482
- } else {
1483
- reject(error);
1484
- }
1485
- } else {
1486
- resolve(statsObject);
1487
- }
1488
- });
1489
- });
1490
- };
1491
-
1492
- /*
1493
- * - Buffer documentation on Node.js
1494
- * https://nodejs.org/docs/latest-v13.x/api/buffer.html
1495
- * - eTag documentation on MDN
1496
- * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
1497
- */
1498
-
1499
-
1500
- const ETAG_FOR_EMPTY_CONTENT$1 = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
1501
-
1502
- const bufferToEtag$1 = (buffer) => {
1503
- if (!Buffer.isBuffer(buffer)) {
1504
- throw new TypeError(`buffer expected, got ${buffer}`);
1505
- }
1506
-
1507
- if (buffer.length === 0) {
1508
- return ETAG_FOR_EMPTY_CONTENT$1;
1509
- }
1510
-
1511
- const hash = createHash("sha1");
1512
- hash.update(buffer, "utf8");
1513
-
1514
- const hashBase64String = hash.digest("base64");
1515
- const hashBase64StringSubset = hashBase64String.slice(0, 27);
1516
- const length = buffer.length;
1517
-
1518
- return `"${length.toString(16)}-${hashBase64StringSubset}"`;
1519
- };
1520
-
1521
- /*
1522
- * See callback_race.md
1523
- */
1524
-
1525
- const raceCallbacks = (raceDescription, winnerCallback) => {
1526
- let cleanCallbacks = [];
1527
- let status = "racing";
1528
-
1529
- const clean = () => {
1530
- cleanCallbacks.forEach((clean) => {
1531
- clean();
1532
- });
1533
- cleanCallbacks = null;
1534
- };
1535
-
1536
- const cancel = () => {
1537
- if (status !== "racing") {
1538
- return;
1539
- }
1540
- status = "cancelled";
1541
- clean();
1542
- };
1543
-
1544
- Object.keys(raceDescription).forEach((candidateName) => {
1545
- const register = raceDescription[candidateName];
1546
- const returnValue = register((data) => {
1547
- if (status !== "racing") {
1548
- return;
1549
- }
1550
- status = "done";
1551
- clean();
1552
- winnerCallback({
1553
- name: candidateName,
1554
- data,
1555
- });
1556
- });
1557
- if (typeof returnValue === "function") {
1558
- cleanCallbacks.push(returnValue);
1559
- }
1560
- });
1426
+ status = "done";
1427
+ clean();
1428
+ winnerCallback({
1429
+ name: candidateName,
1430
+ data,
1431
+ });
1432
+ });
1433
+ if (typeof returnValue === "function") {
1434
+ cleanCallbacks.push(returnValue);
1435
+ }
1436
+ });
1561
1437
 
1562
1438
  return cancel;
1563
1439
  };
@@ -2044,78 +1920,244 @@ const readDirectory = async (url, { emfileMaxWait = 1000 } = {}) => {
2044
1920
  return attempt();
2045
1921
  };
2046
1922
 
2047
- const comparePathnames = (leftPathame, rightPathname) => {
2048
- const leftPartArray = leftPathame.split("/");
2049
- const rightPartArray = rightPathname.split("/");
1923
+ // https://github.com/coderaiser/cloudcmd/issues/63#issuecomment-195478143
1924
+ // https://nodejs.org/api/fs.html#fs_file_modes
1925
+ // https://github.com/TooTallNate/stat-mode
2050
1926
 
2051
- const leftLength = leftPartArray.length;
2052
- const rightLength = rightPartArray.length;
1927
+ // cannot get from fs.constants because they are not available on windows
1928
+ const S_IRUSR = 256; /* 0000400 read permission, owner */
1929
+ const S_IWUSR = 128; /* 0000200 write permission, owner */
1930
+ const S_IXUSR = 64; /* 0000100 execute/search permission, owner */
1931
+ const S_IRGRP = 32; /* 0000040 read permission, group */
1932
+ const S_IWGRP = 16; /* 0000020 write permission, group */
1933
+ const S_IXGRP = 8; /* 0000010 execute/search permission, group */
1934
+ const S_IROTH = 4; /* 0000004 read permission, others */
1935
+ const S_IWOTH = 2; /* 0000002 write permission, others */
1936
+ const S_IXOTH = 1; /* 0000001 execute/search permission, others */
2053
1937
 
2054
- const maxLength = Math.max(leftLength, rightLength);
2055
- let i = 0;
2056
- while (i < maxLength) {
2057
- const leftPartExists = i in leftPartArray;
2058
- const rightPartExists = i in rightPartArray;
1938
+ const permissionsToBinaryFlags = ({ owner, group, others }) => {
1939
+ let binaryFlags = 0;
2059
1940
 
2060
- // longer comes first
2061
- if (!leftPartExists) {
2062
- return +1;
2063
- }
2064
- if (!rightPartExists) {
2065
- return -1;
2066
- }
1941
+ if (owner.read) binaryFlags |= S_IRUSR;
1942
+ if (owner.write) binaryFlags |= S_IWUSR;
1943
+ if (owner.execute) binaryFlags |= S_IXUSR;
2067
1944
 
2068
- const leftPartIsLast = i === leftPartArray.length - 1;
2069
- const rightPartIsLast = i === rightPartArray.length - 1;
2070
- // folder comes first
2071
- if (leftPartIsLast && !rightPartIsLast) {
2072
- return +1;
2073
- }
2074
- if (!leftPartIsLast && rightPartIsLast) {
2075
- return -1;
2076
- }
1945
+ if (group.read) binaryFlags |= S_IRGRP;
1946
+ if (group.write) binaryFlags |= S_IWGRP;
1947
+ if (group.execute) binaryFlags |= S_IXGRP;
2077
1948
 
2078
- const leftPart = leftPartArray[i];
2079
- const rightPart = rightPartArray[i];
2080
- i++;
2081
- // local comparison comes first
2082
- const comparison = leftPart.localeCompare(rightPart);
2083
- if (comparison !== 0) {
2084
- return comparison;
2085
- }
2086
- }
1949
+ if (others.read) binaryFlags |= S_IROTH;
1950
+ if (others.write) binaryFlags |= S_IWOTH;
1951
+ if (others.execute) binaryFlags |= S_IXOTH;
2087
1952
 
2088
- if (leftLength < rightLength) {
2089
- return +1;
2090
- }
2091
- if (leftLength > rightLength) {
2092
- return -1;
2093
- }
2094
- return 0;
1953
+ return binaryFlags;
2095
1954
  };
2096
1955
 
2097
- // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_mkdir_path_options
2098
- const { mkdir } = promises;
2099
-
2100
- const writeDirectory = async (
2101
- destination,
2102
- { recursive = true, allowUseless = false } = {},
2103
- ) => {
2104
- const destinationUrl = assertAndNormalizeDirectoryUrl(destination);
2105
- const destinationPath = urlToFileSystemPath(destinationUrl);
1956
+ const writeEntryPermissions = async (source, permissions) => {
1957
+ const sourceUrl = assertAndNormalizeFileUrl(source);
2106
1958
 
2107
- const destinationStats = await readEntryStat(destinationUrl, {
2108
- nullIfNotFound: true,
2109
- followLink: false,
2110
- });
1959
+ let binaryFlags;
1960
+ if (typeof permissions === "object") {
1961
+ permissions = {
1962
+ owner: {
1963
+ read: getPermissionOrComputeDefault("read", "owner", permissions),
1964
+ write: getPermissionOrComputeDefault("write", "owner", permissions),
1965
+ execute: getPermissionOrComputeDefault("execute", "owner", permissions),
1966
+ },
1967
+ group: {
1968
+ read: getPermissionOrComputeDefault("read", "group", permissions),
1969
+ write: getPermissionOrComputeDefault("write", "group", permissions),
1970
+ execute: getPermissionOrComputeDefault("execute", "group", permissions),
1971
+ },
1972
+ others: {
1973
+ read: getPermissionOrComputeDefault("read", "others", permissions),
1974
+ write: getPermissionOrComputeDefault("write", "others", permissions),
1975
+ execute: getPermissionOrComputeDefault(
1976
+ "execute",
1977
+ "others",
1978
+ permissions,
1979
+ ),
1980
+ },
1981
+ };
1982
+ binaryFlags = permissionsToBinaryFlags(permissions);
1983
+ } else {
1984
+ binaryFlags = permissions;
1985
+ }
2111
1986
 
2112
- if (destinationStats) {
2113
- if (destinationStats.isDirectory()) {
2114
- if (allowUseless) {
2115
- return;
1987
+ return new Promise((resolve, reject) => {
1988
+ chmod(new URL(sourceUrl), binaryFlags, (error) => {
1989
+ if (error) {
1990
+ reject(error);
1991
+ } else {
1992
+ resolve();
2116
1993
  }
2117
- throw new Error(`directory already exists at ${destinationPath}`);
2118
- }
1994
+ });
1995
+ });
1996
+ };
1997
+
1998
+ const actionLevels = { read: 0, write: 1, execute: 2 };
1999
+ const subjectLevels = { others: 0, group: 1, owner: 2 };
2000
+
2001
+ const getPermissionOrComputeDefault = (action, subject, permissions) => {
2002
+ if (subject in permissions) {
2003
+ const subjectPermissions = permissions[subject];
2004
+ if (action in subjectPermissions) {
2005
+ return subjectPermissions[action];
2006
+ }
2007
+
2008
+ const actionLevel = actionLevels[action];
2009
+ const actionFallback = Object.keys(actionLevels).find(
2010
+ (actionFallbackCandidate) =>
2011
+ actionLevels[actionFallbackCandidate] > actionLevel &&
2012
+ actionFallbackCandidate in subjectPermissions,
2013
+ );
2014
+ if (actionFallback) {
2015
+ return subjectPermissions[actionFallback];
2016
+ }
2017
+ }
2018
+
2019
+ const subjectLevel = subjectLevels[subject];
2020
+ // do we have a subject with a stronger level (group or owner)
2021
+ // where we could read the action permission ?
2022
+ const subjectFallback = Object.keys(subjectLevels).find(
2023
+ (subjectFallbackCandidate) =>
2024
+ subjectLevels[subjectFallbackCandidate] > subjectLevel &&
2025
+ subjectFallbackCandidate in permissions,
2026
+ );
2027
+ if (subjectFallback) {
2028
+ const subjectPermissions = permissions[subjectFallback];
2029
+ return action in subjectPermissions
2030
+ ? subjectPermissions[action]
2031
+ : getPermissionOrComputeDefault(action, subjectFallback, permissions);
2032
+ }
2033
+
2034
+ return false;
2035
+ };
2036
+
2037
+ /*
2038
+ * - stats object documentation on Node.js
2039
+ * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
2040
+ */
2041
+
2042
+
2043
+ const isWindows$2 = process.platform === "win32";
2044
+
2045
+ const readEntryStat = async (
2046
+ source,
2047
+ { nullIfNotFound = false, followLink = true } = {},
2048
+ ) => {
2049
+ let sourceUrl = assertAndNormalizeFileUrl(source);
2050
+ if (sourceUrl.endsWith("/")) sourceUrl = sourceUrl.slice(0, -1);
2051
+
2052
+ const sourcePath = urlToFileSystemPath(sourceUrl);
2053
+
2054
+ const handleNotFoundOption = nullIfNotFound
2055
+ ? {
2056
+ handleNotFoundError: () => null,
2057
+ }
2058
+ : {};
2059
+
2060
+ return readStat(sourcePath, {
2061
+ followLink,
2062
+ ...handleNotFoundOption,
2063
+ ...(isWindows$2
2064
+ ? {
2065
+ // Windows can EPERM on stat
2066
+ handlePermissionDeniedError: async (error) => {
2067
+ console.error(
2068
+ `trying to fix windows EPERM after stats on ${sourcePath}`,
2069
+ );
2070
+
2071
+ try {
2072
+ // unfortunately it means we mutate the permissions
2073
+ // without being able to restore them to the previous value
2074
+ // (because reading current permission would also throw)
2075
+ await writeEntryPermissions(sourceUrl, 0o666);
2076
+ const stats = await readStat(sourcePath, {
2077
+ followLink,
2078
+ ...handleNotFoundOption,
2079
+ // could not fix the permission error, give up and throw original error
2080
+ handlePermissionDeniedError: () => {
2081
+ console.error(`still got EPERM after stats on ${sourcePath}`);
2082
+ throw error;
2083
+ },
2084
+ });
2085
+ return stats;
2086
+ } catch (e) {
2087
+ console.error(
2088
+ `error while trying to fix windows EPERM after stats on ${sourcePath}: ${e.stack}`,
2089
+ );
2090
+ throw error;
2091
+ }
2092
+ },
2093
+ }
2094
+ : {}),
2095
+ });
2096
+ };
2097
+
2098
+ const readStat = (
2099
+ sourcePath,
2100
+ {
2101
+ followLink,
2102
+ handleNotFoundError = null,
2103
+ handlePermissionDeniedError = null,
2104
+ } = {},
2105
+ ) => {
2106
+ const nodeMethod = followLink ? stat : lstat;
2107
+
2108
+ return new Promise((resolve, reject) => {
2109
+ nodeMethod(sourcePath, (error, statsObject) => {
2110
+ if (error) {
2111
+ if (handleNotFoundError && error.code === "ENOENT") {
2112
+ resolve(handleNotFoundError(error));
2113
+ } else if (
2114
+ handlePermissionDeniedError &&
2115
+ (error.code === "EPERM" || error.code === "EACCES")
2116
+ ) {
2117
+ resolve(handlePermissionDeniedError(error));
2118
+ } else {
2119
+ reject(error);
2120
+ }
2121
+ } else {
2122
+ resolve(statsObject);
2123
+ }
2124
+ });
2125
+ });
2126
+ };
2127
+
2128
+ const statsToType = (stats) => {
2129
+ if (stats.isFile()) return "file";
2130
+ if (stats.isDirectory()) return "directory";
2131
+ if (stats.isSymbolicLink()) return "symbolic-link";
2132
+ if (stats.isFIFO()) return "fifo";
2133
+ if (stats.isSocket()) return "socket";
2134
+ if (stats.isCharacterDevice()) return "character-device";
2135
+ if (stats.isBlockDevice()) return "block-device";
2136
+ return undefined;
2137
+ };
2138
+
2139
+ // https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_fspromises_mkdir_path_options
2140
+ const { mkdir } = promises;
2141
+
2142
+ const writeDirectory = async (
2143
+ destination,
2144
+ { recursive = true, allowUseless = false } = {},
2145
+ ) => {
2146
+ const destinationUrl = assertAndNormalizeDirectoryUrl(destination);
2147
+ const destinationPath = urlToFileSystemPath(destinationUrl);
2148
+
2149
+ const destinationStats = await readEntryStat(destinationUrl, {
2150
+ nullIfNotFound: true,
2151
+ followLink: false,
2152
+ });
2153
+
2154
+ if (destinationStats) {
2155
+ if (destinationStats.isDirectory()) {
2156
+ if (allowUseless) {
2157
+ return;
2158
+ }
2159
+ throw new Error(`directory already exists at ${destinationPath}`);
2160
+ }
2119
2161
 
2120
2162
  const destinationType = statsToType(destinationStats);
2121
2163
  throw new Error(
@@ -2133,6 +2175,23 @@ const writeDirectory = async (
2133
2175
  }
2134
2176
  };
2135
2177
 
2178
+ const writeFileSync = (destination, content = "") => {
2179
+ const destinationUrl = assertAndNormalizeFileUrl(destination);
2180
+ const destinationUrlObject = new URL(destinationUrl);
2181
+ try {
2182
+ writeFileSync$1(destinationUrlObject, content);
2183
+ } catch (error) {
2184
+ if (error.code === "ENOENT") {
2185
+ mkdirSync(new URL("./", destinationUrlObject), {
2186
+ recursive: true,
2187
+ });
2188
+ writeFileSync$1(destinationUrlObject, content);
2189
+ return;
2190
+ }
2191
+ throw error;
2192
+ }
2193
+ };
2194
+
2136
2195
  const removeEntry = async (
2137
2196
  source,
2138
2197
  {
@@ -2378,100 +2437,231 @@ const removeDirectoryNaive = (
2378
2437
  });
2379
2438
  };
2380
2439
 
2381
- const ensureEmptyDirectory = async (source) => {
2382
- const stats = await readEntryStat(source, {
2383
- nullIfNotFound: true,
2384
- followLink: false,
2385
- });
2386
- if (stats === null) {
2387
- // if there is nothing, create a directory
2388
- return writeDirectory(source, { allowUseless: true });
2389
- }
2390
- if (stats.isDirectory()) {
2391
- // if there is a directory remove its content and done
2392
- return removeEntry(source, {
2393
- allowUseless: true,
2394
- recursive: true,
2395
- onlyContent: true,
2396
- });
2397
- }
2440
+ process.platform === "win32";
2398
2441
 
2399
- const sourceType = statsToType(stats);
2400
- const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source));
2401
- throw new Error(
2402
- `ensureEmptyDirectory expect directory at ${sourcePath}, found ${sourceType} instead`,
2403
- );
2404
- };
2442
+ /*
2443
+ * - stats object documentation on Node.js
2444
+ * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
2445
+ */
2405
2446
 
2406
- const isWindows$2 = process.platform === "win32";
2407
- const baseUrlFallback = fileSystemPathToUrl$1(process.cwd());
2408
2447
 
2409
- /**
2410
- * Some url might be resolved or remapped to url without the windows drive letter.
2411
- * For instance
2412
- * new URL('/foo.js', 'file:///C:/dir/file.js')
2413
- * resolves to
2414
- * 'file:///foo.js'
2415
- *
2416
- * But on windows it becomes a problem because we need the drive letter otherwise
2417
- * url cannot be converted to a filesystem path.
2418
- *
2419
- * ensureWindowsDriveLetter ensure a resolved url still contains the drive letter.
2420
- */
2448
+ process.platform === "win32";
2421
2449
 
2422
- const ensureWindowsDriveLetter = (url, baseUrl) => {
2423
- try {
2424
- url = String(new URL(url));
2425
- } catch (e) {
2426
- throw new Error(`absolute url expected but got ${url}`);
2427
- }
2450
+ const mediaTypeInfos = {
2451
+ "application/json": {
2452
+ extensions: ["json"],
2453
+ isTextual: true,
2454
+ },
2455
+ "application/importmap+json": {
2456
+ extensions: ["importmap"],
2457
+ isTextual: true,
2458
+ },
2459
+ "application/manifest+json": {
2460
+ extensions: ["webmanifest"],
2461
+ isTextual: true,
2462
+ },
2463
+ "application/octet-stream": {},
2464
+ "application/pdf": {
2465
+ extensions: ["pdf"],
2466
+ },
2467
+ "application/xml": {
2468
+ extensions: ["xml"],
2469
+ },
2470
+ "application/x-gzip": {
2471
+ extensions: ["gz"],
2472
+ },
2473
+ "application/wasm": {
2474
+ extensions: ["wasm"],
2475
+ },
2476
+ "application/zip": {
2477
+ extensions: ["zip"],
2478
+ },
2479
+ "audio/basic": {
2480
+ extensions: ["au", "snd"],
2481
+ },
2482
+ "audio/mpeg": {
2483
+ extensions: ["mpga", "mp2", "mp2a", "mp3", "m2a", "m3a"],
2484
+ },
2485
+ "audio/midi": {
2486
+ extensions: ["midi", "mid", "kar", "rmi"],
2487
+ },
2488
+ "audio/mp4": {
2489
+ extensions: ["m4a", "mp4a"],
2490
+ },
2491
+ "audio/ogg": {
2492
+ extensions: ["oga", "ogg", "spx"],
2493
+ },
2494
+ "audio/webm": {
2495
+ extensions: ["weba"],
2496
+ },
2497
+ "audio/x-wav": {
2498
+ extensions: ["wav"],
2499
+ },
2500
+ "font/ttf": {
2501
+ extensions: ["ttf"],
2502
+ },
2503
+ "font/woff": {
2504
+ extensions: ["woff"],
2505
+ },
2506
+ "font/woff2": {
2507
+ extensions: ["woff2"],
2508
+ },
2509
+ "image/png": {
2510
+ extensions: ["png"],
2511
+ },
2512
+ "image/gif": {
2513
+ extensions: ["gif"],
2514
+ },
2515
+ "image/jpeg": {
2516
+ extensions: ["jpg"],
2517
+ },
2518
+ "image/svg+xml": {
2519
+ extensions: ["svg", "svgz"],
2520
+ isTextual: true,
2521
+ },
2522
+ "text/plain": {
2523
+ extensions: ["txt"],
2524
+ },
2525
+ "text/html": {
2526
+ extensions: ["html"],
2527
+ },
2528
+ "text/css": {
2529
+ extensions: ["css"],
2530
+ },
2531
+ "text/javascript": {
2532
+ extensions: ["js", "cjs", "mjs", "ts", "jsx", "tsx"],
2533
+ },
2534
+ "text/x-sass": {
2535
+ extensions: ["sass"],
2536
+ },
2537
+ "text/x-scss": {
2538
+ extensions: ["scss"],
2539
+ },
2540
+ "text/cache-manifest": {
2541
+ extensions: ["appcache"],
2542
+ },
2543
+ "video/mp4": {
2544
+ extensions: ["mp4", "mp4v", "mpg4"],
2545
+ },
2546
+ "video/mpeg": {
2547
+ extensions: ["mpeg", "mpg", "mpe", "m1v", "m2v"],
2548
+ },
2549
+ "video/ogg": {
2550
+ extensions: ["ogv"],
2551
+ },
2552
+ "video/webm": {
2553
+ extensions: ["webm"],
2554
+ },
2555
+ };
2428
2556
 
2429
- if (!isWindows$2) {
2430
- return url;
2431
- }
2557
+ const CONTENT_TYPE = {
2558
+ parse: (string) => {
2559
+ const [mediaType, charset] = string.split(";");
2560
+ return { mediaType: normalizeMediaType(mediaType), charset };
2561
+ },
2432
2562
 
2433
- try {
2434
- baseUrl = String(new URL(baseUrl));
2435
- } catch (e) {
2436
- throw new Error(
2437
- `absolute baseUrl expected but got ${baseUrl} to ensure windows drive letter on ${url}`,
2438
- );
2439
- }
2563
+ stringify: ({ mediaType, charset }) => {
2564
+ if (charset) {
2565
+ return `${mediaType};${charset}`;
2566
+ }
2567
+ return mediaType;
2568
+ },
2440
2569
 
2441
- if (!url.startsWith("file://")) {
2442
- return url;
2443
- }
2444
- const afterProtocol = url.slice("file://".length);
2445
- // we still have the windows drive letter
2446
- if (extractDriveLetter(afterProtocol)) {
2447
- return url;
2448
- }
2570
+ asMediaType: (value) => {
2571
+ if (typeof value === "string") {
2572
+ return CONTENT_TYPE.parse(value).mediaType;
2573
+ }
2574
+ if (typeof value === "object") {
2575
+ return value.mediaType;
2576
+ }
2577
+ return null;
2578
+ },
2449
2579
 
2450
- // drive letter was lost, restore it
2451
- const baseUrlOrFallback = baseUrl.startsWith("file://")
2452
- ? baseUrl
2453
- : baseUrlFallback;
2454
- const driveLetter = extractDriveLetter(
2455
- baseUrlOrFallback.slice("file://".length),
2456
- );
2457
- if (!driveLetter) {
2458
- throw new Error(
2459
- `drive letter expected on baseUrl but got ${baseUrl} to ensure windows drive letter on ${url}`,
2580
+ isJson: (value) => {
2581
+ const mediaType = CONTENT_TYPE.asMediaType(value);
2582
+ return (
2583
+ mediaType === "application/json" ||
2584
+ /^application\/\w+\+json$/.test(mediaType)
2460
2585
  );
2461
- }
2462
- return `file:///${driveLetter}:${afterProtocol}`;
2586
+ },
2587
+
2588
+ isTextual: (value) => {
2589
+ const mediaType = CONTENT_TYPE.asMediaType(value);
2590
+ if (mediaType.startsWith("text/")) {
2591
+ return true;
2592
+ }
2593
+ const mediaTypeInfo = mediaTypeInfos[mediaType];
2594
+ if (mediaTypeInfo && mediaTypeInfo.isTextual) {
2595
+ return true;
2596
+ }
2597
+ // catch things like application/manifest+json, application/importmap+json
2598
+ if (/^application\/\w+\+json$/.test(mediaType)) {
2599
+ return true;
2600
+ }
2601
+ return false;
2602
+ },
2603
+
2604
+ isBinary: (value) => !CONTENT_TYPE.isTextual(value),
2605
+
2606
+ asFileExtension: (value) => {
2607
+ const mediaType = CONTENT_TYPE.asMediaType(value);
2608
+ const mediaTypeInfo = mediaTypeInfos[mediaType];
2609
+ return mediaTypeInfo ? `.${mediaTypeInfo.extensions[0]}` : "";
2610
+ },
2611
+
2612
+ fromUrlExtension: (url) => {
2613
+ const { pathname } = new URL(url);
2614
+ const extensionWithDot = extname(pathname);
2615
+ if (!extensionWithDot || extensionWithDot === ".") {
2616
+ return "application/octet-stream";
2617
+ }
2618
+ const extension = extensionWithDot.slice(1);
2619
+ const mediaTypeFound = Object.keys(mediaTypeInfos).find((mediaType) => {
2620
+ const mediaTypeInfo = mediaTypeInfos[mediaType];
2621
+ return (
2622
+ mediaTypeInfo.extensions && mediaTypeInfo.extensions.includes(extension)
2623
+ );
2624
+ });
2625
+ return mediaTypeFound || "application/octet-stream";
2626
+ },
2463
2627
  };
2464
2628
 
2465
- const extractDriveLetter = (resource) => {
2466
- // we still have the windows drive letter
2467
- if (/[a-zA-Z]/.test(resource[1]) && resource[2] === ":") {
2468
- return resource[1];
2629
+ const normalizeMediaType = (value) => {
2630
+ if (value === "application/javascript") {
2631
+ return "text/javascript";
2469
2632
  }
2470
- return null;
2633
+ return value;
2471
2634
  };
2472
2635
 
2473
2636
  process.platform === "win32";
2474
2637
 
2638
+ const ensureEmptyDirectory = async (source) => {
2639
+ const stats = await readEntryStat(source, {
2640
+ nullIfNotFound: true,
2641
+ followLink: false,
2642
+ });
2643
+ if (stats === null) {
2644
+ // if there is nothing, create a directory
2645
+ await writeDirectory(source, { allowUseless: true });
2646
+ return;
2647
+ }
2648
+ if (stats.isDirectory()) {
2649
+ // if there is a directory remove its content and done
2650
+ await removeEntry(source, {
2651
+ allowUseless: true,
2652
+ recursive: true,
2653
+ onlyContent: true,
2654
+ });
2655
+ return;
2656
+ }
2657
+
2658
+ const sourceType = statsToType(stats);
2659
+ const sourcePath = urlToFileSystemPath(assertAndNormalizeFileUrl(source));
2660
+ throw new Error(
2661
+ `ensureEmptyDirectory expect directory at ${sourcePath}, found ${sourceType} instead`,
2662
+ );
2663
+ };
2664
+
2475
2665
  const guardTooFastSecondCallPerFile = (
2476
2666
  callback,
2477
2667
  cooldownBetweenFileEvents = 40,
@@ -2917,21 +3107,33 @@ const fileSystemPathToDirectoryRelativeUrlAndFilename = (path) => {
2917
3107
  };
2918
3108
  };
2919
3109
 
2920
- const writeFileSync = (destination, content = "") => {
2921
- const destinationUrl = assertAndNormalizeFileUrl(destination);
2922
- const destinationUrlObject = new URL(destinationUrl);
2923
- try {
2924
- writeFileSync$1(destinationUrlObject, content);
2925
- } catch (error) {
2926
- if (error.code === "ENOENT") {
2927
- mkdirSync(new URL("./", destinationUrlObject), {
2928
- recursive: true,
2929
- });
2930
- writeFileSync$1(destinationUrlObject, content);
2931
- return;
2932
- }
2933
- throw error;
3110
+ /*
3111
+ * - Buffer documentation on Node.js
3112
+ * https://nodejs.org/docs/latest-v13.x/api/buffer.html
3113
+ * - eTag documentation on MDN
3114
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
3115
+ */
3116
+
3117
+
3118
+ const ETAG_FOR_EMPTY_CONTENT$1 = '"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"';
3119
+
3120
+ const bufferToEtag$1 = (buffer) => {
3121
+ if (!Buffer.isBuffer(buffer)) {
3122
+ throw new TypeError(`buffer expected, got ${buffer}`);
3123
+ }
3124
+
3125
+ if (buffer.length === 0) {
3126
+ return ETAG_FOR_EMPTY_CONTENT$1;
2934
3127
  }
3128
+
3129
+ const hash = createHash("sha1");
3130
+ hash.update(buffer, "utf8");
3131
+
3132
+ const hashBase64String = hash.digest("base64");
3133
+ const hashBase64StringSubset = hashBase64String.slice(0, 27);
3134
+ const length = buffer.length;
3135
+
3136
+ return `"${length.toString(16)}-${hashBase64StringSubset}"`;
2935
3137
  };
2936
3138
 
2937
3139
  const LOG_LEVEL_OFF = "off";
@@ -6914,192 +7116,6 @@ const PROCESS_TEARDOWN_EVENTS_MAP = {
6914
7116
  exit: STOP_REASON_PROCESS_EXIT,
6915
7117
  };
6916
7118
 
6917
- const mediaTypeInfos = {
6918
- "application/json": {
6919
- extensions: ["json"],
6920
- isTextual: true,
6921
- },
6922
- "application/importmap+json": {
6923
- extensions: ["importmap"],
6924
- isTextual: true,
6925
- },
6926
- "application/manifest+json": {
6927
- extensions: ["webmanifest"],
6928
- isTextual: true,
6929
- },
6930
- "application/octet-stream": {},
6931
- "application/pdf": {
6932
- extensions: ["pdf"],
6933
- },
6934
- "application/xml": {
6935
- extensions: ["xml"],
6936
- },
6937
- "application/x-gzip": {
6938
- extensions: ["gz"],
6939
- },
6940
- "application/wasm": {
6941
- extensions: ["wasm"],
6942
- },
6943
- "application/zip": {
6944
- extensions: ["zip"],
6945
- },
6946
- "audio/basic": {
6947
- extensions: ["au", "snd"],
6948
- },
6949
- "audio/mpeg": {
6950
- extensions: ["mpga", "mp2", "mp2a", "mp3", "m2a", "m3a"],
6951
- },
6952
- "audio/midi": {
6953
- extensions: ["midi", "mid", "kar", "rmi"],
6954
- },
6955
- "audio/mp4": {
6956
- extensions: ["m4a", "mp4a"],
6957
- },
6958
- "audio/ogg": {
6959
- extensions: ["oga", "ogg", "spx"],
6960
- },
6961
- "audio/webm": {
6962
- extensions: ["weba"],
6963
- },
6964
- "audio/x-wav": {
6965
- extensions: ["wav"],
6966
- },
6967
- "font/ttf": {
6968
- extensions: ["ttf"],
6969
- },
6970
- "font/woff": {
6971
- extensions: ["woff"],
6972
- },
6973
- "font/woff2": {
6974
- extensions: ["woff2"],
6975
- },
6976
- "image/png": {
6977
- extensions: ["png"],
6978
- },
6979
- "image/gif": {
6980
- extensions: ["gif"],
6981
- },
6982
- "image/jpeg": {
6983
- extensions: ["jpg"],
6984
- },
6985
- "image/svg+xml": {
6986
- extensions: ["svg", "svgz"],
6987
- isTextual: true,
6988
- },
6989
- "text/plain": {
6990
- extensions: ["txt"],
6991
- },
6992
- "text/html": {
6993
- extensions: ["html"],
6994
- },
6995
- "text/css": {
6996
- extensions: ["css"],
6997
- },
6998
- "text/javascript": {
6999
- extensions: ["js", "cjs", "mjs", "ts", "jsx", "tsx"],
7000
- },
7001
- "text/x-sass": {
7002
- extensions: ["sass"],
7003
- },
7004
- "text/x-scss": {
7005
- extensions: ["scss"],
7006
- },
7007
- "text/cache-manifest": {
7008
- extensions: ["appcache"],
7009
- },
7010
- "video/mp4": {
7011
- extensions: ["mp4", "mp4v", "mpg4"],
7012
- },
7013
- "video/mpeg": {
7014
- extensions: ["mpeg", "mpg", "mpe", "m1v", "m2v"],
7015
- },
7016
- "video/ogg": {
7017
- extensions: ["ogv"],
7018
- },
7019
- "video/webm": {
7020
- extensions: ["webm"],
7021
- },
7022
- };
7023
-
7024
- const CONTENT_TYPE = {
7025
- parse: (string) => {
7026
- const [mediaType, charset] = string.split(";");
7027
- return { mediaType: normalizeMediaType(mediaType), charset };
7028
- },
7029
-
7030
- stringify: ({ mediaType, charset }) => {
7031
- if (charset) {
7032
- return `${mediaType};${charset}`;
7033
- }
7034
- return mediaType;
7035
- },
7036
-
7037
- asMediaType: (value) => {
7038
- if (typeof value === "string") {
7039
- return CONTENT_TYPE.parse(value).mediaType;
7040
- }
7041
- if (typeof value === "object") {
7042
- return value.mediaType;
7043
- }
7044
- return null;
7045
- },
7046
-
7047
- isJson: (value) => {
7048
- const mediaType = CONTENT_TYPE.asMediaType(value);
7049
- return (
7050
- mediaType === "application/json" ||
7051
- /^application\/\w+\+json$/.test(mediaType)
7052
- );
7053
- },
7054
-
7055
- isTextual: (value) => {
7056
- const mediaType = CONTENT_TYPE.asMediaType(value);
7057
- if (mediaType.startsWith("text/")) {
7058
- return true;
7059
- }
7060
- const mediaTypeInfo = mediaTypeInfos[mediaType];
7061
- if (mediaTypeInfo && mediaTypeInfo.isTextual) {
7062
- return true;
7063
- }
7064
- // catch things like application/manifest+json, application/importmap+json
7065
- if (/^application\/\w+\+json$/.test(mediaType)) {
7066
- return true;
7067
- }
7068
- return false;
7069
- },
7070
-
7071
- isBinary: (value) => !CONTENT_TYPE.isTextual(value),
7072
-
7073
- asFileExtension: (value) => {
7074
- const mediaType = CONTENT_TYPE.asMediaType(value);
7075
- const mediaTypeInfo = mediaTypeInfos[mediaType];
7076
- return mediaTypeInfo ? `.${mediaTypeInfo.extensions[0]}` : "";
7077
- },
7078
-
7079
- fromUrlExtension: (url) => {
7080
- const { pathname } = new URL(url);
7081
- const extensionWithDot = extname(pathname);
7082
- if (!extensionWithDot || extensionWithDot === ".") {
7083
- return "application/octet-stream";
7084
- }
7085
- const extension = extensionWithDot.slice(1);
7086
- const mediaTypeFound = Object.keys(mediaTypeInfos).find((mediaType) => {
7087
- const mediaTypeInfo = mediaTypeInfos[mediaType];
7088
- return (
7089
- mediaTypeInfo.extensions && mediaTypeInfo.extensions.includes(extension)
7090
- );
7091
- });
7092
- return mediaTypeFound || "application/octet-stream";
7093
- },
7094
- };
7095
-
7096
- const normalizeMediaType = (value) => {
7097
- if (value === "application/javascript") {
7098
- return "text/javascript";
7099
- }
7100
- return value;
7101
- };
7102
-
7103
7119
  const isFileSystemPath = (value) => {
7104
7120
  if (typeof value !== "string") {
7105
7121
  throw new TypeError(