@jsenv/filesystem 4.3.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/filesystem",
3
- "version": "4.3.0",
3
+ "version": "4.3.1",
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,7 @@ 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 fsWatchSupportsRecursive = process.platform !== "linux";
12
12
 
13
13
  export const registerDirectoryLifecycle = (
14
14
  source,
@@ -91,8 +91,8 @@ export const registerDirectoryLifecycle = (
91
91
  try {
92
92
  const relativeUrl = urlToRelativeUrl(url, source);
93
93
  const previousInfo = infoMap.get(relativeUrl);
94
- const stats = statSync(new URL(url));
95
- const type = statsToType(stats);
94
+ const stat = statSync(new URL(url));
95
+ const type = statsToType(stat);
96
96
  const patternValue = previousInfo
97
97
  ? previousInfo.patternValue
98
98
  : getWatchPatternValue({ url, type });
@@ -101,13 +101,15 @@ export const registerDirectoryLifecycle = (
101
101
  url,
102
102
  relativeUrl,
103
103
  type,
104
- atimeMs: stats.atimeMs,
105
- mtimeMs: stats.mtimeMs,
104
+ stat,
106
105
  patternValue,
107
106
  };
108
107
  } catch (e) {
109
108
  if (e.code === "ENOENT") {
110
- return null;
109
+ return {
110
+ type: null,
111
+ stat: null,
112
+ };
111
113
  }
112
114
  throw e;
113
115
  }
@@ -180,7 +182,7 @@ export const registerDirectoryLifecycle = (
180
182
  const handleChange = (relativeUrl) => {
181
183
  const entryUrl = new URL(relativeUrl, sourceUrl).href;
182
184
  const entryInfo = readEntryInfo(entryUrl);
183
- if (!entryInfo) {
185
+ if (entryInfo.type === null) {
184
186
  const previousEntryInfo = infoMap.get(relativeUrl);
185
187
  if (!previousEntryInfo) {
186
188
  // on MacOS it's possible to receive a "rename" event for
@@ -240,7 +242,7 @@ export const registerDirectoryLifecycle = (
240
242
  readdirSync(new URL(directoryUrl)).forEach((entryName) => {
241
243
  const childEntryUrl = new URL(entryName, directoryUrl).href;
242
244
  const childEntryInfo = readEntryInfo(childEntryUrl);
243
- if (childEntryInfo && childEntryInfo.patternValue) {
245
+ if (childEntryInfo.type !== null && childEntryInfo.patternValue) {
244
246
  handleEntryFound(childEntryInfo, { notify });
245
247
  }
246
248
  });
@@ -269,7 +271,7 @@ export const registerDirectoryLifecycle = (
269
271
  relativeUrl: entryInfo.relativeUrl,
270
272
  type: entryInfo.type,
271
273
  patternValue: entryInfo.patternValue,
272
- mtime: entryInfo.mtimeMs,
274
+ mtime: entryInfo.stat.mtimeMs,
273
275
  });
274
276
  }
275
277
  };
@@ -280,19 +282,19 @@ export const registerDirectoryLifecycle = (
280
282
  relativeUrl: entryInfo.relativeUrl,
281
283
  type: entryInfo.type,
282
284
  patternValue: entryInfo.patternValue,
283
- mtime: entryInfo.mtimeMs,
285
+ mtime: entryInfo.stat.mtimeMs,
284
286
  });
285
287
  }
286
288
  };
287
289
  const handleEntryUpdated = (entryInfo) => {
288
290
  infoMap.set(entryInfo.relativeUrl, entryInfo);
289
- if (updated && entryInfo.patternValue) {
291
+ if (updated && entryInfo.patternValue && shouldCallUpdated(entryInfo)) {
290
292
  updated({
291
293
  relativeUrl: entryInfo.relativeUrl,
292
294
  type: entryInfo.type,
293
295
  patternValue: entryInfo.patternValue,
294
- mtime: entryInfo.mtimeMs,
295
- previousMtime: entryInfo.previousInfo.mtimeMs,
296
+ mtime: entryInfo.stat.mtimeMs,
297
+ previousMtime: entryInfo.previousInfo.stat.mtimeMs,
296
298
  });
297
299
  }
298
300
  };
@@ -300,7 +302,7 @@ export const registerDirectoryLifecycle = (
300
302
  readdirSync(new URL(sourceUrl)).forEach((entry) => {
301
303
  const entryUrl = new URL(entry, sourceUrl).href;
302
304
  const entryInfo = readEntryInfo(entryUrl);
303
- if (entryInfo && entryInfo.patternValue) {
305
+ if (entryInfo.type !== null && entryInfo.patternValue) {
304
306
  handleEntryFound(entryInfo, {
305
307
  notify: notifyExistent,
306
308
  });
@@ -334,6 +336,20 @@ ${relativeUrls.join("\n")}`,
334
336
  return tracker.cleanup;
335
337
  };
336
338
 
339
+ const shouldCallUpdated = (entryInfo) => {
340
+ const { stat, previousInfo } = entryInfo;
341
+ if (!stat.atimeMs) {
342
+ return true;
343
+ }
344
+ if (stat.atimeMs < stat.mtimeMs) {
345
+ return true;
346
+ }
347
+ if (stat.mtimeMs > previousInfo.stat.mtimeMs) {
348
+ return true;
349
+ }
350
+ return false;
351
+ };
352
+
337
353
  const undefinedOrFunction = (value) => {
338
354
  return typeof value === "undefined" || typeof value === "function";
339
355
  };
@@ -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
+ };