@jsenv/core 40.7.0 → 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.
@@ -15,7 +15,7 @@ import {
15
15
  } from "@jsenv/server";
16
16
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/internal/convertFileSystemErrorToResponseProperties.js";
17
17
  import { URL_META } from "@jsenv/url-meta";
18
- import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
18
+ import { urlIsOrIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
19
19
  import { existsSync, readFileSync } from "node:fs";
20
20
  import { defaultRuntimeCompat } from "../build/build_params.js";
21
21
  import { createEventEmitter } from "../helpers/event_emitter.js";
@@ -131,7 +131,7 @@ export const startDevServer = async ({
131
131
  if (
132
132
  process.env.CAPTURING_SIDE_EFFECTS ||
133
133
  (!import.meta.build &&
134
- urlIsInsideOf(sourceDirectoryUrl, jsenvCoreDirectoryUrl))
134
+ urlIsOrIsInsideOf(sourceDirectoryUrl, jsenvCoreDirectoryUrl))
135
135
  ) {
136
136
  outDirectoryUrl = new URL("../.jsenv/", sourceDirectoryUrl);
137
137
  } else {
@@ -248,7 +248,7 @@ export const startDevServer = async ({
248
248
  read: readPackageAtOrNull,
249
249
  };
250
250
 
251
- const devServerPluginStore = createPluginStore([
251
+ const devServerPluginStore = await createPluginStore([
252
252
  jsenvPluginServerEvents({ clientAutoreload }),
253
253
  ...plugins,
254
254
  ...getCorePlugins({
@@ -274,7 +274,7 @@ export const startDevServer = async ({
274
274
  ribbon,
275
275
  }),
276
276
  ]);
277
- const getOrCreateKitchen = (request) => {
277
+ const getOrCreateKitchen = async (request) => {
278
278
  const { runtimeName, runtimeVersion } = parseUserAgentHeader(
279
279
  request.headers["user-agent"] || "",
280
280
  );
@@ -397,7 +397,7 @@ export const startDevServer = async ({
397
397
  );
398
398
  },
399
399
  );
400
- const devServerPluginController = createPluginController(
400
+ const devServerPluginController = await createPluginController(
401
401
  devServerPluginStore,
402
402
  kitchen,
403
403
  );
@@ -413,8 +413,8 @@ export const startDevServer = async ({
413
413
 
414
414
  finalServices.push({
415
415
  name: "jsenv:dev_server_routes",
416
- augmentRouteFetchSecondArg: (request) => {
417
- const kitchen = getOrCreateKitchen(request);
416
+ augmentRouteFetchSecondArg: async (request) => {
417
+ const kitchen = await getOrCreateKitchen(request);
418
418
  return { kitchen };
419
419
  },
420
420
  routes: [
@@ -617,6 +617,7 @@ export const startDevServer = async ({
617
617
  },
618
618
  ],
619
619
  });
620
+ finalServices.push(...devServerPluginStore.allDevServerServices);
620
621
  }
621
622
  // jsenv error handler service
622
623
  {
@@ -1,9 +1,9 @@
1
1
  import { ensureWindowsDriveLetter } from "@jsenv/filesystem";
2
- import { moveUrl, urlIsInsideOf } from "@jsenv/urls";
2
+ import { moveUrl, urlIsOrIsInsideOf } from "@jsenv/urls";
3
3
 
4
4
  export const WEB_URL_CONVERTER = {
5
5
  asWebUrl: (fileUrl, webServer) => {
6
- if (urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
6
+ if (urlIsOrIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
7
7
  return moveUrl({
8
8
  url: fileUrl,
9
9
  from: webServer.rootDirectoryUrl,
@@ -176,7 +176,7 @@ ${error.message}`,
176
176
  name: "TRANSFORM_URL_CONTENT_ERROR",
177
177
  code: "PARSE_ERROR",
178
178
  reason: error.message,
179
- stack: error.stack,
179
+ stack: transformError.stack,
180
180
  trace,
181
181
  asResponse: error.asResponse,
182
182
  });
@@ -1,6 +1,6 @@
1
1
  import { ensureWindowsDriveLetter } from "@jsenv/filesystem";
2
2
  import { generateSourcemapFileUrl } from "@jsenv/sourcemap";
3
- import { moveUrl, setUrlFilename, urlIsInsideOf } from "@jsenv/urls";
3
+ import { moveUrl, setUrlFilename, urlIsOrIsInsideOf } from "@jsenv/urls";
4
4
 
5
5
  export const determineFileUrlForOutDirectory = (urlInfo) => {
6
6
  let { url, filenameHint } = urlInfo;
@@ -11,7 +11,7 @@ export const determineFileUrlForOutDirectory = (urlInfo) => {
11
11
  if (!url.startsWith("file:")) {
12
12
  return url;
13
13
  }
14
- if (!urlIsInsideOf(url, rootDirectoryUrl)) {
14
+ if (!urlIsOrIsInsideOf(url, rootDirectoryUrl)) {
15
15
  const fsRootUrl = ensureWindowsDriveLetter("file:///", url);
16
16
  url = `${rootDirectoryUrl}@fs/${url.slice(fsRootUrl.length)}`;
17
17
  }
@@ -1,4 +1,4 @@
1
- import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
1
+ import { urlIsOrIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
2
2
 
3
3
  export const jsenvPluginAutoreloadServer = ({
4
4
  clientFileChangeEventEmitter,
@@ -10,7 +10,7 @@ export const jsenvPluginAutoreloadServer = ({
10
10
  serverEvents: {
11
11
  reload: (serverEventInfo) => {
12
12
  const formatUrlForClient = (url) => {
13
- if (urlIsInsideOf(url, serverEventInfo.rootDirectoryUrl)) {
13
+ if (urlIsOrIsInsideOf(url, serverEventInfo.rootDirectoryUrl)) {
14
14
  return urlToRelativeUrl(url, serverEventInfo.rootDirectoryUrl);
15
15
  }
16
16
  if (url.startsWith("file:")) {
@@ -28,6 +28,7 @@ export const jsenvPluginChromeDevtoolsJson = () => {
28
28
  devServerRoutes: [
29
29
  {
30
30
  endpoint: "GET /.well-known/appspecific/com.chrome.devtools.json",
31
+ declarationSource: import.meta.url,
31
32
  fetch: (request, { kitchen }) => {
32
33
  const { rootDirectoryUrl } = kitchen.context;
33
34
  return Response.json({
@@ -1,10 +1,19 @@
1
1
  import { performance } from "node:perf_hooks";
2
2
  import { jsenvPluginHtmlSyntaxErrorFallback } from "./html_syntax_error_fallback/jsenv_plugin_html_syntax_error_fallback.js";
3
3
 
4
- export const createPluginStore = (plugins) => {
4
+ export const createPluginStore = async (plugins) => {
5
5
  const allDevServerRoutes = [];
6
+ const allDevServerServices = [];
6
7
  const pluginArray = [];
7
- const addPlugin = (plugin) => {
8
+
9
+ const pluginPromises = [];
10
+ const addPlugin = async (plugin) => {
11
+ if (plugin && typeof plugin.then === "function") {
12
+ pluginPromises.push(plugin);
13
+ const value = await plugin;
14
+ addPlugin(value);
15
+ return;
16
+ }
8
17
  if (Array.isArray(plugin)) {
9
18
  for (const subplugin of plugin) {
10
19
  addPlugin(subplugin);
@@ -23,21 +32,28 @@ export const createPluginStore = (plugins) => {
23
32
  allDevServerRoutes.push(devServerRoute);
24
33
  }
25
34
  }
35
+ if (plugin.devServerServices) {
36
+ const devServerServices = plugin.devServerServices;
37
+ for (const devServerService of devServerServices) {
38
+ allDevServerServices.push(devServerService);
39
+ }
40
+ }
26
41
  pluginArray.push(plugin);
27
42
  };
28
43
  addPlugin(jsenvPluginHtmlSyntaxErrorFallback());
29
44
  for (const plugin of plugins) {
30
45
  addPlugin(plugin);
31
46
  }
47
+ await Promise.all(pluginPromises);
32
48
 
33
49
  return {
34
50
  pluginArray,
35
-
36
51
  allDevServerRoutes,
52
+ allDevServerServices,
37
53
  };
38
54
  };
39
55
 
40
- export const createPluginController = (
56
+ export const createPluginController = async (
41
57
  pluginStore,
42
58
  kitchen,
43
59
  { initialPuginsMeta = {} } = {},
@@ -60,7 +76,7 @@ export const createPluginController = (
60
76
  pluginCandidate.destroy?.();
61
77
  continue;
62
78
  }
63
- const initPluginResult = initPlugin(pluginCandidate, kitchen);
79
+ const initPluginResult = await initPlugin(pluginCandidate, kitchen);
64
80
  if (!initPluginResult) {
65
81
  pluginCandidate.destroy?.();
66
82
  continue;
@@ -112,6 +128,7 @@ export const createPluginController = (
112
128
  key === "serverEvents" ||
113
129
  key === "mustStayFirst" ||
114
130
  key === "devServerRoutes" ||
131
+ key === "devServerServices" ||
115
132
  key === "effect"
116
133
  ) {
117
134
  continue;
@@ -285,6 +302,7 @@ export const createPluginController = (
285
302
  const HOOK_NAMES = [
286
303
  "init",
287
304
  "devServerRoutes", // is called only during dev/tests
305
+ "devServerServices", // is called only during dev/tests
288
306
  "resolveReference",
289
307
  "redirectReference",
290
308
  "transformReferenceSearchParams",
@@ -339,12 +357,12 @@ const testAppliesDuring = (plugin, kitchen) => {
339
357
  `"appliesDuring" must be an object or a string, got ${appliesDuring}`,
340
358
  );
341
359
  };
342
- const initPlugin = (plugin, kitchen) => {
360
+ const initPlugin = async (plugin, kitchen) => {
343
361
  const { init } = plugin;
344
362
  if (!init) {
345
363
  return true;
346
364
  }
347
- const initReturnValue = init(kitchen.context, { plugin });
365
+ const initReturnValue = await init(kitchen.context, { plugin });
348
366
  if (initReturnValue === false) {
349
367
  return false;
350
368
  }
@@ -6,7 +6,7 @@ const fileIconUrl = import.meta.resolve("./assets/file.png");
6
6
  const homeIconUrl = import.meta.resolve("./assets/home.svg#root");
7
7
 
8
8
  let {
9
- navItems,
9
+ breadcrumb,
10
10
  mainFilePath,
11
11
  directoryContentItems,
12
12
  enoentDetails,
@@ -49,38 +49,51 @@ const DirectoryListing = () => {
49
49
  return (
50
50
  <>
51
51
  {enoentDetails ? <ErrorMessage /> : null}
52
- <Nav />
52
+ <Breadcrumb items={breadcrumb} />
53
53
  <DirectoryContent items={directoryItems} />
54
54
  </>
55
55
  );
56
56
  };
57
57
 
58
58
  const ErrorMessage = () => {
59
- const { fileUrl, filePathExisting, filePathNotFound } = enoentDetails;
59
+ const { filePathExisting, filePathNotFound } = enoentDetails;
60
+
61
+ let errorText;
62
+ let errorSuggestion;
63
+ errorText = (
64
+ <>
65
+ <strong>File not found:</strong>&nbsp;
66
+ <code>
67
+ <span className="file_path_good">{filePathExisting}</span>
68
+ <span className="file_path_bad">{filePathNotFound}</span>
69
+ </code>{" "}
70
+ does not exist on the server.
71
+ </>
72
+ );
73
+ errorSuggestion = (
74
+ <>
75
+ <span className="icon">🔍</span> Check available routes in{" "}
76
+ <a href="/.internal/route_inspector">route inspector</a>
77
+ </>
78
+ );
60
79
 
61
80
  return (
62
- <p className="error_message">
63
- <span className="error_text">
64
- No filesystem entry at{" "}
65
- <code title={fileUrl}>
66
- <span className="file_path_good">{filePathExisting}</span>
67
- <span className="file_path_bad">{filePathNotFound}</span>
68
- </code>
69
- .
70
- </span>
71
- <br />
72
- <span className="error_text" style="font-size: 70%;">
73
- See also available routes in the{" "}
74
- <a href="/.internal/route_inspector">route inspector</a>.
75
- </span>
76
- </p>
81
+ <div className="error_message">
82
+ <p className="error_text">{errorText}</p>
83
+ <p
84
+ className="error_suggestion"
85
+ style="font-size: 0.8em; margin-top: 10px;"
86
+ >
87
+ {errorSuggestion}
88
+ </p>
89
+ </div>
77
90
  );
78
91
  };
79
92
 
80
- const Nav = () => {
93
+ const Breadcrumb = ({ items }) => {
81
94
  return (
82
95
  <h1 className="nav">
83
- {navItems.map((navItem) => {
96
+ {items.map((navItem) => {
84
97
  const {
85
98
  url,
86
99
  urlRelativeToServer,
@@ -91,7 +104,7 @@ const Nav = () => {
91
104
  const isDirectory = new URL(url).pathname.endsWith("/");
92
105
  return (
93
106
  <>
94
- <NavItem
107
+ <BreadcrumbItem
95
108
  key={url}
96
109
  url={urlRelativeToServer}
97
110
  isCurrent={isCurrent}
@@ -99,7 +112,7 @@ const Nav = () => {
99
112
  iconLinkUrl={isServerRootDirectory ? `/${mainFilePath}` : ""}
100
113
  >
101
114
  {name}
102
- </NavItem>
115
+ </BreadcrumbItem>
103
116
  {isDirectory ? (
104
117
  <span className="directory_separator">/</span>
105
118
  ) : null}
@@ -109,7 +122,13 @@ const Nav = () => {
109
122
  </h1>
110
123
  );
111
124
  };
112
- const NavItem = ({ url, iconImageUrl, iconLinkUrl, isCurrent, children }) => {
125
+ const BreadcrumbItem = ({
126
+ url,
127
+ iconImageUrl,
128
+ iconLinkUrl,
129
+ isCurrent,
130
+ children,
131
+ }) => {
113
132
  return (
114
133
  <span className="nav_item" data-current={isCurrent ? "" : undefined}>
115
134
  {iconLinkUrl ? (
@@ -1,11 +1,8 @@
1
- import { urlIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
1
+ import { urlIsOrIsInsideOf, urlToRelativeUrl } from "@jsenv/urls";
2
2
 
3
3
  export const FILE_AND_SERVER_URLS_CONVERTER = {
4
4
  asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
5
- if (fileUrl === serverRootDirectoryUrl) {
6
- return "/";
7
- }
8
- if (urlIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
5
+ if (urlIsOrIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
9
6
  const urlRelativeToServer = urlToRelativeUrl(
10
7
  fileUrl,
11
8
  serverRootDirectoryUrl,
@@ -31,7 +31,7 @@ import { pickContentType, WebSocketResponse } from "@jsenv/server";
31
31
  import {
32
32
  asUrlWithoutSearch,
33
33
  ensurePathnameTrailingSlash,
34
- urlIsInsideOf,
34
+ urlIsOrIsInsideOf,
35
35
  urlToFilename,
36
36
  urlToRelativeUrl,
37
37
  } from "@jsenv/urls";
@@ -44,6 +44,7 @@ const htmlFileUrlForDirectory = import.meta.resolve(
44
44
  );
45
45
 
46
46
  export const jsenvPluginDirectoryListing = ({
47
+ spa,
47
48
  urlMocks = false,
48
49
  autoreload = true,
49
50
  directoryContentMagicName,
@@ -85,7 +86,7 @@ export const jsenvPluginDirectoryListing = ({
85
86
  return null;
86
87
  }
87
88
  }
88
- return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(url)}&enoent`;
89
+ return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(requestedUrl)}&enoent`;
89
90
  }
90
91
  const isDirectory = fsStat?.isDirectory();
91
92
  if (!isDirectory) {
@@ -111,21 +112,22 @@ export const jsenvPluginDirectoryListing = ({
111
112
  if (urlWithoutSearch !== String(htmlFileUrlForDirectory)) {
112
113
  return null;
113
114
  }
114
- const requestedUrl = urlInfo.searchParams.get("url");
115
- if (!requestedUrl) {
115
+ const urlNotFound = urlInfo.searchParams.get("url");
116
+ if (!urlNotFound) {
116
117
  return null;
117
118
  }
119
+
118
120
  urlInfo.headers["cache-control"] = "no-cache";
119
121
  const enoent = urlInfo.searchParams.has("enoent");
120
122
  if (enoent) {
121
123
  urlInfo.status = 404;
122
- urlInfo.headers["cache-control"] = "no-cache";
123
124
  }
124
125
  const request = urlInfo.context.request;
125
126
  const { rootDirectoryUrl, mainFilePath } = urlInfo.context;
126
127
  const directoryListingInjections = generateDirectoryListingInjection(
127
- requestedUrl,
128
+ urlNotFound,
128
129
  {
130
+ spa,
129
131
  autoreload,
130
132
  request,
131
133
  urlMocks,
@@ -219,8 +221,9 @@ export const jsenvPluginDirectoryListing = ({
219
221
  };
220
222
 
221
223
  const generateDirectoryListingInjection = (
222
- requestedUrl,
224
+ urlNotFound,
223
225
  {
226
+ spa,
224
227
  rootDirectoryUrl,
225
228
  mainFilePath,
226
229
  packageDirectory,
@@ -233,13 +236,13 @@ const generateDirectoryListingInjection = (
233
236
  ) => {
234
237
  let serverRootDirectoryUrl = rootDirectoryUrl;
235
238
  const firstExistingDirectoryUrl = getFirstExistingDirectoryUrl(
236
- requestedUrl,
239
+ urlNotFound,
237
240
  serverRootDirectoryUrl,
238
241
  );
239
242
  const directoryContentItems = getDirectoryContentItems({
240
243
  serverRootDirectoryUrl,
241
244
  mainFilePath,
242
- requestedUrl,
245
+ requestedUrl: urlNotFound,
243
246
  firstExistingDirectoryUrl,
244
247
  });
245
248
  package_workspaces: {
@@ -285,8 +288,8 @@ const generateDirectoryListingInjection = (
285
288
  const { host } = new URL(request.url);
286
289
  const websocketUrl = `${websocketScheme}://${host}/.internal/directory_content.websocket?directory=${encodeURIComponent(directoryUrlRelativeToServer)}`;
287
290
 
288
- const navItems = [];
289
- nav_items: {
291
+ const generateBreadcrumb = () => {
292
+ const breadcrumb = [];
290
293
  const lastItemUrl = firstExistingDirectoryUrl;
291
294
  const lastItemRelativeUrl = urlToRelativeUrl(lastItemUrl, rootDirectoryUrl);
292
295
  const rootDirectoryUrlName = urlToFilename(rootDirectoryUrl);
@@ -296,7 +299,6 @@ const generateDirectoryListingInjection = (
296
299
  } else {
297
300
  parts = [rootDirectoryUrlName];
298
301
  }
299
-
300
302
  let i = 0;
301
303
  while (i < parts.length) {
302
304
  const part = parts[i];
@@ -317,7 +319,7 @@ const generateDirectoryListingInjection = (
317
319
  navItemUrl,
318
320
  serverRootDirectoryUrl,
319
321
  );
320
- let urlRelativeToDocument = urlToRelativeUrl(navItemUrl, requestedUrl);
322
+ let urlRelativeToDocument = urlToRelativeUrl(navItemUrl, urlNotFound);
321
323
  const isServerRootDirectory = navItemUrl === serverRootDirectoryUrl;
322
324
  if (isServerRootDirectory) {
323
325
  urlRelativeToServer = `/${directoryContentMagicName}`;
@@ -325,7 +327,7 @@ const generateDirectoryListingInjection = (
325
327
  }
326
328
  const name = part;
327
329
  const isCurrent = navItemUrl === String(firstExistingDirectoryUrl);
328
- navItems.push({
330
+ breadcrumb.push({
329
331
  url: navItemUrl,
330
332
  urlRelativeToServer,
331
333
  urlRelativeToDocument,
@@ -335,34 +337,47 @@ const generateDirectoryListingInjection = (
335
337
  });
336
338
  i++;
337
339
  }
338
- }
340
+ return breadcrumb;
341
+ };
342
+ const breadcrumb = generateBreadcrumb(urlNotFound);
339
343
 
340
344
  let enoentDetails = null;
341
345
  if (enoent) {
346
+ const buildEnoentPathInfo = (urlBase, closestExistingUrl) => {
347
+ let filePathExisting;
348
+ let filePathNotFound;
349
+ const existingIndex = String(closestExistingUrl).length;
350
+ filePathExisting = urlToRelativeUrl(
351
+ closestExistingUrl,
352
+ serverRootDirectoryUrl,
353
+ );
354
+ filePathNotFound = urlBase.slice(existingIndex);
355
+ return [filePathExisting, filePathNotFound];
356
+ };
342
357
  const fileRelativeUrl = urlToRelativeUrl(
343
- requestedUrl,
344
- serverRootDirectoryUrl,
345
- );
346
- let filePathExisting;
347
- let filePathNotFound;
348
- const existingIndex = String(firstExistingDirectoryUrl).length;
349
- filePathExisting = urlToRelativeUrl(
350
- firstExistingDirectoryUrl,
358
+ urlNotFound,
351
359
  serverRootDirectoryUrl,
352
360
  );
353
- filePathNotFound = requestedUrl.slice(existingIndex);
354
361
  enoentDetails = {
355
- fileUrl: requestedUrl,
362
+ fileUrl: urlNotFound,
356
363
  fileRelativeUrl,
364
+ };
365
+
366
+ const [filePathExisting, filePathNotFound] = buildEnoentPathInfo(
367
+ urlNotFound,
368
+ firstExistingDirectoryUrl,
369
+ );
370
+ Object.assign(enoentDetails, {
357
371
  filePathExisting: `/${filePathExisting}`,
358
372
  filePathNotFound,
359
- };
373
+ });
360
374
  }
361
375
 
362
376
  return {
363
377
  __DIRECTORY_LISTING__: {
378
+ spa,
364
379
  enoentDetails,
365
- navItems,
380
+ breadcrumb,
366
381
  urlMocks,
367
382
  directoryContentMagicName,
368
383
  directoryUrl: firstExistingDirectoryUrl,
@@ -375,11 +390,11 @@ const generateDirectoryListingInjection = (
375
390
  },
376
391
  };
377
392
  };
378
- const getFirstExistingDirectoryUrl = (requestedUrl, serverRootDirectoryUrl) => {
379
- let directoryUrlCandidate = new URL("./", requestedUrl);
393
+ const getFirstExistingDirectoryUrl = (urlBase, serverRootDirectoryUrl) => {
394
+ let directoryUrlCandidate = new URL("./", urlBase);
380
395
  while (!existsSync(directoryUrlCandidate)) {
381
396
  directoryUrlCandidate = new URL("../", directoryUrlCandidate);
382
- if (!urlIsInsideOf(directoryUrlCandidate, serverRootDirectoryUrl)) {
397
+ if (!urlIsOrIsInsideOf(directoryUrlCandidate, serverRootDirectoryUrl)) {
383
398
  directoryUrlCandidate = new URL(serverRootDirectoryUrl);
384
399
  break;
385
400
  }
@@ -4,7 +4,7 @@ import {
4
4
  getExtensionsToTry,
5
5
  } from "@jsenv/node-esm-resolution";
6
6
  import {
7
- urlIsInsideOf,
7
+ urlIsOrIsInsideOf,
8
8
  urlToExtension,
9
9
  urlToFilename,
10
10
  urlToPathname,
@@ -13,7 +13,7 @@ import { existsSync, realpathSync } from "node:fs";
13
13
  import { pathToFileURL } from "node:url";
14
14
 
15
15
  export const jsenvPluginFsRedirection = ({
16
- spa = true,
16
+ spa,
17
17
  directoryContentMagicName,
18
18
  magicExtensions = ["inherit", ".js"],
19
19
  magicDirectoryIndex = true,
@@ -172,17 +172,12 @@ const getClosestHtmlRootFile = (requestedUrl, serverRootDirectoryUrl) => {
172
172
  if (existsSync(indexHtmlFileUrl)) {
173
173
  return indexHtmlFileUrl.href;
174
174
  }
175
- const htmlFileUrlCandidate = new URL(
176
- `${urlToFilename(directoryUrl)}.html`,
177
- directoryUrl,
178
- );
175
+ const filename = urlToFilename(directoryUrl);
176
+ const htmlFileUrlCandidate = new URL(`${filename}.html`, directoryUrl);
179
177
  if (existsSync(htmlFileUrlCandidate)) {
180
178
  return htmlFileUrlCandidate.href;
181
179
  }
182
- if (
183
- !urlIsInsideOf(directoryUrl, serverRootDirectoryUrl) ||
184
- directoryUrl.href === serverRootDirectoryUrl
185
- ) {
180
+ if (!urlIsOrIsInsideOf(directoryUrl, serverRootDirectoryUrl)) {
186
181
  return null;
187
182
  }
188
183
  directoryUrl = new URL("../", directoryUrl);
@@ -9,7 +9,7 @@ import { jsenvPluginFsRedirection } from "./jsenv_plugin_fs_redirection.js";
9
9
  const directoryContentMagicName = "...";
10
10
 
11
11
  export const jsenvPluginProtocolFile = ({
12
- spa,
12
+ spa = true,
13
13
  magicExtensions,
14
14
  magicDirectoryIndex,
15
15
  preserveSymlinks,
@@ -77,6 +77,7 @@ export const jsenvPluginProtocolFile = ({
77
77
  ...(directoryListing
78
78
  ? [
79
79
  jsenvPluginDirectoryListing({
80
+ spa,
80
81
  ...directoryListing,
81
82
  directoryContentMagicName,
82
83
  rootDirectoryUrl,
@@ -1,43 +1,46 @@
1
1
  export 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>