spaider
Version:
Deterministic-first AI code assistant that crawls your codebase to implement changes using open source LLMs
326 lines • 12 kB
JavaScript
;
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;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.message = message;
exports.system = system;
exports.user = user;
exports.readFile = readFile;
exports.detectLanguage = detectLanguage;
exports.getRelativePath = getRelativePath;
exports.filterPathsWithinProject = filterPathsWithinProject;
exports.sanitizePath = sanitizePath;
exports.getDisplayPath = getDisplayPath;
exports.ensureDirectoryExists = ensureDirectoryExists;
exports.loadFileContexts = loadFileContexts;
exports.formatFilePreview = formatFilePreview;
exports.formatFileSemantic = formatFileSemantic;
exports.formatFilePreviews = formatFilePreviews;
exports.formatFileSemantics = formatFileSemantics;
exports.formatChanges = formatChanges;
exports.formatWriteChanges = formatWriteChanges;
exports.resolveFilePath = resolveFilePath;
exports.ensureDirectoryForFile = ensureDirectoryForFile;
exports.normalizeFilePath = normalizeFilePath;
exports.isWithinProjectRoot = isWithinProjectRoot;
exports.getFileExtension = getFileExtension;
exports.getFileName = getFileName;
exports.getFileNameWithoutExtension = getFileNameWithoutExtension;
exports.joinPaths = joinPaths;
exports.makeRelativeToProject = makeRelativeToProject;
exports.fileExists = fileExists;
exports.isDirectory = isDirectory;
exports.safeWriteFile = safeWriteFile;
exports.safeDeleteFile = safeDeleteFile;
const path = __importStar(require("path"));
const fs_1 = require("fs");
const dedent_1 = __importDefault(require("dedent"));
const const_1 = require("./const");
const logger_1 = require("../services/logger");
function message(role, content) {
return { role, content: (0, dedent_1.default)(content) };
}
function system(content) {
return message("system", content);
}
function user(content) {
return message("user", content);
}
async function readFile(filePath) {
try {
return await fs_1.promises.readFile(filePath, "utf8");
}
catch (error) {
throw new Error(`Failed to read file ${filePath}: ${error}`);
}
}
function detectLanguage(filePath) {
const ext = path.extname(filePath);
switch (ext) {
case ".ts":
return "typescript";
case ".tsx":
return "tsx";
case ".jsx":
return "jsx";
case ".js":
return "javascript";
default:
return "javascript";
}
}
function getRelativePath(from, to) {
return path.normalize(path.relative(from, to)).replace(/\\/g, "/");
}
function filterPathsWithinProject(filePaths, excludePaths, projectRoot) {
return filePaths
.filter((file) => !excludePaths.includes(file))
.filter((filePath) => {
const relativePath = path.relative(projectRoot, filePath);
return !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
});
}
function sanitizePath(filePath) {
return path.normalize(filePath).replace(/\\/g, "/");
}
function getDisplayPath(filePath, projectRoot) {
if (projectRoot && filePath.startsWith(projectRoot)) {
return filePath.substring(projectRoot.length).replace(/^\/+/, "");
}
return sanitizePath(filePath);
}
async function ensureDirectoryExists(dirPath) {
try {
await fs_1.promises.mkdir(dirPath, { recursive: true });
}
catch (error) {
// Directory might already exist, ignore error
}
}
async function loadFileContexts(filePaths) {
const contexts = await Promise.all(filePaths.map(async (path) => {
try {
return {
path,
content: await readFile(path),
language: detectLanguage(path),
};
}
catch (error) {
logger_1.Logger.warn(`Failed to read discovered file ${path}`);
return null;
}
}));
return contexts.filter((c) => c !== null);
}
function formatFilePreview(file) {
if (!file.content) {
throw new Error("Missing file content");
}
const lines = file.content.split("\n");
const fileContent = lines.length > const_1.MAX_FILE_PREVIEW_LINES
? lines.slice(0, const_1.MAX_FILE_PREVIEW_LINES).join("\n") + "\n...[truncated]"
: file.content;
return `${file.path}:\n${fileContent}`;
}
function formatFileSemantic(file) {
if (!file.ast) {
throw new Error("Missing file ast");
}
return (0, dedent_1.default) `${file.path}:
- Imports: ${file.ast.imports.map((i) => i.source).join(", ")}
- Exports: ${file.ast.exports.map((e) => e.name).join(", ")}
- Functions: ${file.ast.functions.map((f) => f.name).join(", ")}
- Classes: ${file.ast.classes.map((c) => c.name).join(", ")}`;
}
function formatFilePreviews(files) {
return files.map(formatFilePreview).join("\n\n");
}
function formatFileSemantics(files) {
return files.map(formatFileSemantic).join("\n");
}
function formatChanges(changes) {
return changes
.map((change) => {
let changeDescription = `${change.filePath}: ${change.operation}`;
if (change.operation !== "delete_file") {
changeDescription += ` (${change.modificationType})`;
}
if (change.modificationDescription) {
changeDescription += `\n${change.modificationDescription}`;
}
if (change.oldCodeBlock) {
changeDescription += `\nOld Code Block:\n${change.oldCodeBlock}`;
}
if (change.newCodeBlock) {
changeDescription += `\nNew Code Block:\n${change.newCodeBlock}`;
}
return changeDescription;
})
.join("\n\n");
}
function formatWriteChanges(filePath, fileChanges) {
const output = [`## File: ${filePath}", "`];
for (const change of fileChanges) {
// Add operation description
let description = "";
switch (change.operation) {
case "new_file":
description = `Create new file: ${change.modificationDescription || "New file creation"}`;
break;
case "delete_file":
description = `Delete file: ${change.modificationDescription || "Remove this file"}`;
break;
case "modify_file":
description = `${change.modificationType}: ${change.modificationDescription || "Modify existing file"}`;
break;
}
output.push(`### ${description}\n`);
// Add old code block if it exists
if (change.oldCodeBlock && change.operation === "modify_file") {
output.push("**Old Code:**\n```");
output.push(change.oldCodeBlock);
output.push("```\n");
}
// Add new code block if it exists
if (change.newCodeBlock && change.operation !== "delete_file") {
output.push("**New Code:**\n```");
output.push(change.newCodeBlock);
output.push("```\n");
}
// Add special instructions for different operations
if (change.operation === "delete_file") {
output.push("**Action:** Delete this file completely\n");
}
else if (change.operation === "new_file") {
output.push("**Action:** Create this file with the new code above\n");
}
else {
switch (change.modificationType) {
case "replace_block":
output.push("**Action:** Replace the old code block with the new code block above\n");
break;
case "add_block":
output.push("**Action:** Add the new code block to the appropriate location in the file\n");
break;
case "remove_block":
output.push("**Action:** Remove the old code block from the file\n");
break;
}
}
output.push("---\n");
}
return output.join("\n");
}
// File Path Utilities
function resolveFilePath(filePath, projectRoot) {
if (path.isAbsolute(filePath)) {
return filePath;
}
return path.resolve(projectRoot, filePath);
}
async function ensureDirectoryForFile(filePath) {
const dirPath = path.dirname(filePath);
await ensureDirectoryExists(dirPath);
}
function normalizeFilePath(filePath) {
return path.normalize(filePath).replace(/\\/g, "/");
}
function isWithinProjectRoot(filePath, projectRoot) {
const resolvedPath = path.resolve(filePath);
const resolvedRoot = path.resolve(projectRoot);
const relativePath = path.relative(resolvedRoot, resolvedPath);
return !relativePath.startsWith("..") && !path.isAbsolute(relativePath);
}
function getFileExtension(filePath) {
return path.extname(filePath).toLowerCase();
}
function getFileName(filePath) {
return path.basename(filePath);
}
function getFileNameWithoutExtension(filePath) {
return path.basename(filePath, path.extname(filePath));
}
function joinPaths(...paths) {
return normalizeFilePath(path.join(...paths));
}
function makeRelativeToProject(filePath, projectRoot) {
const resolved = resolveFilePath(filePath, projectRoot);
return getRelativePath(projectRoot, resolved);
}
async function fileExists(filePath) {
try {
await fs_1.promises.access(filePath);
return true;
}
catch {
return false;
}
}
async function isDirectory(dirPath) {
try {
const stats = await fs_1.promises.stat(dirPath);
return stats.isDirectory();
}
catch {
return false;
}
}
async function safeWriteFile(filePath, content, projectRoot) {
const resolvedPath = projectRoot
? resolveFilePath(filePath, projectRoot)
: filePath;
// Ensure the file path is within project bounds if projectRoot is provided
if (projectRoot && !isWithinProjectRoot(resolvedPath, projectRoot)) {
throw new Error(`File path ${filePath} is outside project root ${projectRoot}`);
}
await ensureDirectoryForFile(resolvedPath);
await fs_1.promises.writeFile(resolvedPath, content, "utf-8");
}
async function safeDeleteFile(filePath, projectRoot) {
const resolvedPath = projectRoot
? resolveFilePath(filePath, projectRoot)
: filePath;
// Ensure the file path is within project bounds if projectRoot is provided
if (projectRoot && !isWithinProjectRoot(resolvedPath, projectRoot)) {
throw new Error(`File path ${filePath} is outside project root ${projectRoot}`);
}
if (await fileExists(resolvedPath)) {
await fs_1.promises.unlink(resolvedPath);
}
}
//# sourceMappingURL=utils.js.map