diff --git a/DOTENV-example.env b/DOTENV-example.env deleted file mode 100644 index 9b2c613..0000000 --- a/DOTENV-example.env +++ /dev/null @@ -1,5 +0,0 @@ -ASSIGNMENT=https://github.com/hollisjamison/repo-name.git -REPONAME=repo-name -USERNAME=hollisjamison@gmail.com -TOKEN=GITHUB-ACCESS-TOKEN -SLACKTOKEN=SLACK-OAUTH-TOKEN \ No newline at end of file diff --git a/index.js b/index.js index a3f6042..5713201 100644 --- a/index.js +++ b/index.js @@ -1,140 +1,147 @@ // Import Student List -const { - studentList -} = require("./studentList"); +let { studentList } = require("./studentList"); + +let { repoDetails } = require("./repos.js"); // Import inquirer to make shell interactive const inquirer = require("inquirer"); // Import env file and all variables -require("dotenv").config(); -const assignmentUrl = process.env.ASSIGNMENT; -const repoName = process.env.REPONAME; -const token = process.env.TOKEN; -const githubUser = process.env.USERNAME; -const slackToken = process.env.SLACKTOKEN; - -// Import Axios for HTTP requests -const axios = require("axios"); - -// Import shelljs to run bash (git) commands -const shell = require("shelljs"); - -// Import Slack API to send messages to students -const { - WebClient -} = require("@slack/web-api"); -const slack = new WebClient(slackToken); - -const checkReposSilent = () => { - // create for loop to check all student repos and see if they exist - studentList.forEach((student) => { - // Do an HTTP get on each students supposed repo - axios.get(`https://github.com/${student.github}/${repoName}`).then( - (response) => { - console.log(`\x1b[32m ${student.github} has made their repo!`) - }, - (error) => { - console.log(`\x1b[31m ${student.github} has not made the repo yet.`); - } - ); - }); -}; - -const checkReposAlert = () => { - // create for loop to check all student repos and see if they exist - studentList.forEach((student) => { - // Do an HTTP get on each students supposed repo - axios.get(`https://github.com/${student.github}/${repoName}`).then( - (response) => { - console.log(`\x1b[32m ${student.github} has made their repo!`); - }, - (error) => { - sendMessage(student.slack); - console.log(`\x1b[31m ${student.github} has not made the repo yet. Alert sent.`); - } - ); - }); -}; - -// Function to clone assignment locally -const cloneAssignment = () => { - // Setup github authentication so you have access to stack education - shell.exec(`git config --global user.name "${githubUser}"`); - shell.exec(`git config --global user.password "${token}"`); +const dotenv = require('dotenv') +dotenv.config(); - // First remove the repo folder and remake it empty - shell.exec("rm -rf repo"); - shell.exec("mkdir repo"); +//const assignments = JSON.parse(dotenv.parse(Buffer.from(process.env.ASSIGNMENTS), opt)["REPOS"]); - // Now clone the repo into that folder - shell.cd("repo"); - shell.exec(`git clone ${assignmentUrl}`); +repoDetails = repoDetails.filter(repoDetail => !repoDetail.skip); - // Change branch to main as this is new Github default - shell.cd(`${repoName}`); - shell.exec("git checkout -b main"); -}; - -const pushToStudents = () => { - // Create a forloop to go through each student - studentList.forEach((student) => { - // First create a URL using the student's username and the repo name - const studentUrl = `https://github.com/${student.github}/${repoName}.git`; +studentList = studentList.filter(student => !student.skip); - // Force a push of the assignment main repo - shell.exec(`git push ${studentUrl} --force`); - }); -}; +studentList.forEach(student => { + student.name = student.name ? student.name : student.github +}); + +const { filterArrayBySelectedNames, setupRepos, checkReposContinuously } = require("./reposProcessor"); + +const processByOption = (option, allReposFiltered, allStudentsFiltered) => { + if ( + option == "1: Privately check if student repos exist. (No Slack message reminder).") { + checkReposContinuously(allStudentsFiltered, allReposFiltered, 0, false); + + // If Option 2 is selected do an alerting check. + } else if (option == "2: Check if student repos exist. (With Slack message reminder).") { + checkReposContinuously(allStudentsFiltered, allReposFiltered, 0, true); + + // If option 3 is selected then clone the assignment then push. + } else if (option == "3: Push assignment repo to students.") { + setupRepos(allStudentsFiltered, allReposFiltered); + + // If option 4 is selected then exit the app. + } + return; +} + +const findFilteredItemsBySelectedNames = (allItem, selectedNames, type) => { + // If option 1 is selected so a silent check. + if (selectedNames == "exit") { + return false; + + } else if (!selectedNames.length) { + console.log(`\x1b[33m Nothing was selected`); + return false; + + } else if (selectedNames.indexOf("all") > -1) { + if (selectedNames.length > 1) { + console.log(`\x1b[33m All Repos selected, will ignore other options`); + } + + return allItem; + } else {// filter out selected repos + let exitSelected = selectedNames.indexOf("exit"); + if (exitSelected > -1 && selectedNames.length > 1) {//dont really need to check size here again + console.log(`\x1b[33m Exit was selected along specific repos, ignoring Exit`); + + selectedNames.splice(exitSelected, 1); + } + + allItem = filterArrayBySelectedNames(allItem, selectedNames); + + if (allItem.length !== selectedNames.length) { + console.log(`Some ${type} are not found, found ${type} include: [${allItem}] and ${type} Names selected were [${selectedNames}]`) + return false; + } + + return allItem; + } +} + +const handlePromptAnswer = (answers) => { + + let reposSelectedByName = answers.repoOptions; + let studentsSelectedByName = answers.studentOptions; + + let allReposFiltered = repoDetails; + let allStudentsFiltered = studentList; + + console.log(` Repos selected: ${answers.repoOptions}\n`); + console.log(` Students selected: ${answers.studentOptions}\n`); + + allReposFiltered = findFilteredItemsBySelectedNames(allReposFiltered, reposSelectedByName, 'repos'); + + allStudentsFiltered = findFilteredItemsBySelectedNames(allStudentsFiltered, studentsSelectedByName, 'students'); + + if (!allReposFiltered || !allStudentsFiltered) { + console.log(`\x1b[33m Exiting`); + return; + } + + //console.log(`Final selected items: \n Repos: ${allReposFiltered}\n Students: ${allStudentsFiltered}`); + + processByOption(answers.option, allReposFiltered, allStudentsFiltered); +} + +const promptsTypes = [ + { + name: "repoOptions", + type: "checkbox", + message: "Please select repos you want to check or push", + choices: ["exit", "all", new inquirer.Separator()].concat(repoDetails), + default: ["all"] + }, + { + name: "studentOptions", + type: "checkbox", + message: "Please select people you want to check or push to", + choices: ["exit", "all", new inquirer.Separator()].concat(studentList), + default: ["all"], + loop: false + }, + { + name: "option", + type: "list", + message: "Please select your operation.", + when: (answers) => { + if (answers.studentOptions == "none" || !answers.studentOptions.length) { + return false; + } else if (answers.studentOptions == "none" || !answers.studentOptions.length) { + return false; + } + return true; + }, + choices: [ + "1: Privately check if student repos exist. (No Slack message reminder).", + "2: Check if student repos exist. (With Slack message reminder).", + "3: Push assignment repo to students.", + "4: Exit application.", + ], + }] // Function that controls interactivity const promptUser = () => { // Prompt the user to see what kind of app needs to run inquirer - .prompt([{ - name: "option", - type: "list", - message: "Please select your operation.", - choices: [ - "1: Privately check if student repos exist. (No Slack message reminder).", - "2: Check if student repos exist. (With Slack message reminder).", - "3: Push assignment repo to students.", - "4: Exit application.", - ], - }, ]) + .prompt(promptsTypes) // Then either check the repos or clone/push the assignment - .then((answer) => { - // If option 1 is selected so a silent check. - if ( - answer.option == - "1: Privately check if student repos exist. (No Slack message reminder)." - ) { - checkReposSilent(); - // If Option 2 is selected do an alerting check. - } else if ( - answer.option == - "2: Check if student repos exist. (With Slack message reminder)." - ) { - checkReposAlert(); - // If option 3 is selected then clone the assignment then push. - } else if (answer.option == "3: Push assignment repo to students.") { - cloneAssignment(); - pushToStudents(); - // If option 4 is selected then exit the app. - } else { - return; - } - }); -}; - -const sendMessage = (slackId) => { - // Connect to slack and post the reminder message using the slackId provided. - (async () => { - const result = await slack.chat.postMessage({ - channel: slackId, - text: `Your repo was not found. Please create the following repo on Github. If you have created the repo please check the spelling or make the repo public. \n \n *Repo Name:* \n \`${repoName}\``, - }); - })(); + .then(handlePromptAnswer); }; // When the app runs simply prompt the user. diff --git a/repos.js b/repos.js new file mode 100644 index 0000000..44d60b9 --- /dev/null +++ b/repos.js @@ -0,0 +1,52 @@ +const repoDetails = [ + { + url: "https://github.com/stackeducation/for-the-empire.git", + name: "for-the-empire", + skip: true + }, + { + url: "https://github.com/stackeducation/fantasy-scoring.git", + name: "fantasy-scoring", + skip: false + }, + { + url: "https://github.com/stackeducation/order-book.git", + name: "order-book", + skip: false + }, + { + url: "https://github.com/stackeducation/password-validator.git", + name: "password-validator", + skip: false + }, + { + url: "https://github.com/stackeducation/hazy-calculator.git", + name: "hazy-calculator", + skip: false + }, + { + url: "https://github.com/stackeducation/custom-error-page.git", + name: "custom-error-page", + skip: false + }, + { + url: "https://github.com/stackeducation/duck-hunt.git", + name: "duck-hunt", + skip: false + }, + { + url: "https://github.com/stackeducation/custom-error-page.git", + name: "custom-error-page", + skip: false + }, + { + url: "https://github.com/stackeducation/tv-synopsis.git", + name: "tv-synopsis", + skip: false + } + + + +]; + +module.exports = { repoDetails }; \ No newline at end of file diff --git a/reposProcessor.js b/reposProcessor.js new file mode 100644 index 0000000..9c6a118 --- /dev/null +++ b/reposProcessor.js @@ -0,0 +1,175 @@ + +// Import Axios for HTTP requests +const axios = require("axios"); + +// Import shelljs to run bash (git) commands +const shell = require("shelljs"); + +//const { studentList } = require("./studentList"); + +const token = process.env.TOKEN; +const githubUser = process.env.GITUSER; +const slackToken = process.env.SLACKTOKEN; + +// Import Slack API to send messages to students +const { WebClient } = require("@slack/web-api"); + +const slack = new WebClient(slackToken); + +function filterArrayBySelectedNames(allItems, selectedNames) { + + let allReposFiltered = []; + selectedNames.forEach(repoName => { + let reposFiltered = allItems.filter(repo => repo.name === repoName); + if (reposFiltered.length === 1) { + allReposFiltered.push(reposFiltered[0]); + } else { + console.log(`repo details for ${repoName} not found`); + } + }) + return allReposFiltered; +} + + +const checkRepo = (student, repoName, slackAlert, callback, failureCallBack) => { + return axios.get(`https://github.com/${student.github}/${repoName}`) + .then( + (response) => { + console.log(`\x1b[32m ${student.github} has made their '${repoName}' repo!`); + if (callback) { + callback(); + } + }, + (error) => { + if (slackAlert) { + sendMessage(student.slack, repoName); + } + console.log(`\n\x1b[31m ${student.github} has not made their '${repoName}' repo yet.` + (slackAlert ? ' Alert sent.' : '')); + if (failureCallBack) { + failureCallBack(); + } + } + ) +} + +const checkReposContinuously = (studentList, reposSelected, i, slackAlert) => { + // create for loop to check all student repos and see if they exist + + const repoName = reposSelected[i].name; + + console.log(`\n\x1b[33m ======= checking if '${repoName}' repo exists =======\n`); + + let promises = []; + studentList.forEach((student) => { + // Do an HTTP get on each students supposed repo + promises.push(checkRepo(student, repoName, slackAlert)); + }); + + // continue to next repo + Promise.allSettled(promises).then((results) => { + i++; + if (reposSelected[i]) { + checkReposContinuously(studentList, reposSelected, i, slackAlert); + } else { + console.log(`\n\x1b[33m ================= FINISHED ==================\n`); + } + + }); +}; + +const cloneAndPushRepos = (studentList, allSelectedRepos, i) => { + + let repoName = allSelectedRepos[i].name; + let repoUrl = allSelectedRepos[i].url; + + console.log(`\n\x1b[0m ==================================================`); + console.log(`\x1b[0m ===== Going to try cloning repo '${repoName}' =====`); + + let { stdoutClone, stderrClone, codeClone } = shell.exec(`git clone ${repoUrl}`, { silent: true }); + if (stderrClone) { + console.log(`\n\x1b[31m Could not clone ${repoUrl}`, stderrClone); + }else{ + console.log(`\n\x1b[32m Clone of ${repoUrl} done`); + } + + // Change branch to main as this is new Github default + shell.cd(`${repoName}`); + console.log(`\n\x1b[0m ===== Going to checkout main branch for repo '${repoName}' =====`); + let { stdoutCheckout, stderrCheckout, codeCheckout } = shell.exec("git checkout -b main", { silent: true }); + if (stderrCheckout) { + console.log(`\n\x1b[31m Could not create and switch to main branch`, stderrCheckout); + }else{ + console.log(`\n\x1b[32m Checked out main branch`); + } + + + pushRepoToStudents(studentList, repoName, allSelectedRepos, i); + +} + + +const pushRepoToStudents = (studentList, repoName, allSelectedRepos, i) => { + + console.log(`\n\x1b[0m ===== Going to try pushing repo '${repoName}' ====\n`); + // Create a forloop to go through each student + let repoCheckPromisesPerStudent = []; + studentList.forEach((student) => { + + repoCheckPromisesPerStudent.push(checkRepo(student, repoName, false, () => { + // First create a URL using the student's username and the repo name + const studentUrl = `https://github.com/${student.github}/${repoName}.git`; + + // Force a push of the assignment main repo + + let { stdout, stderr, code } = shell.exec(`git push ${studentUrl} --force`, { silent: true }); // --force + if (stderr) { + console.log(`\x1b[31m Could not push code to ${student.github} at ${studentUrl}. Error: ${stderr}`); + //console.log(`\x1b[31m Could not push code to ${student.github} at ${studentUrl}, Maybe they have not yet made the repo yet or you are not collabator on it`); + } + })); + + }); + + Promise.allSettled(repoCheckPromisesPerStudent).then((result) => { + console.log(`\n\x1b[32m ============ FINISHED pushing ${repoName} ============`); + + shell.cd("..");// go back to repo directory + + // continue to next repo + i++ + if (allSelectedRepos[i]) { + cloneAndPushRepos(studentList, allSelectedRepos, i); + }else{ + console.log(`\n\x1b[33m =================== FINISHED ===================\n`); + } + }); +}; + +// Function to clone assignment locally +const setupRepos = (studentList, allSelectedRepos) => { + + // Setup github authentication so you have access to stack education + shell.exec(`git config --global user.name "${githubUser}"`); + shell.exec(`git config --global user.password "${token}"`); + + // First remove the repo folder and remake it empty + shell.rm("-rf", "repo"); + shell.exec("mkdir repo"); + + // Now clone the repo into repo folder + shell.cd("repo"); + + cloneAndPushRepos(studentList, allSelectedRepos, 0); +}; + + +const sendMessage = (slackId, repoName) => { + // Connect to slack and post the reminder message using the slackId provided. + (async () => { + const result = await slack.chat.postMessage({ + channel: slackId, + text: `Your repo was not found. Please create the following repo on Github. If you have created the repo please check the spelling or make the repo public. \n \n *Repo Name:* \n \`${repoName}\` \n \n Then please add \`${githubUser}\` as a collaborator`, + }); + })(); +}; +module.exports = { filterArrayBySelectedNames, setupRepos, checkReposContinuously } \ No newline at end of file diff --git a/studentList.js b/studentList.js index 37fb747..16155ea 100644 --- a/studentList.js +++ b/studentList.js @@ -1,25 +1,21 @@ const studentList = [ - { github: "andyschachter", slack: "U01NMC0SZ27" }, - { github: "annie-i0", slack: "U01NECMJMJS" }, - { github: "smithci1", slack: "U01NMC0DDQB" }, - { github: "bellusvulpes", slack: "U01PB1A0Q64" }, - { github: "Danielamoateng1989", slack: "U01N6CNM8GP" }, - { github: "davepenaloza", slack: "U01NMC0FBFV" }, - { github: "firizarry-coder", slack: "U01PFUUJY8G" }, - { github: "gioaceto", slack: "U01NJ6LMVNJ" }, - { github: "jeraldina", slack: "U01NECMLJKG" }, - { github: "grimmi5894", slack: "U01NECMNP54" }, - { github: "keefers1030", slack: "U01NMC0USAF" }, - { github: "kolbycamp211", slack: "U01NMC16NUT" }, - { github: "lamsa01", slack: "U01NMC12ZB5" }, - { github: "mardany126", slack: "U01NECMDXE2" }, - { github: "misheline", slack: "U01NMC0PD51" }, - { github: "oaklanz", slack: "U01NMC0LBBM" }, - { github: "robinmalkus", slack: "U01NQUVF85Q" }, - { github: "pathou80", slack: "U01PB1ATD5W" }, - { github: "larocque-m", slack: "U01NECKHL4E" }, - { github: "markh82581", slack: "U01NMC0GUV9" }, - { github: "marnie-k", slack: "U01NMC0GUV9" }, + { skip: false, name: "karandeep", github: "kdsbatra", slack: "U0246KNS2KB" }, + { github: "brnjackson", slack: "U023U8NK89Z" }, + { github: "Cpluchak", slack: "U0246LJV90R" }, + { github: "Forensicrose", slack: "U0246LKG6E5" }, + { github: "jeraldina", slack: "U0246LK6D33" }, + { github: "kjmatt91", slack: "U0246LJTD97" }, + { github: "katskurka", slack: "U023Z44D690" }, + { github: "KevinSalina", slack: "U023V2RJA7P" }, + { github: "mhafez1978", slack: "U023R04GK1B" }, + { github: "RubyDF", slack: "U0246LKC43T" }, + { github: "sherryliztynan", slack: "U0246LJAMS5" }, + + // { github: "reinhal", slack: "U023U7T8RD1" }, + // { github: "StandyMerizier", slack: "U023QUP3KEZ" }, + // { github: "josephpjacobi", slack: "U0246KP1XL1" }, + // { github: "itsdanielguerrero@gmail.com" , slack: "U023D89UMAT" } + // { github: "hollisjamison", slack: "U01NMC0RD3M" }, // { github: "mikaelacurrier", slack: "U01N6CM60UX" }, // { github: "sebastian-vivas", slack: "U01NECMH4T0" },