mascot-app
Version:
Master ActionScript COnfigurator Tool. MASCOT automatically resolves ActionScript project dependencies and generates `asconfig.json` files for easy compilation with `asconfigc`.
193 lines (169 loc) • 5.9 kB
JavaScript
const shell = require("shelljs");
const axios = require("axios");
const path = require("path");
/**
* Clones a GitHub user's repositories to a local directory, with enhanced filtering, reporting, and error handling.
*
* @param {string} targetDir - The directory where the repositories will be cloned.
* @param {string} githubUser - The GitHub username whose repositories will be cloned.
* @param {Array<string>} withLanguages - Array of languages to filter repositories. Limited to 3 items.
* @param {boolean|undefined} fork - If true, only forks are included; if false, only non-forks are included; if undefined, no filtering is done on forks.
* @param {boolean} dryRun - If true, only lists the repositories without cloning.
*
* @returns {Object} - A detailed report about the process.
*/
async function cloneRepos(
targetDir,
githubUser,
withLanguages = [],
fork = undefined,
dryRun = false
) {
const report = {
report: {
listingResult: "failure",
cloningResult: "skipped",
listingDetails: {},
cloningDetails: {
numSucceeded: 0,
numFailed: 0,
failuresLog: [],
},
},
};
try {
// Limit the number of languages to 3 for safety
const safeLanguages = withLanguages
.slice(0, 3)
.map((lang) => `language:${lang}`);
// Construct the search query
const queryParts = [`user:${githubUser}`, ...safeLanguages];
if (fork === true) {
queryParts.push("fork:only"); // Include only forks
} else if (fork === false) {
queryParts.push("fork:false"); // Exclude forks
} else if (fork === undefined) {
queryParts.push("fork:true"); // Include both forks and non-forks
}
const query = queryParts.join(" ");
// Fetch the repositories using GitHub Search API
const response = await axios.get(
`https://api.github.com/search/repositories?q=${encodeURIComponent(
query
)}&per_page=100`
);
const repos = response.data.items;
if (!repos.length) {
report.report.listingDetails = {
numMatches: 0,
};
report.report.listingResult = "success";
console.log("No repositories found matching the criteria.");
return report;
}
// Prepare the listing report details
const matchesByLanguage = {};
const matchesByType = { forks: 0, non_forks: 0 };
repos.forEach((repo) => {
repo.language &&
(matchesByLanguage[repo.language] =
(matchesByLanguage[repo.language] || 0) + 1);
repo.fork ? matchesByType.forks++ : matchesByType.non_forks++;
});
report.report.listingDetails = {
numMatches: repos.length,
matchesByLanguage: matchesByLanguage,
matchesByType: matchesByType,
};
report.report.listingResult = "success";
report.repos = repos;
if (dryRun) {
console.log("Dry run mode - no cloning performed.");
return report;
}
// Clone each repository
let numSucceeded = 0;
let numFailed = 0;
const failuresLog = [];
for (const repo of repos) {
const cloneUrl = repo.clone_url;
const repoName = repo.name;
const targetPath = path.normalize(path.join(targetDir, repoName));
const cloneResult = await cloneRepo(cloneUrl, repoName, targetPath);
if (cloneResult.result === "success") {
numSucceeded++;
} else {
numFailed++;
failuresLog.push(cloneResult);
}
}
// Update the cloning details report
report.report.cloningDetails.numSucceeded = numSucceeded;
report.report.cloningDetails.numFailed = numFailed;
if (failuresLog.length > 0)
report.report.cloningDetails.failuresLog = failuresLog;
if (numSucceeded > 0 && numFailed === 0) {
report.report.cloningResult = "success";
} else if (numSucceeded > 0 && numFailed > 0) {
report.report.cloningResult = "partial_failure";
} else if (numSucceeded === 0 && numFailed > 0) {
report.report.cloningResult = "failure";
}
console.log("Cloning process complete.");
} catch (error) {
report.report.listingResult = "failure";
const errMsg = `${error.message}${
error.message.includes("ENOTFOUND api.github.com")
? " (are you online?)"
: ""
}`;
report.report.listingFailureDetail = errMsg;
}
return report;
}
/**
* Clones a single repository from GitHub to a specified path.
*
* @param {string} cloneUrl - The Git clone URL of the repository.
* @param {string} repoName - The name of the repository.
* @param {string} targetPath - The directory where the repository will be cloned.
*
* @returns {Object} - An object containing details about the cloning process.
*/
async function cloneRepo(cloneUrl, repoName, targetPath) {
const taskDetails = { cloneUrl, repoName, targetPath };
let commandOutput;
try {
console.log(`Cloning ${repoName} into ${targetPath}...`);
const execResult = shell.exec(`git clone ${cloneUrl} ${targetPath}`, {
silent: true,
});
commandOutput = {
stdout: execResult.stdout || undefined,
stderr: execResult.stderr || undefined,
};
if (execResult.code !== 0) {
return {
taskDetails,
result: "failure",
failureType: "git_error",
failureDetail: execResult.stderr || undefined,
commandOutput,
};
}
return {
taskDetails,
result: "success",
commandOutput,
};
} catch (error) {
return {
taskDetails,
result: "failure",
failureType: "exception",
failureDetail: error.message || undefined,
commandOutput: undefined,
};
}
}
module.exports = { cloneRepos, cloneRepo };