@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 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
- "./**/*.js",
23
- new URL("./", import.meta.url),
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.4.0",
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": ">=14.17.0"
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-check": "node ./node_modules/eslint/bin/eslint.js .",
37
- "generate-importmap": "node ./script/importmap/generate_importmap.mjs",
38
- "measure-performances": "node ./script/performance/generate_performance_report.mjs --local",
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-format": "node ./script/prettier/prettier_format.mjs",
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/cancellation": "3.0.0",
51
- "@jsenv/url-meta": "6.0.1"
42
+ "@jsenv/abort": "4.1.2",
43
+ "@jsenv/url-meta": "6.0.3"
52
44
  },
53
45
  "devDependencies": {
54
- "@babel/core": "7.16.0",
55
- "@babel/eslint-parser": "7.16.0",
56
- "@jsenv/assert": "2.3.2",
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.1.2",
61
- "@jsenv/importmap-node-module": "2.4.1",
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
- "@jsenv/prettier-check-project": "5.6.1",
65
- "eslint": "7.32.0",
66
- "eslint-plugin-import": "2.25.2",
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 { createCancellationToken, createOperation } from "@jsenv/cancellation"
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
- cancellationToken = createCancellationToken(),
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
- const directoryItems = await createOperation({
35
- cancellationToken,
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
- const directoryChildNodeStats = await createOperation({
48
- cancellationToken,
49
- start: () =>
50
- readFileSystemNodeStat(directoryChildNodeUrl, {
51
- // we ignore symlink because recursively traversed
52
- // so symlinked file will be discovered.
53
- // Moreover if they lead outside of directoryPath it can become a problem
54
- // like infinite recursion of whatever.
55
- // that we could handle using an object of pathname already seen but it will be useless
56
- // because directoryPath is recursively traversed
57
- followLink: false,
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
- return {
110
- matchingArray: sortByRelativeUrl(matchingArray),
111
- ignoredArray: sortByRelativeUrl(ignoredArray),
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
 
@@ -1,4 +1,4 @@
1
- import { createCancellationToken, createOperation } from "@jsenv/cancellation"
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
- cancellationToken = createCancellationToken(),
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
- const directoryItems = await createOperation({
38
- cancellationToken,
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
- const directoryChildNodeStats = await createOperation({
47
- cancellationToken,
48
- start: () =>
49
- readFileSystemNodeStat(directoryChildNodeUrl, {
50
- // we ignore symlink because recursively traversed
51
- // so symlinked file will be discovered.
52
- // Moreover if they lead outside of directoryPath it can become a problem
53
- // like infinite recursion of whatever.
54
- // that we could handle using an object of pathname already seen but it will be useless
55
- // because directoryPath is recursively traversed
56
- followLink: false,
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
- // When we operate on thoose files later it feels more natural
113
- // to perform operation in the same order they appear in the filesystem.
114
- // It also allow to get a predictable return value.
115
- // For that reason we sort matchingFileResultArray
116
- matchingFileResultArray.sort((leftFile, rightFile) => {
117
- return comparePathnames(leftFile.relativeUrl, rightFile.relativeUrl)
118
- })
119
- return matchingFileResultArray
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 directoryEntries = await readDirectory(fromUrl)
69
- await Promise.all(
70
- directoryEntries.map(async (directoryEntry) => {
71
- const from = resolveUrl(directoryEntry, fromUrl)
72
- const to = resolveUrl(directoryEntry, toUrl)
73
- await copyFileSystemNode({
74
- from,
75
- to,
76
- overwrite,
77
- followLink,
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
- await visit(fromUrl, sourceStats)
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) => {
@@ -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
- if (typeof patterns === "string") {
8
- patterns = [patterns]
9
- } else if (Array.isArray(patterns)) {
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: patternMap,
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 directoryEntries = await readDirectory(fromUrl)
69
- await Promise.all(
70
- directoryEntries.map(async (directoryEntry) => {
71
- const from = resolveUrl(directoryEntry, fromUrl)
72
- const to = resolveUrl(directoryEntry, toUrl)
73
- await moveFileSystemNode({
74
- from,
75
- to,
76
- overwrite,
77
- followLink,
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
- if (destinationStats) {
61
- const sourceType = statsToType(sourceStats)
62
- const destinationType = statsToType(destinationStats)
61
+ const moveOperation = Abort.startOperation()
62
+ moveOperation.addAbortSignal(signal)
63
63
 
64
- if (sourceType !== destinationType) {
65
- throw new Error(
66
- `cannot move ${sourceType} from ${fromPath} to ${toPath} because destination exists and is not a ${sourceType} (it's a ${destinationType})`,
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
- // remove file, link, directory...
76
- await removeFileSystemNode(toUrl, { recursive: true })
77
- } else {
78
- await ensureParentDirectories(toUrl)
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
- await moveNaive(fromPath, toPath, {
82
- handleCrossDeviceError: async () => {
83
- await copyFileSystemNode({
84
- from: fromUrl,
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
- await removeFileSystemNode(fromUrl, { recursive: true })
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 = (