UNPKG

@intellectronica/ruler

Version:

Ruler — apply the same rules to all coding agents

163 lines (162 loc) 6.35 kB
"use strict"; 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.updateGitignore = updateGitignore; const fs_1 = require("fs"); const path = __importStar(require("path")); const RULER_START_MARKER = '# START Ruler Generated Files'; const RULER_END_MARKER = '# END Ruler Generated Files'; /** * Updates the .gitignore file in the project root with paths in a managed Ruler block. * Creates the file if it doesn't exist, and creates or updates the Ruler-managed block. * * @param projectRoot The project root directory (where .gitignore should be located) * @param paths Array of file paths to add to .gitignore (can be absolute or relative) */ async function updateGitignore(projectRoot, paths) { const gitignorePath = path.join(projectRoot, '.gitignore'); // Read existing .gitignore or start with empty content let existingContent = ''; try { existingContent = await fs_1.promises.readFile(gitignorePath, 'utf8'); } catch (err) { if (err.code !== 'ENOENT') { throw err; } } // Convert paths to relative POSIX format const relativePaths = paths.map((p) => { let relative; if (path.isAbsolute(p)) { relative = path.relative(projectRoot, p); } else { // Handle relative paths that might include the project root prefix const normalizedProjectRoot = path.normalize(projectRoot); const normalizedPath = path.normalize(p); // Get the basename of the project root to match against path prefixes const projectBasename = path.basename(normalizedProjectRoot); // If the path starts with the project basename, remove it if (normalizedPath.startsWith(projectBasename + path.sep)) { relative = normalizedPath.substring(projectBasename.length + 1); } else { relative = normalizedPath; } } return relative.replace(/\\/g, '/'); // Convert to POSIX format }); // Get all existing paths from .gitignore (excluding Ruler block) const existingPaths = getExistingPathsExcludingRulerBlock(existingContent); // Filter out paths that already exist outside the Ruler block const newPaths = relativePaths.filter((p) => !existingPaths.includes(p)); // The Ruler block should contain only the new paths (replacement behavior) const allRulerPaths = [...new Set(newPaths)].sort(); // Create new content const newContent = updateGitignoreContent(existingContent, allRulerPaths); // Write the updated content await fs_1.promises.writeFile(gitignorePath, newContent); } /** * Gets all paths from .gitignore content excluding those in the Ruler block. */ function getExistingPathsExcludingRulerBlock(content) { const lines = content.split('\n'); const paths = []; let inRulerBlock = false; for (const line of lines) { const trimmed = line.trim(); if (trimmed === RULER_START_MARKER) { inRulerBlock = true; continue; } if (trimmed === RULER_END_MARKER) { inRulerBlock = false; continue; } if (!inRulerBlock && trimmed && !trimmed.startsWith('#')) { paths.push(trimmed); } } return paths; } /** * Updates the .gitignore content by replacing or adding the Ruler block. */ function updateGitignoreContent(existingContent, rulerPaths) { const lines = existingContent.split('\n'); const newLines = []; let inFirstRulerBlock = false; let hasRulerBlock = false; let processedFirstBlock = false; for (const line of lines) { const trimmed = line.trim(); if (trimmed === RULER_START_MARKER && !processedFirstBlock) { inFirstRulerBlock = true; hasRulerBlock = true; newLines.push(line); // Add the new Ruler paths rulerPaths.forEach((p) => newLines.push(p)); continue; } if (trimmed === RULER_END_MARKER && inFirstRulerBlock) { inFirstRulerBlock = false; processedFirstBlock = true; newLines.push(line); continue; } if (!inFirstRulerBlock) { newLines.push(line); } // Skip lines that are in the first Ruler block (they get replaced) } // If no Ruler block exists, add one at the end if (!hasRulerBlock) { // Add blank line if content exists and doesn't end with blank line if (existingContent.trim() && !existingContent.endsWith('\n\n')) { newLines.push(''); } newLines.push(RULER_START_MARKER); rulerPaths.forEach((p) => newLines.push(p)); newLines.push(RULER_END_MARKER); } // Ensure file ends with a newline let result = newLines.join('\n'); if (!result.endsWith('\n')) { result += '\n'; } return result; }