UNPKG

create-saltic

Version:

Spec-Driven Development (SDD) framework for constitutional software development - inject into any project

296 lines (254 loc) 10.1 kB
#!/usr/bin/env node const { program } = require('commander'); const chalk = require('chalk').default; const path = require('path'); const fs = require('fs-extra'); const { execSync } = require('child_process'); program .name('create-saltic') .description('Inject Saltic SDD Framework into any project') .version('0.0.1'); // Get the package root directory const packageRoot = path.join(__dirname, '..', '..'); // Helper function to copy framework files to target project function injectFramework(targetDir = process.cwd()) { console.log(chalk.blue('🚀 Injecting Saltic SDD Framework into project...')); const claudeDir = path.join(targetDir, '.claude'); const commandsDir = path.join(claudeDir, 'commands'); // Root directories (in project root) const memoryDir = path.join(targetDir, 'memory'); const templatesDir = path.join(targetDir, 'templates'); const scriptsDir = path.join(targetDir, 'scripts'); // Create directory structure const dirs = [claudeDir, commandsDir, memoryDir, templatesDir, scriptsDir]; dirs.forEach(dir => { fs.ensureDirSync(dir); console.log(chalk.green('✓'), `Created ${path.relative(targetDir, dir)}/`); }); // Copy framework files const copyTasks = [ { source: path.join(packageRoot, 'src/memory'), dest: memoryDir, name: 'memory files' }, { source: path.join(packageRoot, 'src/templates'), dest: templatesDir, name: 'template files' }, { source: path.join(packageRoot, 'src/commands'), dest: commandsDir, name: 'command files' }, { source: path.join(packageRoot, 'src/scripts'), dest: scriptsDir, name: 'script files' } ]; copyTasks.forEach(task => { try { if (fs.existsSync(task.source)) { if (fs.statSync(task.source).isDirectory()) { fs.copySync(task.source, task.dest, { overwrite: false }); } else { fs.copySync(task.source, path.join(task.dest, path.basename(task.source)), { overwrite: false }); } console.log(chalk.green('✓'), `Copied ${task.name}`); } } catch (error) { console.warn(chalk.yellow('⚠'), `Failed to copy ${task.name}:`, error.message); } }); // Create specs directory in project root const specsDir = path.join(targetDir, 'specs'); fs.ensureDirSync(specsDir); console.log(chalk.green('✓'), `Created specs/ directory`); // Create .gitignore for .claude if it doesn't exist const gitignorePath = path.join(targetDir, '.gitignore'); const gitignoreContent = '# Saltic SDD Framework\n'; if (fs.existsSync(gitignorePath)) { const existingContent = fs.readFileSync(gitignorePath, 'utf8'); if (!existingContent.includes('.claude/')) { fs.appendFileSync(gitignorePath, '\n' + gitignoreContent); console.log(chalk.green('✓'), 'Updated .gitignore'); } } else { fs.writeFileSync(gitignorePath, gitignoreContent); console.log(chalk.green('✓'), 'Created .gitignore'); } console.log(chalk.green('🎉 Saltic SDD Framework successfully injected!')); } // Main injection command program .command('inject') .description('Inject Saltic SDD Framework into current project') .option('-f, --force', 'Force overwrite existing files') .option('-d, --dir <path>', 'Target directory (default: current directory)') .action((options) => { const targetDir = options.dir ? path.resolve(options.dir) : process.cwd(); // Check if already injected const claudeDir = path.join(targetDir, '.claude'); if (fs.existsSync(claudeDir) && !options.force) { console.log(chalk.yellow('⚠ Saltic SDD Framework is already injected in this project.')); console.log(chalk.white('Use --force to overwrite existing files.')); process.exit(1); } injectFramework(targetDir); }); // Quick init command (default behavior) program .command('init') .description('Initialize Saltic SDD Framework in current project (alias for inject)') .option('-f, --force', 'Force overwrite existing files') .option('-d, --dir <path>', 'Target directory (default: current directory)') .action((options) => { const targetDir = options.dir ? path.resolve(options.dir) : process.cwd(); injectFramework(targetDir); }); // Show framework status program .command('status') .description('Show Saltic SDD Framework status in current project') .action(() => { const claudeDir = path.join(process.cwd(), '.claude'); const memoryDir = path.join(process.cwd(), 'memory'); const templatesDir = path.join(process.cwd(), 'templates'); const scriptsDir = path.join(process.cwd(), 'scripts'); const frameworkComponents = [ { path: claudeDir, name: '.claude/ directory' }, { path: memoryDir, name: 'memory/ directory' }, { path: templatesDir, name: 'templates/ directory' }, { path: scriptsDir, name: 'scripts/ directory' } ]; const presentComponents = frameworkComponents.filter(comp => fs.existsSync(comp.path)); if (presentComponents.length === 0) { console.log(chalk.red('❌ Saltic SDD Framework is not injected in this project.')); console.log(chalk.white('Run: create-saltic inject')); process.exit(1); } console.log(chalk.blue('📊 Saltic SDD Framework Status:')); console.log(chalk.green('✓'), `Framework partially injected (${presentComponents.length}/${frameworkComponents.length} components)`); presentComponents.forEach(comp => { console.log(chalk.green(' ✓'), comp.name); }); const missingComponents = frameworkComponents.filter(comp => !fs.existsSync(comp.path)); missingComponents.forEach(comp => { console.log(chalk.red(' ✗'), comp.name); }); // Check git branch try { const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', cwd: process.cwd() }).trim(); console.log(chalk.white(' Current Branch:'), chalk.green(currentBranch)); if (/^\d{3}-.+/.test(currentBranch)) { console.log(chalk.green(' ✓ Branch follows naming convention')); } else { console.log(chalk.yellow(' ⚠ Branch does not follow naming convention (###-feature-name)')); } } catch (error) { console.log(chalk.yellow(' ⚠ Not a git repository or git not available')); } // Check for feature directory try { const currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8', cwd: process.cwd() }).trim(); const featureDir = path.join(process.cwd(), 'specs', currentBranch); if (fs.existsSync(featureDir)) { console.log(chalk.green(' ✓ Feature directory exists')); } else { console.log(chalk.yellow(' ⚠ Feature directory does not exist')); } } catch (error) { console.log(chalk.yellow(' ⚠ Could not check feature directory')); } }); // Remove framework from project program .command('remove') .description('Remove Saltic SDD Framework from current project') .option('-f, --force', 'Force removal without confirmation') .action((options) => { const claudeDir = path.join(process.cwd(), '.claude'); if (!fs.existsSync(claudeDir)) { console.log(chalk.yellow('⚠ Saltic SDD Framework is not injected in this project.')); process.exit(0); } if (!options.force) { const inquirer = require('inquirer'); inquirer.prompt([ { type: 'confirm', name: 'confirm', message: 'Are you sure you want to remove Saltic SDD Framework from this project?', default: false } ]).then((answers) => { if (answers.confirm) { removeFramework(); } else { console.log(chalk.blue('Operation cancelled.')); } }); } else { removeFramework(); } }); function removeFramework() { console.log(chalk.blue('🗑️ Removing Saltic SDD Framework...')); const claudeDir = path.join(process.cwd(), '.claude'); const memoryDir = path.join(process.cwd(), 'memory'); const templatesDir = path.join(process.cwd(), 'templates'); const scriptsDir = path.join(process.cwd(), 'scripts'); const specsDir = path.join(process.cwd(), 'specs'); // Remove framework directories and files const removeTasks = [ { path: claudeDir, name: '.claude/ directory' }, { path: memoryDir, name: 'memory/ directory' }, { path: templatesDir, name: 'templates/ directory' }, { path: scriptsDir, name: 'scripts/ directory' } ]; removeTasks.forEach(task => { try { if (fs.existsSync(task.path)) { fs.removeSync(task.path); console.log(chalk.green('✓'), `Removed ${task.name}`); } } catch (error) { console.warn(chalk.yellow('⚠'), `Failed to remove ${task.name}:`, error.message); } }); // Remove specs directory only if it's empty try { if (fs.existsSync(specsDir) && fs.readdirSync(specsDir).length === 0) { fs.removeSync(specsDir); console.log(chalk.green('✓'), 'Removed empty specs/ directory'); } } catch (error) { console.warn(chalk.yellow('⚠'), 'Failed to remove specs/:', error.message); } // Remove from .gitignore const gitignorePath = path.join(process.cwd(), '.gitignore'); if (fs.existsSync(gitignorePath)) { try { let gitignoreContent = fs.readFileSync(gitignorePath, 'utf8'); gitignoreContent = gitignoreContent.replace(/\n# Saltic SDD Framework\n/g, ''); fs.writeFileSync(gitignorePath, gitignoreContent); console.log(chalk.green('✓'), 'Removed from .gitignore'); } catch (error) { console.warn(chalk.yellow('⚠'), 'Failed to update .gitignore:', error.message); } } console.log(chalk.green('🎉 Saltic SDD Framework successfully removed!')); } // Default command is inject if (process.argv.length === 2) { program.commands.find(cmd => cmd.name() === 'inject').action(); } program.parse();