@jsenv/core 40.6.2 → 40.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/build/browserslist_index/browserslist_index.js +62 -48
  2. package/dist/build/build.js +412 -185
  3. package/dist/build/jsenv_core_packages.js +103 -105
  4. package/dist/client/directory_listing/js/directory_listing.js +41 -26
  5. package/dist/client/ribbon/ribbon.js +40 -37
  6. package/dist/jsenv_core.js +4 -0
  7. package/dist/start_build_server/jsenv_core_packages.js +29 -29
  8. package/dist/start_dev_server/jsenv_core_packages.js +103 -105
  9. package/dist/start_dev_server/start_dev_server.js +412 -182
  10. package/package.json +21 -12
  11. package/src/build/build.js +9 -9
  12. package/src/build/build_specifier_manager.js +3 -3
  13. package/src/build/build_urls_generator.js +2 -2
  14. package/src/dev/start_dev_server.js +11 -8
  15. package/src/helpers/web_url_converter.js +2 -2
  16. package/src/kitchen/errors.js +1 -1
  17. package/src/kitchen/kitchen.js +2 -0
  18. package/src/kitchen/out_directory_url.js +2 -2
  19. package/src/kitchen/url_graph/url_graph.js +1 -0
  20. package/src/kitchen/url_graph/url_info_injections.js +172 -0
  21. package/src/kitchen/url_graph/url_info_transformations.js +28 -7
  22. package/src/main.js +1 -1
  23. package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +2 -2
  24. package/src/plugins/chrome_devtools_json/jsenv_plugin_chrome_devtools_json.js +1 -0
  25. package/src/plugins/global_scenarios/jsenv_plugin_global_scenarios.js +4 -9
  26. package/src/plugins/import_meta_scenarios/jsenv_plugin_import_meta_scenarios.js +2 -0
  27. package/src/plugins/injections/jsenv_plugin_injections.js +51 -85
  28. package/src/plugins/plugin_controller.js +28 -7
  29. package/src/plugins/plugins.js +3 -1
  30. package/src/plugins/protocol_file/client/directory_listing.jsx +42 -23
  31. package/src/plugins/protocol_file/file_and_server_urls_converter.js +2 -5
  32. package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +65 -49
  33. package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +36 -3
  34. package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +3 -0
  35. package/src/plugins/ribbon/client/ribbon.js +40 -37
  36. package/src/plugins/injections/internal/inject_globals.js +0 -52
@@ -516,79 +516,6 @@ const SIGINT_CALLBACK = {
516
516
  },
517
517
  };
518
518
 
