@jsenv/filesystem 4.2.6 → 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.2.6",
3
+ "version": "4.3.1",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -16,6 +16,9 @@
16
16
  "url": "https://twitter.com/damienmaillard"
17
17
  },
18
18
  "type": "module",
19
+ "engines": {
20
+ "node": ">=19.1.0"
21
+ },
19
22
  "exports": {
20
23
  ".": {
21
24
  "import": "./src/main.js"
@@ -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,9 +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 isLinux = process.platform === "linux";
12
- // linux does not support recursive option
13
- const fsWatchSupportsRecursive = !isLinux;
11
+ const fsWatchSupportsRecursive = process.platform !== "linux";
14
12
 
15
13
  export const registerDirectoryLifecycle = (
16
14
  source,
@@ -93,8 +91,8 @@ export const registerDirectoryLifecycle = (
93
91
  try {
94
92
  const relativeUrl = urlToRelativeUrl(url, source);
95
93
  const previousInfo = infoMap.get(relativeUrl);
96
- const stats = statSync(new URL(url));
97
- const type = statsToType(stats);
94
+ const stat = statSync(new URL(url));
95
+ const type = statsToType(stat);
98
96
  const patternValue = previousInfo
99
97
  ? previousInfo.patternValue
100
98
  : getWatchPatternValue({ url, type });
@@ -103,13 +101,15 @@ export const registerDirectoryLifecycle = (
103
101
  url,
104
102
  relativeUrl,
105
103
  type,
106
- atimeMs: stats.atimeMs,
107
- mtimeMs: stats.mtimeMs,
104
+ stat,
108
105
  patternValue,
109
106
  };
110
107
  } catch (e) {
111
108
  if (e.code === "ENOENT") {
112
- return null;
109
+ return {
110
+ type: null,
111
+ stat: null,
112
+ };
113
113
  }
114
114
  throw e;
115
115
  }
@@ -182,7 +182,7 @@ export const registerDirectoryLifecycle = (
182
182
  const handleChange = (relativeUrl) => {
183
183
  const entryUrl = new URL(relativeUrl, sourceUrl).href;
184
184
  const entryInfo = readEntryInfo(entryUrl);
185
- if (!entryInfo) {
185
+ if (entryInfo.type === null) {
186
186
  const previousEntryInfo = infoMap.get(relativeUrl);
187
187
  if (!previousEntryInfo) {
188
188
  // on MacOS it's possible to receive a "rename" event for
@@ -242,7 +242,7 @@ export const registerDirectoryLifecycle = (
242
242
  readdirSync(new URL(directoryUrl)).forEach((entryName) => {
243
243
  const childEntryUrl = new URL(entryName, directoryUrl).href;
244
244
  const childEntryInfo = readEntryInfo(childEntryUrl);
245
- if (childEntryInfo && childEntryInfo.patternValue) {
245
+ if (childEntryInfo.type !== null && childEntryInfo.patternValue) {
246
246
  handleEntryFound(childEntryInfo, { notify });
247
247
  }
248
248
  });
@@ -271,7 +271,7 @@ export const registerDirectoryLifecycle = (
271
271
  relativeUrl: entryInfo.relativeUrl,
272
272
  type: entryInfo.type,
273
273
  patternValue: entryInfo.patternValue,
274
- mtime: entryInfo.mtimeMs,
274
+ mtime: entryInfo.stat.mtimeMs,
275
275
  });
276
276
  }
277
277
  };
@@ -282,19 +282,19 @@ export const registerDirectoryLifecycle = (
282
282
  relativeUrl: entryInfo.relativeUrl,
283
283
  type: entryInfo.type,
284
284
  patternValue: entryInfo.patternValue,
285
- mtime: entryInfo.mtimeMs,
285
+ mtime: entryInfo.stat.mtimeMs,
286
286
  });
287
287
  }
288
288
  };
289
289
  const handleEntryUpdated = (entryInfo) => {
290
290
  infoMap.set(entryInfo.relativeUrl, entryInfo);
291
- if (updated && entryInfo.patternValue) {
291
+ if (updated && entryInfo.patternValue && shouldCallUpdated(entryInfo)) {
292
292
  updated({
293
293
  relativeUrl: entryInfo.relativeUrl,
294
294
  type: entryInfo.type,
295
295
  patternValue: entryInfo.patternValue,
296
- mtime: entryInfo.mtimeMs,
297
- previousMtime: entryInfo.previousInfo.mtimeMs,
296
+ mtime: entryInfo.stat.mtimeMs,
297
+ previousMtime: entryInfo.previousInfo.stat.mtimeMs,
298
298
  });
299
299
  }
300
300
  };
@@ -302,7 +302,7 @@ export const registerDirectoryLifecycle = (
302
302
  readdirSync(new URL(sourceUrl)).forEach((entry) => {
303
303
  const entryUrl = new URL(entry, sourceUrl).href;
304
304
  const entryInfo = readEntryInfo(entryUrl);
305
- if (entryInfo && entryInfo.patternValue) {
305
+ if (entryInfo.type !== null && entryInfo.patternValue) {
306
306
  handleEntryFound(entryInfo, {
307
307
  notify: notifyExistent,
308
308
  });
@@ -336,6 +336,20 @@ ${relativeUrls.join("\n")}`,
336
336
  return tracker.cleanup;
337
337
  };
338
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
+
339
353
  const undefinedOrFunction = (value) => {
340
354
  return typeof value === "undefined" || typeof value === "function";
341
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
+ };