@jsenv/core 41.5.34 → 41.5.37

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.
@@ -47,7 +47,11 @@ function envForceColor() {
47
47
  return 1;
48
48
  }
49
49
 
50
- const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
50
+ if (!/^\d+$/v.test(env.FORCE_COLOR)) {
51
+ return;
52
+ }
53
+
54
+ const level = Math.min(Number(env.FORCE_COLOR), 3);
51
55
 
52
56
  if (![0, 1, 2, 3].includes(level)) {
53
57
  return;
@@ -69,6 +73,7 @@ function translateLevel(level) {
69
73
  };
70
74
  }
71
75
 
76
+ // eslint-disable-next-line complexity -- Color support detection necessarily handles multiple environments.
72
77
  function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
73
78
  const noFlagForceColor = envForceColor();
74
79
  if (noFlagForceColor !== undefined) {
@@ -93,6 +98,11 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
93
98
  }
94
99
  }
95
100
 
101
+ // A numeric `FORCE_COLOR` requests an exact level, while `FORCE_COLOR=true` and `FORCE_COLOR=` only enable color and let the level be detected.
102
+ if (forceColor !== undefined && /^\d+$/v.test(env.FORCE_COLOR)) {
103
+ return forceColor;
104
+ }
105
+
96
106
  // Check for Azure DevOps pipelines.
97
107
  // Has to be above the `!streamIsTTY` check.
98
108
  if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
@@ -124,11 +134,11 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
124
134
  }
125
135
 
126
136
  if ('CI' in env) {
127
- if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => key in env)) {
137
+ if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => Object.hasOwn(env, key))) {
128
138
  return 3;
129
139
  }
130
140
 
131
- if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
141
+ if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => Object.hasOwn(env, sign)) || env.CI_NAME === 'codeship') {
132
142
  return 1;
133
143
  }
134
144
 
@@ -136,7 +146,7 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
136
146
  }
137
147
 
138
148
  if ('TEAMCITY_VERSION' in env) {
139
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
149
+ return /^(?:9\.0*[1-9]\d*\.|\d{2,}\.)/v.test(env.TEAMCITY_VERSION) ? 1 : 0;
140
150
  }
141
151
 
142
152
  if (env.COLORTERM === 'truecolor') {
@@ -156,7 +166,7 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
156
166
  }
157
167
 
158
168
  if ('TERM_PROGRAM' in env) {
159
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
169
+ const version = Number((env.TERM_PROGRAM_VERSION || '').split('.', 1)[0]);
160
170
 
161
171
  switch (env.TERM_PROGRAM) {
162
172
  case 'iTerm.app': {
@@ -170,11 +180,11 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
170
180
  }
171
181
  }
172
182
 
173
- if (/-256(color)?$/i.test(env.TERM)) {
183
+ if (/-256(?:color)?$/iv.test(env.TERM)) {
174
184
  return 2;
175
185
  }
176
186
 
177
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
187
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/iv.test(env.TERM)) {
178
188
  return 1;
179
189
  }
180
190
 
@@ -231,7 +241,7 @@ const fullwidthRanges = [12288, 12288, 65281, 65376, 65504, 65510];
231
241
 
232
242
  const wideMinimalCodePoint = 4352;
233
243
  const wideMaximumCodePoint = 262141;
234
- const wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101589, 101631, 101662, 101760, 101874, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110882, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110951, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128728, 128732, 128735, 128747, 128748, 128756, 128764, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129674, 129678, 129734, 129736, 129736, 129741, 129756, 129759, 129770, 129775, 129784, 131072, 196605, 196608, 262141];
244
+ const wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101594, 101631, 101664, 101760, 101874, 101888, 102801, 102816, 102866, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110888, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110952, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127406, 127406, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128729, 128732, 128735, 128747, 128748, 128756, 128764, 128986, 128986, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129734, 129736, 129736, 129740, 129757, 129759, 129771, 129775, 129786, 131072, 196605, 196608, 262141];
235
245
 
