Compare commits
159 Commits
server-202
...
server-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e140f1ddbd | ||
|
|
8d7258b344 | ||
|
|
66af65c9e2 | ||
|
|
880c1fb49c | ||
|
|
bfa712469a | ||
|
|
04254e5905 | ||
|
|
17d04f73be | ||
|
|
01de8910cd | ||
|
|
3300e4a09d | ||
|
|
700566ff26 | ||
|
|
e548414edd | ||
|
|
7404d4e207 | ||
|
|
10bf39632a | ||
|
|
7d7efaec22 | ||
|
|
66ca8830fe | ||
|
|
979361dfb6 | ||
|
|
3e6d412bf7 | ||
|
|
f476b2f3a8 | ||
|
|
4ba86170a7 | ||
|
|
9628fc4642 | ||
|
|
5024688bc3 | ||
|
|
5676ec5b81 | ||
|
|
86581e6ddd | ||
|
|
bd288db3a4 | ||
|
|
188c34d7c2 | ||
|
|
6ec8c7e06f | ||
|
|
8abb365bbf | ||
|
|
595a9a90b7 | ||
|
|
d0bdb8244c | ||
|
|
2de7e8f856 | ||
|
|
b71f812b68 | ||
|
|
9456e441ea | ||
|
|
1cc8d5387c | ||
|
|
1af3dfe687 | ||
|
|
0c4001043d | ||
|
|
657a146a8e | ||
|
|
ffb18a10fc | ||
|
|
1e938d42ea | ||
|
|
c8ba2c2e16 | ||
|
|
5975e6f3ea | ||
|
|
9a9c8b2359 | ||
|
|
d685669fa8 | ||
|
|
6e581b7acf | ||
|
|
c44e8c4cce | ||
|
|
8f1f787ceb | ||
|
|
2814de2ecd | ||
|
|
dce0b9e765 | ||
|
|
2424d4270b | ||
|
|
03e8bebaaa | ||
|
|
5b286f1f68 | ||
|
|
2a2f3bf977 | ||
|
|
c876f45789 | ||
|
|
0036a1dfb2 | ||
|
|
68a2961390 | ||
|
|
bb254053ea | ||
|
|
cee41f5818 | ||
|
|
57ba623fd6 | ||
|
|
407fe39e6e | ||
|
|
78f83249fa | ||
|
|
7d391b4277 | ||
|
|
b73a704b63 | ||
|
|
f110e485d0 | ||
|
|
b3039abeda | ||
|
|
1c2e67d913 | ||
|
|
f693a6e567 | ||
|
|
2ad636d8b9 | ||
|
|
72f7b7b551 | ||
|
|
3decd57855 | ||
|
|
b8819746c1 | ||
|
|
de86924d7d | ||
|
|
f6e40a600e | ||
|
|
57820b2eb9 | ||
|
|
45bb786147 | ||
|
|
791e635408 | ||
|
|
3ca60917b4 | ||
|
|
3eb4959163 | ||
|
|
0bda253fe4 | ||
|
|
113074aa92 | ||
|
|
e7784ac2d6 | ||
|
|
5d1ddbc3fe | ||
|
|
be6598623e | ||
|
|
1c073cbe7d | ||
|
|
9118ba8d24 | ||
|
|
206a58a731 | ||
|
|
a7d6442abd | ||
|
|
dca72ef5b2 | ||
|
|
b01e15c7dd | ||
|
|
5ccc7bd3bb | ||
|
|
0068bd7138 | ||
|
|
7169bb970a | ||
|
|
3cefd28e0c | ||
|
|
083b36c0a1 | ||
|
|
19b50a66a6 | ||
|
|
5ad1e35101 | ||
|
|
a7e6f15e5e | ||
|
|
c4d7c84523 | ||
|
|
fd4b4c62b6 | ||
|
|
d9a68e35d9 | ||
|
|
9421bc48ca | ||
|
|
355ca66f2f | ||
|
|
b543d075f7 | ||
|
|
c109cf545d | ||
|
|
c2fafc9fac | ||
|
|
3512c89054 | ||
|
|
8f921a4078 | ||
|
|
57ffa7a456 | ||
|
|
cc02b015d3 | ||
|
|
8fa0adb77f | ||
|
|
6153d1f15c | ||
|
|
f2e2b0a197 | ||
|
|
a7db57e03b | ||
|
|
9d46340b3e | ||
|
|
bde30b2589 | ||
|
|
3ea96c31c0 | ||
|
|
8411c34ad4 | ||
|
|
9b1021520d | ||
|
|
c20d0185cd | ||
|
|
385c999d75 | ||
|
|
ecac34c258 | ||
|
|
cab0286608 | ||
|
|
646f7f21db | ||
|
|
8b77c8571c | ||
|
|
6a52e25dee | ||
|
|
a976e265bf | ||
|
|
167e7b14f7 | ||
|
|
7698dabe74 | ||
|
|
7bd1d4fb75 | ||
|
|
d4b7735e86 | ||
|
|
b1c2265230 | ||
|
|
7f4cb41e68 | ||
|
|
61cd2082fc | ||
|
|
a10e93d567 | ||
|
|
31e0254051 | ||
|
|
417a1c3e7e | ||
|
|
6da5a68a97 | ||
|
|
8fe11ad9a5 | ||
|
|
7df0098a64 | ||
|
|
e7968bd6cc | ||
|
|
b8bbe7bba9 | ||
|
|
d253a45070 | ||
|
|
87790f226f | ||
|
|
6f2c0a22e9 | ||
|
|
8e868cda4e | ||
|
|
6ff81c1784 | ||
|
|
c585f4eecc | ||
|
|
11f9735d95 | ||
|
|
520fcda548 | ||
|
|
4c43ffb70a | ||
|
|
5120cc0b79 | ||
|
|
7d02104ddb | ||
|
|
9874db7841 | ||
|
|
164209a4b1 | ||
|
|
72e6421d6a | ||
|
|
1fd625ec7d | ||
|
|
58a1176bbc | ||
|
|
075ead0f9b | ||
|
|
4784a9b553 | ||
|
|
685421f890 | ||
|
|
7749c5e8fe |
@@ -8,5 +8,5 @@ inputs:
|
||||
description: 'The GITHUB_TOKEN secret'
|
||||
required: true
|
||||
runs:
|
||||
using: 'node16'
|
||||
using: 'node20'
|
||||
main: 'index.js'
|
||||
|
||||
203
.github/actions/docusaurus-swizzled-warning/package-lock.json
generated
vendored
203
.github/actions/docusaurus-swizzled-warning/package-lock.json
generated
vendored
@@ -10,7 +10,7 @@
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^5.1.1"
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
@@ -23,123 +23,151 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-5.1.1.tgz",
|
||||
"integrity": "sha512-Nk59rMDoJaV+mHCOJPXuvB1zIbomlKS0dmSIqPGxd0enAXBnOfn4VWF+CGtRCwXZG9Epa54tZA7VIRlJDS8A6g==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-6.0.0.tgz",
|
||||
"integrity": "sha512-alScpSVnYmjNEXboZjarjukQEzgCRmjMv6Xj47fsdnqGS73bjJNDpiiXmp8jr0UZLdUB6d9jW63IcmddUP+l0g==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
"@actions/http-client": "^2.2.0",
|
||||
"@octokit/core": "^5.0.1",
|
||||
"@octokit/plugin-paginate-rest": "^9.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.1.1.tgz",
|
||||
"integrity": "sha512-qhrkRMB40bbbLo7gF+0vu+X+UawOvQQqNAA/5Unx774RS8poaOhThDOG6BGmxvAnxhQnDp2BG/ZUm65xZILTpw==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.0.tgz",
|
||||
"integrity": "sha512-q+epW0trjVUUHboliPb4UF9g2msf+w61b32tAkFEwL/IwP0DQWgbCMM0Hbe3e3WXSKz5VcUXbzJQgy8Hkra/Lg==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/busboy": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.0.0.tgz",
|
||||
"integrity": "sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
|
||||
"integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3"
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-4.0.0.tgz",
|
||||
"integrity": "sha512-tY/msAuJo6ARbK6SPIxZrPBms3xPbfwBrulZe0Wtr/DIY9lje2HeV1uoebShn6mx7SjCHif6EjMvoREj+gZ+SA==",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/core": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz",
|
||||
"integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.0.1.tgz",
|
||||
"integrity": "sha512-lyeeeZyESFo+ffI801SaBKmCfsvarO+dgV8/0gD8u1d87clbEdWsP5yC+dSj3zLhb2eIf5SJrn6vDz9AheETHw==",
|
||||
"dependencies": {
|
||||
"@octokit/auth-token": "^2.4.4",
|
||||
"@octokit/graphql": "^4.5.8",
|
||||
"@octokit/request": "^5.6.3",
|
||||
"@octokit/request-error": "^2.0.5",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"@octokit/auth-token": "^4.0.0",
|
||||
"@octokit/graphql": "^7.0.0",
|
||||
"@octokit/request": "^8.0.2",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"before-after-hook": "^2.2.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/endpoint": {
|
||||
"version": "6.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
|
||||
"integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
|
||||
"version": "9.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-9.0.1.tgz",
|
||||
"integrity": "sha512-hRlOKAovtINHQPYHZlfyFwaM8OyetxeoC81lAkBy34uLb8exrZB50SQdeW3EROqiY9G9yxQTpp5OHTV54QD+vA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/graphql": {
|
||||
"version": "4.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
|
||||
"integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-7.0.2.tgz",
|
||||
"integrity": "sha512-OJ2iGMtj5Tg3s6RaXH22cJcxXRi7Y3EBqbHTBRq+PQAqfaS8f/236fUrWhfSn8P4jovyzqucxme7/vWSSZBX2Q==",
|
||||
"dependencies": {
|
||||
"@octokit/request": "^5.6.0",
|
||||
"@octokit/types": "^6.0.3",
|
||||
"@octokit/request": "^8.0.1",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/openapi-types": {
|
||||
"version": "12.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
|
||||
"integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ=="
|
||||
"version": "19.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-19.0.0.tgz",
|
||||
"integrity": "sha512-PclQ6JGMTE9iUStpzMkwLCISFn/wDeRjkZFIKALpvJQNBGwDoYYi2fFvuHwssoQ1rXI5mfh6jgTgWuddeUzfWw=="
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "2.21.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
|
||||
"integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-9.0.0.tgz",
|
||||
"integrity": "sha512-oIJzCpttmBTlEhBmRvb+b9rlnGpmFgDtZ0bB6nq39qIod6A5DP+7RkVLMOixIgRCYSHDTeayWqmiJ2SZ6xgfdw==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.40.0"
|
||||
"@octokit/types": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=2"
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-rest-endpoint-methods": {
|
||||
"version": "5.16.2",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.16.2.tgz",
|
||||
"integrity": "sha512-8QFz29Fg5jDuTPXVtey05BLm7OB+M8fnvE64RNegzX7U+5NUXcOcnpTIK0YfSHBg8gYd0oxIq3IZTe9SfPZiRw==",
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-10.0.1.tgz",
|
||||
"integrity": "sha512-fgS6HPkPvJiz8CCliewLyym9qAx0RZ/LKh3sATaPfM41y/O2wQ4Z9MrdYeGPVh04wYmHFmWiGlKPC7jWVtZXQA==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.39.0",
|
||||
"deprecation": "^2.3.1"
|
||||
"@octokit/types": "^12.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@octokit/core": ">=3"
|
||||
"@octokit/core": ">=5"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request": {
|
||||
"version": "5.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
|
||||
"integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
|
||||
"version": "8.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request/-/request-8.1.4.tgz",
|
||||
"integrity": "sha512-M0aaFfpGPEKrg7XoA/gwgRvc9MSXHRO2Ioki1qrPDbl1e9YhjIwVoHE7HIKmv/m3idzldj//xBujcFNqGX6ENA==",
|
||||
"dependencies": {
|
||||
"@octokit/endpoint": "^6.0.1",
|
||||
"@octokit/request-error": "^2.1.0",
|
||||
"@octokit/types": "^6.16.1",
|
||||
"@octokit/endpoint": "^9.0.0",
|
||||
"@octokit/request-error": "^5.0.0",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"is-plain-object": "^5.0.0",
|
||||
"node-fetch": "^2.6.7",
|
||||
"universal-user-agent": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/request-error": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
|
||||
"integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-5.0.1.tgz",
|
||||
"integrity": "sha512-X7pnyTMV7MgtGmiXBwmO6M5kIPrntOXdyKZLigNfQWSEQzVxR4a4vo49vJjTWX70mPndj8KhfT4Dx+2Ng3vnBQ==",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^6.0.3",
|
||||
"@octokit/types": "^12.0.0",
|
||||
"deprecation": "^2.0.0",
|
||||
"once": "^1.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/types": {
|
||||
"version": "6.41.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
|
||||
"integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-12.0.0.tgz",
|
||||
"integrity": "sha512-EzD434aHTFifGudYAygnFlS1Tl6KhbTynEWELQXIbTY8Msvb5nEqTZIm7sbPEt4mQYLZwu3zPKVdeIrw0g7ovg==",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^12.11.0"
|
||||
"@octokit/openapi-types": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/before-after-hook": {
|
||||
@@ -160,25 +188,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.12",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
|
||||
"integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
@@ -187,11 +196,6 @@
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
@@ -200,6 +204,17 @@
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "5.26.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-5.26.3.tgz",
|
||||
"integrity": "sha512-H7n2zmKEWgOllKkIUkLvFmsJQj062lSm3uA4EYApG8gLuiOM0/go9bIoC3HVaSnfg4xunowDE2i9p8drkXuvDw==",
|
||||
"dependencies": {
|
||||
"@fastify/busboy": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/universal-user-agent": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
|
||||
@@ -213,20 +228,6 @@
|
||||
"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=="
|
||||
},
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
"webidl-conversions": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"license": "CC0",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/github": "^5.1.1"
|
||||
"@actions/github": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
2
.github/actions/draft-release/Dockerfile
vendored
2
.github/actions/draft-release/Dockerfile
vendored
@@ -1,4 +1,4 @@
|
||||
FROM node:18-bullseye
|
||||
FROM node:20-bullseye
|
||||
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y jq
|
||||
|
||||
5
.github/actions/service-tests/action.yml
vendored
5
.github/actions/service-tests/action.yml
vendored
@@ -16,6 +16,10 @@ inputs:
|
||||
description: 'The SERVICETESTS_OBS_PASS secret'
|
||||
required: false
|
||||
default: ''
|
||||
pepy-key:
|
||||
description: 'The SERVICETESTS_PEPY_KEY secret'
|
||||
required: false
|
||||
default: ''
|
||||
sl-insight-user-uuid:
|
||||
description: 'The SERVICETESTS_SL_INSIGHT_USER_UUID secret'
|
||||
required: false
|
||||
@@ -66,6 +70,7 @@ runs:
|
||||
LIBRARIESIO_TOKENS: '${{ inputs.librariesio-tokens }}'
|
||||
OBS_USER: '${{ inputs.obs-user }}'
|
||||
OBS_PASS: '${{ inputs.obs-pass }}'
|
||||
PEPY_KEY: '${{ inputs.pepy-key }}'
|
||||
SL_INSIGHT_USER_UUID: '${{ inputs.sl-insight-user-uuid }}'
|
||||
SL_INSIGHT_API_TOKEN: '${{ inputs.sl-insight-api-token }}'
|
||||
TWITCH_CLIENT_ID: '${{ inputs.twitch-client-id }}'
|
||||
|
||||
10
.github/actions/setup/action.yml
vendored
10
.github/actions/setup/action.yml
vendored
@@ -2,8 +2,12 @@ name: 'Set up project'
|
||||
description: 'Set up project'
|
||||
inputs:
|
||||
node-version:
|
||||
description: 'Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
|
||||
description: 'Version Spec of the node version to use. Examples: 12.x, 10.15.1, >=10.15.0.'
|
||||
required: true
|
||||
npm-version:
|
||||
description: 'Version Spec of the npm version to use. Examples: 9.x, 10.2.3, >=10.1.0.'
|
||||
required: false
|
||||
default: '^10'
|
||||
cypress:
|
||||
description: 'Install Cypress binary (boolean)'
|
||||
type: boolean
|
||||
@@ -19,8 +23,8 @@ runs:
|
||||
with:
|
||||
node-version: ${{ inputs.node-version }}
|
||||
|
||||
- name: Install NPM 9
|
||||
run: npm install -g npm@^9.0.0
|
||||
- name: Install NPM ${{ inputs.npm-version }}
|
||||
run: npm install -g npm@${{ inputs.npm-version }}
|
||||
shell: bash
|
||||
|
||||
- name: Install dependencies
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cypress: true
|
||||
|
||||
- run: .github/scripts/check-docusaurus-versions.sh
|
||||
|
||||
2
.github/workflows/danger.yml
vendored
2
.github/workflows/danger.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Danger
|
||||
run: npm run danger ci
|
||||
|
||||
2
.github/workflows/deploy-docs.yml
vendored
2
.github/workflows/deploy-docs.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Build
|
||||
run: npm run build-docs
|
||||
|
||||
1
.github/workflows/deploy-review-app.yml
vendored
1
.github/workflows/deploy-review-app.yml
vendored
@@ -35,6 +35,7 @@ jobs:
|
||||
LIBRARIESIO_TOKENS=${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}
|
||||
OBS_USER=${{ secrets.SERVICETESTS_OBS_USER }}
|
||||
OBS_PASS=${{ secrets.SERVICETESTS_OBS_PASS }}
|
||||
PEPY_KEY=${{ secrets.SERVICETESTS_PEPY_KEY }}
|
||||
SL_INSIGHT_API_TOKEN=${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}
|
||||
SL_INSIGHT_USER_UUID=${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}
|
||||
TWITCH_CLIENT_ID=${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}
|
||||
|
||||
4
.github/workflows/test-bug-run-badge.yml
vendored
4
.github/workflows/test-bug-run-badge.yml
vendored
@@ -40,7 +40,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cypress: false
|
||||
|
||||
- name: Output debug info
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
run: npm run badge $TEST_BADGE_LINK
|
||||
|
||||
- name: Add Comment to Issue
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueNumber = context.issue.number;
|
||||
|
||||
6
.github/workflows/test-e2e.yml
vendored
6
.github/workflows/test-e2e.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
cypress: true
|
||||
|
||||
- name: Run tests
|
||||
@@ -36,14 +36,14 @@ jobs:
|
||||
|
||||
- name: Archive videos
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: videos
|
||||
path: cypress/videos
|
||||
|
||||
- name: Archive screenshots
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: screenshots
|
||||
path: cypress/screenshots
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Integration@node 20
|
||||
name: Integration@node 21
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-integration-20:
|
||||
test-integration-21:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PAT_EXISTS: ${{ secrets.GH_PAT != '' }}
|
||||
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 21
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
2
.github/workflows/test-integration.yml
vendored
2
.github/workflows/test-integration.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Integration Tests (with PAT)
|
||||
if: ${{ env.PAT_EXISTS == 'true' }}
|
||||
|
||||
2
.github/workflows/test-lint.yml
vendored
2
.github/workflows/test-lint.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: ESLint
|
||||
if: always()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Main@node 20
|
||||
name: Main@node 21
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize]
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
- 'dependabot/**'
|
||||
|
||||
jobs:
|
||||
test-main-20:
|
||||
test-main-21:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 21
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
2
.github/workflows/test-main.yml
vendored
2
.github/workflows/test-main.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Core tests
|
||||
uses: ./.github/actions/core-tests
|
||||
|
||||
2
.github/workflows/test-package-cli.yml
vendored
2
.github/workflows/test-package-cli.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Node JS ${{ inputs.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
|
||||
8
.github/workflows/test-package-lib.yml
vendored
8
.github/workflows/test-package-lib.yml
vendored
@@ -14,11 +14,14 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- node: '16'
|
||||
npm: '^9'
|
||||
engine-strict: 'false'
|
||||
- node: '18'
|
||||
engine-strict: 'true'
|
||||
- node: '20'
|
||||
npm: '^9'
|
||||
engine-strict: 'false'
|
||||
- node: '20'
|
||||
npm: '^10'
|
||||
engine-strict: 'true'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -27,6 +30,7 @@ jobs:
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
npm-version: ${{ matrix.npm }}
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: ${{ matrix.engine-strict }}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Services@node 20
|
||||
name: Services@node 21
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, edited, reopened, synchronize]
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
- 'gh-readonly-queue/**'
|
||||
|
||||
jobs:
|
||||
test-services-20:
|
||||
test-services-21:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 20
|
||||
node-version: 21
|
||||
env:
|
||||
NPM_CONFIG_ENGINE_STRICT: 'false'
|
||||
|
||||
@@ -29,6 +29,7 @@ jobs:
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
3
.github/workflows/test-services.yml
vendored
3
.github/workflows/test-services.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Service tests (triggered from local branch)
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
@@ -27,6 +27,7 @@ jobs:
|
||||
librariesio-tokens: '${{ secrets.SERVICETESTS_LIBRARIESIO_TOKENS }}'
|
||||
obs-user: '${{ secrets.SERVICETESTS_OBS_USER }}'
|
||||
obs-pass: '${{ secrets.SERVICETESTS_OBS_PASS }}'
|
||||
pepy-key: '${{ secrets.SERVICETESTS_PEPY_KEY }}'
|
||||
sl-insight-user-uuid: '${{ secrets.SERVICETESTS_SL_INSIGHT_USER_UUID }}'
|
||||
sl-insight-api-token: '${{ secrets.SERVICETESTS_SL_INSIGHT_API_TOKEN }}'
|
||||
twitch-client-id: '${{ secrets.SERVICETESTS_TWITCH_CLIENT_ID }}'
|
||||
|
||||
2
.github/workflows/update-github-api.yml
vendored
2
.github/workflows/update-github-api.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Setup
|
||||
uses: ./.github/actions/setup
|
||||
with:
|
||||
node-version: 18
|
||||
node-version: 20
|
||||
|
||||
- name: Check for new GitHub API version
|
||||
run: node scripts/update-github-api.js
|
||||
|
||||
50
CHANGELOG.md
50
CHANGELOG.md
@@ -4,6 +4,56 @@ Note: this changelog is for the shields.io server. The changelog for the badge-m
|
||||
|
||||
---
|
||||
|
||||
## server-2024-01-01
|
||||
|
||||
The most important changes in this release for users hosting their own instance are:
|
||||
|
||||
The shields docker image is now based on node 20:
|
||||
|
||||
- deploy on node 20 [#9799](https://github.com/badges/shields/issues/9799)
|
||||
|
||||
It is now possible to use [authentication for DockerHub](https://github.com/badges/shields/blob/master/doc/server-secrets.md#dockerhub) to allow higher API rate limit or access to private repos:
|
||||
|
||||
- call [docker] with auth [#9803](https://github.com/badges/shields/issues/9803)
|
||||
|
||||
### New Badges
|
||||
|
||||
- [Thunderstore] Add Thunderstore Badges [#9782](https://github.com/badges/shields/issues/9782)
|
||||
- Add [Raycast] Badge [#9801](https://github.com/badges/shields/issues/9801)
|
||||
- [GITEA] add new gitea service (release/languages) [#9781](https://github.com/badges/shields/issues/9781)
|
||||
- Add [NpmStatDownloads] Badge [#9783](https://github.com/badges/shields/issues/9783)
|
||||
|
||||
### Frontend Changes
|
||||
|
||||
- improve documentation for [dynamicxml] service [#9798](https://github.com/badges/shields/issues/9798)
|
||||
- add description to interval enums [#9854](https://github.com/badges/shields/issues/9854)
|
||||
- convert 'style' param to enum [#9853](https://github.com/badges/shields/issues/9853)
|
||||
- Ensure social category badges are rendered with social style and logo; affects [gitlab keybase lemmy modrinth thunderstore twitch] gist github reddit [#9859](https://github.com/badges/shields/issues/9859)
|
||||
|
||||
### Fixes
|
||||
|
||||
- [pub] Use official version endpoint for pub-service [#9802](https://github.com/badges/shields/issues/9802)
|
||||
- cache weblate badges for longer [#9786](https://github.com/badges/shields/issues/9786)
|
||||
- [Discourse] Update schema keys to use plural form (`topic_count` -> `topics_count`) [#9778](https://github.com/badges/shields/issues/9778)
|
||||
- cache some badges for longer [#9785](https://github.com/badges/shields/issues/9785)
|
||||
- increase page size for github release badge by semver [#9818](https://github.com/badges/shields/issues/9818)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-12-04
|
||||
|
||||
- move from @renovate/pep440 to @renovatebot/pep440 [#9614](https://github.com/badges/shields/issues/9614)
|
||||
- deprecate/fix [ansible] galaxy services [#9648](https://github.com/badges/shields/issues/9648)
|
||||
- call [pepy] with auth [#9748](https://github.com/badges/shields/issues/9748)
|
||||
- add meaningful descriptions including keywords [#9715](https://github.com/badges/shields/issues/9715)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-11-01
|
||||
|
||||
- fix greasyfork 404 bug [#9632](https://github.com/badges/shields/issues/9632)
|
||||
- Hacktoberfest 2023 support - resolves #9636 [#9637](https://github.com/badges/shields/issues/9637)
|
||||
- switch to fixed OpenCollective images [#9615](https://github.com/badges/shields/issues/9615)
|
||||
- Dependency updates
|
||||
|
||||
## server-2023-10-02
|
||||
|
||||
- add python package total downloads from [pepy] badge [#9564](https://github.com/badges/shields/issues/9564)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM node:18-alpine AS Builder
|
||||
FROM node:20-alpine AS Builder
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
RUN mkdir /usr/src/app/private
|
||||
@@ -19,7 +19,7 @@ RUN npm prune --omit=dev
|
||||
RUN npm cache clean --force
|
||||
|
||||
# Use multi-stage build to reduce size
|
||||
FROM node:18-alpine
|
||||
FROM node:20-alpine
|
||||
|
||||
ARG version=dev
|
||||
ENV DOCKER_SHIELDS_VERSION=$version
|
||||
|
||||
@@ -22,9 +22,6 @@
|
||||
<a href="https://discord.gg/HjJCwm5">
|
||||
<img src="https://img.shields.io/discord/308323056592486420?logo=discord"
|
||||
alt="chat on Discord"></a>
|
||||
<a href="https://twitter.com/intent/follow?screen_name=shields_io">
|
||||
<img src="https://img.shields.io/twitter/follow/shields_io?style=social&logo=twitter"
|
||||
alt="follow on Twitter"></a>
|
||||
</p>
|
||||
|
||||
This is home to [Shields.io][shields.io], a service for concise, consistent,
|
||||
@@ -101,8 +98,8 @@ If you intend on reporting or contributing a fix related to security vulnerabili
|
||||
|
||||
## Development
|
||||
|
||||
1. Install Node 18 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 18 and 20.
|
||||
1. Install Node 20 or later. You can use the [package manager][] of your choice.
|
||||
Tests need to pass in Node 20 and 21.
|
||||
2. Clone this repository.
|
||||
3. Run `npm ci` to install the dependencies.
|
||||
4. Run `npm start` to start the badge server and the frontend dev server.
|
||||
|
||||
@@ -35,7 +35,7 @@ class XmlElement {
|
||||
* Name of the XML tag
|
||||
* @param {Array.<string|module:badge-maker/lib/xml~XmlElement>} [attrs.content=[]]
|
||||
* Array of objects to render inside the tag. content may contain a mix of
|
||||
* string and XmlElement objects. If content is `[]` or ommitted the
|
||||
* string and XmlElement objects. If content is `[]` or omitted the
|
||||
* element will be rendered as a self-closing element.
|
||||
* @param {object} [attrs.attrs={}]
|
||||
* Object representing the tag's attributes as name/value pairs
|
||||
|
||||
@@ -79,6 +79,8 @@ private:
|
||||
bitbucket_server_password: 'BITBUCKET_SERVER_PASS'
|
||||
curseforge_api_key: 'CURSEFORGE_API_KEY'
|
||||
discord_bot_token: 'DISCORD_BOT_TOKEN'
|
||||
dockerhub_username: 'DOCKERHUB_USER'
|
||||
dockerhub_pat: 'DOCKERHUB_PAT'
|
||||
drone_token: 'DRONE_TOKEN'
|
||||
gh_client_id: 'GH_CLIENT_ID'
|
||||
gh_client_secret: 'GH_CLIENT_SECRET'
|
||||
@@ -96,6 +98,7 @@ private:
|
||||
obs_pass: 'OBS_PASS'
|
||||
redis_url: 'REDIS_URL'
|
||||
opencollective_token: 'OPENCOLLECTIVE_TOKEN'
|
||||
pepy_key: 'PEPY_KEY'
|
||||
postgres_url: 'POSTGRES_URL'
|
||||
sentry_dsn: 'SENTRY_DSN'
|
||||
sl_insight_userUuid: 'SL_INSIGHT_USER_UUID'
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { URL } from 'url'
|
||||
import { InvalidParameter } from './errors.js'
|
||||
import dayjs from 'dayjs'
|
||||
import Joi from 'joi'
|
||||
import checkErrorResponse from './check-error-response.js'
|
||||
import { InvalidParameter, InvalidResponse } from './errors.js'
|
||||
import { fetch } from './got.js'
|
||||
import { parseJson } from './json.js'
|
||||
import validate from './validate.js'
|
||||
|
||||
let jwtCache = Object.create(null)
|
||||
|
||||
class AuthHelper {
|
||||
constructor(
|
||||
@@ -87,7 +95,7 @@ class AuthHelper {
|
||||
}
|
||||
}
|
||||
|
||||
shouldAuthenticateRequest({ url, options = {} }) {
|
||||
isAllowedOrigin(url) {
|
||||
let parsed
|
||||
try {
|
||||
parsed = new URL(url)
|
||||
@@ -97,7 +105,11 @@ class AuthHelper {
|
||||
|
||||
const { protocol, host } = parsed
|
||||
const origin = `${protocol}//${host}`
|
||||
const originViolation = !this._authorizedOrigins.includes(origin)
|
||||
return this._authorizedOrigins.includes(origin)
|
||||
}
|
||||
|
||||
shouldAuthenticateRequest({ url, options = {} }) {
|
||||
const originViolation = !this.isAllowedOrigin(url)
|
||||
|
||||
const strictSslCheckViolation =
|
||||
this._requireStrictSslToAuthenticate &&
|
||||
@@ -218,6 +230,103 @@ class AuthHelper {
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
static _getJwtExpiry(token, max = dayjs().add(1, 'hours').unix()) {
|
||||
// get the expiry timestamp for this JWT (capped at a max length)
|
||||
const parts = token.split('.')
|
||||
|
||||
if (parts.length < 2) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'invalid response data from auth endpoint',
|
||||
})
|
||||
}
|
||||
|
||||
const json = validate(
|
||||
{
|
||||
ErrorClass: InvalidResponse,
|
||||
prettyErrorMessage: 'invalid response data from auth endpoint',
|
||||
},
|
||||
parseJson(Buffer.from(parts[1], 'base64').toString()),
|
||||
Joi.object({ exp: Joi.number().required() }).required(),
|
||||
)
|
||||
|
||||
return Math.min(json.exp, max)
|
||||
}
|
||||
|
||||
static _isJwtValid(expiry) {
|
||||
// we consider the token valid if the expiry
|
||||
// datetime is later than (now + 1 minute)
|
||||
return dayjs.unix(expiry).isAfter(dayjs().add(1, 'minutes'))
|
||||
}
|
||||
|
||||
async _getJwt(loginEndpoint) {
|
||||
const { _user: username, _pass: password } = this
|
||||
|
||||
// attempt to get JWT from cache
|
||||
if (
|
||||
jwtCache?.[loginEndpoint]?.[username]?.token &&
|
||||
jwtCache?.[loginEndpoint]?.[username]?.expiry &&
|
||||
this.constructor._isJwtValid(jwtCache[loginEndpoint][username].expiry)
|
||||
) {
|
||||
// cache hit
|
||||
return jwtCache[loginEndpoint][username].token
|
||||
}
|
||||
|
||||
// cache miss - request a new JWT
|
||||
const originViolation = !this.isAllowedOrigin(loginEndpoint)
|
||||
if (originViolation) {
|
||||
throw new InvalidParameter({
|
||||
prettyMessage: 'requested origin not authorized',
|
||||
})
|
||||
}
|
||||
|
||||
const { buffer } = await checkErrorResponse({})(
|
||||
await fetch(loginEndpoint, {
|
||||
method: 'POST',
|
||||
form: { username, password },
|
||||
}),
|
||||
)
|
||||
|
||||
const json = validate(
|
||||
{
|
||||
ErrorClass: InvalidResponse,
|
||||
prettyErrorMessage: 'invalid response data from auth endpoint',
|
||||
},
|
||||
parseJson(buffer),
|
||||
Joi.object({ token: Joi.string().required() }).required(),
|
||||
)
|
||||
|
||||
const token = json.token
|
||||
const expiry = this.constructor._getJwtExpiry(token)
|
||||
|
||||
// store in the cache
|
||||
if (!(loginEndpoint in jwtCache)) {
|
||||
jwtCache[loginEndpoint] = {}
|
||||
}
|
||||
jwtCache[loginEndpoint][username] = { token, expiry }
|
||||
|
||||
return token
|
||||
}
|
||||
|
||||
async _getJwtAuthHeader(loginEndpoint) {
|
||||
if (!this.isConfigured) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const token = await this._getJwt(loginEndpoint)
|
||||
return { Authorization: `Bearer ${token}` }
|
||||
}
|
||||
|
||||
async withJwtAuth(requestParams, loginEndpoint) {
|
||||
const authHeader = await this._getJwtAuthHeader(loginEndpoint)
|
||||
return this._withAnyAuth(requestParams, requestParams =>
|
||||
this.constructor._mergeHeaders(requestParams, authHeader),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export { AuthHelper }
|
||||
function clearJwtCache() {
|
||||
jwtCache = Object.create(null)
|
||||
}
|
||||
|
||||
export { AuthHelper, clearJwtCache }
|
||||
|
||||
@@ -1,7 +1,32 @@
|
||||
import dayjs from 'dayjs'
|
||||
import nock from 'nock'
|
||||
import { expect } from 'chai'
|
||||
import { test, given, forCases } from 'sazerac'
|
||||
import { AuthHelper } from './auth-helper.js'
|
||||
import { InvalidParameter } from './errors.js'
|
||||
import { AuthHelper, clearJwtCache } from './auth-helper.js'
|
||||
import { InvalidParameter, InvalidResponse } from './errors.js'
|
||||
|
||||
function base64UrlEncode(input) {
|
||||
const base64 = btoa(JSON.stringify(input))
|
||||
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
|
||||
}
|
||||
|
||||
function getMockJwt(extras) {
|
||||
// this function returns a mock JWT that contains enough
|
||||
// for a unit test but ignores important aspects e.g: signing
|
||||
|
||||
const header = {
|
||||
alg: 'HS256',
|
||||
typ: 'JWT',
|
||||
}
|
||||
const payload = {
|
||||
iat: Math.floor(Date.now() / 1000),
|
||||
...extras,
|
||||
}
|
||||
|
||||
const encodedHeader = base64UrlEncode(header)
|
||||
const encodedPayload = base64UrlEncode(payload)
|
||||
return `${encodedHeader}.${encodedPayload}`
|
||||
}
|
||||
|
||||
describe('AuthHelper', function () {
|
||||
describe('constructor checks', function () {
|
||||
@@ -381,4 +406,153 @@ describe('AuthHelper', function () {
|
||||
).to.throw(InvalidParameter)
|
||||
})
|
||||
})
|
||||
|
||||
context('JTW Auth', function () {
|
||||
describe('_isJwtValid', function () {
|
||||
test(AuthHelper._isJwtValid, () => {
|
||||
given(dayjs().add(1, 'month').unix()).expect(true)
|
||||
given(dayjs().add(2, 'minutes').unix()).expect(true)
|
||||
given(dayjs().add(30, 'seconds').unix()).expect(false)
|
||||
given(dayjs().unix()).expect(false)
|
||||
given(dayjs().subtract(1, 'seconds').unix()).expect(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('_getJwtExpiry', function () {
|
||||
it('extracts expiry from valid JWT', function () {
|
||||
const nowPlus30Mins = dayjs().add(30, 'minutes').unix()
|
||||
expect(
|
||||
AuthHelper._getJwtExpiry(getMockJwt({ exp: nowPlus30Mins })),
|
||||
).to.equal(nowPlus30Mins)
|
||||
})
|
||||
|
||||
it('caps expiry at max', function () {
|
||||
const nowPlus1Hour = dayjs().add(1, 'hours').unix()
|
||||
const nowPlus2Hours = dayjs().add(2, 'hours').unix()
|
||||
expect(
|
||||
AuthHelper._getJwtExpiry(getMockJwt({ exp: nowPlus2Hours })),
|
||||
).to.equal(nowPlus1Hour)
|
||||
})
|
||||
|
||||
it('throws if JWT does not contain exp', function () {
|
||||
expect(() => {
|
||||
AuthHelper._getJwtExpiry(getMockJwt({}))
|
||||
}).to.throw(InvalidResponse)
|
||||
})
|
||||
|
||||
it('throws if JWT is invalid', function () {
|
||||
expect(() => {
|
||||
AuthHelper._getJwtExpiry('abc')
|
||||
}).to.throw(InvalidResponse)
|
||||
})
|
||||
})
|
||||
|
||||
describe('withJwtAuth', function () {
|
||||
const authHelper = new AuthHelper(
|
||||
{
|
||||
userKey: 'jwt_user',
|
||||
passKey: 'jwt_pass',
|
||||
authorizedOrigins: ['https://example.com'],
|
||||
isRequired: false,
|
||||
},
|
||||
{ private: { jwt_user: 'fred', jwt_pass: 'abc123' } },
|
||||
)
|
||||
|
||||
beforeEach(function () {
|
||||
clearJwtCache()
|
||||
})
|
||||
|
||||
it('should use cached response if valid', async function () {
|
||||
// the expiry is far enough in the future that the token
|
||||
// will still be valid on the second hit
|
||||
const mockToken = getMockJwt({ exp: dayjs().add(1, 'hours').unix() })
|
||||
|
||||
// .times(1) ensures if we try to make a second call to this endpoint,
|
||||
// we will throw `Nock: No match for request`
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken })
|
||||
const params1 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params1).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
// second time round, we'll get the same response again
|
||||
// but this time served from cache
|
||||
const params2 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(params2).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
nock.cleanAll()
|
||||
})
|
||||
|
||||
it('should not use cached response if expired', async function () {
|
||||
// this time we define a token expiry is close enough
|
||||
// that the token will not be valid on the second call
|
||||
const mockToken1 = getMockJwt({
|
||||
exp: dayjs().add(20, 'seconds').unix(),
|
||||
})
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken1 })
|
||||
const params1 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params1).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken1}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
// second time round we make another network request
|
||||
const mockToken2 = getMockJwt({
|
||||
exp: dayjs().add(20, 'seconds').unix(),
|
||||
})
|
||||
nock('https://example.com')
|
||||
.post('/login')
|
||||
.times(1)
|
||||
.reply(200, { token: mockToken2 })
|
||||
const params2 = await authHelper.withJwtAuth(
|
||||
{ url: 'https://example.com/some-endpoint' },
|
||||
'https://example.com/login',
|
||||
)
|
||||
expect(nock.isDone()).to.equal(true)
|
||||
expect(params2).to.deep.equal({
|
||||
options: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${mockToken2}`,
|
||||
},
|
||||
},
|
||||
url: 'https://example.com/some-endpoint',
|
||||
})
|
||||
|
||||
nock.cleanAll()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,7 +20,7 @@ class BaseGraphqlService extends BaseService {
|
||||
/**
|
||||
* Parse data from JSON endpoint
|
||||
*
|
||||
* @param {string} buffer JSON repsonse from upstream API
|
||||
* @param {string} buffer JSON response from upstream API
|
||||
* @returns {object} Parsed response
|
||||
*/
|
||||
_parseJson(buffer) {
|
||||
@@ -50,8 +50,10 @@ class BaseGraphqlService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @param {Function} [attrs.transformJson=data => data] Function which takes the raw json and transforms it before
|
||||
* further procesing. In case of multiple query in a single graphql call and few of them
|
||||
* further processing. In case of multiple query in a single graphql call and few of them
|
||||
* throw error, partial data might be used ignoring the error.
|
||||
* @param {Function} [attrs.transformErrors=defaultTransformErrors]
|
||||
* Function which takes an errors object from a GraphQL
|
||||
@@ -69,6 +71,7 @@ class BaseGraphqlService extends BaseService {
|
||||
options = {},
|
||||
httpErrorMessages = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
transformJson = data => data,
|
||||
transformErrors = defaultTransformErrors,
|
||||
}) {
|
||||
@@ -83,6 +86,7 @@ class BaseGraphqlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors: httpErrorMessages,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
const json = transformJson(this._parseJson(buffer))
|
||||
if (json.errors) {
|
||||
|
||||
@@ -14,7 +14,7 @@ class BaseJsonService extends BaseService {
|
||||
/**
|
||||
* Parse data from JSON endpoint
|
||||
*
|
||||
* @param {string} buffer JSON repsonse from upstream API
|
||||
* @param {string} buffer JSON response from upstream API
|
||||
* @returns {object} Parsed response
|
||||
*/
|
||||
_parseJson(buffer) {
|
||||
@@ -40,6 +40,8 @@ class BaseJsonService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
*/
|
||||
@@ -49,6 +51,7 @@ class BaseJsonService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
const mergedOptions = {
|
||||
...{ headers: { Accept: 'application/json' } },
|
||||
@@ -59,6 +62,7 @@ class BaseJsonService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
const json = this._parseJson(buffer)
|
||||
return this.constructor._validate(json, schema)
|
||||
|
||||
@@ -63,6 +63,8 @@ class BaseSvgScrapingService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
*/
|
||||
@@ -73,6 +75,7 @@ class BaseSvgScrapingService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
const mergedOptions = {
|
||||
@@ -84,6 +87,7 @@ class BaseSvgScrapingService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
logTrace(emojic.dart, 'Response SVG', buffer)
|
||||
const data = {
|
||||
|
||||
@@ -33,6 +33,8 @@ class BaseTomlService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
*/
|
||||
@@ -42,6 +44,7 @@ class BaseTomlService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
const mergedOptions = {
|
||||
@@ -61,6 +64,7 @@ class BaseTomlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
let parsed
|
||||
try {
|
||||
|
||||
@@ -34,6 +34,8 @@ class BaseXmlService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @param {object} [attrs.parserOptions={}] Options to pass to fast-xml-parser. See
|
||||
* [documentation](https://github.com/NaturalIntelligence/fast-xml-parser#xml-to-json)
|
||||
* @returns {object} Parsed response
|
||||
@@ -46,6 +48,7 @@ class BaseXmlService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
parserOptions = {},
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
@@ -58,6 +61,7 @@ class BaseXmlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
const validateResult = XMLValidator.validate(buffer)
|
||||
if (validateResult !== true) {
|
||||
|
||||
@@ -33,6 +33,8 @@ class BaseYamlService extends BaseService {
|
||||
* See {@link https://github.com/sindresorhus/got/blob/main/documentation/7-retry.md#errorcodes got error codes}
|
||||
* for allowed keys
|
||||
* and {@link module:core/base-service/errors~RuntimeErrorProps} for allowed values
|
||||
* @param {number[]} [attrs.logErrors=[429]] An array of http error codes
|
||||
* that will be logged (to sentry, if configured).
|
||||
* @param {object} [attrs.encoding='utf8'] Character encoding
|
||||
* @returns {object} Parsed response
|
||||
* @see https://github.com/sindresorhus/got/blob/main/documentation/2-options.md
|
||||
@@ -43,6 +45,7 @@ class BaseYamlService extends BaseService {
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
encoding = 'utf8',
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
@@ -60,6 +63,7 @@ class BaseYamlService extends BaseService {
|
||||
options: mergedOptions,
|
||||
httpErrors,
|
||||
systemErrors,
|
||||
logErrors,
|
||||
})
|
||||
let parsed
|
||||
try {
|
||||
|
||||
@@ -263,7 +263,13 @@ class BaseService {
|
||||
this._metricHelper = metricHelper
|
||||
}
|
||||
|
||||
async _request({ url, options = {}, httpErrors = {}, systemErrors = {} }) {
|
||||
async _request({
|
||||
url,
|
||||
options = {},
|
||||
httpErrors = {},
|
||||
systemErrors = {},
|
||||
logErrors = [429],
|
||||
}) {
|
||||
const logTrace = (...args) => trace.logTrace('fetch', ...args)
|
||||
let logUrl = url
|
||||
const logOptions = Object.assign({}, options)
|
||||
@@ -290,7 +296,7 @@ class BaseService {
|
||||
)
|
||||
await this._meterResponse(res, buffer)
|
||||
logTrace(emojic.dart, 'Response status code', res.statusCode)
|
||||
return checkErrorResponse(httpErrors)({ buffer, res })
|
||||
return checkErrorResponse(httpErrors, logErrors)({ buffer, res })
|
||||
}
|
||||
|
||||
static enabledMetrics = []
|
||||
|
||||
@@ -322,7 +322,7 @@ describe('BaseService', function () {
|
||||
})
|
||||
|
||||
describe('ScoutCamp integration', function () {
|
||||
// TODO Strangly, without the useless escape the regexes do not match in Node 12.
|
||||
// TODO Strangely, without the useless escape the regexes do not match in Node 12.
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
const expectedRouteRegex = /^\/foo(?:\/([^\/#\?]+?))(|\.svg|\.json)$/
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import log from '../server/log.js'
|
||||
import { NotFound, InvalidResponse, Inaccessible } from './errors.js'
|
||||
|
||||
const defaultErrorMessages = {
|
||||
@@ -5,7 +6,7 @@ const defaultErrorMessages = {
|
||||
429: 'rate limited by upstream service',
|
||||
}
|
||||
|
||||
export default function checkErrorResponse(httpErrors = {}) {
|
||||
export default function checkErrorResponse(httpErrors = {}, logErrors = [429]) {
|
||||
return async function ({ buffer, res }) {
|
||||
let error
|
||||
httpErrors = { ...defaultErrorMessages, ...httpErrors }
|
||||
@@ -25,6 +26,11 @@ export default function checkErrorResponse(httpErrors = {}) {
|
||||
error = new InvalidResponse(props)
|
||||
}
|
||||
}
|
||||
|
||||
if (logErrors.includes(res.statusCode)) {
|
||||
log.error(new Error(`${res.statusCode} calling ${res.requestUrl.origin}`))
|
||||
}
|
||||
|
||||
if (error) {
|
||||
error.response = res
|
||||
error.buffer = buffer
|
||||
|
||||
@@ -47,7 +47,7 @@ describe('async error handler', function () {
|
||||
|
||||
context('when status is 429', function () {
|
||||
const buffer = Buffer.from('some stuff')
|
||||
const res = { statusCode: 429 }
|
||||
const res = { statusCode: 429, requestUrl: new URL('https://example.com/') }
|
||||
|
||||
it('throws InvalidResponse', async function () {
|
||||
try {
|
||||
|
||||
@@ -241,10 +241,10 @@ function category2openapi(category, services) {
|
||||
name: 'style',
|
||||
in: 'query',
|
||||
required: false,
|
||||
description:
|
||||
'One of: flat (default), flat-square, plastic, for-the-badge, social',
|
||||
description: 'If not specified, the defautl style is "flat".',
|
||||
schema: {
|
||||
type: 'string',
|
||||
enum: ['flat', 'flat-square', 'plastic', 'for-the-badge', 'social'],
|
||||
},
|
||||
example: 'flat',
|
||||
},
|
||||
@@ -363,7 +363,7 @@ function pathParam({
|
||||
* { name: 'name2', example: 'example2' },
|
||||
* )
|
||||
* ```
|
||||
* is equivilent to
|
||||
* is equivalent to
|
||||
* ```
|
||||
* const params = [
|
||||
* pathParam({ name: 'name1', example: 'example1' }),
|
||||
@@ -409,11 +409,11 @@ function queryParam({
|
||||
* { name: 'name2', example: 'example2' },
|
||||
* )
|
||||
* ```
|
||||
* is equivilent to
|
||||
* is equivalent to
|
||||
* ```
|
||||
* const params = [
|
||||
* queryParam({ name: 'name1', example: 'example1' }),
|
||||
* queryParams({ name: 'name2', example: 'example2' }),
|
||||
* queryParam({ name: 'name2', example: 'example2' }),
|
||||
* ]
|
||||
* ```
|
||||
*
|
||||
|
||||
@@ -88,9 +88,11 @@ const expected = {
|
||||
name: 'style',
|
||||
in: 'query',
|
||||
required: false,
|
||||
description:
|
||||
'One of: flat (default), flat-square, plastic, for-the-badge, social',
|
||||
schema: { type: 'string' },
|
||||
description: 'If not specified, the defautl style is "flat".',
|
||||
schema: {
|
||||
enum: ['flat', 'flat-square', 'plastic', 'for-the-badge', 'social'],
|
||||
type: 'string',
|
||||
},
|
||||
example: 'flat',
|
||||
},
|
||||
logo: {
|
||||
|
||||
@@ -128,6 +128,7 @@ const publicConfigSchema = Joi.object({
|
||||
},
|
||||
restApiVersion: Joi.date().raw().required(),
|
||||
},
|
||||
gitea: defaultService,
|
||||
gitlab: defaultService,
|
||||
jira: defaultService,
|
||||
jenkins: Joi.object({
|
||||
@@ -164,10 +165,13 @@ const privateConfigSchema = Joi.object({
|
||||
azure_devops_token: Joi.string(),
|
||||
curseforge_api_key: Joi.string(),
|
||||
discord_bot_token: Joi.string(),
|
||||
dockerhub_username: Joi.string(),
|
||||
dockerhub_pat: Joi.string(),
|
||||
drone_token: Joi.string(),
|
||||
gh_client_id: Joi.string(),
|
||||
gh_client_secret: Joi.string(),
|
||||
gh_token: Joi.string(),
|
||||
gitea_token: Joi.string(),
|
||||
gitlab_token: Joi.string(),
|
||||
jenkins_user: Joi.string(),
|
||||
jenkins_pass: Joi.string(),
|
||||
@@ -185,6 +189,7 @@ const privateConfigSchema = Joi.object({
|
||||
obs_pass: Joi.string(),
|
||||
redis_url: Joi.string().uri({ scheme: ['redis', 'rediss'] }),
|
||||
opencollective_token: Joi.string(),
|
||||
pepy_key: Joi.string(),
|
||||
postgres_url: Joi.string().uri({ scheme: 'postgresql' }),
|
||||
sentry_dsn: Joi.string(),
|
||||
sl_insight_userUuid: Joi.string(),
|
||||
|
||||
@@ -25,7 +25,7 @@ and learn about the [GitHub workflow](http://try.github.io/).
|
||||
|
||||
#### Node, NPM
|
||||
|
||||
Node >=18 and NPM 9.x is required. If you don't already have them,
|
||||
Node >=20 and NPM 9.x or 10.x is required. If you don't already have them,
|
||||
install node and npm: https://nodejs.org/en/download/
|
||||
|
||||
### Setup a dev install
|
||||
@@ -134,9 +134,9 @@ export default class Example extends BaseService {
|
||||
|
||||
Description of the code:
|
||||
|
||||
1. Our service badge class will extend `BaseService` so we need to require it. Variables are declared with `const` and `let` in preference to `var`.
|
||||
1. Our service badge class will extend `BaseService` so we need to import it.
|
||||
2. Our module must export a class which extends `BaseService`.
|
||||
3. Returns the name of the category to sort this badge into (eg. "build"). Used to sort the examples on the main [shields.io](https://shields.io) website. [Here](https://github.com/badges/shields/blob/master/services/categories.js) is the list of the valid categories. See [section 4.4](#44-adding-an-example-to-the-front-page) for more details on examples.
|
||||
3. Returns the name of the category to sort this badge into (eg. "build"). Used to sort the examples on the main [shields.io](https://shields.io) website. [Here](https://github.com/badges/shields/blob/master/services/categories.js) is the list of the valid categories. See [section 4.4](#44-adding-documentation-to-the-frontend) for more details.
|
||||
4. `route` declares the URL path at which the service operates. It also maps components of the URL path to handler parameters.
|
||||
- `base` defines the first part of the URL that doesn't change, e.g. `/example/`.
|
||||
- `pattern` defines the variable part of the route, everything that comes after `/example/`. It can include any
|
||||
@@ -223,7 +223,7 @@ Description of the code:
|
||||
2. Our badge will query a JSON API so we will extend `BaseJsonService` instead of `BaseService`. This contains some helpers to reduce the need for boilerplate when calling a JSON API.
|
||||
3. We perform input validation by defining a schema which we expect the JSON we receive to conform to. This is done using [Joi](https://github.com/hapijs/joi). Defining a schema means we can ensure the JSON we receive meets our expectations and throw an error if we receive unexpected input without having to explicitly code validation checks. The schema also acts as a filter on the JSON object. Any properties we're going to reference need to be validated, otherwise they will be filtered out. In this case our schema declares that we expect to receive an object which must have a property called 'version', which is a string. There is further documentation on [input validation](input-validation.md).
|
||||
4. Our module exports a class which extends `BaseJsonService`
|
||||
5. Returns the name of the category to sort this badge into (eg. "build"). Used to sort the examples on the main [shields.io](https://shields.io) website. [Here](https://github.com/badges/shields/blob/master/services/categories.js) is the list of the valid categories. See [section 4.4](#44-adding-an-example-to-the-front-page) for more details on examples.
|
||||
5. Returns the name of the category to sort this badge into (eg. "build"). Used to sort the examples on the main [shields.io](https://shields.io) website. [Here](https://github.com/badges/shields/blob/master/services/categories.js) is the list of the valid categories. See [section 4.4](#44-adding-documentation-to-the-frontend) for more details.
|
||||
6. As with our previous badge, we need to declare a route. This time we will capture a variable called `gem`.
|
||||
7. We can use `defaultBadgeData` to set a default `color`, `logo` and/or `label`. If `handle()` doesn't return any of these keys, we'll use the default. Instead of explicitly setting the label text when we return a badge object, we'll use `defaultBadgeData` here to define it declaratively.
|
||||
8. We now jump to the bottom of the example code to the function all badges must implement: `async handle()`. This is the function the server will invoke to handle an incoming request. Because our URL pattern captures a variable called `gem`, our function signature is `async handle({ gem })`. We usually separate the process of generating a badge into 2 stages or concerns: fetch and render. The `fetch()` function is responsible for calling an API endpoint to get data. The `render()` function formats the data for display. In a case where there is a lot of calculation or intermediate steps, this pattern may be thought of as fetch, transform, render and it might be necessary to define some helper functions to assist with the 'transform' step.
|
||||
@@ -269,42 +269,88 @@ import { NotFound } from '../index.js'
|
||||
throw new NotFound({ prettyMessage: 'package not found' })
|
||||
```
|
||||
|
||||
### (4.4) Adding an Example to the Front Page
|
||||
### (4.4) Adding Documentation to the Frontend
|
||||
|
||||
Once we have implemented our badge, we can add it to the index so that users can discover it. We will do this by adding an additional array `examples` to our class.
|
||||
To render the shields.io website, we produce an [OpenAPI 3 specification](https://swagger.io/specification/) which describes the available badge endpoints. Then we use that specification to render the frontend.
|
||||
|
||||
Once we have implemented our badge, we want to add it to the index so that users can discover it. We will do this by adding an additional object `openApi` to our class. This object contains an [OpenAPI Paths Object](https://swagger.io/specification/#paths-object) describing the endpoint or endpoints exposed by this service class.
|
||||
|
||||
```js
|
||||
// (1)
|
||||
import { pathParams } from '../index.js'
|
||||
|
||||
export default class GemVersion extends BaseJsonService {
|
||||
// ...
|
||||
|
||||
// (1)
|
||||
// (2)
|
||||
static category = 'version'
|
||||
|
||||
// (2)
|
||||
static examples = [
|
||||
{
|
||||
// (3)
|
||||
title: 'Gem',
|
||||
namedParams: { gem: 'formatador' },
|
||||
staticPreview: this.render({ version: '2.1.0' }),
|
||||
keywords: ['ruby'],
|
||||
static openApi = {
|
||||
// (3)
|
||||
'/gem/v/{gem}': {
|
||||
// (4)
|
||||
get: {
|
||||
// (5)
|
||||
summary: 'Gem Version',
|
||||
description:
|
||||
'[Ruby Gems](https://rubygems.org/) is a registry for ruby libraries',
|
||||
// (6)
|
||||
parameters: pathParams({
|
||||
name: 'gem',
|
||||
description: 'Name of the Ruby Gem',
|
||||
example: 'formatador',
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
1. We defined category earlier in the tutorial. The `category` property defines which heading in the index our example will appear under.
|
||||
2. The examples property defines an array of examples. In this case the array will contain a single object, but in some cases it is helpful to provide multiple usage examples.
|
||||
3. Our example object should contain the following properties:
|
||||
- `title`: Descriptive text that will be shown next to the badge
|
||||
- `namedParams`: Provide a valid example of params we can substitute into
|
||||
the pattern. In this case we need a valid ruby gem, so we've picked [formatador](https://rubygems.org/gems/formatador).
|
||||
- `staticPreview`: On the index page we want to show an example badge, but for performance reasons we want that example to be generated without making an API call. `staticPreview` should be populated by calling our `render()` method with some valid data.
|
||||
- `keywords`: If we want to provide additional keywords other than the title and the category, we can add them here. This helps users to search for relevant badges.
|
||||
1. There are four helper functions we can use to assemble [Open API Parameter objects](https://swagger.io/specification/#parameter-object). These are:
|
||||
|
||||
- `pathParam` - returns a single Parameter object describing a single path parameter
|
||||
- `pathParams` - returns an array of path parameter objects
|
||||
- `queryParam` - returns a single Parameter object describing a single query parameter
|
||||
- `queryParams` - returns an array of query parameter objects
|
||||
|
||||
These four helper functions are documented in more detail at http://contributing.shields.io/module-core_base-service_openapi.html
|
||||
|
||||
2. We defined category earlier in the tutorial. The `category` property defines which heading in the index our example will appear under.
|
||||
3. The keys of the `openApi` object are routes. In this case we only need to describe one route. In some cases, a service class can define more than one badge route. Open API doesn't have the concept of optional path parameters (more specifically, `in: 'path'` implies `required: true`) so if there are any optional path parameters in our route, our `openApi` object needs to describe two URLs: one without the optional parameter, and another with it.
|
||||
4. The HTTP method. Shields only allows GET requests, so this is always `get`.
|
||||
5. `summary` (required) is a short title or description of the badge. `description` is an optional longer description or additional documentation. We can use markdown or HTML syntax inside the `description` field.
|
||||
6. `parameters` is an array of [Open API Parameter objects](https://swagger.io/specification/#parameter-object) describing any parameters we can pass to this route. This array should include all path parameters included in the key that this value object describes and all relevant query parameters. As a minimum, we need to supply `name` and `example`. The example must be a valid example of a value we can provide for this parameter. In this case we need a valid ruby gem, so we've picked [formatador](https://rubygems.org/gems/formatador). There are also optional keys we can pass. The code
|
||||
|
||||
```js
|
||||
parameters: pathParams({
|
||||
name: 'gem',
|
||||
description: 'Name of the Ruby Gem',
|
||||
example: 'formatador',
|
||||
})
|
||||
```
|
||||
|
||||
is equivalent to
|
||||
|
||||
```js
|
||||
parameters: [
|
||||
{
|
||||
name: 'gem',
|
||||
in: 'path',
|
||||
required: true,
|
||||
schema: { type: 'string' },
|
||||
example: 'formatador',
|
||||
description: 'Name of the Ruby Gem',
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
but we have used the helper function `pathParams` to imply some defaults and reduce the amount of code we need to write by hand.
|
||||
|
||||
Save, run `npm start`, and you can see it [locally](http://127.0.0.1:3000/).
|
||||
|
||||
If you update `examples`, you don't have to restart the server. Run `npm run defs` in another terminal window and the frontend will update.
|
||||
If you update `openApi`, you don't have to restart the server. Run `npm run prestart` in another terminal window and the frontend will update.
|
||||
|
||||
Note: Some services define this information in an array property called `examples`. This is deprecated and we're in the process of converting them. New services should declare an `openApi` object.
|
||||
|
||||
### (4.5) Write Tests<!-- Change the link below when you change the heading -->
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ When we receive input data from an upstream API, we perform input validation to:
|
||||
|
||||
- We don't need to validate characteristics we don't rely on. For example, if we're just going to render a version on a badge with the same exact value from the API response and do not need to sort or transform the value, then it doesn't matter what format the version number is in. We can use a very relaxed schema to validate in this case, e.g. `Joi.string().required()`
|
||||
|
||||
- http://mterczynski.pl/joi-schema-generator/ is a tool that can be used to reverse engineer a schema from an API response. This can be a great starting point to tweak from. If using this as a starting point, remember to remove fields we don't rely on to render a badge.
|
||||
|
||||
- If theory (docs) and practice (real-world API responses) conflict, real-world outputs take precedence over documented behaviour. e.g: if the docs say version is a semver but we learn that there are real-world packages where the version number is `0.3b` or `1.2.1.27` then we should accept those values in preference to enforcing the documented API behaviour.
|
||||
|
||||
- Shields is descriptive rather than prescriptive. We reflect the established norms of the communities we serve.
|
||||
|
||||
@@ -4,13 +4,13 @@ This document describes how to host your own shields server either from source o
|
||||
|
||||
## Installing from Source
|
||||
|
||||
You will need Node 18 or later, which you can install using a
|
||||
You will need Node 20 or later, which you can install using a
|
||||
[package manager][].
|
||||
|
||||
On Ubuntu / Debian:
|
||||
|
||||
```sh
|
||||
curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash -; sudo apt-get install -y nodejs
|
||||
curl -sL https://deb.nodesource.com/setup_20.x | sudo -E bash -; sudo apt-get install -y nodejs
|
||||
```
|
||||
|
||||
```sh
|
||||
|
||||
@@ -112,13 +112,25 @@ generated API key.
|
||||
|
||||
### Discord
|
||||
|
||||
Using a token for Dicsord is optional but will allow higher API rates.
|
||||
Using a token for Discord is optional but will allow higher API rates.
|
||||
|
||||
- `DISCORD_BOT_TOKEN` (yml: `discord_bot_token`)
|
||||
|
||||
Register an application in the [Discord developer console](https://discord.com/developers).
|
||||
To obtain a token, simply create a bot for your application.
|
||||
|
||||
### DockerHub
|
||||
|
||||
Using authentication for DockerHub is optional but can be used to allow
|
||||
higher API rates or access to private repos.
|
||||
|
||||
- `DOCKERHUB_USER` (yml: `private.dockerhub_username`)
|
||||
- `DOCKERHUB_PAT` (yml: `private.dockerhub_pat`)
|
||||
|
||||
`DOCKERHUB_PAT` is a Personal Access Token. Generate a token in your
|
||||
[account security settings](https://hub.docker.com/settings/security) with
|
||||
"Read-Only" or "Public Repo Read-Only", depending on your needs.
|
||||
|
||||
### Drone
|
||||
|
||||
- `DRONE_ORIGINS` (yml: `public.services.drone.authorizedOrigins`)
|
||||
@@ -167,6 +179,15 @@ These settings are used by shields.io for GitHub OAuth app authorization
|
||||
but will not be necessary for most self-hosted installations. See
|
||||
[production-hosting.md](./production-hosting.md).
|
||||
|
||||
### Gitea
|
||||
|
||||
- `GITEA_ORIGINS` (yml: `public.services.gitea.authorizedOrigins`)
|
||||
- `GITEA_TOKEN` (yml: `private.gitea_token`)
|
||||
|
||||
A Gitea [Personal Access Token][gitea-pat] is required for accessing private content. If you need a Gitea token for your self-hosted Shields server then we recommend limiting the scopes to the minimal set necessary for the badges you are using.
|
||||
|
||||
[gitea-pat]: https://docs.gitea.com/development/api-usage#generating-and-listing-api-tokens
|
||||
|
||||
### GitLab
|
||||
|
||||
- `GITLAB_ORIGINS` (yml: `public.services.gitlab.authorizedOrigins`)
|
||||
@@ -254,6 +275,14 @@ OpenCollective's GraphQL API only allows 10 reqs/minute for anonymous users.
|
||||
An [API token](https://graphql-docs-v2.opencollective.com/access)
|
||||
can be provided to access a higher rate limit of 100 reqs/minute.
|
||||
|
||||
### Pepy
|
||||
|
||||
- `PEPY_KEY` (yml: `pepy_key`)
|
||||
|
||||
The Pepy API requires authentication. To obtain a key,
|
||||
Create an account, sign in and obtain generate a key on your
|
||||
[account page](https://www.pepy.tech/user).
|
||||
|
||||
### SymfonyInsight (formerly Sensiolabs)
|
||||
|
||||
- `SL_INSIGHT_USER_UUID` (yml: `private.sl_insight_userUuid`)
|
||||
|
||||
14
frontend/blog/2023-11-29-simpleicons10.md
Normal file
14
frontend/blog/2023-11-29-simpleicons10.md
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
slug: simple-icons-10
|
||||
title: Simple Icons 10
|
||||
authors:
|
||||
name: chris48s
|
||||
title: Shields.io Core Team
|
||||
url: https://github.com/chris48s
|
||||
image_url: https://avatars.githubusercontent.com/u/6025893
|
||||
tags: []
|
||||
---
|
||||
|
||||
Logos on Shields.io are provided by SimpleIcons. We've recently upgraded to SimpleIcons 10. This release removes 45 icons. A full list of the removals can be found in the [release notes](https://github.com/simple-icons/simple-icons/releases/tag/10.0.0).
|
||||
|
||||
Please remember that we are just consumers of SimpleIcons. Decisions about changes and removals are made by the [SimpleIcons](https://github.com/simple-icons/simple-icons) project.
|
||||
@@ -92,10 +92,6 @@ const config = {
|
||||
label: 'Discord',
|
||||
href: 'https://discord.gg/HjJCwm5',
|
||||
},
|
||||
{
|
||||
label: 'Twitter',
|
||||
href: 'https://twitter.com/shields_io',
|
||||
},
|
||||
{
|
||||
label: 'Awesome Badges',
|
||||
href: 'https://github.com/badges/awesome-badges',
|
||||
|
||||
@@ -23,7 +23,7 @@ Shields.io is possible thanks to the people and companies who donate money, serv
|
||||
|
||||
<p>
|
||||
<object
|
||||
data="https://opencollective.com/shields/sponsors.svg?avatarHeight=80&width=600"
|
||||
data="https://opencollective.com/shields/tiers/sponsor.svg?avatarHeight=80&width=600"
|
||||
class="opencollective-image"
|
||||
></object>
|
||||
</p>
|
||||
@@ -34,7 +34,7 @@ Shields.io is possible thanks to the people and companies who donate money, serv
|
||||
|
||||
<p>
|
||||
<object
|
||||
data="https://opencollective.com/shields/backers.svg?width=600"
|
||||
data="https://opencollective.com/shields/tiers/backer.svg?width=600"
|
||||
class="opencollective-image">
|
||||
</object>
|
||||
</p>
|
||||
|
||||
1954
package-lock.json
generated
1954
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@@ -21,9 +21,9 @@
|
||||
"url": "https://github.com/badges/shields"
|
||||
},
|
||||
"dependencies": {
|
||||
"@renovate/pep440": "^1.0.0",
|
||||
"@renovatebot/ruby-semver": "^3.0.15",
|
||||
"@sentry/node": "^7.72.0",
|
||||
"@renovatebot/pep440": "^3.0.17",
|
||||
"@renovatebot/ruby-semver": "^3.0.22",
|
||||
"@sentry/node": "^7.91.0",
|
||||
"@shields_io/camp": "^18.1.2",
|
||||
"@xmldom/xmldom": "0.8.10",
|
||||
"badge-maker": "file:badge-maker",
|
||||
@@ -41,10 +41,10 @@
|
||||
"fast-xml-parser": "^4.3.2",
|
||||
"glob": "^10.3.10",
|
||||
"global-agent": "^3.0.0",
|
||||
"got": "^13.0.0",
|
||||
"got": "^14.0.0",
|
||||
"graphql": "16.8.1",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"joi": "17.10.2",
|
||||
"joi": "17.11.0",
|
||||
"joi-extension-semver": "5.0.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsonpath": "~1.1.1",
|
||||
@@ -59,14 +59,14 @@
|
||||
"pg": "^8.11.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"priorityqueuejs": "^2.0.0",
|
||||
"prom-client": "^14.2.0",
|
||||
"prom-client": "^15.1.0",
|
||||
"qs": "^6.11.2",
|
||||
"query-string": "^8.1.0",
|
||||
"semver": "~7.5.4",
|
||||
"simple-icons": "9.16.0",
|
||||
"smol-toml": "1.1.2",
|
||||
"simple-icons": "10.4.0",
|
||||
"smol-toml": "1.1.3",
|
||||
"webextension-store-meta": "^1.0.5",
|
||||
"xpath": "~0.0.33"
|
||||
"xpath": "~0.0.34"
|
||||
},
|
||||
"scripts": {
|
||||
"coverage:test:core": "c8 npm run test:core",
|
||||
@@ -97,7 +97,7 @@
|
||||
"pretest": "cross-env BASE_URL=http://localhost:8080 run-s --silent defs",
|
||||
"test": "run-s --silent --continue-on-error lint test:package test:core test:entrypoint check-types:package prettier:check",
|
||||
"check-types:package": "tsd badge-maker",
|
||||
"depcheck": "check-node-version --node \">= 18.0\"",
|
||||
"depcheck": "check-node-version --node \">= 20.0\"",
|
||||
"prebuild": "run-s --silent depcheck",
|
||||
"defs": "node scripts/export-openapi-cli.js",
|
||||
"build": "rimraf public && run-s defs docusaurus:build",
|
||||
@@ -145,9 +145,9 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@docusaurus/core": "^2.4.3",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.36.0",
|
||||
"@easyops-cn/docusaurus-search-local": "^0.40.1",
|
||||
"@mdx-js/react": "^1.6.21",
|
||||
"@typescript-eslint/parser": "^6.7.3",
|
||||
"@typescript-eslint/parser": "^6.16.0",
|
||||
"c8": "^8.0.1",
|
||||
"caller": "^1.1.0",
|
||||
"chai": "^4.3.10",
|
||||
@@ -156,65 +156,65 @@
|
||||
"chai-string": "^1.4.0",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"clsx": "^2.0.0",
|
||||
"concurrently": "^8.2.1",
|
||||
"cypress": "^13.3.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"cypress": "^13.6.2",
|
||||
"cypress-wait-for-stable-dom": "^0.1.0",
|
||||
"danger": "^11.3.0",
|
||||
"danger": "^11.3.1",
|
||||
"deepmerge": "^4.3.1",
|
||||
"docusaurus-preset-openapi": "0.6.4",
|
||||
"eslint": "8.50.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint": "8.56.0",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-config-standard": "17.1.0",
|
||||
"eslint-config-standard-jsx": "11.0.0",
|
||||
"eslint-config-standard-react": "13.0.0",
|
||||
"eslint-plugin-chai-friendly": "^0.7.2",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"eslint-plugin-icedfrisby": "^0.1.0",
|
||||
"eslint-plugin-import": "^2.28.1",
|
||||
"eslint-plugin-jsdoc": "^46.8.2",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-jsdoc": "^46.9.1",
|
||||
"eslint-plugin-mocha": "^10.2.0",
|
||||
"eslint-plugin-no-extension-in-require": "^0.2.0",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "6.1.1",
|
||||
"eslint-plugin-react": "^7.33.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-sort-class-members": "^1.18.0",
|
||||
"eslint-plugin-sort-class-members": "^1.19.0",
|
||||
"form-data": "^4.0.0",
|
||||
"icedfrisby": "4.0.0",
|
||||
"icedfrisby-nock": "^2.1.0",
|
||||
"is-svg": "^5.0.0",
|
||||
"jsdoc": "^4.0.2",
|
||||
"lint-staged": "^14.0.1",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lodash.difference": "^4.5.0",
|
||||
"minimist": "^1.2.8",
|
||||
"mocha": "^10.2.0",
|
||||
"mocha-env-reporter": "^4.0.0",
|
||||
"mocha-junit-reporter": "^2.2.1",
|
||||
"mocha-yaml-loader": "^1.0.3",
|
||||
"nock": "13.3.3",
|
||||
"node-mocks-http": "^1.13.0",
|
||||
"nodemon": "^3.0.1",
|
||||
"nock": "13.4.0",
|
||||
"node-mocks-http": "^1.14.0",
|
||||
"nodemon": "^3.0.2",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"open-cli": "^7.2.0",
|
||||
"open-cli": "^8.0.0",
|
||||
"portfinder": "^1.0.32",
|
||||
"prettier": "3.0.3",
|
||||
"prism-react-renderer": "^2.1.0",
|
||||
"prettier": "3.1.1",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"read-all-stdin-sync": "^1.0.5",
|
||||
"rimraf": "^5.0.5",
|
||||
"sazerac": "^2.0.0",
|
||||
"simple-git-hooks": "^2.9.0",
|
||||
"sinon": "^16.0.0",
|
||||
"sinon": "^17.0.1",
|
||||
"sinon-chai": "^3.7.0",
|
||||
"snap-shot-it": "^7.9.10",
|
||||
"start-server-and-test": "2.0.1",
|
||||
"tsd": "^0.29.0",
|
||||
"start-server-and-test": "2.0.3",
|
||||
"tsd": "^0.30.1",
|
||||
"url": "^0.11.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.16.0",
|
||||
"npm": "^9.0.0"
|
||||
"node": "^20.10.0",
|
||||
"npm": "^9.0.0 || ^10.0.0"
|
||||
},
|
||||
"type": "module",
|
||||
"collective": {
|
||||
|
||||
@@ -2,7 +2,8 @@ import Joi from 'joi'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
|
||||
const keywords = ['amo', 'firefox']
|
||||
const description =
|
||||
'[addons.mozilla.org](https://addons.mozilla.org) (AMO) publishes extensions for Mozilla Firefox'
|
||||
|
||||
const schema = Joi.object({
|
||||
average_daily_users: nonNegativeInteger,
|
||||
@@ -26,4 +27,4 @@ class BaseAmoService extends BaseJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseAmoService, keywords }
|
||||
export { BaseAmoService, description }
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { redirector, pathParams } from '../index.js'
|
||||
import { BaseAmoService } from './amo-base.js'
|
||||
import { BaseAmoService, description as baseDescription } from './amo-base.js'
|
||||
|
||||
const description = `${baseDescription}
|
||||
|
||||
const description = `
|
||||
Previously \`amo/d\` provided a “total downloads” badge. However,
|
||||
[updates to the v3 API](https://github.com/badges/shields/issues/3079)
|
||||
only give us weekly downloads. The route \`amo/d\` redirects to \`amo/dw\`.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { starRating } from '../text-formatters.js'
|
||||
import { floorCount as floorCountColor } from '../color-formatters.js'
|
||||
import { pathParams } from '../index.js'
|
||||
import { BaseAmoService } from './amo-base.js'
|
||||
import { BaseAmoService, description } from './amo-base.js'
|
||||
|
||||
export default class AmoRating extends BaseAmoService {
|
||||
static category = 'rating'
|
||||
@@ -11,12 +11,14 @@ export default class AmoRating extends BaseAmoService {
|
||||
'/amo/rating/{addonId}': {
|
||||
get: {
|
||||
summary: 'Mozilla Add-on Rating',
|
||||
description,
|
||||
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
|
||||
},
|
||||
},
|
||||
'/amo/stars/{addonId}': {
|
||||
get: {
|
||||
summary: 'Mozilla Add-on Stars',
|
||||
description,
|
||||
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { pathParams } from '../index.js'
|
||||
import { BaseAmoService } from './amo-base.js'
|
||||
import { BaseAmoService, description } from './amo-base.js'
|
||||
|
||||
export default class AmoUsers extends BaseAmoService {
|
||||
static category = 'downloads'
|
||||
@@ -10,6 +10,7 @@ export default class AmoUsers extends BaseAmoService {
|
||||
'/amo/users/{addonId}': {
|
||||
get: {
|
||||
summary: 'Mozilla Add-on Users',
|
||||
description,
|
||||
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { renderVersionBadge } from '../version.js'
|
||||
import { pathParams } from '../index.js'
|
||||
import { BaseAmoService } from './amo-base.js'
|
||||
import { BaseAmoService, description } from './amo-base.js'
|
||||
|
||||
export default class AmoVersion extends BaseAmoService {
|
||||
static category = 'version'
|
||||
@@ -10,6 +10,7 @@ export default class AmoVersion extends BaseAmoService {
|
||||
'/amo/v/{addonId}': {
|
||||
get: {
|
||||
summary: 'Mozilla Add-on Version',
|
||||
description,
|
||||
parameters: pathParams({ name: 'addonId', example: 'dustman' }),
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,45 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
import { deprecatedService } from '../index.js'
|
||||
|
||||
const ansibleCollectionSchema = Joi.object({
|
||||
name: Joi.string().required(),
|
||||
namespace: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
}),
|
||||
}).required()
|
||||
|
||||
class AnsibleGalaxyCollectionName extends BaseJsonService {
|
||||
static category = 'other'
|
||||
static route = { base: 'ansible/collection', pattern: ':collectionId' }
|
||||
|
||||
static openApi = {
|
||||
'/ansible/collection/{collectionId}': {
|
||||
get: {
|
||||
summary: 'Ansible Collection',
|
||||
parameters: pathParams({ name: 'collectionId', example: '278' }),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'collection' }
|
||||
|
||||
static render({ name }) {
|
||||
return { message: name, color: 'blue' }
|
||||
}
|
||||
|
||||
async fetch({ collectionId }) {
|
||||
const url = `https://galaxy.ansible.com/api/v2/collections/${collectionId}/`
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema: ansibleCollectionSchema,
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ collectionId }) {
|
||||
const json = await this.fetch({ collectionId })
|
||||
const name = `${json.namespace.name}.${json.name}`
|
||||
return this.constructor.render({ name })
|
||||
}
|
||||
}
|
||||
|
||||
export { AnsibleGalaxyCollectionName }
|
||||
export const AnsibleGalaxyCollectionName = deprecatedService({
|
||||
category: 'other',
|
||||
route: {
|
||||
base: 'ansible/collection',
|
||||
pattern: ':collectionId',
|
||||
},
|
||||
label: 'collection',
|
||||
dateAdded: new Date('2023-10-10'),
|
||||
})
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import { ServiceTester } from '../tester.js'
|
||||
export const t = new ServiceTester({
|
||||
id: 'AnsibleCollection',
|
||||
title: 'AnsibleCollection',
|
||||
id: 'AnsibleGalaxyCollectionName',
|
||||
title: 'AnsibleGalaxyCollectionName',
|
||||
pathPrefix: '/ansible/collection',
|
||||
})
|
||||
|
||||
t.create('collection name (valid)')
|
||||
t.create('collection name')
|
||||
.get('/278.json')
|
||||
.expectBadge({ label: 'collection', message: 'community.general' })
|
||||
|
||||
t.create('collection name (not found)')
|
||||
.get('/000.json')
|
||||
.expectBadge({ label: 'collection', message: 'not found' })
|
||||
.expectBadge({ label: 'collection', message: 'no longer available' })
|
||||
|
||||
@@ -1,52 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { floorCount } from '../color-formatters.js'
|
||||
import { BaseJsonService, InvalidResponse, pathParams } from '../index.js'
|
||||
import { deprecatedService } from '../index.js'
|
||||
|
||||
const ansibleContentSchema = Joi.object({
|
||||
quality_score: Joi.number().allow(null).required(),
|
||||
}).required()
|
||||
|
||||
class AnsibleGalaxyContent extends BaseJsonService {
|
||||
async fetch({ projectId }) {
|
||||
const url = `https://galaxy.ansible.com/api/v1/content/${projectId}/`
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema: ansibleContentSchema,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default class AnsibleGalaxyContentQualityScore extends AnsibleGalaxyContent {
|
||||
static category = 'analysis'
|
||||
static route = { base: 'ansible/quality', pattern: ':projectId' }
|
||||
|
||||
static openApi = {
|
||||
'/ansible/quality/{projectId}': {
|
||||
get: {
|
||||
summary: 'Ansible Quality Score',
|
||||
parameters: pathParams({ name: 'projectId', example: '432' }),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'quality' }
|
||||
|
||||
static render({ qualityScore }) {
|
||||
return {
|
||||
message: qualityScore,
|
||||
color: floorCount(qualityScore, 2, 3, 4),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ projectId }) {
|
||||
const { quality_score: qualityScore } = await this.fetch({ projectId })
|
||||
|
||||
if (qualityScore === null) {
|
||||
throw new InvalidResponse({
|
||||
prettyMessage: 'no score available',
|
||||
})
|
||||
}
|
||||
|
||||
return this.constructor.render({ qualityScore })
|
||||
}
|
||||
}
|
||||
export const AnsibleGalaxyContentQualityScore = deprecatedService({
|
||||
category: 'analysis',
|
||||
route: {
|
||||
base: 'ansible/quality',
|
||||
pattern: ':projectId',
|
||||
},
|
||||
label: 'quality',
|
||||
dateAdded: new Date('2023-10-10'),
|
||||
})
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { createServiceTester } from '../tester.js'
|
||||
export const t = await createServiceTester()
|
||||
import { ServiceTester } from '../tester.js'
|
||||
export const t = new ServiceTester({
|
||||
id: 'AnsibleGalaxyContentQualityScore',
|
||||
title: 'AnsibleGalaxyContentQualityScore',
|
||||
pathPrefix: '/ansible/quality',
|
||||
})
|
||||
|
||||
t.create('quality score (valid)')
|
||||
t.create('quality score')
|
||||
.get('/432.json')
|
||||
.expectBadge({ label: 'quality', message: nonNegativeInteger })
|
||||
|
||||
t.create('quality score (project not found)')
|
||||
.get('/0101.json')
|
||||
.expectBadge({ label: 'quality', message: 'not found' })
|
||||
|
||||
t.create('quality score (no score available)')
|
||||
.get('/2504.json')
|
||||
.expectBadge({ label: 'quality', message: 'no score available' })
|
||||
.expectBadge({ label: 'quality', message: 'no longer available' })
|
||||
|
||||
@@ -1,73 +1,83 @@
|
||||
import Joi from 'joi'
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
import {
|
||||
BaseJsonService,
|
||||
deprecatedService,
|
||||
NotFound,
|
||||
pathParams,
|
||||
} from '../index.js'
|
||||
|
||||
const ansibleRoleSchema = Joi.object({
|
||||
download_count: nonNegativeInteger,
|
||||
name: Joi.string().required(),
|
||||
summary_fields: Joi.object({
|
||||
namespace: Joi.object({
|
||||
name: Joi.string().required(),
|
||||
}),
|
||||
}),
|
||||
results: Joi.array()
|
||||
.items(
|
||||
Joi.object({
|
||||
download_count: nonNegativeInteger,
|
||||
}),
|
||||
)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
class AnsibleGalaxyRole extends BaseJsonService {
|
||||
async fetch({ roleId }) {
|
||||
const url = `https://galaxy.ansible.com/api/v1/roles/${roleId}/`
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema: ansibleRoleSchema,
|
||||
})
|
||||
}
|
||||
}
|
||||
const AnsibleGalaxyRoleName = deprecatedService({
|
||||
name: 'DeprecatedAnsibleGalaxyRoleName',
|
||||
category: 'other',
|
||||
route: {
|
||||
base: 'ansible/role',
|
||||
pattern: ':roleId',
|
||||
},
|
||||
label: 'role',
|
||||
dateAdded: new Date('2023-10-10'),
|
||||
})
|
||||
|
||||
class AnsibleGalaxyRoleDownloads extends AnsibleGalaxyRole {
|
||||
const AnsibleGalaxyRoleLegacyDownloads = deprecatedService({
|
||||
name: 'DeprecatedAnsibleGalaxyRoleDownloads',
|
||||
category: 'downloads',
|
||||
route: {
|
||||
base: 'ansible/role/d',
|
||||
pattern: ':roleId',
|
||||
},
|
||||
label: 'role downloads',
|
||||
dateAdded: new Date('2023-10-10'),
|
||||
})
|
||||
|
||||
class AnsibleGalaxyRoleDownloads extends BaseJsonService {
|
||||
static category = 'downloads'
|
||||
static route = { base: 'ansible/role/d', pattern: ':roleId' }
|
||||
static route = { base: 'ansible/role/d', pattern: ':namespace/:name' }
|
||||
|
||||
static openApi = {
|
||||
'/ansible/role/d/{roleId}': {
|
||||
'/ansible/role/d/{namespace}/{name}': {
|
||||
get: {
|
||||
summary: 'Ansible Role',
|
||||
parameters: pathParams({ name: 'roleId', example: '3078' }),
|
||||
parameters: pathParams(
|
||||
{ name: 'namespace', example: 'openwisp' },
|
||||
{ name: 'name', example: 'openwisp2' },
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'role downloads' }
|
||||
|
||||
async handle({ roleId }) {
|
||||
const json = await this.fetch({ roleId })
|
||||
return renderDownloadsBadge({ downloads: json.download_count })
|
||||
async fetch({ namespace, name }) {
|
||||
const url = 'https://galaxy.ansible.com/api/v1/roles/'
|
||||
return this._requestJson({
|
||||
url,
|
||||
schema: ansibleRoleSchema,
|
||||
options: { searchParams: { namespace, name, limit: 1 } },
|
||||
})
|
||||
}
|
||||
|
||||
async handle({ namespace, name }) {
|
||||
const json = await this.fetch({ namespace, name })
|
||||
if (json.results.length === 0) {
|
||||
throw new NotFound({ prettyMessage: 'not found' })
|
||||
}
|
||||
return renderDownloadsBadge({ downloads: json.results[0].download_count })
|
||||
}
|
||||
}
|
||||
|
||||
class AnsibleGalaxyRoleName extends AnsibleGalaxyRole {
|
||||
static category = 'other'
|
||||
static route = { base: 'ansible/role', pattern: ':roleId' }
|
||||
|
||||
static openApi = {
|
||||
'/ansible/role/{roleId}': {
|
||||
get: {
|
||||
summary: 'Ansible Galaxy Role Name',
|
||||
parameters: pathParams({ name: 'roleId', example: '3078' }),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'role' }
|
||||
|
||||
static render({ name }) {
|
||||
return { message: name, color: 'blue' }
|
||||
}
|
||||
|
||||
async handle({ roleId }) {
|
||||
const json = await this.fetch({ roleId })
|
||||
const name = `${json.summary_fields.namespace.name}.${json.name}`
|
||||
return this.constructor.render({ name })
|
||||
}
|
||||
export {
|
||||
AnsibleGalaxyRoleDownloads,
|
||||
AnsibleGalaxyRoleName,
|
||||
AnsibleGalaxyRoleLegacyDownloads,
|
||||
}
|
||||
|
||||
export { AnsibleGalaxyRoleDownloads, AnsibleGalaxyRoleName }
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import { isMetric } from '../test-validators.js'
|
||||
import { ServiceTester } from '../tester.js'
|
||||
export const t = new ServiceTester({
|
||||
id: 'AnsibleRole',
|
||||
title: 'AnsibleRole',
|
||||
id: 'AnsibleGalaxyRole',
|
||||
title: 'AnsibleGalaxyRole',
|
||||
pathPrefix: '/ansible/role',
|
||||
})
|
||||
|
||||
t.create('role name (valid)')
|
||||
t.create('role name')
|
||||
.get('/14542.json')
|
||||
.expectBadge({ label: 'role', message: 'openwisp.openwisp2' })
|
||||
.expectBadge({ label: 'role', message: 'no longer available' })
|
||||
|
||||
t.create('role name (not found)')
|
||||
.get('/000.json')
|
||||
.expectBadge({ label: 'role', message: 'not found' })
|
||||
t.create('role downloads (deprecated)')
|
||||
.get('/d/14542.json')
|
||||
.expectBadge({ label: 'role downloads', message: 'no longer available' })
|
||||
|
||||
t.create('role downloads (valid)')
|
||||
.get('/d/14542.json')
|
||||
.get('/d/openwisp/openwisp2.json')
|
||||
.expectBadge({ label: 'role downloads', message: isMetric })
|
||||
|
||||
t.create('role downloads (not found)')
|
||||
.get('/d/does-not-exist.json')
|
||||
.get('/d/does-not/exist.json')
|
||||
.expectBadge({ label: 'role downloads', message: 'not found' })
|
||||
|
||||
@@ -6,7 +6,12 @@ import {
|
||||
import { renderLicenseBadge } from '../licenses.js'
|
||||
import { addv, metric, formatDate } from '../text-formatters.js'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService, NotFound, InvalidResponse } from '../index.js'
|
||||
import {
|
||||
BaseJsonService,
|
||||
NotFound,
|
||||
InvalidResponse,
|
||||
pathParams,
|
||||
} from '../index.js'
|
||||
|
||||
const aurSchema = Joi.object({
|
||||
resultcount: nonNegativeInteger,
|
||||
@@ -52,13 +57,18 @@ class AurLicense extends BaseAurService {
|
||||
static category = 'license'
|
||||
static route = { base: 'aur/license', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'AUR license',
|
||||
namedParams: { packageName: 'android-studio' },
|
||||
staticPreview: renderLicenseBadge({ license: 'Apache', color: 'blue' }),
|
||||
static openApi = {
|
||||
'/aur/license/{packageName}': {
|
||||
get: {
|
||||
summary: 'AUR License',
|
||||
description: 'Arch linux User Repository License',
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'android-studio',
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'license' }
|
||||
|
||||
@@ -83,13 +93,15 @@ class AurVotes extends BaseAurService {
|
||||
|
||||
static route = { base: 'aur/votes', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'AUR votes',
|
||||
namedParams: { packageName: 'dropbox' },
|
||||
staticPreview: this.render({ votes: '2280' }),
|
||||
static openApi = {
|
||||
'/aur/votes/{packageName}': {
|
||||
get: {
|
||||
summary: 'AUR Votes',
|
||||
description: 'Arch linux User Repository Votes',
|
||||
parameters: pathParams({ name: 'packageName', example: 'dropbox' }),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'votes' }
|
||||
|
||||
@@ -109,13 +121,19 @@ class AurVotes extends BaseAurService {
|
||||
class AurVersion extends BaseAurService {
|
||||
static category = 'version'
|
||||
static route = { base: 'aur/version', pattern: ':packageName' }
|
||||
static examples = [
|
||||
{
|
||||
title: 'AUR version',
|
||||
namedParams: { packageName: 'visual-studio-code-bin' },
|
||||
staticPreview: this.render({ version: '1.34.0-2', outOfDate: null }),
|
||||
|
||||
static openApi = {
|
||||
'/aur/version/{packageName}': {
|
||||
get: {
|
||||
summary: 'AUR Version',
|
||||
description: 'Arch linux User Repository Version',
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'visual-studio-code-bin',
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static _cacheLength = 3600
|
||||
|
||||
@@ -140,13 +158,18 @@ class AurMaintainer extends BaseAurService {
|
||||
|
||||
static route = { base: 'aur/maintainer', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'AUR maintainer',
|
||||
namedParams: { packageName: 'google-chrome' },
|
||||
staticPreview: this.render({ maintainer: 'luzifer' }),
|
||||
static openApi = {
|
||||
'/aur/maintainer/{packageName}': {
|
||||
get: {
|
||||
summary: 'AUR Maintainer',
|
||||
description: 'Arch linux User Repository Maintainer',
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'google-chrome',
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'maintainer', color: 'blue' }
|
||||
|
||||
@@ -173,13 +196,18 @@ class AurLastModified extends BaseAurService {
|
||||
pattern: ':packageName',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'AUR last modified',
|
||||
namedParams: { packageName: 'google-chrome' },
|
||||
staticPreview: this.render({ date: new Date().getTime() }),
|
||||
static openApi = {
|
||||
'/aur/last-modified/{packageName}': {
|
||||
get: {
|
||||
summary: 'AUR Last Modified',
|
||||
description: 'Arch linux User Repository Last Modified',
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'google-chrome',
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'last modified' }
|
||||
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import Joi from 'joi'
|
||||
import { renderBuildStatusBadge } from '../build-status.js'
|
||||
import { BaseSvgScrapingService, NotFound } from '../index.js'
|
||||
import { keywords, fetch } from './azure-devops-helpers.js'
|
||||
import {
|
||||
BaseSvgScrapingService,
|
||||
NotFound,
|
||||
queryParam,
|
||||
pathParam,
|
||||
} from '../index.js'
|
||||
import { fetch } from './azure-devops-helpers.js'
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
stage: Joi.string(),
|
||||
job: Joi.string(),
|
||||
})
|
||||
|
||||
const documentation = `
|
||||
const description = `
|
||||
[Azure Devops](https://dev.azure.com/) (formerly VSO, VSTS) is Microsoft Azure's CI/CD platform.
|
||||
|
||||
A badge requires three pieces of information:
|
||||
\`ORGANIZATION\`, \`PROJECT_ID\` and \`DEFINITION_ID\`.
|
||||
|
||||
@@ -37,62 +44,68 @@ export default class AzureDevOpsBuild extends BaseSvgScrapingService {
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Azure DevOps builds',
|
||||
pattern: ':organization/:projectId/:definitionId',
|
||||
namedParams: {
|
||||
organization: 'totodem',
|
||||
projectId: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
definitionId: '2',
|
||||
static openApi = {
|
||||
'/azure-devops/build/{organization}/{projectId}/{definitionId}': {
|
||||
get: {
|
||||
summary: 'Azure DevOps builds',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'organization',
|
||||
example: 'totodem',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'projectId',
|
||||
example: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'definitionId',
|
||||
example: '2',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'stage',
|
||||
example: 'Successful Stage',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'job',
|
||||
example: 'Successful Job',
|
||||
}),
|
||||
],
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'succeeded' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps builds (branch)',
|
||||
pattern: ':organization/:projectId/:definitionId/:branch',
|
||||
namedParams: {
|
||||
organization: 'totodem',
|
||||
projectId: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
definitionId: '2',
|
||||
branch: 'master',
|
||||
'/azure-devops/build/{organization}/{projectId}/{definitionId}/{branch}': {
|
||||
get: {
|
||||
summary: 'Azure DevOps builds (branch)',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'organization',
|
||||
example: 'totodem',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'projectId',
|
||||
example: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'definitionId',
|
||||
example: '2',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'branch',
|
||||
example: 'master',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'stage',
|
||||
example: 'Successful Stage',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'job',
|
||||
example: 'Successful Job',
|
||||
}),
|
||||
],
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'succeeded' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps builds (stage)',
|
||||
namedParams: {
|
||||
organization: 'totodem',
|
||||
projectId: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
definitionId: '5',
|
||||
},
|
||||
queryParams: {
|
||||
stage: 'Successful Stage',
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'succeeded' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps builds (job)',
|
||||
namedParams: {
|
||||
organization: 'totodem',
|
||||
projectId: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
definitionId: '5',
|
||||
},
|
||||
queryParams: {
|
||||
stage: 'Successful Stage',
|
||||
job: 'Successful Job',
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'succeeded' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle(
|
||||
{ organization, projectId, definitionId, branch },
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import Joi from 'joi'
|
||||
import { pathParams } from '../index.js'
|
||||
import { coveragePercentage as coveragePercentageColor } from '../color-formatters.js'
|
||||
import AzureDevOpsBase from './azure-devops-base.js'
|
||||
import { keywords } from './azure-devops-helpers.js'
|
||||
|
||||
const documentation = `
|
||||
const description = `
|
||||
[Azure Devops](https://dev.azure.com/) (formerly VSO, VSTS) is Microsoft Azure's CI/CD platform.
|
||||
|
||||
To obtain your own badge, you need to get 3 pieces of information:
|
||||
\`ORGANIZATION\`, \`PROJECT_ID\` and \`DEFINITION_ID\`.
|
||||
|
||||
@@ -47,33 +49,52 @@ export default class AzureDevOpsCoverage extends AzureDevOpsBase {
|
||||
pattern: ':organization/:project/:definitionId/:branch*',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Azure DevOps coverage',
|
||||
pattern: ':organization/:project/:definitionId',
|
||||
namedParams: {
|
||||
organization: 'swellaby',
|
||||
project: 'opensource',
|
||||
definitionId: '25',
|
||||
static openApi = {
|
||||
'/azure-devops/coverage/{organization}/{project}/{definitionId}': {
|
||||
get: {
|
||||
summary: 'Azure DevOps coverage',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'organization',
|
||||
example: 'swellaby',
|
||||
},
|
||||
{
|
||||
name: 'project',
|
||||
example: 'opensource',
|
||||
},
|
||||
{
|
||||
name: 'definitionId',
|
||||
example: '25',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.render({ coverage: 100 }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps coverage (branch)',
|
||||
pattern: ':organization/:project/:definitionId/:branch',
|
||||
namedParams: {
|
||||
organization: 'swellaby',
|
||||
project: 'opensource',
|
||||
definitionId: '25',
|
||||
branch: 'master',
|
||||
'/azure-devops/coverage/{organization}/{project}/{definitionId}/{branch}': {
|
||||
get: {
|
||||
summary: 'Azure DevOps coverage (branch)',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'organization',
|
||||
example: 'swellaby',
|
||||
},
|
||||
{
|
||||
name: 'project',
|
||||
example: 'opensource',
|
||||
},
|
||||
{
|
||||
name: 'definitionId',
|
||||
example: '25',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
example: 'master',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.render({ coverage: 100 }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'coverage' }
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { isBuildStatus } from '../build-status.js'
|
||||
|
||||
const keywords = ['vso', 'vsts', 'azure-devops']
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.alternatives()
|
||||
.try(
|
||||
@@ -26,4 +24,4 @@ async function fetch(serviceInstance, { url, searchParams = {}, httpErrors }) {
|
||||
return { status }
|
||||
}
|
||||
|
||||
export { keywords, fetch }
|
||||
export { fetch }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { renderBuildStatusBadge } from '../build-status.js'
|
||||
import { BaseSvgScrapingService } from '../index.js'
|
||||
import { keywords, fetch } from './azure-devops-helpers.js'
|
||||
import { BaseSvgScrapingService, pathParams } from '../index.js'
|
||||
import { fetch } from './azure-devops-helpers.js'
|
||||
|
||||
const documentation = `
|
||||
const description = `
|
||||
To obtain your own badge, you need to get 4 pieces of information:
|
||||
\`ORGANIZATION\`, \`PROJECT_ID\`, \`DEFINITION_ID\` and \`ENVIRONMENT_ID\`.
|
||||
|
||||
@@ -25,20 +25,33 @@ export default class AzureDevOpsRelease extends BaseSvgScrapingService {
|
||||
pattern: ':organization/:projectId/:definitionId/:environmentId',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Azure DevOps releases',
|
||||
namedParams: {
|
||||
organization: 'totodem',
|
||||
projectId: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
definitionId: '1',
|
||||
environmentId: '1',
|
||||
static openApi = {
|
||||
'/azure-devops/release/{organization}/{projectId}/{definitionId}/{environmentId}':
|
||||
{
|
||||
get: {
|
||||
summary: 'Azure DevOps releases',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'organization',
|
||||
example: 'totodem',
|
||||
},
|
||||
{
|
||||
name: 'projectId',
|
||||
example: '8cf3ec0e-d0c2-4fcd-8206-ad204f254a96',
|
||||
},
|
||||
{
|
||||
name: 'definitionId',
|
||||
example: '1',
|
||||
},
|
||||
{
|
||||
name: 'environmentId',
|
||||
example: '1',
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'succeeded' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'deployment' }
|
||||
|
||||
|
||||
@@ -1,28 +1,18 @@
|
||||
import Joi from 'joi'
|
||||
import { pathParam } from '../index.js'
|
||||
import {
|
||||
testResultOpenApiQueryParams,
|
||||
testResultQueryParamSchema,
|
||||
renderTestResultBadge,
|
||||
documentation as commonDocumentation,
|
||||
} from '../test-results.js'
|
||||
import AzureDevOpsBase from './azure-devops-base.js'
|
||||
|
||||
const commonAttrs = {
|
||||
keywords: ['vso', 'vsts', 'azure-devops'],
|
||||
namedParams: {
|
||||
organization: 'azuredevops-powershell',
|
||||
project: 'azuredevops-powershell',
|
||||
definitionId: '1',
|
||||
branch: 'master',
|
||||
},
|
||||
queryParams: {
|
||||
passed_label: 'passed',
|
||||
failed_label: 'failed',
|
||||
skipped_label: 'skipped',
|
||||
compact_message: null,
|
||||
},
|
||||
documentation: `
|
||||
const description = `
|
||||
[Azure Devops](https://dev.azure.com/) (formerly VSO, VSTS) is Microsoft Azure's CI/CD platform.
|
||||
|
||||
To obtain your own badge, you need to get 3 pieces of information:
|
||||
\`ORGANIZATION\`, \`PROJECT_ID\`, \`DEFINITION_ID\`.
|
||||
\`ORGANIZATION\`, \`PROJECT\`, \`DEFINITION_ID\`.
|
||||
|
||||
First, you need to select your build definition and look at the url:
|
||||
|
||||
@@ -37,8 +27,7 @@ Optionally, you can specify a named branch:
|
||||
\`https://img.shields.io/azure-devops/tests/ORGANIZATION/PROJECT/DEFINITION_ID/NAMED_BRANCH.svg\`.
|
||||
|
||||
${commonDocumentation}
|
||||
`,
|
||||
}
|
||||
`
|
||||
|
||||
const buildTestResultSummarySchema = Joi.object({
|
||||
aggregatedResultsAnalysis: Joi.object({
|
||||
@@ -65,48 +54,54 @@ export default class AzureDevOpsTests extends AzureDevOpsBase {
|
||||
queryParamSchema: testResultQueryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Azure DevOps tests',
|
||||
staticPreview: this.render({
|
||||
passed: 20,
|
||||
failed: 1,
|
||||
skipped: 1,
|
||||
total: 22,
|
||||
}),
|
||||
...commonAttrs,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps tests (compact)',
|
||||
staticPreview: this.render({
|
||||
passed: 20,
|
||||
failed: 1,
|
||||
skipped: 1,
|
||||
total: 22,
|
||||
isCompact: true,
|
||||
}),
|
||||
...commonAttrs,
|
||||
},
|
||||
{
|
||||
title: 'Azure DevOps tests with custom labels',
|
||||
queryParams: {
|
||||
passed_label: 'good',
|
||||
failed_label: 'bad',
|
||||
skipped_label: 'n/a',
|
||||
compact_message: null,
|
||||
static openApi = {
|
||||
'/azure-devops/tests/{organization}/{project}/{definitionId}': {
|
||||
get: {
|
||||
summary: 'Azure DevOps tests',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'organization',
|
||||
example: 'azuredevops-powershell',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'project',
|
||||
example: 'azuredevops-powershell',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'definitionId',
|
||||
example: '1',
|
||||
}),
|
||||
...testResultOpenApiQueryParams,
|
||||
],
|
||||
},
|
||||
staticPreview: this.render({
|
||||
passed: 20,
|
||||
failed: 1,
|
||||
skipped: 1,
|
||||
total: 22,
|
||||
passedLabel: 'good',
|
||||
failedLabel: 'bad',
|
||||
skippedLabel: 'n/a',
|
||||
}),
|
||||
...commonAttrs,
|
||||
},
|
||||
]
|
||||
'/azure-devops/tests/{organization}/{project}/{definitionId}/{branch}': {
|
||||
get: {
|
||||
summary: 'Azure DevOps tests (branch)',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'organization',
|
||||
example: 'azuredevops-powershell',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'project',
|
||||
example: 'azuredevops-powershell',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'definitionId',
|
||||
example: '1',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'branch',
|
||||
example: 'master',
|
||||
}),
|
||||
...testResultOpenApiQueryParams,
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'tests' }
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ export default class BitComponents extends BaseJsonService {
|
||||
static openApi = {
|
||||
'/bit/collection/total-components/{owner}/{collection}': {
|
||||
get: {
|
||||
summary: 'Bit',
|
||||
summary: 'Bit Components',
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'owner',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.array()
|
||||
.items(Joi.array().items(Joi.number().required(), Joi.number().required()))
|
||||
@@ -10,15 +10,17 @@ export default class BStatsPlayers extends BaseJsonService {
|
||||
static category = 'other'
|
||||
static route = { base: 'bstats/players', pattern: ':pluginid' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'bStats Players',
|
||||
namedParams: {
|
||||
pluginid: '1',
|
||||
static openApi = {
|
||||
'/bstats/players/{pluginid}': {
|
||||
get: {
|
||||
summary: 'bStats Players',
|
||||
parameters: pathParams({
|
||||
name: 'pluginid',
|
||||
example: '1',
|
||||
}),
|
||||
},
|
||||
staticPreview: this.render({ players: 74299 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'players', color: 'blue' }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { metric } from '../text-formatters.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.array()
|
||||
.items(Joi.array().items(Joi.number().required(), Joi.number().required()))
|
||||
@@ -10,15 +10,17 @@ export default class BStatsServers extends BaseJsonService {
|
||||
static category = 'other'
|
||||
static route = { base: 'bstats/servers', pattern: ':pluginid' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'bStats Servers',
|
||||
namedParams: {
|
||||
pluginid: '1',
|
||||
static openApi = {
|
||||
'/bstats/servers/{pluginid}': {
|
||||
get: {
|
||||
summary: 'bStats Servers',
|
||||
parameters: pathParams({
|
||||
name: 'pluginid',
|
||||
example: '1',
|
||||
}),
|
||||
},
|
||||
staticPreview: this.render({ servers: 57479 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'servers', color: 'blue' }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { optionalUrl } from '../validators.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParam, queryParam } from '../index.js'
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
baseUrl: optionalUrl,
|
||||
@@ -18,8 +18,9 @@ const schema = Joi.object({
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
const documentation = `
|
||||
const description = `
|
||||
<p>
|
||||
Use the <code>baseUrl</code> query parameter to target different Bugzilla deployments.
|
||||
If your Bugzilla badge errors, it might be because you are trying to load a private bug.
|
||||
</p>
|
||||
`
|
||||
@@ -28,33 +29,26 @@ export default class Bugzilla extends BaseJsonService {
|
||||
static category = 'issue-tracking'
|
||||
static route = { base: 'bugzilla', pattern: ':bugNumber', queryParamSchema }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Bugzilla bug status (Mozilla)',
|
||||
namedParams: {
|
||||
bugNumber: '996038',
|
||||
static openApi = {
|
||||
'/bugzilla/{bugNumber}': {
|
||||
get: {
|
||||
summary: 'Bugzilla bug status',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'bugNumber',
|
||||
example: '545424',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'baseUrl',
|
||||
example: 'https://bugs.eclipse.org/bugs',
|
||||
description:
|
||||
'When not specified, this will default to `https://bugzilla.mozilla.org`.',
|
||||
}),
|
||||
],
|
||||
},
|
||||
staticPreview: this.render({
|
||||
bugNumber: 996038,
|
||||
status: 'FIXED',
|
||||
resolution: '',
|
||||
}),
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'Bugzilla bug status (non-Mozilla)',
|
||||
namedParams: {
|
||||
bugNumber: '545424',
|
||||
},
|
||||
queryParams: { baseUrl: 'https://bugs.eclipse.org/bugs' },
|
||||
staticPreview: this.render({
|
||||
bugNumber: 545424,
|
||||
status: 'RESOLVED',
|
||||
resolution: 'FIXED',
|
||||
}),
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'bugzilla' }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
|
||||
// unknown is a valid 'other' status for Buildkite
|
||||
const schema = Joi.object({
|
||||
@@ -11,25 +11,32 @@ export default class Buildkite extends BaseJsonService {
|
||||
static category = 'build'
|
||||
static route = { base: 'buildkite', pattern: ':identifier/:branch*' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Buildkite',
|
||||
pattern: ':identifier',
|
||||
namedParams: {
|
||||
identifier: '3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489',
|
||||
static openApi = {
|
||||
'/buildkite/{identifier}': {
|
||||
get: {
|
||||
summary: 'Buildkite',
|
||||
parameters: pathParams({
|
||||
name: 'identifier',
|
||||
example: '3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489',
|
||||
}),
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'passing' }),
|
||||
},
|
||||
{
|
||||
title: 'Buildkite (branch)',
|
||||
pattern: ':identifier/:branch',
|
||||
namedParams: {
|
||||
identifier: '3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489',
|
||||
branch: 'master',
|
||||
'/buildkite/{identifier}/{branch}': {
|
||||
get: {
|
||||
summary: 'Buildkite (branch)',
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'identifier',
|
||||
example: '3826789cf8890b426057e6fe1c4e683bdf04fa24d498885489',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
example: 'master',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'passing' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'build' }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Joi from 'joi'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParam, queryParam } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
size: Joi.object({
|
||||
@@ -11,8 +11,6 @@ const queryParamSchema = Joi.object({
|
||||
exports: Joi.string(),
|
||||
}).required()
|
||||
|
||||
const keywords = ['node', 'bundlejs']
|
||||
|
||||
const esbuild =
|
||||
'<a href="https://github.com/evanw/esbuild" target="_blank" rel="noopener">esbuild</a>'
|
||||
const denoflate =
|
||||
@@ -20,9 +18,9 @@ const denoflate =
|
||||
const bundlejs =
|
||||
'<a href="https://bundlejs.com/" target="_blank" rel="noopener">bundlejs</a>'
|
||||
|
||||
const documentation = `
|
||||
const description = `
|
||||
<p>
|
||||
View ${esbuild} minified and ${denoflate} gzipped size of a package or selected exports, via ${bundlejs}.
|
||||
View ${esbuild} minified and ${denoflate} gzipped size of a javascript package or selected exports, via ${bundlejs}.
|
||||
</p>
|
||||
`
|
||||
|
||||
@@ -35,67 +33,48 @@ export default class BundlejsPackage extends BaseJsonService {
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'npm package minimized gzipped size',
|
||||
pattern: ':packageName',
|
||||
namedParams: {
|
||||
packageName: 'react',
|
||||
static openApi = {
|
||||
'/bundlejs/size/{packageName}': {
|
||||
get: {
|
||||
summary: 'npm package minimized gzipped size',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'packageName',
|
||||
example: 'value-enhancer@3.1.2',
|
||||
description:
|
||||
'This can either be a package name e.g: `value-enhancer`, or a package name and version e.g: `value-enhancer@3.1.2`',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'exports',
|
||||
example: 'isVal,val',
|
||||
}),
|
||||
],
|
||||
},
|
||||
staticPreview: this.render({ size: '2.94 kB' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'npm package minimized gzipped size (version)',
|
||||
pattern: ':packageName',
|
||||
namedParams: {
|
||||
packageName: 'react@18.2.0',
|
||||
'/bundlejs/size/{scope}/{packageName}': {
|
||||
get: {
|
||||
summary: 'npm package minimized gzipped size (scoped)',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'scope',
|
||||
example: '@ngneat',
|
||||
}),
|
||||
pathParam({
|
||||
name: 'packageName',
|
||||
example: 'falso@6.4.0',
|
||||
description:
|
||||
'This can either be a package name e.g: `falso`, or a package name and version e.g: `falso@6.4.0`',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'exports',
|
||||
example: 'randEmail,randFullName',
|
||||
}),
|
||||
],
|
||||
},
|
||||
staticPreview: this.render({ size: '2.94 kB' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'npm package minimized gzipped size (scoped)',
|
||||
pattern: ':scope/:packageName',
|
||||
namedParams: {
|
||||
scope: '@cycle',
|
||||
packageName: 'rx-run',
|
||||
},
|
||||
staticPreview: this.render({ size: '32.3 kB' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title: 'npm package minimized gzipped size (select exports)',
|
||||
pattern: ':packageName',
|
||||
namedParams: {
|
||||
packageName: 'value-enhancer',
|
||||
},
|
||||
queryParams: {
|
||||
exports: 'isVal,val',
|
||||
},
|
||||
staticPreview: this.render({ size: '823 B' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
{
|
||||
title:
|
||||
'npm package minimized gzipped size (scoped version select exports)',
|
||||
pattern: ':scope/:packageName',
|
||||
namedParams: {
|
||||
scope: '@ngneat',
|
||||
packageName: 'falso@6.4.0',
|
||||
},
|
||||
queryParams: {
|
||||
exports: 'randEmail,randFullName',
|
||||
},
|
||||
staticPreview: this.render({ size: '17.8 kB' }),
|
||||
keywords,
|
||||
documentation,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'bundlejs', color: 'informational' }
|
||||
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import Joi from 'joi'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
size: nonNegativeInteger,
|
||||
gzip: nonNegativeInteger,
|
||||
}).required()
|
||||
|
||||
const keywords = ['node', 'bundlephobia']
|
||||
const description =
|
||||
'[Bundlephobia](https://bundlephobia.com) lets you understand the size of a javascript package from NPM before it becomes a part of your bundle.'
|
||||
|
||||
export default class Bundlephobia extends BaseJsonService {
|
||||
static category = 'size'
|
||||
@@ -18,52 +19,94 @@ export default class Bundlephobia extends BaseJsonService {
|
||||
pattern: ':format(min|minzip)/:scope(@[^/]+)?/:packageName/:version?',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'npm bundle size',
|
||||
pattern: ':format(min|minzip)/:packageName',
|
||||
namedParams: {
|
||||
format: 'min',
|
||||
packageName: 'react',
|
||||
static openApi = {
|
||||
'/bundlephobia/{format}/{packageName}': {
|
||||
get: {
|
||||
summary: 'npm bundle size',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'format',
|
||||
schema: { type: 'string', enum: this.getEnum('format') },
|
||||
example: 'min',
|
||||
},
|
||||
{
|
||||
name: 'packageName',
|
||||
example: 'react',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.render({ format: 'min', size: 6652 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'npm bundle size (scoped)',
|
||||
pattern: ':format(min|minzip)/:scope/:packageName',
|
||||
namedParams: {
|
||||
format: 'min',
|
||||
scope: '@cycle',
|
||||
packageName: 'core',
|
||||
'/bundlephobia/{format}/{scope}/{packageName}': {
|
||||
get: {
|
||||
summary: 'npm bundle size (scoped)',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'format',
|
||||
schema: { type: 'string', enum: this.getEnum('format') },
|
||||
example: 'min',
|
||||
},
|
||||
{
|
||||
name: 'scope',
|
||||
example: '@cycle',
|
||||
},
|
||||
{
|
||||
name: 'packageName',
|
||||
example: 'core',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.render({ format: 'min', size: 3562 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'npm bundle size (version)',
|
||||
pattern: ':format(min|minzip)/:packageName/:version',
|
||||
namedParams: {
|
||||
format: 'min',
|
||||
packageName: 'react',
|
||||
version: '15.0.0',
|
||||
'/bundlephobia/{format}/{packageName}/{version}': {
|
||||
get: {
|
||||
summary: 'npm bundle size (version)',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'format',
|
||||
schema: { type: 'string', enum: this.getEnum('format') },
|
||||
example: 'min',
|
||||
},
|
||||
{
|
||||
name: 'packageName',
|
||||
example: 'react',
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
example: '15.0.0',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.render({ format: 'min', size: 20535 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'npm bundle size (scoped version)',
|
||||
pattern: ':format(min|minzip)/:scope/:packageName/:version',
|
||||
namedParams: {
|
||||
format: 'min',
|
||||
scope: '@cycle',
|
||||
packageName: 'core',
|
||||
version: '7.0.0',
|
||||
'/bundlephobia/{format}/{scope}/{packageName}/{version}': {
|
||||
get: {
|
||||
summary: 'npm bundle size (scoped version)',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'format',
|
||||
schema: { type: 'string', enum: this.getEnum('format') },
|
||||
example: 'min',
|
||||
},
|
||||
{
|
||||
name: 'scope',
|
||||
example: '@cycle',
|
||||
},
|
||||
{
|
||||
name: 'packageName',
|
||||
example: 'core',
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
example: '7.0.0',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.render({ format: 'min', size: 3562 }),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static _cacheLength = 900
|
||||
|
||||
static defaultBadgeData = { label: 'bundlephobia', color: 'informational' }
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import Joi from 'joi'
|
||||
import { colorScale, coveragePercentage } from '../color-formatters.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
badge_level: Joi.string().required(),
|
||||
tiered_percentage: Joi.number().required(),
|
||||
}).required()
|
||||
|
||||
const keywords = ['core infrastructure initiative']
|
||||
|
||||
const summaryColorScale = colorScale(
|
||||
[10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 200, 300],
|
||||
[
|
||||
@@ -35,37 +33,26 @@ export default class CIIBestPracticesService extends BaseJsonService {
|
||||
pattern: ':metric(level|percentage|summary)/:projectId',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'CII Best Practices Level',
|
||||
pattern: 'level/:projectId',
|
||||
namedParams: {
|
||||
projectId: '1',
|
||||
static openApi = {
|
||||
'/cii/{metric}/{projectId}': {
|
||||
get: {
|
||||
summary: 'CII Best Practices',
|
||||
description:
|
||||
'The Core Infrastructure Initiative (CII) Best Practices badge is a way for Open Source projects to show that they follow best practices',
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'metric',
|
||||
example: 'level',
|
||||
schema: { type: 'string', enum: this.getEnum('metric') },
|
||||
},
|
||||
{
|
||||
name: 'projectId',
|
||||
example: '1',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.renderLevelBadge({ level: 'gold' }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'CII Best Practices Tiered Percentage',
|
||||
pattern: 'percentage/:projectId',
|
||||
namedParams: {
|
||||
projectId: '29',
|
||||
},
|
||||
staticPreview: this.renderTieredPercentageBadge({ percentage: 107 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'CII Best Practices Summary',
|
||||
pattern: 'summary/:projectId',
|
||||
namedParams: {
|
||||
projectId: '33',
|
||||
},
|
||||
staticPreview: this.renderSummaryBadge({ percentage: 94 }),
|
||||
keywords,
|
||||
documentation:
|
||||
'This badge uses the same message and color scale as the native CII one, but with all the configuration and goodness that Shields provides!',
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'cii' }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParam, queryParam } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
subject: Joi.string().required(),
|
||||
@@ -21,37 +21,31 @@ export default class Cirrus extends BaseJsonService {
|
||||
queryParamSchema,
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Cirrus CI - Base Branch Build Status',
|
||||
namedParams: { user: 'flutter', repo: 'flutter' },
|
||||
pattern: 'github/:user/:repo',
|
||||
staticPreview: this.render({ status: 'passing' }),
|
||||
static openApi = {
|
||||
'/cirrus/github/{user}/{repo}': {
|
||||
get: {
|
||||
summary: 'Cirrus CI - Default Branch Build Status',
|
||||
parameters: [
|
||||
pathParam({ name: 'user', example: 'flutter' }),
|
||||
pathParam({ name: 'repo', example: 'flutter' }),
|
||||
queryParam({ name: 'task', example: 'build_docker' }),
|
||||
queryParam({ name: 'script', example: 'test' }),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Cirrus CI - Specific Branch Build Status',
|
||||
pattern: 'github/:user/:repo/:branch',
|
||||
namedParams: { user: 'flutter', repo: 'flutter', branch: 'master' },
|
||||
staticPreview: this.render({ status: 'passing' }),
|
||||
'/cirrus/github/{user}/{repo}/{branch}': {
|
||||
get: {
|
||||
summary: 'Cirrus CI - Specific Branch Build Status',
|
||||
parameters: [
|
||||
pathParam({ name: 'user', example: 'flutter' }),
|
||||
pathParam({ name: 'repo', example: 'flutter' }),
|
||||
pathParam({ name: 'branch', example: 'master' }),
|
||||
queryParam({ name: 'task', example: 'build_docker' }),
|
||||
queryParam({ name: 'script', example: 'test' }),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Cirrus CI - Specific Task Build Status',
|
||||
pattern: 'github/:user/:repo',
|
||||
queryParams: { task: 'build_docker' },
|
||||
namedParams: { user: 'flutter', repo: 'cocoon' },
|
||||
staticPreview: this.render({
|
||||
subject: 'build_docker',
|
||||
status: 'passing',
|
||||
}),
|
||||
},
|
||||
{
|
||||
title: 'Cirrus CI - Task and Script Build Status',
|
||||
pattern: 'github/:user/:repo',
|
||||
queryParams: { task: 'build_docker', script: 'test' },
|
||||
namedParams: { user: 'flutter', repo: 'cocoon' },
|
||||
staticPreview: this.render({ subject: 'test', status: 'passing' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'build' }
|
||||
|
||||
|
||||
@@ -19,4 +19,7 @@ class BaseClojarsService extends BaseJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseClojarsService }
|
||||
const description =
|
||||
'[Clojars](https://clojars.org/) is a repository for Clojure libraries'
|
||||
|
||||
export { BaseClojarsService, description }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { pathParams } from '../index.js'
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { BaseClojarsService } from './clojars-base.js'
|
||||
import { BaseClojarsService, description } from './clojars-base.js'
|
||||
|
||||
export default class ClojarsDownloads extends BaseClojarsService {
|
||||
static category = 'downloads'
|
||||
@@ -10,6 +10,7 @@ export default class ClojarsDownloads extends BaseClojarsService {
|
||||
'/clojars/dt/{clojar}': {
|
||||
get: {
|
||||
summary: 'Clojars Downloads',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'clojar',
|
||||
example: 'prismic',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import { version as versionColor } from '../color-formatters.js'
|
||||
import { redirector } from '../index.js'
|
||||
import { BaseClojarsService } from './clojars-base.js'
|
||||
import { redirector, pathParam, queryParam } from '../index.js'
|
||||
import { BaseClojarsService, description } from './clojars-base.js'
|
||||
|
||||
const queryParamSchema = Joi.object({
|
||||
include_prereleases: Joi.equal(''),
|
||||
@@ -11,19 +11,25 @@ class ClojarsVersionService extends BaseClojarsService {
|
||||
static category = 'version'
|
||||
static route = { base: 'clojars/v', pattern: ':clojar+', queryParamSchema }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Clojars Version',
|
||||
namedParams: { clojar: 'prismic' },
|
||||
staticPreview: this.render({ clojar: 'clojar', version: '1.2' }),
|
||||
static openApi = {
|
||||
'/clojars/v/{clojar}': {
|
||||
get: {
|
||||
summary: 'Clojars Version',
|
||||
description,
|
||||
parameters: [
|
||||
pathParam({
|
||||
name: 'clojar',
|
||||
example: 'prismic',
|
||||
}),
|
||||
queryParam({
|
||||
name: 'include_prereleases',
|
||||
schema: { type: 'boolean' },
|
||||
example: null,
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Clojars Version (including pre-releases)',
|
||||
namedParams: { clojar: 'prismic' },
|
||||
queryParams: { include_prereleases: null },
|
||||
staticPreview: this.render({ clojar: 'clojar', version: '1.2' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'clojars' }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { coveragePercentage as coveragePercentageColor } from '../color-formatters.js'
|
||||
import { BaseSvgScrapingService, NotFound } from '../index.js'
|
||||
import { BaseSvgScrapingService, NotFound, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.alternatives()
|
||||
@@ -12,23 +12,32 @@ export default class CodacyCoverage extends BaseSvgScrapingService {
|
||||
static category = 'coverage'
|
||||
static route = { base: 'codacy/coverage', pattern: ':projectId/:branch*' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Codacy coverage',
|
||||
pattern: ':projectId',
|
||||
namedParams: { projectId: 'd5402a91aa7b4234bd1c19b5e86a63be' },
|
||||
staticPreview: this.render({ percentage: 90 }),
|
||||
},
|
||||
{
|
||||
title: 'Codacy branch coverage',
|
||||
pattern: ':projectId/:branch',
|
||||
namedParams: {
|
||||
projectId: 'd5402a91aa7b4234bd1c19b5e86a63be',
|
||||
branch: 'master',
|
||||
static openApi = {
|
||||
'/codacy/coverage/{projectId}': {
|
||||
get: {
|
||||
summary: 'Codacy coverage',
|
||||
parameters: pathParams({
|
||||
name: 'projectId',
|
||||
example: 'd5402a91aa7b4234bd1c19b5e86a63be',
|
||||
}),
|
||||
},
|
||||
staticPreview: this.render({ percentage: 90 }),
|
||||
},
|
||||
]
|
||||
'/codacy/coverage/{projectId}/{branch}': {
|
||||
get: {
|
||||
summary: 'Codacy coverage (branch)',
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'projectId',
|
||||
example: 'd5402a91aa7b4234bd1c19b5e86a63be',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
example: 'master',
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'coverage' }
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Joi from 'joi'
|
||||
import { BaseSvgScrapingService } from '../index.js'
|
||||
import { BaseSvgScrapingService, pathParams } from '../index.js'
|
||||
import { codacyGrade } from './codacy-helpers.js'
|
||||
|
||||
const schema = Joi.object({ message: codacyGrade }).required()
|
||||
@@ -8,23 +8,32 @@ export default class CodacyGrade extends BaseSvgScrapingService {
|
||||
static category = 'analysis'
|
||||
static route = { base: 'codacy/grade', pattern: ':projectId/:branch*' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Codacy grade',
|
||||
pattern: ':projectId',
|
||||
namedParams: { projectId: '0cb32ce695b743d68257021455330c66' },
|
||||
staticPreview: this.render({ grade: 'A' }),
|
||||
},
|
||||
{
|
||||
title: 'Codacy branch grade',
|
||||
pattern: ':projectId/:branch',
|
||||
namedParams: {
|
||||
projectId: '0cb32ce695b743d68257021455330c66',
|
||||
branch: 'master',
|
||||
static openApi = {
|
||||
'/codacy/grade/{projectId}': {
|
||||
get: {
|
||||
summary: 'Codacy grade',
|
||||
parameters: pathParams({
|
||||
name: 'projectId',
|
||||
example: '0cb32ce695b743d68257021455330c66',
|
||||
}),
|
||||
},
|
||||
staticPreview: this.render({ grade: 'A' }),
|
||||
},
|
||||
]
|
||||
'/codacy/grade/{projectId}/{branch}': {
|
||||
get: {
|
||||
summary: 'Codacy grade (branch)',
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'projectId',
|
||||
example: '0cb32ce695b743d68257021455330c66',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
example: 'master',
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'code quality' }
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { isBuildStatus, renderBuildStatusBadge } from '../build-status.js'
|
||||
import { BaseSvgScrapingService } from '../index.js'
|
||||
import { BaseSvgScrapingService, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
message: Joi.alternatives()
|
||||
@@ -28,25 +28,32 @@ export default class Codeship extends BaseSvgScrapingService {
|
||||
static category = 'build'
|
||||
static route = { base: 'codeship', pattern: ':projectId/:branch*' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Codeship',
|
||||
pattern: ':projectId',
|
||||
namedParams: {
|
||||
projectId: 'd6c1ddd0-16a3-0132-5f85-2e35c05e22b1',
|
||||
static openApi = {
|
||||
'/codeship/{projectId}': {
|
||||
get: {
|
||||
summary: 'Codeship',
|
||||
parameters: pathParams({
|
||||
name: 'projectId',
|
||||
example: 'd6c1ddd0-16a3-0132-5f85-2e35c05e22b1',
|
||||
}),
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'passing' }),
|
||||
},
|
||||
{
|
||||
title: 'Codeship (branch)',
|
||||
pattern: ':projectId/:branch',
|
||||
namedParams: {
|
||||
projectId: '0bdb0440-3af5-0133-00ea-0ebda3a33bf6',
|
||||
branch: 'master',
|
||||
'/codeship/{projectId}/{branch}': {
|
||||
get: {
|
||||
summary: 'Codeship (branch)',
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'projectId',
|
||||
example: '0bdb0440-3af5-0133-00ea-0ebda3a33bf6',
|
||||
},
|
||||
{
|
||||
name: 'branch',
|
||||
example: 'master',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: renderBuildStatusBadge({ status: 'passing' }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static defaultBadgeData = { label: 'build' }
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { BaseJsonService } from '../index.js'
|
||||
|
||||
const description =
|
||||
'[Coincap](https://coincap.io/) is a cryptocurrency exchange'
|
||||
|
||||
export default class BaseCoincapService extends BaseJsonService {
|
||||
static category = 'other'
|
||||
|
||||
@@ -19,4 +22,4 @@ export default class BaseCoincapService extends BaseJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseCoincapService }
|
||||
export { BaseCoincapService, description }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Joi from 'joi'
|
||||
import { pathParams } from '../index.js'
|
||||
import { floorCount } from '../color-formatters.js'
|
||||
import BaseCoincapService from './coincap-base.js'
|
||||
import { BaseCoincapService, description } from './coincap-base.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
data: Joi.object({
|
||||
@@ -19,6 +19,7 @@ export default class CoincapChangePercent24HrUsd extends BaseCoincapService {
|
||||
'/coincap/change-percent-24hr/{assetId}': {
|
||||
get: {
|
||||
summary: 'Coincap (Change Percent 24Hr)',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'assetId',
|
||||
example: 'bitcoin',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { pathParams } from '../index.js'
|
||||
import BaseCoincapService from './coincap-base.js'
|
||||
import { BaseCoincapService, description } from './coincap-base.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
data: Joi.object({
|
||||
@@ -18,6 +18,7 @@ export default class CoincapPriceUsd extends BaseCoincapService {
|
||||
'/coincap/price-usd/{assetId}': {
|
||||
get: {
|
||||
summary: 'Coincap (Price USD)',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'assetId',
|
||||
example: 'bitcoin',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { pathParams } from '../index.js'
|
||||
import BaseCoincapService from './coincap-base.js'
|
||||
import { BaseCoincapService, description } from './coincap-base.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
data: Joi.object({
|
||||
@@ -18,6 +18,7 @@ export default class CoincapRank extends BaseCoincapService {
|
||||
'/coincap/rank/{assetId}': {
|
||||
get: {
|
||||
summary: 'Coincap (Rank)',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'assetId',
|
||||
example: 'bitcoin',
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
*/
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import pep440 from '@renovate/pep440'
|
||||
import pep440 from '@renovatebot/pep440'
|
||||
|
||||
/**
|
||||
* Determines the color used for a badge based on version.
|
||||
|
||||
@@ -13,6 +13,7 @@ export default class ConanVersion extends ConditionalGithubAuthV3Service {
|
||||
'/conan/v/{packageName}': {
|
||||
get: {
|
||||
summary: 'Conan Center',
|
||||
description: '[Conan](https://conan.io/) is a package manager for C++',
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'boost',
|
||||
|
||||
@@ -17,10 +17,10 @@ const condaSchema = Joi.object({
|
||||
export default class BaseCondaService extends BaseJsonService {
|
||||
static defaultBadgeData = { label: 'conda' }
|
||||
|
||||
async fetch({ channel, pkg }) {
|
||||
async fetch({ channel, packageName }) {
|
||||
return this._requestJson({
|
||||
schema: condaSchema,
|
||||
url: `https://api.anaconda.org/package/${channel}/${pkg}`,
|
||||
url: `https://api.anaconda.org/package/${channel}/${packageName}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,13 @@ import BaseCondaService from './conda-base.js'
|
||||
|
||||
export default class CondaDownloads extends BaseCondaService {
|
||||
static category = 'downloads'
|
||||
static route = { base: 'conda', pattern: ':variant(d|dn)/:channel/:pkg' }
|
||||
static route = {
|
||||
base: 'conda',
|
||||
pattern: ':variant(d|dn)/:channel/:packageName',
|
||||
}
|
||||
|
||||
static openApi = {
|
||||
'/conda/{variant}/{channel}/{package}': {
|
||||
'/conda/{variant}/{channel}/{packageName}': {
|
||||
get: {
|
||||
summary: 'Conda Downloads',
|
||||
parameters: pathParams(
|
||||
@@ -21,7 +24,7 @@ export default class CondaDownloads extends BaseCondaService {
|
||||
example: 'conda-forge',
|
||||
},
|
||||
{
|
||||
name: 'package',
|
||||
name: 'packageName',
|
||||
example: 'python',
|
||||
},
|
||||
),
|
||||
@@ -34,8 +37,8 @@ export default class CondaDownloads extends BaseCondaService {
|
||||
return renderDownloadsBadge({ downloads, labelOverride })
|
||||
}
|
||||
|
||||
async handle({ variant, channel, pkg }) {
|
||||
const json = await this.fetch({ channel, pkg })
|
||||
async handle({ variant, channel, packageName }) {
|
||||
const json = await this.fetch({ channel, packageName })
|
||||
const downloads = json.files.reduce(
|
||||
(total, file) => total + file.ndownloads,
|
||||
0,
|
||||
|
||||
@@ -10,10 +10,10 @@ const schema = Joi.object({
|
||||
|
||||
export default class CondaLicense extends BaseCondaService {
|
||||
static category = 'license'
|
||||
static route = { base: 'conda', pattern: 'l/:channel/:pkg' }
|
||||
static route = { base: 'conda', pattern: 'l/:channel/:packageName' }
|
||||
|
||||
static openApi = {
|
||||
'/conda/l/{channel}/{package}': {
|
||||
'/conda/l/{channel}/{packageName}': {
|
||||
get: {
|
||||
summary: 'Conda - License',
|
||||
parameters: pathParams(
|
||||
@@ -22,7 +22,7 @@ export default class CondaLicense extends BaseCondaService {
|
||||
example: 'conda-forge',
|
||||
},
|
||||
{
|
||||
name: 'package',
|
||||
name: 'packageName',
|
||||
example: 'setuptools',
|
||||
},
|
||||
),
|
||||
@@ -36,10 +36,10 @@ export default class CondaLicense extends BaseCondaService {
|
||||
return renderLicenseBadge({ licenses })
|
||||
}
|
||||
|
||||
async handle({ channel, pkg }) {
|
||||
async handle({ channel, packageName }) {
|
||||
const json = await this._requestJson({
|
||||
schema,
|
||||
url: `https://api.anaconda.org/package/${channel}/${pkg}`,
|
||||
url: `https://api.anaconda.org/package/${channel}/${packageName}`,
|
||||
})
|
||||
return this.constructor.render({
|
||||
licenses: toArray(json.license),
|
||||
|
||||
@@ -3,10 +3,13 @@ import BaseCondaService from './conda-base.js'
|
||||
|
||||
export default class CondaPlatform extends BaseCondaService {
|
||||
static category = 'platform-support'
|
||||
static route = { base: 'conda', pattern: ':variant(p|pn)/:channel/:pkg' }
|
||||
static route = {
|
||||
base: 'conda',
|
||||
pattern: ':variant(p|pn)/:channel/:packageName',
|
||||
}
|
||||
|
||||
static openApi = {
|
||||
'/conda/{variant}/{channel}/{package}': {
|
||||
'/conda/{variant}/{channel}/{packageName}': {
|
||||
get: {
|
||||
summary: 'Conda Platform',
|
||||
parameters: pathParams(
|
||||
@@ -20,7 +23,7 @@ export default class CondaPlatform extends BaseCondaService {
|
||||
example: 'conda-forge',
|
||||
},
|
||||
{
|
||||
name: 'package',
|
||||
name: 'packageName',
|
||||
example: 'python',
|
||||
},
|
||||
),
|
||||
@@ -35,8 +38,8 @@ export default class CondaPlatform extends BaseCondaService {
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ variant, channel, pkg }) {
|
||||
const json = await this.fetch({ channel, pkg })
|
||||
async handle({ variant, channel, packageName }) {
|
||||
const json = await this.fetch({ channel, packageName })
|
||||
return this.constructor.render({ variant, platforms: json.conda_platforms })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,37 @@
|
||||
import { pathParams } from '../index.js'
|
||||
import { addv as versionText } from '../text-formatters.js'
|
||||
import { version as versionColor } from '../color-formatters.js'
|
||||
import BaseCondaService from './conda-base.js'
|
||||
|
||||
export default class CondaVersion extends BaseCondaService {
|
||||
static category = 'version'
|
||||
static route = { base: 'conda', pattern: ':variant(v|vn)/:channel/:pkg' }
|
||||
static route = {
|
||||
base: 'conda',
|
||||
pattern: ':variant(v|vn)/:channel/:packageName',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Conda',
|
||||
namedParams: { channel: 'conda-forge', package: 'python' },
|
||||
pattern: 'v/:channel/:package',
|
||||
staticPreview: this.render({
|
||||
variant: 'v',
|
||||
channel: 'conda-forge',
|
||||
version: '3.7.1',
|
||||
}),
|
||||
static openApi = {
|
||||
'/conda/{variant}/{channel}/{packageName}': {
|
||||
get: {
|
||||
summary: 'Conda Version',
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'variant',
|
||||
example: 'vn',
|
||||
schema: { type: 'string', enum: this.getEnum('variant') },
|
||||
},
|
||||
{
|
||||
name: 'channel',
|
||||
example: 'conda-forge',
|
||||
},
|
||||
{
|
||||
name: 'packageName',
|
||||
example: 'python',
|
||||
},
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Conda (channel only)',
|
||||
namedParams: { channel: 'conda-forge', package: 'python' },
|
||||
pattern: 'vn/:channel/:package',
|
||||
staticPreview: this.render({
|
||||
variant: 'vn',
|
||||
channel: 'conda-forge',
|
||||
version: '3.7.1',
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ variant, channel, version }) {
|
||||
return {
|
||||
@@ -37,8 +41,8 @@ export default class CondaVersion extends BaseCondaService {
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ variant, channel, pkg }) {
|
||||
const json = await this.fetch({ channel, pkg })
|
||||
async handle({ variant, channel, packageName }) {
|
||||
const json = await this.fetch({ channel, packageName })
|
||||
return this.constructor.render({
|
||||
variant,
|
||||
channel,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { pathParams } from '../index.js'
|
||||
import BaseCpanService from './cpan.js'
|
||||
import { BaseCpanService, description } from './cpan.js'
|
||||
|
||||
export default class CpanLicense extends BaseCpanService {
|
||||
static category = 'license'
|
||||
@@ -9,6 +9,7 @@ export default class CpanLicense extends BaseCpanService {
|
||||
'/cpan/l/{packageName}': {
|
||||
get: {
|
||||
summary: 'CPAN License',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'Config-Augeas',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { pathParams } from '../index.js'
|
||||
import { renderVersionBadge } from '../version.js'
|
||||
import BaseCpanService from './cpan.js'
|
||||
import { BaseCpanService, description } from './cpan.js'
|
||||
|
||||
export default class CpanVersion extends BaseCpanService {
|
||||
static category = 'version'
|
||||
@@ -10,6 +10,7 @@ export default class CpanVersion extends BaseCpanService {
|
||||
'/cpan/v/{packageName}': {
|
||||
get: {
|
||||
summary: 'CPAN Version',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'Config-Augeas',
|
||||
|
||||
@@ -6,6 +6,9 @@ const schema = Joi.object({
|
||||
license: Joi.array().items(Joi.string()).min(1).required(),
|
||||
}).required()
|
||||
|
||||
const description =
|
||||
'[CPAN](https://www.cpan.org/) is a package registry for Perl'
|
||||
|
||||
export default class BaseCpanService extends BaseJsonService {
|
||||
static defaultBadgeData = { label: 'cpan' }
|
||||
|
||||
@@ -14,3 +17,5 @@ export default class BaseCpanService extends BaseJsonService {
|
||||
return this._requestJson({ schema, url })
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseCpanService, description }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Joi from 'joi'
|
||||
import { renderVersionBadge } from '../version.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
import { BaseJsonService, pathParams } from '../index.js'
|
||||
|
||||
const schema = Joi.object({
|
||||
License: Joi.string().required(),
|
||||
@@ -20,13 +20,17 @@ class CranLicense extends BaseCranService {
|
||||
static category = 'license'
|
||||
static route = { base: 'cran/l', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'CRAN/METACRAN',
|
||||
namedParams: { packageName: 'devtools' },
|
||||
staticPreview: this.render({ license: 'MIT + file LICENSE' }),
|
||||
static openApi = {
|
||||
'/cran/l/{packageName}': {
|
||||
get: {
|
||||
summary: 'CRAN/METACRAN License',
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'devtools',
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ license }) {
|
||||
return {
|
||||
@@ -46,13 +50,17 @@ class CranVersion extends BaseCranService {
|
||||
static category = 'version'
|
||||
static route = { base: 'cran/v', pattern: ':packageName' }
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'CRAN/METACRAN',
|
||||
namedParams: { packageName: 'devtools' },
|
||||
staticPreview: this.render({ version: '2.0.1' }),
|
||||
static openApi = {
|
||||
'/cran/v/{packageName}': {
|
||||
get: {
|
||||
summary: 'CRAN/METACRAN Version',
|
||||
parameters: pathParams({
|
||||
name: 'packageName',
|
||||
example: 'devtools',
|
||||
}),
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ version }) {
|
||||
return renderVersionBadge({ version })
|
||||
|
||||
@@ -2,8 +2,6 @@ import Joi from 'joi'
|
||||
import { nonNegativeInteger } from '../validators.js'
|
||||
import { BaseJsonService } from '../index.js'
|
||||
|
||||
const keywords = ['Rust']
|
||||
|
||||
const crateSchema = Joi.object({
|
||||
crate: Joi.object({
|
||||
downloads: nonNegativeInteger,
|
||||
@@ -49,4 +47,7 @@ class BaseCratesService extends BaseJsonService {
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseCratesService, keywords }
|
||||
const description =
|
||||
'[Crates.io](https://crates.io/) is a package registry for Rust.'
|
||||
|
||||
export { BaseCratesService, description }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { renderDownloadsBadge } from '../downloads.js'
|
||||
import { InvalidParameter, NotFound } from '../index.js'
|
||||
import { BaseCratesService, keywords } from './crates-base.js'
|
||||
import { InvalidParameter, NotFound, pathParams } from '../index.js'
|
||||
import { BaseCratesService, description } from './crates-base.js'
|
||||
|
||||
export default class CratesDownloads extends BaseCratesService {
|
||||
static category = 'downloads'
|
||||
@@ -9,49 +9,54 @@ export default class CratesDownloads extends BaseCratesService {
|
||||
pattern: ':variant(d|dv|dr)/:crate/:version?',
|
||||
}
|
||||
|
||||
static examples = [
|
||||
{
|
||||
title: 'Crates.io',
|
||||
pattern: 'd/:crate',
|
||||
namedParams: {
|
||||
crate: 'rustc-serialize',
|
||||
static openApi = {
|
||||
'/crates/d/{crate}': {
|
||||
get: {
|
||||
summary: 'Crates.io Total Downloads',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'crate',
|
||||
example: 'rustc-serialize',
|
||||
}),
|
||||
},
|
||||
staticPreview: this.render({ variant: 'd', downloads: 5000000 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'Crates.io (latest)',
|
||||
pattern: 'dv/:crate',
|
||||
namedParams: {
|
||||
crate: 'rustc-serialize',
|
||||
'/crates/dv/{crate}': {
|
||||
get: {
|
||||
summary: 'Crates.io Downloads (latest version)',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'crate',
|
||||
example: 'rustc-serialize',
|
||||
}),
|
||||
},
|
||||
staticPreview: this.render({ variant: 'dv', downloads: 2000000 }),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'Crates.io (version)',
|
||||
pattern: 'dv/:crate/:version',
|
||||
namedParams: {
|
||||
crate: 'rustc-serialize',
|
||||
version: '0.3.24',
|
||||
'/crates/dv/{crate}/{version}': {
|
||||
get: {
|
||||
summary: 'Crates.io Downloads (version)',
|
||||
description,
|
||||
parameters: pathParams(
|
||||
{
|
||||
name: 'crate',
|
||||
example: 'rustc-serialize',
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
example: '0.3.24',
|
||||
},
|
||||
),
|
||||
},
|
||||
staticPreview: this.render({
|
||||
variant: 'dv',
|
||||
downloads: 2000000,
|
||||
version: '0.3.24',
|
||||
}),
|
||||
keywords,
|
||||
},
|
||||
{
|
||||
title: 'Crates.io (recent)',
|
||||
pattern: 'dr/:crate',
|
||||
namedParams: {
|
||||
crate: 'rustc-serialize',
|
||||
'/crates/dr/{crate}': {
|
||||
get: {
|
||||
summary: 'Crates.io Downloads (recent)',
|
||||
description,
|
||||
parameters: pathParams({
|
||||
name: 'crate',
|
||||
example: 'rustc-serialize',
|
||||
}),
|
||||
},
|
||||
staticPreview: this.render({ variant: 'dr', downloads: 2000000 }),
|
||||
keywords,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ variant, downloads, version }) {
|
||||
let labelOverride
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user