@jsenv/server 17.0.0 → 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 +115 -116
- 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 -44
package/dist/jsenv_server.js
CHANGED
|
@@ -5370,10 +5370,24 @@ const createResourcePattern = (pattern) => {
|
|
|
5370
5370
|
});
|
|
5371
5371
|
}
|
|
5372
5372
|
|
|
5373
|
+
const patternEndsWithSlash = pathnamePatternString.endsWith("/");
|
|
5374
|
+
|
|
5373
5375
|
return {
|
|
5374
5376
|
match: (resource) => {
|
|
5375
5377
|
const [pathname, search, hash] = resourceToParts(resource);
|
|
5376
|
-
let
|
|
5378
|
+
let decodedPathname = decodeURIComponent(pathname);
|
|
5379
|
+
let result;
|
|
5380
|
+
if (patternEndsWithSlash && !searchPattern && !hashPattern) {
|
|
5381
|
+
if (!decodedPathname.startsWith(pathnamePatternString)) {
|
|
5382
|
+
return null;
|
|
5383
|
+
}
|
|
5384
|
+
result = {
|
|
5385
|
+
named: {},
|
|
5386
|
+
stars: [decodedPathname.slice(pathnamePatternString.length)],
|
|
5387
|
+
};
|
|
5388
|
+
} else {
|
|
5389
|
+
result = pathnamePattern.match(decodedPathname);
|
|
5390
|
+
}
|
|
5377
5391
|
if (!result) {
|
|
5378
5392
|
return null;
|
|
5379
5393
|
}
|
|
@@ -8014,6 +8028,76 @@ const bufferToEtag = (buffer) => {
|
|
|
8014
8028
|
return `"${length.toString(16)}-${hashBase64StringSubset}"`;
|
|
8015
8029
|
};
|
|
8016
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
|
+
|
|
8017
8101
|
const convertFileSystemErrorToResponseProperties = (error) => {
|
|
8018
8102
|
// https://iojs.org/api/errors.html#errors_eacces_permission_denied
|
|
8019
8103
|
if (isErrorWithCode(error, "EACCES")) {
|
|
@@ -8099,76 +8183,6 @@ const fileSystemPathToUrl = (value) => {
|
|
|
8099
8183
|
return String(pathToFileURL(value));
|
|
8100
8184
|
};
|
|
8101
8185
|
|
|
8102
|
-
const serveDirectory = (
|
|
8103
|
-
url,
|
|
8104
|
-
{ headers = {}, rootDirectoryUrl } = {},
|
|
8105
|
-
) => {
|
|
8106
|
-
url = String(url);
|
|
8107
|
-
url = url[url.length - 1] === "/" ? url : `${url}/`;
|
|
8108
|
-
const directoryContentArray = readdirSync(new URL(url));
|
|
8109
|
-
const responseProducers = {
|
|
8110
|
-
"application/json": () => {
|
|
8111
|
-
const directoryContentJson = JSON.stringify(
|
|
8112
|
-
directoryContentArray,
|
|
8113
|
-
null,
|
|
8114
|
-
" ",
|
|
8115
|
-
);
|
|
8116
|
-
return {
|
|
8117
|
-
status: 200,
|
|
8118
|
-
headers: {
|
|
8119
|
-
"content-type": "application/json",
|
|
8120
|
-
"content-length": directoryContentJson.length,
|
|
8121
|
-
},
|
|
8122
|
-
body: directoryContentJson,
|
|
8123
|
-
};
|
|
8124
|
-
},
|
|
8125
|
-
"text/html": () => {
|
|
8126
|
-
const directoryAsHtml = `<!DOCTYPE html>
|
|
8127
|
-
<html>
|
|
8128
|
-
<head>
|
|
8129
|
-
<title>Directory explorer</title>
|
|
8130
|
-
<meta charset="utf-8" />
|
|
8131
|
-
<link rel="icon" href="data:," />
|
|
8132
|
-
</head>
|
|
8133
|
-
|
|
8134
|
-
<body>
|
|
8135
|
-
<h1>Content of directory ${url}</h1>
|
|
8136
|
-
<ul>
|
|
8137
|
-
${directoryContentArray.map((filename) => {
|
|
8138
|
-
const fileUrlObject = new URL(filename, url);
|
|
8139
|
-
const fileUrl = String(fileUrlObject);
|
|
8140
|
-
let fileUrlRelativeToServer = fileUrl.slice(
|
|
8141
|
-
String(rootDirectoryUrl).length,
|
|
8142
|
-
);
|
|
8143
|
-
if (lstatSync(fileUrlObject).isDirectory()) {
|
|
8144
|
-
fileUrlRelativeToServer += "/";
|
|
8145
|
-
}
|
|
8146
|
-
return `<li>
|
|
8147
|
-
<a href="/${fileUrlRelativeToServer}">${fileUrlRelativeToServer}</a>
|
|
8148
|
-
</li>`;
|
|
8149
|
-
}).join(`
|
|
8150
|
-
`)}
|
|
8151
|
-
</ul>
|
|
8152
|
-
</body>
|
|
8153
|
-
</html>`;
|
|
8154
|
-
|
|
8155
|
-
return {
|
|
8156
|
-
status: 200,
|
|
8157
|
-
headers: {
|
|
8158
|
-
"content-type": "text/html",
|
|
8159
|
-
"content-length": Buffer.byteLength(directoryAsHtml),
|
|
8160
|
-
},
|
|
8161
|
-
body: directoryAsHtml,
|
|
8162
|
-
};
|
|
8163
|
-
},
|
|
8164
|
-
};
|
|
8165
|
-
const bestContentType = pickContentType(
|
|
8166
|
-
{ headers },
|
|
8167
|
-
Object.keys(responseProducers),
|
|
8168
|
-
);
|
|
8169
|
-
return responseProducers[bestContentType || "application/json"]();
|
|
8170
|
-
};
|
|
8171
|
-
|
|
8172
8186
|
/*
|
|
8173
8187
|
* This function returns response properties in a plain object like
|
|
8174
8188
|
* { status: 200, body: "Hello world" }.
|
|
@@ -8187,17 +8201,17 @@ const fetchFileSystem = async (
|
|
|
8187
8201
|
helpers,
|
|
8188
8202
|
directoryUrl,
|
|
8189
8203
|
{
|
|
8204
|
+
mainFileRelativeUrl = null,
|
|
8190
8205
|
etagEnabled = false,
|
|
8191
8206
|
etagMemory = true,
|
|
8192
8207
|
etagMemoryMaxSize = 1000,
|
|
8193
8208
|
mtimeEnabled = false,
|
|
8194
8209
|
compressionEnabled = false,
|
|
8195
8210
|
compressionSizeThreshold = 1024,
|
|
8196
|
-
cacheControl
|
|
8197
|
-
|
|
8198
|
-
: "no-store",
|
|
8211
|
+
cacheControl,
|
|
8212
|
+
isVersioned = defaultIsVersioned,
|
|
8199
8213
|
canReadDirectory = false,
|
|
8200
|
-
ENOENTFallback = () =>
|
|
8214
|
+
ENOENTFallback = () => null,
|
|
8201
8215
|
} = {},
|
|
8202
8216
|
) => {
|
|
8203
8217
|
let directoryUrlString = asUrlString(directoryUrl);
|
|
@@ -8223,7 +8237,11 @@ const fetchFileSystem = async (
|
|
|
8223
8237
|
const filesystemUrl = new URL(resource, directoryUrl);
|
|
8224
8238
|
const urlString = asUrlString(filesystemUrl);
|
|
8225
8239
|
|
|
8226
|
-
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") {
|
|
8227
8245
|
cacheControl = cacheControl(request);
|
|
8228
8246
|
}
|
|
8229
8247
|
|
|
@@ -8259,12 +8277,15 @@ const fetchFileSystem = async (
|
|
|
8259
8277
|
|
|
8260
8278
|
if (fileStat.isDirectory()) {
|
|
8261
8279
|
if (canReadDirectory) {
|
|
8262
|
-
return
|
|
8280
|
+
return fetchDirectory(fileUrl, {
|
|
8263
8281
|
headers: request.headers,
|
|
8264
8282
|
canReadDirectory,
|
|
8265
8283
|
rootDirectoryUrl: directoryUrl,
|
|
8266
8284
|
});
|
|
8267
8285
|
}
|
|
8286
|
+
if (mainFileRelativeUrl && fileUrl === directoryUrlString) {
|
|
8287
|
+
return serveFile(new URL(mainFileRelativeUrl, directoryUrl).href);
|
|
8288
|
+
}
|
|
8268
8289
|
return {
|
|
8269
8290
|
status: 403,
|
|
8270
8291
|
statusText: "not allowed to read directory",
|
|
@@ -8335,6 +8356,15 @@ const fetchFileSystem = async (
|
|
|
8335
8356
|
return composeTwoResponses(intermediateResponse, clientCacheResponse);
|
|
8336
8357
|
} catch (e) {
|
|
8337
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
|
+
}
|
|
8338
8368
|
const fallbackFileUrl = ENOENTFallback();
|
|
8339
8369
|
if (fallbackFileUrl) {
|
|
8340
8370
|
return serveFile(fallbackFileUrl);
|
|
@@ -8606,46 +8636,15 @@ const asUrlString = (value) => {
|
|
|
8606
8636
|
return null;
|
|
8607
8637
|
};
|
|
8608
8638
|
|
|
8609
|
-
const serverPluginStaticFiles = ({ directoryUrl, mainFilePath }) => {
|
|
8610
|
-
return {
|
|
8611
|
-
name: "jsenv:static_files",
|
|
8612
|
-
routes: [
|
|
8613
|
-
{
|
|
8614
|
-
endpoint: "GET *",
|
|
8615
|
-
description: "Serve static files.",
|
|
8616
|
-
fetch: (request, helpers) => {
|
|
8617
|
-
const urlIsVersioned = new URL(request.url).searchParams.has("v");
|
|
8618
|
-
if (mainFilePath && request.resource === "/") {
|
|
8619
|
-
request = {
|
|
8620
|
-
...request,
|
|
8621
|
-
resource: `/${mainFilePath}`,
|
|
8622
|
-
};
|
|
8623
|
-
}
|
|
8624
|
-
const urlObject = new URL(request.resource.slice(1), directoryUrl);
|
|
8625
|
-
return createFileSystemFetch(directoryUrl, {
|
|
8626
|
-
cacheControl: urlIsVersioned
|
|
8627
|
-
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
8628
|
-
: "private,max-age=0,must-revalidate",
|
|
8629
|
-
etagEnabled: true,
|
|
8630
|
-
compressionEnabled: true,
|
|
8631
|
-
canReadDirectory: true,
|
|
8632
|
-
ENOENTFallback: () => {
|
|
8633
|
-
if (
|
|
8634
|
-
!urlToExtension(urlObject) &&
|
|
8635
|
-
!urlToPathname(urlObject).endsWith("/")
|
|
8636
|
-
) {
|
|
8637
|
-
return new URL(mainFilePath, directoryUrl);
|
|
8638
|
-
}
|
|
8639
|
-
return null;
|
|
8640
|
-
},
|
|
8641
|
-
})(request, helpers);
|
|
8642
|
-
},
|
|
8643
|
-
},
|
|
8644
|
-
],
|
|
8645
|
-
};
|
|
8646
|
-
};
|
|
8647
8639
|
const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
|
|
8648
8640
|
|
|
8641
|
+
const defaultIsVersioned = (request) => {
|
|
8642
|
+
if (!request.url) {
|
|
8643
|
+
return false;
|
|
8644
|
+
}
|
|
8645
|
+
return new URL(request.url).searchParams.has("v");
|
|
8646
|
+
};
|
|
8647
|
+
|
|
8649
8648
|
const serverPluginResponseAcceptanceCheck = () => {
|
|
8650
8649
|
return {
|
|
8651
8650
|
name: "jsenv:response_acceptance_check",
|
|
@@ -8735,4 +8734,4 @@ const replaceResource = (resourceBeforeAlias, newValue) => {
|
|
|
8735
8734
|
return resource;
|
|
8736
8735
|
};
|
|
8737
8736
|
|
|
8738
|
-
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,44 +0,0 @@
|
|
|
1
|
-
import { urlToExtension, urlToPathname } from "@jsenv/urls";
|
|
2
|
-
|
|
3
|
-
import { createFileSystemFetch } from "./fetch_filesystem.js";
|
|
4
|
-
|
|
5
|
-
export const serverPluginStaticFiles = ({ directoryUrl, mainFilePath }) => {
|
|
6
|
-
return {
|
|
7
|
-
name: "jsenv:static_files",
|
|
8
|
-
routes: [
|
|
9
|
-
{
|
|
10
|
-
endpoint: "GET *",
|
|
11
|
-
description: "Serve static files.",
|
|
12
|
-
fetch: (request, helpers) => {
|
|
13
|
-
const urlIsVersioned = new URL(request.url).searchParams.has("v");
|
|
14
|
-
if (mainFilePath && request.resource === "/") {
|
|
15
|
-
request = {
|
|
16
|
-
...request,
|
|
17
|
-
resource: `/${mainFilePath}`,
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
const urlObject = new URL(request.resource.slice(1), directoryUrl);
|
|
21
|
-
return createFileSystemFetch(directoryUrl, {
|
|
22
|
-
cacheControl: urlIsVersioned
|
|
23
|
-
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
24
|
-
: "private,max-age=0,must-revalidate",
|
|
25
|
-
etagEnabled: true,
|
|
26
|
-
compressionEnabled: true,
|
|
27
|
-
rootDirectoryUrl: directoryUrl,
|
|
28
|
-
canReadDirectory: true,
|
|
29
|
-
ENOENTFallback: () => {
|
|
30
|
-
if (
|
|
31
|
-
!urlToExtension(urlObject) &&
|
|
32
|
-
!urlToPathname(urlObject).endsWith("/")
|
|
33
|
-
) {
|
|
34
|
-
return new URL(mainFilePath, directoryUrl);
|
|
35
|
-
}
|
|
36
|
-
return null;
|
|
37
|
-
},
|
|
38
|
-
})(request, helpers);
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
};
|
|
43
|
-
};
|
|
44
|
-
const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
|