@gesslar/toolkit 3.8.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 +87 -467
- package/src/lib/DirectoryObject.js +109 -147
- package/src/lib/FS.js +221 -70
- package/src/lib/FileObject.js +78 -81
- package/src/lib/TempDirectoryObject.js +93 -129
- 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 +48 -56
- package/src/types/lib/CappedDirectoryObject.d.ts.map +1 -1
- package/src/types/lib/DirectoryObject.d.ts +24 -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 +19 -59
- 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,59 +85,76 @@ 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
|
-
const fixedDir = FS.fixSlashes(directory
|
|
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
|
}
|
|
135
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Creates a DirectoryObject from the current working directory.
|
|
133
|
+
* Useful when working with pnpx or other tools where the project root
|
|
134
|
+
* needs to be determined at runtime.
|
|
135
|
+
*
|
|
136
|
+
* @returns {DirectoryObject} A DirectoryObject representing the current working directory
|
|
137
|
+
* @example
|
|
138
|
+
* const projectRoot = DirectoryObject.fromCwd()
|
|
139
|
+
* console.log(projectRoot.path) // process.cwd()
|
|
140
|
+
*/
|
|
141
|
+
static fromCwd() {
|
|
142
|
+
return new this(process.cwd())
|
|
143
|
+
}
|
|
144
|
+
|
|
136
145
|
/**
|
|
137
146
|
* Returns a string representation of the DirectoryObject.
|
|
138
147
|
*
|
|
139
148
|
* @returns {string} string representation of the DirectoryObject
|
|
140
149
|
*/
|
|
141
150
|
toString() {
|
|
142
|
-
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}]`
|
|
143
158
|
}
|
|
144
159
|
|
|
145
160
|
/**
|
|
@@ -157,8 +172,7 @@ export default class DirectoryObject extends FS {
|
|
|
157
172
|
extension: this.extension,
|
|
158
173
|
isFile: this.isFile,
|
|
159
174
|
isDirectory: this.isDirectory,
|
|
160
|
-
parent: this.parent
|
|
161
|
-
root: this.root.path
|
|
175
|
+
parent: this.parent?.path ?? null,
|
|
162
176
|
}
|
|
163
177
|
}
|
|
164
178
|
|
|
@@ -167,9 +181,9 @@ export default class DirectoryObject extends FS {
|
|
|
167
181
|
*
|
|
168
182
|
* @returns {object} JSON representation of this object.
|
|
169
183
|
*/
|
|
170
|
-
[util.inspect.custom]() {
|
|
171
|
-
|
|
172
|
-
}
|
|
184
|
+
// [util.inspect.custom]() {
|
|
185
|
+
// return this.toJSON()
|
|
186
|
+
// }
|
|
173
187
|
|
|
174
188
|
/**
|
|
175
189
|
* Checks if the directory exists (async).
|
|
@@ -255,15 +269,6 @@ export default class DirectoryObject extends FS {
|
|
|
255
269
|
return this.#meta.trail
|
|
256
270
|
}
|
|
257
271
|
|
|
258
|
-
/**
|
|
259
|
-
* Returns whether this directory is marked as temporary.
|
|
260
|
-
*
|
|
261
|
-
* @returns {boolean} True if this is a temporary directory, false otherwise
|
|
262
|
-
*/
|
|
263
|
-
get temporary() {
|
|
264
|
-
return this.#meta.temporary
|
|
265
|
-
}
|
|
266
|
-
|
|
267
272
|
/**
|
|
268
273
|
* Returns the parent directory of this directory.
|
|
269
274
|
* Returns null if this directory is the root directory.
|
|
@@ -279,84 +284,18 @@ export default class DirectoryObject extends FS {
|
|
|
279
284
|
*/
|
|
280
285
|
get parent() {
|
|
281
286
|
// Return cached value if available
|
|
282
|
-
if(this.#parent !== undefined)
|
|
287
|
+
if(this.#parent !== undefined)
|
|
283
288
|
return this.#parent
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// Compute parent directory (null if we're at root)
|
|
287
|
-
const parentPath = path.dirname(this.path)
|
|
288
|
-
const isRoot = parentPath === this.path
|
|
289
|
-
|
|
290
|
-
// Cache and return
|
|
291
|
-
this.#parent = isRoot
|
|
292
|
-
? null
|
|
293
|
-
: new DirectoryObject(parentPath, this.temporary)
|
|
294
289
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Returns the root directory of the filesystem.
|
|
300
|
-
*
|
|
301
|
-
* For DirectoryObject, this walks up to the filesystem root.
|
|
302
|
-
* For CappedDirectoryObject, this returns the cap root.
|
|
303
|
-
*
|
|
304
|
-
* @returns {DirectoryObject} The root directory
|
|
305
|
-
* @example
|
|
306
|
-
* const dir = new DirectoryObject("/usr/local/bin")
|
|
307
|
-
* console.log(dir.root.path) // "/"
|
|
308
|
-
*
|
|
309
|
-
* @example
|
|
310
|
-
* const capped = new CappedDirectoryObject("/projects/myapp")
|
|
311
|
-
* const sub = capped.getDirectory("src/lib")
|
|
312
|
-
* console.log(sub.root.path) // "/" (virtual, cap root)
|
|
313
|
-
* console.log(sub.root.real.path) // "/projects/myapp"
|
|
314
|
-
*/
|
|
315
|
-
get root() {
|
|
316
|
-
// Walk up until we find a directory with no parent
|
|
317
|
-
let current = this
|
|
290
|
+
if(this.#meta.parentPath === null) {
|
|
291
|
+
this.#parent = null
|
|
318
292
|
|
|
319
|
-
|
|
320
|
-
current = current.parent
|
|
293
|
+
return this.#parent
|
|
321
294
|
}
|
|
322
295
|
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Recursively removes a temporary directory and all its contents.
|
|
328
|
-
*
|
|
329
|
-
* This method will delete all files and subdirectories within this directory,
|
|
330
|
-
* then delete the directory itself. It only works on directories explicitly
|
|
331
|
-
* marked as temporary for safety.
|
|
332
|
-
*
|
|
333
|
-
* @async
|
|
334
|
-
* @returns {Promise<void>}
|
|
335
|
-
* @throws {Sass} If the directory is not marked as temporary
|
|
336
|
-
* @throws {Sass} If the directory deletion fails
|
|
337
|
-
* @example
|
|
338
|
-
* const tempDir = new TempDirectoryObject("my-temp")
|
|
339
|
-
* await tempDir.assureExists()
|
|
340
|
-
* // ... use the directory ...
|
|
341
|
-
* await tempDir.remove() // Recursively deletes everything
|
|
342
|
-
*/
|
|
343
|
-
async remove() {
|
|
344
|
-
if(!this.temporary)
|
|
345
|
-
throw Sass.new("This is not a temporary directory.")
|
|
346
|
-
|
|
347
|
-
/** @type {{files: Array<FileObject>, directories: Array<DirectoryObject>}} */
|
|
348
|
-
const {files, directories} = await this.read()
|
|
349
|
-
|
|
350
|
-
// Remove subdirectories recursively
|
|
351
|
-
for(const dir of directories)
|
|
352
|
-
await dir.remove()
|
|
296
|
+
this.#parent = new this.constructor(this.#meta.parentPath)
|
|
353
297
|
|
|
354
|
-
|
|
355
|
-
for(const file of files)
|
|
356
|
-
await file.delete()
|
|
357
|
-
|
|
358
|
-
// Delete the now-empty directory
|
|
359
|
-
await this.delete()
|
|
298
|
+
return this.#parent
|
|
360
299
|
}
|
|
361
300
|
|
|
362
301
|
/**
|
|
@@ -383,11 +322,15 @@ export default class DirectoryObject extends FS {
|
|
|
383
322
|
* @returns {Promise<boolean>} Whether the directory exists
|
|
384
323
|
*/
|
|
385
324
|
async #directoryExists() {
|
|
325
|
+
const path = this.isCapped
|
|
326
|
+
? this.cap?.real.path
|
|
327
|
+
: this.path
|
|
328
|
+
|
|
386
329
|
try {
|
|
387
|
-
(await opendir(
|
|
330
|
+
(await opendir(path)).close()
|
|
388
331
|
|
|
389
332
|
return true
|
|
390
|
-
} catch
|
|
333
|
+
} catch {
|
|
391
334
|
return false
|
|
392
335
|
}
|
|
393
336
|
}
|
|
@@ -409,14 +352,27 @@ export default class DirectoryObject extends FS {
|
|
|
409
352
|
* console.log(files) // Only .js files in ./src
|
|
410
353
|
*/
|
|
411
354
|
async read(pat="") {
|
|
412
|
-
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
|
+
|
|
413
363
|
const found = !pat
|
|
414
|
-
? await readdir(
|
|
364
|
+
? await readdir(url, {withFileTypes})
|
|
415
365
|
: await Array.fromAsync(
|
|
416
366
|
glob(pat, {
|
|
417
|
-
cwd,
|
|
367
|
+
cwd: this.isCapped ? this.real?.path : this.path,
|
|
418
368
|
withFileTypes,
|
|
419
|
-
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
|
+
// }
|
|
420
376
|
})
|
|
421
377
|
)
|
|
422
378
|
|
|
@@ -427,9 +383,9 @@ export default class DirectoryObject extends FS {
|
|
|
427
383
|
const directories = found
|
|
428
384
|
.filter(dirent => dirent.isDirectory())
|
|
429
385
|
.map(dirent => {
|
|
430
|
-
const dirPath =
|
|
386
|
+
const dirPath = FS.resolvePath(this.path, dirent.name)
|
|
431
387
|
|
|
432
|
-
return new
|
|
388
|
+
return new this.constructor(dirPath, this)
|
|
433
389
|
})
|
|
434
390
|
|
|
435
391
|
return {files, directories}
|
|
@@ -472,31 +428,26 @@ export default class DirectoryObject extends FS {
|
|
|
472
428
|
* @yields {DirectoryObject} Parent directory objects from current to root
|
|
473
429
|
*/
|
|
474
430
|
*#walkUp() {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
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]
|
|
479
436
|
|
|
480
|
-
|
|
481
|
-
|
|
437
|
+
if(trail.length === 0)
|
|
438
|
+
return yield this
|
|
482
439
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
// Yield the root and stop
|
|
486
|
-
yield new DirectoryObject(this.sep)
|
|
487
|
-
break
|
|
488
|
-
}
|
|
440
|
+
do
|
|
441
|
+
yield new this.constructor(path.join(root, ...trail), this.cap)
|
|
489
442
|
|
|
490
|
-
|
|
491
|
-
curr.pop()
|
|
492
|
-
}
|
|
443
|
+
while(trail.pop())
|
|
493
444
|
}
|
|
494
445
|
|
|
495
446
|
/**
|
|
496
447
|
* Generator that walks up the directory tree, yielding each parent directory.
|
|
497
448
|
* Starts from the current directory and yields each parent until reaching the root.
|
|
498
449
|
*
|
|
499
|
-
* @returns {
|
|
450
|
+
* @returns {DirectoryObject} Generator yielding parent DirectoryObject instances
|
|
500
451
|
* @example
|
|
501
452
|
* const dir = new DirectoryObject('/path/to/deep/directory')
|
|
502
453
|
* for(const parent of dir.walkUp) {
|
|
@@ -564,6 +515,14 @@ export default class DirectoryObject extends FS {
|
|
|
564
515
|
return await directory.exists
|
|
565
516
|
}
|
|
566
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
|
+
|
|
567
526
|
/**
|
|
568
527
|
* Creates a new DirectoryObject by extending this directory's path.
|
|
569
528
|
*
|
|
@@ -571,7 +530,7 @@ export default class DirectoryObject extends FS {
|
|
|
571
530
|
* duplication (e.g., "/projects/toolkit" + "toolkit/src" = "/projects/toolkit/src").
|
|
572
531
|
* The temporary flag is preserved from the parent directory.
|
|
573
532
|
*
|
|
574
|
-
* @param {string}
|
|
533
|
+
* @param {string} dir - The subdirectory path to append (can be nested like "src/lib")
|
|
575
534
|
* @returns {DirectoryObject} A new DirectoryObject instance with the combined path
|
|
576
535
|
* @throws {Sass} If newPath is not a string
|
|
577
536
|
* @example
|
|
@@ -585,13 +544,14 @@ export default class DirectoryObject extends FS {
|
|
|
585
544
|
* const subDir = dir.getDirectory("toolkit/src")
|
|
586
545
|
* console.log(subDir.path) // "/projects/toolkit/src" (not /projects/toolkit/toolkit/src)
|
|
587
546
|
*/
|
|
588
|
-
getDirectory(
|
|
589
|
-
Valid.type(
|
|
547
|
+
getDirectory(dir) {
|
|
548
|
+
Valid.type(dir, "String", {allowEmpty: false})
|
|
549
|
+
|
|
550
|
+
const newPath = FS.resolvePath(this.path, dir)
|
|
590
551
|
|
|
591
|
-
|
|
592
|
-
const merged = FS.mergeOverlappingPaths(thisPath, newPath)
|
|
552
|
+
Valid.assert(this.#isLocal(newPath), `${newPath} would be out of bounds.`)
|
|
593
553
|
|
|
594
|
-
return new this.constructor(
|
|
554
|
+
return new this.constructor(newPath, this)
|
|
595
555
|
}
|
|
596
556
|
|
|
597
557
|
/**
|
|
@@ -601,7 +561,7 @@ export default class DirectoryObject extends FS {
|
|
|
601
561
|
* duplication. The resulting FileObject can be used for reading, writing,
|
|
602
562
|
* and other file operations.
|
|
603
563
|
*
|
|
604
|
-
* @param {string}
|
|
564
|
+
* @param {string} file - The filename to append (can include subdirectories like "src/index.js")
|
|
605
565
|
* @returns {FileObject} A new FileObject instance with the combined path
|
|
606
566
|
* @throws {Sass} If filename is not a string
|
|
607
567
|
* @example
|
|
@@ -614,11 +574,13 @@ export default class DirectoryObject extends FS {
|
|
|
614
574
|
* const file = dir.getFile("src/index.js")
|
|
615
575
|
* const data = await file.read()
|
|
616
576
|
*/
|
|
617
|
-
getFile(
|
|
618
|
-
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.`)
|
|
619
583
|
|
|
620
|
-
|
|
621
|
-
// This ensures the FileObject maintains the correct parent reference
|
|
622
|
-
return new FileObject(filename, this)
|
|
584
|
+
return new FileObject(newPath, this)
|
|
623
585
|
}
|
|
624
586
|
}
|