diff --git a/composer.lock b/composer.lock index 02d0845928..224cee3d01 100644 --- a/composer.lock +++ b/composer.lock @@ -610,22 +610,22 @@ }, { "name": "phpstan/extension-installer", - "version": "1.4.1", + "version": "1.4.3", "source": { "type": "git", "url": "https://github.com/phpstan/extension-installer.git", - "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203" + "reference": "85e90b3942d06b2326fba0403ec24fe912372936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/f6b87faf9fc7978eab2f7919a8760bc9f58f9203", - "reference": "f6b87faf9fc7978eab2f7919a8760bc9f58f9203", + "url": "https://api.github.com/repos/phpstan/extension-installer/zipball/85e90b3942d06b2326fba0403ec24fe912372936", + "reference": "85e90b3942d06b2326fba0403ec24fe912372936", "shasum": "" }, "require": { "composer-plugin-api": "^2.0", "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.9.0" + "phpstan/phpstan": "^1.9.0 || ^2.0" }, "require-dev": { "composer/composer": "^2.0", @@ -646,24 +646,28 @@ "MIT" ], "description": "Composer plugin for automatic installation of PHPStan extensions", + "keywords": [ + "dev", + "static analysis" + ], "support": { "issues": "https://github.com/phpstan/extension-installer/issues", - "source": "https://github.com/phpstan/extension-installer/tree/1.4.1" + "source": "https://github.com/phpstan/extension-installer/tree/1.4.3" }, - "time": "2024-06-10T08:20:49+00:00" + "time": "2024-09-04T20:21:43+00:00" }, { "name": "phpstan/php-8-stubs", - "version": "0.3.95", + "version": "0.3.108", "source": { "type": "git", "url": "https://github.com/phpstan/php-8-stubs.git", - "reference": "1e2422fdfc9da3e96bc1038eaf42728025d24756" + "reference": "59ee6d5256a0bb43debf7d131441edf598b155fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/1e2422fdfc9da3e96bc1038eaf42728025d24756", - "reference": "1e2422fdfc9da3e96bc1038eaf42728025d24756", + "url": "https://api.github.com/repos/phpstan/php-8-stubs/zipball/59ee6d5256a0bb43debf7d131441edf598b155fa", + "reference": "59ee6d5256a0bb43debf7d131441edf598b155fa", "shasum": "" }, "type": "library", @@ -680,9 +684,9 @@ "description": "PHP stubs extracted from php-src", "support": { "issues": "https://github.com/phpstan/php-8-stubs/issues", - "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.95" + "source": "https://github.com/phpstan/php-8-stubs/tree/0.3.108" }, - "time": "2024-08-12T00:18:17+00:00" + "time": "2024-09-23T00:19:30+00:00" }, { "name": "phpstan/phpdoc-parser", @@ -733,16 +737,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.11.11", + "version": "1.12.4", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3" + "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3", - "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ffa517cb918591b93acc9b95c0bebdcd0e4538bd", + "reference": "ffa517cb918591b93acc9b95c0bebdcd0e4538bd", "shasum": "" }, "require": { @@ -787,25 +791,25 @@ "type": "github" } ], - "time": "2024-08-19T14:37:29+00:00" + "time": "2024-09-19T07:58:01+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", - "version": "1.2.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-deprecation-rules.git", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26" + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/fa8cce7720fa782899a0aa97b6a41225d1bb7b26", - "reference": "fa8cce7720fa782899a0aa97b6a41225d1bb7b26", + "url": "https://api.github.com/repos/phpstan/phpstan-deprecation-rules/zipball/f94d246cc143ec5a23da868f8f7e1393b50eaa82", + "reference": "f94d246cc143ec5a23da868f8f7e1393b50eaa82", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -832,9 +836,9 @@ "description": "PHPStan rules for detecting usage of deprecated classes, methods, properties, constants and traits.", "support": { "issues": "https://github.com/phpstan/phpstan-deprecation-rules/issues", - "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.0" + "source": "https://github.com/phpstan/phpstan-deprecation-rules/tree/1.2.1" }, - "time": "2024-04-20T06:39:48+00:00" + "time": "2024-09-11T15:52:35+00:00" }, { "name": "phpstan/phpstan-phpunit", @@ -890,21 +894,21 @@ }, { "name": "phpstan/phpstan-strict-rules", - "version": "1.6.0", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1" + "reference": "daeec748b53de80a97498462513066834ec28f8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/363f921dd8441777d4fc137deb99beb486c77df1", - "reference": "363f921dd8441777d4fc137deb99beb486c77df1", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/daeec748b53de80a97498462513066834ec28f8b", + "reference": "daeec748b53de80a97498462513066834ec28f8b", "shasum": "" }, "require": { "php": "^7.2 || ^8.0", - "phpstan/phpstan": "^1.11" + "phpstan/phpstan": "^1.12.4" }, "require-dev": { "nikic/php-parser": "^4.13.0", @@ -933,9 +937,9 @@ "description": "Extra strict and opinionated rules for PHPStan", "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.0" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/1.6.1" }, - "time": "2024-04-20T06:37:51+00:00" + "time": "2024-09-20T14:04:44+00:00" }, { "name": "phpunit/php-code-coverage", @@ -2127,16 +2131,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.10.2", + "version": "3.10.3", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017" + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/86e5f5dd9a840c46810ebe5ff1885581c42a3017", - "reference": "86e5f5dd9a840c46810ebe5ff1885581c42a3017", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/62d32998e820bddc40f99f8251958aed187a5c9c", + "reference": "62d32998e820bddc40f99f8251958aed187a5c9c", "shasum": "" }, "require": { @@ -2203,7 +2207,7 @@ "type": "open_collective" } ], - "time": "2024-07-21T23:26:44+00:00" + "time": "2024-09-18T10:38:58+00:00" }, { "name": "symfony/polyfill-php73", @@ -2462,7 +2466,7 @@ }, { "name": "wp-phpunit/wp-phpunit", - "version": "6.6.1", + "version": "6.6.2", "source": { "type": "git", "url": "https://github.com/wp-phpunit/wp-phpunit.git", diff --git a/package-lock.json b/package-lock.json index 2b7e5caa2e..fbd71d9dc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,17 +12,17 @@ }, "devDependencies": { "@octokit/rest": "^21.0.2", - "@wordpress/env": "^10.5.0", - "@wordpress/prettier-config": "^4.4.0", - "@wordpress/scripts": "^28.5.0", + "@wordpress/env": "^10.8.0", + "@wordpress/prettier-config": "^4.8.0", + "@wordpress/scripts": "^30.0.2", "commander": "12.1.0", "copy-webpack-plugin": "^12.0.2", "fast-glob": "^3.3.2", "fs-extra": "^11.2.0", - "husky": "^9.1.4", - "lint-staged": "^15.2.9", + "husky": "^9.1.6", + "lint-staged": "^15.2.10", "lodash": "4.17.21", - "micromatch": "^4.0.7", + "micromatch": "^4.0.8", "npm-run-all": "^4.1.5", "webpackbar": "^6.0.1" }, @@ -701,12 +701,12 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.23.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.23.3.tgz", - "integrity": "sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1601,15 +1601,15 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", - "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.25.4.tgz", + "integrity": "sha512-8hsyG+KUYGY0coX6KUCDancA0Vw225KJ2HJO0yCNr1vq5r+lJTleDaJf0K7iOhjw4SWhu03TMBzYTJ9krmzULQ==", "dev": true, "dependencies": { "@babel/helper-module-imports": "^7.24.7", - "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-corejs3": "^0.10.6", "babel-plugin-polyfill-regenerator": "^0.6.1", "semver": "^6.3.1" }, @@ -2049,20 +2049,90 @@ "node": ">=18.0.0" } }, - "node_modules/@csstools/selector-specificity": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-2.2.0.tgz", - "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz", + "integrity": "sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], "engines": { - "node": "^14 || ^16 || >=18" + "node": ">=18" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/csstools" + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.1" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz", + "integrity": "sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", + "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz", + "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "engines": { + "node": ">=18" }, "peerDependencies": { - "postcss-selector-parser": "^6.0.10" + "postcss-selector-parser": "^6.1.0" } }, "node_modules/@discoveryjs/json-ext": { @@ -2074,6 +2144,16 @@ "node": ">=10.0.0" } }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.1.0.tgz", + "integrity": "sha512-+nxncfwHM5SgAtrVzgpzJOI1ol0PkumhVo469KCf9lUi21IGcY90G98VuHm9VRrUypmAzawAHO9bs6hqeADaVg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/@es-joy/jsdoccomment": { "version": "0.41.0", "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.41.0.tgz", @@ -2116,9 +2196,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.1.tgz", + "integrity": "sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -2193,9 +2273,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -2217,13 +2297,13 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "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", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", + "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" }, @@ -2962,13 +3042,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.46.0.tgz", - "integrity": "sha512-/QYft5VArOrGRP5pgkrfKksqsKA6CEFyGQ/gjNe6q0y4tZ1aaPfq4gIjudr1s3D+pXyrPRdsy4opKDrjBabE5w==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", + "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", "dev": true, "peer": true, "dependencies": { - "playwright": "1.46.0" + "playwright": "1.47.2" }, "bin": { "playwright": "cli.js" @@ -3034,30 +3114,40 @@ "dev": true }, "node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", "dev": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.3.0" + "node": ">=18" + } + }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "engines": { + "node": ">=6.0" }, "peerDependenciesMeta": { - "typescript": { + "supports-color": { "optional": true } } @@ -3082,46 +3172,30 @@ "@types/yauzl": "^2.9.1" } }, - "node_modules/@puppeteer/browsers/node_modules/tar-fs": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", - "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", - "dev": true, - "dependencies": { - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - } - }, - "node_modules/@puppeteer/browsers/node_modules/tar-stream": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", - "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", - "dev": true, - "dependencies": { - "b4a": "^1.6.4", - "fast-fifo": "^1.2.0", - "streamx": "^2.15.0" - } + "node_modules/@puppeteer/browsers/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, - "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/@puppeteer/browsers/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=12" + "node": ">=10" } }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, "node_modules/@sentry/core": { "version": "6.19.7", "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.7.tgz", @@ -3306,6 +3380,28 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@stylistic/stylelint-plugin": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.0.tgz", + "integrity": "sha512-NU2XR6i1x163KdyDj3zqblA13890fBzHNZYqZ13aor/sB3Yq8kU/0NKCudv5pfl9Kb/UAteo/D7vKMHtaror/A==", + "dev": true, + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1", + "is-plain-object": "^5.0.0", + "postcss-selector-parser": "^6.1.2", + "postcss-value-parser": "^4.2.0", + "style-search": "^0.1.0", + "stylelint": "^16.8.2" + }, + "engines": { + "node": "^18.12 || >=20.9" + }, + "peerDependencies": { + "stylelint": "^16.8.0" + } + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", @@ -4142,9 +4238,9 @@ "dev": true }, "node_modules/@types/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, "optional": true, "dependencies": { @@ -4610,9 +4706,9 @@ } }, "node_modules/@wordpress/babel-preset-default": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-8.5.0.tgz", - "integrity": "sha512-ra9UWhidrAIoYTuqIIMFs+5LawUis/qDhiB2c3mc9HJUXXQDOvdhww5f9H9SjWqOo1oB9jSuVh4pXtBpcR1E6A==", + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/@wordpress/babel-preset-default/-/babel-preset-default-8.8.2.tgz", + "integrity": "sha512-XhIpSw6d8GeaBe+gQ25nck01+Q3UiVQgih/yBCFWNtzB2qp/AB7195lHGxbuAYUO9RM1eXsf8kVJV2caAb4WnA==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", @@ -4621,8 +4717,8 @@ "@babel/preset-env": "^7.16.0", "@babel/preset-typescript": "^7.16.0", "@babel/runtime": "^7.16.0", - "@wordpress/browserslist-config": "^6.5.0", - "@wordpress/warning": "^3.5.0", + "@wordpress/browserslist-config": "^6.8.1", + "@wordpress/warning": "^3.8.1", "browserslist": "^4.21.10", "core-js": "^3.31.0", "react": "^18.3.0" @@ -4633,9 +4729,9 @@ } }, "node_modules/@wordpress/base-styles": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-5.5.0.tgz", - "integrity": "sha512-7sXNXkF/oayvnupDLMfDktkvDlCt8V9jOgKFWhWeVrthwccI8hYLylS7ZHsD1+Cg2uKfQ1In4xJ4ThHNjl2JUQ==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/base-styles/-/base-styles-5.8.1.tgz", + "integrity": "sha512-vYT31mcV74bO+MXMIYKMyethp6kw3rQyh0wKvWIhX5pX/wUYXjts+RE6v9Yf7k0OJ+UT0c/CXF+5KLBuju6EVA==", "dev": true, "engines": { "node": ">=18.12.0", @@ -4643,9 +4739,9 @@ } }, "node_modules/@wordpress/browserslist-config": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.5.0.tgz", - "integrity": "sha512-B5cYN5INRknBjzbNH2qJOq6x66qXS2UoLk5Ebyew1JkW9xzvX1O+eQumjFv65fAN6RNPtomHs8c7BNoD3wAebQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/browserslist-config/-/browserslist-config-6.8.1.tgz", + "integrity": "sha512-hp2eE0DiRbFGTUEQ49kLVyZWlR8lfm8hb2XKqSoWbeqzWM5ZkgrRRJMrJRPS/jCEWTWDdlBwUFfsVNDKpmHc9A==", "dev": true, "engines": { "node": ">=18.12.0", @@ -4653,9 +4749,9 @@ } }, "node_modules/@wordpress/dependency-extraction-webpack-plugin": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.5.0.tgz", - "integrity": "sha512-UpbBNUh+lc8cquul08c6jKVgjxf6+pUJE3Rd08kDz3DUrD3sq3W8lYnJ92mqcZO1RgoZGwtM6Swbliv6erxV8g==", + "version": "6.8.2", + "resolved": "https://registry.npmjs.org/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-6.8.2.tgz", + "integrity": "sha512-qa+zMLzwIHNZyS1Hn//jn6Mgbm9ciwwkZhr1qV0BG31QTKctH4jA1jPikbSvRDs2oiJMRLQpeE3F8JW6UkyQIg==", "dev": true, "dependencies": { "json2php": "^0.0.7" @@ -4669,9 +4765,9 @@ } }, "node_modules/@wordpress/e2e-test-utils-playwright": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.5.0.tgz", - "integrity": "sha512-gQuhdDMejum7a4hWW4ejIWSBv1xeeCFda5o7eq766evarU+HwcN+4GPiSYKj2iEHJ4dge1On2VXeg1RFLY3w5Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/e2e-test-utils-playwright/-/e2e-test-utils-playwright-1.8.1.tgz", + "integrity": "sha512-BKp2EpC35/SWJg1h69Q0RP7hlcNoqyuq1UA5CJycph2yuzrfl8+tfKqkrdCYhyLU/MuW6GFh9d92vb2cTYnSOQ==", "dev": true, "dependencies": { "change-case": "^4.1.2", @@ -4690,9 +4786,9 @@ } }, "node_modules/@wordpress/env": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.5.0.tgz", - "integrity": "sha512-Hx+fi6qTEAuycznulkuMi4d5RDPZ6lPPAxaylpCwXNX2hgx5jrrpgnY4Zn0chBgZMpShO7BbA+zNDq2E6evvTw==", + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/@wordpress/env/-/env-10.8.0.tgz", + "integrity": "sha512-kU66r7y/3AnUd6D4XeWE7h6bVJmzteTKDMMWoIoJsSNI5YP/BmXRa+/dJ4bwk0KFKxfh3tcRBhearGeEa4TGBw==", "dev": true, "dependencies": { "chalk": "^4.0.0", @@ -4717,16 +4813,16 @@ } }, "node_modules/@wordpress/eslint-plugin": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-20.2.0.tgz", - "integrity": "sha512-lC8sUkzf4x3ByQHs3xF7X0XxS3SPEYzXho9+NeIgB5Cg0s/PlyWuPQI+4oHVgZI3hzPH76G6ewRtIt27gQRcdw==", + "version": "21.1.2", + "resolved": "https://registry.npmjs.org/@wordpress/eslint-plugin/-/eslint-plugin-21.1.2.tgz", + "integrity": "sha512-f89Q8J1yGq6b1Myqgby7Xdon+mx/YjSBCs3/saydaJWJoXaDIXZFTMrY0cjWzbSOTDThYCvbkvQm0QGAPanNTA==", "dev": true, "dependencies": { "@babel/eslint-parser": "^7.16.0", "@typescript-eslint/eslint-plugin": "^6.4.1", "@typescript-eslint/parser": "^6.4.1", - "@wordpress/babel-preset-default": "^8.5.0", - "@wordpress/prettier-config": "^4.5.0", + "@wordpress/babel-preset-default": "^8.8.2", + "@wordpress/prettier-config": "^4.8.1", "cosmiconfig": "^7.0.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-import": "^2.25.2", @@ -4787,9 +4883,9 @@ } }, "node_modules/@wordpress/jest-console": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.5.0.tgz", - "integrity": "sha512-kcNNbPFUNMCZfeNgPoee2SpyL3XgNTuPdKHa3JBM/lUNiWl1FAxFnqmElM/0UBUrVdbl/NmggVdOspsCE0eY8w==", + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/jest-console/-/jest-console-8.8.1.tgz", + "integrity": "sha512-TjSQ/jhtT5f1r8NFpP4pjdtambOd4yyyjwG35av+DqXOr8zz68zYZhzxqIy24jmrZGa5KaaOMvBa8q7G7BHcMw==", "dev": true, "dependencies": { "@babel/runtime": "^7.16.0", @@ -4804,12 +4900,12 @@ } }, "node_modules/@wordpress/jest-preset-default": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-12.5.0.tgz", - "integrity": "sha512-VeSW5dbZzwrNF0ffpZedTLbj1wG7ByJflReMRwT/ljjIK+CDTUCUbLynnGsvSfV/lc2GzNqI6bqgY22DmK53Lw==", + "version": "12.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/jest-preset-default/-/jest-preset-default-12.8.1.tgz", + "integrity": "sha512-mnusLFJKz3rEuehy09yQqiwX9fpV4HK1Gh2/hu85DvwjtbHbJajfTW4GjRYU0WEkrGJkQhon6nfC7lGu5nVvkA==", "dev": true, "dependencies": { - "@wordpress/jest-console": "^8.5.0", + "@wordpress/jest-console": "^8.8.1", "babel-jest": "^29.6.2" }, "engines": { @@ -4822,9 +4918,9 @@ } }, "node_modules/@wordpress/npm-package-json-lint-config": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.5.0.tgz", - "integrity": "sha512-W48a+iIwia+RXN6fg6vxUZj4fyrbuRKFiEV0sIOsxKRJK9zFSink/RKEEKq3aLT17QUTcQjuV2DA4f+eSVaadA==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/npm-package-json-lint-config/-/npm-package-json-lint-config-5.8.1.tgz", + "integrity": "sha512-TmY5u6b2w9XKYw/DCF7xFwH45mxVqZk0UcLkrNFpldqe4gjKv46CWpyC7EAD4mMh+atMWqD1llf5uEp5l29qUg==", "dev": true, "engines": { "node": ">=18.12.0", @@ -4835,12 +4931,12 @@ } }, "node_modules/@wordpress/postcss-plugins-preset": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-5.5.0.tgz", - "integrity": "sha512-DatUnlrFMX6MtW+KsmuMiLaBSad78oX5nsroZPSFveXELL6CD5BHNcc24SoV2U8XI5ZGNJHHxzqVar/TQ2tbSQ==", + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/postcss-plugins-preset/-/postcss-plugins-preset-5.8.1.tgz", + "integrity": "sha512-OL+9T7nm8DcOJ95+rpwelSK9XsLQCCm7tMi6JkgTllAV3E+ttrF0ISlKHtJ02mCNkPJMfZGBCtVWvHlVxpXkAw==", "dev": true, "dependencies": { - "@wordpress/base-styles": "^5.5.0", + "@wordpress/base-styles": "^5.8.1", "autoprefixer": "^10.2.5" }, "engines": { @@ -4852,9 +4948,9 @@ } }, "node_modules/@wordpress/prettier-config": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-4.5.0.tgz", - "integrity": "sha512-HxpR4128yfJJs1idQaBJfBffa6P2JXKN7mvx+D/G2UukjjWlXHaCXLO8wuBLnXwXSg4pOq5vfDM/p5TawZd7Zg==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/prettier-config/-/prettier-config-4.8.1.tgz", + "integrity": "sha512-JDiVChhgwv6ZGa4aVOXnDJnj/dUFkD/SSvRLFkLOdB+ZbWgddJQkVB3rpJOfREsPtEFWqgTxcJoZjnkqltNbww==", "dev": true, "engines": { "node": ">=18.12.0", @@ -4865,24 +4961,24 @@ } }, "node_modules/@wordpress/scripts": { - "version": "28.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-28.5.0.tgz", - "integrity": "sha512-J98tOf5+nWaKneVpGqCB8RcBzm/Rv+SWVIgTvUE3AOtp721r7GBkClLMDzjwC08sYW2cANG/JCYZ69LHaK3mlg==", + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/@wordpress/scripts/-/scripts-30.0.2.tgz", + "integrity": "sha512-4NTXPuzeR3BsCqw90EL8XkEtzp6TFdxgl0wWzh+6pA//2QML8Cf2mT620/KH9Q6oUolniWSM9yxeGgYHh9jiRw==", "dev": true, "dependencies": { "@babel/core": "^7.16.0", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@svgr/webpack": "^8.0.1", - "@wordpress/babel-preset-default": "^8.5.0", - "@wordpress/browserslist-config": "^6.5.0", - "@wordpress/dependency-extraction-webpack-plugin": "^6.5.0", - "@wordpress/e2e-test-utils-playwright": "^1.5.0", - "@wordpress/eslint-plugin": "^20.2.0", - "@wordpress/jest-preset-default": "^12.5.0", - "@wordpress/npm-package-json-lint-config": "^5.5.0", - "@wordpress/postcss-plugins-preset": "^5.5.0", - "@wordpress/prettier-config": "^4.5.0", - "@wordpress/stylelint-config": "^22.5.0", + "@wordpress/babel-preset-default": "^8.8.2", + "@wordpress/browserslist-config": "^6.8.1", + "@wordpress/dependency-extraction-webpack-plugin": "^6.8.1", + "@wordpress/e2e-test-utils-playwright": "^1.8.1", + "@wordpress/eslint-plugin": "^21.1.2", + "@wordpress/jest-preset-default": "^12.8.1", + "@wordpress/npm-package-json-lint-config": "^5.8.1", + "@wordpress/postcss-plugins-preset": "^5.8.1", + "@wordpress/prettier-config": "^4.8.1", + "@wordpress/stylelint-config": "^23.0.1", "adm-zip": "^0.5.9", "babel-jest": "^29.6.2", "babel-loader": "^8.2.3", @@ -4911,9 +5007,10 @@ "npm-package-json-lint": "^6.4.0", "npm-packlist": "^3.0.0", "postcss": "^8.4.5", + "postcss-import": "^16.1.0", "postcss-loader": "^6.2.1", "prettier": "npm:wp-prettier@3.0.3", - "puppeteer-core": "^13.2.0", + "puppeteer-core": "^23.1.0", "react-refresh": "^0.14.0", "read-pkg-up": "^7.0.1", "resolve-bin": "^0.4.0", @@ -4922,7 +5019,7 @@ "sass-loader": "^12.1.0", "schema-utils": "^4.2.0", "source-map-loader": "^3.0.0", - "stylelint": "^14.2.0", + "stylelint": "^16.8.2", "terser-webpack-plugin": "^5.3.9", "url-loader": "^4.1.1", "webpack": "^5.88.2", @@ -4938,7 +5035,7 @@ "npm": ">=8.19.2" }, "peerDependencies": { - "@playwright/test": "^1.45.1", + "@playwright/test": "^1.47.0", "react": "^18.0.0", "react-dom": "^18.0.0" } @@ -5065,26 +5162,27 @@ } }, "node_modules/@wordpress/stylelint-config": { - "version": "22.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-22.5.0.tgz", - "integrity": "sha512-CgKU37JVYgcje8SUXt87/5tWpor6zYm+mFf4FGZFPDCIGh9KXuLfsfyau4sJ2YI16qMlFiSwyWQMKWG86p1AhA==", + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/@wordpress/stylelint-config/-/stylelint-config-23.0.1.tgz", + "integrity": "sha512-fxWzz2kX1jCkvVcdkRuMu1HF1LNSlr3hgFk29NW09FbL6nnac/rlSaX3+LQSlbDUSe/aq840B7K0iIq2GwnKog==", "dev": true, "dependencies": { - "stylelint-config-recommended": "^6.0.0", - "stylelint-config-recommended-scss": "^5.0.2" + "@stylistic/stylelint-plugin": "^3.0.1", + "stylelint-config-recommended": "^14.0.1", + "stylelint-config-recommended-scss": "^14.1.0" }, "engines": { "node": ">=18.12.0", "npm": ">=8.19.2" }, "peerDependencies": { - "stylelint": "^14.2" + "stylelint": "^16.8.2" } }, "node_modules/@wordpress/warning": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.5.0.tgz", - "integrity": "sha512-cYM2Vqf2EokJJoWHD5Ry15OUmdsKtDgR8/qxE0sWHUAdSQeoTuJZnqhgYI898cZGxHaZWX2xJCxQa7Qtwl8lqw==", + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/@wordpress/warning/-/warning-3.8.1.tgz", + "integrity": "sha512-xlo0Xw1jiyiE6nh43NAtQMAL05VDk837kY2xfjsus6wD597TeWFpj6gmcRMH25FZULTUHDB2EPfLviWXqOgUfg==", "dev": true, "engines": { "node": ">=18.12.0", @@ -5663,12 +5761,12 @@ } }, "node_modules/axobject-query": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.1.1.tgz", - "integrity": "sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "dev": true, - "dependencies": { - "deep-equal": "^2.0.5" + "engines": { + "node": ">= 0.4" } }, "node_modules/b4a": { @@ -5822,23 +5920,26 @@ } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", "dev": true, "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" @@ -5897,6 +5998,46 @@ "dev": true, "optional": true }, + "node_modules/bare-fs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.3.tgz", + "integrity": "sha512-7RYKL+vZVCyAsMLi5SPu7QGauGGT8avnP/HO571ndEuV4MYdGXvLhtW67FuLPeEI8EiIY7zbbRR9x7x7HU0kgw==", + "dev": true, + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.2.tgz", + "integrity": "sha512-HZoJwzC+rZ9lqEemTMiO0luOePoGYNBgsLLgegKR/cljiJvcDNhDZQkzC+NC5Oh0aHbdBNSOHpghwMuB5tqhjg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "dev": true, + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.2.1.tgz", + "integrity": "sha512-YTB47kHwBW9zSG8LD77MIBAAQXjU2WjAkMHeeb7hUplVs6+IoM5I7uEVQNPMB7lj9r8I76UMdoMkGnCodHOLqg==", + "dev": true, + "optional": true, + "dependencies": { + "b4a": "^1.6.6", + "streamx": "^2.18.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5956,31 +6097,6 @@ "node": ">=8" } }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/body-parser": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", @@ -6446,12 +6562,6 @@ "node": ">= 6" } }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, "node_modules/chrome-launcher": { "version": "0.15.2", "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", @@ -6480,12 +6590,14 @@ } }, "node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.5.tgz", + "integrity": "sha512-RuLrmzYrxSb0s9SgpB+QN5jJucPduZQ/9SIe76MDxYJuecPW5mxMdacJ1f4EtgiV+R0p3sCkznTMvH0MPGFqjA==", "dev": true, "dependencies": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" @@ -6507,9 +6619,9 @@ } }, "node_modules/cjs-module-lexer": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz", - "integrity": "sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", "dev": true }, "node_modules/clean-webpack-plugin": { @@ -7092,9 +7204,9 @@ } }, "node_modules/core-js": { - "version": "3.38.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.0.tgz", - "integrity": "sha512-XPpwqEodRljce9KswjZShh95qJ1URisBeKCjUdq27YdenkslVe7OO0ZJhlYXAChW7OhXaRLl8AAba7IBfoIHug==", + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.38.1.tgz", + "integrity": "sha512-OP35aUorbU3Zvlx7pjsFdu1rGNnD4pgw/CWoYzRY3t2EzoVT7shKHY1dlAy3f41cGIO7ZDPQimhGFTlEYkG/Hw==", "dev": true, "hasInstallScript": true, "funding": { @@ -7170,65 +7282,23 @@ } }, "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", "dev": true, "dependencies": { - "node-fetch": "2.6.7" + "node-fetch": "^2.6.12" } }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/cross-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, - "node_modules/cross-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, - "node_modules/cross-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", - "dev": true, - "dependencies": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" } }, "node_modules/cross-spawn/node_modules/lru-cache": { @@ -7987,9 +8057,9 @@ "dev": true }, "node_modules/devtools-protocol": { - "version": "0.0.981744", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", - "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", + "version": "0.0.1155343", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1155343.tgz", + "integrity": "sha512-oD9vGBV2wTc7fAzAM6KC0chSgs234V8+qDEeK+mcbRj2UvcuA7lgBztGi/opj/iahcXD3BSj8Ymvib628yy9FA==", "dev": true }, "node_modules/diff-sequences": { @@ -8205,31 +8275,6 @@ "node": ">= 0.8" } }, - "node_modules/encoding": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", - "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "iconv-lite": "^0.6.2" - } - }, - "node_modules/encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -8277,6 +8322,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/envinfo": { "version": "7.11.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.11.0.tgz", @@ -8568,16 +8622,16 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "@ungap/structured-clone": "^1.2.0", @@ -8655,9 +8709,9 @@ } }, "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.1.tgz", + "integrity": "sha512-EwcbfLOhwVMAfatfqLecR2yv3dE5+kQ8kx+Rrt0DvDXEVwW86KQ/xbMDQhtp5l42VXukD5SOF8mQQHbaNtO0CQ==", "dev": true, "dependencies": { "debug": "^3.2.7" @@ -8681,26 +8735,27 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "dev": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.9.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", "tsconfig-paths": "^3.15.0" }, @@ -8917,17 +8972,17 @@ } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz", - "integrity": "sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g==", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz", + "integrity": "sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg==", "dev": true, "dependencies": { "aria-query": "~5.1.3", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", - "axe-core": "^4.9.1", - "axobject-query": "~3.1.1", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "es-iterator-helpers": "^1.0.19", @@ -8943,7 +8998,7 @@ "node": ">=4.0" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "node_modules/eslint-plugin-playwright": { @@ -8992,9 +9047,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", - "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", + "version": "7.36.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.36.1.tgz", + "integrity": "sha512-/qwbqNXZoq+VP30s1d4Nc1C5GTxjJQjk4Jzs4Wq2qzxFM7dSmuG2UkIjg2USMLh3A/aVcUNrK7v0J5U1XEGGwA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", @@ -10014,12 +10069,6 @@ "node": ">= 0.6" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-exists-sync": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", @@ -10751,9 +10800,9 @@ } }, "node_modules/husky": { - "version": "9.1.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.4.tgz", - "integrity": "sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==", + "version": "9.1.6", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", + "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", "dev": true, "bin": { "husky": "bin.js" @@ -10867,15 +10916,6 @@ "node": ">=4" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -11162,12 +11202,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -12625,9 +12668,9 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -12664,9 +12707,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.26.0.tgz", - "integrity": "sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==", + "version": "0.34.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", + "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", "dev": true }, "node_modules/language-subtag-registry": { @@ -12802,6 +12845,47 @@ "integrity": "sha512-sRr0z1S/I26VffRLq9KJsKtLk856YrJlNGmcJmbLX8dFn3MuzVPUbstuChEhqnSxZb8TZmVfthuXuwhG9vRoSw==", "dev": true }, + "node_modules/lighthouse/node_modules/@puppeteer/browsers": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", + "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "dev": true, + "dependencies": { + "debug": "4.3.4", + "extract-zip": "2.0.1", + "progress": "2.0.3", + "proxy-agent": "6.3.0", + "tar-fs": "3.0.4", + "unbzip2-stream": "1.4.3", + "yargs": "17.7.1" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=16.3.0" + }, + "peerDependencies": { + "typescript": ">= 4.7.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/lighthouse/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dev": true, + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/lighthouse/node_modules/axe-core": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.2.tgz", @@ -12811,21 +12895,86 @@ "node": ">=4" } }, - "node_modules/lighthouse/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "node_modules/lighthouse/node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "dependencies": { - "node-fetch": "^2.6.12" + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" } }, - "node_modules/lighthouse/node_modules/devtools-protocol": { - "version": "0.0.1155343", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1155343.tgz", - "integrity": "sha512-oD9vGBV2wTc7fAzAM6KC0chSgs234V8+qDEeK+mcbRj2UvcuA7lgBztGi/opj/iahcXD3BSj8Ymvib628yy9FA==", + "node_modules/lighthouse/node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/lighthouse/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/lighthouse/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/lighthouse/node_modules/mitt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", + "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", "dev": true }, + "node_modules/lighthouse/node_modules/proxy-agent": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", + "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.0", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.1" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/lighthouse/node_modules/puppeteer-core": { "version": "20.9.0", "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", @@ -12851,6 +13000,18 @@ } } }, + "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/chromium-bidi": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", + "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", + "dev": true, + "dependencies": { + "mitt": "3.0.0" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, "node_modules/lighthouse/node_modules/puppeteer-core/node_modules/devtools-protocol": { "version": "0.0.1147663", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", @@ -12887,6 +13048,17 @@ "semver": "bin/semver" } }, + "node_modules/lighthouse/node_modules/tar-fs": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.4.tgz", + "integrity": "sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==", + "dev": true, + "dependencies": { + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + } + }, "node_modules/lighthouse/node_modules/ws": { "version": "7.5.10", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", @@ -12908,6 +13080,24 @@ } } }, + "node_modules/lighthouse/node_modules/yargs": { + "version": "17.7.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", + "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/lilconfig": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", @@ -12936,9 +13126,9 @@ } }, "node_modules/lint-staged": { - "version": "15.2.9", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.9.tgz", - "integrity": "sha512-BZAt8Lk3sEnxw7tfxM7jeZlPRuT4M68O0/CwZhhaw6eeWu0Lz5eERE3m386InivXB64fp/mDID452h48tvKlRQ==", + "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", @@ -12947,7 +13137,7 @@ "execa": "~8.0.1", "lilconfig": "~3.1.2", "listr2": "~8.2.4", - "micromatch": "~4.0.7", + "micromatch": "~4.0.8", "pidtree": "~0.6.0", "string-argv": "~0.3.2", "yaml": "~2.5.0" @@ -14001,9 +14191,9 @@ } }, "node_modules/micromatch": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", - "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { "braces": "^3.0.3", @@ -14217,9 +14407,9 @@ } }, "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "dev": true }, "node_modules/mixin-object": { @@ -15501,9 +15691,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", "dev": true }, "node_modules/picomatch": { @@ -15634,13 +15824,13 @@ } }, "node_modules/playwright": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.46.0.tgz", - "integrity": "sha512-XYJ5WvfefWONh1uPAUAi0H2xXV5S3vrtcnXe6uAOgdGi3aSpqOSXX08IAjXW34xitfuOJsvXU5anXZxPSEQiJw==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", + "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", "dev": true, "peer": true, "dependencies": { - "playwright-core": "1.46.0" + "playwright-core": "1.47.2" }, "bin": { "playwright": "cli.js" @@ -15653,9 +15843,9 @@ } }, "node_modules/playwright-core": { - "version": "1.46.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.46.0.tgz", - "integrity": "sha512-9Y/d5UIwuJk8t3+lhmMSAJyNP1BUC/DqP3cQJDQQL/oWqAiuPTLgy7Q5dzglmTLwcBRdetzgNM/gni7ckfTr6A==", + "version": "1.47.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", + "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", "dev": true, "peer": true, "bin": { @@ -15705,9 +15895,9 @@ } }, "node_modules/postcss": { - "version": "8.4.33", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", - "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -15725,8 +15915,8 @@ ], "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.0", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -15830,6 +16020,23 @@ "postcss": "^8.4.31" } }, + "node_modules/postcss-import": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-16.1.0.tgz", + "integrity": "sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==", + "dev": true, + "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" + } + }, "node_modules/postcss-loader": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-6.2.1.tgz", @@ -16235,19 +16442,29 @@ "dev": true }, "node_modules/postcss-safe-parser": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-6.0.0.tgz", - "integrity": "sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.0.tgz", + "integrity": "sha512-ovehqRNVCpuFzbXoTb4qLtyzK3xn3t/CUBxOs8LsnQjQrShaB4lKiHoVqY8ANaC0hBMHq5QVWk77rwGklFUDrg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "engines": { - "node": ">=12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" + "node": ">=18.0" }, "peerDependencies": { - "postcss": "^8.3.3" + "postcss": "^8.4.31" } }, "node_modules/postcss-scss": { @@ -16277,9 +16494,9 @@ } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, "dependencies": { "cssesc": "^3.0.0", @@ -16466,19 +16683,19 @@ } }, "node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" @@ -16581,69 +16798,51 @@ } }, "node_modules/puppeteer-core": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz", - "integrity": "sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q==", + "version": "23.3.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.3.0.tgz", + "integrity": "sha512-sB2SsVMFs4gKad5OCdv6w5vocvtEUrRl0zQqSyRPbo/cj1Ktbarmhxy02Zyb9R9HrssBcJDZbkrvBnbaesPyYg==", "dev": true, "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.981744", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.5.0" + "@puppeteer/browsers": "2.4.0", + "chromium-bidi": "0.6.5", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1330662", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" }, "engines": { - "node": ">=10.18.1" + "node": ">=18" } }, - "node_modules/puppeteer-core/node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "node_modules/puppeteer-core/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dev": true, "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" + "ms": "^2.1.3" }, "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "node": ">=6.0" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { + "supports-color": { "optional": true } } }, + "node_modules/puppeteer-core/node_modules/devtools-protocol": { + "version": "0.0.1330662", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1330662.tgz", + "integrity": "sha512-pzh6YQ8zZfz3iKlCvgzVCu22NdpZ8hNmwU6WnQjNVquh0A9iVosPtNLWDwaWVGyrntQlltPFztTMK5Cg6lfCuw==", + "dev": true + }, + "node_modules/puppeteer-core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/pure-rand": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", @@ -16802,6 +17001,24 @@ "node": ">=0.10.0" } }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/read-cache/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/read-pkg": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", @@ -18010,9 +18227,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, "engines": { "node": ">=0.10.0" @@ -18256,9 +18473,9 @@ } }, "node_modules/streamx": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", - "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.20.0.tgz", + "integrity": "sha512-ZGd1LhDeGFucr1CUCTBOS58ZhEendd0ttpGT3usTvosS4ntIwKN9LJFp+OeCSprsCPL14BXVRZlHGRY1V9PVzQ==", "dev": true, "dependencies": { "fast-fifo": "^1.3.2", @@ -18539,105 +18756,226 @@ } }, "node_modules/stylelint": { - "version": "14.16.1", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-14.16.1.tgz", - "integrity": "sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==", + "version": "16.9.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.9.0.tgz", + "integrity": "sha512-31Nm3WjxGOBGpQqF43o3wO9L5AC36TPIe6030Lnm13H3vDMTcS21DrLh69bMX+DBilKqMMVLian4iG6ybBoNRQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], "dependencies": { - "@csstools/selector-specificity": "^2.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1", + "@csstools/selector-specificity": "^4.0.0", + "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", - "cosmiconfig": "^7.1.0", - "css-functions-list": "^3.1.0", - "debug": "^4.3.4", - "fast-glob": "^3.2.12", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.2", + "css-tree": "^2.3.1", + "debug": "^4.3.6", + "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^9.0.0", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", - "html-tags": "^3.2.0", - "ignore": "^5.2.1", - "import-lazy": "^4.0.0", + "html-tags": "^3.3.1", + "ignore": "^5.3.2", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.26.0", + "known-css-properties": "^0.34.0", "mathml-tag-names": "^2.1.3", - "meow": "^9.0.0", - "micromatch": "^4.0.5", + "meow": "^13.2.0", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.19", - "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-safe-parser": "^6.0.0", - "postcss-selector-parser": "^6.0.11", + "picocolors": "^1.0.1", + "postcss": "^8.4.41", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.0", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "style-search": "^0.1.0", - "supports-hyperlinks": "^2.3.0", + "strip-ansi": "^7.1.0", + "supports-hyperlinks": "^3.1.0", "svg-tags": "^1.0.0", - "table": "^6.8.1", - "v8-compile-cache": "^2.3.0", - "write-file-atomic": "^4.0.2" + "table": "^6.8.2", + "write-file-atomic": "^5.0.1" }, "bin": { - "stylelint": "bin/stylelint.js" + "stylelint": "bin/stylelint.mjs" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/stylelint" + "node": ">=18.12.0" } }, "node_modules/stylelint-config-recommended": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-6.0.0.tgz", - "integrity": "sha512-ZorSSdyMcxWpROYUvLEMm0vSZud2uB7tX1hzBZwvVY9SV/uly4AvvJPPhCcymZL3fcQhEQG5AELmrxWqtmzacw==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "engines": { + "node": ">=18.12.0" + }, "peerDependencies": { - "stylelint": "^14.0.0" + "stylelint": "^16.1.0" } }, "node_modules/stylelint-config-recommended-scss": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-5.0.2.tgz", - "integrity": "sha512-b14BSZjcwW0hqbzm9b0S/ScN2+3CO3O4vcMNOw2KGf8lfVSwJ4p5TbNEXKwKl1+0FMtgRXZj6DqVUe/7nGnuBg==", + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.1.0.tgz", + "integrity": "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==", "dev": true, "dependencies": { - "postcss-scss": "^4.0.2", - "stylelint-config-recommended": "^6.0.0", - "stylelint-scss": "^4.0.0" + "postcss-scss": "^4.0.9", + "stylelint-config-recommended": "^14.0.1", + "stylelint-scss": "^6.4.0" + }, + "engines": { + "node": ">=18.12.0" }, "peerDependencies": { - "stylelint": "^14.0.0" + "postcss": "^8.3.3", + "stylelint": "^16.6.1" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } } }, "node_modules/stylelint-scss": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-4.7.0.tgz", - "integrity": "sha512-TSUgIeS0H3jqDZnby1UO1Qv3poi1N8wUYIJY6D1tuUq2MN3lwp/rITVo0wD+1SWTmRm0tNmGO0b7nKInnqF6Hg==", + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.7.0.tgz", + "integrity": "sha512-RFIa2A+pVWS5wjNT+whtK7wsbZEWazyqesCuSaPbPlZ8lh2TujwVJSnCYJijg6ChZzwI8pZPRZS1L6A9aCbXDg==", "dev": true, "dependencies": { + "css-tree": "2.3.1", + "is-plain-object": "5.0.0", + "known-css-properties": "^0.34.0", "postcss-media-query-parser": "^0.2.3", - "postcss-resolve-nested-selector": "^0.1.1", - "postcss-selector-parser": "^6.0.11", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0" }, + "engines": { + "node": ">=18.12.0" + }, "peerDependencies": { - "stylelint": "^14.5.1 || ^15.0.0" + "stylelint": "^16.0.2" } }, + "node_modules/stylelint/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/stylelint/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==", + "dev": true + }, "node_modules/stylelint/node_modules/balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", "dev": true }, + "node_modules/stylelint/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/stylelint/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/stylelint/node_modules/file-entry-cache": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-9.1.0.tgz", + "integrity": "sha512-/pqPFG+FdxWQj+/WSuzXSDaNzxgTLr/OrR1QuqfEZzDakpdYE70PwUxL7BPUa8hpjbvY1+qvCl8k+8Tq34xJgg==", + "dev": true, + "dependencies": { + "flat-cache": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/stylelint/node_modules/flat-cache": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-5.0.0.tgz", + "integrity": "sha512-JrqFmyUl2PnPi1OvLyTVHnQvwQ0S+e6lGSwu8OkAZlSaNIZciTY2H/cOOROxsBA1m/LZNHDsqAgDZt6akWcjsQ==", + "dev": true, + "dependencies": { + "flatted": "^3.3.1", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/stylelint/node_modules/global-modules": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", @@ -18664,6 +19002,27 @@ "node": ">=6" } }, + "node_modules/stylelint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/stylelint/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==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/stylelint/node_modules/kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -18673,6 +19032,80 @@ "node": ">=0.10.0" } }, + "node_modules/stylelint/node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/stylelint/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stylelint/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/stylelint/node_modules/supports-hyperlinks": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.1.0.tgz", + "integrity": "sha512-2rn0BZ+/f7puLOHZm1HOJfwBggfaHXUpPUSSG/SWM4TWp5KCfmNYwnC3hruy2rZlMnmWZ+QAGpZfchu3f3695A==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stylelint/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -18852,45 +19285,28 @@ } }, "node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=6" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/tar-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" } }, "node_modules/terminal-link": { @@ -19337,6 +19753,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", + "dev": true + }, "node_modules/typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -19579,6 +20001,12 @@ "requires-port": "^1.0.0" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -19603,12 +20031,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/v8-compile-cache": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.4.0.tgz", - "integrity": "sha512-ocyWc3bAHBB/guyqJQVI5o4BZkPhznPYUG2ea80Gond/BgNWpap8TOmLSeeQG7bnh2KMISxskdADG59j7zruhw==", - "dev": true - }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", @@ -20461,9 +20883,9 @@ } }, "node_modules/ws": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz", - "integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "engines": { "node": ">=10.0.0" @@ -20577,6 +20999,15 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index dff8e44a19..9fe86baf1f 100644 --- a/package.json +++ b/package.json @@ -12,17 +12,17 @@ }, "devDependencies": { "@octokit/rest": "^21.0.2", - "@wordpress/env": "^10.5.0", - "@wordpress/prettier-config": "^4.4.0", - "@wordpress/scripts": "^28.5.0", + "@wordpress/env": "^10.8.0", + "@wordpress/prettier-config": "^4.8.0", + "@wordpress/scripts": "^30.0.2", "commander": "12.1.0", "copy-webpack-plugin": "^12.0.2", "fast-glob": "^3.3.2", "fs-extra": "^11.2.0", - "husky": "^9.1.4", - "lint-staged": "^15.2.9", + "husky": "^9.1.6", + "lint-staged": "^15.2.10", "lodash": "4.17.21", - "micromatch": "^4.0.7", + "micromatch": "^4.0.8", "npm-run-all": "^4.1.5", "webpackbar": "^6.0.1" }, diff --git a/phpstan.neon.dist b/phpstan.neon.dist index bd62e8f23b..c66cc423a5 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -26,6 +26,9 @@ parameters: - PERFLAB_OBJECT_CACHE_DROPIN_VERSION strictRules: strictArrayFilter: false + featureToggles: + # Ignore errors related to the plugin not running from inside a WP environment, so paths like ABSPATH . WPINC don't resolve. + requireFileExists: false ignoreErrors: - # Ignore error related to PHP 8 using a GdImage but PHP 7 using a resource. diff --git a/plugins/auto-sizes/auto-sizes.php b/plugins/auto-sizes/auto-sizes.php index 6abd2300c1..48dbe6a46d 100644 --- a/plugins/auto-sizes/auto-sizes.php +++ b/plugins/auto-sizes/auto-sizes.php @@ -5,7 +5,7 @@ * Description: Improves responsive images with better sizes calculations and auto-sizes for lazy-loaded images. * Requires at least: 6.5 * Requires PHP: 7.2 - * Version: 1.2.0 + * Version: 1.3.0 * Author: WordPress Performance Team * Author URI: https://make.wordpress.org/performance/ * License: GPLv2 or later @@ -25,8 +25,6 @@ return; } -define( 'IMAGE_AUTO_SIZES_VERSION', '1.2.0' ); +define( 'IMAGE_AUTO_SIZES_VERSION', '1.3.0' ); require_once __DIR__ . '/hooks.php'; - -require_once __DIR__ . '/optimization-detective.php'; diff --git a/plugins/auto-sizes/hooks.php b/plugins/auto-sizes/hooks.php index 66d4d1efd7..b77c06903b 100644 --- a/plugins/auto-sizes/hooks.php +++ b/plugins/auto-sizes/hooks.php @@ -42,7 +42,6 @@ function auto_sizes_update_image_attributes( $attr ): array { return $attr; } -add_filter( 'wp_get_attachment_image_attributes', 'auto_sizes_update_image_attributes' ); /** * Adds auto to the sizes attribute to the image, if applicable. @@ -85,7 +84,12 @@ function auto_sizes_update_content_img_tag( $html ): string { $processor->set_attribute( 'sizes', "auto, $sizes" ); return $processor->get_updated_html(); } -add_filter( 'wp_content_img_tag', 'auto_sizes_update_content_img_tag' ); + +// Skip loading plugin filters if WordPress Core already loaded the functionality. +if ( ! function_exists( 'wp_sizes_attribute_includes_valid_auto' ) ) { + add_filter( 'wp_get_attachment_image_attributes', 'auto_sizes_update_image_attributes' ); + add_filter( 'wp_content_img_tag', 'auto_sizes_update_content_img_tag' ); +} /** * Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list. @@ -98,8 +102,8 @@ function auto_sizes_update_content_img_tag( $html ): string { * @return bool True if the 'auto' keyword is present, false otherwise. */ function auto_sizes_attribute_includes_valid_auto( string $sizes_attr ): bool { - $token = strtok( strtolower( $sizes_attr ), ',' ); - return false !== $token && 'auto' === trim( $token, " \t\f\r\n" ); + list( $first_size ) = explode( ',', $sizes_attr, 2 ); + return 'auto' === strtolower( trim( $first_size, " \t\f\r\n" ) ); } /** diff --git a/plugins/auto-sizes/optimization-detective.php b/plugins/auto-sizes/optimization-detective.php deleted file mode 100644 index 3b10027dac..0000000000 --- a/plugins/auto-sizes/optimization-detective.php +++ /dev/null @@ -1,66 +0,0 @@ -processor->get_tag() ) { - return false; - } - - $sizes = $context->processor->get_attribute( 'sizes' ); - if ( ! is_string( $sizes ) ) { - return false; - } - - $sizes = preg_split( '/\s*,\s*/', $sizes ); - if ( ! is_array( $sizes ) ) { - return false; - } - - $is_lazy_loaded = ( 'lazy' === $context->processor->get_attribute( 'loading' ) ); - $has_auto_sizes = in_array( 'auto', $sizes, true ); - - $changed = false; - if ( $is_lazy_loaded && ! $has_auto_sizes ) { - array_unshift( $sizes, 'auto' ); - $changed = true; - } elseif ( ! $is_lazy_loaded && $has_auto_sizes ) { - $sizes = array_diff( $sizes, array( 'auto' ) ); - $changed = true; - } - if ( $changed ) { - $context->processor->set_attribute( 'sizes', join( ', ', $sizes ) ); - } - - return false; // Since this tag visitor does not require this tag to be included in the URL Metrics. -} - -/** - * Registers the tag visitor for image tags. - * - * @since 1.1.0 - * - * @param OD_Tag_Visitor_Registry $registry Tag visitor registry. - */ -function auto_sizes_register_tag_visitors( OD_Tag_Visitor_Registry $registry ): void { - $registry->register( 'auto-sizes', 'auto_sizes_visit_tag' ); -} - -// Important: The Image Prioritizer's IMG tag visitor is registered at priority 10, so priority 100 ensures that the loading attribute has been correctly set by the time the Auto Sizes visitor runs. -add_action( 'od_register_tag_visitors', 'auto_sizes_register_tag_visitors', 100 ); diff --git a/plugins/auto-sizes/readme.txt b/plugins/auto-sizes/readme.txt index 46c9ed1b0b..582d84eded 100644 --- a/plugins/auto-sizes/readme.txt +++ b/plugins/auto-sizes/readme.txt @@ -2,7 +2,7 @@ Contributors: wordpressdotorg Tested up to: 6.6 -Stable tag: 1.2.0 +Stable tag: 1.3.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, images, auto-sizes @@ -52,6 +52,13 @@ Contributions are always welcome! Learn more about how to get involved in the [C == Changelog == += 1.3.0 = + +**Enhancements** + +* Move Auto Sizes logic from Enhanced Responsive Images to Image Prioritizer. ([1476](https://github.com/WordPress/performance/pull/1476)) +* Update auto sizes logic in Enhanced Responsive Images plugin to no longer load if already in Core. ([1547](https://github.com/WordPress/performance/pull/1547)) + = 1.2.0 = **Enhancements** diff --git a/plugins/auto-sizes/tests/test-auto-sizes.php b/plugins/auto-sizes/tests/test-auto-sizes.php index 13898a4ae9..8560c33521 100644 --- a/plugins/auto-sizes/tests/test-auto-sizes.php +++ b/plugins/auto-sizes/tests/test-auto-sizes.php @@ -33,8 +33,8 @@ public function get_image_tag( int $attachment_id ): string { } public function test_hooks(): void { - $this->assertSame( 10, has_filter( 'wp_get_attachment_image_attributes', 'auto_sizes_update_image_attributes' ) ); - $this->assertSame( 10, has_filter( 'wp_content_img_tag', 'auto_sizes_update_content_img_tag' ) ); + $this->assertSame( function_exists( 'wp_sizes_attribute_includes_valid_auto' ) ? false : 10, has_filter( 'wp_get_attachment_image_attributes', 'auto_sizes_update_image_attributes' ) ); + $this->assertSame( function_exists( 'wp_sizes_attribute_includes_valid_auto' ) ? false : 10, has_filter( 'wp_content_img_tag', 'auto_sizes_update_content_img_tag' ) ); $this->assertSame( 10, has_action( 'wp_head', 'auto_sizes_render_generator' ) ); } @@ -262,6 +262,10 @@ public function data_provider_to_test_auto_sizes_update_content_img_tag(): array 'input' => '', 'expected' => '', ), + 'expected_when_auto_size_preceded_by_extra_commas' => array( + 'input' => '', + 'expected' => '', + ), ); } diff --git a/plugins/auto-sizes/tests/test-optimization-detective.php b/plugins/auto-sizes/tests/test-optimization-detective.php deleted file mode 100644 index 0dd936c812..0000000000 --- a/plugins/auto-sizes/tests/test-optimization-detective.php +++ /dev/null @@ -1,123 +0,0 @@ -markTestSkipped( 'Optimization Detective is not active.' ); - } - } - - /** - * Tests auto_sizes_register_tag_visitors(). - * - * @covers ::auto_sizes_register_tag_visitors - */ - public function test_auto_sizes_register_tag_visitors(): void { - if ( ! class_exists( OD_Tag_Visitor_Registry::class ) ) { - $this->markTestSkipped( 'Optimization Detective is not active.' ); - } - $registry = new OD_Tag_Visitor_Registry(); - auto_sizes_register_tag_visitors( $registry ); - $this->assertTrue( $registry->is_registered( 'auto-sizes' ) ); - $this->assertEquals( 'auto_sizes_visit_tag', $registry->get_registered( 'auto-sizes' ) ); - } - - /** - * Data provider. - * - * @return array Data. - */ - public function data_provider_test_od_optimize_template_output_buffer(): array { - return array( - // Note: The Image Prioritizer plugin removes the loading attribute, and so then Auto Sizes does not then add sizes=auto. - 'wrongly_lazy_responsive_img' => array( - 'element_metrics' => array( - 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', - 'isLCP' => false, - 'intersectionRatio' => 1, - ), - 'buffer' => 'Foo', - 'expected' => 'Foo', - ), - - 'non_responsive_image' => array( - 'element_metrics' => array( - 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', - 'isLCP' => false, - 'intersectionRatio' => 0, - ), - 'buffer' => 'Quux', - 'expected' => 'Quux', - ), - - 'auto_sizes_added' => array( - 'element_metrics' => array( - 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', - 'isLCP' => false, - 'intersectionRatio' => 0, - ), - 'buffer' => 'Foo', - 'expected' => 'Foo', - ), - - 'auto_sizes_already_added' => array( - 'element_metrics' => array( - 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', - 'isLCP' => false, - 'intersectionRatio' => 0, - ), - 'buffer' => 'Foo', - 'expected' => 'Foo', - ), - - // If Auto Sizes added the sizes=auto attribute but Image Prioritizer ended up removing it due to the image not being lazy-loaded, remove sizes=auto again. - 'wrongly_auto_sized_responsive_img' => array( - 'element_metrics' => array( - 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', - 'isLCP' => false, - 'intersectionRatio' => 1, - ), - 'buffer' => 'Foo', - 'expected' => 'Foo', - ), - ); - } - - /** - * Test auto_sizes_visit_tag(). - * - * @covers ::auto_sizes_visit_tag - * - * @dataProvider data_provider_test_od_optimize_template_output_buffer - * @phpstan-param array{ xpath: string, isLCP: bool, intersectionRatio: int } $element_metrics - */ - public function test_od_optimize_template_output_buffer( array $element_metrics, string $buffer, string $expected ): void { - $this->populate_url_metrics( array( $element_metrics ) ); - - $html_start_doc = '...'; - $html_end_doc = ''; - - $buffer = od_optimize_template_output_buffer( $html_start_doc . $buffer . $html_end_doc ); - $buffer = preg_replace( '#.+?]*>#s', '', $buffer ); - $buffer = preg_replace( '#.*$#s', '', $buffer ); - - $this->assertEquals( - $this->remove_initial_tabs( $expected ), - $this->remove_initial_tabs( $buffer ), - "Buffer snapshot:\n$buffer" - ); - } -} diff --git a/plugins/dominant-color-images/hooks.php b/plugins/dominant-color-images/hooks.php index 1eb930dba0..71c304a98d 100644 --- a/plugins/dominant-color-images/hooks.php +++ b/plugins/dominant-color-images/hooks.php @@ -100,14 +100,18 @@ function dominant_color_img_tag_add_dominant_color( $filtered_image, string $con return $filtered_image; } - // Only apply the dominant color to images that have a src attribute that - // starts with a double quote, ensuring escaped JSON is also excluded. - if ( ! str_contains( $filtered_image, ' src="' ) ) { + $processor = new WP_HTML_Tag_Processor( $filtered_image ); + if ( ! $processor->next_tag( array( 'tag_name' => 'IMG' ) ) ) { + return $filtered_image; + } + + // Only apply the dominant color to images that have a src attribute. + if ( ! is_string( $processor->get_attribute( 'src' ) ) ) { return $filtered_image; } // Ensure to not run the logic below in case relevant attributes are already present. - if ( str_contains( $filtered_image, ' data-dominant-color="' ) || str_contains( $filtered_image, ' data-has-transparency="' ) ) { + if ( null !== $processor->get_attribute( 'data-dominant-color' ) || null !== $processor->get_attribute( 'data-has-transparency' ) ) { return $filtered_image; } @@ -134,34 +138,23 @@ function dominant_color_img_tag_add_dominant_color( $filtered_image, string $con return $filtered_image; } - $data = ''; - $extra_class = ''; - if ( ! empty( $image_meta['dominant_color'] ) ) { - $data .= sprintf( 'data-dominant-color="%s" ', esc_attr( $image_meta['dominant_color'] ) ); + $processor->set_attribute( 'data-dominant-color', $image_meta['dominant_color'] ); - if ( str_contains( $filtered_image, ' style="' ) ) { - $filtered_image = str_replace( ' style="', ' style="--dominant-color: #' . esc_attr( $image_meta['dominant_color'] ) . '; ', $filtered_image ); - } else { - $filtered_image = str_replace( 'get_attribute( 'style' ) ) { + $style_attribute .= $processor->get_attribute( 'style' ); } + $processor->set_attribute( 'style', trim( $style_attribute ) ); } if ( isset( $image_meta['has_transparency'] ) ) { $transparency = $image_meta['has_transparency'] ? 'true' : 'false'; - $data .= sprintf( 'data-has-transparency="%s" ', $transparency ); - $extra_class = $image_meta['has_transparency'] ? 'has-transparency' : 'not-transparent'; - } - - if ( '' !== $data ) { - $filtered_image = str_replace( 'set_attribute( 'data-has-transparency', $transparency ); + $processor->add_class( $image_meta['has_transparency'] ? 'has-transparency' : 'not-transparent' ); } - return $filtered_image; + return $processor->get_updated_html(); } add_filter( 'wp_content_img_tag', 'dominant_color_img_tag_add_dominant_color', 20, 3 ); diff --git a/plugins/dominant-color-images/load.php b/plugins/dominant-color-images/load.php index b505d94a8e..6f36b559eb 100644 --- a/plugins/dominant-color-images/load.php +++ b/plugins/dominant-color-images/load.php @@ -5,7 +5,7 @@ * Description: Displays placeholders based on an image's dominant color while the image is loading. * Requires at least: 6.5 * Requires PHP: 7.2 - * Version: 1.1.1 + * Version: 1.1.2 * Author: WordPress Performance Team * Author URI: https://make.wordpress.org/performance/ * License: GPLv2 or later @@ -25,7 +25,7 @@ return; } -define( 'DOMINANT_COLOR_IMAGES_VERSION', '1.1.1' ); +define( 'DOMINANT_COLOR_IMAGES_VERSION', '1.1.2' ); require_once __DIR__ . '/helper.php'; require_once __DIR__ . '/hooks.php'; diff --git a/plugins/dominant-color-images/readme.txt b/plugins/dominant-color-images/readme.txt index 88880c4c4d..2ac409272c 100644 --- a/plugins/dominant-color-images/readme.txt +++ b/plugins/dominant-color-images/readme.txt @@ -2,7 +2,7 @@ Contributors: wordpressdotorg Tested up to: 6.6 -Stable tag: 1.1.1 +Stable tag: 1.1.2 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, images, dominant color @@ -47,6 +47,17 @@ Contributions are always welcome! Learn more about how to get involved in the [C == Changelog == += 1.1.2 = + +**Enhancements** + +* Use more robust HTML Tag Processor for Image Placeholders. ([1477](https://github.com/WordPress/performance/pull/1477)) + +**Bug Fixes** + +* Re-remove unneeded phpcs:ignore. ([1231](https://github.com/WordPress/performance/pull/1231)) +* Update PHPStan to 1.11.5. ([1318](https://github.com/WordPress/performance/pull/1318)) + = 1.1.1 = **Enhancements** diff --git a/plugins/dominant-color-images/tests/test-dominant-color.php b/plugins/dominant-color-images/tests/test-dominant-color.php index 9c17b55de1..7214f765cc 100644 --- a/plugins/dominant-color-images/tests/test-dominant-color.php +++ b/plugins/dominant-color-images/tests/test-dominant-color.php @@ -183,17 +183,13 @@ public function test_dominant_color_img_tag_add_dominant_color_requires_proper_q */ public function data_dominant_color_img_tag_add_dominant_color_requires_proper_quotes(): array { return array( - 'double quotes' => array( + 'double quotes' => array( 'image' => '', 'expected' => true, ), - 'single quotes' => array( + 'single quotes' => array( 'image' => "", - 'expected' => false, - ), - 'escaped double quotes' => array( - 'image' => '', - 'expected' => false, + 'expected' => true, ), ); } diff --git a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php index 287e955f31..926e448625 100644 --- a/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php +++ b/plugins/image-prioritizer/class-image-prioritizer-img-tag-visitor.php @@ -40,6 +40,20 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { $xpath = $processor->get_xpath(); + /** + * Gets attribute value. + * + * @param string $attribute_name Attribute name. + * @return string|true|null Normalized attribute value. + */ + $get_attribute_value = static function ( string $attribute_name ) use ( $processor ) { + $value = $processor->get_attribute( $attribute_name ); + if ( is_string( $value ) ) { + $value = strtolower( trim( $value, " \t\f\r\n" ) ); + } + return $value; + }; + /* * When the same LCP element is common/shared among all viewport groups, make sure that the element has * fetchpriority=high, even though it won't really be needed because a preload link with fetchpriority=high @@ -47,7 +61,7 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { */ $common_lcp_element = $context->url_metrics_group_collection->get_common_lcp_element(); if ( ! is_null( $common_lcp_element ) && $xpath === $common_lcp_element['xpath'] ) { - if ( 'high' === $processor->get_attribute( 'fetchpriority' ) ) { + if ( 'high' === $get_attribute_value( 'fetchpriority' ) ) { $processor->set_meta_attribute( 'fetchpriority-already-added', true ); } else { $processor->set_attribute( 'fetchpriority', 'high' ); @@ -81,7 +95,7 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { } else { // Otherwise, make sure visible elements omit the loading attribute, and hidden elements include loading=lazy. $is_visible = $element_max_intersection_ratio > 0.0; - $loading = (string) $processor->get_attribute( 'loading' ); + $loading = $get_attribute_value( 'loading' ); if ( $is_visible && 'lazy' === $loading ) { $processor->remove_attribute( 'loading' ); } elseif ( ! $is_visible && 'lazy' !== $loading ) { @@ -90,6 +104,23 @@ public function __invoke( OD_Tag_Visitor_Context $context ): bool { } // TODO: If an image is visible in one breakpoint but not another, add loading=lazy AND add a regular-priority preload link with media queries (unless LCP in which case it should already have a fetchpriority=high link) so that the image won't be eagerly-loaded for viewports on which it is not shown. + // Ensure that sizes=auto is set properly. + $sizes = $processor->get_attribute( 'sizes' ); + if ( is_string( $sizes ) ) { + $is_lazy = 'lazy' === $get_attribute_value( 'loading' ); + $has_auto = $this->sizes_attribute_includes_valid_auto( $sizes ); + + if ( $is_lazy && ! $has_auto ) { + $processor->set_attribute( 'sizes', "auto, $sizes" ); + } elseif ( ! $is_lazy && $has_auto ) { + // Remove auto from the beginning of the list. + $processor->set_attribute( + 'sizes', + (string) preg_replace( '/^[ \t\f\r\n]*auto[ \t\f\r\n]*(,[ \t\f\r\n]*)?/i', '', $sizes ) + ); + } + } + // If this element is the LCP (for a breakpoint group), add a preload link for it. foreach ( $context->url_metrics_group_collection->get_groups_by_lcp_element( $xpath ) as $group ) { $link_attributes = array_merge( @@ -110,8 +141,8 @@ static function ( string $value ): bool { ) ); - $crossorigin = $processor->get_attribute( 'crossorigin' ); - if ( is_string( $crossorigin ) ) { + $crossorigin = $get_attribute_value( 'crossorigin' ); + if ( null !== $crossorigin ) { $link_attributes['crossorigin'] = 'use-credentials' === $crossorigin ? 'use-credentials' : 'anonymous'; } @@ -126,4 +157,24 @@ static function ( string $value ): bool { return true; } + + /** + * Checks whether the given 'sizes' attribute includes the 'auto' keyword as the first item in the list. + * + * Per the HTML spec, if present it must be the first entry. + * + * @since 0.1.4 + * + * @param string $sizes_attr The 'sizes' attribute value. + * @return bool True if the 'auto' keyword is present, false otherwise. + */ + private function sizes_attribute_includes_valid_auto( string $sizes_attr ): bool { + if ( function_exists( 'wp_sizes_attribute_includes_valid_auto' ) ) { + return wp_sizes_attribute_includes_valid_auto( $sizes_attr ); + } elseif ( function_exists( 'auto_sizes_attribute_includes_valid_auto' ) ) { + return auto_sizes_attribute_includes_valid_auto( $sizes_attr ); + } else { + return 'auto' === $sizes_attr || str_starts_with( $sizes_attr, 'auto,' ); + } + } } diff --git a/plugins/image-prioritizer/load.php b/plugins/image-prioritizer/load.php index 550a26c8b8..97b9983ee2 100644 --- a/plugins/image-prioritizer/load.php +++ b/plugins/image-prioritizer/load.php @@ -6,7 +6,7 @@ * Requires at least: 6.5 * Requires PHP: 7.2 * Requires Plugins: optimization-detective - * Version: 0.1.3 + * Version: 0.1.4 * Author: WordPress Performance Team * Author URI: https://make.wordpress.org/performance/ * License: GPLv2 or later @@ -66,7 +66,7 @@ static function ( string $global_var_name, string $version, Closure $load ): voi } )( 'image_prioritizer_pending_plugin', - '0.1.3', + '0.1.4', static function ( string $version ): void { // Define the constant. diff --git a/plugins/image-prioritizer/readme.txt b/plugins/image-prioritizer/readme.txt index 02838b6fc1..7e96509afb 100644 --- a/plugins/image-prioritizer/readme.txt +++ b/plugins/image-prioritizer/readme.txt @@ -2,7 +2,7 @@ Contributors: wordpressdotorg Tested up to: 6.6 -Stable tag: 0.1.3 +Stable tag: 0.1.4 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, optimization, image, lcp, lazy-load @@ -62,6 +62,12 @@ The [plugin source code](https://github.com/WordPress/performance/tree/trunk/plu == Changelog == += 0.1.4 = + +**Enhancements** + +* Move Auto Sizes logic from Enhanced Responsive Images to Image Prioritizer. ([1476](https://github.com/WordPress/performance/pull/1476)) + = 0.1.3 = **Bug Fixes** diff --git a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data.php b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data.php index 356ddfb0d6..31f6a9eba6 100644 --- a/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data.php +++ b/plugins/image-prioritizer/tests/test-cases/common-lcp-image-and-lazy-loaded-image-outside-viewport-with-fully-populated-sample-data.php @@ -55,7 +55,7 @@

Now the following image is definitely outside the initial viewport.

Baz Qux - Quux + Quux ', @@ -73,7 +73,7 @@

Now the following image is definitely outside the initial viewport.

Baz Qux - Quux + Quux ', diff --git a/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints.php b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints.php new file mode 100644 index 0000000000..c51a47dcac --- /dev/null +++ b/plugins/image-prioritizer/tests/test-cases/different-lcp-elements-for-all-breakpoints.php @@ -0,0 +1,77 @@ + static function ( Test_Image_Prioritizer_Helper $test_case ): void { + $breakpoint_max_widths = array( 480, 600, 782 ); + + add_filter( + 'od_breakpoint_max_widths', + static function () use ( $breakpoint_max_widths ) { + return $breakpoint_max_widths; + } + ); + + foreach ( array_merge( $breakpoint_max_widths, array( 1600 ) ) as $i => $viewport_width ) { + $elements = array( + array( + 'isLCP' => false, + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', + ), + array( + 'isLCP' => false, + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[2][self::IMG]', + ), + array( + 'isLCP' => false, + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[3][self::IMG]', + ), + array( + 'isLCP' => false, + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[4][self::IMG]', + ), + ); + $elements[ $i ]['isLCP'] = true; + OD_URL_Metrics_Post_Type::store_url_metric( + od_get_url_metrics_slug( od_get_normalized_query_vars() ), + $test_case->get_sample_url_metric( + array( + 'viewport_width' => $viewport_width, + 'elements' => $elements, + ) + ) + ); + } + }, + 'buffer' => ' + + + + ... + + + Mobile Logo + Phablet Logo + Tablet Logo + Desktop Logo + + + ', + 'expected' => ' + + + + ... + + + + + + + Mobile Logo + Phablet Logo + Tablet Logo + Desktop Logo + + + + ', +); diff --git a/plugins/image-prioritizer/tests/test-helper.php b/plugins/image-prioritizer/tests/test-helper.php index 716e057bc6..708c062a53 100644 --- a/plugins/image-prioritizer/tests/test-helper.php +++ b/plugins/image-prioritizer/tests/test-helper.php @@ -63,4 +63,100 @@ public function test_image_prioritizer_register_tag_visitors( Closure $set_up, s "Buffer snapshot:\n$buffer" ); } + + /** + * Data provider. + * + * @return array Data. + */ + public function data_provider_test_auto_sizes(): array { + return array( + // Note: The Image Prioritizer plugin removes the loading attribute, and so then Auto Sizes does not then add sizes=auto. + 'wrongly_lazy_responsive_img' => array( + 'element_metrics' => array( + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 1, + ), + 'buffer' => 'Foo', + 'expected' => 'Foo', + ), + + 'non_responsive_image' => array( + 'element_metrics' => array( + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 0, + ), + 'buffer' => 'Quux', + 'expected' => 'Quux', + ), + + 'auto_sizes_added' => array( + 'element_metrics' => array( + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 0, + ), + 'buffer' => 'Foo', + 'expected' => 'Foo', + ), + + 'auto_sizes_already_added' => array( + 'element_metrics' => array( + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 0, + ), + 'buffer' => 'Foo', + 'expected' => 'Foo', + ), + + // If Auto Sizes added the sizes=auto attribute but Image Prioritizer ended up removing it due to the image not being lazy-loaded, remove sizes=auto again. + 'wrongly_auto_sized_responsive_img' => array( + 'element_metrics' => array( + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 1, + ), + 'buffer' => 'Foo', + 'expected' => 'Foo', + ), + + 'wrongly_auto_sized_responsive_img_with_only_auto' => array( + 'element_metrics' => array( + 'xpath' => '/*[1][self::HTML]/*[2][self::BODY]/*[1][self::IMG]', + 'isLCP' => false, + 'intersectionRatio' => 1, + ), + 'buffer' => 'Foo', + 'expected' => 'Foo', + ), + ); + } + + /** + * Test auto sizes. + * + * @covers Image_Prioritizer_Img_Tag_Visitor::__invoke + * + * @dataProvider data_provider_test_auto_sizes + * @phpstan-param array{ xpath: string, isLCP: bool, intersectionRatio: int } $element_metrics + */ + public function test_auto_sizes( array $element_metrics, string $buffer, string $expected ): void { + $this->populate_url_metrics( array( $element_metrics ) ); + + $html_start_doc = '...'; + $html_end_doc = ''; + + $buffer = od_optimize_template_output_buffer( $html_start_doc . $buffer . $html_end_doc ); + $buffer = preg_replace( '#.+?]*>#s', '', $buffer ); + $buffer = preg_replace( '#.*$#s', '', $buffer ); + + $this->assertEquals( + $this->remove_initial_tabs( $expected ), + $this->remove_initial_tabs( $buffer ), + "Buffer snapshot:\n$buffer" + ); + } } diff --git a/plugins/optimization-detective/class-od-html-tag-processor.php b/plugins/optimization-detective/class-od-html-tag-processor.php index 900f0a5b1b..3996dfdf8a 100644 --- a/plugins/optimization-detective/class-od-html-tag-processor.php +++ b/plugins/optimization-detective/class-od-html-tag-processor.php @@ -181,7 +181,7 @@ final class OD_HTML_Tag_Processor extends WP_HTML_Tag_Processor { /** * Count for the number of times that the cursor was moved. * - * @since n.e.x.t + * @since 0.6.0 * @var int * @see self::next_token() * @see self::seek() @@ -342,7 +342,7 @@ public function next_token(): bool { /** * Gets the number of times the cursor has moved. * - * @since n.e.x.t + * @since 0.6.0 * @see self::next_token() * @see self::seek() * diff --git a/plugins/optimization-detective/class-od-link-collection.php b/plugins/optimization-detective/class-od-link-collection.php index 28084c9bd5..b1fbfacb6c 100644 --- a/plugins/optimization-detective/class-od-link-collection.php +++ b/plugins/optimization-detective/class-od-link-collection.php @@ -25,7 +25,7 @@ * href?: non-empty-string, * imagesrcset?: non-empty-string, * imagesizes?: non-empty-string, - * crossorigin?: ''|'anonymous'|'use-credentials', + * crossorigin?: 'anonymous'|'use-credentials', * fetchpriority?: 'high'|'low'|'auto', * as?: 'audio'|'document'|'embed'|'fetch'|'font'|'image'|'object'|'script'|'style'|'track'|'video'|'worker', * media?: non-empty-string, diff --git a/plugins/optimization-detective/class-od-strict-url-metric.php b/plugins/optimization-detective/class-od-strict-url-metric.php new file mode 100644 index 0000000000..f3ecfede21 --- /dev/null +++ b/plugins/optimization-detective/class-od-strict-url-metric.php @@ -0,0 +1,79 @@ + Schema. + */ + public static function get_json_schema(): array { + return self::set_additional_properties_to_false( parent::get_json_schema() ); + } + + /** + * Recursively processes the schema to ensure that all objects have additionalProperties set to false. + * + * This is a forked version of `rest_default_additional_properties_to_false()` which isn't being used itself because + * it does not override `additionalProperties` to be false, but rather only sets it when it is empty. + * + * @since 0.6.0 + * @see rest_default_additional_properties_to_false() + * + * @param mixed $schema Schema. + * @return mixed Processed schema. + */ + private static function set_additional_properties_to_false( $schema ) { + if ( ! isset( $schema['type'] ) ) { + return $schema; + } + + $type = (array) $schema['type']; + + if ( in_array( 'object', $type, true ) ) { + if ( isset( $schema['properties'] ) ) { + foreach ( $schema['properties'] as $key => $child_schema ) { + $schema['properties'][ $key ] = self::set_additional_properties_to_false( $child_schema ); + } + } + + if ( isset( $schema['patternProperties'] ) ) { + foreach ( $schema['patternProperties'] as $key => $child_schema ) { + $schema['patternProperties'][ $key ] = self::set_additional_properties_to_false( $child_schema ); + } + } + + $schema['additionalProperties'] = false; + } + + if ( in_array( 'array', $type, true ) ) { + if ( isset( $schema['items'] ) ) { + $schema['items'] = self::set_additional_properties_to_false( $schema['items'] ); + } + } + + return $schema; + } +} diff --git a/plugins/optimization-detective/class-od-url-metric.php b/plugins/optimization-detective/class-od-url-metric.php index 83eca93e81..9de7d57a98 100644 --- a/plugins/optimization-detective/class-od-url-metric.php +++ b/plugins/optimization-detective/class-od-url-metric.php @@ -47,42 +47,42 @@ * @since 0.1.0 * @access private */ -final class OD_URL_Metric implements JsonSerializable { +class OD_URL_Metric implements JsonSerializable { /** * Data. * * @var Data */ - private $data; + protected $data; /** * Constructor. * * @phpstan-param Data|array $data Valid data or invalid data (in which case an exception is thrown). * - * @param array $data URL metric data. - * * @throws OD_Data_Validation_Exception When the input is invalid. + * + * @param array $data URL metric data. */ public function __construct( array $data ) { if ( ! isset( $data['uuid'] ) ) { $data['uuid'] = wp_generate_uuid4(); } - $this->validate_data( $data ); - $this->data = $data; + $this->data = $this->prepare_data( $data ); } /** - * Validate data. + * Prepares data with validation and sanitization. * - * @phpstan-assert Data $data + * @throws OD_Data_Validation_Exception When the input is invalid. * * @param array $data Data to validate. - * @throws OD_Data_Validation_Exception When the input is invalid. + * @return Data Validated and sanitized data. */ - private function validate_data( array $data ): void { - $valid = rest_validate_object_value_from_schema( $data, self::get_json_schema(), self::class ); + private function prepare_data( array $data ): array { + $schema = static::get_json_schema(); + $valid = rest_validate_object_value_from_schema( $data, $schema, self::class ); if ( is_wp_error( $valid ) ) { throw new OD_Data_Validation_Exception( esc_html( $valid->get_error_message() ) ); } @@ -105,6 +105,7 @@ private function validate_data( array $data ): void { ) ); } + return rest_sanitize_value_from_schema( $data, $schema, self::class ); } /** @@ -116,12 +117,12 @@ private function validate_data( array $data ): void { */ public static function get_json_schema(): array { /* - * The intersectionRect and clientBoundingRect are both instances of the DOMRectReadOnly, which + * The intersectionRect and boundingClientRect are both instances of the DOMRectReadOnly, which * the following schema describes. See . * Note that 'number' is used specifically instead of 'integer' because the values are all specified as * floats/doubles. */ - $properties = array_fill_keys( + $dom_rect_properties = array_fill_keys( array( 'width', 'height', @@ -139,17 +140,17 @@ public static function get_json_schema(): array { ); // The spec allows these to be negative but this doesn't make sense in the context of intersectionRect and boundingClientRect. - $properties['width']['minimum'] = 0.0; - $properties['height']['minimum'] = 0.0; + $dom_rect_properties['width']['minimum'] = 0.0; + $dom_rect_properties['height']['minimum'] = 0.0; $dom_rect_schema = array( 'type' => 'object', 'required' => true, - 'properties' => $properties, + 'properties' => $dom_rect_properties, 'additionalProperties' => false, ); - return array( + $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => 'od-url-metric', 'type' => 'object', @@ -225,12 +226,136 @@ public static function get_json_schema(): array { 'intersectionRect' => $dom_rect_schema, 'boundingClientRect' => $dom_rect_schema, ), - 'additionalProperties' => false, + + // Additional properties may be added to the schema for items of elements via the od_url_metric_schema_element_item_additional_properties filter. + // See further explanation below. + 'additionalProperties' => true, ), ), ), - 'additionalProperties' => false, + // Additional root properties may be added to the schema via the od_url_metric_schema_root_additional_properties filter. + // Therefore, additionalProperties is set to true so that additional properties defined in the extended schema may persist + // in a stored URL Metric even when the extension is deactivated. For REST API requests, the OD_Strict_URL_Metric + // which sets this to false so that newly-submitted URL Metrics only ever include the known properties. + 'additionalProperties' => true, ); + + /** + * Filters additional schema properties which should be allowed at the root of a URL metric. + * + * @since 0.6.0 + * + * @param array $additional_properties Additional properties. + */ + $additional_properties = (array) apply_filters( 'od_url_metric_schema_root_additional_properties', array() ); + if ( count( $additional_properties ) > 0 ) { + $schema['properties'] = self::extend_schema_with_optional_properties( $schema['properties'], $additional_properties, 'od_url_metric_schema_root_additional_properties' ); + } + + /** + * Filters additional schema properties which should be allowed for an elements item in a URL metric. + * + * @since 0.6.0 + * + * @param array $additional_properties Additional properties. + */ + $additional_properties = (array) apply_filters( 'od_url_metric_schema_element_item_additional_properties', array() ); + if ( count( $additional_properties ) > 0 ) { + $schema['properties']['elements']['items']['properties'] = self::extend_schema_with_optional_properties( + $schema['properties']['elements']['items']['properties'], + $additional_properties, + 'od_url_metric_schema_root_additional_properties' + ); + } + + return $schema; + } + + /** + * Extends a schema with additional optional properties. + * + * @since 0.6.0 + * + * @param array $properties_schema Properties schema to extend. + * @param array $additional_properties Additional properties. + * @param string $filter_name Filter name used to extend. + * + * @return array Extended schema. + */ + protected static function extend_schema_with_optional_properties( array $properties_schema, array $additional_properties, string $filter_name ): array { + $doing_it_wrong = static function ( string $message ) use ( $filter_name ): void { + _doing_it_wrong( + esc_html( "Filter: '{$filter_name}'" ), + esc_html( $message ), + 'Optimization Detective 0.6.0' + ); + }; + foreach ( $additional_properties as $property_key => $property_schema ) { + if ( ! is_array( $property_schema ) ) { + continue; + } + if ( isset( $properties_schema[ $property_key ] ) ) { + $doing_it_wrong( + sprintf( + /* translators: property name */ + __( 'Disallowed override of existing schema property "%s".', 'optimization-detective' ), + $property_key + ) + ); + continue; + } + if ( ! isset( $property_schema['type'] ) || ! ( is_string( $property_schema['type'] ) || is_array( $property_schema['type'] ) ) ) { + $doing_it_wrong( + sprintf( + /* translators: 1: property name, 2: 'type' */ + __( 'Supplied schema property "%1$s" with missing "%2$s" key.', 'optimization-detective' ), + 'type', + $property_key + ) + ); + continue; + } + if ( isset( $property_schema['required'] ) && false !== $property_schema['required'] ) { + $doing_it_wrong( + sprintf( + /* translators: 1: property name, 2: 'required' */ + __( 'Supplied schema property "%1$s" has a truthy value for "%2$s". All extended properties must be optional so that URL Metrics are not all immediately invalidated once an extension is deactivated.', 'optimization-detective' ), + $property_key, + 'required' + ) + ); + } + $property_schema['required'] = false; + + if ( isset( $property_schema['default'] ) ) { + $doing_it_wrong( + sprintf( + /* translators: 1: property name, 2: 'default' */ + __( 'Supplied schema property "%1$s" has disallowed "%2$s" specified.', 'optimization-detective' ), + $property_key, + 'default' + ) + ); + unset( $property_schema['default'] ); + } + + $properties_schema[ $property_key ] = $property_schema; + } + return $properties_schema; + } + + /** + * Gets property value for an arbitrary key. + * + * This is particularly useful in conjunction with the `od_url_metric_schema_root_additional_properties` filter. + * + * @since 0.6.0 + * + * @param string $key Property. + * @return mixed|null The property value, or null if not set. + */ + public function get( string $key ) { + return $this->data[ $key ] ?? null; } /** diff --git a/plugins/optimization-detective/load.php b/plugins/optimization-detective/load.php index 59e5405e7f..3edf2cf13f 100644 --- a/plugins/optimization-detective/load.php +++ b/plugins/optimization-detective/load.php @@ -5,7 +5,7 @@ * Description: Provides an API for leveraging real user metrics to detect optimizations to apply on the frontend to improve page performance. * Requires at least: 6.5 * Requires PHP: 7.2 - * Version: 0.5.0 + * Version: 0.6.0 * Author: WordPress Performance Team * Author URI: https://make.wordpress.org/performance/ * License: GPLv2 or later @@ -65,7 +65,7 @@ static function ( string $global_var_name, string $version, Closure $load ): voi } )( 'optimization_detective_pending_plugin', - '0.5.0', + '0.6.0', static function ( string $version ): void { // Define the constant. @@ -99,6 +99,7 @@ static function ( string $version ): void { require_once __DIR__ . '/class-od-data-validation-exception.php'; require_once __DIR__ . '/class-od-html-tag-processor.php'; require_once __DIR__ . '/class-od-url-metric.php'; + require_once __DIR__ . '/class-od-strict-url-metric.php'; require_once __DIR__ . '/class-od-url-metrics-group.php'; require_once __DIR__ . '/class-od-url-metrics-group-collection.php'; diff --git a/plugins/optimization-detective/readme.txt b/plugins/optimization-detective/readme.txt index dcc37e4e4b..d23ff3dfc7 100644 --- a/plugins/optimization-detective/readme.txt +++ b/plugins/optimization-detective/readme.txt @@ -2,7 +2,7 @@ Contributors: wordpressdotorg Tested up to: 6.6 -Stable tag: 0.5.0 +Stable tag: 0.6.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, optimization, rum @@ -157,6 +157,20 @@ The [plugin source code](https://github.com/WordPress/performance/tree/trunk/plu == Changelog == += 0.6.0 = + +**Enhancements** + +* Allow URL metric schema to be extended. ([1492](https://github.com/WordPress/performance/pull/1492)) +* Clarify docs around a tag visitor's boolean return value. ([1479](https://github.com/WordPress/performance/pull/1479)) +* Include UUID with each URL metric. ([1489](https://github.com/WordPress/performance/pull/1489)) +* Introduce get_cursor_move_count() to use instead of get_seek_count() and get_next_token_count(). ([1478](https://github.com/WordPress/performance/pull/1478)) + +**Bug Fixes** + +* Add missing global documentation for `delete_all_posts()`. ([1522](https://github.com/WordPress/performance/pull/1522)) +* Introduce viewport aspect ratio validation for URL Metrics. ([1494](https://github.com/WordPress/performance/pull/1494)) + = 0.5.0 = **Enhancements** diff --git a/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php b/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php index d51fbd450c..2c5dd4b98d 100644 --- a/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php +++ b/plugins/optimization-detective/storage/class-od-url-metrics-post-type.php @@ -188,6 +188,7 @@ static function ( $url_metric_data ) use ( $trigger_error ) { * Stores URL metric by merging it with the other URL metrics which share the same normalized query vars. * * @since 0.1.0 + * @todo There is duplicate logic here with od_handle_rest_request(). * * @param string $slug Slug (hash of normalized query vars). * @param OD_URL_Metric $new_url_metric New URL metric. @@ -315,6 +316,8 @@ public static function delete_stale_posts(): void { * This is used during uninstallation. * * @since 0.1.0 + * + * @global wpdb $wpdb WordPress database abstraction object. */ public static function delete_all_posts(): void { global $wpdb; diff --git a/plugins/optimization-detective/storage/data.php b/plugins/optimization-detective/storage/data.php index cbb31f672d..5c7920b1b7 100644 --- a/plugins/optimization-detective/storage/data.php +++ b/plugins/optimization-detective/storage/data.php @@ -182,7 +182,7 @@ function od_verify_url_metrics_storage_nonce( string $nonce, string $slug, strin /** * Gets the minimum allowed viewport aspect ratio for URL metrics. * - * @since n.e.x.t + * @since 0.6.0 * @access private * * @return float Minimum viewport aspect ratio for URL metrics. @@ -194,7 +194,7 @@ function od_get_minimum_viewport_aspect_ratio(): float { * The 0.4 default value is intended to accommodate the phone with the greatest known aspect * ratio at 21:9 when rotated 90 degrees to 9:21 (0.429). * - * @since n.e.x.t + * @since 0.6.0 * * @param float $minimum_viewport_aspect_ratio Minimum viewport aspect ratio. */ @@ -204,7 +204,7 @@ function od_get_minimum_viewport_aspect_ratio(): float { /** * Gets the maximum allowed viewport aspect ratio for URL metrics. * - * @since n.e.x.t + * @since 0.6.0 * @access private * * @return float Maximum viewport aspect ratio for URL metrics. @@ -216,7 +216,7 @@ function od_get_maximum_viewport_aspect_ratio(): float { * The 2.5 default value is intended to accommodate the phone with the greatest known aspect * ratio at 21:9 (2.333). * - * @since n.e.x.t + * @since 0.6.0 * * @param float $maximum_viewport_aspect_ratio Maximum viewport aspect ratio. */ diff --git a/plugins/optimization-detective/storage/rest-api.php b/plugins/optimization-detective/storage/rest-api.php index fb16ed1441..654e3647e9 100644 --- a/plugins/optimization-detective/storage/rest-api.php +++ b/plugins/optimization-detective/storage/rest-api.php @@ -67,7 +67,7 @@ function od_register_endpoint(): void { 'methods' => 'POST', 'args' => array_merge( $args, - rest_get_endpoint_args_for_schema( OD_URL_Metric::get_json_schema() ) + rest_get_endpoint_args_for_schema( OD_Strict_URL_Metric::get_json_schema() ) ), 'callback' => static function ( WP_REST_Request $request ) { return od_handle_rest_request( $request ); @@ -128,19 +128,23 @@ function od_handle_rest_request( WP_REST_Request $request ) { OD_Storage_Lock::set_lock(); try { - $url_metric = new OD_URL_Metric( - array_merge( - wp_array_slice_assoc( - $request->get_params(), - array_keys( OD_URL_Metric::get_json_schema()['properties'] ) - ), - array( - // Now supply the readonly args which were omitted from the REST API params due to being `readonly`. - 'timestamp' => microtime( true ), - 'uuid' => wp_generate_uuid4(), - ) + $data = $request->get_params(); + // Remove params which are only used for the REST API request and which are not part of a URL Metric. + unset( + $data['slug'], + $data['nonce'] + ); + $data = array_merge( + $data, + array( + // Now supply the readonly args which were omitted from the REST API params due to being `readonly`. + 'timestamp' => microtime( true ), + 'uuid' => wp_generate_uuid4(), ) ); + + // The "strict" URL Metric class is being used here to ensure additionalProperties of all objects are disallowed. + $url_metric = new OD_Strict_URL_Metric( $data ); } catch ( OD_Data_Validation_Exception $e ) { return new WP_Error( 'rest_invalid_param', @@ -153,6 +157,7 @@ function od_handle_rest_request( WP_REST_Request $request ) { ); } + // TODO: This should be changed from store_url_metric($slug, $url_metric) instead be update_post( $slug, $group_collection ). As it stands, store_url_metric() is duplicating logic here. $result = OD_URL_Metrics_Post_Type::store_url_metric( $request->get_param( 'slug' ), $url_metric @@ -161,6 +166,33 @@ function od_handle_rest_request( WP_REST_Request $request ) { if ( $result instanceof WP_Error ) { return $result; } + $post_id = $result; + + /** + * Fires whenever a URL Metric was successfully collected. + * + * @since 0.6.0 + * + * @param array $context { + * Context about the successful URL Metric collection. + * + * @var int $post_id + * @var WP_REST_Request> $request + * @var OD_Strict_URL_Metric $url_metric + * @var OD_URL_Metrics_Group $group + * @var OD_URL_Metrics_Group_Collection $group_collection + * } + */ + do_action( + 'od_url_metric_collected', + array( + 'post_id' => $post_id, + 'request' => $request, + 'url_metric' => $url_metric, + 'url_metrics_group' => $group, + 'url_metrics_group_collection' => $group_collection, + ) + ); return new WP_REST_Response( array( diff --git a/plugins/optimization-detective/tests/storage/test-class-od-url-metrics-post-type.php b/plugins/optimization-detective/tests/storage/test-class-od-url-metrics-post-type.php index 8872d13156..b9eec5d906 100644 --- a/plugins/optimization-detective/tests/storage/test-class-od-url-metrics-post-type.php +++ b/plugins/optimization-detective/tests/storage/test-class-od-url-metrics-post-type.php @@ -94,7 +94,7 @@ public function data_provider_test_get_url_metrics_from_post(): array { 'width' => 640, 'height' => 480, ), - 'timestamp' => (int) microtime( true ), // Integer to facilitate equality tests. + 'timestamp' => microtime( true ), 'elements' => array(), ), ); @@ -102,27 +102,34 @@ public function data_provider_test_get_url_metrics_from_post(): array { $valid_content_with_uuid = $valid_content; $valid_content_with_uuid[0]['uuid'] = wp_generate_uuid4(); + $valid_content_with_extra_property = $valid_content_with_uuid; + $valid_content_with_extra_property[0]['extra'] = 'foo'; + return array( - 'malformed_json' => array( + 'malformed_json' => array( 'post_content' => '{"bad":', 'expected_value' => array(), ), - 'not_array_json' => array( + 'not_array_json' => array( 'post_content' => '{"cool":"beans"}', 'expected_value' => array(), ), - 'missing_keys' => array( + 'missing_keys' => array( 'post_content' => '[{},{},{}]', 'expected_value' => array(), ), - 'valid_sans_uuid' => array( + 'valid_sans_uuid' => array( 'post_content' => wp_json_encode( $valid_content ), 'expected_value' => $valid_content, ), - 'valid_with_uuid' => array( + 'valid_with_uuid' => array( 'post_content' => wp_json_encode( $valid_content_with_uuid ), 'expected_value' => $valid_content_with_uuid, ), + 'valid_with_extra_prop' => array( + 'post_content' => wp_json_encode( $valid_content_with_extra_property ), + 'expected_value' => $valid_content_with_extra_property, + ), ); } diff --git a/plugins/optimization-detective/tests/storage/test-rest-api.php b/plugins/optimization-detective/tests/storage/test-rest-api.php index 5661a469bc..603d40cd98 100644 --- a/plugins/optimization-detective/tests/storage/test-rest-api.php +++ b/plugins/optimization-detective/tests/storage/test-rest-api.php @@ -22,15 +22,49 @@ public function test_od_register_endpoint_hooked(): void { $this->assertSame( 10, has_action( 'rest_api_init', 'od_register_endpoint' ) ); } + /** + * Data provider. + * + * @return array + */ + public function data_provider_to_test_rest_request_good_params(): array { + return array( + 'not_extended' => array( + 'set_up' => function () { + return $this->get_valid_params(); + }, + ), + 'extended' => array( + 'set_up' => function () { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( array $properties ): array { + $properties['extra'] = array( + 'type' => 'string', + ); + return $properties; + } + ); + + $params = $this->get_valid_params(); + $params['extra'] = 'foo'; + return $params; + }, + ), + ); + } + /** * Test good params. * + * @dataProvider data_provider_to_test_rest_request_good_params + * * @covers ::od_register_endpoint * @covers ::od_handle_rest_request */ - public function test_rest_request_good_params(): void { + public function test_rest_request_good_params( Closure $set_up ): void { + $valid_params = $set_up(); $request = new WP_REST_Request( 'POST', self::ROUTE ); - $valid_params = $this->get_valid_params(); $this->assertCount( 0, get_posts( array( 'post_type' => OD_URL_Metrics_Post_Type::SLUG ) ) ); $request->set_body_params( $valid_params ); $response = rest_get_server()->dispatch( $request ); @@ -47,6 +81,13 @@ public function test_rest_request_good_params(): void { $this->assertCount( 1, $url_metrics, 'Expected number of URL metrics stored.' ); $this->assertSame( $valid_params['elements'], $url_metrics[0]->get_elements() ); $this->assertSame( $valid_params['viewport']['width'], $url_metrics[0]->get_viewport_width() ); + + $expected_data = $valid_params; + unset( $expected_data['nonce'], $expected_data['slug'] ); + $this->assertSame( + $expected_data, + wp_array_slice_assoc( $url_metrics[0]->jsonSerialize(), array_keys( $expected_data ) ) + ); } /** @@ -151,6 +192,19 @@ function ( $params ) { ), ), ), + 'invalid_root_property' => array( + 'is_touch' => false, + ), + 'invalid_element_property' => array( + 'elements' => array( + array_merge( + $valid_element, + array( + 'is_big' => true, + ) + ), + ), + ), ) ); } @@ -406,6 +460,7 @@ private function get_valid_params( array $extras = array() ): array { ), ) )->jsonSerialize(); + unset( $data['timestamp'], $data['uuid'] ); // Since these are readonly. $data = array_merge( array( 'slug' => $slug, @@ -413,7 +468,6 @@ private function get_valid_params( array $extras = array() ): array { ), $data ); - unset( $data['timestamp'] ); // Since provided by default args. if ( count( $extras ) > 0 ) { $data = $this->recursive_merge( $data, $extras ); } diff --git a/plugins/optimization-detective/tests/test-class-od-strict-url-metric.php b/plugins/optimization-detective/tests/test-class-od-strict-url-metric.php new file mode 100644 index 0000000000..33be2b8ffc --- /dev/null +++ b/plugins/optimization-detective/tests/test-class-od-strict-url-metric.php @@ -0,0 +1,62 @@ + 'object', + 'properties' => array( + 'hex' => array( + 'type' => 'string', + ), + ), + 'additionalProperties' => true, + ); + return $properties; + } + ); + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ) { + $properties['region'] = array( + 'type' => 'object', + 'properties' => array( + 'continent' => array( + 'type' => 'string', + ), + ), + 'additionalProperties' => true, + ); + return $properties; + } + ); + $loose_schema = OD_URL_Metric::get_json_schema(); + $this->assertTrue( $loose_schema['additionalProperties'] ); + $this->assertFalse( $loose_schema['properties']['viewport']['additionalProperties'] ); // The viewport is never extensible. Only the root and the elements are. + $this->assertTrue( $loose_schema['properties']['elements']['items']['additionalProperties'] ); + $this->assertTrue( $loose_schema['properties']['elements']['items']['properties']['region']['additionalProperties'] ); + $this->assertTrue( $loose_schema['properties']['colors']['additionalProperties'] ); + + $strict_schema = OD_Strict_URL_Metric::get_json_schema(); + $this->assertFalse( $strict_schema['additionalProperties'] ); + $this->assertFalse( $strict_schema['properties']['viewport']['additionalProperties'] ); + $this->assertFalse( $strict_schema['properties']['elements']['items']['additionalProperties'] ); + $this->assertFalse( $strict_schema['properties']['elements']['items']['properties']['region']['additionalProperties'] ); + $this->assertFalse( $strict_schema['properties']['colors']['additionalProperties'] ); + } +} diff --git a/plugins/optimization-detective/tests/test-class-od-url-metric.php b/plugins/optimization-detective/tests/test-class-od-url-metric.php index d301997d58..0f9370a0b1 100644 --- a/plugins/optimization-detective/tests/test-class-od-url-metric.php +++ b/plugins/optimization-detective/tests/test-class-od-url-metric.php @@ -14,7 +14,7 @@ class Test_OD_URL_Metric extends WP_UnitTestCase { * * @return array Data. */ - public function data_provider(): array { + public function data_provider_to_test_constructor(): array { $viewport = array( 'width' => 640, 'height' => 480, @@ -48,6 +48,26 @@ public function data_provider(): array { ), ), ), + // This tests that sanitization converts values into their expected PHP types. + 'valid_but_props_are_strings' => array( + 'data' => array( + 'url' => home_url( '/' ), + 'viewport' => array_map( 'strval', $viewport ), + 'timestamp' => (string) microtime( true ), + 'elements' => array( + array_map( + static function ( $value ) { + if ( is_array( $value ) ) { + return array_map( 'strval', $value ); + } else { + return (string) $value; + } + }, + $valid_element + ), + ), + ), + ), 'bad_uuid' => array( 'data' => array( 'uuid' => 'foo', @@ -183,8 +203,10 @@ public function data_provider(): array { * @covers ::get_timestamp * @covers ::get_elements * @covers ::jsonSerialize + * @covers ::get + * @covers ::get_json_schema * - * @dataProvider data_provider + * @dataProvider data_provider_to_test_constructor * * @param array $data Data. * @param string $error Error. @@ -195,19 +217,267 @@ public function test_constructor( array $data, string $error = '' ): void { $this->expectExceptionMessage( $error ); } $url_metric = new OD_URL_Metric( $data ); - $this->assertSame( $data['viewport'], $url_metric->get_viewport() ); - $this->assertSame( $data['viewport']['width'], $url_metric->get_viewport_width() ); - $this->assertSame( $data['timestamp'], $url_metric->get_timestamp() ); - $this->assertSame( $data['elements'], $url_metric->get_elements() ); + + $this->assertSame( array_map( 'intval', $data['viewport'] ), $url_metric->get_viewport() ); + $this->assertSame( array_map( 'intval', $data['viewport'] ), $url_metric->get( 'viewport' ) ); + $this->assertSame( (int) $data['viewport']['width'], $url_metric->get_viewport_width() ); + + $this->assertSame( (float) $data['timestamp'], $url_metric->get_timestamp() ); + $this->assertSame( (float) $data['timestamp'], $url_metric->get( 'timestamp' ) ); + + $this->assertCount( count( $data['elements'] ), $url_metric->get_elements() ); + for ( $i = 0, $length = count( $data['elements'] ); $i < $length; $i++ ) { + $this->assertSame( (bool) $data['elements'][ $i ]['isLCP'], $url_metric->get_elements()[ $i ]['isLCP'] ); + $this->assertSame( (bool) $data['elements'][ $i ]['isLCPCandidate'], $url_metric->get_elements()[ $i ]['isLCPCandidate'] ); + $this->assertSame( (float) $data['elements'][ $i ]['intersectionRatio'], $url_metric->get_elements()[ $i ]['intersectionRatio'] ); + $this->assertSame( array_map( 'floatval', $data['elements'][ $i ]['boundingClientRect'] ), $url_metric->get_elements()[ $i ]['boundingClientRect'] ); + $this->assertSame( array_map( 'floatval', $data['elements'][ $i ]['intersectionRect'] ), $url_metric->get_elements()[ $i ]['intersectionRect'] ); + } + $this->assertSame( $url_metric->get_elements(), $url_metric->get( 'elements' ) ); + + $this->assertSame( $data['url'], $url_metric->get_url() ); + $this->assertSame( $data['url'], $url_metric->get( 'url' ) ); + $this->assertTrue( wp_is_uuid( $url_metric->get_uuid() ) ); + $this->assertSame( $url_metric->get_uuid(), $url_metric->get( 'uuid' ) ); + $serialized = $url_metric->jsonSerialize(); if ( ! array_key_exists( 'uuid', $data ) ) { $this->assertTrue( wp_is_uuid( $serialized['uuid'] ) ); unset( $serialized['uuid'] ); } + + // The use of assertEquals instead of assertSame ensures that lossy type comparisons are employed. $this->assertEquals( $data, $serialized ); } + /** + * Data provider. + * + * @return array Data. + */ + public function data_provider_to_test_constructor_with_extended_schema(): array { + $viewport = array( + 'width' => 640, + 'height' => 480, + ); + $valid_element = array( + 'isLCP' => true, + 'isLCPCandidate' => true, + 'xpath' => '/*[0][self::HTML]/*[1][self::BODY]/*[0][self::IMG]', + 'intersectionRatio' => 1.0, + 'intersectionRect' => $this->get_sample_dom_rect(), + 'boundingClientRect' => $this->get_sample_dom_rect(), + ); + + return array( + 'added_valid_root_property_populated' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( array $properties ): array { + $properties['isTouch'] = array( + 'type' => 'boolean', + ); + return $properties; + } + ); + }, + 'data' => array( + 'url' => home_url( '/' ), + 'viewport' => $viewport, + 'timestamp' => microtime( true ), + 'elements' => array(), + 'isTouch' => 1, // This should get cast to `true` after the extended schema has applied. + ), + 'assert' => function ( OD_URL_Metric $extended_url_metric, OD_URL_Metric $original_url_metric ): void { + $this->assertSame( $original_url_metric->get_viewport(), $extended_url_metric->get_viewport() ); + + $original_data = $original_url_metric->jsonSerialize(); + $this->assertArrayHasKey( 'isTouch', $original_data ); + $this->assertSame( 1, $original_data['isTouch'] ); + $this->assertSame( 1, $original_url_metric->get( 'isTouch' ) ); + + $extended_data = $extended_url_metric->jsonSerialize(); + $this->assertArrayHasKey( 'isTouch', $extended_data ); + $this->assertTrue( $extended_data['isTouch'] ); + $this->assertTrue( $extended_url_metric->get( 'isTouch' ) ); + }, + ), + + 'added_valid_root_property_not_populated' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( array $properties ): array { + $properties['isTouch'] = array( + 'type' => 'boolean', + ); + return $properties; + } + ); + }, + 'data' => array( + 'url' => home_url( '/' ), + 'viewport' => $viewport, + 'timestamp' => microtime( true ), + 'elements' => array(), + ), + 'assert' => function ( OD_URL_Metric $extended_url_metric, OD_URL_Metric $original_url_metric ): void { + $this->assertSame( $original_url_metric->get_viewport(), $extended_url_metric->get_viewport() ); + + $original_data = $original_url_metric->jsonSerialize(); + $this->assertArrayNotHasKey( 'isTouch', $original_data ); + $this->assertNull( $original_url_metric->get( 'isTouch' ) ); + + $extended_data = $extended_url_metric->jsonSerialize(); + $this->assertArrayNotHasKey( 'isTouch', $extended_data ); // If rest_sanitize_value_from_schema() took default into account (and we allowed defaults), this could be different. + $this->assertNull( $extended_url_metric->get( 'isTouch' ) ); + }, + ), + + 'added_invalid_root_property' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( array $properties ): array { + $properties['isTouch'] = array( + 'type' => 'boolean', + ); + return $properties; + } + ); + }, + 'data' => array( + 'url' => home_url( '/' ), + 'viewport' => $viewport, + 'timestamp' => microtime( true ), + 'elements' => array(), + 'isTouch' => array( 'cannot be cast to boolean' ), + ), + 'assert' => static function (): void {}, + 'error' => 'OD_URL_Metric[isTouch] is not of type boolean.', + ), + + 'added_valid_element_property_populated' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['isColorful'] = array( + 'type' => 'boolean', + ); + return $properties; + } + ); + }, + 'data' => array( + 'url' => home_url( '/' ), + 'viewport' => $viewport, + 'timestamp' => microtime( true ), + 'elements' => array( + array_merge( + $valid_element, + array( 'isColorful' => 'false' ) + ), + ), + ), + 'assert' => function ( OD_URL_Metric $extended_url_metric, OD_URL_Metric $original_url_metric ): void { + $this->assertSame( $original_url_metric->get_viewport(), $extended_url_metric->get_viewport() ); + + $original_data = $original_url_metric->jsonSerialize(); + $this->assertArrayHasKey( 'isColorful', $original_data['elements'][0] ); + $this->assertSame( 'false', $original_data['elements'][0]['isColorful'] ); + $this->assertSame( 'false', $original_url_metric->get_elements()[0]['isColorful'] ); + + $extended_data = $extended_url_metric->jsonSerialize(); + $this->assertArrayHasKey( 'isColorful', $extended_data['elements'][0] ); + $this->assertFalse( $extended_data['elements'][0]['isColorful'] ); + $this->assertFalse( $extended_url_metric->get_elements()[0]['isColorful'] ); + }, + ), + + 'added_valid_element_property_not_populated' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['isColorful'] = array( + 'type' => 'boolean', + ); + return $properties; + } + ); + }, + 'data' => array( + 'url' => home_url( '/' ), + 'viewport' => $viewport, + 'timestamp' => microtime( true ), + 'elements' => array( $valid_element ), + ), + 'assert' => function ( OD_URL_Metric $extended_url_metric, OD_URL_Metric $original_url_metric ): void { + $this->assertSame( $original_url_metric->get_viewport(), $extended_url_metric->get_viewport() ); + + $original_data = $original_url_metric->jsonSerialize(); + $this->assertArrayNotHasKey( 'isColorful', $original_data['elements'][0] ); + + $extended_data = $extended_url_metric->jsonSerialize(); + $this->assertArrayNotHasKey( 'isColorful', $extended_data['elements'][0] ); // If rest_sanitize_value_from_schema() took default into account (and we allowed defaults), this could be different. + }, + ), + + 'added_invalid_element_property' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( array $properties ): array { + $properties['isColorful'] = array( + 'type' => 'boolean', + ); + return $properties; + } + ); + }, + 'data' => array( + 'url' => home_url( '/' ), + 'viewport' => $viewport, + 'timestamp' => microtime( true ), + 'elements' => array( + array_merge( + $valid_element, + array( 'isColorful' => array( 'cannot be cast to boolean' ) ) + ), + ), + ), + 'assert' => static function (): void {}, + 'error' => 'OD_URL_Metric[elements][0][isColorful] is not of type boolean.', + ), + ); + } + + /** + * Tests construction with extended schema. + * + * @covers ::get_json_schema + * + * @dataProvider data_provider_to_test_constructor_with_extended_schema + * + * @param Closure $set_up Set up to extend schema. + * @param array $data Data. + * @param Closure $assert Assert. + * @param string $error Error. + */ + public function test_constructor_with_extended_schema( Closure $set_up, array $data, Closure $assert, string $error = '' ): void { + if ( '' !== $error ) { + $this->expectException( OD_Data_Validation_Exception::class ); + $this->expectExceptionMessage( $error ); + } + $url_metric_sans_extended_schema = new OD_URL_Metric( $data ); + $set_up(); + $url_metric_with_extended_schema = new OD_URL_Metric( $data ); + $assert( $url_metric_with_extended_schema, $url_metric_sans_extended_schema ); + } + /** * Tests get_json_schema(). * @@ -215,7 +485,213 @@ public function test_constructor( array $data, string $error = '' ): void { */ public function test_get_json_schema(): void { $schema = OD_URL_Metric::get_json_schema(); - $this->check_schema_subset( $schema, 'root' ); + $this->check_schema_subset( $schema, 'root', false ); + } + + /** + * Data provider. + * + * @return array + */ + public function data_provider_to_test_get_json_schema_extensibility(): array { + return array( + 'missing_type' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( $additional_properties ) { + $additional_properties['foo'] = array( + 'missing', + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $this->assertSame( $original_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => 'Filter: 'od_url_metric_schema_root_additional_properties'', + ), + + 'bad_type' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( $additional_properties ) { + $additional_properties['foo'] = array( + 'type' => 123, + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $this->assertSame( $original_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => 'Filter: 'od_url_metric_schema_root_additional_properties'', + ), + + 'bad_required' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( $additional_properties ) { + $additional_properties['foo'] = array( + 'type' => 'string', + 'required' => true, + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $expected_schema = $original_schema; + $expected_schema['properties']['foo'] = array( + 'type' => 'string', + 'required' => false, + ); + $this->assertSame( $expected_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => 'Filter: 'od_url_metric_schema_root_additional_properties'', + ), + + 'bad_default' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( $additional_properties ) { + $additional_properties['foo'] = array( + 'type' => 'string', + 'default' => 'bar', + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $expected_schema = $original_schema; + $expected_schema['properties']['foo'] = array( + 'type' => 'string', + 'required' => false, + ); + $this->assertSame( $expected_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => 'Filter: 'od_url_metric_schema_root_additional_properties'', + ), + + 'bad_existing_root_property' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( $additional_properties ) { + $additional_properties['uuid'] = array( + 'type' => 'number', + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $this->assertSame( $original_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => 'Filter: 'od_url_metric_schema_root_additional_properties'', + ), + + 'bad_existing_element_property' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( $additional_properties ) { + $additional_properties['intersectionRatio'] = array( + 'type' => 'string', + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $this->assertSame( $original_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => 'Filter: 'od_url_metric_schema_root_additional_properties'', + ), + + 'adding_root_string' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( $additional_properties ) { + $additional_properties['foo'] = array( + 'type' => 'string', + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $expected_schema = $original_schema; + $expected_schema['properties']['foo'] = array( + 'type' => 'string', + 'required' => false, + ); + $this->assertSame( $expected_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => null, + ), + + 'adding_root_and_element_string' => array( + 'set_up' => static function (): void { + add_filter( + 'od_url_metric_schema_root_additional_properties', + static function ( $additional_properties ) { + $additional_properties['foo'] = array( + 'type' => 'string', + ); + return $additional_properties; + } + ); + add_filter( + 'od_url_metric_schema_element_item_additional_properties', + static function ( $additional_properties ) { + $additional_properties['bar'] = array( + 'type' => 'number', + ); + return $additional_properties; + } + ); + }, + 'assert' => function ( array $original_schema, $extended_schema ): void { + $expected_schema = $original_schema; + $expected_schema['properties']['foo'] = array( + 'type' => 'string', + 'required' => false, + ); + $expected_schema['properties']['elements']['items']['properties']['bar'] = array( + 'type' => 'number', + 'required' => false, + ); + $this->assertSame( $expected_schema, $extended_schema ); + }, + 'expected_incorrect_usage' => null, + ), + ); + } + + /** + * Tests get_json_schema() extensibility. + * + * @dataProvider data_provider_to_test_get_json_schema_extensibility + * + * @covers ::get_json_schema + */ + public function test_get_json_schema_extensibility( Closure $set_up, Closure $assert, ?string $expected_incorrect_usage ): void { + if ( null !== $expected_incorrect_usage ) { + $this->setExpectedIncorrectUsage( $expected_incorrect_usage ); + } + $original_schema = OD_URL_Metric::get_json_schema(); + $set_up(); + $extended_schema = OD_URL_Metric::get_json_schema(); + $this->check_schema_subset( $extended_schema, 'root', true ); + $assert( $original_schema, $extended_schema ); } /** @@ -223,20 +699,26 @@ public function test_get_json_schema(): void { * * @param array $schema Schema subset. */ - protected function check_schema_subset( array $schema, string $path ): void { + protected function check_schema_subset( array $schema, string $path, bool $extended = false ): void { $this->assertArrayHasKey( 'required', $schema, $path ); - $this->assertTrue( $schema['required'], $path ); + if ( ! $extended ) { + $this->assertTrue( $schema['required'], $path ); + } $this->assertArrayHasKey( 'type', $schema, $path ); if ( 'object' === $schema['type'] ) { $this->assertArrayHasKey( 'properties', $schema, $path ); $this->assertArrayHasKey( 'additionalProperties', $schema, $path ); - $this->assertFalse( $schema['additionalProperties'] ); + if ( 'root/viewport' === $path || 'root/elements/items/intersectionRect' === $path || 'root/elements/items/boundingClientRect' === $path ) { + $this->assertFalse( $schema['additionalProperties'], "Path: $path" ); + } else { + $this->assertTrue( $schema['additionalProperties'], "Path: $path" ); + } foreach ( $schema['properties'] as $key => $property_schema ) { - $this->check_schema_subset( $property_schema, "$path/$key" ); + $this->check_schema_subset( $property_schema, "$path/$key", $extended ); } } elseif ( 'array' === $schema['type'] ) { $this->assertArrayHasKey( 'items', $schema, $path ); - $this->check_schema_subset( $schema['items'], "$path/items" ); + $this->check_schema_subset( $schema['items'], "$path/items", $extended ); } } } diff --git a/plugins/performance-lab/load.php b/plugins/performance-lab/load.php index 8afe83c0ba..ba5a09dbaf 100644 --- a/plugins/performance-lab/load.php +++ b/plugins/performance-lab/load.php @@ -5,7 +5,7 @@ * Description: Performance plugin from the WordPress Performance Team, which is a collection of standalone performance features. * Requires at least: 6.5 * Requires PHP: 7.2 - * Version: 3.4.0 + * Version: 3.4.1 * Author: WordPress Performance Team * Author URI: https://make.wordpress.org/performance/ * License: GPLv2 or later @@ -19,7 +19,7 @@ exit; // Exit if accessed directly. } -define( 'PERFLAB_VERSION', '3.4.0' ); +define( 'PERFLAB_VERSION', '3.4.1' ); define( 'PERFLAB_MAIN_FILE', __FILE__ ); define( 'PERFLAB_PLUGIN_DIR_PATH', plugin_dir_path( PERFLAB_MAIN_FILE ) ); define( 'PERFLAB_SCREEN', 'performance-lab' ); diff --git a/plugins/performance-lab/readme.txt b/plugins/performance-lab/readme.txt index 3d8f4d1f49..f74e3e808e 100644 --- a/plugins/performance-lab/readme.txt +++ b/plugins/performance-lab/readme.txt @@ -2,7 +2,7 @@ Contributors: wordpressdotorg Tested up to: 6.6 -Stable tag: 3.4.0 +Stable tag: 3.4.1 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, site health, measurement, optimization, diagnostics @@ -70,6 +70,12 @@ Contributions are always welcome! Learn more about how to get involved in the [C == Changelog == += 3.4.1 = + +**Bug Fixes** + +* Fix Incorrect use of _n(). ([1491](https://github.com/WordPress/performance/pull/1491)) + = 3.4.0 = **Enhancements** diff --git a/plugins/webp-uploads/helper.php b/plugins/webp-uploads/helper.php index 4628d69e21..7669154b64 100644 --- a/plugins/webp-uploads/helper.php +++ b/plugins/webp-uploads/helper.php @@ -17,6 +17,7 @@ * * @since 1.0.0 * @since 2.0.0 Added support for AVIF. + * @since 2.2.0 Added support for PNG. * * @return array> An array of valid mime types, where the key is the mime type and the value is the extension type. */ @@ -29,12 +30,14 @@ function webp_uploads_get_upload_image_mime_transforms(): array { 'image/jpeg' => array( 'image/' . $output_format ), 'image/webp' => array( 'image/webp' ), 'image/avif' => array( 'image/avif' ), + 'image/png' => array( 'image/' . $output_format ), ); // Check setting for whether to generate both JPEG and the modern output format. - if ( webp_uploads_is_jpeg_fallback_enabled() ) { + if ( webp_uploads_is_fallback_enabled() ) { $default_transforms = array( 'image/jpeg' => array( 'image/jpeg', 'image/' . $output_format ), + 'image/png' => array( 'image/png', 'image/' . $output_format ), 'image/' . $output_format => array( 'image/' . $output_format, 'image/jpeg' ), ); } @@ -393,17 +396,18 @@ function webp_uploads_sanitize_image_format( $image_format ): string { * @return bool True if the option is enabled, false otherwise. */ function webp_uploads_is_picture_element_enabled(): bool { - return webp_uploads_is_jpeg_fallback_enabled() && (bool) get_option( 'webp_uploads_use_picture_element', false ); + return webp_uploads_is_fallback_enabled() && (bool) get_option( 'webp_uploads_use_picture_element', false ); } /** * Checks if the `perflab_generate_webp_and_jpeg` option is enabled. * * @since 2.0.0 + * @since 2.2.0 Renamed to webp_uploads_is_fallback_enabled(). * * @return bool True if the option is enabled, false otherwise. */ -function webp_uploads_is_jpeg_fallback_enabled(): bool { +function webp_uploads_is_fallback_enabled(): bool { return (bool) get_option( 'perflab_generate_webp_and_jpeg' ); } @@ -413,7 +417,7 @@ function webp_uploads_is_jpeg_fallback_enabled(): bool { * This function attempts to locate an alternate image source URL in the * attachment's metadata that matches the provided MIME type. * - * @since n.e.x.t + * @since 2.2.0 * * @param int $attachment_id The ID of the attachment. * @param string $src The original image src url. diff --git a/plugins/webp-uploads/load.php b/plugins/webp-uploads/load.php index a0d83c6425..b8319e61a7 100644 --- a/plugins/webp-uploads/load.php +++ b/plugins/webp-uploads/load.php @@ -5,7 +5,7 @@ * Description: Converts images to more modern formats such as WebP or AVIF during upload. * Requires at least: 6.5 * Requires PHP: 7.2 - * Version: 2.1.0 + * Version: 2.2.0 * Author: WordPress Performance Team * Author URI: https://make.wordpress.org/performance/ * License: GPLv2 or later @@ -25,7 +25,7 @@ return; } -define( 'WEBP_UPLOADS_VERSION', '2.1.0' ); +define( 'WEBP_UPLOADS_VERSION', '2.2.0' ); define( 'WEBP_UPLOADS_MAIN_FILE', plugin_basename( __FILE__ ) ); require_once __DIR__ . '/helper.php'; diff --git a/plugins/webp-uploads/readme.txt b/plugins/webp-uploads/readme.txt index 97ddad2d9a..ebf564de4a 100644 --- a/plugins/webp-uploads/readme.txt +++ b/plugins/webp-uploads/readme.txt @@ -2,7 +2,7 @@ Contributors: wordpressdotorg Tested up to: 6.6 -Stable tag: 2.1.0 +Stable tag: 2.2.0 License: GPLv2 or later License URI: https://www.gnu.org/licenses/gpl-2.0.html Tags: performance, images, webp, avif, modern image formats @@ -13,7 +13,7 @@ Converts images to more modern formats such as WebP or AVIF during upload. This plugin adds WebP and AVIF support for media uploads within the WordPress application. By default, AVIF images will be generated if supported on the hosting server, otherwise WebP will be used as the output format. When both formats are available, the output format can be selected under `Settings > Media`. Modern images will be generated only for new uploads, pre-existing images will only converted to a modern format if images are regenerated. Images can be regenerated with a plugin like [Regenerate Thumbnails](https://wordpress.org/plugins/regenerate-thumbnails/) or via WP-CLI with the `wp media regenerate` [command](https://developer.wordpress.org/cli/commands/media/regenerate/). -By default, only modern image format sub-sizes will be generated for JPEG uploads - only the original uploaded file will still exist as a JPEG image, generated image sizes use be WebP or AVIF files. To change this behavior, there is a checkbox in `Settings > Media` "Also output JPEG" that - when checked - will result in the plugin generating both JPEG and WebP or AVIF images for every sub-size (noting again that this will only affect newly uploaded images, i.e. after making said change). +By default, only modern image format sub-sizes will be generated for JPEG or PNG uploads - only the original uploaded file will still exist as a JPEG/PNG image, generated image sizes will be WebP or AVIF files. To change this behavior, there is a checkbox in `Settings > Media` "Output fallback images" that - when checked - will result in the plugin generating both the original format as well as WebP or AVIF images for every sub-size (noting again that this will only affect newly uploaded images, i.e. after making said change). _This plugin was formerly known as WebP Uploads._ @@ -60,6 +60,16 @@ By default, the Modern Image Formats plugin will only generate WebP versions of == Changelog == += 2.2.0 = + +**Enhancements** + +* Convert uploaded PNG files to AVIF or WebP. ([1421](https://github.com/WordPress/performance/pull/1421)) + +**Bug Fixes** + +* Account for responsive images being disabled when generating a PICTURE element. ([1449](https://github.com/WordPress/performance/pull/1449)) + = 2.1.0 = **Enhancements** diff --git a/plugins/webp-uploads/settings.php b/plugins/webp-uploads/settings.php index 0a31b571b9..c44d6c452a 100644 --- a/plugins/webp-uploads/settings.php +++ b/plugins/webp-uploads/settings.php @@ -86,10 +86,10 @@ function webp_uploads_add_media_settings_fields(): void { return; } - // Add JPEG Output settings field. + // Add fallback image output settings field. add_settings_field( 'perflab_generate_webp_and_jpeg', - __( 'Also output JPEG', 'webp-uploads' ), + __( 'Output fallback images', 'webp-uploads' ), 'webp_uploads_generate_webp_jpeg_setting_callback', 'media', 'perflab_modern_image_format_settings', @@ -145,7 +145,7 @@ function webp_uploads_generate_avif_webp_setting_callback(): void { -

+


@@ -172,9 +172,9 @@ function webp_uploads_generate_webp_jpeg_setting_callback(): void { ?> -

+

> -

+

-

img).', 'webp-uploads' ); ?>

+

img).', 'webp-uploads' ); ?>

> -

+