@jsenv/core 39.7.5 → 39.7.7
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/{html_404_and_parent_dir.html → html_404_and_ancestor_dir.html} +3 -3
- package/dist/jsenv_core.js +301 -75
- package/package.json +1 -1
- package/src/kitchen/url_graph/url_info_transformations.js +13 -2
- package/src/plugins/protocol_file/client/{html_404_and_parent_dir.html → html_404_and_ancestor_dir.html} +3 -3
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +79 -69
|
@@ -195,10 +195,10 @@
|
|
|
195
195
|
No entry on the filesystem for <code>/${fileRelativeUrl}</code> (at
|
|
196
196
|
${fileUrl})
|
|
197
197
|
<br>
|
|
198
|
-
Content of the
|
|
198
|
+
Content of the ancestor directory is listed below:
|
|
199
199
|
</span>
|
|
200
200
|
</p>
|
|
201
|
-
<h1 class="directory_nav">${
|
|
202
|
-
${
|
|
201
|
+
<h1 class="directory_nav">${ancestorDirectoryNav}</h1>
|
|
202
|
+
${ancestorDirectoryContent}
|
|
203
203
|
</body>
|
|
204
204
|
</html>
|
package/dist/jsenv_core.js
CHANGED
|
@@ -3,7 +3,7 @@ import os, { networkInterfaces } from "node:os";
|
|
|
3
3
|
import tty from "node:tty";
|
|
4
4
|
import stringWidth from "string-width";
|
|
5
5
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
6
|
-
import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFileSync, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir,
|
|
6
|
+
import { readdir, chmod, stat, lstat, chmodSync, statSync, lstatSync, promises, readFileSync, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, unlinkSync, readdirSync, rmdirSync, watch, createReadStream, readFile, existsSync, realpathSync } from "node:fs";
|
|
7
7
|
import { extname } from "node:path";
|
|
8
8
|
import crypto, { createHash } from "node:crypto";
|
|
9
9
|
import cluster from "node:cluster";
|
|
@@ -3668,7 +3668,7 @@ const removeEntry = async (
|
|
|
3668
3668
|
sourceStats.isCharacterDevice() ||
|
|
3669
3669
|
sourceStats.isBlockDevice()
|
|
3670
3670
|
) {
|
|
3671
|
-
await removeNonDirectory(
|
|
3671
|
+
await removeNonDirectory$1(
|
|
3672
3672
|
sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl,
|
|
3673
3673
|
{
|
|
3674
3674
|
maxRetries,
|
|
@@ -3689,7 +3689,7 @@ const removeEntry = async (
|
|
|
3689
3689
|
}
|
|
3690
3690
|
};
|
|
3691
3691
|
|
|
3692
|
-
const removeNonDirectory = (sourceUrl, { maxRetries, retryDelay }) => {
|
|
3692
|
+
const removeNonDirectory$1 = (sourceUrl, { maxRetries, retryDelay }) => {
|
|
3693
3693
|
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
3694
3694
|
|
|
3695
3695
|
let retryCount = 0;
|
|
@@ -3828,11 +3828,11 @@ const removeDirectory = async (
|
|
|
3828
3828
|
};
|
|
3829
3829
|
|
|
3830
3830
|
const visitFile = async (fileUrl) => {
|
|
3831
|
-
await removeNonDirectory(fileUrl, { maxRetries, retryDelay });
|
|
3831
|
+
await removeNonDirectory$1(fileUrl, { maxRetries, retryDelay });
|
|
3832
3832
|
};
|
|
3833
3833
|
|
|
3834
3834
|
const visitSymbolicLink = async (symbolicLinkUrl) => {
|
|
3835
|
-
await removeNonDirectory(symbolicLinkUrl, { maxRetries, retryDelay });
|
|
3835
|
+
await removeNonDirectory$1(symbolicLinkUrl, { maxRetries, retryDelay });
|
|
3836
3836
|
};
|
|
3837
3837
|
|
|
3838
3838
|
try {
|
|
@@ -3877,6 +3877,204 @@ const removeDirectoryNaive = (
|
|
|
3877
3877
|
|
|
3878
3878
|
process.platform === "win32";
|
|
3879
3879
|
|
|
3880
|
+
const removeEntrySync = (
|
|
3881
|
+
source,
|
|
3882
|
+
{
|
|
3883
|
+
allowUseless = false,
|
|
3884
|
+
recursive = false,
|
|
3885
|
+
maxRetries = 3,
|
|
3886
|
+
retryDelay = 100,
|
|
3887
|
+
onlyContent = false,
|
|
3888
|
+
} = {},
|
|
3889
|
+
) => {
|
|
3890
|
+
const sourceUrl = assertAndNormalizeFileUrl(source);
|
|
3891
|
+
const sourceStats = readEntryStatSync(sourceUrl, {
|
|
3892
|
+
nullIfNotFound: true,
|
|
3893
|
+
followLink: false,
|
|
3894
|
+
});
|
|
3895
|
+
if (!sourceStats) {
|
|
3896
|
+
if (allowUseless) {
|
|
3897
|
+
return;
|
|
3898
|
+
}
|
|
3899
|
+
throw new Error(`nothing to remove at ${urlToFileSystemPath(sourceUrl)}`);
|
|
3900
|
+
}
|
|
3901
|
+
|
|
3902
|
+
// https://nodejs.org/dist/latest-v13.x/docs/api/fs.html#fs_class_fs_stats
|
|
3903
|
+
// FIFO and socket are ignored, not sure what they are exactly and what to do with them
|
|
3904
|
+
// other libraries ignore them, let's do the same.
|
|
3905
|
+
if (
|
|
3906
|
+
sourceStats.isFile() ||
|
|
3907
|
+
sourceStats.isSymbolicLink() ||
|
|
3908
|
+
sourceStats.isCharacterDevice() ||
|
|
3909
|
+
sourceStats.isBlockDevice()
|
|
3910
|
+
) {
|
|
3911
|
+
removeNonDirectory(
|
|
3912
|
+
sourceUrl.endsWith("/") ? sourceUrl.slice(0, -1) : sourceUrl);
|
|
3913
|
+
} else if (sourceStats.isDirectory()) {
|
|
3914
|
+
const directoryUrl = ensurePathnameTrailingSlash(sourceUrl);
|
|
3915
|
+
removeDirectorySync$1(directoryUrl, {
|
|
3916
|
+
recursive,
|
|
3917
|
+
maxRetries,
|
|
3918
|
+
retryDelay,
|
|
3919
|
+
onlyContent,
|
|
3920
|
+
});
|
|
3921
|
+
}
|
|
3922
|
+
};
|
|
3923
|
+
|
|
3924
|
+
const removeNonDirectory = (sourceUrl) => {
|
|
3925
|
+
const sourcePath = urlToFileSystemPath(sourceUrl);
|
|
3926
|
+
const attempt = () => {
|
|
3927
|
+
unlinkSyncNaive(sourcePath);
|
|
3928
|
+
};
|
|
3929
|
+
attempt();
|
|
3930
|
+
};
|
|
3931
|
+
|
|
3932
|
+
const unlinkSyncNaive = (sourcePath, { handleTemporaryError = null } = {}) => {
|
|
3933
|
+
try {
|
|
3934
|
+
unlinkSync(sourcePath);
|
|
3935
|
+
} catch (error) {
|
|
3936
|
+
if (error.code === "ENOENT") {
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
if (
|
|
3940
|
+
handleTemporaryError &&
|
|
3941
|
+
(error.code === "EBUSY" ||
|
|
3942
|
+
error.code === "EMFILE" ||
|
|
3943
|
+
error.code === "ENFILE" ||
|
|
3944
|
+
error.code === "ENOENT")
|
|
3945
|
+
) {
|
|
3946
|
+
handleTemporaryError(error);
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
throw error;
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
|
|
3953
|
+
const removeDirectorySync$1 = (
|
|
3954
|
+
rootDirectoryUrl,
|
|
3955
|
+
{ maxRetries, retryDelay, recursive, onlyContent },
|
|
3956
|
+
) => {
|
|
3957
|
+
const visit = (sourceUrl) => {
|
|
3958
|
+
const sourceStats = readEntryStatSync(sourceUrl, {
|
|
3959
|
+
nullIfNotFound: true,
|
|
3960
|
+
followLink: false,
|
|
3961
|
+
});
|
|
3962
|
+
|
|
3963
|
+
// file/directory not found
|
|
3964
|
+
if (sourceStats === null) {
|
|
3965
|
+
return;
|
|
3966
|
+
}
|
|
3967
|
+
|
|
3968
|
+
if (
|
|
3969
|
+
sourceStats.isFile() ||
|
|
3970
|
+
sourceStats.isCharacterDevice() ||
|
|
3971
|
+
sourceStats.isBlockDevice()
|
|
3972
|
+
) {
|
|
3973
|
+
visitFile(sourceUrl);
|
|
3974
|
+
} else if (sourceStats.isSymbolicLink()) {
|
|
3975
|
+
visitSymbolicLink(sourceUrl);
|
|
3976
|
+
} else if (sourceStats.isDirectory()) {
|
|
3977
|
+
visitDirectory(`${sourceUrl}/`);
|
|
3978
|
+
}
|
|
3979
|
+
};
|
|
3980
|
+
|
|
3981
|
+
const visitDirectory = (directoryUrl) => {
|
|
3982
|
+
const directoryPath = urlToFileSystemPath(directoryUrl);
|
|
3983
|
+
const optionsFromRecursive = recursive
|
|
3984
|
+
? {
|
|
3985
|
+
handleNotEmptyError: () => {
|
|
3986
|
+
removeDirectoryContent(directoryUrl);
|
|
3987
|
+
visitDirectory(directoryUrl);
|
|
3988
|
+
},
|
|
3989
|
+
}
|
|
3990
|
+
: {};
|
|
3991
|
+
removeDirectorySyncNaive(directoryPath, {
|
|
3992
|
+
...optionsFromRecursive,
|
|
3993
|
+
// Workaround for https://github.com/joyent/node/issues/4337
|
|
3994
|
+
...(process.platform === "win32"
|
|
3995
|
+
? {
|
|
3996
|
+
handlePermissionError: (error) => {
|
|
3997
|
+
console.error(
|
|
3998
|
+
`trying to fix windows EPERM after readir on ${directoryPath}`,
|
|
3999
|
+
);
|
|
4000
|
+
|
|
4001
|
+
let openOrCloseError;
|
|
4002
|
+
try {
|
|
4003
|
+
const fd = openSync(directoryPath);
|
|
4004
|
+
closeSync(fd);
|
|
4005
|
+
} catch (e) {
|
|
4006
|
+
openOrCloseError = e;
|
|
4007
|
+
}
|
|
4008
|
+
|
|
4009
|
+
if (openOrCloseError) {
|
|
4010
|
+
if (openOrCloseError.code === "ENOENT") {
|
|
4011
|
+
return;
|
|
4012
|
+
}
|
|
4013
|
+
console.error(
|
|
4014
|
+
`error while trying to fix windows EPERM after readir on ${directoryPath}: ${openOrCloseError.stack}`,
|
|
4015
|
+
);
|
|
4016
|
+
throw error;
|
|
4017
|
+
}
|
|
4018
|
+
removeDirectorySyncNaive(directoryPath, {
|
|
4019
|
+
...optionsFromRecursive,
|
|
4020
|
+
});
|
|
4021
|
+
},
|
|
4022
|
+
}
|
|
4023
|
+
: {}),
|
|
4024
|
+
});
|
|
4025
|
+
};
|
|
4026
|
+
|
|
4027
|
+
const removeDirectoryContent = (directoryUrl) => {
|
|
4028
|
+
const entryNames = readdirSync(new URL(directoryUrl));
|
|
4029
|
+
for (const entryName of entryNames) {
|
|
4030
|
+
const url = resolveUrl$1(entryName, directoryUrl);
|
|
4031
|
+
visit(url);
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
|
|
4035
|
+
const visitFile = (fileUrl) => {
|
|
4036
|
+
removeNonDirectory(fileUrl);
|
|
4037
|
+
};
|
|
4038
|
+
|
|
4039
|
+
const visitSymbolicLink = (symbolicLinkUrl) => {
|
|
4040
|
+
removeNonDirectory(symbolicLinkUrl);
|
|
4041
|
+
};
|
|
4042
|
+
|
|
4043
|
+
if (onlyContent) {
|
|
4044
|
+
removeDirectoryContent(rootDirectoryUrl);
|
|
4045
|
+
} else {
|
|
4046
|
+
visitDirectory(rootDirectoryUrl);
|
|
4047
|
+
}
|
|
4048
|
+
};
|
|
4049
|
+
|
|
4050
|
+
const removeDirectorySyncNaive = (
|
|
4051
|
+
directoryPath,
|
|
4052
|
+
{ handleNotEmptyError = null, handlePermissionError = null } = {},
|
|
4053
|
+
) => {
|
|
4054
|
+
try {
|
|
4055
|
+
rmdirSync(directoryPath);
|
|
4056
|
+
} catch (error) {
|
|
4057
|
+
if (handlePermissionError && error.code === "EPERM") {
|
|
4058
|
+
handlePermissionError(error);
|
|
4059
|
+
return;
|
|
4060
|
+
}
|
|
4061
|
+
if (error.code === "ENOENT") {
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
if (
|
|
4065
|
+
handleNotEmptyError &&
|
|
4066
|
+
// linux os
|
|
4067
|
+
(error.code === "ENOTEMPTY" ||
|
|
4068
|
+
// SunOS
|
|
4069
|
+
error.code === "EEXIST")
|
|
4070
|
+
) {
|
|
4071
|
+
handleNotEmptyError(error);
|
|
4072
|
+
return;
|
|
4073
|
+
}
|
|
4074
|
+
throw error;
|
|
4075
|
+
}
|
|
4076
|
+
};
|
|
4077
|
+
|
|
3880
4078
|
process.platform === "win32";
|
|
3881
4079
|
|
|
3882
4080
|
const ensureEmptyDirectory = async (source) => {
|
|
@@ -3906,6 +4104,13 @@ const ensureEmptyDirectory = async (source) => {
|
|
|
3906
4104
|
);
|
|
3907
4105
|
};
|
|
3908
4106
|
|
|
4107
|
+
const removeDirectorySync = (url, options = {}) => {
|
|
4108
|
+
return removeEntrySync(url, {
|
|
4109
|
+
...options,
|
|
4110
|
+
recursive: true,
|
|
4111
|
+
});
|
|
4112
|
+
};
|
|
4113
|
+
|
|
3909
4114
|
const callOnceIdlePerFile = (callback, idleMs) => {
|
|
3910
4115
|
const timeoutIdMap = new Map();
|
|
3911
4116
|
return (fileEvent) => {
|
|
@@ -14163,7 +14368,18 @@ const createUrlInfoTransformer = ({
|
|
|
14163
14368
|
contentIsInlined = false;
|
|
14164
14369
|
}
|
|
14165
14370
|
if (!contentIsInlined) {
|
|
14166
|
-
|
|
14371
|
+
try {
|
|
14372
|
+
writeFileSync(new URL(generatedUrl), urlInfo.content);
|
|
14373
|
+
} catch (e) {
|
|
14374
|
+
if (e.code === "EISDIR") {
|
|
14375
|
+
// happens when directory existed but got delete
|
|
14376
|
+
// we can safely remove that directory and write the new file
|
|
14377
|
+
removeDirectorySync(new URL(generatedUrl));
|
|
14378
|
+
writeFileSync(new URL(generatedUrl), urlInfo.content);
|
|
14379
|
+
} else {
|
|
14380
|
+
throw e;
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14167
14383
|
}
|
|
14168
14384
|
const { sourcemapGeneratedUrl, sourcemapReference } = urlInfo;
|
|
14169
14385
|
if (sourcemapGeneratedUrl && sourcemapReference) {
|
|
@@ -18890,8 +19106,8 @@ const resolveSymlink = (fileUrl) => {
|
|
|
18890
19106
|
return realUrlObject.href;
|
|
18891
19107
|
};
|
|
18892
19108
|
|
|
18893
|
-
const
|
|
18894
|
-
"./html/
|
|
19109
|
+
const html404AndAncestorDirFileUrl = new URL(
|
|
19110
|
+
"./html/html_404_and_ancestor_dir.html",
|
|
18895
19111
|
import.meta.url,
|
|
18896
19112
|
);
|
|
18897
19113
|
const htmlFileUrlForDirectory = new URL(
|
|
@@ -18959,68 +19175,86 @@ const jsenvPluginProtocolFile = ({
|
|
|
18959
19175
|
if (!urlInfo.url.startsWith("file:")) {
|
|
18960
19176
|
return null;
|
|
18961
19177
|
}
|
|
18962
|
-
const
|
|
18963
|
-
|
|
18964
|
-
|
|
18965
|
-
|
|
18966
|
-
|
|
18967
|
-
|
|
19178
|
+
const generateContent = () => {
|
|
19179
|
+
const urlObject = new URL(urlInfo.url);
|
|
19180
|
+
const { firstReference } = urlInfo;
|
|
19181
|
+
if (firstReference.leadsToADirectory) {
|
|
19182
|
+
const directoryContentArray = readdirSync(urlObject);
|
|
19183
|
+
if (firstReference.type === "filesystem") {
|
|
19184
|
+
const content = JSON.stringify(directoryContentArray, null, " ");
|
|
19185
|
+
return {
|
|
19186
|
+
type: "directory",
|
|
19187
|
+
contentType: "application/json",
|
|
19188
|
+
content,
|
|
19189
|
+
};
|
|
19190
|
+
}
|
|
19191
|
+
const acceptsHtml = urlInfo.context.request
|
|
19192
|
+
? pickContentType(urlInfo.context.request, ["text/html"])
|
|
19193
|
+
: false;
|
|
19194
|
+
if (acceptsHtml) {
|
|
19195
|
+
firstReference.expectedType = "html";
|
|
19196
|
+
const html = generateHtmlForDirectory(
|
|
19197
|
+
urlObject.href,
|
|
19198
|
+
directoryContentArray,
|
|
19199
|
+
urlInfo.context.rootDirectoryUrl,
|
|
19200
|
+
);
|
|
19201
|
+
return {
|
|
19202
|
+
type: "html",
|
|
19203
|
+
contentType: "text/html",
|
|
19204
|
+
content: html,
|
|
19205
|
+
};
|
|
19206
|
+
}
|
|
18968
19207
|
return {
|
|
18969
19208
|
type: "directory",
|
|
18970
19209
|
contentType: "application/json",
|
|
18971
|
-
content,
|
|
18972
|
-
};
|
|
18973
|
-
}
|
|
18974
|
-
const acceptsHtml = urlInfo.context.request
|
|
18975
|
-
? pickContentType(urlInfo.context.request, ["text/html"])
|
|
18976
|
-
: false;
|
|
18977
|
-
if (acceptsHtml) {
|
|
18978
|
-
firstReference.expectedType = "html";
|
|
18979
|
-
const html = generateHtmlForDirectory(
|
|
18980
|
-
urlObject.href,
|
|
18981
|
-
directoryContentArray,
|
|
18982
|
-
urlInfo.context.rootDirectoryUrl,
|
|
18983
|
-
);
|
|
18984
|
-
return {
|
|
18985
|
-
type: "html",
|
|
18986
|
-
contentType: "text/html",
|
|
18987
|
-
content: html,
|
|
19210
|
+
content: JSON.stringify(directoryContentArray, null, " "),
|
|
18988
19211
|
};
|
|
18989
19212
|
}
|
|
19213
|
+
const contentType = CONTENT_TYPE.fromUrlExtension(urlInfo.url);
|
|
19214
|
+
const fileBuffer = readFileSync(urlObject);
|
|
19215
|
+
const content = CONTENT_TYPE.isTextual(contentType)
|
|
19216
|
+
? String(fileBuffer)
|
|
19217
|
+
: fileBuffer;
|
|
18990
19218
|
return {
|
|
18991
|
-
|
|
18992
|
-
contentType
|
|
18993
|
-
|
|
19219
|
+
content,
|
|
19220
|
+
contentType,
|
|
19221
|
+
contentLength: fileBuffer.length,
|
|
18994
19222
|
};
|
|
18995
|
-
}
|
|
18996
|
-
|
|
19223
|
+
};
|
|
19224
|
+
|
|
18997
19225
|
const request = urlInfo.context.request;
|
|
18998
19226
|
if (request && request.headers["sec-fetch-dest"] === "document") {
|
|
18999
19227
|
try {
|
|
19000
|
-
|
|
19001
|
-
const content = CONTENT_TYPE.isTextual(contentType)
|
|
19002
|
-
? String(fileBuffer)
|
|
19003
|
-
: fileBuffer;
|
|
19004
|
-
return {
|
|
19005
|
-
content,
|
|
19006
|
-
contentType,
|
|
19007
|
-
contentLength: fileBuffer.length,
|
|
19008
|
-
};
|
|
19228
|
+
return generateContent();
|
|
19009
19229
|
} catch (e) {
|
|
19010
19230
|
if (e.code !== "ENOENT") {
|
|
19011
19231
|
throw e;
|
|
19012
19232
|
}
|
|
19013
|
-
const
|
|
19014
|
-
|
|
19015
|
-
|
|
19233
|
+
const rootDirectoryUrl = urlInfo.context.rootDirectoryUrl;
|
|
19234
|
+
let firstExistingAncestorDirectoryUrl = new URL("./", urlInfo.url);
|
|
19235
|
+
while (!existsSync(firstExistingAncestorDirectoryUrl)) {
|
|
19236
|
+
firstExistingAncestorDirectoryUrl = new URL(
|
|
19237
|
+
"../",
|
|
19238
|
+
firstExistingAncestorDirectoryUrl,
|
|
19239
|
+
);
|
|
19240
|
+
if (
|
|
19241
|
+
!urlIsInsideOf(
|
|
19242
|
+
firstExistingAncestorDirectoryUrl,
|
|
19243
|
+
rootDirectoryUrl,
|
|
19244
|
+
)
|
|
19245
|
+
) {
|
|
19246
|
+
firstExistingAncestorDirectoryUrl = rootDirectoryUrl;
|
|
19247
|
+
break;
|
|
19248
|
+
}
|
|
19016
19249
|
}
|
|
19017
|
-
|
|
19018
|
-
|
|
19250
|
+
|
|
19251
|
+
const firstExistingAncestorDirectoryContent = readdirSync(
|
|
19252
|
+
new URL(firstExistingAncestorDirectoryUrl),
|
|
19019
19253
|
);
|
|
19020
19254
|
const html = generateHtmlForENOENT(
|
|
19021
19255
|
urlInfo.url,
|
|
19022
|
-
|
|
19023
|
-
|
|
19256
|
+
firstExistingAncestorDirectoryContent,
|
|
19257
|
+
firstExistingAncestorDirectoryUrl,
|
|
19024
19258
|
urlInfo.context.rootDirectoryUrl,
|
|
19025
19259
|
directoryListingUrlMocks,
|
|
19026
19260
|
);
|
|
@@ -19034,15 +19268,7 @@ const jsenvPluginProtocolFile = ({
|
|
|
19034
19268
|
};
|
|
19035
19269
|
}
|
|
19036
19270
|
}
|
|
19037
|
-
|
|
19038
|
-
const content = CONTENT_TYPE.isTextual(contentType)
|
|
19039
|
-
? String(fileBuffer)
|
|
19040
|
-
: fileBuffer;
|
|
19041
|
-
return {
|
|
19042
|
-
content,
|
|
19043
|
-
contentType,
|
|
19044
|
-
contentLength: fileBuffer.length,
|
|
19045
|
-
};
|
|
19271
|
+
return generateContent();
|
|
19046
19272
|
},
|
|
19047
19273
|
},
|
|
19048
19274
|
];
|
|
@@ -19073,17 +19299,17 @@ const generateHtmlForDirectory = (
|
|
|
19073
19299
|
};
|
|
19074
19300
|
const generateHtmlForENOENT = (
|
|
19075
19301
|
url,
|
|
19076
|
-
|
|
19077
|
-
|
|
19302
|
+
ancestorDirectoryContentArray,
|
|
19303
|
+
ancestorDirectoryUrl,
|
|
19078
19304
|
rootDirectoryUrl,
|
|
19079
19305
|
directoryListingUrlMocks,
|
|
19080
19306
|
) => {
|
|
19081
|
-
const
|
|
19082
|
-
readFileSync(
|
|
19307
|
+
const htmlFor404AndAncestorDir = String(
|
|
19308
|
+
readFileSync(html404AndAncestorDirFileUrl),
|
|
19083
19309
|
);
|
|
19084
19310
|
const fileRelativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
|
|
19085
|
-
const
|
|
19086
|
-
|
|
19311
|
+
const ancestorDirectoryRelativeUrl = urlToRelativeUrl(
|
|
19312
|
+
ancestorDirectoryUrl,
|
|
19087
19313
|
rootDirectoryUrl,
|
|
19088
19314
|
);
|
|
19089
19315
|
const replacers = {
|
|
@@ -19091,18 +19317,18 @@ const generateHtmlForENOENT = (
|
|
|
19091
19317
|
? `@jsenv/core/${urlToRelativeUrl(url, jsenvCoreDirectoryUrl)}`
|
|
19092
19318
|
: url,
|
|
19093
19319
|
fileRelativeUrl,
|
|
19094
|
-
|
|
19095
|
-
|
|
19096
|
-
|
|
19097
|
-
generateDirectoryNav(
|
|
19098
|
-
|
|
19320
|
+
ancestorDirectoryUrl,
|
|
19321
|
+
ancestorDirectoryRelativeUrl,
|
|
19322
|
+
ancestorDirectoryNav: () =>
|
|
19323
|
+
generateDirectoryNav(ancestorDirectoryRelativeUrl, rootDirectoryUrl),
|
|
19324
|
+
ancestorDirectoryContent: () =>
|
|
19099
19325
|
generateDirectoryContent(
|
|
19100
|
-
|
|
19101
|
-
|
|
19326
|
+
ancestorDirectoryContentArray,
|
|
19327
|
+
ancestorDirectoryUrl,
|
|
19102
19328
|
rootDirectoryUrl,
|
|
19103
19329
|
),
|
|
19104
19330
|
};
|
|
19105
|
-
const html = replacePlaceholders$1(
|
|
19331
|
+
const html = replacePlaceholders$1(htmlFor404AndAncestorDir, replacers);
|
|
19106
19332
|
return html;
|
|
19107
19333
|
};
|
|
19108
19334
|
const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { writeFileSync } from "@jsenv/filesystem";
|
|
1
|
+
import { removeDirectorySync, writeFileSync } from "@jsenv/filesystem";
|
|
2
2
|
import {
|
|
3
3
|
composeTwoSourcemaps,
|
|
4
4
|
generateSourcemapDataUrl,
|
|
@@ -275,7 +275,18 @@ export const createUrlInfoTransformer = ({
|
|
|
275
275
|
contentIsInlined = false;
|
|
276
276
|
}
|
|
277
277
|
if (!contentIsInlined) {
|
|
278
|
-
|
|
278
|
+
try {
|
|
279
|
+
writeFileSync(new URL(generatedUrl), urlInfo.content);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
if (e.code === "EISDIR") {
|
|
282
|
+
// happens when directory existed but got delete
|
|
283
|
+
// we can safely remove that directory and write the new file
|
|
284
|
+
removeDirectorySync(new URL(generatedUrl));
|
|
285
|
+
writeFileSync(new URL(generatedUrl), urlInfo.content);
|
|
286
|
+
} else {
|
|
287
|
+
throw e;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
279
290
|
}
|
|
280
291
|
const { sourcemapGeneratedUrl, sourcemapReference } = urlInfo;
|
|
281
292
|
if (sourcemapGeneratedUrl && sourcemapReference) {
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
No entry on the filesystem for <code>/${fileRelativeUrl}</code> (at
|
|
46
46
|
${fileUrl})
|
|
47
47
|
<br />
|
|
48
|
-
Content of the
|
|
48
|
+
Content of the ancestor directory is listed below:
|
|
49
49
|
</span>
|
|
50
50
|
</p>
|
|
51
|
-
<h1 class="directory_nav">${
|
|
52
|
-
${
|
|
51
|
+
<h1 class="directory_nav">${ancestorDirectoryNav}</h1>
|
|
52
|
+
${ancestorDirectoryContent}
|
|
53
53
|
</body>
|
|
54
54
|
</html>
|
|
@@ -14,8 +14,8 @@ import { existsSync, lstatSync, readdirSync, readFileSync } from "node:fs";
|
|
|
14
14
|
import { jsenvCoreDirectoryUrl } from "../../jsenv_core_directory_url.js";
|
|
15
15
|
import { jsenvPluginFsRedirection } from "./jsenv_plugin_fs_redirection.js";
|
|
16
16
|
|
|
17
|
-
const
|
|
18
|
-
"./client/
|
|
17
|
+
const html404AndAncestorDirFileUrl = new URL(
|
|
18
|
+
"./client/html_404_and_ancestor_dir.html",
|
|
19
19
|
import.meta.url,
|
|
20
20
|
);
|
|
21
21
|
const htmlFileUrlForDirectory = new URL(
|
|
@@ -83,68 +83,86 @@ export const jsenvPluginProtocolFile = ({
|
|
|
83
83
|
if (!urlInfo.url.startsWith("file:")) {
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
86
|
+
const generateContent = () => {
|
|
87
|
+
const urlObject = new URL(urlInfo.url);
|
|
88
|
+
const { firstReference } = urlInfo;
|
|
89
|
+
if (firstReference.leadsToADirectory) {
|
|
90
|
+
const directoryContentArray = readdirSync(urlObject);
|
|
91
|
+
if (firstReference.type === "filesystem") {
|
|
92
|
+
const content = JSON.stringify(directoryContentArray, null, " ");
|
|
93
|
+
return {
|
|
94
|
+
type: "directory",
|
|
95
|
+
contentType: "application/json",
|
|
96
|
+
content,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
const acceptsHtml = urlInfo.context.request
|
|
100
|
+
? pickContentType(urlInfo.context.request, ["text/html"])
|
|
101
|
+
: false;
|
|
102
|
+
if (acceptsHtml) {
|
|
103
|
+
firstReference.expectedType = "html";
|
|
104
|
+
const html = generateHtmlForDirectory(
|
|
105
|
+
urlObject.href,
|
|
106
|
+
directoryContentArray,
|
|
107
|
+
urlInfo.context.rootDirectoryUrl,
|
|
108
|
+
);
|
|
109
|
+
return {
|
|
110
|
+
type: "html",
|
|
111
|
+
contentType: "text/html",
|
|
112
|
+
content: html,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
92
115
|
return {
|
|
93
116
|
type: "directory",
|
|
94
117
|
contentType: "application/json",
|
|
95
|
-
content,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
const acceptsHtml = urlInfo.context.request
|
|
99
|
-
? pickContentType(urlInfo.context.request, ["text/html"])
|
|
100
|
-
: false;
|
|
101
|
-
if (acceptsHtml) {
|
|
102
|
-
firstReference.expectedType = "html";
|
|
103
|
-
const html = generateHtmlForDirectory(
|
|
104
|
-
urlObject.href,
|
|
105
|
-
directoryContentArray,
|
|
106
|
-
urlInfo.context.rootDirectoryUrl,
|
|
107
|
-
);
|
|
108
|
-
return {
|
|
109
|
-
type: "html",
|
|
110
|
-
contentType: "text/html",
|
|
111
|
-
content: html,
|
|
118
|
+
content: JSON.stringify(directoryContentArray, null, " "),
|
|
112
119
|
};
|
|
113
120
|
}
|
|
121
|
+
const contentType = CONTENT_TYPE.fromUrlExtension(urlInfo.url);
|
|
122
|
+
const fileBuffer = readFileSync(urlObject);
|
|
123
|
+
const content = CONTENT_TYPE.isTextual(contentType)
|
|
124
|
+
? String(fileBuffer)
|
|
125
|
+
: fileBuffer;
|
|
114
126
|
return {
|
|
115
|
-
|
|
116
|
-
contentType
|
|
117
|
-
|
|
127
|
+
content,
|
|
128
|
+
contentType,
|
|
129
|
+
contentLength: fileBuffer.length,
|
|
118
130
|
};
|
|
119
|
-
}
|
|
120
|
-
|
|
131
|
+
};
|
|
132
|
+
|
|
121
133
|
const request = urlInfo.context.request;
|
|
122
134
|
if (request && request.headers["sec-fetch-dest"] === "document") {
|
|
123
135
|
try {
|
|
124
|
-
|
|
125
|
-
const content = CONTENT_TYPE.isTextual(contentType)
|
|
126
|
-
? String(fileBuffer)
|
|
127
|
-
: fileBuffer;
|
|
128
|
-
return {
|
|
129
|
-
content,
|
|
130
|
-
contentType,
|
|
131
|
-
contentLength: fileBuffer.length,
|
|
132
|
-
};
|
|
136
|
+
return generateContent();
|
|
133
137
|
} catch (e) {
|
|
134
138
|
if (e.code !== "ENOENT") {
|
|
135
139
|
throw e;
|
|
136
140
|
}
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
const rootDirectoryUrl = urlInfo.context.rootDirectoryUrl;
|
|
142
|
+
let firstExistingAncestorDirectoryUrl = new URL("./", urlInfo.url);
|
|
143
|
+
while (!existsSync(firstExistingAncestorDirectoryUrl)) {
|
|
144
|
+
firstExistingAncestorDirectoryUrl = new URL(
|
|
145
|
+
"../",
|
|
146
|
+
firstExistingAncestorDirectoryUrl,
|
|
147
|
+
);
|
|
148
|
+
if (
|
|
149
|
+
!urlIsInsideOf(
|
|
150
|
+
firstExistingAncestorDirectoryUrl,
|
|
151
|
+
rootDirectoryUrl,
|
|
152
|
+
)
|
|
153
|
+
) {
|
|
154
|
+
firstExistingAncestorDirectoryUrl = rootDirectoryUrl;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
140
157
|
}
|
|
141
|
-
|
|
142
|
-
|
|
158
|
+
|
|
159
|
+
const firstExistingAncestorDirectoryContent = readdirSync(
|
|
160
|
+
new URL(firstExistingAncestorDirectoryUrl),
|
|
143
161
|
);
|
|
144
162
|
const html = generateHtmlForENOENT(
|
|
145
163
|
urlInfo.url,
|
|
146
|
-
|
|
147
|
-
|
|
164
|
+
firstExistingAncestorDirectoryContent,
|
|
165
|
+
firstExistingAncestorDirectoryUrl,
|
|
148
166
|
urlInfo.context.rootDirectoryUrl,
|
|
149
167
|
directoryListingUrlMocks,
|
|
150
168
|
);
|
|
@@ -158,15 +176,7 @@ export const jsenvPluginProtocolFile = ({
|
|
|
158
176
|
};
|
|
159
177
|
}
|
|
160
178
|
}
|
|
161
|
-
|
|
162
|
-
const content = CONTENT_TYPE.isTextual(contentType)
|
|
163
|
-
? String(fileBuffer)
|
|
164
|
-
: fileBuffer;
|
|
165
|
-
return {
|
|
166
|
-
content,
|
|
167
|
-
contentType,
|
|
168
|
-
contentLength: fileBuffer.length,
|
|
169
|
-
};
|
|
179
|
+
return generateContent();
|
|
170
180
|
},
|
|
171
181
|
},
|
|
172
182
|
];
|
|
@@ -197,17 +207,17 @@ const generateHtmlForDirectory = (
|
|
|
197
207
|
};
|
|
198
208
|
const generateHtmlForENOENT = (
|
|
199
209
|
url,
|
|
200
|
-
|
|
201
|
-
|
|
210
|
+
ancestorDirectoryContentArray,
|
|
211
|
+
ancestorDirectoryUrl,
|
|
202
212
|
rootDirectoryUrl,
|
|
203
213
|
directoryListingUrlMocks,
|
|
204
214
|
) => {
|
|
205
|
-
const
|
|
206
|
-
readFileSync(
|
|
215
|
+
const htmlFor404AndAncestorDir = String(
|
|
216
|
+
readFileSync(html404AndAncestorDirFileUrl),
|
|
207
217
|
);
|
|
208
218
|
const fileRelativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
|
|
209
|
-
const
|
|
210
|
-
|
|
219
|
+
const ancestorDirectoryRelativeUrl = urlToRelativeUrl(
|
|
220
|
+
ancestorDirectoryUrl,
|
|
211
221
|
rootDirectoryUrl,
|
|
212
222
|
);
|
|
213
223
|
const replacers = {
|
|
@@ -215,18 +225,18 @@ const generateHtmlForENOENT = (
|
|
|
215
225
|
? `@jsenv/core/${urlToRelativeUrl(url, jsenvCoreDirectoryUrl)}`
|
|
216
226
|
: url,
|
|
217
227
|
fileRelativeUrl,
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
generateDirectoryNav(
|
|
222
|
-
|
|
228
|
+
ancestorDirectoryUrl,
|
|
229
|
+
ancestorDirectoryRelativeUrl,
|
|
230
|
+
ancestorDirectoryNav: () =>
|
|
231
|
+
generateDirectoryNav(ancestorDirectoryRelativeUrl, rootDirectoryUrl),
|
|
232
|
+
ancestorDirectoryContent: () =>
|
|
223
233
|
generateDirectoryContent(
|
|
224
|
-
|
|
225
|
-
|
|
234
|
+
ancestorDirectoryContentArray,
|
|
235
|
+
ancestorDirectoryUrl,
|
|
226
236
|
rootDirectoryUrl,
|
|
227
237
|
),
|
|
228
238
|
};
|
|
229
|
-
const html = replacePlaceholders(
|
|
239
|
+
const html = replacePlaceholders(htmlFor404AndAncestorDir, replacers);
|
|
230
240
|
return html;
|
|
231
241
|
};
|
|
232
242
|
const generateDirectoryNav = (relativeUrl, rootDirectoryUrl) => {
|