├── src ├── globals.d.ts ├── map.ts ├── helpers.ts ├── helpers.test.ts ├── index.ts ├── profiles.ts └── index.test.ts ├── tsconfig.json ├── tsconfig.build.json ├── LICENSE ├── .gitignore ├── package.json ├── tsconfig.base.json ├── README.md └── pnpm-lock.yaml /src/globals.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@multiformats/sha3" { 2 | export * from "@multiformats/sha3/index"; 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This configuration is used for local development and type checking. 3 | "extends": "./tsconfig.base.json", 4 | "include": ["src"], 5 | "exclude": [] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["./src"], 4 | "exclude": ["src/**/*.test.ts"], 5 | "compilerOptions": { 6 | "sourceMap": true, 7 | "rootDir": "./src" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/map.ts: -------------------------------------------------------------------------------- 1 | export const codeToName = { 2 | 0xe3: "ipfs", 3 | 0xe5: "ipns", 4 | 0xe4: "swarm", 5 | 0x01bc: "onion", 6 | 0x01bd: "onion3", 7 | 0xb19910: "skynet", 8 | 0xb29910: "arweave", 9 | } as const; 10 | 11 | export const nameToCode = { 12 | ipfs: 0xe3, 13 | ipns: 0xe5, 14 | swarm: 0xe4, 15 | onion: 0x01bc, 16 | onion3: 0x01bd, 17 | skynet: 0xb19910, 18 | arweave: 0xb29910, 19 | } as const; 20 | 21 | export type CodecId = keyof typeof codeToName; 22 | export type Codec = keyof typeof nameToCode; 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ENS Labs Limited 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, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | .env.test 60 | 61 | # parcel-bundler cache (https://parceljs.org/) 62 | .cache 63 | 64 | # next.js build output 65 | .next 66 | 67 | # nuxt.js build output 68 | .nuxt 69 | 70 | # vuepress build output 71 | .vuepress/dist 72 | 73 | # Serverless directories 74 | .serverless/ 75 | 76 | # FuseBox cache 77 | .fusebox/ 78 | 79 | # DynamoDB Local files 80 | .dynamodb/ 81 | 82 | 83 | dist/ -------------------------------------------------------------------------------- /src/helpers.ts: -------------------------------------------------------------------------------- 1 | import { base32 } from "multiformats/bases/base32"; 2 | import { base36 } from "multiformats/bases/base36"; 3 | import { CID } from "multiformats/cid"; 4 | 5 | // Label's max length in DNS (https://tools.ietf.org/html/rfc1034#page-7) 6 | const dnsLabelMaxLength = 63; 7 | 8 | /** 9 | * Take any ipfsHash and convert it to DNS-compatible CID 10 | * @param ipfsHash a regular ipfs hash either a cid v0 or v1 11 | * @return the resulting ipfs hash as a cid v1 12 | */ 13 | export const cidForWeb = (ipfsHash: string): string => { 14 | let cid = CID.parse(ipfsHash); 15 | if (cid.version === 0) { 16 | cid = cid.toV1(); 17 | } 18 | const dnsLabel = cid.toString(base32); 19 | if (dnsLabel.length > dnsLabelMaxLength) { 20 | const b36 = cid.toString(base36); 21 | if (b36.length <= dnsLabelMaxLength) { 22 | return b36; 23 | } 24 | throw new TypeError( 25 | `CID is longer than DNS limit of ${dnsLabelMaxLength} characters and is not compatible with public gateways` 26 | ); 27 | } 28 | return dnsLabel; 29 | }; 30 | 31 | /** 32 | * Take any ipfsHash and convert it to a CID v1 encoded in base32. 33 | * @param ipfsHash a regular ipfs hash either a cid v0 or v1 (v1 will remain unchanged) 34 | * @return the resulting ipfs hash as a cid v1 35 | */ 36 | export const cidV0ToV1Base32 = (ipfsHash: string): string => { 37 | let cid = CID.parse(ipfsHash); 38 | if (cid.version === 0) { 39 | cid = cid.toV1(); 40 | } 41 | return cid.toString(base32); 42 | }; 43 | 44 | /** 45 | * Concats two Uint8Arrays 46 | * @param array1 first array 47 | * @param array2 second array 48 | * @return the resulting array 49 | */ 50 | export const concatUint8Arrays = ( 51 | array1: Uint8Array, 52 | array2: Uint8Array 53 | ): Uint8Array => { 54 | let result = new Uint8Array(array1.length + array2.length); 55 | result.set(array1, 0); 56 | result.set(array2, array1.length); 57 | return result; 58 | }; 59 | -------------------------------------------------------------------------------- /src/helpers.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from "vitest"; 2 | 3 | import { cidForWeb, cidV0ToV1Base32 } from "./helpers.js"; 4 | 5 | const ipfs_CIDv0 = "QmRAQB6YaCyidP37UdDnjFY5vQuiBrcqdyoW1CuDgwxkD4"; 6 | const ipfsBase32DagPb = 7 | "bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4"; 8 | const ipfsBase32Libp2pKey = 9 | "bafzbeie5745rpv2m6tjyuugywy4d5ewrqgqqhfnf445he3omzpjbx5xqxe"; 10 | 11 | describe("cidV0ToV1Base32", () => { 12 | it("should convert CID v0 into v1", () => { 13 | expect(cidV0ToV1Base32(ipfs_CIDv0)).toBe(ipfsBase32DagPb); 14 | }); 15 | it("should keep CID v1 Base32 as-is", () => { 16 | expect(cidV0ToV1Base32(ipfsBase32DagPb)).toBe(ipfsBase32DagPb); 17 | expect(cidV0ToV1Base32(ipfsBase32Libp2pKey)).toBe(ipfsBase32Libp2pKey); 18 | }); 19 | }); 20 | 21 | describe("cidForWeb", () => { 22 | it("should convert CIDv0 into case-insenitive base", () => { 23 | expect(cidForWeb(ipfs_CIDv0)).toBe(ipfsBase32DagPb); 24 | }); 25 | it("should keep CIDv1 Base32 if under DNS limit", () => { 26 | const b32_59chars = 27 | "bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4"; 28 | expect(cidForWeb(b32_59chars)).toBe(b32_59chars); 29 | }); 30 | it("should convert to Base36 if it helps with DNS limit", () => { 31 | const b32_65chars = 32 | "bafzaajaiaejca4syrpdu6gdx4wsdnokxkprgzxf4wrstuc34gxw5k5jrag2so5gk"; 33 | const b36_62chars = 34 | "k51qzi5uqu5dj16qyiq0tajolkojyl9qdkr254920wxv7ghtuwcz593tp69z9m"; 35 | expect(cidForWeb(b32_65chars)).toBe(b36_62chars); 36 | }); 37 | it("should throw if CID is over DNS limit", () => { 38 | const b32_sha512_110chars = 39 | "bafkrgqhhyivzstcz3hhswshfjgy6ertgmnqeleynhwt4dlfsthi4hn7zgh4uvlsb5xncykzapi3ocd4lzogukir6ksdy6wzrnz6ohnv4aglcs"; 40 | expect(() => cidForWeb(b32_sha512_110chars)).toThrow( 41 | "CID is longer than DNS limit of 63 characters and is not compatible with public gateways" 42 | ); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { varint } from "multiformats"; 2 | import { concatUint8Arrays } from "./helpers.js"; 3 | import { codeToName, nameToCode, type Codec, type CodecId } from "./map.js"; 4 | import { 5 | bytesToHexString, 6 | hexStringToBytes, 7 | profiles, 8 | type Profile, 9 | } from "./profiles.js"; 10 | 11 | export { type Codec, type CodecId } from "./map.js"; 12 | 13 | export { cidForWeb, cidV0ToV1Base32 } from "./helpers.js"; 14 | 15 | /** 16 | * Decode a Content Hash. 17 | * @param contentHash an hex string containing a content hash 18 | * @return the decoded content 19 | */ 20 | export const decode = (contentHash: string): string => { 21 | const bytes = hexStringToBytes(contentHash); 22 | const [code, offset] = varint.decode(bytes); 23 | const value = bytes.slice(offset); 24 | const name = codeToName[code as CodecId]; 25 | let profile = profiles[name as keyof typeof profiles] as Profile | undefined; 26 | if (!profile) profile = profiles["default"]; 27 | return profile.decode(value); 28 | }; 29 | 30 | /** 31 | * General purpose encoding function 32 | * @param name Codec name 33 | * @param value Content to encode 34 | */ 35 | export const encode = (name: Codec, value: string): string => { 36 | let profile = profiles[name as keyof typeof profiles] as Profile | undefined; 37 | if (!profile) profile = profiles["default"]; 38 | const bytes = profile.encode(value); 39 | const code = nameToCode[name] as number; 40 | const codeBytes = varint.encodeTo( 41 | code, 42 | new Uint8Array(varint.encodingLength(code)) 43 | ); 44 | return bytesToHexString(concatUint8Arrays(codeBytes, bytes)); 45 | }; 46 | 47 | /** 48 | * Extract the codec of a content hash 49 | * @param contentHash hex string containing a content hash 50 | * @return the extracted codec 51 | */ 52 | export const getCodec = (contentHash: string): Codec | undefined => { 53 | const bytes = hexStringToBytes(contentHash); 54 | const [code] = varint.decode(bytes); 55 | return codeToName[code as CodecId]; 56 | }; 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ensdomains/content-hash", 3 | "version": "3.0.0-beta.5", 4 | "description": "A simple tool to encode/decode content hash for EIP 1577 compliant ENS Resolvers (fork of pldespaigne/content-hash)", 5 | "type": "module", 6 | "main": "./dist/cjs/index.js", 7 | "module": "./dist/esm/index.js", 8 | "types": "./dist/types/index.d.ts", 9 | "typings": "./dist/types/index.d.ts", 10 | "sideEffects": false, 11 | "exports": { 12 | ".": { 13 | "types": "./dist/types/index.d.ts", 14 | "import": "./dist/esm/index.js", 15 | "default": "./dist/cjs/index.js" 16 | }, 17 | "./package.json": "./package.json" 18 | }, 19 | "files": [ 20 | "dist/", 21 | "src/", 22 | "!src/**/*.test.ts" 23 | ], 24 | "scripts": { 25 | "test": "vitest", 26 | "clean": "rm -rf ./dist", 27 | "build:cjs": "tsc --project tsconfig.build.json --module commonjs --outDir ./dist/cjs --removeComments --verbatimModuleSyntax false && echo > ./dist/cjs/package.json '{\"type\":\"commonjs\"}'", 28 | "build:esm": "tsc --project tsconfig.build.json --module es2022 --outDir ./dist/esm && echo > ./dist/esm/package.json '{\"type\":\"module\",\"sideEffects\":false}'", 29 | "build:types": "tsc --project tsconfig.build.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap", 30 | "build": "pnpm run clean && pnpm run build:cjs && pnpm run build:esm && pnpm run build:types", 31 | "prepublish": "pnpm run build" 32 | }, 33 | "keywords": [ 34 | "ethereum", 35 | "ens", 36 | "eip-1577", 37 | "resolver", 38 | "ipfs", 39 | "swarm", 40 | "content-hash", 41 | "content", 42 | "hash", 43 | "contenthash", 44 | "contentHash" 45 | ], 46 | "author": "ensdomains", 47 | "license": "MIT", 48 | "repository": { 49 | "type": "git", 50 | "url": "https://github.com/ensdomains/content-hash" 51 | }, 52 | "dependencies": { 53 | "@multiformats/sha3": "^2.0.17", 54 | "multiformats": "^12.0.1" 55 | }, 56 | "devDependencies": { 57 | "typescript": "^5.1.6", 58 | "vitest": "^0.33.0" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | // This tsconfig file contains the shared config for the build (tsconfig.build.json) and type checking (tsconfig.json) config. 3 | "include": [], 4 | "compilerOptions": { 5 | // Incremental builds 6 | // NOTE: Enabling incremental builds speeds up `tsc`. Keep in mind though that it does not reliably bust the cache when the `tsconfig.json` file changes. 7 | "incremental": false, 8 | 9 | // Type checking 10 | "strict": true, 11 | "useDefineForClassFields": true, // Not enabled by default in `strict` mode unless we bump `target` to ES2022. 12 | "noFallthroughCasesInSwitch": true, // Not enabled by default in `strict` mode. 13 | "noImplicitReturns": true, // Not enabled by default in `strict` mode. 14 | "useUnknownInCatchVariables": true, // TODO: This would normally be enabled in `strict` mode but would require some adjustments to the codebase. 15 | "noImplicitOverride": true, // Not enabled by default in `strict` mode. 16 | "noUnusedLocals": true, // Not enabled by default in `strict` mode. 17 | "noUnusedParameters": true, // Not enabled by default in `strict` mode. 18 | // TODO: The following options are also not enabled by default in `strict` mode and would be nice to have but would require some adjustments to the codebase. 19 | // "exactOptionalPropertyTypes": true, 20 | // "noUncheckedIndexedAccess": true, 21 | 22 | // JavaScript support 23 | "allowJs": false, 24 | "checkJs": false, 25 | 26 | // Interop constraints 27 | "esModuleInterop": false, 28 | "allowSyntheticDefaultImports": false, 29 | "forceConsistentCasingInFileNames": true, 30 | "verbatimModuleSyntax": true, 31 | "importHelpers": true, // This is only used for build validation. Since we do not have `tslib` installed, this will fail if we accidentally make use of anything that'd require injection of helpers. 32 | 33 | // Language and environment 34 | "moduleResolution": "NodeNext", 35 | "module": "ESNext", 36 | "target": "ES2021", // Setting this to `ES2021` enables native support for `Node v16+`: https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping. 37 | 38 | // Skip type checking for node modules 39 | "skipLibCheck": true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # content-hash 2 | 3 | [![npm package](https://img.shields.io/npm/v/@ensdomains/content-hash.svg)](https://www.npmjs.com/package/@ensdomains/content-hash) ![licence](https://img.shields.io/npm/l/@ensdomains/content-hash.svg) 4 | 5 | > This is a simple package made for encoding and decoding content hashes as specified in the [EIP 1577](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1577.md). 6 | > This package will be useful for every [Ethereum](https://www.ethereum.org/) developer wanting to interact with [EIP 1577](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1577.md) compliant [ENS resolvers](http://docs.ens.domains/en/latest/introduction.html). 7 | 8 | ## 🔠 Supported Codec 9 | 10 | - `swarm` 11 | - `ipfs` 12 | - `ipns` 13 | - `onion` 14 | - `onion3` 15 | - `skynet` 16 | - `arweave` 17 | 18 | ## 📥 Install 19 | 20 | ```bash 21 | npm install @ensdomains/content-hash 22 | ``` 23 | 24 | ## 🛠 Usage 25 | 26 | ```javascript 27 | import { decode } from "@ensdomains/content-hash"; 28 | 29 | const encoded = 30 | "e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"; 31 | 32 | const content = decode(encoded); 33 | // 'QmRAQB6YaCyidP37UdDnjFY5vQuiBrcqdyoW1CuDgwxkD4' 34 | ``` 35 | 36 | ## 📕 API 37 | 38 | > All hex string **inputs** can be prefixed with `0x`, but it's **not mandatory**. 39 | 40 | > ⚠️ All **outputs** are **NOT** prefixed with `0x` 41 | 42 | ### decode( contentHash ) -> string 43 | 44 | This function takes a content hash as a hex **string** and returns the decoded content as a **string**. 45 | 46 | ```javascript 47 | import { decode } from "@ensdomains/content-hash"; 48 | 49 | const encoded = 50 | "e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"; 51 | 52 | const content = decode(encoded); 53 | // 'QmRAQB6YaCyidP37UdDnjFY5vQuiBrcqdyoW1CuDgwxkD4' 54 | ``` 55 | 56 | ### encode( codec, value) -> string 57 | 58 | This function takes a [supported codec](#-supported-codec) as a **string** and a value as a **string** and returns coresponding content hash as a hex **string**. 59 | 60 | ```javascript 61 | import { encode } from "@ensdomains/content-hash"; 62 | 63 | const onion = "zqktlwi4fecvo6ri"; 64 | 65 | const encoded = encode("onion", onion); 66 | // 'bc037a716b746c776934666563766f367269' 67 | ``` 68 | 69 | ### getCodec( contentHash ) -> string 70 | 71 | This function takes a content hash as a hex **string** and returns the codec as a hex **string**. 72 | 73 | ```javascript 74 | import { getCodec } from "@ensdomains/content-hash"; 75 | 76 | const encoded = 77 | "e40101701b20d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162"; 78 | 79 | const codec = getCodec(encoded); 80 | // 'swarm' 81 | ``` 82 | 83 | ### Helpers 84 | 85 | - #### cidV0ToV1Base32( ipfsHash ) -> string 86 | 87 | This function takes an ipfsHash and converts it to a CID v1 encoded in base32. 88 | 89 | ```javascript 90 | import { cidV0ToV1Base32 } from "@ensdomains/content-hash"; 91 | 92 | const ipfs = "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"; 93 | 94 | const cidV1 = cidV0ToV1Base32(ipfs); 95 | // 'bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4' 96 | ``` 97 | 98 | - #### cidForWeb( ipfsHash ) -> string 99 | 100 | This function takes any ipfsHash and converts it to DNS-compatible CID. 101 | 102 | ```javascript 103 | import { cidForWeb } from "@ensdomains/content-hash"; 104 | 105 | const ipfs = "QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG"; 106 | 107 | const cidV1 = cidForWeb(ipfs); 108 | // 'bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4' 109 | ``` 110 | 111 | ## 📝 License 112 | 113 | This project is licensed under the **MIT License**, you can find it [here](https://github.com/ensdomains/content-hash/blob/master/LICENSE). 114 | 115 | > Note that the dependencies may have a different License 116 | 117 | ## 👥 Acknowledgements 118 | 119 | This repo is forked from the original [content-hash](https://github.com/pldespaigne/content-hash) library, which was written by [pldespaigne](https://github.com/pldespaigne). 120 | -------------------------------------------------------------------------------- /src/profiles.ts: -------------------------------------------------------------------------------- 1 | import { base32 } from "multiformats/bases/base32"; 2 | import { base36 } from "multiformats/bases/base36"; 3 | import { base58btc } from "multiformats/bases/base58"; 4 | import { base64url } from "multiformats/bases/base64"; 5 | import { CID } from "multiformats/cid"; 6 | import { 7 | create as createDigest, 8 | decode as multihashDecode, 9 | } from "multiformats/hashes/digest"; 10 | 11 | type Bytes = Uint8Array; 12 | 13 | /** 14 | * Convert a hexadecimal string to Bytes, the string can start with or without '0x' 15 | * @param hex a hexadecimal value 16 | * @return the resulting Bytes 17 | */ 18 | export const hexStringToBytes = (hex: string): Bytes => { 19 | let value: string = hex; 20 | if (value.startsWith("0x")) { 21 | value = value.slice(2); 22 | } 23 | 24 | if (value.length % 2 !== 0) { 25 | throw new Error("Invalid hex string"); 26 | } 27 | 28 | const bytes = new Uint8Array(value.length / 2); 29 | 30 | for (let i = 0; i < value.length; i += 2) { 31 | bytes[i / 2] = parseInt(value.slice(i, i + 2), 16); 32 | } 33 | 34 | return bytes; 35 | }; 36 | 37 | export const bytesToHexString = (bytes: Bytes): string => { 38 | let hex = ""; 39 | for (let i = 0; i < bytes.length; i++) { 40 | hex += bytes[i].toString(16).padStart(2, "0"); 41 | } 42 | return hex; 43 | }; 44 | 45 | /** 46 | * Validates IPNS identifier to safeguard against insecure names. 47 | * @param cid used in ipns-ns 48 | * @return true if cid is a valid cryptographic IPNS identifier 49 | */ 50 | const isCryptographicIPNS = (cid: CID): boolean => { 51 | try { 52 | const { multihash } = cid; 53 | // Additional check for identifiers shorter 54 | // than what inlined ED25519 pubkey would be 55 | // https://github.com/ensdomains/ens-app/issues/849#issuecomment-777088950 56 | if (multihash.size < 38) { 57 | const mh = multihashDecode(multihash.bytes); 58 | // ED25519 pubkeys are inlined using identity hash function 59 | // and we should not see anything shorter than that 60 | if (mh.code === 0x0 && mh.size < 36) { 61 | // One can read inlined string value via: 62 | // console.log('ipns-ns id:', String(multiH.decode(new CID(value).multihash).digest)) 63 | return false; 64 | } 65 | } 66 | // ok, CID looks fine 67 | return true; 68 | } catch (_) { 69 | return false; 70 | } 71 | }; 72 | 73 | const base64Decode = (value: string): Bytes => base64url.decode(`u${value}`); 74 | 75 | /** 76 | * list of known encoding, 77 | * encoding should be a function that takes a `string` input, 78 | * and return a {@link Bytes} result 79 | */ 80 | const encodes = { 81 | skynet: (value: string): Bytes => { 82 | return base64Decode(value); 83 | }, 84 | swarm: (value: string): Bytes => { 85 | const bytes = hexStringToBytes(value); 86 | const multihash = createDigest(0x1b, bytes); 87 | return CID.create(1, 0xfa, multihash).bytes; 88 | }, 89 | ipfs: (value: string): Bytes => { 90 | return CID.parse(value).toV1().bytes; 91 | }, 92 | ipns: (value: string): Bytes => { 93 | let cid: CID; 94 | try { 95 | cid = CID.parse(value, value.startsWith("k") ? base36 : undefined); 96 | } catch (e) { 97 | // legacy v0 decode 98 | const bytes = base58btc.decode(`z${value}`); 99 | cid = new CID(0, 0x72, createDigest(0x00, bytes.slice(2)), bytes); 100 | } 101 | if (!isCryptographicIPNS(cid)) { 102 | throw Error( 103 | "ipns-ns allows only valid cryptographic libp2p-key identifiers, try using ED25519 pubkey instead" 104 | ); 105 | } 106 | // Represent IPNS name as a CID with libp2p-key codec 107 | // https://github.com/libp2p/specs/blob/master/RFC/0001-text-peerid-cid.md 108 | return CID.create(1, 0x72, cid.multihash).bytes; 109 | }, 110 | utf8: (value: string): Bytes => { 111 | const encoder = new TextEncoder(); 112 | return encoder.encode(value); 113 | }, 114 | arweave: (value: string): Bytes => { 115 | return base64Decode(value); 116 | }, 117 | }; 118 | 119 | /** 120 | * list of known decoding, 121 | * decoding should be a function that takes a `Uint8Array` input, 122 | * and return a `string` result 123 | */ 124 | const decodes = { 125 | hexMultiHash: (value: Bytes): string => { 126 | const cid = CID.decode(value); 127 | return bytesToHexString(multihashDecode(cid.multihash.bytes).digest); 128 | }, 129 | ipfs: (value: Bytes): string => { 130 | const cid = CID.decode(value).toV1(); 131 | return cid.toString(cid.code === 0x72 ? base36 : base32); 132 | }, 133 | ipns: (value: Bytes): string => { 134 | const cid = CID.decode(value).toV1(); 135 | if (!isCryptographicIPNS(cid)) { 136 | // Value is not a libp2p-key, return original string 137 | console.warn( 138 | "[ensdomains/content-hash] use of non-cryptographic identifiers in ipns-ns is deprecated and will be removed, migrate to ED25519 libp2p-key" 139 | ); 140 | return String.fromCodePoint(...CID.decode(value).multihash.digest); 141 | // TODO: start throwing an error (after some deprecation period) 142 | // throw Error('ipns-ns allows only valid cryptographic libp2p-key identifiers, try using ED25519 pubkey instead') 143 | } 144 | return cid.toString(base36); 145 | }, 146 | utf8: (value: Bytes): string => { 147 | const decoder = new TextDecoder(); 148 | return decoder.decode(value); 149 | }, 150 | base64: (value: Bytes): string => { 151 | return base64url.encode(value).substring(1); 152 | }, 153 | }; 154 | 155 | export type Profile = { 156 | encode: (value: string) => Bytes; 157 | decode: (value: Bytes) => string; 158 | }; 159 | 160 | /** 161 | * list of known encoding/decoding for a given codec, 162 | * `encode` should be chosen among the `encodes` functions 163 | * `decode` should be chosen among the `decodes` functions 164 | */ 165 | export const profiles = { 166 | skynet: { 167 | encode: encodes.skynet, 168 | decode: decodes.base64, 169 | }, 170 | swarm: { 171 | encode: encodes.swarm, 172 | decode: decodes.hexMultiHash, 173 | }, 174 | ipfs: { 175 | encode: encodes.ipfs, 176 | decode: decodes.ipfs, 177 | }, 178 | ipns: { 179 | encode: encodes.ipns, 180 | decode: decodes.ipns, 181 | }, 182 | arweave: { 183 | encode: encodes.arweave, 184 | decode: decodes.base64, 185 | }, 186 | default: { 187 | encode: encodes.utf8, 188 | decode: decodes.utf8, 189 | }, 190 | } as const; 191 | -------------------------------------------------------------------------------- /src/index.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, test } from "vitest"; 2 | 3 | import { decode, encode, getCodec } from "./index.js"; 4 | 5 | const ipfs_CIDv0 = "QmRAQB6YaCyidP37UdDnjFY5vQuiBrcqdyoW1CuDgwxkD4"; 6 | const ipfs_CIDv1 = 7 | "bafybeibj6lixxzqtsb45ysdjnupvqkufgdvzqbnvmhw2kf7cfkesy7r7d4"; 8 | const ipfs_contentHash = 9 | "e3010170122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"; 10 | const ipns_CIDv1 = "k2k4r8kgnix5x0snul9112xdpqgiwc5xmvi8ja0szfhntep2d7qv8zz3"; 11 | const ipns_peerID_B58_contentHash = 12 | "e5010172122029f2d17be6139079dc48696d1f582a8530eb9805b561eda517e22a892c7e3f1f"; 13 | const ipns_peerID_B58 = "12D3KooWG4NvqQVczTrWY1H2tvsJmbQf5bbA3xGYXC4FM3wWCfE4"; 14 | const ipns_libp2pKey_CIDv1 = 15 | "k51qzi5uqu5dihst24f3rp2ej4co9berxohfkxaenbq1wjty7nrd5e9xp4afx1"; 16 | const ipns_ED25519_contentHash = 17 | "e50101720024080112205cbd1cc86ac20d6640795809c2a185bb2504538a2de8076da5a6971b8acb4715"; 18 | const swarm = 19 | "d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162"; 20 | const swarm_contentHash = 21 | "e40101fa011b20d1de9994b4d039f6548d191eb26786769f580809256b4685ef316805265ea162"; 22 | const onion = "zqktlwi4fecvo6ri"; 23 | const onion_contentHash = "bc037a716b746c776934666563766f367269"; 24 | const onion3 = "p53lf57qovyuvwsc6xnrppyply3vtqm7l6pcobkmyqsiofyeznfu5uqd"; 25 | const onion3_contentHash = 26 | "bd037035336c663537716f7679757677736336786e72707079706c79337674716d376c3670636f626b6d797173696f6679657a6e667535757164"; 27 | const skylink = "CABAB_1Dt0FJsxqsu_J4TodNCbCGvtFf1Uys_3EgzOlTcg"; 28 | const skylink_contentHash = 29 | "90b2c60508004007fd43b74149b31aacbbf2784e874d09b086bed15fd54cacff7120cce95372"; 30 | const arweave = "ys32Pt8uC7TrVxHdOLByOspfPEq2LO63wREHQIM9SJQ"; 31 | const arweave_contentHash = 32 | "90b2ca05cacdf63edf2e0bb4eb5711dd38b0723aca5f3c4ab62ceeb7c1110740833d4894"; 33 | describe("content-hash (legacy tests)", () => { 34 | describe("decode", () => { 35 | test("ipfs", () => { 36 | expect(decode(ipfs_contentHash)).toEqual(ipfs_CIDv1); 37 | }); 38 | test("swarm", () => { 39 | expect(decode(swarm_contentHash)).toEqual(swarm); 40 | }); 41 | test("onion", () => { 42 | expect(decode(onion_contentHash)).toEqual(onion); 43 | }); 44 | }); 45 | describe("encode", () => { 46 | test("ipfs - CIDv0", () => { 47 | expect(encode("ipfs", ipfs_CIDv0)).toEqual(ipfs_contentHash); 48 | }); 49 | test("ipfs - CIDv1", () => { 50 | expect(encode("ipfs", ipfs_CIDv1)).toEqual(ipfs_contentHash); 51 | }); 52 | test("swarm", () => { 53 | expect(encode("swarm", swarm)).toEqual(swarm_contentHash); 54 | }); 55 | test("onion", () => { 56 | expect(encode("onion", onion)).toEqual(onion_contentHash); 57 | }); 58 | }); 59 | describe("getCodec", () => { 60 | test("ipfs", () => { 61 | expect(getCodec(ipfs_contentHash)).toEqual("ipfs"); 62 | }); 63 | test("swarm", () => { 64 | expect(getCodec(swarm_contentHash)).toEqual("swarm"); 65 | }); 66 | test("onion", () => { 67 | expect(getCodec(onion_contentHash)).toEqual("onion"); 68 | }); 69 | }); 70 | }); 71 | 72 | describe("content-hash", () => { 73 | describe("decode", () => { 74 | test("swarm", () => { 75 | expect(decode(swarm_contentHash)).toEqual(swarm); 76 | }); 77 | test("ipfs", () => { 78 | expect(decode(ipfs_contentHash)).toEqual(ipfs_CIDv1); 79 | }); 80 | describe("ipns", () => { 81 | test("legacy PeerID => CIDv1", () => { 82 | expect(decode(ipns_peerID_B58_contentHash)).toEqual(ipns_CIDv1); 83 | }); 84 | test("ED25519 => CIDv1 with libp2p-key codec", () => { 85 | expect(decode(ipns_ED25519_contentHash)).toEqual(ipns_libp2pKey_CIDv1); 86 | }); 87 | test("DNSLINK", () => { 88 | // DNSLink is fine to be used before ENS resolve occurs, but should be avoided after 89 | // Context: https://github.com/ensdomains/ens-app/issues/849#issuecomment-777088950 90 | // For now, we allow decoding of legacy values: 91 | const deprecated_dnslink_contentHash = 92 | "e5010170000f6170702e756e69737761702e6f7267"; 93 | const deprecated_dnslink_value = "app.uniswap.org"; 94 | 95 | expect(decode(deprecated_dnslink_contentHash)).toEqual( 96 | deprecated_dnslink_value 97 | ); 98 | }); 99 | }); 100 | test("onion", () => { 101 | expect(decode(onion_contentHash)).toEqual(onion); 102 | }); 103 | test("onion3", () => { 104 | expect(decode(onion3_contentHash)).toEqual(onion3); 105 | }); 106 | test("skynet", () => { 107 | expect(decode(skylink_contentHash)).toEqual(skylink); 108 | }); 109 | test("arweave", () => { 110 | expect(decode(arweave_contentHash)).toEqual(arweave); 111 | }); 112 | }); 113 | describe("encode", () => { 114 | test("swarm", () => { 115 | expect(encode("swarm", swarm)).toEqual(swarm_contentHash); 116 | }); 117 | test("ipfs - CIDv0", () => { 118 | expect(encode("ipfs", ipfs_CIDv0)).toEqual(ipfs_contentHash); 119 | }); 120 | test("ipfs - CIDv1", () => { 121 | expect(encode("ipfs", ipfs_CIDv1)).toEqual(ipfs_contentHash); 122 | }); 123 | describe("ipns", () => { 124 | test("legacy PeerID (RSA B58)", () => { 125 | // RSA ones lookes like regular multihashes 126 | expect(encode("ipns", ipfs_CIDv0)).toEqual(ipns_peerID_B58_contentHash); 127 | }); 128 | test("ED25519 (B58)", () => { 129 | // ED25519 are allowed to be encoded in Base58 for backward-interop 130 | expect(encode("ipns", ipns_peerID_B58)).toEqual( 131 | ipns_ED25519_contentHash 132 | ); 133 | }); 134 | test("PeerID (CIDv1)", () => { 135 | // libp2p-key as CID is the current canonical notation: 136 | // https://github.com/libp2p/specs/blob/master/RFC/0001-text-peerid-cid.md 137 | expect(encode("ipns", ipns_libp2pKey_CIDv1)).toEqual( 138 | ipns_ED25519_contentHash 139 | ); 140 | }); 141 | test("error on non-libp2p-key value", () => { 142 | expect(() => encode("ipns", "12uA8M8Ku8mHUumxHcu7uee")).throws( 143 | "ipns-ns allows only valid cryptographic libp2p-key identifiers, try using ED25519 pubkey instead" 144 | ); 145 | }); 146 | }); 147 | test("onion", () => { 148 | expect(encode("onion", onion)).toEqual(onion_contentHash); 149 | }); 150 | test("onion3", () => { 151 | expect(encode("onion3", onion3)).toEqual(onion3_contentHash); 152 | }); 153 | test("skynet", () => { 154 | expect(encode("skynet", skylink)).toEqual(skylink_contentHash); 155 | }); 156 | test("arweave", () => { 157 | expect(encode("arweave", arweave)).toEqual(arweave_contentHash); 158 | }); 159 | }); 160 | describe("getCodec", () => { 161 | test("swarm", () => { 162 | expect(getCodec(swarm_contentHash)).toEqual("swarm"); 163 | }); 164 | test("ipfs", () => { 165 | expect(getCodec(ipfs_contentHash)).toEqual("ipfs"); 166 | }); 167 | test("ipns", () => { 168 | expect(getCodec(ipns_ED25519_contentHash)).toEqual("ipns"); 169 | }); 170 | test("onion", () => { 171 | expect(getCodec(onion_contentHash)).toEqual("onion"); 172 | }); 173 | test("onion3", () => { 174 | expect(getCodec(onion3_contentHash)).toEqual("onion3"); 175 | }); 176 | test("skynet", () => { 177 | expect(getCodec(skylink_contentHash)).toEqual("skynet"); 178 | }); 179 | test("arweave", () => { 180 | expect(getCodec(arweave_contentHash)).toEqual("arweave"); 181 | }); 182 | }); 183 | }); 184 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | '@multiformats/sha3': ^2.0.17 5 | multiformats: ^12.0.1 6 | typescript: ^5.1.6 7 | vitest: ^0.33.0 8 | 9 | dependencies: 10 | '@multiformats/sha3': 2.0.17 11 | multiformats: 12.0.1 12 | 13 | devDependencies: 14 | typescript: 5.1.6 15 | vitest: 0.33.0 16 | 17 | packages: 18 | 19 | /@esbuild/android-arm/0.18.13: 20 | resolution: {integrity: sha512-KwqFhxRFMKZINHzCqf8eKxE0XqWlAVPRxwy6rc7CbVFxzUWB2sA/s3hbMZeemPdhN3fKBkqOaFhTbS8xJXYIWQ==} 21 | engines: {node: '>=12'} 22 | cpu: [arm] 23 | os: [android] 24 | requiresBuild: true 25 | dev: true 26 | optional: true 27 | 28 | /@esbuild/android-arm64/0.18.13: 29 | resolution: {integrity: sha512-j7NhycJUoUAG5kAzGf4fPWfd17N6SM3o1X6MlXVqfHvs2buFraCJzos9vbeWjLxOyBKHyPOnuCuipbhvbYtTAg==} 30 | engines: {node: '>=12'} 31 | cpu: [arm64] 32 | os: [android] 33 | requiresBuild: true 34 | dev: true 35 | optional: true 36 | 37 | /@esbuild/android-x64/0.18.13: 38 | resolution: {integrity: sha512-M2eZkRxR6WnWfVELHmv6MUoHbOqnzoTVSIxgtsyhm/NsgmL+uTmag/VVzdXvmahak1I6sOb1K/2movco5ikDJg==} 39 | engines: {node: '>=12'} 40 | cpu: [x64] 41 | os: [android] 42 | requiresBuild: true 43 | dev: true 44 | optional: true 45 | 46 | /@esbuild/darwin-arm64/0.18.13: 47 | resolution: {integrity: sha512-f5goG30YgR1GU+fxtaBRdSW3SBG9pZW834Mmhxa6terzcboz7P2R0k4lDxlkP7NYRIIdBbWp+VgwQbmMH4yV7w==} 48 | engines: {node: '>=12'} 49 | cpu: [arm64] 50 | os: [darwin] 51 | requiresBuild: true 52 | dev: true 53 | optional: true 54 | 55 | /@esbuild/darwin-x64/0.18.13: 56 | resolution: {integrity: sha512-RIrxoKH5Eo+yE5BtaAIMZaiKutPhZjw+j0OCh8WdvKEKJQteacq0myZvBDLU+hOzQOZWJeDnuQ2xgSScKf1Ovw==} 57 | engines: {node: '>=12'} 58 | cpu: [x64] 59 | os: [darwin] 60 | requiresBuild: true 61 | dev: true 62 | optional: true 63 | 64 | /@esbuild/freebsd-arm64/0.18.13: 65 | resolution: {integrity: sha512-AfRPhHWmj9jGyLgW/2FkYERKmYR+IjYxf2rtSLmhOrPGFh0KCETFzSjx/JX/HJnvIqHt/DRQD/KAaVsUKoI3Xg==} 66 | engines: {node: '>=12'} 67 | cpu: [arm64] 68 | os: [freebsd] 69 | requiresBuild: true 70 | dev: true 71 | optional: true 72 | 73 | /@esbuild/freebsd-x64/0.18.13: 74 | resolution: {integrity: sha512-pGzWWZJBInhIgdEwzn8VHUBang8UvFKsvjDkeJ2oyY5gZtAM6BaxK0QLCuZY+qoj/nx/lIaItH425rm/hloETA==} 75 | engines: {node: '>=12'} 76 | cpu: [x64] 77 | os: [freebsd] 78 | requiresBuild: true 79 | dev: true 80 | optional: true 81 | 82 | /@esbuild/linux-arm/0.18.13: 83 | resolution: {integrity: sha512-4iMxLRMCxGyk7lEvkkvrxw4aJeC93YIIrfbBlUJ062kilUUnAiMb81eEkVvCVoh3ON283ans7+OQkuy1uHW+Hw==} 84 | engines: {node: '>=12'} 85 | cpu: [arm] 86 | os: [linux] 87 | requiresBuild: true 88 | dev: true 89 | optional: true 90 | 91 | /@esbuild/linux-arm64/0.18.13: 92 | resolution: {integrity: sha512-hCzZbVJEHV7QM77fHPv2qgBcWxgglGFGCxk6KfQx6PsVIdi1u09X7IvgE9QKqm38OpkzaAkPnnPqwRsltvLkIQ==} 93 | engines: {node: '>=12'} 94 | cpu: [arm64] 95 | os: [linux] 96 | requiresBuild: true 97 | dev: true 98 | optional: true 99 | 100 | /@esbuild/linux-ia32/0.18.13: 101 | resolution: {integrity: sha512-I3OKGbynl3AAIO6onXNrup/ttToE6Rv2XYfFgLK/wnr2J+1g+7k4asLrE+n7VMhaqX+BUnyWkCu27rl+62Adug==} 102 | engines: {node: '>=12'} 103 | cpu: [ia32] 104 | os: [linux] 105 | requiresBuild: true 106 | dev: true 107 | optional: true 108 | 109 | /@esbuild/linux-loong64/0.18.13: 110 | resolution: {integrity: sha512-8pcKDApAsKc6WW51ZEVidSGwGbebYw2qKnO1VyD8xd6JN0RN6EUXfhXmDk9Vc4/U3Y4AoFTexQewQDJGsBXBpg==} 111 | engines: {node: '>=12'} 112 | cpu: [loong64] 113 | os: [linux] 114 | requiresBuild: true 115 | dev: true 116 | optional: true 117 | 118 | /@esbuild/linux-mips64el/0.18.13: 119 | resolution: {integrity: sha512-6GU+J1PLiVqWx8yoCK4Z0GnfKyCGIH5L2KQipxOtbNPBs+qNDcMJr9euxnyJ6FkRPyMwaSkjejzPSISD9hb+gg==} 120 | engines: {node: '>=12'} 121 | cpu: [mips64el] 122 | os: [linux] 123 | requiresBuild: true 124 | dev: true 125 | optional: true 126 | 127 | /@esbuild/linux-ppc64/0.18.13: 128 | resolution: {integrity: sha512-pfn/OGZ8tyR8YCV7MlLl5hAit2cmS+j/ZZg9DdH0uxdCoJpV7+5DbuXrR+es4ayRVKIcfS9TTMCs60vqQDmh+w==} 129 | engines: {node: '>=12'} 130 | cpu: [ppc64] 131 | os: [linux] 132 | requiresBuild: true 133 | dev: true 134 | optional: true 135 | 136 | /@esbuild/linux-riscv64/0.18.13: 137 | resolution: {integrity: sha512-aIbhU3LPg0lOSCfVeGHbmGYIqOtW6+yzO+Nfv57YblEK01oj0mFMtvDJlOaeAZ6z0FZ9D13oahi5aIl9JFphGg==} 138 | engines: {node: '>=12'} 139 | cpu: [riscv64] 140 | os: [linux] 141 | requiresBuild: true 142 | dev: true 143 | optional: true 144 | 145 | /@esbuild/linux-s390x/0.18.13: 146 | resolution: {integrity: sha512-Pct1QwF2sp+5LVi4Iu5Y+6JsGaV2Z2vm4O9Dd7XZ5tKYxEHjFtb140fiMcl5HM1iuv6xXO8O1Vrb1iJxHlv8UA==} 147 | engines: {node: '>=12'} 148 | cpu: [s390x] 149 | os: [linux] 150 | requiresBuild: true 151 | dev: true 152 | optional: true 153 | 154 | /@esbuild/linux-x64/0.18.13: 155 | resolution: {integrity: sha512-zTrIP0KzYP7O0+3ZnmzvUKgGtUvf4+piY8PIO3V8/GfmVd3ZyHJGz7Ht0np3P1wz+I8qJ4rjwJKqqEAbIEPngA==} 156 | engines: {node: '>=12'} 157 | cpu: [x64] 158 | os: [linux] 159 | requiresBuild: true 160 | dev: true 161 | optional: true 162 | 163 | /@esbuild/netbsd-x64/0.18.13: 164 | resolution: {integrity: sha512-I6zs10TZeaHDYoGxENuksxE1sxqZpCp+agYeW039yqFwh3MgVvdmXL5NMveImOC6AtpLvE4xG5ujVic4NWFIDQ==} 165 | engines: {node: '>=12'} 166 | cpu: [x64] 167 | os: [netbsd] 168 | requiresBuild: true 169 | dev: true 170 | optional: true 171 | 172 | /@esbuild/openbsd-x64/0.18.13: 173 | resolution: {integrity: sha512-W5C5nczhrt1y1xPG5bV+0M12p2vetOGlvs43LH8SopQ3z2AseIROu09VgRqydx5qFN7y9qCbpgHLx0kb0TcW7g==} 174 | engines: {node: '>=12'} 175 | cpu: [x64] 176 | os: [openbsd] 177 | requiresBuild: true 178 | dev: true 179 | optional: true 180 | 181 | /@esbuild/sunos-x64/0.18.13: 182 | resolution: {integrity: sha512-X/xzuw4Hzpo/yq3YsfBbIsipNgmsm8mE/QeWbdGdTTeZ77fjxI2K0KP3AlhZ6gU3zKTw1bKoZTuKLnqcJ537qw==} 183 | engines: {node: '>=12'} 184 | cpu: [x64] 185 | os: [sunos] 186 | requiresBuild: true 187 | dev: true 188 | optional: true 189 | 190 | /@esbuild/win32-arm64/0.18.13: 191 | resolution: {integrity: sha512-4CGYdRQT/ILd+yLLE5i4VApMPfGE0RPc/wFQhlluDQCK09+b4JDbxzzjpgQqTPrdnP7r5KUtGVGZYclYiPuHrw==} 192 | engines: {node: '>=12'} 193 | cpu: [arm64] 194 | os: [win32] 195 | requiresBuild: true 196 | dev: true 197 | optional: true 198 | 199 | /@esbuild/win32-ia32/0.18.13: 200 | resolution: {integrity: sha512-D+wKZaRhQI+MUGMH+DbEr4owC2D7XnF+uyGiZk38QbgzLcofFqIOwFs7ELmIeU45CQgfHNy9Q+LKW3cE8g37Kg==} 201 | engines: {node: '>=12'} 202 | cpu: [ia32] 203 | os: [win32] 204 | requiresBuild: true 205 | dev: true 206 | optional: true 207 | 208 | /@esbuild/win32-x64/0.18.13: 209 | resolution: {integrity: sha512-iVl6lehAfJS+VmpF3exKpNQ8b0eucf5VWfzR8S7xFve64NBNz2jPUgx1X93/kfnkfgP737O+i1k54SVQS7uVZA==} 210 | engines: {node: '>=12'} 211 | cpu: [x64] 212 | os: [win32] 213 | requiresBuild: true 214 | dev: true 215 | optional: true 216 | 217 | /@jest/schemas/29.6.0: 218 | resolution: {integrity: sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==} 219 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 220 | dependencies: 221 | '@sinclair/typebox': 0.27.8 222 | dev: true 223 | 224 | /@jridgewell/sourcemap-codec/1.4.15: 225 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 226 | dev: true 227 | 228 | /@multiformats/sha3/2.0.17: 229 | resolution: {integrity: sha512-7ik6pk178qLO2cpNucgf48UnAOBMkq/2H92DP4SprZOJqM9zqbVaKS7XyYW6UvhRsDJ3wi921fYv1ihTtQHLtA==} 230 | dependencies: 231 | js-sha3: 0.8.0 232 | multiformats: 9.9.0 233 | dev: false 234 | 235 | /@sinclair/typebox/0.27.8: 236 | resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} 237 | dev: true 238 | 239 | /@types/chai-subset/1.3.3: 240 | resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} 241 | dependencies: 242 | '@types/chai': 4.3.5 243 | dev: true 244 | 245 | /@types/chai/4.3.5: 246 | resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==} 247 | dev: true 248 | 249 | /@types/node/20.4.2: 250 | resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==} 251 | dev: true 252 | 253 | /@vitest/expect/0.33.0: 254 | resolution: {integrity: sha512-sVNf+Gla3mhTCxNJx+wJLDPp/WcstOe0Ksqz4Vec51MmgMth/ia0MGFEkIZmVGeTL5HtjYR4Wl/ZxBxBXZJTzQ==} 255 | dependencies: 256 | '@vitest/spy': 0.33.0 257 | '@vitest/utils': 0.33.0 258 | chai: 4.3.7 259 | dev: true 260 | 261 | /@vitest/runner/0.33.0: 262 | resolution: {integrity: sha512-UPfACnmCB6HKRHTlcgCoBh6ppl6fDn+J/xR8dTufWiKt/74Y9bHci5CKB8tESSV82zKYtkBJo9whU3mNvfaisg==} 263 | dependencies: 264 | '@vitest/utils': 0.33.0 265 | p-limit: 4.0.0 266 | pathe: 1.1.1 267 | dev: true 268 | 269 | /@vitest/snapshot/0.33.0: 270 | resolution: {integrity: sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==} 271 | dependencies: 272 | magic-string: 0.30.1 273 | pathe: 1.1.1 274 | pretty-format: 29.6.1 275 | dev: true 276 | 277 | /@vitest/spy/0.33.0: 278 | resolution: {integrity: sha512-Kv+yZ4hnH1WdiAkPUQTpRxW8kGtH8VRTnus7ZTGovFYM1ZezJpvGtb9nPIjPnptHbsyIAxYZsEpVPYgtpjGnrg==} 279 | dependencies: 280 | tinyspy: 2.1.1 281 | dev: true 282 | 283 | /@vitest/utils/0.33.0: 284 | resolution: {integrity: sha512-pF1w22ic965sv+EN6uoePkAOTkAPWM03Ri/jXNyMIKBb/XHLDPfhLvf/Fa9g0YECevAIz56oVYXhodLvLQ/awA==} 285 | dependencies: 286 | diff-sequences: 29.4.3 287 | loupe: 2.3.6 288 | pretty-format: 29.6.1 289 | dev: true 290 | 291 | /acorn-walk/8.2.0: 292 | resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} 293 | engines: {node: '>=0.4.0'} 294 | dev: true 295 | 296 | /acorn/8.10.0: 297 | resolution: {integrity: sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==} 298 | engines: {node: '>=0.4.0'} 299 | hasBin: true 300 | dev: true 301 | 302 | /ansi-styles/5.2.0: 303 | resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} 304 | engines: {node: '>=10'} 305 | dev: true 306 | 307 | /assertion-error/1.1.0: 308 | resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} 309 | dev: true 310 | 311 | /cac/6.7.14: 312 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 313 | engines: {node: '>=8'} 314 | dev: true 315 | 316 | /chai/4.3.7: 317 | resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} 318 | engines: {node: '>=4'} 319 | dependencies: 320 | assertion-error: 1.1.0 321 | check-error: 1.0.2 322 | deep-eql: 4.1.3 323 | get-func-name: 2.0.0 324 | loupe: 2.3.6 325 | pathval: 1.1.1 326 | type-detect: 4.0.8 327 | dev: true 328 | 329 | /check-error/1.0.2: 330 | resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} 331 | dev: true 332 | 333 | /debug/4.3.4: 334 | resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 335 | engines: {node: '>=6.0'} 336 | peerDependencies: 337 | supports-color: '*' 338 | peerDependenciesMeta: 339 | supports-color: 340 | optional: true 341 | dependencies: 342 | ms: 2.1.2 343 | dev: true 344 | 345 | /deep-eql/4.1.3: 346 | resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} 347 | engines: {node: '>=6'} 348 | dependencies: 349 | type-detect: 4.0.8 350 | dev: true 351 | 352 | /diff-sequences/29.4.3: 353 | resolution: {integrity: sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==} 354 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 355 | dev: true 356 | 357 | /esbuild/0.18.13: 358 | resolution: {integrity: sha512-vhg/WR/Oiu4oUIkVhmfcc23G6/zWuEQKFS+yiosSHe4aN6+DQRXIfeloYGibIfVhkr4wyfuVsGNLr+sQU1rWWw==} 359 | engines: {node: '>=12'} 360 | hasBin: true 361 | requiresBuild: true 362 | optionalDependencies: 363 | '@esbuild/android-arm': 0.18.13 364 | '@esbuild/android-arm64': 0.18.13 365 | '@esbuild/android-x64': 0.18.13 366 | '@esbuild/darwin-arm64': 0.18.13 367 | '@esbuild/darwin-x64': 0.18.13 368 | '@esbuild/freebsd-arm64': 0.18.13 369 | '@esbuild/freebsd-x64': 0.18.13 370 | '@esbuild/linux-arm': 0.18.13 371 | '@esbuild/linux-arm64': 0.18.13 372 | '@esbuild/linux-ia32': 0.18.13 373 | '@esbuild/linux-loong64': 0.18.13 374 | '@esbuild/linux-mips64el': 0.18.13 375 | '@esbuild/linux-ppc64': 0.18.13 376 | '@esbuild/linux-riscv64': 0.18.13 377 | '@esbuild/linux-s390x': 0.18.13 378 | '@esbuild/linux-x64': 0.18.13 379 | '@esbuild/netbsd-x64': 0.18.13 380 | '@esbuild/openbsd-x64': 0.18.13 381 | '@esbuild/sunos-x64': 0.18.13 382 | '@esbuild/win32-arm64': 0.18.13 383 | '@esbuild/win32-ia32': 0.18.13 384 | '@esbuild/win32-x64': 0.18.13 385 | dev: true 386 | 387 | /fsevents/2.3.2: 388 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 389 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 390 | os: [darwin] 391 | requiresBuild: true 392 | dev: true 393 | optional: true 394 | 395 | /get-func-name/2.0.0: 396 | resolution: {integrity: sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==} 397 | dev: true 398 | 399 | /js-sha3/0.8.0: 400 | resolution: {integrity: sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==} 401 | dev: false 402 | 403 | /jsonc-parser/3.2.0: 404 | resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} 405 | dev: true 406 | 407 | /local-pkg/0.4.3: 408 | resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} 409 | engines: {node: '>=14'} 410 | dev: true 411 | 412 | /loupe/2.3.6: 413 | resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} 414 | dependencies: 415 | get-func-name: 2.0.0 416 | dev: true 417 | 418 | /magic-string/0.30.1: 419 | resolution: {integrity: sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==} 420 | engines: {node: '>=12'} 421 | dependencies: 422 | '@jridgewell/sourcemap-codec': 1.4.15 423 | dev: true 424 | 425 | /mlly/1.4.0: 426 | resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==} 427 | dependencies: 428 | acorn: 8.10.0 429 | pathe: 1.1.1 430 | pkg-types: 1.0.3 431 | ufo: 1.1.2 432 | dev: true 433 | 434 | /ms/2.1.2: 435 | resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 436 | dev: true 437 | 438 | /multiformats/12.0.1: 439 | resolution: {integrity: sha512-s01wijBJoDUqESWSzePY0lvTw7J3PVO9x2Cc6ASI5AMZM2Gnhh7BC17+nlFhHKU7dDzaCaRfb+NiqNzOsgPUoQ==} 440 | engines: {node: '>=16.0.0', npm: '>=7.0.0'} 441 | dev: false 442 | 443 | /multiformats/9.9.0: 444 | resolution: {integrity: sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==} 445 | dev: false 446 | 447 | /nanoid/3.3.6: 448 | resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} 449 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 450 | hasBin: true 451 | dev: true 452 | 453 | /p-limit/4.0.0: 454 | resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} 455 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 456 | dependencies: 457 | yocto-queue: 1.0.0 458 | dev: true 459 | 460 | /pathe/1.1.1: 461 | resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} 462 | dev: true 463 | 464 | /pathval/1.1.1: 465 | resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} 466 | dev: true 467 | 468 | /picocolors/1.0.0: 469 | resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 470 | dev: true 471 | 472 | /pkg-types/1.0.3: 473 | resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==} 474 | dependencies: 475 | jsonc-parser: 3.2.0 476 | mlly: 1.4.0 477 | pathe: 1.1.1 478 | dev: true 479 | 480 | /postcss/8.4.26: 481 | resolution: {integrity: sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==} 482 | engines: {node: ^10 || ^12 || >=14} 483 | dependencies: 484 | nanoid: 3.3.6 485 | picocolors: 1.0.0 486 | source-map-js: 1.0.2 487 | dev: true 488 | 489 | /pretty-format/29.6.1: 490 | resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==} 491 | engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} 492 | dependencies: 493 | '@jest/schemas': 29.6.0 494 | ansi-styles: 5.2.0 495 | react-is: 18.2.0 496 | dev: true 497 | 498 | /react-is/18.2.0: 499 | resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} 500 | dev: true 501 | 502 | /rollup/3.26.2: 503 | resolution: {integrity: sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==} 504 | engines: {node: '>=14.18.0', npm: '>=8.0.0'} 505 | hasBin: true 506 | optionalDependencies: 507 | fsevents: 2.3.2 508 | dev: true 509 | 510 | /siginfo/2.0.0: 511 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 512 | dev: true 513 | 514 | /source-map-js/1.0.2: 515 | resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} 516 | engines: {node: '>=0.10.0'} 517 | dev: true 518 | 519 | /stackback/0.0.2: 520 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 521 | dev: true 522 | 523 | /std-env/3.3.3: 524 | resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==} 525 | dev: true 526 | 527 | /strip-literal/1.0.1: 528 | resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} 529 | dependencies: 530 | acorn: 8.10.0 531 | dev: true 532 | 533 | /tinybench/2.5.0: 534 | resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==} 535 | dev: true 536 | 537 | /tinypool/0.6.0: 538 | resolution: {integrity: sha512-FdswUUo5SxRizcBc6b1GSuLpLjisa8N8qMyYoP3rl+bym+QauhtJP5bvZY1ytt8krKGmMLYIRl36HBZfeAoqhQ==} 539 | engines: {node: '>=14.0.0'} 540 | dev: true 541 | 542 | /tinyspy/2.1.1: 543 | resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==} 544 | engines: {node: '>=14.0.0'} 545 | dev: true 546 | 547 | /type-detect/4.0.8: 548 | resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} 549 | engines: {node: '>=4'} 550 | dev: true 551 | 552 | /typescript/5.1.6: 553 | resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==} 554 | engines: {node: '>=14.17'} 555 | hasBin: true 556 | dev: true 557 | 558 | /ufo/1.1.2: 559 | resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} 560 | dev: true 561 | 562 | /vite-node/0.33.0_@types+node@20.4.2: 563 | resolution: {integrity: sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==} 564 | engines: {node: '>=v14.18.0'} 565 | hasBin: true 566 | dependencies: 567 | cac: 6.7.14 568 | debug: 4.3.4 569 | mlly: 1.4.0 570 | pathe: 1.1.1 571 | picocolors: 1.0.0 572 | vite: 4.4.4_@types+node@20.4.2 573 | transitivePeerDependencies: 574 | - '@types/node' 575 | - less 576 | - lightningcss 577 | - sass 578 | - stylus 579 | - sugarss 580 | - supports-color 581 | - terser 582 | dev: true 583 | 584 | /vite/4.4.4_@types+node@20.4.2: 585 | resolution: {integrity: sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==} 586 | engines: {node: ^14.18.0 || >=16.0.0} 587 | hasBin: true 588 | peerDependencies: 589 | '@types/node': '>= 14' 590 | less: '*' 591 | lightningcss: ^1.21.0 592 | sass: '*' 593 | stylus: '*' 594 | sugarss: '*' 595 | terser: ^5.4.0 596 | peerDependenciesMeta: 597 | '@types/node': 598 | optional: true 599 | less: 600 | optional: true 601 | lightningcss: 602 | optional: true 603 | sass: 604 | optional: true 605 | stylus: 606 | optional: true 607 | sugarss: 608 | optional: true 609 | terser: 610 | optional: true 611 | dependencies: 612 | '@types/node': 20.4.2 613 | esbuild: 0.18.13 614 | postcss: 8.4.26 615 | rollup: 3.26.2 616 | optionalDependencies: 617 | fsevents: 2.3.2 618 | dev: true 619 | 620 | /vitest/0.33.0: 621 | resolution: {integrity: sha512-1CxaugJ50xskkQ0e969R/hW47za4YXDUfWJDxip1hwbnhUjYolpfUn2AMOulqG/Dtd9WYAtkHmM/m3yKVrEejQ==} 622 | engines: {node: '>=v14.18.0'} 623 | hasBin: true 624 | peerDependencies: 625 | '@edge-runtime/vm': '*' 626 | '@vitest/browser': '*' 627 | '@vitest/ui': '*' 628 | happy-dom: '*' 629 | jsdom: '*' 630 | playwright: '*' 631 | safaridriver: '*' 632 | webdriverio: '*' 633 | peerDependenciesMeta: 634 | '@edge-runtime/vm': 635 | optional: true 636 | '@vitest/browser': 637 | optional: true 638 | '@vitest/ui': 639 | optional: true 640 | happy-dom: 641 | optional: true 642 | jsdom: 643 | optional: true 644 | playwright: 645 | optional: true 646 | safaridriver: 647 | optional: true 648 | webdriverio: 649 | optional: true 650 | dependencies: 651 | '@types/chai': 4.3.5 652 | '@types/chai-subset': 1.3.3 653 | '@types/node': 20.4.2 654 | '@vitest/expect': 0.33.0 655 | '@vitest/runner': 0.33.0 656 | '@vitest/snapshot': 0.33.0 657 | '@vitest/spy': 0.33.0 658 | '@vitest/utils': 0.33.0 659 | acorn: 8.10.0 660 | acorn-walk: 8.2.0 661 | cac: 6.7.14 662 | chai: 4.3.7 663 | debug: 4.3.4 664 | local-pkg: 0.4.3 665 | magic-string: 0.30.1 666 | pathe: 1.1.1 667 | picocolors: 1.0.0 668 | std-env: 3.3.3 669 | strip-literal: 1.0.1 670 | tinybench: 2.5.0 671 | tinypool: 0.6.0 672 | vite: 4.4.4_@types+node@20.4.2 673 | vite-node: 0.33.0_@types+node@20.4.2 674 | why-is-node-running: 2.2.2 675 | transitivePeerDependencies: 676 | - less 677 | - lightningcss 678 | - sass 679 | - stylus 680 | - sugarss 681 | - supports-color 682 | - terser 683 | dev: true 684 | 685 | /why-is-node-running/2.2.2: 686 | resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} 687 | engines: {node: '>=8'} 688 | hasBin: true 689 | dependencies: 690 | siginfo: 2.0.0 691 | stackback: 0.0.2 692 | dev: true 693 | 694 | /yocto-queue/1.0.0: 695 | resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} 696 | engines: {node: '>=12.20'} 697 | dev: true 698 | --------------------------------------------------------------------------------