@gesslar/toolkit 3.7.0 → 3.8.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
@@ -5,7 +5,7 @@
5
5
  "name": "gesslar",
6
6
  "url": "https://gesslar.dev"
7
7
  },
8
- "version": "3.7.0",
8
+ "version": "3.8.0",
9
9
  "license": "Unlicense",
10
10
  "homepage": "https://github.com/gesslar/toolkit#readme",
11
11
  "repository": {
@@ -132,6 +132,16 @@ export default class CappedDirectoryObject extends DirectoryObject {
132
132
  }
133
133
  }
134
134
 
135
+ /**
136
+ * Re-caps this directory to itself, making it the new root of the capped tree.
137
+ * This is a protected method intended for use by subclasses like TempDirectoryObject.
138
+ *
139
+ * @protected
140
+ */
141
+ _recapToSelf() {
142
+ this.#cap = this.#realPath
143
+ }
144
+
135
145
  /**
136
146
  * Returns the cap path for this directory.
137
147
  *
@@ -221,14 +231,14 @@ export default class CappedDirectoryObject extends DirectoryObject {
221
231
  * Returns the parent directory of this capped directory.
222
232
  * Returns null only if this directory is at the cap (the "root" of the capped tree).
223
233
  *
224
- * Note: The returned parent is a plain DirectoryObject (not capped).
225
- * Use getDirectory() for creating capped subdirectories.
234
+ * Note: The returned parent is a CappedDirectoryObject with the same cap.
235
+ * This maintains the capping behavior throughout the directory hierarchy.
226
236
  *
227
- * @returns {DirectoryObject|null} Parent directory or null if at cap root
237
+ * @returns {CappedDirectoryObject|null} Parent directory or null if at cap root
228
238
  * @example
229
239
  * const capped = new TempDirectoryObject("myapp")
230
240
  * const subdir = capped.getDirectory("data")
231
- * console.log(subdir.parent.path) // Returns parent DirectoryObject
241
+ * console.log(subdir.parent.path) // Returns parent CappedDirectoryObject
232
242
  * console.log(capped.parent) // null (at cap root)
233
243
  */
