@magic/fs 0.0.38 → 0.0.39
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 +5 -5
- package/src/exists.js +2 -1
- package/src/fs.js +18 -13
- package/src/getDirectories.js +34 -53
- package/src/getFilePath.js +10 -3
- package/src/getFiles.js +32 -52
- package/src/mkdirp.js +7 -17
- package/src/rmrf.js +4 -17
- package/types/fs.d.ts +2 -2
- package/types/index.d.ts +5 -2
- package/types/mkdirp.d.ts +4 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magic/fs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.39",
|
|
4
4
|
"author": "Wizards & Witches",
|
|
5
5
|
"description": "nodejs fs promises + goodies",
|
|
6
6
|
"license": "AGPL-3.0",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"check": "npm run tsc"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=
|
|
29
|
+
"node": ">=20.0.0"
|
|
30
30
|
},
|
|
31
31
|
"engineStrict": true,
|
|
32
32
|
"repository": {
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"@magic-modules/pre": "0.0.12",
|
|
44
44
|
"@magic-themes/docs": "0.0.15",
|
|
45
45
|
"@magic/core": "0.0.158",
|
|
46
|
-
"@magic/format": "0.0.
|
|
47
|
-
"@magic/test": "0.3.
|
|
48
|
-
"@types/node": "
|
|
46
|
+
"@magic/format": "0.0.74",
|
|
47
|
+
"@magic/test": "0.3.19",
|
|
48
|
+
"@types/node": "26.0.1",
|
|
49
49
|
"typescript": "6.0.3"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
package/src/exists.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { fs } from './fs.js'
|
|
2
|
+
import { constants } from 'node:fs'
|
|
2
3
|
|
|
3
4
|
import error from '@magic/error'
|
|
4
5
|
import is from '@magic/types'
|
|
@@ -16,7 +17,7 @@ export const exists = async f => {
|
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
try {
|
|
19
|
-
await fs.
|
|
20
|
+
await fs.access(f, constants.F_OK)
|
|
20
21
|
return true
|
|
21
22
|
} catch (e) {
|
|
22
23
|
const err = /** @type {Error & { code: string }} */ (e)
|
package/src/fs.js
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
|
-
import fso from 'node:fs'
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
const readDir = fso.promises.readdir
|
|
5
|
-
const readFile = fso.promises.readFile
|
|
6
|
-
const rmdir = fso.promises.rmdir
|
|
1
|
+
import fso, { constants } from 'node:fs'
|
|
2
|
+
import { access } from 'node:fs/promises'
|
|
7
3
|
|
|
8
4
|
export const fs = /** @type {const} */ ({
|
|
9
5
|
...fso,
|
|
10
6
|
...fso.promises,
|
|
11
|
-
exists:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
exists: async (/** @type {import('node:fs').PathLike} */ f) => {
|
|
8
|
+
try {
|
|
9
|
+
await access(f, constants.F_OK)
|
|
10
|
+
return true
|
|
11
|
+
} catch {
|
|
12
|
+
return false
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
readdir: fso.promises.readdir,
|
|
16
|
+
readDir: fso.promises.readdir,
|
|
17
|
+
readFile: fso.promises.readFile,
|
|
18
|
+
readfile: fso.promises.readFile,
|
|
19
|
+
rmdir: fso.promises.rmdir,
|
|
20
|
+
rmDir: fso.promises.rmdir,
|
|
18
21
|
watch: fso.watch,
|
|
22
|
+
access,
|
|
23
|
+
constants,
|
|
19
24
|
})
|
package/src/getDirectories.js
CHANGED
|
@@ -6,8 +6,6 @@ import error from '@magic/error'
|
|
|
6
6
|
|
|
7
7
|
import { fs } from './fs.js'
|
|
8
8
|
|
|
9
|
-
import { getFilePath } from './getFilePath.js'
|
|
10
|
-
|
|
11
9
|
const libName = '@magic/fs.getDirectories'
|
|
12
10
|
|
|
13
11
|
/**
|
|
@@ -28,10 +26,17 @@ export const getDirectories = async (dir, options = {}) => {
|
|
|
28
26
|
}
|
|
29
27
|
}
|
|
30
28
|
|
|
31
|
-
let { minDepth, maxDepth = false, depth
|
|
29
|
+
let { minDepth, maxDepth = false, depth, root, noRoot = false } = options
|
|
30
|
+
|
|
31
|
+
// Only apply depth === false logic if explicitly passed, not if undefined
|
|
32
|
+
const depthExplicitlyFalse = 'depth' in options && depth === false
|
|
32
33
|
|
|
33
34
|
if (!is.number(maxDepth)) {
|
|
34
|
-
|
|
35
|
+
if (depthExplicitlyFalse) {
|
|
36
|
+
maxDepth = 1
|
|
37
|
+
} else {
|
|
38
|
+
maxDepth = is.number(depth) ? depth : 200_000
|
|
39
|
+
}
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
if (!is.number(minDepth)) {
|
|
@@ -56,68 +61,44 @@ export const getDirectories = async (dir, options = {}) => {
|
|
|
56
61
|
|
|
57
62
|
try {
|
|
58
63
|
if (is.array(dir)) {
|
|
59
|
-
const dirs = await Promise.all(dir.map(
|
|
64
|
+
const dirs = await Promise.all(dir.map(f => getDirectories(f, options)))
|
|
60
65
|
|
|
61
66
|
return deep.flatten(...dirs).filter(a => a)
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
.split(path.sep)
|
|
67
|
-
.filter(a => a).length
|
|
68
|
-
|
|
69
|
-
if (currentDepth > maxDepth) {
|
|
70
|
-
return []
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const dirContent = await fs.readdir(dir)
|
|
69
|
+
// Use recursive readdir with file types - single syscall, no per-file stat needed
|
|
70
|
+
const entries = await fs.readdir(dir, { recursive: true, withFileTypes: true })
|
|
74
71
|
|
|
75
72
|
/** @type {string[]} */
|
|
76
73
|
const dirs = []
|
|
77
74
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
75
|
+
if (!noRoot) {
|
|
76
|
+
// Root dir is depth 0
|
|
77
|
+
if (0 >= minDepth && 0 <= maxDepth) {
|
|
78
|
+
dirs.push(dir)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
filePath = [filePath]
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
await Promise.all(
|
|
92
|
-
filePath.map(async file => {
|
|
93
|
-
try {
|
|
94
|
-
const stat = await fs.stat(file)
|
|
95
|
-
if (stat.isDirectory()) {
|
|
96
|
-
if (is.array(filePath)) {
|
|
97
|
-
dirs.push(...filePath)
|
|
98
|
-
} else if (is.string(filePath)) {
|
|
99
|
-
dirs.push(filePath)
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
} catch (statErr) {
|
|
103
|
-
// File might have been deleted between readdir and stat
|
|
104
|
-
// Just skip it
|
|
105
|
-
}
|
|
106
|
-
}),
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
}),
|
|
110
|
-
)
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
// Use parentPath + name for correct full path with recursive readdir
|
|
85
|
+
const fullPath = path.join(entry.parentPath, entry.name)
|
|
111
86
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
87
|
+
// Calculate depth relative to root
|
|
88
|
+
const relativePath = path.relative(root || dir, fullPath)
|
|
89
|
+
const entryDepth = relativePath.split(path.sep).filter(Boolean).length
|
|
90
|
+
|
|
91
|
+
// Filter by minDepth and maxDepth
|
|
92
|
+
if (entryDepth >= minDepth && entryDepth <= maxDepth) {
|
|
93
|
+
dirs.push(fullPath)
|
|
94
|
+
}
|
|
95
|
+
}
|
|
115
96
|
}
|
|
116
97
|
|
|
117
|
-
const finalized =
|
|
98
|
+
const finalized = dirs
|
|
118
99
|
.filter(a => is.string(a))
|
|
119
|
-
.filter(
|
|
120
|
-
const currentDepth =
|
|
100
|
+
.filter(d => {
|
|
101
|
+
const currentDepth = d
|
|
121
102
|
.replace(root || process.cwd(), '')
|
|
122
103
|
.split(path.sep)
|
|
123
104
|
.filter(a => a).length
|
package/src/getFilePath.js
CHANGED
|
@@ -39,10 +39,17 @@ export const getFilePath = async (fn, dir, file, args = {}) => {
|
|
|
39
39
|
|
|
40
40
|
const filePath = path.join(dir, file)
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
// Use readdir with withFileTypes - more efficient than stat
|
|
43
|
+
const entries = await fs.readdir(dir, { withFileTypes: true })
|
|
44
|
+
const entry = entries.find(e => e.name === file)
|
|
45
|
+
|
|
46
|
+
if (!entry) {
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (entry.isDirectory()) {
|
|
44
51
|
return await fn(filePath, args)
|
|
45
|
-
} else if (
|
|
52
|
+
} else if (entry.isFile()) {
|
|
46
53
|
return filePath
|
|
47
54
|
}
|
|
48
55
|
}
|
package/src/getFiles.js
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import path from 'node:path'
|
|
2
2
|
|
|
3
|
-
// import deep from '@magic/deep'
|
|
4
3
|
import is from '@magic/types'
|
|
5
4
|
import error from '@magic/error'
|
|
6
5
|
|
|
7
|
-
import { getFilePath } from './getFilePath.js'
|
|
8
6
|
import { fs } from './fs.js'
|
|
9
7
|
|
|
10
8
|
const libName = '@magic/fs.getFiles'
|
|
@@ -54,60 +52,42 @@ export const getFiles = async (dir, options = {}) => {
|
|
|
54
52
|
root = dir
|
|
55
53
|
}
|
|
56
54
|
|
|
57
|
-
const currentDepth = dir
|
|
58
|
-
.replace(root, '')
|
|
59
|
-
.split(path.sep)
|
|
60
|
-
.filter(a => a).length
|
|
61
|
-
|
|
62
|
-
if (currentDepth > maxDepth) {
|
|
63
|
-
return []
|
|
64
|
-
}
|
|
65
|
-
|
|
66
55
|
try {
|
|
67
|
-
|
|
68
|
-
const
|
|
69
|
-
dirContent.map(file => getFilePath(getFiles, dir, file, { maxDepth, minDepth, root })),
|
|
70
|
-
)
|
|
71
|
-
|
|
72
|
-
const flatFiles = files
|
|
73
|
-
.flat(20000)
|
|
74
|
-
.filter(a => !is.undef(a))
|
|
75
|
-
/*
|
|
76
|
-
* if an extension parameter has been passed,
|
|
77
|
-
* remove the file if it does not end with extension
|
|
78
|
-
*/
|
|
79
|
-
.filter(a => !extension || a.endsWith(extension))
|
|
80
|
-
|
|
81
|
-
/*
|
|
82
|
-
* filter nonfiles - use async stat
|
|
83
|
-
*/
|
|
84
|
-
const fileStats = await Promise.all(
|
|
85
|
-
flatFiles.map(async f => {
|
|
86
|
-
try {
|
|
87
|
-
const stat = await fs.stat(f)
|
|
88
|
-
return { path: f, isFile: stat.isFile() }
|
|
89
|
-
} catch {
|
|
90
|
-
return { path: f, isFile: false }
|
|
91
|
-
}
|
|
92
|
-
}),
|
|
93
|
-
)
|
|
56
|
+
// Use recursive readdir with file types - single syscall, no per-file stat needed
|
|
57
|
+
const entries = await fs.readdir(dir, { recursive: true, withFileTypes: true })
|
|
94
58
|
|
|
95
59
|
return (
|
|
96
|
-
|
|
97
|
-
.filter(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
60
|
+
entries
|
|
61
|
+
.filter(entry => {
|
|
62
|
+
// Only files
|
|
63
|
+
if (!entry.isFile()) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Calculate depth relative to root - based on parent directory, not file name
|
|
68
|
+
const parentPath = path.join(entry.parentPath)
|
|
69
|
+
const relativePath = path.relative(root, parentPath)
|
|
70
|
+
const entryDepth = relativePath.split(path.sep).filter(Boolean).length
|
|
71
|
+
|
|
72
|
+
// Filter by maxDepth
|
|
73
|
+
if (entryDepth > maxDepth) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Filter by minDepth
|
|
78
|
+
if (entryDepth < minDepth) {
|
|
79
|
+
return false
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Filter by extension
|
|
83
|
+
if (extension && !relativePath.endsWith(extension)) {
|
|
84
|
+
return false
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return true
|
|
110
88
|
})
|
|
89
|
+
// Use parentPath + name for correct full path with recursive readdir
|
|
90
|
+
.map(entry => path.join(entry.parentPath, entry.name))
|
|
111
91
|
)
|
|
112
92
|
} catch (e) {
|
|
113
93
|
const err = /** @type {Error & { code?: string }} */ (e)
|
package/src/mkdirp.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import path from 'path'
|
|
2
|
-
|
|
3
|
-
import error from '@magic/error'
|
|
4
1
|
import is from '@magic/types'
|
|
5
|
-
|
|
6
2
|
import { fs } from './fs.js'
|
|
3
|
+
import error from '@magic/error'
|
|
7
4
|
|
|
8
5
|
const libName = '@magic/fs.mkdirp'
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
8
|
*
|
|
12
|
-
* @param {
|
|
13
|
-
* @
|
|
9
|
+
* @param {import('node:fs').PathLike} p
|
|
10
|
+
* @param {import('node:fs').MakeDirectoryOptions} opts
|
|
11
|
+
*
|
|
12
|
+
* @returns {Promise<boolean>}
|
|
14
13
|
*/
|
|
15
|
-
export const mkdirp = async p => {
|
|
14
|
+
export const mkdirp = async (p, opts = {}) => {
|
|
16
15
|
if (is.empty(p)) {
|
|
17
16
|
throw error(`${libName} expects a non-empty path string as argument.`, 'E_ARG_EMPTY')
|
|
18
17
|
}
|
|
@@ -21,17 +20,8 @@ export const mkdirp = async p => {
|
|
|
21
20
|
throw error(`${libName} expects a path string as argument, got: ${typeof p}`, 'E_ARG_TYPE')
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
p = path.resolve(p)
|
|
25
|
-
|
|
26
23
|
try {
|
|
27
|
-
|
|
28
|
-
let exists = await fs.exists(dir)
|
|
29
|
-
|
|
30
|
-
if (!exists) {
|
|
31
|
-
await mkdirp(dir)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
await fs.mkdir(p)
|
|
24
|
+
await fs.mkdir(p, { ...opts, recursive: true })
|
|
35
25
|
return true
|
|
36
26
|
} catch (e) {
|
|
37
27
|
const err = /** @type {Error & { code?: string}} */ (e)
|
package/src/rmrf.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import path from 'path'
|
|
1
|
+
import path from 'node:path'
|
|
2
2
|
|
|
3
3
|
import is from '@magic/types'
|
|
4
4
|
import error from '@magic/error'
|
|
@@ -36,23 +36,10 @@ export const rmrf = async (dir, opts = {}) => {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (stat.isFile()) {
|
|
42
|
-
if (!opts.dryRun) {
|
|
43
|
-
await fs.unlink(dir)
|
|
44
|
-
}
|
|
45
|
-
return true
|
|
46
|
-
} else if (stat.isDirectory()) {
|
|
47
|
-
const files = await fs.readdir(dir)
|
|
48
|
-
|
|
49
|
-
await Promise.all(files.map(async file => await rmrf(path.join(dir, file))))
|
|
50
|
-
|
|
51
|
-
if (!opts.dryRun) {
|
|
52
|
-
await fs.rmdir(dir)
|
|
53
|
-
}
|
|
54
|
-
return true
|
|
39
|
+
if (!opts.dryRun) {
|
|
40
|
+
await fs.rm(dir, { recursive: true, force: true })
|
|
55
41
|
}
|
|
42
|
+
return true
|
|
56
43
|
} catch (e) {
|
|
57
44
|
const err = /** @type {Error & { code?: string}} */ (e)
|
|
58
45
|
|
package/types/fs.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const fs: {
|
|
2
|
-
readonly exists:
|
|
2
|
+
readonly exists: (f: import('node:fs').PathLike) => Promise<boolean>
|
|
3
3
|
readonly readdir: typeof fso.promises.readdir
|
|
4
4
|
readonly readDir: typeof fso.promises.readdir
|
|
5
5
|
readonly readFile: typeof fso.promises.readFile
|
|
@@ -8,6 +8,7 @@ export const fs: {
|
|
|
8
8
|
readonly rmDir: typeof fso.promises.rmdir
|
|
9
9
|
readonly watch: typeof fso.watch
|
|
10
10
|
readonly access: typeof fso.promises.access
|
|
11
|
+
readonly constants: typeof fso.constants
|
|
11
12
|
readonly copyFile: typeof fso.promises.copyFile
|
|
12
13
|
readonly open: typeof fso.promises.open
|
|
13
14
|
readonly rename: typeof fso.promises.rename
|
|
@@ -35,7 +36,6 @@ export const fs: {
|
|
|
35
36
|
readonly opendir: typeof fso.promises.opendir
|
|
36
37
|
readonly cp: typeof fso.promises.cp
|
|
37
38
|
readonly glob: typeof fso.promises.glob
|
|
38
|
-
readonly constants: typeof fso.constants
|
|
39
39
|
readonly renameSync: typeof fso.renameSync
|
|
40
40
|
readonly truncateSync: typeof fso.truncateSync
|
|
41
41
|
readonly ftruncate: typeof fso.ftruncate
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export const fs: {
|
|
2
|
-
readonly mkdirp: (
|
|
2
|
+
readonly mkdirp: (
|
|
3
|
+
p: import('node:fs').PathLike,
|
|
4
|
+
opts?: import('node:fs').MakeDirectoryOptions,
|
|
5
|
+
) => Promise<boolean>
|
|
3
6
|
readonly rmrf: (
|
|
4
7
|
dir: string,
|
|
5
8
|
opts?: {
|
|
@@ -84,6 +87,7 @@ export const fs: {
|
|
|
84
87
|
readonly rmDir: typeof import('node:fs/promises').rmdir
|
|
85
88
|
readonly watch: typeof import('node:fs').watch
|
|
86
89
|
readonly access: typeof import('node:fs/promises').access
|
|
90
|
+
readonly constants: typeof import('node:fs').constants
|
|
87
91
|
readonly copyFile: typeof import('node:fs/promises').copyFile
|
|
88
92
|
readonly open: typeof import('node:fs/promises').open
|
|
89
93
|
readonly rename: typeof import('node:fs/promises').rename
|
|
@@ -111,7 +115,6 @@ export const fs: {
|
|
|
111
115
|
readonly opendir: typeof import('node:fs/promises').opendir
|
|
112
116
|
readonly cp: typeof import('node:fs/promises').cp
|
|
113
117
|
readonly glob: typeof import('node:fs/promises').glob
|
|
114
|
-
readonly constants: typeof import('node:fs').constants
|
|
115
118
|
readonly renameSync: typeof import('node:fs').renameSync
|
|
116
119
|
readonly truncateSync: typeof import('node:fs').truncateSync
|
|
117
120
|
readonly ftruncate: typeof import('node:fs').ftruncate
|
package/types/mkdirp.d.ts
CHANGED