diff --git a/package-lock.json b/package-lock.json index 457ec5da..95338564 100644 --- a/package-lock.json +++ b/package-lock.json @@ -50,6 +50,10 @@ "wp-types": "^3.65.0" } }, + "node_modules/@10up/build": { + "resolved": "packages/build", + "link": true + }, "node_modules/@10up/component-accordion": { "resolved": "projects/library", "link": true @@ -3384,433 +3388,481 @@ "node": ">=16" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "node": ">=18" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.11.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", - "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" + "node": ">=18" } }, - "node_modules/@eslint/eslintrc/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" } }, - "node_modules/@floating-ui/core": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", - "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", - "dependencies": { - "@floating-ui/utils": "^0.2.8" + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@floating-ui/dom": { - "version": "1.6.11", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", - "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", - "dependencies": { - "@floating-ui/core": "^1.6.0", - "@floating-ui/utils": "^0.2.8" + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@floating-ui/react-dom": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", - "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=10.10.0" + "node": ">=18" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" + "node": ">=18" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=18" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead" + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6" + "node": ">=18" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dependencies": { - "@types/yargs-parser": "*" + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", "dependencies": { - "color-convert": "^2.0.1" + "eslint-visitor-keys": "^3.3.0" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "engines": { - "node": ">=10" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/console/node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, + "node_modules/@eslint-community/regexpp": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core/node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@jest/core/node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dependencies": { - "@types/yargs-parser": "*" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@jest/core/node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dependencies": { - "type-fest": "^0.21.3" + "type-fest": "^0.20.2" }, "engines": { "node": ">=8" @@ -3819,155 +3871,271 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "argparse": "^2.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "brace-expansion": "^1.1.7" }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/core/node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@jest/core/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node_modules/@floating-ui/core": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.8.tgz", + "integrity": "sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==", + "dependencies": { + "@floating-ui/utils": "^0.2.8" } }, - "node_modules/@jest/core/node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "node_modules/@floating-ui/dom": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.11.tgz", + "integrity": "sha512-qkMCxSR24v2vGkhYDo/UzxfJN3D4syqSjyuTFz6C7XcpU1pASPRieNI0Kj5VP3/503mOfYiGY891ugBX1GlABQ==", "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@floating-ui/core": "^1.6.0", + "@floating-ui/utils": "^0.2.8" } }, - "node_modules/@jest/core/node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "node_modules/@floating-ui/react-dom": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.2.tgz", + "integrity": "sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@floating-ui/dom": "^1.0.0" }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, - "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "engines": { - "node": ">=10" + "node_modules/@floating-ui/utils": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", + "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "engines": { + "node": ">=10.10.0" } }, - "node_modules/@jest/core/node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/@jest/core/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "node_modules/@jest/core/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "engines": { - "node": ">=10" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "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" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jest/core/node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "dev": true, + "license": "MIT", "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "ansi-regex": "^6.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/@jest/environment": { + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dependencies": { - "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.7.0" + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@jest/types": { + "node_modules/@jest/console/node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", @@ -3983,7 +4151,7 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/environment/node_modules/@types/yargs": { + "node_modules/@jest/console/node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", @@ -3991,7 +4159,7 @@ "@types/yargs-parser": "*" } }, - "node_modules/@jest/environment/node_modules/ansi-styles": { + "node_modules/@jest/console/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -4005,7 +4173,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/environment/node_modules/chalk": { + "node_modules/@jest/console/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -4020,46 +4188,94 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/expect": { + "node_modules/@jest/console/node_modules/jest-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/expect-utils": { + "node_modules/@jest/core": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } } }, - "node_modules/@jest/fake-timers": { + "node_modules/@jest/core/node_modules/@jest/transform": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dependencies": { + "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { + "node_modules/@jest/core/node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", @@ -4075,7 +4291,7 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/fake-timers/node_modules/@types/yargs": { + "node_modules/@jest/core/node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", @@ -4083,7 +4299,21 @@ "@types/yargs-parser": "*" } }, - "node_modules/@jest/fake-timers/node_modules/ansi-styles": { + "node_modules/@jest/core/node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jest/core/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -4097,7 +4327,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/fake-timers/node_modules/chalk": { + "node_modules/@jest/core/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -4112,7 +4342,39 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/fake-timers/node_modules/jest-util": { + "node_modules/@jest/core/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/core/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", @@ -4128,21 +4390,78 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals": { + "node_modules/@jest/core/node_modules/pretty-format": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core/node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/@jest/core/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/@jest/core/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@jest/core/node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dependencies": { + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", + "@types/node": "*", "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@jest/types": { + "node_modules/@jest/environment/node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", @@ -4158,7 +4477,7 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/globals/node_modules/@types/yargs": { + "node_modules/@jest/environment/node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", @@ -4166,7 +4485,7 @@ "@types/yargs-parser": "*" } }, - "node_modules/@jest/globals/node_modules/ansi-styles": { + "node_modules/@jest/environment/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -4180,7 +4499,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/globals/node_modules/chalk": { + "node_modules/@jest/environment/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -4195,74 +4514,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/reporters": { + "node_modules/@jest/expect": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dependencies": { + "jest-get-type": "^29.6.3" }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/@jest/transform": { + "node_modules/@jest/fake-timers": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dependencies": { - "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/@jest/types": { + "node_modules/@jest/fake-timers/node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", @@ -4278,7 +4569,7 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/@types/yargs": { + "node_modules/@jest/fake-timers/node_modules/@types/yargs": { "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", @@ -4286,7 +4577,7 @@ "@types/yargs-parser": "*" } }, - "node_modules/@jest/reporters/node_modules/ansi-styles": { + "node_modules/@jest/fake-timers/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", @@ -4300,7 +4591,7 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/chalk": { + "node_modules/@jest/fake-timers/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", @@ -4315,76 +4606,279 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@jest/reporters/node_modules/jest-haste-map": { + "node_modules/@jest/fake-timers/node_modules/jest-util": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dependencies": { "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "picomatch": "^2.2.3" }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/@jest/reporters/node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/jest-util": { + "node_modules/@jest/globals": { "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/reporters/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/@jest/reporters/node_modules/write-file-atomic": { - "version": "4.0.2", + "node_modules/@jest/globals/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/globals/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/globals/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/reporters/node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@jest/reporters/node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/@jest/reporters/node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/@jest/reporters/node_modules/write-file-atomic": { + "version": "4.0.2", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", "dependencies": { @@ -4759,9 +5253,10 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -6044,6 +6539,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", "integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", + "optional": true, "dependencies": { "detect-libc": "^1.0.3", "is-glob": "^4.0.3", @@ -6300,6 +6796,17 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", @@ -7242,9 +7749,10 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "node_modules/@types/ws": { - "version": "8.5.12", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", - "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -8052,8 +8560,94 @@ "webpack": "^4.30.0 || ^5.20.2" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dependencies": { @@ -9438,6 +10032,16 @@ "inherits": "2.0.3" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -10638,6 +11242,23 @@ "upper-case-first": "^2.0.2" } }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -10682,6 +11303,16 @@ "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", @@ -11571,9 +12202,10 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -12117,6 +12749,16 @@ } } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -12333,6 +12975,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "optional": true, "bin": { "detect-libc": "bin/detect-libc.js" }, @@ -12550,6 +13193,13 @@ "stream-shift": "^1.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -12876,6 +13526,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -13778,6 +14468,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -13972,6 +14672,16 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/expect/node_modules/@jest/types": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", @@ -14570,6 +15280,23 @@ "node": ">=0.10.0" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -15685,9 +16412,10 @@ } }, "node_modules/immutable": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", - "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", + "license": "MIT" }, "node_modules/import-fresh": { "version": "3.3.0", @@ -16682,6 +17410,22 @@ "node": ">= 0.4" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/javascript-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/javascript-stringify/-/javascript-stringify-2.1.0.tgz", @@ -19515,92 +20259,350 @@ "node": ">= 0.8.0" } }, - "node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, "engines": { - "node": ">=14" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" - }, - "node_modules/lint-staged": { - "version": "15.2.10", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz", - "integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==", - "dev": true, - "dependencies": { - "chalk": "~5.3.0", - "commander": "~12.1.0", - "debug": "~4.3.6", - "execa": "~8.0.1", - "lilconfig": "~3.1.2", - "listr2": "~8.2.4", - "micromatch": "~4.0.8", - "pidtree": "~0.6.0", - "string-argv": "~0.3.2", - "yaml": "~2.5.0" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=18.12.0" + "node": ">= 12.0.0" }, "funding": { - "url": "https://opencollective.com/lint-staged" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/listr2": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", - "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", - "dev": true, - "dependencies": { - "cli-truncate": "^4.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=18.0.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.11.5" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=8.9.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss/node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/lint-staged": { + "version": "15.2.10", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.10.tgz", + "integrity": "sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==", + "dev": true, + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.8", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/listr2": { + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", + "dev": true, + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { "p-locate": "^4.1.0" }, "engines": { @@ -19815,6 +20817,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -19851,6 +20860,28 @@ "lz-string": "bin/bin.js" } }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", @@ -20389,6 +21420,16 @@ "node": ">= 6" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -20535,15 +21576,16 @@ "peer": true }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -20620,7 +21662,8 @@ "node_modules/node-addon-api": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==" + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "optional": true }, "node_modules/node-fetch": { "version": "2.7.0", @@ -21321,6 +22364,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/package-manager-detector": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-0.2.2.tgz", @@ -21514,6 +22564,30 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -21533,6 +22607,16 @@ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==" }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pbkdf2": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.2.tgz", @@ -21720,9 +22804,9 @@ } }, "node_modules/postcss": { - "version": "8.4.47", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", - "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", "funding": [ { "type": "opencollective", @@ -21737,9 +22821,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", - "picocolors": "^1.1.0", + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, "engines": { @@ -24155,13 +25240,13 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.80.3", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.80.3.tgz", - "integrity": "sha512-ptDWyVmDMVielpz/oWy3YP3nfs7LpJTHIJZboMVs8GEC9eUmtZTZhMHlTW98wY4aEorDfjN38+Wr/XjskFWcfA==", + "version": "1.97.2", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.97.2.tgz", + "integrity": "sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==", + "license": "MIT", "dependencies": { - "@parcel/watcher": "^2.4.1", "chokidar": "^4.0.0", - "immutable": "^4.0.0", + "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "bin": { @@ -24169,6 +25254,9 @@ }, "engines": { "node": ">=14.0.0" + }, + "optionalDependencies": { + "@parcel/watcher": "^2.4.1" } }, "node_modules/sass-loader": { @@ -24677,6 +25765,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -25223,9 +26318,16 @@ "node": ">=8" } }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==" }, "node_modules/static-extend": { @@ -25279,9 +26381,10 @@ } }, "node_modules/std-env": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz", - "integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==" + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "license": "MIT" }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", @@ -25397,6 +26500,39 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string-width/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -25528,6 +26664,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -26423,6 +27573,95 @@ "node": ">=0.6.0" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -26779,9 +28018,10 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -28569,6 +29809,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -28615,6 +29872,73 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", @@ -28865,6 +30189,1047 @@ "jest": "^29.7.0" } }, + "packages/build": { + "name": "@10up/build", + "version": "1.0.0-alpha.20", + "license": "MIT", + "dependencies": { + "@csstools/postcss-global-data": "^3.0.0", + "esbuild": "^0.24.0", + "fast-glob": "^3.3.2", + "lightningcss": "^1.28.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-custom-media": "^11.0.6", + "postcss-import": "^16.1.0", + "postcss-mixins": "^11.0.3", + "react-refresh": "^0.14.2", + "sass": "^1.82.0", + "ws": "^8.18.0" + }, + "bin": { + "10up-build": "bin/10up-build.js" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@types/ws": "^8.5.13", + "@vitest/coverage-v8": "^2.1.8", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/build/node_modules/@csstools/cascade-layer-name-parser": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", + "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "packages/build/node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "packages/build/node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "packages/build/node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "packages/build/node_modules/@csstools/postcss-global-data": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/postcss-global-data/-/postcss-global-data-3.1.0.tgz", + "integrity": "sha512-qfS0bUxBukuyxEyxTTZG+px2xwAQPf7Qk6B7lFdjWnovb/O6h0t3sxrVY81nJLh7z0KvEMhjxTURNtEmOrADpQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "packages/build/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/build/node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "packages/build/node_modules/@vitest/coverage-v8": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "packages/build/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "dev": true, + "license": "ISC", + "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" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/build/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "packages/build/node_modules/postcss-custom-media": { + "version": "11.0.6", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", + "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/cascade-layer-name-parser": "^2.0.5", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.4" + } + }, + "packages/build/node_modules/postcss-import": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.1.tgz", + "integrity": "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "packages/build/node_modules/postcss-mixins": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/postcss-mixins/-/postcss-mixins-11.0.3.tgz", + "integrity": "sha512-HZa6DHlN7uCkp7GTFNvhpyK/Gi9+vrVG7FPl2oQdj+sXUuYo4ri9OsWBseTnvnLfWxRWOq8/VwcHcixtZPrRRg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-js": "^4.0.1", + "postcss-simple-vars": "^7.0.1", + "sugarss": "^4.0.1", + "tinyglobby": "^0.2.7" + }, + "engines": { + "node": "^18.0 || ^ 20.0 || >= 22.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "packages/build/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "packages/build/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "packages/build/node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "packages/build/node_modules/vite-node/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "packages/build/node_modules/vite-node/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "packages/build/node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "packages/build/node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "packages/build/node_modules/vitest/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "packages/build/node_modules/vitest/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "packages/build/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "packages/eslint-config": { "name": "@10up/eslint-config", "version": "4.1.4", diff --git a/packages/build/.gitignore b/packages/build/.gitignore new file mode 100644 index 00000000..dcf59413 --- /dev/null +++ b/packages/build/.gitignore @@ -0,0 +1,9 @@ +# Build output +dist/ + +# Test output directories +tests/fixtures/*/dist/ +tests/fixtures/*/dist-snapshots/ + +# Node modules +node_modules/ diff --git a/packages/build/README.md b/packages/build/README.md new file mode 100644 index 00000000..3f3acf4e --- /dev/null +++ b/packages/build/README.md @@ -0,0 +1,682 @@ +# @10up/build + +> **WARNING: This is pre-release alpha software. APIs may change without notice. Use at your own risk in production environments.** + +> Fast esbuild-powered build tool for WordPress block development + +## Overview + +`@10up/build` is a modern build tool that provides **10-100x faster builds** than webpack-based alternatives. It's designed as a drop-in replacement for 10up-toolkit's build system, using the same configuration schema while leveraging esbuild's speed. + +### Key Features + +- **Lightning Fast** - ~150ms builds vs 15-30s with webpack +- **WordPress Native** - Full support for block.json, script modules, and dependency extraction +- **Drop-in Compatible** - Uses same `package.json["10up-toolkit"]` configuration +- **Modern CSS Pipeline** - SCSS + PostCSS + lightningcss +- **Hot Module Replacement** - React Fast Refresh support for development + +## Installation + +```bash +npm install @10up/build --save-dev +``` + +## Quick Start + +```bash +# Production build +npx 10up-build build + +# Development with watch mode + HMR +npx 10up-build start + +# Watch mode only (no HMR server) +npx 10up-build watch +``` + +Or add scripts to your `package.json`: + +```json +{ + "scripts": { + "build": "10up-build build", + "start": "10up-build start", + "watch": "10up-build watch" + } +} +``` + +## Configuration + +Configuration is read from `package.json` under the `10up-toolkit` field, maintaining full compatibility with existing 10up-toolkit projects. + +### Basic Configuration + +```json +{ + "10up-toolkit": { + "entry": { + "admin": "./assets/js/admin/admin.js", + "frontend": "./assets/js/frontend/frontend.js" + }, + "paths": { + "blocksDir": "./includes/blocks/", + "srcDir": "./assets/", + "copyAssetsDir": "./assets/" + }, + "useBlockAssets": true, + "wpDependencyExternals": true + } +} +``` + +### Configuration Options + +| Option | Type | Default | Description | +|--------|------|---------|-------------| +| `entry` | `object` | `{}` | Entry points for scripts and styles | +| `moduleEntry` | `object` | `{}` | Entry points for ES modules | +| `paths.blocksDir` | `string` | `"./includes/blocks/"` | Directory containing block.json files | +| `paths.srcDir` | `string` | `"./assets/"` | Source directory for assets | +| `paths.copyAssetsDir` | `string` | `"./assets/"` | Directory for static assets to copy | +| `paths.globalStylesDir` | `string` | `"./assets/css/globals/"` | Directory for global CSS custom properties | +| `paths.globalMixinsDir` | `string` | `"./assets/css/mixins/"` | Directory for PostCSS mixins | +| `paths.blocksStyles` | `string` | `"./assets/css/blocks/"` | Directory for block-specific styles | +| `useBlockAssets` | `boolean` | `false` | Auto-detect entries from block.json files | +| `useScriptModules` | `boolean` | `false` | Enable ES module output | +| `wpDependencyExternals` | `boolean` | `true` | Externalize @wordpress/* packages and generate .asset.php | +| `loadBlockSpecificStyles` | `boolean` | `false` | Auto-enqueue block-specific stylesheets | +| `hot` | `boolean` | `false` | Enable Hot Module Replacement | +| `devServerPort` | `number` | `8887` | Port for HMR WebSocket server | +| `sourcemap` | `boolean` | `false` | Generate source maps (always on in development) | +| `externalNamespaces` | `object` | `{}` | Custom package namespaces to externalize | + +### Configuration Files + +The build tool respects these configuration files in your project root: + +| File | Purpose | +|------|---------| +| `buildfiles.config.js` | Entry point configuration (alternative to package.json) | +| `postcss.config.js` | Custom PostCSS configuration | +| `tsconfig.json` | TypeScript configuration (used by esbuild) | + +#### buildfiles.config.js + +```javascript +module.exports = { + admin: './assets/js/admin/admin.js', + frontend: './assets/js/frontend/frontend.js', + 'admin-style': './assets/css/admin/admin-style.scss', +}; +``` + +## Block Development + +### Automatic Entry Detection + +When `useBlockAssets: true` is enabled, the build tool automatically detects entry points from `block.json` files: + +```json +{ + "name": "my-plugin/my-block", + "editorScript": "file:./index.js", + "editorStyle": "file:./editor.css", + "style": "file:./style.css", + "viewScript": "file:./view.js", + "viewScriptModule": "file:./view-module.js" +} +``` + +The tool will: +1. Find source files (`.ts`, `.tsx`, `.scss` variants) +2. Compile them to the appropriate output format +3. Transform the `block.json` to reference compiled files +4. Generate `.asset.php` files with dependencies + +### Supported block.json Fields + +| Field | Output Format | Asset PHP | +|-------|---------------|-----------| +| `script` | IIFE (.js) | Yes | +| `editorScript` | IIFE (.js) | Yes | +| `viewScript` | IIFE (.js) | Yes | +| `scriptModule` | ESM (.js) | Yes (type: module) | +| `viewScriptModule` | ESM (.js) | Yes (type: module) | +| `style` | CSS (.css) | No | +| `editorStyle` | CSS (.css) | No | +| `viewStyle` | CSS (.css) | No | + +### block.json Transformation + +Source files are automatically transformed: +- `.ts` / `.tsx` → `.js` +- `.scss` / `.sass` → `.css` +- A `version` hash is added for cache busting + +## WordPress Dependency Extraction + +The build tool intelligently handles WordPress packages - some are externalized (loaded from WordPress globals), while others are bundled directly. + +### How It Works + +1. **Detection**: Imports from `@wordpress/*` packages are detected during bundling +2. **Decision**: Each package is checked for its `wpScript` flag to determine handling: + - `wpScript: true` → Externalized (loaded from WordPress globals) + - `wpScript: false` → Bundled into your output (e.g., `@wordpress/icons`, `@wordpress/dataviews`) +3. **Virtual Modules**: For IIFE builds, externalized packages use virtual modules to avoid `require()` calls +4. **Asset Generation**: `.asset.php` files are created with dependency arrays +5. **Subpath Support**: Subpath exports like `@wordpress/dataviews/wp` inherit their parent package's behavior + +### Cache File + +For CI environments or when packages aren't installed locally, you can use a `.wp-scripts-cache.json` file: + +```bash +# Generate the cache file +10up-build cache-wp-scripts +``` + +This creates a cache mapping each `@wordpress/*` package to its `wpScript` value, allowing the build to work without optional dependencies installed. + +### Output Format + +**Regular Scripts (IIFE):** +```php + array('wp-blocks', 'wp-element', 'wp-i18n'), + 'version' => 'a1b2c3d4e5f6' +); +``` + +**ES Modules:** +```php + array('@wordpress/interactivity'), + 'version' => 'a1b2c3d4e5f6', + 'type' => 'module' +); +``` + +### Vendor Externals + +These packages are always externalized: + +| Package | Global | Handle | +|---------|--------|--------| +| `react` | `React` | `react` | +| `react-dom` | `ReactDOM` | `react-dom` | +| `react/jsx-runtime` | `ReactJSXRuntime` | `react-jsx-runtime` | +| `lodash` | `lodash` | `lodash` | +| `lodash-es` | `lodash` | `lodash` | +| `moment` | `moment` | `moment` | +| `jquery` | `jQuery` | `jquery` | + +### Custom External Namespaces + +Externalize custom package namespaces (e.g., WooCommerce): + +```json +{ + "10up-toolkit": { + "externalNamespaces": { + "@woocommerce": { + "global": "wc", + "handlePrefix": "wc" + }, + "@my-plugin": { + "global": "myPlugin", + "handlePrefix": "my-plugin" + } + } + } +} +``` + +This transforms: +- `@woocommerce/components` → `wc.components` (global) / `wc-components` (handle) +- `@my-plugin/utils` → `myPlugin.utils` (global) / `my-plugin-utils` (handle) + +## CSS Pipeline + +The CSS pipeline combines multiple tools for maximum compatibility and performance: + +``` +SCSS → Sass → PostCSS → lightningcss → Output +``` + +### SCSS Support + +Full Sass/SCSS support with: +- `@import` and `@use` statements +- Nested selectors +- Variables and mixins +- All Sass features + +### PostCSS Plugins + +The following PostCSS plugins are applied: + +1. **postcss-import** - Inline `@import` statements +2. **@csstools/postcss-global-data** - Inject global CSS custom properties +3. **postcss-custom-media** - Transform custom media queries +4. **postcss-mixins** - CSS mixins support + +### Global Styles + +Place global CSS files (custom properties, custom media queries) in `assets/css/globals/`: + +```css +/* assets/css/globals/breakpoints.css */ +@custom-media --bp-small (min-width: 600px); +@custom-media --bp-medium (min-width: 900px); +@custom-media --bp-large (min-width: 1200px); + +:root { + --color-primary: #0073aa; + --spacing-unit: 8px; +} +``` + +These are automatically injected into all stylesheets. + +### CSS Mixins + +Place mixin files in `assets/css/mixins/`: + +```css +/* assets/css/mixins/typography.css */ +@define-mixin heading { + font-family: var(--font-heading); + font-weight: 700; + line-height: 1.2; +} +``` + +Use in your stylesheets: + +```css +.title { + @mixin heading; + font-size: 2rem; +} +``` + +### lightningcss + +Final CSS processing with lightningcss provides: +- CSS minification (production only) +- Vendor prefixing +- Modern CSS syntax transformation +- Optimized output + +## Development Mode + +### Watch Mode + +```bash +10up-build start +``` + +Features: +- Fast incremental rebuilds (~50ms) +- WebSocket server for HMR notifications +- Automatic browser refresh on changes +- React Fast Refresh support (placeholder) + +### HMR Configuration + +```json +{ + "10up-toolkit": { + "hot": true, + "devServerPort": 8887 + } +} +``` + +## CLI Reference + +### Commands + +| Command | Description | +|---------|-------------| +| `10up-build build` | Production build (default) | +| `10up-build start` | Development mode with HMR | +| `10up-build watch` | Watch mode without HMR | +| `10up-build sync-wp-deps` | Scan source files and install @wordpress/* as optional dependencies | +| `10up-build update-wp-deps` | Update all @wordpress/* optional dependencies to a new version tag | +| `10up-build list-wp-deps` | List installed @wordpress/* dependencies | + +### Options + +| Option | Description | +|--------|-------------| +| `--help`, `-h` | Show help message | +| `--version`, `-v` | Show version number | +| `--hot` | Enable hot reload | +| `--port=` | HMR server port | +| `--tag=` | WordPress version tag for dependency commands (auto-detects latest if not specified) | +| `--dry-run` | Show what would be done without making changes | + +## WordPress Dependency Management + +The build tool includes commands to manage `@wordpress/*` packages as optional dependencies. This provides better TypeScript support and IDE autocompletion while keeping the packages external at runtime (loaded from WordPress globals). + +### Why Optional Dependencies? + +When `@wordpress/*` packages are installed as optional dependencies: +- **TypeScript/IDE support**: Full type definitions and autocompletion +- **No bundle bloat**: Packages are still externalized (loaded from WordPress) +- **Version alignment**: Match your target WordPress version exactly +- **Dependency tracking**: Clear visibility of which packages your code uses + +### Syncing Dependencies + +Scan your source files for `@wordpress/*` imports and install them: + +```bash +# Auto-detect latest WordPress version from API +10up-build sync-wp-deps + +# Use a specific WordPress version tag +10up-build sync-wp-deps --tag=wp-6.8 + +# Preview changes without installing +10up-build sync-wp-deps --dry-run +``` + +**Example output:** +``` +10up-build - Sync WordPress Dependencies + +Fetching latest WordPress version... +Using tag: wp-6.9 + +Scanning for @wordpress/* imports... +Found 7 @wordpress packages: + + • @wordpress/block-editor + • @wordpress/blocks + • @wordpress/components + • @wordpress/data + • @wordpress/i18n + • @wordpress/icons + • @wordpress/interactivity + +Packages to install with tag wp-6.9: + + @wordpress/icons@wp-6.9 + + @wordpress/interactivity@wp-6.9 + +Already installed (5 packages): + • @wordpress/block-editor@^15.6.8 + ... +``` + +### Monorepo Support + +For monorepos, use `--workspace-scan` to scan all workspaces and install dependencies at the root level. This is much faster than installing in each workspace individually: + +```bash +# From anywhere in the monorepo +10up-build sync-wp-deps --workspace-scan + +# With a specific tag +10up-build sync-wp-deps --workspace-scan --tag=wp-6.8 + +# Preview what would be installed +10up-build sync-wp-deps --workspace-scan --dry-run +``` + +**Example output:** +``` +10up-build - Sync WordPress Dependencies + +Fetching latest WordPress version... +Using tag: wp-6.9 + +Monorepo root: /path/to/monorepo + +Scanning 15 workspaces for @wordpress/* imports... + + • plugins/my-plugin: 7 packages + • plugins/another-plugin: 5 packages + • themes/my-theme: 3 packages + +Found 12 unique @wordpress packages: + + • @wordpress/block-editor + • @wordpress/blocks + • @wordpress/components + ... + +Installing at monorepo root: /path/to/monorepo + +Installing 12 packages... +✓ WordPress dependencies synced successfully! +``` + +### Updating Dependencies + +Update all `@wordpress/*` optional dependencies to a new WordPress version: + +```bash +# Update to WordPress 6.9 +10up-build update-wp-deps --tag=wp-6.9 + +# Preview changes +10up-build update-wp-deps --tag=wp-7.0 --dry-run +``` + +**Note:** WordPress npm tags follow the pattern `wp-X.Y` (major.minor only). Patch releases don't get new npm tags, so `wp-6.9` covers 6.9.0, 6.9.1, 6.9.2, etc. + +### Listing Dependencies + +View currently installed `@wordpress/*` packages and check for missing imports: + +```bash +10up-build list-wp-deps +``` + +**Example output:** +``` +10up-build - WordPress Dependencies + +Installed @wordpress packages (5): + + • @wordpress/block-editor@^15.6.8 + • @wordpress/blocks@^15.6.2 + • @wordpress/components@^30.6.4 + • @wordpress/data@^10.33.1 + • @wordpress/i18n@^6.6.1 + +Scanning source files for imports... + +Missing packages (2): + ! @wordpress/icons + ! @wordpress/interactivity + +Run 10up-build sync-wp-deps to install missing packages. +``` + +### Version Tags + +WordPress packages on npm are tagged by WordPress version: + +| WordPress Version | npm Tag | +|-------------------|---------| +| 6.9.x | `wp-6.9` | +| 6.8.x | `wp-6.8` | +| 6.7.x | `wp-6.7` | + +When no `--tag` is specified, `sync-wp-deps` automatically fetches the latest WordPress version from the WordPress.org API. + +## Output Structure + +``` +dist/ +├── js/ +│ ├── admin.js +│ ├── admin.asset.php +│ ├── frontend.js +│ └── frontend.asset.php +├── css/ +│ ├── admin-style.css +│ └── frontend-style.css +├── blocks/ +│ └── my-block/ +│ ├── block.json +│ ├── index.js +│ ├── index.asset.php +│ ├── editor.css +│ ├── style.css +│ ├── view.js +│ ├── view.asset.php +│ ├── view-module.js +│ └── view-module.asset.php +└── images/ + └── (copied static assets) +``` + +## TypeScript Support + +TypeScript is supported out of the box via esbuild: + +- `.ts` and `.tsx` files are automatically compiled +- Type checking is NOT performed (use `tsc --noEmit` separately) +- `tsconfig.json` paths and settings are respected + +### Recommended tsconfig.json + +```json +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["@wordpress/blocks", "@types/react"] + }, + "include": ["assets/**/*", "includes/**/*"] +} +``` + +## Migration from 10up-toolkit + +### Step 1: Install + +```bash +npm install @10up/build --save-dev +``` + +### Step 2: Update Scripts + +```json +{ + "scripts": { + "build": "10up-build build", + "start": "10up-build start" + } +} +``` + +### Step 3: Verify Configuration + +Your existing `10up-toolkit` configuration in `package.json` will work as-is. + +### Key Differences + +| Feature | 10up-toolkit | @10up/build | +|---------|--------------|-------------| +| Bundler | webpack | esbuild | +| Build Speed | 15-30s | ~150ms | +| HMR | webpack-dev-server | WebSocket | +| CSS Processing | PostCSS | Sass + PostCSS + lightningcss | +| Bundle Analysis | Built-in | Not yet supported | + +## Troubleshooting + +### "Could not resolve @wordpress/*" + +Ensure `wpDependencyExternals` is enabled: + +```json +{ + "10up-toolkit": { + "wpDependencyExternals": true + } +} +``` + +### "Custom media query --bp-* is not defined" + +Place your custom media definitions in `assets/css/globals/`: + +```css +@custom-media --bp-small (min-width: 600px); +``` + +### Source maps not working + +Enable source maps explicitly: + +```json +{ + "10up-toolkit": { + "sourcemap": true + } +} +``` + +### TypeScript errors not shown + +esbuild doesn't perform type checking. Run TypeScript separately: + +```bash +tsc --noEmit +``` + +## API Reference + +### Programmatic Usage + +```javascript +import { build, watch, loadConfig } from '@10up/build'; + +// Load configuration +const config = loadConfig(); + +// Run production build +const result = await build(); +console.log(`Built in ${result.duration}ms`); + +// Start watch mode +await watch({ hot: true, port: 8887 }); +``` + +### Build Result + +```typescript +interface BuildResult { + success: boolean; + duration: number; + entries: { + scripts: number; + modules: number; + styles: number; + }; + errors?: string[]; +} +``` + +## Contributing + +See the main [10up-toolkit repository](https://github.com/10up/10up-toolkit) for contribution guidelines. + +## License + +MIT diff --git a/packages/build/bin/10up-build.js b/packages/build/bin/10up-build.js new file mode 100755 index 00000000..63cc5008 --- /dev/null +++ b/packages/build/bin/10up-build.js @@ -0,0 +1,11 @@ +#!/usr/bin/env node + +/** + * 10up-build CLI + * + * esbuild-powered WordPress build tool + */ + +import('../dist/cli.js').then(({ cli }) => { + cli(process.argv.slice(2)); +}); diff --git a/packages/build/config/postcss.config.js b/packages/build/config/postcss.config.js new file mode 100644 index 00000000..7fad1a2e --- /dev/null +++ b/packages/build/config/postcss.config.js @@ -0,0 +1,36 @@ +/** + * Default PostCSS configuration for 10up-build + */ + +import { sync as glob } from 'fast-glob'; +import { resolve } from 'node:path'; + +export default ({ env }) => { + const globalStylesDir = resolve(process.cwd(), 'assets/css/globals'); + const globalMixinsDir = resolve(process.cwd(), 'assets/css/mixins'); + + const globalCssFiles = glob(`${globalStylesDir}/**/*.css`.replace(/\\/g, '/')); + const globalMixinFiles = glob(`${globalMixinsDir}/**/*.css`.replace(/\\/g, '/')); + + const config = { + plugins: { + 'postcss-import': {}, + }, + }; + + // Add global data plugin if files exist + if (globalCssFiles.length > 0) { + config.plugins['@csstools/postcss-global-data'] = { + files: globalCssFiles, + }; + } + + // Add mixins plugin if files exist + if (globalMixinFiles.length > 0) { + config.plugins['postcss-mixins'] = { + mixinsFiles: globalMixinFiles, + }; + } + + return config; +}; diff --git a/packages/build/package.json b/packages/build/package.json new file mode 100644 index 00000000..c5748b2a --- /dev/null +++ b/packages/build/package.json @@ -0,0 +1,73 @@ +{ + "name": "@10up/build", + "version": "1.0.0-alpha.20", + "description": "esbuild-powered WordPress build tool - fast builds for WordPress block development", + "keywords": [ + "wordpress", + "esbuild", + "build", + "blocks", + "gutenberg" + ], + "homepage": "https://github.com/10up/10up-toolkit#readme", + "bugs": { + "url": "https://github.com/10up/10up-toolkit/issues" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/10up/10up-toolkit.git", + "directory": "packages/build" + }, + "license": "MIT", + "author": "10up", + "type": "module", + "exports": { + ".": "./dist/index.js", + "./config": "./dist/config.js" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "bin": { + "10up-build": "bin/10up-build.js" + }, + "files": [ + "bin", + "dist", + "config" + ], + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "prepublishOnly": "npm run build" + }, + "dependencies": { + "@csstools/postcss-global-data": "^3.0.0", + "esbuild": "^0.24.0", + "fast-glob": "^3.3.2", + "lightningcss": "^1.28.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", + "postcss-custom-media": "^11.0.6", + "postcss-import": "^16.1.0", + "postcss-mixins": "^11.0.3", + "react-refresh": "^0.14.2", + "sass": "^1.82.0", + "ws": "^8.18.0" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@types/ws": "^8.5.13", + "@vitest/coverage-v8": "^2.1.8", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + }, + "engines": { + "node": ">=18.0.0" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/build/src/build.ts b/packages/build/src/build.ts new file mode 100644 index 00000000..5ad5231b --- /dev/null +++ b/packages/build/src/build.ts @@ -0,0 +1,402 @@ +/** + * Main build orchestration for 10up-build + */ + +import { mkdirSync, rmSync, existsSync } from 'node:fs'; +// path imports used in type annotations +import * as esbuild from 'esbuild'; +import pc from 'picocolors'; +import { loadConfig, getOutputDir, isProduction } from './config.js'; +import { detectEntries, getBlockSpecificStyles } from './utils/entry-detection.js'; +import { wpDependencyExtractionPlugin, clearDependencies } from './plugins/wp-dependency-extraction.js'; +import { sassPlugin } from './plugins/sass-plugin.js'; +import { processBlockJsonFiles } from './plugins/block-json.js'; +import { copyStaticAssets } from './plugins/copy-assets.js'; +import type { BuildConfig, BuildResult, DetectedEntries } from './types.js'; + +import { getExternalPatterns } from './utils/externals.js'; + +/** + * Profiling data for build phases + */ +interface BuildProfile { + configLoad: number; + entryDetection: number; + cleanOutput: number; + scripts: number; + modules: number; + styles: number; + blockJson: number; + assetCopy: number; + total: number; +} + +/** + * Profile enabled via environment variable + */ +const PROFILE_ENABLED = process.env.BUILD_PROFILE === '1' || process.env.BUILD_PROFILE === 'true'; + +/** + * Get common esbuild options + */ +function getCommonOptions(config: BuildConfig, outputDir: string, isProd: boolean): esbuild.BuildOptions { + // Get external patterns for WordPress and vendor packages + const external = config.wpDependencyExternals ? getExternalPatterns(config) : []; + + return { + bundle: true, + metafile: true, + sourcemap: config.sourcemap || !isProd, + minify: isProd, + target: ['es2020'], + external, + loader: { + '.js': 'jsx', + '.ts': 'tsx', + '.tsx': 'tsx', + '.jsx': 'jsx', + }, + jsx: 'automatic', + logLevel: 'warning', + outdir: outputDir, + // Set working directory explicitly to ensure consistent path resolution + absWorkingDir: process.cwd(), + }; +} + +/** + * Build JavaScript/TypeScript entries + */ +async function buildScripts( + entries: Record, + config: BuildConfig, + outputDir: string, + isProd: boolean, +): Promise { + if (Object.keys(entries).length === 0) { + return null; + } + + // Clear previous dependency tracking + clearDependencies(); + + const result = await esbuild.build({ + ...getCommonOptions(config, outputDir, isProd), + entryPoints: entries, + format: 'iife', + globalName: '__tenupBuild', + plugins: [ + wpDependencyExtractionPlugin(config), + sassPlugin(config, isProd), + ], + outExtension: { '.js': '.js' }, + entryNames: '[dir]/[name]', + chunkNames: 'js/chunks/[name]-[hash]', + assetNames: 'assets/[name]-[hash]', + write: true, + }); + + return result; +} + +/** + * Build ES Module entries + */ +async function buildModules( + entries: Record, + config: BuildConfig, + outputDir: string, + isProd: boolean, +): Promise { + if (Object.keys(entries).length === 0) { + return null; + } + + // Clear previous dependency tracking + clearDependencies(); + + const result = await esbuild.build({ + ...getCommonOptions(config, outputDir, isProd), + entryPoints: entries, + format: 'esm', + splitting: true, + plugins: [ + wpDependencyExtractionPlugin(config, { isModule: true }), + sassPlugin(config, isProd), + ], + outExtension: { '.js': '.js' }, + entryNames: '[dir]/[name]', + chunkNames: 'js/chunks/[name]-[hash]', + assetNames: 'assets/[name]-[hash]', + write: true, + }); + + return result; +} + +/** + * Build standalone CSS entries + */ +async function buildStyles( + entries: Record, + config: BuildConfig, + outputDir: string, + isProd: boolean, +): Promise { + if (Object.keys(entries).length === 0) { + return null; + } + + const result = await esbuild.build({ + ...getCommonOptions(config, outputDir, isProd), + entryPoints: entries, + plugins: [sassPlugin(config, isProd)], + outExtension: { '.js': '.js', '.css': '.css' }, + entryNames: '[dir]/[name]', + write: true, + // CSS-only builds still create empty JS files, we'll clean them up + bundle: true, + }); + + return result; +} + +/** + * Clean output directory + */ +function cleanOutputDir(outputDir: string): void { + if (existsSync(outputDir)) { + rmSync(outputDir, { recursive: true, force: true }); + } + mkdirSync(outputDir, { recursive: true }); +} + +/** + * Format duration for display + */ +function formatDuration(ms: number): string { + if (ms < 1000) { + return `${ms.toFixed(0)}ms`; + } + return `${(ms / 1000).toFixed(2)}s`; +} + +/** + * Format profile duration with padding for alignment + */ +function formatProfileDuration(ms: number, total: number): string { + const percentage = total > 0 ? ((ms / total) * 100).toFixed(1) : '0.0'; + const duration = formatDuration(ms); + return `${duration.padStart(8)} ${pc.dim(`(${percentage.padStart(5)}%)`)}`; +} + +/** + * Print build profile report + */ +function printProfile(profile: BuildProfile): void { + console.log(pc.cyan('\n┌─ Build Profile ─────────────────────────┐')); + + const phases: Array<{ name: string; time: number; entries?: number }> = [ + { name: 'Config loading', time: profile.configLoad }, + { name: 'Entry detection', time: profile.entryDetection }, + { name: 'Clean output', time: profile.cleanOutput }, + { name: 'Scripts (IIFE)', time: profile.scripts }, + { name: 'Modules (ESM)', time: profile.modules }, + { name: 'Styles (CSS)', time: profile.styles }, + { name: 'Block JSON', time: profile.blockJson }, + { name: 'Asset copy', time: profile.assetCopy }, + ]; + + for (const phase of phases) { + if (phase.time > 0) { + const bar = getProgressBar(phase.time, profile.total, 15); + console.log( + pc.dim('│ ') + + phase.name.padEnd(16) + + formatProfileDuration(phase.time, profile.total) + + ' ' + + bar, + ); + } + } + + console.log(pc.dim('├──────────────────────────────────────────┤')); + console.log(pc.dim('│ ') + pc.bold('Total'.padEnd(16)) + formatProfileDuration(profile.total, profile.total)); + console.log(pc.cyan('└──────────────────────────────────────────┘\n')); +} + +/** + * Generate a simple progress bar + */ +function getProgressBar(value: number, total: number, width: number): string { + const percentage = total > 0 ? value / total : 0; + const filled = Math.round(percentage * width); + const empty = width - filled; + return pc.cyan('█'.repeat(filled)) + pc.dim('░'.repeat(empty)); +} + +/** + * Main build function + */ +export async function build(customConfig?: Partial): Promise { + const startTime = performance.now(); + const profile: BuildProfile = { + configLoad: 0, + entryDetection: 0, + cleanOutput: 0, + scripts: 0, + modules: 0, + styles: 0, + blockJson: 0, + assetCopy: 0, + total: 0, + }; + + // Config loading + let phaseStart = performance.now(); + const config = { ...loadConfig(), ...customConfig }; + const outputDir = getOutputDir(config); + const isProd = isProduction(); + profile.configLoad = performance.now() - phaseStart; + + console.log(pc.cyan('\n10up-build') + pc.dim(` v1.0.0`)); + console.log(pc.dim(`Mode: ${isProd ? 'production' : 'development'}\n`)); + + try { + // Clean output directory + phaseStart = performance.now(); + cleanOutputDir(outputDir); + profile.cleanOutput = performance.now() - phaseStart; + + // Detect all entry points + phaseStart = performance.now(); + const entries: DetectedEntries = await detectEntries(config); + + // Add block-specific styles if enabled + if (config.loadBlockSpecificStyles) { + const blockStyles = getBlockSpecificStyles(config); + Object.assign(entries.styles, blockStyles); + } + profile.entryDetection = performance.now() - phaseStart; + + const totalEntries = + Object.keys(entries.scripts).length + + Object.keys(entries.modules).length + + Object.keys(entries.styles).length; + + if (totalEntries === 0) { + console.log(pc.yellow('No entry points found.')); + return { + success: true, + duration: performance.now() - startTime, + entries: { scripts: 0, modules: 0, styles: 0 }, + }; + } + + console.log(pc.dim(`Building ${totalEntries} entries...\n`)); + + // Build in parallel, tracking individual times + const buildPromises: Promise[] = []; + + // Scripts + if (Object.keys(entries.scripts).length > 0) { + buildPromises.push( + (async () => { + const start = performance.now(); + await buildScripts(entries.scripts, config, outputDir, isProd); + profile.scripts = performance.now() - start; + })(), + ); + } + + // Modules + if (Object.keys(entries.modules).length > 0) { + buildPromises.push( + (async () => { + const start = performance.now(); + await buildModules(entries.modules, config, outputDir, isProd); + profile.modules = performance.now() - start; + })(), + ); + } + + // Styles + if (Object.keys(entries.styles).length > 0) { + buildPromises.push( + (async () => { + const start = performance.now(); + await buildStyles(entries.styles, config, outputDir, isProd); + profile.styles = performance.now() - start; + })(), + ); + } + + await Promise.all(buildPromises); + + // Process block.json files + phaseStart = performance.now(); + if (config.useBlockAssets) { + await processBlockJsonFiles(config, outputDir); + } + profile.blockJson = performance.now() - phaseStart; + + // Copy static assets + phaseStart = performance.now(); + const assetCount = await copyStaticAssets(config, outputDir); + profile.assetCopy = performance.now() - phaseStart; + + // Report results + const scriptsCount = Object.keys(entries.scripts).length; + const modulesCount = Object.keys(entries.modules).length; + const stylesCount = Object.keys(entries.styles).length; + + if (scriptsCount > 0) { + console.log(pc.green('✓') + pc.dim(` Scripts: ${scriptsCount} entries`)); + } + if (modulesCount > 0) { + console.log(pc.green('✓') + pc.dim(` Modules: ${modulesCount} entries`)); + } + if (stylesCount > 0) { + console.log(pc.green('✓') + pc.dim(` Styles: ${stylesCount} entries`)); + } + if (assetCount > 0) { + console.log(pc.green('✓') + pc.dim(` Assets: ${assetCount} files copied`)); + } + + const duration = performance.now() - startTime; + profile.total = duration; + + console.log(pc.green(`\n✓ Done in ${formatDuration(duration)}`)); + + // Print profile if enabled + if (PROFILE_ENABLED) { + printProfile(profile); + } + + return { + success: true, + duration, + entries: { + scripts: scriptsCount, + modules: modulesCount, + styles: stylesCount, + }, + }; + } catch (error) { + const duration = performance.now() - startTime; + console.error(pc.red('\n✗ Build failed')); + + if (error instanceof Error) { + console.error(pc.red(error.message)); + } + + return { + success: false, + duration, + entries: { scripts: 0, modules: 0, styles: 0 }, + errors: [error instanceof Error ? error.message : String(error)], + }; + } +} + +export { loadConfig, getOutputDir, isProduction }; diff --git a/packages/build/src/cli.ts b/packages/build/src/cli.ts new file mode 100644 index 00000000..50d976a0 --- /dev/null +++ b/packages/build/src/cli.ts @@ -0,0 +1,180 @@ +/** + * CLI for 10up-build + */ + +import pc from 'picocolors'; +import { build } from './build.js'; +import { watch } from './watch.js'; +import { syncWpDeps, updateWpDeps, listWpDeps, cacheWpScripts } from './wp-deps.js'; +import type { Commands } from './types.js'; + +/** + * Parse CLI arguments + */ +function parseArgs(args: string[]): { command: string; flags: Record } { + const flags: Record = {}; + let command = 'build'; + + for (const arg of args) { + if (arg.startsWith('--')) { + const [key, value] = arg.slice(2).split('='); + flags[key] = value ?? true; + } else if (arg.startsWith('-')) { + flags[arg.slice(1)] = true; + } else if (!command || command === 'build') { + // First non-flag argument is the command + command = arg; + } + } + + return { command, flags }; +} + +/** + * Show help message + */ +function showHelp(): void { + console.log(` +${pc.cyan('10up-build')} - Fast WordPress build tool powered by esbuild + +${pc.bold('Usage:')} + 10up-build [options] + +${pc.bold('Commands:')} + build Build for production (default) + start Start development mode with watch and hot reload + watch Watch for changes without hot reload + sync-wp-deps Scan source files and install @wordpress/* as optional deps + update-wp-deps Update all @wordpress/* optional deps to a new version tag + list-wp-deps List installed @wordpress/* dependencies + cache-wp-scripts Cache wpScript flags for CI (use with --omit=optional) + +${pc.bold('Options:')} + --help, -h Show this help message + --version, -v Show version number + --hot Enable hot reload (default in start mode) + --port= HMR server port (default: 8887) + --tag= WordPress version tag (auto-detects latest if not specified) + --dry-run Show what would be done without making changes + --workspace-scan Scan all workspaces and install at monorepo root + +${pc.bold('Configuration:')} + Configure via package.json "10up-toolkit" field. + See documentation for all options. + +${pc.bold('Examples:')} + 10up-build # Production build + 10up-build start # Development with hot reload + 10up-build watch # Watch without hot reload + 10up-build sync-wp-deps # Install @wordpress deps (auto-detects latest WP) + 10up-build sync-wp-deps --tag=wp-6.8 # Use specific WP version tag + 10up-build sync-wp-deps --workspace-scan # Scan all workspaces, install at root + 10up-build update-wp-deps --tag=wp-6.9 # Update to new WP version + 10up-build list-wp-deps # Show installed @wordpress deps + 10up-build cache-wp-scripts # Cache wpScript flags for CI +`); +} + +/** + * Show version + */ +function showVersion(): void { + console.log('10up-build v1.0.0-alpha.7'); +} + +/** + * Command handlers + */ +const commands: Commands = { + build: async (_args: string[]) => { + process.env.NODE_ENV = process.env.NODE_ENV || 'production'; + const result = await build(); + process.exit(result.success ? 0 : 1); + }, + + start: async (args: string[]) => { + process.env.NODE_ENV = 'development'; + const { flags } = parseArgs(['start', ...args]); + await watch({ + hot: true, + port: flags.port ? parseInt(String(flags.port), 10) : undefined, + }); + }, + + watch: async (args: string[]) => { + process.env.NODE_ENV = 'development'; + const { flags } = parseArgs(['watch', ...args]); + await watch({ + hot: flags.hot === true, + port: flags.port ? parseInt(String(flags.port), 10) : undefined, + }); + }, + + 'sync-wp-deps': async (args: string[]) => { + const { flags } = parseArgs(['sync-wp-deps', ...args]); + await syncWpDeps({ + tag: flags.tag ? String(flags.tag) : undefined, + dryRun: flags['dry-run'] === true, + workspaceScan: flags['workspace-scan'] === true, + }); + }, + + 'update-wp-deps': async (args: string[]) => { + const { flags } = parseArgs(['update-wp-deps', ...args]); + if (!flags.tag) { + console.error(pc.red('Error: --tag is required for update-wp-deps')); + console.log(pc.dim('Example: 10up-build update-wp-deps --tag=wp-6.9')); + process.exit(1); + } + await updateWpDeps({ + tag: String(flags.tag), + dryRun: flags['dry-run'] === true, + }); + }, + + 'list-wp-deps': async () => { + await listWpDeps(); + }, + + 'cache-wp-scripts': async (args: string[]) => { + const { flags } = parseArgs(['cache-wp-scripts', ...args]); + await cacheWpScripts({ + output: flags.output ? String(flags.output) : undefined, + }); + }, +}; + +/** + * Main CLI entry point + */ +export async function cli(args: string[]): Promise { + const { command, flags } = parseArgs(args); + + // Handle help flag + if (flags.help || flags.h) { + showHelp(); + return; + } + + // Handle version flag + if (flags.version || flags.v) { + showVersion(); + return; + } + + // Get command handler + const handler = commands[command as keyof Commands]; + + if (!handler) { + console.error(pc.red(`Unknown command: ${command}`)); + console.log(pc.dim('Run "10up-build --help" for usage information.')); + process.exit(1); + } + + try { + await handler(args.slice(1)); + } catch (error) { + console.error(pc.red('Error:'), error instanceof Error ? error.message : error); + process.exit(1); + } +} diff --git a/packages/build/src/config.ts b/packages/build/src/config.ts new file mode 100644 index 00000000..16c650a7 --- /dev/null +++ b/packages/build/src/config.ts @@ -0,0 +1,255 @@ +/** + * Configuration loading for @10up/build + * + * This module handles loading and merging configuration from multiple sources: + * 1. Default configuration values + * 2. `package.json["10up-toolkit"]` field + * 3. Configuration files (buildfiles.config.js, postcss.config.js) + * + * @packageDocumentation + * @module config + */ + +import { readFileSync, existsSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fromProjectRoot, fromPackageRoot } from './utils/paths.js'; +import type { BuildConfig, PathsConfig, FilenamesConfig, PostCSSConfig } from './types.js'; + +/** + * Default paths configuration. + * + * These defaults follow the 10up-toolkit convention for WordPress + * theme and plugin development. + */ +const defaultPaths: PathsConfig = { + blocksDir: './includes/blocks/', + distDir: './dist/', + srcDir: './assets/', + copyAssetsDir: './assets/', + blocksStyles: './assets/css/blocks/', + globalStylesDir: './assets/css/globals/', + globalMixinsDir: './assets/css/mixins/', +}; + +/** + * Default filenames configuration. + * + * Uses esbuild-style placeholders for output naming. + */ +const defaultFilenames: FilenamesConfig = { + js: 'js/[name].js', + jsChunk: 'js/[name].[contenthash].chunk.js', + css: 'css/[name].css', + block: 'blocks/[name].js', + blockCSS: 'blocks/[name].css', +}; + +/** + * Default configuration values. + * + * These values are used when not overridden by user configuration. + * The configuration schema is designed to be compatible with + * 10up-toolkit's configuration format. + * + * @example + * ```typescript + * import { defaultConfig } from '@10up/build/config'; + * console.log(defaultConfig.wpDependencyExternals); // true + * ``` + */ +export const defaultConfig: BuildConfig = { + // Entry points + entry: {}, + moduleEntry: {}, + + // Paths + paths: defaultPaths, + + // Output filenames + filenames: defaultFilenames, + + // Features + useBlockAssets: false, + useScriptModules: false, + wpDependencyExternals: true, + loadBlockSpecificStyles: false, + + // Development + hot: false, + devServerPort: 8887, + devURL: '', + + // Build options + sourcemap: false, + publicPath: '/dist/', + + // External namespaces for custom packages + externalNamespaces: {}, +}; + +/** + * Load and merge configuration from all sources. + * + * Configuration is loaded in the following order (later sources override earlier): + * 1. Default configuration + * 2. `package.json["10up-toolkit"]` field + * 3. PostCSS configuration file detection + * + * @returns Merged build configuration + * + * @example + * ```typescript + * import { loadConfig } from '@10up/build'; + * + * const config = loadConfig(); + * console.log(config.paths.blocksDir); // "./includes/blocks/" + * ``` + */ +export function loadConfig(): BuildConfig { + const projectPackageJson = loadProjectPackageJson(); + const toolkitConfig = (projectPackageJson?.['10up-toolkit'] || {}) as Partial; + + // Deep merge paths + const paths: PathsConfig = { + ...defaultPaths, + ...(toolkitConfig.paths || {}), + }; + + // Deep merge filenames + const filenames: FilenamesConfig = { + ...defaultFilenames, + ...(toolkitConfig.filenames || {}), + }; + + // Merge all config + const config: BuildConfig = { + ...defaultConfig, + ...toolkitConfig, + paths, + filenames, + }; + + // Load additional config files + config.postcss = loadPostcssConfig(config); + + return config; +} + +/** + * Load the project's package.json file. + * + * Reads and parses the package.json from the current working directory. + * + * @returns Parsed package.json content, or null if not found/invalid + * + * @internal + */ +function loadProjectPackageJson(): Record | null { + const packageJsonPath = fromProjectRoot('package.json'); + + if (!existsSync(packageJsonPath)) { + return null; + } + + try { + return JSON.parse(readFileSync(packageJsonPath, 'utf8')); + } catch { + return null; + } +} + +/** + * Load PostCSS configuration. + * + * Checks for a postcss.config.js in the project root. + * If not found, uses the default configuration with global styles and mixins. + * + * @param config - Current build configuration (for path resolution) + * @returns PostCSS configuration object + * + * @internal + */ +function loadPostcssConfig(config: BuildConfig): PostCSSConfig { + // Check for project postcss.config.js + const projectPostcssPath = fromProjectRoot('postcss.config.js'); + + if (existsSync(projectPostcssPath)) { + return { configPath: projectPostcssPath }; + } + + // Use default postcss config + return { + configPath: fromPackageRoot('config/postcss.config.js'), + globalStylesDir: resolve(process.cwd(), config.paths.globalStylesDir), + globalMixinsDir: resolve(process.cwd(), config.paths.globalMixinsDir), + }; +} + +/** + * Check if a configuration file exists in the project root. + * + * @param filename - Name of the configuration file to check + * @returns True if the file exists + * + * @example + * ```typescript + * if (hasConfigFile('postcss.config.js')) { + * console.log('Using custom PostCSS configuration'); + * } + * ``` + */ +export function hasConfigFile(filename: string): boolean { + return existsSync(fromProjectRoot(filename)); +} + +/** + * Get the output directory path. + * + * Returns the absolute path to the dist directory where + * compiled assets will be written. + * + * @param config - Build configuration + * @returns Absolute path to the output directory + * + * @example + * ```typescript + * const outputDir = getOutputDir(config); + * console.log(outputDir); // "/path/to/project/dist" + * ``` + */ +export function getOutputDir(config: BuildConfig): string { + return fromProjectRoot(config.paths.distDir); +} + +/** + * Check if the build is running in production mode. + * + * Production mode is determined by `NODE_ENV=production`. + * In production mode: + * - Output is minified + * - Source maps are disabled (unless explicitly enabled) + * - Console output is optimized + * + * @returns True if NODE_ENV is "production" + * + * @example + * ```typescript + * if (isProduction()) { + * console.log('Building for production...'); + * } + * ``` + */ +export function isProduction(): boolean { + return process.env.NODE_ENV === 'production'; +} + +/** + * Check if the build is running in watch mode. + * + * Watch mode is detected from command-line arguments. + * + * @returns True if --watch flag is present or command is "start" + */ +export function isWatchMode(): boolean { + return process.argv.includes('--watch') || process.argv.includes('start'); +} diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts new file mode 100644 index 00000000..dd4106b6 --- /dev/null +++ b/packages/build/src/index.ts @@ -0,0 +1,50 @@ +/** + * 10up-build - esbuild-powered WordPress build tool + * + * @packageDocumentation + */ + +// Main exports +export { build } from './build.js'; +export { watch } from './watch.js'; +export { cli } from './cli.js'; +export { loadConfig, defaultConfig, isProduction, isWatchMode } from './config.js'; + +// Type exports +export type { + BuildConfig, + PathsConfig, + FilenamesConfig, + ExternalNamespaceConfig, + PostCSSConfig, + DetectedEntries, + BuildResult, + WatchOptions, + BlockMetadata, + DependencyInfo, +} from './types.js'; + +// Utility exports +export { detectEntries, getBlockSpecificStyles } from './utils/entry-detection.js'; +export { + resolveExternal, + isWpScriptPackage, + wpPackageToHandle, + wpPackageToGlobal, + vendorExternals, +} from './utils/externals.js'; +export { + fromProjectRoot, + fromPackageRoot, + hasProjectFile, + fileExists, + getContentHash, + getFileContentHash, + normalizePath, +} from './utils/paths.js'; + +// Plugin exports +export { wpDependencyExtractionPlugin } from './plugins/wp-dependency-extraction.js'; +export { sassPlugin } from './plugins/sass-plugin.js'; +export { transformBlockJson, processBlockJsonFiles } from './plugins/block-json.js'; +export { copyStaticAssets } from './plugins/copy-assets.js'; diff --git a/packages/build/src/plugins/block-json.ts b/packages/build/src/plugins/block-json.ts new file mode 100644 index 00000000..a697b138 --- /dev/null +++ b/packages/build/src/plugins/block-json.ts @@ -0,0 +1,208 @@ +/** + * Block JSON Plugin for esbuild + * + * Transforms and copies block.json files to the output directory. + * - Converts .ts/.tsx references to .js + * - Converts .scss/.sass references to .css + * - Adds version hash for cache busting + */ + +import { readFileSync, writeFileSync, mkdirSync, copyFileSync, existsSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import fastGlob from 'fast-glob'; +const glob = fastGlob.sync; +import { normalizePath, getFileContentHash } from '../utils/paths.js'; +import type { BuildConfig, BlockMetadata } from '../types.js'; + +/** + * Asset keys for standard JavaScript files (IIFE format) + */ +const JS_ASSET_KEYS: (keyof BlockMetadata)[] = [ + 'script', + 'editorScript', + 'viewScript', +]; + +/** + * Asset keys for ES module files + */ +const MODULE_ASSET_KEYS: (keyof BlockMetadata)[] = ['scriptModule', 'viewScriptModule']; + +/** + * Asset keys for CSS files + */ +const CSS_ASSET_KEYS: (keyof BlockMetadata)[] = ['style', 'editorStyle', 'viewStyle']; + +/** + * Transform TypeScript asset paths to JavaScript (.js) + */ +function transformTSAsset(asset: string | string[]): string | string[] { + const transform = (filePath: string): string => { + if (!filePath.startsWith('file:')) { + return filePath; + } + return filePath.replace(/\.tsx?$/, '.js').replace(/\.js$/, '.js'); + }; + + return Array.isArray(asset) ? asset.map(transform) : transform(asset); +} + +/** + * Transform TypeScript/JS asset paths to JavaScript (.js) + * ES modules use .js extension (not .mjs) for better server compatibility + */ +function transformModuleAsset(asset: string | string[]): string | string[] { + const transform = (filePath: string): string => { + if (!filePath.startsWith('file:')) { + return filePath; + } + // Convert .ts/.tsx to .js (ES modules use .js extension) + return filePath.replace(/\.tsx?$/, '.js'); + }; + + return Array.isArray(asset) ? asset.map(transform) : transform(asset); +} + +/** + * Transform SASS/SCSS asset paths to CSS + */ +function transformSassAsset(asset: string | string[]): string | string[] { + const transform = (filePath: string): string => { + if (!filePath.startsWith('file:')) { + return filePath; + } + return filePath.replace(/\.s[ac]ss$/, '.css'); + }; + + return Array.isArray(asset) ? asset.map(transform) : transform(asset); +} + +/** + * Calculate version hash from style files + */ +function calculateStyleVersion( + metadata: BlockMetadata, + blockDir: string, +): string { + const styleArrays: (string | string[])[] = []; + + if (metadata.style) styleArrays.push(metadata.style); + if (metadata.viewStyle) styleArrays.push(metadata.viewStyle); + + let combinedHash = ''; + + for (const styleField of styleArrays) { + const styles = Array.isArray(styleField) ? styleField : [styleField]; + + for (const rawStylePath of styles) { + if (!rawStylePath.startsWith('file:')) { + continue; + } + + const stylePath = rawStylePath.replace('file:', ''); + const absoluteStylePath = join(blockDir, stylePath); + + if (existsSync(absoluteStylePath)) { + combinedHash += getFileContentHash(absoluteStylePath); + } + } + } + + return combinedHash ? combinedHash.slice(0, 8) : ''; +} + +/** + * Transform block.json content + */ +export function transformBlockJson(content: string, absoluteFilename: string): string { + if (!content || content.trim() === '') { + return content; + } + + try { + const metadata: BlockMetadata = JSON.parse(content); + const blockDir = dirname(absoluteFilename); + + const newMetadata: BlockMetadata = { ...metadata }; + + // Add version hash if not present and has file: style references + if (!metadata.version) { + const versionHash = calculateStyleVersion(metadata, blockDir); + if (versionHash) { + newMetadata.version = versionHash; + } + } + + // Transform standard JS asset paths + for (const key of JS_ASSET_KEYS) { + const asset = metadata[key]; + if (asset) { + (newMetadata as Record)[key] = transformTSAsset(asset); + } + } + + // Transform ES module asset paths + for (const key of MODULE_ASSET_KEYS) { + const asset = metadata[key]; + if (asset) { + (newMetadata as Record)[key] = transformModuleAsset(asset); + } + } + + // Transform CSS asset paths + for (const key of CSS_ASSET_KEYS) { + const asset = metadata[key]; + if (asset) { + (newMetadata as Record)[key] = transformSassAsset(asset); + } + } + + return JSON.stringify(newMetadata, null, '\t'); + } catch { + // Return original content if parsing fails + return content; + } +} + +/** + * Copy and transform all block.json files to output directory + */ +export async function processBlockJsonFiles(config: BuildConfig, outputDir: string): Promise { + const blocksDir = resolve(process.cwd(), config.paths.blocksDir); + + if (!existsSync(blocksDir)) { + return; + } + + // Find all block.json files + const blockJsonFiles = glob(normalizePath(`${blocksDir}/**/block.json`), { + absolute: true, + }); + + for (const blockJsonPath of blockJsonFiles) { + // Calculate relative path from blocks directory + const relativePath = blockJsonPath.replace(blocksDir, '').replace(/^[/\\]/, ''); + const outputPath = join(outputDir, 'blocks', relativePath); + + // Ensure output directory exists + mkdirSync(dirname(outputPath), { recursive: true }); + + // Read, transform, and write + const content = readFileSync(blockJsonPath, 'utf8'); + const transformed = transformBlockJson(content, blockJsonPath); + writeFileSync(outputPath, transformed, 'utf8'); + } + + // Also copy any PHP files from blocks directory + const phpFiles = glob(normalizePath(`${blocksDir}/**/*.php`), { + absolute: true, + }); + + for (const phpPath of phpFiles) { + const relativePath = phpPath.replace(blocksDir, '').replace(/^[/\\]/, ''); + const outputPath = join(outputDir, 'blocks', relativePath); + + mkdirSync(dirname(outputPath), { recursive: true }); + copyFileSync(phpPath, outputPath); + } +} diff --git a/packages/build/src/plugins/copy-assets.ts b/packages/build/src/plugins/copy-assets.ts new file mode 100644 index 00000000..7295583e --- /dev/null +++ b/packages/build/src/plugins/copy-assets.ts @@ -0,0 +1,73 @@ +/** + * Copy Assets Plugin + * + * Copies static assets (images, fonts, etc.) to the output directory. + */ + +import { copyFileSync, mkdirSync, existsSync } from 'node:fs'; +import { dirname, join, resolve } from 'node:path'; +import fastGlob from 'fast-glob'; +const glob = fastGlob.sync; +import { normalizePath } from '../utils/paths.js'; +import type { BuildConfig } from '../types.js'; + +/** + * Asset file extensions to copy + */ +const ASSET_EXTENSIONS = [ + 'jpg', + 'jpeg', + 'png', + 'gif', + 'webp', + 'avif', + 'ico', + 'svg', + 'eot', + 'ttf', + 'woff', + 'woff2', + 'otf', +]; + +/** + * Copy static assets to output directory + */ +export async function copyStaticAssets(config: BuildConfig, outputDir: string): Promise { + const assetsDir = resolve(process.cwd(), config.paths.copyAssetsDir); + + if (!existsSync(assetsDir)) { + return 0; + } + + const pattern = `**/*.{${ASSET_EXTENSIONS.join(',')}}`; + const assetFiles = glob(normalizePath(`${assetsDir}/${pattern}`), { + absolute: true, + ignore: ['**/node_modules/**'], + }); + + let copiedCount = 0; + + for (const assetPath of assetFiles) { + // Calculate relative path from assets directory + const relativePath = assetPath.replace(assetsDir, '').replace(/^[/\\]/, ''); + const outputPath = join(outputDir, relativePath); + + // Ensure output directory exists + mkdirSync(dirname(outputPath), { recursive: true }); + + // Copy file + copyFileSync(assetPath, outputPath); + copiedCount++; + } + + return copiedCount; +} + +/** + * Check if a file is a static asset + */ +export function isAssetFile(filePath: string): boolean { + const ext = filePath.split('.').pop()?.toLowerCase(); + return ext ? ASSET_EXTENSIONS.includes(ext) : false; +} diff --git a/packages/build/src/plugins/sass-plugin.ts b/packages/build/src/plugins/sass-plugin.ts new file mode 100644 index 00000000..c55638f0 --- /dev/null +++ b/packages/build/src/plugins/sass-plugin.ts @@ -0,0 +1,190 @@ +/** + * SASS/SCSS Plugin for esbuild + * + * Compiles SCSS files using the sass package, + * then processes with PostCSS and lightningcss. + */ + +import { readFileSync } from 'node:fs'; +import { dirname, resolve, extname } from 'node:path'; +import * as sass from 'sass'; +import postcss from 'postcss'; +// @ts-expect-error - postcss-import doesn't have types +import postcssImport from 'postcss-import'; +// @ts-expect-error - postcss-mixins doesn't have types +import postcssMixins from 'postcss-mixins'; +// @ts-ignore - @csstools/postcss-global-data doesn't have types +import postcssGlobalData from '@csstools/postcss-global-data'; +// @ts-ignore - postcss-custom-media doesn't have types +import postcssCustomMedia from 'postcss-custom-media'; +import { transform, browserslistToTargets } from 'lightningcss'; +import fastGlob from 'fast-glob'; +const glob = fastGlob.sync; +import type { Plugin, OnLoadArgs, OnLoadResult } from 'esbuild'; +import { normalizePath } from '../utils/paths.js'; +import type { BuildConfig } from '../types.js'; + +/** + * Default browser targets for lightningcss + */ +const defaultTargets = browserslistToTargets([ + '> 1%', + 'last 2 versions', + 'Firefox ESR', + 'not dead', +]); + +/** + * Create the SASS plugin for esbuild + */ +export function sassPlugin(config: BuildConfig, isProduction: boolean): Plugin { + return { + name: 'sass', + + setup(build) { + // Handle .scss and .sass files + build.onLoad({ filter: /\.s[ac]ss$/ }, async (args: OnLoadArgs): Promise => { + try { + // Compile SCSS to CSS + const sassResult = sass.compile(args.path, { + loadPaths: [dirname(args.path), 'node_modules'], + sourceMap: !isProduction, + style: isProduction ? 'compressed' : 'expanded', + }); + + let css = sassResult.css; + + // Process with PostCSS + css = await processWithPostCSS(css, args.path, config); + + // Process with lightningcss for modern CSS features and minification + css = processWithLightningCSS(css, args.path, isProduction); + + return { + contents: css, + loader: 'css', + watchFiles: sassResult.loadedUrls + .filter((url) => url.protocol === 'file:') + .map((url) => url.pathname), + }; + } catch (error) { + return { + errors: [ + { + text: error instanceof Error ? error.message : String(error), + location: { file: args.path }, + }, + ], + }; + } + }); + + // Handle regular .css files (also process with PostCSS and lightningcss) + build.onLoad({ filter: /\.css$/ }, async (args: OnLoadArgs): Promise => { + // Skip if this is a CSS module or already processed + if (args.path.includes('.module.') || args.namespace !== 'file') { + return { loader: 'css' }; + } + + try { + let css = readFileSync(args.path, 'utf8'); + + // Process with PostCSS + css = await processWithPostCSS(css, args.path, config); + + // Process with lightningcss + css = processWithLightningCSS(css, args.path, isProduction); + + return { + contents: css, + loader: 'css', + }; + } catch (error) { + return { + errors: [ + { + text: error instanceof Error ? error.message : String(error), + location: { file: args.path }, + }, + ], + }; + } + }); + }, + }; +} + +/** + * Process CSS with PostCSS plugins + */ +async function processWithPostCSS( + css: string, + filePath: string, + config: BuildConfig, +): Promise { + const plugins: postcss.AcceptedPlugin[] = [postcssImport()]; + + // Add global data plugin if global styles exist + const globalStylesDir = resolve(process.cwd(), config.paths.globalStylesDir); + const globalCssFiles = glob(normalizePath(`${globalStylesDir}/**/*.css`)); + + if (globalCssFiles.length > 0) { + plugins.push(postcssGlobalData({ files: globalCssFiles })); + } + + // Add custom media plugin to transform @custom-media queries + // This must come after postcssGlobalData which makes the definitions available + plugins.push(postcssCustomMedia()); + + // Add mixins plugin if mixin files exist + const globalMixinsDir = resolve(process.cwd(), config.paths.globalMixinsDir); + const globalMixinFiles = glob(normalizePath(`${globalMixinsDir}/**/*.css`)); + + if (globalMixinFiles.length > 0) { + plugins.push(postcssMixins({ mixinsFiles: globalMixinFiles })); + } + + const result = await postcss(plugins).process(css, { + from: filePath, + to: filePath, + }); + + return result.css; +} + +/** + * Process CSS with lightningcss for modern features and minification + */ +function processWithLightningCSS(css: string, filePath: string, isProduction: boolean): string { + // Handle empty CSS (or CSS with only comments/whitespace) + const trimmed = css.replace(/\/\*[\s\S]*?\*\//g, '').trim(); + if (!trimmed) { + return isProduction ? '' : '/* empty */'; + } + + try { + const { code } = transform({ + filename: filePath, + code: Buffer.from(css), + minify: isProduction, + sourceMap: !isProduction, + targets: defaultTargets, + // Note: customMedia is handled by postcss-custom-media in the PostCSS phase + }); + + return code.toString(); + } catch (error) { + // If lightningcss fails, return the original CSS + // This can happen with certain edge cases + console.warn(`Warning: lightningcss failed for ${filePath}, using original CSS`); + return css; + } +} + +/** + * Get file extension + */ +export function isStyleFile(filePath: string): boolean { + const ext = extname(filePath).toLowerCase(); + return ['.css', '.scss', '.sass'].includes(ext); +} diff --git a/packages/build/src/plugins/wp-dependency-extraction.ts b/packages/build/src/plugins/wp-dependency-extraction.ts new file mode 100644 index 00000000..120e2819 --- /dev/null +++ b/packages/build/src/plugins/wp-dependency-extraction.ts @@ -0,0 +1,429 @@ +/** + * WordPress Dependency Extraction Plugin for esbuild + * + * Tracks @wordpress/* and vendor imports, externalizes them, + * and generates .asset.php files with dependency metadata. + * + * For ES Modules, generates a different format: + * array('@wordpress/interactivity'), 'version' => 'xxx', 'type' => 'module'); + */ + +import { writeFileSync, readFileSync, mkdirSync } from 'node:fs'; +import { dirname, resolve } from 'node:path'; +import pc from 'picocolors'; +import type { Plugin, OnResolveArgs, OnResolveResult, OnEndResult } from 'esbuild'; +import { resolveExternal, vendorExternals } from '../utils/externals.js'; +import { getContentHash } from '../utils/paths.js'; +import type { BuildConfig, DependencyInfo } from '../types.js'; + +/** + * Track which packages we've already warned about to avoid duplicate warnings + */ +const warnedPackages = new Set(); + +/** + * Warn about uninstalled @wordpress packages + * + * While packages will still be externalized (since they're available in WordPress), + * installing them provides TypeScript types and allows for better dependency tracking. + * + * @param packageName - The @wordpress/* package name + */ +function warnUninstalledPackage(packageName: string): void { + if (warnedPackages.has(packageName)) { + return; + } + warnedPackages.add(packageName); + + // Only warn once per build + if (warnedPackages.size === 1) { + console.log( + pc.yellow('\nNote: ') + + pc.dim('Some @wordpress packages are not installed locally.'), + ); + console.log( + pc.dim('For better TypeScript support and dependency tracking, consider installing them:'), + ); + console.log(pc.dim('npm install --save-optional @wordpress/blocks @wordpress/i18n ...\n')); + } +} + +/** + * Generate PHP array syntax for dependencies (IIFE/classic scripts) + */ +function generatePhpArray(deps: string[]): string { + if (deps.length === 0) { + return 'array()'; + } + const items = deps.map((d) => `'${d}'`).join(', '); + return `array(${items})`; +} + +/** + * Generate .asset.php file content for classic scripts (IIFE) + */ +function generateAssetPhp(dependencies: string[], version: string): string { + return ` ${generatePhpArray(dependencies)}, 'version' => '${version}'); +`; +} + +/** + * Generate .asset.php file content for ES Modules + * ES Modules use the package name directly (e.g., '@wordpress/interactivity') + * rather than the wp-handle format + */ +function generateModuleAssetPhp(dependencies: string[], version: string): string { + if (dependencies.length === 0) { + return ` array(), 'version' => '${version}', 'type' => 'module'); +`; + } + // For modules, dependencies are the actual package names + const items = dependencies.map((d) => `'${d}'`).join(', '); + return ` array(${items}), 'version' => '${version}', 'type' => 'module'); +`; +} + +/** + * Convert a handle back to package name for ES modules + * wp-interactivity -> @wordpress/interactivity + */ +function handleToPackageName(handle: string): string { + if (handle.startsWith('wp-')) { + return `@wordpress/${handle.slice(3)}`; + } + // For vendor packages, return as-is (react, lodash, etc.) + return handle; +} + +/** + * Normalize path for comparison (resolve and lowercase on Windows) + */ +function normalizePath(p: string): string { + return resolve(p).replace(/\\/g, '/'); +} + +/** + * Plugin options + */ +interface PluginOptions { + /** Whether this build is for ES modules (affects asset.php format) */ + isModule?: boolean; +} + +/** + * Create the WordPress Dependency Extraction plugin + * Each plugin instance has its own dependency tracking state + */ +export function wpDependencyExtractionPlugin(config: BuildConfig, options: PluginOptions = {}): Plugin { + const { isModule = false } = options; + + // Track dependencies per importer file (not per entry) + // This allows us to collect transitive dependencies from bundled packages + const fileDependencies = new Map>(); + + /** + * Track a dependency for a specific file + */ + function trackDependency(importer: string, handle: string): void { + // Always resolve to absolute path for consistent matching with metafile + const normalizedImporter = normalizePath(resolve(importer)); + let deps = fileDependencies.get(normalizedImporter); + if (!deps) { + deps = new Set(); + fileDependencies.set(normalizedImporter, deps); + } + deps.add(handle); + } + + return { + name: 'wp-dependency-extraction', + + setup(build) { + + // Handle @wordpress/* imports + build.onResolve( + { filter: /^@wordpress\// }, + (args: OnResolveArgs): OnResolveResult | undefined => { + const external = resolveExternal(args.path, config) as + | (ReturnType & { _isInstalled?: boolean }) + | null; + + if (external) { + // Warn if package is not installed locally + if (external._isInstalled === false) { + warnUninstalledPackage(args.path); + } + + // Track the dependency for this importer file + if (args.importer) { + trackDependency(args.importer, external.handle); + } + + // For ES modules, use native external (import statements work) + // For IIFE, use virtual module to access global (avoid require()) + if (isModule) { + return { + path: args.path, + external: true, + }; + } + + return { + path: args.path, + namespace: 'wp-external', + }; + } + + return undefined; + }, + ); + + // For IIFE builds: provide virtual modules that access WordPress globals + // This avoids generating require() calls which don't work in browsers + build.onLoad( + { filter: /.*/, namespace: 'wp-external' }, + (args): { contents: string; loader: 'js' } => { + const external = resolveExternal(args.path, config); + const globalVar = external?.global || 'undefined'; + return { + contents: `module.exports = ${globalVar};`, + loader: 'js', + }; + }, + ); + + // Handle vendor externals (react, lodash, etc.) + const vendorFilter = new RegExp( + `^(${Object.keys(vendorExternals) + .map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + .join('|')})$`, + ); + + build.onResolve( + { filter: vendorFilter }, + (args: OnResolveArgs): OnResolveResult | undefined => { + const external = vendorExternals[args.path]; + + if (external) { + // Track the dependency + if (args.importer) { + trackDependency(args.importer, external.handle); + } + + // For ES modules, use native external + // For IIFE, use virtual module to access global + if (isModule) { + return { + path: args.path, + external: true, + }; + } + + return { + path: args.path, + namespace: 'vendor-external', + }; + } + + return undefined; + }, + ); + + // For IIFE builds: provide virtual modules for vendor externals + build.onLoad( + { filter: /.*/, namespace: 'vendor-external' }, + (args): { contents: string; loader: 'js' } => { + const external = vendorExternals[args.path]; + const globalVar = external?.global || 'undefined'; + return { + contents: `module.exports = ${globalVar};`, + loader: 'js', + }; + }, + ); + + // Handle custom namespace externals + if (config.externalNamespaces && Object.keys(config.externalNamespaces).length > 0) { + for (const [namespace, mapping] of Object.entries(config.externalNamespaces)) { + const namespaceFilter = new RegExp( + `^${namespace.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/`, + ); + + build.onResolve( + { filter: namespaceFilter }, + (args: OnResolveArgs): OnResolveResult | undefined => { + const packageName = args.path.replace(`${namespace}/`, ''); + const handle = `${mapping.handlePrefix}-${packageName}`; + + // Track the dependency + if (args.importer) { + trackDependency(args.importer, handle); + } + + // For ES modules, use native external + // For IIFE, use virtual module to access global + if (isModule) { + return { + path: args.path, + external: true, + }; + } + + return { + path: args.path, + namespace: `custom-external-${namespace}`, + }; + }, + ); + + // For IIFE builds: provide virtual modules for custom namespace externals + build.onLoad( + { filter: /.*/, namespace: `custom-external-${namespace}` }, + (args): { contents: string; loader: 'js' } => { + const packageName = args.path.replace(`${namespace}/`, ''); + // Convert to camelCase for global access + const camelName = packageName.replace(/-([a-z])/g, (_: string, letter: string) => letter.toUpperCase()); + const globalVar = `${mapping.global}.${camelName}`; + return { + contents: `module.exports = ${globalVar};`, + loader: 'js', + }; + }, + ); + } + } + + // Generate .asset.php files at the end of build + // Uses metafile to collect ALL dependencies from ALL input files in each bundle + // This properly handles transitive dependencies from bundled packages + build.onEnd(async (result): Promise => { + if (result.errors.length > 0) { + return undefined; + } + + const metafile = result.metafile; + + if (!metafile) { + return undefined; + } + + // Process each output that has an entry point + for (const [outputPath, outputMeta] of Object.entries(metafile.outputs)) { + if (!outputMeta.entryPoint) { + continue; + } + + // Skip chunks + if (outputPath.includes('/chunks/')) { + continue; + } + + // Collect ALL dependencies from ALL input files in this bundle + // This catches transitive deps from bundled packages like @10up/block-components + const allDeps = new Set(); + + for (const inputPath of Object.keys(outputMeta.inputs)) { + // Normalize input path to match our tracking + const normalizedInput = normalizePath(resolve(inputPath)); + const deps = fileDependencies.get(normalizedInput); + if (deps) { + for (const dep of deps) { + allDeps.add(dep); + } + } + } + + // Read output file content for version hash AND to scan for WP globals + let content = ''; + try { + content = readFileSync(outputPath, 'utf8'); + } catch { + // File might not exist yet, use empty content + } + const version = getContentHash(content); + + // Scan the output for WordPress dependencies from pre-bundled packages + // This catches deps from webpack-bundled packages like @10up/block-components + if (content) { + // 1. Match wp.packageName global accesses (e.g., wp.blocks, wp.blockEditor) + const wpGlobalPattern = /(?:=|,|\()\s*wp\.([a-zA-Z][a-zA-Z0-9]*)/g; + let match; + while ((match = wpGlobalPattern.exec(content)) !== null) { + const globalName = match[1]; + // Convert camelCase to kebab-case for handle (e.g., blockEditor -> block-editor) + const handle = 'wp-' + globalName.replace(/([A-Z])/g, '-$1').toLowerCase(); + allDeps.add(handle); + } + + // 2. Match webpack require patterns: __webpack_require__("@wordpress/...") + // or __webpack_require__(/*! @wordpress/... */ "@wordpress/...") + // Use [^"]* to skip the comment and match the actual module ID + const webpackRequirePattern = /__webpack_require__\([^"]*"@wordpress\/([^"]+)"/g; + while ((match = webpackRequirePattern.exec(content)) !== null) { + const packageName = match[1]; + const handle = 'wp-' + packageName; + allDeps.add(handle); + } + + // 3. Also match __webpack_require__.n() wrappers that reference WP externals + // e.g., __webpack_require__.n(_wordpress_element__WEBPACK_IMPORTED_MODULE_0__) + // We need to find the variable declarations that map to @wordpress packages + const wpModuleVarPattern = /var\s+(_wordpress_[a-zA-Z0-9_]+)__WEBPACK_IMPORTED_MODULE_\d+__\s*=\s*__webpack_require__\([^"]*"@wordpress\/([^"]+)"/g; + while ((match = wpModuleVarPattern.exec(content)) !== null) { + const packageName = match[2]; + const handle = 'wp-' + packageName; + allDeps.add(handle); + } + } + + // Also scan for vendor globals (React, ReactDOM, lodash, jQuery, moment) + if (content) { + if (/(?:=|,|\()\s*React[^a-zA-Z]/.test(content)) allDeps.add('react'); + if (/(?:=|,|\()\s*ReactDOM[^a-zA-Z]/.test(content)) allDeps.add('react-dom'); + if (/(?:=|,|\()\s*ReactJSXRuntime[^a-zA-Z]/.test(content)) allDeps.add('react-jsx-runtime'); + if (/(?:=|,|\()\s*lodash[^a-zA-Z]/.test(content)) allDeps.add('lodash'); + if (/(?:=|,|\()\s*jQuery[^a-zA-Z]/.test(content)) allDeps.add('jquery'); + if (/(?:=|,|\()\s*moment[^a-zA-Z]/.test(content)) allDeps.add('moment'); + } + + // Generate .asset.php with appropriate format + const assetPhpPath = outputPath.replace(/\.(m?js)$/, '.asset.php'); + const sortedDeps = Array.from(allDeps).sort(); + + let assetPhpContent: string; + if (isModule) { + // For ES modules, convert handles back to package names + const packageDeps = sortedDeps.map(handleToPackageName); + assetPhpContent = generateModuleAssetPhp(packageDeps, version); + } else { + assetPhpContent = generateAssetPhp(sortedDeps, version); + } + + // Ensure directory exists + mkdirSync(dirname(assetPhpPath), { recursive: true }); + + // Write asset file + writeFileSync(assetPhpPath, assetPhpContent, 'utf8'); + } + + return undefined; + }); + }, + }; +} + +/** + * Get all tracked dependencies (for testing/debugging) + * Note: This returns empty map since dependencies are now instance-specific + */ +export function getDependencies(): Map { + return new Map(); +} + +/** + * Clear all tracked dependencies (for testing) + * Note: This is now a no-op since dependencies are instance-specific + */ +export function clearDependencies(): void { + // No-op - dependencies are now cleared per plugin instance +} diff --git a/packages/build/src/types.ts b/packages/build/src/types.ts new file mode 100644 index 00000000..628f0146 --- /dev/null +++ b/packages/build/src/types.ts @@ -0,0 +1,607 @@ +/** + * Type definitions for @10up/build + * + * This module contains all TypeScript interfaces and types used throughout + * the build tool. These types define the configuration schema, build results, + * and internal data structures. + * + * @packageDocumentation + * @module types + */ + +import type { Plugin } from 'esbuild'; + +/** + * Path configuration for source and output directories. + * + * These paths are relative to the project root (where package.json is located). + * + * @example + * ```json + * { + * "paths": { + * "blocksDir": "./includes/blocks/", + * "srcDir": "./assets/", + * "copyAssetsDir": "./assets/", + * "blocksStyles": "./assets/css/blocks/", + * "globalStylesDir": "./assets/css/globals/", + * "globalMixinsDir": "./assets/css/mixins/" + * } + * } + * ``` + */ +export interface PathsConfig { + /** + * Directory containing block.json files for WordPress blocks. + * The build tool recursively searches this directory for block metadata. + * @default "./includes/blocks/" + */ + blocksDir: string; + + /** + * Output directory for compiled assets. + * @default "./dist/" + */ + distDir: string; + + /** + * Source directory for JavaScript, TypeScript, and CSS assets. + * @default "./assets/" + */ + srcDir: string; + + /** + * Directory containing static assets (images, fonts, etc.) to copy to output. + * Files with extensions like .jpg, .png, .svg, .woff2 are automatically copied. + * @default "./assets/" + */ + copyAssetsDir: string; + + /** + * Directory containing block-specific stylesheets for auto-enqueue feature. + * Used when `loadBlockSpecificStyles` is enabled. + * @default "./assets/css/blocks/" + */ + blocksStyles: string; + + /** + * Directory containing global CSS files with custom properties and custom media queries. + * These files are injected into all stylesheets via postcss-global-data. + * @default "./assets/css/globals/" + */ + globalStylesDir: string; + + /** + * Directory containing PostCSS mixin definition files. + * Mixins defined here can be used in any stylesheet with `@mixin name;`. + * @default "./assets/css/mixins/" + */ + globalMixinsDir: string; +} + +/** + * Output filename patterns for generated assets. + * + * Supports placeholders: + * - `[name]` - Entry point name + * - `[dir]` - Directory structure from entry + * - `[hash]` - Content hash for cache busting + * - `[contenthash]` - Content-based hash + * + * @example + * ```json + * { + * "filenames": { + * "js": "js/[name].js", + * "css": "css/[name].css" + * } + * } + * ``` + */ +export interface FilenamesConfig { + /** + * Pattern for JavaScript output files. + * @default "js/[name].js" + */ + js: string; + + /** + * Pattern for JavaScript chunk files (code splitting). + * @default "js/[name].[contenthash].chunk.js" + */ + jsChunk: string; + + /** + * Pattern for CSS output files. + * @default "css/[name].css" + */ + css: string; + + /** + * Pattern for block JavaScript files. + * @default "blocks/[name].js" + */ + block: string; + + /** + * Pattern for block CSS files. + * @default "blocks/[name].css" + */ + blockCSS: string; +} + +/** + * Configuration for externalizing custom package namespaces. + * + * This allows you to externalize packages from custom npm scopes + * (like @woocommerce/* or @your-plugin/*) similar to how @wordpress/* + * packages are handled. + * + * @example + * ```json + * { + * "externalNamespaces": { + * "@woocommerce": { + * "global": "wc", + * "handlePrefix": "wc" + * } + * } + * } + * ``` + * + * This would transform: + * - `import { Button } from '@woocommerce/components'` + * - To use global: `wc.components` + * - With script handle: `wc-components` + */ +export interface ExternalNamespaceConfig { + /** + * The global variable prefix where the package is available at runtime. + * For `@woocommerce/components`, if global is "wc", the runtime reference + * will be `wc.components`. + */ + global: string; + + /** + * The prefix for WordPress script handles. + * For `@woocommerce/components`, if handlePrefix is "wc", the handle + * will be `wc-components`. + */ + handlePrefix: string; +} + +/** + * Main build configuration object. + * + * This interface defines all configuration options available for the build tool. + * Configuration is typically read from `package.json["10up-toolkit"]`. + * + * @example + * ```json + * { + * "10up-toolkit": { + * "entry": { + * "admin": "./assets/js/admin/admin.js" + * }, + * "useBlockAssets": true, + * "wpDependencyExternals": true + * } + * } + * ``` + */ +export interface BuildConfig { + /** + * Entry points for scripts and styles. + * Keys become output filenames, values are source file paths. + * + * @example + * ```json + * { + * "admin": "./assets/js/admin/admin.js", + * "frontend": "./assets/js/frontend/frontend.js", + * "admin-style": "./assets/css/admin/admin-style.scss" + * } + * ``` + */ + entry: Record; + + /** + * Entry points for ES modules. + * These are built with `format: 'esm'` and support code splitting. + * + * @example + * ```json + * { + * "interactivity-module": "./assets/js/interactivity/module.ts" + * } + * ``` + */ + moduleEntry: Record; + + /** + * Path configuration for source and output directories. + * @see {@link PathsConfig} + */ + paths: PathsConfig; + + /** + * Output filename patterns. + * @see {@link FilenamesConfig} + */ + filenames: FilenamesConfig; + + /** + * Enable automatic entry detection from block.json files. + * When enabled, the build tool scans `paths.blocksDir` for block.json + * files and automatically creates entries for script/style assets. + * @default false + */ + useBlockAssets: boolean; + + /** + * Enable ES module output for script modules. + * When enabled, entries in `moduleEntry` are built as ES modules. + * @default false + */ + useScriptModules: boolean; + + /** + * Enable WordPress dependency externalization. + * When enabled: + * - @wordpress/* imports are externalized to WordPress globals + * - .asset.php files are generated with dependency arrays + * - Vendor packages (react, lodash, etc.) are also externalized + * @default true + */ + wpDependencyExternals: boolean; + + /** + * Enable automatic loading of block-specific stylesheets. + * Scans `paths.blocksStyles` for stylesheets matching core block names. + * @default false + */ + loadBlockSpecificStyles: boolean; + + /** + * Enable Hot Module Replacement in watch mode. + * Starts a WebSocket server for live reload notifications. + * @default false + */ + hot: boolean; + + /** + * Port for the HMR WebSocket server. + * @default 8887 + */ + devServerPort: number; + + /** + * Development URL for the WordPress site. + * Used for proxy configuration in development mode. + * @default "" + */ + devURL: string; + + /** + * Generate source maps for debugging. + * Always enabled in development mode regardless of this setting. + * @default false + */ + sourcemap: boolean; + + /** + * Public path for assets (used in generated URLs). + * @default "/dist/" + */ + publicPath: string; + + /** + * Custom package namespaces to externalize. + * @see {@link ExternalNamespaceConfig} + */ + externalNamespaces: Record; + + /** + * PostCSS configuration (auto-detected). + * @see {@link PostCSSConfig} + */ + postcss?: PostCSSConfig; +} + +/** + * PostCSS configuration options. + * + * The build tool automatically detects postcss.config.js in the project root. + * If not found, it uses the default configuration with global styles and mixins. + */ +export interface PostCSSConfig { + /** + * Path to the PostCSS configuration file. + * If not specified, uses the default configuration. + */ + configPath?: string; + + /** + * Directory containing global CSS files. + * Overrides the default from paths.globalStylesDir. + */ + globalStylesDir?: string; + + /** + * Directory containing mixin definition files. + * Overrides the default from paths.globalMixinsDir. + */ + globalMixinsDir?: string; +} + +/** + * Detected entry points categorized by type. + * + * The entry detection process scans: + * 1. `buildfiles.config.js` or `config.entry` + * 2. `config.moduleEntry` for ES modules + * 3. block.json files (if `useBlockAssets` is enabled) + */ +export interface DetectedEntries { + /** + * Regular JavaScript/TypeScript entries (IIFE output). + * Key is the output name, value is the source file path. + */ + scripts: Record; + + /** + * ES Module entries. + * Key is the output name, value is the source file path. + */ + modules: Record; + + /** + * Standalone CSS/SCSS entries. + * Key is the output name, value is the source file path. + */ + styles: Record; +} + +/** + * Result of resolving an external package. + * + * Used by the dependency extraction plugin to determine how to + * externalize a package and track it as a dependency. + */ +export interface ExternalResolution { + /** + * The global variable path where the package is available. + * For @wordpress/blocks, this would be "wp.blocks". + */ + global: string; + + /** + * The WordPress script handle for the package. + * For @wordpress/blocks, this would be "wp-blocks". + */ + handle: string; +} + +/** + * Dependency information for a single entry point. + * + * Used internally to track which WordPress packages are imported + * by each entry point, for .asset.php generation. + */ +export interface DependencyInfo { + /** + * Set of WordPress script handles that this entry depends on. + * Example: Set(['wp-blocks', 'wp-element', 'wp-i18n']) + */ + dependencies: Set; + + /** + * Content hash for cache busting. + * Generated from the output file content. + */ + version: string; +} + +/** + * Result returned from the build function. + * + * Contains information about the build success, timing, and any errors. + * + * @example + * ```typescript + * const result = await build(); + * if (result.success) { + * console.log(`Built in ${result.duration}ms`); + * } else { + * console.error('Build failed:', result.errors); + * } + * ``` + */ +export interface BuildResult { + /** + * Whether the build completed successfully. + */ + success: boolean; + + /** + * Build duration in milliseconds. + */ + duration: number; + + /** + * Count of entries built by type. + */ + entries: { + /** Number of IIFE script entries built */ + scripts: number; + /** Number of ES module entries built */ + modules: number; + /** Number of stylesheet entries built */ + styles: number; + }; + + /** + * Error messages if the build failed. + * Only present when `success` is false. + */ + errors?: string[]; +} + +/** + * Options for watch mode. + * + * @example + * ```typescript + * await watch({ + * hot: true, + * port: 8887, + * onRebuild: (result) => { + * console.log('Rebuilt:', result.success); + * } + * }); + * ``` + */ +export interface WatchOptions { + /** + * Enable Hot Module Replacement. + * When enabled, starts a WebSocket server for live reload. + * @default false (uses config.hot) + */ + hot?: boolean; + + /** + * Port for the HMR WebSocket server. + * @default 8887 (uses config.devServerPort) + */ + port?: number; + + /** + * Callback invoked after each rebuild. + * Useful for custom notifications or logging. + */ + onRebuild?: (result: BuildResult) => void; +} + +/** + * CLI command handler function type. + * + * Each CLI command (build, start, watch) is implemented as a handler + * that receives command-line arguments and returns a Promise. + * + * @param args - Command-line arguments (excluding the command name) + */ +export type CommandHandler = (args: string[]) => Promise; + +/** + * Map of CLI commands to their handlers. + * + * @see {@link CommandHandler} + */ +export interface Commands { + /** Production build command */ + build: CommandHandler; + /** Development mode with HMR */ + start: CommandHandler; + /** Watch mode without HMR */ + watch: CommandHandler; + /** Sync @wordpress dependencies from source imports */ + 'sync-wp-deps': CommandHandler; + /** Update @wordpress dependencies to a new version tag */ + 'update-wp-deps': CommandHandler; + /** List installed @wordpress dependencies */ + 'list-wp-deps': CommandHandler; + /** Cache wpScript flags for CI environments */ + 'cache-wp-scripts': CommandHandler; +} + +/** + * WordPress block.json metadata structure. + * + * This interface represents the relevant fields from block.json + * that the build tool uses for entry detection and transformation. + * + * @see {@link https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/} + * + * @example + * ```json + * { + * "name": "my-plugin/my-block", + * "editorScript": "file:./index.js", + * "editorStyle": "file:./editor.css", + * "style": "file:./style.css", + * "viewScript": "file:./view.js", + * "viewScriptModule": "file:./view-module.js" + * } + * ``` + */ +export interface BlockMetadata { + /** + * Block name in namespace/block-name format. + */ + name?: string; + + /** + * Block version for cache busting. + * Auto-generated from style file hashes if not provided. + */ + version?: string; + + /** + * Script loaded on both editor and frontend. + * Can be a handle, URL, or file: path. + */ + script?: string | string[]; + + /** + * Script loaded only in the editor. + * Typically the main block registration script. + */ + editorScript?: string | string[]; + + /** + * Script loaded only on the frontend. + * For interactive functionality. + */ + viewScript?: string | string[]; + + /** + * ES Module loaded on both editor and frontend. + * Used for WordPress Script Modules API. + */ + scriptModule?: string | string[]; + + /** + * ES Module loaded only on the frontend. + * For WordPress Interactivity API usage. + */ + viewScriptModule?: string | string[]; + + /** + * Stylesheet loaded on both editor and frontend. + */ + style?: string | string[]; + + /** + * Stylesheet loaded only in the editor. + */ + editorStyle?: string | string[]; + + /** + * Stylesheet loaded only on the frontend. + */ + viewStyle?: string | string[]; +} + +/** + * Extended esbuild plugin interface with dependency tracking. + * + * Used by the WordPress dependency extraction plugin to expose + * tracked dependencies for testing and debugging. + */ +export interface DependencyExtractionPlugin extends Plugin { + /** + * Get all tracked dependencies across entry points. + * @returns Map of entry point paths to their dependency info + */ + getDependencies: () => Map; +} diff --git a/packages/build/src/utils/entry-detection.ts b/packages/build/src/utils/entry-detection.ts new file mode 100644 index 00000000..51a44051 --- /dev/null +++ b/packages/build/src/utils/entry-detection.ts @@ -0,0 +1,275 @@ +/** + * Entry point detection for WordPress blocks and build files + */ + +import { readFileSync, existsSync } from 'node:fs'; +import { dirname, extname, join, resolve } from 'node:path'; +import fastGlob from 'fast-glob'; +const glob = fastGlob.sync; +import { fromProjectRoot, normalizePath } from './paths.js'; +import type { BuildConfig, DetectedEntries, BlockMetadata } from '../types.js'; + +/** + * Asset keys from block.json for scripts + */ +const JS_ASSET_KEYS: (keyof BlockMetadata)[] = ['script', 'editorScript', 'viewScript']; + +/** + * Asset keys from block.json for script modules + */ +const MODULE_ASSET_KEYS: (keyof BlockMetadata)[] = ['scriptModule', 'viewScriptModule']; + +/** + * Asset keys from block.json for styles + */ +const CSS_ASSET_KEYS: (keyof BlockMetadata)[] = ['style', 'editorStyle', 'viewStyle']; + +/** + * Detect all entry points for the build + */ +export async function detectEntries(config: BuildConfig): Promise { + const entries: DetectedEntries = { + scripts: {}, + modules: {}, + styles: {}, + }; + + // 1. Load entries from buildfiles.config.js or config.entry + const buildFileEntries = await loadBuildFileEntries(config); + Object.assign(entries.scripts, buildFileEntries.scripts); + Object.assign(entries.styles, buildFileEntries.styles); + + // 2. Load module entries from config.moduleEntry + if (config.moduleEntry && Object.keys(config.moduleEntry).length > 0) { + Object.assign(entries.modules, filterExistingEntries(config.moduleEntry)); + } + + // 3. Detect block.json entries if useBlockAssets is enabled + if (config.useBlockAssets) { + const blockEntries = detectBlockEntries(config); + Object.assign(entries.scripts, blockEntries.scripts); + Object.assign(entries.modules, blockEntries.modules); + Object.assign(entries.styles, blockEntries.styles); + } + + return entries; +} + +/** + * Load entries from buildfiles.config.js or config.entry + */ +async function loadBuildFileEntries( + config: BuildConfig, +): Promise<{ scripts: Record; styles: Record }> { + const result: { scripts: Record; styles: Record } = { + scripts: {}, + styles: {}, + }; + + // Try buildfiles.config.js first + const buildFilesConfigPath = fromProjectRoot('buildfiles.config.js'); + let entries: Record = {}; + + if (existsSync(buildFilesConfigPath)) { + try { + const buildFilesModule = await import(`file://${buildFilesConfigPath}`); + entries = buildFilesModule.default || buildFilesModule; + } catch (error) { + console.warn( + 'Warning: Could not load buildfiles.config.js:', + error instanceof Error ? error.message : error, + ); + } + } else if (config.entry && Object.keys(config.entry).length > 0) { + // Use config.entry as fallback + entries = config.entry; + } + + // Separate JS and CSS entries, adding js/ and css/ prefixes for output organization + for (const [name, filePath] of Object.entries(entries)) { + const resolvedPath = fromProjectRoot(filePath); + + if (!existsSync(resolvedPath)) { + continue; + } + + const ext = extname(filePath).toLowerCase(); + + if (['.css', '.scss', '.sass'].includes(ext)) { + // CSS entries go to css/ subdirectory + result.styles[`css/${name}`] = resolvedPath; + } else { + // JS entries go to js/ subdirectory + result.scripts[`js/${name}`] = resolvedPath; + } + } + + return result; +} + +/** + * Filter entries to only include existing files, adding js/ prefix for output organization + */ +function filterExistingEntries(entries: Record): Record { + const result: Record = {}; + + for (const [name, filePath] of Object.entries(entries)) { + const resolvedPath = fromProjectRoot(filePath); + if (existsSync(resolvedPath)) { + // Add js/ prefix for module entries + result[`js/${name}`] = resolvedPath; + } + } + + return result; +} + +/** + * Detect entries from block.json files + */ +function detectBlockEntries(config: BuildConfig): DetectedEntries { + const result: DetectedEntries = { scripts: {}, modules: {}, styles: {} }; + + const blocksDir = resolve(process.cwd(), config.paths.blocksDir); + + if (!existsSync(blocksDir)) { + return result; + } + + // Find all block.json files + const blockMetadataFiles = glob(normalizePath(`${blocksDir}/**/block.json`), { + absolute: true, + }); + + for (const blockMetadataFile of blockMetadataFiles) { + try { + const metadata: BlockMetadata = JSON.parse(readFileSync(blockMetadataFile, 'utf8')); + const blockDir = dirname(blockMetadataFile); + + // Process script assets + for (const key of JS_ASSET_KEYS) { + const asset = metadata[key]; + if (asset) { + const entries = processAssetField(asset, blockDir, blocksDir, false); + Object.assign(result.scripts, entries); + } + } + + // Process script module assets + for (const key of MODULE_ASSET_KEYS) { + const asset = metadata[key]; + if (asset) { + const entries = processAssetField(asset, blockDir, blocksDir, false); + Object.assign(result.modules, entries); + } + } + + // Process style assets + for (const key of CSS_ASSET_KEYS) { + const asset = metadata[key]; + if (asset) { + const entries = processAssetField(asset, blockDir, blocksDir, true); + Object.assign(result.styles, entries); + } + } + } catch (error) { + // Skip malformed block.json files + console.warn( + `Warning: Could not parse ${blockMetadataFile}:`, + error instanceof Error ? error.message : error, + ); + } + } + + return result; +} + +/** + * Process an asset field from block.json + */ +function processAssetField( + asset: string | string[], + blockDir: string, + blocksDir: string, + isStyle: boolean, +): Record { + const result: Record = {}; + const assets = Array.isArray(asset) ? asset : [asset]; + + for (const rawFilepath of assets) { + // Only process file: prefixed paths + if (!rawFilepath || !rawFilepath.startsWith('file:')) { + continue; + } + + const relativePath = rawFilepath.replace('file:', ''); + const targetPath = join(blockDir, relativePath); + + // Generate entry name from path + const entryName = targetPath + .replace(extname(targetPath), '') + .replace(blocksDir, '') + .replace(/^[/\\]/, ''); + + // Find the actual source file (might be .ts, .tsx, .scss, etc.) + const sourceFile = findSourceFile(targetPath, isStyle); + + if (sourceFile) { + result[`blocks/${normalizePath(entryName)}`] = sourceFile; + } + } + + return result; +} + +/** + * Find the actual source file for a target path + */ +function findSourceFile(targetPath: string, isStyle: boolean): string | null { + const dir = dirname(targetPath); + const baseName = targetPath + .replace(extname(targetPath), '') + .replace(dir, '') + .replace(/^[/\\]/, ''); + + const extensions = isStyle ? ['.css', '.scss', '.sass'] : ['.js', '.jsx', '.ts', '.tsx']; + + for (const ext of extensions) { + const sourcePath = join(dir, `${baseName}${ext}`); + if (existsSync(sourcePath)) { + return sourcePath; + } + } + + // Try the exact path + if (existsSync(targetPath)) { + return targetPath; + } + + return null; +} + +/** + * Get block-specific style entries (for loadBlockSpecificStyles option) + */ +export function getBlockSpecificStyles(config: BuildConfig): Record { + const result: Record = {}; + + const stylesDir = resolve(process.cwd(), config.paths.blocksStyles); + + if (!existsSync(stylesDir)) { + return result; + } + + const stylesheets = glob(normalizePath(`${stylesDir}/**/*.{css,scss,sass}`), { + absolute: true, + }); + + for (const filePath of stylesheets) { + const blockName = filePath.replace(`${stylesDir}/`, '').replace(extname(filePath), ''); + + result[`autoenqueue/${normalizePath(blockName)}`] = filePath; + } + + return result; +} diff --git a/packages/build/src/utils/externals.ts b/packages/build/src/utils/externals.ts new file mode 100644 index 00000000..8b95ab04 --- /dev/null +++ b/packages/build/src/utils/externals.ts @@ -0,0 +1,305 @@ +/** + * WordPress and vendor externals handling + * + * Uses dynamic detection via wpScript flag in package.json + * instead of maintaining a hardcoded list. + * + * Supports a cache file (.wp-scripts-cache.json) for CI environments + * where optional dependencies are not installed. + */ + +import { readFileSync, existsSync } from 'node:fs'; +import { createRequire } from 'node:module'; +import { join, dirname } from 'node:path'; +import type { BuildConfig, ExternalResolution } from '../types.js'; + +const require = createRequire(import.meta.url); + +/** + * Vendor externals - these don't have wpScript flag + * so we maintain them as a hardcoded list + */ +export const vendorExternals: Record = { + react: { global: 'React', handle: 'react' }, + 'react-dom': { global: 'ReactDOM', handle: 'react-dom' }, + 'react-dom/client': { global: 'ReactDOM', handle: 'react-dom' }, + 'react/jsx-runtime': { global: 'ReactJSXRuntime', handle: 'react-jsx-runtime' }, + lodash: { global: 'lodash', handle: 'lodash' }, + 'lodash-es': { global: 'lodash', handle: 'lodash' }, + moment: { global: 'moment', handle: 'moment' }, + jquery: { global: 'jQuery', handle: 'jquery' }, +}; + +/** + * Cache file name for wpScript lookups + */ +export const WP_SCRIPTS_CACHE_FILE = '.wp-scripts-cache.json'; + +/** + * Cache for package.json wpScript lookups (runtime cache) + */ +const wpScriptCache = new Map(); + +/** + * File-based cache loaded from .wp-scripts-cache.json + */ +let fileCache: Record | null = null; +let fileCacheLoaded = false; + +/** + * Find the cache file by searching up the directory tree. + * This allows workspace packages in a monorepo to find the cache + * file at the monorepo root. + */ +function findCacheFile(startDir: string): string | null { + let dir = startDir; + + while (dir !== dirname(dir)) { + const cachePath = join(dir, WP_SCRIPTS_CACHE_FILE); + if (existsSync(cachePath)) { + return cachePath; + } + dir = dirname(dir); + } + + return null; +} + +/** + * Load the wpScript cache from file if it exists. + * Searches up the directory tree to find the cache file, + * supporting monorepo setups where the cache is at the root. + */ +function loadFileCache(): Record | null { + if (fileCacheLoaded) { + return fileCache; + } + + fileCacheLoaded = true; + + const cachePath = findCacheFile(process.cwd()); + if (cachePath) { + try { + const cacheData = JSON.parse(readFileSync(cachePath, 'utf8')); + if (cacheData && typeof cacheData.packages === 'object') { + fileCache = cacheData.packages; + return fileCache; + } + } catch { + // Invalid cache file, ignore + } + } + + return null; +} + +/** + * Result of checking a WordPress package + */ +interface WpPackageCheckResult { + /** Whether the package should be externalized */ + isExternal: boolean; + /** Whether the package was found/installed locally */ + isInstalled: boolean; +} + +/** + * Check if a @wordpress/* package has wpScript: true + * Returns: + * - isExternal: true if package should be externalized (wpScript: true or not installed) + * - isInstalled: true if package was found in node_modules + * + * This ensures we externalize all @wordpress/* imports, even if + * the package isn't a direct dependency of the project. + * + * Resolution order: + * 1. Runtime cache (fastest) + * 2. File-based cache (.wp-scripts-cache.json) - for CI without optional deps + * 3. Package.json lookup (when package is installed) + * 4. Default to true (externalize) if package not found + * + * @param packageName - The @wordpress/* package name to check + * @returns Object with isExternal and isInstalled flags + */ +export function isWpScriptPackage(packageName: string): WpPackageCheckResult { + // 1. Check runtime cache first + const cached = wpScriptCache.get(packageName); + if (cached !== undefined) { + // For cached results, we can't determine isInstalled from boolean + // So we need to try resolving again + } + + // 2. Try to resolve from installed package + try { + const pkgJsonPath = require.resolve(`${packageName}/package.json`); + const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); + + // If wpScript is explicitly set, use that value + if (typeof pkgJson.wpScript === 'boolean') { + wpScriptCache.set(packageName, pkgJson.wpScript); + return { isExternal: pkgJson.wpScript, isInstalled: true }; + } + + // wpScript is undefined - check file cache for authoritative answer + const fileCacheData = loadFileCache(); + if (fileCacheData && packageName in fileCacheData) { + const hasWpScript = fileCacheData[packageName]; + wpScriptCache.set(packageName, hasWpScript); + return { isExternal: hasWpScript, isInstalled: true }; + } + + // Not in cache either - default to NOT externalizing installed packages without wpScript + wpScriptCache.set(packageName, false); + return { isExternal: false, isInstalled: true }; + } catch { + // Package not installed or subpath export - check file cache and parent package + } + + // 3. Check file-based cache (.wp-scripts-cache.json) + const fileCacheData = loadFileCache(); + if (fileCacheData && packageName in fileCacheData) { + const hasWpScript = fileCacheData[packageName]; + wpScriptCache.set(packageName, hasWpScript); + return { isExternal: hasWpScript, isInstalled: false }; + } + + // 4. For subpath exports like @wordpress/dataviews/wp, check the parent package + const parts = packageName.replace('@wordpress/', '').split('/'); + if (parts.length > 1) { + const parentPackage = `@wordpress/${parts[0]}`; + + // Check parent in file cache + if (fileCacheData && parentPackage in fileCacheData) { + const parentHasWpScript = fileCacheData[parentPackage]; + wpScriptCache.set(packageName, parentHasWpScript); + return { isExternal: parentHasWpScript, isInstalled: false }; + } + + // Try to resolve parent package + try { + const parentPkgJsonPath = require.resolve(`${parentPackage}/package.json`); + const parentPkgJson = JSON.parse(readFileSync(parentPkgJsonPath, 'utf8')); + const parentHasWpScript = typeof parentPkgJson.wpScript === 'boolean' + ? parentPkgJson.wpScript + : false; + wpScriptCache.set(packageName, parentHasWpScript); + return { isExternal: parentHasWpScript, isInstalled: true }; + } catch { + // Parent also not found + } + } + + // 5. Package not installed and not in cache - assume it should be externalized + // since all @wordpress/* packages with wpScript=true are + // available as WordPress globals at runtime + wpScriptCache.set(packageName, true); + return { isExternal: true, isInstalled: false }; +} + +/** + * Convert a string to camelCase + */ +function camelCase(str: string): string { + return str.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase()); +} + +/** + * Convert @wordpress/* package name to WP script handle + * @example '@wordpress/blocks' -> 'wp-blocks' + */ +export function wpPackageToHandle(packageName: string): string { + return packageName.replace('@wordpress/', 'wp-'); +} + +/** + * Convert @wordpress/* package name to global variable path + * @example '@wordpress/blocks' -> 'wp.blocks' + */ +export function wpPackageToGlobal(packageName: string): string { + const name = packageName.replace('@wordpress/', ''); + return `wp.${camelCase(name)}`; +} + +/** + * Resolve external configuration for a package + * + * @param packageName - The package name to resolve + * @param config - Build configuration with external namespaces + * @returns External resolution with global and handle, or null if not external + */ +export function resolveExternal( + packageName: string, + config: Partial = {}, +): ExternalResolution | null { + // 1. Check @wordpress/* packages (read wpScript from package.json) + if (packageName.startsWith('@wordpress/')) { + const checkResult = isWpScriptPackage(packageName); + if (checkResult.isExternal) { + return { + global: wpPackageToGlobal(packageName), + handle: wpPackageToHandle(packageName), + // Include installation status for warning purposes + _isInstalled: checkResult.isInstalled, + } as ExternalResolution & { _isInstalled: boolean }; + } + // Not a wpScript package, don't externalize + return null; + } + + // 2. Check custom namespaces from config + const externalNamespaces = config.externalNamespaces || {}; + for (const [namespace, mapping] of Object.entries(externalNamespaces)) { + if (packageName.startsWith(`${namespace}/`)) { + const name = packageName.replace(`${namespace}/`, ''); + return { + global: `${mapping.global}.${camelCase(name)}`, + handle: `${mapping.handlePrefix}-${name}`, + }; + } + } + + // 3. Check vendor externals + if (vendorExternals[packageName]) { + return vendorExternals[packageName]; + } + + return null; +} + +/** + * Get all external patterns for esbuild + * + * IMPORTANT: We return an empty array because ALL externalization is handled + * by the wp-dependency-extraction plugin's onResolve hooks. + * + * Why? esbuild's `external` option has HIGHER priority than plugins - if a path + * matches an external pattern, onResolve callbacks are NOT run at all. + * + * Our plugin needs to run for ALL packages to: + * - Decide per-package whether to externalize or bundle (wpScript flag) + * - Provide virtual modules for IIFE builds (avoid require() calls in browser) + * - Track dependencies for .asset.php generation + */ +export function getExternalPatterns(_config: Partial = {}): string[] { + // Return empty - let the wp-dependency-extraction plugin handle all externals + return []; +} + +/** + * Create esbuild global mappings for externals + */ +export function createGlobalMappings( + dependencies: Set, + config: Partial = {}, +): Record { + const mappings: Record = {}; + + for (const dep of dependencies) { + const external = resolveExternal(dep, config); + if (external) { + mappings[dep] = external.global; + } + } + + return mappings; +} diff --git a/packages/build/src/utils/paths.ts b/packages/build/src/utils/paths.ts new file mode 100644 index 00000000..8ed2a14b --- /dev/null +++ b/packages/build/src/utils/paths.ts @@ -0,0 +1,79 @@ +/** + * Path utilities for 10up-build + */ + +import { existsSync, readFileSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { createHash } from 'node:crypto'; +import { fileURLToPath } from 'node:url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +/** + * Get the project root directory (where package.json is) + */ +export function getProjectRoot(): string { + return process.cwd(); +} + +/** + * Resolve a path from the project root + */ +export function fromProjectRoot(relativePath: string): string { + return resolve(getProjectRoot(), relativePath); +} + +/** + * Resolve a path from the package root (where this package is installed) + */ +export function fromPackageRoot(relativePath: string): string { + return resolve(__dirname, '..', '..', relativePath); +} + +/** + * Check if a file exists in the project + */ +export function hasProjectFile(relativePath: string): boolean { + return existsSync(fromProjectRoot(relativePath)); +} + +/** + * Check if a file exists + */ +export function fileExists(absolutePath: string): boolean { + return existsSync(absolutePath); +} + +/** + * Get a content-based hash of a file + */ +export function getFileContentHash(filePath: string): string { + try { + const content = readFileSync(filePath); + return createHash('sha256').update(content).digest('hex').slice(0, 8); + } catch { + return ''; + } +} + +/** + * Get a content-based hash from a string or buffer + */ +export function getContentHash(content: string | Buffer): string { + return createHash('sha256').update(content).digest('hex').slice(0, 20); +} + +/** + * Normalize a path to use forward slashes (for glob patterns) + */ +export function normalizePath(filePath: string): string { + return filePath.replace(/\\/g, '/'); +} + +/** + * Remove dist folder prefix from a path + */ +export function removeDistFolder(file: string): string { + return file.replace(/(^\.\/dist\/)|^dist\//, ''); +} diff --git a/packages/build/src/watch.ts b/packages/build/src/watch.ts new file mode 100644 index 00000000..2798c0f4 --- /dev/null +++ b/packages/build/src/watch.ts @@ -0,0 +1,252 @@ +/** + * Watch mode with React Fast Refresh support + */ + +import { mkdirSync, rmSync, existsSync } from 'node:fs'; +import * as esbuild from 'esbuild'; +import { WebSocketServer, WebSocket } from 'ws'; +import pc from 'picocolors'; +import { loadConfig, getOutputDir } from './config.js'; +import { getExternalPatterns } from './utils/externals.js'; +import { detectEntries, getBlockSpecificStyles } from './utils/entry-detection.js'; +import { wpDependencyExtractionPlugin } from './plugins/wp-dependency-extraction.js'; +import { sassPlugin } from './plugins/sass-plugin.js'; +import { processBlockJsonFiles } from './plugins/block-json.js'; +import { copyStaticAssets } from './plugins/copy-assets.js'; +import type { BuildConfig, WatchOptions } from './types.js'; + +let wsServer: WebSocketServer | null = null; +let clients: Set = new Set(); + +/** + * Send message to all connected clients + */ +function broadcast(message: object): void { + const data = JSON.stringify(message); + for (const client of clients) { + if (client.readyState === WebSocket.OPEN) { + client.send(data); + } + } +} + +/** + * Notify clients of rebuild (exported for future use) + */ +export function notifyRebuild(success: boolean): void { + broadcast({ + type: success ? 'reload' : 'error', + timestamp: Date.now(), + }); +} + +/** + * Create React Fast Refresh runtime injection plugin + */ +function reactRefreshPlugin(): esbuild.Plugin { + return { + name: 'react-refresh', + setup(_build) { + // Placeholder for React Fast Refresh implementation + // In a complete implementation, this would inject the refresh runtime + // and handle HMR updates for React components + }, + }; +} + +/** + * Start WebSocket server for HMR + */ +function startWebSocketServer(port: number): void { + if (wsServer) { + return; + } + + wsServer = new WebSocketServer({ port }); + + wsServer.on('connection', (ws) => { + clients.add(ws); + ws.on('close', () => { + clients.delete(ws); + }); + }); + + console.log(pc.dim(`HMR server running on ws://localhost:${port}`)); +} + +/** + * Stop WebSocket server + */ +function stopWebSocketServer(): void { + if (wsServer) { + for (const client of clients) { + client.close(); + } + clients.clear(); + wsServer.close(); + wsServer = null; + } +} + +/** + * Create esbuild context for watch mode + */ +async function createWatchContext( + entries: Record, + config: BuildConfig, + outputDir: string, + isModule: boolean, +): Promise { + if (Object.keys(entries).length === 0) { + return null; + } + + const plugins: esbuild.Plugin[] = [ + wpDependencyExtractionPlugin(config, { isModule }), + sassPlugin(config, false), + ]; + + if (config.hot && !isModule) { + plugins.push(reactRefreshPlugin()); + } + + const external = config.wpDependencyExternals ? getExternalPatterns(config) : []; + + const context = await esbuild.context({ + entryPoints: entries, + bundle: true, + metafile: true, + sourcemap: true, + minify: false, + target: ['es2020'], + format: isModule ? 'esm' : 'iife', + splitting: isModule, + outdir: outputDir, + external, + outExtension: { '.js': '.js' }, + entryNames: '[dir]/[name]', + chunkNames: 'js/chunks/[name]-[hash]', + plugins, + loader: { + '.js': 'jsx', + '.ts': 'tsx', + '.tsx': 'tsx', + '.jsx': 'jsx', + }, + jsx: 'automatic', + logLevel: 'warning', + define: config.hot + ? { __HMR_PORT__: String(config.devServerPort) } + : {}, + }); + + return context; +} + +/** + * Clean output directory + */ +function cleanOutputDir(outputDir: string): void { + if (existsSync(outputDir)) { + rmSync(outputDir, { recursive: true, force: true }); + } + mkdirSync(outputDir, { recursive: true }); +} + +/** + * Watch mode entry point + */ +export async function watch(options: WatchOptions = {}): Promise { + const config = loadConfig(); + const outputDir = getOutputDir(config); + const hot = options.hot ?? config.hot; + const port = options.port ?? config.devServerPort; + + console.log(pc.cyan('\n10up-build') + pc.dim(' v1.0.0 (watch mode)')); + console.log(pc.dim(`Hot reload: ${hot ? 'enabled' : 'disabled'}\n`)); + + // Clean output directory + cleanOutputDir(outputDir); + + // Detect entries + const entries = await detectEntries(config); + + if (config.loadBlockSpecificStyles) { + const blockStyles = getBlockSpecificStyles(config); + Object.assign(entries.styles, blockStyles); + } + + const totalEntries = + Object.keys(entries.scripts).length + + Object.keys(entries.modules).length + + Object.keys(entries.styles).length; + + if (totalEntries === 0) { + console.log(pc.yellow('No entry points found.')); + return; + } + + console.log(pc.dim(`Watching ${totalEntries} entries...\n`)); + + // Start HMR server if hot reload is enabled + if (hot) { + startWebSocketServer(port); + } + + // Create watch contexts + const [scriptsContext, modulesContext] = await Promise.all([ + createWatchContext(entries.scripts, config, outputDir, false), + createWatchContext(entries.modules, config, outputDir, true), + ]); + + // Build styles once (no watch needed for now - could add chokidar later) + if (Object.keys(entries.styles).length > 0) { + try { + await esbuild.build({ + entryPoints: entries.styles, + bundle: true, + outdir: outputDir, + plugins: [sassPlugin(config, false)], + entryNames: '[dir]/[name]', + logLevel: 'warning', + }); + console.log(pc.green('✓') + pc.dim(` Styles built`)); + } catch (error) { + console.error(pc.red('✗ Style build failed')); + } + } + + // Process block.json files + if (config.useBlockAssets) { + await processBlockJsonFiles(config, outputDir); + } + + // Copy static assets + await copyStaticAssets(config, outputDir); + + // Start watching + if (scriptsContext) { + await scriptsContext.watch(); + console.log(pc.green('✓') + pc.dim(` Watching ${Object.keys(entries.scripts).length} script entries`)); + } + + if (modulesContext) { + await modulesContext.watch(); + console.log(pc.green('✓') + pc.dim(` Watching ${Object.keys(entries.modules).length} module entries`)); + } + + console.log(pc.cyan('\nWatching for changes...')); + console.log(pc.dim('Press Ctrl+C to stop\n')); + + // Handle graceful shutdown + const cleanup = async () => { + console.log(pc.dim('\nStopping watch mode...')); + stopWebSocketServer(); + if (scriptsContext) await scriptsContext.dispose(); + if (modulesContext) await modulesContext.dispose(); + process.exit(0); + }; + + process.on('SIGINT', cleanup); + process.on('SIGTERM', cleanup); +} diff --git a/packages/build/src/wp-deps.ts b/packages/build/src/wp-deps.ts new file mode 100644 index 00000000..47dadcca --- /dev/null +++ b/packages/build/src/wp-deps.ts @@ -0,0 +1,569 @@ +/** + * WordPress dependency management utilities + * + * Provides commands to sync and update @wordpress/* dependencies + */ + +import { execSync } from 'child_process'; +import { readFileSync, writeFileSync, existsSync } from 'fs'; +import { join, dirname } from 'path'; +import pc from 'picocolors'; +import fg from 'fast-glob'; +import { WP_SCRIPTS_CACHE_FILE } from './utils/externals.js'; + +const WP_VERSION_API = 'https://api.wordpress.org/core/version-check/1.7/'; + +interface WPVersionResponse { + offers: Array<{ + response: string; + version: string; + current: string; + }>; +} + +/** + * Fetch the latest WordPress version from the API + * and convert it to an npm tag (e.g., "6.9.3" -> "wp-6.9") + */ +async function getLatestWpTag(): Promise { + try { + const response = await fetch(WP_VERSION_API); + if (!response.ok) { + throw new Error(`HTTP ${response.status}`); + } + const data = (await response.json()) as WPVersionResponse; + + // Get the first offer which is the latest version + const latestVersion = data.offers?.[0]?.version; + if (!latestVersion) { + throw new Error('No version found in API response'); + } + + // Convert version to tag: "6.9.3" -> "wp-6.9" (only major.minor) + const [major, minor] = latestVersion.split('.'); + return `wp-${major}.${minor}`; + } catch (error) { + console.warn( + pc.yellow(`\nWarning: Could not fetch latest WordPress version from API.`), + ); + console.warn(pc.dim(`Using fallback: wp-6.8\n`)); + return 'wp-6.8'; + } +} + +/** + * Known @wordpress packages that are available on npm + * This list includes packages commonly used in WordPress block development + */ +const KNOWN_WP_PACKAGES = new Set([ + '@wordpress/a11y', + '@wordpress/annotations', + '@wordpress/api-fetch', + '@wordpress/autop', + '@wordpress/blob', + '@wordpress/block-directory', + '@wordpress/block-editor', + '@wordpress/block-library', + '@wordpress/block-serialization-default-parser', + '@wordpress/blocks', + '@wordpress/commands', + '@wordpress/components', + '@wordpress/compose', + '@wordpress/core-commands', + '@wordpress/core-data', + '@wordpress/data', + '@wordpress/data-controls', + '@wordpress/dataviews', + '@wordpress/date', + '@wordpress/deprecated', + '@wordpress/dom', + '@wordpress/dom-ready', + '@wordpress/edit-post', + '@wordpress/edit-site', + '@wordpress/edit-widgets', + '@wordpress/editor', + '@wordpress/element', + '@wordpress/escape-html', + '@wordpress/format-library', + '@wordpress/hooks', + '@wordpress/html-entities', + '@wordpress/i18n', + '@wordpress/icons', + '@wordpress/interactivity', + '@wordpress/interactivity-router', + '@wordpress/interface', + '@wordpress/is-shallow-equal', + '@wordpress/keyboard-shortcuts', + '@wordpress/keycodes', + '@wordpress/list-reusable-blocks', + '@wordpress/media-utils', + '@wordpress/notices', + '@wordpress/nux', + '@wordpress/patterns', + '@wordpress/plugins', + '@wordpress/preferences', + '@wordpress/preferences-persistence', + '@wordpress/primitives', + '@wordpress/priority-queue', + '@wordpress/private-apis', + '@wordpress/redux-routine', + '@wordpress/reusable-blocks', + '@wordpress/rich-text', + '@wordpress/router', + '@wordpress/server-side-render', + '@wordpress/shortcode', + '@wordpress/style-engine', + '@wordpress/token-list', + '@wordpress/url', + '@wordpress/viewport', + '@wordpress/warning', + '@wordpress/widgets', + '@wordpress/wordcount', +]); + +/** + * Scan source files for @wordpress/* imports + */ +async function scanForWordPressImports(cwd: string): Promise> { + const imports = new Set(); + + // Find all JS/TS source files, excluding node_modules and dist + const files = await fg(['**/*.{js,jsx,ts,tsx}'], { + cwd, + ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/vendor/**'], + absolute: true, + }); + + // Regex patterns to match imports + const importPatterns = [ + /import\s+.*?from\s+['"](@wordpress\/[^'"\/]+)/g, + /import\s*\(\s*['"](@wordpress\/[^'"\/]+)/g, + /require\s*\(\s*['"](@wordpress\/[^'"\/]+)/g, + ]; + + for (const file of files) { + try { + const content = readFileSync(file, 'utf-8'); + + for (const pattern of importPatterns) { + let match; + while ((match = pattern.exec(content)) !== null) { + const packageName = match[1]; + if (KNOWN_WP_PACKAGES.has(packageName)) { + imports.add(packageName); + } + } + } + } catch { + // Skip files that can't be read + } + } + + return imports; +} + +/** + * Read package.json from the current directory + */ +function readPackageJson(cwd: string): Record { + const packageJsonPath = join(cwd, 'package.json'); + try { + return JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + } catch { + throw new Error(`Could not read package.json at ${packageJsonPath}`); + } +} + +/** + * Get existing @wordpress packages from optionalDependencies + */ +function getExistingWpDeps(packageJson: Record): Map { + const deps = new Map(); + const optionalDeps = (packageJson.optionalDependencies || {}) as Record; + + for (const [name, version] of Object.entries(optionalDeps)) { + if (name.startsWith('@wordpress/')) { + deps.set(name, version); + } + } + + return deps; +} + +/** + * Find the monorepo root by looking for package.json with workspaces field + */ +function findMonorepoRoot(startDir: string): string | null { + let dir = startDir; + while (dir !== dirname(dir)) { + const packageJsonPath = join(dir, 'package.json'); + if (existsSync(packageJsonPath)) { + try { + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + if (packageJson.workspaces) { + return dir; + } + } catch { + // Continue searching + } + } + dir = dirname(dir); + } + return null; +} + +/** + * Get workspace directories from monorepo root + */ +async function getWorkspaceDirectories(rootDir: string): Promise { + const packageJson = readPackageJson(rootDir); + const workspaces = packageJson.workspaces as string[] | { packages: string[] } | undefined; + + if (!workspaces) { + return []; + } + + // Handle both array format and object format { packages: [...] } + const patterns = Array.isArray(workspaces) ? workspaces : workspaces.packages || []; + + // Expand glob patterns to find actual workspace directories + const directories: string[] = []; + + for (const pattern of patterns) { + // Find directories matching the pattern that contain package.json + const matches = await fg([`${pattern}/package.json`], { + cwd: rootDir, + absolute: true, + onlyFiles: true, + }); + + for (const match of matches) { + directories.push(dirname(match)); + } + } + + return directories; +} + +/** + * Scan all workspaces for @wordpress/* imports + */ +async function scanWorkspacesForWordPressImports( + rootDir: string, + workspaceDirs: string[], +): Promise> { + const allImports = new Set(); + + console.log(`Scanning ${pc.bold(String(workspaceDirs.length))} workspaces for @wordpress/* imports...\n`); + + for (const dir of workspaceDirs) { + const imports = await scanForWordPressImports(dir); + if (imports.size > 0) { + const relativePath = dir.replace(rootDir + '/', ''); + console.log(` ${pc.dim('•')} ${relativePath}: ${imports.size} packages`); + for (const pkg of imports) { + allImports.add(pkg); + } + } + } + + return allImports; +} + +/** + * Sync @wordpress dependencies + * + * Scans source files for @wordpress/* imports and installs them + * as optional dependencies using the specified WordPress version tag. + * + * With --workspace-scan, scans all workspaces in a monorepo and installs + * dependencies at the root level for better performance. + */ +export async function syncWpDeps( + options: { tag?: string; dryRun?: boolean; workspaceScan?: boolean } = {}, +): Promise { + const cwd = process.cwd(); + const dryRun = options.dryRun || false; + const workspaceScan = options.workspaceScan || false; + + console.log(pc.cyan('\n10up-build') + ' - Sync WordPress Dependencies\n'); + + // Get tag from options or fetch latest from WordPress API + let tag = options.tag; + if (!tag) { + console.log(`Fetching latest WordPress version...`); + tag = await getLatestWpTag(); + console.log(`Using tag: ${pc.cyan(tag)}\n`); + } + + let foundImports: Set; + let installDir: string; + + if (workspaceScan) { + // Find monorepo root + const monorepoRoot = findMonorepoRoot(cwd); + if (!monorepoRoot) { + console.error(pc.red('Error: Could not find monorepo root (no package.json with workspaces field).')); + console.log(pc.dim('Run from within a monorepo or use without --workspace-scan.')); + process.exit(1); + } + + console.log(`Monorepo root: ${pc.dim(monorepoRoot)}\n`); + installDir = monorepoRoot; + + // Get all workspace directories + const workspaceDirs = await getWorkspaceDirectories(monorepoRoot); + if (workspaceDirs.length === 0) { + console.error(pc.red('Error: No workspaces found in monorepo.')); + process.exit(1); + } + + // Scan all workspaces + foundImports = await scanWorkspacesForWordPressImports(monorepoRoot, workspaceDirs); + } else { + console.log(`Scanning for @wordpress/* imports...`); + foundImports = await scanForWordPressImports(cwd); + installDir = cwd; + } + + if (foundImports.size === 0) { + console.log(pc.yellow('\nNo @wordpress/* imports found in source files.')); + return; + } + + console.log(`\nFound ${pc.bold(String(foundImports.size))} unique @wordpress packages:\n`); + + // Sort for consistent output + const sortedImports = Array.from(foundImports).sort(); + for (const pkg of sortedImports) { + console.log(` ${pc.dim('•')} ${pkg}`); + } + + // Read package.json from install directory + const packageJson = readPackageJson(installDir); + const existingDeps = getExistingWpDeps(packageJson); + + // Determine what needs to be installed + const toInstall: string[] = []; + const alreadyInstalled: string[] = []; + + for (const pkg of sortedImports) { + if (existingDeps.has(pkg)) { + alreadyInstalled.push(pkg); + } else { + toInstall.push(pkg); + } + } + + if (toInstall.length === 0) { + console.log(pc.green('\nAll @wordpress packages are already installed as optional dependencies.')); + return; + } + + console.log(`\nPackages to install with tag ${pc.cyan(tag)}:`); + for (const pkg of toInstall) { + console.log(` ${pc.green('+')} ${pkg}@${tag}`); + } + + if (alreadyInstalled.length > 0) { + console.log(`\nAlready installed (${alreadyInstalled.length} packages):`); + for (const pkg of alreadyInstalled) { + console.log(` ${pc.dim('•')} ${pkg}@${existingDeps.get(pkg)}`); + } + } + + if (workspaceScan) { + console.log(`\n${pc.dim('Installing at monorepo root:')} ${installDir}`); + } + + if (dryRun) { + console.log(pc.yellow('\nDry run - no changes made.')); + console.log(`Run without --dry-run to install packages.`); + return; + } + + // Install packages + console.log(`\nInstalling ${toInstall.length} packages...`); + + const installCmd = `npm install --save-optional ${toInstall.map(p => `${p}@${tag}`).join(' ')}`; + + try { + execSync(installCmd, { cwd: installDir, stdio: 'inherit' }); + console.log(pc.green('\n✓ WordPress dependencies synced successfully!')); + } catch (error) { + throw new Error('Failed to install packages. Check npm output above.'); + } +} + +/** + * Update @wordpress dependencies to a new tag + * + * Updates all @wordpress/* packages in optionalDependencies + * to the specified WordPress version tag. + */ +export async function updateWpDeps(options: { tag: string; dryRun?: boolean }): Promise { + const cwd = process.cwd(); + const { tag, dryRun = false } = options; + + console.log(pc.cyan('\n10up-build') + ' - Update WordPress Dependencies\n'); + + // Read current package.json + const packageJson = readPackageJson(cwd); + const existingDeps = getExistingWpDeps(packageJson); + + if (existingDeps.size === 0) { + console.log(pc.yellow('No @wordpress packages found in optionalDependencies.')); + console.log(`Run ${pc.cyan('10up-build sync-wp-deps')} first to add WordPress dependencies.`); + return; + } + + console.log(`Found ${pc.bold(String(existingDeps.size))} @wordpress packages to update:\n`); + + const sortedDeps = Array.from(existingDeps.entries()).sort((a, b) => a[0].localeCompare(b[0])); + + for (const [pkg, currentVersion] of sortedDeps) { + console.log(` ${pkg}: ${pc.dim(currentVersion)} → ${pc.green(tag)}`); + } + + if (dryRun) { + console.log(pc.yellow('\nDry run - no changes made.')); + console.log(`Run without --dry-run to update packages.`); + return; + } + + // Update packages + console.log(`\nUpdating to ${pc.cyan(tag)}...`); + + const packages = sortedDeps.map(([pkg]) => `${pkg}@${tag}`); + const installCmd = `npm install --save-optional ${packages.join(' ')}`; + + try { + execSync(installCmd, { cwd, stdio: 'inherit' }); + console.log(pc.green('\n✓ WordPress dependencies updated successfully!')); + } catch (error) { + throw new Error('Failed to update packages. Check npm output above.'); + } +} + +/** + * List current @wordpress dependencies + */ +export async function listWpDeps(): Promise { + const cwd = process.cwd(); + + console.log(pc.cyan('\n10up-build') + ' - WordPress Dependencies\n'); + + // Read current package.json + const packageJson = readPackageJson(cwd); + const existingDeps = getExistingWpDeps(packageJson); + + if (existingDeps.size === 0) { + console.log(pc.yellow('No @wordpress packages found in optionalDependencies.')); + console.log(`Run ${pc.cyan('10up-build sync-wp-deps')} to add WordPress dependencies.`); + return; + } + + console.log(`Installed @wordpress packages (${existingDeps.size}):\n`); + + const sortedDeps = Array.from(existingDeps.entries()).sort((a, b) => a[0].localeCompare(b[0])); + + for (const [pkg, version] of sortedDeps) { + console.log(` ${pc.dim('•')} ${pkg}@${pc.cyan(version)}`); + } + + // Also scan for imports and show any missing + console.log(`\nScanning source files for imports...`); + const foundImports = await scanForWordPressImports(cwd); + + const missing = Array.from(foundImports).filter(pkg => !existingDeps.has(pkg)).sort(); + + if (missing.length > 0) { + console.log(pc.yellow(`\nMissing packages (${missing.length}):`)); + for (const pkg of missing) { + console.log(` ${pc.red('!')} ${pkg}`); + } + console.log(`\nRun ${pc.cyan('10up-build sync-wp-deps')} to install missing packages.`); + } else { + console.log(pc.green('\nAll imported @wordpress packages are installed.')); + } +} + +/** + * Cache wpScript values from installed @wordpress/* packages + * + * Scans all installed @wordpress/* packages in node_modules and caches + * their wpScript boolean values to a JSON file. This allows CI environments + * to skip installing optional dependencies while still having accurate + * wpScript information for the build. + * + * The cache file should be committed to the repository so it's available + * in CI environments that use --omit=optional. + */ +export async function cacheWpScripts(options: { output?: string } = {}): Promise { + const cwd = process.cwd(); + const outputPath = options.output || join(cwd, WP_SCRIPTS_CACHE_FILE); + + console.log(pc.cyan('\n10up-build') + ' - Cache WordPress Script Flags\n'); + + // Find all installed @wordpress/* packages + const wpPackagesDir = join(cwd, 'node_modules', '@wordpress'); + + if (!existsSync(wpPackagesDir)) { + console.error(pc.red('Error: No @wordpress packages found in node_modules.')); + console.log(pc.dim('Install @wordpress/* packages first, then run this command.')); + process.exit(1); + } + + // Get all package directories + const packageDirs = await fg(['*'], { + cwd: wpPackagesDir, + onlyDirectories: true, + absolute: false, + }); + + if (packageDirs.length === 0) { + console.error(pc.red('Error: No @wordpress packages found in node_modules/@wordpress.')); + process.exit(1); + } + + console.log(`Found ${pc.bold(String(packageDirs.length))} @wordpress packages in node_modules.\n`); + + const cache: Record = {}; + let withWpScript = 0; + let withoutWpScript = 0; + + for (const dir of packageDirs.sort()) { + const packageName = `@wordpress/${dir}`; + const pkgJsonPath = join(wpPackagesDir, dir, 'package.json'); + + try { + const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8')); + const hasWpScript = pkgJson.wpScript === true; + cache[packageName] = hasWpScript; + + if (hasWpScript) { + withWpScript++; + console.log(` ${pc.green('✓')} ${packageName} ${pc.dim('(wpScript: true)')}`); + } else { + withoutWpScript++; + console.log(` ${pc.dim('•')} ${packageName}`); + } + } catch { + console.log(` ${pc.yellow('?')} ${packageName} ${pc.dim('(could not read package.json)')}`); + } + } + + // Write cache file + const cacheData = { + _comment: 'Auto-generated by 10up-build cache-wp-scripts. Do not edit manually.', + packages: cache, + }; + + writeFileSync(outputPath, JSON.stringify(cacheData, null, 2) + '\n'); + + console.log(`\n${pc.bold('Summary:')}`); + console.log(` ${pc.green(String(withWpScript))} packages with wpScript: true`); + console.log(` ${pc.dim(String(withoutWpScript))} packages without wpScript`); + console.log(`\n${pc.green('✓')} Cache written to ${pc.cyan(outputPath)}`); + console.log(pc.dim('\nCommit this file to your repository so CI can use it with --omit=optional.')); +} diff --git a/packages/build/tests/__snapshots__/snapshots.test.ts.snap b/packages/build/tests/__snapshots__/snapshots.test.ts.snap new file mode 100644 index 00000000..97ccd3c9 --- /dev/null +++ b/packages/build/tests/__snapshots__/snapshots.test.ts.snap @@ -0,0 +1,116 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Build Output Snapshots > basic-project > should produce correct admin.asset.php output > admin.asset.php 1`] = ` +" array('react', 'react-jsx-runtime', 'wp-blocks', 'wp-i18n'), 'version' => '[HASH]'); +" +`; + +exports[`Build Output Snapshots > basic-project > should produce correct admin.js output > admin.js 1`] = ` +""use strict";var __tenupBuild=(()=>{var _=Object.create;var i=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var B=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var m=(o,e)=>()=>(e||o((e={exports:{}}).exports,e),e.exports),J=(o,e)=>{for(var t in e)i(o,t,{get:e[t],enumerable:!0})},c=(o,e,t,d)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of y(e))!C.call(o,r)&&r!==t&&i(o,r,{get:()=>e[r],enumerable:!(d=b(e,r))||d.enumerable});return o};var p=(o,e,t)=>(t=o!=null?_(B(o)):{},c(e||!o||!o.__esModule?i(t,"default",{value:o,enumerable:!0}):t,o)),S=o=>c(i({},"__esModule",{value:!0}),o);var s=m((h,l)=>{l.exports=wp.blocks});var a=m((q,f)=>{f.exports=wp.i18n});var R=m((z,x)=>{x.exports=React});var n=m((D,u)=>{u.exports=ReactJSXRuntime});var T={};J(T,{default:()=>v});var A=p(s()),g=p(a()),k=p(R()),w=p(n());console.log("Admin loaded",A.registerBlockType,g.__,k.default);function v(){return(0,w.jsx)("div",{children:"Admin Component"})}return S(T);})(); +" +`; + +exports[`Build Output Snapshots > basic-project > should produce correct frontend.asset.php output > frontend.asset.php 1`] = ` +" array('wp-dom-ready'), 'version' => '[HASH]'); +" +`; + +exports[`Build Output Snapshots > basic-project > should produce correct frontend.js output > frontend.js 1`] = ` +""use strict";var __tenupBuild=(()=>{var t=Object.create;var a=Object.defineProperty;var s=Object.getOwnPropertyDescriptor;var y=Object.getOwnPropertyNames;var R=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty;var f=(d,o)=>()=>(o||d((o={exports:{}}).exports,o),o.exports);var g=(d,o,e,r)=>{if(o&&typeof o=="object"||typeof o=="function")for(let m of y(o))!c.call(d,m)&&m!==e&&a(d,m,{get:()=>o[m],enumerable:!(r=s(o,m))||r.enumerable});return d};var i=(d,o,e)=>(e=d!=null?t(R(d)):{},g(o||!d||!d.__esModule?a(e,"default",{value:d,enumerable:!0}):e,d));var n=f((x,l)=>{l.exports=wp.domReady});var p=i(n());(0,p.default)(()=>{console.log("Frontend loaded")});})(); +" +`; + +exports[`Build Output Snapshots > basic-project > should produce correct style.css output > style.css 1`] = ` +":root{--primary-color:#0073aa;--max-width:1200px}body{color:var(--primary-color);font-family:sans-serif;.container{max-width:var(--max-width);margin:0 auto}} +" +`; + +exports[`Build Output Snapshots > basic-project > should produce expected output files > output-files 1`] = ` +[ + "css/style.css", + "js/admin.asset.php", + "js/admin.js", + "js/frontend.asset.php", + "js/frontend.js", +] +`; + +exports[`Build Output Snapshots > block-project > should produce correct block.json output > block.json 1`] = ` +{ + "apiVersion": 3, + "category": "common", + "editorScript": "file:./index.js", + "editorStyle": "file:./editor.css", + "name": "test/test-block", + "style": "file:./style.css", + "title": "Test Block", + "version": "fb8b156d", + "viewScript": "file:./view.js", + "viewScriptModule": "file:./view-module.js", +} +`; + +exports[`Build Output Snapshots > block-project > should produce correct editor.css output > editor.css 1`] = ` +".wp-block-test-test-block{outline:2px dashed #0073aa} +" +`; + +exports[`Build Output Snapshots > block-project > should produce correct index.asset.php output > index.asset.php 1`] = ` +" array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-i18n'), 'version' => '[HASH]'); +" +`; + +exports[`Build Output Snapshots > block-project > should produce correct index.js (editor script) output > index.js 1`] = ` +""use strict";var __tenupBuild=(()=>{var x=Object.create;var d=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var R=Object.getPrototypeOf,_=Object.prototype.hasOwnProperty;var p=(o,t)=>()=>(t||o((t={exports:{}}).exports,t),t.exports);var g=(o,t,e,k)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of a(t))!_.call(o,s)&&s!==e&&d(o,s,{get:()=>t[s],enumerable:!(k=P(t,s))||k.enumerable});return o};var r=(o,t,e)=>(e=o!=null?x(R(o)):{},g(t||!o||!o.__esModule?d(e,"default",{value:o,enumerable:!0}):e,o));var v=p((E,m)=>{m.exports=wp.blocks});var n=p((J,b)=>{b.exports=wp.blockEditor});var T=p((S,B)=>{B.exports=wp.i18n});var c=p((X,f)=>{f.exports=ReactJSXRuntime});var u=r(v()),i=r(n()),w=r(T()),l=r(c());(0,u.registerBlockType)("test/test-block",{title:(0,w.__)("Test Block","test"),edit:()=>{let o=(0,i.useBlockProps)();return(0,l.jsx)("div",{...o,children:"Test Block"})},save:()=>{let o=i.useBlockProps.save();return(0,l.jsx)("div",{...o,children:"Test Block"})}});})(); +" +`; + +exports[`Build Output Snapshots > block-project > should produce correct style.css output > style.css 1`] = ` +".wp-block-test-test-block{border:1px solid #ccc;border-radius:4px;padding:20px} +" +`; + +exports[`Build Output Snapshots > block-project > should produce correct view.asset.php output > view.asset.php 1`] = ` +" array('wp-dom-ready'), 'version' => '[HASH]'); +" +`; + +exports[`Build Output Snapshots > block-project > should produce correct view.js output > view.js 1`] = ` +""use strict";var __tenupBuild=(()=>{var m=Object.create;var d=Object.defineProperty;var n=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var b=Object.getPrototypeOf,i=Object.prototype.hasOwnProperty;var p=(c,o)=>()=>(o||c((o={exports:{}}).exports,o),o.exports);var y=(c,o,e,t)=>{if(o&&typeof o=="object"||typeof o=="function")for(let l of a(o))!i.call(c,l)&&l!==e&&d(c,l,{get:()=>o[l],enumerable:!(t=n(o,l))||t.enumerable});return c};var f=(c,o,e)=>(e=c!=null?m(b(c)):{},y(o||!c||!c.__esModule?d(e,"default",{value:c,enumerable:!0}):e,c));var k=p((w,s)=>{s.exports=wp.domReady});var r=f(k());(0,r.default)(()=>{document.querySelectorAll(".wp-block-test-test-block").forEach(o=>{o.addEventListener("click",()=>{console.log("Block clicked")})})});})(); +" +`; + +exports[`Build Output Snapshots > block-project > should produce correct view-module.asset.php output > view-module.asset.php 1`] = ` +" array('@wordpress/interactivity'), 'version' => '[HASH]', 'type' => 'module'); +" +`; + +exports[`Build Output Snapshots > block-project > should produce correct view-module.js (ES module) output > view-module.js 1`] = ` +"import{store as e,getContext as o}from"@wordpress/interactivity";e("test/test-block",{actions:{toggle(){let t=o();t.isOpen=!t.isOpen}}}); +" +`; + +exports[`Build Output Snapshots > block-project > should produce expected output files > output-files 1`] = ` +[ + "blocks/test-block/block.json", + "blocks/test-block/editor.css", + "blocks/test-block/index.asset.php", + "blocks/test-block/index.js", + "blocks/test-block/style.css", + "blocks/test-block/view-module.asset.php", + "blocks/test-block/view-module.js", + "blocks/test-block/view.asset.php", + "blocks/test-block/view.js", +] +`; + +exports[`Build Output Snapshots > postcss-project > should produce correct style.css with PostCSS processing > style.css 1`] = ` +".header{background-color:var(--global-primary);padding:var(--global-spacing)}.main-container{max-width:var(--global-max-width);padding-inline:var(--global-spacing);margin-inline:auto}@media (width>=768px){.header{padding:calc(var(--global-spacing)*2)}} +" +`; + +exports[`Build Output Snapshots > postcss-project > should produce expected output files > output-files 1`] = ` +[ + "css/style.css", +] +`; diff --git a/packages/build/tests/block-json.test.ts b/packages/build/tests/block-json.test.ts new file mode 100644 index 00000000..4d15dad7 --- /dev/null +++ b/packages/build/tests/block-json.test.ts @@ -0,0 +1,158 @@ +/** + * Tests for block.json transformation + */ +import { describe, it, expect } from 'vitest'; +import { transformBlockJson } from '../src/plugins/block-json.js'; + +describe('Block JSON Transformation', () => { + describe('transformBlockJson', () => { + it('should transform TypeScript paths to JavaScript', () => { + const input = JSON.stringify({ + name: 'test/block', + editorScript: 'file:./index.ts', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.editorScript).toBe('file:./index.js'); + }); + + it('should transform TSX paths to JavaScript', () => { + const input = JSON.stringify({ + name: 'test/block', + editorScript: 'file:./index.tsx', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.editorScript).toBe('file:./index.js'); + }); + + it('should transform SCSS paths to CSS', () => { + const input = JSON.stringify({ + name: 'test/block', + style: 'file:./style.scss', + editorStyle: 'file:./editor.scss', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.style).toBe('file:./style.css'); + expect(result.editorStyle).toBe('file:./editor.css'); + }); + + it('should transform SASS paths to CSS', () => { + const input = JSON.stringify({ + name: 'test/block', + style: 'file:./style.sass', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.style).toBe('file:./style.css'); + }); + + it('should handle array asset fields', () => { + const input = JSON.stringify({ + name: 'test/block', + editorScript: ['file:./index.ts', 'file:./utils.tsx'], + style: ['file:./style.scss', 'file:./additional.sass'], + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.editorScript).toEqual(['file:./index.js', 'file:./utils.js']); + expect(result.style).toEqual(['file:./style.css', 'file:./additional.css']); + }); + + it('should not transform non-file: paths', () => { + const input = JSON.stringify({ + name: 'test/block', + editorScript: 'wp-block-editor', + style: 'https://example.com/style.css', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.editorScript).toBe('wp-block-editor'); + expect(result.style).toBe('https://example.com/style.css'); + }); + + it('should preserve existing version', () => { + const input = JSON.stringify({ + name: 'test/block', + version: '1.0.0', + editorScript: 'file:./index.ts', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.version).toBe('1.0.0'); + }); + + it('should handle all script asset fields', () => { + const input = JSON.stringify({ + name: 'test/block', + script: 'file:./script.ts', + editorScript: 'file:./editor.tsx', + viewScript: 'file:./view.ts', + scriptModule: 'file:./module.ts', + viewScriptModule: 'file:./view-module.tsx', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.script).toBe('file:./script.js'); + expect(result.editorScript).toBe('file:./editor.js'); + expect(result.viewScript).toBe('file:./view.js'); + // ES modules also output as .js for better server compatibility + expect(result.scriptModule).toBe('file:./module.js'); + expect(result.viewScriptModule).toBe('file:./view-module.js'); + }); + + it('should handle all style asset fields', () => { + const input = JSON.stringify({ + name: 'test/block', + style: 'file:./style.scss', + editorStyle: 'file:./editor.scss', + viewStyle: 'file:./view.scss', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.style).toBe('file:./style.css'); + expect(result.editorStyle).toBe('file:./editor.css'); + expect(result.viewStyle).toBe('file:./view.css'); + }); + + it('should preserve other block.json properties', () => { + const input = JSON.stringify({ + apiVersion: 3, + name: 'test/block', + title: 'Test Block', + category: 'common', + icon: 'smiley', + description: 'A test block', + supports: { + html: false, + align: true, + }, + attributes: { + content: { type: 'string' }, + }, + editorScript: 'file:./index.ts', + }); + + const result = JSON.parse(transformBlockJson(input, '/path/to/block.json')); + expect(result.apiVersion).toBe(3); + expect(result.name).toBe('test/block'); + expect(result.title).toBe('Test Block'); + expect(result.category).toBe('common'); + expect(result.icon).toBe('smiley'); + expect(result.supports).toEqual({ html: false, align: true }); + expect(result.attributes).toEqual({ content: { type: 'string' } }); + }); + + it('should return original content for invalid JSON', () => { + const input = 'not valid json'; + const result = transformBlockJson(input, '/path/to/block.json'); + expect(result).toBe(input); + }); + + it('should return empty content as-is', () => { + expect(transformBlockJson('', '/path/to/block.json')).toBe(''); + expect(transformBlockJson(' ', '/path/to/block.json')).toBe(' '); + }); + }); +}); diff --git a/packages/build/tests/build.test.ts b/packages/build/tests/build.test.ts new file mode 100644 index 00000000..f3737f42 --- /dev/null +++ b/packages/build/tests/build.test.ts @@ -0,0 +1,483 @@ +/** + * Integration tests for the build process + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { resolve, join } from 'node:path'; +import { existsSync, readFileSync, rmSync } from 'node:fs'; +import { build } from '../src/build.js'; +import { defaultConfig } from '../src/config.js'; +import type { BuildConfig } from '../src/types.js'; + +// Store original cwd and env +const originalCwd = process.cwd(); +const originalEnv = process.env.NODE_ENV; + +describe('Build Process', () => { + beforeEach(() => { + process.chdir(originalCwd); + process.env.NODE_ENV = 'production'; + }); + + afterEach(() => { + process.chdir(originalCwd); + process.env.NODE_ENV = originalEnv; + }); + + describe('build with basic project', () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + const distDir = join(fixtureDir, 'dist'); + + beforeEach(() => { + // Clean dist directory before each test + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + afterEach(() => { + // Clean up after tests + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + it('should build JavaScript entries', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + admin: './assets/js/admin.js', + frontend: './assets/js/frontend.js', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + expect(result.entries.scripts).toBe(2); + + // Check output files exist in js/ subdirectory + expect(existsSync(join(distDir, 'js/admin.js'))).toBe(true); + expect(existsSync(join(distDir, 'js/frontend.js'))).toBe(true); + }); + + it('should generate .asset.php files', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + admin: './assets/js/admin.js', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + + // Check .asset.php exists in js/ subdirectory + const assetPhpPath = join(distDir, 'js/admin.asset.php'); + expect(existsSync(assetPhpPath)).toBe(true); + + // Check .asset.php content + const assetPhpContent = readFileSync(assetPhpPath, 'utf8'); + expect(assetPhpContent).toContain(' { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + admin: './assets/js/admin.js', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + + const assetPhpContent = readFileSync(join(distDir, 'js/admin.asset.php'), 'utf8'); + + // admin.js imports @wordpress/blocks and @wordpress/i18n + expect(assetPhpContent).toContain('wp-blocks'); + expect(assetPhpContent).toContain('wp-i18n'); + }); + + it('should track React dependencies in .asset.php', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + admin: './assets/js/admin.js', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + + const assetPhpContent = readFileSync(join(distDir, 'js/admin.asset.php'), 'utf8'); + + // admin.js imports React + expect(assetPhpContent).toContain('react'); + }); + + it('should build CSS entries with PostCSS', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + style: './assets/css/style.css', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + expect(result.entries.styles).toBe(1); + + // Check CSS output exists in css/ subdirectory + expect(existsSync(join(distDir, 'css/style.css'))).toBe(true); + + // Check CSS content is compiled + const cssContent = readFileSync(join(distDir, 'css/style.css'), 'utf8'); + expect(cssContent).toContain('font-family'); + // CSS custom property should be preserved + expect(cssContent).toContain('#0073aa'); + }); + + it('should minify CSS in production', async () => { + process.chdir(fixtureDir); + process.env.NODE_ENV = 'production'; + + const config: Partial = { + entry: { + style: './assets/css/style.css', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + + const cssContent = readFileSync(join(distDir, 'css/style.css'), 'utf8'); + // Minified CSS should not have unnecessary whitespace + expect(cssContent).not.toMatch(/\n\s+\n/); + }); + + it('should return build timing info', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + admin: './assets/js/admin.js', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + expect(result.duration).toBeGreaterThan(0); + expect(result.duration).toBeLessThan(10000); // Should be fast! + }); + }); + + describe('build with block project', () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + const distDir = join(fixtureDir, 'dist'); + + beforeEach(() => { + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + afterEach(() => { + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + it('should build block entries from block.json', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: true, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + expect(result.entries.scripts).toBeGreaterThan(0); + }); + + it('should copy and transform block.json files', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: true, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + + // Check transformed block.json exists + const blockJsonPath = join(distDir, 'blocks/test-block/block.json'); + expect(existsSync(blockJsonPath)).toBe(true); + + // Check block.json content is transformed + const blockJson = JSON.parse(readFileSync(blockJsonPath, 'utf8')); + expect(blockJson.editorScript).toBe('file:./index.js'); // .tsx -> .js + expect(blockJson.editorStyle).toBe('file:./editor.css'); + expect(blockJson.style).toBe('file:./style.css'); + }); + + it('should generate ES module .asset.php with type: module', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: true, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + expect(result.entries.modules).toBeGreaterThan(0); + + // Find the ES module .asset.php + const moduleAssetPath = join(distDir, 'blocks/test-block/view-module.asset.php'); + if (existsSync(moduleAssetPath)) { + const assetContent = readFileSync(moduleAssetPath, 'utf8'); + expect(assetContent).toContain("'type' => 'module'"); + // ES modules use package names, not handles + expect(assetContent).toContain('@wordpress/interactivity'); + } + }); + }); + + describe('build error handling', () => { + it('should return error result for invalid entry', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + // Clean dist first + const distDir = join(fixtureDir, 'dist'); + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + + const config: Partial = { + entry: { + broken: './nonexistent/file.js', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + // With no valid entries, build should complete but with 0 entries + const result = await build(config); + expect(result.entries.scripts).toBe(0); + }); + }); + + describe('build performance', () => { + it('should complete build in under 5 seconds', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + process.chdir(fixtureDir); + + // Clean dist first + const distDir = join(fixtureDir, 'dist'); + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + + const config: Partial = { + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: true, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + expect(result.duration).toBeLessThan(5000); + }); + }); + + describe('build with PostCSS configuration', () => { + const fixtureDir = resolve(__dirname, 'fixtures/postcss-project'); + const distDir = join(fixtureDir, 'dist'); + + beforeEach(() => { + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + afterEach(() => { + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + it('should process CSS with globalStylesDir custom properties', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + style: './assets/css/style.css', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + globalStylesDir: './assets/css/globals/', + globalMixinsDir: './assets/css/mixins/', + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + expect(result.entries.styles).toBe(1); + + // Check CSS output exists in css/ subdirectory + expect(existsSync(join(distDir, 'css/style.css'))).toBe(true); + + // Check CSS uses global custom properties + const cssContent = readFileSync(join(distDir, 'css/style.css'), 'utf8'); + expect(cssContent).toContain('--global-primary'); + expect(cssContent).toContain('--global-spacing'); + }); + + it('should process CSS with globalMixinsDir mixins', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + style: './assets/css/style.css', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + globalStylesDir: './assets/css/globals/', + globalMixinsDir: './assets/css/mixins/', + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + + const cssContent = readFileSync(join(distDir, 'css/style.css'), 'utf8'); + // Mixin should be expanded - check for mixin content + expect(cssContent).toContain('max-width'); + expect(cssContent).toContain('margin-inline'); + }); + + it('should process custom media queries from globalStylesDir', async () => { + process.chdir(fixtureDir); + + const config: Partial = { + entry: { + style: './assets/css/style.css', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + globalStylesDir: './assets/css/globals/', + globalMixinsDir: './assets/css/mixins/', + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + const result = await build(config); + + expect(result.success).toBe(true); + + const cssContent = readFileSync(join(distDir, 'css/style.css'), 'utf8'); + // Custom media should be transformed to standard media query + expect(cssContent).toContain('@media'); + expect(cssContent).toContain('768px'); + }); + }); +}); diff --git a/packages/build/tests/config.test.ts b/packages/build/tests/config.test.ts new file mode 100644 index 00000000..4f193594 --- /dev/null +++ b/packages/build/tests/config.test.ts @@ -0,0 +1,176 @@ +/** + * Tests for configuration loading + */ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { resolve } from 'node:path'; +import { defaultConfig } from '../src/config.js'; + +// Store original cwd +const originalCwd = process.cwd(); + +describe('Configuration Loading', () => { + beforeEach(() => { + // Reset to original directory before each test + process.chdir(originalCwd); + }); + + afterEach(() => { + process.chdir(originalCwd); + vi.resetModules(); + }); + + describe('defaultConfig', () => { + it('should have correct default paths', () => { + expect(defaultConfig.paths.blocksDir).toBe('./includes/blocks/'); + expect(defaultConfig.paths.srcDir).toBe('./assets/'); + expect(defaultConfig.paths.copyAssetsDir).toBe('./assets/'); + expect(defaultConfig.paths.blocksStyles).toBe('./assets/css/blocks/'); + expect(defaultConfig.paths.globalStylesDir).toBe('./assets/css/globals/'); + expect(defaultConfig.paths.globalMixinsDir).toBe('./assets/css/mixins/'); + }); + + it('should have correct default filenames', () => { + expect(defaultConfig.filenames.js).toBe('js/[name].js'); + expect(defaultConfig.filenames.jsChunk).toBe('js/[name].[contenthash].chunk.js'); + expect(defaultConfig.filenames.css).toBe('css/[name].css'); + expect(defaultConfig.filenames.block).toBe('blocks/[name].js'); + expect(defaultConfig.filenames.blockCSS).toBe('blocks/[name].css'); + }); + + it('should have wpDependencyExternals enabled by default', () => { + expect(defaultConfig.wpDependencyExternals).toBe(true); + }); + + it('should have useBlockAssets disabled by default', () => { + expect(defaultConfig.useBlockAssets).toBe(false); + }); + + it('should have hot reload disabled by default', () => { + expect(defaultConfig.hot).toBe(false); + }); + + it('should have correct default dev server port', () => { + expect(defaultConfig.devServerPort).toBe(8887); + }); + + it('should have empty entry points by default', () => { + expect(defaultConfig.entry).toEqual({}); + expect(defaultConfig.moduleEntry).toEqual({}); + }); + + it('should have empty external namespaces by default', () => { + expect(defaultConfig.externalNamespaces).toEqual({}); + }); + }); + + describe('loadConfig', () => { + it('should load config from basic project', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + // Re-import to get fresh module with new cwd + const { loadConfig } = await import('../src/config.js'); + const config = loadConfig(); + + expect(config.entry).toEqual({ + admin: './assets/js/admin.js', + frontend: './assets/js/frontend.js', + style: './assets/css/style.scss', + }); + expect(config.wpDependencyExternals).toBe(true); + expect(config.useBlockAssets).toBe(false); + }); + + it('should load config from block project', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + process.chdir(fixtureDir); + + const { loadConfig } = await import('../src/config.js'); + const config = loadConfig(); + + expect(config.entry).toEqual({}); + expect(config.useBlockAssets).toBe(true); + expect(config.wpDependencyExternals).toBe(true); + }); + + it('should merge paths with defaults', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + const { loadConfig } = await import('../src/config.js'); + const config = loadConfig(); + + // Custom path from fixture + expect(config.paths.blocksDir).toBe('./includes/blocks/'); + // Default path (not overridden) + expect(config.paths.globalStylesDir).toBe('./assets/css/globals/'); + }); + }); + + describe('isProduction', () => { + const originalEnv = process.env.NODE_ENV; + + afterEach(() => { + process.env.NODE_ENV = originalEnv; + }); + + it('should return true when NODE_ENV is production', async () => { + process.env.NODE_ENV = 'production'; + const { isProduction } = await import('../src/config.js'); + expect(isProduction()).toBe(true); + }); + + it('should return false when NODE_ENV is development', async () => { + process.env.NODE_ENV = 'development'; + const { isProduction } = await import('../src/config.js'); + expect(isProduction()).toBe(false); + }); + + it('should return false when NODE_ENV is not set', async () => { + delete process.env.NODE_ENV; + const { isProduction } = await import('../src/config.js'); + expect(isProduction()).toBe(false); + }); + }); + + describe('getOutputDir', () => { + it('should return dist directory path', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + const { loadConfig, getOutputDir } = await import('../src/config.js'); + const config = loadConfig(); + const outputDir = getOutputDir(config); + + expect(outputDir).toBe(resolve(fixtureDir, 'dist')); + }); + }); + + describe('Configuration with externalNamespaces', () => { + it('should support custom external namespaces', async () => { + // Create a temp config with external namespaces + const config = { + ...defaultConfig, + externalNamespaces: { + '@woocommerce': { + global: 'wc', + handlePrefix: 'wc', + }, + '@my-plugin': { + global: 'myPlugin', + handlePrefix: 'my-plugin', + }, + }, + }; + + expect(config.externalNamespaces['@woocommerce']).toEqual({ + global: 'wc', + handlePrefix: 'wc', + }); + expect(config.externalNamespaces['@my-plugin']).toEqual({ + global: 'myPlugin', + handlePrefix: 'my-plugin', + }); + }); + }); +}); diff --git a/packages/build/tests/entry-detection.test.ts b/packages/build/tests/entry-detection.test.ts new file mode 100644 index 00000000..b9034e18 --- /dev/null +++ b/packages/build/tests/entry-detection.test.ts @@ -0,0 +1,237 @@ +/** + * Tests for entry point detection + */ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { resolve } from 'node:path'; +import { detectEntries } from '../src/utils/entry-detection.js'; +import { defaultConfig } from '../src/config.js'; +import type { BuildConfig } from '../src/types.js'; + +// Store original cwd +const originalCwd = process.cwd(); + +describe('Entry Detection', () => { + beforeEach(() => { + process.chdir(originalCwd); + }); + + afterEach(() => { + process.chdir(originalCwd); + }); + + describe('detectEntries with basic project', () => { + it('should detect entries from config.entry', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: { + admin: './assets/js/admin.js', + frontend: './assets/js/frontend.js', + style: './assets/css/style.css', + }, + useBlockAssets: false, + }; + + const entries = await detectEntries(config); + + // Should have 2 script entries and 1 style entry + expect(Object.keys(entries.scripts)).toHaveLength(2); + expect(Object.keys(entries.styles)).toHaveLength(1); + expect(Object.keys(entries.modules)).toHaveLength(0); + + // Check script entries (prefixed with js/) + expect(entries.scripts['js/admin']).toContain('admin.js'); + expect(entries.scripts['js/frontend']).toContain('frontend.js'); + + // Check style entries (prefixed with css/) + expect(entries.styles['css/style']).toContain('style.css'); + }); + + it('should separate JS and CSS entries', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: { + admin: './assets/js/admin.js', + style: './assets/css/style.css', + }, + useBlockAssets: false, + }; + + const entries = await detectEntries(config); + + expect(entries.scripts['js/admin']).toBeDefined(); + expect(entries.scripts['js/style']).toBeUndefined(); + expect(entries.styles['css/style']).toBeDefined(); + expect(entries.styles['css/admin']).toBeUndefined(); + }); + + it('should skip non-existent files', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: { + admin: './assets/js/admin.js', + nonexistent: './assets/js/does-not-exist.js', + }, + useBlockAssets: false, + }; + + const entries = await detectEntries(config); + + expect(entries.scripts['js/admin']).toBeDefined(); + expect(entries.scripts['js/nonexistent']).toBeUndefined(); + }); + }); + + describe('detectEntries with block project', () => { + it('should detect entries from block.json files', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + }, + useBlockAssets: true, + }; + + const entries = await detectEntries(config); + + // Should detect editorScript, viewScript from block.json + expect(Object.keys(entries.scripts).length).toBeGreaterThan(0); + + // Should detect viewScriptModule + expect(Object.keys(entries.modules).length).toBeGreaterThan(0); + + // Should detect style, editorStyle + expect(Object.keys(entries.styles).length).toBeGreaterThan(0); + }); + + it('should find TypeScript source files for JS references', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + }, + useBlockAssets: true, + }; + + const entries = await detectEntries(config); + + // Should find index.tsx for editorScript: "file:./index.tsx" + const indexEntry = Object.entries(entries.scripts).find(([key]) => + key.includes('test-block/index'), + ); + expect(indexEntry).toBeDefined(); + expect(indexEntry?.[1]).toContain('.tsx'); + }); + + it('should find CSS source files for CSS references', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + }, + useBlockAssets: true, + }; + + const entries = await detectEntries(config); + + // Should find style.css for style: "file:./style.css" + const styleEntry = Object.entries(entries.styles).find(([key]) => + key.includes('test-block/style'), + ); + expect(styleEntry).toBeDefined(); + expect(styleEntry?.[1]).toContain('.css'); + }); + + it('should detect ES module entries', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + }, + useBlockAssets: true, + }; + + const entries = await detectEntries(config); + + // Should detect viewScriptModule + const moduleEntry = Object.entries(entries.modules).find(([key]) => + key.includes('view-module'), + ); + expect(moduleEntry).toBeDefined(); + }); + }); + + describe('detectEntries with moduleEntry config', () => { + it('should detect module entries from config.moduleEntry', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: {}, + moduleEntry: { + 'custom-module': './includes/blocks/test-block/view-module.js', + }, + useBlockAssets: false, + }; + + const entries = await detectEntries(config); + + // Module entries are prefixed with js/ + expect(entries.modules['js/custom-module']).toBeDefined(); + expect(entries.modules['js/custom-module']).toContain('view-module.js'); + }); + }); + + describe('detectEntries with empty config', () => { + it('should return empty entries when no sources found', async () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + process.chdir(fixtureDir); + + const config: BuildConfig = { + ...defaultConfig, + entry: {}, + moduleEntry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './nonexistent-blocks/', + }, + useBlockAssets: true, + }; + + const entries = await detectEntries(config); + + expect(Object.keys(entries.scripts)).toHaveLength(0); + expect(Object.keys(entries.modules)).toHaveLength(0); + expect(Object.keys(entries.styles)).toHaveLength(0); + }); + }); +}); diff --git a/packages/build/tests/externals.test.ts b/packages/build/tests/externals.test.ts new file mode 100644 index 00000000..7968db21 --- /dev/null +++ b/packages/build/tests/externals.test.ts @@ -0,0 +1,322 @@ +/** + * Tests for WordPress externals handling + */ +import { describe, it, expect } from 'vitest'; +import { + vendorExternals, + wpPackageToHandle, + wpPackageToGlobal, + resolveExternal, + getExternalPatterns, +} from '../src/utils/externals.js'; +import type { BuildConfig } from '../src/types.js'; + +describe('WordPress Externals', () => { + describe('vendorExternals', () => { + it('should include React packages', () => { + expect(vendorExternals.react).toEqual({ global: 'React', handle: 'react' }); + expect(vendorExternals['react-dom']).toEqual({ global: 'ReactDOM', handle: 'react-dom' }); + expect(vendorExternals['react/jsx-runtime']).toEqual({ + global: 'ReactJSXRuntime', + handle: 'react-jsx-runtime', + }); + }); + + it('should include lodash packages', () => { + expect(vendorExternals.lodash).toEqual({ global: 'lodash', handle: 'lodash' }); + expect(vendorExternals['lodash-es']).toEqual({ global: 'lodash', handle: 'lodash' }); + }); + + it('should include moment', () => { + expect(vendorExternals.moment).toEqual({ global: 'moment', handle: 'moment' }); + }); + + it('should include jQuery', () => { + expect(vendorExternals.jquery).toEqual({ global: 'jQuery', handle: 'jquery' }); + }); + }); + + describe('wpPackageToHandle', () => { + it('should convert @wordpress/blocks to wp-blocks', () => { + expect(wpPackageToHandle('@wordpress/blocks')).toBe('wp-blocks'); + }); + + it('should convert @wordpress/element to wp-element', () => { + expect(wpPackageToHandle('@wordpress/element')).toBe('wp-element'); + }); + + it('should convert @wordpress/i18n to wp-i18n', () => { + expect(wpPackageToHandle('@wordpress/i18n')).toBe('wp-i18n'); + }); + + it('should convert @wordpress/block-editor to wp-block-editor', () => { + expect(wpPackageToHandle('@wordpress/block-editor')).toBe('wp-block-editor'); + }); + + it('should convert @wordpress/interactivity to wp-interactivity', () => { + expect(wpPackageToHandle('@wordpress/interactivity')).toBe('wp-interactivity'); + }); + }); + + describe('wpPackageToGlobal', () => { + it('should convert @wordpress/blocks to wp.blocks', () => { + expect(wpPackageToGlobal('@wordpress/blocks')).toBe('wp.blocks'); + }); + + it('should convert @wordpress/element to wp.element', () => { + expect(wpPackageToGlobal('@wordpress/element')).toBe('wp.element'); + }); + + it('should convert @wordpress/block-editor to wp.blockEditor (camelCase)', () => { + expect(wpPackageToGlobal('@wordpress/block-editor')).toBe('wp.blockEditor'); + }); + + it('should convert @wordpress/dom-ready to wp.domReady (camelCase)', () => { + expect(wpPackageToGlobal('@wordpress/dom-ready')).toBe('wp.domReady'); + }); + }); + + describe('resolveExternal', () => { + const baseConfig: Partial = { + externalNamespaces: {}, + }; + + it('should resolve vendor externals', () => { + expect(resolveExternal('react', baseConfig)).toEqual({ + global: 'React', + handle: 'react', + }); + expect(resolveExternal('react-dom', baseConfig)).toEqual({ + global: 'ReactDOM', + handle: 'react-dom', + }); + expect(resolveExternal('lodash', baseConfig)).toEqual({ + global: 'lodash', + handle: 'lodash', + }); + }); + + it('should resolve @wordpress packages', () => { + const result = resolveExternal('@wordpress/blocks', baseConfig); + // Use toMatchObject to ignore _isInstalled which is an implementation detail + expect(result).toMatchObject({ + global: 'wp.blocks', + handle: 'wp-blocks', + }); + }); + + it('should resolve @wordpress/block-editor with camelCase', () => { + const result = resolveExternal('@wordpress/block-editor', baseConfig); + expect(result).toMatchObject({ + global: 'wp.blockEditor', + handle: 'wp-block-editor', + }); + }); + + it('should return null for unknown packages', () => { + expect(resolveExternal('some-random-package', baseConfig)).toBeNull(); + expect(resolveExternal('@unknown/package', baseConfig)).toBeNull(); + }); + + describe('with custom external namespaces', () => { + const configWithNamespaces: Partial = { + externalNamespaces: { + '@woocommerce': { + global: 'wc', + handlePrefix: 'wc', + }, + '@my-plugin': { + global: 'myPlugin', + handlePrefix: 'my-plugin', + }, + }, + }; + + it('should resolve @woocommerce packages', () => { + const result = resolveExternal('@woocommerce/components', configWithNamespaces); + expect(result).toEqual({ + global: 'wc.components', + handle: 'wc-components', + }); + }); + + it('should resolve @woocommerce/data with camelCase', () => { + const result = resolveExternal('@woocommerce/csv-export', configWithNamespaces); + expect(result).toEqual({ + global: 'wc.csvExport', + handle: 'wc-csv-export', + }); + }); + + it('should resolve custom plugin namespace', () => { + const result = resolveExternal('@my-plugin/utils', configWithNamespaces); + expect(result).toEqual({ + global: 'myPlugin.utils', + handle: 'my-plugin-utils', + }); + }); + + it('should still resolve @wordpress packages with custom namespaces', () => { + const result = resolveExternal('@wordpress/blocks', configWithNamespaces); + expect(result).toMatchObject({ + global: 'wp.blocks', + handle: 'wp-blocks', + }); + }); + + it('should resolve packages with multiple hyphens correctly', () => { + const result = resolveExternal( + '@woocommerce/product-editor-component', + configWithNamespaces, + ); + expect(result).toEqual({ + global: 'wc.productEditorComponent', + handle: 'wc-product-editor-component', + }); + }); + + it('should resolve single-word package names', () => { + const result = resolveExternal('@woocommerce/data', configWithNamespaces); + expect(result).toEqual({ + global: 'wc.data', + handle: 'wc-data', + }); + }); + + it('should not resolve bare namespace without sub-package', () => { + const result = resolveExternal('@woocommerce', configWithNamespaces); + expect(result).toBeNull(); + }); + + it('should not resolve packages from unregistered namespaces', () => { + const result = resolveExternal('@unregistered/package', configWithNamespaces); + expect(result).toBeNull(); + }); + + it('should still resolve vendor externals with custom namespaces', () => { + expect(resolveExternal('react', configWithNamespaces)).toEqual({ + global: 'React', + handle: 'react', + }); + expect(resolveExternal('lodash', configWithNamespaces)).toEqual({ + global: 'lodash', + handle: 'lodash', + }); + }); + }); + + describe('with agency namespace configuration', () => { + const agencyConfig: Partial = { + externalNamespaces: { + '@10up': { + global: 'tenUp', + handlePrefix: '10up', + }, + '@developer': { + global: 'dev', + handlePrefix: 'developer', + }, + }, + }; + + it('should resolve @10up packages with numeric prefix', () => { + const result = resolveExternal('@10up/block-components', agencyConfig); + expect(result).toEqual({ + global: 'tenUp.blockComponents', + handle: '10up-block-components', + }); + }); + + it('should resolve multiple packages from same namespace', () => { + const utils = resolveExternal('@10up/utils', agencyConfig); + const hooks = resolveExternal('@10up/hooks', agencyConfig); + const components = resolveExternal('@10up/components', agencyConfig); + + expect(utils).toEqual({ global: 'tenUp.utils', handle: '10up-utils' }); + expect(hooks).toEqual({ global: 'tenUp.hooks', handle: '10up-hooks' }); + expect(components).toEqual({ global: 'tenUp.components', handle: '10up-components' }); + }); + + it('should handle different global naming conventions', () => { + const result = resolveExternal('@developer/api-client', agencyConfig); + expect(result).toEqual({ + global: 'dev.apiClient', + handle: 'developer-api-client', + }); + }); + }); + + describe('edge cases for external namespaces', () => { + it('should handle empty externalNamespaces object', () => { + const result = resolveExternal('@custom/package', { externalNamespaces: {} }); + expect(result).toBeNull(); + }); + + it('should handle undefined externalNamespaces', () => { + const result = resolveExternal('@custom/package', {}); + expect(result).toBeNull(); + }); + + it('should prioritize @wordpress over custom namespaces', () => { + const configWithWordPressOverride: Partial = { + externalNamespaces: { + '@wordpress': { + global: 'customWp', + handlePrefix: 'custom-wp', + }, + }, + }; + // @wordpress packages should still use the built-in resolution + const result = resolveExternal('@wordpress/blocks', configWithWordPressOverride); + expect(result).toMatchObject({ + global: 'wp.blocks', + handle: 'wp-blocks', + }); + }); + + it('should handle namespace with trailing slash in package name', () => { + const config: Partial = { + externalNamespaces: { + '@test': { global: 'test', handlePrefix: 'test' }, + }, + }; + // This tests internal handling - the slash after namespace is expected + const result = resolveExternal('@test/my-component', config); + expect(result).toEqual({ + global: 'test.myComponent', + handle: 'test-my-component', + }); + }); + }); + }); + + describe('getExternalPatterns', () => { + it('should return an empty array', () => { + // All externalization is now handled by the wp-dependency-extraction plugin + // to support per-package decisions and virtual modules for IIFE builds + const patterns = getExternalPatterns(); + expect(patterns).toEqual([]); + }); + + it('should return empty array even with custom namespaces', () => { + // Custom namespaces are also handled by the plugin's onResolve hooks + const patterns = getExternalPatterns({ + externalNamespaces: { + '@woocommerce': { global: 'wc', handlePrefix: 'wc' }, + '@my-plugin': { global: 'myPlugin', handlePrefix: 'my-plugin' }, + }, + }); + expect(patterns).toEqual([]); + }); + }); + + describe('subpath exports', () => { + it('should convert @wordpress/dataviews/wp to wp-dataviews/wp handle', () => { + expect(wpPackageToHandle('@wordpress/dataviews/wp')).toBe('wp-dataviews/wp'); + }); + + it('should convert @wordpress/dataviews/wp to wp.dataviews/wp global', () => { + expect(wpPackageToGlobal('@wordpress/dataviews/wp')).toBe('wp.dataviews/wp'); + }); + }); +}); diff --git a/packages/build/tests/fixtures/basic-project/assets/css/style.css b/packages/build/tests/fixtures/basic-project/assets/css/style.css new file mode 100644 index 00000000..6300255b --- /dev/null +++ b/packages/build/tests/fixtures/basic-project/assets/css/style.css @@ -0,0 +1,17 @@ +/** + * Test stylesheet with PostCSS features + */ +:root { + --primary-color: #0073aa; + --max-width: 1200px; +} + +body { + font-family: sans-serif; + color: var(--primary-color); + + & .container { + max-width: var(--max-width); + margin: 0 auto; + } +} diff --git a/packages/build/tests/fixtures/basic-project/assets/js/admin.js b/packages/build/tests/fixtures/basic-project/assets/js/admin.js new file mode 100644 index 00000000..6b9448c2 --- /dev/null +++ b/packages/build/tests/fixtures/basic-project/assets/js/admin.js @@ -0,0 +1,12 @@ +/** + * Admin script for testing + */ +import { registerBlockType } from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; +import React from 'react'; + +console.log('Admin loaded', registerBlockType, __, React); + +export default function Admin() { + return
Admin Component
; +} diff --git a/packages/build/tests/fixtures/basic-project/assets/js/frontend.js b/packages/build/tests/fixtures/basic-project/assets/js/frontend.js new file mode 100644 index 00000000..2a0f80e7 --- /dev/null +++ b/packages/build/tests/fixtures/basic-project/assets/js/frontend.js @@ -0,0 +1,8 @@ +/** + * Frontend script for testing + */ +import domReady from '@wordpress/dom-ready'; + +domReady(() => { + console.log('Frontend loaded'); +}); diff --git a/packages/build/tests/fixtures/basic-project/package.json b/packages/build/tests/fixtures/basic-project/package.json new file mode 100644 index 00000000..bf4d63e4 --- /dev/null +++ b/packages/build/tests/fixtures/basic-project/package.json @@ -0,0 +1,18 @@ +{ + "name": "test-basic-project", + "version": "1.0.0", + "10up-toolkit": { + "entry": { + "admin": "./assets/js/admin.js", + "frontend": "./assets/js/frontend.js", + "style": "./assets/css/style.scss" + }, + "paths": { + "blocksDir": "./includes/blocks/", + "srcDir": "./assets/", + "copyAssetsDir": "./assets/" + }, + "wpDependencyExternals": true, + "useBlockAssets": false + } +} diff --git a/packages/build/tests/fixtures/block-project/includes/blocks/test-block/block.json b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/block.json new file mode 100644 index 00000000..12b86598 --- /dev/null +++ b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/block.json @@ -0,0 +1,11 @@ +{ + "apiVersion": 3, + "name": "test/test-block", + "title": "Test Block", + "category": "common", + "editorScript": "file:./index.tsx", + "editorStyle": "file:./editor.css", + "style": "file:./style.css", + "viewScript": "file:./view.js", + "viewScriptModule": "file:./view-module.js" +} diff --git a/packages/build/tests/fixtures/block-project/includes/blocks/test-block/editor.css b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/editor.css new file mode 100644 index 00000000..8d55ee06 --- /dev/null +++ b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/editor.css @@ -0,0 +1,3 @@ +.wp-block-test-test-block { + outline: 2px dashed #0073aa; +} diff --git a/packages/build/tests/fixtures/block-project/includes/blocks/test-block/index.tsx b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/index.tsx new file mode 100644 index 00000000..316cfc57 --- /dev/null +++ b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/index.tsx @@ -0,0 +1,18 @@ +/** + * Test block editor script + */ +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; +import { __ } from '@wordpress/i18n'; + +registerBlockType('test/test-block', { + title: __('Test Block', 'test'), + edit: () => { + const blockProps = useBlockProps(); + return
Test Block
; + }, + save: () => { + const blockProps = useBlockProps.save(); + return
Test Block
; + }, +}); diff --git a/packages/build/tests/fixtures/block-project/includes/blocks/test-block/style.css b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/style.css new file mode 100644 index 00000000..8c3e9350 --- /dev/null +++ b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/style.css @@ -0,0 +1,5 @@ +.wp-block-test-test-block { + padding: 20px; + border: 1px solid #ccc; + border-radius: 4px; +} diff --git a/packages/build/tests/fixtures/block-project/includes/blocks/test-block/view-module.js b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/view-module.js new file mode 100644 index 00000000..7069d0d2 --- /dev/null +++ b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/view-module.js @@ -0,0 +1,13 @@ +/** + * Test block view script module (ES Module) + */ +import { store, getContext } from '@wordpress/interactivity'; + +store('test/test-block', { + actions: { + toggle() { + const context = getContext(); + context.isOpen = !context.isOpen; + }, + }, +}); diff --git a/packages/build/tests/fixtures/block-project/includes/blocks/test-block/view.js b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/view.js new file mode 100644 index 00000000..2ec26401 --- /dev/null +++ b/packages/build/tests/fixtures/block-project/includes/blocks/test-block/view.js @@ -0,0 +1,13 @@ +/** + * Test block view script + */ +import domReady from '@wordpress/dom-ready'; + +domReady(() => { + const blocks = document.querySelectorAll('.wp-block-test-test-block'); + blocks.forEach((block) => { + block.addEventListener('click', () => { + console.log('Block clicked'); + }); + }); +}); diff --git a/packages/build/tests/fixtures/block-project/package.json b/packages/build/tests/fixtures/block-project/package.json new file mode 100644 index 00000000..e7badf50 --- /dev/null +++ b/packages/build/tests/fixtures/block-project/package.json @@ -0,0 +1,14 @@ +{ + "name": "test-block-project", + "version": "1.0.0", + "10up-toolkit": { + "entry": {}, + "paths": { + "blocksDir": "./includes/blocks/", + "srcDir": "./assets/", + "copyAssetsDir": "./assets/" + }, + "wpDependencyExternals": true, + "useBlockAssets": true + } +} diff --git a/packages/build/tests/fixtures/postcss-project/assets/css/globals/custom-media.css b/packages/build/tests/fixtures/postcss-project/assets/css/globals/custom-media.css new file mode 100644 index 00000000..2e007024 --- /dev/null +++ b/packages/build/tests/fixtures/postcss-project/assets/css/globals/custom-media.css @@ -0,0 +1,7 @@ +/** + * Custom media queries + * These are available in all stylesheets via postcss-custom-media + */ +@custom-media --viewport-small (min-width: 480px); +@custom-media --viewport-medium (min-width: 768px); +@custom-media --viewport-large (min-width: 1024px); diff --git a/packages/build/tests/fixtures/postcss-project/assets/css/globals/custom-properties.css b/packages/build/tests/fixtures/postcss-project/assets/css/globals/custom-properties.css new file mode 100644 index 00000000..a45230e0 --- /dev/null +++ b/packages/build/tests/fixtures/postcss-project/assets/css/globals/custom-properties.css @@ -0,0 +1,9 @@ +/** + * Global CSS custom properties + * These are available in all stylesheets via postcss-global-data + */ +:root { + --global-primary: #ff6600; + --global-spacing: 16px; + --global-max-width: 1200px; +} diff --git a/packages/build/tests/fixtures/postcss-project/assets/css/mixins/spacing.css b/packages/build/tests/fixtures/postcss-project/assets/css/mixins/spacing.css new file mode 100644 index 00000000..8a4469a5 --- /dev/null +++ b/packages/build/tests/fixtures/postcss-project/assets/css/mixins/spacing.css @@ -0,0 +1,9 @@ +/** + * Spacing mixin + * Available via postcss-mixins + */ +@define-mixin container { + max-width: var(--global-max-width); + margin-inline: auto; + padding-inline: var(--global-spacing); +} diff --git a/packages/build/tests/fixtures/postcss-project/assets/css/style.css b/packages/build/tests/fixtures/postcss-project/assets/css/style.css new file mode 100644 index 00000000..11e4975d --- /dev/null +++ b/packages/build/tests/fixtures/postcss-project/assets/css/style.css @@ -0,0 +1,17 @@ +/** + * Main stylesheet using global styles and mixins + */ +.header { + background-color: var(--global-primary); + padding: var(--global-spacing); +} + +.main-container { + @mixin container; +} + +@media (--viewport-medium) { + .header { + padding: calc(var(--global-spacing) * 2); + } +} diff --git a/packages/build/tests/fixtures/postcss-project/package.json b/packages/build/tests/fixtures/postcss-project/package.json new file mode 100644 index 00000000..e511d782 --- /dev/null +++ b/packages/build/tests/fixtures/postcss-project/package.json @@ -0,0 +1,11 @@ +{ + "name": "postcss-project", + "version": "1.0.0", + "private": true, + "10up-toolkit": { + "paths": { + "globalStylesDir": "./assets/css/globals/", + "globalMixinsDir": "./assets/css/mixins/" + } + } +} diff --git a/packages/build/tests/snapshots.test.ts b/packages/build/tests/snapshots.test.ts new file mode 100644 index 00000000..10e901af --- /dev/null +++ b/packages/build/tests/snapshots.test.ts @@ -0,0 +1,275 @@ +/** + * Snapshot tests for build outputs + * + * These tests verify the actual content of built files remains consistent + */ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import { resolve, join } from 'node:path'; +import { existsSync, readFileSync, rmSync, readdirSync, statSync } from 'node:fs'; +import { build } from '../src/build.js'; +import { defaultConfig } from '../src/config.js'; +import type { BuildConfig } from '../src/types.js'; + +// Store original cwd +const originalCwd = process.cwd(); +const originalEnv = process.env.NODE_ENV; + +/** + * Helper to get all files in a directory recursively + */ +function getAllFiles(dir: string, files: string[] = []): string[] { + if (!existsSync(dir)) return files; + + const entries = readdirSync(dir); + for (const entry of entries) { + const fullPath = join(dir, entry); + if (statSync(fullPath).isDirectory()) { + getAllFiles(fullPath, files); + } else { + files.push(fullPath); + } + } + return files; +} + +/** + * Helper to normalize JS content for snapshot comparison + * Removes content hashes that change between builds + */ +function normalizeJsContent(content: string): string { + return content + // Remove source map comments + .replace(/\/\/# sourceMappingURL=.+$/gm, '//# sourceMappingURL=[removed]') + // Normalize line endings + .replace(/\r\n/g, '\n'); +} + +/** + * Helper to normalize PHP asset file content + * Replaces dynamic version hashes with placeholder + */ +function normalizeAssetPhp(content: string): string { + return content + // Replace version hash with placeholder + .replace(/'version' => '[a-f0-9]+'/, "'version' => '[HASH]'") + // Normalize line endings + .replace(/\r\n/g, '\n'); +} + +/** + * Helper to normalize CSS content + */ +function normalizeCssContent(content: string): string { + return content + // Normalize line endings + .replace(/\r\n/g, '\n'); +} + +describe('Build Output Snapshots', () => { + describe('basic-project', () => { + const fixtureDir = resolve(__dirname, 'fixtures/basic-project'); + const distDir = join(fixtureDir, 'dist-snapshots'); + + beforeAll(async () => { + process.chdir(fixtureDir); + process.env.NODE_ENV = 'production'; + + // Clean and build + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + + const config: Partial = { + entry: { + admin: './assets/js/admin.js', + frontend: './assets/js/frontend.js', + style: './assets/css/style.css', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + await build(config); + }); + + afterAll(() => { + process.chdir(originalCwd); + process.env.NODE_ENV = originalEnv; + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + it('should produce expected output files', () => { + const files = getAllFiles(distDir).map((f) => f.replace(distDir, '').replace(/^\//, '')); + expect(files.sort()).toMatchSnapshot('output-files'); + }); + + it('should produce correct admin.js output', () => { + const content = readFileSync(join(distDir, 'js/admin.js'), 'utf8'); + expect(normalizeJsContent(content)).toMatchSnapshot('admin.js'); + }); + + it('should produce correct admin.asset.php output', () => { + const content = readFileSync(join(distDir, 'js/admin.asset.php'), 'utf8'); + expect(normalizeAssetPhp(content)).toMatchSnapshot('admin.asset.php'); + }); + + it('should produce correct frontend.js output', () => { + const content = readFileSync(join(distDir, 'js/frontend.js'), 'utf8'); + expect(normalizeJsContent(content)).toMatchSnapshot('frontend.js'); + }); + + it('should produce correct frontend.asset.php output', () => { + const content = readFileSync(join(distDir, 'js/frontend.asset.php'), 'utf8'); + expect(normalizeAssetPhp(content)).toMatchSnapshot('frontend.asset.php'); + }); + + it('should produce correct style.css output', () => { + const content = readFileSync(join(distDir, 'css/style.css'), 'utf8'); + expect(normalizeCssContent(content)).toMatchSnapshot('style.css'); + }); + }); + + describe('block-project', () => { + const fixtureDir = resolve(__dirname, 'fixtures/block-project'); + const distDir = join(fixtureDir, 'dist-snapshots'); + + beforeAll(async () => { + process.chdir(fixtureDir); + process.env.NODE_ENV = 'production'; + + // Clean and build + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + + const config: Partial = { + entry: {}, + paths: { + ...defaultConfig.paths, + blocksDir: './includes/blocks/', + distDir: distDir, + }, + wpDependencyExternals: true, + useBlockAssets: true, + }; + + await build(config); + }); + + afterAll(() => { + process.chdir(originalCwd); + process.env.NODE_ENV = originalEnv; + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + it('should produce expected output files', () => { + const files = getAllFiles(distDir).map((f) => f.replace(distDir, '').replace(/^\//, '')); + expect(files.sort()).toMatchSnapshot('output-files'); + }); + + it('should produce correct block.json output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/block.json'), 'utf8'); + expect(JSON.parse(content)).toMatchSnapshot('block.json'); + }); + + it('should produce correct index.js (editor script) output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/index.js'), 'utf8'); + expect(normalizeJsContent(content)).toMatchSnapshot('index.js'); + }); + + it('should produce correct index.asset.php output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/index.asset.php'), 'utf8'); + expect(normalizeAssetPhp(content)).toMatchSnapshot('index.asset.php'); + }); + + it('should produce correct view.js output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/view.js'), 'utf8'); + expect(normalizeJsContent(content)).toMatchSnapshot('view.js'); + }); + + it('should produce correct view.asset.php output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/view.asset.php'), 'utf8'); + expect(normalizeAssetPhp(content)).toMatchSnapshot('view.asset.php'); + }); + + it('should produce correct view-module.js (ES module) output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/view-module.js'), 'utf8'); + expect(normalizeJsContent(content)).toMatchSnapshot('view-module.js'); + }); + + it('should produce correct view-module.asset.php output', () => { + const content = readFileSync( + join(distDir, 'blocks/test-block/view-module.asset.php'), + 'utf8', + ); + expect(normalizeAssetPhp(content)).toMatchSnapshot('view-module.asset.php'); + }); + + it('should produce correct editor.css output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/editor.css'), 'utf8'); + expect(normalizeCssContent(content)).toMatchSnapshot('editor.css'); + }); + + it('should produce correct style.css output', () => { + const content = readFileSync(join(distDir, 'blocks/test-block/style.css'), 'utf8'); + expect(normalizeCssContent(content)).toMatchSnapshot('style.css'); + }); + }); + + describe('postcss-project', () => { + const fixtureDir = resolve(__dirname, 'fixtures/postcss-project'); + const distDir = join(fixtureDir, 'dist-snapshots'); + + beforeAll(async () => { + process.chdir(fixtureDir); + process.env.NODE_ENV = 'production'; + + // Clean and build + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + + const config: Partial = { + entry: { + style: './assets/css/style.css', + }, + paths: { + ...defaultConfig.paths, + distDir: distDir, + globalStylesDir: './assets/css/globals/', + globalMixinsDir: './assets/css/mixins/', + }, + wpDependencyExternals: true, + useBlockAssets: false, + }; + + await build(config); + }); + + afterAll(() => { + process.chdir(originalCwd); + process.env.NODE_ENV = originalEnv; + if (existsSync(distDir)) { + rmSync(distDir, { recursive: true, force: true }); + } + }); + + it('should produce expected output files', () => { + const files = getAllFiles(distDir).map((f) => f.replace(distDir, '').replace(/^\//, '')); + expect(files.sort()).toMatchSnapshot('output-files'); + }); + + it('should produce correct style.css with PostCSS processing', () => { + const content = readFileSync(join(distDir, 'css/style.css'), 'utf8'); + expect(normalizeCssContent(content)).toMatchSnapshot('style.css'); + }); + }); +}); diff --git a/packages/build/tsconfig.json b/packages/build/tsconfig.json new file mode 100644 index 00000000..3772cdbe --- /dev/null +++ b/packages/build/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/packages/build/vitest.config.ts b/packages/build/vitest.config.ts new file mode 100644 index 00000000..ddc1f45b --- /dev/null +++ b/packages/build/vitest.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts', 'tests/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'html', 'lcov'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.test.ts', 'src/types.ts'], + }, + // Longer timeout for build tests + testTimeout: 30000, + }, +});