@jsenv/core 38.4.17 → 38.4.18

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.
@@ -1,4 +1,4 @@
1
- import { readdir, chmod, stat, lstat, promises, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, createReadStream, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
1
+ import { readdir, chmod, stat, lstat, promises, writeFileSync as writeFileSync$1, mkdirSync, unlink, openSync, closeSync, rmdir, watch, readdirSync, statSync, createReadStream, lstatSync, readFile, existsSync, readFileSync, realpathSync } from "node:fs";
2
2
  import process$1 from "node:process";
3
3
  import os, { networkInterfaces } from "node:os";
4
4
  import tty from "node:tty";
@@ -142,6 +142,19 @@ const applyMatching = (pattern, string) => {
142
142
  consumeRemainingString();
143
143
  return true;
144
144
  }
145
+ if (remainingPattern.slice(0, 4) === "/**/") {
146
+ consumePattern(3); // consumes "/**/"
147
+ const skipResult = skipUntilMatch({
148
+ pattern: remainingPattern,
149
+ string: remainingString,
150
+ canSkipSlash: true,
151
+ });
152
+ groups.push(...skipResult.groups);
153
+ consumePattern(skipResult.patternIndex);
154
+ consumeRemainingString();
155
+ restoreIndexes = false;
156
+ return skipResult.matched;
157
+ }
145
158
  // pattern leading **
146
159
  if (remainingPattern.slice(0, 2) === "**") {
147
160
  consumePattern(2); // consumes "**"
@@ -3159,6 +3172,14 @@ const readStat = (
3159
3172
  });
3160
3173
  };
3161
3174
 
3175
+ /*
3176
+ * - stats object documentation on Node.js
3177
+ * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
3178
+ */
3179
+
3180
+
3181
+ process.platform === "win32";
3182
+
3162
3183
  const statsToType = (stats) => {
3163
3184
  if (stats.isFile()) return "file";
3164
3185
  if (stats.isDirectory()) return "directory";
@@ -3471,14 +3492,6 @@ const removeDirectoryNaive = (
3471
3492
  });
3472
3493
  };
3473
3494
 
3474
- process.platform === "win32";
3475
-
3476
- /*
3477
- * - stats object documentation on Node.js
3478
- * https://nodejs.org/docs/latest-v13.x/api/fs.html#fs_class_fs_stats
3479
- */
3480
-
3481
-
3482
3495
  process.platform === "win32";
3483
3496
 
3484
3497
  process.platform === "win32";
