diff --git a/package-lock.json b/package-lock.json index 1412abb4..17275fc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@blaahaj/lint-git-tree": "^1.1.0", "@types/markdown-it": "^14.1.2", "@types/node": "^22.14.1", + "octokit": "^5.0.3", "typescript": "^5.8.3" } }, @@ -67,6 +68,379 @@ "node": ">=12" } }, + "node_modules/@octokit/app": { + "version": "16.1.1", + "resolved": "https://registry.npmjs.org/@octokit/app/-/app-16.1.1.tgz", + "integrity": "sha512-pcvKSN6Q6aT3gU5heoDFs3ywU5xejxeqs1rQpUwgN7CmBlxCSy9aCoqFuC6GpVv71O/Qq/VuYfCNzrOZp/9Ycw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-app": "^8.1.1", + "@octokit/auth-unauthenticated": "^7.0.2", + "@octokit/core": "^7.0.5", + "@octokit/oauth-app": "^8.0.2", + "@octokit/plugin-paginate-rest": "^13.2.0", + "@octokit/types": "^15.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-app": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-app/-/auth-app-8.1.1.tgz", + "integrity": "sha512-yW9YUy1cuqWlz8u7908ed498wJFt42VYsYWjvepjojM4BdZSp4t+5JehFds7LfvYi550O/GaUI94rgbhswvxfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.2", + "@octokit/auth-oauth-user": "^6.0.1", + "@octokit/request": "^10.0.5", + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0", + "toad-cache": "^3.7.0", + "universal-github-app-jwt": "^2.2.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-app": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-app/-/auth-oauth-app-9.0.2.tgz", + "integrity": "sha512-vmjSHeuHuM+OxZLzOuoYkcY3OPZ8erJ5lfswdTmm+4XiAKB5PmCk70bA1is4uwSl/APhRVAv4KHsgevWfEKIPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.2", + "@octokit/auth-oauth-user": "^6.0.1", + "@octokit/request": "^10.0.5", + "@octokit/types": "^15.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-device": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-device/-/auth-oauth-device-8.0.2.tgz", + "integrity": "sha512-KW7Ywrz7ei7JX+uClWD2DN1259fnkoKuVdhzfpQ3/GdETaCj4Tx0IjvuJrwhP/04OhcMu5yR6tjni0V6LBihdw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/oauth-methods": "^6.0.1", + "@octokit/request": "^10.0.5", + "@octokit/types": "^15.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-oauth-user": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/auth-oauth-user/-/auth-oauth-user-6.0.1.tgz", + "integrity": "sha512-vlKsL1KUUPvwXpv574zvmRd+/4JiDFXABIZNM39+S+5j2kODzGgjk7w5WtiQ1x24kRKNaE7v9DShNbw43UA3Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-device": "^8.0.2", + "@octokit/oauth-methods": "^6.0.1", + "@octokit/request": "^10.0.5", + "@octokit/types": "^15.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-token": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-6.0.0.tgz", + "integrity": "sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/auth-unauthenticated": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@octokit/auth-unauthenticated/-/auth-unauthenticated-7.0.2.tgz", + "integrity": "sha512-vjcPRP1xsKWdYKiyKmHkLFCxeH4QvVTv05VJlZxwNToslBFcHRJlsWRaoI2+2JGCf9tIM99x8cN0b1rlAHJiQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/core": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@octokit/core/-/core-7.0.5.tgz", + "integrity": "sha512-t54CUOsFMappY1Jbzb7fetWeO0n6K0k/4+/ZpkS+3Joz8I4VcvY9OiEBFRYISqaI2fq5sCiPtAjRDOzVYG8m+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^6.0.0", + "@octokit/graphql": "^9.0.2", + "@octokit/request": "^10.0.4", + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0", + "before-after-hook": "^4.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/endpoint": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-11.0.1.tgz", + "integrity": "sha512-7P1dRAZxuWAOPI7kXfio88trNi/MegQ0IJD3vfgC3b+LZo1Qe6gRJc2v0mz2USWWJOKrB2h5spXCzGbw+fAdqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/graphql": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-9.0.2.tgz", + "integrity": "sha512-iz6KzZ7u95Fzy9Nt2L8cG88lGRMr/qy1Q36ih/XVzMIlPDMYwaNLE/ENhqmIzgPrlNWiYJkwmveEetvxAgFBJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request": "^10.0.4", + "@octokit/types": "^15.0.0", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-app": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@octokit/oauth-app/-/oauth-app-8.0.2.tgz", + "integrity": "sha512-Oln8yKrDIDKnp49GUCSqda+XZyocmNapumjA0JdTpRfwMpmFMnh3bCOtSgb3X/kHfdtT7Q9ihFeN1djn58forg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/auth-oauth-app": "^9.0.2", + "@octokit/auth-oauth-user": "^6.0.1", + "@octokit/auth-unauthenticated": "^7.0.2", + "@octokit/core": "^7.0.5", + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/oauth-methods": "^6.0.1", + "@types/aws-lambda": "^8.10.83", + "universal-user-agent": "^7.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-authorization-url": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@octokit/oauth-authorization-url/-/oauth-authorization-url-8.0.0.tgz", + "integrity": "sha512-7QoLPRh/ssEA/HuHBHdVdSgF8xNLz/Bc5m9fZkArJE5bb6NmVkDm3anKxXPmN1zh6b5WKZPRr3697xKT/yM3qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/oauth-methods": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@octokit/oauth-methods/-/oauth-methods-6.0.1.tgz", + "integrity": "sha512-xi6Iut3izMCFzXBJtxxJehxJmAKjE8iwj6L5+raPRwlTNKAbOOBJX7/Z8AF5apD4aXvc2skwIdOnC+CQ4QuA8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/oauth-authorization-url": "^8.0.0", + "@octokit/request": "^10.0.5", + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/openapi-types": { + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-26.0.0.tgz", + "integrity": "sha512-7AtcfKtpo77j7Ts73b4OWhOZHTKo/gGY8bB3bNBQz4H+GRSWqx2yvj8TXRsbdTE0eRmYmXOEY66jM7mJ7LzfsA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/openapi-webhooks-types": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/@octokit/openapi-webhooks-types/-/openapi-webhooks-types-12.0.3.tgz", + "integrity": "sha512-90MF5LVHjBedwoHyJsgmaFhEN1uzXyBDRLEBe7jlTYx/fEhPAk3P3DAJsfZwC54m8hAIryosJOL+UuZHB3K3yA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@octokit/plugin-paginate-graphql": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-graphql/-/plugin-paginate-graphql-6.0.0.tgz", + "integrity": "sha512-crfpnIoFiBtRkvPqOyLOsw12XsveYuY2ieP6uYDosoUegBJpSVxGwut9sxUgFFcll3VTOTqpUf8yGd8x1OmAkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-13.2.0.tgz", + "integrity": "sha512-YuAlyjR8o5QoRSOvMHxSJzPtogkNMgeMv2mpccrvdUGeC3MKyfi/hS+KiFwyH/iRKIKyx+eIMsDjbt3p9r2GYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "16.1.0", + "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-16.1.0.tgz", + "integrity": "sha512-nCsyiKoGRnhH5LkH8hJEZb9swpqOcsW+VXv1QoyUNQXJeVODG4+xM6UICEqyqe9XFr6LkL8BIiFCPev8zMDXPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.0" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=6" + } + }, + "node_modules/@octokit/plugin-retry": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-retry/-/plugin-retry-8.0.2.tgz", + "integrity": "sha512-mVPCe77iaD8g1lIX46n9bHPUirFLzc3BfIzsZOpB7bcQh1ecS63YsAgcsyMGqvGa2ARQWKEFTrhMJX2MLJVHVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": ">=7" + } + }, + "node_modules/@octokit/plugin-throttling": { + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@octokit/plugin-throttling/-/plugin-throttling-11.0.2.tgz", + "integrity": "sha512-ntNIig4zZhQVOZF4fG9Wt8QCoz9ehb+xnlUwp74Ic2ANChCk8oKmRwV9zDDCtrvU1aERIOvtng8wsalEX7Jk5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.0", + "bottleneck": "^2.15.3" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@octokit/core": "^7.0.0" + } + }, + "node_modules/@octokit/request": { + "version": "10.0.5", + "resolved": "https://registry.npmjs.org/@octokit/request/-/request-10.0.5.tgz", + "integrity": "sha512-TXnouHIYLtgDhKo+N6mXATnDBkV05VwbR0TtMWpgTHIoQdRQfCSzmy/LGqR1AbRMbijq/EckC/E3/ZNcU92NaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/endpoint": "^11.0.1", + "@octokit/request-error": "^7.0.1", + "@octokit/types": "^15.0.0", + "fast-content-type-parse": "^3.0.0", + "universal-user-agent": "^7.0.2" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/request-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-7.0.1.tgz", + "integrity": "sha512-CZpFwV4+1uBrxu7Cw8E5NCXDWFNf18MSY23TdxCBgjw1tXXHvTrZVsXlW8hgFTOLw8RQR1BBrMvYRtuyaijHMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/types": "^15.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/types": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-15.0.0.tgz", + "integrity": "sha512-8o6yDfmoGJUIeR9OfYU0/TUJTnMPG2r68+1yEdUeG2Fdqpj8Qetg0ziKIgcBm0RW/j29H41WP37CYCEhp6GoHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^26.0.0" + } + }, + "node_modules/@octokit/webhooks": { + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/@octokit/webhooks/-/webhooks-14.1.3.tgz", + "integrity": "sha512-gcK4FNaROM9NjA0mvyfXl0KPusk7a1BeA8ITlYEZVQCXF5gcETTd4yhAU0Kjzd8mXwYHppzJBWgdBVpIR9wUcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-webhooks-types": "12.0.3", + "@octokit/request-error": "^7.0.0", + "@octokit/webhooks-methods": "^6.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@octokit/webhooks-methods": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@octokit/webhooks-methods/-/webhooks-methods-6.0.0.tgz", + "integrity": "sha512-MFlzzoDJVw/GcbfzVC1RLR36QqkTLUf79vLVO3D+xn7r0QgxnFoLZgtrzxiQErAjFUOdH6fas2KeQJ1yr/qaXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@types/aws-lambda": { + "version": "8.10.155", + "resolved": "https://registry.npmjs.org/@types/aws-lambda/-/aws-lambda-8.10.155.tgz", + "integrity": "sha512-wd1XgoL0gy/ybo7WozUKQBd+IOgUkdfG6uUGI0fQOTEq06FBFdO7tmPDSxgjkFkl8GlfApvk5TvqZlAl0g+Lbg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -159,6 +533,20 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/before-after-hook": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-4.0.0.tgz", + "integrity": "sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/bottleneck": { + "version": "2.19.5", + "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", + "integrity": "sha512-VHiNCbI1lKdl44tGrhNfU3lup0Tj/ZBMJB5/2ZbNXRCPuRCO7ed2mgcK4r17y+KB2EfuYuRaVlwNbAeaWGSpbw==", + "dev": true, + "license": "MIT" + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -315,6 +703,23 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/fast-content-type-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-content-type-parse/-/fast-content-type-parse-3.0.0.tgz", + "integrity": "sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "MIT" + }, "node_modules/foreground-child": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", @@ -1169,6 +1574,46 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/octokit": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/octokit/-/octokit-5.0.3.tgz", + "integrity": "sha512-+bwYsAIRmYv30NTmBysPIlgH23ekVDriB07oRxlPIAH5PI0yTMSxg5i5Xy0OetcnZw+nk/caD4szD7a9YZ3QyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/app": "^16.0.1", + "@octokit/core": "^7.0.2", + "@octokit/oauth-app": "^8.0.1", + "@octokit/plugin-paginate-graphql": "^6.0.0", + "@octokit/plugin-paginate-rest": "^13.0.0", + "@octokit/plugin-rest-endpoint-methods": "^16.0.0", + "@octokit/plugin-retry": "^8.0.1", + "@octokit/plugin-throttling": "^11.0.1", + "@octokit/request-error": "^7.0.0", + "@octokit/types": "^14.0.0", + "@octokit/webhooks": "^14.0.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/octokit/node_modules/@octokit/openapi-types": { + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-25.1.0.tgz", + "integrity": "sha512-idsIggNXUKkk0+BExUn1dQ92sfysJrje03Q0bv0e+KPLrvyqZF8MnBpFz8UNfYDwB3Ie7Z0TByjWfzxt7vseaA==", + "dev": true, + "license": "MIT" + }, + "node_modules/octokit/node_modules/@octokit/types": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@octokit/types/-/types-14.1.0.tgz", + "integrity": "sha512-1y6DgTy8Jomcpu33N+p5w58l6xyt55Ar2I91RPiIA0xCJBXyUAhXCcmZaDWSANiha7R9a6qJJ2CRomGPZ6f46g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@octokit/openapi-types": "^25.1.0" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -1421,6 +1866,16 @@ "node": ">=14.14" } }, + "node_modules/toad-cache": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/toad-cache/-/toad-cache-3.7.0.tgz", + "integrity": "sha512-/m8M+2BJUpoJdgAHoG+baCwBT+tf2VraSfkBgl0Y00qIWt41DJ8R5B8nsEw0I58YwF5IZH6z24/2TobDKnqSWw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -1448,6 +1903,20 @@ "dev": true, "license": "MIT" }, + "node_modules/universal-github-app-jwt": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/universal-github-app-jwt/-/universal-github-app-jwt-2.2.2.tgz", + "integrity": "sha512-dcmbeSrOdTnsjGjUfAlqNDJrhxXizjAz94ija9Qw8YkZ1uu0d+GoZzyH+Jb9tIIqvGsadUfwg+22k5aDqqwzbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/universal-user-agent": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-7.0.3.tgz", + "integrity": "sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==", + "dev": true, + "license": "ISC" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 239d14f6..28b539a0 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "@blaahaj/lint-git-tree": "^1.1.0", "@types/markdown-it": "^14.1.2", "@types/node": "^22.14.1", + "octokit": "^5.0.3", "typescript": "^5.8.3" }, "scripts": { @@ -18,8 +19,8 @@ "lint:validate-links": "./lint validate-links", "lint:validate-links:watch": "node support/dist/watch.js ./lint validate-links", "lint:watch": "node support/dist/watch.js ./lint", - "generate:learninggoals": "node support/src/documentationHelpers/generateLearningGoals.js", - "generate:gitbook-summary": "node support/src/documentationHelpers/generateGitBookSummary.js", - "generate:contributors": "node support/src/documentationHelpers/generateContributors.mjs" + "generate:learninggoals": "node support/dist/documentationHelpers/generateLearningGoals.js", + "generate:gitbook-summary": "node support/dist/documentationHelpers/generateGitBookSummary.js", + "generate:contributors": "node support/dist/documentationHelpers/generateContributors.js" } } diff --git a/support/src/documentationHelpers/generateContributors.mjs b/support/src/documentationHelpers/generateContributors.ts similarity index 77% rename from support/src/documentationHelpers/generateContributors.mjs rename to support/src/documentationHelpers/generateContributors.ts index bca13ce0..ef5c9971 100644 --- a/support/src/documentationHelpers/generateContributors.mjs +++ b/support/src/documentationHelpers/generateContributors.ts @@ -9,19 +9,24 @@ import * as fs from "node:fs/promises"; +import { Octokit } from "octokit"; +const listContributors = (o: Octokit) => + o.request("GET /repos/{owner}/{repo}/contributors"); +type Contributor = Awaited>["data"][number]; + const fetch = global.fetch; // ---------------- CONFIG ---------------- const REPO = "HackYourFuture-CPH/program"; const PER_ROW = 6; const AVATAR_SIZE = 80; -const EXCLUDE_LOGINS = []; +const EXCLUDE_LOGINS: string[] = []; // ---------------------------------------- const API_BASE = "https://api.github.com"; -function parseLinkHeader(link) { - const rels = {}; +function parseLinkHeader(link: string | null): Record { + const rels: Record = {}; if (!link) return rels; link.split(",").forEach((part) => { const section = part.split(";"); @@ -33,9 +38,9 @@ function parseLinkHeader(link) { return rels; } -async function fetchPaginated(url) { - let results = []; - let next = url; +async function fetchPaginated(url: string): Promise { + let results: T[] = []; + let next: string | null = url; while (next) { const res = await fetch(next, { headers: { "User-Agent": "contributors-script" }, @@ -45,7 +50,7 @@ async function fetchPaginated(url) { `GitHub API error ${res.status} ${res.statusText} for ${next}`, ); } - const page = await res.json(); + const page = (await res.json()) as T[]; results = results.concat(page); const link = res.headers.get("link"); const rels = parseLinkHeader(link); @@ -54,7 +59,7 @@ async function fetchPaginated(url) { return results; } -function isBotLogin(login) { +function isBotLogin(login: string): boolean { if (!login) return false; return ( login.toLowerCase().endsWith("[bot]") || @@ -63,12 +68,12 @@ function isBotLogin(login) { ); } -function withAvatarSize(url, size) { +function withAvatarSize(url: string, size: number): string { if (!url) return url; return url + (url.includes("?") ? `&s=${size}` : `?s=${size}`); } -function chunk(array, size) { +function chunk(array: readonly T[], size: number): T[][] { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); @@ -76,9 +81,9 @@ function chunk(array, size) { return chunks; } -async function fetchContributors(repo) { +async function fetchContributors(repo: string): Promise { const url = `${API_BASE}/repos/${repo}/contributors?per_page=100&anon=false`; - const data = await fetchPaginated(url); + const data = await fetchPaginated(url); return data .map((c) => ({ login: c.login, @@ -90,10 +95,12 @@ async function fetchContributors(repo) { .filter((c) => !!c.login); } -function filterAndSortContributors(items) { +function filterAndSortContributors( + items: readonly Contributor[], +): Contributor[] { let list = items.filter((c) => { const excluded = - EXCLUDE_LOGINS.includes(c.login.toLowerCase()) || isBotLogin(c.login); + EXCLUDE_LOGINS.includes(c.login!.toLowerCase()) || isBotLogin(c.login!); const isUser = (c.type || "User") === "User"; return isUser && !excluded; }); @@ -102,13 +109,13 @@ function filterAndSortContributors(items) { list.sort((a, b) => { const byContrib = (b.contributions || 0) - (a.contributions || 0); if (byContrib !== 0) return byContrib; - return a.login.localeCompare(b.login, "en"); + return a.login!.localeCompare(b.login!, "en"); }); return list; } -function renderMarkdown(contributors) { +function renderMarkdown(contributors: readonly Contributor[]): string { const date = new Date().toISOString().split("T")[0]; const total = contributors.length; @@ -130,7 +137,7 @@ Total: **${total}** contributor${total === 1 ? "" : "s"} const avatarCells = row.map( (c) => `[![${c.login}](${withAvatarSize( - c.avatar_url, + c.avatar_url!, AVATAR_SIZE, )})](${c.html_url})`, ); @@ -145,7 +152,10 @@ Total: **${total}** contributor${total === 1 ? "" : "s"} return out; } -async function updateFileSection(contentToInsert, filename) { +async function updateFileSection( + contentToInsert: string, + filename: string, +): Promise { const oldContent = await fs.readFile(filename, "utf-8"); const matches = [ @@ -184,7 +194,7 @@ async function main() { const contributors = filterAndSortContributors(raw); const md = renderMarkdown(contributors); await updateFileSection(md, "./contributing/contributors.md"); - } catch (err) { + } catch (err: any) { console.error("Error:", err.message); process.exit(1); } diff --git a/support/src/documentationHelpers/generateGitBookSummary.js b/support/src/documentationHelpers/generateGitBookSummary.ts similarity index 80% rename from support/src/documentationHelpers/generateGitBookSummary.js rename to support/src/documentationHelpers/generateGitBookSummary.ts index 134e4012..9101dada 100644 --- a/support/src/documentationHelpers/generateGitBookSummary.js +++ b/support/src/documentationHelpers/generateGitBookSummary.ts @@ -11,24 +11,36 @@ * npm run generate:gitbook-summary */ -import fs from "fs"; +import fs, { type Dirent } from "fs"; import path from "path"; import process from "process"; +type ProgramStructure = { + readonly courses: readonly { + readonly name: string; + readonly location: string; + readonly modules: readonly { + readonly name: string; + readonly location: string; + }[]; + }[]; +}; + const jsonPath = "programStructure.json"; let rootDir = process.cwd(); let debug = false; // ---------------- Helpers ---------------- -const toPosix = (p) => p.split(path.sep).join("/"); -const posixJoin = (...parts) => toPosix(path.join(...parts)); -const toPosixFromRoot = (absPath) => { +const toPosix = (p: string): string => p.split(path.sep).join("/"); +const posixJoin = (...parts: readonly string[]): string => + toPosix(path.join(...parts)); +const toPosixFromRoot = (absPath: string): string => { const rel = path.relative(rootDir, absPath); return rel ? toPosix(rel) : "."; }; -const exists = (p) => { +const exists = (p: string): boolean => { try { fs.accessSync(p, fs.constants.F_OK); return true; @@ -37,25 +49,25 @@ const exists = (p) => { } }; -const listDirents = (dir) => +const listDirents = (dir: string): Dirent[] => exists(dir) ? fs.readdirSync(dir, { withFileTypes: true }) : []; -const listDirs = (dir) => +const listDirs = (dir: string): string[] => listDirents(dir) .filter((d) => d.isDirectory()) .map((d) => d.name); -const listFiles = (dir) => +const listFiles = (dir: string): string[] => listDirents(dir) .filter((d) => d.isFile()) .map((d) => d.name); -function ensureReadmeLink(baseAbsDir, relPosix) { +function ensureReadmeLink(baseAbsDir: string, relPosix: string): string { const candidate = path.join(baseAbsDir, "README.md"); if (exists(candidate)) return posixJoin(relPosix, "README.md"); return relPosix; } // Case-insensitive resolver -function resolveCaseInsensitive(absDir, wanted) { +function resolveCaseInsensitive(absDir: string, wanted: string): string | null { const exact = path.join(absDir, wanted); if (exists(exact)) return wanted; const files = listFiles(absDir); @@ -65,17 +77,17 @@ function resolveCaseInsensitive(absDir, wanted) { } const WEEK_RE = /^week(\d+)$/i; -const weekNumber = (name) => { +const weekNumber = (name: string): number | null => { const m = WEEK_RE.exec(name); return m ? parseInt(m[1], 10) : null; }; -const indent = (level) => " ".repeat(level); -const linkLine = (level, text, filePath) => +const indent = (level: number): string => " ".repeat(level); +const linkLine = (level: number, text: string, filePath: string): string => `${indent(level)}- ${filePath ? `[${text}](${filePath})` : text}`; // ---------------- Core ---------------- -function generateSummary(structure, rootDir) { +function generateSummary(structure: ProgramStructure, rootDir: string): string { if (!structure || !Array.isArray(structure.courses)) { throw new Error("Invalid structure JSON: expected { courses: [...] }"); } @@ -110,7 +122,7 @@ function generateSummary(structure, rootDir) { // discover week folders const weekFolders = listDirs(modAbs) .map((d) => ({ name: d, num: weekNumber(d) })) - .filter((w) => w.num !== null) + .filter((w): w is { name: string; num: number } => w.num !== null) .sort((a, b) => a.num - b.num); if (debug) @@ -173,11 +185,11 @@ function generateSummary(structure, rootDir) { // ---------------- Run ---------------- try { const raw = fs.readFileSync(jsonPath, "utf8"); - const structure = JSON.parse(raw); + const structure = JSON.parse(raw) as ProgramStructure; const md = generateSummary(structure, rootDir); process.stdout.write(md); -} catch (err) { +} catch (err: any) { console.error("Error:", err.message); process.exit(1); } diff --git a/support/src/documentationHelpers/generateLearningGoals.js b/support/src/documentationHelpers/generateLearningGoals.ts similarity index 85% rename from support/src/documentationHelpers/generateLearningGoals.js rename to support/src/documentationHelpers/generateLearningGoals.ts index 1bc8e7f6..7d22445d 100644 --- a/support/src/documentationHelpers/generateLearningGoals.js +++ b/support/src/documentationHelpers/generateLearningGoals.ts @@ -17,10 +17,25 @@ import { fileURLToPath } from "url"; import process from "process"; const scriptDir = dirname(fileURLToPath(import.meta.url)); + +type ProgramStructure = { + readonly courses: readonly { + readonly name: string; + readonly location: string; + readonly modules: readonly { + readonly name: string; + readonly location: string; + }[]; + }[]; +}; + const jsonPath = "programStructure.json"; -const outputLines = []; +const outputLines: string[] = []; -function extractLearningGoals(content) { +function extractLearningGoals(content: string): { + readonly found: boolean; + readonly goals: readonly string[]; +} { const sectionRegex = /#+\s*Learning goals\s*\n([\s\S]*?)(?=\n#+\s|$)/i; const match = content.match(sectionRegex); @@ -36,8 +51,10 @@ function extractLearningGoals(content) { }; } -async function processCourse(courseName) { - const data = JSON.parse(await readFile(jsonPath, "utf-8")); +async function processCourse(courseName: string): Promise { + const data = JSON.parse( + await readFile(jsonPath, "utf-8"), + ) as ProgramStructure; const course = data.courses.find((c) => c.name === courseName); if (!course) {