@jsenv/core 40.6.1 → 40.7.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "40.6.1",
3
+ "version": "40.7.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -39,7 +39,6 @@
39
39
  },
40
40
  "packageManager": "npm@11.3.0",
41
41
  "workspaces": [
42
- "./packages/back_and_front/*",
43
42
  "./packages/backend/*",
44
43
  "./packages/frontend/*",
45
44
  "./packages/internal/*",
@@ -82,12 +81,12 @@
82
81
  },
83
82
  "dependencies": {
84
83
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
85
- "@jsenv/ast": "6.7.2",
86
- "@jsenv/js-module-fallback": "1.4.12",
84
+ "@jsenv/ast": "6.7.3",
85
+ "@jsenv/js-module-fallback": "1.4.14",
87
86
  "@jsenv/plugin-bundling": "2.9.7",
88
87
  "@jsenv/plugin-minification": "1.7.0",
89
- "@jsenv/plugin-supervisor": "1.7.0",
90
- "@jsenv/plugin-transpilation": "1.5.19",
88
+ "@jsenv/plugin-supervisor": "1.7.2",
89
+ "@jsenv/plugin-transpilation": "1.5.21",
91
90
  "@jsenv/server": "16.1.2",
92
91
  "@jsenv/sourcemap": "1.3.8"
93
92
  },
@@ -120,13 +119,13 @@
120
119
  "@jsenv/url-meta": "workspace:*",
121
120
  "@jsenv/urls": "workspace:*",
122
121
  "@jsenv/utils": "workspace:*",
123
- "@playwright/browser-chromium": "1.51.1",
124
- "@playwright/browser-firefox": "1.51.1",
125
- "@playwright/browser-webkit": "1.51.1",
122
+ "@playwright/browser-chromium": "1.52.0",
123
+ "@playwright/browser-firefox": "1.52.0",
124
+ "@playwright/browser-webkit": "1.52.0",
126
125
  "babel-plugin-transform-async-to-promises": "0.8.18",
127
- "eslint": "9.24.0",
126
+ "eslint": "9.25.1",
128
127
  "open": "10.1.1",
129
- "playwright": "1.51.1",
128
+ "playwright": "1.52.0",
130
129
  "preact": "10.26.5",
131
130
  "prettier": "3.5.3",
132
131
  "prettier-plugin-organize-imports": "4.1.0",