@@ -7381,10 +7394,14 @@ const serveDirectory = (
7381
7394
  <h1>Content of directory ${url}</h1>
7382
7395
  <ul>
7383
7396
  ${directoryContentArray.map((filename) => {
7384
- const fileUrl = String(new URL(filename, url));
7385
- const fileUrlRelativeToServer = fileUrl.slice(
7397
+ const fileUrlObject = new URL(filename, url);
7398
+ const fileUrl = String(fileUrlObject);
7399
+ let fileUrlRelativeToServer = fileUrl.slice(
7386
7400
  String(rootDirectoryUrl).length,
7387
7401
  );
7402
+ if (lstatSync(fileUrlObject).isDirectory()) {
7403
+ fileUrlRelativeToServer += "/";
7404
+ }
7388
7405
  return `<li>
7389
7406
  <a href="/${fileUrlRelativeToServer}">${fileUrlRelativeToServer}</a>
7390
7407
  </li>`;
@@ -15783,6 +15800,11 @@ const applyDefaultExtension = ({ url, importer, defaultExtension }) => {
15783
15800
  return url
15784
15801
  };
15785
15802
 
15803
+ const htmlSyntaxErrorFileUrl = new URL(
15804
+ "./html/html_syntax_error.html",
15805
+ import.meta.url,
15806
+ );
15807
+
15786
15808
  const jsenvPluginHtmlReferenceAnalysis = ({
15787
15809
  inlineContent,
15788
15810
  inlineConvertedScript,
@@ -15890,14 +15912,43 @@ const jsenvPluginHtmlReferenceAnalysis = ({
15890
15912
  },
15891
15913
  html: async (urlInfo) => {
15892
15914
  let importmapFound = false;
15893
- const importmapLoaded = startLoadingImportmap(urlInfo);
15894
15915
 
15916
+ let htmlAst;
15895
15917
  try {
15896
- const htmlAst = parseHtml({
15918
+ htmlAst = parseHtml({
15897
15919
  html: urlInfo.content,
15898
15920
  url: urlInfo.url,
15899
15921
  });
15922
+ } catch (e) {
15923
+ if (e.code === "PARSE_ERROR") {
15924
+ const line = e.line;
15925
+ const column = e.column;
15926
+ const htmlErrorContentFrame = generateContentFrame({
15927
+ content: urlInfo.content,
15928
+ line,
15929
+ column,
15930
+ });
15931
+ console.error(`Error while handling ${urlInfo.context.request ? urlInfo.context.request.url : urlInfo.url}:
15932
+ ${e.reasonCode}
15933
+ ${urlInfo.url}:${line}:${column}
15934
+ ${htmlErrorContentFrame}`);
15935
+ const html = generateHtmlForSyntaxError(e, {
15936
+ htmlUrl: urlInfo.url,
15937
+ rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
15938
+ htmlErrorContentFrame,
15939
+ });
15940
+ htmlAst = parseHtml({
15941
+ html,
15942
+ url: htmlSyntaxErrorFileUrl,
15943
+ });
15944
+ } else {
15945
+ throw e;
15946
+ }
15947
+ }
15948
+
15949
+ const importmapLoaded = startLoadingImportmap(urlInfo);
15900
15950
 
15951
+ try {
15901
15952
  const mutations = [];
15902
15953
  const actions = [];
15903
15954
  const finalizeCallbacks = [];
@@ -16076,7 +16127,7 @@ const jsenvPluginHtmlReferenceAnalysis = ({
16076
16127
  });
16077
16128
  };
16078
16129
 
16079
- visitHtmlNodes(htmlAst, {
16130
+ visitNonIgnoredHtmlNode(htmlAst, {
16080
16131
  link: (linkNode) => {
16081
16132
  const rel = getHtmlNodeAttribute(linkNode, "rel");
16082
16133
  const type = getHtmlNodeAttribute(linkNode, "type");
@@ -16332,6 +16383,61 @@ const jsenvPluginHtmlReferenceAnalysis = ({
16332
16383
  };
16333
16384
  };
16334
16385
 
16386
+ const visitNonIgnoredHtmlNode = (htmlAst, visitors) => {
16387
+ const visitorsInstrumented = {};
16388
+ for (const key of Object.keys(visitors)) {
16389
+ visitorsInstrumented[key] = (node) => {
16390
+ const jsenvIgnoreAttribute = getHtmlNodeAttribute(node, "jsenv-ignore");
16391
+ if (jsenvIgnoreAttribute !== undefined) {
16392
+ return;
16393
+ }
16394
+ visitors[key](node);
16395
+ };
16396
+ }
16397
+ visitHtmlNodes(htmlAst, visitorsInstrumented);
16398
+ };
16399
+
16400
+ const generateHtmlForSyntaxError = (
16401
+ htmlSyntaxError,
16402
+ { htmlUrl, rootDirectoryUrl, htmlErrorContentFrame },
16403
+ ) => {
16404
+ const htmlForSyntaxError = String(readFileSync(htmlSyntaxErrorFileUrl));
16405
+ const htmlRelativeUrl = urlToRelativeUrl(htmlUrl, rootDirectoryUrl);
16406
+ const { line, column } = htmlSyntaxError;
16407
+ const urlWithLineAndColumn = `${htmlUrl}:${line}:${column}`;
16408
+ const replacers = {
16409
+ fileRelativeUrl: htmlRelativeUrl,
16410
+ reasonCode: htmlSyntaxError.reasonCode,
16411
+ errorLinkHref: `javascript:window.fetch('/__open_in_editor__/${encodeURIComponent(
16412
+ urlWithLineAndColumn,
16413
+ )}')`,
16414
+ errorLinkText: `${htmlRelativeUrl}:${line}:${column}`,
16415
+ syntaxError: escapeHtml(htmlErrorContentFrame),
16416
+ };
16417
+ const html = replacePlaceholders$2(htmlForSyntaxError, replacers);
16418
+ return html;
16419
+ };
16420
+ const escapeHtml = (string) => {
16421
+ return string
16422
+ .replace(/&/g, "&amp;")
16423
+ .replace(/</g, "&lt;")
16424
+ .replace(/>/g, "&gt;")
16425
+ .replace(/"/g, "&quot;")
16426
+ .replace(/'/g, "&#039;");
16427
+ };
16428
+ const replacePlaceholders$2 = (html, replacers) => {
16429
+ return html.replace(/\${([\w]+)}/g, (match, name) => {
16430
+ const replacer = replacers[name];
16431
+ if (replacer === undefined) {
16432
+ return match;
16433
+ }
16434
+ if (typeof replacer === "function") {
16435
+ return replacer();
16436
+ }
16437
+ return replacer;
16438
+ });
16439
+ };
16440
+
16335
16441
  const crossOriginCompatibleTagNames = ["script", "link", "img", "source"];
16336
16442
  const integrityCompatibleTagNames = ["script", "link", "img", "source"];
16337
16443
  const readFetchMetas = (node) => {
@@ -18061,6 +18167,16 @@ const jsenvPluginVersionSearchParam = () => {
18061
18167
  };
18062
18168
  };
18063
18169
 
18170
+ const html404AndParentDirIsEmptyFileUrl = new URL(
18171
+ "./html/html_404_and_parent_dir_is_empty.html",
18172
+ import.meta.url,
18173
+ );
18174
+ const html404AndParentDirFileUrl = new URL(
18175
+ "./html/html_404_and_parent_dir.html",
18176
+ import.meta.url,
18177
+ );
18178
+ const htmlFileUrlForDirectory = new URL("./html/directory.html", import.meta.url);
18179
+
18064
18180
  const jsenvPluginProtocolFile = ({
18065
18181
  magicExtensions = ["inherit", ".js"],
18066
18182
  magicDirectoryIndex = true,
@@ -18147,7 +18263,9 @@ const jsenvPluginProtocolFile = ({
18147
18263
  reference.leadsToADirectory = stat && stat.isDirectory();
18148
18264
  if (reference.leadsToADirectory) {
18149
18265
  let actionForDirectory;
18150
- if (
18266
+ if (reference.type === "a_href") {
18267
+ actionForDirectory = "ignore";
18268
+ } else if (
18151
18269
  reference.type === "http_request" ||
18152
18270
  reference.type === "filesystem"
18153
18271
  ) {
@@ -18232,17 +18350,33 @@ const jsenvPluginProtocolFile = ({
18232
18350
  urlInfo.filenameHint = `${urlToFilename$1(urlInfo.url)}/`;
18233
18351
  }
18234
18352
  }
18235
- const { headers, body } = serveDirectory(urlObject.href, {
18236
- headers: urlInfo.context.request
18237
- ? urlInfo.context.request.headers
18238
- : {},
18239
- rootDirectoryUrl: urlInfo.context.rootDirectoryUrl,
18240
- });
18353
+ const directoryContentArray = readdirSync(urlObject);
18354
+ if (urlInfo.firstReference.type === "filesystem") {
18355
+ const content = JSON.stringify(directoryContentArray, null, " ");
18356
+ return {
18357
+ type: "directory",
18358
+ contentType: "application/json",
18359
+ content,
18360
+ };
18361
+ }
18362
+ const acceptsHtml = urlInfo.context.request
18363
+ ? pickContentType(urlInfo.context.request, ["text/html"])
18364
+ : false;
18365
+ if (acceptsHtml) {
18366
+ const html = generateHtmlForDirectory(
18367
+ urlObject.href,
18368
+ directoryContentArray,
18369
+ urlInfo.context.rootDirectoryUrl,
18370
+ );
18371
+ return {
18372
+ contentType: "text/html",
18373
+ content: html,
18374
+ };
18375
+ }
18241
18376
  return {
18242
18377
  type: "directory",
18243
- contentType: headers["content-type"],
18244
- contentLength: headers["content-length"],
18245
- content: body,
18378
+ contentType: "application/json",
18379
+ content: JSON.stringify(directoryContentArray, null, " "),
18246
18380
  };
18247
18381
  }
18248
18382
  if (
@@ -18252,20 +18386,144 @@ const jsenvPluginProtocolFile = ({
18252
18386
  urlInfo.dirnameHint =
18253
18387
  urlInfo.firstReference.ownerUrlInfo.filenameHint;
18254
18388
  }
18255
- const fileBuffer = readFileSync(urlObject);
18256
18389
  const contentType = CONTENT_TYPE.fromUrlExtension(urlInfo.url);
18390
+ if (contentType === "text/html") {
18391
+ try {
18392
+ const fileBuffer = readFileSync(urlObject);
18393
+ const content = String(fileBuffer);
18394
+ return {
18395
+ content,
18396
+ contentType,
18397
+ contentLength: fileBuffer.length,
18398
+ };
18399
+ } catch (e) {
18400
+ if (e.code !== "ENOENT") {
18401
+ throw e;
18402
+ }
18403
+ const parentDirectoryUrl = new URL("./", urlInfo.url);
18404
+ if (!existsSync(parentDirectoryUrl)) {
18405
+ throw e;
18406
+ }
18407
+ const parentDirectoryContentArray = readdirSync(
18408
+ new URL(parentDirectoryUrl),
18409
+ );
18410
+ const html = generateHtmlForENOENTOnHtmlFile(
18411
+ urlInfo.url,
18412
+ parentDirectoryContentArray,
18413
+ parentDirectoryUrl,
18414
+ urlInfo.context.rootDirectoryUrl,
18415
+ );
18416
+ return {
18417
+ contentType: "text/html",
18418
+ content: html,
18419
+ };
18420
+ }
18421
+ }
18422
+ const fileBuffer = readFileSync(urlObject);
18257
18423
  const content = CONTENT_TYPE.isTextual(contentType)
18258
18424
  ? String(fileBuffer)
18259
18425
  : fileBuffer;
18260
18426
  return {
18261
18427
  content,
18262
18428
  contentType,
18429
+ contentLength: fileBuffer.length,
18263
18430
  };
18264
18431
  },
18265
18432
  },
18266
18433
  ];
18267
18434
  };
18268
18435
 
18436
+ const generateHtmlForDirectory = (
18437
+ directoryUrl,
18438
+ directoryContentArray,
18439
+ rootDirectoryUrl,
18440
+ ) => {
18441
+ directoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl);
18442
+ const htmlForDirectory = String(readFileSync(htmlFileUrlForDirectory));
18443
+ const replacers = {
18444
+ directoryRelativeUrl: urlToRelativeUrl(directoryUrl, rootDirectoryUrl),
18445
+ directoryUrl,
18446
+ directoryContent: () =>
18447
+ generateDirectoryContent(
18448
+ directoryContentArray,
18449
+ directoryUrl,
18450
+ rootDirectoryUrl,
18451
+ ),
18452
+ };
18453
+ const html = replacePlaceholders$1(htmlForDirectory, replacers);
18454
+ return html;
18455
+ };
18456
+ const generateHtmlForENOENTOnHtmlFile = (
18457
+ url,
18458
+ parentDirectoryContentArray,
18459
+ parentDirectoryUrl,
18460
+ rootDirectoryUrl,
18461
+ ) => {
18462
+ if (parentDirectoryContentArray.length === 0) {
18463
+ const htmlFor404AndParentDirIsEmpty = String(
18464
+ readFileSync(html404AndParentDirIsEmptyFileUrl),
18465
+ );
18466
+ return replacePlaceholders$1(htmlFor404AndParentDirIsEmpty, {
18467
+ fileRelativeUrl: urlToRelativeUrl(url, rootDirectoryUrl),
18468
+ parentDirectoryRelativeUrl: urlToRelativeUrl(
18469
+ parentDirectoryUrl,
18470
+ rootDirectoryUrl,
18471
+ ),
18472
+ });
18473
+ }
18474
+ const htmlFor404AndParentDir = String(
18475
+ readFileSync(html404AndParentDirFileUrl),
18476
+ );
18477
+
18478
+ const replacers = {
18479
+ fileUrl: url,
18480
+ fileRelativeUrl: urlToRelativeUrl(url, rootDirectoryUrl),
18481
+ parentDirectoryUrl,
18482
+ parentDirectoryRelativeUrl: urlToRelativeUrl(
18483
+ parentDirectoryUrl,
18484
+ rootDirectoryUrl,
18485
+ ),
18486
+ parentDirectoryContent: () =>
18487
+ generateDirectoryContent(
18488
+ parentDirectoryContentArray,
18489
+ parentDirectoryUrl,
18490
+ rootDirectoryUrl,
18491
+ ),
18492
+ };
18493
+ const html = replacePlaceholders$1(htmlFor404AndParentDir, replacers);
18494
+ return html;
18495
+ };
18496
+ const generateDirectoryContent = (
18497
+ directoryContentArray,
18498
+ directoryUrl,
18499
+ rootDirectoryUrl,
18500
+ ) => {
18501
+ return directoryContentArray.map((filename) => {
18502
+ const fileUrlObject = new URL(filename, directoryUrl);
18503
+ const fileUrl = String(fileUrlObject);
18504
+ let fileUrlRelative = urlToRelativeUrl(fileUrl, rootDirectoryUrl);
18505
+ if (lstatSync(fileUrlObject).isDirectory()) {
18506
+ fileUrlRelative += "/";
18507
+ }
18508
+ return `<li>
18509
+ <a href="/${fileUrlRelative}">/${fileUrlRelative}</a>
18510
+ </li>`;
18511
+ }).join(`
18512
+ `);
18513
+ };
18514
+ const replacePlaceholders$1 = (html, replacers) => {
18515
+ return html.replace(/\${([\w]+)}/g, (match, name) => {
18516
+ const replacer = replacers[name];
18517
+ if (replacer === undefined) {
18518
+ return match;
18519
+ }
18520
+ if (typeof replacer === "function") {
18521
+ return replacer();
18522
+ }
18523
+ return replacer;
18524
+ });
18525
+ };
18526
+
18269
18527
  const resolveSymlink = (fileUrl) => {
18270
18528
  const urlObject = new URL(fileUrl);
18271
18529
  const realpath = realpathSync(urlObject);
@@ -19434,12 +19692,21 @@ const jsenvPluginAutoreloadClient = () => {
19434
19692
  expectedType: "js_module",
19435
19693
  specifier: autoreloadClientFileUrl,
19436
19694
  });
19695
+ const paramsJson = JSON.stringify(
19696
+ {
19697
+ mainFilePath: `/${htmlUrlInfo.kitchen.context.mainFilePath}`,
19698
+ },
19699
+ null,
19700
+ " ",
19701
+ );
19437
19702
  injectHtmlNodeAsEarlyAsPossible(
19438
19703
  htmlAst,
19439
19704
  createHtmlNode({
19440
19705
  tagName: "script",
19441
19706
  type: "module",
19442
- src: autoreloadClientReference.generatedSpecifier,
19707
+ textContent: `import { initAutoreload } from "${autoreloadClientReference.generatedSpecifier}";
19708
+
19709
+ initAutoreload(${paramsJson});`,
19443
19710
  }),
19444
19711
  "jsenv:autoreload_client",
19445
19712
  );
@@ -19490,7 +19757,10 @@ const jsenvPluginAutoreloadServer = ({
19490
19757
  : `a dependent file accepts hot reload`,
19491
19758
  };
19492
19759
  }
19493
- if (urlInfo.data.hotDecline) {
19760
+ if (
19761
+ urlInfo.data.hotDecline ||
19762
+ urlInfo.firstReference?.type === "http_request"
19763
+ ) {
19494
19764
  return {
19495
19765
  declined: true,
19496
19766
  reason: `file declines hot reload`,
@@ -19884,7 +20154,7 @@ const jsenvPluginRibbon = ({
19884
20154
  createHtmlNode({
19885
20155
  tagName: "script",
19886
20156
  type: "module",
19887
- textContent: `import { injectRibbon } from "${ribbonClientFileReference.generatedSpecifier}"
20157
+ textContent: `import { injectRibbon } from "${ribbonClientFileReference.generatedSpecifier}";
19888
20158
 
19889
20159
  injectRibbon(${paramsJson});`,
19890
20160
  }),
@@ -22335,6 +22605,10 @@ const startDevServer = async ({
22335
22605
  `sourceMainFilePath must be a string, got ${sourceMainFilePath}`,
22336
22606
  );
22337
22607
  }
22608
+ sourceMainFilePath = urlToRelativeUrl(
22609
+ new URL(sourceMainFilePath, sourceDirectoryUrl),
22610
+ sourceDirectoryUrl,
22611
+ );
22338
22612
  if (outDirectoryUrl === undefined) {
22339
22613
  if (!process.env.CI) {
22340
22614
  const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/core",
3
- "version": "38.4.17",
3
+ "version": "38.4.18",
4
4
  "description": "Tool to develop, test and build js projects",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -48,7 +48,6 @@
48
48
  "dev": "node --conditions=development ./scripts/dev/dev.mjs",
49
49
  "test": "node --conditions=development ./scripts/test/test.mjs",
50
50
  "test:workspace": "npm run test --workspaces --if-present -- --workspace",
51
- "test:snapshots_clear": "npx @jsenv/snapshot clear **/tests/**/snapshots/",
52
51
  "test:only_dev_server_errors": "node --conditions=development ./tests/dev_server/errors/dev_errors_snapshots.test.mjs",
53
52
  "build": "node --conditions=development ./scripts/build/build.mjs",
54
53
  "build:file_size": "node ./scripts/build/build_file_size.mjs --log",
@@ -66,20 +65,20 @@
66
65
  "@financial-times/polyfill-useragent-normaliser": "1.10.2",
67
66
  "@jsenv/abort": "4.3.0",
68
67
  "@jsenv/ast": "6.0.7",
69
- "@jsenv/filesystem": "4.7.0",
68
+ "@jsenv/filesystem": "4.7.1",
70
69
  "@jsenv/humanize": "1.1.3",
71
70
  "@jsenv/importmap": "1.2.1",
72
71
  "@jsenv/integrity": "0.0.1",
73
72
  "@jsenv/js-module-fallback": "1.3.16",
74
73
  "@jsenv/node-esm-resolution": "1.0.2",
75
- "@jsenv/plugin-bundling": "2.6.10",
74
+ "@jsenv/plugin-bundling": "2.6.11",
76
75
  "@jsenv/plugin-minification": "1.5.4",
77
76
  "@jsenv/plugin-supervisor": "1.4.11",
78
77
  "@jsenv/plugin-transpilation": "1.3.16",
79
78
  "@jsenv/runtime-compat": "1.3.0",
80
- "@jsenv/server": "15.2.6",
79
+ "@jsenv/server": "15.2.7",
81
80
  "@jsenv/sourcemap": "1.2.10",
82
- "@jsenv/url-meta": "8.4.0",
81
+ "@jsenv/url-meta": "8.4.1",
83
82
  "@jsenv/urls": "2.2.7",
84
83
  "@jsenv/utils": "2.1.1"
85
84
  },
@@ -5,6 +5,7 @@ import {
5
5
  bufferToEtag,
6
6
  } from "@jsenv/filesystem";
7
7
  import { createLogger, createTaskLog } from "@jsenv/humanize";
8
+ import { urlToRelativeUrl } from "@jsenv/urls";
8
9
  import {
9
10
  jsenvAccessControlAllowedHeaders,
10
11
  startServer,
@@ -99,6 +100,10 @@ export const startDevServer = async ({
99
100
  `sourceMainFilePath must be a string, got ${sourceMainFilePath}`,
100
101
  );
101
102
  }
103
+ sourceMainFilePath = urlToRelativeUrl(
104
+ new URL(sourceMainFilePath, sourceDirectoryUrl),
105
+ sourceDirectoryUrl,
106
+ );
102
107
  if (outDirectoryUrl === undefined) {
103
108
  if (!process.env.CI) {
104
109
  const packageDirectoryUrl = lookupPackageDirectory(sourceDirectoryUrl);