diff --git a/build b/build index 0444d0b3..5edd2601 100755 --- a/build +++ b/build @@ -1,13 +1,27 @@ #!/bin/bash +build_legacy_caching() { + echo "Building legacy caching module..." + cd sources/legacy-caching + npm clean-install + npm run build + cd ../.. +} + cd sources case "$1" in all) + cd .. + build_legacy_caching + cd sources npm run all ;; act) # Build and copy outputs to the dist directory + cd .. + build_legacy_caching + cd sources npm run build cd .. cp -r sources/dist . @@ -17,6 +31,9 @@ case "$1" in git checkout -- dist ;; dist) + cd .. + build_legacy_caching + cd sources npm clean-install npm run build cd .. @@ -27,6 +44,9 @@ case "$1" in ./gradlew check ;; install) + cd .. + build_legacy_caching + cd sources npm clean-install npm run build ;; @@ -35,6 +55,9 @@ case "$1" in npm test -- $@ ;; *) + cd .. + build_legacy_caching + cd sources npm run build ;; esac diff --git a/sources/legacy-caching/package-lock.json b/sources/legacy-caching/package-lock.json new file mode 100644 index 00000000..9e880095 --- /dev/null +++ b/sources/legacy-caching/package-lock.json @@ -0,0 +1,2427 @@ +{ + "name": "gradle-actions-legacy-caching", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "gradle-actions-legacy-caching", + "version": "1.0.0", + "hasInstallScript": true, + "dependencies": { + "@actions/cache": "4.0.5", + "@actions/core": "3.0.0", + "@actions/exec": "3.0.0", + "@actions/github": "9.0.0", + "@actions/glob": "0.6.1", + "semver": "7.7.4" + }, + "devDependencies": { + "esbuild": "0.27.4", + "patch-package": "8.0.0" + } + }, + "node_modules/@actions/cache": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@actions/cache/-/cache-4.0.5.tgz", + "integrity": "sha512-RjLz1/vvntOfp3FpkY3wB0MjVRbLq7bfQEuQG9UUTKwdtcYmFrKVmuD+9B6ADbzbkSfHM+dM4sMjdr3R4XIkFg==", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.11.1", + "@actions/exec": "^1.0.1", + "@actions/glob": "^0.1.0", + "@actions/http-client": "^2.1.1", + "@actions/io": "^1.0.1", + "@azure/abort-controller": "^1.1.0", + "@azure/ms-rest-js": "^2.6.0", + "@azure/storage-blob": "^12.13.0", + "@protobuf-ts/runtime-rpc": "^2.11.1", + "semver": "^6.3.1" + } + }, + "node_modules/@actions/cache/node_modules/@actions/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", + "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==", + "license": "MIT", + "dependencies": { + "@actions/exec": "^1.1.1", + "@actions/http-client": "^2.0.1" + } + }, + "node_modules/@actions/cache/node_modules/@actions/exec": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz", + "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==", + "license": "MIT", + "dependencies": { + "@actions/io": "^1.0.1" + } + }, + "node_modules/@actions/cache/node_modules/@actions/glob": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.1.2.tgz", + "integrity": "sha512-SclLR7Ia5sEqjkJTPs7Sd86maMDw43p769YxBOxvPvEWuPEhpAnBsQfENOpXjFYMmhCqd127bmf+YdvJqVqR4A==", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.2.6", + "minimatch": "^3.0.4" + } + }, + "node_modules/@actions/cache/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@actions/core": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-3.0.0.tgz", + "integrity": "sha512-zYt6cz+ivnTmiT/ksRVriMBOiuoUpDCJJlZ5KPl2/FRdvwU3f7MPh9qftvbkXJThragzUZieit2nyHUyw53Seg==", + "license": "MIT", + "dependencies": { + "@actions/exec": "^3.0.0", + "@actions/http-client": "^4.0.0" + } + }, + "node_modules/@actions/core/node_modules/@actions/http-client": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-4.0.0.tgz", + "integrity": "sha512-QuwPsgVMsD6qaPD57GLZi9sqzAZCtiJT8kVBCDpLtxhL5MydQ4gS+DrejtZZPdIYyB1e95uCK9Luyds7ybHI3g==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/exec": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-3.0.0.tgz", + "integrity": "sha512-6xH/puSoNBXb72VPlZVm7vQ+svQpFyA96qdDBvhB8eNZOE8LtPf9L4oAsfzK/crCL8YZ+19fKYVnM63Sl+Xzlw==", + "license": "MIT", + "dependencies": { + "@actions/io": "^3.0.2" + } + }, + "node_modules/@actions/exec/node_modules/@actions/io": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-3.0.2.tgz", + "integrity": "sha512-nRBchcMM+QK1pdjO7/idu86rbJI5YHUKCvKs0KxnSYbVe3F51UfGxuZX4Qy/fWlp6l7gWFwIkrOzN+oUK03kfw==", + "license": "MIT" + }, + "node_modules/@actions/github": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@actions/github/-/github-9.0.0.tgz", + "integrity": "sha512-yJ0RoswsAaKcvkmpCE4XxBRiy/whH2SdTBHWzs0gi4wkqTDhXMChjSdqBz/F4AeiDlP28rQqL33iHb+kjAMX6w==", + "license": "MIT", + "dependencies": { + "@actions/http-client": "^3.0.2", + "@octokit/core": "^7.0.6", + "@octokit/plugin-paginate-rest": "^14.0.0", + "@octokit/plugin-rest-endpoint-methods": "^17.0.0", + "@octokit/request": "^10.0.7", + "@octokit/request-error": "^7.1.0", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/github/node_modules/@actions/http-client": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz", + "integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^6.23.0" + } + }, + "node_modules/@actions/glob": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.6.1.tgz", + "integrity": "sha512-K4+2Ac5ILcf2ySdJCha+Pop9NcKjxqCL4xL4zI50dgB2PbXgC0+AcP011xfH4Of6b4QEJJg8dyZYv7zl4byTsw==", + "license": "MIT", + "dependencies": { + "@actions/core": "^3.0.0", + "minimatch": "^3.0.4" + } + }, + "node_modules/@actions/http-client": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz", + "integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==", + "license": "MIT", + "dependencies": { + "tunnel": "^0.0.6", + "undici": "^5.25.4" + } + }, + "node_modules/@actions/http-client/node_modules/undici": { + "version": "5.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-5.29.0.tgz", + "integrity": "sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=14.0" + } + }, + "node_modules/@actions/io": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz", + "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==", + "license": "MIT" + }, + "node_modules/@azure/abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz", + "integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.2.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@azure/core-auth": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.10.1.tgz", + "integrity": "sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-util": "^1.13.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-auth/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-client": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-client/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-http-compat": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@azure/core-http-compat/-/core-http-compat-2.3.2.tgz", + "integrity": "sha512-Tf6ltdKzOJEgxZeWLCjMxrxbodB/ZeCbzzA1A2qHbhzAjzjHoBVSUeSl/baT/oHAxhc4qdqVaDKnc2+iE932gw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "@azure/core-client": "^1.10.0", + "@azure/core-rest-pipeline": "^1.22.0" + } + }, + "node_modules/@azure/core-http-compat/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.7.2.tgz", + "integrity": "sha512-0YIpccoX8m/k00O7mDDMdJpbr6mf1yWo2dfmxt5A8XVZVVMz2SSKaEbMCeJRvgQ0IaSlqhjT47p4hVIRRy90xw==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.0.0", + "@azure/core-util": "^1.2.0", + "@azure/logger": "^1.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-lro/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-paging": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/@azure/core-paging/-/core-paging-1.6.2.tgz", + "integrity": "sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline": { + "version": "1.23.0", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.23.0.tgz", + "integrity": "sha512-Evs1INHo+jUjwHi1T6SG6Ua/LHOQBCLuKEEE6efIpt4ZOoNonaT1kP32GoOcdNDbfqsD2445CPri3MubBy5DEQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.10.0", + "@azure/core-tracing": "^1.3.0", + "@azure/core-util": "^1.13.0", + "@azure/logger": "^1.3.0", + "@typespec/ts-http-runtime": "^0.3.4", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-rest-pipeline/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-tracing": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.3.1.tgz", + "integrity": "sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.13.1.tgz", + "integrity": "sha512-XPArKLzsvl0Hf0CaGyKHUyVgF7oDnhKoP85Xv6M4StF/1AhfORhZudHtOyf2s+FcbuQ9dPRAjB8J2KvRRMUK2A==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/core-util/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/core-xml": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.5.0.tgz", + "integrity": "sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw==", + "license": "MIT", + "dependencies": { + "fast-xml-parser": "^5.0.7", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/logger": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.3.0.tgz", + "integrity": "sha512-fCqPIfOcLE+CGqGPd66c8bZpwAji98tZ4JI9i/mlTNTlsIWslCfpg48s/ypyLxZTump5sypjrKn2/kY7q8oAbA==", + "license": "MIT", + "dependencies": { + "@typespec/ts-http-runtime": "^0.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/ms-rest-js": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/@azure/ms-rest-js/-/ms-rest-js-2.7.0.tgz", + "integrity": "sha512-ngbzWbqF+NmztDOpLBVDxYM+XLcUj7nKhxGbSU9WtIsXfRB//cf2ZbAG5HkOrhU9/wd/ORRB6lM/d69RKVjiyA==", + "license": "MIT", + "dependencies": { + "@azure/core-auth": "^1.1.4", + "abort-controller": "^3.0.0", + "form-data": "^2.5.0", + "node-fetch": "^2.6.7", + "tslib": "^1.10.0", + "tunnel": "0.0.6", + "uuid": "^8.3.2", + "xml2js": "^0.5.0" + } + }, + "node_modules/@azure/ms-rest-js/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@azure/storage-blob": { + "version": "12.31.0", + "resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.31.0.tgz", + "integrity": "sha512-DBgNv10aCSxopt92DkTDD0o9xScXeBqPKGmR50FPZQaEcH4JLQ+GEOGEDv19V5BMkB7kxr+m4h6il/cCDPvmHg==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-client": "^1.9.3", + "@azure/core-http-compat": "^2.2.0", + "@azure/core-lro": "^2.2.0", + "@azure/core-paging": "^1.6.2", + "@azure/core-rest-pipeline": "^1.19.1", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/core-xml": "^1.4.5", + "@azure/logger": "^1.1.4", + "@azure/storage-common": "^12.3.0", + "events": "^3.0.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/storage-blob/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@azure/storage-common": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/@azure/storage-common/-/storage-common-12.3.0.tgz", + "integrity": "sha512-/OFHhy86aG5Pe8dP5tsp+BuJ25JOAl9yaMU3WZbkeoiFMHFtJ7tu5ili7qEdBXNW9G5lDB19trwyI6V49F/8iQ==", + "license": "MIT", + "dependencies": { + "@azure/abort-controller": "^2.1.2", + "@azure/core-auth": "^1.9.0", + "@azure/core-http-compat": "^2.2.0", + "@azure/core-rest-pipeline": "^1.19.1", + "@azure/core-tracing": "^1.2.0", + "@azure/core-util": "^1.11.0", + "@azure/logger": "^1.1.4", + "events": "^3.3.0", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@azure/storage-common/node_modules/@azure/abort-controller": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", + "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", + "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", + "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", + "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", + "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", + "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", + "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", + "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", + "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", + "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", + "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", + "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", + "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", + "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", + "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", + "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", + "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", + "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", + "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", + "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", + "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", + "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", + "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", + "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", + "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", + "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", + "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.6.tgz", + "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.3", + "@octokit/request": "^10.0.6", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.3.tgz", + "integrity": "sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.3.tgz", + "integrity": "sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==", + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.6", + "@octokit/types": "^16.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "27.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-27.0.0.tgz", + "integrity": "sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==", + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-14.0.0.tgz", + "integrity": "sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-17.0.0.tgz", + "integrity": "sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.8", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.8.tgz", + "integrity": "sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==", + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.3", + "@octokit/request-error": "^7.0.2", + "@octokit/types": "^16.0.0", + "fast-content-type-parse": "^3.0.0", + "json-with-bigint": "^3.5.3", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.1.0.tgz", + "integrity": "sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==", + "license": "MIT", + "dependencies": { + "@octokit/types": "^16.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-16.0.0.tgz", + "integrity": "sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==", + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^27.0.0" + } + }, + "node_modules/@protobuf-ts/runtime": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime/-/runtime-2.11.1.tgz", + "integrity": "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ==", + "license": "(Apache-2.0 AND BSD-3-Clause)" + }, + "node_modules/@protobuf-ts/runtime-rpc": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@protobuf-ts/runtime-rpc/-/runtime-rpc-2.11.1.tgz", + "integrity": "sha512-4CqqUmNA+/uMz00+d3CYKgElXO9VrEbucjnBFEjqI4GuDrEQ32MaI3q+9qPBvIGOlL4PmHXrzM32vBPWRhQKWQ==", + "license": "Apache-2.0", + "dependencies": { + "@protobuf-ts/runtime": "^2.11.1" + } + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.4.tgz", + "integrity": "sha512-CI0NhTrz4EBaa0U+HaaUZrJhPoso8sG7ZFya8uQoBA57fjzrjRSv87ekCjLZOFExN+gXE/z0xuN2QfH4H2HrLQ==", + "license": "MIT", + "dependencies": { + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "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==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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" + } + }, + "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/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, + "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/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "license": "Apache-2.0" + }, + "node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "license": "MIT", + "dependencies": { + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "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==", + "dev": true, + "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==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "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", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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" + } + }, + "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" + } + }, + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", + "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.4", + "@esbuild/android-arm": "0.27.4", + "@esbuild/android-arm64": "0.27.4", + "@esbuild/android-x64": "0.27.4", + "@esbuild/darwin-arm64": "0.27.4", + "@esbuild/darwin-x64": "0.27.4", + "@esbuild/freebsd-arm64": "0.27.4", + "@esbuild/freebsd-x64": "0.27.4", + "@esbuild/linux-arm": "0.27.4", + "@esbuild/linux-arm64": "0.27.4", + "@esbuild/linux-ia32": "0.27.4", + "@esbuild/linux-loong64": "0.27.4", + "@esbuild/linux-mips64el": "0.27.4", + "@esbuild/linux-ppc64": "0.27.4", + "@esbuild/linux-riscv64": "0.27.4", + "@esbuild/linux-s390x": "0.27.4", + "@esbuild/linux-x64": "0.27.4", + "@esbuild/netbsd-arm64": "0.27.4", + "@esbuild/netbsd-x64": "0.27.4", + "@esbuild/openbsd-arm64": "0.27.4", + "@esbuild/openbsd-x64": "0.27.4", + "@esbuild/openharmony-arm64": "0.27.4", + "@esbuild/sunos-x64": "0.27.4", + "@esbuild/win32-arm64": "0.27.4", + "@esbuild/win32-ia32": "0.27.4", + "@esbuild/win32-x64": "0.27.4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, + "node_modules/fast-xml-builder": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz", + "integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "path-expression-matcher": "^1.1.3" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.5.9", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.9.tgz", + "integrity": "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "fast-xml-builder": "^1.1.4", + "path-expression-matcher": "^1.2.0", + "strnum": "^2.2.2" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } + }, + "node_modules/form-data": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", + "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.35", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "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/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" + } + }, + "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", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "dev": true, + "license": "ISC" + }, + "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": { + "node": ">=8" + } + }, + "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": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.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==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "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": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/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/json-stable-stringify": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.3.0.tgz", + "integrity": "sha512-qtYiSSFlwot9XHtF9bD9c7rwKjr+RecWT//ZnPvSmEjpV5mmPOCN4j8UjY5hbjNkOwZ/jQv3J6R1/pL7RwgMsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "isarray": "^2.0.5", + "jsonify": "^0.0.1", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/json-with-bigint": { + "version": "3.5.8", + "resolved": "https://registry.npmjs.org/json-with-bigint/-/json-with-bigint-3.5.8.tgz", + "integrity": "sha512-eq/4KP6K34kwa7TcFdtvnftvHCD9KvHOGGICWwMFc4dOOKF5t4iYqnfLK8otCRCRv06FXOzGGyqE8h8ElMvvdw==", + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/jsonify": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz", + "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==", + "dev": true, + "license": "Public Domain", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/klaw-sync": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", + "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.11" + } + }, + "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" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "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" + } + }, + "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" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "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": { + "node": ">= 0.4" + } + }, + "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": { + "wrappy": "1" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/patch-package": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz", + "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yarnpkg/lockfile": "^1.1.0", + "chalk": "^4.1.2", + "ci-info": "^3.7.0", + "cross-spawn": "^7.0.3", + "find-yarn-workspace-root": "^2.0.0", + "fs-extra": "^9.0.0", + "json-stable-stringify": "^1.0.2", + "klaw-sync": "^6.0.0", + "minimist": "^1.2.6", + "open": "^7.4.2", + "rimraf": "^2.6.3", + "semver": "^7.5.3", + "slash": "^2.0.0", + "tmp": "^0.0.33", + "yaml": "^2.2.2" + }, + "bin": { + "patch-package": "index.js" + }, + "engines": { + "node": ">=14", + "npm": ">5" + } + }, + "node_modules/path-expression-matcher": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "engines": { + "node": ">=14.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": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "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", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/sax": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz", + "integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strnum": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.2.tgz", + "integrity": "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "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": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "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==", + "license": "0BSD" + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/undici": { + "version": "6.24.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz", + "integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "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==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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==", + "license": "BSD-2-Clause" + }, + "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==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "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": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "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/xml2js": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz", + "integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "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" + } + }, + "node_modules/yaml": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", + "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/sources/legacy-caching/package.json b/sources/legacy-caching/package.json new file mode 100644 index 00000000..f86973b7 --- /dev/null +++ b/sources/legacy-caching/package.json @@ -0,0 +1,23 @@ +{ + "name": "gradle-actions-legacy-caching", + "version": "1.0.0", + "private": true, + "type": "module", + "main": "./dist/index.js", + "scripts": { + "postinstall": "patch-package", + "build": "esbuild src/index.ts --bundle --platform=node --target=node24 --format=esm --banner:js=\"import {createRequire} from 'module';const require=createRequire(import.meta.url);\" --outfile=dist/index.js --sourcemap --minify" + }, + "dependencies": { + "@actions/cache": "4.0.5", + "@actions/core": "3.0.0", + "@actions/exec": "3.0.0", + "@actions/github": "9.0.0", + "@actions/glob": "0.6.1", + "semver": "7.7.4" + }, + "devDependencies": { + "esbuild": "0.27.4", + "patch-package": "8.0.0" + } +} diff --git a/sources/legacy-caching/patches/@actions+cache+4.0.5.patch b/sources/legacy-caching/patches/@actions+cache+4.0.5.patch new file mode 100644 index 00000000..0ad85789 --- /dev/null +++ b/sources/legacy-caching/patches/@actions+cache+4.0.5.patch @@ -0,0 +1,248 @@ +diff --git a/node_modules/@actions/cache/lib/cache.d.ts b/node_modules/@actions/cache/lib/cache.d.ts +index ef0928b..d06e675 100644 +--- a/node_modules/@actions/cache/lib/cache.d.ts ++++ b/node_modules/@actions/cache/lib/cache.d.ts +@@ -21,7 +21,8 @@ export declare function isFeatureAvailable(): boolean; + * @param enableCrossOsArchive an optional boolean enabled to restore on windows any cache created on any platform + * @returns string returns the key for the cache hit, otherwise returns undefined + */ +-export declare function restoreCache(paths: string[], primaryKey: string, restoreKeys?: string[], options?: DownloadOptions, enableCrossOsArchive?: boolean): Promise; ++export declare function restoreCache(paths: string[], primaryKey: string, restoreKeys?: string[], options?: DownloadOptions, enableCrossOsArchive?: boolean): Promise; ++ + /** + * Saves a list of files with the specified key + * +@@ -31,4 +32,12 @@ export declare function restoreCache(paths: string[], primaryKey: string, restor + * @param options cache upload options + * @returns number returns cacheId if the cache was saved successfully and throws an error if save fails + */ +-export declare function saveCache(paths: string[], key: string, options?: UploadOptions, enableCrossOsArchive?: boolean): Promise; ++export declare function saveCache(paths: string[], key: string, options?: UploadOptions, enableCrossOsArchive?: boolean): Promise; ++ ++// PATCHED: Add `CacheEntry` as return type for save/restore functions ++// This allows us to track and report on cache entry sizes. ++export declare class CacheEntry { ++ key: string; ++ size?: number; ++ constructor(key: string, size?: number); ++} +diff --git a/node_modules/@actions/cache/lib/cache.js b/node_modules/@actions/cache/lib/cache.js +index 41f2a37..2fe1600 100644 +--- a/node_modules/@actions/cache/lib/cache.js ++++ b/node_modules/@actions/cache/lib/cache.js +@@ -165,26 +165,29 @@ function restoreCacheV1(paths, primaryKey, restoreKeys, options, enableCrossOsAr + core.info(`Cache Size: ~${Math.round(archiveFileSize / (1024 * 1024))} MB (${archiveFileSize} B)`); + yield (0, tar_1.extractTar)(archivePath, compressionMethod); + core.info('Cache restored successfully'); +- return cacheEntry.cacheKey; +- } +- catch (error) { +- const typedError = error; +- if (typedError.name === ValidationError.name) { +- throw error; +- } +- else { +- // warn on cache restore failure and continue build +- // Log server errors (5xx) as errors, all other errors as warnings +- if (typedError instanceof http_client_1.HttpClientError && +- typeof typedError.statusCode === 'number' && +- typedError.statusCode >= 500) { +- core.error(`Failed to restore: ${error.message}`); +- } +- else { +- core.warning(`Failed to restore: ${error.message}`); +- } +- } ++ ++ // PATCHED - Include size of restored entry ++ return new CacheEntry(cacheEntry.cacheKey, archiveFileSize); + } ++ // PATCHED - propagate errors ++ // catch (error) { ++ // const typedError = error; ++ // if (typedError.name === ValidationError.name) { ++ // throw error; ++ // } ++ // else { ++ // // warn on cache restore failure and continue build ++ // // Log server errors (5xx) as errors, all other errors as warnings ++ // if (typedError instanceof http_client_1.HttpClientError && ++ // typeof typedError.statusCode === 'number' && ++ // typedError.statusCode >= 500) { ++ // core.error(`Failed to restore: ${error.message}`); ++ // } ++ // else { ++ // core.warning(`Failed to restore: ${error.message}`); ++ // } ++ // } ++ //} + finally { + // Try to delete the archive to save space + try { +@@ -257,26 +260,29 @@ function restoreCacheV2(paths, primaryKey, restoreKeys, options, enableCrossOsAr + } + yield (0, tar_1.extractTar)(archivePath, compressionMethod); + core.info('Cache restored successfully'); +- return response.matchedKey; +- } +- catch (error) { +- const typedError = error; +- if (typedError.name === ValidationError.name) { +- throw error; +- } +- else { +- // Supress all non-validation cache related errors because caching should be optional +- // Log server errors (5xx) as errors, all other errors as warnings +- if (typedError instanceof http_client_1.HttpClientError && +- typeof typedError.statusCode === 'number' && +- typedError.statusCode >= 500) { +- core.error(`Failed to restore: ${error.message}`); +- } +- else { +- core.warning(`Failed to restore: ${error.message}`); +- } +- } ++ ++ // PATCHED - Include size of restored entry ++ return new CacheEntry(response.matchedKey, archiveFileSize); + } ++ // PATCHED - propagate errors ++ // catch (error) { ++ // const typedError = error; ++ // if (typedError.name === ValidationError.name) { ++ // throw error; ++ // } ++ // else { ++ // // Supress all non-validation cache related errors because caching should be optional ++ // // Log server errors (5xx) as errors, all other errors as warnings ++ // if (typedError instanceof http_client_1.HttpClientError && ++ // typeof typedError.statusCode === 'number' && ++ // typedError.statusCode >= 500) { ++ // core.error(`Failed to restore: ${error.message}`); ++ // } ++ // else { ++ // core.warning(`Failed to restore: ${error.message}`); ++ // } ++ // } ++ //} + finally { + try { + if (archivePath) { +@@ -367,27 +373,31 @@ function saveCacheV1(paths, key, options, enableCrossOsArchive = false) { + } + core.debug(`Saving Cache (ID: ${cacheId})`); + yield cacheHttpClient.saveCache(cacheId, archivePath, '', options); ++ ++ // PATCHED - Include size of saved entry ++ return new CacheEntry(key, archiveFileSize); + } +- catch (error) { +- const typedError = error; +- if (typedError.name === ValidationError.name) { +- throw error; +- } +- else if (typedError.name === ReserveCacheError.name) { +- core.info(`Failed to save: ${typedError.message}`); +- } +- else { +- // Log server errors (5xx) as errors, all other errors as warnings +- if (typedError instanceof http_client_1.HttpClientError && +- typeof typedError.statusCode === 'number' && +- typedError.statusCode >= 500) { +- core.error(`Failed to save: ${typedError.message}`); +- } +- else { +- core.warning(`Failed to save: ${typedError.message}`); +- } +- } +- } ++ // PATCHED - propagate errors ++ //catch (error) { ++ // const typedError = error; ++ // if (typedError.name === ValidationError.name) { ++ // throw error; ++ // } ++ // else if (typedError.name === ReserveCacheError.name) { ++ // core.info(`Failed to save: ${typedError.message}`); ++ // } ++ // else { ++ // // Log server errors (5xx) as errors, all other errors as warnings ++ // if (typedError instanceof http_client_1.HttpClientError && ++ // typeof typedError.statusCode === 'number' && ++ // typedError.statusCode >= 500) { ++ // core.error(`Failed to save: ${typedError.message}`); ++ // } ++ // else { ++ // core.warning(`Failed to save: ${typedError.message}`); ++ // } ++ // } ++ //} + finally { + // Try to delete the archive to save space + try { +@@ -471,27 +481,31 @@ function saveCacheV2(paths, key, options, enableCrossOsArchive = false) { + throw new Error(`Unable to finalize cache with key ${key}, another job may be finalizing this cache.`); + } + cacheId = parseInt(finalizeResponse.entryId); ++ ++ // PATCHED - Include size of saved entry ++ return new CacheEntry(key, archiveFileSize); + } +- catch (error) { +- const typedError = error; +- if (typedError.name === ValidationError.name) { +- throw error; +- } +- else if (typedError.name === ReserveCacheError.name) { +- core.info(`Failed to save: ${typedError.message}`); +- } +- else { +- // Log server errors (5xx) as errors, all other errors as warnings +- if (typedError instanceof http_client_1.HttpClientError && +- typeof typedError.statusCode === 'number' && +- typedError.statusCode >= 500) { +- core.error(`Failed to save: ${typedError.message}`); +- } +- else { +- core.warning(`Failed to save: ${typedError.message}`); +- } +- } +- } ++ // PATCHED - propagate errors ++ //catch (error) { ++ // const typedError = error; ++ // if (typedError.name === ValidationError.name) { ++ // throw error; ++ // } ++ // else if (typedError.name === ReserveCacheError.name) { ++ // core.info(`Failed to save: ${typedError.message}`); ++ // } ++ // else { ++ // // Log server errors (5xx) as errors, all other errors as warnings ++ // if (typedError instanceof http_client_1.HttpClientError && ++ // typeof typedError.statusCode === 'number' && ++ // typedError.statusCode >= 500) { ++ // core.error(`Failed to save: ${typedError.message}`); ++ // } ++ // else { ++ // core.warning(`Failed to save: ${typedError.message}`); ++ // } ++ // } ++ //} + finally { + // Try to delete the archive to save space + try { +@@ -504,4 +518,12 @@ function saveCacheV2(paths, key, options, enableCrossOsArchive = false) { + return cacheId; + }); + } ++// PATCHED - CacheEntry class ++class CacheEntry { ++ constructor(key, size) { ++ this.key = key; ++ this.size = size; ++ } ++} ++ + //# sourceMappingURL=cache.js.map +\ No newline at end of file diff --git a/sources/legacy-caching/src/build-results-adapter.ts b/sources/legacy-caching/src/build-results-adapter.ts new file mode 100644 index 00000000..68ebda48 --- /dev/null +++ b/sources/legacy-caching/src/build-results-adapter.ts @@ -0,0 +1,52 @@ +import {versionIsAtLeast} from './gradle-utils' + +/** + * Mirrors the BuildResult interface from the main package. + */ +export interface BuildResult { + get rootProjectName(): string + get rootProjectDir(): string + get requestedTasks(): string + get gradleVersion(): string + get gradleHomeDir(): string + get buildFailed(): boolean + get configCacheHit(): boolean + get buildScanUri(): string + get buildScanFailed(): boolean +} + +/** + * Wraps BuildResult[] to provide the helper methods expected by the old caching code. + */ +export class BuildResults { + results: BuildResult[] + + constructor(results: BuildResult[]) { + this.results = results + } + + anyFailed(): boolean { + return this.results.some(result => result.buildFailed) + } + + anyConfigCacheHit(): boolean { + return this.results.some(result => result.configCacheHit) + } + + uniqueGradleHomes(): string[] { + const allHomes = this.results.map(buildResult => buildResult.gradleHomeDir) + return Array.from(new Set(allHomes)) + } + + highestGradleVersion(): string | null { + if (this.results.length === 0) { + return null + } + return this.results + .map(result => result.gradleVersion) + .reduce((maxVersion: string, currentVersion: string) => { + if (!maxVersion) return currentVersion + return versionIsAtLeast(currentVersion, maxVersion) ? currentVersion : maxVersion + }) + } +} diff --git a/sources/legacy-caching/src/cache-cleaner.ts b/sources/legacy-caching/src/cache-cleaner.ts new file mode 100644 index 00000000..aa30ec95 --- /dev/null +++ b/sources/legacy-caching/src/cache-cleaner.ts @@ -0,0 +1,89 @@ +import * as core from '@actions/core' +import * as exec from '@actions/exec' + +import fs from 'fs' +import path from 'path' +import {BuildResults} from './build-results-adapter' +import {findGradleExecutableForCleanup} from './gradle-utils' + +export class CacheCleaner { + private readonly gradleUserHome: string + private readonly tmpDir: string + + constructor(gradleUserHome: string, tmpDir: string) { + this.gradleUserHome = gradleUserHome + this.tmpDir = tmpDir + } + + async prepare(): Promise { + // Save the current timestamp + const timestamp = Date.now().toString() + core.saveState('clean-timestamp', timestamp) + return timestamp + } + + async forceCleanup(buildResults: BuildResults): Promise { + const executable = findGradleExecutableForCleanup(buildResults) + if (!executable) { + core.warning('Cache cleanup skipped: no suitable Gradle >= 8.11 found in build results.') + return + } + const cleanTimestamp = core.getState('clean-timestamp') + await this.forceCleanupFilesOlderThan(cleanTimestamp, executable) + } + + // Visible for testing + async forceCleanupFilesOlderThan(cleanTimestamp: string, executable: string): Promise { + // Run a dummy Gradle build to trigger cache cleanup + const cleanupProjectDir = path.resolve(this.tmpDir, 'dummy-cleanup-project') + fs.mkdirSync(cleanupProjectDir, {recursive: true}) + fs.writeFileSync( + path.resolve(cleanupProjectDir, 'settings.gradle'), + 'rootProject.name = "dummy-cleanup-project"' + ) + fs.writeFileSync( + path.resolve(cleanupProjectDir, 'init.gradle'), + ` + beforeSettings { settings -> + def cleanupTime = ${cleanTimestamp} + + settings.caches { + cleanup = Cleanup.ALWAYS + + releasedWrappers.setRemoveUnusedEntriesOlderThan(cleanupTime) + snapshotWrappers.setRemoveUnusedEntriesOlderThan(cleanupTime) + downloadedResources.setRemoveUnusedEntriesOlderThan(cleanupTime) + createdResources.setRemoveUnusedEntriesOlderThan(cleanupTime) + buildCache.setRemoveUnusedEntriesOlderThan(cleanupTime) + } + } + ` + ) + fs.writeFileSync(path.resolve(cleanupProjectDir, 'build.gradle'), 'task("noop") {}') + + await core.group('Executing Gradle to clean up caches', async () => { + core.info(`Cleaning up caches last used before ${cleanTimestamp}`) + await this.executeCleanupBuild(executable, cleanupProjectDir) + }) + } + + private async executeCleanupBuild(executable: string, cleanupProjectDir: string): Promise { + const args = [ + '-g', + this.gradleUserHome, + '-I', + 'init.gradle', + '--info', + '--no-daemon', + '--no-scan', + '--build-cache', + '-DGITHUB_DEPENDENCY_GRAPH_ENABLED=false', + '-DGRADLE_ACTIONS_SKIP_BUILD_RESULT_CAPTURE=true', + 'noop' + ] + + await exec.exec(executable, args, { + cwd: cleanupProjectDir + }) + } +} diff --git a/sources/legacy-caching/src/cache-config-adapter.ts b/sources/legacy-caching/src/cache-config-adapter.ts new file mode 100644 index 00000000..acd30a57 --- /dev/null +++ b/sources/legacy-caching/src/cache-config-adapter.ts @@ -0,0 +1,86 @@ +import * as core from '@actions/core' + +export const ACTION_METADATA_DIR = '.setup-gradle' + +/** + * Represents the cache options passed from the main action. + * This mirrors the CacheOptions interface from the main package. + */ +export interface CacheOptions { + disabled: boolean + readOnly: boolean + writeOnly: boolean + overwriteExisting: boolean + strictMatch: boolean + cleanup: string + encryptionKey?: string + includes: string[] + excludes: string[] +} + +/** + * Adapts the CacheOptions interface to the old CacheConfig method-based API + * expected by the recovered caching code. + */ +export class CacheConfig { + private readonly options: CacheOptions + + constructor(options: CacheOptions) { + this.options = options + } + + isCacheDisabled(): boolean { + return this.options.disabled + } + + isCacheReadOnly(): boolean { + return this.options.readOnly + } + + isCacheWriteOnly(): boolean { + return this.options.writeOnly + } + + isCacheOverwriteExisting(): boolean { + return this.options.overwriteExisting + } + + isCacheStrictMatch(): boolean { + return this.options.strictMatch + } + + isCacheCleanupEnabled(): boolean { + return this.options.cleanup !== 'never' + } + + shouldPerformCacheCleanup(hasFailure: boolean): boolean { + const cleanup = this.options.cleanup + if (cleanup === 'always') { + return true + } + if (cleanup === 'on-success') { + return !hasFailure + } + return false + } + + getCacheCleanupOption(): string { + return this.options.cleanup + } + + getCacheEncryptionKey(): string | undefined { + return this.options.encryptionKey + } + + getCacheIncludes(): string[] { + return [...this.options.includes] + } + + getCacheExcludes(): string[] { + return [...this.options.excludes] + } +} + +export function getJobMatrix(): string { + return core.getInput('workflow-job-context') +} diff --git a/sources/legacy-caching/src/cache-key.ts b/sources/legacy-caching/src/cache-key.ts new file mode 100644 index 00000000..6e668955 --- /dev/null +++ b/sources/legacy-caching/src/cache-key.ts @@ -0,0 +1,100 @@ +import * as github from '@actions/github' + +import {CacheConfig, getJobMatrix} from './cache-config-adapter' +import {hashStrings} from './cache-utils' + +const CACHE_PROTOCOL_VERSION = 'v1' + +const CACHE_KEY_PREFIX_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_PREFIX' +const CACHE_KEY_OS_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_ENVIRONMENT' +const CACHE_KEY_JOB_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB' +const CACHE_KEY_JOB_INSTANCE_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_INSTANCE' +const CACHE_KEY_JOB_EXECUTION_VAR = 'GRADLE_BUILD_ACTION_CACHE_KEY_JOB_EXECUTION' + +/** + * Represents a key used to restore a cache entry. + * The Github Actions cache will first try for an exact match on the key. + * If that fails, it will try for a prefix match on any of the restoreKeys. + */ +export class CacheKey { + key: string + restoreKeys: string[] + + constructor(key: string, restoreKeys: string[]) { + this.key = key + this.restoreKeys = restoreKeys + } +} + +/** + * Generates a cache key specific to the current job execution. + * The key is constructed from the following inputs (with some user overrides): + * - The cache key prefix: defaults to 'gradle-' but can be overridden by the user + * - The cache protocol version + * - The runner operating system + * - The name of the workflow and Job being executed + * - The matrix values for the Job being executed (job context) + * - The SHA of the commit being executed + * + * Caches are restored by trying to match the these key prefixes in order: + * - The full key with SHA + * - A previous key for this Job + matrix + * - Any previous key for this Job (any matrix) + * - Any previous key for this cache on the current OS + */ +export function generateCacheKey(cacheName: string, config: CacheConfig): CacheKey { + const prefix = process.env[CACHE_KEY_PREFIX_VAR] || '' + + const cacheKeyBase = `${prefix}${getCacheKeyBase(cacheName, CACHE_PROTOCOL_VERSION)}` + + // At the most general level, share caches for all executions on the same OS + const cacheKeyForEnvironment = `${cacheKeyBase}|${getCacheKeyEnvironment()}` + + // Then prefer caches that run job with the same ID + const cacheKeyForJob = `${cacheKeyForEnvironment}|${getCacheKeyJob()}` + + // Prefer (even more) jobs that run this job in the same workflow with the same context (matrix) + const cacheKeyForJobContext = `${cacheKeyForJob}[${getCacheKeyJobInstance()}]` + + // Exact match on Git SHA + const cacheKey = `${cacheKeyForJobContext}-${getCacheKeyJobExecution()}` + + if (config.isCacheStrictMatch()) { + return new CacheKey(cacheKey, [cacheKeyForJobContext]) + } + + return new CacheKey(cacheKey, [cacheKeyForJobContext, cacheKeyForJob, cacheKeyForEnvironment]) +} + +export function getCacheKeyBase(cacheName: string, cacheProtocolVersion: string): string { + // Prefix can be used to force change all cache keys (defaults to cache protocol version) + return `gradle-${cacheName}-${cacheProtocolVersion}` +} + +function getCacheKeyEnvironment(): string { + const runnerOs = process.env['RUNNER_OS'] || '' + const runnerArch = process.env['RUNNER_ARCH'] || '' + return process.env[CACHE_KEY_OS_VAR] || `${runnerOs}-${runnerArch}` +} + +function getCacheKeyJob(): string { + return process.env[CACHE_KEY_JOB_VAR] || github.context.job +} + +function getCacheKeyJobInstance(): string { + const override = process.env[CACHE_KEY_JOB_INSTANCE_VAR] + if (override) { + return override + } + + // By default, we hash the workflow name and the full `matrix` data for the run, to uniquely identify this job invocation + // The only way we can obtain the `matrix` data is via the `workflow-job-context` parameter in action.yml. + const workflowName = github.context.workflow + const workflowJobContext = getJobMatrix() + return hashStrings([workflowName, workflowJobContext]) +} + +function getCacheKeyJobExecution(): string { + // Used to associate a cache key with a particular execution (default is bound to the git commit sha) + return process.env[CACHE_KEY_JOB_EXECUTION_VAR] || github.context.sha +} diff --git a/sources/legacy-caching/src/cache-reporting.ts b/sources/legacy-caching/src/cache-reporting.ts new file mode 100644 index 00000000..26358b3e --- /dev/null +++ b/sources/legacy-caching/src/cache-reporting.ts @@ -0,0 +1,294 @@ +import * as cache from '@actions/cache' + +export const DEFAULT_CACHE_ENABLED_REASON = `[Cache was enabled](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#caching-build-state-between-jobs). Action attempted to both restore and save the Gradle User Home.` + +export const DEFAULT_READONLY_REASON = `[Cache was read-only](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#using-the-cache-read-only). By default, the action will only write to the cache for Jobs running on the default branch.` + +export const DEFAULT_DISABLED_REASON = `[Cache was disabled](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#disabling-caching). Gradle User Home was not restored from or saved to the cache.` + +export const DEFAULT_WRITEONLY_REASON = `[Cache was set to write-only](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#using-the-cache-write-only). Gradle User Home was not restored from cache.` + +export const EXISTING_GRADLE_HOME = `[Cache was disabled to avoid overwriting a pre-existing Gradle User Home](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#overwriting-an-existing-gradle-user-home). Gradle User Home was not restored from or saved to the cache.` + +export const CLEANUP_DISABLED_READONLY = `[Cache cleanup](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup) is always disabled when cache is read-only or disabled.` + +export const DEFAULT_CLEANUP_ENABLED_REASON = `[Cache cleanup](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup) was enabled. Stale files in Gradle User Home were purged before saving to the cache.` + +export const DEFAULT_CLEANUP_DISABLED_REASON = `[Cache cleanup](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup) was disabled via action parameter. No cleanup of Gradle User Home was performed.` + +export const CLEANUP_DISABLED_DUE_TO_FAILURE = + '[Cache cleanup was disabled due to build failure](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup). Use `cache-cleanup: always` to override this behavior.' + +export const CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT = + '[Cache cleanup was disabled due to configuration-cache reuse](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#configuring-cache-cleanup). This is expected.' + +/** + * Collects information on what entries were saved and restored during the action. + * This information is used to generate a summary of the cache usage. + */ +export class CacheListener { + cacheEntries: CacheEntryListener[] = [] + cacheReadOnly = false + cacheWriteOnly = false + cacheDisabled = false + cacheStatusReason: string = DEFAULT_CACHE_ENABLED_REASON + cacheCleanupMessage: string = DEFAULT_CLEANUP_DISABLED_REASON + + get fullyRestored(): boolean { + return this.cacheEntries.every(x => !x.wasRequestedButNotRestored()) + } + + get cacheStatus(): string { + if (!cache.isFeatureAvailable()) return 'not available' + if (this.cacheDisabled) return 'disabled' + if (this.cacheWriteOnly) return 'write-only' + if (this.cacheReadOnly) return 'read-only' + return 'enabled' + } + + setReadOnly(reason: string = DEFAULT_READONLY_REASON): void { + this.cacheReadOnly = true + this.cacheStatusReason = reason + this.cacheCleanupMessage = CLEANUP_DISABLED_READONLY + } + + setDisabled(reason: string = DEFAULT_DISABLED_REASON): void { + this.cacheDisabled = true + this.cacheStatusReason = reason + this.cacheCleanupMessage = CLEANUP_DISABLED_READONLY + } + + setWriteOnly(reason: string = DEFAULT_WRITEONLY_REASON): void { + this.cacheWriteOnly = true + this.cacheStatusReason = reason + } + + setCacheCleanupEnabled(): void { + this.cacheCleanupMessage = DEFAULT_CLEANUP_ENABLED_REASON + } + + setCacheCleanupDisabled(reason: string = DEFAULT_CLEANUP_DISABLED_REASON): void { + this.cacheCleanupMessage = reason + } + + entry(name: string): CacheEntryListener { + for (const entry of this.cacheEntries) { + if (entry.entryName === name) { + return entry + } + } + + const newEntry = new CacheEntryListener(name) + this.cacheEntries.push(newEntry) + return newEntry + } + + stringify(): string { + return JSON.stringify(this) + } + + static rehydrate(stringRep: string): CacheListener { + if (stringRep === '') { + return new CacheListener() + } + const rehydrated: CacheListener = Object.assign(new CacheListener(), JSON.parse(stringRep)) + const entries = rehydrated.cacheEntries + for (let index = 0; index < entries.length; index++) { + const rawEntry = entries[index] + entries[index] = Object.assign(new CacheEntryListener(rawEntry.entryName), rawEntry) + } + return rehydrated + } +} + +/** + * Collects information on the state of a single cache entry. + */ +export class CacheEntryListener { + entryName: string + requestedKey: string | undefined + requestedRestoreKeys: string[] | undefined + restoredKey: string | undefined + restoredSize: number | undefined + restoredTime: number | undefined + notRestored: string | undefined + + savedKey: string | undefined + savedSize: number | undefined + savedTime: number | undefined + notSaved: string | undefined + + constructor(entryName: string) { + this.entryName = entryName + } + + wasRequestedButNotRestored(): boolean { + return this.requestedKey !== undefined && this.restoredKey === undefined + } + + markRequested(key: string, restoreKeys: string[] = []): CacheEntryListener { + this.requestedKey = key + this.requestedRestoreKeys = restoreKeys + return this + } + + markRestored(key: string, size: number | undefined, time: number): CacheEntryListener { + this.restoredKey = key + this.restoredSize = size + this.restoredTime = time + return this + } + + markNotRestored(message: string): CacheEntryListener { + this.notRestored = message + return this + } + + markSaved(key: string, size: number | undefined, time: number): CacheEntryListener { + this.savedKey = key + this.savedSize = size + this.savedTime = time + return this + } + + markAlreadyExists(key: string): CacheEntryListener { + this.savedKey = key + this.savedSize = 0 + return this + } + + markNotSaved(message: string): CacheEntryListener { + this.notSaved = message + return this + } +} + +export function generateCachingReport(listener: CacheListener): string { + const entries = listener.cacheEntries + + return ` +
+