519
- const isFileSystemPath = (value) => {
520
- if (typeof value !== "string") {
521
- throw new TypeError(
522
- `isFileSystemPath first arg must be a string, got ${value}`,
523
- );
524
- }
525
- if (value[0] === "/") {
526
- return true;
527
- }
528
- return startsWithWindowsDriveLetter(value);
529
- };
530
-
531
- const startsWithWindowsDriveLetter = (string) => {
532
- const firstChar = string[0];
533
- if (!/[a-zA-Z]/.test(firstChar)) return false;
534
-
535
- const secondChar = string[1];
536
- if (secondChar !== ":") return false;
537
-
538
- return true;
539
- };
540
-
541
- const fileSystemPathToUrl = (value) => {
542
- if (!isFileSystemPath(value)) {
543
- throw new Error(`value must be a filesystem path, got ${value}`);
544
- }
545
- return String(pathToFileURL(value));
546
- };
547
-
548
- const getCallerPosition = () => {
549
- const { prepareStackTrace } = Error;
550
- Error.prepareStackTrace = (error, stack) => {
551
- Error.prepareStackTrace = prepareStackTrace;
552
- return stack;
553
- };
554
- const { stack } = new Error();
555
- const callerCallsite = stack[2];
556
- const fileName = callerCallsite.getFileName();
557
- return {
558
- url:
559
- fileName && isFileSystemPath(fileName)
560
- ? fileSystemPathToUrl(fileName)
561
- : fileName,
562
- line: callerCallsite.getLineNumber(),
563
- column: callerCallsite.getColumnNumber(),
564
- };
565
- };
566
-
567
- const urlToFileSystemPath = (url) => {
568
- const urlObject = new URL(url);
569
- let { origin, pathname, hash } = urlObject;
570
- if (urlObject.protocol === "file:") {
571
- origin = "file://";
572
- }
573
- pathname = pathname
574
- .split("/")
575
- .map((part) => {
576
- return part.replace(/%(?![0-9A-F][0-9A-F])/g, "%25");
577
- })
578
- .join("/");
579
- if (hash) {
580
- pathname += `%23${encodeURIComponent(hash.slice(1))}`;
581
- }
582
- const urlString = `${origin}${pathname}`;
583
- const fileSystemPath = fileURLToPath(urlString);
584
- if (fileSystemPath[fileSystemPath.length - 1] === "/") {
585
- // remove trailing / so that nodejs path becomes predictable otherwise it logs
586
- // the trailing slash on linux but does not on windows
587
- return fileSystemPath.slice(0, -1);
588
- }
589
- return fileSystemPath;
590
- };
591
-
592
519
  /*
593
520
  * data:[<mediatype>][;base64],<data>
594
521
  * https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs#syntax
@@ -2012,35 +1939,33 @@ const urlToExtension$1 = (url) => {
2012
1939
  return pathnameToExtension$1(pathname);
2013
1940
  };
2014
1941
 
2015
- const urlToOrigin$1 = (url) => {
2016
- const urlString = String(url);
2017
- if (urlString.startsWith("file://")) {
2018
- return `file://`;
2019
- }
2020
- return new URL(urlString).origin;
2021
- };
2022
-
2023
- const setUrlExtension = (url, extension) => {
2024
- const origin = urlToOrigin$1(url);
2025
- const currentExtension = urlToExtension$1(url);
2026
- if (typeof extension === "function") {
2027
- extension = extension(currentExtension);
2028
- }
2029
- const resource = urlToResource(url);
2030
- const [pathname, search] = resource.split("?");
2031
- const pathnameWithoutExtension = currentExtension
2032
- ? pathname.slice(0, -currentExtension.length)
2033
- : pathname;
2034
- let newPathname;
2035
- if (pathnameWithoutExtension.endsWith("/")) {
2036
- newPathname = pathnameWithoutExtension.slice(0, -1);
2037
- newPathname += extension;
2038
- newPathname += "/";
2039
- } else {
2040
- newPathname = pathnameWithoutExtension;
2041
- newPathname += extension;
2042
- }
2043
- return `${origin}${newPathname}${search ? `?${search}` : ""}`;
1942
+ const setUrlExtension = (
1943
+ url,
1944
+ extension,
1945
+ { trailingSlash = "preserve" } = {},
1946
+ ) => {
1947
+ return transformUrlPathname(url, (pathname) => {
1948
+ const currentExtension = urlToExtension$1(url);
1949
+ if (typeof extension === "function") {
1950
+ extension = extension(currentExtension);
1951
+ }
1952
+ const pathnameWithoutExtension = currentExtension
1953
+ ? pathname.slice(0, -currentExtension.length)
1954
+ : pathname;
1955
+
1956
+ if (pathnameWithoutExtension.endsWith("/")) {
1957
+ let pathnameWithExtension;
1958
+ pathnameWithExtension = pathnameWithoutExtension.slice(0, -1);
1959
+ pathnameWithExtension += extension;
1960
+ if (trailingSlash === "preserve") {
1961
+ pathnameWithExtension += "/";
1962
+ }
1963
+ return pathnameWithExtension;
1964
+ }
1965
+ let pathnameWithExtension = pathnameWithoutExtension;
1966
+ pathnameWithExtension += extension;
1967
+ return pathnameWithExtension;
1968
+ });
2044
1969
  };
2045
1970
 
2046
1971
  const setUrlFilename = (url, filename) => {
@@ -2350,6 +2275,28 @@ const moveUrl = ({ url, from, to, preferRelative }) => {
2350
2275
  return absoluteUrl;
2351
2276
  };
2352
2277
 
2278
+ const isFileSystemPath = (value) => {
2279
+ if (typeof value !== "string") {
2280
+ throw new TypeError(
2281
+ `isFileSystemPath first arg must be a string, got ${value}`,
2282
+ );
2283
+ }
2284
+ if (value[0] === "/") {
2285
+ return true;
2286
+ }
2287
+ return startsWithWindowsDriveLetter(value);
2288
+ };
2289
+
2290
+ const startsWithWindowsDriveLetter = (string) => {
2291
+ const firstChar = string[0];
2292
+ if (!/[a-zA-Z]/.test(firstChar)) return false;
2293
+
2294
+ const secondChar = string[1];
2295
+ if (secondChar !== ":") return false;
2296
+
2297
+ return true;
2298
+ };
2299
+
2353
2300
  const resolveUrl$1 = (specifier, baseUrl) => {
2354
2301
  if (typeof baseUrl === "undefined") {
2355
2302
  throw new TypeError(`baseUrl missing to resolve ${specifier}`);
@@ -2357,7 +2304,7 @@ const resolveUrl$1 = (specifier, baseUrl) => {
2357
2304
  return String(new URL(specifier, baseUrl));
2358
2305
  };
2359
2306
 
2360
- const urlIsInsideOf = (url, otherUrl) => {
2307
+ const urlIsOrIsInsideOf = (url, otherUrl) => {
2361
2308
  const urlObject = new URL(url);
2362
2309
  const otherUrlObject = new URL(otherUrl);
2363
2310
 
@@ -2368,13 +2315,64 @@ const urlIsInsideOf = (url, otherUrl) => {
2368
2315
  const urlPathname = urlObject.pathname;
2369
2316
  const otherUrlPathname = otherUrlObject.pathname;
2370
2317
  if (urlPathname === otherUrlPathname) {
2371
- return false;
2318
+ return true;
2372
2319
  }
2373
2320
 
2374
2321
  const isInside = urlPathname.startsWith(otherUrlPathname);
2375
2322
  return isInside;
2376
2323
  };
2377
2324
 
2325
+ const fileSystemPathToUrl = (value) => {
2326
+ if (!isFileSystemPath(value)) {
2327
+ throw new Error(`value must be a filesystem path, got ${value}`);
2328
+ }
2329
+ return String(pathToFileURL(value));
2330
+ };
2331
+
2332
+ const getCallerPosition = () => {
2333
+ const { prepareStackTrace } = Error;
2334
+ Error.prepareStackTrace = (error, stack) => {
2335
+ Error.prepareStackTrace = prepareStackTrace;
2336
+ return stack;
2337
+ };
2338
+ const { stack } = new Error();
2339
+ const callerCallsite = stack[2];
2340
+ const fileName = callerCallsite.getFileName();
2341
+ return {
2342
+ url:
2343
+ fileName && isFileSystemPath(fileName)
2344
+ ? fileSystemPathToUrl(fileName)
2345
+ : fileName,
2346
+ line: callerCallsite.getLineNumber(),
2347
+ column: callerCallsite.getColumnNumber(),
2348
+ };
2349
+ };
2350
+
2351
+ const urlToFileSystemPath = (url) => {
2352
+ const urlObject = new URL(url);
2353
+ let { origin, pathname, hash } = urlObject;
2354
+ if (urlObject.protocol === "file:") {
2355
+ origin = "file://";
2356
+ }
2357
+ pathname = pathname
2358
+ .split("/")
2359
+ .map((part) => {
2360
+ return part.replace(/%(?![0-9A-F][0-9A-F])/g, "%25");
2361
+ })
2362
+ .join("/");
2363
+ if (hash) {
2364
+ pathname += `%23${encodeURIComponent(hash.slice(1))}`;
2365
+ }
2366
+ const urlString = `${origin}${pathname}`;
2367
+ const fileSystemPath = fileURLToPath(urlString);
2368
+ if (fileSystemPath[fileSystemPath.length - 1] === "/") {
2369
+ // remove trailing / so that nodejs path becomes predictable otherwise it logs
2370
+ // the trailing slash on linux but does not on windows
2371
+ return fileSystemPath.slice(0, -1);
2372
+ }
2373
+ return fileSystemPath;
2374
+ };
2375
+
2378
2376
  const validateDirectoryUrl = (value) => {
2379
2377
  let urlString;
2380
2378
 
@@ -10935,4 +10933,4 @@ const escapeRegexpSpecialChars = (string) => {
10935
10933
  });
10936
10934
  };
10937
10935
 
10938
- export { ANSI, Abort, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, UNICODE, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, browserDefaultRuntimeCompat, bufferToEtag, clearDirectorySync, compareFileUrls, comparePathnames, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createDynamicLog, createLogger, createLookupPackageDirectory, createTaskLog, defaultLookupPackageScope, defaultReadPackageJson, distributePercentages, ensureEmptyDirectory, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, escapeRegexpSpecialChars, generateContentFrame, getCallerPosition, getExtensionsToTry, humanizeDuration, humanizeFileSize, humanizeMemory, inferRuntimeCompatFromClosestPackage, injectQueryParamIntoSpecifierWithoutEncoding, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, nodeDefaultRuntimeCompat, normalizeImportMap, normalizeUrl, raceProcessTeardownEvents, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, renderBigSection, renderDetails, renderTable, renderUrlOrRelativeUrlFilename, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, startMonitoringCpuUsage, startMonitoringMemoryUsage, stringifyUrlSite, updateJsonFileSync, urlIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };
10936
+ export { ANSI, Abort, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, UNICODE, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, browserDefaultRuntimeCompat, bufferToEtag, clearDirectorySync, compareFileUrls, comparePathnames, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createDynamicLog, createLogger, createLookupPackageDirectory, createTaskLog, defaultLookupPackageScope, defaultReadPackageJson, distributePercentages, ensureEmptyDirectory, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, escapeRegexpSpecialChars, generateContentFrame, getCallerPosition, getExtensionsToTry, humanizeDuration, humanizeFileSize, humanizeMemory, inferRuntimeCompatFromClosestPackage, injectQueryParamIntoSpecifierWithoutEncoding, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, nodeDefaultRuntimeCompat, normalizeImportMap, normalizeUrl, raceProcessTeardownEvents, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, renderBigSection, renderDetails, renderTable, renderUrlOrRelativeUrlFilename, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, startMonitoringCpuUsage, startMonitoringMemoryUsage, stringifyUrlSite, updateJsonFileSync, urlIsOrIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };
@@ -4,7 +4,7 @@ const directoryIconUrl = new URL("../other/dir.png", import.meta.url).href;
4
4
  const fileIconUrl = new URL("../other/file.png", import.meta.url).href;
5
5
  const homeIconUrl = new URL("../other/home.svg#root", import.meta.url).href;
6
6
  let {
7
- navItems,
7
+ breadcrumb,
8
8
  mainFilePath,
9
9
  directoryContentItems,
10
10
  enoentDetails,
@@ -36,45 +36,60 @@ const DirectoryListing = () => {
36
36
  return directoryContentItems;
37
37
  });
38
38
  return u(k, {
39
- children: [enoentDetails ? u(ErrorMessage, {}) : null, u(Nav, {}), u(DirectoryContent, {
39
+ children: [enoentDetails ? u(ErrorMessage, {}) : null, u(Breadcrumb, {
40
+ items: breadcrumb
41
+ }), u(DirectoryContent, {
40
42
  items: directoryItems
41
43
  })]
42
44
  });
43
45
  };
44
46
  const ErrorMessage = () => {
45
47
  const {
46
- fileUrl,
47
48
  filePathExisting,
48
49
  filePathNotFound
49
50
  } = enoentDetails;
50
- return u("p", {
51
- className: "error_message",
51
+ let errorText;
52
+ let errorSuggestion;
53
+ errorText = u(k, {
54
+ children: [u("strong", {
55
+ children: "File not found:"
56
+ }), "\xA0", u("code", {
57
+ children: [u("span", {
58
+ className: "file_path_good",
59
+ children: filePathExisting
60
+ }), u("span", {
61
+ className: "file_path_bad",
62
+ children: filePathNotFound
63
+ })]
64
+ }), " ", "does not exist on the server."]
65
+ });
66
+ errorSuggestion = u(k, {
52
67
  children: [u("span", {
68
+ className: "icon",
69
+ children: "\uD83D\uDD0D"
70
+ }), " Check available routes in", " ", u("a", {
71
+ href: "/.internal/route_inspector",
72
+ children: "route inspector"
73
+ })]
74
+ });
75
+ return u("div", {
76
+ className: "error_message",
77
+ children: [u("p", {
53
78
  className: "error_text",
54
- children: ["No filesystem entry at", " ", u("code", {
55
- title: fileUrl,
56
- children: [u("span", {
57
- className: "file_path_good",
58
- children: filePathExisting
59
- }), u("span", {
60
- className: "file_path_bad",
61
- children: filePathNotFound
62
- })]
63
- }), "."]
64
- }), u("br", {}), u("span", {
65
- className: "error_text",
66
- style: "font-size: 70%;",
67
- children: ["See also available routes in the", " ", u("a", {
68
- href: "/.internal/route_inspector",
69
- children: "route inspector"
70
- }), "."]
79
+ children: errorText
80
+ }), u("p", {
81
+ className: "error_suggestion",
82
+ style: "font-size: 0.8em; margin-top: 10px;",
83
+ children: errorSuggestion
71
84
  })]
72
85
  });
73
86
  };
74
- const Nav = () => {
87
+ const Breadcrumb = ({
88
+ items
89
+ }) => {
75
90
  return u("h1", {
76
91
  className: "nav",
77
- children: navItems.map(navItem => {
92
+ children: items.map(navItem => {
78
93
  const {
79
94
  url,
80
95
  urlRelativeToServer,
@@ -84,7 +99,7 @@ const Nav = () => {
84
99
  } = navItem;
85
100
  const isDirectory = new URL(url).pathname.endsWith("/");
86
101
  return u(k, {
87
- children: [u(NavItem, {
102
+ children: [u(BreadcrumbItem, {
88
103
  url: urlRelativeToServer,
89
104
  isCurrent: isCurrent,
90
105
  iconImageUrl: isServerRootDirectory ? homeIconUrl : "",
@@ -98,7 +113,7 @@ const Nav = () => {
98
113
  })
99
114
  });
100
115
  };
101
- const NavItem = ({
116
+ const BreadcrumbItem = ({
102
117
  url,
103
118
  iconImageUrl,
104
119
  iconLinkUrl,
@@ -1,43 +1,46 @@
1
1
  const injectRibbon = ({ text }) => {
2
2
  const css = /* css */ `
3
- #jsenv_ribbon_container {
4
- position: fixed;
5
- z-index: 1001;
6
- top: 0;
7
- right: 0;
8
- width: 100px;
9
- height: 100px;
10
- overflow: hidden;
11
- opacity: 0.5;
12
- pointer-events: none;
13
- }
14
- #jsenv_ribbon {
15
- position: absolute;
16
- top: -10px;
17
- right: -10px;
18
- width: 100%;
19
- height: 100%;
20
- }
21
- #jsenv_ribbon_text {
22
- position: absolute;
23
- left: 0px;
24
- top: 20px;
25
- transform: rotate(45deg);
26
- display: block;
27
- width: 125px;
28
- line-height: 36px;
29
- background-color: orange;
30
- color: rgb(55, 7, 7);
31
- box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
32
- font-weight: 700;
33
- font-size: 16px;
34
- font-family: "Lato", sans-serif;
35
- text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
36
- text-align: center;
37
- user-select: none;
38
- }`;
3
+ #jsenv_ribbon_container {
4
+ position: fixed;
5
+ z-index: 1001;
6
+ top: 0;
7
+ right: 0;
8
+ width: 100px;
9
+ height: 100px;
10
+ overflow: hidden;
11
+ opacity: 0.5;
12
+ pointer-events: none;
13
+ }
14
+ #jsenv_ribbon {
15
+ position: absolute;
16
+ top: -10px;
17
+ right: -10px;
18
+ width: 100%;
19
+ height: 100%;
20
+ }
21
+ #jsenv_ribbon_text {
22
+ position: absolute;
23
+ left: 0px;
24
+ top: 20px;
25
+ transform: rotate(45deg);
26
+ display: block;
27
+ width: 125px;
28
+ line-height: 36px;
29
+ background-color: orange;
30
+ color: rgb(55, 7, 7);
31
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
32
+ font-weight: 700;
33
+ font-size: 16px;
34
+ font-family: "Lato", sans-serif;
35
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
36
+ text-align: center;
37
+ user-select: none;
38
+ }
39
+ `;
39
40
  const html = /* html */ `<div id="jsenv_ribbon_container">
