remcode
Version:
Turn your AI assistant into a codebase expert. Intelligent code analysis, semantic search, and software engineering guidance through MCP integration.
344 lines (343 loc) • 11.5 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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.SourceType = void 0;
exports.resolveSource = resolveSource;
exports.detectLanguages = detectLanguages;
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const child_process = __importStar(require("child_process"));
const util = __importStar(require("util"));
const logger_1 = require("./logger");
const logger = (0, logger_1.getLogger)('Source');
const execAsync = util.promisify(child_process.exec);
/**
* Source types supported by the resolver
*/
var SourceType;
(function (SourceType) {
SourceType["LOCAL_PATH"] = "local_path";
SourceType["GITHUB_REPO"] = "github_repo";
SourceType["GITLAB_REPO"] = "gitlab_repo";
SourceType["BITBUCKET_REPO"] = "bitbucket_repo";
SourceType["GIT_REPO"] = "git_repo";
SourceType["HTTP_URL"] = "http_url";
SourceType["UNKNOWN"] = "unknown";
})(SourceType || (exports.SourceType = SourceType = {}));
/**
* Resolve a source path or URL to a local directory
*/
async function resolveSource(source, options = {}) {
const parsedSource = parseSource(source);
if (!parsedSource) {
throw new Error(`Unsupported source type: ${source}`);
}
switch (parsedSource.type) {
case SourceType.GITHUB_REPO:
return resolveGitHubSource(parsedSource.originalSource, options);
case SourceType.LOCAL_PATH:
return resolveLocalSource(parsedSource.localPath || parsedSource.originalSource);
default:
throw new Error(`Unsupported source type: ${parsedSource.type}`);
}
}
/**
* Parse a source string into its components
*/
function parseSource(source) {
const parsers = [
parseGitHubUrl,
parseGitLabUrl,
parseBitbucketUrl,
parseGenericGitUrl,
parseHttpUrl,
parseLocalPath
];
for (const parser of parsers) {
const parsedSource = parser(source);
if (parsedSource)
return parsedSource;
}
return null;
}
/**
* Parse a GitHub URL into its components
*/
function parseGitHubUrl(url) {
// GitHub URL formats:
// https://github.com/owner/repo
// https://github.com/owner/repo/tree/branch
// https://github.com/owner/repo/tree/branch/path/to/dir
// https://github.com/owner/repo/blob/branch/path/to/file
const githubRegex = /github\.com\/([\w.-]+)\/([\w.-]+)(?:\/(?:tree|blob)\/([\w.-]+)(?:\/(.+))?)?/i;
const match = url.match(githubRegex);
if (!match)
return null;
const [, owner, repo, branch = 'main', path = ''] = match;
logger.debug(`Parsed GitHub URL: owner=${owner}, repo=${repo}, branch=${branch}, path=${path}`);
return {
type: SourceType.GITHUB_REPO,
owner,
repo,
branch,
path,
originalSource: url
};
}
/**
* Parse a GitLab URL into its components
*/
function parseGitLabUrl(url) {
// GitLab URL formats:
// https://gitlab.com/owner/repo
// https://gitlab.com/owner/repo/-/tree/branch
// https://gitlab.com/owner/repo/-/tree/branch/path/to/dir
// https://gitlab.com/owner/repo/-/blob/branch/path/to/file
const gitlabRegex = /gitlab\.com\/([\w.-]+)\/([\w.-]+)(?:\/-\/(?:tree|blob)\/([\w.-]+)(?:\/(.+))?)?/i;
const match = url.match(gitlabRegex);
if (!match)
return null;
const [, owner, repo, branch = 'main', path = ''] = match;
logger.debug(`Parsed GitLab URL: owner=${owner}, repo=${repo}, branch=${branch}, path=${path}`);
return {
type: SourceType.GITLAB_REPO,
owner,
repo,
branch,
path,
originalSource: url
};
}
/**
* Parse a Bitbucket URL into its components
*/
function parseBitbucketUrl(url) {
// Bitbucket URL formats:
// https://bitbucket.org/owner/repo
// https://bitbucket.org/owner/repo/src/branch
// https://bitbucket.org/owner/repo/src/branch/path/to/file
const bitbucketRegex = /bitbucket\.org\/([\w.-]+)\/([\w.-]+)(?:\/src\/([\w.-]+)(?:\/(.+))?)?/i;
const match = url.match(bitbucketRegex);
if (!match)
return null;
const [, owner, repo, branch = 'main', path = ''] = match;
logger.debug(`Parsed Bitbucket URL: owner=${owner}, repo=${repo}, branch=${branch}, path=${path}`);
return {
type: SourceType.BITBUCKET_REPO,
owner,
repo,
branch,
path,
originalSource: url
};
}
/**
* Parse a generic Git URL into its components
*/
function parseGenericGitUrl(url) {
// Git URL formats:
// https://example.com/repo.git
// git@example.com:owner/repo.git
const gitRegex = /(?:https?:\/\/|git@)([\w.-]+)(?::|\/)([\w.-]+\/[\w.-]+)(?:\.git)?(?:\/?|#([\w.-]+))?/i;
const match = url.match(gitRegex);
if (!match)
return null;
const [, domain, repoPath, branch = 'main'] = match;
// Split owner/repo if possible
const pathParts = repoPath.split('/');
const repo = pathParts.pop() || '';
const owner = pathParts.join('/');
logger.debug(`Parsed Git URL: domain=${domain}, owner=${owner}, repo=${repo}, branch=${branch}`);
return {
type: SourceType.GIT_REPO,
owner,
repo,
branch,
originalSource: url
};
}
/**
* Parse an HTTP URL
*/
function parseHttpUrl(url) {
if (url.startsWith('http://') || url.startsWith('https://')) {
logger.debug(`Parsed HTTP URL: ${url}`);
return {
type: SourceType.HTTP_URL,
url,
originalSource: url
};
}
return null;
}
/**
* Parse a local path
*/
function parseLocalPath(source) {
// Check if it's an absolute path or a relative path that exists
if (path.isAbsolute(source) || fs.existsSync(path.resolve(process.cwd(), source))) {
const resolvedPath = path.resolve(process.cwd(), source);
logger.debug(`Parsed local path: ${resolvedPath}`);
return {
type: SourceType.LOCAL_PATH,
localPath: resolvedPath,
originalSource: source
};
}
return null;
}
/**
* Resolve a GitHub URL to a local directory
*/
async function resolveGitHubSource(url, options) {
// Parse GitHub URL
let owner, repo, ref;
if (url.startsWith('https://github.com/')) {
const parts = url.replace('https://github.com/', '').split('/');
owner = parts[0];
repo = parts[1];
ref = parts[3] || 'main';
}
else if (url.startsWith('git@github.com:')) {
const parts = url.replace('git@github.com:', '').split('/');
owner = parts[0];
repo = parts[1].replace('.git', '');
ref = 'main';
}
else {
throw new Error(`Invalid GitHub URL: ${url}`);
}
// Check if token is provided
if (!options.token && !process.env.GITHUB_TOKEN) {
throw new Error('GitHub token is required for accessing GitHub repositories');
}
// Create cache directory if it doesn't exist
const cacheDir = options.cache || path.join(os.tmpdir(), 'remcode-cache');
if (!fs.existsSync(cacheDir)) {
fs.mkdirSync(cacheDir, { recursive: true });
}
// Create a unique directory name for this repository
const repoDir = path.join(cacheDir, `${owner}-${repo}-${Date.now()}`);
// Clone the repository
try {
const token = options.token || process.env.GITHUB_TOKEN;
const cloneUrl = `https://${token}@github.com/${owner}/${repo}.git`;
await execAsync(`git clone --depth 1 ${cloneUrl} ${repoDir}`);
if (ref && ref !== 'main' && ref !== 'master') {
await execAsync(`cd ${repoDir} && git fetch --depth 1 origin ${ref} && git checkout ${ref}`);
}
}
catch (error) {
throw new Error(`Failed to clone repository: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
return {
type: 'github',
name: repo,
path: repoDir,
owner,
repo,
ref
};
}
/**
* Resolve a local path to an absolute path
*/
function resolveLocalSource(source) {
const absolutePath = path.resolve(process.cwd(), source);
if (!fs.existsSync(absolutePath)) {
throw new Error(`Local path does not exist: ${absolutePath}`);
}
if (!fs.statSync(absolutePath).isDirectory()) {
throw new Error(`Local path is not a directory: ${absolutePath}`);
}
return {
type: 'local',
name: path.basename(absolutePath),
path: absolutePath
};
}
/**
* Detect programming languages used in a directory
*/
async function detectLanguages(directory) {
const fileStats = {};
const languages = {};
// Map file extensions to languages
const extensionMap = {
'.py': 'python',
'.js': 'javascript',
'.ts': 'typescript',
'.jsx': 'javascript',
'.tsx': 'typescript',
'.java': 'java',
'.c': 'c',
'.cpp': 'cpp',
'.h': 'c',
'.hpp': 'cpp',
'.rb': 'ruby',
'.go': 'go',
'.php': 'php',
'.cs': 'csharp',
'.swift': 'swift',
'.kt': 'kotlin',
'.rs': 'rust',
};
// Count files by extension
function countFiles(dir) {
const files = fs.readdirSync(dir);
for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
countFiles(filePath);
}
else if (stat.isFile()) {
const ext = path.extname(file).toLowerCase();
fileStats[ext] = (fileStats[ext] || 0) + 1;
}
}
}
// Count files in the directory
countFiles(directory);
// Convert file extensions to languages
for (const [ext, count] of Object.entries(fileStats)) {
const language = extensionMap[ext];
if (language) {
languages[language] = (languages[language] || 0) + count;
}
}
return languages;
}
;