From f1476a710d450c424fe48adba8dd59222b75d7b5 Mon Sep 17 00:00:00 2001 From: daz Date: Wed, 10 Apr 2024 20:43:44 -0600 Subject: [PATCH] Initial import of wrapper-validation-action --- sources/package-lock.json | 328 ++++-- sources/package.json | 13 +- sources/src/wrapper-validation/checksums.ts | 55 + sources/src/wrapper-validation/find.ts | 27 + sources/src/wrapper-validation/hash.ts | 18 + sources/src/wrapper-validation/main.ts | 38 + sources/src/wrapper-validation/validate.ts | 98 ++ .../wrapper-validation/wrapper-checksums.json | 1014 +++++++++++++++++ .../jest/wrapper-validation/checksums.test.ts | 55 + .../data/invalid/gradle-wrapper.jar | 0 .../data/invalid/gradlе-wrapper.jar | 0 .../data/valid/gradle-wrapper.jar | Bin 0 -> 58798 bytes .../test/jest/wrapper-validation/find.test.ts | 12 + .../test/jest/wrapper-validation/hash.test.ts | 12 + .../jest/wrapper-validation/validate.test.ts | 98 ++ sources/tsconfig.json | 7 +- wrapper-validation/action.yml | 29 + 17 files changed, 1709 insertions(+), 95 deletions(-) create mode 100644 sources/src/wrapper-validation/checksums.ts create mode 100644 sources/src/wrapper-validation/find.ts create mode 100644 sources/src/wrapper-validation/hash.ts create mode 100644 sources/src/wrapper-validation/main.ts create mode 100644 sources/src/wrapper-validation/validate.ts create mode 100644 sources/src/wrapper-validation/wrapper-checksums.json create mode 100644 sources/test/jest/wrapper-validation/checksums.test.ts create mode 100644 sources/test/jest/wrapper-validation/data/invalid/gradle-wrapper.jar create mode 100644 sources/test/jest/wrapper-validation/data/invalid/gradlе-wrapper.jar create mode 100644 sources/test/jest/wrapper-validation/data/valid/gradle-wrapper.jar create mode 100644 sources/test/jest/wrapper-validation/find.test.ts create mode 100644 sources/test/jest/wrapper-validation/hash.test.ts create mode 100644 sources/test/jest/wrapper-validation/validate.test.ts create mode 100644 wrapper-validation/action.yml diff --git a/sources/package-lock.json b/sources/package-lock.json index 04e209c..f24ad98 100644 --- a/sources/package-lock.json +++ b/sources/package-lock.json @@ -21,7 +21,9 @@ "@octokit/rest": "20.1.0", "@octokit/webhooks-types": "7.5.0", "semver": "7.6.0", - "string-argv": "0.3.2" + "string-argv": "0.3.2", + "typed-rest-client": "1.8.11", + "unhomoglyph": "1.0.6" }, "devDependencies": { "@types/jest": "29.5.12", @@ -33,8 +35,10 @@ "eslint-plugin-github": "4.10.2", "eslint-plugin-jest": "27.9.0", "eslint-plugin-prettier": "5.1.3", + "glob-parent": "6.0.2", "jest": "29.7.0", "js-yaml": "4.1.0", + "nock": "13.5.4", "npm-run-all": "4.1.5", "patch-package": "8.0.0", "prettier": "3.2.5", @@ -3419,14 +3423,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3774,17 +3782,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, + "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==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "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/define-properties": { @@ -3974,6 +3984,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "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==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -4828,7 +4857,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4879,16 +4907,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -5015,7 +5046,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -5065,12 +5095,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5080,7 +5109,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5092,7 +5120,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -5119,7 +5146,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -6315,6 +6341,12 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6757,6 +6789,20 @@ "tslib": "^2.0.3" } }, + "node_modules/nock": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 10.13" + } + }, "node_modules/node-fetch": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", @@ -6996,7 +7042,6 @@ "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7532,6 +7577,15 @@ "node": ">= 6" } }, + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7557,6 +7611,20 @@ } ] }, + "node_modules/qs": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -7872,15 +7940,16 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, + "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==", "dependencies": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "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.0" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -7931,14 +8000,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8624,6 +8696,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "dependencies": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, "node_modules/typescript": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", @@ -8652,6 +8734,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "node_modules/undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -8668,6 +8755,11 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/unhomoglyph": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", + "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==" + }, "node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", @@ -11601,14 +11693,15 @@ "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==" }, "call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", - "dev": true, + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -11857,14 +11950,13 @@ "dev": true }, "define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", - "dev": true, + "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==", "requires": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" } }, "define-properties": { @@ -12015,6 +12107,19 @@ "which-typed-array": "^1.1.13" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "es-get-iterator": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", @@ -12649,8 +12754,7 @@ "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.6", @@ -12683,11 +12787,11 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", - "dev": true, + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", @@ -12774,7 +12878,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "requires": { "get-intrinsic": "^1.1.3" } @@ -12812,25 +12915,22 @@ "dev": true }, "has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { - "get-intrinsic": "^1.1.1" + "es-define-property": "^1.0.0" } }, "has-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", - "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", - "dev": true + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-tostringtag": { "version": "1.0.0", @@ -12845,7 +12945,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", - "dev": true, "requires": { "function-bind": "^1.1.2" } @@ -13714,6 +13813,12 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, "json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -14086,6 +14191,17 @@ "tslib": "^2.0.3" } }, + "nock": { + "version": "13.5.4", + "resolved": "https://registry.npmjs.org/nock/-/nock-13.5.4.tgz", + "integrity": "sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw==", + "dev": true, + "requires": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "propagate": "^2.0.0" + } + }, "node-fetch": { "version": "2.6.8", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.8.tgz", @@ -14267,8 +14383,7 @@ "object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "dev": true + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "object-is": { "version": "1.1.5", @@ -14644,6 +14759,12 @@ "sisteransi": "^1.0.5" } }, + "propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -14656,6 +14777,14 @@ "integrity": "sha512-KddyFewCsO0j3+np81IQ+SweXLDnDQTs5s67BOnrYmYe/yNmUhttQyGsYzy8yUnoljGAQ9sl38YB4vH8ur7Y+w==", "dev": true }, + "qs": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.0.tgz", + "integrity": "sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==", + "requires": { + "side-channel": "^1.0.6" + } + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -14870,15 +14999,16 @@ } }, "set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", - "dev": true, + "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==", "requires": { - "define-data-property": "^1.1.1", - "get-intrinsic": "^1.2.1", + "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.0" + "has-property-descriptors": "^1.0.2" } }, "set-function-name": { @@ -14914,14 +15044,14 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { @@ -15418,6 +15548,16 @@ "is-typed-array": "^1.1.9" } }, + "typed-rest-client": { + "version": "1.8.11", + "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", + "integrity": "sha512-5UvfMpd1oelmUPRbbaVnq+rHP7ng2cE4qoQkQeAqxRL6PklkxsM0g32/HL0yfvruK6ojQ5x8EE+HF4YV6DtuCA==", + "requires": { + "qs": "^6.9.1", + "tunnel": "0.0.6", + "underscore": "^1.12.1" + } + }, "typescript": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz", @@ -15436,6 +15576,11 @@ "which-boxed-primitive": "^1.0.2" } }, + "underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" + }, "undici": { "version": "5.28.4", "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.4.tgz", @@ -15449,6 +15594,11 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "unhomoglyph": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/unhomoglyph/-/unhomoglyph-1.0.6.tgz", + "integrity": "sha512-7uvcWI3hWshSADBu4JpnyYbTVc7YlhF5GDW/oPD5AxIxl34k4wXR3WDkPnzLxkN32LiTCTKMQLtKVZiwki3zGg==" + }, "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", diff --git a/sources/package.json b/sources/package.json index 3985d5d..e5e4b70 100644 --- a/sources/package.json +++ b/sources/package.json @@ -12,6 +12,7 @@ "compile-dependency-submission-post": "ncc build src/dependency-submission/post.ts --out dist/dependency-submission/post --source-map --no-source-map-register", "compile-setup-gradle-main": "ncc build src/setup-gradle/main.ts --out dist/setup-gradle/main --source-map --no-source-map-register", "compile-setup-gradle-post": "ncc build src/setup-gradle/post.ts --out dist/setup-gradle/post --source-map --no-source-map-register", + "compile-wrapper-validation-main": "ncc build src/wrapper-validation/main.ts --out dist/wrapper-validation/main --source-map --no-source-map-register", "compile": "npm-run-all --parallel compile-*", "check": "npm-run-all --parallel prettier-check lint", "format": "npm-run-all --parallel prettier-write lint", @@ -42,8 +43,11 @@ "@octokit/rest": "20.1.0", "@octokit/webhooks-types": "7.5.0", "semver": "7.6.0", - "string-argv": "0.3.2" - }, + "string-argv": "0.3.2", + + "typed-rest-client": "1.8.11", + "unhomoglyph": "1.0.6" +}, "devDependencies": { "@types/jest": "29.5.12", "@types/node": "20.12.4", @@ -60,6 +64,9 @@ "patch-package": "8.0.0", "prettier": "3.2.5", "ts-jest": "29.1.2", - "typescript": "5.4.3" + "typescript": "5.4.3", + + "glob-parent": "6.0.2", + "nock": "13.5.4" } } diff --git a/sources/src/wrapper-validation/checksums.ts b/sources/src/wrapper-validation/checksums.ts new file mode 100644 index 0000000..4220b0c --- /dev/null +++ b/sources/src/wrapper-validation/checksums.ts @@ -0,0 +1,55 @@ +import * as httpm from 'typed-rest-client/HttpClient' + +import fileWrapperChecksums from './wrapper-checksums.json' + +const httpc = new httpm.HttpClient('gradle/wrapper-validation-action', undefined, {allowRetries: true, maxRetries: 3}) + +function getKnownValidChecksums(): Map> { + const versionsMap = new Map>() + for (const entry of fileWrapperChecksums) { + const checksum = entry.checksum + + let versionNames = versionsMap.get(checksum) + if (versionNames === undefined) { + versionNames = new Set() + versionsMap.set(checksum, versionNames) + } + + versionNames.add(entry.version) + } + + return versionsMap +} + +/** + * Known checksums from previously published Wrapper versions. + * + * Maps from the checksum to the names of the Gradle versions whose wrapper has this checksum. + */ +export const KNOWN_VALID_CHECKSUMS = getKnownValidChecksums() + +export async function fetchValidChecksums(allowSnapshots: boolean): Promise> { + const all = await httpGetJsonArray('https://services.gradle.org/versions/all') + const withChecksum = all.filter( + entry => typeof entry === 'object' && entry != null && entry.hasOwnProperty('wrapperChecksumUrl') + ) + const allowed = withChecksum.filter( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (entry: any) => allowSnapshots || !entry.snapshot + ) + const checksumUrls = allowed.map( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (entry: any) => entry.wrapperChecksumUrl as string + ) + const checksums = await Promise.all(checksumUrls.map(async (url: string) => httpGetText(url))) + return new Set(checksums) +} + +async function httpGetJsonArray(url: string): Promise { + return JSON.parse(await httpGetText(url)) +} + +async function httpGetText(url: string): Promise { + const response = await httpc.get(url) + return await response.readBody() +} diff --git a/sources/src/wrapper-validation/find.ts b/sources/src/wrapper-validation/find.ts new file mode 100644 index 0000000..de1a89e --- /dev/null +++ b/sources/src/wrapper-validation/find.ts @@ -0,0 +1,27 @@ +import * as util from 'util' +import * as path from 'path' +import * as fs from 'fs' +import unhomoglyph from 'unhomoglyph' + +const readdir = util.promisify(fs.readdir) + +export async function findWrapperJars(baseDir: string): Promise { + const files = await recursivelyListFiles(baseDir) + return files + .filter(file => unhomoglyph(file).endsWith('gradle-wrapper.jar')) + .map(wrapperJar => path.relative(baseDir, wrapperJar)) + .sort((a, b) => a.localeCompare(b)) +} + +async function recursivelyListFiles(baseDir: string): Promise { + const childrenNames = await readdir(baseDir) + const childrenPaths = await Promise.all( + childrenNames.map(async childName => { + const childPath = path.resolve(baseDir, childName) + return fs.lstatSync(childPath).isDirectory() + ? recursivelyListFiles(childPath) + : new Promise(resolve => resolve([childPath])) + }) + ) + return Array.prototype.concat(...childrenPaths) +} diff --git a/sources/src/wrapper-validation/hash.ts b/sources/src/wrapper-validation/hash.ts new file mode 100644 index 0000000..90339e1 --- /dev/null +++ b/sources/src/wrapper-validation/hash.ts @@ -0,0 +1,18 @@ +import * as crypto from 'crypto' +import * as fs from 'fs' + +export async function sha256File(path: string): Promise { + return new Promise((resolve, reject) => { + const hash = crypto.createHash('sha256') + const stream = fs.createReadStream(path) + stream.on('data', data => hash.update(data)) + stream.on('end', () => { + stream.destroy() + resolve(hash.digest('hex')) + }) + stream.on('error', error => { + stream.destroy() + reject(error) + }) + }) +} diff --git a/sources/src/wrapper-validation/main.ts b/sources/src/wrapper-validation/main.ts new file mode 100644 index 0000000..9fb3070 --- /dev/null +++ b/sources/src/wrapper-validation/main.ts @@ -0,0 +1,38 @@ +import * as path from 'path' +import * as core from '@actions/core' + +import * as validate from './validate' + +export async function run(): Promise { + try { + const result = await validate.findInvalidWrapperJars( + path.resolve('.'), + +core.getInput('min-wrapper-count'), + core.getInput('allow-snapshots') === 'true', + core.getInput('allow-checksums').split(',') + ) + if (result.isValid()) { + core.info(result.toDisplayString()) + } else { + core.setFailed( + `Gradle Wrapper Validation Failed!\n See https://github.com/gradle/wrapper-validation-action#reporting-failures\n${result.toDisplayString()}` + ) + if (result.invalid.length > 0) { + core.setOutput('failed-wrapper', `${result.invalid.map(w => w.path).join('|')}`) + } + } + } catch (error) { + if (error instanceof AggregateError) { + core.setFailed(`Multiple errors returned`) + for (const err of error.errors) { + core.error(`Error ${error.errors.indexOf(err)}: ${err.message}`) + } + } else if (error instanceof Error) { + core.setFailed(error.message) + } else { + core.setFailed(`Unknown object was thrown: ${error}`) + } + } +} + +run() diff --git a/sources/src/wrapper-validation/validate.ts b/sources/src/wrapper-validation/validate.ts new file mode 100644 index 0000000..add8e83 --- /dev/null +++ b/sources/src/wrapper-validation/validate.ts @@ -0,0 +1,98 @@ +import * as find from './find' +import * as checksums from './checksums' +import * as hash from './hash' +import {resolve} from 'path' + +export async function findInvalidWrapperJars( + gitRepoRoot: string, + minWrapperCount: number, + allowSnapshots: boolean, + allowedChecksums: string[], + knownValidChecksums: Map> = checksums.KNOWN_VALID_CHECKSUMS +): Promise { + const wrapperJars = await find.findWrapperJars(gitRepoRoot) + const result = new ValidationResult([], []) + if (wrapperJars.length < minWrapperCount) { + result.errors.push( + `Expected to find at least ${minWrapperCount} Gradle Wrapper JARs but got only ${wrapperJars.length}` + ) + } + if (wrapperJars.length > 0) { + const notYetValidatedWrappers = [] + for (const wrapperJar of wrapperJars) { + const sha = await hash.sha256File(resolve(gitRepoRoot, wrapperJar)) + if (allowedChecksums.includes(sha) || knownValidChecksums.has(sha)) { + result.valid.push(new WrapperJar(wrapperJar, sha)) + } else { + notYetValidatedWrappers.push(new WrapperJar(wrapperJar, sha)) + } + } + + // Otherwise fall back to fetching checksums from Gradle API and compare against them + if (notYetValidatedWrappers.length > 0) { + result.fetchedChecksums = true + const fetchedValidChecksums = await checksums.fetchValidChecksums(allowSnapshots) + + for (const wrapperJar of notYetValidatedWrappers) { + if (!fetchedValidChecksums.has(wrapperJar.checksum)) { + result.invalid.push(wrapperJar) + } else { + result.valid.push(wrapperJar) + } + } + } + } + return result +} + +export class ValidationResult { + valid: WrapperJar[] + invalid: WrapperJar[] + fetchedChecksums = false + errors: string[] = [] + + constructor(valid: WrapperJar[], invalid: WrapperJar[]) { + this.valid = valid + this.invalid = invalid + } + + isValid(): boolean { + return this.invalid.length === 0 && this.errors.length === 0 + } + + toDisplayString(): string { + let displayString = '' + if (this.invalid.length > 0) { + displayString += `✗ Found unknown Gradle Wrapper JAR files:\n${ValidationResult.toDisplayList( + this.invalid + )}` + } + if (this.errors.length > 0) { + if (displayString.length > 0) displayString += '\n' + displayString += `✗ Other validation errors:\n ${this.errors.join(`\n `)}` + } + if (this.valid.length > 0) { + if (displayString.length > 0) displayString += '\n' + displayString += `✓ Found known Gradle Wrapper JAR files:\n${ValidationResult.toDisplayList(this.valid)}` + } + return displayString + } + + private static toDisplayList(wrapperJars: WrapperJar[]): string { + return ` ${wrapperJars.map(wj => wj.toDisplayString()).join(`\n `)}` + } +} + +export class WrapperJar { + path: string + checksum: string + + constructor(path: string, checksum: string) { + this.path = path + this.checksum = checksum + } + + toDisplayString(): string { + return `${this.checksum} ${this.path}` + } +} diff --git a/sources/src/wrapper-validation/wrapper-checksums.json b/sources/src/wrapper-validation/wrapper-checksums.json new file mode 100644 index 0000000..f159020 --- /dev/null +++ b/sources/src/wrapper-validation/wrapper-checksums.json @@ -0,0 +1,1014 @@ +[ + { + "version": "8.7", + "checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8" + }, + { + "version": "8.7-rc-4", + "checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8" + }, + { + "version": "8.7-rc-3", + "checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8" + }, + { + "version": "8.7-rc-2", + "checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8" + }, + { + "version": "8.7-rc-1", + "checksum": "cb0da6751c2b753a16ac168bb354870ebb1e162e9083f116729cec9c781156b8" + }, + { + "version": "7.6.4", + "checksum": "14dfa961b6704bb3decdea06502781edaa796a82e6da41cd2e1962b14fbe21a3" + }, + { + "version": "8.6", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.6-rc-4", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.6-rc-3", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.6-rc-2", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.6-rc-1", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.6-milestone-1", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.5", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.5-rc-4", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.5-rc-3", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.5-rc-2", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.5-rc-1", + "checksum": "d3b261c2820e9e3d8d639ed084900f11f4a86050a8f83342ade7b6bc9b0d2bdd" + }, + { + "version": "8.4", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "7.6.3", + "checksum": "14dfa961b6704bb3decdea06502781edaa796a82e6da41cd2e1962b14fbe21a3" + }, + { + "version": "8.4-rc-3", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.4-rc-2", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.4-rc-1", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.3", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.3-rc-4", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.3-rc-3", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.3-rc-2", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.3-rc-1", + "checksum": "0336f591bc0ec9aa0c9988929b93ecc916b3c1d52aed202c7381db144aa0ef15" + }, + { + "version": "8.2.1", + "checksum": "a8451eeda314d0568b5340498b36edf147a8f0d692c5ff58082d477abe9146e4" + }, + { + "version": "8.2", + "checksum": "a8451eeda314d0568b5340498b36edf147a8f0d692c5ff58082d477abe9146e4" + }, + { + "version": "7.6.2", + "checksum": "14dfa961b6704bb3decdea06502781edaa796a82e6da41cd2e1962b14fbe21a3" + }, + { + "version": "8.2-rc-3", + "checksum": "a8451eeda314d0568b5340498b36edf147a8f0d692c5ff58082d477abe9146e4" + }, + { + "version": "8.2-rc-2", + "checksum": "5c9a1a6f50b4f8c0264b1ac69013bef9f8363733275fafa56c70c84be3276bb8" + }, + { + "version": "8.2-rc-1", + "checksum": "55e949185c26ba3ddcd2c6a4217d043bfa0ce3cc002bbbb52b709a181a513e81" + }, + { + "version": "8.2-milestone-1", + "checksum": "55e949185c26ba3ddcd2c6a4217d043bfa0ce3cc002bbbb52b709a181a513e81" + }, + { + "version": "8.1.1", + "checksum": "ed2c26eba7cfb93cc2b7785d05e534f07b5b48b5e7fc941921cd098628abca58" + }, + { + "version": "8.1", + "checksum": "ed2c26eba7cfb93cc2b7785d05e534f07b5b48b5e7fc941921cd098628abca58" + }, + { + "version": "8.1-rc-4", + "checksum": "ed2c26eba7cfb93cc2b7785d05e534f07b5b48b5e7fc941921cd098628abca58" + }, + { + "version": "8.1-rc-3", + "checksum": "ed2c26eba7cfb93cc2b7785d05e534f07b5b48b5e7fc941921cd098628abca58" + }, + { + "version": "8.1-rc-2", + "checksum": "ed2c26eba7cfb93cc2b7785d05e534f07b5b48b5e7fc941921cd098628abca58" + }, + { + "version": "8.1-rc-1", + "checksum": "ed2c26eba7cfb93cc2b7785d05e534f07b5b48b5e7fc941921cd098628abca58" + }, + { + "version": "8.0.2", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "7.6.1", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "6.9.4", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "8.0.1", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-rc-5", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-rc-4", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-rc-3", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-rc-2", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-rc-1", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-milestone-6", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-milestone-5", + "checksum": "91941f522fbfd4431cf57e445fc3d5200c85f957bda2de5251353cf11174f4b5" + }, + { + "version": "8.0-milestone-4", + "checksum": "577b2de036000db2e0f04f2ec842a4f1e648c8b6f9c87f29a8d896acb1732538" + }, + { + "version": "7.6", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "7.6-rc-4", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "7.6-rc-3", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "7.6-rc-2", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "8.0-milestone-3", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "7.6-rc-1", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "6.9.3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "8.0-milestone-2", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "8.0-milestone-1", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "7.6-milestone-1", + "checksum": "c5a643cf80162e665cc228f7b16f343fef868e47d3a4836f62e18b7e17ac018a" + }, + { + "version": "7.5.1", + "checksum": "91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60" + }, + { + "version": "7.5", + "checksum": "91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60" + }, + { + "version": "7.5-rc-5", + "checksum": "91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60" + }, + { + "version": "7.5-rc-4", + "checksum": "91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60" + }, + { + "version": "7.5-rc-3", + "checksum": "91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60" + }, + { + "version": "7.5-rc-2", + "checksum": "91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60" + }, + { + "version": "7.5-rc-1", + "checksum": "91a239400bb638f36a1795d8fdf7939d532cdc7d794d1119b7261aac158b1e60" + }, + { + "version": "7.4.2", + "checksum": "575098db54a998ff1c6770b352c3b16766c09848bee7555dab09afc34e8cf590" + }, + { + "version": "7.4.1", + "checksum": "575098db54a998ff1c6770b352c3b16766c09848bee7555dab09afc34e8cf590" + }, + { + "version": "7.4", + "checksum": "575098db54a998ff1c6770b352c3b16766c09848bee7555dab09afc34e8cf590" + }, + { + "version": "7.4-rc-2", + "checksum": "575098db54a998ff1c6770b352c3b16766c09848bee7555dab09afc34e8cf590" + }, + { + "version": "7.4-rc-1", + "checksum": "575098db54a998ff1c6770b352c3b16766c09848bee7555dab09afc34e8cf590" + }, + { + "version": "7.3.3", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3.3-rc-1", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "6.9.2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.3.2", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3.1", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3-rc-5", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3-rc-4", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3-rc-3", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3-rc-2", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.3-rc-1", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "6.9.1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.2", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.2-rc-3", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.2-rc-2", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.2-rc-1", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.1.1", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.1", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.1-rc-2", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.1-rc-1", + "checksum": "33ad4583fd7ee156f533778736fa1b4940bd83b433934d1cc4e9f608e99a6a89" + }, + { + "version": "7.0.2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.0.1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.9", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.9-rc-2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.9-rc-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.0", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.0-rc-2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.0-rc-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.0-milestone-3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.0-milestone-2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8.3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8.2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "7.0-milestone-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8.1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8-rc-5", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8-rc-4", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8-rc-3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8-rc-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.7.1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8-milestone-3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8-milestone-2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.7", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.8-milestone-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.7-rc-5", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.7-rc-4", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.7-rc-3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.7-rc-2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.7-rc-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6.1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-rc-6", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-rc-5", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-rc-4", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-rc-3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-rc-2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-rc-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-milestone-3", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.5.1", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.6-milestone-2", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.6-milestone-1", + "checksum": "e996d452d2645e70c01c11143ca2d3742734a28da2bf61f25c82bdc288c9e637" + }, + { + "version": "6.5", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.5-rc-1", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.4.1", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.5-milestone-2", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.5-milestone-1", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.4", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.4-rc-4", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.4-rc-3", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.4-rc-2", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.4-rc-1", + "checksum": "70239e6ca1f0d5e3b2808ef6d82390cf9ad58d3a3a0d271677a51d1b89475857" + }, + { + "version": "6.3", + "checksum": "1cef53de8dc192036e7b0cc47584449b0cf570a00d560bfaa6c9eabe06e1fc06" + }, + { + "version": "6.3-rc-4", + "checksum": "1cef53de8dc192036e7b0cc47584449b0cf570a00d560bfaa6c9eabe06e1fc06" + }, + { + "version": "6.3-rc-3", + "checksum": "1cef53de8dc192036e7b0cc47584449b0cf570a00d560bfaa6c9eabe06e1fc06" + }, + { + "version": "6.3-rc-2", + "checksum": "1cef53de8dc192036e7b0cc47584449b0cf570a00d560bfaa6c9eabe06e1fc06" + }, + { + "version": "6.3-rc-1", + "checksum": "1cef53de8dc192036e7b0cc47584449b0cf570a00d560bfaa6c9eabe06e1fc06" + }, + { + "version": "6.2.2", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.2.1", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.2", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.2-rc-3", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.2-rc-2", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.2-rc-1", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.1.1", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.1", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.1-rc-3", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.1-rc-2", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.1-rc-1", + "checksum": "96f793a18e056c23ffeec67c1f3bb8eccff5a4a407fc9ceac183527e7eedf4b6" + }, + { + "version": "6.1-milestone-3", + "checksum": "3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce" + }, + { + "version": "6.1-milestone-2", + "checksum": "3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce" + }, + { + "version": "6.1-milestone-1", + "checksum": "3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce" + }, + { + "version": "6.0.1", + "checksum": "28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e" + }, + { + "version": "6.0", + "checksum": "28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e" + }, + { + "version": "6.0-rc-3", + "checksum": "28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e" + }, + { + "version": "5.6.4", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "6.0-rc-2", + "checksum": "28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e" + }, + { + "version": "6.0-rc-1", + "checksum": "28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e" + }, + { + "version": "5.6.3", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.6.2", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.6.1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.6", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.6-rc-2", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.6-rc-1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.5.1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.5", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.5-rc-4", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.5-rc-3", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.5-rc-2", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.5-rc-1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.4.1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.4", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.4-rc-1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.3.1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.3", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.3-rc-3", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.3-rc-2", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.3-rc-1", + "checksum": "3dc39ad650d40f6c029bd8ff605c6d95865d657dbfdeacdb079db0ddfffedf9f" + }, + { + "version": "5.2.1", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "5.2", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "5.2-rc-1", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "5.1.1", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "5.1", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "5.1-rc-3", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "5.1-rc-2", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "5.1-rc-1", + "checksum": "76b12da7f4a7cdd025e5996811a2e49bf5df0fb62d72554ab555c0e434b63aae" + }, + { + "version": "4.10.3", + "checksum": "660ab018b8e319e9ae779fdb1b7ac47d0321bde953bf0eb4545f14952cfdcaa3" + }, + { + "version": "5.1-milestone-1", + "checksum": "8ff6bee43c55efc0cce9e1147860a76fc970398fbef587e64b6e7a5a7e0291df" + }, + { + "version": "5.0", + "checksum": "f1a597a1f2b23089deec11d5b924d074f9e4ed810f2093be7021ded01c8073ad" + }, + { + "version": "4.10.2", + "checksum": "ad63ba21fb91e490e0f6fd0ca7d4049241f0f68a454b0b3075c041c4554e611c" + }, + { + "version": "4.10.1", + "checksum": "d8a69ca8efe271d8de080c42a2ea4b08fc9e85c41aa2d163255c70d9da239db0" + }, + { + "version": "4.10", + "checksum": "778e7f46bd67eaea2de5bcbdbb40878c6614656014ba59a72ce8648eaf43a925" + }, + { + "version": "4.9", + "checksum": "e55e7e47a79e04c26363805b31e2f40b7a9cc89ea12113be7de750a3b2cede85" + }, + { + "version": "4.8.1", + "checksum": "080e30657661539701b66827b96eb0043191e0a7a73090e8a57bd6735e5af5c5" + }, + { + "version": "4.8", + "checksum": "4160d5a6d8d6efc6af336582bbbba8194e4d7a742835f7b0fd3964cbd419c994" + }, + { + "version": "4.7", + "checksum": "7d6fe0a055f133226409de2457fb0e887d1f6b096f36c8d5fd76fb7a9357cd45" + }, + { + "version": "4.6", + "checksum": "381dff8aa434499aa93bc25572b049c8c586a67faff2c02f375e4f23e17e49de" + }, + { + "version": "4.5.1", + "checksum": "3bf04e39ed259ff0a1217a875199a11775855d2a29207b98318ac79178249de8" + }, + { + "version": "4.5", + "checksum": "d283a04caee0e97b666b09e50ab394834934bee6225c2e9cd4650afdeac43828" + }, + { + "version": "4.4.1", + "checksum": "4e318d74d06aa7b998091345c397a3c7c4b291b59da31e6f9c772a596711acac" + }, + { + "version": "4.4", + "checksum": "88b5b31f390a268ab3773df580d83fd1e388f49c2b685f78a16600577bd72fe2" + }, + { + "version": "4.3.1", + "checksum": "383f4efa709b52632a520708e8a07353961970941ab3867ab8ac182132ce1c54" + }, + { + "version": "4.3", + "checksum": "ba496e7e0e03ffa432eaf715bb1466fac2ffc8491a71e7164a5438c48c79d8ea" + }, + { + "version": "4.2.1", + "checksum": "c536e519e65a4bb787e071c6a90d23bd219c9d409a2123db649d3684acbf3ae7" + }, + { + "version": "4.2", + "checksum": "53aa048fef3c06a8442c6a44df5edd2e8c791ee883e42ded6189c7eed112095d" + }, + { + "version": "4.1", + "checksum": "f4d953f31fbf6c38a8c330d19171c8ba6e0d1ff59d4d5c5c2d3ed821c9f3d5a3" + }, + { + "version": "3.2.1", + "checksum": "020ef0245a07b33ca48b12f59415e7e5083cf701ef02690464a8cc2ab3984608" + }, + { + "version": "3.2", + "checksum": "d2d3abae74e89cc4200f48d4a08a7e5960363c33ee62272ef5ffbb39f4c7f83e" + }, + { + "version": "3.1", + "checksum": "0f49043be582d7a39b671f924c66bd9337b92fa88ff5951225acc60560053067" + }, + { + "version": "3.0", + "checksum": "42d7a2f636983aa09d21dfeb6e90d21d7a8dad905351390643ce60cc82c8f8a9" + }, + { + "version": "2.14.1", + "checksum": "8e47da0b2656354d059609cae9d44b196d4f9b14512e688ffee4e0eb7e723ae9" + }, + { + "version": "2.14", + "checksum": "b5ca811c057b3eb4164c78f4155d667c6092ff98ba91a4c90d29e127426f37a7" + }, + { + "version": "2.13", + "checksum": "0c3c576e28b44eddcab6b8b4854f484363dfbcd047657d41654e839835da2c53" + }, + { + "version": "2.12", + "checksum": "4894520b03c007bf38e983bf933320c483a9790010d997029fa8985dc6128559" + }, + { + "version": "2.11", + "checksum": "0bc7b16a0a3fa52af674de44d1fea48abc4dee3431f3d4829cd9ea329836e596" + }, + { + "version": "2.10", + "checksum": "16caeaf66d57a0d1d2087fef6a97efa62de8da69afa5b908f40db35afc4342da" + }, + { + "version": "2.9", + "checksum": "b92386e36a96da6be89e91f71087d1394a26c0450231ba0b22e28ee1ee8fa14b" + }, + { + "version": "2.8", + "checksum": "198159fcd7d29533c0d37423d66c44729982d5280c9e2c7c5f4b7bc6a9317f6b" + }, + { + "version": "2.7", + "checksum": "d7e1975ccf2dc079d4f0b1010febdad466506d1565c5aa8017c88ebc5e471604" + }, + { + "version": "2.6", + "checksum": "695089a2b306f55f0bd63140fbcc5ead8c383819018188ce484cd5a055bec6e4" + }, + { + "version": "2.5", + "checksum": "718d7b25ea60b357fc4cb2212ce10b3f03dfd0e6fe5f23f565b15553ec46bb7e" + }, + { + "version": "2.4", + "checksum": "98420079ffe3e24b1013180d9b9bc2e2ee6a9d867ee232004b75a961d9c18e27" + }, + { + "version": "2.3", + "checksum": "b18a1114ebe81fb7502d40ad9a4f86cef82fff244a865ad45533b4d5e7ff0cc8" + }, + { + "version": "2.2.1", + "checksum": "5f73d431fd1c5dcc2cf11555b8e486c43249c1099f678ccc6088b05be600a2e1" + }, + { + "version": "2.2", + "checksum": "fa9b4294d47cf8db7039cb9b2435de3dd1accb0d3d67926705775a0579dfa397" + }, + { + "version": "2.1", + "checksum": "5e27c39c2336c25748f279d8b105162d14b1a39eb7839d0b658432282d0ce79f" + }, + { + "version": "2.0", + "checksum": "80a33ca14e3bca3116bc8749550397f739f126190c82bb6399fdc8d10f49661f" + }, + { + "version": "1.12", + "checksum": "dea5ceba47b58df0b7f69a65b24357527c1927ccc72b6d4ed90658d39e461b29" + }, + { + "version": "1.11", + "checksum": "a14b54dd3790f5ce1dc08ebbf4b5bcc05f76c4554b43accb84696c970f29aba0" + }, + { + "version": "1.10", + "checksum": "6a6c15e222a0458aa33985b87f67954f4222410b43b1e26866197d0a77d93cbc" + }, + { + "version": "1.9", + "checksum": "134337ea7c13221f9d1a1c14288a5cf8af9f6060167b903b724b115cf5a0cf73" + }, + { + "version": "1.8", + "checksum": "13f23a24252ddca0a0fabef212e3c854f5895b081c09d015c91587a5df9bf9f7" + }, + { + "version": "1.7", + "checksum": "7af529cc3331d38b3d8f8344ddd9b2d3744542b55b68318abd8bb1a6f3812a1c" + }, + { + "version": "1.6", + "checksum": "66dbcc9f0bc33789ea0bdb4d49c8ea037047bda5647ef696c47bdca65f785159" + }, + { + "version": "1.5", + "checksum": "9598ffdf7ee26949d8b861ece267c70c802f21f7fc52596693834792d155195a" + }, + { + "version": "1.4", + "checksum": "c95985b7b5684e133c5d45044fd90faaf6c8f7cd2493d61a11c2b8c5b71ef514" + }, + { + "version": "1.3", + "checksum": "95513eccca99e1ae1aeadc4f69cabd0e7fb64821d3f26c46a489df844c8a7353" + }, + { + "version": "1.2", + "checksum": "5c91fa893665f3051eae14578fac2df14e737423387e75ffbeccd35f335a3d8b" + }, + { + "version": "1.1", + "checksum": "22c56a9780daeee00e5bf31621f991b68e73eff6fe8afca628a1fe2c50c6038e" + }, + { + "version": "1.0", + "checksum": "87e50531ca7aab675f5bb65755ef78328afd64cf0877e37ad876047a8a014055" + } +] diff --git a/sources/test/jest/wrapper-validation/checksums.test.ts b/sources/test/jest/wrapper-validation/checksums.test.ts new file mode 100644 index 0000000..ce5f750 --- /dev/null +++ b/sources/test/jest/wrapper-validation/checksums.test.ts @@ -0,0 +1,55 @@ +import * as checksums from '../../../src/wrapper-validation/checksums' +import nock from 'nock' +import {afterEach, describe, expect, test, jest} from '@jest/globals' + +jest.setTimeout(30000) + +test('has loaded hardcoded wrapper jars checksums', async () => { + // Sanity check that generated checksums file is not empty and was properly imported + expect(checksums.KNOWN_VALID_CHECKSUMS.size).toBeGreaterThan(10) + // Verify that checksums of arbitrary versions are contained + expect( + checksums.KNOWN_VALID_CHECKSUMS.get( + '660ab018b8e319e9ae779fdb1b7ac47d0321bde953bf0eb4545f14952cfdcaa3' + ) + ).toEqual(new Set(['4.10.3'])) + expect( + checksums.KNOWN_VALID_CHECKSUMS.get( + '28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e' + ) + ).toEqual(new Set(['6.0-rc-1', '6.0-rc-2', '6.0-rc-3', '6.0', '6.0.1'])) +}) + +test('fetches wrapper jars checksums', async () => { + const validChecksums = await checksums.fetchValidChecksums(false) + expect(validChecksums.size).toBeGreaterThan(10) + // Verify that checksum of arbitrary version is contained + expect( + validChecksums.has( + // Checksum for version 6.0 + '28b330c20a9a73881dfe9702df78d4d78bf72368e8906c70080ab6932462fe9e' + ) + ).toBe(true) +}) + +describe('retry', () => { + afterEach(() => { + nock.cleanAll() + }) + + describe('for /versions/all API', () => { + test('retry three times', async () => { + nock('https://services.gradle.org', {allowUnmocked: true}) + .get('/versions/all') + .times(3) + .replyWithError({ + message: 'connect ECONNREFUSED 104.18.191.9:443', + code: 'ECONNREFUSED' + }) + + const validChecksums = await checksums.fetchValidChecksums(false) + expect(validChecksums.size).toBeGreaterThan(10) + nock.isDone() + }) + }) +}) diff --git a/sources/test/jest/wrapper-validation/data/invalid/gradle-wrapper.jar b/sources/test/jest/wrapper-validation/data/invalid/gradle-wrapper.jar new file mode 100644 index 0000000..e69de29 diff --git a/sources/test/jest/wrapper-validation/data/invalid/gradlе-wrapper.jar b/sources/test/jest/wrapper-validation/data/invalid/gradlе-wrapper.jar new file mode 100644 index 0000000..e69de29 diff --git a/sources/test/jest/wrapper-validation/data/valid/gradle-wrapper.jar b/sources/test/jest/wrapper-validation/data/valid/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..6d183c6b6f1b37f3fb960b68d8b1386b3e010383 GIT binary patch literal 58798 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 { + const repoRoot = path.resolve('./test/jest/wrapper-validation') + const wrapperJars = await find.findWrapperJars(repoRoot) + expect(wrapperJars.length).toBe(3) + expect(wrapperJars).toContain('data/valid/gradle-wrapper.jar') + expect(wrapperJars).toContain('data/invalid/gradle-wrapper.jar') + expect(wrapperJars).toContain('data/invalid/gradlе-wrapper.jar') // homoglyph +}) diff --git a/sources/test/jest/wrapper-validation/hash.test.ts b/sources/test/jest/wrapper-validation/hash.test.ts new file mode 100644 index 0000000..929f75a --- /dev/null +++ b/sources/test/jest/wrapper-validation/hash.test.ts @@ -0,0 +1,12 @@ +import * as path from 'path' +import * as hash from '../../../src/wrapper-validation/hash' +import {expect, test} from '@jest/globals' + +test('can sha256 files', async () => { + const sha = await hash.sha256File( + path.resolve('test/jest/wrapper-validation/data/invalid/gradle-wrapper.jar') + ) + expect(sha).toEqual( + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ) +}) diff --git a/sources/test/jest/wrapper-validation/validate.test.ts b/sources/test/jest/wrapper-validation/validate.test.ts new file mode 100644 index 0000000..5aacd10 --- /dev/null +++ b/sources/test/jest/wrapper-validation/validate.test.ts @@ -0,0 +1,98 @@ +import * as path from 'path' +import * as validate from '../../../src/wrapper-validation/validate' +import {expect, test, jest} from '@jest/globals' + +jest.setTimeout(30000) + +const baseDir = path.resolve('./test/jest/wrapper-validation') + +test('succeeds if all found wrapper jars are valid', async () => { + const result = await validate.findInvalidWrapperJars(baseDir, 3, false, [ + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ]) + + expect(result.isValid()).toBe(true) + // Only hardcoded and explicitly allowed checksums should have been used + expect(result.fetchedChecksums).toBe(false) + + expect(result.toDisplayString()).toBe( + '✓ Found known Gradle Wrapper JAR files:\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradle-wrapper.jar\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradlе-wrapper.jar\n' + // homoglyph + ' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce data/valid/gradle-wrapper.jar' + ) +}) + +test('succeeds if all found wrapper jars are valid (and checksums are fetched from Gradle API)', async () => { + const knownValidChecksums = new Map>() + const result = await validate.findInvalidWrapperJars( + baseDir, + 1, + false, + ['e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'], + knownValidChecksums + ) + + expect(result.isValid()).toBe(true) + // Should have fetched checksums because no known checksums were provided + expect(result.fetchedChecksums).toBe(true) + + expect(result.toDisplayString()).toBe( + '✓ Found known Gradle Wrapper JAR files:\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradle-wrapper.jar\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradlе-wrapper.jar\n' + // homoglyph + ' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce data/valid/gradle-wrapper.jar' + ) +}) + +test('fails if invalid wrapper jars are found', async () => { + const result = await validate.findInvalidWrapperJars(baseDir, 3, false, []) + + expect(result.isValid()).toBe(false) + + expect(result.valid).toEqual([ + new validate.WrapperJar( + 'data/valid/gradle-wrapper.jar', + '3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce' + ) + ]) + + expect(result.invalid).toEqual([ + new validate.WrapperJar( + 'data/invalid/gradle-wrapper.jar', + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ), + new validate.WrapperJar( + 'data/invalid/gradlе-wrapper.jar', // homoglyph + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + ) + ]) + + expect(result.toDisplayString()).toBe( + '✗ Found unknown Gradle Wrapper JAR files:\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradle-wrapper.jar\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradlе-wrapper.jar\n' + // homoglyph + '✓ Found known Gradle Wrapper JAR files:\n' + + ' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce data/valid/gradle-wrapper.jar' + ) +}) + +test('fails if not enough wrapper jars are found', async () => { + const result = await validate.findInvalidWrapperJars(baseDir, 4, false, []) + + expect(result.isValid()).toBe(false) + + expect(result.errors).toEqual([ + 'Expected to find at least 4 Gradle Wrapper JARs but got only 3' + ]) + + expect(result.toDisplayString()).toBe( + '✗ Found unknown Gradle Wrapper JAR files:\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradle-wrapper.jar\n' + + ' e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 data/invalid/gradlе-wrapper.jar\n' + // homoglyph + '✗ Other validation errors:\n' + + ' Expected to find at least 4 Gradle Wrapper JARs but got only 3\n' + + '✓ Found known Gradle Wrapper JAR files:\n' + + ' 3888c76faa032ea8394b8a54e04ce2227ab1f4be64f65d450f8509fe112d38ce data/valid/gradle-wrapper.jar' + ) +}) diff --git a/sources/tsconfig.json b/sources/tsconfig.json index 583b426..7346688 100644 --- a/sources/tsconfig.json +++ b/sources/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { /* Basic Options */ - "incremental": false, /* Enable incremental compilation */ - "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ + "incremental": false, /* Enable incremental compilation */ + "target": "es2021", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ // "allowJs": true, /* Allow javascript files to be compiled. */ // "checkJs": true, /* Report errors in .js files. */ @@ -45,9 +45,10 @@ // "typeRoots": [], /* List of folders to include type definitions from. */ // "types": [], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + "resolveJsonModule": true, /* Enable importing JSON files as module; used for importing wrapper checksums JSON */ /* Source Map Options */ // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ diff --git a/wrapper-validation/action.yml b/wrapper-validation/action.yml new file mode 100644 index 0000000..117fb54 --- /dev/null +++ b/wrapper-validation/action.yml @@ -0,0 +1,29 @@ +name: 'Gradle Wrapper Validation' +description: 'Validates Gradle Wrapper JAR Files' +author: 'Gradle' + +inputs: + min-wrapper-count: + description: 'Minimum number expected gradle-wrapper.jar files found in the repository. Non-negative number. Higher number is useful in monorepos where each project might have their own wrapper.' + required: false + default: '1' + allow-snapshots: + description: 'Allow Gradle snapshot versions during checksum verification. Boolean, true or false.' + required: false + default: 'false' + allow-checksums: + description: 'Accept arbitrary user-defined checksums as valid. Comma separated list of SHA256 checksums (lowercase hex).' + required: false + default: '' + +outputs: + failed-wrapper: + description: 'The path of the Gradle Wrapper(s) JAR that failed validation. Path is a platform-dependent relative path to git repository root. Multiple paths are separated by a | character.' + +runs: + using: 'node20' + main: '../dist/wrapper-validation/main/index.js' + +branding: + icon: 'shield' + color: gray-dark