@gesslar/toolkit 3.17.0 → 3.20.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.17.0",
8
+ "version": "3.20.0",
9
9
  "license": "Unlicense",
10
10
  "homepage": "https://github.com/gesslar/toolkit#readme",
11
11
  "repository": {
@@ -60,7 +60,7 @@
60
60
  "devDependencies": {
61
61
  "@gesslar/uglier": "^0.6.0",
62
62
  "eslint": "^9.39.2",
63
- "happy-dom": "^20.0.11",
63
+ "happy-dom": "^20.1.0",
64
64
  "typescript": "^5.9.3"
65
65
  },
66
66
  "scripts": {
@@ -1,4 +1,5 @@
1
1
  import Tantrum from "./Tantrum.js"
2
+ import Valid from "./Valid.js"
2
3
 
3
4
  /**
4
5
  * Utility class providing helper functions for working with Promises,
@@ -13,9 +14,24 @@ export default class Promised {
13
14
  * @returns {Promise<Array<unknown>>} Results of all promises
14
15
  */
15
16
  static async await(promises) {
17
+ Valid.type(promises, "Promise[]")
18
+
16
19
  return await Promise.all(promises)
17
20
  }
18
21
 
22
+ /**
23
+ * Returns the first promise to resolve or reject from an array of promises.
24
+ * Wrapper around Promise.race for consistency with other utility methods.
25
+ *
26
+ * @param {Array<Promise<unknown>>} promises - Array of promises to race
27
+ * @returns {Promise<unknown>} Result of the first settled promise
28
+ */
29
+ static async race(promises) {
30
+ Valid.type(promises, "Promise[]")
31
+
32
+ return await Promise.race(promises)
33
+ }
34
+
19
35
  /**
20
36
  * Settles all promises (both fulfilled and rejected) in parallel.
21
37
  * Wrapper around Promise.allSettled for consistency with other utility methods.
@@ -24,6 +40,8 @@ export default class Promised {
24
40
  * @returns {Promise<Array<{status: 'fulfilled'|'rejected', value?: unknown, reason?: unknown}>>} Results of all settled promises with status and value/reason
25
41
  */
26
42
  static async settle(promises) {
43
+ Valid.type(promises, "Promise[]")
44
+
27
45
  return await Promise.allSettled(promises)
28
46
  }
29
47
 
@@ -101,20 +119,12 @@ export default class Promised {
101
119
  * @throws {Tantrum} Throws a Tantrum error with rejection reasons
102
120
  */
103
121
  static throw(message="GIGO", settled) {
122
+ Valid.type(message, "String", {allowEmpty: false})
123
+ Valid.type(settled, "Array")
124
+
104
125
  const rejected = this.rejected(settled)
105
126
  const reasons = this.reasons(rejected)
106
127
 
107
128
  throw Tantrum.new(message, reasons)
108
129
  }
109
-
110
- /**
111
- * Returns the first promise to resolve or reject from an array of promises.
112
- * Wrapper around Promise.race for consistency with other utility methods.
113
- *
114
- * @param {Array<Promise<unknown>>} promises - Array of promises to race
115
- * @returns {Promise<unknown>} Result of the first settled promise
116
- */
117
- static async race(promises) {
118
- return await Promise.race(promises)
119
- }
120
130
  }
package/src/node/index.js CHANGED
@@ -17,7 +17,7 @@ export {default as Util} from "./lib/Util.js"
17
17
  export {default as Cache} from "./lib/Cache.js"
18
18
  export {default as DirectoryObject} from "./lib/DirectoryObject.js"
19
19
  export {default as FileObject} from "./lib/FileObject.js"
20
- export {default as FS} from "./lib/FileSystem.js"
20
+ export {default as FileSystem} from "./lib/FileSystem.js"
21
21
  export {default as Glog} from "./lib/Glog.js"
22
22
  export {default as Notify} from "./lib/Notify.js"
23
23
  export {default as TempDirectoryObject} from "./lib/TempDirectoryObject.js"
@@ -368,26 +368,29 @@ export default class DirectoryObject extends FS {
368
368
  const files = [], directories = []
369
369
  const virtual = this.isVirtual
370
370
 
371
- found.forEach(e => {
371
+ for(const e of found) {
372
372
  if(e.isFile()) {
373
373
  const {name, parentPath} = e
374
374
  const resolved = FS.resolvePath(parentPath, name)
375
375
 
376
376
  const file = virtual
377
- ? new VFileObject(resolved, this)
377
+ ? new VFileObject(path.relative(this.real.path, resolved), this)
378
378
  : new FileObject(resolved, this)
379
379
 
380
380
  files.push(file)
381
381
  } else if(e.isDirectory()) {
382
382
  const {name, parentPath} = e
383
383
  const resolved = FS.resolvePath(parentPath, name)
384
- const directory = new this.constructor(resolved, this)
384
+ const relativePath = virtual
385
+ ? path.relative(this.real.path, resolved)
386
+ : resolved
387
+ const directory = new this.constructor(relativePath, this)
385
388
 
386
389
  directories.push(directory)
387
390
  } else {
388
391
  throw Sass.new(`wtf is this? ${e}`)
389
392
  }
390
- })
393
+ }
391
394
 
392
395
  return {files, directories}
393
396
  }
@@ -69,6 +69,24 @@ export default class FileObject extends FS {
69
69
  parentPath: null,
70
70
  })
71
71
 
72
+ /**
73
+ * Strip root from absolute path to make it relative.
74
+ * Used for virtual filesystem path resolution.
75
+ *
76
+ * @private
77
+ * @static
78
+ * @param {string} pathName - The path to convert
79
+ * @returns {string} Path with root stripped, or original if already relative
80
+ */
81
+ static #absoluteToRelative(pathName) {
82
+ if(!path.isAbsolute(pathName))
83
+ return pathName
84
+
85
+ const {root} = FS.pathParts(pathName)
86
+
87
+ return Data.chopLeft(pathName, root)
88
+ }
89
+
72
90
  /**
73
91
  * Constructs a FileObject instance.
74
92
  *
@@ -82,40 +100,41 @@ export default class FileObject extends FS {
82
100
  Valid.type(parent, "Null|String|DirectoryObject", {allowEmpty: false})
83
101
 
84
102
  const normalizedFile = FS.fixSlashes(submitted)
85
- const absOrRel = path.isAbsolute(normalizedFile) && parent
86
- ? FS.absoluteToRelative(normalizedFile, true)
103
+ const absOrRelPath = path.isAbsolute(normalizedFile) && parent
104
+ ? FileObject.#absoluteToRelative(normalizedFile)
87
105
  : normalizedFile
88
- const {dir, base, ext, name} = FS.pathParts(absOrRel)
106
+ const {dir, base, ext, name} = FS.pathParts(absOrRelPath)
89
107
 
90
108
  const [parentObject, fullPath] = (() => {
91
109
  if(Data.isType(parent, "String")) {
92
- const joined = FS.resolvePath(parent, absOrRel)
93
- const {dir} = FS.pathParts(joined)
110
+ const resolved = FS.resolvePath(parent, absOrRelPath)
111
+ const {dir} = FS.pathParts(resolved)
94
112
 
95
113
  return [
96
114
  new DirectoryObject(dir),
97
- joined,
115
+ resolved,
98
116
  ]
99
117
  }
100
118
 
101
119
  if(Data.isType(parent, "DirectoryObject")) {
102
- const joined = FS.resolvePath(parent.path, absOrRel)
103
- const {dir} = FS.pathParts(joined)
120
+ const parentPath = parent.path
121
+ const resolved = FS.resolvePath(parentPath, absOrRelPath)
122
+ const parts = FS.pathParts(resolved)
104
123
 
105
124
  return [
106
- parent.path === dir
125
+ parent.path === parts.dir
107
126
  ? parent
108
- : new parent.constructor(dir, parent),
109
- joined,
127
+ : new parent.constructor(parts.dir, parent),
128
+ resolved,
110
129
  ]
111
130
  }
112
131
 
113
132
  const directory = new DirectoryObject(dir)
114
- const joined = FS.resolvePath(directory.path, absOrRel)
133
+ const resolved = FS.resolvePath(directory.path, absOrRelPath)
115
134
 
116
135
  return [
117
136
  directory,
118
- joined,
137
+ resolved,
119
138
  ]
120
139
  })()
121
140
 
@@ -439,9 +458,8 @@ export default class FileObject extends FS {
439
458
  any: [JSON5,YAML]
440
459
  }[normalizedType]
441
460
 
442
- if(!toTry) {
461
+ if(!toTry)
443
462
  throw Sass.new(`Unsupported data type '${type}'. Supported types: json, json5, yaml.`)
444
- }
445
463
 
446
464
  for(const format of toTry) {
447
465
  try {
@@ -49,7 +49,7 @@ export default class FileSystem {
49
49
  1
50
50
  )
51
51
 
52
- return FileSystem.relativeOrAbsolutePath(fileOrDirectoryObject, this)
52
+ return FileSystem.relativeOrAbsolute(fileOrDirectoryObject, this)
53
53
  }
54
54
 
55
55
  /**
@@ -104,7 +104,7 @@ export default class FileSystem {
104
104
  * @param {FileObject|DirectoryObject} to - The target file or directory object
105
105
  * @returns {string} The relative path from `from` to `to`, or the absolute path if not reachable
106
106
  */