236
246
  /**
237
247
  Binary search on a sorted flat array of [start, end] pairs.
@@ -299,7 +309,7 @@ const isAmbiguous = codePoint => {
299
309
  return isInRange(ambiguousRanges, codePoint);
300
310
  };
301
311
 
302
- const isFullWidth = codePoint => {
312
+ const isFullwidth = codePoint => {
303
313
  if (
304
314
  codePoint < fullwidthMinimalCodePoint
305
315
  || codePoint > fullwidthMaximumCodePoint
@@ -338,7 +348,7 @@ function eastAsianWidth(codePoint, {ambiguousAsWide = false} = {}) {
338
348
  validate(codePoint);
339
349
 
340
350
  if (
341
- isFullWidth(codePoint)
351
+ isFullwidth(codePoint)
342
352
  || isWide(codePoint)
343
353
  || (ambiguousAsWide && isAmbiguous(codePoint))
344
354
  ) {
@@ -1461,7 +1461,8 @@ const startSpinner = ({
1461
1461
 
1462
1462
  const createTaskLog = (
1463
1463
  label,
1464
- { disabled = false, animated = true, stopOnWriteFromOutside } = {},
1464
+ // animated defaults to what the spinner decides: only when stdout is a terminal
1465
+ { disabled = false, animated, stopOnWriteFromOutside } = {},
1465
1466
  ) => {
1466
1467
  if (disabled) {
1467
1468
  return {
@@ -1471,7 +1472,7 @@ const createTaskLog = (
1471
1472
  fail: () => {},
1472
1473
  };
1473
1474
  }
1474
- if (animated && process.env.CAPTURING_SIDE_EFFECTS) {
1475
+ if (process.env.CAPTURING_SIDE_EFFECTS) {
1475
1476
  animated = false;
1476
1477
  }
1477
1478
  const startMs = Date.now();
@@ -47,7 +47,11 @@ function envForceColor() {
47
47
  return 1;
48
48
  }
49
49
 
50
- const level = Math.min(Number.parseInt(env.FORCE_COLOR, 10), 3);
50
+ if (!/^\d+$/v.test(env.FORCE_COLOR)) {
51
+ return;
52
+ }
53
+
54
+ const level = Math.min(Number(env.FORCE_COLOR), 3);
51
55
 
52
56
  if (![0, 1, 2, 3].includes(level)) {
53
57
  return;
@@ -69,6 +73,7 @@ function translateLevel(level) {
69
73
  };
70
74
  }
71
75
 
76
+ // eslint-disable-next-line complexity -- Color support detection necessarily handles multiple environments.
72
77
  function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
73
78
  const noFlagForceColor = envForceColor();
74
79
  if (noFlagForceColor !== undefined) {
@@ -93,6 +98,11 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
93
98
  }
94
99
  }
95
100
 
101
+ // A numeric `FORCE_COLOR` requests an exact level, while `FORCE_COLOR=true` and `FORCE_COLOR=` only enable color and let the level be detected.
102
+ if (forceColor !== undefined && /^\d+$/v.test(env.FORCE_COLOR)) {
103
+ return forceColor;
104
+ }
105
+
96
106
  // Check for Azure DevOps pipelines.
97
107
  // Has to be above the `!streamIsTTY` check.
98
108
  if ('TF_BUILD' in env && 'AGENT_NAME' in env) {
@@ -124,11 +134,11 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
124
134
  }
125
135
 
126
136
  if ('CI' in env) {
127
- if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => key in env)) {
137
+ if (['GITHUB_ACTIONS', 'GITEA_ACTIONS', 'CIRCLECI'].some(key => Object.hasOwn(env, key))) {
128
138
  return 3;
129
139
  }
130
140
 
131
- if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => sign in env) || env.CI_NAME === 'codeship') {
141
+ if (['TRAVIS', 'APPVEYOR', 'GITLAB_CI', 'BUILDKITE', 'DRONE'].some(sign => Object.hasOwn(env, sign)) || env.CI_NAME === 'codeship') {
132
142
  return 1;
133
143
  }
134
144
 
@@ -136,7 +146,7 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
136
146
  }
137
147
 
138
148
  if ('TEAMCITY_VERSION' in env) {
139
- return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0;
149
+ return /^(?:9\.0*[1-9]\d*\.|\d{2,}\.)/v.test(env.TEAMCITY_VERSION) ? 1 : 0;
140
150
  }
141
151
 
142
152
  if (env.COLORTERM === 'truecolor') {
@@ -156,7 +166,7 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
156
166
  }
157
167
 
158
168
  if ('TERM_PROGRAM' in env) {
159
- const version = Number.parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10);
169
+ const version = Number((env.TERM_PROGRAM_VERSION || '').split('.', 1)[0]);
160
170
 
161
171
  switch (env.TERM_PROGRAM) {
162
172
  case 'iTerm.app': {
@@ -170,11 +180,11 @@ function _supportsColor(haveStream, {streamIsTTY, sniffFlags = true} = {}) {
170
180
  }
171
181
  }
172
182
 
173
- if (/-256(color)?$/i.test(env.TERM)) {
183
+ if (/-256(?:color)?$/iv.test(env.TERM)) {
174
184
  return 2;
175
185
  }
176
186
 
177
- if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) {
187
+ if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/iv.test(env.TERM)) {
178
188
  return 1;
179
189
  }
180
190
 
@@ -231,7 +241,7 @@ const fullwidthRanges = [12288, 12288, 65281, 65376, 65504, 65510];
231
241
 
232
242
  const wideMinimalCodePoint = 4352;
233
243
  const wideMaximumCodePoint = 262141;
234
- const wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101589, 101631, 101662, 101760, 101874, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110882, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110951, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128728, 128732, 128735, 128747, 128748, 128756, 128764, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129674, 129678, 129734, 129736, 129736, 129741, 129756, 129759, 129770, 129775, 129784, 131072, 196605, 196608, 262141];
244
+ const wideRanges = [4352, 4447, 8986, 8987, 9001, 9002, 9193, 9196, 9200, 9200, 9203, 9203, 9725, 9726, 9748, 9749, 9776, 9783, 9800, 9811, 9855, 9855, 9866, 9871, 9875, 9875, 9889, 9889, 9898, 9899, 9917, 9918, 9924, 9925, 9934, 9934, 9940, 9940, 9962, 9962, 9970, 9971, 9973, 9973, 9978, 9978, 9981, 9981, 9989, 9989, 9994, 9995, 10024, 10024, 10060, 10060, 10062, 10062, 10067, 10069, 10071, 10071, 10133, 10135, 10160, 10160, 10175, 10175, 11035, 11036, 11088, 11088, 11093, 11093, 11904, 11929, 11931, 12019, 12032, 12245, 12272, 12287, 12289, 12350, 12353, 12438, 12441, 12543, 12549, 12591, 12593, 12686, 12688, 12773, 12783, 12830, 12832, 12871, 12880, 42124, 42128, 42182, 43360, 43388, 44032, 55203, 63744, 64255, 65040, 65049, 65072, 65106, 65108, 65126, 65128, 65131, 94176, 94180, 94192, 94198, 94208, 101594, 101631, 101664, 101760, 101874, 101888, 102801, 102816, 102866, 110576, 110579, 110581, 110587, 110589, 110590, 110592, 110888, 110898, 110898, 110928, 110930, 110933, 110933, 110948, 110952, 110960, 111355, 119552, 119638, 119648, 119670, 126980, 126980, 127183, 127183, 127374, 127374, 127377, 127386, 127406, 127406, 127488, 127490, 127504, 127547, 127552, 127560, 127568, 127569, 127584, 127589, 127744, 127776, 127789, 127797, 127799, 127868, 127870, 127891, 127904, 127946, 127951, 127955, 127968, 127984, 127988, 127988, 127992, 128062, 128064, 128064, 128066, 128252, 128255, 128317, 128331, 128334, 128336, 128359, 128378, 128378, 128405, 128406, 128420, 128420, 128507, 128591, 128640, 128709, 128716, 128716, 128720, 128722, 128725, 128729, 128732, 128735, 128747, 128748, 128756, 128764, 128986, 128986, 128992, 129003, 129008, 129008, 129292, 129338, 129340, 129349, 129351, 129535, 129648, 129660, 129664, 129734, 129736, 129736, 129740, 129757, 129759, 129771, 129775, 129786, 131072, 196605, 196608, 262141];
235
245
 
236
246
  /**
237
247
  Binary search on a sorted flat array of [start, end] pairs.
@@ -299,7 +309,7 @@ const isAmbiguous = codePoint => {
299
309
  return isInRange(ambiguousRanges, codePoint);
300
310
  };
301
311
 
302
- const isFullWidth = codePoint => {
312
+ const isFullwidth = codePoint => {
303
313
  if (
304
314
  codePoint < fullwidthMinimalCodePoint
305
315
  || codePoint > fullwidthMaximumCodePoint
@@ -338,7 +348,7 @@ function eastAsianWidth(codePoint, {ambiguousAsWide = false} = {}) {
338
348
  validate(codePoint);
339
349
 
340
350
  if (
341
- isFullWidth(codePoint)
351
+ isFullwidth(codePoint)
342
352
  || isWide(codePoint)
343
353
  || (ambiguousAsWide && isAmbiguous(codePoint))
344
354
  ) {
@@ -1264,7 +1264,8 @@ const startSpinner = ({
1264
1264
 
1265
1265
  const createTaskLog = (
1266
1266
  label,
1267
- { disabled = false, animated = true, stopOnWriteFromOutside } = {},
1267
+ // animated defaults to what the spinner decides: only when stdout is a terminal
1268
+ { disabled = false, animated, stopOnWriteFromOutside } = {},
1268
1269
  ) => {
1269
1270
  if (disabled) {
1270
1271
  return {
@@ -1274,7 +1275,7 @@ const createTaskLog = (
1274
1275
  fail: () => {},
1275
1276
  };
1276
1277
  }
1277
- if (animated && process.env.CAPTURING_SIDE_EFFECTS) {
1278
+ if (process.env.CAPTURING_SIDE_EFFECTS) {
1278
1279
  animated = false;
1279
1280
  }
1280
1281
  const startMs = Date.now();
@@ -1,4 +1,5 @@
1
1
  import { WebSocketResponse, pickContentType, ServerEvents, serverPluginErrorHandler, fetchDirectory, composeTwoResponses, serverPluginCORS, jsenvAccessControlAllowedHeaders, startServer } from "@jsenv/server";
2
+ import { randomUUID, X509Certificate } from "node:crypto";
2
3
  import { existsSync, statSync, readFileSync, realpathSync, readdirSync, lstatSync } from "node:fs";
3
4
  import { pathToFileURL } from "node:url";
4
5
  import { urlToRelativeUrl, registerFileLifecycle, lookupPackageDirectory, readPackageAtOrNull, generateContentFrame, errorToHTML, URL_META, asUrlWithoutSearch, DATA_URL, CONTENT_TYPE, normalizeImportMap, composeTwoImportMaps, resolveImport, createDetailedMessage, UNICODE, JS_QUOTES, urlToExtension, urlToBasename, applyNodeEsmResolution, readCustomConditionsFromProcessArgs, urlIsOrIsInsideOf, collectFiles, registerDirectoryLifecycle, readEntryStatSync, applyFileSystemMagicResolution, getExtensionsToTry, urlToFilename, ensurePathnameTrailingSlash, compareFileUrls, setUrlExtension, stringifyUrlSite, injectQueryParamsIntoSpecifier, isSpecifierForNodeBuiltin, injectQueryParams, urlToFileSystemPath, writeFileSync, moveUrl, ensureWindowsDriveLetter, validateResponseIntegrity, setUrlFilename, getCallerPosition, asSpecifierWithoutSearch, bufferToEtag, isFileSystemPath, urlToPathname, setUrlBasename, createLogger, normalizeUrl, ANSI, RUNTIME_COMPAT, formatError, assertAndNormalizeDirectoryUrl, browserDefaultRuntimeCompat, inferRuntimeCompatFromClosestPackage, createTaskLog } from "./jsenv_core_packages.js";
@@ -8,7 +9,6 @@ import { createMagicSource, composeTwoSourcemaps, generateSourcemapFileUrl, gene
8
9
  import { jsenvPluginSupervisor } from "@jsenv/plugin-supervisor";
9
10
  import { jsenvPluginTranspilation } from "@jsenv/plugin-transpilation";
10
11
  import { bundleJsModules } from "@jsenv/plugin-bundling";
11
- import { randomUUID } from "node:crypto";
12
12
  import { convertFileSystemErrorToResponseProperties } from "@jsenv/server/src/plugins/filesystem/filesystem_error_to_response.js";
13
13
  import "./jsenv_core_node_modules.js";
14
14
  import "node:process";
@@ -986,11 +986,11 @@ const clientReporterFileUrl = new URL(
986
986
  import.meta.url,
987
987
  ).href;
988
988
  const clientsPageFileUrl = new URL(
989
- "../html/clients_page.html",
989
+ "../client/client_monitoring/clients_page.html",
990
990
  import.meta.url,
991
991
  ).href;
992
992
  const clientMonitorPageFileUrl = new URL(
993
- "../html/client_monitor_page.html",
993
+ "../client/client_monitoring/client_monitor_page.html",
994
994
  import.meta.url,
995
995
  ).href;
996
996
 
@@ -8602,6 +8602,99 @@ const devServerPluginChromeDevToolsJson = ({ sourceDirectoryUrl }) => {
8602
8602
  };
8603
8603
  };
8604
8604
 
8605
+ /*
8606
+ * Lets another device (a phone) trust the root certificate that signs the dev
8607
+ * server certificate, so it opens the dev server over https without a warning.
8608
+ *
8609
+ * Everything under HTTPS_TRUST_PATHNAME is served over plain http too (see the
8610
+ * redirectHttpToHttps passed by startDevServer): it is the one page that must
8611
+ * load before the certificate is trusted, over https it would show the very
8612
+ * warning it exists to remove. Only the certificate is served, never its key.
8613
+ *
8614
+ * The page is sent as written, not cooked like the project pages: it needs
8615
+ * nothing cooking injects, and a project plugin rewriting html must not be able
8616
+ * to break the one page reached before trust.
8617
+ *
8618
+ * What it means for the device, and why the fingerprint must be compared with
8619
+ * the terminal: docs/users/b_dev/b_dev.md, "Trusting it on a phone".
8620
+ */
8621
+
8622
+
8623
+ const HTTPS_TRUST_PATHNAME = "/.internal/https/";
8624
+
8625
+ const httpsTrustPageFileUrl = new URL(
8626
+ "../client/https_trust/https_trust.html",
8627
+ import.meta.url,
8628
+ );
8629
+
8630
+ // rootCertificate is a node:crypto X509Certificate
8631
+ const devServerPluginHttpsTrust = ({ rootCertificate }) => {
8632
+ const commonName = readCommonName(rootCertificate.subject);
8633
+ const rootCertificateInfo = {
8634
+ fingerprint256: rootCertificate.fingerprint256,
8635
+ commonName,
8636
+ };
8637
+ // "https local root certificate" -> "https_local_root_certificate.crt"
8638
+ const rootCertificateFilename = `${commonName.replace(/[^\w.-]+/g, "_")}.crt`;
8639
+
8640
+ return {
8641
+ name: "jsenv:https_trust",
8642
+ routes: [
8643
+ {
8644
+ endpoint: `GET ${HTTPS_TRUST_PATHNAME}`,
8645
+ description:
8646
+ "Page to trust the dev server https on another device (a phone). Served over http.",
8647
+ declarationSource: import.meta.url,
8648
+ fetch: () => {
8649
+ return new Response(readFileSync(httpsTrustPageFileUrl), {
8650
+ headers: { "content-type": "text/html" },
8651
+ });
8652
+ },
8653
+ },
8654
+ {
8655
+ endpoint: `GET ${HTTPS_TRUST_PATHNAME}root.crt`,
8656
+ description: "The root certificate signing the dev server certificate.",
8657
+ declarationSource: import.meta.url,
8658
+ fetch: () => {
8659
+ return new Response(rootCertificate.toString(), {
8660
+ headers: {
8661
+ "content-type": "application/x-x509-ca-cert",
8662
+ "content-disposition": `attachment; filename="${rootCertificateFilename}"`,
8663
+ },
8664
+ });
8665
+ },
8666
+ },
8667
+ {
8668
+ endpoint: `GET ${HTTPS_TRUST_PATHNAME}root.json`,
8669
+ description: "Fingerprint and name of the root certificate.",
8670
+ declarationSource: import.meta.url,
8671
+ fetch: () => {
8672
+ return Response.json(rootCertificateInfo);
8673
+ },
8674
+ },
8675
+ {
8676
+ endpoint: `GET ${HTTPS_TRUST_PATHNAME}ping`,
8677
+ description:
8678
+ "Fetched over https by the page: it fails as long as the device does not trust the certificate.",
8679
+ declarationSource: import.meta.url,
8680
+ fetch: () => {
8681
+ return new Response(null, { status: 204 });
8682
+ },
8683
+ },
8684
+ ],
8685
+ };
8686
+ };
8687
+
8688
+ // "CN=https local root certificate\nO=..." -> "https local root certificate"
8689
+ const readCommonName = (subject) => {
8690
+ for (const line of subject.split("\n")) {
8691
+ if (line.startsWith("CN=")) {
8692
+ return line.slice("CN=".length);
8693
+ }
8694
+ }
8695
+ return subject;
8696
+ };
8697
+
8605
8698
  const devServerPluginInjectServerResponseHeader = ({
8606
8699
  sourceDirectoryUrl,
8607
8700
  }) => {
@@ -12527,7 +12620,7 @@ const EXECUTED_BY_TEST_PLAN = process.argv.includes("--jsenv-test");
12527
12620
  * @param {number} [params.port=3456] - Port to listen on (0 = a free port).
12528
12621
  * @param {string} [params.hostname] - Hostname to bind to.
12529
12622
  * @param {boolean} [params.acceptAnyIp=false] - Also accept connections on the machine's IPs (so other devices on the network — a phone — can reach the server). Off by default: exposing the dev server beyond localhost is an explicit choice, not something a dev tool decides.
12530
- * @param {boolean|object} [params.https=false] - HTTPS as `{ certificate, privateKey }`.
12623
+ * @param {object} [params.https] - HTTPS as `{ certificate, privateKey }` (PEM strings). With `rootCertificate` (the PEM of the authority that signed `certificate`) and `acceptAnyIp`, the dev server also serves `http://<ip>:<port>/.internal/https/`, a page to trust that authority on a phone (see docs/users/b_dev/b_dev.md, "Trusting it on a phone").
12531
12624
  * @param {boolean} [params.http2=false] - HTTP/2 (requires https).
12532
12625
  * @param {Array} [params.plugins=[]] - jsenv plugins (transformUrlContent, serverRoutes, serverEvents, effect, …).
12533
12626
  * @param {Array} [params.serverPlugins=[]] - `@jsenv/server`-level plugins.
@@ -12659,6 +12752,7 @@ const startDevServer = async ({
12659
12752
  );
12660
12753
  }
12661
12754
  }
