diff --git a/packages/core/index.ts b/packages/core/index.ts index 23c834a..456f6af 100644 --- a/packages/core/index.ts +++ b/packages/core/index.ts @@ -4,6 +4,7 @@ import { preparePayload } from './utils/payload/install'; import { installSupabase } from './utils/supabase/install'; import { prettify } from './utils/prettier/prettify'; import { prepareDrink } from './utils/bar/prepareDrink'; +import { initializeRepository } from './utils/github/install'; interface ProjectOptions { name: string; @@ -31,5 +32,10 @@ export async function createProject(options: ProjectOptions) { await prettify(); + initializeRepository({ + projectName: name, + visibility: 'private', + }); + prepareDrink(name); } diff --git a/packages/core/utils/github/ghInstaller.ts b/packages/core/utils/github/ghInstaller.ts new file mode 100644 index 0000000..769175c --- /dev/null +++ b/packages/core/utils/github/ghInstaller.ts @@ -0,0 +1,68 @@ +import { execSync } from 'child_process'; +import * as os from 'os'; + +export function isGitHubCLIInstalled(): boolean { + try { + execSync('gh --version', { stdio: 'ignore' }); + return true; + } catch (error) { + return false; + } +} + +export function installGitHubCLI(): boolean { + const platform = os.platform(); + let installCommand: string; + + switch (platform) { + case 'darwin': // macOS + installCommand = 'brew install gh'; + break; + case 'linux': // Linux + const linuxDistro = getLinuxDistro(); + if (linuxDistro === 'ubuntu' || linuxDistro === 'debian') { + installCommand = + 'sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-key C99B11DEB97541F0 && sudo apt-add-repository https://cli.github.com/packages && sudo apt update && sudo apt install gh'; + } else if (linuxDistro === 'fedora' || linuxDistro === 'centos' || linuxDistro === 'rhel') { + installCommand = + 'sudo dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo && sudo dnf install gh'; + } else { + console.log('🖇️ Automatic installation is not supported for your Linux distribution.'); + console.log('🖇️ Please visit https://github.com/cli/cli#installation for installation instructions.'); + return false; + } + break; + case 'win32': // Windows + installCommand = 'winget install --id GitHub.cli'; + break; + default: + console.log('🖇️ Automatic installation is not supported for your operating system.'); + console.log('🖇️ Please visit https://github.com/cli/cli#installation for installation instructions.'); + return false; + } + + console.log('🖇️ Installing GitHub CLI...'); + try { + execSync(installCommand, { stdio: 'inherit' }); + console.log('🖇️ GitHub CLI installed successfully.'); + return true; + } catch (error) { + console.error('🍸 Failed to install GitHub CLI.'); + console.log('🖇️ Please install it manually from: https://github.com/cli/cli#installation'); + return false; + } +} + +export function getLinuxDistro(): string { + try { + const osRelease = execSync('cat /etc/os-release').toString(); + if (osRelease.includes('Ubuntu')) return 'ubuntu'; + if (osRelease.includes('Debian')) return 'debian'; + if (osRelease.includes('Fedora')) return 'fedora'; + if (osRelease.includes('CentOS')) return 'centos'; + if (osRelease.includes('Red Hat')) return 'rhel'; + return 'unknown'; + } catch (error) { + return 'unknown'; + } +} diff --git a/packages/core/utils/github/install.ts b/packages/core/utils/github/install.ts new file mode 100644 index 0000000..fb56081 --- /dev/null +++ b/packages/core/utils/github/install.ts @@ -0,0 +1,65 @@ +import { + isGitHubAuthenticated, + createGitHubRepository, + fetchGitHubUsername, + authenticateGitHub, + setupGitRepository, +} from './repositoryManager'; +import { installGitHubCLI, isGitHubCLIInstalled } from './ghInstaller'; + +interface ProjectOptions { + projectName: string; + visibility: 'public' | 'private'; +} + +// Helper function to check if GitHub CLI is installed +function checkGitHubCLI() { + console.log('🖇️ Checking GitHub CLI installation...'); + if (!isGitHubCLIInstalled()) { + console.log('🖇️ GitHub CLI is not installed.'); + const installed = installGitHubCLI(); + if (!installed) { + console.error('🖇️ GitHub CLI installation failed. Exiting...'); + process.exit(1); + } + } +} + +// Helper function to ensure GitHub authentication +function ensureGitHubAuthentication() { + console.log('🖇️ Checking GitHub authentication...'); + + // Check if the user is already authenticated + if (isGitHubAuthenticated()) { + console.log('🖇️ You are already logged in to GitHub.'); + return; // Exit early if authenticated + } + + if (!authenticateGitHub()) { + console.error('🖇️ Authentication failed. Run "gh auth login" in your terminal and try again.'); + process.exit(1); + } +} + +export async function initializeRepository(options: ProjectOptions) { + const { projectName, visibility } = options; + + checkGitHubCLI(); + ensureGitHubAuthentication(); + + // Retrieve GitHub username once + const username = await fetchGitHubUsername(); + if (!username) { + console.error('🖇️ Failed to retrieve GitHub username. Aborting repository creation.'); + process.exit(1); + } + + // Check if the repository exists and create it + const repoCreated = await createGitHubRepository(projectName, visibility, username); + if (!repoCreated) { + console.error('🖇️ Failed to create GitHub repository. Check your permissions or if the repository already exists.'); + process.exit(1); + } + + await setupGitRepository(projectName, username); +} diff --git a/packages/core/utils/github/repositoryManager.ts b/packages/core/utils/github/repositoryManager.ts new file mode 100644 index 0000000..5de555e --- /dev/null +++ b/packages/core/utils/github/repositoryManager.ts @@ -0,0 +1,128 @@ +import { execSync } from 'child_process'; + +async function executeCommand(command: string, silent = false): Promise { + try { + const result = execSync(command, { stdio: 'pipe' }); // Use 'pipe' to capture output + const output = result.toString().trim(); // Convert buffer to string + return output || null; // Ensure we return null if output is empty + } catch (error) { + console.error(`Error executing command: ${command}`); + if (error instanceof Error) { + console.error(`Error message: ${error.message}`); + if (error) { + console.error(`Command stdout: ${error.toString()}`); // Log stdout + } + } + return null; + } +} + +export function isGitHubAuthenticated(): boolean { + console.log('🖇️ Checking GitHub authentication status...'); + + try { + // Use execSync to run the command and capture output + const result = execSync('gh auth status', { stdio: 'pipe' }).toString(); + + // Check if the output includes "Logged in" - this is to be changed in the future but couldn't find a better way + return result.includes('Logged in'); + } catch (error) { + console.error(`🖇️ Error checking authentication status: ${error}`); + return false; + } +} + +export async function authenticateGitHub(): Promise { + console.log('🖇️ Attempting to authenticate with GitHub...'); + + const result = await executeCommand('gh auth login'); + + if (result) { + // Immediately check authentication status after login attempt + const isAuthenticated = isGitHubAuthenticated(); + + if (isAuthenticated) { + console.log('🖇️ Authentication was successful.'); + return true; + } else { + console.error('🖇️ Authentication failed after login attempt.'); + return false; + } + } + + console.error('🖇️ No output from gh auth login command.'); + return false; +} + +export async function fetchGitHubUsername(): Promise { + try { + // Run the command without --jq first to inspect raw output + const username = await executeCommand('echo "$(gh api user --jq .login)"'); + + if (username) { + console.log(`🖇️ Hello \x1b[36m${username}\x1b[0m!`); + return username; + } else { + console.log('🖇️ No username returned or an error occurred.'); + return null; + } + } catch (error) { + console.error('🖇️ Error fetching GitHub username:', error); + return null; + } +} + +export async function createGitHubRepository( + projectName: string, + repositoryVisibility: 'public' | 'private', + username: string, +): Promise { + console.log(`🖇️ Checking if repository already exists...`); + + // Check if the repository exists + const repoCheckCommand = `echo "$(gh repo view ${username}/${projectName} --json name)"`; + const existingRepo = await executeCommand(repoCheckCommand, true); // Silent mode to suppress output + + if (existingRepo) { + console.error(`🖇️ Repository "${projectName}" already exists.`); + return false; // Return false to indicate the repo was not created + } + + console.log(`🖇️ Creating GitHub repository: \x1b[36m${projectName}\x1b[0m`); + + const visibility = repositoryVisibility === 'public' ? '--public' : '--private'; + const command = `gh repo create ${projectName} ${visibility} --confirm`; + + const result = await executeCommand(command); + + if (result) { + console.log(`🖇️ Repository created successfully at \x1b[36m${result}\x1b[0m`); + return true; // Return true to indicate success + } + + console.error('🖇️ Failed to create GitHub repository.'); + return false; // Return false on failure +} + +// New function to set up the local Git repository +export async function setupGitRepository(projectName: string, username: string) { + console.log(`🖇️ Setting up Git for the repository...`); + + // Set the remote origin and push to GitHub + const commands = [ + `git init`, + `git add .`, + `git commit -m "feat: initial commit"`, + `git branch -M main`, + `git remote add origin git@github.com:${username}/${projectName}.git`, + `git push -u origin main`, + ]; + + for (const cmd of commands) { + const result = executeCommand(cmd); + if (!result) { + console.error(`🖇️ Failed to execute command: ${cmd}`); + process.exit(1); + } + } +}