qraft
Version:
A powerful CLI tool to qraft structured project setups from GitHub template repositories
1,105 lines (1,104 loc) • 48.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitignoreCommand = void 0;
exports.createGitignoreCommand = createGitignoreCommand;
exports.validateCommandOptions = validateCommandOptions;
exports.handleCommandFailure = handleCommandFailure;
const commander_1 = require("commander");
const path = __importStar(require("path"));
const config_1 = require("../utils/config");
const gitignoreManager_1 = require("../utils/gitignoreManager");
const qraftPatterns_1 = require("../utils/qraftPatterns");
/**
* GitignoreCommand handles the gitignore CLI command
*/
class GitignoreCommand {
constructor(gitignoreManager, qraftPatterns, configManager) {
this.gitignoreManager = gitignoreManager || new gitignoreManager_1.GitignoreManager();
this.configManager = configManager || new config_1.ConfigManager();
this.qraftPatterns = qraftPatterns || new qraftPatterns_1.QraftPatterns(this.configManager);
}
/**
* Execute the gitignore command
* @param options Command options
* @returns Promise<GitignoreCommandResult> Command execution result
*/
async execute(options = {}) {
// Validate directory option first
if (options.directory !== undefined && options.directory.trim() === '') {
return {
success: false,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: 'Target directory cannot be empty',
error: 'Target directory cannot be empty'
};
}
const targetDirectory = options.directory || process.cwd();
try {
// Display verbose startup information
this.displayVerboseStartup(targetDirectory, options);
// Validate prerequisites
const prerequisiteCheck = await this.validatePrerequisites(targetDirectory, options);
if (!prerequisiteCheck.success) {
return {
success: false,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: this.getUserFriendlyErrorMessage(new Error(prerequisiteCheck.error), 'during initial validation'),
error: prerequisiteCheck.error
};
}
// Validate target directory
await this.validateAndDisplayDirectory(targetDirectory, options);
// Get qraft-specific patterns with error handling
let patterns = [];
let patternStrings = [];
try {
patterns = await this.qraftPatterns.getContextAwarePatterns(targetDirectory);
patternStrings = patterns.map(p => p.pattern);
// Display pattern analysis
this.displayPatternAnalysis(patterns, options);
}
catch (patternError) {
if (options.verbose) {
console.warn('⚠️ Warning: Error getting context-aware patterns');
console.warn(` Error: ${patternError instanceof Error ? patternError.message : 'Unknown error'}`);
console.warn(' Falling back to static patterns only');
}
// Fallback to static patterns only
try {
patterns = this.qraftPatterns.getStaticPatterns();
patternStrings = patterns.map(p => p.pattern);
if (options.verbose) {
console.log('📋 Using static patterns only (fallback mode):');
patterns.forEach(pattern => {
console.log(` • ${pattern.pattern} (${pattern.category}) - ${pattern.description}`);
});
console.log('');
}
}
catch (fallbackError) {
return {
success: false,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: this.getUserFriendlyErrorMessage(fallbackError, 'while getting patterns'),
error: fallbackError instanceof Error ? fallbackError.message : 'Unknown pattern error'
};
}
}
// Validate patterns
const { valid: validPatterns, invalid: invalidPatterns } = this.qraftPatterns.validateAndNormalizePatterns(patternStrings);
// Display validation results
this.displayValidationResults(validPatterns, invalidPatterns, options);
if (validPatterns.length === 0) {
return {
success: true,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: 'No valid qraft patterns to add to .gitignore'
};
}
// Show dry run preview
if (options.dryRun) {
return this.handleDryRun(targetDirectory, validPatterns, options);
}
// Handle file creation and permissions
const fileExists = await this.gitignoreManager.exists(targetDirectory);
const permissionResult = await this.handlePermissions(targetDirectory, fileExists, options);
if (!permissionResult.success) {
return permissionResult;
}
// Execute the main gitignore operation
const result = await this.executeGitignoreOperation(targetDirectory, validPatterns, options);
// Display operation results
this.displayOperationResults(result, targetDirectory, options);
// Format success message
const message = this.formatSuccessMessage(result, targetDirectory, options);
return {
success: result.success,
created: result.created,
modified: result.modified,
patternsAdded: result.patternsAdded,
patternsSkipped: result.patternsSkipped,
message,
error: result.error
};
}
catch (error) {
return this.handleExecutionError(error, targetDirectory, options);
}
}
/**
* Handle execution errors with detailed analysis
* @param error The error that occurred
* @param targetDirectory Target directory
* @param options Command options
* @returns GitignoreCommandResult Error result
*/
handleExecutionError(error, targetDirectory, options) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
if (options.verbose) {
console.error('❌ Error executing gitignore command:');
console.error(` • Error type: ${error instanceof Error ? error.constructor.name : 'Unknown'}`);
console.error(` • Error message: ${errorMessage}`);
console.error(` • Target directory: ${targetDirectory}`);
if (error instanceof Error && error.stack) {
console.error(` • Stack trace: ${error.stack.split('\n')[1]?.trim() || 'Not available'}`);
}
// Provide troubleshooting suggestions
this.provideTroubleshootingSuggestions(error, options);
}
// Determine appropriate error message based on error type
let userMessage = 'Failed to update .gitignore file';
const lowerErrorMessage = errorMessage.toLowerCase();
if (lowerErrorMessage.includes('permission') || lowerErrorMessage.includes('eacces') || lowerErrorMessage.includes('eperm')) {
userMessage = 'Permission denied - unable to create or modify .gitignore file';
}
else if (errorMessage.includes('ENOENT')) {
userMessage = 'Directory not found - please check the target path';
}
else if (errorMessage.includes('ENOSPC')) {
userMessage = 'Insufficient disk space to create .gitignore file';
}
else if (errorMessage.includes('config')) {
userMessage = 'Configuration error - unable to load qraft settings';
}
return {
success: false,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: userMessage,
error: errorMessage
};
}
/**
* Validate command prerequisites
* @param targetDirectory Target directory
* @param options Command options
* @returns Promise<{success: boolean, error?: string}> Validation result
*/
async validatePrerequisites(targetDirectory, options) {
try {
// Check if target directory is valid
if (!targetDirectory || targetDirectory.trim() === '') {
return {
success: false,
error: 'Target directory cannot be empty'
};
}
// Check if directory exists (create if needed)
await this.gitignoreManager.validateTargetDirectory(targetDirectory);
// Check qraft configuration
try {
await this.configManager.getConfig();
}
catch (configError) {
if (options.verbose) {
console.warn('⚠️ Warning: Could not load qraft configuration, using defaults');
}
}
return { success: true };
}
catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : 'Unknown validation error'
};
}
}
/**
* Provide user-friendly error messages
* @param error The error that occurred
* @param context Additional context about when the error occurred
* @returns string User-friendly error message
*/
getUserFriendlyErrorMessage(error, context) {
if (!(error instanceof Error)) {
return `An unexpected error occurred ${context}. Please try again.`;
}
const errorMessage = error.message.toLowerCase();
// Permission errors
if (errorMessage.includes('permission') || errorMessage.includes('eacces') || errorMessage.includes('eperm')) {
return `Permission denied ${context}. You don't have the necessary permissions to modify files in this directory.`;
}
// File not found errors
if (errorMessage.includes('enoent') || errorMessage.includes('not found')) {
return `Directory or file not found ${context}. Please check that the path exists and is accessible.`;
}
// Disk space errors
if (errorMessage.includes('enospc') || errorMessage.includes('no space')) {
return `Insufficient disk space ${context}. Please free up some space and try again.`;
}
// Read-only file system
if (errorMessage.includes('erofs') || errorMessage.includes('read-only')) {
return `Cannot write to read-only file system ${context}. The target location is not writable.`;
}
// Configuration errors
if (errorMessage.includes('config')) {
return `Configuration error ${context}. There may be an issue with your qraft settings.`;
}
// Network/registry errors
if (errorMessage.includes('network') || errorMessage.includes('registry')) {
return `Network error ${context}. Please check your internet connection and try again.`;
}
// Generic file system errors
if (errorMessage.includes('eisdir') || errorMessage.includes('enotdir')) {
return `File system error ${context}. There's a conflict between files and directories in the target path.`;
}
// Default fallback
return `An error occurred ${context}: ${error.message}`;
}
/**
* Provide troubleshooting suggestions based on error type
* @param error The error that occurred
* @param options Command options
*/
provideTroubleshootingSuggestions(error, _options) {
console.error('');
console.error('💡 Troubleshooting suggestions:');
if (error instanceof Error) {
if (error.message.includes('Permission')) {
console.error(' • Try running with --force to override permission checks');
console.error(' • Check directory permissions with: ls -la');
console.error(' • Ensure you have write access to the target directory');
}
else if (error.message.includes('ENOENT')) {
console.error(' • Verify the target directory exists');
console.error(' • Use --directory flag to specify a different path');
console.error(' • Check for typos in the directory path');
}
else if (error.message.includes('config')) {
console.error(' • Check qraft configuration with: qraft config list');
console.error(' • Reset configuration with: qraft config reset');
console.error(' • Verify cache directory permissions');
}
else {
console.error(' • Try running with --verbose for more details');
console.error(' • Check available disk space');
console.error(' • Verify file system permissions');
}
}
console.error(' • Use --dry-run to test without making changes');
console.error('');
}
/**
* Handle dry run mode
* @param targetDirectory Target directory
* @param patterns Patterns to add
* @param options Command options
* @returns Promise<GitignoreCommandResult> Dry run result
*/
async handleDryRun(targetDirectory, patterns, _options) {
const gitignorePath = this.gitignoreManager.getGitignorePath(targetDirectory);
const fileExists = await this.gitignoreManager.exists(targetDirectory);
// Display dry run header
this.displayDryRunHeader(targetDirectory, gitignorePath, fileExists);
let newPatterns = patterns;
let existingPatterns = [];
if (fileExists) {
// Analyze existing file for duplicates
const analysisResult = await this.analyzeDryRunChanges(targetDirectory, patterns);
newPatterns = analysisResult.newPatterns;
existingPatterns = analysisResult.existingPatterns;
this.displayExistingFileAnalysis(existingPatterns);
}
else {
this.displayNewFileCreation();
}
// Show what would be added
this.displayDryRunChanges(newPatterns, fileExists);
// Show file preview if there are changes
if (newPatterns.length > 0) {
await this.displayDryRunPreview(targetDirectory, newPatterns, fileExists);
}
// Display summary
this.displayDryRunSummary(newPatterns, existingPatterns, fileExists);
return {
success: true,
created: !fileExists,
modified: fileExists,
patternsAdded: newPatterns,
patternsSkipped: existingPatterns,
message: this.formatDryRunMessage(newPatterns, existingPatterns, fileExists)
};
}
/**
* Display dry run header information
* @param targetDirectory Target directory
* @param gitignorePath Path to .gitignore file
* @param fileExists Whether file exists
*/
displayDryRunHeader(targetDirectory, gitignorePath, fileExists) {
console.log('');
console.log('🔍 DRY RUN MODE - No changes will be made');
console.log('═'.repeat(50));
console.log(`📁 Target directory: ${targetDirectory}`);
console.log(`📄 .gitignore file: ${gitignorePath}`);
console.log(`📊 File status: ${fileExists ? '✅ Exists' : '❌ Does not exist'}`);
console.log('');
}
/**
* Analyze changes for dry run mode
* @param targetDirectory Target directory
* @param patterns Patterns to analyze
* @returns Analysis result
*/
async analyzeDryRunChanges(targetDirectory, patterns) {
const existingContent = await this.gitignoreManager.read(targetDirectory);
return this.gitignoreManager.filterDuplicatePatterns(existingContent, patterns);
}
/**
* Display analysis of existing file
* @param existingPatterns Patterns that already exist
*/
displayExistingFileAnalysis(existingPatterns) {
if (existingPatterns.length > 0) {
console.log('⏭️ Patterns already present (will be skipped):');
existingPatterns.forEach(pattern => {
console.log(` • ${pattern}`);
});
console.log('');
}
}
/**
* Display new file creation message
*/
displayNewFileCreation() {
console.log('📝 Would create new .gitignore file');
console.log('');
}
/**
* Display what changes would be made
* @param newPatterns New patterns to add
* @param fileExists Whether file exists
*/
displayDryRunChanges(newPatterns, fileExists) {
if (newPatterns.length > 0) {
console.log(`✅ ${fileExists ? 'Would add to existing file' : 'Would create file with'}:`);
console.log('');
newPatterns.forEach(pattern => {
console.log(` + ${pattern}`);
});
console.log('');
}
else {
console.log('ℹ️ No new patterns to add - all patterns already exist');
console.log('');
}
}
/**
* Display preview of the file content
* @param targetDirectory Target directory
* @param newPatterns New patterns to add
* @param fileExists Whether file exists
*/
async displayDryRunPreview(targetDirectory, newPatterns, fileExists) {
console.log('📋 File preview after changes:');
console.log('─'.repeat(40));
if (fileExists) {
// Show existing content first
const existingContent = await this.gitignoreManager.read(targetDirectory);
if (existingContent.trim()) {
console.log(existingContent.trim());
console.log('');
}
}
// Show new section that would be added
const sectionTitle = this.qraftPatterns.getSectionTitle();
const sectionDescription = this.qraftPatterns.getSectionDescription();
console.log(`# ${sectionTitle}`);
console.log(`# ${sectionDescription}`);
newPatterns.forEach(pattern => {
console.log(pattern);
});
console.log('─'.repeat(40));
console.log('');
}
/**
* Display dry run summary
* @param newPatterns New patterns
* @param existingPatterns Existing patterns
* @param fileExists Whether file exists
*/
displayDryRunSummary(newPatterns, existingPatterns, fileExists) {
console.log('📊 Summary:');
console.log(` • ${newPatterns.length} patterns would be added`);
console.log(` • ${existingPatterns.length} patterns already exist`);
console.log(` • File would be ${fileExists ? 'modified' : 'created'}`);
console.log('');
console.log('💡 Run without --dry-run to apply these changes');
}
/**
* Format dry run completion message
* @param newPatterns New patterns
* @param existingPatterns Existing patterns
* @param fileExists Whether file exists
* @returns Formatted message
*/
formatDryRunMessage(newPatterns, existingPatterns, fileExists) {
if (newPatterns.length === 0) {
return 'Dry run completed - no changes needed (all patterns already exist)';
}
const action = fileExists ? 'modified' : 'created';
const summary = `${newPatterns.length} patterns would be added`;
const skipped = existingPatterns.length > 0 ? `, ${existingPatterns.length} skipped` : '';
return `Dry run completed - .gitignore would be ${action} (${summary}${skipped})`;
}
/**
* Handle permissions and confirmations based on force flag
* @param targetDirectory Target directory
* @param fileExists Whether .gitignore file exists
* @param options Command options
* @returns Promise<GitignoreCommandResult> Permission result
*/
async handlePermissions(targetDirectory, fileExists, options) {
// Check directory permissions first
try {
await this.gitignoreManager.validateTargetDirectory(targetDirectory);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Permission error';
if (options.force) {
if (options.verbose) {
console.warn(`⚠️ Permission warning (continuing due to --force): ${errorMessage}`);
}
}
else {
return {
success: false,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: 'Permission denied',
error: errorMessage
};
}
}
// Handle file creation confirmation
if (!fileExists) {
if (options.force) {
if (options.verbose) {
console.log('📝 Creating .gitignore file (--force flag enabled)');
}
}
else {
const shouldCreate = await this.confirmFileCreation(targetDirectory, options);
if (!shouldCreate) {
return {
success: false,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: 'Operation cancelled by user',
error: 'User declined to create .gitignore file'
};
}
}
}
// Handle existing file modification
if (fileExists && !options.force) {
const shouldModify = await this.confirmFileModification(targetDirectory, options);
if (!shouldModify) {
return {
success: false,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: 'Operation cancelled by user',
error: 'User declined to modify existing .gitignore file'
};
}
}
return {
success: true,
created: false,
modified: false,
patternsAdded: [],
patternsSkipped: [],
message: 'Permissions validated'
};
}
/**
* Confirm file creation with user
* @param targetDirectory Target directory
* @param options Command options
* @returns Promise<boolean> True if user confirms
*/
async confirmFileCreation(targetDirectory, options) {
const gitignorePath = this.gitignoreManager.getGitignorePath(targetDirectory);
if (options.verbose) {
console.log('');
console.log('📄 .gitignore file does not exist');
console.log(`📁 Would create: ${gitignorePath}`);
console.log('');
console.log('💡 Use --force to skip this confirmation');
}
// In a real implementation, this would use an interactive prompt
// For now, we'll assume user consent when not in force mode
// This will be enhanced in the interactive implementation
return true;
}
/**
* Confirm file modification with user
* @param targetDirectory Target directory
* @param options Command options
* @returns Promise<boolean> True if user confirms
*/
async confirmFileModification(targetDirectory, options) {
const gitignorePath = this.gitignoreManager.getGitignorePath(targetDirectory);
if (options.verbose) {
console.log('');
console.log('📝 .gitignore file exists and will be modified');
console.log(`📁 File location: ${gitignorePath}`);
console.log('');
console.log('💡 Use --force to skip this confirmation');
}
// In a real implementation, this would use an interactive prompt
// For now, we'll assume user consent when not in force mode
// This will be enhanced in the interactive implementation
return true;
}
/**
* Display force flag information
* @param options Command options
*/
displayForceInfo(options) {
if (options.force && options.verbose) {
console.log('⚡ Force mode enabled - skipping confirmations');
}
}
/**
* Display verbose startup information
* @param targetDirectory Target directory
* @param options Command options
*/
displayVerboseStartup(targetDirectory, options) {
if (!options.verbose)
return;
console.log('');
console.log('🚀 Qraft Gitignore Command');
console.log('═'.repeat(40));
console.log(`📁 Target directory: ${targetDirectory}`);
console.log(`🔧 Options: ${this.formatOptions(options)}`);
this.displayForceInfo(options);
console.log('');
}
/**
* Validate and display directory information
* @param targetDirectory Target directory
* @param options Command options
*/
async validateAndDisplayDirectory(targetDirectory, options) {
if (options.verbose) {
console.log('🔍 Analyzing target directory...');
try {
const permissions = await this.gitignoreManager.checkPermissions(targetDirectory);
console.log(` • Directory writable: ${permissions.canWrite ? '✅' : '❌'}`);
console.log(` • .gitignore exists: ${permissions.fileExists ? '✅' : '❌'}`);
console.log(` • Can create file: ${permissions.canCreate ? '✅' : '❌'}`);
if (permissions.fileExists) {
console.log(` • File writable: ${permissions.fileWritable ? '✅' : '❌'}`);
}
if (permissions.error) {
console.log(` • Warning: ${permissions.error}`);
}
console.log('');
}
catch (error) {
console.log(` • Error checking permissions: ${error instanceof Error ? error.message : 'Unknown error'}`);
console.log('');
}
}
}
/**
* Display pattern analysis results
* @param patterns Detected patterns
* @param options Command options
*/
displayPatternAnalysis(patterns, options) {
if (options.verbose) {
console.log(`📋 Pattern Analysis (${patterns.length} patterns found):`);
if (patterns.length === 0) {
console.log(' • No qraft patterns detected for this directory');
}
else {
// Group patterns by category
const byCategory = patterns.reduce((acc, pattern) => {
if (!acc[pattern.category])
acc[pattern.category] = [];
acc[pattern.category].push(pattern);
return acc;
}, {});
Object.entries(byCategory).forEach(([category, categoryPatterns]) => {
const patterns = categoryPatterns;
console.log(` • ${category.toUpperCase()}: ${patterns.length} patterns`);
patterns.forEach((pattern) => {
console.log(` - ${pattern.pattern} (${pattern.description})`);
});
});
}
console.log('');
}
else if (patterns.length > 0) {
console.log(`📋 Found ${patterns.length} qraft patterns to process`);
}
}
/**
* Display validation results
* @param validPatterns Valid patterns
* @param invalidPatterns Invalid patterns
* @param options Command options
*/
displayValidationResults(validPatterns, invalidPatterns, options) {
if (options.verbose) {
console.log('🔍 Pattern Validation Results:');
console.log(` • Valid patterns: ${validPatterns.length}`);
console.log(` • Invalid patterns: ${invalidPatterns.length}`);
if (validPatterns.length > 0) {
console.log(' • Valid patterns:');
validPatterns.forEach(pattern => {
console.log(` ✅ ${pattern}`);
});
}
if (invalidPatterns.length > 0) {
console.log(' • Invalid patterns:');
invalidPatterns.forEach(({ pattern, errors }) => {
console.log(` ❌ ${pattern}: ${errors.join(', ')}`);
});
}
console.log('');
}
else if (invalidPatterns.length > 0) {
console.warn('⚠️ Some patterns were invalid and will be skipped:');
invalidPatterns.forEach(({ pattern, errors }) => {
console.warn(` • ${pattern}: ${errors.join(', ')}`);
});
}
}
/**
* Format options for display
* @param options Command options
* @returns Formatted options string
*/
formatOptions(options) {
const flags = [];
if (options.dryRun)
flags.push('dry-run');
if (options.force)
flags.push('force');
if (options.verbose)
flags.push('verbose');
if (options.directory)
flags.push(`directory=${options.directory}`);
return flags.length > 0 ? flags.join(', ') : 'none';
}
/**
* Execute the main gitignore operation combining all utilities
* @param targetDirectory Target directory
* @param validPatterns Valid patterns to add
* @param options Command options
* @returns Promise<any> Operation result
*/
async executeGitignoreOperation(targetDirectory, validPatterns, options) {
if (options.verbose) {
console.log('🔧 Executing gitignore operation...');
}
// Get enhanced pattern information for better context
await this.buildPatternContext(validPatterns, options);
// Pre-operation analysis
const preAnalysis = await this.performPreOperationAnalysis(targetDirectory, validPatterns, options);
if (options.verbose) {
this.displayPreOperationSummary(preAnalysis, options);
}
// Execute the core operation
const result = await this.gitignoreManager.addPatterns(targetDirectory, validPatterns, this.qraftPatterns.getSectionTitle(), this.qraftPatterns.getSectionDescription(), {
dryRun: false,
force: options.force || false,
verbose: options.verbose || false
});
// Post-operation validation
if (result.success && options.verbose) {
await this.performPostOperationValidation(targetDirectory, result, options);
}
return result;
}
/**
* Build pattern context for enhanced operation
* @param patterns Patterns to analyze
* @param options Command options
* @returns Promise<any> Pattern context
*/
async buildPatternContext(patterns, options) {
const allPatterns = await this.qraftPatterns.getAllPatterns();
const patternDetails = allPatterns.filter(p => patterns.includes(p.pattern));
const context = {
totalPatterns: patterns.length,
byCategory: {},
staticCount: 0,
dynamicCount: 0,
details: patternDetails
};
patternDetails.forEach(pattern => {
// Count by category
context.byCategory[pattern.category] = (context.byCategory[pattern.category] || 0) + 1;
// Count by type
if (pattern.isStatic) {
context.staticCount++;
}
else {
context.dynamicCount++;
}
});
if (options.verbose) {
console.log('📋 Pattern Context Analysis:');
console.log(` • Total patterns: ${context.totalPatterns}`);
console.log(` • Static patterns: ${context.staticCount}`);
console.log(` • Dynamic patterns: ${context.dynamicCount}`);
Object.entries(context.byCategory).forEach(([category, count]) => {
console.log(` • ${category}: ${count} patterns`);
});
console.log('');
}
return context;
}
/**
* Perform pre-operation analysis
* @param targetDirectory Target directory
* @param patterns Patterns to add
* @param options Command options
* @returns Promise<any> Analysis result
*/
async performPreOperationAnalysis(targetDirectory, patterns, _options) {
const fileExists = await this.gitignoreManager.exists(targetDirectory);
let existingContent = '';
let duplicateAnalysis = { newPatterns: patterns, existingPatterns: [] };
if (fileExists) {
existingContent = await this.gitignoreManager.read(targetDirectory);
duplicateAnalysis = this.gitignoreManager.filterDuplicatePatterns(existingContent, patterns);
}
const analysis = {
fileExists,
existingContent,
duplicateAnalysis,
willCreate: !fileExists,
willModify: fileExists && duplicateAnalysis.newPatterns.length > 0,
hasChanges: duplicateAnalysis.newPatterns.length > 0
};
return analysis;
}
/**
* Display pre-operation summary
* @param analysis Pre-operation analysis
* @param options Command options
*/
displayPreOperationSummary(analysis, _options) {
console.log('📋 Pre-Operation Summary:');
console.log(` • File exists: ${analysis.fileExists ? '✅' : '❌'}`);
console.log(` • Will create: ${analysis.willCreate ? '✅' : '❌'}`);
console.log(` • Will modify: ${analysis.willModify ? '✅' : '❌'}`);
console.log(` • Has changes: ${analysis.hasChanges ? '✅' : '❌'}`);
console.log(` • New patterns: ${analysis.duplicateAnalysis.newPatterns.length}`);
console.log(` • Existing patterns: ${analysis.duplicateAnalysis.existingPatterns.length}`);
console.log('');
}
/**
* Perform post-operation validation
* @param targetDirectory Target directory
* @param result Operation result
* @param options Command options
*/
async performPostOperationValidation(targetDirectory, result, _options) {
console.log('🔍 Post-Operation Validation:');
try {
// Verify file exists
const fileExists = await this.gitignoreManager.exists(targetDirectory);
console.log(` • File exists: ${fileExists ? '✅' : '❌'}`);
if (fileExists) {
// Verify patterns were added
const content = await this.gitignoreManager.read(targetDirectory);
const addedPatternsFound = result.patternsAdded.every((pattern) => this.gitignoreManager.hasPattern(content, pattern));
console.log(` • Patterns verified: ${addedPatternsFound ? '✅' : '❌'}`);
// Check file size
const lines = content.split('\n').length;
console.log(` • File lines: ${lines}`);
// Verify section header exists
const sectionTitle = this.qraftPatterns.getSectionTitle();
const hasSectionHeader = content.includes(`# ${sectionTitle}`);
console.log(` • Section header: ${hasSectionHeader ? '✅' : '❌'}`);
}
console.log('');
}
catch (error) {
console.log(` • Validation error: ${error instanceof Error ? error.message : 'Unknown error'}`);
console.log('');
}
}
/**
* Display operation results
* @param result GitignoreManager result
* @param targetDirectory Target directory
* @param options Command options
*/
displayOperationResults(result, targetDirectory, options) {
if (!options.verbose)
return;
console.log('📊 Operation Results:');
console.log(` • File ${result.created ? 'created' : result.modified ? 'modified' : 'unchanged'}`);
console.log(` • Patterns added: ${result.patternsAdded.length}`);
console.log(` • Patterns skipped: ${result.patternsSkipped.length}`);
if (result.patternsAdded.length > 0) {
console.log(' • Added patterns:');
result.patternsAdded.forEach((pattern) => {
console.log(` + ${pattern}`);
});
}
if (result.patternsSkipped.length > 0) {
console.log(' • Skipped patterns (already exist):');
result.patternsSkipped.forEach((pattern) => {
console.log(` = ${pattern}`);
});
}
const gitignorePath = this.gitignoreManager.getGitignorePath(targetDirectory);
console.log(` • File location: ${gitignorePath}`);
console.log('');
}
/**
* Format success message
* @param result GitignoreManager result
* @param targetDirectory Target directory
* @param options Command options
* @returns string Formatted message
*/
formatSuccessMessage(result, targetDirectory, _options) {
const gitignorePath = this.gitignoreManager.getGitignorePath(targetDirectory);
const relativePath = path.relative(process.cwd(), gitignorePath);
if (result.patternsAdded.length === 0 && result.patternsSkipped.length > 0) {
return `✅ All qraft patterns already exist in ${relativePath}`;
}
if (result.created) {
return `✅ Created ${relativePath} with ${result.patternsAdded.length} qraft patterns`;
}
if (result.modified) {
const addedCount = result.patternsAdded.length;
const skippedCount = result.patternsSkipped.length;
let message = `✅ Updated ${relativePath} with ${addedCount} new qraft patterns`;
if (skippedCount > 0) {
message += ` (${skippedCount} already existed)`;
}
return message;
}
return `✅ .gitignore file processed successfully`;
}
}
exports.GitignoreCommand = GitignoreCommand;
/**
* Create and configure the gitignore command
* @returns Command Configured Commander.js command
*/
function createGitignoreCommand() {
const command = new commander_1.Command('gitignore');
command
.description('Add qraft-specific patterns to .gitignore file')
.summary('Manage .gitignore patterns for qraft-generated files')
.option('-d, --dry-run', 'Show what would be added without making changes')
.option('-f, --force', 'Skip confirmation prompts and create/modify files automatically')
.option('-v, --verbose', 'Show detailed output')
.option('--directory <path>', 'Target directory (defaults to current directory)')
.addHelpText('after', `
Examples:
$ qraft gitignore Add qraft patterns to .gitignore in current directory
$ qraft gitignore --dry-run Preview what patterns would be added
$ qraft gitignore --verbose Show detailed information during execution
$ qraft gitignore --force Skip confirmations and add patterns automatically
$ qraft gitignore --directory ./app Add patterns to .gitignore in ./app directory
Description:
This command adds qraft-specific patterns to your .gitignore file to prevent
qraft-generated files from being committed to version control. It includes
patterns for .qraft metadata directories, configuration files, and cache files.
`)
.action(async (options) => {
try {
const gitignoreCommand = new GitignoreCommand();
// Validate command line options
const optionValidation = validateCommandOptions(options);
if (!optionValidation.valid) {
console.error(`❌ Invalid options: ${optionValidation.error}`);
console.error('💡 Use --help to see available options');
process.exit(1);
}
// Show force mode indicator if enabled
if (options.force && !options.dryRun) {
console.log('⚡ Force mode enabled - will skip confirmations');
}
const result = await gitignoreCommand.execute(options);
if (result.success) {
console.log(result.message);
// Show additional info for force mode
if (options.force && (result.created || result.modified) && !options.dryRun) {
console.log('⚡ Operation completed without confirmations (--force)');
}
if (options.verbose && result.patternsAdded.length > 0) {
console.log('');
console.log('📋 Patterns added:');
result.patternsAdded.forEach(pattern => {
console.log(` • ${pattern}`);
});
}
if (options.verbose && result.patternsSkipped.length > 0) {
console.log('');
console.log('⏭️ Patterns skipped (already exist):');
result.patternsSkipped.forEach(pattern => {
console.log(` • ${pattern}`);
});
}
// Provide helpful hints
if (!options.force && !options.dryRun && options.verbose) {
console.log('');
console.log('💡 Tip: Use --force to skip confirmations in the future');
}
// Success exit
process.exit(0);
}
else {
// Handle different types of failures
handleCommandFailure(result, options);
}
}
catch (unexpectedError) {
// Handle completely unexpected errors
console.error('❌ An unexpected error occurred');
if (options.verbose) {
console.error(` Error: ${unexpectedError instanceof Error ? unexpectedError.message : 'Unknown error'}`);
if (unexpectedError instanceof Error && unexpectedError.stack) {
console.error(` Stack: ${unexpectedError.stack.split('\n')[1]?.trim() || 'Not available'}`);
}
}
console.error('💡 Please report this issue if it persists');
console.error(' Include the error details above when reporting');
process.exit(1);
}
});
return command;
}
/**
* Validate command line options
* @param options Command options to validate
* @returns Validation result
*/
function validateCommandOptions(options) {
// Check for conflicting options
if (options.dryRun && options.force) {
return {
valid: false,
error: '--dry-run and --force cannot be used together'
};
}
// Validate directory path if provided
if (options.directory !== undefined) {
if (typeof options.directory !== 'string' || options.directory.trim() === '') {
return {
valid: false,
error: '--directory must be a valid path'
};
}
// Check for potentially dangerous paths
if (options.directory.includes('..') && !options.force) {
return {
valid: false,
error: 'Relative paths with ".." are not allowed (use --force to override)'
};
}
}
return { valid: true };
}
/**
* Handle command failure with appropriate messaging
* @param result Command result
* @param options Command options
*/
function handleCommandFailure(result, options) {
console.error(`❌ ${result.message}`);
if (result.error && options.verbose) {
console.error(` Error details: ${result.error}`);
}
// Provide specific suggestions based on error type
if (result.error) {
if (result.error.includes('Permission') && !options.force) {
console.error('');
console.error('💡 Permission issue suggestions:');
console.error(' • Try using --force to override permission checks');
console.error(' • Check directory permissions: ls -la');
console.error(' • Ensure you have write access to the target directory');
}
else if (result.error.includes('ENOENT')) {
console.error('');
console.error('💡 Directory not found suggestions:');
console.error(' • Verify the target directory exists');
console.error(' • Use --directory flag to specify a different path');
console.error(' • Check for typos in the directory path');
}
else if (result.error.includes('config')) {
console.error('');
console.error('💡 Configuration issue suggestions:');
console.error(' • Check qraft configuration: qraft config list');
console.error(' • Reset configuration: qraft config reset');
console.error(' • Verify cache directory permissions');
}
}
// General suggestions
console.error('');
console.error('💡 General troubleshooting:');
console.error(' • Use --dry-run to test without making changes');
console.error(' • Use --verbose for more detailed output');
console.error(' • Check available disk space');
process.exit(1);
}
//# sourceMappingURL=gitignore.js.map