UNPKG

testacode-cursor-rules

Version:

CLI tool to install Testacode's Cursor Rules

215 lines (214 loc) 8.39 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const inquirer_1 = __importDefault(require("inquirer")); const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const chalk_1 = __importDefault(require("chalk")); const glob_1 = require("glob"); // Get the package directory path const packageDir = path_1.default.resolve(__dirname, ".."); // Get the current working directory (where the command is run) const currentDir = process.cwd(); /** * Dynamically gets all rule categories by reading directories */ function getRuleCategories() { // First check if we have a .cursor/rules directory with categories const rulesDirPath = path_1.default.join(packageDir, ".cursor", "rules"); if (fs_extra_1.default.existsSync(rulesDirPath)) { const entries = fs_extra_1.default.readdirSync(rulesDirPath, { withFileTypes: true }); const categories = entries .filter((entry) => entry.isDirectory() && !entry.name.startsWith(".")) .map((entry) => entry.name); if (categories.length > 0) { return categories; } } // Then try to read categories directly from package root const rootDirs = fs_extra_1.default.readdirSync(packageDir, { withFileTypes: true }); const packageCategories = rootDirs .filter((entry) => entry.isDirectory() && !entry.name.startsWith(".") && ![ "node_modules", "dist", "src", ".cursor", ".git", ".history", ].includes(entry.name)) .map((entry) => entry.name); // If categories exist in the package root, use those if (packageCategories.length > 0) { return packageCategories; } // Fallback to predefined categories return [ "architecture", "code-style", "domain-specific", "general", "documentation", ]; } /** * Finds all rule files within the package */ function findRules() { const rules = []; const categories = getRuleCategories(); // First check if rules exist in .cursor/rules directory const cursorRulesPath = path_1.default.join(packageDir, ".cursor", "rules"); if (fs_extra_1.default.existsSync(cursorRulesPath)) { for (const category of categories) { const categoryPath = path_1.default.join(cursorRulesPath, category); if (fs_extra_1.default.existsSync(categoryPath)) { const files = fs_extra_1.default .readdirSync(categoryPath) .filter((file) => file.endsWith(".mdc")); files.forEach((file) => { const relativePath = path_1.default.join(category, file); rules.push({ category, file, path: relativePath, }); }); } } // If we found rules in .cursor/rules, return them if (rules.length > 0) { return rules; } } // Otherwise search in the package root directories for (const category of categories) { const categoryPath = path_1.default.join(packageDir, category); if (fs_extra_1.default.existsSync(categoryPath)) { const files = (0, glob_1.globSync)(`${category}/**/*.mdc`, { cwd: packageDir }); files.forEach((file) => { rules.push({ category, file: path_1.default.basename(file), path: file, }); }); } } return rules; } /** * Copies selected rule files to the target project */ async function copyRules(selectedRules) { // Create destination directory if it doesn't exist const destDir = path_1.default.join(currentDir, ".cursor", "rules"); await fs_extra_1.default.ensureDir(destDir); const cursorRulesPath = path_1.default.join(packageDir, ".cursor", "rules"); const hasRulesInCursorDir = fs_extra_1.default.existsSync(cursorRulesPath); // Copy each selected rule for (const rulePath of selectedRules) { // Determine the source path based on whether the rule is in .cursor/rules or not const sourcePath = hasRulesInCursorDir && fs_extra_1.default.existsSync(path_1.default.join(cursorRulesPath, rulePath)) ? path_1.default.join(cursorRulesPath, rulePath) : path_1.default.join(packageDir, rulePath); const destPath = path_1.default.join(currentDir, ".cursor", "rules", rulePath); await fs_extra_1.default.ensureDir(path_1.default.dirname(destPath)); await fs_extra_1.default.copy(sourcePath, destPath); console.log(chalk_1.default.green(`✅ Copied: ${rulePath}`)); } console.log(chalk_1.default.bold.green("\n🎉 Rules installed successfully!")); console.log(chalk_1.default.blue("You can now reference these rules in your conversations with Cursor AI.")); } async function main() { console.log(chalk_1.default.bold("\n🚀 Instalador de Reglas de Cursor")); console.log("Selecciona las reglas que deseas instalar en tu proyecto\n"); const allRules = findRules(); if (allRules.length === 0) { console.log(chalk_1.default.red("❌ No se encontraron reglas en el paquete.")); return; } // First prompt: choose installation method const { installMethod } = await inquirer_1.default.prompt([ { type: "list", name: "installMethod", message: "¿Cómo quieres instalar las reglas?", choices: [ { name: "Instalar todas las reglas", value: "all" }, { name: "Seleccionar categorías", value: "categories" }, { name: "Seleccionar reglas individuales", value: "individual" }, ], }, ]); let selectedRules = []; if (installMethod === "all") { // Install all rules selectedRules = allRules.map((rule) => rule.path); } else if (installMethod === "categories") { // Get categories from all rules to ensure we only show categories that have rules const availableCategories = [ ...new Set(allRules.map((rule) => rule.category)), ]; // Select categories const { categories } = await inquirer_1.default.prompt([ { type: "checkbox", name: "categories", message: "Selecciona las categorías de reglas que deseas instalar:", choices: availableCategories.map((category) => ({ name: category, value: category, checked: false, })), }, ]); selectedRules = allRules .filter((rule) => categories.includes(rule.category)) .map((rule) => rule.path); } else { // Select individual rules const { rules } = await inquirer_1.default.prompt([ { type: "checkbox", name: "rules", message: "Selecciona las reglas que deseas instalar:", choices: allRules.map((rule) => ({ name: `${rule.category}/${rule.file}`, value: rule.path, checked: false, })), }, ]); selectedRules = rules; } if (selectedRules.length === 0) { console.log(chalk_1.default.yellow("⚠️ No se seleccionaron reglas. Operación cancelada.")); return; } // Confirm installation const { confirm } = await inquirer_1.default.prompt([ { type: "confirm", name: "confirm", message: `¿Instalar ${selectedRules.length} reglas en tu proyecto?`, default: true, }, ]); if (confirm) { await copyRules(selectedRules); } else { console.log(chalk_1.default.yellow("⚠️ Instalación cancelada.")); } } main().catch((error) => { console.error(chalk_1.default.red("❌ Error:"), error); process.exit(1); });