107
- static relativeOrAbsolutePath(from, to) {
107
+ static relativeOrAbsolute(from, to) {
108
108
  const fromBasePath = from.isDirectory
109
109
  ? from.path
110
110
  : from.parent?.path ?? path.dirname(from.path)
@@ -116,6 +116,25 @@ export default class FileSystem {
116
116
  : relative
117
117
  }
118
118
 
119
+ /**
120
+ * Computes the relative path from one file or directory to another.
121
+ *
122
+ * If the target is outside the source (i.e., the relative path starts with
123
+ * ".."), returns the absolute path to the target instead.
124
+ *
125
+ * @static
126
+ * @param {string} from - The source file or directory object
127
+ * @param {string} to - The target file or directory object
128
+ * @returns {string} The relative path from `from` to `to`, or the absolute path if not reachable
129
+ */
130
+ static relativeOrAbsolutePath(from, to) {
131
+ const relative = path.relative(from, to)
132
+
133
+ return relative.startsWith("..")
134
+ ? to
135
+ : relative
136
+ }
137
+
119
138
  /**
120
139
  * Merge two paths by finding overlapping segments and combining them
121
140
  * efficiently
@@ -356,32 +375,4 @@ export default class FileSystem {
356
375
 
357
376
  return this.resolvePath(cap, target)
358
377
  }
359
-
360
- /**
361
- * Convert an absolute path to a relative format by removing the root component.
362
- * By default, keeps a leading separator (making it "absolute-like relative").
363
- * Use forceActuallyRelative to get a truly relative path without leading separator.
364
- *
365
- * @static
366
- * @param {string} pathToCheck - The path to convert (returned unchanged if already relative)
367
- * @param {boolean} [forceActuallyRelative=false] - If true, removes leading separator for truly relative path
368
- * @returns {string} The relative path (with or without leading separator based on forceActuallyRelative)
369
- * @example
370
- * FS.absoluteToRelative("/home/user/docs") // "/home/user/docs" (with leading /)
371
- * FS.absoluteToRelative("/home/user/docs", true) // "home/user/docs" (truly relative)
372
- * FS.absoluteToRelative("relative/path") // "relative/path" (unchanged)
373
- */
374
- static absoluteToRelative(pathToCheck, forceActuallyRelative=false) {
375
- if(!path.isAbsolute(pathToCheck))
376
- return pathToCheck
377
-
378
- const {root} = this.pathParts(pathToCheck)
379
- const sep = path.sep
380
- const chopped = Data.chopLeft(pathToCheck, root)
381
- const absolute = forceActuallyRelative
382
- ? chopped
383
- : Data.prepend(chopped, sep)
384
-
385
- return absolute
386
- }
387
378
  }