@jsenv/core 31.1.4 → 32.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.js CHANGED
@@ -1,8 +1,7 @@
1
- import { workerData, Worker, parentPort } from "node:worker_threads";
2
1
  import { pathToFileURL, fileURLToPath } from "node:url";
3
2
  import { chmod, stat, lstat, readdir, promises, unlink, openSync, closeSync, rmdir, readFile as readFile$1, readFileSync as readFileSync$1, watch, readdirSync, statSync, writeFile as writeFile$1, writeFileSync as writeFileSync$1, mkdirSync, createReadStream, existsSync, realpathSync } from "node:fs";
4
3
  import crypto, { createHash } from "node:crypto";
5
- import { dirname, extname } from "node:path";
4
+ import { dirname, extname, basename } from "node:path";
6
5
  import { URL_META, filterV8Coverage } from "./js/v8_coverage.js";
7
6
  import process$1, { memoryUsage } from "node:process";
8
7
  import os, { networkInterfaces } from "node:os";
@@ -26,6 +25,7 @@ import stripAnsi from "strip-ansi";
26
25
  import { createId } from "@paralleldrive/cuid2";
27
26
  import { runInNewContext } from "node:vm";
28
27
  import { fork } from "node:child_process";
28
+ import { Worker } from "node:worker_threads";
29
29
 
