@gesslar/toolkit 0.0.10 → 0.0.13
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/index.js +1 -1
- package/src/lib/Cache.js +1 -1
- package/src/lib/DirectoryObject.js +73 -6
- package/src/lib/FS.js +187 -0
- package/src/lib/FileObject.js +171 -7
- package/src/types/DirectoryObject.d.ts +18 -2
- package/src/types/FS.d.ts +31 -0
- package/src/types/FileObject.d.ts +41 -19
- package/src/types/index.d.ts +2 -2
- package/src/lib/File.js +0 -343
- package/src/types/File.d.ts +0 -77
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Core file system abstractions
|
|
2
2
|
export {default as FileObject} from "./lib/FileObject.js"
|
|
3
3
|
export {default as DirectoryObject} from "./lib/DirectoryObject.js"
|
|
4
|
-
export {default as
|
|
4
|
+
export {default as FS} from "./lib/FS.js"
|
|
5
5
|
|
|
6
6
|
// Utility classes
|
|
7
7
|
export {default as Cache} from "./lib/Cache.js"
|
package/src/lib/Cache.js
CHANGED
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* resolution and existence checks.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import fs from "node:fs/promises"
|
|
7
8
|
import path from "node:path"
|
|
8
9
|
import util from "node:util"
|
|
9
10
|
|
|
10
|
-
import
|
|
11
|
+
import FS from "./FS.js"
|
|
12
|
+
import FileObject from "./FileObject.js"
|
|
13
|
+
import Sass from "./Sass.js"
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* DirectoryObject encapsulates metadata and operations for a directory,
|
|
@@ -23,7 +26,7 @@ import File from "./File.js"
|
|
|
23
26
|
* @property {boolean} isDirectory - Always true
|
|
24
27
|
* @property {Promise<boolean>} exists - Whether the directory exists (async)
|
|
25
28
|
*/
|
|
26
|
-
export default class DirectoryObject {
|
|
29
|
+
export default class DirectoryObject extends FS {
|
|
27
30
|
/**
|
|
28
31
|
* @type {object}
|
|
29
32
|
* @private
|
|
@@ -54,10 +57,12 @@ export default class DirectoryObject {
|
|
|
54
57
|
* @param {string} directory - The directory path
|
|
55
58
|
*/
|
|
56
59
|
constructor(directory) {
|
|
57
|
-
|
|
60
|
+
super(directory)
|
|
61
|
+
|
|
62
|
+
const fixedDir = FS.fixSlashes(directory ?? ".")
|
|
58
63
|
const absolutePath = path.resolve(fixedDir)
|
|
59
|
-
const fileUri =
|
|
60
|
-
const filePath =
|
|
64
|
+
const fileUri = FS.pathToUri(absolutePath)
|
|
65
|
+
const filePath = FS.uriToPath(fileUri)
|
|
61
66
|
const baseName = path.basename(absolutePath) || "."
|
|
62
67
|
|
|
63
68
|
this.#meta.supplied = fixedDir
|
|
@@ -112,7 +117,7 @@ export default class DirectoryObject {
|
|
|
112
117
|
* @returns {Promise<boolean>} - A Promise that resolves to true or false
|
|
113
118
|
*/
|
|
114
119
|
get exists() {
|
|
115
|
-
return
|
|
120
|
+
return this.#directoryExists()
|
|
116
121
|
}
|
|
117
122
|
|
|
118
123
|
/**
|
|
@@ -186,4 +191,66 @@ export default class DirectoryObject {
|
|
|
186
191
|
get isDirectory() {
|
|
187
192
|
return this.#meta.isDirectory
|
|
188
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if a directory exists
|
|
197
|
+
*
|
|
198
|
+
* @returns {Promise<boolean>} Whether the directory exists
|
|
199
|
+
*/
|
|
200
|
+
async #directoryExists() {
|
|
201
|
+
try {
|
|
202
|
+
(await fs.opendir(this.path)).close()
|
|
203
|
+
|
|
204
|
+
return true
|
|
205
|
+
} catch(_) {
|
|
206
|
+
return false
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Lists the contents of a directory.
|
|
212
|
+
*
|
|
213
|
+
* @param {DirectoryObject} directory - The directory to list.
|
|
214
|
+
* @returns {Promise<{files: Array<FileObject>, directories: Array<DirectoryObject>}>} The files and directories in the directory.
|
|
215
|
+
*/
|
|
216
|
+
async read(directory) {
|
|
217
|
+
const found = await fs.readdir(directory.uri, {withFileTypes: true})
|
|
218
|
+
const results = await Promise.all(
|
|
219
|
+
found.map(async dirent => {
|
|
220
|
+
const fullPath = path.join(directory.uri, dirent.name)
|
|
221
|
+
const stat = await fs.stat(fullPath)
|
|
222
|
+
|
|
223
|
+
return {dirent, stat, fullPath}
|
|
224
|
+
}),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
const files = results
|
|
228
|
+
.filter(({stat}) => stat.isFile())
|
|
229
|
+
.map(({fullPath}) => new FileObject(fullPath))
|
|
230
|
+
|
|
231
|
+
const directories = results
|
|
232
|
+
.filter(({stat}) => stat.isDirectory())
|
|
233
|
+
.map(({fullPath}) => new DirectoryObject(fullPath))
|
|
234
|
+
|
|
235
|
+
return {files, directories}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Ensures a directory exists, creating it if necessary
|
|
240
|
+
*
|
|
241
|
+
* @async
|
|
242
|
+
* @param {object} [options] - Any options to pass to mkdir
|
|
243
|
+
* @returns {Promise<void>}
|
|
244
|
+
* @throws {Sass} If directory creation fails
|
|
245
|
+
*/
|
|
246
|
+
async assureExists(options = {}) {
|
|
247
|
+
if(await this.exists)
|
|
248
|
+
return
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
await fs.mkdir(this.path, options)
|
|
252
|
+
} catch(e) {
|
|
253
|
+
throw Sass.new(`Unable to create directory '${this.path}': ${e.message}`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
189
256
|
}
|
package/src/lib/FS.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import {globby} from "globby"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import url from "node:url"
|
|
4
|
+
|
|
5
|
+
import Data from "./Data.js"
|
|
6
|
+
import DirectoryObject from "./DirectoryObject.js"
|
|
7
|
+
import FileObject from "./FileObject.js"
|
|
8
|
+
import Sass from "./Sass.js"
|
|
9
|
+
import Valid from "./Valid.js"
|
|
10
|
+
|
|
11
|
+
export default class FS {
|
|
12
|
+
/**
|
|
13
|
+
* Fix slashes in a path
|
|
14
|
+
*
|
|
15
|
+
* @param {string} pathName - The path to fix
|
|
16
|
+
* @returns {string} The fixed path
|
|
17
|
+
*/
|
|
18
|
+
static fixSlashes(pathName) {
|
|
19
|
+
return pathName.replace(/\\/g, "/")
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Convert a path to a URI
|
|
24
|
+
*
|
|
25
|
+
* @param {string} pathName - The path to convert
|
|
26
|
+
* @returns {string} The URI
|
|
27
|
+
*/
|
|
28
|
+
static pathToUri(pathName) {
|
|
29
|
+
try {
|
|
30
|
+
return url.pathToFileURL(pathName).href
|
|
31
|
+
} catch(e) {
|
|
32
|
+
void e // stfu linter
|
|
33
|
+
|
|
34
|
+
return pathName
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Convert a URI to a path
|
|
40
|
+
*
|
|
41
|
+
* @param {string} pathName - The URI to convert
|
|
42
|
+
* @returns {string} The path
|
|
43
|
+
*/
|
|
44
|
+
static uriToPath(pathName) {
|
|
45
|
+
try {
|
|
46
|
+
return url.fileURLToPath(pathName)
|
|
47
|
+
} catch(_) {
|
|
48
|
+
return pathName
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Retrieve all files matching a specific glob pattern.
|
|
54
|
+
*
|
|
55
|
+
* @param {string|Array<string>} glob - The glob pattern(s) to search.
|
|
56
|
+
* @returns {Promise<Array<FileObject>>} A promise that resolves to an array of file objects
|
|
57
|
+
* @throws {Sass} If the input is not a string or array of strings.
|
|
58
|
+
* @throws {Sass} If the glob pattern array is empty or for other search failures.
|
|
59
|
+
*/
|
|
60
|
+
static async getFiles(glob) {
|
|
61
|
+
Valid.assert(
|
|
62
|
+
(
|
|
63
|
+
(typeof glob === "string" && glob.length > 0) ||
|
|
64
|
+
(
|
|
65
|
+
Array.isArray(glob) && Data.uniformStringArray(glob) &&
|
|
66
|
+
glob.length > 0
|
|
67
|
+
)
|
|
68
|
+
),
|
|
69
|
+
"glob must be a non-empty string or array of strings.",
|
|
70
|
+
1
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
const globbyArray = (
|
|
74
|
+
typeof glob === "string"
|
|
75
|
+
? glob
|
|
76
|
+
.split("|")
|
|
77
|
+
.map(g => g.trim())
|
|
78
|
+
.filter(Boolean)
|
|
79
|
+
: glob
|
|
80
|
+
).map(g => FS.fixSlashes(g))
|
|
81
|
+
|
|
82
|
+
if(
|
|
83
|
+
Array.isArray(globbyArray) &&
|
|
84
|
+
Data.uniformStringArray(globbyArray) &&
|
|
85
|
+
!globbyArray.length
|
|
86
|
+
)
|
|
87
|
+
throw Sass.new(
|
|
88
|
+
`Invalid glob pattern: Array cannot be empty. Got ${JSON.stringify(glob)}`,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// Use Globby to fetch matching files
|
|
92
|
+
const filesArray = await globby(globbyArray)
|
|
93
|
+
const files = filesArray.map(file => new FileObject(file))
|
|
94
|
+
|
|
95
|
+
// Flatten the result and remove duplicates
|
|
96
|
+
return files
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Computes the relative path from one file or directory to another.
|
|
101
|
+
*
|
|
102
|
+
* If the target is outside the source (i.e., the relative path starts with ".."),
|
|
103
|
+
* returns the absolute path to the target instead.
|
|
104
|
+
*
|
|
105
|
+
* @param {FileObject|DirectoryObject} from - The source file or directory object
|
|
106
|
+
* @param {FileObject|DirectoryObject} to - The target file or directory object
|
|
107
|
+
* @returns {string} The relative path from `from` to `to`, or the absolute path if not reachable
|
|
108
|
+
*/
|
|
109
|
+
static relativeOrAbsolutePath(from, to) {
|
|
110
|
+
const relative = path.relative(from.path, to.path)
|
|
111
|
+
|
|
112
|
+
return relative.startsWith("..")
|
|
113
|
+
? to.path
|
|
114
|
+
: relative
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Merge two paths by finding overlapping segments and combining them efficiently
|
|
119
|
+
*
|
|
120
|
+
* @param {string} path1 - The first path
|
|
121
|
+
* @param {string} path2 - The second path to merge with the first
|
|
122
|
+
* @param {string} [sep] - The path separator to use (defaults to system separator)
|
|
123
|
+
* @returns {string} The merged path
|
|
124
|
+
*/
|
|
125
|
+
static mergeOverlappingPaths(path1, path2, sep=path.sep) {
|
|
126
|
+
const from = path1.split(sep).filter(Boolean)
|
|
127
|
+
const to = path2.split(sep).filter(Boolean)
|
|
128
|
+
|
|
129
|
+
// If they're the same, just return path1
|
|
130
|
+
if(to.length === from.length && from.every((f, i) => to[i] === f)) {
|
|
131
|
+
return path1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const overlapIndex = from.findLastIndex((curr, index) => {
|
|
135
|
+
const left = from.at(index)
|
|
136
|
+
const right = to.at(0)
|
|
137
|
+
|
|
138
|
+
return left === right
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
// If overlap is found, slice and join
|
|
142
|
+
if(overlapIndex !== -1) {
|
|
143
|
+
const prefix = from.slice(0, overlapIndex)
|
|
144
|
+
|
|
145
|
+
return path.join(...prefix, ...to)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// If no overlap, just join the paths
|
|
149
|
+
return path.join(path1, path2)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Resolve a path relative to another path using various strategies
|
|
154
|
+
* Handles absolute paths, relative navigation, and overlap-based merging
|
|
155
|
+
*
|
|
156
|
+
* @param {string} fromPath - The base path to resolve from
|
|
157
|
+
* @param {string} toPath - The target path to resolve
|
|
158
|
+
* @returns {string} The resolved path
|
|
159
|
+
*/
|
|
160
|
+
static resolvePath(fromPath, toPath) {
|
|
161
|
+
// Normalize inputs
|
|
162
|
+
const from = fromPath.trim()
|
|
163
|
+
const to = toPath.trim()
|
|
164
|
+
|
|
165
|
+
// Handle empty cases
|
|
166
|
+
if(!from && !to)
|
|
167
|
+
return ""
|
|
168
|
+
|
|
169
|
+
if(!from)
|
|
170
|
+
return to
|
|
171
|
+
|
|
172
|
+
if(!to)
|
|
173
|
+
return from
|
|
174
|
+
|
|
175
|
+
// Strategy 1: If 'to' is absolute, it's standalone
|
|
176
|
+
if(path.isAbsolute(to))
|
|
177
|
+
return to
|
|
178
|
+
|
|
179
|
+
// Strategy 2: If 'to' contains relative navigation (./ or ../)
|
|
180
|
+
if(to.includes("./") || to.includes("../") || to.startsWith(".") || to.startsWith(".."))
|
|
181
|
+
return path.resolve(from, to)
|
|
182
|
+
|
|
183
|
+
// Strategy 3: Try overlap-based merging, which will default to a basic
|
|
184
|
+
// join if no overlap
|
|
185
|
+
return FS.mergeOverlappingPaths(from, to)
|
|
186
|
+
}
|
|
187
|
+
}
|
package/src/lib/FileObject.js
CHANGED
|
@@ -4,11 +4,16 @@
|
|
|
4
4
|
* resolution and existence checks.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import JSON5 from "json5"
|
|
8
|
+
import fs from "node:fs/promises"
|
|
7
9
|
import path from "node:path"
|
|
8
10
|
import util from "node:util"
|
|
11
|
+
import YAML from "yaml"
|
|
9
12
|
|
|
10
13
|
import DirectoryObject from "./DirectoryObject.js"
|
|
11
|
-
import
|
|
14
|
+
import FS from "./FS.js"
|
|
15
|
+
import Sass from "./Sass.js"
|
|
16
|
+
import Valid from "./Valid.js"
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
19
|
* FileObject encapsulates metadata and operations for a file, including path
|
|
@@ -26,7 +31,7 @@ import File from "./File.js"
|
|
|
26
31
|
* @property {Promise<boolean>} exists - Whether the file exists (async)
|
|
27
32
|
*/
|
|
28
33
|
|
|
29
|
-
export default class FileObject {
|
|
34
|
+
export default class FileObject extends FS {
|
|
30
35
|
/**
|
|
31
36
|
* @type {object}
|
|
32
37
|
* @private
|
|
@@ -59,9 +64,11 @@ export default class FileObject {
|
|
|
59
64
|
* @param {DirectoryObject|string|null} [directory] - The parent directory (object or string)
|
|
60
65
|
*/
|
|
61
66
|
constructor(fileName, directory=null) {
|
|
62
|
-
|
|
67
|
+
super(fileName, directory)
|
|
63
68
|
|
|
64
|
-
const
|
|
69
|
+
const fixedFile = FS.fixSlashes(fileName)
|
|
70
|
+
|
|
71
|
+
const {dir,base,ext} = this.#deconstructFilenameToParts(fixedFile)
|
|
65
72
|
|
|
66
73
|
if(!directory)
|
|
67
74
|
directory = new DirectoryObject(dir)
|
|
@@ -71,7 +78,7 @@ export default class FileObject {
|
|
|
71
78
|
: path.resolve(directory?.path ?? ".", fixedFile)
|
|
72
79
|
|
|
73
80
|
const resolved = final
|
|
74
|
-
const fileUri =
|
|
81
|
+
const fileUri = FS.pathToUri(resolved)
|
|
75
82
|
|
|
76
83
|
this.#meta.supplied = fixedFile
|
|
77
84
|
this.#meta.path = resolved
|
|
@@ -80,7 +87,7 @@ export default class FileObject {
|
|
|
80
87
|
this.#meta.extension = ext
|
|
81
88
|
this.#meta.module = path.basename(this.supplied, this.extension)
|
|
82
89
|
|
|
83
|
-
const {dir: newDir} =
|
|
90
|
+
const {dir: newDir} = this.#deconstructFilenameToParts(this.path)
|
|
84
91
|
|
|
85
92
|
this.#meta.directory = new DirectoryObject(newDir)
|
|
86
93
|
|
|
@@ -127,7 +134,7 @@ export default class FileObject {
|
|
|
127
134
|
* @returns {Promise<boolean>} - A Promise that resolves to true or false
|
|
128
135
|
*/
|
|
129
136
|
get exists() {
|
|
130
|
-
return
|
|
137
|
+
return this.#fileExists()
|
|
131
138
|
}
|
|
132
139
|
|
|
133
140
|
/**
|
|
@@ -219,4 +226,161 @@ export default class FileObject {
|
|
|
219
226
|
get directory() {
|
|
220
227
|
return this.#meta.directory
|
|
221
228
|
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Check if a file can be read. Returns true if the file can be read, false
|
|
232
|
+
*
|
|
233
|
+
* @returns {Promise<boolean>} Whether the file can be read
|
|
234
|
+
*/
|
|
235
|
+
async canRead() {
|
|
236
|
+
try {
|
|
237
|
+
await fs.access(this.path, fs.constants.R_OK)
|
|
238
|
+
|
|
239
|
+
return true
|
|
240
|
+
} catch(_) {
|
|
241
|
+
return false
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Check if a file can be written. Returns true if the file can be written,
|
|
247
|
+
*
|
|
248
|
+
* @returns {Promise<boolean>} Whether the file can be written
|
|
249
|
+
*/
|
|
250
|
+
async canWrite() {
|
|
251
|
+
try {
|
|
252
|
+
await fs.access(this.path, fs.constants.W_OK)
|
|
253
|
+
|
|
254
|
+
return true
|
|
255
|
+
} catch(_) {
|
|
256
|
+
return false
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if a file exists
|
|
262
|
+
*
|
|
263
|
+
* @returns {Promise<boolean>} Whether the file exists
|
|
264
|
+
*/
|
|
265
|
+
async #fileExists() {
|
|
266
|
+
try {
|
|
267
|
+
await fs.access(this.path, fs.constants.F_OK)
|
|
268
|
+
|
|
269
|
+
return true
|
|
270
|
+
} catch(_) {
|
|
271
|
+
return false
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Determines the size of a file.
|
|
277
|
+
*
|
|
278
|
+
* @returns {Promise<number?>} - The size of the file or null, if it doesn't exist.
|
|
279
|
+
*/
|
|
280
|
+
async size() {
|
|
281
|
+
try {
|
|
282
|
+
const stat = await fs.stat(this.path)
|
|
283
|
+
|
|
284
|
+
return stat.size
|
|
285
|
+
} catch(_) {
|
|
286
|
+
return null
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Gets the last modification time of a file.
|
|
292
|
+
* Used by the caching system to determine if cached data is still valid.
|
|
293
|
+
*
|
|
294
|
+
* @returns {Promise<Date?>} The last modification time, or null if file doesn't exist
|
|
295
|
+
*/
|
|
296
|
+
async modified() {
|
|
297
|
+
try {
|
|
298
|
+
const stat = await fs.stat(this.path)
|
|
299
|
+
|
|
300
|
+
return stat.mtime
|
|
301
|
+
} catch(_) {
|
|
302
|
+
return null
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* @typedef {object} FileParts
|
|
308
|
+
* @property {string} base - The file name with extension
|
|
309
|
+
* @property {string} dir - The directory path
|
|
310
|
+
* @property {string} ext - The file extension (including dot)
|
|
311
|
+
*/
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Deconstruct a filename into parts
|
|
315
|
+
*
|
|
316
|
+
* @param {string} fileName - The filename to deconstruct
|
|
317
|
+
* @returns {FileParts} The filename parts
|
|
318
|
+
*/
|
|
319
|
+
#deconstructFilenameToParts(fileName) {
|
|
320
|
+
Valid.assert(typeof fileName === "string" && fileName.length > 0,
|
|
321
|
+
"file must be a non-zero length string", 1)
|
|
322
|
+
|
|
323
|
+
return path.parse(fileName)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Reads the content of a file asynchronously.
|
|
328
|
+
*
|
|
329
|
+
* @param {string} [encoding] - The encoding to read the file as.
|
|
330
|
+
* @returns {Promise<string>} The file contents
|
|
331
|
+
*/
|
|
332
|
+
async read(encoding="utf8") {
|
|
333
|
+
const filePath = this.path
|
|
334
|
+
|
|
335
|
+
if(!(await this.exists))
|
|
336
|
+
throw Sass.new(`No such file '${filePath}'`)
|
|
337
|
+
|
|
338
|
+
if(!filePath)
|
|
339
|
+
throw Sass.new("No absolute path in file map")
|
|
340
|
+
|
|
341
|
+
return await fs.readFile(filePath, encoding)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Writes content to a file synchronously.
|
|
346
|
+
*
|
|
347
|
+
* @param {string} content - The content to write
|
|
348
|
+
* @param {string} encoding - The encoding in which to write.
|
|
349
|
+
* @returns {Promise<void>}
|
|
350
|
+
*/
|
|
351
|
+
async write(content, encoding="utf8") {
|
|
352
|
+
if(!this.path)
|
|
353
|
+
throw Sass.new("No absolute path in file")
|
|
354
|
+
|
|
355
|
+
await fs.writeFile(this.path, content, encoding)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Loads an object from JSON or YAML provided a fileMap
|
|
360
|
+
*
|
|
361
|
+
* @param {string} [type] - The expected type of data to parse.
|
|
362
|
+
* @param {string} [encoding] - The encoding to read the file as.
|
|
363
|
+
* @returns {object} The parsed data object.
|
|
364
|
+
*/
|
|
365
|
+
async loadData(type="any", encoding="utf8") {
|
|
366
|
+
const content = await this.read(encoding)
|
|
367
|
+
const toTry = {
|
|
368
|
+
json5: [JSON5],
|
|
369
|
+
json: [JSON5],
|
|
370
|
+
yaml: [YAML],
|
|
371
|
+
any: [JSON5,YAML]
|
|
372
|
+
}[type.toLowerCase()]
|
|
373
|
+
|
|
374
|
+
for(const [format] of toTry) {
|
|
375
|
+
try {
|
|
376
|
+
const result = format.parse(content)
|
|
377
|
+
|
|
378
|
+
return result
|
|
379
|
+
} catch {
|
|
380
|
+
// nothing to see here
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
throw Sass.new(`Content is neither valid JSON5 nor valid YAML:\n'${this.path}'`)
|
|
385
|
+
}
|
|
222
386
|
}
|
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
// Implementation: ../lib/DirectoryObject.js
|
|
2
2
|
// Type definitions for DirectoryObject
|
|
3
3
|
|
|
4
|
+
import FS from './FS.js'
|
|
5
|
+
import FileObject from './FileObject.js'
|
|
6
|
+
|
|
7
|
+
export interface DirectoryListing {
|
|
8
|
+
/** Array of FileObject instances */
|
|
9
|
+
files: Array<FileObject>
|
|
10
|
+
/** Array of DirectoryObject instances */
|
|
11
|
+
directories: Array<DirectoryObject>
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
15
|
* DirectoryObject encapsulates metadata and operations for a directory,
|
|
6
16
|
* including path resolution and existence checks.
|
|
7
17
|
*/
|
|
8
|
-
export default class DirectoryObject {
|
|
18
|
+
export default class DirectoryObject extends FS {
|
|
9
19
|
/**
|
|
10
20
|
* Create a new DirectoryObject instance.
|
|
11
21
|
* @param directory - The directory path
|
|
@@ -53,4 +63,10 @@ export default class DirectoryObject {
|
|
|
53
63
|
isFile: boolean
|
|
54
64
|
isDirectory: boolean
|
|
55
65
|
}
|
|
56
|
-
|
|
66
|
+
|
|
67
|
+
/** List the contents of this directory */
|
|
68
|
+
read(): Promise<DirectoryListing>
|
|
69
|
+
|
|
70
|
+
/** Ensure this directory exists, creating it if necessary */
|
|
71
|
+
assureExists(options?: any): Promise<void>
|
|
72
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Implementation: ../lib/FS.js
|
|
2
|
+
// Type definitions for FS utilities
|
|
3
|
+
|
|
4
|
+
import FileObject from './FileObject.js'
|
|
5
|
+
import DirectoryObject from './DirectoryObject.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Base filesystem utilities class. FileObject and DirectoryObject extend this class.
|
|
9
|
+
*/
|
|
10
|
+
export default class FS {
|
|
11
|
+
/** Fix slashes in a path */
|
|
12
|
+
static fixSlashes(pathName: string): string
|
|
13
|
+
|
|
14
|
+
/** Convert a path to a URI */
|
|
15
|
+
static pathToUri(pathName: string): string
|
|
16
|
+
|
|
17
|
+
/** Convert a URI to a path */
|
|
18
|
+
static uriToPath(pathName: string): string
|
|
19
|
+
|
|
20
|
+
/** Retrieve files matching glob pattern(s) */
|
|
21
|
+
static getFiles(glob: string | Array<string>): Promise<Array<FileObject>>
|
|
22
|
+
|
|
23
|
+
/** Compute relative path between two file system objects */
|
|
24
|
+
static relativeOrAbsolutePath(from: FileObject | DirectoryObject, to: FileObject | DirectoryObject): string
|
|
25
|
+
|
|
26
|
+
/** Merge two paths by finding overlapping segments and combining them efficiently */
|
|
27
|
+
static mergeOverlappingPaths(path1: string, path2: string, sep?: string): string
|
|
28
|
+
|
|
29
|
+
/** Resolve a path relative to another path using various strategies. Handles absolute paths, relative navigation, and overlap-based merging */
|
|
30
|
+
static resolvePath(fromPath: string, toPath: string): string
|
|
31
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Implementation: ../lib/FileObject.js
|
|
2
2
|
|
|
3
3
|
import DirectoryObject from './DirectoryObject.js'
|
|
4
|
+
import FS from './FS.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* FileObject encapsulates metadata and operations for a file, providing intelligent
|
|
@@ -60,7 +61,7 @@ import DirectoryObject from './DirectoryObject.js'
|
|
|
60
61
|
*
|
|
61
62
|
* for (const fileItem of files) {
|
|
62
63
|
* console.log(`${fileItem.module}${fileItem.extension} -> ${fileItem.path}`)
|
|
63
|
-
*
|
|
64
|
+
*
|
|
64
65
|
* // Type-based processing
|
|
65
66
|
* switch (fileItem.extension) {
|
|
66
67
|
* case '.json':
|
|
@@ -99,7 +100,7 @@ import DirectoryObject from './DirectoryObject.js'
|
|
|
99
100
|
* inherits the directory's resolved path, ensuring consistency in hierarchical
|
|
100
101
|
* file operations.
|
|
101
102
|
*/
|
|
102
|
-
export default class FileObject {
|
|
103
|
+
export default class FileObject extends FS {
|
|
103
104
|
/**
|
|
104
105
|
* Create a new FileObject instance with intelligent path resolution.
|
|
105
106
|
*
|
|
@@ -142,8 +143,8 @@ export default class FileObject {
|
|
|
142
143
|
* // Complex directory structures and nested files
|
|
143
144
|
* const projectRoot = new DirectoryObject('./my-project')
|
|
144
145
|
* const srcDir = new DirectoryObject('src', projectRoot)
|
|
145
|
-
*
|
|
146
|
-
* // Create files within nested directory structure
|
|
146
|
+
*
|
|
147
|
+
* // Create files within nested directory structure
|
|
147
148
|
* const mainApp = new FileObject('App.tsx', srcDir)
|
|
148
149
|
* const stylesheet = new FileObject('styles/main.css', srcDir)
|
|
149
150
|
* const testFile = new FileObject('__tests__/App.test.tsx', srcDir)
|
|
@@ -151,7 +152,7 @@ export default class FileObject {
|
|
|
151
152
|
* console.log('Main app:', mainApp.path) // /absolute/path/my-project/src/App.tsx
|
|
152
153
|
* console.log('Stylesheet:', stylesheet.path) // /absolute/path/my-project/src/styles/main.css
|
|
153
154
|
* console.log('Test file:', testFile.path) // /absolute/path/my-project/src/__tests__/App.test.tsx
|
|
154
|
-
*
|
|
155
|
+
*
|
|
155
156
|
* // All files share the same parent directory reference
|
|
156
157
|
* console.log('Same parent?', mainApp.directory === srcDir) // true
|
|
157
158
|
* ```
|
|
@@ -160,7 +161,7 @@ export default class FileObject {
|
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
163
|
* The original user-supplied path string used during construction.
|
|
163
|
-
*
|
|
164
|
+
*
|
|
164
165
|
* Preserves the exact path string passed to the constructor, including
|
|
165
166
|
* any relative path indicators (./, ../) or path separators. Useful
|
|
166
167
|
* for debugging, logging, or when you need to recreate the original
|
|
@@ -170,7 +171,7 @@ export default class FileObject {
|
|
|
170
171
|
* ```typescript
|
|
171
172
|
* const file1 = new FileObject('./config.json')
|
|
172
173
|
* const file2 = new FileObject('../package.json')
|
|
173
|
-
*
|
|
174
|
+
*
|
|
174
175
|
* console.log(file1.supplied) // './config.json'
|
|
175
176
|
* console.log(file2.supplied) // '../package.json'
|
|
176
177
|
* console.log(file1.path) // '/absolute/path/to/config.json'
|
|
@@ -181,7 +182,7 @@ export default class FileObject {
|
|
|
181
182
|
|
|
182
183
|
/**
|
|
183
184
|
* The fully resolved absolute file path with normalized separators.
|
|
184
|
-
*
|
|
185
|
+
*
|
|
185
186
|
* Automatically resolved during construction using Node.js path utilities.
|
|
186
187
|
* Always uses forward slashes on Unix systems and backslashes on Windows.
|
|
187
188
|
* This is the canonical path that should be used for all file operations.
|
|
@@ -189,9 +190,9 @@ export default class FileObject {
|
|
|
189
190
|
* @example
|
|
190
191
|
* ```typescript
|
|
191
192
|
* // Different inputs, same resolved path
|
|
192
|
-
* const file1 = new FileObject('./src/../config.json')
|
|
193
|
+
* const file1 = new FileObject('./src/../config.json')
|
|
193
194
|
* const file2 = new FileObject('config.json')
|
|
194
|
-
*
|
|
195
|
+
*
|
|
195
196
|
* console.log(file1.path) // '/absolute/path/config.json'
|
|
196
197
|
* console.log(file2.path) // '/absolute/path/config.json'
|
|
197
198
|
* console.log(file1.path === file2.path) // true
|
|
@@ -201,7 +202,7 @@ export default class FileObject {
|
|
|
201
202
|
|
|
202
203
|
/**
|
|
203
204
|
* The file URI representation following RFC 3986 standard.
|
|
204
|
-
*
|
|
205
|
+
*
|
|
205
206
|
* Converts the absolute file path to a proper file:// URI scheme,
|
|
206
207
|
* handling URL encoding for special characters and proper formatting
|
|
207
208
|
* for cross-platform file URI access.
|
|
@@ -209,9 +210,9 @@ export default class FileObject {
|
|
|
209
210
|
* @example
|
|
210
211
|
* ```typescript
|
|
211
212
|
* const file = new FileObject('./my project/config file.json')
|
|
212
|
-
* console.log(file.uri)
|
|
213
|
+
* console.log(file.uri)
|
|
213
214
|
* // 'file:///absolute/path/my%20project/config%20file.json'
|
|
214
|
-
*
|
|
215
|
+
*
|
|
215
216
|
* // Can be used with URL constructor or file:// handlers
|
|
216
217
|
* const url = new URL(file.uri)
|
|
217
218
|
* console.log(url.pathname) // '/absolute/path/my project/config file.json'
|
|
@@ -221,7 +222,7 @@ export default class FileObject {
|
|
|
221
222
|
|
|
222
223
|
/**
|
|
223
224
|
* The complete filename including extension.
|
|
224
|
-
*
|
|
225
|
+
*
|
|
225
226
|
* Extracted from the resolved path using Node.js path utilities.
|
|
226
227
|
* Includes the file extension but excludes any directory components.
|
|
227
228
|
*
|
|
@@ -229,7 +230,7 @@ export default class FileObject {
|
|
|
229
230
|
* ```typescript
|
|
230
231
|
* const jsFile = new FileObject('./src/components/Button.tsx')
|
|
231
232
|
* const configFile = new FileObject('../.env.production')
|
|
232
|
-
*
|
|
233
|
+
*
|
|
233
234
|
* console.log(jsFile.name) // 'Button.tsx'
|
|
234
235
|
* console.log(configFile.name) // '.env.production'
|
|
235
236
|
* ```
|
|
@@ -238,7 +239,7 @@ export default class FileObject {
|
|
|
238
239
|
|
|
239
240
|
/**
|
|
240
241
|
* The filename without its extension, suitable for module identification.
|
|
241
|
-
*
|
|
242
|
+
*
|
|
242
243
|
* Useful for generating module names, import statements, or when you need
|
|
243
244
|
* the base name without file type information. Handles complex extensions
|
|
244
245
|
* and dotfiles appropriately.
|
|
@@ -247,7 +248,7 @@ export default class FileObject {
|
|
|
247
248
|
|
|
248
249
|
/**
|
|
249
250
|
* The file extension including the leading dot.
|
|
250
|
-
*
|
|
251
|
+
*
|
|
251
252
|
* Extracted using Node.js path utilities, always includes the dot prefix.
|
|
252
253
|
* Returns an empty string for files without extensions. Handles multiple
|
|
253
254
|
* extensions by returning only the last one.
|
|
@@ -262,7 +263,7 @@ export default class FileObject {
|
|
|
262
263
|
|
|
263
264
|
/**
|
|
264
265
|
* The parent DirectoryObject containing this file.
|
|
265
|
-
*
|
|
266
|
+
*
|
|
266
267
|
* Automatically created during FileObject construction based on the resolved
|
|
267
268
|
* file path. Provides access to parent directory operations and maintains
|
|
268
269
|
* the hierarchical relationship between files and directories.
|
|
@@ -271,7 +272,7 @@ export default class FileObject {
|
|
|
271
272
|
|
|
272
273
|
/**
|
|
273
274
|
* Promise that resolves to whether the file exists on the filesystem.
|
|
274
|
-
*
|
|
275
|
+
*
|
|
275
276
|
* Performs an asynchronous filesystem check to determine file existence.
|
|
276
277
|
* The Promise will resolve to true if the file exists and is accessible,
|
|
277
278
|
* false otherwise. Always await this property before using the result.
|
|
@@ -290,4 +291,25 @@ export default class FileObject {
|
|
|
290
291
|
isDirectory: boolean
|
|
291
292
|
directory: string | null
|
|
292
293
|
}
|
|
294
|
+
|
|
295
|
+
/** Check if a file can be read */
|
|
296
|
+
canRead(): Promise<boolean>
|
|
297
|
+
|
|
298
|
+
/** Check if a file can be written */
|
|
299
|
+
canWrite(): Promise<boolean>
|
|
300
|
+
|
|
301
|
+
/** Get the size of a file */
|
|
302
|
+
size(): Promise<number | null>
|
|
303
|
+
|
|
304
|
+
/** Get the last modification time of a file */
|
|
305
|
+
modified(): Promise<Date | null>
|
|
306
|
+
|
|
307
|
+
/** Read the content of a file */
|
|
308
|
+
read(encoding?: string): Promise<string>
|
|
309
|
+
|
|
310
|
+
/** Write content to a file */
|
|
311
|
+
write(content: string, encoding?: string): Promise<void>
|
|
312
|
+
|
|
313
|
+
/** Load an object from JSON5 or YAML file with type specification */
|
|
314
|
+
loadData(type?: 'json' | 'json5' | 'yaml' | 'any', encoding?: string): Promise<any>
|
|
293
315
|
}
|
package/src/types/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// Core file system abstractions
|
|
3
3
|
export { default as FileObject } from './FileObject.js'
|
|
4
4
|
export { default as DirectoryObject } from './DirectoryObject.js'
|
|
5
|
-
export { default as
|
|
5
|
+
export { default as FS } from './FS.js'
|
|
6
6
|
|
|
7
7
|
// Utility classes
|
|
8
8
|
export { default as Cache } from './Cache.js'
|
|
@@ -15,4 +15,4 @@ export { default as Util } from './Util.js'
|
|
|
15
15
|
export { default as Valid } from './Valid.js'
|
|
16
16
|
|
|
17
17
|
// Type exports
|
|
18
|
-
export type { FileParts } from './
|
|
18
|
+
export type { FileParts } from './FS.js'
|
package/src/lib/File.js
DELETED
|
@@ -1,343 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file File system utilities for reading, writing, and manipulating files and directories.
|
|
3
|
-
* Provides comprehensive file operations including data file loading (JSON5/YAML),
|
|
4
|
-
* path resolution, and file system navigation with support for both files and directories.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {globby} from "globby"
|
|
8
|
-
import JSON5 from "json5"
|
|
9
|
-
import fs from "node:fs/promises"
|
|
10
|
-
import path from "node:path"
|
|
11
|
-
import url from "node:url"
|
|
12
|
-
import YAML from "yaml"
|
|
13
|
-
|
|
14
|
-
import Data from "./Data.js"
|
|
15
|
-
import DirectoryObject from "./DirectoryObject.js"
|
|
16
|
-
import FileObject from "./FileObject.js"
|
|
17
|
-
import Sass from "./Sass.js"
|
|
18
|
-
import Valid from "./Valid.js"
|
|
19
|
-
|
|
20
|
-
export default class File {
|
|
21
|
-
/**
|
|
22
|
-
* Fix slashes in a path
|
|
23
|
-
*
|
|
24
|
-
* @param {string} pathName - The path to fix
|
|
25
|
-
* @returns {string} The fixed path
|
|
26
|
-
*/
|
|
27
|
-
static fixSlashes(pathName) {
|
|
28
|
-
return pathName.replace(/\\/g, "/")
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Convert a path to a URI
|
|
33
|
-
*
|
|
34
|
-
* @param {string} pathName - The path to convert
|
|
35
|
-
* @returns {string} The URI
|
|
36
|
-
*/
|
|
37
|
-
static pathToUri(pathName) {
|
|
38
|
-
try {
|
|
39
|
-
return url.pathToFileURL(pathName).href
|
|
40
|
-
} catch(e) {
|
|
41
|
-
void e // stfu linter
|
|
42
|
-
|
|
43
|
-
return pathName
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Check if a file can be read. Returns true if the file can be read, false
|
|
49
|
-
*
|
|
50
|
-
* @param {FileObject} file - The file map to check
|
|
51
|
-
* @returns {Promise<boolean>} Whether the file can be read
|
|
52
|
-
*/
|
|
53
|
-
static async canReadFile(file) {
|
|
54
|
-
try {
|
|
55
|
-
await fs.access(file.path, fs.constants.R_OK)
|
|
56
|
-
|
|
57
|
-
return true
|
|
58
|
-
} catch(_) {
|
|
59
|
-
return false
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Check if a file can be written. Returns true if the file can be written,
|
|
65
|
-
*
|
|
66
|
-
* @param {FileObject} file - The file map to check
|
|
67
|
-
* @returns {Promise<boolean>} Whether the file can be written
|
|
68
|
-
*/
|
|
69
|
-
static async canWriteFile(file) {
|
|
70
|
-
try {
|
|
71
|
-
await fs.access(file.path, fs.constants.W_OK)
|
|
72
|
-
|
|
73
|
-
return true
|
|
74
|
-
} catch(_) {
|
|
75
|
-
return false
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Check if a file exists
|
|
81
|
-
*
|
|
82
|
-
* @param {FileObject} file - The file map to check
|
|
83
|
-
* @returns {Promise<boolean>} Whether the file exists
|
|
84
|
-
*/
|
|
85
|
-
static async fileExists(file) {
|
|
86
|
-
try {
|
|
87
|
-
await fs.access(file.path, fs.constants.F_OK)
|
|
88
|
-
|
|
89
|
-
return true
|
|
90
|
-
} catch(_) {
|
|
91
|
-
return false
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* Determines the size of a file.
|
|
97
|
-
*
|
|
98
|
-
* @param {FileObject} file - The file object to test
|
|
99
|
-
* @returns {Promise<number?>} - The size of the file or null, if it doesn't exist.
|
|
100
|
-
*/
|
|
101
|
-
static async fileSize(file) {
|
|
102
|
-
try {
|
|
103
|
-
const stat = await fs.stat(file.path)
|
|
104
|
-
|
|
105
|
-
return stat.size
|
|
106
|
-
} catch(_) {
|
|
107
|
-
return null
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Gets the last modification time of a file.
|
|
113
|
-
* Used by the caching system to determine if cached data is still valid.
|
|
114
|
-
*
|
|
115
|
-
* @param {FileObject} file - The file object to check
|
|
116
|
-
* @returns {Promise<Date|null>} The last modification time, or null if file doesn't exist
|
|
117
|
-
*/
|
|
118
|
-
static async fileModified(file) {
|
|
119
|
-
try {
|
|
120
|
-
const stat = await fs.stat(file.path)
|
|
121
|
-
|
|
122
|
-
return stat.mtime
|
|
123
|
-
} catch(_) {
|
|
124
|
-
return null
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Check if a directory exists
|
|
130
|
-
*
|
|
131
|
-
* @param {DirectoryObject} dirObject - The directory map to check
|
|
132
|
-
* @returns {Promise<boolean>} Whether the directory exists
|
|
133
|
-
*/
|
|
134
|
-
static async directoryExists(dirObject) {
|
|
135
|
-
try {
|
|
136
|
-
(await fs.opendir(dirObject.path)).close()
|
|
137
|
-
|
|
138
|
-
return true
|
|
139
|
-
} catch(_) {
|
|
140
|
-
return false
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Convert a URI to a path
|
|
146
|
-
*
|
|
147
|
-
* @param {string} pathName - The URI to convert
|
|
148
|
-
* @returns {string} The path
|
|
149
|
-
*/
|
|
150
|
-
static uriToPath(pathName) {
|
|
151
|
-
try {
|
|
152
|
-
return url.fileURLToPath(pathName)
|
|
153
|
-
} catch(_) {
|
|
154
|
-
return pathName
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* @typedef {object} FileParts
|
|
160
|
-
* @property {string} base - The file name with extension
|
|
161
|
-
* @property {string} dir - The directory path
|
|
162
|
-
* @property {string} ext - The file extension (including dot)
|
|
163
|
-
*/
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Deconstruct a filename into parts
|
|
167
|
-
*
|
|
168
|
-
* @param {string} fileName - The filename to deconstruct
|
|
169
|
-
* @returns {FileParts} The filename parts
|
|
170
|
-
*/
|
|
171
|
-
static deconstructFilenameToParts(fileName) {
|
|
172
|
-
Valid.assert(typeof fileName === "string" && fileName.length > 0,
|
|
173
|
-
"file must be a non-zero length string", 1)
|
|
174
|
-
|
|
175
|
-
return path.parse(fileName)
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Retrieve all files matching a specific glob pattern.
|
|
180
|
-
*
|
|
181
|
-
* @param {string|Array<string>} glob - The glob pattern(s) to search.
|
|
182
|
-
* @returns {Promise<Array<FileObject>>} A promise that resolves to an array of file objects
|
|
183
|
-
* @throws {Sass} If the input is not a string or array of strings.
|
|
184
|
-
* @throws {Sass} If the glob pattern array is empty or for other search failures.
|
|
185
|
-
*/
|
|
186
|
-
static async getFiles(glob) {
|
|
187
|
-
Valid.assert(
|
|
188
|
-
(
|
|
189
|
-
(typeof glob === "string" && glob.length > 0) ||
|
|
190
|
-
(
|
|
191
|
-
Array.isArray(glob) && Data.uniformStringArray(glob) &&
|
|
192
|
-
glob.length > 0
|
|
193
|
-
)
|
|
194
|
-
),
|
|
195
|
-
"glob must be a non-empty string or array of strings.",
|
|
196
|
-
1
|
|
197
|
-
)
|
|
198
|
-
|
|
199
|
-
const globbyArray = (
|
|
200
|
-
typeof glob === "string"
|
|
201
|
-
? glob
|
|
202
|
-
.split("|")
|
|
203
|
-
.map(g => g.trim())
|
|
204
|
-
.filter(Boolean)
|
|
205
|
-
: glob
|
|
206
|
-
).map(g => File.fixSlashes(g))
|
|
207
|
-
|
|
208
|
-
if(
|
|
209
|
-
Array.isArray(globbyArray) &&
|
|
210
|
-
Data.uniformStringArray(globbyArray) &&
|
|
211
|
-
!globbyArray.length
|
|
212
|
-
)
|
|
213
|
-
throw Sass.new(
|
|
214
|
-
`Invalid glob pattern: Array cannot be empty. Got ${JSON.stringify(glob)}`,
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
// Use Globby to fetch matching files
|
|
218
|
-
const filesArray = await globby(globbyArray)
|
|
219
|
-
const files = filesArray.map(file => new FileObject(file))
|
|
220
|
-
|
|
221
|
-
// Flatten the result and remove duplicates
|
|
222
|
-
return files
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Lists the contents of a directory.
|
|
227
|
-
*
|
|
228
|
-
* @param {DirectoryObject} directory - The directory to list.
|
|
229
|
-
* @returns {Promise<{files: Array<FileObject>, directories: Array<DirectoryObject>}>} The files and
|
|
230
|
-
* directories in the directory.
|
|
231
|
-
*/
|
|
232
|
-
static async ls(directory) {
|
|
233
|
-
const found = await fs.readdir(directory.uri, {withFileTypes: true})
|
|
234
|
-
const results = await Promise.all(
|
|
235
|
-
found.map(async dirent => {
|
|
236
|
-
const fullPath = path.join(directory.uri, dirent.name)
|
|
237
|
-
const stat = await fs.stat(fullPath)
|
|
238
|
-
|
|
239
|
-
return {dirent, stat, fullPath}
|
|
240
|
-
}),
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
const files = results
|
|
244
|
-
.filter(({stat}) => stat.isFile())
|
|
245
|
-
.map(({fullPath}) => new FileObject(fullPath))
|
|
246
|
-
|
|
247
|
-
const directories = results
|
|
248
|
-
.filter(({stat}) => stat.isDirectory())
|
|
249
|
-
.map(({fullPath}) => new DirectoryObject(fullPath))
|
|
250
|
-
|
|
251
|
-
return {files, directories}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Reads the content of a file asynchronously.
|
|
256
|
-
*
|
|
257
|
-
* @param {FileObject} fileObject - The file map containing the file path
|
|
258
|
-
* @returns {Promise<string>} The file contents
|
|
259
|
-
*/
|
|
260
|
-
static async readFile(fileObject) {
|
|
261
|
-
const filePath = fileObject.path
|
|
262
|
-
|
|
263
|
-
if(!(await fileObject.exists))
|
|
264
|
-
throw Sass.new(`No such file '${filePath}'`)
|
|
265
|
-
|
|
266
|
-
if(!filePath)
|
|
267
|
-
throw Sass.new("No absolute path in file map")
|
|
268
|
-
|
|
269
|
-
return await fs.readFile(filePath, "utf8")
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Writes content to a file synchronously.
|
|
274
|
-
*
|
|
275
|
-
* @param {FileObject} fileObject - The file map containing the file path
|
|
276
|
-
* @param {string} content - The content to write
|
|
277
|
-
*/
|
|
278
|
-
static async writeFile(fileObject, content) {
|
|
279
|
-
if(!fileObject.path)
|
|
280
|
-
throw Sass.new("No absolute path in file")
|
|
281
|
-
|
|
282
|
-
await fs.writeFile(fileObject.path, content, "utf8")
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Loads an object from JSON or YAML provided a fileMap
|
|
287
|
-
*
|
|
288
|
-
* @param {FileObject} fileObject - The FileObj file to load containing
|
|
289
|
-
* JSON or YAML text.
|
|
290
|
-
* @returns {object} The parsed data object.
|
|
291
|
-
*/
|
|
292
|
-
static async loadDataFile(fileObject) {
|
|
293
|
-
const content = await File.readFile(fileObject)
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
return JSON5.parse(content)
|
|
297
|
-
} catch {
|
|
298
|
-
try {
|
|
299
|
-
return YAML.parse(content)
|
|
300
|
-
} catch {
|
|
301
|
-
throw Sass.new(`Content is neither valid JSON nor valid YAML:\n'${fileObject.path}'`)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
/**
|
|
307
|
-
* Ensures a directory exists, creating it if necessary
|
|
308
|
-
*
|
|
309
|
-
* @async
|
|
310
|
-
* @param {DirectoryObject} dirObject - The path or DirMap of the directory to assure exists
|
|
311
|
-
* @param {object} [options] - Any options to pass to mkdir
|
|
312
|
-
* @returns {Promise<void>}
|
|
313
|
-
* @throws {Sass} If directory creation fails
|
|
314
|
-
*/
|
|
315
|
-
static async assureDirectory(dirObject, options = {}) {
|
|
316
|
-
if(await dirObject.exists)
|
|
317
|
-
return
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
await fs.mkdir(dirObject.path, options)
|
|
321
|
-
} catch(e) {
|
|
322
|
-
throw Sass.new(`Unable to create directory '${dirObject.path}': ${e.message}`)
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Computes the relative path from one file or directory to another.
|
|
328
|
-
*
|
|
329
|
-
* If the target is outside the source (i.e., the relative path starts with ".."),
|
|
330
|
-
* returns the absolute path to the target instead.
|
|
331
|
-
*
|
|
332
|
-
* @param {FileObject|DirectoryObject} from - The source file or directory object
|
|
333
|
-
* @param {FileObject|DirectoryObject} to - The target file or directory object
|
|
334
|
-
* @returns {string} The relative path from `from` to `to`, or the absolute path if not reachable
|
|
335
|
-
*/
|
|
336
|
-
static relativeOrAbsolutePath(from, to) {
|
|
337
|
-
const relative = path.relative(from.path, to.path)
|
|
338
|
-
|
|
339
|
-
return relative.startsWith("..")
|
|
340
|
-
? to.path
|
|
341
|
-
: relative
|
|
342
|
-
}
|
|
343
|
-
}
|
package/src/types/File.d.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// Implementation: ../lib/File.js
|
|
2
|
-
// Type definitions for File utilities
|
|
3
|
-
|
|
4
|
-
import FileObject from './FileObject.js'
|
|
5
|
-
import DirectoryObject from './DirectoryObject.js'
|
|
6
|
-
|
|
7
|
-
export interface FileParts {
|
|
8
|
-
/** The file name with extension */
|
|
9
|
-
base: string
|
|
10
|
-
/** The directory path */
|
|
11
|
-
dir: string
|
|
12
|
-
/** The file extension (including dot) */
|
|
13
|
-
ext: string
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface DirectoryListing {
|
|
17
|
-
/** Array of FileObject instances */
|
|
18
|
-
files: Array<FileObject>
|
|
19
|
-
/** Array of DirectoryObject instances */
|
|
20
|
-
directories: Array<DirectoryObject>
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* File system utilities for reading, writing, and manipulating files and directories.
|
|
25
|
-
*/
|
|
26
|
-
export default class File {
|
|
27
|
-
/** Fix slashes in a path */
|
|
28
|
-
static fixSlashes(pathName: string): string
|
|
29
|
-
|
|
30
|
-
/** Convert a path to a URI */
|
|
31
|
-
static pathToUri(pathName: string): string
|
|
32
|
-
|
|
33
|
-
/** Convert a URI to a path */
|
|
34
|
-
static uriToPath(pathName: string): string
|
|
35
|
-
|
|
36
|
-
/** Check if a file can be read */
|
|
37
|
-
static canReadFile(file: FileObject): Promise<boolean>
|
|
38
|
-
|
|
39
|
-
/** Check if a file can be written */
|
|
40
|
-
static canWriteFile(file: FileObject): Promise<boolean>
|
|
41
|
-
|
|
42
|
-
/** Check if a file exists */
|
|
43
|
-
static fileExists(file: FileObject): Promise<boolean>
|
|
44
|
-
|
|
45
|
-
/** Get the size of a file */
|
|
46
|
-
static fileSize(file: FileObject): Promise<number | null>
|
|
47
|
-
|
|
48
|
-
/** Get the last modification time of a file */
|
|
49
|
-
static fileModified(file: FileObject): Promise<Date | null>
|
|
50
|
-
|
|
51
|
-
/** Check if a directory exists */
|
|
52
|
-
static directoryExists(dirObject: DirectoryObject): Promise<boolean>
|
|
53
|
-
|
|
54
|
-
/** Deconstruct a filename into parts */
|
|
55
|
-
static deconstructFilenameToParts(fileName: string): FileParts
|
|
56
|
-
|
|
57
|
-
/** Retrieve files matching glob pattern(s) */
|
|
58
|
-
static getFiles(glob: string | Array<string>): Promise<Array<FileObject>>
|
|
59
|
-
|
|
60
|
-
/** List the contents of a directory */
|
|
61
|
-
static ls(directory: DirectoryObject): Promise<DirectoryListing>
|
|
62
|
-
|
|
63
|
-
/** Read the content of a file */
|
|
64
|
-
static readFile(fileObject: FileObject): Promise<string>
|
|
65
|
-
|
|
66
|
-
/** Write content to a file */
|
|
67
|
-
static writeFile(fileObject: FileObject, content: string): Promise<void>
|
|
68
|
-
|
|
69
|
-
/** Load an object from JSON or YAML file */
|
|
70
|
-
static loadDataFile(fileObject: FileObject): Promise<any>
|
|
71
|
-
|
|
72
|
-
/** Ensure a directory exists, creating it if necessary */
|
|
73
|
-
static assureDirectory(dirObject: DirectoryObject, options?: any): Promise<boolean>
|
|
74
|
-
|
|
75
|
-
/** Compute relative path between two file system objects */
|
|
76
|
-
static relativeOrAbsolutePath(from: FileObject | DirectoryObject, to: FileObject | DirectoryObject): string
|
|
77
|
-
}
|