@gesslar/toolkit 3.9.0 → 3.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/browser/lib/Data.js +89 -5
- package/src/browser/lib/TypeSpec.js +3 -1
- package/src/lib/CappedDirectoryObject.js +62 -484
- package/src/lib/DirectoryObject.js +86 -170
- package/src/lib/FS.js +221 -70
- package/src/lib/FileObject.js +80 -111
- package/src/lib/TempDirectoryObject.js +92 -141
- package/src/lib/Valid.js +1 -1
- package/src/types/browser/lib/Data.d.ts +46 -2
- package/src/types/browser/lib/Data.d.ts.map +1 -1
- package/src/types/browser/lib/TypeSpec.d.ts.map +1 -1
- package/src/types/lib/CappedDirectoryObject.d.ts +30 -55
- package/src/types/lib/CappedDirectoryObject.d.ts.map +1 -1
- package/src/types/lib/DirectoryObject.d.ts +8 -60
- package/src/types/lib/DirectoryObject.d.ts.map +1 -1
- package/src/types/lib/FS.d.ts +115 -15
- package/src/types/lib/FS.d.ts.map +1 -1
- package/src/types/lib/FileObject.d.ts +6 -10
- package/src/types/lib/FileObject.d.ts.map +1 -1
- package/src/types/lib/TempDirectoryObject.d.ts +15 -62
- package/src/types/lib/TempDirectoryObject.d.ts.map +1 -1
|
@@ -7,12 +7,12 @@
|
|
|
7
7
|
import {glob, mkdir, opendir, readdir, rmdir} from "node:fs/promises"
|
|
8
8
|
import path from "node:path"
|
|
9
9
|
import {URL} from "node:url"
|
|
10
|
-
import util from "node:util"
|
|
11
10
|
|
|
12
|
-
import
|
|
11
|
+
import Data from "../browser/lib/Data.js"
|
|
13
12
|
import FS from "./FS.js"
|
|
14
13
|
import FileObject from "./FileObject.js"
|
|
15
14
|
import Sass from "./Sass.js"
|
|
15
|
+
import Valid from "./Valid.js"
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* DirectoryObject encapsulates metadata and operations for a directory,
|
|
@@ -24,7 +24,6 @@ import Sass from "./Sass.js"
|
|
|
24
24
|
* - Pattern-based content filtering with glob support
|
|
25
25
|
* - Path traversal via walkUp generator
|
|
26
26
|
* - Intelligent path merging for subdirectories and files
|
|
27
|
-
* - Support for temporary directory management
|
|
28
27
|
*
|
|
29
28
|
* @property {string} supplied - The original directory path as supplied to constructor
|
|
30
29
|
* @property {string} path - The absolute resolved directory path
|
|
@@ -34,7 +33,6 @@ import Sass from "./Sass.js"
|
|
|
34
33
|
* @property {string} extension - The directory extension (typically empty string)
|
|
35
34
|
* @property {string} sep - Platform-specific path separator ('/' or '\\')
|
|
36
35
|
* @property {Array<string>} trail - Path segments split by separator
|
|
37
|
-
* @property {boolean} temporary - Whether this is marked as a temporary directory
|
|
38
36
|
* @property {boolean} isFile - Always false (this is a directory)
|
|
39
37
|
* @property {boolean} isDirectory - Always true
|
|
40
38
|
* @property {DirectoryObject|null} parent - The parent directory (null if root)
|
|
@@ -87,48 +85,45 @@ export default class DirectoryObject extends FS {
|
|
|
87
85
|
isDirectory: true,
|
|
88
86
|
trail: null,
|
|
89
87
|
sep: null,
|
|
90
|
-
|
|
88
|
+
parent: undefined,
|
|
89
|
+
parentPath: undefined,
|
|
91
90
|
})
|
|
92
91
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*
|
|
96
|
-
* @type {DirectoryObject|null|undefined}
|
|
97
|
-
* @private
|
|
98
|
-
*/
|
|
92
|
+
// Not in the meta, because it gets frozen, and these are lazily
|
|
93
|
+
// set.
|
|
99
94
|
#parent = undefined
|
|
100
95
|
|
|
101
96
|
/**
|
|
102
97
|
* Constructs a DirectoryObject instance.
|
|
103
98
|
*
|
|
104
|
-
* @param {string?
|
|
105
|
-
* @param {boolean} [temporary] - Whether this is a temporary directory.
|
|
99
|
+
* @param {string?} [directory="."] - The directory path or DirectoryObject (defaults to current directory)
|
|
106
100
|
*/
|
|
107
|
-
constructor(directory
|
|
108
|
-
|
|
101
|
+
constructor(directory) {
|
|
102
|
+
directory ||= "."
|
|
109
103
|
|
|
110
|
-
Valid.type(directory, "String
|
|
104
|
+
Valid.type(directory, "String")
|
|
111
105
|
|
|
112
|
-
|
|
113
|
-
if(Data.isType(directory, "DirectoryObject") || Data.isType(directory, "TempDirectoryObject"))
|
|
114
|
-
directory = directory.path
|
|
106
|
+
super()
|
|
115
107
|
|
|
116
108
|
const fixedDir = FS.fixSlashes(directory)
|
|
117
109
|
const resolved = path.resolve(fixedDir)
|
|
118
|
-
const url = new URL(FS.
|
|
119
|
-
const baseName = path.basename(resolved) || "
|
|
110
|
+
const url = new URL(FS.pathToUrl(resolved))
|
|
111
|
+
const baseName = path.basename(resolved) || ""
|
|
120
112
|
const trail = resolved.split(path.sep)
|
|
121
113
|
const sep = path.sep
|
|
114
|
+
const pathParts = FS.pathParts(resolved)
|
|
122
115
|
|
|
123
116
|
this.#meta.supplied = fixedDir
|
|
124
117
|
this.#meta.path = resolved
|
|
125
118
|
this.#meta.url = url
|
|
126
119
|
this.#meta.name = baseName
|
|
127
120
|
this.#meta.extension = ""
|
|
128
|
-
this.#meta.module = baseName
|
|
121
|
+
this.#meta.module = baseName !== "." ? baseName : ""
|
|
129
122
|
this.#meta.trail = trail
|
|
130
123
|
this.#meta.sep = sep
|
|
131
|
-
this.#meta.
|
|
124
|
+
this.#meta.parentPath = pathParts.dir === pathParts.root
|
|
125
|
+
? null
|
|
126
|
+
: pathParts.dir
|
|
132
127
|
|
|
133
128
|
Object.freeze(this.#meta)
|
|
134
129
|
}
|
|
@@ -153,36 +148,9 @@ export default class DirectoryObject extends FS {
|
|
|
153
148
|
* @returns {string} string representation of the DirectoryObject
|
|
154
149
|
*/
|
|
155
150
|
toString() {
|
|
156
|
-
return
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Returns a JSON representation of the DirectoryObject.
|
|
161
|
-
*
|
|
162
|
-
* @returns {object} JSON representation of the DirectoryObject
|
|
163
|
-
*/
|
|
164
|
-
toJSON() {
|
|
165
|
-
return {
|
|
166
|
-
supplied: this.supplied,
|
|
167
|
-
path: this.path,
|
|
168
|
-
url: this.url.toString(),
|
|
169
|
-
name: this.name,
|
|
170
|
-
module: this.module,
|
|
171
|
-
extension: this.extension,
|
|
172
|
-
isFile: this.isFile,
|
|
173
|
-
isDirectory: this.isDirectory,
|
|
174
|
-
parent: this.parent ? this.parent.path : null,
|
|
175
|
-
root: this.root.path
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* Custom inspect method for Node.js console.
|
|
181
|
-
*
|
|
182
|
-
* @returns {object} JSON representation of this object.
|
|
183
|
-
*/
|
|
184
|
-
[util.inspect.custom]() {
|
|
185
|
-
return this.toJSON()
|
|
151
|
+
return this.isCapped
|
|
152
|
+
?`[${this.constructor.name}: ${this.path} → ${this.real.path}]`
|
|
153
|
+
:`[${this.constructor.name}: ${this.path}]`
|
|
186
154
|
}
|
|
187
155
|
|
|
188
156
|
/**
|
|
@@ -269,15 +237,6 @@ export default class DirectoryObject extends FS {
|
|
|
269
237
|
return this.#meta.trail
|
|
270
238
|
}
|
|
271
239
|
|
|
272
|
-
/**
|
|
273
|
-
* Returns whether this directory is marked as temporary.
|
|
274
|
-
*
|
|
275
|
-
* @returns {boolean} True if this is a temporary directory, false otherwise
|
|
276
|
-
*/
|
|
277
|
-
get temporary() {
|
|
278
|
-
return this.#meta.temporary
|
|
279
|
-
}
|
|
280
|
-
|
|
281
240
|
/**
|
|
282
241
|
* Returns the parent directory of this directory.
|
|
283
242
|
* Returns null if this directory is the root directory.
|
|
@@ -293,84 +252,18 @@ export default class DirectoryObject extends FS {
|
|
|
293
252
|
*/
|
|
294
253
|
get parent() {
|
|
295
254
|
// Return cached value if available
|
|
296
|
-
if(this.#parent !== undefined)
|
|
255
|
+
if(this.#parent !== undefined)
|
|
297
256
|
return this.#parent
|
|
298
|
-
}
|
|
299
257
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const isRoot = parentPath === this.path
|
|
303
|
-
|
|
304
|
-
// Cache and return
|
|
305
|
-
this.#parent = isRoot
|
|
306
|
-
? null
|
|
307
|
-
: new DirectoryObject(parentPath, this.temporary)
|
|
258
|
+
if(this.#meta.parentPath === null) {
|
|
259
|
+
this.#parent = null
|
|
308
260
|
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/**
|
|
313
|
-
* Returns the root directory of the filesystem.
|
|
314
|
-
*
|
|
315
|
-
* For DirectoryObject, this walks up to the filesystem root.
|
|
316
|
-
* For CappedDirectoryObject, this returns the cap root.
|
|
317
|
-
*
|
|
318
|
-
* @returns {DirectoryObject} The root directory
|
|
319
|
-
* @example
|
|
320
|
-
* const dir = new DirectoryObject("/usr/local/bin")
|
|
321
|
-
* console.log(dir.root.path) // "/"
|
|
322
|
-
*
|
|
323
|
-
* @example
|
|
324
|
-
* const capped = new CappedDirectoryObject("/projects/myapp")
|
|
325
|
-
* const sub = capped.getDirectory("src/lib")
|
|
326
|
-
* console.log(sub.root.path) // "/" (virtual, cap root)
|
|
327
|
-
* console.log(sub.root.real.path) // "/projects/myapp"
|
|
328
|
-
*/
|
|
329
|
-
get root() {
|
|
330
|
-
// Walk up until we find a directory with no parent
|
|
331
|
-
let current = this
|
|
332
|
-
|
|
333
|
-
while(current.parent !== null) {
|
|
334
|
-
current = current.parent
|
|
261
|
+
return this.#parent
|
|
335
262
|
}
|
|
336
263
|
|
|
337
|
-
|
|
338
|
-
}
|
|
264
|
+
this.#parent = new this.constructor(this.#meta.parentPath)
|
|
339
265
|
|
|
340
|
-
|
|
341
|
-
* Recursively removes a temporary directory and all its contents.
|
|
342
|
-
*
|
|
343
|
-
* This method will delete all files and subdirectories within this directory,
|
|
344
|
-
* then delete the directory itself. It only works on directories explicitly
|
|
345
|
-
* marked as temporary for safety.
|
|
346
|
-
*
|
|
347
|
-
* @async
|
|
348
|
-
* @returns {Promise<void>}
|
|
349
|
-
* @throws {Sass} If the directory is not marked as temporary
|
|
350
|
-
* @throws {Sass} If the directory deletion fails
|
|
351
|
-
* @example
|
|
352
|
-
* const tempDir = new TempDirectoryObject("my-temp")
|
|
353
|
-
* await tempDir.assureExists()
|
|
354
|
-
* // ... use the directory ...
|
|
355
|
-
* await tempDir.remove() // Recursively deletes everything
|
|
356
|
-
*/
|
|
357
|
-
async remove() {
|
|
358
|
-
if(!this.temporary)
|
|
359
|
-
throw Sass.new("This is not a temporary directory.")
|
|
360
|
-
|
|
361
|
-
/** @type {{files: Array<FileObject>, directories: Array<DirectoryObject>}} */
|
|
362
|
-
const {files, directories} = await this.read()
|
|
363
|
-
|
|
364
|
-
// Remove subdirectories recursively
|
|
365
|
-
for(const dir of directories)
|
|
366
|
-
await dir.remove()
|
|
367
|
-
|
|
368
|
-
// Remove files
|
|
369
|
-
for(const file of files)
|
|
370
|
-
await file.delete()
|
|
371
|
-
|
|
372
|
-
// Delete the now-empty directory
|
|
373
|
-
await this.delete()
|
|
266
|
+
return this.#parent
|
|
374
267
|
}
|
|
375
268
|
|
|
376
269
|
/**
|
|
@@ -397,11 +290,15 @@ export default class DirectoryObject extends FS {
|
|
|
397
290
|
* @returns {Promise<boolean>} Whether the directory exists
|
|
398
291
|
*/
|
|
399
292
|
async #directoryExists() {
|
|
293
|
+
const path = this.isCapped
|
|
294
|
+
? this.cap?.real.path
|
|
295
|
+
: this.path
|
|
296
|
+
|
|
400
297
|
try {
|
|
401
|
-
(await opendir(
|
|
298
|
+
(await opendir(path)).close()
|
|
402
299
|
|
|
403
300
|
return true
|
|
404
|
-
} catch
|
|
301
|
+
} catch {
|
|
405
302
|
return false
|
|
406
303
|
}
|
|
407
304
|
}
|
|
@@ -423,14 +320,27 @@ export default class DirectoryObject extends FS {
|
|
|
423
320
|
* console.log(files) // Only .js files in ./src
|
|
424
321
|
*/
|
|
425
322
|
async read(pat="") {
|
|
426
|
-
const
|
|
323
|
+
const withFileTypes = true
|
|
324
|
+
const url = this.isCapped
|
|
325
|
+
? this.real?.url
|
|
326
|
+
: this.url
|
|
327
|
+
|
|
328
|
+
Valid.type(url, "URL")
|
|
329
|
+
// const href = url.href
|
|
330
|
+
|
|
427
331
|
const found = !pat
|
|
428
|
-
? await readdir(
|
|
332
|
+
? await readdir(url, {withFileTypes})
|
|
429
333
|
: await Array.fromAsync(
|
|
430
334
|
glob(pat, {
|
|
431
|
-
cwd,
|
|
335
|
+
cwd: this.isCapped ? this.real?.path : this.path,
|
|
432
336
|
withFileTypes,
|
|
433
|
-
exclude: candidate =>
|
|
337
|
+
// exclude: candidate => {
|
|
338
|
+
// // Only allow entries within this directory's URL
|
|
339
|
+
// const candidateHref = candidate.url.href
|
|
340
|
+
|
|
341
|
+
// // Must start with our URL + path separator, or be exactly our URL
|
|
342
|
+
// return !candidateHref.startsWith(href + "/") && candidateHref !== href
|
|
343
|
+
// }
|
|
434
344
|
})
|
|
435
345
|
)
|
|
436
346
|
|
|
@@ -441,9 +351,9 @@ export default class DirectoryObject extends FS {
|
|
|
441
351
|
const directories = found
|
|
442
352
|
.filter(dirent => dirent.isDirectory())
|
|
443
353
|
.map(dirent => {
|
|
444
|
-
const dirPath =
|
|
354
|
+
const dirPath = FS.resolvePath(this.path, dirent.name)
|
|
445
355
|
|
|
446
|
-
return new
|
|
356
|
+
return new this.constructor(dirPath, this)
|
|
447
357
|
})
|
|
448
358
|
|
|
449
359
|
return {files, directories}
|
|
@@ -486,31 +396,26 @@ export default class DirectoryObject extends FS {
|
|
|
486
396
|
* @yields {DirectoryObject} Parent directory objects from current to root
|
|
487
397
|
*/
|
|
488
398
|
*#walkUp() {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
399
|
+
const {root, base, dir} = FS.pathParts(this.path)
|
|
400
|
+
const sep = path.sep
|
|
401
|
+
// Remove the root and then re-add it every loop, because that's fun!
|
|
402
|
+
const choppedDir = Data.chopLeft(dir, root)
|
|
403
|
+
const trail = [...choppedDir.split(sep).filter(Boolean), base]
|
|
493
404
|
|
|
494
|
-
|
|
495
|
-
|
|
405
|
+
if(trail.length === 0)
|
|
406
|
+
return yield this
|
|
496
407
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// Yield the root and stop
|
|
500
|
-
yield new DirectoryObject(this.sep)
|
|
501
|
-
break
|
|
502
|
-
}
|
|
408
|
+
do
|
|
409
|
+
yield new this.constructor(path.join(root, ...trail), this.cap)
|
|
503
410
|
|
|
504
|
-
|
|
505
|
-
curr.pop()
|
|
506
|
-
}
|
|
411
|
+
while(trail.pop())
|
|
507
412
|
}
|
|
508
413
|
|
|
509
414
|
/**
|
|
510
415
|
* Generator that walks up the directory tree, yielding each parent directory.
|
|
511
416
|
* Starts from the current directory and yields each parent until reaching the root.
|
|
512
417
|
*
|
|
513
|
-
* @returns {
|
|
418
|
+
* @returns {DirectoryObject} Generator yielding parent DirectoryObject instances
|
|
514
419
|
* @example
|
|
515
420
|
* const dir = new DirectoryObject('/path/to/deep/directory')
|
|
516
421
|
* for(const parent of dir.walkUp) {
|
|
@@ -578,6 +483,14 @@ export default class DirectoryObject extends FS {
|
|
|
578
483
|
return await directory.exists
|
|
579
484
|
}
|
|
580
485
|
|
|
486
|
+
#isLocal = candidate => {
|
|
487
|
+
Valid.type(candidate, "String", {allowEmpty: false})
|
|
488
|
+
|
|
489
|
+
const {dir: candidateDir} = FS.pathParts(candidate)
|
|
490
|
+
|
|
491
|
+
return candidateDir === this.path
|
|
492
|
+
}
|
|
493
|
+
|
|
581
494
|
/**
|
|
582
495
|
* Creates a new DirectoryObject by extending this directory's path.
|
|
583
496
|
*
|
|
@@ -585,7 +498,7 @@ export default class DirectoryObject extends FS {
|
|
|
585
498
|
* duplication (e.g., "/projects/toolkit" + "toolkit/src" = "/projects/toolkit/src").
|
|
586
499
|
* The temporary flag is preserved from the parent directory.
|
|
587
500
|
*
|
|
588
|
-
* @param {string}
|
|
501
|
+
* @param {string} dir - The subdirectory path to append (can be nested like "src/lib")
|
|
589
502
|
* @returns {DirectoryObject} A new DirectoryObject instance with the combined path
|
|
590
503
|
* @throws {Sass} If newPath is not a string
|
|
591
504
|
* @example
|
|
@@ -599,13 +512,14 @@ export default class DirectoryObject extends FS {
|
|
|
599
512
|
* const subDir = dir.getDirectory("toolkit/src")
|
|
600
513
|
* console.log(subDir.path) // "/projects/toolkit/src" (not /projects/toolkit/toolkit/src)
|
|
601
514
|
*/
|
|
602
|
-
getDirectory(
|
|
603
|
-
Valid.type(
|
|
515
|
+
getDirectory(dir) {
|
|
516
|
+
Valid.type(dir, "String", {allowEmpty: false})
|
|
517
|
+
|
|
518
|
+
const newPath = FS.resolvePath(this.path, dir)
|
|
604
519
|
|
|
605
|
-
|
|
606
|
-
const merged = FS.mergeOverlappingPaths(thisPath, newPath)
|
|
520
|
+
Valid.assert(this.#isLocal(newPath), `${newPath} would be out of bounds.`)
|
|
607
521
|
|
|
608
|
-
return new this.constructor(
|
|
522
|
+
return new this.constructor(newPath, this)
|
|
609
523
|
}
|
|
610
524
|
|
|
611
525
|
/**
|
|
@@ -615,7 +529,7 @@ export default class DirectoryObject extends FS {
|
|
|
615
529
|
* duplication. The resulting FileObject can be used for reading, writing,
|
|
616
530
|
* and other file operations.
|
|
617
531
|
*
|
|
618
|
-
* @param {string}
|
|
532
|
+
* @param {string} file - The filename to append (can include subdirectories like "src/index.js")
|
|
619
533
|
* @returns {FileObject} A new FileObject instance with the combined path
|
|
620
534
|
* @throws {Sass} If filename is not a string
|
|
621
535
|
* @example
|
|
@@ -628,11 +542,13 @@ export default class DirectoryObject extends FS {
|
|
|
628
542
|
* const file = dir.getFile("src/index.js")
|
|
629
543
|
* const data = await file.read()
|
|
630
544
|
*/
|
|
631
|
-
getFile(
|
|
632
|
-
Valid.type(
|
|
545
|
+
getFile(file) {
|
|
546
|
+
Valid.type(file, "String", {allowEmpty: false})
|
|
547
|
+
|
|
548
|
+
const newPath = FS.resolvePath(this.path, file)
|
|
549
|
+
|
|
550
|
+
Valid.assert(this.#isLocal(newPath), `${newPath} would be out of bounds.`)
|
|
633
551
|
|
|
634
|
-
|
|
635
|
-
// This ensures the FileObject maintains the correct parent reference
|
|
636
|
-
return new FileObject(filename, this)
|
|
552
|
+
return new FileObject(newPath, this)
|
|
637
553
|
}
|
|
638
554
|
}
|