@jsenv/core 40.8.4 → 40.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -527,6 +527,34 @@ const assertFetchedContentCompliance = ({ urlInfo, content }) => {
527
527
  }
528
528
  };
529
529
 
530
+ const FILE_AND_SERVER_URLS_CONVERTER = {
531
+ asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
532
+ if (urlIsOrIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
533
+ const urlRelativeToServer = urlToRelativeUrl(
534
+ fileUrl,
535
+ serverRootDirectoryUrl,
536
+ );
537
+ return `/${urlRelativeToServer}`;
538
+ }
539
+ const urlRelativeToFilesystemRoot = String(fileUrl).slice(
540
+ "file:///".length,
541
+ );
542
+ return `/@fs/${urlRelativeToFilesystemRoot}`;
543
+ },
544
+ asFileUrl: (urlRelativeToServer, serverRootDirectoryUrl) => {
545
+ if (urlRelativeToServer.startsWith("/@fs/")) {
546
+ const urlRelativeToFilesystemRoot = urlRelativeToServer.slice(
547
+ "/@fs/".length,
548
+ );
549
+ return `file:///${urlRelativeToFilesystemRoot}`;
550
+ }
551
+ if (urlRelativeToServer[0] === "/") {
552
+ return new URL(urlRelativeToServer.slice(1), serverRootDirectoryUrl).href;
553
+ }
554
+ return new URL(urlRelativeToServer, serverRootDirectoryUrl).href;
555
+ },
556
+ };
557
+
530
558
  const determineFileUrlForOutDirectory = (urlInfo) => {
531
559
  let { url, filenameHint } = urlInfo;
532
560
  const { rootDirectoryUrl, outDirectoryUrl } = urlInfo.context;
@@ -2952,6 +2980,10 @@ const createKitchen = ({
2952
2980
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
2953
2981
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
2954
2982
  isPlaceholderInjection,
2983
+ asServerUrl: (fileUrl) =>
2984
+ FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(fileUrl, rootDirectoryUrl),
2985
+ asFileUrl: (serverUrl) =>
2986
+ FILE_AND_SERVER_URLS_CONVERTER.asFileUrl(serverUrl, rootDirectoryUrl),
2955
2987
  INJECTIONS,
2956
2988
  getPluginMeta: null,
2957
2989
  sourcemaps,
@@ -6486,34 +6518,6 @@ const jsenvPluginVersionSearchParam = () => {
6486
6518
  };
6487
6519
  };
6488
6520
 
6489
- const FILE_AND_SERVER_URLS_CONVERTER = {
6490
- asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
6491
- if (urlIsOrIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
6492
- const urlRelativeToServer = urlToRelativeUrl(
6493
- fileUrl,
6494
- serverRootDirectoryUrl,
6495
- );
6496
- return `/${urlRelativeToServer}`;
6497
- }
6498
- const urlRelativeToFilesystemRoot = String(fileUrl).slice(
6499
- "file:///".length,
6500
- );
6501
- return `/@fs/${urlRelativeToFilesystemRoot}`;
6502
- },
6503
- asFileUrl: (urlRelativeToServer, serverRootDirectoryUrl) => {
6504
- if (urlRelativeToServer.startsWith("/@fs/")) {
6505
- const urlRelativeToFilesystemRoot = urlRelativeToServer.slice(
6506
- "/@fs/".length,
6507
- );
6508
- return `file:///${urlRelativeToFilesystemRoot}`;
6509
- }
6510
- if (urlRelativeToServer[0] === "/") {
6511
- return new URL(urlRelativeToServer.slice(1), serverRootDirectoryUrl).href;
6512
- }
6513
- return new URL(urlRelativeToServer, serverRootDirectoryUrl).href;
6514
- },
6515
- };
6516
-
6517
6521
  /*
6518
6522
  * NICE TO HAVE:
6519
6523
  *
@@ -8818,6 +8822,49 @@ const jsenvPluginRibbon = ({
8818
8822
  };
8819
8823
  };
8820
8824
 
8825
+ /**
8826
+ * HTML page server by jsenv dev server will listen for drop events
8827
+ * and redirect the browser to the dropped file location.
8828
+ *
8829
+ * Works only for VSCode right now (because it sets "resourceurls" dataTransfer type).
8830
+ *
8831
+ */
8832
+
8833
+
8834
+ const jsenvPluginDropToOpen = () => {
8835
+ const clientFileUrl = import.meta.resolve("../js/drop_to_open.js");
8836
+ return {
8837
+ name: "jsenv:drop_to_open",
8838
+ appliesDuring: "dev",
8839
+ transformUrlContent: {
8840
+ html: (urlInfo) => {
8841
+ const htmlAst = parseHtml({
8842
+ html: urlInfo.content,
8843
+ url: urlInfo.url,
8844
+ });
8845
+ const clientFileReference = urlInfo.dependencies.inject({
8846
+ type: "script",
8847
+ subtype: "js_module",
8848
+ expectedType: "js_module",
8849
+ specifier: clientFileUrl,
8850
+ });
8851
+ injectJsenvScript(htmlAst, {
8852
+ type: "module",
8853
+ src: clientFileReference.generatedSpecifier,
8854
+ initCall: {
8855
+ callee: "initDropToOpen",
8856
+ params: {
8857
+ rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
8858
+ },
8859
+ },
8860
+ pluginName: "jsenv:drop_to_open",
8861
+ });
8862
+ return stringifyHtmlAst(htmlAst);
8863
+ },
8864
+ },
8865
+ };
8866
+ };
8867
+
8821
8868
  const jsenvPluginCleanHTML = () => {
8822
8869
  return {
8823
8870
  name: "jsenv:cleanup_html_during_dev",
@@ -9198,6 +9245,7 @@ const getCorePlugins = ({
9198
9245
  : []),
9199
9246
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
9200
9247
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
9248
+ jsenvPluginDropToOpen(),
9201
9249
  jsenvPluginCleanHTML(),
9202
9250
  jsenvPluginChromeDevtoolsJson(),
9203
9251
  ...(packageSideEffects
@@ -2692,15 +2692,24 @@ const createLookupPackageDirectory = () => {
2692
2692
  };
2693
2693
 
2694
2694
  const readPackageAtOrNull = (packageDirectoryUrl) => {
2695
+ const packageJsonFileUrl = new URL("./package.json", packageDirectoryUrl);
2696
+ let packageJsonFileContentBuffer;
2695
2697
  try {
2696
- const packageFileContent = readFileSync$1(
2697
- new URL("./package.json", packageDirectoryUrl),
2698
- "utf8",
2698
+ packageJsonFileContentBuffer = readFileSync$1(packageJsonFileUrl, "utf8");
2699
+ } catch (e) {
2700
+ if (e.code === "ENOENT") {
2701
+ return null;
2702
+ }
2703
+ throw e;
2704
+ }
2705
+ const packageJsonFileContentString = String(packageJsonFileContentBuffer);
2706
+ try {
2707
+ const packageJsonFileContentObject = JSON.parse(
2708
+ packageJsonFileContentString,
2699
2709
  );
2700
- const packageJSON = JSON.parse(packageFileContent);
2701
- return packageJSON;
2710
+ return packageJsonFileContentObject;
2702
2711
  } catch {
2703
- return null;
2712
+ throw new Error(`Invalid package configuration at ${packageJsonFileUrl}`);
2704
2713
  }
2705
2714
  };
2706
2715
 
@@ -5244,13 +5253,24 @@ const defaultLookupPackageScope = (url) => {
5244
5253
  };
5245
5254
 
5246
5255
  const defaultReadPackageJson = (packageUrl) => {
5247
- const packageJsonUrl = new URL("package.json", packageUrl);
5248
- const buffer = readFileSync$1(packageJsonUrl);
5249
- const string = String(buffer);
5256
+ const packageJsonFileUrl = new URL("./package.json", packageUrl);
5257
+ let packageJsonFileContentBuffer;
5250
5258
  try {
5251
- return JSON.parse(string);
5259
+ packageJsonFileContentBuffer = readFileSync$1(packageJsonFileUrl, "utf8");
5260
+ } catch (e) {
5261
+ if (e.code === "ENOENT") {
5262
+ return null;
5263
+ }
5264
+ throw e;
5265
+ }
5266
+ const packageJsonFileContentString = String(packageJsonFileContentBuffer);
5267
+ try {
5268
+ const packageJsonFileContentObject = JSON.parse(
5269
+ packageJsonFileContentString,
5270
+ );
5271
+ return packageJsonFileContentObject;
5252
5272
  } catch {
5253
- throw new Error(`Invalid package configuration`);
5273
+ throw new Error(`Invalid package configuration at ${packageJsonFileUrl}`);
5254
5274
  }
5255
5275
  };
5256
5276
 
@@ -99,8 +99,10 @@ a.nav_item_text {
99
99
 
100
100
  .directory_content {
101
101
  border-radius: 3px;
102
+ flex-direction: column;
102
103
  margin: 10px 15px;
103
104
  list-style-type: none;
105
+ display: flex;
104
106
  }
105
107
 
106
108
  .directory_content_item {
@@ -109,7 +111,7 @@ a.nav_item_text {
109
111
  min-height: 19px;
110
112
  padding: 10px 15px;
111
113
  font-size: 15px;
112
- display: flex;
114
+ display: inline-flex;
113
115
  position: relative;
114
116
  }
115
117
 
@@ -117,13 +119,16 @@ a.nav_item_text {
117
119
  flex: 1;
118
120
  align-items: center;
119
121
  gap: 10px;
122
+ min-width: 0;
123
+ max-width: 100%;
120
124
  text-decoration: none;
121
- display: flex;
125
+ display: inline-flex;
122
126
  }
123
127
 
124
128
  .directory_content_item_icon {
125
129
  aspect-ratio: 1;
126
130
  color: #f1f1f1;
131
+ flex-shrink: 0;
127
132
  width: 15px;
128
133
  display: flex;
129
134
  }
@@ -133,7 +138,9 @@ a.nav_item_text {
133
138
  }
134
139
 
135
140
  .directory_content_item_text {
136
- display: flex;
141
+ flex-grow: 1;
142
+ align-items: center;
143
+ display: inline-flex;
137
144
  }
138
145
 
139
146
  .directory_content_item:last-child {
@@ -53,15 +53,17 @@ const ErrorMessage = () => {
53
53
  errorText = u(k, {
54
54
  children: [u("strong", {
55
55
  children: "File not found:"
56
- }), "\xA0", u("code", {
57
- children: [u("span", {
58
- className: "file_path_good",
59
- children: filePathExisting
60
- }), u("span", {
61
- className: "file_path_bad",
62
- children: filePathNotFound
63
- })]
64
- }), " ", "does not exist on the server."]
56
+ }), "\xA0", u(Overflow, {
57
+ children: [u("code", {
58
+ children: [u("span", {
59
+ className: "file_path_good",
60
+ children: filePathExisting
61
+ }), u("span", {
62
+ className: "file_path_bad",
63
+ children: filePathNotFound
64
+ })]
65
+ }), " ", "does not exist on the server."]
66
+ })]
65
67
  });
66
68
  errorSuggestion = u(k, {
67
69
  children: [u("span", {
@@ -84,6 +86,21 @@ const ErrorMessage = () => {
84
86
  })]
85
87
  });
86
88
  };
89
+ const Overflow = ({
90
+ children,
91
+ afterContent
92
+ }) => {
93
+ return u("div", {
94
+ style: "display: flex; flex-wrap: wrap; overflow: hidden; width: 100%; box-sizing: border-box; white-space: nowrap; text-overflow: ellipsis;",
95
+ children: u("div", {
96
+ style: "display: flex; flex-grow: 1; width: 0;",
97
+ children: [u("div", {
98
+ style: "overflow: hidden; max-width: 100%; text-overflow: ellipsis;",
99
+ children: children
100
+ }), afterContent]
101
+ })
102
+ });
103
+ };
87
104
  const Breadcrumb = ({
88
105
  items
89
106
  }) => {
@@ -163,7 +180,7 @@ const DirectoryContent = ({
163
180
  url: directoryItem.urlRelativeToServer,
164
181
  isDirectory: directoryItem.url.endsWith("/"),
165
182
  isMainFile: directoryItem.isMainFile,
166
- children: directoryItem.urlRelativeToCurrentDirectory
183
+ children: decodeURI(directoryItem.urlRelativeToCurrentDirectory)
167
184
  }, directoryItem.url);
168
185
  })
169
186
  });
@@ -189,14 +206,19 @@ const DirectoryContentItem = ({
189
206
  children: u(Icon, {
190
207
  url: isMainFile ? homeIconUrl : isDirectory ? directoryIconUrl : fileIconUrl
191
208
  })
192
- }), children, isDirectory ? u(k, {
193
- children: [u("span", {
194
- style: "flex:1"
195
- }), u("span", {
196
- className: "directory_content_item_arrow",
197
- children: u(RightArrowSvg, {})
198
- })]
199
- }) : null]
209
+ }), u("span", {
210
+ className: "directory_content_item_text",
211
+ children: [u(Overflow, {
212
+ children: children
213
+ }), isDirectory ? u(k, {
214
+ children: [u("span", {
215
+ style: "flex:1"
216
+ }), u("span", {
217
+ className: "directory_content_item_arrow",
218
+ children: u(RightArrowSvg, {})
219
+ })]
220
+ }) : null]
221
+ })]
200
222
  })
