Spaces:
Sleeping
Sleeping
File size: 3,452 Bytes
a5d1c1b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | import { config } from '../config.js';
import { FileInfo, FilesResponse } from '../types/contract.js';
import { isBinaryOrIgnoredFile } from '../utils/binary-filter.js';
import { logger } from '../utils/logger.js';
import { resolveMimeType } from '../utils/mime.js';
import { githubService } from './github-service.js';
import { treeCache } from './tree-cache.js';
export interface ResolvedTreeResult extends FilesResponse {
pathMap: Map<string, string>; // lowercase -> exact path
}
export class ResolverService {
async getOrDiscoverFiles(
owner: string,
repo: string,
branch?: string,
fresh = false,
prefix?: string
): Promise<ResolvedTreeResult> {
const startTime = performance.now();
// 1. Resolve default branch if not provided
let targetBranch = branch;
if (!targetBranch) {
const meta = await githubService.getRepoMetadata(owner, repo);
targetBranch = meta.defaultBranch;
}
// 2. Check in-memory cache if fresh is false
if (!fresh) {
const cached = treeCache.get(owner, repo, targetBranch);
if (cached) {
let files = cached.files;
if (prefix) {
const normalizedPrefix = prefix.replace(/^\/+/, '');
files = files.filter((f) => f.path.startsWith(normalizedPrefix));
}
return {
repository: `${owner}/${repo}`,
branch: targetBranch,
totalFiles: files.length,
files,
pathMap: cached.pathMap,
};
}
}
// 3. Live discovery from GitHub
const rawTree = await githubService.getGitTree(owner, repo, targetBranch);
const pathMap = new Map<string, string>();
const discoveredFiles: FileInfo[] = [];
let skippedCount = 0;
for (const item of rawTree) {
if (item.type !== 'blob') {
continue;
}
if (isBinaryOrIgnoredFile(item.path)) {
skippedCount++;
continue;
}
const mimeType = resolveMimeType(item.path);
const fileInfo: FileInfo = {
path: item.path,
type: 'file',
sizeBytes: item.size || 0,
mimeType,
};
discoveredFiles.push(fileInfo);
pathMap.set(item.path.toLowerCase(), item.path);
}
// 4. Save to cache
treeCache.set(owner, repo, targetBranch, discoveredFiles, pathMap, config.cacheTtlMs);
const durationMs = Math.round(performance.now() - startTime);
logger.tree(discoveredFiles.length, durationMs, skippedCount);
// 5. Apply prefix filter if requested
let files = discoveredFiles;
if (prefix) {
const normalizedPrefix = prefix.replace(/^\/+/, '');
files = files.filter((f) => f.path.startsWith(normalizedPrefix));
}
return {
repository: `${owner}/${repo}`,
branch: targetBranch,
totalFiles: files.length,
files,
pathMap,
};
}
async resolveFilePath(
owner: string,
repo: string,
branch: string,
requestedPath: string
): Promise<{ exactPath: string; branch: string }> {
const cleanPath = requestedPath.replace(/^\/+/, '');
const tree = await this.getOrDiscoverFiles(owner, repo, branch, false);
// Exact or case-insensitive match
const lowercaseKey = cleanPath.toLowerCase();
const matchedPath = tree.pathMap.get(lowercaseKey);
return {
exactPath: matchedPath || cleanPath,
branch: tree.branch,
};
}
}
export const resolverService = new ResolverService();
|