40
- <style>${css}</style>
41
+ <style>
42
+ ${css}
43
+ </style>
41
44
  <div id="jsenv_ribbon">
42
45
  <div id="jsenv_ribbon_text">${text}</div>
43
46
  </div>
@@ -1,7 +1,11 @@
1
+ import "@jsenv/ast";
1
2
  import "@jsenv/sourcemap";
2
3
 
3
4
  const injectionSymbol = Symbol.for("jsenv_injection");
4
5
  const INJECTIONS = {
6
+ global: (value) => {
7
+ return { [injectionSymbol]: "global", value };
8
+ },
5
9
  optional: (value) => {
6
10
  return { [injectionSymbol]: "optional", value };
7
11
  },
@@ -511,35 +511,6 @@ const SIGINT_CALLBACK = {
511
511
  },
512
512
  };
513
513
 
514
- const isFileSystemPath = (value) => {
515
- if (typeof value !== "string") {
516
- throw new TypeError(
517
- `isFileSystemPath first arg must be a string, got ${value}`,
518
- );
519
- }
520
- if (value[0] === "/") {
521
- return true;
522
- }
523
- return startsWithWindowsDriveLetter(value);
524
- };
525
-
526
- const startsWithWindowsDriveLetter = (string) => {
527
- const firstChar = string[0];
528
- if (!/[a-zA-Z]/.test(firstChar)) return false;
529
-
530
- const secondChar = string[1];
531
- if (secondChar !== ":") return false;
532
-
533
- return true;
534
- };
535
-
536
- const fileSystemPathToUrl = (value) => {
537
- if (!isFileSystemPath(value)) {
538
- throw new Error(`value must be a filesystem path, got ${value}`);
539
- }
540
- return String(pathToFileURL(value));
541
- };
542
-
543
514
  // https://github.com/Marak/colors.js/blob/master/lib/styles.js
