UNPKG

@deriv-com/shiftai-cli

Version:

A comprehensive AI code detection and analysis CLI tool for tracking AI-generated code in projects

223 lines (194 loc) 6.63 kB
const fs = require('fs-extra'); const path = require('path'); const { execSync } = require('child_process'); const chalk = require('chalk'); const inquirer = require('inquirer'); const { getAllRemotes } = require('../utils/git-utils'); /** * Remote Selector - Manages main remote selection per project */ class RemoteSelector { constructor(options = {}) { this.projectRoot = options.projectRoot || process.cwd(); this.projectHash = options.projectHash; this.shiftaiDir = path.join(require('os').homedir(), '.shiftai'); } /** * Get the path for storing remote selection for this project */ getRemoteConfigPath() { if (this.projectHash) { return path.join(this.shiftaiDir, this.projectHash, 'remote-config.json'); } // Fallback to project-local config return path.join(this.projectRoot, '.shiftai-remote-config.json'); } /** * Load the selected remote for this project */ async getSelectedRemote() { try { const configPath = this.getRemoteConfigPath(); if (await fs.pathExists(configPath)) { const config = await fs.readJson(configPath); return config.selectedRemote || null; } } catch (error) { // Config doesn't exist or is invalid } return null; } /** * Save the selected remote for this project */ async saveSelectedRemote(remoteName, remoteInfo) { try { const configPath = this.getRemoteConfigPath(); await fs.ensureDir(path.dirname(configPath)); const config = { selectedRemote: remoteName, remoteInfo: remoteInfo, selectedAt: new Date().toISOString(), projectPath: this.projectRoot }; await fs.writeJson(configPath, config, { spaces: 2 }); return true; } catch (error) { console.warn(chalk.yellow(`⚠ Could not save remote selection: ${error.message}`)); return false; } } /** * Get all available remotes with enhanced information */ getAvailableRemotes() { const remotes = getAllRemotes(); const remoteList = []; for (const [name, info] of Object.entries(remotes)) { remoteList.push({ name, url: info.url, organization: info.organization, repository: info.repository, displayName: `${name} (${info.organization}/${info.repository})` }); } return remoteList; } /** * Check if remote selection is needed */ async shouldPromptForRemote() { const selectedRemote = await this.getSelectedRemote(); if (selectedRemote) { // Check if the selected remote still exists in git const remotes = getAllRemotes(); if (remotes[selectedRemote]) { return false; // Already have valid selection } else { // Selected remote no longer exists, clear the invalid selection console.warn(`Warning: Selected remote '${selectedRemote}' no longer exists. Clearing selection.`); await this.resetRemoteSelection(); } } // Check if we have multiple remotes (use actual git remotes, not cached) const actualRemotes = getAllRemotes(); return Object.keys(actualRemotes).length > 1; } /** * Prompt user to select main remote for diff comparisons using interactive arrow keys */ async promptForRemoteSelection() { const availableRemotes = this.getAvailableRemotes(); if (availableRemotes.length === 0) { throw new Error('No git remotes found'); } if (availableRemotes.length === 1) { // Only one remote, use it automatically const remote = availableRemotes[0]; await this.saveSelectedRemote(remote.name, remote); return remote.name; } console.log(); console.log(chalk.blue('🔄 Multiple Git Remotes Detected')); console.log(chalk.blue('═══════════════════════════════')); console.log(chalk.yellow('ShiftAI needs to know which remote to use for diff comparisons.')); console.log(chalk.gray('This selection will be saved for this project.')); console.log(); // Create choices for inquirer with enhanced display const choices = availableRemotes.map(remote => ({ name: `${chalk.cyan(remote.name)} ${chalk.gray(`(${remote.organization}/${remote.repository})`)}`, value: remote, short: remote.name })); try { const answers = await inquirer.prompt([ { type: 'list', name: 'selectedRemote', message: 'Select the main remote for diff comparisons:', choices: choices, pageSize: 10, loop: false } ]); const selectedRemote = answers.selectedRemote; console.log(); console.log(chalk.green(`✅ Selected: ${selectedRemote.displayName}`)); // Save the selection const saved = await this.saveSelectedRemote(selectedRemote.name, selectedRemote); if (saved) { console.log(chalk.green('✅ Remote selection saved for this project')); } return selectedRemote.name; } catch (error) { if (error.name === 'ExitPromptError') { throw new Error('Remote selection cancelled'); } throw error; } } /** * Get the main remote for diff comparisons (with auto-selection if needed) */ async getMainRemote() { // Check if we already have a selection const selectedRemote = await this.getSelectedRemote(); if (selectedRemote) { const remotes = getAllRemotes(); if (remotes[selectedRemote]) { return selectedRemote; } } // Check if we need to prompt const shouldPrompt = await this.shouldPromptForRemote(); if (shouldPrompt) { return await this.promptForRemoteSelection(); } // Single remote or no remotes - use default logic const availableRemotes = this.getAvailableRemotes(); if (availableRemotes.length === 1) { const remote = availableRemotes[0]; await this.saveSelectedRemote(remote.name, remote); return remote.name; } // Fallback to origin return 'origin'; } /** * Reset remote selection for this project */ async resetRemoteSelection() { try { const configPath = this.getRemoteConfigPath(); if (await fs.pathExists(configPath)) { await fs.remove(configPath); return true; } } catch (error) { console.warn(chalk.yellow(`⚠ Could not reset remote selection: ${error.message}`)); } return false; } } module.exports = RemoteSelector;