diff --git a/README.md b/README.md index ddaf786..3c423aa 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,8 @@ jobs: uses: hasithaishere/route-commenter-action@main env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - INPUT_COMMENT_CONTENT_FILE: '.github/config/route-comment-content.md' + INPUT_COMMENT_CONTENT_FILE: '.github/config/route-comment-content.md' # Optional: has hardcoded default content + INPUT_TAG_NAME: 'change-route' # Optional: default is 'change-route' ``` Create a route comment content file in your repository (e.g., `.github/config/route-comment-content.md`): @@ -50,6 +51,23 @@ Create a route comment content file in your repository (e.g., `.github/config/ro ``` This content file will be optional and the default content will be used if not provided. +## For Maintainers + +As general, this Github action also use ncc to package the code in to single js file. So in the development please globally install ncc first. + +```sh +npm i -g @vercel/ncc --save +``` + +After your development, please execute following command for building the package file, then push the code to GitHub. + +```sh +npm run build +``` + +## Developers + +- [Hasitha Gamage](hasitha@rocketbots.io) ## License diff --git a/dist/index.js b/dist/index.js index 041ee62..fc819cf 100644 --- a/dist/index.js +++ b/dist/index.js @@ -32083,17 +32083,29 @@ module.exports = JSON.parse('[[[0,44],"disallowed_STD3_valid"],[[45,46],"valid"] var __webpack_exports__ = {}; // This entry need to be wrapped in an IIFE because it need to be isolated against other modules in the chunk. (() => { +/** + * @fileoverview This GitHub Action script detects changes in route files within a monorepo, + * adds comments to the PR where route changes are detected, and requests changes if necessary. + * It also adds a specific label to the PR if route changes are found. + */ + const fs = __nccwpck_require__(7147); const path = __nccwpck_require__(1017); const { execSync } = __nccwpck_require__(2081); const { Octokit } = __nccwpck_require__(1540); const { context } = __nccwpck_require__(8555); +// Constants const GITHUB_TOKEN = process.env.GITHUB_TOKEN; const COMMENT_FILE_PATH = process.env.INPUT_COMMENT_CONTENT_FILE; +const TAG_NAME = process.env.INPUT_TAG_NAME || 'change-route'; const octokit = new Octokit({ auth: GITHUB_TOKEN }); +/** + * Gets the list of changed files in the pull request. + * @returns {Promise} - An array of filenames that have been changed. + */ async function getChangedFiles() { const prNumber = context.payload.pull_request.number; const owner = context.repo.owner; @@ -32108,10 +32120,20 @@ async function getChangedFiles() { return files.map(file => file.filename); } +/** + * Checks if the specified folder is a service folder by looking for a 'routes' subfolder. + * @param {string} folderPath - The path of the folder to check. + * @returns {boolean} - True if the folder is a service folder, false otherwise. + */ function isServiceFolder(folderPath) { return fs.existsSync(path.join(folderPath, 'routes')); } +/** + * Recursively gets all route files in the specified folder. + * @param {string} folderPath - The path of the folder to search for route files. + * @returns {string[]} - An array of paths to the route files. + */ function getRouteFiles(folderPath) { let routeFiles = []; const items = fs.readdirSync(folderPath, { withFileTypes: true }); @@ -32133,6 +32155,12 @@ function getRouteFiles(folderPath) { return routeFiles; } +/** + * Detects the routes in the specified file that have changed based on the changed lines. + * @param {string} filePath - The path to the file to analyze. + * @param {number[]} changedLines - An array of line numbers that have changed in the file. + * @returns {Object[]} - An array of route objects that have changed. + */ function detectRoutesInFile(filePath, changedLines) { const data = fs.readFileSync(filePath, 'utf8'); const lines = data.split('\n'); @@ -32200,6 +32228,11 @@ function detectRoutesInFile(filePath, changedLines) { return routes.filter(route => changedLines.some(line => line >= route.startLine && line <= route.endLine)); } +/** + * Gets the diff hunks for the specified file. + * @param {string} filePath - The path to the file to get the diff hunks for. + * @returns {Promise} - An array of diff hunk objects. + */ async function getDiffHunks(filePath) { const diffOutput = execSync(`git diff --unified=0 HEAD~1 HEAD ${filePath}`).toString(); const diffHunks = diffOutput.split('\n').filter(line => line.startsWith('@@')).map(hunk => { @@ -32213,6 +32246,12 @@ async function getDiffHunks(filePath) { return diffHunks; } +/** + * Finds the corresponding line number in the original file for the specified target line in the diff hunks. + * @param {Object[]} diffHunks - An array of diff hunk objects. + * @param {number} targetLine - The target line number in the diff hunks. + * @returns {number|null} - The corresponding line number in the original file, or null if not found. + */ function findDiffHunkLineNumber(diffHunks, targetLine) { for (const hunk of diffHunks) { if (targetLine >= hunk.newStart) { @@ -32222,11 +32261,17 @@ function findDiffHunkLineNumber(diffHunks, targetLine) { return null; } +/** + * Gets the lines that need to be commented on based on the detected routes and changed lines. + * @param {Object[]} routes - An array of route objects. + * @param {number[]} changedLines - An array of changed line numbers. + * @returns {number[]} - An array of line numbers to be commented on. + */ function getCommentingLines(routes, changedLines) { const commentingLines = []; changedLines.sort().forEach((line) => { routes.forEach((route, index) => { - if (route.selectedLine == undefined && line >= route.startLine && line <= route.endLine) { + if (route.selectedLine === undefined && line >= route.startLine && line <= route.endLine) { routes[index]['selectedLine'] = line; commentingLines.push(line); } @@ -32235,6 +32280,14 @@ function getCommentingLines(routes, changedLines) { return commentingLines; } +/** + * Gets the existing comments made by the bot in the pull request. + * @param {string} owner - The owner of the repository. + * @param {string} repo - The name of the repository. + * @param {number} pullNumber - The pull request number. + * @param {string} botUsername - The username of the bot. + * @returns {Promise} - An array of existing comment objects. + */ async function getExistingComments(owner, repo, pullNumber, botUsername) { const { data: comments } = await octokit.rest.pulls.listReviewComments({ owner, @@ -32245,6 +32298,14 @@ async function getExistingComments(owner, repo, pullNumber, botUsername) { return comments.filter(comment => comment.user.login === botUsername); } +/** + * Adds comments to the pull request for the specified lines and file. + * @param {number[]} commentingLines - An array of line numbers to comment on. + * @param {string} file - The path to the file to comment on. + * @param {Object[]} existingComments - An array of existing comment objects. + * @param {string} commentBody - The body of the comment to add. + * @returns {Promise} - An object indicating whether a comment was added. + */ async function addPRComments(commentingLines, file, existingComments, commentBody) { let commentAdded = false; if (commentingLines.length > 0) { @@ -32278,6 +32339,10 @@ async function addPRComments(commentingLines, file, existingComments, commentBod return { commentAdded }; } +/** + * Gets the body of the comment from the specified file or returns a default comment body. + * @returns {Promise} - The body of the comment. + */ async function getCommentBody() { // Default comment body let commentBody = ` @@ -32294,7 +32359,15 @@ async function getCommentBody() { return commentBody; } -async function addLabelIfNotExists(owner, repo, prNumber, labelName, labelColor) { +/** + * Adds a label to the pull request if it does not already exist. + * @param {string} owner - The owner of the repository. + * @param {string} repo - The name of the repository. + * @param {number} prNumber - The pull request number. + * @param {string} labelName - The name of the label to add. + * @returns {Promise} + */ +async function addLabelIfNotExists(owner, repo, prNumber, labelName) { const { data: { labels } } = await octokit.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, @@ -32313,6 +32386,10 @@ async function addLabelIfNotExists(owner, repo, prNumber, labelName, labelColor) } } +/** + * Main function to execute the GitHub Action. + * @returns {Promise} + */ async function main() { const rootPath = 'service'; const changedFiles = await getChangedFiles(); @@ -32368,8 +32445,8 @@ async function main() { body: 'Please address the comments related to the route changes.', }); - // Add 'routes-changed' label with red color - await addLabelIfNotExists(context.repo.owner, context.repo.repo, context.payload.pull_request.number, 'routes-changed', 'ff0000'); + // Add 'change-route' label with red color + await addLabelIfNotExists(context.repo.owner, context.repo.repo, context.payload.pull_request.number, TAG_NAME); } } diff --git a/index.js b/index.js index 4b3dc90..5c271c0 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,26 @@ +/** + * @fileoverview This GitHub Action script detects changes in route files within a monorepo, + * adds comments to the PR where route changes are detected, and requests changes if necessary. + * It also adds a specific label to the PR if route changes are found. + */ + const fs = require('fs'); const path = require('path'); const { execSync } = require('child_process'); const { Octokit } = require('@octokit/rest'); const { context } = require('@actions/github'); +// Constants const GITHUB_TOKEN = process.env.GITHUB_TOKEN; const COMMENT_FILE_PATH = process.env.INPUT_COMMENT_CONTENT_FILE; +const TAG_NAME = process.env.INPUT_TAG_NAME || 'change-route'; const octokit = new Octokit({ auth: GITHUB_TOKEN }); +/** + * Gets the list of changed files in the pull request. + * @returns {Promise} - An array of filenames that have been changed. + */ async function getChangedFiles() { const prNumber = context.payload.pull_request.number; const owner = context.repo.owner; @@ -23,10 +35,20 @@ async function getChangedFiles() { return files.map(file => file.filename); } +/** + * Checks if the specified folder is a service folder by looking for a 'routes' subfolder. + * @param {string} folderPath - The path of the folder to check. + * @returns {boolean} - True if the folder is a service folder, false otherwise. + */ function isServiceFolder(folderPath) { return fs.existsSync(path.join(folderPath, 'routes')); } +/** + * Recursively gets all route files in the specified folder. + * @param {string} folderPath - The path of the folder to search for route files. + * @returns {string[]} - An array of paths to the route files. + */ function getRouteFiles(folderPath) { let routeFiles = []; const items = fs.readdirSync(folderPath, { withFileTypes: true }); @@ -48,6 +70,12 @@ function getRouteFiles(folderPath) { return routeFiles; } +/** + * Detects the routes in the specified file that have changed based on the changed lines. + * @param {string} filePath - The path to the file to analyze. + * @param {number[]} changedLines - An array of line numbers that have changed in the file. + * @returns {Object[]} - An array of route objects that have changed. + */ function detectRoutesInFile(filePath, changedLines) { const data = fs.readFileSync(filePath, 'utf8'); const lines = data.split('\n'); @@ -115,6 +143,11 @@ function detectRoutesInFile(filePath, changedLines) { return routes.filter(route => changedLines.some(line => line >= route.startLine && line <= route.endLine)); } +/** + * Gets the diff hunks for the specified file. + * @param {string} filePath - The path to the file to get the diff hunks for. + * @returns {Promise} - An array of diff hunk objects. + */ async function getDiffHunks(filePath) { const diffOutput = execSync(`git diff --unified=0 HEAD~1 HEAD ${filePath}`).toString(); const diffHunks = diffOutput.split('\n').filter(line => line.startsWith('@@')).map(hunk => { @@ -128,6 +161,12 @@ async function getDiffHunks(filePath) { return diffHunks; } +/** + * Finds the corresponding line number in the original file for the specified target line in the diff hunks. + * @param {Object[]} diffHunks - An array of diff hunk objects. + * @param {number} targetLine - The target line number in the diff hunks. + * @returns {number|null} - The corresponding line number in the original file, or null if not found. + */ function findDiffHunkLineNumber(diffHunks, targetLine) { for (const hunk of diffHunks) { if (targetLine >= hunk.newStart) { @@ -137,11 +176,17 @@ function findDiffHunkLineNumber(diffHunks, targetLine) { return null; } +/** + * Gets the lines that need to be commented on based on the detected routes and changed lines. + * @param {Object[]} routes - An array of route objects. + * @param {number[]} changedLines - An array of changed line numbers. + * @returns {number[]} - An array of line numbers to be commented on. + */ function getCommentingLines(routes, changedLines) { const commentingLines = []; changedLines.sort().forEach((line) => { routes.forEach((route, index) => { - if (route.selectedLine == undefined && line >= route.startLine && line <= route.endLine) { + if (route.selectedLine === undefined && line >= route.startLine && line <= route.endLine) { routes[index]['selectedLine'] = line; commentingLines.push(line); } @@ -150,6 +195,14 @@ function getCommentingLines(routes, changedLines) { return commentingLines; } +/** + * Gets the existing comments made by the bot in the pull request. + * @param {string} owner - The owner of the repository. + * @param {string} repo - The name of the repository. + * @param {number} pullNumber - The pull request number. + * @param {string} botUsername - The username of the bot. + * @returns {Promise} - An array of existing comment objects. + */ async function getExistingComments(owner, repo, pullNumber, botUsername) { const { data: comments } = await octokit.rest.pulls.listReviewComments({ owner, @@ -160,6 +213,14 @@ async function getExistingComments(owner, repo, pullNumber, botUsername) { return comments.filter(comment => comment.user.login === botUsername); } +/** + * Adds comments to the pull request for the specified lines and file. + * @param {number[]} commentingLines - An array of line numbers to comment on. + * @param {string} file - The path to the file to comment on. + * @param {Object[]} existingComments - An array of existing comment objects. + * @param {string} commentBody - The body of the comment to add. + * @returns {Promise} - An object indicating whether a comment was added. + */ async function addPRComments(commentingLines, file, existingComments, commentBody) { let commentAdded = false; if (commentingLines.length > 0) { @@ -193,6 +254,10 @@ async function addPRComments(commentingLines, file, existingComments, commentBod return { commentAdded }; } +/** + * Gets the body of the comment from the specified file or returns a default comment body. + * @returns {Promise} - The body of the comment. + */ async function getCommentBody() { // Default comment body let commentBody = ` @@ -209,7 +274,15 @@ async function getCommentBody() { return commentBody; } -async function addLabelIfNotExists(owner, repo, prNumber, labelName, labelColor) { +/** + * Adds a label to the pull request if it does not already exist. + * @param {string} owner - The owner of the repository. + * @param {string} repo - The name of the repository. + * @param {number} prNumber - The pull request number. + * @param {string} labelName - The name of the label to add. + * @returns {Promise} + */ +async function addLabelIfNotExists(owner, repo, prNumber, labelName) { const { data: { labels } } = await octokit.rest.pulls.get({ owner: context.repo.owner, repo: context.repo.repo, @@ -228,6 +301,10 @@ async function addLabelIfNotExists(owner, repo, prNumber, labelName, labelColor) } } +/** + * Main function to execute the GitHub Action. + * @returns {Promise} + */ async function main() { const rootPath = 'service'; const changedFiles = await getChangedFiles(); @@ -283,8 +360,8 @@ async function main() { body: 'Please address the comments related to the route changes.', }); - // Add 'routes-changed' label with red color - await addLabelIfNotExists(context.repo.owner, context.repo.repo, context.payload.pull_request.number, 'routes-changed', 'ff0000'); + // Add 'change-route' label with red color + await addLabelIfNotExists(context.repo.owner, context.repo.repo, context.payload.pull_request.number, TAG_NAME); } }