201
223
  });
202
224
  };
@@ -0,0 +1,51 @@
1
+ const initDropToOpen = ({ rootDirectoryUrl }) => {
2
+ const dataTransferCandidates = [
3
+ (dataTransfer) => {
4
+ if (!dataTransfer.types.includes("resourceurls")) {
5
+ return null;
6
+ }
7
+ return () => {
8
+ const data = dataTransfer.getData("resourceurls");
9
+ const urls = JSON.parse(data);
10
+ if (!Array.isArray(urls) || urls.length === 0) {
11
+ return;
12
+ }
13
+ const [url] = urls;
14
+ const fileUrl = new URL(url).href;
15
+ let serverUrl;
16
+
17
+ if (fileUrl.startsWith(rootDirectoryUrl)) {
18
+ const serverRelativeUrl = fileUrl.slice(rootDirectoryUrl.length);
19
+ serverUrl = `/${serverRelativeUrl}`;
20
+ } else {
21
+ serverUrl = `/@fs/${fileUrl}`;
22
+ }
23
+ window.location.href = serverUrl;
24
+ };
25
+ },
26
+ ];
27
+
28
+ document.addEventListener("dragover", (event) => {
29
+ for (const candidate of dataTransferCandidates) {
30
+ const dataTransferHandler = candidate(event.dataTransfer);
31
+ if (dataTransferHandler) {
32
+ event.preventDefault();
33
+ return;
34
+ }
35
+ }
36
+ });
37
+ document.addEventListener("drop", (event) => {
38
+ let handler;
39
+ for (const candidate of dataTransferCandidates) {
40
+ const dataTransferHandler = candidate(event.dataTransfer);
41
+ if (dataTransferHandler) {
42
+ handler = dataTransferHandler;
43
+ break;
44
+ }
45
+ }
46
+ event.preventDefault();
47
+ handler();
48
+ });
49
+ };
50
+
51
+ export { initDropToOpen };
@@ -2,12 +2,17 @@ import "file:///Users/dmail/Documents/dev/jsenv/core/packages/internal/plugin-tr
2
2
 
