aiwf
Version:
AI Workflow Framework for Claude Code with multi-language support (Korean/English)
160 lines (136 loc) • 4.91 kB
JavaScript
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
import prompts from 'prompts';
import chalk from 'chalk';
import ora from 'ora';
import { ResourceLoader } from '../lib/resource-loader.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// ResourceLoader를 사용하여 템플릿 관리
const resourceLoader = new ResourceLoader();
async function createProject() {
console.log(chalk.cyan('\n🚀 AIWF 프로젝트 생성기\n'));
// ResourceLoader를 사용하여 템플릿 찾기
const templates = await resourceLoader.listTemplates();
const templateConfigs = [];
for (const template of templates) {
try {
const config = await resourceLoader.loadTemplateConfig(template);
templateConfigs.push({
value: template,
title: config.displayName,
description: config.description
});
} catch (error) {
console.error(`템플릿 ${template} 로드 실패:`, error.message);
}
}
const questions = [
{
type: 'select',
name: 'template',
message: '템플릿을 선택하세요',
choices: templateConfigs,
},
{
type: 'text',
name: 'projectName',
message: '프로젝트 이름',
validate: value => {
if (!value) return '프로젝트 이름을 입력하세요';
if (!/^[a-z0-9-]+$/.test(value)) {
return '소문자, 숫자, 하이픈만 사용 가능합니다';
}
return true;
}
},
{
type: prev => prev === 'npm-library' ? 'text' : null,
name: 'description',
message: '프로젝트 설명',
initial: 'A new AIWF project'
},
{
type: prev => prev === 'npm-library' ? 'text' : null,
name: 'author',
message: '작성자',
initial: ''
},
{
type: 'text',
name: 'directory',
message: '프로젝트 디렉토리',
initial: prev => prev.projectName,
validate: value => {
if (!value) return '디렉토리를 입력하세요';
if (fs.existsSync(value)) {
return '이미 존재하는 디렉토리입니다';
}
return true;
}
}
];
const answers = await prompts(questions);
if (!answers.template || !answers.projectName) {
console.log(chalk.red('\n❌ 프로젝트 생성이 취소되었습니다.'));
return;
}
const spinner = ora('프로젝트 생성 중...').start();
try {
// ResourceLoader를 사용하여 템플릿 경로 가져오기
const templatePath = await resourceLoader.getTemplatePath(answers.template);
const targetPath = path.resolve(answers.directory);
// 템플릿 복사
await fs.copy(templatePath, targetPath);
// 플레이스홀더 교체
const replacements = {
'{{projectName}}': answers.projectName,
'{{description}}': answers.description || '',
'{{author}}': answers.author || '',
'{{year}}': new Date().getFullYear().toString(),
'{{createdAt}}': new Date().toISOString(),
'{{username}}': answers.author?.toLowerCase().replace(/\s+/g, '') || 'username'
};
// 모든 파일에서 플레이스홀더 교체
await replacePlaceholders(targetPath, replacements);
spinner.succeed('프로젝트가 성공적으로 생성되었습니다!');
console.log(chalk.green(`\n✅ ${answers.projectName} 프로젝트가 생성되었습니다!\n`));
console.log('다음 명령어로 시작하세요:\n');
console.log(chalk.cyan(` cd ${answers.directory}`));
console.log(chalk.cyan(' npm install'));
console.log(chalk.cyan(' npm run dev\n'));
console.log('AIWF 명령어:');
console.log(chalk.gray(' npm run aiwf status # 프로젝트 상태 확인'));
console.log(chalk.gray(' npm run aiwf feature # Feature Ledger 관리\n'));
} catch (error) {
spinner.fail('프로젝트 생성 중 오류가 발생했습니다.');
console.error(chalk.red(error.message));
process.exit(1);
}
}
async function replacePlaceholders(dir, replacements) {
const files = await fs.readdir(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = await fs.stat(filePath);
if (stat.isDirectory()) {
await replacePlaceholders(filePath, replacements);
} else if (stat.isFile()) {
let content = await fs.readFile(filePath, 'utf8');
for (const [placeholder, value] of Object.entries(replacements)) {
content = content.replace(new RegExp(placeholder, 'g'), value);
}
await fs.writeFile(filePath, content, 'utf8');
}
}
}
// CLI 실행
if (import.meta.url === `file://${process.argv[1]}`) {
createProject().catch(error => {
console.error(chalk.red('오류:', error.message));
process.exit(1);
});
}
export { createProject };