12755
+ let httpsTrust = null;
12662
12756
  // params normalization
12663
12757
  {
12664
12758
  if (runtimeCompat === undefined) {
@@ -12673,6 +12767,16 @@ const startDevServer = async ({
12673
12767
  if (clientAutoreload === false) {
12674
12768
  clientAutoreload = { enabled: false };
12675
12769
  }
12770
+ if (https) {
12771
+ const { certificate, privateKey, rootCertificate } = https;
12772
+ https = { certificate, privateKey };
12773
+ // a localhost-only server has no phone to serve
12774
+ if (rootCertificate && acceptAnyIp) {
12775
+ httpsTrust = {
12776
+ rootCertificate: new X509Certificate(rootCertificate),
12777
+ };
12778
+ }
12779
+ }
12676
12780
  }
12677
12781
 
12678
12782
  const logger = createLogger({ logLevel });
@@ -12830,6 +12934,7 @@ const startDevServer = async ({
12830
12934
  }),
12831
12935
  // chrome devtools
12832
12936
  devServerPluginChromeDevToolsJson({ sourceDirectoryUrl }),
12937
+ ...(httpsTrust ? [devServerPluginHttpsTrust(httpsTrust)] : []),
12833
12938
  ...serverPlugins,
12834
12939
  devServerPluginServeSourceFiles({
12835
12940
  packageDirectory,
@@ -12868,6 +12973,12 @@ const startDevServer = async ({
12868
12973
  startLog: false,
12869
12974
 
12870
12975
  https,
12976
+ ...(httpsTrust
12977
+ ? {
12978
+ redirectHttpToHttps: ({ resource }) =>
12979
+ !resource.startsWith(HTTPS_TRUST_PATHNAME),
12980
+ }
12981
+ : {}),
12871
12982
  http2,
12872
12983
  acceptAnyIp,
12873
12984
  hostname,
@@ -12893,6 +13004,17 @@ const startDevServer = async ({
12893
13004
  logger.info(``);
12894
13005
  Object.keys(server.origins).forEach((key) => {
12895
13006
  logger.info(`- ${server.origins[key]}`);
13007
+ if (httpsTrust && key === "externalip") {
13008
+ const externalHostname = new URL(server.origins.externalip).hostname;
13009
+ const httpsTrustPageUrl = new URL(
13010
+ HTTPS_TRUST_PATHNAME,
13011
+ `http://${externalHostname}:${server.port}`,
13012
+ );
13013
+ logger.info(` trust it on a phone: ${httpsTrustPageUrl}`);
13014
+ logger.info(
13015
+ ` root certificate SHA-256: ${httpsTrust.rootCertificate.fingerprint256}`,
13016
+ );
13017
+ }
12896
13018
  });
12897
13019
  logger.info(``);
12898
13020
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "41.5.34",
3
+ "version": "41.5.37",
4
4
  "type": "module",
5
5
  "description": "Tool to develop, test and build js projects",
6
6
  "repository": {
@@ -57,9 +57,10 @@
57
57
  "https:setup": "npx @jsenv/https-local setup",
58
58
  "md:build": "node ./docs/build.js",
59
59
  "monorepo:node_modules_clear": "npx @jsenv/filesystem clear **/node_modules/",
60
- "monorepo:publish": "node ./scripts/monorepo/publish_packages.mjs",
60
+ "monorepo:publish": "node ./scripts/monorepo/prepare_publish.mjs",
61
61
  "monorepo:sync_versions": "node ./scripts/monorepo/sync_packages_versions.mjs",
62
62
  "monorepo:upgrade_versions": "node ./scripts/monorepo/upgrade_external_versions.mjs",
63
+ "monorepo:wait_publish": "node ./scripts/monorepo/wait_publish.mjs",
63
64
  "oto:start": "npm run start -w oto",
64
65
  "performances": "node --expose-gc ./scripts/performance/generate_performance_report.mjs --log --once",
65
66
  "playwright:install": "npx playwright install-deps && npx playwright install",
@@ -73,14 +74,14 @@
73
74
  "test:snapshot_clear": "npx @jsenv/filesystem clear **/tests/**/side_effects/"
74
75
  },
75
76
  "dependencies": {
76
- "@jsenv/ast": "6.10.9",
77
- "@jsenv/js-module-fallback": "1.6.13",
78
- "@jsenv/plugin-bundling": "2.10.31",
79
- "@jsenv/plugin-minification": "1.7.26",
80
- "@jsenv/plugin-supervisor": "1.8.28",
81
- "@jsenv/plugin-transpilation": "1.7.13",
82
- "@jsenv/server": "17.6.8",
83
- "@jsenv/sourcemap": "1.4.12",
77
+ "@jsenv/ast": "6.10.10",
78
+ "@jsenv/js-module-fallback": "1.6.14",
79
+ "@jsenv/plugin-bundling": "2.10.32",
80
+ "@jsenv/plugin-minification": "1.7.27",
81
+ "@jsenv/plugin-supervisor": "1.8.29",
82
+ "@jsenv/plugin-transpilation": "1.7.14",
83
+ "@jsenv/server": "17.6.9",
84
+ "@jsenv/sourcemap": "1.4.13",
84
85
  "react-table": "7.8.0"
85
86
  },
86
87
  "devDependencies": {