@@ -96,6 +96,7 @@ export const startDevServer = async ({
96
96
  ribbon = true,
97
97
  // toolbar = false,
98
98
  onKitchenCreated = () => {},
99
+ spa,
99
100
 
100
101
  sourcemaps = "inline",
101
102
  sourcemapsSourcesContent,
@@ -265,6 +266,7 @@ export const startDevServer = async ({
265
266
  supervisor,
266
267
  injections,
267
268
  transpilation,
269
+ spa,
268
270
 
269
271
  clientAutoreload,
270
272
  clientAutoreloadOnServerRestart,
@@ -643,7 +645,7 @@ export const startDevServer = async ({
643
645
  body: response.body,
644
646
  });
645
647
  return {
646
- status: 200,
648
+ status: response.status,
647
649
  headers: {
648
650
  "content-type": "application/json",
649
651
  "content-length": Buffer.byteLength(body),
@@ -17,6 +17,7 @@ import {
17
17
  determineSourcemapFileUrl,
18
18
  } from "./out_directory_url.js";
19
19
  import { createUrlGraph } from "./url_graph/url_graph.js";
20
+ import { isPlaceholderInjection } from "./url_graph/url_info_injections.js";
20
21
  import { createUrlInfoTransformer } from "./url_graph/url_info_transformations.js";
21
22
  import { urlSpecifierEncoding } from "./url_graph/url_specifier_encoding.js";
22
23
 
@@ -102,6 +103,7 @@ export const createKitchen = ({
102
103
  inlineContentClientFileUrl,
103
104
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
104
105
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
106
+ isPlaceholderInjection,
105
107
  getPluginMeta: null,
106
108
  sourcemaps,
107
109
  outDirectoryUrl,
@@ -213,6 +213,7 @@ const createUrlInfo = (url, context) => {
213
213
  contentLength: undefined,
214
214
  contentFinalized: false,
215
215
  contentSideEffects: [],
216
+ contentInjections: {},
216
217
 
217
218
  sourcemap: null,
218
219
  sourcemapIsWrong: false,
@@ -0,0 +1,172 @@
1
+ import { injectJsenvScript, parseHtml, stringifyHtmlAst } from "@jsenv/ast";
2
+ import { composeTwoSourcemaps, createMagicSource } from "@jsenv/sourcemap";
3
+
4
+ const injectionSymbol = Symbol.for("jsenv_injection");
5
+ export const INJECTIONS = {
6
+ global: (value) => {
7
+ return { [injectionSymbol]: "global", value };
8
+ },
9
+ optional: (value) => {
10
+ return { [injectionSymbol]: "optional", value };
11
+ },
12
+ };
13
+
14
+ export const isPlaceholderInjection = (value) => {
15
+ return (
16
+ !value || !value[injectionSymbol] || value[injectionSymbol] !== "global"
17
+ );
18
+ };
19
+
20
+ export const applyContentInjections = (content, contentInjections, urlInfo) => {
21
+ const keys = Object.keys(contentInjections);
22
+ const globals = {};
23
+ const placeholderReplacements = [];
24
+ for (const key of keys) {
25
+ const contentInjection = contentInjections[key];
26
+ if (contentInjection && contentInjection[injectionSymbol]) {
27
+ const valueBehindSymbol = contentInjection[injectionSymbol];
28
+ if (valueBehindSymbol === "global") {
29
+ globals[key] = contentInjection.value;
30
+ } else if (valueBehindSymbol === "optional") {
31
+ placeholderReplacements.push({
32
+ key,
33
+ isOptional: true,
34
+ value: contentInjection.value,
35
+ });
36
+ } else {
37
+ throw new Error(`unknown injection type "${valueBehindSymbol}"`);
38
+ }
39
+ } else {
40
+ placeholderReplacements.push({
41
+ key,
42
+ value: contentInjection,
43
+ });
44
+ }
45
+ }
46
+
47
+ const needGlobalsInjection = Object.keys(globals).length > 0;
48
+ const needPlaceholderReplacements = placeholderReplacements.length > 0;
49
+
50
+ if (needGlobalsInjection && needPlaceholderReplacements) {
51
+ const globalInjectionResult = injectGlobals(content, globals, urlInfo);
52
+ const replaceInjectionResult = injectPlaceholderReplacements(
53
+ globalInjectionResult.content,
54
+ placeholderReplacements,
55
+ urlInfo,
56
+ );
57
+ return {
58
+ content: replaceInjectionResult.content,
59
+ sourcemap: composeTwoSourcemaps(
60
+ globalInjectionResult.sourcemap,
61
+ replaceInjectionResult.sourcemap,
62
+ ),
63
+ };
64
+ }
65
+ if (needGlobalsInjection) {
66
+ return injectGlobals(content, globals, urlInfo);
67
+ }
68
+ if (needPlaceholderReplacements) {
69
+ return injectPlaceholderReplacements(
70
+ content,
71
+ placeholderReplacements,
72
+ urlInfo,
73
+ );
74
+ }
75
+ return null;
76
+ };
77
+
78
+ export const injectPlaceholderReplacements = (
79
+ content,
80
+ placeholderReplacements,
81
+ urlInfo,
82
+ ) => {
83
+ const magicSource = createMagicSource(content);
84
+ for (const { key, isOptional, value } of placeholderReplacements) {
85
+ let index = content.indexOf(key);
86
+ if (index === -1) {
87
+ if (!isOptional) {
88
+ urlInfo.context.logger.warn(
89
+ `placeholder "${key}" not found in ${urlInfo.url}.
90
+ --- suggestion a ---
91
+ Add "${key}" in that file.
92
+ --- suggestion b ---
93
+ Fix eventual typo in "${key}"?
94
+ --- suggestion c ---
95
+ Mark injection as optional using INJECTIONS.optional():
96
+ import { INJECTIONS } from "@jsenv/core";
97
+
98
+ return {
99
+ "${key}": INJECTIONS.optional(${JSON.stringify(value)}),
100
+ };`,
101
+ );
102
+ }
103
+ continue;
104
+ }
105
+
106
+ while (index !== -1) {
107
+ const start = index;
108
+ const end = index + key.length;
109
+ magicSource.replace({
110
+ start,
111
+ end,
112
+ replacement:
113
+ urlInfo.type === "js_classic" ||
114
+ urlInfo.type === "js_module" ||
115
+ urlInfo.type === "html"
116
+ ? JSON.stringify(value, null, " ")
117
+ : value,
118
+ });
119
+ index = content.indexOf(key, end);
120
+ }
121
+ }
122
+ return magicSource.toContentAndSourcemap();
123
+ };
124
+
125
+ export const injectGlobals = (content, globals, urlInfo) => {
126
+ if (urlInfo.type === "html") {
127
+ return globalInjectorOnHtml(content, globals, urlInfo);
128
+ }
129
+ if (urlInfo.type === "js_classic" || urlInfo.type === "js_module") {
130
+ return globalsInjectorOnJs(content, globals, urlInfo);
131
+ }
132
+ throw new Error(`cannot inject globals into "${urlInfo.type}"`);
133
+ };
134
+ const globalInjectorOnHtml = (content, globals, urlInfo) => {
135
+ // ideally we would inject an importmap but browser support is too low
136
+ // (even worse for worker/service worker)
137
+ // so for now we inject code into entry points
138
+ const htmlAst = parseHtml({
139
+ html: content,
140
+ url: urlInfo.url,
141
+ storeOriginalPositions: false,
142
+ });
143
+ const clientCode = generateClientCodeForGlobals(globals, {
144
+ isWebWorker: false,
145
+ });
146
+ injectJsenvScript(htmlAst, {
147
+ content: clientCode,
148
+ pluginName: "jsenv:inject_globals",
149
+ });
150
+ return {
151
+ content: stringifyHtmlAst(htmlAst),
152
+ };
153
+ };
154
+ const globalsInjectorOnJs = (content, globals, urlInfo) => {
155
+ const clientCode = generateClientCodeForGlobals(globals, {
156
+ isWebWorker:
157
+ urlInfo.subtype === "worker" ||
158
+ urlInfo.subtype === "service_worker" ||
159
+ urlInfo.subtype === "shared_worker",
160
+ });
161
+ const magicSource = createMagicSource(content);
162
+ magicSource.prepend(clientCode);
163
+ return magicSource.toContentAndSourcemap();
164
+ };
165
+ const generateClientCodeForGlobals = (globals, { isWebWorker = false }) => {
166
+ const globalName = isWebWorker ? "self" : "window";
167
+ return `Object.assign(${globalName}, ${JSON.stringify(
168
+ globals,
169
+ null,
170
+ " ",
171
+ )});`;
172
+ };
@@ -17,6 +17,7 @@ import {
17
17
  defineGettersOnPropertiesDerivedFromContent,
18
18
  defineGettersOnPropertiesDerivedFromOriginalContent,
19
19
  } from "./url_content.js";
20
+ import { applyContentInjections } from "./url_info_injections.js";
20
21
 
21
22
  export const createUrlInfoTransformer = ({
22
23
  logger,
@@ -195,6 +196,7 @@ export const createUrlInfoTransformer = ({
195
196
  contentLength,
196
197
  sourcemap,
197
198
  sourcemapIsWrong,
199
+ contentInjections,
198
200
  } = transformations;
199
201
  if (type) {
200
202
  urlInfo.type = type;
@@ -202,13 +204,23 @@ export const createUrlInfoTransformer = ({
202
204
  if (contentType) {
203
205
  urlInfo.contentType = contentType;
204
206
  }
205
- const contentModified = setContentProperties(urlInfo, {
206
- content,
207
- contentAst,
208
- contentEtag,
209
- contentLength,
210
- });
211
-
207
+ if (Object.hasOwn(transformations, "contentInjections")) {
208
+ if (contentInjections) {
209
+ Object.assign(urlInfo.contentInjections, contentInjections);
210
+ }
211
+ if (content === undefined) {
212
+ return;
213
+ }
214
+ }
215
+ let contentModified;
216
+ if (Object.hasOwn(transformations, "content")) {
217
+ contentModified = setContentProperties(urlInfo, {
218
+ content,
219
+ contentAst,
220
+ contentEtag,
221
+ contentLength,
222
+ });
223
+ }
212
224
  if (
213
225
  sourcemap &&
214
226
  mayHaveSourcemap(urlInfo) &&
@@ -400,6 +412,15 @@ export const createUrlInfoTransformer = ({
400
412
  if (transformations) {
401
413
  applyTransformations(urlInfo, transformations);
402
414
  }
415
+ const { contentInjections } = urlInfo;
416
+ if (contentInjections && Object.keys(contentInjections).length > 0) {
417
+ const injectionTransformations = applyContentInjections(
418
+ urlInfo.content,
419
+ contentInjections,
420
+ urlInfo,
421
+ );
422
+ applyTransformations(urlInfo, injectionTransformations);
423
+ }
403
424
  applyContentEffects(urlInfo);
404
425
  urlInfo.contentFinalized = true;
405
426
  };
package/src/main.js CHANGED
@@ -15,4 +15,4 @@ export const startBuildServer = async (...args) => {
15
15
  };
16
16
 
17
17
  // others
18
- export { INJECTIONS } from "./plugins/injections/jsenv_plugin_injections.js";
18
+ export { INJECTIONS } from "./kitchen/url_graph/url_info_injections.js";
@@ -5,21 +5,16 @@
5
5
  * That will be replaced with true/false
6
6
  */
7
7
 
8
- import {
9
- INJECTIONS,
10
- replacePlaceholders,
11
- } from "../injections/jsenv_plugin_injections.js";
8
+ import { INJECTIONS } from "../../kitchen/url_graph/url_info_injections.js";
12
9
 
13
10
  export const jsenvPluginGlobalScenarios = () => {
14
11
  const transformIfNeeded = (urlInfo) => {
15
- return replacePlaceholders(
16
- urlInfo.content,
17
- {
12
+ return {
13
+ contentInjections: {
18
14
  __DEV__: INJECTIONS.optional(urlInfo.context.dev),
19
15
  __BUILD__: INJECTIONS.optional(urlInfo.context.build),
20
16
  },
21
- urlInfo,
22
- );
17
+ };
23
18
  };
24
19
 
25
20
  return {
@@ -6,6 +6,8 @@
6
6
  * - replaced by true: When scenario matches (import.meta.dev and it's the dev server)
7
7
  * - left as is to be evaluated to undefined (import.meta.build but it's the dev server)
8
8
  * - replaced by undefined (import.meta.dev but it's build; the goal is to ensure it's tree-shaked)
9
+ *
10
+ * TODO: ideally during dev we would keep import.meta.dev and ensure we set it to true rather than replacing it with true?
9
11
  */
10
12
 
11
13
  import { applyBabelPlugins } from "@jsenv/ast";
@@ -1,100 +1,66 @@
1
- import { createMagicSource } from "@jsenv/sourcemap";
2
1
  import { URL_META } from "@jsenv/url-meta";
3
- import { asUrlWithoutSearch } from "@jsenv/urls";
2
+ import { asUrlWithoutSearch, urlToRelativeUrl } from "@jsenv/urls";
3
+ import { INJECTIONS } from "../../kitchen/url_graph/url_info_injections.js";
4
4
 
5
5
  export const jsenvPluginInjections = (rawAssociations) => {
6
- let resolvedAssociations;
6
+ const getDefaultInjections = (urlInfo) => {
7
+ if (urlInfo.context.dev && urlInfo.type === "html") {
8
+ const relativeUrl = urlToRelativeUrl(
9
+ urlInfo.url,
10
+ urlInfo.context.rootDirectoryUrl,
11
+ );
12
+ return {
13
+ HTML_ROOT_PATHNAME: INJECTIONS.global(`/${relativeUrl}`),
14
+ };
15
+ }
16
+ return null;
17
+ };
18
+ let getInjections = null;
7
19
 
8
20
  return {
9
21
  name: "jsenv:injections",
10
22
  appliesDuring: "*",
11
23
  init: (context) => {
12
- resolvedAssociations = URL_META.resolveAssociations(
13
- { injectionsGetter: rawAssociations },
14
- context.rootDirectoryUrl,
15
- );
24
+ if (rawAssociations && Object.keys(rawAssociations).length > 0) {
25
+ const resolvedAssociations = URL_META.resolveAssociations(
26
+ { injectionsGetter: rawAssociations },
27
+ context.rootDirectoryUrl,
28
+ );
29
+ getInjections = (urlInfo) => {
30
+ const { injectionsGetter } = URL_META.applyAssociations({
31
+ url: asUrlWithoutSearch(urlInfo.url),
32
+ associations: resolvedAssociations,
33
+ });
34
+ if (!injectionsGetter) {
35
+ return null;
36
+ }
37
+ if (typeof injectionsGetter !== "function") {
38
+ throw new TypeError("injectionsGetter must be a function");
39
+ }
40
+ return injectionsGetter(urlInfo);
41
+ };
42
+ }
16
43
  },
17
44
  transformUrlContent: async (urlInfo) => {
18
- const { injectionsGetter } = URL_META.applyAssociations({
19
- url: asUrlWithoutSearch(urlInfo.url),
20
- associations: resolvedAssociations,
21
- });
22
- if (!injectionsGetter) {
23
- return null;
24
- }
25
- if (typeof injectionsGetter !== "function") {
26
- throw new TypeError("injectionsGetter must be a function");
27
- }
28
- const injections = await injectionsGetter(urlInfo);
29
- if (!injections) {
30
- return null;
45
+ const defaultInjections = getDefaultInjections(urlInfo);
46
+ if (!getInjections) {
47
+ return {
48
+ contentInjections: defaultInjections,
49
+ };
31
50
  }
32
- const keys = Object.keys(injections);
33
- if (keys.length === 0) {
34
- return null;
51
+ const injectionsResult = getInjections(urlInfo);
52
+ if (!injectionsResult) {
53
+ return {
54
+ contentInjections: defaultInjections,
55
+ };
35
56
  }
36
- return replacePlaceholders(urlInfo.content, injections, urlInfo);
57
+ const injections = await injectionsResult;
58
+ return {
59
+ contentInjections: {
60
+ ...defaultInjections,
61
+ ...injections,
62
+ },
63
+ };
37
64
  },
38
65
  };
39
66
  };
40
-
41
- const injectionSymbol = Symbol.for("jsenv_injection");
42
- export const INJECTIONS = {
43
- optional: (value) => {
44
- return { [injectionSymbol]: "optional", value };
45
- },
46
- };
47
-
48
- // we export this because it is imported by jsenv_plugin_placeholder.js and unit test
49
- export const replacePlaceholders = (content, replacements, urlInfo) => {
50
- const magicSource = createMagicSource(content);
51
- for (const key of Object.keys(replacements)) {
52
- let index = content.indexOf(key);
53
- const replacement = replacements[key];
54
- let isOptional;
55
- let value;
56
- if (replacement && replacement[injectionSymbol]) {
57
- const valueBehindSymbol = replacement[injectionSymbol];
58
- isOptional = valueBehindSymbol === "optional";
59
- value = replacement.value;
60
- } else {
61
- value = replacement;
62
- }
63
- if (index === -1) {
64
- if (!isOptional) {
65
- urlInfo.context.logger.warn(
66
- `placeholder "${key}" not found in ${urlInfo.url}.
67
- --- suggestion a ---
68
- Add "${key}" in that file.
69
- --- suggestion b ---
70
- Fix eventual typo in "${key}"?
71
- --- suggestion c ---
72
- Mark injection as optional using INJECTIONS.optional():
73
- import { INJECTIONS } from "@jsenv/core";
74
-
75
- return {
76
- "${key}": INJECTIONS.optional(${JSON.stringify(value)}),
77
- };`,
78
- );
79
- }
80
- continue;
81
- }
82
-
83
- while (index !== -1) {
84
- const start = index;
85
- const end = index + key.length;
86
- magicSource.replace({
87
- start,
88
- end,
89
- replacement:
90
- urlInfo.type === "js_classic" ||
91
- urlInfo.type === "js_module" ||
92
- urlInfo.type === "html"
93
- ? JSON.stringify(value, null, " ")
94
- : value,
95
- });
96
- index = content.indexOf(key, end);
97
- }
98
- }
99
- return magicSource.toContentAndSourcemap();
100
- };
@@ -423,6 +423,9 @@ const returnValueAssertions = [
423
423
  return undefined;
424
424
  }
425
425
  if (typeof content !== "string" && !Buffer.isBuffer(content) && !body) {
426
+ if (Object.hasOwn(valueReturned, "contentInjections")) {
427
+ return undefined;
428
+ }
426
429
  throw new Error(
427
430
  `Unexpected "content" returned by "${hook.plugin.name}" ${hook.name} hook: it must be a string or a buffer; got ${content}`,
428
431
  );
@@ -46,6 +46,7 @@ export const getCorePlugins = ({
46
46
  transpilation = true,
47
47
  inlining = true,
48
48
  http = false,
49
+ spa,
49
50
 
50
51
  clientAutoreload,
51
52
  clientAutoreloadOnServerRestart,
@@ -75,7 +76,7 @@ export const getCorePlugins = ({
75
76
 
76
77
  return [
77
78
  jsenvPluginReferenceAnalysis(referenceAnalysis),
78
- ...(injections ? [jsenvPluginInjections(injections)] : []),
79
+ jsenvPluginInjections(injections),
79
80
  jsenvPluginTranspilation(transpilation),
80
81
  // "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
81
82
  ...(inlining ? [jsenvPluginInlining()] : []),
@@ -88,6 +89,7 @@ export const getCorePlugins = ({
88
89
  */
89
90
  jsenvPluginProtocolHttp(http),
90
91
  jsenvPluginProtocolFile({
92
+ spa,
91
93
  magicExtensions,
92
94
  magicDirectoryIndex,
93
95
  directoryListing,
@@ -37,7 +37,6 @@ import {
37
37
  } from "@jsenv/urls";
38
38
  import { existsSync, lstatSync, readdirSync } from "node:fs";
39
39
  import { getDirectoryWatchPatterns } from "../../helpers/watch_source_files.js";
40
- import { replacePlaceholders } from "../injections/jsenv_plugin_injections.js";
41
40
  import { FILE_AND_SERVER_URLS_CONVERTER } from "./file_and_server_urls_converter.js";
42
41
 
43
42
  const htmlFileUrlForDirectory = import.meta.resolve(
@@ -124,22 +123,22 @@ export const jsenvPluginDirectoryListing = ({
124
123
  }
125
124
  const request = urlInfo.context.request;
126
125
  const { rootDirectoryUrl, mainFilePath } = urlInfo.context;
127
- return replacePlaceholders(
128
- urlInfo.content,
126
+ const directoryListingInjections = generateDirectoryListingInjection(
127
+ requestedUrl,
129
128
  {
130
- ...generateDirectoryListingInjection(requestedUrl, {
131
- autoreload,
132
- request,
133
- urlMocks,
134
- directoryContentMagicName,
135
- rootDirectoryUrl,
136
- mainFilePath,
137
- packageDirectory,
138
- enoent,
139
- }),
129
+ autoreload,
130
+ request,
131
+ urlMocks,
132
+ directoryContentMagicName,
133
+ rootDirectoryUrl,
134
+ mainFilePath,
135
+ packageDirectory,
136
+ enoent,
140
137
  },
141
- urlInfo,
142
138
  );
139
+ return {
140
+ contentInjections: directoryListingInjections,
141
+ };
143
142
  },
144
143
  },
145
144
  devServerRoutes: [
@@ -158,8 +157,10 @@ export const jsenvPluginDirectoryListing = ({
158
157
  directoryRelativeUrl,
159
158
  rootDirectoryUrl,
160
159
  );
161
- const closestDirectoryUrl =
162
- getFirstExistingDirectoryUrl(requestedUrl);
160
+ const closestDirectoryUrl = getFirstExistingDirectoryUrl(
161
+ requestedUrl,
162
+ rootDirectoryUrl,
163
+ );
163
164
  const sendMessage = (message) => {
164
165
  websocket.send(JSON.stringify(message));
165
166
  };
@@ -375,15 +376,15 @@ const generateDirectoryListingInjection = (
375
376
  };
376
377
  };
377
378
  const getFirstExistingDirectoryUrl = (requestedUrl, serverRootDirectoryUrl) => {
378
- let firstExistingDirectoryUrl = new URL("./", requestedUrl);
379
- while (!existsSync(firstExistingDirectoryUrl)) {
380
- firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
381
- if (!urlIsInsideOf(firstExistingDirectoryUrl, serverRootDirectoryUrl)) {
382
- firstExistingDirectoryUrl = new URL(serverRootDirectoryUrl);
379
+ let directoryUrlCandidate = new URL("./", requestedUrl);
380
+ while (!existsSync(directoryUrlCandidate)) {
381
+ directoryUrlCandidate = new URL("../", directoryUrlCandidate);
382
+ if (!urlIsInsideOf(directoryUrlCandidate, serverRootDirectoryUrl)) {
383
+ directoryUrlCandidate = new URL(serverRootDirectoryUrl);
383
384
  break;
384
385
  }
385
386
  }
386
- return firstExistingDirectoryUrl;
387
+ return directoryUrlCandidate;
387
388
  };
388
389
  const getDirectoryContentItems = ({
389
390
  serverRootDirectoryUrl,