├── .gitignore ├── .node-version ├── .npmignore ├── .prettierrc ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── src ├── index.test.ts └── index.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | dist 5 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v18.18.2 2 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | jest.config.js 3 | .prettierrc 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2019-2024 Lily Skye 3 | 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 18 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 19 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 20 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 21 | OR OTHER DEALINGS IN THE SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nice-path 2 | 3 | `nice-path` provides a class that represents a filesystem path (POSIX-style or Win32-style), which has various nice methods on it that make it easy to work with. It can be used as a replacement for the Node.js `path` builtin module, where you pass around Path objects and stringify them before use, rather than passing around strings. 4 | 5 | > `nice-path`'s code is derived from [yavascript](https://github.com/suchipi/yavascript). 6 | 7 | ## Example 8 | 9 | ```ts 10 | import { Path } from "nice-path"; 11 | 12 | const here = new Path(__dirname); 13 | console.log(here); 14 | // Path { 15 | // segments: ["", "home", "suchipi", "Code", "nice-path"], 16 | // separator: "/" 17 | // } 18 | 19 | here.toString(); // "/home/suchipi/Code/nice-path" 20 | here.isAbsolute(); // true 21 | 22 | const readme = here.concat("README.md"); 23 | console.log(readme); 24 | // Path { 25 | // segments: [ 26 | // "", 27 | // "home", 28 | // "suchipi", 29 | // "Code", 30 | // "nice-path", 31 | // "README.md" 32 | // ], 33 | // separator: "/" 34 | // } 35 | readme.basename(); // "README.md" 36 | readme.extname(); // ".md" 37 | readme.dirname(); // Path object with same contents as 'here' 38 | 39 | // normalize resolves . and .. components 40 | const homeDir = here.concat("../../.").normalize(); 41 | console.log(homeDir); 42 | // Path { 43 | // segments: [ 44 | // "", 45 | // "home", 46 | // "suchipi" 47 | // ], 48 | // separator: "/" 49 | // } 50 | 51 | here.relativeTo(homeDir).toString(); // "./Code/nice-path" 52 | readme.relativeTo(homeDir).toString(); // "./Code/nice-path/README.txt" 53 | 54 | // There are also several other methods which aren't in this example. 55 | ``` 56 | 57 | ## API Documentation 58 | 59 | ### Overview 60 | 61 | This package has one named export: "Path", which is a class. 62 | 63 | The "Path" class has the following instance properties: 64 | 65 | - segments (Array of string) 66 | - separator (string) 67 | 68 | and the following instance methods: 69 | 70 | - normalize 71 | - concat 72 | - isAbsolute 73 | - clone 74 | - relativeTo 75 | - toString 76 | - basename 77 | - extname 78 | - dirname 79 | - startsWith 80 | - endsWith 81 | - indexOf 82 | - includes 83 | - replace 84 | - replaceAll 85 | - replaceLast 86 | 87 | and the following static methods: 88 | 89 | - splitToSegments 90 | - detectSeparator 91 | - normalize 92 | - isAbsolute 93 | - fromRaw 94 | 95 | ### Details 96 | 97 | #### Path (class) 98 | 99 | An object that represents a filesystem path. It has the following constructor signature: 100 | 101 | ```ts 102 | class Path { 103 | constructor(...inputs: Array>); 104 | } 105 | ``` 106 | 107 | You can pass in zero or more arguments to the constructor, where each argument can be either a string, a Path object, or an array of strings and Path objects. 108 | 109 | When multiple strings/Paths are provided to the constructor, they will be concatenated together in order, left-to-right. 110 | 111 | The resulting object has two properties: `segments`, which is an array of strings containing all the non-slash portions of the Path, and `separator`, which is the slash string that those portions would have between them if this path were represented as a string. 112 | 113 | #### `segments: Array` (instance property of Path) 114 | 115 | Each `Path` object has a `segments` property, which is an array of strings containing all the non-slash portions of the Path. 116 | 117 | For example, given a path object `myPath = new Path("a/b/c/d")`, `myPath.segments` is an array of strings `["a", "b", "c", "d"]`. 118 | 119 | You may freely mutate this array in order to add or remove segments, but I recommend you instead use instance methods on the Path object, which take a "separator-aware" approach to looking at path segments. 120 | 121 | POSIX-style absolute paths start with a leading slash character, like "/a/b/c". A Path object representing that path, (ie `new Path("/a/b/c")`) would have the following path segments: 122 | 123 | ```json 124 | ["", "a", "b", "c"] 125 | ``` 126 | 127 | That empty string in the first position represents the "left side" of the first slash. When you use `.toString()` on that Path object, it will become "/a/b/c" again, as expected. 128 | 129 | Windows [UNC paths](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats#unc-paths) have two empty strings at the beginning of the Array. 130 | 131 | #### `separator: string` (instance property of Path) 132 | 133 | Each `Path` object has a `separator` property, which is the slash string that those portions would have between them if this path were represented as a string. It's always either `/` (forward slash) or `\` (backward slash). If you change this property, the result of calling `.toString()` on the Path object will change: 134 | 135 | ```ts 136 | // The initial value of separator is inferred from the input path: 137 | const myPath = new Path("hi/there/friend"); 138 | console.log(myPath.separator); // prints / 139 | 140 | // Using toString() joins the path segments using the separator: 141 | console.log(myPath.toString()); // prints hi/there/friend 142 | 143 | // If you change the separator... (note: this is a single backslash character. It has to be "escaped" with another one, which is why there are two.) 144 | myPath.separator = "\\"; 145 | 146 | // Then toString() uses the new separator instead: 147 | console.log(myPath.toString()); // prints hi\there\friend 148 | ``` 149 | 150 | The initial value of the `separator` property is inferred by searching the input string(s) the Path was constructed with for a slash character and using the first one found. If none is found, a forward slash (`/`) is used. 151 | 152 | #### `normalize(): Path` (instance method of Path) 153 | 154 | The `normalize` method returns a new Path with all non-leading `.` and `..` segments resolved. 155 | 156 | ```ts 157 | const myPath = new Path("/some/where/../why/over/./here"); 158 | const resolved = myPath.normalize(); 159 | console.log(resolved.toString()); // /some/why/over/here 160 | ``` 161 | 162 | If you want to evaluate a relative path (a path with leading `.` or `..` segments) using a base directory, you can concatenate and then normalize them: 163 | 164 | ```ts 165 | const baseDir = new Path("/home/me"); 166 | const relativeDir = new Path("./somewhere/something/../blue"); 167 | const resolved = baseDir.concat(relativeDir).normalize(); 168 | console.log(resolved.toString()); // /home/me/something/blue 169 | ``` 170 | 171 | #### `concat(...others): Path` (instance method of Path) 172 | 173 | The `concat` method creates a new Path by appending additional path segments onto the end of the target Path's segments. The additional path segments are passed to the concat method as either strings, Paths, or Arrays of strings and Paths. 174 | 175 | The new Path will use the separator from the target Path. 176 | 177 | ```ts 178 | const pathOne = new Path("a/one"); 179 | const withStuffAdded = pathOne.concat( 180 | "two", 181 | "three/four", 182 | new Path("yeah\\yes"), 183 | ); 184 | 185 | console.log(withStuffAdded.toString()); 186 | // "a/one/two/three/four/yeah/yes" 187 | ``` 188 | 189 | #### `isAbsolute(): boolean` (instance method of Path) 190 | 191 | The `isAbsolute` method returns whether the target Path is an absolute path; that is, whether it starts with either `/`, `\`, or a drive letter (ie `C:`). 192 | 193 | #### `clone(): Path` (instance method of Path) 194 | 195 | The `clone` method makes a second Path object containing the same segments and separator as the target. 196 | 197 | Note that although the new Path has the same segments as the target Path, it doesn't use the same Array instance. 198 | 199 | #### `relativeTo(dir, options?): Path` (instance method of Path) 200 | 201 | The `relativeTo` method expresses the target path as a relative path, relative to the `dir` argument. 202 | 203 | The `options` argument, if present, should be an object with the property "noLeadingDot", which is a boolean. The noLeadingDot option controls whether the resulting relative path has a leading `.` segment or not. If this option isn't provided, the leading dot will be present. Note that if the resulting path starts with "..", that will always be present, regardless of the option. 204 | 205 | #### `toString(): string` (instance method of Path) 206 | 207 | The `toString` method returns a string representation of the target Path by joining its segments using its separator. 208 | 209 | #### `basename(): string` (instance method of Path) 210 | 211 | The `basename` method returns the final segment string of the target Path. If that Path has no segments, the empty string is returned. 212 | 213 | #### `extname(options?): string` (instance method of Path) 214 | 215 | The `extname` method returns the trailing extension of the target Path. Pass `{ full: true }` as the "options" argument to get a compound extension like ".d.ts", rather than the final extension (like ".ts"). 216 | 217 | If the Path doesn't have a trailing extension, the empty string (`""`) is returned. 218 | 219 | #### `dirname(): Path` (instance method of Path) 220 | 221 | The `dirname` method returns a new Path containing all of the segments in the target Path except for the last one; ie. the path to the directory that contains the target path. 222 | 223 | #### `startsWith(value): boolean` (instance method of Path) 224 | 225 | The `startsWith` method returns whether the target Path starts with the provided value (string, Path, or Array of string/Path), by comparing one path segment at a time, left-to-right. 226 | 227 | The starting segment(s) of the target Path must _exactly_ match the segment(s) in the provided value. 228 | 229 | This means that, given two Paths A and B: 230 | 231 | ``` 232 | A: Path { /home/user/.config } 233 | B: Path { /home/user/.config2 } 234 | ``` 235 | 236 | Path B does _not_ start with Path A, because `".config" !== ".config2"`. 237 | 238 | #### `endsWith(value): boolean` (instance method of Path) 239 | 240 | The `endsWith` method returns whether the target Path ends with the provided value (string, Path, or Array of string/Path), by comparing one path segment at a time, right-to-left. 241 | 242 | The ending segment(s) of the target Path must _exactly_ match the segment(s) in the provided value. 243 | 244 | This means that, given two Paths A and B: 245 | 246 | ``` 247 | A: Path { /home/1user/.config } 248 | B: Path { user/.config } 249 | ``` 250 | 251 | Path A does _not_ end with Path B, because `"1user" !== "user"`. 252 | 253 | #### `indexOf(value, fromIndex?): number;` (instance method of Path) 254 | 255 | The `indexOf` method returns the path segment index (number) at which `value` (string, Path, or Array of string/Path) appears in the target Path, or `-1` if it doesn't appear. 256 | 257 | If the provided value argument contains more than one path segment, the returned index will refer to the location of the value's first path segment. 258 | 259 | The optional argument `fromIndex` can be provided to specify which index into the target Path's segments to begin the search at. If not provided, the search starts at the beginning (index 0). 260 | 261 | #### `includes(value, fromIndex?): boolean;` (instance method of Path) 262 | 263 | The `includes` method returns a boolean indicating whether `value` (string, Path, or Array of string/Path) appears in the target Path. 264 | 265 | The optional argument `fromIndex` can be provided to specify which index into the target Path's segments to begin the search at. If not provided, the search starts at the beginning (index 0). 266 | 267 | #### `replace(value, replacement): Path;` (instance method of Path) 268 | 269 | The `replace` method returns a new `Path` object wherein the first occurrence of `value` (string, Path, or Array of string/Path) in the target Path has been replaced with `replacement` (string, Path, or Array of string/Path). 270 | 271 | Note that only the first match is replaced; to replace multiple occurrences, use `replaceAll`. 272 | 273 | If `value` is not present in the target Path, a clone of said Path is returned. 274 | 275 | > Tip: To "replace with nothing", pass an empty array as the replacement. 276 | 277 | #### `replaceAll(value, replacement): Path;` (instance method of Path) 278 | 279 | The `replaceAll` method returns a new `Path` object wherein all occurrences of `value` (string, Path, or Array of string/Path) in the target Path have been replaced with `replacement` (string, Path, or Array of string/Path). 280 | 281 | If you want to only replace the first occurrence, use `replace` instead. 282 | 283 | If `value` is not present in the target Path, a clone of said Path is returned. 284 | 285 | > Tip: To "replace with nothing", pass an empty array as the replacement. 286 | 287 | #### `replaceLast(replacement): Path;` (instance method of Path) 288 | 289 | The `replaceLast` method returns a copy of the target Path, but with its final segment replaced with `replacement` (string, Path, or Array of string/Path). 290 | 291 | This method is most commonly used to modify the final (filename) part of a path. 292 | 293 | If the target Path has no segments, the returned Path will contain exactly the segments from `replacement`. 294 | 295 | #### `Path.splitToSegments(inputParts): Array` (static method on Path) 296 | 297 | Splits one or more path strings into an array of path segments. Used internally by the Path constructor. 298 | 299 | #### `Path.detectSeparator(input, fallback)` (static method on Path) 300 | 301 | Searches input (a string or Array of strings) for a path separator character (either forward slash or backslash), and returns it. If none is found, returns `fallback`. 302 | 303 | #### `Path.normalize(...inputs): Path` (static method on Path) 304 | 305 | Concatenates the input path(s) and then resolves all non-leading `.` and `..` segments. Shortcut for `new Path(...inputs).normalize()`. 306 | 307 | #### `Path.isAbsolute(path)`: boolean (static method on Path) 308 | 309 | Return whether the `path` argument (string or Path) is an absolute path; that is, whether it starts with either `/`, `\`, or a drive letter (ie `C:`). 310 | 311 | #### `Path.fromRaw(segments, separator): Path` (static method on Path) 312 | 313 | Creates a new Path object using the provided segments and separator. 314 | 315 | ## License 316 | 317 | MIT 318 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nice-path", 3 | "version": "3.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nice-path", 9 | "version": "3.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@types/node": "^20.11.0", 13 | "prettier": "^3.2.2", 14 | "typescript": "^5.3.3", 15 | "vitest": "^1.2.0" 16 | } 17 | }, 18 | "node_modules/@esbuild/aix-ppc64": { 19 | "version": "0.21.5", 20 | "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", 21 | "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", 22 | "cpu": [ 23 | "ppc64" 24 | ], 25 | "dev": true, 26 | "optional": true, 27 | "os": [ 28 | "aix" 29 | ], 30 | "engines": { 31 | "node": ">=12" 32 | } 33 | }, 34 | "node_modules/@esbuild/android-arm": { 35 | "version": "0.21.5", 36 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", 37 | "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", 38 | "cpu": [ 39 | "arm" 40 | ], 41 | "dev": true, 42 | "optional": true, 43 | "os": [ 44 | "android" 45 | ], 46 | "engines": { 47 | "node": ">=12" 48 | } 49 | }, 50 | "node_modules/@esbuild/android-arm64": { 51 | "version": "0.21.5", 52 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", 53 | "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", 54 | "cpu": [ 55 | "arm64" 56 | ], 57 | "dev": true, 58 | "optional": true, 59 | "os": [ 60 | "android" 61 | ], 62 | "engines": { 63 | "node": ">=12" 64 | } 65 | }, 66 | "node_modules/@esbuild/android-x64": { 67 | "version": "0.21.5", 68 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", 69 | "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", 70 | "cpu": [ 71 | "x64" 72 | ], 73 | "dev": true, 74 | "optional": true, 75 | "os": [ 76 | "android" 77 | ], 78 | "engines": { 79 | "node": ">=12" 80 | } 81 | }, 82 | "node_modules/@esbuild/darwin-arm64": { 83 | "version": "0.21.5", 84 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", 85 | "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", 86 | "cpu": [ 87 | "arm64" 88 | ], 89 | "dev": true, 90 | "optional": true, 91 | "os": [ 92 | "darwin" 93 | ], 94 | "engines": { 95 | "node": ">=12" 96 | } 97 | }, 98 | "node_modules/@esbuild/darwin-x64": { 99 | "version": "0.21.5", 100 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", 101 | "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", 102 | "cpu": [ 103 | "x64" 104 | ], 105 | "dev": true, 106 | "optional": true, 107 | "os": [ 108 | "darwin" 109 | ], 110 | "engines": { 111 | "node": ">=12" 112 | } 113 | }, 114 | "node_modules/@esbuild/freebsd-arm64": { 115 | "version": "0.21.5", 116 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", 117 | "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", 118 | "cpu": [ 119 | "arm64" 120 | ], 121 | "dev": true, 122 | "optional": true, 123 | "os": [ 124 | "freebsd" 125 | ], 126 | "engines": { 127 | "node": ">=12" 128 | } 129 | }, 130 | "node_modules/@esbuild/freebsd-x64": { 131 | "version": "0.21.5", 132 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", 133 | "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", 134 | "cpu": [ 135 | "x64" 136 | ], 137 | "dev": true, 138 | "optional": true, 139 | "os": [ 140 | "freebsd" 141 | ], 142 | "engines": { 143 | "node": ">=12" 144 | } 145 | }, 146 | "node_modules/@esbuild/linux-arm": { 147 | "version": "0.21.5", 148 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", 149 | "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", 150 | "cpu": [ 151 | "arm" 152 | ], 153 | "dev": true, 154 | "optional": true, 155 | "os": [ 156 | "linux" 157 | ], 158 | "engines": { 159 | "node": ">=12" 160 | } 161 | }, 162 | "node_modules/@esbuild/linux-arm64": { 163 | "version": "0.21.5", 164 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", 165 | "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", 166 | "cpu": [ 167 | "arm64" 168 | ], 169 | "dev": true, 170 | "optional": true, 171 | "os": [ 172 | "linux" 173 | ], 174 | "engines": { 175 | "node": ">=12" 176 | } 177 | }, 178 | "node_modules/@esbuild/linux-ia32": { 179 | "version": "0.21.5", 180 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", 181 | "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", 182 | "cpu": [ 183 | "ia32" 184 | ], 185 | "dev": true, 186 | "optional": true, 187 | "os": [ 188 | "linux" 189 | ], 190 | "engines": { 191 | "node": ">=12" 192 | } 193 | }, 194 | "node_modules/@esbuild/linux-loong64": { 195 | "version": "0.21.5", 196 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", 197 | "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", 198 | "cpu": [ 199 | "loong64" 200 | ], 201 | "dev": true, 202 | "optional": true, 203 | "os": [ 204 | "linux" 205 | ], 206 | "engines": { 207 | "node": ">=12" 208 | } 209 | }, 210 | "node_modules/@esbuild/linux-mips64el": { 211 | "version": "0.21.5", 212 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", 213 | "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", 214 | "cpu": [ 215 | "mips64el" 216 | ], 217 | "dev": true, 218 | "optional": true, 219 | "os": [ 220 | "linux" 221 | ], 222 | "engines": { 223 | "node": ">=12" 224 | } 225 | }, 226 | "node_modules/@esbuild/linux-ppc64": { 227 | "version": "0.21.5", 228 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", 229 | "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", 230 | "cpu": [ 231 | "ppc64" 232 | ], 233 | "dev": true, 234 | "optional": true, 235 | "os": [ 236 | "linux" 237 | ], 238 | "engines": { 239 | "node": ">=12" 240 | } 241 | }, 242 | "node_modules/@esbuild/linux-riscv64": { 243 | "version": "0.21.5", 244 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", 245 | "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", 246 | "cpu": [ 247 | "riscv64" 248 | ], 249 | "dev": true, 250 | "optional": true, 251 | "os": [ 252 | "linux" 253 | ], 254 | "engines": { 255 | "node": ">=12" 256 | } 257 | }, 258 | "node_modules/@esbuild/linux-s390x": { 259 | "version": "0.21.5", 260 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", 261 | "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", 262 | "cpu": [ 263 | "s390x" 264 | ], 265 | "dev": true, 266 | "optional": true, 267 | "os": [ 268 | "linux" 269 | ], 270 | "engines": { 271 | "node": ">=12" 272 | } 273 | }, 274 | "node_modules/@esbuild/linux-x64": { 275 | "version": "0.21.5", 276 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", 277 | "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", 278 | "cpu": [ 279 | "x64" 280 | ], 281 | "dev": true, 282 | "optional": true, 283 | "os": [ 284 | "linux" 285 | ], 286 | "engines": { 287 | "node": ">=12" 288 | } 289 | }, 290 | "node_modules/@esbuild/netbsd-x64": { 291 | "version": "0.21.5", 292 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", 293 | "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", 294 | "cpu": [ 295 | "x64" 296 | ], 297 | "dev": true, 298 | "optional": true, 299 | "os": [ 300 | "netbsd" 301 | ], 302 | "engines": { 303 | "node": ">=12" 304 | } 305 | }, 306 | "node_modules/@esbuild/openbsd-x64": { 307 | "version": "0.21.5", 308 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", 309 | "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", 310 | "cpu": [ 311 | "x64" 312 | ], 313 | "dev": true, 314 | "optional": true, 315 | "os": [ 316 | "openbsd" 317 | ], 318 | "engines": { 319 | "node": ">=12" 320 | } 321 | }, 322 | "node_modules/@esbuild/sunos-x64": { 323 | "version": "0.21.5", 324 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", 325 | "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", 326 | "cpu": [ 327 | "x64" 328 | ], 329 | "dev": true, 330 | "optional": true, 331 | "os": [ 332 | "sunos" 333 | ], 334 | "engines": { 335 | "node": ">=12" 336 | } 337 | }, 338 | "node_modules/@esbuild/win32-arm64": { 339 | "version": "0.21.5", 340 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", 341 | "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", 342 | "cpu": [ 343 | "arm64" 344 | ], 345 | "dev": true, 346 | "optional": true, 347 | "os": [ 348 | "win32" 349 | ], 350 | "engines": { 351 | "node": ">=12" 352 | } 353 | }, 354 | "node_modules/@esbuild/win32-ia32": { 355 | "version": "0.21.5", 356 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", 357 | "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", 358 | "cpu": [ 359 | "ia32" 360 | ], 361 | "dev": true, 362 | "optional": true, 363 | "os": [ 364 | "win32" 365 | ], 366 | "engines": { 367 | "node": ">=12" 368 | } 369 | }, 370 | "node_modules/@esbuild/win32-x64": { 371 | "version": "0.21.5", 372 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", 373 | "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", 374 | "cpu": [ 375 | "x64" 376 | ], 377 | "dev": true, 378 | "optional": true, 379 | "os": [ 380 | "win32" 381 | ], 382 | "engines": { 383 | "node": ">=12" 384 | } 385 | }, 386 | "node_modules/@jest/schemas": { 387 | "version": "29.6.3", 388 | "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", 389 | "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", 390 | "dev": true, 391 | "dependencies": { 392 | "@sinclair/typebox": "^0.27.8" 393 | }, 394 | "engines": { 395 | "node": "^14.15.0 || ^16.10.0 || >=18.0.0" 396 | } 397 | }, 398 | "node_modules/@jridgewell/sourcemap-codec": { 399 | "version": "1.4.15", 400 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 401 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 402 | "dev": true 403 | }, 404 | "node_modules/@rollup/rollup-android-arm-eabi": { 405 | "version": "4.29.1", 406 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.29.1.tgz", 407 | "integrity": "sha512-ssKhA8RNltTZLpG6/QNkCSge+7mBQGUqJRisZ2MDQcEGaK93QESEgWK2iOpIDZ7k9zPVkG5AS3ksvD5ZWxmItw==", 408 | "cpu": [ 409 | "arm" 410 | ], 411 | "dev": true, 412 | "optional": true, 413 | "os": [ 414 | "android" 415 | ] 416 | }, 417 | "node_modules/@rollup/rollup-android-arm64": { 418 | "version": "4.29.1", 419 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.29.1.tgz", 420 | "integrity": "sha512-CaRfrV0cd+NIIcVVN/jx+hVLN+VRqnuzLRmfmlzpOzB87ajixsN/+9L5xNmkaUUvEbI5BmIKS+XTwXsHEb65Ew==", 421 | "cpu": [ 422 | "arm64" 423 | ], 424 | "dev": true, 425 | "optional": true, 426 | "os": [ 427 | "android" 428 | ] 429 | }, 430 | "node_modules/@rollup/rollup-darwin-arm64": { 431 | "version": "4.29.1", 432 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.29.1.tgz", 433 | "integrity": "sha512-2ORr7T31Y0Mnk6qNuwtyNmy14MunTAMx06VAPI6/Ju52W10zk1i7i5U3vlDRWjhOI5quBcrvhkCHyF76bI7kEw==", 434 | "cpu": [ 435 | "arm64" 436 | ], 437 | "dev": true, 438 | "optional": true, 439 | "os": [ 440 | "darwin" 441 | ] 442 | }, 443 | "node_modules/@rollup/rollup-darwin-x64": { 444 | "version": "4.29.1", 445 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.29.1.tgz", 446 | "integrity": "sha512-j/Ej1oanzPjmN0tirRd5K2/nncAhS9W6ICzgxV+9Y5ZsP0hiGhHJXZ2JQ53iSSjj8m6cRY6oB1GMzNn2EUt6Ng==", 447 | "cpu": [ 448 | "x64" 449 | ], 450 | "dev": true, 451 | "optional": true, 452 | "os": [ 453 | "darwin" 454 | ] 455 | }, 456 | "node_modules/@rollup/rollup-freebsd-arm64": { 457 | "version": "4.29.1", 458 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.29.1.tgz", 459 | "integrity": "sha512-91C//G6Dm/cv724tpt7nTyP+JdN12iqeXGFM1SqnljCmi5yTXriH7B1r8AD9dAZByHpKAumqP1Qy2vVNIdLZqw==", 460 | "cpu": [ 461 | "arm64" 462 | ], 463 | "dev": true, 464 | "optional": true, 465 | "os": [ 466 | "freebsd" 467 | ] 468 | }, 469 | "node_modules/@rollup/rollup-freebsd-x64": { 470 | "version": "4.29.1", 471 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.29.1.tgz", 472 | "integrity": "sha512-hEioiEQ9Dec2nIRoeHUP6hr1PSkXzQaCUyqBDQ9I9ik4gCXQZjJMIVzoNLBRGet+hIUb3CISMh9KXuCcWVW/8w==", 473 | "cpu": [ 474 | "x64" 475 | ], 476 | "dev": true, 477 | "optional": true, 478 | "os": [ 479 | "freebsd" 480 | ] 481 | }, 482 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 483 | "version": "4.29.1", 484 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.29.1.tgz", 485 | "integrity": "sha512-Py5vFd5HWYN9zxBv3WMrLAXY3yYJ6Q/aVERoeUFwiDGiMOWsMs7FokXihSOaT/PMWUty/Pj60XDQndK3eAfE6A==", 486 | "cpu": [ 487 | "arm" 488 | ], 489 | "dev": true, 490 | "optional": true, 491 | "os": [ 492 | "linux" 493 | ] 494 | }, 495 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 496 | "version": "4.29.1", 497 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.29.1.tgz", 498 | "integrity": "sha512-RiWpGgbayf7LUcuSNIbahr0ys2YnEERD4gYdISA06wa0i8RALrnzflh9Wxii7zQJEB2/Eh74dX4y/sHKLWp5uQ==", 499 | "cpu": [ 500 | "arm" 501 | ], 502 | "dev": true, 503 | "optional": true, 504 | "os": [ 505 | "linux" 506 | ] 507 | }, 508 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 509 | "version": "4.29.1", 510 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.29.1.tgz", 511 | "integrity": "sha512-Z80O+taYxTQITWMjm/YqNoe9d10OX6kDh8X5/rFCMuPqsKsSyDilvfg+vd3iXIqtfmp+cnfL1UrYirkaF8SBZA==", 512 | "cpu": [ 513 | "arm64" 514 | ], 515 | "dev": true, 516 | "optional": true, 517 | "os": [ 518 | "linux" 519 | ] 520 | }, 521 | "node_modules/@rollup/rollup-linux-arm64-musl": { 522 | "version": "4.29.1", 523 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.29.1.tgz", 524 | "integrity": "sha512-fOHRtF9gahwJk3QVp01a/GqS4hBEZCV1oKglVVq13kcK3NeVlS4BwIFzOHDbmKzt3i0OuHG4zfRP0YoG5OF/rA==", 525 | "cpu": [ 526 | "arm64" 527 | ], 528 | "dev": true, 529 | "optional": true, 530 | "os": [ 531 | "linux" 532 | ] 533 | }, 534 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 535 | "version": "4.29.1", 536 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.29.1.tgz", 537 | "integrity": "sha512-5a7q3tnlbcg0OodyxcAdrrCxFi0DgXJSoOuidFUzHZ2GixZXQs6Tc3CHmlvqKAmOs5eRde+JJxeIf9DonkmYkw==", 538 | "cpu": [ 539 | "loong64" 540 | ], 541 | "dev": true, 542 | "optional": true, 543 | "os": [ 544 | "linux" 545 | ] 546 | }, 547 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 548 | "version": "4.29.1", 549 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.29.1.tgz", 550 | "integrity": "sha512-9b4Mg5Yfz6mRnlSPIdROcfw1BU22FQxmfjlp/CShWwO3LilKQuMISMTtAu/bxmmrE6A902W2cZJuzx8+gJ8e9w==", 551 | "cpu": [ 552 | "ppc64" 553 | ], 554 | "dev": true, 555 | "optional": true, 556 | "os": [ 557 | "linux" 558 | ] 559 | }, 560 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 561 | "version": "4.29.1", 562 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.29.1.tgz", 563 | "integrity": "sha512-G5pn0NChlbRM8OJWpJFMX4/i8OEU538uiSv0P6roZcbpe/WfhEO+AT8SHVKfp8qhDQzaz7Q+1/ixMy7hBRidnQ==", 564 | "cpu": [ 565 | "riscv64" 566 | ], 567 | "dev": true, 568 | "optional": true, 569 | "os": [ 570 | "linux" 571 | ] 572 | }, 573 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 574 | "version": "4.29.1", 575 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.29.1.tgz", 576 | "integrity": "sha512-WM9lIkNdkhVwiArmLxFXpWndFGuOka4oJOZh8EP3Vb8q5lzdSCBuhjavJsw68Q9AKDGeOOIHYzYm4ZFvmWez5g==", 577 | "cpu": [ 578 | "s390x" 579 | ], 580 | "dev": true, 581 | "optional": true, 582 | "os": [ 583 | "linux" 584 | ] 585 | }, 586 | "node_modules/@rollup/rollup-linux-x64-gnu": { 587 | "version": "4.29.1", 588 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.29.1.tgz", 589 | "integrity": "sha512-87xYCwb0cPGZFoGiErT1eDcssByaLX4fc0z2nRM6eMtV9njAfEE6OW3UniAoDhX4Iq5xQVpE6qO9aJbCFumKYQ==", 590 | "cpu": [ 591 | "x64" 592 | ], 593 | "dev": true, 594 | "optional": true, 595 | "os": [ 596 | "linux" 597 | ] 598 | }, 599 | "node_modules/@rollup/rollup-linux-x64-musl": { 600 | "version": "4.29.1", 601 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.29.1.tgz", 602 | "integrity": "sha512-xufkSNppNOdVRCEC4WKvlR1FBDyqCSCpQeMMgv9ZyXqqtKBfkw1yfGMTUTs9Qsl6WQbJnsGboWCp7pJGkeMhKA==", 603 | "cpu": [ 604 | "x64" 605 | ], 606 | "dev": true, 607 | "optional": true, 608 | "os": [ 609 | "linux" 610 | ] 611 | }, 612 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 613 | "version": "4.29.1", 614 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.29.1.tgz", 615 | "integrity": "sha512-F2OiJ42m77lSkizZQLuC+jiZ2cgueWQL5YC9tjo3AgaEw+KJmVxHGSyQfDUoYR9cci0lAywv2Clmckzulcq6ig==", 616 | "cpu": [ 617 | "arm64" 618 | ], 619 | "dev": true, 620 | "optional": true, 621 | "os": [ 622 | "win32" 623 | ] 624 | }, 625 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 626 | "version": "4.29.1", 627 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.29.1.tgz", 628 | "integrity": "sha512-rYRe5S0FcjlOBZQHgbTKNrqxCBUmgDJem/VQTCcTnA2KCabYSWQDrytOzX7avb79cAAweNmMUb/Zw18RNd4mng==", 629 | "cpu": [ 630 | "ia32" 631 | ], 632 | "dev": true, 633 | "optional": true, 634 | "os": [ 635 | "win32" 636 | ] 637 | }, 638 | "node_modules/@rollup/rollup-win32-x64-msvc": { 639 | "version": "4.29.1", 640 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.29.1.tgz", 641 | "integrity": "sha512-+10CMg9vt1MoHj6x1pxyjPSMjHTIlqs8/tBztXvPAx24SKs9jwVnKqHJumlH/IzhaPUaj3T6T6wfZr8okdXaIg==", 642 | "cpu": [ 643 | "x64" 644 | ], 645 | "dev": true, 646 | "optional": true, 647 | "os": [ 648 | "win32" 649 | ] 650 | }, 651 | "node_modules/@sinclair/typebox": { 652 | "version": "0.27.8", 653 | "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", 654 | "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", 655 | "dev": true 656 | }, 657 | "node_modules/@types/estree": { 658 | "version": "1.0.6", 659 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 660 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 661 | "dev": true 662 | }, 663 | "node_modules/@types/node": { 664 | "version": "20.11.0", 665 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz", 666 | "integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==", 667 | "dev": true, 668 | "dependencies": { 669 | "undici-types": "~5.26.4" 670 | } 671 | }, 672 | "node_modules/@vitest/expect": { 673 | "version": "1.2.0", 674 | "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.2.0.tgz", 675 | "integrity": "sha512-H+2bHzhyvgp32o7Pgj2h9RTHN0pgYaoi26Oo3mE+dCi1PAqV31kIIVfTbqMO3Bvshd5mIrJLc73EwSRrbol9Lw==", 676 | "dev": true, 677 | "dependencies": { 678 | "@vitest/spy": "1.2.0", 679 | "@vitest/utils": "1.2.0", 680 | "chai": "^4.3.10" 681 | }, 682 | "funding": { 683 | "url": "https://opencollective.com/vitest" 684 | } 685 | }, 686 | "node_modules/@vitest/runner": { 687 | "version": "1.2.0", 688 | "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.2.0.tgz", 689 | "integrity": "sha512-vaJkDoQaNUTroT70OhM0NPznP7H3WyRwt4LvGwCVYs/llLaqhoSLnlIhUClZpbF5RgAee29KRcNz0FEhYcgxqA==", 690 | "dev": true, 691 | "dependencies": { 692 | "@vitest/utils": "1.2.0", 693 | "p-limit": "^5.0.0", 694 | "pathe": "^1.1.1" 695 | }, 696 | "funding": { 697 | "url": "https://opencollective.com/vitest" 698 | } 699 | }, 700 | "node_modules/@vitest/snapshot": { 701 | "version": "1.2.0", 702 | "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.2.0.tgz", 703 | "integrity": "sha512-P33EE7TrVgB3HDLllrjK/GG6WSnmUtWohbwcQqmm7TAk9AVHpdgf7M3F3qRHKm6vhr7x3eGIln7VH052Smo6Kw==", 704 | "dev": true, 705 | "dependencies": { 706 | "magic-string": "^0.30.5", 707 | "pathe": "^1.1.1", 708 | "pretty-format": "^29.7.0" 709 | }, 710 | "funding": { 711 | "url": "https://opencollective.com/vitest" 712 | } 713 | }, 714 | "node_modules/@vitest/spy": { 715 | "version": "1.2.0", 716 | "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.2.0.tgz", 717 | "integrity": "sha512-MNxSAfxUaCeowqyyGwC293yZgk7cECZU9wGb8N1pYQ0yOn/SIr8t0l9XnGRdQZvNV/ZHBYu6GO/W3tj5K3VN1Q==", 718 | "dev": true, 719 | "dependencies": { 720 | "tinyspy": "^2.2.0" 721 | }, 722 | "funding": { 723 | "url": "https://opencollective.com/vitest" 724 | } 725 | }, 726 | "node_modules/@vitest/utils": { 727 | "version": "1.2.0", 728 | "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.2.0.tgz", 729 | "integrity": "sha512-FyD5bpugsXlwVpTcGLDf3wSPYy8g541fQt14qtzo8mJ4LdEpDKZ9mQy2+qdJm2TZRpjY5JLXihXCgIxiRJgi5g==", 730 | "dev": true, 731 | "dependencies": { 732 | "diff-sequences": "^29.6.3", 733 | "estree-walker": "^3.0.3", 734 | "loupe": "^2.3.7", 735 | "pretty-format": "^29.7.0" 736 | }, 737 | "funding": { 738 | "url": "https://opencollective.com/vitest" 739 | } 740 | }, 741 | "node_modules/acorn": { 742 | "version": "8.11.3", 743 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 744 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 745 | "dev": true, 746 | "bin": { 747 | "acorn": "bin/acorn" 748 | }, 749 | "engines": { 750 | "node": ">=0.4.0" 751 | } 752 | }, 753 | "node_modules/acorn-walk": { 754 | "version": "8.3.2", 755 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 756 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 757 | "dev": true, 758 | "engines": { 759 | "node": ">=0.4.0" 760 | } 761 | }, 762 | "node_modules/ansi-styles": { 763 | "version": "5.2.0", 764 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", 765 | "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", 766 | "dev": true, 767 | "engines": { 768 | "node": ">=10" 769 | }, 770 | "funding": { 771 | "url": "https://github.com/chalk/ansi-styles?sponsor=1" 772 | } 773 | }, 774 | "node_modules/assertion-error": { 775 | "version": "1.1.0", 776 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 777 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 778 | "dev": true, 779 | "engines": { 780 | "node": "*" 781 | } 782 | }, 783 | "node_modules/cac": { 784 | "version": "6.7.14", 785 | "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", 786 | "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", 787 | "dev": true, 788 | "engines": { 789 | "node": ">=8" 790 | } 791 | }, 792 | "node_modules/chai": { 793 | "version": "4.4.1", 794 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", 795 | "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", 796 | "dev": true, 797 | "dependencies": { 798 | "assertion-error": "^1.1.0", 799 | "check-error": "^1.0.3", 800 | "deep-eql": "^4.1.3", 801 | "get-func-name": "^2.0.2", 802 | "loupe": "^2.3.6", 803 | "pathval": "^1.1.1", 804 | "type-detect": "^4.0.8" 805 | }, 806 | "engines": { 807 | "node": ">=4" 808 | } 809 | }, 810 | "node_modules/check-error": { 811 | "version": "1.0.3", 812 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", 813 | "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", 814 | "dev": true, 815 | "dependencies": { 816 | "get-func-name": "^2.0.2" 817 | }, 818 | "engines": { 819 | "node": "*" 820 | } 821 | }, 822 | "node_modules/cross-spawn": { 823 | "version": "7.0.6", 824 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 825 | "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 826 | "dev": true, 827 | "dependencies": { 828 | "path-key": "^3.1.0", 829 | "shebang-command": "^2.0.0", 830 | "which": "^2.0.1" 831 | }, 832 | "engines": { 833 | "node": ">= 8" 834 | } 835 | }, 836 | "node_modules/debug": { 837 | "version": "4.3.4", 838 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", 839 | "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", 840 | "dev": true, 841 | "dependencies": { 842 | "ms": "2.1.2" 843 | }, 844 | "engines": { 845 | "node": ">=6.0" 846 | }, 847 | "peerDependenciesMeta": { 848 | "supports-color": { 849 | "optional": true 850 | } 851 | } 852 | }, 853 | "node_modules/deep-eql": { 854 | "version": "4.1.3", 855 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", 856 | "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", 857 | "dev": true, 858 | "dependencies": { 859 | "type-detect": "^4.0.0" 860 | }, 861 | "engines": { 862 | "node": ">=6" 863 | } 864 | }, 865 | "node_modules/diff-sequences": { 866 | "version": "29.6.3", 867 | "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", 868 | "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", 869 | "dev": true, 870 | "engines": { 871 | "node": "^14.15.0 || ^16.10.0 || >=18.0.0" 872 | } 873 | }, 874 | "node_modules/esbuild": { 875 | "version": "0.21.5", 876 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", 877 | "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", 878 | "dev": true, 879 | "hasInstallScript": true, 880 | "bin": { 881 | "esbuild": "bin/esbuild" 882 | }, 883 | "engines": { 884 | "node": ">=12" 885 | }, 886 | "optionalDependencies": { 887 | "@esbuild/aix-ppc64": "0.21.5", 888 | "@esbuild/android-arm": "0.21.5", 889 | "@esbuild/android-arm64": "0.21.5", 890 | "@esbuild/android-x64": "0.21.5", 891 | "@esbuild/darwin-arm64": "0.21.5", 892 | "@esbuild/darwin-x64": "0.21.5", 893 | "@esbuild/freebsd-arm64": "0.21.5", 894 | "@esbuild/freebsd-x64": "0.21.5", 895 | "@esbuild/linux-arm": "0.21.5", 896 | "@esbuild/linux-arm64": "0.21.5", 897 | "@esbuild/linux-ia32": "0.21.5", 898 | "@esbuild/linux-loong64": "0.21.5", 899 | "@esbuild/linux-mips64el": "0.21.5", 900 | "@esbuild/linux-ppc64": "0.21.5", 901 | "@esbuild/linux-riscv64": "0.21.5", 902 | "@esbuild/linux-s390x": "0.21.5", 903 | "@esbuild/linux-x64": "0.21.5", 904 | "@esbuild/netbsd-x64": "0.21.5", 905 | "@esbuild/openbsd-x64": "0.21.5", 906 | "@esbuild/sunos-x64": "0.21.5", 907 | "@esbuild/win32-arm64": "0.21.5", 908 | "@esbuild/win32-ia32": "0.21.5", 909 | "@esbuild/win32-x64": "0.21.5" 910 | } 911 | }, 912 | "node_modules/estree-walker": { 913 | "version": "3.0.3", 914 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", 915 | "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", 916 | "dev": true, 917 | "dependencies": { 918 | "@types/estree": "^1.0.0" 919 | } 920 | }, 921 | "node_modules/execa": { 922 | "version": "8.0.1", 923 | "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", 924 | "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", 925 | "dev": true, 926 | "dependencies": { 927 | "cross-spawn": "^7.0.3", 928 | "get-stream": "^8.0.1", 929 | "human-signals": "^5.0.0", 930 | "is-stream": "^3.0.0", 931 | "merge-stream": "^2.0.0", 932 | "npm-run-path": "^5.1.0", 933 | "onetime": "^6.0.0", 934 | "signal-exit": "^4.1.0", 935 | "strip-final-newline": "^3.0.0" 936 | }, 937 | "engines": { 938 | "node": ">=16.17" 939 | }, 940 | "funding": { 941 | "url": "https://github.com/sindresorhus/execa?sponsor=1" 942 | } 943 | }, 944 | "node_modules/fsevents": { 945 | "version": "2.3.3", 946 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 947 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 948 | "dev": true, 949 | "hasInstallScript": true, 950 | "optional": true, 951 | "os": [ 952 | "darwin" 953 | ], 954 | "engines": { 955 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 956 | } 957 | }, 958 | "node_modules/get-func-name": { 959 | "version": "2.0.2", 960 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", 961 | "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", 962 | "dev": true, 963 | "engines": { 964 | "node": "*" 965 | } 966 | }, 967 | "node_modules/get-stream": { 968 | "version": "8.0.1", 969 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", 970 | "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", 971 | "dev": true, 972 | "engines": { 973 | "node": ">=16" 974 | }, 975 | "funding": { 976 | "url": "https://github.com/sponsors/sindresorhus" 977 | } 978 | }, 979 | "node_modules/human-signals": { 980 | "version": "5.0.0", 981 | "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", 982 | "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", 983 | "dev": true, 984 | "engines": { 985 | "node": ">=16.17.0" 986 | } 987 | }, 988 | "node_modules/is-stream": { 989 | "version": "3.0.0", 990 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", 991 | "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", 992 | "dev": true, 993 | "engines": { 994 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 995 | }, 996 | "funding": { 997 | "url": "https://github.com/sponsors/sindresorhus" 998 | } 999 | }, 1000 | "node_modules/isexe": { 1001 | "version": "2.0.0", 1002 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 1003 | "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", 1004 | "dev": true 1005 | }, 1006 | "node_modules/jsonc-parser": { 1007 | "version": "3.2.0", 1008 | "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", 1009 | "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==", 1010 | "dev": true 1011 | }, 1012 | "node_modules/local-pkg": { 1013 | "version": "0.5.0", 1014 | "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.0.tgz", 1015 | "integrity": "sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==", 1016 | "dev": true, 1017 | "dependencies": { 1018 | "mlly": "^1.4.2", 1019 | "pkg-types": "^1.0.3" 1020 | }, 1021 | "engines": { 1022 | "node": ">=14" 1023 | }, 1024 | "funding": { 1025 | "url": "https://github.com/sponsors/antfu" 1026 | } 1027 | }, 1028 | "node_modules/loupe": { 1029 | "version": "2.3.7", 1030 | "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", 1031 | "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", 1032 | "dev": true, 1033 | "dependencies": { 1034 | "get-func-name": "^2.0.1" 1035 | } 1036 | }, 1037 | "node_modules/magic-string": { 1038 | "version": "0.30.5", 1039 | "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.5.tgz", 1040 | "integrity": "sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==", 1041 | "dev": true, 1042 | "dependencies": { 1043 | "@jridgewell/sourcemap-codec": "^1.4.15" 1044 | }, 1045 | "engines": { 1046 | "node": ">=12" 1047 | } 1048 | }, 1049 | "node_modules/merge-stream": { 1050 | "version": "2.0.0", 1051 | "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", 1052 | "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", 1053 | "dev": true 1054 | }, 1055 | "node_modules/mimic-fn": { 1056 | "version": "4.0.0", 1057 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", 1058 | "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", 1059 | "dev": true, 1060 | "engines": { 1061 | "node": ">=12" 1062 | }, 1063 | "funding": { 1064 | "url": "https://github.com/sponsors/sindresorhus" 1065 | } 1066 | }, 1067 | "node_modules/mlly": { 1068 | "version": "1.5.0", 1069 | "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.5.0.tgz", 1070 | "integrity": "sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==", 1071 | "dev": true, 1072 | "dependencies": { 1073 | "acorn": "^8.11.3", 1074 | "pathe": "^1.1.2", 1075 | "pkg-types": "^1.0.3", 1076 | "ufo": "^1.3.2" 1077 | } 1078 | }, 1079 | "node_modules/ms": { 1080 | "version": "2.1.2", 1081 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", 1082 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", 1083 | "dev": true 1084 | }, 1085 | "node_modules/nanoid": { 1086 | "version": "3.3.8", 1087 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", 1088 | "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", 1089 | "dev": true, 1090 | "funding": [ 1091 | { 1092 | "type": "github", 1093 | "url": "https://github.com/sponsors/ai" 1094 | } 1095 | ], 1096 | "bin": { 1097 | "nanoid": "bin/nanoid.cjs" 1098 | }, 1099 | "engines": { 1100 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1101 | } 1102 | }, 1103 | "node_modules/npm-run-path": { 1104 | "version": "5.2.0", 1105 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.2.0.tgz", 1106 | "integrity": "sha512-W4/tgAXFqFA0iL7fk0+uQ3g7wkL8xJmx3XdK0VGb4cHW//eZTtKGvFBBoRKVTpY7n6ze4NL9ly7rgXcHufqXKg==", 1107 | "dev": true, 1108 | "dependencies": { 1109 | "path-key": "^4.0.0" 1110 | }, 1111 | "engines": { 1112 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 1113 | }, 1114 | "funding": { 1115 | "url": "https://github.com/sponsors/sindresorhus" 1116 | } 1117 | }, 1118 | "node_modules/npm-run-path/node_modules/path-key": { 1119 | "version": "4.0.0", 1120 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", 1121 | "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", 1122 | "dev": true, 1123 | "engines": { 1124 | "node": ">=12" 1125 | }, 1126 | "funding": { 1127 | "url": "https://github.com/sponsors/sindresorhus" 1128 | } 1129 | }, 1130 | "node_modules/onetime": { 1131 | "version": "6.0.0", 1132 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", 1133 | "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", 1134 | "dev": true, 1135 | "dependencies": { 1136 | "mimic-fn": "^4.0.0" 1137 | }, 1138 | "engines": { 1139 | "node": ">=12" 1140 | }, 1141 | "funding": { 1142 | "url": "https://github.com/sponsors/sindresorhus" 1143 | } 1144 | }, 1145 | "node_modules/p-limit": { 1146 | "version": "5.0.0", 1147 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", 1148 | "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", 1149 | "dev": true, 1150 | "dependencies": { 1151 | "yocto-queue": "^1.0.0" 1152 | }, 1153 | "engines": { 1154 | "node": ">=18" 1155 | }, 1156 | "funding": { 1157 | "url": "https://github.com/sponsors/sindresorhus" 1158 | } 1159 | }, 1160 | "node_modules/path-key": { 1161 | "version": "3.1.1", 1162 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 1163 | "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 1164 | "dev": true, 1165 | "engines": { 1166 | "node": ">=8" 1167 | } 1168 | }, 1169 | "node_modules/pathe": { 1170 | "version": "1.1.2", 1171 | "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", 1172 | "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", 1173 | "dev": true 1174 | }, 1175 | "node_modules/pathval": { 1176 | "version": "1.1.1", 1177 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", 1178 | "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", 1179 | "dev": true, 1180 | "engines": { 1181 | "node": "*" 1182 | } 1183 | }, 1184 | "node_modules/picocolors": { 1185 | "version": "1.1.1", 1186 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", 1187 | "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", 1188 | "dev": true 1189 | }, 1190 | "node_modules/pkg-types": { 1191 | "version": "1.0.3", 1192 | "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.0.3.tgz", 1193 | "integrity": "sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==", 1194 | "dev": true, 1195 | "dependencies": { 1196 | "jsonc-parser": "^3.2.0", 1197 | "mlly": "^1.2.0", 1198 | "pathe": "^1.1.0" 1199 | } 1200 | }, 1201 | "node_modules/postcss": { 1202 | "version": "8.4.49", 1203 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", 1204 | "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", 1205 | "dev": true, 1206 | "funding": [ 1207 | { 1208 | "type": "opencollective", 1209 | "url": "https://opencollective.com/postcss/" 1210 | }, 1211 | { 1212 | "type": "tidelift", 1213 | "url": "https://tidelift.com/funding/github/npm/postcss" 1214 | }, 1215 | { 1216 | "type": "github", 1217 | "url": "https://github.com/sponsors/ai" 1218 | } 1219 | ], 1220 | "dependencies": { 1221 | "nanoid": "^3.3.7", 1222 | "picocolors": "^1.1.1", 1223 | "source-map-js": "^1.2.1" 1224 | }, 1225 | "engines": { 1226 | "node": "^10 || ^12 || >=14" 1227 | } 1228 | }, 1229 | "node_modules/prettier": { 1230 | "version": "3.2.2", 1231 | "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.2.tgz", 1232 | "integrity": "sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==", 1233 | "dev": true, 1234 | "bin": { 1235 | "prettier": "bin/prettier.cjs" 1236 | }, 1237 | "engines": { 1238 | "node": ">=14" 1239 | }, 1240 | "funding": { 1241 | "url": "https://github.com/prettier/prettier?sponsor=1" 1242 | } 1243 | }, 1244 | "node_modules/pretty-format": { 1245 | "version": "29.7.0", 1246 | "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", 1247 | "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", 1248 | "dev": true, 1249 | "dependencies": { 1250 | "@jest/schemas": "^29.6.3", 1251 | "ansi-styles": "^5.0.0", 1252 | "react-is": "^18.0.0" 1253 | }, 1254 | "engines": { 1255 | "node": "^14.15.0 || ^16.10.0 || >=18.0.0" 1256 | } 1257 | }, 1258 | "node_modules/react-is": { 1259 | "version": "18.2.0", 1260 | "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", 1261 | "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", 1262 | "dev": true 1263 | }, 1264 | "node_modules/rollup": { 1265 | "version": "4.29.1", 1266 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.29.1.tgz", 1267 | "integrity": "sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==", 1268 | "dev": true, 1269 | "dependencies": { 1270 | "@types/estree": "1.0.6" 1271 | }, 1272 | "bin": { 1273 | "rollup": "dist/bin/rollup" 1274 | }, 1275 | "engines": { 1276 | "node": ">=18.0.0", 1277 | "npm": ">=8.0.0" 1278 | }, 1279 | "optionalDependencies": { 1280 | "@rollup/rollup-android-arm-eabi": "4.29.1", 1281 | "@rollup/rollup-android-arm64": "4.29.1", 1282 | "@rollup/rollup-darwin-arm64": "4.29.1", 1283 | "@rollup/rollup-darwin-x64": "4.29.1", 1284 | "@rollup/rollup-freebsd-arm64": "4.29.1", 1285 | "@rollup/rollup-freebsd-x64": "4.29.1", 1286 | "@rollup/rollup-linux-arm-gnueabihf": "4.29.1", 1287 | "@rollup/rollup-linux-arm-musleabihf": "4.29.1", 1288 | "@rollup/rollup-linux-arm64-gnu": "4.29.1", 1289 | "@rollup/rollup-linux-arm64-musl": "4.29.1", 1290 | "@rollup/rollup-linux-loongarch64-gnu": "4.29.1", 1291 | "@rollup/rollup-linux-powerpc64le-gnu": "4.29.1", 1292 | "@rollup/rollup-linux-riscv64-gnu": "4.29.1", 1293 | "@rollup/rollup-linux-s390x-gnu": "4.29.1", 1294 | "@rollup/rollup-linux-x64-gnu": "4.29.1", 1295 | "@rollup/rollup-linux-x64-musl": "4.29.1", 1296 | "@rollup/rollup-win32-arm64-msvc": "4.29.1", 1297 | "@rollup/rollup-win32-ia32-msvc": "4.29.1", 1298 | "@rollup/rollup-win32-x64-msvc": "4.29.1", 1299 | "fsevents": "~2.3.2" 1300 | } 1301 | }, 1302 | "node_modules/shebang-command": { 1303 | "version": "2.0.0", 1304 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 1305 | "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 1306 | "dev": true, 1307 | "dependencies": { 1308 | "shebang-regex": "^3.0.0" 1309 | }, 1310 | "engines": { 1311 | "node": ">=8" 1312 | } 1313 | }, 1314 | "node_modules/shebang-regex": { 1315 | "version": "3.0.0", 1316 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 1317 | "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 1318 | "dev": true, 1319 | "engines": { 1320 | "node": ">=8" 1321 | } 1322 | }, 1323 | "node_modules/siginfo": { 1324 | "version": "2.0.0", 1325 | "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", 1326 | "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", 1327 | "dev": true 1328 | }, 1329 | "node_modules/signal-exit": { 1330 | "version": "4.1.0", 1331 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", 1332 | "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", 1333 | "dev": true, 1334 | "engines": { 1335 | "node": ">=14" 1336 | }, 1337 | "funding": { 1338 | "url": "https://github.com/sponsors/isaacs" 1339 | } 1340 | }, 1341 | "node_modules/source-map-js": { 1342 | "version": "1.2.1", 1343 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", 1344 | "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", 1345 | "dev": true, 1346 | "engines": { 1347 | "node": ">=0.10.0" 1348 | } 1349 | }, 1350 | "node_modules/stackback": { 1351 | "version": "0.0.2", 1352 | "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", 1353 | "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", 1354 | "dev": true 1355 | }, 1356 | "node_modules/std-env": { 1357 | "version": "3.7.0", 1358 | "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", 1359 | "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==", 1360 | "dev": true 1361 | }, 1362 | "node_modules/strip-final-newline": { 1363 | "version": "3.0.0", 1364 | "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", 1365 | "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", 1366 | "dev": true, 1367 | "engines": { 1368 | "node": ">=12" 1369 | }, 1370 | "funding": { 1371 | "url": "https://github.com/sponsors/sindresorhus" 1372 | } 1373 | }, 1374 | "node_modules/strip-literal": { 1375 | "version": "1.3.0", 1376 | "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-1.3.0.tgz", 1377 | "integrity": "sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==", 1378 | "dev": true, 1379 | "dependencies": { 1380 | "acorn": "^8.10.0" 1381 | }, 1382 | "funding": { 1383 | "url": "https://github.com/sponsors/antfu" 1384 | } 1385 | }, 1386 | "node_modules/tinybench": { 1387 | "version": "2.5.1", 1388 | "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.5.1.tgz", 1389 | "integrity": "sha512-65NKvSuAVDP/n4CqH+a9w2kTlLReS9vhsAP06MWx+/89nMinJyB2icyl58RIcqCmIggpojIGeuJGhjU1aGMBSg==", 1390 | "dev": true 1391 | }, 1392 | "node_modules/tinypool": { 1393 | "version": "0.8.1", 1394 | "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.1.tgz", 1395 | "integrity": "sha512-zBTCK0cCgRROxvs9c0CGK838sPkeokNGdQVUUwHAbynHFlmyJYj825f/oRs528HaIJ97lo0pLIlDUzwN+IorWg==", 1396 | "dev": true, 1397 | "engines": { 1398 | "node": ">=14.0.0" 1399 | } 1400 | }, 1401 | "node_modules/tinyspy": { 1402 | "version": "2.2.0", 1403 | "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.0.tgz", 1404 | "integrity": "sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==", 1405 | "dev": true, 1406 | "engines": { 1407 | "node": ">=14.0.0" 1408 | } 1409 | }, 1410 | "node_modules/type-detect": { 1411 | "version": "4.0.8", 1412 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 1413 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 1414 | "dev": true, 1415 | "engines": { 1416 | "node": ">=4" 1417 | } 1418 | }, 1419 | "node_modules/typescript": { 1420 | "version": "5.3.3", 1421 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 1422 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 1423 | "dev": true, 1424 | "bin": { 1425 | "tsc": "bin/tsc", 1426 | "tsserver": "bin/tsserver" 1427 | }, 1428 | "engines": { 1429 | "node": ">=14.17" 1430 | } 1431 | }, 1432 | "node_modules/ufo": { 1433 | "version": "1.3.2", 1434 | "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.2.tgz", 1435 | "integrity": "sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==", 1436 | "dev": true 1437 | }, 1438 | "node_modules/undici-types": { 1439 | "version": "5.26.5", 1440 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 1441 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 1442 | "dev": true 1443 | }, 1444 | "node_modules/vite": { 1445 | "version": "5.4.11", 1446 | "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", 1447 | "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", 1448 | "dev": true, 1449 | "dependencies": { 1450 | "esbuild": "^0.21.3", 1451 | "postcss": "^8.4.43", 1452 | "rollup": "^4.20.0" 1453 | }, 1454 | "bin": { 1455 | "vite": "bin/vite.js" 1456 | }, 1457 | "engines": { 1458 | "node": "^18.0.0 || >=20.0.0" 1459 | }, 1460 | "funding": { 1461 | "url": "https://github.com/vitejs/vite?sponsor=1" 1462 | }, 1463 | "optionalDependencies": { 1464 | "fsevents": "~2.3.3" 1465 | }, 1466 | "peerDependencies": { 1467 | "@types/node": "^18.0.0 || >=20.0.0", 1468 | "less": "*", 1469 | "lightningcss": "^1.21.0", 1470 | "sass": "*", 1471 | "sass-embedded": "*", 1472 | "stylus": "*", 1473 | "sugarss": "*", 1474 | "terser": "^5.4.0" 1475 | }, 1476 | "peerDependenciesMeta": { 1477 | "@types/node": { 1478 | "optional": true 1479 | }, 1480 | "less": { 1481 | "optional": true 1482 | }, 1483 | "lightningcss": { 1484 | "optional": true 1485 | }, 1486 | "sass": { 1487 | "optional": true 1488 | }, 1489 | "sass-embedded": { 1490 | "optional": true 1491 | }, 1492 | "stylus": { 1493 | "optional": true 1494 | }, 1495 | "sugarss": { 1496 | "optional": true 1497 | }, 1498 | "terser": { 1499 | "optional": true 1500 | } 1501 | } 1502 | }, 1503 | "node_modules/vite-node": { 1504 | "version": "1.2.0", 1505 | "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.2.0.tgz", 1506 | "integrity": "sha512-ETnQTHeAbbOxl7/pyBck9oAPZZZo+kYnFt1uQDD+hPReOc+wCjXw4r4jHriBRuVDB5isHmPXxrfc1yJnfBERqg==", 1507 | "dev": true, 1508 | "dependencies": { 1509 | "cac": "^6.7.14", 1510 | "debug": "^4.3.4", 1511 | "pathe": "^1.1.1", 1512 | "picocolors": "^1.0.0", 1513 | "vite": "^5.0.0" 1514 | }, 1515 | "bin": { 1516 | "vite-node": "vite-node.mjs" 1517 | }, 1518 | "engines": { 1519 | "node": "^18.0.0 || >=20.0.0" 1520 | }, 1521 | "funding": { 1522 | "url": "https://opencollective.com/vitest" 1523 | } 1524 | }, 1525 | "node_modules/vitest": { 1526 | "version": "1.2.0", 1527 | "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.2.0.tgz", 1528 | "integrity": "sha512-Ixs5m7BjqvLHXcibkzKRQUvD/XLw0E3rvqaCMlrm/0LMsA0309ZqYvTlPzkhh81VlEyVZXFlwWnkhb6/UMtcaQ==", 1529 | "dev": true, 1530 | "dependencies": { 1531 | "@vitest/expect": "1.2.0", 1532 | "@vitest/runner": "1.2.0", 1533 | "@vitest/snapshot": "1.2.0", 1534 | "@vitest/spy": "1.2.0", 1535 | "@vitest/utils": "1.2.0", 1536 | "acorn-walk": "^8.3.1", 1537 | "cac": "^6.7.14", 1538 | "chai": "^4.3.10", 1539 | "debug": "^4.3.4", 1540 | "execa": "^8.0.1", 1541 | "local-pkg": "^0.5.0", 1542 | "magic-string": "^0.30.5", 1543 | "pathe": "^1.1.1", 1544 | "picocolors": "^1.0.0", 1545 | "std-env": "^3.5.0", 1546 | "strip-literal": "^1.3.0", 1547 | "tinybench": "^2.5.1", 1548 | "tinypool": "^0.8.1", 1549 | "vite": "^5.0.0", 1550 | "vite-node": "1.2.0", 1551 | "why-is-node-running": "^2.2.2" 1552 | }, 1553 | "bin": { 1554 | "vitest": "vitest.mjs" 1555 | }, 1556 | "engines": { 1557 | "node": "^18.0.0 || >=20.0.0" 1558 | }, 1559 | "funding": { 1560 | "url": "https://opencollective.com/vitest" 1561 | }, 1562 | "peerDependencies": { 1563 | "@edge-runtime/vm": "*", 1564 | "@types/node": "^18.0.0 || >=20.0.0", 1565 | "@vitest/browser": "^1.0.0", 1566 | "@vitest/ui": "^1.0.0", 1567 | "happy-dom": "*", 1568 | "jsdom": "*" 1569 | }, 1570 | "peerDependenciesMeta": { 1571 | "@edge-runtime/vm": { 1572 | "optional": true 1573 | }, 1574 | "@types/node": { 1575 | "optional": true 1576 | }, 1577 | "@vitest/browser": { 1578 | "optional": true 1579 | }, 1580 | "@vitest/ui": { 1581 | "optional": true 1582 | }, 1583 | "happy-dom": { 1584 | "optional": true 1585 | }, 1586 | "jsdom": { 1587 | "optional": true 1588 | } 1589 | } 1590 | }, 1591 | "node_modules/which": { 1592 | "version": "2.0.2", 1593 | "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 1594 | "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 1595 | "dev": true, 1596 | "dependencies": { 1597 | "isexe": "^2.0.0" 1598 | }, 1599 | "bin": { 1600 | "node-which": "bin/node-which" 1601 | }, 1602 | "engines": { 1603 | "node": ">= 8" 1604 | } 1605 | }, 1606 | "node_modules/why-is-node-running": { 1607 | "version": "2.2.2", 1608 | "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.2.2.tgz", 1609 | "integrity": "sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==", 1610 | "dev": true, 1611 | "dependencies": { 1612 | "siginfo": "^2.0.0", 1613 | "stackback": "0.0.2" 1614 | }, 1615 | "bin": { 1616 | "why-is-node-running": "cli.js" 1617 | }, 1618 | "engines": { 1619 | "node": ">=8" 1620 | } 1621 | }, 1622 | "node_modules/yocto-queue": { 1623 | "version": "1.0.0", 1624 | "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz", 1625 | "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", 1626 | "dev": true, 1627 | "engines": { 1628 | "node": ">=12.20" 1629 | }, 1630 | "funding": { 1631 | "url": "https://github.com/sponsors/sindresorhus" 1632 | } 1633 | } 1634 | } 1635 | } 1636 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nice-path", 3 | "version": "3.0.0", 4 | "main": "dist/index.js", 5 | "types": "dist/index.d.ts", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/suchipi/nice-path.git" 9 | }, 10 | "author": "Lily Skye ", 11 | "license": "MIT", 12 | "keywords": [ 13 | "path", 14 | "directory", 15 | "filesystem", 16 | "fs", 17 | "file" 18 | ], 19 | "devDependencies": { 20 | "@types/node": "^20.11.0", 21 | "prettier": "^3.2.2", 22 | "typescript": "^5.3.3", 23 | "vitest": "^1.2.0" 24 | }, 25 | "scripts": { 26 | "build": "rm -rf dist && tsc", 27 | "build:watch": "tsc -w", 28 | "test": "vitest" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { test, expect } from "vitest"; 2 | import { Path } from "./index"; 3 | 4 | test("Path.splitToSegments", async () => { 5 | const result = [ 6 | Path.splitToSegments("/some/path/some/where"), 7 | Path.splitToSegments("/with/trailing/slash/"), 8 | Path.splitToSegments("./this/one's/relative"), 9 | Path.splitToSegments(".."), 10 | Path.splitToSegments("../yeah"), 11 | Path.splitToSegments("hi"), 12 | Path.splitToSegments("hello/mario"), 13 | Path.splitToSegments("///what/"), 14 | Path.splitToSegments("/who//tf//keeps putting/double/slashes/"), 15 | ]; 16 | 17 | expect(result).toMatchInlineSnapshot(` 18 | [ 19 | [ 20 | "", 21 | "some", 22 | "path", 23 | "some", 24 | "where", 25 | ], 26 | [ 27 | "", 28 | "with", 29 | "trailing", 30 | "slash", 31 | ], 32 | [ 33 | ".", 34 | "this", 35 | "one's", 36 | "relative", 37 | ], 38 | [ 39 | "..", 40 | ], 41 | [ 42 | "..", 43 | "yeah", 44 | ], 45 | [ 46 | "hi", 47 | ], 48 | [ 49 | "hello", 50 | "mario", 51 | ], 52 | [ 53 | "", 54 | "what", 55 | ], 56 | [ 57 | "", 58 | "who", 59 | "tf", 60 | "keeps putting", 61 | "double", 62 | "slashes", 63 | ], 64 | ] 65 | `); 66 | }); 67 | 68 | test("Path.splitToSegments (windows-style paths)", async () => { 69 | const result = [ 70 | Path.splitToSegments("C:\\some\\path\\some\\where"), 71 | Path.splitToSegments("D:\\with\\trailing\\slash\\"), 72 | Path.splitToSegments(".\\this\\one's\\relative"), 73 | Path.splitToSegments(".."), 74 | Path.splitToSegments("..\\yeah"), 75 | Path.splitToSegments("hi"), 76 | Path.splitToSegments("hello\\mario"), 77 | Path.splitToSegments("E:\\what\\"), 78 | Path.splitToSegments( 79 | "Z:\\\\who\\\\tf\\\\keeps putting\\\\double\\\\slashes\\\\", 80 | ), 81 | Path.splitToSegments("\\\\SERVERNAME\\ShareName$\\file.txt"), 82 | ]; 83 | 84 | expect(result).toMatchInlineSnapshot(` 85 | [ 86 | [ 87 | "C:", 88 | "some", 89 | "path", 90 | "some", 91 | "where", 92 | ], 93 | [ 94 | "D:", 95 | "with", 96 | "trailing", 97 | "slash", 98 | ], 99 | [ 100 | ".", 101 | "this", 102 | "one's", 103 | "relative", 104 | ], 105 | [ 106 | "..", 107 | ], 108 | [ 109 | "..", 110 | "yeah", 111 | ], 112 | [ 113 | "hi", 114 | ], 115 | [ 116 | "hello", 117 | "mario", 118 | ], 119 | [ 120 | "E:", 121 | "what", 122 | ], 123 | [ 124 | "Z:", 125 | "who", 126 | "tf", 127 | "keeps putting", 128 | "double", 129 | "slashes", 130 | ], 131 | [ 132 | "", 133 | "", 134 | "SERVERNAME", 135 | "ShareName$", 136 | "file.txt", 137 | ], 138 | ] 139 | `); 140 | }); 141 | 142 | test("Path.detectSeparator", async () => { 143 | const result = [ 144 | Path.detectSeparator("./hi/there", "/"), 145 | Path.detectSeparator(".\\hi\\there", "/"), 146 | Path.detectSeparator("hi", "/"), 147 | Path.detectSeparator("hi", "\\"), 148 | Path.detectSeparator("hi", null), 149 | ]; 150 | 151 | expect(result).toMatchInlineSnapshot(` 152 | [ 153 | "/", 154 | "\\", 155 | "/", 156 | "\\", 157 | null, 158 | ] 159 | `); 160 | }); 161 | 162 | test("Path.normalize with absolute path with . and ..s in it", async () => { 163 | const result = Path.normalize("/hi/./there/yeah/../yup/./"); 164 | expect(result).toMatchInlineSnapshot(` 165 | Path { 166 | "segments": [ 167 | "", 168 | "hi", 169 | "there", 170 | "yup", 171 | ], 172 | "separator": "/", 173 | } 174 | `); 175 | }); 176 | 177 | test("Path.normalize with non-absolute path with . and ..s in it", async () => { 178 | const result = Path.normalize("hi/./there/yeah/../yup/./"); 179 | expect(result).toMatchInlineSnapshot(` 180 | Path { 181 | "segments": [ 182 | "hi", 183 | "there", 184 | "yup", 185 | ], 186 | "separator": "/", 187 | } 188 | `); 189 | }); 190 | 191 | test("Path.normalize with already-absolute path", async () => { 192 | const result = Path.normalize("/hi/there/yeah"); 193 | expect(result).toMatchInlineSnapshot(` 194 | Path { 195 | "segments": [ 196 | "", 197 | "hi", 198 | "there", 199 | "yeah", 200 | ], 201 | "separator": "/", 202 | } 203 | `); 204 | }); 205 | 206 | test("Path.normalize with non-absolute path with no . or .. in it", async () => { 207 | const result = Path.normalize("hi/there/yeah"); 208 | expect(result).toMatchInlineSnapshot(` 209 | Path { 210 | "segments": [ 211 | "hi", 212 | "there", 213 | "yeah", 214 | ], 215 | "separator": "/", 216 | } 217 | `); 218 | }); 219 | 220 | test("Path.normalize with non-absolute path with leading .", async () => { 221 | const result = Path.normalize("./hi/there/yeah"); 222 | expect(result).toMatchInlineSnapshot(` 223 | Path { 224 | "segments": [ 225 | ".", 226 | "hi", 227 | "there", 228 | "yeah", 229 | ], 230 | "separator": "/", 231 | } 232 | `); 233 | }); 234 | 235 | test("Path.normalize with non-absolute path with leading ..", async () => { 236 | const result = Path.normalize("../hi/there/yeah"); 237 | expect(result).toMatchInlineSnapshot(` 238 | Path { 239 | "segments": [ 240 | "..", 241 | "hi", 242 | "there", 243 | "yeah", 244 | ], 245 | "separator": "/", 246 | } 247 | `); 248 | }); 249 | 250 | test("Path.relativeTo", async () => { 251 | const result = [ 252 | new Path("/tmp/a/b/c").relativeTo("/tmp").toString(), 253 | new Path("/tmp/a/b/c").relativeTo("/").toString(), 254 | new Path("/tmp/a/b/c").relativeTo("/tmp/a/b/c/d/e").toString(), 255 | new Path("/tmp/a/b/c").relativeTo("/tmp/a/b/f/g/h").toString(), 256 | 257 | new Path("/home/suchipi/Code/something/src/index.ts") 258 | .relativeTo("/home/suchipi/Code/something") 259 | .toString(), 260 | new Path("/home/suchipi/Code/something/src/index.ts") 261 | .relativeTo("/home/suchipi/Code/something", { noLeadingDot: true }) 262 | .toString(), 263 | 264 | new Path("/home/suchipi/Code/something/src/index.ts") 265 | .relativeTo("/home/suchipi/Code/something-else") 266 | .toString(), 267 | new Path("/home/suchipi/Code/something/src/index.ts") 268 | .relativeTo("/home/suchipi/Code/something-else", { noLeadingDot: true }) 269 | .toString(), 270 | ]; 271 | expect(result).toMatchInlineSnapshot(` 272 | [ 273 | "./a/b/c", 274 | "./tmp/a/b/c", 275 | "../..", 276 | "../../../c", 277 | "./src/index.ts", 278 | "src/index.ts", 279 | "../something/src/index.ts", 280 | "../something/src/index.ts", 281 | ] 282 | `); 283 | }); 284 | 285 | test("Path constructor with fs root strings", async () => { 286 | const path = new Path("/"); 287 | const path2 = new Path("\\\\"); 288 | 289 | const result = { path, path2 }; 290 | expect(result).toMatchInlineSnapshot(` 291 | { 292 | "path": Path { 293 | "segments": [ 294 | "", 295 | ], 296 | "separator": "/", 297 | }, 298 | "path2": Path { 299 | "segments": [ 300 | "", 301 | "", 302 | ], 303 | "separator": "\\", 304 | }, 305 | } 306 | `); 307 | }); 308 | 309 | test("Path constructor with absolute paths", async () => { 310 | const path = new Path("/tmp"); 311 | const path2 = new Path("\\\\SERVERNAME\\ShareName$"); 312 | 313 | const result = { path, path2 }; 314 | 315 | expect(result).toMatchInlineSnapshot(` 316 | { 317 | "path": Path { 318 | "segments": [ 319 | "", 320 | "tmp", 321 | ], 322 | "separator": "/", 323 | }, 324 | "path2": Path { 325 | "segments": [ 326 | "", 327 | "", 328 | "SERVERNAME", 329 | "ShareName$", 330 | ], 331 | "separator": "\\", 332 | }, 333 | } 334 | `); 335 | }); 336 | 337 | test("Path.startsWith", async () => { 338 | const base = "/tmp/blah/whatever"; 339 | 340 | const p = new Path(base, "something", "yeah"); 341 | const p2 = new Path(base, "something-else", "yeah", "yup"); 342 | const p3 = new Path(base, "something"); 343 | 344 | expect(p.startsWith(base)).toBe(true); 345 | expect(p2.startsWith(base)).toBe(true); 346 | expect(p3.startsWith(base)).toBe(true); 347 | expect(p.startsWith(p)).toBe(true); 348 | expect(p2.startsWith(p2)).toBe(true); 349 | expect(p3.startsWith(p3)).toBe(true); 350 | 351 | expect(p.startsWith(p2)).toBe(false); 352 | expect(p2.startsWith(p)).toBe(false); 353 | expect(p2.startsWith(p3)).toBe(false); 354 | 355 | expect(p.startsWith(p3)).toBe(true); 356 | }); 357 | 358 | test("Path.endsWith", async () => { 359 | const base = "/tmp/blah/whatever"; 360 | 361 | const p = new Path(base, "something", "yup"); 362 | const p2 = new Path(base, "something-else", "yeah", "yup"); 363 | const p3 = new Path("yeah", "yup"); 364 | 365 | expect(p.endsWith(p)).toBe(true); 366 | expect(p2.endsWith(p2)).toBe(true); 367 | expect(p3.endsWith(p3)).toBe(true); 368 | 369 | expect(p.endsWith(p2)).toBe(false); 370 | expect(p2.endsWith(p)).toBe(false); 371 | expect(p.endsWith(p3)).toBe(false); 372 | 373 | expect(p2.endsWith(p3)).toBe(true); 374 | }); 375 | 376 | test("Path.indexOf", async () => { 377 | const p = new Path("/tmp/something/yeah/yup"); 378 | 379 | const result = [ 380 | p.indexOf("not here"), 381 | p.indexOf("/tmp"), 382 | // weird quirk of how we store segments: first segment in unix-style absolute path is "" 383 | p.indexOf("tmp"), 384 | p.indexOf("yup"), 385 | p.indexOf("something"), 386 | 387 | // You can specify search index... best way to test that is to make it miss something, I guess 388 | p.indexOf("tmp", 2), 389 | p.indexOf("yup", 2), // works because yup is after index 2 390 | ]; 391 | 392 | expect(result).toMatchInlineSnapshot(` 393 | [ 394 | -1, 395 | 0, 396 | 1, 397 | 4, 398 | 2, 399 | -1, 400 | 4, 401 | ] 402 | `); 403 | }); 404 | 405 | test("Path.replace", async () => { 406 | const base = "/home/blah/whatever"; 407 | 408 | const p = new Path(base, "something", "yup", "yeah"); 409 | 410 | const result = [ 411 | p.replace("something", "something-else"), 412 | p.replace("something/yup", "something/nah"), 413 | p.replace("something/yup", "something-again"), 414 | p.replace(base, "/tmp"), 415 | p.replace(["something", "yup", "yeah"], "/mhm"), 416 | ]; 417 | 418 | expect(result).toMatchInlineSnapshot(` 419 | [ 420 | Path { 421 | "segments": [ 422 | "", 423 | "home", 424 | "blah", 425 | "whatever", 426 | "something-else", 427 | "yup", 428 | "yeah", 429 | ], 430 | "separator": "/", 431 | }, 432 | Path { 433 | "segments": [ 434 | "", 435 | "home", 436 | "blah", 437 | "whatever", 438 | "something", 439 | "nah", 440 | "yeah", 441 | ], 442 | "separator": "/", 443 | }, 444 | Path { 445 | "segments": [ 446 | "", 447 | "home", 448 | "blah", 449 | "whatever", 450 | "something-again", 451 | "yeah", 452 | ], 453 | "separator": "/", 454 | }, 455 | Path { 456 | "segments": [ 457 | "", 458 | "tmp", 459 | "something", 460 | "yup", 461 | "yeah", 462 | ], 463 | "separator": "/", 464 | }, 465 | Path { 466 | "segments": [ 467 | "", 468 | "home", 469 | "blah", 470 | "whatever", 471 | "mhm", 472 | ], 473 | "separator": "/", 474 | }, 475 | ] 476 | `); 477 | }); 478 | 479 | test("Path.replaceAll", async () => { 480 | const p = new Path("/one/two/three/two/one/zero"); 481 | const result1 = p.replaceAll("one", "nine/ten"); 482 | 483 | // replaceAll avoids an infinite loop by only replacing forwards 484 | const result2 = p.replaceAll("one", "one"); 485 | 486 | expect([result1, result2]).toMatchInlineSnapshot(` 487 | [ 488 | Path { 489 | "segments": [ 490 | "", 491 | "nine", 492 | "ten", 493 | "two", 494 | "three", 495 | "two", 496 | "nine", 497 | "ten", 498 | "zero", 499 | ], 500 | "separator": "/", 501 | }, 502 | Path { 503 | "segments": [ 504 | "", 505 | "one", 506 | "two", 507 | "three", 508 | "two", 509 | "one", 510 | "zero", 511 | ], 512 | "separator": "/", 513 | }, 514 | ] 515 | `); 516 | }); 517 | 518 | test("Path.replaceLast", async () => { 519 | const p = new Path("/one/two/three/two/one/zero"); 520 | const result = p.replaceLast("twenty-two"); 521 | 522 | expect(result).toMatchInlineSnapshot(` 523 | Path { 524 | "segments": [ 525 | "", 526 | "one", 527 | "two", 528 | "three", 529 | "two", 530 | "one", 531 | "twenty-two", 532 | ], 533 | "separator": "/", 534 | } 535 | `); 536 | }); 537 | 538 | test("Path.basename", async () => { 539 | const p = new Path("/one/two/three/two/one/zero.help.txt"); 540 | const result = p.basename(); 541 | 542 | expect(result).toMatchInlineSnapshot(`"zero.help.txt"`); 543 | }); 544 | 545 | test("Path.extname", async () => { 546 | const p = new Path("/one/two/three/two/one/zero.help.txt"); 547 | const result = p.extname(); 548 | 549 | expect(result).toMatchInlineSnapshot(`".txt"`); 550 | }); 551 | 552 | test("Path.extname full", async () => { 553 | const p = new Path("/one/two/three/two/one/zero.help.txt"); 554 | const result = p.extname({ full: true }); 555 | expect(result).toMatchInlineSnapshot(`".help.txt"`); 556 | }); 557 | 558 | test("Path.dirname", async () => { 559 | const p = new Path("/one/two/three/two/one/zero.help.txt"); 560 | const result = p.dirname(); 561 | expect(result).toMatchInlineSnapshot(` 562 | Path { 563 | "segments": [ 564 | "", 565 | "one", 566 | "two", 567 | "three", 568 | "two", 569 | "one", 570 | ], 571 | "separator": "/", 572 | } 573 | `); 574 | }); 575 | 576 | test("Path.fromRaw", async () => { 577 | const p = Path.fromRaw(["one", "two", "three"], "\\"); 578 | expect(p).toMatchInlineSnapshot(` 579 | Path { 580 | "segments": [ 581 | "one", 582 | "two", 583 | "three", 584 | ], 585 | "separator": "\\", 586 | } 587 | `); 588 | }); 589 | 590 | test("Path.fromRaw, invalid inputs", async () => { 591 | const p = Path.fromRaw(["", "", "", "one", "", "two", "three"], "\\"); 592 | expect(p).toMatchInlineSnapshot(` 593 | Path { 594 | "segments": [ 595 | "", 596 | "", 597 | "", 598 | "one", 599 | "", 600 | "two", 601 | "three", 602 | ], 603 | "separator": "\\", 604 | } 605 | `); 606 | }); 607 | 608 | test("Path.from", async () => { 609 | const p = Path.from(["one", "two", "three"], "\\"); 610 | expect(p).toMatchInlineSnapshot(` 611 | Path { 612 | "segments": [ 613 | "one", 614 | "two", 615 | "three", 616 | ], 617 | "separator": "\\", 618 | } 619 | `); 620 | }); 621 | 622 | test("Path.from, invalid inputs", async () => { 623 | const p = Path.from(["", "", "", "one", "", "two", "three"], "\\"); 624 | expect(p).toMatchInlineSnapshot(` 625 | Path { 626 | "segments": [ 627 | "", 628 | "", 629 | "one", 630 | "two", 631 | "three", 632 | ], 633 | "separator": "\\", 634 | } 635 | `); 636 | 637 | // NOTE: different number of allowed leading empty segments depending on separator 638 | const p2 = Path.from(["", "", "", "one", "", "two", "three"], "/"); 639 | expect(p2).toMatchInlineSnapshot(` 640 | Path { 641 | "segments": [ 642 | "", 643 | "one", 644 | "two", 645 | "three", 646 | ], 647 | "separator": "/", 648 | } 649 | `); 650 | }); 651 | 652 | test("Path.from, unspecified separator", async () => { 653 | // defaults to "/" 654 | const p = Path.from(["one", "", "two", "three"]); 655 | expect(p).toMatchInlineSnapshot(` 656 | Path { 657 | "segments": [ 658 | "one", 659 | "two", 660 | "three", 661 | ], 662 | "separator": "/", 663 | } 664 | `); 665 | 666 | // uses one from input segments if possible 667 | const p2 = Path.from(["", "", "", "one", "", "two\\three", "four"]); 668 | expect(p2).toMatchInlineSnapshot(` 669 | Path { 670 | "segments": [ 671 | "", 672 | "", 673 | "one", 674 | "two\\three", 675 | "four", 676 | ], 677 | "separator": "\\", 678 | } 679 | `); 680 | 681 | // ...but specified separator takes priority over that 682 | const p3 = Path.from(["", "", "", "one", "", "two\\three", "four"], "/"); 683 | expect(p3).toMatchInlineSnapshot(` 684 | Path { 685 | "segments": [ 686 | "", 687 | "one", 688 | "two\\three", 689 | "four", 690 | ], 691 | "separator": "/", 692 | } 693 | `); 694 | }); 695 | 696 | test("Path.clone", async () => { 697 | const p = new Path("/one/two/three/two/one/zero.help.txt"); 698 | const p2 = p.clone(); 699 | 700 | const result = [ 701 | p, 702 | p2, 703 | p === p2, 704 | p.segments === p2.segments, 705 | p.separator === p2.separator, 706 | ]; 707 | 708 | expect(result).toMatchInlineSnapshot(` 709 | [ 710 | Path { 711 | "segments": [ 712 | "", 713 | "one", 714 | "two", 715 | "three", 716 | "two", 717 | "one", 718 | "zero.help.txt", 719 | ], 720 | "separator": "/", 721 | }, 722 | Path { 723 | "segments": [ 724 | "", 725 | "one", 726 | "two", 727 | "three", 728 | "two", 729 | "one", 730 | "zero.help.txt", 731 | ], 732 | "separator": "/", 733 | }, 734 | false, 735 | false, 736 | true, 737 | ] 738 | `); 739 | }); 740 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | const WIN32_DRIVE_LETTER_REGEXP = /^[A-Za-z]:$/; 2 | 3 | function isWin32DriveLetter(pathString: string) { 4 | return WIN32_DRIVE_LETTER_REGEXP.test(pathString); 5 | } 6 | 7 | function validateSegments( 8 | segments: Array, 9 | separator: string, 10 | ): Array { 11 | return segments.filter((part, index) => { 12 | // first part can be "" to represent left side of root "/" 13 | // second part can be "" to support windows UNC paths 14 | if (part === "" && index === 0) { 15 | return true; 16 | } else if ( 17 | part === "" && 18 | index === 1 && 19 | separator === "\\" && 20 | segments[0] === "" 21 | ) { 22 | return true; 23 | } 24 | 25 | return Boolean(part); 26 | }); 27 | } 28 | 29 | /** An object that represents a filesystem path. */ 30 | export class Path { 31 | /** Split one or more path strings into an array of path segments. */ 32 | static splitToSegments(inputParts: Array | string): Array { 33 | if (!Array.isArray(inputParts)) { 34 | inputParts = [inputParts]; 35 | } 36 | 37 | const separator = Path.detectSeparator(inputParts, "/"); 38 | 39 | return validateSegments( 40 | inputParts.map((part) => part.split(/(?:\/|\\)/g)).flat(1), 41 | separator, 42 | ); 43 | } 44 | 45 | /** 46 | * Search the provided path string or strings for a path separator character 47 | * (either forward slash or backslash), and return it. If none is found, 48 | * return `fallback`. 49 | */ 50 | static detectSeparator( 51 | input: Array | string, 52 | fallback: Fallback, 53 | ): string | Fallback { 54 | let testStr = input; 55 | if (Array.isArray(input)) { 56 | testStr = input.join("|"); 57 | } 58 | 59 | for (const char of testStr) { 60 | if (char === "/") { 61 | return "/"; 62 | } else if (char === "\\") { 63 | return "\\"; 64 | } 65 | } 66 | 67 | return fallback; 68 | } 69 | 70 | /** 71 | * Concatenates the input path(s) and then resolves all non-leading `.` and 72 | * `..` segments. 73 | */ 74 | static normalize( 75 | ...inputs: Array> 76 | ): Path { 77 | return new Path(...inputs).normalize(); 78 | } 79 | 80 | /** 81 | * Return whether the provided path is absolute; that is, whether it 82 | * starts with either `/`, `\`, or a drive letter (ie `C:`). 83 | */ 84 | static isAbsolute(path: string | Path): boolean { 85 | if (path instanceof Path) { 86 | return path.isAbsolute(); 87 | } else { 88 | return new Path(path).isAbsolute(); 89 | } 90 | } 91 | 92 | /** 93 | * An array of the path segments that make up this path. 94 | * 95 | * For `/tmp/foo.txt`, it'd be `["", "tmp", "foo.txt"]`. 96 | * 97 | * For `C:\something\somewhere.txt`, it'd be `["C:", "something", "somewhere.txt"]`. 98 | */ 99 | segments: Array; 100 | 101 | /** 102 | * The path separator that should be used to turn this path into a string. 103 | * 104 | * Will be either `/` or `\`. 105 | */ 106 | separator: string; 107 | 108 | /** Create a new Path object using the provided input(s). */ 109 | constructor(...inputs: Array>) { 110 | const parts = inputs 111 | .flat(1) 112 | .map((part) => (typeof part === "string" ? part : part.segments)) 113 | .flat(1); 114 | 115 | this.segments = Path.splitToSegments(parts); 116 | this.separator = Path.detectSeparator(parts, "/"); 117 | } 118 | 119 | /** 120 | * Create a new Path object using the provided segments and, optionally, 121 | * separator. 122 | * 123 | * NOTE: this doesn't set the `segments` directly; it passes them through a 124 | * filtering step first, to remove any double-slashes or etc. To set the 125 | * `.segments` directly, use {@link fromRaw}. 126 | */ 127 | static from(segments: Array, separator?: string) { 128 | const separatorToUse = separator || Path.detectSeparator(segments, "/"); 129 | const path = new Path(); 130 | path.segments = validateSegments(segments, separatorToUse); 131 | path.separator = separatorToUse; 132 | return path; 133 | } 134 | 135 | /** 136 | * Create a new Path object using the provided segments and separator. 137 | * 138 | * NOTE: this method doesn't do any sort of validation on `segments`; as such, 139 | * it can be used to construct an invalid Path object. Consider using 140 | * {@link from} instead. 141 | */ 142 | static fromRaw(segments: Array, separator: string) { 143 | const path = new Path(); 144 | path.segments = segments; 145 | path.separator = separator; 146 | return path; 147 | } 148 | 149 | /** 150 | * Resolve all non-leading `.` and `..` segments in this path. 151 | */ 152 | normalize(): Path { 153 | // we clone this cause we're gonna mutate it 154 | const segments = [...this.segments]; 155 | 156 | const newSegments: Array = []; 157 | let currentSegment: string | undefined; 158 | while (segments.length > 0) { 159 | currentSegment = segments.shift(); 160 | 161 | switch (currentSegment) { 162 | case ".": { 163 | if (newSegments.length === 0) { 164 | newSegments.push(currentSegment); 165 | } 166 | break; 167 | } 168 | case "..": { 169 | if (newSegments.length === 0) { 170 | newSegments.push(currentSegment); 171 | } else { 172 | newSegments.pop(); 173 | } 174 | 175 | break; 176 | } 177 | default: { 178 | if (currentSegment != null) { 179 | newSegments.push(currentSegment); 180 | } 181 | break; 182 | } 183 | } 184 | } 185 | 186 | return Path.fromRaw(newSegments, this.separator); 187 | } 188 | 189 | /** 190 | * Create a new Path by appending additional path segments onto the end of 191 | * this Path's segments. 192 | * 193 | * The returned path will use this path's separator. 194 | */ 195 | concat(...others: Array>): Path { 196 | const otherSegments = new Path(others.flat(1)).segments; 197 | return Path.from(this.segments.concat(otherSegments), this.separator); 198 | } 199 | 200 | /** 201 | * Return whether this path is absolute; that is, whether it starts with 202 | * either `/`, `\`, or a drive letter (ie `C:`). 203 | */ 204 | isAbsolute(): boolean { 205 | const firstPart = this.segments[0]; 206 | 207 | // empty first component indicates that path starts with leading slash. 208 | // could be unix fs root, or windows unc path 209 | if (firstPart === "") return true; 210 | 211 | // windows drive 212 | if (/^[A-Za-z]:/.test(firstPart)) return true; 213 | 214 | return false; 215 | } 216 | 217 | /** 218 | * Make a second Path object containing the same segments and separator as 219 | * this one. 220 | */ 221 | clone(): this { 222 | const theClone = (this.constructor as typeof Path).fromRaw( 223 | [...this.segments], 224 | this.separator, 225 | ); 226 | return theClone as any; 227 | } 228 | 229 | /** 230 | * Express this path relative to `dir`. 231 | * 232 | * @param dir - The directory to create a new path relative to. 233 | * @param options - Options that affect the resulting path. 234 | */ 235 | relativeTo( 236 | dir: Path | string, 237 | options: { noLeadingDot?: boolean } = {}, 238 | ): Path { 239 | if (!(dir instanceof Path)) { 240 | dir = new Path(dir); 241 | } 242 | 243 | const ownSegments = [...this.segments]; 244 | const dirSegments = [...dir.segments]; 245 | 246 | while (ownSegments[0] === dirSegments[0]) { 247 | ownSegments.shift(); 248 | dirSegments.shift(); 249 | } 250 | 251 | if (dirSegments.length === 0) { 252 | if (options.noLeadingDot) { 253 | return Path.from(ownSegments, this.separator); 254 | } else { 255 | return Path.from([".", ...ownSegments], this.separator); 256 | } 257 | } else { 258 | const dotDots = dirSegments.map((_) => ".."); 259 | return Path.from([...dotDots, ...ownSegments], this.separator); 260 | } 261 | } 262 | 263 | /** 264 | * Turn this path into a string by joining its segments using its separator. 265 | */ 266 | toString(): string { 267 | let result = this.segments.join(this.separator); 268 | if (result == "") { 269 | return "/"; 270 | } else { 271 | if (isWin32DriveLetter(result)) { 272 | return result + this.separator; 273 | } else { 274 | return result; 275 | } 276 | } 277 | } 278 | 279 | /** 280 | * Return the final path segment of this path. If this path has no path 281 | * segments, the empty string is returned. 282 | */ 283 | basename(): string { 284 | const last = this.segments[this.segments.length - 1]; 285 | return last || ""; 286 | } 287 | 288 | /** 289 | * Return the trailing extension of this path. Set option `full` to `true` to 290 | * get a compound extension like ".d.ts" instead of ".ts". 291 | */ 292 | extname(options: { full?: boolean } = {}): string { 293 | const filename = this.basename(); 294 | const parts = filename.split("."); 295 | 296 | if (parts.length === 1) { 297 | return ""; 298 | } 299 | 300 | if (options.full) { 301 | return "." + parts.slice(1).join("."); 302 | } else { 303 | return "." + parts[parts.length - 1]; 304 | } 305 | } 306 | 307 | /** 308 | * Return a new Path containing all of the path segments in this one except 309 | * for the last one; ie. the path to the directory that contains this path. 310 | */ 311 | dirname(): Path { 312 | return this.replaceLast([]); 313 | } 314 | 315 | /** 316 | * Return whether this path starts with the provided value, by comparing one 317 | * path segment at a time. 318 | * 319 | * The starting segments of this path must *exactly* match the segments in the 320 | * provided value. 321 | * 322 | * This means that, given two Paths A and B: 323 | * 324 | * ``` 325 | * A: Path { /home/user/.config } 326 | * B: Path { /home/user/.config2 } 327 | * ``` 328 | * 329 | * Path B does *not* start with Path A, because `".config" !== ".config2"`. 330 | */ 331 | startsWith(value: string | Path | Array): boolean { 332 | value = new Path(value); 333 | 334 | return value.segments.every( 335 | (segment, index) => this.segments[index] === segment, 336 | ); 337 | } 338 | 339 | /** 340 | * Return whether this path ends with the provided value, by comparing one 341 | * path segment at a time. 342 | * 343 | * The ending segments of this path must *exactly* match the segments in the 344 | * provided value. 345 | * 346 | * This means that, given two Paths A and B: 347 | * 348 | * ``` 349 | * A: Path { /home/1user/.config } 350 | * B: Path { user/.config } 351 | * ``` 352 | * 353 | * Path A does *not* end with Path B, because `"1user" !== "user"`. 354 | */ 355 | endsWith(value: string | Path | Array): boolean { 356 | value = new Path(value); 357 | 358 | const valueSegmentsReversed = [...value.segments].reverse(); 359 | const ownSegmentsReversed = [...this.segments].reverse(); 360 | 361 | return valueSegmentsReversed.every( 362 | (segment, index) => ownSegmentsReversed[index] === segment, 363 | ); 364 | } 365 | 366 | /** 367 | * Return the path segment index at which `value` appears in this path, or 368 | * `-1` if it doesn't appear in this path. 369 | * 370 | * @param value - The value to search for. If the value contains more than one path segment, the returned index will refer to the location of the value's first path segment. 371 | * @param fromIndex - The index into this path's segments to begin searching at. Defaults to `0`. 372 | */ 373 | indexOf( 374 | value: string | Path | Array, 375 | fromIndex: number = 0, 376 | ): number { 377 | value = new Path(value); 378 | 379 | const ownSegmentsLength = this.segments.length; 380 | for (let i = fromIndex; i < ownSegmentsLength; i++) { 381 | if ( 382 | value.segments.every((valueSegment, valueIndex) => { 383 | return this.segments[i + valueIndex] === valueSegment; 384 | }) 385 | ) { 386 | return i; 387 | } 388 | } 389 | 390 | return -1; 391 | } 392 | 393 | /** 394 | * Return whether `value` appears in this path. 395 | * 396 | * @param value - The value to search for. 397 | * @param fromIndex - The index into this path's segments to begin searching at. Defaults to `0`. 398 | */ 399 | includes( 400 | value: string | Path | Array, 401 | fromIndex: number = 0, 402 | ): boolean { 403 | return this.indexOf(value, fromIndex) !== -1; 404 | } 405 | 406 | /** 407 | * Return a new Path wherein the segments in `value` have been replaced with 408 | * the segments in `replacement`. If the segments in `value` are not present 409 | * in this path, a clone of this path is returned. 410 | * 411 | * Note that only the first match is replaced. 412 | * 413 | * @param value - What should be replaced 414 | * @param replacement - What it should be replaced with 415 | * 416 | * NOTE: to remove segments, use an empty Array for `replacement`. 417 | */ 418 | replace( 419 | value: string | Path | Array, 420 | replacement: string | Path | Array, 421 | ): Path { 422 | value = new Path(value); 423 | replacement = new Path(replacement); 424 | 425 | const matchIndex = this.indexOf(value); 426 | 427 | if (matchIndex === -1) { 428 | return this.clone(); 429 | } else { 430 | const newSegments = [ 431 | ...this.segments.slice(0, matchIndex), 432 | ...replacement.segments, 433 | ...this.segments.slice(matchIndex + value.segments.length), 434 | ]; 435 | return Path.from(newSegments, this.separator); 436 | } 437 | } 438 | 439 | /** 440 | * Return a new Path wherein all occurrences of the segments in `value` have 441 | * been replaced with the segments in `replacement`. If the segments in 442 | * `value` are not present in this path, a clone of this path is returned. 443 | * 444 | * @param value - What should be replaced 445 | * @param replacement - What it should be replaced with 446 | */ 447 | replaceAll( 448 | value: string | Path | Array, 449 | replacement: string | Path | Array, 450 | ): Path { 451 | replacement = new Path(replacement); 452 | 453 | let searchIndex = 0; 454 | 455 | let currentPath: Path = this; 456 | 457 | const ownLength = this.segments.length; 458 | while (searchIndex < ownLength) { 459 | const matchingIndex = this.indexOf(value, searchIndex); 460 | if (matchingIndex === -1) { 461 | break; 462 | } else { 463 | currentPath = currentPath.replace(value, replacement); 464 | searchIndex = matchingIndex + replacement.segments.length; 465 | } 466 | } 467 | 468 | return currentPath; 469 | } 470 | 471 | /** 472 | * Return a copy of this path but with the final segment replaced with `replacement` 473 | * 474 | * @param replacement - The new final segment(s) for the returned Path 475 | */ 476 | replaceLast(replacement: string | Path | Array): Path { 477 | replacement = new Path(replacement); 478 | 479 | const segments = [...this.segments]; 480 | segments.pop(); 481 | segments.push(...replacement.segments); 482 | 483 | return Path.from(segments, this.separator); 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src"], 3 | "exclude": ["./src/**/*.test.ts"], 4 | 5 | "compilerOptions": { 6 | "lib": ["es2020"], 7 | "target": "es2020", 8 | "module": "commonjs", 9 | "outDir": "./dist", 10 | "declaration": true, 11 | 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "strictPropertyInitialization": true, 17 | "noImplicitThis": true, 18 | "alwaysStrict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | 24 | "moduleResolution": "node", 25 | "esModuleInterop": true 26 | } 27 | } 28 | --------------------------------------------------------------------------------