@gesslar/toolkit 2.8.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 +1 -1
- package/src/lib/CappedDirectoryObject.js +376 -103
- package/src/lib/FileObject.js +51 -10
- package/src/lib/TempDirectoryObject.js +58 -15
- package/src/types/lib/CappedDirectoryObject.d.ts +74 -16
- package/src/types/lib/CappedDirectoryObject.d.ts.map +1 -1
- package/src/types/lib/FileObject.d.ts +21 -2
- package/src/types/lib/FileObject.d.ts.map +1 -1
- package/src/types/lib/TempDirectoryObject.d.ts.map +1 -1
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ import path from "node:path"
|
|
|
13
13
|
import {Data, Valid} from "../browser/index.js"
|
|
14
14
|
import DirectoryObject from "./DirectoryObject.js"
|
|
15
15
|
import FileObject from "./FileObject.js"
|
|
16
|
+
import FS from "./FS.js"
|
|
16
17
|
import Sass from "./Sass.js"
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -30,88 +31,80 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
30
31
|
/**
|
|
31
32
|
* Constructs a CappedDirectoryObject instance.
|
|
32
33
|
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
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).
|
|
35
37
|
*
|
|
36
|
-
* @param {string
|
|
37
|
-
* @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)
|
|
38
39
|
* @param {CappedDirectoryObject?} [parent] - Optional parent capped directory
|
|
39
40
|
* @param {boolean} [temporary=false] - Whether this is a temporary directory
|
|
40
|
-
* @throws {Sass} If
|
|
41
|
-
* @throws {Sass} If
|
|
42
|
-
* @throws {Sass} If name contains path separators
|
|
43
|
-
* @throws {Sass} If parent is not a capped directory
|
|
44
|
-
* @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
|
|
45
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
|
|
46
58
|
*/
|
|
47
|
-
constructor(
|
|
48
|
-
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.")
|
|
49
62
|
|
|
50
63
|
// Validate parent using instanceof since TypeSpec doesn't understand inheritance
|
|
51
64
|
if(parent !== null && !(parent instanceof CappedDirectoryObject)) {
|
|
52
65
|
throw Sass.new(`Parent must be null or a CappedDirectoryObject instance, got ${Data.typeOf(parent)}`)
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
let
|
|
68
|
+
let cap
|
|
69
|
+
let resolvedPath
|
|
56
70
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
71
|
+
if(!parent) {
|
|
72
|
+
// No parent: dirPath becomes both the directory and the cap
|
|
73
|
+
cap = path.resolve(dirPath)
|
|
74
|
+
resolvedPath = cap
|
|
60
75
|
} else {
|
|
61
|
-
|
|
76
|
+
// With parent: inherit cap and resolve dirPath relative to it
|
|
77
|
+
cap = parent.#cap
|
|
62
78
|
|
|
63
|
-
//
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"Capped directory name must not be an absolute path.",
|
|
67
|
-
)
|
|
68
|
-
Valid.assert(
|
|
69
|
-
name.length > 0,
|
|
70
|
-
"Capped directory name must not be empty.",
|
|
71
|
-
)
|
|
72
|
-
Valid.assert(
|
|
73
|
-
!name.includes("/") && !name.includes("\\") && !name.includes(path.sep),
|
|
74
|
-
"Capped directory name must not contain path separators.",
|
|
75
|
-
)
|
|
79
|
+
// Use real path for filesystem operations
|
|
80
|
+
const parentPath = parent.realPath || parent.path
|
|
81
|
+
const capResolved = path.resolve(cap)
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
break
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
Valid.assert(
|
|
101
|
-
found,
|
|
102
|
-
`The lineage of this directory must trace back to the cap '${cap}'.`,
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
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
|
|
106
101
|
} else {
|
|
107
|
-
|
|
108
|
-
dirPath = path.join(cap, name)
|
|
102
|
+
resolvedPath = resolved
|
|
109
103
|
}
|
|
110
104
|
}
|
|
111
105
|
|
|
112
106
|
// Call parent constructor with the path
|
|
113
|
-
|
|
114
|
-
super(dirPath, temporary)
|
|
107
|
+
super(resolvedPath, temporary)
|
|
115
108
|
|
|
116
109
|
// Store the cap AFTER calling super()
|
|
117
110
|
this.#cap = cap
|
|
@@ -128,13 +121,13 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
128
121
|
*/
|
|
129
122
|
#validateCapPath() {
|
|
130
123
|
const cap = this.#cap
|
|
131
|
-
const resolved = path.resolve(this
|
|
124
|
+
const resolved = path.resolve(this.#realPath)
|
|
132
125
|
const capResolved = path.resolve(cap)
|
|
133
126
|
|
|
134
127
|
// Check if the resolved path starts with the cap directory
|
|
135
128
|
if(!resolved.startsWith(capResolved)) {
|
|
136
129
|
throw Sass.new(
|
|
137
|
-
`Path '${this
|
|
130
|
+
`Path '${this.#realPath}' is not within the cap directory '${cap}'`
|
|
138
131
|
)
|
|
139
132
|
}
|
|
140
133
|
}
|
|
@@ -157,6 +150,73 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
157
150
|
return true
|
|
158
151
|
}
|
|
159
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Returns the real filesystem path (for internal and subclass use).
|
|
155
|
+
*
|
|
156
|
+
* @protected
|
|
157
|
+
* @returns {string} The actual filesystem path
|
|
158
|
+
*/
|
|
159
|
+
get realPath() {
|
|
160
|
+
return super.path
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Private alias for realPath (for use in private methods).
|
|
165
|
+
*
|
|
166
|
+
* @private
|
|
167
|
+
* @returns {string} The actual filesystem path
|
|
168
|
+
*/
|
|
169
|
+
get #realPath() {
|
|
170
|
+
return this.realPath
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Returns the virtual path relative to the cap.
|
|
175
|
+
* This is the default path representation in the capped environment.
|
|
176
|
+
* Use `.real.path` to access the actual filesystem path.
|
|
177
|
+
*
|
|
178
|
+
* @returns {string} Path relative to cap, or "/" if at cap root
|
|
179
|
+
* @example
|
|
180
|
+
* const temp = new TempDirectoryObject("myapp")
|
|
181
|
+
* const subdir = temp.getDirectory("data/cache")
|
|
182
|
+
* console.log(subdir.path) // "/data/cache" (virtual, relative to cap)
|
|
183
|
+
* console.log(subdir.real.path) // "/tmp/myapp-ABC123/data/cache" (actual filesystem)
|
|
184
|
+
*/
|
|
185
|
+
get path() {
|
|
186
|
+
const capResolved = path.resolve(this.#cap)
|
|
187
|
+
const relative = path.relative(capResolved, this.#realPath)
|
|
188
|
+
|
|
189
|
+
// If at cap root or empty, return "/"
|
|
190
|
+
if(!relative || relative === ".") {
|
|
191
|
+
return "/"
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Return with leading slash to indicate it's cap-relative
|
|
195
|
+
return "/" + relative.split(path.sep).join("/")
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Returns a plain DirectoryObject representing the actual filesystem location.
|
|
200
|
+
* This provides an "escape hatch" from the capped environment to interact
|
|
201
|
+
* with the real filesystem when needed.
|
|
202
|
+
*
|
|
203
|
+
* @returns {DirectoryObject} Uncapped directory object at the real filesystem path
|
|
204
|
+
* @example
|
|
205
|
+
* const temp = new TempDirectoryObject("myapp")
|
|
206
|
+
* const subdir = temp.getDirectory("data")
|
|
207
|
+
*
|
|
208
|
+
* // Work within the capped environment (virtual paths)
|
|
209
|
+
* console.log(subdir.path) // "/data" (virtual)
|
|
210
|
+
* subdir.getFile("config.json") // Stays within cap
|
|
211
|
+
*
|
|
212
|
+
* // Break out to real filesystem when needed
|
|
213
|
+
* console.log(subdir.real.path) // "/tmp/myapp-ABC123/data" (real)
|
|
214
|
+
* subdir.real.parent // Can traverse outside the cap
|
|
215
|
+
*/
|
|
216
|
+
get real() {
|
|
217
|
+
return new DirectoryObject(this.#realPath)
|
|
218
|
+
}
|
|
219
|
+
|
|
160
220
|
/**
|
|
161
221
|
* Returns the parent directory of this capped directory.
|
|
162
222
|
* Returns null only if this directory is at the cap (the "root" of the capped tree).
|
|
@@ -175,12 +235,17 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
175
235
|
const capResolved = path.resolve(this.#cap)
|
|
176
236
|
|
|
177
237
|
// If we're at the cap, return null (cap is the "root")
|
|
178
|
-
if(this
|
|
238
|
+
if(this.#realPath === capResolved) {
|
|
179
239
|
return null
|
|
180
240
|
}
|
|
181
241
|
|
|
182
|
-
// Otherwise return the parent (plain DirectoryObject, not capped)
|
|
183
|
-
|
|
242
|
+
// Otherwise return the parent using real path (plain DirectoryObject, not capped)
|
|
243
|
+
const parentPath = path.dirname(this.#realPath)
|
|
244
|
+
const isRoot = parentPath === this.#realPath
|
|
245
|
+
|
|
246
|
+
return isRoot
|
|
247
|
+
? null
|
|
248
|
+
: new DirectoryObject(parentPath, this.temporary)
|
|
184
249
|
}
|
|
185
250
|
|
|
186
251
|
/**
|
|
@@ -201,19 +266,27 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
201
266
|
*#walkUpCapped() {
|
|
202
267
|
const capResolved = path.resolve(this.#cap)
|
|
203
268
|
|
|
204
|
-
//
|
|
205
|
-
|
|
269
|
+
// Build trail from real path
|
|
270
|
+
const trail = this.#realPath.split(path.sep).filter(Boolean)
|
|
271
|
+
const curr = [...trail]
|
|
272
|
+
|
|
273
|
+
while(curr.length > 0) {
|
|
274
|
+
const joined = path.sep + curr.join(path.sep)
|
|
275
|
+
|
|
206
276
|
// Don't yield anything beyond the cap
|
|
207
|
-
if(!
|
|
277
|
+
if(!joined.startsWith(capResolved)) {
|
|
208
278
|
break
|
|
209
279
|
}
|
|
210
280
|
|
|
211
|
-
|
|
281
|
+
// Yield plain DirectoryObject with real path
|
|
282
|
+
yield new DirectoryObject(joined, this.temporary)
|
|
212
283
|
|
|
213
284
|
// Stop after yielding the cap
|
|
214
|
-
if(
|
|
285
|
+
if(joined === capResolved) {
|
|
215
286
|
break
|
|
216
287
|
}
|
|
288
|
+
|
|
289
|
+
curr.pop()
|
|
217
290
|
}
|
|
218
291
|
}
|
|
219
292
|
|
|
@@ -229,69 +302,269 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
229
302
|
/**
|
|
230
303
|
* Creates a new CappedDirectoryObject by extending this directory's path.
|
|
231
304
|
*
|
|
232
|
-
*
|
|
305
|
+
* All paths are coerced to remain within the cap directory tree:
|
|
306
|
+
* - Absolute paths (e.g., "/foo") are treated as relative to the cap
|
|
307
|
+
* - Parent traversal ("..") is allowed but clamped at the cap boundary
|
|
308
|
+
* - The cap acts as the virtual root directory
|
|
233
309
|
*
|
|
234
|
-
* @param {string} newPath - The path
|
|
235
|
-
* @returns {CappedDirectoryObject} A new CappedDirectoryObject with the
|
|
236
|
-
* @throws {Sass} If the path would escape the cap directory
|
|
237
|
-
* @throws {Sass} If the path is absolute
|
|
238
|
-
* @throws {Sass} If the path contains traversal (..)
|
|
310
|
+
* @param {string} newPath - The path to resolve (can be absolute or contain ..)
|
|
311
|
+
* @returns {CappedDirectoryObject} A new CappedDirectoryObject with the coerced path
|
|
239
312
|
* @example
|
|
240
313
|
* const capped = new TempDirectoryObject("myapp")
|
|
241
314
|
* const subDir = capped.getDirectory("data")
|
|
242
315
|
* console.log(subDir.path) // "/tmp/myapp-ABC123/data"
|
|
316
|
+
*
|
|
317
|
+
* @example
|
|
318
|
+
* // Absolute paths are relative to cap
|
|
319
|
+
* const abs = capped.getDirectory("/foo/bar")
|
|
320
|
+
* console.log(abs.path) // "/tmp/myapp-ABC123/foo/bar"
|
|
321
|
+
*
|
|
322
|
+
* @example
|
|
323
|
+
* // Excessive .. traversal clamps to cap
|
|
324
|
+
* const up = capped.getDirectory("../../../etc/passwd")
|
|
325
|
+
* console.log(up.path) // "/tmp/myapp-ABC123" (clamped to cap)
|
|
243
326
|
*/
|
|
244
327
|
getDirectory(newPath) {
|
|
245
328
|
Valid.type(newPath, "String")
|
|
246
329
|
|
|
247
|
-
//
|
|
330
|
+
// Fast path: if it's a simple name (no separators, not absolute, no ..)
|
|
331
|
+
// use the subclass constructor directly to preserve type
|
|
332
|
+
const isSimpleName = !path.isAbsolute(newPath) &&
|
|
333
|
+
!newPath.includes("/") &&
|
|
334
|
+
!newPath.includes("\\") &&
|
|
335
|
+
!newPath.includes("..")
|
|
336
|
+
|
|
337
|
+
if(isSimpleName) {
|
|
338
|
+
// Both CappedDirectoryObject and subclasses use same signature now
|
|
339
|
+
return new this.constructor(newPath, this, this.temporary)
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Complex path - handle coercion
|
|
343
|
+
const capResolved = path.resolve(this.#cap)
|
|
344
|
+
let targetPath
|
|
345
|
+
|
|
346
|
+
// If absolute, treat as relative to cap (virtual root)
|
|
248
347
|
if(path.isAbsolute(newPath)) {
|
|
249
|
-
|
|
348
|
+
// Strip leading slashes to make relative
|
|
349
|
+
const relative = newPath.replace(/^[/\\]+/, "")
|
|
350
|
+
|
|
351
|
+
// Join with cap (unless empty, which means cap root)
|
|
352
|
+
targetPath = relative ? path.join(capResolved, relative) : capResolved
|
|
353
|
+
} else {
|
|
354
|
+
// Relative path - resolve from current directory
|
|
355
|
+
targetPath = FS.resolvePath(this.#realPath, newPath)
|
|
250
356
|
}
|
|
251
357
|
|
|
252
|
-
//
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
358
|
+
// Resolve to absolute path (handles .. and .)
|
|
359
|
+
const resolved = path.resolve(targetPath)
|
|
360
|
+
|
|
361
|
+
// Coerce: if path escaped cap, clamp to cap boundary
|
|
362
|
+
const coerced = resolved.startsWith(capResolved)
|
|
363
|
+
? resolved
|
|
364
|
+
: capResolved
|
|
365
|
+
|
|
366
|
+
// Compute path relative to cap for reconstruction
|
|
367
|
+
const relativeToCap = path.relative(capResolved, coerced)
|
|
368
|
+
|
|
369
|
+
// If we're at the cap root, return cap root directory
|
|
370
|
+
if(!relativeToCap || relativeToCap === ".") {
|
|
371
|
+
return this.#createCappedAtRoot()
|
|
256
372
|
}
|
|
257
373
|
|
|
258
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
374
|
+
// Build directory by traversing segments from cap
|
|
375
|
+
return this.#buildDirectoryFromRelativePath(relativeToCap)
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Creates a CappedDirectoryObject at the cap root.
|
|
380
|
+
* Can be overridden by subclasses that have different root semantics.
|
|
381
|
+
*
|
|
382
|
+
* @private
|
|
383
|
+
* @returns {CappedDirectoryObject} Directory object at cap root
|
|
384
|
+
*/
|
|
385
|
+
#createCappedAtRoot() {
|
|
386
|
+
// Create a base CappedDirectoryObject at the cap path
|
|
387
|
+
// This works for direct usage of CappedDirectoryObject
|
|
388
|
+
// Subclasses may need to override if they have special semantics
|
|
389
|
+
return new CappedDirectoryObject(this.#cap, null, this.temporary)
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Builds a directory by traversing path segments from cap.
|
|
394
|
+
*
|
|
395
|
+
* @private
|
|
396
|
+
* @param {string} relativePath - Path relative to cap
|
|
397
|
+
* @returns {CappedDirectoryObject} The directory at the final path
|
|
398
|
+
*/
|
|
399
|
+
#buildDirectoryFromRelativePath(relativePath) {
|
|
400
|
+
const segments = relativePath.split(path.sep).filter(Boolean)
|
|
401
|
+
|
|
402
|
+
// Start at cap root
|
|
403
|
+
let current = this.#createCappedAtRoot()
|
|
404
|
+
|
|
405
|
+
// Traverse each segment, creating CappedDirectoryObject instances
|
|
406
|
+
// (not subclass instances, to avoid constructor signature issues)
|
|
407
|
+
for(const segment of segments) {
|
|
408
|
+
current = new CappedDirectoryObject(segment, current, this.temporary)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return current
|
|
261
412
|
}
|
|
262
413
|
|
|
263
414
|
/**
|
|
264
415
|
* Creates a new FileObject by extending this directory's path.
|
|
265
416
|
*
|
|
266
|
-
*
|
|
417
|
+
* All paths are coerced to remain within the cap directory tree:
|
|
418
|
+
* - Absolute paths (e.g., "/config.json") are treated as relative to the cap
|
|
419
|
+
* - Parent traversal ("..") is allowed but clamped at the cap boundary
|
|
420
|
+
* - The cap acts as the virtual root directory
|
|
267
421
|
*
|
|
268
|
-
* @param {string} filename - The filename to
|
|
269
|
-
* @returns {FileObject} A new FileObject with the
|
|
270
|
-
* @throws {Sass} If the path would escape the cap directory
|
|
271
|
-
* @throws {Sass} If the path is absolute
|
|
272
|
-
* @throws {Sass} If the path contains traversal (..)
|
|
422
|
+
* @param {string} filename - The filename to resolve (can be absolute or contain ..)
|
|
423
|
+
* @returns {FileObject} A new FileObject with the coerced path
|
|
273
424
|
* @example
|
|
274
425
|
* const capped = new TempDirectoryObject("myapp")
|
|
275
426
|
* const file = capped.getFile("config.json")
|
|
276
427
|
* console.log(file.path) // "/tmp/myapp-ABC123/config.json"
|
|
428
|
+
*
|
|
429
|
+
* @example
|
|
430
|
+
* // Absolute paths are relative to cap
|
|
431
|
+
* const abs = capped.getFile("/data/config.json")
|
|
432
|
+
* console.log(abs.path) // "/tmp/myapp-ABC123/data/config.json"
|
|
433
|
+
*
|
|
434
|
+
* @example
|
|
435
|
+
* // Excessive .. traversal clamps to cap
|
|
436
|
+
* const up = capped.getFile("../../../etc/passwd")
|
|
437
|
+
* console.log(up.path) // "/tmp/myapp-ABC123/passwd" (clamped to cap)
|
|
277
438
|
*/
|
|
278
439
|
getFile(filename) {
|
|
279
440
|
Valid.type(filename, "String")
|
|
280
441
|
|
|
281
|
-
//
|
|
442
|
+
// Fast path: if it's a simple filename (no separators, not absolute, no ..)
|
|
443
|
+
// use this as the parent directly
|
|
444
|
+
const isSimpleName = !path.isAbsolute(filename) &&
|
|
445
|
+
!filename.includes("/") &&
|
|
446
|
+
!filename.includes("\\") &&
|
|
447
|
+
!filename.includes("..")
|
|
448
|
+
|
|
449
|
+
if(isSimpleName) {
|
|
450
|
+
// Simple filename - create directly with this as parent
|
|
451
|
+
return new FileObject(filename, this)
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Complex path - handle coercion
|
|
455
|
+
const capResolved = path.resolve(this.#cap)
|
|
456
|
+
let targetPath
|
|
457
|
+
|
|
458
|
+
// If absolute, treat as relative to cap (virtual root)
|
|
282
459
|
if(path.isAbsolute(filename)) {
|
|
283
|
-
|
|
460
|
+
// Strip leading slashes to make relative
|
|
461
|
+
const relative = filename.replace(/^[/\\]+/, "")
|
|
462
|
+
|
|
463
|
+
// Join with cap
|
|
464
|
+
targetPath = path.join(capResolved, relative)
|
|
465
|
+
} else {
|
|
466
|
+
// Relative path - resolve from current directory
|
|
467
|
+
targetPath = FS.resolvePath(this.#realPath, filename)
|
|
284
468
|
}
|
|
285
469
|
|
|
286
|
-
//
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
470
|
+
// Resolve to absolute path (handles .. and .)
|
|
471
|
+
const resolved = path.resolve(targetPath)
|
|
472
|
+
|
|
473
|
+
// Coerce: if path escaped cap, clamp to cap boundary
|
|
474
|
+
const coerced = resolved.startsWith(capResolved)
|
|
475
|
+
? resolved
|
|
476
|
+
: capResolved
|
|
477
|
+
|
|
478
|
+
// Extract directory and filename parts
|
|
479
|
+
let fileDir = path.dirname(coerced)
|
|
480
|
+
let fileBasename = path.basename(coerced)
|
|
481
|
+
|
|
482
|
+
// Special case: if coerced is exactly the cap (file tried to escape),
|
|
483
|
+
// the file should be placed at the cap root with just the filename
|
|
484
|
+
if(coerced === capResolved) {
|
|
485
|
+
// Extract just the filename from the original path
|
|
486
|
+
fileBasename = path.basename(resolved)
|
|
487
|
+
fileDir = capResolved
|
|
290
488
|
}
|
|
291
489
|
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
490
|
+
// Get or create the parent directory
|
|
491
|
+
const relativeToCap = path.relative(capResolved, fileDir)
|
|
492
|
+
const parentDir = !relativeToCap || relativeToCap === "."
|
|
493
|
+
? this.#createCappedAtRoot()
|
|
494
|
+
: this.#buildDirectoryFromRelativePath(relativeToCap)
|
|
495
|
+
|
|
496
|
+
// Create FileObject with parent directory
|
|
497
|
+
return new FileObject(fileBasename, parentDir)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Override exists to use real filesystem path.
|
|
502
|
+
*
|
|
503
|
+
* @returns {Promise<boolean>} Whether the directory exists
|
|
504
|
+
*/
|
|
505
|
+
get exists() {
|
|
506
|
+
return this.real.exists
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/**
|
|
510
|
+
* Override read to use real filesystem path and return capped objects.
|
|
511
|
+
*
|
|
512
|
+
* @param {string} [pat=""] - Optional glob pattern
|
|
513
|
+
* @returns {Promise<{files: Array<FileObject>, directories: Array}>} Directory contents
|
|
514
|
+
*/
|
|
515
|
+
async read(pat="") {
|
|
516
|
+
const {files, directories} = await this.real.read(pat)
|
|
517
|
+
|
|
518
|
+
// Convert plain DirectoryObjects to CappedDirectoryObjects with same cap
|
|
519
|
+
const cappedDirectories = directories.map(dir => {
|
|
520
|
+
const name = dir.name
|
|
521
|
+
|
|
522
|
+
return new this.constructor(name, this)
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
return {files, directories: cappedDirectories}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* Override assureExists to use real filesystem path.
|
|
530
|
+
*
|
|
531
|
+
* @param {object} [options] - Options for mkdir
|
|
532
|
+
* @returns {Promise<void>}
|
|
533
|
+
*/
|
|
534
|
+
async assureExists(options = {}) {
|
|
535
|
+
return await this.real.assureExists(options)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Override delete to use real filesystem path.
|
|
540
|
+
*
|
|
541
|
+
* @returns {Promise<void>}
|
|
542
|
+
*/
|
|
543
|
+
async delete() {
|
|
544
|
+
return await this.real.delete()
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Override remove to preserve temporary flag check.
|
|
549
|
+
*
|
|
550
|
+
* @returns {Promise<void>}
|
|
551
|
+
*/
|
|
552
|
+
async remove() {
|
|
553
|
+
if(!this.temporary)
|
|
554
|
+
throw Sass.new("This is not a temporary directory.")
|
|
555
|
+
|
|
556
|
+
const {files, directories} = await this.read()
|
|
557
|
+
|
|
558
|
+
// Remove subdirectories recursively
|
|
559
|
+
for(const dir of directories)
|
|
560
|
+
await dir.remove()
|
|
561
|
+
|
|
562
|
+
// Remove files
|
|
563
|
+
for(const file of files)
|
|
564
|
+
await file.delete()
|
|
565
|
+
|
|
566
|
+
// Delete the now-empty directory
|
|
567
|
+
await this.delete()
|
|
295
568
|
}
|
|
296
569
|
|
|
297
570
|
/**
|
|
@@ -300,6 +573,6 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
300
573
|
* @returns {string} string representation of the CappedDirectoryObject
|
|
301
574
|
*/
|
|
302
575
|
toString() {
|
|
303
|
-
return `[CappedDirectoryObject: ${this.path}]`
|
|
576
|
+
return `[CappedDirectoryObject: ${this.path} (real: ${this.#realPath})]`
|
|
304
577
|
}
|
|
305
578
|
}
|
package/src/lib/FileObject.js
CHANGED
|
@@ -96,6 +96,7 @@ export default class FileObject extends FS {
|
|
|
96
96
|
case "String":
|
|
97
97
|
return new DirectoryObject(parent)
|
|
98
98
|
case "DirectoryObject":
|
|
99
|
+
case "CappedDirectoryObject":
|
|
99
100
|
case "TempDirectoryObject":
|
|
100
101
|
return parent
|
|
101
102
|
default:
|
|
@@ -103,7 +104,9 @@ export default class FileObject extends FS {
|
|
|
103
104
|
}
|
|
104
105
|
})()
|
|
105
106
|
|
|
106
|
-
|
|
107
|
+
// Use real path if parent is capped, otherwise use path
|
|
108
|
+
const parentPath = parentObject.realPath || parentObject.path
|
|
109
|
+
const final = FS.resolvePath(parentPath ?? ".", fixedFile)
|
|
107
110
|
|
|
108
111
|
const resolved = final
|
|
109
112
|
const url = new URL(FS.pathToUri(resolved))
|
|
@@ -113,7 +116,9 @@ export default class FileObject extends FS {
|
|
|
113
116
|
|
|
114
117
|
// If the file is directly in the provided parent directory, reuse that object
|
|
115
118
|
// Otherwise, create a DirectoryObject for the actual parent directory
|
|
116
|
-
|
|
119
|
+
// Use real path for comparison if parent is capped
|
|
120
|
+
const parentRealPath = parentObject.realPath || parentObject.path
|
|
121
|
+
const actualParent = parentObject && actualParentPath === parentRealPath
|
|
117
122
|
? parentObject
|
|
118
123
|
: new DirectoryObject(actualParentPath)
|
|
119
124
|
|
|
@@ -184,12 +189,28 @@ export default class FileObject extends FS {
|
|
|
184
189
|
}
|
|
185
190
|
|
|
186
191
|
/**
|
|
187
|
-
*
|
|
192
|
+
* Returns the file path. If the parent is a capped directory, returns the
|
|
193
|
+
* virtual path relative to the cap. Otherwise returns the real filesystem path.
|
|
194
|
+
* Use `.real.path` to always get the actual filesystem path.
|
|
188
195
|
*
|
|
189
|
-
* @returns {string} The
|
|
196
|
+
* @returns {string} The file path (virtual if parent is capped, real otherwise)
|
|
190
197
|
*/
|
|
191
198
|
get path() {
|
|
192
|
-
|
|
199
|
+
const realPath = this.#meta.path
|
|
200
|
+
const parent = this.#meta.parent
|
|
201
|
+
|
|
202
|
+
// If parent is capped, return virtual path
|
|
203
|
+
if(parent?.capped) {
|
|
204
|
+
const cap = parent.cap
|
|
205
|
+
const capResolved = path.resolve(cap)
|
|
206
|
+
const relative = path.relative(capResolved, realPath)
|
|
207
|
+
|
|
208
|
+
// Return with leading slash to indicate it's cap-relative
|
|
209
|
+
return "/" + relative.split(path.sep).join("/")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Otherwise return real path
|
|
213
|
+
return realPath
|
|
193
214
|
}
|
|
194
215
|
|
|
195
216
|
/**
|
|
@@ -264,6 +285,26 @@ export default class FileObject extends FS {
|
|
|
264
285
|
return this.#meta.parent
|
|
265
286
|
}
|
|
266
287
|
|
|
288
|
+
/**
|
|
289
|
+
* Returns a plain FileObject representing the actual filesystem location.
|
|
290
|
+
* This provides an "escape hatch" when working with capped directories,
|
|
291
|
+
* allowing direct filesystem access when needed.
|
|
292
|
+
*
|
|
293
|
+
* @returns {FileObject} Uncapped file object at the real filesystem path
|
|
294
|
+
* @example
|
|
295
|
+
* const temp = new TempDirectoryObject("myapp")
|
|
296
|
+
* const file = temp.getFile("/config/app.json")
|
|
297
|
+
*
|
|
298
|
+
* // file.path shows virtual path
|
|
299
|
+
* console.log(file.path) // "/config/app.json"
|
|
300
|
+
* // file.real.path shows actual filesystem path
|
|
301
|
+
* console.log(file.real.path) // "/tmp/myapp-ABC123/config/app.json"
|
|
302
|
+
* file.real.parent.parent // Can traverse outside the cap
|
|
303
|
+
*/
|
|
304
|
+
get real() {
|
|
305
|
+
return new FileObject(this.#meta.path)
|
|
306
|
+
}
|
|
307
|
+
|
|
267
308
|
/**
|
|
268
309
|
* Check if a file can be read. Returns true if the file can be read, false
|
|
269
310
|
*
|
|
@@ -271,7 +312,7 @@ export default class FileObject extends FS {
|
|
|
271
312
|
*/
|
|
272
313
|
async canRead() {
|
|
273
314
|
try {
|
|
274
|
-
await fs.access(this.path, fs.constants.R_OK)
|
|
315
|
+
await fs.access(this.#meta.path, fs.constants.R_OK)
|
|
275
316
|
|
|
276
317
|
return true
|
|
277
318
|
} catch(_) {
|
|
@@ -286,7 +327,7 @@ export default class FileObject extends FS {
|
|
|
286
327
|
*/
|
|
287
328
|
async canWrite() {
|
|
288
329
|
try {
|
|
289
|
-
await fs.access(this.path, fs.constants.W_OK)
|
|
330
|
+
await fs.access(this.#meta.path, fs.constants.W_OK)
|
|
290
331
|
|
|
291
332
|
return true
|
|
292
333
|
} catch(_) {
|
|
@@ -301,7 +342,7 @@ export default class FileObject extends FS {
|
|
|
301
342
|
*/
|
|
302
343
|
async #fileExists() {
|
|
303
344
|
try {
|
|
304
|
-
await fs.access(this.path, fs.constants.F_OK)
|
|
345
|
+
await fs.access(this.#meta.path, fs.constants.F_OK)
|
|
305
346
|
|
|
306
347
|
return true
|
|
307
348
|
} catch(_) {
|
|
@@ -316,7 +357,7 @@ export default class FileObject extends FS {
|
|
|
316
357
|
*/
|
|
317
358
|
async size() {
|
|
318
359
|
try {
|
|
319
|
-
const stat = await fs.stat(this.path)
|
|
360
|
+
const stat = await fs.stat(this.#meta.path)
|
|
320
361
|
|
|
321
362
|
return stat.size
|
|
322
363
|
} catch(_) {
|
|
@@ -332,7 +373,7 @@ export default class FileObject extends FS {
|
|
|
332
373
|
*/
|
|
333
374
|
async modified() {
|
|
334
375
|
try {
|
|
335
|
-
const stat = await fs.stat(this.path)
|
|
376
|
+
const stat = await fs.stat(this.#meta.path)
|
|
336
377
|
|
|
337
378
|
return stat.mtime
|
|
338
379
|
} catch(_) {
|
|
@@ -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,12 +128,13 @@ 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") {
|
|
93
136
|
throw Sass.new(
|
|
94
|
-
`Unable to create temporary directory '${this.
|
|
137
|
+
`Unable to create temporary directory '${this.realPath}': ${e.message}`
|
|
95
138
|
)
|
|
96
139
|
}
|
|
97
140
|
}
|
|
@@ -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
|
*
|
|
@@ -38,6 +49,32 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
38
49
|
* @returns {boolean} Always true for CappedDirectoryObject instances
|
|
39
50
|
*/
|
|
40
51
|
get capped(): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Returns the real filesystem path (for internal and subclass use).
|
|
54
|
+
*
|
|
55
|
+
* @protected
|
|
56
|
+
* @returns {string} The actual filesystem path
|
|
57
|
+
*/
|
|
58
|
+
protected get realPath(): string;
|
|
59
|
+
/**
|
|
60
|
+
* Returns a plain DirectoryObject representing the actual filesystem location.
|
|
61
|
+
* This provides an "escape hatch" from the capped environment to interact
|
|
62
|
+
* with the real filesystem when needed.
|
|
63
|
+
*
|
|
64
|
+
* @returns {DirectoryObject} Uncapped directory object at the real filesystem path
|
|
65
|
+
* @example
|
|
66
|
+
* const temp = new TempDirectoryObject("myapp")
|
|
67
|
+
* const subdir = temp.getDirectory("data")
|
|
68
|
+
*
|
|
69
|
+
* // Work within the capped environment (virtual paths)
|
|
70
|
+
* console.log(subdir.path) // "/data" (virtual)
|
|
71
|
+
* subdir.getFile("config.json") // Stays within cap
|
|
72
|
+
*
|
|
73
|
+
* // Break out to real filesystem when needed
|
|
74
|
+
* console.log(subdir.real.path) // "/tmp/myapp-ABC123/data" (real)
|
|
75
|
+
* subdir.real.parent // Can traverse outside the cap
|
|
76
|
+
*/
|
|
77
|
+
get real(): DirectoryObject;
|
|
41
78
|
/**
|
|
42
79
|
* Returns a generator that walks up to the cap.
|
|
43
80
|
*
|
|
@@ -47,20 +84,41 @@ export default class CappedDirectoryObject extends DirectoryObject {
|
|
|
47
84
|
/**
|
|
48
85
|
* Creates a new CappedDirectoryObject by extending this directory's path.
|
|
49
86
|
*
|
|
50
|
-
*
|
|
87
|
+
* All paths are coerced to remain within the cap directory tree:
|
|
88
|
+
* - Absolute paths (e.g., "/foo") are treated as relative to the cap
|
|
89
|
+
* - Parent traversal ("..") is allowed but clamped at the cap boundary
|
|
90
|
+
* - The cap acts as the virtual root directory
|
|
51
91
|
*
|
|
52
|
-
* @param {string} newPath - The path
|
|
53
|
-
* @returns {CappedDirectoryObject} A new CappedDirectoryObject with the
|
|
54
|
-
* @throws {Sass} If the path would escape the cap directory
|
|
55
|
-
* @throws {Sass} If the path is absolute
|
|
56
|
-
* @throws {Sass} If the path contains traversal (..)
|
|
92
|
+
* @param {string} newPath - The path to resolve (can be absolute or contain ..)
|
|
93
|
+
* @returns {CappedDirectoryObject} A new CappedDirectoryObject with the coerced path
|
|
57
94
|
* @example
|
|
58
95
|
* const capped = new TempDirectoryObject("myapp")
|
|
59
96
|
* const subDir = capped.getDirectory("data")
|
|
60
97
|
* console.log(subDir.path) // "/tmp/myapp-ABC123/data"
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* // Absolute paths are relative to cap
|
|
101
|
+
* const abs = capped.getDirectory("/foo/bar")
|
|
102
|
+
* console.log(abs.path) // "/tmp/myapp-ABC123/foo/bar"
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* // Excessive .. traversal clamps to cap
|
|
106
|
+
* const up = capped.getDirectory("../../../etc/passwd")
|
|
107
|
+
* console.log(up.path) // "/tmp/myapp-ABC123" (clamped to cap)
|
|
61
108
|
*/
|
|
62
109
|
getDirectory(newPath: string): CappedDirectoryObject;
|
|
110
|
+
/**
|
|
111
|
+
* Override read to use real filesystem path and return capped objects.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} [pat=""] - Optional glob pattern
|
|
114
|
+
* @returns {Promise<{files: Array<FileObject>, directories: Array}>} Directory contents
|
|
115
|
+
*/
|
|
116
|
+
read(pat?: string): Promise<{
|
|
117
|
+
files: Array<FileObject>;
|
|
118
|
+
directories: any[];
|
|
119
|
+
}>;
|
|
63
120
|
#private;
|
|
64
121
|
}
|
|
65
122
|
import DirectoryObject from "./DirectoryObject.js";
|
|
123
|
+
import FileObject from "./FileObject.js";
|
|
66
124
|
//# sourceMappingURL=CappedDirectoryObject.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CappedDirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/CappedDirectoryObject.js"],"names":[],"mappings":"
|
|
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"}
|
|
@@ -50,9 +50,11 @@ export default class FileObject extends FS {
|
|
|
50
50
|
*/
|
|
51
51
|
get supplied(): string;
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* Returns the file path. If the parent is a capped directory, returns the
|
|
54
|
+
* virtual path relative to the cap. Otherwise returns the real filesystem path.
|
|
55
|
+
* Use `.real.path` to always get the actual filesystem path.
|
|
54
56
|
*
|
|
55
|
-
* @returns {string} The
|
|
57
|
+
* @returns {string} The file path (virtual if parent is capped, real otherwise)
|
|
56
58
|
*/
|
|
57
59
|
get path(): string;
|
|
58
60
|
/**
|
|
@@ -107,6 +109,23 @@ export default class FileObject extends FS {
|
|
|
107
109
|
* @returns {DirectoryObject} The parent directory object
|
|
108
110
|
*/
|
|
109
111
|
get parent(): DirectoryObject;
|
|
112
|
+
/**
|
|
113
|
+
* Returns a plain FileObject representing the actual filesystem location.
|
|
114
|
+
* This provides an "escape hatch" when working with capped directories,
|
|
115
|
+
* allowing direct filesystem access when needed.
|
|
116
|
+
*
|
|
117
|
+
* @returns {FileObject} Uncapped file object at the real filesystem path
|
|
118
|
+
* @example
|
|
119
|
+
* const temp = new TempDirectoryObject("myapp")
|
|
120
|
+
* const file = temp.getFile("/config/app.json")
|
|
121
|
+
*
|
|
122
|
+
* // file.path shows virtual path
|
|
123
|
+
* console.log(file.path) // "/config/app.json"
|
|
124
|
+
* // file.real.path shows actual filesystem path
|
|
125
|
+
* console.log(file.real.path) // "/tmp/myapp-ABC123/config/app.json"
|
|
126
|
+
* file.real.parent.parent // Can traverse outside the cap
|
|
127
|
+
*/
|
|
128
|
+
get real(): FileObject;
|
|
110
129
|
/**
|
|
111
130
|
* Check if a file can be read. Returns true if the file can be read, false
|
|
112
131
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FileObject.d.ts","sourceRoot":"","sources":["../../lib/FileObject.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;GAcG;AAEH;
|
|
1
|
+
{"version":3,"file":"FileObject.d.ts","sourceRoot":"","sources":["../../lib/FileObject.js"],"names":[],"mappings":"AAmBA;;;;;;;;;;;;;;GAcG;AAEH;uBAmIe,MAAM;IAlInB;;;;;OAKG;IACH,yBAFU;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAAC,OAAO,KAAK,GAAG,OAAO,IAAI,CAAC,CAAA;KAAC,CAO1D;IA2BF;;;;;OAKG;IACH,sBAHW,MAAM,GAAG,UAAU,WACnB,eAAe,GAAC,MAAM,GAAC,IAAI,EAuDrC;IAWD;;;;OAIG;IACH,UAFa,MAAM,CAclB;IAWD;;;;OAIG;IACH,cAFa,OAAO,CAAC,OAAO,CAAC,CAI5B;IAED;;;;OAIG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,YAFa,MAAM,CAkBlB;IAED;;;;OAIG;IACH,WAFa,GAAG,CAIf;IAED;;;;OAIG;IACH,YAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,cAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,iBAFa,MAAM,CAIlB;IACD;;;;OAIG;IACH,cAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,mBAFa,OAAO,CAInB;IAED;;;;;;;;;;;;;;OAcG;IACH,cAFa,eAAe,CAI3B;IAED;;;;;;;;;;;;;;;OAeG;IACH,YAXa,UAAU,CAatB;IAED;;;;OAIG;IACH,WAFa,OAAO,CAAC,OAAO,CAAC,CAU5B;IAED;;;;OAIG;IACH,YAFa,OAAO,CAAC,OAAO,CAAC,CAU5B;IAiBD;;;;OAIG;IACH,QAFa,OAAO,CAAC,MAAM,OAAC,CAAC,CAU5B;IAED;;;;;OAKG;IACH,YAFa,OAAO,CAAC,IAAI,OAAC,CAAC,CAU1B;IAsBD;;;;;OAKG;IACH,gBAHW,MAAM,GACJ,OAAO,CAAC,MAAM,CAAC,CAY3B;IAED;;;;;;;;;;;OAWG;IACH,cARa,OAAO,CAAC,MAAM,CAAC,CAkB3B;IAED;;;;;;;;;;;OAWG;IACH,eARW,MAAM,aACN,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAezB;IAED;;;;;;;;;;;;;;;OAeG;IACH,kBAXW,WAAW,GAAC,IAAI,GAAC,MAAM,GACrB,OAAO,CAAC,IAAI,CAAC,CAyBzB;IAED;;;;;;;;;;;;;;OAcG;IACH,gBAXW,MAAM,aACN,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAkC5B;IAED;;;;OAIG;IACH,UAFa,OAAO,CAAC,MAAM,CAAC,CAY3B;IAED;;;;;;;;;OASG;IACH,UAPa,OAAO,CAAC,IAAI,CAAC,CAiBzB;;CACF;eAtjBc,SAAS;4BADI,sBAAsB;kBARhC,OAAO;iBAIR,MAAM"}
|
|
@@ -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"}
|