@jsenv/core 31.2.0 → 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,
@@ -17063,7 +17162,7 @@ const addRelationshipWithPackageJson = ({
17063
17162
  */
17064
17163
  const jsenvPluginUrlResolution = ({
17065
17164
  runtimeCompat,
17066
- clientMainFileUrl,
17165
+ mainFileUrl,
17067
17166
  urlResolution
17068
17167
  }) => {
17069
17168
  const resolveUrlUsingWebResolution = reference => {
@@ -17124,7 +17223,7 @@ const jsenvPluginUrlResolution = ({
17124
17223
  appliesDuring: "*",
17125
17224
  resolveUrl: (reference, context) => {
17126
17225
  if (reference.specifier === "/") {
17127
- return String(clientMainFileUrl);
17226
+ return String(mainFileUrl);
17128
17227
  }
17129
17228
  if (reference.specifier[0] === "/") {
17130
17229
  return new URL(reference.specifier.slice(1), context.rootDirectoryUrl).href;
@@ -20192,13 +20291,14 @@ const explorerHtmlFileUrl = new URL("./html/explorer.html", import.meta.url);
20192
20291
  const jsenvPluginExplorer = ({
20193
20292
  groups = {
20194
20293
  src: {
20195
- "./src/**/*.html": true
20294
+ "./**/*.html": true,
20295
+ "./**/*.test.html": false
20196
20296
  },
20197
20297
  tests: {
20198
- "./tests/**/*.test.html": true
20298
+ "./**/*.test.html": true
20199
20299
  }
20200
20300
  },
20201
- clientMainFileUrl
20301
+ mainFileUrl
20202
20302
  }) => {
20203
20303
  const faviconClientFileUrl = new URL("./other/jsenv.png", import.meta.url);
20204
20304
  return {
@@ -20206,7 +20306,7 @@ const jsenvPluginExplorer = ({
20206
20306
  appliesDuring: "dev",
20207
20307
  transformUrlContent: {
20208
20308
  html: async (urlInfo, context) => {
20209
- if (urlInfo.url !== clientMainFileUrl) {
20309
+ if (urlInfo.url !== mainFileUrl) {
20210
20310
  return null;
20211
20311
  }
20212
20312
  let html = urlInfo.content;
@@ -20308,6 +20408,7 @@ injectRibbon(${paramsJson})`
20308
20408
 
20309
20409
  const getCorePlugins = ({
20310
20410
  rootDirectoryUrl,
20411
+ mainFileUrl,
20311
20412
  runtimeCompat,
20312
20413
  urlAnalysis = {},
20313
20414
  urlResolution = {},
@@ -20315,7 +20416,6 @@ const getCorePlugins = ({
20315
20416
  directoryReferenceAllowed,
20316
20417
  supervisor,
20317
20418
  transpilation = true,
20318
- clientMainFileUrl,
20319
20419
  clientAutoreload = false,
20320
20420
  clientFileChangeCallbackList,
20321
20421
  clientFilesPruneCallbackList,
@@ -20339,11 +20439,6 @@ const getCorePlugins = ({
20339
20439
  if (clientAutoreload === true) {
20340
20440
  clientAutoreload = {};
20341
20441
  }
20342
- if (clientMainFileUrl === undefined) {
20343
- clientMainFileUrl = explorer ? String(explorerHtmlFileUrl) : String(new URL("./index.html", rootDirectoryUrl));
20344
- } else {
20345
- clientMainFileUrl = String(clientMainFileUrl);
20346
- }
20347
20442
  if (ribbon === true) {
20348
20443
  ribbon = {};
20349
20444
  }
@@ -20362,7 +20457,7 @@ const getCorePlugins = ({
20362
20457
  ...fileSystemMagicRedirection
20363
20458
  }), jsenvPluginHttpUrls(), jsenvPluginUrlResolution({
20364
20459
  runtimeCompat,
20365
- clientMainFileUrl,
20460
+ mainFileUrl,
20366
20461
  urlResolution
20367
20462
  }), jsenvPluginUrlVersion(), jsenvPluginCommonJsGlobals(), jsenvPluginImportMetaScenarios(), ...(scenarioPlaceholders ? [jsenvPluginGlobalScenarios()] : []), jsenvPluginNodeRuntime({
20368
20463
  runtimeCompat
@@ -20372,7 +20467,7 @@ const getCorePlugins = ({
20372
20467
  clientFilesPruneCallbackList
20373
20468
  })] : []), ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []), ...(explorer ? [jsenvPluginExplorer({
20374
20469
  ...explorer,
20375
- clientMainFileUrl
20470
+ mainFileUrl
20376
20471
  })] : []), ...(ribbon ? [jsenvPluginRibbon({
20377
20472
  rootDirectoryUrl,
20378
20473
  ...ribbon
@@ -20731,12 +20826,13 @@ const defaultRuntimeCompat = {
20731
20826
  /**
20732
20827
  * Generate an optimized version of source files into a directory
20733
20828
  * @param {Object} buildParameters
20734
- * @param {string|url} buildParameters.rootDirectoryUrl
20829
+ * @param {string|url} buildParameters.sourceDirectoryUrl
20735
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
20736
20834
  * @param {string|url} buildParameters.buildDirectoryUrl
20737
20835
  * 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
20836
  * @param {object} buildParameters.runtimeCompat
20741
20837
  * Code generated will be compatible with these runtimes
20742
20838
  * @param {string} [buildParameters.assetsDirectory=""]
@@ -20761,10 +20857,10 @@ const build = async ({
20761
20857
  signal = new AbortController().signal,
20762
20858
  handleSIGINT = true,
20763
20859
  logLevel = "info",
20764
- rootDirectoryUrl,
20860
+ sourceDirectoryUrl,
20861
+ entryPoints = {},
20765
20862
  buildDirectoryUrl,
20766
20863
  assetsDirectory = "",
20767
- entryPoints = {},
20768
20864
  runtimeCompat = defaultRuntimeCompat,
20769
20865
  base = runtimeCompat.node ? "./" : "/",
20770
20866
  plugins = [],
@@ -20781,9 +20877,7 @@ const build = async ({
20781
20877
  // "filename", "search_param"
20782
20878
  versioningViaImportmap = true,
20783
20879
  lineBreakNormalization = process.platform === "win32",
20784
- clientFiles = {
20785
- "./src/": true
20786
- },
20880
+ sourceFilesConfig = {},
20787
20881
  cooldownBetweenFileEvents,
20788
20882
  watch = false,
20789
20883
  directoryToClean,
@@ -20799,16 +20893,27 @@ const build = async ({
20799
20893
  if (unexpectedParamNames.length > 0) {
20800
20894
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
20801
20895
  }
20802
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
20803
- if (!rootDirectoryUrlValidation.valid) {
20804
- 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}`);
20805
20899
  }
20806
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
20807
- const buildDirectoryUrlValidation = validateDirectoryUrl(buildDirectoryUrl);
20808
- if (!buildDirectoryUrlValidation.valid) {
20809
- 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}`);
20810
20916
  }
20811
- buildDirectoryUrl = buildDirectoryUrlValidation.value;
20812
20917
  }
20813
20918
  const operation = Abort.startOperation();
20814
20919
  operation.addAbortSignal(signal);
@@ -20819,12 +20924,6 @@ const build = async ({
20819
20924
  }, abort);
20820
20925
  });
20821
20926
  }
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
20927
  if (assetsDirectory && assetsDirectory[assetsDirectory.length - 1] !== "/") {
20829
20928
  assetsDirectory = `${assetsDirectory}/`;
20830
20929
  }
@@ -20835,9 +20934,10 @@ const build = async ({
20835
20934
  directoryToClean = new URL(assetsDirectory, buildDirectoryUrl).href;
20836
20935
  }
20837
20936
  }
20937
+ const jsenvInternalDirectoryUrl = determineJsenvInternalDirectoryUrl(sourceDirectoryUrl);
20838
20938
  const asFormattedBuildUrl = (generatedUrl, reference) => {
20839
20939
  if (base === "./") {
20840
- const urlRelativeToParent = urlToRelativeUrl(generatedUrl, reference.parentUrl === rootDirectoryUrl ? buildDirectoryUrl : reference.parentUrl);
20940
+ const urlRelativeToParent = urlToRelativeUrl(generatedUrl, reference.parentUrl === sourceDirectoryUrl ? buildDirectoryUrl : reference.parentUrl);
20841
20941
  if (urlRelativeToParent[0] !== ".") {
20842
20942
  // ensure "./" on relative url (otherwise it could be a "bare specifier")
20843
20943
  return `./${urlRelativeToParent}`;
@@ -20887,7 +20987,8 @@ build ${entryPointKeys.length} entry points`);
20887
20987
  const rawGraphKitchen = createKitchen({
20888
20988
  signal,
20889
20989
  logLevel,
20890
- rootDirectoryUrl,
20990
+ rootDirectoryUrl: sourceDirectoryUrl,
20991
+ jsenvInternalDirectoryUrl,
20891
20992
  urlGraph: rawGraph,
20892
20993
  build: true,
20893
20994
  runtimeCompat,
@@ -20906,7 +21007,7 @@ build ${entryPointKeys.length} entry points`);
20906
21007
  return null;
20907
21008
  }
20908
21009
  }, ...getCorePlugins({
20909
- rootDirectoryUrl,
21010
+ rootDirectoryUrl: sourceDirectoryUrl,
20910
21011
  urlGraph: rawGraph,
20911
21012
  runtimeCompat,
20912
21013
  urlAnalysis,
@@ -20923,7 +21024,7 @@ build ${entryPointKeys.length} entry points`);
20923
21024
  sourcemaps,
20924
21025
  sourcemapsSourcesContent,
20925
21026
  writeGeneratedFiles,
20926
- outDirectoryUrl: new URL(`.jsenv/build/`, rootDirectoryUrl)
21027
+ outDirectoryUrl: new URL("build/", jsenvInternalDirectoryUrl)
20927
21028
  });
20928
21029
  const buildUrlsGenerator = createBuildUrlsGenerator({
20929
21030
  buildDirectoryUrl,
@@ -20945,12 +21046,13 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
20945
21046
  const bundlers = {};
20946
21047
  const finalGraph = createUrlGraph();
20947
21048
  const urlAnalysisPlugin = jsenvPluginUrlAnalysis({
20948
- rootDirectoryUrl,
21049
+ rootDirectoryUrl: sourceDirectoryUrl,
20949
21050
  ...urlAnalysis
20950
21051
  });
20951
21052
  const finalGraphKitchen = createKitchen({
20952
21053
  logLevel,
20953
21054
  rootDirectoryUrl: buildDirectoryUrl,
21055
+ jsenvInternalDirectoryUrl,
20954
21056
  urlGraph: finalGraph,
20955
21057
  build: true,
20956
21058
  runtimeCompat,
@@ -21197,7 +21299,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21197
21299
  sourcemapsSourcesContent,
21198
21300
  sourcemapsSourcesRelative: !versioning,
21199
21301
  writeGeneratedFiles,
21200
- outDirectoryUrl: new URL(".jsenv/postbuild/", rootDirectoryUrl)
21302
+ outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl)
21201
21303
  });
21202
21304
  const finalEntryUrls = [];
21203
21305
  {
@@ -21206,7 +21308,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21206
21308
  });
21207
21309
  try {
21208
21310
  if (writeGeneratedFiles) {
21209
- await ensureEmptyDirectory(new URL(`.jsenv/build/`, rootDirectoryUrl));
21311
+ await ensureEmptyDirectory(new URL(`build/`, sourceDirectoryUrl));
21210
21312
  }
21211
21313
  const rawUrlGraphLoader = createUrlGraphLoader(rawGraphKitchen.kitchenContext);
21212
21314
  Object.keys(entryPoints).forEach(key => {
@@ -21214,7 +21316,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21214
21316
  trace: {
21215
21317
  message: `"${key}" in entryPoints parameter`
21216
21318
  },
21217
- parentUrl: rootDirectoryUrl,
21319
+ parentUrl: sourceDirectoryUrl,
21218
21320
  type: "entry_point",
21219
21321
  specifier: key
21220
21322
  });
@@ -21420,7 +21522,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21420
21522
  });
21421
21523
  try {
21422
21524
  if (writeGeneratedFiles) {
21423
- await ensureEmptyDirectory(new URL(`.jsenv/postbuild/`, rootDirectoryUrl));
21525
+ await ensureEmptyDirectory(new URL(`postbuild/`, jsenvInternalDirectoryUrl));
21424
21526
  }
21425
21527
  const finalUrlGraphLoader = createUrlGraphLoader(finalGraphKitchen.kitchenContext);
21426
21528
  entryUrls.forEach(entryUrl => {
@@ -21428,7 +21530,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21428
21530
  trace: {
21429
21531
  message: `entryPoint`
21430
21532
  },
21431
- parentUrl: rootDirectoryUrl,
21533
+ parentUrl: sourceDirectoryUrl,
21432
21534
  type: "entry_point",
21433
21535
  specifier: entryUrl
21434
21536
  });
@@ -21620,6 +21722,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21620
21722
  const versioningKitchen = createKitchen({
21621
21723
  logLevel: logger.level,
21622
21724
  rootDirectoryUrl: buildDirectoryUrl,
21725
+ jsenvInternalDirectoryUrl,
21623
21726
  urlGraph: finalGraph,
21624
21727
  build: true,
21625
21728
  runtimeCompat,
@@ -21712,7 +21815,7 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
21712
21815
  sourcemapsSourcesContent,
21713
21816
  sourcemapsSourcesRelative: true,
21714
21817
  writeGeneratedFiles,
21715
- outDirectoryUrl: new URL(".jsenv/postbuild/", finalGraphKitchen.rootDirectoryUrl)
21818
+ outDirectoryUrl: new URL("postbuild/", jsenvInternalDirectoryUrl)
21716
21819
  });
21717
21820
  const versioningUrlGraphLoader = createUrlGraphLoader(versioningKitchen.kitchenContext);
21718
21821
  finalEntryUrls.forEach(finalEntryUrl => {
@@ -22088,13 +22191,12 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22088
22191
  };
22089
22192
  startBuild();
22090
22193
  let startTimeout;
22091
- const clientFileChangeCallback = ({
22092
- relativeUrl,
22194
+ const stopWatchingSourceFiles = watchSourceFiles(sourceDirectoryUrl, ({
22195
+ url,
22093
22196
  event
22094
22197
  }) => {
22095
- const url = new URL(relativeUrl, rootDirectoryUrl).href;
22096
22198
  if (watchFilesTask) {
22097
- watchFilesTask.happen(`${url.slice(rootDirectoryUrl.length)} ${event}`);
22199
+ watchFilesTask.happen(`${url.slice(sourceDirectoryUrl.length)} ${event}`);
22098
22200
  watchFilesTask = null;
22099
22201
  }
22100
22202
  buildAbortController.abort();
@@ -22103,42 +22205,16 @@ ${ANSI.color(buildUrl, ANSI.MAGENTA)}
22103
22205
  // then logs about re-running the build happens
22104
22206
  clearTimeout(startTimeout);
22105
22207
  startTimeout = setTimeout(startBuild, 20);
22106
- };
22107
- const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22108
- watchPatterns: clientFiles,
22109
- cooldownBetweenFileEvents,
22208
+ }, {
22209
+ sourceFilesConfig,
22110
22210
  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
- }
22211
+ cooldownBetweenFileEvents
22136
22212
  });
22137
22213
  operation.addAbortCallback(() => {
22138
- stopWatchingClientFiles();
22214
+ stopWatchingSourceFiles();
22139
22215
  });
22140
22216
  await firstBuildPromise;
22141
- return stopWatchingClientFiles;
22217
+ return stopWatchingSourceFiles;
22142
22218
  };
22143
22219
  const findKey = (map, value) => {
22144
22220
  for (const [keyCandidate, valueCandidate] of map) {
@@ -22164,26 +22240,6 @@ const injectVersionIntoBuildUrl = ({
22164
22240
  const versionedUrl = setUrlFilename(buildUrl, versionedFilename);
22165
22241
  return versionedUrl;
22166
22242
  };
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
22243
  const isUsed = urlInfo => {
22188
22244
  // nothing uses this url anymore
22189
22245
  // - versioning update inline content
@@ -22206,57 +22262,6 @@ const canUseVersionedUrl = urlInfo => {
22206
22262
  return urlInfo.type !== "webmanifest";
22207
22263
  };
22208
22264
 
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
22265
  /*
22261
22266
  * This plugin is very special because it is here
22262
22267
  * to provide "serverEvents" used by other plugins
@@ -22320,7 +22325,9 @@ const createFileService = ({
22320
22325
  serverStopCallbacks,
22321
22326
  serverEventsDispatcher,
22322
22327
  contextCache,
22323
- rootDirectoryUrl,
22328
+ sourceDirectoryUrl,
22329
+ sourceMainFilePath,
22330
+ sourceFilesConfig,
22324
22331
  runtimeCompat,
22325
22332
  plugins,
22326
22333
  urlAnalysis,
@@ -22329,8 +22336,6 @@ const createFileService = ({
22329
22336
  supervisor,
22330
22337
  transpilation,
22331
22338
  clientAutoreload,
22332
- clientFiles,
22333
- clientMainFileUrl,
22334
22339
  cooldownBetweenFileEvents,
22335
22340
  explorer,
22336
22341
  cacheControl,
@@ -22340,49 +22345,18 @@ const createFileService = ({
22340
22345
  sourcemapsSourcesContent,
22341
22346
  writeGeneratedFiles
22342
22347
  }) => {
22343
- const jsenvDirectoryUrl = new URL(".jsenv/", rootDirectoryUrl).href;
22344
22348
  const clientFileChangeCallbackList = [];
22345
22349
  const clientFilesPruneCallbackList = [];
22346
- const clientFilePatterns = {
22347
- ...clientFiles,
22348
- ".jsenv/": false
22349
- };
22350
- const onFileChange = url => {
22350
+ const stopWatchingSourceFiles = watchSourceFiles(sourceDirectoryUrl, fileInfo => {
22351
22351
  clientFileChangeCallbackList.forEach(callback => {
22352
- callback(url);
22352
+ callback(fileInfo);
22353
22353
  });
22354
- };
22355
- const stopWatchingClientFiles = registerDirectoryLifecycle(rootDirectoryUrl, {
22356
- watchPatterns: clientFilePatterns,
22357
- cooldownBetweenFileEvents,
22354
+ }, {
22355
+ sourceFilesConfig,
22358
22356
  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
- }
22357
+ cooldownBetweenFileEvents
22384
22358
  });
22385
- serverStopCallbacks.push(stopWatchingClientFiles);
22359
+ serverStopCallbacks.push(stopWatchingSourceFiles);
22386
22360
  const getOrCreateContext = request => {
22387
22361
  const {
22388
22362
  runtimeName,
@@ -22394,8 +22368,8 @@ const createFileService = ({
22394
22368
  return existingContext;
22395
22369
  }
22396
22370
  const watchAssociations = URL_META.resolveAssociations({
22397
- watch: clientFilePatterns
22398
- }, rootDirectoryUrl);
22371
+ watch: stopWatchingSourceFiles.watchPatterns
22372
+ }, sourceDirectoryUrl);
22399
22373
  const urlGraph = createUrlGraph();
22400
22374
  clientFileChangeCallbackList.push(({
22401
22375
  url
@@ -22418,24 +22392,32 @@ const createFileService = ({
22418
22392
  const clientRuntimeCompat = {
22419
22393
  [runtimeName]: runtimeVersion
22420
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
+ }
22421
22402
  const kitchen = createKitchen({
22422
22403
  signal,
22423
22404
  logLevel,
22424
- rootDirectoryUrl,
22405
+ rootDirectoryUrl: sourceDirectoryUrl,
22406
+ jsenvInternalDirectoryUrl,
22425
22407
  urlGraph,
22426
22408
  dev: true,
22427
22409
  runtimeCompat,
22428
22410
  clientRuntimeCompat,
22429
22411
  systemJsTranspilation: !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "script_type_module") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_dynamic") || !RUNTIME_COMPAT.isSupported(clientRuntimeCompat, "import_meta"),
22430
22412
  plugins: [...plugins, ...getCorePlugins({
22431
- rootDirectoryUrl,
22413
+ rootDirectoryUrl: sourceDirectoryUrl,
22414
+ mainFileUrl,
22432
22415
  runtimeCompat,
22433
22416
  urlAnalysis,
22434
22417
  urlResolution,
22435
22418
  fileSystemMagicRedirection,
22436
22419
  supervisor,
22437
22420
  transpilation,
22438
- clientMainFileUrl,
22439
22421
  clientAutoreload,
22440
22422
  clientFileChangeCallbackList,
22441
22423
  clientFilesPruneCallbackList,
@@ -22449,7 +22431,7 @@ const createFileService = ({
22449
22431
  sourcemapsSourcesProtocol,
22450
22432
  sourcemapsSourcesContent,
22451
22433
  writeGeneratedFiles,
22452
- outDirectoryUrl: `${rootDirectoryUrl}.jsenv/${runtimeName}@${runtimeVersion}/`
22434
+ outDirectoryUrl: new URL(`${runtimeName}@${runtimeVersion}/`, jsenvInternalDirectoryUrl)
22453
22435
  });
22454
22436
  urlGraph.createUrlInfoCallbackRef.current = urlInfo => {
22455
22437
  const {
@@ -22528,7 +22510,7 @@ const createFileService = ({
22528
22510
  if (serverEventNames.length > 0) {
22529
22511
  Object.keys(allServerEvents).forEach(serverEventName => {
22530
22512
  allServerEvents[serverEventName]({
22531
- rootDirectoryUrl,
22513
+ rootDirectoryUrl: sourceDirectoryUrl,
22532
22514
  urlGraph,
22533
22515
  dev: true,
22534
22516
  sendServerEvent: data => {
@@ -22544,7 +22526,7 @@ const createFileService = ({
22544
22526
  }
22545
22527
  }
22546
22528
  const context = {
22547
- rootDirectoryUrl,
22529
+ rootDirectoryUrl: sourceDirectoryUrl,
22548
22530
  dev: true,
22549
22531
  runtimeName,
22550
22532
  runtimeVersion,
@@ -22555,13 +22537,6 @@ const createFileService = ({
22555
22537
  return context;
22556
22538
  };
22557
22539
  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
22540
  const {
22566
22541
  urlGraph,
22567
22542
  kitchen
@@ -22571,16 +22546,16 @@ const createFileService = ({
22571
22546
  return responseFromPlugin;
22572
22547
  }
22573
22548
  let reference;
22574
- const parentUrl = inferParentFromRequest(request, rootDirectoryUrl);
22549
+ const parentUrl = inferParentFromRequest(request, sourceDirectoryUrl);
22575
22550
  if (parentUrl) {
22576
22551
  reference = urlGraph.inferReference(request.resource, parentUrl);
22577
22552
  }
22578
22553
  if (!reference) {
22579
22554
  const entryPoint = kitchen.injectReference({
22580
22555
  trace: {
22581
- message: parentUrl || rootDirectoryUrl
22556
+ message: parentUrl || sourceDirectoryUrl
22582
22557
  },
22583
- parentUrl: parentUrl || rootDirectoryUrl,
22558
+ parentUrl: parentUrl || sourceDirectoryUrl,
22584
22559
  type: "http_request",
22585
22560
  specifier: request.resource
22586
22561
  });
@@ -22702,7 +22677,7 @@ const createFileService = ({
22702
22677
  accept: "text/html"
22703
22678
  },
22704
22679
  canReadDirectory: true,
22705
- rootDirectoryUrl
22680
+ rootDirectoryUrl: sourceDirectoryUrl
22706
22681
  });
22707
22682
  }
22708
22683
  if (code === "NOT_ALLOWED") {
@@ -22729,7 +22704,7 @@ const createFileService = ({
22729
22704
  }
22730
22705
  };
22731
22706
  };
22732
- const inferParentFromRequest = (request, rootDirectoryUrl) => {
22707
+ const inferParentFromRequest = (request, sourceDirectoryUrl) => {
22733
22708
  const {
22734
22709
  referer
22735
22710
  } = request.headers;
@@ -22750,7 +22725,7 @@ const inferParentFromRequest = (request, rootDirectoryUrl) => {
22750
22725
  return moveUrl({
22751
22726
  url: referer,
22752
22727
  from: `${request.origin}/`,
22753
- to: rootDirectoryUrl,
22728
+ to: sourceDirectoryUrl,
22754
22729
  preferAbsolute: true
22755
22730
  });
22756
22731
  };
@@ -22760,38 +22735,28 @@ const inferParentFromRequest = (request, rootDirectoryUrl) => {
22760
22735
  * - cook source files according to jsenv plugins
22761
22736
  * - inject code to autoreload the browser when a file is modified
22762
22737
  * @param {Object} devServerParameters
22763
- * @param {string|url} devServerParameters.rootDirectoryUrl Root directory of the project
22738
+ * @param {string|url} devServerParameters.sourceDirectoryUrl Root directory of the project
22764
22739
  * @return {Object} A dev server object
22765
22740
  */
22766
22741
  const startDevServer = async ({
22767
- signal = new AbortController().signal,
22768
- handleSIGINT = true,
22769
- logLevel = "info",
22770
- serverLogLevel = "warn",
22742
+ sourceDirectoryUrl,
22743
+ sourceMainFilePath,
22744
+ port = 3456,
22745
+ hostname,
22746
+ acceptAnyIp,
22771
22747
  https,
22772
22748
  // it's better to use http1 by default because it allows to get statusText in devtools
22773
22749
  // which gives valuable information when there is errors
22774
22750
  http2 = false,
22775
- hostname,
22776
- port = 3456,
22777
- acceptAnyIp,
22778
- keepProcessAlive = true,
22751
+ logLevel = process.env.IMPORTED_BY_TEST_PLAN ? "warn" : "info",
22752
+ serverLogLevel = "warn",
22779
22753
  services = [],
22754
+ signal = new AbortController().signal,
22755
+ handleSIGINT = true,
22756
+ keepProcessAlive = true,
22780
22757
  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
- },
22758
+ sourceFilesConfig,
22791
22759
  clientAutoreload = true,
22792
- clientMainFileUrl,
22793
- devServerAutoreload = false,
22794
- devServerMainFile = getCallerPosition().url,
22795
22760
  cooldownBetweenFileEvents,
22796
22761
  // runtimeCompat is the runtimeCompat for the build
22797
22762
  // when specified, dev server use it to warn in case
@@ -22823,11 +22788,7 @@ const startDevServer = async ({
22823
22788
  if (unexpectedParamNames.length > 0) {
22824
22789
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
22825
22790
  }
22826
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
22827
- if (!rootDirectoryUrlValidation.valid) {
22828
- throw new TypeError(`rootDirectoryUrl ${rootDirectoryUrlValidation.message}, got ${rootDirectoryUrl}`);
22829
- }
22830
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
22791
+ sourceDirectoryUrl = assertAndNormalizeDirectoryUrl(sourceDirectoryUrl, "sourceDirectoryUrl");
22831
22792
  }
22832
22793
  const logger = createLogger({
22833
22794
  logLevel
@@ -22841,70 +22802,6 @@ const startDevServer = async ({
22841
22802
  }, abort);
22842
22803
  });
22843
22804
  }
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
22805
  const startDevServerTask = createTaskLog("start dev server", {
22909
22806
  disabled: !logger.levels.info
22910
22807
  });
@@ -22935,7 +22832,30 @@ const startDevServer = async ({
22935
22832
  accessControlAllowedRequestHeaders: [...jsenvAccessControlAllowedHeaders, "x-jsenv-execution-id"],
22936
22833
  accessControlAllowCredentials: true,
22937
22834
  timingAllowOrigin: true
22938
- }), ...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, {
22939
22859
  name: "jsenv:omega_file_service",
22940
22860
  handleRequest: createFileService({
22941
22861
  signal,
@@ -22943,7 +22863,9 @@ const startDevServer = async ({
22943
22863
  serverStopCallbacks,
22944
22864
  serverEventsDispatcher,
22945
22865
  contextCache,
22946
- rootDirectoryUrl,
22866
+ sourceDirectoryUrl,
22867
+ sourceMainFilePath,
22868
+ sourceFilesConfig,
22947
22869
  runtimeCompat,
22948
22870
  plugins,
22949
22871
  urlAnalysis,
@@ -22952,8 +22874,6 @@ const startDevServer = async ({
22952
22874
  supervisor,
22953
22875
  transpilation,
22954
22876
  clientAutoreload,
22955
- clientFiles,
22956
- clientMainFileUrl,
22957
22877
  cooldownBetweenFileEvents,
22958
22878
  explorer,
22959
22879
  cacheControl,
@@ -23026,9 +22946,6 @@ const startDevServer = async ({
23026
22946
  logger.info(`- ${server.origins[key]}`);
23027
22947
  });
23028
22948
  logger.info(``);
23029
- if (reloadableWorker && reloadableWorker.isWorker) {
23030
- parentPort.postMessage(server.origin);
23031
- }
23032
22949
  return {
23033
22950
  origin: server.origin,
23034
22951
  stop: () => {
@@ -23038,6 +22955,80 @@ const startDevServer = async ({
23038
22955
  };
23039
22956
  };
23040
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
+
23041
23032
  const generateCoverageJsonFile = async ({
23042
23033
  coverage,
23043
23034
  coverageJsonFileUrl,
@@ -23127,7 +23118,7 @@ const readNodeV8CoverageDirectory = async ({
23127
23118
  try {
23128
23119
  operation.throwIfAborted();
23129
23120
  const dirContent = await tryReadDirectory();
23130
- const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE);
23121
+ const coverageDirectoryUrl = assertAndNormalizeDirectoryUrl(NODE_V8_COVERAGE, "NODE_V8_COVERAGE");
23131
23122
  await dirContent.reduce(async (previous, dirEntry) => {
23132
23123
  operation.throwIfAborted();
23133
23124
  await previous;
@@ -23750,37 +23741,6 @@ const run = async ({
23750
23741
  return result;
23751
23742
  };
23752
23743
 
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
23744
  const ensureGlobalGc = () => {
23785
23745
  if (!global.gc) {
23786
23746
  v8.setFlagsFromString("--expose_gc");
@@ -24208,7 +24168,7 @@ const executePlan = async (plan, {
24208
24168
  coverageMethodForBrowsers,
24209
24169
  coverageMethodForNodeJs,
24210
24170
  coverageV8ConflictWarning,
24211
- coverageTempDirectoryRelativeUrl,
24171
+ coverageTempDirectoryUrl,
24212
24172
  beforeExecutionCallback = () => {},
24213
24173
  afterExecutionCallback = () => {}
24214
24174
  } = {}) => {
@@ -24218,30 +24178,6 @@ const executePlan = async (plan, {
24218
24178
  const stopAfterAllSignal = {
24219
24179
  notify: () => {}
24220
24180
  };
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
24181
  const multipleExecutionsOperation = Abort.startOperation();
24246
24182
  multipleExecutionsOperation.addAbortSignal(signal);
24247
24183
  if (handleSIGINT) {
@@ -24259,19 +24195,6 @@ const executePlan = async (plan, {
24259
24195
  multipleExecutionsOperation.addAbortSignal(failFastAbortController.signal);
24260
24196
  }
24261
24197
  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
24198
  if (gcBetweenExecutions) {
24276
24199
  ensureGlobalGc();
24277
24200
  }
@@ -24318,15 +24241,6 @@ const executePlan = async (plan, {
24318
24241
  coverageMethodForNodeJs,
24319
24242
  stopAfterAllSignal
24320
24243
  };
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
24244
  logger.debug(`Generate executions`);
24331
24245
  const executionSteps = await getExecutionAsSteps({
24332
24246
  plan,
@@ -24423,7 +24337,7 @@ const executePlan = async (plan, {
24423
24337
  executionResult = await run({
24424
24338
  signal: multipleExecutionsOperation.signal,
24425
24339
  logger,
24426
- allocatedMs: executionParams.allocatedMs,
24340
+ allocatedMs: typeof executionParams.allocatedMs === "function" ? executionParams.allocatedMs(beforeExecutionInfo) : executionParams.allocatedMs,
24427
24341
  keepRunning,
24428
24342
  mirrorConsole: false,
24429
24343
  // file are executed in parallel, log would be a mess to read
@@ -24639,8 +24553,8 @@ const executeInParallel = async ({
24639
24553
  /**
24640
24554
  * Execute a list of files and log how it goes.
24641
24555
  * @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
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
24644
24558
  * @param {Object} testPlanParameters.testPlan Object associating patterns leading to files to runtimes where they should be executed
24645
24559
  * @param {boolean} [testPlanParameters.completedExecutionLogAbbreviation=false] Abbreviate completed execution information to shorten terminal output
24646
24560
  * @param {boolean} [testPlanParameters.completedExecutionLogMerging=false] Merge completed execution logs to shorten terminal output
@@ -24666,7 +24580,8 @@ const executeTestPlan = async ({
24666
24580
  logFileRelativeUrl = ".jsenv/test_plan_debug.txt",
24667
24581
  completedExecutionLogAbbreviation = false,
24668
24582
  completedExecutionLogMerging = false,
24669
- rootDirectoryUrl,
24583
+ testDirectoryUrl,
24584
+ devServerModuleUrl,
24670
24585
  devServerOrigin,
24671
24586
  testPlan,
24672
24587
  updateProcessExitCode = true,
@@ -24683,7 +24598,7 @@ const executeTestPlan = async ({
24683
24598
  gcBetweenExecutions = logMemoryHeapUsage,
24684
24599
  coverageEnabled = process.argv.includes("--coverage"),
24685
24600
  coverageConfig = {
24686
- "./src/": true
24601
+ "./**/*": true
24687
24602
  },
24688
24603
  coverageIncludeMissing = true,
24689
24604
  coverageAndExecutionAllowed = false,
@@ -24691,30 +24606,87 @@ const executeTestPlan = async ({
24691
24606
  coverageMethodForBrowsers = "playwright_api",
24692
24607
  // "istanbul" also accepted
24693
24608
  coverageV8ConflictWarning = true,
24694
- coverageTempDirectoryRelativeUrl = "./.coverage/tmp/",
24609
+ coverageTempDirectoryUrl,
24610
+ coverageReportRootDirectoryUrl,
24695
24611
  // skip empty means empty files won't appear in the coverage reports (json and html)
24696
24612
  coverageReportSkipEmpty = false,
24697
24613
  // skip full means file with 100% coverage won't appear in coverage reports (json and html)
24698
24614
  coverageReportSkipFull = false,
24699
24615
  coverageReportTextLog = true,
24700
- coverageReportJsonFile = process.env.CI ? null : "./.coverage/coverage.json",
24701
- coverageReportHtmlDirectory = process.env.CI ? "./.coverage/" : null,
24616
+ coverageReportJson = process.env.CI,
24617
+ coverageReportJsonFileUrl,
24618
+ coverageReportHtml = !process.env.CI,
24619
+ coverageReportHtmlDirectoryUrl,
24702
24620
  ...rest
24703
24621
  }) => {
24622
+ let someNeedsServer = false;
24623
+ let someNodeRuntime = false;
24624
+ let stopDevServerNeeded = false;
24625
+ const runtimes = {};
24704
24626
  // param validation
24705
24627
  {
24706
24628
  const unexpectedParamNames = Object.keys(rest);
24707
24629
  if (unexpectedParamNames.length > 0) {
24708
24630
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
24709
24631
  }
24710
- const rootDirectoryUrlValidation = validateDirectoryUrl(rootDirectoryUrl);
24711
- if (!rootDirectoryUrlValidation.valid) {
24712
- 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}`);
24713
24635
  }
24714
- rootDirectoryUrl = rootDirectoryUrlValidation.value;
24715
24636
  if (typeof testPlan !== "object") {
24716
24637
  throw new Error(`testPlan must be an object, got ${testPlan}`);
24717
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
+ }
24718
24690
  if (coverageEnabled) {
24719
24691
  if (typeof coverageConfig !== "object") {
24720
24692
  throw new TypeError(`coverageConfig must be an object, got ${coverageConfig}`);
@@ -24742,14 +24714,63 @@ const executeTestPlan = async ({
24742
24714
  }));
24743
24715
  }
24744
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
+ }
24745
24741
  }
24746
24742
  }
24747
24743
  const logger = createLogger({
24748
24744
  logLevel
24749
24745
  });
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`);
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
+ }
24752
24769
  }
24770
+ testPlan = {
24771
+ ...testPlan,
24772
+ "**/.jsenv/": null
24773
+ };
24753
24774
  const result = await executePlan(testPlan, {
24754
24775
  signal,
24755
24776
  handleSIGINT,
@@ -24763,7 +24784,7 @@ const executeTestPlan = async ({
24763
24784
  logFileRelativeUrl,
24764
24785
  completedExecutionLogMerging,
24765
24786
  completedExecutionLogAbbreviation,
24766
- rootDirectoryUrl,
24787
+ rootDirectoryUrl: testDirectoryUrl,
24767
24788
  devServerOrigin,
24768
24789
  maxExecutionsInParallel,
24769
24790
  defaultMsAllocatedPerExecution,
@@ -24777,8 +24798,17 @@ const executeTestPlan = async ({
24777
24798
  coverageMethodForBrowsers,
24778
24799
  coverageMethodForNodeJs,
24779
24800
  coverageV8ConflictWarning,
24780
- coverageTempDirectoryRelativeUrl
24801
+ coverageTempDirectoryUrl
24781
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
+ }
24782
24812
  if (updateProcessExitCode && result.planSummary.counters.total !== result.planSummary.counters.completed) {
24783
24813
  process.exitCode = 1;
24784
24814
  }
@@ -24789,26 +24819,21 @@ const executeTestPlan = async ({
24789
24819
  // keep this one first because it does ensureEmptyDirectory
24790
24820
  // and in case coverage json file gets written in the same directory
24791
24821
  // 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`;
24822
+ if (coverageEnabled && coverageReportHtml) {
24823
+ await ensureEmptyDirectory(coverageReportHtmlDirectoryUrl);
24824
+ const htmlCoverageDirectoryIndexFileUrl = `${coverageReportHtmlDirectoryUrl}index.html`;
24799
24825
  logger.info(`-> ${urlToFileSystemPath(htmlCoverageDirectoryIndexFileUrl)}`);
24800
24826
  promises.push(generateCoverageHtmlDirectory(planCoverage, {
24801
- rootDirectoryUrl,
24802
- coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(coverageHtmlDirectoryUrl, rootDirectoryUrl),
24827
+ rootDirectoryUrl: coverageReportRootDirectoryUrl,
24828
+ coverageHtmlDirectoryRelativeUrl: urlToRelativeUrl(coverageReportHtmlDirectoryUrl, coverageReportRootDirectoryUrl),
24803
24829
  coverageReportSkipEmpty,
24804
24830
  coverageReportSkipFull
24805
24831
  }));
24806
24832
  }
24807
- if (coverageEnabled && coverageReportJsonFile) {
24808
- const coverageJsonFileUrl = new URL(coverageReportJsonFile, rootDirectoryUrl).href;
24833
+ if (coverageEnabled && coverageReportJson) {
24809
24834
  promises.push(generateCoverageJsonFile({
24810
24835
  coverage: result.planCoverage,
24811
- coverageJsonFileUrl,
24836
+ coverageJsonFileUrl: coverageReportJsonFileUrl,
24812
24837
  logger
24813
24838
  }));
24814
24839
  }
@@ -25672,7 +25697,6 @@ nodeChildProcess.run = async ({
25672
25697
  env: envForChildProcess
25673
25698
  });
25674
25699
  logger.debug(createDetailedMessage$1(`child process forked (pid ${childProcess.pid})`, {
25675
- "execArgv": execArgv.join(`\n`),
25676
25700
  "custom env": JSON.stringify(env, null, " ")
25677
25701
  }));
25678
25702
  // if we pass stream, pipe them https://github.com/sindresorhus/execa/issues/81
@@ -26195,32 +26219,23 @@ const onceWorkerThreadEvent = (worker, type, callback) => {
26195
26219
  /**
26196
26220
  * Start a server for build files.
26197
26221
  * @param {Object} buildServerParameters
26198
- * @param {string|url} buildServerParameters.rootDirectoryUrl Root directory of the project
26199
26222
  * @param {string|url} buildServerParameters.buildDirectoryUrl Directory where build files are written
26200
26223
  * @return {Object} A build server object
26201
26224
  */
26202
26225
  const startBuildServer = async ({
26203
- signal = new AbortController().signal,
26204
- handleSIGINT = true,
26205
- logLevel,
26206
- serverLogLevel = "warn",
26207
- https,
26208
- http2,
26209
- acceptAnyIp,
26210
- hostname,
26226
+ buildDirectoryUrl,
26227
+ buildMainFilePath = "index.html",
26211
26228
  port = 9779,
26212
26229
  services = [],
26230
+ acceptAnyIp,
26231
+ hostname,
26232
+ https,
26233
+ http2,
26234
+ logLevel,
26235
+ serverLogLevel = "warn",
26236
+ signal = new AbortController().signal,
26237
+ handleSIGINT = true,
26213
26238
  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
26239
  ...rest
26225
26240
  }) => {
26226
26241
  // params validation
@@ -26229,31 +26244,22 @@ const startBuildServer = async ({
26229
26244
  if (unexpectedParamNames.length > 0) {
26230
26245
  throw new TypeError(`${unexpectedParamNames.join(",")}: there is no such param`);
26231
26246
  }
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}`);
26247
+ buildDirectoryUrl = assertAndNormalizeDirectoryUrl(buildDirectoryUrl, "buildDirectoryUrl");
26248
+ if (buildMainFilePath) {
26249
+ if (typeof buildMainFilePath !== "string") {
26250
+ throw new TypeError(`buildMainFilePath must be a string, got ${buildMainFilePath}`);
26245
26251
  }
26246
- if (buildIndexPath[0] === "/") {
26247
- buildIndexPath = buildIndexPath.slice(1);
26252
+ if (buildMainFilePath[0] === "/") {
26253
+ buildMainFilePath = buildMainFilePath.slice(1);
26248
26254
  } else {
26249
- const buildIndexUrl = new URL(buildIndexPath, buildDirectoryUrl).href;
26250
- if (!buildIndexUrl.startsWith(buildDirectoryUrl)) {
26251
- 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}`);
26252
26258
  }
26253
- buildIndexPath = buildIndexUrl.slice(buildDirectoryUrl.length);
26259
+ buildMainFilePath = buildMainFileUrl.slice(buildDirectoryUrl.length);
26254
26260
  }
26255
- if (!existsSync(new URL(buildIndexPath, buildDirectoryUrl))) {
26256
- buildIndexPath = null;
26261
+ if (!existsSync(new URL(buildMainFilePath, buildDirectoryUrl))) {
26262
+ buildMainFilePath = null;
26257
26263
  }
26258
26264
  }
26259
26265
  }
@@ -26269,73 +26275,6 @@ const startBuildServer = async ({
26269
26275
  }, abort);
26270
26276
  });
26271
26277
  }
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
26278
  const startBuildServerTask = createTaskLog("start build server", {
26340
26279
  disabled: !logger.levels.info
26341
26280
  });
@@ -26366,7 +26305,7 @@ const startBuildServer = async ({
26366
26305
  name: "jsenv:build_files_service",
26367
26306
  handleRequest: createBuildFilesService({
26368
26307
  buildDirectoryUrl,
26369
- buildIndexPath
26308
+ buildMainFilePath
26370
26309
  })
26371
26310
  }, jsenvServiceErrorHandler({
26372
26311
  sendErrorDetails: true
@@ -26382,9 +26321,6 @@ const startBuildServer = async ({
26382
26321
  logger.info(`- ${server.origins[key]}`);
26383
26322
  });
26384
26323
  logger.info(``);
26385
- if (reloadableWorker && reloadableWorker.isWorker) {
26386
- parentPort.postMessage(server.origin);
26387
- }
26388
26324
  return {
26389
26325
  origin: server.origin,
26390
26326
  stop: () => {
@@ -26394,14 +26330,14 @@ const startBuildServer = async ({
26394
26330
  };
26395
26331
  const createBuildFilesService = ({
26396
26332
  buildDirectoryUrl,
26397
- buildIndexPath
26333
+ buildMainFilePath
26398
26334
  }) => {
26399
26335
  return request => {
26400
26336
  const urlIsVersioned = new URL(request.url).searchParams.has("v");
26401
- if (buildIndexPath && request.resource === "/") {
26337
+ if (buildMainFilePath && request.resource === "/") {
26402
26338
  request = {
26403
26339
  ...request,
26404
- resource: `/${buildIndexPath}`
26340
+ resource: `/${buildMainFilePath}`
26405
26341
  };
26406
26342
  }
26407
26343
  return fetchFileSystem(new URL(request.resource.slice(1), buildDirectoryUrl), {
@@ -26448,7 +26384,7 @@ const execute = async ({
26448
26384
  const logger = createLogger({
26449
26385
  logLevel
26450
26386
  });
26451
- rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl);
26387
+ rootDirectoryUrl = assertAndNormalizeDirectoryUrl(rootDirectoryUrl, "rootDirectoryUrl");
26452
26388
  const executeOperation = Abort.startOperation();
26453
26389
  executeOperation.addAbortSignal(signal);
26454
26390
  if (handleSIGINT) {