@jsenv/server 17.0.1 → 17.0.2
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/jsenv_server.js +109 -129
- package/index.js +2 -3
- package/package.json +1 -1
- package/src/plugins/filesystem/{serve_directory.js → fetch_directory.js} +1 -1
- package/src/plugins/filesystem/{fetch_filesystem.js → fetch_file.js} +33 -7
- package/src/plugins/filesystem/server_plugin_static_files.js +0 -48
package/dist/jsenv_server.js
CHANGED
|
@@ -5370,25 +5370,24 @@ const createResourcePattern = (pattern) => {
|
|
|
5370
5370
|
});
|
|
5371
5371
|
}
|
|
5372
5372
|
|
|
5373
|
-
const patternEndsWithSlash =
|
|
5374
|
-
pathnamePatternString.length > 1 && pathnamePatternString.endsWith("/");
|
|
5373
|
+
const patternEndsWithSlash = pathnamePatternString.endsWith("/");
|
|
5375
5374
|
|
|
5376
5375
|
return {
|
|
5377
5376
|
match: (resource) => {
|
|
5378
5377
|
const [pathname, search, hash] = resourceToParts(resource);
|
|
5379
5378
|
let decodedPathname = decodeURIComponent(pathname);
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
decodedPathname.startsWith(pathnamePatternString)
|
|
5383
|
-
|
|
5384
|
-
|
|
5379
|
+
let result;
|
|
5380
|
+
if (patternEndsWithSlash && !searchPattern && !hashPattern) {
|
|
5381
|
+
if (!decodedPathname.startsWith(pathnamePatternString)) {
|
|
5382
|
+
return null;
|
|
5383
|
+
}
|
|
5384
|
+
result = {
|
|
5385
5385
|
named: {},
|
|
5386
5386
|
stars: [decodedPathname.slice(pathnamePatternString.length)],
|
|
5387
5387
|
};
|
|
5388
|
+
} else {
|
|
5389
|
+
result = pathnamePattern.match(decodedPathname);
|
|
5388
5390
|
}
|
|
5389
|
-
let result = patternEndsWithSlash
|
|
5390
|
-
? null
|
|
5391
|
-
: pathnamePattern.match(decodedPathname);
|
|
5392
5391
|
if (!result) {
|
|
5393
5392
|
return null;
|
|
5394
5393
|
}
|
|
@@ -8029,6 +8028,76 @@ const bufferToEtag = (buffer) => {
|
|
|
8029
8028
|
return `"${length.toString(16)}-${hashBase64StringSubset}"`;
|
|
8030
8029
|
};
|
|
8031
8030
|
|
|
8031
|
+
const fetchDirectory = (
|
|
8032
|
+
url,
|
|
8033
|
+
{ headers = {}, rootDirectoryUrl } = {},
|
|
8034
|
+
) => {
|
|
8035
|
+
url = String(url);
|
|
8036
|
+
url = url[url.length - 1] === "/" ? url : `${url}/`;
|
|
8037
|
+
const directoryContentArray = readdirSync(new URL(url));
|
|
8038
|
+
const responseProducers = {
|
|
8039
|
+
"application/json": () => {
|
|
8040
|
+
const directoryContentJson = JSON.stringify(
|
|
8041
|
+
directoryContentArray,
|
|
8042
|
+
null,
|
|
8043
|
+
" ",
|
|
8044
|
+
);
|
|
8045
|
+
return {
|
|
8046
|
+
status: 200,
|
|
8047
|
+
headers: {
|
|
8048
|
+
"content-type": "application/json",
|
|
8049
|
+
"content-length": directoryContentJson.length,
|
|
8050
|
+
},
|
|
8051
|
+
body: directoryContentJson,
|
|
8052
|
+
};
|
|
8053
|
+
},
|
|
8054
|
+
"text/html": () => {
|
|
8055
|
+
const directoryAsHtml = `<!DOCTYPE html>
|
|
8056
|
+
<html>
|
|
8057
|
+
<head>
|
|
8058
|
+
<title>Directory explorer</title>
|
|
8059
|
+
<meta charset="utf-8" />
|
|
8060
|
+
<link rel="icon" href="data:," />
|
|
8061
|
+
</head>
|
|
8062
|
+
|
|
8063
|
+
<body>
|
|
8064
|
+
<h1>Content of directory ${url}</h1>
|
|
8065
|
+
<ul>
|
|
8066
|
+
${directoryContentArray.map((filename) => {
|
|
8067
|
+
const fileUrlObject = new URL(filename, url);
|
|
8068
|
+
const fileUrl = String(fileUrlObject);
|
|
8069
|
+
let fileUrlRelativeToServer = fileUrl.slice(
|
|
8070
|
+
String(rootDirectoryUrl).length,
|
|
8071
|
+
);
|
|
8072
|
+
if (lstatSync(fileUrlObject).isDirectory()) {
|
|
8073
|
+
fileUrlRelativeToServer += "/";
|
|
8074
|
+
}
|
|
8075
|
+
return `<li>
|
|
8076
|
+
<a href="/${fileUrlRelativeToServer}">${fileUrlRelativeToServer}</a>
|
|
8077
|
+
</li>`;
|
|
8078
|
+
}).join(`
|
|
8079
|
+
`)}
|
|
8080
|
+
</ul>
|
|
8081
|
+
</body>
|
|
8082
|
+
</html>`;
|
|
8083
|
+
|
|
8084
|
+
return {
|
|
8085
|
+
status: 200,
|
|
8086
|
+
headers: {
|
|
8087
|
+
"content-type": "text/html",
|
|
8088
|
+
"content-length": Buffer.byteLength(directoryAsHtml),
|
|
8089
|
+
},
|
|
8090
|
+
body: directoryAsHtml,
|
|
8091
|
+
};
|
|
8092
|
+
},
|
|
8093
|
+
};
|
|
8094
|
+
const bestContentType = pickContentType(
|
|
8095
|
+
{ headers },
|
|
8096
|
+
Object.keys(responseProducers),
|
|
8097
|
+
);
|
|
8098
|
+
return responseProducers[bestContentType || "application/json"]();
|
|
8099
|
+
};
|
|
8100
|
+
|
|
8032
8101
|
const convertFileSystemErrorToResponseProperties = (error) => {
|
|
8033
8102
|
// https://iojs.org/api/errors.html#errors_eacces_permission_denied
|
|
8034
8103
|
if (isErrorWithCode(error, "EACCES")) {
|
|
@@ -8114,76 +8183,6 @@ const fileSystemPathToUrl = (value) => {
|
|
|
8114
8183
|
return String(pathToFileURL(value));
|
|
8115
8184
|
};
|
|
8116
8185
|
|
|
8117
|
-
const serveDirectory = (
|
|
8118
|
-
url,
|
|
8119
|
-
{ headers = {}, rootDirectoryUrl } = {},
|
|
8120
|
-
) => {
|
|
8121
|
-
url = String(url);
|
|
8122
|
-
url = url[url.length - 1] === "/" ? url : `${url}/`;
|
|
8123
|
-
const directoryContentArray = readdirSync(new URL(url));
|
|
8124
|
-
const responseProducers = {
|
|
8125
|
-
"application/json": () => {
|
|
8126
|
-
const directoryContentJson = JSON.stringify(
|
|
8127
|
-
directoryContentArray,
|
|
8128
|
-
null,
|
|
8129
|
-
" ",
|
|
8130
|
-
);
|
|
8131
|
-
return {
|
|
8132
|
-
status: 200,
|
|
8133
|
-
headers: {
|
|
8134
|
-
"content-type": "application/json",
|
|
8135
|
-
"content-length": directoryContentJson.length,
|
|
8136
|
-
},
|
|
8137
|
-
body: directoryContentJson,
|
|
8138
|
-
};
|
|
8139
|
-
},
|
|
8140
|
-
"text/html": () => {
|
|
8141
|
-
const directoryAsHtml = `<!DOCTYPE html>
|
|
8142
|
-
<html>
|
|
8143
|
-
<head>
|
|
8144
|
-
<title>Directory explorer</title>
|
|
8145
|
-
<meta charset="utf-8" />
|
|
8146
|
-
<link rel="icon" href="data:," />
|
|
8147
|
-
</head>
|
|
8148
|
-
|
|
8149
|
-
<body>
|
|
8150
|
-
<h1>Content of directory ${url}</h1>
|
|
8151
|
-
<ul>
|
|
8152
|
-
${directoryContentArray.map((filename) => {
|
|
8153
|
-
const fileUrlObject = new URL(filename, url);
|
|
8154
|
-
const fileUrl = String(fileUrlObject);
|
|
8155
|
-
let fileUrlRelativeToServer = fileUrl.slice(
|
|
8156
|
-
String(rootDirectoryUrl).length,
|
|
8157
|
-
);
|
|
8158
|
-
if (lstatSync(fileUrlObject).isDirectory()) {
|
|
8159
|
-
fileUrlRelativeToServer += "/";
|
|
8160
|
-
}
|
|
8161
|
-
return `<li>
|
|
8162
|
-
<a href="/${fileUrlRelativeToServer}">${fileUrlRelativeToServer}</a>
|
|
8163
|
-
</li>`;
|
|
8164
|
-
}).join(`
|
|
8165
|
-
`)}
|
|
8166
|
-
</ul>
|
|
8167
|
-
</body>
|
|
8168
|
-
</html>`;
|
|
8169
|
-
|
|
8170
|
-
return {
|
|
8171
|
-
status: 200,
|
|
8172
|
-
headers: {
|
|
8173
|
-
"content-type": "text/html",
|
|
8174
|
-
"content-length": Buffer.byteLength(directoryAsHtml),
|
|
8175
|
-
},
|
|
8176
|
-
body: directoryAsHtml,
|
|
8177
|
-
};
|
|
8178
|
-
},
|
|
8179
|
-
};
|
|
8180
|
-
const bestContentType = pickContentType(
|
|
8181
|
-
{ headers },
|
|
8182
|
-
Object.keys(responseProducers),
|
|
8183
|
-
);
|
|
8184
|
-
return responseProducers[bestContentType || "application/json"]();
|
|
8185
|
-
};
|
|
8186
|
-
|
|
8187
8186
|
/*
|
|
8188
8187
|
* This function returns response properties in a plain object like
|
|
8189
8188
|
* { status: 200, body: "Hello world" }.
|
|
@@ -8202,17 +8201,17 @@ const fetchFileSystem = async (
|
|
|
8202
8201
|
helpers,
|
|
8203
8202
|
directoryUrl,
|
|
8204
8203
|
{
|
|
8204
|
+
mainFileRelativeUrl = null,
|
|
8205
8205
|
etagEnabled = false,
|
|
8206
8206
|
etagMemory = true,
|
|
8207
8207
|
etagMemoryMaxSize = 1000,
|
|
8208
8208
|
mtimeEnabled = false,
|
|
8209
8209
|
compressionEnabled = false,
|
|
8210
8210
|
compressionSizeThreshold = 1024,
|
|
8211
|
-
cacheControl
|
|
8212
|
-
|
|
8213
|
-
: "no-store",
|
|
8211
|
+
cacheControl,
|
|
8212
|
+
isVersioned = defaultIsVersioned,
|
|
8214
8213
|
canReadDirectory = false,
|
|
8215
|
-
ENOENTFallback = () =>
|
|
8214
|
+
ENOENTFallback = () => null,
|
|
8216
8215
|
} = {},
|
|
8217
8216
|
) => {
|
|
8218
8217
|
let directoryUrlString = asUrlString(directoryUrl);
|
|
@@ -8238,7 +8237,11 @@ const fetchFileSystem = async (
|
|
|
8238
8237
|
const filesystemUrl = new URL(resource, directoryUrl);
|
|
8239
8238
|
const urlString = asUrlString(filesystemUrl);
|
|
8240
8239
|
|
|
8241
|
-
if (
|
|
8240
|
+
if (cacheControl === undefined) {
|
|
8241
|
+
cacheControl = isVersioned(request)
|
|
8242
|
+
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
8243
|
+
: "private,max-age=0,must-revalidate";
|
|
8244
|
+
} else if (typeof cacheControl === "function") {
|
|
8242
8245
|
cacheControl = cacheControl(request);
|
|
8243
8246
|
}
|
|
8244
8247
|
|
|
@@ -8274,12 +8277,15 @@ const fetchFileSystem = async (
|
|
|
8274
8277
|
|
|
8275
8278
|
if (fileStat.isDirectory()) {
|
|
8276
8279
|
if (canReadDirectory) {
|
|
8277
|
-
return
|
|
8280
|
+
return fetchDirectory(fileUrl, {
|
|
8278
8281
|
headers: request.headers,
|
|
8279
8282
|
canReadDirectory,
|
|
8280
8283
|
rootDirectoryUrl: directoryUrl,
|
|
8281
8284
|
});
|
|
8282
8285
|
}
|
|
8286
|
+
if (mainFileRelativeUrl && fileUrl === directoryUrlString) {
|
|
8287
|
+
return serveFile(new URL(mainFileRelativeUrl, directoryUrl).href);
|
|
8288
|
+
}
|
|
8283
8289
|
return {
|
|
8284
8290
|
status: 403,
|
|
8285
8291
|
statusText: "not allowed to read directory",
|
|
@@ -8350,6 +8356,15 @@ const fetchFileSystem = async (
|
|
|
8350
8356
|
return composeTwoResponses(intermediateResponse, clientCacheResponse);
|
|
8351
8357
|
} catch (e) {
|
|
8352
8358
|
if (e.code === "ENOENT") {
|
|
8359
|
+
if (mainFileRelativeUrl) {
|
|
8360
|
+
if (
|
|
8361
|
+
!urlToExtension(fileUrl) &&
|
|
8362
|
+
!urlToPathname(fileUrl).endsWith("/")
|
|
8363
|
+
) {
|
|
8364
|
+
const mainFileUrl = new URL(mainFileRelativeUrl, directoryUrl);
|
|
8365
|
+
return serveFile(mainFileUrl);
|
|
8366
|
+
}
|
|
8367
|
+
}
|
|
8353
8368
|
const fallbackFileUrl = ENOENTFallback();
|
|
8354
8369
|
if (fallbackFileUrl) {
|
|
8355
8370
|
return serveFile(fallbackFileUrl);
|
|
@@ -8621,50 +8636,15 @@ const asUrlString = (value) => {
|
|
|
8621
8636
|
return null;
|
|
8622
8637
|
};
|
|
8623
8638
|
|
|
8624
|
-
const serverPluginStaticFiles = ({
|
|
8625
|
-
serverRelativeUrl = "/",
|
|
8626
|
-
directoryUrl,
|
|
8627
|
-
directoryMainFileRelativeUrl = "index.html",
|
|
8628
|
-
}) => {
|
|
8629
|
-
return {
|
|
8630
|
-
name: "jsenv:static_files",
|
|
8631
|
-
routes: [
|
|
8632
|
-
{
|
|
8633
|
-
endpoint: `GET ${serverRelativeUrl}`,
|
|
8634
|
-
description: "Serve static files.",
|
|
8635
|
-
fetch: (request, helpers) => {
|
|
8636
|
-
const urlIsVersioned = new URL(request.url).searchParams.has("v");
|
|
8637
|
-
if (directoryMainFileRelativeUrl && request.resource === "/") {
|
|
8638
|
-
request = {
|
|
8639
|
-
...request,
|
|
8640
|
-
resource: `/${directoryMainFileRelativeUrl}`,
|
|
8641
|
-
};
|
|
8642
|
-
}
|
|
8643
|
-
const urlObject = new URL(request.resource.slice(1), directoryUrl);
|
|
8644
|
-
return createFileSystemFetch(directoryUrl, {
|
|
8645
|
-
cacheControl: urlIsVersioned
|
|
8646
|
-
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
8647
|
-
: "private,max-age=0,must-revalidate",
|
|
8648
|
-
etagEnabled: true,
|
|
8649
|
-
compressionEnabled: true,
|
|
8650
|
-
canReadDirectory: true,
|
|
8651
|
-
ENOENTFallback: () => {
|
|
8652
|
-
if (
|
|
8653
|
-
!urlToExtension(urlObject) &&
|
|
8654
|
-
!urlToPathname(urlObject).endsWith("/")
|
|
8655
|
-
) {
|
|
8656
|
-
return new URL(directoryMainFileRelativeUrl, directoryUrl);
|
|
8657
|
-
}
|
|
8658
|
-
return null;
|
|
8659
|
-
},
|
|
8660
|
-
})(request, helpers);
|
|
8661
|
-
},
|
|
8662
|
-
},
|
|
8663
|
-
],
|
|
8664
|
-
};
|
|
8665
|
-
};
|
|
8666
8639
|
const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
|
|
8667
8640
|
|
|
8641
|
+
const defaultIsVersioned = (request) => {
|
|
8642
|
+
if (!request.url) {
|
|
8643
|
+
return false;
|
|
8644
|
+
}
|
|
8645
|
+
return new URL(request.url).searchParams.has("v");
|
|
8646
|
+
};
|
|
8647
|
+
|
|
8668
8648
|
const serverPluginResponseAcceptanceCheck = () => {
|
|
8669
8649
|
return {
|
|
8670
8650
|
name: "jsenv:response_acceptance_check",
|
|
@@ -8754,4 +8734,4 @@ const replaceResource = (resourceBeforeAlias, newValue) => {
|
|
|
8754
8734
|
return resource;
|
|
8755
8735
|
};
|
|
8756
8736
|
|
|
8757
|
-
export { LazyServerEvents, ProgressiveResponse, STOP_REASON_INTERNAL_ERROR, STOP_REASON_NOT_SPECIFIED, STOP_REASON_PROCESS_BEFORE_EXIT, STOP_REASON_PROCESS_EXIT, STOP_REASON_PROCESS_SIGHUP, STOP_REASON_PROCESS_SIGINT, STOP_REASON_PROCESS_SIGTERM, ServerEvents, WebSocketResponse, composeTwoResponses, createFileSystemFetch, createPluginsController, fetchFileSystem, findFreePort, jsenvAccessControlAllowedHeaders, jsenvAccessControlAllowedMethods, pickContentEncoding, pickContentLanguage, pickContentType,
|
|
8737
|
+
export { LazyServerEvents, ProgressiveResponse, STOP_REASON_INTERNAL_ERROR, STOP_REASON_NOT_SPECIFIED, STOP_REASON_PROCESS_BEFORE_EXIT, STOP_REASON_PROCESS_EXIT, STOP_REASON_PROCESS_SIGHUP, STOP_REASON_PROCESS_SIGINT, STOP_REASON_PROCESS_SIGTERM, ServerEvents, WebSocketResponse, composeTwoResponses, createFileSystemFetch, createPluginsController, fetchDirectory, fetchFileSystem, findFreePort, jsenvAccessControlAllowedHeaders, jsenvAccessControlAllowedMethods, pickContentEncoding, pickContentLanguage, pickContentType, serverPluginCORS, serverPluginErrorHandler, serverPluginRequestAliases, serverPluginResponseAcceptanceCheck, startServer };
|
package/index.js
CHANGED
|
@@ -37,9 +37,8 @@ export {
|
|
|
37
37
|
export {
|
|
38
38
|
createFileSystemFetch,
|
|
39
39
|
fetchFileSystem,
|
|
40
|
-
} from "./src/plugins/filesystem/
|
|
41
|
-
export {
|
|
42
|
-
export { serverPluginStaticFiles } from "./src/plugins/filesystem/server_plugin_static_files.js";
|
|
40
|
+
} from "./src/plugins/filesystem/fetch_file.js";
|
|
41
|
+
export { fetchDirectory } from "./src/plugins/filesystem/fetch_directory.js";
|
|
43
42
|
|
|
44
43
|
// content-negotiation
|
|
45
44
|
export { pickContentType } from "./src/content_negotiation/pick_content_type.js";
|
package/package.json
CHANGED
|
@@ -4,18 +4,19 @@
|
|
|
4
4
|
* It is meant to be used inside "requestToResponse"
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { urlToExtension, urlToPathname } from "@jsenv/urls";
|
|
7
8
|
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
|
|
8
9
|
import { createReadStream, readFile, statSync } from "node:fs";
|
|
9
10
|
|
|
10
11
|
import { pickContentEncoding } from "../../content_negotiation/pick_content_encoding.js";
|
|
11
12
|
import { composeTwoResponses } from "../../internal/response_composition.js";
|
|
12
13
|
import { bufferToEtag } from "./etag.js";
|
|
14
|
+
import { fetchDirectory } from "./fetch_directory.js";
|
|
13
15
|
import { convertFileSystemErrorToResponseProperties } from "./filesystem_error_to_response.js";
|
|
14
16
|
import {
|
|
15
17
|
fileSystemPathToUrl,
|
|
16
18
|
isFileSystemPath,
|
|
17
19
|
} from "./filesystem_path_and_url.js";
|
|
18
|
-
import { serveDirectory } from "./serve_directory.js";
|
|
19
20
|
|
|
20
21
|
export const createFileSystemFetch = (directoryUrl, options) => {
|
|
21
22
|
return (request, helpers) => {
|
|
@@ -28,17 +29,17 @@ export const fetchFileSystem = async (
|
|
|
28
29
|
helpers,
|
|
29
30
|
directoryUrl,
|
|
30
31
|
{
|
|
32
|
+
mainFileRelativeUrl = null,
|
|
31
33
|
etagEnabled = false,
|
|
32
34
|
etagMemory = true,
|
|
33
35
|
etagMemoryMaxSize = 1000,
|
|
34
36
|
mtimeEnabled = false,
|
|
35
37
|
compressionEnabled = false,
|
|
36
38
|
compressionSizeThreshold = 1024,
|
|
37
|
-
cacheControl
|
|
38
|
-
|
|
39
|
-
: "no-store",
|
|
39
|
+
cacheControl,
|
|
40
|
+
isVersioned = defaultIsVersioned,
|
|
40
41
|
canReadDirectory = false,
|
|
41
|
-
ENOENTFallback = () =>
|
|
42
|
+
ENOENTFallback = () => null,
|
|
42
43
|
} = {},
|
|
43
44
|
) => {
|
|
44
45
|
let directoryUrlString = asUrlString(directoryUrl);
|
|
@@ -64,7 +65,11 @@ export const fetchFileSystem = async (
|
|
|
64
65
|
const filesystemUrl = new URL(resource, directoryUrl);
|
|
65
66
|
const urlString = asUrlString(filesystemUrl);
|
|
66
67
|
|
|
67
|
-
if (
|
|
68
|
+
if (cacheControl === undefined) {
|
|
69
|
+
cacheControl = isVersioned(request)
|
|
70
|
+
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
71
|
+
: "private,max-age=0,must-revalidate";
|
|
72
|
+
} else if (typeof cacheControl === "function") {
|
|
68
73
|
cacheControl = cacheControl(request);
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -100,12 +105,15 @@ export const fetchFileSystem = async (
|
|
|
100
105
|
|
|
101
106
|
if (fileStat.isDirectory()) {
|
|
102
107
|
if (canReadDirectory) {
|
|
103
|
-
return
|
|
108
|
+
return fetchDirectory(fileUrl, {
|
|
104
109
|
headers: request.headers,
|
|
105
110
|
canReadDirectory,
|
|
106
111
|
rootDirectoryUrl: directoryUrl,
|
|
107
112
|
});
|
|
108
113
|
}
|
|
114
|
+
if (mainFileRelativeUrl && fileUrl === directoryUrlString) {
|
|
115
|
+
return serveFile(new URL(mainFileRelativeUrl, directoryUrl).href);
|
|
116
|
+
}
|
|
109
117
|
return {
|
|
110
118
|
status: 403,
|
|
111
119
|
statusText: "not allowed to read directory",
|
|
@@ -176,6 +184,15 @@ export const fetchFileSystem = async (
|
|
|
176
184
|
return composeTwoResponses(intermediateResponse, clientCacheResponse);
|
|
177
185
|
} catch (e) {
|
|
178
186
|
if (e.code === "ENOENT") {
|
|
187
|
+
if (mainFileRelativeUrl) {
|
|
188
|
+
if (
|
|
189
|
+
!urlToExtension(fileUrl) &&
|
|
190
|
+
!urlToPathname(fileUrl).endsWith("/")
|
|
191
|
+
) {
|
|
192
|
+
const mainFileUrl = new URL(mainFileRelativeUrl, directoryUrl);
|
|
193
|
+
return serveFile(mainFileUrl);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
179
196
|
const fallbackFileUrl = ENOENTFallback();
|
|
180
197
|
if (fallbackFileUrl) {
|
|
181
198
|
return serveFile(fallbackFileUrl);
|
|
@@ -446,3 +463,12 @@ const asUrlString = (value) => {
|
|
|
446
463
|
}
|
|
447
464
|
return null;
|
|
448
465
|
};
|
|
466
|
+
|
|
467
|
+
const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
|
|
468
|
+
|
|
469
|
+
const defaultIsVersioned = (request) => {
|
|
470
|
+
if (!request.url) {
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
return new URL(request.url).searchParams.has("v");
|
|
474
|
+
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { urlToExtension, urlToPathname } from "@jsenv/urls";
|
|
2
|
-
|
|
3
|
-
import { createFileSystemFetch } from "./fetch_filesystem.js";
|
|
4
|
-
|
|
5
|
-
export const serverPluginStaticFiles = ({
|
|
6
|
-
serverRelativeUrl = "/",
|
|
7
|
-
directoryUrl,
|
|
8
|
-
directoryMainFileRelativeUrl = "index.html",
|
|
9
|
-
}) => {
|
|
10
|
-
return {
|
|
11
|
-
name: "jsenv:static_files",
|
|
12
|
-
routes: [
|
|
13
|
-
{
|
|
14
|
-
endpoint: `GET ${serverRelativeUrl}`,
|
|
15
|
-
description: "Serve static files.",
|
|
16
|
-
fetch: (request, helpers) => {
|
|
17
|
-
const urlIsVersioned = new URL(request.url).searchParams.has("v");
|
|
18
|
-
if (directoryMainFileRelativeUrl && request.resource === "/") {
|
|
19
|
-
request = {
|
|
20
|
-
...request,
|
|
21
|
-
resource: `/${directoryMainFileRelativeUrl}`,
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
const urlObject = new URL(request.resource.slice(1), directoryUrl);
|
|
25
|
-
return createFileSystemFetch(directoryUrl, {
|
|
26
|
-
cacheControl: urlIsVersioned
|
|
27
|
-
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
28
|
-
: "private,max-age=0,must-revalidate",
|
|
29
|
-
etagEnabled: true,
|
|
30
|
-
compressionEnabled: true,
|
|
31
|
-
rootDirectoryUrl: directoryUrl,
|
|
32
|
-
canReadDirectory: true,
|
|
33
|
-
ENOENTFallback: () => {
|
|
34
|
-
if (
|
|
35
|
-
!urlToExtension(urlObject) &&
|
|
36
|
-
!urlToPathname(urlObject).endsWith("/")
|
|
37
|
-
) {
|
|
38
|
-
return new URL(directoryMainFileRelativeUrl, directoryUrl);
|
|
39
|
-
}
|
|
40
|
-
return null;
|
|
41
|
-
},
|
|
42
|
-
})(request, helpers);
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
],
|
|
46
|
-
};
|
|
47
|
-
};
|
|
48
|
-
const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
|