Skip to main content
Glama
MIT License
27,120
19,789
  • Linux
  • Apple
packAction.ts•5.83 kB
import type { Context } from 'hono'; import { isValidRemoteValue } from 'repomix'; import { z } from 'zod'; import { processZipFile } from '../domains/pack/processZipFile.js'; import { processRemoteRepo } from '../domains/pack/remoteRepo.js'; import { FILE_SIZE_LIMITS } from '../domains/pack/utils/fileUtils.js'; import { sanitizePattern } from '../domains/pack/utils/validation.js'; import type { PackResult } from '../types.js'; import { getClientInfo } from '../utils/clientInfo.js'; import { createErrorResponse } from '../utils/http.js'; import { logError, logInfo } from '../utils/logger.js'; import { calculateMemoryDiff, getMemoryUsage } from '../utils/memory.js'; import { formatLatencyForDisplay } from '../utils/time.js'; import { validateRequest } from '../utils/validation.js'; const packRequestSchema = z .object({ url: z .string() .min(1, 'Repository URL is required') .max(200, 'Repository URL is too long') .transform((val) => val.trim()) .refine((val) => isValidRemoteValue(val), { message: 'Invalid repository URL' }) .optional(), file: z .custom<File>() .refine((file) => file instanceof File, { message: 'Invalid file format', }) .refine((file) => file.type === 'application/zip' || file.name.endsWith('.zip'), { message: 'Only ZIP files are allowed', }) .refine((file) => file.size <= FILE_SIZE_LIMITS.MAX_ZIP_SIZE, { // 10MB limit message: 'File size must be less than 10MB', }) .optional(), format: z.enum(['xml', 'markdown', 'plain']), options: z .object({ removeComments: z.boolean().optional(), removeEmptyLines: z.boolean().optional(), showLineNumbers: z.boolean().optional(), fileSummary: z.boolean().optional(), directoryStructure: z.boolean().optional(), includePatterns: z .string() .max(100_000, 'Include patterns too long') .optional() .transform((val) => val?.trim()), ignorePatterns: z .string() // Regular expression to validate ignore patterns // Allowed characters: alphanumeric, *, ?, /, -, _, ., !, (, ), space, comma .regex(/^[a-zA-Z0-9*?/\-_.,!()\s]*$/, 'Invalid characters in ignore patterns') .max(1000, 'Ignore patterns too long') .optional() .transform((val) => val?.trim()), outputParsable: z.boolean().optional(), compress: z.boolean().optional(), }) .strict(), }) .strict() .refine((data) => data.url || data.file, { message: 'Either URL or file must be provided', }) .refine((data) => !(data.url && data.file), { message: 'Cannot provide both URL and file', }); export const packAction = async (c: Context) => { try { const formData = await c.req.formData(); const requestId = c.get('requestId'); // Get client information for logging const clientInfo = getClientInfo(c); // Get form data const format = formData.get('format') as 'xml' | 'markdown' | 'plain'; const optionsRaw = formData.get('options') as string | null; let options: unknown = {}; try { options = optionsRaw ? JSON.parse(optionsRaw) : {}; } catch { return c.json(createErrorResponse('Invalid JSON in options', requestId), 400); } const file = formData.get('file') as File | null; const url = formData.get('url') as string | null; // Validate and sanitize request data const validatedData = validateRequest(packRequestSchema, { url: url || undefined, file: file || undefined, format, options, }); const sanitizedIncludePatterns = sanitizePattern(validatedData.options.includePatterns); const sanitizedIgnorePatterns = sanitizePattern(validatedData.options.ignorePatterns); // Create sanitized options const sanitizedOptions = { ...validatedData.options, includePatterns: sanitizedIncludePatterns, ignorePatterns: sanitizedIgnorePatterns, }; const startTime = Date.now(); const beforeMemory = getMemoryUsage(); // Process file or repository let result: PackResult; if (validatedData.file) { result = await processZipFile(validatedData.file, validatedData.format, sanitizedOptions); } else { // Zod schema guarantees that url is present when file is not result = await processRemoteRepo(validatedData.url as string, validatedData.format, sanitizedOptions); } // Log operation result with memory usage const afterMemory = getMemoryUsage(); const memoryDiff = calculateMemoryDiff(beforeMemory, afterMemory); logInfo('Pack operation completed', { requestId, format: validatedData.format, repository: result.metadata.repository, duration: formatLatencyForDisplay(startTime), inputType: validatedData.file ? 'file' : validatedData.url ? 'url' : 'unknown', clientInfo: { ip: clientInfo.ip, userAgent: clientInfo.userAgent, }, memory: { before: beforeMemory, after: afterMemory, diff: memoryDiff, }, metrics: { totalFiles: result.metadata.summary?.totalFiles, totalCharacters: result.metadata.summary?.totalCharacters, totalTokens: result.metadata.summary?.totalTokens, }, }); return c.json(result); } catch (error) { // Handle errors logError('Pack operation failed', error instanceof Error ? error : new Error('Unknown error'), { requestId: c.get('requestId'), }); const { handlePackError } = await import('../utils/errorHandler.js'); const appError = handlePackError(error); return c.json(createErrorResponse(appError.message, c.get('requestId')), appError.statusCode); } };

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/yamadashy/repomix'

If you have feedback or need assistance with the MCP directory API, please join our Discord server