@jsenv/filesystem 4.11.0 → 4.13.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/package.json +1 -1
- package/src/lifecycle/register_directory_lifecycle.js +6 -5
- package/src/lifecycle/register_file_lifecycle.js +24 -10
- package/src/main.js +2 -0
- package/src/path_and_url/find_ancestor_directory_url.js +12 -0
- package/src/path_and_url/get_parent_directory_url.js +24 -0
- package/src/read_write/write_directory_sync.js +44 -7
- package/src/read_write/write_file_sync.js +16 -8
package/package.json
CHANGED
|
@@ -339,7 +339,8 @@ export const registerDirectoryLifecycle = (
|
|
|
339
339
|
}
|
|
340
340
|
};
|
|
341
341
|
|
|
342
|
-
readdirSync(new URL(sourceUrl))
|
|
342
|
+
const entries = readdirSync(new URL(sourceUrl));
|
|
343
|
+
for (const entry of entries) {
|
|
343
344
|
const entryUrl = new URL(entry, sourceUrl).href;
|
|
344
345
|
const entryInfo = readEntryInfo(entryUrl);
|
|
345
346
|
if (entryInfo.type !== null && entryInfo.patternValue) {
|
|
@@ -347,7 +348,7 @@ export const registerDirectoryLifecycle = (
|
|
|
347
348
|
notify: notifyExistent,
|
|
348
349
|
});
|
|
349
350
|
}
|
|
350
|
-
}
|
|
351
|
+
}
|
|
351
352
|
if (debug) {
|
|
352
353
|
const relativeUrls = Array.from(infoMap.keys());
|
|
353
354
|
if (relativeUrls.length === 0) {
|
|
@@ -381,11 +382,11 @@ const shouldCallUpdated = (entryInfo) => {
|
|
|
381
382
|
if (!stat.atimeMs) {
|
|
382
383
|
return true;
|
|
383
384
|
}
|
|
384
|
-
if (stat.atimeMs
|
|
385
|
+
if (stat.atimeMs <= stat.mtimeMs) {
|
|
385
386
|
return true;
|
|
386
387
|
}
|
|
387
|
-
if (
|
|
388
|
-
return
|
|
388
|
+
if (stat.mtimeMs !== previousInfo.stat.mtimeMs) {
|
|
389
|
+
return true;
|
|
389
390
|
}
|
|
390
391
|
return true;
|
|
391
392
|
};
|
|
@@ -8,6 +8,10 @@ import { createWatcher } from "./create_watcher.js";
|
|
|
8
8
|
import { guardTooFastSecondCall } from "./guard_second_call.js";
|
|
9
9
|
import { trackResources } from "./track_resources.js";
|
|
10
10
|
|
|
11
|
+
const isMacos = process.platform === "darwin";
|
|
12
|
+
const isLinux = process.platform === "linux";
|
|
13
|
+
const isFreeBSD = process.platform === "freebsd";
|
|
14
|
+
|
|
11
15
|
export const registerFileLifecycle = (
|
|
12
16
|
source,
|
|
13
17
|
{
|
|
@@ -171,16 +175,13 @@ const watchFileMutation = (
|
|
|
171
175
|
{ updated, removed, keepProcessAlive, stat },
|
|
172
176
|
) => {
|
|
173
177
|
let prevStat = stat;
|
|
174
|
-
let watcher
|
|
175
|
-
persistent: keepProcessAlive,
|
|
176
|
-
});
|
|
178
|
+
let watcher;
|
|
177
179
|
|
|
178
|
-
|
|
180
|
+
const onChange = () => {
|
|
179
181
|
const { type, stat } = readFileInfo(sourceUrl);
|
|
180
182
|
|
|
181
183
|
if (type === null) {
|
|
182
|
-
|
|
183
|
-
watcher = undefined;
|
|
184
|
+
stopWatching();
|
|
184
185
|
if (removed) {
|
|
185
186
|
removed();
|
|
186
187
|
}
|
|
@@ -188,25 +189,38 @@ const watchFileMutation = (
|
|
|
188
189
|
if (updated && shouldCallUpdated(stat, prevStat)) {
|
|
189
190
|
updated();
|
|
190
191
|
}
|
|
192
|
+
if ((isMacos || isLinux || isFreeBSD) && prevStat.ino !== stat.ino) {
|
|
193
|
+
stopWatching();
|
|
194
|
+
watch();
|
|
195
|
+
}
|
|
191
196
|
}
|
|
192
197
|
prevStat = stat;
|
|
193
|
-
}
|
|
198
|
+
};
|
|
194
199
|
|
|
195
|
-
|
|
200
|
+
const watch = () => {
|
|
201
|
+
watcher = createWatcher(urlToFileSystemPath(sourceUrl), {
|
|
202
|
+
persistent: keepProcessAlive,
|
|
203
|
+
});
|
|
204
|
+
watcher.on("change", onChange);
|
|
205
|
+
};
|
|
206
|
+
const stopWatching = () => {
|
|
196
207
|
if (watcher) {
|
|
197
208
|
watcher.close();
|
|
209
|
+
watcher = undefined;
|
|
198
210
|
}
|
|
199
211
|
};
|
|
212
|
+
watch();
|
|
213
|
+
return stopWatching;
|
|
200
214
|
};
|
|
201
215
|
|
|
202
216
|
const shouldCallUpdated = (stat, prevStat) => {
|
|
203
217
|
if (!stat.atimeMs) {
|
|
204
218
|
return true;
|
|
205
219
|
}
|
|
206
|
-
if (stat.atimeMs
|
|
220
|
+
if (stat.atimeMs <= stat.mtimeMs) {
|
|
207
221
|
return true;
|
|
208
222
|
}
|
|
209
|
-
if (stat.mtimeMs
|
|
223
|
+
if (stat.mtimeMs !== prevStat.mtimeMs) {
|
|
210
224
|
return true;
|
|
211
225
|
}
|
|
212
226
|
return false;
|
package/src/main.js
CHANGED
|
@@ -17,6 +17,8 @@ export {
|
|
|
17
17
|
} from "./path_and_url/file_url_validation.js";
|
|
18
18
|
export { comparePathnames } from "./path_and_url/compare_pathnames.js";
|
|
19
19
|
export { ensureWindowsDriveLetter } from "./path_and_url/ensure_windows_drive_letter.js";
|
|
20
|
+
export { getParentDirectoryUrl } from "./path_and_url/get_parent_directory_url.js";
|
|
21
|
+
export { findAncestorDirectoryUrl } from "./path_and_url/find_ancestor_directory_url.js";
|
|
20
22
|
|
|
21
23
|
// list
|
|
22
24
|
export { collectDirectoryMatchReport } from "./list/collect_directory_match_report.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { getParentDirectoryUrl } from "./get_parent_directory_url.js";
|
|
2
|
+
|
|
3
|
+
export const findAncestorDirectoryUrl = (url, callback) => {
|
|
4
|
+
url = String(url);
|
|
5
|
+
while (url !== "file:///") {
|
|
6
|
+
if (callback(url)) {
|
|
7
|
+
return url;
|
|
8
|
+
}
|
|
9
|
+
url = getParentDirectoryUrl(url);
|
|
10
|
+
}
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const getParentDirectoryUrl = (url) => {
|
|
2
|
+
if (url.startsWith("file://")) {
|
|
3
|
+
// With node.js new URL('../', 'file:///C:/').href
|
|
4
|
+
// returns "file:///C:/" instead of "file:///"
|
|
5
|
+
const resource = url.slice("file://".length);
|
|
6
|
+
const slashLastIndex = resource.lastIndexOf("/");
|
|
7
|
+
if (slashLastIndex === -1) {
|
|
8
|
+
return url;
|
|
9
|
+
}
|
|
10
|
+
const lastCharIndex = resource.length - 1;
|
|
11
|
+
if (slashLastIndex === lastCharIndex) {
|
|
12
|
+
const slashBeforeLastIndex = resource.lastIndexOf(
|
|
13
|
+
"/",
|
|
14
|
+
slashLastIndex - 1,
|
|
15
|
+
);
|
|
16
|
+
if (slashBeforeLastIndex === -1) {
|
|
17
|
+
return url;
|
|
18
|
+
}
|
|
19
|
+
return `file://${resource.slice(0, slashBeforeLastIndex + 1)}`;
|
|
20
|
+
}
|
|
21
|
+
return `file://${resource.slice(0, slashLastIndex + 1)}`;
|
|
22
|
+
}
|
|
23
|
+
return new URL(url.endsWith("/") ? "../" : "./", url).href;
|
|
24
|
+
};
|
|
@@ -1,21 +1,59 @@
|
|
|
1
1
|
import { urlToFileSystemPath } from "@jsenv/urls";
|
|
2
|
-
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, statSync, unlinkSync } from "node:fs";
|
|
3
3
|
|
|
4
4
|
import { assertAndNormalizeDirectoryUrl } from "../path_and_url/directory_url_validation.js";
|
|
5
|
+
import { findAncestorDirectoryUrl } from "../path_and_url/find_ancestor_directory_url.js";
|
|
5
6
|
import { readEntryStatSync } from "./stat/read_entry_stat_sync.js";
|
|
6
7
|
import { statsToType } from "./stat/stats_to_type.js";
|
|
7
8
|
|
|
8
9
|
export const writeDirectorySync = (
|
|
9
10
|
destination,
|
|
10
|
-
{ recursive = true, allowUseless = false } = {},
|
|
11
|
+
{ recursive = true, allowUseless = false, force } = {},
|
|
11
12
|
) => {
|
|
12
13
|
const destinationUrl = assertAndNormalizeDirectoryUrl(destination);
|
|
13
14
|
const destinationPath = urlToFileSystemPath(destinationUrl);
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
let destinationStats;
|
|
17
|
+
try {
|
|
18
|
+
destinationStats = readEntryStatSync(destinationUrl, {
|
|
19
|
+
nullIfNotFound: true,
|
|
20
|
+
followLink: false,
|
|
21
|
+
});
|
|
22
|
+
} catch (e) {
|
|
23
|
+
if (e.code === "ENOTDIR") {
|
|
24
|
+
let previousNonDirUrl = destinationUrl;
|
|
25
|
+
// we must try all parent directories as long as it fails with ENOTDIR
|
|
26
|
+
findAncestorDirectoryUrl(destinationUrl, (ancestorUrl) => {
|
|
27
|
+
try {
|
|
28
|
+
statSync(new URL(ancestorUrl));
|
|
29
|
+
return true;
|
|
30
|
+
} catch (e) {
|
|
31
|
+
if (e.code === "ENOTDIR") {
|
|
32
|
+
previousNonDirUrl = ancestorUrl;
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if (force) {
|
|
39
|
+
unlinkSync(
|
|
40
|
+
new URL(
|
|
41
|
+
previousNonDirUrl
|
|
42
|
+
// remove trailing slash
|
|
43
|
+
.slice(0, -1),
|
|
44
|
+
),
|
|
45
|
+
);
|
|
46
|
+
} else {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`cannot write directory at ${destinationPath} because there is a file at ${urlToFileSystemPath(
|
|
49
|
+
previousNonDirUrl,
|
|
50
|
+
)}`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
19
57
|
|
|
20
58
|
if (destinationStats) {
|
|
21
59
|
if (destinationStats.isDirectory()) {
|
|
@@ -24,7 +62,6 @@ export const writeDirectorySync = (
|
|
|
24
62
|
}
|
|
25
63
|
throw new Error(`directory already exists at ${destinationPath}`);
|
|
26
64
|
}
|
|
27
|
-
|
|
28
65
|
const destinationType = statsToType(destinationStats);
|
|
29
66
|
throw new Error(
|
|
30
67
|
`cannot write directory at ${destinationPath} because there is a ${destinationType}`,
|
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
mkdirSync,
|
|
3
|
-
readFileSync,
|
|
4
|
-
writeFileSync as writeFileSyncNode,
|
|
5
|
-
} from "node:fs";
|
|
1
|
+
import { readFileSync, writeFileSync as writeFileSyncNode } from "node:fs";
|
|
6
2
|
|
|
7
3
|
import { assertAndNormalizeFileUrl } from "../path_and_url/file_url_validation.js";
|
|
4
|
+
import { removeDirectorySync } from "../remove/remove_directory_sync.js";
|
|
5
|
+
import { writeDirectorySync } from "./write_directory_sync.js";
|
|
8
6
|
|
|
9
|
-
export const writeFileSync = (destination, content = "") => {
|
|
7
|
+
export const writeFileSync = (destination, content = "", { force } = {}) => {
|
|
10
8
|
const destinationUrl = assertAndNormalizeFileUrl(destination);
|
|
11
9
|
const destinationUrlObject = new URL(destinationUrl);
|
|
12
10
|
if (content && content instanceof URL) {
|
|
@@ -15,8 +13,18 @@ export const writeFileSync = (destination, content = "") => {
|
|
|
15
13
|
try {
|
|
16
14
|
writeFileSyncNode(destinationUrlObject, content);
|
|
17
15
|
} catch (error) {
|
|
18
|
-
if (error.code === "
|
|
19
|
-
|
|
16
|
+
if (error.code === "EISDIR") {
|
|
17
|
+
// happens when directory existed but got deleted and now it's a file
|
|
18
|
+
if (force) {
|
|
19
|
+
removeDirectorySync(destinationUrlObject);
|
|
20
|
+
writeFileSyncNode(destinationUrlObject, content);
|
|
21
|
+
} else {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (error.code === "ENOENT" || error.code === "ENOTDIR") {
|
|
26
|
+
writeDirectorySync(new URL("./", destinationUrlObject), {
|
|
27
|
+
force,
|
|
20
28
|
recursive: true,
|
|
21
29
|
});
|
|
22
30
|
writeFileSyncNode(destinationUrlObject, content);
|