544
515
  // https://stackoverflow.com/a/75985833/2634179
545
516
  const RESET = "\x1b[0m";
@@ -1491,6 +1462,35 @@ const ensurePathnameTrailingSlash = (url) => {
1491
1462
  });
1492
1463
  };
1493
1464
 
1465
+ const isFileSystemPath = (value) => {
1466
+ if (typeof value !== "string") {
1467
+ throw new TypeError(
1468
+ `isFileSystemPath first arg must be a string, got ${value}`,
1469
+ );
1470
+ }
1471
+ if (value[0] === "/") {
1472
+ return true;
1473
+ }
1474
+ return startsWithWindowsDriveLetter(value);
1475
+ };
1476
+
1477
+ const startsWithWindowsDriveLetter = (string) => {
1478
+ const firstChar = string[0];
1479
+ if (!/[a-zA-Z]/.test(firstChar)) return false;
1480
+
1481
+ const secondChar = string[1];
1482
+ if (secondChar !== ":") return false;
1483
+
1484
+ return true;
1485
+ };
1486
+
1487
+ const fileSystemPathToUrl = (value) => {
1488
+ if (!isFileSystemPath(value)) {
1489
+ throw new Error(`value must be a filesystem path, got ${value}`);
1490
+ }
1491
+ return String(pathToFileURL(value));
1492
+ };
1493
+
1494
1494
  const validateDirectoryUrl = (value) => {
1495
1495
  let urlString;
1496
1496