@jsenv/filesystem 4.3.0 → 4.3.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/filesystem",
3
- "version": "4.3.0",
3
+ "version": "4.3.2",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,7 +16,9 @@ export const createWatcher = (sourcePath, options) => {
16
16
  if (e.code === "ENOENT") {
17
17
  return;
18
18
  }
19
- console.error(`error while fixing windows eperm: ${e.stack}`);
19
+ console.error(
20
+ `error while trying to get rid of windows EPERM: ${e.stack}`,
21
+ );
20
22
  throw error;
21
23
  }
22
24
  } else {
@@ -8,7 +8,8 @@ import { guardTooFastSecondCallPerFile } from "./internal/guard_second_call.js";
8
8
  import { createWatcher } from "./internal/createWatcher.js";
9
9
  import { trackResources } from "./internal/track_resources.js";
10
10
 
11
- const fsWatchSupportsRecursive = true;
11
+ const isLinux = process.platform === "linux";
12
+ const fsWatchSupportsRecursive = !isLinux;
12
13
 
13
14
  export const registerDirectoryLifecycle = (
14
15
  source,
@@ -91,8 +92,8 @@ export const registerDirectoryLifecycle = (
91
92
  try {
92
93
  const relativeUrl = urlToRelativeUrl(url, source);
93
94
  const previousInfo = infoMap.get(relativeUrl);
94
- const stats = statSync(new URL(url));
95
- const type = statsToType(stats);
95
+ const stat = statSync(new URL(url));
96
+ const type = statsToType(stat);
96
97
  const patternValue = previousInfo
97
98
  ? previousInfo.patternValue
98
99
  : getWatchPatternValue({ url, type });
@@ -101,13 +102,15 @@ export const registerDirectoryLifecycle = (
101
102
  url,
102
103
  relativeUrl,
103
104
  type,
104
- atimeMs: stats.atimeMs,
105
- mtimeMs: stats.mtimeMs,
105
+ stat,
106
106
  patternValue,
107
107
  };
108
108
  } catch (e) {
109
109
  if (e.code === "ENOENT") {
110
- return null;
110
+ return {
111
+ type: null,
112
+ stat: null,
113
+ };
111
114
  }
112
115
  throw e;
113
116
  }
@@ -180,7 +183,7 @@ export const registerDirectoryLifecycle = (
180
183
  const handleChange = (relativeUrl) => {
181
184
  const entryUrl = new URL(relativeUrl, sourceUrl).href;
182
185
  const entryInfo = readEntryInfo(entryUrl);
183
- if (!entryInfo) {
186
+ if (entryInfo.type === null) {
184
187
  const previousEntryInfo = infoMap.get(relativeUrl);
185
188
  if (!previousEntryInfo) {
186
189
  // on MacOS it's possible to receive a "rename" event for
@@ -240,7 +243,7 @@ export const registerDirectoryLifecycle = (
240
243
  readdirSync(new URL(directoryUrl)).forEach((entryName) => {
241
244
  const childEntryUrl = new URL(entryName, directoryUrl).href;
242
245
  const childEntryInfo = readEntryInfo(childEntryUrl);
243
- if (childEntryInfo && childEntryInfo.patternValue) {
246
+ if (childEntryInfo.type !== null && childEntryInfo.patternValue) {
244
247
  handleEntryFound(childEntryInfo, { notify });
245
248
  }
246
249
  });
@@ -269,7 +272,7 @@ export const registerDirectoryLifecycle = (
269
272
  relativeUrl: entryInfo.relativeUrl,
270
273
  type: entryInfo.type,
271
274
  patternValue: entryInfo.patternValue,
272
- mtime: entryInfo.mtimeMs,
275
+ mtime: entryInfo.stat.mtimeMs,
273
276
  });
274
277
  }
275
278
  };
@@ -280,19 +283,19 @@ export const registerDirectoryLifecycle = (
280
283
  relativeUrl: entryInfo.relativeUrl,
281
284
  type: entryInfo.type,
282
285
  patternValue: entryInfo.patternValue,
283
- mtime: entryInfo.mtimeMs,
286
+ mtime: entryInfo.stat.mtimeMs,
284
287
  });
285
288
  }
286
289
  };
287
290
  const handleEntryUpdated = (entryInfo) => {
288
291
  infoMap.set(entryInfo.relativeUrl, entryInfo);
289
- if (updated && entryInfo.patternValue) {
292
+ if (updated && entryInfo.patternValue && shouldCallUpdated(entryInfo)) {
290
293
  updated({
291
294
  relativeUrl: entryInfo.relativeUrl,
292
295
  type: entryInfo.type,
293
296
  patternValue: entryInfo.patternValue,
294
- mtime: entryInfo.mtimeMs,
295
- previousMtime: entryInfo.previousInfo.mtimeMs,
297
+ mtime: entryInfo.stat.mtimeMs,
298
+ previousMtime: entryInfo.previousInfo.stat.mtimeMs,
296
299
  });
297
300
  }
298
301
  };
@@ -300,7 +303,7 @@ export const registerDirectoryLifecycle = (
300
303
  readdirSync(new URL(sourceUrl)).forEach((entry) => {
301
304
  const entryUrl = new URL(entry, sourceUrl).href;
302
305
  const entryInfo = readEntryInfo(entryUrl);
303
- if (entryInfo && entryInfo.patternValue) {
306
+ if (entryInfo.type !== null && entryInfo.patternValue) {
304
307
  handleEntryFound(entryInfo, {
305
308
  notify: notifyExistent,
306
309
  });
@@ -334,6 +337,20 @@ ${relativeUrls.join("\n")}`,
334
337
  return tracker.cleanup;
335
338
  };
336
339
 
340
+ const shouldCallUpdated = (entryInfo) => {
341
+ const { stat, previousInfo } = entryInfo;
342
+ if (!stat.atimeMs) {
343
+ return true;
344
+ }
345
+ if (stat.atimeMs < stat.mtimeMs) {
346
+ return true;
347
+ }
348
+ if (isLinux && stat.mtimeMs <= previousInfo.stat.mtimeMs) {
349
+ return false;
350
+ }
351
+ return true;
352
+ };
353
+
337
354
  const undefinedOrFunction = (value) => {
338
355
  return typeof value === "undefined" || typeof value === "function";
339
356
  };
@@ -47,7 +47,7 @@ export const registerFileLifecycle = (
47
47
 
48
48
  const tracker = trackResources();
49
49
 
50
- const handleFileFound = ({ existent }) => {
50
+ const handleFileFound = ({ stat, existent }) => {
51
51
  const fileMutationStopWatching = watchFileMutation(sourceUrl, {
52
52
  updated,
53
53
  removed: () => {
@@ -58,6 +58,7 @@ export const registerFileLifecycle = (
58
58
  }
59
59
  },
60
60
  keepProcessAlive,
61
+ stat,
61
62
  });
62
63
  const fileMutationStopTracking = tracker.registerCleanupCallback(
63
64
  fileMutationStopWatching,
@@ -77,9 +78,9 @@ export const registerFileLifecycle = (
77
78
  const watchFileAdded = () => {
78
79
  const fileCreationStopWatching = watchFileCreation(
79
80
  sourceUrl,
80
- () => {
81
+ (stat) => {
81
82
  fileCreationgStopTracking();
82
- handleFileFound({ existent: false });
83
+ handleFileFound({ stat, existent: false });
83
84
  },
84
85
  keepProcessAlive,
85
86
  );
@@ -88,8 +89,8 @@ export const registerFileLifecycle = (
88
89
  );
89
90
  };
90
91
 
91
- const sourceType = entryToTypeOrNull(sourceUrl);
92
- if (sourceType === null) {
92
+ const { type, stat } = readFileInfo(sourceUrl);
93
+ if (type === null) {
93
94
  if (added) {
94
95
  watchFileAdded();
95
96
  } else {
@@ -97,8 +98,8 @@ export const registerFileLifecycle = (
97
98
  `${urlToFileSystemPath(sourceUrl)} must lead to a file, found nothing`,
98
99
  );
99
100
  }
100
- } else if (sourceType === "file") {
101
- handleFileFound({ existent: true });
101
+ } else if (type === "file") {
102
+ handleFileFound({ stat, existent: true });
102
103
  } else {
103
104
  throw new Error(
104
105
  `${urlToFileSystemPath(
@@ -110,13 +111,19 @@ export const registerFileLifecycle = (
110
111
  return tracker.cleanup;
111
112
  };
112
113
 
113
- const entryToTypeOrNull = (url) => {
114
+ const readFileInfo = (url) => {
114
115
  try {
115
- const stats = statSync(new URL(url));
116
- return statsToType(stats);
116
+ const stat = statSync(new URL(url));
117
+ return {
118
+ type: statsToType(stat),
119
+ stat,
120
+ };
117
121
  } catch (e) {
118
122
  if (e.code === "ENOENT") {
119
- return null;
123
+ return {
124
+ type: null,
125
+ stat: null,
126
+ };
120
127
  }
121
128
  throw e;
122
129
  }
@@ -135,14 +142,15 @@ const watchFileCreation = (source, callback, keepProcessAlive) => {
135
142
  directoryWatcher.on("change", (eventType, filename) => {
136
143
  if (filename && filename !== sourceFilename) return;
137
144
 
138
- const type = entryToTypeOrNull(source);
145
+ const { type, stat } = readFileInfo(source);
139
146
  // ignore if something else with that name gets created
140
147
  // we are only interested into files
141
- if (type !== "file") return;
142
-
148
+ if (type !== "file") {
149
+ return;
150
+ }
143
151
  directoryWatcher.close();
144
152
  directoryWatcher = undefined;
145
- callback();
153
+ callback(stat);
146
154
  });
147
155
 
148
156
  return () => {
@@ -154,26 +162,28 @@ const watchFileCreation = (source, callback, keepProcessAlive) => {
154
162
 
155
163
  const watchFileMutation = (
156
164
  sourceUrl,
157
- { updated, removed, keepProcessAlive },
165
+ { updated, removed, keepProcessAlive, stat },
158
166
  ) => {
167
+ let prevStat = stat;
159
168
  let watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
160
169
  persistent: keepProcessAlive,
161
170
  });
162
171
 
163
172
  watcher.on("change", () => {
164
- const sourceType = entryToTypeOrNull(sourceUrl);
173
+ const { type, stat } = readFileInfo(sourceUrl);
165
174
 
166
- if (sourceType === null) {
175
+ if (type === null) {
167
176
  watcher.close();
168
177
  watcher = undefined;
169
178
  if (removed) {
170
179
  removed();
171
180
  }
172
- } else if (sourceType === "file") {
173
- if (updated) {
181
+ } else if (type === "file") {
182
+ if (updated && shouldCallUpdated(stat, prevStat)) {
174
183
  updated();
175
184
  }
176
185
  }
186
+ prevStat = stat;
177
187
  });
178
188
 
179
189
  return () => {
@@ -182,3 +192,16 @@ const watchFileMutation = (
182
192
  }
183
193
  };
184
194
  };
195
+
196
+ const shouldCallUpdated = (stat, prevStat) => {
197
+ if (!stat.atimeMs) {
198
+ return true;
199
+ }
200
+ if (stat.atimeMs < stat.mtimeMs) {
201
+ return true;
202
+ }
203
+ if (stat.mtimeMs > prevStat.mtimeMs) {
204
+ return true;
205
+ }
206
+ return false;
207
+ };