@gesslar/toolkit 3.9.0 → 3.12.3
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 +71 -470
- package/src/lib/DirectoryObject.js +94 -146
- package/src/lib/FS.js +221 -70
- package/src/lib/FileObject.js +78 -81
- 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 +13 -54
- 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 -3
- 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,7 +148,13 @@ export default class DirectoryObject extends FS {
|
|
|
153
148
|
* @returns {string} string representation of the DirectoryObject
|
|
154
149
|
*/
|
|
155
150
|
toString() {
|
|
156
|
-
return
|
|
151
|
+
return this.isCapped
|
|
152
|
+
?`[${this.constructor.name}: ${this.path} → ${this.real.path}]`
|
|
153
|
+
:`[${this.constructor.name}: ${this.path}]`
|
|
154
|
+
|
|
155
|
+
return this.isCapped
|
|
156
|
+
?`[${this.constructor.name}: ${this.path} → ${this.real.path}]`
|
|
157
|
+
:`[${this.constructor.name}: ${this.path}]`
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
/**
|
|
@@ -171,8 +172,7 @@ export default class DirectoryObject extends FS {
|
|
|
171
172
|
extension: this.extension,
|
|
172
173
|
isFile: this.isFile,
|
|
173
174
|
isDirectory: this.isDirectory,
|
|
174
|
-
parent: this.parent
|
|
175
|
-
root: this.root.path
|
|
175
|
+
parent: this.parent?.path ?? null,
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
|
|
@@ -181,9 +181,9 @@ export default class DirectoryObject extends FS {
|
|
|
181
181
|
*
|
|
182
182
|
* @returns {object} JSON representation of this object.
|
|
183
183
|
*/
|
|
184
|
-
[util.inspect.custom]() {
|
|
185
|
-
|
|
186
|
-
}
|
|
184
|
+
// [util.inspect.custom]() {
|
|
185
|
+
// return this.toJSON()
|
|
186
|
+
// }
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* Checks if the directory exists (async).
|
|
@@ -269,15 +269,6 @@ export default class DirectoryObject extends FS {
|
|
|
269
269
|
return this.#meta.trail
|
|
270
270
|
}
|
|
271
271
|
|
|
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
272
|
/**
|
|
282
273
|
* Returns the parent directory of this directory.
|
|
283
274
|
* Returns null if this directory is the root directory.
|
|
@@ -293,84 +284,18 @@ export default class DirectoryObject extends FS {
|
|
|
293
284
|
*/
|
|
294
285
|
get parent() {
|
|
295
286
|
// Return cached value if available
|
|
296
|
-
if(this.#parent !== undefined)
|
|
287
|
+
if(this.#parent !== undefined)
|
|
297
288
|
return this.#parent
|
|
298
|
-
}
|
|
299
289
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
const isRoot = parentPath === this.path
|
|
290
|
+
if(this.#meta.parentPath === null) {
|
|
291
|
+
this.#parent = null
|
|
303
292
|
|
|
304
|
-
|
|
305
|
-
this.#parent = isRoot
|
|
306
|
-
? null
|
|
307
|
-
: new DirectoryObject(parentPath, this.temporary)
|
|
308
|
-
|
|
309
|
-
return this.#parent
|
|
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
|
|
293
|
+
return this.#parent
|
|
335
294
|
}
|
|
336
295
|
|
|
337
|
-
|
|
338
|
-
}
|
|
296
|
+
this.#parent = new this.constructor(this.#meta.parentPath)
|
|
339
297
|
|
|
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()
|
|
298
|
+
return this.#parent
|
|
374
299
|
}
|
|
375
300
|
|
|
376
301
|
/**
|
|
@@ -397,11 +322,15 @@ export default class DirectoryObject extends FS {
|
|
|
397
322
|
* @returns {Promise<boolean>} Whether the directory exists
|
|
398
323
|
*/
|
|
399
324
|
async #directoryExists() {
|
|
325
|
+
const path = this.isCapped
|
|
326
|
+
? this.cap?.real.path
|
|
327
|
+
: this.path
|
|
328
|
+
|
|
400
329
|
try {
|
|
401
|
-
(await opendir(
|
|
330
|
+
(await opendir(path)).close()
|
|
402
331
|
|
|
403
332
|
return true
|
|
404
|
-
} catch
|
|
333
|
+
} catch {
|
|
405
334
|
return false
|
|
406
335
|
}
|
|
407
336
|
}
|
|
@@ -423,14 +352,27 @@ export default class DirectoryObject extends FS {
|
|
|
423
352
|
* console.log(files) // Only .js files in ./src
|
|
424
353
|
*/
|
|
425
354
|
async read(pat="") {
|
|
426
|
-
const
|
|
355
|
+
const withFileTypes = true
|
|
356
|
+
const url = this.isCapped
|
|
357
|
+
? this.real?.url
|
|
358
|
+
: this.url
|
|
359
|
+
|
|
360
|
+
Valid.type(url, "URL")
|
|
361
|
+
// const href = url.href
|
|
362
|
+
|
|
427
363
|
const found = !pat
|
|
428
|
-
? await readdir(
|
|
364
|
+
? await readdir(url, {withFileTypes})
|
|
429
365
|
: await Array.fromAsync(
|
|
430
366
|
glob(pat, {
|
|
431
|
-
cwd,
|
|
367
|
+
cwd: this.isCapped ? this.real?.path : this.path,
|
|
432
368
|
withFileTypes,
|
|
433
|
-
exclude: candidate =>
|
|
369
|
+
// exclude: candidate => {
|
|
370
|
+
// // Only allow entries within this directory's URL
|
|
371
|
+
// const candidateHref = candidate.url.href
|
|
372
|
+
|
|
373
|
+
// // Must start with our URL + path separator, or be exactly our URL
|
|
374
|
+
// return !candidateHref.startsWith(href + "/") && candidateHref !== href
|
|
375
|
+
// }
|
|
434
376
|
})
|
|
435
377
|
)
|
|
436
378
|
|
|
@@ -441,9 +383,9 @@ export default class DirectoryObject extends FS {
|
|
|
441
383
|
const directories = found
|
|
442
384
|
.filter(dirent => dirent.isDirectory())
|
|
443
385
|
.map(dirent => {
|
|
444
|
-
const dirPath =
|
|
386
|
+
const dirPath = FS.resolvePath(this.path, dirent.name)
|
|
445
387
|
|
|
446
|
-
return new
|
|
388
|
+
return new this.constructor(dirPath, this)
|
|
447
389
|
})
|
|
448
390
|
|
|
449
391
|
return {files, directories}
|
|
@@ -486,31 +428,26 @@ export default class DirectoryObject extends FS {
|
|
|
486
428
|
* @yields {DirectoryObject} Parent directory objects from current to root
|
|
487
429
|
*/
|
|
488
430
|
*#walkUp() {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
const
|
|
431
|
+
const {root, base, dir} = FS.pathParts(this.path)
|
|
432
|
+
const sep = path.sep
|
|
433
|
+
// Remove the root and then re-add it every loop, because that's fun!
|
|
434
|
+
const choppedDir = Data.chopLeft(dir, root)
|
|
435
|
+
const trail = [...choppedDir.split(sep).filter(Boolean), base]
|
|
493
436
|
|
|
494
|
-
|
|
495
|
-
|
|
437
|
+
if(trail.length === 0)
|
|
438
|
+
return yield this
|
|
496
439
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// Yield the root and stop
|
|
500
|
-
yield new DirectoryObject(this.sep)
|
|
501
|
-
break
|
|
502
|
-
}
|
|
440
|
+
do
|
|
441
|
+
yield new this.constructor(path.join(root, ...trail), this.cap)
|
|
503
442
|
|
|
504
|
-
|
|
505
|
-
curr.pop()
|
|
506
|
-
}
|
|
443
|
+
while(trail.pop())
|
|
507
444
|
}
|
|
508
445
|
|
|
509
446
|
/**
|
|
510
447
|
* Generator that walks up the directory tree, yielding each parent directory.
|
|
511
448
|
* Starts from the current directory and yields each parent until reaching the root.
|
|
512
449
|
*
|
|
513
|
-
* @returns {
|
|
450
|
+
* @returns {DirectoryObject} Generator yielding parent DirectoryObject instances
|
|
514
451
|
* @example
|
|
515
452
|
* const dir = new DirectoryObject('/path/to/deep/directory')
|
|
516
453
|
* for(const parent of dir.walkUp) {
|
|
@@ -578,6 +515,14 @@ export default class DirectoryObject extends FS {
|
|
|
578
515
|
return await directory.exists
|
|
579
516
|
}
|
|
580
517
|
|
|
518
|
+
#isLocal = candidate => {
|
|
519
|
+
Valid.type(candidate, "String", {allowEmpty: false})
|
|
520
|
+
|
|
521
|
+
const {dir: candidateDir} = FS.pathParts(candidate)
|
|
522
|
+
|
|
523
|
+
return candidateDir === this.path
|
|
524
|
+
}
|
|
525
|
+
|
|
581
526
|
/**
|
|
582
527
|
* Creates a new DirectoryObject by extending this directory's path.
|
|
583
528
|
*
|
|
@@ -585,7 +530,7 @@ export default class DirectoryObject extends FS {
|
|
|
585
530
|
* duplication (e.g., "/projects/toolkit" + "toolkit/src" = "/projects/toolkit/src").
|
|
586
531
|
* The temporary flag is preserved from the parent directory.
|
|
587
532
|
*
|
|
588
|
-
* @param {string}
|
|
533
|
+
* @param {string} dir - The subdirectory path to append (can be nested like "src/lib")
|
|
589
534
|
* @returns {DirectoryObject} A new DirectoryObject instance with the combined path
|
|
590
535
|
* @throws {Sass} If newPath is not a string
|
|
591
536
|
* @example
|
|
@@ -599,13 +544,14 @@ export default class DirectoryObject extends FS {
|
|
|
599
544
|
* const subDir = dir.getDirectory("toolkit/src")
|
|
600
545
|
* console.log(subDir.path) // "/projects/toolkit/src" (not /projects/toolkit/toolkit/src)
|
|
601
546
|
*/
|
|
602
|
-
getDirectory(
|
|
603
|
-
Valid.type(
|
|
547
|
+
getDirectory(dir) {
|
|
548
|
+
Valid.type(dir, "String", {allowEmpty: false})
|
|
604
549
|
|
|
605
|
-
const
|
|
606
|
-
const merged = FS.mergeOverlappingPaths(thisPath, newPath)
|
|
550
|
+
const newPath = FS.resolvePath(this.path, dir)
|
|
607
551
|
|
|
608
|
-
|
|
552
|
+
Valid.assert(this.#isLocal(newPath), `${newPath} would be out of bounds.`)
|
|
553
|
+
|
|
554
|
+
return new this.constructor(newPath, this)
|
|
609
555
|
}
|
|
610
556
|
|
|
611
557
|
/**
|
|
@@ -615,7 +561,7 @@ export default class DirectoryObject extends FS {
|
|
|
615
561
|
* duplication. The resulting FileObject can be used for reading, writing,
|
|
616
562
|
* and other file operations.
|
|
617
563
|
*
|
|
618
|
-
* @param {string}
|
|
564
|
+
* @param {string} file - The filename to append (can include subdirectories like "src/index.js")
|
|
619
565
|
* @returns {FileObject} A new FileObject instance with the combined path
|
|
620
566
|
* @throws {Sass} If filename is not a string
|
|
621
567
|
* @example
|
|
@@ -628,11 +574,13 @@ export default class DirectoryObject extends FS {
|
|
|
628
574
|
* const file = dir.getFile("src/index.js")
|
|
629
575
|
* const data = await file.read()
|
|
630
576
|
*/
|
|
631
|
-
getFile(
|
|
632
|
-
Valid.type(
|
|
577
|
+
getFile(file) {
|
|
578
|
+
Valid.type(file, "String", {allowEmpty: false})
|
|
579
|
+
|
|
580
|
+
const newPath = FS.resolvePath(this.path, file)
|
|
581
|
+
|
|
582
|
+
Valid.assert(this.#isLocal(newPath), `${newPath} would be out of bounds.`)
|
|
633
583
|
|
|
634
|
-
|
|
635
|
-
// This ensures the FileObject maintains the correct parent reference
|
|
636
|
-
return new FileObject(filename, this)
|
|
584
|
+
return new FileObject(newPath, this)
|
|
637
585
|
}
|
|
638
586
|
}
|