diff --git a/.github/workflows/cron.stale.yml b/.github/workflows/cron.stale.yml index 8767261..a816111 100644 --- a/.github/workflows/cron.stale.yml +++ b/.github/workflows/cron.stale.yml @@ -24,4 +24,4 @@ jobs: - name: Unassign contributor after days of inactivity uses: BoundfoxStudios/action-unassign-contributor-after-days-of-inactivity@v1.0.3 with: - last-activity: 14 \ No newline at end of file + last-activity: 14 diff --git a/index.js b/index.js deleted file mode 100644 index 17487f8..0000000 --- a/index.js +++ /dev/null @@ -1,106 +0,0 @@ -import express from "express"; -import {exec} from "child_process"; -import crypto from "crypto"; -import dotenv from "dotenv"; - -dotenv.config(); - -const app = express(); -const port = process.env.PORT || 3000; - -let isDocumentationWebsiteUpdated = false; -let isMindmapUpdated = false; -let contributorsBuildRequired = false; - -let documentationWebsiteBuildTime = 0; -let mindmapBuildTime = 0; -let contributorsBuildTime = 0; - -app.use(express.json()); - -app.post("/webhook", async (req, res) => { - console.log("req receieved"); - const signature = req.headers["x-hub-signature"]; - const payload = JSON.stringify(req.body); - - const hmac = crypto.createHmac("sha1", process.env.GITHUB_SECRET); - const calculatedSignature = `sha1=${hmac.update(payload).digest("hex")}`; - - if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(calculatedSignature))) { - const { result, respMessage } = await getBranchStatus(); - console.log("Result: ", result); - res.status(result).send(respMessage); - } else { - res.status(400).send("Invalid Github signature"); - } -}); - -app.listen(process.env.PORT, () => { - console.log(`Server listening on port ${port}`); -}); - -const executeCmd = async (cmd) => { - try { - const { stdout, stderr } = await exec(cmd); - return stderr + "\n" + stdout; - } catch (error) { - console.error(`exec error: ${error}`); - throw new Error(stderr + "\n" + stdout); - } -}; - -const getBranchStatus = async (req) => { - console.log("Webhook received successfully"); - - const branchName = req.body?.ref?.split("/").pop(); - if (!branchName) { - return 400, "Branch name not found in the request."; - } - return branchName === process.env.BRANCH_NAME ? await buildProject() : 202, "Build not required."; -}; - -const isUpdateRequired = () => { - const currentTime = Date.now(); - isMindmapUpdated = (currentTime - mindmapBuildTime) / 1000 / 60 > process.env.MINDMAP_UPDATE_TIME_INTERVAL ? true : false; - isDocumentationWebsiteUpdated = (currentTime - documentationWebsiteBuildTime) / 1000 / 60 > process.env.DOCUMENTATION_WEBSITE_UPDATE_TIME_INTERVAL ? true : false; - return isMindmapUpdated || isDocumentationWebsiteUpdated; -}; - -const buildProject = async () => { - const currentTime = Date.now(); - if (!isUpdateRequired()) { - if (contributorsBuildRequired || (currentTime - contributorsBuildTime) / 1000 / 60 > process.env.CONTRIBUTORS_UPDATE_TIME_INTERVAL) { - console.log("No update required, updating the contributors only"); - await initiateBuild("npm run contributor-build", process.env.DOCUMENTATION_WEBSITE_PATH, process.env.DOCUMENTATION_WEBSITE_DEST_PATH); - contributorsBuildTime = currentTime; - contributorsBuildRequired = false; - return 200; - } else { - contributorsBuildRequired = true; - return 202, "Contributors build will be done after the next build."; - } - } - if (isMindmapUpdated) { - console.log("Building Mindmap"); - await initiateBuild("npm run build", process.env.MINDMAP_PATH, process.env.MINDMAP_DEST_PATH); - mindmapBuildTime = currentTime; - isMindmapUpdated = false; - } - - if (isDocumentationWebsiteUpdated) { - console.log("Building Documentation Website"); - await initiateBuild("npm run build", process.env.DOCUMENTATION_WEBSITE_PATH, process.env.DOCUMENTATION_WEBSITE_DEST_PATH); - documentationWebsiteBuildTime = currentTime; - contributorsBuildTime = currentTime; - isDocumentationWebsiteUpdated = false; - } - - return 200, "Build has been created."; -}; - -const initiateBuild = async (command, projectPath, destPath) => { - await executeCmd(`cd ${projectPath}/ && git pull`); - await executeCmd(`cd ${projectPath}/ && npm ci`); - await executeCmd(`cd ${projectPath}/ && ${command}`); - await executeCmd(`cp -r ${projectPath}/dist/ ${destPath}/`); -}; diff --git a/index.ts b/index.ts new file mode 100644 index 0000000..5c1b762 --- /dev/null +++ b/index.ts @@ -0,0 +1,133 @@ +import express, {Request, Response} from "express"; +import {exec} from "child_process"; +import crypto from "crypto"; +import dotenv from "dotenv"; + +dotenv.config(); +// waiting for replies +const app = express(); +const port = process.env.PORT; +const secret = process.env.GITHUB_SECRET; +if(!secret) { + console.error("Error: GITHUB_SECRET environment variable is not set."); + process.exit(1); +}; + +let isDocumentationWebsiteUpdated = false; +let isMindmapUpdated = false; +let contributorsBuildRequired = false; + +let documentationWebsiteBuildTime = 0; +let mindmapBuildTime = 0; +let contributorsBuildTime = 0; + +app.use(express.json()); + +app.post("/webhook", async (req: Request, res: Response) => { + console.log("Request received"); + const signature = req.headers["x-hub-signature"] as string; + + if(!signature) { + throw new Error("Please provide a valid signature") + } + + const payload = JSON.stringify(req.body); + const hmac = crypto.createHmac("sha1", secret) + + const calculatedSignature = `sha1=${hmac.update(payload).digest("hex")}` + + if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(calculatedSignature))) { + const {result, respMessage} = await getBranchStatus(req.body); + console.log("Result: ", result); + res.status(200).send({ result, respMessage }); + } else { + res.status(400).send({ error: "Invalid GitHub signature. Ensure the secret is correctly configured." }); + } +}); + +interface BranchStatus { + result: number; + respMessage: string; +}; + +app.listen(port, () => { + console.log(`Server listening on port ${port}`); +}); + +const executeCmd = async (cmd: string) => { + try { + const {stdout, stderr} = await exec(cmd); + return stderr + "\n" + stdout; + } catch (error: unknown) { + console.error(`exec error: ${error}`); + throw new Error("Command execution failed. Check logs for details."); + }; +}; + +const getBranchStatus = async (req: Request): Promise => { + console.log("Webhook received successfully"); + + const branchName = req.body?.ref?.split("/").pop(); + + if (!branchName) { + return { result: 400, respMessage: "Branch name not found in the request." }; + } + + if (branchName === process.env.BRANCH_NAME) { + const { status, message } = await buildProject(); + return { result: status, respMessage: message }; + } + + return { result: 200, respMessage: "Build not required." }; + +}; + +const isUpdateRequired = () => { + const currentTime = Date.now() + const mindMapUpdateInterval = Number.parseInt(process.env.MINDMAP_UPDATE_TIME_INTERVAL ?? "10000"); + const documentationWebsiteUpdateInterval = Number.parseInt(process.env.DOCUMENTATION_WEBSITE_UPDATE_TIME_INTERVAL ?? "10000"); + isMindmapUpdated = (currentTime - mindmapBuildTime) / 1000 / 60 > mindMapUpdateInterval; + isDocumentationWebsiteUpdated = (currentTime - documentationWebsiteBuildTime) / 1000 / 60 > documentationWebsiteUpdateInterval; + return isMindmapUpdated || isDocumentationWebsiteUpdated; +}; + +const buildProject = async (): Promise<{ status: number; message: string }> => { + const currentTime = Date.now(); + const contributionUpdateTimeInterval = Number.parseInt(process.env.CONTRIBUTORS_UPDATE_TIME_INTERVAL ?? "10000"); + if (!isUpdateRequired()) { + if (contributorsBuildRequired || (currentTime - contributorsBuildTime) / 1000 / 60 > contributionUpdateTimeInterval) { + console.log("No update required, updating the contributors only"); + await initiateBuild("npm run contributor-build", process.env.DOCUMENTATION_WEBSITE_PATH!, process.env.DOCUMENTATION_WEBSITE_DEST_PATH!); + contributorsBuildTime = currentTime; + contributorsBuildRequired = false; + return { status: 200, message: "Contributors build has been created." }; + } else { + contributorsBuildRequired = true; + return { status: 202, message: "Contributors build will be done after the next build." }; + } + } + if (isMindmapUpdated) { + console.log("Building Mindmap"); + await initiateBuild("npm run build", process.env.MINDMAP_PATH!, process.env.MINDMAP_DEST_PATH!); + mindmapBuildTime = currentTime; + contributorsBuildTime = currentTime; + isMindmapUpdated = false; + } + + if (isDocumentationWebsiteUpdated) { + console.log("Building Documentation Website"); + await initiateBuild("npm run build", process.env.DOCUMENTATION_WEBSITE_PATH!, process.env.DOCUMENTATION_WEBSITE_DEST_PATH!); + documentationWebsiteBuildTime = currentTime; + contributorsBuildTime = currentTime; + isDocumentationWebsiteUpdated = false; + } + + return {status: 200, message: "Contributors build will be done after the next build."}; +}; + +const initiateBuild = async (command: string, projectPath: string, destPath: string) => { + await executeCmd(`cd ${(projectPath)}/ && git pull`); + await executeCmd(`cd ${(projectPath)}/ && npm ci`); + await executeCmd(`cd ${(projectPath)}/ && ${(command)}`); + await executeCmd(`cp -r ${(projectPath)}/dist/ ${(destPath)}/`); +}; diff --git a/package-lock.json b/package-lock.json index 95e8616..66fb3ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "devDependencies": { "@commitlint/cli": "^19.3.0", "@idrinth-api-bench/eslint-config": "https://github.com/idrinth-api-bench/eslint-config#setup-base-config", + "@types/express": "^4.17.21", "simple-git-hooks": "^2.11.1" }, "engines": { @@ -588,6 +589,27 @@ "node": ">= 8" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/conventional-commits-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", @@ -597,12 +619,52 @@ "@types/node": "*" } }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.5", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz", + "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.12.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", @@ -612,12 +674,49 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/semver": { "version": "7.5.8", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz", "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==", "dev": true }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.8.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz", @@ -1836,6 +1935,7 @@ "version": "4.19.2", "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", diff --git a/package.json b/package.json index 1da2456..68d1395 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "description": "This repository is our website deploy and update tool to minimize github api queries.", "main": "index.js", - "type" : "module", + "type": "module", "scripts": { "start": "node index.js", "lint": "eslint --ext=.ts --debug .", @@ -36,8 +36,9 @@ "express": "^4.19.2" }, "devDependencies": { - "@idrinth-api-bench/eslint-config": "https://github.com/idrinth-api-bench/eslint-config#setup-base-config", "@commitlint/cli": "^19.3.0", + "@idrinth-api-bench/eslint-config": "https://github.com/idrinth-api-bench/eslint-config#setup-base-config", + "@types/express": "^4.17.21", "simple-git-hooks": "^2.11.1" }, "engineStrict": true, diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..54bdc45 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + }, + "include": ["index.ts"], + "exclude": ["node_modules", "**/*.spec.ts"] +}