@jsenv/core 39.12.0 → 39.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +1035 -764
- 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/build/build_urls_generator.js +0 -1
- package/src/dev/start_dev_server.js +66 -52
- package/src/kitchen/kitchen.js +1 -4
- package/src/kitchen/out_directory_url.js +2 -1
- package/src/kitchen/url_graph/references.js +1 -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 +10 -6
- package/src/plugins/protocol_file/client/assets/home.svg +5 -5
- package/src/plugins/protocol_file/client/directory_listing.css +190 -0
- package/src/plugins/protocol_file/client/directory_listing.html +18 -0
- package/src/plugins/protocol_file/client/directory_listing.jsx +250 -0
- package/src/plugins/protocol_file/file_and_server_urls_converter.js +32 -0
- package/src/plugins/protocol_file/jsenv_plugin_directory_listing.js +398 -0
- package/src/plugins/protocol_file/jsenv_plugin_protocol_file.js +43 -370
- package/src/plugins/protocol_http/jsenv_plugin_protocol_http.js +3 -2
- package/src/plugins/reference_analysis/html/jsenv_plugin_html_reference_analysis.js +7 -6
- package/src/plugins/reference_analysis/js/jsenv_plugin_js_reference_analysis.js +1 -3
- package/src/plugins/reference_analysis/jsenv_plugin_reference_analysis.js +2 -18
- package/src/plugins/server_events/jsenv_plugin_server_events.js +100 -0
- package/dist/html/directory.html +0 -184
- package/dist/html/html_404_and_ancestor_dir.html +0 -222
- package/src/plugins/protocol_file/client/assets/directory.css +0 -150
- package/src/plugins/protocol_file/client/directory.html +0 -17
- package/src/plugins/protocol_file/client/html_404_and_ancestor_dir.html +0 -54
- package/src/plugins/server_events/jsenv_plugin_server_events_client_injection.js +0 -37
package/dist/jsenv_core.js
CHANGED
|
@@ -1467,6 +1467,7 @@ const pathnameToExtension$1 = (pathname) => {
|
|
|
1467
1467
|
};
|
|
1468
1468
|
|
|
1469
1469
|
const asUrlWithoutSearch = (url) => {
|
|
1470
|
+
url = String(url);
|
|
1470
1471
|
if (url.includes("?")) {
|
|
1471
1472
|
const urlObject = new URL(url);
|
|
1472
1473
|
urlObject.search = "";
|
|
@@ -1618,6 +1619,15 @@ const setUrlFilename = (url, filename) => {
|
|
|
1618
1619
|
});
|
|
1619
1620
|
};
|
|
1620
1621
|
|
|
1622
|
+
const setUrlBasename = (url, basename) => {
|
|
1623
|
+
return setUrlFilename(url, (filename) => {
|
|
1624
|
+
if (typeof basename === "function") {
|
|
1625
|
+
basename = basename(filenameToBasename(filename));
|
|
1626
|
+
}
|
|
1627
|
+
return `${basename}${urlToExtension$1(url)}`;
|
|
1628
|
+
});
|
|
1629
|
+
};
|
|
1630
|
+
|
|
1621
1631
|
const transformUrlPathname = (url, transformer) => {
|
|
1622
1632
|
if (typeof url === "string") {
|
|
1623
1633
|
const urlObject = new URL(url);
|
|
@@ -5003,6 +5013,17 @@ const fromNodeRequest = (
|
|
|
5003
5013
|
nodeRequest,
|
|
5004
5014
|
{ serverOrigin, signal, requestBodyLifetime },
|
|
5005
5015
|
) => {
|
|
5016
|
+
const handleRequestOperation = Abort.startOperation();
|
|
5017
|
+
if (signal) {
|
|
5018
|
+
handleRequestOperation.addAbortSignal(signal);
|
|
5019
|
+
}
|
|
5020
|
+
handleRequestOperation.addAbortSource((abort) => {
|
|
5021
|
+
nodeRequest.once("close", abort);
|
|
5022
|
+
return () => {
|
|
5023
|
+
nodeRequest.removeListener("close", abort);
|
|
5024
|
+
};
|
|
5025
|
+
});
|
|
5026
|
+
|
|
5006
5027
|
const headers = headersFromObject(nodeRequest.headers);
|
|
5007
5028
|
const body = observableFromNodeStream(nodeRequest, {
|
|
5008
5029
|
readableStreamLifetime: requestBodyLifetime,
|
|
@@ -5024,7 +5045,7 @@ const fromNodeRequest = (
|
|
|
5024
5045
|
}
|
|
5025
5046
|
|
|
5026
5047
|
return Object.freeze({
|
|
5027
|
-
signal,
|
|
5048
|
+
signal: handleRequestOperation.signal,
|
|
5028
5049
|
http2: Boolean(nodeRequest.stream),
|
|
5029
5050
|
origin: requestOrigin,
|
|
5030
5051
|
...getPropertiesFromResource({
|
|
@@ -6924,6 +6945,15 @@ const startServer = async ({
|
|
|
6924
6945
|
status = "stopped";
|
|
6925
6946
|
stoppedResolve(reason);
|
|
6926
6947
|
});
|
|
6948
|
+
let stopAbortSignal;
|
|
6949
|
+
{
|
|
6950
|
+
let stopAbortController = new AbortController();
|
|
6951
|
+
stopCallbackSet.add(() => {
|
|
6952
|
+
stopAbortController.abort();
|
|
6953
|
+
stopAbortController = undefined;
|
|
6954
|
+
});
|
|
6955
|
+
stopAbortSignal = stopAbortController.signal;
|
|
6956
|
+
}
|
|
6927
6957
|
|
|
6928
6958
|
const cancelProcessTeardownRace = raceProcessTeardownEvents(
|
|
6929
6959
|
processTeardownEvents,
|
|
@@ -6991,6 +7021,9 @@ const startServer = async ({
|
|
|
6991
7021
|
}
|
|
6992
7022
|
|
|
6993
7023
|
const receiveRequestOperation = Abort.startOperation();
|
|
7024
|
+
receiveRequestOperation.addAbortSignal(stopAbortSignal);
|
|
7025
|
+
const sendResponseOperation = Abort.startOperation();
|
|
7026
|
+
sendResponseOperation.addAbortSignal(stopAbortSignal);
|
|
6994
7027
|
receiveRequestOperation.addAbortSource((abort) => {
|
|
6995
7028
|
const closeEventCallback = () => {
|
|
6996
7029
|
if (nodeRequest.complete) {
|
|
@@ -7005,19 +7038,11 @@ const startServer = async ({
|
|
|
7005
7038
|
nodeRequest.removeListener("close", closeEventCallback);
|
|
7006
7039
|
};
|
|
7007
7040
|
});
|
|
7008
|
-
receiveRequestOperation.addAbortSource((abort) => {
|
|
7009
|
-
return stopCallbackSet.add(abort);
|
|
7010
|
-
});
|
|
7011
|
-
|
|
7012
|
-
const sendResponseOperation = Abort.startOperation();
|
|
7013
7041
|
sendResponseOperation.addAbortSignal(receiveRequestOperation.signal);
|
|
7014
|
-
sendResponseOperation.addAbortSource((abort) => {
|
|
7015
|
-
return stopCallbackSet.add(abort);
|
|
7016
|
-
});
|
|
7017
7042
|
|
|
7018
7043
|
const request = fromNodeRequest(nodeRequest, {
|
|
7044
|
+
signal: stopAbortSignal,
|
|
7019
7045
|
serverOrigin,
|
|
7020
|
-
signal: receiveRequestOperation.signal,
|
|
7021
7046
|
});
|
|
7022
7047
|
|
|
7023
7048
|
// Handling request is asynchronous, we buffer logs for that request
|
|
@@ -7591,13 +7616,16 @@ const startServer = async ({
|
|
|
7591
7616
|
socket,
|
|
7592
7617
|
head,
|
|
7593
7618
|
async (websocket) => {
|
|
7619
|
+
const websocketAbortController = new AbortController();
|
|
7594
7620
|
websocketClients.add(websocket);
|
|
7621
|
+
websocket.signal = websocketAbortController.signal;
|
|
7595
7622
|
websocket.once("close", () => {
|
|
7596
7623
|
websocketClients.delete(websocket);
|
|
7624
|
+
websocketAbortController.abort();
|
|
7597
7625
|
});
|
|
7598
7626
|
const request = fromNodeRequest(nodeRequest, {
|
|
7627
|
+
signal: stopAbortSignal,
|
|
7599
7628
|
serverOrigin: websocketOrigin,
|
|
7600
|
-
signal: new AbortController().signal,
|
|
7601
7629
|
requestBodyLifetime,
|
|
7602
7630
|
});
|
|
7603
7631
|
serviceController.callAsyncHooksUntil(
|
|
@@ -11711,7 +11739,7 @@ const generateHtmlForSyntaxError = (
|
|
|
11711
11739
|
errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
|
|
11712
11740
|
syntaxError: escapeHtml(htmlErrorContentFrame),
|
|
11713
11741
|
};
|
|
11714
|
-
const html = replacePlaceholders$
|
|
11742
|
+
const html = replacePlaceholders$1(htmlForSyntaxError, replacers);
|
|
11715
11743
|
return html;
|
|
11716
11744
|
};
|
|
11717
11745
|
const escapeHtml = (string) => {
|
|
@@ -11722,7 +11750,7 @@ const escapeHtml = (string) => {
|
|
|
11722
11750
|
.replace(/"/g, """)
|
|
11723
11751
|
.replace(/'/g, "'");
|
|
11724
11752
|
};
|
|
11725
|
-
const replacePlaceholders$
|
|
11753
|
+
const replacePlaceholders$1 = (html, replacers) => {
|
|
11726
11754
|
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
11727
11755
|
const replacer = replacers[name];
|
|
11728
11756
|
if (replacer === undefined) {
|
|
@@ -11738,6 +11766,7 @@ const replacePlaceholders$2 = (html, replacers) => {
|
|
|
11738
11766
|
const HOOK_NAMES = [
|
|
11739
11767
|
"init",
|
|
11740
11768
|
"serve", // is called only during dev/tests
|
|
11769
|
+
"serveWebsocket",
|
|
11741
11770
|
"resolveReference",
|
|
11742
11771
|
"redirectReference",
|
|
11743
11772
|
"transformReferenceSearchParams",
|
|
@@ -11750,6 +11779,7 @@ const HOOK_NAMES = [
|
|
|
11750
11779
|
"cooked",
|
|
11751
11780
|
"augmentResponse", // is called only during dev/tests
|
|
11752
11781
|
"destroy",
|
|
11782
|
+
"effect",
|
|
11753
11783
|
];
|
|
11754
11784
|
|
|
11755
11785
|
const createPluginController = (
|
|
@@ -11763,19 +11793,18 @@ const createPluginController = (
|
|
|
11763
11793
|
return value;
|
|
11764
11794
|
};
|
|
11765
11795
|
|
|
11766
|
-
const
|
|
11767
|
-
|
|
11768
|
-
|
|
11769
|
-
//
|
|
11770
|
-
|
|
11771
|
-
|
|
11796
|
+
const pluginCandidates = [];
|
|
11797
|
+
const activeEffectSet = new Set();
|
|
11798
|
+
const activePlugins = [];
|
|
11799
|
+
// precompute a list of hooks per hookName because:
|
|
11800
|
+
// 1. [MAJOR REASON] when debugging, there is less iteration (so much better)
|
|
11801
|
+
// 2. [MINOR REASON] it should increase perf as there is less work to do
|
|
11802
|
+
const hookSetMap = new Map();
|
|
11803
|
+
const addPlugin = (plugin, options) => {
|
|
11772
11804
|
if (Array.isArray(plugin)) {
|
|
11773
|
-
|
|
11774
|
-
|
|
11805
|
+
for (const value of plugin) {
|
|
11806
|
+
addPlugin(value);
|
|
11775
11807
|
}
|
|
11776
|
-
plugin.forEach((plugin) => {
|
|
11777
|
-
addPlugin(plugin, { position });
|
|
11778
|
-
});
|
|
11779
11808
|
return;
|
|
11780
11809
|
}
|
|
11781
11810
|
if (plugin === null || typeof plugin !== "object") {
|
|
@@ -11785,65 +11814,10 @@ const createPluginController = (
|
|
|
11785
11814
|
plugin.name = "anonymous";
|
|
11786
11815
|
}
|
|
11787
11816
|
if (!testAppliesDuring(plugin) || !initPlugin(plugin)) {
|
|
11788
|
-
|
|
11789
|
-
plugin.destroy();
|
|
11790
|
-
}
|
|
11817
|
+
plugin.destroy?.();
|
|
11791
11818
|
return;
|
|
11792
11819
|
}
|
|
11793
|
-
|
|
11794
|
-
for (const key of Object.keys(plugin)) {
|
|
11795
|
-
if (key === "meta") {
|
|
11796
|
-
const value = plugin[key];
|
|
11797
|
-
if (typeof value !== "object" || value === null) {
|
|
11798
|
-
console.warn(`plugin.meta must be an object, got ${value}`);
|
|
11799
|
-
continue;
|
|
11800
|
-
}
|
|
11801
|
-
Object.assign(pluginsMeta, value);
|
|
11802
|
-
// any extension/modification on plugin.meta
|
|
11803
|
-
// won't be taken into account so we freeze object
|
|
11804
|
-
// to throw in case it happen
|
|
11805
|
-
Object.freeze(value);
|
|
11806
|
-
continue;
|
|
11807
|
-
}
|
|
11808
|
-
|
|
11809
|
-
if (
|
|
11810
|
-
key === "name" ||
|
|
11811
|
-
key === "appliesDuring" ||
|
|
11812
|
-
key === "init" ||
|
|
11813
|
-
key === "serverEvents" ||
|
|
11814
|
-
key === "mustStayFirst"
|
|
11815
|
-
) {
|
|
11816
|
-
continue;
|
|
11817
|
-
}
|
|
11818
|
-
const isHook = HOOK_NAMES.includes(key);
|
|
11819
|
-
if (!isHook) {
|
|
11820
|
-
console.warn(`Unexpected "${key}" property on "${plugin.name}" plugin`);
|
|
11821
|
-
continue;
|
|
11822
|
-
}
|
|
11823
|
-
const hookName = key;
|
|
11824
|
-
const hookValue = plugin[hookName];
|
|
11825
|
-
if (hookValue) {
|
|
11826
|
-
const group = hookGroups[hookName] || (hookGroups[hookName] = []);
|
|
11827
|
-
const hook = {
|
|
11828
|
-
plugin,
|
|
11829
|
-
name: hookName,
|
|
11830
|
-
value: hookValue,
|
|
11831
|
-
};
|
|
11832
|
-
if (position === "start") {
|
|
11833
|
-
let i = 0;
|
|
11834
|
-
while (i < group.length) {
|
|
11835
|
-
const before = group[i];
|
|
11836
|
-
if (!before.plugin.mustStayFirst) {
|
|
11837
|
-
break;
|
|
11838
|
-
}
|
|
11839
|
-
i++;
|
|
11840
|
-
}
|
|
11841
|
-
group.splice(i, 0, hook);
|
|
11842
|
-
} else {
|
|
11843
|
-
group.push(hook);
|
|
11844
|
-
}
|
|
11845
|
-
}
|
|
11846
|
-
}
|
|
11820
|
+
pluginCandidates.push(plugin);
|
|
11847
11821
|
};
|
|
11848
11822
|
const testAppliesDuring = (plugin) => {
|
|
11849
11823
|
const { appliesDuring } = plugin;
|
|
@@ -11882,22 +11856,131 @@ const createPluginController = (
|
|
|
11882
11856
|
);
|
|
11883
11857
|
};
|
|
11884
11858
|
const initPlugin = (plugin) => {
|
|
11885
|
-
|
|
11886
|
-
|
|
11887
|
-
|
|
11888
|
-
|
|
11889
|
-
|
|
11890
|
-
|
|
11891
|
-
|
|
11892
|
-
|
|
11859
|
+
const { init } = plugin;
|
|
11860
|
+
if (!init) {
|
|
11861
|
+
return true;
|
|
11862
|
+
}
|
|
11863
|
+
const initReturnValue = init(kitchenContext, { plugin });
|
|
11864
|
+
if (initReturnValue === false) {
|
|
11865
|
+
return false;
|
|
11866
|
+
}
|
|
11867
|
+
if (typeof initReturnValue === "function" && !plugin.destroy) {
|
|
11868
|
+
plugin.destroy = initReturnValue;
|
|
11893
11869
|
}
|
|
11894
11870
|
return true;
|
|
11895
11871
|
};
|
|
11896
|
-
const pushPlugin = (
|
|
11897
|
-
|
|
11872
|
+
const pushPlugin = (...args) => {
|
|
11873
|
+
for (const arg of args) {
|
|
11874
|
+
addPlugin(arg);
|
|
11875
|
+
}
|
|
11876
|
+
updateActivePlugins();
|
|
11898
11877
|
};
|
|
11899
|
-
const
|
|
11900
|
-
|
|
11878
|
+
const updateActivePlugins = () => {
|
|
11879
|
+
// construct activePlugins and hooks according
|
|
11880
|
+
// to the one present in candidates and their effects
|
|
11881
|
+
// 1. active plugins is an empty array
|
|
11882
|
+
// 2. all active effects are cleaned-up
|
|
11883
|
+
// 3. all effects are re-activated if still relevant
|
|
11884
|
+
// 4. hooks are precomputed according to plugin order
|
|
11885
|
+
|
|
11886
|
+
// 1.
|
|
11887
|
+
activePlugins.length = 0;
|
|
11888
|
+
// 2.
|
|
11889
|
+
for (const { cleanup } of activeEffectSet) {
|
|
11890
|
+
cleanup();
|
|
11891
|
+
}
|
|
11892
|
+
activeEffectSet.clear();
|
|
11893
|
+
for (const pluginCandidate of pluginCandidates) {
|
|
11894
|
+
const effect = pluginCandidate.effect;
|
|
11895
|
+
if (!effect) {
|
|
11896
|
+
activePlugins.push(pluginCandidate);
|
|
11897
|
+
continue;
|
|
11898
|
+
}
|
|
11899
|
+
}
|
|
11900
|
+
// 3.
|
|
11901
|
+
for (const pluginCandidate of pluginCandidates) {
|
|
11902
|
+
const effect = pluginCandidate.effect;
|
|
11903
|
+
if (!effect) {
|
|
11904
|
+
continue;
|
|
11905
|
+
}
|
|
11906
|
+
const returnValue = effect({
|
|
11907
|
+
kitchenContext,
|
|
11908
|
+
otherPlugins: activePlugins,
|
|
11909
|
+
});
|
|
11910
|
+
if (!returnValue) {
|
|
11911
|
+
continue;
|
|
11912
|
+
}
|
|
11913
|
+
activePlugins.push(pluginCandidate);
|
|
11914
|
+
activeEffectSet.add({
|
|
11915
|
+
plugin: pluginCandidate,
|
|
11916
|
+
cleanup: typeof returnValue === "function" ? returnValue : () => {},
|
|
11917
|
+
});
|
|
11918
|
+
}
|
|
11919
|
+
// 4.
|
|
11920
|
+
activePlugins.sort((a, b) => {
|
|
11921
|
+
return pluginCandidates.indexOf(a) - pluginCandidates.indexOf(b);
|
|
11922
|
+
});
|
|
11923
|
+
hookSetMap.clear();
|
|
11924
|
+
for (const activePlugin of activePlugins) {
|
|
11925
|
+
for (const key of Object.keys(activePlugin)) {
|
|
11926
|
+
if (key === "meta") {
|
|
11927
|
+
const value = activePlugin[key];
|
|
11928
|
+
if (typeof value !== "object" || value === null) {
|
|
11929
|
+
console.warn(`plugin.meta must be an object, got ${value}`);
|
|
11930
|
+
continue;
|
|
11931
|
+
}
|
|
11932
|
+
Object.assign(pluginsMeta, value);
|
|
11933
|
+
// any extension/modification on plugin.meta
|
|
11934
|
+
// won't be taken into account so we freeze object
|
|
11935
|
+
// to throw in case it happen
|
|
11936
|
+
Object.freeze(value);
|
|
11937
|
+
continue;
|
|
11938
|
+
}
|
|
11939
|
+
if (
|
|
11940
|
+
key === "name" ||
|
|
11941
|
+
key === "appliesDuring" ||
|
|
11942
|
+
key === "init" ||
|
|
11943
|
+
key === "serverEvents" ||
|
|
11944
|
+
key === "mustStayFirst" ||
|
|
11945
|
+
key === "effect"
|
|
11946
|
+
) {
|
|
11947
|
+
continue;
|
|
11948
|
+
}
|
|
11949
|
+
const isHook = HOOK_NAMES.includes(key);
|
|
11950
|
+
if (!isHook) {
|
|
11951
|
+
console.warn(
|
|
11952
|
+
`Unexpected "${key}" property on "${activePlugin.name}" plugin`,
|
|
11953
|
+
);
|
|
11954
|
+
continue;
|
|
11955
|
+
}
|
|
11956
|
+
const hookName = key;
|
|
11957
|
+
const hookValue = activePlugin[hookName];
|
|
11958
|
+
if (hookValue) {
|
|
11959
|
+
let hookSet = hookSetMap.get(hookName);
|
|
11960
|
+
if (!hookSet) {
|
|
11961
|
+
hookSet = new Set();
|
|
11962
|
+
hookSetMap.set(hookName, hookSet);
|
|
11963
|
+
}
|
|
11964
|
+
const hook = {
|
|
11965
|
+
plugin: activePlugin,
|
|
11966
|
+
name: hookName,
|
|
11967
|
+
value: hookValue,
|
|
11968
|
+
};
|
|
11969
|
+
// if (position === "start") {
|
|
11970
|
+
// let i = 0;
|
|
11971
|
+
// while (i < group.length) {
|
|
11972
|
+
// const before = group[i];
|
|
11973
|
+
// if (!before.plugin.mustStayFirst) {
|
|
11974
|
+
// break;
|
|
11975
|
+
// }
|
|
11976
|
+
// i++;
|
|
11977
|
+
// }
|
|
11978
|
+
// group.splice(i, 0, hook);
|
|
11979
|
+
// } else {
|
|
11980
|
+
hookSet.add(hook);
|
|
11981
|
+
}
|
|
11982
|
+
}
|
|
11983
|
+
}
|
|
11901
11984
|
};
|
|
11902
11985
|
|
|
11903
11986
|
let lastPluginUsed = null;
|
|
@@ -11950,64 +12033,66 @@ const createPluginController = (
|
|
|
11950
12033
|
};
|
|
11951
12034
|
|
|
11952
12035
|
const callHooks = (hookName, info, callback) => {
|
|
11953
|
-
const
|
|
11954
|
-
if (
|
|
11955
|
-
|
|
11956
|
-
|
|
11957
|
-
|
|
11958
|
-
|
|
11959
|
-
|
|
11960
|
-
|
|
11961
|
-
|
|
11962
|
-
|
|
12036
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12037
|
+
if (!hookSet) {
|
|
12038
|
+
return;
|
|
12039
|
+
}
|
|
12040
|
+
const setHookParams = (firstArg = info) => {
|
|
12041
|
+
info = firstArg;
|
|
12042
|
+
};
|
|
12043
|
+
for (const hook of hookSet) {
|
|
12044
|
+
const returnValue = callHook(hook, info);
|
|
12045
|
+
if (returnValue && callback) {
|
|
12046
|
+
callback(returnValue, hook.plugin, setHookParams);
|
|
11963
12047
|
}
|
|
11964
12048
|
}
|
|
11965
12049
|
};
|
|
11966
12050
|
const callAsyncHooks = async (hookName, info, callback, options) => {
|
|
11967
|
-
const
|
|
11968
|
-
if (
|
|
11969
|
-
|
|
11970
|
-
|
|
11971
|
-
|
|
11972
|
-
|
|
11973
|
-
|
|
12051
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12052
|
+
if (!hookSet) {
|
|
12053
|
+
return;
|
|
12054
|
+
}
|
|
12055
|
+
for (const hook of hookSet) {
|
|
12056
|
+
const returnValue = await callAsyncHook(hook, info);
|
|
12057
|
+
if (returnValue && callback) {
|
|
12058
|
+
await callback(returnValue, hook.plugin);
|
|
11974
12059
|
}
|
|
11975
12060
|
}
|
|
11976
12061
|
};
|
|
11977
12062
|
|
|
11978
12063
|
const callHooksUntil = (hookName, info) => {
|
|
11979
|
-
const
|
|
11980
|
-
if (
|
|
11981
|
-
|
|
11982
|
-
|
|
11983
|
-
|
|
11984
|
-
|
|
11985
|
-
|
|
12064
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12065
|
+
if (!hookSet) {
|
|
12066
|
+
return null;
|
|
12067
|
+
}
|
|
12068
|
+
for (const hook of hookSet) {
|
|
12069
|
+
const returnValue = callHook(hook, info);
|
|
12070
|
+
if (returnValue) {
|
|
12071
|
+
return returnValue;
|
|
11986
12072
|
}
|
|
11987
12073
|
}
|
|
11988
12074
|
return null;
|
|
11989
12075
|
};
|
|
11990
12076
|
const callAsyncHooksUntil = async (hookName, info, options) => {
|
|
11991
|
-
const
|
|
11992
|
-
if (!
|
|
12077
|
+
const hookSet = hookSetMap.get(hookName);
|
|
12078
|
+
if (!hookSet) {
|
|
11993
12079
|
return null;
|
|
11994
12080
|
}
|
|
11995
|
-
if (
|
|
12081
|
+
if (hookSet.size === 0) {
|
|
11996
12082
|
return null;
|
|
11997
12083
|
}
|
|
12084
|
+
const iterator = hookSet.values()[Symbol.iterator]();
|
|
11998
12085
|
let result;
|
|
11999
|
-
let index = 0;
|
|
12000
12086
|
const visit = async () => {
|
|
12001
|
-
|
|
12087
|
+
const { done, value: hook } = iterator.next();
|
|
12088
|
+
if (done) {
|
|
12002
12089
|
return;
|
|
12003
12090
|
}
|
|
12004
|
-
const hook = hooks[index];
|
|
12005
12091
|
const returnValue = await callAsyncHook(hook, info);
|
|
12006
12092
|
if (returnValue) {
|
|
12007
12093
|
result = returnValue;
|
|
12008
12094
|
return;
|
|
12009
12095
|
}
|
|
12010
|
-
index++;
|
|
12011
12096
|
await visit();
|
|
12012
12097
|
};
|
|
12013
12098
|
await visit();
|
|
@@ -12016,9 +12101,8 @@ const createPluginController = (
|
|
|
12016
12101
|
|
|
12017
12102
|
return {
|
|
12018
12103
|
pluginsMeta,
|
|
12019
|
-
|
|
12104
|
+
activePlugins,
|
|
12020
12105
|
pushPlugin,
|
|
12021
|
-
unshiftPlugin,
|
|
12022
12106
|
getHookFunction,
|
|
12023
12107
|
callHook,
|
|
12024
12108
|
callAsyncHook,
|
|
@@ -12614,11 +12698,12 @@ const determineFileUrlForOutDirectory = (urlInfo) => {
|
|
|
12614
12698
|
if (filenameHint) {
|
|
12615
12699
|
url = setUrlFilename(url, filenameHint);
|
|
12616
12700
|
}
|
|
12617
|
-
|
|
12701
|
+
const outUrl = moveUrl({
|
|
12618
12702
|
url,
|
|
12619
12703
|
from: rootDirectoryUrl,
|
|
12620
12704
|
to: outDirectoryUrl,
|
|
12621
12705
|
});
|
|
12706
|
+
return outUrl;
|
|
12622
12707
|
};
|
|
12623
12708
|
|
|
12624
12709
|
const determineSourcemapFileUrl = (urlInfo) => {
|
|
@@ -12910,7 +12995,7 @@ const createDependencies = (ownerUrlInfo) => {
|
|
|
12910
12995
|
const injectAsBannerCodeBeforeFinalize = (urlInfoReceiver) => {
|
|
12911
12996
|
const basename = urlToBasename(sideEffectFileUrl);
|
|
12912
12997
|
const inlineUrl = generateUrlForInlineContent({
|
|
12913
|
-
url: urlInfoReceiver.url,
|
|
12998
|
+
url: urlInfoReceiver.originalUrl || urlInfoReceiver.url,
|
|
12914
12999
|
basename,
|
|
12915
13000
|
extension: urlToExtension$1(sideEffectFileUrl),
|
|
12916
13001
|
});
|
|
@@ -13982,6 +14067,7 @@ const createUrlInfo = (url, context) => {
|
|
|
13982
14067
|
writable: false,
|
|
13983
14068
|
value: url,
|
|
13984
14069
|
});
|
|
14070
|
+
urlInfo.pathname = new URL(url).pathname;
|
|
13985
14071
|
urlInfo.searchParams = new URL(url).searchParams;
|
|
13986
14072
|
|
|
13987
14073
|
urlInfo.dependencies = createDependencies(urlInfo);
|
|
@@ -14570,7 +14656,15 @@ const createUrlInfoTransformer = ({
|
|
|
14570
14656
|
contentIsInlined = false;
|
|
14571
14657
|
}
|
|
14572
14658
|
if (!contentIsInlined) {
|
|
14573
|
-
|
|
14659
|
+
const generatedUrlObject = new URL(generatedUrl);
|
|
14660
|
+
let baseName = urlToBasename(generatedUrlObject);
|
|
14661
|
+
for (const [key, value] of generatedUrlObject.searchParams) {
|
|
14662
|
+
baseName += `7${encodeFilePathComponent(key)}=${encodeFilePathComponent(value)}`;
|
|
14663
|
+
}
|
|
14664
|
+
const outFileUrl = setUrlBasename(generatedUrlObject, baseName);
|
|
14665
|
+
let outFilePath = urlToFileSystemPath(outFileUrl);
|
|
14666
|
+
outFilePath = truncate(outFilePath, 2055); // for windows
|
|
14667
|
+
writeFileSync(outFilePath, urlInfo.content, { force: true });
|
|
14574
14668
|
}
|
|
14575
14669
|
const { sourcemapGeneratedUrl, sourcemapReference } = urlInfo;
|
|
14576
14670
|
if (sourcemapGeneratedUrl && sourcemapReference) {
|
|
@@ -14689,6 +14783,26 @@ const createUrlInfoTransformer = ({
|
|
|
14689
14783
|
};
|
|
14690
14784
|
};
|
|
14691
14785
|
|
|
14786
|
+
// https://gist.github.com/barbietunnie/7bc6d48a424446c44ff4
|
|
14787
|
+
const illegalRe = /[/?<>\\:*|"]/g;
|
|
14788
|
+
// eslint-disable-next-line no-control-regex
|
|
14789
|
+
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
|
14790
|
+
const reservedRe = /^\.+$/;
|
|
14791
|
+
const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i;
|
|
14792
|
+
const encodeFilePathComponent = (input, replacement = "") => {
|
|
14793
|
+
const encoded = input
|
|
14794
|
+
.replace(illegalRe, replacement)
|
|
14795
|
+
.replace(controlRe, replacement)
|
|
14796
|
+
.replace(reservedRe, replacement)
|
|
14797
|
+
.replace(windowsReservedRe, replacement);
|
|
14798
|
+
return encoded;
|
|
14799
|
+
};
|
|
14800
|
+
const truncate = (sanitized, length) => {
|
|
14801
|
+
const uint8Array = new TextEncoder().encode(sanitized);
|
|
14802
|
+
const truncated = uint8Array.slice(0, length);
|
|
14803
|
+
return new TextDecoder().decode(truncated);
|
|
14804
|
+
};
|
|
14805
|
+
|
|
14692
14806
|
const shouldUpdateSourcemapComment = (urlInfo, sourcemaps) => {
|
|
14693
14807
|
if (urlInfo.context.buildStep === "shape") {
|
|
14694
14808
|
return false;
|
|
@@ -14698,7 +14812,6 @@ const shouldUpdateSourcemapComment = (urlInfo, sourcemaps) => {
|
|
|
14698
14812
|
}
|
|
14699
14813
|
return false;
|
|
14700
14814
|
};
|
|
14701
|
-
|
|
14702
14815
|
const mayHaveSourcemap = (urlInfo) => {
|
|
14703
14816
|
if (urlInfo.url.startsWith("data:")) {
|
|
14704
14817
|
return false;
|
|
@@ -14708,7 +14821,6 @@ const mayHaveSourcemap = (urlInfo) => {
|
|
|
14708
14821
|
}
|
|
14709
14822
|
return true;
|
|
14710
14823
|
};
|
|
14711
|
-
|
|
14712
14824
|
const shouldHandleSourcemap = (urlInfo) => {
|
|
14713
14825
|
const { sourcemaps } = urlInfo.context;
|
|
14714
14826
|
if (
|
|
@@ -14792,10 +14904,7 @@ const createKitchen = ({
|
|
|
14792
14904
|
initialPluginsMeta,
|
|
14793
14905
|
);
|
|
14794
14906
|
kitchen.pluginController = pluginController;
|
|
14795
|
-
pluginController.pushPlugin(jsenvPluginHtmlSyntaxErrorFallback());
|
|
14796
|
-
plugins.forEach((pluginEntry) => {
|
|
14797
|
-
pluginController.pushPlugin(pluginEntry);
|
|
14798
|
-
});
|
|
14907
|
+
pluginController.pushPlugin(jsenvPluginHtmlSyntaxErrorFallback(), ...plugins);
|
|
14799
14908
|
|
|
14800
14909
|
const urlInfoTransformer = createUrlInfoTransformer({
|
|
14801
14910
|
logger,
|
|
@@ -15808,10 +15917,11 @@ const jsenvPluginInliningIntoHtml = () => {
|
|
|
15808
15917
|
const { line, column, isOriginal } = getHtmlNodePosition(linkNode, {
|
|
15809
15918
|
preferOriginal: true,
|
|
15810
15919
|
});
|
|
15811
|
-
const linkInlineUrl = getUrlForContentInsideHtml(
|
|
15812
|
-
|
|
15813
|
-
|
|
15814
|
-
|
|
15920
|
+
const linkInlineUrl = getUrlForContentInsideHtml(
|
|
15921
|
+
linkNode,
|
|
15922
|
+
urlInfo,
|
|
15923
|
+
linkReference,
|
|
15924
|
+
);
|
|
15815
15925
|
const linkReferenceInlined = linkReference.inline({
|
|
15816
15926
|
line,
|
|
15817
15927
|
column,
|
|
@@ -15860,10 +15970,11 @@ const jsenvPluginInliningIntoHtml = () => {
|
|
|
15860
15970
|
const { line, column, isOriginal } = getHtmlNodePosition(scriptNode, {
|
|
15861
15971
|
preferOriginal: true,
|
|
15862
15972
|
});
|
|
15863
|
-
const scriptInlineUrl = getUrlForContentInsideHtml(
|
|
15864
|
-
|
|
15865
|
-
|
|
15866
|
-
|
|
15973
|
+
const scriptInlineUrl = getUrlForContentInsideHtml(
|
|
15974
|
+
scriptNode,
|
|
15975
|
+
urlInfo,
|
|
15976
|
+
scriptReference,
|
|
15977
|
+
);
|
|
15867
15978
|
const scriptReferenceInlined = scriptReference.inline({
|
|
15868
15979
|
line,
|
|
15869
15980
|
column,
|
|
@@ -17052,9 +17163,11 @@ const jsenvPluginHtmlReferenceAnalysis = ({
|
|
|
17052
17163
|
const { line, column, isOriginal } = getHtmlNodePosition(node, {
|
|
17053
17164
|
preferOriginal: true,
|
|
17054
17165
|
});
|
|
17055
|
-
const inlineContentUrl = getUrlForContentInsideHtml(
|
|
17056
|
-
|
|
17057
|
-
|
|
17166
|
+
const inlineContentUrl = getUrlForContentInsideHtml(
|
|
17167
|
+
node,
|
|
17168
|
+
urlInfo,
|
|
17169
|
+
null,
|
|
17170
|
+
);
|
|
17058
17171
|
const debug =
|
|
17059
17172
|
getHtmlNodeAttribute(node, "jsenv-debug") !== undefined;
|
|
17060
17173
|
const inlineReference = urlInfo.dependencies.foundInline({
|
|
@@ -17195,9 +17308,8 @@ const jsenvPluginHtmlReferenceAnalysis = ({
|
|
|
17195
17308
|
);
|
|
17196
17309
|
const importmapInlineUrl = getUrlForContentInsideHtml(
|
|
17197
17310
|
scriptNode,
|
|
17198
|
-
|
|
17199
|
-
|
|
17200
|
-
},
|
|
17311
|
+
urlInfo,
|
|
17312
|
+
importmapReference,
|
|
17201
17313
|
);
|
|
17202
17314
|
const importmapReferenceInlined = importmapReference.inline({
|
|
17203
17315
|
line,
|
|
@@ -17481,9 +17593,7 @@ const parseAndTransformJsReferences = async (
|
|
|
17481
17593
|
Object.keys(urlInfo.context.runtimeCompat).toString() === "node";
|
|
17482
17594
|
|
|
17483
17595
|
const onInlineReference = (inlineReferenceInfo) => {
|
|
17484
|
-
const inlineUrl = getUrlForContentInsideJs(inlineReferenceInfo,
|
|
17485
|
-
url: urlInfo.url,
|
|
17486
|
-
});
|
|
17596
|
+
const inlineUrl = getUrlForContentInsideJs(inlineReferenceInfo, urlInfo);
|
|
17487
17597
|
let { quote } = inlineReferenceInfo;
|
|
17488
17598
|
if (quote === "`" && !canUseTemplateLiterals) {
|
|
17489
17599
|
// if quote is "`" and template literals are not supported
|
|
@@ -17726,23 +17836,7 @@ const jsenvPluginInlineContentFetcher = () => {
|
|
|
17726
17836
|
if (!urlInfo.isInline) {
|
|
17727
17837
|
return null;
|
|
17728
17838
|
}
|
|
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
|
-
}
|
|
17839
|
+
const isDirectRequest = urlInfo.context.requestedUrl === urlInfo.url;
|
|
17746
17840
|
/*
|
|
17747
17841
|
* We want to find inline content but it's not straightforward
|
|
17748
17842
|
*
|
|
@@ -17771,7 +17865,7 @@ const jsenvPluginInlineContentFetcher = () => {
|
|
|
17771
17865
|
originalContent = reference.content;
|
|
17772
17866
|
}
|
|
17773
17867
|
lastInlineReference = reference;
|
|
17774
|
-
if (
|
|
17868
|
+
if (isDirectRequest) {
|
|
17775
17869
|
break;
|
|
17776
17870
|
}
|
|
17777
17871
|
}
|
|
@@ -19184,41 +19278,551 @@ const jsenvPluginVersionSearchParam = () => {
|
|
|
19184
19278
|
};
|
|
19185
19279
|
};
|
|
19186
19280
|
|
|
19187
|
-
const
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19281
|
+
const FILE_AND_SERVER_URLS_CONVERTER = {
|
|
19282
|
+
asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
|
|
19283
|
+
if (fileUrl === serverRootDirectoryUrl) {
|
|
19284
|
+
return "/";
|
|
19285
|
+
}
|
|
19286
|
+
if (urlIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
|
|
19287
|
+
const urlRelativeToServer = urlToRelativeUrl(
|
|
19288
|
+
fileUrl,
|
|
19289
|
+
serverRootDirectoryUrl,
|
|
19290
|
+
);
|
|
19291
|
+
return `/${urlRelativeToServer}`;
|
|
19292
|
+
}
|
|
19293
|
+
const urlRelativeToFilesystemRoot = String(fileUrl).slice(
|
|
19294
|
+
"file:///".length,
|
|
19295
|
+
);
|
|
19296
|
+
return `/@fs/${urlRelativeToFilesystemRoot}`;
|
|
19297
|
+
},
|
|
19298
|
+
asFileUrl: (urlRelativeToServer, serverRootDirectoryUrl) => {
|
|
19299
|
+
if (urlRelativeToServer.startsWith("/@fs/")) {
|
|
19300
|
+
const urlRelativeToFilesystemRoot = urlRelativeToServer.slice(
|
|
19301
|
+
"/@fs/".length,
|
|
19302
|
+
);
|
|
19303
|
+
return `file:///${urlRelativeToFilesystemRoot}`;
|
|
19304
|
+
}
|
|
19305
|
+
if (urlRelativeToServer[0] === "/") {
|
|
19306
|
+
return new URL(urlRelativeToServer.slice(1), serverRootDirectoryUrl).href;
|
|
19307
|
+
}
|
|
19308
|
+
return new URL(urlRelativeToServer, serverRootDirectoryUrl).href;
|
|
19309
|
+
},
|
|
19310
|
+
};
|
|
19311
|
+
|
|
19312
|
+
const jsenvPluginInjections = (rawAssociations) => {
|
|
19313
|
+
let resolvedAssociations;
|
|
19314
|
+
|
|
19193
19315
|
return {
|
|
19194
|
-
name: "jsenv:
|
|
19316
|
+
name: "jsenv:injections",
|
|
19195
19317
|
appliesDuring: "*",
|
|
19196
|
-
|
|
19197
|
-
|
|
19198
|
-
|
|
19318
|
+
init: (context) => {
|
|
19319
|
+
resolvedAssociations = URL_META.resolveAssociations(
|
|
19320
|
+
{ injectionsGetter: rawAssociations },
|
|
19321
|
+
context.rootDirectoryUrl,
|
|
19322
|
+
);
|
|
19323
|
+
},
|
|
19324
|
+
transformUrlContent: async (urlInfo) => {
|
|
19325
|
+
const { injectionsGetter } = URL_META.applyAssociations({
|
|
19326
|
+
url: asUrlWithoutSearch(urlInfo.url),
|
|
19327
|
+
associations: resolvedAssociations,
|
|
19328
|
+
});
|
|
19329
|
+
if (!injectionsGetter) {
|
|
19199
19330
|
return null;
|
|
19200
19331
|
}
|
|
19201
|
-
if (
|
|
19202
|
-
|
|
19332
|
+
if (typeof injectionsGetter !== "function") {
|
|
19333
|
+
throw new TypeError("injectionsGetter must be a function");
|
|
19203
19334
|
}
|
|
19204
|
-
|
|
19205
|
-
|
|
19335
|
+
const injections = await injectionsGetter(urlInfo);
|
|
19336
|
+
if (!injections) {
|
|
19337
|
+
return null;
|
|
19206
19338
|
}
|
|
19207
|
-
|
|
19208
|
-
if (
|
|
19209
|
-
return
|
|
19339
|
+
const keys = Object.keys(injections);
|
|
19340
|
+
if (keys.length === 0) {
|
|
19341
|
+
return null;
|
|
19210
19342
|
}
|
|
19211
|
-
|
|
19212
|
-
|
|
19213
|
-
|
|
19214
|
-
|
|
19215
|
-
|
|
19216
|
-
|
|
19217
|
-
|
|
19218
|
-
|
|
19219
|
-
|
|
19220
|
-
|
|
19221
|
-
|
|
19343
|
+
return replacePlaceholders(urlInfo.content, injections, urlInfo);
|
|
19344
|
+
},
|
|
19345
|
+
};
|
|
19346
|
+
};
|
|
19347
|
+
|
|
19348
|
+
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
19349
|
+
const INJECTIONS = {
|
|
19350
|
+
optional: (value) => {
|
|
19351
|
+
return { [injectionSymbol]: "optional", value };
|
|
19352
|
+
},
|
|
19353
|
+
};
|
|
19354
|
+
|
|
19355
|
+
// we export this because it is imported by jsenv_plugin_placeholder.js and unit test
|
|
19356
|
+
const replacePlaceholders = (content, replacements, urlInfo) => {
|
|
19357
|
+
const magicSource = createMagicSource(content);
|
|
19358
|
+
for (const key of Object.keys(replacements)) {
|
|
19359
|
+
let index = content.indexOf(key);
|
|
19360
|
+
const replacement = replacements[key];
|
|
19361
|
+
let isOptional;
|
|
19362
|
+
let value;
|
|
19363
|
+
if (replacement && replacement[injectionSymbol]) {
|
|
19364
|
+
const valueBehindSymbol = replacement[injectionSymbol];
|
|
19365
|
+
isOptional = valueBehindSymbol === "optional";
|
|
19366
|
+
value = replacement.value;
|
|
19367
|
+
} else {
|
|
19368
|
+
value = replacement;
|
|
19369
|
+
}
|
|
19370
|
+
if (index === -1) {
|
|
19371
|
+
if (!isOptional) {
|
|
19372
|
+
urlInfo.context.logger.warn(
|
|
19373
|
+
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
19374
|
+
--- suggestion a ---
|
|
19375
|
+
Add "${key}" in that file.
|
|
19376
|
+
--- suggestion b ---
|
|
19377
|
+
Fix eventual typo in "${key}"?
|
|
19378
|
+
--- suggestion c ---
|
|
19379
|
+
Mark injection as optional using INJECTIONS.optional():
|
|
19380
|
+
import { INJECTIONS } from "@jsenv/core";
|
|
19381
|
+
|
|
19382
|
+
return {
|
|
19383
|
+
"${key}": INJECTIONS.optional(${JSON.stringify(value)}),
|
|
19384
|
+
};`,
|
|
19385
|
+
);
|
|
19386
|
+
}
|
|
19387
|
+
continue;
|
|
19388
|
+
}
|
|
19389
|
+
|
|
19390
|
+
while (index !== -1) {
|
|
19391
|
+
const start = index;
|
|
19392
|
+
const end = index + key.length;
|
|
19393
|
+
magicSource.replace({
|
|
19394
|
+
start,
|
|
19395
|
+
end,
|
|
19396
|
+
replacement:
|
|
19397
|
+
urlInfo.type === "js_classic" ||
|
|
19398
|
+
urlInfo.type === "js_module" ||
|
|
19399
|
+
urlInfo.type === "html"
|
|
19400
|
+
? JSON.stringify(value, null, " ")
|
|
19401
|
+
: value,
|
|
19402
|
+
});
|
|
19403
|
+
index = content.indexOf(key, end);
|
|
19404
|
+
}
|
|
19405
|
+
}
|
|
19406
|
+
return magicSource.toContentAndSourcemap();
|
|
19407
|
+
};
|
|
19408
|
+
|
|
19409
|
+
/*
|
|
19410
|
+
* NICE TO HAVE:
|
|
19411
|
+
*
|
|
19412
|
+
* - when visiting urls outside server root directory the UI is messed up
|
|
19413
|
+
*
|
|
19414
|
+
* Let's say I visit file outside the server root directory that is in 404
|
|
19415
|
+
* We must update the enoent message and maybe other things to take into account
|
|
19416
|
+
* that url is no longer /something but "@fs/project_root/something" in the browser url bar
|
|
19417
|
+
*
|
|
19418
|
+
* - watching directory might result into things that are not properly handled:
|
|
19419
|
+
* 1. the existing directory is deleted
|
|
19420
|
+
* -> we should update the whole page to use a new "firstExistingDirectoryUrl"
|
|
19421
|
+
* 2. the enoent is impacted
|
|
19422
|
+
* -> we should update the ENOENT message
|
|
19423
|
+
* It means the websocket should contain more data and we can't assume firstExistingDirectoryUrl won't change
|
|
19424
|
+
*
|
|
19425
|
+
|
|
19426
|
+
*/
|
|
19427
|
+
|
|
19428
|
+
|
|
19429
|
+
const htmlFileUrlForDirectory = new URL(
|
|
19430
|
+
"./html/directory_listing.html",
|
|
19431
|
+
import.meta.url,
|
|
19432
|
+
);
|
|
19433
|
+
|
|
19434
|
+
const jsenvPluginDirectoryListing = ({
|
|
19435
|
+
urlMocks = false,
|
|
19436
|
+
autoreload = true,
|
|
19437
|
+
directoryContentMagicName,
|
|
19438
|
+
}) => {
|
|
19439
|
+
return {
|
|
19440
|
+
name: "jsenv:directory_listing",
|
|
19441
|
+
appliesDuring: "dev",
|
|
19442
|
+
redirectReference: (reference) => {
|
|
19443
|
+
if (reference.isInline) {
|
|
19444
|
+
return null;
|
|
19445
|
+
}
|
|
19446
|
+
const url = reference.url;
|
|
19447
|
+
if (!url.startsWith("file:")) {
|
|
19448
|
+
return null;
|
|
19449
|
+
}
|
|
19450
|
+
let { fsStat } = reference;
|
|
19451
|
+
if (!fsStat) {
|
|
19452
|
+
fsStat = readEntryStatSync(url, { nullIfNotFound: true });
|
|
19453
|
+
reference.fsStat = fsStat;
|
|
19454
|
+
}
|
|
19455
|
+
const { request, requestedUrl } = reference.ownerUrlInfo.context;
|
|
19456
|
+
if (!fsStat) {
|
|
19457
|
+
if (
|
|
19458
|
+
requestedUrl === url &&
|
|
19459
|
+
request &&
|
|
19460
|
+
request.headers["sec-fetch-dest"] === "document"
|
|
19461
|
+
) {
|
|
19462
|
+
return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(url)}&enoent`;
|
|
19463
|
+
}
|
|
19464
|
+
return null;
|
|
19465
|
+
}
|
|
19466
|
+
const isDirectory = fsStat?.isDirectory();
|
|
19467
|
+
if (!isDirectory) {
|
|
19468
|
+
return null;
|
|
19469
|
+
}
|
|
19470
|
+
if (reference.type === "filesystem") {
|
|
19471
|
+
// TODO: we should redirect to something like /...json
|
|
19472
|
+
// and any file name ...json is a special file serving directory content as json
|
|
19473
|
+
return null;
|
|
19474
|
+
}
|
|
19475
|
+
const acceptsHtml = request
|
|
19476
|
+
? pickContentType(request, ["text/html"])
|
|
19477
|
+
: false;
|
|
19478
|
+
if (!acceptsHtml) {
|
|
19479
|
+
return null;
|
|
19480
|
+
}
|
|
19481
|
+
reference.fsStat = null; // reset fsStat, now it's not a directory anyor
|
|
19482
|
+
return `${htmlFileUrlForDirectory}?url=${encodeURIComponent(url)}`;
|
|
19483
|
+
},
|
|
19484
|
+
transformUrlContent: {
|
|
19485
|
+
html: (urlInfo) => {
|
|
19486
|
+
const urlWithoutSearch = asUrlWithoutSearch(urlInfo.url);
|
|
19487
|
+
if (urlWithoutSearch !== String(htmlFileUrlForDirectory)) {
|
|
19488
|
+
return null;
|
|
19489
|
+
}
|
|
19490
|
+
const requestedUrl = urlInfo.searchParams.get("url");
|
|
19491
|
+
if (!requestedUrl) {
|
|
19492
|
+
return null;
|
|
19493
|
+
}
|
|
19494
|
+
urlInfo.headers["cache-control"] = "no-cache";
|
|
19495
|
+
const enoent = urlInfo.searchParams.has("enoent");
|
|
19496
|
+
if (enoent) {
|
|
19497
|
+
urlInfo.status = 404;
|
|
19498
|
+
urlInfo.headers["cache-control"] = "no-cache";
|
|
19499
|
+
}
|
|
19500
|
+
const request = urlInfo.context.request;
|
|
19501
|
+
const { rootDirectoryUrl, mainFilePath } = urlInfo.context;
|
|
19502
|
+
return replacePlaceholders(
|
|
19503
|
+
urlInfo.content,
|
|
19504
|
+
{
|
|
19505
|
+
...generateDirectoryListingInjection(requestedUrl, {
|
|
19506
|
+
autoreload,
|
|
19507
|
+
request,
|
|
19508
|
+
urlMocks,
|
|
19509
|
+
directoryContentMagicName,
|
|
19510
|
+
rootDirectoryUrl,
|
|
19511
|
+
mainFilePath,
|
|
19512
|
+
enoent,
|
|
19513
|
+
}),
|
|
19514
|
+
},
|
|
19515
|
+
urlInfo,
|
|
19516
|
+
);
|
|
19517
|
+
},
|
|
19518
|
+
},
|
|
19519
|
+
serveWebsocket: ({ websocket, request, context }) => {
|
|
19520
|
+
if (!autoreload) {
|
|
19521
|
+
return false;
|
|
19522
|
+
}
|
|
19523
|
+
const secProtocol = request.headers["sec-websocket-protocol"];
|
|
19524
|
+
if (secProtocol !== "watch-directory") {
|
|
19525
|
+
return false;
|
|
19526
|
+
}
|
|
19527
|
+
const { rootDirectoryUrl, mainFilePath } = context;
|
|
19528
|
+
const requestedUrl = FILE_AND_SERVER_URLS_CONVERTER.asFileUrl(
|
|
19529
|
+
request.pathname,
|
|
19530
|
+
rootDirectoryUrl,
|
|
19531
|
+
);
|
|
19532
|
+
const closestDirectoryUrl = getFirstExistingDirectoryUrl(requestedUrl);
|
|
19533
|
+
const sendMessage = (message) => {
|
|
19534
|
+
websocket.send(JSON.stringify(message));
|
|
19535
|
+
};
|
|
19536
|
+
const generateItems = () => {
|
|
19537
|
+
const firstExistingDirectoryUrl = getFirstExistingDirectoryUrl(
|
|
19538
|
+
requestedUrl,
|
|
19539
|
+
rootDirectoryUrl,
|
|
19540
|
+
);
|
|
19541
|
+
const items = getDirectoryContentItems({
|
|
19542
|
+
serverRootDirectoryUrl: rootDirectoryUrl,
|
|
19543
|
+
mainFilePath,
|
|
19544
|
+
requestedUrl,
|
|
19545
|
+
firstExistingDirectoryUrl,
|
|
19546
|
+
});
|
|
19547
|
+
return items;
|
|
19548
|
+
};
|
|
19549
|
+
|
|
19550
|
+
const unwatch = registerDirectoryLifecycle(closestDirectoryUrl, {
|
|
19551
|
+
added: ({ relativeUrl }) => {
|
|
19552
|
+
sendMessage({
|
|
19553
|
+
type: "change",
|
|
19554
|
+
reason: `${relativeUrl} added`,
|
|
19555
|
+
items: generateItems(),
|
|
19556
|
+
});
|
|
19557
|
+
},
|
|
19558
|
+
updated: ({ relativeUrl }) => {
|
|
19559
|
+
sendMessage({
|
|
19560
|
+
type: "change",
|
|
19561
|
+
reason: `${relativeUrl} updated`,
|
|
19562
|
+
items: generateItems(),
|
|
19563
|
+
});
|
|
19564
|
+
},
|
|
19565
|
+
removed: ({ relativeUrl }) => {
|
|
19566
|
+
sendMessage({
|
|
19567
|
+
type: "change",
|
|
19568
|
+
reason: `${relativeUrl} removed`,
|
|
19569
|
+
items: generateItems(),
|
|
19570
|
+
});
|
|
19571
|
+
},
|
|
19572
|
+
});
|
|
19573
|
+
websocket.signal.addEventListener("abort", () => {
|
|
19574
|
+
unwatch();
|
|
19575
|
+
});
|
|
19576
|
+
return true;
|
|
19577
|
+
},
|
|
19578
|
+
};
|
|
19579
|
+
};
|
|
19580
|
+
|
|
19581
|
+
const generateDirectoryListingInjection = (
|
|
19582
|
+
requestedUrl,
|
|
19583
|
+
{
|
|
19584
|
+
rootDirectoryUrl,
|
|
19585
|
+
mainFilePath,
|
|
19586
|
+
request,
|
|
19587
|
+
urlMocks,
|
|
19588
|
+
directoryContentMagicName,
|
|
19589
|
+
autoreload,
|
|
19590
|
+
enoent,
|
|
19591
|
+
},
|
|
19592
|
+
) => {
|
|
19593
|
+
let serverRootDirectoryUrl = rootDirectoryUrl;
|
|
19594
|
+
const firstExistingDirectoryUrl = getFirstExistingDirectoryUrl(
|
|
19595
|
+
requestedUrl,
|
|
19596
|
+
serverRootDirectoryUrl,
|
|
19597
|
+
);
|
|
19598
|
+
const directoryContentItems = getDirectoryContentItems({
|
|
19599
|
+
serverRootDirectoryUrl,
|
|
19600
|
+
mainFilePath,
|
|
19601
|
+
requestedUrl,
|
|
19602
|
+
firstExistingDirectoryUrl,
|
|
19603
|
+
});
|
|
19604
|
+
package_workspaces: {
|
|
19605
|
+
const packageDirectoryUrl = lookupPackageDirectory(serverRootDirectoryUrl);
|
|
19606
|
+
if (!packageDirectoryUrl) {
|
|
19607
|
+
break package_workspaces;
|
|
19608
|
+
}
|
|
19609
|
+
if (String(packageDirectoryUrl) === String(serverRootDirectoryUrl)) {
|
|
19610
|
+
break package_workspaces;
|
|
19611
|
+
}
|
|
19612
|
+
rootDirectoryUrl = packageDirectoryUrl;
|
|
19613
|
+
// if (String(firstExistingDirectoryUrl) === String(serverRootDirectoryUrl)) {
|
|
19614
|
+
// let packageContent;
|
|
19615
|
+
// try {
|
|
19616
|
+
// packageContent = JSON.parse(
|
|
19617
|
+
// readFileSync(new URL("package.json", packageDirectoryUrl), "utf8"),
|
|
19618
|
+
// );
|
|
19619
|
+
// } catch {
|
|
19620
|
+
// break package_workspaces;
|
|
19621
|
+
// }
|
|
19622
|
+
// const { workspaces } = packageContent;
|
|
19623
|
+
// if (Array.isArray(workspaces)) {
|
|
19624
|
+
// for (const workspace of workspaces) {
|
|
19625
|
+
// const workspaceUrlObject = new URL(workspace, packageDirectoryUrl);
|
|
19626
|
+
// const workspaceUrl = workspaceUrlObject.href;
|
|
19627
|
+
// if (workspaceUrl.endsWith("*")) {
|
|
19628
|
+
// const directoryUrl = ensurePathnameTrailingSlash(
|
|
19629
|
+
// workspaceUrl.slice(0, -1),
|
|
19630
|
+
// );
|
|
19631
|
+
// fileUrls.push(new URL(directoryUrl));
|
|
19632
|
+
// } else {
|
|
19633
|
+
// fileUrls.push(ensurePathnameTrailingSlash(workspaceUrlObject));
|
|
19634
|
+
// }
|
|
19635
|
+
// }
|
|
19636
|
+
// }
|
|
19637
|
+
// }
|
|
19638
|
+
}
|
|
19639
|
+
const directoryUrlRelativeToServer =
|
|
19640
|
+
FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
19641
|
+
firstExistingDirectoryUrl,
|
|
19642
|
+
serverRootDirectoryUrl,
|
|
19643
|
+
);
|
|
19644
|
+
const websocketScheme = request.protocol === "https" ? "wss" : "ws";
|
|
19645
|
+
const { host } = new URL(request.url);
|
|
19646
|
+
const websocketUrl = `${websocketScheme}://${host}${directoryUrlRelativeToServer}`;
|
|
19647
|
+
|
|
19648
|
+
const navItems = [];
|
|
19649
|
+
{
|
|
19650
|
+
const lastItemUrl = firstExistingDirectoryUrl;
|
|
19651
|
+
const lastItemRelativeUrl = urlToRelativeUrl(lastItemUrl, rootDirectoryUrl);
|
|
19652
|
+
const rootDirectoryUrlName = urlToFilename$1(rootDirectoryUrl);
|
|
19653
|
+
let parts;
|
|
19654
|
+
if (lastItemRelativeUrl) {
|
|
19655
|
+
parts = `${rootDirectoryUrlName}/${lastItemRelativeUrl}`.split("/");
|
|
19656
|
+
} else {
|
|
19657
|
+
parts = [rootDirectoryUrlName];
|
|
19658
|
+
}
|
|
19659
|
+
|
|
19660
|
+
let i = 0;
|
|
19661
|
+
while (i < parts.length) {
|
|
19662
|
+
const part = parts[i];
|
|
19663
|
+
const isLastPart = i === parts.length - 1;
|
|
19664
|
+
if (isLastPart && part === "") {
|
|
19665
|
+
// ignore trailing slash
|
|
19666
|
+
break;
|
|
19667
|
+
}
|
|
19668
|
+
let navItemRelativeUrl = `${parts.slice(1, i + 1).join("/")}`;
|
|
19669
|
+
let navItemUrl =
|
|
19670
|
+
navItemRelativeUrl === ""
|
|
19671
|
+
? rootDirectoryUrl
|
|
19672
|
+
: new URL(navItemRelativeUrl, rootDirectoryUrl).href;
|
|
19673
|
+
if (!isLastPart) {
|
|
19674
|
+
navItemUrl = ensurePathnameTrailingSlash(navItemUrl);
|
|
19675
|
+
}
|
|
19676
|
+
let urlRelativeToServer = FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
19677
|
+
navItemUrl,
|
|
19678
|
+
serverRootDirectoryUrl,
|
|
19679
|
+
);
|
|
19680
|
+
let urlRelativeToDocument = urlToRelativeUrl(navItemUrl, requestedUrl);
|
|
19681
|
+
const isServerRootDirectory = navItemUrl === serverRootDirectoryUrl;
|
|
19682
|
+
if (isServerRootDirectory) {
|
|
19683
|
+
urlRelativeToServer = `/${directoryContentMagicName}`;
|
|
19684
|
+
urlRelativeToDocument = `/${directoryContentMagicName}`;
|
|
19685
|
+
}
|
|
19686
|
+
const name = part;
|
|
19687
|
+
const isCurrent = navItemUrl === String(firstExistingDirectoryUrl);
|
|
19688
|
+
navItems.push({
|
|
19689
|
+
url: navItemUrl,
|
|
19690
|
+
urlRelativeToServer,
|
|
19691
|
+
urlRelativeToDocument,
|
|
19692
|
+
isServerRootDirectory,
|
|
19693
|
+
isCurrent,
|
|
19694
|
+
name,
|
|
19695
|
+
});
|
|
19696
|
+
i++;
|
|
19697
|
+
}
|
|
19698
|
+
}
|
|
19699
|
+
|
|
19700
|
+
let enoentDetails = null;
|
|
19701
|
+
if (enoent) {
|
|
19702
|
+
const fileRelativeUrl = urlToRelativeUrl(
|
|
19703
|
+
requestedUrl,
|
|
19704
|
+
serverRootDirectoryUrl,
|
|
19705
|
+
);
|
|
19706
|
+
let filePathExisting;
|
|
19707
|
+
let filePathNotFound;
|
|
19708
|
+
const existingIndex = String(firstExistingDirectoryUrl).length;
|
|
19709
|
+
filePathExisting = urlToRelativeUrl(
|
|
19710
|
+
firstExistingDirectoryUrl,
|
|
19711
|
+
serverRootDirectoryUrl,
|
|
19712
|
+
);
|
|
19713
|
+
filePathNotFound = requestedUrl.slice(existingIndex);
|
|
19714
|
+
enoentDetails = {
|
|
19715
|
+
fileUrl: requestedUrl,
|
|
19716
|
+
fileRelativeUrl,
|
|
19717
|
+
filePathExisting: `/${filePathExisting}`,
|
|
19718
|
+
filePathNotFound,
|
|
19719
|
+
};
|
|
19720
|
+
}
|
|
19721
|
+
|
|
19722
|
+
return {
|
|
19723
|
+
__DIRECTORY_LISTING__: {
|
|
19724
|
+
enoentDetails,
|
|
19725
|
+
navItems,
|
|
19726
|
+
urlMocks,
|
|
19727
|
+
directoryContentMagicName,
|
|
19728
|
+
directoryUrl: firstExistingDirectoryUrl,
|
|
19729
|
+
serverRootDirectoryUrl,
|
|
19730
|
+
rootDirectoryUrl,
|
|
19731
|
+
mainFilePath,
|
|
19732
|
+
directoryContentItems,
|
|
19733
|
+
websocketUrl,
|
|
19734
|
+
autoreload,
|
|
19735
|
+
},
|
|
19736
|
+
};
|
|
19737
|
+
};
|
|
19738
|
+
const getFirstExistingDirectoryUrl = (requestedUrl, serverRootDirectoryUrl) => {
|
|
19739
|
+
let firstExistingDirectoryUrl = new URL("./", requestedUrl);
|
|
19740
|
+
while (!existsSync(firstExistingDirectoryUrl)) {
|
|
19741
|
+
firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
|
|
19742
|
+
if (!urlIsInsideOf(firstExistingDirectoryUrl, serverRootDirectoryUrl)) {
|
|
19743
|
+
firstExistingDirectoryUrl = new URL(serverRootDirectoryUrl);
|
|
19744
|
+
break;
|
|
19745
|
+
}
|
|
19746
|
+
}
|
|
19747
|
+
return firstExistingDirectoryUrl;
|
|
19748
|
+
};
|
|
19749
|
+
const getDirectoryContentItems = ({
|
|
19750
|
+
serverRootDirectoryUrl,
|
|
19751
|
+
mainFilePath,
|
|
19752
|
+
firstExistingDirectoryUrl,
|
|
19753
|
+
}) => {
|
|
19754
|
+
const directoryContentArray = readdirSync(new URL(firstExistingDirectoryUrl));
|
|
19755
|
+
const fileUrls = [];
|
|
19756
|
+
for (const filename of directoryContentArray) {
|
|
19757
|
+
const fileUrlObject = new URL(filename, firstExistingDirectoryUrl);
|
|
19758
|
+
if (lstatSync(fileUrlObject).isDirectory()) {
|
|
19759
|
+
fileUrls.push(ensurePathnameTrailingSlash(fileUrlObject));
|
|
19760
|
+
} else {
|
|
19761
|
+
fileUrls.push(fileUrlObject);
|
|
19762
|
+
}
|
|
19763
|
+
}
|
|
19764
|
+
fileUrls.sort((a, b) => {
|
|
19765
|
+
return comparePathnames(a.pathname, b.pathname);
|
|
19766
|
+
});
|
|
19767
|
+
const items = [];
|
|
19768
|
+
for (const fileUrl of fileUrls) {
|
|
19769
|
+
const urlRelativeToCurrentDirectory = urlToRelativeUrl(
|
|
19770
|
+
fileUrl,
|
|
19771
|
+
firstExistingDirectoryUrl,
|
|
19772
|
+
);
|
|
19773
|
+
const urlRelativeToServer = FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
19774
|
+
fileUrl,
|
|
19775
|
+
serverRootDirectoryUrl,
|
|
19776
|
+
);
|
|
19777
|
+
const url = String(fileUrl);
|
|
19778
|
+
const mainFileUrl = new URL(mainFilePath, serverRootDirectoryUrl).href;
|
|
19779
|
+
const isMainFile = url === mainFileUrl;
|
|
19780
|
+
|
|
19781
|
+
items.push({
|
|
19782
|
+
url,
|
|
19783
|
+
urlRelativeToCurrentDirectory,
|
|
19784
|
+
urlRelativeToServer,
|
|
19785
|
+
isMainFile,
|
|
19786
|
+
});
|
|
19787
|
+
}
|
|
19788
|
+
return items;
|
|
19789
|
+
};
|
|
19790
|
+
|
|
19791
|
+
const jsenvPluginFsRedirection = ({
|
|
19792
|
+
directoryContentMagicName,
|
|
19793
|
+
magicExtensions = ["inherit", ".js"],
|
|
19794
|
+
magicDirectoryIndex = true,
|
|
19795
|
+
preserveSymlinks = false,
|
|
19796
|
+
}) => {
|
|
19797
|
+
return {
|
|
19798
|
+
name: "jsenv:fs_redirection",
|
|
19799
|
+
appliesDuring: "*",
|
|
19800
|
+
redirectReference: (reference) => {
|
|
19801
|
+
// http, https, data, about, ...
|
|
19802
|
+
if (!reference.url.startsWith("file:")) {
|
|
19803
|
+
return null;
|
|
19804
|
+
}
|
|
19805
|
+
if (reference.isInline) {
|
|
19806
|
+
return null;
|
|
19807
|
+
}
|
|
19808
|
+
if (reference.url === "file:///" || reference.url === "file://") {
|
|
19809
|
+
return `ignore:file:///`;
|
|
19810
|
+
}
|
|
19811
|
+
// ignore all new URL second arg
|
|
19812
|
+
if (reference.subtype === "new_url_second_arg") {
|
|
19813
|
+
return `ignore:${reference.url}`;
|
|
19814
|
+
}
|
|
19815
|
+
if (
|
|
19816
|
+
reference.specifierPathname.endsWith(`/${directoryContentMagicName}`)
|
|
19817
|
+
) {
|
|
19818
|
+
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
19819
|
+
const directoryUrl = new URL(
|
|
19820
|
+
reference.specifierPathname
|
|
19821
|
+
.replace(`/${directoryContentMagicName}`, "/")
|
|
19822
|
+
.slice(1),
|
|
19823
|
+
rootDirectoryUrl,
|
|
19824
|
+
).href;
|
|
19825
|
+
return directoryUrl;
|
|
19222
19826
|
}
|
|
19223
19827
|
// ignore "./" on new URL("./")
|
|
19224
19828
|
// if (
|
|
@@ -19318,21 +19922,13 @@ const resolveSymlink = (fileUrl) => {
|
|
|
19318
19922
|
return realUrlObject.href;
|
|
19319
19923
|
};
|
|
19320
19924
|
|
|
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
19925
|
const directoryContentMagicName = "...";
|
|
19330
19926
|
|
|
19331
19927
|
const jsenvPluginProtocolFile = ({
|
|
19332
19928
|
magicExtensions,
|
|
19333
19929
|
magicDirectoryIndex,
|
|
19334
19930
|
preserveSymlinks,
|
|
19335
|
-
|
|
19931
|
+
directoryListing,
|
|
19336
19932
|
}) => {
|
|
19337
19933
|
return [
|
|
19338
19934
|
jsenvPluginFsRedirection({
|
|
@@ -19363,8 +19959,7 @@ const jsenvPluginProtocolFile = ({
|
|
|
19363
19959
|
appliesDuring: "dev",
|
|
19364
19960
|
resolveReference: (reference) => {
|
|
19365
19961
|
if (reference.specifier.startsWith("/@fs/")) {
|
|
19366
|
-
|
|
19367
|
-
return `file:///${fsRootRelativeUrl}`;
|
|
19962
|
+
return FILE_AND_SERVER_URLS_CONVERTER.asFileUrl(reference.specifier);
|
|
19368
19963
|
}
|
|
19369
19964
|
return null;
|
|
19370
19965
|
},
|
|
@@ -19383,12 +19978,43 @@ const jsenvPluginProtocolFile = ({
|
|
|
19383
19978
|
}
|
|
19384
19979
|
}
|
|
19385
19980
|
const { rootDirectoryUrl } = reference.ownerUrlInfo.context;
|
|
19386
|
-
|
|
19387
|
-
|
|
19388
|
-
|
|
19981
|
+
return FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(
|
|
19982
|
+
generatedUrl,
|
|
19983
|
+
rootDirectoryUrl,
|
|
19984
|
+
);
|
|
19985
|
+
},
|
|
19986
|
+
},
|
|
19987
|
+
...(directoryListing
|
|
19988
|
+
? [
|
|
19989
|
+
jsenvPluginDirectoryListing({
|
|
19990
|
+
...directoryListing,
|
|
19991
|
+
directoryContentMagicName,
|
|
19992
|
+
}),
|
|
19993
|
+
]
|
|
19994
|
+
: []),
|
|
19995
|
+
{
|
|
19996
|
+
name: "jsenv:directory_as_json",
|
|
19997
|
+
appliesDuring: "*",
|
|
19998
|
+
fetchUrlContent: (urlInfo) => {
|
|
19999
|
+
const { firstReference } = urlInfo;
|
|
20000
|
+
let { fsStat } = firstReference;
|
|
20001
|
+
if (!fsStat) {
|
|
20002
|
+
fsStat = readEntryStatSync(urlInfo.url, { nullIfNotFound: true });
|
|
19389
20003
|
}
|
|
19390
|
-
|
|
19391
|
-
|
|
20004
|
+
if (!fsStat) {
|
|
20005
|
+
return null;
|
|
20006
|
+
}
|
|
20007
|
+
const isDirectory = fsStat.isDirectory();
|
|
20008
|
+
if (!isDirectory) {
|
|
20009
|
+
return null;
|
|
20010
|
+
}
|
|
20011
|
+
const directoryContentArray = readdirSync(new URL(urlInfo.url));
|
|
20012
|
+
const content = JSON.stringify(directoryContentArray, null, " ");
|
|
20013
|
+
return {
|
|
20014
|
+
type: "directory",
|
|
20015
|
+
contentType: "application/json",
|
|
20016
|
+
content,
|
|
20017
|
+
};
|
|
19392
20018
|
},
|
|
19393
20019
|
},
|
|
19394
20020
|
{
|
|
@@ -19399,366 +20025,27 @@ const jsenvPluginProtocolFile = ({
|
|
|
19399
20025
|
return null;
|
|
19400
20026
|
}
|
|
19401
20027
|
const { firstReference } = urlInfo;
|
|
19402
|
-
const { mainFilePath } = urlInfo.context;
|
|
19403
20028
|
let { fsStat } = firstReference;
|
|
19404
20029
|
if (!fsStat) {
|
|
19405
20030
|
fsStat = readEntryStatSync(urlInfo.url, { nullIfNotFound: true });
|
|
19406
20031
|
}
|
|
19407
|
-
const isDirectory = fsStat?.isDirectory();
|
|
19408
|
-
const { rootDirectoryUrl, request } = urlInfo.context;
|
|
19409
20032
|
const serveFile = (url) => {
|
|
19410
20033
|
const contentType = CONTENT_TYPE.fromUrlExtension(url);
|
|
19411
20034
|
const fileBuffer = readFileSync(new URL(url));
|
|
19412
20035
|
const content = CONTENT_TYPE.isTextual(contentType)
|
|
19413
20036
|
? String(fileBuffer)
|
|
19414
20037
|
: fileBuffer;
|
|
19415
|
-
return {
|
|
19416
|
-
content,
|
|
19417
|
-
contentType,
|
|
19418
|
-
contentLength: fileBuffer.length,
|
|
19419
|
-
};
|
|
19420
|
-
};
|
|
19421
|
-
|
|
19422
|
-
|
|
19423
|
-
|
|
19424
|
-
|
|
19425
|
-
|
|
19426
|
-
rootDirectoryUrl,
|
|
19427
|
-
);
|
|
19428
|
-
const html = generateHtmlForENOENT(
|
|
19429
|
-
urlInfo.url,
|
|
19430
|
-
directoryContentItems,
|
|
19431
|
-
directoryListingUrlMocks,
|
|
19432
|
-
{ mainFilePath },
|
|
19433
|
-
);
|
|
19434
|
-
return {
|
|
19435
|
-
status: 404,
|
|
19436
|
-
contentType: "text/html",
|
|
19437
|
-
content: html,
|
|
19438
|
-
headers: {
|
|
19439
|
-
"cache-control": "no-cache",
|
|
19440
|
-
},
|
|
19441
|
-
};
|
|
19442
|
-
}
|
|
19443
|
-
}
|
|
19444
|
-
if (isDirectory) {
|
|
19445
|
-
const directoryContentArray = readdirSync(new URL(urlInfo.url));
|
|
19446
|
-
if (firstReference.type === "filesystem") {
|
|
19447
|
-
const content = JSON.stringify(directoryContentArray, null, " ");
|
|
19448
|
-
return {
|
|
19449
|
-
type: "directory",
|
|
19450
|
-
contentType: "application/json",
|
|
19451
|
-
content,
|
|
19452
|
-
};
|
|
19453
|
-
}
|
|
19454
|
-
const acceptsHtml = request
|
|
19455
|
-
? pickContentType(request, ["text/html"])
|
|
19456
|
-
: false;
|
|
19457
|
-
if (acceptsHtml) {
|
|
19458
|
-
firstReference.expectedType = "html";
|
|
19459
|
-
const directoryUrl = urlInfo.url;
|
|
19460
|
-
const directoryContentItems = generateDirectoryContentItems(
|
|
19461
|
-
directoryUrl,
|
|
19462
|
-
rootDirectoryUrl,
|
|
19463
|
-
);
|
|
19464
|
-
const html = generateHtmlForDirectory(directoryContentItems, {
|
|
19465
|
-
mainFilePath,
|
|
19466
|
-
});
|
|
19467
|
-
return {
|
|
19468
|
-
type: "html",
|
|
19469
|
-
contentType: "text/html",
|
|
19470
|
-
content: html,
|
|
19471
|
-
};
|
|
19472
|
-
}
|
|
19473
|
-
return {
|
|
19474
|
-
type: "directory",
|
|
19475
|
-
contentType: "application/json",
|
|
19476
|
-
content: JSON.stringify(directoryContentArray, null, " "),
|
|
19477
|
-
};
|
|
19478
|
-
}
|
|
19479
|
-
return serveFile(urlInfo.url);
|
|
19480
|
-
},
|
|
19481
|
-
},
|
|
19482
|
-
];
|
|
19483
|
-
};
|
|
19484
|
-
|
|
19485
|
-
const generateHtmlForDirectory = (directoryContentItems, { mainFilePath }) => {
|
|
19486
|
-
let directoryUrl = directoryContentItems.firstExistingDirectoryUrl;
|
|
19487
|
-
const rootDirectoryUrl = directoryContentItems.rootDirectoryUrl;
|
|
19488
|
-
directoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
|
|
19489
|
-
|
|
19490
|
-
const htmlForDirectory = String(readFileSync(htmlFileUrlForDirectory));
|
|
19491
|
-
const replacers = {
|
|
19492
|
-
directoryUrl,
|
|
19493
|
-
directoryNav: () =>
|
|
19494
|
-
generateDirectoryNav(directoryUrl, {
|
|
19495
|
-
rootDirectoryUrl,
|
|
19496
|
-
rootDirectoryUrlForServer:
|
|
19497
|
-
directoryContentItems.rootDirectoryUrlForServer,
|
|
19498
|
-
mainFilePath,
|
|
19499
|
-
}),
|
|
19500
|
-
directoryContent: () =>
|
|
19501
|
-
generateDirectoryContent(directoryContentItems, { mainFilePath }),
|
|
19502
|
-
};
|
|
19503
|
-
const html = replacePlaceholders$1(htmlForDirectory, replacers);
|
|
19504
|
-
return html;
|
|
19505
|
-
};
|
|
19506
|
-
const generateHtmlForENOENT = (
|
|
19507
|
-
url,
|
|
19508
|
-
directoryContentItems,
|
|
19509
|
-
directoryListingUrlMocks,
|
|
19510
|
-
{ mainFilePath },
|
|
19511
|
-
) => {
|
|
19512
|
-
const ancestorDirectoryUrl = directoryContentItems.firstExistingDirectoryUrl;
|
|
19513
|
-
const rootDirectoryUrl = directoryContentItems.rootDirectoryUrl;
|
|
19514
|
-
|
|
19515
|
-
const htmlFor404AndAncestorDir = String(
|
|
19516
|
-
readFileSync(html404AndAncestorDirFileUrl),
|
|
19517
|
-
);
|
|
19518
|
-
const fileRelativeUrl = urlToRelativeUrl(url, rootDirectoryUrl);
|
|
19519
|
-
const ancestorDirectoryRelativeUrl = urlToRelativeUrl(
|
|
19520
|
-
ancestorDirectoryUrl,
|
|
19521
|
-
rootDirectoryUrl,
|
|
19522
|
-
);
|
|
19523
|
-
const replacers = {
|
|
19524
|
-
fileUrl: directoryListingUrlMocks
|
|
19525
|
-
? `@jsenv/core/${urlToRelativeUrl(url, jsenvCoreDirectoryUrl)}`
|
|
19526
|
-
: url,
|
|
19527
|
-
fileRelativeUrl,
|
|
19528
|
-
ancestorDirectoryUrl,
|
|
19529
|
-
ancestorDirectoryRelativeUrl,
|
|
19530
|
-
ancestorDirectoryNav: () =>
|
|
19531
|
-
generateDirectoryNav(ancestorDirectoryUrl, {
|
|
19532
|
-
rootDirectoryUrl,
|
|
19533
|
-
rootDirectoryUrlForServer:
|
|
19534
|
-
directoryContentItems.rootDirectoryUrlForServer,
|
|
19535
|
-
mainFilePath,
|
|
19536
|
-
}),
|
|
19537
|
-
ancestorDirectoryContent: () =>
|
|
19538
|
-
generateDirectoryContent(directoryContentItems, { mainFilePath }),
|
|
19539
|
-
};
|
|
19540
|
-
const html = replacePlaceholders$1(htmlFor404AndAncestorDir, replacers);
|
|
19541
|
-
return html;
|
|
19542
|
-
};
|
|
19543
|
-
const generateDirectoryNav = (
|
|
19544
|
-
entryDirectoryUrl,
|
|
19545
|
-
{ rootDirectoryUrl, rootDirectoryUrlForServer, mainFilePath },
|
|
19546
|
-
) => {
|
|
19547
|
-
const entryDirectoryRelativeUrl = urlToRelativeUrl(
|
|
19548
|
-
entryDirectoryUrl,
|
|
19549
|
-
rootDirectoryUrl,
|
|
19550
|
-
);
|
|
19551
|
-
const isDir =
|
|
19552
|
-
entryDirectoryRelativeUrl === "" || entryDirectoryRelativeUrl.endsWith("/");
|
|
19553
|
-
const rootDirectoryUrlName = urlToFilename$1(rootDirectoryUrl);
|
|
19554
|
-
const items = [];
|
|
19555
|
-
let dirPartsHtml = "";
|
|
19556
|
-
const parts = entryDirectoryRelativeUrl
|
|
19557
|
-
? `${rootDirectoryUrlName}/${entryDirectoryRelativeUrl.slice(0, -1)}`.split(
|
|
19558
|
-
"/",
|
|
19559
|
-
)
|
|
19560
|
-
: [rootDirectoryUrlName];
|
|
19561
|
-
let i = 0;
|
|
19562
|
-
while (i < parts.length) {
|
|
19563
|
-
const part = parts[i];
|
|
19564
|
-
const directoryRelativeUrl = `${parts.slice(1, i + 1).join("/")}`;
|
|
19565
|
-
const directoryUrl =
|
|
19566
|
-
directoryRelativeUrl === ""
|
|
19567
|
-
? rootDirectoryUrl
|
|
19568
|
-
: new URL(`${directoryRelativeUrl}/`, rootDirectoryUrl).href;
|
|
19569
|
-
let href =
|
|
19570
|
-
directoryUrl === rootDirectoryUrlForServer ||
|
|
19571
|
-
urlIsInsideOf(directoryUrl, rootDirectoryUrlForServer)
|
|
19572
|
-
? urlToRelativeUrl(directoryUrl, rootDirectoryUrlForServer)
|
|
19573
|
-
: directoryUrl;
|
|
19574
|
-
if (href === "") {
|
|
19575
|
-
href = `/${directoryContentMagicName}`;
|
|
19576
|
-
} else {
|
|
19577
|
-
href = `/${href}`;
|
|
19578
|
-
}
|
|
19579
|
-
const text = part;
|
|
19580
|
-
items.push({
|
|
19581
|
-
href,
|
|
19582
|
-
text,
|
|
19583
|
-
});
|
|
19584
|
-
i++;
|
|
19585
|
-
}
|
|
19586
|
-
i = 0;
|
|
19587
|
-
|
|
19588
|
-
const renderDirNavItem = ({ isCurrent, href, text }) => {
|
|
19589
|
-
const isServerRootDir = href === `/${directoryContentMagicName}`;
|
|
19590
|
-
if (isServerRootDir) {
|
|
19591
|
-
if (isCurrent) {
|
|
19592
|
-
return `
|
|
19593
|
-
<span class="directory_nav_item" data-current>
|
|
19594
|
-
<a class="directory_root_for_server" hot-decline href="/${mainFilePath}"></a>
|
|
19595
|
-
<span class="directory_name">${text}</span>
|
|
19596
|
-
</span>`;
|
|
19597
|
-
}
|
|
19598
|
-
return `
|
|
19599
|
-
<span class="directory_nav_item">
|
|
19600
|
-
<a class="directory_root_for_server" hot-decline href="/${mainFilePath}"></a>
|
|
19601
|
-
<a class="directory_name" hot-decline href="${href}">${text}</a>
|
|
19602
|
-
</span>`;
|
|
19603
|
-
}
|
|
19604
|
-
if (isCurrent) {
|
|
19605
|
-
return `
|
|
19606
|
-
<span class="directory_nav_item" data-current>
|
|
19607
|
-
<span class="directory_text">${text}</span>
|
|
19608
|
-
</span>`;
|
|
19609
|
-
}
|
|
19610
|
-
return `
|
|
19611
|
-
<span class="directory_nav_item">
|
|
19612
|
-
<a class="directory_text" hot-decline href="${href}">${text}</a>
|
|
19613
|
-
</span>`;
|
|
19614
|
-
};
|
|
19615
|
-
|
|
19616
|
-
for (const { href, text } of items) {
|
|
19617
|
-
const isLastPart = i === items.length - 1;
|
|
19618
|
-
dirPartsHtml += renderDirNavItem({
|
|
19619
|
-
isCurrent: isLastPart,
|
|
19620
|
-
href,
|
|
19621
|
-
text,
|
|
19622
|
-
});
|
|
19623
|
-
if (isLastPart) {
|
|
19624
|
-
break;
|
|
19625
|
-
}
|
|
19626
|
-
dirPartsHtml += `
|
|
19627
|
-
<span class="directory_separator">/</span>`;
|
|
19628
|
-
i++;
|
|
19629
|
-
}
|
|
19630
|
-
if (isDir) {
|
|
19631
|
-
dirPartsHtml += `
|
|
19632
|
-
<span class="directory_separator">/</span>`;
|
|
19633
|
-
}
|
|
19634
|
-
return dirPartsHtml;
|
|
19635
|
-
};
|
|
19636
|
-
const generateDirectoryContentItems = (
|
|
19637
|
-
directoryUrl,
|
|
19638
|
-
rootDirectoryUrlForServer,
|
|
19639
|
-
) => {
|
|
19640
|
-
let firstExistingDirectoryUrl = new URL("./", directoryUrl);
|
|
19641
|
-
while (!existsSync(firstExistingDirectoryUrl)) {
|
|
19642
|
-
firstExistingDirectoryUrl = new URL("../", firstExistingDirectoryUrl);
|
|
19643
|
-
if (!urlIsInsideOf(firstExistingDirectoryUrl, rootDirectoryUrlForServer)) {
|
|
19644
|
-
firstExistingDirectoryUrl = new URL(rootDirectoryUrlForServer);
|
|
19645
|
-
break;
|
|
19646
|
-
}
|
|
19647
|
-
}
|
|
19648
|
-
const directoryContentArray = readdirSync(firstExistingDirectoryUrl);
|
|
19649
|
-
const fileUrls = [];
|
|
19650
|
-
for (const filename of directoryContentArray) {
|
|
19651
|
-
const fileUrlObject = new URL(filename, firstExistingDirectoryUrl);
|
|
19652
|
-
fileUrls.push(fileUrlObject);
|
|
19653
|
-
}
|
|
19654
|
-
let rootDirectoryUrl = rootDirectoryUrlForServer;
|
|
19655
|
-
package_workspaces: {
|
|
19656
|
-
const packageDirectoryUrl = lookupPackageDirectory(
|
|
19657
|
-
rootDirectoryUrlForServer,
|
|
19658
|
-
);
|
|
19659
|
-
if (!packageDirectoryUrl) {
|
|
19660
|
-
break package_workspaces;
|
|
19661
|
-
}
|
|
19662
|
-
if (String(packageDirectoryUrl) === String(rootDirectoryUrlForServer)) {
|
|
19663
|
-
break package_workspaces;
|
|
19664
|
-
}
|
|
19665
|
-
rootDirectoryUrl = packageDirectoryUrl;
|
|
19666
|
-
if (
|
|
19667
|
-
String(firstExistingDirectoryUrl) === String(rootDirectoryUrlForServer)
|
|
19668
|
-
) {
|
|
19669
|
-
let packageContent;
|
|
19670
|
-
try {
|
|
19671
|
-
packageContent = JSON.parse(
|
|
19672
|
-
readFileSync(new URL("package.json", packageDirectoryUrl), "utf8"),
|
|
19673
|
-
);
|
|
19674
|
-
} catch {
|
|
19675
|
-
break package_workspaces;
|
|
19676
|
-
}
|
|
19677
|
-
const { workspaces } = packageContent;
|
|
19678
|
-
if (Array.isArray(workspaces)) {
|
|
19679
|
-
for (const workspace of workspaces) {
|
|
19680
|
-
const workspaceUrlObject = new URL(workspace, packageDirectoryUrl);
|
|
19681
|
-
const workspaceUrl = workspaceUrlObject.href;
|
|
19682
|
-
if (workspaceUrl.endsWith("*")) {
|
|
19683
|
-
const directoryUrl = ensurePathnameTrailingSlash(
|
|
19684
|
-
workspaceUrl.slice(0, -1),
|
|
19685
|
-
);
|
|
19686
|
-
fileUrls.push(new URL(directoryUrl));
|
|
19687
|
-
} else {
|
|
19688
|
-
fileUrls.push(ensurePathnameTrailingSlash(workspaceUrlObject));
|
|
19689
|
-
}
|
|
19690
|
-
}
|
|
19691
|
-
}
|
|
19692
|
-
}
|
|
19693
|
-
}
|
|
19694
|
-
|
|
19695
|
-
const sortedUrls = [];
|
|
19696
|
-
for (let fileUrl of fileUrls) {
|
|
19697
|
-
if (lstatSync(fileUrl).isDirectory()) {
|
|
19698
|
-
sortedUrls.push(ensurePathnameTrailingSlash(fileUrl));
|
|
19699
|
-
} else {
|
|
19700
|
-
sortedUrls.push(fileUrl);
|
|
19701
|
-
}
|
|
19702
|
-
}
|
|
19703
|
-
sortedUrls.sort((a, b) => {
|
|
19704
|
-
return comparePathnames(a.pathname, b.pathname);
|
|
19705
|
-
});
|
|
19706
|
-
|
|
19707
|
-
const items = [];
|
|
19708
|
-
for (const sortedUrl of sortedUrls) {
|
|
19709
|
-
const fileUrlRelativeToParent = urlToRelativeUrl(
|
|
19710
|
-
sortedUrl,
|
|
19711
|
-
firstExistingDirectoryUrl,
|
|
19712
|
-
);
|
|
19713
|
-
const fileUrlRelativeToServer = urlToRelativeUrl(
|
|
19714
|
-
sortedUrl,
|
|
19715
|
-
rootDirectoryUrlForServer,
|
|
19716
|
-
);
|
|
19717
|
-
const type = fileUrlRelativeToParent.endsWith("/") ? "dir" : "file";
|
|
19718
|
-
items.push({
|
|
19719
|
-
type,
|
|
19720
|
-
fileUrlRelativeToParent,
|
|
19721
|
-
fileUrlRelativeToServer,
|
|
19722
|
-
});
|
|
19723
|
-
}
|
|
19724
|
-
items.rootDirectoryUrlForServer = rootDirectoryUrlForServer;
|
|
19725
|
-
items.rootDirectoryUrl = rootDirectoryUrl;
|
|
19726
|
-
items.firstExistingDirectoryUrl = firstExistingDirectoryUrl;
|
|
19727
|
-
return items;
|
|
19728
|
-
};
|
|
19729
|
-
const generateDirectoryContent = (directoryContentItems, { mainFilePath }) => {
|
|
19730
|
-
if (directoryContentItems.length === 0) {
|
|
19731
|
-
return `<p class="directory_empty_message">Directory is empty</p>`;
|
|
19732
|
-
}
|
|
19733
|
-
let html = `<ul class="directory_content">`;
|
|
19734
|
-
for (const directoryContentItem of directoryContentItems) {
|
|
19735
|
-
const { type, fileUrlRelativeToParent, fileUrlRelativeToServer } =
|
|
19736
|
-
directoryContentItem;
|
|
19737
|
-
let href = fileUrlRelativeToServer;
|
|
19738
|
-
if (href === "") {
|
|
19739
|
-
href = `${directoryContentMagicName}`;
|
|
19740
|
-
}
|
|
19741
|
-
const isMainFile = href === mainFilePath;
|
|
19742
|
-
const mainFileAttr = isMainFile ? ` data-main-file` : "";
|
|
19743
|
-
html += `
|
|
19744
|
-
<li class="directory_child" data-type="${type}"${mainFileAttr}>
|
|
19745
|
-
<a href="/${href}" hot-decline>${fileUrlRelativeToParent}</a>
|
|
19746
|
-
</li>`;
|
|
19747
|
-
}
|
|
19748
|
-
html += `\n </ul>`;
|
|
19749
|
-
return html;
|
|
19750
|
-
};
|
|
19751
|
-
const replacePlaceholders$1 = (html, replacers) => {
|
|
19752
|
-
return html.replace(/\$\{(\w+)\}/g, (match, name) => {
|
|
19753
|
-
const replacer = replacers[name];
|
|
19754
|
-
if (replacer === undefined) {
|
|
19755
|
-
return match;
|
|
19756
|
-
}
|
|
19757
|
-
if (typeof replacer === "function") {
|
|
19758
|
-
return replacer();
|
|
19759
|
-
}
|
|
19760
|
-
return replacer;
|
|
19761
|
-
});
|
|
20038
|
+
return {
|
|
20039
|
+
content,
|
|
20040
|
+
contentType,
|
|
20041
|
+
contentLength: fileBuffer.length,
|
|
20042
|
+
};
|
|
20043
|
+
};
|
|
20044
|
+
|
|
20045
|
+
return serveFile(urlInfo.url);
|
|
20046
|
+
},
|
|
20047
|
+
},
|
|
20048
|
+
];
|
|
19762
20049
|
};
|
|
19763
20050
|
|
|
19764
20051
|
const jsenvPluginProtocolHttp = ({ include }) => {
|
|
@@ -19815,10 +20102,11 @@ const jsenvPluginProtocolHttp = ({ include }) => {
|
|
|
19815
20102
|
return fileUrl;
|
|
19816
20103
|
},
|
|
19817
20104
|
fetchUrlContent: async (urlInfo) => {
|
|
19818
|
-
|
|
20105
|
+
const originalUrl = urlInfo.originalUrl;
|
|
20106
|
+
if (!originalUrl.startsWith("http")) {
|
|
19819
20107
|
return null;
|
|
19820
20108
|
}
|
|
19821
|
-
const response = await fetch(
|
|
20109
|
+
const response = await fetch(originalUrl);
|
|
19822
20110
|
const responseStatus = response.status;
|
|
19823
20111
|
if (responseStatus < 200 || responseStatus > 299) {
|
|
19824
20112
|
throw new Error(`unexpected response status ${responseStatus}`);
|
|
@@ -19851,103 +20139,6 @@ const asValidFilename = (string) => {
|
|
|
19851
20139
|
return string;
|
|
19852
20140
|
};
|
|
19853
20141
|
|
|
19854
|
-
const jsenvPluginInjections = (rawAssociations) => {
|
|
19855
|
-
let resolvedAssociations;
|
|
19856
|
-
|
|
19857
|
-
return {
|
|
19858
|
-
name: "jsenv:injections",
|
|
19859
|
-
appliesDuring: "*",
|
|
19860
|
-
init: (context) => {
|
|
19861
|
-
resolvedAssociations = URL_META.resolveAssociations(
|
|
19862
|
-
{ injectionsGetter: rawAssociations },
|
|
19863
|
-
context.rootDirectoryUrl,
|
|
19864
|
-
);
|
|
19865
|
-
},
|
|
19866
|
-
transformUrlContent: async (urlInfo) => {
|
|
19867
|
-
const { injectionsGetter } = URL_META.applyAssociations({
|
|
19868
|
-
url: asUrlWithoutSearch(urlInfo.url),
|
|
19869
|
-
associations: resolvedAssociations,
|
|
19870
|
-
});
|
|
19871
|
-
if (!injectionsGetter) {
|
|
19872
|
-
return null;
|
|
19873
|
-
}
|
|
19874
|
-
if (typeof injectionsGetter !== "function") {
|
|
19875
|
-
throw new TypeError("injectionsGetter must be a function");
|
|
19876
|
-
}
|
|
19877
|
-
const injections = await injectionsGetter(urlInfo);
|
|
19878
|
-
if (!injections) {
|
|
19879
|
-
return null;
|
|
19880
|
-
}
|
|
19881
|
-
const keys = Object.keys(injections);
|
|
19882
|
-
if (keys.length === 0) {
|
|
19883
|
-
return null;
|
|
19884
|
-
}
|
|
19885
|
-
return replacePlaceholders(urlInfo.content, injections, urlInfo);
|
|
19886
|
-
},
|
|
19887
|
-
};
|
|
19888
|
-
};
|
|
19889
|
-
|
|
19890
|
-
const injectionSymbol = Symbol.for("jsenv_injection");
|
|
19891
|
-
const INJECTIONS = {
|
|
19892
|
-
optional: (value) => {
|
|
19893
|
-
return { [injectionSymbol]: "optional", value };
|
|
19894
|
-
},
|
|
19895
|
-
};
|
|
19896
|
-
|
|
19897
|
-
// we export this because it is imported by jsenv_plugin_placeholder.js and unit test
|
|
19898
|
-
const replacePlaceholders = (content, replacements, urlInfo) => {
|
|
19899
|
-
const magicSource = createMagicSource(content);
|
|
19900
|
-
for (const key of Object.keys(replacements)) {
|
|
19901
|
-
let index = content.indexOf(key);
|
|
19902
|
-
const replacement = replacements[key];
|
|
19903
|
-
let isOptional;
|
|
19904
|
-
let value;
|
|
19905
|
-
if (replacement && replacement[injectionSymbol]) {
|
|
19906
|
-
const valueBehindSymbol = replacement[injectionSymbol];
|
|
19907
|
-
isOptional = valueBehindSymbol === "optional";
|
|
19908
|
-
value = replacement.value;
|
|
19909
|
-
} else {
|
|
19910
|
-
value = replacement;
|
|
19911
|
-
}
|
|
19912
|
-
if (index === -1) {
|
|
19913
|
-
if (!isOptional) {
|
|
19914
|
-
urlInfo.context.logger.warn(
|
|
19915
|
-
`placeholder "${key}" not found in ${urlInfo.url}.
|
|
19916
|
-
--- suggestion a ---
|
|
19917
|
-
Add "${key}" in that file.
|
|
19918
|
-
--- suggestion b ---
|
|
19919
|
-
Fix eventual typo in "${key}"?
|
|
19920
|
-
--- suggestion c ---
|
|
19921
|
-
Mark injection as optional using INJECTIONS.optional():
|
|
19922
|
-
import { INJECTIONS } from "@jsenv/core";
|
|
19923
|
-
|
|
19924
|
-
return {
|
|
19925
|
-
"${key}": INJECTIONS.optional(${JSON.stringify(value)}),
|
|
19926
|
-
};`,
|
|
19927
|
-
);
|
|
19928
|
-
}
|
|
19929
|
-
continue;
|
|
19930
|
-
}
|
|
19931
|
-
|
|
19932
|
-
while (index !== -1) {
|
|
19933
|
-
const start = index;
|
|
19934
|
-
const end = index + key.length;
|
|
19935
|
-
magicSource.replace({
|
|
19936
|
-
start,
|
|
19937
|
-
end,
|
|
19938
|
-
replacement:
|
|
19939
|
-
urlInfo.type === "js_classic" ||
|
|
19940
|
-
urlInfo.type === "js_module" ||
|
|
19941
|
-
urlInfo.type === "html"
|
|
19942
|
-
? JSON.stringify(value, null, " ")
|
|
19943
|
-
: value,
|
|
19944
|
-
});
|
|
19945
|
-
index = content.indexOf(key, end);
|
|
19946
|
-
}
|
|
19947
|
-
}
|
|
19948
|
-
return magicSource.toContentAndSourcemap();
|
|
19949
|
-
};
|
|
19950
|
-
|
|
19951
20142
|
/*
|
|
19952
20143
|
* Some code uses globals specific to Node.js in code meant to run in browsers...
|
|
19953
20144
|
* This plugin will replace some node globals to things compatible with web:
|
|
@@ -21236,7 +21427,7 @@ const getCorePlugins = ({
|
|
|
21236
21427
|
nodeEsmResolution = {},
|
|
21237
21428
|
magicExtensions,
|
|
21238
21429
|
magicDirectoryIndex,
|
|
21239
|
-
|
|
21430
|
+
directoryListing = true,
|
|
21240
21431
|
directoryReferenceEffect,
|
|
21241
21432
|
supervisor,
|
|
21242
21433
|
injections,
|
|
@@ -21264,13 +21455,16 @@ const getCorePlugins = ({
|
|
|
21264
21455
|
if (http === false) {
|
|
21265
21456
|
http = { include: false };
|
|
21266
21457
|
}
|
|
21458
|
+
if (directoryListing === true) {
|
|
21459
|
+
directoryListing = {};
|
|
21460
|
+
}
|
|
21267
21461
|
|
|
21268
21462
|
return [
|
|
21269
21463
|
jsenvPluginReferenceAnalysis(referenceAnalysis),
|
|
21270
21464
|
...(injections ? [jsenvPluginInjections(injections)] : []),
|
|
21271
21465
|
jsenvPluginTranspilation(transpilation),
|
|
21466
|
+
// "jsenvPluginInlining" must be very soon because all other plugins will react differently once they see the file is inlined
|
|
21272
21467
|
...(inlining ? [jsenvPluginInlining()] : []),
|
|
21273
|
-
...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []), // after inline as it needs inline script to be cooked
|
|
21274
21468
|
|
|
21275
21469
|
/* When resolving references the following applies by default:
|
|
21276
21470
|
- http urls are resolved by jsenvPluginHttpUrls
|
|
@@ -21282,9 +21476,8 @@ const getCorePlugins = ({
|
|
|
21282
21476
|
jsenvPluginProtocolFile({
|
|
21283
21477
|
magicExtensions,
|
|
21284
21478
|
magicDirectoryIndex,
|
|
21285
|
-
|
|
21479
|
+
directoryListing,
|
|
21286
21480
|
}),
|
|
21287
|
-
|
|
21288
21481
|
{
|
|
21289
21482
|
name: "jsenv:resolve_root_as_main",
|
|
21290
21483
|
appliesDuring: "*",
|
|
@@ -21303,12 +21496,14 @@ const getCorePlugins = ({
|
|
|
21303
21496
|
: []),
|
|
21304
21497
|
jsenvPluginWebResolution(),
|
|
21305
21498
|
jsenvPluginDirectoryReferenceEffect(directoryReferenceEffect),
|
|
21306
|
-
|
|
21307
21499
|
jsenvPluginVersionSearchParam(),
|
|
21500
|
+
|
|
21501
|
+
// "jsenvPluginSupervisor" MUST be after "jsenvPluginInlining" as it needs inline script to be cooked
|
|
21502
|
+
...(supervisor ? [jsenvPluginSupervisor(supervisor)] : []),
|
|
21503
|
+
|
|
21308
21504
|
jsenvPluginCommonJsGlobals(),
|
|
21309
21505
|
jsenvPluginImportMetaScenarios(),
|
|
21310
21506
|
...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []),
|
|
21311
|
-
|
|
21312
21507
|
jsenvPluginNodeRuntime({ runtimeCompat }),
|
|
21313
21508
|
|
|
21314
21509
|
jsenvPluginImportMetaHot(),
|
|
@@ -21442,7 +21637,6 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
|
|
|
21442
21637
|
integer++;
|
|
21443
21638
|
nameCandidate = `${basename}${integer}${extension}`;
|
|
21444
21639
|
}
|
|
21445
|
-
hash = "";
|
|
21446
21640
|
const buildUrl = `${buildDirectoryUrl}${directoryPath}${nameCandidate}${search}${hash}`;
|
|
21447
21641
|
associateBuildUrl(url, buildUrl);
|
|
21448
21642
|
return buildUrl;
|
|
@@ -21868,7 +22062,6 @@ const createBuildSpecifierManager = ({
|
|
|
21868
22062
|
type: reference.type,
|
|
21869
22063
|
expectedType: reference.expectedType,
|
|
21870
22064
|
specifier: reference.specifier,
|
|
21871
|
-
specifierPathname: reference.specifierPathname,
|
|
21872
22065
|
specifierLine: reference.specifierLine,
|
|
21873
22066
|
specifierColumn: reference.specifierColumn,
|
|
21874
22067
|
specifierStart: reference.specifierStart,
|
|
@@ -23317,33 +23510,33 @@ build ${entryPointKeys.length} entry points`);
|
|
|
23317
23510
|
|
|
23318
23511
|
const bundlers = {};
|
|
23319
23512
|
{
|
|
23320
|
-
rawKitchen.pluginController.
|
|
23513
|
+
for (const plugin of rawKitchen.pluginController.activePlugins) {
|
|
23321
23514
|
const bundle = plugin.bundle;
|
|
23322
23515
|
if (!bundle) {
|
|
23323
|
-
|
|
23516
|
+
continue;
|
|
23324
23517
|
}
|
|
23325
23518
|
if (typeof bundle !== "object") {
|
|
23326
23519
|
throw new Error(
|
|
23327
23520
|
`bundle must be an object, found "${bundle}" on plugin named "${plugin.name}"`,
|
|
23328
23521
|
);
|
|
23329
23522
|
}
|
|
23330
|
-
Object.keys(bundle)
|
|
23523
|
+
for (const type of Object.keys(bundle)) {
|
|
23331
23524
|
const bundleFunction = bundle[type];
|
|
23332
23525
|
if (!bundleFunction) {
|
|
23333
|
-
|
|
23526
|
+
continue;
|
|
23334
23527
|
}
|
|
23335
23528
|
const bundlerForThatType = bundlers[type];
|
|
23336
23529
|
if (bundlerForThatType) {
|
|
23337
23530
|
// first plugin to define a bundle hook wins
|
|
23338
|
-
|
|
23531
|
+
continue;
|
|
23339
23532
|
}
|
|
23340
23533
|
bundlers[type] = {
|
|
23341
23534
|
plugin,
|
|
23342
23535
|
bundleFunction: bundle[type],
|
|
23343
23536
|
urlInfoMap: new Map(),
|
|
23344
23537
|
};
|
|
23345
|
-
}
|
|
23346
|
-
}
|
|
23538
|
+
}
|
|
23539
|
+
}
|
|
23347
23540
|
const addToBundlerIfAny = (rawUrlInfo) => {
|
|
23348
23541
|
const bundler = bundlers[rawUrlInfo.type];
|
|
23349
23542
|
if (bundler) {
|
|
@@ -23663,43 +23856,6 @@ const WEB_URL_CONVERTER = {
|
|
|
23663
23856
|
},
|
|
23664
23857
|
};
|
|
23665
23858
|
|
|
23666
|
-
/*
|
|
23667
|
-
* This plugin is very special because it is here
|
|
23668
|
-
* to provide "serverEvents" used by other plugins
|
|
23669
|
-
*/
|
|
23670
|
-
|
|
23671
|
-
|
|
23672
|
-
const serverEventsClientFileUrl = new URL(
|
|
23673
|
-
"./js/server_events_client.js",
|
|
23674
|
-
import.meta.url,
|
|
23675
|
-
).href;
|
|
23676
|
-
|
|
23677
|
-
const jsenvPluginServerEventsClientInjection = ({ logs = true }) => {
|
|
23678
|
-
return {
|
|
23679
|
-
name: "jsenv:server_events_client_injection",
|
|
23680
|
-
appliesDuring: "*",
|
|
23681
|
-
transformUrlContent: {
|
|
23682
|
-
html: (urlInfo) => {
|
|
23683
|
-
const htmlAst = parseHtml({
|
|
23684
|
-
html: urlInfo.content,
|
|
23685
|
-
url: urlInfo.url,
|
|
23686
|
-
});
|
|
23687
|
-
injectJsenvScript(htmlAst, {
|
|
23688
|
-
src: serverEventsClientFileUrl,
|
|
23689
|
-
initCall: {
|
|
23690
|
-
callee: "window.__server_events__.setup",
|
|
23691
|
-
params: {
|
|
23692
|
-
logs,
|
|
23693
|
-
},
|
|
23694
|
-
},
|
|
23695
|
-
pluginName: "jsenv:server_events_client_injection",
|
|
23696
|
-
});
|
|
23697
|
-
return stringifyHtmlAst(htmlAst);
|
|
23698
|
-
},
|
|
23699
|
-
},
|
|
23700
|
-
};
|
|
23701
|
-
};
|
|
23702
|
-
|
|
23703
23859
|
const createServerEventsDispatcher = () => {
|
|
23704
23860
|
const clients = [];
|
|
23705
23861
|
const MAX_CLIENTS = 100;
|
|
@@ -23795,6 +23951,105 @@ const createServerEventsDispatcher = () => {
|
|
|
23795
23951
|
};
|
|
23796
23952
|
};
|
|
23797
23953
|
|
|
23954
|
+
/*
|
|
23955
|
+
* This plugin is very special because it is here
|
|
23956
|
+
* to provide "serverEvents" used by other plugins
|
|
23957
|
+
*/
|
|
23958
|
+
|
|
23959
|
+
|
|
23960
|
+
const serverEventsClientFileUrl = new URL(
|
|
23961
|
+
"./js/server_events_client.js",
|
|
23962
|
+
import.meta.url,
|
|
23963
|
+
).href;
|
|
23964
|
+
|
|
23965
|
+
const jsenvPluginServerEvents = ({ clientAutoreload }) => {
|
|
23966
|
+
let serverEventsDispatcher;
|
|
23967
|
+
|
|
23968
|
+
const { clientServerEventsConfig } = clientAutoreload;
|
|
23969
|
+
const { logs = true } = clientServerEventsConfig;
|
|
23970
|
+
|
|
23971
|
+
return {
|
|
23972
|
+
name: "jsenv:server_events",
|
|
23973
|
+
appliesDuring: "dev",
|
|
23974
|
+
effect: ({ kitchenContext, otherPlugins }) => {
|
|
23975
|
+
const allServerEvents = {};
|
|
23976
|
+
for (const otherPlugin of otherPlugins) {
|
|
23977
|
+
const { serverEvents } = otherPlugin;
|
|
23978
|
+
if (!serverEvents) {
|
|
23979
|
+
continue;
|
|
23980
|
+
}
|
|
23981
|
+
for (const serverEventName of Object.keys(serverEvents)) {
|
|
23982
|
+
// we could throw on serverEvent name conflict
|
|
23983
|
+
// we could throw if serverEvents[serverEventName] is not a function
|
|
23984
|
+
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
23985
|
+
}
|
|
23986
|
+
}
|
|
23987
|
+
const serverEventNames = Object.keys(allServerEvents);
|
|
23988
|
+
if (serverEventNames.length === 0) {
|
|
23989
|
+
return false;
|
|
23990
|
+
}
|
|
23991
|
+
serverEventsDispatcher = createServerEventsDispatcher();
|
|
23992
|
+
const onabort = () => {
|
|
23993
|
+
serverEventsDispatcher.destroy();
|
|
23994
|
+
};
|
|
23995
|
+
kitchenContext.signal.addEventListener("abort", onabort);
|
|
23996
|
+
for (const serverEventName of Object.keys(allServerEvents)) {
|
|
23997
|
+
const serverEventInfo = {
|
|
23998
|
+
...kitchenContext,
|
|
23999
|
+
// serverEventsDispatcher variable is safe, we can disable esling warning
|
|
24000
|
+
// eslint-disable-next-line no-loop-func
|
|
24001
|
+
sendServerEvent: (data) => {
|
|
24002
|
+
if (!serverEventsDispatcher) {
|
|
24003
|
+
// this can happen if a plugin wants to send a server event but
|
|
24004
|
+
// server is closing or the plugin got destroyed but still wants to do things
|
|
24005
|
+
// if plugin code is correctly written it is never supposed to happen
|
|
24006
|
+
// because it means a plugin is still trying to do stuff after being destroyed
|
|
24007
|
+
return;
|
|
24008
|
+
}
|
|
24009
|
+
serverEventsDispatcher.dispatch({
|
|
24010
|
+
type: serverEventName,
|
|
24011
|
+
data,
|
|
24012
|
+
});
|
|
24013
|
+
},
|
|
24014
|
+
};
|
|
24015
|
+
const serverEventInit = allServerEvents[serverEventName];
|
|
24016
|
+
serverEventInit(serverEventInfo);
|
|
24017
|
+
}
|
|
24018
|
+
return () => {
|
|
24019
|
+
kitchenContext.signal.removeEventListener("abort", onabort);
|
|
24020
|
+
serverEventsDispatcher.destroy();
|
|
24021
|
+
serverEventsDispatcher = undefined;
|
|
24022
|
+
};
|
|
24023
|
+
},
|
|
24024
|
+
serveWebsocket: async ({ websocket, request }) => {
|
|
24025
|
+
if (request.headers["sec-websocket-protocol"] !== "jsenv") {
|
|
24026
|
+
return false;
|
|
24027
|
+
}
|
|
24028
|
+
serverEventsDispatcher.addWebsocket(websocket, request);
|
|
24029
|
+
return true;
|
|
24030
|
+
},
|
|
24031
|
+
transformUrlContent: {
|
|
24032
|
+
html: (urlInfo) => {
|
|
24033
|
+
const htmlAst = parseHtml({
|
|
24034
|
+
html: urlInfo.content,
|
|
24035
|
+
url: urlInfo.url,
|
|
24036
|
+
});
|
|
24037
|
+
injectJsenvScript(htmlAst, {
|
|
24038
|
+
src: serverEventsClientFileUrl,
|
|
24039
|
+
initCall: {
|
|
24040
|
+
callee: "window.__server_events__.setup",
|
|
24041
|
+
params: {
|
|
24042
|
+
logs,
|
|
24043
|
+
},
|
|
24044
|
+
},
|
|
24045
|
+
pluginName: "jsenv:server_events",
|
|
24046
|
+
});
|
|
24047
|
+
return stringifyHtmlAst(htmlAst);
|
|
24048
|
+
},
|
|
24049
|
+
},
|
|
24050
|
+
};
|
|
24051
|
+
};
|
|
24052
|
+
|
|
23798
24053
|
const memoizeByFirstArgument = (compute) => {
|
|
23799
24054
|
const urlCache = new Map();
|
|
23800
24055
|
|
|
@@ -23879,7 +24134,7 @@ const startDevServer = async ({
|
|
|
23879
24134
|
supervisor = true,
|
|
23880
24135
|
magicExtensions,
|
|
23881
24136
|
magicDirectoryIndex,
|
|
23882
|
-
|
|
24137
|
+
directoryListing,
|
|
23883
24138
|
injections,
|
|
23884
24139
|
transpilation,
|
|
23885
24140
|
cacheControl = true,
|
|
@@ -23952,10 +24207,11 @@ const startDevServer = async ({
|
|
|
23952
24207
|
});
|
|
23953
24208
|
|
|
23954
24209
|
const serverStopCallbackSet = new Set();
|
|
23955
|
-
const
|
|
24210
|
+
const serverStopAbortController = new AbortController();
|
|
23956
24211
|
serverStopCallbackSet.add(() => {
|
|
23957
|
-
|
|
24212
|
+
serverStopAbortController.abort();
|
|
23958
24213
|
});
|
|
24214
|
+
const serverStopAbortSignal = serverStopAbortController.signal;
|
|
23959
24215
|
const kitchenCache = new Map();
|
|
23960
24216
|
|
|
23961
24217
|
const finalServices = [];
|
|
@@ -24058,7 +24314,7 @@ const startDevServer = async ({
|
|
|
24058
24314
|
|
|
24059
24315
|
kitchen = createKitchen({
|
|
24060
24316
|
name: runtimeId,
|
|
24061
|
-
signal,
|
|
24317
|
+
signal: serverStopAbortSignal,
|
|
24062
24318
|
logLevel,
|
|
24063
24319
|
rootDirectoryUrl: sourceDirectoryUrl,
|
|
24064
24320
|
mainFilePath: sourceMainFilePath,
|
|
@@ -24067,6 +24323,7 @@ const startDevServer = async ({
|
|
|
24067
24323
|
runtimeCompat,
|
|
24068
24324
|
clientRuntimeCompat,
|
|
24069
24325
|
plugins: [
|
|
24326
|
+
jsenvPluginServerEvents({ clientAutoreload }),
|
|
24070
24327
|
...plugins,
|
|
24071
24328
|
...getCorePlugins({
|
|
24072
24329
|
rootDirectoryUrl: sourceDirectoryUrl,
|
|
@@ -24076,7 +24333,7 @@ const startDevServer = async ({
|
|
|
24076
24333
|
nodeEsmResolution,
|
|
24077
24334
|
magicExtensions,
|
|
24078
24335
|
magicDirectoryIndex,
|
|
24079
|
-
|
|
24336
|
+
directoryListing,
|
|
24080
24337
|
supervisor,
|
|
24081
24338
|
injections,
|
|
24082
24339
|
transpilation,
|
|
@@ -24142,7 +24399,22 @@ const startDevServer = async ({
|
|
|
24142
24399
|
for (const implicitUrl of urlInfoCreated.implicitUrlSet) {
|
|
24143
24400
|
const implicitUrlInfo =
|
|
24144
24401
|
urlInfoCreated.graph.getUrlInfo(implicitUrl);
|
|
24145
|
-
if (
|
|
24402
|
+
if (!implicitUrlInfo) {
|
|
24403
|
+
continue;
|
|
24404
|
+
}
|
|
24405
|
+
if (implicitUrlInfo.content === undefined) {
|
|
24406
|
+
// happens when we explicitely load an url with a search param
|
|
24407
|
+
// - it creates an implicit url info to the url without params
|
|
24408
|
+
// - we never explicitely request the url without search param so it has no content
|
|
24409
|
+
// in that case the underlying urlInfo cannot be invalidate by the implicit
|
|
24410
|
+
// we use modifiedTimestamp to detect if the url was loaded once
|
|
24411
|
+
// or is just here to be used later
|
|
24412
|
+
if (implicitUrlInfo.modifiedTimestamp) {
|
|
24413
|
+
return false;
|
|
24414
|
+
}
|
|
24415
|
+
continue;
|
|
24416
|
+
}
|
|
24417
|
+
if (!implicitUrlInfo.isValid()) {
|
|
24146
24418
|
return false;
|
|
24147
24419
|
}
|
|
24148
24420
|
}
|
|
@@ -24161,41 +24433,6 @@ const startDevServer = async ({
|
|
|
24161
24433
|
serverStopCallbackSet.add(() => {
|
|
24162
24434
|
kitchen.pluginController.callHooks("destroy", kitchen.context);
|
|
24163
24435
|
});
|
|
24164
|
-
{
|
|
24165
|
-
const allServerEvents = {};
|
|
24166
|
-
kitchen.pluginController.plugins.forEach((plugin) => {
|
|
24167
|
-
const { serverEvents } = plugin;
|
|
24168
|
-
if (serverEvents) {
|
|
24169
|
-
Object.keys(serverEvents).forEach((serverEventName) => {
|
|
24170
|
-
// we could throw on serverEvent name conflict
|
|
24171
|
-
// we could throw if serverEvents[serverEventName] is not a function
|
|
24172
|
-
allServerEvents[serverEventName] = serverEvents[serverEventName];
|
|
24173
|
-
});
|
|
24174
|
-
}
|
|
24175
|
-
});
|
|
24176
|
-
const serverEventNames = Object.keys(allServerEvents);
|
|
24177
|
-
if (serverEventNames.length > 0) {
|
|
24178
|
-
Object.keys(allServerEvents).forEach((serverEventName) => {
|
|
24179
|
-
const serverEventInfo = {
|
|
24180
|
-
...kitchen.context,
|
|
24181
|
-
sendServerEvent: (data) => {
|
|
24182
|
-
serverEventsDispatcher.dispatch({
|
|
24183
|
-
type: serverEventName,
|
|
24184
|
-
data,
|
|
24185
|
-
});
|
|
24186
|
-
},
|
|
24187
|
-
};
|
|
24188
|
-
const serverEventInit = allServerEvents[serverEventName];
|
|
24189
|
-
serverEventInit(serverEventInfo);
|
|
24190
|
-
});
|
|
24191
|
-
kitchen.pluginController.unshiftPlugin(
|
|
24192
|
-
jsenvPluginServerEventsClientInjection(
|
|
24193
|
-
clientAutoreload.clientServerEventsConfig,
|
|
24194
|
-
),
|
|
24195
|
-
);
|
|
24196
|
-
}
|
|
24197
|
-
}
|
|
24198
|
-
|
|
24199
24436
|
kitchenCache.set(runtimeId, kitchen);
|
|
24200
24437
|
onKitchenCreated(kitchen);
|
|
24201
24438
|
return kitchen;
|
|
@@ -24217,6 +24454,20 @@ const startDevServer = async ({
|
|
|
24217
24454
|
if (responseFromPlugin) {
|
|
24218
24455
|
return responseFromPlugin;
|
|
24219
24456
|
}
|
|
24457
|
+
const { rootDirectoryUrl, mainFilePath } = kitchen.context;
|
|
24458
|
+
let requestResource = request.resource;
|
|
24459
|
+
let requestedUrl;
|
|
24460
|
+
if (requestResource.startsWith("/@fs/")) {
|
|
24461
|
+
const fsRootRelativeUrl = requestResource.slice("/@fs/".length);
|
|
24462
|
+
requestedUrl = `file:///${fsRootRelativeUrl}`;
|
|
24463
|
+
} else {
|
|
24464
|
+
const requestedUrlObject = new URL(
|
|
24465
|
+
requestResource === "/" ? mainFilePath : requestResource.slice(1),
|
|
24466
|
+
rootDirectoryUrl,
|
|
24467
|
+
);
|
|
24468
|
+
requestedUrlObject.searchParams.delete("hot");
|
|
24469
|
+
requestedUrl = requestedUrlObject.href;
|
|
24470
|
+
}
|
|
24220
24471
|
const { referer } = request.headers;
|
|
24221
24472
|
const parentUrl = referer
|
|
24222
24473
|
? WEB_URL_CONVERTER.asFileUrl(referer, {
|
|
@@ -24228,15 +24479,20 @@ const startDevServer = async ({
|
|
|
24228
24479
|
request.resource,
|
|
24229
24480
|
parentUrl,
|
|
24230
24481
|
);
|
|
24231
|
-
if (
|
|
24482
|
+
if (reference) {
|
|
24483
|
+
reference.urlInfo.context.request = request;
|
|
24484
|
+
reference.urlInfo.context.requestedUrl = requestedUrl;
|
|
24485
|
+
} else {
|
|
24232
24486
|
const rootUrlInfo = kitchen.graph.rootUrlInfo;
|
|
24233
24487
|
rootUrlInfo.context.request = request;
|
|
24488
|
+
rootUrlInfo.context.requestedUrl = requestedUrl;
|
|
24234
24489
|
reference = rootUrlInfo.dependencies.createResolveAndFinalize({
|
|
24235
24490
|
trace: { message: parentUrl },
|
|
24236
24491
|
type: "http_request",
|
|
24237
24492
|
specifier: request.resource,
|
|
24238
24493
|
});
|
|
24239
24494
|
rootUrlInfo.context.request = null;
|
|
24495
|
+
rootUrlInfo.context.requestedUrl = null;
|
|
24240
24496
|
}
|
|
24241
24497
|
const urlInfo = reference.urlInfo;
|
|
24242
24498
|
const ifNoneMatch = request.headers["if-none-match"];
|
|
@@ -24280,9 +24536,10 @@ const startDevServer = async ({
|
|
|
24280
24536
|
// If they match jsenv bypass cooking and returns 304
|
|
24281
24537
|
// This must not happen when a plugin uses "no-store" or "no-cache" as it means
|
|
24282
24538
|
// plugin logic wants to happens for every request to this url
|
|
24283
|
-
...(
|
|
24284
|
-
|
|
24285
|
-
|
|
24539
|
+
...(cacheIsDisabledInResponseHeader(urlInfoTargetedByCache)
|
|
24540
|
+
? {
|
|
24541
|
+
"cache-control": "no-store", // for inline file we force no-store when parent is no-store
|
|
24542
|
+
}
|
|
24286
24543
|
: {
|
|
24287
24544
|
"cache-control": `private,max-age=0,must-revalidate`,
|
|
24288
24545
|
// it's safe to use "_" separator because etag is encoded with base64 (see https://stackoverflow.com/a/13195197)
|
|
@@ -24383,13 +24640,20 @@ ${error.trace?.message}`);
|
|
|
24383
24640
|
};
|
|
24384
24641
|
}
|
|
24385
24642
|
},
|
|
24386
|
-
handleWebsocket: (websocket, { request }) => {
|
|
24643
|
+
handleWebsocket: async (websocket, { request }) => {
|
|
24387
24644
|
// if (true || logLevel === "debug") {
|
|
24388
24645
|
// console.log("handleWebsocket", websocket, request.headers);
|
|
24389
24646
|
// }
|
|
24390
|
-
|
|
24391
|
-
|
|
24392
|
-
|
|
24647
|
+
const kitchen = getOrCreateKitchen(request);
|
|
24648
|
+
const serveWebsocketHookInfo = {
|
|
24649
|
+
request,
|
|
24650
|
+
websocket,
|
|
24651
|
+
context: kitchen.context,
|
|
24652
|
+
};
|
|
24653
|
+
await kitchen.pluginController.callAsyncHooksUntil(
|
|
24654
|
+
"serveWebsocket",
|
|
24655
|
+
serveWebsocketHookInfo,
|
|
24656
|
+
);
|
|
24393
24657
|
},
|
|
24394
24658
|
});
|
|
24395
24659
|
}
|
|
@@ -24483,6 +24747,13 @@ ${error.trace?.message}`);
|
|
|
24483
24747
|
};
|
|
24484
24748
|
};
|
|
24485
24749
|
|
|
24750
|
+
const cacheIsDisabledInResponseHeader = (urlInfo) => {
|
|
24751
|
+
return (
|
|
24752
|
+
urlInfo.headers["cache-control"] === "no-store" ||
|
|
24753
|
+
urlInfo.headers["cache-control"] === "no-cache"
|
|
24754
|
+
);
|
|
24755
|
+
};
|
|
24756
|
+
|
|
24486
24757
|
/*
|
|
24487
24758
|
* startBuildServer is mean to interact with the build files;
|
|
24488
24759
|
* files that will be deployed to production server(s).
|