@gesslar/toolkit 2.9.0 → 2.10.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
CHANGED
|
@@ -31,89 +31,80 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
31
31
|
/**
|
|
32
32
|
* Constructs a CappedDirectoryObject instance.
|
|
33
33
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
34
|
+
* Without a parent, the path becomes both the directory location and the cap
|
|
35
|
+
* (virtual root). With a parent, the path is resolved relative to the parent's
|
|
36
|
+
* cap using virtual path semantics (absolute paths treated as cap-relative).
|
|
36
37
|
*
|
|
37
|
-
* @param {string
|
|
38
|
-
* @param {string} cap - The root path that constrains this directory tree
|
|
38
|
+
* @param {string} dirPath - Directory path (becomes cap if no parent, else relative to parent's cap)
|
|
39
39
|
* @param {CappedDirectoryObject?} [parent] - Optional parent capped directory
|
|
40
40
|
* @param {boolean} [temporary=false] - Whether this is a temporary directory
|
|
41
|
-
* @throws {Sass} If
|
|
42
|
-
* @throws {Sass} If
|
|
43
|
-
* @throws {Sass} If name contains path separators
|
|
44
|
-
* @throws {Sass} If parent is not a capped directory
|
|
45
|
-
* @throws {Sass} If parent's lineage does not trace back to the cap
|
|
41
|
+
* @throws {Sass} If path is empty
|
|
42
|
+
* @throws {Sass} If parent is provided but not a CappedDirectoryObject
|
|
46
43
|
* @throws {Sass} If the resulting path would escape the cap
|
|
44
|
+
* @example
|
|
45
|
+
* // Create new capped directory
|
|
46
|
+
* const cache = new CappedDirectoryObject("/home/user/.cache")
|
|
47
|
+
* // path: /home/user/.cache, cap: /home/user/.cache
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Create subdirectory with parent
|
|
51
|
+
* const data = new CappedDirectoryObject("data", cache)
|
|
52
|
+
* // path: /home/user/.cache/data, cap: /home/user/.cache
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Virtual absolute path with parent
|
|
56
|
+
* const config = new CappedDirectoryObject("/etc/config", cache)
|
|
57
|
+
* // path: /home/user/.cache/etc/config, cap: /home/user/.cache
|
|
47
58
|
*/
|
|
48
|
-
constructor(
|
|
49
|
-
Valid.type(
|
|
59
|
+
constructor(dirPath, parent=null, temporary=false) {
|
|
60
|
+
Valid.type(dirPath, "String")
|
|
61
|
+
Valid.assert(dirPath.length > 0, "Path must not be empty.")
|
|
50
62
|
|
|
51
63
|
// Validate parent using instanceof since TypeSpec doesn't understand inheritance
|
|
52
64
|
if(parent !== null && !(parent instanceof CappedDirectoryObject)) {
|
|
53
65
|
throw Sass.new(`Parent must be null or a CappedDirectoryObject instance, got ${Data.typeOf(parent)}`)
|
|
54
66
|
}
|
|
55
67
|
|
|
56
|
-
let
|
|
68
|
+
let cap
|
|
69
|
+
let resolvedPath
|
|
57
70
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
if(!parent) {
|
|
72
|
+
// No parent: dirPath becomes both the directory and the cap
|
|
73
|
+
cap = path.resolve(dirPath)
|
|
74
|
+
resolvedPath = cap
|
|
61
75
|
} else {
|
|
62
|
-
|
|
76
|
+
// With parent: inherit cap and resolve dirPath relative to it
|
|
77
|
+
cap = parent.#cap
|
|
63
78
|
|
|
64
|
-
//
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
"Capped directory name must not be an absolute path.",
|
|
68
|
-
)
|
|
69
|
-
Valid.assert(
|
|
70
|
-
name.length > 0,
|
|
71
|
-
"Capped directory name must not be empty.",
|
|
72
|
-
)
|
|
73
|
-
Valid.assert(
|
|
74
|
-
!name.includes("/") && !name.includes("\\") && !name.includes(path.sep),
|
|
75
|
-
"Capped directory name must not contain path separators.",
|
|
76
|
-
)
|
|
79
|
+
// Use real path for filesystem operations
|
|
80
|
+
const parentPath = parent.realPath || parent.path
|
|
81
|
+
const capResolved = path.resolve(cap)
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
found = true
|
|
97
|
-
break
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
Valid.assert(
|
|
103
|
-
found,
|
|
104
|
-
`The lineage of this directory must trace back to the cap '${cap}'.`,
|
|
105
|
-
)
|
|
106
|
-
|
|
107
|
-
dirPath = path.join(parentPath, name)
|
|
83
|
+
let targetPath
|
|
84
|
+
|
|
85
|
+
// If absolute, treat as virtual path relative to cap (strip leading /)
|
|
86
|
+
if(path.isAbsolute(dirPath)) {
|
|
87
|
+
const relative = dirPath.replace(/^[/\\]+/, "")
|
|
88
|
+
targetPath = relative ? path.join(capResolved, relative) : capResolved
|
|
89
|
+
} else {
|
|
90
|
+
// Relative path - resolve from parent directory
|
|
91
|
+
targetPath = FS.resolvePath(parentPath, dirPath)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Resolve to absolute path (handles .. and .)
|
|
95
|
+
const resolved = path.resolve(targetPath)
|
|
96
|
+
|
|
97
|
+
// Clamp to cap boundary - cannot escape above cap
|
|
98
|
+
if(!resolved.startsWith(capResolved)) {
|
|
99
|
+
// Path tried to escape - clamp to cap root
|
|
100
|
+
resolvedPath = capResolved
|
|
108
101
|
} else {
|
|
109
|
-
|
|
110
|
-
dirPath = path.join(cap, name)
|
|
102
|
+
resolvedPath = resolved
|
|
111
103
|
}
|
|
112
104
|
}
|
|
113
105
|
|
|
114
106
|
// Call parent constructor with the path
|
|
115
|
-
|
|
116
|
-
super(dirPath, temporary)
|
|
107
|
+
super(resolvedPath, temporary)
|
|
117
108
|
|
|
118
109
|
// Store the cap AFTER calling super()
|
|
119
110
|
this.#cap = cap
|
|
@@ -344,20 +335,8 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
344
335
|
!newPath.includes("..")
|
|
345
336
|
|
|
346
337
|
if(isSimpleName) {
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
// internally call super with the cap parameter
|
|
350
|
-
if(this.constructor === CappedDirectoryObject) {
|
|
351
|
-
return new CappedDirectoryObject(
|
|
352
|
-
newPath,
|
|
353
|
-
this.#cap,
|
|
354
|
-
this,
|
|
355
|
-
this.temporary
|
|
356
|
-
)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// For subclasses like TempDirectoryObject
|
|
360
|
-
return new this.constructor(newPath, this)
|
|
338
|
+
// Both CappedDirectoryObject and subclasses use same signature now
|
|
339
|
+
return new this.constructor(newPath, this, this.temporary)
|
|
361
340
|
}
|
|
362
341
|
|
|
363
342
|
// Complex path - handle coercion
|
|
@@ -407,7 +386,7 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
407
386
|
// Create a base CappedDirectoryObject at the cap path
|
|
408
387
|
// This works for direct usage of CappedDirectoryObject
|
|
409
388
|
// Subclasses may need to override if they have special semantics
|
|
410
|
-
return new CappedDirectoryObject(
|
|
389
|
+
return new CappedDirectoryObject(this.#cap, null, this.temporary)
|
|
411
390
|
}
|
|
412
391
|
|
|
413
392
|
/**
|
|
@@ -426,12 +405,7 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
426
405
|
// Traverse each segment, creating CappedDirectoryObject instances
|
|
427
406
|
// (not subclass instances, to avoid constructor signature issues)
|
|
428
407
|
for(const segment of segments) {
|
|
429
|
-
current = new CappedDirectoryObject(
|
|
430
|
-
segment,
|
|
431
|
-
this.#cap,
|
|
432
|
-
current,
|
|
433
|
-
this.temporary
|
|
434
|
-
)
|
|
408
|
+
current = new CappedDirectoryObject(segment, current, this.temporary)
|
|
435
409
|
}
|
|
436
410
|
|
|
437
411
|
return current
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs"
|
|
8
8
|
import os from "node:os"
|
|
9
|
+
import path from "node:path"
|
|
9
10
|
|
|
10
11
|
import CappedDirectoryObject from "./CappedDirectoryObject.js"
|
|
11
12
|
import Sass from "./Sass.js"
|
|
@@ -57,22 +58,63 @@ export default class TempDirectoryObject extends CappedDirectoryObject {
|
|
|
57
58
|
* await parent.remove() // Removes both parent and child
|
|
58
59
|
*/
|
|
59
60
|
constructor(name, parent=null) {
|
|
60
|
-
let
|
|
61
|
+
let dirPath
|
|
62
|
+
let cappedParent = parent
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
64
|
+
if(!parent) {
|
|
65
|
+
// No parent: need to create a capped parent at tmpdir first
|
|
66
|
+
cappedParent = new CappedDirectoryObject(os.tmpdir(), null, true)
|
|
67
|
+
|
|
68
|
+
if(name) {
|
|
69
|
+
// Check if name is a simple name (no separators, not absolute)
|
|
70
|
+
const isSimpleName = !path.isAbsolute(name) &&
|
|
71
|
+
!name.includes("/") &&
|
|
72
|
+
!name.includes("\\") &&
|
|
73
|
+
!name.includes(path.sep)
|
|
74
|
+
|
|
75
|
+
if(isSimpleName) {
|
|
76
|
+
// Simple name: add unique suffix
|
|
77
|
+
const prefix = name.endsWith("-") ? name : `${name}-`
|
|
78
|
+
const uniqueSuffix =
|
|
79
|
+
Math.random()
|
|
80
|
+
.toString(36)
|
|
81
|
+
.substring(2, 8)
|
|
82
|
+
.toUpperCase()
|
|
83
|
+
dirPath = `${prefix}${uniqueSuffix}`
|
|
84
|
+
} else {
|
|
85
|
+
// Complex path: use as-is, let CappedDirectoryObject handle coercion
|
|
86
|
+
dirPath = name
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// No name: use tmpdir itself (no parent)
|
|
90
|
+
dirPath = os.tmpdir()
|
|
91
|
+
cappedParent = null
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
// With parent: validate it's a proper temp directory parent
|
|
95
|
+
if(!(parent instanceof CappedDirectoryObject)) {
|
|
96
|
+
throw Sass.new(
|
|
97
|
+
"Parent must be a CappedDirectoryObject or TempDirectoryObject."
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// SECURITY: Ensure parent's cap is tmpdir (prevent escape to other caps)
|
|
102
|
+
const tmpdir = os.tmpdir()
|
|
103
|
+
if(parent.cap !== tmpdir) {
|
|
104
|
+
throw Sass.new(
|
|
105
|
+
`Parent must be capped to OS temp directory (${tmpdir}), ` +
|
|
106
|
+
`got cap: ${parent.cap}`
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
dirPath = name || ""
|
|
111
|
+
if(!dirPath) {
|
|
112
|
+
throw Sass.new("Name must not be empty when parent is provided.")
|
|
113
|
+
}
|
|
71
114
|
}
|
|
72
115
|
|
|
73
|
-
// Call parent constructor with
|
|
74
|
-
|
|
75
|
-
super(finalName, os.tmpdir(), parent, true)
|
|
116
|
+
// Call parent constructor with new signature
|
|
117
|
+
super(dirPath, cappedParent, true)
|
|
76
118
|
|
|
77
119
|
// Temp-specific behavior: create directory immediately
|
|
78
120
|
this.#createDirectory()
|
|
@@ -86,7 +128,8 @@ export default class TempDirectoryObject extends CappedDirectoryObject {
|
|
|
86
128
|
*/
|
|
87
129
|
#createDirectory() {
|
|
88
130
|
try {
|
|
89
|
-
|
|
131
|
+
// Use recursive: true to create parent directories as needed
|
|
132
|
+
fs.mkdirSync(this.realPath, {recursive: true})
|
|
90
133
|
} catch(e) {
|
|
91
134
|
// EEXIST is fine - directory already exists
|
|
92
135
|
if(e.code !== "EEXIST") {
|
|
@@ -11,21 +11,32 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
11
11
|
/**
|
|
12
12
|
* Constructs a CappedDirectoryObject instance.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* Without a parent, the path becomes both the directory location and the cap
|
|
15
|
+
* (virtual root). With a parent, the path is resolved relative to the parent's
|
|
16
|
+
* cap using virtual path semantics (absolute paths treated as cap-relative).
|
|
16
17
|
*
|
|
17
|
-
* @param {string
|
|
18
|
-
* @param {string} cap - The root path that constrains this directory tree
|
|
18
|
+
* @param {string} dirPath - Directory path (becomes cap if no parent, else relative to parent's cap)
|
|
19
19
|
* @param {CappedDirectoryObject?} [parent] - Optional parent capped directory
|
|
20
20
|
* @param {boolean} [temporary=false] - Whether this is a temporary directory
|
|
21
|
-
* @throws {Sass} If
|
|
22
|
-
* @throws {Sass} If
|
|
23
|
-
* @throws {Sass} If name contains path separators
|
|
24
|
-
* @throws {Sass} If parent is not a capped directory
|
|
25
|
-
* @throws {Sass} If parent's lineage does not trace back to the cap
|
|
21
|
+
* @throws {Sass} If path is empty
|
|
22
|
+
* @throws {Sass} If parent is provided but not a CappedDirectoryObject
|
|
26
23
|
* @throws {Sass} If the resulting path would escape the cap
|
|
24
|
+
* @example
|
|
25
|
+
* // Create new capped directory
|
|
26
|
+
* const cache = new CappedDirectoryObject("/home/user/.cache")
|
|
27
|
+
* // path: /home/user/.cache, cap: /home/user/.cache
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // Create subdirectory with parent
|
|
31
|
+
* const data = new CappedDirectoryObject("data", cache)
|
|
32
|
+
* // path: /home/user/.cache/data, cap: /home/user/.cache
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* // Virtual absolute path with parent
|
|
36
|
+
* const config = new CappedDirectoryObject("/etc/config", cache)
|
|
37
|
+
* // path: /home/user/.cache/etc/config, cap: /home/user/.cache
|
|
27
38
|
*/
|
|
28
|
-
constructor(
|
|
39
|
+
constructor(dirPath: string, parent?: CappedDirectoryObject | null, temporary?: boolean);
|
|
29
40
|
/**
|
|
30
41
|
* Returns the cap path for this directory.
|
|
31
42
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CappedDirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/CappedDirectoryObject.js"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH;IAGE
|
|
1
|
+
{"version":3,"file":"CappedDirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/CappedDirectoryObject.js"],"names":[],"mappings":"AAkBA;;;;;;;;GAQG;AACH;IAGE;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,qBArBW,MAAM,WACN,qBAAqB,OAAC,cACtB,OAAO,EA0EjB;IAqBD;;;;OAIG;IACH,WAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,cAFa,OAAO,CAInB;IAED;;;;;OAKG;IACH,0BAFa,MAAM,CAIlB;IAqCD;;;;;;;;;;;;;;;;;OAiBG;IACH,YAba,eAAe,CAe3B;IA2ED;;;;OAIG;IACH,cAFa,SAAS,CAAC,eAAe,CAAC,CAItC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,sBAjBW,MAAM,GACJ,qBAAqB,CAiEjC;IAqID;;;;;OAKG;IACH,WAHW,MAAM,GACJ,OAAO,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAAC,WAAW,QAAO;KAAC,CAAC,CAanE;;CAoDF;4BApjB2B,sBAAsB;uBAC3B,iBAAiB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TempDirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/TempDirectoryObject.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"TempDirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/TempDirectoryObject.js"],"names":[],"mappings":"AAaA;;;;;;;;;GASG;AACH;IAEE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,mBAxBW,MAAM,OAAC,WACP,mBAAmB,OAAC,EAoF9B;IAsBD;;;;;;;;;;;;;;OAcG;IACH,sBAVW,MAAM,GACJ,mBAAmB,CAY/B;IAED;;;;;;;;;;;;;;OAcG;IACH,kBAVW,MAAM,GACJ,UAAU,CAYtB;;CAUF;kCApLiC,4BAA4B"}
|