@sprouted/create-vibes
Version:
Create a new Vibes monorepo
151 lines (132 loc) • 4.78 kB
JavaScript
import * as path from 'path';
import * as fs from 'fs/promises';
import { fileURLToPath } from 'url';
import { dirname } from 'path';
import chalk from 'chalk';
import inquirer from 'inquirer';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export class CreateSpecCommand {
async execute(options) {
const projectRoot = options.projectRoot || process.cwd();
const type = options.type || 'feature';
const status = options.status || 'draft';
// Convert spec name to kebab-case
const specName = options.name.toLowerCase().replace(/\s+/g, '-');
const specType = type === 'feature' ? 'features' : type;
// Create specs directory structure
const specsDir = path.join(projectRoot, 'specs', specType);
await fs.mkdir(specsDir, { recursive: true });
// Define spec file path
const specFile = path.join(specsDir, `${specName}.md`);
// Check if spec already exists
if (!options.force) {
try {
await fs.access(specFile);
console.log(chalk.yellow(`Spec '${specName}' already exists at ${specFile}`));
console.log(chalk.yellow('Use --force to overwrite'));
return;
}
catch {
// File doesn't exist, continue
}
}
else if (options.force) {
console.log(chalk.yellow(`Overwriting existing spec: ${specFile}`));
}
// Create spec content
const specContent = this.generateSpecContent(specName, type, status);
// Write spec file
await fs.writeFile(specFile, specContent);
console.log(chalk.green(`✅ Created spec: ${specFile}`));
console.log('\nNext steps:');
console.log(chalk.cyan('1. Fill out the specification'));
console.log(chalk.cyan('2. Get it reviewed and approved'));
console.log(chalk.cyan(`3. Generate tests: vibe test ${specName}`));
console.log(chalk.cyan(`4. Implement: vibe create ${specName}`));
}
generateSpecContent(name, type, status) {
const title = name.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
const template = `# ${type === 'feature' ? 'Feature' : type === 'api' ? 'API' : 'Architecture'}: ${name}
**Status**: ${status}
**Created**: ${new Date().toISOString().split('T')[0]}
**Type**: ${type}
## Overview
<!-- Provide a brief description of what this ${type} does -->
## Motivation
<!-- Why is this ${type} needed? What problem does it solve? -->
## Requirements
### Functional Requirements
<!-- List the functional requirements -->
- [ ] Requirement 1
- [ ] Requirement 2
### Non-Functional Requirements
<!-- List non-functional requirements (performance, security, etc.) -->
- [ ] Performance: ...
- [ ] Security: ...
## Technical Design
### Architecture
<!-- Describe the high-level architecture -->
### API Design
<!-- Define the API interface (if applicable) -->
### Data Model
<!-- Define data structures and models -->
## Implementation Plan
### Phase 1: Foundation
- [ ] Task 1
- [ ] Task 2
### Phase 2: Core Features
- [ ] Task 3
- [ ] Task 4
## Testing Strategy
### Unit Tests
- [ ] Test case 1
- [ ] Test case 2
### Integration Tests
- [ ] Test scenario 1
- [ ] Test scenario 2
## Success Criteria
<!-- How will we know this ${type} is successful? -->
- [ ] Criteria 1
- [ ] Criteria 2
## Dependencies
<!-- List any dependencies on other features or systems -->
## Notes
<!-- Additional notes or considerations -->
`;
return template;
}
async interactivePrompt() {
const answers = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'What should we call this spec?',
validate: (input) => {
if (!input.trim())
return 'Spec name is required';
if (!/^[a-zA-Z0-9-\s]+$/.test(input)) {
return 'Spec name can only contain letters, numbers, hyphens, and spaces';
}
return true;
}
},
{
type: 'list',
name: 'type',
message: 'What type of spec?',
choices: ['feature', 'api', 'architecture'],
default: 'feature'
},
{
type: 'list',
name: 'status',
message: 'Initial status?',
choices: ['draft', 'review', 'approved'],
default: 'draft'
}
]);
return answers;
}
}
//# sourceMappingURL=create-spec.js.map