@relbns/github-cloner
Version:
Github Cloner is a CLI tool for cloning Github repositories with interactive lists in the terminal. the tool lists your repositories and sorts them by owners (users and organizations) and by name. choosing the repo will clone it to the current directory i
314 lines (276 loc) • 11.7 kB
JavaScript
import inquirer from 'inquirer';
import inquirerAutocomplete from 'inquirer-autocomplete-prompt';
import chalk from 'chalk';
import ora from 'ora';
import {
getAllRemoteBranches,
getLocalBranches,
getOpenPullRequests,
checkoutBranch,
pullLatestChanges,
stashChanges,
hasUncommittedChanges,
checkoutAndPull
} from './gitUtils.js';
import { exit, goBack } from './constants.js';
inquirer.registerPrompt('autocomplete', inquirerAutocomplete);
const searchChoices = (choices, input = '') => {
if (!input) return choices;
const searchTerm = input.toLowerCase();
return choices.filter(choice => {
const searchValue = choice.searchValue.toLowerCase();
return searchValue.includes(searchTerm);
});
};
const handleUncommittedChanges = async (branchName) => {
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'You have uncommitted changes. What would you like to do?',
choices: [
{ name: 'Stash changes', value: 'stash' },
// { name: 'Cancel operation', value: 'cancel' }
{ name: chalk.yellow(goBack), value: goBack },
{ name: chalk.yellow(exit), value: exit }
]
});
if (action === exit) {
console.log(chalk.yellow('Bye!'));
process.exit(0);
}
if (action === goBack) {
return false;
}
if (action === 'stash') {
const spinner = ora('Stashing changes...').start();
const result = await stashChanges();
spinner.stop();
if (!result.success) {
console.log(chalk.red(`Failed to stash changes: ${result.message}`));
return false;
}
console.log(chalk.green('Changes stashed successfully'));
return true;
}
return false;
};
export const handleBranchSelection = async (token) => {
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: 'What would you like to do?',
choices: [
{ name: 'Show branches from open PRs', value: 'prs' },
{ name: 'Show all remote branches', value: 'all' },
{ name: 'Clone a new repository', value: 'clone' },
{ name: chalk.yellow(exit), value: exit }
]
});
if (action === exit) {
console.log(chalk.yellow('Bye!'));
process.exit(0);
}
if (action === 'clone') {
return { shouldClone: true };
}
const spinner = ora('Fetching branches...').start();
try {
const localBranches = await getLocalBranches();
let branches = [];
if (action === 'prs') {
spinner.text = 'Fetching open pull requests...';
const prs = await getOpenPullRequests(token);
spinner.stop();
if (prs.length === 0) {
console.log(chalk.yellow('No open pull requests found.'));
return handleBranchSelection(token);
}
branches = prs.map(pr => ({
name: `${pr.branch} - ${pr.title} ${localBranches.includes(pr.branch)
? chalk.dim('(exists locally)')
: ''
}`,
value: pr.branch,
searchValue: `${pr.branch} ${pr.title}`,
exists: localBranches.includes(pr.branch)
}));
} else {
spinner.text = 'Fetching remote branches...';
const remoteBranches = await getAllRemoteBranches();
spinner.stop();
if (remoteBranches.length === 0) {
console.log(chalk.yellow('No remote branches found.'));
return handleBranchSelection(token);
}
branches = remoteBranches.map(branch => ({
name: `${branch.name} ${localBranches.includes(branch.name)
? chalk.dim('(exists locally)')
: ''
}`,
value: branch.name,
searchValue: branch.name,
exists: localBranches.includes(branch.name)
}));
if (branches.length === 0) {
console.log(chalk.yellow('No remote branches found.'));
return handleBranchSelection(token);
}
}
spinner.stop();
const { selectedBranch } = await inquirer.prompt({
type: 'autocomplete',
name: 'selectedBranch',
message: 'Select a branch (type to search):',
source: (answersYet, input) => {
return Promise.resolve(searchChoices([
...branches,
{
name: chalk.yellow(goBack),
value: goBack,
searchValue: goBack
},
{
name: chalk.yellow(exit),
value: exit,
searchValue: exit
}
], input));
},
pageSize: 10
});
if (selectedBranch === exit) {
console.log(chalk.yellow('Bye!'));
process.exit(0);
}
if (selectedBranch === goBack) {
return handleBranchSelection(token);
}
const selectedBranchInfo = branches.find(b => b.value === selectedBranch);
// Check for uncommitted changes first
if (await hasUncommittedChanges()) {
const shouldContinue = await handleUncommittedChanges(selectedBranch);
if (!shouldContinue) {
return handleBranchSelection(token);
}
}
if (selectedBranchInfo.exists) {
const { confirmPull } = await inquirer.prompt({
type: 'list',
name: 'confirmPull',
message: 'Branch exists locally. What would you like to do?',
choices: [
{ name: 'Pull latest changes', value: 'pull' },
{ name: chalk.yellow(goBack), value: goBack },
{ name: chalk.yellow(exit), value: exit }
]
});
if (confirmPull === exit) {
console.log(chalk.yellow('Bye!'));
process.exit(0);
}
if (confirmPull === goBack) {
return handleBranchSelection(token);
}
if (confirmPull === 'pull') {
spinner.start('Pulling latest changes...');
const result = await pullLatestChanges(selectedBranch);
spinner.stop();
if (!result.success) {
if (result.needsCheckout) {
// Handle different branch scenario
const { action } = await inquirer.prompt({
type: 'list',
name: 'action',
message: chalk.yellow(result.message) + '\nWhat would you like to do?',
choices: [
{ name: 'Checkout and pull branch', value: 'checkout' },
{ name: chalk.yellow(goBack), value: goBack },
{ name: chalk.yellow(exit), value: exit }
]
});
if (action === exit) {
console.log(chalk.yellow('Bye!'));
process.exit(0);
}
if (action === goBack) {
return handleBranchSelection(token);
}
if (action === 'checkout') {
spinner.start(`Checking out and pulling ${selectedBranch}...`);
const checkoutResult = await checkoutAndPull(selectedBranch);
spinner.stop();
if (!checkoutResult.success) {
console.log(chalk.red(checkoutResult.message));
return handleBranchSelection(token);
}
console.log(chalk.green(`Successfully switched to and pulled ${selectedBranch}`));
}
} else if (result.hasConflicts) {
// Handle merge conflicts
console.log(chalk.red('\nMerge conflicts detected!'));
const { conflictAction } = await inquirer.prompt({
type: 'list',
name: 'conflictAction',
message: 'How would you like to proceed?',
choices: [
{
name: 'Abort merge (restore to previous state)',
value: 'abort'
},
{
name: 'Leave as is (resolve conflicts manually)',
value: 'manual'
},
{ name: chalk.yellow(goBack), value: goBack },
{ name: chalk.yellow(exit), value: exit }
]
});
if (conflictAction === exit) {
console.log(chalk.yellow('Bye!'));
process.exit(0);
}
if (conflictAction === goBack) {
return handleBranchSelection(token);
}
if (conflictAction === 'abort') {
spinner.start('Aborting merge...');
const abortResult = await abortMerge();
spinner.stop();
if (abortResult.success) {
console.log(chalk.green('Merge aborted successfully'));
} else {
console.log(chalk.red(`Failed to abort merge: ${abortResult.message}`));
}
} else {
console.log(chalk.yellow('\nPlease resolve conflicts and commit the changes manually.'));
console.log(chalk.dim('Tip: Use `git status` to see conflicting files.'));
}
} else {
console.log(chalk.red(result.message));
}
} else {
console.log(chalk.green(result.message));
}
// if (result.success) {
// console.log(chalk.green(result.message));
// } else {
// console.log(chalk.red(result.message));
// }
}
} else {
spinner.start('Checking out branch...');
const result = await checkoutBranch(selectedBranch);
spinner.stop();
if (result.success) {
console.log(chalk.green(result.message));
} else {
console.log(chalk.red(result.message));
}
}
return { shouldClone: false };
} catch (error) {
spinner.fail();
console.error(chalk.red('Error:', error.message));
return handleBranchSelection(token);
}
};