@jsenv/filesystem 2.4.0 → 2.6.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/README.md +7 -4
- package/package.json +20 -31
- package/src/collectDirectoryMatchReport.js +29 -23
- package/src/collectFiles.js +34 -45
- package/src/copyDirectoryContent.js +26 -14
- package/src/copyFileSystemNode.js +37 -23
- package/src/listFilesMatching.js +8 -20
- package/src/moveDirectoryContent.js +27 -14
- package/src/moveFileSystemNode.js +43 -28
- package/src/readFileSystemNodeStat.js +2 -2
- package/src/removeFileSystemNode.js +56 -35
- package/dist/commonjs/jsenv_filesystem.cjs +0 -2647
- package/dist/commonjs/jsenv_filesystem.cjs.map +0 -604
- package/dist/dev/template_node_package.dev.cjs +0 -20
- package/dist/dev/template_node_package.dev.cjs.map +0 -16
- package/dist/prod/template_node_package.prod.cjs +0 -20
- package/dist/prod/template_node_package.prod.cjs.map +0 -16
package/README.md
CHANGED
|
@@ -18,10 +18,13 @@ console.log(relativeUrl) // "src/file.js"
|
|
|
18
18
|
```js
|
|
19
19
|
import { listFilesMatching } from "@jsenv/filesystem"
|
|
20
20
|
|
|
21
|
-
const jsFiles = await listFilesMatching(
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
const jsFiles = await listFilesMatching({
|
|
22
|
+
directoryUrl: new URL("./", import.meta.url),
|
|
23
|
+
patterns: {
|
|
24
|
+
"./**/*.js": true,
|
|
25
|
+
"./**/*.test.js": false,
|
|
26
|
+
},
|
|
27
|
+
})
|
|
25
28
|
```
|
|
26
29
|
|
|
27
30
|
```console
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/filesystem",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "Collection of functions to interact with filesystem in Node.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -13,57 +13,46 @@
|
|
|
13
13
|
"url": "https://github.com/jsenv/filesystem"
|
|
14
14
|
},
|
|
15
15
|
"engines": {
|
|
16
|
-
"node": ">=
|
|
16
|
+
"node": ">=16.13.0"
|
|
17
17
|
},
|
|
18
18
|
"publishConfig": {
|
|
19
|
-
"access": "public"
|
|
20
|
-
"registry": "https://registry.npmjs.org"
|
|
19
|
+
"access": "public"
|
|
21
20
|
},
|
|
22
21
|
"type": "module",
|
|
23
22
|
"exports": {
|
|
24
23
|
".": {
|
|
25
|
-
"import": "./main.js"
|
|
26
|
-
"require": "./dist/commonjs/jsenv_filesystem.cjs"
|
|
24
|
+
"import": "./main.js"
|
|
27
25
|
},
|
|
28
26
|
"./*": "./*"
|
|
29
27
|
},
|
|
28
|
+
"main": "./main.js",
|
|
30
29
|
"files": [
|
|
31
|
-
"/dist/",
|
|
32
30
|
"/src/",
|
|
33
31
|
"/main.js"
|
|
34
32
|
],
|
|
35
33
|
"scripts": {
|
|
36
|
-
"eslint
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"dist": "npm run build",
|
|
40
|
-
"build": "node --experimental-import-meta-resolve ./script/build/build.mjs",
|
|
34
|
+
"eslint": "node ./node_modules/eslint/bin/eslint.js .",
|
|
35
|
+
"importmap": "node ./script/importmap/importmap.mjs",
|
|
36
|
+
"performance": "node --expose-gc ./script/performance/performance.mjs --log",
|
|
41
37
|
"test": "node ./script/test/test.mjs",
|
|
42
38
|
"test-with-coverage": "npm run test -- --coverage",
|
|
43
|
-
"prettier
|
|
44
|
-
"prettier-format-stage": "npm run prettier-format -- --staged",
|
|
45
|
-
"prettier-check": "npm run prettier-format -- --dry-run",
|
|
46
|
-
"prepublishOnly": "node ./script/publish/remove_postinstall.mjs && npm run dist",
|
|
47
|
-
"postpublish": "node ./script/publish/restore_postinstall.mjs"
|
|
39
|
+
"prettier": "prettier --write ."
|
|
48
40
|
},
|
|
49
41
|
"dependencies": {
|
|
50
|
-
"@jsenv/
|
|
51
|
-
"@jsenv/url-meta": "6.0.
|
|
42
|
+
"@jsenv/abort": "4.1.2",
|
|
43
|
+
"@jsenv/url-meta": "6.0.3"
|
|
52
44
|
},
|
|
53
45
|
"devDependencies": {
|
|
54
|
-
"@
|
|
55
|
-
"@
|
|
56
|
-
"@jsenv/
|
|
57
|
-
"@jsenv/core": "23.2.2",
|
|
58
|
-
"@jsenv/eslint-config": "16.0.8",
|
|
46
|
+
"@jsenv/assert": "2.4.1",
|
|
47
|
+
"@jsenv/core": "25.1.0",
|
|
48
|
+
"@jsenv/eslint-config": "16.0.9",
|
|
59
49
|
"@jsenv/github-release-package": "1.2.3",
|
|
60
|
-
"@jsenv/importmap-eslint-resolver": "5.
|
|
61
|
-
"@jsenv/importmap-node-module": "
|
|
50
|
+
"@jsenv/importmap-eslint-resolver": "5.2.2",
|
|
51
|
+
"@jsenv/importmap-node-module": "5.1.0",
|
|
62
52
|
"@jsenv/package-publish": "1.6.2",
|
|
63
53
|
"@jsenv/performance-impact": "2.2.1",
|
|
64
|
-
"
|
|
65
|
-
"eslint": "
|
|
66
|
-
"
|
|
67
|
-
"prettier": "2.4.1"
|
|
54
|
+
"eslint": "8.6.0",
|
|
55
|
+
"eslint-plugin-import": "2.25.4",
|
|
56
|
+
"prettier": "2.5.1"
|
|
68
57
|
}
|
|
69
|
-
}
|
|
58
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Abort } from "@jsenv/abort"
|
|
2
2
|
import {
|
|
3
3
|
normalizeStructuredMetaMap,
|
|
4
4
|
urlCanContainsMetaMatching,
|
|
@@ -13,7 +13,7 @@ import { urlToRelativeUrl } from "./urlToRelativeUrl.js"
|
|
|
13
13
|
import { comparePathnames } from "./comparePathnames.js"
|
|
14
14
|
|
|
15
15
|
export const collectDirectoryMatchReport = async ({
|
|
16
|
-
|
|
16
|
+
signal = new AbortController().signal,
|
|
17
17
|
directoryUrl,
|
|
18
18
|
structuredMetaMap,
|
|
19
19
|
predicate,
|
|
@@ -30,11 +30,12 @@ export const collectDirectoryMatchReport = async ({
|
|
|
30
30
|
rootDirectoryUrl,
|
|
31
31
|
)
|
|
32
32
|
|
|
33
|
+
const collectOperation = Abort.startOperation()
|
|
34
|
+
collectOperation.addAbortSignal(signal)
|
|
35
|
+
|
|
33
36
|
const visitDirectory = async (directoryUrl) => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
start: () => readDirectory(directoryUrl),
|
|
37
|
-
})
|
|
37
|
+
collectOperation.throwIfAborted()
|
|
38
|
+
const directoryItems = await readDirectory(directoryUrl)
|
|
38
39
|
|
|
39
40
|
await Promise.all(
|
|
40
41
|
directoryItems.map(async (directoryItem) => {
|
|
@@ -44,19 +45,19 @@ export const collectDirectoryMatchReport = async ({
|
|
|
44
45
|
rootDirectoryUrl,
|
|
45
46
|
)
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
collectOperation.throwIfAborted()
|
|
49
|
+
const directoryChildNodeStats = await readFileSystemNodeStat(
|
|
50
|
+
directoryChildNodeUrl,
|
|
51
|
+
{
|
|
52
|
+
// we ignore symlink because recursively traversed
|
|
53
|
+
// so symlinked file will be discovered.
|
|
54
|
+
// Moreover if they lead outside of directoryPath it can become a problem
|
|
55
|
+
// like infinite recursion of whatever.
|
|
56
|
+
// that we could handle using an object of pathname already seen but it will be useless
|
|
57
|
+
// because directoryPath is recursively traversed
|
|
58
|
+
followLink: false,
|
|
59
|
+
},
|
|
60
|
+
)
|
|
60
61
|
|
|
61
62
|
if (directoryChildNodeStats.isDirectory()) {
|
|
62
63
|
const subDirectoryUrl = `${directoryChildNodeUrl}/`
|
|
@@ -104,11 +105,16 @@ export const collectDirectoryMatchReport = async ({
|
|
|
104
105
|
}),
|
|
105
106
|
)
|
|
106
107
|
}
|
|
107
|
-
await visitDirectory(rootDirectoryUrl)
|
|
108
108
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
109
|
+
try {
|
|
110
|
+
await visitDirectory(rootDirectoryUrl)
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
matchingArray: sortByRelativeUrl(matchingArray),
|
|
114
|
+
ignoredArray: sortByRelativeUrl(ignoredArray),
|
|
115
|
+
}
|
|
116
|
+
} finally {
|
|
117
|
+
await collectOperation.end()
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
package/src/collectFiles.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Abort } from "@jsenv/abort"
|
|
2
2
|
import {
|
|
3
3
|
normalizeStructuredMetaMap,
|
|
4
4
|
urlCanContainsMetaMatching,
|
|
@@ -12,50 +12,45 @@ import { urlToRelativeUrl } from "./urlToRelativeUrl.js"
|
|
|
12
12
|
import { comparePathnames } from "./comparePathnames.js"
|
|
13
13
|
|
|
14
14
|
export const collectFiles = async ({
|
|
15
|
-
|
|
15
|
+
signal = new AbortController().signal,
|
|
16
16
|
directoryUrl,
|
|
17
17
|
structuredMetaMap,
|
|
18
18
|
predicate,
|
|
19
|
-
matchingFileOperation = () => null,
|
|
20
19
|
}) => {
|
|
21
20
|
const rootDirectoryUrl = assertAndNormalizeDirectoryUrl(directoryUrl)
|
|
22
21
|
if (typeof predicate !== "function") {
|
|
23
22
|
throw new TypeError(`predicate must be a function, got ${predicate}`)
|
|
24
23
|
}
|
|
25
|
-
if (typeof matchingFileOperation !== "function") {
|
|
26
|
-
throw new TypeError(
|
|
27
|
-
`matchingFileOperation must be a function, got ${matchingFileOperation}`,
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
24
|
const structuredMetaMapNormalized = normalizeStructuredMetaMap(
|
|
31
25
|
structuredMetaMap,
|
|
32
26
|
rootDirectoryUrl,
|
|
33
27
|
)
|
|
34
28
|
|
|
29
|
+
const collectOperation = Abort.startOperation()
|
|
30
|
+
collectOperation.addAbortSignal(signal)
|
|
31
|
+
|
|
35
32
|
const matchingFileResultArray = []
|
|
36
33
|
const visitDirectory = async (directoryUrl) => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
start: () => readDirectory(directoryUrl),
|
|
40
|
-
})
|
|
34
|
+
collectOperation.throwIfAborted()
|
|
35
|
+
const directoryItems = await readDirectory(directoryUrl)
|
|
41
36
|
|
|
42
37
|
await Promise.all(
|
|
43
38
|
directoryItems.map(async (directoryItem) => {
|
|
44
39
|
const directoryChildNodeUrl = `${directoryUrl}${directoryItem}`
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
41
|
+
collectOperation.throwIfAborted()
|
|
42
|
+
const directoryChildNodeStats = await readFileSystemNodeStat(
|
|
43
|
+
directoryChildNodeUrl,
|
|
44
|
+
{
|
|
45
|
+
// we ignore symlink because recursively traversed
|
|
46
|
+
// so symlinked file will be discovered.
|
|
47
|
+
// Moreover if they lead outside of directoryPath it can become a problem
|
|
48
|
+
// like infinite recursion of whatever.
|
|
49
|
+
// that we could handle using an object of pathname already seen but it will be useless
|
|
50
|
+
// because directoryPath is recursively traversed
|
|
51
|
+
followLink: false,
|
|
52
|
+
},
|
|
53
|
+
)
|
|
59
54
|
|
|
60
55
|
if (directoryChildNodeStats.isDirectory()) {
|
|
61
56
|
const subDirectoryUrl = `${directoryChildNodeUrl}/`
|
|
@@ -85,36 +80,30 @@ export const collectFiles = async ({
|
|
|
85
80
|
directoryChildNodeUrl,
|
|
86
81
|
rootDirectoryUrl,
|
|
87
82
|
)
|
|
88
|
-
const operationResult = await createOperation({
|
|
89
|
-
cancellationToken,
|
|
90
|
-
start: () =>
|
|
91
|
-
matchingFileOperation({
|
|
92
|
-
cancellationToken,
|
|
93
|
-
relativeUrl,
|
|
94
|
-
meta,
|
|
95
|
-
fileStats: directoryChildNodeStats,
|
|
96
|
-
}),
|
|
97
|
-
})
|
|
98
83
|
matchingFileResultArray.push({
|
|
99
84
|
url: new URL(relativeUrl, rootDirectoryUrl).href,
|
|
100
85
|
relativeUrl,
|
|
101
86
|
meta,
|
|
102
87
|
fileStats: directoryChildNodeStats,
|
|
103
|
-
operationResult,
|
|
104
88
|
})
|
|
105
89
|
return
|
|
106
90
|
}
|
|
107
91
|
}),
|
|
108
92
|
)
|
|
109
93
|
}
|
|
110
|
-
await visitDirectory(rootDirectoryUrl)
|
|
111
94
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
try {
|
|
96
|
+
await visitDirectory(rootDirectoryUrl)
|
|
97
|
+
|
|
98
|
+
// When we operate on thoose files later it feels more natural
|
|
99
|
+
// to perform operation in the same order they appear in the filesystem.
|
|
100
|
+
// It also allow to get a predictable return value.
|
|
101
|
+
// For that reason we sort matchingFileResultArray
|
|
102
|
+
matchingFileResultArray.sort((leftFile, rightFile) => {
|
|
103
|
+
return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl)
|
|
104
|
+
})
|
|
105
|
+
return matchingFileResultArray
|
|
106
|
+
} finally {
|
|
107
|
+
await collectOperation.end()
|
|
108
|
+
}
|
|
120
109
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Abort } from "@jsenv/abort"
|
|
2
|
+
|
|
1
3
|
import { assertAndNormalizeDirectoryUrl } from "./assertAndNormalizeDirectoryUrl.js"
|
|
2
4
|
import { readFileSystemNodeStat } from "./readFileSystemNodeStat.js"
|
|
3
5
|
import { urlToFileSystemPath } from "./urlToFileSystemPath.js"
|
|
@@ -5,11 +7,11 @@ import { readSymbolicLink } from "./readSymbolicLink.js"
|
|
|
5
7
|
import { resolveUrl } from "./resolveUrl.js"
|
|
6
8
|
import { readDirectory } from "./readDirectory.js"
|
|
7
9
|
import { copyFileSystemNode } from "./copyFileSystemNode.js"
|
|
8
|
-
|
|
9
10
|
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"
|
|
10
11
|
import { statsToType } from "./internal/statsToType.js"
|
|
11
12
|
|
|
12
13
|
export const copyDirectoryContent = async ({
|
|
14
|
+
signal = new AbortController().signal,
|
|
13
15
|
from,
|
|
14
16
|
to,
|
|
15
17
|
overwrite,
|
|
@@ -65,17 +67,27 @@ export const copyDirectoryContent = async ({
|
|
|
65
67
|
)
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
const copyOperation = Abort.startOperation()
|
|
71
|
+
copyOperation.addAbortSignal(signal)
|
|
72
|
+
try {
|
|
73
|
+
copyOperation.throwIfAborted()
|
|
74
|
+
const directoryEntries = await readDirectory(fromUrl)
|
|
75
|
+
await Promise.all(
|
|
76
|
+
directoryEntries.map(async (directoryEntry) => {
|
|
77
|
+
const from = resolveUrl(directoryEntry, fromUrl)
|
|
78
|
+
const to = resolveUrl(directoryEntry, toUrl)
|
|
79
|
+
await copyOperation.withSignal(async (signal) => {
|
|
80
|
+
await copyFileSystemNode({
|
|
81
|
+
signal,
|
|
82
|
+
from,
|
|
83
|
+
to,
|
|
84
|
+
overwrite,
|
|
85
|
+
followLink,
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
}),
|
|
89
|
+
)
|
|
90
|
+
} finally {
|
|
91
|
+
await copyOperation.end()
|
|
92
|
+
}
|
|
81
93
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* eslint-disable import/max-dependencies */
|
|
2
1
|
import { copyFile as copyFileNode } from "node:fs"
|
|
2
|
+
import { Abort } from "@jsenv/abort"
|
|
3
3
|
|
|
4
4
|
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"
|
|
5
5
|
import { statsToType } from "./internal/statsToType.js"
|
|
@@ -21,6 +21,7 @@ import { removeFileSystemNode } from "./removeFileSystemNode.js"
|
|
|
21
21
|
import { urlToFileSystemPath } from "./urlToFileSystemPath.js"
|
|
22
22
|
|
|
23
23
|
export const copyFileSystemNode = async ({
|
|
24
|
+
signal = new AbortController().signal,
|
|
24
25
|
from,
|
|
25
26
|
to,
|
|
26
27
|
overwrite = false,
|
|
@@ -68,32 +69,15 @@ export const copyFileSystemNode = async ({
|
|
|
68
69
|
)
|
|
69
70
|
}
|
|
70
71
|
|
|
71
|
-
if (destinationStats) {
|
|
72
|
-
const sourceType = statsToType(sourceStats)
|
|
73
|
-
const destinationType = statsToType(destinationStats)
|
|
74
|
-
|
|
75
|
-
if (sourceType !== destinationType) {
|
|
76
|
-
throw new Error(
|
|
77
|
-
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`,
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
if (!overwrite) {
|
|
81
|
-
throw new Error(
|
|
82
|
-
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`,
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// remove file, link, directory...
|
|
87
|
-
await removeFileSystemNode(toUrl, { recursive: true, allowUseless: true })
|
|
88
|
-
} else {
|
|
89
|
-
await ensureParentDirectories(toUrl)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
72
|
if (sourceStats.isDirectory()) {
|
|
93
73
|
toUrl = ensureUrlTrailingSlash(toUrl)
|
|
94
74
|
}
|
|
95
75
|
|
|
76
|
+
const copyOperation = Abort.startOperation()
|
|
77
|
+
copyOperation.addAbortSignal(signal)
|
|
78
|
+
|
|
96
79
|
const visit = async (url, stats) => {
|
|
80
|
+
copyOperation.throwIfAborted()
|
|
97
81
|
if (stats.isFile() || stats.isCharacterDevice() || stats.isBlockDevice()) {
|
|
98
82
|
await visitFile(url, stats)
|
|
99
83
|
} else if (stats.isSymbolicLink()) {
|
|
@@ -197,7 +181,37 @@ export const copyFileSystemNode = async ({
|
|
|
197
181
|
)
|
|
198
182
|
}
|
|
199
183
|
|
|
200
|
-
|
|
184
|
+
try {
|
|
185
|
+
if (destinationStats) {
|
|
186
|
+
const sourceType = statsToType(sourceStats)
|
|
187
|
+
const destinationType = statsToType(destinationStats)
|
|
188
|
+
|
|
189
|
+
if (sourceType !== destinationType) {
|
|
190
|
+
throw new Error(
|
|
191
|
+
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`,
|
|
192
|
+
)
|
|
193
|
+
}
|
|
194
|
+
if (!overwrite) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`cannot copy ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`,
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// remove file, link, directory...
|
|
201
|
+
await removeFileSystemNode(toUrl, {
|
|
202
|
+
signal: copyOperation.signal,
|
|
203
|
+
recursive: true,
|
|
204
|
+
allowUseless: true,
|
|
205
|
+
})
|
|
206
|
+
} else {
|
|
207
|
+
await ensureParentDirectories(toUrl)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
copyOperation.throwIfAborted()
|
|
211
|
+
await visit(fromUrl, sourceStats)
|
|
212
|
+
} finally {
|
|
213
|
+
await copyOperation.end()
|
|
214
|
+
}
|
|
201
215
|
}
|
|
202
216
|
|
|
203
217
|
const copyFileContentNaive = (filePath, fileDestinationPath) => {
|
package/src/listFilesMatching.js
CHANGED
|
@@ -1,31 +1,19 @@
|
|
|
1
1
|
import { collectFiles } from "./collectFiles.js"
|
|
2
2
|
|
|
3
|
-
export const listFilesMatching = async (
|
|
4
|
-
patterns,
|
|
3
|
+
export const listFilesMatching = async ({
|
|
5
4
|
directoryUrl = process.cwd(),
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
throw new TypeError(
|
|
11
|
-
`patterns must be a string or an array of strings, got ${patterns}`,
|
|
12
|
-
)
|
|
5
|
+
patterns,
|
|
6
|
+
signal,
|
|
7
|
+
}) => {
|
|
8
|
+
if (typeof patterns !== "object" || patterns === null) {
|
|
9
|
+
throw new TypeError(`patterns must be an object, got ${patterns}`)
|
|
13
10
|
}
|
|
14
11
|
|
|
15
|
-
const patternMap = {}
|
|
16
|
-
patterns.forEach((pattern, index) => {
|
|
17
|
-
if (typeof pattern !== "string") {
|
|
18
|
-
throw new TypeError(
|
|
19
|
-
`every pattern must be a string, found ${pattern} at index ${index}`,
|
|
20
|
-
)
|
|
21
|
-
}
|
|
22
|
-
patternMap[pattern] = true
|
|
23
|
-
})
|
|
24
|
-
|
|
25
12
|
const fileDatas = await collectFiles({
|
|
13
|
+
signal,
|
|
26
14
|
directoryUrl,
|
|
27
15
|
structuredMetaMap: {
|
|
28
|
-
matches:
|
|
16
|
+
matches: patterns,
|
|
29
17
|
},
|
|
30
18
|
predicate: ({ matches }) => matches,
|
|
31
19
|
})
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Abort } from "@jsenv/abort"
|
|
2
|
+
|
|
1
3
|
import { assertAndNormalizeDirectoryUrl } from "./assertAndNormalizeDirectoryUrl.js"
|
|
2
4
|
import { readFileSystemNodeStat } from "./readFileSystemNodeStat.js"
|
|
3
5
|
import { urlToFileSystemPath } from "./urlToFileSystemPath.js"
|
|
@@ -5,11 +7,11 @@ import { readSymbolicLink } from "./readSymbolicLink.js"
|
|
|
5
7
|
import { resolveUrl } from "./resolveUrl.js"
|
|
6
8
|
import { readDirectory } from "./readDirectory.js"
|
|
7
9
|
import { moveFileSystemNode } from "./moveFileSystemNode.js"
|
|
8
|
-
|
|
9
10
|
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"
|
|
10
11
|
import { statsToType } from "./internal/statsToType.js"
|
|
11
12
|
|
|
12
13
|
export const moveDirectoryContent = async ({
|
|
14
|
+
signal = new AbortController().signal,
|
|
13
15
|
from,
|
|
14
16
|
to,
|
|
15
17
|
overwrite,
|
|
@@ -65,17 +67,28 @@ export const moveDirectoryContent = async ({
|
|
|
65
67
|
)
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
70
|
+
const moveOperation = Abort.startOperation()
|
|
71
|
+
moveOperation.addAbortSignal(signal)
|
|
72
|
+
try {
|
|
73
|
+
moveOperation.throwIfAborted()
|
|
74
|
+
const directoryEntries = await readDirectory(fromUrl)
|
|
75
|
+
await Promise.all(
|
|
76
|
+
directoryEntries.map(async (directoryEntry) => {
|
|
77
|
+
const from = resolveUrl(directoryEntry, fromUrl)
|
|
78
|
+
const to = resolveUrl(directoryEntry, toUrl)
|
|
79
|
+
|
|
80
|
+
await moveOperation.withSignal(async (signal) => {
|
|
81
|
+
await moveFileSystemNode({
|
|
82
|
+
signal,
|
|
83
|
+
from,
|
|
84
|
+
to,
|
|
85
|
+
overwrite,
|
|
86
|
+
followLink,
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
}),
|
|
90
|
+
)
|
|
91
|
+
} finally {
|
|
92
|
+
await moveOperation.end()
|
|
93
|
+
}
|
|
81
94
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/* eslint-disable import/max-dependencies */
|
|
2
1
|
import { rename } from "node:fs"
|
|
2
|
+
import { Abort } from "@jsenv/abort"
|
|
3
3
|
|
|
4
4
|
import { urlTargetsSameFileSystemPath } from "./internal/urlTargetsSameFileSystemPath.js"
|
|
5
5
|
import { statsToType } from "./internal/statsToType.js"
|
|
@@ -13,6 +13,7 @@ import { readFileSystemNodeStat } from "./readFileSystemNodeStat.js"
|
|
|
13
13
|
import { readSymbolicLink } from "./readSymbolicLink.js"
|
|
14
14
|
|
|
15
15
|
export const moveFileSystemNode = async ({
|
|
16
|
+
signal = new AbortController().signal,
|
|
16
17
|
from,
|
|
17
18
|
to,
|
|
18
19
|
overwrite = false,
|
|
@@ -57,37 +58,51 @@ export const moveFileSystemNode = async ({
|
|
|
57
58
|
)
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const destinationType = statsToType(destinationStats)
|
|
61
|
+
const moveOperation = Abort.startOperation()
|
|
62
|
+
moveOperation.addAbortSignal(signal)
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
)
|
|
68
|
-
}
|
|
69
|
-
if (!overwrite) {
|
|
70
|
-
throw new Error(
|
|
71
|
-
`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`,
|
|
72
|
-
)
|
|
73
|
-
}
|
|
64
|
+
try {
|
|
65
|
+
if (destinationStats) {
|
|
66
|
+
const sourceType = statsToType(sourceStats)
|
|
67
|
+
const destinationType = statsToType(destinationStats)
|
|
74
68
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
69
|
+
if (sourceType !== destinationType) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`,
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
if (!overwrite) {
|
|
75
|
+
throw new Error(
|
|
76
|
+
`cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and overwrite option is disabled`,
|
|
77
|
+
)
|
|
78
|
+
}
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
to: toUrl,
|
|
86
|
-
preserveStat: true,
|
|
80
|
+
// remove file, link, directory...
|
|
81
|
+
await removeFileSystemNode(toUrl, {
|
|
82
|
+
signal: moveOperation.signal,
|
|
83
|
+
recursive: true,
|
|
87
84
|
})
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
} else {
|
|
86
|
+
await ensureParentDirectories(toUrl)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
moveOperation.throwIfAborted()
|
|
90
|
+
await moveNaive(fromPath, toPath, {
|
|
91
|
+
handleCrossDeviceError: async () => {
|
|
92
|
+
await copyFileSystemNode({
|
|
93
|
+
from: fromUrl,
|
|
94
|
+
to: toUrl,
|
|
95
|
+
preserveStat: true,
|
|
96
|
+
})
|
|
97
|
+
await removeFileSystemNode(fromUrl, {
|
|
98
|
+
signal: moveOperation.signal,
|
|
99
|
+
recursive: true,
|
|
100
|
+
})
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
} finally {
|
|
104
|
+
await moveOperation.end()
|
|
105
|
+
}
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
const moveNaive = (
|