@jsenv/core 39.12.0 → 39.13.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/css/directory_listing.css +211 -0
- package/dist/html/directory_listing.html +18 -0
- package/dist/js/directory_listing.js +240 -0
- package/dist/jsenv_core.js +1057 -793
- package/dist/other/dir.png +0 -0
- package/dist/other/file.png +0 -0
- package/dist/other/home.svg +6 -0
- package/package.json +6 -6
- package/src/build/build.js +7 -7
- package/src/build/build_specifier_manager.js +0 -1
- package/src/dev/start_dev_server.js +39 -49
- package/src/kitchen/kitchen.js +20 -4
- package/src/kitchen/out_directory_url.js +2 -1
- package/src/kitchen/url_graph/references.js +3 -1
- package/src/kitchen/url_graph/url_graph.js +1 -0
- package/src/kitchen/url_graph/url_info_transformations.js +37 -4
- package/src/plugins/inlining/jsenv_plugin_inlining_into_html.js +10 -8
- package/src/plugins/plugin_controller.js +170 -114
- package/src/plugins/plugins.js +5 -4
- package/src/plugins/protocol_file/client/assets/home.svg +5 -5
- package/src/plugins/protocol_file/client/directory_listing.css +190 -0
- package/src/plugins/protocol_file/client/directory_listing.html +18 -0
- package/src/plugins/protocol_file/client/directory_listing.jsx +250 -0
- package/src/plugins/protocol_file/file_and_server_urls_converter.js +32 -0
- package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +398 -0
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +40 -369
- package/src/plugins/protocol_http/jsenv_plugin_protocol_http.js +3 -2
- package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +7 -6
- package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +1 -3
- package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +2 -18
- package/src/plugins/server_events/jsenv_plugin_server_events.js +100 -0
- package/dist/html/directory.html +0 -184
- package/dist/html/html_404_and_ancestor_dir.html +0 -222
- package/src/plugins/protocol_file/client/assets/directory.css +0 -150
- package/src/plugins/protocol_file/client/directory.html +0 -17
- package/src/plugins/protocol_file/client/html_404_and_ancestor_dir.html +0 -54
- package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +0 -37
package/dist/jsenv_core.js
CHANGED
|
@@ -1467,6 +1467,7 @@ const pathnameToExtension$1 = (pathname) => {
|
|
|
1467
1467
|
};
|
|
1468
1468
|
|
|
1469
1469
|
const asUrlWithoutSearch = (url) => {
|
|
1470
|
+
url = String(url);
|
|
1470
1471
|
if (url.includes("?")) {
|
|
1471
1472
|
const urlObject = new URL(url);
|
|
1472
1473
|
urlObject.search = "";
|
|
@@ -1618,6 +1619,15 @@ const setUrlFilename = (url, filename) => {
|
|
|
1618
1619
|
});
|
|
1619
1620
|
};
|
|
1620
1621
|
|
|
1622
|
+
const setUrlBasename = (url, basename) => {
|
|
1623
|
+
return setUrlFilename(url, (filename) => {
|
|
1624
|
+
if (typeof basename === "function") {
|
|
1625
|
+
basename = basename(filenameToBasename(filename));
|
|
1626
|
+
}
|
|
1627
|
+
return `${basename}${urlToExtension$1(url)}`;
|
|
1628
|
+
});
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1621
1631
|
const transformUrlPathname = (url, transformer) => {
|
|
1622
1632
|
if (typeof url === "string") {
|
|
1623
1633
|
const urlObject = new URL(url);
|
|
@@ -5003,6 +5013,17 @@ const fromNodeRequest = (
|
|
|
5003
5013
|
nodeRequest,
|
|
5004
5014
|
{ serverOrigin, signal, requestBodyLifetime },
|
|
5005
5015
|
) => {
|
|
5016
|
+
const handleRequestOperation = Abort.startOperation();
|
|
5017
|
+
if (signal) {
|
|
5018
|
+
handleRequestOperation.addAbortSignal(signal);
|
|
5019
|
+
}
|
|
5020
|
+
handleRequestOperation.addAbortSource((abort) => {
|
|
5021
|
+
nodeRequest.once("close", abort);
|
|
5022
|
+
return () => {
|
|
5023
|
+
nodeRequest.removeListener("close", abort);
|
|
5024
|
+
};
|
|
5025
|
+
});
|
|
5026
|
+
|
|
5006
5027
|
const headers = headersFromObject(nodeRequest.headers);
|
|
5007
5028
|
const body = observableFromNodeStream(nodeRequest, {
|
|
5008
5029
|
readableStreamLifetime: requestBodyLifetime,
|
|
@@ -5024,7 +5045,7 @@ const fromNodeRequest = (
|
|
|
5024
5045
|
}
|
|
5025
5046
|
|
|
5026
5047
|
return Object.freeze({
|
|
5027
|
-
signal,
|
|
5048
|
+
signal: handleRequestOperation.signal,
|
|
5028
5049
|
http2: Boolean(nodeRequest.stream),
|
|
5029
5050
|
origin: requestOrigin,
|
|
5030
5051
|
...getPropertiesFromResource({
|
|
@@ -6924,6 +6945,15 @@ const startServer = async ({
|
|
|
6924
6945
|
status = "stopped";
|
|
6925
6946
|
stoppedResolve(reason);
|
|
6926
6947
|
});
|
|
6948
|
+
let stopAbortSignal;
|
|
6949
|
+
{
|
|
6950
|
+
let stopAbortController = new AbortController();
|
|
6951
|
+
stopCallbackSet.add(() => {
|
|
6952
|
+
stopAbortController.abort();
|
|
6953
|
+
stopAbortController = undefined;
|
|
6954
|
+
});
|
|
6955
|
+
stopAbortSignal = stopAbortController.signal;
|
|
6956
|
+
}
|
|
6927
6957
|
|
|
6928
6958
|
const cancelProcessTeardownRace = raceProcessTeardownEvents(
|
|
6929
6959
|
processTeardownEvents,
|
|
@@ -6991,6 +7021,9 @@ const startServer = async ({
|
|
|
6991
7021
|
}
|
|
6992
7022
|
|
|
6993
7023
|
const receiveRequestOperation = Abort.startOperation();
|
|
7024
|
+
receiveRequestOperation.addAbortSignal(stopAbortSignal);
|
|
7025
|
+
const sendResponseOperation = Abort.startOperation();
|
|
7026
|
+
sendResponseOperation.addAbortSignal(stopAbortSignal);
|
|
6994
7027
|
receiveRequestOperation.addAbortSource((abort) => {
|
|
6995
7028
|
const closeEventCallback = () => {
|
|
6996
7029
|
if (nodeRequest.complete) {
|
|
@@ -7005,19 +7038,11 @@ const startServer = async ({
|
|
|
7005
7038
|
nodeRequest.removeListener("close", closeEventCallback);
|
|
7006
7039
|
};
|
|
7007
7040
|
});
|
|
7008
|
-
receiveRequestOperation.addAbortSource((abort) => {
|
|
7009
|
-
return stopCallbackSet.add(abort);
|
|
7010
|
-
});
|
|
7011
|
-
|
|
7012
|
-
const sendResponseOperation = Abort.startOperation();
|
|
7013
7041
|
sendResponseOperation.addAbortSignal(receiveRequestOperation.signal);
|
|
7014
|
-
sendResponseOperation.addAbortSource((abort) => {
|
|
7015
|
-
return stopCallbackSet.add(abort);
|
|
7016
|
-
});
|
|
7017
7042
|
|
|
7018
7043
|
const request = fromNodeRequest(nodeRequest, {
|
|
7044
|
+
signal: stopAbortSignal,
|
|
7019
7045
|
serverOrigin,
|
|
7020
|
-
signal: receiveRequestOperation.signal,
|
|
7021
7046
|
});
|
|
7022
7047
|
|
|
7023
7048
|
// Handling request is asynchronous, we buffer logs for that request
|
|
@@ -7591,13 +7616,16 @@ const startServer = async ({
|
|
|
7591
7616
|
socket,
|
|
7592
7617
|
head,
|
|
7593
7618
|
async (websocket) => {
|
|
7619
|
+
const websocketAbortController = new AbortController();
|
|
7594
7620
|
websocketClients.add(websocket);
|
|
7621
|
+
websocket.signal = websocketAbortController.signal;
|
|
7595
7622
|
websocket.once("close", () => {
|
|
7596
7623
|
websocketClients.delete(websocket);
|
|
7624
|
+
websocketAbortController.abort();
|
|
7597
7625
|
});
|
|
7598
7626
|
const request = fromNodeRequest(nodeRequest, {
|
|
7627
|
+
signal: stopAbortSignal,
|
|
7599
7628
|
serverOrigin: websocketOrigin,
|
|
7600
|
-
signal: new AbortController().signal,
|
|
7601
7629
|
requestBodyLifetime,
|
|
7602
7630
|
});
|
|
7603
7631
|
serviceController.callAsyncHooksUntil(
|
|
@@ -11711,7 +11739,7 @@ const generateHtmlForSyntaxError = (
|
|
|
11711
11739
|
errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
|
|
11712
11740
|
syntaxError: escapeHtml(htmlErrorContentFrame),
|
|
11713
11741
|
};
|
|
11714
|
-
const html = replacePlaceholders$
|
|
11742
|
+
const html = replacePlaceholders$1(htmlForSyntaxError, replacers);
|
|
11715
11743
|
return html;
|
|
11716
11744
|
};
|
|
11717
11745
|
const escapeHtml = (string) => {
|
|
@@ -11722,7 +11750,7 @@ const escapeHtml = (string) => {
|
|
|
11722
11750
|
.replace(/"/g, """)
|
|
11723
11751
|
.replace(/'/g, "'");
|
|
11724
11752
|
};
|
|
11725
|
-
const replacePlaceholders$
|
|
11753
|
+
const replacePlaceholders$1 = (html, replacers) => {
|
|
11726
11754
|
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
11727
11755
|
const replacer = replacers[name];
|
|
11728
11756
|
if (replacer === undefined) {
|
|
@@ -11738,6 +11766,7 @@ const replacePlaceholders$2 = (html, replacers) => {
|
|
|
11738
11766
|
const HOOK_NAMES = [
|
|
11739
11767
|
"init",
|
|
11740
11768
|
"serve", // is called only during dev/tests
|
|
11769
|
+
"serveWebsocket",
|
|
11741
11770
|
"resolveReference",
|
|
11742
11771
|
"redirectReference",
|
|
11743
11772
|
"transformReferenceSearchParams",
|
|
@@ -11750,6 +11779,7 @@ const HOOK_NAMES = [
|
|
|
11750
11779
|
"cooked",
|
|
11751
11780
|
"augmentResponse", // is called only during dev/tests
|
|
11752
11781
|
"destroy",
|
|
11782
|
+
"effect",
|
|
11753
11783
|
];
|
|
11754
11784
|
|
|
11755
11785
|
const createPluginController = (
|
|
@@ -11763,19 +11793,18 @@ const createPluginController = (
|
|
|
11763
11793
|
return value;
|
|
11764
11794
|
};
|
|
11765
11795
|
|
|
11766
|
-
const
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
//
|
|
11770
|
-
|
|
11771
|
-
|
|
11796
|
+
const pluginCandidates = [];
|
|
11797
|
+
const activeEffectSet = new Set();
|
|
11798
|
+
const activePlugins = [];
|
|
11799
|
+
// precompute a list of hooks per hookName because:
|
|
11800
|
+
// 1. [MAJOR REASON] when debugging, there is less iteration (so much better)
|
|
11801
|
+
// 2. [MINOR REASON] it should increase perf as there is less work to do
|
|
11802
|
+
const hookSetMap = new Map();
|
|
11803
|
+
const addPlugin = (plugin, options) => {
|
|
11772
11804
|
if (Array.isArray(plugin)) {
|
|
11773
|
-
|
|
11774
|
-
|
|
11805
|
+
for (const value of plugin) {
|
|
11806
|
+
addPlugin(value);
|
|
11775
11807
|
}
|
|
11776
|
-
plugin.forEach((plugin) => {
|
|
11777
|
-
addPlugin(plugin, { position });
|
|
11778
|
-
});
|
|
11779
11808
|
return;
|
|
11780
11809
|
}
|
|
11781
11810
|
if (plugin === null || typeof plugin !== "object") {
|
|
@@ -11785,65 +11814,10 @@ const createPluginController = (
|
|
|
11785
11814
|
plugin.name = "anonymous";
|
|
11786
11815
|
}
|
|
11787
11816
|
if (!testAppliesDuring(plugin) || !initPlugin(plugin)) {
|
|
11788
|
-
|
|
11789
|
-
plugin.destroy();
|
|
11790
|
-
}
|
|
11817
|
+
plugin.destroy?.();
|
|
11791
11818
|
return;
|
|
11792
11819
|
}
|
|
11793
|
-
|
|
11794
|
-
for (const key of Object.keys(plugin)) {
|
|
11795
|
-
if (key === "meta") {
|
|
11796
|
-
const value = plugin[key];
|
|
11797
|
-
if (typeof value !== "object" || value === null) {
|
|
11798
|
-
console.warn(`plugin.meta must be an object, got ${value}`);
|
|
11799
|
-
continue;
|
|
11800
|
-
}
|
|
11801
|
-
Object.assign(pluginsMeta, value);
|
|
11802
|
-
// any extension/modification on plugin.meta
|
|
11803
|
-
// won't be taken into account so we freeze object
|
|
11804
|
-
// to throw in case it happen
|
|
11805
|
-
Object.freeze(value);
|
|
11806
|
-
continue;
|
|
11807
|
-
}
|
|
11808
|
-
|
|
11809
|
-
if (
|
|
11810
|
-
key === "name" ||
|
|
11811
|
-
key === "appliesDuring" ||
|
|
11812
|
-
key === "init" ||
|
|
11813
|
-
key === "serverEvents" ||
|
|
11814
|
-
key === "mustStayFirst"
|
|
11815
|
-
) {
|
|
11816
|
-
continue;
|
|
11817
|
-
}
|
|
11818
|
-
const isHook = HOOK_NAMES.includes(key);
|
|
11819
|
-
if (!isHook) {
|
|
11820
|
-
console.warn(`Unexpected "${key}" property on "${plugin.name}" plugin`);
|
|
11821
|
-
continue;
|
|
11822
|
-
}
|
|
11823
|
-
const hookName = key;
|
|
11824
|
-
const hookValue = plugin[hookName];
|
|
11825
|
-
if (hookValue) {
|
|
11826
|
-
const group = hookGroups[hookName] || (hookGroups[hookName] = []);
|
|
11827
|
-
const hook = {
|
|
11828
|
-
plugin,
|
|
11829
|
-
name: hookName,
|
|
11830
|
-
value: hookValue,
|
|
11831
|
-
};
|
|
11832
|
-
if (position === "start") {
|
|
11833
|
-
let i = 0;
|
|
11834
|
-
while (i < group.length) {
|
|
11835
|
-
const before = group[i];
|
|
11836
|
-
if (!before.plugin.mustStayFirst) {
|
|
11837
|
-
break;
|
|
11838
|
-
}
|
|
11839
|
-
i++;
|
|
11840
|
-
}
|
|
11841
|
-
group.splice(i, 0, hook);
|
|
11842
|
-
} else {
|
|
11843
|
-
group.push(hook);
|
|
11844
|
-
}
|
|
11845
|
-
}
|
|
11846
|
-
}
|
|
11820
|
+
pluginCandidates.push(plugin);
|
|
11847
11821
|
};
|
|
11848
11822
|
const testAppliesDuring = (plugin) => {
|
|
11849
11823
|
const { appliesDuring } = plugin;
|
|
@@ -11882,22 +11856,131 @@ const createPluginController = (
|
|
|
11882
11856
|
);
|
|
11883
11857
|
};
|
|
11884
11858
|
const initPlugin = (plugin) => {
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11859
|
+
const { init } = plugin;
|
|
11860
|
+
if (!init) {
|
|
11861
|
+
return true;
|
|
11862
|
+
}
|
|
11863
|
+
const initReturnValue = init(kitchenContext, { plugin });
|
|
11864
|
+
if (initReturnValue === false) {
|
|
11865
|
+
return false;
|
|
11866
|
+
}
|
|
11867
|
+
if (typeof initReturnValue === "function" && !plugin.destroy) {
|
|
11868
|
+
plugin.destroy = initReturnValue;
|
|
11893
11869
|
}
|
|
11894
11870
|
return true;
|
|
11895
11871
|
};
|
|
11896
|
-
const pushPlugin = (
|
|
11897
|
-
|
|
11872
|
+
const pushPlugin = (...args) => {
|
|
11873
|
+
for (const arg of args) {
|
|
11874
|
+
addPlugin(arg);
|
|
11875
|
+
}
|
|
11876
|
+
updateActivePlugins();
|
|
11898
11877
|
};
|
|
11899
|
-
const
|
|
11900
|
-
|
|
11878
|
+
const updateActivePlugins = () => {
|
|
11879
|
+
// construct activePlugins and hooks according
|
|
11880
|
+
// to the one present in candidates and their effects
|
|
11881
|
+
// 1. active plugins is an empty array
|
|
11882
|
+
// 2. all active effects are cleaned-up
|
|
11883
|
+
// 3. all effects are re-activated if still relevant
|
|
11884
|
+
// 4. hooks are precomputed according to plugin order
|
|
11885
|
+
|
|
11886
|
+
// 1.
|
|
11887
|
+
activePlugins.length = 0;
|
|
11888
|
+
// 2.
|
|
11889
|
+
for (const { cleanup } of activeEffectSet) {
|
|
11890
|
+
cleanup();
|
|
11891
|
+
}
|
|
11892
|
+
activeEffectSet.clear();
|
|
11893
|
+
for (const pluginCandidate of pluginCandidates) {
|
|
11894
|
+
const effect = pluginCandidate.effect;
|
|
11895
|
+
if (!effect) {
|
|
11896
|
+
activePlugins.push(pluginCandidate);
|
|
11897
|
+
continue;
|
|
11898
|
+
}
|
|
11899
|
+
}
|
|
11900
|
+
// 3.
|
|
11901
|
+
for (const pluginCandidate of pluginCandidates) {
|
|
11902
|
+
const effect = pluginCandidate.effect;
|
|
11903
|
+
if (!effect) {
|
|
11904
|
+
continue;
|
|
11905
|
+
}
|
|
11906
|
+
const returnValue = effect({
|
|
11907
|
+
kitchenContext,
|
|
11908
|
+
otherPlugins: activePlugins,
|
|
11909
|
+
});
|
|
11910
|
+
if (!returnValue) {
|
|
11911
|
+
continue;
|
|
11912
|
+
}
|
|
11913
|
+
activePlugins.push(pluginCandidate);
|
|
11914
|
+
activeEffectSet.add({
|
|
11915
|
+
plugin: pluginCandidate,
|
|
11916
|
+
cleanup: typeof returnValue === "function" ? returnValue : () => {},
|
|
11917
|
+
});
|
|
11918
|
+
}
|
|
11919
|
+
// 4.
|
|
11920
|
+
activePlugins.sort((a, b) => {
|
|
11921
|
+
return pluginCandidates.indexOf(a) - pluginCandidates.indexOf(b);
|
|
11922
|
+
});
|
|
11923
|
+
hookSetMap.clear();
|
|
11924
|
+
for (const activePlugin of activePlugins) {
|
|
11925
|
+
for (const key of Object.keys(activePlugin)) {
|
|
11926
|
+
if (key === "meta") {
|
|
11927
|
+
const value = activePlugin[key];
|
|
11928
|
+
if (typeof value !== "object" || value === null) {
|
|
11929
|
+
console.warn(`plugin.meta must be an object, got ${value}`);
|
|
11930
|
+
continue;
|
|
11931
|
+
}
|
|
11932
|
+
Object.assign(pluginsMeta, value);
|
|
11933
|
+
// any extension/modification on plugin.meta
|
|
11934
|
+
// won't be taken into account so we freeze object
|
|
11935
|
+
// to throw in case it happen
|
|
11936
|
+
Object.freeze(value);
|
|
11937
|
+
continue;
|
|
11938
|
+
}
|
|
11939
|
+
if (
|
|
11940
|
+
key === "name" ||
|
|
11941
|
+
key === "appliesDuring" ||
|
|
11942
|
+
key === "init" ||
|
|
11943
|
+
key === "serverEvents" ||
|
|
11944
|
+
key === "mustStayFirst" ||
|
|
11945
|
+
key === "effect"
|
|
11946
|
+
) {
|
|
11947
|
+
continue;
|
|
11948
|
+
}
|
|
11949
|
+
const isHook = HOOK_NAMES.includes(key);
|
|
11950
|
+
if (!isHook) {
|
|
11951
|
+
console.warn(
|
|
11952
|
+
`Unexpected "${key}" property on "${activePlugin.name}" plugin`,
|
|
11953
|
+
);
|
|
11954
|
+
continue;
|
|
11955
|
+
}
|
|
11956
|
+
const hookName = key;
|
|
11957
|
+
const hookValue = activePlugin[hookName];
|
|
11958
|
+
if (hookValue) {
|
|
11959
|
+
let hookSet = hookSetMap.get(hookName);
|
|
11960
|
+
if (!hookSet) {
|
|
11961
|
+
hookSet = new Set();
|
|
11962
|
+
hookSetMap.set(hookName, hookSet);
|
|
11963
|
+
}
|
|
11964
|
+
const hook = {
|
|
11965
|
+
plugin: activePlugin,
|
|
11966
|
+
name: hookName,
|
|
11967
|
+
value: hookValue,
|
|
11968
|
+
};
|
|
11969
|
+
// if (position === "start") {
|
|
11970
|
+
// let i = 0;
|
|
11971
|
+
// while (i < group.length) {
|
|
11972
|
+
// const before = group[i];
|
|
11973
|
+
// if (!before.plugin.mustStayFirst) {
|
|
11974
|
+
// break;
|
|
11975
|
+
// }
|
|
11976
|
+
// i++;
|
|
11977
|
+
// }
|
|
11978
|
+
// group.splice(i, 0, hook);
|
|
11979
|
+
// } else {
|
|
11980
|
+
hookSet.add(hook);
|
|
11981
|
+
}
|
|
11982
|
+
}
|
|
11983
|
+
}
|
|
11901
11984
|
};
|
|
11902
11985
|
|
|
11903
11986
|
let lastPluginUsed = null;
|
|
@@ -11950,64 +12033,66 @@ const createPluginController = (
|
|
|
11950
12033
|
};
|
|
11951
12034
|
|
|
11952
12035
|
const callHooks = (hookName, info, callback) => {
|
|
11953
|
-
const
|
|
11954
|
-
if (
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
12036
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12037
|
+
if (!hookSet) {
|
|
12038
|
+
return;
|
|
12039
|
+
}
|
|
12040
|
+
const setHookParams = (firstArg = info) => {
|
|
12041
|
+
info = firstArg;
|
|
12042
|
+
};
|
|
12043
|
+
for (const hook of hookSet) {
|
|
12044
|
+
const returnValue = callHook(hook, info);
|
|
12045
|
+
if (returnValue && callback) {
|
|
12046
|
+
callback(returnValue, hook.plugin, setHookParams);
|
|
11963
12047
|
}
|
|
11964
12048
|
}
|
|
11965
12049
|
};
|
|
11966
12050
|
const callAsyncHooks = async (hookName, info, callback, options) => {
|
|
11967
|
-
const
|
|
11968
|
-
if (
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
12051
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12052
|
+
if (!hookSet) {
|
|
12053
|
+
return;
|
|
12054
|
+
}
|
|
12055
|
+
for (const hook of hookSet) {
|
|
12056
|
+
const returnValue = await callAsyncHook(hook, info);
|
|
12057
|
+
if (returnValue && callback) {
|
|
12058
|
+
await callback(returnValue, hook.plugin);
|
|
11974
12059
|
}
|
|
11975
12060
|
}
|
|
11976
12061
|
};
|
|
11977
12062
|
|
|
11978
12063
|
const callHooksUntil = (hookName, info) => {
|
|
11979
|
-
const
|
|
11980
|
-
if (
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
12064
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12065
|
+
if (!hookSet) {
|
|
12066
|
+
return null;
|
|
12067
|
+
}
|
|
12068
|
+
for (const hook of hookSet) {
|
|
12069
|
+
const returnValue = callHook(hook, info);
|
|
12070
|
+
if (returnValue) {
|
|
12071
|
+
return returnValue;
|
|
11986
12072
|
}
|
|
11987
12073
|
}
|
|
11988
12074
|
return null;
|
|
11989
12075
|
};
|
|
11990
12076
|
const callAsyncHooksUntil = async (hookName, info, options) => {
|
|
11991
|
-
const
|
|
11992
|
-
if (!
|
|
12077
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12078
|
+
if (!hookSet) {
|
|
11993
12079
|
return null;
|
|
11994
12080
|
}
|
|
11995
|
-
if (
|
|
12081
|
+
if (hookSet.size === 0) {
|
|
11996
12082
|
return null;
|
|
11997
12083
|
}
|
|
12084
|
+
const iterator = hookSet.values()[Symbol.iterator]();
|
|
11998
12085
|
let result;
|
|
11999
|
-
let index = 0;
|
|
12000
12086
|
const visit = async () => {
|
|
12001
|
-
|
|
12087
|
+
const { done, value: hook } = iterator.next();
|
|
12088
|
+
if (done) {
|
|
12002
12089
|
return;
|
|
12003
12090
|
}
|
|
12004
|
-
const hook = hooks[index];
|
|
12005
12091
|
const returnValue = await callAsyncHook(hook, info);
|
|
12006
12092
|
if (returnValue) {
|
|
12007
12093
|
result = returnValue;
|
|
12008
12094
|
return;
|
|
12009
12095
|
}
|
|
12010
|
-
index++;
|
|
12011
12096
|
await visit();
|
|
12012
12097
|
};
|
|
12013
12098
|
await visit();
|
|
@@ -12016,9 +12101,8 @@ const createPluginController = (
|
|
|
12016
12101
|
|
|
12017
12102
|
return {
|
|
12018
12103
|
pluginsMeta,
|
|
12019
|
-
|
|
12104
|
+
activePlugins,
|
|
12020
12105
|
pushPlugin,
|
|
12021
|
-
unshiftPlugin,
|
|
12022
12106
|
getHookFunction,
|
|
12023
12107
|
callHook,
|
|
12024
12108
|
callAsyncHook,
|
|
@@ -12614,11 +12698,12 @@ const determineFileUrlForOutDirectory = (urlInfo) => {
|
|
|
12614
12698
|
if (filenameHint) {
|
|
12615
12699
|
url = setUrlFilename(url, filenameHint);
|
|
12616
12700
|
}
|
|
12617
|
-
|
|
12701
|
+
const outUrl = moveUrl({
|
|
12618
12702
|
url,
|
|
12619
12703
|
from: rootDirectoryUrl,
|
|
12620
12704
|
to: outDirectoryUrl,
|
|
12621
12705
|
});
|
|
12706
|
+
return outUrl;
|
|
12622
12707
|
};
|
|
12623
12708
|
|
|
12624
12709
|
const determineSourcemapFileUrl = (urlInfo) => {
|
|
@@ -12910,7 +12995,7 @@ const createDependencies = (ownerUrlInfo) => {
|
|
|
12910
12995
|
const injectAsBannerCodeBeforeFinalize = (urlInfoReceiver) => {
|
|
12911
12996
|
const basename = urlToBasename(sideEffectFileUrl);
|
|
12912
12997
|
const inlineUrl = generateUrlForInlineContent({
|
|
12913
|
-
url: urlInfoReceiver.url,
|
|
12998
|
+
url: urlInfoReceiver.originalUrl || urlInfoReceiver.url,
|
|
12914
12999
|
basename,
|
|
12915
13000
|
extension: urlToExtension$1(sideEffectFileUrl),
|
|
12916
13001
|
});
|
|
@@ -13073,6 +13158,7 @@ const createReference = ({
|
|
|
13073
13158
|
specifierColumn,
|
|
13074
13159
|
baseUrl,
|
|
13075
13160
|
isOriginalPosition,
|
|
13161
|
+
isDirectRequest = false,
|
|
13076
13162
|
isEntryPoint = false,
|
|
13077
13163
|
isResourceHint = false,
|
|
13078
13164
|
// implicit references are not real references
|
|
@@ -13147,6 +13233,7 @@ const createReference = ({
|
|
|
13147
13233
|
specifierColumn,
|
|
13148
13234
|
isOriginalPosition,
|
|
13149
13235
|
baseUrl,
|
|
13236
|
+
isDirectRequest,
|
|
13150
13237
|
isEntryPoint,
|
|
13151
13238
|
isResourceHint,
|
|
13152
13239
|
isImplicit,
|
|
@@ -13982,6 +14069,7 @@ const createUrlInfo = (url, context) => {
|
|
|
13982
14069
|
writable: false,
|
|
13983
14070
|
value: url,
|
|
13984
14071
|
});
|
|
14072
|
+
urlInfo.pathname = new URL(url).pathname;
|
|
13985
14073
|
urlInfo.searchParams = new URL(url).searchParams;
|
|
13986
14074
|
|
|
13987
14075
|
urlInfo.dependencies = createDependencies(urlInfo);
|
|
@@ -14570,7 +14658,15 @@ const createUrlInfoTransformer = ({
|
|
|
14570
14658
|
contentIsInlined = false;
|
|
14571
14659
|
}
|
|
14572
14660
|
if (!contentIsInlined) {
|
|
14573
|
-
|
|
14661
|
+
const generatedUrlObject = new URL(generatedUrl);
|
|
14662
|
+
let baseName = urlToBasename(generatedUrlObject);
|
|
14663
|
+
for (const [key, value] of generatedUrlObject.searchParams) {
|
|
14664
|
+
baseName += `7${encodeFilePathComponent(key)}=${encodeFilePathComponent(value)}`;
|
|
14665
|
+
}
|
|
14666
|
+
const outFileUrl = setUrlBasename(generatedUrlObject, baseName);
|
|
14667
|
+
let outFilePath = urlToFileSystemPath(outFileUrl);
|
|
14668
|
+
outFilePath = truncate(outFilePath, 2055); // for windows
|
|
14669
|
+
writeFileSync(outFilePath, urlInfo.content, { force: true });
|
|
14574
14670
|
}
|
|
14575
14671
|
const { sourcemapGeneratedUrl, sourcemapReference } = urlInfo;
|
|
14576
14672
|
if (sourcemapGeneratedUrl && sourcemapReference) {
|
|
@@ -14689,6 +14785,26 @@ const createUrlInfoTransformer = ({
|
|
|
14689
14785
|
};
|
|
14690
14786
|
};
|
|
14691
14787
|
|
|
14788
|
+
// https://gist.github.com/barbietunnie/7bc6d48a424446c44ff4
|
|
14789
|
+
const illegalRe = /[/?<>\\:*|"]/g;
|
|
14790
|
+
// eslint-disable-next-line no-control-regex
|
|
14791
|
+
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
|
14792
|
+
const reservedRe = /^\.+$/;
|
|
14793
|
+
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
|
14794
|
+
const encodeFilePathComponent = (input, replacement = "") => {
|
|
14795
|
+
const encoded = input
|
|
14796
|
+
.replace(illegalRe, replacement)
|
|
14797
|
+
.replace(controlRe, replacement)
|
|
14798
|
+
.replace(reservedRe, replacement)
|
|
14799
|
+
.replace(windowsReservedRe, replacement);
|
|
14800
|
+
return encoded;
|
|
14801
|
+
};
|
|
14802
|
+
const truncate = (sanitized, length) => {
|
|
14803
|
+
const uint8Array = new TextEncoder().encode(sanitized);
|
|
14804
|
+
const truncated = uint8Array.slice(0, length);
|
|
14805
|
+
return new TextDecoder().decode(truncated);
|
|
14806
|
+
};
|
|
14807
|
+
|
|
14692
14808
|
const shouldUpdateSourcemapComment = (urlInfo, sourcemaps) => {
|
|
14693
14809
|
if (urlInfo.context.buildStep === "shape") {
|
|
14694
14810
|
return false;
|
|
@@ -14698,7 +14814,6 @@ const shouldUpdateSourcemapComment = (urlInfo, sourcemaps) => {
|
|
|
14698
14814
|
}
|
|
14699
14815
|
return false;
|
|
14700
14816
|
};
|
|
14701
|
-
|
|
14702
14817
|
const mayHaveSourcemap = (urlInfo) => {
|
|
14703
14818
|
if (urlInfo.url.startsWith("data:")) {
|
|
14704
14819
|
return false;
|
|
@@ -14708,7 +14823,6 @@ const mayHaveSourcemap = (urlInfo) => {
|
|
|
14708
14823
|
}
|
|
14709
14824
|
return true;
|
|
14710
14825
|
};
|
|
14711
|
-
|
|
14712
14826
|
const shouldHandleSourcemap = (urlInfo) => {
|
|
14713
14827
|
const { sourcemaps } = urlInfo.context;
|
|
14714
14828
|
if (
|
|
@@ -14792,10 +14906,7 @@ const createKitchen = ({
|
|
|
14792
14906
|
initialPluginsMeta,
|
|
14793
14907
|
);
|
|
14794
14908
|
kitchen.pluginController = pluginController;
|
|
14795
|
-
pluginController.pushPlugin(jsenvPluginHtmlSyntaxErrorFallback());
|
|
14796
|
-
plugins.forEach((pluginEntry) => {
|
|
14797
|
-
pluginController.pushPlugin(pluginEntry);
|
|
14798
|
-
});
|
|
14909
|
+
pluginController.pushPlugin(jsenvPluginHtmlSyntaxErrorFallback(), ...plugins);
|
|
14799
14910
|
|
|
14800
14911
|
const urlInfoTransformer = createUrlInfoTransformer({
|
|
14801
14912
|
logger,
|
|
@@ -14901,6 +15012,25 @@ ${ANSI.color(reference.url, ANSI.YELLOW)}
|
|
|
14901
15012
|
`);
|
|
14902
15013
|
}
|
|
14903
15014
|
}
|
|
15015
|
+
const request = kitchen.context.request;
|
|
15016
|
+
if (request) {
|
|
15017
|
+
let requestResource = request.resource;
|
|
15018
|
+
let requestedUrl;
|
|
15019
|
+
if (requestResource.startsWith("/@fs/")) {
|
|
15020
|
+
const fsRootRelativeUrl = requestResource.slice("/@fs/".length);
|
|
15021
|
+
requestedUrl = `file:///${fsRootRelativeUrl}`;
|
|
15022
|
+
} else {
|
|
15023
|
+
const requestedUrlObject = new URL(
|
|
15024
|
+
requestResource === "/" ? mainFilePath : requestResource.slice(1),
|
|
15025
|
+
rootDirectoryUrl,
|
|
15026
|
+
);
|
|
15027
|
+
requestedUrlObject.searchParams.delete("hot");
|
|
15028
|
+
requestedUrl = requestedUrlObject.href;
|
|
15029
|
+
}
|
|
15030
|
+
if (requestedUrl === reference.url) {
|
|
15031
|
+
reference.isDirectRequest = true;
|
|
15032
|
+
}
|
|
15033
|
+
}
|
|
14904
15034
|
redirect: {
|
|
14905
15035
|
if (reference.isImplicit && reference.isWeak) {
|
|
14906
15036
|
// not needed for implicit references that are not rendered anywhere
|
|
@@ -15808,10 +15938,11 @@ const jsenvPluginInliningIntoHtml = () => {
|
|
|
15808
15938
|
const { line, column, isOriginal } = getHtmlNodePosition(linkNode, {
|
|
15809
15939
|
preferOriginal: true,
|
|
15810
15940
|
});
|
|
15811
|
-
const linkInlineUrl = getUrlForContentInsideHtml(
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15941
|
+
const linkInlineUrl = getUrlForContentInsideHtml(
|
|
15942
|
+
linkNode,
|
|
15943
|
+
urlInfo,
|
|
15944
|
+
linkReference,
|
|
15945
|
+
);
|
|
15815
15946
|
const linkReferenceInlined = linkReference.inline({
|
|
15816
15947
|
line,
|
|
15817
15948
|
column,
|
|
@@ -15860,10 +15991,11 @@ const jsenvPluginInliningIntoHtml = () => {
|
|
|
15860
15991
|
const { line, column, isOriginal } = getHtmlNodePosition(scriptNode, {
|
|
15861
15992
|
preferOriginal: true,
|
|
15862
15993
|
});
|
|
15863
|
-
const scriptInlineUrl = getUrlForContentInsideHtml(
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15994
|
+
const scriptInlineUrl = getUrlForContentInsideHtml(
|
|
15995
|
+
scriptNode,
|
|
15996
|
+
urlInfo,
|
|
15997
|
+
scriptReference,
|
|
15998
|
+
);
|
|
15867
15999
|
const scriptReferenceInlined = scriptReference.inline({
|
|
15868
16000
|
line,
|
|
15869
16001
|
column,
|
|
@@ -17052,9 +17184,11 @@ const jsenvPluginHtmlReferenceAnalysis = ({
|
|
|
17052
17184
|
const { line, column, isOriginal } = getHtmlNodePosition(node, {
|
|
17053
17185
|
preferOriginal: true,
|
|
17054
17186
|
});
|
|
17055
|
-
const inlineContentUrl = getUrlForContentInsideHtml(
|
|
17056
|
-
|
|
17057
|
-
|
|
17187
|
+
const inlineContentUrl = getUrlForContentInsideHtml(
|
|
17188
|
+
node,
|
|
17189
|
+
urlInfo,
|
|
17190
|
+
null,
|
|
17191
|
+
);
|
|
17058
17192
|
const debug =
|
|
17059
17193
|
getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
|
|
17060
17194
|
const inlineReference = urlInfo.dependencies.foundInline({
|
|
@@ -17195,9 +17329,8 @@ const jsenvPluginHtmlReferenceAnalysis = ({
|
|
|
17195
17329
|
);
|
|
17196
17330
|
const importmapInlineUrl = getUrlForContentInsideHtml(
|
|
17197
17331
|
scriptNode,
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
},
|
|
17332
|
+
urlInfo,
|
|
17333
|
+
importmapReference,
|
|
17201
17334
|
);
|
|
17202
17335
|
const importmapReferenceInlined = importmapReference.inline({
|
|
17203
17336
|
line,
|
|
@@ -17481,9 +17614,7 @@ const parseAndTransformJsReferences = async (
|
|
|
17481
17614
|
Object.keys(urlInfo.context.runtimeCompat).toString() === "node";
|
|
17482
17615
|
|
|
17483
17616
|
const onInlineReference = (inlineReferenceInfo) => {
|
|
17484
|
-
const inlineUrl = getUrlForContentInsideJs(inlineReferenceInfo,
|
|
17485
|
-
url: urlInfo.url,
|
|
17486
|
-
});
|
|
17617
|
+
const inlineUrl = getUrlForContentInsideJs(inlineReferenceInfo, urlInfo);
|
|
17487
17618
|
let { quote } = inlineReferenceInfo;
|
|
17488
17619
|
if (quote === "`" && !canUseTemplateLiterals) {
|
|
17489
17620
|
// if quote is "`" and template literals are not supported
|
|
@@ -17726,23 +17857,7 @@ const jsenvPluginInlineContentFetcher = () => {
|
|
|
17726
17857
|
if (!urlInfo.isInline) {
|
|
17727
17858
|
return null;
|
|
17728
17859
|
}
|
|
17729
|
-
|
|
17730
|
-
if (urlInfo.context.request) {
|
|
17731
|
-
let requestResource = urlInfo.context.request.resource;
|
|
17732
|
-
let requestedUrl;
|
|
17733
|
-
if (requestResource.startsWith("/@fs/")) {
|
|
17734
|
-
const fsRootRelativeUrl = requestResource.slice("/@fs/".length);
|
|
17735
|
-
requestedUrl = `file:///${fsRootRelativeUrl}`;
|
|
17736
|
-
} else {
|
|
17737
|
-
const requestedUrlObject = new URL(
|
|
17738
|
-
requestResource.slice(1),
|
|
17739
|
-
urlInfo.context.rootDirectoryUrl,
|
|
17740
|
-
);
|
|
17741
|
-
requestedUrlObject.searchParams.delete("hot");
|
|
17742
|
-
requestedUrl = requestedUrlObject.href;
|
|
17743
|
-
}
|
|
17744
|
-
isDirectRequestToFile = requestedUrl === urlInfo.url;
|
|
17745
|
-
}
|
|
17860
|
+
const { isDirectRequest } = urlInfo.lastReference;
|
|
17746
17861
|
/*
|
|
17747
17862
|
* We want to find inline content but it's not straightforward
|
|
17748
17863
|
*
|
|
@@ -17771,7 +17886,7 @@ const jsenvPluginInlineContentFetcher = () => {
|
|
|
17771
17886
|
originalContent = reference.content;
|
|
17772
17887
|
}
|
|
17773
17888
|
lastInlineReference = reference;
|
|
17774
|
-
if (
|
|
17889
|
+
if (isDirectRequest) {
|
|
17775
17890
|
break;
|
|
17776
17891
|
}
|
|
17777
17892
|
}
|
|
@@ -19184,71 +19299,581 @@ const jsenvPluginVersionSearchParam = () => {
|
|
|
19184
19299
|
};
|
|
19185
19300
|
};
|
|
19186
19301
|
|
|
19187
|
-
const
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19302
|
+
const FILE_AND_SERVER_URLS_CONVERTER = {
|
|
19303
|
+
asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
|
|
19304
|
+
if (fileUrl === serverRootDirectoryUrl) {
|
|
19305
|
+
return "/";
|
|
19306
|
+
}
|
|
19307
|
+
if (urlIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
|
|
19308
|
+
const urlRelativeToServer = urlToRelativeUrl(
|
|
19309
|
+
fileUrl,
|
|
19310
|
+
serverRootDirectoryUrl,
|
|
19311
|
+
);
|
|
19312
|
+
return `/${urlRelativeToServer}`;
|
|
19313
|
+
}
|
|
19314
|
+
const urlRelativeToFilesystemRoot = String(fileUrl).slice(
|
|
19315
|
+
"file:///".length,
|
|
19316
|
+
);
|
|
19317
|
+
return `/@fs/${urlRelativeToFilesystemRoot}`;
|
|
19318
|
+
},
|
|
19319
|
+
asFileUrl: (urlRelativeToServer, serverRootDirectoryUrl) => {
|
|
19320
|
+
if (urlRelativeToServer.startsWith("/@fs/")) {
|
|
19321
|
+
const urlRelativeToFilesystemRoot = urlRelativeToServer.slice(
|
|
19322
|
+
"/@fs/".length,
|
|
19323
|
+
);
|
|
19324
|
+
return `file:///${urlRelativeToFilesystemRoot}`;
|
|
19325
|
+
}
|
|
19326
|
+
if (urlRelativeToServer[0] === "/") {
|
|
19327
|
+
return new URL(urlRelativeToServer.slice(1), serverRootDirectoryUrl).href;
|
|
19328
|
+
}
|
|
19329
|
+
return new URL(urlRelativeToServer, serverRootDirectoryUrl).href;
|
|
19330
|
+
},
|
|
19331
|
+
};
|
|
19332
|
+
|
|
19333
|
+
const jsenvPluginInjections = (rawAssociations) => {
|
|
19334
|
+
let resolvedAssociations;
|
|
19335
|
+
|
|
19193
19336
|
return {
|
|
19194
|
-
name: "jsenv:
|
|
19337
|
+
name: "jsenv:injections",
|
|
19195
19338
|
appliesDuring: "*",
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
|
|
19199
|
-
|
|
19200
|
-
|
|
19201
|
-
|
|
19339
|
+
init: (context) => {
|
|
19340
|
+
resolvedAssociations = URL_META.resolveAssociations(
|
|
19341
|
+
{ injectionsGetter: rawAssociations },
|
|
19342
|
+
context.rootDirectoryUrl,
|
|
19343
|
+
);
|
|
19344
|
+
},
|
|
19345
|
+
transformUrlContent: async (urlInfo) => {
|
|
19346
|
+
const { injectionsGetter } = URL_META.applyAssociations({
|
|
19347
|
+
url: asUrlWithoutSearch(urlInfo.url),
|
|
19348
|
+
associations: resolvedAssociations,
|
|
19349
|
+
});
|
|
19350
|
+
if (!injectionsGetter) {
|
|
19202
19351
|
return null;
|
|
19203
19352
|
}
|
|
19204
|
-
if (
|
|
19205
|
-
|
|
19353
|
+
if (typeof injectionsGetter !== "function") {
|
|
19354
|
+
throw new TypeError("injectionsGetter must be a function");
|
|
19206
19355
|
}
|
|
19207
|
-
|
|
19208
|
-
if (
|
|
19209
|
-
return
|
|
19356
|
+
const injections = await injectionsGetter(urlInfo);
|
|
19357
|
+
if (!injections) {
|
|
19358
|
+
return null;
|
|
19210
19359
|
}
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
19215
|
-
const directoryUrl = new URL(
|
|
19216
|
-
reference.specifierPathname
|
|
19217
|
-
.replace(`/${directoryContentMagicName}`, "/")
|
|
19218
|
-
.slice(1),
|
|
19219
|
-
rootDirectoryUrl,
|
|
19220
|
-
).href;
|
|
19221
|
-
return directoryUrl;
|
|
19360
|
+
const keys = Object.keys(injections);
|
|
19361
|
+
if (keys.length === 0) {
|
|
19362
|
+
return null;
|
|
19222
19363
|
}
|
|
19223
|
-
|
|
19224
|
-
|
|
19225
|
-
|
|
19226
|
-
|
|
19227
|
-
|
|
19228
|
-
|
|
19229
|
-
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19237
|
-
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
|
|
19243
|
-
|
|
19244
|
-
|
|
19245
|
-
|
|
19246
|
-
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19364
|
+
return replacePlaceholders(urlInfo.content, injections, urlInfo);
|
|
19365
|
+
},
|
|
19366
|
+
};
|
|
19367
|
+
};
|
|
19368
|
+
|
|
19369
|
+
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
19370
|
+
const INJECTIONS = {
|
|
19371
|
+
optional: (value) => {
|
|
19372
|
+
return { [injectionSymbol]: "optional", value };
|
|
19373
|
+
},
|
|
19374
|
+
};
|
|
19375
|
+
|
|
19376
|
+
// we export this because it is imported by jsenv_plugin_placeholder.js and unit test
|
|
19377
|
+
const replacePlaceholders = (content, replacements, urlInfo) => {
|
|
19378
|
+
const magicSource = createMagicSource(content);
|
|
19379
|
+
for (const key of Object.keys(replacements)) {
|
|
19380
|
+
let index = content.indexOf(key);
|
|
19381
|
+
const replacement = replacements[key];
|
|
19382
|
+
let isOptional;
|
|
19383
|
+
let value;
|
|
19384
|
+
if (replacement && replacement[injectionSymbol]) {
|
|
19385
|
+
const valueBehindSymbol = replacement[injectionSymbol];
|
|
19386
|
+
isOptional = valueBehindSymbol === "optional";
|
|
19387
|
+
value = replacement.value;
|
|
19388
|
+
} else {
|
|
19389
|
+
value = replacement;
|
|
19390
|
+
}
|
|
19391
|
+
if (index === -1) {
|
|
19392
|
+
if (!isOptional) {
|
|
19393
|
+
urlInfo.context.logger.warn(
|
|
19394
|
+
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
19395
|
+
--- suggestion a ---
|
|
19396
|
+
Add "${key}" in that file.
|
|
19397
|
+
--- suggestion b ---
|
|
19398
|
+
Fix eventual typo in "${key}"?
|
|
19399
|
+
--- suggestion c ---
|
|
19400
|
+
Mark injection as optional using INJECTIONS.optional():
|
|
19401
|
+
import { INJECTIONS } from "@jsenv/core";
|
|
19402
|
+
|
|
19403
|
+
return {
|
|
19404
|
+
"${key}": INJECTIONS.optional(${JSON.stringify(value)}),
|
|
19405
|
+
};`,
|
|
19406
|
+
);
|
|
19407
|
+
}
|
|
19408
|
+
continue;
|
|
19409
|
+
}
|
|
19410
|
+
|
|
19411
|
+
while (index !== -1) {
|
|
19412
|
+
const start = index;
|
|
19413
|
+
const end = index + key.length;
|
|
19414
|
+
magicSource.replace({
|
|
19415
|
+
start,
|
|
19416
|
+
end,
|
|
19417
|
+
replacement:
|
|
19418
|
+
urlInfo.type === "js_classic" ||
|
|
19419
|
+
urlInfo.type === "js_module" ||
|
|
19420
|
+
urlInfo.type === "html"
|
|
19421
|
+
? JSON.stringify(value, null, " ")
|
|
19422
|
+
: value,
|
|
19423
|
+
});
|
|
19424
|
+
index = content.indexOf(key, end);
|
|
19425
|
+
}
|
|
19426
|
+
}
|
|
19427
|
+
return magicSource.toContentAndSourcemap();
|
|
19428
|
+
};
|
|
19429
|
+
|
|
19430
|
+
/*
|
|
19431
|
+
* NICE TO HAVE:
|
|
19432
|
+
*
|
|
19433
|
+
* - when visiting urls outside server root directory the UI is messed up
|
|
19434
|
+
*
|
|
19435
|
+
* Let's say I visit file outside the server root directory that is in 404
|
|
19436
|
+
* We must update the enoent message and maybe other things to take into account
|
|
19437
|
+
* that url is no longer /something but "@fs/project_root/something" in the browser url bar
|
|
19438
|
+
*
|
|
19439
|
+
* - watching directory might result into things that are not properly handled:
|
|
19440
|
+
* 1. the existing directory is deleted
|
|
19441
|
+
* -> we should update the whole page to use a new "firstExistingDirectoryUrl"
|
|
19442
|
+
* 2. the enoent is impacted
|
|
19443
|
+
* -> we should update the ENOENT message
|
|
19444
|
+
* It means the websocket should contain more data and we can't assume firstExistingDirectoryUrl won't change
|
|
19445
|
+
*
|
|
19446
|
+
|
|
19447
|
+
*/
|
|
19448
|
+
|
|
19449
|
+
|
|
19450
|
+
const htmlFileUrlForDirectory = new URL(
|
|
19451
|
+
"./html/directory_listing.html",
|
|
19452
|
+
import.meta.url,
|
|
19453
|
+
);
|
|
19454
|
+
|
|
19455
|
+
const jsenvPluginDirectoryListing = ({
|
|
19456
|
+
directoryContentMagicName,
|
|
19457
|
+
directoryListingUrlMocks,
|
|
19458
|
+
autoreload = true,
|
|
19459
|
+
}) => {
|
|
19460
|
+
return {
|
|
19461
|
+
name: "jsenv:directory_listing",
|
|
19462
|
+
appliesDuring: "dev",
|
|
19463
|
+
redirectReference: (reference) => {
|
|
19464
|
+
if (reference.isInline) {
|
|
19465
|
+
return null;
|
|
19466
|
+
}
|
|
19467
|
+
const url = reference.url;
|
|
19468
|
+
if (!url.startsWith("file:")) {
|
|
19469
|
+
return null;
|
|
19470
|
+
}
|
|
19471
|
+
let { fsStat } = reference;
|
|
19472
|
+
if (!fsStat) {
|
|
19473
|
+
fsStat = readEntryStatSync(url, { nullIfNotFound: true });
|
|
19474
|
+
reference.fsStat = fsStat;
|
|
19475
|
+
}
|
|
19476
|
+
const { request } = reference.ownerUrlInfo.context;
|
|
19477
|
+
if (!fsStat) {
|
|
19478
|
+
if (
|
|
19479
|
+
reference.isDirectRequest &&
|
|
19480
|
+
request &&
|
|
19481
|
+
request.headers["sec-fetch-dest"] === "document"
|
|
19482
|
+
) {
|
|
19483
|
+
return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(url)}&enoent`;
|
|
19484
|
+
}
|
|
19485
|
+
return null;
|
|
19486
|
+
}
|
|
19487
|
+
const isDirectory = fsStat?.isDirectory();
|
|
19488
|
+
if (!isDirectory) {
|
|
19489
|
+
return null;
|
|
19490
|
+
}
|
|
19491
|
+
if (reference.type === "filesystem") {
|
|
19492
|
+
// TODO: we should redirect to something like /...json
|
|
19493
|
+
// and any file name ...json is a special file serving directory content as json
|
|
19494
|
+
return null;
|
|
19495
|
+
}
|
|
19496
|
+
const acceptsHtml = request
|
|
19497
|
+
? pickContentType(request, ["text/html"])
|
|
19498
|
+
: false;
|
|
19499
|
+
if (!acceptsHtml) {
|
|
19500
|
+
return null;
|
|
19501
|
+
}
|
|
19502
|
+
reference.fsStat = null; // reset fsStat, now it's not a directory anyor
|
|
19503
|
+
return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(url)}`;
|
|
19504
|
+
},
|
|
19505
|
+
transformUrlContent: {
|
|
19506
|
+
html: (urlInfo) => {
|
|
19507
|
+
const urlWithoutSearch = asUrlWithoutSearch(urlInfo.url);
|
|
19508
|
+
if (urlWithoutSearch !== String(htmlFileUrlForDirectory)) {
|
|
19509
|
+
return null;
|
|
19510
|
+
}
|
|
19511
|
+
const requestedUrl = urlInfo.searchParams.get("url");
|
|
19512
|
+
if (!requestedUrl) {
|
|
19513
|
+
return null;
|
|
19514
|
+
}
|
|
19515
|
+
urlInfo.headers["cache-control"] = "no-cache";
|
|
19516
|
+
const enoent = urlInfo.searchParams.has("enoent");
|
|
19517
|
+
if (enoent) {
|
|
19518
|
+
urlInfo.status = 404;
|
|
19519
|
+
urlInfo.headers["cache-control"] = "no-cache";
|
|
19520
|
+
}
|
|
19521
|
+
const request = urlInfo.context.request;
|
|
19522
|
+
const { rootDirectoryUrl, mainFilePath } = urlInfo.context;
|
|
19523
|
+
return replacePlaceholders(
|
|
19524
|
+
urlInfo.content,
|
|
19525
|
+
{
|
|
19526
|
+
...generateDirectoryListingInjection(requestedUrl, {
|
|
19527
|
+
autoreload,
|
|
19528
|
+
request,
|
|
19529
|
+
directoryListingUrlMocks,
|
|
19530
|
+
directoryContentMagicName,
|
|
19531
|
+
rootDirectoryUrl,
|
|
19532
|
+
mainFilePath,
|
|
19533
|
+
enoent,
|
|
19534
|
+
}),
|
|
19535
|
+
},
|
|
19536
|
+
urlInfo,
|
|
19537
|
+
);
|
|
19538
|
+
},
|
|
19539
|
+
},
|
|
19540
|
+
serveWebsocket: ({ websocket, request, context }) => {
|
|
19541
|
+
if (!autoreload) {
|
|
19542
|
+
return false;
|
|
19543
|
+
}
|
|
19544
|
+
const secProtocol = request.headers["sec-websocket-protocol"];
|
|
19545
|
+
if (secProtocol !== "watch-directory") {
|
|
19546
|
+
return false;
|
|
19547
|
+
}
|
|
19548
|
+
const { rootDirectoryUrl, mainFilePath } = context;
|
|
19549
|
+
const requestedUrl = FILE_AND_SERVER_URLS_CONVERTER.asFileUrl(
|
|
19550
|
+
request.pathname,
|
|
19551
|
+
rootDirectoryUrl,
|
|
19552
|
+
);
|
|
19553
|
+
const closestDirectoryUrl = getFirstExistingDirectoryUrl(requestedUrl);
|
|
19554
|
+
const sendMessage = (message) => {
|
|
19555
|
+
websocket.send(JSON.stringify(message));
|
|
19556
|
+
};
|
|
19557
|
+
const generateItems = () => {
|
|
19558
|
+
const firstExistingDirectoryUrl = getFirstExistingDirectoryUrl(
|
|
19559
|
+
requestedUrl,
|
|
19560
|
+
rootDirectoryUrl,
|
|
19561
|
+
);
|
|
19562
|
+
const items = getDirectoryContentItems({
|
|
19563
|
+
serverRootDirectoryUrl: rootDirectoryUrl,
|
|
19564
|
+
mainFilePath,
|
|
19565
|
+
requestedUrl,
|
|
19566
|
+
firstExistingDirectoryUrl,
|
|
19567
|
+
});
|
|
19568
|
+
return items;
|
|
19569
|
+
};
|
|
19570
|
+
|
|
19571
|
+
const unwatch = registerDirectoryLifecycle(closestDirectoryUrl, {
|
|
19572
|
+
added: ({ relativeUrl }) => {
|
|
19573
|
+
sendMessage({
|
|
19574
|
+
type: "change",
|
|
19575
|
+
reason: `${relativeUrl} added`,
|
|
19576
|
+
items: generateItems(),
|
|
19577
|
+
});
|
|
19578
|
+
},
|
|
19579
|
+
updated: ({ relativeUrl }) => {
|
|
19580
|
+
sendMessage({
|
|
19581
|
+
type: "change",
|
|
19582
|
+
reason: `${relativeUrl} updated`,
|
|
19583
|
+
items: generateItems(),
|
|
19584
|
+
});
|
|
19585
|
+
},
|
|
19586
|
+
removed: ({ relativeUrl }) => {
|
|
19587
|
+
sendMessage({
|
|
19588
|
+
type: "change",
|
|
19589
|
+
reason: `${relativeUrl} removed`,
|
|
19590
|
+
items: generateItems(),
|
|
19591
|
+
});
|
|
19592
|
+
},
|
|
19593
|
+
});
|
|
19594
|
+
websocket.signal.addEventListener("abort", () => {
|
|
19595
|
+
unwatch();
|
|
19596
|
+
});
|
|
19597
|
+
return true;
|
|
19598
|
+
},
|
|
19599
|
+
};
|
|
19600
|
+
};
|
|
19601
|
+
|
|
19602
|
+
const generateDirectoryListingInjection = (
|
|
19603
|
+
requestedUrl,
|
|
19604
|
+
{
|
|
19605
|
+
rootDirectoryUrl,
|
|
19606
|
+
mainFilePath,
|
|
19607
|
+
request,
|
|
19608
|
+
directoryListingUrlMocks,
|
|
19609
|
+
directoryContentMagicName,
|
|
19610
|
+
autoreload,
|
|
19611
|
+
enoent,
|
|
19612
|
+
},
|
|
19613
|
+
) => {
|
|
19614
|
+
let serverRootDirectoryUrl = rootDirectoryUrl;
|
|
19615
|
+
const firstExistingDirectoryUrl = getFirstExistingDirectoryUrl(
|
|
19616
|
+
requestedUrl,
|
|
19617
|
+
serverRootDirectoryUrl,
|
|
19618
|
+
);
|
|
19619
|
+
const directoryContentItems = getDirectoryContentItems({
|
|
19620
|
+
serverRootDirectoryUrl,
|
|
19621
|
+
mainFilePath,
|
|
19622
|
+
requestedUrl,
|
|
19623
|
+
firstExistingDirectoryUrl,
|
|
19624
|
+
});
|
|
19625
|
+
package_workspaces: {
|
|
19626
|
+
const packageDirectoryUrl = lookupPackageDirectory(serverRootDirectoryUrl);
|
|
19627
|
+
if (!packageDirectoryUrl) {
|
|
19628
|
+
break package_workspaces;
|
|
19629
|
+
}
|
|
19630
|
+
if (String(packageDirectoryUrl) === String(serverRootDirectoryUrl)) {
|
|
19631
|
+
break package_workspaces;
|
|
19632
|
+
}
|
|
19633
|
+
rootDirectoryUrl = packageDirectoryUrl;
|
|
19634
|
+
// if (String(firstExistingDirectoryUrl) === String(serverRootDirectoryUrl)) {
|
|
19635
|
+
// let packageContent;
|
|
19636
|
+
// try {
|
|
19637
|
+
// packageContent = JSON.parse(
|
|
19638
|
+
// readFileSync(new URL("package.json", packageDirectoryUrl), "utf8"),
|
|
19639
|
+
// );
|
|
19640
|
+
// } catch {
|
|
19641
|
+
// break package_workspaces;
|
|
19642
|
+
// }
|
|
19643
|
+
// const { workspaces } = packageContent;
|
|
19644
|
+
// if (Array.isArray(workspaces)) {
|
|
19645
|
+
// for (const workspace of workspaces) {
|
|
19646
|
+
// const workspaceUrlObject = new URL(workspace, packageDirectoryUrl);
|
|
19647
|
+
// const workspaceUrl = workspaceUrlObject.href;
|
|
19648
|
+
// if (workspaceUrl.endsWith("*")) {
|
|
19649
|
+
// const directoryUrl = ensurePathnameTrailingSlash(
|
|
19650
|
+
// workspaceUrl.slice(0, -1),
|
|
19651
|
+
// );
|
|
19652
|
+
// fileUrls.push(new URL(directoryUrl));
|
|
19653
|
+
// } else {
|
|
19654
|
+
// fileUrls.push(ensurePathnameTrailingSlash(workspaceUrlObject));
|
|
19655
|
+
// }
|
|
19656
|
+
// }
|
|
19657
|
+
// }
|
|
19658
|
+
// }
|
|
19659
|
+
}
|
|
19660
|
+
const directoryUrlRelativeToServer =
|
|
19661
|
+
FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
19662
|
+
firstExistingDirectoryUrl,
|
|
19663
|
+
serverRootDirectoryUrl,
|
|
19664
|
+
);
|
|
19665
|
+
const websocketScheme = request.protocol === "https" ? "wss" : "ws";
|
|
19666
|
+
const { host } = new URL(request.url);
|
|
19667
|
+
const websocketUrl = `${websocketScheme}://${host}${directoryUrlRelativeToServer}`;
|
|
19668
|
+
|
|
19669
|
+
const navItems = [];
|
|
19670
|
+
{
|
|
19671
|
+
const lastItemUrl = firstExistingDirectoryUrl;
|
|
19672
|
+
const lastItemRelativeUrl = urlToRelativeUrl(lastItemUrl, rootDirectoryUrl);
|
|
19673
|
+
const rootDirectoryUrlName = urlToFilename$1(rootDirectoryUrl);
|
|
19674
|
+
let parts;
|
|
19675
|
+
if (lastItemRelativeUrl) {
|
|
19676
|
+
parts = `${rootDirectoryUrlName}/${lastItemRelativeUrl}`.split("/");
|
|
19677
|
+
} else {
|
|
19678
|
+
parts = [rootDirectoryUrlName];
|
|
19679
|
+
}
|
|
19680
|
+
|
|
19681
|
+
let i = 0;
|
|
19682
|
+
while (i < parts.length) {
|
|
19683
|
+
const part = parts[i];
|
|
19684
|
+
const isLastPart = i === parts.length - 1;
|
|
19685
|
+
if (isLastPart && part === "") {
|
|
19686
|
+
// ignore trailing slash
|
|
19687
|
+
break;
|
|
19688
|
+
}
|
|
19689
|
+
let navItemRelativeUrl = `${parts.slice(1, i + 1).join("/")}`;
|
|
19690
|
+
let navItemUrl =
|
|
19691
|
+
navItemRelativeUrl === ""
|
|
19692
|
+
? rootDirectoryUrl
|
|
19693
|
+
: new URL(navItemRelativeUrl, rootDirectoryUrl).href;
|
|
19694
|
+
if (!isLastPart) {
|
|
19695
|
+
navItemUrl = ensurePathnameTrailingSlash(navItemUrl);
|
|
19696
|
+
}
|
|
19697
|
+
let urlRelativeToServer = FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
19698
|
+
navItemUrl,
|
|
19699
|
+
serverRootDirectoryUrl,
|
|
19700
|
+
);
|
|
19701
|
+
let urlRelativeToDocument = urlToRelativeUrl(navItemUrl, requestedUrl);
|
|
19702
|
+
const isServerRootDirectory = navItemUrl === serverRootDirectoryUrl;
|
|
19703
|
+
if (isServerRootDirectory) {
|
|
19704
|
+
urlRelativeToServer = `/${directoryContentMagicName}`;
|
|
19705
|
+
urlRelativeToDocument = `/${directoryContentMagicName}`;
|
|
19706
|
+
}
|
|
19707
|
+
const name = part;
|
|
19708
|
+
const isCurrent = navItemUrl === String(firstExistingDirectoryUrl);
|
|
19709
|
+
navItems.push({
|
|
19710
|
+
url: navItemUrl,
|
|
19711
|
+
urlRelativeToServer,
|
|
19712
|
+
urlRelativeToDocument,
|
|
19713
|
+
isServerRootDirectory,
|
|
19714
|
+
isCurrent,
|
|
19715
|
+
name,
|
|
19716
|
+
});
|
|
19717
|
+
i++;
|
|
19718
|
+
}
|
|
19719
|
+
}
|
|
19720
|
+
|
|
19721
|
+
let enoentDetails = null;
|
|
19722
|
+
if (enoent) {
|
|
19723
|
+
const fileRelativeUrl = urlToRelativeUrl(
|
|
19724
|
+
requestedUrl,
|
|
19725
|
+
serverRootDirectoryUrl,
|
|
19726
|
+
);
|
|
19727
|
+
let filePathExisting;
|
|
19728
|
+
let filePathNotFound;
|
|
19729
|
+
const existingIndex = String(firstExistingDirectoryUrl).length;
|
|
19730
|
+
filePathExisting = urlToRelativeUrl(
|
|
19731
|
+
firstExistingDirectoryUrl,
|
|
19732
|
+
serverRootDirectoryUrl,
|
|
19733
|
+
);
|
|
19734
|
+
filePathNotFound = requestedUrl.slice(existingIndex);
|
|
19735
|
+
enoentDetails = {
|
|
19736
|
+
fileUrl: requestedUrl,
|
|
19737
|
+
fileRelativeUrl,
|
|
19738
|
+
filePathExisting: `/${filePathExisting}`,
|
|
19739
|
+
filePathNotFound,
|
|
19740
|
+
};
|
|
19741
|
+
}
|
|
19742
|
+
|
|
19743
|
+
return {
|
|
19744
|
+
__DIRECTORY_LISTING__: {
|
|
19745
|
+
enoentDetails,
|
|
19746
|
+
navItems,
|
|
19747
|
+
directoryListingUrlMocks,
|
|
19748
|
+
directoryContentMagicName,
|
|
19749
|
+
directoryUrl: firstExistingDirectoryUrl,
|
|
19750
|
+
serverRootDirectoryUrl,
|
|
19751
|
+
rootDirectoryUrl,
|
|
19752
|
+
mainFilePath,
|
|
19753
|
+
directoryContentItems,
|
|
19754
|
+
websocketUrl,
|
|
19755
|
+
autoreload,
|
|
19756
|
+
},
|
|
19757
|
+
};
|
|
19758
|
+
};
|
|
19759
|
+
const getFirstExistingDirectoryUrl = (requestedUrl, serverRootDirectoryUrl) => {
|
|
19760
|
+
let firstExistingDirectoryUrl = new URL("./", requestedUrl);
|
|
19761
|
+
while (!existsSync(firstExistingDirectoryUrl)) {
|
|
19762
|
+
firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
|
|
19763
|
+
if (!urlIsInsideOf(firstExistingDirectoryUrl, serverRootDirectoryUrl)) {
|
|
19764
|
+
firstExistingDirectoryUrl = new URL(serverRootDirectoryUrl);
|
|
19765
|
+
break;
|
|
19766
|
+
}
|
|
19767
|
+
}
|
|
19768
|
+
return firstExistingDirectoryUrl;
|
|
19769
|
+
};
|
|
19770
|
+
const getDirectoryContentItems = ({
|
|
19771
|
+
serverRootDirectoryUrl,
|
|
19772
|
+
mainFilePath,
|
|
19773
|
+
firstExistingDirectoryUrl,
|
|
19774
|
+
}) => {
|
|
19775
|
+
const directoryContentArray = readdirSync(new URL(firstExistingDirectoryUrl));
|
|
19776
|
+
const fileUrls = [];
|
|
19777
|
+
for (const filename of directoryContentArray) {
|
|
19778
|
+
const fileUrlObject = new URL(filename, firstExistingDirectoryUrl);
|
|
19779
|
+
if (lstatSync(fileUrlObject).isDirectory()) {
|
|
19780
|
+
fileUrls.push(ensurePathnameTrailingSlash(fileUrlObject));
|
|
19781
|
+
} else {
|
|
19782
|
+
fileUrls.push(fileUrlObject);
|
|
19783
|
+
}
|
|
19784
|
+
}
|
|
19785
|
+
fileUrls.sort((a, b) => {
|
|
19786
|
+
return comparePathnames(a.pathname, b.pathname);
|
|
19787
|
+
});
|
|
19788
|
+
const items = [];
|
|
19789
|
+
for (const fileUrl of fileUrls) {
|
|
19790
|
+
const urlRelativeToCurrentDirectory = urlToRelativeUrl(
|
|
19791
|
+
fileUrl,
|
|
19792
|
+
firstExistingDirectoryUrl,
|
|
19793
|
+
);
|
|
19794
|
+
const urlRelativeToServer = FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
19795
|
+
fileUrl,
|
|
19796
|
+
serverRootDirectoryUrl,
|
|
19797
|
+
);
|
|
19798
|
+
const url = String(fileUrl);
|
|
19799
|
+
const mainFileUrl = new URL(mainFilePath, serverRootDirectoryUrl).href;
|
|
19800
|
+
const isMainFile = url === mainFileUrl;
|
|
19801
|
+
|
|
19802
|
+
items.push({
|
|
19803
|
+
url,
|
|
19804
|
+
urlRelativeToCurrentDirectory,
|
|
19805
|
+
urlRelativeToServer,
|
|
19806
|
+
isMainFile,
|
|
19807
|
+
});
|
|
19808
|
+
}
|
|
19809
|
+
return items;
|
|
19810
|
+
};
|
|
19811
|
+
|
|
19812
|
+
const jsenvPluginFsRedirection = ({
|
|
19813
|
+
directoryContentMagicName,
|
|
19814
|
+
magicExtensions = ["inherit", ".js"],
|
|
19815
|
+
magicDirectoryIndex = true,
|
|
19816
|
+
preserveSymlinks = false,
|
|
19817
|
+
}) => {
|
|
19818
|
+
return {
|
|
19819
|
+
name: "jsenv:fs_redirection",
|
|
19820
|
+
appliesDuring: "*",
|
|
19821
|
+
redirectReference: (reference) => {
|
|
19822
|
+
// http, https, data, about, ...
|
|
19823
|
+
if (!reference.url.startsWith("file:")) {
|
|
19824
|
+
return null;
|
|
19825
|
+
}
|
|
19826
|
+
if (reference.isInline) {
|
|
19827
|
+
return null;
|
|
19828
|
+
}
|
|
19829
|
+
if (reference.url === "file:///" || reference.url === "file://") {
|
|
19830
|
+
return `ignore:file:///`;
|
|
19831
|
+
}
|
|
19832
|
+
// ignore all new URL second arg
|
|
19833
|
+
if (reference.subtype === "new_url_second_arg") {
|
|
19834
|
+
return `ignore:${reference.url}`;
|
|
19835
|
+
}
|
|
19836
|
+
if (
|
|
19837
|
+
reference.specifierPathname.endsWith(`/${directoryContentMagicName}`)
|
|
19838
|
+
) {
|
|
19839
|
+
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
19840
|
+
const directoryUrl = new URL(
|
|
19841
|
+
reference.specifierPathname
|
|
19842
|
+
.replace(`/${directoryContentMagicName}`, "/")
|
|
19843
|
+
.slice(1),
|
|
19844
|
+
rootDirectoryUrl,
|
|
19845
|
+
).href;
|
|
19846
|
+
return directoryUrl;
|
|
19847
|
+
}
|
|
19848
|
+
// ignore "./" on new URL("./")
|
|
19849
|
+
// if (
|
|
19850
|
+
// reference.subtype === "new_url_first_arg" &&
|
|
19851
|
+
// reference.specifier === "./"
|
|
19852
|
+
// ) {
|
|
19853
|
+
// return `ignore:${reference.url}`;
|
|
19854
|
+
// }
|
|
19855
|
+
const urlObject = new URL(reference.url);
|
|
19856
|
+
let fsStat = readEntryStatSync(urlObject, { nullIfNotFound: true });
|
|
19857
|
+
reference.fsStat = fsStat;
|
|
19858
|
+
const { search, hash } = urlObject;
|
|
19859
|
+
urlObject.search = "";
|
|
19860
|
+
urlObject.hash = "";
|
|
19861
|
+
applyFsStatEffectsOnUrlObject(urlObject, fsStat);
|
|
19862
|
+
const shouldApplyFilesystemMagicResolution =
|
|
19863
|
+
reference.type === "js_import";
|
|
19864
|
+
if (shouldApplyFilesystemMagicResolution) {
|
|
19865
|
+
const filesystemResolution = applyFileSystemMagicResolution(
|
|
19866
|
+
urlObject.href,
|
|
19867
|
+
{
|
|
19868
|
+
fileStat: fsStat,
|
|
19869
|
+
magicDirectoryIndex,
|
|
19870
|
+
magicExtensions: getExtensionsToTry(
|
|
19871
|
+
magicExtensions,
|
|
19872
|
+
reference.ownerUrlInfo.url,
|
|
19873
|
+
),
|
|
19874
|
+
},
|
|
19875
|
+
);
|
|
19876
|
+
if (filesystemResolution.stat) {
|
|
19252
19877
|
fsStat = filesystemResolution.stat;
|
|
19253
19878
|
reference.fsStat = fsStat;
|
|
19254
19879
|
urlObject.href = filesystemResolution.url;
|
|
@@ -19318,17 +19943,10 @@ const resolveSymlink = (fileUrl) => {
|
|
|
19318
19943
|
return realUrlObject.href;
|
|
19319
19944
|
};
|
|
19320
19945
|
|
|
19321
|
-
const html404AndAncestorDirFileUrl = new URL(
|
|
19322
|
-
"./html/html_404_and_ancestor_dir.html",
|
|
19323
|
-
import.meta.url,
|
|
19324
|
-
);
|
|
19325
|
-
const htmlFileUrlForDirectory = new URL(
|
|
19326
|
-
"./html/directory.html",
|
|
19327
|
-
import.meta.url,
|
|
19328
|
-
);
|
|
19329
19946
|
const directoryContentMagicName = "...";
|
|
19330
19947
|
|
|
19331
19948
|
const jsenvPluginProtocolFile = ({
|
|
19949
|
+
supervisorEnabled,
|
|
19332
19950
|
magicExtensions,
|
|
19333
19951
|
magicDirectoryIndex,
|
|
19334
19952
|
preserveSymlinks,
|
|
@@ -19363,8 +19981,7 @@ const jsenvPluginProtocolFile = ({
|
|
|
19363
19981
|
appliesDuring: "dev",
|
|
19364
19982
|
resolveReference: (reference) => {
|
|
19365
19983
|
if (reference.specifier.startsWith("/@fs/")) {
|
|
19366
|
-
|
|
19367
|
-
return `file:///${fsRootRelativeUrl}`;
|
|
19984
|
+
return FILE_AND_SERVER_URLS_CONVERTER.asFileUrl(reference.specifier);
|
|
19368
19985
|
}
|
|
19369
19986
|
return null;
|
|
19370
19987
|
},
|
|
@@ -19383,382 +20000,71 @@ const jsenvPluginProtocolFile = ({
|
|
|
19383
20000
|
}
|
|
19384
20001
|
}
|
|
19385
20002
|
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19389
|
-
|
|
19390
|
-
const result = `/@fs/${generatedUrl.slice("file:///".length)}`;
|
|
19391
|
-
return result;
|
|
20003
|
+
return FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
20004
|
+
generatedUrl,
|
|
20005
|
+
rootDirectoryUrl,
|
|
20006
|
+
);
|
|
19392
20007
|
},
|
|
19393
20008
|
},
|
|
20009
|
+
jsenvPluginDirectoryListing({
|
|
20010
|
+
supervisorEnabled,
|
|
20011
|
+
directoryContentMagicName,
|
|
20012
|
+
directoryListingUrlMocks,
|
|
20013
|
+
}),
|
|
19394
20014
|
{
|
|
19395
|
-
name: "jsenv:
|
|
20015
|
+
name: "jsenv:directory_as_json",
|
|
19396
20016
|
appliesDuring: "*",
|
|
19397
20017
|
fetchUrlContent: (urlInfo) => {
|
|
19398
|
-
if (!urlInfo.url.startsWith("file:")) {
|
|
19399
|
-
return null;
|
|
19400
|
-
}
|
|
19401
20018
|
const { firstReference } = urlInfo;
|
|
19402
|
-
const { mainFilePath } = urlInfo.context;
|
|
19403
20019
|
let { fsStat } = firstReference;
|
|
19404
20020
|
if (!fsStat) {
|
|
19405
20021
|
fsStat = readEntryStatSync(urlInfo.url, { nullIfNotFound: true });
|
|
19406
20022
|
}
|
|
19407
|
-
const isDirectory = fsStat?.isDirectory();
|
|
19408
|
-
const { rootDirectoryUrl, request } = urlInfo.context;
|
|
19409
|
-
const serveFile = (url) => {
|
|
19410
|
-
const contentType = CONTENT_TYPE.fromUrlExtension(url);
|
|
19411
|
-
const fileBuffer = readFileSync(new URL(url));
|
|
19412
|
-
const content = CONTENT_TYPE.isTextual(contentType)
|
|
19413
|
-
? String(fileBuffer)
|
|
19414
|
-
: fileBuffer;
|
|
19415
|
-
return {
|
|
19416
|
-
content,
|
|
19417
|
-
contentType,
|
|
19418
|
-
contentLength: fileBuffer.length,
|
|
19419
|
-
};
|
|
19420
|
-
};
|
|
19421
|
-
|
|
19422
20023
|
if (!fsStat) {
|
|
19423
|
-
|
|
19424
|
-
const directoryContentItems = generateDirectoryContentItems(
|
|
19425
|
-
urlInfo.url,
|
|
19426
|
-
rootDirectoryUrl,
|
|
19427
|
-
);
|
|
19428
|
-
const html = generateHtmlForENOENT(
|
|
19429
|
-
urlInfo.url,
|
|
19430
|
-
directoryContentItems,
|
|
19431
|
-
directoryListingUrlMocks,
|
|
19432
|
-
{ mainFilePath },
|
|
19433
|
-
);
|
|
19434
|
-
return {
|
|
19435
|
-
status: 404,
|
|
19436
|
-
contentType: "text/html",
|
|
19437
|
-
content: html,
|
|
19438
|
-
headers: {
|
|
19439
|
-
"cache-control": "no-cache",
|
|
19440
|
-
},
|
|
19441
|
-
};
|
|
19442
|
-
}
|
|
20024
|
+
return null;
|
|
19443
20025
|
}
|
|
19444
|
-
|
|
19445
|
-
|
|
19446
|
-
|
|
19447
|
-
const content = JSON.stringify(directoryContentArray, null, " ");
|
|
19448
|
-
return {
|
|
19449
|
-
type: "directory",
|
|
19450
|
-
contentType: "application/json",
|
|
19451
|
-
content,
|
|
19452
|
-
};
|
|
19453
|
-
}
|
|
19454
|
-
const acceptsHtml = request
|
|
19455
|
-
? pickContentType(request, ["text/html"])
|
|
19456
|
-
: false;
|
|
19457
|
-
if (acceptsHtml) {
|
|
19458
|
-
firstReference.expectedType = "html";
|
|
19459
|
-
const directoryUrl = urlInfo.url;
|
|
19460
|
-
const directoryContentItems = generateDirectoryContentItems(
|
|
19461
|
-
directoryUrl,
|
|
19462
|
-
rootDirectoryUrl,
|
|
19463
|
-
);
|
|
19464
|
-
const html = generateHtmlForDirectory(directoryContentItems, {
|
|
19465
|
-
mainFilePath,
|
|
19466
|
-
});
|
|
19467
|
-
return {
|
|
19468
|
-
type: "html",
|
|
19469
|
-
contentType: "text/html",
|
|
19470
|
-
content: html,
|
|
19471
|
-
};
|
|
19472
|
-
}
|
|
19473
|
-
return {
|
|
19474
|
-
type: "directory",
|
|
19475
|
-
contentType: "application/json",
|
|
19476
|
-
content: JSON.stringify(directoryContentArray, null, " "),
|
|
19477
|
-
};
|
|
20026
|
+
const isDirectory = fsStat.isDirectory();
|
|
20027
|
+
if (!isDirectory) {
|
|
20028
|
+
return null;
|
|
19478
20029
|
}
|
|
19479
|
-
|
|
20030
|
+
const directoryContentArray = readdirSync(new URL(urlInfo.url));
|
|
20031
|
+
const content = JSON.stringify(directoryContentArray, null, " ");
|
|
20032
|
+
return {
|
|
20033
|
+
type: "directory",
|
|
20034
|
+
contentType: "application/json",
|
|
20035
|
+
content,
|
|
20036
|
+
};
|
|
19480
20037
|
},
|
|
19481
20038
|
},
|
|
19482
|
-
|
|
19483
|
-
|
|
19484
|
-
|
|
19485
|
-
|
|
19486
|
-
|
|
19487
|
-
|
|
19488
|
-
|
|
19489
|
-
|
|
19490
|
-
|
|
19491
|
-
|
|
19492
|
-
|
|
19493
|
-
|
|
19494
|
-
|
|
19495
|
-
|
|
19496
|
-
|
|
19497
|
-
|
|
19498
|
-
|
|
19499
|
-
|
|
19500
|
-
|
|
19501
|
-
|
|
19502
|
-
|
|
19503
|
-
|
|
19504
|
-
|
|
19505
|
-
};
|
|
19506
|
-
|
|
19507
|
-
|
|
19508
|
-
|
|
19509
|
-
|
|
19510
|
-
|
|
19511
|
-
) => {
|
|
19512
|
-
const ancestorDirectoryUrl = directoryContentItems.firstExistingDirectoryUrl;
|
|
19513
|
-
const rootDirectoryUrl = directoryContentItems.rootDirectoryUrl;
|
|
19514
|
-
|
|
19515
|
-
const htmlFor404AndAncestorDir = String(
|
|
19516
|
-
readFileSync(html404AndAncestorDirFileUrl),
|
|
19517
|
-
);
|
|
19518
|
-
const fileRelativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
|
|
19519
|
-
const ancestorDirectoryRelativeUrl = urlToRelativeUrl(
|
|
19520
|
-
ancestorDirectoryUrl,
|
|
19521
|
-
rootDirectoryUrl,
|
|
19522
|
-
);
|
|
19523
|
-
const replacers = {
|
|
19524
|
-
fileUrl: directoryListingUrlMocks
|
|
19525
|
-
? `@jsenv/core/${urlToRelativeUrl(url, jsenvCoreDirectoryUrl)}`
|
|
19526
|
-
: url,
|
|
19527
|
-
fileRelativeUrl,
|
|
19528
|
-
ancestorDirectoryUrl,
|
|
19529
|
-
ancestorDirectoryRelativeUrl,
|
|
19530
|
-
ancestorDirectoryNav: () =>
|
|
19531
|
-
generateDirectoryNav(ancestorDirectoryUrl, {
|
|
19532
|
-
rootDirectoryUrl,
|
|
19533
|
-
rootDirectoryUrlForServer:
|
|
19534
|
-
directoryContentItems.rootDirectoryUrlForServer,
|
|
19535
|
-
mainFilePath,
|
|
19536
|
-
}),
|
|
19537
|
-
ancestorDirectoryContent: () =>
|
|
19538
|
-
generateDirectoryContent(directoryContentItems, { mainFilePath }),
|
|
19539
|
-
};
|
|
19540
|
-
const html = replacePlaceholders$1(htmlFor404AndAncestorDir, replacers);
|
|
19541
|
-
return html;
|
|
19542
|
-
};
|
|
19543
|
-
const generateDirectoryNav = (
|
|
19544
|
-
entryDirectoryUrl,
|
|
19545
|
-
{ rootDirectoryUrl, rootDirectoryUrlForServer, mainFilePath },
|
|
19546
|
-
) => {
|
|
19547
|
-
const entryDirectoryRelativeUrl = urlToRelativeUrl(
|
|
19548
|
-
entryDirectoryUrl,
|
|
19549
|
-
rootDirectoryUrl,
|
|
19550
|
-
);
|
|
19551
|
-
const isDir =
|
|
19552
|
-
entryDirectoryRelativeUrl === "" || entryDirectoryRelativeUrl.endsWith("/");
|
|
19553
|
-
const rootDirectoryUrlName = urlToFilename$1(rootDirectoryUrl);
|
|
19554
|
-
const items = [];
|
|
19555
|
-
let dirPartsHtml = "";
|
|
19556
|
-
const parts = entryDirectoryRelativeUrl
|
|
19557
|
-
? `${rootDirectoryUrlName}/${entryDirectoryRelativeUrl.slice(0, -1)}`.split(
|
|
19558
|
-
"/",
|
|
19559
|
-
)
|
|
19560
|
-
: [rootDirectoryUrlName];
|
|
19561
|
-
let i = 0;
|
|
19562
|
-
while (i < parts.length) {
|
|
19563
|
-
const part = parts[i];
|
|
19564
|
-
const directoryRelativeUrl = `${parts.slice(1, i + 1).join("/")}`;
|
|
19565
|
-
const directoryUrl =
|
|
19566
|
-
directoryRelativeUrl === ""
|
|
19567
|
-
? rootDirectoryUrl
|
|
19568
|
-
: new URL(`${directoryRelativeUrl}/`, rootDirectoryUrl).href;
|
|
19569
|
-
let href =
|
|
19570
|
-
directoryUrl === rootDirectoryUrlForServer ||
|
|
19571
|
-
urlIsInsideOf(directoryUrl, rootDirectoryUrlForServer)
|
|
19572
|
-
? urlToRelativeUrl(directoryUrl, rootDirectoryUrlForServer)
|
|
19573
|
-
: directoryUrl;
|
|
19574
|
-
if (href === "") {
|
|
19575
|
-
href = `/${directoryContentMagicName}`;
|
|
19576
|
-
} else {
|
|
19577
|
-
href = `/${href}`;
|
|
19578
|
-
}
|
|
19579
|
-
const text = part;
|
|
19580
|
-
items.push({
|
|
19581
|
-
href,
|
|
19582
|
-
text,
|
|
19583
|
-
});
|
|
19584
|
-
i++;
|
|
19585
|
-
}
|
|
19586
|
-
i = 0;
|
|
19587
|
-
|
|
19588
|
-
const renderDirNavItem = ({ isCurrent, href, text }) => {
|
|
19589
|
-
const isServerRootDir = href === `/${directoryContentMagicName}`;
|
|
19590
|
-
if (isServerRootDir) {
|
|
19591
|
-
if (isCurrent) {
|
|
19592
|
-
return `
|
|
19593
|
-
<span class="directory_nav_item" data-current>
|
|
19594
|
-
<a class="directory_root_for_server" hot-decline href="/${mainFilePath}"></a>
|
|
19595
|
-
<span class="directory_name">${text}</span>
|
|
19596
|
-
</span>`;
|
|
19597
|
-
}
|
|
19598
|
-
return `
|
|
19599
|
-
<span class="directory_nav_item">
|
|
19600
|
-
<a class="directory_root_for_server" hot-decline href="/${mainFilePath}"></a>
|
|
19601
|
-
<a class="directory_name" hot-decline href="${href}">${text}</a>
|
|
19602
|
-
</span>`;
|
|
19603
|
-
}
|
|
19604
|
-
if (isCurrent) {
|
|
19605
|
-
return `
|
|
19606
|
-
<span class="directory_nav_item" data-current>
|
|
19607
|
-
<span class="directory_text">${text}</span>
|
|
19608
|
-
</span>`;
|
|
19609
|
-
}
|
|
19610
|
-
return `
|
|
19611
|
-
<span class="directory_nav_item">
|
|
19612
|
-
<a class="directory_text" hot-decline href="${href}">${text}</a>
|
|
19613
|
-
</span>`;
|
|
19614
|
-
};
|
|
19615
|
-
|
|
19616
|
-
for (const { href, text } of items) {
|
|
19617
|
-
const isLastPart = i === items.length - 1;
|
|
19618
|
-
dirPartsHtml += renderDirNavItem({
|
|
19619
|
-
isCurrent: isLastPart,
|
|
19620
|
-
href,
|
|
19621
|
-
text,
|
|
19622
|
-
});
|
|
19623
|
-
if (isLastPart) {
|
|
19624
|
-
break;
|
|
19625
|
-
}
|
|
19626
|
-
dirPartsHtml += `
|
|
19627
|
-
<span class="directory_separator">/</span>`;
|
|
19628
|
-
i++;
|
|
19629
|
-
}
|
|
19630
|
-
if (isDir) {
|
|
19631
|
-
dirPartsHtml += `
|
|
19632
|
-
<span class="directory_separator">/</span>`;
|
|
19633
|
-
}
|
|
19634
|
-
return dirPartsHtml;
|
|
19635
|
-
};
|
|
19636
|
-
const generateDirectoryContentItems = (
|
|
19637
|
-
directoryUrl,
|
|
19638
|
-
rootDirectoryUrlForServer,
|
|
19639
|
-
) => {
|
|
19640
|
-
let firstExistingDirectoryUrl = new URL("./", directoryUrl);
|
|
19641
|
-
while (!existsSync(firstExistingDirectoryUrl)) {
|
|
19642
|
-
firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
|
|
19643
|
-
if (!urlIsInsideOf(firstExistingDirectoryUrl, rootDirectoryUrlForServer)) {
|
|
19644
|
-
firstExistingDirectoryUrl = new URL(rootDirectoryUrlForServer);
|
|
19645
|
-
break;
|
|
19646
|
-
}
|
|
19647
|
-
}
|
|
19648
|
-
const directoryContentArray = readdirSync(firstExistingDirectoryUrl);
|
|
19649
|
-
const fileUrls = [];
|
|
19650
|
-
for (const filename of directoryContentArray) {
|
|
19651
|
-
const fileUrlObject = new URL(filename, firstExistingDirectoryUrl);
|
|
19652
|
-
fileUrls.push(fileUrlObject);
|
|
19653
|
-
}
|
|
19654
|
-
let rootDirectoryUrl = rootDirectoryUrlForServer;
|
|
19655
|
-
package_workspaces: {
|
|
19656
|
-
const packageDirectoryUrl = lookupPackageDirectory(
|
|
19657
|
-
rootDirectoryUrlForServer,
|
|
19658
|
-
);
|
|
19659
|
-
if (!packageDirectoryUrl) {
|
|
19660
|
-
break package_workspaces;
|
|
19661
|
-
}
|
|
19662
|
-
if (String(packageDirectoryUrl) === String(rootDirectoryUrlForServer)) {
|
|
19663
|
-
break package_workspaces;
|
|
19664
|
-
}
|
|
19665
|
-
rootDirectoryUrl = packageDirectoryUrl;
|
|
19666
|
-
if (
|
|
19667
|
-
String(firstExistingDirectoryUrl) === String(rootDirectoryUrlForServer)
|
|
19668
|
-
) {
|
|
19669
|
-
let packageContent;
|
|
19670
|
-
try {
|
|
19671
|
-
packageContent = JSON.parse(
|
|
19672
|
-
readFileSync(new URL("package.json", packageDirectoryUrl), "utf8"),
|
|
19673
|
-
);
|
|
19674
|
-
} catch {
|
|
19675
|
-
break package_workspaces;
|
|
19676
|
-
}
|
|
19677
|
-
const { workspaces } = packageContent;
|
|
19678
|
-
if (Array.isArray(workspaces)) {
|
|
19679
|
-
for (const workspace of workspaces) {
|
|
19680
|
-
const workspaceUrlObject = new URL(workspace, packageDirectoryUrl);
|
|
19681
|
-
const workspaceUrl = workspaceUrlObject.href;
|
|
19682
|
-
if (workspaceUrl.endsWith("*")) {
|
|
19683
|
-
const directoryUrl = ensurePathnameTrailingSlash(
|
|
19684
|
-
workspaceUrl.slice(0, -1),
|
|
19685
|
-
);
|
|
19686
|
-
fileUrls.push(new URL(directoryUrl));
|
|
19687
|
-
} else {
|
|
19688
|
-
fileUrls.push(ensurePathnameTrailingSlash(workspaceUrlObject));
|
|
19689
|
-
}
|
|
19690
|
-
}
|
|
19691
|
-
}
|
|
19692
|
-
}
|
|
19693
|
-
}
|
|
19694
|
-
|
|
19695
|
-
const sortedUrls = [];
|
|
19696
|
-
for (let fileUrl of fileUrls) {
|
|
19697
|
-
if (lstatSync(fileUrl).isDirectory()) {
|
|
19698
|
-
sortedUrls.push(ensurePathnameTrailingSlash(fileUrl));
|
|
19699
|
-
} else {
|
|
19700
|
-
sortedUrls.push(fileUrl);
|
|
19701
|
-
}
|
|
19702
|
-
}
|
|
19703
|
-
sortedUrls.sort((a, b) => {
|
|
19704
|
-
return comparePathnames(a.pathname, b.pathname);
|
|
19705
|
-
});
|
|
19706
|
-
|
|
19707
|
-
const items = [];
|
|
19708
|
-
for (const sortedUrl of sortedUrls) {
|
|
19709
|
-
const fileUrlRelativeToParent = urlToRelativeUrl(
|
|
19710
|
-
sortedUrl,
|
|
19711
|
-
firstExistingDirectoryUrl,
|
|
19712
|
-
);
|
|
19713
|
-
const fileUrlRelativeToServer = urlToRelativeUrl(
|
|
19714
|
-
sortedUrl,
|
|
19715
|
-
rootDirectoryUrlForServer,
|
|
19716
|
-
);
|
|
19717
|
-
const type = fileUrlRelativeToParent.endsWith("/") ? "dir" : "file";
|
|
19718
|
-
items.push({
|
|
19719
|
-
type,
|
|
19720
|
-
fileUrlRelativeToParent,
|
|
19721
|
-
fileUrlRelativeToServer,
|
|
19722
|
-
});
|
|
19723
|
-
}
|
|
19724
|
-
items.rootDirectoryUrlForServer = rootDirectoryUrlForServer;
|
|
19725
|
-
items.rootDirectoryUrl = rootDirectoryUrl;
|
|
19726
|
-
items.firstExistingDirectoryUrl = firstExistingDirectoryUrl;
|
|
19727
|
-
return items;
|
|
19728
|
-
};
|
|
19729
|
-
const generateDirectoryContent = (directoryContentItems, { mainFilePath }) => {
|
|
19730
|
-
if (directoryContentItems.length === 0) {
|
|
19731
|
-
return `<p class="directory_empty_message">Directory is empty</p>`;
|
|
19732
|
-
}
|
|
19733
|
-
let html = `<ul class="directory_content">`;
|
|
19734
|
-
for (const directoryContentItem of directoryContentItems) {
|
|
19735
|
-
const { type, fileUrlRelativeToParent, fileUrlRelativeToServer } =
|
|
19736
|
-
directoryContentItem;
|
|
19737
|
-
let href = fileUrlRelativeToServer;
|
|
19738
|
-
if (href === "") {
|
|
19739
|
-
href = `${directoryContentMagicName}`;
|
|
19740
|
-
}
|
|
19741
|
-
const isMainFile = href === mainFilePath;
|
|
19742
|
-
const mainFileAttr = isMainFile ? ` data-main-file` : "";
|
|
19743
|
-
html += `
|
|
19744
|
-
<li class="directory_child" data-type="${type}"${mainFileAttr}>
|
|
19745
|
-
<a href="/${href}" hot-decline>${fileUrlRelativeToParent}</a>
|
|
19746
|
-
</li>`;
|
|
19747
|
-
}
|
|
19748
|
-
html += `\n </ul>`;
|
|
19749
|
-
return html;
|
|
19750
|
-
};
|
|
19751
|
-
const replacePlaceholders$1 = (html, replacers) => {
|
|
19752
|
-
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
19753
|
-
const replacer = replacers[name];
|
|
19754
|
-
if (replacer === undefined) {
|
|
19755
|
-
return match;
|
|
19756
|
-
}
|
|
19757
|
-
if (typeof replacer === "function") {
|
|
19758
|
-
return replacer();
|
|
19759
|
-
}
|
|
19760
|
-
return replacer;
|
|
19761
|
-
});
|
|
20039
|
+
{
|
|
20040
|
+
name: "jsenv:file_url_fetching",
|
|
20041
|
+
appliesDuring: "*",
|
|
20042
|
+
fetchUrlContent: (urlInfo) => {
|
|
20043
|
+
if (!urlInfo.url.startsWith("file:")) {
|
|
20044
|
+
return null;
|
|
20045
|
+
}
|
|
20046
|
+
const { firstReference } = urlInfo;
|
|
20047
|
+
let { fsStat } = firstReference;
|
|
20048
|
+
if (!fsStat) {
|
|
20049
|
+
fsStat = readEntryStatSync(urlInfo.url, { nullIfNotFound: true });
|
|
20050
|
+
}
|
|
20051
|
+
const serveFile = (url) => {
|
|
20052
|
+
const contentType = CONTENT_TYPE.fromUrlExtension(url);
|
|
20053
|
+
const fileBuffer = readFileSync(new URL(url));
|
|
20054
|
+
const content = CONTENT_TYPE.isTextual(contentType)
|
|
20055
|
+
? String(fileBuffer)
|
|
20056
|
+
: fileBuffer;
|
|
20057
|
+
return {
|
|
20058
|
+
content,
|
|
20059
|
+
contentType,
|
|
20060
|
+
contentLength: fileBuffer.length,
|
|
20061
|
+
};
|
|
20062
|
+
};
|
|
20063
|
+
|
|
20064
|
+
return serveFile(urlInfo.url);
|
|
20065
|
+
},
|
|
20066
|
+
},
|
|
20067
|
+
];
|
|
19762
20068
|
};
|
|
19763
20069
|
|
|
19764
20070
|
const jsenvPluginProtocolHttp = ({ include }) => {
|
|
@@ -19815,10 +20121,11 @@ const jsenvPluginProtocolHttp = ({ include }) => {
|
|
|
19815
20121
|
return fileUrl;
|
|
19816
20122
|
},
|
|
19817
20123
|
fetchUrlContent: async (urlInfo) => {
|
|
19818
|
-
|
|
20124
|
+
const originalUrl = urlInfo.originalUrl;
|
|
20125
|
+
if (!originalUrl.startsWith("http")) {
|
|
19819
20126
|
return null;
|
|
19820
20127
|
}
|
|
19821
|
-
const response = await fetch(
|
|
20128
|
+
const response = await fetch(originalUrl);
|
|
19822
20129
|
const responseStatus = response.status;
|
|
19823
20130
|
if (responseStatus < 200 || responseStatus > 299) {
|
|
19824
20131
|
throw new Error(`unexpected response status ${responseStatus}`);
|
|
@@ -19851,103 +20158,6 @@ const asValidFilename = (string) => {
|
|
|
19851
20158
|
return string;
|
|
19852
20159
|
};
|
|
19853
20160
|
|
|
19854
|
-
const jsenvPluginInjections = (rawAssociations) => {
|
|
19855
|
-
let resolvedAssociations;
|
|
19856
|
-
|
|
19857
|
-
return {
|
|
19858
|
-
name: "jsenv:injections",
|
|
19859
|
-
appliesDuring: "*",
|
|
19860
|
-
init: (context) => {
|
|
19861
|
-
resolvedAssociations = URL_META.resolveAssociations(
|
|
19862
|
-
{ injectionsGetter: rawAssociations },
|
|
19863
|
-
context.rootDirectoryUrl,
|
|
19864
|
-
);
|
|
19865
|
-
},
|
|
19866
|
-
transformUrlContent: async (urlInfo) => {
|
|
19867
|
-
const { injectionsGetter } = URL_META.applyAssociations({
|
|
19868
|
-
url: asUrlWithoutSearch(urlInfo.url),
|
|
19869
|
-
associations: resolvedAssociations,
|
|
19870
|
-
});
|
|
19871
|
-
if (!injectionsGetter) {
|
|
19872
|
-
return null;
|
|
19873
|
-
}
|
|
19874
|
-
if (typeof injectionsGetter !== "function") {
|
|
19875
|
-
throw new TypeError("injectionsGetter must be a function");
|
|
19876
|
-
}
|
|
19877
|
-
const injections = await injectionsGetter(urlInfo);
|
|
19878
|
-
if (!injections) {
|
|
19879
|
-
return null;
|
|
19880
|
-
}
|
|
19881
|
-
const keys = Object.keys(injections);
|
|
19882
|
-
if (keys.length === 0) {
|
|
19883
|
-
return null;
|
|
19884
|
-
}
|
|
19885
|
-
return replacePlaceholders(urlInfo.content, injections, urlInfo);
|
|
19886
|
-
},
|
|
19887
|
-
};
|
|
19888
|
-
};
|
|
19889
|
-
|
|
19890
|
-
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
19891
|
-
const INJECTIONS = {
|
|
19892
|
-
optional: (value) => {
|
|
19893
|
-
return { [injectionSymbol]: "optional", value };
|
|
19894
|
-
},
|
|
19895
|
-
};
|
|
19896
|
-
|
|
19897
|
-
// we export this because it is imported by jsenv_plugin_placeholder.js and unit test
|
|
19898
|
-
const replacePlaceholders = (content, replacements, urlInfo) => {
|
|
19899
|
-
const magicSource = createMagicSource(content);
|
|
19900
|
-
for (const key of Object.keys(replacements)) {
|
|
19901
|
-
let index = content.indexOf(key);
|
|
19902
|
-
const replacement = replacements[key];
|
|
19903
|
-
let isOptional;
|
|
19904
|
-
let value;
|
|
19905
|
-
if (replacement && replacement[injectionSymbol]) {
|
|
19906
|
-
const valueBehindSymbol = replacement[injectionSymbol];
|
|
19907
|
-
isOptional = valueBehindSymbol === "optional";
|
|
19908
|
-
value = replacement.value;
|
|
19909
|
-
} else {
|
|
19910
|
-
value = replacement;
|
|
19911
|
-
}
|
|
19912
|
-
if (index === -1) {
|
|
19913
|
-
if (!isOptional) {
|
|
19914
|
-
urlInfo.context.logger.warn(
|
|
19915
|
-
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
19916
|
-
--- suggestion a ---
|
|
19917
|
-
Add "${key}" in that file.
|
|
19918
|
-
--- suggestion b ---
|
|
19919
|
-
Fix eventual typo in "${key}"?
|
|
19920
|
-
--- suggestion c ---
|
|
19921
|
-
Mark injection as optional using INJECTIONS.optional():
|
|
19922
|
-
import { INJECTIONS } from "@jsenv/core";
|
|
19923
|
-
|
|
19924
|
-
return {
|
|
19925
|
-
"${key}": INJECTIONS.optional(${JSON.stringify(value)}),
|
|
19926
|
-
};`,
|
|
19927
|
-
);
|
|
19928
|
-
}
|
|
19929
|
-
continue;
|
|
19930
|
-
}
|
|
19931
|
-
|
|
19932
|
-
while (index !== -1) {
|
|
19933
|
-
const start = index;
|
|
19934
|
-
const end = index + key.length;
|
|
19935
|
-
magicSource.replace({
|
|
19936
|
-
start,
|
|
19937
|
-
end,
|
|
19938
|
-
replacement:
|
|
19939
|
-
urlInfo.type === "js_classic" ||
|
|
19940
|
-
urlInfo.type === "js_module" ||
|
|
19941
|
-
urlInfo.type === "html"
|
|
19942
|
-
? JSON.stringify(value, null, " ")
|
|
19943
|
-
: value,
|
|
19944
|
-
});
|
|
19945
|
-
index = content.indexOf(key, end);
|
|
19946
|
-
}
|
|
19947
|
-
}
|
|
19948
|
-
return magicSource.toContentAndSourcemap();
|
|
19949
|
-
};
|
|
19950
|
-
|
|
19951
20161
|
/*
|
|
19952
20162
|
* Some code uses globals specific to Node.js in code meant to run in browsers...
|
|
19953
20163
|
* This plugin will replace some node globals to things compatible with web:
|
|
@@ -21269,8 +21479,8 @@ const getCorePlugins = ({
|
|
|
21269
21479
|
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
21270
21480
|
...(injections ? [jsenvPluginInjections(injections)] : []),
|
|
21271
21481
|
jsenvPluginTranspilation(transpilation),
|
|
21482
|
+
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
21272
21483
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
21273
|
-
...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []), // after inline as it needs inline script to be cooked
|
|
21274
21484
|
|
|
21275
21485
|
/* When resolving references the following applies by default:
|
|
21276
21486
|
- http urls are resolved by jsenvPluginHttpUrls
|
|
@@ -21284,7 +21494,6 @@ const getCorePlugins = ({
|
|
|
21284
21494
|
magicDirectoryIndex,
|
|
21285
21495
|
directoryListingUrlMocks,
|
|
21286
21496
|
}),
|
|
21287
|
-
|
|
21288
21497
|
{
|
|
21289
21498
|
name: "jsenv:resolve_root_as_main",
|
|
21290
21499
|
appliesDuring: "*",
|
|
@@ -21303,12 +21512,14 @@ const getCorePlugins = ({
|
|
|
21303
21512
|
: []),
|
|
21304
21513
|
jsenvPluginWebResolution(),
|
|
21305
21514
|
jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect),
|
|
21306
|
-
|
|
21307
21515
|
jsenvPluginVersionSearchParam(),
|
|
21516
|
+
|
|
21517
|
+
// "jsenvPluginSupervisor" MUST be after "jsenvPluginInlining" as it needs inline script to be cooked
|
|
21518
|
+
...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []),
|
|
21519
|
+
|
|
21308
21520
|
jsenvPluginCommonJsGlobals(),
|
|
21309
21521
|
jsenvPluginImportMetaScenarios(),
|
|
21310
21522
|
...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []),
|
|
21311
|
-
|
|
21312
21523
|
jsenvPluginNodeRuntime({ runtimeCompat }),
|
|
21313
21524
|
|
|
21314
21525
|
jsenvPluginImportMetaHot(),
|
|
@@ -21868,7 +22079,6 @@ const createBuildSpecifierManager = ({
|
|
|
21868
22079
|
type: reference.type,
|
|
21869
22080
|
expectedType: reference.expectedType,
|
|
21870
22081
|
specifier: reference.specifier,
|
|
21871
|
-
specifierPathname: reference.specifierPathname,
|
|
21872
22082
|
specifierLine: reference.specifierLine,
|
|
21873
22083
|
specifierColumn: reference.specifierColumn,
|
|
21874
22084
|
specifierStart: reference.specifierStart,
|
|
@@ -23317,33 +23527,33 @@ build ${entryPointKeys.length} entry points`);
|
|
|
23317
23527
|
|
|
23318
23528
|
const bundlers = {};
|
|
23319
23529
|
{
|
|
23320
|
-
rawKitchen.pluginController.
|
|
23530
|
+
for (const plugin of rawKitchen.pluginController.activePlugins) {
|
|
23321
23531
|
const bundle = plugin.bundle;
|
|
23322
23532
|
if (!bundle) {
|
|
23323
|
-
|
|
23533
|
+
continue;
|
|
23324
23534
|
}
|
|
23325
23535
|
if (typeof bundle !== "object") {
|
|
23326
23536
|
throw new Error(
|
|
23327
23537
|
`bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
|
|
23328
23538
|
);
|
|
23329
23539
|
}
|
|
23330
|
-
Object.keys(bundle)
|
|
23540
|
+
for (const type of Object.keys(bundle)) {
|
|
23331
23541
|
const bundleFunction = bundle[type];
|
|
23332
23542
|
if (!bundleFunction) {
|
|
23333
|
-
|
|
23543
|
+
continue;
|
|
23334
23544
|
}
|
|
23335
23545
|
const bundlerForThatType = bundlers[type];
|
|
23336
23546
|
if (bundlerForThatType) {
|
|
23337
23547
|
// first plugin to define a bundle hook wins
|
|
23338
|
-
|
|
23548
|
+
continue;
|
|
23339
23549
|
}
|
|
23340
23550
|
bundlers[type] = {
|
|
23341
23551
|
plugin,
|
|
23342
23552
|
bundleFunction: bundle[type],
|
|
23343
23553
|
urlInfoMap: new Map(),
|
|
23344
23554
|
};
|
|
23345
|
-
}
|
|
23346
|
-
}
|
|
23555
|
+
}
|
|
23556
|
+
}
|
|
23347
23557
|
const addToBundlerIfAny = (rawUrlInfo) => {
|
|
23348
23558
|
const bundler = bundlers[rawUrlInfo.type];
|
|
23349
23559
|
if (bundler) {
|
|
@@ -23663,43 +23873,6 @@ const WEB_URL_CONVERTER = {
|
|
|
23663
23873
|
},
|
|
23664
23874
|
};
|
|
23665
23875
|
|
|
23666
|
-
/*
|
|
23667
|
-
* This plugin is very special because it is here
|
|
23668
|
-
* to provide "serverEvents" used by other plugins
|
|
23669
|
-
*/
|
|
23670
|
-
|
|
23671
|
-
|
|
23672
|
-
const serverEventsClientFileUrl = new URL(
|
|
23673
|
-
"./js/server_events_client.js",
|
|
23674
|
-
import.meta.url,
|
|
23675
|
-
).href;
|
|
23676
|
-
|
|
23677
|
-
const jsenvPluginServerEventsClientInjection = ({ logs = true }) => {
|
|
23678
|
-
return {
|
|
23679
|
-
name: "jsenv:server_events_client_injection",
|
|
23680
|
-
appliesDuring: "*",
|
|
23681
|
-
transformUrlContent: {
|
|
23682
|
-
html: (urlInfo) => {
|
|
23683
|
-
const htmlAst = parseHtml({
|
|
23684
|
-
html: urlInfo.content,
|
|
23685
|
-
url: urlInfo.url,
|
|
23686
|
-
});
|
|
23687
|
-
injectJsenvScript(htmlAst, {
|
|
23688
|
-
src: serverEventsClientFileUrl,
|
|
23689
|
-
initCall: {
|
|
23690
|
-
callee: "window.__server_events__.setup",
|
|
23691
|
-
params: {
|
|
23692
|
-
logs,
|
|
23693
|
-
},
|
|
23694
|
-
},
|
|
23695
|
-
pluginName: "jsenv:server_events_client_injection",
|
|
23696
|
-
});
|
|
23697
|
-
return stringifyHtmlAst(htmlAst);
|
|
23698
|
-
},
|
|
23699
|
-
},
|
|
23700
|
-
};
|
|
23701
|
-
};
|
|
23702
|
-
|
|
23703
23876
|
const createServerEventsDispatcher = () => {
|
|
23704
23877
|
const clients = [];
|
|
23705
23878
|
const MAX_CLIENTS = 100;
|
|
@@ -23795,6 +23968,105 @@ const createServerEventsDispatcher = () => {
|
|
|
23795
23968
|
};
|
|
23796
23969
|
};
|
|
23797
23970
|
|
|
23971
|
+
/*
|
|
23972
|
+
* This plugin is very special because it is here
|
|
23973
|
+
* to provide "serverEvents" used by other plugins
|
|
23974
|
+
*/
|
|
23975
|
+
|
|
23976
|
+
|
|
23977
|
+
const serverEventsClientFileUrl = new URL(
|
|
23978
|
+
"./js/server_events_client.js",
|
|
23979
|
+
import.meta.url,
|
|
23980
|
+
).href;
|
|
23981
|
+
|
|
23982
|
+
const jsenvPluginServerEvents = ({ clientAutoreload }) => {
|
|
23983
|
+
let serverEventsDispatcher;
|
|
23984
|
+
|
|
23985
|
+
const { clientServerEventsConfig } = clientAutoreload;
|
|
23986
|
+
const { logs = true } = clientServerEventsConfig;
|
|
23987
|
+
|
|
23988
|
+
return {
|
|
23989
|
+
name: "jsenv:server_events",
|
|
23990
|
+
appliesDuring: "dev",
|
|
23991
|
+
effect: ({ kitchenContext, otherPlugins }) => {
|
|
23992
|
+
const allServerEvents = {};
|
|
23993
|
+
for (const otherPlugin of otherPlugins) {
|
|
23994
|
+
const { serverEvents } = otherPlugin;
|
|
23995
|
+
if (!serverEvents) {
|
|
23996
|
+
continue;
|
|
23997
|
+
}
|
|
23998
|
+
for (const serverEventName of Object.keys(serverEvents)) {
|
|
23999
|
+
// we could throw on serverEvent name conflict
|
|
24000
|
+
// we could throw if serverEvents[serverEventName] is not a function
|
|
24001
|
+
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
24002
|
+
}
|
|
24003
|
+
}
|
|
24004
|
+
const serverEventNames = Object.keys(allServerEvents);
|
|
24005
|
+
if (serverEventNames.length === 0) {
|
|
24006
|
+
return false;
|
|
24007
|
+
}
|
|
24008
|
+
serverEventsDispatcher = createServerEventsDispatcher();
|
|
24009
|
+
const onabort = () => {
|
|
24010
|
+
serverEventsDispatcher.destroy();
|
|
24011
|
+
};
|
|
24012
|
+
kitchenContext.signal.addEventListener("abort", onabort);
|
|
24013
|
+
for (const serverEventName of Object.keys(allServerEvents)) {
|
|
24014
|
+
const serverEventInfo = {
|
|
24015
|
+
...kitchenContext,
|
|
24016
|
+
// serverEventsDispatcher variable is safe, we can disable esling warning
|
|
24017
|
+
// eslint-disable-next-line no-loop-func
|
|
24018
|
+
sendServerEvent: (data) => {
|
|
24019
|
+
if (!serverEventsDispatcher) {
|
|
24020
|
+
// this can happen if a plugin wants to send a server event but
|
|
24021
|
+
// server is closing or the plugin got destroyed but still wants to do things
|
|
24022
|
+
// if plugin code is correctly written it is never supposed to happen
|
|
24023
|
+
// because it means a plugin is still trying to do stuff after being destroyed
|
|
24024
|
+
return;
|
|
24025
|
+
}
|
|
24026
|
+
serverEventsDispatcher.dispatch({
|
|
24027
|
+
type: serverEventName,
|
|
24028
|
+
data,
|
|
24029
|
+
});
|
|
24030
|
+
},
|
|
24031
|
+
};
|
|
24032
|
+
const serverEventInit = allServerEvents[serverEventName];
|
|
24033
|
+
serverEventInit(serverEventInfo);
|
|
24034
|
+
}
|
|
24035
|
+
return () => {
|
|
24036
|
+
kitchenContext.signal.removeEventListener("abort", onabort);
|
|
24037
|
+
serverEventsDispatcher.destroy();
|
|
24038
|
+
serverEventsDispatcher = undefined;
|
|
24039
|
+
};
|
|
24040
|
+
},
|
|
24041
|
+
serveWebsocket: async ({ websocket, request }) => {
|
|
24042
|
+
if (request.headers["sec-websocket-protocol"] !== "jsenv") {
|
|
24043
|
+
return false;
|
|
24044
|
+
}
|
|
24045
|
+
serverEventsDispatcher.addWebsocket(websocket, request);
|
|
24046
|
+
return true;
|
|
24047
|
+
},
|
|
24048
|
+
transformUrlContent: {
|
|
24049
|
+
html: (urlInfo) => {
|
|
24050
|
+
const htmlAst = parseHtml({
|
|
24051
|
+
html: urlInfo.content,
|
|
24052
|
+
url: urlInfo.url,
|
|
24053
|
+
});
|
|
24054
|
+
injectJsenvScript(htmlAst, {
|
|
24055
|
+
src: serverEventsClientFileUrl,
|
|
24056
|
+
initCall: {
|
|
24057
|
+
callee: "window.__server_events__.setup",
|
|
24058
|
+
params: {
|
|
24059
|
+
logs,
|
|
24060
|
+
},
|
|
24061
|
+
},
|
|
24062
|
+
pluginName: "jsenv:server_events",
|
|
24063
|
+
});
|
|
24064
|
+
return stringifyHtmlAst(htmlAst);
|
|
24065
|
+
},
|
|
24066
|
+
},
|
|
24067
|
+
};
|
|
24068
|
+
};
|
|
24069
|
+
|
|
23798
24070
|
const memoizeByFirstArgument = (compute) => {
|
|
23799
24071
|
const urlCache = new Map();
|
|
23800
24072
|
|
|
@@ -23952,10 +24224,11 @@ const startDevServer = async ({
|
|
|
23952
24224
|
});
|
|
23953
24225
|
|
|
23954
24226
|
const serverStopCallbackSet = new Set();
|
|
23955
|
-
const
|
|
24227
|
+
const serverStopAbortController = new AbortController();
|
|
23956
24228
|
serverStopCallbackSet.add(() => {
|
|
23957
|
-
|
|
24229
|
+
serverStopAbortController.abort();
|
|
23958
24230
|
});
|
|
24231
|
+
const serverStopAbortSignal = serverStopAbortController.signal;
|
|
23959
24232
|
const kitchenCache = new Map();
|
|
23960
24233
|
|
|
23961
24234
|
const finalServices = [];
|
|
@@ -24058,7 +24331,7 @@ const startDevServer = async ({
|
|
|
24058
24331
|
|
|
24059
24332
|
kitchen = createKitchen({
|
|
24060
24333
|
name: runtimeId,
|
|
24061
|
-
signal,
|
|
24334
|
+
signal: serverStopAbortSignal,
|
|
24062
24335
|
logLevel,
|
|
24063
24336
|
rootDirectoryUrl: sourceDirectoryUrl,
|
|
24064
24337
|
mainFilePath: sourceMainFilePath,
|
|
@@ -24067,6 +24340,7 @@ const startDevServer = async ({
|
|
|
24067
24340
|
runtimeCompat,
|
|
24068
24341
|
clientRuntimeCompat,
|
|
24069
24342
|
plugins: [
|
|
24343
|
+
jsenvPluginServerEvents({ clientAutoreload }),
|
|
24070
24344
|
...plugins,
|
|
24071
24345
|
...getCorePlugins({
|
|
24072
24346
|
rootDirectoryUrl: sourceDirectoryUrl,
|
|
@@ -24142,7 +24416,17 @@ const startDevServer = async ({
|
|
|
24142
24416
|
for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
|
|
24143
24417
|
const implicitUrlInfo =
|
|
24144
24418
|
urlInfoCreated.graph.getUrlInfo(implicitUrl);
|
|
24145
|
-
if (
|
|
24419
|
+
if (!implicitUrlInfo) {
|
|
24420
|
+
continue;
|
|
24421
|
+
}
|
|
24422
|
+
if (implicitUrlInfo.content === undefined) {
|
|
24423
|
+
// happens when we explicitely load an url with a search param
|
|
24424
|
+
// - it creates an implicit url info to the url without params
|
|
24425
|
+
// - we never explicitely request the url without search param so it has no content
|
|
24426
|
+
// in that case the underlying urlInfo cannot be invalidate by the implicit
|
|
24427
|
+
continue;
|
|
24428
|
+
}
|
|
24429
|
+
if (!implicitUrlInfo.isValid()) {
|
|
24146
24430
|
return false;
|
|
24147
24431
|
}
|
|
24148
24432
|
}
|
|
@@ -24161,41 +24445,6 @@ const startDevServer = async ({
|
|
|
24161
24445
|
serverStopCallbackSet.add(() => {
|
|
24162
24446
|
kitchen.pluginController.callHooks("destroy", kitchen.context);
|
|
24163
24447
|
});
|
|
24164
|
-
{
|
|
24165
|
-
const allServerEvents = {};
|
|
24166
|
-
kitchen.pluginController.plugins.forEach((plugin) => {
|
|
24167
|
-
const { serverEvents } = plugin;
|
|
24168
|
-
if (serverEvents) {
|
|
24169
|
-
Object.keys(serverEvents).forEach((serverEventName) => {
|
|
24170
|
-
// we could throw on serverEvent name conflict
|
|
24171
|
-
// we could throw if serverEvents[serverEventName] is not a function
|
|
24172
|
-
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
24173
|
-
});
|
|
24174
|
-
}
|
|
24175
|
-
});
|
|
24176
|
-
const serverEventNames = Object.keys(allServerEvents);
|
|
24177
|
-
if (serverEventNames.length > 0) {
|
|
24178
|
-
Object.keys(allServerEvents).forEach((serverEventName) => {
|
|
24179
|
-
const serverEventInfo = {
|
|
24180
|
-
...kitchen.context,
|
|
24181
|
-
sendServerEvent: (data) => {
|
|
24182
|
-
serverEventsDispatcher.dispatch({
|
|
24183
|
-
type: serverEventName,
|
|
24184
|
-
data,
|
|
24185
|
-
});
|
|
24186
|
-
},
|
|
24187
|
-
};
|
|
24188
|
-
const serverEventInit = allServerEvents[serverEventName];
|
|
24189
|
-
serverEventInit(serverEventInfo);
|
|
24190
|
-
});
|
|
24191
|
-
kitchen.pluginController.unshiftPlugin(
|
|
24192
|
-
jsenvPluginServerEventsClientInjection(
|
|
24193
|
-
clientAutoreload.clientServerEventsConfig,
|
|
24194
|
-
),
|
|
24195
|
-
);
|
|
24196
|
-
}
|
|
24197
|
-
}
|
|
24198
|
-
|
|
24199
24448
|
kitchenCache.set(runtimeId, kitchen);
|
|
24200
24449
|
onKitchenCreated(kitchen);
|
|
24201
24450
|
return kitchen;
|
|
@@ -24280,9 +24529,10 @@ const startDevServer = async ({
|
|
|
24280
24529
|
// If they match jsenv bypass cooking and returns 304
|
|
24281
24530
|
// This must not happen when a plugin uses "no-store" or "no-cache" as it means
|
|
24282
24531
|
// plugin logic wants to happens for every request to this url
|
|
24283
|
-
...(
|
|
24284
|
-
|
|
24285
|
-
|
|
24532
|
+
...(cacheIsDisabledInResponseHeader(urlInfoTargetedByCache)
|
|
24533
|
+
? {
|
|
24534
|
+
"cache-control": "no-store", // for inline file we force no-store when parent is no-store
|
|
24535
|
+
}
|
|
24286
24536
|
: {
|
|
24287
24537
|
"cache-control": `private,max-age=0,must-revalidate`,
|
|
24288
24538
|
// it's safe to use "_" separator because etag is encoded with base64 (see https://stackoverflow.com/a/13195197)
|
|
@@ -24383,13 +24633,20 @@ ${error.trace?.message}`);
|
|
|
24383
24633
|
};
|
|
24384
24634
|
}
|
|
24385
24635
|
},
|
|
24386
|
-
handleWebsocket: (websocket, { request }) => {
|
|
24636
|
+
handleWebsocket: async (websocket, { request }) => {
|
|
24387
24637
|
// if (true || logLevel === "debug") {
|
|
24388
24638
|
// console.log("handleWebsocket", websocket, request.headers);
|
|
24389
24639
|
// }
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
24640
|
+
const kitchen = getOrCreateKitchen(request);
|
|
24641
|
+
const serveWebsocketHookInfo = {
|
|
24642
|
+
request,
|
|
24643
|
+
websocket,
|
|
24644
|
+
context: kitchen.context,
|
|
24645
|
+
};
|
|
24646
|
+
await kitchen.pluginController.callAsyncHooksUntil(
|
|
24647
|
+
"serveWebsocket",
|
|
24648
|
+
serveWebsocketHookInfo,
|
|
24649
|
+
);
|
|
24393
24650
|
},
|
|
24394
24651
|
});
|
|
24395
24652
|
}
|
|
@@ -24483,6 +24740,13 @@ ${error.trace?.message}`);
|
|
|
24483
24740
|
};
|
|
24484
24741
|
};
|
|
24485
24742
|
|
|
24743
|
+
const cacheIsDisabledInResponseHeader = (urlInfo) => {
|
|
24744
|
+
return (
|
|
24745
|
+
urlInfo.headers["cache-control"] === "no-store" ||
|
|
24746
|
+
urlInfo.headers["cache-control"] === "no-cache"
|
|
24747
|
+
);
|
|
24748
|
+
};
|
|
24749
|
+
|
|
24486
24750
|
/*
|
|
24487
24751
|
* startBuildServer is mean to interact with the build files;
|
|
24488
24752
|
* files that will be deployed to production server(s).
|