@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.
- package/dist/build/browserslist_index/browserslist_index.js +62 -48
- package/dist/build/build.js +90 -63
- package/dist/build/jsenv_core_packages.js +3 -3
- package/dist/client/directory_listing/js/directory_listing.js +41 -26
- package/dist/client/ribbon/ribbon.js +40 -37
- package/dist/start_dev_server/jsenv_core_packages.js +3 -3
- package/dist/start_dev_server/start_dev_server.js +87 -59
- package/package.json +21 -12
- package/src/build/build.js +9 -9
- package/src/build/build_specifier_manager.js +3 -3
- package/src/build/build_urls_generator.js +2 -2
- package/src/dev/start_dev_server.js +8 -7
- package/src/helpers/web_url_converter.js +2 -2
- package/src/kitchen/errors.js +1 -1
- package/src/kitchen/out_directory_url.js +2 -2
- package/src/plugins/autoreload/jsenv_plugin_autoreload_server.js +2 -2
- package/src/plugins/chrome_devtools_json/jsenv_plugin_chrome_devtools_json.js +1 -0
- package/src/plugins/plugin_controller.js +25 -7
- package/src/plugins/protocol_file/client/directory_listing.jsx +42 -23
- package/src/plugins/protocol_file/file_and_server_urls_converter.js +2 -5
- package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +45 -30
- package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +5 -10
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +2 -1
- package/src/plugins/ribbon/client/ribbon.js +40 -37
|
@@ -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 {
|
|
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
|
-
|
|
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,
|
|
2
|
+
import { moveUrl, urlIsOrIsInsideOf } from "@jsenv/urls";
|
|
3
3
|
|
|
4
4
|
export const WEB_URL_CONVERTER = {
|
|
5
5
|
asWebUrl: (fileUrl, webServer) => {
|
|
6
|
-
if (
|
|
6
|
+
if (urlIsOrIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
|
|
7
7
|
return moveUrl({
|
|
8
8
|
url: fileUrl,
|
|
9
9
|
from: webServer.rootDirectoryUrl,
|
package/src/kitchen/errors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ensureWindowsDriveLetter } from "@jsenv/filesystem";
|
|
2
2
|
import { generateSourcemapFileUrl } from "@jsenv/sourcemap";
|
|
3
|
-
import { moveUrl, setUrlFilename,
|
|
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 (!
|
|
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 {
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
<
|
|
52
|
+
<Breadcrumb items={breadcrumb} />
|
|
53
53
|
<DirectoryContent items={directoryItems} />
|
|
54
54
|
</>
|
|
55
55
|
);
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const ErrorMessage = () => {
|
|
59
|
-
const {
|
|
59
|
+
const { filePathExisting, filePathNotFound } = enoentDetails;
|
|
60
|
+
|
|
61
|
+
let errorText;
|
|
62
|
+
let errorSuggestion;
|
|
63
|
+
errorText = (
|
|
64
|
+
<>
|
|
65
|
+
<strong>File not found:</strong>
|
|
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
|
-
<
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
93
|
+
const Breadcrumb = ({ items }) => {
|
|
81
94
|
return (
|
|
82
95
|
<h1 className="nav">
|
|
83
|
-
{
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
115
|
-
if (!
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
289
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
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 = (
|
|
379
|
-
let directoryUrlCandidate = new URL("./",
|
|
393
|
+
const getFirstExistingDirectoryUrl = (urlBase, serverRootDirectoryUrl) => {
|
|
394
|
+
let directoryUrlCandidate = new URL("./", urlBase);
|
|
380
395
|
while (!existsSync(directoryUrlCandidate)) {
|
|
381
396
|
directoryUrlCandidate = new URL("../", directoryUrlCandidate);
|
|
382
|
-
if (!
|
|
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
|
-
|
|
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
|
|
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
|
|
176
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
|
41
|
+
<style>
|
|
42
|
+
${css}
|
|
43
|
+
</style>
|
|
41
44
|
<div id="jsenv_ribbon">
|
|
42
45
|
<div id="jsenv_ribbon_text">${text}</div>
|
|
43
46
|
</div>
|