@jsenv/core 31.2.0 → 32.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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,109 @@ 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
+ // line below is commented until @jsenv/url-meta fixes the fact that is matches
7122
+ // any file with an extension
7123
+ // "**/.*": false, // file starting with a dot -> do not watch
7124
+ "**/.*/": false,
7125
+ // directory starting with a dot -> do not watch
7126
+ "**/node_modules/": false,
7127
+ // node_modules directory -> do not watch
7128
+ ...sourceFileConfig
7129
+ };
7130
+ const stopWatchingSourceFiles = registerDirectoryLifecycle(sourceDirectoryUrl, {
7131
+ watchPatterns,
7132
+ cooldownBetweenFileEvents,
7133
+ keepProcessAlive,
7134
+ recursive: true,
7135
+ added: ({
7136
+ relativeUrl
7137
+ }) => {
7138
+ callback({
7139
+ url: new URL(relativeUrl, sourceDirectoryUrl).href,
7140
+ event: "added"
7141
+ });
7142
+ },
7143
+ updated: ({
7144
+ relativeUrl
7145
+ }) => {
7146
+ callback({
7147
+ url: new URL(relativeUrl, sourceDirectoryUrl).href,
7148
+ event: "modified"
7149
+ });
7150
+ },
7151
+ removed: ({
7152
+ relativeUrl
7153
+ }) => {
7154
+ callback({
7155
+ url: new URL(relativeUrl, sourceDirectoryUrl).href,
7156
+ event: "removed"
7157
+ });
7158
+ }
7159
+ });
7160
+ stopWatchingSourceFiles.watchPatterns = watchPatterns;
7161
+ return stopWatchingSourceFiles;
7162
+ };
7163
+
7066
7164
  const urlSpecifierEncoding = {
7067
7165
  encode: reference => {
7068
7166
  const {
@@ -8653,6 +8751,7 @@ const createKitchen = ({
8653
8751
  signal,
8654
8752
  logLevel,
8655
8753
  rootDirectoryUrl,
8754
+ jsenvInternalDirectoryUrl,
8656
8755
  urlGraph,
8657
8756
  dev = false,
8658
8757
  build = false,
@@ -8678,6 +8777,7 @@ const createKitchen = ({
8678
8777
  signal,
8679
8778
  logger,
8680
8779
  rootDirectoryUrl,
8780
+ jsenvInternalDirectoryUrl,
8681
8781
  urlGraph,
8682
8782
  dev,
8683
8783
  build,
@@ -17063,7 +17163,7 @@ const addRelationshipWithPackageJson = ({
17063
17163
  */
17064
17164
  const jsenvPluginUrlResolution = ({
17065
17165
  runtimeCompat,
17066
- clientMainFileUrl,
17166
+ mainFileUrl,
17067
17167
  urlResolution
17068
17168
  }) => {
17069
17169
  const resolveUrlUsingWebResolution = reference => {
@@ -17124,7 +17224,7 @@ const jsenvPluginUrlResolution = ({
17124
17224
  appliesDuring: "*",
17125
17225
  resolveUrl: (reference, context) => {
17126
17226
  if (reference.specifier === "/") {
17127
- return String(clientMainFileUrl);
17227
+ return String(mainFileUrl);
17128
17228
  }
17129
17229
  if (reference.specifier[0] === "/") {
17130
17230
  return new URL(reference.specifier.slice(1), context.rootDirectoryUrl).href;
@@ -20192,13 +20292,14 @@ const explorerHtmlFileUrl = new URL("./html/explorer.html", import.meta.url);
20192
20292
  const jsenvPluginExplorer = ({
20193
20293
  groups = {
20194
20294
  src: {
20195
- "./src/**/*.html": true
20295
+ "./**/*.html": true,
20296
+ "./**/*.test.html": false
20196
20297
  },
20197
20298
  tests: {
20198
- "./tests/**/*.test.html": true
20299
+ "./**/*.test.html": true
20199
20300
  }
20200
20301
  },
20201
- clientMainFileUrl
20302
+ mainFileUrl
20202
20303
  }) => {
20203
20304
  const faviconClientFileUrl = new URL("./other/jsenv.png", import.meta.url);
20204
20305
  return {
@@ -20206,7 +20307,7 @@ const jsenvPluginExplorer = ({
20206
20307
  appliesDuring: "dev",
20207
20308
  transformUrlContent: {
20208
20309
  html: async (urlInfo, context) => {
20209
- if (urlInfo.url !== clientMainFileUrl) {
20310
+ if (urlInfo.url !== mainFileUrl) {
20210
20311
  return null;
20211
20312
  }
20212
20313
  let html = urlInfo.content;
@@ -20308,6 +20409,7 @@ injectRibbon(${paramsJson})`
20308
20409
 
20309
20410
  const getCorePlugins = ({
20310
20411
  rootDirectoryUrl,
20412
+ mainFileUrl,
20311
20413
  runtimeCompat,
20312
20414
  urlAnalysis = {},
20313
20415
  urlResolution = {},
@@ -20315,7 +20417,6 @@ const getCorePlugins = ({
20315
20417
  directoryReferenceAllowed,
20316
20418
  supervisor,
20317
20419
  transpilation = true,
20318
- clientMainFileUrl,
20319
20420
  clientAutoreload = false,
20320
20421
  clientFileChangeCallbackList,
20321
20422
  clientFilesPruneCallbackList,
@@ -20339,11 +20440,6 @@ const getCorePlugins = ({
20339
20440
  if (clientAutoreload === true) {
20340
20441
  clientAutoreload = {};
20341
20442
  }
20342
- if (clientMainFileUrl === undefined) {
20343
- clientMainFileUrl = explorer ? String(explorerHtmlFileUrl) : String(new URL("./index.html", rootDirectoryUrl));
20344
- } else {
20345
- clientMainFileUrl = String(clientMainFileUrl);
20346
- }
20347
20443
  if (ribbon === true) {
20348
20444
  ribbon = {};
20349
20445
  }
@@ -20362,7 +20458,7 @@ const getCorePlugins = ({
20362
20458
  ...fileSystemMagicRedirection
20363
20459
  }), jsenvPluginHttpUrls(), jsenvPluginUrlResolution({
20364
20460
  runtimeCompat,
20365
- clientMainFileUrl,
20461
+ mainFileUrl,
20366
20462
  urlResolution
20367
20463
  }), jsenvPluginUrlVersion(), jsenvPluginCommonJsGlobals(), jsenvPluginImportMetaScenarios(), ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []), jsenvPluginNodeRuntime({
20368
20464
  runtimeCompat
@@ -20372,7 +20468,7 @@ const getCorePlugins = ({
20372
20468
  clientFilesPruneCallbackList
20373
20469
  })] : []), ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []), ...(explorer ? [jsenvPluginExplorer({
20374
20470
  ...explorer,
20375
- clientMainFileUrl
20471
+ mainFileUrl
20376
20472
  })] : []), ...(ribbon ? [jsenvPluginRibbon({
20377
20473
  rootDirectoryUrl,
20378
20474
  ...ribbon
@@ -20731,12 +20827,13 @@ const defaultRuntimeCompat = {
20731
20827
  /**
20732
20828
  * Generate an optimized version of source files into a directory
20733
20829
  * @param {Object} buildParameters
20734
- * @param {string|url} buildParameters.rootDirectoryUrl
20830
+ * @param {string|url} buildParameters.sourceDirectoryUrl
20735
20831
  * Directory containing source files
20832
+ * @param {object} buildParameters.entryPoints
20833
+ * Object where keys are paths to source files and values are their future name in the build directory.
20834
+ * Keys are relative to sourceDirectoryUrl
20736
20835
  * @param {string|url} buildParameters.buildDirectoryUrl
20737
20836
  * Directory where optimized files will be written
20738
- * @param {object} buildParameters.entryPoints
20739
- * Describe entry point paths and control their names in the build directory
20740
20837
  * @param {object} buildParameters.runtimeCompat
20741
20838
  * Code generated will be compatible with these runtimes
20742
20839
  * @param {string} [buildParameters.assetsDirectory=""]
@@ -20761,10 +20858,10 @@ const build = async ({
20761
20858
  signal = new AbortController().signal,
20762
20859
  handleSIGINT = true,
20763
20860
  logLevel = "info",
20764
- rootDirectoryUrl,
20861
+ sourceDirectoryUrl,
20862
+ entryPoints = {},
20765
20863
  buildDirectoryUrl,
20766
20864
  assetsDirectory = "",
20767
- entryPoints = {},
20768
20865
  runtimeCompat = defaultRuntimeCompat,
20769
20866
  base = runtimeCompat.node ? "./" : "/",
20770
20867
  plugins = [],
@@ -20781,9 +20878,7 @@ const build = async ({
20781
20878
  // "filename", "search_param"
20782
20879
  versioningViaImportmap = true,
20783
20880
  lineBreakNormalization = process.platform === "win32",
20784
- clientFiles = {
20785
- "./src/": true
20786
- },
20881
+ sourceFilesConfig = {},
20787
20882
  cooldownBetweenFileEvents,
20788
20883
  watch = false,
20789
20884
  directoryToClean,
@@ -20799,16 +20894,27 @@ const build = async ({
20799
20894
  if (unexpectedParamNames.length > 0) {
20800
20895
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
20801
20896
  }
20802
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
20803
- if (!rootDirectoryUrlValidation.valid) {
20804
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
20897
+ sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(sourceDirectoryUrl, "sourceDirectoryUrl");
20898
+ if (typeof entryPoints !== "object" || entryPoints === null) {
20899
+ throw new TypeError(`entryPoints must be an object, got ${entryPoints}`);
20805
20900
  }
20806
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
20807
- const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl);
20808
- if (!buildDirectoryUrlValidation.valid) {
20809
- throw new TypeError(`buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`);
20901
+ const keys = Object.keys(entryPoints);
20902
+ keys.forEach(key => {
20903
+ if (!key.startsWith("./")) {
20904
+ throw new TypeError(`entryPoints keys must start with "./", found ${key}`);
20905
+ }
20906
+ const value = entryPoints[key];
20907
+ if (typeof value !== "string") {
20908
+ throw new TypeError(`entryPoints values must be strings, found "${value}" on key "${key}"`);
20909
+ }
20910
+ if (value.includes("/")) {
20911
+ throw new TypeError(`entryPoints values must be plain strings (no "/"), found "${value}" on key "${key}"`);
20912
+ }
20913
+ });
20914
+ buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl, "buildDirectoryUrl");
20915
+ if (!["filename", "search_param"].includes(versioningMethod)) {
20916
+ throw new TypeError(`versioningMethod must be "filename" or "search_param", got ${versioning}`);
20810
20917
  }
20811
- buildDirectoryUrl = buildDirectoryUrlValidation.value;
20812
20918
  }
20813
20919
  const operation = Abort.startOperation();
20814
20920
  operation.addAbortSignal(signal);
@@ -20819,12 +20925,6 @@ const build = async ({
20819
20925
  }, abort);
20820
20926
  });
20821
20927
  }
20822
- assertEntryPoints({
20823
- entryPoints
20824
- });
20825
- if (!["filename", "search_param"].includes(versioningMethod)) {
20826
- throw new Error(`Unexpected "versioningMethod": must be "filename", "search_param"; got ${versioning}`);
20827
- }
20828
20928
  if (assetsDirectory && assetsDirectory[assetsDirectory.length - 1] !== "/") {
20829
20929
  assetsDirectory = `${assetsDirectory}/`;
20830
20930
  }
@@ -20835,9 +20935,10 @@ const build = async ({
20835
20935
  directoryToClean = new URL(assetsDirectory, buildDirectoryUrl).href;
20836
20936
  }
20837
20937
  }
20938
+ const jsenvInternalDirectoryUrl = determineJsenvInternalDirectoryUrl(sourceDirectoryUrl);
20838
20939
  const asFormattedBuildUrl = (generatedUrl, reference) => {
20839
20940
  if (base === "./") {
20840
- const urlRelativeToParent = urlToRelativeUrl(generatedUrl, reference.parentUrl === rootDirectoryUrl ? buildDirectoryUrl : reference.parentUrl);
20941
+ const urlRelativeToParent = urlToRelativeUrl(generatedUrl, reference.parentUrl === sourceDirectoryUrl ? buildDirectoryUrl : reference.parentUrl);
20841
20942
  if (urlRelativeToParent[0] !== ".") {
20842
20943
  // ensure "./" on relative url (otherwise it could be a "bare specifier")
20843
20944
  return `./${urlRelativeToParent}`;
@@ -20887,7 +20988,8 @@ build ${entryPointKeys.length} entry points`);
20887
20988
  const rawGraphKitchen = createKitchen({
20888
20989
  signal,
20889
20990
  logLevel,
20890
- rootDirectoryUrl,
20991
+ rootDirectoryUrl: sourceDirectoryUrl,
20992
+ jsenvInternalDirectoryUrl,
20891
20993
  urlGraph: rawGraph,
20892
20994
  build: true,
20893
20995
  runtimeCompat,
@@ -20906,7 +21008,7 @@ build ${entryPointKeys.length} entry points`);
20906
21008
  return null;
20907
21009
  }
20908
21010
  }, ...getCorePlugins({
20909
- rootDirectoryUrl,
21011
+ rootDirectoryUrl: sourceDirectoryUrl,
20910
21012
  urlGraph: rawGraph,
20911
21013
  runtimeCompat,
20912
21014
  urlAnalysis,
@@ -20923,7 +21025,7 @@ build ${entryPointKeys.length} entry points`);
20923
21025
  sourcemaps,
20924
21026
  sourcemapsSourcesContent,
20925
21027
  writeGeneratedFiles,
20926
- outDirectoryUrl: new URL(`.jsenv/build/`, rootDirectoryUrl)
21028
+ outDirectoryUrl: new URL("build/", jsenvInternalDirectoryUrl)
20927
21029
  });
20928
21030
  const buildUrlsGenerator = createBuildUrlsGenerator({
20929
21031
  buildDirectoryUrl,
@@ -20945,12 +21047,13 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20945
21047
  const bundlers = {};
20946
21048
  const finalGraph = createUrlGraph();
20947
21049
  const urlAnalysisPlugin = jsenvPluginUrlAnalysis({
20948
- rootDirectoryUrl,
21050
+ rootDirectoryUrl: sourceDirectoryUrl,
20949
21051
  ...urlAnalysis
20950
21052
  });
20951
21053
  const finalGraphKitchen = createKitchen({
20952
21054
  logLevel,
20953
21055
  rootDirectoryUrl: buildDirectoryUrl,
21056
+ jsenvInternalDirectoryUrl,
20954
21057
  urlGraph: finalGraph,
20955
21058
  build: true,
20956
21059
  runtimeCompat,
@@ -21197,7 +21300,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21197
21300
  sourcemapsSourcesContent,
21198
21301
  sourcemapsSourcesRelative: !versioning,
21199
21302
  writeGeneratedFiles,
21200
- outDirectoryUrl: new URL(".jsenv/postbuild/", rootDirectoryUrl)
21303
+ outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl)
21201
21304
  });
21202
21305
  const finalEntryUrls = [];
21203
21306
  {
@@ -21206,7 +21309,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21206
21309
  });
21207
21310
  try {
21208
21311
  if (writeGeneratedFiles) {
21209
- await ensureEmptyDirectory(new URL(`.jsenv/build/`, rootDirectoryUrl));
21312
+ await ensureEmptyDirectory(new URL(`build/`, sourceDirectoryUrl));
21210
21313
  }
21211
21314
  const rawUrlGraphLoader = createUrlGraphLoader(rawGraphKitchen.kitchenContext);
21212
21315
  Object.keys(entryPoints).forEach(key => {
@@ -21214,7 +21317,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21214
21317
  trace: {
21215
21318
  message: `"${key}" in entryPoints parameter`
21216
21319
  },
21217
- parentUrl: rootDirectoryUrl,
21320
+ parentUrl: sourceDirectoryUrl,
21218
21321
  type: "entry_point",
21219
21322
  specifier: key
21220
21323
  });
@@ -21420,7 +21523,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21420
21523
  });
21421
21524
  try {
21422
21525
  if (writeGeneratedFiles) {
21423
- await ensureEmptyDirectory(new URL(`.jsenv/postbuild/`, rootDirectoryUrl));
21526
+ await ensureEmptyDirectory(new URL(`postbuild/`, jsenvInternalDirectoryUrl));
21424
21527
  }
21425
21528
  const finalUrlGraphLoader = createUrlGraphLoader(finalGraphKitchen.kitchenContext);
21426
21529
  entryUrls.forEach(entryUrl => {
@@ -21428,7 +21531,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21428
21531
  trace: {
21429
21532
  message: `entryPoint`
21430
21533
  },
21431
- parentUrl: rootDirectoryUrl,
21534
+ parentUrl: sourceDirectoryUrl,
21432
21535
  type: "entry_point",
21433
21536
  specifier: entryUrl
21434
21537
  });
@@ -21620,6 +21723,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21620
21723
  const versioningKitchen = createKitchen({
21621
21724
  logLevel: logger.level,
21622
21725
  rootDirectoryUrl: buildDirectoryUrl,
21726
+ jsenvInternalDirectoryUrl,
21623
21727
  urlGraph: finalGraph,
21624
21728
  build: true,
21625
21729
  runtimeCompat,
@@ -21712,7 +21816,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21712
21816
  sourcemapsSourcesContent,
21713
21817
  sourcemapsSourcesRelative: true,
21714
21818
  writeGeneratedFiles,
21715
- outDirectoryUrl: new URL(".jsenv/postbuild/", finalGraphKitchen.rootDirectoryUrl)
21819
+ outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl)
21716
21820
  });
21717
21821
  const versioningUrlGraphLoader = createUrlGraphLoader(versioningKitchen.kitchenContext);
21718
21822
  finalEntryUrls.forEach(finalEntryUrl => {
@@ -22088,13 +22192,12 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22088
22192
  };
22089
22193
  startBuild();
22090
22194
  let startTimeout;
22091
- const clientFileChangeCallback = ({
22092
- relativeUrl,
22195
+ const stopWatchingSourceFiles = watchSourceFiles(sourceDirectoryUrl, ({
22196
+ url,
22093
22197
  event
22094
22198
  }) => {
22095
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
22096
22199
  if (watchFilesTask) {
22097
- watchFilesTask.happen(`${url.slice(rootDirectoryUrl.length)} ${event}`);
22200
+ watchFilesTask.happen(`${url.slice(sourceDirectoryUrl.length)} ${event}`);
22098
22201
  watchFilesTask = null;
22099
22202
  }
22100
22203
  buildAbortController.abort();
@@ -22103,42 +22206,16 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22103
22206
  // then logs about re-running the build happens
22104
22207
  clearTimeout(startTimeout);
22105
22208
  startTimeout = setTimeout(startBuild, 20);
22106
- };
22107
- const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22108
- watchPatterns: clientFiles,
22109
- cooldownBetweenFileEvents,
22209
+ }, {
22210
+ sourceFilesConfig,
22110
22211
  keepProcessAlive: true,
22111
- recursive: true,
22112
- added: ({
22113
- relativeUrl
22114
- }) => {
22115
- clientFileChangeCallback({
22116
- relativeUrl,
22117
- event: "added"
22118
- });
22119
- },
22120
- updated: ({
22121
- relativeUrl
22122
- }) => {
22123
- clientFileChangeCallback({
22124
- relativeUrl,
22125
- event: "modified"
22126
- });
22127
- },
22128
- removed: ({
22129
- relativeUrl
22130
- }) => {
22131
- clientFileChangeCallback({
22132
- relativeUrl,
22133
- event: "removed"
22134
- });
22135
- }
22212
+ cooldownBetweenFileEvents
22136
22213
  });
22137
22214
  operation.addAbortCallback(() => {
22138
- stopWatchingClientFiles();
22215
+ stopWatchingSourceFiles();
22139
22216
  });
22140
22217
  await firstBuildPromise;
22141
- return stopWatchingClientFiles;
22218
+ return stopWatchingSourceFiles;
22142
22219
  };
22143
22220
  const findKey = (map, value) => {
22144
22221
  for (const [keyCandidate, valueCandidate] of map) {
@@ -22164,26 +22241,6 @@ const injectVersionIntoBuildUrl = ({
22164
22241
  const versionedUrl = setUrlFilename(buildUrl, versionedFilename);
22165
22242
  return versionedUrl;
22166
22243
  };
22167
- const assertEntryPoints = ({
22168
- entryPoints
22169
- }) => {
22170
- if (typeof entryPoints !== "object" || entryPoints === null) {
22171
- throw new TypeError(`entryPoints must be an object, got ${entryPoints}`);
22172
- }
22173
- const keys = Object.keys(entryPoints);
22174
- keys.forEach(key => {
22175
- if (!key.startsWith("./")) {
22176
- throw new TypeError(`unexpected key in entryPoints, all keys must start with ./ but found ${key}`);
22177
- }
22178
- const value = entryPoints[key];
22179
- if (typeof value !== "string") {
22180
- throw new TypeError(`unexpected value in entryPoints, all values must be strings found ${value} for key ${key}`);
22181
- }
22182
- if (value.includes("/")) {
22183
- throw new TypeError(`unexpected value in entryPoints, all values must be plain strings (no "/") but found ${value} for key ${key}`);
22184
- }
22185
- });
22186
- };
22187
22244
  const isUsed = urlInfo => {
22188
22245
  // nothing uses this url anymore
22189
22246
  // - versioning update inline content
@@ -22206,57 +22263,6 @@ const canUseVersionedUrl = urlInfo => {
22206
22263
  return urlInfo.type !== "webmanifest";
22207
22264
  };
22208
22265
 
22209
- // https://nodejs.org/api/worker_threads.html
22210
- const createReloadableWorker = (workerFileUrl, options = {}) => {
22211
- const workerFilePath = fileURLToPath(workerFileUrl);
22212
- const isPrimary = !workerData || workerData.workerFilePath !== workerFilePath;
22213
- let worker;
22214
- const terminate = async () => {
22215
- if (worker) {
22216
- let _worker = worker;
22217
- worker = null;
22218
- const exitPromise = new Promise(resolve => {
22219
- _worker.once("exit", resolve);
22220
- });
22221
- _worker.terminate();
22222
- await exitPromise;
22223
- }
22224
- };
22225
- const load = async () => {
22226
- if (!isPrimary) {
22227
- throw new Error(`worker can be loaded from primary file only`);
22228
- }
22229
- worker = new Worker(workerFilePath, {
22230
- ...options,
22231
- workerData: {
22232
- ...options.workerData,
22233
- workerFilePath
22234
- }
22235
- });
22236
- worker.once("error", error => {
22237
- console.error(error);
22238
- });
22239
- worker.once("exit", () => {
22240
- worker = null;
22241
- });
22242
- await new Promise(resolve => {
22243
- worker.once("online", resolve);
22244
- });
22245
- return worker;
22246
- };
22247
- const reload = async () => {
22248
- await terminate();
22249
- await load();
22250
- };
22251
- return {
22252
- isPrimary,
22253
- isWorker: !isPrimary,
22254
- load,
22255
- reload,
22256
- terminate
22257
- };
22258
- };
22259
-
22260
22266
  /*
22261
22267
  * This plugin is very special because it is here
22262
22268
  * to provide "serverEvents" used by other plugins
@@ -22320,7 +22326,9 @@ const createFileService = ({
22320
22326
  serverStopCallbacks,
22321
22327
  serverEventsDispatcher,
22322
22328
  contextCache,
22323
- rootDirectoryUrl,
22329
+ sourceDirectoryUrl,
22330
+ sourceMainFilePath,
22331
+ sourceFilesConfig,
22324
22332
  runtimeCompat,
22325
22333
  plugins,
22326
22334
  urlAnalysis,
@@ -22329,8 +22337,6 @@ const createFileService = ({
22329
22337
  supervisor,
22330
22338
  transpilation,
22331
22339
  clientAutoreload,
22332
- clientFiles,
22333
- clientMainFileUrl,
22334
22340
  cooldownBetweenFileEvents,
22335
22341
  explorer,
22336
22342
  cacheControl,
@@ -22340,49 +22346,18 @@ const createFileService = ({
22340
22346
  sourcemapsSourcesContent,
22341
22347
  writeGeneratedFiles
22342
22348
  }) => {
22343
- const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href;
22344
22349
  const clientFileChangeCallbackList = [];
22345
22350
  const clientFilesPruneCallbackList = [];
22346
- const clientFilePatterns = {
22347
- ...clientFiles,
22348
- ".jsenv/": false
22349
- };
22350
- const onFileChange = url => {
22351
+ const stopWatchingSourceFiles = watchSourceFiles(sourceDirectoryUrl, fileInfo => {
22351
22352
  clientFileChangeCallbackList.forEach(callback => {
22352
- callback(url);
22353
+ callback(fileInfo);
22353
22354
  });
22354
- };
22355
- const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22356
- watchPatterns: clientFilePatterns,
22357
- cooldownBetweenFileEvents,
22355
+ }, {
22356
+ sourceFilesConfig,
22358
22357
  keepProcessAlive: false,
22359
- recursive: true,
22360
- added: ({
22361
- relativeUrl
22362
- }) => {
22363
- onFileChange({
22364
- url: new URL(relativeUrl, rootDirectoryUrl).href,
22365
- event: "added"
22366
- });
22367
- },
22368
- updated: ({
22369
- relativeUrl
22370
- }) => {
22371
- onFileChange({
22372
- url: new URL(relativeUrl, rootDirectoryUrl).href,
22373
- event: "modified"
22374
- });
22375
- },
22376
- removed: ({
22377
- relativeUrl
22378
- }) => {
22379
- onFileChange({
22380
- url: new URL(relativeUrl, rootDirectoryUrl).href,
22381
- event: "removed"
22382
- });
22383
- }
22358
+ cooldownBetweenFileEvents
22384
22359
  });
22385
- serverStopCallbacks.push(stopWatchingClientFiles);
22360
+ serverStopCallbacks.push(stopWatchingSourceFiles);
22386
22361
  const getOrCreateContext = request => {
22387
22362
  const {
22388
22363
  runtimeName,
@@ -22394,8 +22369,8 @@ const createFileService = ({
22394
22369
  return existingContext;
22395
22370
  }
22396
22371
  const watchAssociations = URL_META.resolveAssociations({
22397
- watch: clientFilePatterns
22398
- }, rootDirectoryUrl);
22372
+ watch: stopWatchingSourceFiles.watchPatterns
22373
+ }, sourceDirectoryUrl);
22399
22374
  const urlGraph = createUrlGraph();
22400
22375
  clientFileChangeCallbackList.push(({
22401
22376
  url
@@ -22418,24 +22393,32 @@ const createFileService = ({
22418
22393
  const clientRuntimeCompat = {
22419
22394
  [runtimeName]: runtimeVersion
22420
22395
  };
22396
+ const jsenvInternalDirectoryUrl = determineJsenvInternalDirectoryUrl(sourceDirectoryUrl);
22397
+ let mainFileUrl;
22398
+ if (sourceMainFilePath === undefined) {
22399
+ mainFileUrl = explorer ? String(explorerHtmlFileUrl) : String(new URL("./index.html", sourceDirectoryUrl));
22400
+ } else {
22401
+ mainFileUrl = String(new URL(sourceMainFilePath, sourceDirectoryUrl));
22402
+ }
22421
22403
  const kitchen = createKitchen({
22422
22404
  signal,
22423
22405
  logLevel,
22424
- rootDirectoryUrl,
22406
+ rootDirectoryUrl: sourceDirectoryUrl,
22407
+ jsenvInternalDirectoryUrl,
22425
22408
  urlGraph,
22426
22409
  dev: true,
22427
22410
  runtimeCompat,
22428
22411
  clientRuntimeCompat,
22429
22412
  systemJsTranspilation: !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "script_type_module") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_dynamic") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_meta"),
22430
22413
  plugins: [...plugins, ...getCorePlugins({
22431
- rootDirectoryUrl,
22414
+ rootDirectoryUrl: sourceDirectoryUrl,
22415
+ mainFileUrl,
22432
22416
  runtimeCompat,
22433
22417
  urlAnalysis,
22434
22418
  urlResolution,
22435
22419
  fileSystemMagicRedirection,
22436
22420
  supervisor,
22437
22421
  transpilation,
22438
- clientMainFileUrl,
22439
22422
  clientAutoreload,
22440
22423
  clientFileChangeCallbackList,
22441
22424
  clientFilesPruneCallbackList,
@@ -22449,7 +22432,7 @@ const createFileService = ({
22449
22432
  sourcemapsSourcesProtocol,
22450
22433
  sourcemapsSourcesContent,
22451
22434
  writeGeneratedFiles,
22452
- outDirectoryUrl: `${rootDirectoryUrl}.jsenv/${runtimeName}@${runtimeVersion}/`
22435
+ outDirectoryUrl: new URL(`${runtimeName}@${runtimeVersion}/`, jsenvInternalDirectoryUrl)
22453
22436
  });
22454
22437
  urlGraph.createUrlInfoCallbackRef.current = urlInfo => {
22455
22438
  const {
@@ -22528,7 +22511,7 @@ const createFileService = ({
22528
22511
  if (serverEventNames.length > 0) {
22529
22512
  Object.keys(allServerEvents).forEach(serverEventName => {
22530
22513
  allServerEvents[serverEventName]({
22531
- rootDirectoryUrl,
22514
+ rootDirectoryUrl: sourceDirectoryUrl,
22532
22515
  urlGraph,
22533
22516
  dev: true,
22534
22517
  sendServerEvent: data => {
@@ -22544,7 +22527,7 @@ const createFileService = ({
22544
22527
  }
22545
22528
  }
22546
22529
  const context = {
22547
- rootDirectoryUrl,
22530
+ rootDirectoryUrl: sourceDirectoryUrl,
22548
22531
  dev: true,
22549
22532
  runtimeName,
22550
22533
  runtimeVersion,
@@ -22555,13 +22538,6 @@ const createFileService = ({
22555
22538
  return context;
22556
22539
  };
22557
22540
  return async request => {
22558
- // serve file inside ".jsenv" directory
22559
- const requestFileUrl = new URL(request.resource.slice(1), rootDirectoryUrl).href;
22560
- if (urlIsInsideOf(requestFileUrl, jsenvDirectoryUrl)) {
22561
- return fetchFileSystem(requestFileUrl, {
22562
- headers: request.headers
22563
- });
22564
- }
22565
22541
  const {
22566
22542
  urlGraph,
22567
22543
  kitchen
@@ -22571,16 +22547,16 @@ const createFileService = ({
22571
22547
  return responseFromPlugin;
22572
22548
  }
22573
22549
  let reference;
22574
- const parentUrl = inferParentFromRequest(request, rootDirectoryUrl);
22550
+ const parentUrl = inferParentFromRequest(request, sourceDirectoryUrl);
22575
22551
  if (parentUrl) {
22576
22552
  reference = urlGraph.inferReference(request.resource, parentUrl);
22577
22553
  }
22578
22554
  if (!reference) {
22579
22555
  const entryPoint = kitchen.injectReference({
22580
22556
  trace: {
22581
- message: parentUrl || rootDirectoryUrl
22557
+ message: parentUrl || sourceDirectoryUrl
22582
22558
  },
22583
- parentUrl: parentUrl || rootDirectoryUrl,
22559
+ parentUrl: parentUrl || sourceDirectoryUrl,
22584
22560
  type: "http_request",
22585
22561
  specifier: request.resource
22586
22562
  });
@@ -22702,7 +22678,7 @@ const createFileService = ({
22702
22678
  accept: "text/html"
22703
22679
  },
22704
22680
  canReadDirectory: true,
22705
- rootDirectoryUrl
22681
+ rootDirectoryUrl: sourceDirectoryUrl
22706
22682
  });
22707
22683
  }
22708
22684
  if (code === "NOT_ALLOWED") {
@@ -22729,7 +22705,7 @@ const createFileService = ({
22729
22705
  }
22730
22706
  };
22731
22707
  };
22732
- const inferParentFromRequest = (request, rootDirectoryUrl) => {
22708
+ const inferParentFromRequest = (request, sourceDirectoryUrl) => {
22733
22709
  const {
22734
22710
  referer
22735
22711
  } = request.headers;
@@ -22750,7 +22726,7 @@ const inferParentFromRequest = (request, rootDirectoryUrl) => {
22750
22726
  return moveUrl({
22751
22727
  url: referer,
22752
22728
  from: `${request.origin}/`,
22753
- to: rootDirectoryUrl,
22729
+ to: sourceDirectoryUrl,
22754
22730
  preferAbsolute: true
22755
22731
  });
22756
22732
  };
@@ -22760,38 +22736,28 @@ const inferParentFromRequest = (request, rootDirectoryUrl) => {
22760
22736
  * - cook source files according to jsenv plugins
22761
22737
  * - inject code to autoreload the browser when a file is modified
22762
22738
  * @param {Object} devServerParameters
22763
- * @param {string|url} devServerParameters.rootDirectoryUrl Root directory of the project
22739
+ * @param {string|url} devServerParameters.sourceDirectoryUrl Root directory of the project
22764
22740
  * @return {Object} A dev server object
22765
22741
  */
22766
22742
  const startDevServer = async ({
22767
- signal = new AbortController().signal,
22768
- handleSIGINT = true,
22769
- logLevel = "info",
22770
- serverLogLevel = "warn",
22743
+ sourceDirectoryUrl,
22744
+ sourceMainFilePath,
22745
+ port = 3456,
22746
+ hostname,
22747
+ acceptAnyIp,
22771
22748
  https,
22772
22749
  // it's better to use http1 by default because it allows to get statusText in devtools
22773
22750
  // which gives valuable information when there is errors
22774
22751
  http2 = false,
22775
- hostname,
22776
- port = 3456,
22777
- acceptAnyIp,
22778
- keepProcessAlive = true,
22752
+ logLevel = process.env.IMPORTED_BY_TEST_PLAN ? "warn" : "info",
22753
+ serverLogLevel = "warn",
22779
22754
  services = [],
22755
+ signal = new AbortController().signal,
22756
+ handleSIGINT = true,
22757
+ keepProcessAlive = true,
22780
22758
  onStop = () => {},
22781
- rootDirectoryUrl,
22782
- clientFiles = {
22783
- "./src/": true,
22784
- "./tests/": true,
22785
- "./package.json": true
22786
- },
22787
- devServerFiles = {
22788
- "./package.json": true,
22789
- "./jsenv.config.mjs": true
22790
- },
22759
+ sourceFilesConfig,
22791
22760
  clientAutoreload = true,
22792
- clientMainFileUrl,
22793
- devServerAutoreload = false,
22794
- devServerMainFile = getCallerPosition().url,
22795
22761
  cooldownBetweenFileEvents,
22796
22762
  // runtimeCompat is the runtimeCompat for the build
22797
22763
  // when specified, dev server use it to warn in case
@@ -22823,11 +22789,7 @@ const startDevServer = async ({
22823
22789
  if (unexpectedParamNames.length > 0) {
22824
22790
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
22825
22791
  }
22826
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
22827
- if (!rootDirectoryUrlValidation.valid) {
22828
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
22829
- }
22830
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
22792
+ sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(sourceDirectoryUrl, "sourceDirectoryUrl");
22831
22793
  }
22832
22794
  const logger = createLogger({
22833
22795
  logLevel
@@ -22841,70 +22803,6 @@ const startDevServer = async ({
22841
22803
  }, abort);
22842
22804
  });
22843
22805
  }
22844
- let reloadableWorker;
22845
- if (devServerAutoreload) {
22846
- reloadableWorker = createReloadableWorker(devServerMainFile);
22847
- if (reloadableWorker.isPrimary) {
22848
- const devServerFileChangeCallback = ({
22849
- relativeUrl,
22850
- event
22851
- }) => {
22852
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
22853
- logger.info(`file ${event} ${url} -> restarting server...`);
22854
- reloadableWorker.reload();
22855
- };
22856
- const stopWatchingDevServerFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22857
- watchPatterns: {
22858
- ...devServerFiles.include,
22859
- [devServerMainFile]: true,
22860
- ".jsenv/": false
22861
- },
22862
- cooldownBetweenFileEvents,
22863
- keepProcessAlive: false,
22864
- recursive: true,
22865
- added: ({
22866
- relativeUrl
22867
- }) => {
22868
- devServerFileChangeCallback({
22869
- relativeUrl,
22870
- event: "added"
22871
- });
22872
- },
22873
- updated: ({
22874
- relativeUrl
22875
- }) => {
22876
- devServerFileChangeCallback({
22877
- relativeUrl,
22878
- event: "modified"
22879
- });
22880
- },
22881
- removed: ({
22882
- relativeUrl
22883
- }) => {
22884
- devServerFileChangeCallback({
22885
- relativeUrl,
22886
- event: "removed"
22887
- });
22888
- }
22889
- });
22890
- operation.addAbortCallback(() => {
22891
- stopWatchingDevServerFiles();
22892
- reloadableWorker.terminate();
22893
- });
22894
- const worker = await reloadableWorker.load();
22895
- const messagePromise = new Promise(resolve => {
22896
- worker.once("message", resolve);
22897
- });
22898
- const origin = await messagePromise;
22899
- return {
22900
- origin,
22901
- stop: () => {
22902
- stopWatchingDevServerFiles();
22903
- reloadableWorker.terminate();
22904
- }
22905
- };
22906
- }
22907
- }
22908
22806
  const startDevServerTask = createTaskLog("start dev server", {
22909
22807
  disabled: !logger.levels.info
22910
22808
  });
@@ -22935,7 +22833,30 @@ const startDevServer = async ({
22935
22833
  accessControlAllowedRequestHeaders: [...jsenvAccessControlAllowedHeaders, "x-jsenv-execution-id"],
22936
22834
  accessControlAllowCredentials: true,
22937
22835
  timingAllowOrigin: true
22938
- }), ...services, {
22836
+ }), {
22837
+ handleRequest: request => {
22838
+ if (request.pathname === "/__server_params__.json") {
22839
+ const json = JSON.stringify({
22840
+ sourceDirectoryUrl
22841
+ });
22842
+ return {
22843
+ status: 200,
22844
+ headers: {
22845
+ "content-type": "application/json",
22846
+ "content-length": Buffer.byteLength(json)
22847
+ },
22848
+ body: json
22849
+ };
22850
+ }
22851
+ if (request.pathname === "/__stop__") {
22852
+ server.stop();
22853
+ return {
22854
+ status: 200
22855
+ };
22856
+ }
22857
+ return null;
22858
+ }
22859
+ }, ...services, {
22939
22860
  name: "jsenv:omega_file_service",
22940
22861
  handleRequest: createFileService({
22941
22862
  signal,
@@ -22943,7 +22864,9 @@ const startDevServer = async ({
22943
22864
  serverStopCallbacks,
22944
22865
  serverEventsDispatcher,
22945
22866
  contextCache,
22946
- rootDirectoryUrl,
22867
+ sourceDirectoryUrl,
22868
+ sourceMainFilePath,
22869
+ sourceFilesConfig,
22947
22870
  runtimeCompat,
22948
22871
  plugins,
22949
22872
  urlAnalysis,
@@ -22952,8 +22875,6 @@ const startDevServer = async ({
22952
22875
  supervisor,
22953
22876
  transpilation,
22954
22877
  clientAutoreload,
22955
- clientFiles,
22956
- clientMainFileUrl,
22957
22878
  cooldownBetweenFileEvents,
22958
22879
  explorer,
22959
22880
  cacheControl,
@@ -23026,9 +22947,6 @@ const startDevServer = async ({
23026
22947
  logger.info(`- ${server.origins[key]}`);
23027
22948
  });
23028
22949
  logger.info(``);
23029
- if (reloadableWorker && reloadableWorker.isWorker) {
23030
- parentPort.postMessage(server.origin);
23031
- }
23032
22950
  return {
23033
22951
  origin: server.origin,
23034
22952
  stop: () => {
@@ -23038,6 +22956,80 @@ const startDevServer = async ({
23038
22956
  };
23039
22957
  };
23040
22958
 
22959
+ const pingServer = async url => {
22960
+ const server = createServer();
22961
+ const {
22962
+ hostname,
22963
+ port
22964
+ } = new URL(url);
22965
+ try {
22966
+ await new Promise((resolve, reject) => {
22967
+ server.on("error", reject);
22968
+ server.on("listening", () => {
22969
+ resolve();
22970
+ });
22971
+ server.listen(port, hostname);
22972
+ });
22973
+ } catch (error) {
22974
+ if (error && error.code === "EADDRINUSE") {
22975
+ return true;
22976
+ }
22977
+ if (error && error.code === "EACCES") {
22978
+ return true;
22979
+ }
22980
+ throw error;
22981
+ }
22982
+ await new Promise((resolve, reject) => {
22983
+ server.on("error", reject);
22984
+ server.on("close", resolve);
22985
+ server.close();
22986
+ });
22987
+ return false;
22988
+ };
22989
+
22990
+ const basicFetch = async (url, {
22991
+ method = "GET",
22992
+ headers = {}
22993
+ } = {}) => {
22994
+ let requestModule;
22995
+ if (url.startsWith("http:")) {
22996
+ requestModule = await import("node:http");
22997
+ } else {
22998
+ requestModule = await import("node:https");
22999
+ }
23000
+ const {
23001
+ request
23002
+ } = requestModule;
23003
+ const urlObject = new URL(url);
23004
+ return new Promise((resolve, reject) => {
23005
+ const req = request({
23006
+ hostname: urlObject.hostname,
23007
+ port: urlObject.port,
23008
+ path: urlObject.pathname,
23009
+ method,
23010
+ headers
23011
+ });
23012
+ req.on("response", response => {
23013
+ req.setTimeout(0);
23014
+ let responseBody = "";
23015
+ response.setEncoding("utf8");
23016
+ response.on("data", chunk => {
23017
+ responseBody += chunk;
23018
+ });
23019
+ response.on("end", () => {
23020
+ req.destroy();
23021
+ if (response.headers["content-type"] === "application/json") {
23022
+ resolve(JSON.parse(responseBody));
23023
+ } else {
23024
+ resolve(responseBody);
23025
+ }
23026
+ });
23027
+ });
23028
+ req.on("error", reject);
23029
+ req.end();
23030
+ });
23031
+ };
23032
+
23041
23033
  const generateCoverageJsonFile = async ({
23042
23034
  coverage,
23043
23035
  coverageJsonFileUrl,
@@ -23127,7 +23119,7 @@ const readNodeV8CoverageDirectory = async ({
23127
23119
  try {
23128
23120
  operation.throwIfAborted();
23129
23121
  const dirContent = await tryReadDirectory();
23130
- const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE);
23122
+ const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE, "NODE_V8_COVERAGE");
23131
23123
  await dirContent.reduce(async (previous, dirEntry) => {
23132
23124
  operation.throwIfAborted();
23133
23125
  await previous;
@@ -23750,37 +23742,6 @@ const run = async ({
23750
23742
  return result;
23751
23743
  };
23752
23744
 
23753
- const pingServer = async url => {
23754
- const server = createServer();
23755
- const {
23756
- hostname,
23757
- port
23758
- } = new URL(url);
23759
- try {
23760
- await new Promise((resolve, reject) => {
23761
- server.on("error", reject);
23762
- server.on("listening", () => {
23763
- resolve();
23764
- });
23765
- server.listen(port, hostname);
23766
- });
23767
- } catch (error) {
23768
- if (error && error.code === "EADDRINUSE") {
23769
- return true;
23770
- }
23771
- if (error && error.code === "EACCES") {
23772
- return true;
23773
- }
23774
- throw error;
23775
- }
23776
- await new Promise((resolve, reject) => {
23777
- server.on("error", reject);
23778
- server.on("close", resolve);
23779
- server.close();
23780
- });
23781
- return false;
23782
- };
23783
-
23784
23745
  const ensureGlobalGc = () => {
23785
23746
  if (!global.gc) {
23786
23747
  v8.setFlagsFromString("--expose_gc");
@@ -24208,7 +24169,7 @@ const executePlan = async (plan, {
24208
24169
  coverageMethodForBrowsers,
24209
24170
  coverageMethodForNodeJs,
24210
24171
  coverageV8ConflictWarning,
24211
- coverageTempDirectoryRelativeUrl,
24172
+ coverageTempDirectoryUrl,
24212
24173
  beforeExecutionCallback = () => {},
24213
24174
  afterExecutionCallback = () => {}
24214
24175
  } = {}) => {
@@ -24218,30 +24179,6 @@ const executePlan = async (plan, {
24218
24179
  const stopAfterAllSignal = {
24219
24180
  notify: () => {}
24220
24181
  };
24221
- let someNeedsServer = false;
24222
- let someNodeRuntime = false;
24223
- const runtimes = {};
24224
- Object.keys(plan).forEach(filePattern => {
24225
- const filePlan = plan[filePattern];
24226
- Object.keys(filePlan).forEach(executionName => {
24227
- const executionConfig = filePlan[executionName];
24228
- const {
24229
- runtime
24230
- } = executionConfig;
24231
- if (runtime) {
24232
- runtimes[runtime.name] = runtime.version;
24233
- if (runtime.type === "browser") {
24234
- someNeedsServer = true;
24235
- }
24236
- if (runtime.type === "node") {
24237
- someNodeRuntime = true;
24238
- }
24239
- }
24240
- });
24241
- });
24242
- logger.debug(createDetailedMessage$1(`Prepare executing plan`, {
24243
- runtimes: JSON.stringify(runtimes, null, " ")
24244
- }));
24245
24182
  const multipleExecutionsOperation = Abort.startOperation();
24246
24183
  multipleExecutionsOperation.addAbortSignal(signal);
24247
24184
  if (handleSIGINT) {
@@ -24259,19 +24196,6 @@ const executePlan = async (plan, {
24259
24196
  multipleExecutionsOperation.addAbortSignal(failFastAbortController.signal);
24260
24197
  }
24261
24198
  try {
24262
- const coverageTempDirectoryUrl = new URL(coverageTempDirectoryRelativeUrl, rootDirectoryUrl).href;
24263
- if (someNodeRuntime && coverageEnabled && coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
24264
- if (process.env.NODE_V8_COVERAGE) {
24265
- // when runned multiple times, we don't want to keep previous files in this directory
24266
- await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE);
24267
- } else {
24268
- coverageMethodForNodeJs = "Profiler";
24269
- logger.warn(createDetailedMessage$1(`process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`, {
24270
- "suggestion": `set process.env.NODE_V8_COVERAGE`,
24271
- "suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`
24272
- }));
24273
- }
24274
- }
24275
24199
  if (gcBetweenExecutions) {
24276
24200
  ensureGlobalGc();
24277
24201
  }
@@ -24318,15 +24242,6 @@ const executePlan = async (plan, {
24318
24242
  coverageMethodForNodeJs,
24319
24243
  stopAfterAllSignal
24320
24244
  };
24321
- if (someNeedsServer) {
24322
- if (!devServerOrigin) {
24323
- throw new TypeError(`devServerOrigin is required when running tests on browser(s)`);
24324
- }
24325
- const devServerStarted = await pingServer(devServerOrigin);
24326
- if (!devServerStarted) {
24327
- throw new Error(`dev server not started at ${devServerOrigin}. It is required to run tests`);
24328
- }
24329
- }
24330
24245
  logger.debug(`Generate executions`);
24331
24246
  const executionSteps = await getExecutionAsSteps({
24332
24247
  plan,
@@ -24423,7 +24338,7 @@ const executePlan = async (plan, {
24423
24338
  executionResult = await run({
24424
24339
  signal: multipleExecutionsOperation.signal,
24425
24340
  logger,
24426
- allocatedMs: executionParams.allocatedMs,
24341
+ allocatedMs: typeof executionParams.allocatedMs === "function" ? executionParams.allocatedMs(beforeExecutionInfo) : executionParams.allocatedMs,
24427
24342
  keepRunning,
24428
24343
  mirrorConsole: false,
24429
24344
  // file are executed in parallel, log would be a mess to read
@@ -24639,8 +24554,8 @@ const executeInParallel = async ({
24639
24554
  /**
24640
24555
  * Execute a list of files and log how it goes.
24641
24556
  * @param {Object} testPlanParameters
24642
- * @param {string|url} testPlanParameters.rootDirectoryUrl Root directory of the project
24643
- * @param {string|url} [testPlanParameters.serverOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
24557
+ * @param {string|url} testPlanParameters.testDirectoryUrl Directory containing test files
24558
+ * @param {string|url} [testPlanParameters.devServerOrigin=undefined] Jsenv dev server origin; required when executing test on browsers
24644
24559
  * @param {Object} testPlanParameters.testPlan Object associating patterns leading to files to runtimes where they should be executed
24645
24560
  * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
24646
24561
  * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
@@ -24666,7 +24581,8 @@ const executeTestPlan = async ({
24666
24581
  logFileRelativeUrl = ".jsenv/test_plan_debug.txt",
24667
24582
  completedExecutionLogAbbreviation = false,
24668
24583
  completedExecutionLogMerging = false,
24669
- rootDirectoryUrl,
24584
+ testDirectoryUrl,
24585
+ devServerModuleUrl,
24670
24586
  devServerOrigin,
24671
24587
  testPlan,
24672
24588
  updateProcessExitCode = true,
@@ -24683,7 +24599,7 @@ const executeTestPlan = async ({
24683
24599
  gcBetweenExecutions = logMemoryHeapUsage,
24684
24600
  coverageEnabled = process.argv.includes("--coverage"),
24685
24601
  coverageConfig = {
24686
- "./src/": true
24602
+ "./**/*": true
24687
24603
  },
24688
24604
  coverageIncludeMissing = true,
24689
24605
  coverageAndExecutionAllowed = false,
@@ -24691,30 +24607,87 @@ const executeTestPlan = async ({
24691
24607
  coverageMethodForBrowsers = "playwright_api",
24692
24608
  // "istanbul" also accepted
24693
24609
  coverageV8ConflictWarning = true,
24694
- coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
24610
+ coverageTempDirectoryUrl,
24611
+ coverageReportRootDirectoryUrl,
24695
24612
  // skip empty means empty files won't appear in the coverage reports (json and html)
24696
24613
  coverageReportSkipEmpty = false,
24697
24614
  // skip full means file with 100% coverage won't appear in coverage reports (json and html)
24698
24615
  coverageReportSkipFull = false,
24699
24616
  coverageReportTextLog = true,
24700
- coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
24701
- coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
24617
+ coverageReportJson = process.env.CI,
24618
+ coverageReportJsonFileUrl,
24619
+ coverageReportHtml = !process.env.CI,
24620
+ coverageReportHtmlDirectoryUrl,
24702
24621
  ...rest
24703
24622
  }) => {
24623
+ let someNeedsServer = false;
24624
+ let someNodeRuntime = false;
24625
+ let stopDevServerNeeded = false;
24626
+ const runtimes = {};
24704
24627
  // param validation
24705
24628
  {
24706
24629
  const unexpectedParamNames = Object.keys(rest);
24707
24630
  if (unexpectedParamNames.length > 0) {
24708
24631
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
24709
24632
  }
24710
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
24711
- if (!rootDirectoryUrlValidation.valid) {
24712
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
24633
+ testDirectoryUrl = assertAndNormalizeDirectoryUrl(testDirectoryUrl, "testDirectoryUrl");
24634
+ if (!existsSync(new URL(testDirectoryUrl))) {
24635
+ throw new Error(`ENOENT on testDirectoryUrl at ${testDirectoryUrl}`);
24713
24636
  }
24714
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
24715
24637
  if (typeof testPlan !== "object") {
24716
24638
  throw new Error(`testPlan must be an object, got ${testPlan}`);
24717
24639
  }
24640
+ Object.keys(testPlan).forEach(filePattern => {
24641
+ const filePlan = testPlan[filePattern];
24642
+ if (!filePlan) return;
24643
+ Object.keys(filePlan).forEach(executionName => {
24644
+ const executionConfig = filePlan[executionName];
24645
+ const {
24646
+ runtime
24647
+ } = executionConfig;
24648
+ if (runtime) {
24649
+ runtimes[runtime.name] = runtime.version;
24650
+ if (runtime.type === "browser") {
24651
+ someNeedsServer = true;
24652
+ }
24653
+ if (runtime.type === "node") {
24654
+ someNodeRuntime = true;
24655
+ }
24656
+ }
24657
+ });
24658
+ });
24659
+ if (someNeedsServer) {
24660
+ if (!devServerOrigin) {
24661
+ throw new TypeError(`devServerOrigin is required when running tests on browser(s)`);
24662
+ }
24663
+ let devServerStarted = await pingServer(devServerOrigin);
24664
+ if (!devServerStarted) {
24665
+ if (!devServerModuleUrl) {
24666
+ throw new TypeError(`devServerModuleUrl is required when dev server is not started in order to run tests on browser(s)`);
24667
+ }
24668
+ try {
24669
+ process.env.IMPORTED_BY_TEST_PLAN = "1";
24670
+ await import(devServerModuleUrl);
24671
+ delete process.env.IMPORTED_BY_TEST_PLAN;
24672
+ } catch (e) {
24673
+ if (e.code === "MODULE_NOT_FOUND") {
24674
+ throw new Error(`Cannot find file responsible to start dev server at "${devServerModuleUrl}"`);
24675
+ }
24676
+ throw e;
24677
+ }
24678
+ devServerStarted = await pingServer(devServerOrigin);
24679
+ if (!devServerStarted) {
24680
+ throw new Error(`dev server not started after importing "${devServerModuleUrl}", ensure this module file is starting a server at "${devServerOrigin}"`);
24681
+ }
24682
+ stopDevServerNeeded = true;
24683
+ }
24684
+ const {
24685
+ sourceDirectoryUrl
24686
+ } = await basicFetch(`${devServerOrigin}/__server_params__.json`);
24687
+ if (testDirectoryUrl !== sourceDirectoryUrl && !urlIsInsideOf(testDirectoryUrl, sourceDirectoryUrl)) {
24688
+ throw new Error(`testDirectoryUrl must be inside sourceDirectoryUrl when running tests on browser(s)`);
24689
+ }
24690
+ }
24718
24691
  if (coverageEnabled) {
24719
24692
  if (typeof coverageConfig !== "object") {
24720
24693
  throw new TypeError(`coverageConfig must be an object, got ${coverageConfig}`);
@@ -24742,14 +24715,63 @@ const executeTestPlan = async ({
24742
24715
  }));
24743
24716
  }
24744
24717
  }
24718
+ if (coverageReportRootDirectoryUrl === undefined) {
24719
+ coverageReportRootDirectoryUrl = lookupPackageDirectory(testDirectoryUrl);
24720
+ } else {
24721
+ coverageReportRootDirectoryUrl = assertAndNormalizeDirectoryUrl(coverageReportRootDirectoryUrl, "coverageReportRootDirectoryUrl");
24722
+ }
24723
+ if (coverageTempDirectoryUrl === undefined) {
24724
+ coverageTempDirectoryUrl = new URL("./.coverage/tmp/", coverageReportRootDirectoryUrl);
24725
+ } else {
24726
+ coverageTempDirectoryUrl = assertAndNormalizeDirectoryUrl(coverageTempDirectoryUrl, "coverageTempDirectoryUrl");
24727
+ }
24728
+ if (coverageReportJson) {
24729
+ if (coverageReportJsonFileUrl === undefined) {
24730
+ coverageReportJsonFileUrl = new URL("./.coverage/coverage.json", coverageReportRootDirectoryUrl);
24731
+ } else {
24732
+ coverageReportJsonFileUrl = assertAndNormalizeFileUrl(coverageReportJsonFileUrl, "coverageReportJsonFileUrl");
24733
+ }
24734
+ }
24735
+ if (coverageReportHtml) {
24736
+ if (coverageReportHtmlDirectoryUrl === undefined) {
24737
+ coverageReportHtmlDirectoryUrl = new URL("./.coverage/", coverageReportRootDirectoryUrl);
24738
+ } else {
24739
+ coverageReportHtmlDirectoryUrl = assertAndNormalizeDirectoryUrl(coverageReportHtmlDirectoryUrl, "coverageReportHtmlDirectoryUrl");
24740
+ }
24741
+ }
24745
24742
  }
24746
24743
  }
24747
24744
  const logger = createLogger({
24748
24745
  logLevel
24749
24746
  });
24750
- if (Object.keys(coverageConfig).length === 0) {
24751
- logger.warn(`coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`);
24747
+ logger.debug(createDetailedMessage$1(`Prepare executing plan`, {
24748
+ runtimes: JSON.stringify(runtimes, null, " ")
24749
+ }));
24750
+
24751
+ // param normalization
24752
+ {
24753
+ if (coverageEnabled) {
24754
+ if (Object.keys(coverageConfig).length === 0) {
24755
+ logger.warn(`coverageConfig is an empty object. Nothing will be instrumented for coverage so your coverage will be empty`);
24756
+ }
24757
+ if (someNodeRuntime && coverageEnabled && coverageMethodForNodeJs === "NODE_V8_COVERAGE") {
24758
+ if (process.env.NODE_V8_COVERAGE) {
24759
+ // when runned multiple times, we don't want to keep previous files in this directory
24760
+ await ensureEmptyDirectory(process.env.NODE_V8_COVERAGE);
24761
+ } else {
24762
+ coverageMethodForNodeJs = "Profiler";
24763
+ logger.warn(createDetailedMessage$1(`process.env.NODE_V8_COVERAGE is required to generate coverage for Node.js subprocesses`, {
24764
+ "suggestion": `set process.env.NODE_V8_COVERAGE`,
24765
+ "suggestion 2": `use coverageMethodForNodeJs: "Profiler". But it means coverage for child_process and worker_thread cannot be collected`
24766
+ }));
24767
+ }
24768
+ }
24769
+ }
24752
24770
  }
24771
+ testPlan = {
24772
+ ...testPlan,
24773
+ "**/.jsenv/": null
24774
+ };
24753
24775
  const result = await executePlan(testPlan, {
24754
24776
  signal,
24755
24777
  handleSIGINT,
@@ -24763,7 +24785,7 @@ const executeTestPlan = async ({
24763
24785
  logFileRelativeUrl,
24764
24786
  completedExecutionLogMerging,
24765
24787
  completedExecutionLogAbbreviation,
24766
- rootDirectoryUrl,
24788
+ rootDirectoryUrl: testDirectoryUrl,
24767
24789
  devServerOrigin,
24768
24790
  maxExecutionsInParallel,
24769
24791
  defaultMsAllocatedPerExecution,
@@ -24777,8 +24799,17 @@ const executeTestPlan = async ({
24777
24799
  coverageMethodForBrowsers,
24778
24800
  coverageMethodForNodeJs,
24779
24801
  coverageV8ConflictWarning,
24780
- coverageTempDirectoryRelativeUrl
24802
+ coverageTempDirectoryUrl
24781
24803
  });
24804
+ if (stopDevServerNeeded) {
24805
+ // we are expecting ECONNRESET because server will be stopped by the request
24806
+ basicFetch(`${devServerOrigin}/__stop__`).catch(e => {
24807
+ if (e.code === "ECONNRESET") {
24808
+ return;
24809
+ }
24810
+ throw e;
24811
+ });
24812
+ }
24782
24813
  if (updateProcessExitCode && result.planSummary.counters.total !== result.planSummary.counters.completed) {
24783
24814
  process.exitCode = 1;
24784
24815
  }
@@ -24789,26 +24820,21 @@ const executeTestPlan = async ({
24789
24820
  // keep this one first because it does ensureEmptyDirectory
24790
24821
  // and in case coverage json file gets written in the same directory
24791
24822
  // it must be done before
24792
- if (coverageEnabled && coverageReportHtmlDirectory) {
24793
- const coverageHtmlDirectoryUrl = resolveDirectoryUrl(coverageReportHtmlDirectory, rootDirectoryUrl);
24794
- if (!urlIsInsideOf(coverageHtmlDirectoryUrl, rootDirectoryUrl)) {
24795
- throw new Error(`coverageReportHtmlDirectory must be inside rootDirectoryUrl`);
24796
- }
24797
- await ensureEmptyDirectory(coverageHtmlDirectoryUrl);
24798
- const htmlCoverageDirectoryIndexFileUrl = `${coverageHtmlDirectoryUrl}index.html`;
24823
+ if (coverageEnabled && coverageReportHtml) {
24824
+ await ensureEmptyDirectory(coverageReportHtmlDirectoryUrl);
24825
+ const htmlCoverageDirectoryIndexFileUrl = `${coverageReportHtmlDirectoryUrl}index.html`;
24799
24826
  logger.info(`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`);
24800
24827
  promises.push(generateCoverageHtmlDirectory(planCoverage, {
24801
- rootDirectoryUrl,
24802
- coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(coverageHtmlDirectoryUrl, rootDirectoryUrl),
24828
+ rootDirectoryUrl: coverageReportRootDirectoryUrl,
24829
+ coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(coverageReportHtmlDirectoryUrl, coverageReportRootDirectoryUrl),
24803
24830
  coverageReportSkipEmpty,
24804
24831
  coverageReportSkipFull
24805
24832
  }));
24806
24833
  }
24807
- if (coverageEnabled && coverageReportJsonFile) {
24808
- const coverageJsonFileUrl = new URL(coverageReportJsonFile, rootDirectoryUrl).href;
24834
+ if (coverageEnabled && coverageReportJson) {
24809
24835
  promises.push(generateCoverageJsonFile({
24810
24836
  coverage: result.planCoverage,
24811
- coverageJsonFileUrl,
24837
+ coverageJsonFileUrl: coverageReportJsonFileUrl,
24812
24838
  logger
24813
24839
  }));
24814
24840
  }
@@ -25672,7 +25698,6 @@ nodeChildProcess.run = async ({
25672
25698
  env: envForChildProcess
25673
25699
  });
25674
25700
  logger.debug(createDetailedMessage$1(`child process forked (pid ${childProcess.pid})`, {
25675
- "execArgv": execArgv.join(`\n`),
25676
25701
  "custom env": JSON.stringify(env, null, " ")
25677
25702
  }));
25678
25703
  // if we pass stream, pipe them https://github.com/sindresorhus/execa/issues/81
@@ -26195,32 +26220,23 @@ const onceWorkerThreadEvent = (worker, type, callback) => {
26195
26220
  /**
26196
26221
  * Start a server for build files.
26197
26222
  * @param {Object} buildServerParameters
26198
- * @param {string|url} buildServerParameters.rootDirectoryUrl Root directory of the project
26199
26223
  * @param {string|url} buildServerParameters.buildDirectoryUrl Directory where build files are written
26200
26224
  * @return {Object} A build server object
26201
26225
  */
26202
26226
  const startBuildServer = async ({
26203
- signal = new AbortController().signal,
26204
- handleSIGINT = true,
26205
- logLevel,
26206
- serverLogLevel = "warn",
26207
- https,
26208
- http2,
26209
- acceptAnyIp,
26210
- hostname,
26227
+ buildDirectoryUrl,
26228
+ buildMainFilePath = "index.html",
26211
26229
  port = 9779,
26212
26230
  services = [],
26231
+ acceptAnyIp,
26232
+ hostname,
26233
+ https,
26234
+ http2,
26235
+ logLevel,
26236
+ serverLogLevel = "warn",
26237
+ signal = new AbortController().signal,
26238
+ handleSIGINT = true,
26213
26239
  keepProcessAlive = true,
26214
- rootDirectoryUrl,
26215
- buildDirectoryUrl,
26216
- buildIndexPath = "index.html",
26217
- buildServerFiles = {
26218
- "./package.json": true,
26219
- "./jsenv.config.mjs": true
26220
- },
26221
- buildServerAutoreload = false,
26222
- buildServerMainFile = getCallerPosition().url,
26223
- cooldownBetweenFileEvents,
26224
26240
  ...rest
26225
26241
  }) => {
26226
26242
  // params validation
@@ -26229,31 +26245,22 @@ const startBuildServer = async ({
26229
26245
  if (unexpectedParamNames.length > 0) {
26230
26246
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
26231
26247
  }
26232
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
26233
- if (!rootDirectoryUrlValidation.valid) {
26234
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
26235
- }
26236
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
26237
- const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl);
26238
- if (!buildDirectoryUrlValidation.valid) {
26239
- throw new TypeError(`buildDirectoryUrl ${buildDirectoryUrlValidation.message}, got ${buildDirectoryUrlValidation}`);
26240
- }
26241
- buildDirectoryUrl = buildDirectoryUrlValidation.value;
26242
- if (buildIndexPath) {
26243
- if (typeof buildIndexPath !== "string") {
26244
- throw new TypeError(`buildIndexPath must be a string, got ${buildIndexPath}`);
26248
+ buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl, "buildDirectoryUrl");
26249
+ if (buildMainFilePath) {
26250
+ if (typeof buildMainFilePath !== "string") {
26251
+ throw new TypeError(`buildMainFilePath must be a string, got ${buildMainFilePath}`);
26245
26252
  }
26246
- if (buildIndexPath[0] === "/") {
26247
- buildIndexPath = buildIndexPath.slice(1);
26253
+ if (buildMainFilePath[0] === "/") {
26254
+ buildMainFilePath = buildMainFilePath.slice(1);
26248
26255
  } else {
26249
- const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href;
26250
- if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
26251
- throw new Error(`buildIndexPath must be relative, got ${buildIndexPath}`);
26256
+ const buildMainFileUrl = new URL(buildMainFilePath, buildDirectoryUrl).href;
26257
+ if (!buildMainFileUrl.startsWith(buildDirectoryUrl)) {
26258
+ throw new Error(`buildMainFilePath must be relative, got ${buildMainFilePath}`);
26252
26259
  }
26253
- buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length);
26260
+ buildMainFilePath = buildMainFileUrl.slice(buildDirectoryUrl.length);
26254
26261
  }
26255
- if (!existsSync(new URL(buildIndexPath, buildDirectoryUrl))) {
26256
- buildIndexPath = null;
26262
+ if (!existsSync(new URL(buildMainFilePath, buildDirectoryUrl))) {
26263
+ buildMainFilePath = null;
26257
26264
  }
26258
26265
  }
26259
26266
  }
@@ -26269,73 +26276,6 @@ const startBuildServer = async ({
26269
26276
  }, abort);
26270
26277
  });
26271
26278
  }
26272
- let reloadableWorker;
26273
- if (buildServerAutoreload) {
26274
- reloadableWorker = createReloadableWorker(buildServerMainFile);
26275
- if (reloadableWorker.isPrimary) {
26276
- const buildServerFileChangeCallback = ({
26277
- relativeUrl,
26278
- event
26279
- }) => {
26280
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
26281
- logger.info(`file ${event} ${url} -> restarting server...`);
26282
- reloadableWorker.reload();
26283
- };
26284
- const stopWatchingBuildServerFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
26285
- watchPatterns: {
26286
- ...buildServerFiles,
26287
- [buildServerMainFile]: true,
26288
- ".jsenv/": false
26289
- },
26290
- cooldownBetweenFileEvents,
26291
- keepProcessAlive: false,
26292
- recursive: true,
26293
- added: ({
26294
- relativeUrl
26295
- }) => {
26296
- buildServerFileChangeCallback({
26297
- relativeUrl,
26298
- event: "added"
26299
- });
26300
- },
26301
- updated: ({
26302
- relativeUrl
26303
- }) => {
26304
- buildServerFileChangeCallback({
26305
- relativeUrl,
26306
- event: "modified"
26307
- });
26308
- },
26309
- removed: ({
26310
- relativeUrl
26311
- }) => {
26312
- buildServerFileChangeCallback({
26313
- relativeUrl,
26314
- event: "removed"
26315
- });
26316
- }
26317
- });
26318
- operation.addAbortCallback(() => {
26319
- stopWatchingBuildServerFiles();
26320
- reloadableWorker.terminate();
26321
- });
26322
- const worker = await reloadableWorker.load();
26323
- const messagePromise = new Promise(resolve => {
26324
- worker.once("message", resolve);
26325
- });
26326
- const origin = await messagePromise;
26327
- // if (!keepProcessAlive) {
26328
- // worker.unref()
26329
- // }
26330
- return {
26331
- origin,
26332
- stop: () => {
26333
- stopWatchingBuildServerFiles();
26334
- reloadableWorker.terminate();
26335
- }
26336
- };
26337
- }
26338
- }
26339
26279
  const startBuildServerTask = createTaskLog("start build server", {
26340
26280
  disabled: !logger.levels.info
26341
26281
  });
@@ -26366,7 +26306,7 @@ const startBuildServer = async ({
26366
26306
  name: "jsenv:build_files_service",
26367
26307
  handleRequest: createBuildFilesService({
26368
26308
  buildDirectoryUrl,
26369
- buildIndexPath
26309
+ buildMainFilePath
26370
26310
  })
26371
26311
  }, jsenvServiceErrorHandler({
26372
26312
  sendErrorDetails: true
@@ -26382,9 +26322,6 @@ const startBuildServer = async ({
26382
26322
  logger.info(`- ${server.origins[key]}`);
26383
26323
  });
26384
26324
  logger.info(``);
26385
- if (reloadableWorker && reloadableWorker.isWorker) {
26386
- parentPort.postMessage(server.origin);
26387
- }
26388
26325
  return {
26389
26326
  origin: server.origin,
26390
26327
  stop: () => {
@@ -26394,14 +26331,14 @@ const startBuildServer = async ({
26394
26331
  };
26395
26332
  const createBuildFilesService = ({
26396
26333
  buildDirectoryUrl,
26397
- buildIndexPath
26334
+ buildMainFilePath
26398
26335
  }) => {
26399
26336
  return request => {
26400
26337
  const urlIsVersioned = new URL(request.url).searchParams.has("v");
26401
- if (buildIndexPath && request.resource === "/") {
26338
+ if (buildMainFilePath && request.resource === "/") {
26402
26339
  request = {
26403
26340
  ...request,
26404
- resource: `/${buildIndexPath}`
26341
+ resource: `/${buildMainFilePath}`
26405
26342
  };
26406
26343
  }
26407
26344
  return fetchFileSystem(new URL(request.resource.slice(1), buildDirectoryUrl), {
@@ -26448,7 +26385,7 @@ const execute = async ({
26448
26385
  const logger = createLogger({
26449
26386
  logLevel
26450
26387
  });
26451
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
26388
+ rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl, "rootDirectoryUrl");
26452
26389
  const executeOperation = Abort.startOperation();
26453
26390
  executeOperation.addAbortSignal(signal);
26454
26391
  if (handleSIGINT) {