muggleout
Version:
Transform muggles into terminal wizards / 비개발자를 터미널 마법사로 만들어주는 도구
732 lines (653 loc) • 32.2 kB
JavaScript
import chalk from 'chalk';
import inquirer from 'inquirer';
import { createSpinner, isCI } from '../utils/ci-helper.js';
import { execa } from 'execa';
import { checkInstalled, saveInstallRecord } from '../utils/config.js';
import { runCommand, commandExists } from '../utils/runner.js';
import { SudoManager } from '../utils/sudo-manager.js';
// checkCommand 함수 정의
async function checkCommand(command) {
if (command.startsWith('test ')) {
// test 명령어는 runCommand로 직접 실행
try {
const result = await runCommand(command, { silent: true });
return result.exitCode === 0;
} catch {
return false;
}
} else if (command.startsWith('ls ')) {
// ls 명령어도 runCommand로 실행
try {
const result = await runCommand(command, { silent: true });
return result.exitCode === 0;
} catch {
return false;
}
}
return commandExists(command);
}
// 예상 설치 시간
const estimatedTimes = {
'homebrew': '5-10분',
'iterm2': '1-2분',
'oh-my-zsh': '1-2분',
'p10k': '30초',
'nvm': '1분',
'node': '2-3분',
'git': '1분',
'claude-code': '1분',
'gemini-cli': '30초'
};
// 설치 가능한 도구들의 설정
const installConfigs = {
'homebrew': {
name: 'Homebrew',
check: () => commandExists('brew'),
install: async () => {
console.log(chalk.yellow('\n📋 Homebrew 설치 안내'));
console.log(chalk.gray('━'.repeat(50)));
console.log(chalk.yellow('\n⚠️ 설치 중 이런 일이 일어납니다:'));
console.log(chalk.cyan(' 1. 비밀번호 입력 → Mac 로그인 비밀번호를 입력하세요'));
console.log(chalk.gray(' (입력해도 화면에 아무것도 안 보이는게 정상입니다!)'));
console.log(chalk.cyan(' 2. "Press RETURN/ENTER to continue" → Enter 키를 누르세요'));
console.log(chalk.cyan(' 3. 한번 더 비밀번호 입력 → 다시 입력하세요'));
console.log(chalk.red('\n 💡 설치를 취소하려면 Ctrl+C를 누르세요'));
console.log(chalk.gray('━'.repeat(50)));
// inquirer로 준비 확인 (stdin 충돌 방지)
const { ready } = await inquirer.prompt([{
type: 'confirm',
name: 'ready',
message: '위 안내사항을 확인했습니다. 설치를 시작하시겠습니까?',
default: true
}]);
if (!ready) {
console.log(chalk.yellow('설치가 취소되었습니다.'));
return;
}
console.log(chalk.blue('\n🚀 Homebrew 설치를 시작합니다...'));
console.log(chalk.yellow('💡 비밀번호를 물어보면 Mac 로그인 비밀번호를 입력하세요!'));
console.log(chalk.gray('(비밀번호는 보안상 화면에 표시되지 않습니다)\n'));
try {
// Homebrew 설치 스크립트는 자체적으로 sudo를 처리하므로 별도의 권한 요청 불필요
console.log(chalk.blue('📥 Homebrew 설치 스크립트를 다운로드합니다...'));
console.log(chalk.gray('설치 스크립트가 필요한 권한을 자동으로 요청할 거예요\n'));
// 임시 파일로 스크립트 다운로드 후 실행 (TTY 유지)
const tmpFile = '/tmp/homebrew-install.sh';
await runCommand(`curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh -o ${tmpFile}`, { silent: true });
await runCommand(`chmod +x ${tmpFile}`, { silent: true });
await runCommand(`${tmpFile}`, {
interactive: true,
stdio: 'inherit' // TTY 유지를 위해 inherit 사용
});
await runCommand(`rm -f ${tmpFile}`, { silent: true });
} catch (error) {
// 사용자가 취소한 경우
if (error.message.includes('SIGINT') || error.message.includes('canceled')) {
console.log(chalk.yellow('\n설치가 취소되었습니다.'));
return;
}
console.log(chalk.red('\n❌ Homebrew 설치 중 오류가 발생했습니다.'));
// 권한 문제인 경우
if (error.message.includes('sudo') || error.message.includes('permission') || error.message.includes('Password')) {
console.log(chalk.yellow('\n💡 비밀번호 관련 팁:'));
console.log('• Mac 로그인할 때 사용하는 비밀번호를 입력하세요');
console.log('• 입력 시 화면에 표시되지 않습니다 (정상)');
console.log('• Caps Lock이 켜져있지 않은지 확인하세요');
console.log('• 한글로 입력하고 있지 않은지 확인하세요');
}
// brew가 이미 설치되어 있는지 확인
const brewExists = await commandExists('brew');
if (brewExists) {
console.log(chalk.green('\n✅ 하지만 brew가 이미 설치되어 있습니다!'));
return;
}
// 권한 문제인 경우
if (error.message.includes('sudo') || error.message.includes('Administrator')) {
console.log(chalk.red('\n❌ 관리자 권한이 필요합니다!'));
console.log(chalk.yellow('\n💡 해결 방법:'));
console.log('1. 현재 사용자가 관리자 권한을 가지고 있는지 확인하세요');
console.log('2. 시스템 환경설정 > 사용자 및 그룹에서 확인 가능합니다');
console.log('3. 관리자 계정으로 로그인 후 다시 시도하세요');
console.log(chalk.gray('\n자세한 내용: https://support.apple.com/ko-kr/HT204012'));
} else if (error.message.includes('non-interactive mode') || error.message.includes('stdin is not a TTY')) {
console.log(chalk.red('\n❌ 터미널 환경 문제가 발생했습니다!'));
console.log(chalk.yellow('\n💡 해결 방법:'));
console.log('1. 정식 터미널 앱(Terminal.app 또는 iTerm2)을 사용하세요');
console.log('2. VS Code 터미널이나 다른 에디터 내장 터미널에서는 작동하지 않을 수 있습니다');
console.log('3. 수동 설치: https://brew.sh 에서 설치 명령어를 복사하여 실행하세요');
} else {
// 일반적인 해결 방법
console.log(chalk.yellow('\n💡 해결 방법:'));
console.log('1. 인터넷 연결을 확인하세요');
console.log('2. 다시 시도해보세요: muggleout install homebrew');
console.log('3. 수동 설치: https://brew.sh 에서 설치 명령어 복사');
}
throw error;
}
// PATH 설정
const arch = process.arch;
const brewPath = arch === 'arm64' ? '/opt/homebrew' : '/usr/local';
console.log(chalk.blue('\n🔧 PATH 설정 중...'));
await runCommand(`echo 'eval "$(${brewPath}/bin/brew shellenv)"' >> ~/.zprofile`);
// 현재 세션에도 PATH 적용
process.env.PATH = `${brewPath}/bin:${brewPath}/sbin:${process.env.PATH}`;
},
postInstall: async () => {
console.log(chalk.green('\n✅ Homebrew 설치 완료!'));
console.log(chalk.yellow('💡 다음 단계:'));
console.log(' 1. 터미널을 완전히 종료했다가 다시 열어주세요');
console.log(' 2. 다시 열고 이 명령어로 확인하세요:');
console.log(chalk.cyan(' brew --version'));
console.log(chalk.gray(' → Homebrew 4.x.x 같은 버전이 나오면 성공!'));
}
},
'iterm2': {
name: 'iTerm2',
check: () => checkCommand('test -d /Applications/iTerm.app'),
requires: ['homebrew'],
install: async () => {
console.log(chalk.cyan(`⏱️ 예상 소요 시간: ${estimatedTimes.iterm2}`));
await runCommand('brew install --cask iterm2');
},
postInstall: async () => {
console.log(chalk.green('\n✅ iTerm2가 설치되었습니다!'));
console.log(chalk.yellow('💡 iTerm2를 열려면 Spotlight에서 "iTerm"을 검색하세요.'));
// 현재 터미널이 기본 터미널인 경우 iTerm2로 전환 제안
const { getCurrentTerminal, openInITerm } = await import('../utils/terminal-check.js');
const currentTerminal = getCurrentTerminal();
if (currentTerminal === 'Terminal') {
console.log(chalk.blue('\n🔄 iTerm2로 자동 전환하시겠습니까?'));
const { switchToITerm } = await inquirer.prompt([
{
type: 'confirm',
name: 'switchToITerm',
message: 'iTerm2에서 dev-setup을 다시 실행할까요?',
default: true
}
]);
if (switchToITerm) {
console.log(chalk.green('iTerm2를 실행합니다...'));
await openInITerm('dev-setup');
console.log(chalk.yellow('\n💡 iTerm2에서 계속 진행하세요!'));
process.exit(0);
}
}
}
},
'oh-my-zsh': {
name: 'Oh My Zsh',
check: () => checkCommand('test -d ~/.oh-my-zsh'),
install: async () => {
console.log(chalk.yellow('📋 Oh My Zsh 설치를 시작합니다...'));
console.log(chalk.cyan(`⏱️ 예상 소요 시간: ${estimatedTimes['oh-my-zsh']}`));
await runCommand('sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended');
},
postInstall: async () => {
console.log(chalk.green('\n✅ Oh My Zsh 설치 완료!'));
console.log(chalk.yellow('💡 다음 단계:'));
console.log(' 1. 터미널을 재시작하면 새로운 테마가 적용됩니다');
console.log(' 2. 확인 명령어:');
console.log(chalk.cyan(' echo $ZSH_THEME'));
console.log(chalk.gray(' → robbyrussell 같은 테마 이름이 나오면 성공!'));
console.log(' 3. Powerlevel10k 테마를 추가로 설치하면 더 예뻐집니다!');
}
},
'p10k': {
name: 'Powerlevel10k',
check: () => checkCommand('test -d ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k'),
requires: ['oh-my-zsh'],
install: async () => {
console.log(chalk.cyan(`⏱️ 예상 소요 시간: ${estimatedTimes.p10k}`));
await runCommand('git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k');
await runCommand('sed -i.bak \'s/ZSH_THEME=".*"/ZSH_THEME="powerlevel10k\\/powerlevel10k"/\' ~/.zshrc');
console.log(chalk.green('✨ 터미널을 재시작하면 설정 마법사가 시작됩니다!'));
},
postInstall: async () => {
console.log(chalk.green('\n✅ Powerlevel10k 테마 설치 완료!'));
console.log(chalk.yellow('🎨 중요: 다음 단계를 꼭 따라해주세요!'));
console.log(chalk.cyan('\n1. iTerm2를 완전히 종료했다가 다시 열어주세요'));
console.log(chalk.cyan('2. 자동으로 Powerlevel10k 설정 마법사가 시작됩니다'));
console.log(chalk.cyan('3. 질문에 답하면서 원하는 스타일을 선택하세요'));
console.log(chalk.gray('\n팁: y(Yes) 또는 n(No)로 답하고, 숫자로 스타일을 선택하세요'));
console.log(chalk.gray('\n테스트 명령어:'));
console.log(chalk.cyan(' p10k configure ') + chalk.gray('# 설정 마법사 다시 실행'));
}
},
'node': {
name: 'Node.js',
check: () => checkCommand('node'),
requires: ['homebrew'],
install: async () => {
await runCommand('brew install node');
}
},
'claude-code': {
name: 'Claude Code CLI',
check: () => checkCommand('claude'),
requires: ['node'],
install: async () => {
console.log(chalk.blue('📦 Claude Code CLI 설치 중...'));
console.log(chalk.cyan(`⏱️ 예상 소요 시간: ${estimatedTimes['claude-code']}`));
try {
await runCommand('npm install -g @anthropic-ai/claude-code');
console.log(chalk.green('✅ 설치 완료!'));
} catch (error) {
if (error.message.includes('EACCES') || error.message.includes('permission denied')) {
console.log(chalk.red('\n❌ npm 전역 설치 권한 문제가 발생했습니다!'));
console.log(chalk.yellow('\n💡 해결 방법을 선택하세요:'));
const { solution } = await inquirer.prompt([{
type: 'list',
name: 'solution',
message: '어떻게 해결하시겠습니까?',
choices: [
{ name: '1. sudo로 다시 시도 (간단하지만 권장하지 않음)', value: 'sudo' },
{ name: '2. npm 전역 디렉토리 변경 (권장)', value: 'npm-global' },
{ name: '3. 나중에 해결하기', value: 'skip' }
]
}]);
if (solution === 'sudo') {
console.log(chalk.yellow('\n⚠️ sudo를 사용한 npm 설치는 권장하지 않습니다!'));
console.log(chalk.gray('이유: 나중에 권한 문제가 계속 발생할 수 있습니다\n'));
const hasSudo = await SudoManager.requestSudo('Claude Code CLI 설치 (npm)');
if (!hasSudo) {
console.log(chalk.yellow('\n설치가 취소되었습니다.'));
return;
}
await runCommand('sudo npm install -g @anthropic-ai/claude-code');
console.log(chalk.green('✅ 설치 완료!'));
console.log(chalk.yellow('\n💡 향후 npm 전역 디렉토리 변경을 권장합니다'));
console.log(chalk.gray('방법: muggleout fix 실행 후 "npm 권한 문제" 선택'));
} else if (solution === 'npm-global') {
console.log(chalk.blue('\nnpm 전역 디렉토리를 설정합니다...'));
await runCommand('mkdir -p ~/.npm-global');
await runCommand('npm config set prefix "~/.npm-global"');
await runCommand('echo \'export PATH=~/.npm-global/bin:$PATH\' >> ~/.zprofile');
await runCommand('export PATH=~/.npm-global/bin:$PATH');
console.log(chalk.green('✅ 설정 완료! 다시 설치합니다...'));
await runCommand('npm install -g @anthropic-ai/claude-code');
console.log(chalk.green('✅ Claude Code CLI 설치 완료!'));
console.log(chalk.yellow('\n⚠️ 터미널을 재시작하면 어디서나 claude 명령을 사용할 수 있습니다!'));
} else {
console.log(chalk.yellow('\n나중에 다음 명령으로 설치하세요:'));
console.log(chalk.cyan(' muggleout install claude-code'));
return;
}
} else {
throw error;
}
}
},
postInstall: async () => {
console.log(chalk.yellow('\n📌 다음 단계:'));
console.log('1. 프로젝트 폴더로 이동: cd your-project');
console.log('2. Claude 실행: claude');
console.log('3. 자연어로 코딩 시작!');
console.log(chalk.gray('\n예시: "이 파일의 버그를 찾아줘"'));
console.log(chalk.gray('예시: "테스트 코드 작성해줘"'));
console.log(chalk.cyan('\n더 자세한 사용법: claude --help\n'));
}
},
'gemini-cli': {
name: 'Gemini CLI',
check: () => checkCommand('gemini'),
requires: ['node'],
install: async () => {
console.log(chalk.cyan(`⏱️ 예상 소요 시간: ${estimatedTimes['gemini-cli']}`));
console.log(chalk.blue('📦 Gemini CLI 설치 중...'));
// 실제 패키지명 확인 필요 - 현재는 예시
console.log(chalk.yellow('\n📚 Gemini CLI 설치 방법:'));
console.log(chalk.cyan('1. Google AI Studio 방문:'));
console.log(' https://makersuite.google.com/app/apikey');
console.log(chalk.cyan('\n2. API 키 발급 (무료)'));
console.log(chalk.cyan('\n3. 환경 변수 설정:'));
console.log(chalk.gray(' export GEMINI_API_KEY="your-api-key"'));
console.log(chalk.cyan('\n4. CLI 도구 설치 (예시):'));
console.log(chalk.gray(' npm install -g @google/generative-ai'));
console.log(chalk.yellow('\n⚠️ 공식 Gemini CLI는 아직 준비 중입니다.'));
console.log(chalk.blue('대신 API를 직접 사용하거나 서드파티 도구를 활용하세요.'));
},
postInstall: async () => {
console.log(chalk.green('\n✅ Gemini 설정 완료!'));
console.log(chalk.yellow('🔧 테스트 방법:'));
console.log(chalk.cyan(' curl -X POST https://generativelanguage.googleapis.com/v1/models/gemini-pro:generateContent?key=$GEMINI_API_KEY \\'));
console.log(chalk.cyan(' -H "Content-Type: application/json" \\'));
console.log(chalk.cyan(' -d \'{"contents":[{"parts":[{"text":"Hello!"}]}]}\''));
}
},
'copilot': {
name: 'GitHub Copilot',
check: () => checkCommand('test -d ~/.config/github-copilot'),
virtual: true, // VSCode 확장이므로 virtual
install: async () => {
console.log(chalk.blue('🧠 GitHub Copilot 설정 안내\n'));
console.log(chalk.yellow('1️⃣ VSCode 설치 확인'));
const vscodeInstalled = await checkCommand('code');
if (!vscodeInstalled) {
console.log(chalk.red(' ❌ VSCode가 설치되어 있지 않습니다.'));
const { installVSCode } = await inquirer.prompt([{
type: 'confirm',
name: 'installVSCode',
message: 'VSCode를 먼저 설치하시겠습니까?',
default: true
}]);
if (installVSCode) {
await installTool('vscode');
} else {
console.log(chalk.gray(' VSCode 설치 후 다시 시도해주세요.'));
return;
}
} else {
console.log(chalk.green(' ✅ VSCode 설치됨'));
}
console.log(chalk.yellow('\n2️⃣ Copilot 확장 설치'));
console.log(chalk.cyan(' 다음 명령어를 실행하세요:'));
console.log(chalk.gray(' code --install-extension GitHub.copilot'));
console.log(chalk.yellow('\n3️⃣ GitHub 계정 연결'));
console.log(' • VSCode에서 Copilot 아이콘 클릭');
console.log(' • "Sign in to GitHub" 선택');
console.log(' • 브라우저에서 인증 완료');
console.log(chalk.yellow('\n4️⃣ Copilot 구독 확인'));
console.log(' • 무료 체험: 30일');
console.log(' • 학생/오픈소스 기여자: 무료');
console.log(' • 일반: $10/월');
console.log(chalk.cyan(' https://github.com/features/copilot'));
},
postInstall: async () => {
console.log(chalk.green('\n✅ GitHub Copilot 설정 완료!'));
console.log(chalk.yellow('🎯 사용 방법:'));
console.log(' • 코드 작성 시 자동으로 제안이 나타남');
console.log(' • Tab: 제안 수락');
console.log(' • Esc: 제안 거절');
console.log(' • Ctrl+Enter: 더 많은 제안 보기');
}
},
'terminal-beautify': {
name: '터미널 전체 꾸미기',
virtual: true, // 실제 명령어가 아닌 묶음
install: async () => {
const steps = ['iterm2', 'oh-my-zsh', 'p10k', 'plugins'];
for (const step of steps) {
if (step === 'plugins') {
await installPlugins();
} else {
await installTool(step);
}
}
}
},
'git': {
name: 'Git',
check: () => checkCommand('git'),
install: async () => {
console.log(chalk.cyan(`⏱️ 예상 소요 시간: ${estimatedTimes.git}`));
const platform = process.platform;
if (platform === 'darwin') {
// macOS
const hasHomebrew = await checkCommand('brew');
if (hasHomebrew) {
console.log(chalk.blue('Homebrew로 Git을 설치합니다...'));
await runCommand('brew install git');
} else {
console.log(chalk.yellow('Git 설치를 위해 Xcode Command Line Tools가 필요합니다.'));
console.log(chalk.blue('다음 중 하나를 선택하세요:'));
console.log(chalk.gray('1. Homebrew 먼저 설치: muggleout install homebrew'));
console.log(chalk.gray('2. Xcode 도구 설치: xcode-select --install'));
}
} else {
// Linux
console.log(chalk.blue('Git을 설치합니다...'));
console.log(chalk.yellow('💡 비밀번호를 입력하세요 (Linux 로그인 비밀번호):'));
console.log(chalk.gray('(비밀번호는 화면에 표시되지 않습니다)\n'));
try {
await runCommand('sudo apt-get update && sudo apt-get install -y git', {
interactive: true
});
} catch (error) {
if (error.message.includes('incorrect password')) {
console.log(chalk.red('\n❌ 비밀번호가 틀렸습니다!'));
console.log(chalk.yellow('다시 시도: muggleout install git'));
} else {
throw error;
}
}
}
},
postInstall: async () => {
console.log(chalk.green('\n✅ Git 설치 완료!'));
console.log(chalk.yellow('🔧 첫 Git 설정 (한번만 하면 됩니다):'));
console.log(chalk.cyan(' git config --global user.name "내이름"'));
console.log(chalk.cyan(' git config --global user.email "내이메일@example.com"'));
console.log(chalk.gray('\n확인 명령어:'));
console.log(chalk.cyan(' git --version ') + chalk.gray('# git version 2.x.x가 나오면 성공!'));
}
},
'vscode': {
name: 'Visual Studio Code',
check: () => checkCommand('code'),
requires: ['homebrew'],
install: async () => {
console.log(chalk.cyan(`⏱️ 예상 소요 시간: 1-2분`));
console.log(chalk.blue('📝 Visual Studio Code 설치 중...'));
await runCommand('brew install --cask visual-studio-code');
},
postInstall: async () => {
console.log(chalk.green('\n✅ VSCode 설치 완료!'));
console.log(chalk.yellow('🚀 빠른 시작:'));
console.log(chalk.cyan(' code . ') + chalk.gray('# 현재 폴더를 VSCode로 열기'));
console.log(chalk.cyan(' code file.txt ') + chalk.gray('# 특정 파일 열기'));
console.log(chalk.gray('\n💡 추천 확장 프로그램:'));
console.log(' • Korean Language Pack - 한국어 지원');
console.log(' • Prettier - 코드 자동 정리');
console.log(' • GitLens - Git 기능 강화');
}
},
'docker': {
name: 'Docker Desktop',
check: () => checkCommand('docker'),
requires: ['homebrew'],
install: async () => {
console.log(chalk.cyan(`⏱️ 예상 소요 시간: 3-5분`));
console.log(chalk.blue('🐳 Docker Desktop 설치 중...'));
await runCommand('brew install --cask docker');
},
postInstall: async () => {
console.log(chalk.green('\n✅ Docker Desktop 설치 완료!'));
console.log(chalk.yellow('🚀 다음 단계:'));
console.log(' 1. Applications 폴더에서 Docker 실행');
console.log(' 2. 메뉴바에 고래 아이콘 확인');
console.log(' 3. Docker Desktop 초기 설정 완료');
console.log(chalk.gray('\n테스트 명령어:'));
console.log(chalk.cyan(' docker --version'));
console.log(chalk.cyan(' docker run hello-world'));
}
}
};
// 도구 설치
export async function installTool(toolName) {
const config = installConfigs[toolName];
if (!config) {
console.log(chalk.red(`❌ ${toolName}은(는) 지원하지 않는 도구입니다.`));
return;
}
// 이미 설치되어 있는지 확인
if (!config.virtual && config.check && await config.check()) {
console.log(chalk.green(`✅ ${config.name}은(는) 이미 설치되어 있습니다!`));
const { continueAnyway } = await inquirer.prompt([
{
type: 'confirm',
name: 'continueAnyway',
message: '그래도 계속하시겠습니까?',
default: false
}
]);
if (!continueAnyway) return;
}
// 의존성 확인
if (config.requires) {
for (const dep of config.requires) {
const depConfig = installConfigs[dep];
if (depConfig && depConfig.check && !(await depConfig.check())) {
console.log(chalk.yellow(`⚠️ ${config.name} 설치를 위해 ${depConfig.name}이(가) 필요합니다.`));
const { installDep } = await inquirer.prompt([
{
type: 'confirm',
name: 'installDep',
message: `${depConfig.name}을(를) 먼저 설치하시겠습니까?`,
default: true
}
]);
if (installDep) {
await installTool(dep);
} else {
console.log(chalk.red('❌ 의존성이 없어 설치를 중단합니다.'));
return;
}
}
}
}
// 설치 시작
if (estimatedTimes[toolName] && !config.virtual) {
console.log(chalk.cyan(`⏱️ 예상 소요 시간: ${estimatedTimes[toolName]}`));
}
// Homebrew 같은 interactive 설치는 스피너 사용하지 않음
const isInteractiveInstall = toolName === 'homebrew' || toolName === 'git';
let spinner = null;
if (!isInteractiveInstall) {
// 도구별 맞춤 메시지
const installMessages = {
'node': '☕ Node.js 설치 중... (잠시만 기다려주세요)',
'nodejs': '☕ Node.js 설치 중... (잠시만 기다려주세요)',
'git': '🐙 Git 설치 중... (버전 관리의 첫걸음!)',
'vscode': '📝 VS Code 설치 중... (최고의 에디터를 준비 중)',
'docker': '🐳 Docker 설치 중... (조금 오래 걸릴 수 있어요)',
'claude-code': '🤖 Claude Code 설치 중... (AI 코딩 도우미)',
'github-copilot': '🤝 GitHub Copilot 설치 중... (페어 프로그래밍 준비)',
'iterm2': '🖥️ iTerm2 설치 중... (더 멋진 터미널!)',
'oh-my-zsh': '🎨 Oh My Zsh 설치 중... (터미널이 예뻐집니다)',
'powerlevel10k': '✨ Powerlevel10k 설치 중... (최고의 테마)',
'default': `${config.name} 설치 중...`
};
const message = installMessages[toolName] || installMessages.default;
spinner = await createSpinner(message);
spinner.start();
// 긴 설치 시간 예상 도구들에 대한 추가 메시지
if (['docker', 'android-studio', 'xcode'].includes(toolName)) {
const { getRandomLoadingMessage } = await import('../ui/ascii-art.js');
setTimeout(() => {
if (spinner && spinner.isSpinning) {
spinner.text = `${message} ${chalk.gray(getRandomLoadingMessage())}`;
}
}, 10000); // 10초 후 메시지 추가
}
} else {
console.log(chalk.blue(`🚀 ${config.name} 설치를 시작합니다...`));
}
try {
await config.install();
if (spinner) {
spinner.succeed(`${config.name} 설치 완료!`);
} else {
console.log(chalk.green(`✅ ${config.name} 설치 완료!`));
}
// 축하 메시지 표시
const { getRandomCompletionMessage } = await import('../ui/ascii-art.js');
console.log(chalk.yellow(`\n${getRandomCompletionMessage()}\n`));
// 설치 기록 저장
if (!config.virtual) {
await saveInstallRecord(toolName);
}
// 설치 후 작업
if (config.postInstall) {
await config.postInstall();
}
} catch (error) {
if (spinner) {
spinner.fail(`${config.name} 설치 실패`);
} else {
console.log(chalk.red(`❌ ${config.name} 설치 실패`));
}
console.error(chalk.red('에러:', error.message));
// 에러 리포팅
const { captureError } = await import('../utils/error-reporter.js');
const errorResult = await captureError(error, {
tool: toolName,
command: `install ${toolName}`,
stage: 'installation'
});
// 자동 수정 가능한 경우
if (errorResult?.autoFix) {
console.log(chalk.yellow('\n🔧 자동 수정을 시도합니다...'));
// autoFix 명령 실행은 메인 프로세스에서 처리
return { needsAutoFix: true, command: errorResult.command };
}
// 에러 해결 제안
await suggestFix(toolName, error);
}
}
// 플러그인 설치
async function installPlugins() {
console.log(chalk.blue('🔌 유용한 플러그인을 설치합니다...'));
const plugins = [
{
name: 'zsh-autosuggestions',
url: 'https://github.com/zsh-users/zsh-autosuggestions',
desc: '명령어 자동완성'
},
{
name: 'zsh-syntax-highlighting',
url: 'https://github.com/zsh-users/zsh-syntax-highlighting.git',
desc: '문법 하이라이팅'
}
];
for (const plugin of plugins) {
const spinner = ora(`${plugin.desc} 플러그인 설치 중...`).start();
try {
await runCommand(`git clone ${plugin.url} \${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/${plugin.name}`);
spinner.succeed(`${plugin.desc} 설치 완료!`);
} catch (error) {
spinner.warn(`${plugin.desc} 이미 설치됨`);
}
}
// .zshrc 업데이트
console.log(chalk.blue('📝 플러그인 활성화 중...'));
await runCommand(`sed -i.bak 's/plugins=(git)/plugins=(git zsh-autosuggestions zsh-syntax-highlighting colored-man-pages)/' ~/.zshrc`);
console.log(chalk.green('✅ 플러그인 설정 완료!'));
}
// 에러 해결 제안
async function suggestFix(toolName, error) {
console.log(chalk.yellow('\n💡 해결 방법:'));
const errorMsg = error.message.toLowerCase();
if (errorMsg.includes('permission denied') || errorMsg.includes('eacces')) {
console.log(chalk.cyan('\n🔐 권한 문제가 발생했습니다:'));
console.log(' 1. 비밀번호를 입력해야 할 수 있습니다');
console.log(' 2. Mac 사용자 비밀번호를 입력하세요');
console.log(chalk.gray(' 팁: 비밀번호 입력 시 화면에 표시되지 않습니다'));
} else if (errorMsg.includes('command not found') || errorMsg.includes('not found')) {
console.log(chalk.cyan('\n🔍 필요한 도구가 없습니다:'));
if (errorMsg.includes('brew')) {
console.log(' Homebrew가 설치되지 않았거나 PATH에 없습니다');
// PATH 자동 해결 시도
const { diagnoseAndFixPath } = await import('../utils/path-fixer.js');
console.log(chalk.yellow('\n🔧 PATH 문제일 수 있습니다. 확인해보겠습니다...'));
await diagnoseAndFixPath();
console.log(chalk.green('\n 다시 시도: muggleout install ' + toolName));
} else if (errorMsg.includes('git')) {
console.log(' Git이 설치되지 않았습니다');
console.log(chalk.green(' 해결: muggleout install git'));
} else if (errorMsg.includes('node') || errorMsg.includes('npm')) {
console.log(' Node.js가 설치되지 않았습니다');
console.log(chalk.green(' 해결: muggleout install node'));
}
} else if (errorMsg.includes('network') || errorMsg.includes('timeout') || errorMsg.includes('enotfound')) {
console.log(chalk.cyan('\n🌐 네트워크 문제입니다:'));
console.log(' 1. 인터넷 연결을 확인하세요');
console.log(' 2. Wi-Fi가 켜져 있는지 확인하세요');
console.log(' 3. 회사 네트워크의 경우 보안 설정을 확인하세요');
} else if (errorMsg.includes('already exists') || errorMsg.includes('eexist')) {
console.log(chalk.cyan('\n📁 이미 설치되어 있을 수 있습니다:'));
console.log(' 정상적인 상황일 수 있으니 걱정하지 마세요!');
}
console.log(chalk.gray(`\n그래도 문제가 계속되면:`))
console.log(chalk.green(` muggleout doctor `) + chalk.gray('# 시스템 진단'));
console.log(chalk.green(` muggleout status `) + chalk.gray('# 설치 상태 확인\n'));
}