3
3
  const installImportMetaCss = importMeta => {
4
4
  let cssText = "";
5
- let stylesheet = new CSSStyleSheet();
5
+ let stylesheet = new CSSStyleSheet({
6
+ baseUrl: importMeta.url
7
+ });
6
8
  let adopted = false;
7
9
  const css = {
8
10
  toString: () => cssText,
9
11
  update: value => {
10
12
  cssText = value;
13
+ cssText += `
14
+ /* sourceURL=${importMeta.url} */
15
+ /* inlined from ${importMeta.url} */`;
11
16
  stylesheet.replaceSync(cssText);
12
17
  },
13
18
  inject: () => {
@@ -267,6 +267,31 @@ const UNICODE = createUnicode({
267
267
  ANSI,
268
268
  });
269
269
 
270
+ const prefixFirstAndIndentRemainingLines = (
271
+ text,
272
+ { prefix, indentation, trimLines, trimLastLine },
273
+ ) => {
274
+ const lines = text.split(/\r?\n/);
275
+ const firstLine = lines.shift();
276
+ if (indentation === undefined) {
277
+ {
278
+ indentation = " "; // prefix + space
279
+ }
280
+ }
281
+ let result = `${prefix} ${firstLine}` ;
282
+ let i = 0;
283
+ while (i < lines.length) {
284
+ const line = trimLines ? lines[i].trim() : lines[i];
285
+ i++;
286
+ result += line.length
287
+ ? `\n${indentation}${line}`
288
+ : trimLastLine && i === lines.length
289
+ ? ""
290
+ : `\n`;
291
+ }
292
+ return result;
293
+ };
294
+
270
295
  const setRoundedPrecision = (
271
296
  number,
272
297
  { decimals = 1, decimalsWhenSmall = decimals } = {},
@@ -643,10 +668,9 @@ const formatError = (error) => {
643
668
  const { cause } = error;
644
669
  if (cause) {
645
670
  const formatCause = (cause, depth) => {
646
- let causeText = prefixFirstAndIndentRemainingLines({
671
+ let causeText = prefixFirstAndIndentRemainingLines(cause.stack, {
647
672
  prefix: " [cause]:",
648
673
  indentation: " ".repeat(depth + 1),
649
- text: cause.stack,
650
674
  });
651
675
  const nestedCause = cause.cause;
652
676
  if (nestedCause) {
@@ -661,34 +685,6 @@ const formatError = (error) => {
661
685
  return text;
662
686
  };
663
687
 
664
- const prefixFirstAndIndentRemainingLines = ({
665
- prefix,
666
- indentation,
667
- text,
668
- trimLines,
669
- trimLastLine,
670
- }) => {
671
- const lines = text.split(/\r?\n/);
672
- const firstLine = lines.shift();
673
- if (indentation === undefined) {
674
- {
675
- indentation = " "; // prefix + space
676
- }
677
- }
678
- let result = `${prefix} ${firstLine}` ;
679
- let i = 0;
680
- while (i < lines.length) {
681
- const line = trimLines ? lines[i].trim() : lines[i];
682
- i++;
683
- result += line.length
684
- ? `\n${indentation}${line}`
685
- : trimLastLine && i === lines.length
686
- ? ""
687
- : `\n`;
688
- }
689
- return result;
690
- };
691
-
692
688
  const LOG_LEVEL_OFF = "off";
693
689
 
694
690
  const LOG_LEVEL_DEBUG = "debug";
@@ -1958,15 +1954,24 @@ const lookupPackageDirectory = (currentUrl) => {
1958
1954
  };
1959
1955
 
1960
1956
  const readPackageAtOrNull = (packageDirectoryUrl) => {
1957
+ const packageJsonFileUrl = new URL("./package.json", packageDirectoryUrl);
1958
+ let packageJsonFileContentBuffer;
1959
+ try {
1960
+ packageJsonFileContentBuffer = readFileSync(packageJsonFileUrl, "utf8");
1961
+ } catch (e) {
1962
+ if (e.code === "ENOENT") {
1963
+ return null;
1964
+ }
1965
+ throw e;
1966
+ }
1967
+ const packageJsonFileContentString = String(packageJsonFileContentBuffer);
1961
1968
  try {
1962
- const packageFileContent = readFileSync(
1963
- new URL("./package.json", packageDirectoryUrl),
1964
- "utf8",
1969
+ const packageJsonFileContentObject = JSON.parse(
1970
+ packageJsonFileContentString,
1965
1971
  );
1966
- const packageJSON = JSON.parse(packageFileContent);
1967
- return packageJSON;
1972
+ return packageJsonFileContentObject;
1968
1973
  } catch {
1969
- return null;
1974
+ throw new Error(`Invalid package configuration at ${packageJsonFileUrl}`);
1970
1975
  }
1971
1976
  };
1972
1977
 
@@ -3878,13 +3883,24 @@ const defaultLookupPackageScope = (url) => {
3878
3883
  };
3879
3884
 
3880
3885
  const defaultReadPackageJson = (packageUrl) => {
3881
- const packageJsonUrl = new URL("package.json", packageUrl);
3882
- const buffer = readFileSync(packageJsonUrl);
3883
- const string = String(buffer);
3886
+ const packageJsonFileUrl = new URL("./package.json", packageUrl);
3887
+ let packageJsonFileContentBuffer;
3884
3888
  try {
3885
- return JSON.parse(string);
3889
+ packageJsonFileContentBuffer = readFileSync(packageJsonFileUrl, "utf8");
3890
+ } catch (e) {
3891
+ if (e.code === "ENOENT") {
3892
+ return null;
3893
+ }
3894
+ throw e;
3895
+ }
3896
+ const packageJsonFileContentString = String(packageJsonFileContentBuffer);
3897
+ try {
3898
+ const packageJsonFileContentObject = JSON.parse(
3899
+ packageJsonFileContentString,
3900
+ );
3901
+ return packageJsonFileContentObject;
3886
3902
  } catch {
3887
- throw new Error(`Invalid package configuration`);
3903
+ throw new Error(`Invalid package configuration at ${packageJsonFileUrl}`);
3888
3904
  }
3889
3905
  };
3890
3906
 
@@ -586,6 +586,34 @@ const assertFetchedContentCompliance = ({ urlInfo, content }) => {
586
586
  }
587
587
  };
588
588
 
589
+ const FILE_AND_SERVER_URLS_CONVERTER = {
590
+ asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
591
+ if (urlIsOrIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
592
+ const urlRelativeToServer = urlToRelativeUrl(
593
+ fileUrl,
594
+ serverRootDirectoryUrl,
595
+ );
596
+ return `/${urlRelativeToServer}`;
597
+ }
598
+ const urlRelativeToFilesystemRoot = String(fileUrl).slice(
599
+ "file:///".length,
600
+ );
601
+ return `/@fs/${urlRelativeToFilesystemRoot}`;
602
+ },
603
+ asFileUrl: (urlRelativeToServer, serverRootDirectoryUrl) => {
604
+ if (urlRelativeToServer.startsWith("/@fs/")) {
605
+ const urlRelativeToFilesystemRoot = urlRelativeToServer.slice(
606
+ "/@fs/".length,
607
+ );
608
+ return `file:///${urlRelativeToFilesystemRoot}`;
609
+ }
610
+ if (urlRelativeToServer[0] === "/") {
611
+ return new URL(urlRelativeToServer.slice(1), serverRootDirectoryUrl).href;
612
+ }
613
+ return new URL(urlRelativeToServer, serverRootDirectoryUrl).href;
614
+ },
615
+ };
616
+
589
617
  const determineFileUrlForOutDirectory = (urlInfo) => {
590
618
  let { url, filenameHint } = urlInfo;
591
619
  const { rootDirectoryUrl, outDirectoryUrl } = urlInfo.context;
@@ -2992,6 +3020,10 @@ const createKitchen = ({
2992
3020
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
2993
3021
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
2994
3022
  isPlaceholderInjection,
3023
+ asServerUrl: (fileUrl) =>
3024
+ FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(fileUrl, rootDirectoryUrl),
3025
+ asFileUrl: (serverUrl) =>
3026
+ FILE_AND_SERVER_URLS_CONVERTER.asFileUrl(serverUrl, rootDirectoryUrl),
2995
3027
  INJECTIONS,
2996
3028
  getPluginMeta: null,
2997
3029
  sourcemaps,
@@ -6191,34 +6223,6 @@ const jsenvPluginVersionSearchParam = () => {
6191
6223
  };
6192
6224
  };
6193
6225
 
6194
- const FILE_AND_SERVER_URLS_CONVERTER = {
6195
- asServerUrl: (fileUrl, serverRootDirectoryUrl) => {
6196
- if (urlIsOrIsInsideOf(fileUrl, serverRootDirectoryUrl)) {
6197
- const urlRelativeToServer = urlToRelativeUrl(
6198
- fileUrl,
6199
- serverRootDirectoryUrl,
6200
- );
6201
- return `/${urlRelativeToServer}`;
6202
- }
6203
- const urlRelativeToFilesystemRoot = String(fileUrl).slice(
6204
- "file:///".length,
6205
- );
6206
- return `/@fs/${urlRelativeToFilesystemRoot}`;
6207
- },
6208
- asFileUrl: (urlRelativeToServer, serverRootDirectoryUrl) => {
6209
- if (urlRelativeToServer.startsWith("/@fs/")) {
6210
- const urlRelativeToFilesystemRoot = urlRelativeToServer.slice(
6211
- "/@fs/".length,
6212
- );
6213
- return `file:///${urlRelativeToFilesystemRoot}`;
6214
- }
6215
- if (urlRelativeToServer[0] === "/") {
6216
- return new URL(urlRelativeToServer.slice(1), serverRootDirectoryUrl).href;
6217
- }
6218
- return new URL(urlRelativeToServer, serverRootDirectoryUrl).href;
6219
- },
6220
- };
6221
-
6222
6226
  /*
6223
6227
  * NICE TO HAVE:
6224
6228
  *
@@ -8858,6 +8862,49 @@ const jsenvPluginRibbon = ({
8858
8862
  };
8859
8863
  };
8860
8864
 
8865
+ /**
8866
+ * HTML page server by jsenv dev server will listen for drop events
8867
+ * and redirect the browser to the dropped file location.
8868
+ *
8869
+ * Works only for VSCode right now (because it sets "resourceurls" dataTransfer type).
8870
+ *
8871
+ */
8872
+
8873
+
8874
+ const jsenvPluginDropToOpen = () => {
8875
+ const clientFileUrl = import.meta.resolve("../js/drop_to_open.js");
8876
+ return {
8877
+ name: "jsenv:drop_to_open",
8878
+ appliesDuring: "dev",
8879
+ transformUrlContent: {
8880
+ html: (urlInfo) => {
8881
+ const htmlAst = parseHtml({
8882
+ html: urlInfo.content,
8883
+ url: urlInfo.url,
8884
+ });
8885
+ const clientFileReference = urlInfo.dependencies.inject({
8886
+ type: "script",
8887
+ subtype: "js_module",
8888
+ expectedType: "js_module",
8889
+ specifier: clientFileUrl,
8890
+ });
8891
+ injectJsenvScript(htmlAst, {
8892
+ type: "module",
8893
+ src: clientFileReference.generatedSpecifier,
8894
+ initCall: {
8895
+ callee: "initDropToOpen",
8896
+ params: {
8897
+ rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
8898
+ },
8899
+ },
8900
+ pluginName: "jsenv:drop_to_open",
8901
+ });
8902
+ return stringifyHtmlAst(htmlAst);
8903
+ },
8904
+ },
8905
+ };
8906
+ };
8907
+
8861
8908
  const jsenvPluginCleanHTML = () => {
8862
8909
  return {
8863
8910
  name: "jsenv:cleanup_html_during_dev",
@@ -9238,6 +9285,7 @@ const getCorePlugins = ({
9238
9285
  : []),
9239
9286
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
9240
9287
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
9288
+ jsenvPluginDropToOpen(),
9241
9289
  jsenvPluginCleanHTML(),
9242
9290
  jsenvPluginChromeDevtoolsJson(),
9243
9291
  ...(packageSideEffects
@@ -9405,6 +9453,7 @@ const startDevServer = async ({
9405
9453
  http2 = false,
9406
9454
  logLevel = EXECUTED_BY_TEST_PLAN ? "warn" : "info",
9407
9455
  serverLogLevel = "warn",
9456
+ serverRouterLogLevel = "warn",
9408
9457
  services = [],
9409
9458
 
9410
9459
  signal = new AbortController().signal,
@@ -10011,6 +10060,7 @@ const startDevServer = async ({
10011
10060
  stopOnInternalError: false,
10012
10061
  keepProcessAlive,
10013
10062
  logLevel: serverLogLevel,
10063
+ routerLogLevel: serverRouterLogLevel,
10014
10064
  startLog: false,
10015
10065
 
10016
10066
  https,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "40.8.4",
3
+ "version": "40.9.0",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -81,7 +81,7 @@
81
81
  "@jsenv/plugin-minification": "1.7.0",
82
82
  "@jsenv/plugin-supervisor": "1.7.5",
83
83
  "@jsenv/plugin-transpilation": "1.5.52",
84
- "@jsenv/server": "16.2.1",
84
+ "@jsenv/server": "16.3.0",
85
85
  "@jsenv/sourcemap": "1.3.10",
86
86
  "react-table": "7.8.0"
87
87
  },
@@ -102,12 +102,12 @@
102
102
  "@jsenv/integrity": "workspace:*",
103
103
  "@jsenv/md-up": "workspace:*",
104
104
  "@jsenv/monorepo": "workspace:*",
105
+ "@jsenv/navi": "workspace:*",
105
106
  "@jsenv/node-esm-resolution": "workspace:*",
106
107
  "@jsenv/os-metrics": "workspace:*",
107
108
  "@jsenv/performance-impact": "workspace:*",
108
109
  "@jsenv/plugin-as-js-classic": "workspace:*",
109
110
  "@jsenv/plugin-database-manager": "workspace:*",
110
- "@jsenv/router": "workspace:*",
111
111
  "@jsenv/runtime-compat": "workspace:*",
112
112
  "@jsenv/snapshot": "workspace:*",
113
113
  "@jsenv/terminal-table": "workspace:*",
@@ -124,11 +124,11 @@
124
124
  "playwright": "1.52.0",
125
125
  "preact": "10.26.5",
126
126
  "preact-iso": "2.9.1",
127
- "prettier": "3.5.3",
127
+ "prettier": "3.6.2",
128
128
  "prettier-plugin-embed": "0.5.0",
129
129
  "prettier-plugin-organize-imports": "4.1.0",
130
- "prettier-plugin-packagejson": "2.5.15",
131
- "prettier-plugin-sql": "0.19.0",
130
+ "prettier-plugin-packagejson": "2.5.18",
131
+ "prettier-plugin-sql": "0.19.2",
132
132
  "strip-ansi": "7.1.0"
133
133
  },
134
134
  "packageManager": "npm@11.3.0",
@@ -68,6 +68,7 @@ export const startDevServer = async ({
68
68
  http2 = false,
69
69
  logLevel = EXECUTED_BY_TEST_PLAN ? "warn" : "info",
70
70
  serverLogLevel = "warn",
71
+ serverRouterLogLevel = "warn",
71
72
  services = [],
72
73
 
73
74
  signal = new AbortController().signal,
@@ -676,6 +677,7 @@ export const startDevServer = async ({
676
677
  stopOnInternalError: false,
677
678
  keepProcessAlive,
678
679
  logLevel: serverLogLevel,
680
+ routerLogLevel: serverRouterLogLevel,
679
681
  startLog: false,
680
682
 
681
683
  https,
@@ -12,6 +12,7 @@ import {
12
12
  defineNonEnumerableProperties,
13
13
  } from "./errors.js";
14
14
  import { assertFetchedContentCompliance } from "./fetched_content_compliance.js";
15
+ import { FILE_AND_SERVER_URLS_CONVERTER } from "./file_and_server_urls_converter.js";
15
16
  import {
16
17
  determineFileUrlForOutDirectory,
17
18
  determineSourcemapFileUrl,
@@ -107,6 +108,10 @@ export const createKitchen = ({
107
108
  isSupportedOnCurrentClients: memoizeIsSupported(clientRuntimeCompat),
108
109
  isSupportedOnFutureClients: memoizeIsSupported(runtimeCompat),
109
110
  isPlaceholderInjection,
111
+ asServerUrl: (fileUrl) =>
112
+ FILE_AND_SERVER_URLS_CONVERTER.asServerUrl(fileUrl, rootDirectoryUrl),
113
+ asFileUrl: (serverUrl) =>
114
+ FILE_AND_SERVER_URLS_CONVERTER.asFileUrl(serverUrl, rootDirectoryUrl),
110
115
  INJECTIONS,
111
116
  getPluginMeta: null,
112
117
  sourcemaps,
@@ -0,0 +1,49 @@
1
+ export const initDropToOpen = ({ rootDirectoryUrl }) => {
2
+ const dataTransferCandidates = [
3
+ (dataTransfer) => {
4
+ if (!dataTransfer.types.includes("resourceurls")) {
5
+ return null;
6
+ }
7
+ return () => {
8
+ const data = dataTransfer.getData("resourceurls");
9
+ const urls = JSON.parse(data);
10
+ if (!Array.isArray(urls) || urls.length === 0) {
11
+ return;
12
+ }
13
+ const [url] = urls;
14
+ const fileUrl = new URL(url).href;
15
+ let serverUrl;
16
+
17
+ if (fileUrl.startsWith(rootDirectoryUrl)) {
18
+ const serverRelativeUrl = fileUrl.slice(rootDirectoryUrl.length);
19
+ serverUrl = `/${serverRelativeUrl}`;
20
+ } else {
21
+ serverUrl = `/@fs/${fileUrl}`;
22
+ }
23
+ window.location.href = serverUrl;
24
+ };
25
+ },
26
+ ];
27
+
28
+ document.addEventListener("dragover", (event) => {
29
+ for (const candidate of dataTransferCandidates) {
30
+ const dataTransferHandler = candidate(event.dataTransfer);
31
+ if (dataTransferHandler) {
32
+ event.preventDefault();
33
+ return;
34
+ }
35
+ }
36
+ });
37
+ document.addEventListener("drop", (event) => {
38
+ let handler;
39
+ for (const candidate of dataTransferCandidates) {
40
+ const dataTransferHandler = candidate(event.dataTransfer);
41
+ if (dataTransferHandler) {
42
+ handler = dataTransferHandler;
43
+ break;
44
+ }
45
+ }
46
+ event.preventDefault();
47
+ handler();
48
+ });
49
+ };
@@ -0,0 +1,43 @@
1
+ /**
2
+ * HTML page server by jsenv dev server will listen for drop events
3
+ * and redirect the browser to the dropped file location.
4
+ *
5
+ * Works only for VSCode right now (because it sets "resourceurls" dataTransfer type).
6
+ *
7
+ */
8
+
9
+ import { injectJsenvScript, parseHtml, stringifyHtmlAst } from "@jsenv/ast";
10
+
11
+ export const jsenvPluginDropToOpen = () => {
12
+ const clientFileUrl = import.meta.resolve("./client/drop_to_open.js");
13
+ return {
14
+ name: "jsenv:drop_to_open",
15
+ appliesDuring: "dev",
16
+ transformUrlContent: {
17
+ html: (urlInfo) => {
18
+ const htmlAst = parseHtml({
19
+ html: urlInfo.content,
20
+ url: urlInfo.url,
21
+ });
22
+ const clientFileReference = urlInfo.dependencies.inject({
23
+ type: "script",
24
+ subtype: "js_module",
25
+ expectedType: "js_module",
26
+ specifier: clientFileUrl,
27
+ });
28
+ injectJsenvScript(htmlAst, {
29
+ type: "module",
30
+ src: clientFileReference.generatedSpecifier,
31
+ initCall: {
32
+ callee: "initDropToOpen",
33
+ params: {
34
+ rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
35
+ },
36
+ },
37
+ pluginName: "jsenv:drop_to_open",
38
+ });
39
+ return stringifyHtmlAst(htmlAst);
40
+ },
41
+ },
42
+ };
43
+ };
@@ -1,12 +1,15 @@
1
1
  export const installImportMetaCss = (importMeta) => {
2
2
  let cssText = "";
3
- let stylesheet = new CSSStyleSheet();
3
+ let stylesheet = new CSSStyleSheet({ baseUrl: importMeta.url });
4
4
  let adopted = false;
5
5
 
6
6
  const css = {
7
7
  toString: () => cssText,
8
8
  update: (value) => {
9
9
  cssText = value;
10
+ cssText += `
11
+ /* sourceURL=${importMeta.url} */
12
+ /* inlined from ${importMeta.url} */`;
10
13
  stylesheet.replaceSync(cssText);
11
14
  },
12
15
  inject: () => {
@@ -23,6 +23,7 @@ import { jsenvPluginAutoreload } from "./autoreload/jsenv_plugin_autoreload.js";
23
23
  import { jsenvPluginCacheControl } from "./cache_control/jsenv_plugin_cache_control.js";
24
24
  // other
25
25
  import { jsenvPluginRibbon } from "./ribbon/jsenv_plugin_ribbon.js";
26
+ import { jsenvPluginDropToOpen } from "./drop_to_open/jsenv_plugin_drop_to_open.js";
26
27
  import { jsenvPluginCleanHTML } from "./clean_html/jsenv_plugin_clean_html.js";
27
28
  import { jsenvPluginChromeDevtoolsJson } from "./chrome_devtools_json/jsenv_plugin_chrome_devtools_json.js";
28
29
  import { jsenvPluginAutoreloadOnServerRestart } from "./autoreload_on_server_restart/jsenv_plugin_autoreload_on_server_restart.js";
@@ -146,6 +147,7 @@ export const getCorePlugins = ({
146
147
  : []),
147
148
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
148
149
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
150
+ jsenvPluginDropToOpen(),
149
151
  jsenvPluginCleanHTML(),
150
152
  jsenvPluginChromeDevtoolsJson(),
151
153
  ...(packageSideEffects
@@ -101,9 +101,11 @@ a.nav_item_text {
101
101
  margin: 10px 15px 10px 15px;
102
102
  list-style-type: none;
103
103
  border-radius: 3px;
104
+ display: flex;
105
+ flex-direction: column;
104
106
  }
105
107
  .directory_content_item {
106
- display: flex;
108
+ display: inline-flex;
107
109
  position: relative;
108
110
  padding: 10px 15px 10px 15px;
109
111
  font-size: 15px;
@@ -112,7 +114,9 @@ a.nav_item_text {
112
114
  align-items: center;
113
115
  }
114
116
  .directory_content_item_link {
115
- display: flex;
117
+ display: inline-flex;
118
+ min-width: 0;
119
+ max-width: 100%;
116
120
  flex: 1;
117
121
  gap: 10px;
118
122
  align-items: center;
@@ -123,12 +127,15 @@ a.nav_item_text {
123
127
  aspect-ratio: 1/1;
124
128
  display: flex;
125
129
  color: #f1f1f1;
130
+ flex-shrink: 0;
126
131
  }
127
132
  .directory_content_item_icon img {
128
133
  width: 100%;
129
134
  }
130
135
  .directory_content_item_text {
131
- display: flex;
136
+ display: inline-flex;
137
+ flex-grow: 1;
138
+ align-items: center;
132
139
  }
133
140
  .directory_content_item:last-child {
134
141
  border-bottom: none;
@@ -63,11 +63,13 @@ const ErrorMessage = () => {
63
63
  errorText = (
64
64
  <>
65
65
  <strong>File not found:</strong>&nbsp;
66
- <code>
67
- <span className="file_path_good">{filePathExisting}</span>
68
- <span className="file_path_bad">{filePathNotFound}</span>
69
- </code>{" "}
70
- does not exist on the server.
66
+ <Overflow>
67
+ <code>
68
+ <span className="file_path_good">{filePathExisting}</span>
69
+ <span className="file_path_bad">{filePathNotFound}</span>
70
+ </code>{" "}
71
+ does not exist on the server.
72
+ </Overflow>
71
73
  </>
72
74
  );
73
75
  errorSuggestion = (
@@ -90,6 +92,19 @@ const ErrorMessage = () => {
90
92
  );
91
93
  };
92
94
 
95
+ const Overflow = ({ children, afterContent }) => {
96
+ return (
97
+ <div style="display: flex; flex-wrap: wrap; overflow: hidden; width: 100%; box-sizing: border-box; white-space: nowrap; text-overflow: ellipsis;">
98
+ <div style="display: flex; flex-grow: 1; width: 0;">
99
+ <div style="overflow: hidden; max-width: 100%; text-overflow: ellipsis;">
100
+ {children}
101
+ </div>
102
+ {afterContent}
103
+ </div>
104
+ </div>
105
+ );
106
+ };
107
+
93
108
  const Breadcrumb = ({ items }) => {
94
109
  return (
95
110
  <h1 className="nav">
@@ -170,7 +185,7 @@ const DirectoryContent = ({ items }) => {
170
185
  isDirectory={directoryItem.url.endsWith("/")}
171
186
  isMainFile={directoryItem.isMainFile}
172
187
  >
173
- {directoryItem.urlRelativeToCurrentDirectory}
188
+ {decodeURI(directoryItem.urlRelativeToCurrentDirectory)}
174
189
  </DirectoryContentItem>
175
190
  );
176
191
  })}
@@ -201,15 +216,17 @@ const DirectoryContentItem = ({ url, isDirectory, isMainFile, children }) => {
201
216
  }
202
217
  />
203
218
  </span>
204
- {children}
205
- {isDirectory ? (
206
- <>
207
- <span style="flex:1"></span>
208
- <span className="directory_content_item_arrow">
209
- <RightArrowSvg />
210
- </span>
211
- </>
212
- ) : null}
219
+ <span className="directory_content_item_text">
220
+ <Overflow>{children}</Overflow>
221
+ {isDirectory ? (
222
+ <>
223
+ <span style="flex:1"></span>
224
+ <span className="directory_content_item_arrow">
225
+ <RightArrowSvg />
226
+ </span>
227
+ </>
228
+ ) : null}
229
+ </span>
213
230
  </a>
214
231
  </li>
215
232
  );
@@ -37,7 +37,7 @@ import {
37
37
  } from "@jsenv/urls";
38
38
  import { existsSync, lstatSync, readdirSync } from "node:fs";
39
39
  import { getDirectoryWatchPatterns } from "../../helpers/watch_source_files.js";
40
- import { FILE_AND_SERVER_URLS_CONVERTER } from "./file_and_server_urls_converter.js";
40
+ import { FILE_AND_SERVER_URLS_CONVERTER } from "../../kitchen/file_and_server_urls_converter.js";
41
41
 
42
42
  const htmlFileUrlForDirectory = import.meta.resolve(
43
43
  "./client/directory_listing.html",
@@ -2,7 +2,7 @@ import { readEntryStatSync } from "@jsenv/filesystem";
2
2
  import { ensurePathnameTrailingSlash } from "@jsenv/urls";
3
3
  import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
4
4
  import { readFileSync, readdirSync } from "node:fs";
5
- import { FILE_AND_SERVER_URLS_CONVERTER } from "./file_and_server_urls_converter.js";
5
+ import { FILE_AND_SERVER_URLS_CONVERTER } from "../../kitchen/file_and_server_urls_converter.js";
6
6
  import { jsenvPluginDirectoryListing } from "./jsenv_plugin_directory_listing.js";
7
7
  import { jsenvPluginFsRedirection } from "./jsenv_plugin_fs_redirection.js";
8
8