@jsenv/core 39.9.7 → 39.10.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/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)}`;
|
|
@@ -19487,7 +19521,7 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|
|
|
19487
19521
|
let i = 0;
|
|
19488
19522
|
while (i < parts.length) {
|
|
19489
19523
|
const part = parts[i];
|
|
19490
|
-
const href = i === 0 ? "
|
|
19524
|
+
const href = i === 0 ? "/..." : `/${parts.slice(1, i + 1).join("/")}/`;
|
|
19491
19525
|
const text = part;
|
|
19492
19526
|
const isLastPart = i === parts.length - 1;
|
|
19493
19527
|
if (isLastPart) {
|
|
@@ -24497,19 +24531,26 @@ const createBuildFilesService = ({ buildDirectoryUrl, buildMainFilePath }) => {
|
|
|
24497
24531
|
resource: `/${buildMainFilePath}`,
|
|
24498
24532
|
};
|
|
24499
24533
|
}
|
|
24500
|
-
|
|
24501
|
-
|
|
24502
|
-
|
|
24503
|
-
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
|
|
24509
|
-
|
|
24510
|
-
|
|
24534
|
+
const urlObject = new URL(request.resource.slice(1), buildDirectoryUrl);
|
|
24535
|
+
return fetchFileSystem(urlObject, {
|
|
24536
|
+
headers: request.headers,
|
|
24537
|
+
cacheControl: urlIsVersioned
|
|
24538
|
+
? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
|
|
24539
|
+
: "private,max-age=0,must-revalidate",
|
|
24540
|
+
etagEnabled: true,
|
|
24541
|
+
compressionEnabled: !request.pathname.endsWith(".mp4"),
|
|
24542
|
+
rootDirectoryUrl: buildDirectoryUrl,
|
|
24543
|
+
canReadDirectory: true,
|
|
24544
|
+
ENOENTFallback: () => {
|
|
24545
|
+
if (
|
|
24546
|
+
!urlToExtension$1(urlObject) &&
|
|
24547
|
+
!urlToPathname$1(urlObject).endsWith("/")
|
|
24548
|
+
) {
|
|
24549
|
+
return new URL(buildMainFilePath, buildDirectoryUrl);
|
|
24550
|
+
}
|
|
24551
|
+
return null;
|
|
24511
24552
|
},
|
|
24512
|
-
);
|
|
24553
|
+
});
|
|
24513
24554
|
};
|
|
24514
24555
|
};
|
|
24515
24556
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "39.
|
|
3
|
+
"version": "39.10.0",
|
|
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
|
|
|
@@ -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)}`;
|
|
@@ -225,7 +233,7 @@ const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|
|
|
225
233
|
let i = 0;
|
|
226
234
|
while (i < parts.length) {
|
|
227
235
|
const part = parts[i];
|
|
228
|
-
const href = i === 0 ? "
|
|
236
|
+
const href = i === 0 ? "/..." : `/${parts.slice(1, i + 1).join("/")}/`;
|
|
229
237
|
const text = part;
|
|
230
238
|
const isLastPart = i === parts.length - 1;
|
|
231
239
|
if (isLastPart) {
|