147 lines
4.1 KiB
JavaScript
147 lines
4.1 KiB
JavaScript
// Cursor conversion profile for rule-transformer
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import { log } from '../../scripts/modules/utils.js';
|
|
import { createProfile } from './base-profile.js';
|
|
|
|
// Helper copy; use cpSync when available, fallback to manual recursion
|
|
function copyRecursiveSync(src, dest) {
|
|
if (fs.cpSync) {
|
|
try {
|
|
fs.cpSync(src, dest, { recursive: true, force: true });
|
|
return;
|
|
} catch (err) {
|
|
throw new Error(`Failed to copy ${src} to ${dest}: ${err.message}`);
|
|
}
|
|
}
|
|
const exists = fs.existsSync(src);
|
|
let stats = null;
|
|
let isDirectory = false;
|
|
|
|
if (exists) {
|
|
try {
|
|
stats = fs.statSync(src);
|
|
isDirectory = stats.isDirectory();
|
|
} catch (err) {
|
|
// Handle TOCTOU race condition - treat as non-existent/not-a-directory
|
|
isDirectory = false;
|
|
}
|
|
}
|
|
|
|
if (isDirectory) {
|
|
try {
|
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
for (const child of fs.readdirSync(src)) {
|
|
copyRecursiveSync(path.join(src, child), path.join(dest, child));
|
|
}
|
|
} catch (err) {
|
|
throw new Error(
|
|
`Failed to copy directory ${src} to ${dest}: ${err.message}`
|
|
);
|
|
}
|
|
} else {
|
|
try {
|
|
// ensure parent exists for file copies
|
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
fs.copyFileSync(src, dest);
|
|
} catch (err) {
|
|
throw new Error(`Failed to copy file ${src} to ${dest}: ${err.message}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Helper function to recursively remove directory
|
|
function removeDirectoryRecursive(dirPath) {
|
|
if (fs.existsSync(dirPath)) {
|
|
try {
|
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
return true;
|
|
} catch (err) {
|
|
log('error', `Failed to remove directory ${dirPath}: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Resolve the Cursor profile directory from either project root, profile root, or rules dir
|
|
function resolveCursorProfileDir(baseDir) {
|
|
const base = path.basename(baseDir);
|
|
// If called with .../.cursor/rules -> return .../.cursor
|
|
if (base === 'rules' && path.basename(path.dirname(baseDir)) === '.cursor') {
|
|
return path.dirname(baseDir);
|
|
}
|
|
// If called with .../.cursor -> return as-is
|
|
if (base === '.cursor') return baseDir;
|
|
// Otherwise assume project root and append .cursor
|
|
return path.join(baseDir, '.cursor');
|
|
}
|
|
|
|
// Lifecycle functions for Cursor profile
|
|
function onAddRulesProfile(targetDir, assetsDir) {
|
|
// Copy commands directory recursively
|
|
const commandsSourceDir = path.join(assetsDir, 'claude', 'commands');
|
|
const profileDir = resolveCursorProfileDir(targetDir);
|
|
const commandsDestDir = path.join(profileDir, 'commands');
|
|
|
|
if (!fs.existsSync(commandsSourceDir)) {
|
|
log(
|
|
'warn',
|
|
`[Cursor] Source commands directory does not exist: ${commandsSourceDir}`
|
|
);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// Ensure fresh state to avoid stale command files
|
|
try {
|
|
fs.rmSync(commandsDestDir, { recursive: true, force: true });
|
|
log(
|
|
'debug',
|
|
`[Cursor] Removed existing commands directory: ${commandsDestDir}`
|
|
);
|
|
} catch (deleteErr) {
|
|
// Directory might not exist, which is fine
|
|
log(
|
|
'debug',
|
|
`[Cursor] Commands directory did not exist or could not be removed: ${deleteErr.message}`
|
|
);
|
|
}
|
|
|
|
copyRecursiveSync(commandsSourceDir, commandsDestDir);
|
|
log('debug', `[Cursor] Copied commands directory to ${commandsDestDir}`);
|
|
} catch (err) {
|
|
log(
|
|
'error',
|
|
`[Cursor] An error occurred during commands copy: ${err.message}`
|
|
);
|
|
}
|
|
}
|
|
|
|
function onRemoveRulesProfile(targetDir) {
|
|
// Remove .cursor/commands directory recursively
|
|
const profileDir = resolveCursorProfileDir(targetDir);
|
|
const commandsDir = path.join(profileDir, 'commands');
|
|
if (removeDirectoryRecursive(commandsDir)) {
|
|
log(
|
|
'debug',
|
|
`[Cursor] Ensured commands directory removed at ${commandsDir}`
|
|
);
|
|
}
|
|
}
|
|
|
|
// Create and export cursor profile using the base factory
|
|
export const cursorProfile = createProfile({
|
|
name: 'cursor',
|
|
displayName: 'Cursor',
|
|
url: 'cursor.so',
|
|
docsUrl: 'docs.cursor.com',
|
|
targetExtension: '.mdc', // Cursor keeps .mdc extension
|
|
supportsRulesSubdirectories: true,
|
|
onAdd: onAddRulesProfile,
|
|
onRemove: onRemoveRulesProfile
|
|
});
|
|
|
|
// Export lifecycle functions separately to avoid naming conflicts
|
|
export { onAddRulesProfile, onRemoveRulesProfile };
|