testacode-cursor-rules
Version:
CLI tool to install Testacode's Cursor Rules
215 lines (214 loc) • 8.39 kB
JavaScript
#!/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);
});