30
30
  /*
31
31
  * data:[<mediatype>][;base64],<data>
@@ -454,11 +454,6 @@ const resolveUrl$1 = (specifier, baseUrl) => {
454
454
  return String(new URL(specifier, baseUrl));
455
455
  };
456
456
 
457
- const resolveDirectoryUrl = (specifier, baseUrl) => {
458
- const url = resolveUrl$1(specifier, baseUrl);
459
- return ensurePathnameTrailingSlash(url);
460
- };
461
-
462
457
  const getCommonPathname = (pathname, otherPathname) => {
463
458
  if (pathname === otherPathname) {
464
459
  return pathname;
@@ -629,14 +624,14 @@ const validateDirectoryUrl = value => {
629
624
  value: ensurePathnameTrailingSlash(urlString)
630
625
  };
631
626
  };
632
- const assertAndNormalizeDirectoryUrl = directoryUrl => {
627
+ const assertAndNormalizeDirectoryUrl = (directoryUrl, name = "directoryUrl") => {
633
628
  const {
634
629
  valid,
635
630
  message,
636
631
  value
637
632
  } = validateDirectoryUrl(directoryUrl);
638
633
  if (!valid) {
639
- throw new TypeError(`directoryUrl ${message}, got ${value}`);
634
+ throw new TypeError(`${name} ${message}, got ${value}`);
640
635
  }
641
636
  return value;
642
637
  };
@@ -678,14 +673,14 @@ const validateFileUrl = (value, baseUrl) => {
678
673
  value: urlString
679
674
  };
680
675
  };
681
- const assertAndNormalizeFileUrl = (fileUrl, baseUrl) => {
676
+ const assertAndNormalizeFileUrl = (fileUrl, baseUrl, name = "fileUrl") => {
682
677
  const {
683
678
  valid,
684
679
  message,
685
680
  value
686
681
  } = validateFileUrl(fileUrl, baseUrl);
687
682
  if (!valid) {
688
- throw new TypeError(`fileUrl ${message}, got ${fileUrl}`);
683
+ throw new TypeError(`${name} ${message}, got ${fileUrl}`);
689
684
  }
690
685
  return value;
691
686
  };
@@ -7063,6 +7058,108 @@ const createServerEventsDispatcher = () => {
7063
7058
  };
7064
7059
  };
7065
7060
 
7061
+ const lookupPackageDirectory = currentUrl => {
7062
+ if (currentUrl === "file:///") {
7063
+ return null;
7064
+ }
7065
+ const packageJsonFileUrl = `${currentUrl}package.json`;
7066
+ if (existsSync(new URL(packageJsonFileUrl))) {
7067
+ return currentUrl;
7068
+ }
7069
+ return lookupPackageDirectory(getParentUrl$1(currentUrl));
7070
+ };
7071
+ const getParentUrl$1 = url => {
7072
+ if (url.startsWith("file://")) {
7073
+ // With node.js new URL('../', 'file:///C:/').href
7074
+ // returns "file:///C:/" instead of "file:///"
7075
+ const resource = url.slice("file://".length);
7076
+ const slashLastIndex = resource.lastIndexOf("/");
7077
+ if (slashLastIndex === -1) {
7078
+ return url;
7079
+ }
7080
+ const lastCharIndex = resource.length - 1;
7081
+ if (slashLastIndex === lastCharIndex) {
7082
+ const slashBeforeLastIndex = resource.lastIndexOf("/", slashLastIndex - 1);
7083
+ if (slashBeforeLastIndex === -1) {
7084
+ return url;
7085
+ }
7086
+ return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`;
7087
+ }
7088
+ return `file://${resource.slice(0, slashLastIndex + 1)}`;
7089
+ }
7090
+ return new URL(url.endsWith("/") ? "../" : "./", url).href;
7091
+ };
7092
+
7093
+ const determineJsenvInternalDirectoryUrl = currentUrl => {
7094
+ const packageDirectoryUrl = lookupPackageDirectory(currentUrl);
7095
+ if (packageDirectoryUrl) {
7096
+ return `${packageDirectoryUrl}.jsenv/${getDirectoryName(packageDirectoryUrl)}/`;
7097
+ }
7098
+ return `${currentUrl}.jsenv/`;
7099
+ };
7100
+ const getDirectoryName = directoryUrl => {
7101
+ const {
7102
+ pathname
7103
+ } = new URL(directoryUrl);
7104
+ return basename(pathname);
7105
+ };
7106
+
7107
+ const watchSourceFiles = (sourceDirectoryUrl, callback, {
7108
+ sourceFileConfig = {},
7109
+ keepProcessAlive,
7110
+ cooldownBetweenFileEvents
7111
+ }) => {
7112
+ // Project should use a dedicated directory (usually "src/")
7113
+ // passed to the dev server via "sourceDirectoryUrl" param
7114
+ // In that case all files inside the source directory should be watched
7115
+ // But some project might want to use their root directory as source directory
7116
+ // In that case source directory might contain files matching "node_modules/*" or ".git/*"
7117
+ // And jsenv should not consider these as source files and watch them (to not hurt performances)
7118
+ const watchPatterns = {
7119
+ "**/*": true,
7120
+ // by default watch everything inside the source directory
7121
+ "**/.*": false,
7122
+ // file starting with a dot -> do not watch
7123
+ "**/.*/": false,
7124
+ // directory starting with a dot -> do not watch
7125
+ "**/node_modules/": false,
7126
+ // node_modules directory -> do not watch
7127
+ ...sourceFileConfig
7128
+ };
7129
+ const stopWatchingSourceFiles = registerDirectoryLifecycle(sourceDirectoryUrl, {
7130
+ watchPatterns,
7131
+ cooldownBetweenFileEvents,
7132
+ keepProcessAlive,
7133
+ recursive: true,
7134
+ added: ({
7135
+ relativeUrl
7136
+ }) => {
7137
+ callback({
7138
+ url: new URL(relativeUrl, sourceDirectoryUrl).href,
7139
+ event: "added"
7140
+ });
7141
+ },
7142
+ updated: ({
7143
+ relativeUrl
7144
+ }) => {
7145
+ callback({
7146
+ url: new URL(relativeUrl, sourceDirectoryUrl).href,
7147
+ event: "modified"
7148
+ });
7149
+ },
7150
+ removed: ({
7151
+ relativeUrl
7152
+ }) => {
7153
+ callback({
7154
+ url: new URL(relativeUrl, sourceDirectoryUrl).href,
7155
+ event: "removed"
7156
+ });
7157
+ }
7158
+ });
7159
+ stopWatchingSourceFiles.watchPatterns = watchPatterns;
7160
+ return stopWatchingSourceFiles;
7161
+ };
7162
+
7066
7163
  const urlSpecifierEncoding = {
7067
7164
  encode: reference => {
7068
7165
  const {
@@ -8653,6 +8750,7 @@ const createKitchen = ({
8653
8750
  signal,
8654
8751
  logLevel,
8655
8752
  rootDirectoryUrl,
8753
+ jsenvInternalDirectoryUrl,
8656
8754
  urlGraph,
8657
8755
  dev = false,
8658
8756
  build = false,
@@ -8678,6 +8776,7 @@ const createKitchen = ({
8678
8776
  signal,
8679
8777
  logger,
8680
8778
  rootDirectoryUrl,
8779
+ jsenvInternalDirectoryUrl,
8681
8780
  urlGraph,
8682
8781
  dev,
8683
8782
  build,
@@ -10175,6 +10274,17 @@ const parseAndTransformWebmanifestUrls = async (urlInfo, context) => {
10175
10274
  const content = urlInfo.content;
10176
10275
  const manifest = JSON.parse(content);
10177
10276
  const actions = [];
10277
+ const {
10278
+ start_url
10279
+ } = manifest;
10280
+ if (start_url) {
10281
+ if (context.build) {
10282
+ manifest.start_url = "/";
10283
+ } else {
10284
+ const parentUrl = context.reference.parentUrl;
10285
+ manifest.start_url = `${parentUrl.slice(context.rootDirectoryUrl.length)}`;
10286
+ }
10287
+ }
10178
10288
  const {
10179
10289
  icons = []
10180
10290
  } = manifest;
@@ -17052,7 +17162,7 @@ const addRelationshipWithPackageJson = ({
17052
17162
  */
17053
17163
  const jsenvPluginUrlResolution = ({
17054
17164
  runtimeCompat,
17055
- clientMainFileUrl,
17165
+ mainFileUrl,
17056
17166
  urlResolution
17057
17167
  }) => {
17058
17168
  const resolveUrlUsingWebResolution = reference => {
@@ -17113,7 +17223,7 @@ const jsenvPluginUrlResolution = ({
17113
17223
  appliesDuring: "*",
17114
17224
  resolveUrl: (reference, context) => {
17115
17225
  if (reference.specifier === "/") {
17116
- return String(clientMainFileUrl);
17226
+ return String(mainFileUrl);
17117
17227
  }
17118
17228
  if (reference.specifier[0] === "/") {
17119
17229
  return new URL(reference.specifier.slice(1), context.rootDirectoryUrl).href;
@@ -20181,13 +20291,14 @@ const explorerHtmlFileUrl = new URL("./html/explorer.html", import.meta.url);
20181
20291
  const jsenvPluginExplorer = ({
20182
20292
  groups = {
20183
20293
  src: {
20184
- "./src/**/*.html": true
20294
+ "./**/*.html": true,
20295
+ "./**/*.test.html": false
20185
20296
  },
20186
20297
  tests: {
20187
- "./tests/**/*.test.html": true
20298
+ "./**/*.test.html": true
20188
20299
  }
20189
20300
  },
20190
- clientMainFileUrl
20301
+ mainFileUrl
20191
20302
  }) => {
20192
20303
  const faviconClientFileUrl = new URL("./other/jsenv.png", import.meta.url);
20193
20304
  return {
@@ -20195,7 +20306,7 @@ const jsenvPluginExplorer = ({
20195
20306
  appliesDuring: "dev",
20196
20307
  transformUrlContent: {
20197
20308
  html: async (urlInfo, context) => {
20198
- if (urlInfo.url !== clientMainFileUrl) {
20309
+ if (urlInfo.url !== mainFileUrl) {
20199
20310
  return null;
20200
20311
  }
20201
20312
  let html = urlInfo.content;
@@ -20297,6 +20408,7 @@ injectRibbon(${paramsJson})`
20297
20408
 
20298
20409
  const getCorePlugins = ({
20299
20410
  rootDirectoryUrl,
20411
+ mainFileUrl,
20300
20412
  runtimeCompat,
20301
20413
  urlAnalysis = {},
20302
20414
  urlResolution = {},
@@ -20304,7 +20416,6 @@ const getCorePlugins = ({
20304
20416
  directoryReferenceAllowed,
20305
20417
  supervisor,
20306
20418
  transpilation = true,
20307
- clientMainFileUrl,
20308
20419
  clientAutoreload = false,
20309
20420
  clientFileChangeCallbackList,
20310
20421
  clientFilesPruneCallbackList,
@@ -20328,11 +20439,6 @@ const getCorePlugins = ({
20328
20439
  if (clientAutoreload === true) {
20329
20440
  clientAutoreload = {};
20330
20441
  }
20331
- if (clientMainFileUrl === undefined) {
20332
- clientMainFileUrl = explorer ? String(explorerHtmlFileUrl) : String(new URL("./index.html", rootDirectoryUrl));
20333
- } else {
20334
- clientMainFileUrl = String(clientMainFileUrl);
20335
- }
20336
20442
  if (ribbon === true) {
20337
20443
  ribbon = {};
20338
20444
  }
@@ -20351,7 +20457,7 @@ const getCorePlugins = ({
20351
20457
  ...fileSystemMagicRedirection
20352
20458
  }), jsenvPluginHttpUrls(), jsenvPluginUrlResolution({
20353
20459
  runtimeCompat,
20354
- clientMainFileUrl,
20460
+ mainFileUrl,
20355
20461
  urlResolution
20356
20462
  }), jsenvPluginUrlVersion(), jsenvPluginCommonJsGlobals(), jsenvPluginImportMetaScenarios(), ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []), jsenvPluginNodeRuntime({
20357
20463
  runtimeCompat
@@ -20361,7 +20467,7 @@ const getCorePlugins = ({
20361
20467
  clientFilesPruneCallbackList
20362
20468
  })] : []), ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []), ...(explorer ? [jsenvPluginExplorer({
20363
20469
  ...explorer,
20364
- clientMainFileUrl
20470
+ mainFileUrl
20365
20471
  })] : []), ...(ribbon ? [jsenvPluginRibbon({
20366
20472
  rootDirectoryUrl,
20367
20473
  ...ribbon
@@ -20720,12 +20826,13 @@ const defaultRuntimeCompat = {
20720
20826
  /**
20721
20827
  * Generate an optimized version of source files into a directory
20722
20828
  * @param {Object} buildParameters
20723
- * @param {string|url} buildParameters.rootDirectoryUrl
20829
+ * @param {string|url} buildParameters.sourceDirectoryUrl
20724
20830
  * Directory containing source files
20831
+ * @param {object} buildParameters.entryPoints
20832
+ * Object where keys are paths to source files and values are their future name in the build directory.
20833
+ * Keys are relative to sourceDirectoryUrl
20725
20834
  * @param {string|url} buildParameters.buildDirectoryUrl
20726
20835
  * Directory where optimized files will be written
20727
- * @param {object} buildParameters.entryPoints
20728
- * Describe entry point paths and control their names in the build directory
20729
20836
  * @param {object} buildParameters.runtimeCompat
20730
20837
  * Code generated will be compatible with these runtimes
20731
20838
  * @param {string} [buildParameters.assetsDirectory=""]
@@ -20750,10 +20857,10 @@ const build = async ({
20750
20857
  signal = new AbortController().signal,
20751
20858
  handleSIGINT = true,
20752
20859
  logLevel = "info",
20753
- rootDirectoryUrl,
20860
+ sourceDirectoryUrl,
20861
+ entryPoints = {},
20754
20862
  buildDirectoryUrl,
20755
20863
  assetsDirectory = "",
20756
- entryPoints = {},
20757
20864
  runtimeCompat = defaultRuntimeCompat,
20758
20865
  base = runtimeCompat.node ? "./" : "/",
20759
20866
  plugins = [],
@@ -20770,9 +20877,7 @@ const build = async ({
20770
20877
  // "filename", "search_param"
20771
20878
  versioningViaImportmap = true,
20772
20879
  lineBreakNormalization = process.platform === "win32",
20773
- clientFiles = {
20774
- "./src/": true
20775
- },
20880
+ sourceFilesConfig = {},
20776
20881
  cooldownBetweenFileEvents,
20777
20882
  watch = false,
20778
20883
  directoryToClean,
@@ -20788,16 +20893,27 @@ const build = async ({
20788
20893
  if (unexpectedParamNames.length > 0) {
20789
20894
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
20790
20895
  }
20791
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
20792
- if (!rootDirectoryUrlValidation.valid) {
20793
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
20896
+ sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(sourceDirectoryUrl, "sourceDirectoryUrl");
20897
+ if (typeof entryPoints !== "object" || entryPoints === null) {
20898
+ throw new TypeError(`entryPoints must be an object, got ${entryPoints}`);
20794
20899
  }
20795
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
20796
- const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl);
20797
- if (!buildDirectoryUrlValidation.valid) {
20798
- throw new TypeError(`buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`);
20900
+ const keys = Object.keys(entryPoints);
20901
+ keys.forEach(key => {
20902
+ if (!key.startsWith("./")) {
20903
+ throw new TypeError(`entryPoints keys must start with "./", found ${key}`);
20904
+ }
20905
+ const value = entryPoints[key];
20906
+ if (typeof value !== "string") {
20907
+ throw new TypeError(`entryPoints values must be strings, found "${value}" on key "${key}"`);
20908
+ }
20909
+ if (value.includes("/")) {
20910
+ throw new TypeError(`entryPoints values must be plain strings (no "/"), found "${value}" on key "${key}"`);
20911
+ }
20912
+ });
20913
+ buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl, "buildDirectoryUrl");
20914
+ if (!["filename", "search_param"].includes(versioningMethod)) {
20915
+ throw new TypeError(`versioningMethod must be "filename" or "search_param", got ${versioning}`);
20799
20916
  }
20800
- buildDirectoryUrl = buildDirectoryUrlValidation.value;
20801
20917
  }
20802
20918
  const operation = Abort.startOperation();
20803
20919
  operation.addAbortSignal(signal);
@@ -20808,12 +20924,6 @@ const build = async ({
20808
20924
  }, abort);
20809
20925
  });
20810
20926
  }
20811
- assertEntryPoints({
20812
- entryPoints
20813
- });
20814
- if (!["filename", "search_param"].includes(versioningMethod)) {
20815
- throw new Error(`Unexpected "versioningMethod": must be "filename", "search_param"; got ${versioning}`);
20816
- }
20817
20927
  if (assetsDirectory && assetsDirectory[assetsDirectory.length - 1] !== "/") {
20818
20928
  assetsDirectory = `${assetsDirectory}/`;
20819
20929
  }
@@ -20824,9 +20934,10 @@ const build = async ({
20824
20934
  directoryToClean = new URL(assetsDirectory, buildDirectoryUrl).href;
20825
20935
  }
20826
20936
  }
20937
+ const jsenvInternalDirectoryUrl = determineJsenvInternalDirectoryUrl(sourceDirectoryUrl);
20827
20938
  const asFormattedBuildUrl = (generatedUrl, reference) => {
20828
20939
  if (base === "./") {
20829
- const urlRelativeToParent = urlToRelativeUrl(generatedUrl, reference.parentUrl === rootDirectoryUrl ? buildDirectoryUrl : reference.parentUrl);
20940
+ const urlRelativeToParent = urlToRelativeUrl(generatedUrl, reference.parentUrl === sourceDirectoryUrl ? buildDirectoryUrl : reference.parentUrl);
20830
20941
  if (urlRelativeToParent[0] !== ".") {
20831
20942
  // ensure "./" on relative url (otherwise it could be a "bare specifier")
20832
20943
  return `./${urlRelativeToParent}`;
@@ -20876,7 +20987,8 @@ build ${entryPointKeys.length} entry points`);
20876
20987
  const rawGraphKitchen = createKitchen({
20877
20988
  signal,
20878
20989
  logLevel,
20879
- rootDirectoryUrl,
20990
+ rootDirectoryUrl: sourceDirectoryUrl,
20991
+ jsenvInternalDirectoryUrl,
20880
20992
  urlGraph: rawGraph,
20881
20993
  build: true,
20882
20994
  runtimeCompat,
@@ -20895,7 +21007,7 @@ build ${entryPointKeys.length} entry points`);
20895
21007
  return null;
20896
21008
  }
20897
21009
  }, ...getCorePlugins({
20898
- rootDirectoryUrl,
21010
+ rootDirectoryUrl: sourceDirectoryUrl,
20899
21011
  urlGraph: rawGraph,
20900
21012
  runtimeCompat,
20901
21013
  urlAnalysis,
@@ -20912,7 +21024,7 @@ build ${entryPointKeys.length} entry points`);
20912
21024
  sourcemaps,
20913
21025
  sourcemapsSourcesContent,
20914
21026
  writeGeneratedFiles,
20915
- outDirectoryUrl: new URL(`.jsenv/build/`, rootDirectoryUrl)
21027
+ outDirectoryUrl: new URL("build/", jsenvInternalDirectoryUrl)
20916
21028
  });
20917
21029
  const buildUrlsGenerator = createBuildUrlsGenerator({
20918
21030
  buildDirectoryUrl,
@@ -20934,12 +21046,13 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20934
21046
  const bundlers = {};
20935
21047
  const finalGraph = createUrlGraph();
20936
21048
  const urlAnalysisPlugin = jsenvPluginUrlAnalysis({
20937
- rootDirectoryUrl,
21049
+ rootDirectoryUrl: sourceDirectoryUrl,
20938
21050
  ...urlAnalysis
20939
21051
  });
20940
21052
  const finalGraphKitchen = createKitchen({
20941
21053
  logLevel,
20942
21054
  rootDirectoryUrl: buildDirectoryUrl,
21055
+ jsenvInternalDirectoryUrl,
20943
21056
  urlGraph: finalGraph,
20944
21057
  build: true,
20945
21058
  runtimeCompat,
@@ -21186,7 +21299,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21186
21299
  sourcemapsSourcesContent,
21187
21300
  sourcemapsSourcesRelative: !versioning,
21188
21301
  writeGeneratedFiles,
21189
- outDirectoryUrl: new URL(".jsenv/postbuild/", rootDirectoryUrl)
21302
+ outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl)
21190
21303
  });
21191
21304
  const finalEntryUrls = [];
21192
21305
  {
@@ -21195,7 +21308,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21195
21308
  });
21196
21309
  try {
21197
21310
  if (writeGeneratedFiles) {
21198
- await ensureEmptyDirectory(new URL(`.jsenv/build/`, rootDirectoryUrl));
21311
+ await ensureEmptyDirectory(new URL(`build/`, sourceDirectoryUrl));
21199
21312
  }
21200
21313
  const rawUrlGraphLoader = createUrlGraphLoader(rawGraphKitchen.kitchenContext);
21201
21314
  Object.keys(entryPoints).forEach(key => {
@@ -21203,7 +21316,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21203
21316
  trace: {
21204
21317
  message: `"${key}" in entryPoints parameter`
21205
21318
  },
21206
- parentUrl: rootDirectoryUrl,
21319
+ parentUrl: sourceDirectoryUrl,
21207
21320
  type: "entry_point",
21208
21321
  specifier: key
21209
21322
  });
@@ -21409,7 +21522,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21409
21522
  });
21410
21523
  try {
21411
21524
  if (writeGeneratedFiles) {
21412
- await ensureEmptyDirectory(new URL(`.jsenv/postbuild/`, rootDirectoryUrl));
21525
+ await ensureEmptyDirectory(new URL(`postbuild/`, jsenvInternalDirectoryUrl));
21413
21526
  }
21414
21527
  const finalUrlGraphLoader = createUrlGraphLoader(finalGraphKitchen.kitchenContext);
21415
21528
  entryUrls.forEach(entryUrl => {
@@ -21417,7 +21530,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21417
21530
  trace: {
21418
21531
  message: `entryPoint`
21419
21532
  },
21420
- parentUrl: rootDirectoryUrl,
21533
+ parentUrl: sourceDirectoryUrl,
21421
21534
  type: "entry_point",
21422
21535
  specifier: entryUrl
21423
21536
  });
@@ -21609,6 +21722,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21609
21722
  const versioningKitchen = createKitchen({
21610
21723
  logLevel: logger.level,
21611
21724
  rootDirectoryUrl: buildDirectoryUrl,
21725
+ jsenvInternalDirectoryUrl,
21612
21726
  urlGraph: finalGraph,
21613
21727
  build: true,
21614
21728
  runtimeCompat,
@@ -21701,7 +21815,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21701
21815
  sourcemapsSourcesContent,
21702
21816
  sourcemapsSourcesRelative: true,
21703
21817
  writeGeneratedFiles,
21704
- outDirectoryUrl: new URL(".jsenv/postbuild/", finalGraphKitchen.rootDirectoryUrl)
21818
+ outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl)
21705
21819
  });
21706
21820
  const versioningUrlGraphLoader = createUrlGraphLoader(versioningKitchen.kitchenContext);
21707
21821
  finalEntryUrls.forEach(finalEntryUrl => {
@@ -22077,13 +22191,12 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22077
22191
  };
22078
22192
  startBuild();
22079
22193
  let startTimeout;
22080
- const clientFileChangeCallback = ({
22081
- relativeUrl,
22194
+ const stopWatchingSourceFiles = watchSourceFiles(sourceDirectoryUrl, ({
22195
+ url,
22082
22196
  event
22083
22197
  }) => {
22084
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
22085
22198
  if (watchFilesTask) {
22086
- watchFilesTask.happen(`${url.slice(rootDirectoryUrl.length)} ${event}`);
22199
+ watchFilesTask.happen(`${url.slice(sourceDirectoryUrl.length)} ${event}`);
22087
22200
  watchFilesTask = null;
22088
22201
  }
22089
22202
  buildAbortController.abort();
@@ -22092,42 +22205,16 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22092
22205
  // then logs about re-running the build happens
22093
22206
  clearTimeout(startTimeout);
22094
22207
  startTimeout = setTimeout(startBuild, 20);
22095
- };
22096
- const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22097
- watchPatterns: clientFiles,
22098
- cooldownBetweenFileEvents,
22208
+ }, {
22209
+ sourceFilesConfig,
22099
22210
  keepProcessAlive: true,
22100
- recursive: true,
22101
- added: ({
22102
- relativeUrl
22103
- }) => {
22104
- clientFileChangeCallback({
22105
- relativeUrl,
22106
- event: "added"
22107
- });
22108
- },
22109
- updated: ({
22110
- relativeUrl
22111
- }) => {
22112
- clientFileChangeCallback({
22113
- relativeUrl,
22114
- event: "modified"
22115
- });
22116
- },
22117
- removed: ({
22118
- relativeUrl
22119
- }) => {
22120
- clientFileChangeCallback({
22121
- relativeUrl,
22122
- event: "removed"
22123
- });
22124
- }
22211
+ cooldownBetweenFileEvents
22125
22212
  });
22126
22213
  operation.addAbortCallback(() => {
22127
- stopWatchingClientFiles();
22214
+ stopWatchingSourceFiles();
22128
22215
  });
22129
22216
  await firstBuildPromise;
22130
- return stopWatchingClientFiles;
22217
+ return stopWatchingSourceFiles;
22131
22218
  };
22132
22219
  const findKey = (map, value) => {
22133
22220
  for (const [keyCandidate, valueCandidate] of map) {
@@ -22153,26 +22240,6 @@ const injectVersionIntoBuildUrl = ({
22153
22240
  const versionedUrl = setUrlFilename(buildUrl, versionedFilename);
22154
22241
  return versionedUrl;
22155
22242
  };
22156
- const assertEntryPoints = ({
22157
- entryPoints
22158
- }) => {
22159
- if (typeof entryPoints !== "object" || entryPoints === null) {
22160
- throw new TypeError(`entryPoints must be an object, got ${entryPoints}`);
22161
- }
22162
- const keys = Object.keys(entryPoints);
22163
- keys.forEach(key => {
22164
- if (!key.startsWith("./")) {
22165
- throw new TypeError(`unexpected key in entryPoints, all keys must start with ./ but found ${key}`);
22166
- }
22167
- const value = entryPoints[key];
22168
- if (typeof value !== "string") {
22169
- throw new TypeError(`unexpected value in entryPoints, all values must be strings found ${value} for key ${key}`);
22170
- }
22171
- if (value.includes("/")) {
22172
- throw new TypeError(`unexpected value in entryPoints, all values must be plain strings (no "/") but found ${value} for key ${key}`);
22173
- }
22174
- });
22175
- };
22176
22243
  const isUsed = urlInfo => {
22177
22244
  // nothing uses this url anymore
22178
22245
  // - versioning update inline content
@@ -22195,57 +22262,6 @@ const canUseVersionedUrl = urlInfo => {
22195
22262
  return urlInfo.type !== "webmanifest";
22196
22263
  };
22197
22264
 
22198
- // https://nodejs.org/api/worker_threads.html
22199
- const createReloadableWorker = (workerFileUrl, options = {}) => {
22200
- const workerFilePath = fileURLToPath(workerFileUrl);
22201
- const isPrimary = !workerData || workerData.workerFilePath !== workerFilePath;
22202
- let worker;
22203
- const terminate = async () => {
22204
- if (worker) {
22205
- let _worker = worker;
22206
- worker = null;
22207
- const exitPromise = new Promise(resolve => {
22208
- _worker.once("exit", resolve);
22209
- });
22210
- _worker.terminate();
22211
- await exitPromise;
22212
- }
22213
- };
22214
- const load = async () => {
22215
- if (!isPrimary) {
22216
- throw new Error(`worker can be loaded from primary file only`);
22217
- }
22218
- worker = new Worker(workerFilePath, {
22219
- ...options,
22220
- workerData: {
22221
- ...options.workerData,
22222
- workerFilePath
22223
- }
22224
- });
22225
- worker.once("error", error => {
22226
- console.error(error);
22227
- });
22228
- worker.once("exit", () => {
22229
- worker = null;
22230
- });
22231
- await new Promise(resolve => {
22232
- worker.once("online", resolve);
22233
- });
22234
- return worker;
22235
- };
22236
- const reload = async () => {
22237
- await terminate();
22238
- await load();
22239
- };
22240
- return {
22241
- isPrimary,
22242
- isWorker: !isPrimary,
22243
- load,
22244
- reload,
22245
- terminate
22246
- };
22247
- };
22248
-
22249
22265
  /*
22250
22266
  * This plugin is very special because it is here
22251
22267
  * to provide "serverEvents" used by other plugins
@@ -22309,7 +22325,9 @@ const createFileService = ({
22309
22325
  serverStopCallbacks,
22310
22326
  serverEventsDispatcher,
22311
22327
  contextCache,
22312
- rootDirectoryUrl,
22328
+ sourceDirectoryUrl,
22329
+ sourceMainFilePath,
22330
+ sourceFilesConfig,
22313
22331
  runtimeCompat,
22314
22332
  plugins,
22315
22333
  urlAnalysis,
@@ -22318,8 +22336,6 @@ const createFileService = ({
22318
22336
  supervisor,
22319
22337
  transpilation,
22320
22338
  clientAutoreload,
22321
- clientFiles,
22322
- clientMainFileUrl,
22323
22339
  cooldownBetweenFileEvents,
22324
22340
  explorer,
22325
22341
  cacheControl,
@@ -22329,49 +22345,18 @@ const createFileService = ({
22329
22345
  sourcemapsSourcesContent,
22330
22346
  writeGeneratedFiles
22331
22347
  }) => {
22332
- const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href;
22333
22348
  const clientFileChangeCallbackList = [];
22334
22349
  const clientFilesPruneCallbackList = [];
22335
- const clientFilePatterns = {
22336
- ...clientFiles,
22337
- ".jsenv/": false
22338
- };
22339
- const onFileChange = url => {
22350
+ const stopWatchingSourceFiles = watchSourceFiles(sourceDirectoryUrl, fileInfo => {
22340
22351
  clientFileChangeCallbackList.forEach(callback => {
22341
- callback(url);
22352
+ callback(fileInfo);
22342
22353
  });
22343
- };
22344
- const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22345
- watchPatterns: clientFilePatterns,
22346
- cooldownBetweenFileEvents,
22354
+ }, {
22355
+ sourceFilesConfig,
22347
22356
  keepProcessAlive: false,
22348
- recursive: true,
22349
- added: ({
22350
- relativeUrl
22351
- }) => {
22352
- onFileChange({
22353
- url: new URL(relativeUrl, rootDirectoryUrl).href,
22354
- event: "added"
22355
- });
22356
- },
22357
- updated: ({
22358
- relativeUrl
22359
- }) => {
22360
- onFileChange({
22361
- url: new URL(relativeUrl, rootDirectoryUrl).href,
22362
- event: "modified"
22363
- });
22364
- },
22365
- removed: ({
22366
- relativeUrl
22367
- }) => {
22368
- onFileChange({
22369
- url: new URL(relativeUrl, rootDirectoryUrl).href,
22370
- event: "removed"
22371
- });
22372
- }
22357
+ cooldownBetweenFileEvents
22373
22358
  });
22374
- serverStopCallbacks.push(stopWatchingClientFiles);
22359
+ serverStopCallbacks.push(stopWatchingSourceFiles);
22375
22360
  const getOrCreateContext = request => {
22376
22361
  const {
22377
22362
  runtimeName,
@@ -22383,8 +22368,8 @@ const createFileService = ({
22383
22368
  return existingContext;
22384
22369
  }
22385
22370
  const watchAssociations = URL_META.resolveAssociations({
22386
- watch: clientFilePatterns
22387
- }, rootDirectoryUrl);
22371
+ watch: stopWatchingSourceFiles.watchPatterns
22372
+ }, sourceDirectoryUrl);
22388
22373
  const urlGraph = createUrlGraph();
22389
22374
  clientFileChangeCallbackList.push(({
22390
22375
  url
@@ -22407,24 +22392,32 @@ const createFileService = ({
22407
22392
  const clientRuntimeCompat = {
22408
22393
  [runtimeName]: runtimeVersion
22409
22394
  };
22395
+ const jsenvInternalDirectoryUrl = determineJsenvInternalDirectoryUrl(sourceDirectoryUrl);
22396
+ let mainFileUrl;
22397
+ if (sourceMainFilePath === undefined) {
22398
+ mainFileUrl = explorer ? String(explorerHtmlFileUrl) : String(new URL("./index.html", sourceDirectoryUrl));
22399
+ } else {
22400
+ mainFileUrl = String(new URL(sourceMainFilePath, sourceDirectoryUrl));
22401
+ }
22410
22402
  const kitchen = createKitchen({
22411
22403
  signal,
22412
22404
  logLevel,
22413
- rootDirectoryUrl,
22405
+ rootDirectoryUrl: sourceDirectoryUrl,
22406
+ jsenvInternalDirectoryUrl,
22414
22407
  urlGraph,
22415
22408
  dev: true,
22416
22409
  runtimeCompat,
22417
22410
  clientRuntimeCompat,
22418
22411
  systemJsTranspilation: !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "script_type_module") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_dynamic") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_meta"),
22419
22412
  plugins: [...plugins, ...getCorePlugins({
22420
- rootDirectoryUrl,
22413
+ rootDirectoryUrl: sourceDirectoryUrl,
22414
+ mainFileUrl,
22421
22415
  runtimeCompat,
22422
22416
  urlAnalysis,
22423
22417
  urlResolution,
22424
22418
  fileSystemMagicRedirection,
22425
22419
  supervisor,
22426
22420
  transpilation,
22427
- clientMainFileUrl,
22428
22421
  clientAutoreload,
22429
22422
  clientFileChangeCallbackList,
22430
22423
  clientFilesPruneCallbackList,
@@ -22438,7 +22431,7 @@ const createFileService = ({
22438
22431
  sourcemapsSourcesProtocol,
22439
22432
  sourcemapsSourcesContent,
22440
22433
  writeGeneratedFiles,
22441
- outDirectoryUrl: `${rootDirectoryUrl}.jsenv/${runtimeName}@${runtimeVersion}/`
22434
+ outDirectoryUrl: new URL(`${runtimeName}@${runtimeVersion}/`, jsenvInternalDirectoryUrl)
22442
22435
  });
22443
22436
  urlGraph.createUrlInfoCallbackRef.current = urlInfo => {
22444
22437
  const {
@@ -22517,7 +22510,7 @@ const createFileService = ({
22517
22510
  if (serverEventNames.length > 0) {
22518
22511
  Object.keys(allServerEvents).forEach(serverEventName => {
22519
22512
  allServerEvents[serverEventName]({
22520
- rootDirectoryUrl,
22513
+ rootDirectoryUrl: sourceDirectoryUrl,
22521
22514
  urlGraph,
22522
22515
  dev: true,
22523
22516
  sendServerEvent: data => {
@@ -22533,7 +22526,7 @@ const createFileService = ({
22533
22526
  }
22534
22527
  }
22535
22528
  const context = {
22536
- rootDirectoryUrl,
22529
+ rootDirectoryUrl: sourceDirectoryUrl,
22537
22530
  dev: true,
22538
22531
  runtimeName,
22539
22532
  runtimeVersion,
@@ -22544,13 +22537,6 @@ const createFileService = ({
22544
22537
  return context;
22545
22538
  };
22546
22539
  return async request => {
22547
- // serve file inside ".jsenv" directory
22548
- const requestFileUrl = new URL(request.resource.slice(1), rootDirectoryUrl).href;
22549
- if (urlIsInsideOf(requestFileUrl, jsenvDirectoryUrl)) {
22550
- return fetchFileSystem(requestFileUrl, {
22551
- headers: request.headers
22552
- });
22553
- }
22554
22540
  const {
22555
22541
  urlGraph,
22556
22542
  kitchen
@@ -22560,16 +22546,16 @@ const createFileService = ({
22560
22546
  return responseFromPlugin;
22561
22547
  }
22562
22548
  let reference;
22563
- const parentUrl = inferParentFromRequest(request, rootDirectoryUrl);
22549
+ const parentUrl = inferParentFromRequest(request, sourceDirectoryUrl);
22564
22550
  if (parentUrl) {
22565
22551
  reference = urlGraph.inferReference(request.resource, parentUrl);
22566
22552
  }
22567
22553
  if (!reference) {
22568
22554
  const entryPoint = kitchen.injectReference({
22569
22555
  trace: {
22570
- message: parentUrl || rootDirectoryUrl
22556
+ message: parentUrl || sourceDirectoryUrl
22571
22557
  },
22572
- parentUrl: parentUrl || rootDirectoryUrl,
22558
+ parentUrl: parentUrl || sourceDirectoryUrl,
22573
22559
  type: "http_request",
22574
22560
  specifier: request.resource
22575
22561
  });
@@ -22691,7 +22677,7 @@ const createFileService = ({
22691
22677
  accept: "text/html"
22692
22678
  },
22693
22679
  canReadDirectory: true,
22694
- rootDirectoryUrl
22680
+ rootDirectoryUrl: sourceDirectoryUrl
22695
22681
  });
22696
22682
  }
22697
22683
  if (code === "NOT_ALLOWED") {
@@ -22718,7 +22704,7 @@ const createFileService = ({
22718
22704
  }
22719
22705
  };
22720
22706
  };
22721
- const inferParentFromRequest = (request, rootDirectoryUrl) => {
22707
+ const inferParentFromRequest = (request, sourceDirectoryUrl) => {
22722
22708
  const {
22723
22709
  referer
22724
22710
  } = request.headers;
@@ -22739,7 +22725,7 @@ const inferParentFromRequest = (request, rootDirectoryUrl) => {
22739
22725
  return moveUrl({
22740
22726
  url: referer,
22741
22727
  from: `${request.origin}/`,
22742
- to: rootDirectoryUrl,
22728
+ to: sourceDirectoryUrl,
22743
22729
  preferAbsolute: true
22744
22730
  });
22745
22731
  };
@@ -22749,38 +22735,28 @@ const inferParentFromRequest = (request, rootDirectoryUrl) => {
22749
22735
  * - cook source files according to jsenv plugins
22750
22736
  * - inject code to autoreload the browser when a file is modified
22751
22737
  * @param {Object} devServerParameters
22752
- * @param {string|url} devServerParameters.rootDirectoryUrl Root directory of the project
22738
+ * @param {string|url} devServerParameters.sourceDirectoryUrl Root directory of the project
22753
22739
  * @return {Object} A dev server object
22754
22740
  */
22755
22741
  const startDevServer = async ({
22756
- signal = new AbortController().signal,
22757
- handleSIGINT = true,
22758
- logLevel = "info",
22759
- serverLogLevel = "warn",
22742
+ sourceDirectoryUrl,
22743
+ sourceMainFilePath,
22744
+ port = 3456,
22745
+ hostname,
22746
+ acceptAnyIp,
22760
22747
  https,
22761
22748
  // it's better to use http1 by default because it allows to get statusText in devtools
22762
22749
  // which gives valuable information when there is errors
22763
22750
  http2 = false,
22764
- hostname,
22765
- port = 3456,
22766
- acceptAnyIp,
22767
- keepProcessAlive = true,
22751
+ logLevel = process.env.IMPORTED_BY_TEST_PLAN ? "warn" : "info",
22752
+ serverLogLevel = "warn",
22768
22753
  services = [],
22754
+ signal = new AbortController().signal,
22755
+ handleSIGINT = true,
22756
+ keepProcessAlive = true,
22769
22757
  onStop = () => {},
22770
- rootDirectoryUrl,
22771
- clientFiles = {
22772
- "./src/": true,
22773
- "./tests/": true,
22774
- "./package.json": true
22775
- },
22776
- devServerFiles = {
22777
- "./package.json": true,
22778
- "./jsenv.config.mjs": true
22779
- },
22758
+ sourceFilesConfig,
22780
22759
  clientAutoreload = true,
22781
- clientMainFileUrl,
22782
- devServerAutoreload = false,
22783
- devServerMainFile = getCallerPosition().url,
22784
22760
  cooldownBetweenFileEvents,
22785
22761
  // runtimeCompat is the runtimeCompat for the build
22786
22762
  // when specified, dev server use it to warn in case
@@ -22812,11 +22788,7 @@ const startDevServer = async ({
22812
22788
  if (unexpectedParamNames.length > 0) {
22813
22789
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
22814
22790
  }
22815
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
22816
- if (!rootDirectoryUrlValidation.valid) {
22817
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
22818
- }
22819
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
22791
+ sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(sourceDirectoryUrl, "sourceDirectoryUrl");
22820
22792
  }
22821
22793
  const logger = createLogger({
22822
22794
  logLevel
@@ -22830,70 +22802,6 @@ const startDevServer = async ({
22830
22802
  }, abort);
22831
22803
  });
22832
22804
  }
22833
- let reloadableWorker;
22834
- if (devServerAutoreload) {
22835
- reloadableWorker = createReloadableWorker(devServerMainFile);
22836
- if (reloadableWorker.isPrimary) {
22837
- const devServerFileChangeCallback = ({
22838
- relativeUrl,
22839
- event
22840
- }) => {
22841
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
22842
- logger.info(`file ${event} ${url} -> restarting server...`);
22843
- reloadableWorker.reload();
22844
- };
22845
- const stopWatchingDevServerFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22846
- watchPatterns: {
22847
- ...devServerFiles.include,
22848
- [devServerMainFile]: true,
22849
- ".jsenv/": false
22850
- },
22851
- cooldownBetweenFileEvents,
22852
- keepProcessAlive: false,
22853
- recursive: true,
22854
- added: ({
22855
- relativeUrl
22856
- }) => {
22857
- devServerFileChangeCallback({
22858
- relativeUrl,
22859
- event: "added"
22860
- });
22861
- },
22862
- updated: ({
22863
- relativeUrl
22864
- }) => {
22865
- devServerFileChangeCallback({
22866
- relativeUrl,
22867
- event: "modified"
22868
- });
22869
- },
22870
- removed: ({
22871
- relativeUrl
22872
- }) => {
22873
- devServerFileChangeCallback({
22874
- relativeUrl,
22875
- event: "removed"
22876
- });
22877
- }
22878
- });
22879
- operation.addAbortCallback(() => {
22880
- stopWatchingDevServerFiles();
22881
- reloadableWorker.terminate();
22882
- });
22883
- const worker = await reloadableWorker.load();
22884
- const messagePromise = new Promise(resolve => {
22885
- worker.once("message", resolve);
22886
- });
22887
- const origin = await messagePromise;
22888
- return {
22889
- origin,
22890
- stop: () => {
22891
- stopWatchingDevServerFiles();
22892
- reloadableWorker.terminate();
22893
- }
22894
- };
22895
- }
22896
- }
22897
22805
  const startDevServerTask = createTaskLog("start dev server", {
22898
22806
  disabled: !logger.levels.info
22899
22807
  });
@@ -22924,7 +22832,30 @@ const startDevServer = async ({
22924
22832
  accessControlAllowedRequestHeaders: [...jsenvAccessControlAllowedHeaders, "x-jsenv-execution-id"],
22925
22833
  accessControlAllowCredentials: true,
22926
22834
  timingAllowOrigin: true
22927
- }), ...services, {
22835
+ }), {
22836
+ handleRequest: request => {
22837
+ if (request.pathname === "/__server_params__.json") {
22838
+ const json = JSON.stringify({
22839
+ sourceDirectoryUrl
22840
+ });
22841
+ return {
22842
+ status: 200,
22843
+ headers: {
22844
+ "content-type": "application/json",
22845
+ "content-length": Buffer.byteLength(json)
22846
+ },
22847
+ body: json
22848
+ };
22849
+ }
22850
+ if (request.pathname === "/__stop__") {
22851
+ server.stop();
22852
+ return {
22853
+ status: 200
22854
+ };
22855
+ }
22856
+ return null;
22857
+ }
22858
+ }, ...services, {
22928
22859
  name: "jsenv:omega_file_service",
22929
22860
  handleRequest: createFileService({
22930
22861
  signal,
@@ -22932,7 +22863,9 @@ const startDevServer = async ({
22932
22863
  serverStopCallbacks,
22933
22864
  serverEventsDispatcher,
22934
22865
  contextCache,
22935
- rootDirectoryUrl,
22866
+ sourceDirectoryUrl,
22867
+ sourceMainFilePath,
22868
+ sourceFilesConfig,
22936
22869
  runtimeCompat,
22937
22870
  plugins,
22938
22871
  urlAnalysis,
@@ -22941,8 +22874,6 @@ const startDevServer = async ({
22941
22874
  supervisor,
22942
22875
  transpilation,
22943
22876
  clientAutoreload,
22944
- clientFiles,
22945
- clientMainFileUrl,
22946
22877
  cooldownBetweenFileEvents,
22947
22878
  explorer,
22948
22879
  cacheControl,
@@ -23015,9 +22946,6 @@ const startDevServer = async ({
23015
22946
  logger.info(`- ${server.origins[key]}`);
23016
22947
  });
23017
22948
  logger.info(``);
23018
- if (reloadableWorker && reloadableWorker.isWorker) {
23019
- parentPort.postMessage(server.origin);
23020
- }
23021
22949
  return {
23022
22950
  origin: server.origin,
23023
22951
  stop: () => {
@@ -23027,6 +22955,80 @@ const startDevServer = async ({
23027
22955
  };
23028
22956
  };
23029
22957
 
22958
+ const pingServer = async url => {
22959
+ const server = createServer();
22960
+ const {
22961
+ hostname,
22962
+ port
22963
+ } = new URL(url);
22964
+ try {
22965
+ await new Promise((resolve, reject) => {
22966
+ server.on("error", reject);
22967
+ server.on("listening", () => {
22968
+ resolve();
22969
+ });
22970
+ server.listen(port, hostname);
22971
+ });
22972
+ } catch (error) {
22973
+ if (error && error.code === "EADDRINUSE") {
22974
+ return true;
22975
+ }
22976
+ if (error && error.code === "EACCES") {
22977
+ return true;
22978
+ }
22979
+ throw error;
22980
+ }
22981
+ await new Promise((resolve, reject) => {
22982
+ server.on("error", reject);
22983
+ server.on("close", resolve);
22984
+ server.close();
22985
+ });
22986
+ return false;
22987
+ };
22988
+
22989
+ const basicFetch = async (url, {
22990
+ method = "GET",
22991
+ headers = {}
22992
+ } = {}) => {
22993
+ let requestModule;
22994
+ if (url.startsWith("http:")) {
22995
+ requestModule = await import("node:http");
22996
+ } else {
22997
+ requestModule = await import("node:https");
22998
+ }
22999
+ const {
23000
+ request
23001
+ } = requestModule;
23002
+ const urlObject = new URL(url);
23003
+ return new Promise((resolve, reject) => {
23004
+ const req = request({
23005
+ hostname: urlObject.hostname,
23006
+ port: urlObject.port,
23007
+ path: urlObject.pathname,
23008
+ method,
23009
+ headers
23010
+ });
23011
+ req.on("response", response => {
23012
+ req.setTimeout(0);
23013
+ let responseBody = "";
23014
+ response.setEncoding("utf8");
23015
+ response.on("data", chunk => {
23016
+ responseBody += chunk;
23017
+ });
23018
+ response.on("end", () => {
23019
+ req.destroy();
23020
+ if (response.headers["content-type"] === "application/json") {
23021
+ resolve(JSON.parse(responseBody));
23022
+ } else {
23023
+ resolve(responseBody);
23024
+ }
23025
+ });
23026
+ });
23027
+ req.on("error", reject);
23028
+ req.end();
23029
+ });
23030
+ };
23031
+
23030
23032
  const generateCoverageJsonFile = async ({
23031
23033
  coverage,
23032
23034
  coverageJsonFileUrl,
@@ -23116,7 +23118,7 @@ const readNodeV8CoverageDirectory = async ({
23116
23118
  try {
23117
23119
  operation.throwIfAborted();
23118
23120
  const dirContent = await tryReadDirectory();
23119
- const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE);
23121
+ const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE, "NODE_V8_COVERAGE");
23120
23122
  await dirContent.reduce(async (previous, dirEntry) => {
23121
23123
  operation.throwIfAborted();
23122
23124
  await previous;
@@ -23739,37 +23741,6 @@ const run = async ({
23739
23741
  return result;
23740
23742
  };
23741
23743
 
23742
- const pingServer = async url => {
23743
- const server = createServer();
23744
- const {
23745
- hostname,
23746
- port
23747
- } = new URL(url);
23748
- try {
23749
- await new Promise((resolve, reject) => {
23750
- server.on("error", reject);
23751
- server.on("listening", () => {
23752
- resolve();
23753
- });
23754
- server.listen(port, hostname);
23755
- });
23756
- } catch (error) {
23757
- if (error && error.code === "EADDRINUSE") {
23758
- return true;
23759
- }
23760
- if (error && error.code === "EACCES") {
23761
- return true;
23762
- }
23763
- throw error;
23764
- }
23765
- await new Promise((resolve, reject) => {
23766
- server.on("error", reject);
23767
- server.on("close", resolve);
23768
- server.close();
23769
- });
23770
- return false;
23771
- };
23772
-
23773
23744
  const ensureGlobalGc = () => {
23774
23745
  if (!global.gc) {
23775
23746
  v8.setFlagsFromString("--expose_gc");
@@ -24197,7 +24168,7 @@ const executePlan = async (plan, {
24197
24168
  coverageMethodForBrowsers,
24198
24169
  coverageMethodForNodeJs,
24199
24170
  coverageV8ConflictWarning,
24200
- coverageTempDirectoryRelativeUrl,
24171
+ coverageTempDirectoryUrl,
24201
24172
  beforeExecutionCallback = () => {},
24202
24173
  afterExecutionCallback = () => {}
24203
24174
  } = {}) => {
@@ -24207,30 +24178,6 @@ const executePlan = async (plan, {
24207
24178
  const stopAfterAllSignal = {
24208
24179
  notify: () => {}
24209
24180
  };
24210
- let someNeedsServer = false;
24211
- let someNodeRuntime = false;
24212
- const runtimes = {};
24213
- Object.keys(plan).forEach(filePattern => {
24214
- const filePlan = plan[filePattern];
24215
- Object.keys(filePlan).forEach(executionName => {
24216
- const executionConfig = filePlan[executionName];
24217
- const {
24218
- runtime
24219
- } = executionConfig;
24220
- if (runtime) {
24221
- runtimes[runtime.name] = runtime.version;
24222
- if (runtime.type === "browser") {
24223
- someNeedsServer = true;
24224
- }
24225
- if (runtime.type === "node") {
24226
- someNodeRuntime = true;
24227
- }
24228
- }
24229
- });
24230
- });
24231
- logger.debug(createDetailedMessage$1(`Prepare executing plan`, {
24232
- runtimes: JSON.stringify(runtimes, null, " ")
24233
- }));
24234
24181
  const multipleExecutionsOperation = Abort.startOperation();
24235
24182
  multipleExecutionsOperation.addAbortSignal(signal);
24236
24183
  if (handleSIGINT) {
@@ -24248,19 +24195,6 @@ const executePlan = async (plan, {
24248
24195
  multipleExecutionsOperation.addAbortSignal(failFastAbortController.signal);
24249
24196
  }
24250
24197
  try {
24251
- const coverageTempDirectoryUrl = new URL(coverageTempDirectoryRelativeUrl, rootDirectoryUrl).href;
24252
- if (someNodeRuntime && coverageEnabled && coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
24253
- if (process.env.NODE_V8_COVERAGE) {
24254
- // when runned multiple times, we don't want to keep previous files in this directory
24255
- await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE);
24256
- } else {
24257
- coverageMethodForNodeJs = "Profiler";
24258
- logger.warn(createDetailedMessage$1(`process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`, {
24259
- "suggestion": `set process.env.NODE_V8_COVERAGE`,
24260
- "suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`
24261
- }));
24262
- }
24263
- }
24264
24198
  if (gcBetweenExecutions) {
24265
24199
  ensureGlobalGc();
24266
24200
  }
@@ -24307,15 +24241,6 @@ const executePlan = async (plan, {
24307
24241
  coverageMethodForNodeJs,
24308
24242
  stopAfterAllSignal
24309
24243
  };
24310
- if (someNeedsServer) {
24311
- if (!devServerOrigin) {
24312
- throw new TypeError(`devServerOrigin is required when running tests on browser(s)`);
24313
- }
24314
- const devServerStarted = await pingServer(devServerOrigin);
24315
- if (!devServerStarted) {
24316
- throw new Error(`dev server not started at ${devServerOrigin}. It is required to run tests`);
24317
- }
24318
- }
24319
24244
  logger.debug(`Generate executions`);
24320
24245
  const executionSteps = await getExecutionAsSteps({
24321
24246
  plan,
@@ -24412,7 +24337,7 @@ const executePlan = async (plan, {
24412
24337
  executionResult = await run({
24413
24338
  signal: multipleExecutionsOperation.signal,
24414
24339
  logger,
24415
- allocatedMs: executionParams.allocatedMs,
24340
+ allocatedMs: typeof executionParams.allocatedMs === "function" ? executionParams.allocatedMs(beforeExecutionInfo) : executionParams.allocatedMs,
24416
24341
  keepRunning,
24417
24342
  mirrorConsole: false,
24418
24343
  // file are executed in parallel, log would be a mess to read
@@ -24628,8 +24553,8 @@ const executeInParallel = async ({
24628
24553
  /**
24629
24554
  * Execute a list of files and log how it goes.
24630
24555
  * @param {Object} testPlanParameters
24631
- * @param {string|url} testPlanParameters.rootDirectoryUrl Root directory of the project
24632
- * @param {string|url} [testPlanParameters.serverOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
24556
+ * @param {string|url} testPlanParameters.testDirectoryUrl Directory containing test files
24557
+ * @param {string|url} [testPlanParameters.devServerOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
24633
24558
  * @param {Object} testPlanParameters.testPlan Object associating patterns leading to files to runtimes where they should be executed
24634
24559
  * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
24635
24560
  * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
@@ -24655,7 +24580,8 @@ const executeTestPlan = async ({
24655
24580
  logFileRelativeUrl = ".jsenv/test_plan_debug.txt",
24656
24581
  completedExecutionLogAbbreviation = false,
24657
24582
  completedExecutionLogMerging = false,
24658
- rootDirectoryUrl,
24583
+ testDirectoryUrl,
24584
+ devServerModuleUrl,
24659
24585
  devServerOrigin,
24660
24586
  testPlan,
24661
24587
  updateProcessExitCode = true,
@@ -24672,7 +24598,7 @@ const executeTestPlan = async ({
24672
24598
  gcBetweenExecutions = logMemoryHeapUsage,
24673
24599
  coverageEnabled = process.argv.includes("--coverage"),
24674
24600
  coverageConfig = {
24675
- "./src/": true
24601
+ "./**/*": true
24676
24602
  },
24677
24603
  coverageIncludeMissing = true,
24678
24604
  coverageAndExecutionAllowed = false,
@@ -24680,30 +24606,87 @@ const executeTestPlan = async ({
24680
24606
  coverageMethodForBrowsers = "playwright_api",
24681
24607
  // "istanbul" also accepted
24682
24608
  coverageV8ConflictWarning = true,
24683
- coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
24609
+ coverageTempDirectoryUrl,
24610
+ coverageReportRootDirectoryUrl,
24684
24611
  // skip empty means empty files won't appear in the coverage reports (json and html)
24685
24612
  coverageReportSkipEmpty = false,
24686
24613
  // skip full means file with 100% coverage won't appear in coverage reports (json and html)
24687
24614
  coverageReportSkipFull = false,
24688
24615
  coverageReportTextLog = true,
24689
- coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
24690
- coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
24616
+ coverageReportJson = process.env.CI,
24617
+ coverageReportJsonFileUrl,
24618
+ coverageReportHtml = !process.env.CI,
24619
+ coverageReportHtmlDirectoryUrl,
24691
24620
  ...rest
24692
24621
  }) => {
24622
+ let someNeedsServer = false;
24623
+ let someNodeRuntime = false;
24624
+ let stopDevServerNeeded = false;
24625
+ const runtimes = {};
24693
24626
  // param validation
24694
24627
  {
24695
24628
  const unexpectedParamNames = Object.keys(rest);
24696
24629
  if (unexpectedParamNames.length > 0) {
24697
24630
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
24698
24631
  }
24699
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
24700
- if (!rootDirectoryUrlValidation.valid) {
24701
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
24632
+ testDirectoryUrl = assertAndNormalizeDirectoryUrl(testDirectoryUrl, "testDirectoryUrl");
24633
+ if (!existsSync(new URL(testDirectoryUrl))) {
24634
+ throw new Error(`ENOENT on testDirectoryUrl at ${testDirectoryUrl}`);
24702
24635
  }
24703
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
24704
24636
  if (typeof testPlan !== "object") {
24705
24637
  throw new Error(`testPlan must be an object, got ${testPlan}`);
24706
24638
  }
24639
+ Object.keys(testPlan).forEach(filePattern => {
24640
+ const filePlan = testPlan[filePattern];
24641
+ if (!filePlan) return;
24642
+ Object.keys(filePlan).forEach(executionName => {
24643
+ const executionConfig = filePlan[executionName];
24644
+ const {
24645
+ runtime
24646
+ } = executionConfig;
24647
+ if (runtime) {
24648
+ runtimes[runtime.name] = runtime.version;
24649
+ if (runtime.type === "browser") {
24650
+ someNeedsServer = true;
24651
+ }
24652
+ if (runtime.type === "node") {
24653
+ someNodeRuntime = true;
24654
+ }
24655
+ }
24656
+ });
24657
+ });
24658
+ if (someNeedsServer) {
24659
+ if (!devServerOrigin) {
24660
+ throw new TypeError(`devServerOrigin is required when running tests on browser(s)`);
24661
+ }
24662
+ let devServerStarted = await pingServer(devServerOrigin);
24663
+ if (!devServerStarted) {
24664
+ if (!devServerModuleUrl) {
24665
+ throw new TypeError(`devServerModuleUrl is required when dev server is not started in order to run tests on browser(s)`);
24666
+ }
24667
+ try {
24668
+ process.env.IMPORTED_BY_TEST_PLAN = "1";
24669
+ await import(devServerModuleUrl);
24670
+ delete process.env.IMPORTED_BY_TEST_PLAN;
24671
+ } catch (e) {
24672
+ if (e.code === "MODULE_NOT_FOUND") {
24673
+ throw new Error(`Cannot find file responsible to start dev server at "${devServerModuleUrl}"`);
24674
+ }
24675
+ throw e;
24676
+ }
24677
+ devServerStarted = await pingServer(devServerOrigin);
24678
+ if (!devServerStarted) {
24679
+ throw new Error(`dev server not started after importing "${devServerModuleUrl}", ensure this module file is starting a server at "${devServerOrigin}"`);
24680
+ }
24681
+ stopDevServerNeeded = true;
24682
+ }
24683
+ const {
24684
+ sourceDirectoryUrl
24685
+ } = await basicFetch(`${devServerOrigin}/__server_params__.json`);
24686
+ if (testDirectoryUrl !== sourceDirectoryUrl && !urlIsInsideOf(testDirectoryUrl, sourceDirectoryUrl)) {
24687
+ throw new Error(`testDirectoryUrl must be inside sourceDirectoryUrl when running tests on browser(s)`);
24688
+ }
24689
+ }
24707
24690
  if (coverageEnabled) {
24708
24691
  if (typeof coverageConfig !== "object") {
24709
24692
  throw new TypeError(`coverageConfig must be an object, got ${coverageConfig}`);
@@ -24731,14 +24714,63 @@ const executeTestPlan = async ({
24731
24714
  }));
24732
24715
  }
24733
24716
  }
24717
+ if (coverageReportRootDirectoryUrl === undefined) {
24718
+ coverageReportRootDirectoryUrl = lookupPackageDirectory(testDirectoryUrl);
24719
+ } else {
24720
+ coverageReportRootDirectoryUrl = assertAndNormalizeDirectoryUrl(coverageReportRootDirectoryUrl, "coverageReportRootDirectoryUrl");
24721
+ }
24722
+ if (coverageTempDirectoryUrl === undefined) {
24723
+ coverageTempDirectoryUrl = new URL("./.coverage/tmp/", coverageReportRootDirectoryUrl);
24724
+ } else {
24725
+ coverageTempDirectoryUrl = assertAndNormalizeDirectoryUrl(coverageTempDirectoryUrl, "coverageTempDirectoryUrl");
24726
+ }
24727
+ if (coverageReportJson) {
24728
+ if (coverageReportJsonFileUrl === undefined) {
24729
+ coverageReportJsonFileUrl = new URL("./.coverage/coverage.json", coverageReportRootDirectoryUrl);
24730
+ } else {
24731
+ coverageReportJsonFileUrl = assertAndNormalizeFileUrl(coverageReportJsonFileUrl, "coverageReportJsonFileUrl");
24732
+ }
24733
+ }
24734
+ if (coverageReportHtml) {
24735
+ if (coverageReportHtmlDirectoryUrl === undefined) {
24736
+ coverageReportHtmlDirectoryUrl = new URL("./.coverage/", coverageReportRootDirectoryUrl);
24737
+ } else {
24738
+ coverageReportHtmlDirectoryUrl = assertAndNormalizeDirectoryUrl(coverageReportHtmlDirectoryUrl, "coverageReportHtmlDirectoryUrl");
24739
+ }
24740
+ }
24734
24741
  }
24735
24742
  }
24736
24743
  const logger = createLogger({
24737
24744
  logLevel
24738
24745
  });
24739
- if (Object.keys(coverageConfig).length === 0) {
24740
- logger.warn(`coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`);
24746
+ logger.debug(createDetailedMessage$1(`Prepare executing plan`, {
24747
+ runtimes: JSON.stringify(runtimes, null, " ")
24748
+ }));
24749
+
24750
+ // param normalization
24751
+ {
24752
+ if (coverageEnabled) {
24753
+ if (Object.keys(coverageConfig).length === 0) {
24754
+ logger.warn(`coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`);
24755
+ }
24756
+ if (someNodeRuntime && coverageEnabled && coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
24757
+ if (process.env.NODE_V8_COVERAGE) {
24758
+ // when runned multiple times, we don't want to keep previous files in this directory
24759
+ await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE);
24760
+ } else {
24761
+ coverageMethodForNodeJs = "Profiler";
24762
+ logger.warn(createDetailedMessage$1(`process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`, {
24763
+ "suggestion": `set process.env.NODE_V8_COVERAGE`,
24764
+ "suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`
24765
+ }));
24766
+ }
24767
+ }
24768
+ }
24741
24769
  }
24770
+ testPlan = {
24771
+ ...testPlan,
24772
+ "**/.jsenv/": null
24773
+ };
24742
24774
  const result = await executePlan(testPlan, {
24743
24775
  signal,
24744
24776
  handleSIGINT,
@@ -24752,7 +24784,7 @@ const executeTestPlan = async ({
24752
24784
  logFileRelativeUrl,
24753
24785
  completedExecutionLogMerging,
24754
24786
  completedExecutionLogAbbreviation,
24755
- rootDirectoryUrl,
24787
+ rootDirectoryUrl: testDirectoryUrl,
24756
24788
  devServerOrigin,
24757
24789
  maxExecutionsInParallel,
24758
24790
  defaultMsAllocatedPerExecution,
@@ -24766,8 +24798,17 @@ const executeTestPlan = async ({
24766
24798
  coverageMethodForBrowsers,
24767
24799
  coverageMethodForNodeJs,
24768
24800
  coverageV8ConflictWarning,
24769
- coverageTempDirectoryRelativeUrl
24801
+ coverageTempDirectoryUrl
24770
24802
  });
24803
+ if (stopDevServerNeeded) {
24804
+ // we are expecting ECONNRESET because server will be stopped by the request
24805
+ basicFetch(`${devServerOrigin}/__stop__`).catch(e => {
24806
+ if (e.code === "ECONNRESET") {
24807
+ return;
24808
+ }
24809
+ throw e;
24810
+ });
24811
+ }
24771
24812
  if (updateProcessExitCode && result.planSummary.counters.total !== result.planSummary.counters.completed) {
24772
24813
  process.exitCode = 1;
24773
24814
  }
@@ -24778,26 +24819,21 @@ const executeTestPlan = async ({
24778
24819
  // keep this one first because it does ensureEmptyDirectory
24779
24820
  // and in case coverage json file gets written in the same directory
24780
24821
  // it must be done before
24781
- if (coverageEnabled && coverageReportHtmlDirectory) {
24782
- const coverageHtmlDirectoryUrl = resolveDirectoryUrl(coverageReportHtmlDirectory, rootDirectoryUrl);
24783
- if (!urlIsInsideOf(coverageHtmlDirectoryUrl, rootDirectoryUrl)) {
24784
- throw new Error(`coverageReportHtmlDirectory must be inside rootDirectoryUrl`);
24785
- }
24786
- await ensureEmptyDirectory(coverageHtmlDirectoryUrl);
24787
- const htmlCoverageDirectoryIndexFileUrl = `${coverageHtmlDirectoryUrl}index.html`;
24822
+ if (coverageEnabled && coverageReportHtml) {
24823
+ await ensureEmptyDirectory(coverageReportHtmlDirectoryUrl);
24824
+ const htmlCoverageDirectoryIndexFileUrl = `${coverageReportHtmlDirectoryUrl}index.html`;
24788
24825
  logger.info(`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`);
24789
24826
  promises.push(generateCoverageHtmlDirectory(planCoverage, {
24790
- rootDirectoryUrl,
24791
- coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(coverageHtmlDirectoryUrl, rootDirectoryUrl),
24827
+ rootDirectoryUrl: coverageReportRootDirectoryUrl,
24828
+ coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(coverageReportHtmlDirectoryUrl, coverageReportRootDirectoryUrl),
24792
24829
  coverageReportSkipEmpty,
24793
24830
  coverageReportSkipFull
24794
24831
  }));
24795
24832
  }
24796
- if (coverageEnabled && coverageReportJsonFile) {
24797
- const coverageJsonFileUrl = new URL(coverageReportJsonFile, rootDirectoryUrl).href;
24833
+ if (coverageEnabled && coverageReportJson) {
24798
24834
  promises.push(generateCoverageJsonFile({
24799
24835
  coverage: result.planCoverage,
24800
- coverageJsonFileUrl,
24836
+ coverageJsonFileUrl: coverageReportJsonFileUrl,
24801
24837
  logger
24802
24838
  }));
24803
24839
  }
@@ -25661,7 +25697,6 @@ nodeChildProcess.run = async ({
25661
25697
  env: envForChildProcess
25662
25698
  });
25663
25699
  logger.debug(createDetailedMessage$1(`child process forked (pid ${childProcess.pid})`, {
25664
- "execArgv": execArgv.join(`\n`),
25665
25700
  "custom env": JSON.stringify(env, null, " ")
25666
25701
  }));
25667
25702
  // if we pass stream, pipe them https://github.com/sindresorhus/execa/issues/81
@@ -26184,32 +26219,23 @@ const onceWorkerThreadEvent = (worker, type, callback) => {
26184
26219
  /**
26185
26220
  * Start a server for build files.
26186
26221
  * @param {Object} buildServerParameters
26187
- * @param {string|url} buildServerParameters.rootDirectoryUrl Root directory of the project
26188
26222
  * @param {string|url} buildServerParameters.buildDirectoryUrl Directory where build files are written
26189
26223
  * @return {Object} A build server object
26190
26224
  */
26191
26225
  const startBuildServer = async ({
26192
- signal = new AbortController().signal,
26193
- handleSIGINT = true,
26194
- logLevel,
26195
- serverLogLevel = "warn",
26196
- https,
26197
- http2,
26198
- acceptAnyIp,
26199
- hostname,
26226
+ buildDirectoryUrl,
26227
+ buildMainFilePath = "index.html",
26200
26228
  port = 9779,
26201
26229
  services = [],
26230
+ acceptAnyIp,
26231
+ hostname,
26232
+ https,
26233
+ http2,
26234
+ logLevel,
26235
+ serverLogLevel = "warn",
26236
+ signal = new AbortController().signal,
26237
+ handleSIGINT = true,
26202
26238
  keepProcessAlive = true,
26203
- rootDirectoryUrl,
26204
- buildDirectoryUrl,
26205
- buildIndexPath = "index.html",
26206
- buildServerFiles = {
26207
- "./package.json": true,
26208
- "./jsenv.config.mjs": true
26209
- },
26210
- buildServerAutoreload = false,
26211
- buildServerMainFile = getCallerPosition().url,
26212
- cooldownBetweenFileEvents,
26213
26239
  ...rest
26214
26240
  }) => {
26215
26241
  // params validation
@@ -26218,31 +26244,22 @@ const startBuildServer = async ({
26218
26244
  if (unexpectedParamNames.length > 0) {
26219
26245
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
26220
26246
  }
26221
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
26222
- if (!rootDirectoryUrlValidation.valid) {
26223
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
26224
- }
26225
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
26226
- const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl);
26227
- if (!buildDirectoryUrlValidation.valid) {
26228
- throw new TypeError(`buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`);
26229
- }
26230
- buildDirectoryUrl = buildDirectoryUrlValidation.value;
26231
- if (buildIndexPath) {
26232
- if (typeof buildIndexPath !== "string") {
26233
- throw new TypeError(`buildIndexPath must be a string, got ${buildIndexPath}`);
26247
+ buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl, "buildDirectoryUrl");
26248
+ if (buildMainFilePath) {
26249
+ if (typeof buildMainFilePath !== "string") {
26250
+ throw new TypeError(`buildMainFilePath must be a string, got ${buildMainFilePath}`);
26234
26251
  }
26235
- if (buildIndexPath[0] === "/") {
26236
- buildIndexPath = buildIndexPath.slice(1);
26252
+ if (buildMainFilePath[0] === "/") {
26253
+ buildMainFilePath = buildMainFilePath.slice(1);
26237
26254
  } else {
26238
- const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href;
26239
- if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
26240
- throw new Error(`buildIndexPath must be relative, got ${buildIndexPath}`);
26255
+ const buildMainFileUrl = new URL(buildMainFilePath, buildDirectoryUrl).href;
26256
+ if (!buildMainFileUrl.startsWith(buildDirectoryUrl)) {
26257
+ throw new Error(`buildMainFilePath must be relative, got ${buildMainFilePath}`);
26241
26258
  }
26242
- buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length);
26259
+ buildMainFilePath = buildMainFileUrl.slice(buildDirectoryUrl.length);
26243
26260
  }
26244
- if (!existsSync(new URL(buildIndexPath, buildDirectoryUrl))) {
26245
- buildIndexPath = null;
26261
+ if (!existsSync(new URL(buildMainFilePath, buildDirectoryUrl))) {
26262
+ buildMainFilePath = null;
26246
26263
  }
26247
26264
  }
26248
26265
  }
@@ -26258,73 +26275,6 @@ const startBuildServer = async ({
26258
26275
  }, abort);
26259
26276
  });
26260
26277
  }
26261
- let reloadableWorker;
26262
- if (buildServerAutoreload) {
26263
- reloadableWorker = createReloadableWorker(buildServerMainFile);
26264
- if (reloadableWorker.isPrimary) {
26265
- const buildServerFileChangeCallback = ({
26266
- relativeUrl,
26267
- event
26268
- }) => {
26269
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
26270
- logger.info(`file ${event} ${url} -> restarting server...`);
26271
- reloadableWorker.reload();
26272
- };
26273
- const stopWatchingBuildServerFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
26274
- watchPatterns: {
26275
- ...buildServerFiles,
26276
- [buildServerMainFile]: true,
26277
- ".jsenv/": false
26278
- },
26279
- cooldownBetweenFileEvents,
26280
- keepProcessAlive: false,
26281
- recursive: true,
26282
- added: ({
26283
- relativeUrl
26284
- }) => {
26285
- buildServerFileChangeCallback({
26286
- relativeUrl,
26287
- event: "added"
26288
- });
26289
- },
26290
- updated: ({
26291
- relativeUrl
26292
- }) => {
26293
- buildServerFileChangeCallback({
26294
- relativeUrl,
26295
- event: "modified"
26296
- });
26297
- },
26298
- removed: ({
26299
- relativeUrl
26300
- }) => {
26301
- buildServerFileChangeCallback({
26302
- relativeUrl,
26303
- event: "removed"
26304
- });
26305
- }
26306
- });
26307
- operation.addAbortCallback(() => {
26308
- stopWatchingBuildServerFiles();
26309
- reloadableWorker.terminate();
26310
- });
26311
- const worker = await reloadableWorker.load();
26312
- const messagePromise = new Promise(resolve => {
26313
- worker.once("message", resolve);
26314
- });
26315
- const origin = await messagePromise;
26316
- // if (!keepProcessAlive) {
26317
- // worker.unref()
26318
- // }
26319
- return {
26320
- origin,
26321
- stop: () => {
26322
- stopWatchingBuildServerFiles();
26323
- reloadableWorker.terminate();
26324
- }
26325
- };
26326
- }
26327
- }
26328
26278
  const startBuildServerTask = createTaskLog("start build server", {
26329
26279
  disabled: !logger.levels.info
26330
26280
  });
@@ -26355,7 +26305,7 @@ const startBuildServer = async ({
26355
26305
  name: "jsenv:build_files_service",
26356
26306
  handleRequest: createBuildFilesService({
26357
26307
  buildDirectoryUrl,
26358
- buildIndexPath
26308
+ buildMainFilePath
26359
26309
  })
26360
26310
  }, jsenvServiceErrorHandler({
26361
26311
  sendErrorDetails: true
@@ -26371,9 +26321,6 @@ const startBuildServer = async ({
26371
26321
  logger.info(`- ${server.origins[key]}`);
26372
26322
  });
26373
26323
  logger.info(``);
26374
- if (reloadableWorker && reloadableWorker.isWorker) {
26375
- parentPort.postMessage(server.origin);
26376
- }
26377
26324
  return {
26378
26325
  origin: server.origin,
26379
26326
  stop: () => {
@@ -26383,14 +26330,14 @@ const startBuildServer = async ({
26383
26330
  };
26384
26331
  const createBuildFilesService = ({
26385
26332
  buildDirectoryUrl,
26386
- buildIndexPath
26333
+ buildMainFilePath
26387
26334
  }) => {
26388
26335
  return request => {
26389
26336
  const urlIsVersioned = new URL(request.url).searchParams.has("v");
26390
- if (buildIndexPath && request.resource === "/") {
26337
+ if (buildMainFilePath && request.resource === "/") {
26391
26338
  request = {
26392
26339
  ...request,
26393
- resource: `/${buildIndexPath}`
26340
+ resource: `/${buildMainFilePath}`
26394
26341
  };
26395
26342
  }
26396
26343
  return fetchFileSystem(new URL(request.resource.slice(1), buildDirectoryUrl), {
@@ -26437,7 +26384,7 @@ const execute = async ({
26437
26384
  const logger = createLogger({
26438
26385
  logLevel
26439
26386
  });
26440
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
26387
+ rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl, "rootDirectoryUrl");
26441
26388
  const executeOperation = Abort.startOperation();
26442
26389
  executeOperation.addAbortSignal(signal);
26443
26390
  if (handleSIGINT) {