Caching for Gradle actions was ${listener.cacheStatus} - expand for details

+ +- ${listener.cacheStatusReason} +- ${listener.cacheCleanupMessage} + +${renderEntryTable(entries)} + +
Cache Entry Details
+
+    ${renderEntryDetails(listener)}
+
+
+ ` +} + +function renderEntryTable(entries: CacheEntryListener[]): string { + return ` + + + + + + + + + + + + +
CountTotal Size (Mb)Total Time (ms)
Entries Restored${getCount(entries, e => e.restoredSize)}${getSize(entries, e => e.restoredSize)}${getTime(entries, e => e.restoredTime)}
Entries Saved${getCount(entries, e => e.savedSize)}${getSize(entries, e => e.savedSize)}${getTime(entries, e => e.savedTime)}
+ ` +} + +function renderEntryDetails(listener: CacheListener): string { + return listener.cacheEntries + .map( + entry => `Entry: ${entry.entryName} + Requested Key : ${entry.requestedKey ?? ''} + Restored Key : ${entry.restoredKey ?? ''} + Size: ${formatSize(entry.restoredSize)} + Time: ${formatTime(entry.restoredTime)} + ${getRestoredMessage(entry, listener.cacheWriteOnly)} + Saved Key : ${entry.savedKey ?? ''} + Size: ${formatSize(entry.savedSize)} + Time: ${formatTime(entry.savedTime)} + ${getSavedMessage(entry, listener.cacheReadOnly)} +` + ) + .join('---\n') +} + +function getRestoredMessage(entry: CacheEntryListener, cacheWriteOnly: boolean): string { + if (entry.notRestored) { + return `(Entry not restored: ${entry.notRestored})` + } + if (cacheWriteOnly) { + return '(Entry not restored: cache is write-only)' + } + if (entry.requestedKey === undefined) { + return '(Entry not restored: not requested)' + } + if (entry.restoredKey === undefined) { + return '(Entry not restored: no match found)' + } + if (entry.restoredKey === entry.requestedKey) { + return '(Entry restored: exact match found)' + } + return '(Entry restored: partial match found)' +} + +function getSavedMessage(entry: CacheEntryListener, cacheReadOnly: boolean): string { + if (entry.notSaved) { + return `(Entry not saved: ${entry.notSaved})` + } + if (entry.savedKey === undefined) { + if (cacheReadOnly) { + return '(Entry not saved: cache is read-only)' + } + if (entry.notRestored) { + return '(Entry not saved: not restored)' + } + return '(Entry not saved: reason unknown)' + } + if (entry.savedSize === 0) { + return '(Entry not saved: entry with key already exists)' + } + return '(Entry saved)' +} + +function getCount( + cacheEntries: CacheEntryListener[], + predicate: (value: CacheEntryListener) => number | undefined +): number { + return cacheEntries.filter(e => predicate(e)).length +} + +function getSize( + cacheEntries: CacheEntryListener[], + predicate: (value: CacheEntryListener) => number | undefined +): number { + const bytes = cacheEntries.map(e => predicate(e) ?? 0).reduce((p, v) => p + v, 0) + return Math.round(bytes / (1024 * 1024)) +} + +function getTime( + cacheEntries: CacheEntryListener[], + predicate: (value: CacheEntryListener) => number | undefined +): number { + return cacheEntries.map(e => predicate(e) ?? 0).reduce((p, v) => p + v, 0) +} + +function formatSize(bytes: number | undefined): string { + if (bytes === undefined || bytes === 0) { + return '' + } + return `${Math.round(bytes / (1024 * 1024))} MB (${bytes} B)` +} + +function formatTime(ms: number | undefined): string { + if (ms === undefined || ms === 0) { + return '' + } + return `${ms} ms` +} diff --git a/sources/legacy-caching/src/cache-utils.ts b/sources/legacy-caching/src/cache-utils.ts new file mode 100644 index 00000000..3d5e3b3d --- /dev/null +++ b/sources/legacy-caching/src/cache-utils.ts @@ -0,0 +1,140 @@ +import * as core from '@actions/core' +import * as cache from '@actions/cache' +import * as exec from '@actions/exec' + +import * as crypto from 'crypto' +import * as path from 'path' +import * as fs from 'fs' + +import {CacheEntryListener} from './cache-reporting' + +const SEGMENT_DOWNLOAD_TIMEOUT_VAR = 'SEGMENT_DOWNLOAD_TIMEOUT_MINS' +const SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT = 10 * 60 * 1000 // 10 minutes + +export function isCacheDebuggingEnabled(): boolean { + if (core.isDebug()) { + return true + } + return process.env['GRADLE_BUILD_ACTION_CACHE_DEBUG_ENABLED'] ? true : false +} + +export function hashFileNames(fileNames: string[]): string { + return hashStrings(fileNames.map(x => x.replace(new RegExp(`\\${path.sep}`, 'g'), '/'))) +} + +export function hashStrings(values: string[]): string { + const hash = crypto.createHash('md5') + for (const value of values) { + hash.update(value) + } + return hash.digest('hex') +} + +export async function restoreCache( + cachePath: string[], + cacheKey: string, + cacheRestoreKeys: string[], + listener: CacheEntryListener +): Promise { + listener.markRequested(cacheKey, cacheRestoreKeys) + try { + const startTime = Date.now() + // Only override the read timeout if the SEGMENT_DOWNLOAD_TIMEOUT_MINS env var has NOT been set + const cacheRestoreOptions = process.env[SEGMENT_DOWNLOAD_TIMEOUT_VAR] + ? {} + : {segmentTimeoutInMs: SEGMENT_DOWNLOAD_TIMEOUT_DEFAULT} + const restoredEntry = await cache.restoreCache(cachePath, cacheKey, cacheRestoreKeys, cacheRestoreOptions) + if (restoredEntry !== undefined) { + const restoreTime = Date.now() - startTime + listener.markRestored(restoredEntry.key, restoredEntry.size, restoreTime) + core.info(`Restored cache entry with key ${cacheKey} to ${cachePath.join()} in ${restoreTime}ms`) + } + return restoredEntry + } catch (error) { + listener.markNotRestored((error as Error).message) + handleCacheFailure(error, `Failed to restore ${cacheKey}`) + return undefined + } +} + +export async function saveCache(cachePath: string[], cacheKey: string, listener: CacheEntryListener): Promise { + try { + const startTime = Date.now() + const savedEntry = await cache.saveCache(cachePath, cacheKey) + const saveTime = Date.now() - startTime + listener.markSaved(savedEntry.key, savedEntry.size, saveTime) + core.info(`Saved cache entry with key ${cacheKey} from ${cachePath.join()} in ${saveTime}ms`) + } catch (error) { + if (error instanceof cache.ReserveCacheError) { + listener.markAlreadyExists(cacheKey) + } else { + listener.markNotSaved((error as Error).message) + } + handleCacheFailure(error, `Failed to save cache entry with path '${cachePath}' and key: ${cacheKey}`) + } +} + +export function cacheDebug(message: string): void { + if (isCacheDebuggingEnabled()) { + core.info(message) + } else { + core.debug(message) + } +} + +export function handleCacheFailure(error: unknown, message: string): void { + if (error instanceof cache.ValidationError) { + // Fail on cache validation errors + throw error + } + if (error instanceof cache.ReserveCacheError) { + // Reserve cache errors are expected if the artifact has been previously cached + core.info(`${message}: ${error}`) + } else { + // Warn on all other errors + core.warning(`${message}: ${error}`) + if (error instanceof Error && error.stack) { + cacheDebug(error.stack) + } + } +} + +/** + * Attempt to delete a file or directory, waiting to allow locks to be released + */ +export async function tryDelete(file: string): Promise { + const maxAttempts = 5 + for (let attempt = 1; attempt <= maxAttempts; attempt++) { + if (!fs.existsSync(file)) { + return + } + try { + const stat = fs.lstatSync(file) + if (stat.isDirectory()) { + fs.rmSync(file, {recursive: true}) + } else { + fs.unlinkSync(file) + } + return + } catch (error) { + if (attempt === maxAttempts) { + core.warning(`Failed to delete ${file}, which will impact caching. +It is likely locked by another process. Output of 'jps -ml': +${await getJavaProcesses()}`) + throw error + } else { + cacheDebug(`Attempt to delete ${file} failed. Will try again.`) + await delay(1000) + } + } + } +} + +async function delay(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)) +} + +async function getJavaProcesses(): Promise { + const jpsOutput = await exec.getExecOutput('jps', ['-lm']) + return jpsOutput.stdout +} diff --git a/sources/legacy-caching/src/caches.ts b/sources/legacy-caching/src/caches.ts new file mode 100644 index 00000000..ccf80281 --- /dev/null +++ b/sources/legacy-caching/src/caches.ts @@ -0,0 +1,124 @@ +import * as core from '@actions/core' +import { + CacheListener, + EXISTING_GRADLE_HOME, + CLEANUP_DISABLED_DUE_TO_FAILURE, + CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT +} from './cache-reporting' +import {GradleUserHomeCache} from './gradle-user-home-cache' +import {CacheCleaner} from './cache-cleaner' +import {DaemonController} from './daemon-controller' +import {CacheConfig} from './cache-config-adapter' +import {BuildResults} from './build-results-adapter' + +const CACHE_RESTORED_VAR = 'GRADLE_BUILD_ACTION_CACHE_RESTORED' + +export async function restore( + userHome: string, + gradleUserHome: string, + cacheListener: CacheListener, + cacheConfig: CacheConfig +): Promise { + // Bypass restore cache on all but first action step in workflow. + if (process.env[CACHE_RESTORED_VAR]) { + core.info('Cache only restored on first action step.') + return + } + core.exportVariable(CACHE_RESTORED_VAR, true) + + const gradleStateCache = new GradleUserHomeCache(userHome, gradleUserHome, cacheConfig) + + if (cacheConfig.isCacheDisabled()) { + core.info('Cache is disabled: will not restore state from previous builds.') + // Initialize the Gradle User Home even when caching is disabled. + gradleStateCache.init() + cacheListener.setDisabled() + return + } + + if (gradleStateCache.cacheOutputExists()) { + if (!cacheConfig.isCacheOverwriteExisting()) { + core.info('Gradle User Home already exists: will not restore from cache.') + // Initialize pre-existing Gradle User Home. + gradleStateCache.init() + cacheListener.setDisabled(EXISTING_GRADLE_HOME) + return + } + core.info('Gradle User Home already exists: will overwrite with cached contents.') + } + + gradleStateCache.init() + // Mark the state as restored so that post-action will perform save. + core.saveState(CACHE_RESTORED_VAR, true) + + if (cacheConfig.isCacheCleanupEnabled()) { + core.info('Preparing cache for cleanup.') + const cacheCleaner = new CacheCleaner(gradleUserHome, process.env['RUNNER_TEMP']!) + await cacheCleaner.prepare() + } + + if (cacheConfig.isCacheWriteOnly()) { + core.info('Cache is write-only: will not restore from cache.') + cacheListener.setWriteOnly() + return + } + + await core.group('Restore Gradle state from cache', async () => { + await gradleStateCache.restore(cacheListener) + }) +} + +export async function save( + userHome: string, + gradleUserHome: string, + cacheListener: CacheListener, + daemonController: DaemonController, + buildResults: BuildResults, + cacheConfig: CacheConfig +): Promise { + if (cacheConfig.isCacheDisabled()) { + core.info('Cache is disabled: will not save state for later builds.') + return + } + + if (!core.getState(CACHE_RESTORED_VAR)) { + core.info('Cache will not be saved: not restored in main action step.') + return + } + + if (cacheConfig.isCacheReadOnly()) { + core.info('Cache is read-only: will not save state for use in subsequent builds.') + cacheListener.setReadOnly() + return + } + + await core.group('Stopping Gradle daemons', async () => { + await daemonController.stopAllDaemons() + }) + + if (cacheConfig.isCacheCleanupEnabled()) { + if (buildResults.anyConfigCacheHit()) { + core.info('Not performing cache-cleanup due to config-cache reuse') + cacheListener.setCacheCleanupDisabled(CLEANUP_DISABLED_DUE_TO_CONFIG_CACHE_HIT) + } else if (cacheConfig.shouldPerformCacheCleanup(buildResults.anyFailed())) { + cacheListener.setCacheCleanupEnabled() + await performCacheCleanup(gradleUserHome, buildResults) + } else { + core.info('Not performing cache-cleanup due to build failure') + cacheListener.setCacheCleanupDisabled(CLEANUP_DISABLED_DUE_TO_FAILURE) + } + } + + await core.group('Caching Gradle state', async () => { + return new GradleUserHomeCache(userHome, gradleUserHome, cacheConfig).save(cacheListener) + }) +} + +async function performCacheCleanup(gradleUserHome: string, buildResults: BuildResults): Promise { + const cacheCleaner = new CacheCleaner(gradleUserHome, process.env['RUNNER_TEMP']!) + try { + await cacheCleaner.forceCleanup(buildResults) + } catch (e) { + core.warning(`Cache cleanup failed. Will continue. ${String(e)}`) + } +} diff --git a/sources/legacy-caching/src/daemon-controller.ts b/sources/legacy-caching/src/daemon-controller.ts new file mode 100644 index 00000000..ccc0666c --- /dev/null +++ b/sources/legacy-caching/src/daemon-controller.ts @@ -0,0 +1,33 @@ +import * as core from '@actions/core' +import * as exec from '@actions/exec' +import * as fs from 'fs' +import * as path from 'path' +import {BuildResults} from './build-results-adapter' + +export class DaemonController { + private readonly gradleHomes: string[] + + constructor(buildResults: BuildResults) { + this.gradleHomes = buildResults.uniqueGradleHomes() + } + + async stopAllDaemons(): Promise { + const executions: Promise[] = [] + const args = ['--stop'] + + for (const gradleHome of this.gradleHomes) { + const executable = path.resolve(gradleHome, 'bin', 'gradle') + if (!fs.existsSync(executable)) { + core.warning(`Gradle executable not found at ${executable}. Could not stop Gradle daemons.`) + continue + } + core.info(`Stopping Gradle daemons for ${gradleHome}`) + executions.push( + exec.exec(executable, args, { + ignoreReturnCode: true + }) + ) + } + await Promise.all(executions) + } +} diff --git a/sources/legacy-caching/src/gradle-home-extry-extractor.ts b/sources/legacy-caching/src/gradle-home-extry-extractor.ts new file mode 100644 index 00000000..82cdad45 --- /dev/null +++ b/sources/legacy-caching/src/gradle-home-extry-extractor.ts @@ -0,0 +1,353 @@ +import path from 'path' +import fs from 'fs' +import * as core from '@actions/core' +import * as glob from '@actions/glob' + +import {CacheEntryListener, CacheListener} from './cache-reporting' +import {cacheDebug, hashFileNames, isCacheDebuggingEnabled, restoreCache, saveCache, tryDelete} from './cache-utils' + +import {CacheConfig, ACTION_METADATA_DIR} from './cache-config-adapter' +import {getCacheKeyBase} from './cache-key' + +const SKIP_RESTORE_VAR = 'GRADLE_BUILD_ACTION_SKIP_RESTORE' +const CACHE_PROTOCOL_VERSION = 'v1' + +/** + * Represents the result of attempting to load or store an extracted cache entry. + * An undefined cacheKey indicates that the operation did not succeed. + * The collected results are then used to populate the `cache-metadata.json` file for later use. + */ +class ExtractedCacheEntry { + artifactType: string + pattern: string + cacheKey: string | undefined + + constructor(artifactType: string, pattern: string, cacheKey: string | undefined) { + this.artifactType = artifactType + this.pattern = pattern + this.cacheKey = cacheKey + } +} + +/** + * Representation of all of the extracted cache entries for this Gradle User Home. + * This object is persisted to JSON file in the Gradle User Home directory for storing, + * and subsequently used to restore the Gradle User Home. + */ +class ExtractedCacheEntryMetadata { + entries: ExtractedCacheEntry[] = [] +} + +/** + * The specification for a type of extracted cache entry. + */ +class ExtractedCacheEntryDefinition { + artifactType: string + pattern: string + bundle: boolean + uniqueFileNames = true + notCacheableReason: string | undefined + + constructor(artifactType: string, pattern: string, bundle: boolean) { + this.artifactType = artifactType + this.pattern = pattern + this.bundle = bundle + } + + /** + * Indicate that the file names matching the cache entry pattern are NOT sufficient to uniquely identify the contents. + * If the file names are sufficient, then we use a hash of the file names to identify the entry. + * With non-unique-file-names, we hash the file contents to identify the cache entry. + */ + withNonUniqueFileNames(): ExtractedCacheEntryDefinition { + this.uniqueFileNames = false + return this + } +} + +/** + * Caches and restores the entire Gradle User Home directory, extracting entries containing common artifacts + * for more efficient storage. + */ +abstract class AbstractEntryExtractor { + protected readonly cacheConfig: CacheConfig + protected readonly gradleUserHome: string + private extractorName: string + + constructor(gradleUserHome: string, extractorName: string, cacheConfig: CacheConfig) { + this.gradleUserHome = gradleUserHome + this.extractorName = extractorName + this.cacheConfig = cacheConfig + } + + /** + * Restores any artifacts that were cached separately, based on the information in the `cache-metadata.json` file. + * Each extracted cache entry is restored in parallel, except when debugging is enabled. + */ + async restore(listener: CacheListener): Promise { + const previouslyExtractedCacheEntries = this.loadExtractedCacheEntries() + + const processes: Promise[] = [] + + for (const cacheEntry of previouslyExtractedCacheEntries) { + const artifactType = cacheEntry.artifactType + const entryListener = listener.entry(cacheEntry.pattern) + + // Handle case where the extracted-cache-entry definitions have been changed + const skipRestore = process.env[SKIP_RESTORE_VAR] || '' + if (skipRestore.includes(artifactType)) { + core.info(`Not restoring extracted cache entry for ${artifactType}`) + entryListener.markRequested('SKIP_RESTORE') + } else { + processes.push( + this.awaitForDebugging( + this.restoreExtractedCacheEntry( + artifactType, + cacheEntry.cacheKey!, + cacheEntry.pattern, + entryListener + ) + ) + ) + } + } + + this.saveMetadataForCacheResults(await Promise.all(processes)) + } + + private async restoreExtractedCacheEntry( + artifactType: string, + cacheKey: string, + pattern: string, + listener: CacheEntryListener + ): Promise { + const restoredEntry = await restoreCache(pattern.split('\n'), cacheKey, [], listener) + if (restoredEntry) { + return new ExtractedCacheEntry(artifactType, pattern, cacheKey) + } else { + core.info(`Did not restore ${artifactType} with key ${cacheKey} to ${pattern}`) + return new ExtractedCacheEntry(artifactType, pattern, undefined) + } + } + + /** + * Saves any artifacts that are configured to be cached separately, based on the extracted cache entry definitions. + * Each entry is extracted and saved in parallel, except when debugging is enabled. + */ + async extract(listener: CacheListener): Promise { + // Load the cache entry definitions (from config) and the previously restored entries (from persisted metadata file) + const cacheEntryDefinitions = this.getExtractedCacheEntryDefinitions() + cacheDebug( + `Extracting cache entries for ${this.extractorName}: ${JSON.stringify(cacheEntryDefinitions, null, 2)}` + ) + + const previouslyRestoredEntries = this.loadExtractedCacheEntries() + const cacheActions: Promise[] = [] + + // For each cache entry definition, determine if it has already been restored, and if not, extract it + for (const cacheEntryDefinition of cacheEntryDefinitions) { + const artifactType = cacheEntryDefinition.artifactType + const pattern = cacheEntryDefinition.pattern + + if (cacheEntryDefinition.notCacheableReason) { + listener.entry(pattern).markNotSaved(cacheEntryDefinition.notCacheableReason) + continue + } + + // Find all matching files for this cache entry definition + const globber = await glob.create(pattern, { + implicitDescendants: false + }) + const matchingFiles = await globber.glob() + + if (matchingFiles.length === 0) { + cacheDebug(`No files found to cache for ${artifactType}`) + continue + } + + if (cacheEntryDefinition.bundle) { + // For an extracted "bundle", use the defined pattern and cache all matching files in a single entry. + cacheActions.push( + this.awaitForDebugging( + this.saveExtractedCacheEntry( + matchingFiles, + artifactType, + pattern, + cacheEntryDefinition.uniqueFileNames, + previouslyRestoredEntries, + listener.entry(pattern) + ) + ) + ) + } else { + // Otherwise cache each matching file in a separate entry, using the complete file path as the cache pattern. + for (const cacheFile of matchingFiles) { + cacheActions.push( + this.awaitForDebugging( + this.saveExtractedCacheEntry( + [cacheFile], + artifactType, + cacheFile, + cacheEntryDefinition.uniqueFileNames, + previouslyRestoredEntries, + listener.entry(cacheFile) + ) + ) + ) + } + } + } + + this.saveMetadataForCacheResults(await Promise.all(cacheActions)) + } + + private async saveExtractedCacheEntry( + matchingFiles: string[], + artifactType: string, + pattern: string, + uniqueFileNames: boolean, + previouslyRestoredEntries: ExtractedCacheEntry[], + entryListener: CacheEntryListener + ): Promise { + const cacheKey = uniqueFileNames + ? this.createCacheKeyFromFileNames(artifactType, matchingFiles) + : await this.createCacheKeyFromFileContents(artifactType, pattern) + const previouslyRestoredKey = previouslyRestoredEntries.find( + x => x.artifactType === artifactType && x.pattern === pattern + )?.cacheKey + + if (previouslyRestoredKey === cacheKey) { + cacheDebug(`No change to previously restored ${artifactType}. Not saving.`) + entryListener.markNotSaved('contents unchanged') + } else { + await saveCache(pattern.split('\n'), cacheKey, entryListener) + } + + for (const file of matchingFiles) { + tryDelete(file) + } + + return new ExtractedCacheEntry(artifactType, pattern, cacheKey) + } + + protected createCacheKeyFromFileNames(artifactType: string, files: string[]): string { + const relativeFiles = files.map(x => path.relative(this.gradleUserHome, x)) + const key = hashFileNames(relativeFiles) + + cacheDebug(`Generating cache key for ${artifactType} from file names: ${relativeFiles}`) + + return `${getCacheKeyBase(artifactType, CACHE_PROTOCOL_VERSION)}-${key}` + } + + protected async createCacheKeyFromFileContents(artifactType: string, pattern: string): Promise { + const key = await glob.hashFiles(pattern) + + cacheDebug(`Generating cache key for ${artifactType} from files matching: ${pattern}`) + + return `${getCacheKeyBase(artifactType, CACHE_PROTOCOL_VERSION)}-${key}` + } + + // Run actions sequentially if debugging is enabled + private async awaitForDebugging(p: Promise): Promise { + if (isCacheDebuggingEnabled()) { + await p + } + return p + } + + /** + * Load information about the extracted cache entries previously restored/saved. This is loaded from the 'cache-metadata.json' file. + */ + protected loadExtractedCacheEntries(): ExtractedCacheEntry[] { + const cacheMetadataFile = this.getCacheMetadataFile() + if (!fs.existsSync(cacheMetadataFile)) { + return [] + } + + const filedata = fs.readFileSync(cacheMetadataFile, 'utf-8') + cacheDebug(`Loaded cache metadata for ${this.extractorName}: ${filedata}`) + const extractedCacheEntryMetadata = JSON.parse(filedata) as ExtractedCacheEntryMetadata + return extractedCacheEntryMetadata.entries + } + + /** + * Saves information about the extracted cache entries into the 'cache-metadata.json' file. + */ + protected saveMetadataForCacheResults(results: ExtractedCacheEntry[]): void { + const extractedCacheEntryMetadata = new ExtractedCacheEntryMetadata() + extractedCacheEntryMetadata.entries = results.filter(x => x.cacheKey !== undefined) + + const filedata = JSON.stringify(extractedCacheEntryMetadata) + cacheDebug(`Saving cache metadata for ${this.extractorName}: ${filedata}`) + + fs.writeFileSync(this.getCacheMetadataFile(), filedata, 'utf-8') + } + + private getCacheMetadataFile(): string { + const actionMetadataDirectory = path.resolve(this.gradleUserHome, ACTION_METADATA_DIR) + fs.mkdirSync(actionMetadataDirectory, {recursive: true}) + + return path.resolve(actionMetadataDirectory, `${this.extractorName}-entry-metadata.json`) + } + + protected abstract getExtractedCacheEntryDefinitions(): ExtractedCacheEntryDefinition[] +} + +export class GradleHomeEntryExtractor extends AbstractEntryExtractor { + constructor(gradleUserHome: string, cacheConfig: CacheConfig) { + super(gradleUserHome, 'gradle-home', cacheConfig) + } + + async extract(listener: CacheListener): Promise { + await this.deleteWrapperZips() + return super.extract(listener) + } + + /** + * Delete any downloaded wrapper zip files that are not needed after extraction. + * These files are cleaned up by Gradle >= 7.5, but for older versions we remove them manually. + */ + private async deleteWrapperZips(): Promise { + const wrapperZips = path.resolve(this.gradleUserHome, 'wrapper/dists/*/*/*.zip') + const globber = await glob.create(wrapperZips, { + implicitDescendants: false + }) + + for (const wrapperZip of await globber.glob()) { + cacheDebug(`Deleting wrapper zip: ${wrapperZip}`) + await tryDelete(wrapperZip) + } + } + + /** + * Return the extracted cache entry definitions, which determine which artifacts will be cached + * separately from the rest of the Gradle User Home cache entry. + */ + protected getExtractedCacheEntryDefinitions(): ExtractedCacheEntryDefinition[] { + const entryDefinition = ( + artifactType: string, + patterns: string[], + bundle: boolean + ): ExtractedCacheEntryDefinition => { + const resolvedPatterns = patterns + .map(x => { + const isDir = x.endsWith('/') + const resolved = path.resolve(this.gradleUserHome, x) + return isDir ? `${resolved}/` : resolved // Restore trailing '/' removed by path.resolve() + }) + .join('\n') + return new ExtractedCacheEntryDefinition(artifactType, resolvedPatterns, bundle) + } + + return [ + entryDefinition('generated-gradle-jars', ['caches/*/generated-gradle-jars/*.jar'], false), + entryDefinition('wrapper-zips', ['wrapper/dists/*/*/'], false), // Each wrapper directory cached separately + entryDefinition('java-toolchains', ['jdks/*/'], false), // Each extracted JDK cached separately + entryDefinition('dependencies', ['caches/modules-*/files-*/*/*/*/*'], true), + entryDefinition('instrumented-jars', ['caches/jars-*/*/'], true), + entryDefinition('kotlin-dsl', ['caches/*/kotlin-dsl/accessors/*/', 'caches/*/kotlin-dsl/scripts/*/'], true), + entryDefinition('groovy-dsl', ['caches/*/groovy-dsl/*/'], true), + entryDefinition('transforms', ['caches/transforms-4/*/', 'caches/*/transforms/*/'], true) + ] + } +} diff --git a/sources/legacy-caching/src/gradle-user-home-cache.ts b/sources/legacy-caching/src/gradle-user-home-cache.ts new file mode 100644 index 00000000..9b0615b2 --- /dev/null +++ b/sources/legacy-caching/src/gradle-user-home-cache.ts @@ -0,0 +1,222 @@ +import * as core from '@actions/core' +import * as exec from '@actions/exec' +import * as glob from '@actions/glob' + +import path from 'path' +import fs from 'fs' +import {generateCacheKey} from './cache-key' +import {CacheListener} from './cache-reporting' +import {saveCache, restoreCache, cacheDebug, isCacheDebuggingEnabled, tryDelete} from './cache-utils' +import {CacheConfig, ACTION_METADATA_DIR} from './cache-config-adapter' +import {GradleHomeEntryExtractor} from './gradle-home-extry-extractor' + +const RESTORED_CACHE_KEY_KEY = 'restored-cache-key' + +export class GradleUserHomeCache { + private readonly cacheName = 'home' + private readonly cacheDescription = 'Gradle User Home' + + private readonly userHome: string + private readonly gradleUserHome: string + private readonly cacheConfig: CacheConfig + + constructor(userHome: string, gradleUserHome: string, cacheConfig: CacheConfig) { + this.userHome = userHome + this.gradleUserHome = gradleUserHome + this.cacheConfig = cacheConfig + } + + init(): void { + this.initializeGradleUserHome() + + // Export the GRADLE_ENCRYPTION_KEY variable if provided + const encryptionKey = this.cacheConfig.getCacheEncryptionKey() + if (encryptionKey) { + core.exportVariable('GRADLE_ENCRYPTION_KEY', encryptionKey) + } + } + + cacheOutputExists(): boolean { + const cachesDir = path.resolve(this.gradleUserHome, 'caches') + if (fs.existsSync(cachesDir)) { + cacheDebug(`Cache output exists at ${cachesDir}`) + return true + } + return false + } + + /** + * Restores the cache entry, finding the closest match to the currently running job. + */ + async restore(listener: CacheListener): Promise { + const entryListener = listener.entry(this.cacheDescription) + + const cacheKey = generateCacheKey(this.cacheName, this.cacheConfig) + + cacheDebug( + `Requesting ${this.cacheDescription} with + key:${cacheKey.key} + restoreKeys:[${cacheKey.restoreKeys}]` + ) + + const cachePath = this.getCachePath() + const cacheResult = await restoreCache(cachePath, cacheKey.key, cacheKey.restoreKeys, entryListener) + if (!cacheResult) { + core.info(`${this.cacheDescription} cache not found. Will initialize empty.`) + return + } + + core.saveState(RESTORED_CACHE_KEY_KEY, cacheResult.key) + + try { + await this.afterRestore(listener) + } catch (error) { + core.warning(`Restore ${this.cacheDescription} failed in 'afterRestore': ${error}`) + } + } + + /** + * Restore any extracted cache entries after the main Gradle User Home entry is restored. + */ + async afterRestore(listener: CacheListener): Promise { + await this.debugReportGradleUserHomeSize('as restored from cache') + await new GradleHomeEntryExtractor(this.gradleUserHome, this.cacheConfig).restore(listener) + await this.deleteExcludedPaths() + await this.debugReportGradleUserHomeSize('after restoring common artifacts') + } + + /** + * Saves the cache entry based on the current cache key unless the cache was restored with the exact key, + * in which case we cannot overwrite it. + * + * If the cache entry was restored with a partial match on a restore key, then + * it is saved with the exact key. + */ + async save(listener: CacheListener): Promise { + const cacheKey = generateCacheKey(this.cacheName, this.cacheConfig).key + const restoredCacheKey = core.getState(RESTORED_CACHE_KEY_KEY) + const gradleHomeEntryListener = listener.entry(this.cacheDescription) + + if (restoredCacheKey && cacheKey === restoredCacheKey) { + core.info(`Cache hit occurred on the cache key ${cacheKey}, not saving cache.`) + + for (const entryListener of listener.cacheEntries) { + if (entryListener === gradleHomeEntryListener) { + entryListener.markNotSaved('cache key not changed') + } else { + entryListener.markNotSaved(`referencing '${this.cacheDescription}' cache entry not saved`) + } + } + return + } + + try { + await this.beforeSave(listener) + } catch (error) { + core.warning(`Save ${this.cacheDescription} failed in 'beforeSave': ${error}`) + return + } + + const cachePath = this.getCachePath() + await saveCache(cachePath, cacheKey, gradleHomeEntryListener) + return + } + + /** + * Extract and save any defined extracted cache entries prior to the main Gradle User Home entry being saved. + */ + async beforeSave(listener: CacheListener): Promise { + await this.debugReportGradleUserHomeSize('before saving common artifacts') + await this.deleteExcludedPaths() + await new GradleHomeEntryExtractor(this.gradleUserHome, this.cacheConfig).extract(listener) + await this.debugReportGradleUserHomeSize( + "after extracting common artifacts (only 'caches' and 'notifications' will be stored)" + ) + } + + /** + * Delete any file paths that are excluded by the `gradle-home-cache-excludes` parameter. + */ + private async deleteExcludedPaths(): Promise { + const rawPaths: string[] = this.cacheConfig.getCacheExcludes() + rawPaths.push('caches/*/cc-keystore') + const resolvedPaths = rawPaths.map(x => path.resolve(this.gradleUserHome, x)) + + for (const p of resolvedPaths) { + cacheDebug(`Removing excluded path: ${p}`) + const globber = await glob.create(p, { + implicitDescendants: false + }) + + for (const toDelete of await globber.glob()) { + cacheDebug(`Removing excluded file: ${toDelete}`) + await tryDelete(toDelete) + } + } + } + + /** + * Determines the paths within Gradle User Home to cache. + * By default, this is the 'caches' and 'notifications' directories, + * but this can be overridden by the `gradle-home-cache-includes` parameter. + */ + protected getCachePath(): string[] { + const rawPaths: string[] = this.cacheConfig.getCacheIncludes() + rawPaths.push(ACTION_METADATA_DIR) + const resolvedPaths = rawPaths.map(x => this.resolveCachePath(x)) + cacheDebug(`Using cache paths: ${resolvedPaths}`) + return resolvedPaths + } + + private resolveCachePath(rawPath: string): string { + if (rawPath.startsWith('!')) { + const resolved = this.resolveCachePath(rawPath.substring(1)) + return `!${resolved}` + } + return path.resolve(this.gradleUserHome, rawPath) + } + + /** + * Initialize the Gradle User Home directory for caching. + * Note: init scripts, toolchain registration, and debug log level are handled by + * initializeGradleUserHome() in the main package before cacheService.restore() is called. + */ + private initializeGradleUserHome(): void { + // Create a directory for storing action metadata + const actionCacheDir = path.resolve(this.gradleUserHome, ACTION_METADATA_DIR) + fs.mkdirSync(actionCacheDir, {recursive: true}) + } + + /** + * When cache debugging is enabled (or ACTIONS_STEP_DEBUG is on), + * this method will give a detailed report of the Gradle User Home contents. + */ + private async debugReportGradleUserHomeSize(label: string): Promise { + if (!isCacheDebuggingEnabled() && !core.isDebug()) { + return + } + if (!fs.existsSync(this.gradleUserHome)) { + return + } + const result = await exec.getExecOutput('du', ['-h', '-c', '-t', '5M'], { + cwd: this.gradleUserHome, + silent: true, + ignoreReturnCode: true + }) + + core.info(`Gradle User Home (directories >5M): ${label}`) + + core.info( + result.stdout + .trimEnd() + .replace(/\t/g, ' ') + .split('\n') + .map(it => { + return ` ${it}` + }) + .join('\n') + ) + + core.info('-----------------------') + } +} diff --git a/sources/legacy-caching/src/gradle-utils.ts b/sources/legacy-caching/src/gradle-utils.ts new file mode 100644 index 00000000..b0bff1a7 --- /dev/null +++ b/sources/legacy-caching/src/gradle-utils.ts @@ -0,0 +1,105 @@ +import * as core from '@actions/core' +import * as path from 'path' +import * as fs from 'fs' +import * as semver from 'semver' +import {BuildResults} from './build-results-adapter' + +const IS_WINDOWS = process.platform === 'win32' + +class GradleVersion { + static PATTERN = /((\d+)(\.\d+)+)(-([a-z]+)-(\w+))?(-(SNAPSHOT|\d{14}([-+]\d{4})?))?/ + + versionPart: string + stagePart: string + snapshotPart: string + + constructor(readonly version: string) { + const matcher = GradleVersion.PATTERN.exec(version) + if (!matcher) { + throw new Error(`'${version}' is not a valid Gradle version string (examples: '1.0', '1.0-rc-1')`) + } + + this.versionPart = matcher[1] + this.stagePart = matcher[4] + this.snapshotPart = matcher[7] + } +} + +export function versionIsAtLeast(actualVersion: string, requiredVersion: string): boolean { + if (actualVersion === requiredVersion) { + return true + } + + const actual = new GradleVersion(actualVersion) + const required = new GradleVersion(requiredVersion) + + const actualSemver = semver.coerce(actual.versionPart)! + const comparisonSemver = semver.coerce(required.versionPart)! + + if (semver.gt(actualSemver, comparisonSemver)) { + return true + } + if (semver.lt(actualSemver, comparisonSemver)) { + return false + } + + if (actual.snapshotPart || required.snapshotPart) { + if (actual.snapshotPart && !required.snapshotPart && !required.stagePart) { + return false + } + if (required.snapshotPart && !actual.snapshotPart && !actual.stagePart) { + return true + } + return false + } + + if (actual.stagePart) { + if (required.stagePart) { + return actual.stagePart >= required.stagePart + } + return false + } + + return true +} + +function wrapperScriptFilename(): string { + return IS_WINDOWS ? 'gradlew.bat' : 'gradlew' +} + +/** + * Attempts to find a Gradle wrapper script from build results that has Gradle >= 8.11. + * Returns the full path to the wrapper script, or null if none found. + */ +export function findGradleExecutableForCleanup(buildResults: BuildResults): string | null { + const preferredVersion = buildResults.highestGradleVersion() + if (!preferredVersion || !versionIsAtLeast(preferredVersion, '8.11')) { + core.info( + `No Gradle version >= 8.11 found in build results (highest: ${preferredVersion ?? 'none'}). Cache cleanup will be skipped.` + ) + return null + } + + // Find a build result with the highest version that has a wrapper script + for (const result of buildResults.results) { + if (versionIsAtLeast(result.gradleVersion, '8.11')) { + const wrapperScript = path.resolve(result.rootProjectDir, wrapperScriptFilename()) + if (fs.existsSync(wrapperScript)) { + return wrapperScript + } + } + } + + // Try the Gradle installation directory as a fallback + for (const result of buildResults.results) { + if (versionIsAtLeast(result.gradleVersion, '8.11')) { + const executable = path.resolve(result.gradleHomeDir, 'bin', IS_WINDOWS ? 'gradle.bat' : 'gradle') + if (fs.existsSync(executable)) { + return executable + } + } + } + + core.info('Could not locate a Gradle >= 8.11 executable for cache cleanup.') + return null +} diff --git a/sources/legacy-caching/src/index.ts b/sources/legacy-caching/src/index.ts new file mode 100644 index 00000000..45755ddd --- /dev/null +++ b/sources/legacy-caching/src/index.ts @@ -0,0 +1,41 @@ +import * as core from '@actions/core' +import * as os from 'os' + +import {CacheConfig, CacheOptions} from './cache-config-adapter' +import {BuildResult, BuildResults} from './build-results-adapter' +import {CacheListener, generateCachingReport} from './cache-reporting' +import {DaemonController} from './daemon-controller' +import * as caches from './caches' + +const CACHE_LISTENER_STATE_KEY = 'legacy-cache-listener' + +export async function restore(gradleUserHome: string, cacheOptions: CacheOptions): Promise { + const userHome = os.homedir() + const cacheListener = new CacheListener() + const cacheConfig = new CacheConfig(cacheOptions) + + await caches.restore(userHome, gradleUserHome, cacheListener, cacheConfig) + + // Persist the listener so it can be rehydrated in save() + core.saveState(CACHE_LISTENER_STATE_KEY, cacheListener.stringify()) +} + +export async function save( + gradleUserHome: string, + buildResults: BuildResult[], + cacheOptions: CacheOptions +): Promise { + const userHome = os.homedir() + const cacheConfig = new CacheConfig(cacheOptions) + + // Rehydrate the listener from the restore phase + const listenerState = core.getState(CACHE_LISTENER_STATE_KEY) + const cacheListener = CacheListener.rehydrate(listenerState) + + const buildResultsWrapper = new BuildResults(buildResults) + const daemonController = new DaemonController(buildResultsWrapper) + + await caches.save(userHome, gradleUserHome, cacheListener, daemonController, buildResultsWrapper, cacheConfig) + + return generateCachingReport(cacheListener) +} diff --git a/sources/legacy-caching/tsconfig.json b/sources/legacy-caching/tsconfig.json new file mode 100644 index 00000000..207b8d04 --- /dev/null +++ b/sources/legacy-caching/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "./dist", + "rootDir": "./src", + "declaration": false, + "sourceMap": true + }, + "include": ["src/**/*.ts"] +} diff --git a/sources/src/cache-service-loader.ts b/sources/src/cache-service-loader.ts index cbe4f2f1..b25f2b84 100644 --- a/sources/src/cache-service-loader.ts +++ b/sources/src/cache-service-loader.ts @@ -1,3 +1,4 @@ +import * as core from '@actions/core' import * as fs from 'fs' import * as path from 'path' import {pathToFileURL} from 'url' @@ -10,35 +11,15 @@ const NOOP_CACHING_REPORT = ` [Cache was disabled](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md#disabling-caching). Gradle User Home was not restored from or saved to the cache. ` -const CACHE_LICENSE_WARNING = ` -*********************************************************** -LICENSING NOTICE +const LEGACY_CACHE_LOG_MESSAGE = 'Cache module: built-in (legacy)' +const VENDORED_CACHE_LOG_MESSAGE = 'Cache module: enhanced (vendored)' -The caching functionality in \`gradle-actions\` has been extracted into \`gradle-actions-caching\`, a proprietary commercial component that is not covered by the MIT License. -The bundled \`gradle-actions-caching\` component is licensed and governed by a separate license, available at https://gradle.com/legal/terms-of-use/. - -The \`gradle-actions-caching\` component is used only when caching is enabled and is not loaded or used when caching is disabled. - -Use of the \`gradle-actions-caching\` component is subject to a separate license, available at https://gradle.com/legal/terms-of-use/. -If you do not agree to these license terms, do not use the \`gradle-actions-caching\` component. - -You can suppress this message by accepting the terms in your action configuration: see https://github.com/gradle/actions/blob/main/README.md -*********************************************************** +const LEGACY_CACHE_REPORT_NOTICE = ` +> _Using the built-in open-source caching module._ ` -const CACHE_LICENSE_SUMMARY = ` -> [!IMPORTANT] -> #### Licensing notice -> -> The caching functionality in \`gradle-actions\` has been extracted into \`gradle-actions-caching\`, a proprietary commercial component that is not covered by the MIT License. -> The bundled \`gradle-actions-caching\` component is licensed and governed by a separate license, available at https://gradle.com/legal/terms-of-use/. -> -> The \`gradle-actions-caching\` component is used only when caching is enabled and is not loaded or used when caching is disabled. -> -> Use of the \`gradle-actions-caching\` component is subject to a separate license, available at https://gradle.com/legal/terms-of-use/. -> If you do not agree to these license terms, do not use the \`gradle-actions-caching\` component. -> ->You can suppress this message by [accepting the terms in your action configuration](https://github.com/gradle/actions/blob/main/README.md). +const VENDORED_CACHE_REPORT_NOTICE = ` +> _Using the enhanced caching module._ ` class NoOpCacheService implements CacheService { @@ -51,20 +32,25 @@ class NoOpCacheService implements CacheService { } } -class LicenseWarningCacheService implements CacheService { +class LoggingCacheService implements CacheService { private delegate: CacheService + private logMessage: string + private reportNotice: string - constructor(delegate: CacheService) { + constructor(delegate: CacheService, logMessage: string, reportNotice: string) { this.delegate = delegate + this.logMessage = logMessage + this.reportNotice = reportNotice } async restore(gradleUserHome: string, cacheOptions: CacheOptions): Promise { + core.info(this.logMessage) await this.delegate.restore(gradleUserHome, cacheOptions) } async save(gradleUserHome: string, buildResults: BuildResult[], cacheOptions: CacheOptions): Promise { const cachingReport = await this.delegate.save(gradleUserHome, buildResults, cacheOptions) - return `${cachingReport}\n${CACHE_LICENSE_SUMMARY}` + return `${cachingReport}\n${this.reportNotice}` } } @@ -73,32 +59,34 @@ export async function getCacheService(cacheConfig: CacheConfig): Promise { - const vendoredLibraryPath = findVendoredLibraryPath() + const vendoredLibraryPath = findLibraryPath('sources/vendor/gradle-actions-caching/index.js') const moduleUrl = pathToFileURL(vendoredLibraryPath).href return (await import(moduleUrl)) as CacheService } -function findVendoredLibraryPath(): string { +export async function loadLegacyCacheService(): Promise { + const legacyLibraryPath = findLibraryPath('sources/legacy-caching/dist/index.js') + const moduleUrl = pathToFileURL(legacyLibraryPath).href + return (await import(moduleUrl)) as CacheService +} + +function findLibraryPath(relativePath: string): string { const moduleDir = import.meta.dirname - const absolutePath = path.resolve(moduleDir, '../../../sources/vendor/gradle-actions-caching/index.js') + const absolutePath = path.resolve(moduleDir, '../../..', relativePath) if (fs.existsSync(absolutePath)) { return absolutePath } - throw new Error(`Unable to locate vendored cache library at ${absolutePath}.`) -} - -export async function logCacheLicenseWarning(): Promise { - console.info(CACHE_LICENSE_WARNING) + throw new Error(`Unable to locate cache library at ${absolutePath}.`) }