@jsenv/core 41.3.1 → 41.4.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.
@@ -132,6 +132,136 @@ const watchSourceFiles = (
132
132
 
133
133
  const jsenvCoreDirectoryUrl = new URL("../", import.meta.url);
134
134
 
135
+ /*
136
+ * Compares what a package.json declares with what is actually inside
137
+ * node_modules, so the dev server can tell a dependency apart when it is
138
+ * missing (never installed) or outdated (installed at an other version).
139
+ *
140
+ * Only the dependencies declared by a package are looked at, never the
141
+ * transitive ones: a declared dependency is what pulls the rest, so it is
142
+ * enough to know whether an install is needed or over.
143
+ *
144
+ * Only exact declared versions ("1.2.3") are compared: a range ("^1.2.3"), a
145
+ * file/workspace protocol or a tag cannot be checked without resolving what npm
146
+ * would pick, which is way beyond what is needed here.
147
+ */
148
+
149
+
150
+ const DEPENDENCY_FIELDS = [
151
+ "dependencies",
152
+ "devDependencies",
153
+ "optionalDependencies",
154
+ ];
155
+
156
+ const packageNameFromSpecifier = (specifier) => {
157
+ const parts = specifier.split("/");
158
+ if (specifier[0] === "@") {
159
+ return parts.slice(0, 2).join("/");
160
+ }
161
+ return parts[0];
162
+ };
163
+
164
+ /*
165
+ * declaringDirectoryUrl is the package directory the importer belongs to, which
166
+ * is not always the project one: a file inside node_modules resolves its bare
167
+ * specifiers against the dependencies of the package containing it.
168
+ */
169
+ const readDependencyStatus = (
170
+ packageDirectory,
171
+ packageName,
172
+ declaringDirectoryUrl = packageDirectory.url,
173
+ ) => {
174
+ const packageJSON = readPackageJSON(packageDirectory, declaringDirectoryUrl);
175
+ if (!packageJSON) {
176
+ return null;
177
+ }
178
+ const declaredVersion = readDeclaredVersion(packageJSON, packageName);
179
+ if (!declaredVersion) {
180
+ return null;
181
+ }
182
+ return createStatus(packageDirectory, {
183
+ packageName,
184
+ declaredVersion,
185
+ declaringDirectoryUrl,
186
+ declaredBy: packageJSON.name,
187
+ });
188
+ };
189
+
190
+ const createStatus = (
191
+ packageDirectory,
192
+ { packageName, declaredVersion, declaringDirectoryUrl, declaredBy },
193
+ ) => {
194
+ const status = {
195
+ packageName,
196
+ declaredVersion,
197
+ declaredBy,
198
+ installedVersion: null,
199
+ state: "missing",
200
+ };
201
+ const installedDirectoryUrl = findInstalledDirectoryUrl(
202
+ declaringDirectoryUrl,
203
+ packageName,
204
+ );
205
+ if (!installedDirectoryUrl) {
206
+ return status;
207
+ }
208
+ const installedPackageJSON = readPackageJSON(
209
+ packageDirectory,
210
+ installedDirectoryUrl,
211
+ );
212
+ status.installedVersion = installedPackageJSON
213
+ ? installedPackageJSON.version
214
+ : null;
215
+ status.state =
216
+ isExactVersion(declaredVersion) &&
217
+ status.installedVersion !== declaredVersion
218
+ ? "outdated"
219
+ : "installed";
220
+ return status;
221
+ };
222
+
223
+ const findInstalledDirectoryUrl = (declaringDirectoryUrl, packageName) => {
224
+ let directoryUrl = declaringDirectoryUrl;
225
+ while (directoryUrl) {
226
+ const candidateUrl = `${directoryUrl}node_modules/${packageName}/`;
227
+ if (existsSync(new URL(`${candidateUrl}package.json`))) {
228
+ return candidateUrl;
229
+ }
230
+ const parentUrl = new URL("../", directoryUrl).href;
231
+ if (parentUrl === directoryUrl) {
232
+ return null;
233
+ }
234
+ directoryUrl = parentUrl;
235
+ }
236
+ return null;
237
+ };
238
+
239
+ const readDeclaredVersion = (packageJSON, packageName) => {
240
+ for (const field of DEPENDENCY_FIELDS) {
241
+ const dependencies = packageJSON[field];
242
+ if (dependencies && dependencies[packageName]) {
243
+ return dependencies[packageName];
244
+ }
245
+ }
246
+ return null;
247
+ };
248
+
249
+ // an install in progress can be caught halfway, with a package.json not written yet
250
+ const readPackageJSON = (packageDirectory, directoryUrl) => {
251
+ if (!directoryUrl) {
252
+ return null;
253
+ }
254
+ try {
255
+ return packageDirectory.read(directoryUrl);
256
+ } catch {
257
+ return null;
258
+ }
259
+ };
260
+
261
+ const isExactVersion = (declaredVersion) => {
262
+ return /^\d+\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(declaredVersion);
263
+ };
264
+
135
265
  const createResolveUrlError = ({
136
266
  jsenvPluginsController,
137
267
  reference,
@@ -171,6 +301,18 @@ ${reason}`,
171
301
  });
172
302
  }
173
303
  if (error.code === "MODULE_NOT_FOUND") {
304
+ const notInstalledStatus = readNotInstalledStatus(reference);
305
+ if (notInstalledStatus) {
306
+ const { packageName, declaredVersion, declaredBy, isProjectDependency } =
307
+ notInstalledStatus;
308
+ return createFailedToResolveUrlError({
309
+ "reason": isProjectDependency
310
+ ? `"${packageName}" is declared in package.json but not installed`
311
+ : `"${packageName}" is declared by "${declaredBy}" but not installed`,
312
+ "declared version": declaredVersion,
313
+ "suggestion": `run npm install, the page will reload once "${packageName}" is installed`,
314
+ });
315
+ }
174
316
  const bareSpecifierError = createFailedToResolveUrlError({
175
317
  reason: `"${reference.specifier}" is a bare specifier but cannot be remapped to a package`,
176
318
  });
@@ -425,6 +567,32 @@ const getErrorTrace = (error, reference) => {
425
567
  };
426
568
  };
427
569
 
570
+ // a bare specifier is resolved against the dependencies of the package
571
+ // containing the file that imports it, which is the project one for a source
572
+ // file but an other one for a file inside node_modules
573
+ const readNotInstalledStatus = (reference) => {
574
+ const { ownerUrlInfo } = reference;
575
+ const { packageDirectory } = ownerUrlInfo.context;
576
+ if (!packageDirectory) {
577
+ return null;
578
+ }
579
+ const declaringDirectoryUrl =
580
+ packageDirectory.find(ownerUrlInfo.url) || packageDirectory.url;
581
+ const packageName = packageNameFromSpecifier(reference.specifier);
582
+ const status = readDependencyStatus(
583
+ packageDirectory,
584
+ packageName,
585
+ declaringDirectoryUrl,
586
+ );
587
+ if (!status || status.state !== "missing") {
588
+ return null;
589
+ }
590
+ return {
591
+ ...status,
592
+ isProjectDependency: declaringDirectoryUrl === packageDirectory.url,
593
+ };
594
+ };
595
+
428
596
  const detailsFromFirstReference = (reference) => {
429
597
  const referenceInProject = getFirstReferenceInProject(reference);
430
598
  if (
@@ -8288,6 +8456,7 @@ const jsenvPluginAutoreloadClient = () => {
8288
8456
  const jsenvPluginAutoreloadServer = ({
8289
8457
  clientFileChangeEventEmitter,
8290
8458
  clientFileDereferencedEventEmitter,
8459
+ reloadRequestEventEmitter,
8291
8460
  }) => {
8292
8461
  return {
8293
8462
  name: "jsenv:autoreload_server",
@@ -8616,6 +8785,15 @@ const jsenvPluginAutoreloadServer = ({
8616
8785
  });
8617
8786
  },
8618
8787
  );
8788
+ // something outside the url graph wants the page back from scratch,
8789
+ // typically a dependency that just got installed into node_modules
8790
+ reloadRequestEventEmitter.on(({ cause, reason }) => {
8791
+ serverEventInfo.sendServerEvent({
8792
+ cause,
8793
+ type: "full",
8794
+ typeReason: reason,
8795
+ });
8796
+ });
8619
8797
  },
8620
8798
  },
8621
8799
  serverRoutes: [
@@ -8726,6 +8904,7 @@ const jsenvPluginHotSearchParam = () => {
8726
8904
  const jsenvPluginAutoreload = ({
8727
8905
  clientFileChangeEventEmitter,
8728
8906
  clientFileDereferencedEventEmitter,
8907
+ reloadRequestEventEmitter,
8729
8908
  }) => {
8730
8909
  return [
8731
8910
  jsenvPluginHotSearchParam(),
@@ -8733,10 +8912,75 @@ const jsenvPluginAutoreload = ({
8733
8912
  jsenvPluginAutoreloadServer({
8734
8913
  clientFileChangeEventEmitter,
8735
8914
  clientFileDereferencedEventEmitter,
8915
+ reloadRequestEventEmitter,
8736
8916
  }),
8737
8917
  ];
8738
8918
  };
8739
8919
 
8920
+ /*
8921
+ * Tells the browser about the dependencies declared in package.json that
8922
+ * node_modules does not match, so the page can say it is running with something
8923
+ * else than what the project asks for.
8924
+ *
8925
+ * The state is sent on page load (it is known before any client connects) and
8926
+ * again whenever it changes, so a page opened during an install is updated
8927
+ * without being reloaded.
8928
+ */
8929
+
8930
+
8931
+ const clientFileUrl = import.meta.resolve("../js/dependency_status.js");
8932
+
8933
+ const jsenvPluginDependencyStatus = ({
8934
+ dependencyProblemEventEmitter,
8935
+ getDependencyProblems,
8936
+ }) => {
8937
+ return {
8938
+ name: "jsenv:dependency_status",
8939
+ appliesDuring: "dev",
8940
+ serverEvents: {
8941
+ dependency_status: (serverEventInfo) => {
8942
+ dependencyProblemEventEmitter.on((problems) => {
8943
+ // the state is baked into the html by the injection below, so a page
8944
+ // served from the graph as it is would come back with the previous
8945
+ // state, which is exactly what a reload triggered by an install does
8946
+ for (const urlInfo of serverEventInfo.kitchen.graph.urlInfoMap.values()) {
8947
+ if (urlInfo.type === "html" && urlInfo.content !== undefined) {
8948
+ urlInfo.onModified();
8949
+ }
8950
+ }
8951
+ serverEventInfo.sendServerEvent({ problems });
8952
+ });
8953
+ },
8954
+ },
8955
+ transformUrlContent: {
8956
+ html: (htmlUrlInfo) => {
8957
+ const htmlAst = parseHtml({
8958
+ html: htmlUrlInfo.content,
8959
+ url: htmlUrlInfo.url,
8960
+ });
8961
+ const clientReference = htmlUrlInfo.dependencies.inject({
8962
+ type: "script",
8963
+ subtype: "js_module",
8964
+ expectedType: "js_module",
8965
+ specifier: clientFileUrl,
8966
+ });
8967
+ injectJsenvScript(htmlAst, {
8968
+ type: "module",
8969
+ src: clientReference.generatedSpecifier,
8970
+ initCall: {
8971
+ callee: "initDependencyStatus",
8972
+ params: { problems: getDependencyProblems() },
8973
+ },
8974
+ pluginName: "jsenv:dependency_status",
8975
+ });
8976
+ return {
8977
+ content: stringifyHtmlAst(htmlAst),
8978
+ };
8979
+ },
8980
+ },
8981
+ };
8982
+ };
8983
+
8740
8984
  const jsenvPluginCacheControl = ({
8741
8985
  versionedUrls = true,
8742
8986
  maxAge = SECONDS_IN_30_DAYS,
@@ -9273,6 +9517,7 @@ const getCorePlugins = ({
9273
9517
 
9274
9518
  clientAutoreload,
9275
9519
  clientAutoreloadOnServerRestart,
9520
+ dependencyStatus,
9276
9521
  cacheControl,
9277
9522
  scenarioPlaceholders = true,
9278
9523
  ribbon = true,
@@ -9371,6 +9616,9 @@ const getCorePlugins = ({
9371
9616
  ...(clientAutoreload && clientAutoreload.enabled
9372
9617
  ? [jsenvPluginAutoreload(clientAutoreload)]
9373
9618
  : []),
9619
+ ...(dependencyStatus
9620
+ ? [jsenvPluginDependencyStatus(dependencyStatus)]
9621
+ : []),
9374
9622
  ...(cacheControl ? [jsenvPluginCacheControl(cacheControl)] : []),
9375
9623
  ...(ribbon ? [jsenvPluginRibbon({ rootDirectoryUrl, ...ribbon })] : []),
9376
9624
  ...(dropToOpen ? [jsenvPluginDropToOpen()] : []),
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Displays the "some dependencies are outdated" warning overlay.
3
+ *
4
+ * A missing dependency is not handled here: it makes the import fail, so the
5
+ * error overlay already says it, with more precision (which import, where).
6
+ */
7
+
8
+ let removeOverlay = () => {};
9
+
10
+ const initDependencyStatus = ({ problems }) => {
11
+ render(problems);
12
+ // without the server events channel the page only knows the state it was
13
+ // served with, which is still better than nothing
14
+ if (window.__server_events__) {
15
+ window.__server_events__.listenEvents({
16
+ dependency_status: (event) => {
17
+ render(event.data.problems);
18
+ },
19
+ });
20
+ }
21
+ };
22
+
23
+ const render = (problems) => {
24
+ const outdatedList = problems.filter(({ state }) => state === "outdated");
25
+ removeOverlay();
26
+ removeOverlay = () => {};
27
+ if (outdatedList.length === 0) {
28
+ return;
29
+ }
30
+ const supervisor = window.__supervisor__;
31
+ if (!supervisor || !supervisor.reportWarning) {
32
+ console.warn(summarize(outdatedList));
33
+ return;
34
+ }
35
+ removeOverlay = supervisor.reportWarning({
36
+ title:
37
+ outdatedList.length === 1
38
+ ? "A dependency is outdated"
39
+ : `${outdatedList.length} dependencies are outdated`,
40
+ text: outdatedList.map(describe).join("\n\n"),
41
+ details: [
42
+ "The page is running with what is installed in node_modules, which is not what package.json asks for. It may work, but it is not the code the project expects.",
43
+ "Run npm install to fix it. If an install is already running, there is nothing to do but wait.",
44
+ "This page reloads on its own as soon as node_modules matches package.json.",
45
+ ],
46
+ });
47
+ };
48
+
49
+ const describe = ({ packageName, declaredVersion, installedVersion }) => {
50
+ return `${packageName}
51
+ package.json declares ${declaredVersion}
52
+ node_modules holds ${installedVersion || "an unknown version"}`;
53
+ };
54
+
55
+ const summarize = (outdatedList) => {
56
+ return `[jsenv] outdated dependencies, run npm install: ${outdatedList
57
+ .map(({ packageName }) => packageName)
58
+ .join(", ")}`;
59
+ };
60
+
61
+ export { initDependencyStatus };
@@ -1,7 +1,7 @@
1
1
  import { createSupportsColor, isUnicodeSupported, eastAsianWidth, clearTerminal, eraseLines } from "./jsenv_core_node_modules.js";
2
2
  import { stripVTControlCharacters } from "node:util";
3
3
  import { readFileSync, existsSync, chmodSync, statSync, lstatSync, readdirSync, openSync, closeSync, unlinkSync, rmdirSync, mkdirSync, writeFileSync as writeFileSync$1, watch, realpathSync } from "node:fs";
4
- import { extname } from "node:path";
4
+ import { extname, basename, dirname } from "node:path";
5
5
  import crypto, { createHash } from "node:crypto";
6
6
  import { pathToFileURL, fileURLToPath } from "node:url";
7
7
 
@@ -3382,6 +3382,21 @@ const writeFileSync = (destination, content = "", { force } = {}) => {
3382
3382
  }
3383
3383
  };
3384
3384
 
3385
+ const callOnceIdle = (callback, idleMs) => {
3386
+ let timeoutId;
3387
+ return (...args) => {
3388
+ if (timeoutId) {
3389
+ clearTimeout(timeoutId);
3390
+ }
3391
+ timeoutId = setTimeout(() => {
3392
+ callback(...args);
3393
+ }, idleMs);
3394
+ if (timeoutId.unref) {
3395
+ timeoutId.unref();
3396
+ }
3397
+ };
3398
+ };
3399
+
3385
3400
  const callOnceIdlePerFile = (callback, idleMs) => {
3386
3401
  const timeoutIdMap = new Map();
3387
3402
  return (fileEvent) => {
@@ -3433,6 +3448,25 @@ const createWatcher = (sourcePath, options) => {
3433
3448
  return watcher;
3434
3449
  };
3435
3450
 
3451
+ const guardTooFastSecondCall = (
3452
+ callback,
3453
+ cooldownBetweenFileEvents = 40,
3454
+ ) => {
3455
+ let previousCallMs;
3456
+ return (...args) => {
3457
+ const nowMs = Date.now();
3458
+ if (previousCallMs) {
3459
+ const msEllapsed = nowMs - previousCallMs;
3460
+ if (msEllapsed < cooldownBetweenFileEvents) {
3461
+ previousCallMs = null;
3462
+ return;
3463
+ }
3464
+ }
3465
+ previousCallMs = nowMs;
3466
+ callback(...args);
3467
+ };
3468
+ };
3469
+
3436
3470
  const guardTooFastSecondCallPerFile = (
3437
3471
  callback,
3438
3472
  cooldownBetweenFileEvents = 40,
@@ -3476,8 +3510,8 @@ callback: ${callback}`);
3476
3510
  return { registerCleanupCallback, cleanup };
3477
3511
  };
3478
3512
 
3479
- const isLinux = process.platform === "linux";
3480
- const fsWatchSupportsRecursive = !isLinux;
3513
+ const isLinux$1 = process.platform === "linux";
3514
+ const fsWatchSupportsRecursive = !isLinux$1;
3481
3515
 
3482
3516
  const registerDirectoryLifecycle = (
3483
3517
  source,
@@ -3502,15 +3536,15 @@ const registerDirectoryLifecycle = (
3502
3536
  },
3503
3537
  ) => {
3504
3538
  const sourceUrl = assertAndNormalizeDirectoryUrl(source);
3505
- if (!undefinedOrFunction(added)) {
3539
+ if (!undefinedOrFunction$1(added)) {
3506
3540
  throw new TypeError(`added must be a function or undefined, got ${added}`);
3507
3541
  }
3508
- if (!undefinedOrFunction(updated)) {
3542
+ if (!undefinedOrFunction$1(updated)) {
3509
3543
  throw new TypeError(
3510
3544
  `updated must be a function or undefined, got ${updated}`,
3511
3545
  );
3512
3546
  }
3513
- if (!undefinedOrFunction(removed)) {
3547
+ if (!undefinedOrFunction$1(removed)) {
3514
3548
  throw new TypeError(
3515
3549
  `removed must be a function or undefined, got ${removed}`,
3516
3550
  );
@@ -3805,7 +3839,7 @@ const registerDirectoryLifecycle = (
3805
3839
  }
3806
3840
  };
3807
3841
  const handleEntryUpdated = (entryInfo) => {
3808
- if (updated && entryInfo.patternValue && shouldCallUpdated(entryInfo)) {
3842
+ if (updated && entryInfo.patternValue && shouldCallUpdated$1(entryInfo)) {
3809
3843
  infoMap.set(entryInfo.relativeUrl, entryInfo);
3810
3844
  updated({
3811
3845
  relativeUrl: entryInfo.relativeUrl,
@@ -3848,7 +3882,7 @@ ${relativeUrls.join("\n")}`,
3848
3882
  return tracker.cleanup;
3849
3883
  };
3850
3884
 
3851
- const shouldCallUpdated = (entryInfo) => {
3885
+ const shouldCallUpdated$1 = (entryInfo) => {
3852
3886
  const { stat, previousInfo } = entryInfo;
3853
3887
  if (!stat.atimeMs) {
3854
3888
  return true;
@@ -3862,7 +3896,7 @@ const shouldCallUpdated = (entryInfo) => {
3862
3896
  return true;
3863
3897
  };
3864
3898
 
3865
- const undefinedOrFunction = (value) => {
3899
+ const undefinedOrFunction$1 = (value) => {
3866
3900
  return typeof value === "undefined" || typeof value === "function";
3867
3901
  };
3868
3902
 
@@ -3891,6 +3925,224 @@ const fileSystemPathToDirectoryRelativeUrlAndFilename = (path) => {
3891
3925
  };
3892
3926
  };
3893
3927
 
3928
+ const isMacos = process.platform === "darwin";
3929
+ const isLinux = process.platform === "linux";
3930
+ const isFreeBSD = process.platform === "freebsd";
3931
+
3932
+ const registerFileLifecycle = (
3933
+ source,
3934
+ {
3935
+ added,
3936
+ updated,
3937
+ removed,
3938
+ notifyExistent = false,
3939
+ keepProcessAlive = true,
3940
+ cooldownBetweenFileEvents = 0,
3941
+ idleMs = 50,
3942
+ },
3943
+ ) => {
3944
+ const sourceUrl = assertAndNormalizeFileUrl(source);
3945
+ if (!undefinedOrFunction(added)) {
3946
+ throw new TypeError(`added must be a function or undefined, got ${added}`);
3947
+ }
3948
+ if (!undefinedOrFunction(updated)) {
3949
+ throw new TypeError(
3950
+ `updated must be a function or undefined, got ${updated}`,
3951
+ );
3952
+ }
3953
+ if (!undefinedOrFunction(removed)) {
3954
+ throw new TypeError(
3955
+ `removed must be a function or undefined, got ${removed}`,
3956
+ );
3957
+ }
3958
+ if (idleMs) {
3959
+ if (updated) {
3960
+ updated = callOnceIdle(updated, idleMs);
3961
+ }
3962
+ }
3963
+ if (cooldownBetweenFileEvents) {
3964
+ if (added) {
3965
+ added = guardTooFastSecondCall(added, cooldownBetweenFileEvents);
3966
+ }
3967
+ if (updated) {
3968
+ updated = guardTooFastSecondCall(updated, cooldownBetweenFileEvents);
3969
+ }
3970
+ if (removed) {
3971
+ removed = guardTooFastSecondCall(removed, cooldownBetweenFileEvents);
3972
+ }
3973
+ }
3974
+
3975
+ const tracker = trackResources();
3976
+
3977
+ const handleFileFound = ({ stat, existent }) => {
3978
+ const fileMutationStopWatching = watchFileMutation(sourceUrl, {
3979
+ updated,
3980
+ removed: () => {
3981
+ fileMutationStopTracking();
3982
+ watchFileAdded();
3983
+ if (removed) {
3984
+ removed();
3985
+ }
3986
+ },
3987
+ keepProcessAlive,
3988
+ stat,
3989
+ });
3990
+ const fileMutationStopTracking = tracker.registerCleanupCallback(
3991
+ fileMutationStopWatching,
3992
+ );
3993
+
3994
+ if (added) {
3995
+ if (existent) {
3996
+ if (notifyExistent) {
3997
+ added({ existent: true });
3998
+ }
3999
+ } else {
4000
+ added({});
4001
+ }
4002
+ }
4003
+ };
4004
+
4005
+ const watchFileAdded = () => {
4006
+ const fileCreationStopWatching = watchFileCreation(
4007
+ sourceUrl,
4008
+ (stat) => {
4009
+ fileCreationgStopTracking();
4010
+ handleFileFound({ stat, existent: false });
4011
+ },
4012
+ keepProcessAlive,
4013
+ );
4014
+ const fileCreationgStopTracking = tracker.registerCleanupCallback(
4015
+ fileCreationStopWatching,
4016
+ );
4017
+ };
4018
+
4019
+ const { type, stat } = readFileInfo(sourceUrl);
4020
+ if (type === null) {
4021
+ if (added) {
4022
+ watchFileAdded();
4023
+ } else {
4024
+ throw new Error(
4025
+ `${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`,
4026
+ );
4027
+ }
4028
+ } else if (type === "file") {
4029
+ handleFileFound({ stat, existent: true });
4030
+ } else {
4031
+ throw new Error(
4032
+ `${urlToFileSystemPath(
4033
+ sourceUrl,
4034
+ )} must lead to a file, type found instead`,
4035
+ );
4036
+ }
4037
+
4038
+ return tracker.cleanup;
4039
+ };
4040
+
4041
+ const readFileInfo = (url) => {
4042
+ try {
4043
+ const stat = readEntryStatSync(new URL(url));
4044
+ return {
4045
+ type: statsToType(stat),
4046
+ stat,
4047
+ };
4048
+ } catch (e) {
4049
+ if (e.code === "ENOENT") {
4050
+ return {
4051
+ type: null,
4052
+ stat: null,
4053
+ };
4054
+ }
4055
+ throw e;
4056
+ }
4057
+ };
4058
+
4059
+ const undefinedOrFunction = (value) =>
4060
+ typeof value === "undefined" || typeof value === "function";
4061
+
4062
+ const watchFileCreation = (source, callback, keepProcessAlive) => {
4063
+ const sourcePath = urlToFileSystemPath(source);
4064
+ const sourceFilename = basename(sourcePath);
4065
+ const directoryPath = dirname(sourcePath);
4066
+ let directoryWatcher = createWatcher(directoryPath, {
4067
+ persistent: keepProcessAlive,
4068
+ });
4069
+ directoryWatcher.on("change", (eventType, filename) => {
4070
+ if (filename && filename !== sourceFilename) return;
4071
+
4072
+ const { type, stat } = readFileInfo(source);
4073
+ // ignore if something else with that name gets created
4074
+ // we are only interested into files
4075
+ if (type !== "file") {
4076
+ return;
4077
+ }
4078
+ directoryWatcher.close();
4079
+ directoryWatcher = undefined;
4080
+ callback(stat);
4081
+ });
4082
+
4083
+ return () => {
4084
+ if (directoryWatcher) {
4085
+ directoryWatcher.close();
4086
+ }
4087
+ };
4088
+ };
4089
+
4090
+ const watchFileMutation = (
4091
+ sourceUrl,
4092
+ { updated, removed, keepProcessAlive, stat },
4093
+ ) => {
4094
+ let prevStat = stat;
4095
+ let watcher;
4096
+
4097
+ const onChange = () => {
4098
+ const { type, stat } = readFileInfo(sourceUrl);
4099
+
4100
+ if (type === null) {
4101
+ stopWatching();
4102
+ if (removed) {
4103
+ removed();
4104
+ }
4105
+ } else if (type === "file") {
4106
+ if (updated && shouldCallUpdated(stat, prevStat)) {
4107
+ updated();
4108
+ }
4109
+ if ((isMacos || isLinux || isFreeBSD) && prevStat.ino !== stat.ino) {
4110
+ stopWatching();
4111
+ watch();
4112
+ }
4113
+ }
4114
+ prevStat = stat;
4115
+ };
4116
+
4117
+ const watch = () => {
4118
+ watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
4119
+ persistent: keepProcessAlive,
4120
+ });
4121
+ watcher.on("change", onChange);
4122
+ };
4123
+ const stopWatching = () => {
4124
+ if (watcher) {
4125
+ watcher.close();
4126
+ watcher = undefined;
4127
+ }
4128
+ };
4129
+ watch();
4130
+ return stopWatching;
4131
+ };
4132
+
4133
+ const shouldCallUpdated = (stat, prevStat) => {
4134
+ if (!stat.atimeMs) {
4135
+ return true;
4136
+ }
4137
+ if (stat.atimeMs <= stat.mtimeMs) {
4138
+ return true;
4139
+ }
4140
+ if (stat.mtimeMs !== prevStat.mtimeMs) {
4141
+ return true;
4142
+ }
4143
+ return false;
4144
+ };
4145
+
3894
4146
  /*
3895
4147
  * - Buffer documentation on Node.js
3896
4148
  * https://nodejs.org/docs/latest-v13.x/api/buffer.html
@@ -6419,4 +6671,4 @@ const isResponseEligibleForIntegrityValidation = (response) => {
6419
6671
  return ["basic", "cors", "default"].includes(response.type);
6420
6672
  };
6421
6673
 
6422
- export { ANSI, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, bufferToEtag, compareFileUrls, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createLogger, createTaskLog, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, formatError, generateContentFrame, getCallerPosition, getExtensionsToTry, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, normalizeImportMap, normalizeUrl, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, stringifyUrlSite, urlIsOrIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };
6674
+ export { ANSI, CONTENT_TYPE, DATA_URL, JS_QUOTES, RUNTIME_COMPAT, URL_META, applyFileSystemMagicResolution, applyNodeEsmResolution, asSpecifierWithoutSearch, asUrlWithoutSearch, assertAndNormalizeDirectoryUrl, bufferToEtag, compareFileUrls, composeTwoImportMaps, createDetailedMessage$1 as createDetailedMessage, createLogger, createTaskLog, ensurePathnameTrailingSlash, ensureWindowsDriveLetter, errorToHTML, formatError, generateContentFrame, getCallerPosition, getExtensionsToTry, injectQueryParams, injectQueryParamsIntoSpecifier, isFileSystemPath, isSpecifierForNodeBuiltin, lookupPackageDirectory, moveUrl, normalizeImportMap, normalizeUrl, readCustomConditionsFromProcessArgs, readEntryStatSync, readPackageAtOrNull, registerDirectoryLifecycle, registerFileLifecycle, resolveImport, setUrlBasename, setUrlExtension, setUrlFilename, stringifyUrlSite, urlIsOrIsInsideOf, urlToBasename, urlToExtension$1 as urlToExtension, urlToFileSystemPath, urlToFilename$1 as urlToFilename, urlToPathname$1 as urlToPathname, urlToRelativeUrl, validateResponseIntegrity, writeFileSync };