@jsenv/core 39.11.2 → 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 -757
- 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 +6 -0
- 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 -333
- 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 -165
- package/dist/html/html_404_and_ancestor_dir.html +0 -203
- package/src/plugins/protocol_file/client/assets/directory.css +0 -133
- 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,346 +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
20019
|
let { fsStat } = firstReference;
|
|
19403
20020
|
if (!fsStat) {
|
|
19404
20021
|
fsStat = readEntryStatSync(urlInfo.url, { nullIfNotFound: true });
|
|
19405
20022
|
}
|
|
19406
|
-
const isDirectory = fsStat?.isDirectory();
|
|
19407
|
-
const { rootDirectoryUrl, request } = urlInfo.context;
|
|
19408
|
-
const serveFile = (url) => {
|
|
19409
|
-
const contentType = CONTENT_TYPE.fromUrlExtension(url);
|
|
19410
|
-
const fileBuffer = readFileSync(new URL(url));
|
|
19411
|
-
const content = CONTENT_TYPE.isTextual(contentType)
|
|
19412
|
-
? String(fileBuffer)
|
|
19413
|
-
: fileBuffer;
|
|
19414
|
-
return {
|
|
19415
|
-
content,
|
|
19416
|
-
contentType,
|
|
19417
|
-
contentLength: fileBuffer.length,
|
|
19418
|
-
};
|
|
19419
|
-
};
|
|
19420
|
-
|
|
19421
20023
|
if (!fsStat) {
|
|
19422
|
-
|
|
19423
|
-
const directoryContentItems = generateDirectoryContentItems(
|
|
19424
|
-
urlInfo.url,
|
|
19425
|
-
rootDirectoryUrl,
|
|
19426
|
-
);
|
|
19427
|
-
const html = generateHtmlForENOENT(
|
|
19428
|
-
urlInfo.url,
|
|
19429
|
-
directoryContentItems,
|
|
19430
|
-
directoryListingUrlMocks,
|
|
19431
|
-
);
|
|
19432
|
-
return {
|
|
19433
|
-
status: 404,
|
|
19434
|
-
contentType: "text/html",
|
|
19435
|
-
content: html,
|
|
19436
|
-
headers: {
|
|
19437
|
-
"cache-control": "no-cache",
|
|
19438
|
-
},
|
|
19439
|
-
};
|
|
19440
|
-
}
|
|
20024
|
+
return null;
|
|
19441
20025
|
}
|
|
19442
|
-
|
|
19443
|
-
|
|
19444
|
-
|
|
19445
|
-
const content = JSON.stringify(directoryContentArray, null, " ");
|
|
19446
|
-
return {
|
|
19447
|
-
type: "directory",
|
|
19448
|
-
contentType: "application/json",
|
|
19449
|
-
content,
|
|
19450
|
-
};
|
|
19451
|
-
}
|
|
19452
|
-
const acceptsHtml = request
|
|
19453
|
-
? pickContentType(request, ["text/html"])
|
|
19454
|
-
: false;
|
|
19455
|
-
if (acceptsHtml) {
|
|
19456
|
-
firstReference.expectedType = "html";
|
|
19457
|
-
const directoryUrl = urlInfo.url;
|
|
19458
|
-
const directoryContentItems = generateDirectoryContentItems(
|
|
19459
|
-
directoryUrl,
|
|
19460
|
-
rootDirectoryUrl,
|
|
19461
|
-
);
|
|
19462
|
-
const html = generateHtmlForDirectory(directoryContentItems);
|
|
19463
|
-
return {
|
|
19464
|
-
type: "html",
|
|
19465
|
-
contentType: "text/html",
|
|
19466
|
-
content: html,
|
|
19467
|
-
};
|
|
19468
|
-
}
|
|
19469
|
-
return {
|
|
19470
|
-
type: "directory",
|
|
19471
|
-
contentType: "application/json",
|
|
19472
|
-
content: JSON.stringify(directoryContentArray, null, " "),
|
|
19473
|
-
};
|
|
20026
|
+
const isDirectory = fsStat.isDirectory();
|
|
20027
|
+
if (!isDirectory) {
|
|
20028
|
+
return null;
|
|
19474
20029
|
}
|
|
19475
|
-
|
|
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
|
+
};
|
|
19476
20037
|
},
|
|
19477
20038
|
},
|
|
19478
|
-
|
|
19479
|
-
|
|
19480
|
-
|
|
19481
|
-
|
|
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
|
-
const htmlFor404AndAncestorDir = String(
|
|
19509
|
-
readFileSync(html404AndAncestorDirFileUrl),
|
|
19510
|
-
);
|
|
19511
|
-
const fileRelativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
|
|
19512
|
-
const ancestorDirectoryRelativeUrl = urlToRelativeUrl(
|
|
19513
|
-
ancestorDirectoryUrl,
|
|
19514
|
-
rootDirectoryUrl,
|
|
19515
|
-
);
|
|
19516
|
-
const replacers = {
|
|
19517
|
-
fileUrl: directoryListingUrlMocks
|
|
19518
|
-
? `@jsenv/core/${urlToRelativeUrl(url, jsenvCoreDirectoryUrl)}`
|
|
19519
|
-
: url,
|
|
19520
|
-
fileRelativeUrl,
|
|
19521
|
-
ancestorDirectoryUrl,
|
|
19522
|
-
ancestorDirectoryRelativeUrl,
|
|
19523
|
-
ancestorDirectoryNav: () =>
|
|
19524
|
-
generateDirectoryNav(ancestorDirectoryUrl, {
|
|
19525
|
-
rootDirectoryUrl,
|
|
19526
|
-
rootDirectoryUrlForServer:
|
|
19527
|
-
directoryContentItems.rootDirectoryUrlForServer,
|
|
19528
|
-
}),
|
|
19529
|
-
ancestorDirectoryContent: () =>
|
|
19530
|
-
generateDirectoryContent(directoryContentItems),
|
|
19531
|
-
};
|
|
19532
|
-
const html = replacePlaceholders$1(htmlFor404AndAncestorDir, replacers);
|
|
19533
|
-
return html;
|
|
19534
|
-
};
|
|
19535
|
-
const generateDirectoryNav = (
|
|
19536
|
-
entryDirectoryUrl,
|
|
19537
|
-
{ rootDirectoryUrl, rootDirectoryUrlForServer },
|
|
19538
|
-
) => {
|
|
19539
|
-
const entryDirectoryRelativeUrl = urlToRelativeUrl(
|
|
19540
|
-
entryDirectoryUrl,
|
|
19541
|
-
rootDirectoryUrl,
|
|
19542
|
-
);
|
|
19543
|
-
const isDir =
|
|
19544
|
-
entryDirectoryRelativeUrl === "" || entryDirectoryRelativeUrl.endsWith("/");
|
|
19545
|
-
const rootDirectoryUrlName = urlToFilename$1(rootDirectoryUrl);
|
|
19546
|
-
const items = [];
|
|
19547
|
-
let dirPartsHtml = "";
|
|
19548
|
-
const parts = entryDirectoryRelativeUrl
|
|
19549
|
-
? `${rootDirectoryUrlName}/${entryDirectoryRelativeUrl.slice(0, -1)}`.split(
|
|
19550
|
-
"/",
|
|
19551
|
-
)
|
|
19552
|
-
: [rootDirectoryUrlName];
|
|
19553
|
-
let i = 0;
|
|
19554
|
-
while (i < parts.length) {
|
|
19555
|
-
const part = parts[i];
|
|
19556
|
-
const directoryRelativeUrl = `${parts.slice(1, i + 1).join("/")}`;
|
|
19557
|
-
const directoryUrl =
|
|
19558
|
-
directoryRelativeUrl === ""
|
|
19559
|
-
? rootDirectoryUrl
|
|
19560
|
-
: new URL(`${directoryRelativeUrl}/`, rootDirectoryUrl).href;
|
|
19561
|
-
let href =
|
|
19562
|
-
directoryUrl === rootDirectoryUrlForServer ||
|
|
19563
|
-
urlIsInsideOf(directoryUrl, rootDirectoryUrlForServer)
|
|
19564
|
-
? urlToRelativeUrl(directoryUrl, rootDirectoryUrlForServer)
|
|
19565
|
-
: directoryUrl;
|
|
19566
|
-
if (href === "") {
|
|
19567
|
-
href = `/${directoryContentMagicName}`;
|
|
19568
|
-
} else {
|
|
19569
|
-
href = `/${href}`;
|
|
19570
|
-
}
|
|
19571
|
-
const text = part;
|
|
19572
|
-
items.push({
|
|
19573
|
-
href,
|
|
19574
|
-
text,
|
|
19575
|
-
});
|
|
19576
|
-
i++;
|
|
19577
|
-
}
|
|
19578
|
-
i = 0;
|
|
19579
|
-
for (const { href, text } of items) {
|
|
19580
|
-
const isLastPart = i === items.length - 1;
|
|
19581
|
-
if (isLastPart) {
|
|
19582
|
-
dirPartsHtml += `
|
|
19583
|
-
<span class="directory_nav_item" data-current>
|
|
19584
|
-
${text}
|
|
19585
|
-
</span>`;
|
|
19586
|
-
break;
|
|
19587
|
-
}
|
|
19588
|
-
dirPartsHtml += `
|
|
19589
|
-
<a class="directory_nav_item" href="${href}">
|
|
19590
|
-
${text}
|
|
19591
|
-
</a>`;
|
|
19592
|
-
dirPartsHtml += `
|
|
19593
|
-
<span class="directory_separator">/</span>`;
|
|
19594
|
-
i++;
|
|
19595
|
-
}
|
|
19596
|
-
if (isDir) {
|
|
19597
|
-
dirPartsHtml += `
|
|
19598
|
-
<span class="directory_separator">/</span>`;
|
|
19599
|
-
}
|
|
19600
|
-
return dirPartsHtml;
|
|
19601
|
-
};
|
|
19602
|
-
const generateDirectoryContentItems = (
|
|
19603
|
-
directoryUrl,
|
|
19604
|
-
rootDirectoryUrlForServer,
|
|
19605
|
-
) => {
|
|
19606
|
-
let firstExistingDirectoryUrl = new URL("./", directoryUrl);
|
|
19607
|
-
while (!existsSync(firstExistingDirectoryUrl)) {
|
|
19608
|
-
firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
|
|
19609
|
-
if (!urlIsInsideOf(firstExistingDirectoryUrl, rootDirectoryUrlForServer)) {
|
|
19610
|
-
firstExistingDirectoryUrl = new URL(rootDirectoryUrlForServer);
|
|
19611
|
-
break;
|
|
19612
|
-
}
|
|
19613
|
-
}
|
|
19614
|
-
const directoryContentArray = readdirSync(firstExistingDirectoryUrl);
|
|
19615
|
-
const fileUrls = [];
|
|
19616
|
-
for (const filename of directoryContentArray) {
|
|
19617
|
-
const fileUrlObject = new URL(filename, firstExistingDirectoryUrl);
|
|
19618
|
-
fileUrls.push(fileUrlObject);
|
|
19619
|
-
}
|
|
19620
|
-
let rootDirectoryUrl = rootDirectoryUrlForServer;
|
|
19621
|
-
package_workspaces: {
|
|
19622
|
-
const packageDirectoryUrl = lookupPackageDirectory(
|
|
19623
|
-
rootDirectoryUrlForServer,
|
|
19624
|
-
);
|
|
19625
|
-
if (!packageDirectoryUrl) {
|
|
19626
|
-
break package_workspaces;
|
|
19627
|
-
}
|
|
19628
|
-
if (String(packageDirectoryUrl) === String(rootDirectoryUrlForServer)) {
|
|
19629
|
-
break package_workspaces;
|
|
19630
|
-
}
|
|
19631
|
-
rootDirectoryUrl = packageDirectoryUrl;
|
|
19632
|
-
if (
|
|
19633
|
-
String(firstExistingDirectoryUrl) === String(rootDirectoryUrlForServer)
|
|
19634
|
-
) {
|
|
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
|
-
|
|
19661
|
-
const sortedUrls = [];
|
|
19662
|
-
for (let fileUrl of fileUrls) {
|
|
19663
|
-
if (lstatSync(fileUrl).isDirectory()) {
|
|
19664
|
-
sortedUrls.push(ensurePathnameTrailingSlash(fileUrl));
|
|
19665
|
-
} else {
|
|
19666
|
-
sortedUrls.push(fileUrl);
|
|
19667
|
-
}
|
|
19668
|
-
}
|
|
19669
|
-
sortedUrls.sort((a, b) => {
|
|
19670
|
-
return comparePathnames(a.pathname, b.pathname);
|
|
19671
|
-
});
|
|
19672
|
-
|
|
19673
|
-
const items = [];
|
|
19674
|
-
for (const sortedUrl of sortedUrls) {
|
|
19675
|
-
const fileUrlRelativeToParent = urlToRelativeUrl(
|
|
19676
|
-
sortedUrl,
|
|
19677
|
-
firstExistingDirectoryUrl,
|
|
19678
|
-
);
|
|
19679
|
-
const fileUrlRelativeToServer = urlToRelativeUrl(
|
|
19680
|
-
sortedUrl,
|
|
19681
|
-
rootDirectoryUrlForServer,
|
|
19682
|
-
);
|
|
19683
|
-
const type = fileUrlRelativeToParent.endsWith("/") ? "dir" : "file";
|
|
19684
|
-
items.push({
|
|
19685
|
-
type,
|
|
19686
|
-
fileUrlRelativeToParent,
|
|
19687
|
-
fileUrlRelativeToServer,
|
|
19688
|
-
});
|
|
19689
|
-
}
|
|
19690
|
-
items.rootDirectoryUrlForServer = rootDirectoryUrlForServer;
|
|
19691
|
-
items.rootDirectoryUrl = rootDirectoryUrl;
|
|
19692
|
-
items.firstExistingDirectoryUrl = firstExistingDirectoryUrl;
|
|
19693
|
-
return items;
|
|
19694
|
-
};
|
|
19695
|
-
const generateDirectoryContent = (directoryContentItems) => {
|
|
19696
|
-
if (directoryContentItems.length === 0) {
|
|
19697
|
-
return `<p class="directory_empty_message">Directory is empty</p>`;
|
|
19698
|
-
}
|
|
19699
|
-
let html = `<ul class="directory_content">`;
|
|
19700
|
-
for (const directoryContentItem of directoryContentItems) {
|
|
19701
|
-
const { type, fileUrlRelativeToParent, fileUrlRelativeToServer } =
|
|
19702
|
-
directoryContentItem;
|
|
19703
|
-
let href = fileUrlRelativeToServer;
|
|
19704
|
-
if (href === "") {
|
|
19705
|
-
href = `${directoryContentMagicName}`;
|
|
19706
|
-
}
|
|
19707
|
-
html += `
|
|
19708
|
-
<li class="directory_child" data-type="${type}">
|
|
19709
|
-
<a href="/${href}">${fileUrlRelativeToParent}</a>
|
|
19710
|
-
</li>`;
|
|
19711
|
-
}
|
|
19712
|
-
html += `\n </ul>`;
|
|
19713
|
-
return html;
|
|
19714
|
-
};
|
|
19715
|
-
const replacePlaceholders$1 = (html, replacers) => {
|
|
19716
|
-
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
19717
|
-
const replacer = replacers[name];
|
|
19718
|
-
if (replacer === undefined) {
|
|
19719
|
-
return match;
|
|
19720
|
-
}
|
|
19721
|
-
if (typeof replacer === "function") {
|
|
19722
|
-
return replacer();
|
|
19723
|
-
}
|
|
19724
|
-
return replacer;
|
|
19725
|
-
});
|
|
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
|
+
];
|
|
19726
20068
|
};
|
|
19727
20069
|
|
|
19728
20070
|
const jsenvPluginProtocolHttp = ({ include }) => {
|
|
@@ -19779,10 +20121,11 @@ const jsenvPluginProtocolHttp = ({ include }) => {
|
|
|
19779
20121
|
return fileUrl;
|
|
19780
20122
|
},
|
|
19781
20123
|
fetchUrlContent: async (urlInfo) => {
|
|
19782
|
-
|
|
20124
|
+
const originalUrl = urlInfo.originalUrl;
|
|
20125
|
+
if (!originalUrl.startsWith("http")) {
|
|
19783
20126
|
return null;
|
|
19784
20127
|
}
|
|
19785
|
-
const response = await fetch(
|
|
20128
|
+
const response = await fetch(originalUrl);
|
|
19786
20129
|
const responseStatus = response.status;
|
|
19787
20130
|
if (responseStatus < 200 || responseStatus > 299) {
|
|
19788
20131
|
throw new Error(`unexpected response status ${responseStatus}`);
|
|
@@ -19815,103 +20158,6 @@ const asValidFilename = (string) => {
|
|
|
19815
20158
|
return string;
|
|
19816
20159
|
};
|
|
19817
20160
|
|
|
19818
|
-
const jsenvPluginInjections = (rawAssociations) => {
|
|
19819
|
-
let resolvedAssociations;
|
|
19820
|
-
|
|
19821
|
-
return {
|
|
19822
|
-
name: "jsenv:injections",
|
|
19823
|
-
appliesDuring: "*",
|
|
19824
|
-
init: (context) => {
|
|
19825
|
-
resolvedAssociations = URL_META.resolveAssociations(
|
|
19826
|
-
{ injectionsGetter: rawAssociations },
|
|
19827
|
-
context.rootDirectoryUrl,
|
|
19828
|
-
);
|
|
19829
|
-
},
|
|
19830
|
-
transformUrlContent: async (urlInfo) => {
|
|
19831
|
-
const { injectionsGetter } = URL_META.applyAssociations({
|
|
19832
|
-
url: asUrlWithoutSearch(urlInfo.url),
|
|
19833
|
-
associations: resolvedAssociations,
|
|
19834
|
-
});
|
|
19835
|
-
if (!injectionsGetter) {
|
|
19836
|
-
return null;
|
|
19837
|
-
}
|
|
19838
|
-
if (typeof injectionsGetter !== "function") {
|
|
19839
|
-
throw new TypeError("injectionsGetter must be a function");
|
|
19840
|
-
}
|
|
19841
|
-
const injections = await injectionsGetter(urlInfo);
|
|
19842
|
-
if (!injections) {
|
|
19843
|
-
return null;
|
|
19844
|
-
}
|
|
19845
|
-
const keys = Object.keys(injections);
|
|
19846
|
-
if (keys.length === 0) {
|
|
19847
|
-
return null;
|
|
19848
|
-
}
|
|
19849
|
-
return replacePlaceholders(urlInfo.content, injections, urlInfo);
|
|
19850
|
-
},
|
|
19851
|
-
};
|
|
19852
|
-
};
|
|
19853
|
-
|
|
19854
|
-
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
19855
|
-
const INJECTIONS = {
|
|
19856
|
-
optional: (value) => {
|
|
19857
|
-
return { [injectionSymbol]: "optional", value };
|
|
19858
|
-
},
|
|
19859
|
-
};
|
|
19860
|
-
|
|
19861
|
-
// we export this because it is imported by jsenv_plugin_placeholder.js and unit test
|
|
19862
|
-
const replacePlaceholders = (content, replacements, urlInfo) => {
|
|
19863
|
-
const magicSource = createMagicSource(content);
|
|
19864
|
-
for (const key of Object.keys(replacements)) {
|
|
19865
|
-
let index = content.indexOf(key);
|
|
19866
|
-
const replacement = replacements[key];
|
|
19867
|
-
let isOptional;
|
|
19868
|
-
let value;
|
|
19869
|
-
if (replacement && replacement[injectionSymbol]) {
|
|
19870
|
-
const valueBehindSymbol = replacement[injectionSymbol];
|
|
19871
|
-
isOptional = valueBehindSymbol === "optional";
|
|
19872
|
-
value = replacement.value;
|
|
19873
|
-
} else {
|
|
19874
|
-
value = replacement;
|
|
19875
|
-
}
|
|
19876
|
-
if (index === -1) {
|
|
19877
|
-
if (!isOptional) {
|
|
19878
|
-
urlInfo.context.logger.warn(
|
|
19879
|
-
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
19880
|
-
--- suggestion a ---
|
|
19881
|
-
Add "${key}" in that file.
|
|
19882
|
-
--- suggestion b ---
|
|
19883
|
-
Fix eventual typo in "${key}"?
|
|
19884
|
-
--- suggestion c ---
|
|
19885
|
-
Mark injection as optional using INJECTIONS.optional():
|
|
19886
|
-
import { INJECTIONS } from "@jsenv/core";
|
|
19887
|
-
|
|
19888
|
-
return {
|
|
19889
|
-
"${key}": INJECTIONS.optional(${JSON.stringify(value)}),
|
|
19890
|
-
};`,
|
|
19891
|
-
);
|
|
19892
|
-
}
|
|
19893
|
-
continue;
|
|
19894
|
-
}
|
|
19895
|
-
|
|
19896
|
-
while (index !== -1) {
|
|
19897
|
-
const start = index;
|
|
19898
|
-
const end = index + key.length;
|
|
19899
|
-
magicSource.replace({
|
|
19900
|
-
start,
|
|
19901
|
-
end,
|
|
19902
|
-
replacement:
|
|
19903
|
-
urlInfo.type === "js_classic" ||
|
|
19904
|
-
urlInfo.type === "js_module" ||
|
|
19905
|
-
urlInfo.type === "html"
|
|
19906
|
-
? JSON.stringify(value, null, " ")
|
|
19907
|
-
: value,
|
|
19908
|
-
});
|
|
19909
|
-
index = content.indexOf(key, end);
|
|
19910
|
-
}
|
|
19911
|
-
}
|
|
19912
|
-
return magicSource.toContentAndSourcemap();
|
|
19913
|
-
};
|
|
19914
|
-
|
|
19915
20161
|
/*
|
|
19916
20162
|
* Some code uses globals specific to Node.js in code meant to run in browsers...
|
|
19917
20163
|
* This plugin will replace some node globals to things compatible with web:
|
|
@@ -21233,8 +21479,8 @@ const getCorePlugins = ({
|
|
|
21233
21479
|
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
21234
21480
|
...(injections ? [jsenvPluginInjections(injections)] : []),
|
|
21235
21481
|
jsenvPluginTranspilation(transpilation),
|
|
21482
|
+
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
21236
21483
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
21237
|
-
...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []), // after inline as it needs inline script to be cooked
|
|
21238
21484
|
|
|
21239
21485
|
/* When resolving references the following applies by default:
|
|
21240
21486
|
- http urls are resolved by jsenvPluginHttpUrls
|
|
@@ -21248,7 +21494,6 @@ const getCorePlugins = ({
|
|
|
21248
21494
|
magicDirectoryIndex,
|
|
21249
21495
|
directoryListingUrlMocks,
|
|
21250
21496
|
}),
|
|
21251
|
-
|
|
21252
21497
|
{
|
|
21253
21498
|
name: "jsenv:resolve_root_as_main",
|
|
21254
21499
|
appliesDuring: "*",
|
|
@@ -21267,12 +21512,14 @@ const getCorePlugins = ({
|
|
|
21267
21512
|
: []),
|
|
21268
21513
|
jsenvPluginWebResolution(),
|
|
21269
21514
|
jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect),
|
|
21270
|
-
|
|
21271
21515
|
jsenvPluginVersionSearchParam(),
|
|
21516
|
+
|
|
21517
|
+
// "jsenvPluginSupervisor" MUST be after "jsenvPluginInlining" as it needs inline script to be cooked
|
|
21518
|
+
...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []),
|
|
21519
|
+
|
|
21272
21520
|
jsenvPluginCommonJsGlobals(),
|
|
21273
21521
|
jsenvPluginImportMetaScenarios(),
|
|
21274
21522
|
...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []),
|
|
21275
|
-
|
|
21276
21523
|
jsenvPluginNodeRuntime({ runtimeCompat }),
|
|
21277
21524
|
|
|
21278
21525
|
jsenvPluginImportMetaHot(),
|
|
@@ -21832,7 +22079,6 @@ const createBuildSpecifierManager = ({
|
|
|
21832
22079
|
type: reference.type,
|
|
21833
22080
|
expectedType: reference.expectedType,
|
|
21834
22081
|
specifier: reference.specifier,
|
|
21835
|
-
specifierPathname: reference.specifierPathname,
|
|
21836
22082
|
specifierLine: reference.specifierLine,
|
|
21837
22083
|
specifierColumn: reference.specifierColumn,
|
|
21838
22084
|
specifierStart: reference.specifierStart,
|
|
@@ -23281,33 +23527,33 @@ build ${entryPointKeys.length} entry points`);
|
|
|
23281
23527
|
|
|
23282
23528
|
const bundlers = {};
|
|
23283
23529
|
{
|
|
23284
|
-
rawKitchen.pluginController.
|
|
23530
|
+
for (const plugin of rawKitchen.pluginController.activePlugins) {
|
|
23285
23531
|
const bundle = plugin.bundle;
|
|
23286
23532
|
if (!bundle) {
|
|
23287
|
-
|
|
23533
|
+
continue;
|
|
23288
23534
|
}
|
|
23289
23535
|
if (typeof bundle !== "object") {
|
|
23290
23536
|
throw new Error(
|
|
23291
23537
|
`bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
|
|
23292
23538
|
);
|
|
23293
23539
|
}
|
|
23294
|
-
Object.keys(bundle)
|
|
23540
|
+
for (const type of Object.keys(bundle)) {
|
|
23295
23541
|
const bundleFunction = bundle[type];
|
|
23296
23542
|
if (!bundleFunction) {
|
|
23297
|
-
|
|
23543
|
+
continue;
|
|
23298
23544
|
}
|
|
23299
23545
|
const bundlerForThatType = bundlers[type];
|
|
23300
23546
|
if (bundlerForThatType) {
|
|
23301
23547
|
// first plugin to define a bundle hook wins
|
|
23302
|
-
|
|
23548
|
+
continue;
|
|
23303
23549
|
}
|
|
23304
23550
|
bundlers[type] = {
|
|
23305
23551
|
plugin,
|
|
23306
23552
|
bundleFunction: bundle[type],
|
|
23307
23553
|
urlInfoMap: new Map(),
|
|
23308
23554
|
};
|
|
23309
|
-
}
|
|
23310
|
-
}
|
|
23555
|
+
}
|
|
23556
|
+
}
|
|
23311
23557
|
const addToBundlerIfAny = (rawUrlInfo) => {
|
|
23312
23558
|
const bundler = bundlers[rawUrlInfo.type];
|
|
23313
23559
|
if (bundler) {
|
|
@@ -23627,43 +23873,6 @@ const WEB_URL_CONVERTER = {
|
|
|
23627
23873
|
},
|
|
23628
23874
|
};
|
|
23629
23875
|
|
|
23630
|
-
/*
|
|
23631
|
-
* This plugin is very special because it is here
|
|
23632
|
-
* to provide "serverEvents" used by other plugins
|
|
23633
|
-
*/
|
|
23634
|
-
|
|
23635
|
-
|
|
23636
|
-
const serverEventsClientFileUrl = new URL(
|
|
23637
|
-
"./js/server_events_client.js",
|
|
23638
|
-
import.meta.url,
|
|
23639
|
-
).href;
|
|
23640
|
-
|
|
23641
|
-
const jsenvPluginServerEventsClientInjection = ({ logs = true }) => {
|
|
23642
|
-
return {
|
|
23643
|
-
name: "jsenv:server_events_client_injection",
|
|
23644
|
-
appliesDuring: "*",
|
|
23645
|
-
transformUrlContent: {
|
|
23646
|
-
html: (urlInfo) => {
|
|
23647
|
-
const htmlAst = parseHtml({
|
|
23648
|
-
html: urlInfo.content,
|
|
23649
|
-
url: urlInfo.url,
|
|
23650
|
-
});
|
|
23651
|
-
injectJsenvScript(htmlAst, {
|
|
23652
|
-
src: serverEventsClientFileUrl,
|
|
23653
|
-
initCall: {
|
|
23654
|
-
callee: "window.__server_events__.setup",
|
|
23655
|
-
params: {
|
|
23656
|
-
logs,
|
|
23657
|
-
},
|
|
23658
|
-
},
|
|
23659
|
-
pluginName: "jsenv:server_events_client_injection",
|
|
23660
|
-
});
|
|
23661
|
-
return stringifyHtmlAst(htmlAst);
|
|
23662
|
-
},
|
|
23663
|
-
},
|
|
23664
|
-
};
|
|
23665
|
-
};
|
|
23666
|
-
|
|
23667
23876
|
const createServerEventsDispatcher = () => {
|
|
23668
23877
|
const clients = [];
|
|
23669
23878
|
const MAX_CLIENTS = 100;
|
|
@@ -23759,6 +23968,105 @@ const createServerEventsDispatcher = () => {
|
|
|
23759
23968
|
};
|
|
23760
23969
|
};
|
|
23761
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
|
+
|
|
23762
24070
|
const memoizeByFirstArgument = (compute) => {
|
|
23763
24071
|
const urlCache = new Map();
|
|
23764
24072
|
|
|
@@ -23916,10 +24224,11 @@ const startDevServer = async ({
|
|
|
23916
24224
|
});
|
|
23917
24225
|
|
|
23918
24226
|
const serverStopCallbackSet = new Set();
|
|
23919
|
-
const
|
|
24227
|
+
const serverStopAbortController = new AbortController();
|
|
23920
24228
|
serverStopCallbackSet.add(() => {
|
|
23921
|
-
|
|
24229
|
+
serverStopAbortController.abort();
|
|
23922
24230
|
});
|
|
24231
|
+
const serverStopAbortSignal = serverStopAbortController.signal;
|
|
23923
24232
|
const kitchenCache = new Map();
|
|
23924
24233
|
|
|
23925
24234
|
const finalServices = [];
|
|
@@ -24022,7 +24331,7 @@ const startDevServer = async ({
|
|
|
24022
24331
|
|
|
24023
24332
|
kitchen = createKitchen({
|
|
24024
24333
|
name: runtimeId,
|
|
24025
|
-
signal,
|
|
24334
|
+
signal: serverStopAbortSignal,
|
|
24026
24335
|
logLevel,
|
|
24027
24336
|
rootDirectoryUrl: sourceDirectoryUrl,
|
|
24028
24337
|
mainFilePath: sourceMainFilePath,
|
|
@@ -24031,6 +24340,7 @@ const startDevServer = async ({
|
|
|
24031
24340
|
runtimeCompat,
|
|
24032
24341
|
clientRuntimeCompat,
|
|
24033
24342
|
plugins: [
|
|
24343
|
+
jsenvPluginServerEvents({ clientAutoreload }),
|
|
24034
24344
|
...plugins,
|
|
24035
24345
|
...getCorePlugins({
|
|
24036
24346
|
rootDirectoryUrl: sourceDirectoryUrl,
|
|
@@ -24106,7 +24416,17 @@ const startDevServer = async ({
|
|
|
24106
24416
|
for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
|
|
24107
24417
|
const implicitUrlInfo =
|
|
24108
24418
|
urlInfoCreated.graph.getUrlInfo(implicitUrl);
|
|
24109
|
-
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()) {
|
|
24110
24430
|
return false;
|
|
24111
24431
|
}
|
|
24112
24432
|
}
|
|
@@ -24125,41 +24445,6 @@ const startDevServer = async ({
|
|
|
24125
24445
|
serverStopCallbackSet.add(() => {
|
|
24126
24446
|
kitchen.pluginController.callHooks("destroy", kitchen.context);
|
|
24127
24447
|
});
|
|
24128
|
-
{
|
|
24129
|
-
const allServerEvents = {};
|
|
24130
|
-
kitchen.pluginController.plugins.forEach((plugin) => {
|
|
24131
|
-
const { serverEvents } = plugin;
|
|
24132
|
-
if (serverEvents) {
|
|
24133
|
-
Object.keys(serverEvents).forEach((serverEventName) => {
|
|
24134
|
-
// we could throw on serverEvent name conflict
|
|
24135
|
-
// we could throw if serverEvents[serverEventName] is not a function
|
|
24136
|
-
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
24137
|
-
});
|
|
24138
|
-
}
|
|
24139
|
-
});
|
|
24140
|
-
const serverEventNames = Object.keys(allServerEvents);
|
|
24141
|
-
if (serverEventNames.length > 0) {
|
|
24142
|
-
Object.keys(allServerEvents).forEach((serverEventName) => {
|
|
24143
|
-
const serverEventInfo = {
|
|
24144
|
-
...kitchen.context,
|
|
24145
|
-
sendServerEvent: (data) => {
|
|
24146
|
-
serverEventsDispatcher.dispatch({
|
|
24147
|
-
type: serverEventName,
|
|
24148
|
-
data,
|
|
24149
|
-
});
|
|
24150
|
-
},
|
|
24151
|
-
};
|
|
24152
|
-
const serverEventInit = allServerEvents[serverEventName];
|
|
24153
|
-
serverEventInit(serverEventInfo);
|
|
24154
|
-
});
|
|
24155
|
-
kitchen.pluginController.unshiftPlugin(
|
|
24156
|
-
jsenvPluginServerEventsClientInjection(
|
|
24157
|
-
clientAutoreload.clientServerEventsConfig,
|
|
24158
|
-
),
|
|
24159
|
-
);
|
|
24160
|
-
}
|
|
24161
|
-
}
|
|
24162
|
-
|
|
24163
24448
|
kitchenCache.set(runtimeId, kitchen);
|
|
24164
24449
|
onKitchenCreated(kitchen);
|
|
24165
24450
|
return kitchen;
|
|
@@ -24244,9 +24529,10 @@ const startDevServer = async ({
|
|
|
24244
24529
|
// If they match jsenv bypass cooking and returns 304
|
|
24245
24530
|
// This must not happen when a plugin uses "no-store" or "no-cache" as it means
|
|
24246
24531
|
// plugin logic wants to happens for every request to this url
|
|
24247
|
-
...(
|
|
24248
|
-
|
|
24249
|
-
|
|
24532
|
+
...(cacheIsDisabledInResponseHeader(urlInfoTargetedByCache)
|
|
24533
|
+
? {
|
|
24534
|
+
"cache-control": "no-store", // for inline file we force no-store when parent is no-store
|
|
24535
|
+
}
|
|
24250
24536
|
: {
|
|
24251
24537
|
"cache-control": `private,max-age=0,must-revalidate`,
|
|
24252
24538
|
// it's safe to use "_" separator because etag is encoded with base64 (see https://stackoverflow.com/a/13195197)
|
|
@@ -24347,13 +24633,20 @@ ${error.trace?.message}`);
|
|
|
24347
24633
|
};
|
|
24348
24634
|
}
|
|
24349
24635
|
},
|
|
24350
|
-
handleWebsocket: (websocket, { request }) => {
|
|
24636
|
+
handleWebsocket: async (websocket, { request }) => {
|
|
24351
24637
|
// if (true || logLevel === "debug") {
|
|
24352
24638
|
// console.log("handleWebsocket", websocket, request.headers);
|
|
24353
24639
|
// }
|
|
24354
|
-
|
|
24355
|
-
|
|
24356
|
-
|
|
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
|
+
);
|
|
24357
24650
|
},
|
|
24358
24651
|
});
|
|
24359
24652
|
}
|
|
@@ -24447,6 +24740,13 @@ ${error.trace?.message}`);
|
|
|
24447
24740
|
};
|
|
24448
24741
|
};
|
|
24449
24742
|
|
|
24743
|
+
const cacheIsDisabledInResponseHeader = (urlInfo) => {
|
|
24744
|
+
return (
|
|
24745
|
+
urlInfo.headers["cache-control"] === "no-store" ||
|
|
24746
|
+
urlInfo.headers["cache-control"] === "no-cache"
|
|
24747
|
+
);
|
|
24748
|
+
};
|
|
24749
|
+
|
|
24450
24750
|
/*
|
|
24451
24751
|
* startBuildServer is mean to interact with the build files;
|
|
24452
24752
|
* files that will be deployed to production server(s).
|