diff --git a/bun.lock b/bun.lock index 92c5ebd6..54f28eb1 100644 --- a/bun.lock +++ b/bun.lock @@ -268,6 +268,28 @@ "name": "@wrnexus/benchmark", "version": "0.8.8", }, + "packages/block-editor": { + "name": "@wrnexus/block-editor", + "version": "0.1.0", + "dependencies": { + "@wrnexus/blocks": "workspace:*", + "@wrnexus/dnd": "workspace:*", + "@wrnexus/reactive": "workspace:*", + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "happy-dom": "^20.11.2", + "typescript": "^6.0.3", + }, + }, + "packages/blocks": { + "name": "@wrnexus/blocks", + "version": "0.1.0", + "devDependencies": { + "@types/bun": "^1.4.0", + "typescript": "^6.0.3", + }, + }, "packages/cache": { "name": "@wrnexus/cache", "version": "0.8.8", @@ -392,6 +414,32 @@ "typescript": "^6.0.3", }, }, + "packages/dnd": { + "name": "@wrnexus/dnd", + "version": "0.1.0", + "dependencies": { + "@dnd-kit/dom": "0.5.0", + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "happy-dom": "^20.11.2", + "typescript": "^6.0.3", + }, + }, + "packages/email-renderer": { + "name": "@wrnexus/email-renderer", + "version": "0.1.0", + "dependencies": { + "@wrnexus/blocks": "workspace:*", + "amphtml-validator": "1.0.38", + "mjml": "5.4.0", + "mjml-parser-xml": "5.4.0", + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "typescript": "^6.0.3", + }, + }, "packages/encryption": { "name": "@wrnexus/encryption", "version": "0.8.10", @@ -742,8 +790,22 @@ "@app/shared": ["@app/shared@workspace:examples/inter-app-api-showcase/packages/shared"], + "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], + + "@colordx/core": ["@colordx/core@5.8.0", "", {}, "sha512-cG0QJAO6VkaRUlIb0zOzX9gfJgs1pjoOL9gZ/PK1kfvBs0GCkADu5oQh6gPxXgQnZ3gV575h+lQRC0NlMDTclA=="], + "@cyberalien/svg-utils": ["@cyberalien/svg-utils@1.2.19", "", { "dependencies": { "@iconify/types": "^2.0.0" } }, "sha512-paDJoDu+LhuH5Ma1q0BDqBpg6d16Xk22cZZrlg/s/J9oB8KgnnpU/snaEETJu2G1pwYcWQINUz6WUqu0Wm9k6A=="], + "@dnd-kit/abstract": ["@dnd-kit/abstract@0.5.0", "", { "dependencies": { "@dnd-kit/geometry": "^0.5.0", "@dnd-kit/state": "^0.5.0", "tslib": "^2.6.2" } }, "sha512-hi13iMJgjPX/KDYVKg5VeDIhmYiV6buc9bAX+tCLYf4QdyYjPbsXjn2sPo6m7fQ6SGJBEFgHJ2PemeKDUbwBaA=="], + + "@dnd-kit/collision": ["@dnd-kit/collision@0.5.0", "", { "dependencies": { "@dnd-kit/abstract": "^0.5.0", "@dnd-kit/geometry": "^0.5.0", "tslib": "^2.6.2" } }, "sha512-xUqRn3lS7oqLkT0AnnHS/STh/Czvwe1UapZFYiLbsUGxopMsQd4teaPCzPouOThoMdGEe+dHWjfqJl6t9iG4mQ=="], + + "@dnd-kit/dom": ["@dnd-kit/dom@0.5.0", "", { "dependencies": { "@dnd-kit/abstract": "^0.5.0", "@dnd-kit/collision": "^0.5.0", "@dnd-kit/geometry": "^0.5.0", "@dnd-kit/state": "^0.5.0", "tslib": "^2.6.2" } }, "sha512-f2xFJp5SYQ8EW/Fbtaa8iBb66hpkWc7qa8vU826KW11/tb44sH+AisZnGtwOOTWTQ0GraqBDr5ixTErww+eKXw=="], + + "@dnd-kit/geometry": ["@dnd-kit/geometry@0.5.0", "", { "dependencies": { "@dnd-kit/state": "^0.5.0", "tslib": "^2.6.2" } }, "sha512-ubHQS1CiSDH8ssYH2xG5BnpwPSFP1tStXXjug7/Ba6qnQdu/EUH47l6QXKIksQnnanfVfDf0aGeevRxgZlj28A=="], + + "@dnd-kit/state": ["@dnd-kit/state@0.5.0", "", { "dependencies": { "@preact/signals-core": "^1.10.0", "tslib": "^2.6.2" } }, "sha512-y7XbabQqjF58Lk8YmDQuR8l6QjN+Kh4qlGEjUvHuIeasLk1QP+9L5diXS98VMxQIivyMmUtX2//f+3N7qPJX4w=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], @@ -832,6 +894,8 @@ "@iconify/utils": ["@iconify/utils@3.1.4", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "import-meta-resolve": "^4.2.0" } }, "sha512-b1S7B1k9ohZ+iNTi2ATxbRYG9fTrJmUT0rc46bvVnNxqNRGW7dyo/vRREwyniI5IRN2RSJHDcm+s3BjWrSAjHw=="], + "@isaacs/cliui": ["@isaacs/cliui@9.0.0", "", {}, "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg=="], + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], @@ -846,6 +910,8 @@ "@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="], + "@one-ini/wasm": ["@one-ini/wasm@0.1.1", "", {}, "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw=="], + "@parcel/watcher": ["@parcel/watcher@2.5.1", "", { "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", "micromatch": "^4.0.5", "node-addon-api": "^7.0.0" }, "optionalDependencies": { "@parcel/watcher-android-arm64": "2.5.1", "@parcel/watcher-darwin-arm64": "2.5.1", "@parcel/watcher-darwin-x64": "2.5.1", "@parcel/watcher-freebsd-x64": "2.5.1", "@parcel/watcher-linux-arm-glibc": "2.5.1", "@parcel/watcher-linux-arm-musl": "2.5.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1", "@parcel/watcher-linux-arm64-musl": "2.5.1", "@parcel/watcher-linux-x64-glibc": "2.5.1", "@parcel/watcher-linux-x64-musl": "2.5.1", "@parcel/watcher-win32-arm64": "2.5.1", "@parcel/watcher-win32-ia32": "2.5.1", "@parcel/watcher-win32-x64": "2.5.1" } }, "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg=="], "@parcel/watcher-android-arm64": ["@parcel/watcher-android-arm64@2.5.1", "", { "os": "android", "cpu": "arm64" }, "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA=="], @@ -898,6 +964,10 @@ "@peculiar/x509": ["@peculiar/x509@1.14.3", "", { "dependencies": { "@peculiar/asn1-cms": "^2.6.0", "@peculiar/asn1-csr": "^2.6.0", "@peculiar/asn1-ecc": "^2.6.0", "@peculiar/asn1-pkcs9": "^2.6.0", "@peculiar/asn1-rsa": "^2.6.0", "@peculiar/asn1-schema": "^2.6.0", "@peculiar/asn1-x509": "^2.6.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA=="], + "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], + + "@preact/signals-core": ["@preact/signals-core@1.14.4", "", {}, "sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.62.5", "", { "os": "android", "cpu": "arm" }, "sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.62.5", "", { "os": "android", "cpu": "arm64" }, "sha512-oGVqyQlxnrz9/ty89oHpU857VUHEl5/Xu4R2lS+aivCTrNnSsbiENzTnNaBsjxH0CNWGPhzHArOLFwo+oKXveA=="], @@ -992,6 +1062,8 @@ "@types/react-dom": ["@types/react-dom@19.2.5", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-fMPwH9v7r/pp43yUd2/Mbiex5KouJwwR3dzHkhLREUC6764VyDsqxhAxv6OFEYR1RhjOyD1naqba8ECDBe7ZQg=="], + "@types/relateurl": ["@types/relateurl@0.2.33", "", {}, "sha512-bTQCKsVbIdzLqZhLkF5fcJQreE4y1ro4DIyVrlDNSCJRRwHhB8Z+4zXXa8jN6eDvc2HbRsEYgbvrnGvi54EpSw=="], + "@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="], "@types/ws": ["@types/ws@8.18.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg=="], @@ -1026,6 +1098,10 @@ "@wrnexus/benchmark": ["@wrnexus/benchmark@workspace:packages/benchmark"], + "@wrnexus/block-editor": ["@wrnexus/block-editor@workspace:packages/block-editor"], + + "@wrnexus/blocks": ["@wrnexus/blocks@workspace:packages/blocks"], + "@wrnexus/cache": ["@wrnexus/cache@workspace:packages/cache"], "@wrnexus/captcha": ["@wrnexus/captcha@workspace:packages/captcha"], @@ -1046,6 +1122,10 @@ "@wrnexus/dev-toolbar": ["@wrnexus/dev-toolbar@workspace:packages/dev-toolbar"], + "@wrnexus/dnd": ["@wrnexus/dnd@workspace:packages/dnd"], + + "@wrnexus/email-renderer": ["@wrnexus/email-renderer@workspace:packages/email-renderer"], + "@wrnexus/encryption": ["@wrnexus/encryption@workspace:packages/encryption"], "@wrnexus/graphql": ["@wrnexus/graphql@workspace:packages/graphql"], @@ -1122,6 +1202,8 @@ "@wrnexus/validation": ["@wrnexus/validation@workspace:packages/validation"], + "abbrev": ["abbrev@2.0.0", "", {}, "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ=="], + "acorn": ["acorn@8.18.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -1130,14 +1212,28 @@ "ajv": ["ajv@6.15.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="], + "amphtml-validator": ["amphtml-validator@1.0.38", "", { "dependencies": { "colors": "1.4.0", "commander": "12.0.0", "promise": "8.3.0" }, "bin": { "amphtml-validator": "cli.js" } }, "sha512-TqGeHVRUEsMiJoaRvMhzZBv8oljLhhJgzkaPX/zT2eJFG5FLeaMehd6riNwsuMmi9e/5xsI0pocbTgN/u2sonQ=="], + + "ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="], + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], + "asn1js": ["asn1js@3.0.10", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.5", "tslib": "^2.8.1" } }, "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg=="], "auth-showcase": ["auth-showcase@workspace:examples/auth-showcase"], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.11.20", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-H0ulySigv6icDJ1F7SjtdCD6PrhTpdYCmP0CactWy1+ekh0AFd0o1Wn5T8b+hnTmdBx19u9yhL6wvCylXMY7zw=="], + "basic-app": ["basic-app@workspace:examples/basic-app"], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], @@ -1146,6 +1242,8 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "browserslist": ["browserslist@4.28.8", "", { "dependencies": { "baseline-browser-mapping": "^2.11.12", "caniuse-lite": "^1.0.30001809", "electron-to-chromium": "^1.5.402", "node-releases": "^2.0.53", "update-browserslist-db": "^1.3.0" }, "bin": { "browserslist": "cli.js" } }, "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA=="], + "buffer-image-size": ["buffer-image-size@0.6.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-nEh+kZOPY1w+gcCMobZ6ETUp9WfibndnosbpwB1iJk/8Gt5ZF2bhS6+B6bPYz424KtwsR6Rflc3tCz1/ghX2dQ=="], "bun-types": ["bun-types@1.4.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-iIKw23BspnQQYd3prITOBxeUsxBHnwzX6YJfGMuNOZzeNcMmVqzIIVGRm1l69ogaPQmb4wB6BN8mA5bE9YuC5Q=="], @@ -1156,26 +1254,58 @@ "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + "caniuse-api": ["caniuse-api@3.0.0", "", { "dependencies": { "browserslist": "^4.0.0", "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" } }, "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001810", "", {}, "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg=="], + "captcha-showcase": ["captcha-showcase@workspace:examples/captcha-showcase"], + "cheerio": ["cheerio@1.0.0", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "encoding-sniffer": "^0.2.0", "htmlparser2": "^9.1.0", "parse5": "^7.1.2", "parse5-htmlparser2-tree-adapter": "^7.0.0", "parse5-parser-stream": "^7.1.2", "undici": "^6.19.5", "whatwg-mimetype": "^4.0.0" } }, "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww=="], + + "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="], + + "commander": ["commander@12.0.0", "", {}, "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA=="], "component-showcase": ["component-showcase@workspace:examples/component-showcase"], "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + "config-chain": ["config-chain@1.1.13", "", { "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" } }, "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ=="], + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "cosmiconfig": ["cosmiconfig@10.0.1", "", { "dependencies": { "env-paths": "^2.2.1", "js-yaml": "^5.4.1" } }, "sha512-/zvGoYn0y27QTBmK+0B+oPdEPC4fKGUmJsMTQbDjqph00TrkqfVFW/M+C6ya54sYCgHC6QCwK/mI6djt8cEGHQ=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "css-declaration-sorter": ["css-declaration-sorter@7.4.0", "", { "peerDependencies": { "postcss": "^8.0.9" } }, "sha512-LTuzjPoyA2vMGKKcaOqKSp7Ub2eGrNfKiZH4LpezxpNrsICGCSFvsQOI29psISxNZtaXibkC2CXzrQ5enMeGGw=="], + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], "css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="], "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], + + "cssnano": ["cssnano@7.1.9", "", { "dependencies": { "cssnano-preset-default": "^7.0.17", "lilconfig": "^3.1.3" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-uPR75+5Dk/WJ/YSPR1/YDHdwMM9c5FsaARljfKWgeCKLKOtJ0we21xy/RcCjn53fZnD/f6yYEIZ8pu18+GnbNQ=="], + + "cssnano-preset-default": ["cssnano-preset-default@7.0.17", "", { "dependencies": { "browserslist": "^4.28.2", "css-declaration-sorter": "^7.2.0", "cssnano-utils": "^5.0.3", "postcss-calc": "^10.1.1", "postcss-colormin": "^7.0.10", "postcss-convert-values": "^7.0.12", "postcss-discard-comments": "^7.0.8", "postcss-discard-duplicates": "^7.0.4", "postcss-discard-empty": "^7.0.3", "postcss-discard-overridden": "^7.0.3", "postcss-merge-longhand": "^7.0.7", "postcss-merge-rules": "^7.0.11", "postcss-minify-font-values": "^7.0.3", "postcss-minify-gradients": "^7.0.5", "postcss-minify-params": "^7.0.9", "postcss-minify-selectors": "^7.1.2", "postcss-normalize-charset": "^7.0.3", "postcss-normalize-display-values": "^7.0.3", "postcss-normalize-positions": "^7.0.4", "postcss-normalize-repeat-style": "^7.0.4", "postcss-normalize-string": "^7.0.3", "postcss-normalize-timing-functions": "^7.0.3", "postcss-normalize-unicode": "^7.0.9", "postcss-normalize-url": "^7.0.3", "postcss-normalize-whitespace": "^7.0.3", "postcss-ordered-values": "^7.0.4", "postcss-reduce-initial": "^7.0.9", "postcss-reduce-transforms": "^7.0.3", "postcss-svgo": "^7.1.3", "postcss-unique-selectors": "^7.0.7" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-11qO63A+czwguQFJCaTdICvbaxn0pJzz/XghLlv+OT7WyToDxAMR0Xb3/26/l0y0hQJywwNbj/SLSQlGBHE1OA=="], + + "cssnano-preset-lite": ["cssnano-preset-lite@4.0.6", "", { "dependencies": { "cssnano-utils": "^5.0.3", "postcss-discard-comments": "^7.0.8", "postcss-discard-empty": "^7.0.3", "postcss-normalize-whitespace": "^7.0.3" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-EI/VDoucl8SmVkXUZtWIux31cWoxgNUbF7njnpPxdz5ZbnKOjAd5DueLuCE1RKKLrOPQsEUaNfUgB1taohIIyQ=="], + + "cssnano-utils": ["cssnano-utils@5.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-ynIREMICLxkxm7e9bCR9sh75s4Q5drICi0ua1yxo5jH2XPBqSKkl4dOh4EbFqtUmnTMhRffHgYL0EKKkMjtJTg=="], + "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], @@ -1186,6 +1316,8 @@ "detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="], + "detect-node": ["detect-node@2.1.0", "", {}, "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], @@ -1194,12 +1326,28 @@ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + + "editorconfig": ["editorconfig@1.0.7", "", { "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", "minimatch": "^9.0.1", "semver": "^7.5.3" }, "bin": { "editorconfig": "bin/editorconfig" } }, "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.420", "", {}, "sha512-2yD6XreGusOfNV+dUcvipJEXc3n/n7fgr7996aszTG+YY5E4mqM4tOq/3uhP129cazL9YHbVWSpc79ePotWtPA=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], + "enhanced-resolve": ["enhanced-resolve@5.24.5", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A=="], "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-goat": ["escape-goat@3.0.0", "", {}, "sha512-w3PwNZJwRxlp47QGzhuEBldEqVHHhh8/tIPcl6ecf2Bou99cdAt0knihBV0Ecc7CGxYduXVBDheH1K2oADRlvw=="], + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], "eslint": ["eslint@10.9.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.2", "@eslint/config-array": "^0.23.5", "@eslint/config-helpers": "^0.7.0", "@eslint/core": "^1.2.1", "@eslint/plugin-kit": "^0.7.2", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.14.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^9.1.2", "eslint-visitor-keys": "^5.0.1", "espree": "^11.2.0", "esquery": "^1.7.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "minimatch": "^10.2.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-5KeEOJZBfEVA47boFiBsf+6MmmJpffM7qEBg4pLla2e4nlKgdKlqCW0oSLOGsT8Wl5uCGJptLV1bkaiShj90Gw=="], @@ -1240,42 +1388,70 @@ "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], + "foreground-child": ["foreground-child@3.3.1", "", { "dependencies": { "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" } }, "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw=="], + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "glob": ["glob@11.1.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "happy-dom": ["happy-dom@20.11.6", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "buffer-image-size": "^0.6.4", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.21.0" } }, "sha512-Hldbg8AdAa5a5oDcZpjqnGitp7JB0hqWmfv/8qr+kft4vzSD8BHsbdRfzYvL/0QcbKcURC/yyoygbeDQarPvYg=="], + "htmlnano": ["htmlnano@3.4.1", "", { "dependencies": { "@types/relateurl": "^0.2.33", "commander": "^15.0.0", "cosmiconfig": "^10.0.0", "posthtml": "^0.16.5", "tinyglobby": "^0.2.17" }, "peerDependencies": { "cssnano": "^7.0.0 || ^8.0.0 || ^9.0.0", "postcss": "^8.3.11", "purgecss": "^8.0.0", "relateurl": "^0.2.7", "srcset": "^5.0.1", "svgo": "^4.0.0", "terser": "^5.21.0", "uncss": "^0.17.3" }, "optionalPeers": ["cssnano", "postcss", "purgecss", "relateurl", "srcset", "svgo", "terser", "uncss"], "bin": { "htmlnano": "dist/bin.js" } }, "sha512-1Wltr/RySrpaOhLdn6F9VzwbYfB9udYCFTaKOuK5gj0zV7gRVk61yyjPr6rlnAyfQtgFSeCH95tgrh2oMI2npQ=="], + + "htmlparser2": ["htmlparser2@9.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.1.0", "entities": "^4.5.0" } }, "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ=="], + "i18n-showcase": ["i18n-showcase@workspace:examples/i18n-showcase"], + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + "inter-app-api-showcase": ["inter-app-api-showcase@workspace:examples/inter-app-api-showcase"], "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + "is-json": ["is-json@2.0.1", "", {}, "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA=="], + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jackspeak": ["jackspeak@4.2.3", "", { "dependencies": { "@isaacs/cliui": "^9.0.0" } }, "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg=="], + "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ=="], "joycon": ["joycon@3.1.1", "", {}, "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw=="], + "js-beautify": ["js-beautify@1.15.4", "", { "dependencies": { "config-chain": "^1.1.13", "editorconfig": "^1.0.4", "glob": "^10.4.2", "js-cookie": "^3.0.5", "nopt": "^7.2.1" }, "bin": { "js-beautify": "js/bin/js-beautify.js", "css-beautify": "js/bin/css-beautify.js", "html-beautify": "js/bin/html-beautify.js" } }, "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA=="], + + "js-cookie": ["js-cookie@3.0.8", "", {}, "sha512-yeJd4aNAdYZQjaon2bpD/Gb0B/omw7HQOsynXXcOiWVCacbBcPlgn8S/d1X6blFSaHao7ozqtW7NZW19xpCtIw=="], + + "js-yaml": ["js-yaml@5.4.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.mjs" } }, "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + "juice": ["juice@11.1.1", "", { "dependencies": { "cheerio": "1.0.0", "commander": "^12.1.0", "entities": "^7.0.0", "mensch": "^0.3.4", "slick": "^1.12.2", "web-resource-inliner": "^8.0.0" }, "bin": { "juice": "bin/juice" } }, "sha512-4SBfZqKcc6DrIS+5b/WiGoWaZsdUPBH+e6SbRlNjJpaIRtfoBhYReAtobIEW6mcLeFFDXLBJMuZwkJLkBJjs2w=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], @@ -1312,14 +1488,90 @@ "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + "lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="], + + "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="], + + "lodash.uniq": ["lodash.uniq@4.5.0", "", {}, "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="], + + "lru-cache": ["lru-cache@11.5.2", "", {}, "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g=="], + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="], + "mensch": ["mensch@0.3.4", "", {}, "sha512-IAeFvcOnV9V0Yk+bFhYR07O3yNina9ANIN5MoXBKYJ/RLYPurd2d0yw14MDhpr9/momp0WofT1bPUh3hkzdi/g=="], + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + "mime": ["mime@2.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="], + "minimatch": ["minimatch@10.2.6", "", { "dependencies": { "brace-expansion": "^5.0.8" } }, "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A=="], + "minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="], + + "mjml": ["mjml@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "mjml-cli": "5.4.0", "mjml-core": "5.4.0", "mjml-preset-core": "5.4.0", "mjml-validator": "5.4.0" }, "bin": { "mjml": "bin/mjml" } }, "sha512-nKeUbKsNtSLzqKcmOwGh3ELxLYHY1fdeSNLif+A33uQV4zBdHahNL7LLshvpTpG2yyu5LlZHQWogVs1dS/V30w=="], + + "mjml-accordion": ["mjml-accordion@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-yElB+84k5kZpTz8Ct3eRu63fkEeGc4mBZmbAfZrS5sZCb9DiamAfhozLee5WgO4Y3cwuf8cFsfhYGPuBw60XCw=="], + + "mjml-body": ["mjml-body@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-fPZLONKnRGR2NxkmfKPvnnr/ycVeyahi1ySX6Rk8lEO76ywXNFDWzTbzz/UQ2gzfnD8AhDbuqzd/UZRpW0vdOg=="], + + "mjml-button": ["mjml-button@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-HlecSMeio6xf21nh4vMaHIJF8bN24KatK3gwcR6ByxKfzTkQVR7jtWjJLUiyiuLOJcam9wCOVl7m5J6elrFpjg=="], + + "mjml-carousel": ["mjml-carousel@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-fuhOEETPC/+ZtISh5iOlc6uQqOKWwhwg1W8XTJrzWj5pfa46BzMdR7Nx0Z+8I8/HRqrZA3Ws4hiSTTgbpTIRjg=="], + + "mjml-cli": ["mjml-cli@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "chokidar": "^4.0.3", "glob": "^11.1.0", "lodash": "^4.17.21", "minimatch": "^10.2.5", "mjml-core": "5.4.0", "mjml-parser-xml": "5.4.0", "mjml-preset-core": "5.4.0", "mjml-validator": "5.4.0", "yargs": "^17.7.2" }, "bin": { "mjml-cli": "bin/mjml" } }, "sha512-6HeOz0zadc9iOmMVg85ts1HhoW2CiLUNaTFfnC5YXfowPnjK0bWfMluDzxHcaAk12wlnDF06kROl8pKpZeiy6A=="], + + "mjml-column": ["mjml-column@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-vnseCiUUKhtQXx5ZEoApxA3elu2AZZyyQkOhE2ntG5cPQuZd3EhRv/mkKKCS99Vi51Tcf7AQ3chwSD4AL+bDHg=="], + + "mjml-core": ["mjml-core@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "cheerio": "1.0.0", "cssnano": "^7.1.2", "cssnano-preset-lite": "^4.0.4", "detect-node": "^2.0.4", "htmlnano": "^3.3.1", "js-beautify": "^1.15.4", "juice": "^11.0.0", "lodash": "^4.17.21", "mjml-parser-xml": "5.4.0", "mjml-validator": "5.4.0", "postcss": "^8.5.8" } }, "sha512-dhcbpBmxktzv/tV2Gcz7BJC15fKEP8LzXUlEYMhi0XDsNEkhHxPGXxvhMoc020jJ9Ypjobnh1q7ow+F8Xx72jA=="], + + "mjml-divider": ["mjml-divider@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-8mk7J0tn0rX+FBO43cJkaKO3x0GCjUX4bdPI5txoh1UWyq1N6xgoEqTAR3luwR0f8juTmUXZv97GnQdRUsWtvQ=="], + + "mjml-group": ["mjml-group@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-gCCU0WV8Aytt68uczjId3xhsvUJ8qU1WDCL+b7I3wrI1ja5npRyXdATHM6Q2uXbm+an8Dxz5q77Ts9sodAHZIQ=="], + + "mjml-head": ["mjml-head@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-npWsul6ANzgxl2AZP0kG1yn9G5tOoX8iCgMhRwJkbIJ2rEX91SUvim9P/75s7HuqhccVsjO2L3OVHmnUEpWYng=="], + + "mjml-head-attributes": ["mjml-head-attributes@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-c2/Zi/2wCutEVChxY8RGbPIb2Wb0Ro3A9WVJ0DezyEeN9noTT1fRiMWYQIc2y3H5STfNtIFu6RbtDU+AK2p4rg=="], + + "mjml-head-breakpoint": ["mjml-head-breakpoint@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-qtJZ7uaMxVObrr13um5tbktxih8ycTStYAdcKMdSsgqLG3dTNEfVF9wg7AEHs6e3GB1cPnHgQwF9v7nxe+vocw=="], + + "mjml-head-font": ["mjml-head-font@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-c0shDE+Bt7tob9NNpNOk8pRpJMWztfgNuoyXAIkOc7VhILnsYzH5+4Ly70wlHOzQAspC3cODdkZxut8i3ApRcQ=="], + + "mjml-head-html-attributes": ["mjml-head-html-attributes@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-o9yEfrA1/5r3EbxXXUVBKf91wSh+vxBSNDYQFc0Do8/8gLg7MvczFVXH2Gq9PQdxPEO+AeBrP4sGP5ZxGJnSwg=="], + + "mjml-head-preview": ["mjml-head-preview@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-jXbbRIGPn7IiAq6M/KZpQMX+RjRN9dW4Az4QTyqL4PObqsrn+rbug6vdZXdxXnCERUZiA7a7K3R4Z5BTOi+qFw=="], + + "mjml-head-style": ["mjml-head-style@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-wUPT4G8GjHlcy0Zq67taYT0E65pa6gwSxO43VJfjpU8Qa1QNmFYkOuDkzbb9oZN0QsETmbfG/RPK/mS4uOAfsg=="], + + "mjml-head-title": ["mjml-head-title@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-Tx4a6/CPDapUwq8NjGqfb3xBrzMXCxo3s1IGnd1Fnohl0uAZ5EySeBFf8c0uNSwrEhCDOTC5qrERm890Yselfw=="], + + "mjml-hero": ["mjml-hero@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-j9bKjilTHqJMp3GIDtKUdP3JRnktAc0o7gHhv1NgThgv/z1DDQJ2zgtW/pme6wA5VWng5DTriPk9aoCLPuOlyQ=="], + + "mjml-image": ["mjml-image@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-6Y8aMIZbzIZ7SSpo0/o4n5E+JQx5d7OCwUoIIiXWPWGevfOE8JvjFt5MIa24CmSDbKWbhVuJEi1h4TUJhvAVbQ=="], + + "mjml-navbar": ["mjml-navbar@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-knEsmNN6uBLtEU3a9uwnRqUqAE38EeJcXYaGlI0ly75Zj+vyhKQGPjJzRN7XJqA2dFSgIm3dV9KU9vNW+jI3Zg=="], + + "mjml-parser-xml": ["mjml-parser-xml@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "detect-node": "2.1.0", "htmlparser2": "^9.1.0", "lodash": "^4.18.1" } }, "sha512-A+KzRx+AeWIWxYN9z2KungvmVKMdW+NGLc5h+B9DeY93lSvcSpWH26nGDW9pcPi9B3fdwgYvqgOkYtX6EQATCA=="], + + "mjml-preset-core": ["mjml-preset-core@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "mjml-accordion": "5.4.0", "mjml-body": "5.4.0", "mjml-button": "5.4.0", "mjml-carousel": "5.4.0", "mjml-column": "5.4.0", "mjml-divider": "5.4.0", "mjml-group": "5.4.0", "mjml-head": "5.4.0", "mjml-head-attributes": "5.4.0", "mjml-head-breakpoint": "5.4.0", "mjml-head-font": "5.4.0", "mjml-head-html-attributes": "5.4.0", "mjml-head-preview": "5.4.0", "mjml-head-style": "5.4.0", "mjml-head-title": "5.4.0", "mjml-hero": "5.4.0", "mjml-image": "5.4.0", "mjml-navbar": "5.4.0", "mjml-raw": "5.4.0", "mjml-section": "5.4.0", "mjml-social": "5.4.0", "mjml-spacer": "5.4.0", "mjml-table": "5.4.0", "mjml-text": "5.4.0", "mjml-wrapper": "5.4.0" } }, "sha512-rq22rNFCp4brsSAgpKrY4tXR/ZWeJeU/GyypihrzmDVu3dSFmOYbrJgW1eCo4g5rWMpEbnY8pn0t1SB8EB+ymQ=="], + + "mjml-raw": ["mjml-raw@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-ewGXtauxkE35Xd9cJ3REjfD6vNSU82HfIm2pPi2RZF9eXrmfuSrrbSpXuPy13BP2lPpDJa6umUAuDsg5XhXr4A=="], + + "mjml-section": ["mjml-section@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-BetZqHS31bK5FS7rr5HlFo24m5OGDZi3yjkSn0aanF1SgRkmX1+LwnMQoDdT986SMys0oUYFeNSlz9lp8QgtrA=="], + + "mjml-social": ["mjml-social@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-7LUoIAOUzXzkLWfdErGrrqc8isxmDxaYQPcUNubQ1d9IXR48/S7Pi5s6PEiDxlaIgWHBcAJEoMszpLHA8+oCSQ=="], + + "mjml-spacer": ["mjml-spacer@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-v7P6InD4u7nyXTmNjKMOY0oQ2EaZzFzCcftexb6JdcqvFaZvO9vUSoOdfZUp2FzzFcXQaD7K5RRW3stJSdEJOQ=="], + + "mjml-table": ["mjml-table@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-e1Kq3AWzzVFv6rPgAy4eK1txA4rfMPivaavW9BrvCDJU3Wiz+fOo9FqtMDyUQXrsJJKSOi6MMA0h2lAESTsR5w=="], + + "mjml-text": ["mjml-text@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0" } }, "sha512-QTLdNM6Fs6T4LlquEzJmM+i3lJ+toNmRLRSZyUXP1tjJQhlNmc9aT1UHRy1Gn5fb7XTAY4lo2LznVv/0Tb3qhw=="], + + "mjml-validator": ["mjml-validator@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4" } }, "sha512-IVsV3RxiEFfAed9U0C5DYmQA06e1JWDQQ8MqBinU7lQCYUkZIexTStohDACzHpN6RTIawi+U0GkT6PUHprv+3Q=="], + + "mjml-wrapper": ["mjml-wrapper@5.4.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "lodash": "^4.17.21", "mjml-core": "5.4.0", "mjml-section": "5.4.0" } }, "sha512-niCuz5T7IfLKIKVv6G4BNu6sW07yl/kJ0o8oovd+CfG4lO9K+tXUwJQ+Iv3Y3tEQp0TfJE0aN1ysGrhAz6aB6Q=="], + "mlly": ["mlly@1.8.2", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA=="], "modern-tar": ["modern-tar@0.7.7", "", {}, "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ=="], @@ -1330,10 +1582,16 @@ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], + "nanoid": ["nanoid@3.3.18", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w=="], + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + "node-releases": ["node-releases@2.0.54", "", {}, "sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ=="], + + "nopt": ["nopt@7.2.1", "", { "dependencies": { "abbrev": "^2.0.0" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w=="], + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], @@ -1344,12 +1602,22 @@ "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], + + "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + "path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], @@ -1362,12 +1630,82 @@ "pkijs": ["pkijs@3.4.0", "", { "dependencies": { "@noble/hashes": "1.4.0", "asn1js": "^3.0.6", "bytestreamjs": "^2.0.1", "pvtsutils": "^1.3.6", "pvutils": "^1.1.3", "tslib": "^2.8.1" } }, "sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw=="], + "postcss": ["postcss@8.5.26", "", { "dependencies": { "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ=="], + + "postcss-calc": ["postcss-calc@10.1.1", "", { "dependencies": { "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.4.38" } }, "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw=="], + + "postcss-colormin": ["postcss-colormin@7.0.10", "", { "dependencies": { "@colordx/core": "^5.4.3", "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-yFr6JezOolHLta/buLE71VKPh2mXursp4saVe98/ol8ZnEWhL+racShqPKlvd/DKWLre/39B6HhcMXf7RZ3hxg=="], + + "postcss-convert-values": ["postcss-convert-values@7.0.12", "", { "dependencies": { "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-xurKu5qqk4viR3Cp3p4xBR4KfnZm4w4ys6+UBwBmeuBSNkH7+DtLnYOYnOffgtE4yx8sH9S1VZ6RAAvROXzP2Q=="], + + "postcss-discard-comments": ["postcss-discard-comments@7.0.8", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-CvvS5S9WrXblFXCEJ9nVo+4z+eA7zSC7Z88V1HEJuwlQhlFnYTIjg1xJY+BCUiG2bvICap2tXii4mP22BD108Q=="], + + "postcss-discard-duplicates": ["postcss-discard-duplicates@7.0.4", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-VBNn1+EuMZkeGVVtz0gRfbNGtx9IFgAsAV+E2pHtXPrp4qfGBkhTIiAuE/wrb+Y6Pakg9NewAlfTpYIFAWODtw=="], + + "postcss-discard-empty": ["postcss-discard-empty@7.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-M2pyjQCU+/7cMHVtL6bKTHjv0lZnPLMpicgr67Dlth7AbuV9gjVTtUqaRwn6Pp6BwSDspUzhz8SaUrRykJU5Dw=="], + + "postcss-discard-overridden": ["postcss-discard-overridden@7.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-aNovXo9UsZuRNLzHJtp13lHIvinDPfiXBPePpXkSjCbgp++iU2FqE+YxvjIsg6EdyPZsASFbfu+JcBFVsErXIQ=="], + "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], + "postcss-merge-longhand": ["postcss-merge-longhand@7.0.7", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "stylehacks": "^7.0.11" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-b3mfYUxR388u5Pt0HPcVIUtUDn/k15UfTY9M+ORW+meCR6JLNxoZffiYvXyOYQoRYQNZyX/UFkMCM/mNHxe1qA=="], + + "postcss-merge-rules": ["postcss-merge-rules@7.0.11", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-api": "^3.0.0", "cssnano-utils": "^5.0.3", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-SJUPM18g2BmPhf8BVlbwqWz4aK3pLu6u6xjfwEzra7xL6IBR10sUaiB++EzqcVfadPHrKBSMlNdP+XieykhI+Q=="], + + "postcss-minify-font-values": ["postcss-minify-font-values@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-yilG/VOaNI74IylQvAQQxm3/wZVBkXyYUqNUAdxqwtbWUXPsbK1q8Ms0mL83v+f8YicgcyfYCRZtWACUdYajpA=="], + + "postcss-minify-gradients": ["postcss-minify-gradients@7.0.5", "", { "dependencies": { "@colordx/core": "^5.4.3", "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-YraROyQRg3BI1+Hg8E05B/JPdnTm8EDSVu4P2BxdM+CRiOyfmou809+chGIqo6fQqwjPGQ947nbGncSjmTU1WQ=="], + + "postcss-minify-params": ["postcss-minify-params@7.0.9", "", { "dependencies": { "browserslist": "^4.28.2", "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-R8itbB8BhlpoYyBm1ou0dD+vJnQ3F6adQipR4UnkCHUwlo+S9WXJaDRg1RHjC8YVAtIdrQzSWvJl40HnGDTKjA=="], + + "postcss-minify-selectors": ["postcss-minify-selectors@7.1.2", "", { "dependencies": { "browserslist": "^4.28.1", "caniuse-api": "^3.0.0", "cssesc": "^3.0.0", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-aQtrEWKwqafNlExcKHQvPGsXR2+vlUqqJtf5XsCQcgsSb5PL4wlujWBYDJuWsP4UnQX1YHDHU8qRlD+1PzTQ+Q=="], + + "postcss-normalize-charset": ["postcss-normalize-charset@7.0.3", "", { "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-NoBfZu8PR4c2NlmjvrqQTzCzLY79hwcSRgNQ3ZiNK0ABzf9kYKloE/jNj+/8GQY1wsm8pRRgANk6ydLH8cwo0Q=="], + + "postcss-normalize-display-values": ["postcss-normalize-display-values@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-ldsCX0QIt05pKIOobZtVQ48wXJecr+czw4+e1/YjVhLMqslShgpVxgPtI2CefURR8oyVoYaU/l829MMwExDMLw=="], + + "postcss-normalize-positions": ["postcss-normalize-positions@7.0.4", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-VEvlpeGd3Ju1Hqa/oN4jaP3+ms4laYwkEL9N9u+B6k54PZjXbW1n6wI+aVprf1BQXlCYpS5+1pl/7/vHiKgARg=="], + + "postcss-normalize-repeat-style": ["postcss-normalize-repeat-style@7.0.4", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-6mPKlY/8cSaDHxX502wERADarJsccwlky6yIrOapHH2ZgfoKAV94SbiTKfKEs4EEpdazuc3J72WsqeYk7hp9+Q=="], + + "postcss-normalize-string": ["postcss-normalize-string@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-HnEQPUchi1eznmDKEYrKUTqrprEq97SrpUYClgUkv7V2zRODD9DFoUsYU+m9ZOetmD5ku7fEMZB/lwy8IT6xVQ=="], + + "postcss-normalize-timing-functions": ["postcss-normalize-timing-functions@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-zmEzHdvpZBZu0OKlbJSfgASQvaayyAoVuWtvyr34IJ/LyS+DaOKvvR3EvFJ9RWWtNIx+CMvO125OVophaxNYew=="], + + "postcss-normalize-unicode": ["postcss-normalize-unicode@7.0.9", "", { "dependencies": { "browserslist": "^4.28.2", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-DRAdWfeh/TjmhLJsw91vdiWCnUod9iwvM7xyS02/nF/sLsCR3A8l3pztrSUrWG8DSBqfX7yEk9FM0USaVJ2mSg=="], + + "postcss-normalize-url": ["postcss-normalize-url@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-CL93wmloq5qsffmFv+bw24MIRbmhHrp53qoh1LDAb/5TtjWEXI/np4xcP/Gw9oWCb2XyWnqHYLDUwiKRoJBA1Q=="], + + "postcss-normalize-whitespace": ["postcss-normalize-whitespace@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-FdHjjn+Ht5Z2ZRjNOmeCbNq6lq09sUYKpmlF/Aq0XjVNSLTL6fmHlA/3swN2wP2caY9GV/tjSDcIIyS7aN7W0A=="], + + "postcss-ordered-values": ["postcss-ordered-values@7.0.4", "", { "dependencies": { "cssnano-utils": "^5.0.3", "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-nubSi49hDHQk4E8KIj+IbLY8Bg+8OcSUEhgyolgM+atnOvXjV7EjaR6bac4YGZoFyPa9mWoAF3EaYbWdFkKqVg=="], + + "postcss-reduce-initial": ["postcss-reduce-initial@7.0.9", "", { "dependencies": { "browserslist": "^4.28.2", "caniuse-api": "^3.0.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-ztTNPdIxXTxtBcG03E9u8v44M4ElXbMIRT7pf2onlquGula0Y83nKKxqM22FA/hMgkfCjN7ohevkVlaNwI8iOQ=="], + + "postcss-reduce-transforms": ["postcss-reduce-transforms@7.0.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-FXsnN9ZwcZTT8Yf8cAHA8qIGUXcX6WfLd9JoYhrdDfmvsVhhfqkkv7m4AC3rwFOfz+GzkUa87OCKF9dUcicd+g=="], + + "postcss-selector-parser": ["postcss-selector-parser@7.1.5", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-KvvtD7SrlBP7dlgkBghEE3r84CABm5SmV2aNcG4oCA+qDnJ/tvKonFVvwWAyyWUEwxuNawdfEAZKP9zM3oZ2Uw=="], + + "postcss-svgo": ["postcss-svgo@7.1.3", "", { "dependencies": { "postcss-value-parser": "^4.2.0", "svgo": "^4.0.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-2QfoFOYMcj8lwcVEf9WeTlkVIAm7u2QvOEhMzkQU3KUhhGX/l8hVV9EtjMv4iq3E9iI3OeeMN0YoMLbGusuigw=="], + + "postcss-unique-selectors": ["postcss-unique-selectors@7.0.7", "", { "dependencies": { "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-d+sCkaRnSefghOUdH8CMJZV9yUQhj2ojpe8Nw/lA+LV1UOfeleGkLTl6XdCFFSai9UJ+DJPb69FFuqthXYsY8w=="], + + "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="], + + "posthtml": ["posthtml@0.16.7", "", { "dependencies": { "posthtml-parser": "^0.11.0", "posthtml-render": "^3.0.0" } }, "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw=="], + + "posthtml-parser": ["posthtml-parser@0.11.0", "", { "dependencies": { "htmlparser2": "^7.1.1" } }, "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw=="], + + "posthtml-render": ["posthtml-render@3.0.0", "", { "dependencies": { "is-json": "^2.0.1" } }, "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prettier": ["prettier@3.9.6", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g=="], + "promise": ["promise@8.3.0", "", { "dependencies": { "asap": "~2.0.6" } }, "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg=="], + + "proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], @@ -1382,10 +1720,14 @@ "reflect-metadata": ["reflect-metadata@0.2.2", "", {}, "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q=="], + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "rollup": ["rollup@4.62.5", "", { "dependencies": { "@types/estree": "1.0.9" }, "optionalDependencies": { "@napi-rs/lzma-linux-x64-gnu": "1.5.1", "@rollup/rollup-android-arm-eabi": "4.62.5", "@rollup/rollup-android-arm64": "4.62.5", "@rollup/rollup-darwin-arm64": "4.62.5", "@rollup/rollup-darwin-x64": "4.62.5", "@rollup/rollup-freebsd-arm64": "4.62.5", "@rollup/rollup-freebsd-x64": "4.62.5", "@rollup/rollup-linux-arm-gnueabihf": "4.62.5", "@rollup/rollup-linux-arm-musleabihf": "4.62.5", "@rollup/rollup-linux-arm64-gnu": "4.62.5", "@rollup/rollup-linux-arm64-musl": "4.62.5", "@rollup/rollup-linux-loong64-gnu": "4.62.5", "@rollup/rollup-linux-loong64-musl": "4.62.5", "@rollup/rollup-linux-ppc64-gnu": "4.62.5", "@rollup/rollup-linux-ppc64-musl": "4.62.5", "@rollup/rollup-linux-riscv64-gnu": "4.62.5", "@rollup/rollup-linux-riscv64-musl": "4.62.5", "@rollup/rollup-linux-s390x-gnu": "4.62.5", "@rollup/rollup-linux-x64-gnu": "4.62.5", "@rollup/rollup-linux-x64-musl": "4.62.5", "@rollup/rollup-openbsd-x64": "4.62.5", "@rollup/rollup-openharmony-arm64": "4.62.5", "@rollup/rollup-win32-arm64-msvc": "4.62.5", "@rollup/rollup-win32-ia32-msvc": "4.62.5", "@rollup/rollup-win32-x64-gnu": "4.62.5", "@rollup/rollup-win32-x64-msvc": "4.62.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-/tqMfgP7GPA3PHhCmuiS4vIjrSVhHLgY++i+dhbG462euyAj7FpM4D9uq1X3BgjlqRdpcOrYhcQtfiQLNc8tqw=="], + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "sax": ["sax@1.6.1", "", {}, "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q=="], "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], @@ -1398,10 +1740,24 @@ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "slick": ["slick@1.12.2", "", {}, "sha512-4qdtOGcBjral6YIBCWJ0ljFSKNLz9KkhbWtuGvUyRowl1kxfuE1x/Z/aJcaiilpb3do9bl5K7/1h9XC5wWpY/A=="], + "source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "stylehacks": ["stylehacks@7.0.11", "", { "dependencies": { "browserslist": "^4.28.2", "postcss-selector-parser": "^7.1.1" }, "peerDependencies": { "postcss": "^8.5.13" } }, "sha512-iODNfhXVLqc5LADs+Y6Oh5wJuK5ZcHbVng8aiK3y9pjMQdc5hLrBW0eFU6FtnpNrE6PoEg/MmFTU4waotj5WNg=="], + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], "svgo": ["svgo@4.0.2", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-ekx94z1rRc5LDi6oSUaeRnYhd0UOJxdtQCL2rF8xpWxD3TPAsISWOrxezqGovqS38GRZOdpDfvQe3ts6F7nsng=="], @@ -1440,10 +1796,18 @@ "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], + "undici": ["undici@6.28.0", "", {}, "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA=="], + "undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="], + "update-browserslist-db": ["update-browserslist-db@1.3.2", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw=="], + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "valid-data-url": ["valid-data-url@3.0.1", "", {}, "sha512-jOWVmzVceKlVVdwjNSenT4PbGghU0SBIizAev8ofZVgivk/TVHXSbNL8LP6M3spZvkR9/QolkyJavGSX5Cs0UA=="], + "vscode-html-languageservice": ["vscode-html-languageservice@5.6.2", "", { "dependencies": { "@vscode/l10n": "^0.0.18", "vscode-languageserver-textdocument": "^1.0.12", "vscode-languageserver-types": "^3.17.5", "vscode-uri": "^3.1.0" } }, "sha512-ulCrSnFnfQ16YzvwnYUgEbUEl/ZG7u2eV27YhvLObSHKkb8fw1Z9cgsnUwjTEeDIdJDoTDTDpxuhQwoenoLNMg=="], "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], @@ -1454,16 +1818,30 @@ "web": ["web@workspace:examples/inter-app-api-showcase/apps/web"], + "web-resource-inliner": ["web-resource-inliner@8.0.0", "", { "dependencies": { "ansi-colors": "^4.1.1", "escape-goat": "^3.0.0", "htmlparser2": "^9.1.0", "mime": "^2.4.6", "valid-data-url": "^3.0.0" } }, "sha512-Ezr98sqXW/+OCGoUEXuOKVR+oVFlSdn1tIySEEJdiSAw4IjrW8hQkwARSSBJTSB5Us5dnytDgL0ZDliAYBhaNA=="], + + "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], + "whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrnexus-crm-example": ["wrnexus-crm-example@workspace:examples/crm-app"], "ws": ["ws@8.21.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], "@antfu/install-pkg/tinyexec": ["tinyexec@1.3.0", "", {}, "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ=="], @@ -1484,18 +1862,70 @@ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], + "cheerio/whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "editorconfig/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="], + + "editorconfig/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "htmlnano/commander": ["commander@15.0.0", "", {}, "sha512-z67u4ZhzCL/Tydu1lJARtEZYWbWaN7oYLHbsuzocr6y4N6WZAagG3RQ4FW61V1/0+jImpj293XfrcYnd1qxtPg=="], + + "htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + + "js-beautify/glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], + + "juice/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="], + "lightningcss/detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "micromatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + + "posthtml-parser/htmlparser2": ["htmlparser2@7.2.0", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.2", "domutils": "^2.8.0", "entities": "^3.0.1" } }, "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog=="], + + "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="], + "svgo/commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "tsyringe/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], + + "js-beautify/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], + + "js-beautify/glob/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "js-beautify/glob/path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="], + + "posthtml-parser/htmlparser2/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="], + + "posthtml-parser/htmlparser2/domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="], + + "posthtml-parser/htmlparser2/entities": ["entities@3.0.1", "", {}, "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q=="], + + "js-beautify/glob/jackspeak/@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], + + "js-beautify/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], + + "posthtml-parser/htmlparser2/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="], + + "js-beautify/glob/jackspeak/@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], + + "js-beautify/glob/jackspeak/@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="], + + "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], + + "posthtml-parser/htmlparser2/domutils/dom-serializer/entities": ["entities@2.2.0", "", {}, "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="], + + "js-beautify/glob/jackspeak/@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "js-beautify/glob/jackspeak/@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="], + + "js-beautify/glob/jackspeak/@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], } } diff --git a/packages/block-editor/README.md b/packages/block-editor/README.md new file mode 100644 index 00000000..016ff865 --- /dev/null +++ b/packages/block-editor/README.md @@ -0,0 +1,4 @@ +# @wrnexus/block-editor + +Hydrated editor state and DOM runtime for portable block documents. All changes +flow through validated `@wrnexus/blocks` commands; DnD produces move intents. diff --git a/packages/block-editor/package.json b/packages/block-editor/package.json new file mode 100644 index 00000000..bae31410 --- /dev/null +++ b/packages/block-editor/package.json @@ -0,0 +1,28 @@ +{ + "name": "@wrnexus/block-editor", + "version": "0.1.0", + "type": "module", + "description": "Hydrated accessible editor runtime for WRNexus block documents.", + "exports": { + ".": "./src/index.ts", + "./runtime": "./src/runtime.ts" + }, + "files": [ + "src", + "README.md" + ], + "scripts": { + "test": "bun test", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@wrnexus/blocks": "workspace:*", + "@wrnexus/dnd": "workspace:*", + "@wrnexus/reactive": "workspace:*" + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "happy-dom": "^20.11.2", + "typescript": "^6.0.3" + } +} diff --git a/packages/block-editor/src/index.ts b/packages/block-editor/src/index.ts new file mode 100644 index 00000000..8789eccf --- /dev/null +++ b/packages/block-editor/src/index.ts @@ -0,0 +1,2 @@ +export * from "./state.ts"; +export * from "./runtime.ts"; diff --git a/packages/block-editor/src/runtime.ts b/packages/block-editor/src/runtime.ts new file mode 100644 index 00000000..5ea0a8ae --- /dev/null +++ b/packages/block-editor/src/runtime.ts @@ -0,0 +1,76 @@ +import { createDndManager, type DndAnnouncement, type DndManager } from "@wrnexus/dnd"; + +export interface MountedEditor { + destroy(): void; + refresh(): void; +} +export interface MountEditorOptions { + direction?: "ltr" | "rtl"; + announce?(announcement: DndAnnouncement): string; + onMove(intent: { + nodeIds: string[]; + to: { parentId: string; slot: string; index: number }; + }): boolean | void | Promise; +} + +export function mountBlockEditor(root: HTMLElement, options: MountEditorOptions): MountedEditor { + let manager: DndManager | undefined; + let cleanups: Array<() => void> = []; + const live = document.createElement("div"); + live.setAttribute("role", "status"); + live.setAttribute("aria-live", "polite"); + live.setAttribute("aria-atomic", "true"); + live.style.cssText = + "position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0"; + root.append(live); + const refresh = () => { + cleanups.forEach((cleanup) => cleanup()); + cleanups = []; + manager?.destroy(); + manager = createDndManager({ + direction: options.direction ?? (getComputedStyle(root).direction === "rtl" ? "rtl" : "ltr"), + reducedMotion: matchMedia("(prefers-reduced-motion: reduce)").matches, + announce(announcement) { + live.textContent = options.announce?.(announcement) ?? announcement.type; + }, + async onMoveIntent(intent) { + const [parentId, slot] = String(intent.to.group).split(":", 2); + if (!parentId || !slot) return false; + return options.onMove({ + nodeIds: intent.ids.map(String), + to: { parentId, slot, index: intent.to.index }, + }); + }, + }); + for (const element of root.querySelectorAll( + "[data-block-id][data-block-parent][data-block-slot]", + )) { + const id = element.dataset.blockId!; + const parent = element.dataset.blockParent!; + const slot = element.dataset.blockSlot!; + const siblings = [ + ...element.parentElement!.querySelectorAll(":scope > [data-block-id]"), + ]; + const handle = element.querySelector("[data-block-handle]") ?? undefined; + cleanups.push( + manager.registerSortable({ + id, + group: `${parent}:${slot}`, + index: siblings.indexOf(element), + element, + handle, + type: element.dataset.blockType, + }), + ); + } + }; + refresh(); + return { + refresh, + destroy() { + cleanups.forEach((cleanup) => cleanup()); + manager?.destroy(); + live.remove(); + }, + }; +} diff --git a/packages/block-editor/src/state.ts b/packages/block-editor/src/state.ts new file mode 100644 index 00000000..3472a5fa --- /dev/null +++ b/packages/block-editor/src/state.ts @@ -0,0 +1,123 @@ +import { signal, type Signal } from "@wrnexus/reactive"; +import { + applyCommand, + type BlockCommand, + type BlockDocument, + type CommandResult, + type DocumentKindDefinition, + type ReadonlyBlockRegistry, + type Result, + type SelectionState, +} from "@wrnexus/blocks"; + +export interface EditorSnapshot { + document: BlockDocument; + revision: number; + selection: SelectionState; + dirty: boolean; + status: "idle" | "saving" | "saved" | "conflict" | "error"; +} + +interface HistoryEntry { + forward: BlockCommand; + inverse: BlockCommand; +} + +export interface BlockEditorState extends Signal { + execute(command: BlockCommand): Result; + undo(): boolean; + redo(): boolean; + select(ids: string[], primary?: string | null): void; + markSaving(): void; + markSaved(revision: number): void; + markConflict(): void; + replaceFromServer(document: BlockDocument, revision: number): void; + canUndo(): boolean; + canRedo(): boolean; +} + +export function createBlockEditorState(options: { + document: BlockDocument; + revision: number; + kind: DocumentKindDefinition; + registry: ReadonlyBlockRegistry; + historyLimit?: number; +}): BlockEditorState { + const current = signal({ + document: options.document, + revision: options.revision, + selection: { primary: null, ordered: [] }, + dirty: false, + status: "idle", + }); + const past: HistoryEntry[] = []; + const future: HistoryEntry[] = []; + const limit = Math.max(1, options.historyLimit ?? 100); + const apply = (command: BlockCommand, remember: boolean): Result => { + const result = applyCommand(current.get().document, command, options.kind, options.registry); + if (!result.ok) return result; + if (remember) { + past.push({ forward: command, inverse: result.value.inverse }); + if (past.length > limit) past.shift(); + future.length = 0; + } + current.set({ + ...current.get(), + document: result.value.document, + selection: result.value.selection, + dirty: true, + status: "idle", + }); + return result; + }; + return Object.assign(current, { + execute: (command: BlockCommand) => apply(command, true), + undo() { + const entry = past.pop(); + if (!entry) return false; + const result = apply(entry.inverse, false); + if (!result.ok) { + past.push(entry); + return false; + } + future.push(entry); + return true; + }, + redo() { + const entry = future.pop(); + if (!entry) return false; + const result = apply(entry.forward, false); + if (!result.ok) { + future.push(entry); + return false; + } + past.push(entry); + return true; + }, + select(ids: string[], primary: string | null = ids[0] ?? null) { + current.set({ ...current.get(), selection: { primary, ordered: [...new Set(ids)] } }); + }, + markSaving() { + current.set({ ...current.get(), status: "saving" }); + }, + markSaved(revision: number) { + current.set({ ...current.get(), revision, dirty: false, status: "saved" }); + }, + markConflict() { + current.set({ ...current.get(), status: "conflict" }); + }, + replaceFromServer(document: BlockDocument, revision: number) { + past.length = 0; + future.length = 0; + current.set({ + document, + revision, + selection: { primary: null, ordered: [] }, + dirty: false, + status: "idle", + }); + }, + canUndo: () => past.length > 0, + canRedo: () => future.length > 0, + }); +} diff --git a/packages/block-editor/test/state.test.ts b/packages/block-editor/test/state.test.ts new file mode 100644 index 00000000..e39933c7 --- /dev/null +++ b/packages/block-editor/test/state.test.ts @@ -0,0 +1,102 @@ +import { expect, test } from "bun:test"; +import { + createRegistry, + defineBlock, + defineDocumentKind, + json, + object, + record, + string, + type BlockDocument, +} from "@wrnexus/blocks"; +import { createBlockEditorState } from "../src/index.ts"; + +const styles = record(json) as never; +const definitions = [ + defineBlock({ + manifest: { + type: "layout.root", + version: 1, + name: { key: "root", fallback: "Root" }, + icon: "root", + category: "layout", + props: object({}) as never, + styles, + slots: { content: { accepts: { capabilities: ["content"] } } }, + }, + create: ({ id }) => ({ + id: id(), + type: "layout.root", + version: 1, + props: {}, + styles: {}, + slots: { content: [] }, + }), + }), + defineBlock({ + manifest: { + type: "content.text", + version: 1, + name: { key: "text", fallback: "Text" }, + icon: "text", + category: "content", + capabilities: ["content"], + props: object({ text: string() }) as never, + styles, + slots: {}, + }, + create: ({ id }) => ({ + id: id(), + type: "content.text", + version: 1, + props: { text: "" }, + styles: {}, + slots: {}, + }), + }), +]; +const kind = defineDocumentKind({ + kind: "test", + version: 1, + metadata: object({ title: string() }) as never, + rootTypes: ["layout.root"], + blocks: definitions, + variables: {}, + breakpoints: {}, + capabilities: [], + targets: [], + limits: { + maxBytes: 10000, + maxNodes: 20, + maxDepth: 5, + maxChildrenPerSlot: 10, + maxStringLength: 100, + maxOutputBytes: 1000, + }, + urlSchemes: ["https"], +}); +const registry = createRegistry(definitions).freeze(); +const document: BlockDocument = { + schemaVersion: 1, + kind: "test", + meta: { title: "x" }, + theme: { tokens: {} }, + root: definitions[0]!.create({ id: () => "root" }), +}; + +test("editor state executes undo and redo", () => { + const state = createBlockEditorState({ document, revision: 1, kind, registry }); + const node = definitions[1]!.create({ id: () => "text" }); + expect( + state.execute({ + type: "insert", + location: { parentId: "root", slot: "content", index: 0 }, + nodes: [node], + }).ok, + ).toBe(true); + expect(state.get().dirty).toBe(true); + expect(state.undo()).toBe(true); + expect(state.get().document.root.slots.content).toEqual([]); + expect(state.redo()).toBe(true); + expect(state.get().document.root.slots.content[0]?.id).toBe("text"); +}); diff --git a/packages/block-editor/tsconfig.json b/packages/block-editor/tsconfig.json new file mode 100644 index 00000000..e8b02ca4 --- /dev/null +++ b/packages/block-editor/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/packages/blocks/README.md b/packages/blocks/README.md new file mode 100644 index 00000000..445f02fc --- /dev/null +++ b/packages/blocks/README.md @@ -0,0 +1,5 @@ +# @wrnexus/blocks + +Pure, dependency-free block document engine. Documents contain portable JSON; +trusted packages register schemas, editor metadata, migrations, and target +renderers explicitly. Named slots are the only containment model. diff --git a/packages/blocks/package.json b/packages/blocks/package.json new file mode 100644 index 00000000..c18457f2 --- /dev/null +++ b/packages/blocks/package.json @@ -0,0 +1,21 @@ +{ + "name": "@wrnexus/blocks", + "version": "0.1.0", + "type": "module", + "description": "Pure dynamic block document engine for WRNexus builders.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src", + "README.md" + ], + "scripts": { + "test": "bun test", + "typecheck": "tsc --noEmit" + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "typescript": "^6.0.3" + } +} diff --git a/packages/blocks/src/clipboard.ts b/packages/blocks/src/clipboard.ts new file mode 100644 index 00000000..2509ed30 --- /dev/null +++ b/packages/blocks/src/clipboard.ts @@ -0,0 +1,65 @@ +import { duplicateNodes } from "./commands.ts"; +import type { BlockNode, Result } from "./types.ts"; + +export const BLOCK_CLIPBOARD_MIME = "application/x-wrnexus-blocks+json"; + +export interface BlockClipboardEnvelope { + format: typeof BLOCK_CLIPBOARD_MIME; + version: 1; + sourceKind: string; + roots: BlockNode[]; + requiredTypes: Array<{ type: string; version: number }>; +} + +export function encodeClipboard(sourceKind: string, roots: readonly BlockNode[]): string { + const required = new Map(); + const visit = (node: BlockNode) => { + required.set(node.type, Math.max(required.get(node.type) ?? 0, node.version)); + for (const children of Object.values(node.slots)) children.forEach(visit); + }; + roots.forEach(visit); + return JSON.stringify({ + format: BLOCK_CLIPBOARD_MIME, + version: 1, + sourceKind, + roots, + requiredTypes: [...required] + .sort(([a], [b]) => a.localeCompare(b)) + .map(([type, version]) => ({ type, version })), + }); +} + +export function decodeClipboard( + value: string, + id: () => string, + maxBytes = 250_000, +): Result { + if (new TextEncoder().encode(value).byteLength > maxBytes) + return { + ok: false, + errors: [ + { code: "clipboard.size", message: "Clipboard content is too large.", severity: "error" }, + ], + }; + try { + const parsed = JSON.parse(value) as BlockClipboardEnvelope; + if ( + parsed.format !== BLOCK_CLIPBOARD_MIME || + parsed.version !== 1 || + !Array.isArray(parsed.roots) + ) + throw new Error(); + return { ok: true, value: { ...parsed, roots: duplicateNodes(parsed.roots, id) } }; + } catch { + return { + ok: false, + errors: [ + { + code: "clipboard.format", + message: "Clipboard does not contain WRNexus blocks.", + severity: "error", + }, + ], + }; + } +} diff --git a/packages/blocks/src/commands.ts b/packages/blocks/src/commands.ts new file mode 100644 index 00000000..97fe38fa --- /dev/null +++ b/packages/blocks/src/commands.ts @@ -0,0 +1,233 @@ +import { findBlock, validateDocument } from "./document.ts"; +import type { DocumentKindDefinition } from "./kinds.ts"; +import type { ReadonlyBlockRegistry } from "./registry.ts"; +import type { + BlockDocument, + BlockLocation, + BlockNode, + BlockStyles, + Diagnostic, + JsonObject, + MoveIntent, + Result, + SelectionState, +} from "./types.ts"; + +export type BlockCommand = + | { type: "insert"; location: BlockLocation; nodes: BlockNode[] } + | { type: "remove"; nodeIds: string[] } + | { type: "move"; intent: MoveIntent } + | { type: "updateProps"; nodeId: string; patch: JsonObject } + | { type: "updateStyles"; nodeId: string; patch: BlockStyles } + | { type: "replace"; nodeId: string; node: BlockNode } + | { type: "batch"; commands: BlockCommand[] }; + +export interface CommandResult { + document: BlockDocument; + inverse: BlockCommand; + affectedIds: string[]; + selection: SelectionState; + diagnostics: Diagnostic[]; +} + +const clone = (value: T): T => structuredClone(value); + +function updateNode( + root: BlockNode, + id: string, + update: (node: BlockNode) => BlockNode, +): BlockNode { + if (root.id === id) return update(root); + const slots: Record = {}; + for (const [name, children] of Object.entries(root.slots)) + slots[name] = children.map((child) => updateNode(child, id, update)); + return { ...root, slots }; +} + +function locationOf(root: BlockNode, id: string): BlockLocation | undefined { + for (const [slot, children] of Object.entries(root.slots)) { + const index = children.findIndex((child) => child.id === id); + if (index >= 0) return { parentId: root.id, slot, index }; + for (const child of children) { + const nested = locationOf(child, id); + if (nested) return nested; + } + } +} + +function insert(root: BlockNode, location: BlockLocation, nodes: BlockNode[]): BlockNode { + return updateNode(root, location.parentId, (parent) => { + const children = [...(parent.slots[location.slot] ?? [])]; + children.splice(location.index, 0, ...nodes); + return { ...parent, slots: { ...parent.slots, [location.slot]: children } }; + }); +} + +function remove(root: BlockNode, ids: Set): BlockNode { + const slots: Record = {}; + for (const [name, children] of Object.entries(root.slots)) + slots[name] = children.filter((child) => !ids.has(child.id)).map((child) => remove(child, ids)); + return { ...root, slots }; +} + +export function applyCommand( + document: BlockDocument, + command: BlockCommand, + kind: DocumentKindDefinition, + registry: ReadonlyBlockRegistry, +): Result { + if (command.type === "batch") { + let current = document; + const inverses: BlockCommand[] = []; + const affected: string[] = []; + for (const child of command.commands) { + const result = applyCommand(current, child, kind, registry); + if (!result.ok) return result; + current = result.value.document; + inverses.unshift(result.value.inverse); + affected.push(...result.value.affectedIds); + } + return { + ok: true, + value: { + document: current, + inverse: { type: "batch", commands: inverses }, + affectedIds: [...new Set(affected)], + selection: { primary: affected.at(-1) ?? null, ordered: [...new Set(affected)] }, + diagnostics: [], + }, + }; + } + let root = document.root; + let inverse: BlockCommand; + let affected: string[]; + if (command.type === "insert") { + root = insert(root, command.location, clone(command.nodes)); + affected = command.nodes.map((node) => node.id); + inverse = { type: "remove", nodeIds: affected }; + } else if (command.type === "remove") { + const captures = command.nodeIds.map((id) => ({ + node: findBlock(root, id), + location: locationOf(root, id), + })); + if (captures.some((item) => !item.node || !item.location)) + return { + ok: false, + errors: [ + { + code: "command.missing", + message: "Cannot remove the root or a missing block.", + severity: "error", + }, + ], + }; + root = remove(root, new Set(command.nodeIds)); + affected = command.nodeIds; + inverse = { + type: "batch", + commands: captures.map((item) => ({ + type: "insert", + location: item.location!, + nodes: [item.node!], + })), + }; + } else if (command.type === "move") { + const moving = command.intent.nodeIds.map((id) => findBlock(root, id)); + if (moving.some((node) => !node)) + return { + ok: false, + errors: [ + { code: "command.missing", message: "A moved block does not exist.", severity: "error" }, + ], + }; + for (const node of moving) + if (node && findBlock(node, command.intent.to.parentId)) + return { + ok: false, + errors: [ + { + code: "command.cycle", + message: "A block cannot move into its descendant.", + severity: "error", + }, + ], + }; + root = insert( + remove(root, new Set(command.intent.nodeIds)), + command.intent.to, + moving as BlockNode[], + ); + affected = command.intent.nodeIds; + inverse = { + type: "batch", + commands: command.intent.from.map((location, index) => ({ + type: "move", + intent: { + nodeIds: [command.intent.nodeIds[index]!], + from: [command.intent.to], + to: location, + }, + })), + }; + } else if (command.type === "updateProps") { + const previous = findBlock(root, command.nodeId); + if (!previous) + return { + ok: false, + errors: [{ code: "command.missing", message: "Block does not exist.", severity: "error" }], + }; + root = updateNode(root, command.nodeId, (node) => ({ + ...node, + props: { ...node.props, ...command.patch }, + })); + affected = [command.nodeId]; + inverse = { type: "replace", nodeId: command.nodeId, node: clone(previous) }; + } else if (command.type === "updateStyles") { + const previous = findBlock(root, command.nodeId); + if (!previous) + return { + ok: false, + errors: [{ code: "command.missing", message: "Block does not exist.", severity: "error" }], + }; + root = updateNode(root, command.nodeId, (node) => ({ + ...node, + styles: { ...node.styles, ...command.patch }, + })); + affected = [command.nodeId]; + inverse = { type: "replace", nodeId: command.nodeId, node: clone(previous) }; + } else { + const previous = findBlock(root, command.nodeId); + if (!previous) + return { + ok: false, + errors: [{ code: "command.missing", message: "Block does not exist.", severity: "error" }], + }; + root = updateNode(root, command.nodeId, () => clone(command.node)); + affected = [command.node.id]; + inverse = { type: "replace", nodeId: command.node.id, node: clone(previous) }; + } + const next = { ...document, root }; + const valid = validateDocument(next, kind, registry); + if (!valid.ok) return valid; + return { + ok: true, + value: { + document: next, + inverse, + affectedIds: affected, + selection: { primary: affected[0] ?? null, ordered: affected }, + diagnostics: [], + }, + }; +} + +export function duplicateNodes(nodes: readonly BlockNode[], id: () => string): BlockNode[] { + const copy = (node: BlockNode): BlockNode => ({ + ...clone(node), + id: id(), + slots: Object.fromEntries( + Object.entries(node.slots).map(([name, children]) => [name, children.map(copy)]), + ), + }); + return nodes.map(copy); +} diff --git a/packages/blocks/src/document.ts b/packages/blocks/src/document.ts new file mode 100644 index 00000000..125294c9 --- /dev/null +++ b/packages/blocks/src/document.ts @@ -0,0 +1,165 @@ +import type { BlockRegistry, ReadonlyBlockRegistry } from "./registry.ts"; +import type { DocumentKindDefinition } from "./kinds.ts"; +import { + DEFAULT_LIMITS, + type BlockDocument, + type BlockNode, + type Diagnostic, + type Result, +} from "./types.ts"; + +const byteLength = (value: string): number => new TextEncoder().encode(value).byteLength; + +export function walkBlocks( + root: BlockNode, + visitor: (node: BlockNode, depth: number) => void, +): void { + const visit = (node: BlockNode, depth: number) => { + visitor(node, depth); + for (const slot of Object.keys(node.slots).sort()) + for (const child of node.slots[slot] ?? []) visit(child, depth + 1); + }; + visit(root, 0); +} + +export function findBlock(root: BlockNode, id: string): BlockNode | undefined { + let found: BlockNode | undefined; + walkBlocks(root, (node) => { + if (node.id === id) found = node; + }); + return found; +} + +export function validateDocument( + document: BlockDocument, + kind: DocumentKindDefinition, + registry: ReadonlyBlockRegistry, +): Result { + const errors: Diagnostic[] = []; + const limits = kind.limits ?? DEFAULT_LIMITS; + let encoded = ""; + try { + encoded = JSON.stringify(document); + } catch { + errors.push({ + code: "document.json", + message: "Document must be JSON-serializable.", + severity: "error", + }); + } + if (byteLength(encoded) > limits.maxBytes) + errors.push({ + code: "document.size", + message: `Document exceeds ${limits.maxBytes} bytes.`, + severity: "error", + }); + if (document.kind !== kind.kind) + errors.push({ + code: "document.kind", + message: `Expected document kind ${kind.kind}.`, + severity: "error", + }); + if (!kind.rootTypes.includes(document.root.type)) + errors.push({ + code: "document.root", + message: `Block ${document.root.type} cannot be the root.`, + severity: "error", + nodeId: document.root.id, + }); + const meta = kind.metadata.parse(document.meta, "meta"); + if (!meta.ok) errors.push(...meta.errors); + const ids = new Set(); + let count = 0; + walkBlocks(document.root, (node, depth) => { + count++; + if (!node.id || ids.has(node.id)) + errors.push({ + code: "document.id", + message: `Duplicate or empty block id ${node.id}.`, + severity: "error", + nodeId: node.id, + }); + ids.add(node.id); + if (depth > limits.maxDepth) + errors.push({ + code: "document.depth", + message: `Document exceeds depth ${limits.maxDepth}.`, + severity: "error", + nodeId: node.id, + }); + const definition = registry.get(node.type); + if (!definition) { + errors.push({ + code: "document.unknown-block", + message: `Unknown block type ${node.type}.`, + severity: "error", + nodeId: node.id, + }); + return; + } + if (node.version !== definition.manifest.version) + errors.push({ + code: "document.block-version", + message: `${node.type} requires migration to version ${definition.manifest.version}.`, + severity: "error", + nodeId: node.id, + }); + const props = definition.manifest.props.parse(node.props, `blocks.${node.id}.props`); + if (!props.ok) errors.push(...props.errors.map((error) => ({ ...error, nodeId: node.id }))); + const styles = definition.manifest.styles.parse(node.styles, `blocks.${node.id}.styles`); + if (!styles.ok) errors.push(...styles.errors.map((error) => ({ ...error, nodeId: node.id }))); + for (const slotName of Object.keys(node.slots)) + if (!(slotName in definition.manifest.slots)) + errors.push({ + code: "document.slot", + message: `Unknown slot ${slotName} on ${node.type}.`, + severity: "error", + nodeId: node.id, + }); + for (const [slotName, slot] of Object.entries(definition.manifest.slots)) { + const children = node.slots[slotName] ?? []; + const maximum = Math.min( + slot.maxChildren ?? limits.maxChildrenPerSlot, + limits.maxChildrenPerSlot, + ); + if (children.length < (slot.minChildren ?? 0) || children.length > maximum) + errors.push({ + code: "document.cardinality", + message: `Slot ${slotName} has invalid child count.`, + severity: "error", + nodeId: node.id, + }); + for (const child of children) { + const childDefinition = registry.get(child.type); + const acceptedType = !slot.accepts?.types || slot.accepts.types.includes(child.type); + const acceptedCapabilities = + !slot.accepts?.capabilities || + slot.accepts.capabilities.every((capability) => + childDefinition?.manifest.capabilities?.includes(capability), + ); + if (!acceptedType || !acceptedCapabilities) + errors.push({ + code: "document.slot-accept", + message: `${child.type} is not accepted by ${node.type}.${slotName}.`, + severity: "error", + nodeId: child.id, + }); + } + } + errors.push(...(definition.validate?.(node) ?? [])); + }); + if (count > limits.maxNodes) + errors.push({ + code: "document.nodes", + message: `Document exceeds ${limits.maxNodes} nodes.`, + severity: "error", + }); + return errors.length ? { ok: false, errors } : { ok: true, value: document }; +} + +export function registryForKind(kind: DocumentKindDefinition): BlockRegistry { + const registry = createRegistry(kind.blocks); + return registry; +} + +import { createRegistry } from "./registry.ts"; diff --git a/packages/blocks/src/index.ts b/packages/blocks/src/index.ts new file mode 100644 index 00000000..fef80eab --- /dev/null +++ b/packages/blocks/src/index.ts @@ -0,0 +1,8 @@ +export * from "./types.ts"; +export * from "./schema.ts"; +export * from "./registry.ts"; +export * from "./kinds.ts"; +export * from "./document.ts"; +export * from "./commands.ts"; +export * from "./clipboard.ts"; +export * from "./render.ts"; diff --git a/packages/blocks/src/kinds.ts b/packages/blocks/src/kinds.ts new file mode 100644 index 00000000..6aa9e37c --- /dev/null +++ b/packages/blocks/src/kinds.ts @@ -0,0 +1,20 @@ +import type { DocumentLimits, JsonObject } from "./types.ts"; +import type { ValueSchema } from "./schema.ts"; +import type { BlockDefinition } from "./registry.ts"; + +export interface DocumentKindDefinition { + kind: string; + version: number; + metadata: ValueSchema; + rootTypes: string[]; + blocks: readonly BlockDefinition[]; + variables: Record; + breakpoints: Record; + capabilities: string[]; + targets: string[]; + limits: DocumentLimits; + urlSchemes: string[]; +} + +export const defineDocumentKind = (definition: T): T => + definition; diff --git a/packages/blocks/src/registry.ts b/packages/blocks/src/registry.ts new file mode 100644 index 00000000..096f4872 --- /dev/null +++ b/packages/blocks/src/registry.ts @@ -0,0 +1,143 @@ +import type { BlockNode, Diagnostic, JsonObject, Result } from "./types.ts"; +import type { ValueSchema } from "./schema.ts"; + +export interface LocalizedLabel { + key: string; + fallback: string; +} +export type BlockCapability = string; + +export interface SlotDefinition { + label?: LocalizedLabel; + accepts?: { types?: string[]; capabilities?: string[] }; + minChildren?: number; + maxChildren?: number; + orientation?: "vertical" | "horizontal" | "grid"; + sortable?: boolean; +} + +export interface InspectorControl { + kind: string; + path: `props.${string}` | `styles.${string}`; + label?: LocalizedLabel; + options?: string[]; +} + +export interface BlockManifest { + type: string; + version: number; + name: LocalizedLabel; + description?: LocalizedLabel; + icon: string; + category: string; + tags?: string[]; + capabilities?: BlockCapability[]; + props: ValueSchema; + styles: ValueSchema>; + slots: Record; +} + +export interface BlockRenderContext { + node: BlockNode; + slots: Readonly>; + context: unknown; +} +export type BlockRenderer = (input: BlockRenderContext) => string | Promise; + +export interface BlockDefinition { + manifest: BlockManifest; + create(context: { id(): string }): BlockNode; + normalize?(node: BlockNode): BlockNode; + validate?(node: BlockNode): Diagnostic[]; + resolveStyles?( + styles: Record, + context: unknown, + ): Record; + renderers?: Record; + textRenderer?: BlockRenderer; + editor?: { controls: InspectorControl[]; outlineLabel?(node: BlockNode): string }; + migrations?: Array<{ from: number; to: number; up(node: BlockNode): BlockNode }>; +} + +export const defineBlock = (definition: T): T => definition; + +export interface BlockRegistry { + get(type: string): BlockDefinition | undefined; + has(type: string): boolean; + list(): readonly BlockDefinition[]; + register(definition: BlockDefinition): () => void; + freeze(): ReadonlyBlockRegistry; +} + +export type ReadonlyBlockRegistry = Pick; + +const TYPE = /^[a-z][a-z0-9]*(?:[.-][a-z0-9]+)+$/; + +function validateDefinition(definition: BlockDefinition): Result { + const errors: Diagnostic[] = []; + const { manifest } = definition; + if (!TYPE.test(manifest.type)) + errors.push({ + code: "registry.type", + message: `Invalid block type ${manifest.type}.`, + severity: "error", + }); + if (!Number.isInteger(manifest.version) || manifest.version < 1) + errors.push({ + code: "registry.version", + message: "Block version must be a positive integer.", + severity: "error", + }); + if (!manifest.name?.key || !manifest.name.fallback) + errors.push({ + code: "registry.name", + message: "Block name key and fallback are required.", + severity: "error", + }); + for (const control of definition.editor?.controls ?? []) { + if (!/^(props|styles)\.[A-Za-z0-9_.-]+$/.test(control.path)) + errors.push({ + code: "registry.control", + message: `Invalid inspector path ${control.path}.`, + severity: "error", + }); + } + const migrations = definition.migrations ?? []; + for (let version = 1; version < manifest.version; version++) { + if (!migrations.some((migration) => migration.from === version && migration.to === version + 1)) + errors.push({ + code: "registry.migration", + message: `Missing ${manifest.type} migration ${version} -> ${version + 1}.`, + severity: "error", + }); + } + return errors.length ? { ok: false, errors } : { ok: true, value: definition }; +} + +export function createRegistry(initial: readonly BlockDefinition[] = []): BlockRegistry { + const entries = new Map(); + let frozen = false; + const registry: BlockRegistry = { + get: (type) => entries.get(type), + has: (type) => entries.has(type), + list: () => [...entries.values()], + register(definition) { + if (frozen) throw new Error("Block registry is frozen."); + const valid = validateDefinition(definition); + if (!valid.ok) + throw new Error(valid.errors.map((error) => `${error.code}: ${error.message}`).join("\n")); + if (entries.has(definition.manifest.type)) + throw new Error(`Duplicate block type ${definition.manifest.type}.`); + entries.set(definition.manifest.type, definition); + return () => { + if (!frozen) entries.delete(definition.manifest.type); + }; + }, + freeze() { + frozen = true; + return registry; + }, + }; + for (const definition of initial) registry.register(definition); + return registry; +} diff --git a/packages/blocks/src/render.ts b/packages/blocks/src/render.ts new file mode 100644 index 00000000..78aa914b --- /dev/null +++ b/packages/blocks/src/render.ts @@ -0,0 +1,73 @@ +import { validateDocument } from "./document.ts"; +import type { DocumentKindDefinition } from "./kinds.ts"; +import type { ReadonlyBlockRegistry } from "./registry.ts"; +import type { BlockDocument, BlockNode, Diagnostic, Result } from "./types.ts"; + +export interface RenderContext { + target: string; + locale: string; + timezone: string; + direction: "ltr" | "rtl"; + data?: Record; + resolveAsset?: (reference: string) => Promise; +} + +export interface RenderOutput { + content: string; + warnings: Diagnostic[]; +} + +export async function renderTree( + document: BlockDocument, + kind: DocumentKindDefinition, + registry: ReadonlyBlockRegistry, + context: RenderContext, +): Promise> { + const valid = validateDocument(document, kind, registry); + if (!valid.ok) return valid; + const warnings: Diagnostic[] = []; + const renderNode = async (node: BlockNode): Promise => { + const definition = registry.get(node.type)!; + const renderer = + context.target === "plainText" + ? definition.textRenderer + : definition.renderers?.[context.target]; + if (!renderer) throw new Error(`Block ${node.type} does not support ${context.target}.`); + const slots: Record = {}; + for (const name of Object.keys(definition.manifest.slots)) + slots[name] = (node.slots[name] ?? []).length + ? (await Promise.all((node.slots[name] ?? []).map(renderNode))).join("") + : ""; + return renderer({ node, slots, context }); + }; + try { + const content = await renderNode(document.root); + if (new TextEncoder().encode(content).byteLength > kind.limits.maxOutputBytes) + return { + ok: false, + errors: [ + { code: "render.size", message: "Rendered output is too large.", severity: "error" }, + ], + }; + return { ok: true, value: { content, warnings } }; + } catch (error) { + return { + ok: false, + errors: [ + { + code: "render.failed", + message: error instanceof Error ? error.message : "Rendering failed.", + severity: "error", + }, + ], + }; + } +} + +export const escapeHtml = (value: unknown): string => + String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); diff --git a/packages/blocks/src/schema.ts b/packages/blocks/src/schema.ts new file mode 100644 index 00000000..a4f88d32 --- /dev/null +++ b/packages/blocks/src/schema.ts @@ -0,0 +1,173 @@ +import type { Diagnostic, JsonValue, Result } from "./types.ts"; + +export interface ValueSchema { + readonly kind: string; + parse(value: unknown, path?: string): Result; + readonly defaultValue?: T; +} + +const fail = (code: string, message: string, path: string): Result => ({ + ok: false, + errors: [{ code, message, severity: "error", path }], +}); + +export function string( + options: { min?: number; max?: number; default?: string } = {}, +): ValueSchema { + return { + kind: "string", + defaultValue: options.default, + parse(value, path = "value") { + if (value === undefined && options.default !== undefined) + return { ok: true, value: options.default }; + if (typeof value !== "string") return fail("schema.string", "Expected a string.", path); + if (options.min !== undefined && value.length < options.min) + return fail("schema.string.min", `Must contain at least ${options.min} characters.`, path); + if (options.max !== undefined && value.length > options.max) + return fail("schema.string.max", `Must contain at most ${options.max} characters.`, path); + return { ok: true, value }; + }, + }; +} + +export function number( + options: { min?: number; max?: number; default?: number } = {}, +): ValueSchema { + return { + kind: "number", + defaultValue: options.default, + parse(value, path = "value") { + if (value === undefined && options.default !== undefined) + return { ok: true, value: options.default }; + if (typeof value !== "number" || !Number.isFinite(value)) + return fail("schema.number", "Expected a finite number.", path); + if (options.min !== undefined && value < options.min) + return fail("schema.number.min", `Must be at least ${options.min}.`, path); + if (options.max !== undefined && value > options.max) + return fail("schema.number.max", `Must be at most ${options.max}.`, path); + return { ok: true, value }; + }, + }; +} + +export function boolean(defaultValue?: boolean): ValueSchema { + return { + kind: "boolean", + defaultValue, + parse(value, path = "value") { + if (value === undefined && defaultValue !== undefined) + return { ok: true, value: defaultValue }; + return typeof value === "boolean" + ? { ok: true, value } + : fail("schema.boolean", "Expected a boolean.", path); + }, + }; +} + +export function choice( + values: T, + options: { default?: T[number] } = {}, +): ValueSchema { + const allowed = new Set(values); + return { + kind: "choice", + defaultValue: options.default, + parse(value, path = "value") { + if (value === undefined && options.default !== undefined) + return { ok: true, value: options.default }; + return typeof value === "string" && allowed.has(value) + ? { ok: true, value: value as T[number] } + : fail("schema.choice", `Expected one of: ${values.join(", ")}.`, path); + }, + }; +} + +export function safeUrl( + options: { schemes?: string[]; optional?: boolean } = {}, +): ValueSchema { + const schemes = new Set((options.schemes ?? ["https"]).map((value) => value.replace(/:$/, ""))); + return { + kind: "url", + parse(value, path = "value") { + if ((value === undefined || value === "") && options.optional) return { ok: true, value: "" }; + if (typeof value !== "string" || value.length > 4_096) + return fail("schema.url", "Expected a bounded URL.", path); + try { + const parsed = new URL(value); + return schemes.has(parsed.protocol.replace(/:$/, "")) + ? { ok: true, value: parsed.href } + : fail("schema.url.scheme", `URL scheme ${parsed.protocol} is not allowed.`, path); + } catch { + return fail("schema.url", "Expected an absolute URL.", path); + } + }, + }; +} + +export function object>( + shape: T, +): ValueSchema<{ [K in keyof T]: ReturnType extends Result ? V : never }> { + return { + kind: "object", + parse(value, path = "value") { + if (!value || typeof value !== "object" || Array.isArray(value)) + return fail("schema.object", "Expected an object.", path); + const source = value as Record; + const output: Record = {}; + const errors: Diagnostic[] = []; + for (const key of Object.keys(source)) { + if (!(key in shape)) + errors.push({ + code: "schema.unknown", + message: `Unknown field ${key}.`, + severity: "error", + path: `${path}.${key}`, + }); + } + for (const [key, schema] of Object.entries(shape)) { + const result = schema.parse(source[key], `${path}.${key}`); + if (result.ok) output[key] = result.value; + else errors.push(...result.errors); + } + return errors.length ? { ok: false, errors } : { ok: true, value: output as never }; + }, + }; +} + +export const json: ValueSchema = { + kind: "json", + parse(value, path = "value") { + try { + const encoded = JSON.stringify(value); + if (encoded === undefined) + return fail("schema.json", "Expected JSON-serializable data.", path); + return { ok: true, value: JSON.parse(encoded) as JsonValue }; + } catch { + return fail("schema.json", "Expected JSON-serializable data.", path); + } + }, +}; + +export function record( + valueSchema: ValueSchema, + options: { maxKeys?: number } = {}, +): ValueSchema> { + return { + kind: "record", + parse(value, path = "value") { + if (!value || typeof value !== "object" || Array.isArray(value)) + return fail("schema.record", "Expected an object.", path); + const entries = Object.entries(value as Record); + if (entries.length > (options.maxKeys ?? 100)) + return fail("schema.record.size", "Object contains too many fields.", path); + const output: Record = {}; + const errors: Diagnostic[] = []; + for (const [key, raw] of entries) { + const result = valueSchema.parse(raw, `${path}.${key}`); + if (result.ok) output[key] = result.value; + else errors.push(...result.errors); + } + return errors.length ? { ok: false, errors } : { ok: true, value: output }; + }, + }; +} diff --git a/packages/blocks/src/types.ts b/packages/blocks/src/types.ts new file mode 100644 index 00000000..cfbb8d28 --- /dev/null +++ b/packages/blocks/src/types.ts @@ -0,0 +1,80 @@ +export type JsonPrimitive = string | number | boolean | null; +export type JsonValue = JsonPrimitive | JsonValue[] | { [key: string]: JsonValue }; +export type JsonObject = { [key: string]: JsonValue }; + +export interface ResponsiveValue { + base: T; + overrides?: Record; + states?: Partial>; +} + +export type BlockStyles = Record; + +export interface BlockNode

{ + id: string; + type: string; + version: number; + props: P; + styles: S; + slots: Record; +} + +export interface DocumentTheme { + tokens: Record; + darkTokens?: Record; +} + +export interface BlockDocument { + schemaVersion: number; + kind: string; + meta: JsonObject; + theme: DocumentTheme; + root: BlockNode; +} + +export type Severity = "error" | "warning" | "info"; + +export interface Diagnostic { + code: string; + message: string; + severity: Severity; + nodeId?: string; + path?: string; +} + +export type Result = { ok: true; value: T } | { ok: false; errors: E }; + +export interface SelectionState { + primary: string | null; + ordered: string[]; +} + +export interface BlockLocation { + parentId: string; + slot: string; + index: number; +} + +export interface MoveIntent { + nodeIds: string[]; + from: BlockLocation[]; + to: BlockLocation; +} + +export interface DocumentLimits { + maxBytes: number; + maxNodes: number; + maxDepth: number; + maxChildrenPerSlot: number; + maxStringLength: number; + maxOutputBytes: number; +} + +export const DEFAULT_LIMITS: DocumentLimits = { + maxBytes: 1_000_000, + maxNodes: 2_000, + maxDepth: 32, + maxChildrenPerSlot: 500, + maxStringLength: 200_000, + maxOutputBytes: 2_000_000, +}; diff --git a/packages/blocks/test/engine.test.ts b/packages/blocks/test/engine.test.ts new file mode 100644 index 00000000..d1141513 --- /dev/null +++ b/packages/blocks/test/engine.test.ts @@ -0,0 +1,140 @@ +import { describe, expect, test } from "bun:test"; +import { + applyCommand, + createRegistry, + decodeClipboard, + defineBlock, + defineDocumentKind, + duplicateNodes, + encodeClipboard, + json, + object, + record, + string, + validateDocument, + type BlockDocument, +} from "../src/index.ts"; + +let serial = 0; +const id = () => `id-${++serial}`; +const styles = record(json) as never; +const text = defineBlock({ + manifest: { + type: "content.text", + version: 1, + name: { key: "text", fallback: "Text" }, + icon: "text", + category: "content", + capabilities: ["content"], + props: object({ text: string({ max: 20 }) }) as never, + styles, + slots: {}, + }, + create: ({ id }) => ({ + id: id(), + type: "content.text", + version: 1, + props: { text: "" }, + styles: {}, + slots: {}, + }), + renderers: { html: ({ node }) => String(node.props.text) }, + textRenderer: ({ node }) => String(node.props.text), +}); +const root = defineBlock({ + manifest: { + type: "layout.root", + version: 1, + name: { key: "root", fallback: "Root" }, + icon: "root", + category: "layout", + props: object({}) as never, + styles, + slots: { content: { accepts: { capabilities: ["content"] }, maxChildren: 5 } }, + }, + create: ({ id }) => ({ + id: id(), + type: "layout.root", + version: 1, + props: {}, + styles: {}, + slots: { content: [] }, + }), + renderers: { html: ({ slots }) => slots.content }, + textRenderer: ({ slots }) => slots.content, +}); +const kind = defineDocumentKind({ + kind: "test", + version: 1, + metadata: object({ title: string() }) as never, + rootTypes: ["layout.root"], + blocks: [root, text], + variables: {}, + breakpoints: {}, + capabilities: [], + targets: ["html"], + limits: { + maxBytes: 10_000, + maxNodes: 20, + maxDepth: 5, + maxChildrenPerSlot: 5, + maxStringLength: 100, + maxOutputBytes: 1_000, + }, + urlSchemes: ["https"], +}); +const registry = createRegistry(kind.blocks).freeze(); +const doc = (): BlockDocument => ({ + schemaVersion: 1, + kind: "test", + meta: { title: "x" }, + theme: { tokens: {} }, + root: { + id: "root", + type: "layout.root", + version: 1, + props: {}, + styles: {}, + slots: { content: [] }, + }, +}); + +describe("dynamic blocks engine", () => { + test("rejects duplicate registrations and freezes registries", () => { + expect(() => createRegistry([text, text])).toThrow(/Duplicate block/); + const frozen = createRegistry([text]).freeze(); + expect(() => (frozen as never as { register(value: unknown): void }).register(text)).toThrow(); + }); + + test("validates named slots and applies invertible commands", () => { + const node = text.create({ id }); + node.props.text = "Hello"; + const inserted = applyCommand( + doc(), + { type: "insert", location: { parentId: "root", slot: "content", index: 0 }, nodes: [node] }, + kind, + registry, + ); + expect(inserted.ok).toBe(true); + if (!inserted.ok) return; + expect(inserted.value.document.root.slots.content[0]?.props.text).toBe("Hello"); + const undone = applyCommand(inserted.value.document, inserted.value.inverse, kind, registry); + expect(undone.ok && undone.value.document.root.slots.content).toEqual([]); + }); + + test("rejects cycles and invalid slots", () => { + const bad = doc(); + bad.root.slots.missing = []; + expect(validateDocument(bad, kind, registry).ok).toBe(false); + }); + + test("clipboard is deterministic and remaps ids", () => { + const node = text.create({ id }); + node.props.text = "Hello"; + const encoded = encodeClipboard("test", [node]); + expect(encoded).toBe(encodeClipboard("test", [node])); + const decoded = decodeClipboard(encoded, id); + expect(decoded.ok && decoded.value.roots[0]?.id).not.toBe(node.id); + expect(duplicateNodes([node], id)[0]?.props).toEqual(node.props); + }); +}); diff --git a/packages/blocks/tsconfig.json b/packages/blocks/tsconfig.json new file mode 100644 index 00000000..e8b02ca4 --- /dev/null +++ b/packages/blocks/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/packages/dnd/README.md b/packages/dnd/README.md new file mode 100644 index 00000000..3679ded3 --- /dev/null +++ b/packages/dnd/README.md @@ -0,0 +1,4 @@ +# @wrnexus/dnd + +Stable WRNexus drag-and-drop API backed by `@dnd-kit/dom`. Applications consume +move intents and never import dnd-kit directly. diff --git a/packages/dnd/package.json b/packages/dnd/package.json new file mode 100644 index 00000000..752db87e --- /dev/null +++ b/packages/dnd/package.json @@ -0,0 +1,25 @@ +{ + "name": "@wrnexus/dnd", + "version": "0.1.0", + "type": "module", + "description": "Stable accessible drag-and-drop primitives for WRNexus.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src", + "README.md" + ], + "scripts": { + "test": "bun test", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@dnd-kit/dom": "0.5.0" + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "happy-dom": "^20.11.2", + "typescript": "^6.0.3" + } +} diff --git a/packages/dnd/src/index.ts b/packages/dnd/src/index.ts new file mode 100644 index 00000000..a6e19ced --- /dev/null +++ b/packages/dnd/src/index.ts @@ -0,0 +1,3 @@ +export * from "./types.ts"; +export * from "./reorder.ts"; +export * from "./manager.ts"; diff --git a/packages/dnd/src/manager.ts b/packages/dnd/src/manager.ts new file mode 100644 index 00000000..8ed3ae15 --- /dev/null +++ b/packages/dnd/src/manager.ts @@ -0,0 +1,108 @@ +import { DragDropManager } from "@dnd-kit/dom"; +import { Sortable } from "@dnd-kit/dom/sortable"; +import type { + DndManager, + DndManagerOptions, + DndMoveIntent, + SortableRegistration, +} from "./types.ts"; + +export function createDndManager(options: DndManagerOptions): DndManager { + const direction = options.direction ?? "ltr"; + const reducedMotion = options.reducedMotion ?? false; + const entries = new Map< + string | number, + { + sortable: Sortable; + initial: { group: string | number; index: number }; + current: SortableRegistration; + } + >(); + const manager = new DragDropManager(); + let activeIds: Array = []; + + manager.monitor.addEventListener("dragstart", (event) => { + const source = event.operation.source; + if (!source) return; + activeIds = [source.id]; + options.announce?.({ type: "pickedUp", ids: activeIds }); + }); + manager.monitor.addEventListener("dragover", (event) => { + const source = event.operation.source as + (typeof event.operation.source & { group?: string | number; index?: number }) | null; + if (!source) return; + const destination = { group: source.group ?? "default", index: source.index ?? 0 }; + if (options.accept && !options.accept(activeIds, destination)) { + event.preventDefault(); + options.announce?.({ type: "invalid", ids: activeIds, location: destination }); + } else options.announce?.({ type: "movedOver", ids: activeIds, location: destination }); + }); + manager.monitor.addEventListener("dragend", async (event) => { + const source = event.operation.source as + | (typeof event.operation.source & { + group?: string | number; + index?: number; + initialGroup?: string | number; + initialIndex?: number; + }) + | null; + if (!source || event.operation.canceled) { + options.announce?.({ type: "cancelled", ids: activeIds }); + activeIds = []; + return; + } + const intent: DndMoveIntent = { + ids: [...activeIds], + from: [ + { + group: source.initialGroup ?? source.group ?? "default", + index: source.initialIndex ?? source.index ?? 0, + }, + ], + to: { group: source.group ?? "default", index: source.index ?? 0 }, + }; + const accepted = await options.onMoveIntent(intent); + options.announce?.({ + type: accepted === false ? "cancelled" : "dropped", + ids: activeIds, + location: intent.to, + }); + activeIds = []; + }); + + return { + registerSortable(registration) { + if (entries.has(registration.id)) + throw new Error(`Duplicate sortable id ${String(registration.id)}.`); + const sortable = new Sortable( + { + id: registration.id, + group: registration.group, + index: registration.index, + element: registration.element, + handle: registration.handle, + type: registration.type, + accept: registration.accepts, + disabled: registration.disabled, + transition: reducedMotion ? null : undefined, + data: { ...registration.data, direction }, + }, + manager, + ); + entries.set(registration.id, { + sortable, + initial: { group: registration.group, index: registration.index }, + current: registration, + }); + return () => { + sortable.destroy(); + entries.delete(registration.id); + }; + }, + destroy() { + for (const entry of entries.values()) entry.sortable.destroy(); + entries.clear(); + manager.destroy(); + }, + }; +} diff --git a/packages/dnd/src/reorder.ts b/packages/dnd/src/reorder.ts new file mode 100644 index 00000000..a12089d2 --- /dev/null +++ b/packages/dnd/src/reorder.ts @@ -0,0 +1,42 @@ +import type { Direction, DndMoveIntent, SortableLocation } from "./types.ts"; + +export function visualIndex( + index: number, + length: number, + orientation: "vertical" | "horizontal", + direction: Direction, +): number { + return orientation === "horizontal" && direction === "rtl" + ? Math.max(0, length - 1 - index) + : index; +} + +export function reorderGroups( + groups: ReadonlyMap, + intent: DndMoveIntent, +): Map { + const output = new Map(); + for (const [group, ids] of groups) output.set(group, [...ids]); + const moving = new Set(intent.ids as T[]); + for (const [group, ids] of output) + output.set( + group, + ids.filter((id) => !moving.has(id)), + ); + const destination = output.get(intent.to.group as T); + if (!destination) throw new Error(`Unknown destination group ${String(intent.to.group)}.`); + destination.splice( + Math.max(0, Math.min(intent.to.index, destination.length)), + 0, + ...(intent.ids as T[]), + ); + return output; +} + +export function deepestValidTarget< + T extends { depth: number; location: SortableLocation; disabled?: boolean }, +>(targets: readonly T[], accept: (location: SortableLocation) => boolean): T | undefined { + return [...targets] + .filter((target) => !target.disabled && accept(target.location)) + .sort((left, right) => right.depth - left.depth)[0]; +} diff --git a/packages/dnd/src/types.ts b/packages/dnd/src/types.ts new file mode 100644 index 00000000..a5cf4fe1 --- /dev/null +++ b/packages/dnd/src/types.ts @@ -0,0 +1,43 @@ +export type DndId = string | number; +export type Direction = "ltr" | "rtl"; +export type Orientation = "vertical" | "horizontal" | "grid"; + +export interface SortableLocation { + group: DndId; + index: number; +} +export interface DndMoveIntent { + ids: DndId[]; + from: SortableLocation[]; + to: SortableLocation; +} +export interface DndAnnouncement { + type: "pickedUp" | "movedOver" | "invalid" | "dropped" | "cancelled"; + ids: DndId[]; + location?: SortableLocation; +} + +export interface SortableRegistration { + id: DndId; + group: DndId; + index: number; + element: HTMLElement; + handle?: HTMLElement; + type?: DndId; + accepts?: DndId[]; + disabled?: boolean; + data?: Record; +} + +export interface DndManagerOptions { + direction?: Direction; + reducedMotion?: boolean; + announce?: (announcement: DndAnnouncement) => void; + accept?: (ids: DndId[], destination: SortableLocation) => boolean; + onMoveIntent(intent: DndMoveIntent): boolean | void | Promise; +} + +export interface DndManager { + registerSortable(registration: SortableRegistration): () => void; + destroy(): void; +} diff --git a/packages/dnd/test/reorder.test.ts b/packages/dnd/test/reorder.test.ts new file mode 100644 index 00000000..81e7a5f7 --- /dev/null +++ b/packages/dnd/test/reorder.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from "bun:test"; +import { deepestValidTarget, reorderGroups, visualIndex } from "../src/index.ts"; + +describe("dnd pure behavior", () => { + test("moves across groups without mutation", () => { + const input = new Map([ + ["a", ["1", "2"]], + ["b", ["3"]], + ]); + const result = reorderGroups(input, { + ids: ["2"], + from: [{ group: "a", index: 1 }], + to: { group: "b", index: 0 }, + }); + expect(result.get("a")).toEqual(["1"]); + expect(result.get("b")).toEqual(["2", "3"]); + expect(input.get("a")).toEqual(["1", "2"]); + }); + test("mirrors horizontal RTL index", () => + expect(visualIndex(0, 4, "horizontal", "rtl")).toBe(3)); + test("selects deepest valid target", () => + expect( + deepestValidTarget( + [ + { depth: 1, location: { group: "a", index: 0 } }, + { depth: 3, location: { group: "b", index: 0 } }, + ], + (location) => location.group === "b", + )?.depth, + ).toBe(3)); +}); diff --git a/packages/dnd/tsconfig.json b/packages/dnd/tsconfig.json new file mode 100644 index 00000000..e8b02ca4 --- /dev/null +++ b/packages/dnd/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +} diff --git a/packages/email-renderer/README.md b/packages/email-renderer/README.md new file mode 100644 index 00000000..f22bdd01 --- /dev/null +++ b/packages/email-renderer/README.md @@ -0,0 +1,5 @@ +# @wrnexus/email-renderer + +Email target pack for `@wrnexus/blocks`. Standard HTML and plain text are +required outputs; valid AMP Email is an optional MIME alternative. MJML is a +strict import/export interoperability format, never the canonical document. diff --git a/packages/email-renderer/package.json b/packages/email-renderer/package.json new file mode 100644 index 00000000..fb6b97e0 --- /dev/null +++ b/packages/email-renderer/package.json @@ -0,0 +1,27 @@ +{ + "name": "@wrnexus/email-renderer", + "version": "0.1.0", + "type": "module", + "description": "WRNexus block email renderers with HTML, text, AMP Email, and MJML interoperability.", + "exports": { + ".": "./src/index.ts" + }, + "files": [ + "src", + "README.md" + ], + "scripts": { + "test": "bun test", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@wrnexus/blocks": "workspace:*", + "amphtml-validator": "1.0.38", + "mjml": "5.4.0", + "mjml-parser-xml": "5.4.0" + }, + "devDependencies": { + "@types/bun": "^1.4.0", + "typescript": "^6.0.3" + } +} diff --git a/packages/email-renderer/src/blocks.ts b/packages/email-renderer/src/blocks.ts new file mode 100644 index 00000000..56ec138c --- /dev/null +++ b/packages/email-renderer/src/blocks.ts @@ -0,0 +1,340 @@ +import { + boolean, + choice, + defineBlock, + json, + object, + record, + safeUrl, + string, + type BlockDefinition, + type BlockNode, +} from "@wrnexus/blocks"; + +const styles = record(json, { maxKeys: 50 }) as never; +const label = (key: string, fallback: string) => ({ key, fallback }); +const empty = object({}) as never; +const contentSlot = { + label: label("blocks.slot.content", "Content"), + accepts: { capabilities: ["content"] }, + maxChildren: 500, + orientation: "vertical" as const, + sortable: true, +}; + +const html = (value: unknown) => + String(value ?? "") + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +const attrs = (node: BlockNode) => node.props as Record; +const emailCell = (body: string, node: BlockNode) => + `${body}`; + +export const emailDocumentBlock = defineBlock({ + manifest: { + type: "layout.email-document", + version: 1, + name: label("blocks.document.name", "Document"), + icon: "icon-[lucide--mail]", + category: "layout", + capabilities: ["root"], + props: object({ + width: string({ default: "600" }), + background: string({ default: "#f3f4f6" }), + }) as never, + styles, + slots: { content: contentSlot }, + }, + create: ({ id }) => ({ + id: id(), + type: "layout.email-document", + version: 1, + props: { width: "600", background: "#f3f4f6" }, + styles: {}, + slots: { content: [] }, + }), + renderers: { + emailHtml: ({ node, slots }) => + `
${slots.content}
`, + ampEmail: ({ node, slots }) => + ``, + mjml: ({ node, slots }) => + `${slots.content}`, + }, + textRenderer: ({ slots }) => slots.content, +} satisfies BlockDefinition); + +export const sectionBlock = defineBlock({ + manifest: { + type: "layout.section", + version: 1, + name: label("blocks.section.name", "Section"), + icon: "icon-[lucide--panel-top]", + category: "layout", + capabilities: ["content"], + props: object({ + background: string({ default: "#ffffff" }), + padding: string({ default: "24px" }), + }) as never, + styles, + slots: { content: contentSlot }, + }, + create: ({ id }) => ({ + id: id(), + type: "layout.section", + version: 1, + props: { background: "#ffffff", padding: "24px" }, + styles: {}, + slots: { content: [] }, + }), + renderers: { + emailHtml: ({ node, slots }) => + emailCell( + `
${slots.content}
`, + node, + ), + ampEmail: ({ node, slots }) => + emailCell( + `
${slots.content}
`, + node, + ), + mjml: ({ node, slots }) => + `${slots.content}`, + }, + textRenderer: ({ slots }) => `${slots.content}\n`, +} satisfies BlockDefinition); + +export const columnsBlock = defineBlock({ + manifest: { + type: "layout.columns", + version: 1, + name: label("blocks.columns.name", "Columns"), + icon: "icon-[lucide--columns-3]", + category: "layout", + capabilities: ["content"], + props: empty, + styles, + slots: { + columns: { + accepts: { types: ["layout.column"] }, + minChildren: 1, + maxChildren: 4, + orientation: "horizontal", + sortable: true, + }, + }, + }, + create: ({ id }) => ({ + id: id(), + type: "layout.columns", + version: 1, + props: {}, + styles: {}, + slots: { columns: [] }, + }), + renderers: { + emailHtml: ({ node, slots }) => + emailCell( + `${slots.columns}
`, + node, + ), + ampEmail: ({ node, slots }) => + emailCell(`${slots.columns}
`, node), + mjml: ({ slots }) => slots.columns, + }, + textRenderer: ({ slots }) => slots.columns, +} satisfies BlockDefinition); + +export const columnBlock = defineBlock({ + manifest: { + type: "layout.column", + version: 1, + name: label("blocks.column.name", "Column"), + icon: "icon-[lucide--panel-left]", + category: "layout", + capabilities: ["column"], + props: object({ width: string({ default: "100%" }) }) as never, + styles, + slots: { content: contentSlot }, + }, + create: ({ id }) => ({ + id: id(), + type: "layout.column", + version: 1, + props: { width: "100%" }, + styles: {}, + slots: { content: [] }, + }), + renderers: { + emailHtml: ({ node, slots }) => + `${slots.content}`, + ampEmail: ({ node, slots }) => + `${slots.content}`, + mjml: ({ node, slots }) => + `${slots.content}`, + }, + textRenderer: ({ slots }) => slots.content, +} satisfies BlockDefinition); + +const leaf = ( + type: string, + name: string, + icon: string, + props: never, + renderers: BlockDefinition["renderers"], + textRenderer: BlockDefinition["textRenderer"], +): BlockDefinition => + defineBlock({ + manifest: { + type, + version: 1, + name: label(`blocks.${type}.name`, name), + icon, + category: "content", + capabilities: ["content"], + props, + styles, + slots: {}, + }, + create: ({ id }) => ({ id: id(), type, version: 1, props: {}, styles: {}, slots: {} }), + renderers, + textRenderer, + }); + +export const headingBlock = leaf( + "content.heading", + "Heading", + "icon-[lucide--heading]", + object({ + text: string({ min: 1, max: 500, default: "Heading" }), + level: choice(["1", "2", "3", "4"] as const, { default: "2" }), + }) as never, + { + emailHtml: ({ node }) => + emailCell( + `${html(attrs(node).text)}`, + node, + ), + ampEmail: ({ node }) => + emailCell(`${html(attrs(node).text)}`, node), + mjml: ({ node }) => + `${html(attrs(node).text)}`, + }, + ({ node }) => `${String(attrs(node).text)}\n\n`, +); + +export const textBlock = leaf( + "content.text", + "Text", + "icon-[lucide--text]", + object({ text: string({ max: 200_000, default: "" }) }) as never, + { + emailHtml: ({ node }) => + emailCell( + `

${html(attrs(node).text)}

`, + node, + ), + ampEmail: ({ node }) => emailCell(`

${html(attrs(node).text)}

`, node), + mjml: ({ node }) => `${html(attrs(node).text)}`, + }, + ({ node }) => `${String(attrs(node).text)}\n\n`, +); + +export const imageBlock = leaf( + "content.image", + "Image", + "icon-[lucide--image]", + object({ + src: safeUrl({ schemes: ["https"] }), + alt: string({ max: 1_000, default: "" }), + width: string({ default: "600" }), + height: string({ default: "300" }), + decorative: boolean(false), + }) as never, + { + emailHtml: ({ node }) => + emailCell( + `${html(attrs(node).decorative ? `, + node, + ), + ampEmail: ({ node }) => + emailCell( + ``, + node, + ), + mjml: ({ node }) => + ``, + }, + ({ node }) => (attrs(node).decorative ? "" : `[${String(attrs(node).alt)}]\n\n`), +); + +export const buttonBlock = leaf( + "content.button", + "Button", + "icon-[lucide--mouse-pointer-click]", + object({ + label: string({ min: 1, max: 200, default: "Button" }), + href: safeUrl({ schemes: ["https", "mailto"] }), + }) as never, + { + emailHtml: ({ node }) => + emailCell( + `
${html(attrs(node).label)}
`, + node, + ), + ampEmail: ({ node }) => + emailCell( + `${html(attrs(node).label)}`, + node, + ), + mjml: ({ node }) => + `${html(attrs(node).label)}`, + }, + ({ node }) => `${String(attrs(node).label)}: ${String(attrs(node).href)}\n\n`, +); + +export const dividerBlock = leaf( + "content.divider", + "Divider", + "icon-[lucide--minus]", + empty, + { + emailHtml: ({ node }) => + emailCell(`
`, node), + ampEmail: ({ node }) => emailCell(`
`, node), + mjml: () => ``, + }, + () => "----------------\n", +); +export const spacerBlock = leaf( + "content.spacer", + "Spacer", + "icon-[lucide--move-vertical]", + object({ height: string({ default: "24" }) }) as never, + { + emailHtml: ({ node }) => + emailCell( + `
 
`, + node, + ), + ampEmail: ({ node }) => emailCell(`
 
`, node), + mjml: ({ node }) => ``, + }, + () => "\n", +); + +export const emailCoreBlocks: readonly BlockDefinition[] = [ + emailDocumentBlock, + sectionBlock, + columnsBlock, + columnBlock, + headingBlock, + textBlock, + imageBlock, + buttonBlock, + dividerBlock, + spacerBlock, +]; diff --git a/packages/email-renderer/src/email.ts b/packages/email-renderer/src/email.ts new file mode 100644 index 00000000..c5df48a3 --- /dev/null +++ b/packages/email-renderer/src/email.ts @@ -0,0 +1,149 @@ +import { createRequire } from "node:module"; +import { + DEFAULT_LIMITS, + defineDocumentKind, + escapeHtml, + object, + registryForKind, + renderTree, + string, + type BlockDocument, + type Diagnostic, + type Result, +} from "@wrnexus/blocks"; +import { emailCoreBlocks } from "./blocks.ts"; + +interface AmpValidationError { + severity: string; + line: number; + col: number; + message: string; +} +const amphtmlValidator = createRequire(import.meta.url)("amphtml-validator") as { + getInstance(): Promise<{ + validateString( + value: string, + format: string, + ): { + status: string; + errors: AmpValidationError[]; + }; + }>; +}; + +export interface ValidationReport { + valid: boolean; + diagnostics: Diagnostic[]; +} +export interface EmailRenderBundle { + kind: "email"; + subject: string; + preheader?: string; + parts: { + text: { contentType: "text/plain; charset=utf-8"; content: string }; + amp?: { + contentType: "text/x-amp-html; charset=utf-8"; + content: string; + validation: ValidationReport; + }; + html: { contentType: "text/html; charset=utf-8"; content: string }; + }; + exports?: { mjml?: string }; + warnings: Diagnostic[]; + assets: string[]; +} + +export const emailKind = defineDocumentKind({ + kind: "email", + version: 1, + metadata: object({ + subject: string({ min: 1, max: 998 }), + preheader: string({ max: 500, default: "" }), + lang: string({ max: 35, default: "en" }), + }) as never, + rootTypes: ["layout.email-document"], + blocks: emailCoreBlocks, + variables: { recipientName: "string", unsubscribeUrl: "string", organizationAddress: "string" }, + breakpoints: { compact: 620 }, + capabilities: ["email-html", "plain-text", "amp-email", "mjml"], + targets: ["emailHtml", "plainText", "ampEmail", "mjml"], + limits: { ...DEFAULT_LIMITS, maxOutputBytes: 2_000_000 }, + urlSchemes: ["https", "mailto"], +}); + +const head = (document: BlockDocument, body: string, direction: "ltr" | "rtl") => + `${escapeHtml(document.meta.subject)}${document.meta.preheader ? `
${escapeHtml(document.meta.preheader)}
` : ""}${body}`; +const ampHead = (document: BlockDocument, body: string, direction: "ltr" | "rtl") => + `${body}`; + +export async function validateAmpEmail(content: string): Promise { + const validator = await amphtmlValidator.getInstance(); + const result = validator.validateString(content, "AMP4EMAIL"); + return { + valid: result.status === "PASS", + diagnostics: result.errors.map((error) => ({ + code: "amp.validation", + message: error.message, + severity: error.severity === "ERROR" ? "error" : "warning", + path: `${error.line}:${error.col}`, + })), + }; +} + +export async function renderEmail( + document: BlockDocument, + options: { + amp?: boolean; + mjml?: boolean; + locale?: string; + timezone?: string; + direction?: "ltr" | "rtl"; + } = {}, +): Promise> { + const registry = registryForKind(emailKind).freeze(); + const base = { + locale: options.locale ?? "en", + timezone: options.timezone ?? "UTC", + direction: options.direction ?? "ltr", + }; + const [htmlResult, textResult, mjmlResult] = await Promise.all([ + renderTree(document, emailKind, registry, { ...base, target: "emailHtml" }), + renderTree(document, emailKind, registry, { ...base, target: "plainText" }), + options.mjml + ? renderTree(document, emailKind, registry, { ...base, target: "mjml" }) + : Promise.resolve(undefined), + ]); + if (!htmlResult.ok) return htmlResult; + if (!textResult.ok) return textResult; + const warnings: Diagnostic[] = [...htmlResult.value.warnings, ...textResult.value.warnings]; + const bundle: EmailRenderBundle = { + kind: "email", + subject: String(document.meta.subject), + preheader: String(document.meta.preheader || "") || undefined, + parts: { + text: { + contentType: "text/plain; charset=utf-8", + content: textResult.value.content.trimEnd() + "\n", + }, + html: { + contentType: "text/html; charset=utf-8", + content: head(document, htmlResult.value.content, base.direction), + }, + }, + warnings, + assets: [], + }; + if (mjmlResult?.ok) bundle.exports = { mjml: mjmlResult.value.content }; + if (options.amp) { + const amp = await renderTree(document, emailKind, registry, { ...base, target: "ampEmail" }); + if (amp.ok) { + const content = ampHead(document, amp.value.content, base.direction); + const validation = await validateAmpEmail(content); + if (validation.valid) + bundle.parts.amp = { contentType: "text/x-amp-html; charset=utf-8", content, validation }; + else warnings.push(...validation.diagnostics); + } else + warnings.push(...amp.errors.map((error) => ({ ...error, severity: "warning" as const }))); + } + return { ok: true, value: bundle }; +} diff --git a/packages/email-renderer/src/index.ts b/packages/email-renderer/src/index.ts new file mode 100644 index 00000000..ab16dff0 --- /dev/null +++ b/packages/email-renderer/src/index.ts @@ -0,0 +1,3 @@ +export * from "./blocks.ts"; +export * from "./email.ts"; +export * from "./mjml.ts"; diff --git a/packages/email-renderer/src/mjml.ts b/packages/email-renderer/src/mjml.ts new file mode 100644 index 00000000..abb95592 --- /dev/null +++ b/packages/email-renderer/src/mjml.ts @@ -0,0 +1,278 @@ +import { createRequire } from "node:module"; +import { + DEFAULT_LIMITS, + type BlockDocument, + type BlockNode, + type Diagnostic, + type Result, +} from "@wrnexus/blocks"; + +interface MjmlAst { + tagName: string; + attributes?: Record; + children?: MjmlAst[]; + content?: string; +} +interface MjmlCompileError { + line?: number; + message: string; + tagName?: string; +} +const require = createRequire(import.meta.url); +const parseMjml = require("mjml-parser-xml") as ( + value: string, + options?: Record, +) => MjmlAst; +const compileMjml = require("mjml") as ( + value: string, + options?: Record, +) => Promise<{ html: string; errors: MjmlCompileError[] }>; + +const supported = new Set([ + "mjml", + "mj-head", + "mj-title", + "mj-preview", + "mj-attributes", + "mj-breakpoint", + "mj-body", + "mj-wrapper", + "mj-section", + "mj-group", + "mj-column", + "mj-text", + "mj-image", + "mj-button", + "mj-divider", + "mj-spacer", +]); +const containers = new Set([ + "mjml", + "mj-body", + "mj-wrapper", + "mj-section", + "mj-group", + "mj-column", +]); + +export interface MjmlDiagnostic extends Diagnostic { + line?: number; + column?: number; + element?: string; + attribute?: string; +} + +export function importMjml( + source: string, + options: { id(): string; subject: string; preheader?: string }, +): Result { + if (new TextEncoder().encode(source).byteLength > DEFAULT_LIMITS.maxBytes) + return { + ok: false, + errors: [{ code: "mjml.size", message: "MJML source is too large.", severity: "error" }], + }; + if (/ { + if (!supported.has(node.tagName)) { + errors.push({ + code: "mjml.component", + message: `Unsupported MJML component ${node.tagName}.`, + severity: "error", + element: node.tagName, + }); + return []; + } + const children = (node.children ?? []).flatMap(convert); + if (node.tagName === "mjml" || node.tagName === "mj-head" || node.tagName === "mj-body") + return children; + if ( + node.tagName === "mj-title" || + node.tagName === "mj-preview" || + node.tagName === "mj-attributes" || + node.tagName === "mj-breakpoint" + ) + return []; + const id = options.id(); + const a = node.attributes ?? {}; + if (["mj-wrapper", "mj-section", "mj-group"].includes(node.tagName)) + return [ + { + id, + type: "layout.section", + version: 1, + props: { background: a["background-color"] ?? "#ffffff", padding: a.padding ?? "24px" }, + styles: {}, + slots: { content: children }, + }, + ]; + if (node.tagName === "mj-column") + return [ + { + id, + type: "layout.column", + version: 1, + props: { width: a.width ?? "100%" }, + styles: {}, + slots: { content: children }, + }, + ]; + if (node.tagName === "mj-text") + return [ + { + id, + type: "content.text", + version: 1, + props: { text: node.content ?? "" }, + styles: {}, + slots: {}, + }, + ]; + if (node.tagName === "mj-image") + return [ + { + id, + type: "content.image", + version: 1, + props: { + src: a.src ?? "", + alt: a.alt ?? "", + width: (a.width ?? "600").replace(/px$/, ""), + height: (a.height ?? "300").replace(/px$/, ""), + decorative: false, + }, + styles: {}, + slots: {}, + }, + ]; + if (node.tagName === "mj-button") + return [ + { + id, + type: "content.button", + version: 1, + props: { label: node.content ?? "Button", href: a.href ?? "" }, + styles: {}, + slots: {}, + }, + ]; + if (node.tagName === "mj-divider") + return [{ id, type: "content.divider", version: 1, props: {}, styles: {}, slots: {} }]; + if (node.tagName === "mj-spacer") + return [ + { + id, + type: "content.spacer", + version: 1, + props: { height: (a.height ?? "24").replace(/px$/, "") }, + styles: {}, + slots: {}, + }, + ]; + return containers.has(node.tagName) ? children : []; + }; + const content = convert(ast); + if (errors.length) return { ok: false, errors }; + const root: BlockNode = { + id: options.id(), + type: "layout.email-document", + version: 1, + props: { width: "600", background: "#f3f4f6" }, + styles: {}, + slots: { + content: content.map((node) => + node.type === "layout.column" + ? { + id: options.id(), + type: "layout.columns", + version: 1, + props: {}, + styles: {}, + slots: { columns: [node] }, + } + : node, + ), + }, + }; + return { + ok: true, + value: { + schemaVersion: 1, + kind: "email", + meta: { subject: options.subject, preheader: options.preheader ?? "", lang: "en" }, + theme: { tokens: {} }, + root, + }, + }; +} + +export async function validateMjml(source: string): Promise { + try { + const result = await compileMjml(source, { + validationLevel: "strict", + ignoreIncludes: true, + minify: false, + }); + return (result.errors ?? []).map((error) => ({ + code: "mjml.validation", + message: error.message, + severity: "error", + line: error.line, + element: error.tagName, + })); + } catch (error) { + const details = + error && typeof error === "object" && "errors" in error + ? (( + error as { + errors?: Array<{ + line?: number; + message?: string; + formattedMessage?: string; + tagName?: string; + }>; + } + ).errors ?? []) + : []; + if (details.length) + return details.map((detail) => ({ + code: "mjml.validation", + message: detail.message ?? detail.formattedMessage ?? "Invalid MJML.", + severity: "error", + line: detail.line, + element: detail.tagName, + })); + return [ + { + code: "mjml.validation", + message: error instanceof Error ? error.message : "Invalid MJML.", + severity: "error", + }, + ]; + } +} diff --git a/packages/email-renderer/test/email.test.ts b/packages/email-renderer/test/email.test.ts new file mode 100644 index 00000000..328a85c0 --- /dev/null +++ b/packages/email-renderer/test/email.test.ts @@ -0,0 +1,94 @@ +import { describe, expect, test } from "bun:test"; +import { + renderEmail, + validateAmpEmail, + validateMjml, + importMjml, + type EmailRenderBundle, +} from "../src/index.ts"; +import type { BlockDocument } from "@wrnexus/blocks"; + +const document: BlockDocument = { + schemaVersion: 1, + kind: "email", + meta: { subject: "Welcome", preheader: "Preview", lang: "en" }, + theme: { tokens: {} }, + root: { + id: "root", + type: "layout.email-document", + version: 1, + props: { width: "600", background: "#f3f4f6" }, + styles: {}, + slots: { + content: [ + { + id: "section", + type: "layout.section", + version: 1, + props: { background: "#ffffff", padding: "24px" }, + styles: {}, + slots: { + content: [ + { + id: "heading", + type: "content.heading", + version: 1, + props: { text: "Hello ", level: "1" }, + styles: {}, + slots: {}, + }, + { + id: "button", + type: "content.button", + version: 1, + props: { label: "Open", href: "https://example.com/" }, + styles: {}, + slots: {}, + }, + ], + }, + }, + ], + }, + }, +}; + +describe("email renderer", () => { + test("renders deterministic HTML, text, AMP, and MJML", async () => { + const first = await renderEmail(document, { amp: true, mjml: true }); + const second = await renderEmail(document, { amp: true, mjml: true }); + expect(first).toEqual(second); + expect(first.ok).toBe(true); + if (!first.ok) return; + const value: EmailRenderBundle = first.value; + expect(value.parts.html.content).toContain("Hello <world>"); + expect(value.parts.text.content).toContain("Hello "); + expect(value.parts.amp?.validation.valid).toBe(true); + expect(value.exports?.mjml).toContain(""); + }); + + test("validates AMP output", async () => { + expect((await validateAmpEmail("")).valid).toBe(false); + }); + + test("rejects unsafe MJML and imports supported content", () => { + expect( + importMjml("x", { + id: () => crypto.randomUUID(), + subject: "x", + }).ok, + ).toBe(false); + let n = 0; + const imported = importMjml( + "Hello", + { id: () => `n${++n}`, subject: "Imported" }, + ); + expect(imported.ok).toBe(true); + }); + + test("strict MJML validation catches invalid nesting", async () => { + expect( + (await validateMjml("x")).length, + ).toBeGreaterThan(0); + }); +}); diff --git a/packages/email-renderer/tsconfig.json b/packages/email-renderer/tsconfig.json new file mode 100644 index 00000000..e8b02ca4 --- /dev/null +++ b/packages/email-renderer/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts", "test/**/*.ts"] +}