agent-rules-kit
Version:
CLI tool to bootstrap AI agent rules for multiple IDEs and frameworks. Generates optimized rules for Cursor, VS Code, Claude, and 6+ other AI coding assistants.
246 lines (218 loc) • 7.28 kB
JavaScript
/**
* Base Service for Agent Rules Kit
* Provides shared functionality for all services
*/
import chalk from 'chalk';
import { promises as fsPromises } from 'fs';
import fs from 'fs-extra';
/**
* Base service that provides shared utilities
*/
export class BaseService {
constructor(options = {}) {
this.debug = options.debug || false;
this.fileService = options.fileService;
this.configService = options.configService;
this.stackService = options.stackService;
}
/**
* Debug log helper - Centralized for all services
* @param {...any} args - Arguments to log
*/
debugLog(...args) {
if (this.debug) {
console.log(chalk.gray('[DEBUG]'), ...args);
}
}
/**
* Checks if a directory exists
* @param {string} dir - Path of the directory to check
* @returns {boolean} - true if it exists, false if not
*/
directoryExists(dir) {
return fs.existsSync(dir);
}
/**
* Checks if a directory exists (async version)
* @param {string} dir - Path of the directory to check
* @returns {Promise<boolean>} - true if it exists, false if not
*/
async directoryExistsAsync(dir) {
try {
const stats = await fsPromises.stat(dir);
return stats.isDirectory();
} catch (error) {
return false;
}
}
/**
* Ensures a directory exists, creating it if necessary
* @param {string} dir - Path of the directory to create
*/
ensureDirectoryExists(dir) {
fs.ensureDirSync(dir);
}
/**
* Ensures a directory exists, creating it if necessary (async version)
* @param {string} dir - Path of the directory to create
* @returns {Promise<void>}
*/
async ensureDirectoryExistsAsync(dir) {
await fs.ensureDir(dir);
}
/**
* Gets the files in a directory
* @param {string} dir - Directory path
* @returns {Array<string>} - List of files
*/
getFilesInDirectory(dir) {
if (!this.directoryExists(dir)) {
this.debugLog(`Directory not found: ${dir}`);
return [];
}
return fs.readdirSync(dir);
}
/**
* Gets the files in a directory (async version)
* @param {string} dir - Directory path
* @returns {Promise<Array<string>>} - List of files
*/
async getFilesInDirectoryAsync(dir) {
if (!await this.directoryExistsAsync(dir)) {
this.debugLog(`Directory not found: ${dir}`);
return [];
}
return await fsPromises.readdir(dir);
}
/**
* Reads a file
* @param {string} file - Path of the file to read
* @returns {string} - File contents
*/
readFile(file) {
return fs.readFileSync(file, 'utf8');
}
/**
* Reads a file (async version)
* @param {string} file - Path of the file to read
* @returns {Promise<string>} - File contents
*/
async readFileAsync(file) {
return await fsPromises.readFile(file, 'utf8');
}
/**
* Optimized file reading that chooses appropriate method based on file size
* @param {string} file - Path of the file to read
* @returns {Promise<string>} - File contents
*/
async readFileOptimized(file) {
try {
const stats = await fsPromises.stat(file);
// For large files (>1MB), use streaming
if (stats.size > 1024 * 1024) {
this.debugLog(`Using stream for large file: ${file} (${stats.size} bytes)`);
return new Promise((resolve, reject) => {
let content = '';
const stream = fs.createReadStream(file, { encoding: 'utf8' });
stream.on('data', (chunk) => {
content += chunk;
});
stream.on('end', () => {
resolve(content);
});
stream.on('error', reject);
});
}
// For smaller files, read all at once
return await fsPromises.readFile(file, 'utf8');
} catch (error) {
this.debugLog(`Error reading file ${file}: ${error.message}`);
throw error;
}
}
/**
* Writes a file
* @param {string} file - Path of the file to write
* @param {string} content - Content to write
*/
writeFile(file, content) {
fs.outputFileSync(file, content);
this.debugLog(`File created: ${chalk.green(file)}`);
}
/**
* Writes a file (async version)
* @param {string} file - Path of the file to write
* @param {string} content - Content to write
* @returns {Promise<void>}
*/
async writeFileAsync(file, content) {
await fs.outputFile(file, content);
this.debugLog(`File created: ${chalk.green(file)}`);
}
/**
* Copies a file
* @param {string} src - Source file
* @param {string} dest - Destination
*/
copyFile(src, dest) {
fs.copyFileSync(src, dest);
this.debugLog(`File copied: ${chalk.green(dest)}`);
}
/**
* Copies a file (async version)
* @param {string} src - Source file
* @param {string} dest - Destination
* @returns {Promise<void>}
*/
async copyFileAsync(src, dest) {
await fs.copy(src, dest);
this.debugLog(`File copied: ${chalk.green(dest)}`);
}
/**
* Process files in batches for better memory management
* @param {Array<any>} items - Array of items to process
* @param {Function} processFn - Async function to process each item
* @param {number} batchSize - Number of items to process at once
* @returns {Promise<Array<any>>} - Results of processing
*/
async processBatch(items, processFn, batchSize = 10) {
const results = [];
for (let i = 0; i < items.length; i += batchSize) {
const batch = items.slice(i, i + batchSize);
const batchResults = await Promise.all(
batch.map(item => processFn(item))
);
results.push(...batchResults);
// Allow event loop to handle other tasks between batches
if (i + batchSize < items.length) {
await new Promise(resolve => setTimeout(resolve, 0));
}
}
return results;
}
// Proxy methods to stackService if available
getAvailableArchitectures(stack) {
if (this.stackService) {
return this.stackService.getAvailableArchitectures(stack);
}
return [];
}
getAvailableVersions(stack) {
if (this.stackService) {
return this.stackService.getAvailableVersions(stack);
}
return [];
}
mapVersionToRange(stack, version) {
if (this.stackService) {
return this.stackService.mapVersionToRange(stack, version);
}
return version;
}
getFormattedVersionName(stack, versionRange) {
if (this.stackService) {
return this.stackService.getFormattedVersionName(stack, versionRange);
}
return versionRange;
}
}