234
244
  get parent() {
@@ -239,13 +249,17 @@ export default class CappedDirectoryObject extends DirectoryObject {
239
249
  return null
240
250
  }
241
251
 
242
- // Otherwise return the parent using real path (plain DirectoryObject, not capped)
252
+ // Compute parent's real path
243
253
  const parentPath = path.dirname(this.#realPath)
244
254
  const isRoot = parentPath === this.#realPath
245
255
 
246
- return isRoot
247
- ? null
248
- : new DirectoryObject(parentPath, this.temporary)
256
+ if(isRoot) {
257
+ return null
258
+ }
259
+
260
+ // Compute relative path from current to parent (just "..")
261
+ // Then use getDirectory to create the parent, which preserves the class type
262
+ return this.getDirectory("..")
249
263
  }
250
264
 
251
265
  /**
@@ -264,9 +278,24 @@ export default class CappedDirectoryObject extends DirectoryObject {
264
278
  */
265
279
  toJSON() {
266
280
  const capResolved = path.resolve(this.#cap)
267
- const parentPath = this.#realPath === capResolved
268
- ? null
269
- : "/"
281
+ let parentPath
282
+
283
+ if(this.#realPath === capResolved) {
284
+ // At cap root, no parent
285
+ parentPath = null
286
+ } else {
287
+ // Compute parent's virtual path
288
+ const parentReal = path.dirname(this.#realPath)
289
+ const relative = path.relative(capResolved, parentReal)
290
+
291
+ // If parent is cap root or empty, return "/"
292
+ if(!relative || relative === ".") {
293
+ parentPath = "/"
294
+ } else {
295
+ // Return parent's virtual path with leading slash
296
+ parentPath = "/" + relative.split(path.sep).join("/")
297
+ }
298
+ }
270
299
 
271
300
  return {
272
301
  supplied: this.supplied,
@@ -278,6 +307,7 @@ export default class CappedDirectoryObject extends DirectoryObject {
278
307
  isFile: this.isFile,
279
308
  isDirectory: this.isDirectory,
280
309
  parent: parentPath,
310
+ root: this.root.path,
281
311
  real: this.real.toJSON()
282
312
  }
283
313
  }
@@ -436,10 +466,11 @@ export default class CappedDirectoryObject extends DirectoryObject {
436
466
  // Start at cap root
437
467
  let current = this.#createCappedAtRoot()
438
468
 
439
- // Traverse each segment, creating CappedDirectoryObject instances
440
- // (not subclass instances, to avoid constructor signature issues)
469
+ // Traverse each segment, using constructor to preserve class type
441
470
  for(const segment of segments) {
442
- current = new CappedDirectoryObject(segment, current, this.temporary)
471
+ // Use simple name constructor to preserve subclass type
472
+ // Works for both CappedDirectoryObject and TempDirectoryObject
473
+ current = new this.constructor(segment, current, this.temporary)
443
474
  }
444
475
 
445
476
  return current
@@ -157,7 +157,8 @@ export default class DirectoryObject extends FS {
157
157
  extension: this.extension,
158
158
  isFile: this.isFile,
159
159
  isDirectory: this.isDirectory,
160
- parent: this.parent ? this.parent.path : null
160
+ parent: this.parent ? this.parent.path : null,
161
+ root: this.root.path
161
162
  }
162
163
  }
163
164
 
@@ -294,6 +295,34 @@ export default class DirectoryObject extends FS {
294
295
  return this.#parent
295
296
  }
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
318
+
319
+ while(current.parent !== null) {
320
+ current = current.parent
321
+ }
322
+
323
+ return current
324
+ }
325
+
297
326
  /**
298
327
  * Recursively removes a temporary directory and all its contents.
299
328
  *
@@ -99,11 +99,13 @@ export default class TempDirectoryObject extends CappedDirectoryObject {
99
99
  )
100
100
  }
101
101
 
102
- // SECURITY: Ensure parent's cap is tmpdir (prevent escape to other caps)
103
- const tmpdir = os.tmpdir()
104
- if(parent.cap !== tmpdir) {
102
+ // SECURITY: Ensure parent's cap is within tmpdir (prevent escape to other caps)
103
+ const tmpdir = path.resolve(os.tmpdir())
104
+ const parentCap = path.resolve(parent.cap)
105
+
106
+ if(!parentCap.startsWith(tmpdir)) {
105
107
  throw Sass.new(
106
- `Parent must be capped to OS temp directory (${tmpdir}), ` +
108
+ `Parent must be capped to OS temp directory (${tmpdir}) or a subdirectory thereof, ` +
107
109
  `got cap: ${parent.cap}`
108
110
  )
109
111
  }
@@ -119,6 +121,13 @@ export default class TempDirectoryObject extends CappedDirectoryObject {
119
121
 
120
122
  // Temp-specific behavior: create directory immediately
121
123
  this.#createDirectory()
124
+
125
+ // Re-cap to the temp directory itself for consistent behavior with CappedDirectoryObject
126
+ // This makes temp.cap === temp.real.path and temp.parent === null
127
+ if(!parent) {
128
+ // Only re-cap if this is a root temp directory (no TempDirectoryObject parent)
129
+ this._recapToSelf()
130
+ }
122
131
  }
123
132
 
124
133
  /**
@@ -37,6 +37,13 @@ export default class CappedDirectoryObject extends DirectoryObject {
37
37
  * // path: /home/user/.cache/etc/config, cap: /home/user/.cache
38
38
  */
39
39
  constructor(dirPath: string, parent?: CappedDirectoryObject | null, temporary?: boolean);
40
+ /**
41
+ * Re-caps this directory to itself, making it the new root of the capped tree.
42
+ * This is a protected method intended for use by subclasses like TempDirectoryObject.
43
+ *
44
+ * @protected
45
+ */
46
+ protected _recapToSelf(): void;
40
47
  /**
41
48
  * Returns the cap path for this directory.
42
49
  *
@@ -75,6 +82,21 @@ export default class CappedDirectoryObject extends DirectoryObject {
75
82
  * subdir.real.parent // Can traverse outside the cap
76
83
  */
77
84
  get real(): DirectoryObject;
85
+ /**
86
+ * Returns the parent directory of this capped directory.
87
+ * Returns null only if this directory is at the cap (the "root" of the capped tree).
88
+ *
89
+ * Note: The returned parent is a CappedDirectoryObject with the same cap.
90
+ * This maintains the capping behavior throughout the directory hierarchy.
91
+ *
92
+ * @returns {CappedDirectoryObject|null} Parent directory or null if at cap root
93
+ * @example
94
+ * const capped = new TempDirectoryObject("myapp")
95
+ * const subdir = capped.getDirectory("data")
96
+ * console.log(subdir.parent.path) // Returns parent CappedDirectoryObject
97
+ * console.log(capped.parent) // null (at cap root)
98
+ */
99
+ get parent(): CappedDirectoryObject | null;
78
100
  /**
79
101
  * Returns the URL with virtual path (cap-relative).
80
102
  *
@@ -1 +1 @@
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;IAiCD;;;;OAIG;IACH,WAFa,GAAG,CAIf;IAqED;;;;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,CAgBnE;;CA8DF;4BAnmB2B,sBAAsB;uBAC3B,iBAAiB"}
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;;;;;OAKG;IACH,+BAEC;IAED;;;;OAIG;IACH,WAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,cAFa,OAAO,CAInB;IAED;;;;;OAKG;IACH,0BAFa,MAAM,CAIlB;IAqCD;;;;;;;;;;;;;;;;;OAiBG;IACH,YAba,eAAe,CAe3B;IAED;;;;;;;;;;;;;OAaG;IACH,cAPa,qBAAqB,GAAC,IAAI,CA0BtC;IAED;;;;OAIG;IACH,WAFa,GAAG,CAIf;IAqFD;;;;OAIG;IACH,cAFa,SAAS,CAAC,eAAe,CAAC,CAItC;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,sBAjBW,MAAM,GACJ,qBAAqB,CAiEjC;IAsID;;;;;OAKG;IACH,WAHW,MAAM,GACJ,OAAO,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAAC,WAAW,QAAO;KAAC,CAAC,CAgBnE;;CA8DF;4BAloB2B,sBAAsB;uBAC3B,iBAAiB"}
@@ -139,6 +139,24 @@ export default class DirectoryObject extends FS {
139
139
  * console.log(root.parent) // null
140
140
  */
141
141
  get parent(): DirectoryObject | null;
142
+ /**
143
+ * Returns the root directory of the filesystem.
144
+ *
145
+ * For DirectoryObject, this walks up to the filesystem root.
146
+ * For CappedDirectoryObject, this returns the cap root.
147
+ *
148
+ * @returns {DirectoryObject} The root directory
149
+ * @example
150
+ * const dir = new DirectoryObject("/usr/local/bin")
151
+ * console.log(dir.root.path) // "/"
152
+ *
153
+ * @example
154
+ * const capped = new CappedDirectoryObject("/projects/myapp")
155
+ * const sub = capped.getDirectory("src/lib")
156
+ * console.log(sub.root.path) // "/" (virtual, cap root)
157
+ * console.log(sub.root.real.path) // "/projects/myapp"
158
+ */
159
+ get root(): DirectoryObject;
142
160
  /**
143
161
  * Recursively removes a temporary directory and all its contents.
144
162
  *
@@ -1 +1 @@
1
- {"version":3,"file":"DirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/DirectoryObject.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH;uBAqGe,MAAM;IAlEnB;;;;;OAKG;IACH,yCAFW,OAAO,EA6BjB;IAWD;;;;OAIG;IACH,UAFa,MAAM,CAclB;IAWD;;;;OAIG;IACH,cAFa,OAAO,CAAC,OAAO,CAAC,CAI5B;IAED;;;;OAIG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,YAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,WAFa,GAAG,CAIf;IAED;;;;OAIG;IACH,YAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,cAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,iBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,WAFa,MAAM,CAIlB;IAED;;;;;;;OAOG;IACH,aALa,KAAK,CAAC,MAAM,CAAC,CAOzB;IAED;;;;OAIG;IACH,iBAFa,OAAO,CAInB;IAED;;;;;;;;;;;;OAYG;IACH,cARa,eAAe,GAAC,IAAI,CAwBhC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,UATa,OAAO,CAAC,IAAI,CAAC,CA0BzB;IAED;;;;OAIG;IACH,cAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,mBAFa,OAAO,CAInB;IAiBD;;;;;;;;;;;;;;;OAeG;IACH,WAZW,MAAM,GACJ,OAAO,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAAC,WAAW,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;KAAC,CAAC,CAoCpF;IAED;;;;;;;;;;;;OAYG;IACH,uBARW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAqBzB;IA8BD;;;;;;;;;;;;;;;OAeG;IACH,cAZa,MAAM,CAclB;IAED;;;;;;;;;;;;;;OAcG;IACH,UARa,OAAO,CAAC,IAAI,CAAC,CAkBzB;IAED;;;;;OAKG;IACH,kBAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAM5B;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAO5B;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,sBAdW,MAAM,GACJ,eAAe,CAoB3B;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAbW,MAAM,GACJ,UAAU,CAkBtB;;CACF;eAtkBc,SAAS;uBACD,iBAAiB"}
1
+ {"version":3,"file":"DirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/DirectoryObject.js"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH;uBAsGe,MAAM;IAnEnB;;;;;OAKG;IACH,yCAFW,OAAO,EA6BjB;IAWD;;;;OAIG;IACH,UAFa,MAAM,CAelB;IAWD;;;;OAIG;IACH,cAFa,OAAO,CAAC,OAAO,CAAC,CAI5B;IAED;;;;OAIG;IACH,gBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,YAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,WAFa,GAAG,CAIf;IAED;;;;OAIG;IACH,YAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,cAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,iBAFa,MAAM,CAIlB;IAED;;;;OAIG;IACH,WAFa,MAAM,CAIlB;IAED;;;;;;;OAOG;IACH,aALa,KAAK,CAAC,MAAM,CAAC,CAOzB;IAED;;;;OAIG;IACH,iBAFa,OAAO,CAInB;IAED;;;;;;;;;;;;OAYG;IACH,cARa,eAAe,GAAC,IAAI,CAwBhC;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,YAXa,eAAe,CAoB3B;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,UATa,OAAO,CAAC,IAAI,CAAC,CA0BzB;IAED;;;;OAIG;IACH,cAFa,OAAO,CAInB;IAED;;;;OAIG;IACH,mBAFa,OAAO,CAInB;IAiBD;;;;;;;;;;;;;;;OAeG;IACH,WAZW,MAAM,GACJ,OAAO,CAAC;QAAC,KAAK,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;QAAC,WAAW,EAAE,KAAK,CAAC,eAAe,CAAC,CAAA;KAAC,CAAC,CAoCpF;IAED;;;;;;;;;;;;OAYG;IACH,uBARW,MAAM,GACJ,OAAO,CAAC,IAAI,CAAC,CAqBzB;IA8BD;;;;;;;;;;;;;;;OAeG;IACH,cAZa,MAAM,CAclB;IAED;;;;;;;;;;;;;;OAcG;IACH,UARa,OAAO,CAAC,IAAI,CAAC,CAkBzB;IAED;;;;;OAKG;IACH,kBAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAM5B;IAED;;;;;OAKG;IACH,sBAHW,MAAM,GACJ,OAAO,CAAC,OAAO,CAAC,CAO5B;IAED;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,sBAdW,MAAM,GACJ,eAAe,CAoB3B;IAED;;;;;;;;;;;;;;;;;;;OAmBG;IACH,kBAbW,MAAM,GACJ,UAAU,CAkBtB;;CACF;eAnmBc,SAAS;uBACD,iBAAiB"}
@@ -1 +1 @@
1
- {"version":3,"file":"TempDirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/TempDirectoryObject.js"],"names":[],"mappings":"AAcA;;;;;;;;;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;kCArLiC,4BAA4B"}
1
+ {"version":3,"file":"TempDirectoryObject.d.ts","sourceRoot":"","sources":["../../lib/TempDirectoryObject.js"],"names":[],"mappings":"AAcA;;;;;;;;;GASG;AACH;IAEE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAiCG;IACH,mBAxBW,MAAM,OAAC,WACP,mBAAmB,OAAC,EA6F9B;IAsBD;;;;;;;;;;;;;;OAcG;IACH,sBAVW,MAAM,GACJ,mBAAmB,CAY/B;IAED;;;;;;;;;;;;;;OAcG;IACH,kBAVW,MAAM,GACJ,UAAU,CAYtB;;CAUF;kCA9LiC,4BAA4B"}