diff --git a/package-lock.json b/package-lock.json index 0b119fa2cb2..d249b28380d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,6 +73,13 @@ "npm": ">= 8" } }, + "internal/documentation/node_modules/@types/node": { + "version": "25.2.2", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "internal/shrinkwrap-extractor": { "name": "@ui5/shrinkwrap-extractor", "version": "1.0.0", @@ -94,28 +101,28 @@ } }, "internal/shrinkwrap-extractor/node_modules/@npmcli/arborist": { - "version": "9.3.1", + "version": "9.1.7", "license": "ISC", "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", - "@npmcli/fs": "^5.0.0", + "@npmcli/fs": "^4.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/map-workspaces": "^5.0.0", "@npmcli/metavuln-calculator": "^9.0.2", "@npmcli/name-from-folder": "^4.0.0", "@npmcli/node-gyp": "^5.0.0", "@npmcli/package-json": "^7.0.0", - "@npmcli/query": "^5.0.0", + "@npmcli/query": "^4.0.0", "@npmcli/redact": "^4.0.0", "@npmcli/run-script": "^10.0.0", "bin-links": "^6.0.0", "cacache": "^20.0.1", - "common-ancestor-path": "^2.0.0", + "common-ancestor-path": "^1.0.1", "hosted-git-info": "^9.0.0", "json-stringify-nice": "^1.1.4", "lru-cache": "^11.2.1", "minimatch": "^10.0.3", - "nopt": "^9.0.0", + "nopt": "^8.0.0", "npm-install-checks": "^8.0.0", "npm-package-arg": "^13.0.0", "npm-pick-manifest": "^11.0.1", @@ -123,7 +130,7 @@ "pacote": "^21.0.2", "parse-conflict-json": "^5.0.1", "proc-log": "^6.0.0", - "proggy": "^4.0.0", + "proggy": "^3.0.0", "promise-all-reject-late": "^1.0.0", "promise-call-limit": "^3.0.1", "semver": "^7.3.7", @@ -138,44 +145,72 @@ "node": "^20.17.0 || >=22.9.0" } }, - "internal/shrinkwrap-extractor/node_modules/lru-cache": { - "version": "11.2.6", - "license": "BlueOak-1.0.0", + "internal/shrinkwrap-extractor/node_modules/@npmcli/arborist/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", "engines": { - "node": "20 || >=22" + "node": "^20.17.0 || >=22.9.0" } }, - "internal/shrinkwrap-extractor/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", + "internal/shrinkwrap-extractor/node_modules/@npmcli/config": { + "version": "10.4.2", + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.1.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "^20.17.0 || >=22.9.0" + } + }, + "internal/shrinkwrap-extractor/node_modules/@npmcli/fs": { + "version": "4.0.0", + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "internal/shrinkwrap-extractor/node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "internal/shrinkwrap-extractor/node_modules/walk-up-path": { + "version": "4.0.0", + "license": "ISC", + "engines": { + "node": "20 || >=22" } }, "node_modules/@adobe/css-tools": { "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", "license": "MIT" }, "node_modules/@algolia/abtesting": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.15.1.tgz", - "integrity": "sha512-2yuIC48rUuHGhU1U5qJ9kJHaxYpJ0jpDHJVI5ekOxSMYXlH4+HP+pA31G820lsAznfmu2nzDV7n5RO44zIY1zw==", + "version": "1.10.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" @@ -183,8 +218,6 @@ }, "node_modules/@algolia/autocomplete-core": { "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", - "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", "license": "MIT", "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", @@ -193,8 +226,6 @@ }, "node_modules/@algolia/autocomplete-plugin-algolia-insights": { "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", - "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.17.7" @@ -205,8 +236,6 @@ }, "node_modules/@algolia/autocomplete-preset-algolia": { "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", - "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", "license": "MIT", "dependencies": { "@algolia/autocomplete-shared": "1.17.7" @@ -218,8 +247,6 @@ }, "node_modules/@algolia/autocomplete-shared": { "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", - "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", "license": "MIT", "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", @@ -227,180 +254,154 @@ } }, "node_modules/@algolia/client-abtesting": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.49.1.tgz", - "integrity": "sha512-h6M7HzPin+45/l09q0r2dYmocSSt2MMGOOk5c4O5K/bBBlEwf1BKfN6z+iX4b8WXcQQhf7rgQwC52kBZJt/ZZw==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-analytics": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.49.1.tgz", - "integrity": "sha512-048T9/Z8OeLmTk8h76QUqaNFp7Rq2VgS2Zm6Y2tNMYGQ1uNuzePY/udB5l5krlXll7ZGflyCjFvRiOtlPZpE9g==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-common": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.49.1.tgz", - "integrity": "sha512-vp5/a9ikqvf3mn9QvHN8PRekn8hW34aV9eX+O0J5mKPZXeA6Pd5OQEh2ZWf7gJY6yyfTlLp5LMFzQUAU+Fpqpg==", + "version": "5.44.0", "license": "MIT", "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-insights": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.49.1.tgz", - "integrity": "sha512-B6N7PgkvYrul3bntTz/l6uXnhQ2bvP+M7NqTcayh681tSqPaA5cJCUBp/vrP7vpPRpej4Eeyx2qz5p0tE/2N2g==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-personalization": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.49.1.tgz", - "integrity": "sha512-v+4DN+lkYfBd01Hbnb9ZrCHe7l+mvihyx218INRX/kaCXROIWUDIT1cs3urQxfE7kXBFnLsqYeOflQALv/gA5w==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-query-suggestions": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.49.1.tgz", - "integrity": "sha512-Un11cab6ZCv0W+Jiak8UktGIqoa4+gSNgEZNfG8m8eTsXGqwIEr370H3Rqwj87zeNSlFpH2BslMXJ/cLNS1qtg==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/client-search": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.49.1.tgz", - "integrity": "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/ingestion": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.49.1.tgz", - "integrity": "sha512-b5hUXwDqje0Y4CpU6VL481DXgPgxpTD5sYMnfQTHKgUispGnaCLCm2/T9WbJo1YNUbX3iHtYDArp804eD6CmRQ==", + "version": "1.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/monitoring": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.49.1.tgz", - "integrity": "sha512-bvrXwZ0WsL3rN6Q4m4QqxsXFCo6WAew7sAdrpMQMK4Efn4/W920r9ptOuckejOSSvyLr9pAWgC5rsHhR2FYuYw==", + "version": "1.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/recommend": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.49.1.tgz", - "integrity": "sha512-h2yz3AGeGkQwNgbLmoe3bxYs8fac4An1CprKTypYyTU/k3Q+9FbIvJ8aS1DoBKaTjSRZVoyQS7SZQio6GaHbZw==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "@algolia/client-common": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-browser-xhr": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.49.1.tgz", - "integrity": "sha512-2UPyRuUR/qpqSqH8mxFV5uBZWEpxhGPHLlx9Xf6OVxr79XO2ctzZQAhsmTZ6X22x+N8MBWpB9UEky7YU2HGFgA==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1" + "@algolia/client-common": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-fetch": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.49.1.tgz", - "integrity": "sha512-N+xlE4lN+wpuT+4vhNEwPVlrfN+DWAZmSX9SYhbz986Oq8AMsqdntOqUyiOXVxYsQtfLwmiej24vbvJGYv1Qtw==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1" + "@algolia/client-common": "5.44.0" }, "engines": { "node": ">= 14.0.0" } }, "node_modules/@algolia/requester-node-http": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.49.1.tgz", - "integrity": "sha512-zA5bkUOB5PPtTr182DJmajCiizHp0rCJQ0Chf96zNFvkdESKYlDeYA3tQ7r2oyHbu/8DiohAQ5PZ85edctzbXA==", + "version": "5.44.0", "license": "MIT", "dependencies": { - "@algolia/client-common": "5.49.1" + "@algolia/client-common": "5.44.0" }, "engines": { "node": ">= 14.0.0" @@ -408,8 +409,6 @@ }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", - "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "license": "MIT", "engines": { "node": ">=10" @@ -420,8 +419,6 @@ }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "14.2.1", - "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.2.1.tgz", - "integrity": "sha512-HmdFw9CDYqM6B25pqGBpNeLCKvGPlIx1EbLrVL0zPvj50CJQUHyBNBw45Muk0kEIkogo1VZvOKHajdMuAzSxRg==", "dev": true, "license": "MIT", "dependencies": { @@ -438,12 +435,10 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.27.1", "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -452,9 +447,7 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "version": "7.28.5", "dev": true, "license": "MIT", "engines": { @@ -462,21 +455,19 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.28.5", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helpers": "^7.28.4", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -492,10 +483,29 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/core/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -503,14 +513,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.28.5", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -521,8 +529,6 @@ }, "node_modules/@babel/helper-annotate-as-pure": { "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { @@ -533,13 +539,11 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.27.2", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", + "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -549,29 +553,38 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "dev": true, + "license": "ISC" + }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz", - "integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==", + "version": "7.28.5", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.28.6", + "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.6", + "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "engines": { @@ -583,8 +596,6 @@ }, "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -593,8 +604,6 @@ }, "node_modules/@babel/helper-globals": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, "license": "MIT", "engines": { @@ -603,8 +612,6 @@ }, "node_modules/@babel/helper-member-expression-to-functions": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { @@ -616,29 +623,25 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.27.1", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.28.3", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.28.3" }, "engines": { "node": ">=6.9.0" @@ -649,8 +652,6 @@ }, "node_modules/@babel/helper-optimise-call-expression": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", "dev": true, "license": "MIT", "dependencies": { @@ -661,9 +662,7 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "version": "7.27.1", "dev": true, "license": "MIT", "engines": { @@ -671,15 +670,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", - "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", + "version": "7.27.1", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.28.5", + "@babel/helper-member-expression-to-functions": "^7.27.1", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/traverse": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -690,8 +687,6 @@ }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", "dev": true, "license": "MIT", "dependencies": { @@ -704,8 +699,6 @@ }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -713,8 +706,6 @@ }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -722,8 +713,6 @@ }, "node_modules/@babel/helper-validator-option": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -731,26 +720,22 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.28.4", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.4" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.28.5", "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -760,13 +745,11 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", - "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", + "version": "7.27.1", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -776,13 +759,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", - "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", + "version": "7.27.1", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -792,13 +773,11 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", - "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", + "version": "7.27.1", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -808,14 +787,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", - "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", + "version": "7.27.1", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6" + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -825,17 +802,15 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", - "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", + "version": "7.28.5", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-create-class-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.28.6" + "@babel/plugin-syntax-typescript": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -846,8 +821,6 @@ }, "node_modules/@babel/preset-typescript": { "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, "license": "MIT", "dependencies": { @@ -865,43 +838,58 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.27.2", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.28.5", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.28.5", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.28.5", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -913,23 +901,19 @@ }, "node_modules/@blueoak/list": { "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@blueoak/list/-/list-15.0.0.tgz", - "integrity": "sha512-xW5Xb9Fr3WtYAOwavxxWL0CaJK/ReT+HKb5/R6dR1p9RVJ55MTdaxPdeTKY2ukhFchv2YHPMM8YuZyfyLqxedg==", "dev": true, "license": "CC0-1.0" }, "node_modules/@commitlint/cli": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-20.4.3.tgz", - "integrity": "sha512-Z37EMoDT7+Upg500vlr/vZrgRsb6Xc5JAA3Tv7BYbobnN/ZpqUeZnSLggBg2+1O+NptRDtyujr2DD1CPV2qwhA==", + "version": "20.1.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/format": "^20.4.3", - "@commitlint/lint": "^20.4.3", - "@commitlint/load": "^20.4.3", - "@commitlint/read": "^20.4.3", - "@commitlint/types": "^20.4.3", + "@commitlint/format": "^20.0.0", + "@commitlint/lint": "^20.0.0", + "@commitlint/load": "^20.1.0", + "@commitlint/read": "^20.0.0", + "@commitlint/types": "^20.0.0", "tinyexec": "^1.0.0", "yargs": "^17.0.0" }, @@ -941,41 +925,55 @@ } }, "node_modules/@commitlint/config-conventional": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/config-conventional/-/config-conventional-20.4.3.tgz", - "integrity": "sha512-9RtLySbYQAs8yEqWEqhSZo9nYhbm57jx7qHXtgRmv/nmeQIjjMcwf6Dl+y5UZcGWgWx435TAYBURONaJIuCjWg==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", - "conventional-changelog-conventionalcommits": "^9.2.0" + "@commitlint/types": "^20.0.0", + "conventional-changelog-conventionalcommits": "^7.0.2" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/config-validator": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/config-validator/-/config-validator-20.4.3.tgz", - "integrity": "sha512-jCZpZFkcSL3ZEdL5zgUzFRdytv3xPo8iukTe9VA+QGus/BGhpp1xXSVu2B006GLLb2gYUAEGEqv64kTlpZNgmA==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.0.0", "ajv": "^8.11.0" }, "engines": { "node": ">=v18" } }, + "node_modules/@commitlint/config-validator/node_modules/ajv": { + "version": "8.17.1", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@commitlint/config-validator/node_modules/json-schema-traverse": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/@commitlint/ensure": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/ensure/-/ensure-20.4.3.tgz", - "integrity": "sha512-WcXGKBNn0wBKpX8VlXgxqedyrLxedIlLBCMvdamLnJFEbUGJ9JZmBVx4vhLV3ZyA8uONGOb+CzW0Y9HDbQ+ONQ==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.0.0", "lodash.camelcase": "^4.3.0", "lodash.kebabcase": "^4.1.1", "lodash.snakecase": "^4.1.1", @@ -988,8 +986,6 @@ }, "node_modules/@commitlint/execute-rule": { "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/execute-rule/-/execute-rule-20.0.0.tgz", - "integrity": "sha512-xyCoOShoPuPL44gVa+5EdZsBVao/pNzpQhkzq3RdtlFdKZtjWcLlUFQHSWBuhk5utKYykeJPSz2i8ABHQA+ZZw==", "dev": true, "license": "MIT", "engines": { @@ -997,27 +993,23 @@ } }, "node_modules/@commitlint/format": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/format/-/format-20.4.3.tgz", - "integrity": "sha512-UDJVErjLbNghop6j111rsHJYGw6MjCKAi95K0GT2yf4eeiDHy3JDRLWYWEjIaFgO+r+dQSkuqgJ1CdMTtrvHsA==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", - "picocolors": "^1.1.1" + "@commitlint/types": "^20.0.0", + "chalk": "^5.3.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/is-ignored": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-20.4.3.tgz", - "integrity": "sha512-W5VQKZ7fdJ1X3Tko+h87YZaqRMGN1KvQKXyCM8xFdxzMIf1KCZgN4uLz3osLB1zsFcVS4ZswHY64LI26/9ACag==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", + "@commitlint/types": "^20.0.0", "semver": "^7.6.0" }, "engines": { @@ -1025,46 +1017,41 @@ } }, "node_modules/@commitlint/lint": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/lint/-/lint-20.4.3.tgz", - "integrity": "sha512-CYOXL23e+nRKij81+d0+dymtIi7Owl9QzvblJYbEfInON/4MaETNSLFDI74LDu+YJ0ML5HZyw9Vhp9QpckwQ0A==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/is-ignored": "^20.4.3", - "@commitlint/parse": "^20.4.3", - "@commitlint/rules": "^20.4.3", - "@commitlint/types": "^20.4.3" + "@commitlint/is-ignored": "^20.0.0", + "@commitlint/parse": "^20.0.0", + "@commitlint/rules": "^20.0.0", + "@commitlint/types": "^20.0.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/load": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/load/-/load-20.4.3.tgz", - "integrity": "sha512-3cdJOUVP+VcgHa7bhJoWS+Z8mBNXB5aLWMBu7Q7uX8PSeWDzdbrBlR33J1MGGf7r1PZDp+mPPiFktk031PgdRw==", + "version": "20.1.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.4.3", + "@commitlint/config-validator": "^20.0.0", "@commitlint/execute-rule": "^20.0.0", - "@commitlint/resolve-extends": "^20.4.3", - "@commitlint/types": "^20.4.3", - "cosmiconfig": "^9.0.1", + "@commitlint/resolve-extends": "^20.1.0", + "@commitlint/types": "^20.0.0", + "chalk": "^5.3.0", + "cosmiconfig": "^9.0.0", "cosmiconfig-typescript-loader": "^6.1.0", - "is-plain-obj": "^4.1.0", - "lodash.mergewith": "^4.6.2", - "picocolors": "^1.1.1" + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "lodash.uniq": "^4.5.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/message": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-20.4.3.tgz", - "integrity": "sha512-6akwCYrzcrFcTYz9GyUaWlhisY4lmQ3KvrnabmhoeAV8nRH4dXJAh4+EUQ3uArtxxKQkvxJS78hNX2EU3USgxQ==", + "version": "20.0.0", "dev": true, "license": "MIT", "engines": { @@ -1072,29 +1059,25 @@ } }, "node_modules/@commitlint/parse": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/parse/-/parse-20.4.3.tgz", - "integrity": "sha512-hzC3JCo3zs3VkQ833KnGVuWjWIzR72BWZWjQM7tY/7dfKreKAm7fEsy71tIFCRtxf2RtMP2d3RLF1U9yhFSccA==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/types": "^20.4.3", - "conventional-changelog-angular": "^8.2.0", - "conventional-commits-parser": "^6.3.0" + "@commitlint/types": "^20.0.0", + "conventional-changelog-angular": "^7.0.0", + "conventional-commits-parser": "^5.0.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/read": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/read/-/read-20.4.3.tgz", - "integrity": "sha512-j42OWv3L31WfnP8WquVjHZRt03w50Y/gEE8FAyih7GQTrIv2+pZ6VZ6pWLD/ml/3PO+RV2SPtRtTp/MvlTb8rQ==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/top-level": "^20.4.3", - "@commitlint/types": "^20.4.3", + "@commitlint/top-level": "^20.0.0", + "@commitlint/types": "^20.0.0", "git-raw-commits": "^4.0.0", "minimist": "^1.2.8", "tinyexec": "^1.0.0" @@ -1104,14 +1087,12 @@ } }, "node_modules/@commitlint/resolve-extends": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/resolve-extends/-/resolve-extends-20.4.3.tgz", - "integrity": "sha512-QucxcOy+00FhS9s4Uy0OyS5HeUV+hbC6OLqkTSIm6fwMdKva+OEavaCDuLtgd9akZZlsUo//XzSmPP3sLKBPog==", + "version": "20.1.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/config-validator": "^20.4.3", - "@commitlint/types": "^20.4.3", + "@commitlint/config-validator": "^20.0.0", + "@commitlint/types": "^20.0.0", "global-directory": "^4.0.1", "import-meta-resolve": "^4.0.0", "lodash.mergewith": "^4.6.2", @@ -1122,16 +1103,14 @@ } }, "node_modules/@commitlint/rules": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/rules/-/rules-20.4.3.tgz", - "integrity": "sha512-Yuosd7Grn5qiT7FovngXLyRXTMUbj9PYiSkvUgWK1B5a7+ZvrbWDS7epeUapYNYatCy/KTpPFPbgLUdE+MUrBg==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "@commitlint/ensure": "^20.4.3", - "@commitlint/message": "^20.4.3", + "@commitlint/ensure": "^20.0.0", + "@commitlint/message": "^20.0.0", "@commitlint/to-lines": "^20.0.0", - "@commitlint/types": "^20.4.3" + "@commitlint/types": "^20.0.0" }, "engines": { "node": ">=v18" @@ -1139,8 +1118,6 @@ }, "node_modules/@commitlint/to-lines": { "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@commitlint/to-lines/-/to-lines-20.0.0.tgz", - "integrity": "sha512-2l9gmwiCRqZNWgV+pX1X7z4yP0b3ex/86UmUFgoRt672Ez6cAM2lOQeHFRUTuE6sPpi8XBCGnd8Kh3bMoyHwJw==", "dev": true, "license": "MIT", "engines": { @@ -1148,27 +1125,23 @@ } }, "node_modules/@commitlint/top-level": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/top-level/-/top-level-20.4.3.tgz", - "integrity": "sha512-qD9xfP6dFg5jQ3NMrOhG0/w5y3bBUsVGyJvXxdWEwBm8hyx4WOk3kKXw28T5czBYvyeCVJgJJ6aoJZUWDpaacQ==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "escalade": "^3.2.0" + "find-up": "^7.0.0" }, "engines": { "node": ">=v18" } }, "node_modules/@commitlint/types": { - "version": "20.4.3", - "resolved": "https://registry.npmjs.org/@commitlint/types/-/types-20.4.3.tgz", - "integrity": "sha512-51OWa1Gi6ODOasPmfJPq6js4pZoomima4XLZZCrkldaH2V5Nb3bVhNXPeT6XV0gubbainSpTw4zi68NqAeCNCg==", + "version": "20.0.0", "dev": true, "license": "MIT", "dependencies": { - "conventional-commits-parser": "^6.3.0", - "picocolors": "^1.1.1" + "@types/conventional-commits-parser": "^5.0.0", + "chalk": "^5.3.0" }, "engines": { "node": ">=v18" @@ -1176,14 +1149,10 @@ }, "node_modules/@docsearch/css": { "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", - "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", "license": "MIT" }, "node_modules/@docsearch/js": { "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", - "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", "license": "MIT", "dependencies": { "@docsearch/react": "3.8.2", @@ -1192,8 +1161,6 @@ }, "node_modules/@docsearch/react": { "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", - "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", "license": "MIT", "dependencies": { "@algolia/autocomplete-core": "1.17.7", @@ -1258,8 +1225,6 @@ }, "node_modules/@es-joy/jsdoccomment": { "version": "0.84.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.84.0.tgz", - "integrity": "sha512-0xew1CxOam0gV5OMjh2KjFQZsKL2bByX1+q4j3E73MpYIdyUxcZb/xQct9ccUb+ve5KGUYbCUxyPnYB7RbuP+w==", "dev": true, "license": "MIT", "dependencies": { @@ -1273,619 +1238,273 @@ "node": "^20.19.0 || ^22.13.0 || >=24" } }, + "node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": { + "version": "8.54.0", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@es-joy/resolve.exports": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", - "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", "dev": true, "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/@esbuild/aix-ppc64": { + "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ - "ppc64" + "arm64" ], "license": "MIT", "optional": true, "os": [ - "aix" + "darwin" ], "engines": { "node": ">=12" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.0", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], "engines": { - "node": ">=12" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/config-array/node_modules/debug": { + "version": "4.4.3", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/@eslint/config-array/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@eslint/core": { + "version": "0.17.0", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/eslintrc": { + "version": "3.3.1", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], + "node_modules/@eslint/eslintrc/node_modules/debug": { + "version": "4.4.3", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=12" + "node": "*" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], + "node_modules/@eslint/eslintrc/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/js": { + "version": "9.39.1", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "node_modules/@humanfs/core": { + "version": "0.19.1", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=18.18.0" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "node_modules/@humanfs/node": { + "version": "0.16.7", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, "engines": { - "node": ">=12" + "node": ">=18.18.0" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.4.tgz", - "integrity": "sha512-4h4MVF8pmBsncB60r0wSJiIeUKTSD4m7FmTFThG8RHlsg9ajqckLm9OraguFGZE4vVdpiI1Q4+hFnisopmG6gQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.3", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/js": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.3.tgz", - "integrity": "sha512-1B1VkCq6FuUNlQvlBYb+1jDu/gV297TIs/OeiaSR9l1H27SVW55ONE1e1Vp16NqP683+xEGzxYtv4XCiDPaQiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@gar/promise-retry": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.2.tgz", - "integrity": "sha512-Lm/ZLhDZcBECta3TmCQSngiQykFdfw+QtI1/GYMsZd4l3nG+P8WLB16XuS7WaBGLQ+9E+cOcWQsth9cayuGt8g==", - "license": "MIT", - "dependencies": { - "retry": "^0.13.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1897,9 +1516,7 @@ } }, "node_modules/@iconify-json/simple-icons": { - "version": "1.2.72", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.72.tgz", - "integrity": "sha512-wkcixntHvaCoqPqerGrNFcHQ3Yx1ux4ZkhscCDK0DEHpP62XCH+cxq1HTsRjbUiQl/M9K8bj03HF6Wgn5iE2rQ==", + "version": "1.2.60", "license": "CC0-1.0", "dependencies": { "@iconify/types": "*" @@ -1907,14 +1524,27 @@ }, "node_modules/@iconify/types": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", "license": "MIT" }, + "node_modules/@isaacs/balanced-match": { + "version": "4.0.1", + "license": "MIT", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@isaacs/brace-expansion": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "@isaacs/balanced-match": "^4.0.1" + }, + "engines": { + "node": "20 || >=22" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "license": "ISC", "dependencies": { "string-width": "^5.1.2", @@ -1930,14 +1560,10 @@ }, "node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "license": "MIT" }, "node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", @@ -1953,8 +1579,6 @@ }, "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", @@ -1970,8 +1594,6 @@ }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "license": "ISC", "dependencies": { "minipass": "^7.0.4" @@ -1982,14 +1604,10 @@ }, "node_modules/@isaacs/string-locale-compare": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", - "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", "license": "ISC" }, "node_modules/@istanbuljs/esm-loader-hook": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/esm-loader-hook/-/esm-loader-hook-0.3.0.tgz", - "integrity": "sha512-lEnYroBUYfNQuJDYrPvre8TSwPZnyIQv9qUT3gACvhr3igZr+BbrdyIcz4+2RnEXZzi12GqkUW600+QQPpIbVg==", "dev": true, "license": "ISC", "dependencies": { @@ -2007,8 +1625,6 @@ }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2024,18 +1640,22 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -2048,8 +1668,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -2062,8 +1680,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -2075,8 +1691,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -2091,8 +1705,6 @@ }, "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -2102,10 +1714,16 @@ "node": ">=8" } }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@istanbuljs/schema": { "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true, "license": "MIT", "engines": { @@ -2114,8 +1732,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -2124,8 +1740,6 @@ }, "node_modules/@jridgewell/remapping": { "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2135,8 +1749,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", "engines": { "node": ">=6.0.0" @@ -2144,8 +1756,6 @@ }, "node_modules/@jridgewell/source-map": { "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -2154,14 +1764,10 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2169,21 +1775,17 @@ } }, "node_modules/@jsdoc/salty": { - "version": "0.2.10", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.10.tgz", - "integrity": "sha512-VFHSsQAQp8y1NJvAJBpLs9I2shHE6hz9TwukocDObuUgGVAq62yZGbTgJg04Z3Fj0XSMWe0sJqGg5dhKGTV92A==", + "version": "0.2.9", "license": "Apache-2.0", "dependencies": { - "lodash": "^4.17.23" + "lodash": "^4.17.21" }, "engines": { "node": ">=v12.0.0" } }, "node_modules/@mapbox/node-pre-gyp": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", - "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", + "version": "2.0.0", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -2202,32 +1804,6 @@ "node": ">=18" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, - "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", - "dev": true, - "license": "ISC", - "dependencies": { - "abbrev": "^3.0.0" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": "^18.17.0 || >=20.5.0" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz", @@ -2247,8 +1823,6 @@ }, "node_modules/@noble/hashes": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { @@ -2260,8 +1834,6 @@ }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -2273,8 +1845,6 @@ }, "node_modules/@nodelib/fs.stat": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "license": "MIT", "engines": { "node": ">= 8" @@ -2282,8 +1852,6 @@ }, "node_modules/@nodelib/fs.walk": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -2295,8 +1863,6 @@ }, "node_modules/@npmcli/agent": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz", - "integrity": "sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA==", "license": "ISC", "dependencies": { "agent-base": "^7.1.0", @@ -2309,19 +1875,38 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@npmcli/agent/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", + "node_modules/@npmcli/agent/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": "20 || >=22" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@npmcli/agent/node_modules/http-proxy-agent": { + "version": "7.0.2", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" } }, + "node_modules/@npmcli/agent/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, "node_modules/@npmcli/arborist": { "version": "7.5.4", - "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-7.5.4.tgz", - "integrity": "sha512-nWtIc6QwwoUORCRNzKx4ypHqCk3drI+5aeYdMTQQiRCcn4lOOgfQh7WyZobGYTxXPSq1VwV53lkpN/BRlRk08g==", "dev": true, "license": "ISC", "dependencies": { @@ -2370,8 +1955,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/agent": { "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/agent/-/agent-2.2.2.tgz", - "integrity": "sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==", "dev": true, "license": "ISC", "dependencies": { @@ -2387,8 +1970,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/fs": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-3.1.1.tgz", - "integrity": "sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==", "dev": true, "license": "ISC", "dependencies": { @@ -2400,8 +1981,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/git": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-5.0.8.tgz", - "integrity": "sha512-liASfw5cqhjNW9UFd+ruwwdEf/lbOAQjLL2XY2dFW/bkJheXDYZgOyul/4gVvEV4BWkTXjYGmDqMw9uegdbJNQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2421,8 +2000,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/installed-package-contents": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-2.1.0.tgz", - "integrity": "sha512-c8UuGLeZpm69BryRykLuKRyKFZYJsZSCT4aVY5ds4omyZqJ172ApzgfKJ5eV/r3HgLdUYgFVe54KSFVjKoe27w==", "dev": true, "license": "ISC", "dependencies": { @@ -2438,8 +2015,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/map-workspaces": { "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-3.0.6.tgz", - "integrity": "sha512-tkYs0OYnzQm6iIRdfy+LcLBjcKuQCeE5YLb8KnrIlutJfheNaPvPpgoFEyEFgbjzl5PLZ3IA/BWAwRU0eHuQDA==", "dev": true, "license": "ISC", "dependencies": { @@ -2454,8 +2029,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/metavuln-calculator": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-7.1.1.tgz", - "integrity": "sha512-Nkxf96V0lAx3HCpVda7Vw4P23RILgdi/5K1fmj2tZkWIYLpXAN8k2UVVOsW16TsS5F8Ws2I7Cm+PU1/rsVF47g==", "dev": true, "license": "ISC", "dependencies": { @@ -2471,8 +2044,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/name-from-folder": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-2.0.0.tgz", - "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", "dev": true, "license": "ISC", "engines": { @@ -2481,8 +2052,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/node-gyp": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-3.0.0.tgz", - "integrity": "sha512-gp8pRXC2oOxu0DUE1/M3bYtb1b3/DbJ5aM113+XJBgfXdussRAsX0YOrOhdd8WvnAR6auDBvJomGAkLKA5ydxA==", "dev": true, "license": "ISC", "engines": { @@ -2491,8 +2060,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/package-json": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-5.2.1.tgz", - "integrity": "sha512-f7zYC6kQautXHvNbLEWgD/uGu1+xCn9izgqBfgItWSx22U0ZDekxN08A1vM8cTxj/cRVe0Q94Ode+tdoYmIOOQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2510,8 +2077,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/promise-spawn": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-7.0.2.tgz", - "integrity": "sha512-xhfYPXoV5Dy4UkY0D+v2KkwvnDfiA/8Mt3sWCGI/hM03NsYIH8ZaG6QzS9x7pje5vHZBZJ2v6VRFVTWACnqcmQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2523,8 +2088,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/query": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-3.1.0.tgz", - "integrity": "sha512-C/iR0tk7KSKGldibYIB9x8GtO/0Bd0I2mhOaDb8ucQL/bQVTmGoeREaFj64Z5+iCBRf3dQfed0CjJL7I8iTkiQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2536,8 +2099,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/redact": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-2.0.1.tgz", - "integrity": "sha512-YgsR5jCQZhVmTJvjduTOIHph0L73pK8xwMVaDY0PatySqVM9AZj93jpoXYSJqfHFxFkN9dmqTw6OiqExsS3LPw==", "dev": true, "license": "ISC", "engines": { @@ -2546,8 +2107,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@npmcli/run-script": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-8.1.0.tgz", - "integrity": "sha512-y7efHHwghQfk28G2z3tlZ67pLG0XdfYbcVG26r7YIXALRsrVQcTq4/tdenSmdOrEsNahIYA/eh8aEVROWGFUDg==", "dev": true, "license": "ISC", "dependencies": { @@ -2564,8 +2123,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@sigstore/bundle": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", - "integrity": "sha512-wueKWDk70QixNLB363yHc2D2ItTgYiMTdPwK8D9dKQMR3ZQ0c35IxP5xnwQ8cNLoCgCRcHf14kE+CLIvNX1zmA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2577,8 +2134,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@sigstore/core": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-1.1.0.tgz", - "integrity": "sha512-JzBqdVIyqm2FRQCulY6nbQzMpJJpSiJ8XXWMhtOX9eKgaXXpfNOF53lzQEjIydlStnd/eFtuC1dW4VYdD93oRg==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2587,8 +2142,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@sigstore/protobuf-specs": { "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.3.3.tgz", - "integrity": "sha512-RpacQhBlwpBWd7KEJsRKcBQalbV28fvkxwTOJIqhIuDysMMaJW47V4OqW30iJB9uRpqOSxxEAQFdr8tTattReQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -2597,8 +2150,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@sigstore/sign": { "version": "2.3.2", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-2.3.2.tgz", - "integrity": "sha512-5Vz5dPVuunIIvC5vBb0APwo7qKA4G9yM48kPWJT+OEERs40md5GoUR1yedwpekWZ4m0Hhw44m6zU+ObsON+iDA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2615,8 +2166,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@sigstore/tuf": { "version": "2.3.4", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-2.3.4.tgz", - "integrity": "sha512-44vtsveTPUpqhm9NCrbU8CWLe3Vck2HO1PNLw7RIajbB7xhtn5RBPm1VNSCMwqGYHhDsBJG8gDF0q4lgydsJvw==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2629,8 +2178,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@sigstore/verify": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-1.2.1.tgz", - "integrity": "sha512-8iKx79/F73DKbGfRf7+t4dqrc0bRr0thdPrxAtCKWRm/F0tG71i6O1rvlnScncJLLBZHn3h8M3c1BSUAb9yu8g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2644,8 +2191,6 @@ }, "node_modules/@npmcli/arborist/node_modules/@tufjs/models": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-2.0.1.tgz", - "integrity": "sha512-92F7/SFyufn4DXsha9+QfKnN03JGqtMFMXgSHbZOo8JG59WkTni7UzAouNQDf7AuP9OAMxVOPQcqG3sB7w+kkg==", "dev": true, "license": "MIT", "dependencies": { @@ -2658,25 +2203,14 @@ }, "node_modules/@npmcli/arborist/node_modules/abbrev": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/@npmcli/arborist/node_modules/bin-links": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.4.tgz", - "integrity": "sha512-cMtq4W5ZsEwcutJrVId+a/tjt8GSbS+h0oNkdl6+6rBuEv8Ot33Bevj5KPm40t309zuhVic8NjpuL42QCiJWWA==", "dev": true, "license": "ISC", "dependencies": { @@ -2691,8 +2225,6 @@ }, "node_modules/@npmcli/arborist/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2701,8 +2233,6 @@ }, "node_modules/@npmcli/arborist/node_modules/cacache": { "version": "18.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-18.0.4.tgz", - "integrity": "sha512-B+L5iIa9mgcjLbliir2th36yEwPftrzteHYujzsx3dFP/31GCHcIeS8f5MGd80odLOjaOvSpU3EEAmRQptkxLQ==", "dev": true, "license": "ISC", "dependencies": { @@ -2723,88 +2253,32 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/cacache/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", + "node_modules/@npmcli/arborist/node_modules/cmd-shim": { + "version": "6.0.3", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "engines": { - "node": ">=18" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/cacache/node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "node_modules/@npmcli/arborist/node_modules/debug": { + "version": "4.4.3", "dev": true, "license": "MIT", "dependencies": { - "minipass": "^7.1.2" + "ms": "^2.1.3" }, "engines": { - "node": ">= 18" - } - }, - "node_modules/@npmcli/arborist/node_modules/cacache/node_modules/tar": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz", - "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" + "node": ">=6.0" }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@npmcli/arborist/node_modules/cacache/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@npmcli/arborist/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/arborist/node_modules/cmd-shim": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.3.tgz", - "integrity": "sha512-FMabTRlc5t5zjdenF6mS0MBeFZm0XqHqeOkcskKFb/LYCcRQ5fVgLOHVc4Lq9CqABd9zhjwPjMBCJvMCziSVtA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@npmcli/arborist/node_modules/common-ancestor-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", - "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "dev": true, - "license": "ISC" - }, "node_modules/@npmcli/arborist/node_modules/glob": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -2824,8 +2298,6 @@ }, "node_modules/@npmcli/arborist/node_modules/hosted-git-info": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", "dev": true, "license": "ISC", "dependencies": { @@ -2835,10 +2307,20 @@ "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/@npmcli/arborist/node_modules/http-proxy-agent": { + "version": "7.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/@npmcli/arborist/node_modules/ignore-walk": { "version": "6.0.5", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-6.0.5.tgz", - "integrity": "sha512-VuuG0wCnjhnylG1ABXT3dAuIpTNDs/G8jlpmwXY03fXoXy/8ZK8/T+hMzt8L4WnrLCJgdybqgPagnF/f97cg3A==", "dev": true, "license": "ISC", "dependencies": { @@ -2850,28 +2332,14 @@ }, "node_modules/@npmcli/arborist/node_modules/ini": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.3.tgz", - "integrity": "sha512-X7rqawQBvfdjS10YU1y1YVreA3SsLrW9dX2CewP2EbBJM4ypVNLDkO5y04gejPwKIY9lR+7r9gn3rFPt/kmWFg==", "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/isexe": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.5.tgz", - "integrity": "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/@npmcli/arborist/node_modules/json-parse-even-better-errors": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, "license": "MIT", "engines": { @@ -2880,15 +2348,11 @@ }, "node_modules/@npmcli/arborist/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@npmcli/arborist/node_modules/make-fetch-happen": { "version": "13.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-13.0.1.tgz", - "integrity": "sha512-cKTUFc/rbKUd/9meOvgrpJ2WrNzymt6jfRDdwg5UCnVzv9dTpEj9JS5m3wtziXVCjluIXyL8pcaukYqezIzZQA==", "dev": true, "license": "ISC", "dependencies": { @@ -2910,13 +2374,11 @@ } }, "node_modules/@npmcli/arborist/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.5", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -2927,8 +2389,6 @@ }, "node_modules/@npmcli/arborist/node_modules/minipass-fetch": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-3.0.5.tgz", - "integrity": "sha512-2N8elDQAtSnFV0Dk7gt15KHsS0Fyz6CbYZ360h0WTYV1Ty46li3rAXVOQj1THMNLdmrD9Vt5pBPtWtVkpwGBqg==", "dev": true, "license": "MIT", "dependencies": { @@ -2943,36 +2403,8 @@ "encoding": "^0.1.13" } }, - "node_modules/@npmcli/arborist/node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, - "license": "ISC", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@npmcli/arborist/node_modules/minipass-sized/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/@npmcli/arborist/node_modules/minizlib": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "license": "MIT", "dependencies": { @@ -2985,8 +2417,6 @@ }, "node_modules/@npmcli/arborist/node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "license": "ISC", "dependencies": { @@ -2996,20 +2426,13 @@ "node": ">=8" } }, - "node_modules/@npmcli/arborist/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "node_modules/@npmcli/arborist/node_modules/ms": { + "version": "2.1.3", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "license": "MIT" }, "node_modules/@npmcli/arborist/node_modules/node-gyp": { "version": "10.3.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-10.3.1.tgz", - "integrity": "sha512-Pp3nFHBThHzVtNY7U6JfPjvT/DTE8+o/4xKsLQtBoU+j2HLsGlhcfzflAoUreaJbNmYnX+LlLi0qjV8kpyO6xQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3031,60 +2454,8 @@ "node": "^16.14.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/node-gyp/node_modules/chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/@npmcli/arborist/node_modules/node-gyp/node_modules/minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minipass": "^7.1.2" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/@npmcli/arborist/node_modules/node-gyp/node_modules/tar": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz", - "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@npmcli/arborist/node_modules/node-gyp/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/@npmcli/arborist/node_modules/nopt": { "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "license": "ISC", "dependencies": { @@ -3097,25 +2468,8 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, "node_modules/@npmcli/arborist/node_modules/npm-bundled": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-3.0.1.tgz", - "integrity": "sha512-+AvaheE/ww1JEwRHOrn4WHNzOxGtVp+adrg2AeZS/7KuxGUYFuBta98wYpfHBbJp6Tg6j1NKSEVHNcfZzJHQwQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3127,8 +2481,6 @@ }, "node_modules/@npmcli/arborist/node_modules/npm-install-checks": { "version": "6.3.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-6.3.0.tgz", - "integrity": "sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3140,8 +2492,6 @@ }, "node_modules/@npmcli/arborist/node_modules/npm-normalize-package-bin": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "license": "ISC", "engines": { @@ -3150,8 +2500,6 @@ }, "node_modules/@npmcli/arborist/node_modules/npm-package-arg": { "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz", - "integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==", "dev": true, "license": "ISC", "dependencies": { @@ -3166,8 +2514,6 @@ }, "node_modules/@npmcli/arborist/node_modules/npm-packlist": { "version": "8.0.2", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-8.0.2.tgz", - "integrity": "sha512-shYrPFIS/JLP4oQmAwDyk5HcyysKW8/JLTEA32S0Z5TzvpaeeX2yMFfoK1fjEBnCBvVyIB/Jj/GBFdm0wsgzbA==", "dev": true, "license": "ISC", "dependencies": { @@ -3179,8 +2525,6 @@ }, "node_modules/@npmcli/arborist/node_modules/npm-pick-manifest": { "version": "9.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-9.1.0.tgz", - "integrity": "sha512-nkc+3pIIhqHVQr085X9d2JzPzLyjzQS96zbruppqC9aZRm/x8xx6xhI98gHtsfELP2bE+loHq8ZaHFHhe+NauA==", "dev": true, "license": "ISC", "dependencies": { @@ -3195,8 +2539,6 @@ }, "node_modules/@npmcli/arborist/node_modules/npm-registry-fetch": { "version": "17.1.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-17.1.0.tgz", - "integrity": "sha512-5+bKQRH0J1xG1uZ1zMNvxW0VEyoNWgJpY9UDuluPFLKDfJ9u2JmmjmTJV1srBGQOROfdBMiVvnH2Zvpbm+xkVA==", "dev": true, "license": "ISC", "dependencies": { @@ -3215,8 +2557,6 @@ }, "node_modules/@npmcli/arborist/node_modules/p-map": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3231,8 +2571,6 @@ }, "node_modules/@npmcli/arborist/node_modules/pacote": { "version": "18.0.6", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-18.0.6.tgz", - "integrity": "sha512-+eK3G27SMwsB8kLIuj4h1FUhHtwiEUo21Tw8wNjmvdlpOEr613edv+8FUsTj/4F/VN5ywGE19X18N7CC2EJk6A==", "dev": true, "license": "ISC", "dependencies": { @@ -3263,8 +2601,6 @@ }, "node_modules/@npmcli/arborist/node_modules/parse-conflict-json": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-3.0.1.tgz", - "integrity": "sha512-01TvEktc68vwbJOtWZluyWeVGWjP+bZwXtPDMQVbBKzbJ/vZBif0L69KH1+cHv1SZ6e0FKLvjyHe8mqsIqYOmw==", "dev": true, "license": "ISC", "dependencies": { @@ -3278,8 +2614,6 @@ }, "node_modules/@npmcli/arborist/node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -3295,8 +2629,6 @@ }, "node_modules/@npmcli/arborist/node_modules/postcss-selector-parser": { "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, "license": "MIT", "dependencies": { @@ -3309,8 +2641,6 @@ }, "node_modules/@npmcli/arborist/node_modules/proc-log": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz", - "integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==", "dev": true, "license": "ISC", "engines": { @@ -3319,8 +2649,6 @@ }, "node_modules/@npmcli/arborist/node_modules/proggy": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/proggy/-/proggy-2.0.0.tgz", - "integrity": "sha512-69agxLtnI8xBs9gUGqEnK26UfiexpHy+KUpBQWabiytQjnn5wFY8rklAi7GRfABIuPNnQ/ik48+LGLkYYJcy4A==", "dev": true, "license": "ISC", "engines": { @@ -3329,8 +2657,6 @@ }, "node_modules/@npmcli/arborist/node_modules/read-cmd-shim": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", - "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", "dev": true, "license": "ISC", "engines": { @@ -3339,8 +2665,6 @@ }, "node_modules/@npmcli/arborist/node_modules/sigstore": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-2.3.1.tgz", - "integrity": "sha512-8G+/XDU8wNsJOQS5ysDVO0Etg9/2uA5gR9l4ZwijjlwxBcrU6RPfwi2+jJmbP+Ap1Hlp/nVAaEO4Fj22/SL2gQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -3357,8 +2681,6 @@ }, "node_modules/@npmcli/arborist/node_modules/ssri": { "version": "10.0.6", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-10.0.6.tgz", - "integrity": "sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3369,64 +2691,41 @@ } }, "node_modules/@npmcli/arborist/node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/arborist/node_modules/tar/node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "version": "7.5.10", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "minipass": "^3.0.0" + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" }, "engines": { - "node": ">= 8" + "node": ">=18" } }, - "node_modules/@npmcli/arborist/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "node_modules/@npmcli/arborist/node_modules/tar/node_modules/minizlib": { + "version": "3.1.0", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "minipass": "^7.1.2" }, "engines": { - "node": ">=8" + "node": ">= 18" } }, - "node_modules/@npmcli/arborist/node_modules/tar/node_modules/minipass": { + "node_modules/@npmcli/arborist/node_modules/tar/node_modules/yallist": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/@npmcli/arborist/node_modules/tuf-js": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-2.2.1.tgz", - "integrity": "sha512-GwIJau9XaA8nLVbUXsN3IlFi7WmQ48gBUrl3FTkkL/XLu/POhBzfmX9hd33FNMX1qAsfl6ozO1iMmW9NC8YniA==", "dev": true, "license": "MIT", "dependencies": { @@ -3440,8 +2739,6 @@ }, "node_modules/@npmcli/arborist/node_modules/unique-filename": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-3.0.0.tgz", - "integrity": "sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==", "dev": true, "license": "ISC", "dependencies": { @@ -3453,8 +2750,6 @@ }, "node_modules/@npmcli/arborist/node_modules/unique-slug": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-4.0.0.tgz", - "integrity": "sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==", "dev": true, "license": "ISC", "dependencies": { @@ -3466,25 +2761,14 @@ }, "node_modules/@npmcli/arborist/node_modules/validate-npm-package-name": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz", - "integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==", "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/@npmcli/arborist/node_modules/walk-up-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", - "integrity": "sha512-9YlCL/ynK3CTlrSRrDxZvUauLzAswPCrsaCgilqFevUYpeEW0/3ScEjaa3kbW/T0ghhkEr7mv+fpjqn1Y1YuTA==", - "dev": true, - "license": "ISC" - }, "node_modules/@npmcli/arborist/node_modules/which": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", - "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", "dev": true, "license": "ISC", "dependencies": { @@ -3499,8 +2783,6 @@ }, "node_modules/@npmcli/arborist/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, "license": "ISC", "dependencies": { @@ -3513,34 +2795,11 @@ }, "node_modules/@npmcli/arborist/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true, "license": "ISC" }, - "node_modules/@npmcli/config": { - "version": "10.7.1", - "resolved": "https://registry.npmjs.org/@npmcli/config/-/config-10.7.1.tgz", - "integrity": "sha512-lh0eZYOknIpIKYKxbQKX7xFmb4FbmrOHUD25+0iEo3djRQP6YleHwBFgjH3X7QvUVM4t+Xm7rGsjDwJp63WkAg==", - "license": "ISC", - "dependencies": { - "@npmcli/map-workspaces": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "ci-info": "^4.0.0", - "ini": "^6.0.0", - "nopt": "^9.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "walk-up-path": "^4.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/fs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz", - "integrity": "sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og==", + "node_modules/@npmcli/fs": { + "version": "5.0.0", "license": "ISC", "dependencies": { "semver": "^7.3.5" @@ -3550,890 +2809,531 @@ } }, "node_modules/@npmcli/git": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz", - "integrity": "sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg==", + "version": "7.0.1", "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/promise-spawn": "^9.0.0", "ini": "^6.0.0", "lru-cache": "^11.2.1", - "npm-pick-manifest": "^11.0.1", - "proc-log": "^6.0.0", - "semver": "^7.3.5", - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/git/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/@npmcli/installed-package-contents": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz", - "integrity": "sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA==", - "license": "ISC", - "dependencies": { - "npm-bundled": "^5.0.0", - "npm-normalize-package-bin": "^5.0.0" - }, - "bin": { - "installed-package-contents": "bin/index.js" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/map-workspaces": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-5.0.3.tgz", - "integrity": "sha512-o2grssXo1e774E5OtEwwrgoszYRh0lqkJH+Pb9r78UcqdGJRDRfhpM8DvZPjzNLLNYeD/rNbjOKM3Ss5UABROw==", - "license": "ISC", - "dependencies": { - "@npmcli/name-from-folder": "^4.0.0", - "@npmcli/package-json": "^7.0.0", - "glob": "^13.0.0", - "minimatch": "^10.0.3" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/map-workspaces/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@npmcli/metavuln-calculator": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-9.0.3.tgz", - "integrity": "sha512-94GLSYhLXF2t2LAC7pDwLaM4uCARzxShyAQKsirmlNcpidH89VA4/+K1LbJmRMgz5gy65E/QBBWQdUvGLe2Frg==", - "license": "ISC", - "dependencies": { - "cacache": "^20.0.0", - "json-parse-even-better-errors": "^5.0.0", - "pacote": "^21.0.0", - "proc-log": "^6.0.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/name-from-folder": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-4.0.0.tgz", - "integrity": "sha512-qfrhVlOSqmKM8i6rkNdZzABj8MKEITGFAY+4teqBziksCQAOLutiAxM1wY2BKEd8KjUSpWmWCYxvXr0y4VTlPg==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/node-gyp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-5.0.0.tgz", - "integrity": "sha512-uuG5HZFXLfyFKqg8QypsmgLQW7smiRjVc45bqD/ofZZcR/uxEjgQU8qDPv0s9TEeMUiAAU/GC5bR6++UdTirIQ==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/package-json": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-7.0.5.tgz", - "integrity": "sha512-iVuTlG3ORq2iaVa1IWUxAO/jIp77tUKBhoMjuzYW2kL4MLN1bi/ofqkZ7D7OOwh8coAx1/S2ge0rMdGv8sLSOQ==", - "license": "ISC", - "dependencies": { - "@npmcli/git": "^7.0.0", - "glob": "^13.0.0", - "hosted-git-info": "^9.0.0", - "json-parse-even-better-errors": "^5.0.0", - "proc-log": "^6.0.0", - "semver": "^7.5.3", - "spdx-expression-parse": "^4.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/promise-spawn": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-9.0.1.tgz", - "integrity": "sha512-OLUaoqBuyxeTqUvjA3FZFiXUfYC1alp3Sa99gW3EUDz3tZ3CbXDdcZ7qWKBzicrJleIgucoWamWH1saAmH/l2Q==", - "license": "ISC", - "dependencies": { - "which": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/query": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/query/-/query-5.0.0.tgz", - "integrity": "sha512-8TZWfTQOsODpLqo9SVhVjHovmKXNpevHU0gO9e+y4V4fRIOneiXy0u0sMP9LmS71XivrEWfZWg50ReH4WRT4aQ==", - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/redact": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz", - "integrity": "sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q==", - "license": "ISC", - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@npmcli/run-script": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz", - "integrity": "sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg==", - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^5.0.0", - "@npmcli/package-json": "^7.0.0", - "@npmcli/promise-spawn": "^9.0.0", - "node-gyp": "^12.1.0", - "proc-log": "^6.0.0" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - } - }, - "node_modules/@one-ini/wasm": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", - "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@oxc-resolver/binding-android-arm-eabi": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.19.1.tgz", - "integrity": "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@oxc-resolver/binding-android-arm64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.19.1.tgz", - "integrity": "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@oxc-resolver/binding-darwin-arm64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.19.1.tgz", - "integrity": "sha512-nUC6d2i3R5B12sUW4O646qD5cnMXf2oBGPLIIeaRfU9doJRORAbE2SGv4eW6rMqhD+G7nf2Y8TTJTLiiO3Q/dQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-darwin-x64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.19.1.tgz", - "integrity": "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@oxc-resolver/binding-freebsd-x64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.19.1.tgz", - "integrity": "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.19.1.tgz", - "integrity": "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.19.1.tgz", - "integrity": "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.19.1.tgz", - "integrity": "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-arm64-musl": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.19.1.tgz", - "integrity": "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.19.1.tgz", - "integrity": "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.19.1.tgz", - "integrity": "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.19.1.tgz", - "integrity": "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.19.1.tgz", - "integrity": "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-gnu": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.19.1.tgz", - "integrity": "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-linux-x64-musl": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.19.1.tgz", - "integrity": "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@oxc-resolver/binding-openharmony-arm64": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.19.1.tgz", - "integrity": "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@oxc-resolver/binding-wasm32-wasi": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.19.1.tgz", - "integrity": "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^1.1.1" + "npm-pick-manifest": "^11.0.1", + "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^6.0.0" }, "engines": { - "node": ">=14.0.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.19.1.tgz", - "integrity": "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@npmcli/git/node_modules/ini": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.19.1.tgz", - "integrity": "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@npmcli/git/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@oxc-resolver/binding-win32-x64-msvc": { - "version": "11.19.1", - "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.19.1.tgz", - "integrity": "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "node_modules/@npmcli/installed-package-contents": { + "version": "4.0.0", + "license": "ISC", + "dependencies": { + "npm-bundled": "^5.0.0", + "npm-normalize-package-bin": "^5.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", - "dev": true, - "license": "MIT", + "node_modules/@npmcli/metavuln-calculator": { + "version": "9.0.3", + "license": "ISC", "dependencies": { - "@noble/hashes": "^1.1.5" + "cacache": "^20.0.0", + "json-parse-even-better-errors": "^5.0.0", + "pacote": "^21.0.0", + "proc-log": "^6.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "license": "MIT", - "optional": true, + "node_modules/@npmcli/metavuln-calculator/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", "engines": { - "node": ">=14" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "license": "MIT", + "node_modules/@npmcli/name-from-folder": { + "version": "4.0.0", + "license": "ISC", "engines": { - "node": ">=12.22.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "license": "MIT", + "node_modules/@npmcli/node-gyp": { + "version": "5.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/package-json": { + "version": "7.0.4", + "license": "ISC", "dependencies": { - "graceful-fs": "4.2.10" + "@npmcli/git": "^7.0.0", + "glob": "^13.0.0", + "hosted-git-info": "^9.0.0", + "json-parse-even-better-errors": "^5.0.0", + "proc-log": "^6.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": ">=12.22.0" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "license": "ISC" + "node_modules/@npmcli/package-json/node_modules/proc-log": { + "version": "6.1.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } }, - "node_modules/@pnpm/npm-conf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-3.0.2.tgz", - "integrity": "sha512-h104Kh26rR8tm+a3Qkc5S4VLYint3FE48as7+/5oCEcKR2idC/pF1G6AhIXKI+eHPJa/3J9i5z0Al47IeGHPkA==", - "license": "MIT", + "node_modules/@npmcli/promise-spawn": { + "version": "9.0.1", + "license": "ISC", "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" + "which": "^6.0.0" }, "engines": { - "node": ">=12" + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, - "license": "MIT", + "node_modules/@npmcli/query": { + "version": "4.0.1", + "license": "ISC", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" + "postcss-selector-parser": "^7.0.0" }, "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "4.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "10.0.3", + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "@npmcli/promise-spawn": "^9.0.0", + "node-gyp": "^12.1.0", + "proc-log": "^6.0.0", + "which": "^6.0.0" }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } + "engines": { + "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", + "node_modules/@npmcli/run-script/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.19.1.tgz", + "integrity": "sha512-aUs47y+xyXHUKlbhqHUjBABjvycq6YSD7bpxSW7vplUmdzAlJ93yXY6ZR0c1o1x5A/QKbENCvs3+NlY8IpIVzg==", "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "android" ] }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.19.1.tgz", + "integrity": "sha512-oolbkRX+m7Pq2LNjr/kKgYeC7bRDMVTWPgxBGMjSpZi/+UskVo4jsMU3MLheZV55jL6c3rNelPl4oD60ggYmqA==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "android" ] }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.19.1", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.19.1.tgz", + "integrity": "sha512-cV50vE5+uAgNcFa3QY1JOeKDSkM/9ReIcc/9wn4TavhW/itkDGrXhw9jaKnkQnGbjJ198Yh5nbX/Gr2mr4Z5jQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ] }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.19.1.tgz", + "integrity": "sha512-xZOQiYGFxtk48PBKff+Zwoym7ScPAIVp4c14lfLxizO2LTTTJe5sx9vQNGrBymrf/vatSPNMD4FgsaaRigPkqw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ] }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.19.1.tgz", + "integrity": "sha512-lXZYWAC6kaGe/ky2su94e9jN9t6M0/6c+GrSlCqL//XO1cxi5lpAhnJYdyrKfm0ZEr/c7RNyAx3P7FSBcBd5+A==", "cpu": [ - "arm64" + "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.19.1.tgz", + "integrity": "sha512-veG1kKsuK5+t2IsO9q0DErYVSw2azvCVvWHnfTOS73WE0STdLLB7Q1bB9WR+yHPQM76ASkFyRbogWo1GR1+WbQ==", "cpu": [ - "loong64" + "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.19.1.tgz", + "integrity": "sha512-heV2+jmXyYnUrpUXSPugqWDRpnsQcDm2AX4wzTuvgdlZfoNYO0O3W2AVpJYaDn9AG4JdM6Kxom8+foE7/BcSig==", "cpu": [ - "loong64" + "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.19.1.tgz", + "integrity": "sha512-jvo2Pjs1c9KPxMuMPIeQsgu0mOJF9rEb3y3TdpsrqwxRM+AN6/nDDwv45n5ZrUnQMsdBy5gIabioMKnQfWo9ew==", "cpu": [ - "ppc64" + "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.19.1.tgz", + "integrity": "sha512-vLmdNxWCdN7Uo5suays6A/+ywBby2PWBBPXctWPg5V0+eVuzsJxgAn6MMB4mPlshskYbppjpN2Zg83ArHze9gQ==", "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.19.1.tgz", + "integrity": "sha512-/b+WgR+VTSBxzgOhDO7TlMXC1ufPIMR6Vj1zN+/x+MnyXGW7prTLzU9eW85Aj7Th7CCEG9ArCbTeqxCzFWdg2w==", "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.19.1.tgz", + "integrity": "sha512-YlRdeWb9j42p29ROh+h4eg/OQ3dTJlpHSa+84pUM9+p6i3djtPz1q55yLJhgW9XfDch7FN1pQ/Vd6YP+xfRIuw==", "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.19.1.tgz", + "integrity": "sha512-EDpafVOQWF8/MJynsjOGFThcqhRHy417sRyLfQmeiamJ8qVhSKAn2Dn2VVKUGCjVB9C46VGjhNo7nOPUi1x6uA==", "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.19.1.tgz", + "integrity": "sha512-NxjZe+rqWhr+RT8/Ik+5ptA3oz7tUw361Wa5RWQXKnfqwSSHdHyrw6IdcTfYuml9dM856AlKWZIUXDmA9kkiBQ==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.19.1.tgz", + "integrity": "sha512-cM/hQwsO3ReJg5kR+SpI69DMfvNCp+A/eVR4b4YClE5bVZwz8rh2Nh05InhwI5HR/9cArbEkzMjcKgTHS6UaNw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ] }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.19.1.tgz", + "integrity": "sha512-QF080IowFB0+9Rh6RcD19bdgh49BpQHUW5TajG1qvWHvmrQznTZZjYlgE2ltLXyKY+qs4F/v5xuX1XS7Is+3qA==", "cpu": [ - "x64" + "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ - "openbsd" + "openharmony" ] }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.19.1.tgz", + "integrity": "sha512-w8UCKhX826cP/ZLokXDS6+milN8y4X7zidsAttEdWlVoamTNf6lhBJldaWr3ukTDiye7s4HRcuPEPOXNC432Vg==", "cpu": [ - "arm64" + "wasm32" ], + "dev": true, "license": "MIT", "optional": true, - "os": [ - "openharmony" - ] + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.19.1.tgz", + "integrity": "sha512-nJ4AsUVZrVKwnU/QRdzPCCrO0TrabBqgJ8pJhXITdZGYOV28TIYystV1VFLbQ7DtAcaBHpocT5/ZJnF78YJPtQ==", "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", + "node_modules/@oxc-resolver/binding-win32-ia32-msvc": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.19.1.tgz", + "integrity": "sha512-EW+ND5q2Tl+a3pH81l1QbfgbF3HmqgwLfDfVithRFheac8OTcnbXt/JxqD2GbDkb7xYEqy1zNaVFRr3oeG8npA==", "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.19.1", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.19.1.tgz", + "integrity": "sha512-6hIU3RQu45B+VNTY4Ru8ppFwjVS/S5qwYyGhBotmjxfEKk41I2DlGtRfGJndZ5+6lneE2pwloqunlOyZuX/XAw==", "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ "win32" ] }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "^1.1.5" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@pnpm/config.env-replace": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file": { + "version": "1.0.2", + "license": "MIT", + "dependencies": { + "graceful-fs": "4.2.10" + }, + "engines": { + "node": ">=12.22.0" + } + }, + "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { + "version": "4.2.10", + "license": "ISC" + }, + "node_modules/@pnpm/npm-conf": { + "version": "2.3.1", + "license": "MIT", + "dependencies": { + "@pnpm/config.env-replace": "^1.1.0", + "@pnpm/network.ca-file": "^1.0.1", + "config-chain": "^1.1.11" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.53.3", "cpu": [ - "x64" + "arm64" ], "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ] }, "node_modules/@sec-ant/readable-stream": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", - "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true, "license": "MIT" }, "node_modules/@shikijs/core": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", - "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", "license": "MIT", "dependencies": { "@shikijs/engine-javascript": "2.5.0", @@ -4446,8 +3346,6 @@ }, "node_modules/@shikijs/engine-javascript": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", - "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0", @@ -4457,8 +3355,6 @@ }, "node_modules/@shikijs/engine-oniguruma": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", - "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0", @@ -4467,8 +3363,6 @@ }, "node_modules/@shikijs/langs": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", - "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0" @@ -4476,8 +3370,6 @@ }, "node_modules/@shikijs/themes": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", - "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", "license": "MIT", "dependencies": { "@shikijs/types": "2.5.0" @@ -4485,8 +3377,6 @@ }, "node_modules/@shikijs/transformers": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", - "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", "license": "MIT", "dependencies": { "@shikijs/core": "2.5.0", @@ -4495,8 +3385,6 @@ }, "node_modules/@shikijs/types": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", - "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", "license": "MIT", "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", @@ -4505,14 +3393,10 @@ }, "node_modules/@shikijs/vscode-textmate": { "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", "license": "MIT" }, "node_modules/@sigstore/bundle": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-4.0.0.tgz", - "integrity": "sha512-NwCl5Y0V6Di0NexvkTqdoVfmjTaQwoLM236r89KEojGmq/jMls8S+zb7yOwAPdXvbwfKDlP+lmXgAL4vKSQT+A==", "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.5.0" @@ -4522,9 +3406,7 @@ } }, "node_modules/@sigstore/core": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/core/-/core-3.1.0.tgz", - "integrity": "sha512-o5cw1QYhNQ9IroioJxpzexmPjfCe7gzafd2RY3qnMpxr4ZEja+Jad/U8sgFpaue6bOaF+z7RVkyKVV44FN+N8A==", + "version": "3.0.0", "license": "Apache-2.0", "engines": { "node": "^20.17.0 || >=22.9.0" @@ -4532,24 +3414,20 @@ }, "node_modules/@sigstore/protobuf-specs": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@sigstore/protobuf-specs/-/protobuf-specs-0.5.0.tgz", - "integrity": "sha512-MM8XIwUjN2bwvCg1QvrMtbBmpcSHrkhFSCu1D11NyPvDQ25HEc4oG5/OcQfd/Tlf/OxmKWERDj0zGE23jQaMwA==", "license": "Apache-2.0", "engines": { "node": "^18.17.0 || >=20.5.0" } }, "node_modules/@sigstore/sign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/sign/-/sign-4.1.0.tgz", - "integrity": "sha512-Vx1RmLxLGnSUqx/o5/VsCjkuN5L7y+vxEEwawvc7u+6WtX2W4GNa7b9HEjmcRWohw/d6BpATXmvOwc78m+Swdg==", + "version": "4.0.1", "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.0.0", "@sigstore/protobuf-specs": "^0.5.0", - "make-fetch-happen": "^15.0.3", - "proc-log": "^6.1.0", + "make-fetch-happen": "^15.0.2", + "proc-log": "^5.0.0", "promise-retry": "^2.0.1" }, "engines": { @@ -4557,49 +3435,30 @@ } }, "node_modules/@sigstore/tuf": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@sigstore/tuf/-/tuf-4.0.1.tgz", - "integrity": "sha512-OPZBg8y5Vc9yZjmWCHrlWPMBqW5yd8+wFNl+thMdtcWz3vjVSoJQutF8YkrzI0SLGnkuFof4HSsWUhXrf219Lw==", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { "@sigstore/protobuf-specs": "^0.5.0", - "tuf-js": "^4.1.0" + "tuf-js": "^4.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/@sigstore/verify": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@sigstore/verify/-/verify-3.1.0.tgz", - "integrity": "sha512-mNe0Iigql08YupSOGv197YdHpPPr+EzDZmfCgMc7RPNaZTw5aLN01nBl6CHJOh3BGtnMIj83EeN4butBchc8Ag==", + "version": "3.0.0", "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.0.0", "@sigstore/protobuf-specs": "^0.5.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/@simple-libs/stream-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@simple-libs/stream-utils/-/stream-utils-1.2.0.tgz", - "integrity": "sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://ko-fi.com/dangreen" - } - }, "node_modules/@sindresorhus/base62": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", - "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", "dev": true, "license": "MIT", "engines": { @@ -4611,8 +3470,6 @@ }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", - "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "license": "MIT", "engines": { "node": ">=18" @@ -4623,8 +3480,6 @@ }, "node_modules/@sinonjs/commons": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4632,9 +3487,7 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.1.tgz", - "integrity": "sha512-cO5W33JgAPbOh07tvZjUOJ7oWhtaqGHiZw+11DPbyqh2kHTBc3eF/CjJDeQ4205RLQsX6rxCuYOroFQwl7JDRw==", + "version": "15.1.0", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4642,9 +3495,7 @@ } }, "node_modules/@sinonjs/samsam": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-9.0.2.tgz", - "integrity": "sha512-H/JSxa4GNKZuuU41E3b8Y3tbSEx8y4uq4UH1C56ONQac16HblReJomIvv3Ud7ANQHQmkeSowY49Ij972e/pGxQ==", + "version": "8.0.3", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4654,8 +3505,6 @@ }, "node_modules/@sinonjs/samsam/node_modules/type-detect": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, "license": "MIT", "engines": { @@ -4664,43 +3513,42 @@ }, "node_modules/@tokenizer/token": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", - "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "dev": true, "license": "MIT" }, "node_modules/@tufjs/canonical-json": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tufjs/canonical-json/-/canonical-json-2.0.0.tgz", - "integrity": "sha512-yVtV8zsdo8qFHe+/3kw81dSLyF7D576A5cCFCi4X7B39tWT7SekaEFUnvnWJHz+9qO7qJTah1JbrDjWKqFtdWA==", "license": "MIT", "engines": { "node": "^16.14.0 || >=18.0.0" } }, "node_modules/@tufjs/models": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@tufjs/models/-/models-4.1.0.tgz", - "integrity": "sha512-Y8cK9aggNRsqJVaKUlEYs4s7CvQ1b1ta2DVPyAimb0I2qhzjNk+A+mxvll/klL0RlfuIUei8BF7YWiua4kQqww==", + "version": "4.0.0", "license": "MIT", "dependencies": { "@tufjs/canonical-json": "2.0.0", - "minimatch": "^10.1.1" + "minimatch": "^9.0.5" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/@tufjs/models/node_modules/brace-expansion": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/@tufjs/models/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", + "version": "9.0.5", + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4717,16 +3565,20 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/conventional-commits-parser": { + "version": "5.0.2", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, "node_modules/@types/hast": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -4734,21 +3586,15 @@ }, "node_modules/@types/json-schema": { "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, "node_modules/@types/linkify-it": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", "license": "MIT" }, "node_modules/@types/markdown-it": { "version": "14.1.2", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", - "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "license": "MIT", "dependencies": { "@types/linkify-it": "^5", @@ -4757,8 +3603,6 @@ }, "node_modules/@types/mdast": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", "license": "MIT", "dependencies": { "@types/unist": "*" @@ -4766,51 +3610,28 @@ }, "node_modules/@types/mdurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", "license": "MIT" }, "node_modules/@types/node": { - "version": "25.3.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.3.tgz", - "integrity": "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ==", + "version": "24.10.1", + "devOptional": true, "license": "MIT", "dependencies": { - "undici-types": "~7.18.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", - "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==", "license": "MIT" }, "node_modules/@types/unist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, "node_modules/@types/web-bluetooth": { "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", - "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", "license": "MIT" }, - "node_modules/@typescript-eslint/types": { - "version": "8.56.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.56.1.tgz", - "integrity": "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@ui5-internal/benchmark": { "resolved": "internal/benchmark", "link": true @@ -4849,14 +3670,10 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, "node_modules/@vercel/nft": { "version": "0.29.4", - "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.29.4.tgz", - "integrity": "sha512-6lLqMNX3TuycBPABycx7A9F1bHQR7kiQln6abjFbPrf5C/05qHM9M5E4PeTE59c7z8g6vHnx1Ioihb2AQl7BTA==", "dev": true, "license": "MIT", "dependencies": { @@ -4880,17 +3697,8 @@ "node": ">=18" } }, - "node_modules/@vercel/nft/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/@vercel/nft/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4899,9 +3707,6 @@ }, "node_modules/@vercel/nft/node_modules/glob": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -4921,19 +3726,15 @@ }, "node_modules/@vercel/nft/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/@vercel/nft/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.5", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4944,8 +3745,6 @@ }, "node_modules/@vercel/nft/node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -4961,8 +3760,6 @@ }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", - "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -4973,51 +3770,33 @@ } }, "node_modules/@vue/compiler-core": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.29.tgz", - "integrity": "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/shared": "3.5.29", - "entities": "^7.0.1", + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.25", + "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, - "node_modules/@vue/compiler-core/node_modules/entities": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", - "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, "node_modules/@vue/compiler-dom": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.29.tgz", - "integrity": "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.29", - "@vue/shared": "3.5.29" + "@vue/compiler-core": "3.5.25", + "@vue/shared": "3.5.25" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.29.tgz", - "integrity": "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@vue/compiler-core": "3.5.29", - "@vue/compiler-dom": "3.5.29", - "@vue/compiler-ssr": "3.5.29", - "@vue/shared": "3.5.29", + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.25", + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", @@ -5025,19 +3804,15 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.29.tgz", - "integrity": "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.29", - "@vue/shared": "3.5.29" + "@vue/compiler-dom": "3.5.25", + "@vue/shared": "3.5.25" } }, "node_modules/@vue/devtools-api": { "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", "license": "MIT", "dependencies": { "@vue/devtools-kit": "^7.7.9" @@ -5045,8 +3820,6 @@ }, "node_modules/@vue/devtools-kit": { "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", "license": "MIT", "dependencies": { "@vue/devtools-shared": "^7.7.9", @@ -5060,67 +3833,53 @@ }, "node_modules/@vue/devtools-shared": { "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", "license": "MIT", "dependencies": { "rfdc": "^1.4.1" } }, "node_modules/@vue/reactivity": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.29.tgz", - "integrity": "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@vue/shared": "3.5.29" + "@vue/shared": "3.5.25" } }, "node_modules/@vue/runtime-core": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.29.tgz", - "integrity": "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.29", - "@vue/shared": "3.5.29" + "@vue/reactivity": "3.5.25", + "@vue/shared": "3.5.25" } }, "node_modules/@vue/runtime-dom": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.29.tgz", - "integrity": "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.29", - "@vue/runtime-core": "3.5.29", - "@vue/shared": "3.5.29", - "csstype": "^3.2.3" + "@vue/reactivity": "3.5.25", + "@vue/runtime-core": "3.5.25", + "@vue/shared": "3.5.25", + "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.29.tgz", - "integrity": "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.29", - "@vue/shared": "3.5.29" + "@vue/compiler-ssr": "3.5.25", + "@vue/shared": "3.5.25" }, "peerDependencies": { - "vue": "3.5.29" + "vue": "3.5.25" } }, "node_modules/@vue/shared": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.29.tgz", - "integrity": "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==", + "version": "3.5.25", "license": "MIT" }, "node_modules/@vueuse/core": { "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", - "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", "license": "MIT", "dependencies": { "@types/web-bluetooth": "^0.0.21", @@ -5134,8 +3893,6 @@ }, "node_modules/@vueuse/integrations": { "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", - "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", "license": "MIT", "dependencies": { "@vueuse/core": "12.8.2", @@ -5200,8 +3957,6 @@ }, "node_modules/@vueuse/metadata": { "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", - "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -5209,8 +3964,6 @@ }, "node_modules/@vueuse/shared": { "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", - "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", "license": "MIT", "dependencies": { "vue": "^3.5.13" @@ -5220,18 +3973,14 @@ } }, "node_modules/abbrev": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-4.0.0.tgz", - "integrity": "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==", + "version": "3.0.1", "license": "ISC", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/abort-controller": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, "license": "MIT", "dependencies": { @@ -5243,8 +3992,6 @@ }, "node_modules/accepts": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -5256,17 +4003,13 @@ }, "node_modules/accepts/node_modules/negotiator": { "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "version": "8.15.0", "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5277,8 +4020,6 @@ }, "node_modules/acorn-import-attributes": { "version": "1.9.5", - "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", - "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5287,17 +4028,13 @@ }, "node_modules/acorn-jsx": { "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "node_modules/acorn-walk": { - "version": "8.3.5", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", - "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "version": "8.3.4", "dev": true, "license": "MIT", "dependencies": { @@ -5309,8 +4046,6 @@ }, "node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -5318,8 +4053,6 @@ }, "node_modules/aggregate-error": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "license": "MIT", "dependencies": { @@ -5332,8 +4065,6 @@ }, "node_modules/aggregate-error/node_modules/indent-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "license": "MIT", "engines": { @@ -5341,15 +4072,13 @@ } }, "node_modules/ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "6.12.6", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, "funding": { "type": "github", @@ -5358,33 +4087,29 @@ }, "node_modules/ajv-errors": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", "license": "MIT", "peerDependencies": { "ajv": ">=5.0.0" } }, "node_modules/algoliasearch": { - "version": "5.49.1", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.49.1.tgz", - "integrity": "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ==", - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.15.1", - "@algolia/client-abtesting": "5.49.1", - "@algolia/client-analytics": "5.49.1", - "@algolia/client-common": "5.49.1", - "@algolia/client-insights": "5.49.1", - "@algolia/client-personalization": "5.49.1", - "@algolia/client-query-suggestions": "5.49.1", - "@algolia/client-search": "5.49.1", - "@algolia/ingestion": "1.49.1", - "@algolia/monitoring": "1.49.1", - "@algolia/recommend": "5.49.1", - "@algolia/requester-browser-xhr": "5.49.1", - "@algolia/requester-fetch": "5.49.1", - "@algolia/requester-node-http": "5.49.1" + "version": "5.44.0", + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.10.0", + "@algolia/client-abtesting": "5.44.0", + "@algolia/client-analytics": "5.44.0", + "@algolia/client-common": "5.44.0", + "@algolia/client-insights": "5.44.0", + "@algolia/client-personalization": "5.44.0", + "@algolia/client-query-suggestions": "5.44.0", + "@algolia/client-search": "5.44.0", + "@algolia/ingestion": "1.44.0", + "@algolia/monitoring": "1.44.0", + "@algolia/recommend": "5.44.0", + "@algolia/requester-browser-xhr": "5.44.0", + "@algolia/requester-fetch": "5.44.0", + "@algolia/requester-node-http": "5.44.0" }, "engines": { "node": ">= 14.0.0" @@ -5392,8 +4117,6 @@ }, "node_modules/ansi-align": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "license": "ISC", "dependencies": { "string-width": "^4.1.0" @@ -5401,8 +4124,6 @@ }, "node_modules/ansi-regex": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", "engines": { "node": ">=12" @@ -5413,8 +4134,6 @@ }, "node_modules/ansi-styles": { "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", "engines": { "node": ">=12" @@ -5425,14 +4144,10 @@ }, "node_modules/any-promise": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -5444,8 +4159,6 @@ }, "node_modules/anymatch/node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -5456,8 +4169,6 @@ }, "node_modules/append-transform": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", "dev": true, "license": "MIT", "dependencies": { @@ -5469,15 +4180,11 @@ }, "node_modules/archy": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true, "license": "MIT" }, "node_modules/are-docs-informative": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", - "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", "dev": true, "license": "MIT", "engines": { @@ -5486,20 +4193,14 @@ }, "node_modules/arg": { "version": "5.0.2", - "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", - "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "license": "MIT" }, "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==", "license": "Python-2.0" }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "license": "MIT", "dependencies": { @@ -5515,8 +4216,6 @@ }, "node_modules/array-find-index": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", "dev": true, "license": "MIT", "engines": { @@ -5525,21 +4224,15 @@ }, "node_modules/array-flatten": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, "node_modules/array-ify": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==", "dev": true, "license": "MIT" }, "node_modules/arraybuffer.prototype.slice": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5560,8 +4253,6 @@ }, "node_modules/arrgv": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/arrgv/-/arrgv-1.0.2.tgz", - "integrity": "sha512-a4eg4yhp7mmruZDQFqVMlxNRFGi/i1r87pt8SDHy0/I8PqSXoUTlWZRdAZo0VXgvEARcujbtTk8kiZRi1uDGRw==", "dev": true, "license": "MIT", "engines": { @@ -5570,8 +4261,6 @@ }, "node_modules/arrify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-3.0.0.tgz", - "integrity": "sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==", "dev": true, "license": "MIT", "engines": { @@ -5583,15 +4272,11 @@ }, "node_modules/asap": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, "license": "MIT" }, "node_modules/async": { "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "license": "MIT", "dependencies": { "lodash": "^4.17.14" @@ -5599,32 +4284,31 @@ }, "node_modules/async-function": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" } }, + "node_modules/async-mutex": { + "version": "0.5.0", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/async-sema": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", - "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", "dev": true, "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, "license": "MIT" }, "node_modules/atomically": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.1.tgz", - "integrity": "sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==", + "version": "2.1.0", "license": "MIT", "dependencies": { "stubborn-fs": "^2.0.0", @@ -5632,9 +4316,7 @@ } }, "node_modules/autoprefixer": { - "version": "10.4.27", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", - "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "version": "10.4.22", "funding": [ { "type": "opencollective", @@ -5651,9 +4333,10 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001774", + "browserslist": "^4.27.0", + "caniuse-lite": "^1.0.30001754", "fraction.js": "^5.3.4", + "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -5669,8 +4352,6 @@ }, "node_modules/ava": { "version": "6.4.1", - "resolved": "https://registry.npmjs.org/ava/-/ava-6.4.1.tgz", - "integrity": "sha512-vxmPbi1gZx9zhAjHBgw81w/iEDKcrokeRk/fqDTyA2DQygZ0o+dUGRHFOtX8RA5N0heGJTTsIk7+xYxitDb61Q==", "dev": true, "license": "MIT", "dependencies": { @@ -5730,23 +4411,29 @@ } } }, - "node_modules/ava/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "node_modules/ava/node_modules/debug": { + "version": "4.4.3", "dev": true, "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=6.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/ava/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5761,8 +4448,6 @@ }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5778,8 +4463,6 @@ }, "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5795,8 +4478,6 @@ }, "node_modules/babel-plugin-istanbul/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -5804,18 +4485,11 @@ } }, "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "version": "1.0.2", + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { @@ -5834,21 +4508,14 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz", - "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==", + "version": "2.9.17", "license": "Apache-2.0", "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" + "baseline-browser-mapping": "dist/cli.js" } }, "node_modules/bin-links": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-6.0.0.tgz", - "integrity": "sha512-X4CiKlcV2GjnCMwnKAfbVWpHa++65th9TuzAEYtZoATiOE2DQKhSp4CJlyLoTqdhBKlXjpXjCTYPNNFS33Fi6w==", "license": "ISC", "dependencies": { "cmd-shim": "^8.0.0", @@ -5861,12 +4528,18 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/bin-links/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/bin-links/node_modules/write-file-atomic": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-7.0.1.tgz", - "integrity": "sha512-OTIk8iR8/aCRWBqvxrzxR0hgxWpnYBblY1S5hDWBQfk/VFmJwzmJgQFN3WsoUKHISv2eAwe+PpbUzyL1CKTLXg==", + "version": "7.0.0", "license": "ISC", "dependencies": { + "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" }, "engines": { @@ -5875,8 +4548,6 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "license": "MIT", "engines": { "node": ">=8" @@ -5887,8 +4558,6 @@ }, "node_modules/bindings": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5896,9 +4565,7 @@ } }, "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "version": "2.8.0", "license": "MIT", "funding": { "url": "https://github.com/sponsors/antfu" @@ -5906,51 +4573,66 @@ }, "node_modules/bluebird": { "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "license": "MIT" }, "node_modules/blueimp-md5": { "version": "2.19.0", - "resolved": "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz", - "integrity": "sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w==", "dev": true, "license": "MIT" }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "1.20.4", "license": "MIT", "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=18" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/http-errors": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/statuses": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/boolbase": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "license": "ISC" }, "node_modules/boxen": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", - "integrity": "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==", "license": "MIT", "dependencies": { "ansi-align": "^3.0.1", @@ -5971,8 +4653,6 @@ }, "node_modules/boxen/node_modules/camelcase": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", - "integrity": "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==", "license": "MIT", "engines": { "node": ">=16" @@ -5981,28 +4661,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/boxen/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, "node_modules/boxen/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -6018,8 +4682,6 @@ }, "node_modules/boxen/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -6029,21 +4691,16 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "1.1.12", + "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -6053,9 +4710,7 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.0", "funding": [ { "type": "opencollective", @@ -6072,11 +4727,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", + "baseline-browser-mapping": "^2.8.25", + "caniuse-lite": "^1.0.30001754", + "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -6087,8 +4742,6 @@ }, "node_modules/buffer": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "funding": [ { @@ -6112,14 +4765,10 @@ }, "node_modules/buffer-from": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, "node_modules/bundle-name": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" @@ -6133,8 +4782,6 @@ }, "node_modules/bytes": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -6142,8 +4789,6 @@ }, "node_modules/cacache": { "version": "20.0.3", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-20.0.3.tgz", - "integrity": "sha512-3pUp4e8hv07k1QlijZu6Kn7c9+ZpWWk4j3F8N3xPuCExULobqJydKYOTj1FTq58srkJsXvO7LbGAH4C0ZU3WGw==", "license": "ISC", "dependencies": { "@npmcli/fs": "^5.0.0", @@ -6162,19 +4807,8 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/caching-transform": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", "dev": true, "license": "MIT", "dependencies": { @@ -6189,8 +4823,6 @@ }, "node_modules/caching-transform/node_modules/make-dir": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", "dependencies": { @@ -6205,8 +4837,6 @@ }, "node_modules/caching-transform/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -6215,15 +4845,11 @@ }, "node_modules/caching-transform/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, "node_modules/caching-transform/node_modules/write-file-atomic": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", "dev": true, "license": "ISC", "dependencies": { @@ -6235,8 +4861,6 @@ }, "node_modules/call-bind": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "license": "MIT", "dependencies": { @@ -6254,8 +4878,6 @@ }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -6267,8 +4889,6 @@ }, "node_modules/call-bound": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -6283,8 +4903,6 @@ }, "node_modules/callsites": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-4.2.0.tgz", - "integrity": "sha512-kfzR4zzQtAE9PC7CzZsjl3aBNbXWuXiSeOCdLcPpBfGW8YuCqQHcRPFDbr/BPVmd3EEPVpuFzLyuT/cUhPr4OQ==", "dev": true, "license": "MIT", "engines": { @@ -6294,20 +4912,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/camelcase-css": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", - "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", "license": "MIT", "engines": { "node": ">= 6" @@ -6315,8 +4921,6 @@ }, "node_modules/caniuse-api": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", "license": "MIT", "dependencies": { "browserslist": "^4.0.0", @@ -6326,9 +4930,7 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001776", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001776.tgz", - "integrity": "sha512-sg01JDPzZ9jGshqKSckOQthXnYwOEP50jeVFhaSFbZcOy05TiuuaffDOfcwtCisJ9kNQuLBFibYywv2Bgm9osw==", + "version": "1.0.30001757", "funding": [ { "type": "opencollective", @@ -6347,8 +4949,6 @@ }, "node_modules/catharsis": { "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", "license": "MIT", "dependencies": { "lodash": "^4.17.15" @@ -6358,9 +4958,7 @@ } }, "node_modules/cbor": { - "version": "10.0.12", - "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.12.tgz", - "integrity": "sha512-exQDevYd7ZQLP4moMQcZkKCVZsXLAtUSflObr3xTh4xzFIv/xBCdvCd6L259kQOUP2kcTC0jvC6PpZIf/WmRXA==", + "version": "10.0.11", "dev": true, "license": "MIT", "dependencies": { @@ -6372,8 +4970,6 @@ }, "node_modules/ccount": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "license": "MIT", "funding": { "type": "github", @@ -6381,42 +4977,17 @@ } }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, + "version": "5.6.2", "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/chalk/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/character-entities-html4": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "license": "MIT", "funding": { "type": "github", @@ -6425,8 +4996,6 @@ }, "node_modules/character-entities-legacy": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", "license": "MIT", "funding": { "type": "github", @@ -6435,8 +5004,6 @@ }, "node_modules/cheerio": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", "license": "MIT", "dependencies": { "cheerio-select": "^2.1.0", @@ -6460,8 +5027,6 @@ }, "node_modules/cheerio-select": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -6477,8 +5042,6 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -6501,8 +5064,6 @@ }, "node_modules/chokidar/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -6513,8 +5074,6 @@ }, "node_modules/chownr": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "license": "BlueOak-1.0.0", "engines": { "node": ">=18" @@ -6522,15 +5081,11 @@ }, "node_modules/chunkd": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chunkd/-/chunkd-2.0.1.tgz", - "integrity": "sha512-7d58XsFmOq0j6el67Ug9mHf9ELUXsQXYJBkyxhH/k+6Ke0qXRnv0kbemx+Twc6fRJ07C49lcbdgm9FL1Ei/6SQ==", "dev": true, "license": "MIT" }, "node_modules/ci-info": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", - "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", + "version": "4.3.1", "funding": [ { "type": "github", @@ -6544,15 +5099,11 @@ }, "node_modules/ci-parallel-vars": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ci-parallel-vars/-/ci-parallel-vars-1.0.1.tgz", - "integrity": "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg==", "dev": true, "license": "MIT" }, "node_modules/clean-stack": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "license": "MIT", "engines": { @@ -6561,8 +5112,6 @@ }, "node_modules/cli-boxes": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", "license": "MIT", "engines": { "node": ">=10" @@ -6573,8 +5122,6 @@ }, "node_modules/cli-progress": { "version": "3.12.0", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.12.0.tgz", - "integrity": "sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A==", "license": "MIT", "dependencies": { "string-width": "^4.2.3" @@ -6585,8 +5132,6 @@ }, "node_modules/cli-truncate": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, "license": "MIT", "dependencies": { @@ -6602,15 +5147,11 @@ }, "node_modules/cli-truncate/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, "node_modules/cli-truncate/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6627,8 +5168,6 @@ }, "node_modules/cliui": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -6641,8 +5180,6 @@ }, "node_modules/cliui/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" @@ -6650,8 +5187,6 @@ }, "node_modules/cliui/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -6665,8 +5200,6 @@ }, "node_modules/cliui/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -6677,8 +5210,6 @@ }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -6694,8 +5225,6 @@ }, "node_modules/clone": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", "license": "MIT", "engines": { "node": ">=0.8" @@ -6703,8 +5232,6 @@ }, "node_modules/cmd-shim": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-8.0.0.tgz", - "integrity": "sha512-Jk/BK6NCapZ58BKUxlSI+ouKRbjH1NLZCgJkYoab+vEHUY3f6OzpNBN9u7HFSv9J6TRDGs4PLOHezoKGaFRSCA==", "license": "ISC", "engines": { "node": "^20.17.0 || >=22.9.0" @@ -6712,8 +5239,6 @@ }, "node_modules/code-excerpt": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/code-excerpt/-/code-excerpt-4.0.0.tgz", - "integrity": "sha512-xxodCmBen3iy2i0WtAK8FlFNrRzjUqjRsMfho58xT/wvZU1YTM3fCnRjcy1gJPMepaRlgm/0e6w8SpWHpn3/cA==", "dev": true, "license": "MIT", "dependencies": { @@ -6725,8 +5250,6 @@ }, "node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -6737,20 +5260,14 @@ }, "node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, "node_modules/colord": { "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "license": "MIT", "dependencies": { @@ -6762,8 +5279,6 @@ }, "node_modules/comma-separated-tokens": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "license": "MIT", "funding": { "type": "github", @@ -6772,14 +5287,10 @@ }, "node_modules/command-exists": { "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", "license": "MIT" }, "node_modules/commander": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "license": "MIT", "engines": { @@ -6788,8 +5299,6 @@ }, "node_modules/comment-parser": { "version": "1.4.5", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz", - "integrity": "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==", "dev": true, "license": "MIT", "engines": { @@ -6797,32 +5306,21 @@ } }, "node_modules/common-ancestor-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-2.0.0.tgz", - "integrity": "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">= 18" - } + "version": "1.0.1", + "license": "ISC" }, "node_modules/common-path-prefix": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", "dev": true, "license": "ISC" }, "node_modules/commondir": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", "dev": true, "license": "MIT" }, "node_modules/compare-func": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz", - "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", "dev": true, "license": "MIT", "dependencies": { @@ -6832,8 +5330,6 @@ }, "node_modules/component-emitter": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", "funding": { @@ -6842,8 +5338,6 @@ }, "node_modules/compressible": { "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", "license": "MIT", "dependencies": { "mime-db": ">= 1.43.0 < 2" @@ -6854,8 +5348,6 @@ }, "node_modules/compression": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", "license": "MIT", "dependencies": { "bytes": "3.1.2", @@ -6870,41 +5362,13 @@ "node": ">= 0.8.0" } }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/compression/node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "license": "MIT" }, "node_modules/concordance": { "version": "5.0.4", - "resolved": "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz", - "integrity": "sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw==", "dev": true, "license": "ISC", "dependencies": { @@ -6923,8 +5387,6 @@ }, "node_modules/config-chain": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "license": "MIT", "dependencies": { "ini": "^1.3.4", @@ -6933,14 +5395,10 @@ }, "node_modules/config-chain/node_modules/ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, "node_modules/configstore": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-7.1.0.tgz", - "integrity": "sha512-N4oog6YJWbR9kGyXvS7jEykLDXIE2C0ILYqNBZBp9iwiJpoCBWYsuAdW6PPFn6w06jjnC+3JstVvWHO4cZqvRg==", "license": "BSD-2-Clause", "dependencies": { "atomically": "^2.0.3", @@ -6957,8 +5415,6 @@ }, "node_modules/configstore/node_modules/dot-prop": { "version": "9.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", - "integrity": "sha512-1gxPBJpI/pcjQhKgIU91II6Wkay+dLcN3M6rf2uwP8hRur3HtQXjVrdAK3sjC0piaEuxzMwjXChcETiJl47lAQ==", "license": "MIT", "dependencies": { "type-fest": "^4.18.2" @@ -6972,8 +5428,6 @@ }, "node_modules/configstore/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -6984,8 +5438,6 @@ }, "node_modules/consola": { "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "dev": true, "license": "MIT", "engines": { @@ -6994,8 +5446,6 @@ }, "node_modules/content-disposition": { "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "license": "MIT", "dependencies": { "safe-buffer": "5.2.1" @@ -7006,67 +5456,57 @@ }, "node_modules/content-type": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/conventional-changelog-angular": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-8.3.0.tgz", - "integrity": "sha512-DOuBwYSqWzfwuRByY9O4oOIvDlkUCTDzfbOgcSbkY+imXXj+4tmrEFao3K+FxemClYfYnZzsvudbwrhje9VHDA==", + "version": "7.0.0", "dev": true, "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=16" } }, "node_modules/conventional-changelog-conventionalcommits": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-9.3.0.tgz", - "integrity": "sha512-kYFx6gAyjSIMwNtASkI3ZE99U1fuVDJr0yTYgVy+I2QG46zNZfl2her+0+eoviG82c5WQvW1jMt1eOQTeJLodA==", + "version": "7.0.2", "dev": true, "license": "ISC", "dependencies": { "compare-func": "^2.0.0" }, "engines": { - "node": ">=18" + "node": ">=16" } }, "node_modules/conventional-commits-parser": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-6.3.0.tgz", - "integrity": "sha512-RfOq/Cqy9xV9bOA8N+ZH6DlrDR+5S3Mi0B5kACEjESpE+AviIpAptx9a9cFpWCCvgRtWT+0BbUw+e1BZfts9jg==", + "version": "5.0.0", "dev": true, "license": "MIT", "dependencies": { - "@simple-libs/stream-utils": "^1.2.0", - "meow": "^13.0.0" + "is-text-path": "^2.0.0", + "JSONStream": "^1.3.5", + "meow": "^12.0.1", + "split2": "^4.0.0" }, "bin": { - "conventional-commits-parser": "dist/cli/index.js" + "conventional-commits-parser": "cli.mjs" }, "engines": { - "node": ">=18" + "node": ">=16" } }, "node_modules/convert-source-map": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, "license": "MIT" }, "node_modules/convert-to-spaces": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/convert-to-spaces/-/convert-to-spaces-2.0.1.tgz", - "integrity": "sha512-rcQ1bsQO9799wq24uE5AM2tAILy4gXGIK/njFWcVQkGNZ96edlpY+A7bjwvzjYvLDyzmG1MmMLZhpcsb+klNMQ==", "dev": true, "license": "MIT", "engines": { @@ -7074,31 +5514,23 @@ } }, "node_modules/cookie": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", - "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "version": "0.7.1", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "version": "1.0.6", "license": "MIT" }, "node_modules/cookiejar": { "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", "dev": true, "license": "MIT" }, "node_modules/copy-anything": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", "license": "MIT", "dependencies": { "is-what": "^5.2.0" @@ -7112,14 +5544,10 @@ }, "node_modules/core-util-is": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, "node_modules/correct-license-metadata": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/correct-license-metadata/-/correct-license-metadata-1.5.0.tgz", - "integrity": "sha512-fVBH+P7EJvvzqQ1Jn7xrdAD7tKFrjeBDNawOgNELcSopCL70Ie8H9Cyn1nYO0E7jihunnpqjWdpEQinDhhKrzw==", + "version": "1.4.0", "dev": true, "license": "MIT", "dependencies": { @@ -7127,9 +5555,7 @@ } }, "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "version": "2.8.5", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -7137,16 +5563,10 @@ }, "engines": { "node": ">= 0.10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/cosmiconfig": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.1.tgz", - "integrity": "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==", + "version": "9.0.0", "dev": true, "license": "MIT", "dependencies": { @@ -7172,8 +5592,6 @@ }, "node_modules/cosmiconfig-typescript-loader": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.2.0.tgz", - "integrity": "sha512-GEN39v7TgdxgIoNcdkRE3uiAzQt3UXLyHbRHD6YoL048XAeOomyxaP+Hh/+2C6C2wYjxJ2onhJcsQp+L4YEkVQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7190,8 +5608,6 @@ }, "node_modules/cross-env": { "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", - "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", "dev": true, "license": "MIT", "dependencies": { @@ -7209,8 +5625,6 @@ }, "node_modules/cross-spawn": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -7223,14 +5637,10 @@ }, "node_modules/cross-spawn/node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, "node_modules/cross-spawn/node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -7244,8 +5654,6 @@ }, "node_modules/crypto-random-string": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", "dev": true, "license": "MIT", "dependencies": { @@ -7260,8 +5668,6 @@ }, "node_modules/crypto-random-string/node_modules/type-fest": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -7272,9 +5678,7 @@ } }, "node_modules/css-declaration-sorter": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.1.tgz", - "integrity": "sha512-gz6x+KkgNCjxq3Var03pRYLhyNfwhkKF1g/yoLgDNtFvVu0/fOLV9C8fFEZRjACp/XQLumjAYo7JVjzH3wLbxA==", + "version": "7.3.0", "license": "ISC", "engines": { "node": "^14 || ^16 || >=18" @@ -7285,8 +5689,6 @@ }, "node_modules/css-select": { "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0", @@ -7300,13 +5702,11 @@ } }, "node_modules/css-tree": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.0.tgz", - "integrity": "sha512-t99A4LolkP0ZX9WUoaHz4YrPT1FKNlV8IDCeCPPpGaWyxegh64tt/BSUqN3u5necrYRon+ddZ6mPMjxIlfpobg==", + "version": "3.1.0", "license": "MIT", "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" }, "engines": { "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" @@ -7314,8 +5714,6 @@ }, "node_modules/css-what": { "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "license": "BSD-2-Clause", "engines": { "node": ">= 6" @@ -7326,8 +5724,6 @@ }, "node_modules/cssesc": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", - "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -7338,8 +5734,6 @@ }, "node_modules/cssnano": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-7.1.2.tgz", - "integrity": "sha512-HYOPBsNvoiFeR1eghKD5C3ASm64v9YVyJB4Ivnl2gqKoQYvjjN/G0rztvKQq8OxocUtC6sjqY8jwYngIB4AByA==", "license": "MIT", "dependencies": { "cssnano-preset-default": "^7.0.10", @@ -7358,8 +5752,6 @@ }, "node_modules/cssnano-preset-default": { "version": "7.0.10", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-7.0.10.tgz", - "integrity": "sha512-6ZBjW0Lf1K1Z+0OKUAUpEN62tSXmYChXWi2NAA0afxEVsj9a+MbcB1l5qel6BHJHmULai2fCGRthCeKSFbScpA==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -7402,8 +5794,6 @@ }, "node_modules/cssnano-utils": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-5.0.1.tgz", - "integrity": "sha512-ZIP71eQgG9JwjVZsTPSqhc6GHgEr53uJ7tK5///VfyWj6Xp2DBmixWHqJgPno+PqATzn48pL42ww9x5SSGmhZg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -7414,8 +5804,6 @@ }, "node_modules/csso": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", "license": "MIT", "dependencies": { "css-tree": "~2.2.0" @@ -7427,8 +5815,6 @@ }, "node_modules/csso/node_modules/css-tree": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", "license": "MIT", "dependencies": { "mdn-data": "2.0.28", @@ -7441,20 +5827,14 @@ }, "node_modules/csso/node_modules/mdn-data": { "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", "license": "CC0-1.0" }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, "node_modules/currently-unhandled": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", "dev": true, "license": "MIT", "dependencies": { @@ -7466,8 +5846,6 @@ }, "node_modules/dargs": { "version": "8.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-8.1.0.tgz", - "integrity": "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==", "dev": true, "license": "MIT", "engines": { @@ -7479,8 +5857,6 @@ }, "node_modules/data-view-buffer": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7497,8 +5873,6 @@ }, "node_modules/data-view-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7515,8 +5889,6 @@ }, "node_modules/data-view-byte-offset": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7533,8 +5905,6 @@ }, "node_modules/data-with-position": { "version": "0.5.0", - "resolved": "https://registry.npmjs.org/data-with-position/-/data-with-position-0.5.0.tgz", - "integrity": "sha512-GhsgEIPWk7WCAisjwBkOjvPqpAlVUOSl1CTmy9KyhVMG1wxl29Zj5+J71WhQ/KgoJS/Psxq6Cnioz3xdBjeIWQ==", "license": "BSD-3-Clause", "dependencies": { "yaml-ast-parser": "^0.0.43" @@ -7542,8 +5912,6 @@ }, "node_modules/date-time": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz", - "integrity": "sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg==", "dev": true, "license": "MIT", "dependencies": { @@ -7554,26 +5922,14 @@ } }, "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "2.6.9", "license": "MIT", "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "ms": "2.0.0" } }, "node_modules/decamelize": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", "dev": true, "license": "MIT", "engines": { @@ -7582,8 +5938,6 @@ }, "node_modules/deep-extend": { "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "license": "MIT", "engines": { "node": ">=4.0.0" @@ -7591,15 +5945,11 @@ }, "node_modules/deep-is": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, "license": "MIT" }, "node_modules/default-browser": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", - "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "version": "5.4.0", "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", @@ -7614,8 +5964,6 @@ }, "node_modules/default-browser-id": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", "license": "MIT", "engines": { "node": ">=18" @@ -7626,8 +5974,6 @@ }, "node_modules/default-require-extensions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", "dev": true, "license": "MIT", "dependencies": { @@ -7642,8 +5988,6 @@ }, "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", "dependencies": { @@ -7658,22 +6002,8 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-properties": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "dependencies": { @@ -7690,8 +6020,6 @@ }, "node_modules/delayed-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, "license": "MIT", "engines": { @@ -7700,8 +6028,6 @@ }, "node_modules/depd": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7709,8 +6035,6 @@ }, "node_modules/dequal": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "license": "MIT", "engines": { "node": ">=6" @@ -7718,8 +6042,6 @@ }, "node_modules/destroy": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "license": "MIT", "engines": { "node": ">= 0.8", @@ -7728,8 +6050,6 @@ }, "node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -7738,14 +6058,10 @@ }, "node_modules/detect-node": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", "license": "MIT" }, "node_modules/devcert-sanscache": { "version": "0.5.1", - "resolved": "https://registry.npmjs.org/devcert-sanscache/-/devcert-sanscache-0.5.1.tgz", - "integrity": "sha512-9ePmMvWItstun0c35V5WXUlNU4MCHtpXWxKUJcDiZvyKkcA3FxkL6PFHKqTd446mXMmvLpOGBxVD6GjBXeMA5A==", "license": "MIT", "dependencies": { "command-exists": "^1.2.9", @@ -7757,16 +6073,8 @@ "node": "^14.13.1 || >=16.0.0" } }, - "node_modules/devcert-sanscache/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "license": "MIT" - }, "node_modules/devcert-sanscache/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -7774,9 +6082,6 @@ }, "node_modules/devcert-sanscache/node_modules/glob": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -7795,17 +6100,13 @@ }, "node_modules/devcert-sanscache/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, "node_modules/devcert-sanscache/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.5", "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -7816,8 +6117,6 @@ }, "node_modules/devcert-sanscache/node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", @@ -7830,25 +6129,8 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/devcert-sanscache/node_modules/rimraf": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", - "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", - "license": "ISC", - "dependencies": { - "glob": "^10.3.7" - }, - "bin": { - "rimraf": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/devlop": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", "license": "MIT", "dependencies": { "dequal": "^2.0.0" @@ -7860,8 +6142,6 @@ }, "node_modules/dezalgo": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", "dev": true, "license": "ISC", "dependencies": { @@ -7871,14 +6151,10 @@ }, "node_modules/didyoumean": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", - "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "license": "Apache-2.0" }, "node_modules/diff": { "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -7887,14 +6163,10 @@ }, "node_modules/dlv": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "license": "MIT" }, "node_modules/docopt": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/docopt/-/docopt-0.6.2.tgz", - "integrity": "sha512-NqTbaYeE4gA/wU1hdKFdU+AFahpDOpgGLzHP42k6H6DKExJd0A55KEVWYhL9FEmHmgeLvEU2vuKXDuU+4yToOw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -7902,8 +6174,6 @@ }, "node_modules/dom-serializer": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "license": "MIT", "dependencies": { "domelementtype": "^2.3.0", @@ -7916,8 +6186,6 @@ }, "node_modules/domelementtype": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", "funding": [ { "type": "github", @@ -7928,8 +6196,6 @@ }, "node_modules/domhandler": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "license": "BSD-2-Clause", "dependencies": { "domelementtype": "^2.3.0" @@ -7943,8 +6209,6 @@ }, "node_modules/domutils": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", "license": "BSD-2-Clause", "dependencies": { "dom-serializer": "^2.0.0", @@ -7957,8 +6221,6 @@ }, "node_modules/dot-prop": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", - "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7970,8 +6232,6 @@ }, "node_modules/dunder-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -7984,20 +6244,16 @@ }, "node_modules/eastasianwidth": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "license": "MIT" }, "node_modules/editorconfig": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz", - "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==", + "version": "1.0.4", "dev": true, "license": "MIT", "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", - "minimatch": "^9.0.1", + "minimatch": "9.0.1", "semver": "^7.5.3" }, "bin": { @@ -8007,17 +6263,8 @@ "node": ">=14" } }, - "node_modules/editorconfig/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/editorconfig/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8025,13 +6272,11 @@ } }, "node_modules/editorconfig/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.1", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -8042,20 +6287,14 @@ }, "node_modules/ee-first": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.307", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz", - "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==", + "version": "1.5.259", "license": "ISC" }, "node_modules/emittery": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-1.2.1.tgz", - "integrity": "sha512-sFz64DCRjirhwHLxofFqxYQm6DCp6o0Ix7jwKQvuCHPn4GMRZNuBZyLPu9Ccmk/QSCAMZt6FOUqA8JZCQvA9fw==", + "version": "1.2.0", "dev": true, "license": "MIT", "engines": { @@ -8067,20 +6306,14 @@ }, "node_modules/emoji-regex": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "license": "MIT" }, "node_modules/emoji-regex-xs": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -8088,9 +6321,6 @@ }, "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, "license": "MIT", "optional": true, "dependencies": { @@ -8099,8 +6329,6 @@ }, "node_modules/encoding-sniffer": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz", - "integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==", "license": "MIT", "dependencies": { "iconv-lite": "^0.6.3", @@ -8112,8 +6340,6 @@ }, "node_modules/encoding-sniffer/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==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -8124,9 +6350,6 @@ }, "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, "license": "MIT", "optional": true, "dependencies": { @@ -8138,8 +6361,6 @@ }, "node_modules/enhance-visitors": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/enhance-visitors/-/enhance-visitors-1.0.0.tgz", - "integrity": "sha512-+29eJLiUixTEDRaZ35Vu8jP3gPLNcQQkQkOQjLp2X+6cZGGPDD/uasbFzvLsJKnGZnvmyZ0srxudwOtskHeIDA==", "dev": true, "license": "MIT", "dependencies": { @@ -8151,8 +6372,6 @@ }, "node_modules/entities": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -8163,8 +6382,6 @@ }, "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==", "license": "MIT", "engines": { "node": ">=6" @@ -8172,14 +6389,10 @@ }, "node_modules/err-code": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", - "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", "license": "MIT" }, "node_modules/error-ex": { "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8187,9 +6400,7 @@ } }, "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "version": "1.24.0", "dev": true, "license": "MIT", "dependencies": { @@ -8257,8 +6468,6 @@ }, "node_modules/es-define-property": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8266,8 +6475,6 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -8275,8 +6482,6 @@ }, "node_modules/es-object-atoms": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -8287,8 +6492,6 @@ }, "node_modules/es-set-tostringtag": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", "dependencies": { @@ -8303,8 +6506,6 @@ }, "node_modules/es-to-primitive": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "license": "MIT", "dependencies": { @@ -8321,15 +6522,11 @@ }, "node_modules/es6-error": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -8366,8 +6563,6 @@ }, "node_modules/escalade": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "license": "MIT", "engines": { "node": ">=6" @@ -8375,8 +6570,6 @@ }, "node_modules/escape-goat": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", "license": "MIT", "engines": { "node": ">=12" @@ -8387,18 +6580,13 @@ }, "node_modules/escape-html": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "version": "5.0.0", "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8406,8 +6594,6 @@ }, "node_modules/escape-unicode": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/escape-unicode/-/escape-unicode-0.3.0.tgz", - "integrity": "sha512-4Lr9Prysw8FBwpW8dURr4T3/VRU4RYlhayLgy34zavplBG9bUsTtaCuM7Lw3szWTuidQvkZ2a1qJxG3e5+o99w==", "funding": [ { "type": "individual", @@ -8421,31 +6607,18 @@ "license": "MIT" }, "node_modules/escope": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escope/-/escope-4.0.0.tgz", - "integrity": "sha512-E36qlD/r6RJHVpPKArgMoMlNJzoRJFH8z/cAZlI9lbc45zB3+S7i9k6e/MNb+7bZQzNEa6r8WKN3BovpeIBwgA==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escope/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "version": "4.0.0", "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + }, "engines": { "node": ">=4.0" } }, "node_modules/eslint": { - "version": "9.39.3", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.3.tgz", - "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", + "version": "9.39.1", "dev": true, "license": "MIT", "dependencies": { @@ -8455,7 +6628,7 @@ "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.3", + "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -8504,8 +6677,6 @@ }, "node_modules/eslint-config-google": { "version": "0.14.0", - "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", - "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -8517,8 +6688,6 @@ }, "node_modules/eslint-plugin-ava": { "version": "15.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-ava/-/eslint-plugin-ava-15.1.0.tgz", - "integrity": "sha512-+6Zxk1uYW3mf7lxCLWIQsFYgn3hfuCMbsKc0MtqfloOz1F6fiV5/PaWEaLgkL1egrSQmnyR7vOFP1wSPJbVUbw==", "dev": true, "license": "MIT", "dependencies": { @@ -8540,8 +6709,6 @@ }, "node_modules/eslint-plugin-ava/node_modules/eslint-visitor-keys": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "license": "Apache-2.0", "engines": { @@ -8553,8 +6720,6 @@ }, "node_modules/eslint-plugin-ava/node_modules/espree": { "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8570,9 +6735,7 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "62.7.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.7.1.tgz", - "integrity": "sha512-4Zvx99Q7d1uggYBUX/AIjvoyqXhluGbbKrRmG8SQTLprPFg6fa293tVJH1o1GQwNe3lUydd8ZHzn37OaSncgSQ==", + "version": "62.5.4", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8587,7 +6750,7 @@ "html-entities": "^2.6.0", "object-deep-merge": "^2.0.0", "parse-imports-exports": "^0.2.4", - "semver": "^7.7.4", + "semver": "^7.7.3", "spdx-expression-parse": "^4.0.0", "to-valid-identifier": "^1.0.0" }, @@ -8595,13 +6758,38 @@ "node": "^20.19.0 || ^22.13.0 || >=24" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "version": "5.0.0", "dev": true, "license": "Apache-2.0", "engines": { @@ -8612,15 +6800,13 @@ } }, "node_modules/eslint-plugin-jsdoc/node_modules/espree": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.1.1.tgz", - "integrity": "sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ==", + "version": "11.1.0", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.16.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^20.19.0 || ^22.13.0 || >=24" @@ -8629,10 +6815,13 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-plugin-jsdoc/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, "node_modules/eslint-scope": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8646,10 +6835,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint-scope/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/eslint-utils": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", "dev": true, "license": "MIT", "dependencies": { @@ -8667,8 +6862,6 @@ }, "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -8677,8 +6870,6 @@ }, "node_modules/eslint-visitor-keys": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8687,34 +6878,156 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/ms": { + "version": "2.1.3", "dev": true, "license": "MIT" }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/esmock": { "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esmock/-/esmock-2.7.3.tgz", - "integrity": "sha512-/M/YZOjgyLaVoY6K83pwCsGE1AJQnj4S4GyXLYgi/Y79KL8EeW6WU7Rmjc89UO7jv6ec8+j34rKeWOfiLeEu0A==", "dev": true, "license": "ISC", "engines": { @@ -8723,8 +7036,6 @@ }, "node_modules/espree": { "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "license": "BSD-2-Clause", "dependencies": { "acorn": "^8.15.0", @@ -8740,8 +7051,6 @@ }, "node_modules/esprima": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, "license": "BSD-2-Clause", "bin": { @@ -8754,15 +7063,11 @@ }, "node_modules/espurify": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/espurify/-/espurify-2.1.1.tgz", - "integrity": "sha512-zttWvnkhcDyGOhSH4vO2qCBILpdCMv/MX8lp4cqgRkQoDRGK2oZxi2GfWhlP2dIXmk7BaKeOTuzbHhyC68o8XQ==", "dev": true, "license": "MIT" }, "node_modules/esquery": { "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -8772,10 +7077,16 @@ "node": ">=0.10" } }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -8784,10 +7095,15 @@ "node": ">=4.0" } }, - "node_modules/estraverse": { + "node_modules/esrecurse/node_modules/estraverse": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", "license": "BSD-2-Clause", "engines": { "node": ">=4.0" @@ -8795,14 +7111,10 @@ }, "node_modules/estree-walker": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, "node_modules/esutils": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -8811,8 +7123,6 @@ }, "node_modules/etag": { "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8820,8 +7130,6 @@ }, "node_modules/event-target-shim": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, "license": "MIT", "engines": { @@ -8830,8 +7138,6 @@ }, "node_modules/events": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "license": "MIT", "engines": { @@ -8839,9 +7145,7 @@ } }, "node_modules/execa": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", - "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "version": "9.6.0", "dev": true, "license": "MIT", "dependencies": { @@ -8867,14 +7171,10 @@ }, "node_modules/exponential-backoff": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, "node_modules/express": { "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", @@ -8904,139 +7204,30 @@ "send": "~0.19.0", "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/express/node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" }, "engines": { - "node": ">=0.6" + "node": ">= 0.10.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/express/node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/express/node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/fast-deep-equal": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, "node_modules/fast-diff": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true, "license": "Apache-2.0" }, "node_modules/fast-glob": { "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", @@ -9051,8 +7242,6 @@ }, "node_modules/fast-glob/node_modules/glob-parent": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", "dependencies": { "is-glob": "^4.0.1" @@ -9063,28 +7252,21 @@ }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, "license": "MIT" }, "node_modules/fast-safe-stringify": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, "funding": [ { "type": "github", @@ -9098,9 +7280,7 @@ "license": "BSD-3-Clause" }, "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "version": "1.19.1", "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -9108,18 +7288,22 @@ }, "node_modules/fd-package-json": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", - "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", "dev": true, "license": "MIT", "dependencies": { "walk-up-path": "^4.0.0" } }, + "node_modules/fd-package-json/node_modules/walk-up-path": { + "version": "4.0.0", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/fdir": { "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", "engines": { "node": ">=12.0.0" @@ -9135,8 +7319,6 @@ }, "node_modules/figures": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", - "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "license": "MIT", "dependencies": { "is-unicode-supported": "^2.0.0" @@ -9150,8 +7332,6 @@ }, "node_modules/file-entry-cache": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9163,8 +7343,6 @@ }, "node_modules/file-type": { "version": "18.7.0", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-18.7.0.tgz", - "integrity": "sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw==", "dev": true, "license": "MIT", "dependencies": { @@ -9181,15 +7359,11 @@ }, "node_modules/file-uri-to-path": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "dev": true, "license": "MIT" }, "node_modules/fill-range": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -9199,42 +7373,23 @@ } }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "version": "1.3.1", "license": "MIT", "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", - "on-finished": "~2.4.1", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~2.0.2", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { "node": ">= 0.8" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/find-cache-dir": { "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", "dev": true, "license": "MIT", "dependencies": { @@ -9251,8 +7406,6 @@ }, "node_modules/find-cache-dir/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -9265,8 +7418,6 @@ }, "node_modules/find-cache-dir/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -9278,8 +7429,6 @@ }, "node_modules/find-cache-dir/node_modules/make-dir": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", "dependencies": { @@ -9294,8 +7443,6 @@ }, "node_modules/find-cache-dir/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -9310,8 +7457,6 @@ }, "node_modules/find-cache-dir/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -9321,10 +7466,16 @@ "node": ">=8" } }, + "node_modules/find-cache-dir/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/find-cache-dir/node_modules/pkg-dir": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9336,8 +7487,6 @@ }, "node_modules/find-cache-dir/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -9345,17 +7494,16 @@ } }, "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "version": "7.0.0", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "locate-path": "^7.2.0", + "path-exists": "^5.0.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -9363,8 +7511,6 @@ }, "node_modules/find-up-simple": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", - "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", "license": "MIT", "engines": { "node": ">=18" @@ -9375,8 +7521,6 @@ }, "node_modules/flat-cache": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { @@ -9388,25 +7532,19 @@ } }, "node_modules/flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.3.3", "dev": true, "license": "ISC" }, "node_modules/focus-trap": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", - "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "version": "7.6.6", "license": "MIT", "dependencies": { - "tabbable": "^6.4.0" + "tabbable": "^6.3.0" } }, "node_modules/for-each": { "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { @@ -9421,8 +7559,6 @@ }, "node_modules/foreground-child": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "license": "ISC", "dependencies": { "cross-spawn": "^7.0.6", @@ -9437,8 +7573,6 @@ }, "node_modules/form-data": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, "license": "MIT", "dependencies": { @@ -9454,8 +7588,6 @@ }, "node_modules/formatly": { "version": "0.3.0", - "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", - "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", "dev": true, "license": "MIT", "dependencies": { @@ -9470,8 +7602,6 @@ }, "node_modules/formidable": { "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { @@ -9488,8 +7618,6 @@ }, "node_modules/forwarded": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -9497,8 +7625,6 @@ }, "node_modules/fraction.js": { "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "license": "MIT", "engines": { "node": "*" @@ -9510,8 +7636,6 @@ }, "node_modules/fresh": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -9519,8 +7643,6 @@ }, "node_modules/fromentries": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", "dev": true, "funding": [ { @@ -9540,8 +7662,6 @@ }, "node_modules/fs-minipass": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-3.0.3.tgz", - "integrity": "sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==", "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -9552,16 +7672,11 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, "license": "MIT", "optional": true, "os": [ @@ -9573,8 +7688,6 @@ }, "node_modules/function-bind": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9582,8 +7695,6 @@ }, "node_modules/function.prototype.name": { "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, "license": "MIT", "dependencies": { @@ -9603,8 +7714,6 @@ }, "node_modules/functions-have-names": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true, "license": "MIT", "funding": { @@ -9613,8 +7722,6 @@ }, "node_modules/generator-function": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", "dev": true, "license": "MIT", "engines": { @@ -9623,8 +7730,6 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, "license": "MIT", "engines": { @@ -9633,17 +7738,13 @@ }, "node_modules/get-caller-file": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "version": "1.4.0", "license": "MIT", "engines": { "node": ">=18" @@ -9654,8 +7755,6 @@ }, "node_modules/get-intrinsic": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -9678,8 +7777,6 @@ }, "node_modules/get-package-type": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true, "license": "MIT", "engines": { @@ -9688,8 +7785,6 @@ }, "node_modules/get-port": { "version": "6.1.2", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-6.1.2.tgz", - "integrity": "sha512-BrGGraKm2uPqurfGVj/z97/zv8dPleC6x9JBNRTrDNtCkkRF4rPwrQXFgL7+I+q8QSdU4ntLQX2D7KIxSy8nGw==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -9700,8 +7795,6 @@ }, "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -9713,8 +7806,6 @@ }, "node_modules/get-stdin": { "version": "9.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", - "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", "dev": true, "license": "MIT", "engines": { @@ -9726,8 +7817,6 @@ }, "node_modules/get-stream": { "version": "9.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", - "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "license": "MIT", "dependencies": { @@ -9743,8 +7832,6 @@ }, "node_modules/get-symbol-description": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "license": "MIT", "dependencies": { @@ -9761,9 +7848,6 @@ }, "node_modules/git-raw-commits": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-4.0.0.tgz", - "integrity": "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ==", - "deprecated": "This package is no longer maintained. For the JavaScript API, please use @conventional-changelog/git-client instead.", "dev": true, "license": "MIT", "dependencies": { @@ -9778,31 +7862,16 @@ "node": ">=16" } }, - "node_modules/git-raw-commits/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16.10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { - "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "version": "13.0.0", "license": "BlueOak-1.0.0", "dependencies": { - "minimatch": "^10.2.2", - "minipass": "^7.1.3", - "path-scurry": "^2.0.2" + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "path-scurry": "^2.0.0" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -9810,8 +7879,6 @@ }, "node_modules/glob-parent": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "license": "ISC", "dependencies": { "is-glob": "^4.0.3" @@ -9820,25 +7887,8 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/global-directory": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", "license": "MIT", "dependencies": { "ini": "4.1.1" @@ -9852,8 +7902,6 @@ }, "node_modules/global-directory/node_modules/ini": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -9861,8 +7909,6 @@ }, "node_modules/globals": { "version": "17.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", - "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true, "license": "MIT", "engines": { @@ -9874,8 +7920,6 @@ }, "node_modules/globalthis": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9891,8 +7935,6 @@ }, "node_modules/globby": { "version": "14.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-14.1.0.tgz", - "integrity": "sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==", "license": "MIT", "dependencies": { "@sindresorhus/merge-streams": "^2.1.0", @@ -9911,8 +7953,6 @@ }, "node_modules/globby/node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", - "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", "license": "MIT", "engines": { "node": ">=18" @@ -9923,17 +7963,23 @@ }, "node_modules/globby/node_modules/ignore": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "license": "MIT", "engines": { "node": ">= 4" } }, + "node_modules/globby/node_modules/unicorn-magic": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -9944,20 +7990,14 @@ }, "node_modules/graceful-fs": { "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, "node_modules/handle-thing": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", "license": "MIT" }, "node_modules/handlebars": { "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, "license": "MIT", "dependencies": { @@ -9978,8 +8018,6 @@ }, "node_modules/has-bigints": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", "engines": { @@ -9991,8 +8029,6 @@ }, "node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, "license": "MIT", "engines": { @@ -10001,8 +8037,6 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", "dependencies": { @@ -10014,8 +8048,6 @@ }, "node_modules/has-proto": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10030,8 +8062,6 @@ }, "node_modules/has-symbols": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -10042,8 +8072,6 @@ }, "node_modules/has-tostringtag": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", "dependencies": { @@ -10058,8 +8086,6 @@ }, "node_modules/hasha": { "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10075,8 +8101,6 @@ }, "node_modules/hasha/node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, "license": "MIT", "engines": { @@ -10088,8 +8112,6 @@ }, "node_modules/hasown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -10100,8 +8122,6 @@ }, "node_modules/hast-util-to-html": { "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -10123,8 +8143,6 @@ }, "node_modules/hast-util-whitespace": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -10136,14 +8154,10 @@ }, "node_modules/hookable": { "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", "license": "MIT" }, "node_modules/hosted-git-info": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-9.0.2.tgz", - "integrity": "sha512-M422h7o/BR3rmCQ8UHi7cyyMqKltdP9Uo+J2fXK+RSAY+wTcKOIRyhTuKv4qn+DJf3g+PL890AzId5KZpX+CBg==", "license": "ISC", "dependencies": { "lru-cache": "^11.1.0" @@ -10152,19 +8166,8 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/hosted-git-info/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/hpack.js": { "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", "license": "MIT", "dependencies": { "inherits": "^2.0.1", @@ -10175,8 +8178,6 @@ }, "node_modules/hpack.js/node_modules/readable-stream": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -10190,14 +8191,10 @@ }, "node_modules/hpack.js/node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, "node_modules/hpack.js/node_modules/string_decoder": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -10205,8 +8202,6 @@ }, "node_modules/html-entities": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", - "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", "dev": true, "funding": [ { @@ -10222,15 +8217,11 @@ }, "node_modules/html-escaper": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, "license": "MIT" }, "node_modules/html-void-elements": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", "license": "MIT", "funding": { "type": "github", @@ -10239,8 +8230,6 @@ }, "node_modules/htmlparser2": { "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", "funding": [ "https://github.com/fb55/htmlparser2?sponsor=1", { @@ -10258,66 +8247,58 @@ }, "node_modules/http-cache-semantics": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", "license": "BSD-2-Clause" }, "node_modules/http-deceiver": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", "license": "MIT" }, "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "version": "2.0.0", "license": "MIT", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" }, "engines": { "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, - "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==", + "node_modules/https-proxy-agent": { + "version": "7.0.6", "license": "MIT", "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" + "agent-base": "^7.1.2", + "debug": "4" }, "engines": { "node": ">= 14" } }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.4.3", "license": "MIT", "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" + "ms": "^2.1.3" }, "engines": { - "node": ">= 14" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, "node_modules/human-signals": { "version": "8.0.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", - "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", "dev": true, "license": "Apache-2.0", "engines": { @@ -10326,8 +8307,6 @@ }, "node_modules/husky": { "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { @@ -10341,25 +8320,17 @@ } }, "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "version": "0.4.24", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, "engines": { "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" } }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "funding": [ { @@ -10379,8 +8350,6 @@ }, "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, "license": "MIT", "engines": { @@ -10389,8 +8358,6 @@ }, "node_modules/ignore-by-default": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-2.1.0.tgz", - "integrity": "sha512-yiWd4GVmJp0Q6ghmM2B/V3oZGRmjrKLXvHR3TE1nfoXsmoggllfZUQe74EN0fJdPFZu2NIvNdrMMLm3OsV7Ohw==", "dev": true, "license": "ISC", "engines": { @@ -10399,8 +8366,6 @@ }, "node_modules/ignore-walk": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-8.0.0.tgz", - "integrity": "sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==", "license": "ISC", "dependencies": { "minimatch": "^10.0.3" @@ -10409,25 +8374,8 @@ "node": "^20.17.0 || >=22.9.0" } }, - "node_modules/ignore-walk/node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/import-fresh": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10443,8 +8391,6 @@ }, "node_modules/import-fresh/node_modules/resolve-from": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, "license": "MIT", "engines": { @@ -10453,8 +8399,6 @@ }, "node_modules/import-local": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", "license": "MIT", "dependencies": { "pkg-dir": "^4.2.0", @@ -10472,8 +8416,6 @@ }, "node_modules/import-local/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "license": "MIT", "dependencies": { "locate-path": "^5.0.0", @@ -10485,8 +8427,6 @@ }, "node_modules/import-local/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "license": "MIT", "dependencies": { "p-locate": "^4.1.0" @@ -10497,8 +8437,6 @@ }, "node_modules/import-local/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "license": "MIT", "dependencies": { "p-try": "^2.0.0" @@ -10512,8 +8450,6 @@ }, "node_modules/import-local/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "license": "MIT", "dependencies": { "p-limit": "^2.2.0" @@ -10522,10 +8458,15 @@ "node": ">=8" } }, + "node_modules/import-local/node_modules/path-exists": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/import-local/node_modules/pkg-dir": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "license": "MIT", "dependencies": { "find-up": "^4.0.0" @@ -10536,8 +8477,6 @@ }, "node_modules/import-meta-resolve": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", - "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", "dev": true, "license": "MIT", "funding": { @@ -10547,8 +8486,6 @@ }, "node_modules/import-modules": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/import-modules/-/import-modules-2.1.0.tgz", - "integrity": "sha512-8HEWcnkbGpovH9yInoisxaSoIg9Brbul+Ju3Kqe2UsYDUBJD/iQjSgEj0zPcTDPKfPp2fs5xlv1i+JSye/m1/A==", "dev": true, "license": "MIT", "engines": { @@ -10560,8 +8497,6 @@ }, "node_modules/imurmurhash": { "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "license": "MIT", "engines": { "node": ">=0.8.19" @@ -10569,8 +8504,6 @@ }, "node_modules/indent-string": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", - "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==", "dev": true, "license": "MIT", "engines": { @@ -10582,8 +8515,6 @@ }, "node_modules/index-to-position": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/index-to-position/-/index-to-position-1.2.0.tgz", - "integrity": "sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==", "license": "MIT", "engines": { "node": ">=18" @@ -10594,9 +8525,6 @@ }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "license": "ISC", "dependencies": { @@ -10606,23 +8534,17 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, "node_modules/ini": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", - "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", + "version": "5.0.0", "license": "ISC", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/internal-slot": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "license": "MIT", "dependencies": { @@ -10636,8 +8558,6 @@ }, "node_modules/ip-address": { "version": "10.1.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", - "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "license": "MIT", "engines": { "node": ">= 12" @@ -10645,8 +8565,6 @@ }, "node_modules/ipaddr.js": { "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { "node": ">= 0.10" @@ -10654,8 +8572,6 @@ }, "node_modules/irregular-plurals": { "version": "3.5.0", - "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-3.5.0.tgz", - "integrity": "sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==", "dev": true, "license": "MIT", "engines": { @@ -10664,8 +8580,6 @@ }, "node_modules/is-array-buffer": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "license": "MIT", "dependencies": { @@ -10682,15 +8596,11 @@ }, "node_modules/is-arrayish": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", "dev": true, "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10709,8 +8619,6 @@ }, "node_modules/is-bigint": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -10725,8 +8633,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -10737,8 +8643,6 @@ }, "node_modules/is-boolean-object": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { @@ -10754,8 +8658,6 @@ }, "node_modules/is-callable": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, "license": "MIT", "engines": { @@ -10767,8 +8669,6 @@ }, "node_modules/is-core-module": { "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -10782,8 +8682,6 @@ }, "node_modules/is-data-view": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "license": "MIT", "dependencies": { @@ -10800,8 +8698,6 @@ }, "node_modules/is-date-object": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { @@ -10815,25 +8711,8 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -10841,8 +8720,6 @@ }, "node_modules/is-finalizationregistry": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "license": "MIT", "dependencies": { @@ -10857,8 +8734,6 @@ }, "node_modules/is-fullwidth-code-point": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, "license": "MIT", "engines": { @@ -10870,8 +8745,6 @@ }, "node_modules/is-generator-function": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", "dev": true, "license": "MIT", "dependencies": { @@ -10890,8 +8763,6 @@ }, "node_modules/is-glob": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -10902,8 +8773,6 @@ }, "node_modules/is-in-ci": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-in-ci/-/is-in-ci-1.0.0.tgz", - "integrity": "sha512-eUuAjybVTHMYWm/U+vBO1sY/JOCgoPCXRxzdju0K+K0BiGW0SChEL1MLC0PoCIR1OlPo5YAp8HuQoUlsWEICwg==", "license": "MIT", "bin": { "is-in-ci": "cli.js" @@ -10917,8 +8786,6 @@ }, "node_modules/is-inside-container": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", "license": "MIT", "dependencies": { "is-docker": "^3.0.0" @@ -10933,10 +8800,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-inside-container/node_modules/is-docker": { + "version": "3.0.0", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-installed-globally": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-1.0.0.tgz", - "integrity": "sha512-K55T22lfpQ63N4KEN57jZUAaAYqYHEe8veb/TycJRk9DdSCLLcovXz/mL6mOnhQaZsQGwPhuFopdQIlqGSEjiQ==", "license": "MIT", "dependencies": { "global-directory": "^4.0.1", @@ -10951,15 +8829,11 @@ }, "node_modules/is-lambda": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", - "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", "dev": true, "license": "MIT" }, "node_modules/is-map": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", "dev": true, "license": "MIT", "engines": { @@ -10971,8 +8845,6 @@ }, "node_modules/is-negative-zero": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", "dev": true, "license": "MIT", "engines": { @@ -10984,8 +8856,6 @@ }, "node_modules/is-npm": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", - "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -10996,8 +8866,6 @@ }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -11005,8 +8873,6 @@ }, "node_modules/is-number-like": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/is-number-like/-/is-number-like-1.0.8.tgz", - "integrity": "sha512-6rZi3ezCyFcn5L71ywzz2bS5b2Igl1En3eTlZlvKjpz1n3IZLAYMbKYAIQgFmEu0GENg92ziU/faEOA/aixjbA==", "license": "ISC", "dependencies": { "lodash.isfinite": "^3.3.2" @@ -11014,8 +8880,6 @@ }, "node_modules/is-number-object": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { @@ -11031,8 +8895,6 @@ }, "node_modules/is-obj": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", "dev": true, "license": "MIT", "engines": { @@ -11041,8 +8903,6 @@ }, "node_modules/is-path-inside": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", - "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "license": "MIT", "engines": { "node": ">=12" @@ -11053,8 +8913,6 @@ }, "node_modules/is-plain-obj": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", - "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, "license": "MIT", "engines": { @@ -11066,8 +8924,6 @@ }, "node_modules/is-plain-object": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", "dev": true, "license": "MIT", "engines": { @@ -11076,14 +8932,10 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, "node_modules/is-regex": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { @@ -11101,8 +8953,6 @@ }, "node_modules/is-regexp": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", "dev": true, "license": "MIT", "engines": { @@ -11111,8 +8961,6 @@ }, "node_modules/is-set": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", "dev": true, "license": "MIT", "engines": { @@ -11124,8 +8972,6 @@ }, "node_modules/is-shared-array-buffer": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", "dependencies": { @@ -11140,8 +8986,6 @@ }, "node_modules/is-stream": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", - "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "license": "MIT", "engines": { @@ -11153,8 +8997,6 @@ }, "node_modules/is-string": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "license": "MIT", "dependencies": { @@ -11170,8 +9012,6 @@ }, "node_modules/is-symbol": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { @@ -11186,10 +9026,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-text-path": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "text-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-typed-array": { "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11204,15 +9053,11 @@ }, "node_modules/is-typedarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "dev": true, "license": "MIT" }, "node_modules/is-unicode-supported": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", - "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "license": "MIT", "engines": { "node": ">=18" @@ -11223,8 +9068,6 @@ }, "node_modules/is-weakmap": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", "dev": true, "license": "MIT", "engines": { @@ -11236,8 +9079,6 @@ }, "node_modules/is-weakref": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", "dev": true, "license": "MIT", "dependencies": { @@ -11252,8 +9093,6 @@ }, "node_modules/is-weakset": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11269,8 +9108,6 @@ }, "node_modules/is-what": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", "license": "MIT", "engines": { "node": ">=18" @@ -11281,48 +9118,25 @@ }, "node_modules/is-windows": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-wsl": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz", - "integrity": "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, "node_modules/isexe": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz", - "integrity": "sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw==", - "license": "BlueOak-1.0.0", + "version": "3.1.1", + "license": "ISC", "engines": { - "node": ">=20" + "node": ">=16" } }, "node_modules/isobject": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", "dev": true, "license": "MIT", "dependencies": { @@ -11334,8 +9148,6 @@ }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -11344,8 +9156,6 @@ }, "node_modules/istanbul-lib-hook": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -11357,8 +9167,6 @@ }, "node_modules/istanbul-lib-instrument": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -11374,8 +9182,6 @@ }, "node_modules/istanbul-lib-processinfo": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", "dev": true, "license": "ISC", "dependencies": { @@ -11392,9 +9198,6 @@ }, "node_modules/istanbul-lib-processinfo/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -11412,10 +9215,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/istanbul-lib-processinfo/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/istanbul-lib-processinfo/node_modules/p-map": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11427,9 +9239,6 @@ }, "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -11444,8 +9253,6 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -11459,8 +9266,6 @@ }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -11472,10 +9277,29 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-source-maps/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/istanbul-lib-source-maps/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, "node_modules/istanbul-reports": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", - "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -11488,8 +9312,6 @@ }, "node_modules/jackspeak": { "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" @@ -11503,8 +9325,6 @@ }, "node_modules/jiti": { "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", "devOptional": true, "license": "MIT", "bin": { @@ -11513,8 +9333,6 @@ }, "node_modules/js-beautify": { "version": "1.15.4", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz", - "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==", "dev": true, "license": "MIT", "dependencies": { @@ -11535,25 +9353,14 @@ }, "node_modules/js-beautify/node_modules/abbrev": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz", - "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==", "dev": true, "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, - "node_modules/js-beautify/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/js-beautify/node_modules/brace-expansion": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11562,9 +9369,6 @@ }, "node_modules/js-beautify/node_modules/glob": { "version": "10.5.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", - "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -11584,19 +9388,15 @@ }, "node_modules/js-beautify/node_modules/lru-cache": { "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true, "license": "ISC" }, "node_modules/js-beautify/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "9.0.5", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^2.0.1" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -11607,8 +9407,6 @@ }, "node_modules/js-beautify/node_modules/nopt": { "version": "7.2.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz", - "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==", "dev": true, "license": "ISC", "dependencies": { @@ -11623,8 +9421,6 @@ }, "node_modules/js-beautify/node_modules/path-scurry": { "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -11640,8 +9436,6 @@ }, "node_modules/js-cookie": { "version": "3.0.5", - "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", - "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", "dev": true, "license": "MIT", "engines": { @@ -11650,8 +9444,6 @@ }, "node_modules/js-string-escape": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", - "integrity": "sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg==", "dev": true, "license": "MIT", "engines": { @@ -11660,14 +9452,10 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -11678,8 +9466,6 @@ }, "node_modules/js2xmlparser": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", "license": "Apache-2.0", "dependencies": { "xmlcreate": "^2.0.4" @@ -11687,8 +9473,6 @@ }, "node_modules/jsdoc": { "version": "4.0.5", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.5.tgz", - "integrity": "sha512-P4C6MWP9yIlMiK8nwoZvxN84vb6MsnXcHuy7XzVOvQoCizWX5JFCBsWIIWKXBltpoRZXddUOVQmCTOZt9yDj9g==", "license": "Apache-2.0", "dependencies": { "@babel/parser": "^7.20.15", @@ -11716,8 +9500,6 @@ }, "node_modules/jsdoc-type-pratt-parser": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.1.1.tgz", - "integrity": "sha512-/2uqY7x6bsrpi3i9LVU6J89352C0rpMk0as8trXxCtvd4kPk1ke/Eyif6wqfSLvoNJqcDG9Vk4UsXgygzCt2xA==", "dev": true, "license": "MIT", "engines": { @@ -11726,8 +9508,6 @@ }, "node_modules/jsdoc/node_modules/escape-string-regexp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "license": "MIT", "engines": { "node": ">=8" @@ -11735,8 +9515,6 @@ }, "node_modules/jsesc": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -11748,37 +9526,27 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-parse-even-better-errors": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-5.0.0.tgz", - "integrity": "sha512-ZF1nxZ28VhQouRWhUcVlUIN3qwSgPuswK05s/HIaoetAoE/9tngVmCHjSxmSQPav1nd+lPtTL0YZ/2AFdR/iYQ==", "license": "MIT", "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "version": "0.4.1", "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, "node_modules/json-stringify-nice": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", - "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", "license": "ISC", "funding": { "url": "https://github.com/sponsors/isaacs" @@ -11786,8 +9554,6 @@ }, "node_modules/json5": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, "license": "MIT", "bin": { @@ -11799,29 +9565,36 @@ }, "node_modules/jsonparse": { "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", "engines": [ "node >= 0.2.0" ], "license": "MIT" }, + "node_modules/JSONStream": { + "version": "1.3.5", + "dev": true, + "license": "(MIT OR Apache-2.0)", + "dependencies": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + }, + "bin": { + "JSONStream": "bin.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/just-diff": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-6.0.2.tgz", - "integrity": "sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==", "license": "MIT" }, "node_modules/just-diff-apply": { "version": "5.5.0", - "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-5.5.0.tgz", - "integrity": "sha512-OYTthRfSh55WOItVqwpefPtNt2VdKsq5AnAK6apdtR6yCH8pr0CmSr710J0Mf+WdQy7K/OzMy7K2MgAfdQURDw==", "license": "MIT" }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { @@ -11830,8 +9603,6 @@ }, "node_modules/klaw": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", "license": "MIT", "dependencies": { "graceful-fs": "^4.1.9" @@ -11839,8 +9610,6 @@ }, "node_modules/knip": { "version": "5.85.0", - "resolved": "https://registry.npmjs.org/knip/-/knip-5.85.0.tgz", - "integrity": "sha512-V2kyON+DZiYdNNdY6GALseiNCwX7dYdpz9Pv85AUn69Gk0UKCts+glOKWfe5KmaMByRjM9q17Mzj/KinTVOyxg==", "dev": true, "funding": [ { @@ -11881,8 +9650,6 @@ }, "node_modules/knip/node_modules/strip-json-comments": { "version": "5.0.3", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", - "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", "dev": true, "license": "MIT", "engines": { @@ -11893,9 +9660,7 @@ } }, "node_modules/ky": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-1.14.3.tgz", - "integrity": "sha512-9zy9lkjac+TR1c2tG+mkNSVlyOpInnWdSMiue4F+kq8TwJSgv6o8jhLRg8Ho6SnZ9wOYUq/yozts9qQCfk7bIw==", + "version": "1.14.0", "license": "MIT", "engines": { "node": ">=18" @@ -11906,8 +9671,6 @@ }, "node_modules/latest-version": { "version": "9.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-9.0.0.tgz", - "integrity": "sha512-7W0vV3rqv5tokqkBAFV1LbR7HPOWzXQDpDgEuib/aJ1jsZZx6x3c2mBI+TJhJzOhkGeaLbCKEHXEXLfirtG2JA==", "license": "MIT", "dependencies": { "package-json": "^10.0.0" @@ -11921,8 +9684,6 @@ }, "node_modules/less-openui5": { "version": "0.11.6", - "resolved": "https://registry.npmjs.org/less-openui5/-/less-openui5-0.11.6.tgz", - "integrity": "sha512-sQmU+G2pJjFfzRI+XtXkk+T9G0s6UmWWUfOW0utPR46C9lfhNr4DH1lNJuImj64reXYi+vOwyNxPRkj0F3mofA==", "license": "Apache-2.0", "dependencies": { "@adobe/css-tools": "^4.0.2", @@ -11936,8 +9697,6 @@ }, "node_modules/levn": { "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11950,8 +9709,6 @@ }, "node_modules/licensee": { "version": "11.1.1", - "resolved": "https://registry.npmjs.org/licensee/-/licensee-11.1.1.tgz", - "integrity": "sha512-FpgdKKjvJULlBqYiKtrK7J4Oo7sQO1lHQTUOcxxE4IPQccx6c0tJWMgwVdG46+rPnLPSV7EWD6eWUtAjGO52Lg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11976,8 +9733,6 @@ }, "node_modules/lilconfig": { "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "license": "MIT", "engines": { "node": ">=14" @@ -11988,8 +9743,6 @@ }, "node_modules/line-column": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/line-column/-/line-column-1.0.2.tgz", - "integrity": "sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww==", "dev": true, "license": "MIT", "dependencies": { @@ -11999,14 +9752,10 @@ }, "node_modules/lines-and-columns": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "license": "MIT" }, "node_modules/linkify-it": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", "license": "MIT", "dependencies": { "uc.micro": "^2.0.0" @@ -12014,8 +9763,6 @@ }, "node_modules/load-json-file": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", - "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", "dev": true, "license": "MIT", "engines": { @@ -12026,16 +9773,14 @@ } }, "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "version": "7.2.0", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^5.0.0" + "p-locate": "^6.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -12043,8 +9788,6 @@ }, "node_modules/lockfile": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lockfile/-/lockfile-1.0.4.tgz", - "integrity": "sha512-cvbTwETRfsFh4nHsL1eGWapU1XFi5Ot9E85sWAwia7Y7EgB7vfqcZhTKZ+l7hCGxSPoushMv5GKhT5PdLv03WA==", "license": "ISC", "dependencies": { "signal-exit": "^3.0.2" @@ -12052,104 +9795,78 @@ }, "node_modules/lockfile/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "license": "ISC" }, "node_modules/lodash": { "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.camelcase": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true, "license": "MIT" }, "node_modules/lodash.flattendeep": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", "dev": true, "license": "MIT" }, "node_modules/lodash.isfinite": { "version": "3.3.2", - "resolved": "https://registry.npmjs.org/lodash.isfinite/-/lodash.isfinite-3.3.2.tgz", - "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "dev": true, "license": "MIT" }, "node_modules/lodash.kebabcase": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", - "integrity": "sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==", "dev": true, "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "license": "MIT" }, "node_modules/lodash.merge": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, "license": "MIT" }, "node_modules/lodash.mergewith": { "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", "dev": true, "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", - "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", "dev": true, "license": "MIT" }, "node_modules/lodash.startcase": { "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz", - "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==", "dev": true, "license": "MIT" }, "node_modules/lodash.uniq": { "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", "license": "MIT" }, "node_modules/lodash.upperfirst": { "version": "4.3.1", - "resolved": "https://registry.npmjs.org/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", - "integrity": "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==", "dev": true, "license": "MIT" }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, + "version": "11.2.2", "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "engines": { + "node": "20 || >=22" } }, "node_modules/magic-string": { "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -12157,8 +9874,6 @@ }, "node_modules/make-dir": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { @@ -12172,12 +9887,9 @@ } }, "node_modules/make-fetch-happen": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-15.0.4.tgz", - "integrity": "sha512-vM2sG+wbVeVGYcCm16mM3d5fuem9oC28n436HjsGO3LcxoTI8LNVa4rwZDn3f76+cWyT4GGJDxjTYU1I2nr6zw==", + "version": "15.0.3", "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/agent": "^4.0.0", "cacache": "^20.0.1", "http-cache-semantics": "^4.1.1", @@ -12187,22 +9899,48 @@ "minipass-pipeline": "^1.2.4", "negotiator": "^1.0.0", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "ssri": "^13.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/make-fetch-happen/node_modules/minipass-fetch": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/make-fetch-happen/node_modules/proc-log": { + "version": "6.1.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/mark.js": { "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", "license": "MIT" }, "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "version": "14.1.0", "license": "MIT", "dependencies": { "argparse": "^2.0.1", @@ -12218,8 +9956,6 @@ }, "node_modules/markdown-it-anchor": { "version": "8.6.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", - "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", "license": "Unlicense", "peerDependencies": { "@types/markdown-it": "*", @@ -12228,8 +9964,6 @@ }, "node_modules/markdown-it-implicit-figures": { "version": "0.12.0", - "resolved": "https://registry.npmjs.org/markdown-it-implicit-figures/-/markdown-it-implicit-figures-0.12.0.tgz", - "integrity": "sha512-IeD2V74f3ZBYrZ+bz/9uEGii0S61BYoD2731qsHTgYLlENUrTevlgODScScS1CK44/TV9ddlufGHCYCQueh1rw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12237,8 +9971,6 @@ }, "node_modules/marked": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", - "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", "license": "MIT", "bin": { "marked": "bin/marked.js" @@ -12249,8 +9981,6 @@ }, "node_modules/matcher": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/matcher/-/matcher-5.0.0.tgz", - "integrity": "sha512-s2EMBOWtXFc8dgqvoAzKJXxNHibcdJMV0gwqKUaw9E2JBJuGUK7DrNKrA6g/i+v72TT16+6sVm5mS3thaMLQUw==", "dev": true, "license": "MIT", "dependencies": { @@ -12263,23 +9993,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/matcher/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -12287,8 +10002,6 @@ }, "node_modules/md5-hex": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz", - "integrity": "sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw==", "dev": true, "license": "MIT", "dependencies": { @@ -12300,8 +10013,6 @@ }, "node_modules/mdast-util-to-hast": { "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -12320,30 +10031,22 @@ } }, "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "version": "2.12.2", "license": "CC0-1.0" }, "node_modules/mdurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "version": "0.3.0", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.6" } }, "node_modules/memoize": { "version": "10.2.0", - "resolved": "https://registry.npmjs.org/memoize/-/memoize-10.2.0.tgz", - "integrity": "sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==", "dev": true, "license": "MIT", "dependencies": { @@ -12357,13 +10060,11 @@ } }, "node_modules/meow": { - "version": "13.2.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", - "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "version": "12.1.1", "dev": true, "license": "MIT", "engines": { - "node": ">=18" + "node": ">=16.10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -12371,8 +10072,6 @@ }, "node_modules/merge-descriptors": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -12380,8 +10079,6 @@ }, "node_modules/merge2": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", "engines": { "node": ">= 8" @@ -12389,8 +10086,6 @@ }, "node_modules/methods": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -12398,15 +10093,11 @@ }, "node_modules/micro-spelling-correcter": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/micro-spelling-correcter/-/micro-spelling-correcter-1.1.1.tgz", - "integrity": "sha512-lkJ3Rj/mtjlRcHk6YyCbvZhyWTOzdBvTHsxMmZSk5jxN1YyVSQ+JETAom55mdzfcyDrY/49Z7UCW760BK30crg==", "dev": true, "license": "CC0-1.0" }, "node_modules/micromark-util-character": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", "funding": [ { "type": "GitHub Sponsors", @@ -12425,8 +10116,6 @@ }, "node_modules/micromark-util-encode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", "funding": [ { "type": "GitHub Sponsors", @@ -12441,8 +10130,6 @@ }, "node_modules/micromark-util-sanitize-uri": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", "funding": [ { "type": "GitHub Sponsors", @@ -12462,8 +10149,6 @@ }, "node_modules/micromark-util-symbol": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", "funding": [ { "type": "GitHub Sponsors", @@ -12478,8 +10163,6 @@ }, "node_modules/micromark-util-types": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", "funding": [ { "type": "GitHub Sponsors", @@ -12494,8 +10177,6 @@ }, "node_modules/micromatch": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -12507,8 +10188,6 @@ }, "node_modules/micromatch/node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -12519,8 +10198,6 @@ }, "node_modules/mime": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", "license": "MIT", "bin": { "mime": "cli.js" @@ -12531,8 +10208,6 @@ }, "node_modules/mime-db": { "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -12540,8 +10215,6 @@ }, "node_modules/mime-types": { "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -12552,8 +10225,6 @@ }, "node_modules/mime-types/node_modules/mime-db": { "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -12561,8 +10232,6 @@ }, "node_modules/mimic-function": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, "license": "MIT", "engines": { @@ -12574,63 +10243,37 @@ }, "node_modules/minimalistic-assert": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", "license": "ISC" }, "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", + "version": "10.1.1", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@isaacs/brace-expansion": "^5.0.0" }, "engines": { - "node": "*" - } - }, - "node_modules/minimatch/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/minimatch/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/minimist": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", - "license": "BlueOak-1.0.0", + "version": "7.1.2", + "license": "ISC", "engines": { "node": ">=16 || 14 >=14.17" } }, "node_modules/minipass-collect": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-2.0.1.tgz", - "integrity": "sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==", "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -12639,27 +10282,8 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-fetch": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-5.0.2.tgz", - "integrity": "sha512-2d0q2a8eCi2IRg/IGubCNRJoYbA1+YPXAzQVRFmB45gdGZafyivnZ5YSEfo3JikbjGxOdntGFvBQGqaSMXlAFQ==", - "license": "MIT", - "dependencies": { - "minipass": "^7.0.3", - "minipass-sized": "^2.0.0", - "minizlib": "^3.0.1" - }, - "engines": { - "node": "^20.17.0 || >=22.9.0" - }, - "optionalDependencies": { - "iconv-lite": "^0.7.2" - } - }, "node_modules/minipass-flush": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -12670,8 +10294,6 @@ }, "node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -12682,14 +10304,10 @@ }, "node_modules/minipass-flush/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, "node_modules/minipass-pipeline": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", "license": "ISC", "dependencies": { "minipass": "^3.0.0" @@ -12700,8 +10318,6 @@ }, "node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -12712,32 +10328,38 @@ }, "node_modules/minipass-pipeline/node_modules/yallist": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, "node_modules/minipass-sized": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-2.0.0.tgz", - "integrity": "sha512-zSsHhto5BcUVM2m1LurnXY6M//cGhVaegT71OfOXoprxT6o780GZd792ea6FfrQkuU4usHZIUczAQMRUE2plzA==", + "version": "1.0.3", "license": "ISC", "dependencies": { - "minipass": "^7.1.2" + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" }, "engines": { "node": ">=8" } }, + "node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" + }, "node_modules/minisearch": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", - "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", "license": "MIT" }, "node_modules/minizlib": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", "dependencies": { "minipass": "^7.1.2" @@ -12748,14 +10370,10 @@ }, "node_modules/mitt": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", "license": "MIT" }, "node_modules/mkdirp": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -12765,15 +10383,11 @@ } }, "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==", + "version": "2.0.0", "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -12783,8 +10397,6 @@ }, "node_modules/nanoid": { "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", "funding": [ { "type": "github", @@ -12801,15 +10413,11 @@ }, "node_modules/natural-compare": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "version": "0.6.4", "license": "MIT", "engines": { "node": ">= 0.6" @@ -12817,15 +10425,11 @@ }, "node_modules/neo-async": { "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, "license": "MIT" }, "node_modules/node-fetch": { "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "license": "MIT", "dependencies": { @@ -12844,9 +10448,7 @@ } }, "node_modules/node-gyp": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-12.2.0.tgz", - "integrity": "sha512-q23WdzrQv48KozXlr0U1v9dwO/k59NHeSzn6loGcasyf0UnSrtzs8kRxM+mfwJSf0DkX0s43hcqgnSO4/VNthQ==", + "version": "12.1.0", "license": "MIT", "dependencies": { "env-paths": "^2.2.0", @@ -12856,7 +10458,7 @@ "nopt": "^9.0.0", "proc-log": "^6.0.0", "semver": "^7.3.5", - "tar": "^7.5.4", + "tar": "^7.5.2", "tinyglobby": "^0.2.12", "which": "^6.0.0" }, @@ -12869,8 +10471,6 @@ }, "node_modules/node-gyp-build": { "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "dev": true, "license": "MIT", "bin": { @@ -12879,10 +10479,35 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp/node_modules/abbrev": { + "version": "4.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/nopt": { + "version": "9.0.0", + "license": "ISC", + "dependencies": { + "abbrev": "^4.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/node-gyp/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/node-preload": { "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12893,15 +10518,11 @@ } }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.27", "license": "MIT" }, "node_modules/node-stream-zip": { "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", "license": "MIT", "engines": { "node": ">=0.12.0" @@ -12913,8 +10534,6 @@ }, "node_modules/nofilter": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", - "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", "dev": true, "license": "MIT", "engines": { @@ -12922,38 +10541,53 @@ } }, "node_modules/nopt": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-9.0.0.tgz", - "integrity": "sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==", + "version": "8.1.0", "license": "ISC", "dependencies": { - "abbrev": "^4.0.0" + "abbrev": "^3.0.0" }, "bin": { "nopt": "bin/nopt.js" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/normalize-package-data": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-8.0.0.tgz", - "integrity": "sha512-RWk+PI433eESQ7ounYxIp67CYuVsS1uYSonX3kA6ps/3LWfjVQa/ptEg6Y3T6uAMq1mWpX9PQ+qx+QaHpsc7gQ==", + "version": "6.0.2", "license": "BSD-2-Clause", "dependencies": { - "hosted-git-info": "^9.0.0", + "hosted-git-info": "^7.0.0", "semver": "^7.3.5", "validate-npm-package-license": "^3.0.4" }, "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/normalize-package-data/node_modules/hosted-git-info": { + "version": "7.0.2", + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" } }, + "node_modules/normalize-package-data/node_modules/lru-cache": { + "version": "10.4.3", + "license": "ISC" + }, "node_modules/normalize-path": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12961,8 +10595,6 @@ }, "node_modules/npm-bundled": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-5.0.0.tgz", - "integrity": "sha512-JLSpbzh6UUXIEoqPsYBvVNVmyrjVZ1fzEFbqxKkTJQkWBO3xFzFT+KDnSKQWwOQNbuWRwt5LSD6HOTLGIWzfrw==", "license": "ISC", "dependencies": { "npm-normalize-package-bin": "^5.0.0" @@ -12973,8 +10605,6 @@ }, "node_modules/npm-install-checks": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-8.0.0.tgz", - "integrity": "sha512-ScAUdMpyzkbpxoNekQ3tNRdFI8SJ86wgKZSQZdUxT+bj0wVFpsEMWnkXP0twVe1gJyNF5apBWDJhhIbgrIViRA==", "license": "BSD-2-Clause", "dependencies": { "semver": "^7.1.1" @@ -12985,15 +10615,11 @@ }, "node_modules/npm-license-corrections": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/npm-license-corrections/-/npm-license-corrections-1.9.0.tgz", - "integrity": "sha512-9Tq6y6zop5lsZy6dInbgrCLnqtuN+3jBc9NCusKjbeQL4LRudDkvmCYyInsDOaKN7GIVbBSvDto5MnEqYXVhxQ==", "dev": true, "license": "CC0-1.0" }, "node_modules/npm-normalize-package-bin": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-5.0.0.tgz", - "integrity": "sha512-CJi3OS4JLsNMmr2u07OJlhcrPxCeOeP/4xq67aWNai6TNWWbTrlNDgl8NcFKVlcBKp18GPj+EzbNIgrBfZhsag==", "license": "ISC", "engines": { "node": "^20.17.0 || >=22.9.0" @@ -13001,8 +10627,6 @@ }, "node_modules/npm-package-arg": { "version": "13.0.2", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-13.0.2.tgz", - "integrity": "sha512-IciCE3SY3uE84Ld8WZU23gAPPV9rIYod4F+rc+vJ7h7cwAJt9Vk6TVsK60ry7Uj3SRS3bqRRIGuTp9YVlk6WNA==", "license": "ISC", "dependencies": { "hosted-git-info": "^9.0.0", @@ -13014,10 +10638,15 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm-package-arg/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/npm-packlist": { - "version": "10.0.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-10.0.4.tgz", - "integrity": "sha512-uMW73iajD8hiH4ZBxEV3HC+eTnppIqwakjOYuvgddnalIw2lJguKviK1pcUJDlIWm1wSJkchpDZDSVVsZEYRng==", + "version": "10.0.3", "license": "ISC", "dependencies": { "ignore-walk": "^8.0.0", @@ -13027,10 +10656,15 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm-packlist/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/npm-pick-manifest": { "version": "11.0.3", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-11.0.3.tgz", - "integrity": "sha512-buzyCfeoGY/PxKqmBqn1IUJrZnUi1VVJTdSSRPGI60tJdUhUoSQFhs0zycJokDdOznQentgrpf8LayEHyyYlqQ==", "license": "ISC", "dependencies": { "npm-install-checks": "^8.0.0", @@ -13044,8 +10678,6 @@ }, "node_modules/npm-registry-fetch": { "version": "19.1.1", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-19.1.1.tgz", - "integrity": "sha512-TakBap6OM1w0H73VZVDf44iFXsOS3h+L4wVMXmbWOQroZgFhMch0juN6XSzBNlD965yIKvWg2dfu7NSiaYLxtw==", "license": "ISC", "dependencies": { "@npmcli/redact": "^4.0.0", @@ -13061,10 +10693,30 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/npm-registry-fetch/node_modules/minipass-fetch": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm-registry-fetch/node_modules/proc-log": { + "version": "6.0.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/npm-run-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", - "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "license": "MIT", "dependencies": { @@ -13080,8 +10732,6 @@ }, "node_modules/npm-run-path/node_modules/path-key": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true, "license": "MIT", "engines": { @@ -13091,10 +10741,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npm-run-path/node_modules/unicorn-magic": { + "version": "0.3.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/nth-check": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", "license": "BSD-2-Clause", "dependencies": { "boolbase": "^1.0.0" @@ -13105,8 +10764,6 @@ }, "node_modules/nyc": { "version": "17.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-17.1.0.tgz", - "integrity": "sha512-U42vQ4czpKa0QdI1hu950XuNhYqgoM+ZF1HT+VuUHL9hPfDPVvNQyltmMqdE9bUHMVa+8yNbc3QKTj8zQhlVxQ==", "dev": true, "license": "ISC", "dependencies": { @@ -13147,8 +10804,6 @@ }, "node_modules/nyc/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", "engines": { @@ -13157,8 +10812,6 @@ }, "node_modules/nyc/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { @@ -13171,10 +10824,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/nyc/node_modules/camelcase": { + "version": "5.3.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/nyc/node_modules/cliui": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", "dev": true, "license": "ISC", "dependencies": { @@ -13185,15 +10844,11 @@ }, "node_modules/nyc/node_modules/convert-source-map": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "dev": true, "license": "MIT" }, "node_modules/nyc/node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "license": "MIT", "dependencies": { @@ -13206,9 +10861,6 @@ }, "node_modules/nyc/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -13228,8 +10880,6 @@ }, "node_modules/nyc/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "license": "MIT", "dependencies": { @@ -13241,8 +10891,6 @@ }, "node_modules/nyc/node_modules/make-dir": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", "dependencies": { @@ -13255,10 +10903,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nyc/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/nyc/node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "license": "MIT", "dependencies": { @@ -13273,8 +10930,6 @@ }, "node_modules/nyc/node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "license": "MIT", "dependencies": { @@ -13284,24 +10939,27 @@ "node": ">=8" } }, - "node_modules/nyc/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/path-exists": { + "version": "4.0.0", "dev": true, "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, "engines": { "node": ">=8" } }, "node_modules/nyc/node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -13316,8 +10974,6 @@ }, "node_modules/nyc/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -13326,15 +10982,11 @@ }, "node_modules/nyc/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, "node_modules/nyc/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { @@ -13346,8 +10998,6 @@ }, "node_modules/nyc/node_modules/wrap-ansi": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", "dev": true, "license": "MIT", "dependencies": { @@ -13361,15 +11011,11 @@ }, "node_modules/nyc/node_modules/y18n": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "dev": true, "license": "ISC" }, "node_modules/nyc/node_modules/yargs": { "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "license": "MIT", "dependencies": { @@ -13391,8 +11037,6 @@ }, "node_modules/nyc/node_modules/yargs-parser": { "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", "dev": true, "license": "ISC", "dependencies": { @@ -13405,8 +11049,6 @@ }, "node_modules/object-assign": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -13414,15 +11056,11 @@ }, "node_modules/object-deep-merge": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.0.tgz", - "integrity": "sha512-3DC3UMpeffLTHiuXSy/UG4NOIYTLlY9u3V82+djSCLYClWobZiS4ivYzpIUWrRY/nfsJ8cWsKyG3QfyLePmhvg==", "dev": true, "license": "MIT" }, "node_modules/object-hash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", "license": "MIT", "engines": { "node": ">= 6" @@ -13430,8 +11068,6 @@ }, "node_modules/object-inspect": { "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -13442,8 +11078,6 @@ }, "node_modules/object-keys": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", "dev": true, "license": "MIT", "engines": { @@ -13452,8 +11086,6 @@ }, "node_modules/object.assign": { "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "license": "MIT", "dependencies": { @@ -13473,14 +11105,10 @@ }, "node_modules/obuf": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", "license": "MIT" }, "node_modules/on-finished": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -13491,8 +11119,6 @@ }, "node_modules/on-headers": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -13500,8 +11126,6 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "license": "ISC", "dependencies": { @@ -13510,8 +11134,6 @@ }, "node_modules/oniguruma-to-es": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", - "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", "license": "MIT", "dependencies": { "emoji-regex-xs": "^1.0.0", @@ -13519,28 +11141,8 @@ "regex-recursion": "^6.0.2" } }, - "node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/open-cli": { "version": "8.0.0", - "resolved": "https://registry.npmjs.org/open-cli/-/open-cli-8.0.0.tgz", - "integrity": "sha512-3muD3BbfLyzl+aMVSEfn2FfOqGdPYR0O4KNnxXsLEPE2q9OSjBfJAaB6XKbrUzLgymoSMejvb5jpXJfru/Ko2A==", "dev": true, "license": "MIT", "dependencies": { @@ -13560,14 +11162,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open-cli/node_modules/meow": { - "version": "12.1.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-12.1.1.tgz", - "integrity": "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==", + "node_modules/open-cli/node_modules/define-lazy-prop": { + "version": "3.0.0", "dev": true, "license": "MIT", "engines": { - "node": ">=16.10" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open-cli/node_modules/open": { + "version": "10.2.0", + "dev": true, + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13575,8 +11192,6 @@ }, "node_modules/optionator": { "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -13593,8 +11208,6 @@ }, "node_modules/own-keys": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", "dev": true, "license": "MIT", "dependencies": { @@ -13611,8 +11224,6 @@ }, "node_modules/oxc-resolver": { "version": "11.19.1", - "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.19.1.tgz", - "integrity": "sha512-qE/CIg/spwrTBFt5aKmwe3ifeDdLfA2NESN30E42X/lII5ClF8V7Wt6WIJhcGZjp0/Q+nQ+9vgxGk//xZNX2hg==", "dev": true, "license": "MIT", "funding": { @@ -13642,32 +11253,28 @@ } }, "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "version": "4.0.0", "dev": true, "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "yocto-queue": "^1.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "version": "6.0.0", "dev": true, "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13675,8 +11282,6 @@ }, "node_modules/p-map": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", - "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", "license": "MIT", "engines": { "node": ">=18" @@ -13687,8 +11292,6 @@ }, "node_modules/p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "license": "MIT", "engines": { "node": ">=6" @@ -13696,8 +11299,6 @@ }, "node_modules/package-config": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/package-config/-/package-config-5.0.0.tgz", - "integrity": "sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==", "dev": true, "license": "MIT", "dependencies": { @@ -13713,8 +11314,6 @@ }, "node_modules/package-hash": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", "dev": true, "license": "ISC", "dependencies": { @@ -13729,8 +11328,6 @@ }, "node_modules/package-json": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-10.0.1.tgz", - "integrity": "sha512-ua1L4OgXSBdsu1FPb7F3tYH0F48a6kxvod4pLUlGY9COeJAJQNX/sNH2IiEmsxw7lqYiAwrdHMjz1FctOsyDQg==", "license": "MIT", "dependencies": { "ky": "^1.2.0", @@ -13747,17 +11344,12 @@ }, "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, "node_modules/pacote": { - "version": "21.4.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-21.4.0.tgz", - "integrity": "sha512-DR7mn7HUOomAX1BORnpYy678qVIidbvOojkBscqy27dRKN+s/hLeQT1MeYYrx1Cxh62jyKjiWiDV7RTTqB+ZEQ==", + "version": "21.0.4", "license": "ISC", "dependencies": { - "@gar/promise-retry": "^1.0.0", "@npmcli/git": "^7.0.0", "@npmcli/installed-package-contents": "^4.0.0", "@npmcli/package-json": "^7.0.0", @@ -13771,6 +11363,7 @@ "npm-pick-manifest": "^11.0.1", "npm-registry-fetch": "^19.0.0", "proc-log": "^6.0.0", + "promise-retry": "^2.0.1", "sigstore": "^4.0.0", "ssri": "^13.0.0", "tar": "^7.4.3" @@ -13782,10 +11375,15 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/pacote/node_modules/proc-log": { + "version": "6.1.0", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/parent-module": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "license": "MIT", "dependencies": { @@ -13797,8 +11395,6 @@ }, "node_modules/parent-module/node_modules/callsites": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { @@ -13807,8 +11403,6 @@ }, "node_modules/parse-conflict-json": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-5.0.1.tgz", - "integrity": "sha512-ZHEmNKMq1wyJXNwLxyHnluPfRAFSIliBvbK/UiOceROt4Xh9Pz0fq49NytIaeaCUf5VR86hwQ/34FCcNU5/LKQ==", "license": "ISC", "dependencies": { "json-parse-even-better-errors": "^5.0.0", @@ -13821,8 +11415,6 @@ }, "node_modules/parse-imports-exports": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", - "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", "dev": true, "license": "MIT", "dependencies": { @@ -13831,8 +11423,6 @@ }, "node_modules/parse-json": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "license": "MIT", "dependencies": { @@ -13850,15 +11440,11 @@ }, "node_modules/parse-json/node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true, "license": "MIT" }, "node_modules/parse-ms": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", - "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, "license": "MIT", "engines": { @@ -13870,15 +11456,11 @@ }, "node_modules/parse-statements": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", - "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", "dev": true, "license": "MIT" }, "node_modules/parse5": { "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", "license": "MIT", "dependencies": { "entities": "^6.0.0" @@ -13889,8 +11471,6 @@ }, "node_modules/parse5-htmlparser2-tree-adapter": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", "license": "MIT", "dependencies": { "domhandler": "^5.0.3", @@ -13902,8 +11482,6 @@ }, "node_modules/parse5-parser-stream": { "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", "license": "MIT", "dependencies": { "parse5": "^7.0.0" @@ -13914,8 +11492,6 @@ }, "node_modules/parse5/node_modules/entities": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -13926,26 +11502,21 @@ }, "node_modules/parseurl": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "version": "5.0.0", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "license": "MIT", "engines": { @@ -13954,8 +11525,6 @@ }, "node_modules/path-key": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "license": "MIT", "engines": { "node": ">=8" @@ -13963,45 +11532,28 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, "node_modules/path-scurry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "version": "2.0.1", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.6", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", - "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, "node_modules/path-to-regexp": { "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "license": "MIT" }, "node_modules/path-type": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-6.0.0.tgz", - "integrity": "sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==", "license": "MIT", "engines": { "node": ">=18" @@ -14012,8 +11564,6 @@ }, "node_modules/peek-readable": { "version": "5.4.2", - "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.4.2.tgz", - "integrity": "sha512-peBp3qZyuS6cNIJ2akRNG1uo1WJ1d0wTxg/fxMdZ0BqCVhx242bSFHM9eNqflfJVS9SsgkzgT/1UgnsurBOTMg==", "dev": true, "license": "MIT", "engines": { @@ -14026,20 +11576,14 @@ }, "node_modules/perfect-debounce": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", "license": "MIT" }, "node_modules/picocolors": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", "engines": { "node": ">=12" @@ -14050,8 +11594,6 @@ }, "node_modules/pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14059,8 +11601,6 @@ }, "node_modules/pirates": { "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "license": "MIT", "engines": { "node": ">= 6" @@ -14068,8 +11608,6 @@ }, "node_modules/pkg-dir": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz", - "integrity": "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA==", "dev": true, "license": "MIT", "dependencies": { @@ -14079,10 +11617,84 @@ "node": ">=10" } }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "6.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/path-exists": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/plur": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/plur/-/plur-5.1.0.tgz", - "integrity": "sha512-VP/72JeXqak2KiOzjgKtQen5y3IZHn+9GOuLDafPv0eXa47xq0At93XahYBs26MsifCQ4enGKwbjBTKgb9QJXg==", "dev": true, "license": "MIT", "dependencies": { @@ -14097,8 +11709,6 @@ }, "node_modules/portscanner": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/portscanner/-/portscanner-2.2.0.tgz", - "integrity": "sha512-IFroCz/59Lqa2uBvzK3bKDbDDIEaAY8XJ1jFxcLWTqosrsc32//P4VuSB2vZXoHiHqOmx8B5L5hnKOxL/7FlPw==", "license": "MIT", "dependencies": { "async": "^2.6.0", @@ -14111,8 +11721,6 @@ }, "node_modules/possible-typed-array-names": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, "license": "MIT", "engines": { @@ -14120,9 +11728,7 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.6", "funding": [ { "type": "opencollective", @@ -14149,8 +11755,6 @@ }, "node_modules/postcss-calc": { "version": "10.1.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-10.1.1.tgz", - "integrity": "sha512-NYEsLHh8DgG/PRH2+G9BTuUdtf9ViS+vdoQ0YA5OQdGsfN4ztiwtDWNtBl9EKeqNMFnIu8IKZ0cLxEQ5r5KVMw==", "license": "MIT", "dependencies": { "postcss-selector-parser": "^7.0.0", @@ -14165,8 +11769,6 @@ }, "node_modules/postcss-colormin": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-7.0.5.tgz", - "integrity": "sha512-ekIBP/nwzRWhEMmIxHHbXHcMdzd1HIUzBECaj5KEdLz9DVP2HzT065sEhvOx1dkLjYW7jyD0CngThx6bpFi2fA==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -14183,8 +11785,6 @@ }, "node_modules/postcss-convert-values": { "version": "7.0.8", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-7.0.8.tgz", - "integrity": "sha512-+XNKuPfkHTCEo499VzLMYn94TiL3r9YqRE3Ty+jP7UX4qjewUONey1t7CG21lrlTLN07GtGM8MqFVp86D4uKJg==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -14199,8 +11799,6 @@ }, "node_modules/postcss-discard-comments": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-7.0.5.tgz", - "integrity": "sha512-IR2Eja8WfYgN5n32vEGSctVQ1+JARfu4UH8M7bgGh1bC+xI/obsPJXaBpQF7MAByvgwZinhpHpdrmXtvVVlKcQ==", "license": "MIT", "dependencies": { "postcss-selector-parser": "^7.1.0" @@ -14214,8 +11812,6 @@ }, "node_modules/postcss-discard-duplicates": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-7.0.2.tgz", - "integrity": "sha512-eTonaQvPZ/3i1ASDHOKkYwAybiM45zFIc7KXils4mQmHLqIswXD9XNOKEVxtTFnsmwYzF66u4LMgSr0abDlh5w==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -14226,8 +11822,6 @@ }, "node_modules/postcss-discard-empty": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-7.0.1.tgz", - "integrity": "sha512-cFrJKZvcg/uxB6Ijr4l6qmn3pXQBna9zyrPC+sK0zjbkDUZew+6xDltSF7OeB7rAtzaaMVYSdbod+sZOCWnMOg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -14238,8 +11832,6 @@ }, "node_modules/postcss-discard-overridden": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-7.0.1.tgz", - "integrity": "sha512-7c3MMjjSZ/qYrx3uc1940GSOzN1Iqjtlqe8uoSg+qdVPYyRb0TILSqqmtlSFuE4mTDECwsm397Ya7iXGzfF7lg==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -14250,8 +11842,6 @@ }, "node_modules/postcss-import": { "version": "15.1.0", - "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", - "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.0.0", @@ -14267,8 +11857,6 @@ }, "node_modules/postcss-js": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", - "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", "funding": [ { "type": "opencollective", @@ -14292,8 +11880,6 @@ }, "node_modules/postcss-load-config": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", - "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "funding": [ { "type": "opencollective", @@ -14334,8 +11920,6 @@ }, "node_modules/postcss-merge-longhand": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-7.0.5.tgz", - "integrity": "sha512-Kpu5v4Ys6QI59FxmxtNB/iHUVDn9Y9sYw66D6+SZoIk4QTz1prC4aYkhIESu+ieG1iylod1f8MILMs1Em3mmIw==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -14350,8 +11934,6 @@ }, "node_modules/postcss-merge-rules": { "version": "7.0.7", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-7.0.7.tgz", - "integrity": "sha512-njWJrd/Ms6XViwowaaCc+/vqhPG3SmXn725AGrnl+BgTuRPEacjiLEaGq16J6XirMJbtKkTwnt67SS+e2WGoew==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -14368,8 +11950,6 @@ }, "node_modules/postcss-minify-font-values": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-7.0.1.tgz", - "integrity": "sha512-2m1uiuJeTplll+tq4ENOQSzB8LRnSUChBv7oSyFLsJRtUgAAJGP6LLz0/8lkinTgxrmJSPOEhgY1bMXOQ4ZXhQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14383,8 +11963,6 @@ }, "node_modules/postcss-minify-gradients": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-7.0.1.tgz", - "integrity": "sha512-X9JjaysZJwlqNkJbUDgOclyG3jZEpAMOfof6PUZjPnPrePnPG62pS17CjdM32uT1Uq1jFvNSff9l7kNbmMSL2A==", "license": "MIT", "dependencies": { "colord": "^2.9.3", @@ -14400,8 +11978,6 @@ }, "node_modules/postcss-minify-params": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-7.0.5.tgz", - "integrity": "sha512-FGK9ky02h6Ighn3UihsyeAH5XmLEE2MSGH5Tc4tXMFtEDx7B+zTG6hD/+/cT+fbF7PbYojsmmWjyTwFwW1JKQQ==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -14417,8 +11993,6 @@ }, "node_modules/postcss-minify-selectors": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-7.0.5.tgz", - "integrity": "sha512-x2/IvofHcdIrAm9Q+p06ZD1h6FPcQ32WtCRVodJLDR+WMn8EVHI1kvLxZuGKz/9EY5nAmI6lIQIrpo4tBy5+ug==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14433,8 +12007,6 @@ }, "node_modules/postcss-nested": { "version": "6.2.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", - "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "funding": [ { "type": "opencollective", @@ -14458,8 +12030,6 @@ }, "node_modules/postcss-nested/node_modules/postcss-selector-parser": { "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==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14471,8 +12041,6 @@ }, "node_modules/postcss-normalize-charset": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-7.0.1.tgz", - "integrity": "sha512-sn413ofhSQHlZFae//m9FTOfkmiZ+YQXsbosqOWRiVQncU2BA3daX3n0VF3cG6rGLSFVc5Di/yns0dFfh8NFgQ==", "license": "MIT", "engines": { "node": "^18.12.0 || ^20.9.0 || >=22.0" @@ -14483,8 +12051,6 @@ }, "node_modules/postcss-normalize-display-values": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-7.0.1.tgz", - "integrity": "sha512-E5nnB26XjSYz/mGITm6JgiDpAbVuAkzXwLzRZtts19jHDUBFxZ0BkXAehy0uimrOjYJbocby4FVswA/5noOxrQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14498,8 +12064,6 @@ }, "node_modules/postcss-normalize-positions": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-7.0.1.tgz", - "integrity": "sha512-pB/SzrIP2l50ZIYu+yQZyMNmnAcwyYb9R1fVWPRxm4zcUFCY2ign7rcntGFuMXDdd9L2pPNUgoODDk91PzRZuQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14513,8 +12077,6 @@ }, "node_modules/postcss-normalize-repeat-style": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-7.0.1.tgz", - "integrity": "sha512-NsSQJ8zj8TIDiF0ig44Byo3Jk9e4gNt9x2VIlJudnQQ5DhWAHJPF4Tr1ITwyHio2BUi/I6Iv0HRO7beHYOloYQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14528,8 +12090,6 @@ }, "node_modules/postcss-normalize-string": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-7.0.1.tgz", - "integrity": "sha512-QByrI7hAhsoze992kpbMlJSbZ8FuCEc1OT9EFbZ6HldXNpsdpZr+YXC5di3UEv0+jeZlHbZcoCADgb7a+lPmmQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14543,8 +12103,6 @@ }, "node_modules/postcss-normalize-timing-functions": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-7.0.1.tgz", - "integrity": "sha512-bHifyuuSNdKKsnNJ0s8fmfLMlvsQwYVxIoUBnowIVl2ZAdrkYQNGVB4RxjfpvkMjipqvbz0u7feBZybkl/6NJg==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14558,8 +12116,6 @@ }, "node_modules/postcss-normalize-unicode": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-7.0.5.tgz", - "integrity": "sha512-X6BBwiRxVaFHrb2WyBMddIeB5HBjJcAaUHyhLrM2FsxSq5TFqcHSsK7Zu1otag+o0ZphQGJewGH1tAyrD0zX1Q==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -14574,8 +12130,6 @@ }, "node_modules/postcss-normalize-url": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-7.0.1.tgz", - "integrity": "sha512-sUcD2cWtyK1AOL/82Fwy1aIVm/wwj5SdZkgZ3QiUzSzQQofrbq15jWJ3BA7Z+yVRwamCjJgZJN0I9IS7c6tgeQ==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14589,8 +12143,6 @@ }, "node_modules/postcss-normalize-whitespace": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-7.0.1.tgz", - "integrity": "sha512-vsbgFHMFQrJBJKrUFJNZ2pgBeBkC2IvvoHjz1to0/0Xk7sII24T0qFOiJzG6Fu3zJoq/0yI4rKWi7WhApW+EFA==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14604,8 +12156,6 @@ }, "node_modules/postcss-ordered-values": { "version": "7.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-7.0.2.tgz", - "integrity": "sha512-AMJjt1ECBffF7CEON/Y0rekRLS6KsePU6PRP08UqYW4UGFRnTXNrByUzYK1h8AC7UWTZdQ9O3Oq9kFIhm0SFEw==", "license": "MIT", "dependencies": { "cssnano-utils": "^5.0.1", @@ -14620,8 +12170,6 @@ }, "node_modules/postcss-reduce-initial": { "version": "7.0.5", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-7.0.5.tgz", - "integrity": "sha512-RHagHLidG8hTZcnr4FpyMB2jtgd/OcyAazjMhoy5qmWJOx1uxKh4ntk0Pb46ajKM0rkf32lRH4C8c9qQiPR6IA==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -14636,8 +12184,6 @@ }, "node_modules/postcss-reduce-transforms": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-7.0.1.tgz", - "integrity": "sha512-MhyEbfrm+Mlp/36hvZ9mT9DaO7dbncU0CvWI8V93LRkY6IYlu38OPg3FObnuKTUxJ4qA8HpurdQOo5CyqqO76g==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0" @@ -14650,9 +12196,7 @@ } }, "node_modules/postcss-selector-parser": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", - "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "version": "7.1.0", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -14664,8 +12208,6 @@ }, "node_modules/postcss-svgo": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-7.1.0.tgz", - "integrity": "sha512-KnAlfmhtoLz6IuU3Sij2ycusNs4jPW+QoFE5kuuUOK8awR6tMxZQrs5Ey3BUz7nFCzT3eqyFgqkyrHiaU2xx3w==", "license": "MIT", "dependencies": { "postcss-value-parser": "^4.2.0", @@ -14680,8 +12222,6 @@ }, "node_modules/postcss-unique-selectors": { "version": "7.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-7.0.4.tgz", - "integrity": "sha512-pmlZjsmEAG7cHd7uK3ZiNSW6otSZ13RHuZ/4cDN/bVglS5EpF2r2oxY99SuOHa8m7AWoBCelTS3JPpzsIs8skQ==", "license": "MIT", "dependencies": { "postcss-selector-parser": "^7.1.0" @@ -14695,14 +12235,10 @@ }, "node_modules/postcss-value-parser": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, "node_modules/preact": { - "version": "10.28.4", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.4.tgz", - "integrity": "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ==", + "version": "10.27.2", "license": "MIT", "funding": { "type": "opencollective", @@ -14711,8 +12247,6 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -14721,17 +12255,10 @@ }, "node_modules/pretty-data": { "version": "0.40.0", - "resolved": "https://registry.npmjs.org/pretty-data/-/pretty-data-0.40.0.tgz", - "integrity": "sha512-YFLnEdDEDnkt/GEhet5CYZHCvALw6+Elyb/tp8kQG03ZSIuzeaDWpZYndCXwgqu4NAjh1PI534dhDS1mHarRnQ==", - "license": "MIT", - "engines": { - "node": "*" - } + "license": "MIT" }, "node_modules/pretty-hrtime": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -14739,8 +12266,6 @@ }, "node_modules/pretty-ms": { "version": "9.3.0", - "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", - "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", "dev": true, "license": "MIT", "dependencies": { @@ -14754,18 +12279,14 @@ } }, "node_modules/proc-log": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", - "integrity": "sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==", + "version": "5.0.0", "license": "ISC", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, "license": "MIT", "engines": { @@ -14774,14 +12295,10 @@ }, "node_modules/process-nextick-args": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, "node_modules/process-on-spawn": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", - "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -14792,18 +12309,14 @@ } }, "node_modules/proggy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/proggy/-/proggy-4.0.0.tgz", - "integrity": "sha512-MbA4R+WQT76ZBm/5JUpV9yqcJt92175+Y0Bodg3HgiXzrmKu7Ggq+bpn6y6wHH+gN9NcyKn3yg1+d47VaKwNAQ==", + "version": "3.0.0", "license": "ISC", "engines": { - "node": "^20.17.0 || >=22.9.0" + "node": "^18.17.0 || >=20.5.0" } }, "node_modules/promise-all-reject-late": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", - "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", "license": "ISC", "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14811,8 +12324,6 @@ }, "node_modules/promise-call-limit": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-3.0.2.tgz", - "integrity": "sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==", "license": "ISC", "funding": { "url": "https://github.com/sponsors/isaacs" @@ -14820,15 +12331,11 @@ }, "node_modules/promise-inflight": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "dev": true, "license": "ISC" }, "node_modules/promise-retry": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", - "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", "license": "MIT", "dependencies": { "err-code": "^2.0.2", @@ -14838,19 +12345,8 @@ "node": ">=10" } }, - "node_modules/promise-retry/node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/property-information": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", "license": "MIT", "funding": { "type": "github", @@ -14859,14 +12355,10 @@ }, "node_modules/proto-list": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "license": "ISC" }, "node_modules/proxy-addr": { "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -14878,8 +12370,6 @@ }, "node_modules/punycode": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "license": "MIT", "engines": { "node": ">=6" @@ -14887,8 +12377,6 @@ }, "node_modules/punycode.js": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", "license": "MIT", "engines": { "node": ">=6" @@ -14896,8 +12384,6 @@ }, "node_modules/pupa": { "version": "3.3.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", - "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", "license": "MIT", "dependencies": { "escape-goat": "^4.0.0" @@ -14910,9 +12396,7 @@ } }, "node_modules/qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.14.1", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -14926,8 +12410,6 @@ }, "node_modules/queue-microtask": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", "funding": [ { "type": "github", @@ -14946,8 +12428,6 @@ }, "node_modules/quibble": { "version": "0.9.2", - "resolved": "https://registry.npmjs.org/quibble/-/quibble-0.9.2.tgz", - "integrity": "sha512-BrL7hrZcbyyt5ZDfePkGFDc3m82uUtxCPOnpRUrkOdtBnmV9ldQKxXORkKL8eIzToRNaCpIPyKyfdfq/tBlFAA==", "dev": true, "license": "MIT", "dependencies": { @@ -14960,8 +12440,6 @@ }, "node_modules/random-int": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/random-int/-/random-int-3.1.0.tgz", - "integrity": "sha512-h8CRz8cpvzj0hC/iH/1Gapgcl2TQ6xtnCpyOI5WvWfXf/yrDx2DOU+tD9rX23j36IF11xg1KqB9W11Z18JPMdw==", "license": "MIT", "engines": { "node": ">=12" @@ -14972,32 +12450,51 @@ }, "node_modules/range-parser": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "version": "2.5.3", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", + "iconv-lite": "~0.4.24", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/http-errors": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/raw-body/node_modules/statuses": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, "node_modules/rc": { "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", "dependencies": { "deep-extend": "^0.6.0", @@ -15011,14 +12508,10 @@ }, "node_modules/rc/node_modules/ini": { "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, "node_modules/rc/node_modules/strip-json-comments": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15026,8 +12519,6 @@ }, "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==", "license": "MIT", "dependencies": { "pify": "^2.3.0" @@ -15035,8 +12526,6 @@ }, "node_modules/read-cmd-shim": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-6.0.0.tgz", - "integrity": "sha512-1zM5HuOfagXCBWMN83fuFI/x+T/UhZ7k+KIzhrHXcQoeX5+7gmaDYjELQHmmzIodumBHeByBJT4QYS7ufAgs7A==", "license": "ISC", "engines": { "node": "^20.17.0 || >=22.9.0" @@ -15044,8 +12533,6 @@ }, "node_modules/read-package-json-fast": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-3.0.2.tgz", - "integrity": "sha512-0J+Msgym3vrLOUB3hzQCuZHII0xkNGCtz/HJH9xZshwv9DbDwkw1KaE3gx/e2J5rpEY5rtOy6cyhKOPrkP7FZw==", "dev": true, "license": "ISC", "dependencies": { @@ -15058,8 +12545,6 @@ }, "node_modules/read-package-json-fast/node_modules/json-parse-even-better-errors": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-3.0.2.tgz", - "integrity": "sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==", "dev": true, "license": "MIT", "engines": { @@ -15068,8 +12553,6 @@ }, "node_modules/read-package-json-fast/node_modules/npm-normalize-package-bin": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", - "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", "dev": true, "license": "ISC", "engines": { @@ -15078,8 +12561,6 @@ }, "node_modules/read-package-up": { "version": "11.0.0", - "resolved": "https://registry.npmjs.org/read-package-up/-/read-package-up-11.0.0.tgz", - "integrity": "sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==", "license": "MIT", "dependencies": { "find-up-simple": "^1.0.0", @@ -15093,112 +12574,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-package-up/node_modules/hosted-git-info": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz", - "integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==", - "license": "ISC", - "dependencies": { - "lru-cache": "^10.0.1" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/read-package-up/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "license": "ISC" - }, - "node_modules/read-package-up/node_modules/normalize-package-data": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-6.0.2.tgz", - "integrity": "sha512-V6gygoYb/5EmNI+MEGrWkC+e6+Rr7mTmfHrxDbLzxQogBkgzo76rkok0Am6thgSF7Mv2nLOajAJj5vDJZEFn7g==", - "license": "BSD-2-Clause", - "dependencies": { - "hosted-git-info": "^7.0.0", - "semver": "^7.3.5", - "validate-npm-package-license": "^3.0.4" - }, - "engines": { - "node": "^16.14.0 || >=18.0.0" - } - }, - "node_modules/read-package-up/node_modules/parse-json": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "index-to-position": "^1.1.0", - "type-fest": "^4.39.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-package-up/node_modules/read-pkg": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-9.0.1.tgz", - "integrity": "sha512-9viLL4/n1BJUCT1NXVTdS1jtm80yDEgR5T4yCelII49Mbj0v1rZdKqj7zCiYdbB0CuCgdrvHcNogAKTFPBocFA==", - "license": "MIT", - "dependencies": { - "@types/normalize-package-data": "^2.4.3", - "normalize-package-data": "^6.0.0", - "parse-json": "^8.0.0", - "type-fest": "^4.6.0", - "unicorn-magic": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/read-package-up/node_modules/type-fest": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-package-up/node_modules/unicorn-magic": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", - "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", - "license": "MIT", + "version": "4.41.0", + "license": "(MIT OR CC0-1.0)", "engines": { - "node": ">=18" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/read-pkg": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-10.1.0.tgz", - "integrity": "sha512-I8g2lArQiP78ll51UeMZojewtYgIRCKCWqZEgOO8c/uefTI+XDXvCSXu3+YNUaTNvZzobrL5+SqHjBrByRRTdg==", + "version": "9.0.1", "license": "MIT", "dependencies": { - "@types/normalize-package-data": "^2.4.4", - "normalize-package-data": "^8.0.0", - "parse-json": "^8.3.0", - "type-fest": "^5.4.4", - "unicorn-magic": "^0.4.0" + "@types/normalize-package-data": "^2.4.3", + "normalize-package-data": "^6.0.0", + "parse-json": "^8.0.0", + "type-fest": "^4.6.0", + "unicorn-magic": "^0.1.0" }, "engines": { - "node": ">=20" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -15206,8 +12603,6 @@ }, "node_modules/read-pkg/node_modules/parse-json": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-8.3.0.tgz", - "integrity": "sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -15221,10 +12616,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/parse-json/node_modules/type-fest": { + "node_modules/read-pkg/node_modules/type-fest": { "version": "4.41.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.41.0.tgz", - "integrity": "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==", "license": "(MIT OR CC0-1.0)", "engines": { "node": ">=16" @@ -15233,37 +12626,8 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.4.4.tgz", - "integrity": "sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==", - "license": "(MIT OR CC0-1.0)", - "dependencies": { - "tagged-tag": "^1.0.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/unicorn-magic": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", - "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", - "license": "MIT", - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/readable-stream": { "version": "4.7.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", - "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", "dev": true, "license": "MIT", "dependencies": { @@ -15279,8 +12643,6 @@ }, "node_modules/readable-web-to-node-stream": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", - "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", "dev": true, "license": "MIT", "dependencies": { @@ -15296,8 +12658,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -15308,8 +12668,6 @@ }, "node_modules/readdirp/node_modules/picomatch": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "license": "MIT", "engines": { "node": ">=8.6" @@ -15320,8 +12678,6 @@ }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, "license": "MIT", "dependencies": { @@ -15342,9 +12698,7 @@ } }, "node_modules/regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", - "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "version": "6.0.1", "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -15352,8 +12706,6 @@ }, "node_modules/regex-recursion": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", "license": "MIT", "dependencies": { "regex-utilities": "^2.3.0" @@ -15361,14 +12713,10 @@ }, "node_modules/regex-utilities": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", "license": "MIT" }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", "dev": true, "license": "MIT", "dependencies": { @@ -15387,12 +12735,10 @@ } }, "node_modules/registry-auth-token": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.1.tgz", - "integrity": "sha512-P7B4+jq8DeD2nMsAcdfaqHbssgHtZ7Z5+++a5ask90fvmJ8p5je4mOa+wzu+DB4vQ5tdJV/xywY+UnVFeQLV5Q==", + "version": "5.1.0", "license": "MIT", "dependencies": { - "@pnpm/npm-conf": "^3.0.2" + "@pnpm/npm-conf": "^2.1.0" }, "engines": { "node": ">=14" @@ -15400,8 +12746,6 @@ }, "node_modules/registry-url": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", "license": "MIT", "dependencies": { "rc": "1.2.8" @@ -15415,8 +12759,6 @@ }, "node_modules/release-zalgo": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", "dev": true, "license": "ISC", "dependencies": { @@ -15426,60 +12768,8 @@ "node": ">=4" } }, - "node_modules/replacestream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", - "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", - "license": "BSD-3-Clause", - "dependencies": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/replacestream/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/replacestream/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/replacestream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/replacestream/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/require-directory": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15487,8 +12777,7 @@ }, "node_modules/require-from-string": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15496,15 +12785,11 @@ }, "node_modules/require-main-filename": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "dev": true, "license": "ISC" }, "node_modules/requizzle": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", - "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", "license": "MIT", "dependencies": { "lodash": "^4.17.21" @@ -15512,8 +12797,6 @@ }, "node_modules/reserved-identifiers": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", - "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", "dev": true, "license": "MIT", "engines": { @@ -15525,8 +12808,6 @@ }, "node_modules/resolve": { "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -15545,8 +12826,6 @@ }, "node_modules/resolve-cwd": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", "license": "MIT", "dependencies": { "resolve-from": "^5.0.0" @@ -15557,17 +12836,13 @@ }, "node_modules/resolve-from": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "version": "0.12.0", "license": "MIT", "engines": { "node": ">= 4" @@ -15575,8 +12850,6 @@ }, "node_modules/reusify": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", "engines": { "iojs": ">=1.0.0", @@ -15585,34 +12858,79 @@ }, "node_modules/rfdc": { "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "license": "MIT" }, "node_modules/rimraf": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", - "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", - "dev": true, - "license": "BlueOak-1.0.0", + "version": "5.0.10", + "license": "ISC", "dependencies": { - "glob": "^13.0.3", - "package-json-from-dist": "^1.0.1" + "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "10.5.0", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "10.4.3", + "license": "ISC" + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "9.0.5", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": "20 || >=22" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "1.11.1", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "version": "4.53.3", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -15625,38 +12943,33 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", + "@rollup/rollup-android-arm-eabi": "4.53.3", + "@rollup/rollup-android-arm64": "4.53.3", + "@rollup/rollup-darwin-arm64": "4.53.3", + "@rollup/rollup-darwin-x64": "4.53.3", + "@rollup/rollup-freebsd-arm64": "4.53.3", + "@rollup/rollup-freebsd-x64": "4.53.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", + "@rollup/rollup-linux-arm64-gnu": "4.53.3", + "@rollup/rollup-linux-arm64-musl": "4.53.3", + "@rollup/rollup-linux-loong64-gnu": "4.53.3", + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", + "@rollup/rollup-linux-riscv64-musl": "4.53.3", + "@rollup/rollup-linux-s390x-gnu": "4.53.3", + "@rollup/rollup-linux-x64-gnu": "4.53.3", + "@rollup/rollup-linux-x64-musl": "4.53.3", + "@rollup/rollup-openharmony-arm64": "4.53.3", + "@rollup/rollup-win32-arm64-msvc": "4.53.3", + "@rollup/rollup-win32-ia32-msvc": "4.53.3", + "@rollup/rollup-win32-x64-gnu": "4.53.3", + "@rollup/rollup-win32-x64-msvc": "4.53.3", "fsevents": "~2.3.2" } }, "node_modules/router": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -15669,10 +12982,27 @@ "node": ">= 18" } }, + "node_modules/router/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/router/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, "node_modules/router/node_modules/path-to-regexp": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", "funding": { "type": "opencollective", @@ -15681,8 +13011,6 @@ }, "node_modules/run-applescript": { "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", "license": "MIT", "engines": { "node": ">=18" @@ -15693,8 +13021,6 @@ }, "node_modules/run-parallel": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "funding": [ { "type": "github", @@ -15716,8 +13042,6 @@ }, "node_modules/safe-array-concat": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -15736,15 +13060,11 @@ }, "node_modules/safe-array-concat/node_modules/isarray": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, "node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -15763,8 +13083,6 @@ }, "node_modules/safe-push-apply": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, "license": "MIT", "dependencies": { @@ -15780,15 +13098,11 @@ }, "node_modules/safe-push-apply/node_modules/isarray": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, "node_modules/safe-regex-test": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, "license": "MIT", "dependencies": { @@ -15805,36 +13119,23 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/sax": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", - "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=11.0.0" - } + "version": "1.4.3", + "license": "BlueOak-1.0.0" }, "node_modules/search-insights": { "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", "license": "MIT", "peer": true }, "node_modules/select-hose": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", "license": "MIT" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.7.3", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -15844,48 +13145,40 @@ } }, "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "version": "0.19.0", "license": "MIT", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", - "encodeurl": "~2.0.0", + "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.4.1", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", "license": "MIT", - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": ">= 0.8" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/send/node_modules/ms": { + "version": "2.1.3", "license": "MIT" }, "node_modules/serialize-error": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", - "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", "dev": true, "license": "MIT", "dependencies": { @@ -15900,8 +13193,6 @@ }, "node_modules/serialize-error/node_modules/type-fest": { "version": "0.13.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", - "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -15912,15 +13203,13 @@ } }, "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "version": "1.16.2", "license": "MIT", "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "~0.19.1" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" @@ -15928,15 +13217,11 @@ }, "node_modules/set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, "license": "ISC" }, "node_modules/set-function-length": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, "license": "MIT", "dependencies": { @@ -15953,8 +13238,6 @@ }, "node_modules/set-function-name": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", "dev": true, "license": "MIT", "dependencies": { @@ -15969,8 +13252,6 @@ }, "node_modules/set-proto": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", "dev": true, "license": "MIT", "dependencies": { @@ -15984,14 +13265,10 @@ }, "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -16002,8 +13279,6 @@ }, "node_modules/shebang-regex": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "license": "MIT", "engines": { "node": ">=8" @@ -16011,8 +13286,6 @@ }, "node_modules/shiki": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", - "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", "license": "MIT", "dependencies": { "@shikijs/core": "2.5.0", @@ -16027,8 +13300,6 @@ }, "node_modules/side-channel": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -16046,8 +13317,6 @@ }, "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -16062,8 +13331,6 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -16080,8 +13347,6 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -16099,8 +13364,6 @@ }, "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==", "license": "ISC", "engines": { "node": ">=14" @@ -16110,33 +13373,29 @@ } }, "node_modules/sigstore": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sigstore/-/sigstore-4.1.0.tgz", - "integrity": "sha512-/fUgUhYghuLzVT/gaJoeVehLCgZiUxPCPMcyVNY0lIf/cTCz58K/WTI7PefDarXxp9nUKpEwg1yyz3eSBMTtgA==", + "version": "4.0.0", "license": "Apache-2.0", "dependencies": { "@sigstore/bundle": "^4.0.0", - "@sigstore/core": "^3.1.0", + "@sigstore/core": "^3.0.0", "@sigstore/protobuf-specs": "^0.5.0", - "@sigstore/sign": "^4.1.0", - "@sigstore/tuf": "^4.0.1", - "@sigstore/verify": "^3.1.0" + "@sigstore/sign": "^4.0.0", + "@sigstore/tuf": "^4.0.0", + "@sigstore/verify": "^3.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, "node_modules/sinon": { - "version": "21.0.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.2.tgz", - "integrity": "sha512-VHV4UaoxIe5jrMd89Y9duI76T5g3Lp+ET+ctLhLDaZtSznDPah1KKpRElbdBV4RwqWSw2vadFiVs9Del7MbVeQ==", + "version": "21.0.1", "dev": true, "license": "BSD-3-Clause", "dependencies": { "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^15.1.1", - "@sinonjs/samsam": "^9.0.2", - "diff": "^8.0.3", + "@sinonjs/fake-timers": "^15.1.0", + "@sinonjs/samsam": "^8.0.3", + "diff": "^8.0.2", "supports-color": "^7.2.0" }, "funding": { @@ -16146,8 +13405,6 @@ }, "node_modules/slash": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", - "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "license": "MIT", "engines": { "node": ">=14.16" @@ -16158,8 +13415,6 @@ }, "node_modules/slice-ansi": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16175,8 +13430,6 @@ }, "node_modules/smart-buffer": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", "license": "MIT", "engines": { "node": ">= 6.0.0", @@ -16185,8 +13438,6 @@ }, "node_modules/smol-toml": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz", - "integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -16198,8 +13449,6 @@ }, "node_modules/socks": { "version": "2.8.7", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", - "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", "license": "MIT", "dependencies": { "ip-address": "^10.0.1", @@ -16212,8 +13461,6 @@ }, "node_modules/socks-proxy-agent": { "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -16224,10 +13471,27 @@ "node": ">= 14" } }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/socks-proxy-agent/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, "node_modules/source-map": { "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16235,8 +13499,6 @@ }, "node_modules/source-map-js": { "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==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16244,8 +13506,6 @@ }, "node_modules/source-map-support": { "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", @@ -16254,8 +13514,6 @@ }, "node_modules/space-separated-tokens": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", "license": "MIT", "funding": { "type": "github", @@ -16264,8 +13522,6 @@ }, "node_modules/spawn-wrap": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", "dev": true, "license": "ISC", "dependencies": { @@ -16282,8 +13538,6 @@ }, "node_modules/spawn-wrap/node_modules/foreground-child": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", "dev": true, "license": "ISC", "dependencies": { @@ -16296,9 +13550,6 @@ }, "node_modules/spawn-wrap/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -16318,15 +13569,11 @@ }, "node_modules/spawn-wrap/node_modules/isexe": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "license": "ISC" }, "node_modules/spawn-wrap/node_modules/make-dir": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "dev": true, "license": "MIT", "dependencies": { @@ -16339,11 +13586,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/spawn-wrap/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/spawn-wrap/node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "license": "ISC", "dependencies": { @@ -16358,8 +13613,6 @@ }, "node_modules/spawn-wrap/node_modules/semver": { "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { @@ -16368,15 +13621,11 @@ }, "node_modules/spawn-wrap/node_modules/signal-exit": { "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "license": "ISC" }, "node_modules/spawn-wrap/node_modules/which": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "license": "ISC", "dependencies": { @@ -16391,8 +13640,6 @@ }, "node_modules/spdx-compare": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", - "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", "dev": true, "license": "MIT", "dependencies": { @@ -16403,8 +13650,6 @@ }, "node_modules/spdx-compare/node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -16414,8 +13659,6 @@ }, "node_modules/spdx-correct": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", - "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "license": "Apache-2.0", "dependencies": { "spdx-expression-parse": "^3.0.0", @@ -16424,8 +13667,6 @@ }, "node_modules/spdx-correct/node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", @@ -16434,14 +13675,11 @@ }, "node_modules/spdx-exceptions": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", - "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", - "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", @@ -16450,8 +13688,6 @@ }, "node_modules/spdx-expression-validate": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-validate/-/spdx-expression-validate-2.0.0.tgz", - "integrity": "sha512-b3wydZLM+Tc6CFvaRDBOF9d76oGIHNCLYFeHbftFXUWjnfZWganmDmvtM5sm1cRwJc/VDBMLyGGrsLFd1vOxbg==", "dev": true, "license": "(MIT AND CC-BY-3.0)", "dependencies": { @@ -16460,8 +13696,6 @@ }, "node_modules/spdx-expression-validate/node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -16470,29 +13704,21 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.23", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", - "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "version": "3.0.22", "license": "CC0-1.0" }, "node_modules/spdx-osi": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-osi/-/spdx-osi-3.0.0.tgz", - "integrity": "sha512-7DZMaD/rNHWGf82qWOazBsLXQsaLsoJb9RRjhEUQr5o86kw3A1ErGzSdvaXl+KalZyKkkU5T2a5NjCCutAKQSw==", "dev": true, "license": "CC0-1.0" }, "node_modules/spdx-ranges": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", - "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", "dev": true, "license": "(MIT AND CC-BY-3.0)" }, "node_modules/spdx-whitelisted": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/spdx-whitelisted/-/spdx-whitelisted-1.0.0.tgz", - "integrity": "sha512-X4FOpUCvZuo42MdB1zAZ/wdX4N0lLcWDozf2KYFVDgtLv8Lx+f31LOYLP2/FcwTzsPi64bS/VwKqklI4RBletg==", "dev": true, "license": "MIT", "dependencies": { @@ -16502,8 +13728,6 @@ }, "node_modules/spdy": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -16518,8 +13742,6 @@ }, "node_modules/spdy-transport": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", "license": "MIT", "dependencies": { "debug": "^4.1.0", @@ -16530,10 +13752,27 @@ "wbuf": "^1.7.3" } }, + "node_modules/spdy-transport/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy-transport/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, "node_modules/spdy-transport/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==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -16544,10 +13783,27 @@ "node": ">= 6" } }, + "node_modules/spdy/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/spdy/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, "node_modules/speakingurl": { "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16555,8 +13811,6 @@ }, "node_modules/split2": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "dev": true, "license": "ISC", "engines": { @@ -16565,15 +13819,11 @@ }, "node_modules/sprintf-js": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, "license": "BSD-3-Clause" }, "node_modules/ssri": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-13.0.1.tgz", - "integrity": "sha512-QUiRf1+u9wPTL/76GTYlKttDEBWV1ga9ZXW8BG6kfdeyyM8LGPix9gROyg9V2+P0xNyF3X2Go526xKFdMZrHSQ==", + "version": "13.0.0", "license": "ISC", "dependencies": { "minipass": "^7.0.3" @@ -16584,8 +13834,6 @@ }, "node_modules/stack-utils": { "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16597,8 +13845,6 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, "license": "MIT", "engines": { @@ -16606,9 +13852,7 @@ } }, "node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "version": "2.0.1", "license": "MIT", "engines": { "node": ">= 0.8" @@ -16616,8 +13860,6 @@ }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16630,8 +13872,6 @@ }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -16639,8 +13879,6 @@ }, "node_modules/string-width": { "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -16654,8 +13892,6 @@ "node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -16668,8 +13904,6 @@ }, "node_modules/string-width-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" @@ -16677,8 +13911,6 @@ }, "node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" @@ -16686,8 +13918,6 @@ }, "node_modules/string-width-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -16698,8 +13928,6 @@ }, "node_modules/string-width/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" @@ -16707,8 +13935,6 @@ }, "node_modules/string-width/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "license": "MIT", "engines": { "node": ">=8" @@ -16716,8 +13942,6 @@ }, "node_modules/string-width/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -16728,8 +13952,6 @@ }, "node_modules/string.prototype.trim": { "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "license": "MIT", "dependencies": { @@ -16750,8 +13972,6 @@ }, "node_modules/string.prototype.trimend": { "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "license": "MIT", "dependencies": { @@ -16769,8 +13989,6 @@ }, "node_modules/string.prototype.trimstart": { "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", "dev": true, "license": "MIT", "dependencies": { @@ -16787,8 +14005,6 @@ }, "node_modules/stringify-entities": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -16801,8 +14017,6 @@ }, "node_modules/stringify-object-es5": { "version": "2.5.0", - "resolved": "https://registry.npmjs.org/stringify-object-es5/-/stringify-object-es5-2.5.0.tgz", - "integrity": "sha512-vE7Xdx9ylG4JI16zy7/ObKUB+MtxuMcWlj/WHHr3+yAlQoN6sst2stU9E+2Qs3OrlJw/Pf3loWxL1GauEHf6MA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -16815,8 +14029,6 @@ }, "node_modules/stringify-object-es5/node_modules/is-plain-obj": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", "dev": true, "license": "MIT", "engines": { @@ -16824,12 +14036,10 @@ } }, "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "version": "7.1.2", "license": "MIT", "dependencies": { - "ansi-regex": "^6.2.2" + "ansi-regex": "^6.0.1" }, "engines": { "node": ">=12" @@ -16841,8 +14051,6 @@ "node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -16853,8 +14061,6 @@ }, "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" @@ -16862,8 +14068,6 @@ }, "node_modules/strip-bom": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", "dev": true, "license": "MIT", "engines": { @@ -16872,8 +14076,6 @@ }, "node_modules/strip-final-newline": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", - "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "license": "MIT", "engines": { @@ -16885,8 +14087,6 @@ }, "node_modules/strip-json-comments": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "license": "MIT", "engines": { "node": ">=8" @@ -16897,8 +14097,6 @@ }, "node_modules/strtok3": { "version": "7.1.1", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-7.1.1.tgz", - "integrity": "sha512-mKX8HA/cdBqMKUr0MMZAFssCkIGoZeSCMXgnt79yKxNFguMLVFgRe6wB+fsL0NmoHDbeyZXczy7vEPSoo3rkzg==", "dev": true, "license": "MIT", "dependencies": { @@ -16915,8 +14113,6 @@ }, "node_modules/stubborn-fs": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/stubborn-fs/-/stubborn-fs-2.0.0.tgz", - "integrity": "sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==", "license": "MIT", "dependencies": { "stubborn-utils": "^1.0.1" @@ -16924,14 +14120,10 @@ }, "node_modules/stubborn-utils": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stubborn-utils/-/stubborn-utils-1.0.2.tgz", - "integrity": "sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==", "license": "MIT" }, "node_modules/stylehacks": { "version": "7.0.7", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-7.0.7.tgz", - "integrity": "sha512-bJkD0JkEtbRrMFtwgpJyBbFIwfDDONQ1Ov3sDLZQP8HuJ73kBOyx66H4bOcAbVWmnfLdvQ0AJwXxOMkpujcO6g==", "license": "MIT", "dependencies": { "browserslist": "^4.27.0", @@ -16946,8 +14138,6 @@ }, "node_modules/sucrase": { "version": "3.35.1", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", - "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", @@ -16968,17 +14158,13 @@ }, "node_modules/sucrase/node_modules/commander": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", "license": "MIT", "engines": { "node": ">= 6" } }, "node_modules/superagent": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-10.3.0.tgz", - "integrity": "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ==", + "version": "10.2.3", "dev": true, "license": "MIT", "dependencies": { @@ -16986,20 +14172,34 @@ "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", - "form-data": "^4.0.5", + "form-data": "^4.0.4", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", - "qs": "^6.14.1" + "qs": "^6.11.2" }, "engines": { "node": ">=14.18.0" } }, + "node_modules/superagent/node_modules/debug": { + "version": "4.4.3", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/superagent/node_modules/mime": { "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", "dev": true, "license": "MIT", "bin": { @@ -17009,10 +14209,13 @@ "node": ">=4.0.0" } }, + "node_modules/superagent/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "version": "2.2.5", "license": "MIT", "dependencies": { "copy-anything": "^4" @@ -17023,8 +14226,6 @@ }, "node_modules/supertap": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/supertap/-/supertap-3.0.1.tgz", - "integrity": "sha512-u1ZpIBCawJnO+0QePsEiOknOfCRq0yERxiAchT0i4li0WHNUJbf0evXXSXOcCAR4M8iMDoajXYmstm/qO81Isw==", "dev": true, "license": "MIT", "dependencies": { @@ -17039,8 +14240,6 @@ }, "node_modules/supertap/node_modules/argparse": { "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "license": "MIT", "dependencies": { @@ -17049,8 +14248,6 @@ }, "node_modules/supertap/node_modules/js-yaml": { "version": "3.14.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", - "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { @@ -17062,34 +14259,19 @@ } }, "node_modules/supertest": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", - "integrity": "sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==", + "version": "7.1.4", "dev": true, "license": "MIT", "dependencies": { - "cookie-signature": "^1.2.2", "methods": "^1.1.2", - "superagent": "^10.3.0" + "superagent": "^10.2.3" }, "engines": { "node": ">=14.18.0" } }, - "node_modules/supertest/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { @@ -17101,8 +14283,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -17112,9 +14292,7 @@ } }, "node_modules/svgo": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz", - "integrity": "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==", + "version": "4.0.0", "license": "MIT", "dependencies": { "commander": "^11.1.0", @@ -17123,7 +14301,7 @@ "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", - "sax": "^1.5.0" + "sax": "^1.4.1" }, "bin": { "svgo": "bin/svgo.js" @@ -17138,23 +14316,17 @@ }, "node_modules/svgo/node_modules/commander": { "version": "11.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", - "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "license": "MIT", "engines": { "node": ">=16" } }, "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "version": "6.3.0", "license": "MIT" }, "node_modules/tagged-tag": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz", - "integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==", "license": "MIT", "engines": { "node": ">=20" @@ -17164,9 +14336,7 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.19", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", - "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "version": "3.4.18", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -17202,8 +14372,6 @@ }, "node_modules/tailwindcss/node_modules/jiti": { "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "license": "MIT", "bin": { "jiti": "bin/jiti.js" @@ -17211,8 +14379,6 @@ }, "node_modules/tailwindcss/node_modules/postcss-selector-parser": { "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==", "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -17223,9 +14389,7 @@ } }, "node_modules/tar": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.10.tgz", - "integrity": "sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==", + "version": "7.5.6", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -17238,19 +14402,8 @@ "node": ">=18" } }, - "node_modules/tar/node_modules/yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", - "license": "BlueOak-1.0.0", - "engines": { - "node": ">=18" - } - }, "node_modules/temp-dir": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-3.0.0.tgz", - "integrity": "sha512-nHc6S/bwIilKHNRgK/3jlhDoIHcp45YgyiwcAk46Tr0LfEqGBVpmiAyuiuxeVE44m3mXnEeVhaipLOEWmH+Njw==", "dev": true, "license": "MIT", "engines": { @@ -17258,9 +14411,7 @@ } }, "node_modules/tempy": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/tempy/-/tempy-3.2.0.tgz", - "integrity": "sha512-d79HhZya5Djd7am0q+W4RTsSU+D/aJzM+4Y4AGJGuGlgM2L6sx5ZvOYTmZjqPhrDrV6xJTtRSm1JCLj6V6LHLQ==", + "version": "3.1.0", "dev": true, "license": "MIT", "dependencies": { @@ -17278,8 +14429,6 @@ }, "node_modules/tempy/node_modules/is-stream": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, "license": "MIT", "engines": { @@ -17291,8 +14440,6 @@ }, "node_modules/tempy/node_modules/type-fest": { "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -17303,9 +14450,7 @@ } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.44.1", "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -17322,14 +14467,10 @@ }, "node_modules/terser/node_modules/commander": { "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "license": "MIT" }, "node_modules/test-exclude": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, "license": "ISC", "dependencies": { @@ -17343,9 +14484,6 @@ }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -17363,10 +14501,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.2", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/testdouble": { "version": "3.20.2", - "resolved": "https://registry.npmjs.org/testdouble/-/testdouble-3.20.2.tgz", - "integrity": "sha512-790e9vJKdfddWNOaxW1/V9FcMk48cPEl3eJSj2i8Hh1fX89qArEJ6cp3DBnaECpGXc3xKJVWbc1jeNlWYWgiMg==", "dev": true, "license": "MIT", "dependencies": { @@ -17379,10 +14526,19 @@ "node": ">= 16" } }, + "node_modules/text-extensions": { + "version": "2.4.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/thenify": { "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -17390,8 +14546,6 @@ }, "node_modules/thenify-all": { "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -17402,15 +14556,16 @@ }, "node_modules/theredoc": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/theredoc/-/theredoc-1.0.0.tgz", - "integrity": "sha512-KU3SA3TjRRM932jpNfD3u4Ec3bSvedyo5ITPI7zgWYnKep7BwQQaxlhI9qbO+lKJoRnoAbEVfMcAHRuKVYikDA==", + "dev": true, + "license": "MIT" + }, + "node_modules/through": { + "version": "2.3.8", "dev": true, "license": "MIT" }, "node_modules/time-zone": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz", - "integrity": "sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA==", "dev": true, "license": "MIT", "engines": { @@ -17419,8 +14574,6 @@ }, "node_modules/tinyexec": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, "license": "MIT", "engines": { @@ -17429,8 +14582,6 @@ }, "node_modules/tinyglobby": { "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -17445,8 +14596,6 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -17457,8 +14606,6 @@ }, "node_modules/to-valid-identifier": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", - "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", "dev": true, "license": "MIT", "dependencies": { @@ -17474,8 +14621,6 @@ }, "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -17483,8 +14628,6 @@ }, "node_modules/token-types": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-5.0.1.tgz", - "integrity": "sha512-Y2fmSnZjQdDb9W4w4r1tswlMHylzWIeOKpx0aZH9BgGtACHhrk3OkT52AzwcuqTRBZtvvnTjDBh8eynMulu8Vg==", "dev": true, "license": "MIT", "dependencies": { @@ -17501,15 +14644,11 @@ }, "node_modules/tr46": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true, "license": "MIT" }, "node_modules/traverse": { "version": "0.6.11", - "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.11.tgz", - "integrity": "sha512-vxXDZg8/+p3gblxB6BhhG5yWVn1kGRlaL8O78UDXc3wRnPizB5g83dcvWV1jpDMIPnjZjOFuxlMmE82XJ4407w==", "dev": true, "license": "MIT", "dependencies": { @@ -17526,8 +14665,6 @@ }, "node_modules/treeverse": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-3.0.0.tgz", - "integrity": "sha512-gcANaAnd2QDZFmHFEOF4k7uc1J/6a6z3DJMd/QwEyxLoKGiptJRwid582r7QIsFlFMIZ3SnxfS52S4hm2DHkuQ==", "license": "ISC", "engines": { "node": "^14.17.0 || ^16.13.0 || >=18.0.0" @@ -17535,8 +14672,6 @@ }, "node_modules/trim-lines": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "license": "MIT", "funding": { "type": "github", @@ -17545,36 +14680,45 @@ }, "node_modules/ts-interface-checker": { "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "license": "Apache-2.0" }, "node_modules/tslib": { "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true + "license": "0BSD" }, "node_modules/tuf-js": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/tuf-js/-/tuf-js-4.1.0.tgz", - "integrity": "sha512-50QV99kCKH5P/Vs4E2Gzp7BopNV+KzTXqWeaxrfu5IQJBOULRsTIS9seSsOVT8ZnGXzCyx55nYWAi4qJzpZKEQ==", + "version": "4.0.0", "license": "MIT", "dependencies": { - "@tufjs/models": "4.1.0", - "debug": "^4.4.3", - "make-fetch-happen": "^15.0.1" + "@tufjs/models": "4.0.0", + "debug": "^4.4.1", + "make-fetch-happen": "^15.0.0" }, "engines": { "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/tuf-js/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/tuf-js/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, "node_modules/type-check": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -17586,8 +14730,6 @@ }, "node_modules/type-detect": { "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", "dev": true, "license": "MIT", "engines": { @@ -17596,8 +14738,6 @@ }, "node_modules/type-fest": { "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -17605,39 +14745,18 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "1.6.18", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" + "media-typer": "0.3.0", + "mime-types": "~2.1.24" }, "engines": { "node": ">= 0.6" } }, - "node_modules/type-is/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "license": "MIT", "dependencies": { @@ -17651,8 +14770,6 @@ }, "node_modules/typed-array-byte-length": { "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "license": "MIT", "dependencies": { @@ -17671,8 +14788,6 @@ }, "node_modules/typed-array-byte-offset": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, "license": "MIT", "dependencies": { @@ -17693,8 +14808,6 @@ }, "node_modules/typed-array-length": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "license": "MIT", "dependencies": { @@ -17714,8 +14827,6 @@ }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "dev": true, "license": "MIT", "dependencies": { @@ -17724,8 +14835,6 @@ }, "node_modules/typedarray.prototype.slice": { "version": "1.0.5", - "resolved": "https://registry.npmjs.org/typedarray.prototype.slice/-/typedarray.prototype.slice-1.0.5.tgz", - "integrity": "sha512-q7QNVDGTdl702bVFiI5eY4l/HkgCM6at9KhcFbgUAzezHFbOVy4+0O/lCjsABEQwbZPravVfBIiBVGo89yzHFg==", "dev": true, "license": "MIT", "dependencies": { @@ -17747,8 +14856,6 @@ }, "node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "peer": true, @@ -17762,14 +14869,10 @@ }, "node_modules/uc.micro": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", "license": "MIT" }, "node_modules/uglify-js": { "version": "3.19.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", - "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, "license": "BSD-2-Clause", "optional": true, @@ -17782,8 +14885,6 @@ }, "node_modules/unbox-primitive": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "license": "MIT", "dependencies": { @@ -17800,30 +14901,22 @@ } }, "node_modules/underscore": { - "version": "1.13.8", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.8.tgz", - "integrity": "sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==", + "version": "1.13.7", "license": "MIT" }, "node_modules/undici": { "version": "6.23.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", - "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", "license": "MIT", "engines": { "node": ">=18.17" } }, "node_modules/undici-types": { - "version": "7.18.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", - "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "version": "7.16.0", "license": "MIT" }, "node_modules/unicorn-magic": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", - "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "version": "0.1.0", "license": "MIT", "engines": { "node": ">=18" @@ -17834,8 +14927,6 @@ }, "node_modules/unique-filename": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-5.0.0.tgz", - "integrity": "sha512-2RaJTAvAb4owyjllTfXzFClJ7WsGxlykkPvCr9pA//LD9goVq+m4PPAeBgNodGZ7nSrntT/auWpJ6Y5IFXcfjg==", "license": "ISC", "dependencies": { "unique-slug": "^6.0.0" @@ -17846,8 +14937,6 @@ }, "node_modules/unique-slug": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-6.0.0.tgz", - "integrity": "sha512-4Lup7Ezn8W3d52/xBhZBVdx323ckxa7DEvd9kPQHppTkLoJXw6ltrBCyj5pnrxj0qKDxYMJ56CoxNuFCscdTiw==", "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4" @@ -17858,8 +14947,6 @@ }, "node_modules/unique-string": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", "dev": true, "license": "MIT", "dependencies": { @@ -17874,8 +14961,6 @@ }, "node_modules/unist-util-is": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -17887,8 +14972,6 @@ }, "node_modules/unist-util-position": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -17900,8 +14983,6 @@ }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" @@ -17912,9 +14993,7 @@ } }, "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "version": "5.0.0", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -17928,8 +15007,6 @@ }, "node_modules/unist-util-visit-parents": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -17942,17 +15019,13 @@ }, "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "version": "1.1.4", "funding": [ { "type": "opencollective", @@ -17981,8 +15054,6 @@ }, "node_modules/update-notifier": { "version": "7.3.1", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-7.3.1.tgz", - "integrity": "sha512-+dwUY4L35XFYEzE+OAL3sarJdUioVovq+8f7lcIJ7wnmnYQV5UD1Y/lcwaMSyaQ6Bj3JMj1XSTjZbNLHn/19yA==", "license": "BSD-2-Clause", "dependencies": { "boxen": "^8.0.1", @@ -18003,22 +15074,8 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/uri-js": { "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -18026,14 +15083,10 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", "license": "MIT", "engines": { "node": ">= 0.4.0" @@ -18041,8 +15094,6 @@ }, "node_modules/uuid": { "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true, "license": "MIT", "bin": { @@ -18051,8 +15102,6 @@ }, "node_modules/validate-npm-package-license": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "license": "Apache-2.0", "dependencies": { "spdx-correct": "^3.0.0", @@ -18061,8 +15110,6 @@ }, "node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "license": "MIT", "dependencies": { "spdx-exceptions": "^2.1.0", @@ -18070,9 +15117,7 @@ } }, "node_modules/validate-npm-package-name": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-7.0.2.tgz", - "integrity": "sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==", + "version": "7.0.0", "license": "ISC", "engines": { "node": "^20.17.0 || >=22.9.0" @@ -18080,8 +15125,6 @@ }, "node_modules/vary": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -18089,8 +15132,6 @@ }, "node_modules/vfile": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -18103,8 +15144,6 @@ }, "node_modules/vfile-message": { "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -18117,8 +15156,6 @@ }, "node_modules/vite": { "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "license": "MIT", "dependencies": { "esbuild": "^0.21.3", @@ -18176,8 +15213,6 @@ }, "node_modules/vitepress": { "version": "1.6.4", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", - "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", "license": "MIT", "dependencies": { "@docsearch/css": "3.8.2", @@ -18216,16 +15251,14 @@ } }, "node_modules/vue": { - "version": "3.5.29", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.29.tgz", - "integrity": "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==", + "version": "3.5.25", "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.29", - "@vue/compiler-sfc": "3.5.29", - "@vue/runtime-dom": "3.5.29", - "@vue/server-renderer": "3.5.29", - "@vue/shared": "3.5.29" + "@vue/compiler-dom": "3.5.25", + "@vue/compiler-sfc": "3.5.25", + "@vue/runtime-dom": "3.5.25", + "@vue/server-renderer": "3.5.25", + "@vue/shared": "3.5.25" }, "peerDependencies": { "typescript": "*" @@ -18237,18 +15270,12 @@ } }, "node_modules/walk-up-path": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", - "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", - "license": "ISC", - "engines": { - "node": "20 || >=22" - } + "version": "3.0.1", + "dev": true, + "license": "ISC" }, "node_modules/wbuf": { "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", "license": "MIT", "dependencies": { "minimalistic-assert": "^1.0.0" @@ -18256,15 +15283,11 @@ }, "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, "license": "BSD-2-Clause" }, "node_modules/well-known-symbols": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz", - "integrity": "sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==", "dev": true, "license": "ISC", "engines": { @@ -18273,9 +15296,6 @@ }, "node_modules/whatwg-encoding": { "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "license": "MIT", "dependencies": { "iconv-lite": "0.6.3" @@ -18286,8 +15306,6 @@ }, "node_modules/whatwg-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==", "license": "MIT", "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" @@ -18298,8 +15316,6 @@ }, "node_modules/whatwg-mimetype": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", "license": "MIT", "engines": { "node": ">=18" @@ -18307,8 +15323,6 @@ }, "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, "license": "MIT", "dependencies": { @@ -18318,17 +15332,13 @@ }, "node_modules/when-exit": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz", - "integrity": "sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==", "license": "MIT" }, "node_modules/which": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/which/-/which-6.0.1.tgz", - "integrity": "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==", + "version": "6.0.0", "license": "ISC", "dependencies": { - "isexe": "^4.0.0" + "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" @@ -18339,8 +15349,6 @@ }, "node_modules/which-boxed-primitive": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, "license": "MIT", "dependencies": { @@ -18359,8 +15367,6 @@ }, "node_modules/which-builtin-type": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -18387,15 +15393,11 @@ }, "node_modules/which-builtin-type/node_modules/isarray": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, "license": "MIT" }, "node_modules/which-collection": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", "dev": true, "license": "MIT", "dependencies": { @@ -18413,15 +15415,11 @@ }, "node_modules/which-module": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", "dev": true, "license": "ISC" }, "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "version": "1.1.19", "dev": true, "license": "MIT", "dependencies": { @@ -18442,8 +15440,6 @@ }, "node_modules/widest-line": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-5.0.0.tgz", - "integrity": "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==", "license": "MIT", "dependencies": { "string-width": "^7.0.0" @@ -18457,14 +15453,10 @@ }, "node_modules/widest-line/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, "node_modules/widest-line/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -18480,8 +15472,6 @@ }, "node_modules/word-wrap": { "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -18490,21 +15480,15 @@ }, "node_modules/wordwrap": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, "license": "MIT" }, "node_modules/workerpool": { "version": "10.0.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-10.0.1.tgz", - "integrity": "sha512-NAnKwZJxWlj/U1cp6ZkEtPE+GQY1S6KtOS3AlCiPfPFLxV3m64giSp7g2LsNJxzYCocDT7TSl+7T0sgrDp3KoQ==", "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "license": "MIT", "dependencies": { "ansi-styles": "^6.2.1", @@ -18521,8 +15505,6 @@ "node_modules/wrap-ansi-cjs": { "name": "wrap-ansi", "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -18538,8 +15520,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", "engines": { "node": ">=8" @@ -18547,8 +15527,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -18562,8 +15540,6 @@ }, "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -18574,14 +15550,10 @@ }, "node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, "node_modules/wrap-ansi/node_modules/string-width": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", "dependencies": { "emoji-regex": "^10.3.0", @@ -18597,15 +15569,11 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-6.0.0.tgz", - "integrity": "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ==", "dev": true, "license": "ISC", "dependencies": { @@ -18618,8 +15586,6 @@ }, "node_modules/wsl-utils": { "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" @@ -18631,10 +15597,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/wsl-utils/node_modules/is-wsl": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "is-inside-container": "^1.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/xdg-basedir": { "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", "license": "MIT", "engines": { "node": ">=12" @@ -18645,8 +15622,6 @@ }, "node_modules/xml2js": { "version": "0.6.2", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", - "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", "license": "MIT", "dependencies": { "sax": ">=0.6.0", @@ -18658,8 +15633,6 @@ }, "node_modules/xmlbuilder": { "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "license": "MIT", "engines": { "node": ">=4.0" @@ -18667,36 +15640,28 @@ }, "node_modules/xmlcreate": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", "license": "Apache-2.0" }, "node_modules/y18n": { "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "license": "ISC", "engines": { "node": ">=10" } }, "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" + "version": "5.0.0", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } }, "node_modules/yaml-ast-parser": { "version": "0.0.43", - "resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz", - "integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==", "license": "Apache-2.0" }, "node_modules/yargs": { "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -18713,8 +15678,6 @@ }, "node_modules/yargs-parser": { "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "license": "ISC", "engines": { "node": ">=12" @@ -18722,18 +15685,14 @@ }, "node_modules/yesno": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", - "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==", "license": "BSD" }, "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "version": "1.2.2", "dev": true, "license": "MIT", "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -18741,8 +15700,6 @@ }, "node_modules/yoctocolors": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", - "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", "dev": true, "license": "MIT", "engines": { @@ -18754,8 +15711,6 @@ }, "node_modules/zod": { "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", "funding": { @@ -18764,8 +15719,6 @@ }, "node_modules/zwitch": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", "license": "MIT", "funding": { "type": "github", @@ -18811,6 +15764,24 @@ "npm": ">= 8" } }, + "packages/builder/node_modules/rimraf": { + "version": "6.1.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "packages/cli": { "name": "@ui5/cli", "version": "5.0.0-alpha.3", @@ -18852,16 +15823,48 @@ "npm": ">= 8" } }, - "packages/cli/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "packages/cli/node_modules/define-lazy-prop": { + "version": "3.0.0", "license": "MIT", "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/cli/node_modules/open": { + "version": "10.2.0", + "license": "MIT", + "dependencies": { + "default-browser": "^5.2.1", + "define-lazy-prop": "^3.0.0", + "is-inside-container": "^1.0.0", + "wsl-utils": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/cli/node_modules/rimraf": { + "version": "6.1.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "packages/fs": { @@ -18870,6 +15873,7 @@ "license": "Apache-2.0", "dependencies": { "@ui5/logger": "^5.0.0-alpha.3", + "async-mutex": "^0.5.0", "clone": "^2.1.2", "escape-string-regexp": "^5.0.0", "globby": "^15.0.0", @@ -18877,7 +15881,8 @@ "micromatch": "^4.0.8", "minimatch": "^10.2.2", "pretty-hrtime": "^1.0.3", - "random-int": "^3.1.0" + "random-int": "^3.1.0", + "ssri": "^13.0.0" }, "devDependencies": { "@istanbuljs/esm-loader-hook": "^0.3.0", @@ -18894,16 +15899,21 @@ "npm": ">= 8" } }, - "packages/fs/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "packages/fs/node_modules/balanced-match": { + "version": "4.0.4", "license": "MIT", "engines": { - "node": ">=12" + "node": "18 || 20 || >=22" + } + }, + "packages/fs/node_modules/brace-expansion": { + "version": "5.0.4", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "18 || 20 || >=22" } }, "packages/fs/node_modules/globby": { @@ -18933,8 +15943,6 @@ }, "packages/fs/node_modules/minimatch": { "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -18946,6 +15954,34 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/fs/node_modules/rimraf": { + "version": "6.1.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/fs/node_modules/unicorn-magic": { + "version": "0.3.0", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/logger": { "name": "@ui5/logger", "version": "5.0.0-alpha.3", @@ -18970,18 +16006,6 @@ "npm": ">= 8" } }, - "packages/logger/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "packages/logger/node_modules/cross-env": { "version": "10.1.0", "dev": true, @@ -18991,11 +16015,29 @@ "cross-spawn": "^7.0.6" }, "bin": { - "cross-env": "dist/bin/cross-env.js", - "cross-env-shell": "dist/bin/cross-env-shell.js" + "cross-env": "dist/bin/cross-env.js", + "cross-env-shell": "dist/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=20" + } + }, + "packages/logger/node_modules/rimraf": { + "version": "6.1.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" }, "engines": { - "node": ">=20" + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "packages/project": { @@ -19008,13 +16050,16 @@ "@ui5/logger": "^5.0.0-alpha.3", "ajv": "^6.12.6", "ajv-errors": "^1.0.1", + "cacache": "^20.0.3", "chalk": "^5.6.2", + "chokidar": "^3.6.0", "escape-string-regexp": "^5.0.0", "globby": "^14.1.0", "graceful-fs": "^4.2.11", "js-yaml": "^4.1.1", "lockfile": "^1.0.4", "make-fetch-happen": "^15.0.3", + "micromatch": "^4.0.8", "node-stream-zip": "^1.15.0", "pacote": "^21.0.4", "pretty-hrtime": "^1.0.3", @@ -19053,47 +16098,137 @@ } } }, - "packages/project/node_modules/ajv": { - "version": "6.14.0", + "packages/project/node_modules/@npmcli/config": { + "version": "10.4.2", + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^5.0.0", + "@npmcli/package-json": "^7.0.0", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.1.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^4.0.0" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "packages/project/node_modules/@npmcli/map-workspaces": { + "version": "5.0.3", + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^4.0.0", + "@npmcli/package-json": "^7.0.0", + "glob": "^13.0.0", + "minimatch": "^10.0.3" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "packages/project/node_modules/normalize-package-data": { + "version": "8.0.0", + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^9.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "packages/project/node_modules/parse-json": { + "version": "8.3.0", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "@babel/code-frame": "^7.26.2", + "index-to-position": "^1.1.0", + "type-fest": "^4.39.1" + }, + "engines": { + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/project/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "packages/project/node_modules/parse-json/node_modules/type-fest": { + "version": "4.41.0", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/project/node_modules/read-pkg": { + "version": "10.1.0", "license": "MIT", + "dependencies": { + "@types/normalize-package-data": "^2.4.4", + "normalize-package-data": "^8.0.0", + "parse-json": "^8.3.0", + "type-fest": "^5.4.4", + "unicorn-magic": "^0.4.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": ">=20" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/project/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "packages/project/node_modules/rimraf": { + "version": "6.1.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/project/node_modules/type-fest": { + "version": "5.4.4", + "license": "(MIT OR CC0-1.0)", + "dependencies": { + "tagged-tag": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "packages/project/node_modules/unicorn-magic": { + "version": "0.4.0", "license": "MIT", "engines": { - "node": ">=12" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/project/node_modules/json-schema-traverse": { - "version": "0.4.1", - "license": "MIT" + "packages/project/node_modules/walk-up-path": { + "version": "4.0.0", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } }, "packages/server": { "name": "@ui5/server", @@ -19115,7 +16250,6 @@ "mime-types": "^2.1.35", "parseurl": "^1.3.3", "portscanner": "^2.2.0", - "replacestream": "^4.0.3", "router": "^2.2.0", "spdy": "^4.0.2", "yesno": "^0.4.0" @@ -19136,6 +16270,150 @@ "node": "^22.20.0 || >=24.0.0", "npm": ">= 8" } + }, + "packages/server/node_modules/body-parser": { + "version": "2.2.2", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "packages/server/node_modules/debug": { + "version": "4.4.3", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "packages/server/node_modules/http-errors": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "packages/server/node_modules/iconv-lite": { + "version": "0.7.2", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "packages/server/node_modules/media-typer": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "packages/server/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "packages/server/node_modules/raw-body": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "packages/server/node_modules/rimraf": { + "version": "6.1.2", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "glob": "^13.0.0", + "package-json-from-dist": "^1.0.1" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "packages/server/node_modules/statuses": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "packages/server/node_modules/type-is": { + "version": "2.0.1", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "packages/server/node_modules/type-is/node_modules/mime-types": { + "version": "3.0.2", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } } } } diff --git a/packages/builder/lib/processors/nonAsciiEscaper.js b/packages/builder/lib/processors/nonAsciiEscaper.js index ff9d58e97d6..493c680453b 100644 --- a/packages/builder/lib/processors/nonAsciiEscaper.js +++ b/packages/builder/lib/processors/nonAsciiEscaper.js @@ -83,8 +83,8 @@ async function nonAsciiEscaper({resources, options: {encoding}}) { // only modify the resource's string if it was changed if (escaped.modified) { resource.setString(escaped.string); + return resource; } - return resource; } return Promise.all(resources.map(processResource)); diff --git a/packages/builder/lib/processors/stringReplacer.js b/packages/builder/lib/processors/stringReplacer.js index 2485032cc76..8c4bf6560dc 100644 --- a/packages/builder/lib/processors/stringReplacer.js +++ b/packages/builder/lib/processors/stringReplacer.js @@ -21,9 +21,10 @@ export default function({resources, options: {pattern, replacement}}) { return Promise.all(resources.map(async (resource) => { const content = await resource.getString(); const newContent = content.replaceAll(pattern, replacement); + // only modify the resource's string if it was changed if (content !== newContent) { resource.setString(newContent); + return resource; } - return resource; })); } diff --git a/packages/builder/lib/tasks/buildThemes.js b/packages/builder/lib/tasks/buildThemes.js index d407bb97ff7..de9f2e4d6fe 100644 --- a/packages/builder/lib/tasks/buildThemes.js +++ b/packages/builder/lib/tasks/buildThemes.js @@ -192,7 +192,7 @@ export default async function({ } let processedResources; - const useWorkers = !!taskUtil; + const useWorkers = !process.env.UI5_CLI_NO_WORKERS && !!taskUtil; if (useWorkers) { const threadMessageHandler = new FsMainThreadInterface(fsInterface(combo)); diff --git a/packages/builder/lib/tasks/escapeNonAsciiCharacters.js b/packages/builder/lib/tasks/escapeNonAsciiCharacters.js index 53cb3e8d9f3..697b2425080 100644 --- a/packages/builder/lib/tasks/escapeNonAsciiCharacters.js +++ b/packages/builder/lib/tasks/escapeNonAsciiCharacters.js @@ -14,17 +14,24 @@ import nonAsciiEscaper from "../processors/nonAsciiEscaper.js"; * * @param {object} parameters Parameters * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project. + * This is only set if a cache is used and changes have been detected. * @param {object} parameters.options Options * @param {string} parameters.options.pattern Glob pattern to locate the files to be processed * @param {string} parameters.options.encoding source file encoding either "UTF-8" or "ISO-8859-1" * @returns {Promise} Promise resolving with undefined once data has been written */ -export default async function({workspace, options: {pattern, encoding}}) { +export default async function({workspace, changedProjectResourcePaths, options: {pattern, encoding}}) { if (!encoding) { throw new Error("[escapeNonAsciiCharacters] Mandatory option 'encoding' not provided"); } - const allResources = await workspace.byGlob(pattern); + let allResources; + if (changedProjectResourcePaths) { + allResources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource))); + } else { + allResources = await workspace.byGlob(pattern); + } const processedResources = await nonAsciiEscaper({ resources: allResources, @@ -33,5 +40,5 @@ export default async function({workspace, options: {pattern, encoding}}) { } }); - await Promise.all(processedResources.map((resource) => workspace.write(resource))); + await Promise.all(processedResources.map((resource) => resource && workspace.write(resource))); } diff --git a/packages/builder/lib/tasks/minify.js b/packages/builder/lib/tasks/minify.js index 2969ca688dc..439f53fc9a0 100644 --- a/packages/builder/lib/tasks/minify.js +++ b/packages/builder/lib/tasks/minify.js @@ -16,6 +16,8 @@ import fsInterface from "@ui5/fs/fsInterface"; * @param {object} parameters Parameters * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files * @param {@ui5/project/build/helpers/TaskUtil|object} [parameters.taskUtil] TaskUtil + * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project. + * This is only set if a cache is used and changes have been detected. * @param {object} parameters.options Options * @param {string} parameters.options.pattern Pattern to locate the files to be processed * @param {boolean} [parameters.options.omitSourceMapResources=false] Whether source map resources shall @@ -26,9 +28,24 @@ import fsInterface from "@ui5/fs/fsInterface"; * @returns {Promise} Promise resolving with undefined once data has been written */ export default async function({ - workspace, taskUtil, options: {pattern, omitSourceMapResources = false, useInputSourceMaps = true - }}) { - const resources = await workspace.byGlob(pattern); + workspace, taskUtil, changedProjectResourcePaths, + options: {pattern, omitSourceMapResources = false, useInputSourceMaps = true} +}) { + let resources; + if (changedProjectResourcePaths) { + resources = await Promise.all( + changedProjectResourcePaths + // Filtering out non-JS resources such as .map files + // FIXME: The changed resources should rather be matched against the provided pattern + .filter((resourcePath) => resourcePath.endsWith(".js")) + .map((resource) => workspace.byPath(resource)) + ); + } else { + resources = await workspace.byGlob(pattern); + } + if (resources.length === 0) { + return; + } const processedResources = await minifier({ resources, fs: fsInterface(workspace), @@ -36,7 +53,7 @@ export default async function({ options: { addSourceMappingUrl: !omitSourceMapResources, readSourceMappingUrl: !!useInputSourceMaps, - useWorkers: !!taskUtil, + useWorkers: !process.env.UI5_CLI_NO_WORKERS && !!taskUtil, } }); diff --git a/packages/builder/lib/tasks/replaceBuildtime.js b/packages/builder/lib/tasks/replaceBuildtime.js index f4093c0b732..44498a09186 100644 --- a/packages/builder/lib/tasks/replaceBuildtime.js +++ b/packages/builder/lib/tasks/replaceBuildtime.js @@ -28,26 +28,30 @@ function getTimestamp() { * * @param {object} parameters Parameters * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project. + * This is only set if a cache is used and changes have been detected. * @param {object} parameters.options Options * @param {string} parameters.options.pattern Pattern to locate the files to be processed * @returns {Promise} Promise resolving with undefined once data has been written */ -export default function({workspace, options: {pattern}}) { +export default async function({workspace, changedProjectResourcePaths, options: {pattern}}) { + let resources; + if (changedProjectResourcePaths) { + resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource))); + } else { + resources = await workspace.byGlob(pattern); + } const timestamp = getTimestamp(); - - return workspace.byGlob(pattern) - .then((processedResources) => { - return stringReplacer({ - resources: processedResources, - options: { - pattern: "${buildtime}", - replacement: timestamp - } - }); - }) - .then((processedResources) => { - return Promise.all(processedResources.map((resource) => { - return workspace.write(resource); - })); - }); + const processedResources = await stringReplacer({ + resources, + options: { + pattern: "${buildtime}", + replacement: timestamp + } + }); + return Promise.all(processedResources.map((resource) => { + if (resource) { + return workspace.write(resource); + } + })); } diff --git a/packages/builder/lib/tasks/replaceCopyright.js b/packages/builder/lib/tasks/replaceCopyright.js index 2ccb6a596df..90daed02fd5 100644 --- a/packages/builder/lib/tasks/replaceCopyright.js +++ b/packages/builder/lib/tasks/replaceCopyright.js @@ -24,32 +24,38 @@ import stringReplacer from "../processors/stringReplacer.js"; * * @param {object} parameters Parameters * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project. + * This is only set if a cache is used and changes have been detected. * @param {object} parameters.options Options * @param {string} parameters.options.copyright Replacement copyright * @param {string} parameters.options.pattern Pattern to locate the files to be processed * @returns {Promise} Promise resolving with undefined once data has been written */ -export default function({workspace, options: {copyright, pattern}}) { +export default async function({workspace, changedProjectResourcePaths, options: {copyright, pattern}}) { if (!copyright) { - return Promise.resolve(); + return; } // Replace optional placeholder ${currentYear} with the current year copyright = copyright.replace(/(?:\$\{currentYear\})/, new Date().getFullYear()); - return workspace.byGlob(pattern) - .then((processedResources) => { - return stringReplacer({ - resources: processedResources, - options: { - pattern: /(?:\$\{copyright\}|@copyright@)/g, - replacement: copyright - } - }); - }) - .then((processedResources) => { - return Promise.all(processedResources.map((resource) => { - return workspace.write(resource); - })); - }); + let resources; + if (changedProjectResourcePaths) { + resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource))); + } else { + resources = await workspace.byGlob(pattern); + } + + const processedResources = await stringReplacer({ + resources, + options: { + pattern: /(?:\$\{copyright\}|@copyright@)/g, + replacement: copyright + } + }); + return Promise.all(processedResources.map((resource) => { + if (resource) { + return workspace.write(resource); + } + })); } diff --git a/packages/builder/lib/tasks/replaceVersion.js b/packages/builder/lib/tasks/replaceVersion.js index 699a6221a95..d30b0839dc6 100644 --- a/packages/builder/lib/tasks/replaceVersion.js +++ b/packages/builder/lib/tasks/replaceVersion.js @@ -14,25 +14,30 @@ import stringReplacer from "../processors/stringReplacer.js"; * * @param {object} parameters Parameters * @param {@ui5/fs/DuplexCollection} parameters.workspace DuplexCollection to read and write files + * @param {string[]} [parameters.changedProjectResourcePaths] Set of changed resource paths within the project. + * This is only set if a cache is used and changes have been detected. * @param {object} parameters.options Options * @param {string} parameters.options.pattern Pattern to locate the files to be processed * @param {string} parameters.options.version Replacement version * @returns {Promise} Promise resolving with undefined once data has been written */ -export default function({workspace, options: {pattern, version}}) { - return workspace.byGlob(pattern) - .then((allResources) => { - return stringReplacer({ - resources: allResources, - options: { - pattern: /\$\{(?:project\.)?version\}/g, - replacement: version - } - }); - }) - .then((processedResources) => { - return Promise.all(processedResources.map((resource) => { - return workspace.write(resource); - })); - }); +export default async function({workspace, changedProjectResourcePaths, options: {pattern, version}}) { + let resources; + if (changedProjectResourcePaths) { + resources = await Promise.all(changedProjectResourcePaths.map((resource) => workspace.byPath(resource))); + } else { + resources = await workspace.byGlob(pattern); + } + const processedResources = await stringReplacer({ + resources, + options: { + pattern: /\$\{(?:project\.)?version\}/g, + replacement: version + } + }); + await Promise.all(processedResources.map((resource) => { + if (resource) { + return workspace.write(resource); + } + })); } diff --git a/packages/builder/test/utils/fshelper.js b/packages/builder/test/utils/fshelper.js index 25e74974b79..9d069654a92 100644 --- a/packages/builder/test/utils/fshelper.js +++ b/packages/builder/test/utils/fshelper.js @@ -11,8 +11,8 @@ export async function readFileContent(filePath) { } export async function directoryDeepEqual(t, destPath, expectedPath) { - const dest = await readdir(destPath, {recursive: true}); - const expected = await readdir(expectedPath, {recursive: true}); + const dest = (await readdir(destPath, {recursive: true})).sort(); + const expected = (await readdir(expectedPath, {recursive: true})).sort(); t.deepEqual(dest, expected); } diff --git a/packages/cli/lib/cli/commands/build.js b/packages/cli/lib/cli/commands/build.js index df93ac5a12e..31e7b56062c 100644 --- a/packages/cli/lib/cli/commands/build.js +++ b/packages/cli/lib/cli/commands/build.js @@ -1,4 +1,5 @@ import baseMiddleware from "../middlewares/base.js"; +import path from "node:path"; const build = { command: "build", @@ -173,6 +174,7 @@ async function handleBuild(argv) { const buildSettings = graph.getRoot().getBuilderSettings() || {}; await graph.build({ graph, + cacheDir: path.join(graph.getRoot().getRootPath(), ".ui5-cache"), destPath: argv.dest, cleanDest: argv["clean-dest"], createBuildManifest: argv["create-build-manifest"], diff --git a/packages/cli/lib/cli/commands/serve.js b/packages/cli/lib/cli/commands/serve.js index 4719e82bf34..218c72ab1d0 100644 --- a/packages/cli/lib/cli/commands/serve.js +++ b/packages/cli/lib/cli/commands/serve.js @@ -146,7 +146,10 @@ serve.handler = async function(argv) { serverConfig.cert = cert; } - const {h2, port: actualPort} = await serverServe(graph, serverConfig); + const {promise: pOnError, reject} = Promise.withResolvers(); + const {h2, port: actualPort} = await serverServe(graph, serverConfig, function(err) { + reject(err); + }); const protocol = h2 ? "https" : "http"; let browserUrl = protocol + "://localhost:" + actualPort; @@ -183,6 +186,7 @@ serve.handler = async function(argv) { const {default: open} = await import("open"); open(browserUrl); } + await pOnError; // Await errors that should bubble into the yargs handler }; export default serve; diff --git a/packages/fs/lib/MonitoredReader.js b/packages/fs/lib/MonitoredReader.js new file mode 100644 index 00000000000..820b75169fe --- /dev/null +++ b/packages/fs/lib/MonitoredReader.js @@ -0,0 +1,49 @@ +import AbstractReader from "./AbstractReader.js"; + +export default class MonitoredReader extends AbstractReader { + #reader; + #sealed = false; + #paths = []; + #patterns = []; + + constructor(reader) { + super(reader.getName()); + this.#reader = reader; + } + + getResourceRequests() { + this.#sealed = true; + return { + paths: this.#paths, + patterns: this.#patterns, + }; + } + + async _byGlob(virPattern, options, trace) { + if (this.#sealed) { + throw new Error(`Unexpected read operation after reader has been sealed`); + } + if (this.#reader.resolvePattern) { + const resolvedPattern = this.#reader.resolvePattern(virPattern); + this.#patterns.push(resolvedPattern); + } else { + this.#patterns.push(virPattern); + } + return await this.#reader._byGlob(virPattern, options, trace); + } + + async _byPath(virPath, options, trace) { + if (this.#sealed) { + throw new Error(`Unexpected read operation after reader has been sealed`); + } + if (this.#reader.resolvePath) { + const resolvedPath = this.#reader.resolvePath(virPath); + if (resolvedPath) { + this.#paths.push(resolvedPath); + } + } else { + this.#paths.push(virPath); + } + return await this.#reader._byPath(virPath, options, trace); + } +} diff --git a/packages/fs/lib/MonitoredReaderWriter.js b/packages/fs/lib/MonitoredReaderWriter.js new file mode 100644 index 00000000000..0472fb9b610 --- /dev/null +++ b/packages/fs/lib/MonitoredReaderWriter.js @@ -0,0 +1,53 @@ +import AbstractReaderWriter from "./AbstractReaderWriter.js"; + +export default class MonitoredReaderWriter extends AbstractReaderWriter { + #readerWriter; + #sealed = false; + #paths = new Set(); + #patterns = new Set(); + + constructor(readerWriter) { + super(readerWriter.getName()); + this.#readerWriter = readerWriter; + } + + getResourceRequests() { + this.#sealed = true; + return { + paths: this.#paths, + patterns: this.#patterns, + }; + } + + async _byGlob(virPattern, options, trace) { + if (this.#sealed) { + throw new Error(`Unexpected read operation after reader has been sealed`); + } + if (this.#readerWriter.resolvePattern) { + const resolvedPattern = this.#readerWriter.resolvePattern(virPattern); + this.#patterns.add(resolvedPattern); + } else { + this.#patterns.add(virPattern); + } + return await this.#readerWriter._byGlob(virPattern, options, trace); + } + + async _byPath(virPath, options, trace) { + if (this.#sealed) { + throw new Error(`Unexpected read operation after reader has been sealed`); + } + if (this.#readerWriter.resolvePath) { + const resolvedPath = this.#readerWriter.resolvePath(virPath); + if (resolvedPath) { + this.#paths.add(resolvedPath); + } + } else { + this.#paths.add(virPath); + } + return await this.#readerWriter._byPath(virPath, options, trace); + } + + async _write(resource, options) { + return this.#readerWriter.write(resource, options); + } +} diff --git a/packages/fs/lib/MonitoredResourceTagCollection.js b/packages/fs/lib/MonitoredResourceTagCollection.js new file mode 100644 index 00000000000..cbc84fca26f --- /dev/null +++ b/packages/fs/lib/MonitoredResourceTagCollection.js @@ -0,0 +1,77 @@ +/** + * Proxy of ResourceTagCollection + * + * @class + * @alias @ui5/fs/internal/MonitoredTagCollection + */ +class MonitoredTagCollection { + #tagCollection; + #tagOperations = new Map(); // resourcePath -> Map + + /** + * Constructor + * + * @param {object} tagCollection The ResourceTagCollection instance to wrap + */ + constructor(tagCollection) { + this.#tagCollection = tagCollection; + } + + /** + * Returns tags created or cleared via this MonitoredTagCollection during the execution of a task + * + * @returns {Map>} + * Map of resource paths to their tags that were set or cleared during this task's execution + */ + getTagOperations() { + return this.#tagOperations; + } + + /** + * Set a tag on a resource and track the operation + * + * @param {string|object} resourcePathOrResource Path of the resource or a resource instance + * @param {string} tag Tag in the format "namespace:Name" + * @param {string|number|boolean} [value=true] Tag value + */ + setTag(resourcePathOrResource, tag, value = true) { + this.#tagCollection.setTag(resourcePathOrResource, tag, value); + const resourcePath = this.#tagCollection._getPath(resourcePathOrResource); + + // Track tags set during this task's execution + if (!this.#tagOperations.has(resourcePath)) { + this.#tagOperations.set(resourcePath, new Map()); + } + this.#tagOperations.get(resourcePath).set(tag, value); + } + + /** + * Get a tag value from a resource + * + * @param {string|object} resourcePathOrResource Path of the resource or a resource instance + * @param {string} tag Tag in the format "namespace:Name" + * @returns {string|number|boolean|undefined} Tag value or undefined if not set + */ + getTag(resourcePathOrResource, tag) { + return this.#tagCollection.getTag(resourcePathOrResource, tag); + } + + /** + * Clear a tag from a resource and track the operation + * + * @param {string|object} resourcePathOrResource Path of the resource or a resource instance + * @param {string} tag Tag in the format "namespace:Name" + */ + clearTag(resourcePathOrResource, tag) { + this.#tagCollection.clearTag(resourcePathOrResource, tag); + const resourcePath = this.#tagCollection._getPath(resourcePathOrResource); + + // Track cleared tags during this task's execution + const resourceTags = this.#tagOperations.has(resourcePath); + if (resourceTags) { + resourceTags.set(tag, undefined); + } + } +} + +export default MonitoredTagCollection; diff --git a/packages/fs/lib/ReaderCollectionPrioritized.js b/packages/fs/lib/ReaderCollectionPrioritized.js index 680b71357ca..8f235f22148 100644 --- a/packages/fs/lib/ReaderCollectionPrioritized.js +++ b/packages/fs/lib/ReaderCollectionPrioritized.js @@ -68,7 +68,7 @@ class ReaderCollectionPrioritized extends AbstractReader { * @returns {Promise<@ui5/fs/Resource|null>} * Promise resolving to a single resource or null if no resource is found */ - _byPath(virPath, options, trace) { + async _byPath(virPath, options, trace) { const that = this; const byPath = (i) => { if (i > this._readers.length - 1) { diff --git a/packages/fs/lib/Resource.js b/packages/fs/lib/Resource.js index c43edc2716f..df5009bfe6b 100644 --- a/packages/fs/lib/Resource.js +++ b/packages/fs/lib/Resource.js @@ -1,11 +1,28 @@ -import stream from "node:stream"; +import {Readable} from "node:stream"; +import {buffer as streamToBuffer} from "node:stream/consumers"; +import ssri from "ssri"; import clone from "clone"; import posixPath from "node:path/posix"; +import {setTimeout} from "node:timers/promises"; +import {Mutex} from "async-mutex"; +import {getLogger} from "@ui5/logger"; + +const log = getLogger("fs:Resource"); +let deprecatedGetStreamCalled = false; +let deprecatedGetStatInfoCalled = false; -const fnTrue = () => true; -const fnFalse = () => false; const ALLOWED_SOURCE_METADATA_KEYS = ["adapter", "fsPath", "contentModified"]; +const CONTENT_TYPES = { + BUFFER: "buffer", + STREAM: "stream", + FACTORY: "factory", + DRAINED_STREAM: "drainedStream", + IN_TRANSFORMATION: "inTransformation", +}; + +const SSRI_OPTIONS = {algorithms: ["sha256"]}; + /** * Resource. UI5 CLI specific representation of a file's content and metadata * @@ -14,26 +31,39 @@ const ALLOWED_SOURCE_METADATA_KEYS = ["adapter", "fsPath", "contentModified"]; * @alias @ui5/fs/Resource */ class Resource { - #project; - #buffer; - #buffering; - #collections; - #contentDrained; - #createStream; #name; #path; + #project; #sourceMetadata; + + /* Resource Content */ + #content; + #createBufferFactory; + #createStreamFactory; + #contentType; + + /* Content Metadata */ + #byteSize; + #lastModified; #statInfo; - #stream; - #streamDrained; - #isModified; + #isDirectory; + #integrity; + #inode; + + /* States */ + #isModified = false; + // Mutex to prevent access/modification while content is being transformed + #contentMutex = new Mutex(); + + // Tracing + #collections = []; /** - * Function for dynamic creation of content streams + * Factory function to dynamic (and potentially repeated) creation of readable streams of the resource's content * * @public * @callback @ui5/fs/Resource~createStream - * @returns {stream.Readable} A readable stream of a resources content + * @returns {stream.Readable} A readable stream of the resource's content */ /** @@ -49,6 +79,9 @@ class Resource { * (cannot be used in conjunction with parameters buffer, stream or createStream) * @param {Stream} [parameters.stream] Readable stream of the content of this resource * (cannot be used in conjunction with parameters buffer, string or createStream) + * @param {@ui5/fs/Resource~createBuffer} [parameters.createBuffer] Function callback that returns a promise + * resolving with a Buffer of the content of this resource (cannot be used in conjunction with + * parameters buffer, string or stream). Must be used in conjunction with parameters createStream. * @param {@ui5/fs/Resource~createStream} [parameters.createStream] Function callback that returns a readable * stream of the content of this resource (cannot be used in conjunction with parameters buffer, * string or stream). @@ -57,20 +90,34 @@ class Resource { * @param {object} [parameters.sourceMetadata] Source metadata for UI5 CLI internal use. * Some information may be set by an adapter to store information for later retrieval. Also keeps track of whether * a resource content has been modified since it has been read from a source + * @param {boolean} [parameters.isDirectory] Flag whether the resource represents a directory + * @param {number} [parameters.byteSize] Size of the resource content in bytes + * @param {number} [parameters.lastModified] Last modified timestamp (in milliseconds since UNIX epoch) + * @param {string} [parameters.integrity] Integrity hash of the resource content + * @param {number} [parameters.inode] Inode number of the resource */ - constructor({path, statInfo, buffer, string, createStream, stream, project, sourceMetadata}) { + constructor({ + path, statInfo, buffer, createBuffer, string, createStream, stream, project, sourceMetadata, + isDirectory, byteSize, lastModified, integrity, inode, + }) { if (!path) { throw new Error("Unable to create Resource: Missing parameter 'path'"); } + if (createBuffer && !createStream) { + // If createBuffer is provided, createStream must be provided as well + throw new Error("Unable to create Resource: Parameter 'createStream' must be provided when " + + "parameter 'createBuffer' is used"); + } if (buffer && createStream || buffer && string || string && createStream || buffer && stream || string && stream || createStream && stream) { - throw new Error("Unable to create Resource: Please set only one content parameter. " + + throw new Error("Unable to create Resource: Multiple content parameters provided. " + + "Please provide only one of the following parameters: " + "'buffer', 'string', 'stream' or 'createStream'"); } if (sourceMetadata) { if (typeof sourceMetadata !== "object") { - throw new Error(`Parameter 'sourceMetadata' must be of type "object"`); + throw new Error(`Unable to create Resource: Parameter 'sourceMetadata' must be of type "object"`); } /* eslint-disable-next-line guard-for-in */ @@ -96,42 +143,91 @@ class Resource { // Since the sourceMetadata object is inherited to clones, it is the only correct indicator this.#sourceMetadata.contentModified ??= false; - this.#isModified = false; - this.#project = project; - - this.#statInfo = statInfo || { // TODO - isFile: fnTrue, - isDirectory: fnFalse, - isBlockDevice: fnFalse, - isCharacterDevice: fnFalse, - isSymbolicLink: fnFalse, - isFIFO: fnFalse, - isSocket: fnFalse, - atimeMs: new Date().getTime(), - mtimeMs: new Date().getTime(), - ctimeMs: new Date().getTime(), - birthtimeMs: new Date().getTime(), - atime: new Date(), - mtime: new Date(), - ctime: new Date(), - birthtime: new Date() - }; + this.#integrity = integrity; if (createStream) { - this.#createStream = createStream; - } else if (stream) { - this.#stream = stream; + // We store both factories individually + // This allows to create either a stream or a buffer on demand + // Note that it's possible and acceptable if only one factory is provided + if (createBuffer) { + if (typeof createBuffer !== "function") { + throw new Error("Unable to create Resource: Parameter 'createBuffer' must be a function"); + } + this.#createBufferFactory = createBuffer; + } + // createStream is always provided if a factory is used + if (typeof createStream !== "function") { + throw new Error("Unable to create Resource: Parameter 'createStream' must be a function"); + } + this.#createStreamFactory = createStream; + this.#contentType = CONTENT_TYPES.FACTORY; + } if (stream) { + if (typeof stream !== "object" || typeof stream.pipe !== "function") { + throw new Error("Unable to create Resource: Parameter 'stream' must be a readable stream"); + } + this.#content = stream; + this.#contentType = CONTENT_TYPES.STREAM; } else if (buffer) { - // Use private setter, not to accidentally set any modified flags - this.#setBuffer(buffer); - } else if (typeof string === "string" || string instanceof String) { - // Use private setter, not to accidentally set any modified flags - this.#setBuffer(Buffer.from(string, "utf8")); + if (!Buffer.isBuffer(buffer)) { + throw new Error("Unable to create Resource: Parameter 'buffer' must be of type Buffer"); + } + this.#content = buffer; + this.#contentType = CONTENT_TYPES.BUFFER; + } else if (string !== undefined) { + if (typeof string !== "string" && !(string instanceof String)) { + throw new Error("Unable to create Resource: Parameter 'string' must be of type string"); + } + this.#content = Buffer.from(string, "utf8"); // Assuming utf8 encoding + this.#contentType = CONTENT_TYPES.BUFFER; + } + + if (isDirectory !== undefined) { + this.#isDirectory = !!isDirectory; + } + if (byteSize !== undefined) { + if (typeof byteSize !== "number" || byteSize < 0) { + throw new Error("Unable to create Resource: Parameter 'byteSize' must be a positive number"); + } + this.#byteSize = byteSize; + } + if (lastModified !== undefined) { + if (typeof lastModified !== "number" || lastModified < 0) { + throw new Error("Unable to create Resource: Parameter 'lastModified' must be a positive number"); + } + this.#lastModified = lastModified; } - // Tracing: - this.#collections = []; + if (inode !== undefined) { + if (typeof inode !== "number" || inode < 0) { + throw new Error("Unable to create Resource: Parameter 'inode' must be a positive number"); + } + this.#inode = inode; + } + + if (statInfo) { + this.#isDirectory ??= statInfo.isDirectory(); + if (!this.#isDirectory && statInfo.isFile && !statInfo.isFile()) { + throw new Error("Unable to create Resource: statInfo must represent either a file or a directory"); + } + this.#byteSize ??= statInfo.size; + this.#lastModified ??= statInfo.mtimeMs; + this.#inode ??= statInfo.ino; + + // Create legacy statInfo object + this.#statInfo = parseStat(statInfo); + } else { + // if (this.#byteSize === undefined && this.#contentType) { + // if (this.#contentType !== CONTENT_TYPES.BUFFER) { + // throw new Error("Unable to create Resource: byteSize or statInfo must be provided when resource " + + // "content is stream- or factory-based"); + // } + // this.#byteSize ??= this.#content.byteLength; + // } + + // Create legacy statInfo object + this.#statInfo = createStat(this.#byteSize, this.#isDirectory, this.#lastModified); + } } /** @@ -141,20 +237,56 @@ class Resource { * @returns {Promise} Promise resolving with a buffer of the resource content. */ async getBuffer() { - if (this.#contentDrained) { - throw new Error(`Content of Resource ${this.#path} has been drained. ` + - "This might be caused by requesting resource content after a content stream has been " + - "requested and no new content (e.g. a new stream) has been set."); - } - if (this.#buffer) { - return this.#buffer; - } else if (this.#createStream || this.#stream) { - return this.#getBufferFromStream(); - } else { + // First wait for new content if the current content is flagged as drained + if (this.#contentType === CONTENT_TYPES.DRAINED_STREAM) { + await this.#waitForNewContent(); + } + + // Then make sure no other operation is currently modifying the content (such as a concurrent getBuffer call + // that might be transforming the content right now) + if (this.#contentMutex.isLocked()) { + await this.#contentMutex.waitForUnlock(); + } + + switch (this.#contentType) { + case CONTENT_TYPES.FACTORY: + if (this.#createBufferFactory) { + // Prefer buffer factory if available + return await this.#getBufferFromFactory(this.#createBufferFactory); + } + // Fallback to stream factory + return this.#getBufferFromStream(this.#createStreamFactory()); + case CONTENT_TYPES.STREAM: + return this.#getBufferFromStream(this.#content); + case CONTENT_TYPES.BUFFER: + return this.#content; + case CONTENT_TYPES.DRAINED_STREAM: + // waitForNewContent call above should prevent this from ever happening + throw new Error(`Unexpected error: Content of Resource ${this.#path} is flagged as drained.`); + case CONTENT_TYPES.IN_TRANSFORMATION: + // contentMutex.waitForUnlock call above should prevent this from ever happening + throw new Error(`Unexpected error: Content of Resource ${this.#path} is currently being transformed`); + default: throw new Error(`Resource ${this.#path} has no content`); } } + async #getBufferFromFactory(factoryFn) { + const release = await this.#contentMutex.acquire(); + try { + this.#contentType = CONTENT_TYPES.IN_TRANSFORMATION; + const buffer = await factoryFn(); + if (!Buffer.isBuffer(buffer)) { + throw new Error(`Buffer factory of Resource ${this.#path} did not return a Buffer instance`); + } + this.#content = buffer; + this.#contentType = CONTENT_TYPES.BUFFER; + return buffer; + } finally { + release(); + } + } + /** * Sets a Buffer as content. * @@ -162,20 +294,12 @@ class Resource { * @param {Buffer} buffer Buffer instance */ setBuffer(buffer) { - this.#sourceMetadata.contentModified = true; - this.#isModified = true; - this.#setBuffer(buffer); - } - - #setBuffer(buffer) { - this.#createStream = null; - // if (this.#stream) { // TODO this may cause strange issues - // this.#stream.destroy(); - // } - this.#stream = null; - this.#buffer = buffer; - this.#contentDrained = false; - this.#streamDrained = false; + if (this.#contentMutex.isLocked()) { + throw new Error(`Unable to set buffer: Content of Resource ${this.#path} is currently being transformed`); + } + this.#content = buffer; + this.#contentType = CONTENT_TYPES.BUFFER; + this.#contendModified(); } /** @@ -184,13 +308,9 @@ class Resource { * @public * @returns {Promise} Promise resolving with the resource content. */ - getString() { - if (this.#contentDrained) { - return Promise.reject(new Error(`Content of Resource ${this.#path} has been drained. ` + - "This might be caused by requesting resource content after a content stream has been " + - "requested and no new content (e.g. a new stream) has been set.")); - } - return this.getBuffer().then((buffer) => buffer.toString()); + async getString() { + const buff = await this.getBuffer(); + return buff.toString("utf8"); } /** @@ -208,29 +328,130 @@ class Resource { * * Repetitive calls of this function are only possible if new content has been set in the meantime (through * [setStream]{@link @ui5/fs/Resource#setStream}, [setBuffer]{@link @ui5/fs/Resource#setBuffer} - * or [setString]{@link @ui5/fs/Resource#setString}). This - * is to prevent consumers from accessing drained streams. + * or [setString]{@link @ui5/fs/Resource#setString}). + * This is to prevent subsequent consumers from accessing drained streams. + * + * This method is deprecated. Please use the asynchronous version + * [getStreamAsync]{@link @ui5/fs/Resource#getStreamAsync} instead. + * + * For atomic operations, consider using [modifyStream]{@link @ui5/fs/Resource#modifyStream}. * * @public + * @deprecated Use asynchronous Resource.getStreamAsync() instead * @returns {stream.Readable} Readable stream for the resource content. */ getStream() { - if (this.#contentDrained) { - throw new Error(`Content of Resource ${this.#path} has been drained. ` + - "This might be caused by requesting resource content after a content stream has been " + - "requested and no new content (e.g. a new stream) has been set."); - } - let contentStream; - if (this.#buffer) { - const bufferStream = new stream.PassThrough(); - bufferStream.end(this.#buffer); - contentStream = bufferStream; - } else if (this.#createStream || this.#stream) { - contentStream = this.#getStream(); - } - if (!contentStream) { + if (!deprecatedGetStreamCalled) { + log.verbose(`[DEPRECATION] Synchronous Resource.getStream() is deprecated and will be removed ` + + `in future versions. Please use asynchronous Resource.getStreamAsync() instead.`); + deprecatedGetStreamCalled = true; + } + + // First check for drained content + if (this.#contentType === CONTENT_TYPES.DRAINED_STREAM) { + throw new Error(`Content of Resource ${this.#path} is currently flagged as drained. ` + + `Consider using Resource.getStreamAsync() to wait for new content.`); + } + + // Make sure no other operation is currently modifying the content + if (this.#contentMutex.isLocked()) { + throw new Error(`Content of Resource ${this.#path} is currently being transformed. ` + + `Consider using Resource.getStreamAsync() to wait for the transformation to finish.`); + } + + return this.#getStream(); + } + + /** + * Gets a readable stream for the resource content. + * + * Repetitive calls of this function are only possible if new content has been set in the meantime (through + * [setStream]{@link @ui5/fs/Resource#setStream}, [setBuffer]{@link @ui5/fs/Resource#setBuffer} + * or [setString]{@link @ui5/fs/Resource#setString}). + * This is to prevent subsequent consumers from accessing drained streams. + * + * For atomic operations, consider using [modifyStream]{@link @ui5/fs/Resource#modifyStream}. + * + * @public + * @returns {Promise} Promise resolving with a readable stream for the resource content. + */ + async getStreamAsync() { + // First wait for new content if the current content is flagged as drained + if (this.#contentType === CONTENT_TYPES.DRAINED_STREAM) { + await this.#waitForNewContent(); + } + + // Then make sure no other operation is currently modifying the content + if (this.#contentMutex.isLocked()) { + await this.#contentMutex.waitForUnlock(); + } + + return this.#getStream(); + } + + /** + * Modifies the resource content by applying the given callback function. + * The callback function receives a readable stream of the current content + * and must return either a Buffer or a readable stream with the new content. + * The resource content is locked during the modification to prevent concurrent access. + * + * @param {function(stream.Readable): (Buffer|stream.Readable|Promise)} callback + */ + async modifyStream(callback) { + // First wait for new content if the current content is flagged as drained + if (this.#contentType === CONTENT_TYPES.DRAINED_STREAM) { + await this.#waitForNewContent(); + } + // Then make sure no other operation is currently modifying the content and then lock it + const release = await this.#contentMutex.acquire(); + try { + const newContent = await callback(this.#getStream()); + + // New content is either buffer or stream + if (Buffer.isBuffer(newContent)) { + this.#content = newContent; + this.#contentType = CONTENT_TYPES.BUFFER; + } else if (typeof newContent === "object" && typeof newContent.pipe === "function") { + this.#content = newContent; + this.#contentType = CONTENT_TYPES.STREAM; + } else { + throw new Error("Unable to set new content: Content must be either a Buffer or a Readable Stream"); + } + this.#contendModified(); + } finally { + release(); + } + } + + /** + * Returns the content as stream. + * + * @private + * @returns {stream.Readable} Readable stream + */ + #getStream() { + let stream; + switch (this.#contentType) { + case CONTENT_TYPES.BUFFER: + stream = Readable.from(this.#content); + break; + case CONTENT_TYPES.FACTORY: + // Prefer stream factory (which must always be set if content type is FACTORY) + stream = this.#createStreamFactory(); + break; + case CONTENT_TYPES.STREAM: + stream = this.#content; + break; + case CONTENT_TYPES.DRAINED_STREAM: + // This case is unexpected as callers should already handle this content type (by waiting for it to change) + throw new Error(`Unexpected error: Content of Resource ${this.#path} is flagged as drained.`); + case CONTENT_TYPES.IN_TRANSFORMATION: + // This case is unexpected as callers should already handle this content type (by waiting for it to change) + throw new Error(`Unexpected error: Content of Resource ${this.#path} is currently being transformed`); + default: throw new Error(`Resource ${this.#path} has no content`); } + // If a stream instance is being returned, it will typically get drained be the consumer. // In that case, further content access will result in a "Content stream has been drained" error. // However, depending on the execution environment, a resources content stream might have been @@ -239,8 +460,56 @@ class Resource { // To prevent unexpected "Content stream has been drained" errors caused by changing environments, we flag // the resource content as "drained" every time a stream is requested. Even if actually a buffer or // createStream callback is being used. - this.#contentDrained = true; - return contentStream; + this.#contentType = CONTENT_TYPES.DRAINED_STREAM; + return stream; + } + + /** + * Converts the buffer into a stream. + * + * @private + * @param {stream.Readable} stream Readable stream + * @returns {Promise} Promise resolving with buffer. + */ + async #getBufferFromStream(stream) { + const release = await this.#contentMutex.acquire(); + try { + this.#contentType = CONTENT_TYPES.IN_TRANSFORMATION; + if (this.hasSize()) { + // If size is known. preallocate buffer for improved performance + try { + const size = await this.getSize(); + const buffer = Buffer.allocUnsafe(size); + let offset = 0; + for await (const chunk of stream) { + const len = chunk.length; + if (offset + len > size) { + throw new Error(`Stream exceeded expected size: ${size}, got at least ${offset + len}`); + } + chunk.copy(buffer, offset); + offset += len; + } + if (offset !== size) { + throw new Error(`Stream ended early: expected ${size} bytes, got ${offset}`); + } + this.#content = buffer; + } catch (err) { + // Ensure the stream is cleaned up on error + if (!stream.destroyed) { + stream.destroy(err); + } + throw err; + } + } else { + // Is size is unknown, simply use utility consumer from Node.js webstreams + // See https://nodejs.org/api/webstreams.html#utility-consumers + this.#content = await streamToBuffer(stream); + } + this.#contentType = CONTENT_TYPES.BUFFER; + } finally { + release(); + } + return this.#content; } /** @@ -251,22 +520,110 @@ class Resource { callback for dynamic creation of a readable stream */ setStream(stream) { - this.#isModified = true; + if (this.#contentMutex.isLocked()) { + throw new Error(`Unable to set stream: Content of Resource ${this.#path} is currently being transformed`); + } + if (typeof stream === "function") { + this.#content = undefined; + this.#createStreamFactory = stream; + this.#contentType = CONTENT_TYPES.FACTORY; + } else { + this.#content = stream; + this.#contentType = CONTENT_TYPES.STREAM; + } + this.#contendModified(); + } + + async getIntegrity() { + if (this.#integrity) { + return this.#integrity; + } + if (this.isDirectory()) { + throw new Error(`Unable to calculate integrity for directory resource: ${this.#path}`); + } + + // First wait for new content if the current content is flagged as drained + if (this.#contentType === CONTENT_TYPES.DRAINED_STREAM) { + await this.#waitForNewContent(); + } + + // Then make sure no other operation is currently modifying the content + if (this.#contentMutex.isLocked()) { + await this.#contentMutex.waitForUnlock(); + } + + switch (this.#contentType) { + case CONTENT_TYPES.BUFFER: + this.#integrity = ssri.fromData(this.#content, SSRI_OPTIONS).toString(); + break; + case CONTENT_TYPES.FACTORY: + // TODO: Investigate performance impact of buffer factory vs. stream factory for integrity calculation + // if (this.#createBufferFactory) { + // this.#integrity = ssri.fromData( + // await this.#getBufferFromFactory(this.#createBufferFactory, SSRI_OPTIONS).toString()); + // } else { + this.#integrity = (await ssri.fromStream(this.#createStreamFactory(), SSRI_OPTIONS)).toString(); + // } + break; + case CONTENT_TYPES.STREAM: + // To be discussed: Should we read the stream into a buffer here (using #getBufferFromStream) to avoid + // draining it? + this.#integrity = ssri.fromData(await this.#getBufferFromStream(this.#content), SSRI_OPTIONS).toString(); + break; + case CONTENT_TYPES.DRAINED_STREAM: + throw new Error(`Unexpected error: Content of Resource ${this.#path} is flagged as drained.`); + case CONTENT_TYPES.IN_TRANSFORMATION: + throw new Error(`Unexpected error: Content of Resource ${this.#path} is currently being transformed`); + default: + throw new Error(`Resource ${this.#path} has no content`); + } + return this.#integrity; + } + + #contendModified() { this.#sourceMetadata.contentModified = true; + this.#isModified = true; - this.#buffer = null; - // if (this.#stream) { // TODO this may cause strange issues - // this.#stream.destroy(); - // } - if (typeof stream === "function") { - this.#createStream = stream; - this.#stream = null; + this.#byteSize = undefined; + this.#integrity = undefined; + this.#lastModified = new Date().getTime(); // TODO: Always update or keep initial value (= fs stat)? + + if (this.#contentType === CONTENT_TYPES.BUFFER) { + this.#byteSize = this.#content.byteLength; + this.#updateStatInfo(this.#byteSize); } else { - this.#stream = stream; - this.#createStream = null; + this.#byteSize = undefined; + // Stat-info can't be updated based on streams or factory functions } - this.#contentDrained = false; - this.#streamDrained = false; + } + + /** + * In case the resource content is flagged as drained stream, wait for new content to be set. + * Either resolves once the content type is no longer DRAINED_STREAM, or rejects with a timeout error. + */ + async #waitForNewContent() { + if (this.#contentType !== CONTENT_TYPES.DRAINED_STREAM) { + return; + } + // Stream might currently be processed by another consumer. Try again after a short wait, hoping the + // other consumer has processing it and has set new content + let timeoutCounter = 0; + log.verbose(`Content of Resource ${this.#path} is flagged as drained, waiting for new content...`); + while (this.#contentType === CONTENT_TYPES.DRAINED_STREAM) { + timeoutCounter++; + await setTimeout(1); + if (timeoutCounter > 100) { // 100 ms timeout + throw new Error(`Timeout waiting for content of Resource ${this.#path} to become available.`); + } + } + // New content is now available + } + + #updateStatInfo(byteSize) { + const now = new Date(); + this.#statInfo.mtimeMs = now.getTime(); + this.#statInfo.mtime = now; + this.#statInfo.size = byteSize; } /** @@ -279,6 +636,16 @@ class Resource { return this.#path; } + /** + * Gets the virtual resources path + * + * @public + * @returns {string} (Virtual) path of the resource + */ + getOriginalPath() { + return this.#path; + } + /** * Sets the virtual resources path * @@ -311,26 +678,81 @@ class Resource { * [fs.Stats]{@link https://nodejs.org/api/fs.html#fs_class_fs_stats} instance. * * @public + * @deprecated Use dedicated APIs like Resource.getSize(), .isDirectory(), .getLastModified() instead * @returns {fs.Stats|object} Instance of [fs.Stats]{@link https://nodejs.org/api/fs.html#fs_class_fs_stats} * or similar object */ getStatInfo() { + if (!deprecatedGetStatInfoCalled) { + log.verbose(`[DEPRECATION] Resource.getStatInfo() is deprecated and will be removed in future versions. ` + + `Please switch to dedicated APIs like Resource.getSize() instead.`); + deprecatedGetStatInfoCalled = true; + } return this.#statInfo; } /** - * Size in bytes allocated by the underlying buffer. + * Checks whether the resource represents a directory. + * + * @public + * @returns {boolean} True if resource is a directory + */ + isDirectory() { + return this.#isDirectory; + } + + /** + * Gets the last modified timestamp of the resource. * + * @public + * @returns {number} Last modified timestamp (in milliseconds since UNIX epoch) + */ + getLastModified() { + return this.#lastModified; + } + + /** + * Gets the inode number of the resource. + * + * @public + * @returns {number} Inode number of the resource + */ + getInode() { + return this.#inode; + } + + /** + * Resource content size in bytes. + * + * @public * @see {TypedArray#byteLength} - * @returns {Promise} size in bytes, 0 if there is no content yet + * @returns {Promise} size in bytes, 0 if the resource has no content */ async getSize() { - // if resource does not have any content it should have 0 bytes - if (!this.#buffer && !this.#createStream && !this.#stream) { + if (this.#byteSize !== undefined) { + return this.#byteSize; + } + if (this.#contentType === undefined) { return 0; } const buffer = await this.getBuffer(); - return buffer.byteLength; + this.#byteSize = buffer.byteLength; + return this.#byteSize; + } + + /** + * Checks whether the resource size can be determined without reading the entire content. + * E.g. for buffer-based content or if the size has been provided when the resource was created. + * + * @public + * @returns {boolean} True if size can be determined statically + */ + hasSize() { + return ( + this.#contentType === undefined || // No content => size is 0 + this.#byteSize !== undefined || // Size has been determined already + this.#contentType === CONTENT_TYPES.BUFFER // Buffer content => size can be determined + ); } /** @@ -354,20 +776,41 @@ class Resource { } async #getCloneOptions() { + // First wait for new content if the current content is flagged as drained + if (this.#contentType === CONTENT_TYPES.DRAINED_STREAM) { + await this.#waitForNewContent(); + } + + // Then make sure no other operation is currently modifying the content + if (this.#contentMutex.isLocked()) { + await this.#contentMutex.waitForUnlock(); + } + const options = { path: this.#path, - statInfo: clone(this.#statInfo), + statInfo: this.#statInfo, // Will be cloned in constructor + isDirectory: this.#isDirectory, + byteSize: this.#isDirectory ? undefined : await this.getSize(), + lastModified: this.#lastModified, + integrity: this.#isDirectory ? undefined : (this.#contentType ? await this.getIntegrity() : undefined), sourceMetadata: clone(this.#sourceMetadata) }; - if (this.#stream) { - options.buffer = await this.#getBufferFromStream(); - } else if (this.#createStream) { - options.createStream = this.#createStream; - } else if (this.#buffer) { - options.buffer = this.#buffer; + switch (this.#contentType) { + case CONTENT_TYPES.STREAM: + // When cloning resource we have to read the stream into memory + options.buffer = await this.#getBufferFromStream(this.#content); + break; + case CONTENT_TYPES.BUFFER: + options.buffer = this.#content; + break; + case CONTENT_TYPES.FACTORY: + if (this.#createBufferFactory) { + options.createBuffer = this.#createBufferFactory; + } + options.createStream = this.#createStreamFactory; + break; } - return options; } @@ -448,51 +891,65 @@ class Resource { getSourceMetadata() { return this.#sourceMetadata; } +} - /** - * Returns the content as stream. - * - * @private - * @returns {stream.Readable} Readable stream - */ - #getStream() { - if (this.#streamDrained) { - throw new Error(`Content stream of Resource ${this.#path} is flagged as drained.`); - } - if (this.#createStream) { - return this.#createStream(); - } - this.#streamDrained = true; - return this.#stream; - } +const fnTrue = function() { + return true; +}; +const fnFalse = function() { + return false; +}; - /** - * Converts the buffer into a stream. - * - * @private - * @returns {Promise} Promise resolving with buffer. - */ - #getBufferFromStream() { - if (this.#buffering) { // Prevent simultaneous buffering, causing unexpected access to drained stream - return this.#buffering; - } - return this.#buffering = new Promise((resolve, reject) => { - const contentStream = this.#getStream(); - const buffers = []; - contentStream.on("data", (data) => { - buffers.push(data); - }); - contentStream.on("error", (err) => { - reject(err); - }); - contentStream.on("end", () => { - const buffer = Buffer.concat(buffers); - this.#setBuffer(buffer); - this.#buffering = null; - resolve(buffer); - }); - }); - } +/** + * Parses a Node.js stat object to a UI5 Tooling stat object + * + * @param {fs.Stats} statInfo Node.js stat + * @returns {object} UI5 Tooling stat +*/ +function parseStat(statInfo) { + return { + isFile: statInfo.isFile?.bind(statInfo), + isDirectory: statInfo.isDirectory?.bind(statInfo), + isBlockDevice: statInfo.isBlockDevice?.bind(statInfo), + isCharacterDevice: statInfo.isCharacterDevice?.bind(statInfo), + isSymbolicLink: statInfo.isSymbolicLink?.bind(statInfo), + isFIFO: statInfo.isFIFO?.bind(statInfo), + isSocket: statInfo.isSocket?.bind(statInfo), + ino: statInfo.ino, + size: statInfo.size, + atimeMs: statInfo.atimeMs, + mtimeMs: statInfo.mtimeMs, + ctimeMs: statInfo.ctimeMs, + birthtimeMs: statInfo.birthtimeMs, + atime: statInfo.atime, + mtime: statInfo.mtime, + ctime: statInfo.ctime, + birthtime: statInfo.birthtime, + }; +} + +function createStat(size, isDirectory = false, lastModified) { + const now = new Date(); + const mtime = lastModified === undefined ? now : new Date(lastModified); + return { + isFile: isDirectory ? fnFalse : fnTrue, + isDirectory: isDirectory ? fnTrue : fnFalse, + isBlockDevice: fnFalse, + isCharacterDevice: fnFalse, + isSymbolicLink: fnFalse, + isFIFO: fnFalse, + isSocket: fnFalse, + ino: 0, + size, // Might be undefined + atimeMs: now.getTime(), + mtimeMs: mtime.getTime(), + ctimeMs: now.getTime(), + birthtimeMs: now.getTime(), + atime: now, + mtime, + ctime: now, + birthtime: now, + }; } export default Resource; diff --git a/packages/fs/lib/ResourceFacade.js b/packages/fs/lib/ResourceFacade.js index 58ba37b2a4d..fe549e7ac3c 100644 --- a/packages/fs/lib/ResourceFacade.js +++ b/packages/fs/lib/ResourceFacade.js @@ -45,6 +45,16 @@ class ResourceFacade { return this.#path; } + /** + * Gets the path original resource's path + * + * @public + * @returns {string} (Virtual) path of the resource + */ + getOriginalPath() { + return this.#resource.getPath(); + } + /** * Gets the resource name * @@ -129,16 +139,46 @@ class ResourceFacade { * * Repetitive calls of this function are only possible if new content has been set in the meantime (through * [setStream]{@link @ui5/fs/Resource#setStream}, [setBuffer]{@link @ui5/fs/Resource#setBuffer} - * or [setString]{@link @ui5/fs/Resource#setString}). This - * is to prevent consumers from accessing drained streams. + * or [setString]{@link @ui5/fs/Resource#setString}). + * This is to prevent subsequent consumers from accessing drained streams. * * @public + * @deprecated Use asynchronous Resource.getStreamAsync() instead * @returns {stream.Readable} Readable stream for the resource content. */ getStream() { return this.#resource.getStream(); } + /** + * Gets a readable stream for the resource content. + * + * Repetitive calls of this function are only possible if new content has been set in the meantime (through + * [setStream]{@link @ui5/fs/Resource#setStream}, [setBuffer]{@link @ui5/fs/Resource#setBuffer} + * or [setString]{@link @ui5/fs/Resource#setString}). + * This is to prevent subsequent consumers from accessing drained streams. + * + * For atomic operations, please use [modifyStream]{@link @ui5/fs/Resource#modifyStream} + * + * @public + * @returns {Promise} Promise resolving with a readable stream for the resource content. + */ + async getStreamAsync() { + return this.#resource.getStreamAsync(); + } + + /** + * Modifies the resource content by applying the given callback function. + * The callback function receives a readable stream of the current content + * and must return either a Buffer or a readable stream with the new content. + * The resource content is locked during the modification to prevent concurrent access. + * + * @param {function(stream.Readable): (Buffer|stream.Readable|Promise)} callback + */ + async modifyStream(callback) { + return this.#resource.modifyStream(callback); + } + /** * Sets a readable stream as content. * @@ -150,6 +190,14 @@ class ResourceFacade { return this.#resource.setStream(stream); } + getIntegrity() { + return this.#resource.getIntegrity(); + } + + getInode() { + return this.#resource.getInode(); + } + /** * Gets the resources stat info. * Note that a resources stat information is not updated when the resource is being modified. @@ -157,6 +205,7 @@ class ResourceFacade { * [fs.Stats]{@link https://nodejs.org/api/fs.html#fs_class_fs_stats} instance. * * @public + * @deprecated Use dedicated APIs like Resource.getSize(), .isDirectory(), .getLastModified() instead * @returns {fs.Stats|object} Instance of [fs.Stats]{@link https://nodejs.org/api/fs.html#fs_class_fs_stats} * or similar object */ @@ -164,16 +213,47 @@ class ResourceFacade { return this.#resource.getStatInfo(); } + /** + * Checks whether the resource represents a directory. + * + * @public + * @returns {boolean} True if resource is a directory + */ + isDirectory() { + return this.#resource.isDirectory(); + } + + /** + * Gets the last modified timestamp of the resource. + * + * @public + * @returns {number} Last modified timestamp (in milliseconds since UNIX epoch) + */ + getLastModified() { + return this.#resource.getLastModified(); + } + /** * Size in bytes allocated by the underlying buffer. * * @see {TypedArray#byteLength} - * @returns {Promise} size in bytes, 0 if there is no content yet + * @returns {Promise} size in bytes, 0 if the resource has no content */ async getSize() { return this.#resource.getSize(); } + /** + * Checks whether the resource size can be determined without reading the entire content. + * E.g. for buffer-based content or if the size has been provided when the resource was created. + * + * @public + * @returns {boolean} True if size can be determined statically + */ + hasSize() { + return this.#resource.hasSize(); + } + /** * Adds a resource collection name that was involved in locating this resource. * diff --git a/packages/fs/lib/ResourceTagCollection.js b/packages/fs/lib/ResourceTagCollection.js index 9214c15bd0b..19232e9aa75 100644 --- a/packages/fs/lib/ResourceTagCollection.js +++ b/packages/fs/lib/ResourceTagCollection.js @@ -5,11 +5,18 @@ import ResourceFacade from "./ResourceFacade.js"; /** * A ResourceTagCollection * - * @private * @class * @alias @ui5/fs/internal/ResourceTagCollection */ class ResourceTagCollection { + /** + * Constructor + * + * @param {object} options Options + * @param {string[]} [options.allowedTags=[]] List of allowed tags + * @param {string[]} [options.allowedNamespaces=[]] List of allowed namespaces + * @param {object} [options.tags] Initial tags object mapping resource paths to their tags + */ constructor({allowedTags = [], allowedNamespaces = [], tags}) { this._allowedTags = allowedTags; // Allowed tags are validated during use this._allowedNamespaces = allowedNamespaces; @@ -32,6 +39,13 @@ class ResourceTagCollection { this._pathTags = tags || Object.create(null); } + /** + * Set a tag on a resource + * + * @param {string|object} resourcePath Path of the resource or a resource instance + * @param {string} tag Tag in the format "namespace:Name" + * @param {string|number|boolean} [value=true] Tag value + */ setTag(resourcePath, tag, value = true) { resourcePath = this._getPath(resourcePath); this._validateTag(tag); @@ -43,6 +57,12 @@ class ResourceTagCollection { this._pathTags[resourcePath][tag] = value; } + /** + * Clear a tag from a resource + * + * @param {string|object} resourcePath Path of the resource or a resource instance + * @param {string} tag Tag in the format "namespace:Name" + */ clearTag(resourcePath, tag) { resourcePath = this._getPath(resourcePath); this._validateTag(tag); @@ -52,6 +72,13 @@ class ResourceTagCollection { } } + /** + * Get a tag value from a resource + * + * @param {string|object} resourcePath Path of the resource or a resource instance + * @param {string} tag Tag in the format "namespace:Name" + * @returns {string|number|boolean|undefined} Tag value or undefined if not set + */ getTag(resourcePath, tag) { resourcePath = this._getPath(resourcePath); this._validateTag(tag); @@ -61,10 +88,21 @@ class ResourceTagCollection { } } + /** + * Get all tags for all resources + * + * @returns {object} Object mapping resource paths to their tags + */ getAllTags() { return this._pathTags; } + /** + * Check if a tag is accepted by this collection + * + * @param {string} tag Tag in the format "namespace:Name" + * @returns {boolean} Whether the tag is accepted + */ acceptsTag(tag) { if (this._allowedTags.includes(tag) || this._allowedNamespacesRegExp?.test(tag)) { return true; @@ -72,6 +110,12 @@ class ResourceTagCollection { return false; } + /** + * Extract the path from a resource or validate a path string + * + * @param {string|object} resourcePath Path of the resource or a resource instance + * @returns {string} Resolved resource path + */ _getPath(resourcePath) { if (typeof resourcePath !== "string") { if (resourcePath instanceof ResourceFacade) { @@ -86,6 +130,12 @@ class ResourceTagCollection { return resourcePath; } + /** + * Validate a tag format and check if it's accepted by this collection + * + * @param {string} tag Tag in the format "namespace:Name" + * @throws {Error} If the tag format is invalid or not accepted + */ _validateTag(tag) { if (!tag.includes(":")) { throw new Error(`Invalid Tag "${tag}": Colon required after namespace`); @@ -112,6 +162,12 @@ class ResourceTagCollection { } } + /** + * Validate that a tag value has an acceptable type + * + * @param {any} value Value to validate + * @throws {Error} If the value type is not string, number, or boolean + */ _validateValue(value) { const type = typeof value; if (!["string", "number", "boolean"].includes(type)) { diff --git a/packages/fs/lib/WriterCollection.js b/packages/fs/lib/WriterCollection.js index f601867632e..51f49f21f5d 100644 --- a/packages/fs/lib/WriterCollection.js +++ b/packages/fs/lib/WriterCollection.js @@ -62,10 +62,14 @@ class WriterCollection extends AbstractReaderWriter { this._writerMapping = writerMapping; this._readerCollection = new ReaderCollection({ name: `Reader collection of writer collection '${this._name}'`, - readers: Object.values(writerMapping) + readers: Array.from(new Set(Object.values(writerMapping))) // Ensure unique readers }); } + getMapping() { + return this._writerMapping; + } + /** * Locates resources by glob. * diff --git a/packages/fs/lib/adapters/AbstractAdapter.js b/packages/fs/lib/adapters/AbstractAdapter.js index 96cf4154250..9e0ee367cbb 100644 --- a/packages/fs/lib/adapters/AbstractAdapter.js +++ b/packages/fs/lib/adapters/AbstractAdapter.js @@ -17,20 +17,20 @@ import Resource from "../Resource.js"; */ class AbstractAdapter extends AbstractReaderWriter { /** - * The constructor * * @public * @param {object} parameters Parameters + * @param {string} parameters.name * @param {string} parameters.virBasePath * Virtual base path. Must be absolute, POSIX-style, and must end with a slash * @param {string[]} [parameters.excludes] List of glob patterns to exclude * @param {object} [parameters.project] Experimental, internal parameter. Do not use */ - constructor({virBasePath, excludes = [], project}) { + constructor({name, virBasePath, excludes = [], project}) { if (new.target === AbstractAdapter) { throw new TypeError("Class 'AbstractAdapter' is abstract"); } - super(); + super(name); if (!virBasePath) { throw new Error(`Unable to create adapter: Missing parameter 'virBasePath'`); @@ -81,17 +81,7 @@ class AbstractAdapter extends AbstractReaderWriter { if (patterns[i] && idx !== -1 && idx < this._virBaseDir.length) { const subPath = patterns[i]; return [ - this._createResource({ - statInfo: { // TODO: make closer to fs stat info - isDirectory: function() { - return true; - } - }, - source: { - adapter: "Abstract" - }, - path: subPath - }) + this._createDirectoryResource(subPath) ]; } } @@ -106,7 +96,7 @@ class AbstractAdapter extends AbstractReaderWriter { * @returns {boolean} True if path is excluded, otherwise false */ _isPathExcluded(virPath) { - return micromatch(virPath, this._excludes).length > 0; + return micromatch(virPath, this._excludes, {dot: true}).length > 0; } /** @@ -201,6 +191,10 @@ class AbstractAdapter extends AbstractReaderWriter { if (this._project) { parameters.project = this._project; } + if (!parameters.source) { + parameters.source = Object.create(null); + } + parameters.source.adapter = this.constructor.name; return new Resource(parameters); } @@ -289,6 +283,38 @@ class AbstractAdapter extends AbstractReaderWriter { const relPath = virPath.substr(this._virBasePath.length); return relPath; } + + _createDirectoryResource(dirPath) { + const now = new Date(); + const fnFalse = function() { + return false; + }; + const fnTrue = function() { + return true; + }; + const statInfo = { + isFile: fnFalse, + isDirectory: fnTrue, + isBlockDevice: fnFalse, + isCharacterDevice: fnFalse, + isSymbolicLink: fnFalse, + isFIFO: fnFalse, + isSocket: fnFalse, + size: 0, + atimeMs: now.getTime(), + mtimeMs: now.getTime(), + ctimeMs: now.getTime(), + birthtimeMs: now.getTime(), + atime: now, + mtime: now, + ctime: now, + birthtime: now, + }; + return this._createResource({ + statInfo: statInfo, + path: dirPath, + }); + } } export default AbstractAdapter; diff --git a/packages/fs/lib/adapters/FileSystem.js b/packages/fs/lib/adapters/FileSystem.js index d086fac40dd..84c133adbc1 100644 --- a/packages/fs/lib/adapters/FileSystem.js +++ b/packages/fs/lib/adapters/FileSystem.js @@ -7,12 +7,13 @@ const copyFile = promisify(fs.copyFile); const chmod = promisify(fs.chmod); const mkdir = promisify(fs.mkdir); const stat = promisify(fs.stat); +const readFile = promisify(fs.readFile); import {globby, isGitIgnored} from "globby"; import {PassThrough} from "node:stream"; import AbstractAdapter from "./AbstractAdapter.js"; const READ_ONLY_MODE = 0o444; -const ADAPTER_NAME = "FileSystem"; + /** * File system resource adapter * @@ -23,9 +24,9 @@ const ADAPTER_NAME = "FileSystem"; */ class FileSystem extends AbstractAdapter { /** - * The Constructor. * * @param {object} parameters Parameters + * @param {string} parameters.name * @param {string} parameters.virBasePath * Virtual base path. Must be absolute, POSIX-style, and must end with a slash * @param {string} parameters.fsBasePath @@ -35,8 +36,8 @@ class FileSystem extends AbstractAdapter { * Whether to apply any excludes defined in an optional .gitignore in the given fsBasePath directory * @param {@ui5/project/specifications/Project} [parameters.project] Project this adapter belongs to (if any) */ - constructor({virBasePath, project, fsBasePath, excludes, useGitignore=false}) { - super({virBasePath, project, excludes}); + constructor({name, virBasePath, project, fsBasePath, excludes, useGitignore=false}) { + super({name, virBasePath, project, excludes}); if (!fsBasePath) { throw new Error(`Unable to create adapter: Missing parameter 'fsBasePath'`); @@ -80,7 +81,7 @@ class FileSystem extends AbstractAdapter { statInfo: stat, path: this._virBaseDir, sourceMetadata: { - adapter: ADAPTER_NAME, + adapter: this.constructor.name, fsPath: this._fsBasePath }, createStream: () => { @@ -124,11 +125,14 @@ class FileSystem extends AbstractAdapter { statInfo: stat, path: virPath, sourceMetadata: { - adapter: ADAPTER_NAME, + adapter: this.constructor.name, fsPath: fsPath }, createStream: () => { return fs.createReadStream(fsPath); + }, + createBuffer: () => { + return readFile(fsPath); } })); } @@ -158,16 +162,8 @@ class FileSystem extends AbstractAdapter { // Neither starts with basePath, nor equals baseDirectory if (!options.nodir && this._virBasePath.startsWith(virPath)) { // Create virtual directories for the virtual base path (which has to exist) - // TODO: Maybe improve this by actually matching the base paths segments to the virPath - return this._createResource({ - project: this._project, - statInfo: { // TODO: make closer to fs stat info - isDirectory: function() { - return true; - } - }, - path: virPath - }); + // FUTURE: Maybe improve this by actually matching the base paths segments to the virPath + return this._createDirectoryResource(virPath); } else { return null; } @@ -200,7 +196,7 @@ class FileSystem extends AbstractAdapter { statInfo, path: virPath, sourceMetadata: { - adapter: ADAPTER_NAME, + adapter: this.constructor.name, fsPath } }; @@ -210,6 +206,9 @@ class FileSystem extends AbstractAdapter { resourceOptions.createStream = function() { return fs.createReadStream(fsPath); }; + resourceOptions.createBuffer = function() { + return readFile(fsPath); + }; } return this._createResource(resourceOptions); @@ -260,7 +259,7 @@ class FileSystem extends AbstractAdapter { await mkdir(dirPath, {recursive: true}); const sourceMetadata = resource.getSourceMetadata(); - if (sourceMetadata && sourceMetadata.adapter === ADAPTER_NAME && sourceMetadata.fsPath) { + if (sourceMetadata && sourceMetadata.adapter === this.constructor.name && sourceMetadata.fsPath) { // Resource has been created by FileSystem adapter. This means it might require special handling /* The following code covers these four conditions: diff --git a/packages/fs/lib/adapters/Memory.js b/packages/fs/lib/adapters/Memory.js index 35be99cf953..7215e2ab189 100644 --- a/packages/fs/lib/adapters/Memory.js +++ b/packages/fs/lib/adapters/Memory.js @@ -3,8 +3,6 @@ const log = getLogger("resources:adapters:Memory"); import micromatch from "micromatch"; import AbstractAdapter from "./AbstractAdapter.js"; -const ADAPTER_NAME = "Memory"; - /** * Virtual resource Adapter * @@ -15,17 +13,17 @@ const ADAPTER_NAME = "Memory"; */ class Memory extends AbstractAdapter { /** - * The constructor. * * @public * @param {object} parameters Parameters + * @param {string} parameters.name * @param {string} parameters.virBasePath * Virtual base path. Must be absolute, POSIX-style, and must end with a slash * @param {string[]} [parameters.excludes] List of glob patterns to exclude * @param {@ui5/project/specifications/Project} [parameters.project] Project this adapter belongs to (if any) */ - constructor({virBasePath, project, excludes}) { - super({virBasePath, project, excludes}); + constructor({name, virBasePath, project, excludes}) { + super({name, virBasePath, project, excludes}); this._virFiles = Object.create(null); // map full of files this._virDirs = Object.create(null); // map full of directories } @@ -72,18 +70,7 @@ class Memory extends AbstractAdapter { async _runGlob(patterns, options = {nodir: true}, trace) { if (patterns[0] === "" && !options.nodir) { // Match virtual root directory return [ - this._createResource({ - project: this._project, - statInfo: { // TODO: make closer to fs stat info - isDirectory: function() { - return true; - } - }, - sourceMetadata: { - adapter: ADAPTER_NAME - }, - path: this._virBasePath.slice(0, -1) - }) + this._createDirectoryResource(this._virBasePath.slice(0, -1)) ]; } @@ -157,18 +144,7 @@ class Memory extends AbstractAdapter { for (let i = pathSegments.length - 1; i >= 0; i--) { const segment = pathSegments[i]; if (!this._virDirs[segment]) { - this._virDirs[segment] = this._createResource({ - project: this._project, - sourceMetadata: { - adapter: ADAPTER_NAME - }, - statInfo: { // TODO: make closer to fs stat info - isDirectory: function() { - return true; - } - }, - path: this._virBasePath + segment - }); + this._virDirs[segment] = this._createDirectoryResource(this._virBasePath + segment); } } } diff --git a/packages/fs/lib/readers/Filter.js b/packages/fs/lib/readers/Filter.js index b95654daa29..903f43cef76 100644 --- a/packages/fs/lib/readers/Filter.js +++ b/packages/fs/lib/readers/Filter.js @@ -23,12 +23,13 @@ class Filter extends AbstractReader { * * @public * @param {object} parameters Parameters + * @param {object} parameters.name Name of the reader * @param {@ui5/fs/AbstractReader} parameters.reader The resource reader or collection to wrap * @param {@ui5/fs/readers/Filter~callback} parameters.callback * Filter function. Will be called for every resource read through this reader. */ - constructor({reader, callback}) { - super(); + constructor({name, reader, callback}) { + super(name); if (!reader) { throw new Error(`Missing parameter "reader"`); } diff --git a/packages/fs/lib/readers/Link.js b/packages/fs/lib/readers/Link.js index 726a22b763b..b21c7f469ae 100644 --- a/packages/fs/lib/readers/Link.js +++ b/packages/fs/lib/readers/Link.js @@ -42,11 +42,12 @@ class Link extends AbstractReader { * * @public * @param {object} parameters Parameters + * @param {object} parameters.name Name of the reader * @param {@ui5/fs/AbstractReader} parameters.reader The resource reader or collection to wrap * @param {@ui5/fs/readers/Link/PathMapping} parameters.pathMapping */ - constructor({reader, pathMapping}) { - super(); + constructor({name, reader, pathMapping}) { + super(name); if (!reader) { throw new Error(`Missing parameter "reader"`); } @@ -58,17 +59,7 @@ class Link extends AbstractReader { Link._validatePathMapping(pathMapping); } - /** - * Locates resources by glob. - * - * @private - * @param {string|string[]} patterns glob pattern as string or an array of - * glob patterns for virtual directory structure - * @param {object} options glob options - * @param {@ui5/fs/tracing/Trace} trace Trace instance - * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving to list of resources - */ - async _byGlob(patterns, options, trace) { + resolvePattern(patterns) { if (!(patterns instanceof Array)) { patterns = [patterns]; } @@ -80,7 +71,29 @@ class Link extends AbstractReader { }); // Flatten prefixed patterns - patterns = Array.prototype.concat.apply([], patterns); + return Array.prototype.concat.apply([], patterns); + } + + resolvePath(virPath) { + if (!virPath.startsWith(this._pathMapping.linkPath)) { + return null; + } + const targetPath = this._pathMapping.targetPath + virPath.substr(this._pathMapping.linkPath.length); + return targetPath; + } + + /** + * Locates resources by glob. + * + * @private + * @param {string|string[]} patterns glob pattern as string or an array of + * glob patterns for virtual directory structure + * @param {object} options glob options + * @param {@ui5/fs/tracing/Trace} trace Trace instance + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving to list of resources + */ + async _byGlob(patterns, options, trace) { + patterns = this.resolvePattern(patterns); // Keep resource's internal path unchanged for now const resources = await this._reader._byGlob(patterns, options, trace); @@ -105,10 +118,10 @@ class Link extends AbstractReader { * @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource */ async _byPath(virPath, options, trace) { - if (!virPath.startsWith(this._pathMapping.linkPath)) { + const targetPath = this.resolvePath(virPath); + if (!targetPath) { return null; } - const targetPath = this._pathMapping.targetPath + virPath.substr(this._pathMapping.linkPath.length); log.silly(`byPath: Rewriting virtual path ${virPath} to ${targetPath}`); const resource = await this._reader._byPath(targetPath, options, trace); diff --git a/packages/fs/lib/readers/Proxy.js b/packages/fs/lib/readers/Proxy.js new file mode 100644 index 00000000000..23f340f27bb --- /dev/null +++ b/packages/fs/lib/readers/Proxy.js @@ -0,0 +1,126 @@ +import micromatch from "micromatch"; +import AbstractReader from "../AbstractReader.js"; + +/** + * Callback function to retrieve a resource by its virtual path. + * + * @public + * @callback @ui5/fs/readers/Proxy~getResource + * @param {string} virPath Virtual path + * @returns {Promise} Promise resolving with a Resource instance + */ + +/** + * Callback function to list all available virtual resource paths. + * + * @public + * @callback @ui5/fs/readers/Proxy~listResourcePaths + * @returns {Promise} Promise resolving to an array of strings (the virtual resource paths) + */ + +/** + * Generic proxy adapter. Allowing to serve resources using callback functions. Read only. + * + * @public + * @class + * @alias @ui5/fs/readers/Proxy + * @extends @ui5/fs/readers/AbstractReader + */ +class Proxy extends AbstractReader { + /** + * Constructor + * + * @public + * @param {object} parameters + * @param {object} parameters.name Name of the reader + * @param {@ui5/fs/readers/Proxy~getResource} parameters.getResource + * Callback function to retrieve a resource by its virtual path. + * @param {@ui5/fs/readers/Proxy~listResourcePaths} parameters.listResourcePaths + * Callback function to list all available virtual resource paths. + * @returns {@ui5/fs/readers/Proxy} Reader instance + */ + constructor({name, getResource, listResourcePaths}) { + super(name); + if (typeof getResource !== "function") { + throw new Error(`Proxy adapter: Missing or invalid parameter 'getResource'`); + } + if (typeof listResourcePaths !== "function") { + throw new Error(`Proxy adapter: Missing or invalid parameter 'listResourcePaths'`); + } + this._getResource = getResource; + this._listResourcePaths = listResourcePaths; + } + + async _listResourcePaths() { + const virPaths = await this._listResourcePaths(); + if (!Array.isArray(virPaths) || !virPaths.every((p) => typeof p === "string")) { + throw new Error( + `Proxy adapter: 'listResourcePaths' did not return an array of strings`); + } + return virPaths; + } + + /** + * Matches and returns resources from a given map (either _virFiles or _virDirs). + * + * @private + * @param {string[]} patterns + * @param {string[]} resourcePaths + * @returns {Promise} + */ + async _matchPatterns(patterns, resourcePaths) { + const matchedPaths = micromatch(resourcePaths, patterns, { + dot: true + }); + return await Promise.all(matchedPaths.map((virPath) => { + return this._getResource(virPath); + })); + } + + /** + * Locate resources by glob. + * + * @private + * @param {string|string[]} virPattern glob pattern as string or array of glob patterns for + * virtual directory structure + * @param {object} [options={}] glob options + * @param {boolean} [options.nodir=true] Do not match directories + * @param {@ui5/fs/tracing.Trace} trace Trace instance + * @returns {Promise<@ui5/fs/Resource[]>} Promise resolving to list of resources + */ + async _byGlob(virPattern, options = {nodir: true}, trace) { + if (!(virPattern instanceof Array)) { + virPattern = [virPattern]; + } + + if (virPattern[0] === "" && !options.nodir) { // Match virtual root directory + return [ + this._createDirectoryResource(this._virBasePath.slice(0, -1)) + ]; + } + + return await this._matchPatterns(virPattern, await this._listResourcePaths()); + } + + /** + * Locates resources by path. + * + * @private + * @param {string} virPath Virtual path + * @param {object} options Options + * @param {@ui5/fs/tracing.Trace} trace Trace instance + * @returns {Promise<@ui5/fs/Resource>} Promise resolving to a single resource + */ + async _byPath(virPath, options, trace) { + trace.pathCall(); + + const resource = await this._getResource(virPath); + if (!resource || (options.nodir && resource.getStatInfo().isDirectory())) { + return null; + } else { + return resource; + } + } +} + +export default Proxy; diff --git a/packages/fs/lib/resourceFactory.js b/packages/fs/lib/resourceFactory.js index 6a98c9a7961..cfa27fd7bc5 100644 --- a/packages/fs/lib/resourceFactory.js +++ b/packages/fs/lib/resourceFactory.js @@ -9,6 +9,9 @@ import Resource from "./Resource.js"; import WriterCollection from "./WriterCollection.js"; import Filter from "./readers/Filter.js"; import Link from "./readers/Link.js"; +import Proxy from "./readers/Proxy.js"; +import MonitoredReader from "./MonitoredReader.js"; +import MonitoredReaderWriter from "./MonitoredReaderWriter.js"; import {getLogger} from "@ui5/logger"; const log = getLogger("resources:resourceFactory"); @@ -26,6 +29,7 @@ const log = getLogger("resources:resourceFactory"); * * @public * @param {object} parameters Parameters + * @param {string} parameters.name * @param {string} parameters.virBasePath Virtual base path. Must be absolute, POSIX-style, and must end with a slash * @param {string} [parameters.fsBasePath] * File System base path. @@ -38,11 +42,11 @@ const log = getLogger("resources:resourceFactory"); * @param {@ui5/project/specifications/Project} [parameters.project] Project this adapter belongs to (if any) * @returns {@ui5/fs/adapters/FileSystem|@ui5/fs/adapters/Memory} File System- or Virtual Adapter */ -export function createAdapter({fsBasePath, virBasePath, project, excludes, useGitignore}) { +export function createAdapter({name, fsBasePath, virBasePath, project, excludes, useGitignore}) { if (fsBasePath) { - return new FsAdapter({fsBasePath, virBasePath, project, excludes, useGitignore}); + return new FsAdapter({name, fsBasePath, virBasePath, project, excludes, useGitignore}); } else { - return new MemAdapter({virBasePath, project, excludes}); + return new MemAdapter({name, virBasePath, project, excludes}); } } @@ -178,15 +182,17 @@ export function createResource(parameters) { export function createWorkspace({reader, writer, virBasePath = "/", name = "workspace"}) { if (!writer) { writer = new MemAdapter({ + name: `Workspace writer for ${name}`, virBasePath }); } - return new DuplexCollection({ + const d = new DuplexCollection({ reader, writer, name }); + return d; } /** @@ -234,6 +240,19 @@ export function createLinkReader(parameters) { return new Link(parameters); } +/** + * @param {object} parameters + * @param {object} parameters.name Name of the reader + * @param {@ui5/fs/readers/Proxy~getResource} parameters.getResource + * Callback function to retrieve a resource by its virtual path. + * @param {@ui5/fs/readers/Proxy~listResourcePaths} parameters.listResourcePaths + * Callback function to list all available virtual resource paths. + * @returns {@ui5/fs/readers/Proxy} Reader instance + */ +export function createProxy(parameters) { + return new Proxy(parameters); +} + /** * Create a [Link-Reader]{@link @ui5/fs/readers/Link} where all requests are prefixed with * /resources/<namespace>. @@ -243,12 +262,14 @@ export function createLinkReader(parameters) { * * @public * @param {object} parameters + * @param {string} parameters.name * @param {@ui5/fs/AbstractReader} parameters.reader Single reader or collection of readers * @param {string} parameters.namespace Project namespace * @returns {@ui5/fs/readers/Link} Reader instance */ -export function createFlatReader({reader, namespace}) { +export function createFlatReader({name, reader, namespace}) { return new Link({ + name, reader: reader, pathMapping: { linkPath: `/`, @@ -257,6 +278,13 @@ export function createFlatReader({reader, namespace}) { }); } +export function createMonitor(readerWriter) { + if (readerWriter instanceof DuplexCollection) { + return new MonitoredReaderWriter(readerWriter); + } + return new MonitoredReader(readerWriter); +} + /** * Normalizes virtual glob patterns by prefixing them with * a given virtual base directory path diff --git a/packages/fs/package.json b/packages/fs/package.json index 3a81ef91183..cb2166595e2 100644 --- a/packages/fs/package.json +++ b/packages/fs/package.json @@ -29,7 +29,8 @@ "./Resource": "./lib/Resource.js", "./resourceFactory": "./lib/resourceFactory.js", "./package.json": "./package.json", - "./internal/ResourceTagCollection": "./lib/ResourceTagCollection.js" + "./internal/ResourceTagCollection": "./lib/ResourceTagCollection.js", + "./internal/MonitoredResourceTagCollection": "./lib/MonitoredResourceTagCollection.js" }, "engines": { "node": "^22.20.0 || >=24.0.0", @@ -56,6 +57,7 @@ }, "dependencies": { "@ui5/logger": "^5.0.0-alpha.3", + "async-mutex": "^0.5.0", "clone": "^2.1.2", "escape-string-regexp": "^5.0.0", "globby": "^15.0.0", @@ -63,7 +65,8 @@ "micromatch": "^4.0.8", "minimatch": "^10.2.2", "pretty-hrtime": "^1.0.3", - "random-int": "^3.1.0" + "random-int": "^3.1.0", + "ssri": "^13.0.0" }, "devDependencies": { "@istanbuljs/esm-loader-hook": "^0.3.0", diff --git a/packages/fs/test/lib/Resource.js b/packages/fs/test/lib/Resource.js index aca04728746..aa0d64fa507 100644 --- a/packages/fs/test/lib/Resource.js +++ b/packages/fs/test/lib/Resource.js @@ -1,18 +1,21 @@ import test from "ava"; +import sinon from "sinon"; import {Stream, Transform} from "node:stream"; -import {promises as fs, createReadStream} from "node:fs"; +import {statSync, createReadStream} from "node:fs"; +import {stat, readFile} from "node:fs/promises"; import path from "node:path"; import Resource from "../../lib/Resource.js"; function createBasicResource() { const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); + const statInfo = statSync(fsPath); const resource = new Resource({ path: "/app/index.html", createStream: function() { return createReadStream(fsPath); }, project: {}, - statInfo: {}, + statInfo: statInfo, fsPath }); return resource; @@ -39,6 +42,10 @@ const readStream = (readableStream) => { }); }; +test.afterEach.always((t) => { + sinon.restore(); +}); + test("Resource: constructor with missing path parameter", (t) => { t.throws(() => { new Resource({}); @@ -85,12 +92,152 @@ test("Resource: constructor with duplicated content parameter", (t) => { new Resource(resourceParams); }, { instanceOf: Error, - message: "Unable to create Resource: Please set only one content parameter. " + - "'buffer', 'string', 'stream' or 'createStream'" + message: "Unable to create Resource: Multiple content parameters provided. " + + "Please provide only one of the following parameters: 'buffer', 'string', 'stream' or 'createStream'" }, "Threw with expected error message"); }); }); +test("Resource: constructor with createBuffer factory must provide createStream", (t) => { + t.throws(() => { + new Resource({ + path: "/my/path", + createBuffer: () => Buffer.from("Content"), + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'createStream' must be provided when " + + "parameter 'createBuffer' is used" + }); +}); + +test("Resource: constructor with invalid createBuffer parameter", (t) => { + t.throws(() => { + new Resource({ + path: "/my/path", + createBuffer: "not a function", + createStream: () => { + return new Stream.Readable(); + } + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'createBuffer' must be a function" + }); +}); + +test("Resource: constructor with invalid content parameters", (t) => { + t.throws(() => { + new Resource({ + path: "/my/path", + createStream: "not a function" + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'createStream' must be a function" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + stream: "not a stream" + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'stream' must be a readable stream" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + buffer: "not a buffer" + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'buffer' must be of type Buffer" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + string: 123 + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'string' must be of type string" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + buffer: Buffer.from("Content"), + byteSize: -1 + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'byteSize' must be a positive number" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + buffer: Buffer.from("Content"), + byteSize: "not a number" + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'byteSize' must be a positive number" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + buffer: Buffer.from("Content"), + lastModified: -1 + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'lastModified' must be a positive number" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + buffer: Buffer.from("Content"), + lastModified: "not a number" + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: Parameter 'lastModified' must be a positive number" + }); + + const invalidStatInfo = { + isDirectory: () => false, + isFile: () => false, + size: 100, + mtimeMs: Date.now() + }; + t.throws(() => { + new Resource({ + path: "/my/path", + statInfo: invalidStatInfo + }); + }, { + instanceOf: Error, + message: "Unable to create Resource: statInfo must represent either a file or a directory" + }); + + t.throws(() => { + new Resource({ + path: "/my/path", + sourceMetadata: "invalid value" + }); + }, { + instanceOf: Error, + message: `Unable to create Resource: Parameter 'sourceMetadata' must be of type "object"` + }); +}); + test("Resource: From buffer", async (t) => { const resource = new Resource({ path: "/my/path", @@ -126,6 +273,24 @@ test("Resource: From createStream", async (t) => { const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); const resource = new Resource({ path: "/my/path", + byteSize: 91, + createStream: () => { + return createReadStream(fsPath); + } + }); + t.is(await resource.getSize(), 91, "Content is set"); + t.false(resource.isModified(), "Content of new resource is not modified"); + t.false(resource.getSourceMetadata().contentModified, "Content of new resource is not modified"); +}); + +test("Resource: From createBuffer", async (t) => { + const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); + const resource = new Resource({ + path: "/my/path", + byteSize: 91, + createBuffer: async () => { + return Buffer.from(await readFile(fsPath)); + }, createStream: () => { return createReadStream(fsPath); } @@ -150,6 +315,7 @@ test("Resource: Source metadata", async (t) => { t.is(resource.getSourceMetadata().adapter, "My Adapter", "Correct source metadata 'adapter' value"); t.is(resource.getSourceMetadata().fsPath, "/some/path", "Correct source metadata 'fsPath' value"); }); + test("Resource: Source metadata with modified content", async (t) => { const resource = new Resource({ path: "/my/path", @@ -307,6 +473,31 @@ test("Resource: getStream throwing an error", (t) => { }); }); +test("Resource: getStream call while resource is being transformed", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + stream: new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + }) + }); + + const p1 = resource.getBuffer(); // Trigger async transformation of stream to buffer + t.throws(() => { + resource.getStream(); // Synchronous getStream can't wait for transformation to finish + }, { + message: /Content of Resource \/my\/path\/to\/resource is currently being transformed. Consider using Resource.getStreamAsync\(\) to wait for the transformation to finish./ + }); + await p1; // Wait for initial transformation to finish + + t.false(resource.isModified(), "Resource has not been modified"); + + const value = await resource.getString(); + t.is(value, "Stream content", "Initial content still set"); +}); + test("Resource: setString", async (t) => { const resource = new Resource({ path: "/my/path/to/resource", @@ -315,11 +506,13 @@ test("Resource: setString", async (t) => { t.is(resource.getSourceMetadata().contentModified, false, "sourceMetadata modified flag set correctly"); t.false(resource.isModified(), "Resource is not modified"); + t.falsy(resource.getLastModified(), "lastModified is not set"); resource.setString("Content"); t.is(resource.getSourceMetadata().contentModified, true, "sourceMetadata modified flag updated correctly"); t.true(resource.isModified(), "Resource is modified"); + t.truthy(resource.getLastModified(), "lastModified should be updated"); const value = await resource.getString(); t.is(value, "Content", "String set"); @@ -333,20 +526,48 @@ test("Resource: setBuffer", async (t) => { t.is(resource.getSourceMetadata().contentModified, false, "sourceMetadata modified flag set correctly"); t.false(resource.isModified(), "Resource is not modified"); + t.falsy(resource.getLastModified(), "lastModified is not set"); resource.setBuffer(Buffer.from("Content")); t.is(resource.getSourceMetadata().contentModified, true, "sourceMetadata modified flag updated correctly"); t.true(resource.isModified(), "Resource is modified"); + t.truthy(resource.getLastModified(), "lastModified should be updated"); const value = await resource.getString(); t.is(value, "Content", "String set"); }); +test("Resource: setBuffer call while resource is being transformed", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + stream: new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + }) + }); + + const p1 = resource.getBuffer(); // Trigger async transformation of stream to buffer + t.throws(() => { + resource.setBuffer(Buffer.from("Content")); // Set new buffer while transformation is still ongoing + }, { + message: `Unable to set buffer: Content of Resource /my/path/to/resource is currently being transformed` + }); + await p1; // Wait for initial transformation to finish + + t.false(resource.isModified(), "Resource has not been modified"); + + const value = await resource.getString(); + t.is(value, "Stream content", "Initial content still set"); +}); + test("Resource: size modification", async (t) => { const resource = new Resource({ path: "/my/path/to/resource" }); + t.true(resource.hasSize(), "resource without content has size"); t.is(await resource.getSize(), 0, "initial size without content"); // string @@ -361,9 +582,11 @@ test("Resource: size modification", async (t) => { // buffer resource.setBuffer(Buffer.from("Super")); + t.true(resource.hasSize(), "has size"); t.is(await resource.getSize(), 5, "size after manually setting the string"); const clonedResource1 = await resource.clone(); + t.true(clonedResource1.hasSize(), "has size after cloning"); t.is(await clonedResource1.getSize(), 5, "size after cloning the resource"); // buffer with alloc @@ -378,6 +601,7 @@ test("Resource: size modification", async (t) => { }).getSize(), 1234, "buffer with alloc when passing buffer to constructor"); const clonedResource2 = await resource.clone(); + t.true(clonedResource2.hasSize(), "buffer with alloc after clone has size"); t.is(await clonedResource2.getSize(), 1234, "buffer with alloc after clone"); // stream @@ -392,9 +616,11 @@ test("Resource: size modification", async (t) => { stream.push(null); streamResource.setStream(stream); + t.false(streamResource.hasSize(), "size not yet known for streamResource"); // stream is read and stored in buffer // test parallel size retrieval + await streamResource.getBuffer(); const [size1, size2] = await Promise.all([streamResource.getSize(), streamResource.getSize()]); t.is(size1, 23, "size for streamResource, parallel 1"); t.is(size2, 23, "size for streamResource, parallel 2"); @@ -409,6 +635,7 @@ test("Resource: setStream (Stream)", async (t) => { t.is(resource.getSourceMetadata().contentModified, false, "sourceMetadata modified flag set correctly"); t.false(resource.isModified(), "Resource is not modified"); + t.falsy(resource.getLastModified(), "lastModified is not set"); const stream = new Stream.Readable(); stream._read = function() {}; @@ -421,6 +648,7 @@ test("Resource: setStream (Stream)", async (t) => { t.is(resource.getSourceMetadata().contentModified, true, "sourceMetadata modified flag updated correctly"); t.true(resource.isModified(), "Resource is modified"); + t.truthy(resource.getLastModified(), "lastModified should be updated"); const value = await resource.getString(); t.is(value, "I am a readable stream!", "Stream set correctly"); @@ -434,6 +662,7 @@ test("Resource: setStream (Create stream callback)", async (t) => { t.is(resource.getSourceMetadata().contentModified, false, "sourceMetadata modified flag set correctly"); t.false(resource.isModified(), "Resource is not modified"); + t.falsy(resource.getLastModified(), "lastModified is not set"); resource.setStream(() => { const stream = new Stream.Readable(); @@ -447,11 +676,38 @@ test("Resource: setStream (Create stream callback)", async (t) => { t.is(resource.getSourceMetadata().contentModified, true, "sourceMetadata modified flag updated correctly"); t.true(resource.isModified(), "Resource is modified"); + t.truthy(resource.getLastModified(), "lastModified should be updated"); const value = await resource.getString(); t.is(value, "I am a readable stream!", "Stream set correctly"); }); +test("Resource: setStream call while resource is being transformed", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + stream: new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + }) + }); + + const p1 = resource.getBuffer(); // Trigger async transformation of stream to buffer + t.throws(() => { + resource.setStream(new Stream.Readable()); // Set new stream while transformation is still ongoing + }, { + message: `Unable to set stream: Content of Resource /my/path/to/resource is currently being transformed` + }); + await p1; // Wait for initial transformation to finish + + t.false(resource.isModified(), "Resource has not been modified"); + + const value = await resource.getString(); + t.is(value, "Stream content", "Initial content still set"); +}); + + test("Resource: clone resource with buffer", async (t) => { t.plan(2); @@ -487,6 +743,89 @@ test("Resource: clone resource with stream", async (t) => { t.is(clonedResourceContent, "Content", "Cloned resource has correct content string"); }); +test("Resource: clone resource with createBuffer factory", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + createStream: () => { + const stream = new Stream.Readable(); + stream._read = function() {}; + stream.push("Stream Content"); + stream.push(null); + return stream; + }, + createBuffer: async () => { + return Buffer.from("Buffer Content"); + } + + }); + + const clonedResource = await resource.clone(); + + const clonedResourceContent = await clonedResource.getString(); + t.is(clonedResourceContent, "Buffer Content", "Cloned resource has correct content string"); +}); + +test("Resource: clone resource with createStream factory", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + createStream: () => { + const stream = new Stream.Readable(); + stream._read = function() {}; + stream.push("Stream Content"); + stream.push(null); + return stream; + }, + }); + + const clonedResource = await resource.clone(); + + const clonedResourceContent = await clonedResource.getString(); + t.is(clonedResourceContent, "Stream Content", "Cloned resource has correct content string"); +}); + +test("Resource: clone resource with stream during transformation to buffer", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource" + }); + const stream = new Stream.Readable(); + stream._read = function() {}; + stream.push("Content"); + stream.push(null); + + resource.setStream(stream); + + const p1 = resource.getBuffer(); // Trigger async transformation of stream to buffer + + const clonedResource = await resource.clone(); + t.pass("Resource cloned"); + await p1; // Wait for initial transformation to finish + + t.is(await resource.getString(), "Content", "Original resource has correct content string"); + t.is(await clonedResource.getString(), "Content", "Cloned resource has correct content string"); +}); + +test("Resource: clone resource while stream is drained/waiting for new content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource" + }); + const stream = new Stream.Readable(); + stream._read = function() {}; + stream.push("Content"); + stream.push(null); + + resource.setStream(stream); + + resource.getStream(); // Drain stream + + const p1 = resource.clone(); // Trigger async clone while stream is drained + + resource.setString("New Content"); + const clonedResource = await p1; // Wait for clone to finish + + t.is(await resource.getString(), "New Content", "Original resource has correct content string"); + t.is(await clonedResource.getString(), "New Content", "Cloned resource has correct content string"); +}); + test("Resource: clone resource with sourceMetadata", async (t) => { const resource = new Resource({ path: "/my/path/to/resource", @@ -553,6 +892,7 @@ test("Resource: create resource with sourceMetadata.contentModified: true", (t) t.true(resource.getSourceMetadata().contentModified, "Modified flag is still true"); t.false(resource.isModified(), "Resource is not modified"); + t.falsy(resource.getLastModified(), "lastModified is not set"); }); test("getStream with createStream callback content: Subsequent content requests should throw error due " + @@ -561,9 +901,13 @@ test("getStream with createStream callback content: Subsequent content requests resource.getStream(); t.throws(() => { resource.getStream(); - }, {message: /Content of Resource \/app\/index.html has been drained/}); - await t.throwsAsync(resource.getBuffer(), {message: /Content of Resource \/app\/index.html has been drained/}); - await t.throwsAsync(resource.getString(), {message: /Content of Resource \/app\/index.html has been drained/}); + }, {message: /Content of Resource \/app\/index.html is currently flagged as drained. Consider using Resource\.getStreamAsync\(\) to wait for new content./}); + await t.throwsAsync(resource.getBuffer(), { + message: /Timeout waiting for content of Resource \/app\/index.html to become available/ + }); + await t.throwsAsync(resource.getString(), { + message: /Timeout waiting for content of Resource \/app\/index.html to become available/ + }); }); test("getStream with Buffer content: Subsequent content requests should throw error due to drained " + @@ -573,9 +917,9 @@ test("getStream with Buffer content: Subsequent content requests should throw er resource.getStream(); t.throws(() => { resource.getStream(); - }, {message: /Content of Resource \/app\/index.html has been drained/}); - await t.throwsAsync(resource.getBuffer(), {message: /Content of Resource \/app\/index.html has been drained/}); - await t.throwsAsync(resource.getString(), {message: /Content of Resource \/app\/index.html has been drained/}); + }, {message: /Content of Resource \/app\/index.html is currently flagged as drained. Consider using Resource\.getStreamAsync\(\) to wait for new content./}); + await t.throwsAsync(resource.getBuffer(), {message: /Timeout waiting for content of Resource \/app\/index.html to become available./}); + await t.throwsAsync(resource.getString(), {message: /Timeout waiting for content of Resource \/app\/index.html to become available./}); }); test("getStream with Stream content: Subsequent content requests should throw error due to drained " + @@ -594,51 +938,552 @@ test("getStream with Stream content: Subsequent content requests should throw er resource.getStream(); t.throws(() => { resource.getStream(); - }, {message: /Content of Resource \/app\/index.html has been drained/}); - await t.throwsAsync(resource.getBuffer(), {message: /Content of Resource \/app\/index.html has been drained/}); - await t.throwsAsync(resource.getString(), {message: /Content of Resource \/app\/index.html has been drained/}); + }, {message: /Content of Resource \/app\/index.html is currently flagged as drained. Consider using Resource\.getStreamAsync\(\) to wait for new content./}); + await t.throwsAsync(resource.getBuffer(), {message: /Timeout waiting for content of Resource \/app\/index.html to become available./}); + await t.throwsAsync(resource.getString(), {message: /Timeout waiting for content of Resource \/app\/index.html to become available./}); }); -test("getBuffer from Stream content: Subsequent content requests should not throw error due to drained " + - "content", async (t) => { - const resource = createBasicResource(); - const tStream = new Transform({ - transform(chunk, encoding, callback) { - this.push(chunk.toString()); - callback(); - } +test("getStream from factory content: Prefers createStream factory over createBuffer", async (t) => { + const createBufferStub = sinon.stub().resolves(Buffer.from("Buffer content")); + const createStreamStub = sinon.stub().returns( + new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + })); + const resource = new Resource({ + path: "/my/path/to/resource", + createBuffer: createBufferStub, + createStream: createStreamStub }); - const stream = resource.getStream(); - stream.pipe(tStream); - resource.setStream(tStream); - - const p1 = resource.getBuffer(); - const p2 = resource.getBuffer(); - - await t.notThrowsAsync(p1); - - // Race condition in _getBufferFromStream used to cause p2 - // to throw "Content stream of Resource /app/index.html is flagged as drained." - await t.notThrowsAsync(p2); + const stream = await resource.getStream(); + const streamedResult = await readStream(stream); + t.is(streamedResult, "Stream content", "getStream used createStream factory"); + t.true(createStreamStub.calledOnce, "createStream factory called once"); + t.false(createBufferStub.called, "createBuffer factory not called"); }); -test("Resource: getProject", (t) => { - t.plan(1); +test("getStreamAsync with Buffer content", async (t) => { const resource = new Resource({ path: "/my/path/to/resource", - project: {getName: () => "Mock Project"} + buffer: Buffer.from("Content") }); - const project = resource.getProject(); - t.is(project.getName(), "Mock Project"); + + const stream = await resource.getStreamAsync(); + const result = await readStream(stream); + t.is(result, "Content", "Stream has been read correctly"); }); -test("Resource: setProject", (t) => { - t.plan(1); +test("getStreamAsync with createStream callback", async (t) => { + const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); const resource = new Resource({ - path: "/my/path/to/resource" + path: "/my/path/to/resource", + createStream: () => { + return createReadStream(fsPath); + } }); - const project = {getName: () => "Mock Project"}; - resource.setProject(project); + + const stream = await resource.getStreamAsync(); + const result = await readStream(stream); + t.is(result.length, 91, "Stream content has correct length"); +}); + +test("getStreamAsync with Stream content", async (t) => { + const stream = new Stream.Readable(); + stream._read = function() {}; + stream.push("Stream "); + stream.push("content!"); + stream.push(null); + + const resource = new Resource({ + path: "/my/path/to/resource", + stream + }); + + const resultStream = await resource.getStreamAsync(); + const result = await readStream(resultStream); + t.is(result, "Stream content!", "Stream has been read correctly"); +}); + +test("getStreamAsync: Factory content can be used to create new streams after setting new content", async (t) => { + const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); + const createStreamStub = sinon.stub().returns( + new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + })); + const resource = new Resource({ + path: "/my/path/to/resource", + createStream: createStreamStub, + }); + + // First call creates a stream + const stream1 = await resource.getStreamAsync(); + const result1 = await readStream(stream1); + t.is(result1.length, 14, "First stream read successfully"); + t.is(createStreamStub.callCount, 1, "Factory called once"); + + // Content is now drained. To call getStreamAsync again, we need to set new content + // by calling setStream with the factory again + resource.setStream(() => createReadStream(fsPath)); + + const stream2 = await resource.getStreamAsync(); + const result2 = await readStream(stream2); + t.is(result2.length, 91, "Second stream read successfully after resetting content"); +}); + +test("getStreamAsync: Waits for new content after stream is drained", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "Initial content" + }); + + const stream1 = await resource.getStreamAsync(); + const result1 = await readStream(stream1); + t.is(result1, "Initial content", "First stream read successfully"); + + // Content is now drained, set new content + setTimeout(() => { + resource.setString("New content"); + }, 10); + + const stream2 = await resource.getStreamAsync(); + const result2 = await readStream(stream2); + t.is(result2, "New content", "Second stream read successfully after setting new content"); +}); + +test("getStreamAsync: Waits for content transformation to complete", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + stream: new Stream.Readable({ + read() { + this.push("Initial content"); + this.push(null); + } + }) + }); + + // Start getBuffer which will transform content + const bufferPromise = resource.getBuffer(); + + // Immediately call getStreamAsync while transformation is in progress + const streamPromise = resource.getStreamAsync(); + + // Both should complete successfully + await bufferPromise; + const stream = await streamPromise; + const result = await readStream(stream); + t.is(result, "Initial content", "Stream read successfully after waiting for transformation"); +}); + +test("getStreamAsync with no content throws error", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource" + }); + + await t.throwsAsync(resource.getStreamAsync(), { + message: "Resource /my/path/to/resource has no content" + }); +}); + +test("getStreamAsync from factory content: Prefers createStream factory", async (t) => { + const createBufferStub = sinon.stub().resolves(Buffer.from("Buffer content")); + const createStreamStub = sinon.stub().returns( + new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + })); + const resource = new Resource({ + path: "/my/path/to/resource", + createBuffer: createBufferStub, + createStream: createStreamStub + }); + + const stream = await resource.getStreamAsync(); + const streamedResult = await readStream(stream); + t.is(streamedResult, "Stream content", "getStreamAsync used createStream factory"); + t.true(createStreamStub.calledOnce, "createStream factory called once"); + t.false(createBufferStub.called, "createBuffer factory not called"); +}); + +test("modifyStream: Modify buffer content with transform stream", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "hello world" + }); + + t.false(resource.isModified(), "Resource is not modified initially"); + + await resource.modifyStream(async (stream) => { + const content = await readStream(stream); + return Buffer.from(content.toUpperCase()); + }); + + const result = await resource.getString(); + t.is(result, "HELLO WORLD", "Content was modified correctly"); + t.true(resource.isModified(), "Resource is marked as modified"); +}); + +test("modifyStream: Return new stream from callback", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "test content" + }); + + await resource.modifyStream((stream) => { + const transformStream = new Transform({ + transform(chunk, encoding, callback) { + this.push(chunk.toString().toUpperCase()); + callback(); + } + }); + stream.pipe(transformStream); + return transformStream; + }); + + const result = await resource.getString(); + t.is(result, "TEST CONTENT", "Content was modified with transform stream"); +}); + +test("modifyStream: Can modify multiple times", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "test" + }); + + await resource.modifyStream(async (stream) => { + const content = await readStream(stream); + return Buffer.from(content + " modified"); + }); + + t.is(await resource.getString(), "test modified", "First modification applied"); + + await resource.modifyStream(async (stream) => { + const content = await readStream(stream); + return Buffer.from(content + " again"); + }); + + t.is(await resource.getString(), "test modified again", "Second modification applied"); +}); + +test("modifyStream: Works with factory content", async (t) => { + const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); + const resource = new Resource({ + path: "/my/path/to/resource", + createStream: () => createReadStream(fsPath) + }); + + await resource.modifyStream(async (stream) => { + const content = await readStream(stream); + return Buffer.from(content.toUpperCase()); + }); + + const result = await resource.getString(); + t.true(result.includes(""), "Content was read and modified from factory"); +}); + +test("modifyStream: Waits for drained content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "initial" + }); + + // Drain the content + const stream1 = await resource.getStreamAsync(); + await readStream(stream1); + + // Set new content after a delay + setTimeout(() => { + resource.setString("new content"); + }, 10); + + // modifyStream should wait for new content + await resource.modifyStream(async (stream) => { + const content = await readStream(stream); + return Buffer.from(content.toUpperCase()); + }); + + const result = await resource.getString(); + t.is(result, "NEW CONTENT", "modifyStream waited for new content and modified it"); +}); + +test("modifyStream: Locks content during modification", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "test" + }); + + const modifyPromise = resource.modifyStream(async (stream) => { + // Simulate slow transformation + await new Promise((resolve) => setTimeout(resolve, 20)); + const content = await readStream(stream); + return Buffer.from(content.toUpperCase()); + }); + + // Try to access content while modification is in progress + // This should wait for the lock to be released + const bufferPromise = resource.getBuffer(); + + await modifyPromise; + const buffer = await bufferPromise; + + t.is(buffer.toString(), "TEST", "Content access waited for modification to complete"); +}); + +test("modifyStream: Throws error if callback returns invalid content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "test" + }); + + await t.throwsAsync( + resource.modifyStream(async (stream) => { + return "not a buffer or stream"; + }), + { + message: "Unable to set new content: Content must be either a Buffer or a Readable Stream" + } + ); +}); + +test("modifyStream: Async callback returning Promise", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "async test" + }); + + await resource.modifyStream(async (stream) => { + const content = await readStream(stream); + // Simulate async operation + await new Promise((resolve) => setTimeout(resolve, 5)); + return Buffer.from(content.replace("async", "ASYNC")); + }); + + const result = await resource.getString(); + t.is(result, "ASYNC test", "Async callback worked correctly"); +}); + +test("modifyStream: Sync callback returning Buffer", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "sync test" + }); + + await resource.modifyStream((stream) => { + // Return buffer synchronously + return Buffer.from("SYNC TEST"); + }); + + const result = await resource.getString(); + t.is(result, "SYNC TEST", "Sync callback returning Buffer worked correctly"); +}); + +test("modifyStream: Updates modified flag", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "test", + sourceMetadata: {} + }); + + t.false(resource.isModified(), "Resource is not marked as modified"); + t.false(resource.getSourceMetadata().contentModified, "contentModified is false initially"); + + await resource.modifyStream(async (stream) => { + const content = await readStream(stream); + return Buffer.from(content.toUpperCase()); + }); + + t.true(resource.isModified(), "Resource is marked as modified"); + t.true(resource.getSourceMetadata().contentModified, "contentModified is true after modification"); +}); + +test("getBuffer from Stream content with known size", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + byteSize: 14, + stream: new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + }) + }); + + const p1 = resource.getBuffer(); + const p2 = resource.getBuffer(); + + t.is((await p1).toString(), "Stream content"); + t.is((await p2).toString(), "Stream content"); +}); + +test("getBuffer from Stream content with incorrect size", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + byteSize: 80, + stream: new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + }) + }); + + await await t.throwsAsync(resource.getBuffer(), { + message: `Stream ended early: expected 80 bytes, got 14` + }, `Threw with expected error message`); + + const resource2 = new Resource({ + path: "/my/path/to/resource", + byteSize: 1, + stream: new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + }) + }); + + await await t.throwsAsync(resource2.getBuffer(), { + message: `Stream exceeded expected size: 1, got at least 14` + }, `Threw with expected error message`); +}); + +test("getBuffer from Stream content with stream error", async (t) => { + let destroyCalled = false; + const resource = new Resource({ + path: "/my/path/to/resource", + byteSize: 14, + stream: new Stream.Readable({ + read() { + this.emit("error", new Error("Stream failure")); + }, + destroy(err, callback) { + destroyCalled = true; + // The error will be present when stream.destroy is called due to the error + t.truthy(err, "destroy called with error"); + callback(err); + } + }) + }); + + await t.throwsAsync(resource.getBuffer()); + t.true(destroyCalled, "Stream destroy was called due to error"); +}); + +test("getBuffer from Stream content: Subsequent content requests should not throw error due to drained " + + "content", async (t) => { + const resource = createBasicResource(); + const tStream = new Transform({ + transform(chunk, encoding, callback) { + this.push(chunk.toString()); + callback(); + } + }); + const stream = resource.getStream(); + stream.pipe(tStream); + resource.setStream(tStream); + + const p1 = resource.getBuffer(); + const p2 = resource.getBuffer(); + + await t.notThrowsAsync(p1); + + // Race condition in _getBufferFromStream used to cause p2 + // to throw "Content stream of Resource /app/index.html is flagged as drained." + await t.notThrowsAsync(p2); +}); + +test("getBuffer from Stream content: getBuffer call while stream is consumed and new content is not yet set", + async (t) => { + const resource = createBasicResource(); + const tStream = new Transform({ + transform(chunk, encoding, callback) { + this.push(chunk.toString()); + callback(); + } + }); + const stream = resource.getStream(); + const p1 = resource.getBuffer(); + stream.pipe(tStream); + resource.setStream(tStream); + + const p2 = resource.getBuffer(); + + await t.notThrowsAsync(p1); + + // Race condition in _getBufferFromStream used to cause p2 + // to throw "Content stream of Resource /app/index.html is flagged as drained." + await t.notThrowsAsync(p2); + }); + +test("getBuffer from factory content: Prefers createBuffer factory over createStream", async (t) => { + const createBufferStub = sinon.stub().resolves(Buffer.from("Buffer content")); + const createStreamStub = sinon.stub().returns( + new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + })); + const resource = new Resource({ + path: "/my/path/to/resource", + createBuffer: createBufferStub, + createStream: createStreamStub + }); + const buffer = await resource.getBuffer(); + t.is(buffer.toString(), "Buffer content", "getBuffer used createBuffer factory"); + t.true(createBufferStub.calledOnce, "createBuffer factory called once"); + t.false(createStreamStub.called, "createStream factory not called"); + + // Calling getBuffer again should not call factories again + const buffer2 = await resource.getBuffer(); + t.is(buffer2, buffer, "getBuffer returned same buffer instance"); + t.true(createBufferStub.calledOnce, "createBuffer factory still called only once"); +}); + +test("getBuffer from factory content: Factory does not return buffer instance", async (t) => { + const createBufferStub = sinon.stub().resolves("Buffer content"); + const createStreamStub = sinon.stub().returns( + new Stream.Readable({ + read() { + this.push("Stream content"); + this.push(null); + } + })); + const resource = new Resource({ + path: "/my/path/to/resource", + createBuffer: createBufferStub, + createStream: createStreamStub + }); + await t.throwsAsync(resource.getBuffer(), { + message: `Buffer factory of Resource /my/path/to/resource did not return a Buffer instance` + }, `Threw with expected error message`); + t.true(createBufferStub.calledOnce, "createBuffer factory called once"); + t.false(createStreamStub.called, "createStream factory not called"); +}); + +test("Resource: getProject", (t) => { + t.plan(1); + const resource = new Resource({ + path: "/my/path/to/resource", + project: {getName: () => "Mock Project"} + }); + const project = resource.getProject(); + t.is(project.getName(), "Mock Project"); +}); + +test("Resource: setProject", (t) => { + t.plan(1); + const resource = new Resource({ + path: "/my/path/to/resource" + }); + const project = {getName: () => "Mock Project"}; + resource.setProject(project); t.is(resource.getProject().getName(), "Mock Project"); }); @@ -665,6 +1510,7 @@ test("Resource: constructor with stream", async (t) => { const resource = new Resource({ path: "/my/path/to/resource", stream, + byteSize: 23, sourceMetadata: {} // Needs to be passed in order to get the "modified" state }); @@ -677,9 +1523,9 @@ test("Resource: constructor with stream", async (t) => { t.is(resource.getSourceMetadata().contentModified, false); }); -test("integration stat - resource size", async (t) => { +test("integration stat", async (t) => { const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); - const statInfo = await fs.stat(fsPath); + const statInfo = await stat(fsPath); const resource = new Resource({ path: "/some/path", @@ -689,11 +1535,302 @@ test("integration stat - resource size", async (t) => { } }); t.is(await resource.getSize(), 91); + t.false(resource.isDirectory()); + t.is(resource.getLastModified(), statInfo.mtimeMs); // Setting the same content again should end up with the same size resource.setString(await resource.getString()); t.is(await resource.getSize(), 91); + t.true(resource.getLastModified() > statInfo.mtimeMs, "lastModified should be updated"); resource.setString("myvalue"); t.is(await resource.getSize(), 7); }); + +test("getSize", async (t) => { + const fsPath = path.join("test", "fixtures", "application.a", "webapp", "index.html"); + const statInfo = await stat(fsPath); + + const resource = new Resource({ + path: "/some/path", + byteSize: statInfo.size, + createStream: () => { + return createReadStream(fsPath); + } + }); + t.true(resource.hasSize()); + t.is(await resource.getSize(), 91); + + const resourceNoSize = new Resource({ + path: "/some/path", + createStream: () => { + return createReadStream(fsPath); + } + }); + t.false(resourceNoSize.hasSize(), "Resource with createStream and no byteSize has no size"); + t.is(await resourceNoSize.getSize(), 91); +}); + +/* Integrity Glossary + + "Content" = "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=" + "New content" = "sha256-EvQbHDId8MgpzlgZllZv3lKvbK/h0qDHRmzeU+bxPMo=" +*/ +test("getIntegrity: Throws error for directory resource", async (t) => { + const resource = new Resource({ + path: "/my/directory", + isDirectory: true + }); + + await t.throwsAsync(resource.getIntegrity(), { + message: "Unable to calculate integrity for directory resource: /my/directory" + }); +}); + +test("getIntegrity: Returns integrity for buffer content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + buffer: Buffer.from("Content") + }); + + const integrity = await resource.getIntegrity(); + t.is(integrity, "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=", + "Correct integrity for content"); +}); + +test("getIntegrity: Returns integrity for stream content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + stream: new Stream.Readable({ + read() { + this.push("Content"); + this.push(null); + } + }), + }); + + const integrity = await resource.getIntegrity(); + t.is(integrity, "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=", + "Correct integrity for content"); +}); + +test("getIntegrity: Returns integrity for factory content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + createStream: () => { + return new Stream.Readable({ + read() { + this.push("Content"); + this.push(null); + } + }); + } + }); + + const integrity = await resource.getIntegrity(); + t.is(integrity, "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=", + "Correct integrity for content"); +}); + +test("getIntegrity: Throws error for resource with no content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource" + }); + + await t.throwsAsync(resource.getIntegrity(), { + message: "Resource /my/path/to/resource has no content" + }); +}); + +test("getIntegrity: Different content produces different integrities", async (t) => { + const resource1 = new Resource({ + path: "/my/path/to/resource1", + string: "Content 1" + }); + + const resource2 = new Resource({ + path: "/my/path/to/resource2", + string: "Content 2" + }); + + const integrity1 = await resource1.getIntegrity(); + const integrity2 = await resource2.getIntegrity(); + + t.not(integrity1, integrity2, "Different content produces different integrities"); +}); + +test("getIntegrity: Same content produces same integrity", async (t) => { + const resource1 = new Resource({ + path: "/my/path/to/resource1", + string: "Content" + }); + + const resource2 = new Resource({ + path: "/my/path/to/resource2", + buffer: Buffer.from("Content") + }); + + const resource3 = new Resource({ + path: "/my/path/to/resource2", + stream: new Stream.Readable({ + read() { + this.push("Content"); + this.push(null); + } + }), + }); + + const integrity1 = await resource1.getIntegrity(); + const integrity2 = await resource2.getIntegrity(); + const integrity3 = await resource3.getIntegrity(); + + t.is(integrity1, integrity2, "Same content produces same integrity for string and buffer content"); + t.is(integrity1, integrity3, "Same content produces same integrity for string and stream"); +}); + +test("getIntegrity: Waits for drained content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "Initial content" + }); + + // Drain the stream + await resource.getStreamAsync(); + const p1 = resource.getIntegrity(); // Start getIntegrity which should wait for new content + + resource.setString("New content"); + + const integrity = await p1; + t.is(integrity, "sha256-EvQbHDId8MgpzlgZllZv3lKvbK/h0qDHRmzeU+bxPMo=", + "Correct integrity for new content"); +}); + +test("getIntegrity: Waits for content transformation to complete", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + stream: new Stream.Readable({ + read() { + this.push("Content"); + this.push(null); + } + }) + }); + + // Start getBuffer which will transform content + const bufferPromise = resource.getBuffer(); + + // Immediately call getIntegrity while transformation is in progress + const integrityPromise = resource.getIntegrity(); + + // Both should complete successfully + await bufferPromise; + const integrity = await integrityPromise; + t.is(integrity, "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=", + "Correct integrity after waiting for transformation"); +}); + +test("getIntegrity: Can be called multiple times on buffer content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + buffer: Buffer.from("Content") + }); + + const integrity1 = await resource.getIntegrity(); + const integrity2 = await resource.getIntegrity(); + const integrity3 = await resource.getIntegrity(); + + t.is(integrity1, integrity2, "First and second integrity are identical"); + t.is(integrity2, integrity3, "Second and third integrity are identical"); + + t.is(integrity1, "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=", + "Correct integrity for content"); +}); + +test("getIntegrity: Can be called multiple times on factory content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + createStream: () => { + return new Stream.Readable({ + read() { + this.push("Content"); + this.push(null); + } + }); + } + }); + + const integrity1 = await resource.getIntegrity(); + const integrity2 = await resource.getIntegrity(); + const integrity3 = await resource.getIntegrity(); + + t.is(integrity1, integrity2, "First and second integrity are identical"); + t.is(integrity2, integrity3, "Second and third integrity are identical"); + + t.is(integrity1, "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=", + "Correct integrity for content"); +}); + +test("getIntegrity: Can be called multiple times on stream content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + stream: new Stream.Readable({ + read() { + this.push("Content"); + this.push(null); + } + }) + }); + + const integrity1 = await resource.getIntegrity(); + const integrity2 = await resource.getIntegrity(); + const integrity3 = await resource.getIntegrity(); + + t.is(integrity1, integrity2, "First and second integrity are identical"); + t.is(integrity2, integrity3, "Second and third integrity are identical"); + + t.is(integrity1, "sha256-R70pB1+LgBnwvuxthr7afJv2eq8FBT3L4LO8tjloUX8=", + "Correct integrity for content"); +}); + +test("getIntegrity: Integrity changes after content modification", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "Original content" + }); + + const integrity1 = await resource.getIntegrity(); + t.is(integrity1, "sha256-OUni2q0Lopc2NkTnXeaaYPNQJNUATQtbAqMWJvtCVNo=", + "Correct integrity for original content"); + + resource.setString("Modified content"); + + const integrity2 = await resource.getIntegrity(); + t.is(integrity2, "sha256-8fba0TDG5CusKMUf/7GVTTxaYjVbRXacQv2lt3RdtT8=", + "Integrity changes after content modification"); + t.not(integrity1, integrity2, "New integrity is different from original"); +}); + +test("getIntegrity: Works with empty content", async (t) => { + const resource = new Resource({ + path: "/my/path/to/resource", + string: "" + }); + + const integrity = await resource.getIntegrity(); + + t.is(integrity, "sha256-47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=", + "Correct integrity for empty content"); +}); + +test("getIntegrity: Works with large content", async (t) => { + const largeContent = "x".repeat(1024 * 1024); // 1MB of 'x' + const resource = new Resource({ + path: "/my/path/to/resource", + string: largeContent + }); + + const integrity = await resource.getIntegrity(); + + t.is(integrity, "sha256-j5kLoLV3tRzwCeoEk2jBa72hsh4bk74HqCR1i7JTw5s=", + "Correct integrity for large content"); +}); diff --git a/packages/fs/test/lib/ResourceFacade.js b/packages/fs/test/lib/ResourceFacade.js index 5dee2fc8f1c..cabaa1b6748 100644 --- a/packages/fs/test/lib/ResourceFacade.js +++ b/packages/fs/test/lib/ResourceFacade.js @@ -17,6 +17,7 @@ test("Create instance", (t) => { resource }); t.is(resourceFacade.getPath(), "/my/path", "Returns correct path"); + t.is(resourceFacade.getOriginalPath(), "/my/path/to/resource", "Returns correct original path"); t.is(resourceFacade.getName(), "path", "Returns correct name"); t.is(resourceFacade.getConcealedResource(), resource, "Returns correct concealed resource"); }); @@ -86,7 +87,7 @@ test("ResourceFacade provides same public functions as Resource", (t) => { methods.forEach((method) => { t.truthy(resourceFacade[method], `resourceFacade provides function #${method}`); - if (["constructor", "getPath", "getName", "setPath", "clone"].includes(method)) { + if (["constructor", "getPath", "getOriginalPath", "getName", "setPath", "clone"].includes(method)) { // special functions with separate tests return; } diff --git a/packages/fs/test/lib/adapters/FileSystem_write.js b/packages/fs/test/lib/adapters/FileSystem_write.js index 8386c2a5487..33b3a72f021 100644 --- a/packages/fs/test/lib/adapters/FileSystem_write.js +++ b/packages/fs/test/lib/adapters/FileSystem_write.js @@ -116,7 +116,7 @@ test("Write modified resource in drain mode", async (t) => { await t.notThrowsAsync(fileEqual(t, destFsPath, "./test/fixtures/application.a/webapp/index.html")); await t.throwsAsync(resource.getBuffer(), - {message: /Content of Resource \/app\/index.html has been drained/}); + {message: /Timeout waiting for content of Resource \/app\/index.html to become available./}); }); test("Write with readOnly and drain options set should fail", async (t) => { @@ -216,7 +216,7 @@ test("Write modified resource into same file in drain mode", async (t) => { await t.notThrowsAsync(fileEqual(t, destFsPath, "./test/fixtures/application.a/webapp/index.html")); await t.throwsAsync(resource.getBuffer(), - {message: /Content of Resource \/app\/index.html has been drained/}); + {message: /Timeout waiting for content of Resource \/app\/index.html to become available./}); }); test("Write modified resource into same file in read-only mode", async (t) => { @@ -268,7 +268,7 @@ test("Write new resource in drain mode", async (t) => { await readerWriters.dest.write(resource, {drain: true}); await t.notThrowsAsync(fileContent(t, destFsPath, "Resource content")); await t.throwsAsync(resource.getBuffer(), - {message: /Content of Resource \/app\/index.html has been drained/}); + {message: /Timeout waiting for content of Resource \/app\/index.html to become available./}); }); test("Write new resource in read-only mode", async (t) => { diff --git a/packages/fs/test/lib/package-exports.js b/packages/fs/test/lib/package-exports.js index 13201c3d901..9000ba782cb 100644 --- a/packages/fs/test/lib/package-exports.js +++ b/packages/fs/test/lib/package-exports.js @@ -12,7 +12,7 @@ test("export of package.json", (t) => { // Check number of definied exports test("check number of exports", (t) => { const packageJson = require("@ui5/fs/package.json"); - t.is(Object.keys(packageJson.exports).length, 12); + t.is(Object.keys(packageJson.exports).length, 13); }); // Public API contract (exported modules) @@ -74,6 +74,10 @@ test("check number of exports", (t) => { exportedSpecifier: "@ui5/fs/internal/ResourceTagCollection", mappedModule: "../../lib/ResourceTagCollection.js" }, + { + exportedSpecifier: "@ui5/fs/internal/MonitoredResourceTagCollection", + mappedModule: "../../lib/MonitoredResourceTagCollection.js" + }, ].forEach(({exportedSpecifier, mappedModule}) => { test(`${exportedSpecifier}`, async (t) => { const actual = await import(exportedSpecifier); diff --git a/packages/logger/lib/loggers/ProjectBuild.js b/packages/logger/lib/loggers/ProjectBuild.js index 61c81115963..57856e211fe 100644 --- a/packages/logger/lib/loggers/ProjectBuild.js +++ b/packages/logger/lib/loggers/ProjectBuild.js @@ -48,7 +48,7 @@ class ProjectBuild extends Logger { }); } - startTask(taskName) { + startTask(taskName, isDifferentialBuild) { if (!this.#tasksToRun || !this.#tasksToRun.includes(taskName)) { throw new Error(`loggers/ProjectBuild#startTask: Unknown task ${taskName}`); } @@ -59,6 +59,7 @@ class ProjectBuild extends Logger { projectType: this.#projectType, taskName, status: "task-start", + isDifferentialBuild, }); if (!hasListeners) { @@ -66,7 +67,7 @@ class ProjectBuild extends Logger { } } - endTask(taskName) { + endTask(taskName, isDifferentialBuild) { if (!this.#tasksToRun || !this.#tasksToRun.includes(taskName)) { throw new Error(`loggers/ProjectBuild#endTask: Unknown task ${taskName}`); } @@ -77,6 +78,7 @@ class ProjectBuild extends Logger { projectType: this.#projectType, taskName, status: "task-end", + isDifferentialBuild, }); if (!hasListeners) { diff --git a/packages/logger/lib/writers/Console.js b/packages/logger/lib/writers/Console.js index 8243df7720c..846701cf807 100644 --- a/packages/logger/lib/writers/Console.js +++ b/packages/logger/lib/writers/Console.js @@ -334,7 +334,7 @@ class Console { } projectMetadata.buildSkipped = true; message = `${chalk.yellow(figures.tick)} ` + - `Skipping build of ${projectType} project ${chalk.bold(projectName)}`; + chalk.grey(`Skipping build of ${projectType} project ${chalk.bold(projectName)}`); // Update progress bar (if used) // All tasks of this projects are completed @@ -349,7 +349,7 @@ class Console { this.#writeMessage(level, `${chalk.grey(buildIndex)}: ${message}`); } - #handleProjectBuildStatusEvent({level, projectName, projectType, taskName, status}) { + #handleProjectBuildStatusEvent({level, projectName, projectType, taskName, status, isDifferentialBuild}) { const {projectTasks} = this.#getProjectMetadata(projectName); const taskMetadata = projectTasks.get(taskName); if (!taskMetadata) { @@ -382,7 +382,8 @@ class Console { `for project ${projectName}, task ${taskName}`); } taskMetadata.executionStarted = true; - message = `${chalk.blue(figures.pointerSmall)} Running task ${chalk.bold(taskName)}...`; + message = (isDifferentialBuild ? chalk.grey(figures.lozengeOutline) : chalk.blue(figures.pointerSmall)) + + ` Running task ${chalk.bold(taskName)}...`; break; case "task-end": if (taskMetadata.executionEnded) { @@ -412,7 +413,7 @@ class Console { `Task execution already started`); } taskMetadata.executionEnded = true; - message = `${chalk.green(figures.tick)} Skipping task ${chalk.bold(taskName)}`; + message = chalk.yellow(figures.tick) + chalk.grey(` Skipping task ${chalk.bold(taskName)}`); // Update progress bar (if used) this._getProgressBar()?.increment(1); diff --git a/packages/logger/test/lib/loggers/ProjectBuild.js b/packages/logger/test/lib/loggers/ProjectBuild.js index cc8ae00d45a..446732d41fe 100644 --- a/packages/logger/test/lib/loggers/ProjectBuild.js +++ b/packages/logger/test/lib/loggers/ProjectBuild.js @@ -115,6 +115,7 @@ test.serial("Start task", (t) => { projectType: "projectType", status: "task-start", taskName: "task.a", + isDifferentialBuild: undefined, }, "Metadata event has expected payload"); t.is(logHandler.callCount, 0, "No log event emitted"); @@ -135,6 +136,7 @@ test.serial("End task", (t) => { projectType: "projectType", status: "task-end", taskName: "task.a", + isDifferentialBuild: undefined, }, "Metadata event has expected payload"); t.is(logHandler.callCount, 0, "No log event emitted"); diff --git a/packages/project/lib/build/BuildReader.js b/packages/project/lib/build/BuildReader.js new file mode 100644 index 00000000000..51abd136ee3 --- /dev/null +++ b/packages/project/lib/build/BuildReader.js @@ -0,0 +1,165 @@ +import AbstractReader from "@ui5/fs/AbstractReader"; + +/** + * Reader for accessing build results of multiple projects + * + * Provides efficient resource access by delegating to appropriate project readers + * based on resource paths and namespaces. Supports namespace-based routing to + * minimize unnecessary project searches. + * + * @class + * @extends @ui5/fs/AbstractReader + */ +class BuildReader extends AbstractReader { + #projects; + #projectNames; + #applicationProjectName; + #namespaces = new Map(); + #buildServerInterface; + + /** + * Creates a new BuildReader instance + * + * @public + * @param {string} name Name of the reader + * @param {Array<@ui5/project/specifications/Project>} projects Array of projects to read from + * @param {object} buildServerInterface Function that returns a reader for a single project by name + * @throws {Error} If multiple projects share the same namespace + */ + constructor(name, projects, buildServerInterface) { + super(name); + this.#projects = projects; + this.#projectNames = projects.map((p) => p.getName()); + this.#buildServerInterface = buildServerInterface; + + for (const project of projects) { + const ns = project.getNamespace(); + // Not all projects have a namespace, e.g. modules or theme-libraries + if (ns) { + if (this.#namespaces.has(ns)) { + throw new Error(`Multiple projects with namespace '${ns}' found: ` + + `${this.#namespaces.get(ns)} and ${project.getName()}`); + } + this.#namespaces.set(ns, project.getName()); + } + + if (project.getType() === "application") { + this.#applicationProjectName = project.getName(); + } + } + } + + /** + * Locates resources by glob pattern + * + * Retrieves a combined reader for all projects and delegates the glob search to it. + * + * @public + * @param {...*} args Arguments to pass to the underlying reader's byGlob method + * @returns {Promise>} Promise resolving to list of resources + */ + async byGlob(...args) { + const reader = await this.#buildServerInterface.getReaderForProjects(this.#projectNames); + return reader.byGlob(...args); + } + + /** + * Locates a resource by path + * + * Attempts to determine the appropriate project reader based on the resource path + * and namespace. Falls back to searching all projects if the resource cannot be found. + * + * @public + * @param {string} virPath Virtual path of the resource + * @param {...*} args Additional arguments to pass to the underlying reader's byPath method + * @returns {Promise<@ui5/fs/Resource|null>} Promise resolving to resource or null if not found + */ + async byPath(virPath, ...args) { + const reader = await this._getReaderForResource(virPath); + let res = await reader.byPath(virPath, ...args); + if (!res) { + // Fallback to unspecified projects + const allReader = await this.#buildServerInterface.getReaderForProjects(this.#projectNames); + res = await allReader.byPath(virPath, ...args); + } + return res; + } + + /** + * Gets the appropriate reader for a resource at the given path + * + * Determines which project(s) might contain the resource based on namespace matching + * and returns a reader for those projects. For single-project readers, returns that + * project's reader directly. + * + * @param {string} virPath Virtual path of the resource + * @returns {Promise<@ui5/fs/AbstractReader>} Promise resolving to appropriate reader + */ + async _getReaderForResource(virPath) { + if (this.#projects.length === 1) { + // Filtering on a single project (typically the root project) + return await this.#buildServerInterface.getReaderForProject(this.#projectNames[0]); + } + // Determine project for resource path + const projects = this._getProjectsForResourcePath(virPath); + if (projects.length) { + return await this.#buildServerInterface.getReaderForProjects(projects); + } + + // Unable to determine project for resource using path + // Fallback 1: Try to find resource in cached readers (if available) to identify the relevant project + const cachedReader = this.#buildServerInterface.getCachedReadersForProjects(this.#projectNames); + if (cachedReader) { + const res = await cachedReader.byPath(virPath); + if (res) { + // Found resource in one of the cached readers. Assume it still belongs to the associated project + return this.#buildServerInterface.getReaderForProject(res.getProject().getName()); + } + } + + // Fallback 2: If the root project is of type application, and the request does not start with + // /resources/ or /test-resources/, test whether the resource can be found in the root project + if (this.#applicationProjectName && !virPath.startsWith("/resources/") && + !virPath.startsWith("/test-resources/")) { + const appReader = await this.#buildServerInterface.getReaderForProject(this.#applicationProjectName); + const res = await appReader.byPath(virPath); + if (res) { + return appReader; + } + } + + // Fallback to request a reader for all projects + return await this.#buildServerInterface.getReaderForProjects(this.#projectNames); + } + + /** + * Determines which projects might contain the resource for the given path + * + * Analyzes the resource path to identify matching project namespaces. Only processes + * paths starting with /resources/ or /test-resources/. Returns project names in order + * from most specific to least specific namespace match. + * + * @param {string} virPath Virtual resource path + * @returns {string[]} Array of project names that might contain the resource + */ + _getProjectsForResourcePath(virPath) { + if (!virPath.startsWith("/resources/") && !virPath.startsWith("/test-resources/")) { + return []; + } + // Remove first two entries (e.g. "/resources/") + const parts = virPath.split("/").slice(2); + + const projectNames = []; + while (parts.length > 1) { + // Search for namespace, starting with the longest path + parts.pop(); + const ns = parts.join("/"); + if (this.#namespaces.has(ns)) { + projectNames.push(this.#namespaces.get(ns)); + } + } + return projectNames; + } +} + +export default BuildReader; diff --git a/packages/project/lib/build/BuildServer.js b/packages/project/lib/build/BuildServer.js new file mode 100644 index 00000000000..ebeb9744e78 --- /dev/null +++ b/packages/project/lib/build/BuildServer.js @@ -0,0 +1,509 @@ +import EventEmitter from "node:events"; +import {createReaderCollectionPrioritized} from "@ui5/fs/resourceFactory"; +import BuildReader from "./BuildReader.js"; +import WatchHandler from "./helpers/WatchHandler.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("build:BuildServer"); + +class AbortBuildError extends Error { + constructor(message) { + super(message); + this.name = "AbortBuildError"; + } +}; + +/** + * Development server that provides access to built project resources with automatic rebuilding + * + * BuildServer watches project sources for changes and automatically rebuilds affected projects + * on-demand. It provides readers for accessing built resources and emits events for build + * completion and source changes. + * + * The server maintains separate readers for: + * - All projects (root + dependencies) + * - Root project only + * - Dependencies only + * + * Projects are built lazily when their resources are first requested, and rebuilt automatically + * when source files change. + * + * @class + * @extends EventEmitter + * @fires BuildServer#buildFinished + * @fires BuildServer#sourcesChanged + * @fires BuildServer#error + */ +class BuildServer extends EventEmitter { + #graph; + #projectBuilder; + #watchHandler; + #rootProjectName; + #resourceChangeQueue = new Map(); + #projectBuildStatus = new Map(); + #pendingBuildRequest = new Set(); + #activeBuild = null; + #processBuildRequestsTimeout; + #allReader; + #rootReader; + #dependenciesReader; + + /** + * Creates a new BuildServer instance + * + * Initializes readers for different project combinations, sets up file watching, + * and optionally performs an initial build of specified dependencies. + * + * @public + * @param {@ui5/project/graph/ProjectGraph} graph Project graph containing all projects + * @param {@ui5/project/build/ProjectBuilder} projectBuilder Builder instance for executing builds + * @param {boolean} initialBuildRootProject Whether to build the root project in the initial build + * @param {string[]} initialBuildIncludedDependencies Project names to include in initial build + * @param {string[]} initialBuildExcludedDependencies Project names to exclude from initial build + */ + constructor( + graph, projectBuilder, + initialBuildRootProject, initialBuildIncludedDependencies, initialBuildExcludedDependencies + ) { + super(); + this.#graph = graph; + this.#rootProjectName = graph.getRoot().getName(); + this.#projectBuilder = projectBuilder; + + const buildServerInterface = { + getReaderForProject: this.#getReaderForProject.bind(this), + getReaderForProjects: this.#getReaderForProjects.bind(this), + getCachedReadersForProjects: this.#getCachedReadersForProjects.bind(this), + }; + + this.#allReader = new BuildReader( + "Build Server: All Projects Reader", Array.from(this.#graph.getProjects()), buildServerInterface); + + const rootProject = this.#graph.getRoot(); + this.#rootReader = new BuildReader("Build Server: Root Project Reader", [rootProject], buildServerInterface); + + const dependencies = graph.getTransitiveDependencies(rootProject.getName()).map((dep) => graph.getProject(dep)); + this.#dependenciesReader = new BuildReader( + "Build Server: Dependencies Reader", dependencies, buildServerInterface); + + // Initialize cache states + this.#projectBuildStatus.set(this.#rootProjectName, new ProjectBuildStatus()); + + for (const dep of dependencies) { + this.#projectBuildStatus.set(dep.getName(), new ProjectBuildStatus()); + } + + if (initialBuildRootProject) { + log.verbose("Enqueueing root project for initial build"); + this.#enqueueBuild(this.#rootProjectName); + } + if (initialBuildIncludedDependencies.length > 0) { + // Enqueue initial build dependencies + for (const projectName of initialBuildIncludedDependencies) { + if (!initialBuildExcludedDependencies.includes(projectName)) { + log.verbose(`Enqueueing project '${projectName}' for initial build`); + this.#enqueueBuild(projectName); + } + } + } + + const watchHandler = new WatchHandler(); + this.#watchHandler = watchHandler; + const allProjects = graph.getProjects(); + watchHandler.watch(allProjects).catch((err) => { + // Error during watch setup + this.emit("error", err); + }); + watchHandler.on("error", (err) => { + this.emit("error", err); + }); + watchHandler.on("change", (eventType, resourcePath, project) => { + log.verbose(`Source change detected: ${eventType} ${resourcePath} in project '${project.getName()}'`); + this._projectResourceChanged(project, resourcePath, ["add", "unlink", "unlinkDir"].includes(eventType)); + }); + } + + async destroy() { + await this.#watchHandler.destroy(); + if (this.#activeBuild) { + // Await active build to finish + await this.#activeBuild; + } + } + + /** + * Gets a reader for all projects (root and dependencies) + * + * Returns a reader that provides access to built resources from all projects in the graph. + * Projects are built on-demand when their resources are requested. + * + * @public + * @returns {BuildReader} Reader for all projects + */ + getReader() { + return this.#allReader; + } + + /** + * Gets a reader for the root project only + * + * Returns a reader that provides access to built resources from only the root project, + * excluding all dependencies. The root project is built on-demand when its resources + * are requested. + * + * @public + * @returns {BuildReader} Reader for root project + */ + getRootReader() { + return this.#rootReader; + } + + /** + * Gets a reader for dependencies only (excluding root project) + * + * Returns a reader that provides access to built resources from all transitive + * dependencies of the root project. Dependencies are built on-demand when their + * resources are requested. + * + * @public + * @returns {BuildReader} Reader for all dependencies + */ + getDependenciesReader() { + return this.#dependenciesReader; + } + + /** + * Gets a reader for a single project, building it if necessary + * + * Checks if the project has already been built and returns its reader from cache. + * If not built, enqueues the project for building and returns a promise that + * resolves when the reader is available. + * + * @param {string} projectName Name of the project to get reader for + * @returns {Promise<@ui5/fs/AbstractReader>} Reader for the built project + */ + async #getReaderForProject(projectName) { + if (!this.#projectBuildStatus.has(projectName)) { + throw new Error(`Project '${projectName}' not found in project graph`); + } + const projectBuildStatus = this.#projectBuildStatus.get(projectName); + + if (projectBuildStatus.isFresh()) { + return projectBuildStatus.getReader(); + } + const {promise, resolve, reject} = Promise.withResolvers(); + projectBuildStatus.addReaderRequest({resolve, reject}); + + log.verbose(`Reader for project '${projectName}' is not fresh. Enqueuing build request.`); + this.#enqueueBuild(projectName); + return promise; + } + + /** + * Gets a combined reader for multiple projects, building them if necessary + * + * Enqueues all projects that need to be built and waits for all of them to complete. + * Returns a prioritized collection reader combining all requested projects. + * + * @param {string[]} projectNames Array of project names to get readers for + * @returns {Promise<@ui5/fs/ReaderCollection>} Combined reader for all requested projects + */ + async #getReaderForProjects(projectNames) { + if (projectNames.length === 1) { + return await this.#getReaderForProject(projectNames[0]); + } + const readers = await Promise.all(projectNames.map((projectName) => this.#getReaderForProject(projectName))); + return createReaderCollectionPrioritized({ + name: `Build Server: Reader for projects: ${projectNames.join(", ")}`, + readers + }); + } + + #getCachedReadersForProjects(projectNames) { + const readers = []; + for (const projectName of projectNames) { + const projectBuildStatus = this.#projectBuildStatus.get(projectName); + const reader = projectBuildStatus.getReader(); + if (reader) { + readers.push(reader); + } + } + if (!readers.length) { + return; + } + + return createReaderCollectionPrioritized({ + name: `Build Server: Cached readers for projects: ${projectNames.join(", ")}`, + readers + }); + } + + /** + * Several projects might be affected by the source file change. + * However, at this time we can't tell for sure which ones: + * Only the project builder can determine the affected projects for a given (set of) source file changes. + * This check is only possible while no build is running, and is therefore only done in the batched change handler. + * + * Assuming that the change in source files might corrupt a currently running (or about to be started) build, + * we abort all active builds affecting the changed project or any of its dependents. + * + * @param {@ui5/project/specifications/Project} project Project where the resource change occurred + * @param {string} filePath Path of the affected file + * @param {boolean} fileAddedOrRemoved Whether a file was added or removed + */ + _projectResourceChanged(project, filePath, fileAddedOrRemoved) { + // First, invalidate all potentially affected projects (which also aborts any running builds) + for (const {project: affectedProject} of this.#graph.traverseDependents(project.getName(), true)) { + const projectBuildStatus = this.#projectBuildStatus.get(affectedProject.getName()); + projectBuildStatus.invalidate(`Source change in project '${project.getName()}'`); + if (fileAddedOrRemoved) { + // Reset any cached readers in case files were added or removed + projectBuildStatus.resetReaderCache(); + } + } + + // Enqueue resource change for processing before next build + const queuedChanges = this.#resourceChangeQueue.get(project.getName()); + if (queuedChanges) { + queuedChanges.add(filePath); + } else { + this.#resourceChangeQueue.set(project.getName(), new Set([filePath])); + } + + // : Emit event debounced + // Emit change event immediately so that consumers can react to it (like browser reloading) + // const changedResourcePaths = [...changes.values()].flat(); + // this.emit("sourcesChanged", changedResourcePaths); + } + + #flushResourceChanges() { + if (this.#resourceChangeQueue.size === 0) { + return; + } + const changes = this.#resourceChangeQueue; + this.#resourceChangeQueue = new Map(); + + // Inform project builder + // This is essential so that the project builder can determine changed resources as it does not + // use file watchers or check for all changed files by itself + this.#projectBuilder.resourcesChanged(changes); + } + + /** + * Enqueues a project for building and returns a promise that resolves with its reader + * + * If the project is already queued, returns the existing promise. Otherwise, creates + * a new promise, adds the project to the pending build queue, and triggers queue processing. + * + * @param {string} projectName Name of the project to enqueue + */ + #enqueueBuild(projectName) { + if (this.#pendingBuildRequest.has(projectName)) { + // Already queued + return; + } + + log.verbose(`Enqueuing project '${projectName}' for build`); + + // Add to pending build requests + this.#pendingBuildRequest.add(projectName); + + this.#triggerRequestQueue(); + } + + #triggerRequestQueue() { + if (this.#activeBuild) { + return; + } + // If no build is active, trigger queue processing debounced + if (this.#processBuildRequestsTimeout) { + clearTimeout(this.#processBuildRequestsTimeout); + } + this.#processBuildRequestsTimeout = setTimeout(() => { + this.#processBuildRequests().catch((err) => { + this.emit("error", err); + }); + }, 10); + } + + /** + * Processes the build queue by batching pending projects and building them + * + * Runs while there are pending build requests. Collects all pending projects, + * builds them in a single batch, resolves/rejects promises for built projects, + * and handles errors with proper isolation. + * + * @returns {Promise} Promise that resolves when queue processing is complete + */ + async #processBuildRequests() { + // Process queue while there are pending requests + while (this.#pendingBuildRequest.size > 0) { + // Collect all pending projects for this batch + const projectsToBuild = Array.from(this.#pendingBuildRequest); + let buildRootProject = false; + let dependenciesToBuild; + const rootProjectIdx = projectsToBuild.indexOf(this.#rootProjectName); + if (rootProjectIdx !== -1) { + buildRootProject = true; + dependenciesToBuild = projectsToBuild.toSpliced(rootProjectIdx, 1); + } else { + dependenciesToBuild = projectsToBuild; + } + this.#pendingBuildRequest.clear(); + + log.verbose(`Building projects: ${projectsToBuild.join(", ")}`); + const signal = AbortSignal.any(projectsToBuild.map((projectName) => { + return this.#projectBuildStatus.get(projectName).getAbortSignal(); + })); + + // Process any queued resource changes (must be done before starting the build) + this.#flushResourceChanges(); + + // Set active build to prevent concurrent builds + const buildPromise = this.#activeBuild = this.#projectBuilder.build({ + includeRootProject: buildRootProject, + includedDependencies: dependenciesToBuild, + signal, + }, (projectName, project) => { + // Project has been built and result can be used + const projectBuildStatus = this.#projectBuildStatus.get(projectName); + projectBuildStatus.setReader(project.getReader({style: "runtime"})); + }).catch((err) => { + if (err instanceof AbortBuildError) { + log.info("Build aborted"); + log.verbose(`Projects affected by abort: ${projectsToBuild.join(", ")}`); + // Build was aborted - do not log as error + // Re-queue any outstanding projects + for (const projectName of projectsToBuild) { + const projectBuildStatus = this.#projectBuildStatus.get(projectName); + if (!projectBuildStatus.isFresh()) { + log.verbose(`Re-enqueueing project '${projectName}' after aborted build`); + this.#pendingBuildRequest.add(projectName); + } + } + } else { + log.error(`Build failed: ${err.message}`); + // Build failed - reject promises for projects that weren't built + for (const projectName of projectsToBuild) { + const projectBuildStatus = this.#projectBuildStatus.get(projectName); + projectBuildStatus.rejectReaderRequests(err); + } + // Re-throw to be handled by caller + // TODO: rather emit 'error' event for the BuildServer and continue processing the queue? + // Currently, this.#activeBuild will not be cleared. + throw err; + } + }); + + const builtProjects = await buildPromise; + this.emit("buildFinished", builtProjects); + // Clear active build + this.#activeBuild = null; + if (signal.aborted) { + log.verbose(`Build aborted for projects: ${projectsToBuild.join(", ")}`); + // Do not continue processing the queue if the build was aborted, but re-trigger processing debounced + // to ensure that any source changes are properly queued before the next build. + // This is also essential to re-trigger the build in case all resources changes have already been + // processed while the build was still aborting. Otherwise the build would not be re-triggered. + this.#triggerRequestQueue(); + return; + } + } + } +} + +const PROJECT_STATES = Object.freeze({ + INITIAL: "initial", + INVALIDATED: "invalidated", + // TODO: New state BUILDING + FRESH: "fresh", +}); + +class ProjectBuildStatus { + #state = PROJECT_STATES.INITIAL; + #readerQueue = []; + #reader; + #abortController = new AbortController(); + + invalidate(reason = "Project invalidated") { + if (this.#state === PROJECT_STATES.INVALIDATED) { + // Already invalidated + return; + } + this.#state = PROJECT_STATES.INVALIDATED; + // Ensure any running build is aborted. Then reset the abort controller + this.#abortController.abort(new AbortBuildError(reason)); + this.#abortController = new AbortController(); + } + + abortBuild(reason) { + this.#abortController.abort(reason); + } + + getAbortSignal() { + return this.#abortController.signal; + } + + isFresh() { + return this.#state === PROJECT_STATES.FRESH; + } + + getReader() { + return this.#reader; + } + + setReader(reader) { + this.#reader = reader; + this.#state = PROJECT_STATES.FRESH; + // Resolve any queued getReader promises + for (const {resolve} of this.#readerQueue) { + resolve(reader); + } + this.#readerQueue = []; + } + + resetReaderCache() { + this.#reader = null; + } + + addReaderRequest(promiseResolvers) { + this.#readerQueue.push(promiseResolvers); + } + + rejectReaderRequests(error) { + this.#state = PROJECT_STATES.INVALIDATED; + for (const {reject} of this.#readerQueue) { + reject(error); + } + this.#readerQueue = []; + } +} + +/** + * Build finished event + * + * Emitted when one or more projects have finished building. + * + * @event BuildServer#buildFinished + * @param {string[]} projectNames Array of project names that were built + */ + +/** + * Sources changed event + * + * Emitted when source files have changed and affected projects have been invalidated. + * + * @event BuildServer#sourcesChanged + * @param {string[]} changedResourcePaths Array of changed resource paths + */ + +/** + * Error event + * + * Emitted when an error occurs during watching or building. + * + * @event BuildServer#error + * @param {Error} error The error that occurred + */ + + +export default BuildServer; diff --git a/packages/project/lib/build/ProjectBuilder.js b/packages/project/lib/build/ProjectBuilder.js index 4a805d8a385..4af522746ff 100644 --- a/packages/project/lib/build/ProjectBuilder.js +++ b/packages/project/lib/build/ProjectBuilder.js @@ -13,6 +13,8 @@ import OutputStyleEnum from "./helpers/ProjectBuilderOutputStyle.js"; */ class ProjectBuilder { #log; + #buildIsRunning = false; + /** * Build Configuration * @@ -118,6 +120,43 @@ class ProjectBuilder { this.#log = new BuildLogger("ProjectBuilder"); } + /** + * Propagate resource changes through the build context + * + * @public + * @param {Array} changes Array of resource changes to propagate + * @returns {Set} Names of projects potentially affected by the resource changes + * @throws {Error} If a build is currently running + */ + resourcesChanged(changes) { + if (this.#buildIsRunning) { + throw new Error(`Unable to safely propagate resource changes. Build is currently running.`); + } + return this._buildContext.propagateResourceChanges(changes); + } + + /** + * Build projects without writing to a target directory + * + * @public + * @param {object} parameters Parameters + * @param {boolean} [parameters.includeRootProject=true] Whether to include the root project + * @param {Array.} [parameters.includedDependencies=[]] List of dependencies to include + * @param {Array.} [parameters.excludedDependencies=[]] List of dependencies to exclude + * @param {AbortSignal} [parameters.signal] Signal to abort the build + * @param {Function} [projectBuiltCallback] Callback invoked after each project is built + * @returns {Promise} Promise resolving with array of processed project names + */ + async build({ + includeRootProject = true, + includedDependencies = [], excludedDependencies = [], + signal, + }, projectBuiltCallback) { + const requestedProjects = this._determineRequestedProjects( + includeRootProject, includedDependencies, excludedDependencies); + return await this.#build(requestedProjects, projectBuiltCallback, signal); + } + /** * Executes a project build, including all necessary or requested dependencies * @@ -136,14 +175,15 @@ class ProjectBuilder { * part of the build result. If this is provided, the other mentioned parameters are ignored. * @returns {Promise} Promise resolving once the build has finished */ - async build({ + async buildToTarget({ destPath, cleanDest = false, includedDependencies = [], excludedDependencies = [], - dependencyIncludes + dependencyIncludes, }) { if (!destPath) { throw new Error(`Missing parameter 'destPath'`); } + if (dependencyIncludes) { if (includedDependencies.length || excludedDependencies.length) { throw new Error( @@ -151,13 +191,52 @@ class ProjectBuilder { "with parameters 'includedDependencies' or 'excludedDependencies"); } } - const rootProjectName = this._graph.getRoot().getName(); - this.#log.info(`Preparing build for project ${rootProjectName}`); - this.#log.info(` Target directory: ${destPath}`); + this.#log.info(`Target directory: ${destPath}`); + const requestedProjects = this._determineRequestedProjects( + true, includedDependencies, excludedDependencies, dependencyIncludes); + + if (cleanDest) { + this.#log.info(`Cleaning target directory...`); + await rmrf(destPath); + } + + let fsTarget; + if (!process.env.UI5_BUILD_NO_WRITE_DEST) { + fsTarget = resourceFactory.createAdapter({ + fsBasePath: destPath, + virBasePath: "/" + }); + } + const pWrites = []; + await this.#build(requestedProjects, async (projectName, project, projectBuildContext) => { + if (!fsTarget) { + // Nothing to write to + return; + } + // Only write requested projects to target + // (excluding dependencies that were required to be built, but not requested) + this.#log.verbose(`Writing out files for project ${projectName}...`); + await this._writeResults(projectBuildContext, fsTarget, pWrites); + }); + await Promise.all(pWrites); + } + /** + * Determine which projects should be built based on filter criteria + * + * @param {boolean} includeRootProject Whether to include the root project + * @param {Array.} includedDependencies Dependencies to include + * @param {Array.} excludedDependencies Dependencies to exclude + * @param {@ui5/project/build/ProjectBuilder~DependencyIncludes} [dependencyIncludes] + * Alternative dependency configuration + * @returns {string[]} Array of project names to build + * @throws {Error} If creating a build manifest with multiple projects + */ + _determineRequestedProjects(includeRootProject, includedDependencies, excludedDependencies, dependencyIncludes) { // Get project filter function based on include/exclude params // (also logs some info to console) - const filterProject = await this._getProjectFilter({ + const filterProject = this._createProjectFilter({ + includeRootProject, explicitIncludes: includedDependencies, explicitExcludes: excludedDependencies, dependencyIncludes @@ -177,18 +256,30 @@ class ProjectBuilder { } } - const projectBuildContexts = await this._createRequiredBuildContexts(requestedProjects); - const cleanupSigHooks = this._registerCleanupSigHooks(); - const fsTarget = resourceFactory.createAdapter({ - fsBasePath: destPath, - virBasePath: "/" - }); + return requestedProjects; + } - const queue = []; - const alreadyBuilt = []; + /** + * Internal build implementation that orchestrates the actual build process + * + * @param {string[]} requestedProjects Array of project names to build + * @param {Function} [projectBuiltCallback] Callback invoked after each project is built + * @param {AbortSignal} [signal] Signal to abort the build + * @returns {Promise} Promise resolving with array of processed project names + * @throws {Error} If a build is already running + */ + async #build(requestedProjects, projectBuiltCallback, signal) { + if (this.#buildIsRunning) { + throw new Error("A build is already running"); + } + this.#buildIsRunning = true; + this.#log.info(`Preparing build for projects: ${requestedProjects.join(", ")}`); + const projectBuildContexts = await this._buildContext.getRequiredProjectContexts(requestedProjects); // Create build queue based on graph depth-first search to ensure correct build order - await this._graph.traverseDepthFirst(async ({project}) => { + const queue = []; + const processedProjectNames = []; + for (const {project} of this._graph.traverseDependenciesDepthFirst(true)) { const projectName = project.getName(); const projectBuildContext = projectBuildContexts.get(projectName); if (projectBuildContext) { @@ -196,128 +287,108 @@ class ProjectBuilder { // => This project needs to be built or, in case it has already // been built, it's build result needs to be written out (if requested) queue.push(projectBuildContext); - if (!projectBuildContext.requiresBuild()) { - alreadyBuilt.push(projectName); - } + processedProjectNames.push(projectName); } - }); + } this.#log.setProjects(queue.map((projectBuildContext) => { return projectBuildContext.getProject().getName(); })); - if (queue.length > 1) { // Do not log if only the root project is being built - this.#log.info(`Processing ${queue.length} projects`); - if (alreadyBuilt.length) { - this.#log.info(` Reusing build results of ${alreadyBuilt.length} projects`); - this.#log.info(` Building ${queue.length - alreadyBuilt.length} projects`); - } - if (this.#log.isLevelEnabled("verbose")) { - this.#log.verbose(` Required projects:`); - this.#log.verbose(` ${queue - .map((projectBuildContext) => { - const projectName = projectBuildContext.getProject().getName(); - let msg; - if (alreadyBuilt.includes(projectName)) { - const buildMetadata = projectBuildContext.getBuildMetadata(); - const ts = new Date(buildMetadata.timestamp).toUTCString(); - msg = `*> ${projectName} /// already built at ${ts}`; - } else { - msg = `=> ${projectName}`; - } - return msg; - }) - .join("\n ")}`); + const alreadyBuilt = []; + for (const projectBuildContext of queue) { + if (!projectBuildContext.possiblyRequiresBuild()) { + const projectName = projectBuildContext.getProject().getName(); + alreadyBuilt.push(projectName); } } - if (cleanDest) { - this.#log.info(`Cleaning target directory...`); - await rmrf(destPath); - } - const startTime = process.hrtime(); + const cleanupSigHooks = this._registerCleanupSigHooks(); + const pCacheWrites = []; try { - const pWrites = []; - for (const projectBuildContext of queue) { - const projectName = projectBuildContext.getProject().getName(); - const projectType = projectBuildContext.getProject().getType(); + const startTime = process.hrtime(); + while (queue.length) { + const projectBuildContext = queue.shift(); + const project = projectBuildContext.getProject(); + const projectName = project.getName(); + const projectType = project.getType(); this.#log.verbose(`Processing project ${projectName}...`); // Only build projects that are not already build (i.e. provide a matching build manifest) if (alreadyBuilt.includes(projectName)) { this.#log.skipProjectBuild(projectName, projectType); } else { - this.#log.startProjectBuild(projectName, projectType); - await projectBuildContext.getTaskRunner().runTasks(); - this.#log.endProjectBuild(projectName, projectType); + const usesCache = await projectBuildContext.prepareProjectBuildAndValidateCache(); + if (usesCache) { + this.#log.skipProjectBuild(projectName, projectType); + alreadyBuilt.push(projectName); + } else { + await this._buildProject(projectBuildContext); + } + } + signal?.throwIfAborted(); + + if (projectBuiltCallback && requestedProjects.includes(projectName)) { + await projectBuiltCallback(projectName, project, projectBuildContext); } - if (!requestedProjects.includes(projectName) || !!process.env.UI5_BUILD_NO_WRITE_DEST) { - // Project has not been requested or writing is disabled - // => Its resources shall not be part of the build result - continue; + + if (!alreadyBuilt.includes(projectName) && !process.env.UI5_BUILD_NO_WRITE_CACHE) { + this.#log.verbose(`Triggering cache update for project ${projectName}...`); + pCacheWrites.push(projectBuildContext.writeBuildCache()); } - this.#log.verbose(`Writing out files...`); - pWrites.push(this._writeResults(projectBuildContext, fsTarget)); + projectBuildContext.buildFinished(); } - await Promise.all(pWrites); this.#log.info(`Build succeeded in ${this._getElapsedTime(startTime)}`); } catch (err) { - this.#log.error(`Build failed in ${this._getElapsedTime(startTime)}`); + this.#log.error(`Build failed`); throw err; } finally { + await Promise.all(pCacheWrites); this._deregisterCleanupSigHooks(cleanupSigHooks); await this._executeCleanupTasks(); + this.#buildIsRunning = false; } + return processedProjectNames; } - async _createRequiredBuildContexts(requestedProjects) { - const requiredProjects = new Set(this._graph.getProjectNames().filter((projectName) => { - return requestedProjects.includes(projectName); - })); - - const projectBuildContexts = new Map(); - - for (const projectName of requiredProjects) { - this.#log.verbose(`Creating build context for project ${projectName}...`); - const projectBuildContext = this._buildContext.createProjectContext({ - project: this._graph.getProject(projectName) - }); - - projectBuildContexts.set(projectName, projectBuildContext); + /** + * Build a single project + * + * @param {object} projectBuildContext Build context for the project + * @param {AbortSignal} [signal] Signal to abort the build + * @returns {Promise} Promise resolving with array of changed resources + */ + async _buildProject(projectBuildContext, signal) { + const project = projectBuildContext.getProject(); + const projectName = project.getName(); + const projectType = project.getType(); - if (projectBuildContext.requiresBuild()) { - const taskRunner = projectBuildContext.getTaskRunner(); - const requiredDependencies = await taskRunner.getRequiredDependencies(); + this.#log.startProjectBuild(projectName, projectType); + const changedResources = await projectBuildContext.buildProject(signal); + this.#log.endProjectBuild(projectName, projectType); - if (requiredDependencies.size === 0) { - continue; - } - // This project needs to be built and required dependencies to be built as well - this._graph.getDependencies(projectName).forEach((depName) => { - if (projectBuildContexts.has(depName)) { - // Build context already exists - // => Dependency will be built - return; - } - if (!requiredDependencies.has(depName)) { - return; - } - // Add dependency to list of projects to build - requiredProjects.add(depName); - }); - } - } - - return projectBuildContexts; + return changedResources; } - async _getProjectFilter({ + /** + * Create a filter function to determine which projects should be built + * + * @param {object} parameters Parameters + * @param {boolean} [parameters.includeRootProject=true] Whether to include the root project + * @param {@ui5/project/build/ProjectBuilder~DependencyIncludes} [parameters.dependencyIncludes] + * Dependency configuration + * @param {Array.} [parameters.explicitIncludes] Explicit dependencies to include + * @param {Array.} [parameters.explicitExcludes] Explicit dependencies to exclude + * @returns {Function} Filter function that takes a project name and returns boolean + */ + _createProjectFilter({ + includeRootProject = true, dependencyIncludes, explicitIncludes, explicitExcludes }) { - const {includedDependencies, excludedDependencies} = await composeProjectList( + const {includedDependencies, excludedDependencies} = composeProjectList( this._graph, dependencyIncludes || { includeDependencyTree: explicitIncludes, @@ -327,15 +398,13 @@ class ProjectBuilder { if (includedDependencies.length) { if (includedDependencies.length === this._graph.getSize() - 1) { - this.#log.info(` Including all dependencies`); + this.#log.info(` Requested all dependencies`); } else { - this.#log.info(` Requested dependencies:`); - this.#log.info(` + ${includedDependencies.join("\n + ")}`); + this.#log.info(` Requested dependencies:\n + ${includedDependencies.join("\n + ")}`); } } if (excludedDependencies.length) { - this.#log.info(` Excluded dependencies:`); - this.#log.info(` - ${excludedDependencies.join("\n + ")}`); + this.#log.info(` Excluded dependencies:\n - ${excludedDependencies.join("\n + ")}`); } const rootProjectName = this._graph.getRoot().getName(); @@ -345,8 +414,8 @@ class ProjectBuilder { dep.test(projectName) : dep === projectName); } - if (projectName === rootProjectName) { - // Always include the root project + if (includeRootProject && projectName === rootProjectName) { + // Include root project return true; } @@ -362,7 +431,15 @@ class ProjectBuilder { }; } - async _writeResults(projectBuildContext, target) { + /** + * Write build results for a project to the target destination + * + * @param {object} projectBuildContext Build context for the project + * @param {@ui5/fs/adapters/FileSystem} target Target adapter to write to + * @param {Array} deferredWork + * @returns {Promise} Promise resolving when write is complete + */ + async _writeResults(projectBuildContext, target, deferredWork) { const project = projectBuildContext.getProject(); const taskUtil = projectBuildContext.getTaskUtil(); const buildConfig = this._buildContext.getBuildConfig(); @@ -385,34 +462,47 @@ class ProjectBuilder { const resources = await reader.byGlob("/**/*"); if (createBuildManifest) { - // Create and write a build manifest metadata file + // Create and write a build manifest metadata file+ const { default: createBuildManifest } = await import("./helpers/createBuildManifest.js"); - const metadata = await createBuildManifest(project, buildConfig, this._buildContext.getTaskRepository()); + const buildManifest = await createBuildManifest( + project, buildConfig, this._buildContext.getTaskRepository(), + projectBuildContext.getBuildSignature()); await target.write(resourceFactory.createResource({ path: `/.ui5/build-manifest.json`, - string: JSON.stringify(metadata, null, "\t") + string: JSON.stringify(buildManifest, null, "\t") })); } - await Promise.all(resources.map((resource) => { + const resourcesToWrite = resources.filter((resource) => { if (taskUtil.getTag(resource, taskUtil.STANDARD_TAGS.OmitFromBuildResult)) { - this.#log.silly(`Skipping write of resource tagged as "OmitFromBuildResult": ` + + this.#log.silly(`Skipping resource tagged as "OmitFromBuildResult": ` + resource.getPath()); - return; // Skip target write for this resource + return false; // Skip this resource } + return true; + }); + + deferredWork.push( + this._writeToDisk(resourcesToWrite, target, resources, project, isRootProject, outputStyle)); + } + + async _writeToDisk(resourcesToWrite, target, resources, project, isRootProject, outputStyle) { + await Promise.all(resourcesToWrite.map((resource) => { return target.write(resource); })); if (isRootProject && outputStyle === OutputStyleEnum.Flat && - project.getType() !== "application" /* application type is with a default flat build output structure */) { + /* application type is with a default flat build output structure */ + project.getType() !== "application") { const namespace = project.getNamespace(); const libraryResourcesPrefix = `/resources/${namespace}/`; const testResourcesPrefix = "/test-resources/"; const namespacedRegex = new RegExp(`/(resources|test-resources)/${namespace}`); - const processedResourcesSet = resources.reduce((acc, resource) => acc.add(resource.getPath()), new Set()); + const processedResourcesSet = resources.reduce( + (acc, resource) => acc.add(resource.getPath()), new Set()); // If outputStyle === "Flat", then the FlatReader would have filtered // some resources. We now need to get all of the available resources and @@ -431,7 +521,8 @@ class ProjectBuilder { skippedResources.forEach((resource) => { if (resource.originalPath.startsWith(testResourcesPrefix)) { this.#log.verbose( - `Omitting ${resource.originalPath} from build result. File is part of ${testResourcesPrefix}.` + `Omitting ${resource.originalPath} from build result. ` + + `File is part of ${testResourcesPrefix}.` ); } else if (!resource.originalPath.startsWith(libraryResourcesPrefix)) { this.#log.warn( @@ -443,12 +534,23 @@ class ProjectBuilder { } } + /** + * Execute cleanup tasks for all build contexts + * + * @param {boolean} [force] Whether to force cleanup execution + * @returns {Promise} Promise resolving when cleanup is complete + */ async _executeCleanupTasks(force) { this.#log.info("Executing cleanup tasks..."); await this._buildContext.executeCleanupTasks(force); } + /** + * Register signal handlers for cleanup on process termination + * + * @returns {object} Map of signal names to their handlers + */ _registerCleanupSigHooks() { const that = this; function createListener(exitCode) { @@ -490,6 +592,11 @@ class ProjectBuilder { return processSignals; } + /** + * Remove previously registered signal handlers + * + * @param {object} signals Map of signal names to their handlers + */ _deregisterCleanupSigHooks(signals) { for (const signal of Object.keys(signals)) { process.removeListener(signal, signals[signal]); @@ -499,7 +606,6 @@ class ProjectBuilder { /** * Calculates the elapsed build time and returns a prettified output * - * @private * @param {Array} startTime Array provided by process.hrtime() * @returns {string} Difference between now and the provided time array as formatted string */ diff --git a/packages/project/lib/build/TaskRunner.js b/packages/project/lib/build/TaskRunner.js index 0f5677170a3..1587b143525 100644 --- a/packages/project/lib/build/TaskRunner.js +++ b/packages/project/lib/build/TaskRunner.js @@ -1,28 +1,31 @@ import {getLogger} from "@ui5/logger"; import composeTaskList from "./helpers/composeTaskList.js"; -import {createReaderCollection} from "@ui5/fs/resourceFactory"; +import {createReaderCollection, createMonitor} from "@ui5/fs/resourceFactory"; /** * TaskRunner * - * @private + * Manages the execution of build tasks for a project, including task composition, + * dependency management, and custom task integration. + * * @hideconstructor */ class TaskRunner { /** * Constructor * - * @param {object} parameters - * @param {object} parameters.graph - * @param {object} parameters.project + * @param {object} parameters Parameters + * @param {@ui5/project/graph/ProjectGraph} parameters.graph Project graph instance + * @param {@ui5/project/specifications/Project} parameters.project Project instance * @param {@ui5/logger/loggers/ProjectBuild} parameters.log Logger to use + * @param {@ui5/project/build/cache/ProjectBuildCache} parameters.buildCache Build cache instance * @param {@ui5/project/build/helpers/TaskUtil} parameters.taskUtil TaskUtil instance * @param {@ui5/builder/tasks/taskRepository} parameters.taskRepository Task repository * @param {@ui5/project/build/ProjectBuilder~BuildConfiguration} parameters.buildConfig * Build configuration */ - constructor({graph, project, log, taskUtil, taskRepository, buildConfig}) { - if (!graph || !project || !log || !taskUtil || !taskRepository || !buildConfig) { + constructor({graph, project, log, buildCache, taskUtil, taskRepository, buildConfig}) { + if (!graph || !project || !log || !buildCache || !taskUtil || !taskRepository || !buildConfig) { throw new Error("TaskRunner: One or more mandatory parameters not provided"); } this._project = project; @@ -31,10 +34,21 @@ class TaskRunner { this._taskRepository = taskRepository; this._buildConfig = buildConfig; this._log = log; + this._buildCache = buildCache; this._directDependencies = new Set(this._taskUtil.getDependencies()); } + /** + * Initializes the task list based on the project type + * + * This method: + * 1. Loads the appropriate build definition for the project type + * 2. Adds all standard tasks from the definition + * 3. Adds any custom tasks configured for the project + * + * @returns {Promise} + */ async _initTasks() { if (this._tasks) { return; @@ -79,30 +93,28 @@ class TaskRunner { } await this._addCustomTasks(); - - // Create readers for *all* dependencies - const depReaders = []; - await this._graph.traverseBreadthFirst(project.getName(), async function({project: dep}) { - if (dep.getName() === project.getName()) { - // Ignore project itself - return; - } - depReaders.push(dep.getReader()); - }); - - this._allDependenciesReader = createReaderCollection({ - name: `Dependency reader collection of project ${project.getName()}`, - readers: depReaders - }); } /** - * Takes a list of tasks which should be executed from the available task list of the current builder + * Executes all configured tasks for the project + * + * This method: + * 1. Initializes the task list if not already done + * 2. Ensures dependency reader is ready + * 3. Composes the final list of tasks to execute based on build configuration + * 4. Executes each task in order, respecting cache and abort signals + * 5. Returns the list of changed resources after all tasks complete * - * @returns {Promise} Returns promise resolving once all tasks have been executed + * @public + * @param {AbortSignal} [signal] Abort signal to cancel task execution + * @returns {Promise} Array of changed resource paths since the last build */ - async runTasks() { + async runTasks(signal) { await this._initTasks(); + + // Ensure cached dependencies reader is initialized and up-to-date (TODO: improve this lifecycle) + await this.getDependenciesReader(this._directDependencies); + const tasksToRun = composeTaskList(Object.keys(this._tasks), this._buildConfig); const allTasks = this._taskExecutionOrder.filter((taskName) => { // There might be a numeric suffix in case a custom task is configured multiple times. @@ -120,22 +132,33 @@ class TaskRunner { }); this._log.setTasks(allTasks); + this._buildCache.setTasks(allTasks); for (const taskName of allTasks) { + signal?.throwIfAborted(); const taskFunction = this._tasks[taskName].task; if (typeof taskFunction === "function") { await this._executeTask(taskName, taskFunction); } } + return await this._buildCache.allTasksCompleted(); } /** - * First compiles a list of all tasks that will be executed, then a list of all direct project - * dependencies that those tasks require access to. + * Determines which project dependencies are required by the tasks that will be executed + * + * This method: + * 1. Initializes the task list if needed + * 2. Composes the list of tasks that will be executed + * 3. Collects all dependencies required by those tasks * - * @returns {Set} Returns a set containing the names of all required direct project dependencies + * @public + * @returns {Promise>} Set containing the names of all required direct project dependencies */ async getRequiredDependencies() { + if (this._requiredDependencies) { + return this._requiredDependencies; + } await this._initTasks(); const tasksToRun = composeTaskList(Object.keys(this._tasks), this._buildConfig); const allTasks = this._taskExecutionOrder.filter((taskName) => { @@ -150,7 +173,7 @@ class TaskRunner { const taskWithoutSuffixCounter = taskName.replace(/--\d+$/, ""); return tasksToRun.includes(taskWithoutSuffixCounter); }); - return allTasks.reduce((requiredDependencies, taskName) => { + this._requiredDependencies = allTasks.reduce((requiredDependencies, taskName) => { if (this._tasks[taskName].requiredDependencies.size) { this._log.verbose(`Task ${taskName} for project ${this._project.getName()} requires dependencies`); } @@ -159,20 +182,29 @@ class TaskRunner { } return requiredDependencies; }, new Set()); + return this._requiredDependencies; } /** * Adds an executable task to the builder * - * The order this function is being called defines the build order. FIFO. + * The order this function is called defines the build order (FIFO). + * Tasks can be explicitly skipped by setting taskFunction to null. * - * @param {string} taskName Name of the task which should be in the list availableTasks. - * @param {object} [parameters] - * @param {boolean} [parameters.requiresDependencies] - * @param {object} [parameters.options] - * @param {Function} [parameters.taskFunction] + * @param {string} taskName Name of the task to add + * @param {object} [parameters] Task parameters + * @param {boolean} [parameters.requiresDependencies=false] + * Whether the task requires access to project dependencies + * @param {boolean} [parameters.supportsDifferentialBuilds=false] + * Whether the task supports differential updates using cache + * @param {object} [parameters.options={}] Options to pass to the task + * @param {Function|null} [parameters.taskFunction] + * Task function to execute, or null to explicitly skip the task + * @returns {void} */ - _addTask(taskName, {requiresDependencies = false, options = {}, taskFunction} = {}) { + _addTask(taskName, { + requiresDependencies = false, supportsDifferentialBuilds = false, options = {}, taskFunction + } = {}) { if (this._tasks[taskName]) { throw new Error(`Failed to add duplicate task ${taskName} for project ${this._project.getName()}`); } @@ -190,20 +222,47 @@ class TaskRunner { options.projectName = this._project.getName(); options.projectNamespace = this._project.getNamespace(); + const cacheInfo = await this._buildCache.prepareTaskExecutionAndValidateCache(taskName); + if (cacheInfo === true) { + this._log.skipTask(taskName); + return; + } + const usingCache = !!(supportsDifferentialBuilds && cacheInfo); + const workspace = createMonitor(this._project.getWorkspace()); const params = { - workspace: this._project.getWorkspace(), + workspace, taskUtil: this._taskUtil, - options + options, }; + let dependencies; if (requiresDependencies) { - params.dependencies = this._allDependenciesReader; + dependencies = createMonitor(this._cachedDependenciesReader); + params.dependencies = dependencies; + } + if (usingCache) { + params.changedProjectResourcePaths = cacheInfo.changedProjectResourcePaths; + if (requiresDependencies) { + params.changedDependencyResourcePaths = cacheInfo.changedDependencyResourcePaths; + } } - if (!taskFunction) { - taskFunction = (await this._taskRepository.getTask(taskName)).task; + const {task} = await this._taskRepository.getTask(taskName); + taskFunction = task; + } + this._log.startTask(taskName, usingCache); + this._taskStart = performance.now(); + await taskFunction(params); + if (this._log.isLevelEnabled("perf")) { + this._log.perf( + `Task ${taskName} finished in ${Math.round((performance.now() - this._taskStart))} ms`); } - return taskFunction(params); + this._log.endTask(taskName); + await this._buildCache.recordTaskResult(taskName, + workspace.getResourceRequests(), + dependencies?.getResourceRequests(), + usingCache ? cacheInfo : undefined, + supportsDifferentialBuilds); }; } this._tasks[taskName] = { @@ -214,8 +273,11 @@ class TaskRunner { } /** + * Adds all custom tasks configured for the project * - * @private + * Processes custom tasks in the order they are defined in the project configuration. + * + * @returns {Promise} */ async _addCustomTasks() { const projectCustomTasks = this._project.getCustomTasks(); @@ -228,10 +290,21 @@ class TaskRunner { } } /** - * Adds custom tasks to execute + * Adds a single custom task to the task execution order + * + * This method: + * 1. Validates the custom task definition + * 2. Loads the task extension from the project graph + * 3. Determines required dependencies via callback if provided + * 4. Creates a wrapper function for the custom task + * 5. Inserts the task at the correct position based on beforeTask/afterTask configuration * - * @private - * @param {object} taskDef + * @param {object} taskDef Custom task definition from project configuration + * @param {string} taskDef.name Name of the custom task + * @param {string} [taskDef.beforeTask] Name of task to insert before + * @param {string} [taskDef.afterTask] Name of task to insert after + * @param {object} [taskDef.configuration] Custom task configuration + * @returns {Promise} */ async _addCustomTask(taskDef) { const project = this._project; @@ -276,6 +349,9 @@ class TaskRunner { // Tasks can provide an optional callback to tell build process which dependencies they require const requiredDependenciesCallback = await task.getRequiredDependenciesCallback(); + // const buildSignatureCallback = await task.getBuildSignatureCallback(); + // const expectedOutputCallback = await task.getExpectedOutputCallback(); + const supportsDifferentialBuildsCallback = await task.getSupportsDifferentialBuildsCallback(); const specVersion = task.getSpecVersion(); let requiredDependencies; @@ -338,6 +414,10 @@ class TaskRunner { } }); } + let supportsDifferentialBuilds = false; + if (specVersion.gte("5.0") && supportsDifferentialBuildsCallback && supportsDifferentialBuildsCallback()) { + supportsDifferentialBuilds = true; + } this._tasks[newTaskName] = { task: this._createCustomTaskWrapper({ @@ -347,9 +427,10 @@ class TaskRunner { taskName: newTaskName, taskConfiguration: taskDef.configuration, provideDependenciesReader, - getDependenciesReader: () => { + supportsDifferentialBuilds, + getDependenciesReaderCb: () => { // Create the dependencies reader on-demand - return this._createDependenciesReader(requiredDependencies); + return this.getDependenciesReader(requiredDependencies); }, }), requiredDependencies @@ -381,10 +462,42 @@ class TaskRunner { } } + /** + * Creates a wrapper function for executing a custom task + * + * The wrapper: + * 1. Validates cache and determines if task can be skipped + * 2. Prepares workspace and dependencies readers + * 3. Builds the parameter object for the custom task interface + * 4. Executes the custom task function + * 5. Records the task result in the build cache + * + * @param {object} parameters Parameters + * @param {@ui5/project/specifications/Project} parameters.project Project instance + * @param {@ui5/project/build/helpers/TaskUtil} parameters.taskUtil TaskUtil instance + * @param {Function} parameters.getDependenciesReaderCb + * Callback to get dependencies reader on-demand + * @param {boolean} parameters.provideDependenciesReader + * Whether to provide dependencies reader to the task + * @param {boolean} parameters.supportsDifferentialBuilds + * Whether the task supports differential updates + * @param {@ui5/project/specifications/Extension} parameters.task Task extension instance + * @param {string} parameters.taskName Runtime name of the task (may include suffix) + * @param {object} [parameters.taskConfiguration] Task configuration from ui5.yaml + * @returns {Function} Async wrapper function for the custom task + */ _createCustomTaskWrapper({ - project, taskUtil, getDependenciesReader, provideDependenciesReader, task, taskName, taskConfiguration + project, taskUtil, getDependenciesReaderCb, provideDependenciesReader, supportsDifferentialBuilds, + task, taskName, taskConfiguration }) { - return async function() { + return async () => { + const cacheInfo = await this._buildCache.prepareTaskExecutionAndValidateCache(taskName); + if (cacheInfo === true) { + this._log.skipTask(taskName); + return; + } + const usingCache = !!(supportsDifferentialBuilds && cacheInfo); + /* Custom Task Interface Parameters: {Object} parameters Parameters @@ -407,14 +520,21 @@ class TaskRunner { Returns: {Promise} Promise resolving with undefined once data has been written */ + const workspace = createMonitor(this._project.getWorkspace()); const params = { - workspace: project.getWorkspace(), + workspace, options: { projectName: project.getName(), projectNamespace: project.getNamespace(), configuration: taskConfiguration, } }; + if (usingCache) { + params.changedProjectResourcePaths = cacheInfo.changedProjectResourcePaths; + if (provideDependenciesReader) { + params.changedDependencyResourcePaths = cacheInfo.changedDependencyResourcePaths; + } + } const specVersion = task.getSpecVersion(); const taskUtilInterface = taskUtil.getInterface(specVersion); // Interface is undefined if specVersion does not support taskUtil @@ -428,36 +548,60 @@ class TaskRunner { params.log = getLogger(`builder:custom-task:${taskName}`); } + let dependencies; if (provideDependenciesReader) { - params.dependencies = await getDependenciesReader(); + dependencies = createMonitor(await getDependenciesReaderCb()); + params.dependencies = dependencies; } - return taskFunction(params); + this._log.startTask(taskName, usingCache); + await taskFunction(params); + this._log.endTask(taskName); + await this._buildCache.recordTaskResult(taskName, + workspace.getResourceRequests(), + dependencies?.getResourceRequests(), + usingCache ? cacheInfo : undefined, + supportsDifferentialBuilds); }; } /** - * Adds progress related functionality to task function. + * Executes a task function with performance tracking + * + * Wraps task execution with performance measurements and logging. * - * @private * @param {string} taskName Name of the task - * @param {Function} taskFunction Function which executed the task + * @param {Function} taskFunction Function which executes the task * @param {object} taskParams Base parameters for all tasks - * @returns {Promise} Resolves when task has finished + * @returns {Promise} Resolves when task has finished */ async _executeTask(taskName, taskFunction, taskParams) { - this._log.startTask(taskName); this._taskStart = performance.now(); await taskFunction(taskParams, this._log); if (this._log.isLevelEnabled("perf")) { + // FIXME: Standard tasks are currently additionally measured within taskFunction (See _addTask). + // The measurement here includes the time for checking whether the task can be skipped via cache. this._log.perf(`Task ${taskName} finished in ${Math.round((performance.now() - this._taskStart))} ms`); } - this._log.endTask(taskName); } - async _createDependenciesReader(requiredDirectDependencies) { - if (requiredDirectDependencies.size === this._directDependencies.size) { + /** + * Creates a reader collection for the specified project dependencies + * + * This method: + * 1. Returns a cached reader if all direct dependencies are requested and available + * 2. Resolves transitive dependencies for the requested dependency names + * 3. Creates a reader collection containing readers for all required dependencies + * 4. Caches the reader if it covers all direct dependencies + * + * @public + * @param {Set} dependencyNames Set of dependency project names to include + * @param {boolean} [forceUpdate=false] Force creation of a new reader even if cached + * @returns {Promise<@ui5/fs/ReaderCollection>} Reader collection for the requested dependencies + */ + async getDependenciesReader(dependencyNames, forceUpdate = false) { + if (!forceUpdate && dependencyNames.size === this._directDependencies.size && this._cachedDependenciesReader) { // Shortcut: If all direct dependencies are required, just return the already created reader - return this._allDependenciesReader; + return this._cachedDependenciesReader; } const rootProject = this._project; @@ -465,8 +609,8 @@ class TaskRunner { const readers = []; // Add transitive dependencies to set of required dependencies - const requiredDependencies = new Set(requiredDirectDependencies); - for (const projectName of requiredDirectDependencies) { + const requiredDependencies = new Set(dependencyNames); + for (const projectName of dependencyNames) { this._graph.getTransitiveDependencies(projectName).forEach((depName) => { requiredDependencies.add(depName); }); @@ -480,10 +624,15 @@ class TaskRunner { }); // Create a reader collection for that - return createReaderCollection({ + const reader = createReaderCollection({ name: `Reduced dependency reader collection of project ${rootProject.getName()}`, readers }); + + if (dependencyNames.size === this._directDependencies.size) { + this._cachedDependenciesReader = reader; + } + return reader; } } diff --git a/packages/project/lib/build/cache/BuildTaskCache.js b/packages/project/lib/build/cache/BuildTaskCache.js new file mode 100644 index 00000000000..b2aac3cfe90 --- /dev/null +++ b/packages/project/lib/build/cache/BuildTaskCache.js @@ -0,0 +1,284 @@ +import {getLogger} from "@ui5/logger"; +import ResourceRequestManager from "./ResourceRequestManager.js"; +const log = getLogger("build:cache:BuildTaskCache"); + +/** + * @typedef {object} @ui5/project/build/cache/BuildTaskCache~ResourceRequests + * @property {Set} paths Specific resource paths that were accessed + * @property {Set} patterns Glob patterns used to access resources + */ + +/** + * Manages the build cache for a single task + * + * This class tracks all resources accessed by a task (both project and dependency resources) + * and maintains a graph of resource request sets. Each request set represents a unique + * combination of resource accesses, enabling efficient cache invalidation and reuse. + * + * Key features: + * - Tracks resource reads using paths and glob patterns + * - Maintains resource indices for different request combinations + * - Supports incremental updates when resources change + * - Provides cache invalidation based on changed resources + * - Serializes/deserializes cache metadata for persistence + * + * The request graph allows derived request sets (when a task reads additional resources) + * to reuse existing resource indices, optimizing both memory and computation. + * + * @class + */ +export default class BuildTaskCache { + #projectName; + #taskName; + #supportsDifferentialBuilds; + + #projectRequestManager; + #dependencyRequestManager; + + /** + * Creates a new BuildTaskCache instance + * + * @public + * @param {string} projectName Name of the project this task belongs to + * @param {string} taskName Name of the task this cache manages + * @param {boolean} supportsDifferentialBuilds Whether the task supports differential updates + * @param {ResourceRequestManager} [projectRequestManager] Optional pre-existing project request manager from cache + * @param {ResourceRequestManager} [dependencyRequestManager] + * Optional pre-existing dependency request manager from cache + */ + constructor(projectName, taskName, supportsDifferentialBuilds, projectRequestManager, dependencyRequestManager) { + this.#projectName = projectName; + this.#taskName = taskName; + this.#supportsDifferentialBuilds = supportsDifferentialBuilds; + log.verbose(`Initializing BuildTaskCache for task "${taskName}" of project "${this.#projectName}" ` + + `(supportsDifferentialBuilds=${supportsDifferentialBuilds})`); + + this.#projectRequestManager = projectRequestManager ?? + new ResourceRequestManager(projectName, taskName, supportsDifferentialBuilds); + this.#dependencyRequestManager = dependencyRequestManager ?? + new ResourceRequestManager(projectName, taskName, supportsDifferentialBuilds); + } + + /** + * Factory method to restore a BuildTaskCache from cached data + * + * Deserializes previously cached request managers for both project and dependency resources, + * allowing the task cache to resume from a prior build state. + * + * @public + * @param {string} projectName Name of the project + * @param {string} taskName Name of the task + * @param {boolean} supportsDifferentialBuilds Whether the task supports differential updates + * @param {object} projectRequests Cached project request manager data + * @param {object} dependencyRequests Cached dependency request manager data + * @returns {BuildTaskCache} Restored task cache instance + */ + static fromCache(projectName, taskName, supportsDifferentialBuilds, projectRequests, dependencyRequests) { + const projectRequestManager = ResourceRequestManager.fromCache(projectName, taskName, + supportsDifferentialBuilds, projectRequests); + const dependencyRequestManager = ResourceRequestManager.fromCache(projectName, taskName, + supportsDifferentialBuilds, dependencyRequests); + return new BuildTaskCache(projectName, taskName, supportsDifferentialBuilds, + projectRequestManager, dependencyRequestManager); + } + + // ===== METADATA ACCESS ===== + + /** + * Gets the name of the task + * + * @public + * @returns {string} Task name + */ + getTaskName() { + return this.#taskName; + } + + /** + * Checks whether the task supports differential updates + * + * Tasks that support differential updates can use incremental cache invalidation, + * processing only changed resources rather than rebuilding from scratch. + * + * @public + * @returns {boolean} True if differential updates are supported + */ + getSupportsDifferentialBuilds() { + return this.#supportsDifferentialBuilds; + } + + /** + * Checks whether new or modified cache entries exist + * + * Returns true if either the project or dependency request managers have new or + * modified cache entries that need to be persisted. + * + * @public + * @returns {boolean} True if cache entries need to be written + */ + hasNewOrModifiedCacheEntries() { + return this.#projectRequestManager.hasNewOrModifiedCacheEntries() || + this.#dependencyRequestManager.hasNewOrModifiedCacheEntries(); + } + + /** + * Updates project resource indices based on changed resource paths + * + * Processes changed resource paths and updates the project request manager's indices + * accordingly. Only relevant resources (those matching recorded requests) are processed. + * + * @public + * @param {module:@ui5/fs.AbstractReader} projectReader Reader for accessing project resources + * @param {string[]} changedProjectResourcePaths Array of changed project resource paths + * @returns {Promise} True if any index has changed + */ + updateProjectIndices(projectReader, changedProjectResourcePaths) { + return this.#projectRequestManager.updateIndices(projectReader, changedProjectResourcePaths); + } + + /** + * Updates dependency resource indices based on changed resource paths + * + * Processes changed dependency resource paths and updates the dependency request manager's + * indices accordingly. Only relevant resources (those matching recorded requests) are processed. + * + * @public + * @param {module:@ui5/fs.AbstractReader} dependencyReader Reader for accessing dependency resources + * @param {string[]} changedDepResourcePaths Array of changed dependency resource paths + * @returns {Promise} True if any index has changed + */ + updateDependencyIndices(dependencyReader, changedDepResourcePaths) { + return this.#dependencyRequestManager.updateIndices(dependencyReader, changedDepResourcePaths); + } + + /** + * Performs a full refresh of the dependency resource index + * + * Since dependency resources may change independently from this project's cache, a full + * refresh of the dependency index is required at the beginning of every build from cache. + * This ensures all dependency resources are current before task execution. + * + * @public + * @param {module:@ui5/fs.AbstractReader} dependencyReader Reader for accessing dependency resources + * @returns {Promise} + */ + refreshDependencyIndices(dependencyReader) { + return this.#dependencyRequestManager.refreshIndices(dependencyReader); + } + + /** + * Gets all project index signatures for this task + * + * Returns signatures from all recorded project-request sets. Each signature represents + * a unique combination of resources, belonging to the current project, that were accessed + * during task execution. These can be used as cache keys for restoring cached task results. + * + * @public + * @returns {string[]} Array of signature strings + * @throws {Error} If resource index is missing for any request set + */ + getProjectIndexSignatures() { + return this.#projectRequestManager.getIndexSignatures(); + } + + /** + * Gets all dependency index signatures for this task + * + * Returns signatures from all recorded dependency-request sets. Each signature represents + * a unique combination of resources, belonging to all dependencies of the current project, + * that were accessed during task execution. These can be used as cache keys for restoring + * cached task results. + * + * @public + * @returns {string[]} Array of signature strings + * @throws {Error} If resource index is missing for any request set + */ + getDependencyIndexSignatures() { + return this.#dependencyRequestManager.getIndexSignatures(); + } + + /** + * Gets all project index delta transitions for differential updates + * + * Returns a map of signature transitions and their associated changed resource paths + * for project resources. Used when tasks support differential updates to identify + * which resources changed between cache states. + * + * @public + * @returns {Map} Map from original signature to delta information + * containing newSignature and changedPaths array + */ + getProjectIndexDeltas() { + return this.#projectRequestManager.getDeltas(); + } + + /** + * Gets all dependency index delta transitions for differential updates + * + * Returns a map of signature transitions and their associated changed resource paths + * for dependency resources. Used when tasks support differential updates to identify + * which dependency resources changed between cache states. + * + * @public + * @returns {Map} Map from original signature to delta information + * containing newSignature and changedPaths array + */ + getDependencyIndexDeltas() { + return this.#dependencyRequestManager.getDeltas(); + } + + /** + * Records resource requests and calculates signatures for the task + * + * This method: + * 1. Processes project and dependency resource requests + * 2. Searches for exact matches in the request graphs + * 3. If found, returns the existing index signatures + * 4. If not found, creates new request sets and resource indices + * 5. Uses tree derivation when possible to reuse parent indices + * + * The returned signatures uniquely identify the set of resources accessed and their + * content, enabling cache lookup for previously executed task results. + * + * @public + * @param {@ui5/project/build/cache/BuildTaskCache~ResourceRequests} projectRequestRecording + * Project resource requests (paths and patterns) + * @param {@ui5/project/build/cache/BuildTaskCache~ResourceRequests|undefined} dependencyRequestRecording + * Dependency resource requests (paths and patterns) + * @param {module:@ui5/fs.AbstractReader} projectReader Reader for accessing project resources + * @param {module:@ui5/fs.AbstractReader} dependencyReader Reader for accessing dependency resources + * @returns {Promise} Array containing [projectSignature, dependencySignature] + */ + async recordRequests(projectRequestRecording, dependencyRequestRecording, projectReader, dependencyReader) { + const { + setId: projectReqSetId, signature: projectReqSignature + } = await this.#projectRequestManager.addRequests(projectRequestRecording, projectReader); + + let dependencyReqSignature; + if (dependencyRequestRecording) { + const { + setId: depReqSetId, signature: depReqSignature + } = await this.#dependencyRequestManager.addRequests(dependencyRequestRecording, dependencyReader); + + this.#projectRequestManager.addAffiliatedRequestSet(projectReqSetId, depReqSetId); + dependencyReqSignature = depReqSignature; + } else { + dependencyReqSignature = this.#dependencyRequestManager.recordNoRequests(); + } + return [projectReqSignature, dependencyReqSignature]; + } + + /** + * Serializes the task cache to plain objects for persistence + * + * Exports both project and dependency resource request graphs in a format suitable + * for JSON serialization. The serialized data can be passed to fromCache() to restore + * the cache state. Returns undefined for request managers with no new or modified entries. + * + * @public + * @returns {Array} Array containing [projectCacheObject, dependencyCacheObject] + */ + toCacheObjects() { + return [this.#projectRequestManager.toCacheObject(), this.#dependencyRequestManager.toCacheObject()]; + } +} diff --git a/packages/project/lib/build/cache/CacheManager.js b/packages/project/lib/build/cache/CacheManager.js new file mode 100644 index 00000000000..0647a62ade4 --- /dev/null +++ b/packages/project/lib/build/cache/CacheManager.js @@ -0,0 +1,483 @@ +import cacache from "cacache"; +import path from "node:path"; +import fs from "graceful-fs"; +import {promisify} from "node:util"; +import {gzip} from "node:zlib"; +const mkdir = promisify(fs.mkdir); +const readFile = promisify(fs.readFile); +const writeFile = promisify(fs.writeFile); +import os from "node:os"; +import Configuration from "../../config/Configuration.js"; +import {getPathFromPackageName} from "../../utils/sanitizeFileName.js"; +import {getLogger} from "@ui5/logger"; + +const log = getLogger("build:cache:CacheManager"); + +// Singleton instances mapped by cache directory path +const chacheManagerInstances = new Map(); + +// Options for cacache operations (using SHA-256 for integrity checks) +const CACACHE_OPTIONS = {algorithms: ["sha256"]}; + +// Cache version for compatibility management +const CACHE_VERSION = "v0_2"; + +/** + * Manages persistence for the build cache using file-based storage and cacache + * + * CacheManager provides a hierarchical file-based cache structure: + * - cas/ - Content-addressable storage (cacache) for resource content + * - buildManifests/ - Build manifest files containing metadata about builds + * - stageMetadata/ - Stage-level metadata organized by project, build, and stage + * - index/ - Resource index files for efficient change detection + * + * The cache is organized by: + * 1. Project ID (sanitized package name) + * 2. Build signature (hash of build configuration) + * 3. Stage ID (e.g., "result" or "task/taskName") + * 4. Stage signature (hash of input resources) + * + * Key features: + * - Content-addressable storage with integrity verification + * - Singleton pattern per cache directory + * - Configurable cache location via UI5_DATA_DIR or configuration + * - Efficient resource deduplication through cacache + * + * @class + */ +export default class CacheManager { + #casDir; + #manifestDir; + #stageMetadataDir; + #taskMetadataDir; + #resultMetadataDir; + #indexDir; + + /** + * Creates a new CacheManager instance + * + * Initializes the directory structure for the cache. This constructor is private - + * use CacheManager.create() instead to get a singleton instance. + * + * @private + * @param {string} cacheDir Base directory for the cache + */ + constructor(cacheDir) { + cacheDir = path.join(cacheDir, CACHE_VERSION); + this.#casDir = path.join(cacheDir, "cas"); + this.#manifestDir = path.join(cacheDir, "buildManifests"); + this.#stageMetadataDir = path.join(cacheDir, "stageMetadata"); + this.#taskMetadataDir = path.join(cacheDir, "taskMetadata"); + this.#resultMetadataDir = path.join(cacheDir, "resultMetadata"); + this.#indexDir = path.join(cacheDir, "index"); + } + + /** + * Factory method to create or retrieve a CacheManager instance + * + * Returns a singleton CacheManager for the determined cache directory. + * The cache directory is resolved in this order: + * 1. UI5_DATA_DIR environment variable (resolved relative to cwd) + * 2. ui5DataDir from UI5 configuration file + * 3. Default: ~/.ui5/ + * + * @public + * @param {string} cwd Current working directory for resolving relative paths + * @returns {Promise} Singleton CacheManager instance for the cache directory + */ + static async create(cwd) { + // ENV var should take precedence over the dataDir from the configuration. + let ui5DataDir = process.env.UI5_DATA_DIR; + if (!ui5DataDir) { + const config = await Configuration.fromFile(); + ui5DataDir = config.getUi5DataDir(); + } + if (ui5DataDir) { + ui5DataDir = path.resolve(cwd, ui5DataDir); + } else { + ui5DataDir = path.join(os.homedir(), ".ui5"); + } + const cacheDir = path.join(ui5DataDir, "buildCache"); + log.verbose(`Using build cache directory: ${cacheDir}`); + + if (!chacheManagerInstances.has(cacheDir)) { + chacheManagerInstances.set(cacheDir, new CacheManager(cacheDir)); + } + return chacheManagerInstances.get(cacheDir); + } + + /** + * Generates the file path for a build manifest + * + * @param {string} packageName Package/project identifier + * @param {string} buildSignature Build signature hash + * @returns {string} Absolute path to the build manifest file + */ + #getBuildManifestPath(packageName, buildSignature) { + const pkgDir = getPathFromPackageName(packageName); + return path.join(this.#manifestDir, pkgDir, `${buildSignature}.json`); + } + + /** + * Reads a build manifest from cache + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @returns {Promise} Parsed manifest object or null if not found + * @throws {Error} If file read fails for reasons other than file not existing + */ + async readBuildManifest(projectId, buildSignature) { + try { + const manifest = await readFile(this.#getBuildManifestPath(projectId, buildSignature), "utf8"); + return JSON.parse(manifest); + } catch (err) { + if (err.code === "ENOENT") { + // Cache miss + return null; + } + throw new Error(`Failed to read build manifest for ` + + `${projectId} / ${buildSignature}: ${err.message}`, { + cause: err, + }); + } + } + + /** + * Writes a build manifest to cache + * + * Creates parent directories if they don't exist. Manifests are stored as + * formatted JSON (2-space indentation) for readability. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {object} manifest Build manifest object to serialize + * @returns {Promise} + */ + async writeBuildManifest(projectId, buildSignature, manifest) { + const manifestPath = this.#getBuildManifestPath(projectId, buildSignature); + await mkdir(path.dirname(manifestPath), {recursive: true}); + await writeFile(manifestPath, JSON.stringify(manifest, null, 2), "utf8"); + } + + /** + * Generates the file path for resource index metadata + * + * @param {string} packageName Package/project identifier + * @param {string} buildSignature Build signature hash + * @param {string} kind "source" or "result" + * @returns {string} Absolute path to the index metadata file + */ + #getIndexCachePath(packageName, buildSignature, kind) { + const pkgDir = getPathFromPackageName(packageName); + return path.join(this.#indexDir, pkgDir, `${kind}-${buildSignature}.json`); + } + + /** + * Reads resource index cache from storage + * + * The index cache contains the resource tree structure and task metadata, + * enabling efficient change detection and cache validation. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} kind "source" or "result" + * @returns {Promise} Parsed index cache object or null if not found + * @throws {Error} If file read fails for reasons other than file not existing + */ + async readIndexCache(projectId, buildSignature, kind) { + try { + const metadata = await readFile(this.#getIndexCachePath(projectId, buildSignature, kind), "utf8"); + return JSON.parse(metadata); + } catch (err) { + if (err.code === "ENOENT") { + // Cache miss + return null; + } + throw new Error(`Failed to read resource index cache for ` + + `${projectId} / ${buildSignature}: ${err.message}`, { + cause: err, + }); + } + } + + /** + * Writes resource index cache to storage + * + * Persists the resource index and associated task metadata for later retrieval. + * Creates parent directories if needed. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} kind "source" or "result" + * @param {object} index Index object containing resource tree and task metadata + * @returns {Promise} + */ + async writeIndexCache(projectId, buildSignature, kind, index) { + const indexPath = this.#getIndexCachePath(projectId, buildSignature, kind); + await mkdir(path.dirname(indexPath), {recursive: true}); + await writeFile(indexPath, JSON.stringify(index, null, 2), "utf8"); + } + + /** + * Generates the file path for stage metadata + * + * @param {string} packageName Package/project identifier + * @param {string} buildSignature Build signature hash + * @param {string} stageId Stage identifier (e.g., "result" or "task/taskName") + * @param {string} stageSignature Stage signature hash (based on input resources) + * @returns {string} Absolute path to the stage metadata file + */ + #getStageMetadataPath(packageName, buildSignature, stageId, stageSignature) { + const pkgDir = getPathFromPackageName(packageName); + stageId = stageId.replace("/", "_"); + return path.join(this.#stageMetadataDir, pkgDir, buildSignature, stageId, `${stageSignature}.json`); + } + + /** + * Reads stage metadata from cache + * + * Stage metadata contains information about resources produced by a build stage, + * including resource paths and their metadata. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} stageId Stage identifier (e.g., "result" or "task/taskName") + * @param {string} stageSignature Stage signature hash (based on input resources) + * @returns {Promise} Parsed stage metadata or null if not found + * @throws {Error} If file read fails for reasons other than file not existing + */ + async readStageCache(projectId, buildSignature, stageId, stageSignature) { + try { + const metadata = await readFile( + this.#getStageMetadataPath(projectId, buildSignature, stageId, stageSignature + ), "utf8"); + return JSON.parse(metadata); + } catch (err) { + if (err.code === "ENOENT") { + // Cache miss + return null; + } + throw new Error(`Failed to read stage metadata from cache for ` + + `${projectId} / ${buildSignature} / ${stageId} / ${stageSignature}: ${err.message}`, { + cause: err, + }); + } + } + + /** + * Writes stage metadata to cache + * + * Persists metadata about resources produced by a build stage. + * Creates parent directories if needed. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} stageId Stage identifier (e.g., "result" or "task/taskName") + * @param {string} stageSignature Stage signature hash (based on input resources) + * @param {object} metadata Stage metadata object to serialize + * @returns {Promise} + */ + async writeStageCache(projectId, buildSignature, stageId, stageSignature, metadata) { + const metadataPath = this.#getStageMetadataPath( + projectId, buildSignature, stageId, stageSignature); + await mkdir(path.dirname(metadataPath), {recursive: true}); + await writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf8"); + } + + /** + * Generates the file path for task metadata + * + * @param {string} packageName Package/project identifier + * @param {string} buildSignature Build signature hash + * @param {string} taskName Task name + * @param {string} type "project" or "dependency" + * @returns {string} Absolute path to the task metadata file + */ + #getTaskMetadataPath(packageName, buildSignature, taskName, type) { + const pkgDir = getPathFromPackageName(packageName); + return path.join(this.#taskMetadataDir, pkgDir, buildSignature, taskName, `${type}.json`); + } + + /** + * Reads task metadata from cache + * + * Task metadata contains resource request graphs and indices for tracking + * which resources a task accessed during execution. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} taskName Task name + * @param {string} type "project" or "dependency" + * @returns {Promise} Parsed task metadata or null if not found + * @throws {Error} If file read fails for reasons other than file not existing + */ + async readTaskMetadata(projectId, buildSignature, taskName, type) { + try { + const metadata = await readFile( + this.#getTaskMetadataPath(projectId, buildSignature, taskName, type), "utf8"); + return JSON.parse(metadata); + } catch (err) { + if (err.code === "ENOENT") { + // Cache miss + return null; + } + throw new Error(`Failed to read task metadata from cache for ` + + `${projectId} / ${buildSignature} / ${taskName} / ${type}: ${err.message}`, { + cause: err, + }); + } + } + + /** + * Writes task metadata to cache + * + * Persists task-specific metadata including resource request graphs and indices. + * Creates parent directories if needed. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} taskName Task name + * @param {string} type "project" or "dependency" + * @param {object} metadata Task metadata object to serialize + * @returns {Promise} + */ + async writeTaskMetadata(projectId, buildSignature, taskName, type, metadata) { + const metadataPath = this.#getTaskMetadataPath(projectId, buildSignature, taskName, type); + await mkdir(path.dirname(metadataPath), {recursive: true}); + await writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf8"); + } + + /** + * Generates the file path for result metadata + * + * @param {string} packageName Package/project identifier + * @param {string} buildSignature Build signature hash + * @param {string} stageSignature Stage signature hash (based on input resources) + * @returns {string} Absolute path to the result metadata file + */ + #getResultMetadataPath(packageName, buildSignature, stageSignature) { + const pkgDir = getPathFromPackageName(packageName); + return path.join(this.#resultMetadataDir, pkgDir, buildSignature, `${stageSignature}.json`); + } + + /** + * Reads result metadata from cache + * + * Result metadata contains information about the final build output, including + * references to all stage signatures that comprise the result. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} stageSignature Stage signature hash (based on input resources) + * @returns {Promise} Parsed result metadata or null if not found + * @throws {Error} If file read fails for reasons other than file not existing + */ + async readResultMetadata(projectId, buildSignature, stageSignature) { + try { + const metadata = await readFile( + this.#getResultMetadataPath(projectId, buildSignature, stageSignature + ), "utf8"); + return JSON.parse(metadata); + } catch (err) { + if (err.code === "ENOENT") { + // Cache miss + return null; + } + throw new Error(`Failed to read stage metadata from cache for ` + + `${projectId} / ${buildSignature} / ${stageSignature}: ${err.message}`, { + cause: err, + }); + } + } + + /** + * Writes result metadata to cache + * + * Persists metadata about the final build result, including stage signature mappings. + * Creates parent directories if needed. + * + * @public + * @param {string} projectId Project identifier (typically package name) + * @param {string} buildSignature Build signature hash + * @param {string} stageSignature Stage signature hash (based on input resources) + * @param {object} metadata Result metadata object to serialize + * @returns {Promise} + */ + async writeResultMetadata(projectId, buildSignature, stageSignature, metadata) { + const metadataPath = this.#getResultMetadataPath( + projectId, buildSignature, stageSignature); + await mkdir(path.dirname(metadataPath), {recursive: true}); + await writeFile(metadataPath, JSON.stringify(metadata, null, 2), "utf8"); + } + + /** + * Retrieves the file system path for a cached resource + * + * Looks up a resource in the content-addressable storage using its cache key + * and verifies its integrity. If integrity mismatches, attempts to recover by + * looking up the content by digest and updating the index. + * + * @public + * @param {string} buildSignature Build signature hash + * @param {string} stageId Stage identifier (e.g., "result" or "task/taskName") + * @param {string} stageSignature Stage signature hash + * @param {string} resourcePath Virtual path of the resource + * @param {string} integrity Expected integrity hash (e.g., "sha256-...") + * @returns {Promise} Absolute path to the cached resource file, or null if not found + * @throws {Error} If integrity is not provided + */ + async getResourcePathForStage(buildSignature, stageId, stageSignature, resourcePath, integrity) { + if (!integrity) { + throw new Error("Integrity hash must be provided to read from cache"); + } + // const cacheKey = this.#createKeyForStage(buildSignature, stageId, stageSignature, resourcePath, integrity); + const result = await cacache.get.info(this.#casDir, integrity); + if (!result) { + return null; + } + return result.path; + } + + /** + * Writes a resource to the cache for a specific stage + * + * If the resource content (identified by integrity hash) already exists in the + * content-addressable storage, only updates the index with a new cache key. + * Otherwise, writes the full content to storage. + * + * This enables efficient deduplication when the same resource content appears + * in multiple stages or builds. + * + * @public + * @param {string} buildSignature Build signature hash + * @param {string} stageId Stage identifier (e.g., "result" or "task/taskName") + * @param {string} stageSignature Stage signature hash + * @param {@ui5/fs/Resource} resource Resource to cache + * @returns {Promise} + */ + async writeStageResource(buildSignature, stageId, stageSignature, resource) { + // Check if resource has already been written + const integrity = await resource.getIntegrity(); + const hasResource = await cacache.get.info(this.#casDir, integrity); + if (!hasResource) { + const buffer = await resource.getBuffer(); + // Compress the buffer using gzip before caching + const compressedBuffer = await promisify(gzip)(buffer); + await cacache.put( + this.#casDir, + integrity, + compressedBuffer, + CACACHE_OPTIONS + ); + } + } +} diff --git a/packages/project/lib/build/cache/ProjectBuildCache.js b/packages/project/lib/build/cache/ProjectBuildCache.js new file mode 100644 index 00000000000..c242240e19b --- /dev/null +++ b/packages/project/lib/build/cache/ProjectBuildCache.js @@ -0,0 +1,1227 @@ +import {createResource, createProxy, createWriterCollection} from "@ui5/fs/resourceFactory"; +import {getLogger} from "@ui5/logger"; +import fs from "graceful-fs"; +import {promisify} from "node:util"; +import crypto from "node:crypto"; +import {gunzip, createGunzip} from "node:zlib"; +const readFile = promisify(fs.readFile); +import BuildTaskCache from "./BuildTaskCache.js"; +import StageCache from "./StageCache.js"; +import ResourceIndex from "./index/ResourceIndex.js"; +import {firstTruthy} from "./utils.js"; +const log = getLogger("build:cache:ProjectBuildCache"); + +export const INDEX_STATES = Object.freeze({ + RESTORING_PROJECT_INDICES: "restoring_project_indices", + RESTORING_DEPENDENCY_INDICES: "restoring_dependency_indices", + INITIAL: "initial", + FRESH: "fresh", + REQUIRES_UPDATE: "requires_update", +}); + +export const RESULT_CACHE_STATES = Object.freeze({ + PENDING_VALIDATION: "pending_validation", + NO_CACHE: "no_cache", + FRESH_AND_IN_USE: "fresh_and_in_use", +}); + +/** + * @typedef {object} StageMetadata + * @property {Object} resourceMetadata + * Resource metadata indexed by resource path + */ + +/** + * @typedef {object} StageCacheEntry + * @property {string} signature Signature of the cached stage + * @property {@ui5/fs/AbstractReader} stage Reader for the cached stage + * @property {string[]} writtenResourcePaths Array of resource paths written by the task + * @property {Map>} projectTagOperations + * Map of resource paths to their tags that were set or cleared during this stage's execution, for project tags + * @property {Map>} buildTagOperations + * Map of resource paths to their tags that were set or cleared during this stage's execution, for build tags + */ + +export default class ProjectBuildCache { + #taskCache = new Map(); + #stageCache = new StageCache(); + + #project; + #buildSignature; + #cacheManager; + #currentProjectReader; + #currentDependencyReader; + #sourceIndex; + #cachedSourceSignature; + #currentStageSignatures = new Map(); + #cachedResultSignature; + #currentResultSignature; + + // Pending changes + #changedProjectSourcePaths = []; + #changedDependencyResourcePaths = []; + #writtenResultResourcePaths = []; + + #combinedIndexState = INDEX_STATES.RESTORING_PROJECT_INDICES; + #resultCacheState = RESULT_CACHE_STATES.PENDING_VALIDATION; + + /** + * Creates a new ProjectBuildCache instance + * Use ProjectBuildCache.create() instead + * + * @param {@ui5/project/specifications/Project} project Project instance + * @param {string} buildSignature Build signature for the current build + * @param {object} cacheManager Cache manager instance for reading/writing cache data + */ + constructor(project, buildSignature, cacheManager) { + log.verbose( + `ProjectBuildCache for project ${project.getName()} uses build signature ${buildSignature}`); + this.#project = project; + this.#buildSignature = buildSignature; + this.#cacheManager = cacheManager; + } + + /** + * Factory method to create and initialize a ProjectBuildCache instance + * + * This is the recommended way to create a ProjectBuildCache as it ensures + * proper asynchronous initialization of the resource index and cache loading. + * + * @public + * @param {@ui5/project/specifications/Project} project Project instance + * @param {string} buildSignature Build signature for the current build + * @param {object} cacheManager Cache manager instance + * @returns {Promise<@ui5/project/build/cache/ProjectBuildCache>} Initialized cache instance + */ + static async create(project, buildSignature, cacheManager) { + const cache = new ProjectBuildCache(project, buildSignature, cacheManager); + const initStart = performance.now(); + await cache.#initSourceIndex(); + if (log.isLevelEnabled("perf")) { + log.perf( + `Initialized source index for project ${project.getName()} ` + + `in ${(performance.now() - initStart).toFixed(2)} ms`); + } + return cache; + } + + /** + * Sets the dependency reader for accessing dependency resources + * + * The dependency reader is used by tasks to access resources from project + * dependencies. Must be set before tasks that require dependencies are executed. + * + * @public + * @param {@ui5/fs/AbstractReader} dependencyReader Reader for dependency resources + * @returns {Promise} + * Array of changed resource paths since last build, true if cache is fresh, false + * if cache is empty + */ + async prepareProjectBuildAndValidateCache(dependencyReader) { + this.#currentProjectReader = this.#project.getReader(); + + this.#currentDependencyReader = dependencyReader; + + if (this.#combinedIndexState === INDEX_STATES.INITIAL) { + log.verbose(`Project ${this.#project.getName()} has an empty index cache, skipping change processing.`); + return false; + } + + if (this.#combinedIndexState === INDEX_STATES.RESTORING_DEPENDENCY_INDICES) { + const updateStart = performance.now(); + await this._refreshDependencyIndices(dependencyReader); + if (log.isLevelEnabled("perf")) { + log.perf( + `Initialized dependency indices for project ${this.#project.getName()} ` + + `in ${(performance.now() - updateStart).toFixed(2)} ms`); + } + this.#combinedIndexState = INDEX_STATES.FRESH; + + // After initializing dependency indices, the result cache must be validated + // This should be it's initial state anyways, so we just verify it here + if (this.#resultCacheState !== RESULT_CACHE_STATES.PENDING_VALIDATION) { + throw new Error(`Unexpected result cache state after restoring dependency indices ` + + `for project ${this.#project.getName()}: ${this.#resultCacheState}`); + } + } + + if (this.#combinedIndexState === INDEX_STATES.REQUIRES_UPDATE) { + const flushStart = performance.now(); + const changesDetected = await this.#flushPendingChanges(); + if (changesDetected) { + this.#resultCacheState = RESULT_CACHE_STATES.PENDING_VALIDATION; + } + if (log.isLevelEnabled("perf")) { + log.perf( + `Flushed pending changes for project ${this.#project.getName()} ` + + `in ${(performance.now() - flushStart).toFixed(2)} ms`); + } + this.#combinedIndexState = INDEX_STATES.FRESH; + } + + if (this.#resultCacheState === RESULT_CACHE_STATES.PENDING_VALIDATION) { + log.verbose(`Project ${this.#project.getName()} cache requires validation due to detected changes.`); + const findStart = performance.now(); + const changedResourcesOrFalse = await this.#findResultCache(); + if (log.isLevelEnabled("perf")) { + log.perf( + `Validated result cache for project ${this.#project.getName()} ` + + `in ${(performance.now() - findStart).toFixed(2)} ms`); + } + if (changedResourcesOrFalse) { + this.#resultCacheState = RESULT_CACHE_STATES.FRESH_AND_IN_USE; + } else { + this.#resultCacheState = RESULT_CACHE_STATES.NO_CACHE; + } + return changedResourcesOrFalse; + } + return this.isFresh(); + } + + /** + * Processes changed resources since last build, updating indices and invalidating tasks as needed + * + * @returns {Promise} + */ + async #flushPendingChanges() { + if (this.#changedProjectSourcePaths.length === 0 && + this.#changedDependencyResourcePaths.length === 0) { + return; + } + let sourceIndexChanged = false; + if (this.#changedProjectSourcePaths.length) { + // Update source index so we can use the signature later as part of the result stage signature + sourceIndexChanged = await this.#updateSourceIndex(this.#changedProjectSourcePaths); + } + + let depIndicesChanged = false; + if (this.#changedDependencyResourcePaths.length) { + await Promise.all(Array.from(this.#taskCache.values()).map(async (taskCache) => { + const changed = await taskCache + .updateDependencyIndices(this.#currentDependencyReader, this.#changedDependencyResourcePaths); + if (changed) { + depIndicesChanged = true; + } + })); + } + + // Reset pending changes + this.#changedProjectSourcePaths = []; + this.#changedDependencyResourcePaths = []; + + if (sourceIndexChanged || depIndicesChanged) { + // Relevant resources have changed, mark the cache as invalidated + return true; + } else { + log.verbose(`No relevant resource changes detected for project ${this.#project.getName()}`); + } + } + + /** + * Initialize dependency indices for all tasks. This only needs to be called once per build. + * Later builds of the same project during the same overall build can reuse the existing indices + * (they will be updated based on input via dependencyResourcesChanged) + * + * @param {@ui5/fs/AbstractReader} dependencyReader Reader for dependency resources + * @returns {Promise} + */ + async _refreshDependencyIndices(dependencyReader) { + await Promise.all(Array.from(this.#taskCache.values()).map(async (taskCache) => { + await taskCache.refreshDependencyIndices(dependencyReader); + })); + // Reset pending dependency changes since indices are fresh now anyways + this.#changedDependencyResourcePaths = []; + } + + /** + * Checks whether the cache is in a fresh state + * + * @public + * @returns {boolean} True if the cache is fresh + */ + isFresh() { + return this.#combinedIndexState === INDEX_STATES.FRESH && + this.#resultCacheState === RESULT_CACHE_STATES.FRESH_AND_IN_USE; + } + + /** + * Loads a cached result stage from persistent storage if available + * + * Attempts to load a cached result stage using the resource index signature. + * If found, creates a reader for the cached stage and sets it as the project's + * result stage. + * + * @returns {Promise} + * Array of resource paths written by the cached result stage (empty if the result stage remains unchanged), + * or false if no cache found + */ + async #findResultCache() { + const resultSignatures = this.#getPossibleResultStageSignatures(); + if (resultSignatures.includes(this.#currentResultSignature)) { + log.verbose( + `Project ${this.#project.getName()} result stage signature unchanged: ${this.#currentResultSignature}`); + return []; + } + + const res = await firstTruthy(resultSignatures.map(async (resultSignature) => { + const metadata = await this.#cacheManager.readResultMetadata( + this.#project.getId(), this.#buildSignature, resultSignature); + if (!metadata) { + return; + } + return [resultSignature, metadata]; + })); + + if (!res) { + log.verbose( + `No cached stage found for project ${this.#project.getName()}. Searched with ` + + `${resultSignatures.length} possible signatures.`); + return false; + } + const [resultSignature, resultMetadata] = res; + log.verbose(`Found result cache with signature ${resultSignature}`); + const {stageSignatures} = resultMetadata; + + const writtenResourcePaths = await this.#importStages(stageSignatures); + + log.verbose( + `Using cached result stage for project ${this.#project.getName()} with index signature ${resultSignature}`); + this.#currentResultSignature = resultSignature; + this.#cachedResultSignature = resultSignature; + return writtenResourcePaths; + } + + /** + * Imports cached stages and sets them in the project + * + * @param {Object} stageSignatures Map of stage names to their signatures + * @returns {Promise} Array of resource paths written by all imported stages + */ + async #importStages(stageSignatures) { + const stageNames = Object.keys(stageSignatures); + if (this.#project.getProjectResources().getStage()?.getId() === "initial") { + // Only initialize stages once + this.#project.getProjectResources().initStages(stageNames); + } + const importedStages = await Promise.all(stageNames.map(async (stageName) => { + const stageSignature = stageSignatures[stageName]; + const stageCache = await this.#findStageCache(stageName, [stageSignature]); + if (!stageCache) { + throw new Error(`Inconsistent result cache: Could not find cached stage ` + + `${stageName} with signature ${stageSignature} for project ${this.#project.getName()}`); + } + return [stageName, stageCache]; + })); + this.#project.getProjectResources().useResultStage(); + const writtenResourcePaths = new Set(); + for (const [stageName, stageCache] of importedStages) { + // Check whether the stage differs form the one currently in use + if (this.#currentStageSignatures.get(stageName)?.join("-") !== stageCache.signature) { + // Set stage + this.#project.getProjectResources().setStage(stageName, stageCache.stage, + stageCache.projectTagOperations, stageCache.buildTagOperations); + + // Store signature for later use in result stage signature calculation + this.#currentStageSignatures.set(stageName, stageCache.signature.split("-")); + + // Cached stage likely differs from the previous one (if any) + // Add all resources written by the cached stage to the set of written/potentially changed resources + for (const resourcePath of stageCache.writtenResourcePaths) { + writtenResourcePaths.add(resourcePath); + } + } + } + return Array.from(writtenResourcePaths); + } + + /** + * Calculates all possible result stage signatures based on current state + * + * @returns {string[]} Array of possible result stage signatures + */ + #getPossibleResultStageSignatures() { + const projectSourceSignature = this.#sourceIndex.getSignature(); + + const taskDependencySignatures = []; + for (const taskCache of this.#taskCache.values()) { + taskDependencySignatures.push(taskCache.getDependencyIndexSignatures()); + } + const dependencySignaturesCombinations = cartesianProduct(taskDependencySignatures); + + return dependencySignaturesCombinations.map((dependencySignatures) => { + const combinedDepSignature = createDependencySignature(dependencySignatures); + return createStageSignature(projectSourceSignature, combinedDepSignature); + }); + } + + /** + * Gets the current result stage signature + * + * @returns {string} Current result stage signature + */ + #getResultStageSignature() { + const projectSourceSignature = this.#sourceIndex.getSignature(); + const dependencySignatures = []; + for (const [, depSignature] of this.#currentStageSignatures.values()) { + dependencySignatures.push(depSignature); + } + const combinedDepSignature = createDependencySignature(dependencySignatures); + return createStageSignature(projectSourceSignature, combinedDepSignature); + } + + // ===== TASK MANAGEMENT ===== + + /** + * Prepares a task for execution by switching to its stage and checking for cached results + * + * This method: + * 1. Switches the project to the task's stage + * 2. Updates task indices if the task has been invalidated + * 3. Attempts to find a cached stage for the task + * 4. Returns whether the task needs to be executed + * + * @public + * @param {string} taskName Name of the task to prepare + * @returns {Promise} + * True if task can use cache, false if task needs execution, + * or an object with cache information for differential updates + */ + async prepareTaskExecutionAndValidateCache(taskName) { + const stageName = this.#getStageNameForTask(taskName); + const taskCache = this.#taskCache.get(taskName); + // Store current project reader (= state of the previous stage) for later use (e.g. in recordTaskResult) + this.#currentProjectReader = this.#project.getReader(); + // Switch project to new stage + this.#project.getProjectResources().useStage(stageName); + log.verbose(`Preparing task execution for task ${taskName} in project ${this.#project.getName()}...`); + if (!taskCache) { + log.verbose(`No task cache found`); + return false; + } + if (this.#writtenResultResourcePaths.length) { + // Update task indices based on source changes and changes from by previous tasks + const updateProjectIndicesStart = performance.now(); + await taskCache.updateProjectIndices(this.#currentProjectReader, this.#writtenResultResourcePaths); + if (log.isLevelEnabled("perf")) { + log.perf( + `Updated project indices for task ${taskName} in project ${this.#project.getName()} ` + + `in ${(performance.now() - updateProjectIndicesStart).toFixed(2)} ms`); + } + } + + // TODO: Implement: + // After index update, try to find cached stages for the new signatures + // let stageSignatures = taskCache.getAffiliatedSignaturePairs(); + + const projectSignatures = taskCache.getProjectIndexSignatures(); + const dependencySignatures = taskCache.getDependencyIndexSignatures(); + const stageSignatures = combineTwoArraysFast( + projectSignatures, + dependencySignatures, + ).map((signaturePair) => { + return createStageSignature(...signaturePair); + }); + + const stageCache = await this.#findStageCache(stageName, stageSignatures); + const oldStageSig = this.#currentStageSignatures.get(stageName)?.join("-"); + if (stageCache) { + this.#project.getProjectResources().setStage(stageName, stageCache.stage, + stageCache.projectTagOperations, stageCache.buildTagOperations); + + // Check whether the stage actually changed + if (stageCache.signature !== oldStageSig) { + // Store new stage signature for later use in result stage signature calculation + this.#currentStageSignatures.set(stageName, stageCache.signature.split("-")); + + // Cached stage likely differs from the previous one (if any) + // Add all resources written by the cached stage to the set of written/potentially changed resources + for (const resourcePath of stageCache.writtenResourcePaths) { + if (!this.#writtenResultResourcePaths.includes(resourcePath)) { + this.#writtenResultResourcePaths.push(resourcePath); + } + } + } + return true; // No need to execute the task + } else { + log.verbose(`No cached stage found for task ${taskName} in project ${this.#project.getName()}`); + // TODO: Optimize this crazy thing + const projectDeltas = taskCache.getProjectIndexDeltas(); + const depDeltas = taskCache.getDependencyIndexDeltas(); + + // Combine deltas of project stages with cached dependency signatures + const projDeltaSignatures = combineTwoArraysFast( + Array.from(projectDeltas.keys()), + dependencySignatures, + ).map((signaturePair) => { + return createStageSignature(...signaturePair); + }); + // Combine deltas of dependency stages with cached project signatures + const depDeltaSignatures = combineTwoArraysFast( + projectSignatures, + Array.from(projectDeltas.keys()), + ).map((signaturePair) => { + return createStageSignature(...signaturePair); + }); + // Combine deltas of both project and dependency stages + const deltaDeltaSignatures = combineTwoArraysFast( + Array.from(projectDeltas.keys()), + Array.from(depDeltas.keys()), + ).map((signaturePair) => { + return createStageSignature(...signaturePair); + }); + const deltaSignatures = [...projDeltaSignatures, ...depDeltaSignatures, ...deltaDeltaSignatures]; + const deltaStageCache = await this.#findStageCache(stageName, deltaSignatures); + if (deltaStageCache) { + // Store dependency signature for later use in result stage signature calculation + const [foundProjectSig, foundDepSig] = deltaStageCache.signature.split("-"); + + // Check whether the stage actually changed + if (oldStageSig !== deltaStageCache.signature) { + this.#currentStageSignatures.set(stageName, [foundProjectSig, foundDepSig]); + + // Cached stage likely differs from the previous one (if any) + // Add all resources written by the cached stage to the set of written/potentially changed resources + for (const resourcePath of deltaStageCache.writtenResourcePaths) { + if (!this.#writtenResultResourcePaths.includes(resourcePath)) { + this.#writtenResultResourcePaths.push(resourcePath); + } + } + } + + // Create new signature and determine changed resource paths + const projectDeltaInfo = projectDeltas.get(foundProjectSig); + const dependencyDeltaInfo = depDeltas.get(foundDepSig); + + const newSignature = createStageSignature( + projectDeltaInfo?.newSignature ?? foundProjectSig, + dependencyDeltaInfo?.newSignature ?? foundDepSig); + + log.verbose( + `Using delta cached stage for task ${taskName} in project ${this.#project.getName()} ` + + `with original signature ${deltaStageCache.signature} (now ${newSignature}) ` + + `and ${projectDeltaInfo?.changedPaths.length ?? "unknown"} changed project resource paths and ` + + `${dependencyDeltaInfo?.changedPaths.length ?? "unknown"} changed dependency resource paths.`); + + return { + previousStageCache: deltaStageCache, + newSignature: newSignature, + changedProjectResourcePaths: projectDeltaInfo?.changedPaths ?? [], + changedDependencyResourcePaths: dependencyDeltaInfo?.changedPaths ?? [] + }; + } + } + return false; // Task needs to be executed + } + + /** + * Attempts to find a cached stage for the given task + * + * Checks both in-memory stage cache and persistent cache storage for a matching + * stage signature. Returns the first matching cached stage found. + * + * @param {string} stageName Name of the stage to find + * @param {string[]} stageSignatures Possible signatures for the stage + * @returns {Promise<@ui5/project/build/cache/ProjectBuildCache~StageCacheEntry|undefined>} + * Cached stage entry or undefined if not found + */ + async #findStageCache(stageName, stageSignatures) { + if (!stageSignatures.length) { + return; + } + // Check cache exists and ensure it's still valid before using it + log.verbose(`Looking for cached stage for task ${stageName} in project ${this.#project.getName()} ` + + `with ${stageSignatures.length} possible signatures:\n - ${stageSignatures.join("\n - ")}`); + for (const stageSignature of stageSignatures) { + const stageCache = this.#stageCache.getCacheForSignature(stageName, stageSignature); + if (stageCache) { + return stageCache; + } + } + // TODO: If list of signatures is longer than N, + // retrieve all available signatures from cache manager first. + // Later maybe add a bloom filter for even larger sets + const stageCache = await firstTruthy(stageSignatures.map(async (stageSignature) => { + const stageMetadata = await this.#cacheManager.readStageCache( + this.#project.getId(), this.#buildSignature, stageName, stageSignature); + if (!stageMetadata) { + return; + } + log.verbose(`Found cached stage for task ${stageName} with signature ${stageSignature}`); + const {resourceMapping, resourceMetadata, projectTagOperations, buildTagOperations} = stageMetadata; + let writtenResourcePaths; + let stageReader; + if (resourceMapping) { + writtenResourcePaths = []; + // Restore writer collection + const readers = resourceMetadata.map((metadata) => { + writtenResourcePaths.push(...Object.keys(metadata)); + return this.#createReaderForStageCache( + stageName, stageSignature, metadata); + }); + + const writerMapping = Object.create(null); + for (const [resourcePath, metadataIndex] of Object.entries(resourceMapping)) { + if (!readers[metadataIndex]) { + throw new Error(`Inconsistent stage cache: No resource metadata ` + + `found at index ${metadataIndex} for resource ${resourcePath}`); + } + writerMapping[resourcePath] = readers[metadataIndex]; + } + + stageReader = createWriterCollection({ + name: `Restored cached stage ${stageName} for project ${this.#project.getName()}`, + writerMapping, + }); + } else { + writtenResourcePaths = Object.keys(resourceMetadata); + stageReader = this.#createReaderForStageCache(stageName, stageSignature, resourceMetadata); + } + + return { + signature: stageSignature, + stage: stageReader, + writtenResourcePaths, + projectTagOperations: tagOpsToMap(projectTagOperations), + buildTagOperations: tagOpsToMap(buildTagOperations), + }; + })); + return stageCache; + } + + /** + * Records the result of a task execution and updates the cache + * + * This method: + * 1. Creates a signature for the executed task based on its resource requests + * 2. Stores the resulting stage in the stage cache using that signature + * 3. Invalidates downstream tasks if they depend on written resources + * 4. Removes the task from the invalidated tasks list + * + * @public + * @param {string} taskName Name of the executed task + * @param {@ui5/project/build/cache/BuildTaskCache~ResourceRequests} projectResourceRequests + * Resource requests for project resources + * @param {@ui5/project/build/cache/BuildTaskCache~ResourceRequests|undefined} dependencyResourceRequests + * Resource requests for dependency resources + * @param {object} cacheInfo Cache information for differential updates + * @param {boolean} supportsDifferentialBuilds Whether the task supports differential updates + * @returns {Promise} + */ + async recordTaskResult( + taskName, projectResourceRequests, dependencyResourceRequests, cacheInfo, supportsDifferentialBuilds + ) { + if (!this.#taskCache.has(taskName)) { + // Initialize task cache + this.#taskCache.set(taskName, + new BuildTaskCache(this.#project.getName(), taskName, supportsDifferentialBuilds)); + } + log.verbose(`Recording results of task ${taskName} in project ${this.#project.getName()}...`); + const taskCache = this.#taskCache.get(taskName); + + // Identify resources written by task + const stage = this.#project.getProjectResources().getStage(); + const stageWriter = stage.getWriter(); + const writtenResources = await stageWriter.byGlob("/**/*"); + const writtenResourcePaths = writtenResources.map((res) => res.getOriginalPath()); + const {projectTagOperations, buildTagOperations} = + this.#project.getProjectResources().getResourceTagOperations(); + + let stageSignature; + if (cacheInfo) { + // TODO: Update + stageSignature = cacheInfo.newSignature; + // Add resources from previous stage cache to current stage + let reader; + if (cacheInfo.previousStageCache.stage.byGlob) { + // Reader instance + reader = cacheInfo.previousStageCache.stage; + } else { + // Stage instance + reader = cacheInfo.previousStageCache.stage.getWriter() ?? + cacheInfo.previousStageCache.stage.getCachedWriter(); + } + const previousWrittenResources = await reader.byGlob("/**/*"); + for (const res of previousWrittenResources) { + if (!writtenResourcePaths.includes(res.getOriginalPath())) { + await stageWriter.write(res); + } + } + } else { + // Calculate signature for executed task + const currentSignaturePair = await taskCache.recordRequests( + projectResourceRequests, + dependencyResourceRequests, + this.#currentProjectReader, + this.#currentDependencyReader + ); + // If provided, set dependency signature for later use in result stage signature calculation + const stageName = this.#getStageNameForTask(taskName); + this.#currentStageSignatures.set(stageName, currentSignaturePair); + stageSignature = createStageSignature(...currentSignaturePair); + } + + log.verbose(`Caching stage for task ${taskName} in project ${this.#project.getName()} ` + + `with signature ${stageSignature}`); + + // Store resulting stage in stage cache + this.#stageCache.addSignature( + this.#getStageNameForTask(taskName), stageSignature, this.#project.getProjectResources().getStage(), + writtenResourcePaths, projectTagOperations, buildTagOperations); + + // Update task cache with new metadata + log.verbose(`Task ${taskName} produced ${writtenResourcePaths.length} resources`); + + for (const resourcePath of writtenResourcePaths) { + if (!this.#writtenResultResourcePaths.includes(resourcePath)) { + this.#writtenResultResourcePaths.push(resourcePath); + } + } + // Reset current project reader + this.#currentProjectReader = null; + } + + /** + * Returns the task cache for a specific task + * + * @public + * @param {string} taskName Name of the task + * @returns {@ui5/project/build/cache/BuildTaskCache|undefined} + * The task cache or undefined if not found + */ + getTaskCache(taskName) { + return this.#taskCache.get(taskName); + } + + /** + * Records changed source files of the project and marks cache as requiring validation. + * This method must not be called during creation of the ProjectBuildCache or while the project is being built to + * avoid inconsistent result and cache corruption. + * + * @public + * @param {string[]} changedPaths Changed project source file paths + */ + projectSourcesChanged(changedPaths) { + for (const resourcePath of changedPaths) { + if (!this.#changedProjectSourcePaths.includes(resourcePath)) { + this.#changedProjectSourcePaths.push(resourcePath); + } + } + if (this.#combinedIndexState !== INDEX_STATES.INITIAL) { + // If there is an index cache, mark it as requiring update + this.#combinedIndexState = INDEX_STATES.REQUIRES_UPDATE; + } + } + + /** + * Records changed dependency resources and marks cache as requiring validation. + * This method must not be called during creation of the ProjectBuildCache or while the project is being built to + * avoid inconsistent result and cache corruption. + * + * @public + * @param {string[]} changedPaths Changed dependency resource paths + */ + dependencyResourcesChanged(changedPaths) { + for (const resourcePath of changedPaths) { + if (!this.#changedDependencyResourcePaths.includes(resourcePath)) { + this.#changedDependencyResourcePaths.push(resourcePath); + } + } + if (this.#combinedIndexState !== INDEX_STATES.INITIAL) { + // If there is an index cache, mark it as requiring update + this.#combinedIndexState = INDEX_STATES.REQUIRES_UPDATE; + } + } + + /** + * Initializes project stages for the given tasks + * + * Creates stage names for each task and initializes them in the project. + * This must be called before task execution begins. + * + * @public + * @param {string[]} taskNames Array of task names to initialize stages for + * @returns {Promise} + */ + async setTasks(taskNames) { + const stageNames = taskNames.map((taskName) => this.#getStageNameForTask(taskName)); + this.#project.getProjectResources().initStages(stageNames); + + // TODO: Rename function? We simply use it to have a point in time right before the project is built + } + + /** + * Signals that all tasks have completed and switches to the result stage + * + * This finalizes the build process by switching the project to use the + * final result stage containing all build outputs. + * Also updates the result resource index accordingly. + * + * @public + * @returns {Promise} Array of changed resource paths since the last build + */ + async allTasksCompleted() { + this.#project.getProjectResources().useResultStage(); + if (this.#combinedIndexState === INDEX_STATES.INITIAL) { + this.#combinedIndexState = INDEX_STATES.FRESH; + } + this.#resultCacheState = RESULT_CACHE_STATES.FRESH_AND_IN_USE; + const changedPaths = this.#writtenResultResourcePaths; + + this.#currentResultSignature = this.#getResultStageSignature(); + + // Reset updated resource paths + this.#writtenResultResourcePaths = []; + return changedPaths; + } + + buildFinished() { + this.#project.getProjectResources().buildFinished(); + } + + /** + * Generates the stage name for a given task + * + * @param {string} taskName Name of the task + * @returns {string} Stage name in the format "task/{taskName}" + */ + #getStageNameForTask(taskName) { + return `task/${taskName}`; + } + + /** + * Initializes the resource index from cache or creates a new one + * + * This method attempts to load a cached resource index. If found, it validates + * the index against current source files and invalidates affected tasks if + * resources have changed. If no cache exists, creates a fresh index. + * + * @returns {Promise} + * @throws {Error} If cached index signature doesn't match computed signature + */ + async #initSourceIndex() { + const sourceReader = this.#project.getSourceReader(); + const [resources, indexCache] = await Promise.all([ + await sourceReader.byGlob("/**/*"), + await this.#cacheManager.readIndexCache(this.#project.getId(), this.#buildSignature, "source"), + ]); + if (indexCache) { + log.verbose(`Using cached resource index for project ${this.#project.getName()}`); + // Create and diff resource index + const {resourceIndex, changedPaths} = + await ResourceIndex.fromCacheWithDelta(indexCache, resources, Date.now()); + + // Import task caches + const buildTaskCaches = await Promise.all( + indexCache.tasks.map(async ([taskName, supportsDifferentialBuilds]) => { + const projectRequests = await this.#cacheManager.readTaskMetadata( + this.#project.getId(), this.#buildSignature, taskName, "project"); + if (!projectRequests) { + throw new Error(`Failed to load project request cache for task ` + + `${taskName} in project ${this.#project.getName()}`); + } + const dependencyRequests = await this.#cacheManager.readTaskMetadata( + this.#project.getId(), this.#buildSignature, taskName, "dependencies"); + if (!dependencyRequests) { + throw new Error(`Failed to load dependency request cache for task ` + + `${taskName} in project ${this.#project.getName()}`); + } + return BuildTaskCache.fromCache(this.#project.getName(), taskName, !!supportsDifferentialBuilds, + projectRequests, dependencyRequests); + }) + ); + // Ensure taskCache is filled in the order of task execution + for (const buildTaskCache of buildTaskCaches) { + this.#taskCache.set(buildTaskCache.getTaskName(), buildTaskCache); + } + + if (changedPaths.length) { + // Relevant resources have changed, mark the cache as invalidated + // this.#resultCacheState = RESULT_CACHE_STATES.INVALIDATED; + } else { + // Source index is up-to-date, awaiting dependency indices validation + // Status remains at initializing + // this.#resultCacheState = RESULT_CACHE_STATES.INITIALIZING; + this.#cachedSourceSignature = resourceIndex.getSignature(); + } + this.#sourceIndex = resourceIndex; + // Since all source files are part of the result, declare any detected changes as newly written resources + this.#writtenResultResourcePaths = changedPaths; + // Now awaiting initialization of dependency indices + this.#combinedIndexState = INDEX_STATES.RESTORING_DEPENDENCY_INDICES; + } else { + // No index cache found, create new index + this.#sourceIndex = await ResourceIndex.create(resources, Date.now()); + this.#combinedIndexState = INDEX_STATES.INITIAL; + } + log.verbose( + `Initialized source index for project ${this.#project.getName()} ` + + `with signature ${this.#sourceIndex.getSignature()}`); + } + + /** + * Updates the source index with changed resource paths + * + * @param {string[]} changedResourcePaths Array of changed resource paths + * @returns {Promise} True if changes were detected, false otherwise + */ + async #updateSourceIndex(changedResourcePaths) { + const sourceReader = this.#project.getSourceReader(); + + const resources = []; + const removedResourcePaths = []; + await Promise.all(changedResourcePaths.map(async (resourcePath) => { + const resource = await sourceReader.byPath(resourcePath); + if (resource) { + resources.push(resource); + } else { + removedResourcePaths.push(resourcePath); + } + })); + const {removed} = await this.#sourceIndex.removeResources(removedResourcePaths); + const {added, updated} = await this.#sourceIndex.upsertResources(resources, Date.now()); + + if (removed.length || added.length || updated.length) { + log.verbose(`Source resource index for project ${this.#project.getName()} updated: ` + + `${removed.length} removed, ${added.length} added, ${updated.length} updated resources. ` + + `New signature: ${this.#sourceIndex.getSignature()}`); + const changedPaths = [...removed, ...added, ...updated]; + // Since all source files are part of the result, declare any detected changes as newly written resources + for (const resourcePath of changedPaths) { + if (!this.#writtenResultResourcePaths.includes(resourcePath)) { + this.#writtenResultResourcePaths.push(resourcePath); + } + } + return true; + } + return false; + } + + // ===== CACHE SERIALIZATION ===== + + /** + * Stores all cache data to persistent storage + * + * This method: + * 1. Stores the signatures of all stages that lead to the current build result + * 2. Writes all pending task stage caches to persistent storage + * 3. Writes task request metadata to persistent storage + * 4. Writes the source resource index to persistent storage + * + * @public + * @returns {Promise} + */ + async writeCache() { + const cacheWriteStart = performance.now(); + await Promise.all([ + this.#writeResultCache(), + + this.#writeTaskStageCache(), + this.#writeTaskRequestCache(), + + this.#writeSourceIndex(), + ]); + if (log.isLevelEnabled("perf")) { + log.perf( + `Wrote build cache for project ${this.#project.getName()} in ` + + `${(performance.now() - cacheWriteStart).toFixed(2)} ms`); + } + } + + /** + * Stores the signatures of all stages that lead to the current build result. This can be used to + * recreate the build result + * + * @returns {Promise} + */ + async #writeResultCache() { + const stageSignature = this.#currentResultSignature; + if (stageSignature === this.#cachedResultSignature) { + // No changes to already cached result stage + return; + } + log.verbose(`Storing result metadata for project ${this.#project.getName()} ` + + `using result stage signature ${stageSignature}`); + const stageSignatures = Object.create(null); + for (const [stageName, stageSigs] of this.#currentStageSignatures.entries()) { + stageSignatures[stageName] = stageSigs.join("-"); + } + + const metadata = { + stageSignatures, + }; + await this.#cacheManager.writeResultMetadata( + this.#project.getId(), this.#buildSignature, stageSignature, metadata); + } + + /** + * Writes all pending task stage caches to persistent storage + * + * @returns {Promise} + */ + async #writeTaskStageCache() { + if (!this.#stageCache.hasPendingCacheQueue()) { + return; + } + // Store stage caches + log.verbose(`Storing stage caches for project ${this.#project.getName()} ` + + `with build signature ${this.#buildSignature}`); + const stageQueue = this.#stageCache.flushCacheQueue(); + await Promise.all(stageQueue.map(async ([stageId, stageSignature]) => { + const {stage, projectTagOperations, buildTagOperations} = + this.#stageCache.getCacheForSignature(stageId, stageSignature); + const writer = stage.getWriter(); + + let metadata; + if (writer.getMapping) { + const writerMapping = writer.getMapping(); + // Ensure unique readers are used + const readers = Array.from(new Set(Object.values(writerMapping))); + // Map mapping entries to reader indices + const resourceMapping = Object.create(null); + for (const [virPath, reader] of Object.entries(writerMapping)) { + const readerIdx = readers.indexOf(reader); + resourceMapping[virPath] = readerIdx; + } + + const resourceMetadata = await Promise.all(readers.map(async (reader, idx) => { + const resources = await reader.byGlob("/**/*"); + + return await this.#writeStageResources(resources, stageId, stageSignature); + })); + + metadata = {resourceMapping, resourceMetadata}; + } else { + const resources = await writer.byGlob("/**/*"); + const resourceMetadata = await this.#writeStageResources(resources, stageId, stageSignature); + metadata = {resourceMetadata}; + } + metadata.projectTagOperations = tagOpsToObject(projectTagOperations); + metadata.buildTagOperations = tagOpsToObject(buildTagOperations); + await this.#cacheManager.writeStageCache( + this.#project.getId(), this.#buildSignature, stageId, stageSignature, metadata); + })); + } + + /** + * Writes stage resources to persistent storage and returns their metadata + * + * @param {@ui5/fs/Resource[]} resources Array of resources to write + * @param {string} stageId Stage identifier + * @param {string} stageSignature Stage signature + * @returns {Promise>} Resource metadata indexed by path + */ + async #writeStageResources(resources, stageId, stageSignature) { + const resourceMetadata = Object.create(null); + await Promise.all(resources.map(async (res) => { + // Store resource content in cacache via CacheManager + await this.#cacheManager.writeStageResource(this.#buildSignature, stageId, stageSignature, res); + + resourceMetadata[res.getOriginalPath()] = { + inode: res.getInode(), + lastModified: res.getLastModified(), + size: await res.getSize(), + integrity: await res.getIntegrity(), + }; + })); + return resourceMetadata; + } + + /** + * Writes task request metadata to persistent storage + * + * @returns {Promise} + */ + async #writeTaskRequestCache() { + // Store task caches + for (const [taskName, taskCache] of this.#taskCache) { + if (taskCache.hasNewOrModifiedCacheEntries()) { + const [projectRequests, dependencyRequests] = taskCache.toCacheObjects(); + log.verbose(`Storing task cache metadata for task ${taskName} in project ${this.#project.getName()} ` + + `with build signature ${this.#buildSignature}`); + const writes = []; + if (projectRequests) { + writes.push(this.#cacheManager.writeTaskMetadata( + this.#project.getId(), this.#buildSignature, taskName, "project", projectRequests)); + } + if (dependencyRequests) { + writes.push(this.#cacheManager.writeTaskMetadata( + this.#project.getId(), this.#buildSignature, taskName, "dependencies", dependencyRequests)); + } + await Promise.all(writes); + } + } + } + + /** + * Writes the source index cache to persistent storage + * + * @returns {Promise} + */ + async #writeSourceIndex() { + if (this.#cachedSourceSignature === this.#sourceIndex.getSignature()) { + // No changes to already cached result index + return; + } + log.verbose(`Storing resource index cache for project ${this.#project.getName()} ` + + `with build signature ${this.#buildSignature}`); + const sourceIndexObject = this.#sourceIndex.toCacheObject(); + const tasks = []; + for (const [taskName, taskCache] of this.#taskCache) { + tasks.push([taskName, taskCache.getSupportsDifferentialBuilds() ? 1 : 0]); + } + await this.#cacheManager.writeIndexCache(this.#project.getId(), this.#buildSignature, "source", { + ...sourceIndexObject, + tasks, + }); + } + + /** + * Creates a proxy reader for accessing cached stage resources + * + * The reader provides virtual access to cached resources by loading them from + * the cache storage on demand. Resource metadata is used to validate cache entries. + * + * @param {string} stageId Identifier for the stage (e.g., "result" or "task/{taskName}") + * @param {string} stageSignature Signature hash of the stage + * @param {Object} resourceMetadata Metadata for all cached resources + * @returns {@ui5/fs/AbstractReader} Proxy reader for cached resources + */ + #createReaderForStageCache(stageId, stageSignature, resourceMetadata) { + const allResourcePaths = Object.keys(resourceMetadata); + return createProxy({ + name: `Cache reader for task ${stageId} in project ${this.#project.getName()}`, + listResourcePaths: () => { + return allResourcePaths; + }, + getResource: async (virPath) => { + if (!allResourcePaths.includes(virPath)) { + return null; + } + const {lastModified, size, integrity, inode} = resourceMetadata[virPath]; + if (size === undefined || lastModified === undefined || + integrity === undefined) { + throw new Error(`Incomplete metadata for resource ${virPath} of task ${stageId} ` + + `in project ${this.#project.getName()}`); + } + // Get path to cached file contend stored in cacache via CacheManager + const cachePath = await this.#cacheManager.getResourcePathForStage( + this.#buildSignature, stageId, stageSignature, virPath, integrity); + if (!cachePath) { + throw new Error(`Unexpected cache miss for resource ${virPath} of task ${stageId} ` + + `in project ${this.#project.getName()}`); + } + return createResource({ + path: virPath, + sourceMetadata: { + fsPath: cachePath + }, + createStream: () => { + // Decompress the gzip-compressed stream + return fs.createReadStream(cachePath).pipe(createGunzip()); + }, + createBuffer: async () => { + // Decompress the gzip-compressed buffer + const compressedBuffer = await readFile(cachePath); + return await promisify(gunzip)(compressedBuffer); + }, + size, + lastModified, + integrity, + inode, + project: this.#project, + }); + } + }); + } +} + +/** + * Computes the cartesian product of an array of arrays + * + * @param {Array} arrays Array of arrays to compute the product of + * @returns {Array} Array of all possible combinations + */ +function cartesianProduct(arrays) { + if (arrays.length === 0) return [[]]; + if (arrays.some((arr) => arr.length === 0)) return []; + + let result = [[]]; + + for (const array of arrays) { + const temp = []; + for (const resultItem of result) { + for (const item of array) { + temp.push([...resultItem, item]); + } + } + result = temp; + } + + return result; +} + +/** + * Fast combination of two arrays into pairs + * + * Creates all possible pairs by combining each element from the first array + * with each element from the second array. + * + * @param {Array} array1 First array + * @param {Array} array2 Second array + * @returns {Array} Array of two-element pairs + */ +function combineTwoArraysFast(array1, array2) { + const len1 = array1.length; + const len2 = array2.length; + const result = new Array(len1 * len2); + + let idx = 0; + for (let i = 0; i < len1; i++) { + for (let j = 0; j < len2; j++) { + result[idx++] = [array1[i], array2[j]]; + } + } + + return result; +} + +/** + * Creates a combined stage signature from project and dependency signatures + * + * @param {string} projectSignature Project resource signature + * @param {string} dependencySignature Dependency resource signature + * @returns {string} Combined stage signature in format "projectSignature-dependencySignature" + */ +function createStageSignature(projectSignature, dependencySignature) { + return `${projectSignature}-${dependencySignature}`; +} + +/** + * Creates a combined signature hash from multiple stage dependency signatures + * + * @param {string[]} stageDependencySignatures Array of dependency signatures to combine + * @returns {string} SHA-256 hash of the combined signatures + */ +function createDependencySignature(stageDependencySignatures) { + return crypto.createHash("sha256").update(stageDependencySignatures.join("")).digest("hex"); +} + +function tagOpsToMap(tagOps) { + const map = new Map(); + for (const [resourcePath, tags] of Object.entries(tagOps)) { + map.set(resourcePath, new Map(Object.entries(tags))); + } + return map; +} + +/** + * @param {Map>} tagOps + * Map of resource paths to their tag operations + */ +function tagOpsToObject(tagOps) { + const obj = Object.create(null); + for (const [resourcePath, tags] of tagOps.entries()) { + obj[resourcePath] = Object.fromEntries(tags.entries()); + } + return obj; +} diff --git a/packages/project/lib/build/cache/ResourceRequestGraph.js b/packages/project/lib/build/cache/ResourceRequestGraph.js new file mode 100644 index 00000000000..fb152ec7e55 --- /dev/null +++ b/packages/project/lib/build/cache/ResourceRequestGraph.js @@ -0,0 +1,684 @@ +const ALLOWED_REQUEST_TYPES = new Set(["path", "patterns"]); + +/** + * Represents a single resource request with type and value + * + * A request can be either a path-based request (single resource) or a pattern-based + * request (multiple resources via glob patterns). + * + * @class + */ +export class Request { + /** + * Creates a new Request instance + * + * @public + * @param {string} type Either 'path' or 'patterns' + * @param {string|string[]} value The request value (string for path, array for patterns) + * @throws {Error} If type is invalid or value type doesn't match request type + */ + constructor(type, value) { + if (!ALLOWED_REQUEST_TYPES.has(type)) { + throw new Error(`Invalid request type: ${type}`); + } + + // Validate value type based on request type + if (type === "path" && typeof value !== "string") { + throw new Error(`Request type '${type}' requires value to be a string`); + } + + this.type = type; + this.value = value; + } + + /** + * Creates a canonical string representation for comparison + * + * Converts the request to a unique key string that can be used for equality + * checks and set operations. + * + * @public + * @returns {string} Canonical key in format "type:value" or "type:[pattern1,pattern2,...]" + */ + toKey() { + if (Array.isArray(this.value)) { + return `${this.type}:${JSON.stringify(this.value)}`; + } + return `${this.type}:${this.value}`; + } + + /** + * Creates a Request instance from a key string + * + * Inverse operation of toKey(), reconstructing a Request from its string representation. + * + * @public + * @param {string} key Key in format "type:value" or "type:[...]" + * @returns {Request} Reconstructed Request instance + */ + static fromKey(key) { + const colonIndex = key.indexOf(":"); + const type = key.substring(0, colonIndex); + const valueStr = key.substring(colonIndex + 1); + + // Check if value is a JSON array + if (valueStr.startsWith("[")) { + const value = JSON.parse(valueStr); + return new Request(type, value); + } + + return new Request(type, valueStr); + } + + /** + * Checks equality with another Request + * + * Compares both type and value, handling array values correctly. + * + * @public + * @param {Request} other Request to compare with + * @returns {boolean} True if requests are equal + */ + equals(other) { + if (this.type !== other.type) { + return false; + } + + if (Array.isArray(this.value) && Array.isArray(other.value)) { + if (this.value.length !== other.value.length) { + return false; + } + return this.value.every((val, idx) => val === other.value[idx]); + } + + return this.value === other.value; + } +} + +/** + * Represents a node in the request set graph + * + * Each node stores a delta of requests added at this level, with an optional parent + * reference. The full request set is computed by traversing up the parent chain. + * This enables efficient storage through delta encoding. + * + * @class + */ +class RequestSetNode { + /** + * Creates a new RequestSetNode instance + * + * @param {number} id Unique node identifier + * @param {number|null} [parent=null] Parent node ID or null for root nodes + * @param {Request[]} [addedRequests=[]] Requests added in this node (delta from parent) + * @param {*} [metadata={}] Associated metadata + */ + constructor(id, parent = null, addedRequests = [], metadata = {}) { + this.id = id; + this.parent = parent; // NodeId or null + this.addedRequests = new Set(addedRequests.map((r) => r.toKey())); + this.metadata = metadata; + + // Cached materialized set (lazy computed) + this._fullSetCache = null; + this._cacheValid = false; + } + + /** + * Gets the full materialized set of requests for this node + * + * Computes the complete set of requests by traversing up the parent chain + * and collecting all added requests. Results are cached for performance. + * + * @param {ResourceRequestGraph} graph The graph containing this node + * @returns {Set} Set of request keys + */ + getMaterializedSet(graph) { + if (this._cacheValid && this._fullSetCache !== null) { + return new Set(this._fullSetCache); + } + + const result = new Set(); + let current = this; + + // Walk up parent chain, collecting all added requests + while (current !== null) { + for (const requestKey of current.addedRequests) { + result.add(requestKey); + } + current = current.parent ? graph.getNode(current.parent) : null; + } + + // Cache the result + this._fullSetCache = result; + this._cacheValid = true; + + return new Set(result); + } + + /** + * Invalidates the materialized set cache + * + * Should be called when the graph structure changes to ensure the cached + * materialized set is recomputed on next access. + */ + invalidateCache() { + this._cacheValid = false; + this._fullSetCache = null; + } + + /** + * Gets the full set of requests as Request objects + * + * Similar to getMaterializedSet but returns Request instances instead of keys. + * + * @param {ResourceRequestGraph} graph The graph containing this node + * @returns {Request[]} Array of Request objects + */ + getMaterializedRequests(graph) { + const keys = this.getMaterializedSet(graph); + return Array.from(keys).map((key) => Request.fromKey(key)); + } + + /** + * Gets only the requests added in this node (delta) + * + * Returns the requests added at this level, not including parent requests. + * This is the delta that was stored in the node. + * + * @returns {Request[]} Array of Request objects added in this node + */ + getAddedRequests() { + return Array.from(this.addedRequests).map((key) => Request.fromKey(key)); + } + + /** + * Gets the parent node ID + * + * @returns {number|null} Parent node ID or null if this is a root node + */ + getParentId() { + return this.parent; + } +} + +/** + * Graph managing request set nodes with delta encoding + * + * This graph structure optimizes storage of multiple related request sets by using + * delta encoding - each node stores only the requests added relative to its parent. + * This is particularly efficient when request sets have significant overlap. + * + * The graph automatically finds the best parent for new request sets to minimize + * the delta size and maintain efficient storage. + * + * @class + */ +export default class ResourceRequestGraph { + /** + * Creates a new ResourceRequestGraph instance + * + * @public + */ + constructor() { + this.nodes = new Map(); // nodeId -> RequestSetNode + this.nextId = 1; + } + + /** + * Gets a node by ID + * + * @public + * @param {number} nodeId Node identifier + * @returns {RequestSetNode|undefined} The node or undefined if not found + */ + getNode(nodeId) { + return this.nodes.get(nodeId); + } + + /** + * Gets all node IDs in the graph + * + * @public + * @returns {number[]} Array of all node IDs + */ + getAllNodeIds() { + return Array.from(this.nodes.keys()); + } + + /** + * Calculates which requests need to be added (delta) + * + * Determines the difference between a new request set and a parent's materialized set, + * returning only the requests that need to be stored in the delta. + * + * @param {Request[]} newRequestSet New request set + * @param {Set} parentSet Parent's materialized set (as keys) + * @returns {Request[]} Array of requests to add + */ + _calculateAddedRequests(newRequestSet, parentSet) { + const newKeys = new Set(newRequestSet.map((r) => r.toKey())); + const addedKeys = newKeys.difference(parentSet); + + return Array.from(addedKeys).map((key) => Request.fromKey(key)); + } + + /** + * Adds a new request set to the graph + * + * Automatically finds the best parent node (largest subset) and stores only + * the delta of requests. If no suitable parent is found, creates a root node. + * + * @public + * @param {Request[]} requests Array of Request objects + * @param {*} [metadata=null] Optional metadata to store with this node + * @returns {number} The new node ID + */ + addRequestSet(requests, metadata = null) { + const nodeId = this.nextId++; + + // Find best parent + const parentId = this.findBestParent(requests); + + if (parentId === null) { + // No existing nodes, or no suitable parent - create root node + const node = new RequestSetNode(nodeId, null, requests, metadata); + this.nodes.set(nodeId, node); + return nodeId; + } + + // Create node with delta from best parent + const parentNode = this.getNode(parentId); + const parentSet = parentNode.getMaterializedSet(this); + const addedRequests = this._calculateAddedRequests(requests, parentSet); + + const node = new RequestSetNode(nodeId, parentId, addedRequests, metadata); + this.nodes.set(nodeId, node); + + return nodeId; + } + + /** + * Finds the best parent for a new request set + * + * Searches for the existing node with the largest subset of the new request set. + * This minimizes the delta size and optimizes storage efficiency. + * + * @public + * @param {Request[]} requestSet Array of Request objects + * @returns {number|null} Parent node ID or null if no suitable parent exists + */ + findBestParent(requestSet) { + if (this.nodes.size === 0) { + return null; + } + + const queryKeys = new Set(requestSet.map((r) => r.toKey())); + let bestParent = null; + let greatestSubset = -1; + + // Compare against all existing nodes + for (const [nodeId, node] of this.nodes) { + const nodeSet = node.getMaterializedSet(this); + + // Check if nodeSet is a subset of queryKeys + const isSubset = nodeSet.isSubsetOf(queryKeys); + + // We want the parent the greatest overlap + if (isSubset && nodeSet.size > greatestSubset) { + bestParent = nodeId; + greatestSubset = nodeSet.size; + } + } + + return bestParent; + } + + /** + * Finds a node with an identical request set + * + * Searches for an existing node whose materialized request set exactly matches + * the given request set. Used to avoid creating duplicate nodes. + * + * @public + * @param {Request[]} requests Array of Request objects + * @returns {number|null} Node ID of exact match, or null if no match found + */ + findExactMatch(requests) { + // Convert to request keys for comparison + const queryKeys = new Set(requests.map((req) => new Request(req.type, req.value).toKey())); + + // Must have same size to be identical + const querySize = queryKeys.size; + + for (const [nodeId, node] of this.nodes) { + const nodeSet = node.getMaterializedSet(this); + + // Quick size check first + if (nodeSet.size !== querySize) { + continue; + } + + // Check if sets are identical (same size + subset = equality) + if (nodeSet.isSubsetOf(queryKeys)) { + return nodeId; + } + } + + return null; + } + + /** + * Gets metadata associated with a node + * + * @public + * @param {number} nodeId Node identifier + * @returns {*} Metadata or null if node not found + */ + getMetadata(nodeId) { + const node = this.getNode(nodeId); + return node ? node.metadata : null; + } + + /** + * Updates metadata for a node + * + * @public + * @param {number} nodeId Node identifier + * @param {*} metadata New metadata value + */ + setMetadata(nodeId, metadata) { + const node = this.getNode(nodeId); + if (node) { + node.metadata = metadata; + } + } + + /** + * Gets all unique requests across all nodes in the graph + * + * Collects the union of all materialized request sets from every node. + * + * @public + * @returns {Request[]} Array of all unique Request objects in the graph + */ + getAllRequests() { + const allRequestKeys = new Set(); + + for (const node of this.nodes.values()) { + const nodeSet = node.getMaterializedSet(this); + for (const key of nodeSet) { + allRequestKeys.add(key); + } + } + + return Array.from(allRequestKeys).map((key) => Request.fromKey(key)); + } + + /** + * Gets statistics about the graph structure + * + * Provides metrics about the graph's efficiency, including node count, + * average requests per node, storage overhead, and tree depth statistics. + * + * @public + * @returns {object} Statistics object + * @returns {number} return.nodeCount Total number of nodes + * @returns {number} return.averageRequestsPerNode Average materialized requests per node + * @returns {number} return.averageStoredDeltaSize Average stored delta size per node + * @returns {number} return.averageDepth Average depth in the tree + * @returns {number} return.maxDepth Maximum depth in the tree + * @returns {number} return.compressionRatio Ratio of stored deltas to total requests (lower is better) + */ + getStats() { + let totalRequests = 0; + let totalStoredDeltas = 0; + const depths = []; + + for (const node of this.nodes.values()) { + totalRequests += node.getMaterializedSet(this).size; + totalStoredDeltas += node.addedRequests.size; + + // Calculate depth + let depth = 0; + let current = node; + while (current.parent !== null) { + depth++; + current = this.getNode(current.parent); + } + depths.push(depth); + } + + return { + nodeCount: this.nodes.size, + averageRequestsPerNode: this.nodes.size > 0 ? totalRequests / this.nodes.size : 0, + averageStoredDeltaSize: this.nodes.size > 0 ? totalStoredDeltas / this.nodes.size : 0, + averageDepth: depths.length > 0 ? depths.reduce((a, b) => a + b, 0) / depths.length : 0, + maxDepth: depths.length > 0 ? Math.max(...depths) : 0, + compressionRatio: totalRequests > 0 ? totalStoredDeltas / totalRequests : 1 + }; + } + + /** + * Gets the number of nodes in the graph + * + * @public + * @returns {number} Node count + */ + getSize() { + return this.nodes.size; + } + + /** + * Iterates through nodes in breadth-first order (by depth level) + * + * Parents are always yielded before their children, allowing efficient traversal + * where you can check parent nodes first and only examine deltas of subtrees as needed. + * + * @public + * @generator + * @yields {object} Node information + * @yields {number} return.nodeId Node identifier + * @yields {RequestSetNode} return.node Node instance + * @yields {number} return.depth Depth level in the tree + * @yields {number|null} return.parentId Parent node ID or null for root nodes + * + * @example + * // Traverse all nodes, checking parents before children + * for (const {nodeId, node, depth, parentId} of graph.traverseByDepth()) { + * const delta = node.getAddedRequests(); + * const fullSet = node.getMaterializedRequests(graph); + * console.log(`Node ${nodeId} at depth ${depth}: +${delta.length} requests`); + * } + * + * @example + * // Early termination: find first matching node without processing children + * for (const {nodeId, node} of graph.traverseByDepth()) { + * if (nodeMatchesQuery(node)) { + * console.log(`Found match at node ${nodeId}`); + * break; // Stop traversal + * } + * } + */ + * traverseByDepth() { + if (this.nodes.size === 0) { + return; + } + + // Build children map for efficient traversal + const childrenMap = new Map(); // parentId -> [childIds] + const rootNodes = []; + + for (const [nodeId, node] of this.nodes) { + if (node.parent === null) { + rootNodes.push(nodeId); + } else { + if (!childrenMap.has(node.parent)) { + childrenMap.set(node.parent, []); + } + childrenMap.get(node.parent).push(nodeId); + } + } + + // Breadth-first traversal using a queue + const queue = rootNodes.map((nodeId) => ({nodeId, depth: 0})); + + while (queue.length > 0) { + const {nodeId, depth} = queue.shift(); + const node = this.getNode(nodeId); + + // Yield current node + yield { + nodeId, + node, + depth, + parentId: node.parent + }; + + // Enqueue children for next depth level + const children = childrenMap.get(nodeId); + if (children) { + for (const childId of children) { + queue.push({nodeId: childId, depth: depth + 1}); + } + } + } + } + + /** + * Iterates through nodes starting from a specific node, traversing its subtree + * + * Useful for examining only a portion of the graph rooted at a particular node. + * + * @public + * @generator + * @param {number} startNodeId Node ID to start traversal from + * @yields {object} Node information + * @yields {number} return.nodeId Node identifier + * @yields {RequestSetNode} return.node Node instance + * @yields {number} return.depth Relative depth from the start node + * @yields {number|null} return.parentId Parent node ID or null + * + * @example + * // Traverse only the subtree under a specific node + * const matchNodeId = graph.findBestParent(query); + * for (const {nodeId, node, depth} of graph.traverseSubtree(matchNodeId)) { + * console.log(`Processing node ${nodeId} at relative depth ${depth}`); + * } + */ + * traverseSubtree(startNodeId) { + const startNode = this.getNode(startNodeId); + if (!startNode) { + return; + } + + // Build children map + const childrenMap = new Map(); + for (const [nodeId, node] of this.nodes) { + if (node.parent !== null) { + if (!childrenMap.has(node.parent)) { + childrenMap.set(node.parent, []); + } + childrenMap.get(node.parent).push(nodeId); + } + } + + // Breadth-first traversal starting from the specified node + const queue = [{nodeId: startNodeId, depth: 0}]; + + while (queue.length > 0) { + const {nodeId, depth} = queue.shift(); + const node = this.getNode(nodeId); + + yield { + nodeId, + node, + depth, + parentId: node.parent + }; + + // Enqueue children + const children = childrenMap.get(nodeId); + if (children) { + for (const childId of children) { + queue.push({nodeId: childId, depth: depth + 1}); + } + } + } + } + + /** + * Gets all children node IDs for a given parent node + * + * @public + * @param {number} parentId Parent node identifier + * @returns {number[]} Array of child node IDs + */ + getChildren(parentId) { + const children = []; + for (const [nodeId, node] of this.nodes) { + if (node.parent === parentId) { + children.push(nodeId); + } + } + return children; + } + + /** + * Exports graph structure for serialization + * + * Converts the graph to a plain object suitable for JSON serialization. + * Metadata is not included in the export and must be handled separately. + * + * @public + * @returns {object} Graph structure + * @returns {Array} return.nodes Array of node objects with id, parent, and addedRequests + * @returns {number} return.nextId Next available node ID + */ + toCacheObject() { + const nodes = []; + + for (const [nodeId, node] of this.nodes) { + nodes.push({ + id: nodeId, + parent: node.parent, + addedRequests: Array.from(node.addedRequests) + }); + } + + return {nodes, nextId: this.nextId}; + } + + /** + * Creates a graph from a serialized cache object + * + * Reconstructs the graph structure from a plain object produced by toCacheObject(). + * Metadata must be restored separately if needed. + * + * @public + * @param {object} metadata Serialized graph structure + * @param {Array} metadata.nodes Array of node objects with id, parent, and addedRequests + * @param {number} metadata.nextId Next available node ID + * @returns {ResourceRequestGraph} Reconstructed graph instance + */ + static fromCache(metadata) { + const graph = new ResourceRequestGraph(); + + // Restore nextId + graph.nextId = metadata.nextId; + + // Recreate all nodes + for (const nodeData of metadata.nodes) { + const {id, parent, addedRequests} = nodeData; + + // Convert request keys back to Request instances + const requestInstances = addedRequests.map((key) => Request.fromKey(key)); + + // Create node directly + const node = new RequestSetNode(id, parent, requestInstances); + graph.nodes.set(id, node); + } + + return graph; + } +} diff --git a/packages/project/lib/build/cache/ResourceRequestManager.js b/packages/project/lib/build/cache/ResourceRequestManager.js new file mode 100644 index 00000000000..cc1872623d9 --- /dev/null +++ b/packages/project/lib/build/cache/ResourceRequestManager.js @@ -0,0 +1,669 @@ +import micromatch from "micromatch"; +import ResourceRequestGraph, {Request} from "./ResourceRequestGraph.js"; +import ResourceIndex from "./index/ResourceIndex.js"; +import TreeRegistry from "./index/TreeRegistry.js"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("build:cache:ResourceRequestManager"); + +/** + * Manages resource requests and their associated indices for a single task + * + * Tracks all resources accessed by a task during execution and maintains resource indices + * for cache validation and differential updates. Supports both full and delta-based caching + * strategies. + * + * @class + */ +class ResourceRequestManager { + #taskName; + #projectName; + #requestGraph; + + #treeRegistries = []; + #treeUpdateDeltas = new Map(); + + #hasNewOrModifiedCacheEntries; + #useDifferentialUpdate; + #unusedAtLeastOnce; + + /** + * Creates a new ResourceRequestManager instance + * + * @param {string} projectName Name of the project + * @param {string} taskName Name of the task + * @param {boolean} useDifferentialUpdate Whether to track differential updates + * @param {ResourceRequestGraph} [requestGraph] Optional pre-existing request graph from cache + * @param {boolean} [unusedAtLeastOnce=false] Whether the task has been unused at least once + */ + constructor(projectName, taskName, useDifferentialUpdate, requestGraph, unusedAtLeastOnce = false) { + this.#projectName = projectName; + this.#taskName = taskName; + this.#useDifferentialUpdate = useDifferentialUpdate; + this.#unusedAtLeastOnce = unusedAtLeastOnce; + if (requestGraph) { + this.#requestGraph = requestGraph; + this.#hasNewOrModifiedCacheEntries = false; // Using cache + } else { + this.#requestGraph = new ResourceRequestGraph(); + this.#hasNewOrModifiedCacheEntries = true; + } + } + + /** + * Factory method to restore a ResourceRequestManager from cached data + * + * Deserializes a previously cached request graph and its associated resource indices, + * including both root indices and delta indices for differential updates. + * + * @param {string} projectName Name of the project + * @param {string} taskName Name of the task + * @param {boolean} useDifferentialUpdate Whether to track differential updates + * @param {object} cacheData Cached metadata object + * @param {object} cacheData.requestSetGraph Serialized request graph + * @param {Array} cacheData.rootIndices Array of root resource indices + * @param {Array} [cacheData.deltaIndices] Array of delta resource indices + * @param {boolean} [cacheData.unusedAtLeastOnce] Whether the task has been unused + * @returns {ResourceRequestManager} Restored manager instance + */ + static fromCache(projectName, taskName, useDifferentialUpdate, { + requestSetGraph, rootIndices, deltaIndices, unusedAtLeastOnce + }) { + const requestGraph = ResourceRequestGraph.fromCache(requestSetGraph); + const resourceRequestManager = new ResourceRequestManager( + projectName, taskName, useDifferentialUpdate, requestGraph, unusedAtLeastOnce); + const registries = new Map(); + // Restore root resource indices + for (const {nodeId, resourceIndex: serializedIndex} of rootIndices) { + const metadata = requestGraph.getMetadata(nodeId); + const registry = resourceRequestManager.#newTreeRegistry(); + registries.set(nodeId, registry); + metadata.resourceIndex = ResourceIndex.fromCacheShared(serializedIndex, registry); + } + // Restore delta resource indices + if (deltaIndices) { + for (const {nodeId, addedResourceIndex} of deltaIndices) { + const node = requestGraph.getNode(nodeId); + const {resourceIndex: parentResourceIndex} = requestGraph.getMetadata(node.getParentId()); + const registry = registries.get(node.getParentId()); + if (!registry) { + throw new Error(`Missing tree registry for parent of node ID ${nodeId} of task ` + + `'${taskName}' of project '${projectName}'`); + } + const resourceIndex = parentResourceIndex.deriveTreeWithIndex(addedResourceIndex); + + requestGraph.setMetadata(nodeId, { + resourceIndex, + }); + } + } + return resourceRequestManager; + } + + /** + * Gets all project index signatures for this task + * + * Returns signatures from all recorded project-request sets. Each signature represents + * a unique combination of resources belonging to the current project that were accessed + * during task execution. This can be used to form cache keys for restoring cached task results. + * + * @public + * @returns {string[]} Array of signature strings + * @throws {Error} If resource index is missing for any request set + */ + getIndexSignatures() { + const requestSetIds = this.#requestGraph.getAllNodeIds(); + const signatures = requestSetIds.map((requestSetId) => { + const {resourceIndex} = this.#requestGraph.getMetadata(requestSetId); + if (!resourceIndex) { + throw new Error(`Resource index missing for request set ID ${requestSetId}`); + } + return resourceIndex.getSignature(); + }); + if (this.#unusedAtLeastOnce) { + signatures.push("X"); // Signature for when no requests were made + } + return signatures; + } + + /** + * Updates all indices based on current resources without delta tracking + * + * Performs a full refresh of all resource indices by fetching current resources + * and updating or removing indexed resources as needed. Does not track changes + * between the old and new state. + * + * @public + * @param {module:@ui5/fs.AbstractReader} reader Reader for accessing project resources + * @returns {Promise} + */ + async refreshIndices(reader) { + if (this.#requestGraph.getSize() === 0) { + // No requests recorded -> No updates necessary + return; + } + + const resourceCache = new Map(); + for (const {nodeId} of this.#requestGraph.traverseByDepth()) { + const {resourceIndex} = this.#requestGraph.getMetadata(nodeId); + if (!resourceIndex) { + throw new Error(`Missing resource index for request set ID ${nodeId}`); + } + const addedRequests = this.#requestGraph.getNode(nodeId).getAddedRequests(); + const resourcesToUpdate = await this.#getResourcesForRequests(addedRequests, reader, resourceCache); + + // Determine resources to remove + const indexedResourcePaths = resourceIndex.getResourcePaths(); + const currentResourcePaths = resourcesToUpdate.map((res) => res.getOriginalPath()); + const resourcesToRemove = indexedResourcePaths.filter((resPath) => { + return !currentResourcePaths.includes(resPath); + }); + if (resourcesToRemove.length) { + await resourceIndex.removeResources(resourcesToRemove); + } + if (resourcesToUpdate.length) { + await resourceIndex.upsertResources(resourcesToUpdate); + } + } + + await this.#flushTreeChangesWithoutDiffTracking(); + } + + /** + * Filters relevant resource changes and updates the indices if necessary + * + * Processes changed resource paths, identifies which request sets are affected, + * and updates their resource indices accordingly. Supports both full updates and + * differential tracking based on the manager configuration. + * + * @public + * @param {module:@ui5/fs.AbstractReader} reader Reader for accessing project resources + * @param {string[]} changedResourcePaths Array of changed project resource paths + * @returns {Promise} True if any changes were detected, false otherwise + */ + async updateIndices(reader, changedResourcePaths) { + const matchingRequestSetIds = []; + const updatesByRequestSetId = new Map(); + if (this.#requestGraph.getSize() === 0) { + // No requests recorded -> No updates necessary + return false; + } + + // Process all nodes, parents before children + for (const {nodeId, node, parentId} of this.#requestGraph.traverseByDepth()) { + const addedRequests = node.getAddedRequests(); // Resource requests added at this level + let relevantUpdates; + if (addedRequests.length) { + relevantUpdates = this.#matchResourcePaths(addedRequests, changedResourcePaths); + } else { + relevantUpdates = []; + } + if (parentId) { + // Include updates from parent nodes + const parentUpdates = updatesByRequestSetId.get(parentId); + if (parentUpdates && parentUpdates.length) { + relevantUpdates.push(...parentUpdates); + } + } + if (relevantUpdates.length) { + updatesByRequestSetId.set(nodeId, relevantUpdates); + matchingRequestSetIds.push(nodeId); + } + } + if (!matchingRequestSetIds.length) { + return false; // No relevant changes for any request set + } + + const resourceCache = new Map(); + // Update matching resource indices + for (const requestSetId of matchingRequestSetIds) { + const {resourceIndex} = this.#requestGraph.getMetadata(requestSetId); + if (!resourceIndex) { + throw new Error(`Missing resource index for request set ID ${requestSetId}`); + } + + const resourcePathsToUpdate = updatesByRequestSetId.get(requestSetId); + const resourcesToUpdate = []; + const removedResourcePaths = []; + for (const resourcePath of resourcePathsToUpdate) { + let resource; + if (resourceCache.has(resourcePath)) { + resource = resourceCache.get(resourcePath); + } else { + resource = await reader.byPath(resourcePath); + resourceCache.set(resourcePath, resource); + } + if (resource) { + resourcesToUpdate.push(resource); + } else { + // Resource has been removed + removedResourcePaths.push(resourcePath); + } + } + if (removedResourcePaths.length) { + await resourceIndex.removeResources(removedResourcePaths); + } + if (resourcesToUpdate.length) { + await resourceIndex.upsertResources(resourcesToUpdate); + } + } + let hasChanges; + if (this.#useDifferentialUpdate) { + hasChanges = await this.#flushTreeChangesWithDiffTracking(); + } else { + hasChanges = await this.#flushTreeChangesWithoutDiffTracking(); + } + if (hasChanges) { + this.#hasNewOrModifiedCacheEntries = true; + } + return hasChanges; + } + + /** + * Matches changed resources against a set of requests + * + * Tests each request against the changed resource paths using exact path matching + * for 'path'/'dep-path' requests and glob pattern matching for 'patterns'/'dep-patterns' requests. + * + * @param {Request[]} resourceRequests - Array of resource requests to match against + * @param {string[]} resourcePaths - Changed project resource paths + * @returns {string[]} Array of matched resource paths + */ + #matchResourcePaths(resourceRequests, resourcePaths) { + const matchedResources = []; + for (const {type, value} of resourceRequests) { + if (type === "path") { + if (resourcePaths.includes(value) && !matchedResources.includes(value)) { + matchedResources.push(value); + } + } else { + const globMatches = micromatch(resourcePaths, value, { + dot: true + }); + for (const match of globMatches) { + if (!matchedResources.includes(match)) { + matchedResources.push(match); + } + } + } + } + return matchedResources; + } + + /** + * Flushes all tree registries to apply batched updates without tracking changes + * + * Commits all pending tree modifications but does not record the specific changes + * (added, updated, removed resources). Used when differential updates are disabled. + * + * @returns {Promise} True if any changes were detected, false otherwise + */ + async #flushTreeChangesWithoutDiffTracking() { + const results = await this.#flushTreeChanges(); + + // Check for changes + for (const res of results) { + if (res.added.length || res.updated.length || res.unchanged.length || res.removed.length) { + return true; + } + } + return false; + } + + /** + * Flushes all tree registries to apply batched updates while tracking changes + * + * Commits all pending tree modifications and records detailed information about + * which resources were added, updated, or removed. Used when differential updates + * are enabled to support incremental cache invalidation. + * + * @returns {Promise} True if any changes were detected, false otherwise + */ + async #flushTreeChangesWithDiffTracking() { + const requestSetIds = this.#requestGraph.getAllNodeIds(); + const previousTreeSignatures = new Map(); + // Record current signatures and create mapping between trees and request sets + requestSetIds.map((requestSetId) => { + const {resourceIndex} = this.#requestGraph.getMetadata(requestSetId); + if (!resourceIndex) { + throw new Error(`Resource index missing for request set ID ${requestSetId}`); + } + // Remember the original signature + previousTreeSignatures.set(resourceIndex.getTree(), [requestSetId, resourceIndex.getSignature()]); + }); + const results = await this.#flushTreeChanges(); + let hasChanges = false; + for (const res of results) { + if (res.added.length || res.updated.length || res.unchanged.length || res.removed.length) { + hasChanges = true; + } + for (const [tree, diff] of res.treeStats) { + const [requestSetId, originalSignature] = previousTreeSignatures.get(tree); + const newSignature = tree.getRootHash(); + this.#addDeltaEntry(requestSetId, originalSignature, newSignature, diff); + } + } + return hasChanges; + } + + /** + * Flushes all tree registries to apply batched updates + * + * Commits all pending tree modifications across all registries in parallel. + * Must be called after operations that schedule updates via registries. + * + * @returns {Promise>} Array of flush results from all registries, + * each containing added, updated, unchanged, and removed resource paths + */ + async #flushTreeChanges() { + return await Promise.all(this.#treeRegistries.map((registry) => registry.flush())); + } + + /** + * Adds or updates a delta entry for tracking resource index changes + * + * Records the transition from an original signature to a new signature along with + * the specific resources that changed. Accumulates changes across multiple updates. + * + * @param {string} requestSetId Identifier of the request set + * @param {string} originalSignature Original resource index signature + * @param {string} newSignature New resource index signature + * @param {object} diff Object containing arrays of added, updated, unchanged, and removed resource paths + */ + #addDeltaEntry(requestSetId, originalSignature, newSignature, diff) { + if (!this.#treeUpdateDeltas.has(requestSetId)) { + this.#treeUpdateDeltas.set(requestSetId, { + originalSignature, + newSignature, + diff + }); + return; + } + const entry = this.#treeUpdateDeltas.get(requestSetId); + + entry.previousSignatures ??= []; + entry.previousSignatures.push(entry.originalSignature); + entry.originalSignature = originalSignature; + entry.newSignature = newSignature; + + const {added, updated, unchanged, removed} = entry.diff; + for (const resourcePath of diff.added) { + if (!added.includes(resourcePath)) { + added.push(resourcePath); + } + } + for (const resourcePath of diff.updated) { + if (!updated.includes(resourcePath)) { + updated.push(resourcePath); + } + } + for (const resourcePath of diff.unchanged) { + if (!unchanged.includes(resourcePath)) { + unchanged.push(resourcePath); + } + } + for (const resourcePath of diff.removed) { + if (!removed.includes(resourcePath)) { + removed.push(resourcePath); + } + } + } + + /** + * Gets all delta entries for differential cache updates + * + * Returns a map of signature transitions and their associated changed resource paths. + * Only includes deltas where no resources were removed, as removed resources prevent + * differential updates. + * + * @public + * @returns {Map} Map from original signature to delta information + * containing newSignature and changedPaths array + */ + getDeltas() { + const deltas = new Map(); + for (const {originalSignature, newSignature, diff} of this.#treeUpdateDeltas.values()) { + let changedPaths; + if (diff) { + const {added, updated, removed} = diff; + if (removed.length) { + // Cannot use differential build if a resource has been removed + continue; + } + changedPaths = Array.from(new Set([...added, ...updated])); + } else { + changedPaths = []; + } + deltas.set(originalSignature, { + newSignature, + changedPaths, + }); + } + return deltas; + } + + /** + * Adds a new set of resource requests and returns their signature + * + * Processes recorded resource requests (both path and pattern-based), creates or reuses + * a request set in the graph, and returns the resulting resource index signature. + * + * @public + * @param {object} requestRecording Project resource requests + * @param {string[]} requestRecording.paths Array of requested resource paths + * @param {Array} requestRecording.patterns Array of glob pattern arrays + * @param {module:@ui5/fs.AbstractReader} reader Reader for accessing project resources + * @returns {Promise} Object containing setId and signature of the resource index + */ + async addRequests(requestRecording, reader) { + const projectRequests = []; + for (const pathRead of requestRecording.paths) { + projectRequests.push(new Request("path", pathRead)); + } + for (const patterns of requestRecording.patterns) { + projectRequests.push(new Request("patterns", patterns)); + } + return await this.#addRequestSet(projectRequests, reader); + } + + /** + * Records that a task made no resource requests + * + * Marks the manager as having been unused at least once and returns a special + * signature indicating no requests were made. + * + * @public + * @returns {string} Special signature "X" indicating no requests + */ + recordNoRequests() { + if (!this.#unusedAtLeastOnce) { + this.#hasNewOrModifiedCacheEntries = true; + } + this.#unusedAtLeastOnce = true; + return "X"; // Signature for when no requests were made + } + + /** + * Adds a request set and creates or reuses a resource index + * + * Attempts to find an existing matching request set to reuse. If not found, creates + * a new request set with either a derived or fresh resource index based on whether + * a parent request set exists. + * + * @param {Request[]} requests Array of resource requests + * @param {module:@ui5/fs.AbstractReader} reader Reader for accessing project resources + * @returns {Promise} Object containing setId and signature of the resource index + */ + async #addRequestSet(requests, reader) { + this.#hasNewOrModifiedCacheEntries = true; + // Try to find an existing request set that we can reuse + let setId = this.#requestGraph.findExactMatch(requests); + let resourceIndex; + if (setId) { + // Reuse existing resource index. + // Note: This index has already been updated before the task executed, so no update is necessary here + resourceIndex = this.#requestGraph.getMetadata(setId).resourceIndex; + } else { + // New request set, check whether we can create a delta + const metadata = {}; // Will populate with resourceIndex below + setId = this.#requestGraph.addRequestSet(requests, metadata); + + const requestSet = this.#requestGraph.getNode(setId); + const parentId = requestSet.getParentId(); + if (parentId) { + const {resourceIndex: parentResourceIndex} = this.#requestGraph.getMetadata(parentId); + // Add resources from delta to index + const addedRequests = requestSet.getAddedRequests(); + const resourcesToAdd = + await this.#getResourcesForRequests(addedRequests, reader); + if (!resourcesToAdd.length) { + throw new Error(`Unexpected empty added resources for request set ID ${setId} ` + + `of task '${this.#taskName}' of project '${this.#projectName}'`); + } + log.verbose(`Task '${this.#taskName}' of project '${this.#projectName}' ` + + `created derived resource index for request set ID ${setId} ` + + `based on parent ID ${parentId} with ${resourcesToAdd.length} additional resources`); + resourceIndex = await parentResourceIndex.deriveTree(resourcesToAdd); + } else { + const resourcesRead = + await this.#getResourcesForRequests(requests, reader); + resourceIndex = await ResourceIndex.createShared(resourcesRead, Date.now(), this.#newTreeRegistry()); + } + metadata.resourceIndex = resourceIndex; + } + return { + setId, + signature: resourceIndex.getSignature(), + }; + } + + /** + * Associates a request set from this manager with one from another manager + * + * @public + * @param {string} ourRequestSetId Request set ID from this manager + * @param {string} foreignRequestSetId Request set ID from another manager + * @todo Implementation pending + */ + addAffiliatedRequestSet(ourRequestSetId, foreignRequestSetId) { + // TODO + } + + /** + * Creates and registers a new tree registry + * + * Tree registries enable batched updates across multiple derived trees, + * improving performance when multiple indices share common subtrees. + * + * @returns {TreeRegistry} New tree registry instance + */ + #newTreeRegistry() { + const registry = new TreeRegistry(); + this.#treeRegistries.push(registry); + return registry; + } + + /** + * Retrieves resources for a set of resource requests + * + * Processes different request types: + * - 'path': Retrieves single resource by path from the given reader + * - 'patterns': Retrieves resources matching glob patterns from the given reader + * + * @param {Request[]|Array<{type: string, value: string|string[]}>} resourceRequests - Resource requests to process + * @param {module:@ui5/fs.AbstractReader} reader - Resource reader + * @param {Map} [resourceCache] + * @returns {Promise>} Array of matched resources + */ + async #getResourcesForRequests(resourceRequests, reader, resourceCache) { + const resourcesMap = new Map(); + await Promise.all(resourceRequests.map(async ({type, value}) => { + if (type === "path") { + if (resourcesMap.has(value)) { + // Resource already found + return; + } + if (resourceCache?.has(value)) { + const cachedResource = resourceCache.get(value); + resourcesMap.set(cachedResource.getOriginalPath(), cachedResource); + } + const resource = await reader.byPath(value); + if (resource) { + resourcesMap.set(resource.getOriginalPath(), resource); + } + } else if (type === "patterns") { + const matchedResources = await reader.byGlob(value); + for (const resource of matchedResources) { + resourcesMap.set(resource.getOriginalPath(), resource); + } + } + })); + return Array.from(resourcesMap.values()); + } + + /** + * Checks whether new or modified cache entries exist + * + * Returns false if the manager was restored from cache and no modifications were made. + * Returns true if this is a new manager or if new request sets have been added. + * + * @public + * @returns {boolean} True if cache entries need to be written + */ + hasNewOrModifiedCacheEntries() { + return this.#hasNewOrModifiedCacheEntries; + } + + /** + * Serializes the manager to a plain object for persistence + * + * Exports the resource request graph and all resource indices in a format suitable + * for JSON serialization. The serialized data can be passed to fromCache() to restore + * the manager state. Returns undefined if no new or modified cache entries exist. + * + * @public + * @returns {object|undefined} Serialized cache metadata or undefined if no changes + * @returns {object} return.requestSetGraph Serialized request graph + * @returns {Array} return.rootIndices Array of root resource indices with node IDs + * @returns {Array} return.deltaIndices Array of delta resource indices with node IDs + * @returns {boolean} return.unusedAtLeastOnce Whether the task has been unused + */ + toCacheObject() { + if (!this.#hasNewOrModifiedCacheEntries) { + return; + } + const rootIndices = []; + const deltaIndices = []; + for (const {nodeId, parentId} of this.#requestGraph.traverseByDepth()) { + const {resourceIndex} = this.#requestGraph.getMetadata(nodeId); + if (!resourceIndex) { + throw new Error(`Missing resource index for node ID ${nodeId}`); + } + if (!parentId) { + rootIndices.push({ + nodeId, + resourceIndex: resourceIndex.toCacheObject(), + }); + } else { + const {resourceIndex: rootResourceIndex} = this.#requestGraph.getMetadata(parentId); + if (!rootResourceIndex) { + throw new Error(`Missing root resource index for parent ID ${parentId}`); + } + // Store the metadata for all added resources. Note: Those resources might not be available + // in the current tree. In that case we store an empty array. + const addedResourceIndex = resourceIndex.getAddedResourceIndex(rootResourceIndex); + deltaIndices.push({ + nodeId, + addedResourceIndex, + }); + } + } + return { + requestSetGraph: this.#requestGraph.toCacheObject(), + rootIndices, + deltaIndices, + unusedAtLeastOnce: this.#unusedAtLeastOnce, + }; + } +} + +export default ResourceRequestManager; diff --git a/packages/project/lib/build/cache/StageCache.js b/packages/project/lib/build/cache/StageCache.js new file mode 100644 index 00000000000..50699044b86 --- /dev/null +++ b/packages/project/lib/build/cache/StageCache.js @@ -0,0 +1,111 @@ +/** + * @typedef {object} StageCacheEntry + * @property {object} stage The cached stage instance (typically a reader or writer) + * @property {string[]} writtenResourcePaths Array of resource paths written during stage execution + * @property {Map>} resourceTagOperations + * Map of resource paths to their tags that were set or cleared during this stage's execution + */ + +/** + * In-memory cache for build stage results + * + * Manages cached build stages by their signatures, allowing quick lookup and reuse + * of previously executed build stages. Each stage is identified by a stage ID + * (e.g., "task/taskName") and a signature (content hash of input resources). + * + * The cache maintains a queue of added signatures that need to be persisted, + * enabling batch writes to persistent storage. + * + * Key features: + * - Fast in-memory lookup by stage ID and signature + * - Tracks written resources for cache invalidation + * - Supports batch persistence via flush queue + * - Multiple signatures per stage ID (for different input combinations) + * + * @class + */ +export default class StageCache { + #stageIdToSignatures = new Map(); + #cacheQueue = []; + + /** + * Adds a stage signature to the cache + * + * Stores the stage instance and its written resources under the given stage ID + * and signature. The signature is added to the flush queue for later persistence. + * + * Multiple signatures can exist for the same stage ID, representing different + * input resource combinations that produce different outputs. + * + * @public + * @param {string} stageId Identifier for the stage (e.g., "task/generateBundle") + * @param {string} signature Content hash signature of the stage's input resources + * @param {object} stageInstance The stage instance to cache (typically a reader or writer) + * @param {string[]} writtenResourcePaths Array of resource paths written during this stage + * @param {Map>} projectTagOperations + * @param {Map>} buildTagOperations + * Map of resource paths to their tags that were set or cleared during this stage's execution + */ + addSignature(stageId, signature, stageInstance, writtenResourcePaths, projectTagOperations, buildTagOperations) { + if (!this.#stageIdToSignatures.has(stageId)) { + this.#stageIdToSignatures.set(stageId, new Map()); + } + const signatureToStageInstance = this.#stageIdToSignatures.get(stageId); + signatureToStageInstance.set(signature, { + signature, + stage: stageInstance, + writtenResourcePaths, + projectTagOperations, + buildTagOperations, + }); + this.#cacheQueue.push([stageId, signature]); + } + + /** + * Retrieves cached stage data for a specific signature + * + * Looks up a previously cached stage by its ID and signature. Returns null + * if either the stage ID or signature is not found in the cache. + * + * @public + * @param {string} stageId Identifier for the stage to look up + * @param {string} signature Signature hash to match + * @returns {StageCacheEntry|null} Cached stage entry with stage instance and written paths, + * or null if not found + */ + getCacheForSignature(stageId, signature) { + if (!this.#stageIdToSignatures.has(stageId)) { + return null; + } + const signatureToStageInstance = this.#stageIdToSignatures.get(stageId); + return signatureToStageInstance.get(signature) || null; + } + + /** + * Retrieves and clears the cache queue + * + * Returns all stage signatures that have been added since the last flush, + * then resets the queue. The returned entries should be persisted to storage. + * + * Each queue entry is a tuple of [stageId, signature] that can be used to + * retrieve the full stage data via getCacheForSignature(). + * + * @public + * @returns {Array<[string, string]>} Array of [stageId, signature] tuples to persist + */ + flushCacheQueue() { + const queue = this.#cacheQueue; + this.#cacheQueue = []; + return queue; + } + + /** + * Checks if there are pending entries in the cache queue + * + * @public + * @returns {boolean} True if there are entries to flush, false otherwise + */ + hasPendingCacheQueue() { + return this.#cacheQueue.length > 0; + } +} diff --git a/packages/project/lib/build/cache/index/HashTree.js b/packages/project/lib/build/cache/index/HashTree.js new file mode 100644 index 00000000000..dea4105c04a --- /dev/null +++ b/packages/project/lib/build/cache/index/HashTree.js @@ -0,0 +1,714 @@ +import crypto from "node:crypto"; +import path from "node:path/posix"; +import TreeNode from "./TreeNode.js"; +import {matchResourceMetadataStrict} from "../utils.js"; + +/** + * @typedef {object} @ui5/project/build/cache/index/HashTree~ResourceMetadata + * @property {string} path Resource path using POSIX separators, prefixed with a slash (e.g. "/resources/file.js") + * @property {number} size File size in bytes + * @property {number} lastModified Last modification timestamp + * @property {number|undefined} inode File inode identifier + * @property {string} integrity Content hash + */ + +/** + * Directory-based Merkle Tree for efficient resource tracking with hierarchical structure. + * + * Computes deterministic SHA256 hashes for resources and directories, enabling: + * - Fast change detection via root hash comparison + * - Structural sharing through derived trees (memory efficient) + * - Batch upsert and removal operations + * + * Primary use case: Build caching systems where multiple related resource trees + * (e.g., source files, build artifacts) need to be tracked and synchronized efficiently. + */ +export default class HashTree { + #indexTimestamp; + /** + * Create a new HashTree + * + * @param {Array|null} resources + * Initial resources to populate the tree. Each resource should have a path and optional metadata. + * @param {object} options + * @param {number} [options.indexTimestamp] Timestamp of the latest resource metadata update + * @param {TreeNode} [options._root] Internal: pre-existing root node for derived trees (enables structural sharing) + */ + constructor(resources = null, options = {}) { + this.root = options._root || new TreeNode("", "directory"); + this.#indexTimestamp = options.indexTimestamp; + + if (resources && !options._root) { + this._buildTree(resources); + } else if (resources && options._root) { + // Derived tree: insert additional resources into shared structure + for (const resource of resources) { + this._insertResourceWithSharing(resource.path, resource); + } + // Recompute hashes for newly added paths + this._computeHash(this.root); + } + } + + /** + * Shallow copy a directory node (copies node, shares children) + * + * @param {TreeNode} dirNode + * @returns {TreeNode} + * @private + */ + _shallowCopyDirectory(dirNode) { + if (dirNode.type !== "directory") { + throw new Error("Can only shallow copy directory nodes"); + } + + const copy = new TreeNode(dirNode.name, "directory", { + hash: dirNode.hash ? Buffer.from(dirNode.hash) : null, + children: new Map(dirNode.children) // Shallow copy of Map (shares TreeNode references) + }); + + return copy; + } + + /** + * Build tree from resource list + * + * @param {Array<{path: string, integrity?: string}>} resources + * @private + */ + _buildTree(resources) { + // Sort resources by path for deterministic ordering + const sortedResources = [...resources].sort((a, b) => a.path.localeCompare(b.path)); + + // Insert each resource into the tree + for (const resource of sortedResources) { + this._insertResource(resource.path, resource); + } + + // Compute all hashes bottom-up + this._computeHash(this.root); + } + + /** + * Insert a resource with structural sharing for derived trees + * Implements copy-on-write: only copies directories that will be modified + * + * Key optimization: When adding "a/b/c/file.js", only copies: + * - Directory "c" (will get new child) + * Directories "a" and "b" remain shared references if they existed. + * + * This preserves memory efficiency when derived trees have different + * resources in some paths but share others. + * + * @param {string} resourcePath + * @param {object} resourceData + * @private + */ + _insertResourceWithSharing(resourcePath, resourceData) { + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + let current = this.root; + + // Navigate and copy-on-write for all directories in the path + for (let i = 0; i < parts.length - 1; i++) { + const dirName = parts[i]; + + if (!current.children.has(dirName)) { + // Create new directory from here + const newDir = new TreeNode(dirName, "directory"); + current.children.set(dirName, newDir); + current = newDir; + } else { + // Directory exists - need to copy it because we'll modify its children + const existing = current.children.get(dirName); + if (existing.type !== "directory") { + throw new Error(`Path conflict: ${dirName} exists as resource but expected directory`); + } + + // Shallow copy to preserve copy-on-write semantics + const copiedDir = this._shallowCopyDirectory(existing); + current.children.set(dirName, copiedDir); + current = copiedDir; + } + } + + // Insert the resource + const resourceName = parts[parts.length - 1]; + + // if (current.children.has(resourceName)) { + // throw new Error(`Duplicate resource path: ${resourcePath}`); + // } + + const resourceNode = new TreeNode(resourceName, "resource", { + integrity: resourceData.integrity, + lastModified: resourceData.lastModified, + size: resourceData.size, + inode: resourceData.inode + }); + + current.children.set(resourceName, resourceNode); + } + + /** + * Insert a resource into the directory tree + * + * @param {string} resourcePath + * @param {object} resourceData + * @param {string} [resourceData.integrity] - Content hash for regular resources + * @param {number} [resourceData.lastModified] - Last modified timestamp + * @param {number} [resourceData.size] - File size in bytes + * @param {number} [resourceData.inode] - File system inode number + * @private + */ + _insertResource(resourcePath, resourceData) { + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + let current = this.root; + + // Navigate/create directory structure + for (let i = 0; i < parts.length - 1; i++) { + const dirName = parts[i]; + + if (!current.children.has(dirName)) { + current.children.set(dirName, new TreeNode(dirName, "directory")); + } + + current = current.children.get(dirName); + + if (current.type !== "directory") { + throw new Error(`Path conflict: ${dirName} exists as resource but expected directory`); + } + } + + // Insert the resource + const resourceName = parts[parts.length - 1]; + + if (current.children.has(resourceName)) { + throw new Error(`Duplicate resource path: ${resourcePath}`); + } + + const resourceNode = new TreeNode(resourceName, "resource", { + integrity: resourceData.integrity, + lastModified: resourceData.lastModified, + size: resourceData.size, + inode: resourceData.inode + }); + + current.children.set(resourceName, resourceNode); + } + + /** + * Compute hash for a node and all its children (recursive) + * + * @param {TreeNode} node + * @returns {Buffer} + * @private + */ + _computeHash(node) { + if (node.type === "resource") { + // Resource hash + node.hash = this._hashData(`resource:${node.name}:${node.integrity}`); + } else { + // Directory hash - compute from sorted children + const childHashes = []; + + // Sort children by name for deterministic ordering + const sortedChildren = Array.from(node.children.entries()) + .sort((a, b) => a[0].localeCompare(b[0])); + + for (const [, child] of sortedChildren) { + this._computeHash(child); // Recursively compute child hashes + childHashes.push(child.hash); + } + + // Combine all child hashes + if (childHashes.length === 0) { + // Empty directory + node.hash = this._hashData(`dir:${node.name}:empty`); + } else { + const combined = Buffer.concat(childHashes); + node.hash = crypto.createHash("sha256") + .update(`dir:${node.name}:`) + .update(combined) + .digest(); + } + } + + return node.hash; + } + + /** + * Hash a string + * + * @param {string} data + * @returns {Buffer} + * @private + */ + _hashData(data) { + return crypto.createHash("sha256").update(data).digest(); + } + + /** + * Get the root hash as a hex string + * + * @returns {string} + */ + getRootHash() { + if (!this.root.hash) { + this._computeHash(this.root); + } + return this.root.hash.toString("hex"); + } + + /** + * Get the index timestamp + * + * @returns {number} + */ + getIndexTimestamp() { + return this.#indexTimestamp; + } + + setIndexTimestamp(timestamp) { + if (timestamp) { + this.#indexTimestamp = timestamp; + } + } + + /** + * Find a node by path + * + * @param {string} resourcePath + * @returns {TreeNode|null} + * @private + */ + _findNode(resourcePath) { + if (!resourcePath || resourcePath === "" || resourcePath === ".") { + return this.root; + } + + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + let current = this.root; + + for (const part of parts) { + if (!current.children.has(part)) { + return null; + } + current = current.children.get(part); + } + + return current; + } + + /** + * Upsert multiple resources (insert if new, update if exists). + * + * Intelligently determines whether each resource is new (insert) or existing (update). + * Applies operations immediately with optimized hash recomputation. + * + * Automatically creates missing parent directories during insertion. + * Skips resources whose metadata hasn't changed (optimization). + * + * @param {Array<@ui5/fs/Resource>} resources - Array of Resource instances to upsert + * @param {number} newIndexTimestamp Timestamp at which the provided resources have been indexed + * @returns {Promise<{added: Array, updated: Array, unchanged: Array}>} + * Status report: arrays of paths by operation type. + */ + async upsertResources(resources, newIndexTimestamp) { + if (!resources || resources.length === 0) { + return {added: [], updated: [], unchanged: []}; + } + + // Immediate mode + const added = []; + const updated = []; + const unchanged = []; + const affectedPaths = new Set(); + + for (const resource of resources) { + const resourcePath = resource.getOriginalPath(); + const existingNode = this.getResourceByPath(resourcePath); + + if (!existingNode) { + // Insert new resource + const resourceData = { + integrity: await resource.getIntegrity(), + lastModified: resource.getLastModified(), + size: await resource.getSize(), + inode: resource.getInode() + }; + this._insertResource(resourcePath, resourceData); + + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + const resourceNode = this._findNode(resourcePath); + this._computeHash(resourceNode); + + added.push(resourcePath); + + // Mark ancestors for recomputation + for (let i = 0; i < parts.length; i++) { + affectedPaths.add(parts.slice(0, i).join(path.sep)); + } + } else { + // Check if unchanged + const currentMetadata = { + integrity: existingNode.integrity, + lastModified: existingNode.lastModified, + size: existingNode.size, + inode: existingNode.inode + }; + + const isUnchanged = await matchResourceMetadataStrict(resource, currentMetadata, this.#indexTimestamp); + if (isUnchanged) { + unchanged.push(resourcePath); + continue; + } + + // Update existing resource + existingNode.integrity = await resource.getIntegrity(); + existingNode.lastModified = resource.getLastModified(); + existingNode.size = await resource.getSize(); + existingNode.inode = resource.getInode(); + + this._computeHash(existingNode); + updated.push(resourcePath); + + // Mark ancestors for recomputation + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + for (let i = 0; i < parts.length; i++) { + affectedPaths.add(parts.slice(0, i).join(path.sep)); + } + } + } + + // Recompute directory hashes bottom-up + const sortedPaths = Array.from(affectedPaths).sort((a, b) => { + const depthA = a ? a.split(path.sep).length : 0; + const depthB = b ? b.split(path.sep).length : 0; + if (depthA !== depthB) return depthB - depthA; + return a.localeCompare(b); + }); + + for (const dirPath of sortedPaths) { + const node = this._findNode(dirPath); + if (node && node.type === "directory") { + this._computeHash(node); + } + } + this.setIndexTimestamp(newIndexTimestamp); + return {added, updated, unchanged}; + } + + /** + * Remove multiple resources efficiently. + * + * Removes resources immediately and recomputes affected ancestor hashes. + * + * @param {Array} resourcePaths - Array of resource paths to remove + * @returns {Promise<{removed: Array, notFound: Array}>} + * Status report: 'removed' contains successfully removed paths, 'notFound' contains paths that didn't exist. + */ + async removeResources(resourcePaths) { + if (!resourcePaths || resourcePaths.length === 0) { + return {removed: [], notFound: []}; + } + + // Immediate mode + const removed = []; + const notFound = []; + const affectedPaths = new Set(); + + for (const resourcePath of resourcePaths) { + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + + if (parts.length === 0) { + throw new Error("Cannot remove root"); + } + + // Navigate to parent, keeping track of the path + const pathNodes = [this.root]; + let current = this.root; + let pathExists = true; + + for (let i = 0; i < parts.length - 1; i++) { + if (!current.children.has(parts[i])) { + pathExists = false; + break; + } + current = current.children.get(parts[i]); + pathNodes.push(current); + } + + if (!pathExists) { + notFound.push(resourcePath); + continue; + } + + // Remove resource + const resourceName = parts[parts.length - 1]; + const wasRemoved = current.children.delete(resourceName); + + if (wasRemoved) { + removed.push(resourcePath); + + // Clean up empty parent directories bottom-up + for (let i = parts.length - 1; i > 0; i--) { + const parentNode = pathNodes[i]; + if (parentNode.children.size === 0) { + // Directory is empty, remove it from its parent + const grandparentNode = pathNodes[i - 1]; + grandparentNode.children.delete(parts[i - 1]); + } else { + // Directory still has children, stop cleanup + break; + } + } + + // Mark ancestors for recomputation (only up to where directories still exist) + for (let i = 0; i < parts.length; i++) { + const ancestorPath = parts.slice(0, i).join(path.sep); + if (this._findNode(ancestorPath)) { + affectedPaths.add(ancestorPath); + } + } + } else { + notFound.push(resourcePath); + } + } + + // Recompute directory hashes bottom-up + const sortedPaths = Array.from(affectedPaths).sort((a, b) => { + const depthA = a ? a.split(path.sep).length : 0; + const depthB = b ? b.split(path.sep).length : 0; + if (depthA !== depthB) return depthB - depthA; + return a.localeCompare(b); + }); + + for (const dirPath of sortedPaths) { + const node = this._findNode(dirPath); + if (node && node.type === "directory") { + this._computeHash(node); + } + } + + return {removed, notFound}; + } + + /** + * Recompute hashes for all ancestor directories up to root. + * + * Used after modifications to ensure the entire path from the modified + * resource/directory up to the root has correct hash values. + * + * @param {string} resourcePath - Path to resource or directory that was modified + * @private + */ + _recomputeAncestorHashes(resourcePath) { + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + + // Recompute from deepest to root + for (let i = parts.length; i >= 0; i--) { + const dirPath = parts.slice(0, i).join(path.sep); + const node = this._findNode(dirPath); + if (node && node.type === "directory") { + this._computeHash(node); + } + } + } + + /** + * Get hash for a specific directory. + * + * Useful for checking if a specific subtree has changed without comparing the entire tree. + * + * @param {string} dirPath - Path to directory + * @returns {string} Directory hash as hex string + * @throws {Error} If path not found or path is not a directory + */ + getDirectoryHash(dirPath) { + const node = this._findNode(dirPath); + if (!node) { + throw new Error(`Path not found: ${dirPath}`); + } + if (node.type !== "directory") { + throw new Error(`Path is not a directory: ${dirPath}`); + } + return node.hash.toString("hex"); + } + + /** + * Check if a directory's contents have changed by comparing hashes. + * + * Efficient way to detect changes in a subtree without comparing individual files. + * + * @param {string} dirPath - Path to directory + * @param {string} previousHash - Previous hash to compare against + * @returns {boolean} true if directory contents changed, false otherwise + */ + hasDirectoryChanged(dirPath, previousHash) { + const currentHash = this.getDirectoryHash(dirPath); + return currentHash !== previousHash; + } + + /** + * Get tree statistics. + * + * Provides summary information about tree size and structure. + * + * @returns {{resources: number, directories: number, maxDepth: number, rootHash: string}} + * Statistics object with counts and root hash + */ + getStats() { + let resourceCount = 0; + let dirCount = 0; + let maxDepth = 0; + + const traverse = (node, depth) => { + maxDepth = Math.max(maxDepth, depth); + + if (node.type === "resource") { + resourceCount++; + } else { + dirCount++; + for (const child of node.children.values()) { + traverse(child, depth + 1); + } + } + }; + + traverse(this.root, 0); + + return { + resources: resourceCount, + directories: dirCount, + maxDepth, + rootHash: this.getRootHash() + }; + } + + /** + * Serialize tree to JSON + * + * @returns {object} + */ + toCacheObject() { + return { + version: 1, + root: this.root.toJSON(), + }; + } + + /** + * Deserialize tree from JSON + * + * @param {object} data + * @param {object} [options] + * @returns {HashTree} + */ + static fromCache(data, options = {}) { + if (data.version !== 1) { + throw new Error(`Unsupported version: ${data.version}`); + } + + const tree = new HashTree(null, options); + tree.root = TreeNode.fromJSON(data.root); + + return tree; + } + + /** + * Validate tree structure and hashes + * + * Currently unused, but possibly useful future integrity checks. + * + * @returns {boolean} + */ + validate() { + const errors = []; + + const validateNode = (node, currentPath) => { + // Recompute hash + const originalHash = node.hash; + this._computeHash(node); + + if (!originalHash.equals(node.hash)) { + errors.push(`Hash mismatch at ${currentPath || "root"}`); + } + + // Restore original (in case validation is non-destructive) + node.hash = originalHash; + + // Recurse for directories + if (node.type === "directory") { + for (const [name, child] of node.children) { + const childPath = currentPath ? path.join(currentPath, name) : name; + validateNode(child, childPath); + } + } + }; + + validateNode(this.root, ""); + + if (errors.length > 0) { + throw new Error(`Validation failed:\n${errors.join("\n")}`); + } + + return true; + } + + /** + * Create a deep clone of this tree. + * + * Unlike deriveTree(), this creates a completely independent copy + * with no shared node references. + * + * @returns {HashTree} New independent tree instance + */ + clone() { + const cloned = new HashTree(); + cloned.root = this.root.clone(); + return cloned; + } + + /** + * Get resource node by path + * + * @param {string} resourcePath + * @returns {TreeNode|null} + */ + getResourceByPath(resourcePath) { + const node = this._findNode(resourcePath); + return node && node.type === "resource" ? node : null; + } + + /** + * Check if a path exists in the tree + * + * @param {string} resourcePath + * @returns {boolean} + */ + hasPath(resourcePath) { + return this._findNode(resourcePath) !== null; + } + + /** + * Get all resource paths in sorted order + * + * @returns {Array} + */ + getResourcePaths() { + const paths = []; + + const traverse = (node, currentPath) => { + if (node.type === "resource") { + paths.push(currentPath); + } else { + for (const [name, child] of node.children) { + const childPath = currentPath ? path.join(currentPath, name) : name; + traverse(child, childPath); + } + } + }; + + traverse(this.root, "/"); + return paths.sort(); + } +} diff --git a/packages/project/lib/build/cache/index/ResourceIndex.js b/packages/project/lib/build/cache/index/ResourceIndex.js new file mode 100644 index 00000000000..e345922da6b --- /dev/null +++ b/packages/project/lib/build/cache/index/ResourceIndex.js @@ -0,0 +1,300 @@ +/** + * @module @ui5/project/build/cache/index/ResourceIndex + * @description Manages an indexed view of build resources with hash-based tracking. + * + * ResourceIndex provides efficient resource tracking through hash tree structures, + * enabling fast delta detection and signature calculation for build caching. + */ +import HashTree from "./HashTree.js"; +import SharedHashTree from "./SharedHashTree.js"; +import {createResourceIndex} from "../utils.js"; + +/** + * Manages an indexed view of build resources with content-based hashing. + * + * ResourceIndex wraps a HashTree to provide resource indexing capabilities for build caching. + * It maintains resource metadata (path, integrity, size, modification time) and computes + * signatures for change detection. The index supports efficient updates and can be + * persisted/restored from cache. + * + * @example + * // Create from resources + * const index = await ResourceIndex.create(resources, registry); + * const signature = index.getSignature(); + * + * @example + * // Update with delta detection + * const {changedPaths, resourceIndex} = await ResourceIndex.fromCacheWithDelta( + * cachedIndex, + * currentResources + * ); + */ +export default class ResourceIndex { + #tree; + + /** + * Creates a new ResourceIndex instance. + * + * @param {HashTree} tree - The hash tree containing resource metadata + * @private + */ + constructor(tree) { + this.#tree = tree; + } + + /** + * Creates a new ResourceIndex from a set of resources. + * + * Builds a hash tree from the provided resources, computing content hashes + * and metadata for each resource. The resulting index can be used for + * signature calculation and change tracking. + * + * @param {Array<@ui5/fs/Resource>} resources - Resources to index + * @param {number} indexTimestamp Timestamp at which the provided resources have been indexed + * @returns {Promise} A new resource index + * @public + */ + static async create(resources, indexTimestamp) { + const resourceIndex = await createResourceIndex(resources); + const tree = new HashTree(resourceIndex, {indexTimestamp}); + return new ResourceIndex(tree); + } + + /** + * Creates a new shared ResourceIndex from a set of resources. + * + * Creates a SharedHashTree that coordinates updates through a TreeRegistry. + * Use this for scenarios where multiple indices need to share nodes and + * coordinate batch updates. + * + * @param {Array<@ui5/fs/Resource>} resources - Resources to index + * @param {number} indexTimestamp Timestamp at which the provided resources have been indexed + * @param {TreeRegistry} registry - Registry for coordinated batch updates + * @returns {Promise} A new resource index with shared tree + * @public + */ + static async createShared(resources, indexTimestamp, registry) { + const resourceIndex = await createResourceIndex(resources); + const tree = new SharedHashTree(resourceIndex, registry, {indexTimestamp}); + return new ResourceIndex(tree); + } + + /** + * Restores a ResourceIndex from cache and applies delta updates. + * + * Takes a cached index and a current set of resources, then: + * 1. Identifies removed resources (in cache but not in current set) + * 2. Identifies added/updated resources (new or modified since cache) + * 3. Returns both the updated index and list of all changed paths + * + * This method is optimized for incremental builds where most resources + * remain unchanged between builds. + * + * @param {object} indexCache - Cached index object from previous build + * @param {number} indexCache.indexTimestamp - Timestamp of cached index + * @param {object} indexCache.indexTree - Cached hash tree structure + * @param {Array<@ui5/fs/Resource>} resources - Current resources to compare against cache + * @param {number} newIndexTimestamp Timestamp at which the provided resources have been indexed + * @returns {Promise<{changedPaths: string[], resourceIndex: ResourceIndex}>} + * Object containing array of all changed resource paths and the updated index + * @public + */ + static async fromCacheWithDelta(indexCache, resources, newIndexTimestamp) { + const {indexTimestamp, indexTree} = indexCache; + const tree = HashTree.fromCache(indexTree, {indexTimestamp}); + const currentResourcePaths = new Set(resources.map((resource) => resource.getOriginalPath())); + const removedPaths = tree.getResourcePaths().filter((resourcePath) => { + return !currentResourcePaths.has(resourcePath); + }); + const {removed} = await tree.removeResources(removedPaths); + const {added, updated} = await tree.upsertResources(resources, newIndexTimestamp); + return { + changedPaths: [...added, ...updated, ...removed], + resourceIndex: new ResourceIndex(tree), + }; + } + + /** + * Restores a shared ResourceIndex from cache and applies delta updates. + * + * Same as fromCacheWithDelta, but creates a SharedHashTree that coordinates + * updates through a TreeRegistry. + * + * @param {object} indexCache - Cached index object from previous build + * @param {number} indexCache.indexTimestamp - Timestamp of cached index + * @param {object} indexCache.indexTree - Cached hash tree structure + * @param {Array<@ui5/fs/Resource>} resources - Current resources to compare against cache + * @param {number} newIndexTimestamp Timestamp at which the provided resources have been indexed + * @param {TreeRegistry} registry - Registry for coordinated batch updates + * @returns {Promise<{changedPaths: string[], resourceIndex: ResourceIndex}>} + * Object containing array of all changed resource paths and the updated index + * @public + */ + static async fromCacheWithDeltaShared(indexCache, resources, newIndexTimestamp, registry) { + const {indexTimestamp, indexTree} = indexCache; + const tree = SharedHashTree.fromCache(indexTree, registry, {indexTimestamp}); + const currentResourcePaths = new Set(resources.map((resource) => resource.getOriginalPath())); + const removedPaths = tree.getResourcePaths().filter((resourcePath) => { + return !currentResourcePaths.has(resourcePath); + }); + await tree.removeResources(removedPaths); + await tree.upsertResources(resources, newIndexTimestamp); + // For shared trees, we need to flush the registry to get results + const {added, updated, removed} = await registry.flush(); + return { + changedPaths: [...added, ...updated, ...removed], + resourceIndex: new ResourceIndex(tree), + }; + } + + /** + * Restores a ResourceIndex from cached metadata. + * + * Reconstructs the resource index from cached metadata without performing + * content hash verification. Useful when the cache is known to be valid + * and fast restoration is needed. + * + * @param {object} indexCache - Cached index object + * @param {number} indexCache.indexTimestamp - Timestamp of cached index + * @param {object} indexCache.indexTree - Cached hash tree structure + * @returns {ResourceIndex} Restored resource index + * @public + */ + static fromCache(indexCache) { + const {indexTimestamp, indexTree} = indexCache; + const tree = HashTree.fromCache(indexTree, {indexTimestamp}); + return new ResourceIndex(tree); + } + + /** + * Restores a shared ResourceIndex from cached metadata. + * + * Same as fromCache, but creates a SharedHashTree that coordinates + * updates through a TreeRegistry. + * + * @param {object} indexCache - Cached index object + * @param {number} indexCache.indexTimestamp - Timestamp of cached index + * @param {object} indexCache.indexTree - Cached hash tree structure + * @param {TreeRegistry} registry - Registry for coordinated batch updates + * @returns {ResourceIndex} Restored resource index with shared tree + * @public + */ + static fromCacheShared(indexCache, registry) { + const {indexTimestamp, indexTree} = indexCache; + const tree = SharedHashTree.fromCache(indexTree, registry, {indexTimestamp}); + return new ResourceIndex(tree); + } + + getTree() { + return this.#tree; + } + + /** + * Creates a deep copy of this ResourceIndex. + * + * The cloned index has its own hash tree but shares the same timestamp + * as the original. Useful for creating independent index variations. + * + * @returns {ResourceIndex} A cloned resource index + * @public + */ + clone() { + const cloned = new ResourceIndex(this.#tree.clone()); + return cloned; + } + + /** + * Creates a derived ResourceIndex by adding additional resources. + * + * Derives a new hash tree from the current tree by incorporating + * additional resources. The original index remains unchanged. + * This is useful for creating task-specific resource views. + * + * @param {Array<@ui5/fs/Resource>} additionalResources - Resources to add to the derived index + * @returns {Promise} A new resource index with the additional resources + * @public + */ + async deriveTree(additionalResources) { + const resourceIndex = await createResourceIndex(additionalResources); + return new ResourceIndex(this.#tree.deriveTree(resourceIndex)); + } + + deriveTreeWithIndex(resourceIndex) { + return new ResourceIndex(this.#tree.deriveTree(resourceIndex)); + } + + /** + * Compares this index against a base index and returns metadata + * for resources that have been added in this index. + * + * @param {ResourceIndex} baseIndex - The base resource index to compare against + * @returns {Array<@ui5/project/build/cache/index/HashTree~ResourceMetadata>} + */ + getAddedResourceIndex(baseIndex) { + return this.#tree.getAddedResources(baseIndex.getTree()); + } + + /** + * Inserts or updates resources in the index. + * + * For each resource: + * - If it exists in the index and has changed, it's updated + * - If it doesn't exist in the index, it's added + * - If it exists and hasn't changed, no action is taken + * + * @param {Array<@ui5/fs/Resource>} resources - Resources to upsert + * @param {number} newIndexTimestamp Timestamp at which the provided resources have been indexed + * @returns {Promise<{added: string[], updated: string[]}>} + * Object with arrays of added and updated resource paths + * @public + */ + async upsertResources(resources, newIndexTimestamp) { + return await this.#tree.upsertResources(resources, newIndexTimestamp); + } + + /** + * Removes resources from the index. + * + * @param {Array} resourcePaths - Paths of resources to remove + */ + async removeResources(resourcePaths) { + return await this.#tree.removeResources(resourcePaths); + } + + getResourcePaths() { + return this.#tree.getResourcePaths(); + } + + /** + * Computes the signature hash for this resource index. + * + * The signature is the root hash of the underlying hash tree, + * representing the combined state of all indexed resources. + * Any change to any resource will result in a different signature. + * + * @returns {string} SHA-256 hash signature of the resource index + * @public + */ + getSignature() { + return this.#tree.getRootHash(); + } + + /** + * Serializes the ResourceIndex to a cache object. + * + * Converts the index to a plain object suitable for JSON serialization + * and storage in the build cache. The cached object can be restored + * using fromCache() or fromCacheWithDelta(). + * + * @returns {object} Cache object containing timestamp and tree structure + * @returns {number} return.indexTimestamp - Timestamp when index was created + * @returns {object} return.indexTree - Serialized hash tree structure + * @public + */ + toCacheObject() { + return { + indexTimestamp: this.#tree.getIndexTimestamp(), + indexTree: this.#tree.toCacheObject(), + }; + } +} diff --git a/packages/project/lib/build/cache/index/SharedHashTree.js b/packages/project/lib/build/cache/index/SharedHashTree.js new file mode 100644 index 00000000000..e4c18514089 --- /dev/null +++ b/packages/project/lib/build/cache/index/SharedHashTree.js @@ -0,0 +1,204 @@ +import path from "node:path/posix"; +import HashTree from "./HashTree.js"; +import TreeNode from "./TreeNode.js"; + +/** + * Shared HashTree that coordinates updates through a TreeRegistry. + * + * This variant of HashTree is designed for scenarios where multiple trees need + * to share nodes and coordinate batch updates. All modifications (upserts and removals) + * are delegated to the registry, which applies them atomically across all registered trees. + * + * Key differences from base HashTree: + * - Requires a TreeRegistry instance + * - upsertResources() and removeResources() return undefined (results available via registry.flush()) + * - Derived trees share the same registry + * - Changes to shared nodes propagate to all trees + * + * @extends HashTree + */ +export default class SharedHashTree extends HashTree { + /** + * Create a new SharedHashTree + * + * @param {Array|null} resources + * Initial resources to populate the tree. Each resource should have a path and optional metadata. + * @param {TreeRegistry} registry Required registry for coordinated batch updates across multiple trees + * @param {object} options + * @param {number} [options.indexTimestamp] Timestamp of the latest resource metadata update + * @param {TreeNode} [options._root] Internal: pre-existing root node for derived trees (enables structural sharing) + */ + constructor(resources = null, registry, options = {}) { + if (!registry) { + throw new Error("SharedHashTree requires a registry option"); + } + + super(resources, options); + + this.registry = registry; + this.registry.register(this); + } + + /** + * Schedule resource upserts (insert or update) to be applied during registry flush. + * + * Unlike base HashTree, this method doesn't immediately modify the tree. + * Instead, it schedules operations with the registry for batch processing. + * Call registry.flush() to apply all pending operations atomically. + * + * @param {Array<@ui5/fs/Resource>} resources - Array of Resource instances to upsert + * @param {number} newIndexTimestamp Timestamp at which the provided resources have been indexed + * @returns {Promise} Returns undefined; results available via registry.flush() + */ + async upsertResources(resources, newIndexTimestamp) { + if (!resources || resources.length === 0) { + return; + } + + for (const resource of resources) { + this.registry.scheduleUpsert(resource, newIndexTimestamp, this); + } + } + + /** + * Schedule resource removals to be applied during registry flush. + * + * Unlike base HashTree, this method doesn't immediately modify the tree. + * Instead, it schedules operations with the registry for batch processing. + * Call registry.flush() to apply all pending operations atomically. + * + * @param {Array} resourcePaths - Array of resource paths to remove + * @returns {Promise} Returns undefined; results available via registry.flush() + */ + async removeResources(resourcePaths) { + if (!resourcePaths || resourcePaths.length === 0) { + return; + } + + for (const resourcePath of resourcePaths) { + this.registry.scheduleRemoval(resourcePath); + } + } + + /** + * Create a derived shared tree that shares subtrees with this tree. + * + * The derived tree shares the same registry and will participate in + * coordinated batch updates. Changes to shared nodes propagate to all trees. + * + * @param {Array} additionalResources + * Resources to add to the derived tree (in addition to shared resources from parent) + * @returns {SharedHashTree} New shared tree sharing subtrees and registry with this tree + */ + deriveTree(additionalResources = []) { + // Shallow copy root to allow adding new top-level directories + const derivedRoot = this._shallowCopyDirectory(this.root); + + // Create derived tree with shared root and same registry + const derived = new SharedHashTree(additionalResources, this.registry, { + _root: derivedRoot + }); + + // Register the derived tree with parent tree reference + if (this.registry) { + this.registry.register(derived, this); + } + + return derived; + } + + /** + * For a tree derived from a base tree, get the list of resource nodes + * that were added compared to the base tree. + * + * @param {HashTree} rootTree - The base tree to compare against + * @returns {Array} + * Array of added resource metadata + */ + getAddedResources(rootTree) { + const added = []; + + const traverse = (node, currentPath, implicitlyAdded = false) => { + if (implicitlyAdded) { + // We're in a subtree that's entirely new - add all resources + if (node.type === "resource") { + added.push({ + path: currentPath, + integrity: node.integrity, + size: node.size, + lastModified: node.lastModified, + inode: node.inode + }); + } + } else { + const baseNode = rootTree._findNode(currentPath); + if (baseNode && baseNode === node) { + // Node exists in base tree and is the same object (structural sharing) + // Neither node nor children are added + return; + } else if (baseNode && node.type === "directory") { + // Directory exists in both trees but may have been shallow-copied + // Check children individually - only process children that differ + for (const [name, child] of node.children) { + const childPath = currentPath ? path.join(currentPath, name) : name; + const baseChild = baseNode.children.get(name); + + if (!baseChild || baseChild !== child) { + // Child doesn't exist in base or is different - determine if added + if (!baseChild) { + // Entirely new - all descendants are added + traverse(child, childPath, true); + } else { + // Child was modified/replaced - recurse normally + traverse(child, childPath, false); + } + } + // If baseChild === child, skip it (shared) + } + return; // Don't continue with normal traversal + } else if (!baseNode && node.type === "resource") { + // Resource doesn't exist in base tree - it's added + added.push({ + path: currentPath, + integrity: node.integrity, + size: node.size, + lastModified: node.lastModified, + inode: node.inode + }); + return; + } else if (!baseNode && node.type === "directory") { + // Directory doesn't exist in base tree - all children are added + implicitlyAdded = true; + } + } + + if (node.type === "directory") { + for (const [name, child] of node.children) { + const childPath = currentPath ? path.join(currentPath, name) : name; + traverse(child, childPath, implicitlyAdded); + } + } + }; + traverse(this.root, "/"); + return added; + } + + /** + * Deserialize tree from JSON + * + * @param {object} data + * @param {TreeRegistry} registry Required registry for coordinated batch updates across multiple trees + * @param {object} [options] + * @returns {HashTree} + */ + static fromCache(data, registry, options = {}) { + if (data.version !== 1) { + throw new Error(`Unsupported version: ${data.version}`); + } + + const tree = new SharedHashTree(null, registry, options); + tree.root = TreeNode.fromJSON(data.root); + + return tree; + } +} diff --git a/packages/project/lib/build/cache/index/TreeNode.js b/packages/project/lib/build/cache/index/TreeNode.js new file mode 100644 index 00000000000..130e9ef9152 --- /dev/null +++ b/packages/project/lib/build/cache/index/TreeNode.js @@ -0,0 +1,107 @@ +import path from "node:path/posix"; + +/** + * Represents a node in the directory-based Merkle tree + */ +export default class TreeNode { + constructor(name, type, options = {}) { + this.name = name; // resource name or directory name + this.type = type; // 'resource' | 'directory' + this.hash = options.hash || null; // Buffer + + // Resource node properties + this.integrity = options.integrity; // Resource content hash + this.lastModified = options.lastModified; // Last modified timestamp + this.size = options.size; // File size in bytes + this.inode = options.inode; // File system inode number + + // Directory node properties + this.children = options.children || new Map(); // name -> TreeNode + } + + /** + * Get full path from root to this node + * + * @param {string} parentPath + * @returns {string} + */ + getPath(parentPath = "") { + return parentPath ? path.join(parentPath, this.name) : this.name; + } + + /** + * Serialize to JSON + * + * @returns {object} + */ + toJSON() { + const obj = { + name: this.name, + type: this.type, + hash: this.hash ? this.hash.toString("hex") : null + }; + + if (this.type === "resource") { + obj.integrity = this.integrity; + obj.lastModified = this.lastModified; + obj.size = this.size; + obj.inode = this.inode; + } else { + obj.children = {}; + for (const [name, child] of this.children) { + obj.children[name] = child.toJSON(); + } + } + + return obj; + } + + /** + * Deserialize from JSON + * + * @param {object} data + * @returns {TreeNode} + */ + static fromJSON(data) { + const options = { + hash: data.hash ? Buffer.from(data.hash, "hex") : null, + integrity: data.integrity, + lastModified: data.lastModified, + size: data.size, + inode: data.inode + }; + + if (data.type === "directory" && data.children) { + options.children = new Map(); + for (const [name, childData] of Object.entries(data.children)) { + options.children.set(name, TreeNode.fromJSON(childData)); + } + } + + return new TreeNode(data.name, data.type, options); + } + + /** + * Create a deep copy of this node + * + * @returns {TreeNode} + */ + clone() { + const options = { + hash: this.hash ? Buffer.from(this.hash) : null, + integrity: this.integrity, + lastModified: this.lastModified, + size: this.size, + inode: this.inode + }; + + if (this.type === "directory") { + options.children = new Map(); + for (const [name, child] of this.children) { + options.children.set(name, child.clone()); + } + } + + return new TreeNode(this.name, this.type, options); + } +} diff --git a/packages/project/lib/build/cache/index/TreeRegistry.js b/packages/project/lib/build/cache/index/TreeRegistry.js new file mode 100644 index 00000000000..2781d731c86 --- /dev/null +++ b/packages/project/lib/build/cache/index/TreeRegistry.js @@ -0,0 +1,528 @@ +import path from "node:path/posix"; +import TreeNode from "./TreeNode.js"; +import {matchResourceMetadataStrict} from "../utils.js"; + +/** + * Registry for coordinating batch updates across multiple Merkle trees that share nodes by reference. + * + * When multiple trees (e.g., derived trees) share directory and resource nodes through structural sharing, + * direct mutations would be visible to all trees simultaneously. The TreeRegistry provides a transaction-like + * mechanism to batch and coordinate updates: + * + * 1. Changes are scheduled via scheduleUpsert() and scheduleRemoval() without immediately modifying trees + * 2. During flush(), all pending operations are applied atomically across all registered trees + * 3. Shared nodes are modified only once, with changes propagating to all trees that reference them + * 4. Directory hashes are recomputed efficiently in a single bottom-up pass + * + * This approach ensures consistency when multiple trees represent filtered views of the same underlying data. + * + * @property {Set} trees - All registered HashTree/SharedHashTree instances + * @property {Map} + * pendingUpserts - Resource path to resource and source tree mappings for scheduled upserts + * @property {Set} pendingRemovals - Resource paths scheduled for removal + * @property {Map>} derivedTrees + * Maps parent trees to their directly derived children + */ +export default class TreeRegistry { + trees = new Set(); + pendingUpserts = new Map(); + pendingRemovals = new Set(); + pendingTimestampUpdate; + derivedTrees = new Map(); // parent -> Set of derived trees + + /** + * Register a HashTree or SharedHashTree instance with this registry for coordinated updates. + * + * Once registered, the tree will participate in all batch operations triggered by flush(). + * Multiple trees can share the same underlying nodes through structural sharing. + * + * @param {import('./SharedHashTree.js').default} tree - HashTree or SharedHashTree instance to register + * @param {import('./SharedHashTree.js').default} [parentTree] - Parent tree if this is a derived tree + */ + register(tree, parentTree = null) { + this.trees.add(tree); + + if (parentTree) { + if (!this.derivedTrees.has(parentTree)) { + this.derivedTrees.set(parentTree, new Set()); + } + this.derivedTrees.get(parentTree).add(tree); + } + } + + /** + * Remove a HashTree or SharedHashTree instance from this registry. + * + * After unregistering, the tree will no longer participate in batch operations. + * Any pending operations scheduled before unregistration will still be applied during flush(). + * + * @param {import('./SharedHashTree.js').default} tree - HashTree or SharedHashTree instance to unregister + */ + unregister(tree) { + this.trees.delete(tree); + + // Remove from derivedTrees mappings + this.derivedTrees.delete(tree); + for (const [, derivedSet] of this.derivedTrees) { + derivedSet.delete(tree); + } + } + + /** + * Get all trees derived from a given tree (recursively). + * + * @param {import('./SharedHashTree.js').default} tree - The parent tree + * @returns {Set} Set of all derived trees (direct and transitive) + */ + _getDerivedTrees(tree) { + const result = new Set(); + const directDerived = this.derivedTrees.get(tree); + + if (directDerived) { + for (const derived of directDerived) { + result.add(derived); + // Recursively get trees derived from derived + for (const transitive of this._getDerivedTrees(derived)) { + result.add(transitive); + } + } + } + + return result; + } + + /** + * Check if targetTree is the same as or derived from sourceTree. + * + * @param {import('./SharedHashTree.js').default} sourceTree - The source/parent tree + * @param {import('./SharedHashTree.js').default} targetTree - The tree to check + * @returns {boolean} True if targetTree is sourceTree or derived from it + */ + _isTreeOrDerived(sourceTree, targetTree) { + if (sourceTree === targetTree) { + return true; + } + return this._getDerivedTrees(sourceTree).has(targetTree); + } + + /** + * Schedule a resource upsert (insert or update) to be applied during flush(). + * + * If a resource with the same path doesn't exist, it will be inserted (including creating + * any necessary parent directories). If it exists, its metadata will be updated if changed. + * Scheduling an upsert cancels any pending removal for the same resource path. + * + * When sourceTree is specified, new resources will only be inserted into that tree and + * any trees derived from it. Updates to existing resources will still propagate to all + * trees that share the resource node. + * + * @param {@ui5/fs/Resource} resource - Resource instance to upsert + * @param {number} [newIndexTimestamp] - Timestamp at which the provided resources have been indexed + * @param {import('./SharedHashTree.js').default} [sourceTree] - Tree that initiated this upsert + * (for controlling insert propagation) + */ + scheduleUpsert(resource, newIndexTimestamp, sourceTree = null) { + const resourcePath = resource.getOriginalPath(); + this.pendingUpserts.set(resourcePath, {resource, sourceTree}); + // Cancel any pending removal for this path + this.pendingRemovals.delete(resourcePath); + if (newIndexTimestamp) { + this.pendingTimestampUpdate = newIndexTimestamp; + } + } + + /** + * Schedule a resource removal to be applied during flush(). + * + * The resource will be removed from all registered trees that contain it. + * Scheduling a removal cancels any pending upsert for the same resource path. + * Removals are processed before upserts during flush() to handle replacement scenarios. + * + * @param {string} resourcePath - POSIX-style path to the resource (e.g., "src/main.js") + */ + scheduleRemoval(resourcePath) { + this.pendingRemovals.add(resourcePath); + // Cancel any pending upsert for this path + this.pendingUpserts.delete(resourcePath); + } + + /** + * Apply all pending upserts and removals atomically across all registered trees. + * + * This method processes scheduled operations in three phases: + * + * Phase 1: Process removals + * - Delete resource nodes from all trees that contain them + * - Mark affected ancestor directories for hash recomputation + * + * Phase 2: Process upserts (inserts and updates) + * - Group operations by parent directory for efficiency + * - For inserts: only create in source tree and its derived trees + * - For updates: apply to all trees that share the resource node + * - Skip updates for resources with unchanged metadata + * - Track modified nodes to avoid duplicate updates to shared nodes + * + * Phase 3: Recompute directory hashes + * - Sort affected directories by depth (deepest first) + * - Recompute hashes bottom-up to root + * - Each shared node is updated once, visible to all trees + * + * After successful completion, all pending operations are cleared. + * + * @returns {Promise<{added: string[], updated: string[], unchanged: string[], removed: string[], + * treeStats: Map}>} + * Object containing arrays of resource paths categorized by operation result, + * plus per-tree statistics showing which resource paths were added/updated/unchanged/removed in each tree + */ + async flush() { + if (this.pendingUpserts.size === 0 && this.pendingRemovals.size === 0) { + return { + added: [], + updated: [], + unchanged: [], + removed: [], + treeStats: new Map() + }; + } + + // Track added, updated, unchanged, and removed resources + const addedResources = []; + const updatedResources = []; + const unchangedResources = []; + const removedResources = []; + + // Track per-tree statistics + const treeStats = new Map(); + for (const tree of this.trees) { + treeStats.set(tree, {added: [], updated: [], unchanged: [], removed: []}); + } + + // Track which resource nodes we've already modified to handle shared nodes + const modifiedNodes = new Set(); + + // Track all affected trees and the paths that need recomputation + const affectedTrees = new Map(); // tree -> Set of directory paths needing recomputation + + // 1. Handle removals first + for (const resourcePath of this.pendingRemovals) { + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + const resourceName = parts[parts.length - 1]; + const parentPath = parts.slice(0, -1).join(path.sep); + + // Track which trees have this resource before deletion (for shared nodes) + const treesWithResource = []; + for (const tree of this.trees) { + const parentNode = tree._findNode(parentPath); + if (parentNode && parentNode.type === "directory" && parentNode.children.has(resourceName)) { + treesWithResource.push({tree, parentNode, pathNodes: this._getPathNodes(tree, parts)}); + } + } + + // Perform deletion once and track for all trees that had it + if (treesWithResource.length > 0) { + const {parentNode} = treesWithResource[0]; + parentNode.children.delete(resourceName); + + // Clean up empty parent directories in all affected trees + for (const {tree, pathNodes} of treesWithResource) { + // Clean up empty parent directories bottom-up + for (let i = parts.length - 1; i > 0; i--) { + const currentDirNode = pathNodes[i]; + if (currentDirNode && currentDirNode.children.size === 0) { + // Directory is empty, remove it from its parent + const parentDirNode = pathNodes[i - 1]; + if (parentDirNode) { + parentDirNode.children.delete(parts[i - 1]); + } + } else { + // Directory still has children, stop cleanup for this tree + break; + } + } + + if (!affectedTrees.has(tree)) { + affectedTrees.set(tree, new Set()); + } + + // Mark ancestors for recomputation (only up to where directories still exist) + for (let i = 0; i < parts.length; i++) { + const ancestorPath = parts.slice(0, i).join(path.sep); + if (tree._findNode(ancestorPath)) { + affectedTrees.get(tree).add(ancestorPath); + } + } + + // Track per-tree removal + treeStats.get(tree).removed.push(resourcePath); + } + + if (!removedResources.includes(resourcePath)) { + removedResources.push(resourcePath); + } + } + } + + // 2. Handle upserts - group by directory + const upsertsByDir = new Map(); // parentPath -> [{resourceName, resource, fullPath, sourceTree}] + + for (const [resourcePath, {resource, sourceTree}] of this.pendingUpserts) { + const parts = resourcePath.split(path.sep).filter((p) => p.length > 0); + const resourceName = parts[parts.length - 1]; + const parentPath = parts.slice(0, -1).join(path.sep); + + if (!upsertsByDir.has(parentPath)) { + upsertsByDir.set(parentPath, []); + } + upsertsByDir.get(parentPath).push({resourceName, resource, fullPath: resourcePath, sourceTree}); + } + + // Apply upserts + for (const [parentPath, upserts] of upsertsByDir) { + for (const tree of this.trees) { + // Check if parent directory exists in this tree + let parentNode = tree._findNode(parentPath); + + let dirModified = false; + for (const upsert of upserts) { + let resourceNode = parentNode?.children?.get(upsert.resourceName); + + if (!resourceNode) { + // INSERT: Check derivation rules + if (upsert.sourceTree !== null) { + // Source tree specified - only insert into source tree and its derived trees + if (!this._isTreeOrDerived(upsert.sourceTree, tree)) { + // This tree is not the source tree or derived from it - skip insert + continue; + } + } + // If sourceTree is null, insert into all trees (backward compatibility) + + // Ensure parent directory exists (only for trees we're inserting into) + if (!parentNode) { + parentNode = this._ensureDirectoryPath( + tree, parentPath.split(path.sep).filter((p) => p.length > 0)); + } + + if (parentNode.type !== "directory") { + continue; + } + + // Create new resource node + resourceNode = new TreeNode(upsert.resourceName, "resource", { + integrity: await upsert.resource.getIntegrity(), + lastModified: upsert.resource.getLastModified(), + size: await upsert.resource.getSize(), + inode: upsert.resource.getInode() + }); + parentNode.children.set(upsert.resourceName, resourceNode); + modifiedNodes.add(resourceNode); + dirModified = true; + + // Track per-tree addition + treeStats.get(tree).added.push(upsert.fullPath); + + if (!addedResources.includes(upsert.fullPath)) { + addedResources.push(upsert.fullPath); + } + } else if (resourceNode.type === "resource") { + // UPDATE: Check if modified + if (!modifiedNodes.has(resourceNode)) { + const currentMetadata = { + integrity: resourceNode.integrity, + lastModified: resourceNode.lastModified, + size: resourceNode.size, + inode: resourceNode.inode + }; + + const isUnchanged = await matchResourceMetadataStrict( + upsert.resource, + currentMetadata, + tree.getIndexTimestamp() + ); + + if (!isUnchanged) { + resourceNode.integrity = await upsert.resource.getIntegrity(); + resourceNode.lastModified = upsert.resource.getLastModified(); + resourceNode.size = await upsert.resource.getSize(); + resourceNode.inode = upsert.resource.getInode(); + modifiedNodes.add(resourceNode); + dirModified = true; + + // Track per-tree update + treeStats.get(tree).updated.push(upsert.fullPath); + + if (!updatedResources.includes(upsert.fullPath)) { + updatedResources.push(upsert.fullPath); + } + } else { + // Track per-tree unchanged + treeStats.get(tree).unchanged.push(upsert.fullPath); + + if (!unchangedResources.includes(upsert.fullPath)) { + unchangedResources.push(upsert.fullPath); + } + } + } else { + // Node was already modified by another tree (shared node) + // Still count it as an update for this tree since the change affects it + treeStats.get(tree).updated.push(upsert.fullPath); + dirModified = true; + } + } + } + + if (dirModified && parentNode) { + // Compute hashes for modified/new resources + for (const upsert of upserts) { + const resourceNode = parentNode.children.get(upsert.resourceName); + if (resourceNode && resourceNode.type === "resource" && modifiedNodes.has(resourceNode)) { + tree._computeHash(resourceNode); + } + } + + if (!affectedTrees.has(tree)) { + affectedTrees.set(tree, new Set()); + } + + tree._computeHash(parentNode); + this._markAncestorsAffected( + tree, parentPath.split(path.sep).filter((p) => p.length > 0), affectedTrees); + } + } + } + + // Recompute ancestor hashes for all affected trees + for (const [tree, affectedPaths] of affectedTrees) { + // Sort paths by depth (deepest first) to recompute bottom-up + const sortedPaths = Array.from(affectedPaths).sort((a, b) => { + const depthA = a ? a.split(path.sep).length : 0; + const depthB = b ? b.split(path.sep).length : 0; + if (depthA !== depthB) return depthB - depthA; // deeper first + return a.localeCompare(b); + }); + + for (const dirPath of sortedPaths) { + const node = tree._findNode(dirPath); + if (node && node.type === "directory") { + tree._computeHash(node); + } + } + if (this.pendingTimestampUpdate) { + tree.setIndexTimestamp(this.pendingTimestampUpdate); + } + } + + // Clear all pending operations + this.pendingUpserts.clear(); + this.pendingRemovals.clear(); + this.pendingTimestampUpdate = null; + + return { + added: addedResources, + updated: updatedResources, + unchanged: unchangedResources, + removed: removedResources, + treeStats + }; + } + + /** + * Get all nodes along a path from root to the target. + * + * Returns an array of TreeNode objects representing the full path, + * starting with root at index 0 and ending with the target node. + * + * @param {import('./SharedHashTree.js').default} tree - Tree to traverse + * @param {string[]} pathParts - Path components to follow + * @returns {Array} Array of TreeNode objects along the path + */ + _getPathNodes(tree, pathParts) { + const nodes = [tree.root]; + let current = tree.root; + + for (let i = 0; i < pathParts.length - 1; i++) { + if (!current.children.has(pathParts[i])) { + break; + } + current = current.children.get(pathParts[i]); + nodes.push(current); + } + + return nodes; + } + + /** + * Mark all ancestor directories in a tree as requiring hash recomputation. + * + * When a resource or directory is modified, all ancestor directories up to the root + * need their hashes recomputed to reflect the change. This method tracks those paths + * in the affectedTrees map for later batch processing. + * + * @param {import('./SharedHashTree.js').default} tree - Tree containing the affected path + * @param {string[]} pathParts - Path components of the modified resource/directory + * @param {Map>} affectedTrees + * Map tracking affected paths per tree + */ + _markAncestorsAffected(tree, pathParts, affectedTrees) { + if (!affectedTrees.has(tree)) { + affectedTrees.set(tree, new Set()); + } + + for (let i = 0; i <= pathParts.length; i++) { + affectedTrees.get(tree).add(pathParts.slice(0, i).join(path.sep)); + } + } + + /** + * Ensure a directory path exists in a tree, creating missing directories as needed. + * + * This method walks down the path from root, creating any missing directory nodes. + * It's used during upsert operations to automatically create parent directories + * when inserting resources into paths that don't yet exist. + * + * @param {import('./SharedHashTree.js').default} tree - Tree to create directory path in + * @param {string[]} pathParts - Path components of the directory to ensure exists + * @returns {TreeNode} The directory node at the end of the path + */ + _ensureDirectoryPath(tree, pathParts) { + let current = tree.root; + + for (const part of pathParts) { + if (!current.children.has(part)) { + const dirNode = new TreeNode(part, "directory"); + current.children.set(part, dirNode); + } + current = current.children.get(part); + } + + return current; + } + + /** + * Get the number of HashTree instances currently registered with this registry. + * + * @returns {number} Count of registered trees + */ + getTreeCount() { + return this.trees.size; + } + + /** + * Get the total number of pending operations (upserts + removals) waiting to be applied. + * + * @returns {number} Count of pending upserts and removals combined + */ + getPendingUpdateCount() { + return this.pendingUpserts.size + this.pendingRemovals.size; + } + + /** + * Check if there are any pending operations waiting to be applied. + * + * @returns {boolean} True if there are pending upserts or removals, false otherwise + */ + hasPendingUpdates() { + return this.pendingUpserts.size > 0 || this.pendingRemovals.size > 0; + } +} diff --git a/packages/project/lib/build/cache/utils.js b/packages/project/lib/build/cache/utils.js new file mode 100644 index 00000000000..e6b93545d1c --- /dev/null +++ b/packages/project/lib/build/cache/utils.js @@ -0,0 +1,168 @@ +/** + * @typedef {object} ResourceMetadata + * @property {string} integrity Content integrity of the resource + * @property {number} lastModified Last modified timestamp (mtimeMs) + * @property {number} inode Inode number of the resource + * @property {number} size Size of the resource in bytes + */ + +/** + * Compares a resource instance with cached resource metadata + * + * Optimized for quickly rejecting changed files. Performs a series of checks + * starting with the cheapest (timestamp) to more expensive (integrity hash). + * + * @public + * @param {@ui5/fs/Resource} resource Resource instance to compare + * @param {ResourceMetadata} resourceMetadata Resource metadata to compare against + * @param {number} [indexTimestamp] Timestamp of the metadata creation + * @returns {Promise} True if resource is found to match the metadata + * @throws {Error} If resource or metadata is undefined + */ +export async function matchResourceMetadata(resource, resourceMetadata, indexTimestamp) { + if (!resource || !resourceMetadata) { + throw new Error("Cannot compare undefined resources or metadata"); + } + + const currentLastModified = resource.getLastModified(); + if (indexTimestamp && currentLastModified > indexTimestamp) { + // Resource modified after index was created, no need for further checks + return false; + } + if (currentLastModified !== resourceMetadata.lastModified) { + return false; + } + if (await resource.getSize() !== resourceMetadata.size) { + return false; + } + const incomingInode = resource.getInode(); + if (resourceMetadata.inode !== undefined && incomingInode !== undefined && + incomingInode !== resourceMetadata.inode) { + return false; + } + + if (currentLastModified === indexTimestamp) { + // If the source modification time is equal to index creation time, + // it's possible for a race condition to have occurred where the file was modified + // during index creation without changing its size. + // In this case, we need to perform an integrity check to determine if the file has changed. + if (await resource.getIntegrity() !== resourceMetadata.integrity) { + return false; + } + } + return true; +} + +/** + * Determines if a resource has changed compared to cached metadata + * + * Optimized for quickly accepting unchanged files. Resources are assumed to be + * usually unchanged (same lastModified timestamp). Performs checks from cheapest + * to most expensive, falling back to integrity comparison when necessary. + * + * @public + * @param {@ui5/fs/Resource} resource Resource instance to compare + * @param {ResourceMetadata} cachedMetadata Cached metadata from the tree + * @param {number} [indexTimestamp] Timestamp when the tree state was created + * @returns {Promise} True if resource content is unchanged + * @throws {Error} If resource or metadata is undefined + */ +export async function matchResourceMetadataStrict(resource, cachedMetadata, indexTimestamp) { + if (!resource || !cachedMetadata) { + throw new Error("Cannot compare undefined resources or metadata"); + } + + // Check 1: Inode mismatch would indicate file replacement (comparison only if inodes are provided) + // const currentInode = resource.getInode(); + // if (cachedMetadata.inode !== undefined && currentInode !== undefined && + // currentInode !== cachedMetadata.inode) { + // return false; + // } + + // Check 2: Modification time unchanged would suggest no update needed + const currentLastModified = resource.getLastModified(); + if (currentLastModified === cachedMetadata.lastModified) { + if (indexTimestamp && currentLastModified !== indexTimestamp) { + // File has not been modified since last indexing. No update needed + return true; + } // else: Edge case. File modified exactly at index time + // Race condition possible - content may have changed during indexing + // Fall through to integrity check + } + + // Check 3: Size mismatch indicates definite content change + const currentSize = await resource.getSize(); + if (currentSize !== cachedMetadata.size) { + return false; + } + + // Check 4: Compare integrity (expensive) + // lastModified has changed, but the content might be the same. E.g. in case of a metadata-only update + const currentIntegrity = await resource.getIntegrity(); + return currentIntegrity === cachedMetadata.integrity; +} + + +/** + * Creates an index of resource metadata from an array of resources + * + * Processes all resources in parallel, extracting their metadata including + * path, integrity, lastModified timestamp, and size. Optionally includes inode information. + * + * @public + * @param {Array<@ui5/fs/Resource>} resources Array of resources to index + * @param {boolean} [includeInode=false] Whether to include inode information in the metadata + * @returns {Promise>} + * Array of resource metadata objects + */ +export async function createResourceIndex(resources, includeInode = false) { + return await Promise.all(resources.map(async (resource) => { + const resourceMetadata = { + path: resource.getOriginalPath(), + integrity: await resource.getIntegrity(), + lastModified: resource.getLastModified(), + size: await resource.getSize(), + }; + if (includeInode) { + resourceMetadata.inode = resource.getInode(); + } + return resourceMetadata; + })); +} + +/** + * Returns the first truthy value from an array of promises + * + * This function evaluates all promises in parallel and returns immediately + * when the first truthy value is found. If all promises resolve to falsy + * values, null is returned. + * + * @param {Promise[]} promises Array of promises to evaluate + * @returns {Promise<*>} The first truthy resolved value or null if all are falsy + */ +export async function firstTruthy(promises) { + return new Promise((resolve, reject) => { + let completed = 0; + const total = promises.length; + + if (total === 0) { + resolve(null); + return; + } + + promises.forEach((promise) => { + Promise.resolve(promise) + .then((value) => { + if (value) { + resolve(value); + } else { + completed++; + if (completed === total) { + resolve(null); + } + } + }) + .catch(reject); + }); + }); +} diff --git a/packages/project/lib/build/definitions/application.js b/packages/project/lib/build/definitions/application.js index c546ee9d6bf..9b502502836 100644 --- a/packages/project/lib/build/definitions/application.js +++ b/packages/project/lib/build/definitions/application.js @@ -20,6 +20,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceCopyright", { + supportsDifferentialBuilds: true, options: { copyright: project.getCopyright(), pattern: "/**/*.{js,json}" @@ -27,6 +28,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceVersion", { + supportsDifferentialBuilds: true, options: { version: project.getVersion(), pattern: "/**/*.{js,json}" @@ -42,6 +44,7 @@ export default function({project, taskUtil, getTask}) { } } tasks.set("minify", { + supportsDifferentialBuilds: true, options: { pattern: minificationPattern } diff --git a/packages/project/lib/build/definitions/component.js b/packages/project/lib/build/definitions/component.js index 48684b6df03..ac64531ee98 100644 --- a/packages/project/lib/build/definitions/component.js +++ b/packages/project/lib/build/definitions/component.js @@ -20,6 +20,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceCopyright", { + supportsDifferentialBuilds: true, options: { copyright: project.getCopyright(), pattern: "/**/*.{js,json}" @@ -27,6 +28,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceVersion", { + supportsDifferentialBuilds: true, options: { version: project.getVersion(), pattern: "/**/*.{js,json}" @@ -41,6 +43,7 @@ export default function({project, taskUtil, getTask}) { } tasks.set("minify", { + supportsDifferentialBuilds: true, options: { pattern: minificationPattern } diff --git a/packages/project/lib/build/definitions/library.js b/packages/project/lib/build/definitions/library.js index 9b92177d1cd..ab7a0cca58e 100644 --- a/packages/project/lib/build/definitions/library.js +++ b/packages/project/lib/build/definitions/library.js @@ -20,6 +20,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceCopyright", { + supportsDifferentialBuilds: true, options: { copyright: project.getCopyright(), pattern: "/**/*.{js,library,css,less,theme,html}" @@ -27,6 +28,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceVersion", { + supportsDifferentialBuilds: true, options: { version: project.getVersion(), pattern: "/**/*.{js,json,library,css,less,theme,html}" @@ -34,6 +36,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceBuildtime", { + supportsDifferentialBuilds: true, options: { pattern: "/resources/sap/ui/{Global,core/Core}.js" } @@ -82,6 +85,7 @@ export default function({project, taskUtil, getTask}) { } tasks.set("minify", { + supportsDifferentialBuilds: true, options: { pattern: minificationPattern } diff --git a/packages/project/lib/build/definitions/themeLibrary.js b/packages/project/lib/build/definitions/themeLibrary.js index 2acf0392768..00ef7424290 100644 --- a/packages/project/lib/build/definitions/themeLibrary.js +++ b/packages/project/lib/build/definitions/themeLibrary.js @@ -11,6 +11,7 @@ export default function({project, taskUtil, getTask}) { const tasks = new Map(); tasks.set("replaceCopyright", { + supportsDifferentialBuilds: true, options: { copyright: project.getCopyright(), pattern: "/resources/**/*.{less,theme}" @@ -18,6 +19,7 @@ export default function({project, taskUtil, getTask}) { }); tasks.set("replaceVersion", { + supportsDifferentialBuilds: true, options: { version: project.getVersion(), pattern: "/resources/**/*.{less,theme}" diff --git a/packages/project/lib/build/helpers/BuildContext.js b/packages/project/lib/build/helpers/BuildContext.js index 8d8d1e1a329..791e0cbaf70 100644 --- a/packages/project/lib/build/helpers/BuildContext.js +++ b/packages/project/lib/build/helpers/BuildContext.js @@ -1,5 +1,7 @@ import ProjectBuildContext from "./ProjectBuildContext.js"; import OutputStyleEnum from "./ProjectBuilderOutputStyle.js"; +import CacheManager from "../cache/CacheManager.js"; +import {getBaseSignature} from "./getBuildSignature.js"; /** * Context of a build process @@ -8,6 +10,8 @@ import OutputStyleEnum from "./ProjectBuilderOutputStyle.js"; * @memberof @ui5/project/build/helpers */ class BuildContext { + #cacheManager; + constructor(graph, taskRepository, { // buildConfig selfContained = false, cssVariables = false, @@ -68,13 +72,14 @@ class BuildContext { includedTasks, excludedTasks, }; + this._buildSignatureBase = getBaseSignature(this._buildConfig); this._taskRepository = taskRepository; this._options = { cssVariables: cssVariables }; - this._projectBuildContexts = []; + this._projectBuildContexts = new Map(); } getRootProject() { @@ -97,20 +102,91 @@ class BuildContext { return this._graph; } - createProjectContext({project}) { - const projectBuildContext = new ProjectBuildContext({ - buildContext: this, - project - }); - this._projectBuildContexts.push(projectBuildContext); + async getProjectContext(projectName) { + if (this._projectBuildContexts.has(projectName)) { + return this._projectBuildContexts.get(projectName); + } + const project = this._graph.getProject(projectName); + const projectBuildContext = await ProjectBuildContext.create( + this, project, await this.getCacheManager(), this._buildSignatureBase); + this._projectBuildContexts.set(projectName, projectBuildContext); return projectBuildContext; } + async getRequiredProjectContexts(requestedProjects) { + const projectBuildContexts = new Map(); + const requiredProjects = new Set(requestedProjects); + + for (const projectName of requiredProjects) { + const projectBuildContext = await this.getProjectContext(projectName); + + projectBuildContexts.set(projectName, projectBuildContext); + + // Collect all direct dependencies of the project that are required to build the project + const requiredDependencies = await projectBuildContext.getRequiredDependencies(); + + for (const depName of requiredDependencies) { + // Add dependency to list of required projects + requiredProjects.add(depName); + } + } + return projectBuildContexts; + } + + async getCacheManager() { + if (this.#cacheManager) { + return this.#cacheManager; + } + this.#cacheManager = await CacheManager.create(this._graph.getRoot().getRootPath()); + return this.#cacheManager; + } + + getBuildContext(projectName) { + return this._projectBuildContexts.get(projectName); + } + async executeCleanupTasks(force = false) { - await Promise.all(this._projectBuildContexts.map((ctx) => { + await Promise.all(Array.from(this._projectBuildContexts.values()).map((ctx) => { return ctx.executeCleanupTasks(force); })); } + + /** + * + * @param {Map>} resourceChanges Mapping project name to changed resource paths + * @returns {Set} Names of projects potentially affected by the resource changes + */ + propagateResourceChanges(resourceChanges) { + const affectedProjectNames = new Set(); + const dependencyChanges = new Map(); + for (const [projectName, changedResourcePaths] of resourceChanges) { + affectedProjectNames.add(projectName); + // Propagate changes to dependents of the project + for (const {project: dep} of this._graph.traverseDependents(projectName)) { + const depChanges = dependencyChanges.get(dep.getName()); + if (!depChanges) { + dependencyChanges.set(dep.getName(), new Set(changedResourcePaths)); + } else { + for (const res of changedResourcePaths) { + depChanges.add(res); + } + } + } + const projectBuildContext = this.getBuildContext(projectName); + if (projectBuildContext) { + projectBuildContext.projectSourcesChanged(Array.from(changedResourcePaths)); + } + } + + for (const [projectName, changedResourcePaths] of dependencyChanges) { + affectedProjectNames.add(projectName); + const projectBuildContext = this.getBuildContext(projectName); + if (projectBuildContext) { + projectBuildContext.dependencyResourcesChanged(Array.from(changedResourcePaths)); + } + } + return affectedProjectNames; + } } export default BuildContext; diff --git a/packages/project/lib/build/helpers/ProjectBuildContext.js b/packages/project/lib/build/helpers/ProjectBuildContext.js index 10eb2a67a83..0df6b74f854 100644 --- a/packages/project/lib/build/helpers/ProjectBuildContext.js +++ b/packages/project/lib/build/helpers/ProjectBuildContext.js @@ -1,17 +1,26 @@ -import ResourceTagCollection from "@ui5/fs/internal/ResourceTagCollection"; import ProjectBuildLogger from "@ui5/logger/internal/loggers/ProjectBuild"; import TaskUtil from "./TaskUtil.js"; import TaskRunner from "../TaskRunner.js"; +import {getProjectSignature} from "./getBuildSignature.js"; +import ProjectBuildCache from "../cache/ProjectBuildCache.js"; /** * Build context of a single project. Always part of an overall * [Build Context]{@link @ui5/project/build/helpers/BuildContext} - * - * @private + * @memberof @ui5/project/build/helpers */ class ProjectBuildContext { - constructor({buildContext, project}) { + /** + * Creates a new ProjectBuildContext instance + * + * @param {@ui5/project/build/helpers/BuildContext} buildContext Overall build context + * @param {@ui5/project/specifications/Project} project Project instance to build + * @param {string} buildSignature Signature of the build configuration + * @param {@ui5/project/build/cache/ProjectBuildCache} buildCache Build cache instance + * @throws {Error} If 'buildContext' or 'project' is missing + */ + constructor(buildContext, project, buildSignature, buildCache) { if (!buildContext) { throw new Error(`Missing parameter 'buildContext'`); } @@ -25,40 +34,91 @@ class ProjectBuildContext { projectName: project.getName(), projectType: project.getType() }); + this._buildSignature = buildSignature; + this._buildCache = buildCache; this._queues = { cleanup: [] }; + } - this._resourceTagCollection = new ResourceTagCollection({ - allowedTags: ["ui5:OmitFromBuildResult", "ui5:IsBundle"], - allowedNamespaces: ["build"] - }); + /** + * Factory method to create and initialize a ProjectBuildContext instance + * + * This is the recommended way to create a ProjectBuildContext as it ensures + * proper initialization of the build signature and cache. + * + * @param {@ui5/project/build/helpers/BuildContext} buildContext Overall build context + * @param {@ui5/project/specifications/Project} project Project instance to build + * @param {object} cacheManager Cache manager instance + * @param {string} baseSignature Base signature for the build + * @returns {Promise<@ui5/project/build/helpers/ProjectBuildContext>} Initialized context instance + */ + static async create(buildContext, project, cacheManager, baseSignature) { + const buildSignature = getProjectSignature( + baseSignature, project, buildContext.getGraph(), buildContext.getTaskRepository()); + const buildCache = await ProjectBuildCache.create( + project, buildSignature, cacheManager); + return new ProjectBuildContext( + buildContext, + project, + buildSignature, + buildCache + ); } + /** + * Checks whether this context is for the root project + * + * @returns {boolean} True if this is the root project context + */ isRootProject() { return this._project === this._buildContext.getRootProject(); } + /** + * Retrieves a build configuration option + * + * @param {string} key Option key to retrieve + * @returns {*} Option value + */ getOption(key) { return this._buildContext.getOption(key); } + /** + * Registers a cleanup task to be executed after the build + * + * Cleanup tasks are called after all regular tasks have completed, + * allowing resources to be freed or temporary data to be cleaned up. + * + * @param {Function} callback Cleanup callback function that accepts a force parameter + */ registerCleanupTask(callback) { this._queues.cleanup.push(callback); } + /** + * Executes all registered cleanup tasks + * + * Calls all cleanup callbacks in parallel and clears the cleanup queue. + * + * @param {boolean} force Whether to force cleanup even if conditions aren't met + * @returns {Promise} + */ async executeCleanupTasks(force) { await Promise.all(this._queues.cleanup.map((callback) => { return callback(force); })); + this._queues.cleanup = []; } /** - * Retrieve a single project from the dependency graph + * Retrieves a single project from the dependency graph * - * @param {string} [projectName] Name of the project to retrieve. Defaults to the project currently being built + * @param {string} [projectName] Name of the project to retrieve. + * Defaults to the project currently being built * @returns {@ui5/project/specifications/Project|undefined} - * project instance or undefined if the project is unknown to the graph + * Project instance or undefined if the project is unknown to the graph */ getProject(projectName) { if (projectName) { @@ -68,9 +128,10 @@ class ProjectBuildContext { } /** - * Retrieve a list of direct dependencies of a given project from the dependency graph + * Retrieves a list of direct dependencies of a given project from the dependency graph * - * @param {string} [projectName] Name of the project to retrieve. Defaults to the project currently being built + * @param {string} [projectName] Name of the project to retrieve. + * Defaults to the project currently being built * @returns {string[]} Names of all direct dependencies * @throws {Error} If the requested project is unknown to the graph */ @@ -78,24 +139,51 @@ class ProjectBuildContext { return this._buildContext.getGraph().getDependencies(projectName || this._project.getName()); } + /** + * Gets the list of required dependencies for the current project + * + * Determines which dependencies are actually needed based on the tasks that will be executed. + * Results are cached after the first call. + * + * @returns {Promise} Array of required dependency names + */ + async getRequiredDependencies() { + if (this._requiredDependencies) { + return this._requiredDependencies; + } + const taskRunner = this.getTaskRunner(); + this._requiredDependencies = await taskRunner.getRequiredDependencies(); + return this._requiredDependencies; + } + + /** + * Gets the appropriate resource tag collection for a resource and tag + * + * Determines which tag collection (project-specific or build-level) should be used + * for the given resource and tag combination. Associates the resource with the current + * project if not already associated. + * + * @param {@ui5/fs/Resource} resource Resource to get tag collection for + * @param {string} tag Tag to check acceptance for + * @returns {@ui5/fs/internal/ResourceTagCollection} Appropriate tag collection + * @throws {Error} If no collection accepts the given tag + */ getResourceTagCollection(resource, tag) { if (!resource.hasProject()) { this._log.silly(`Associating resource ${resource.getPath()} with project ${this._project.getName()}`); resource.setProject(this._project); - // throw new Error( - // `Unable to get tag collection for resource ${resource.getPath()}: ` + - // `Resource must be associated to a project`); - } - const projectCollection = resource.getProject().getResourceTagCollection(); - if (projectCollection.acceptsTag(tag)) { - return projectCollection; } - if (this._resourceTagCollection.acceptsTag(tag)) { - return this._resourceTagCollection; - } - throw new Error(`Could not find collection for resource ${resource.getPath()} and tag ${tag}`); + return resource.getProject().getResourceTagCollection(resource, tag); } + /** + * Gets the task utility instance for this build context + * + * Creates a TaskUtil instance on first access and caches it for subsequent calls. + * The TaskUtil provides helper functions for tasks during execution. + * + * @returns {@ui5/project/build/helpers/TaskUtil} Task utility instance + */ getTaskUtil() { if (!this._taskUtil) { this._taskUtil = new TaskUtil({ @@ -106,11 +194,20 @@ class ProjectBuildContext { return this._taskUtil; } + /** + * Gets the task runner instance for this build context + * + * Creates a TaskRunner instance on first access and caches it for subsequent calls. + * The TaskRunner is responsible for executing all build tasks for the project. + * + * @returns {@ui5/project/build/TaskRunner} Task runner instance + */ getTaskRunner() { if (!this._taskRunner) { this._taskRunner = new TaskRunner({ project: this._project, log: this._log, + buildCache: this._buildCache, taskUtil: this.getTaskUtil(), graph: this._buildContext.getGraph(), taskRepository: this._buildContext.getTaskRepository(), @@ -121,28 +218,170 @@ class ProjectBuildContext { } /** - * Determine whether the project has to be built or is already built - * (typically indicated by the presence of a build manifest) + * Early check whether a project build is possibly required. + * + * In some cases, the cache state cannot be determined until all dependencies have been processed and + * the cache has been updated with that information. This happens during prepareProjectBuildAndValidateCache(). + * + * This method allows for an early check whether a project build can be skipped. + * + * @returns {boolean} True if a build might required, false otherwise + */ + possiblyRequiresBuild() { + if (this.#getBuildManifest()) { + // Build manifest present -> No build required + return false; + } + // Without build manifest, check cache state + return !this.getBuildCache().isFresh(); + } + + /** + * Prepares the project build by updating and validating the build cache + * + * Creates a dependency reader and validates the cache state against current resources. + * Must be called before buildProject(). + * + * @returns {Promise} + * True if a valid cache was found and is being used. False otherwise (indicating a build is required). + */ + async prepareProjectBuildAndValidateCache() { + const depReader = await this.getTaskRunner().getDependenciesReader( + await this.getTaskRunner().getRequiredDependencies(), + true, // Force creation of new reader since project readers might have changed during their (re-)build + ); + const boolOrChangedPaths = await this.getBuildCache().prepareProjectBuildAndValidateCache(depReader); + if (Array.isArray(boolOrChangedPaths)) { + // Cache can be used, but some resources have changed + // Propagate changed paths to dependents + this.propagateResourceChanges(boolOrChangedPaths); + } + return !!boolOrChangedPaths; + } + + /** + * Builds the project by running all required tasks + * + * Executes all configured build tasks for the project using the task runner. + * Must be called after prepareProjectBuildAndValidateCache(). + * + * @param {AbortSignal} [signal] Abort signal + */ + async buildProject(signal) { + const changedPaths = await this.getTaskRunner().runTasks(signal); + // Propagate changed paths to dependents + this.propagateResourceChanges(changedPaths); + } + + buildFinished() { + this.getBuildCache().buildFinished(); + } + + /** + * Informs the build cache about changed project source resources + * + * Notifies the cache that source files have changed so it can invalidate + * affected cache entries and mark the cache as stale. + * + * @param {string[]} changedPaths Changed project source file paths + */ + projectSourcesChanged(changedPaths) { + return this._buildCache.projectSourcesChanged(changedPaths); + } + + /** + * Informs the build cache about changed dependency resources + * + * Notifies the cache that dependency resources have changed so it can invalidate + * affected cache entries and mark the cache as stale. + * + * @param {string[]} changedPaths Changed dependency resource paths + */ + dependencyResourcesChanged(changedPaths) { + return this._buildCache.dependencyResourcesChanged(changedPaths); + } + + propagateResourceChanges(changedPaths) { + if (!changedPaths.length) { + return; + } + for (const {project: dep} of this._buildContext.getGraph().traverseDependents(this._project.getName())) { + const projectBuildContext = this._buildContext.getBuildContext(dep.getName()); + if (projectBuildContext) { + projectBuildContext.dependencyResourcesChanged(changedPaths); + } + } + } + + /** + * Gets the build manifest if available and compatible + * + * Retrieves the project's build manifest and validates its version. + * Only manifest versions 0.1 and 0.2 are currently supported. * - * @returns {boolean} True if the project needs to be built + * @returns {object|undefined} Build manifest object or undefined if unavailable or incompatible */ - requiresBuild() { - return !this._project.getBuildManifest(); + #getBuildManifest() { + const manifest = this._project.getBuildManifest(); + if (!manifest) { + return; + } + // Check whether the manifest can be used for this build + if (manifest.manifestVersion === "0.1" || manifest.manifestVersion === "0.2") { + // Manifest version 0.1 and 0.2 are always used without further checks for legacy reasons + return manifest; + } + // Unknown manifest version can't be used + return; } + /** + * Gets metadata about the previous build from the build manifest + * + * Extracts timestamp and age information from the build manifest if available. + * + * @returns {object|null} Build metadata with timestamp and age, or null if no manifest exists + * @returns {string} return.timestamp ISO timestamp of the previous build + * @returns {string} return.age Human-readable age of the previous build + */ getBuildMetadata() { - const buildManifest = this._project.getBuildManifest(); + const buildManifest = this.#getBuildManifest(); if (!buildManifest) { return null; } const timeDiff = (new Date().getTime() - new Date(buildManifest.timestamp).getTime()); - // TODO: Format age properly via a new @ui5/logger util module + // TODO: Format age properly return { timestamp: buildManifest.timestamp, age: timeDiff / 1000 + " seconds" }; } + + /** + * Gets the project build cache instance + * + * @returns {@ui5/project/build/cache/ProjectBuildCache} Build cache instance + */ + getBuildCache() { + return this._buildCache; + } + + async writeBuildCache() { + await this._buildCache.writeCache(); + } + + /** + * Gets the build signature for this project + * + * The build signature uniquely identifies the build configuration and dependencies, + * used for cache validation and invalidation. + * + * @returns {string} Build signature string + */ + getBuildSignature() { + return this._buildSignature; + } } export default ProjectBuildContext; diff --git a/packages/project/lib/build/helpers/TaskUtil.js b/packages/project/lib/build/helpers/TaskUtil.js index b3a4fb97437..3a3f8e21d81 100644 --- a/packages/project/lib/build/helpers/TaskUtil.js +++ b/packages/project/lib/build/helpers/TaskUtil.js @@ -35,10 +35,10 @@ class TaskUtil { * This tag identifies resources that contain (i.e. bundle) multiple other resources * @property {string} IsDebugVariant * This tag identifies resources that are a debug variant (typically named with a "-dbg" suffix) - * of another resource. This tag is part of the build manifest. + * of another resource. This tag is visible to other projects * @property {string} HasDebugVariant * This tag identifies resources for which a debug variant has been created. - * This tag is part of the build manifest. + * This tag is visible to other projects */ /** diff --git a/packages/project/lib/build/helpers/WatchHandler.js b/packages/project/lib/build/helpers/WatchHandler.js new file mode 100644 index 00000000000..08d5d6d2ffd --- /dev/null +++ b/packages/project/lib/build/helpers/WatchHandler.js @@ -0,0 +1,77 @@ +import EventEmitter from "node:events"; +import chokidar from "chokidar"; +import {getLogger} from "@ui5/logger"; +const log = getLogger("build:helpers:WatchHandler"); + +/** + * Context of a build process + * + * @private + * @memberof @ui5/project/build/helpers + */ +class WatchHandler extends EventEmitter { + #closeCallbacks = []; + + constructor() { + super(); + } + + async watch(projects) { + const readyPromises = []; + for (const project of projects) { + readyPromises.push(this._watchProject(project)); + } + await Promise.all(readyPromises); + } + + async _watchProject(project) { + let ready = false; + const paths = project.getSourcePaths(); + log.verbose(`Watching source paths: ${paths.join(", ")}`); + + const watcher = chokidar.watch(paths, { + ignoreInitial: true, + }); + this.#closeCallbacks.push(async () => { + await watcher.close(); + }); + watcher.on("all", (event, filePath) => { + if (!ready) { + // Ignore events before ready + return; + } + if (event === "addDir") { + // Ignore directory creation events + return; + } + this.#handleWatchEvents(event, filePath, project).catch((err) => { + this.emit("error", err); + }); + }); + const {promise, resolve} = Promise.withResolvers(); + + watcher.on("ready", () => { + ready = true; + resolve(); + }); + watcher.on("error", (err) => { + this.emit("error", err); + }); + + return promise; + } + + async destroy() { + for (const cb of this.#closeCallbacks) { + await cb(); + } + } + + async #handleWatchEvents(eventType, filePath, project) { + const resourcePath = project.getVirtualPath(filePath); + log.verbose(`File changed: ${eventType} ${filePath} (as ${resourcePath} in project '${project.getName()}')`); + this.emit("change", eventType, resourcePath, project); + } +} + +export default WatchHandler; diff --git a/packages/project/lib/build/helpers/composeProjectList.js b/packages/project/lib/build/helpers/composeProjectList.js index d98ad17929e..0f4c5d6d01f 100644 --- a/packages/project/lib/build/helpers/composeProjectList.js +++ b/packages/project/lib/build/helpers/composeProjectList.js @@ -6,17 +6,17 @@ const log = getLogger("build:helpers:composeProjectList"); * its value is an array of all of its transitive dependencies. * * @param {@ui5/project/graph/ProjectGraph} graph - * @returns {Promise>} A promise resolving to an object with dependency names as + * @returns {Object} A promise resolving to an object with dependency names as * key and each with an array of its transitive dependencies as value */ -async function getFlattenedDependencyTree(graph) { +function getFlattenedDependencyTree(graph) { const dependencyMap = Object.create(null); const rootName = graph.getRoot().getName(); - await graph.traverseDepthFirst(({project, dependencies}) => { + for (const {project, dependencies} of graph.traverseDependenciesDepthFirst()) { if (project.getName() === rootName) { // Skip root project - return; + continue; } const projectDeps = []; dependencies.forEach((depName) => { @@ -26,7 +26,7 @@ async function getFlattenedDependencyTree(graph) { } }); dependencyMap[project.getName()] = projectDeps; - }); + } return dependencyMap; } @@ -41,7 +41,7 @@ async function getFlattenedDependencyTree(graph) { * @returns {{includedDependencies:string[],excludedDependencies:string[]}} An object containing the * 'includedDependencies' and 'excludedDependencies' */ -async function createDependencyLists(graph, { +function createDependencyLists(graph, { includeAllDependencies = false, includeDependency = [], includeDependencyRegExp = [], includeDependencyTree = [], excludeDependency = [], excludeDependencyRegExp = [], excludeDependencyTree = [], @@ -57,7 +57,7 @@ async function createDependencyLists(graph, { return {includedDependencies: [], excludedDependencies: []}; } - const flattenedDependencyTree = await getFlattenedDependencyTree(graph); + const flattenedDependencyTree = getFlattenedDependencyTree(graph); function isExcluded(excludeList, depName) { return excludeList && excludeList.has(depName); diff --git a/packages/project/lib/build/helpers/createBuildManifest.js b/packages/project/lib/build/helpers/createBuildManifest.js index 998935b3c05..1fe44ca7f40 100644 --- a/packages/project/lib/build/helpers/createBuildManifest.js +++ b/packages/project/lib/build/helpers/createBuildManifest.js @@ -8,7 +8,7 @@ async function getVersion(pkg) { } function getSortedTags(project) { - const tags = project.getResourceTagCollection().getAllTags(); + const tags = project.getProjectResourceTagCollection().getAllTags(); const entities = Object.entries(tags); entities.sort(([keyA], [keyB]) => { return keyA.localeCompare(keyB); @@ -16,7 +16,7 @@ function getSortedTags(project) { return Object.fromEntries(entities); } -export default async function(project, buildConfig, taskRepository) { +export default async function(project, buildConfig, taskRepository, signature) { if (!project) { throw new Error(`Missing parameter 'project'`); } @@ -26,6 +26,10 @@ export default async function(project, buildConfig, taskRepository) { if (!taskRepository) { throw new Error(`Missing parameter 'taskRepository'`); } + if (!signature) { + throw new Error(`Missing parameter 'signature'`); + } + const projectName = project.getName(); const type = project.getType(); @@ -45,7 +49,6 @@ export default async function(project, buildConfig, taskRepository) { `Project type ${type} is currently not supported`); } - const {builderVersion, fsVersion: builderFsVersion} = await taskRepository.getVersions(); const metadata = { project: { specVersion: project.getSpecVersion().toString(), @@ -59,27 +62,35 @@ export default async function(project, buildConfig, taskRepository) { } } }, - buildManifest: { - manifestVersion: "0.2", - timestamp: new Date().toISOString(), - versions: { - builderVersion: builderVersion, - projectVersion: await getVersion("@ui5/project"), - fsVersion: await getVersion("@ui5/fs"), - }, - buildConfig, - version: project.getVersion(), - namespace: project.getNamespace(), - tags: getSortedTags(project) - } + buildManifest: await createBuildManifest(project, buildConfig, taskRepository, signature), }; - if (metadata.buildManifest.versions.fsVersion !== builderFsVersion) { + return metadata; +} + +async function createBuildManifest(project, buildConfig, taskRepository, signature) { + // Use legacy manifest version for framework libraries to ensure compatibility + const {builderVersion, fsVersion: builderFsVersion} = await taskRepository.getVersions(); + const buildManifest = { + manifestVersion: "1.0", + timestamp: new Date().toISOString(), + signature, + versions: { + builderVersion: builderVersion, + projectVersion: await getVersion("@ui5/project"), + fsVersion: await getVersion("@ui5/fs"), + }, + buildConfig, + version: project.getVersion(), + namespace: project.getNamespace(), + tags: getSortedTags(project) + }; + + if (buildManifest.versions.fsVersion !== builderFsVersion) { // Added in manifestVersion 0.2: // @ui5/project and @ui5/builder use different versions of @ui5/fs. // This should be mentioned in the build manifest: - metadata.buildManifest.versions.builderFsVersion = builderFsVersion; + buildManifest.versions.builderFsVersion = builderFsVersion; } - - return metadata; + return buildManifest; } diff --git a/packages/project/lib/build/helpers/getBuildSignature.js b/packages/project/lib/build/helpers/getBuildSignature.js new file mode 100644 index 00000000000..f3dc3bc440c --- /dev/null +++ b/packages/project/lib/build/helpers/getBuildSignature.js @@ -0,0 +1,29 @@ +import crypto from "node:crypto"; + +const BUILD_SIG_VERSION = "0"; + +export function getBaseSignature(buildConfig) { + const key = BUILD_SIG_VERSION + JSON.stringify(buildConfig); + return crypto.createHash("sha256").update(key).digest("hex"); +} + +/** + * The build signature is calculated based on the **build configuration and environment** of a project. + * + * The hash is represented as a hexadecimal string to allow safe usage in file names. + * + * @private + * @param {string} baseSignature + * @param {@ui5/project/lib/Project} project The project to create the cache integrity for + * @param {@ui5/project/lib/graph/ProjectGraph} graph The project graph + * @param {@ui5/builder/tasks/taskRepository} taskRepository The task repository (used to determine the effective + * versions of ui5-builder and ui5-fs) + */ +export function getProjectSignature(baseSignature, project, graph, taskRepository) { + const key = baseSignature + project.getId() + JSON.stringify(project.getConfig()); + // TODO: Add signatures of relevant custom tasks + + // Create a hash for all metadata + const hash = crypto.createHash("sha256").update(key).digest("hex"); + return hash; +} diff --git a/packages/project/lib/graph/ProjectGraph.js b/packages/project/lib/graph/ProjectGraph.js index ba6967154e6..f3d2ccc0384 100644 --- a/packages/project/lib/graph/ProjectGraph.js +++ b/packages/project/lib/graph/ProjectGraph.js @@ -284,6 +284,40 @@ class ProjectGraph { processDependency(projectName); return Array.from(dependencies); } + + getDependents(projectName) { + if (!this._projects.has(projectName)) { + throw new Error( + `Failed to get dependents for project ${projectName}: ` + + `Unable to find project in project graph`); + } + const dependents = []; + for (const [fromProjectName, adjacencies] of this._adjList) { + if (adjacencies.has(projectName)) { + dependents.push(fromProjectName); + } + } + return dependents; + } + + getTransitiveDependents(projectName) { + const dependents = new Set(); + if (!this._projects.has(projectName)) { + throw new Error( + `Failed to get transitive dependents for project ${projectName}: ` + + `Unable to find project in project graph`); + } + const addDependents = (projectName) => { + const projectDependents = this.getDependents(projectName); + projectDependents.forEach((dependent) => { + dependents.add(dependent); + addDependents(dependent); + }); + }; + addDependents(projectName); + return Array.from(dependents); + } + /** * Checks whether a dependency is optional or not. * Currently only used in tests. @@ -475,6 +509,135 @@ class ProjectGraph { })(); } + /** + * Generator function that traverses all dependencies of the given start project depth-first. + * Each dependency project is visited exactly once, and dependencies are fully explored + * before the dependent project is yielded (post-order traversal). + * In case a cycle is detected, an error is thrown. + * + * @public + * @generator + * @param {string|boolean} [startName] Name of the project to start the traversal at, + * or a boolean to set includeStartModule while using the root project as start. + * Defaults to the graph's root project. + * @param {boolean} [includeStartModule=false] Whether to include the start project itself in the results + * @yields {object} Object containing the project and its direct dependencies + * @yields {module:@ui5/project/specifications/Project} return.project The dependency project + * @yields {string[]} return.dependencies Array of direct dependency names for this project + * @throws {Error} If the start project cannot be found or if a cycle is detected + */ + * traverseDependenciesDepthFirst(startName, includeStartModule = false) { + if (typeof startName === "boolean") { + includeStartModule = startName; + startName = undefined; + } + if (!startName) { + startName = this._rootProjectName; + } else if (!this.getProject(startName)) { + throw new Error(`Failed to start graph traversal: Could not find project ${startName} in project graph`); + } + + const visited = Object.create(null); + const processing = Object.create(null); + + const traverse = function* (projectName, ancestors) { + this._checkCycle(ancestors, projectName); + + if (visited[projectName]) { + return; + } + + if (processing[projectName]) { + return; + } + + processing[projectName] = true; + const newAncestors = [...ancestors, projectName]; + const dependencies = this.getDependencies(projectName); + + for (const depName of dependencies) { + yield* traverse.call(this, depName, newAncestors); + } + + visited[projectName] = true; + processing[projectName] = false; + + if (includeStartModule || projectName !== startName) { + yield { + project: this.getProject(projectName), + dependencies + }; + } + }.bind(this); + + yield* traverse(startName, []); + } + + /** + * Generator function that traverses all projects that depend on the given start project. + * Traversal is breadth-first, visiting each dependent project exactly once. + * Projects are yielded in the order they are discovered as dependents. + * In case a cycle is detected, an error is thrown. + * + * @public + * @generator + * @param {string|boolean} [startName] Name of the project to start the traversal at, + * or a boolean to set includeStartModule while using the root project as start. + * Defaults to the graph's root project. + * @param {boolean} [includeStartModule=false] Whether to include the start project itself in the results + * @yields {object} Object containing the dependent project and its dependents + * @yields {module:@ui5/project/specifications/Project} return.project The dependent project + * @yields {string[]} return.dependents Array of project names that depend on this project + * @throws {Error} If the start project cannot be found or if a cycle is detected + */ + * traverseDependents(startName, includeStartModule = false) { + if (typeof startName === "boolean") { + includeStartModule = startName; + startName = undefined; + } + if (!startName) { + startName = this._rootProjectName; + } else if (!this.getProject(startName)) { + throw new Error(`Failed to start graph traversal: Could not find project ${startName} in project graph`); + } + + const queue = [{ + projectNames: [startName], + ancestors: [] + }]; + + const visited = Object.create(null); + + while (queue.length) { + const {projectNames, ancestors} = queue.shift(); // Get and remove first entry from queue + + for (const projectName of projectNames) { + this._checkCycle(ancestors, projectName); + if (visited[projectName]) { + continue; + } + + visited[projectName] = true; + + const newAncestors = [...ancestors, projectName]; + const dependents = this.getDependents(projectName); + + queue.push({ + projectNames: dependents, + ancestors: newAncestors + }); + + if (includeStartModule || projectName !== startName) { + // Do not yield the start module itself + yield { + project: this.getProject(projectName), + dependents + }; + } + } + } + } + /** * Join another project graph into this one. * Projects and extensions which already exist in this graph will cause an error to be thrown @@ -558,15 +721,15 @@ class ProjectGraph { dependencyIncludes, selfContained = false, cssVariables = false, jsdoc = false, createBuildManifest = false, includedTasks = [], excludedTasks = [], - outputStyle = OutputStyleEnum.Default + outputStyle = OutputStyleEnum.Default, }) { this.seal(); // Do not allow further changes to the graph - if (this._built) { + if (this._builtOrServed) { throw new Error( - `Project graph with root node ${this._rootProjectName} has already been built. ` + - `Each graph can only be built once`); + `Project graph with root node ${this._rootProjectName} has already been built or served. ` + + `Each graph can only be built or served once`); } - this._built = true; + this._builtOrServed = true; const { default: ProjectBuilder } = await import("../build/ProjectBuilder.js"); @@ -579,13 +742,46 @@ class ProjectGraph { includedTasks, excludedTasks, outputStyle, } }); - await builder.build({ + return await builder.buildToTarget({ destPath, cleanDest, includedDependencies, excludedDependencies, dependencyIncludes, }); } + async serve({ + initialBuildRootProject = false, + initialBuildIncludedDependencies = [], initialBuildExcludedDependencies = [], + selfContained = false, cssVariables = false, jsdoc = false, createBuildManifest = false, + includedTasks = [], excludedTasks = [], + }) { + this.seal(); // Do not allow further changes to the graph + if (this._builtOrServed) { + throw new Error( + `Project graph with root node ${this._rootProjectName} has already been built or served. ` + + `Each graph can only be built or served once`); + } + this._builtOrServed = true; + const { + default: ProjectBuilder + } = await import("../build/ProjectBuilder.js"); + const builder = new ProjectBuilder({ + graph: this, + taskRepository: await this._getTaskRepository(), + buildConfig: { + selfContained, cssVariables, jsdoc, + createBuildManifest, + includedTasks, excludedTasks, + outputStyle: OutputStyleEnum.Default, + } + }); + const { + default: BuildServer + } = await import("../build/BuildServer.js"); + return new BuildServer(this, builder, + initialBuildRootProject, initialBuildIncludedDependencies, initialBuildExcludedDependencies); + } + /** * Seal the project graph so that no further changes can be made to it * diff --git a/packages/project/lib/resources/ProjectResources.js b/packages/project/lib/resources/ProjectResources.js new file mode 100644 index 00000000000..319db848c3e --- /dev/null +++ b/packages/project/lib/resources/ProjectResources.js @@ -0,0 +1,489 @@ +import {createWorkspace, createReaderCollectionPrioritized} from "@ui5/fs/resourceFactory"; +import MonitoredResourceTagCollection from "@ui5/fs/internal/MonitoredResourceTagCollection"; +import ResourceTagCollection from "@ui5/fs/internal/ResourceTagCollection"; +import Stage from "./Stage.js"; + +const INITIAL_STAGE_ID = "initial"; +const RESULT_STAGE_ID = "result"; + +/** + * Manages resource access and stages for a project. + * + * @public + * @class + * @alias @ui5/project/resources/ProjectResources + */ +class ProjectResources { + #stages = []; // Stages in order of creation + + // State + #currentStage; + #currentStageReadIndex; + #lastTagCacheImportIndex; + #currentTagCacheImportIndex; + #currentStageId; + + // Cache + #currentStageWorkspace; + #currentStageReaders; // Map to store the various reader styles + + // Callbacks (interface object) + #getName; + #getStyledReader; + #createWriter; + #addReadersForWriter; + + // Project tag collection resets at the beginning of every build + #projectResourceTagCollection; + // Build tag collection resets at the end of every build + // (so that those tags are not accessible to dependent projects) + #buildResourceTagCollection; + + // Individual monitors per stage + #monitoredProjectResourceTagCollection; + #monitoredBuildResourceTagCollection; + + #buildManifest; + + /** + * @param {object} options Configuration options + * @param {Function} options.getName Returns the project name (for error messages and reader names) + * @param {Function} options.getStyledReader Gets the source reader for a given style + * @param {Function} options.createWriter Creates a writer for a stage + * @param {Function} options.addReadersForWriter Adds readers for a writer to a readers array + * @param {object} options.buildManifest + */ + constructor({getName, getStyledReader, createWriter, addReadersForWriter, buildManifest}) { + this.#getName = getName; + this.#getStyledReader = getStyledReader; + this.#createWriter = createWriter; + this.#addReadersForWriter = addReadersForWriter; + this.#buildManifest = buildManifest; + + this.#initStageMetadata(); + } + + /** + * Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the + * project in the specified "style": + * + *
    + *
  • buildtime: Resource paths are always prefixed with /resources/ + * or /test-resources/ followed by the project's namespace. + * Any configured build-excludes are applied
  • + *
  • dist: Resource paths always match with what the UI5 runtime expects. + * This means that paths generally depend on the project type. Applications for example use a "flat"-like + * structure, while libraries use a "buildtime"-like structure. + * Any configured build-excludes are applied
  • + *
  • runtime: Resource paths always match with what the UI5 runtime expects. + * This means that paths generally depend on the project type. Applications for example use a "flat"-like + * structure, while libraries use a "buildtime"-like structure. + * This style is typically used for serving resources directly. Therefore, build-excludes are not applied
  • + *
  • flat: Resource paths are never prefixed and namespaces are omitted if possible. Note that + * project types like "theme-library", which can have multiple namespaces, can't omit them. + * Any configured build-excludes are applied
  • + *
+ * + * If project resources have been changed through the means of a workspace, those changes + * are reflected in the provided reader too. + * + * Resource readers always use POSIX-style paths. + * + * @public + * @param {object} [options] + * @param {string} [options.style=buildtime] Path style to access resources. + * Can be "buildtime", "dist", "runtime" or "flat" + * @returns {@ui5/fs/ReaderCollection} A reader collection instance + */ + getReader({style = "buildtime"} = {}) { + let reader = this.#currentStageReaders.get(style); + if (reader) { + // Use cached reader + return reader; + } + + const readers = []; + if (this.#currentStage) { + // Add current writer as highest priority reader + const currentWriter = this.#currentStage.getWriter(); + if (currentWriter) { + this.#addReadersForWriter(readers, currentWriter, style); + } else { + const currentReader = this.#currentStage.getCachedWriter(); + if (currentReader) { + this.#addReadersForWriter(readers, currentReader, style); + } + } + } + // Add readers for previous stages and source + readers.push(...this.#getReaders(style)); + + reader = createReaderCollectionPrioritized({ + name: `Reader collection for stage '${this.#currentStageId}' of project ${this.#getName()}`, + readers + }); + + this.#currentStageReaders.set(style, reader); + return reader; + } + + #getReaders(style = "buildtime") { + const readers = []; + + // Add writers for previous stages as readers + const stageReadIdx = this.#currentStageReadIndex; + + // Collect writers from all relevant stages + for (let i = stageReadIdx; i >= 0; i--) { + this.#addReaderForStage(this.#stages[i], readers, style); + } + + // Finally add the project's source reader + readers.push(this.#getStyledReader(style)); + + return readers; + } + + /** + * Get the source reader for the project. + * + * @public + * @param {string} [style=buildtime] Path style to access resources + * @returns {@ui5/fs/ReaderCollection} A reader collection instance + */ + getSourceReader(style = "buildtime") { + return this.#getStyledReader(style); + } + + /** + * Get a [DuplexCollection]{@link @ui5/fs/DuplexCollection} for accessing and modifying a + * project's resources. This is always of style buildtime. + * + * Once a project has finished building, this method will throw to prevent further modifications + * since those would have no effect. Use the getReader method to access the project's (modified) resources + * + * @public + * @returns {@ui5/fs/DuplexCollection} DuplexCollection + */ + getWorkspace() { + if (this.#currentStageId === RESULT_STAGE_ID) { + throw new Error( + `Workspace of project ${this.#getName()} is currently not available. ` + + `This might indicate that the project has already finished building ` + + `and its content can not be modified further. ` + + `Use method 'getReader' for read-only access`); + } + if (this.#currentStageWorkspace) { + return this.#currentStageWorkspace; + } + const reader = createReaderCollectionPrioritized({ + name: `Reader collection for stage '${this.#currentStageId}' of project ${this.#getName()}`, + readers: this.#getReaders(), + }); + const writer = this.#currentStage.getWriter(); + const workspace = createWorkspace({ + reader, + writer + }); + this.#currentStageWorkspace = workspace; + return workspace; + } + + /** + * Seal the workspace of the project, preventing further modifications. + * This is typically called once the project has finished building. Resources from all stages will be used. + * + * A project can be unsealed by calling useStage() again. + * + * @public + */ + useResultStage() { + this.#currentStage = null; + this.#currentStageId = RESULT_STAGE_ID; + this.#currentStageReadIndex = this.#stages.length - 1; // Read from all stages + this.#currentTagCacheImportIndex = this.#stages.length - 1; // Import cached tags from all stages + + // Unset "current" reader/writer. They will be recreated on demand + this.#currentStageReaders = new Map(); + this.#currentStageWorkspace = null; + + this.#monitoredProjectResourceTagCollection = null; + this.#monitoredBuildResourceTagCollection = null; + } + + #initStageMetadata() { + this.#stages = []; + // Initialize with an empty stage for use without stages (i.e. without build cache) + this.#currentStage = new Stage(INITIAL_STAGE_ID, this.#createWriter(INITIAL_STAGE_ID)); + this.#currentStageId = INITIAL_STAGE_ID; + this.#currentStageReadIndex = -1; + this.#lastTagCacheImportIndex = -1; + this.#currentTagCacheImportIndex = -1; + this.#currentStageReaders = new Map(); + this.#currentStageWorkspace = null; + this.#projectResourceTagCollection = null; + } + + #addReaderForStage(stage, readers, style = "buildtime") { + const writer = stage.getWriter(); + if (writer) { + this.#addReadersForWriter(readers, writer, style); + } else { + const reader = stage.getCachedWriter(); + if (reader) { + this.#addReadersForWriter(readers, reader, style); + } + } + } + + /** + * Initialize stages for the build process. + * + * @public + * @param {string[]} stageIds Array of stage IDs to initialize + */ + initStages(stageIds) { + this.#initStageMetadata(); + for (let i = 0; i < stageIds.length; i++) { + const stageId = stageIds[i]; + const newStage = new Stage(stageId, this.#createWriter(stageId)); + this.#stages.push(newStage); + } + } + + /** + * Get the current stage. + * + * @public + * @returns {Stage|null} The current stage or null if in result stage + */ + getStage() { + return this.#currentStage; + } + + /** + * Switch to a specific stage. + * + * @public + * @param {string} stageId The ID of the stage to use + * @throws {Error} If the stage does not exist + */ + useStage(stageId) { + if (stageId === this.#currentStage?.getId()) { + // Already using requested stage + return; + } + + const stageIdx = this.#stages.findIndex((s) => s.getId() === stageId); + + if (stageIdx === -1) { + throw new Error(`Stage '${stageId}' does not exist in project ${this.#getName()}`); + } + + const stage = this.#stages[stageIdx]; + this.#currentStage = stage; + this.#currentStageId = stageId; + this.#currentStageReadIndex = stageIdx - 1; // Read from all previous stages + this.#currentTagCacheImportIndex = stageIdx; // Import cached tags from previous and current stages + + // Unset "current" reader/writer caches. They will be recreated on demand + this.#currentStageReaders = new Map(); + this.#currentStageWorkspace = null; + + this.#monitoredProjectResourceTagCollection = null; + this.#monitoredBuildResourceTagCollection = null; + } + + /** + * Set or replace a stage. + * + * @public + * @param {string} stageId The ID of the stage to set + * @param {Stage|object} stageOrCachedWriter A Stage instance or a cached writer/reader + * @param {Map>} projectTagOperations + * @param {Map>} buildTagOperations + * @returns {boolean} True if the stored stage has changed, false otherwise + * @throws {Error} If the stage does not exist or invalid parameters are provided + */ + setStage(stageId, stageOrCachedWriter, projectTagOperations, buildTagOperations) { + const stageIdx = this.#stages.findIndex((s) => s.getId() === stageId); + if (stageIdx === -1) { + throw new Error(`Stage '${stageId}' does not exist in project ${this.#getName()}`); + } + if (!stageOrCachedWriter) { + throw new Error( + `Invalid stage or cache reader provided for stage '${stageId}' in project ${this.#getName()}`); + } + const oldStage = this.#stages[stageIdx]; + if (oldStage.getId() !== stageId) { + throw new Error( + `Stage ID mismatch for stage '${stageId}' in project ${this.#getName()}`); + } + let newStage; + if (stageOrCachedWriter instanceof Stage) { + newStage = stageOrCachedWriter; + if (oldStage === newStage) { + // Same stage as before, nothing to do + return false; // Stored stage has not changed + } + } else { + newStage = new Stage(stageId, undefined, stageOrCachedWriter, + projectTagOperations, buildTagOperations); + } + this.#stages[stageIdx] = newStage; + + // If we are updating the current stage, make sure to update and reset all relevant references + if (oldStage === this.#currentStage) { + this.#currentStage = newStage; + // Unset "current" reader/writer. They might be outdated + this.#currentStageReaders = new Map(); + this.#currentStageWorkspace = null; + } + return true; // Indicate that the stored stage has changed + } + + buildFinished() { + // Clear build resource tag collections. They must not be provided to dependent projects + this.#buildResourceTagCollection = null; + } + + /** + * Gets the appropriate resource tag collection for a resource and tag + * + * Determines which tag collection (project-specific or build-level) should be used + * for the given resource and tag combination. Associates the resource with the current + * project if not already associated. + * + * @param {@ui5/fs/Resource} resource Resource to get tag collection for + * @param {string} tag Tag to check acceptance for + * @returns {@ui5/fs/internal/ResourceTagCollection} Appropriate tag collection + * @throws {Error} If no collection accepts the given tag + */ + getResourceTagCollection(resource, tag) { + this.#applyCachedResourceTags(); + const projectCollection = this.#getProjectResourceTagCollection(); + if (projectCollection.acceptsTag(tag)) { + if (!this.#monitoredProjectResourceTagCollection) { + this.#monitoredProjectResourceTagCollection = new MonitoredResourceTagCollection(projectCollection); + } + return this.#monitoredProjectResourceTagCollection; + } + const buildCollection = this.#getBuildResourceTagCollection(); + if (buildCollection.acceptsTag(tag)) { + if (!this.#monitoredBuildResourceTagCollection) { + this.#monitoredBuildResourceTagCollection = new MonitoredResourceTagCollection(buildCollection); + } + return this.#monitoredBuildResourceTagCollection; + } + throw new Error(`Could not find collection for resource ${resource.getPath()} and tag ${tag}`); + } + + getResourceTagOperations() { + return { + projectTagOperations: new Map([ + ...this.#currentStage.getCachedProjectTagOperations() ?? [], + ...this.#monitoredProjectResourceTagCollection?.getTagOperations() ?? [], + ]), + buildTagOperations: new Map([ + ...this.#currentStage.getCachedBuildTagOperations() ?? [], + ...this.#monitoredBuildResourceTagCollection?.getTagOperations() ?? [], + ]), + }; + } + + /** + * Returns the project-level resource tag collection. + * + * This provides direct access to the collection holding project-level tags + * (e.g. ui5:IsDebugVariant, ui5:HasDebugVariant), which is needed for + * build manifest creation and reading. + * + * @returns {@ui5/fs/internal/ResourceTagCollection} The project-level resource tag collection + */ + getProjectResourceTagCollection() { + return this.#getProjectResourceTagCollection(); + } + + #getProjectResourceTagCollection() { + if (!this.#projectResourceTagCollection) { + this.#projectResourceTagCollection = new ResourceTagCollection({ + allowedTags: ["ui5:IsDebugVariant", "ui5:HasDebugVariant"], + allowedNamespaces: ["project"], + tags: this.#buildManifest?.tags + }); + } + return this.#projectResourceTagCollection; + } + + #getBuildResourceTagCollection() { + if (!this.#buildResourceTagCollection) { + this.#buildResourceTagCollection = new ResourceTagCollection({ + allowedTags: ["ui5:OmitFromBuildResult", "ui5:IsBundle"], + allowedNamespaces: ["build"], + tags: this.#buildManifest?.tags + }); + } + return this.#buildResourceTagCollection; + } + + #applyCachedResourceTags() { + // Collect tag ops from all relevant stages + const cachedProjectTagOps = []; + const cachedBuildTagOps = []; + + for (let i = this.#lastTagCacheImportIndex + 1; i <= this.#currentTagCacheImportIndex; i++) { + const projectTagOps = this.#stages[i].getCachedProjectTagOperations(); + if (projectTagOps) { + cachedProjectTagOps.push(projectTagOps); + } + const buildTagOps = this.#stages[i].getCachedBuildTagOperations(); + if (buildTagOps) { + cachedBuildTagOps.push(buildTagOps); + } + } + + // if (this.#currentStage) { + // const projectTagOps = this.#currentStage.getCachedProjectTagOperations(); + // if (projectTagOps) { + // cachedProjectTagOps.push(projectTagOps); + // } + // const buildTagOps = this.#currentStage.getCachedBuildTagOperations(); + // if (buildTagOps) { + // cachedBuildTagOps.push(buildTagOps); + // } + // } + this.#lastTagCacheImportIndex = this.#currentTagCacheImportIndex; + + const projectTagOps = mergeMaps(...cachedProjectTagOps); + const buildTagOps = mergeMaps(...cachedBuildTagOps); + + if (projectTagOps.size) { + const projectTagCollection = this.#getProjectResourceTagCollection(); + for (const [resourcePath, tags] of projectTagOps.entries()) { + for (const [tag, value] of tags.entries()) { + projectTagCollection.setTag(resourcePath, tag, value); + } + } + } + if (buildTagOps.size) { + const buildTagCollection = this.#getBuildResourceTagCollection(); + for (const [resourcePath, tags] of buildTagOps.entries()) { + for (const [tag, value] of tags.entries()) { + buildTagCollection.setTag(resourcePath, tag, value); + } + } + } + } +} + +const mergeMaps = (...maps) => { + const result = new Map(); + for (const map of maps) { + for (const [key, value] of map) { + result.set(key, value); + } + } + return result; +}; + +export default ProjectResources; diff --git a/packages/project/lib/resources/Stage.js b/packages/project/lib/resources/Stage.js new file mode 100644 index 00000000000..e0e6d4276ed --- /dev/null +++ b/packages/project/lib/resources/Stage.js @@ -0,0 +1,45 @@ +/** + * A stage has either a writer or a reader, never both. + * Consumers need to be able to differentiate between the two + */ +class Stage { + #id; + #writer; + #cachedWriter; + #cachedProjectTagOperations; + #cachedBuildTagOperations; + + constructor(id, writer, cachedWriter, cachedProjectTagOperations, cachedBuildTagOperations) { + if (writer && cachedWriter) { + throw new Error( + `Stage '${id}' cannot have both a writer and a cache reader`); + } + this.#id = id; + this.#writer = writer; + this.#cachedWriter = cachedWriter; + this.#cachedProjectTagOperations = cachedProjectTagOperations; + this.#cachedBuildTagOperations = cachedBuildTagOperations; + } + + getId() { + return this.#id; + } + + getWriter() { + return this.#writer; + } + + getCachedWriter() { + return this.#cachedWriter; + } + + getCachedProjectTagOperations() { + return this.#cachedProjectTagOperations; + } + + getCachedBuildTagOperations() { + return this.#cachedBuildTagOperations; + } +} + +export default Stage; diff --git a/packages/project/lib/specifications/ComponentProject.js b/packages/project/lib/specifications/ComponentProject.js index 337b3652e29..654c49c1b08 100644 --- a/packages/project/lib/specifications/ComponentProject.js +++ b/packages/project/lib/specifications/ComponentProject.js @@ -91,39 +91,7 @@ class ComponentProject extends Project { /* === Resource Access === */ - /** - * Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the - * project in the specified "style": - * - *
    - *
  • buildtime: Resource paths are always prefixed with /resources/ - * or /test-resources/ followed by the project's namespace. - * Any configured build-excludes are applied
  • - *
  • dist: Resource paths always match with what the UI5 runtime expects. - * This means that paths generally depend on the project type. Applications for example use a "flat"-like - * structure, while libraries use a "buildtime"-like structure. - * Any configured build-excludes are applied
  • - *
  • runtime: Resource paths always match with what the UI5 runtime expects. - * This means that paths generally depend on the project type. Applications for example use a "flat"-like - * structure, while libraries use a "buildtime"-like structure. - * This style is typically used for serving resources directly. Therefore, build-excludes are not applied
  • - *
  • flat: Resource paths are never prefixed and namespaces are omitted if possible. Note that - * project types like "theme-library", which can have multiple namespaces, can't omit them. - * Any configured build-excludes are applied
  • - *
- * - * If project resources have been changed through the means of a workspace, those changes - * are reflected in the provided reader too. - * - * Resource readers always use POSIX-style paths. - * - * @public - * @param {object} [options] - * @param {string} [options.style=buildtime] Path style to access resources. - * Can be "buildtime", "dist", "runtime" or "flat" - * @returns {@ui5/fs/ReaderCollection} A reader collection instance - */ - getReader({style = "buildtime"} = {}) { + _getStyledReader(style) { // TODO: Additional style 'ABAP' using "sap.platform.abap".uri from manifest.json? // Apply builder excludes to all styles but "runtime" @@ -161,7 +129,6 @@ class ComponentProject extends Project { throw new Error(`Unknown path mapping style ${style}`); } - reader = this._addWriter(reader, style); return reader; } @@ -183,52 +150,30 @@ class ComponentProject extends Project { throw new Error(`_getTestReader must be implemented by subclass ${this.constructor.name}`); } - /** - * Get a resource reader/writer for accessing and modifying a project's resources - * - * @public - * @returns {@ui5/fs/ReaderCollection} A reader collection instance - */ - getWorkspace() { - // Workspace is always of style "buildtime" - // Therefore builder resource-excludes are always to be applied - const excludes = this.getBuilderResourcesExcludes(); - return resourceFactory.createWorkspace({ - name: `Workspace for project ${this.getName()}`, - reader: this._getReader(excludes), - writer: this._getWriter().collection + _createWriter(stageId) { + // writer is always of style "buildtime" + const namespaceWriter = resourceFactory.createAdapter({ + name: `Namespace writer for project ${this.getName()}, stage ${stageId}`, + virBasePath: "/", + project: this }); - } - - _getWriter() { - if (!this._writers) { - // writer is always of style "buildtime" - const namespaceWriter = resourceFactory.createAdapter({ - virBasePath: "/", - project: this - }); - const generalWriter = resourceFactory.createAdapter({ - virBasePath: "/", - project: this - }); + const generalWriter = resourceFactory.createAdapter({ + name: `General writer for project ${this.getName()}, stage ${stageId}`, + virBasePath: "/", + project: this + }); - const collection = resourceFactory.createWriterCollection({ - name: `Writers for project ${this.getName()}`, - writerMapping: { - [`/resources/${this._namespace}/`]: namespaceWriter, - [`/test-resources/${this._namespace}/`]: namespaceWriter, - [`/`]: generalWriter - } - }); + const collection = resourceFactory.createWriterCollection({ + name: `Writers for project ${this.getName()}, stage ${stageId}`, + writerMapping: { + [`/resources/${this._namespace}/`]: namespaceWriter, + [`/test-resources/${this._namespace}/`]: namespaceWriter, + [`/`]: generalWriter + } + }); - this._writers = { - namespaceWriter, - generalWriter, - collection - }; - } - return this._writers; + return collection; } _getReader(excludes) { @@ -243,15 +188,31 @@ class ComponentProject extends Project { return reader; } - _addWriter(reader, style) { - const {namespaceWriter, generalWriter} = this._getWriter(); - + _addReadersForWriter(readers, writer, style) { if ((style === "runtime" || style === "dist") && this._isRuntimeNamespaced) { // If the project's type requires a namespace at runtime, the // dist- and runtime-style paths are identical to buildtime-style paths style = "buildtime"; } - const readers = []; + + let generalWriter; + let namespaceWriter; + if (writer.getMapping) { + const mapping = writer.getMapping(); + generalWriter = mapping[`/`]; + for (const writer of Object.values(mapping)) { + if (writer === generalWriter) { + continue; + } + if (namespaceWriter && writer !== namespaceWriter) { + throw new Error(`Cannot determine unique namespace writer for project ${this.getName()}`); + } + namespaceWriter = writer; + } + } else { + throw new Error(`Cannot determine writers for project ${this.getName()}`); + } + switch (style) { case "buildtime": // Writer already uses buildtime style @@ -265,8 +226,10 @@ class ComponentProject extends Project { reader: namespaceWriter, namespace: this._namespace })); - // Add general writer as is - readers.push(generalWriter); + // Add general writer only if it differs to prevent duplicate entries (with and without namespace) + if (namespaceWriter !== generalWriter) { + readers.push(generalWriter); + } break; case "flat": // Rewrite paths from "flat" to "buildtime" @@ -279,12 +242,6 @@ class ComponentProject extends Project { default: throw new Error(`Unknown path mapping style ${style}`); } - readers.push(reader); - - return resourceFactory.createReaderCollectionPrioritized({ - name: `Reader/Writer collection for project ${this.getName()}`, - readers - }); } /* === Internals === */ diff --git a/packages/project/lib/specifications/Project.js b/packages/project/lib/specifications/Project.js index 98cbf29da1d..97c6703231a 100644 --- a/packages/project/lib/specifications/Project.js +++ b/packages/project/lib/specifications/Project.js @@ -1,5 +1,5 @@ import Specification from "./Specification.js"; -import ResourceTagCollection from "@ui5/fs/internal/ResourceTagCollection"; +import ProjectResources from "../resources/ProjectResources.js"; /** * Project @@ -12,6 +12,8 @@ import ResourceTagCollection from "@ui5/fs/internal/ResourceTagCollection"; * @hideconstructor */ class Project extends Specification { + #projectResources; + constructor(parameters) { super(parameters); if (new.target === Project) { @@ -37,6 +39,15 @@ class Project extends Specification { await this._configureAndValidatePaths(this._config); await this._parseConfiguration(this._config, this._buildManifest); + // Initialize ProjectResources with interface callbacks + this.#projectResources = new ProjectResources({ + getName: () => this.getName(), + getStyledReader: (style) => this._getStyledReader(style), + createWriter: (stageId) => this._createWriter(stageId), + addReadersForWriter: (readers, writer, style) => this._addReadersForWriter(readers, writer, style), + buildManifest: this._buildManifest + }); + return this; } @@ -87,6 +98,14 @@ class Project extends Specification { throw new Error(`getSourcePath must be implemented by subclass ${this.constructor.name}`); } + getSourcePaths() { + throw new Error(`getSourcePaths must be implemented by subclass ${this.constructor.name}`); + } + + getVirtualPath() { + throw new Error(`getVirtualPath must be implemented by subclass ${this.constructor.name}`); + } + /** * Get the project's framework name configuration * @@ -220,6 +239,17 @@ class Project extends Specification { } /* === Resource Access === */ + + /** + * Get the ProjectResources instance for this project. + * + * @public + * @returns {@ui5/project/resources/ProjectResources} The ProjectResources instance + */ + getProjectResources() { + return this.#projectResources; + } + /** * Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the * project in the specified "style": @@ -241,38 +271,74 @@ class Project extends Specification { * Any configured build-excludes are applied * * + * If project resources have been changed through the means of a workspace, those changes + * are reflected in the provided reader too. + * * Resource readers always use POSIX-style paths. * * @public * @param {object} [options] * @param {string} [options.style=buildtime] Path style to access resources. * Can be "buildtime", "dist", "runtime" or "flat" - * @returns {@ui5/fs/ReaderCollection} Reader collection allowing access to all resources of the project + * @returns {@ui5/fs/ReaderCollection} A reader collection instance */ getReader(options) { - throw new Error(`getReader must be implemented by subclass ${this.constructor.name}`); + return this.#projectResources.getReader(options); } - getResourceTagCollection() { - if (!this._resourceTagCollection) { - this._resourceTagCollection = new ResourceTagCollection({ - allowedTags: ["ui5:IsDebugVariant", "ui5:HasDebugVariant"], - allowedNamespaces: ["project"], - tags: this.getBuildManifest()?.tags - }); - } - return this._resourceTagCollection; + /** + * Get the source reader for the project. + * + * @public + * @param {string} [style=buildtime] Path style to access resources + * @returns {@ui5/fs/ReaderCollection} A reader collection instance + */ + getSourceReader(style = "buildtime") { + return this.#projectResources.getSourceReader(style); } /** * Get a [DuplexCollection]{@link @ui5/fs/DuplexCollection} for accessing and modifying a * project's resources. This is always of style buildtime. * + * Once a project has finished building, this method will throw to prevent further modifications + * since those would have no effect. Use the getReader method to access the project's (modified) resources + * * @public * @returns {@ui5/fs/DuplexCollection} DuplexCollection */ getWorkspace() { - throw new Error(`getWorkspace must be implemented by subclass ${this.constructor.name}`); + return this.#projectResources.getWorkspace(); + } + + /* Overwritten in ComponentProject subclass */ + _addReadersForWriter(readers, writer, style) { + readers.unshift(writer); + } + + /** + * Gets the appropriate resource tag collection for a resource and tag + * + * Determines which tag collection (project-specific or build-level) should be used + * for the given resource and tag combination. Associates the resource with the current + * project if not already associated. + * + * @param {@ui5/fs/Resource} resource Resource to get tag collection for + * @param {string} tag Tag to check acceptance for + * @returns {@ui5/fs/internal/ResourceTagCollection} Appropriate tag collection + * @throws {Error} If no collection accepts the given tag + */ + getResourceTagCollection(resource, tag) { + return this.#projectResources.getResourceTagCollection(resource, tag); + } + + /** + * Returns the project-level resource tag collection + * + * @returns {@ui5/fs/internal/ResourceTagCollection} The project-level resource tag collection + */ + getProjectResourceTagCollection() { + return this.#projectResources.getProjectResourceTagCollection(); } /* === Internals === */ diff --git a/packages/project/lib/specifications/Specification.js b/packages/project/lib/specifications/Specification.js index 02bdc58036e..212aa37ecdd 100644 --- a/packages/project/lib/specifications/Specification.js +++ b/packages/project/lib/specifications/Specification.js @@ -161,6 +161,10 @@ class Specification { } /* === Attributes === */ + getConfig() { + return this._config; + } + /** * Gets the ID of this specification. * diff --git a/packages/project/lib/specifications/extensions/Task.js b/packages/project/lib/specifications/extensions/Task.js index c5aee60b7a0..9964dbe43f3 100644 --- a/packages/project/lib/specifications/extensions/Task.js +++ b/packages/project/lib/specifications/extensions/Task.js @@ -31,6 +31,27 @@ class Task extends Extension { return (await this._getImplementation()).determineRequiredDependencies; } + /** + * @public + */ + async getBuildSignatureCallback() { + return (await this._getImplementation()).determineBuildSignature; + } + + /** + * @public + */ + async getSupportsDifferentialBuildsCallback() { + return (await this._getImplementation()).supportsDifferentialBuilds; + } + + /** + * @public + */ + async getExpectedOutputCallback() { + return (await this._getImplementation()).determineExpectedOutput; + } + /* === Internals === */ /** * @private diff --git a/packages/project/lib/specifications/types/Application.js b/packages/project/lib/specifications/types/Application.js index 1dc17b4bc1c..44f39b4ef6d 100644 --- a/packages/project/lib/specifications/types/Application.js +++ b/packages/project/lib/specifications/types/Application.js @@ -45,6 +45,21 @@ class Application extends ComponentProject { return fsPath.join(this.getRootPath(), this._webappPath); } + getSourcePaths() { + return [this.getSourcePath()]; + } + + getVirtualPath(sourceFilePath) { + const sourcePath = this.getSourcePath(); + if (sourceFilePath.startsWith(sourcePath)) { + const relSourceFilePath = fsPath.relative(sourcePath, sourceFilePath); + return `/resources/${this._namespace}/${relSourceFilePath}`; + } + + throw new Error( + `Unable to convert source path ${sourceFilePath} to virtual path for project ${this.getName()}`); + } + /* === Resource Access === */ /** * Get a resource reader for the sources of the project (excluding any test resources) @@ -107,13 +122,13 @@ class Application extends ComponentProject { /** * @private * @param {object} config Configuration object - * @param {object} buildDescription Cache metadata object + * @param {object} buildManifest Cache metadata object */ - async _parseConfiguration(config, buildDescription) { - await super._parseConfiguration(config, buildDescription); + async _parseConfiguration(config, buildManifest) { + await super._parseConfiguration(config, buildManifest); - if (buildDescription) { - this._namespace = buildDescription.namespace; + if (buildManifest) { + this._namespace = buildManifest.namespace; return; } this._namespace = await this._getNamespace(); diff --git a/packages/project/lib/specifications/types/Component.js b/packages/project/lib/specifications/types/Component.js index 8ca5b94df26..54a19df7f51 100644 --- a/packages/project/lib/specifications/types/Component.js +++ b/packages/project/lib/specifications/types/Component.js @@ -165,13 +165,13 @@ class Component extends ComponentProject { /** * @private * @param {object} config Configuration object - * @param {object} buildDescription Cache metadata object + * @param {object} buildManifest Cache metadata object */ - async _parseConfiguration(config, buildDescription) { - await super._parseConfiguration(config, buildDescription); + async _parseConfiguration(config, buildManifest) { + await super._parseConfiguration(config, buildManifest); - if (buildDescription) { - this._namespace = buildDescription.namespace; + if (buildManifest) { + this._namespace = buildManifest.namespace; return; } this._namespace = await this._getNamespace(); diff --git a/packages/project/lib/specifications/types/Library.js b/packages/project/lib/specifications/types/Library.js index d3d2059a055..e118f39e6b6 100644 --- a/packages/project/lib/specifications/types/Library.js +++ b/packages/project/lib/specifications/types/Library.js @@ -56,6 +56,39 @@ class Library extends ComponentProject { return fsPath.join(this.getRootPath(), this._srcPath); } + getSourcePaths() { + const paths = [this.getSourcePath()]; + if (this._testPathExists) { + paths.push(fsPath.join(this.getRootPath(), this._testPath)); + } + return paths; + } + + getVirtualPath(sourceFilePath) { + const sourcePath = this.getSourcePath(); + if (sourceFilePath.startsWith(sourcePath)) { + const relSourceFilePath = fsPath.relative(sourcePath, sourceFilePath); + let virBasePath = "/resources/"; + if (!this._isSourceNamespaced) { + virBasePath += `${this._namespace}/`; + } + return posixPath.join(virBasePath, relSourceFilePath); + } + + const testPath = fsPath.join(this.getRootPath(), this._testPath); + if (sourceFilePath.startsWith(testPath)) { + const relSourceFilePath = fsPath.relative(testPath, sourceFilePath); + let virBasePath = "/test-resources/"; + if (!this._isSourceNamespaced) { + virBasePath += `${this._namespace}/`; + } + return posixPath.join(virBasePath, relSourceFilePath); + } + + throw new Error( + `Unable to convert source path ${sourceFilePath} to virtual path for project ${this.getName()}`); + } + /* === Resource Access === */ /** * Get a resource reader for the sources of the project (excluding any test resources) @@ -156,13 +189,13 @@ class Library extends ComponentProject { /** * @private * @param {object} config Configuration object - * @param {object} buildDescription Cache metadata object + * @param {object} buildManifest Cache metadata object */ - async _parseConfiguration(config, buildDescription) { - await super._parseConfiguration(config, buildDescription); + async _parseConfiguration(config, buildManifest) { + await super._parseConfiguration(config, buildManifest); - if (buildDescription) { - this._namespace = buildDescription.namespace; + if (buildManifest) { + this._namespace = buildManifest.namespace; return; } diff --git a/packages/project/lib/specifications/types/Module.js b/packages/project/lib/specifications/types/Module.js index 69c5987c9d8..201dccfe130 100644 --- a/packages/project/lib/specifications/types/Module.js +++ b/packages/project/lib/specifications/types/Module.js @@ -16,7 +16,6 @@ class Module extends Project { super(parameters); this._paths = null; - this._writer = null; } /* === Attributes === */ @@ -31,44 +30,39 @@ class Module extends Project { throw new Error(`Projects of type module have more than one source path`); } + getSourcePaths() { + return this._paths.map(({fsBasePath}) => { + return fsBasePath; + }); + } + /* === Resource Access === */ - /** - * Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the - * project in the specified "style": - * - *
    - *
  • buildtime: Resource paths are always prefixed with /resources/ - * or /test-resources/ followed by the project's namespace. - * Any configured build-excludes are applied
  • - *
  • dist: Resource paths always match with what the UI5 runtime expects. - * This means that paths generally depend on the project type. Applications for example use a "flat"-like - * structure, while libraries use a "buildtime"-like structure. - * Any configured build-excludes are applied
  • - *
  • runtime: Resource paths always match with what the UI5 runtime expects. - * This means that paths generally depend on the project type. Applications for example use a "flat"-like - * structure, while libraries use a "buildtime"-like structure. - * This style is typically used for serving resources directly. Therefore, build-excludes are not applied
  • - *
  • flat: Resource paths are never prefixed and namespaces are omitted if possible. Note that - * project types like "theme-library", which can have multiple namespaces, can't omit them. - * Any configured build-excludes are applied
  • - *
- * - * If project resources have been changed through the means of a workspace, those changes - * are reflected in the provided reader too. - * - * Resource readers always use POSIX-style paths. - * - * @public - * @param {object} [options] - * @param {string} [options.style=buildtime] Path style to access resources. - * Can be "buildtime", "dist", "runtime" or "flat" - * @returns {@ui5/fs/ReaderCollection} A reader collection instance - */ - getReader({style = "buildtime"} = {}) { + _getStyledReader(style) { // Apply builder excludes to all styles but "runtime" const excludes = style === "runtime" ? [] : this.getBuilderResourcesExcludes(); + return this._getReader(excludes); + } + + // /** + // * Get a resource reader/writer for accessing and modifying a project's resources + // * + // * @public + // * @returns {@ui5/fs/ReaderCollection} A reader collection instance + // */ + // getWorkspace() { + // const excludes = this.getBuilderResourcesExcludes(); + // const reader = this._getReader(excludes); + + // const writer = this._createWriter(); + // return resourceFactory.createWorkspace({ + // reader, + // writer + // }); + // } + + _getReader(excludes) { const readers = this._paths.map(({name, virBasePath, fsBasePath}) => { return resourceFactory.createReader({ name, @@ -81,42 +75,18 @@ class Module extends Project { if (readers.length === 1) { return readers[0]; } - const readerCollection = resourceFactory.createReaderCollection({ + return resourceFactory.createReaderCollection({ name: `Reader collection for module project ${this.getName()}`, readers }); - return resourceFactory.createReaderCollectionPrioritized({ - name: `Reader/Writer collection for project ${this.getName()}`, - readers: [this._getWriter(), readerCollection] - }); } - /** - * Get a resource reader/writer for accessing and modifying a project's resources - * - * @public - * @returns {@ui5/fs/ReaderCollection} A reader collection instance - */ - getWorkspace() { - const reader = this.getReader(); - - const writer = this._getWriter(); - return resourceFactory.createWorkspace({ - reader, - writer + _createWriter() { + return resourceFactory.createAdapter({ + virBasePath: "/" }); } - _getWriter() { - if (!this._writer) { - this._writer = resourceFactory.createAdapter({ - virBasePath: "/" - }); - } - - return this._writer; - } - /* === Internals === */ /** * @private diff --git a/packages/project/lib/specifications/types/ThemeLibrary.js b/packages/project/lib/specifications/types/ThemeLibrary.js index 398e570cdfb..c9c00dc6cea 100644 --- a/packages/project/lib/specifications/types/ThemeLibrary.js +++ b/packages/project/lib/specifications/types/ThemeLibrary.js @@ -18,7 +18,6 @@ class ThemeLibrary extends Project { this._srcPath = "src"; this._testPath = "test"; this._testPathExists = false; - this._writer = null; } /* === Attributes === */ @@ -39,43 +38,52 @@ class ThemeLibrary extends Project { return fsPath.join(this.getRootPath(), this._srcPath); } + getSourcePaths() { + return [this.getSourcePath()]; + } + + getVirtualPath(sourceFilePath) { + const sourcePath = this.getSourcePath(); + if (sourceFilePath.startsWith(sourcePath)) { + const relSourceFilePath = fsPath.relative(sourcePath, sourceFilePath); + return `/resources/${relSourceFilePath}`; + } + + throw new Error( + `Unable to convert source path ${sourceFilePath} to virtual path for project ${this.getName()}`); + } + /* === Resource Access === */ - /** - * Get a [ReaderCollection]{@link @ui5/fs/ReaderCollection} for accessing all resources of the - * project in the specified "style": - * - *
    - *
  • buildtime: Resource paths are always prefixed with /resources/ - * or /test-resources/ followed by the project's namespace. - * Any configured build-excludes are applied
  • - *
  • dist: Resource paths always match with what the UI5 runtime expects. - * This means that paths generally depend on the project type. Applications for example use a "flat"-like - * structure, while libraries use a "buildtime"-like structure. - * Any configured build-excludes are applied
  • - *
  • runtime: Resource paths always match with what the UI5 runtime expects. - * This means that paths generally depend on the project type. Applications for example use a "flat"-like - * structure, while libraries use a "buildtime"-like structure. - * This style is typically used for serving resources directly. Therefore, build-excludes are not applied
  • - *
  • flat: Resource paths are never prefixed and namespaces are omitted if possible. Note that - * project types like "theme-library", which can have multiple namespaces, can't omit them. - * Any configured build-excludes are applied
  • - *
- * - * If project resources have been changed through the means of a workspace, those changes - * are reflected in the provided reader too. - * - * Resource readers always use POSIX-style paths. - * - * @public - * @param {object} [options] - * @param {string} [options.style=buildtime] Path style to access resources. - * Can be "buildtime", "dist", "runtime" or "flat" - * @returns {@ui5/fs/ReaderCollection} A reader collection instance - */ - getReader({style = "buildtime"} = {}) { + + _getStyledReader(style) { // Apply builder excludes to all styles but "runtime" const excludes = style === "runtime" ? [] : this.getBuilderResourcesExcludes(); + return this._getReader(excludes); + } + + // /** + // * Get a [DuplexCollection]{@link @ui5/fs/DuplexCollection} for accessing and modifying a + // * project's resources. + // * + // * This is always of style buildtime, which for theme libraries is identical to style + // * runtime. + // * + // * @public + // * @returns {@ui5/fs/DuplexCollection} DuplexCollection + // */ + // getWorkspace() { + // const excludes = this.getBuilderResourcesExcludes(); + // const reader = this._getReader(excludes); + + // const writer = this._createWriter(); + // return resourceFactory.createWorkspace({ + // reader, + // writer + // }); + // } + + _getReader(excludes) { let reader = resourceFactory.createReader({ fsBasePath: this.getSourcePath(), virBasePath: "/resources/", @@ -96,45 +104,16 @@ class ThemeLibrary extends Project { readers: [reader, testReader] }); } - const writer = this._getWriter(); - - return resourceFactory.createReaderCollectionPrioritized({ - name: `Reader/Writer collection for project ${this.getName()}`, - readers: [writer, reader] - }); + return reader; } - /** - * Get a [DuplexCollection]{@link @ui5/fs/DuplexCollection} for accessing and modifying a - * project's resources. - * - * This is always of style buildtime, wich for theme libraries is identical to style - * runtime. - * - * @public - * @returns {@ui5/fs/DuplexCollection} DuplexCollection - */ - getWorkspace() { - const reader = this.getReader(); - - const writer = this._getWriter(); - return resourceFactory.createWorkspace({ - reader, - writer + _createWriter() { + return resourceFactory.createAdapter({ + virBasePath: "/", + project: this }); } - _getWriter() { - if (!this._writer) { - this._writer = resourceFactory.createAdapter({ - virBasePath: "/", - project: this - }); - } - - return this._writer; - } - /* === Internals === */ /** * @private diff --git a/packages/project/lib/utils/sanitizeFileName.js b/packages/project/lib/utils/sanitizeFileName.js new file mode 100644 index 00000000000..8950705de2c --- /dev/null +++ b/packages/project/lib/utils/sanitizeFileName.js @@ -0,0 +1,44 @@ +import path from "node:path"; + +const forbiddenCharsRegex = /[^0-9a-zA-Z\-._]/g; +const windowsReservedNames = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(\..*)?$/i; + +/** + * Sanitize a file name by replacing any characters not matching the allowed set with a dash. + * Additionally validate that the file name to make sure it is safe to use on various file systems. + * + * @param {string} fileName The file name to validate + * @returns {string} The sanitized file name + * @throws {Error} If the file name is empty, starts with a dot, contains a reserved value, or is too long + */ +export default function sanitizeFileName(fileName) { + if (!fileName) { + throw new Error("Illegal empty file name"); + } + if (fileName.startsWith(".")) { + throw new Error(`Illegal file name starting with a dot: ${fileName}`); + } + fileName = fileName.replaceAll(forbiddenCharsRegex, "-"); + + if (fileName.length > 255) { + throw new Error(`Illegal file name exceeding maximum length of 255 characters: ${fileName}`); + } + + if (windowsReservedNames.test(fileName)) { + throw new Error(`Illegal file name reserved on Windows systems: ${fileName}`); + } + + return fileName; +} + +export function getPathFromPackageName(pkgName) { + // If pkgName starts with a scope, that becomes a folder + if (pkgName.startsWith("@") && pkgName.includes("/")) { + // Split at first slash to get the scope and sanitize it without the "@" + const scope = sanitizeFileName(pkgName.substring(1, pkgName.indexOf("/"))); + // Get the rest of the package name + const pkg = pkgName.substring(pkgName.indexOf("/") + 1); + return path.join(`@${sanitizeFileName(scope)}`, sanitizeFileName(pkg)); + } + return sanitizeFileName(pkgName); +} diff --git a/packages/project/package.json b/packages/project/package.json index 64bec2c55c0..dccb49e05fc 100644 --- a/packages/project/package.json +++ b/packages/project/package.json @@ -61,13 +61,16 @@ "@ui5/logger": "^5.0.0-alpha.3", "ajv": "^6.12.6", "ajv-errors": "^1.0.1", + "cacache": "^20.0.3", "chalk": "^5.6.2", + "chokidar": "^3.6.0", "escape-string-regexp": "^5.0.0", "globby": "^14.1.0", "graceful-fs": "^4.2.11", "js-yaml": "^4.1.1", "lockfile": "^1.0.4", "make-fetch-happen": "^15.0.3", + "micromatch": "^4.0.8", "node-stream-zip": "^1.15.0", "pacote": "^21.0.4", "pretty-hrtime": "^1.0.3", diff --git a/packages/project/test/fixtures/application.a/custom-tasks-2/custom-task-0.js b/packages/project/test/fixtures/application.a/custom-tasks-2/custom-task-0.js new file mode 100644 index 00000000000..78495298e3a --- /dev/null +++ b/packages/project/test/fixtures/application.a/custom-tasks-2/custom-task-0.js @@ -0,0 +1,22 @@ +const Logger = require("@ui5/logger"); +const log = Logger.getLogger("builder:tasks:customTask0"); + +let buildRanOnce; +module.exports = async function ({ + workspace, taskUtil, + options: {projectNamespace} +}) { + log.verbose("Custom task 0 executed"); + + // Read a file to trigger execution of this task: + const testJS = await workspace.byPath(`/resources/${projectNamespace}/test.js`); + + if (buildRanOnce != true) { + log.verbose("Flag NOT set -> We are in #1 Build still"); + buildRanOnce = true; + taskUtil.setTag(testJS, taskUtil.STANDARD_TAGS.IsDebugVariant); + } else { + log.verbose("Flag set -> We are in #2 Build"); + taskUtil.setTag(testJS, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + } +}; diff --git a/packages/project/test/fixtures/application.a/custom-tasks-2/custom-task-1.js b/packages/project/test/fixtures/application.a/custom-tasks-2/custom-task-1.js new file mode 100644 index 00000000000..98f8b94c848 --- /dev/null +++ b/packages/project/test/fixtures/application.a/custom-tasks-2/custom-task-1.js @@ -0,0 +1,32 @@ +const Logger = require("@ui5/logger"); +const log = Logger.getLogger("builder:tasks:customTask1"); + +let buildRanOnce; +module.exports = async function ({ + workspace, taskUtil, + options: {projectNamespace} +}) { + log.verbose("Custom task 1 executed"); + + // Read a file to trigger execution of this task: + const testJS = await workspace.byPath(`/resources/${projectNamespace}/test.js`); + + if (buildRanOnce != true) { + log.verbose("Flag NOT set -> We are in #1 Build still"); + buildRanOnce = true; + const tag = taskUtil.getTag(testJS, taskUtil.STANDARD_TAGS.IsDebugVariant); + if (!tag) { + throw new Error("Tag set during #1 Build is not readable, which is UNEXPECTED."); + } + } else { + const previousTag = taskUtil.getTag(testJS, taskUtil.STANDARD_TAGS.IsDebugVariant); + if (previousTag) { + throw new Error("Tag set during #1 Build is still readable, which is UNEXPECTED."); + } + log.verbose("Flag set -> We are in #2 Build"); + const tag = taskUtil.getTag(testJS, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + if (!tag) { + throw new Error("Tag set during #2 Build is not readable, which is UNEXPECTED."); + } + } +}; diff --git a/packages/project/test/fixtures/application.a/custom-tasks/custom-task-0.js b/packages/project/test/fixtures/application.a/custom-tasks/custom-task-0.js new file mode 100644 index 00000000000..753a5fbc1e9 --- /dev/null +++ b/packages/project/test/fixtures/application.a/custom-tasks/custom-task-0.js @@ -0,0 +1,19 @@ +module.exports = async function ({ + workspace, taskUtil, + options: {projectNamespace} +}) { + console.log("Custom task 0 executed"); + + // Read a file which is an input of custom-task-1 (which sets a tag on it): + const testJS = await workspace.byPath(`/resources/${projectNamespace}/test.js`); + + const tag = taskUtil.getTag(testJS, taskUtil.STANDARD_TAGS.IsDebugVariant); + // For #1 & #3 build: + if (tag) { + throw new Error("Tag set by custom-task-1 is present in custom-task-0, which is UNEXPECTED."); + } + + // For #3 build: Read a different file which is not an input of custom-task-1 + // (ensures that this task is executed): + const test2JS = await workspace.byPath(`/resources/${projectNamespace}/test2.js`); +}; diff --git a/packages/project/test/fixtures/application.a/custom-tasks/custom-task-1.js b/packages/project/test/fixtures/application.a/custom-tasks/custom-task-1.js new file mode 100644 index 00000000000..a2a992c9653 --- /dev/null +++ b/packages/project/test/fixtures/application.a/custom-tasks/custom-task-1.js @@ -0,0 +1,12 @@ +module.exports = async function ({ + workspace, taskUtil, + options: {projectNamespace} +}) { + console.log("Custom task 1 executed"); + + // Set a tag on a specific resource: + const resource = await workspace.byPath(`/resources/${projectNamespace}/test.js`); + if (resource) { + taskUtil.setTag(resource, taskUtil.STANDARD_TAGS.IsDebugVariant); + }; +}; diff --git a/packages/project/test/fixtures/application.a/custom-tasks/custom-task-2.js b/packages/project/test/fixtures/application.a/custom-tasks/custom-task-2.js new file mode 100644 index 00000000000..5cb3723e5f1 --- /dev/null +++ b/packages/project/test/fixtures/application.a/custom-tasks/custom-task-2.js @@ -0,0 +1,19 @@ +module.exports = async function ({ + workspace, taskUtil, + options: {projectNamespace} +}) { + console.log("Custom task 2 executed"); + + // Read a file which is an input of custom-task-1 (which sets a tag on it): + const testJS = await workspace.byPath(`/resources/${projectNamespace}/test.js`); + + const tag = taskUtil.getTag(testJS, taskUtil.STANDARD_TAGS.IsDebugVariant); + // For #1 & #3 build: + if (!tag) { + throw new Error("Tag set by custom-task-1 is NOT present in custom-task-2, which is UNEXPECTED."); + } + + // For #3 build: Read a different file which is not an input of custom-task-1 + // (ensures that this task is executed): + const test2JS = await workspace.byPath(`/resources/${projectNamespace}/test2.js`); +}; diff --git a/packages/project/test/fixtures/application.a/race-condition-task.js b/packages/project/test/fixtures/application.a/race-condition-task.js new file mode 100644 index 00000000000..e70ed51ce7f --- /dev/null +++ b/packages/project/test/fixtures/application.a/race-condition-task.js @@ -0,0 +1,13 @@ +const {readFile, writeFile} = require("fs/promises"); +const path = require("path"); + +module.exports = async function ({ + workspace, taskUtil, + options: {projectNamespace} +}) { + const webappPath = taskUtil.getProject().getSourcePath(); + // Modify source file during build + const testFilePath = path.join(webappPath, "test.js"); + const originalContent = await readFile(testFilePath, {encoding: "utf8"}); + await writeFile(testFilePath, originalContent + `\nconsole.log("RACE CONDITION MODIFICATION");\n`); +}; diff --git a/packages/project/test/fixtures/application.a/task.dependency-change.js b/packages/project/test/fixtures/application.a/task.dependency-change.js new file mode 100644 index 00000000000..118e74b1cb1 --- /dev/null +++ b/packages/project/test/fixtures/application.a/task.dependency-change.js @@ -0,0 +1,36 @@ +// This is a modified version of the compileLicenseSummary example of the UI5 CLI documentation. +// (https://github.com/UI5/cli/blob/b72919469d856508dd757ecf325a5fb45f15e56d/internal/documentation/docs/pages/extensibility/CustomTasks.md#example-libtaskscompilelicensesummaryjs) + +module.exports = async function ({log, taskUtil, workspace}) { + const {createResource} = taskUtil.resourceFactory; + const projectsVisited = new Set(); + + async function processProject() { + return Promise.all(taskUtil.getDependencies().map(async (projectName) => { + if (projectName !== "library.d") { + return; + } + if (projectsVisited.has(projectName)) { + return; + } + projectsVisited.add(projectName); + const project = taskUtil.getProject(projectName); + const newLibraryFile = await project.getReader().byGlob("**/newLibraryFile.js"); + if (newLibraryFile.length > 0) { + log.verbose('New Library file found. We are in #4 build.'); + // Change content of application.a: + const applicationResource = await workspace.byPath("/resources/id1/test.js"); + const content = (await applicationResource.getString()) + "\n console.log('something new');"; + await workspace.write(createResource({ + path: "/test.js", + string: content + })); + } else { + log.verbose(`New Library file not found. We are still in an earlier build.`); + } + return processProject(project); + })); + } + // Start processing dependencies of the root project + await processProject(taskUtil.getProject()); +}; diff --git a/packages/project/test/fixtures/application.a/task.example.js b/packages/project/test/fixtures/application.a/task.example.js new file mode 100644 index 00000000000..efc4d0f12d9 --- /dev/null +++ b/packages/project/test/fixtures/application.a/task.example.js @@ -0,0 +1,12 @@ +module.exports = async function ({ + workspace, taskUtil, + options: {projectNamespace} +}) { + console.log("Example task executed"); + + // Omit a specific resource from the build result + const omittedResource = await workspace.byPath(`/resources/${projectNamespace}/fileToBeOmitted.js`); + if (omittedResource) { + taskUtil.setTag(omittedResource, taskUtil.STANDARD_TAGS.OmitFromBuildResult); + }; +}; diff --git a/packages/project/test/fixtures/application.a/ui5-custom-preload-config.yaml b/packages/project/test/fixtures/application.a/ui5-custom-preload-config.yaml new file mode 100644 index 00000000000..41a5a497fe4 --- /dev/null +++ b/packages/project/test/fixtures/application.a/ui5-custom-preload-config.yaml @@ -0,0 +1,11 @@ +--- +specVersion: "5.0" +type: application +metadata: + name: application.a +builder: + componentPreload: + namespaces: + - "id1" + excludes: + - "id1/thirdparty/scriptWithSourceMap.js" \ No newline at end of file diff --git a/packages/project/test/fixtures/application.a/ui5-customTask-dependency-change.yaml b/packages/project/test/fixtures/application.a/ui5-customTask-dependency-change.yaml new file mode 100644 index 00000000000..fa7743f34bf --- /dev/null +++ b/packages/project/test/fixtures/application.a/ui5-customTask-dependency-change.yaml @@ -0,0 +1,18 @@ +--- +specVersion: "2.3" +type: application +metadata: + name: application.a +builder: + customTasks: + - name: dependency-change + afterTask: minify +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: dependency-change +task: + path: task.dependency-change.js + diff --git a/packages/project/test/fixtures/application.a/ui5-customTask.yaml b/packages/project/test/fixtures/application.a/ui5-customTask.yaml new file mode 100644 index 00000000000..3c44bbf65c7 --- /dev/null +++ b/packages/project/test/fixtures/application.a/ui5-customTask.yaml @@ -0,0 +1,17 @@ +--- +specVersion: "5.0" +type: application +metadata: + name: application.a +builder: + customTasks: + - name: example-task + afterTask: minify +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: example-task +task: + path: task.example.js diff --git a/packages/project/test/fixtures/application.a/ui5-multiple-customTasks-2.yaml b/packages/project/test/fixtures/application.a/ui5-multiple-customTasks-2.yaml new file mode 100644 index 00000000000..0e8f71305b2 --- /dev/null +++ b/packages/project/test/fixtures/application.a/ui5-multiple-customTasks-2.yaml @@ -0,0 +1,27 @@ +--- +specVersion: "5.0" +type: application +metadata: + name: application.a +builder: + customTasks: + - name: custom-task-0 + afterTask: minify + - name: custom-task-1 + afterTask: custom-task-0 +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: custom-task-0 +task: + path: custom-tasks-2/custom-task-0.js +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: custom-task-1 +task: + path: custom-tasks-2/custom-task-1.js \ No newline at end of file diff --git a/packages/project/test/fixtures/application.a/ui5-multiple-customTasks.yaml b/packages/project/test/fixtures/application.a/ui5-multiple-customTasks.yaml new file mode 100644 index 00000000000..bb99285b85c --- /dev/null +++ b/packages/project/test/fixtures/application.a/ui5-multiple-customTasks.yaml @@ -0,0 +1,37 @@ +--- +specVersion: "5.0" +type: application +metadata: + name: application.a +builder: + customTasks: + - name: custom-task-0 + afterTask: minify + - name: custom-task-1 + afterTask: custom-task-0 + - name: custom-task-2 + afterTask: custom-task-1 +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: custom-task-0 +task: + path: custom-tasks/custom-task-0.js +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: custom-task-1 +task: + path: custom-tasks/custom-task-1.js +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: custom-task-2 +task: + path: custom-tasks/custom-task-2.js \ No newline at end of file diff --git a/packages/project/test/fixtures/application.a/ui5-race-condition.yaml b/packages/project/test/fixtures/application.a/ui5-race-condition.yaml new file mode 100644 index 00000000000..aa6d48ec208 --- /dev/null +++ b/packages/project/test/fixtures/application.a/ui5-race-condition.yaml @@ -0,0 +1,17 @@ +--- +specVersion: "5.0" +type: application +metadata: + name: application.a +builder: + customTasks: + - name: race-condition-task + afterTask: escapeNonAsciiCharacters +--- +specVersion: "5.0" +kind: extension +type: task +metadata: + name: race-condition-task +task: + path: race-condition-task.js diff --git a/packages/project/test/fixtures/application.a/webapp/thirdparty/scriptWithSourceMap.js b/packages/project/test/fixtures/application.a/webapp/thirdparty/scriptWithSourceMap.js new file mode 100644 index 00000000000..5c633c08703 --- /dev/null +++ b/packages/project/test/fixtures/application.a/webapp/thirdparty/scriptWithSourceMap.js @@ -0,0 +1,2 @@ +console.log("This is a script with a source map."); +//# sourceMappingURL=scriptWithSourceMap.js.map diff --git a/packages/project/test/fixtures/application.a/webapp/thirdparty/scriptWithSourceMap.js.map b/packages/project/test/fixtures/application.a/webapp/thirdparty/scriptWithSourceMap.js.map new file mode 100644 index 00000000000..5e28dc9849d --- /dev/null +++ b/packages/project/test/fixtures/application.a/webapp/thirdparty/scriptWithSourceMap.js.map @@ -0,0 +1 @@ +{"version":3,"file":"scriptWithSourceMap.js","names":["console","log"],"sources":["scriptWithSourceMap.ts"],"sourcesContent":["console.log(\"This is a script with a source map.\");\n"],"mappings":"AAAAA,QAAQC,IAAI","ignoreList":[]} diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.a/package.json b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/package.json new file mode 100644 index 00000000000..2179673d41d --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/package.json @@ -0,0 +1,17 @@ +{ + "name": "library.a", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "ui5": { + "name": "library.a", + "type": "library", + "settings": { + "src": "src", + "test": "test" + } + } +} diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/.library b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/.library new file mode 100644 index 00000000000..25c8603f31a --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/.library @@ -0,0 +1,17 @@ + + + + library.a + SAP SE + ${copyright} + ${version} + + Library A + + + + library.d + + + + diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/themes/base/library.source.less b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/themes/base/library.source.less new file mode 100644 index 00000000000..ff0f1d5e3df --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/src/library/a/themes/base/library.source.less @@ -0,0 +1,6 @@ +@libraryAColor1: lightgoldenrodyellow; + +.library-a-foo { + color: @libraryAColor1; + padding: 1px 2px 3px 4px; +} diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.a/test/library/a/Test.html b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/test/library/a/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.a/ui5.yaml b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/ui5.yaml new file mode 100644 index 00000000000..8d4784313c3 --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.a/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.a diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.b/package.json b/packages/project/test/fixtures/component.a/node_modules/collection/library.b/package.json new file mode 100644 index 00000000000..2a0243b1683 --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.b/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.b", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.b/src/library/b/.library b/packages/project/test/fixtures/component.a/node_modules/collection/library.b/src/library/b/.library new file mode 100644 index 00000000000..36052acebdc --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.b/src/library/b/.library @@ -0,0 +1,17 @@ + + + + library.b + SAP SE + ${copyright} + ${version} + + Library B + + + + library.d + + + + diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.b/test/library/b/Test.html b/packages/project/test/fixtures/component.a/node_modules/collection/library.b/test/library/b/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.b/ui5.yaml b/packages/project/test/fixtures/component.a/node_modules/collection/library.b/ui5.yaml new file mode 100644 index 00000000000..b2fe5be59ee --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.b/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.b diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.c/package.json b/packages/project/test/fixtures/component.a/node_modules/collection/library.c/package.json new file mode 100644 index 00000000000..64ac75d6ffe --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.c/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.c", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.c/src/library/c/.library b/packages/project/test/fixtures/component.a/node_modules/collection/library.c/src/library/c/.library new file mode 100644 index 00000000000..4180ce2af2f --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.c/src/library/c/.library @@ -0,0 +1,17 @@ + + + + library.c + SAP SE + ${copyright} + ${version} + + Library C + + + + library.d + + + + diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.c/test/LibraryC/Test.html b/packages/project/test/fixtures/component.a/node_modules/collection/library.c/test/LibraryC/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/library.c/ui5.yaml b/packages/project/test/fixtures/component.a/node_modules/collection/library.c/ui5.yaml new file mode 100644 index 00000000000..7c5e38a7fc1 --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/library.c/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.c diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/package.json b/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/package.json new file mode 100644 index 00000000000..90c75040abe --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.d", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/src/library/d/.library b/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/src/library/d/.library new file mode 100644 index 00000000000..21251d1bbba --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/src/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + ${copyright} + ${version} + + Library D + + diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/test/library/d/Test.html b/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/test/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/ui5.yaml b/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/ui5.yaml new file mode 100644 index 00000000000..a47c1f64c3d --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/node_modules/library.d/ui5.yaml @@ -0,0 +1,10 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.d +resources: + configuration: + paths: + src: main/src + test: main/test diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/package.json b/packages/project/test/fixtures/component.a/node_modules/collection/package.json new file mode 100644 index 00000000000..81b948438bd --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/package.json @@ -0,0 +1,18 @@ +{ + "name": "collection", + "version": "1.0.0", + "description": "Simple Collection", + "dependencies": { + "library.d": "file:../library.d" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "collection": { + "modules": { + "library.a": "./library.a", + "library.b": "./library.b", + "library.c": "./library.c" + } + } +} diff --git a/packages/project/test/fixtures/component.a/node_modules/collection/ui5.yaml b/packages/project/test/fixtures/component.a/node_modules/collection/ui5.yaml new file mode 100644 index 00000000000..e47048de6a7 --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/collection/ui5.yaml @@ -0,0 +1,12 @@ +specVersion: "2.1" +metadata: + name: application.a.collection.dependency.shim +kind: extension +type: project-shim +shims: + collections: + collection: + modules: + "library.a": "./library.a" + "library.b": "./library.b" + "library.c": "./library.c" \ No newline at end of file diff --git a/packages/project/test/fixtures/component.a/node_modules/library.d/main/src/library/d/.library b/packages/project/test/fixtures/component.a/node_modules/library.d/main/src/library/d/.library new file mode 100644 index 00000000000..53c2d14c9d6 --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/library.d/main/src/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + Some fancy copyright + ${version} + + Library D + + diff --git a/packages/project/test/fixtures/component.a/node_modules/library.d/main/src/library/d/some.js b/packages/project/test/fixtures/component.a/node_modules/library.d/main/src/library/d/some.js new file mode 100644 index 00000000000..81e73436075 --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/library.d/main/src/library/d/some.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); \ No newline at end of file diff --git a/packages/project/test/fixtures/component.a/node_modules/library.d/main/test/library/d/Test.html b/packages/project/test/fixtures/component.a/node_modules/library.d/main/test/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/component.a/node_modules/library.d/package.json b/packages/project/test/fixtures/component.a/node_modules/library.d/package.json new file mode 100644 index 00000000000..90c75040abe --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/library.d/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.d", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/component.a/node_modules/library.d/ui5.yaml b/packages/project/test/fixtures/component.a/node_modules/library.d/ui5.yaml new file mode 100644 index 00000000000..a47c1f64c3d --- /dev/null +++ b/packages/project/test/fixtures/component.a/node_modules/library.d/ui5.yaml @@ -0,0 +1,10 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.d +resources: + configuration: + paths: + src: main/src + test: main/test diff --git a/packages/project/test/fixtures/component.a/ui5-custom-preload-config.yaml b/packages/project/test/fixtures/component.a/ui5-custom-preload-config.yaml new file mode 100644 index 00000000000..9d1ef25beca --- /dev/null +++ b/packages/project/test/fixtures/component.a/ui5-custom-preload-config.yaml @@ -0,0 +1,11 @@ +--- +specVersion: "5.0" +type: component +metadata: + name: component.a +builder: + componentPreload: + namespaces: + - "id1" + excludes: + - "id1/test.js" \ No newline at end of file diff --git a/packages/project/test/fixtures/library.d/main/src/library/d/.library b/packages/project/test/fixtures/library.d/main/src/library/d/.library index 53c2d14c9d6..21251d1bbba 100644 --- a/packages/project/test/fixtures/library.d/main/src/library/d/.library +++ b/packages/project/test/fixtures/library.d/main/src/library/d/.library @@ -3,7 +3,7 @@ library.d SAP SE - Some fancy copyright + ${copyright} ${version} Library D diff --git a/packages/project/test/fixtures/library.d/ui5-custom-preload-config.yaml b/packages/project/test/fixtures/library.d/ui5-custom-preload-config.yaml new file mode 100644 index 00000000000..6187b116068 --- /dev/null +++ b/packages/project/test/fixtures/library.d/ui5-custom-preload-config.yaml @@ -0,0 +1,15 @@ +--- +specVersion: "5.0" +type: library +metadata: + name: library.d + copyright: Some fancy copyright +resources: + configuration: + paths: + src: main/src + test: main/test +builder: + libraryPreload: + excludes: + - "library/d/some.js" \ No newline at end of file diff --git a/packages/project/test/fixtures/library.d/ui5.yaml b/packages/project/test/fixtures/library.d/ui5.yaml index a47c1f64c3d..9d1317fba3f 100644 --- a/packages/project/test/fixtures/library.d/ui5.yaml +++ b/packages/project/test/fixtures/library.d/ui5.yaml @@ -3,6 +3,7 @@ specVersion: "2.3" type: library metadata: name: library.d + copyright: Some fancy copyright resources: configuration: paths: diff --git a/packages/project/test/fixtures/module.b/dev/devTools.js b/packages/project/test/fixtures/module.b/dev/devTools.js new file mode 100644 index 00000000000..e035bfaeab6 --- /dev/null +++ b/packages/project/test/fixtures/module.b/dev/devTools.js @@ -0,0 +1 @@ +console.log("dev dev dev"); diff --git a/packages/project/test/fixtures/module.b/node_modules/.package-lock.json b/packages/project/test/fixtures/module.b/node_modules/.package-lock.json new file mode 100644 index 00000000000..ba2e1378c35 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/.package-lock.json @@ -0,0 +1,28 @@ +{ + "name": "module.b", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "../library.d": { + "version": "1.0.0", + "extraneous": true + }, + "node_modules/collection": { + "version": "1.0.0", + "resolved": "file:../collection", + "workspaces": [ + "library.a", + "library.b", + "library.c" + ], + "dependencies": { + "library.d": "file:../library.d" + } + }, + "node_modules/library.d": { + "version": "1.0.0", + "resolved": "file:../library.d" + } + } +} diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.a/package.json b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/package.json new file mode 100644 index 00000000000..aec498f7283 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.a", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.a/src/library/a/.library b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/src/library/a/.library new file mode 100644 index 00000000000..ef0ea1065bc --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/src/library/a/.library @@ -0,0 +1,17 @@ + + + + library.a + SAP SE + Some fancy copyright ${currentYear} + ${version} + + Library A + + + + library.d + + + + diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.a/src/library/a/themes/base/library.source.less b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/src/library/a/themes/base/library.source.less new file mode 100644 index 00000000000..ff0f1d5e3df --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/src/library/a/themes/base/library.source.less @@ -0,0 +1,6 @@ +@libraryAColor1: lightgoldenrodyellow; + +.library-a-foo { + color: @libraryAColor1; + padding: 1px 2px 3px 4px; +} diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.a/test/library/a/Test.html b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/test/library/a/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.a/ui5.yaml b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/ui5.yaml new file mode 100644 index 00000000000..8d4784313c3 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.a/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.a diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.b/package.json b/packages/project/test/fixtures/module.b/node_modules/collection/library.b/package.json new file mode 100644 index 00000000000..2a0243b1683 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.b/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.b", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.b/src/library/b/.library b/packages/project/test/fixtures/module.b/node_modules/collection/library.b/src/library/b/.library new file mode 100644 index 00000000000..7128151f3f4 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.b/src/library/b/.library @@ -0,0 +1,17 @@ + + + + library.b + SAP SE + Some fancy copyright ${currentYear} + ${version} + + Library B + + + + library.d + + + + diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.b/test/library/b/Test.html b/packages/project/test/fixtures/module.b/node_modules/collection/library.b/test/library/b/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.b/ui5.yaml b/packages/project/test/fixtures/module.b/node_modules/collection/library.b/ui5.yaml new file mode 100644 index 00000000000..b2fe5be59ee --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.b/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.b diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.c/package.json b/packages/project/test/fixtures/module.b/node_modules/collection/library.c/package.json new file mode 100644 index 00000000000..64ac75d6ffe --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.c/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.c", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.c/src/library/c/.library b/packages/project/test/fixtures/module.b/node_modules/collection/library.c/src/library/c/.library new file mode 100644 index 00000000000..4180ce2af2f --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.c/src/library/c/.library @@ -0,0 +1,17 @@ + + + + library.c + SAP SE + ${copyright} + ${version} + + Library C + + + + library.d + + + + diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.c/test/LibraryC/Test.html b/packages/project/test/fixtures/module.b/node_modules/collection/library.c/test/LibraryC/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/library.c/ui5.yaml b/packages/project/test/fixtures/module.b/node_modules/collection/library.c/ui5.yaml new file mode 100644 index 00000000000..7c5e38a7fc1 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/library.c/ui5.yaml @@ -0,0 +1,5 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.c diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/package.json b/packages/project/test/fixtures/module.b/node_modules/collection/package.json new file mode 100644 index 00000000000..24849dbe4a8 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/package.json @@ -0,0 +1,16 @@ +{ + "name": "collection", + "version": "1.0.0", + "description": "Simple Collection", + "dependencies": { + "library.d": "file:../library.d" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "workspaces": [ + "library.a", + "library.b", + "library.c" + ] +} diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/test.js b/packages/project/test/fixtures/module.b/node_modules/collection/test.js new file mode 100644 index 00000000000..d063db1e726 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/test.js @@ -0,0 +1,4 @@ +import {globby} from 'globby'; + +const paths = await globby(["library.a"]); +console.log("paths") diff --git a/packages/project/test/fixtures/module.b/node_modules/collection/ui5.yaml b/packages/project/test/fixtures/module.b/node_modules/collection/ui5.yaml new file mode 100644 index 00000000000..e47048de6a7 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/collection/ui5.yaml @@ -0,0 +1,12 @@ +specVersion: "2.1" +metadata: + name: application.a.collection.dependency.shim +kind: extension +type: project-shim +shims: + collections: + collection: + modules: + "library.a": "./library.a" + "library.b": "./library.b" + "library.c": "./library.c" \ No newline at end of file diff --git a/packages/project/test/fixtures/module.b/node_modules/library.d/main/src/library/d/.library b/packages/project/test/fixtures/module.b/node_modules/library.d/main/src/library/d/.library new file mode 100644 index 00000000000..21251d1bbba --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/library.d/main/src/library/d/.library @@ -0,0 +1,11 @@ + + + + library.d + SAP SE + ${copyright} + ${version} + + Library D + + diff --git a/packages/project/test/fixtures/module.b/node_modules/library.d/main/src/library/d/some.js b/packages/project/test/fixtures/module.b/node_modules/library.d/main/src/library/d/some.js new file mode 100644 index 00000000000..81e73436075 --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/library.d/main/src/library/d/some.js @@ -0,0 +1,4 @@ +/*! + * ${copyright} + */ +console.log('HelloWorld'); \ No newline at end of file diff --git a/packages/project/test/fixtures/module.b/node_modules/library.d/main/test/library/d/Test.html b/packages/project/test/fixtures/module.b/node_modules/library.d/main/test/library/d/Test.html new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/project/test/fixtures/module.b/node_modules/library.d/package.json b/packages/project/test/fixtures/module.b/node_modules/library.d/package.json new file mode 100644 index 00000000000..90c75040abe --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/library.d/package.json @@ -0,0 +1,9 @@ +{ + "name": "library.d", + "version": "1.0.0", + "description": "Simple SAPUI5 based library", + "dependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + } +} diff --git a/packages/project/test/fixtures/module.b/node_modules/library.d/ui5.yaml b/packages/project/test/fixtures/module.b/node_modules/library.d/ui5.yaml new file mode 100644 index 00000000000..9d1317fba3f --- /dev/null +++ b/packages/project/test/fixtures/module.b/node_modules/library.d/ui5.yaml @@ -0,0 +1,11 @@ +--- +specVersion: "2.3" +type: library +metadata: + name: library.d + copyright: Some fancy copyright +resources: + configuration: + paths: + src: main/src + test: main/test diff --git a/packages/project/test/fixtures/module.b/package-lock.json b/packages/project/test/fixtures/module.b/package-lock.json new file mode 100644 index 00000000000..fcbbe63defc --- /dev/null +++ b/packages/project/test/fixtures/module.b/package-lock.json @@ -0,0 +1,36 @@ +{ + "name": "module.b", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "module.b", + "version": "1.0.0", + "dependencies": { + "collection": "file:../collection", + "library.d": "file:../library.d" + } + }, + "../library.d": { + "version": "1.0.0", + "extraneous": true + }, + "node_modules/collection": { + "version": "1.0.0", + "resolved": "file:../collection", + "workspaces": [ + "library.a", + "library.b", + "library.c" + ], + "dependencies": { + "library.d": "file:../library.d" + } + }, + "node_modules/library.d": { + "version": "1.0.0", + "resolved": "file:../library.d" + } + } +} diff --git a/packages/project/test/fixtures/module.b/package.json b/packages/project/test/fixtures/module.b/package.json new file mode 100644 index 00000000000..384989cb3da --- /dev/null +++ b/packages/project/test/fixtures/module.b/package.json @@ -0,0 +1,9 @@ +{ + "name": "module.b", + "version": "1.0.0", + "description": "Custom UI5 module", + "dependencies": { + "library.d": "file:../library.d", + "collection": "file:../collection" + } +} diff --git a/packages/project/test/fixtures/module.b/ui5.yaml b/packages/project/test/fixtures/module.b/ui5.yaml new file mode 100644 index 00000000000..f5365cf1f0b --- /dev/null +++ b/packages/project/test/fixtures/module.b/ui5.yaml @@ -0,0 +1,9 @@ +--- +specVersion: "5.0" +type: module +metadata: + name: module.b +resources: + configuration: + paths: + /resources/b/module/dev/: dev \ No newline at end of file diff --git a/packages/project/test/fixtures/theme.library.e/package.json b/packages/project/test/fixtures/theme.library.e/package.json new file mode 100644 index 00000000000..2315226524d --- /dev/null +++ b/packages/project/test/fixtures/theme.library.e/package.json @@ -0,0 +1,4 @@ +{ + "name": "theme.library.e", + "version": "1.0.0" +} diff --git a/packages/project/test/lib/build/BuildServer.integration.js b/packages/project/test/lib/build/BuildServer.integration.js new file mode 100644 index 00000000000..f8fcb3023af --- /dev/null +++ b/packages/project/test/lib/build/BuildServer.integration.js @@ -0,0 +1,465 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import {fileURLToPath} from "node:url"; +import {setTimeout} from "node:timers/promises"; +import fs from "node:fs/promises"; +import {graphFromPackageDependencies} from "../../../lib/graph/graph.js"; +import {setLogLevel} from "@ui5/logger"; + +// Ensures that all logging code paths are tested +setLogLevel("silly"); + +test.beforeEach((t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.logEventStub = sinon.stub(); + t.context.buildMetadataEventStub = sinon.stub(); + t.context.projectBuildMetadataEventStub = sinon.stub(); + t.context.buildStatusEventStub = sinon.stub(); + t.context.projectBuildStatusEventStub = sinon.stub(); + + process.on("ui5.log", t.context.logEventStub); + process.on("ui5.build-metadata", t.context.buildMetadataEventStub); + process.on("ui5.project-build-metadata", t.context.projectBuildMetadataEventStub); + process.on("ui5.build-status", t.context.buildStatusEventStub); + process.on("ui5.project-build-status", t.context.projectBuildStatusEventStub); +}); + +test.afterEach.always(async (t) => { + await t.context.fixtureTester.teardown(); + t.context.sinon.restore(); + delete process.env.UI5_DATA_DIR; + + process.off("ui5.log", t.context.logEventStub); + process.off("ui5.build-metadata", t.context.buildMetadataEventStub); + process.off("ui5.project-build-metadata", t.context.projectBuildMetadataEventStub); + process.off("ui5.build-status", t.context.buildStatusEventStub); + process.off("ui5.project-build-status", t.context.projectBuildStatusEventStub); +}); + +// Note: This test should be the first test to run, as it covers initial build scenarios, which are not reproducible +// once the BuildServer has been started and built a project at least once. +// This is independent of caching on file-system level, which is isolated per test via tmp folders. +test.serial("Serve application.a, initial file changes", async (t) => { + const fixtureTester = t.context.fixtureTester = new FixtureTester(t, "application.a"); + + await fixtureTester.serveProject(); + + // Directly change a source file in application.a before requesting it + const changedFilePath = `${fixtureTester.fixturePath}/webapp/test.js`; + await fs.appendFile(changedFilePath, `\ntest("initial change");\n`); + + // Request the changed resource immediately + const resourceRequestPromise = fixtureTester.requestResource({ + resource: "/test.js", + assertions: { + projects: { + "application.a": {} + } + } + }); + // Directly change the source file again, which should abort the current build and trigger a new one + await fs.appendFile(changedFilePath, `\ntest("second change");\n`); + await fs.appendFile(changedFilePath, `\ntest("third change");\n`); + + // Wait for the resource to be served + const resource = await resourceRequestPromise; + + // Check whether the change is reflected + const servedFileContent = await resource.getString(); + t.true(servedFileContent.includes(`test("initial change");`), "Resource contains initial changed file content"); + t.true(servedFileContent.includes(`test("second change");`), "Resource contains second changed file content"); + t.true(servedFileContent.includes(`test("third change");`), "Resource contains third changed file content"); +}); + +test.serial("Serve application.a, request application resource", async (t) => { + const fixtureTester = t.context.fixtureTester = new FixtureTester(t, "application.a"); + + // #1 request with empty cache + await fixtureTester.serveProject(); + await fixtureTester.requestResource({ + resource: "/test.js", + assertions: { + projects: { + "application.a": {} + } + } + }); + + // #2 request with cache + await fixtureTester.requestResource({ + resource: "/test.js", + assertions: { + projects: {} + } + }); + + // Change a source file in application.a + const changedFilePath = `${fixtureTester.fixturePath}/webapp/test.js`; + await fs.appendFile(changedFilePath, `\ntest("line added");\n`); + + await setTimeout(500); // Wait for the file watcher to detect and propagate the change + + // #3 request with cache and changes + const res = await fixtureTester.requestResource({ + resource: "/test.js", + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + // Note: replaceCopyright is skipped because no copyright is configured in the project + "replaceCopyright", + "enhanceManifest", + "generateFlexChangesBundle", + ] + } + } + } + }); + + // Check whether the changed file is in the destPath + const servedFileContent = await res.getString(); + t.true(servedFileContent.includes(`test("line added");`), "Resource contains changed file content"); +}); + +test.serial("Serve application.a, request library resource", async (t) => { + const fixtureTester = t.context.fixtureTester = new FixtureTester(t, "application.a"); + + // #1 request with empty cache + await fixtureTester.serveProject(); + await fixtureTester.requestResource({ + resource: "/resources/library/a/.library", + assertions: { + projects: { + "library.a": {} + } + } + }); + + // #2 request with cache + await fixtureTester.requestResource({ + resource: "/resources/library/a/.library", + assertions: { + projects: {} + } + }); + + // Change a source file in library.a + const changedFilePath = `${fixtureTester.fixturePath}/node_modules/collection/library.a/src/library/a/.library`; + await fs.writeFile( + changedFilePath, + (await fs.readFile(changedFilePath, {encoding: "utf8"})).replace( + `Library A`, + `Library A (updated #1)` + ) + ); + + await setTimeout(500); // Wait for the file watcher to detect and propagate the change + + // #3 request with cache and changes + const dotLibraryResource = await fixtureTester.requestResource({ + resource: "/resources/library/a/.library", + assertions: { + projects: { + "library.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + "minify", + "replaceBuildtime", + ] + } + } + } + }); + + // Check whether the changed file is served + const servedFileContent = await dotLibraryResource.getString(); + t.true( + servedFileContent.includes(`Library A (updated #1)`), + "Resource contains changed file content" + ); + + // #4 request with cache (no changes) + const manifestResource = await fixtureTester.requestResource({ + resource: "/resources/library/a/manifest.json", + assertions: { + projects: {} + } + }); + + // Check whether the manifest is served correctly with changed .library content reflected + const manifestContent = JSON.parse(await manifestResource.getString()); + t.is( + manifestContent["sap.app"]["description"], "Library A (updated #1)", + "Manifest reflects changed .library content" + ); +}); + +test.serial.skip("Serve library", async (t) => { + const fixtureTester = t.context.fixtureTester = new FixtureTester(t, "library.d"); + + // #1 request with empty cache + await fixtureTester.serveProject({ + config: { + excludedTasks: ["minify"], + } + }); + await fixtureTester.requestResource({ + resource: "/resources/library/d/some.js", + assertions: { + projects: { + "library.d": {} + } + } + }); + + // #2 request with cache + await fixtureTester.requestResource({ + resource: "/resources/library/d/some.js", + assertions: { + projects: {} + } + }); + + // Change a source file in library.d + const changedFilePath = `${fixtureTester.fixturePath}/main/src/library/d/some.js`; + const originalContent = await fs.readFile(changedFilePath, {encoding: "utf8"}); + await fs.writeFile( + changedFilePath, + originalContent.replace( + ` */`, + ` */\n// Test 1` + ) + ); + + await setTimeout(500); // Wait for the file watcher to detect and propagate the change + + // #3 request with cache and changes + const resourceContent1 = await fixtureTester.requestResource({ + resource: "/resources/library/d/some.js", + assertions: { + projects: { + "library.d": { + skippedTasks: [ + "buildThemes", + "enhanceManifest", + "escapeNonAsciiCharacters", + "replaceBuildtime", + ] + } + } + } + }); + + // Check whether the changed file is served + const servedFileContent1 = await resourceContent1.getString(); + t.true( + servedFileContent1.includes(`Test 1`), + "Resource contains changed file content" + ); + + // Restore original file content + + await fs.writeFile(changedFilePath, originalContent); + + // #4 request with cache (no changes) + const resourceContent2 = await fixtureTester.requestResource({ + resource: "/resources/library/d/some.js", + assertions: { + projects: {} + } + }); + + const servedFileContent2 = await resourceContent2.getString(); + t.false( + servedFileContent2.includes(`Test 1`), + "Resource does not contain changed file content" + ); +}); + +test.serial("Serve application.a, request application resource AND library resource", async (t) => { + const fixtureTester = t.context.fixtureTester = new FixtureTester(t, "application.a"); + + // #1 request with empty cache + await fixtureTester.serveProject(); + await fixtureTester.requestResources({ + resources: ["/test.js", "/resources/library/a/.library"], + assertions: { + projects: { + "library.a": {}, + "application.a": {} + } + } + }); + + // #2 request with cache + await fixtureTester.requestResources({ + resources: ["/test.js", "/resources/library/a/.library"], + assertions: { + projects: {} + } + }); + + // Change a source file in application.a and library.a + const changedFilePath = `${fixtureTester.fixturePath}/webapp/test.js`; + await fs.appendFile(changedFilePath, `\ntest("line added");\n`); + const changedFilePath2 = `${fixtureTester.fixturePath}/node_modules/collection/library.a/src/library/a/.library`; + await fs.writeFile( + changedFilePath2, + (await fs.readFile(changedFilePath2, {encoding: "utf8"})).replace( + `Library A`, + `Library A (updated #1)` + ) + ); + + await setTimeout(500); // Wait for the file watcher to detect and propagate the changes + + // #3 request with cache and changes + const [resource1, resource2] = await fixtureTester.requestResources({ + resources: ["/test.js", "/resources/library/a/.library"], + assertions: { + projects: { + "library.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + "minify", + "replaceBuildtime", + ] + }, + "application.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + // Note: replaceCopyright is skipped because no copyright is configured in the project + "replaceCopyright", + "enhanceManifest", + "generateFlexChangesBundle", + ] + } + } + } + }); + + // Check whether the changed files contain the correct contents + const resource1FileContent = await resource1.getString(); + const resource2FileContent = await resource2.getString(); + t.true(resource1FileContent.includes(`test("line added");`), "Resource contains changed file content"); + t.true( + resource2FileContent.includes(`Library A (updated #1)`), + "Resource contains changed file content" + ); +}); + +function getFixturePath(fixtureName) { + return fileURLToPath(new URL(`../../fixtures/${fixtureName}`, import.meta.url)); +} + +function getTmpPath(folderName) { + return fileURLToPath(new URL(`../../tmp/BuildServer/${folderName}`, import.meta.url)); +} + +async function rmrf(dirPath) { + return fs.rm(dirPath, {recursive: true, force: true}); +} + +class FixtureTester { + constructor(t, fixtureName) { + this._t = t; + this._sinon = t.context.sinon; + this._fixtureName = fixtureName; + this._initialized = false; + + // Public + this.fixturePath = getTmpPath(fixtureName); + this.buildServer = null; + this.graph = null; + } + + async _initialize() { + if (this._initialized) { + return; + } + process.env.UI5_DATA_DIR = getTmpPath(`${this._fixtureName}/.ui5`); + await rmrf(this.fixturePath); // Clean up any previous test runs + await fs.cp(getFixturePath(this._fixtureName), this.fixturePath, {recursive: true}); + this._initialized = true; + } + + async teardown() { + if (this.buildServer) { + await this.buildServer.destroy(); + } + } + + async serveProject({graphConfig = {}, config = {}} = {}) { + await this._initialize(); + + const graph = this.graph = await graphFromPackageDependencies({ + ...graphConfig, + cwd: this.fixturePath, + }); + + // Execute the build + this.buildServer = await graph.serve(config); + this.buildServer.on("error", (err) => { + this._t.fail(`Build server error: ${err.message}`); + }); + this._reader = this.buildServer.getReader(); + } + + async requestResource({resource, assertions = {}}) { + this._sinon.resetHistory(); + const res = await this._reader.byPath(resource); + // Apply assertions if provided + if (assertions) { + this._assertBuild(assertions); + } + return res; + } + + async requestResources({resources, assertions = {}}) { + this._sinon.resetHistory(); + const returnedResources = await Promise.all(resources.map((resource) => this._reader.byPath(resource))); + // Apply assertions if provided + if (assertions) { + this._assertBuild(assertions); + } + return returnedResources; + } + + _assertBuild(assertions) { + const {projects = {}} = assertions; + const eventArgs = this._t.context.projectBuildStatusEventStub.args.map((args) => args[0]); + + const projectsInOrder = []; + const seenProjects = new Set(); + const tasksByProject = {}; + + for (const event of eventArgs) { + if (!seenProjects.has(event.projectName)) { + projectsInOrder.push(event.projectName); + seenProjects.add(event.projectName); + } + if (!tasksByProject[event.projectName]) { + tasksByProject[event.projectName] = {executed: [], skipped: []}; + } + if (event.status === "task-skip") { + tasksByProject[event.projectName].skipped.push(event.taskName); + } else if (event.status === "task-start") { + tasksByProject[event.projectName].executed.push(event.taskName); + } + } + + // Assert projects built in order + const expectedProjects = Object.keys(projects); + this._t.deepEqual(projectsInOrder, expectedProjects); + + // Assert skipped tasks per project + for (const [projectName, expectedSkipped] of Object.entries(projects)) { + const skippedTasks = expectedSkipped.skippedTasks || []; + const actualSkipped = (tasksByProject[projectName]?.skipped || []).sort(); + const expectedArray = skippedTasks.sort(); + this._t.deepEqual(actualSkipped, expectedArray); + } + } +} diff --git a/packages/project/test/lib/build/ProjectBuilder.integration.js b/packages/project/test/lib/build/ProjectBuilder.integration.js new file mode 100644 index 00000000000..51bdff82611 --- /dev/null +++ b/packages/project/test/lib/build/ProjectBuilder.integration.js @@ -0,0 +1,1403 @@ +import test from "ava"; +import sinonGlobal from "sinon"; +import {fileURLToPath} from "node:url"; +import fs from "node:fs/promises"; +import {graphFromPackageDependencies} from "../../../lib/graph/graph.js"; +import {setLogLevel} from "@ui5/logger"; + +// Ensures that all logging code paths are tested +setLogLevel("silly"); + +test.beforeEach((t) => { + const sinon = t.context.sinon = sinonGlobal.createSandbox(); + + t.context.logEventStub = sinon.stub(); + t.context.buildMetadataEventStub = sinon.stub(); + t.context.projectBuildMetadataEventStub = sinon.stub(); + t.context.buildStatusEventStub = sinon.stub(); + t.context.projectBuildStatusEventStub = sinon.stub(); + + process.on("ui5.log", t.context.logEventStub); + process.on("ui5.build-metadata", t.context.buildMetadataEventStub); + process.on("ui5.project-build-metadata", t.context.projectBuildMetadataEventStub); + process.on("ui5.build-status", t.context.buildStatusEventStub); + process.on("ui5.project-build-status", t.context.projectBuildStatusEventStub); +}); + +test.afterEach.always((t) => { + t.context.sinon.restore(); + delete process.env.UI5_DATA_DIR; + + process.off("ui5.log", t.context.logEventStub); + process.off("ui5.build-metadata", t.context.buildMetadataEventStub); + process.off("ui5.project-build-metadata", t.context.projectBuildMetadataEventStub); + process.off("ui5.build-status", t.context.buildStatusEventStub); + process.off("ui5.project-build-status", t.context.projectBuildStatusEventStub); +}); + +test.serial("Build application.a project multiple times", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // #1 build (with empty cache) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: false}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Change a source file in application.a + const changedFilePath = `${fixtureTester.fixturePath}/webapp/test.js`; + await fs.appendFile(changedFilePath, `\ntest("line added");\n`); + + // #3 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + // Note: replaceCopyright is skipped because no copyright is configured in the project + "replaceCopyright", + "enhanceManifest", + "generateFlexChangesBundle", + ] + } + } + } + }); + + // Check whether the changed file is in the destPath + const builtFileContent = await fs.readFile(`${destPath}/test.js`, {encoding: "utf8"}); + t.true(builtFileContent.includes(`test("line added");`), "Build dest contains changed file content"); + + + // #4 build (with cache, no changes, with dependencies) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {}, + } + } + }); + + + // #5 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // #6 build (with cache, no changes, with dependencies) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: {} + } + }); + + + // #6 build (with cache, no changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + + // #7 build (with cache, no changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // #8 build (with cache, no changes, with dependencies) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: {} + } + }); + + + // Change a source file with existing source map in application.a + const fileWithSourceMapPath = + `${fixtureTester.fixturePath}/webapp/thirdparty/scriptWithSourceMap.js`; + const fileWithSourceMapContent = await fs.readFile(fileWithSourceMapPath, {encoding: "utf8"}); + await fs.writeFile( + fileWithSourceMapPath, + fileWithSourceMapContent.replace( + `This is a script with a source map.`, + `This is a CHANGED script with a source map.` + ) + ); + const sourceMapPath = `${fixtureTester.fixturePath}/webapp/thirdparty/scriptWithSourceMap.js.map`; + const sourceMapContent = await fs.readFile(sourceMapPath, {encoding: "utf8"}); + await fs.writeFile( + sourceMapPath, + sourceMapContent.replace( + `This is a script with a source map.`, + `This is a CHANGED script with a source map.` + ) + ); + + // #9 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "enhanceManifest", + "escapeNonAsciiCharacters", + "generateFlexChangesBundle", + "replaceCopyright" + ] + } + } + } + }); +}); + +test.serial("Build application.a (custom task and tag handling)", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // #1 build (no cache, no changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + + // Create new file which should get tagged as "OmitFromBuildResult" by a custom task + await fs.writeFile(`${fixtureTester.fixturePath}/webapp/fileToBeOmitted.js`, + `console.log("this file should be omitted in the build result")`); + + // #2 build (with cache, with changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + // Note: replaceCopyright is skipped because no copyright is configured in the project + "replaceCopyright", + "enhanceManifest", + "generateFlexChangesBundle", + ] + } + } + } + }); + + // Check that fileToBeOmitted.js is not in dist + await t.throwsAsync(fs.readFile(`${destPath}/fileToBeOmitted.js`, {encoding: "utf8"})); + + + // #3 build (with cache, no changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + // Check that fileToBeOmitted.js is not in dist again + await t.throwsAsync(fs.readFile(`${destPath}/fileToBeOmitted.js`, {encoding: "utf8"})); + + + // Delete the file again + await fs.rm(`${fixtureTester.fixturePath}/webapp/fileToBeOmitted.js`); + + // #4 build (with cache, with changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} // everything should be skipped (already done in very first build) + } + }); +}); + +test.serial("Build application.a (multiple custom tasks)", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // This test should cover a scenario with multiple custom tasks. + // Specifically, a tag is set in custom-task-1 on a resource which is read in custom-task-0 and custom-task-2. + // The expected behavior is that the tag is not present in custom-task-0 (which runs before custom-task-1), + // but is present in custom-task-2 (which runs after custom-task-1). + // (for testing purposes, the custom tasks already check for this tag by themselves and handle errors accordingly) + + // #1 build (no cache, no changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-multiple-customTasks.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + // #2 build (with cache, no changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-multiple-customTasks.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Create a new file to allow a new build: + // Logic of custom-task-1 will NOT handle this file, while custom-task-0 and 2 WILL DO it, + // resulting in custom-task-1 getting skipped (cache reuse). + // The test should then verify that the tag is still only readable for custom-task-2. + // This ensures that the build result is exactly the same with or without using the cache. + // (as in #1 build, the custom tasks already check for this tag by themselves and handle errors accordingly) + await fs.cp(`${fixtureTester.fixturePath}/webapp/test.js`, + `${fixtureTester.fixturePath}/webapp/test2.js`); + + // #3 build (with cache, with changes, with custom tasks) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-multiple-customTasks.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "custom-task-1", // SHOULD BE SKIPPED + // remaining skipped tasks don't matter here: + "enhanceManifest", + "escapeNonAsciiCharacters", + "generateFlexChangesBundle", + "replaceCopyright", + ] + } + } + } + }); +}); + +test.serial.skip("Build application.a (multiple custom tasks 2)", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // This test should cover a scenario with multiple custom tasks. + // Specifically, it's invalidating the task cache by only modifying tags on resources, + // but not the resources themselves. + + // #1 build (no cache, no changes, with custom tasks) + // During this build, "custom-task-0" sets the tag "isDebugVariant" to test.js. + // "custom-task-1" checks if it's able to read this tag. + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-multiple-customTasks-2.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + + // #2 build (with cache, no changes, with custom tasks) + // During this build, "custom-task-0" sets a different tag to test.js (namely "OmitFromBuildResult"). + // "custom-task-1" again checks if it's able to read this different tag. + // It's expected that both custom tasks are not getting skipped during this build, + // even though any resources weren't modified. + // FIXME: Currently, the entire build is skipped and therefore the custom tasks are not executed. + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-multiple-customTasks-2.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} // TODO: add non-relevant skippedTasks here, once the tag handling works + } + } + }); + + // Check that test.js is omitted from build output: + await t.throwsAsync(fs.readFile(`${destPath}/test.js`, {encoding: "utf8"})); +}); + +test.serial.skip("Build application.a (dependency content changes)", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // This test should cover a scenario with an application depending on a library. + // Specifically, we're directly modifying the contents of the library + // which should have effects on the application because a custom task will detect it + // and modify the application's resources. The application is expected to get rebuilt. + + // #1 build (no cache, no changes, no dependencies) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask-dependency-change.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + + // #2 build (with cache, no changes, no dependencies) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask-dependency-change.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Change content of library.d (this will not affect application.a): + const someJsOfLibrary = `${fixtureTester.fixturePath}/node_modules/library.d/main/src/library/d/some.js`; + await fs.appendFile(someJsOfLibrary, `\ntest("line added");\n`); + + // #3 build (with cache, with changes, with dependencies) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask-dependency-change.yaml"}, + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {}, + } + } + }); + + // Check if library contains correct changed content: + const builtFileContent = await fs.readFile(`${destPath}/resources/library/d/some.js`, {encoding: "utf8"}); + t.true(builtFileContent.includes(`test("line added");`), "Build dest contains changed file content"); + + + // Change content of library.d again (this time it affects application.a): + await fs.writeFile(`${fixtureTester.fixturePath}/node_modules/library.d/main/src/library/d/newLibraryFile.js`, + `console.log("SOME NEW CONTENT");`); + + // #4 build (no cache, with changes, with dependencies) + // This build should execute the custom task "task.dependency-change.js" again which now detects "newLibraryFile.js" + // and modifies a resource of application.a (namely "test.js"). + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-customTask-dependency-change.yaml"}, + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "application.a": { // FIXME: currently failing (getting skipped entirely) + skippedTasks: [ + "enhanceManifest", + "escapeNonAsciiCharacters", + "generateFlexChangesBundle", + "replaceCopyright", + ] + }, + } + } + }); + + // Check that application.a contains correct changed content (test.js): + const builtFileContent2 = await fs.readFile(`${destPath}/test.js`, {encoding: "utf8"}); + t.true(builtFileContent2.includes(`console.log('something new');`), "Build dest contains changed file content"); +}); + +test.serial("Build application.a (JSDoc build)", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // This test should cover a scenario with an application depending on a library. + // We're executing a JSDoc build including dependencies (as with "ui5 build jsdoc --all") + // and testing if the output contains the expected JSDoc contents. + // Then, we're adding some additional JSDoc annotations to the library + // and testing the same again. + + // #1 build (no cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, jsdoc: "jsdoc", dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {}, + "application.a": {} + } + } + }); + + // Check that JSDoc build ran successfully: + await t.throwsAsync(fs.readFile(`${destPath}/resources/library/d/some-dbg.js`, {encoding: "utf8"})); + await t.throwsAsync(fs.readFile(`${destPath}/resources/library/d/some.js.map`, {encoding: "utf8"})); + const builtFileContent = await fs.readFile(`${destPath}/resources/library/d/some.js`, {encoding: "utf8"}); + // Check that output contains correct file content: + t.false(builtFileContent.includes(`//# sourceMappingURL=some.js.map`), + "Build dest does not contain source map reference"); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, jsdoc: "jsdoc", dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: {} + } + }); + + + // Add additional JSDoc annotations to library.d: + const jsdocContent = `/*! +* ` + "${copyright}" + ` +*/ + +/** +* Example JSDoc annotation +* +* @public +* @static +* @param {object} param +* @returns {string} output +*/ +function functionWithJSDoc(param) {return "test"}`; + + await fs.writeFile(`${fixtureTester.fixturePath}/node_modules/library.d/main/src/library/d/some.js`, + jsdocContent); + + // #3 build (no cache, with changes) + // application.a should get skipped: + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, jsdoc: "jsdoc", dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": { + skippedTasks: [ + "buildThemes", + "enhanceManifest", + "escapeNonAsciiCharacters", + "executeJsdocSdkTransformation", + ] + } + } + } + }); + + // Check that JSDoc build ran successfully: + await t.throwsAsync(fs.readFile(`${destPath}/resources/library/d/some-dbg.js`, {encoding: "utf8"})); + await t.throwsAsync(fs.readFile(`${destPath}/resources/library/d/some.js.map`, {encoding: "utf8"})); + const builtFileContent2 = await fs.readFile(`${destPath}/resources/library/d/some.js`, {encoding: "utf8"}); + t.true(builtFileContent2.includes(`Example JSDoc annotation`), "Build dest contains new JSDoc content"); + // Check that output contains new file content: + t.false(builtFileContent2.includes(`//# sourceMappingURL=some.js.map`), + "Build dest does not contain source map reference"); + + + // #4 build (no cache, no changes) + // Normal build again (non-JSDoc build); should not execute task "generateJsdoc": + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {}, + "application.a": {} + } + } + }); + + // Check that normal build ran successfully: + await t.notThrowsAsync(fs.readFile(`${destPath}/resources/library/d/some-dbg.js`, {encoding: "utf8"})); + await t.notThrowsAsync(fs.readFile(`${destPath}/resources/library/d/some.js.map`, {encoding: "utf8"})); + const builtFileContent3 = await fs.readFile(`${destPath}/resources/library/d/some.js`, {encoding: "utf8"}); + t.false(builtFileContent3.includes(`Example JSDoc annotation`), "Build dest doesn't contain JSDoc content anymore"); + // Check that output contains content generated by the normal build: + t.true(builtFileContent3.includes(`//# sourceMappingURL=some.js.map`), + "Build dest does contain source map reference"); +}); + +test.serial("Build application.a (Custom Component preload configuration)", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // In this test, we're testing the behavior of a custom preload configuration + // which is defined in "ui5-custom-preload-config.yaml". + // This custom preload configuration generates a Component-preload.js similar to a default one. + // However, it will omit a resource ("scriptWithSourceMap.js") from the bundle. + + // #1 build (no cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + // Check that generated preload bundle doesn't contain the omitted file: + t.false((await fs.readFile(`${destPath}/Component-preload.js`, {encoding: "utf8"})) + .includes("id1/thirdparty/scriptWithSourceMap.js")); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); +}); + +test.serial("Build application.a (self-contained build)", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + + // We're executing a self-contained build including dependencies (as with "ui5 build self-contained --all") + // and testing if the output contains the expected self-contained bundle. + // Then, we're changing the content only of application.a + // and testing if the self-contained build output changes accordingly. + + // #1 build (no cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, selfContained: "self-contained", + dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {}, + "application.a": {} + } + } + }); + + // Check that output contains the correct content: + const builtFileContent = await fs.readFile(`${destPath}/resources/sap-ui-custom.js`, {encoding: "utf8"}); + t.true(builtFileContent.includes(`"id1/test.js":'function test(t){var o=t;console.log(o)}test();\\n'`)); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, selfContained: "self-contained", + dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: {} + } + }); + + + // Remove the file "test.js" from application.a: + await fs.rm(`${fixtureTester.fixturePath}/webapp/test.js`); + + // #3 build (with cache, with changes) + // Dependencies should get skipped, application.a should get rebuilt: + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, selfContained: "self-contained", + dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "application.a": { + skippedTasks: [ + "enhanceManifest", + "escapeNonAsciiCharacters", + "generateFlexChangesBundle", + "replaceCopyright", + "transformBootstrapHtml", + ] + } + } + } + }); + + // Check that output contains the correct content (test.js should be missing): + const builtFileContent2 = await fs.readFile(`${destPath}/resources/sap-ui-custom.js`, {encoding: "utf8"}); + t.false(builtFileContent2.includes(`"id1/test.js":`)); + + + // #4 build (with cache, no changes) + // Run a self-contained build but with a different config which defines a custom preload. + // The build should run and the output should still contain the expected self-contained bundle + // (tasks "generateComponentPreload" and "generateLibraryPreload" should not get executed): + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"}, + config: {destPath, cleanDest: true, selfContained: "self-contained", + dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + // Check that output contains still the correct content: + const builtFileContent3 = await fs.readFile(`${destPath}/resources/sap-ui-custom.js`, {encoding: "utf8"}); + t.false(builtFileContent3.includes(`"id1/test.js":`)); + + + // #5 build (with cache, no changes) + // Run a self-contained build but without dependencies: + // (everything should get skipped) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5.yaml"}, + config: {destPath, cleanDest: true, selfContained: "self-contained"}, + assertions: { + projects: {} + } + }); +}); + +test.serial("Build library.d project multiple times", async (t) => { + const fixtureTester = new FixtureTester(t, "library.d"); + const destPath = fixtureTester.destPath; + + // #1 build (with empty cache) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: false}, + assertions: { + projects: {"library.d": {}} + } + }); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Change a source file in library.d + const changedFilePath = `${fixtureTester.fixturePath}/main/src/library/d/.library`; + await fs.writeFile( + changedFilePath, + (await fs.readFile(changedFilePath, {encoding: "utf8"})).replace( + `Library D`, + `Library D (updated #1)` + ) + ); + + // #3 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"library.d": { + skippedTasks: [ + "buildThemes", + "escapeNonAsciiCharacters", + "minify", + "replaceBuildtime", + ] + }} + } + }); + + // Check whether the changes are in the destPath + const builtFileContent = await fs.readFile(`${destPath}/resources/library/d/.library`, {encoding: "utf8"}); + t.true( + builtFileContent.includes(`Library D (updated #1)`), + "Build dest contains changed file content" + ); + + // Check whether the manifest.json was updated with the new documentation + const manifestContent = await fs.readFile(`${destPath}/resources/library/d/manifest.json`, {encoding: "utf8"}); + t.true( + manifestContent.includes(`"Library D (updated #1)"`), + "Build dest contains updated description in manifest.json" + ); + + + // #4 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Update copyright in ui5.yaml (should trigger a full rebuild of the project) + const ui5YamlPath = `${fixtureTester.fixturePath}/ui5.yaml`; + await fs.writeFile( + ui5YamlPath, + (await fs.readFile(ui5YamlPath, {encoding: "utf8"})).replace( + "copyright: Some fancy copyright", + "copyright: Some updated fancy copyright" + ) + ); + + // #5 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"library.d": {}} + } + }); +}); + +test.serial("Build library.d (Custom Library preload configuration)", async (t) => { + const fixtureTester = new FixtureTester(t, "library.d"); + const destPath = fixtureTester.destPath; + + // In this test, we're testing the behavior of a custom preload configuration + // which is defined in "ui5-custom-preload-config.yaml". + // This custom preload configuration generates a library-preload.js similar to a default one. + // However, it will omit a resource ("some.js") from the bundle. + + // #1 build (no cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "library.d": {} + } + } + }); + + // Check that generated preload bundle doesn't contain the omitted file: + t.false((await fs.readFile(`${destPath}/resources/library/d/library-preload.js`, {encoding: "utf8"})) + .includes("library/d/some.js")); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); +}); + +test.serial("Build theme.library.e project multiple times", async (t) => { + const fixtureTester = new FixtureTester(t, "theme.library.e"); + const destPath = fixtureTester.destPath; + + // #1 build (with empty cache) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: false}, + assertions: { + projects: {"theme.library.e": {}} + } + }); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Change a source file in theme.library.e + const librarySourceFilePath = + `${fixtureTester.fixturePath}/src/theme/library/e/themes/my_theme/library.source.less`; + await fs.appendFile(librarySourceFilePath, `\n.someNewClass {\n\tcolor: red;\n}\n`); + + // #3 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"theme.library.e": {}} + } + }); + + // Check whether the changed file is in the destPath + const builtFileContent = await fs.readFile( + `${destPath}/resources/theme/library/e/themes/my_theme/library.source.less`, {encoding: "utf8"} + ); + t.true( + builtFileContent.includes(`.someNewClass`), + "Build dest contains changed file content" + ); + + // Check whether the build output contains the new CSS rule + const builtCssContent = await fs.readFile( + `${destPath}/resources/theme/library/e/themes/my_theme/library.css`, {encoding: "utf8"} + ); + t.true( + builtCssContent.includes(`.someNewClass`), + "Build dest contains new rule in library.css" + ); + + + // Add a new less file and import it in library.source.less + await fs.writeFile(`${fixtureTester.fixturePath}/src/theme/library/e/themes/my_theme/newImportFile.less`, + `.someOtherNewClass {\n\tcolor: blue;\n}\n` + ); + await fs.appendFile(librarySourceFilePath, `\n@import "newImportFile.less";\n`); + + // #4 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"theme.library.e": {}}, + } + }); + + // Check whether the build output contains the import to the new file + const builtCssContent2 = await fs.readFile( + `${destPath}/resources/theme/library/e/themes/my_theme/library.css`, {encoding: "utf8"} + ); + t.true( + builtCssContent2.includes(`.someOtherNewClass`), + "Build dest contains new rule in library.css" + ); + + + // #5 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {}, + } + }); + + + // Change content of new less file + await fs.writeFile(`${fixtureTester.fixturePath}/src/theme/library/e/themes/my_theme/newImportFile.less`, + `.someOtherNewClass {\n\tcolor: green;\n}\n` + ); + + // #6 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"theme.library.e": {}}, + } + }); + + // Check whether the build output contains the changed content of the imported file + const builtCssContent3 = await fs.readFile( + `${destPath}/resources/theme/library/e/themes/my_theme/library.css`, {encoding: "utf8"} + ); + t.true( + builtCssContent3.includes(`.someOtherNewClass{color:green}`), + "Build dest contains new rule in library.css" + ); + + + // Delete import of library.source.less + const librarySourceFileContent = (await fs.readFile(librarySourceFilePath)).toString(); + await fs.writeFile(librarySourceFilePath, + librarySourceFileContent.replace(`\n@import "newImportFile.less";\n`, "") + ); + + // Change content of new less file again + await fs.writeFile(`${fixtureTester.fixturePath}/src/theme/library/e/themes/my_theme/newImportFile.less`, + `.someOtherNewClass {\n\tcolor: yellow;\n}\n` + ); + + // #7 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"theme.library.e": { + skippedTasks: ["buildThemes"] + }}, + } + }); + + // Check if library.css does NOT contain the imported rule anymore + t.false( + (await fs.readFile( + `${destPath}/resources/theme/library/e/themes/my_theme/library.css`, {encoding: "utf8"} + )).includes(`.someOtherNewClass`), + "Build dest should NOT contain the rule in library.css anymore" + ); + + + // Delete the imported less file + await fs.rm(`${fixtureTester.fixturePath}/src/theme/library/e/themes/my_theme/newImportFile.less`); + + // #8 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {}, // -> everything should be skipped + } + }); +}); + +test.serial("Build component.a project multiple times", async (t) => { + const fixtureTester = new FixtureTester(t, "component.a"); + const destPath = fixtureTester.destPath; + + // #1 build (no cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "component.a": {} + } + } + }); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Change a source file in component.a + const changedFilePath = `${fixtureTester.fixturePath}/src/test.js`; + await fs.appendFile(changedFilePath, `\ntest("line added");\n`); + + // #3 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "component.a": { + skippedTasks: [ + "escapeNonAsciiCharacters", + // Note: replaceCopyright is skipped because no copyright is configured in the project + "replaceCopyright", + "enhanceManifest", + "generateFlexChangesBundle", + ] + } + } + } + }); + + // Check whether the changed file is in the destPath + const builtFileContent = await fs.readFile(`${destPath}/resources/id1/test.js`, {encoding: "utf8"}); + t.true(builtFileContent.includes(`test("line added");`), "Build dest contains changed file content"); + + + // #4 build (with cache, no changes, with dependencies) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {}, + } + } + }); + + + // #5 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // #6 build (with cache, no changes, with dependencies) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: {} + } + }); +}); + +test.serial("Build component.a (Custom Component preload configuration)", async (t) => { + const fixtureTester = new FixtureTester(t, "component.a"); + const destPath = fixtureTester.destPath; + + // In this test, we're testing the behavior of a custom preload configuration + // which is defined in "ui5-custom-preload-config.yaml". + // This custom preload configuration generates a Component-preload.js similar to a default one. + // However, it will omit a resource ("test.js") from the bundle. + + // #1 build (no cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "component.a": {} + } + } + }); + + // Check that generated preload bundle doesn't contain the omitted file: + t.false((await fs.readFile(`${destPath}/resources/id1/Component-preload.js`, {encoding: "utf8"})) + .includes("id1/test.js")); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-custom-preload-config.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); +}); + +test.serial("Build module.b project multiple times", async (t) => { + const fixtureTester = new FixtureTester(t, "module.b"); + const destPath = fixtureTester.destPath; + + // #1 build (no cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"module.b": {}} + }, + }); + + + // #2 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Change a source file in module.b + const changedFilePath = `${fixtureTester.fixturePath}/dev/devTools.js`; + await fs.appendFile(changedFilePath, `\ntest("line added");\n`); + + // #3 build (no cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"module.b": {}} + } + }); + + // Check whether the changed file is in the destPath + const builtFileContent = await fs.readFile(`${destPath}/resources/b/module/dev/devTools.js`, {encoding: "utf8"}); + t.true(builtFileContent.includes(`test("line added");`), "Build dest contains changed file content"); + + + // Remove a source file in module.b + await fs.rm(`${fixtureTester.fixturePath}/dev/devTools.js`); + + // #4 build (no cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"module.b": {}} + } + }); + + // Check that the removed file is NOT in the destPath anymore + // (dist output should be totally empty: no source files -> no build result) + await t.throwsAsync(fs.readFile(`${destPath}/resources/b/module/dev/devTools.js`, {encoding: "utf8"})); + + + // #5 build (with cache, no changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} + } + }); + + + // Add a new file in module.b + await fs.mkdir(`${fixtureTester.fixturePath}/dev/newFolder`, {recursive: true}); + await fs.writeFile(`${fixtureTester.fixturePath}/dev/newFolder/newFile.js`, + `console.log("this is a new file which should be included in the build result")`); + + // #6 build (no cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"module.b": {}} + }, + }); + + // Check whether the added file is in the destPath + const newFile = await fs.readFile(`${destPath}/resources/b/module/dev/newFolder/newFile.js`, + {encoding: "utf8"}); + t.true(newFile.includes(`this is a new file which should be included in the build result`), + "Build dest contains correct file content"); + + + // Add a new path mapping: + const originalUi5Yaml = await fs.readFile(`${fixtureTester.fixturePath}/ui5.yaml`, {encoding: "utf8"}); // for later + const newFileName = "someOtherNewFile.js"; + const newFolderName = "newPathmapping"; + const virtualPath = `/resources/b/module/${newFolderName}/`; + await fs.writeFile(`${fixtureTester.fixturePath}/ui5.yaml`, + `--- +specVersion: "5.0" +type: module +metadata: + name: module.b +resources: + configuration: + paths: + /resources/b/module/dev/: dev + ${virtualPath}: ${newFolderName}` + ); + + // Create a resource for this new path mapping: + await fs.mkdir(`${fixtureTester.fixturePath}/${newFolderName}`, {recursive: true}); + await fs.writeFile(`${fixtureTester.fixturePath}/${newFolderName}/${newFileName}`, + `console.log("this should be included in the build result if the path mapping has been set")`); + + // #7 build (no cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {"module.b": {}} + }, + }); + + // Check whether the added file is in the destPath + const someOtherNewFile = await fs.readFile(`${destPath}${virtualPath}${newFileName}`, + {encoding: "utf8"}); + t.true(someOtherNewFile.includes(`path mapping has been set`), "Build dest contains correct file content"); + + + // Remove the path mapping again (revert original ui5.yaml): + await fs.writeFile(`${fixtureTester.fixturePath}/ui5.yaml`, originalUi5Yaml); + + // #8 build (with cache, with changes) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true}, + assertions: { + projects: {} // -> cache can be reused + }, + }); + + // Check that the added resource of the path mapping is NOT in the destPath anymore: + await t.throwsAsync(fs.readFile(`${destPath}${virtualPath}${newFileName}`, + {encoding: "utf8"})); + + + // #9 build (with cache, no changes, with dependencies) + await fixtureTester.buildProject({ + config: {destPath, cleanDest: true, dependencyIncludes: {includeAllDependencies: true}}, + assertions: { + projects: { + "library.d": {}, + "library.a": {}, + "library.b": {}, + "library.c": {}, + } + }, + }); +}); + +test.serial("Build race condition: file modified during active build", async (t) => { + const fixtureTester = new FixtureTester(t, "application.a"); + const destPath = fixtureTester.destPath; + await fixtureTester._initialize(); + const testFilePath = `${fixtureTester.fixturePath}/webapp/test.js`; + const originalContent = await fs.readFile(testFilePath, {encoding: "utf8"}); + + // #1 Build with race condition triggered by custom task + // The custom task (configured in ui5-race-condition.yaml) modifies test.js during the build, + // after the source index is created but before tasks that process test.js execute. + // This creates a race condition where the cached content hash no longer matches the actual file. + // + // Expected behavior: + // - Build should detect that source file hash changed during execution + // - Build should fail with an error OR mark cache as invalid + // + // FIXME: Current behavior: + // - Build succeeds without detecting the race condition + // - Cache is written with inconsistent data (index hash != processed content hash) + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-race-condition.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: { + "application.a": {} + } + } + }); + + // Verify the race condition occurred: the modification made by the custom task is in the output + const builtFileContent = await fs.readFile(`${destPath}/test.js`, {encoding: "utf8"}); + t.true( + builtFileContent.includes(`RACE CONDITION MODIFICATION`), + "Build output contains the modification made during build" + ); + + // #2 Revert the source file to original content + await fs.writeFile(testFilePath, originalContent); + + // #3 Build again after reverting the source + // FIXME: The cache should be invalidated because the previous build had a race condition, + // but currently it's reused (projects: {}). Once proper validation is implemented, + // this should trigger a full rebuild: {"application.a": {}} + await fixtureTester.buildProject({ + graphConfig: {rootConfigPath: "ui5-race-condition.yaml"}, + config: {destPath, cleanDest: true}, + assertions: { + projects: {} // Current: cache reused | Expected: {"application.a": {}} + } + }); + + // FIXME: Due to incorrect cache reuse from build #1, the output still contains the modification + // even though the source was reverted. This demonstrates the cache corruption issue. + // Expected: finalBuiltContent should NOT contain "RACE CONDITION MODIFICATION" + const finalBuiltContent = await fs.readFile(`${destPath}/test.js`, {encoding: "utf8"}); + t.true( + finalBuiltContent.includes(`RACE CONDITION MODIFICATION`), + "Build output incorrectly contains the modification due to corrupted cache" + ); +}); + +function getFixturePath(fixtureName) { + return fileURLToPath(new URL(`../../fixtures/${fixtureName}`, import.meta.url)); +} + +function getTmpPath(folderName) { + return fileURLToPath(new URL(`../../tmp/ProjectBuilder/${folderName}`, import.meta.url)); +} + +async function rmrf(dirPath) { + return fs.rm(dirPath, {recursive: true, force: true}); +} + +class FixtureTester { + constructor(t, fixtureName) { + this._t = t; + this._sinon = t.context.sinon; + this._fixtureName = fixtureName; + this._initialized = false; + + // Public + this.fixturePath = getTmpPath(fixtureName); + this.destPath = getTmpPath(`${fixtureName}/dist`); + } + + async _initialize() { + if (this._initialized) { + return; + } + process.env.UI5_DATA_DIR = getTmpPath(`${this._fixtureName}/.ui5`); + await rmrf(this.fixturePath); // Clean up any previous test runs + await fs.cp(getFixturePath(this._fixtureName), this.fixturePath, {recursive: true}); + this._initialized = true; + } + + async buildProject({graphConfig = {}, config = {}, assertions = {}} = {}) { + await this._initialize(); + this._sinon.resetHistory(); + + const graph = await graphFromPackageDependencies({ + ...graphConfig, + cwd: this.fixturePath, + }); + + // Execute the build + await graph.build(config); + + // Apply assertions if provided + if (assertions) { + this._assertBuild(assertions); + } + } + + _assertBuild(assertions) { + const {projects = {}} = assertions; + + const projectsInOrder = []; + const seenProjects = new Set(); + const tasksByProject = {}; + + // Extract build status to identify built projects and their order + const buildStatusEvents = this._t.context.buildStatusEventStub.args.map((args) => args[0]); + for (const event of buildStatusEvents) { + if (!seenProjects.has(event.projectName)) { + seenProjects.add(event.projectName); + if (event.status === "project-build-start") { + projectsInOrder.push(event.projectName); + } + } + } + + // Extract task status to identify skipped & executed tasks per project + const projectBuildStatusEvents = this._t.context.projectBuildStatusEventStub.args.map((args) => args[0]); + for (const event of projectBuildStatusEvents) { + if (!tasksByProject[event.projectName]) { + tasksByProject[event.projectName] = {executed: [], skipped: []}; + } + if (event.status === "task-skip") { + tasksByProject[event.projectName].skipped.push(event.taskName); + } else if (event.status === "task-start") { + tasksByProject[event.projectName].executed.push(event.taskName); + } + } + + // Assert projects built in order + const expectedProjects = Object.keys(projects); + this._t.deepEqual(projectsInOrder, expectedProjects); + + // Assert skipped tasks per project + for (const [projectName, expectedSkipped] of Object.entries(projects)) { + const skippedTasks = expectedSkipped.skippedTasks || []; + const actualSkipped = (tasksByProject[projectName]?.skipped || []).sort(); + const expectedArray = skippedTasks.sort(); + this._t.deepEqual(actualSkipped, expectedArray); + } + } +} diff --git a/packages/project/test/lib/build/ProjectBuilder.js b/packages/project/test/lib/build/ProjectBuilder.js index 64b36ab85e9..d1255d8224b 100644 --- a/packages/project/test/lib/build/ProjectBuilder.js +++ b/packages/project/test/lib/build/ProjectBuilder.js @@ -16,6 +16,8 @@ function getMockProject(type, id = "b") { getVersion: noop, getReader: () => "reader", getWorkspace: () => "workspace", + sealWorkspace: noop, + createNewWorkspaceVersion: noop, }; } @@ -67,6 +69,13 @@ test.beforeEach(async (t) => { project: getMockProject("library", "c") }); }, + traverseDependenciesDepthFirst: sinon.stub().callsFake(function* (includeRoot) { + if (includeRoot) { + yield {project: getMockProject("application", "a")}; + } + yield {project: getMockProject("library", "b")}; + yield {project: getMockProject("library", "c")}; + }), getProject: sinon.stub().callsFake((projectName) => { return getMockProject(...projectName.split(".")); }) @@ -100,20 +109,23 @@ test("build", async (t) => { const builder = new ProjectBuilder({graph, taskRepository}); const filterProjectStub = sinon.stub().returns(true); - const getProjectFilterStub = sinon.stub(builder, "_getProjectFilter").resolves(filterProjectStub); + sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub); const requiresBuildStub = sinon.stub().returns(true); - const runTasksStub = sinon.stub().resolves(); + const possiblyRequiresBuildStub = sinon.stub().returns(true); + const prepareProjectBuildAndValidateCacheStub = sinon.stub().resolves(false); + const buildProjectStub = sinon.stub().resolves(); + const writeBuildCacheStub = sinon.stub().resolves(); const projectBuildContextMock = { - getTaskRunner: () => { - return { - runTasks: runTasksStub, - }; - }, + possiblyRequiresBuild: possiblyRequiresBuildStub, + prepareProjectBuildAndValidateCache: prepareProjectBuildAndValidateCacheStub, + buildProject: buildProjectStub, + writeBuildCache: writeBuildCacheStub, requiresBuild: requiresBuildStub, - getProject: sinon.stub().returns(getMockProject("library")) + getProject: sinon.stub().returns(getMockProject("library")), + buildFinished: sinon.stub() }; - const createRequiredBuildContextsStub = sinon.stub(builder, "_createRequiredBuildContexts") + const getRequiredProjectContextsStub = sinon.stub(builder._buildContext, "getRequiredProjectContexts") .resolves(new Map().set("project.a", projectBuildContextMock)); const registerCleanupSigHooksStub = sinon.stub(builder, "_registerCleanupSigHooks").returns("cleanup sig hooks"); @@ -122,28 +134,21 @@ test("build", async (t) => { const deregisterCleanupSigHooksStub = sinon.stub(builder, "_deregisterCleanupSigHooks"); const executeCleanupTasksStub = sinon.stub(builder, "_executeCleanupTasks").resolves(); - await builder.build({ + await builder.buildToTarget({ destPath: "dest/path", includedDependencies: ["dep a"], excludedDependencies: ["dep b"] }); - t.is(getProjectFilterStub.callCount, 1, "_getProjectFilter got called once"); - t.deepEqual(getProjectFilterStub.getCall(0).args[0], { - explicitIncludes: ["dep a"], - explicitExcludes: ["dep b"], - dependencyIncludes: undefined - }, "_getProjectFilter got called with correct arguments"); - - t.is(createRequiredBuildContextsStub.callCount, 1, "_createRequiredBuildContexts got called once"); - t.deepEqual(createRequiredBuildContextsStub.getCall(0).args[0], [ + t.is(getRequiredProjectContextsStub.callCount, 1, "getRequiredProjectContexts got called once"); + t.deepEqual(getRequiredProjectContextsStub.getCall(0).args[0], [ "project.a", "project.b", "project.c" - ], "_createRequiredBuildContexts got called with correct arguments"); + ], "getRequiredProjectContexts got called with correct arguments"); - t.is(requiresBuildStub.callCount, 1, "ProjectBuildContext#requiresBuild got called once"); + t.is(possiblyRequiresBuildStub.callCount, 1, "ProjectBuildContext#possiblyRequiresBuild got called once"); t.is(registerCleanupSigHooksStub.callCount, 1, "_registerCleanupSigHooksStub got called once"); - t.is(runTasksStub.callCount, 1, "TaskRunner#runTasks got called once"); + t.is(buildProjectStub.callCount, 1, "ProjectBuildContext#buildProject got called once"); t.is(writeResultsStub.callCount, 1, "_writeResults got called once"); t.is(writeResultsStub.getCall(0).args[0], projectBuildContextMock, @@ -151,18 +156,20 @@ test("build", async (t) => { t.is(writeResultsStub.getCall(0).args[1]._fsBasePath, path.resolve("dest/path") + path.sep, "_writeResults got called with correct second argument"); + t.is(writeBuildCacheStub.callCount, 1, "writeBuildCache got called once"); + t.is(deregisterCleanupSigHooksStub.callCount, 1, "_deregisterCleanupSigHooks got called once"); t.is(deregisterCleanupSigHooksStub.getCall(0).args[0], "cleanup sig hooks", "_deregisterCleanupSigHooks got called with correct arguments"); t.is(executeCleanupTasksStub.callCount, 1, "_executeCleanupTasksStub got called once"); }); -test("build: Missing dest parameter", async (t) => { +test("build: Conflicting dependency parameters", async (t) => { const {graph, taskRepository, ProjectBuilder} = t.context; const builder = new ProjectBuilder({graph, taskRepository}); - const err = await t.throwsAsync(builder.build({ + const err = await t.throwsAsync(builder.buildToTarget({ destPath: "dest/path", dependencyIncludes: "dependencyIncludes", includedDependencies: ["dep a"], @@ -180,7 +187,7 @@ test("build: Too many dependency parameters", async (t) => { const builder = new ProjectBuilder({graph, taskRepository}); - const err = await t.throwsAsync(builder.build({ + const err = await t.throwsAsync(builder.buildToTarget({ includedDependencies: ["dep a"], excludedDependencies: ["dep b"] })); @@ -198,8 +205,8 @@ test("build: createBuildManifest in conjunction with dependencies", async (t) => }); const filterProjectStub = sinon.stub().returns(true); - sinon.stub(builder, "_getProjectFilter").resolves(filterProjectStub); - const err = await t.throwsAsync(builder.build({ + sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub); + const err = await t.throwsAsync(builder.buildToTarget({ destPath: "dest/path", includedDependencies: ["dep a"] })); @@ -216,20 +223,18 @@ test("build: Failure", async (t) => { const builder = new ProjectBuilder({graph, taskRepository}); const filterProjectStub = sinon.stub().returns(true); - sinon.stub(builder, "_getProjectFilter").resolves(filterProjectStub); + sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub); - const requiresBuildStub = sinon.stub().returns(true); - const runTasksStub = sinon.stub().rejects(new Error("Some Error")); + const possiblyRequiresBuildStub = sinon.stub().returns(true); + const prepareProjectBuildAndValidateCacheStub = sinon.stub().resolves(false); + const buildProjectStub = sinon.stub().rejects(new Error("Some Error")); const projectBuildContextMock = { - requiresBuild: requiresBuildStub, - getTaskRunner: () => { - return { - runTasks: runTasksStub - }; - }, + possiblyRequiresBuild: possiblyRequiresBuildStub, + prepareProjectBuildAndValidateCache: prepareProjectBuildAndValidateCacheStub, + buildProject: buildProjectStub, getProject: sinon.stub().returns(getMockProject("library")) }; - sinon.stub(builder, "_createRequiredBuildContexts") + sinon.stub(builder._buildContext, "getRequiredProjectContexts") .resolves(new Map().set("project.a", projectBuildContextMock)); sinon.stub(builder, "_registerCleanupSigHooks").returns("cleanup sig hooks"); @@ -237,7 +242,7 @@ test("build: Failure", async (t) => { const deregisterCleanupSigHooksStub = sinon.stub(builder, "_deregisterCleanupSigHooks"); const executeCleanupTasksStub = sinon.stub(builder, "_executeCleanupTasks").resolves(); - const err = await t.throwsAsync(builder.build({ + const err = await t.throwsAsync(builder.buildToTarget({ destPath: "dest/path", includedDependencies: ["dep a"], excludedDependencies: ["dep b"] @@ -279,45 +284,38 @@ test.serial("build: Multiple projects", async (t) => { const builder = new ProjectBuilder({graph, taskRepository}); const filterProjectStub = sinon.stub().returns(true).onFirstCall().returns(false); - const getProjectFilterStub = sinon.stub(builder, "_getProjectFilter").resolves(filterProjectStub); - - const requiresBuildAStub = sinon.stub().returns(true); - const requiresBuildBStub = sinon.stub().returns(false); - const requiresBuildCStub = sinon.stub().returns(true); - const getBuildMetadataStub = sinon.stub().returns({ - timestamp: "2022-07-28T12:00:00.000Z", - age: "xx days" - }); - const runTasksStub = sinon.stub().resolves(); + sinon.stub(builder, "_createProjectFilter").returns(filterProjectStub); + + const buildProjectAStub = sinon.stub().resolves(); + const buildProjectBStub = sinon.stub().resolves(); + const buildProjectCStub = sinon.stub().resolves(); + const writeBuildCacheStub = sinon.stub().resolves(); + const projectBuildContextMockA = { - getTaskRunner: () => { - return { - runTasks: runTasksStub - }; - }, - requiresBuild: requiresBuildAStub, - getProject: sinon.stub().returns(getMockProject("library", "a")) + possiblyRequiresBuild: sinon.stub().returns(true), + prepareProjectBuildAndValidateCache: sinon.stub().resolves(false), + buildProject: buildProjectAStub, + writeBuildCache: writeBuildCacheStub, + getProject: sinon.stub().returns(getMockProject("library", "a")), + buildFinished: sinon.stub() }; const projectBuildContextMockB = { - getTaskRunner: () => { - return { - runTasks: runTasksStub - }; - }, - getBuildMetadata: getBuildMetadataStub, - requiresBuild: requiresBuildBStub, - getProject: sinon.stub().returns(getMockProject("library", "b")) + possiblyRequiresBuild: sinon.stub().returns(false), + prepareProjectBuildAndValidateCache: sinon.stub().resolves(false), + buildProject: buildProjectBStub, + writeBuildCache: writeBuildCacheStub, + getProject: sinon.stub().returns(getMockProject("library", "b")), + buildFinished: sinon.stub() }; const projectBuildContextMockC = { - getTaskRunner: () => { - return { - runTasks: runTasksStub - }; - }, - requiresBuild: requiresBuildCStub, - getProject: sinon.stub().returns(getMockProject("library", "c")) + possiblyRequiresBuild: sinon.stub().returns(true), + prepareProjectBuildAndValidateCache: sinon.stub().resolves(false), + buildProject: buildProjectCStub, + writeBuildCache: writeBuildCacheStub, + getProject: sinon.stub().returns(getMockProject("library", "c")), + buildFinished: sinon.stub() }; - const createRequiredBuildContextsStub = sinon.stub(builder, "_createRequiredBuildContexts") + const getRequiredProjectContextsStub = sinon.stub(builder._buildContext, "getRequiredProjectContexts") .resolves(new Map() .set("project.a", projectBuildContextMockA) .set("project.b", projectBuildContextMockB) @@ -330,30 +328,22 @@ test.serial("build: Multiple projects", async (t) => { const executeCleanupTasksStub = sinon.stub(builder, "_executeCleanupTasks").resolves(); setLogLevel("verbose"); - await builder.build({ + await builder.buildToTarget({ destPath: path.join("dest", "path"), dependencyIncludes: "dependencyIncludes" }); setLogLevel("info"); - t.is(getProjectFilterStub.callCount, 1, "_getProjectFilter got called once"); - t.deepEqual(getProjectFilterStub.getCall(0).args[0], { - explicitIncludes: [], - explicitExcludes: [], - dependencyIncludes: "dependencyIncludes" - }, "_getProjectFilter got called with correct arguments"); - - t.is(createRequiredBuildContextsStub.callCount, 1, "_createRequiredBuildContexts got called once"); - t.deepEqual(createRequiredBuildContextsStub.getCall(0).args[0], [ + t.is(getRequiredProjectContextsStub.callCount, 1, "getRequiredProjectContexts got called once"); + t.deepEqual(getRequiredProjectContextsStub.getCall(0).args[0], [ "project.b", "project.c" - ], "_createRequiredBuildContexts got called with correct arguments"); + ], "getRequiredProjectContexts got called with correct arguments"); - t.is(requiresBuildAStub.callCount, 1, "TaskRunner#requiresBuild got called once times for library.a"); - t.is(requiresBuildBStub.callCount, 1, "TaskRunner#requiresBuild got called once times for library.b"); - t.is(requiresBuildCStub.callCount, 1, "TaskRunner#requiresBuild got called once times for library.c"); t.is(registerCleanupSigHooksStub.callCount, 1, "_registerCleanupSigHooksStub got called once"); - t.is(runTasksStub.callCount, 2, "TaskRunner#runTasks got called twice"); // library.b does not require a build + t.is(buildProjectAStub.callCount, 1, "buildProject got called once for library.a"); + t.is(buildProjectBStub.callCount, 0, "buildProject not called for library.b (possiblyRequiresBuild = false)"); + t.is(buildProjectCStub.callCount, 1, "buildProject got called once for library.c"); t.is(writeResultsStub.callCount, 2, "_writeResults got called twice"); // library.a has not been requested t.is(writeResultsStub.getCall(0).args[0], projectBuildContextMockB, @@ -394,47 +384,10 @@ test.serial("build: Multiple projects", async (t) => { "BuildLogger#skipProjectBuild got called with expected argument"); }); -test("_createRequiredBuildContexts", async (t) => { - const {graph, taskRepository, ProjectBuilder, sinon} = t.context; - - const builder = new ProjectBuilder({graph, taskRepository}); - - const requiresBuildStub = sinon.stub().returns(true); - const getRequiredDependenciesStub = sinon.stub() - .returns(new Set()) - .onFirstCall().returns(new Set(["project.b"])); // required dependency of project.a - - const projectBuildContextMock = { - requiresBuild: requiresBuildStub, - getTaskRunner: () => { - return { - getRequiredDependencies: getRequiredDependenciesStub - }; - } - }; - const createProjectContextStub = sinon.stub(builder._buildContext, "createProjectContext") - .returns(projectBuildContextMock); - const projectBuildContexts = await builder._createRequiredBuildContexts(["project.a", "project.c"]); - - t.is(requiresBuildStub.callCount, 3, "TaskRunner#requiresBuild got called three times"); - t.is(getRequiredDependenciesStub.callCount, 3, "TaskRunner#getRequiredDependencies got called three times"); - - t.deepEqual(Object.fromEntries(projectBuildContexts), { - "project.a": projectBuildContextMock, - "project.b": projectBuildContextMock, // is a required dependency of project.a - "project.c": projectBuildContextMock, - }, "Returned expected project build contexts"); - - t.is(createProjectContextStub.callCount, 3, "BuildContext#createProjectContextStub got called three times"); - t.is(createProjectContextStub.getCall(0).args[0].project.getName(), "project.a", - "First call to BuildContext#createProjectContextStub with expected project"); - t.is(createProjectContextStub.getCall(1).args[0].project.getName(), "project.c", - "Second call to BuildContext#createProjectContextStub with expected project"); - t.is(createProjectContextStub.getCall(2).args[0].project.getName(), "project.b", - "Third call to BuildContext#createProjectContextStub with expected project"); -}); +// _createRequiredBuildContexts is now part of BuildContext, not ProjectBuilder +// This logic is tested through integration tests -test.serial("_getProjectFilter with dependencyIncludes", async (t) => { +test.serial("_createProjectFilter with dependencyIncludes", async (t) => { const {graph, taskRepository, sinon} = t.context; const composeProjectListStub = sinon.stub().returns({ includedDependencies: ["project.b", "project.c"], @@ -446,7 +399,7 @@ test.serial("_getProjectFilter with dependencyIncludes", async (t) => { const builder = new ProjectBuilder({graph, taskRepository}); - const filterProject = await builder._getProjectFilter({ + const filterProject = builder._createProjectFilter({ dependencyIncludes: "dependencyIncludes", explicitIncludes: "explicitIncludes", explicitExcludes: "explicitExcludes", @@ -465,7 +418,7 @@ test.serial("_getProjectFilter with dependencyIncludes", async (t) => { t.false(filterProject("project.e"), "project.e is not allowed"); }); -test.serial("_getProjectFilter with explicit include/exclude", async (t) => { +test.serial("_createProjectFilter with explicit include/exclude", async (t) => { const {graph, taskRepository, sinon} = t.context; const composeProjectListStub = sinon.stub().returns({ includedDependencies: ["project.b", "project.c"], @@ -477,7 +430,7 @@ test.serial("_getProjectFilter with explicit include/exclude", async (t) => { const builder = new ProjectBuilder({graph, taskRepository}); - const filterProject = await builder._getProjectFilter({ + const filterProject = builder._createProjectFilter({ explicitIncludes: "explicitIncludes", explicitExcludes: "explicitExcludes", }); @@ -543,7 +496,9 @@ test("_writeResults", async (t) => { write: sinon.stub().resolves() }; - await builder._writeResults(projectBuildContextMock, writerMock); + const deferredWork = []; + await builder._writeResults(projectBuildContextMock, writerMock, deferredWork); + await Promise.all(deferredWork); t.is(getReaderStub.callCount, 1, "One reader requested"); t.deepEqual(getReaderStub.getCall(0).args[0], { @@ -608,6 +563,7 @@ test.serial("_writeResults: Create build manifest", async (t) => { const getTagStub = sinon.stub().returns(false).onFirstCall().returns(true); const projectBuildContextMock = { getProject: () => mockProject, + getBuildSignature: () => "build-signature", getTaskUtil: () => { return { isRootProject: () => true, @@ -622,7 +578,9 @@ test.serial("_writeResults: Create build manifest", async (t) => { write: sinon.stub().resolves() }; - await builder._writeResults(projectBuildContextMock, writerMock); + const deferredWork = []; + await builder._writeResults(projectBuildContextMock, writerMock, deferredWork); + await Promise.all(deferredWork); t.is(getReaderStub.callCount, 1, "One reader requested"); t.deepEqual(getReaderStub.getCall(0).args[0], { @@ -644,6 +602,10 @@ test.serial("_writeResults: Create build manifest", async (t) => { jsdoc: false, selfContained: false, }, "createBuildManifest got called with correct build configuration"); + t.is(createBuildManifestStub.getCall(0).args[2], taskRepository, + "createBuildManifest got called with correct taskRepository"); + t.is(createBuildManifestStub.getCall(0).args[3], "build-signature", + "createBuildManifest got called with correct buildSignature"); t.is(createResourceStub.callCount, 1, "One resource has been created"); t.deepEqual(createResourceStub.getCall(0).args[0], { @@ -716,7 +678,9 @@ test.serial("_writeResults: Flat build output", async (t) => { write: sinon.stub().resolves() }; - await builder._writeResults(projectBuildContextMock, writerMock); + const deferredWork = []; + await builder._writeResults(projectBuildContextMock, writerMock, deferredWork); + await Promise.all(deferredWork); t.is(getReaderStub.callCount, 2, "One reader requested"); t.deepEqual(getReaderStub.getCall(0).args[0], { diff --git a/packages/project/test/lib/build/TaskRunner.js b/packages/project/test/lib/build/TaskRunner.js index 84b46bb9bbe..8fd3f9addc2 100644 --- a/packages/project/test/lib/build/TaskRunner.js +++ b/packages/project/test/lib/build/TaskRunner.js @@ -57,8 +57,14 @@ function getMockProject(type) { getCachebusterSignatureType: noop, getCustomTasks: () => [], hasBuildManifest: () => false, - getWorkspace: () => "workspace", - isFrameworkProject: () => false + getWorkspace: () => { + return { + getName: () => "workspace" + }; + }, + isFrameworkProject: () => false, + sealWorkspace: noop, + createNewWorkspaceVersion: noop, }; } @@ -92,6 +98,7 @@ test.beforeEach(async (t) => { }; }, getRequiredDependenciesCallback: t.context.getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false), }; t.context.graph = { @@ -113,14 +120,34 @@ test.beforeEach(async (t) => { setTasks: sinon.stub(), startTask: sinon.stub(), endTask: sinon.stub(), + skipTask: sinon.stub(), verbose: sinon.stub(), perf: sinon.stub(), isLevelEnabled: sinon.stub().returns(true), }; + t.context.buildCache = { + setTasks: sinon.stub(), + prepareTaskExecutionAndValidateCache: sinon.stub().resolves(false), + recordTaskResult: sinon.stub().resolves(), + allTasksCompleted: sinon.stub().resolves([]), + }; + t.context.resourceFactory = { createReaderCollection: sinon.stub() - .returns("reader collection") + .returns({getName: () => "reader collection"}), + createMonitor: sinon.stub().callsFake((resource) => { + // Return a MonitoredReader-like object with both getName and getResourceRequests + if (resource && typeof resource.getName === "function") { + const name = resource.getName(); + return { + constructor: {name: "MonitoredReader"}, + getName: () => name, + getResourceRequests: sinon.stub().returns([]) + }; + } + return resource; + }) }; t.context.TaskRunner = await esmock("../../../lib/build/TaskRunner.js", { @@ -134,7 +161,7 @@ test.afterEach.always((t) => { }); test("Missing parameters", (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; t.throws(() => { new TaskRunner({ graph, @@ -152,6 +179,7 @@ test("Missing parameters", (t) => { taskUtil, taskRepository, log: projectBuildLogger, + buildCache, buildConfig }); }, { @@ -163,6 +191,7 @@ test("Missing parameters", (t) => { graph, taskRepository, log: projectBuildLogger, + buildCache, buildConfig }); }, { @@ -174,6 +203,7 @@ test("Missing parameters", (t) => { graph, taskUtil, log: projectBuildLogger, + buildCache, buildConfig }); }, { @@ -197,6 +227,7 @@ test("Missing parameters", (t) => { taskUtil, taskRepository, log: projectBuildLogger, + buildCache, }); }, { message: "TaskRunner: One or more mandatory parameters not provided" @@ -204,9 +235,10 @@ test("Missing parameters", (t) => { }); test("_initTasks: Project of type 'application'", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskRunner = new TaskRunner({ - project: getMockProject("application"), graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project: getMockProject("application"), graph, taskUtil, taskRepository, + log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); t.deepEqual(taskRunner._taskExecutionOrder, [ @@ -228,9 +260,10 @@ test("_initTasks: Project of type 'application'", async (t) => { }); test("_initTasks: Project of type 'library'", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskRunner = new TaskRunner({ - project: getMockProject("library"), graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project: getMockProject("library"), graph, taskUtil, taskRepository, + log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -254,13 +287,13 @@ test("_initTasks: Project of type 'library'", async (t) => { }); test("_initTasks: Project of type 'library' (framework project)", async (t) => { - const {graph, taskUtil, taskRepository, projectBuildLogger, TaskRunner} = t.context; + const {graph, taskUtil, taskRepository, projectBuildLogger, TaskRunner, buildCache} = t.context; const project = getMockProject("library"); project.isFrameworkProject = () => true; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -284,9 +317,10 @@ test("_initTasks: Project of type 'library' (framework project)", async (t) => { }); test("_initTasks: Project of type 'theme-library'", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskRunner = new TaskRunner({ - project: getMockProject("theme-library"), graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project: getMockProject("theme-library"), graph, taskUtil, taskRepository, + log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -300,13 +334,13 @@ test("_initTasks: Project of type 'theme-library'", async (t) => { }); test("_initTasks: Project of type 'theme-library' (framework project)", async (t) => { - const {graph, taskUtil, taskRepository, projectBuildLogger, TaskRunner} = t.context; + const {graph, taskUtil, taskRepository, projectBuildLogger, buildCache, TaskRunner} = t.context; const project = getMockProject("theme-library"); project.isFrameworkProject = () => true; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -320,9 +354,10 @@ test("_initTasks: Project of type 'theme-library' (framework project)", async (t }); test("_initTasks: Project of type 'module'", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskRunner = new TaskRunner({ - project: getMockProject("module"), graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project: getMockProject("module"), graph, taskUtil, taskRepository, + log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -330,9 +365,10 @@ test("_initTasks: Project of type 'module'", async (t) => { }); test("_initTasks: Unknown project type", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskRunner = new TaskRunner({ - project: getMockProject("pony"), graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project: getMockProject("pony"), graph, taskUtil, taskRepository, + log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(taskRunner._initTasks()); @@ -340,14 +376,14 @@ test("_initTasks: Unknown project type", async (t) => { }); test("_initTasks: Custom tasks", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask", afterTask: "minify"}, {name: "myOtherTask", beforeTask: "replaceVersion"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); t.deepEqual(taskRunner._taskExecutionOrder, [ @@ -371,14 +407,14 @@ test("_initTasks: Custom tasks", async (t) => { }); test("_initTasks: Custom tasks with no standard tasks", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); project.getCustomTasks = () => [ {name: "myTask"}, {name: "myOtherTask", beforeTask: "myTask"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); t.deepEqual(taskRunner._taskExecutionOrder, [ @@ -388,14 +424,14 @@ test("_initTasks: Custom tasks with no standard tasks", async (t) => { }); test("_initTasks: Custom tasks with no standard tasks and second task defining no before-/afterTask", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); project.getCustomTasks = () => [ {name: "myTask"}, {name: "myOtherTask"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -407,13 +443,13 @@ test("_initTasks: Custom tasks with no standard tasks and second task defining n }); test("_initTasks: Custom tasks with both, before- and afterTask reference", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask", beforeTask: "minify", afterTask: "replaceVersion"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -425,13 +461,13 @@ test("_initTasks: Custom tasks with both, before- and afterTask reference", asyn }); test("_initTasks: Custom tasks with no before-/afterTask reference", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -443,13 +479,13 @@ test("_initTasks: Custom tasks with no before-/afterTask reference", async (t) = }); test("_initTasks: Custom tasks without name", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: ""} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -460,13 +496,13 @@ test("_initTasks: Custom tasks without name", async (t) => { }); test("_initTasks: Custom task with name of standard tasks", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "replaceVersion", afterTask: "minify"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -478,7 +514,7 @@ test("_initTasks: Custom task with name of standard tasks", async (t) => { }); test("_initTasks: Multiple custom tasks with same name", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask", afterTask: "minify"}, @@ -486,7 +522,7 @@ test("_initTasks: Multiple custom tasks with same name", async (t) => { {name: "myTask", afterTask: "minify"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); t.deepEqual(taskRunner._taskExecutionOrder, [ @@ -511,13 +547,13 @@ test("_initTasks: Multiple custom tasks with same name", async (t) => { }); test("_initTasks: Custom tasks with unknown beforeTask", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask", beforeTask: "unknownTask"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -529,13 +565,13 @@ test("_initTasks: Custom tasks with unknown beforeTask", async (t) => { }); test("_initTasks: Custom tasks with unknown afterTask", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask", afterTask: "unknownTask"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -547,14 +583,14 @@ test("_initTasks: Custom tasks with unknown afterTask", async (t) => { }); test("_initTasks: Custom tasks is unknown", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; graph.getExtension.returns(undefined); const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask", afterTask: "minify"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -566,13 +602,13 @@ test("_initTasks: Custom tasks is unknown", async (t) => { }); test("_initTasks: Custom tasks with removed beforeTask", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getCustomTasks = () => [ {name: "myTask", beforeTask: "removedTask"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); const err = await t.throwsAsync(async () => { await taskRunner._initTasks(); @@ -585,12 +621,16 @@ test("_initTasks: Custom tasks with removed beforeTask", async (t) => { }); test("_initTasks: Create dependencies reader for all dependencies", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); + // Dependencies reader is now created lazily via getDependenciesReader + // Use forceUpdate=true to bypass the cache shortcut and actually trigger graph traversal + const readerPromise = taskRunner.getDependenciesReader(new Set(["dep.a", "dep.b"]), true); + // Verify traverseBreadthFirst was called t.is(graph.traverseBreadthFirst.callCount, 1, "ProjectGraph#traverseBreadthFirst called once"); t.is(graph.traverseBreadthFirst.getCall(0).args[0], "project.b", "ProjectGraph#traverseBreadthFirst called with correct project name for start"); @@ -617,21 +657,23 @@ test("_initTasks: Create dependencies reader for all dependencies", async (t) => }); await traversalCallback({ project: { - getName: () => "transitive.dep.a", - getReader: () => "transitive.dep.a reader", + getName: () => "dep.c", + getReader: () => "dep.c reader", } }); + // Now wait for the reader to be created + await readerPromise; t.is(resourceFactory.createReaderCollection.callCount, 1, "createReaderCollection got called once"); t.deepEqual(resourceFactory.createReaderCollection.getCall(0).args[0], { - name: "Dependency reader collection of project project.b", + name: "Reduced dependency reader collection of project project.b", readers: [ - "dep.a reader", "dep.b reader", "transitive.dep.a reader" + "dep.a reader", "dep.b reader", "dep.c reader" ] }, "createReaderCollection got called with correct arguments"); }); test("Custom task is called correctly", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); const specVersionGteStub = sinon.stub().returns(false); const mockSpecVersion = { @@ -643,7 +685,8 @@ test("Custom task is called correctly", async (t) => { graph.getExtension.returns({ getTask: () => taskStub, getSpecVersion: () => mockSpecVersion, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); t.context.taskUtil.getInterface.returns("taskUtil interface"); const project = getMockProject("module"); @@ -652,39 +695,39 @@ test("Custom task is called correctly", async (t) => { ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); t.truthy(taskRunner._tasks["myTask"], "Custom tasks has been added to task map"); t.deepEqual(taskRunner._tasks["myTask"].requiredDependencies, new Set(["dep.a", "dep.b"]), "Custom tasks requires all dependencies by default"); - const createDependencyReaderStub = sinon.stub(taskRunner, "_createDependenciesReader").resolves("dependencies"); + const createDependencyReaderStub = sinon.stub(taskRunner, "getDependenciesReader") + .resolves({getName: () => "dependencies"}); await taskRunner._tasks["myTask"].task(); - t.is(specVersionGteStub.callCount, 2, "SpecificationVersion#gte got called twice"); + t.is(specVersionGteStub.callCount, 3, "SpecificationVersion#gte got called three times"); + t.is(specVersionGteStub.getCall(2).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments on third call (task execution)"); t.is(specVersionGteStub.getCall(0).args[0], "3.0", "SpecificationVersion#gte got called with correct arguments on first call"); - t.is(specVersionGteStub.getCall(1).args[0], "3.0", - "SpecificationVersion#gte got called with correct arguments on second call"); + t.is(specVersionGteStub.getCall(1).args[0], "5.0", + "SpecificationVersion#gte got called with correct arguments on second call (differential updates check)"); - t.is(createDependencyReaderStub.callCount, 1, "_createDependenciesReader got called once"); + t.is(createDependencyReaderStub.callCount, 1, "getDependenciesReader got called once"); t.deepEqual(createDependencyReaderStub.getCall(0).args[0], new Set(["dep.a", "dep.b"]), - "_createDependenciesReader got called with correct arguments"); + "getDependenciesReader got called with correct arguments"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); - t.deepEqual(taskStub.getCall(0).args[0], { - workspace: "workspace", - dependencies: "dependencies", - options: { - projectName: "project.b", - projectNamespace: "project/b", - configuration: "configuration", - }, - taskUtil: "taskUtil interface" - }, "Task got called with one argument"); + const taskArgs = taskStub.getCall(0).args[0]; + t.is(taskArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskArgs.taskUtil, "taskUtil interface", "taskUtil is correct"); + t.is(taskArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskArgs.options.configuration, "configuration", "configuration is correct"); t.is(taskUtil.getInterface.callCount, 1, "taskUtil#getInterface got called once"); t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, @@ -692,7 +735,7 @@ test("Custom task is called correctly", async (t) => { }); test("Custom task with legacy spec version", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); const specVersionGteStub = sinon.stub().returns(false); const mockSpecVersion = { @@ -703,7 +746,8 @@ test("Custom task with legacy spec version", async (t) => { graph.getExtension.returns({ getTask: () => taskStub, getSpecVersion: () => mockSpecVersion, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); t.context.taskUtil.getInterface.returns(undefined); // simulating no taskUtil for old specVersion const project = getMockProject("module"); @@ -712,7 +756,7 @@ test("Custom task with legacy spec version", async (t) => { ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -720,31 +764,31 @@ test("Custom task with legacy spec version", async (t) => { t.deepEqual(taskRunner._tasks["myTask"].requiredDependencies, new Set(["dep.a", "dep.b"]), "Custom tasks requires all dependencies by default"); - const createDependencyReaderStub = sinon.stub(taskRunner, "_createDependenciesReader").resolves("dependencies"); + const createDependencyReaderStub = sinon.stub(taskRunner, "getDependenciesReader") + .resolves({getName: () => "dependencies"}); await taskRunner._tasks["myTask"].task(); - t.is(specVersionGteStub.callCount, 2, "SpecificationVersion#gte got called twice"); + t.is(specVersionGteStub.callCount, 3, "SpecificationVersion#gte got called three times"); + t.is(specVersionGteStub.getCall(2).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments on third call (task execution)"); t.is(specVersionGteStub.getCall(0).args[0], "3.0", "SpecificationVersion#gte got called with correct arguments on first call"); - t.is(specVersionGteStub.getCall(1).args[0], "3.0", - "SpecificationVersion#gte got called with correct arguments on second call"); + t.is(specVersionGteStub.getCall(1).args[0], "5.0", + "SpecificationVersion#gte got called with correct arguments on second call (differential updates check)"); - t.is(createDependencyReaderStub.callCount, 1, "_createDependenciesReader got called once"); + t.is(createDependencyReaderStub.callCount, 1, "getDependenciesReader got called once"); t.deepEqual(createDependencyReaderStub.getCall(0).args[0], new Set(["dep.a", "dep.b"]), - "_createDependenciesReader got called with correct arguments"); + "getDependenciesReader got called with correct arguments"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); - t.deepEqual(taskStub.getCall(0).args[0], { - workspace: "workspace", - dependencies: "dependencies", - options: { - projectName: "project.b", - projectNamespace: "project/b", - configuration: "configuration", - } - }, "Task got called with one argument"); + const taskArgs = taskStub.getCall(0).args[0]; + t.is(taskArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskArgs.options.configuration, "configuration", "configuration is correct"); t.is(taskUtil.getInterface.callCount, 1, "taskUtil#getInterface got called once"); t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, @@ -752,7 +796,7 @@ test("Custom task with legacy spec version", async (t) => { }); test("Custom task with legacy spec version and requiredDependenciesCallback", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); const specVersionGteStub = sinon.stub().returns(false); const mockSpecVersion = { @@ -764,7 +808,8 @@ test("Custom task with legacy spec version and requiredDependenciesCallback", as graph.getExtension.returns({ getTask: () => taskStub, getSpecVersion: () => mockSpecVersion, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); t.context.taskUtil.getInterface.returns(undefined); // simulating no taskUtil for old specVersion const project = getMockProject("module"); @@ -773,7 +818,7 @@ test("Custom task with legacy spec version and requiredDependenciesCallback", as ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -792,31 +837,31 @@ test("Custom task with legacy spec version and requiredDependenciesCallback", as } }, "requiredDependenciesCallback got called with expected arguments"); - const createDependencyReaderStub = sinon.stub(taskRunner, "_createDependenciesReader").resolves("dependencies"); + const createDependencyReaderStub = sinon.stub(taskRunner, "getDependenciesReader") + .resolves({getName: () => "dependencies"}); await taskRunner._tasks["myTask"].task(); - t.is(specVersionGteStub.callCount, 2, "SpecificationVersion#gte got called twice"); + t.is(specVersionGteStub.callCount, 3, "SpecificationVersion#gte got called three times"); + t.is(specVersionGteStub.getCall(2).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments on third call (task execution)"); t.is(specVersionGteStub.getCall(0).args[0], "3.0", "SpecificationVersion#gte got called with correct arguments on first call"); - t.is(specVersionGteStub.getCall(1).args[0], "3.0", - "SpecificationVersion#gte got called with correct arguments on second call"); + t.is(specVersionGteStub.getCall(1).args[0], "5.0", + "SpecificationVersion#gte got called with correct arguments on second call (differential updates check)"); - t.is(createDependencyReaderStub.callCount, 1, "_createDependenciesReader got called once"); + t.is(createDependencyReaderStub.callCount, 1, "getDependenciesReader got called once"); t.deepEqual(createDependencyReaderStub.getCall(0).args[0], new Set(["dep.b"]), - "_createDependenciesReader got called with correct arguments"); + "getDependenciesReader got called with correct arguments"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); - t.deepEqual(taskStub.getCall(0).args[0], { - workspace: "workspace", - dependencies: "dependencies", - options: { - projectName: "project.b", - projectNamespace: "project/b", - configuration: "configuration", - } - }, "Task got called with one argument"); + const taskArgs = taskStub.getCall(0).args[0]; + t.is(taskArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskArgs.options.configuration, "configuration", "configuration is correct"); t.is(taskUtil.getInterface.callCount, 1, "taskUtil#getInterface got called once"); t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, @@ -824,7 +869,7 @@ test("Custom task with legacy spec version and requiredDependenciesCallback", as }); test("Custom task with specVersion 3.0", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); const specVersionGteStub = sinon.stub().returns(true); const mockSpecVersion = { @@ -839,7 +884,8 @@ test("Custom task with specVersion 3.0", async (t) => { graph.getExtension.returns({ getTask: () => taskStub, getSpecVersion: () => mockSpecVersion, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); const project = getMockProject("module"); @@ -848,7 +894,7 @@ test("Custom task with specVersion 3.0", async (t) => { ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -884,14 +930,17 @@ test("Custom task with specVersion 3.0", async (t) => { t.truthy(taskRunner._tasks["myTask"], "Custom tasks has been added to task map"); t.deepEqual(taskRunner._tasks["myTask"].requiredDependencies, new Set(["dep.b"]), "Custom tasks requires all dependencies by default"); - const createDependencyReaderStub = sinon.stub(taskRunner, "_createDependenciesReader").resolves("dependencies"); + const createDependencyReaderStub = sinon.stub(taskRunner, "getDependenciesReader") + .resolves({getName: () => "dependencies"}); await taskRunner._tasks["myTask"].task(); - t.is(specVersionGteStub.callCount, 2, "SpecificationVersion#gte got called twice"); + t.is(specVersionGteStub.callCount, 3, "SpecificationVersion#gte got called three times"); + t.is(specVersionGteStub.getCall(2).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments on third call (task execution)"); t.is(specVersionGteStub.getCall(0).args[0], "3.0", "SpecificationVersion#gte got called with correct arguments on first call"); - t.is(specVersionGteStub.getCall(1).args[0], "3.0", - "SpecificationVersion#gte got called with correct arguments on second call"); + t.is(specVersionGteStub.getCall(1).args[0], "5.0", + "SpecificationVersion#gte got called with correct arguments on second call (differential updates check)"); t.is(taskUtil.getInterface.callCount, 2, "taskUtil#getInterface got called twice"); t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, @@ -899,29 +948,26 @@ test("Custom task with specVersion 3.0", async (t) => { t.is(taskUtil.getInterface.getCall(1).args[0], mockSpecVersion, "taskUtil#getInterface got called with correct argument on second call"); - t.is(createDependencyReaderStub.callCount, 1, "_createDependenciesReader got called once"); + t.is(createDependencyReaderStub.callCount, 1, "getDependenciesReader got called once"); t.deepEqual(createDependencyReaderStub.getCall(0).args[0], new Set(["dep.b"]), - "_createDependenciesReader got called with correct arguments"); + "getDependenciesReader got called with correct arguments"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); - t.deepEqual(taskStub.getCall(0).args[0], { - workspace: "workspace", - dependencies: "dependencies", - log: "group logger", - taskUtil, - options: { - projectName: "project.b", - projectNamespace: "project/b", - taskName: "myTask", // specVersion 3.0 feature - configuration: "configuration", - }, - }, "Task got called with one argument"); + const taskArgs = taskStub.getCall(0).args[0]; + t.is(taskArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskArgs.log, "group logger", "log is correct"); + t.deepEqual(taskArgs.taskUtil, taskUtil, "taskUtil is correct"); + t.is(taskArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskArgs.options.taskName, "myTask", "taskName is correct"); + t.is(taskArgs.options.configuration, "configuration", "configuration is correct"); }); test("Custom task with specVersion 3.0 and no requiredDependenciesCallback", async (t) => { - const {sinon, graph, taskUtil, taskRepository, projectBuildLogger, TaskRunner} = t.context; + const {sinon, graph, taskUtil, taskRepository, projectBuildLogger, buildCache, TaskRunner} = t.context; const taskStub = sinon.stub(); const specVersionGteStub = sinon.stub().returns(true); const mockSpecVersion = { @@ -935,7 +981,8 @@ test("Custom task with specVersion 3.0 and no requiredDependenciesCallback", asy getName: () => "custom task name", getTask: () => taskStub, getSpecVersion: () => mockSpecVersion, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); const project = getMockProject("module"); @@ -944,45 +991,45 @@ test("Custom task with specVersion 3.0 and no requiredDependenciesCallback", asy ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); t.truthy(taskRunner._tasks["myTask"], "Custom tasks has been added to task map"); t.deepEqual(taskRunner._tasks["myTask"].requiredDependencies, new Set(), "Custom tasks requires no dependencies by default"); - const createDependencyReaderStub = sinon.stub(taskRunner, "_createDependenciesReader").resolves("dependencies"); + const createDependencyReaderStub = sinon.stub(taskRunner, "getDependenciesReader") + .resolves({getName: () => "dependencies"}); await taskRunner._tasks["myTask"].task(); - t.is(specVersionGteStub.callCount, 2, "SpecificationVersion#gte got called twice"); + t.is(specVersionGteStub.callCount, 3, "SpecificationVersion#gte got called three times"); + t.is(specVersionGteStub.getCall(2).args[0], "3.0", + "SpecificationVersion#gte got called with correct arguments on third call (task execution)"); t.is(specVersionGteStub.getCall(0).args[0], "3.0", "SpecificationVersion#gte got called with correct arguments on first call"); - t.is(specVersionGteStub.getCall(1).args[0], "3.0", - "SpecificationVersion#gte got called with correct arguments on second call"); + t.is(specVersionGteStub.getCall(1).args[0], "5.0", + "SpecificationVersion#gte got called with correct arguments on second call (differential updates check)"); t.is(taskUtil.getInterface.callCount, 1, "taskUtil#getInterface got called once"); t.is(taskUtil.getInterface.getCall(0).args[0], mockSpecVersion, "taskUtil#getInterface got called with correct argument on first call"); - t.is(createDependencyReaderStub.callCount, 0, "_createDependenciesReader did not get called"); + t.is(createDependencyReaderStub.callCount, 0, "getDependenciesReader did not get called"); t.is(taskStub.callCount, 1, "Task got called once"); t.is(taskStub.getCall(0).args.length, 1, "Task got called with one argument"); - t.deepEqual(taskStub.getCall(0).args[0], { - workspace: "workspace", - log: "group logger", - taskUtil, - options: { - projectName: "project.b", - projectNamespace: "project/b", - taskName: "myTask", // specVersion 3.0 feature - configuration: "configuration", - }, - }, "Task got called with one argument"); + const taskArgs = taskStub.getCall(0).args[0]; + t.is(taskArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskArgs.log, "group logger", "log is correct"); + t.deepEqual(taskArgs.taskUtil, taskUtil, "taskUtil is correct"); + t.is(taskArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskArgs.options.taskName, "myTask", "taskName is correct"); + t.is(taskArgs.options.configuration, "configuration", "configuration is correct"); }); test("Multiple custom tasks with same name are called correctly", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStubA = sinon.stub(); const taskStubB = sinon.stub(); const taskStubC = sinon.stub(); @@ -1014,25 +1061,29 @@ test("Multiple custom tasks with same name are called correctly", async (t) => { getName: () => "Task Name A", getTask: () => taskStubA, getSpecVersion: () => mockSpecVersionA, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); graph.getExtension.onSecondCall().returns({ getName: () => "Task Name B", getTask: () => taskStubB, getSpecVersion: () => mockSpecVersionB, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); graph.getExtension.onThirdCall().returns({ getName: () => "Task Name C", getTask: () => taskStubC, getSpecVersion: () => mockSpecVersionC, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); graph.getExtension.onCall(3).returns({ getName: () => "Task Name D", getTask: () => taskStubD, getSpecVersion: () => mockSpecVersionD, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); const project = getMockProject("module"); project.getCustomTasks = () => [ @@ -1042,7 +1093,7 @@ test("Multiple custom tasks with same name are called correctly", async (t) => { {name: "myTask", afterTask: "myTask", configuration: "bird"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -1078,7 +1129,8 @@ test("Multiple custom tasks with same name are called correctly", async (t) => { "myTask--2", ], "Correct order of custom tasks"); - const createDependencyReaderStub = sinon.stub(taskRunner, "_createDependenciesReader").resolves("dependencies"); + const createDependencyReaderStub = sinon.stub(taskRunner, "getDependenciesReader") + .resolves({getName: () => "dependencies"}); await taskRunner.runTasks(); t.is(projectBuildLogger.setTasks.callCount, 1, "ProjectBuildLogger#setTask got called once"); @@ -1116,75 +1168,64 @@ test("Multiple custom tasks with same name are called correctly", async (t) => { t.is(taskUtil.getInterface.getCall(4).args[0], mockSpecVersionB, "taskUtil#getInterface got called with correct argument on fifth call"); - t.is(createDependencyReaderStub.callCount, 3, "_createDependenciesReader got called three times"); + t.is(createDependencyReaderStub.callCount, 4, "getDependenciesReader got called four times"); t.deepEqual(createDependencyReaderStub.getCall(0).args[0], - new Set(["dep.b"]), - "_createDependenciesReader got called with correct arguments on first call"); + new Set(["dep.a", "dep.b"]), + "getDependenciesReader got called with correct arguments on first call (runTasks init)"); t.deepEqual(createDependencyReaderStub.getCall(1).args[0], - new Set(["dep.a"]), - "_createDependenciesReader got called with correct arguments on second call"); + new Set(["dep.b"]), + "getDependenciesReader got called with correct arguments on second call (Task A)"); t.deepEqual(createDependencyReaderStub.getCall(2).args[0], + new Set(["dep.a"]), + "getDependenciesReader got called with correct arguments on third call (Task D)"); + t.deepEqual(createDependencyReaderStub.getCall(3).args[0], new Set(["dep.a", "dep.b"]), - "_createDependenciesReader got called with correct arguments on third call"); + "getDependenciesReader got called with correct arguments on fourth call (Task B)"); t.is(taskStubA.callCount, 1, "Task A got called once"); t.is(taskStubA.getCall(0).args.length, 1, "Task A got called with one argument"); - t.deepEqual(taskStubA.getCall(0).args[0], { - workspace: "workspace", - dependencies: "dependencies", - taskUtil, - options: { - projectName: "project.b", - projectNamespace: "project/b", - configuration: "cat", - } - }, "Task A got called with one argument"); + const taskAArgs = taskStubA.getCall(0).args[0]; + t.is(taskAArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskAArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskAArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskAArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskAArgs.options.configuration, "cat", "configuration is correct"); t.is(taskStubB.callCount, 1, "Task B got called once"); t.is(taskStubB.getCall(0).args.length, 1, "Task B got called with one argument"); - t.deepEqual(taskStubB.getCall(0).args[0], { - workspace: "workspace", - dependencies: "dependencies", - taskUtil, - options: { - projectName: "project.b", - projectNamespace: "project/b", - configuration: "dog", - } - }, "Task B got called with one argument"); + const taskBArgs = taskStubB.getCall(0).args[0]; + t.is(taskBArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskBArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskBArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskBArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskBArgs.options.configuration, "dog", "configuration is correct"); t.is(taskStubC.callCount, 1, "Task C got called once"); t.is(taskStubC.getCall(0).args.length, 1, "Task C got called with one argument"); - t.deepEqual(taskStubC.getCall(0).args[0], { - workspace: "workspace", - log: "group logger", - taskUtil, - options: { - projectName: "project.b", - projectNamespace: "project/b", - taskName: "myTask--3", - configuration: "bird", - } - }, "Task C got called with one argument"); + const taskCArgs = taskStubC.getCall(0).args[0]; + t.is(taskCArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskCArgs.log, "group logger", "log is correct"); + t.deepEqual(taskCArgs.taskUtil, taskUtil, "taskUtil is correct"); + t.is(taskCArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskCArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskCArgs.options.taskName, "myTask--3", "taskName is correct"); + t.is(taskCArgs.options.configuration, "bird", "configuration is correct"); t.is(taskStubD.callCount, 1, "Task D got called once"); t.is(taskStubD.getCall(0).args.length, 1, "Task D got called with one argument"); - t.deepEqual(taskStubD.getCall(0).args[0], { - workspace: "workspace", - dependencies: "dependencies", - log: "group logger", - taskUtil, - options: { - projectName: "project.b", - projectNamespace: "project/b", - taskName: "myTask--4", - configuration: "bird", - } - }, "Task D got called with one argument"); + const taskDArgs = taskStubD.getCall(0).args[0]; + t.is(taskDArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskDArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskDArgs.log, "group logger", "log is correct"); + t.deepEqual(taskDArgs.taskUtil, taskUtil, "taskUtil is correct"); + t.is(taskDArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskDArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskDArgs.options.taskName, "myTask--4", "taskName is correct"); + t.is(taskDArgs.options.configuration, "bird", "configuration is correct"); }); test("Custom task: requiredDependenciesCallback returns unknown dependency", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); const specVersionGteStub = sinon.stub().returns(true); const mockSpecVersion = { @@ -1200,7 +1241,8 @@ test("Custom task: requiredDependenciesCallback returns unknown dependency", asy getName: () => "custom.task.a", getTask: () => taskStub, getSpecVersion: () => mockSpecVersion, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); const project = getMockProject("module"); @@ -1209,7 +1251,7 @@ test("Custom task: requiredDependenciesCallback returns unknown dependency", asy ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await t.throwsAsync(taskRunner._initTasks(), { message: @@ -1221,7 +1263,7 @@ test("Custom task: requiredDependenciesCallback returns unknown dependency", asy test("Custom task: requiredDependenciesCallback returns Array instead of Set", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); const specVersionGteStub = sinon.stub().returns(true); const mockSpecVersion = { @@ -1237,7 +1279,8 @@ test("Custom task: requiredDependenciesCallback returns Array instead of Set", a getName: () => "custom.task.a", getTask: () => taskStub, getSpecVersion: () => mockSpecVersion, - getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub + getRequiredDependenciesCallback: getRequiredDependenciesCallbackStub, + getSupportsDifferentialBuildsCallback: sinon.stub().returns(() => false) }); const project = getMockProject("module"); @@ -1246,7 +1289,7 @@ test("Custom task: requiredDependenciesCallback returns Array instead of Set", a ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await t.throwsAsync(taskRunner._initTasks(), { message: @@ -1256,7 +1299,7 @@ test("Custom task: requiredDependenciesCallback returns Array instead of Set", a }); test("Custom task attached to a disabled task", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, sinon, customTask} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache, sinon, customTask} = t.context; const project = getMockProject("application"); const customTaskFnStub = sinon.stub(); @@ -1269,7 +1312,7 @@ test("Custom task attached to a disabled task", async (t) => { customTask.getTask = () => customTaskFnStub; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner.runTasks(); @@ -1296,7 +1339,7 @@ test("Custom task attached to a disabled task", async (t) => { }); test.serial("_addTask", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); taskRepository.getTask.withArgs("standardTask").resolves({ @@ -1305,7 +1348,7 @@ test.serial("_addTask", async (t) => { const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -1326,24 +1369,20 @@ test.serial("_addTask", async (t) => { t.is(taskRepository.getTask.getCall(0).args[0], "standardTask", "taskRepository#getTask got called with correct argument"); t.is(taskStub.callCount, 1, "Task got called once"); - t.deepEqual(taskStub.getCall(0).args[0], { - workspace: "workspace", - // No dependencies - options: { - projectName: "project.b", - projectNamespace: "project/b" - }, - taskUtil - }, "Task got called with correct arguments"); + const taskCallArgs = taskStub.getCall(0).args[0]; + t.is(taskCallArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskCallArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskCallArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskCallArgs.taskUtil, taskUtil, "taskUtil is correct"); }); test.serial("_addTask with options", async (t) => { - const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {sinon, graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const taskStub = sinon.stub(); const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -1361,33 +1400,31 @@ test.serial("_addTask with options", async (t) => { t.truthy(taskRunner._tasks["standardTask"].task, "Task function got set correctly"); t.deepEqual(taskRunner._taskExecutionOrder, ["standardTask"], "Task got added to execution order"); - const createDependencyReaderStub = sinon.stub(taskRunner, "_createDependenciesReader").resolves("dependencies"); - await taskRunner._tasks["standardTask"].task({ - workspace: "workspace", - dependencies: "dependencies", - }); + // Warm the cache (normally done by runTasks) + await taskRunner.getDependenciesReader(new Set(["dep.a", "dep.b"]), true); + const createDependencyReaderStub = sinon.stub(taskRunner, "getDependenciesReader") + .resolves({getName: () => "dependencies"}); + // Call the task wrapper without parameters (it creates workspace/dependencies internally) + await taskRunner._tasks["standardTask"].task(); t.is(taskRepository.getTask.callCount, 0, "taskRepository#getTask did not get called"); - t.is(createDependencyReaderStub.callCount, 0, "_createDependenciesReader did not get called"); + t.is(createDependencyReaderStub.callCount, 0, "getDependenciesReader did not get called (using cached reader)"); t.is(taskStub.callCount, 1, "Task got called once"); - t.deepEqual(taskStub.getCall(0).args[0], { - workspace: "workspace", - dependencies: taskRunner._allDependenciesReader, - options: { - projectName: "project.b", - projectNamespace: "project/b", - myTaskOption: "cat" - }, - taskUtil - }, "Task got called with correct arguments"); + const taskCallArgs = taskStub.getCall(0).args[0]; + t.is(taskCallArgs.workspace.constructor.name, "MonitoredReader", "workspace is MonitoredReader"); + t.is(taskCallArgs.dependencies.constructor.name, "MonitoredReader", "dependencies is MonitoredReader"); + t.is(taskCallArgs.options.projectName, "project.b", "projectName is correct"); + t.is(taskCallArgs.options.projectNamespace, "project/b", "projectNamespace is correct"); + t.is(taskCallArgs.options.myTaskOption, "cat", "myTaskOption is correct"); + t.is(taskCallArgs.taskUtil, taskUtil, "taskUtil is correct"); }); test("_addTask: Duplicate task", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -1405,10 +1442,10 @@ test("_addTask: Duplicate task", async (t) => { }); test("_addTask: Task already added to execution order", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); @@ -1424,13 +1461,13 @@ test("_addTask: Task already added to execution order", async (t) => { }); test("getRequiredDependencies: Custom Task", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); project.getCustomTasks = () => [ {name: "myTask"} ]; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); t.deepEqual(await taskRunner.getRequiredDependencies(), new Set([]), "Project with custom task >= specVersion 3.0 and no requiredDependenciesCallback " + @@ -1438,72 +1475,72 @@ test("getRequiredDependencies: Custom Task", async (t) => { }); test("getRequiredDependencies: Default application", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("application"); project.getBundles = () => []; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); t.deepEqual(await taskRunner.getRequiredDependencies(), new Set([]), "Default application project does not require dependencies"); }); test("getRequiredDependencies: Default component", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("component"); project.getBundles = () => []; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); t.deepEqual(await taskRunner.getRequiredDependencies(), new Set([]), "Default component project does not require dependencies"); }); test("getRequiredDependencies: Default library", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("library"); project.getBundles = () => []; const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); t.deepEqual(await taskRunner.getRequiredDependencies(), new Set(["dep.a", "dep.b"]), "Default library project requires dependencies"); }); test("getRequiredDependencies: Default theme-library", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("theme-library"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); t.deepEqual(await taskRunner.getRequiredDependencies(), new Set(["dep.a", "dep.b"]), "Default theme-library project requires dependencies"); }); test("getRequiredDependencies: Default module", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger} = t.context; + const {graph, taskUtil, taskRepository, TaskRunner, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); t.deepEqual(await taskRunner.getRequiredDependencies(), new Set([]), "Default module project does not require dependencies"); }); -test("_createDependenciesReader", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger} = t.context; +test("getDependenciesReader", async (t) => { + const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); graph.traverseBreadthFirst.reset(); // Ignore the call in initTask resourceFactory.createReaderCollection.reset(); // Ignore the call in initTask - resourceFactory.createReaderCollection.returns("custom reader collection"); - const res = await taskRunner._createDependenciesReader(new Set(["dep.a"])); + resourceFactory.createReaderCollection.returns({getName: () => "custom reader collection"}); + const res = await taskRunner.getDependenciesReader(new Set(["dep.a"])); t.is(graph.traverseBreadthFirst.callCount, 1, "ProjectGraph#traverseBreadthFirst got called once"); t.is(graph.traverseBreadthFirst.getCall(0).args[0], "project.b", @@ -1550,42 +1587,45 @@ test("_createDependenciesReader", async (t) => { "dep.a reader", "dep.b reader", "dep.c reader" ] }, "createReaderCollection got called with correct arguments"); - t.is(res, "custom reader collection", "Returned expected value"); + t.is(res.getName(), "custom reader collection", "Returned expected value"); }); -test("_createDependenciesReader: All dependencies required", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger} = t.context; +test("getDependenciesReader: All dependencies required", async (t) => { + const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); - graph.traverseBreadthFirst.reset(); // Ignore the call in initTask - resourceFactory.createReaderCollection.reset(); // Ignore the call in initTask - resourceFactory.createReaderCollection.returns("custom reader collection"); - const res = await taskRunner._createDependenciesReader(new Set(["dep.a", "dep.b"])); + // Initialize the cache by calling getDependenciesReader with a subset first to avoid the shortcut + // Then call with forceUpdate to populate the cache + const cachedReader = await taskRunner.getDependenciesReader(new Set(["dep.a", "dep.b"]), true); + graph.traverseBreadthFirst.reset(); // Ignore the call in init + resourceFactory.createReaderCollection.reset(); // Ignore the call in init + resourceFactory.createReaderCollection.returns({getName: () => "custom reader collection"}); + const res = await taskRunner.getDependenciesReader(new Set(["dep.a", "dep.b"])); t.is(graph.traverseBreadthFirst.callCount, 0, "ProjectGraph#traverseBreadthFirst did not get called again"); t.is(resourceFactory.createReaderCollection.callCount, 0, "createReaderCollection did not get called again"); - t.is(res, "reader collection", "Shared (all-)dependency reader returned"); + t.is(res, cachedReader, "Shared (all-)dependency reader returned"); }); -test("_createDependenciesReader: No dependencies required", async (t) => { - const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger} = t.context; +test("getDependenciesReader: No dependencies required", async (t) => { + const {graph, taskUtil, taskRepository, TaskRunner, resourceFactory, projectBuildLogger, buildCache} = t.context; const project = getMockProject("module"); const taskRunner = new TaskRunner({ - project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildConfig + project, graph, taskUtil, taskRepository, log: projectBuildLogger, buildCache, buildConfig }); await taskRunner._initTasks(); graph.traverseBreadthFirst.reset(); // Ignore the call in initTask resourceFactory.createReaderCollection.reset(); // Ignore the call in initTask - resourceFactory.createReaderCollection.returns("custom reader collection"); - const res = await taskRunner._createDependenciesReader(new Set()); + resourceFactory.createReaderCollection.returns({getName: () => "custom reader collection"}); + const res = await taskRunner.getDependenciesReader(new Set()); t.is(graph.traverseBreadthFirst.callCount, 1, "ProjectGraph#traverseBreadthFirst got called once"); t.is(resourceFactory.createReaderCollection.callCount, 1, "createReaderCollection got called once"); t.deepEqual(resourceFactory.createReaderCollection.getCall(0).args[0].readers, [], "createReaderCollection got called with no readers"); - t.is(res, "custom reader collection", "Shared (all-)dependency reader returned"); + t.is(res.getName(), "custom reader collection", "Shared (all-)dependency reader returned"); }); diff --git a/packages/project/test/lib/build/cache/BuildTaskCache.js b/packages/project/test/lib/build/cache/BuildTaskCache.js new file mode 100644 index 00000000000..a72a9cc234d --- /dev/null +++ b/packages/project/test/lib/build/cache/BuildTaskCache.js @@ -0,0 +1,494 @@ +import test from "ava"; +import sinon from "sinon"; +import BuildTaskCache from "../../../../lib/build/cache/BuildTaskCache.js"; + +// Helper to create mock readers +function createMockReader(resources = []) { + const resourceMap = new Map(resources.map((r) => [r.getPath(), r])); + return { + byGlob: sinon.stub().callsFake(async (pattern) => { + // Simple pattern matching for tests + if (pattern === "/**/*") { + return Array.from(resourceMap.values()); + } + return resources.filter((r) => r.getPath().includes(pattern.replace(/[*]/g, ""))); + }), + byPath: sinon.stub().callsFake(async (path) => { + return resourceMap.get(path) || null; + }) + }; +} + +// Helper to create mock resources +function createMockResource(path, content = "test content", hash = null) { + const actualHash = hash || `hash-${path}`; + return { + getPath: () => path, + getOriginalPath: () => path, + getBuffer: async () => Buffer.from(content), + getIntegrity: async () => actualHash, + getLastModified: () => 1000, + getSize: async () => content.length, + getInode: () => 1 + }; +} + +test.afterEach.always(() => { + sinon.restore(); +}); + +// ===== CREATION AND INITIALIZATION TESTS ===== + +test("Create BuildTaskCache instance", (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + t.truthy(cache, "BuildTaskCache instance created"); + t.is(cache.getTaskName(), "testTask", "Task name matches"); + t.is(cache.getSupportsDifferentialBuilds(), false, "Differential updates disabled"); +}); + +test("Create with differential updates enabled", (t) => { + const cache = new BuildTaskCache("test.project", "testTask", true); + + t.is(cache.getSupportsDifferentialBuilds(), true, "Differential updates enabled"); +}); + +test("fromCache: restore BuildTaskCache from cached data", (t) => { + const projectRequests = { + requestSetGraph: { + nodes: [], + nextId: 1 + }, + rootIndices: [], + deltaIndices: [], + unusedAtLeastOnce: false + }; + + const dependencyRequests = { + requestSetGraph: { + nodes: [], + nextId: 1 + }, + rootIndices: [], + deltaIndices: [], + unusedAtLeastOnce: false + }; + + const cache = BuildTaskCache.fromCache("test.project", "testTask", false, + projectRequests, dependencyRequests); + + t.truthy(cache, "Cache restored from cached data"); + t.is(cache.getTaskName(), "testTask", "Task name preserved"); + t.is(cache.getSupportsDifferentialBuilds(), false, "Differential updates setting preserved"); +}); + +// ===== METADATA ACCESS TESTS ===== + +test("getTaskName: returns task name", (t) => { + const cache = new BuildTaskCache("test.project", "myTask", false); + + t.is(cache.getTaskName(), "myTask", "Task name returned"); +}); + +test("getSupportsDifferentialBuilds: returns correct value", (t) => { + const cache1 = new BuildTaskCache("test.project", "task1", false); + const cache2 = new BuildTaskCache("test.project", "task2", true); + + t.false(cache1.getSupportsDifferentialBuilds(), "Returns false when disabled"); + t.true(cache2.getSupportsDifferentialBuilds(), "Returns true when enabled"); +}); + +test("hasNewOrModifiedCacheEntries: initially true for new instance", (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + // A new instance has new entries that need to be written + t.true(cache.hasNewOrModifiedCacheEntries(), "New instance has entries to write"); +}); + +test("hasNewOrModifiedCacheEntries: true after recording requests", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const resource = createMockResource("/test.js"); + const projectReader = createMockReader([resource]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, undefined, projectReader, dependencyReader); + + t.true(cache.hasNewOrModifiedCacheEntries(), "Has new entries after recording"); +}); + +// ===== SIGNATURE TESTS ===== + +test("getProjectIndexSignatures: returns signatures after recording", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const resource = createMockResource("/test.js"); + const projectReader = createMockReader([resource]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, undefined, projectReader, dependencyReader); + + const signatures = cache.getProjectIndexSignatures(); + + t.true(Array.isArray(signatures), "Returns array"); + t.true(signatures.length > 0, "Has at least one signature"); + t.is(typeof signatures[0], "string", "Signature is a string"); +}); + +test("getDependencyIndexSignatures: returns signatures after recording", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const projectResource = createMockResource("/test.js"); + const depResource = createMockResource("/dep.js"); + const projectReader = createMockReader([projectResource]); + const dependencyReader = createMockReader([depResource]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + const dependencyRequests = { + paths: new Set(["/dep.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, dependencyRequests, projectReader, dependencyReader); + + const signatures = cache.getDependencyIndexSignatures(); + + t.true(Array.isArray(signatures), "Returns array"); + t.true(signatures.length > 0, "Has at least one signature"); + t.is(typeof signatures[0], "string", "Signature is a string"); +}); + +// ===== REQUEST RECORDING TESTS ===== + +test("recordRequests: handles project requests only", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const resource = createMockResource("/test.js"); + const projectReader = createMockReader([resource]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + const [projectSig, depSig] = await cache.recordRequests( + projectRequests, undefined, projectReader, dependencyReader); + + t.is(typeof projectSig, "string", "Project signature returned"); + t.is(typeof depSig, "string", "Dependency signature returned"); + t.true(projectSig.length > 0, "Project signature not empty"); + t.true(depSig.length > 0, "Dependency signature not empty"); +}); + +test("recordRequests: handles both project and dependency requests", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const projectResource = createMockResource("/test.js"); + const depResource = createMockResource("/dep.js"); + const projectReader = createMockReader([projectResource]); + const dependencyReader = createMockReader([depResource]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + const dependencyRequests = { + paths: new Set(["/dep.js"]), + patterns: new Set() + }; + + const [projectSig, depSig] = await cache.recordRequests( + projectRequests, dependencyRequests, projectReader, dependencyReader); + + t.is(typeof projectSig, "string", "Project signature returned"); + t.is(typeof depSig, "string", "Dependency signature returned"); +}); + +test("recordRequests: handles glob patterns", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const resource1 = createMockResource("/src/test1.js"); + const resource2 = createMockResource("/src/test2.js"); + const projectReader = createMockReader([resource1, resource2]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(), + patterns: new Set(["/src/**/*.js"]) + }; + + const [projectSig, depSig] = await cache.recordRequests( + projectRequests, undefined, projectReader, dependencyReader); + + t.is(typeof projectSig, "string", "Project signature returned"); + t.is(typeof depSig, "string", "Dependency signature returned"); +}); + +test("recordRequests: handles empty requests", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const projectReader = createMockReader([]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(), + patterns: new Set() + }; + + const [projectSig, depSig] = await cache.recordRequests( + projectRequests, undefined, projectReader, dependencyReader); + + t.is(typeof projectSig, "string", "Project signature returned"); + t.is(typeof depSig, "string", "Dependency signature returned"); +}); + +// ===== INDEX UPDATE TESTS ===== + +test("updateProjectIndices: processes changed resources", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + // First, record some requests + const resource = createMockResource("/test.js", "initial content"); + const projectReader = createMockReader([resource]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, undefined, projectReader, dependencyReader); + + // Now update with changed resource + const updatedResource = createMockResource("/test.js", "updated content", "new-hash"); + const updatedReader = createMockReader([updatedResource]); + + const changed = await cache.updateProjectIndices(updatedReader, ["/test.js"]); + + t.is(typeof changed, "boolean", "Returns boolean"); +}); + +test("updateDependencyIndices: processes changed dependencies", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + // First, record some requests + const projectResource = createMockResource("/test.js"); + const depResource = createMockResource("/dep.js", "initial"); + const projectReader = createMockReader([projectResource]); + const dependencyReader = createMockReader([depResource]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + const dependencyRequests = { + paths: new Set(["/dep.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, dependencyRequests, projectReader, dependencyReader); + + // Now update with changed dependency + const updatedDepResource = createMockResource("/dep.js", "updated", "new-dep-hash"); + const updatedDepReader = createMockReader([updatedDepResource]); + + const changed = await cache.updateDependencyIndices(updatedDepReader, ["/dep.js"]); + + t.is(typeof changed, "boolean", "Returns boolean"); +}); + +test("refreshDependencyIndices: refreshes all dependency indices", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + // First, record some requests + const projectResource = createMockResource("/test.js"); + const depResource = createMockResource("/dep.js"); + const projectReader = createMockReader([projectResource]); + const dependencyReader = createMockReader([depResource]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + const dependencyRequests = { + paths: new Set(["/dep.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, dependencyRequests, projectReader, dependencyReader); + + // Refresh all indices - returns undefined when processing changes, or false if no requests + const result = await cache.refreshDependencyIndices(dependencyReader); + + t.true(result === undefined || result === false, "Returns undefined or false"); +}); + +// ===== DELTA TESTS (for differential updates) ===== + +test("getProjectIndexDeltas: returns deltas when enabled", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", true); + + const resource = createMockResource("/test.js"); + const projectReader = createMockReader([resource]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, undefined, projectReader, dependencyReader); + + const deltas = cache.getProjectIndexDeltas(); + + t.true(deltas instanceof Map, "Returns Map"); +}); + +test("getDependencyIndexDeltas: returns deltas when enabled", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", true); + + const projectResource = createMockResource("/test.js"); + const depResource = createMockResource("/dep.js"); + const projectReader = createMockReader([projectResource]); + const dependencyReader = createMockReader([depResource]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + const dependencyRequests = { + paths: new Set(["/dep.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, dependencyRequests, projectReader, dependencyReader); + + const deltas = cache.getDependencyIndexDeltas(); + + t.true(deltas instanceof Map, "Returns Map"); +}); + +// ===== SERIALIZATION TESTS ===== + +test("toCacheObjects: returns cache objects", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const resource = createMockResource("/test.js"); + const projectReader = createMockReader([resource]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests, undefined, projectReader, dependencyReader); + + const [projectCache, dependencyCache] = cache.toCacheObjects(); + + t.truthy(projectCache, "Project cache object exists"); + t.truthy(dependencyCache, "Dependency cache object exists"); + t.truthy(projectCache.requestSetGraph, "Has request set graph"); + t.true(Array.isArray(projectCache.rootIndices), "Has root indices array"); +}); + +test("toCacheObjects: can restore from serialized data", async (t) => { + const cache1 = new BuildTaskCache("test.project", "testTask", false); + + const resource = createMockResource("/test.js"); + const projectReader = createMockReader([resource]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/test.js"]), + patterns: new Set() + }; + + await cache1.recordRequests(projectRequests, undefined, projectReader, dependencyReader); + + const [projectCache, dependencyCache] = cache1.toCacheObjects(); + + // Restore from cache + const cache2 = BuildTaskCache.fromCache("test.project", "testTask", false, + projectCache, dependencyCache); + + t.truthy(cache2, "Cache restored"); + t.is(cache2.getTaskName(), "testTask", "Task name preserved"); +}); + +// ===== EDGE CASES ===== + +test("Create with empty project name", (t) => { + const cache = new BuildTaskCache("", "testTask", false); + + t.truthy(cache, "Cache created with empty project name"); + t.is(cache.getTaskName(), "testTask", "Task name still accessible"); +}); + +test("Multiple recordRequests calls accumulate", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const resource1 = createMockResource("/test1.js"); + const resource2 = createMockResource("/test2.js"); + const projectReader = createMockReader([resource1, resource2]); + const dependencyReader = createMockReader([]); + + // First request + const projectRequests1 = { + paths: new Set(["/test1.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests1, undefined, projectReader, dependencyReader); + + const sigsBefore = cache.getProjectIndexSignatures(); + + // Second request with different resources + const projectRequests2 = { + paths: new Set(["/test2.js"]), + patterns: new Set() + }; + + await cache.recordRequests(projectRequests2, undefined, projectReader, dependencyReader); + + const sigsAfter = cache.getProjectIndexSignatures(); + + t.true(sigsAfter.length >= sigsBefore.length, "Signatures accumulated"); +}); + +test("Handles non-existent resource paths", async (t) => { + const cache = new BuildTaskCache("test.project", "testTask", false); + + const projectReader = createMockReader([]); + const dependencyReader = createMockReader([]); + + const projectRequests = { + paths: new Set(["/nonexistent.js"]), + patterns: new Set() + }; + + const [projectSig, depSig] = await cache.recordRequests( + projectRequests, undefined, projectReader, dependencyReader); + + t.is(typeof projectSig, "string", "Still returns signature"); + t.is(typeof depSig, "string", "Still returns dependency signature"); +}); diff --git a/packages/project/test/lib/build/cache/ProjectBuildCache.js b/packages/project/test/lib/build/cache/ProjectBuildCache.js new file mode 100644 index 00000000000..93f05251069 --- /dev/null +++ b/packages/project/test/lib/build/cache/ProjectBuildCache.js @@ -0,0 +1,608 @@ +import test from "ava"; +import sinon from "sinon"; +import ProjectBuildCache from "../../../../lib/build/cache/ProjectBuildCache.js"; + +// Helper to create mock Project instances +function createMockProject(name = "test.project", id = "test-project-id") { + const stages = new Map(); + let currentStage = {getId: () => "initial"}; + let resultStageReader = null; + + // Create a reusable reader with both byGlob and byPath + const createReader = () => ({ + byGlob: sinon.stub().resolves([]), + byPath: sinon.stub().resolves(null) + }); + + const projectResources = { + getStage: sinon.stub().returns({ + getId: () => currentStage.id || "initial", + getWriter: sinon.stub().returns({ + byGlob: sinon.stub().resolves([]) + }) + }), + useStage: sinon.stub().callsFake((stageName) => { + currentStage = {id: stageName}; + }), + setStage: sinon.stub().callsFake((stageName, stage) => { + stages.set(stageName, stage); + }), + initStages: sinon.stub(), + setResultStage: sinon.stub().callsFake((reader) => { + resultStageReader = reader; + }), + useResultStage: sinon.stub().callsFake(() => { + currentStage = {id: "result"}; + }), + getResourceTagOperations: sinon.stub().returns({ + projectTagOperations: new Map(), + buildTagOperations: new Map(), + }), + buildFinished: sinon.stub(), + }; + + return { + getName: () => name, + getId: () => id, + getSourceReader: sinon.stub().callsFake(() => createReader()), + getReader: sinon.stub().callsFake(() => createReader()), + getProjectResources: () => projectResources, + _getCurrentStage: () => currentStage, + _getResultStageReader: () => resultStageReader + }; +} + +// Helper to create mock CacheManager instances +function createMockCacheManager() { + return { + readIndexCache: sinon.stub().resolves(null), + writeIndexCache: sinon.stub().resolves(), + readStageCache: sinon.stub().resolves(null), + writeStageCache: sinon.stub().resolves(), + readResultMetadata: sinon.stub().resolves(null), + writeResultMetadata: sinon.stub().resolves(), + readTaskMetadata: sinon.stub().resolves(null), + writeTaskMetadata: sinon.stub().resolves(), + writeStageResource: sinon.stub().resolves() + }; +} + +// Helper to create mock Resource instances +function createMockResource(path, integrity = "test-hash", lastModified = 1000, size = 100, inode = 1) { + return { + getOriginalPath: () => path, + getPath: () => path, + getIntegrity: async () => integrity, + getLastModified: () => lastModified, + getSize: async () => size, + getInode: () => inode, + getBuffer: async () => Buffer.from("test content"), + getStream: () => null + }; +} + +test.afterEach.always(() => { + sinon.restore(); +}); + +// ===== CREATION AND INITIALIZATION TESTS ===== + +test("Create ProjectBuildCache instance", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const buildSignature = "test-signature"; + + const cache = await ProjectBuildCache.create(project, buildSignature, cacheManager); + + t.truthy(cache, "ProjectBuildCache instance created"); + t.true(cacheManager.readIndexCache.called, "Index cache was attempted to be loaded"); +}); + +test("Create with existing index cache", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const buildSignature = "test-signature"; + + const resource = createMockResource("/test.js", "hash1", 1000, 100, 1); + project.getSourceReader.callsFake(() => ({ + byGlob: sinon.stub().resolves([resource]) + })); + + const indexCache = { + version: "1.0", + indexTree: { + version: 1, + indexTimestamp: 1000, + root: { + hash: "hash1", + children: { + "test.js": { + hash: "hash1", + metadata: { + path: "/test.js", + lastModified: 1000, + size: 100, + inode: 1 + } + } + } + } + }, + tasks: [["task1", false]] + }; + + // Mock task metadata responses + cacheManager.readTaskMetadata.callsFake((projectId, buildSig, taskName, type) => { + if (type === "project") { + return Promise.resolve({ + requestSetGraph: { + nodes: [], + nextId: 1 + }, + rootIndices: [], + deltaIndices: [], + unusedAtLeastOnce: false + }); + } else if (type === "dependencies") { + return Promise.resolve({ + requestSetGraph: { + nodes: [], + nextId: 1 + }, + rootIndices: [], + deltaIndices: [], + unusedAtLeastOnce: false + }); + } + return Promise.resolve(null); + }); + + cacheManager.readIndexCache.resolves(indexCache); + + const cache = await ProjectBuildCache.create(project, buildSignature, cacheManager); + + t.truthy(cache, "Cache created with existing index"); + const taskCache = cache.getTaskCache("task1"); + t.truthy(taskCache, "Task cache loaded from index"); +}); + +test("Initialize without any cache", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const buildSignature = "test-signature"; + + const cache = await ProjectBuildCache.create(project, buildSignature, cacheManager); + + t.false(cache.isFresh(), "Cache is not fresh when empty"); +}); + +test("isFresh returns false for empty cache", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + t.false(cache.isFresh(), "Empty cache is not fresh"); +}); + +test("getTaskCache returns undefined for non-existent task", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + t.is(cache.getTaskCache("nonexistent"), undefined, "Returns undefined"); +}); + +// ===== TASK MANAGEMENT TESTS ===== + +test("setTasks initializes project stages", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + await cache.setTasks(["task1", "task2", "task3"]); + + t.true(project.getProjectResources().initStages.calledOnce, "initStages called once"); + t.deepEqual( + project.getProjectResources().initStages.firstCall.args[0], + ["task/task1", "task/task2", "task/task3"], + "Stage names generated correctly" + ); +}); + +test("setTasks with empty task list", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + await cache.setTasks([]); + + t.true(project.getProjectResources().initStages.calledWith([]), "initStages called with empty array"); +}); + +test("allTasksCompleted switches to result stage", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + const changedPaths = await cache.allTasksCompleted(); + + t.true(project.getProjectResources().useResultStage.calledOnce, "useResultStage called"); + t.true(Array.isArray(changedPaths), "Returns array of changed paths"); + t.true(cache.isFresh(), "Cache is fresh after all tasks completed"); +}); + +test("allTasksCompleted returns changed resource paths", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + + // Create cache with existing index to be able to track changes + const resource = createMockResource("/test.js", "hash1", 1000, 100, 1); + project.getSourceReader.callsFake(() => ({ + byGlob: sinon.stub().resolves([resource]) + })); + + const indexCache = { + version: "1.0", + indexTree: { + version: 1, + indexTimestamp: 1000, + root: { + hash: "hash1", + children: { + "test.js": { + hash: "hash1", + metadata: { + path: "/test.js", + lastModified: 1000, + size: 100, + inode: 1 + } + } + } + } + }, + tasks: [] + }; + cacheManager.readIndexCache.resolves(indexCache); + + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + // Simulate some changes - change tracking happens during prepareProjectBuildAndValidateCache + cache.projectSourcesChanged(["/test.js"]); + + const changedPaths = await cache.allTasksCompleted(); + + t.true(Array.isArray(changedPaths), "Returns array of changed paths"); +}); + +// ===== TASK EXECUTION AND RECORDING TESTS ===== + +test("prepareTaskExecutionAndValidateCache: task needs execution when no cache exists", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + await cache.setTasks(["myTask"]); + const canUseCache = await cache.prepareTaskExecutionAndValidateCache("myTask"); + + t.false(canUseCache, "Task cannot use cache"); + t.true(project.getProjectResources().useStage.calledWith("task/myTask"), "Project switched to task stage"); +}); + +test("prepareTaskExecutionAndValidateCache: switches project to correct stage", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + await cache.setTasks(["task1", "task2"]); + await cache.prepareTaskExecutionAndValidateCache("task2"); + + t.true(project.getProjectResources().useStage.calledWith("task/task2"), "Switched to task2 stage"); +}); + +test("recordTaskResult: creates task cache", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + await cache.setTasks(["newTask"]); + await cache.prepareTaskExecutionAndValidateCache("newTask"); + + const projectRequests = {paths: new Set(["/input.js"]), patterns: new Set()}; + const dependencyRequests = {paths: new Set(), patterns: new Set()}; + + await cache.recordTaskResult("newTask", projectRequests, dependencyRequests, null, false); + + const taskCache = cache.getTaskCache("newTask"); + t.truthy(taskCache, "Task cache created"); +}); + +test("recordTaskResult with empty requests", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + await cache.setTasks(["task1"]); + await cache.prepareTaskExecutionAndValidateCache("task1"); + + const projectRequests = {paths: new Set(), patterns: new Set()}; + const dependencyRequests = {paths: new Set(), patterns: new Set()}; + + await cache.recordTaskResult("task1", projectRequests, dependencyRequests, null, false); + + const taskCache = cache.getTaskCache("task1"); + t.truthy(taskCache, "Task cache created even with no requests"); +}); + +// ===== RESOURCE CHANGE TRACKING TESTS ===== + +test("projectSourcesChanged: marks cache as requiring validation", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + + // Create cache with existing index + const resource = createMockResource("/test.js", "hash1", 1000, 100, 1); + project.getSourceReader.callsFake(() => ({ + byGlob: sinon.stub().resolves([resource]) + })); + + const indexCache = { + version: "1.0", + indexTree: { + version: 1, + indexTimestamp: 1000, + root: { + hash: "hash1", + children: { + "test.js": { + hash: "hash1", + metadata: { + path: "/test.js", + lastModified: 1000, + size: 100, + inode: 1 + } + } + } + } + }, + tasks: [] + }; + cacheManager.readIndexCache.resolves(indexCache); + + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + cache.projectSourcesChanged(["/test.js"]); + + t.false(cache.isFresh(), "Cache is not fresh after changes"); +}); + +test("dependencyResourcesChanged: marks cache as requiring validation", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + + // Create cache with existing index + const resource = createMockResource("/test.js", "hash1", 1000, 100, 1); + project.getSourceReader.callsFake(() => ({ + byGlob: sinon.stub().resolves([resource]) + })); + + const indexCache = { + version: "1.0", + indexTree: { + version: 1, + indexTimestamp: 1000, + root: { + hash: "hash1", + children: { + "test.js": { + hash: "hash1", + metadata: { + path: "/test.js", + lastModified: 1000, + size: 100, + inode: 1 + } + } + } + } + }, + tasks: [] + }; + cacheManager.readIndexCache.resolves(indexCache); + + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + cache.dependencyResourcesChanged(["/dep.js"]); + + t.false(cache.isFresh(), "Cache is not fresh after dependency changes"); +}); + +test("projectSourcesChanged: tracks multiple changes", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + cache.projectSourcesChanged(["/test1.js"]); + cache.projectSourcesChanged(["/test2.js", "/test3.js"]); + + // Changes are tracked internally + t.pass("Multiple changes tracked"); +}); + +test("prepareProjectBuildAndValidateCache: returns false for empty cache", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + const mockDependencyReader = { + byGlob: sinon.stub().resolves([]), + byPath: sinon.stub().resolves(null) + }; + + const result = await cache.prepareProjectBuildAndValidateCache(mockDependencyReader); + + t.is(result, false, "Returns false for empty cache"); +}); + +test("_refreshDependencyIndices: updates dependency indices", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + + // Create cache with existing task + const resource = createMockResource("/test.js", "hash1", 1000, 100, 1); + project.getSourceReader.callsFake(() => ({ + byGlob: sinon.stub().resolves([resource]) + })); + + const indexCache = { + version: "1.0", + indexTree: { + version: 1, + indexTimestamp: 1000, + root: { + hash: "hash1", + children: { + "test.js": { + hash: "hash1", + metadata: { + path: "/test.js", + lastModified: 1000, + size: 100, + inode: 1 + } + } + } + } + }, + tasks: [["task1", false]] + }; + + // Mock task metadata responses + cacheManager.readTaskMetadata.callsFake((projectId, buildSig, taskName, type) => { + if (type === "project") { + return Promise.resolve({ + requestSetGraph: { + nodes: [], + nextId: 1 + }, + rootIndices: [], + deltaIndices: [], + unusedAtLeastOnce: false + }); + } else if (type === "dependencies") { + return Promise.resolve({ + requestSetGraph: { + nodes: [], + nextId: 1 + }, + rootIndices: [], + deltaIndices: [], + unusedAtLeastOnce: false + }); + } + return Promise.resolve(null); + }); + + cacheManager.readIndexCache.resolves(indexCache); + + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + const mockDependencyReader = { + byGlob: sinon.stub().resolves([]), + byPath: sinon.stub().resolves(null) + }; + + await cache._refreshDependencyIndices(mockDependencyReader); + + t.pass("Dependency indices refreshed"); +}); + +// ===== CACHE STORAGE TESTS ===== + +test("writeCache: writes index and stage caches", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + project.getReader.returns({ + byGlob: sinon.stub().resolves([]), + byPath: sinon.stub().resolves(null) + }); + + await cache.writeCache(); + + t.true(cacheManager.writeIndexCache.called, "Index cache written"); +}); + +test("writeCache: skips writing unchanged caches", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + + // Create cache with existing index + const resource = createMockResource("/test.js", "hash1", 1000, 100, 1); + project.getSourceReader.callsFake(() => ({ + byGlob: sinon.stub().resolves([resource]) + })); + + const indexCache = { + version: "1.0", + indexTree: { + version: 1, + indexTimestamp: 1000, + root: { + hash: "hash1", + children: { + "test.js": { + hash: "hash1", + metadata: { + path: "/test.js", + lastModified: 1000, + size: 100, + inode: 1 + } + } + } + } + }, + tasks: [] + }; + cacheManager.readIndexCache.resolves(indexCache); + + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + project.getReader.returns({ + byGlob: sinon.stub().resolves([]), + byPath: sinon.stub().resolves(null) + }); + + // Write cache multiple times + await cache.writeCache(); + const firstCallCount = cacheManager.writeIndexCache.callCount; + + await cache.writeCache(); + const secondCallCount = cacheManager.writeIndexCache.callCount; + + t.is(secondCallCount, firstCallCount + 1, "Index written each time"); +}); + +// ===== EDGE CASES ===== + +test("Create cache with empty project name", async (t) => { + const project = createMockProject("", "empty-project"); + const cacheManager = createMockCacheManager(); + + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + t.truthy(cache, "Cache created with empty project name"); +}); + +test("Empty task list doesn't fail", async (t) => { + const project = createMockProject(); + const cacheManager = createMockCacheManager(); + const cache = await ProjectBuildCache.create(project, "sig", cacheManager); + + await cache.setTasks([]); + + t.true(project.getProjectResources().initStages.calledWith([]), "initStages called with empty array"); +}); diff --git a/packages/project/test/lib/build/cache/ResourceRequestGraph.js b/packages/project/test/lib/build/cache/ResourceRequestGraph.js new file mode 100644 index 00000000000..6f4e7f7089f --- /dev/null +++ b/packages/project/test/lib/build/cache/ResourceRequestGraph.js @@ -0,0 +1,959 @@ +import test from "ava"; +import ResourceRequestGraph, {Request} from "../../../../lib/build/cache/ResourceRequestGraph.js"; + +// Request Class Tests +test("Request: Create path request", (t) => { + const request = new Request("path", "a.js"); + t.is(request.type, "path"); + t.is(request.value, "a.js"); +}); + +test("Request: Create patterns request", (t) => { + const request = new Request("patterns", ["*.js", "*.css"]); + t.is(request.type, "patterns"); + t.deepEqual(request.value, ["*.js", "*.css"]); +}); + +test("Request: Reject invalid type", (t) => { + const error = t.throws(() => { + new Request("invalid-type", "value"); + }, {instanceOf: Error}); + t.is(error.message, "Invalid request type: invalid-type"); +}); + +test("Request: Reject non-string value for path type", (t) => { + const error = t.throws(() => { + new Request("path", ["array", "value"]); + }, {instanceOf: Error}); + t.is(error.message, "Request type 'path' requires value to be a string"); +}); + +test("Request: toKey with string value", (t) => { + const request = new Request("path", "a.js"); + t.is(request.toKey(), "path:a.js"); +}); + +test("Request: toKey with array value", (t) => { + const request = new Request("patterns", ["*.js", "*.css"]); + t.is(request.toKey(), "patterns:[\"*.js\",\"*.css\"]"); +}); + +test("Request: fromKey with string value", (t) => { + const request = Request.fromKey("path:a.js"); + t.is(request.type, "path"); + t.is(request.value, "a.js"); +}); + +test("Request: fromKey with array value", (t) => { + const request = Request.fromKey("patterns:[\"*.js\",\"*.css\"]"); + t.is(request.type, "patterns"); + t.deepEqual(request.value, ["*.js", "*.css"]); +}); + +test("Request: equals returns true for identical requests", (t) => { + const req1 = new Request("path", "a.js"); + const req2 = new Request("path", "a.js"); + t.true(req1.equals(req2)); +}); + +test("Request: equals returns false for different values", (t) => { + const req1 = new Request("path", "a.js"); + const req2 = new Request("path", "b.js"); + t.false(req1.equals(req2)); +}); + +test("Request: equals returns true for identical array values", (t) => { + const req1 = new Request("patterns", ["*.js", "*.css"]); + const req2 = new Request("patterns", ["*.js", "*.css"]); + t.true(req1.equals(req2)); +}); + +test("Request: equals returns false for different array lengths", (t) => { + const req1 = new Request("patterns", ["*.js", "*.css"]); + const req2 = new Request("patterns", ["*.js"]); + t.false(req1.equals(req2)); +}); + +test("Request: equals returns false for different array values", (t) => { + const req1 = new Request("patterns", ["*.js", "*.css"]); + const req2 = new Request("patterns", ["*.js", "*.html"]); + t.false(req1.equals(req2)); +}); + +// ResourceRequestGraph Tests +test("ResourceRequestGraph: Initialize empty graph", (t) => { + const graph = new ResourceRequestGraph(); + t.is(graph.nodes.size, 0); + t.is(graph.nextId, 1); +}); + +test("ResourceRequestGraph: Add first request set (root node)", (t) => { + const graph = new ResourceRequestGraph(); + const requests = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const nodeId = graph.addRequestSet(requests, {result: "xyz-1"}); + + t.is(nodeId, 1); + t.is(graph.nodes.size, 1); + + const node = graph.getNode(nodeId); + t.is(node.id, 1); + t.is(node.parent, null); + t.is(node.addedRequests.size, 2); + t.deepEqual(node.metadata, {result: "xyz-1"}); +}); + +test("ResourceRequestGraph: Add request set with parent relationship", (t) => { + const graph = new ResourceRequestGraph(); + + // Add first request set + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const node1 = graph.addRequestSet(set1, {result: "xyz-1"}); + + // Add second request set (superset of first) + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const node2 = graph.addRequestSet(set2, {result: "xyz-2"}); + + // Verify parent relationship + const node2Data = graph.getNode(node2); + t.is(node2Data.parent, node1); + t.is(node2Data.addedRequests.size, 1); + t.true(node2Data.addedRequests.has("path:c.js")); +}); + +test("ResourceRequestGraph: Add request set with no overlap", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const set2 = [new Request("path", "x.js")]; + const node2 = graph.addRequestSet(set2); + + const node2Data = graph.getNode(node2); + t.falsy(node2Data.parent); + t.is(node2Data.addedRequests.size, 1); + t.true(node2Data.addedRequests.has("path:x.js")); +}); + +test("ResourceRequestGraph: getMaterializedRequests returns full set", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1); + + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const node2 = graph.addRequestSet(set2); + + const node2Data = graph.getNode(node2); + const materialized = node2Data.getMaterializedRequests(graph); + + t.is(materialized.length, 3); + const keys = materialized.map((r) => r.toKey()).sort(); + t.deepEqual(keys, ["path:a.js", "path:b.js", "path:c.js"]); +}); + +test("ResourceRequestGraph: getAddedRequests returns only delta", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1); + + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const node2 = graph.addRequestSet(set2); + + const node2Data = graph.getNode(node2); + const added = node2Data.getAddedRequests(); + + t.is(added.length, 1); + t.is(added[0].toKey(), "path:c.js"); +}); + +test("ResourceRequestGraph: findBestParent returns node with largest subset", (t) => { + const graph = new ResourceRequestGraph(); + + // Add first request set + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1, {result: "xyz-1"}); + + // Add second request set (superset) + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const node2 = graph.addRequestSet(set2, {result: "xyz-2"}); + + // Query that contains set2 + const query = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js"), + new Request("path", "x.js") + ]; + const match = graph.findBestParent(query); + + // Should return node2 (largest subset: 3 items) + t.is(match, node2); +}); + +test("ResourceRequestGraph: findBestParent returns null when no subset found", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1); + + // Query with no overlap + const query = [ + new Request("path", "x.js"), + new Request("path", "y.js") + ]; + const match = graph.findBestParent(query); + + t.is(match, null); +}); + +test("ResourceRequestGraph: findExactMatch returns node with identical set", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const node1 = graph.addRequestSet(set1); + + const query = [ + new Request("path", "b.js"), + new Request("path", "a.js") // Different order, but same set + ]; + const match = graph.findExactMatch(query); + + t.is(match, node1); +}); + +test("ResourceRequestGraph: findExactMatch returns null for subset", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + graph.addRequestSet(set1); + + const query = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const match = graph.findExactMatch(query); + + t.is(match, null); +}); + +test("ResourceRequestGraph: findExactMatch returns null for superset", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1); + + const query = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const match = graph.findExactMatch(query); + + t.is(match, null); +}); + +test("ResourceRequestGraph: getMetadata returns stored metadata", (t) => { + const graph = new ResourceRequestGraph(); + const metadata = {result: "xyz", cached: true}; + + const set1 = [new Request("path", "a.js")]; + const nodeId = graph.addRequestSet(set1, metadata); + + const retrieved = graph.getMetadata(nodeId); + t.deepEqual(retrieved, metadata); +}); + +test("ResourceRequestGraph: getMetadata returns null for non-existent node", (t) => { + const graph = new ResourceRequestGraph(); + const retrieved = graph.getMetadata(999); + t.is(retrieved, null); +}); + +test("ResourceRequestGraph: setMetadata updates metadata", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + const nodeId = graph.addRequestSet(set1, {original: true}); + + graph.setMetadata(nodeId, {updated: true}); + + const retrieved = graph.getMetadata(nodeId); + t.deepEqual(retrieved, {updated: true}); +}); + +test("ResourceRequestGraph: getAllNodeIds returns all node IDs", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + const node1 = graph.addRequestSet(set1); + + const set2 = [new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2); + + const ids = graph.getAllNodeIds(); + t.is(ids.length, 2); + t.true(ids.includes(node1)); + t.true(ids.includes(node2)); +}); + +test("ResourceRequestGraph: getAllRequests returns all unique requests", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1); + + const set2 = [ + new Request("path", "a.js"), // Duplicate + new Request("path", "c.js") + ]; + graph.addRequestSet(set2); + + const allRequests = graph.getAllRequests(); + const keys = allRequests.map((r) => r.toKey()).sort(); + + // Should have 3 unique requests + t.is(keys.length, 3); + t.deepEqual(keys, ["path:a.js", "path:b.js", "path:c.js"]); +}); + +test("ResourceRequestGraph: getStats returns correct statistics", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1); + + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + graph.addRequestSet(set2); + + const stats = graph.getStats(); + + t.is(stats.nodeCount, 2); + t.is(stats.averageRequestsPerNode, 2.5); // (2 + 3) / 2 + t.is(stats.averageStoredDeltaSize, 1.5); // (2 + 1) / 2 + t.is(stats.maxDepth, 1); // node2 is at depth 1 + t.is(stats.compressionRatio, 0.6); // 3 stored / 5 total +}); + +test("ResourceRequestGraph: toCacheObject exports graph structure", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + const node1 = graph.addRequestSet(set1); + + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const node2 = graph.addRequestSet(set2); + + const exported = graph.toCacheObject(); + + t.is(exported.nodes.length, 2); + t.is(exported.nextId, 3); + + const exportedNode1 = exported.nodes.find((n) => n.id === node1); + t.truthy(exportedNode1); + t.is(exportedNode1.parent, null); + t.deepEqual(exportedNode1.addedRequests, ["path:a.js"]); + + const exportedNode2 = exported.nodes.find((n) => n.id === node2); + t.truthy(exportedNode2); + t.is(exportedNode2.parent, node1); + t.deepEqual(exportedNode2.addedRequests, ["path:b.js"]); +}); + +test("ResourceRequestGraph: fromCache reconstructs graph", (t) => { + const graph1 = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + const node1 = graph1.addRequestSet(set1); + + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const node2 = graph1.addRequestSet(set2); + + // Export and reconstruct + const exported = graph1.toCacheObject(); + const graph2 = ResourceRequestGraph.fromCache(exported); + + // Verify reconstruction + t.is(graph2.nodes.size, 2); + t.is(graph2.nextId, 3); + + const reconstructedNode1 = graph2.getNode(node1); + t.truthy(reconstructedNode1); + t.is(reconstructedNode1.parent, null); + t.is(reconstructedNode1.addedRequests.size, 1); + + const reconstructedNode2 = graph2.getNode(node2); + t.truthy(reconstructedNode2); + t.is(reconstructedNode2.parent, node1); + t.is(reconstructedNode2.addedRequests.size, 1); + + // Verify materialized sets work correctly + const materialized = reconstructedNode2.getMaterializedRequests(graph2); + t.is(materialized.length, 2); +}); + +test("ResourceRequestGraph: Handles different request types", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("patterns", ["*.js"]), + ]; + const nodeId = graph.addRequestSet(set1); + + const node = graph.getNode(nodeId); + const materialized = node.getMaterializedRequests(graph); + + t.is(materialized.length, 2); + + const types = materialized.map((r) => r.type).sort(); + t.deepEqual(types, ["path", "patterns"]); +}); + +test("ResourceRequestGraph: Complex parent hierarchy", (t) => { + const graph = new ResourceRequestGraph(); + + // Level 0: Root with 2 requests + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const node1 = graph.addRequestSet(set1); + + // Level 1: Add one request + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const node2 = graph.addRequestSet(set2); + + // Level 2: Add another request + const set3 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js"), + new Request("path", "d.js") + ]; + const node3 = graph.addRequestSet(set3); + + // Verify hierarchy + const node3Data = graph.getNode(node3); + t.is(node3Data.parent, node2); + + const node2Data = graph.getNode(node2); + t.is(node2Data.parent, node1); + + const stats = graph.getStats(); + t.is(stats.maxDepth, 2); +}); + +test("ResourceRequestGraph: findBestParent chooses optimal parent", (t) => { + const graph = new ResourceRequestGraph(); + + // Create two potential parents + const set1 = [ + new Request("path", "x.js"), + new Request("path", "y.js") + ]; + graph.addRequestSet(set1); + + const set2 = [ + new Request("path", "x.js"), + new Request("path", "y.js"), + new Request("path", "z.js"), + ]; + const node2 = graph.addRequestSet(set2); + + // New set overlaps more with set2 + const set3 = [ + new Request("path", "x.js"), + new Request("path", "y.js"), + new Request("path", "z.js"), + new Request("path", "w.js") + ]; + const node3 = graph.addRequestSet(set3); + + const node3Data = graph.getNode(node3); + // Should choose node2 as parent (only needs to add 1 request vs 5) + t.is(node3Data.parent, node2); + t.is(node3Data.addedRequests.size, 1); +}); + +test("ResourceRequestGraph: Empty request set", (t) => { + const graph = new ResourceRequestGraph(); + + const nodeId = graph.addRequestSet([]); + const node = graph.getNode(nodeId); + + t.is(node.addedRequests.size, 0); + t.is(node.parent, null); +}); + +test("ResourceRequestGraph: Caching works correctly", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + graph.addRequestSet(set1); + + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const node2 = graph.addRequestSet(set2); + + const node2Data = graph.getNode(node2); + + // First call should compute and cache + const materialized1 = node2Data.getMaterializedSet(graph); + t.is(materialized1.size, 3); + + // Second call should use cache (same result) + const materialized2 = node2Data.getMaterializedSet(graph); + t.is(materialized2.size, 3); + t.deepEqual(Array.from(materialized1).sort(), Array.from(materialized2).sort()); +}); + +test("ResourceRequestGraph: Integration", (t) => { + // Create graph + const graph = new ResourceRequestGraph(); + + // Add first request set + const set1 = [ + new Request("path", "a.js"), + new Request("path", "b.js") + ]; + const node1 = graph.addRequestSet(set1, {result: "xyz-1"}); + t.is(node1, 1); + + // Add second request set (superset of first) + const set2 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + const node2 = graph.addRequestSet(set2, {result: "xyz-2"}); + t.is(node2, 2); + + // Verify parent relationship + const node2Data = graph.getNode(node2); + t.is(node2Data.parent, node1); + t.deepEqual(Array.from(node2Data.addedRequests), ["path:c.js"]); + + // Find best match for a query + const query = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + ]; + const match = graph.findExactMatch(query); + t.is(match, node1); + + // Get metadata + const metadata = graph.getMetadata(match); + t.deepEqual(metadata, {result: "xyz-1"}); + + // Get statistics + const stats = graph.getStats(); + t.is(stats.nodeCount, 2); + t.truthy(stats.averageRequestsPerNode); +}); + +// Traversal Iterator Tests +test("ResourceRequestGraph: traverseByDepth iterates in breadth-first order", (t) => { + const graph = new ResourceRequestGraph(); + + // Create a tree structure: + // 1 (depth 0) + // / \ + // 2 3 (depth 1) + // / + // 4 (depth 2) + + const set1 = [new Request("path", "a.js")]; + const node1 = graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2); + + const set3 = [new Request("path", "a.js"), new Request("path", "c.js")]; + const node3 = graph.addRequestSet(set3); + + const set4 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "d.js") + ]; + const node4 = graph.addRequestSet(set4); + + // Collect traversal results + const traversal = []; + for (const {nodeId, depth} of graph.traverseByDepth()) { + traversal.push({nodeId, depth}); + } + + // Verify: depth 0 node comes first, then depth 1 nodes, then depth 2 + t.is(traversal.length, 4); + t.is(traversal[0].nodeId, node1); + t.is(traversal[0].depth, 0); + + // Nodes 2 and 3 should both be at depth 1 (order may vary) + t.is(traversal[1].depth, 1); + t.is(traversal[2].depth, 1); + t.true([node2, node3].includes(traversal[1].nodeId)); + t.true([node2, node3].includes(traversal[2].nodeId)); + + // Node 4 should be at depth 2 + t.is(traversal[3].nodeId, node4); + t.is(traversal[3].depth, 2); +}); + +test("ResourceRequestGraph: traverseByDepth yields correct node information", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + const node1 = graph.addRequestSet(set1, {meta: "root"}); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2, {meta: "child"}); + + const results = Array.from(graph.traverseByDepth()); + + t.is(results.length, 2); + + // First node + t.is(results[0].nodeId, node1); + t.truthy(results[0].node); + t.is(results[0].depth, 0); + t.is(results[0].parentId, null); + t.deepEqual(results[0].node.metadata, {meta: "root"}); + + // Second node + t.is(results[1].nodeId, node2); + t.is(results[1].depth, 1); + t.is(results[1].parentId, node1); + t.deepEqual(results[1].node.metadata, {meta: "child"}); +}); + +test("ResourceRequestGraph: traverseByDepth handles empty graph", (t) => { + const graph = new ResourceRequestGraph(); + const results = Array.from(graph.traverseByDepth()); + t.is(results.length, 0); +}); + +test("ResourceRequestGraph: traverseByDepth handles multiple root nodes", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + // Create a disconnected node by manipulating internal structure + // Add it without using addRequestSet to avoid automatic parent assignment + const set2 = [new Request("path", "x.js")]; + const node2 = graph.nextId++; + const requestSetNode = { + id: node2, + parent: null, + addedRequests: new Set(set2.map((r) => r.toKey())), + metadata: null, + _fullSetCache: null, + _cacheValid: false, + getMaterializedSet: function(g) { + return new Set(this.addedRequests); + }, + getMaterializedRequests: function(g) { + return Array.from(this.addedRequests).map((key) => Request.fromKey(key)); + }, + getAddedRequests: function() { + return Array.from(this.addedRequests).map((key) => Request.fromKey(key)); + }, + invalidateCache: function() { + this._cacheValid = false; + this._fullSetCache = null; + } + }; + graph.nodes.set(node2, requestSetNode); + + const results = Array.from(graph.traverseByDepth()); + + // Both roots should be at depth 0 + t.is(results.length, 2); + t.is(results[0].depth, 0); + t.is(results[1].depth, 0); + t.is(results[0].parentId, null); + t.is(results[1].parentId, null); +}); + +test("ResourceRequestGraph: traverseByDepth allows early termination", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2); + + const set3 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "c.js") + ]; + graph.addRequestSet(set3); + + // Stop after finding node2 + let count = 0; + for (const {nodeId} of graph.traverseByDepth()) { + count++; + if (nodeId === node2) { + break; + } + } + + // Should have visited 2 nodes, not all 3 + t.is(count, 2); +}); + +test("ResourceRequestGraph: traverseByDepth allows checking deltas", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + graph.addRequestSet(set2); + + const deltas = []; + for (const {node} of graph.traverseByDepth()) { + const delta = node.getAddedRequests(); + deltas.push(delta.map((r) => r.toKey())); + } + + // First node has 1 request, second node adds 1 request + t.deepEqual(deltas, [["path:a.js"], ["path:b.js"]]); +}); + +test("ResourceRequestGraph: traverseSubtree traverses only specified subtree", (t) => { + const graph = new ResourceRequestGraph(); + + // Create structure: + // 1 + // / \ + // 2 3 + // / + // 4 + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2); + + const set3 = [new Request("path", "a.js"), new Request("path", "c.js")]; + graph.addRequestSet(set3); + + const set4 = [ + new Request("path", "a.js"), + new Request("path", "b.js"), + new Request("path", "d.js") + ]; + const node4 = graph.addRequestSet(set4); + + // Traverse only subtree starting from node2 + const results = Array.from(graph.traverseSubtree(node2)); + + // Should only visit node2 and node4 (not node1 or node3) + t.is(results.length, 2); + t.is(results[0].nodeId, node2); + t.is(results[0].depth, 0); // Relative depth from start + t.is(results[1].nodeId, node4); + t.is(results[1].depth, 1); +}); + +test("ResourceRequestGraph: traverseSubtree with root node traverses entire tree", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + const node1 = graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + graph.addRequestSet(set2); + + const results = Array.from(graph.traverseSubtree(node1)); + + // Should visit all nodes + t.is(results.length, 2); +}); + +test("ResourceRequestGraph: traverseSubtree handles non-existent node", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const results = Array.from(graph.traverseSubtree(999)); + t.is(results.length, 0); +}); + +test("ResourceRequestGraph: traverseSubtree handles leaf node", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2); + + // Traverse from leaf node + const results = Array.from(graph.traverseSubtree(node2)); + + // Should only visit the leaf node itself + t.is(results.length, 1); + t.is(results[0].nodeId, node2); + t.is(results[0].depth, 0); +}); + +test("ResourceRequestGraph: getChildren returns child node IDs", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + const node1 = graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2); + + const set3 = [new Request("path", "a.js"), new Request("path", "c.js")]; + const node3 = graph.addRequestSet(set3); + + const children = graph.getChildren(node1); + + t.is(children.length, 2); + t.true(children.includes(node2)); + t.true(children.includes(node3)); +}); + +test("ResourceRequestGraph: getChildren returns empty array for leaf nodes", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const set2 = [new Request("path", "a.js"), new Request("path", "b.js")]; + const node2 = graph.addRequestSet(set2); + + const children = graph.getChildren(node2); + t.is(children.length, 0); +}); + +test("ResourceRequestGraph: getChildren returns empty array for non-existent node", (t) => { + const graph = new ResourceRequestGraph(); + + const set1 = [new Request("path", "a.js")]; + graph.addRequestSet(set1); + + const children = graph.getChildren(999); + t.is(children.length, 0); +}); + +test("ResourceRequestGraph: Efficient traversal use case", (t) => { + const graph = new ResourceRequestGraph(); + + // Simulate real usage: build a graph of resource requests + const set1 = [new Request("path", "core.js"), new Request("path", "utils.js")]; + const node1 = graph.addRequestSet(set1, {cached: true}); + + const set2 = [ + new Request("path", "core.js"), + new Request("path", "utils.js"), + new Request("path", "components.js") + ]; + const node2 = graph.addRequestSet(set2, {cached: false}); + + // Traverse and collect information + const visited = []; + for (const {nodeId, node, depth} of graph.traverseByDepth()) { + visited.push({ + nodeId, + depth, + deltaSize: node.addedRequests.size, + cached: node.metadata?.cached + }); + } + + t.is(visited.length, 2); + + // Parent processed first + t.is(visited[0].nodeId, node1); + t.is(visited[0].depth, 0); + t.is(visited[0].deltaSize, 2); + t.true(visited[0].cached); + + // Child processed second with delta + t.is(visited[1].nodeId, node2); + t.is(visited[1].depth, 1); + t.is(visited[1].deltaSize, 1); // Only added "components.js" + t.false(visited[1].cached); +}); diff --git a/packages/project/test/lib/build/cache/ResourceRequestManager.js b/packages/project/test/lib/build/cache/ResourceRequestManager.js new file mode 100644 index 00000000000..7bedc40abaa --- /dev/null +++ b/packages/project/test/lib/build/cache/ResourceRequestManager.js @@ -0,0 +1,696 @@ +import test from "ava"; +import sinon from "sinon"; +import ResourceRequestManager from "../../../../lib/build/cache/ResourceRequestManager.js"; +import ResourceRequestGraph from "../../../../lib/build/cache/ResourceRequestGraph.js"; + +// Helper to create mock Resource instances +function createMockResource(path, integrity = "test-hash", lastModified = 1000, size = 100, inode = 1) { + return { + getOriginalPath: () => path, + getPath: () => path, + getIntegrity: async () => integrity, + getLastModified: () => lastModified, + getSize: async () => size, + getInode: () => inode, + getBuffer: async () => Buffer.from("test content"), + getStream: () => null + }; +} + +// Helper to create mock Reader (project or dependency) +function createMockReader(resources = new Map()) { + return { + byPath: sinon.stub().callsFake(async (path) => { + return resources.get(path) || null; + }), + byGlob: sinon.stub().callsFake(async (patterns) => { + const patternArray = Array.isArray(patterns) ? patterns : [patterns]; + const results = []; + for (const [path, resource] of resources.entries()) { + for (const pattern of patternArray) { + // Simple pattern matching + if (pattern === "/**/*" || pattern === "**/*") { + results.push(resource); + break; + } + // Convert glob pattern to regex + const regex = new RegExp(pattern.replace(/\*/g, ".*").replace(/\?/g, ".")); + if (regex.test(path)) { + results.push(resource); + break; + } + } + } + return results; + }) + }; +} + +test.afterEach.always(() => { + sinon.restore(); +}); + +// ===== CONSTRUCTOR TESTS ===== + +test("ResourceRequestManager: Create new instance", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false); + + t.truthy(manager, "Manager instance created"); + t.true(manager.hasNewOrModifiedCacheEntries(), "New manager has modified entries"); +}); + +test("ResourceRequestManager: Create with request graph from cache", (t) => { + const graph = new ResourceRequestGraph(); + const manager = new ResourceRequestManager("test.project", "myTask", false, graph); + + t.truthy(manager, "Manager instance created with graph"); + t.false(manager.hasNewOrModifiedCacheEntries(), "Manager restored from cache has no new entries initially"); +}); + +test("ResourceRequestManager: Create with differential update enabled", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", true); + + t.truthy(manager, "Manager instance created with differential updates"); +}); + +test("ResourceRequestManager: Create with unusedAtLeastOnce flag", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false, null, true); + + t.truthy(manager, "Manager instance created"); + const signatures = manager.getIndexSignatures(); + t.true(signatures.includes("X"), "Signatures include 'X' for unused state"); +}); + +// ===== fromCache FACTORY METHOD TESTS ===== + +test("ResourceRequestManager: fromCache with basic data", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + // Create a manager and add some requests + const manager1 = new ResourceRequestManager("test.project", "myTask", false); + await manager1.addRequests({ + paths: ["/a.js", "/b.js"], + patterns: [] + }, reader); + + // Serialize and restore + const cacheData = manager1.toCacheObject(); + t.truthy(cacheData, "Cache data created"); + + const manager2 = ResourceRequestManager.fromCache("test.project", "myTask", false, cacheData); + + t.truthy(manager2, "Manager restored from cache"); + t.false(manager2.hasNewOrModifiedCacheEntries(), "Restored manager has no new entries"); +}); + +test("ResourceRequestManager: fromCache with unusedAtLeastOnce", (t) => { + const cacheData = { + requestSetGraph: { + nodes: [], + nextId: 1 + }, + rootIndices: [], + unusedAtLeastOnce: true + }; + + const manager = ResourceRequestManager.fromCache("test.project", "myTask", false, cacheData); + + t.truthy(manager, "Manager restored"); + const signatures = manager.getIndexSignatures(); + t.true(signatures.includes("X"), "Includes 'X' signature for unused state"); +}); + +// ===== addRequests TESTS ===== + +test("ResourceRequestManager: Add path requests", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const result = await manager.addRequests({ + paths: ["/a.js", "/b.js"], + patterns: [] + }, reader); + + t.truthy(result, "Result returned"); + t.truthy(result.setId, "Result has setId"); + t.truthy(result.signature, "Result has signature"); + t.is(typeof result.signature, "string", "Signature is a string"); +}); + +test("ResourceRequestManager: Add pattern requests", async (t) => { + const resources = new Map([ + ["/src/a.js", createMockResource("/src/a.js", "hash-a")], + ["/src/b.js", createMockResource("/src/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const result = await manager.addRequests({ + paths: [], + patterns: [["/src/*.js"]] + }, reader); + + t.truthy(result, "Result returned"); + t.truthy(result.signature, "Result has signature"); +}); + +test("ResourceRequestManager: Add multiple request sets", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")], + ["/c.js", createMockResource("/c.js", "hash-c")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + + // First request set + const result1 = await manager.addRequests({ + paths: ["/a.js", "/b.js"], + patterns: [] + }, reader); + + // Second request set (superset) + const result2 = await manager.addRequests({ + paths: ["/a.js", "/b.js", "/c.js"], + patterns: [] + }, reader); + + t.not(result1.signature, result2.signature, "Different signatures for different request sets"); + t.true(manager.hasNewOrModifiedCacheEntries(), "Has new entries"); +}); + +test("ResourceRequestManager: Reuse existing request set", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + + // Add first request set + const result1 = await manager.addRequests({ + paths: ["/a.js", "/b.js"], + patterns: [] + }, reader); + + // Add identical request set + const result2 = await manager.addRequests({ + paths: ["/a.js", "/b.js"], + patterns: [] + }, reader); + + t.is(result1.setId, result2.setId, "Same setId for identical requests"); + t.is(result1.signature, result2.signature, "Same signature for identical requests"); +}); + +// ===== recordNoRequests TESTS ===== + +test("ResourceRequestManager: Record no requests", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const signature = manager.recordNoRequests(); + + t.is(signature, "X", "Returns 'X' signature"); + t.true(manager.hasNewOrModifiedCacheEntries(), "Has new entries"); +}); + +test("ResourceRequestManager: Record no requests multiple times", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const sig1 = manager.recordNoRequests(); + const sig2 = manager.recordNoRequests(); + + t.is(sig1, "X", "First call returns 'X'"); + t.is(sig2, "X", "Second call returns 'X'"); +}); + +// ===== getIndexSignatures TESTS ===== + +test("ResourceRequestManager: Get signatures from empty manager", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const signatures = manager.getIndexSignatures(); + + t.is(signatures.length, 0, "No signatures for empty manager"); +}); + +test("ResourceRequestManager: Get signatures after adding requests", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + const signatures = manager.getIndexSignatures(); + + t.is(signatures.length, 1, "One signature"); + t.is(typeof signatures[0], "string", "Signature is a string"); +}); + +test("ResourceRequestManager: Get signatures includes 'X' when unused", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false); + manager.recordNoRequests(); + + const signatures = manager.getIndexSignatures(); + + t.true(signatures.includes("X"), "Includes 'X' signature"); +}); + +// ===== hasNewOrModifiedCacheEntries TESTS ===== + +test("ResourceRequestManager: New manager has modified entries", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false); + + t.true(manager.hasNewOrModifiedCacheEntries(), "New manager has modified entries"); +}); + +test("ResourceRequestManager: Restored manager has no modified entries initially", (t) => { + const graph = new ResourceRequestGraph(); + const manager = new ResourceRequestManager("test.project", "myTask", false, graph); + + t.false(manager.hasNewOrModifiedCacheEntries(), "Restored manager has no modified entries"); +}); + +test("ResourceRequestManager: Adding requests marks as modified", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")] + ]); + const reader = createMockReader(resources); + + const graph = new ResourceRequestGraph(); + const manager = new ResourceRequestManager("test.project", "myTask", false, graph); + + t.false(manager.hasNewOrModifiedCacheEntries(), "Initially no modified entries"); + + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + t.true(manager.hasNewOrModifiedCacheEntries(), "Has modified entries after adding requests"); +}); + +// ===== toCacheObject TESTS ===== + +test("ResourceRequestManager: Serialize to cache object", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + const cacheObj = manager.toCacheObject(); + + t.truthy(cacheObj, "Cache object created"); + t.truthy(cacheObj.requestSetGraph, "Has requestSetGraph"); + t.truthy(cacheObj.rootIndices, "Has rootIndices"); + t.true(Array.isArray(cacheObj.rootIndices), "rootIndices is an array"); +}); + +test("ResourceRequestManager: Serialize returns undefined when no changes", (t) => { + const graph = new ResourceRequestGraph(); + const manager = new ResourceRequestManager("test.project", "myTask", false, graph); + + const cacheObj = manager.toCacheObject(); + + t.is(cacheObj, undefined, "Returns undefined when no changes"); +}); + +test("ResourceRequestManager: Serialize includes unusedAtLeastOnce", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", false); + manager.recordNoRequests(); + + const cacheObj = manager.toCacheObject(); + + t.truthy(cacheObj, "Cache object created"); + t.true(cacheObj.unusedAtLeastOnce, "Includes unusedAtLeastOnce flag"); +}); + +test("ResourceRequestManager: Serialize with differential updates", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", true); + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + const cacheObj = manager.toCacheObject(); + + t.truthy(cacheObj, "Cache object created"); + t.truthy(cacheObj.deltaIndices, "Has deltaIndices"); + t.true(Array.isArray(cacheObj.deltaIndices), "deltaIndices is an array"); +}); + +// ===== getDeltas TESTS ===== + +test("ResourceRequestManager: Get deltas returns empty map initially", (t) => { + const manager = new ResourceRequestManager("test.project", "myTask", true); + + const deltas = manager.getDeltas(); + + t.true(deltas instanceof Map, "Returns a Map"); + t.is(deltas.size, 0, "Empty initially"); +}); + +test("ResourceRequestManager: Get deltas after updates", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", true); + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + // Update resource + resources.set("/a.js", createMockResource("/a.js", "hash-a-new")); + + // Note: In a real scenario, updateIndices would be called here + // For this test, we're just checking the method exists and returns a Map + const deltas = manager.getDeltas(); + + t.true(deltas instanceof Map, "Returns a Map"); +}); + +// ===== updateIndices TESTS ===== + +test("ResourceRequestManager: updateIndices with no requests", async (t) => { + const reader = createMockReader(new Map()); + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const hasChanges = await manager.updateIndices(reader, []); + + t.false(hasChanges, "No changes when no requests recorded"); +}); + +test("ResourceRequestManager: updateIndices with no changed paths", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + const hasChanges = await manager.updateIndices(reader, []); + + t.false(hasChanges, "No changes when paths don't match"); +}); + +test("ResourceRequestManager: updateIndices with matching changed path", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + // Update the resource + resources.set("/a.js", createMockResource("/a.js", "hash-a-new")); + + const hasChanges = await manager.updateIndices(reader, ["/a.js"]); + + t.true(hasChanges, "Detects changes for matching path"); +}); + +test("ResourceRequestManager: updateIndices with pattern matches", async (t) => { + const resources = new Map([ + ["/src/a.js", createMockResource("/src/a.js", "hash-a")], + ["/src/b.js", createMockResource("/src/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + await manager.addRequests({ + paths: [], + patterns: [["/src/*.js"]] + }, reader); + + // Update one resource + resources.set("/src/a.js", createMockResource("/src/a.js", "hash-a-new")); + + const hasChanges = await manager.updateIndices(reader, ["/src/a.js"]); + + t.true(hasChanges, "Detects changes for pattern-matched resources"); +}); + +test("ResourceRequestManager: updateIndices with removed resource", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + await manager.addRequests({paths: ["/a.js", "/b.js"], patterns: []}, reader); + + // Remove a resource + resources.delete("/b.js"); + + const hasChanges = await manager.updateIndices(reader, ["/b.js"]); + + t.true(hasChanges, "Detects removal of resource"); +}); + +// ===== refreshIndices TESTS ===== + +/* eslint-disable-next-line */ +test.skip("ResourceRequestManager: refreshIndices with no requests", async (t) => { + const reader = createMockReader(new Map()); + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const hasChanges = await manager.refreshIndices(reader); + + t.false(hasChanges, "No changes when no requests recorded"); +}); + +test("ResourceRequestManager: refreshIndices after adding requests", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + await manager.addRequests({paths: ["/a.js", "/b.js"], patterns: []}, reader); + + // Update resources + resources.set("/a.js", createMockResource("/a.js", "hash-a-new")); + + const result = await manager.refreshIndices(reader); + + // refreshIndices doesn't return a value (undefined) when changes are made + // It only returns false when there are no requests + t.is(result, undefined, "refreshIndices returns undefined when it processes changes"); + t.pass("refreshIndices completed"); +}); + +// ===== INTEGRATION TESTS ===== + +test("ResourceRequestManager: Complete workflow", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + // 1. Create manager + const manager = new ResourceRequestManager("test.project", "myTask", false); + + // 2. Add requests + const result = await manager.addRequests({ + paths: ["/a.js", "/b.js"], + patterns: [] + }, reader); + + t.truthy(result.signature, "Got signature from addRequests"); + + // 3. Get signatures + const signatures = manager.getIndexSignatures(); + t.is(signatures.length, 1, "One signature recorded"); + t.is(signatures[0], result.signature, "Signature matches"); + + // 4. Serialize + const cacheObj = manager.toCacheObject(); + t.truthy(cacheObj, "Can serialize to cache object"); + + // 5. Restore from cache + const manager2 = ResourceRequestManager.fromCache("test.project", "myTask", false, cacheObj); + t.truthy(manager2, "Can restore from cache"); + + const signatures2 = manager2.getIndexSignatures(); + t.deepEqual(signatures2, signatures, "Restored manager has same signatures"); +}); + +test("ResourceRequestManager: Differential update workflow", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")] + ]); + const reader = createMockReader(resources); + + // Create with differential updates enabled + const manager = new ResourceRequestManager("test.project", "myTask", true); + + // Add requests + await manager.addRequests({paths: ["/a.js"], patterns: []}, reader); + + // Update resource + resources.set("/a.js", createMockResource("/a.js", "hash-a-new")); + + // Update indices + const hasChanges = await manager.updateIndices(reader, ["/a.js"]); + t.true(hasChanges, "Detected changes"); + + // Get deltas + const deltas = manager.getDeltas(); + t.true(deltas instanceof Map, "Has deltas"); + + // Serialize + const cacheObj = manager.toCacheObject(); + t.truthy(cacheObj, "Can serialize"); + t.truthy(cacheObj.deltaIndices, "Has delta indices"); +}); + +test("ResourceRequestManager: Mixed path and pattern requests", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/src/b.js", createMockResource("/src/b.js", "hash-b")], + ["/src/c.js", createMockResource("/src/c.js", "hash-c")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const result = await manager.addRequests({ + paths: ["/a.js"], + patterns: [["/src/*.js"]] + }, reader); + + t.truthy(result.signature, "Got signature for mixed requests"); + + const signatures = manager.getIndexSignatures(); + t.is(signatures.length, 1, "One signature"); +}); + +test("ResourceRequestManager: Hierarchical request sets", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")], + ["/c.js", createMockResource("/c.js", "hash-c")] + ]); + const reader = createMockReader(resources); + + const manager = new ResourceRequestManager("test.project", "myTask", false); + + // First request set + const result1 = await manager.addRequests({ + paths: ["/a.js"], + patterns: [] + }, reader); + + // Second request set (superset) + const result2 = await manager.addRequests({ + paths: ["/a.js", "/b.js"], + patterns: [] + }, reader); + + // Third request set (superset) + const result3 = await manager.addRequests({ + paths: ["/a.js", "/b.js", "/c.js"], + patterns: [] + }, reader); + + const signatures = manager.getIndexSignatures(); + t.is(signatures.length, 3, "Three different signatures"); + t.not(result1.signature, result2.signature, "Different signatures"); + t.not(result2.signature, result3.signature, "Different signatures"); +}); + +test("ResourceRequestManager: Empty request sets", async (t) => { + const reader = createMockReader(new Map()); + const manager = new ResourceRequestManager("test.project", "myTask", false); + + const result = await manager.addRequests({ + paths: [], + patterns: [] + }, reader); + + t.truthy(result, "Can add empty request set"); + t.truthy(result.signature, "Has signature even when empty"); +}); + +test("ResourceRequestManager: Serialization round-trip with multiple request sets", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + // Create manager and add multiple request sets (keep it simpler - two levels) + const manager1 = new ResourceRequestManager("test.project", "myTask", false); + await manager1.addRequests({paths: ["/a.js"], patterns: []}, reader); + await manager1.addRequests({paths: ["/a.js", "/b.js"], patterns: []}, reader); + + const signatures1 = manager1.getIndexSignatures(); + + // Serialize and restore + const cacheObj = manager1.toCacheObject(); + const manager2 = ResourceRequestManager.fromCache("test.project", "myTask", false, cacheObj); + + const signatures2 = manager2.getIndexSignatures(); + + t.deepEqual(signatures2, signatures1, "Signatures preserved through serialization"); + t.false(manager2.hasNewOrModifiedCacheEntries(), "Restored manager has no new entries"); +}); + +test("ResourceRequestManager: Serialization round-trip with multiple request sets and following update", async (t) => { + const resources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], + ["/b.js", createMockResource("/b.js", "hash-b")] + ]); + const reader = createMockReader(resources); + + // Create manager and add multiple request sets (keep it simpler - two levels) + const manager1 = new ResourceRequestManager("test.project", "myTask", false); + await manager1.addRequests({paths: ["/a.js"], patterns: []}, reader); + await manager1.addRequests({paths: ["/a.js", "/b.js"], patterns: []}, reader); + + const signatures1 = manager1.getIndexSignatures(); + + // Serialize and restore + const cacheObj = manager1.toCacheObject(); + const manager2 = ResourceRequestManager.fromCache("test.project", "myTask", false, cacheObj); + + + const changedResources = new Map([ + ["/a.js", createMockResource("/a.js", "hash-a")], // Identical to first + ["/b.js", createMockResource("/b.js", "hash-y")] + ]); + const changedReader = createMockReader(changedResources); + + const hasChanges = await manager2.updateIndices(changedReader, ["/a.js", "/b.js"]); + + t.true(hasChanges, "Detected changes after update"); + + const signatures2 = manager2.getIndexSignatures(); + + t.is(signatures2[0], signatures1[0], "Unchanged signature of first request set"); + t.not(signatures2[1], signatures1[1], "Changed signature of second request set"); + t.true(manager2.hasNewOrModifiedCacheEntries(), "Restored manager has new entries"); +}); + diff --git a/packages/project/test/lib/build/cache/index/HashTree.js b/packages/project/test/lib/build/cache/index/HashTree.js new file mode 100644 index 00000000000..aa462840b69 --- /dev/null +++ b/packages/project/test/lib/build/cache/index/HashTree.js @@ -0,0 +1,504 @@ +import test from "ava"; +import sinon from "sinon"; +import HashTree from "../../../../../lib/build/cache/index/HashTree.js"; + +// Helper to create mock Resource instances +function createMockResource(path, integrity, lastModified, size, inode) { + return { + getOriginalPath: () => path, + getIntegrity: async () => integrity, + getLastModified: () => lastModified, + getSize: async () => size, + getInode: () => inode + }; +} + +test.afterEach.always((t) => { + sinon.restore(); +}); + +test("Create HashTree", (t) => { + const mt = new HashTree(); + t.truthy(mt, "HashTree instance created"); +}); + +test("Two instances with same resources produce same root hash", (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1"}, + {path: "file2.js", integrity: "hash2"}, + {path: "dir/file3.js", integrity: "hash3"} + ]; + + const tree1 = new HashTree(resources); + const tree2 = new HashTree(resources); + + t.is(tree1.getRootHash(), tree2.getRootHash(), + "Trees with identical resources should have identical root hashes"); +}); + +test("Order of resource insertion doesn't affect root hash", (t) => { + const resources1 = [ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"}, + {path: "c.js", integrity: "hash-c"} + ]; + + const resources2 = [ + {path: "c.js", integrity: "hash-c"}, + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ]; + + const tree1 = new HashTree(resources1); + const tree2 = new HashTree(resources2); + + t.is(tree1.getRootHash(), tree2.getRootHash(), + "Trees should produce same hash regardless of insertion order"); +}); + +test("Updating resources in two trees produces same root hash", async (t) => { + const initialResources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100}, + {path: "file2.js", integrity: "hash2", lastModified: 2000, size: 200}, + {path: "dir/file3.js", integrity: "hash3", lastModified: 3000, size: 300} + ]; + + const tree1 = new HashTree(initialResources); + const tree2 = new HashTree(initialResources); + const indexTimestamp = tree1.getIndexTimestamp(); + + // Update same resource in both trees + const resource = createMockResource("file1.js", "new-hash1", indexTimestamp + 1, 101, 1); + await tree1.upsertResources([resource]); + await tree2.upsertResources([resource]); + + t.is(tree1.getRootHash(), tree2.getRootHash(), + "Trees should have same root hash after identical updates"); +}); + +test("Multiple updates in same order produce same root hash", async (t) => { + const initialResources = [ + {path: "a.js", integrity: "hash-a", lastModified: 1000, size: 100}, + {path: "b.js", integrity: "hash-b", lastModified: 2000, size: 200}, + {path: "c.js", integrity: "hash-c", lastModified: 3000, size: 300}, + {path: "dir/d.js", integrity: "hash-d", lastModified: 4000, size: 400} + ]; + + const tree1 = new HashTree(initialResources); + const tree2 = new HashTree(initialResources); + const indexTimestamp = tree1.getIndexTimestamp(); + + // Update multiple resources in same order + await tree1.upsertResources([createMockResource("a.js", "new-hash-a", indexTimestamp + 1, 101, 1)]); + await tree1.upsertResources([createMockResource("dir/d.js", "new-hash-d", indexTimestamp + 1, 401, 4)]); + await tree1.upsertResources([createMockResource("b.js", "new-hash-b", indexTimestamp + 1, 201, 2)]); + + await tree2.upsertResources([createMockResource("a.js", "new-hash-a", indexTimestamp + 1, 101, 1)]); + await tree2.upsertResources([createMockResource("dir/d.js", "new-hash-d", indexTimestamp + 1, 401, 4)]); + await tree2.upsertResources([createMockResource("b.js", "new-hash-b", indexTimestamp + 1, 201, 2)]); + + t.is(tree1.getRootHash(), tree2.getRootHash(), + "Trees should have same root hash after same sequence of updates"); +}); + +test("Multiple updates in different order produce same root hash", async (t) => { + const initialResources = [ + {path: "a.js", integrity: "hash-a", lastModified: 1000, size: 100}, + {path: "b.js", integrity: "hash-b", lastModified: 2000, size: 200}, + {path: "c.js", integrity: "hash-c", lastModified: 3000, size: 300} + ]; + + const tree1 = new HashTree(initialResources); + const tree2 = new HashTree(initialResources); + const indexTimestamp = tree1.getIndexTimestamp(); + + // Update in different orders + await tree1.upsertResources([createMockResource("a.js", "new-hash-a", indexTimestamp + 1, 101, 1)]); + await tree1.upsertResources([createMockResource("b.js", "new-hash-b", indexTimestamp + 1, 201, 2)]); + await tree1.upsertResources([createMockResource("c.js", "new-hash-c", indexTimestamp + 1, 301, 3)]); + + await tree2.upsertResources([createMockResource("c.js", "new-hash-c", indexTimestamp + 1, 301, 3)]); + await tree2.upsertResources([createMockResource("a.js", "new-hash-a", indexTimestamp + 1, 101, 1)]); + await tree2.upsertResources([createMockResource("b.js", "new-hash-b", indexTimestamp + 1, 201, 2)]); + + t.is(tree1.getRootHash(), tree2.getRootHash(), + "Trees should have same root hash regardless of update order"); +}); + +test("Batch updates produce same hash as individual updates", async (t) => { + const initialResources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100}, + {path: "file2.js", integrity: "hash2", lastModified: 2000, size: 200}, + {path: "file3.js", integrity: "hash3", lastModified: 3000, size: 300} + ]; + + const tree1 = new HashTree(initialResources); + const tree2 = new HashTree(initialResources); + const indexTimestamp = tree1.getIndexTimestamp(); + + // Individual updates + await tree1.upsertResources([createMockResource("file1.js", "new-hash1", indexTimestamp + 1, 101, 1)]); + await tree1.upsertResources([createMockResource("file2.js", "new-hash2", indexTimestamp + 1, 201, 2)]); + + // Batch update + const resources = [ + createMockResource("file1.js", "new-hash1", 1001, 101, 1), + createMockResource("file2.js", "new-hash2", 2001, 201, 2) + ]; + await tree2.upsertResources(resources); + + t.is(tree1.getRootHash(), tree2.getRootHash(), + "Batch updates should produce same hash as individual updates"); +}); + +test("Updating resource changes root hash", async (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100}, + {path: "file2.js", integrity: "hash2", lastModified: 2000, size: 200} + ]; + + const tree = new HashTree(resources); + const originalHash = tree.getRootHash(); + const indexTimestamp = tree.getIndexTimestamp(); + + await tree.upsertResources([createMockResource("file1.js", "new-hash1", indexTimestamp + 1, 101, 1)]); + const newHash = tree.getRootHash(); + + t.not(originalHash, newHash, + "Root hash should change after resource update"); +}); + +test("Updating resource back to original value restores original hash", async (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100}, + {path: "file2.js", integrity: "hash2", lastModified: 2000, size: 200} + ]; + + const tree = new HashTree(resources); + const originalHash = tree.getRootHash(); + const indexTimestamp = tree.getIndexTimestamp(); + + // Update and then revert + await tree.upsertResources([createMockResource("file1.js", "new-hash1", indexTimestamp + 1, 101, 1)]); + await tree.upsertResources([createMockResource("file1.js", "hash1", 1000, 100, 1)]); + + t.is(tree.getRootHash(), originalHash, + "Root hash should be restored when resource is reverted to original value"); +}); + +test("updateResource returns changed resource path", async (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100} + ]; + + const tree = new HashTree(resources); + const indexTimestamp = tree.getIndexTimestamp(); + const {updated} = await tree.upsertResources([ + createMockResource("file1.js", "new-hash1", indexTimestamp + 1, 101, 1) + ]); + + t.deepEqual(updated, ["file1.js"], "Should return path of changed resource"); +}); + +test("updateResource returns empty array when integrity unchanged", async (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100} + ]; + + const tree = new HashTree(resources); + const {updated} = await tree.upsertResources([createMockResource("file1.js", "hash1", 1000, 100, 1)]); + + t.deepEqual(updated, [], "Should return empty array when integrity unchanged"); +}); + +test("updateResource does not change hash when integrity unchanged", async (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100} + ]; + + const tree = new HashTree(resources); + const originalHash = tree.getRootHash(); + await tree.upsertResources([createMockResource("file1.js", "hash1", 1000, 100, 1)]); + + t.is(tree.getRootHash(), originalHash, "Hash should not change when integrity unchanged"); +}); + +test("upsertResources returns changed resource paths", async (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100}, + {path: "file2.js", integrity: "hash2", lastModified: 2000, size: 200}, + {path: "file3.js", integrity: "hash3", lastModified: 3000, size: 300} + ]; + + const tree = new HashTree(resources); + const indexTimestamp = tree.getIndexTimestamp(); + + const resourceUpdates = [ + createMockResource("file1.js", "new-hash1", indexTimestamp + 1, 101, 1), // Changed + createMockResource("file2.js", "hash2", 2000, 200, 2), // unchanged + createMockResource("file3.js", "new-hash3", indexTimestamp + 1, 301, 3) // Changed + ]; + const {updated} = await tree.upsertResources(resourceUpdates); + + t.deepEqual(updated, ["file1.js", "file3.js"], "Should return only updated paths"); +}); + +test("upsertResources returns empty array when no changes", async (t) => { + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: 1000, size: 100}, + {path: "file2.js", integrity: "hash2", lastModified: 2000, size: 200} + ]; + + const tree = new HashTree(resources); + const resourceUpdates = [ + createMockResource("file1.js", "hash1", 1000, 100, 1), + createMockResource("file2.js", "hash2", 2000, 200, 2) + ]; + const {updated} = await tree.upsertResources(resourceUpdates); + + t.deepEqual(updated, [], "Should return empty array when no changes"); +}); + +test("Different nested structures with same resources produce different hashes", (t) => { + const resources1 = [ + {path: "a/b/file.js", integrity: "hash1"} + ]; + + const resources2 = [ + {path: "a/file.js", integrity: "hash1"} + ]; + + const tree1 = new HashTree(resources1); + const tree2 = new HashTree(resources2); + + t.not(tree1.getRootHash(), tree2.getRootHash(), + "Different directory structures should produce different hashes"); +}); + +test("Updating unrelated resource doesn't affect consistency", async (t) => { + const initialResources = [ + {path: "file1.js", integrity: "hash1"}, + {path: "file2.js", integrity: "hash2"}, + {path: "dir/file3.js", integrity: "hash3"} + ]; + + const tree1 = new HashTree(initialResources); + const tree2 = new HashTree(initialResources); + + // Update different resources + await tree1.upsertResources([createMockResource("file1.js", "new-hash1", Date.now(), 1024, 789)]); + await tree2.upsertResources([createMockResource("file1.js", "new-hash1", Date.now(), 1024, 789)]); + + // Update an unrelated resource in both + await tree1.upsertResources([createMockResource("dir/file3.js", "new-hash3", Date.now(), 2048, 790)]); + await tree2.upsertResources([createMockResource("dir/file3.js", "new-hash3", Date.now(), 2048, 790)]); + + t.is(tree1.getRootHash(), tree2.getRootHash(), + "Trees should remain consistent after updating multiple resources"); +}); + +test("getResourcePaths returns all resource paths in sorted order", (t) => { + const resources = [ + {path: "z.js", integrity: "hash-z"}, + {path: "a.js", integrity: "hash-a"}, + {path: "dir/b.js", integrity: "hash-b"}, + {path: "dir/nested/c.js", integrity: "hash-c"} + ]; + + const tree = new HashTree(resources); + const paths = tree.getResourcePaths(); + + t.deepEqual(paths, [ + "/a.js", + "/dir/b.js", + "/dir/nested/c.js", + "/z.js" + ], "Resource paths should be sorted alphabetically"); +}); + +test("getResourcePaths returns empty array for empty tree", (t) => { + const tree = new HashTree(); + const paths = tree.getResourcePaths(); + + t.deepEqual(paths, [], "Empty tree should return empty array"); +}); + +// ============================================================================ +// upsertResources Tests +// ============================================================================ + +test("upsertResources - insert new resources", async (t) => { + const tree = new HashTree([{path: "a.js", integrity: "hash-a"}]); + const originalHash = tree.getRootHash(); + + const result = await tree.upsertResources([ + createMockResource("b.js", "hash-b", Date.now(), 1024, 1), + createMockResource("c.js", "hash-c", Date.now(), 2048, 2) + ]); + + t.deepEqual(result.added, ["b.js", "c.js"], "Should report added resources"); + t.deepEqual(result.updated, [], "Should have no updates"); + t.deepEqual(result.unchanged, [], "Should have no unchanged"); + + t.truthy(tree.hasPath("b.js"), "Tree should have b.js"); + t.truthy(tree.hasPath("c.js"), "Tree should have c.js"); + t.not(tree.getRootHash(), originalHash, "Root hash should change"); +}); + +test("upsertResources - update existing resources", async (t) => { + const tree = new HashTree([ + {path: "a.js", integrity: "hash-a", lastModified: 1000, size: 100}, + {path: "b.js", integrity: "hash-b", lastModified: 2000, size: 200} + ]); + const originalHash = tree.getRootHash(); + const indexTimestamp = tree.getIndexTimestamp(); + + const result = await tree.upsertResources([ + createMockResource("a.js", "new-hash-a", indexTimestamp + 1, 101, 1), + createMockResource("b.js", "new-hash-b", indexTimestamp + 1, 201, 2) + ]); + + t.deepEqual(result.added, [], "Should have no additions"); + t.deepEqual(result.updated, ["a.js", "b.js"], "Should report updated resources"); + t.deepEqual(result.unchanged, [], "Should have no unchanged"); + + t.is(tree.getResourceByPath("a.js").integrity, "new-hash-a"); + t.is(tree.getResourceByPath("b.js").integrity, "new-hash-b"); + t.not(tree.getRootHash(), originalHash, "Root hash should change"); +}); + +test("upsertResources - mixed insert, update, and unchanged", async (t) => { + const timestamp = Date.now(); + const tree = new HashTree([ + {path: "a.js", integrity: "hash-a", lastModified: timestamp, size: 100, inode: 1} + ]); + const originalHash = tree.getRootHash(); + + const result = await tree.upsertResources([ + createMockResource("a.js", "hash-a", timestamp, 100, 1), // unchanged + createMockResource("b.js", "hash-b", timestamp, 200, 2), // new + createMockResource("c.js", "hash-c", timestamp, 300, 3) // new + ]); + + t.deepEqual(result.unchanged, ["a.js"], "Should report unchanged resource"); + t.deepEqual(result.added, ["b.js", "c.js"], "Should report added resources"); + t.deepEqual(result.updated, [], "Should have no updates"); + + t.not(tree.getRootHash(), originalHash, "Root hash should change (new resources added)"); +}); + +// ============================================================================ +// removeResources Tests +// ============================================================================ + +test("removeResources - remove existing resources", async (t) => { + const tree = new HashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"}, + {path: "c.js", integrity: "hash-c"} + ]); + const originalHash = tree.getRootHash(); + + const result = await tree.removeResources(["b.js", "c.js"]); + + t.deepEqual(result.removed, ["b.js", "c.js"], "Should report removed resources"); + t.deepEqual(result.notFound, [], "Should have no not found"); + + t.truthy(tree.hasPath("a.js"), "Tree should still have a.js"); + t.false(tree.hasPath("b.js"), "Tree should not have b.js"); + t.false(tree.hasPath("c.js"), "Tree should not have c.js"); + t.not(tree.getRootHash(), originalHash, "Root hash should change"); +}); + +test("removeResources - remove non-existent resources", async (t) => { + const tree = new HashTree([{path: "a.js", integrity: "hash-a"}]); + const originalHash = tree.getRootHash(); + + const result = await tree.removeResources(["b.js", "c.js"]); + + t.deepEqual(result.removed, [], "Should have no removals"); + t.deepEqual(result.notFound, ["b.js", "c.js"], "Should report not found"); + + t.is(tree.getRootHash(), originalHash, "Root hash should not change"); +}); + +test("removeResources - mixed existing and non-existent", async (t) => { + const tree = new HashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ]); + + const result = await tree.removeResources(["b.js", "c.js", "d.js"]); + + t.deepEqual(result.removed, ["b.js"], "Should report removed resources"); + t.deepEqual(result.notFound, ["c.js", "d.js"], "Should report not found"); + + t.truthy(tree.hasPath("a.js"), "Tree should still have a.js"); + t.false(tree.hasPath("b.js"), "Tree should not have b.js"); +}); + +test("removeResources - remove from nested directory", async (t) => { + const tree = new HashTree([ + {path: "dir1/dir2/a.js", integrity: "hash-a"}, + {path: "dir1/dir2/b.js", integrity: "hash-b"}, + {path: "dir1/c.js", integrity: "hash-c"} + ]); + + const result = await tree.removeResources(["dir1/dir2/a.js"]); + + t.deepEqual(result.removed, ["dir1/dir2/a.js"], "Should remove nested resource"); + t.false(tree.hasPath("dir1/dir2/a.js"), "Should not have dir1/dir2/a.js"); + t.truthy(tree.hasPath("dir1/dir2/b.js"), "Should still have dir1/dir2/b.js"); + t.truthy(tree.hasPath("dir1/c.js"), "Should still have dir1/c.js"); +}); + +test("removeResources - removing last resource in directory cleans up directory", async (t) => { + const tree = new HashTree([ + {path: "dir1/dir2/only.js", integrity: "hash-only"}, + {path: "dir1/other.js", integrity: "hash-other"} + ]); + + // Verify structure before removal + t.truthy(tree.hasPath("dir1/dir2/only.js"), "Should have dir1/dir2/only.js"); + t.truthy(tree._findNode("dir1/dir2"), "Directory dir1/dir2 should exist"); + + // Remove the only resource in dir2 + const result = await tree.removeResources(["dir1/dir2/only.js"]); + + t.deepEqual(result.removed, ["dir1/dir2/only.js"], "Should remove resource"); + t.false(tree.hasPath("dir1/dir2/only.js"), "Should not have dir1/dir2/only.js"); + + // Check if empty directory is cleaned up + const dir2Node = tree._findNode("dir1/dir2"); + t.is(dir2Node, null, "Empty directory dir1/dir2 should be removed"); + + // Parent directory should still exist with other.js + t.truthy(tree.hasPath("dir1/other.js"), "Should still have dir1/other.js"); + t.truthy(tree._findNode("dir1"), "Parent directory dir1 should still exist"); +}); + +test("removeResources - cleans up deeply nested empty directories", async (t) => { + const tree = new HashTree([ + {path: "a/b/c/d/e/deep.js", integrity: "hash-deep"}, + {path: "a/sibling.js", integrity: "hash-sibling"} + ]); + + // Verify structure before removal + t.truthy(tree.hasPath("a/b/c/d/e/deep.js"), "Should have deeply nested file"); + t.truthy(tree._findNode("a/b/c/d/e"), "Deep directory should exist"); + + // Remove the only resource in the deep hierarchy + const result = await tree.removeResources(["a/b/c/d/e/deep.js"]); + + t.deepEqual(result.removed, ["a/b/c/d/e/deep.js"], "Should remove resource"); + + // All empty directories in the chain should be removed + t.is(tree._findNode("a/b/c/d/e"), null, "Directory e should be removed"); + t.is(tree._findNode("a/b/c/d"), null, "Directory d should be removed"); + t.is(tree._findNode("a/b/c"), null, "Directory c should be removed"); + t.is(tree._findNode("a/b"), null, "Directory b should be removed"); + + // Parent directory with sibling should still exist + t.truthy(tree._findNode("a"), "Directory a should still exist (has sibling.js)"); + t.truthy(tree.hasPath("a/sibling.js"), "Sibling file should still exist"); +}); diff --git a/packages/project/test/lib/build/cache/index/SharedHashTree.js b/packages/project/test/lib/build/cache/index/SharedHashTree.js new file mode 100644 index 00000000000..d01073e21d0 --- /dev/null +++ b/packages/project/test/lib/build/cache/index/SharedHashTree.js @@ -0,0 +1,729 @@ +import test from "ava"; +import sinon from "sinon"; +import SharedHashTree from "../../../../../lib/build/cache/index/SharedHashTree.js"; +import TreeRegistry from "../../../../../lib/build/cache/index/TreeRegistry.js"; + +// Helper to create mock Resource instances +function createMockResource(path, integrity, lastModified, size, inode) { + return { + getOriginalPath: () => path, + getIntegrity: async () => integrity, + getLastModified: () => lastModified, + getSize: async () => size, + getInode: () => inode + }; +} + +test.afterEach.always((t) => { + sinon.restore(); +}); + +// ============================================================================ +// SharedHashTree Construction Tests +// ============================================================================ + +test("SharedHashTree - requires registry option", (t) => { + t.throws(() => { + new SharedHashTree([{path: "a.js", integrity: "hash1"}]); + }, { + message: "SharedHashTree requires a registry option" + }, "Should throw error when registry is missing"); +}); + +test("SharedHashTree - auto-registers with registry", (t) => { + const registry = new TreeRegistry(); + new SharedHashTree([{path: "a.js", integrity: "hash1"}], registry); + + t.is(registry.getTreeCount(), 1, "Should auto-register with registry"); +}); + +test("SharedHashTree - creates tree with resources", (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ]; + const tree = new SharedHashTree(resources, registry); + + t.truthy(tree.getRootHash(), "Should have root hash"); + t.true(tree.hasPath("a.js"), "Should have a.js"); + t.true(tree.hasPath("b.js"), "Should have b.js"); +}); + +// ============================================================================ +// SharedHashTree fromCache Tests +// ============================================================================ + +test("SharedHashTree.fromCache - restores tree from cache data", (t) => { + const registry = new TreeRegistry(); + + // Create original tree + const tree1 = new SharedHashTree([ + {path: "a.js", integrity: "hash-a", size: 100, lastModified: 1000, inode: 1}, + {path: "b.js", integrity: "hash-b", size: 200, lastModified: 2000, inode: 2} + ], registry); + + // Serialize tree + const cacheData = tree1.toCacheObject(); + + // Restore from cache + const registry2 = new TreeRegistry(); + const tree2 = SharedHashTree.fromCache(cacheData, registry2); + + t.truthy(tree2, "Should create tree from cache"); + t.is(tree2.getRootHash(), tree1.getRootHash(), "Root hash should match"); + t.true(tree2.hasPath("a.js"), "Should have a.js"); + t.true(tree2.hasPath("b.js"), "Should have b.js"); +}); + +test("SharedHashTree.fromCache - registers with provided registry", (t) => { + const registry1 = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"} + ], registry1); + + const cacheData = tree1.toCacheObject(); + + const registry2 = new TreeRegistry(); + t.is(registry2.getTreeCount(), 0, "Registry should be empty initially"); + + SharedHashTree.fromCache(cacheData, registry2); + + t.is(registry2.getTreeCount(), 1, "Tree should be registered with new registry"); +}); + +test("SharedHashTree.fromCache - throws on unsupported version", (t) => { + const registry = new TreeRegistry(); + + const invalidCacheData = { + version: 999, + root: { + type: "directory", + hash: "some-hash", + children: {} + } + }; + + const error = t.throws(() => { + SharedHashTree.fromCache(invalidCacheData, registry); + }, { + instanceOf: Error + }); + + t.is(error.message, "Unsupported version: 999", "Should throw error for unsupported version"); +}); + +test("SharedHashTree.fromCache - preserves tree structure", (t) => { + const registry = new TreeRegistry(); + + // Create tree with nested structure + const tree1 = new SharedHashTree([ + {path: "src/components/Button.js", integrity: "hash-button", size: 300, lastModified: 3000, inode: 3}, + {path: "src/utils/helper.js", integrity: "hash-helper", size: 400, lastModified: 4000, inode: 4}, + {path: "test/button.test.js", integrity: "hash-test", size: 500, lastModified: 5000, inode: 5} + ], registry); + + const cacheData = tree1.toCacheObject(); + + const registry2 = new TreeRegistry(); + const tree2 = SharedHashTree.fromCache(cacheData, registry2); + + // Verify all paths exist + t.true(tree2.hasPath("src/components/Button.js"), "Should have Button.js"); + t.true(tree2.hasPath("src/utils/helper.js"), "Should have helper.js"); + t.true(tree2.hasPath("test/button.test.js"), "Should have test file"); + + // Verify structure matches + const paths1 = tree1.getResourcePaths().sort(); + const paths2 = tree2.getResourcePaths().sort(); + t.deepEqual(paths2, paths1, "Resource paths should match"); +}); + +test("SharedHashTree.fromCache - preserves resource metadata", (t) => { + const registry = new TreeRegistry(); + + const tree1 = new SharedHashTree([ + {path: "file.js", integrity: "hash-abc123", size: 12345, lastModified: 9999, inode: 7777} + ], registry); + + const cacheData = tree1.toCacheObject(); + + const registry2 = new TreeRegistry(); + const tree2 = SharedHashTree.fromCache(cacheData, registry2); + + const node1 = tree1.root.children.get("file.js"); + const node2 = tree2.root.children.get("file.js"); + + t.is(node2.integrity, node1.integrity, "Should preserve integrity"); + t.is(node2.size, node1.size, "Should preserve size"); + t.is(node2.lastModified, node1.lastModified, "Should preserve lastModified"); + t.is(node2.inode, node1.inode, "Should preserve inode"); +}); + +test("SharedHashTree.fromCache - accepts indexTimestamp option", (t) => { + const registry = new TreeRegistry(); + + const tree1 = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"} + ], registry, {indexTimestamp: 5000}); + + const cacheData = tree1.toCacheObject(); + + const registry2 = new TreeRegistry(); + const tree2 = SharedHashTree.fromCache(cacheData, registry2, {indexTimestamp: 5000}); + + t.is(tree2.getIndexTimestamp(), 5000, "Should accept and use indexTimestamp option"); +}); + +test("SharedHashTree.fromCache - restored tree can be modified", async (t) => { + const registry = new TreeRegistry(); + + const tree1 = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"} + ], registry); + + const cacheData = tree1.toCacheObject(); + + const registry2 = new TreeRegistry(); + const tree2 = SharedHashTree.fromCache(cacheData, registry2); + + const originalHash = tree2.getRootHash(); + + // Modify restored tree + await tree2.upsertResources([ + createMockResource("b.js", "hash-b", Date.now(), 1024, 1) + ], Date.now()); + await registry2.flush(); + + const newHash = tree2.getRootHash(); + t.not(newHash, originalHash, "Hash should change after modification"); + t.true(tree2.hasPath("b.js"), "Should have new resource"); +}); + +test("SharedHashTree.fromCache - handles empty tree", (t) => { + const registry = new TreeRegistry(); + + const tree1 = new SharedHashTree([], registry); + const cacheData = tree1.toCacheObject(); + + const registry2 = new TreeRegistry(); + const tree2 = SharedHashTree.fromCache(cacheData, registry2); + + t.truthy(tree2, "Should create tree from empty cache"); + t.is(tree2.getResourcePaths().length, 0, "Should have no resources"); + t.truthy(tree2.getRootHash(), "Should have root hash even when empty"); +}); + +// ============================================================================ +// SharedHashTree upsertResources Tests +// ============================================================================ + +test("SharedHashTree - upsertResources schedules with registry", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + const resource = createMockResource("b.js", "hash-b", Date.now(), 1024, 1); + const result = await tree.upsertResources([resource], Date.now()); + + t.is(result, undefined, "Should return undefined (scheduled mode)"); + t.is(registry.getPendingUpdateCount(), 1, "Should schedule upsert with registry"); +}); + +test("SharedHashTree - upsertResources with empty array returns immediately", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + const result = await tree.upsertResources([], Date.now()); + + t.is(result, undefined, "Should return undefined"); + t.is(registry.getPendingUpdateCount(), 0, "Should not schedule anything"); +}); + +test("SharedHashTree - multiple upserts are batched", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + await tree.upsertResources([createMockResource("b.js", "hash-b", Date.now(), 1024, 1)], Date.now()); + await tree.upsertResources([createMockResource("c.js", "hash-c", Date.now(), 2048, 2)], Date.now()); + + t.is(registry.getPendingUpdateCount(), 2, "Should have 2 pending upserts"); + + const result = await registry.flush(); + t.deepEqual(result.added.sort(), ["b.js", "c.js"], "Should add both resources"); +}); + +// ============================================================================ +// SharedHashTree removeResources Tests +// ============================================================================ + +test("SharedHashTree - removeResources schedules with registry", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ], registry); + + const result = await tree.removeResources(["b.js"]); + + t.is(result, undefined, "Should return undefined (scheduled mode)"); + t.is(registry.getPendingUpdateCount(), 1, "Should schedule removal with registry"); +}); + +test("SharedHashTree - removeResources with empty array returns immediately", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + const result = await tree.removeResources([]); + + t.is(result, undefined, "Should return undefined"); + t.is(registry.getPendingUpdateCount(), 0, "Should not schedule anything"); +}); + +test("SharedHashTree - multiple removals are batched", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"}, + {path: "c.js", integrity: "hash-c"} + ], registry); + + await tree.removeResources(["b.js"]); + await tree.removeResources(["c.js"]); + + t.is(registry.getPendingUpdateCount(), 2, "Should have 2 pending removals"); + + const result = await registry.flush(); + t.deepEqual(result.removed.sort(), ["b.js", "c.js"], "Should remove both resources"); +}); + +// ============================================================================ +// SharedHashTree deriveTree Tests +// ============================================================================ + +test("SharedHashTree - deriveTree creates SharedHashTree", (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + const tree2 = tree1.deriveTree([{path: "b.js", integrity: "hash-b"}]); + + t.true(tree2 instanceof SharedHashTree, "Derived tree should be SharedHashTree"); + t.is(tree2.registry, registry, "Derived tree should share same registry"); +}); + +test("SharedHashTree - deriveTree registers derived tree", (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + t.is(registry.getTreeCount(), 1, "Should have 1 tree initially"); + + tree1.deriveTree([{path: "b.js", integrity: "hash-b"}]); + + t.is(registry.getTreeCount(), 2, "Should have 2 trees after derivation"); +}); + +test("SharedHashTree - deriveTree shares nodes with parent", (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a"}, + {path: "shared/b.js", integrity: "hash-b"} + ], registry); + + const tree2 = tree1.deriveTree([{path: "unique/c.js", integrity: "hash-c"}]); + + // Verify they share the "shared" directory node + const sharedDir1 = tree1.root.children.get("shared"); + const sharedDir2 = tree2.root.children.get("shared"); + + t.is(sharedDir1, sharedDir2, "Should share the same 'shared' directory node"); +}); + +test("SharedHashTree - deriveTree with empty resources", (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + const tree2 = tree1.deriveTree([]); + + t.is(tree1.getRootHash(), tree2.getRootHash(), "Empty derivation should have same hash"); + t.true(tree2 instanceof SharedHashTree, "Should be SharedHashTree"); +}); + +test("deriveTree - copies only modified directories (copy-on-write)", (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a"}, + {path: "shared/b.js", integrity: "hash-b"} + ], registry); + + // Derive a new tree (should share structure per design goal) + const tree2 = tree1.deriveTree([]); + + // Check if they share the "shared" directory node initially + const dir1Before = tree1.root.children.get("shared"); + const dir2Before = tree2.root.children.get("shared"); + + t.is(dir1Before, dir2Before, "Should share same directory node after deriveTree"); + + // Now insert into tree2 via the intended API (not directly) + tree2._insertResourceWithSharing("shared/c.js", {integrity: "hash-c"}); + + // Check what happened + const dir1After = tree1.root.children.get("shared"); + const dir2After = tree2.root.children.get("shared"); + + // EXPECTED BEHAVIOR (per copy-on-write): + // - Tree2 should copy "shared" directory to add "c.js" without affecting tree1 + // - dir2After !== dir1After (tree2 has its own copy) + // - dir1After === dir1Before (tree1 unchanged) + + t.is(dir1After, dir1Before, "Tree1 should be unaffected"); + t.not(dir2After, dir1After, "Tree2 should have its own copy after modification"); +}); + +test("deriveTree - preserves structural sharing for unmodified paths", (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/nested/deep/a.js", integrity: "hash-a"}, + {path: "other/b.js", integrity: "hash-b"} + ], registry); + + // Derive tree and add to "other" directory + const tree2 = tree1.deriveTree([]); + tree2._insertResourceWithSharing("other/c.js", {integrity: "hash-c"}); + + // The "shared" directory should still be shared (not copied) + // because we didn't modify it + const sharedDir1 = tree1.root.children.get("shared"); + const sharedDir2 = tree2.root.children.get("shared"); + + t.is(sharedDir1, sharedDir2, + "Unmodified 'shared' directory should remain shared between trees"); + + // But "other" should be copied (we modified it) + const otherDir1 = tree1.root.children.get("other"); + const otherDir2 = tree2.root.children.get("other"); + + t.not(otherDir1, otherDir2, + "Modified 'other' directory should be copied in tree2"); + + // Verify tree1 wasn't affected + t.false(tree1.hasPath("other/c.js"), "Tree1 should not have c.js"); + t.true(tree2.hasPath("other/c.js"), "Tree2 should have c.js"); +}); + +test("deriveTree - changes propagate to derived trees (shared view)", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a", lastModified: 1000, size: 100} + ], registry); + + // Create derived tree - it's a view on the same data, not an independent copy + const tree2 = tree1.deriveTree([ + {path: "unique/b.js", integrity: "hash-b"} + ]); + + // Get reference to shared directory in both trees + const sharedDir1 = tree1.root.children.get("shared"); + const sharedDir2 = tree2.root.children.get("shared"); + + // By design: They SHOULD share the same node reference + t.is(sharedDir1, sharedDir2, "Trees share directory nodes (intentional design)"); + + // When tree1 is updated, tree2 sees the change (filtered view behavior) + const indexTimestamp = tree1.getIndexTimestamp(); + await tree1.upsertResources([ + createMockResource("shared/a.js", "new-hash-a", indexTimestamp + 1, 101, 1) + ]); + await registry.flush(); + + // Both trees see the update as per design + const node1 = tree1.root.children.get("shared").children.get("a.js"); + const node2 = tree2.root.children.get("shared").children.get("a.js"); + + t.is(node1, node2, "Same resource node (shared reference)"); + t.is(node1.integrity, "new-hash-a", "Tree1 sees update"); + t.is(node2.integrity, "new-hash-a", "Tree2 also sees update (intentional)"); + + // This is the intended behavior: derived trees are views, not snapshots + // Tree2 filters which resources it exposes, but underlying data is shared +}); + + +// ============================================================================ +// getAddedResources Tests +// ============================================================================ + +test("getAddedResources - returns empty array when no resources added", (t) => { + const registry = new TreeRegistry(); + const baseTree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ], registry); + + const derivedTree = baseTree.deriveTree([]); + + const added = derivedTree.getAddedResources(baseTree); + + t.deepEqual(added, [], "Should return empty array when no resources added"); +}); + +test("getAddedResources - returns added resources from derived tree", (t) => { + const registry = new TreeRegistry(); + const baseTree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ], registry); + + const derivedTree = baseTree.deriveTree([ + {path: "c.js", integrity: "hash-c", size: 100, lastModified: 1000, inode: 1}, + {path: "d.js", integrity: "hash-d", size: 200, lastModified: 2000, inode: 2} + ]); + + const added = derivedTree.getAddedResources(baseTree); + + t.is(added.length, 2, "Should return 2 added resources"); + t.deepEqual(added, [ + {path: "/c.js", integrity: "hash-c", size: 100, lastModified: 1000, inode: 1}, + {path: "/d.js", integrity: "hash-d", size: 200, lastModified: 2000, inode: 2} + ], "Should return correct added resources with metadata"); +}); + +test("getAddedResources - handles nested directory additions", (t) => { + const registry = new TreeRegistry(); + const baseTree = new SharedHashTree([ + {path: "root/a.js", integrity: "hash-a"} + ], registry); + + const derivedTree = baseTree.deriveTree([ + {path: "root/nested/b.js", integrity: "hash-b", size: 100, lastModified: 1000, inode: 1}, + {path: "root/nested/c.js", integrity: "hash-c", size: 200, lastModified: 2000, inode: 2} + ]); + + const added = derivedTree.getAddedResources(baseTree); + + t.is(added.length, 2, "Should return 2 added resources"); + t.true(added.some((r) => r.path === "/root/nested/b.js"), "Should include nested b.js"); + t.true(added.some((r) => r.path === "/root/nested/c.js"), "Should include nested c.js"); +}); + +test("getAddedResources - handles new directory with multiple resources", (t) => { + const registry = new TreeRegistry(); + const baseTree = new SharedHashTree([ + {path: "src/a.js", integrity: "hash-a"} + ], registry); + + const derivedTree = baseTree.deriveTree([ + {path: "lib/b.js", integrity: "hash-b", size: 100, lastModified: 1000, inode: 1}, + {path: "lib/c.js", integrity: "hash-c", size: 200, lastModified: 2000, inode: 2}, + {path: "lib/nested/d.js", integrity: "hash-d", size: 300, lastModified: 3000, inode: 3} + ]); + + const added = derivedTree.getAddedResources(baseTree); + + t.is(added.length, 3, "Should return 3 added resources"); + t.true(added.some((r) => r.path === "/lib/b.js"), "Should include lib/b.js"); + t.true(added.some((r) => r.path === "/lib/c.js"), "Should include lib/c.js"); + t.true(added.some((r) => r.path === "/lib/nested/d.js"), "Should include nested resource"); +}); + +test("getAddedResources - preserves metadata for added resources", (t) => { + const registry = new TreeRegistry(); + const baseTree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"} + ], registry); + + const derivedTree = baseTree.deriveTree([ + {path: "b.js", integrity: "hash-b", size: 12345, lastModified: 9999, inode: 7777} + ]); + + const added = derivedTree.getAddedResources(baseTree); + + t.is(added.length, 1, "Should return 1 added resource"); + t.is(added[0].path, "/b.js", "Should have correct path"); + t.is(added[0].integrity, "hash-b", "Should preserve integrity"); + t.is(added[0].size, 12345, "Should preserve size"); + t.is(added[0].lastModified, 9999, "Should preserve lastModified"); + t.is(added[0].inode, 7777, "Should preserve inode"); +}); + +test("getAddedResources - handles mixed shared and added resources", (t) => { + const registry = new TreeRegistry(); + const baseTree = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a"}, + {path: "shared/b.js", integrity: "hash-b"} + ], registry); + + const derivedTree = baseTree.deriveTree([ + {path: "shared/c.js", integrity: "hash-c", size: 100, lastModified: 1000, inode: 1}, + {path: "unique/d.js", integrity: "hash-d", size: 200, lastModified: 2000, inode: 2} + ]); + + const added = derivedTree.getAddedResources(baseTree); + + t.is(added.length, 2, "Should return 2 added resources"); + t.true(added.some((r) => r.path === "/shared/c.js"), "Should include c.js in shared dir"); + t.true(added.some((r) => r.path === "/unique/d.js"), "Should include d.js in unique dir"); + t.false(added.some((r) => r.path === "/shared/a.js"), "Should not include shared a.js"); + t.false(added.some((r) => r.path === "/shared/b.js"), "Should not include shared b.js"); +}); + +test("getAddedResources - handles deeply nested additions", (t) => { + const registry = new TreeRegistry(); + const baseTree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"} + ], registry); + + const derivedTree = baseTree.deriveTree([ + {path: "dir1/dir2/dir3/dir4/deep.js", integrity: "hash-deep", size: 100, lastModified: 1000, inode: 1} + ]); + + const added = derivedTree.getAddedResources(baseTree); + + t.is(added.length, 1, "Should return 1 added resource"); + t.is(added[0].path, "/dir1/dir2/dir3/dir4/deep.js", "Should have correct deeply nested path"); + t.is(added[0].integrity, "hash-deep", "Should preserve integrity"); +}); + + +// ============================================================================ +// SharedHashTree with Registry Integration Tests +// ============================================================================ + +test("SharedHashTree - changes via registry affect tree", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + const originalHash = tree.getRootHash(); + + await tree.upsertResources([createMockResource("b.js", "hash-b", Date.now(), 1024, 1)], Date.now()); + await registry.flush(); + + const newHash = tree.getRootHash(); + t.not(originalHash, newHash, "Root hash should change after flush"); + t.true(tree.hasPath("b.js"), "Tree should have new resource"); +}); + +test("SharedHashTree - batch updates via registry", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a", lastModified: 1000, size: 100} + ], registry); + + const indexTimestamp = tree.getIndexTimestamp(); + + // Schedule multiple operations + await tree.upsertResources([createMockResource("b.js", "hash-b", Date.now(), 1024, 1)], Date.now()); + await tree.upsertResources([createMockResource("a.js", "new-hash-a", indexTimestamp + 1, 101, 1)], Date.now()); + + const result = await registry.flush(); + + t.deepEqual(result.added, ["b.js"], "Should add b.js"); + t.deepEqual(result.updated, ["a.js"], "Should update a.js"); +}); + +test("SharedHashTree - multiple trees coordinate via registry", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a"} + ], registry); + + const tree2 = tree1.deriveTree([{path: "unique/b.js", integrity: "hash-b"}]); + + // Verify they share directory nodes + const sharedDir1Before = tree1.root.children.get("shared"); + const sharedDir2Before = tree2.root.children.get("shared"); + t.is(sharedDir1Before, sharedDir2Before, "Should share nodes before update"); + + // Update shared resource via tree1 + const indexTimestamp = tree1.getIndexTimestamp(); + await tree1.upsertResources([ + createMockResource("shared/a.js", "new-hash-a", indexTimestamp + 1, 101, 1) + ], Date.now()); + + await registry.flush(); + + // Both trees see the change + const node1 = tree1.root.children.get("shared").children.get("a.js"); + const node2 = tree2.root.children.get("shared").children.get("a.js"); + + t.is(node1, node2, "Should share same resource node"); + t.is(node1.integrity, "new-hash-a", "Tree1 sees update"); + t.is(node2.integrity, "new-hash-a", "Tree2 sees update (shared node)"); +}); + +test("SharedHashTree - registry tracks per-tree statistics", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + const tree2 = new SharedHashTree([{path: "b.js", integrity: "hash-b"}], registry); + + await tree1.upsertResources([createMockResource("c.js", "hash-c", Date.now(), 1024, 1)], Date.now()); + await tree2.upsertResources([createMockResource("d.js", "hash-d", Date.now(), 2048, 2)], Date.now()); + + const result = await registry.flush(); + + t.is(result.treeStats.size, 2, "Should have stats for 2 trees"); + // Each tree only sees additions for resources added to itself (not to other independent trees) + const stats1 = result.treeStats.get(tree1); + const stats2 = result.treeStats.get(tree2); + + // c.js is only added to tree1, d.js is only added to tree2 + t.deepEqual(stats1.added.sort(), ["c.js"], "Tree1 should see c.js addition"); + t.deepEqual(stats2.added.sort(), ["d.js"], "Tree2 should see d.js addition"); +}); + +test("SharedHashTree - unregister removes tree from coordination", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + new SharedHashTree([{path: "b.js", integrity: "hash-b"}], registry); + + t.is(registry.getTreeCount(), 2, "Should have 2 trees"); + + registry.unregister(tree1); + + t.is(registry.getTreeCount(), 1, "Should have 1 tree after unregister"); + + // Operations on tree1 no longer coordinated + await tree1.upsertResources([createMockResource("c.js", "hash-c", Date.now(), 1024, 1)], Date.now()); + const result = await registry.flush(); + + // tree1 not in results since it's unregistered + t.is(result.treeStats.size, 1, "Should only have stats for tree2"); + t.false(result.treeStats.has(tree1), "Should not have stats for unregistered tree1"); +}); + +test("SharedHashTree - complex multi-tree coordination", async (t) => { + const registry = new TreeRegistry(); + + // Create base tree + const baseTree = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a"}, + {path: "shared/b.js", integrity: "hash-b"} + ], registry); + + // Derive two trees from base + const derived1 = baseTree.deriveTree([{path: "d1/c.js", integrity: "hash-c"}]); + const derived2 = baseTree.deriveTree([{path: "d2/d.js", integrity: "hash-d"}]); + + t.is(registry.getTreeCount(), 3, "Should have 3 trees"); + + // Schedule updates to shared resource + const indexTimestamp = baseTree.getIndexTimestamp(); + await baseTree.upsertResources([ + createMockResource("shared/a.js", "new-hash-a", indexTimestamp + 1, 101, 1) + ], Date.now()); + + const result = await registry.flush(); + + // All trees see the update + t.deepEqual(result.treeStats.get(baseTree).updated, ["shared/a.js"]); + t.deepEqual(result.treeStats.get(derived1).updated, ["shared/a.js"]); + t.deepEqual(result.treeStats.get(derived2).updated, ["shared/a.js"]); + + // Verify shared nodes + const sharedA1 = baseTree.root.children.get("shared").children.get("a.js"); + const sharedA2 = derived1.root.children.get("shared").children.get("a.js"); + const sharedA3 = derived2.root.children.get("shared").children.get("a.js"); + + t.is(sharedA1, sharedA2, "baseTree and derived1 share node"); + t.is(sharedA1, sharedA3, "baseTree and derived2 share node"); + t.is(sharedA1.integrity, "new-hash-a", "All see updated value"); +}); diff --git a/packages/project/test/lib/build/cache/index/TreeRegistry.js b/packages/project/test/lib/build/cache/index/TreeRegistry.js new file mode 100644 index 00000000000..d4e116a7cfd --- /dev/null +++ b/packages/project/test/lib/build/cache/index/TreeRegistry.js @@ -0,0 +1,972 @@ +import test from "ava"; +import sinon from "sinon"; +import SharedHashTree from "../../../../../lib/build/cache/index/SharedHashTree.js"; +import TreeRegistry from "../../../../../lib/build/cache/index/TreeRegistry.js"; + +// Helper to create mock Resource instances +function createMockResource(path, integrity, lastModified, size, inode) { + return { + getOriginalPath: () => path, + getIntegrity: async () => integrity, + getLastModified: () => lastModified, + getSize: async () => size, + getInode: () => inode + }; +} + +test.afterEach.always((t) => { + sinon.restore(); +}); + +// ============================================================================ +// TreeRegistry Tests +// ============================================================================ + +test("TreeRegistry - register and track trees", (t) => { + const registry = new TreeRegistry(); + new SharedHashTree([{path: "a.js", integrity: "hash1"}], registry); + new SharedHashTree([{path: "b.js", integrity: "hash2"}], registry); + + t.is(registry.getTreeCount(), 2, "Should track both trees"); +}); + +test("TreeRegistry - schedule and flush updates", async (t) => { + const registry = new TreeRegistry(); + const resources = [{path: "file.js", integrity: "hash1"}]; + const tree = new SharedHashTree(resources, registry); + + const originalHash = tree.getRootHash(); + + const resource = createMockResource("file.js", "hash2", Date.now(), 2048, 456); + registry.scheduleUpsert(resource); + t.is(registry.getPendingUpdateCount(), 1, "Should have one pending update"); + + const result = await registry.flush(); + t.is(registry.getPendingUpdateCount(), 0, "Should have no pending updates after flush"); + t.deepEqual(result.updated, ["file.js"], "Should return changed resource path"); + + const newHash = tree.getRootHash(); + t.not(originalHash, newHash, "Root hash should change after flush"); +}); + +test("TreeRegistry - flush returns only changed resources", async (t) => { + const registry = new TreeRegistry(); + const timestamp = Date.now(); + const resources = [ + {path: "file1.js", integrity: "hash1", lastModified: timestamp, size: 1024, inode: 123}, + {path: "file2.js", integrity: "hash2", lastModified: timestamp, size: 2048, inode: 124} + ]; + new SharedHashTree(resources, registry); + + registry.scheduleUpsert(createMockResource("file1.js", "new-hash1", timestamp, 1024, 123)); + registry.scheduleUpsert(createMockResource("file2.js", "hash2", timestamp, 2048, 124)); // unchanged + + const result = await registry.flush(); + t.deepEqual(result.updated, ["file1.js"], "Should return only changed resource"); +}); + +test("TreeRegistry - flush returns empty array when no changes", async (t) => { + const registry = new TreeRegistry(); + const timestamp = Date.now(); + const resources = [{path: "file.js", integrity: "hash1", lastModified: timestamp, size: 1024, inode: 123}]; + new SharedHashTree(resources, registry); + + registry.scheduleUpsert(createMockResource("file.js", "hash1", timestamp, 1024, 123)); // same value + + const result = await registry.flush(); + t.deepEqual(result.updated, [], "Should return empty array when no actual changes"); +}); + +test("TreeRegistry - batch updates affect all trees sharing nodes", async (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "shared/a.js", integrity: "hash-a"}, + {path: "shared/b.js", integrity: "hash-b"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const originalHash1 = tree1.getRootHash(); + + // Create derived tree that shares "shared" directory + const tree2 = tree1.deriveTree([{path: "unique/c.js", integrity: "hash-c"}]); + const originalHash2 = tree2.getRootHash(); + t.not(originalHash1, originalHash2, "Hashes should differ due to unique content"); + + // Verify they share the same "shared" directory node + const sharedDir1 = tree1.root.children.get("shared"); + const sharedDir2 = tree2.root.children.get("shared"); + t.is(sharedDir1, sharedDir2, "Should share the same 'shared' directory node"); + + // Update shared resource + registry.scheduleUpsert(createMockResource("shared/a.js", "new-hash-a", Date.now(), 2048, 999)); + const result = await registry.flush(); + + t.deepEqual(result.updated, ["shared/a.js"], "Should report the updated resource"); + + const newHash1 = tree1.getRootHash(); + const newHash2 = tree2.getRootHash(); + + t.not(originalHash1, newHash1, "Tree1 hash should change"); + t.not(originalHash2, newHash2, "Tree2 hash should change"); + t.not(newHash1, newHash2, "Hashes should differ due to unique content"); + + // Both trees should see the update + const resource1 = tree1.getResourceByPath("shared/a.js"); + const resource2 = tree2.getResourceByPath("shared/a.js"); + + t.is(resource1.integrity, "new-hash-a", "Tree1 should have updated integrity"); + t.is(resource2.integrity, "new-hash-a", "Tree2 should have updated integrity (shared node)"); +}); + +test("TreeRegistry - handles missing resources gracefully during flush", async (t) => { + const registry = new TreeRegistry(); + new SharedHashTree([{path: "exists.js", integrity: "hash1"}], registry); + + // Schedule update for non-existent resource + registry.scheduleUpsert(createMockResource("missing.js", "hash2", Date.now(), 1024, 444)); + + // Should not throw + await t.notThrows(async () => await registry.flush(), "Should handle missing resources gracefully"); +}); + +test("TreeRegistry - multiple updates to same resource", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "file.js", integrity: "v1"}], registry); + + const timestamp = Date.now(); + registry.scheduleUpsert(createMockResource("file.js", "v2", timestamp, 1024, 100)); + registry.scheduleUpsert(createMockResource("file.js", "v3", timestamp + 1, 1024, 100)); + registry.scheduleUpsert(createMockResource("file.js", "v4", timestamp + 2, 1024, 100)); + + t.is(registry.getPendingUpdateCount(), 1, "Should consolidate updates to same path"); + + await registry.flush(); + + // Should apply the last update + t.is(tree.getResourceByPath("file.js").integrity, "v4", "Should apply last update"); +}); + +test("TreeRegistry - updates without changes lead to same hash", async (t) => { + const registry = new TreeRegistry(); + const timestamp = Date.now(); + const tree = new SharedHashTree([{ + path: "/src/foo/file1.js", integrity: "v1", + }, { + path: "/src/foo/file3.js", integrity: "v1", + }, { + path: "/src/foo/file2.js", integrity: "v1", + }], registry); + const initialHash = tree.getRootHash(); + const file2Hash = tree.getResourceByPath("/src/foo/file2.js").hash; + + registry.scheduleUpsert(createMockResource("/src/foo/file2.js", "v1", timestamp, 1024, 200)); + + t.is(registry.getPendingUpdateCount(), 1, "Should have one pending update"); + + await registry.flush(); + + // Should apply the last update + t.is(tree.getResourceByPath("/src/foo/file2.js").hash.toString("hex"), file2Hash.toString("hex"), + "Should have same has for file"); + t.is(tree.getRootHash(), initialHash, "Root hash should remain unchanged"); +}); + +test("TreeRegistry - unregister tree", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash1"}], registry); + const tree2 = new SharedHashTree([{path: "b.js", integrity: "hash2"}], registry); + + t.is(registry.getTreeCount(), 2); + + registry.unregister(tree1); + t.is(registry.getTreeCount(), 1); + + // Flush should only affect tree2 + registry.scheduleUpsert(createMockResource("b.js", "new-hash2", Date.now(), 1024, 777)); + await registry.flush(); + + t.notThrows(() => tree2.getRootHash(), "Tree2 should still work"); +}); + +// ============================================================================ +// Derived Tree Tests +// ============================================================================ + +test("deriveTree - creates tree sharing subtrees", (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "dir1/a.js", integrity: "hash-a"}, + {path: "dir1/b.js", integrity: "hash-b"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([{path: "dir2/c.js", integrity: "hash-c"}]); + + // Both trees should have dir1 + t.truthy(tree2.hasPath("dir1/a.js"), "Derived tree should have shared resources"); + t.truthy(tree2.hasPath("dir2/c.js"), "Derived tree should have new resources"); + + // Tree1 should not have dir2 + t.false(tree1.hasPath("dir2/c.js"), "Original tree should not have derived resources"); +}); + +test("deriveTree - shared nodes are the same reference", (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "shared/file.js", integrity: "hash1"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([]); + + // Get the shared directory node from both trees + const dir1 = tree1.root.children.get("shared"); + const dir2 = tree2.root.children.get("shared"); + + t.is(dir1, dir2, "Shared directory nodes should be same reference"); + + // Get the file node + const file1 = dir1.children.get("file.js"); + const file2 = dir2.children.get("file.js"); + + t.is(file1, file2, "Shared resource nodes should be same reference"); +}); + +test("deriveTree - updates to shared nodes visible in all sub-trees", async (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "shared/file.js", integrity: "original"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([]); + + // Get nodes before update + const node1Before = tree1.getResourceByPath("shared/file.js"); + const node2Before = tree2.getResourceByPath("shared/file.js"); + + t.is(node1Before, node2Before, "Should be same node reference"); + t.is(node1Before.integrity, "original", "Original integrity"); + + // Update via registry + registry.scheduleUpsert(createMockResource("shared/file.js", "updated", Date.now(), 1024, 555)); + await registry.flush(); + + // Both should see the update (same node) + t.is(node1Before.integrity, "updated", "Tree1 node should be updated"); + t.is(node2Before.integrity, "updated", "Tree2 node should be updated (same reference)"); +}); + +test("deriveTree - updates to sub-tree nodes are not visible in parents", async (t) => { + const registry = new TreeRegistry(); + const sharedResources = [ + {path: "shared/file.js", integrity: "original"} + ]; + const uniqueResources = [ + {path: "unique/file.js", integrity: "original"} + ]; + + const tree1 = new SharedHashTree(sharedResources, registry); + const tree2 = tree1.deriveTree(uniqueResources); + + // Update via tree2.upsertResources to ensure it's scoped to tree2 + await tree2.upsertResources([createMockResource("unique/file.js", "updated", Date.now(), 1024, 555)], Date.now()); + await registry.flush(); + + t.deepEqual(tree1.getResourcePaths(), ["/shared/file.js"], "Parent tree should not have unique resource"); + t.is(tree2.getResourceByPath("/unique/file.js").integrity, "updated", "Derived tree should see its own update"); +}); + +test("deriveTree - multiple levels of derivation", async (t) => { + const registry = new TreeRegistry(); + + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + const tree2 = tree1.deriveTree([{path: "b.js", integrity: "hash-b"}]); + const tree3 = tree2.deriveTree([{path: "c.js", integrity: "hash-c"}]); + + t.truthy(tree3.hasPath("a.js"), "Should have resources from tree1"); + t.truthy(tree3.hasPath("b.js"), "Should have resources from tree2"); + t.truthy(tree3.hasPath("c.js"), "Should have its own resources"); + + // Update shared resource + registry.scheduleUpsert(createMockResource("a.js", "new-hash-a", Date.now(), 1024, 111)); + await registry.flush(); + + // All trees should see the update + t.is(tree1.getResourceByPath("a.js").integrity, "new-hash-a"); + t.is(tree2.getResourceByPath("a.js").integrity, "new-hash-a"); + t.is(tree3.getResourceByPath("a.js").integrity, "new-hash-a"); +}); + +test("deriveTree - efficient hash recomputation", async (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "dir1/a.js", integrity: "hash-a"}, + {path: "dir1/b.js", integrity: "hash-b"}, + {path: "dir2/c.js", integrity: "hash-c"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([{path: "dir3/d.js", integrity: "hash-d"}]); + + // Spy on _computeHash to count calls + const computeSpy = sinon.spy(tree1, "_computeHash"); + const compute2Spy = sinon.spy(tree2, "_computeHash"); + + // Update resource in shared directory + registry.scheduleUpsert(createMockResource("dir1/a.js", "new-hash-a", Date.now(), 2048, 222)); + await registry.flush(); + + // Each affected directory should be hashed once per tree + // dir1/a.js node, dir1 node, root node for each tree + t.true(computeSpy.callCount >= 3, "Tree1 should recompute affected nodes"); + t.true(compute2Spy.callCount >= 3, "Tree2 should recompute affected nodes"); +}); + +test("deriveTree - independent updates to different directories", async (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "dir1/a.js", integrity: "hash-a"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([{path: "dir2/b.js", integrity: "hash-b"}]); + + const hash1Before = tree1.getRootHash(); + const hash2Before = tree2.getRootHash(); + + // Update only in tree2's unique directory + registry.scheduleUpsert(createMockResource("dir2/b.js", "new-hash-b", Date.now(), 1024, 333)); + await registry.flush(); + + const hash1After = tree1.getRootHash(); + const hash2After = tree2.getRootHash(); + + // Both trees are affected because they share the root and dir2 is added/updated via registry + t.not(hash1Before, hash1After, "Tree1 hash changes (dir2 added to shared root)"); + t.not(hash2Before, hash2After, "Tree2 hash should change"); + + // Tree1 now has dir2 because registry ensures directory path exists + t.truthy(tree1.hasPath("dir2/b.js"), "Tree1 should now have dir2/b.js"); + t.truthy(tree2.hasPath("dir2/b.js"), "Tree2 should have dir2/b.js"); +}); + +test("deriveTree - preserves tree statistics correctly", (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "dir1/a.js", integrity: "hash-a"}, + {path: "dir1/b.js", integrity: "hash-b"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([ + {path: "dir2/c.js", integrity: "hash-c"}, + {path: "dir2/d.js", integrity: "hash-d"} + ]); + + const stats1 = tree1.getStats(); + const stats2 = tree2.getStats(); + + t.is(stats1.resources, 2, "Tree1 should have 2 resources"); + t.is(stats2.resources, 4, "Tree2 should have 4 resources"); + t.true(stats2.directories >= stats1.directories, "Tree2 should have at least as many directories"); +}); + +test("deriveTree - empty derivation creates exact copy with shared nodes", (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "file.js", integrity: "hash1"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([]); + + // Should have same structure + t.is(tree1.getRootHash(), tree2.getRootHash(), "Should have same root hash"); + + // But different root nodes (shallow copied) + t.not(tree1.root, tree2.root, "Root nodes should be different"); + + // But shared children + const child1 = tree1.root.children.get("file.js"); + const child2 = tree2.root.children.get("file.js"); + t.is(child1, child2, "Children should be shared"); +}); + +test("deriveTree - complex shared structure", async (t) => { + const registry = new TreeRegistry(); + const resources = [ + {path: "shared/deep/nested/file1.js", integrity: "hash1"}, + {path: "shared/deep/file2.js", integrity: "hash2"}, + {path: "shared/file3.js", integrity: "hash3"} + ]; + + const tree1 = new SharedHashTree(resources, registry); + const tree2 = tree1.deriveTree([ + {path: "unique/file4.js", integrity: "hash4"} + ]); + + // Update deeply nested shared file + registry.scheduleUpsert(createMockResource("shared/deep/nested/file1.js", "new-hash1", Date.now(), 2048, 666)); + await registry.flush(); + + // Both trees should reflect the change + t.is(tree1.getResourceByPath("shared/deep/nested/file1.js").integrity, "new-hash1"); + t.is(tree2.getResourceByPath("shared/deep/nested/file1.js").integrity, "new-hash1"); + + // Root hashes should both change + const paths1 = tree1.getResourcePaths(); + const paths2 = tree2.getResourcePaths(); + + t.is(paths1.length, 3, "Tree1 should have 3 resources"); + t.is(paths2.length, 4, "Tree2 should have 4 resources"); +}); + +// ============================================================================ +// upsertResources Tests with Registry +// ============================================================================ + +test("upsertResources - with registry schedules operations", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + const result = await tree.upsertResources([ + createMockResource("b.js", "hash-b", Date.now(), 1024, 1) + ]); + + t.is(result, undefined, "Should return undefined in scheduled mode"); +}); + +test("upsertResources - with registry and flush", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + const originalHash = tree.getRootHash(); + + await tree.upsertResources([ + createMockResource("b.js", "hash-b", Date.now(), 1024, 1), + createMockResource("c.js", "hash-c", Date.now(), 2048, 2) + ]); + + const result = await registry.flush(); + + t.truthy(result.added, "Result should have added array"); + t.true(result.added.includes("b.js"), "Should report b.js as added"); + t.true(result.added.includes("c.js"), "Should report c.js as added"); + + t.truthy(tree.hasPath("b.js"), "Tree should have b.js"); + t.truthy(tree.hasPath("c.js"), "Tree should have c.js"); + t.not(tree.getRootHash(), originalHash, "Root hash should change"); +}); + +test("upsertResources - with derived trees", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "shared/a.js", integrity: "hash-a"}], registry); + const tree2 = tree1.deriveTree([{path: "unique/b.js", integrity: "hash-b"}]); + + await tree1.upsertResources([ + createMockResource("shared/c.js", "hash-c", Date.now(), 1024, 3) + ]); + + await registry.flush(); + + t.truthy(tree1.hasPath("shared/c.js"), "Tree1 should have shared/c.js"); + t.truthy(tree2.hasPath("shared/c.js"), "Tree2 should also have shared/c.js"); + t.false(tree1.hasPath("unique/b.js"), "Tree1 should not have unique/b.js"); + t.truthy(tree2.hasPath("unique/b.js"), "Tree2 should have unique/b.js"); +}); + +// ============================================================================ +// removeResources Tests with Registry +// ============================================================================ + +test("removeResources - with registry schedules operations", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ], registry); + + const result = await tree.removeResources(["b.js"]); + + t.is(result, undefined, "Should return undefined in scheduled mode"); +}); + +test("removeResources - with registry and flush", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"}, + {path: "c.js", integrity: "hash-c"} + ], registry); + const originalHash = tree.getRootHash(); + + await tree.removeResources(["b.js", "c.js"]); + + const result = await registry.flush(); + + t.truthy(result.removed, "Result should have removed array"); + t.true(result.removed.includes("b.js"), "Should report b.js as removed"); + t.true(result.removed.includes("c.js"), "Should report c.js as removed"); + + t.truthy(tree.hasPath("a.js"), "Tree should still have a.js"); + t.false(tree.hasPath("b.js"), "Tree should not have b.js"); + t.false(tree.hasPath("c.js"), "Tree should not have c.js"); + t.not(tree.getRootHash(), originalHash, "Root hash should change"); +}); + +test("removeResources - with derived trees propagates removal", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a"}, + {path: "shared/b.js", integrity: "hash-b"} + ], registry); + const tree2 = tree1.deriveTree([{path: "unique/c.js", integrity: "hash-c"}]); + + // Verify both trees share the resources + t.truthy(tree1.hasPath("shared/a.js")); + t.truthy(tree1.hasPath("shared/b.js")); + t.truthy(tree2.hasPath("shared/a.js")); + t.truthy(tree2.hasPath("shared/b.js")); + + // Remove from shared directory + await tree1.removeResources(["shared/b.js"]); + await registry.flush(); + + // Both trees should see the removal + t.truthy(tree1.hasPath("shared/a.js"), "Tree1 should still have shared/a.js"); + t.false(tree1.hasPath("shared/b.js"), "Tree1 should not have shared/b.js"); + t.truthy(tree2.hasPath("shared/a.js"), "Tree2 should still have shared/a.js"); + t.false(tree2.hasPath("shared/b.js"), "Tree2 should not have shared/b.js"); + t.truthy(tree2.hasPath("unique/c.js"), "Tree2 should still have unique/c.js"); +}); + +test("removeResources - with registry cleans up empty directories", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "dir1/dir2/only.js", integrity: "hash-only"}, + {path: "dir1/other.js", integrity: "hash-other"} + ], registry); + + // Verify structure before removal + t.truthy(tree.hasPath("dir1/dir2/only.js"), "Should have dir1/dir2/only.js"); + t.truthy(tree._findNode("dir1/dir2"), "Directory dir1/dir2 should exist"); + + // Remove the only resource in dir2 + await tree.removeResources(["dir1/dir2/only.js"]); + const result = await registry.flush(); + + t.true(result.removed.includes("dir1/dir2/only.js"), "Should report resource as removed"); + t.false(tree.hasPath("dir1/dir2/only.js"), "Should not have dir1/dir2/only.js"); + + // Check if empty directory is cleaned up + const dir2Node = tree._findNode("dir1/dir2"); + t.is(dir2Node, null, "Empty directory dir1/dir2 should be removed"); + + // Parent directory should still exist with other.js + t.truthy(tree.hasPath("dir1/other.js"), "Should still have dir1/other.js"); + t.truthy(tree._findNode("dir1"), "Parent directory dir1 should still exist"); +}); + +test("removeResources - with registry cleans up deeply nested empty directories", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "a/b/c/d/e/deep.js", integrity: "hash-deep"}, + {path: "a/sibling.js", integrity: "hash-sibling"} + ], registry); + + // Verify structure before removal + t.truthy(tree.hasPath("a/b/c/d/e/deep.js"), "Should have deeply nested file"); + t.truthy(tree._findNode("a/b/c/d/e"), "Deep directory should exist"); + + // Remove the only resource in the deep hierarchy + await tree.removeResources(["a/b/c/d/e/deep.js"]); + const result = await registry.flush(); + + t.true(result.removed.includes("a/b/c/d/e/deep.js"), "Should report resource as removed"); + + // All empty directories in the chain should be removed + t.is(tree._findNode("a/b/c/d/e"), null, "Directory e should be removed"); + t.is(tree._findNode("a/b/c/d"), null, "Directory d should be removed"); + t.is(tree._findNode("a/b/c"), null, "Directory c should be removed"); + t.is(tree._findNode("a/b"), null, "Directory b should be removed"); + + // Parent directory with sibling should still exist + t.truthy(tree._findNode("a"), "Directory a should still exist (has sibling.js)"); + t.truthy(tree.hasPath("a/sibling.js"), "Sibling file should still exist"); +}); + +test("removeResources - with derived trees cleans up empty directories in both trees", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/dir/only.js", integrity: "hash-only"}, + {path: "shared/other.js", integrity: "hash-other"} + ], registry); + const tree2 = tree1.deriveTree([{path: "unique/file.js", integrity: "hash-unique"}]); + + // Verify both trees share the directory structure + const sharedDirBefore = tree1.root.children.get("shared").children.get("dir"); + const sharedDirBefore2 = tree2.root.children.get("shared").children.get("dir"); + t.is(sharedDirBefore, sharedDirBefore2, "Should share the same 'shared/dir' node"); + + // Remove the only resource in shared/dir + await tree1.removeResources(["shared/dir/only.js"]); + await registry.flush(); + + // Both trees should see empty directory removal + t.is(tree1._findNode("shared/dir"), null, "Tree1: empty directory should be removed"); + t.is(tree2._findNode("shared/dir"), null, "Tree2: empty directory should be removed"); + + // Shared parent directory should still exist with other.js + t.truthy(tree1._findNode("shared"), "Tree1: shared directory should still exist"); + t.truthy(tree2._findNode("shared"), "Tree2: shared directory should still exist"); + t.truthy(tree1.hasPath("shared/other.js"), "Tree1 should still have shared/other.js"); + t.truthy(tree2.hasPath("shared/other.js"), "Tree2 should still have shared/other.js"); + + // Tree2's unique content should be unaffected + t.truthy(tree2.hasPath("unique/file.js"), "Tree2 should still have unique file"); +}); + +test("removeResources - multiple removals with registry clean up shared empty directories", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "dir1/sub1/file1.js", integrity: "hash1"}, + {path: "dir1/sub2/file2.js", integrity: "hash2"}, + {path: "dir2/file3.js", integrity: "hash3"} + ], registry); + + // Remove both files from dir1 (making both sub1 and sub2 empty) + await tree.removeResources(["dir1/sub1/file1.js", "dir1/sub2/file2.js"]); + await registry.flush(); + + // Both subdirectories should be cleaned up + t.is(tree._findNode("dir1/sub1"), null, "sub1 should be removed"); + t.is(tree._findNode("dir1/sub2"), null, "sub2 should be removed"); + + // dir1 should also be removed since it's now empty + const dir1 = tree._findNode("dir1"); + t.is(dir1, null, "dir1 should be removed (now empty)"); + + // dir2 should be unaffected + t.truthy(tree.hasPath("dir2/file3.js"), "dir2/file3.js should still exist"); +}); + +// ============================================================================ +// Combined upsert and remove operations with Registry +// ============================================================================ + +test("upsertResources and removeResources - combined operations", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"} + ], registry); + const originalHash = tree.getRootHash(); + + // Schedule both operations + await tree.upsertResources([ + createMockResource("c.js", "hash-c", Date.now(), 1024, 3) + ]); + await tree.removeResources(["b.js"]); + + const result = await registry.flush(); + + t.true(result.added.includes("c.js"), "Should add c.js"); + t.true(result.removed.includes("b.js"), "Should remove b.js"); + + t.truthy(tree.hasPath("a.js"), "Tree should have a.js"); + t.false(tree.hasPath("b.js"), "Tree should not have b.js"); + t.truthy(tree.hasPath("c.js"), "Tree should have c.js"); + t.not(tree.getRootHash(), originalHash, "Root hash should change"); +}); + +test("upsertResources and removeResources - conflicting operations on same path", async (t) => { + const registry = new TreeRegistry(); + const tree = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + + // Schedule removal then upsert (upsert should win) + await tree.removeResources(["a.js"]); + await tree.upsertResources([ + createMockResource("a.js", "new-hash-a", Date.now(), 1024, 1) + ]); + + const result = await registry.flush(); + + // Upsert cancels removal + t.deepEqual(result.removed, [], "Should have no removals"); + t.true(result.updated.includes("a.js") || result.changed.includes("a.js"), "Should update or keep a.js"); + t.truthy(tree.hasPath("a.js"), "Tree should still have a.js"); +}); + +// ============================================================================ +// Per-Tree Statistics Tests +// ============================================================================ + +test("TreeRegistry - flush returns per-tree statistics", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + const tree2 = new SharedHashTree([{path: "b.js", integrity: "hash-b"}], registry); + + // Update tree1 resource + registry.scheduleUpsert(createMockResource("a.js", "new-hash-a", Date.now(), 1024, 1)); + // Add new resource - gets added to all trees + registry.scheduleUpsert(createMockResource("c.js", "hash-c", Date.now(), 2048, 2)); + + const result = await registry.flush(); + + // Verify global results + // a.js gets updated in tree1 but added to tree2 (didn't exist before) + t.true(result.updated.includes("a.js"), "Should report a.js as updated"); + t.true(result.added.includes("c.js"), "Should report c.js as added"); + t.true(result.added.includes("a.js"), "Should report a.js as added to tree2"); + + // Verify per-tree statistics + t.truthy(result.treeStats, "Should have treeStats"); + t.is(result.treeStats.size, 2, "Should have stats for both trees"); + + const stats1 = result.treeStats.get(tree1); + const stats2 = result.treeStats.get(tree2); + + t.truthy(stats1, "Should have stats for tree1"); + t.truthy(stats2, "Should have stats for tree2"); + + // Tree1: 1 update to a.js, 1 add for c.js + t.is(stats1.updated.length, 1, "Tree1 should have 1 update (a.js)"); + t.true(stats1.updated.includes("a.js"), "Tree1 should have a.js in updated"); + t.is(stats1.added.length, 1, "Tree1 should have 1 addition (c.js)"); + t.true(stats1.added.includes("c.js"), "Tree1 should have c.js in added"); + t.is(stats1.unchanged.length, 0, "Tree1 should have 0 unchanged"); + t.is(stats1.removed.length, 0, "Tree1 should have 0 removals"); + + // Tree2: 1 add for c.js, 1 add for a.js (didn't exist in tree2) + t.is(stats2.updated.length, 0, "Tree2 should have 0 updates"); + t.is(stats2.added.length, 2, "Tree2 should have 2 additions (a.js, c.js)"); + t.true(stats2.added.includes("a.js"), "Tree2 should have a.js in added"); + t.true(stats2.added.includes("c.js"), "Tree2 should have c.js in added"); + t.is(stats2.unchanged.length, 0, "Tree2 should have 0 unchanged"); + t.is(stats2.removed.length, 0, "Tree2 should have 0 removals"); +}); + +test("TreeRegistry - per-tree statistics with shared nodes", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "shared/a.js", integrity: "hash-a"}, + {path: "shared/b.js", integrity: "hash-b"} + ], registry); + const tree2 = tree1.deriveTree([{path: "unique/c.js", integrity: "hash-c"}]); + + // Verify trees share the "shared" directory + const sharedDir1 = tree1.root.children.get("shared"); + const sharedDir2 = tree2.root.children.get("shared"); + t.is(sharedDir1, sharedDir2, "Should share the same 'shared' directory node"); + + // Update shared resource + registry.scheduleUpsert(createMockResource("shared/a.js", "new-hash-a", Date.now(), 1024, 1)); + + const result = await registry.flush(); + + // Verify global results + t.deepEqual(result.updated, ["shared/a.js"], "Should report shared/a.js as updated"); + + // Verify per-tree statistics + const stats1 = result.treeStats.get(tree1); + const stats2 = result.treeStats.get(tree2); + + // Both trees should count the update since they share the node + t.is(stats1.updated.length, 1, "Tree1 should count the shared update"); + t.true(stats1.updated.includes("shared/a.js"), "Tree1 should have shared/a.js in updated"); + t.is(stats2.updated.length, 1, "Tree2 should count the shared update"); + t.true(stats2.updated.includes("shared/a.js"), "Tree2 should have shared/a.js in updated"); + t.is(stats1.added.length, 0, "Tree1 should have 0 additions"); + t.is(stats2.added.length, 0, "Tree2 should have 0 additions"); + t.is(stats1.unchanged.length, 0, "Tree1 should have 0 unchanged"); + t.is(stats2.unchanged.length, 0, "Tree2 should have 0 unchanged"); + t.is(stats1.removed.length, 0, "Tree1 should have 0 removals"); + t.is(stats2.removed.length, 0, "Tree2 should have 0 removals"); +}); + +test("TreeRegistry - per-tree statistics with mixed operations", async (t) => { + const registry = new TreeRegistry(); + const tree1 = new SharedHashTree([ + {path: "a.js", integrity: "hash-a"}, + {path: "b.js", integrity: "hash-b"}, + {path: "c.js", integrity: "hash-c"} + ], registry); + const tree2 = tree1.deriveTree([{path: "d.js", integrity: "hash-d"}]); + + // Update a.js (affects both trees - shared) + registry.scheduleUpsert(createMockResource("a.js", "new-hash-a", Date.now(), 1024, 1)); + // Remove b.js (affects both trees - shared) + registry.scheduleRemoval("b.js"); + // Add e.js (affects both trees) + registry.scheduleUpsert(createMockResource("e.js", "hash-e", Date.now(), 2048, 5)); + // Update d.js (exists in tree2, will be added to tree1) + registry.scheduleUpsert(createMockResource("d.js", "new-hash-d", Date.now(), 1024, 4)); + + const result = await registry.flush(); + + // Verify per-tree statistics + const stats1 = result.treeStats.get(tree1); + const stats2 = result.treeStats.get(tree2); + + // Tree1: 1 update (a.js), 2 additions (e.js, d.js), 1 removal (b.js) + t.is(stats1.updated.length, 1, "Tree1 should have 1 update (a.js)"); + t.true(stats1.updated.includes("a.js"), "Tree1 should have a.js in updated"); + t.is(stats1.added.length, 2, "Tree1 should have 2 additions (e.js, d.js)"); + t.true(stats1.added.includes("e.js"), "Tree1 should have e.js in added"); + t.true(stats1.added.includes("d.js"), "Tree1 should have d.js in added"); + t.is(stats1.unchanged.length, 0, "Tree1 should have 0 unchanged"); + t.is(stats1.removed.length, 1, "Tree1 should have 1 removal (b.js)"); + t.true(stats1.removed.includes("b.js"), "Tree1 should have b.js in removed"); + + // Tree2: 2 updates (a.js shared, d.js), 1 addition (e.js), 1 removal (b.js shared) + t.is(stats2.updated.length, 2, "Tree2 should have 2 updates (a.js, d.js)"); + t.true(stats2.updated.includes("a.js"), "Tree2 should have a.js in updated"); + t.true(stats2.updated.includes("d.js"), "Tree2 should have d.js in updated"); + t.is(stats2.added.length, 1, "Tree2 should have 1 addition (e.js)"); + t.true(stats2.added.includes("e.js"), "Tree2 should have e.js in added"); + t.is(stats2.unchanged.length, 0, "Tree2 should have 0 unchanged"); + t.is(stats2.removed.length, 1, "Tree2 should have 1 removal (b.js)"); + t.true(stats2.removed.includes("b.js"), "Tree2 should have b.js in removed"); +}); + +test("TreeRegistry - per-tree statistics with no changes", async (t) => { + const registry = new TreeRegistry(); + const timestamp = Date.now(); + const tree1 = new SharedHashTree([{ + path: "a.js", + integrity: "hash-a", + lastModified: timestamp, + size: 1024, + inode: 100 + }], registry); + const tree2 = new SharedHashTree([{ + path: "b.js", + integrity: "hash-b", + lastModified: timestamp, + size: 2048, + inode: 200 + }], registry); + + // Schedule updates with unchanged metadata + // Note: These will add missing resources to the other tree + registry.scheduleUpsert(createMockResource("a.js", "hash-a", timestamp, 1024, 100)); + registry.scheduleUpsert(createMockResource("b.js", "hash-b", timestamp, 2048, 200)); + + const result = await registry.flush(); + + // a.js is unchanged in tree1 but added to tree2 + // b.js is unchanged in tree2 but added to tree1 + t.deepEqual(result.updated, [], "Should have no updates"); + t.true(result.added.includes("a.js"), "a.js should be added to tree2"); + t.true(result.added.includes("b.js"), "b.js should be added to tree1"); + t.true(result.unchanged.includes("a.js"), "a.js should be unchanged in tree1"); + t.true(result.unchanged.includes("b.js"), "b.js should be unchanged in tree2"); + + // Verify per-tree statistics + const stats1 = result.treeStats.get(tree1); + const stats2 = result.treeStats.get(tree2); + + // Tree1: a.js unchanged, b.js added + t.is(stats1.updated.length, 0, "Tree1 should have 0 updates"); + t.is(stats1.added.length, 1, "Tree1 should have 1 addition (b.js)"); + t.true(stats1.added.includes("b.js"), "Tree1 should have b.js in added"); + t.is(stats1.unchanged.length, 1, "Tree1 should have 1 unchanged (a.js)"); + t.true(stats1.unchanged.includes("a.js"), "Tree1 should have a.js in unchanged"); + t.is(stats1.removed.length, 0, "Tree1 should have 0 removals"); + + // Tree2: b.js unchanged, a.js added + t.is(stats2.updated.length, 0, "Tree2 should have 0 updates"); + t.is(stats2.added.length, 1, "Tree2 should have 1 addition (a.js)"); + t.true(stats2.added.includes("a.js"), "Tree2 should have a.js in added"); + t.is(stats2.unchanged.length, 1, "Tree2 should have 1 unchanged (b.js)"); + t.true(stats2.unchanged.includes("b.js"), "Tree2 should have b.js in unchanged"); + t.is(stats2.removed.length, 0, "Tree2 should have 0 removals"); +}); + +test("TreeRegistry - empty flush returns empty treeStats", async (t) => { + const registry = new TreeRegistry(); + new SharedHashTree([{path: "a.js", integrity: "hash-a"}], registry); + new SharedHashTree([{path: "b.js", integrity: "hash-b"}], registry); + + // Flush without scheduling any operations + const result = await registry.flush(); + + t.truthy(result.treeStats, "Should have treeStats"); + t.is(result.treeStats.size, 0, "Should have empty treeStats when no operations"); + t.deepEqual(result.added, [], "Should have no additions"); + t.deepEqual(result.updated, [], "Should have no updates"); + t.deepEqual(result.removed, [], "Should have no removals"); +}); + +test("TreeRegistry - derived tree reflects base tree resource changes in statistics", async (t) => { + const registry = new TreeRegistry(); + + // Create base tree with some resources + const baseTree = new SharedHashTree([ + {path: "shared/resource1.js", integrity: "hash1"}, + {path: "shared/resource2.js", integrity: "hash2"} + ], registry); + + // Derive a new tree from base tree (shares same registry) + // Note: deriveTree doesn't schedule the new resources, it adds them directly to the derived tree + const derivedTree = baseTree.deriveTree([ + {path: "derived/resource3.js", integrity: "hash3"} + ]); + + // Verify both trees are registered + t.is(registry.getTreeCount(), 2, "Registry should have both trees"); + + // Verify they share the same nodes + const sharedDir1 = baseTree.root.children.get("shared"); + const sharedDir2 = derivedTree.root.children.get("shared"); + t.is(sharedDir1, sharedDir2, "Both trees should share the 'shared' directory node"); + + // Update a resource that exists in base tree (and is shared with derived tree) + registry.scheduleUpsert(createMockResource("shared/resource1.js", "new-hash1", Date.now(), 2048, 100)); + + // Add a new resource to the shared path + registry.scheduleUpsert(createMockResource("shared/resource4.js", "hash4", Date.now(), 1024, 200)); + + // Remove a shared resource + registry.scheduleRemoval("shared/resource2.js"); + + const result = await registry.flush(); + + // Verify global results + t.deepEqual(result.updated, ["shared/resource1.js"], "Should report resource1 as updated"); + t.true(result.added.includes("shared/resource4.js"), "Should report resource4 as added"); + t.deepEqual(result.removed, ["shared/resource2.js"], "Should report resource2 as removed"); + + // Verify per-tree statistics + const baseStats = result.treeStats.get(baseTree); + const derivedStats = result.treeStats.get(derivedTree); + + // Base tree statistics + // Base tree will also get derived/resource3.js added via registry (since it processes all trees) + t.is(baseStats.updated.length, 1, "Base tree should have 1 update"); + t.true(baseStats.updated.includes("shared/resource1.js"), "Base tree should have resource1 in updated"); + // baseStats.added should include both resource4 and resource3 + t.true(baseStats.added.includes("shared/resource4.js"), "Base tree should have resource4 in added"); + t.is(baseStats.removed.length, 1, "Base tree should have 1 removal"); + t.true(baseStats.removed.includes("shared/resource2.js"), "Base tree should have resource2 in removed"); + + // Derived tree statistics - CRITICAL: should reflect the same changes for shared resources + // Note: resource4 shows as "updated" because it's added to an already-existing shared node that was modified + t.is(derivedStats.updated.length, 2, + "Derived tree should have 2 updates (resource1 changed, resource4 added to shared dir)"); + t.true(derivedStats.updated.includes("shared/resource1.js"), "Derived tree should have resource1 in updated"); + t.true(derivedStats.updated.includes("shared/resource4.js"), "Derived tree should have resource4 in updated"); + t.is(derivedStats.added.length, 0, "Derived tree should have 0 additions tracked separately"); + t.is(derivedStats.removed.length, 1, "Derived tree should have 1 removal (shared resource2)"); + t.true(derivedStats.removed.includes("shared/resource2.js"), "Derived tree should have resource2 in removed"); + + // Verify the actual tree state + t.is(baseTree.getResourceByPath("shared/resource1.js").integrity, "new-hash1", + "Base tree should have updated integrity"); + t.is(derivedTree.getResourceByPath("shared/resource1.js").integrity, "new-hash1", + "Derived tree should have updated integrity (shared node)"); + t.truthy(baseTree.hasPath("shared/resource4.js"), "Base tree should have new resource"); + t.truthy(derivedTree.hasPath("shared/resource4.js"), "Derived tree should have new resource (shared)"); + t.false(baseTree.hasPath("shared/resource2.js"), "Base tree should not have removed resource"); + t.false(derivedTree.hasPath("shared/resource2.js"), "Derived tree should not have removed resource (shared)"); +}); diff --git a/packages/project/test/lib/build/definitions/application.js b/packages/project/test/lib/build/definitions/application.js index 742d398e988..cc6fb8eabee 100644 --- a/packages/project/test/lib/build/definitions/application.js +++ b/packages/project/test/lib/build/definitions/application.js @@ -57,12 +57,14 @@ test("Standard build", (t) => { replaceCopyright: { options: { copyright: "copyright", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, minify: { options: { @@ -70,7 +72,8 @@ test("Standard build", (t) => { "/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, enhanceManifest: {}, generateFlexChangesBundle: {}, @@ -137,12 +140,14 @@ test("Standard build with legacy spec version", (t) => { replaceCopyright: { options: { copyright: "copyright", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, minify: { options: { @@ -150,7 +155,8 @@ test("Standard build with legacy spec version", (t) => { "/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, enhanceManifest: {}, generateFlexChangesBundle: {}, @@ -250,12 +256,14 @@ test("Custom bundles", async (t) => { replaceCopyright: { options: { copyright: "copyright", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, minify: { options: { @@ -263,7 +271,8 @@ test("Custom bundles", async (t) => { "/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, enhanceManifest: {}, generateFlexChangesBundle: {}, @@ -396,7 +405,8 @@ test("Minification excludes", (t) => { "!**/*.support.js", "!/resources/**.html", ] - } + }, + supportsDifferentialBuilds: true, }, "Correct minify task definition"); }); @@ -421,7 +431,8 @@ test("Minification excludes not applied for legacy specVersion", (t) => { "/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, "Correct minify task definition"); }); diff --git a/packages/project/test/lib/build/definitions/component.js b/packages/project/test/lib/build/definitions/component.js index abb281a86ed..1e773369234 100644 --- a/packages/project/test/lib/build/definitions/component.js +++ b/packages/project/test/lib/build/definitions/component.js @@ -56,12 +56,14 @@ test("Standard build", (t) => { replaceCopyright: { options: { copyright: "copyright", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, minify: { options: { @@ -69,7 +71,8 @@ test("Standard build", (t) => { "/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, enhanceManifest: {}, generateFlexChangesBundle: {}, @@ -162,12 +165,14 @@ test("Custom bundles", async (t) => { replaceCopyright: { options: { copyright: "copyright", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json}" - } + }, + supportsDifferentialBuilds: true, }, minify: { options: { @@ -175,7 +180,8 @@ test("Custom bundles", async (t) => { "/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, enhanceManifest: {}, generateFlexChangesBundle: {}, @@ -301,7 +307,8 @@ test("Minification excludes", (t) => { "!**/*.support.js", "!/resources/**.html", ] - } + }, + supportsDifferentialBuilds: true, }, "Correct minify task definition"); }); diff --git a/packages/project/test/lib/build/definitions/library.js b/packages/project/test/lib/build/definitions/library.js index 121e8951442..3914be256da 100644 --- a/packages/project/test/lib/build/definitions/library.js +++ b/packages/project/test/lib/build/definitions/library.js @@ -68,18 +68,21 @@ test("Standard build", async (t) => { options: { copyright: "copyright", pattern: "/**/*.{js,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceBuildtime: { options: { pattern: "/resources/sap/ui/{Global,core/Core}.js" - } + }, + supportsDifferentialBuilds: true, }, generateJsdoc: { requiresDependencies: true, @@ -97,7 +100,8 @@ test("Standard build", async (t) => { "/resources/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, generateLibraryManifest: {}, enhanceManifest: {}, @@ -205,18 +209,21 @@ test("Standard build with legacy spec version", (t) => { options: { copyright: "copyright", pattern: "/**/*.{js,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceBuildtime: { options: { pattern: "/resources/sap/ui/{Global,core/Core}.js" - } + }, + supportsDifferentialBuilds: true, }, generateJsdoc: { requiresDependencies: true, @@ -234,7 +241,8 @@ test("Standard build with legacy spec version", (t) => { "/resources/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, generateLibraryManifest: {}, enhanceManifest: {}, @@ -331,18 +339,21 @@ test("Custom bundles", async (t) => { options: { copyright: "copyright", pattern: "/**/*.{js,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceBuildtime: { options: { pattern: "/resources/sap/ui/{Global,core/Core}.js" - } + }, + supportsDifferentialBuilds: true, }, generateJsdoc: { requiresDependencies: true, @@ -360,7 +371,8 @@ test("Custom bundles", async (t) => { "/resources/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, generateLibraryManifest: {}, enhanceManifest: {}, @@ -489,7 +501,8 @@ test("Minification excludes", (t) => { "!**/*.support.js", "!/resources/**.html", ] - } + }, + supportsDifferentialBuilds: true, }, "Correct minify task definition"); }); @@ -514,7 +527,8 @@ test("Minification excludes not applied for legacy specVersion", (t) => { "/resources/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, "Correct minify task definition"); }); @@ -675,18 +689,21 @@ test("Standard build: nulled taskFunction to skip tasks", (t) => { options: { copyright: "copyright", pattern: "/**/*.{js,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/**/*.{js,json,library,css,less,theme,html}" - } + }, + supportsDifferentialBuilds: true, }, replaceBuildtime: { options: { pattern: "/resources/sap/ui/{Global,core/Core}.js" - } + }, + supportsDifferentialBuilds: true, }, generateJsdoc: { requiresDependencies: true, @@ -704,7 +721,8 @@ test("Standard build: nulled taskFunction to skip tasks", (t) => { "/resources/**/*.js", "!**/*.support.js", ] - } + }, + supportsDifferentialBuilds: true, }, generateLibraryManifest: {}, enhanceManifest: {}, diff --git a/packages/project/test/lib/build/definitions/themeLibrary.js b/packages/project/test/lib/build/definitions/themeLibrary.js index 2da2457b538..9d35d11a174 100644 --- a/packages/project/test/lib/build/definitions/themeLibrary.js +++ b/packages/project/test/lib/build/definitions/themeLibrary.js @@ -53,13 +53,15 @@ test("Standard build", (t) => { options: { copyright: "copyright", pattern: "/resources/**/*.{less,theme}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/resources/**/*.{less,theme}" - } + }, + supportsDifferentialBuilds: true, }, buildThemes: { requiresDependencies: true, @@ -114,13 +116,15 @@ test("Standard build for non root project", (t) => { options: { copyright: "copyright", pattern: "/resources/**/*.{less,theme}" - } + }, + supportsDifferentialBuilds: true, }, replaceVersion: { options: { version: "version", pattern: "/resources/**/*.{less,theme}" - } + }, + supportsDifferentialBuilds: true, }, buildThemes: { requiresDependencies: true, diff --git a/packages/project/test/lib/build/helpers/BuildContext.js b/packages/project/test/lib/build/helpers/BuildContext.js index cc09a7cd870..7cfbfea5ed8 100644 --- a/packages/project/test/lib/build/helpers/BuildContext.js +++ b/packages/project/test/lib/build/helpers/BuildContext.js @@ -1,14 +1,29 @@ import test from "ava"; import sinon from "sinon"; +import esmock from "esmock"; import OutputStyleEnum from "../../../../lib/build/helpers/ProjectBuilderOutputStyle.js"; +test.beforeEach(async (t) => { + t.context.ProjectBuildContextCreateStub = sinon.stub().callsFake(async () => { + return {}; // Explicitly returning empty object to show uniqueness + }); + t.context.CacheManagerCreate = sinon.stub().returns({}); + t.context.BuildContext = await esmock("../../../../lib/build/helpers/BuildContext.js", { + "../../../../lib/build/helpers/ProjectBuildContext.js": { + create: t.context.ProjectBuildContextCreateStub + }, + "../../../../lib/build/cache/CacheManager.js": { + create: t.context.CacheManagerCreate + } + }); +}); + test.afterEach.always((t) => { sinon.restore(); }); -import BuildContext from "../../../../lib/build/helpers/BuildContext.js"; - test("Missing parameters", (t) => { + const {BuildContext} = t.context; const error1 = t.throws(() => { new BuildContext(); }); @@ -23,6 +38,8 @@ test("Missing parameters", (t) => { }); test("getRootProject", (t) => { + const {BuildContext} = t.context; + const rootProjectStub = sinon.stub() .onFirstCall().returns({getType: () => "library"}) .returns("pony"); @@ -33,6 +50,8 @@ test("getRootProject", (t) => { }); test("getGraph", (t) => { + const {BuildContext} = t.context; + const graph = { getRoot: () => ({getType: () => "library"}), }; @@ -42,6 +61,8 @@ test("getGraph", (t) => { }); test("getTaskRepository", (t) => { + const {BuildContext} = t.context; + const graph = { getRoot: () => ({getType: () => "library"}), }; @@ -51,6 +72,8 @@ test("getTaskRepository", (t) => { }); test("getBuildConfig: Default values", (t) => { + const {BuildContext} = t.context; + const graph = { getRoot: () => ({getType: () => "library"}), }; @@ -68,6 +91,8 @@ test("getBuildConfig: Default values", (t) => { }); test("getBuildConfig: Custom values", (t) => { + const {BuildContext} = t.context; + const buildContext = new BuildContext({ getRoot: () => { return { @@ -96,6 +121,8 @@ test("getBuildConfig: Custom values", (t) => { }); test("createBuildManifest not supported for type application", (t) => { + const {BuildContext} = t.context; + const err = t.throws(() => { new BuildContext({ getRoot: () => { @@ -113,6 +140,8 @@ test("createBuildManifest not supported for type application", (t) => { }); test("createBuildManifest not supported for type module", (t) => { + const {BuildContext} = t.context; + const err = t.throws(() => { new BuildContext({ getRoot: () => { @@ -130,6 +159,8 @@ test("createBuildManifest not supported for type module", (t) => { }); test("createBuildManifest not supported for self-contained build", (t) => { + const {BuildContext} = t.context; + const err = t.throws(() => { new BuildContext({ getRoot: () => { @@ -148,6 +179,8 @@ test("createBuildManifest not supported for self-contained build", (t) => { }); test("createBuildManifest supported for css-variables build", (t) => { + const {BuildContext} = t.context; + t.notThrows(() => { new BuildContext({ getRoot: () => { @@ -163,6 +196,8 @@ test("createBuildManifest supported for css-variables build", (t) => { }); test("createBuildManifest supported for jsdoc build", (t) => { + const {BuildContext} = t.context; + t.notThrows(() => { new BuildContext({ getRoot: () => { @@ -178,6 +213,8 @@ test("createBuildManifest supported for jsdoc build", (t) => { }); test("outputStyle='Namespace' supported for type application", (t) => { + const {BuildContext} = t.context; + t.notThrows(() => { new BuildContext({ getRoot: () => { @@ -192,6 +229,8 @@ test("outputStyle='Namespace' supported for type application", (t) => { }); test("outputStyle='Flat' not supported for type theme-library", (t) => { + const {BuildContext} = t.context; + const err = t.throws(() => { new BuildContext({ getRoot: () => { @@ -210,6 +249,8 @@ test("outputStyle='Flat' not supported for type theme-library", (t) => { }); test("outputStyle='Flat' not supported for type module", (t) => { + const {BuildContext} = t.context; + const err = t.throws(() => { new BuildContext({ getRoot: () => { @@ -228,6 +269,8 @@ test("outputStyle='Flat' not supported for type module", (t) => { }); test("outputStyle='Flat' not supported for createBuildManifest build", (t) => { + const {BuildContext} = t.context; + const err = t.throws(() => { new BuildContext({ getRoot: () => { @@ -246,6 +289,8 @@ test("outputStyle='Flat' not supported for createBuildManifest build", (t) => { }); test("getOption", (t) => { + const {BuildContext} = t.context; + const graph = { getRoot: () => ({getType: () => "library"}), }; @@ -260,23 +305,25 @@ test("getOption", (t) => { "(not exposed as build option)"); }); -test("createProjectContext", async (t) => { - const graph = { - getRoot: () => ({getType: () => "library"}), - }; +test("getProjectContext", async (t) => { + const {BuildContext} = t.context; + + const rootProjectStub = sinon.stub() + .returns({getType: () => "library", getRootPath: () => ""}); + const graph = {getRoot: rootProjectStub, getProject: () => "project"}; + const buildContext = new BuildContext(graph, "taskRepository"); - const projectBuildContext = await buildContext.createProjectContext({ - project: { - getName: () => "project", - getType: () => "type", - }, - }); + const projectBuildContext = await buildContext.getProjectContext("project"); + t.is(t.context.ProjectBuildContextCreateStub.callCount, 1); - t.deepEqual(buildContext._projectBuildContexts, [projectBuildContext], - "Project build context has been added to internal array"); + const projectBuildContext2 = await buildContext.getProjectContext("project"); + t.is(t.context.ProjectBuildContextCreateStub.callCount, 1); + t.is(projectBuildContext, projectBuildContext2); }); test("executeCleanupTasks", async (t) => { + const {BuildContext} = t.context; + const graph = { getRoot: () => ({getType: () => "library"}), }; @@ -284,12 +331,8 @@ test("executeCleanupTasks", async (t) => { const executeCleanupTasks = sinon.stub().resolves(); - buildContext._projectBuildContexts.push({ - executeCleanupTasks - }); - buildContext._projectBuildContexts.push({ - executeCleanupTasks - }); + buildContext._projectBuildContexts.set("project", {executeCleanupTasks}); + buildContext._projectBuildContexts.set("project2", {executeCleanupTasks}); await buildContext.executeCleanupTasks(); diff --git a/packages/project/test/lib/build/helpers/ProjectBuildContext.js b/packages/project/test/lib/build/helpers/ProjectBuildContext.js index 03f9a568325..16310e07119 100644 --- a/packages/project/test/lib/build/helpers/ProjectBuildContext.js +++ b/packages/project/test/lib/build/helpers/ProjectBuildContext.js @@ -16,20 +16,22 @@ import ProjectBuildContext from "../../../../lib/build/helpers/ProjectBuildConte test("Missing parameters", (t) => { t.throws(() => { - new ProjectBuildContext({ - project: { + new ProjectBuildContext( + undefined, + { getName: () => "project", getType: () => "type", - }, - }); + } + ); }, { message: `Missing parameter 'buildContext'` }, "Correct error message"); t.throws(() => { - new ProjectBuildContext({ - buildContext: "buildContext", - }); + new ProjectBuildContext( + "buildContext", + undefined + ); }, { message: `Missing parameter 'project'` }, "Correct error message"); @@ -40,54 +42,61 @@ test("isRootProject: true", (t) => { getName: () => "root project", getType: () => "type", }; - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getRootProject: () => rootProject - }, - project: rootProject - }); + const buildContext = { + getRootProject: () => rootProject + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + rootProject + ); t.true(projectBuildContext.isRootProject(), "Correctly identified root project"); }); test("isRootProject: false", (t) => { - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getRootProject: () => "root project" - }, - project: { - getName: () => "not the root project", - getType: () => "type", - } - }); + const buildContext = { + getRootProject: () => "root project" + }; + const project = { + getName: () => "not the root project", + getType: () => "type", + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project + ); t.false(projectBuildContext.isRootProject(), "Correctly identified non-root project"); }); test("getBuildOption", (t) => { const getOptionStub = sinon.stub().returns("pony"); - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getOption: getOptionStub - }, - project: { - getName: () => "project", - getType: () => "type", - } - }); + const buildContext = { + getOption: getOptionStub + }; + const project = { + getName: () => "project", + getType: () => "type", + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project + ); t.is(projectBuildContext.getOption("option"), "pony", "Returned value is correct"); t.is(getOptionStub.getCall(0).args[0], "option", "getOption called with correct argument"); }); test("registerCleanupTask", (t) => { - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, - project: { - getName: () => "project", - getType: () => "type", - } - }); + const buildContext = {}; + const project = { + getName: () => "project", + getType: () => "type", + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project + ); projectBuildContext.registerCleanupTask("my task 1"); projectBuildContext.registerCleanupTask("my task 2"); @@ -96,13 +105,15 @@ test("registerCleanupTask", (t) => { }); test("executeCleanupTasks", (t) => { - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, - project: { - getName: () => "project", - getType: () => "type", - } - }); + const buildContext = {}; + const project = { + getName: () => "project", + getType: () => "type", + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project + ); const task1 = sinon.stub().resolves(); const task2 = sinon.stub().resolves(); projectBuildContext.registerCleanupTask(task1); @@ -115,65 +126,35 @@ test("executeCleanupTasks", (t) => { }); test.serial("getResourceTagCollection", async (t) => { - const projectAcceptsTagStub = sinon.stub().returns(false); - projectAcceptsTagStub.withArgs("project-tag").returns(true); - const projectContextAcceptsTagStub = sinon.stub().returns(false); - projectContextAcceptsTagStub.withArgs("project-context-tag").returns(true); - - class DummyResourceTagCollection { - constructor({allowedTags, allowedNamespaces}) { - t.deepEqual(allowedTags, [ - "ui5:OmitFromBuildResult", - "ui5:IsBundle" - ], - "Correct allowedTags parameter supplied"); - - t.deepEqual(allowedNamespaces, [ - "build" - ], - "Correct allowedNamespaces parameter supplied"); - } - acceptsTag(tag) { - // Redirect to stub - return projectContextAcceptsTagStub(tag); - } - } - - const ProjectBuildContext = await esmock("../../../../lib/build/helpers/ProjectBuildContext.js", { - "@ui5/fs/internal/ResourceTagCollection": DummyResourceTagCollection - }); - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, - project: { - getName: () => "project", - getType: () => "type", - } - }); + const ProjectBuildContext = (await import("../../../../lib/build/helpers/ProjectBuildContext.js")).default; + const buildContext = {}; + const project = { + getName: () => "project", + getType: () => "type", + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project + ); - const fakeProjectCollection = { - acceptsTag: projectAcceptsTagStub + const fakeCollection = { + acceptsTag: sinon.stub().returns(true) }; + const getResourceTagCollectionStub = sinon.stub().returns(fakeCollection); const fakeResource = { getProject: () => { return { - getResourceTagCollection: () => fakeProjectCollection + getResourceTagCollection: getResourceTagCollectionStub }; }, getPath: () => "/resource/path", hasProject: () => true }; - const collection1 = projectBuildContext.getResourceTagCollection(fakeResource, "project-tag"); - t.is(collection1, fakeProjectCollection, "Returned tag collection of resource project"); - - const collection2 = projectBuildContext.getResourceTagCollection(fakeResource, "project-context-tag"); - t.true(collection2 instanceof DummyResourceTagCollection, - "Returned tag collection of project build context"); - - t.throws(() => { - projectBuildContext.getResourceTagCollection(fakeResource, "not-accepted-tag"); - }, { - message: `Could not find collection for resource /resource/path and tag not-accepted-tag` - }); + const collection = projectBuildContext.getResourceTagCollection(fakeResource, "some-tag"); + t.is(collection, fakeCollection, "Returned tag collection from resource's project"); + t.is(getResourceTagCollectionStub.callCount, 1, "getResourceTagCollection called once"); + t.is(getResourceTagCollectionStub.firstCall.args[0], fakeResource, "Called with resource"); + t.is(getResourceTagCollectionStub.firstCall.args[1], "some-tag", "Called with tag"); }); test("getResourceTagCollection: Assigns project to resource if necessary", (t) => { @@ -181,13 +162,11 @@ test("getResourceTagCollection: Assigns project to resource if necessary", (t) = getName: () => "project", getType: () => "type", }; - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, - project: fakeProject, - log: { - silly: () => {} - } - }); + const buildContext = {}; + const projectBuildContext = new ProjectBuildContext( + buildContext, + fakeProject + ); const setProjectStub = sinon.stub(); const fakeResource = { @@ -215,16 +194,17 @@ test("getProject", (t) => { getType: () => "type", }; const getProjectStub = sinon.stub().returns("pony"); - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getGraph: () => { - return { - getProject: getProjectStub - }; - } - }, + const buildContext = { + getGraph: () => { + return { + getProject: getProjectStub + }; + } + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, project - }); + ); t.is(projectBuildContext.getProject("pony project"), "pony", "Returned correct value"); t.is(getProjectStub.callCount, 1, "ProjectGraph#getProject got called once"); @@ -240,16 +220,17 @@ test("getProject: No name provided", (t) => { getType: () => "type", }; const getProjectStub = sinon.stub().returns("pony"); - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getGraph: () => { - return { - getProject: getProjectStub - }; - } - }, + const buildContext = { + getGraph: () => { + return { + getProject: getProjectStub + }; + } + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, project - }); + ); t.is(projectBuildContext.getProject(), project, "Returned correct value"); t.is(getProjectStub.callCount, 0, "ProjectGraph#getProject has not been called"); @@ -261,16 +242,17 @@ test("getDependencies", (t) => { getType: () => "type", }; const getDependenciesStub = sinon.stub().returns(["dep a", "dep b"]); - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getGraph: () => { - return { - getDependencies: getDependenciesStub - }; - } - }, + const buildContext = { + getGraph: () => { + return { + getDependencies: getDependenciesStub + }; + } + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, project - }); + ); t.deepEqual(projectBuildContext.getDependencies("pony project"), ["dep a", "dep b"], "Returned correct value"); t.is(getDependenciesStub.callCount, 1, "ProjectGraph#getDependencies got called once"); @@ -284,16 +266,17 @@ test("getDependencies: No name provided", (t) => { getType: () => "type", }; const getDependenciesStub = sinon.stub().returns(["dep a", "dep b"]); - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getGraph: () => { - return { - getDependencies: getDependenciesStub - }; - } - }, + const buildContext = { + getGraph: () => { + return { + getDependencies: getDependenciesStub + }; + } + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, project - }); + ); t.deepEqual(projectBuildContext.getDependencies(), ["dep a", "dep b"], "Returned correct value"); t.is(getDependenciesStub.callCount, 1, "ProjectGraph#getDependencies got called once"); @@ -302,20 +285,22 @@ test("getDependencies: No name provided", (t) => { }); test("getTaskUtil", (t) => { - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, - project: { - getName: () => "project", - getType: () => "type", - } - }); + const buildContext = {}; + const project = { + getName: () => "project", + getType: () => "type", + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project + ); t.truthy(projectBuildContext.getTaskUtil(), "Returned a TaskUtil instance"); t.is(projectBuildContext.getTaskUtil(), projectBuildContext.getTaskUtil(), "Caches TaskUtil instance"); }); test.serial("getTaskRunner", async (t) => { - t.plan(3); + t.plan(4); const project = { getName: () => "project", getType: () => "type", @@ -325,10 +310,14 @@ test.serial("getTaskRunner", async (t) => { constructor(params) { t.true(params.log instanceof ProjectBuildLogger, "TaskRunner receives an instance of ProjectBuildLogger"); params.log = "log"; // replace log instance with string for deep comparison + t.is(params.buildCache, buildCache, + "TaskRunner receives the ProjectBuildCache instance"); + params.buildCache = "buildCache"; // replace buildCache instance with string for deep comparison t.deepEqual(params, { graph: "graph", project: project, log: "log", + buildCache: "buildCache", taskUtil: "taskUtil", taskRepository: "taskRepository", buildConfig: "buildConfig" @@ -339,14 +328,18 @@ test.serial("getTaskRunner", async (t) => { "../../../../lib/build/TaskRunner.js": TaskRunnerMock }); - const projectBuildContext = new ProjectBuildContext({ - buildContext: { - getGraph: () => "graph", - getTaskRepository: () => "taskRepository", - getBuildConfig: () => "buildConfig", - }, - project - }); + const buildContext = { + getGraph: () => "graph", + getTaskRepository: () => "taskRepository", + getBuildConfig: () => "buildConfig", + }; + const buildCache = {}; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project, + undefined, + buildCache, + ); projectBuildContext.getTaskUtil = () => "taskUtil"; @@ -354,76 +347,42 @@ test.serial("getTaskRunner", async (t) => { t.is(projectBuildContext.getTaskRunner(), taskRunner, "Returns cached TaskRunner instance"); }); - -test.serial("createProjectContext", async (t) => { - t.plan(4); - - const project = { - getName: sinon.stub().returns("foo"), - getType: sinon.stub().returns("bar"), - }; - const taskRunner = {"task": "runner"}; - class ProjectContextMock { - constructor({buildContext, project}) { - t.is(buildContext, testBuildContext, "Correct buildContext parameter"); - t.is(project, project, "Correct project parameter"); - } - getTaskUtil() { - return "taskUtil"; - } - setTaskRunner(_taskRunner) { - t.is(_taskRunner, taskRunner); - } - } - const BuildContext = await esmock("../../../../lib/build/helpers/BuildContext.js", { - "../../../../lib/build/helpers/ProjectBuildContext.js": ProjectContextMock, - "../../../../lib/build/TaskRunner.js": { - create: sinon.stub().resolves(taskRunner) - } - }); - const graph = { - getRoot: () => ({getType: () => "library"}), - }; - const testBuildContext = new BuildContext(graph, "taskRepository"); - - const projectContext = await testBuildContext.createProjectContext({ - project - }); - - t.true(projectContext instanceof ProjectContextMock, - "Project context is an instance of ProjectContextMock"); - t.is(testBuildContext._projectBuildContexts[0], projectContext, - "BuildContext stored correct ProjectBuildContext"); -}); - -test("requiresBuild: has no build-manifest", (t) => { +test("possiblyRequiresBuild: has no build-manifest", (t) => { const project = { getName: sinon.stub().returns("foo"), getType: sinon.stub().returns("bar"), getBuildManifest: () => null }; - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, - project - }); - t.true(projectBuildContext.requiresBuild(), "Project without build-manifest requires to be build"); + const buildContext = {}; + const buildCache = { + isFresh: sinon.stub().returns(false) + }; + const projectBuildContext = new ProjectBuildContext( + buildContext, + project, + undefined, + buildCache + ); + t.true(projectBuildContext.possiblyRequiresBuild(), "Project without build-manifest requires to be build"); }); -test("requiresBuild: has build-manifest", (t) => { +test("possiblyRequiresBuild: has build-manifest", (t) => { const project = { getName: sinon.stub().returns("foo"), getType: sinon.stub().returns("bar"), getBuildManifest: () => { return { + manifestVersion: "0.1", timestamp: "2022-07-28T12:00:00.000Z" }; } }; - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, + const buildContext = {}; + const projectBuildContext = new ProjectBuildContext( + buildContext, project - }); - t.false(projectBuildContext.requiresBuild(), "Project with build-manifest does not require to be build"); + ); + t.false(projectBuildContext.possiblyRequiresBuild(), "Project with build-manifest does not require to be build"); }); test.serial("getBuildMetadata", (t) => { @@ -432,15 +391,17 @@ test.serial("getBuildMetadata", (t) => { getType: sinon.stub().returns("bar"), getBuildManifest: () => { return { + manifestVersion: "0.1", timestamp: "2022-07-28T12:00:00.000Z" }; } }; const getTimeStub = sinon.stub(Date.prototype, "getTime").callThrough().onFirstCall().returns(1659016800000); - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, + const buildContext = {}; + const projectBuildContext = new ProjectBuildContext( + buildContext, project - }); + ); t.deepEqual(projectBuildContext.getBuildMetadata(), { timestamp: "2022-07-28T12:00:00.000Z", @@ -455,9 +416,10 @@ test("getBuildMetadata: has no build-manifest", (t) => { getType: sinon.stub().returns("bar"), getBuildManifest: () => null }; - const projectBuildContext = new ProjectBuildContext({ - buildContext: {}, + const buildContext = {}; + const projectBuildContext = new ProjectBuildContext( + buildContext, project - }); + ); t.is(projectBuildContext.getBuildMetadata(), null, "Project has no build manifest"); }); diff --git a/packages/project/test/lib/build/helpers/composeProjectList.js b/packages/project/test/lib/build/helpers/composeProjectList.js index f8f58185f38..8556efcdfbb 100644 --- a/packages/project/test/lib/build/helpers/composeProjectList.js +++ b/packages/project/test/lib/build/helpers/composeProjectList.js @@ -224,9 +224,9 @@ test.serial("createDependencyLists: include all", async (t) => { excludeDependencyRegExp: [], excludeDependencyTree: [], expectedIncludedDependencies: [ - "library.d", "library.b", "library.c", - "library.d-depender", "library.a", "library.g", - "library.e", "library.f" + "library.d", "library.b", "library.a", + "library.e", "library.c", "library.f", + "library.d-depender", "library.g" ], expectedExcludedDependencies: [] }); @@ -239,7 +239,7 @@ test.serial("createDependencyLists: includeDependencyTree has lower priority tha excludeDependency: ["library.f"], excludeDependencyRegExp: ["^library\\.[acd]$"], expectedIncludedDependencies: ["library.b"], - expectedExcludedDependencies: ["library.f", "library.d", "library.c", "library.a"] + expectedExcludedDependencies: ["library.f", "library.d", "library.a", "library.c"] }); }); @@ -249,7 +249,7 @@ test.serial("createDependencyLists: excludeDependencyTree has lower priority tha includeDependency: ["library.f"], includeDependencyRegExp: ["^library\\.[acd]$"], excludeDependencyTree: ["library.f"], - expectedIncludedDependencies: ["library.f", "library.d", "library.c", "library.a"], + expectedIncludedDependencies: ["library.f", "library.d", "library.a", "library.c"], expectedExcludedDependencies: ["library.b"] }); }); @@ -261,8 +261,8 @@ test.serial("createDependencyLists: include all, exclude tree and include single includeDependencyRegExp: ["^library\\.[acd]$"], excludeDependencyTree: ["library.f"], expectedIncludedDependencies: [ - "library.f", "library.d", "library.c", "library.a", "library.d-depender", - "library.g", "library.e" + "library.f", "library.d", "library.a", "library.c", "library.e", + "library.d-depender", "library.g" ], expectedExcludedDependencies: ["library.b"] }); @@ -287,7 +287,7 @@ test.serial("createDependencyLists: defaultIncludeDependency/RegExp has lower pr excludeDependency: ["library.f"], excludeDependencyRegExp: ["^library\\.[acd](-depender)?$"], expectedIncludedDependencies: ["library.b"], - expectedExcludedDependencies: ["library.f", "library.d", "library.c", "library.d-depender", "library.a"] + expectedExcludedDependencies: ["library.f", "library.d", "library.a", "library.c", "library.d-depender"] }); }); test.serial("createDependencyLists: include all and defaultIncludeDependency/RegExp", async (t) => { @@ -297,8 +297,8 @@ test.serial("createDependencyLists: include all and defaultIncludeDependency/Reg defaultIncludeDependencyRegExp: ["^library\\.d$"], excludeDependency: ["library.f"], excludeDependencyRegExp: ["^library\\.[acd](-depender)?$"], - expectedIncludedDependencies: ["library.b", "library.g", "library.e"], - expectedExcludedDependencies: ["library.f", "library.d", "library.c", "library.d-depender", "library.a"] + expectedIncludedDependencies: ["library.b", "library.e", "library.g"], + expectedExcludedDependencies: ["library.f", "library.d", "library.a", "library.c", "library.d-depender"] }); }); diff --git a/packages/project/test/lib/build/helpers/createBuildManifest.integration.js b/packages/project/test/lib/build/helpers/createBuildManifest.integration.js index 015ca68bd1d..c6b49792b52 100644 --- a/packages/project/test/lib/build/helpers/createBuildManifest.integration.js +++ b/packages/project/test/lib/build/helpers/createBuildManifest.integration.js @@ -45,13 +45,13 @@ const buildConfig = { test("Create project from application project providing a build manifest", async (t) => { const inputProject = await Specification.create(applicationAConfig); - inputProject.getResourceTagCollection().setTag("/resources/id1/foo.js", "ui5:HasDebugVariant"); + inputProject.getProjectResourceTagCollection().setTag("/resources/id1/foo.js", "ui5:HasDebugVariant"); const taskRepository = { getVersions: async () => ({a: "a", b: "b"}) }; - const metadata = await createBuildManifest(inputProject, buildConfig, taskRepository); + const metadata = await createBuildManifest(inputProject, buildConfig, taskRepository, "yyy"); const m = new Module({ id: "build-descr-application.a.id", version: "2.0.0", @@ -63,7 +63,7 @@ test("Create project from application project providing a build manifest", async t.truthy(project, "Module was able to create project from build manifest metadata"); t.is(project.getName(), project.getName(), "Archive project has correct name"); t.is(project.getNamespace(), project.getNamespace(), "Archive project has correct namespace"); - t.is(project.getResourceTagCollection().getTag("/resources/id1/foo.js", "ui5:HasDebugVariant"), true, + t.is(project.getProjectResourceTagCollection().getTag("/resources/id1/foo.js", "ui5:HasDebugVariant"), true, "Archive project has correct tag"); t.is(project.getVersion(), "2.0.0", "Archive project has version from archive module"); @@ -77,13 +77,13 @@ test("Create project from application project providing a build manifest", async test("Create project from library project providing a build manifest", async (t) => { const inputProject = await Specification.create(libraryEConfig); - inputProject.getResourceTagCollection().setTag("/resources/library/e/file.js", "ui5:HasDebugVariant"); + inputProject.getProjectResourceTagCollection().setTag("/resources/library/e/file.js", "ui5:HasDebugVariant"); const taskRepository = { getVersions: async () => ({a: "a", b: "b"}) }; - const metadata = await createBuildManifest(inputProject, buildConfig, taskRepository); + const metadata = await createBuildManifest(inputProject, buildConfig, taskRepository, "zzz"); const m = new Module({ id: "build-descr-library.e.id", version: "2.0.0", @@ -95,7 +95,7 @@ test("Create project from library project providing a build manifest", async (t) t.truthy(project, "Module was able to create project from build manifest metadata"); t.is(project.getName(), project.getName(), "Archive project has correct name"); t.is(project.getNamespace(), project.getNamespace(), "Archive project has correct namespace"); - t.is(project.getResourceTagCollection().getTag("/resources/library/e/file.js", "ui5:HasDebugVariant"), true, + t.is(project.getProjectResourceTagCollection().getTag("/resources/library/e/file.js", "ui5:HasDebugVariant"), true, "Archive project has correct tag"); t.is(project.getVersion(), "2.0.0", "Archive project has version from archive module"); diff --git a/packages/project/test/lib/build/helpers/createBuildManifest.js b/packages/project/test/lib/build/helpers/createBuildManifest.js index 7b2266b8897..e62b41a5e36 100644 --- a/packages/project/test/lib/build/helpers/createBuildManifest.js +++ b/packages/project/test/lib/build/helpers/createBuildManifest.js @@ -64,15 +64,26 @@ test("Missing parameter: taskRepository", async (t) => { }); }); +test("Missing parameter: signature", async (t) => { + const project = await Specification.create(applicationProjectInput); + + const taskRepository = { + getVersions: async () => ({builderVersion: "", fsVersion: ""}) + }; + await t.throwsAsync(createBuildManifest(project, "buildConfig", taskRepository), { + message: "Missing parameter 'signature'" + }); +}); + test("Create application from project with build manifest", async (t) => { const project = await Specification.create(applicationProjectInput); - project.getResourceTagCollection().setTag("/resources/id1/foo.js", "ui5:HasDebugVariant"); + project.getProjectResourceTagCollection().setTag("/resources/id1/foo.js", "ui5:HasDebugVariant"); const taskRepository = { getVersions: async () => ({builderVersion: "", fsVersion: ""}) }; - const metadata = await createBuildManifest(project, "buildConfig", taskRepository); + const metadata = await createBuildManifest(project, "buildConfig", taskRepository, "yyy"); t.truthy(new Date(metadata.buildManifest.timestamp), "Timestamp is valid"); metadata.buildManifest.timestamp = ""; @@ -99,7 +110,8 @@ test("Create application from project with build manifest", async (t) => { } }, buildManifest: { - manifestVersion: "0.2", + manifestVersion: "1.0", + signature: "yyy", buildConfig: "buildConfig", namespace: "id1", timestamp: "", @@ -121,13 +133,13 @@ test("Create application from project with build manifest", async (t) => { test("Create library from project with build manifest", async (t) => { const project = await Specification.create(libraryProjectInput); - project.getResourceTagCollection().setTag("/resources/library/d/foo.js", "ui5:HasDebugVariant"); + project.getProjectResourceTagCollection().setTag("/resources/library/d/foo.js", "ui5:HasDebugVariant"); const taskRepository = { getVersions: async () => ({builderVersion: "", fsVersion: ""}) }; - const metadata = await createBuildManifest(project, "buildConfig", taskRepository); + const metadata = await createBuildManifest(project, "buildConfig", taskRepository, "zzz"); t.truthy(new Date(metadata.buildManifest.timestamp), "Timestamp is valid"); metadata.buildManifest.timestamp = ""; @@ -155,7 +167,8 @@ test("Create library from project with build manifest", async (t) => { } }, buildManifest: { - manifestVersion: "0.2", + manifestVersion: "1.0", + signature: "zzz", buildConfig: "buildConfig", namespace: "library/d", timestamp: "", diff --git a/packages/project/test/lib/graph/ProjectGraph.js b/packages/project/test/lib/graph/ProjectGraph.js index 46295f96723..9f546a47685 100644 --- a/packages/project/test/lib/graph/ProjectGraph.js +++ b/packages/project/test/lib/graph/ProjectGraph.js @@ -1152,6 +1152,550 @@ test("traverseDepthFirst: Dependency declaration order is followed", async (t) = ]); }); +test("traverseDependenciesDepthFirst: Basic traversal without including start module", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.b", "library.c"); + + const results = []; + for (const result of graph.traverseDependenciesDepthFirst("library.a")) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.c", + "library.b" + ], "Should traverse dependencies in depth-first order, excluding start module"); +}); + +test("traverseDependenciesDepthFirst: Basic traversal including start module", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.b", "library.c"); + + const results = []; + for (const result of graph.traverseDependenciesDepthFirst("library.a", true)) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.c", + "library.b", + "library.a" + ], "Should traverse dependencies in depth-first order, including start module"); +}); + +test("traverseDependenciesDepthFirst: Using boolean as first parameter", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.b", "library.c"); + + const results = []; + for (const result of graph.traverseDependenciesDepthFirst(true)) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.c", + "library.b", + "library.a" + ], "Should traverse from root and include root when boolean is passed as first parameter"); +}); + +test("traverseDependenciesDepthFirst: No dependencies", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + + const results = []; + for (const result of graph.traverseDependenciesDepthFirst("library.a")) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [], "Should return empty results when project has no dependencies"); +}); + +test("traverseDependenciesDepthFirst: Diamond dependency structure", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.a", "library.c"); + graph.declareDependency("library.b", "library.d"); + graph.declareDependency("library.c", "library.d"); + + const results = []; + for (const result of graph.traverseDependenciesDepthFirst("library.a")) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.d", + "library.b", + "library.c" + ], "Should visit library.d once, then library.b, then library.c"); +}); + +test("traverseDependenciesDepthFirst: Complex dependency chain", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + graph.addProject(await createProject("library.e")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.b", "library.c"); + graph.declareDependency("library.c", "library.d"); + graph.declareDependency("library.d", "library.e"); + + const results = []; + for (const result of graph.traverseDependenciesDepthFirst("library.a")) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.e", + "library.d", + "library.c", + "library.b" + ], "Should traverse entire dependency chain in depth-first order"); +}); + +test("traverseDependenciesDepthFirst: No project visited twice", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.a", "library.c"); + graph.declareDependency("library.b", "library.d"); + graph.declareDependency("library.c", "library.d"); + + const results = []; + for (const result of graph.traverseDependenciesDepthFirst("library.a")) { + results.push(result.project.getName()); + } + + // library.d should appear only once + const dCount = results.filter((name) => name === "library.d").length; + t.is(dCount, 1, "library.d should be visited exactly once"); + t.is(results.length, 3, "Should visit exactly 3 projects"); +}); + +test("traverseDependenciesDepthFirst: Can't find start node", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + + const error = t.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const result of graph.traverseDependenciesDepthFirst("library.nonexistent")) { + // Should not reach here + } + }); + t.is(error.message, + "Failed to start graph traversal: Could not find project library.nonexistent in project graph", + "Should throw with expected error message"); +}); + +test("traverseDependenciesDepthFirst: dependencies parameter", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.a", "library.c"); + graph.declareDependency("library.b", "library.d"); + + const results = []; + const dependencies = []; + for (const result of graph.traverseDependenciesDepthFirst("library.a")) { + results.push(result.project.getName()); + dependencies.push(result.dependencies); + } + + t.deepEqual(results, [ + "library.d", + "library.b", + "library.c" + ], "Should visit dependencies in depth-first order"); + + const dIndex = results.indexOf("library.d"); + const bIndex = results.indexOf("library.b"); + const cIndex = results.indexOf("library.c"); + + t.deepEqual(dependencies[dIndex], [], "library.d should have no dependencies"); + t.deepEqual(dependencies[bIndex], ["library.d"], "library.b should have library.d as dependency"); + t.deepEqual(dependencies[cIndex], [], "library.c should have no dependencies"); +}); + +test("traverseDependenciesDepthFirst: Detect cycle", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.b", "library.a"); + + const error = t.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const result of graph.traverseDependenciesDepthFirst("library.a")) { + // Should not complete iteration + } + }); + t.is(error.message, + "Detected cyclic dependency chain: *library.a* -> library.b -> *library.a*", + "Should throw with expected error message"); +}); + +test("traverseDependenciesDepthFirst: Dependency declaration order is followed", async (t) => { + const {ProjectGraph} = t.context; + const graph1 = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph1.addProject(await createProject("library.a")); + graph1.addProject(await createProject("library.b")); + graph1.addProject(await createProject("library.c")); + graph1.addProject(await createProject("library.d")); + + graph1.declareDependency("library.a", "library.b"); + graph1.declareDependency("library.a", "library.c"); + graph1.declareDependency("library.a", "library.d"); + + const results1 = []; + for (const result of graph1.traverseDependenciesDepthFirst("library.a")) { + results1.push(result.project.getName()); + } + + t.deepEqual(results1, [ + "library.b", + "library.c", + "library.d" + ], "First graph should visit in declaration order"); + + const graph2 = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph2.addProject(await createProject("library.a")); + graph2.addProject(await createProject("library.b")); + graph2.addProject(await createProject("library.c")); + graph2.addProject(await createProject("library.d")); + + graph2.declareDependency("library.a", "library.d"); + graph2.declareDependency("library.a", "library.c"); + graph2.declareDependency("library.a", "library.b"); + + const results2 = []; + for (const result of graph2.traverseDependenciesDepthFirst("library.a")) { + results2.push(result.project.getName()); + } + + t.deepEqual(results2, [ + "library.d", + "library.c", + "library.b" + ], "Second graph should visit in reverse declaration order"); +}); + +test("traverseDependents: Basic traversal without including start module", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + + graph.declareDependency("library.b", "library.c"); + graph.declareDependency("library.a", "library.b"); + + const results = []; + for (const result of graph.traverseDependents("library.c")) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.b", + "library.a" + ], "Should traverse dependents in correct order, excluding start module"); +}); + +test("traverseDependents: Basic traversal including start module", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + + graph.declareDependency("library.b", "library.c"); + graph.declareDependency("library.a", "library.b"); + + const results = []; + for (const result of graph.traverseDependents("library.c", true)) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.c", + "library.b", + "library.a" + ], "Should traverse dependents in correct order, including start module"); +}); + +test("traverseDependents: Using boolean as first parameter", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.c" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + + graph.declareDependency("library.a", "library.c"); + graph.declareDependency("library.b", "library.c"); + + const results = []; + for (const result of graph.traverseDependents(true)) { + results.push(result.project.getName()); + } + + t.deepEqual(results.sort(), [ + "library.a", + "library.b", + "library.c" + ].sort(), "Should traverse from root and include root when boolean is passed as first parameter"); +}); + +test("traverseDependents: No dependents", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + + graph.declareDependency("library.a", "library.b"); + + const results = []; + for (const result of graph.traverseDependents("library.a")) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [], "Should return empty results when project has no dependents"); +}); + +test("traverseDependents: Multiple dependents", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + + graph.declareDependency("library.a", "library.d"); + graph.declareDependency("library.b", "library.d"); + graph.declareDependency("library.c", "library.d"); + + const results = []; + for (const result of graph.traverseDependents("library.d")) { + results.push(result.project.getName()); + } + + t.deepEqual(results.sort(), [ + "library.a", + "library.b", + "library.c" + ].sort(), "Should return all projects that depend on the target project"); +}); + +test("traverseDependents: Complex chain", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + graph.addProject(await createProject("library.e")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.b", "library.c"); + graph.declareDependency("library.c", "library.d"); + graph.declareDependency("library.d", "library.e"); + + const results = []; + for (const result of graph.traverseDependents("library.e")) { + results.push(result.project.getName()); + } + + t.deepEqual(results, [ + "library.d", + "library.c", + "library.b", + "library.a" + ], "Should traverse entire dependent chain"); +}); + +test("traverseDependents: No project visited twice", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + + graph.declareDependency("library.a", "library.c"); + graph.declareDependency("library.b", "library.c"); + graph.declareDependency("library.a", "library.d"); + graph.declareDependency("library.b", "library.d"); + graph.declareDependency("library.c", "library.d"); + + const results = []; + for (const result of graph.traverseDependents("library.d")) { + results.push(result.project.getName()); + } + + t.deepEqual(results.sort(), [ + "library.a", + "library.b", + "library.c" + ].sort(), "Should visit each project exactly once"); +}); + +test("traverseDependents: Can't find start node", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + + const error = t.throws(() => { + // Consume the generator to trigger the error + // eslint-disable-next-line no-unused-vars + for (const result of graph.traverseDependents("library.nonexistent")) { + // Should not reach here + } + }); + t.is(error.message, + "Failed to start graph traversal: Could not find project library.nonexistent in project graph", + "Should throw with expected error message"); +}); + +test("traverseDependents: dependents parameter", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + graph.addProject(await createProject("library.c")); + graph.addProject(await createProject("library.d")); + + graph.declareDependency("library.a", "library.d"); + graph.declareDependency("library.b", "library.d"); + graph.declareDependency("library.b", "library.c"); + graph.declareDependency("library.c", "library.d"); + + const results = []; + const dependents = []; + for (const result of graph.traverseDependents("library.d")) { + results.push(result.project.getName()); + dependents.push(result.dependents); + } + + t.deepEqual(results.sort(), [ + "library.a", + "library.b", + "library.c" + ].sort(), "Should visit all dependents"); + + // Check that dependents information is provided correctly + const aIndex = results.indexOf("library.a"); + const bIndex = results.indexOf("library.b"); + const cIndex = results.indexOf("library.c"); + + t.deepEqual(dependents[aIndex], [], "library.a should have no dependents"); + t.deepEqual(dependents[bIndex], [], "library.b should have no dependents"); + t.deepEqual(dependents[cIndex], ["library.b"], "library.c should have library.b as dependent"); +}); + +test("traverseDependents: Detect cycle", async (t) => { + const {ProjectGraph} = t.context; + const graph = new ProjectGraph({ + rootProjectName: "library.a" + }); + graph.addProject(await createProject("library.a")); + graph.addProject(await createProject("library.b")); + + graph.declareDependency("library.a", "library.b"); + graph.declareDependency("library.b", "library.a"); + + const error = t.throws(() => { + // eslint-disable-next-line no-unused-vars + for (const result of graph.traverseDependents("library.a")) { + // Should not complete iteration + } + }); + t.is(error.message, + "Detected cyclic dependency chain: *library.a* -> library.b -> *library.a*", + "Should throw with expected error message"); +}); + test("join", async (t) => { const {ProjectGraph} = t.context; const graph1 = new ProjectGraph({ diff --git a/packages/project/test/lib/specifications/types/Application.js b/packages/project/test/lib/specifications/types/Application.js index 0a53ae309b0..cf27337910d 100644 --- a/packages/project/test/lib/specifications/types/Application.js +++ b/packages/project/test/lib/specifications/types/Application.js @@ -357,7 +357,8 @@ test("Read and write resources outside of app namespace", async (t) => { const workspace = project.getWorkspace(); await workspace.write(createResource({ - path: "/resources/my-custom-bundle.js" + path: "/resources/my-custom-bundle.js", + string: "// some custom bundle content" })); const buildtimeReader = project.getReader({style: "buildtime"}); diff --git a/packages/project/test/lib/specifications/types/Component.js b/packages/project/test/lib/specifications/types/Component.js index f5713be9d95..25bac64d823 100644 --- a/packages/project/test/lib/specifications/types/Component.js +++ b/packages/project/test/lib/specifications/types/Component.js @@ -358,7 +358,8 @@ test("Read and write resources outside of app namespace", async (t) => { const workspace = project.getWorkspace(); await workspace.write(createResource({ - path: "/resources/my-custom-bundle.js" + path: "/resources/my-custom-bundle.js", + string: "// some custom bundle content" })); const buildtimeReader = project.getReader({style: "buildtime"}); diff --git a/packages/project/test/lib/specifications/types/Library.js b/packages/project/test/lib/specifications/types/Library.js index aaeed466701..fc7f4d339b3 100644 --- a/packages/project/test/lib/specifications/types/Library.js +++ b/packages/project/test/lib/specifications/types/Library.js @@ -480,7 +480,7 @@ test("_parseConfiguration: Get copyright", async (t) => { const {projectInput} = t.context; const project = await (new Library().init(projectInput)); - t.is(project.getCopyright(), "Some fancy copyright", "Copyright was read correctly"); + t.is(project.getCopyright(), "${copyright}", "Copyright was read correctly"); }); test("_parseConfiguration: Copyright already configured", async (t) => { diff --git a/packages/server/lib/middleware/MiddlewareManager.js b/packages/server/lib/middleware/MiddlewareManager.js index 36894892e4d..10348b82154 100644 --- a/packages/server/lib/middleware/MiddlewareManager.js +++ b/packages/server/lib/middleware/MiddlewareManager.js @@ -21,17 +21,19 @@ const hasOwn = Function.prototype.call.bind(Object.prototype.hasOwnProperty); * @alias @ui5/server/internal/MiddlewareManager */ class MiddlewareManager { - constructor({graph, rootProject, resources, options = { + constructor({graph, rootProject, sources, resources, buildReader, options = { sendSAPTargetCSP: false, serveCSPReports: false }}) { - if (!graph || !rootProject || !resources || !resources.all || + if (!graph || !rootProject || !sources || !resources || !resources.all || !resources.rootProject || !resources.dependencies) { throw new Error("[MiddlewareManager]: One or more mandatory parameters not provided"); } this.graph = graph; this.rootProject = rootProject; + this.sources = sources; this.resources = resources; + this.buildReader = buildReader; this.options = options; this.middleware = Object.create(null); @@ -218,7 +220,8 @@ class MiddlewareManager { }); await this.addMiddleware("serveResources"); await this.addMiddleware("testRunner"); - await this.addMiddleware("serveThemes"); + // TODO: Allow to still reference 'serveThemes' middleware in custom middleware + // await this.addMiddleware("serveThemes"); await this.addMiddleware("versionInfo", { mountPath: "/resources/sap-ui-version.json" }); diff --git a/packages/server/lib/middleware/serveResources.js b/packages/server/lib/middleware/serveResources.js index 3e6c1d0ac63..e0a96ce2441 100644 --- a/packages/server/lib/middleware/serveResources.js +++ b/packages/server/lib/middleware/serveResources.js @@ -1,15 +1,5 @@ -import {getLogger} from "@ui5/logger"; -const log = getLogger("server:middleware:serveResources"); -import replaceStream from "replacestream"; import etag from "etag"; import fresh from "fresh"; -import fsInterface from "@ui5/fs/fsInterface"; - -const rProperties = /\.properties$/i; -const rReplaceVersion = /\.(library|js|json)$/i; -const rManifest = /\/manifest\.json$/i; -const rResourcesPrefix = /^\/resources\//i; -const rTestResourcesPrefix = /^\/test-resources\//i; function isFresh(req, res) { return fresh(req.headers, { @@ -18,7 +8,7 @@ function isFresh(req, res) { } /** - * Creates and returns the middleware to serve application resources. + * Creates and returns the middleware to serve project resources. * * @module @ui5/server/middleware/serveResources * @param {object} parameters Parameters @@ -30,90 +20,23 @@ function createMiddleware({resources, middlewareUtil}) { return async function serveResources(req, res, next) { try { const pathname = middlewareUtil.getPathname(req); - let resource = await resources.all.byPath(pathname); - if (!resource) { // Not found - if (!rManifest.test(pathname) || !rResourcesPrefix.test(pathname)) { - next(); - return; - } - log.verbose(`Could not find manifest.json for ${pathname}. ` + - `Checking for .library file to generate manifest.json from.`); - const {default: generateLibraryManifest} = await import("./helper/generateLibraryManifest.js"); - // Attempt to find a .library file, which is required for generating a manifest.json - const dotLibraryPath = pathname.replace(rManifest, "/.library"); - const dotLibraryResource = await resources.all.byPath(dotLibraryPath); - if (dotLibraryResource && dotLibraryResource.getProject()?.getType() === "library") { - resource = await generateLibraryManifest(middlewareUtil, dotLibraryResource); - } - if (!resource) { - // Not a library project, missing .library file or other reason for failed manifest.json generation - next(); - return; - } - } else if ( - rManifest.test(pathname) && !rTestResourcesPrefix.test(pathname) && - resource.getProject()?.getNamespace() - ) { - // Special handling for manifest.json file by adding additional content to the served manifest.json - // NOTE: This should only be done for manifest.json files that exist in the sources, - // not in test-resources. - // Files created by generateLibraryManifest (see above) should not be handled in here. - // Only manifest.json files in library / application projects should be handled. - // resource.getProject.getNamespace() returns null for all other kind of projects. - const {default: manifestEnhancer} = await import("@ui5/builder/processors/manifestEnhancer"); - await manifestEnhancer({ - resources: [resource], - // Ensure that only files within the manifest's project are accessible - // Using the "runtime" style to match the style used by the UI5 server - fs: fsInterface(resource.getProject().getReader({style: "runtime"})) - }); + const resource = await resources.all.byPath(pathname); + if (!resource) { + // Not found + next(); + return; } const resourcePath = resource.getPath(); - if (rProperties.test(resourcePath)) { - // Special handling for *.properties files escape non ascii characters. - const {default: nonAsciiEscaper} = await import("@ui5/builder/processors/nonAsciiEscaper"); - const project = resource.getProject(); - let propertiesFileSourceEncoding = project?.getPropertiesFileSourceEncoding?.(); - - if (!propertiesFileSourceEncoding) { - if (project && project.getSpecVersion().lte("1.1")) { - // default encoding to "ISO-8859-1" for old specVersions - propertiesFileSourceEncoding = "ISO-8859-1"; - } else { - // default encoding to "UTF-8" for all projects starting with specVersion 2.0 - propertiesFileSourceEncoding = "UTF-8"; - } - } - const encoding = nonAsciiEscaper.getEncodingFromAlias(propertiesFileSourceEncoding); - await nonAsciiEscaper({ - resources: [resource], options: { - encoding - } - }); - } - const {contentType, charset} = middlewareUtil.getMimeInfo(resourcePath); + const {contentType} = middlewareUtil.getMimeInfo(resourcePath); if (!res.getHeader("Content-Type")) { res.setHeader("Content-Type", contentType); } // Enable ETag caching - const statInfo = resource.getStatInfo(); - if (statInfo?.size !== undefined && !resource.isModified()) { - let etagHeader = etag(statInfo); - if (resource.getProject()) { - // Add project version to ETag to invalidate cache when project version changes. - // This is necessary to invalidate files with ${version} placeholders. - etagHeader = etagHeader.slice(0, -1) + `-${resource.getProject().getVersion()}"`; - } - res.setHeader("ETag", etagHeader); - } else { - // Fallback to buffer if stats are not available or insufficient or resource is modified. - // Modified resources must use the buffer for cache invalidation so that UI5 CLI changes - // invalidate the cache even when the original resource is not modified. - res.setHeader("ETag", etag(await resource.getBuffer())); - } + const resourceIntegrity = await resource.getIntegrity(); + res.setHeader("ETag", etag(resourceIntegrity)); if (isFresh(req, res)) { // client has a fresh copy of the resource @@ -122,22 +45,9 @@ function createMiddleware({resources, middlewareUtil}) { return; } - let stream = resource.getStream(); - - // Only execute version replacement for UTF-8 encoded resources because replaceStream will always output - // UTF-8 anyways. - // Also, only process .library, *.js and *.json files. Just like it's done in Application- - // and LibraryBuilder - if ((!charset || charset === "UTF-8") && rReplaceVersion.test(resourcePath)) { - if (resource.getProject()) { - stream.setEncoding("utf8"); - stream = stream.pipe(replaceStream("${version}", resource.getProject().getVersion())); - } else { - log.verbose(`Project missing from resource ${pathname}"`); - } - } - - stream.pipe(res); + // Pipe resource stream to response + // TODO: Check whether we can optimize this for small or even all resources by using getBuffer() + res.send(await resource.getBuffer()); } catch (err) { next(err); } diff --git a/packages/server/lib/server.js b/packages/server/lib/server.js index 3b108a551d7..4a3ec568dcd 100644 --- a/packages/server/lib/server.js +++ b/packages/server/lib/server.js @@ -127,6 +127,7 @@ async function _addSsl({app, key, cert}) { * are send for any requested *.html file * @param {boolean} [options.serveCSPReports=false] Enable CSP reports serving for request url * '/.ui5/csp/csp-reports.json' + * @param {Function} error Error callback. Will be called when an error occurs outside of request handling. * @returns {Promise} Promise resolving once the server is listening. * It resolves with an object containing the port, * h2-flag and a close function, @@ -135,7 +136,7 @@ async function _addSsl({app, key, cert}) { export async function serve(graph, { port: requestedPort, changePortIfInUse = false, h2 = false, key, cert, acceptRemoteConnections = false, sendSAPTargetCSP = false, simpleIndex = false, serveCSPReports = false -}) { +}, error) { const rootProject = graph.getRoot(); const readers = []; @@ -144,30 +145,51 @@ export async function serve(graph, { // Ignore root project return; } - readers.push(dep.getReader({style: "runtime"})); + readers.push(dep.getSourceReader("runtime")); }); const dependencies = createReaderCollection({ - name: `Dependency reader collection for project ${rootProject.getName()}`, + name: `Dependency reader collection for sources of project ${rootProject.getName()}`, readers }); - const rootReader = rootProject.getReader({style: "runtime"}); + const rootReader = rootProject.getSourceReader("runtime"); // TODO change to ReaderCollection once duplicates are sorted out const combo = new ReaderCollectionPrioritized({ - name: "server - prioritize workspace over dependencies", + name: "Server: Reader for sources of all projects", readers: [rootReader, dependencies] }); - const resources = { + const sources = { rootProject: rootReader, dependencies: dependencies, all: combo }; + const initialBuildIncludedDependencies = []; + if (graph.getProject("sap.ui.core")) { + // Ensure sap.ui.core is always built initially (if present in the graph) + initialBuildIncludedDependencies.push("sap.ui.core"); + } + const buildServer = await graph.serve({ + initialBuildIncludedDependencies, + excludedTasks: ["minify"], + }); + + const resources = { + rootProject: buildServer.getRootReader(), + dependencies: buildServer.getDependenciesReader(), + all: buildServer.getReader(), + }; + + buildServer.on("error", async (err) => { + error(err); + }); + const middlewareManager = new MiddlewareManager({ graph, rootProject, + sources, resources, options: { sendSAPTargetCSP, diff --git a/packages/server/package.json b/packages/server/package.json index 83b9b10c7fe..268b631d1a6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -101,7 +101,6 @@ "mime-types": "^2.1.35", "parseurl": "^1.3.3", "portscanner": "^2.2.0", - "replacestream": "^4.0.3", "router": "^2.2.0", "spdy": "^4.0.2", "yesno": "^0.4.0"