@jsenv/core 39.9.7 → 39.10.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/html/directory.html +1 -8
- package/dist/html/html_404_and_ancestor_dir.html +1 -8
- package/dist/jsenv_core.js +179 -122
- package/package.json +2 -2
- package/src/build/start_build_server.js +20 -12
- package/src/plugins/protocol_file/client/assets/directory.css +1 -6
- package/src/plugins/protocol_file/jsenv_plugin_fs_redirection.js +8 -0
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +29 -5
package/dist/html/directory.html
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.directory_nav {
|
|
18
|
+
gap: .3em;
|
|
18
19
|
margin: 20px 25px 15px;
|
|
19
20
|
font-size: 16px;
|
|
20
21
|
font-weight: bold;
|
|
@@ -26,14 +27,6 @@
|
|
|
26
27
|
position: relative;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
.directory_separator {
|
|
30
|
-
margin: 0 .3em;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.directory_separator:first-child {
|
|
34
|
-
margin-left: 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
30
|
.directory_content {
|
|
38
31
|
border-radius: 3px;
|
|
39
32
|
margin: 10px 15px;
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
.directory_nav {
|
|
18
|
+
gap: .3em;
|
|
18
19
|
margin: 20px 25px 15px;
|
|
19
20
|
font-size: 16px;
|
|
20
21
|
font-weight: bold;
|
|
@@ -26,14 +27,6 @@
|
|
|
26
27
|
position: relative;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
.directory_separator {
|
|
30
|
-
margin: 0 .3em;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.directory_separator:first-child {
|
|
34
|
-
margin-left: 0;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
30
|
.directory_content {
|
|
38
31
|
border-radius: 3px;
|
|
39
32
|
margin: 10px 15px;
|
package/dist/jsenv_core.js
CHANGED
|
@@ -6981,6 +6981,14 @@ const startServer = async ({
|
|
|
6981
6981
|
nodeResponse.end();
|
|
6982
6982
|
return;
|
|
6983
6983
|
}
|
|
6984
|
+
try {
|
|
6985
|
+
// eslint-disable-next-line no-new
|
|
6986
|
+
new URL(nodeRequest.url, "http://example.com/");
|
|
6987
|
+
} catch {
|
|
6988
|
+
nodeResponse.writeHead(400, "Request url is not supported");
|
|
6989
|
+
nodeResponse.end();
|
|
6990
|
+
return;
|
|
6991
|
+
}
|
|
6984
6992
|
|
|
6985
6993
|
const receiveRequestOperation = Abort.startOperation();
|
|
6986
6994
|
receiveRequestOperation.addAbortSource((abort) => {
|
|
@@ -8053,6 +8061,7 @@ const fetchFileSystem = async (
|
|
|
8053
8061
|
: "no-store",
|
|
8054
8062
|
canReadDirectory = false,
|
|
8055
8063
|
rootDirectoryUrl, // = `${pathToFileURL(process.cwd())}/`,
|
|
8064
|
+
ENOENTFallback = () => {},
|
|
8056
8065
|
} = {},
|
|
8057
8066
|
) => {
|
|
8058
8067
|
const urlString = asUrlString(filesystemUrl);
|
|
@@ -8110,100 +8119,109 @@ const fetchFileSystem = async (
|
|
|
8110
8119
|
};
|
|
8111
8120
|
}
|
|
8112
8121
|
|
|
8113
|
-
const
|
|
8114
|
-
|
|
8115
|
-
|
|
8116
|
-
|
|
8117
|
-
|
|
8118
|
-
|
|
8119
|
-
|
|
8120
|
-
|
|
8121
|
-
|
|
8122
|
+
const serveFile = async (fileUrl) => {
|
|
8123
|
+
try {
|
|
8124
|
+
const [readStatTiming, fileStat] = timeFunction(
|
|
8125
|
+
"file service>read file stat",
|
|
8126
|
+
() => statSync(new URL(fileUrl)),
|
|
8127
|
+
);
|
|
8128
|
+
if (fileStat.isDirectory()) {
|
|
8129
|
+
if (canReadDirectory) {
|
|
8130
|
+
return serveDirectory(fileUrl, {
|
|
8131
|
+
headers,
|
|
8132
|
+
canReadDirectory,
|
|
8133
|
+
rootDirectoryUrl,
|
|
8134
|
+
});
|
|
8135
|
+
}
|
|
8136
|
+
return {
|
|
8137
|
+
status: 403,
|
|
8138
|
+
statusText: "not allowed to read directory",
|
|
8139
|
+
};
|
|
8140
|
+
}
|
|
8141
|
+
// not a file, give up
|
|
8142
|
+
if (!fileStat.isFile()) {
|
|
8143
|
+
return {
|
|
8144
|
+
status: 404,
|
|
8145
|
+
timing: readStatTiming,
|
|
8146
|
+
};
|
|
8147
|
+
}
|
|
8148
|
+
|
|
8149
|
+
const clientCacheResponse = await getClientCacheResponse({
|
|
8150
|
+
headers,
|
|
8151
|
+
etagEnabled,
|
|
8152
|
+
etagMemory,
|
|
8153
|
+
etagMemoryMaxSize,
|
|
8154
|
+
mtimeEnabled,
|
|
8155
|
+
fileStat,
|
|
8156
|
+
fileUrl,
|
|
8157
|
+
});
|
|
8158
|
+
|
|
8159
|
+
// send 304 (redirect response to client cache)
|
|
8160
|
+
// because the response body does not have to be transmitted
|
|
8161
|
+
if (clientCacheResponse.status === 304) {
|
|
8162
|
+
return composeTwoResponses(
|
|
8163
|
+
{
|
|
8164
|
+
timing: readStatTiming,
|
|
8165
|
+
headers: {
|
|
8166
|
+
...(cacheControl ? { "cache-control": cacheControl } : {}),
|
|
8167
|
+
},
|
|
8168
|
+
},
|
|
8169
|
+
clientCacheResponse,
|
|
8170
|
+
);
|
|
8171
|
+
}
|
|
8172
|
+
|
|
8173
|
+
let response;
|
|
8174
|
+
if (compressionEnabled && fileStat.size >= compressionSizeThreshold) {
|
|
8175
|
+
const compressedResponse = await getCompressedResponse({
|
|
8122
8176
|
headers,
|
|
8123
|
-
|
|
8124
|
-
|
|
8177
|
+
fileUrl,
|
|
8178
|
+
});
|
|
8179
|
+
if (compressedResponse) {
|
|
8180
|
+
response = compressedResponse;
|
|
8181
|
+
}
|
|
8182
|
+
}
|
|
8183
|
+
if (!response) {
|
|
8184
|
+
response = await getRawResponse({
|
|
8185
|
+
fileStat,
|
|
8186
|
+
fileUrl,
|
|
8125
8187
|
});
|
|
8126
8188
|
}
|
|
8127
|
-
return {
|
|
8128
|
-
status: 403,
|
|
8129
|
-
statusText: "not allowed to read directory",
|
|
8130
|
-
};
|
|
8131
|
-
}
|
|
8132
|
-
// not a file, give up
|
|
8133
|
-
if (!sourceStat.isFile()) {
|
|
8134
|
-
return {
|
|
8135
|
-
status: 404,
|
|
8136
|
-
timing: readStatTiming,
|
|
8137
|
-
};
|
|
8138
|
-
}
|
|
8139
|
-
|
|
8140
|
-
const clientCacheResponse = await getClientCacheResponse({
|
|
8141
|
-
headers,
|
|
8142
|
-
etagEnabled,
|
|
8143
|
-
etagMemory,
|
|
8144
|
-
etagMemoryMaxSize,
|
|
8145
|
-
mtimeEnabled,
|
|
8146
|
-
sourceStat,
|
|
8147
|
-
sourceUrl,
|
|
8148
|
-
});
|
|
8149
8189
|
|
|
8150
|
-
|
|
8151
|
-
// because the response body does not have to be transmitted
|
|
8152
|
-
if (clientCacheResponse.status === 304) {
|
|
8153
|
-
return composeTwoResponses(
|
|
8190
|
+
const intermediateResponse = composeTwoResponses(
|
|
8154
8191
|
{
|
|
8155
8192
|
timing: readStatTiming,
|
|
8156
8193
|
headers: {
|
|
8157
8194
|
...(cacheControl ? { "cache-control": cacheControl } : {}),
|
|
8195
|
+
// even if client cache is disabled, server can still
|
|
8196
|
+
// send his own cache control but client should just ignore it
|
|
8197
|
+
// and keep sending cache-control: 'no-store'
|
|
8198
|
+
// if not, uncomment the line below to preserve client
|
|
8199
|
+
// desire to ignore cache
|
|
8200
|
+
// ...(headers["cache-control"] === "no-store" ? { "cache-control": "no-store" } : {}),
|
|
8158
8201
|
},
|
|
8159
8202
|
},
|
|
8160
|
-
|
|
8203
|
+
response,
|
|
8161
8204
|
);
|
|
8162
|
-
|
|
8163
|
-
|
|
8164
|
-
|
|
8165
|
-
|
|
8166
|
-
|
|
8167
|
-
|
|
8168
|
-
|
|
8169
|
-
});
|
|
8170
|
-
if (compressedResponse) {
|
|
8171
|
-
response = compressedResponse;
|
|
8205
|
+
return composeTwoResponses(intermediateResponse, clientCacheResponse);
|
|
8206
|
+
} catch (e) {
|
|
8207
|
+
if (e.code === "ENOENT") {
|
|
8208
|
+
const fallbackFileUrl = ENOENTFallback();
|
|
8209
|
+
if (fallbackFileUrl) {
|
|
8210
|
+
return serveFile(fallbackFileUrl);
|
|
8211
|
+
}
|
|
8172
8212
|
}
|
|
8213
|
+
return composeTwoResponses(
|
|
8214
|
+
{
|
|
8215
|
+
headers: {
|
|
8216
|
+
...(cacheControl ? { "cache-control": cacheControl } : {}),
|
|
8217
|
+
},
|
|
8218
|
+
},
|
|
8219
|
+
convertFileSystemErrorToResponseProperties(e) || {},
|
|
8220
|
+
);
|
|
8173
8221
|
}
|
|
8174
|
-
|
|
8175
|
-
response = await getRawResponse({
|
|
8176
|
-
sourceStat,
|
|
8177
|
-
sourceUrl,
|
|
8178
|
-
});
|
|
8179
|
-
}
|
|
8222
|
+
};
|
|
8180
8223
|
|
|
8181
|
-
|
|
8182
|
-
{
|
|
8183
|
-
timing: readStatTiming,
|
|
8184
|
-
headers: {
|
|
8185
|
-
...(cacheControl ? { "cache-control": cacheControl } : {}),
|
|
8186
|
-
// even if client cache is disabled, server can still
|
|
8187
|
-
// send his own cache control but client should just ignore it
|
|
8188
|
-
// and keep sending cache-control: 'no-store'
|
|
8189
|
-
// if not, uncomment the line below to preserve client
|
|
8190
|
-
// desire to ignore cache
|
|
8191
|
-
// ...(headers["cache-control"] === "no-store" ? { "cache-control": "no-store" } : {}),
|
|
8192
|
-
},
|
|
8193
|
-
},
|
|
8194
|
-
response,
|
|
8195
|
-
);
|
|
8196
|
-
return composeTwoResponses(intermediateResponse, clientCacheResponse);
|
|
8197
|
-
} catch (e) {
|
|
8198
|
-
return composeTwoResponses(
|
|
8199
|
-
{
|
|
8200
|
-
headers: {
|
|
8201
|
-
...(cacheControl ? { "cache-control": cacheControl } : {}),
|
|
8202
|
-
},
|
|
8203
|
-
},
|
|
8204
|
-
convertFileSystemErrorToResponseProperties(e) || {},
|
|
8205
|
-
);
|
|
8206
|
-
}
|
|
8224
|
+
return serveFile(`file://${new URL(urlString).pathname}`);
|
|
8207
8225
|
};
|
|
8208
8226
|
|
|
8209
8227
|
const create500Response = (message) => {
|
|
@@ -8223,8 +8241,8 @@ const getClientCacheResponse = async ({
|
|
|
8223
8241
|
etagMemory,
|
|
8224
8242
|
etagMemoryMaxSize,
|
|
8225
8243
|
mtimeEnabled,
|
|
8226
|
-
|
|
8227
|
-
|
|
8244
|
+
fileStat,
|
|
8245
|
+
fileUrl,
|
|
8228
8246
|
}) => {
|
|
8229
8247
|
// here you might be tempted to add || headers["cache-control"] === "no-cache"
|
|
8230
8248
|
// but no-cache means resource can be cache but must be revalidated (yeah naming is strange)
|
|
@@ -8243,15 +8261,15 @@ const getClientCacheResponse = async ({
|
|
|
8243
8261
|
headers,
|
|
8244
8262
|
etagMemory,
|
|
8245
8263
|
etagMemoryMaxSize,
|
|
8246
|
-
|
|
8247
|
-
|
|
8264
|
+
fileStat,
|
|
8265
|
+
fileUrl,
|
|
8248
8266
|
});
|
|
8249
8267
|
}
|
|
8250
8268
|
|
|
8251
8269
|
if (mtimeEnabled) {
|
|
8252
8270
|
return getMtimeResponse({
|
|
8253
8271
|
headers,
|
|
8254
|
-
|
|
8272
|
+
fileStat,
|
|
8255
8273
|
});
|
|
8256
8274
|
}
|
|
8257
8275
|
|
|
@@ -8262,8 +8280,8 @@ const getEtagResponse = async ({
|
|
|
8262
8280
|
headers,
|
|
8263
8281
|
etagMemory,
|
|
8264
8282
|
etagMemoryMaxSize,
|
|
8265
|
-
|
|
8266
|
-
|
|
8283
|
+
fileUrl,
|
|
8284
|
+
fileStat,
|
|
8267
8285
|
}) => {
|
|
8268
8286
|
const [computeEtagTiming, fileContentEtag] = await timeFunction(
|
|
8269
8287
|
"file service>generate file etag",
|
|
@@ -8271,8 +8289,8 @@ const getEtagResponse = async ({
|
|
|
8271
8289
|
computeEtag({
|
|
8272
8290
|
etagMemory,
|
|
8273
8291
|
etagMemoryMaxSize,
|
|
8274
|
-
|
|
8275
|
-
|
|
8292
|
+
fileUrl,
|
|
8293
|
+
fileStat,
|
|
8276
8294
|
}),
|
|
8277
8295
|
);
|
|
8278
8296
|
|
|
@@ -8300,20 +8318,20 @@ const ETAG_MEMORY_MAP = new Map();
|
|
|
8300
8318
|
const computeEtag = async ({
|
|
8301
8319
|
etagMemory,
|
|
8302
8320
|
etagMemoryMaxSize,
|
|
8303
|
-
|
|
8304
|
-
|
|
8321
|
+
fileUrl,
|
|
8322
|
+
fileStat,
|
|
8305
8323
|
}) => {
|
|
8306
8324
|
if (etagMemory) {
|
|
8307
|
-
const etagMemoryEntry = ETAG_MEMORY_MAP.get(
|
|
8325
|
+
const etagMemoryEntry = ETAG_MEMORY_MAP.get(fileUrl);
|
|
8308
8326
|
if (
|
|
8309
8327
|
etagMemoryEntry &&
|
|
8310
|
-
fileStatAreTheSame(etagMemoryEntry.
|
|
8328
|
+
fileStatAreTheSame(etagMemoryEntry.fileStat, fileStat)
|
|
8311
8329
|
) {
|
|
8312
8330
|
return etagMemoryEntry.eTag;
|
|
8313
8331
|
}
|
|
8314
8332
|
}
|
|
8315
8333
|
const fileContentAsBuffer = await new Promise((resolve, reject) => {
|
|
8316
|
-
readFile(new URL(
|
|
8334
|
+
readFile(new URL(fileUrl), (error, buffer) => {
|
|
8317
8335
|
if (error) {
|
|
8318
8336
|
reject(error);
|
|
8319
8337
|
} else {
|
|
@@ -8327,7 +8345,7 @@ const computeEtag = async ({
|
|
|
8327
8345
|
const firstKey = Array.from(ETAG_MEMORY_MAP.keys())[0];
|
|
8328
8346
|
ETAG_MEMORY_MAP.delete(firstKey);
|
|
8329
8347
|
}
|
|
8330
|
-
ETAG_MEMORY_MAP.set(
|
|
8348
|
+
ETAG_MEMORY_MAP.set(fileUrl, { fileStat, eTag });
|
|
8331
8349
|
}
|
|
8332
8350
|
return eTag;
|
|
8333
8351
|
};
|
|
@@ -8352,7 +8370,7 @@ const fileStatKeysToCompare = [
|
|
|
8352
8370
|
"blksize",
|
|
8353
8371
|
];
|
|
8354
8372
|
|
|
8355
|
-
const getMtimeResponse = async ({ headers,
|
|
8373
|
+
const getMtimeResponse = async ({ headers, fileStat }) => {
|
|
8356
8374
|
if ("if-modified-since" in headers) {
|
|
8357
8375
|
let cachedModificationDate;
|
|
8358
8376
|
try {
|
|
@@ -8364,7 +8382,7 @@ const getMtimeResponse = async ({ headers, sourceStat }) => {
|
|
|
8364
8382
|
};
|
|
8365
8383
|
}
|
|
8366
8384
|
|
|
8367
|
-
const actualModificationDate = dateToSecondsPrecision(
|
|
8385
|
+
const actualModificationDate = dateToSecondsPrecision(fileStat.mtime);
|
|
8368
8386
|
if (Number(cachedModificationDate) >= Number(actualModificationDate)) {
|
|
8369
8387
|
return {
|
|
8370
8388
|
status: 304,
|
|
@@ -8375,12 +8393,12 @@ const getMtimeResponse = async ({ headers, sourceStat }) => {
|
|
|
8375
8393
|
return {
|
|
8376
8394
|
status: 200,
|
|
8377
8395
|
headers: {
|
|
8378
|
-
"last-modified": dateToUTCString(
|
|
8396
|
+
"last-modified": dateToUTCString(fileStat.mtime),
|
|
8379
8397
|
},
|
|
8380
8398
|
};
|
|
8381
8399
|
};
|
|
8382
8400
|
|
|
8383
|
-
const getCompressedResponse = async ({
|
|
8401
|
+
const getCompressedResponse = async ({ fileUrl, headers }) => {
|
|
8384
8402
|
const acceptedCompressionFormat = pickContentEncoding(
|
|
8385
8403
|
{ headers },
|
|
8386
8404
|
Object.keys(availableCompressionFormats),
|
|
@@ -8389,7 +8407,7 @@ const getCompressedResponse = async ({ sourceUrl, headers }) => {
|
|
|
8389
8407
|
return null;
|
|
8390
8408
|
}
|
|
8391
8409
|
|
|
8392
|
-
const fileReadableStream = fileUrlToReadableStream(
|
|
8410
|
+
const fileReadableStream = fileUrlToReadableStream(fileUrl);
|
|
8393
8411
|
const body =
|
|
8394
8412
|
await availableCompressionFormats[acceptedCompressionFormat](
|
|
8395
8413
|
fileReadableStream,
|
|
@@ -8398,7 +8416,7 @@ const getCompressedResponse = async ({ sourceUrl, headers }) => {
|
|
|
8398
8416
|
return {
|
|
8399
8417
|
status: 200,
|
|
8400
8418
|
headers: {
|
|
8401
|
-
"content-type": CONTENT_TYPE.fromUrlExtension(
|
|
8419
|
+
"content-type": CONTENT_TYPE.fromUrlExtension(fileUrl),
|
|
8402
8420
|
"content-encoding": acceptedCompressionFormat,
|
|
8403
8421
|
"vary": "accept-encoding",
|
|
8404
8422
|
},
|
|
@@ -8428,14 +8446,14 @@ const availableCompressionFormats = {
|
|
|
8428
8446
|
},
|
|
8429
8447
|
};
|
|
8430
8448
|
|
|
8431
|
-
const getRawResponse = async ({
|
|
8449
|
+
const getRawResponse = async ({ fileUrl, fileStat }) => {
|
|
8432
8450
|
return {
|
|
8433
8451
|
status: 200,
|
|
8434
8452
|
headers: {
|
|
8435
|
-
"content-type": CONTENT_TYPE.fromUrlExtension(
|
|
8436
|
-
"content-length":
|
|
8453
|
+
"content-type": CONTENT_TYPE.fromUrlExtension(fileUrl),
|
|
8454
|
+
"content-length": fileStat.size,
|
|
8437
8455
|
},
|
|
8438
|
-
body: fileUrlToReadableStream(
|
|
8456
|
+
body: fileUrlToReadableStream(fileUrl),
|
|
8439
8457
|
};
|
|
8440
8458
|
};
|
|
8441
8459
|
|
|
@@ -19180,6 +19198,14 @@ const jsenvPluginFsRedirection = ({
|
|
|
19180
19198
|
if (reference.subtype === "new_url_second_arg") {
|
|
19181
19199
|
return `ignore:${reference.url}`;
|
|
19182
19200
|
}
|
|
19201
|
+
if (reference.specifierPathname.endsWith("/...")) {
|
|
19202
|
+
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
19203
|
+
const directoryUrl = new URL(
|
|
19204
|
+
reference.specifierPathname.replace("/...", "/").slice(1),
|
|
19205
|
+
rootDirectoryUrl,
|
|
19206
|
+
).href;
|
|
19207
|
+
return directoryUrl;
|
|
19208
|
+
}
|
|
19183
19209
|
// ignore "./" on new URL("./")
|
|
19184
19210
|
// if (
|
|
19185
19211
|
// reference.subtype === "new_url_first_arg" &&
|
|
@@ -19331,6 +19357,14 @@ const jsenvPluginProtocolFile = ({
|
|
|
19331
19357
|
if (!generatedUrl.startsWith("file:")) {
|
|
19332
19358
|
return null;
|
|
19333
19359
|
}
|
|
19360
|
+
if (reference.original) {
|
|
19361
|
+
const originalSpecifierPathname =
|
|
19362
|
+
reference.original.specifierPathname;
|
|
19363
|
+
|
|
19364
|
+
if (originalSpecifierPathname.endsWith("/...")) {
|
|
19365
|
+
return originalSpecifierPathname;
|
|
19366
|
+
}
|
|
19367
|
+
}
|
|
19334
19368
|
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
19335
19369
|
if (urlIsInsideOf(generatedUrl, rootDirectoryUrl)) {
|
|
19336
19370
|
const result = `/${urlToRelativeUrl(generatedUrl, rootDirectoryUrl)}`;
|
|
@@ -19483,14 +19517,28 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|
|
|
19483
19517
|
const parts = isDir
|
|
19484
19518
|
? relativeUrlWithRoot.slice(0, -1).split("/")
|
|
19485
19519
|
: relativeUrlWithRoot.split("/");
|
|
19520
|
+
const items = [];
|
|
19521
|
+
items.push({
|
|
19522
|
+
href: "/",
|
|
19523
|
+
text: "/",
|
|
19524
|
+
});
|
|
19486
19525
|
let dirPartsHtml = "";
|
|
19487
19526
|
let i = 0;
|
|
19488
19527
|
while (i < parts.length) {
|
|
19489
19528
|
const part = parts[i];
|
|
19490
|
-
const href = i === 0 ? "
|
|
19529
|
+
const href = i === 0 ? "/..." : `/${parts.slice(1, i + 1).join("/")}/`;
|
|
19491
19530
|
const text = part;
|
|
19492
19531
|
const isLastPart = i === parts.length - 1;
|
|
19493
|
-
|
|
19532
|
+
items.push({
|
|
19533
|
+
href,
|
|
19534
|
+
text,
|
|
19535
|
+
isCurrent: isLastPart,
|
|
19536
|
+
});
|
|
19537
|
+
i++;
|
|
19538
|
+
}
|
|
19539
|
+
i = 0;
|
|
19540
|
+
for (const { href, text, isCurrent } of items) {
|
|
19541
|
+
if (isCurrent) {
|
|
19494
19542
|
dirPartsHtml += `
|
|
19495
19543
|
<span class="directory_nav_item" data-current>
|
|
19496
19544
|
${text}
|
|
@@ -19501,13 +19549,15 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|
|
|
19501
19549
|
<a class="directory_nav_item" href="${href}">
|
|
19502
19550
|
${text}
|
|
19503
19551
|
</a>`;
|
|
19504
|
-
|
|
19505
|
-
|
|
19552
|
+
if (i > 0) {
|
|
19553
|
+
dirPartsHtml += `
|
|
19554
|
+
<span class="directory_separator">/</span>`;
|
|
19555
|
+
}
|
|
19506
19556
|
i++;
|
|
19507
19557
|
}
|
|
19508
19558
|
if (isDir) {
|
|
19509
19559
|
dirPartsHtml += `
|
|
19510
|
-
|
|
19560
|
+
<span class="directory_separator">/</span>`;
|
|
19511
19561
|
}
|
|
19512
19562
|
return dirPartsHtml;
|
|
19513
19563
|
};
|
|
@@ -24497,19 +24547,26 @@ const createBuildFilesService = ({ buildDirectoryUrl, buildMainFilePath }) => {
|
|
|
24497
24547
|
resource: `/${buildMainFilePath}`,
|
|
24498
24548
|
};
|
|
24499
24549
|
}
|
|
24500
|
-
|
|
24501
|
-
|
|
24502
|
-
|
|
24503
|
-
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
|
|
24509
|
-
|
|
24510
|
-
|
|
24550
|
+
const urlObject = new URL(request.resource.slice(1), buildDirectoryUrl);
|
|
24551
|
+
return fetchFileSystem(urlObject, {
|
|
24552
|
+
headers: request.headers,
|
|
24553
|
+
cacheControl: urlIsVersioned
|
|
24554
|
+
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
24555
|
+
: "private,max-age=0,must-revalidate",
|
|
24556
|
+
etagEnabled: true,
|
|
24557
|
+
compressionEnabled: !request.pathname.endsWith(".mp4"),
|
|
24558
|
+
rootDirectoryUrl: buildDirectoryUrl,
|
|
24559
|
+
canReadDirectory: true,
|
|
24560
|
+
ENOENTFallback: () => {
|
|
24561
|
+
if (
|
|
24562
|
+
!urlToExtension$1(urlObject) &&
|
|
24563
|
+
!urlToPathname$1(urlObject).endsWith("/")
|
|
24564
|
+
) {
|
|
24565
|
+
return new URL(buildMainFilePath, buildDirectoryUrl);
|
|
24566
|
+
}
|
|
24567
|
+
return null;
|
|
24511
24568
|
},
|
|
24512
|
-
);
|
|
24569
|
+
});
|
|
24513
24570
|
};
|
|
24514
24571
|
};
|
|
24515
24572
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "39.
|
|
3
|
+
"version": "39.10.1",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"@jsenv/plugin-supervisor": "1.6.3",
|
|
82
82
|
"@jsenv/plugin-transpilation": "1.4.92",
|
|
83
83
|
"@jsenv/runtime-compat": "1.3.1",
|
|
84
|
-
"@jsenv/server": "15.
|
|
84
|
+
"@jsenv/server": "15.4.0",
|
|
85
85
|
"@jsenv/sourcemap": "1.2.30",
|
|
86
86
|
"@jsenv/url-meta": "8.5.2",
|
|
87
87
|
"@jsenv/urls": "2.6.0",
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
jsenvServiceErrorHandler,
|
|
24
24
|
startServer,
|
|
25
25
|
} from "@jsenv/server";
|
|
26
|
+
import { urlToExtension, urlToPathname } from "@jsenv/urls";
|
|
26
27
|
import { existsSync } from "node:fs";
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -168,19 +169,26 @@ const createBuildFilesService = ({ buildDirectoryUrl, buildMainFilePath }) => {
|
|
|
168
169
|
resource: `/${buildMainFilePath}`,
|
|
169
170
|
};
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
172
|
+
const urlObject = new URL(request.resource.slice(1), buildDirectoryUrl);
|
|
173
|
+
return fetchFileSystem(urlObject, {
|
|
174
|
+
headers: request.headers,
|
|
175
|
+
cacheControl: urlIsVersioned
|
|
176
|
+
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
177
|
+
: "private,max-age=0,must-revalidate",
|
|
178
|
+
etagEnabled: true,
|
|
179
|
+
compressionEnabled: !request.pathname.endsWith(".mp4"),
|
|
180
|
+
rootDirectoryUrl: buildDirectoryUrl,
|
|
181
|
+
canReadDirectory: true,
|
|
182
|
+
ENOENTFallback: () => {
|
|
183
|
+
if (
|
|
184
|
+
!urlToExtension(urlObject) &&
|
|
185
|
+
!urlToPathname(urlObject).endsWith("/")
|
|
186
|
+
) {
|
|
187
|
+
return new URL(buildMainFilePath, buildDirectoryUrl);
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
182
190
|
},
|
|
183
|
-
);
|
|
191
|
+
});
|
|
184
192
|
};
|
|
185
193
|
};
|
|
186
194
|
|
|
@@ -20,17 +20,12 @@ button {
|
|
|
20
20
|
font-weight: bold;
|
|
21
21
|
margin: 20px 25px 15px 25px;
|
|
22
22
|
display: flex;
|
|
23
|
+
gap: 0.3em;
|
|
23
24
|
}
|
|
24
25
|
.directory_nav_item {
|
|
25
26
|
text-decoration: none;
|
|
26
27
|
position: relative;
|
|
27
28
|
}
|
|
28
|
-
.directory_separator {
|
|
29
|
-
margin: 0 0.3em;
|
|
30
|
-
}
|
|
31
|
-
.directory_separator:first-child {
|
|
32
|
-
margin-left: 0;
|
|
33
|
-
}
|
|
34
29
|
.directory_content {
|
|
35
30
|
margin: 10px 15px 10px 15px;
|
|
36
31
|
list-style-type: none;
|
|
@@ -30,6 +30,14 @@ export const jsenvPluginFsRedirection = ({
|
|
|
30
30
|
if (reference.subtype === "new_url_second_arg") {
|
|
31
31
|
return `ignore:${reference.url}`;
|
|
32
32
|
}
|
|
33
|
+
if (reference.specifierPathname.endsWith("/...")) {
|
|
34
|
+
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
35
|
+
const directoryUrl = new URL(
|
|
36
|
+
reference.specifierPathname.replace("/...", "/").slice(1),
|
|
37
|
+
rootDirectoryUrl,
|
|
38
|
+
).href;
|
|
39
|
+
return directoryUrl;
|
|
40
|
+
}
|
|
33
41
|
// ignore "./" on new URL("./")
|
|
34
42
|
// if (
|
|
35
43
|
// reference.subtype === "new_url_first_arg" &&
|
|
@@ -69,6 +69,14 @@ export const jsenvPluginProtocolFile = ({
|
|
|
69
69
|
if (!generatedUrl.startsWith("file:")) {
|
|
70
70
|
return null;
|
|
71
71
|
}
|
|
72
|
+
if (reference.original) {
|
|
73
|
+
const originalSpecifierPathname =
|
|
74
|
+
reference.original.specifierPathname;
|
|
75
|
+
|
|
76
|
+
if (originalSpecifierPathname.endsWith("/...")) {
|
|
77
|
+
return originalSpecifierPathname;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
72
80
|
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
73
81
|
if (urlIsInsideOf(generatedUrl, rootDirectoryUrl)) {
|
|
74
82
|
const result = `/${urlToRelativeUrl(generatedUrl, rootDirectoryUrl)}`;
|
|
@@ -221,14 +229,28 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|
|
|
221
229
|
const parts = isDir
|
|
222
230
|
? relativeUrlWithRoot.slice(0, -1).split("/")
|
|
223
231
|
: relativeUrlWithRoot.split("/");
|
|
232
|
+
const items = [];
|
|
233
|
+
items.push({
|
|
234
|
+
href: "/",
|
|
235
|
+
text: "/",
|
|
236
|
+
});
|
|
224
237
|
let dirPartsHtml = "";
|
|
225
238
|
let i = 0;
|
|
226
239
|
while (i < parts.length) {
|
|
227
240
|
const part = parts[i];
|
|
228
|
-
const href = i === 0 ? "
|
|
241
|
+
const href = i === 0 ? "/..." : `/${parts.slice(1, i + 1).join("/")}/`;
|
|
229
242
|
const text = part;
|
|
230
243
|
const isLastPart = i === parts.length - 1;
|
|
231
|
-
|
|
244
|
+
items.push({
|
|
245
|
+
href,
|
|
246
|
+
text,
|
|
247
|
+
isCurrent: isLastPart,
|
|
248
|
+
});
|
|
249
|
+
i++;
|
|
250
|
+
}
|
|
251
|
+
i = 0;
|
|
252
|
+
for (const { href, text, isCurrent } of items) {
|
|
253
|
+
if (isCurrent) {
|
|
232
254
|
dirPartsHtml += `
|
|
233
255
|
<span class="directory_nav_item" data-current>
|
|
234
256
|
${text}
|
|
@@ -239,13 +261,15 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|
|
|
239
261
|
<a class="directory_nav_item" href="${href}">
|
|
240
262
|
${text}
|
|
241
263
|
</a>`;
|
|
242
|
-
|
|
243
|
-
|
|
264
|
+
if (i > 0) {
|
|
265
|
+
dirPartsHtml += `
|
|
266
|
+
<span class="directory_separator">/</span>`;
|
|
267
|
+
}
|
|
244
268
|
i++;
|
|
245
269
|
}
|
|
246
270
|
if (isDir) {
|
|
247
271
|
dirPartsHtml += `
|
|
248
|
-
|
|
272
|
+
<span class="directory_separator">/</span>`;
|
|
249
273
|
}
|
|
250
274
|
return dirPartsHtml;
|
|
251
275
|
};
|