UNPKG

recoder-code

Version:

🚀 AI-powered development platform - Chat with 32+ models, build projects, automate workflows. Free models included!

561 lines • 24.6 kB
"use strict"; /** * Package Routes * NPM-compatible package registry endpoints */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = require("express"); const container_1 = require("../container"); const PackageService_1 = require("../services/PackageService"); const AuthService_1 = require("../services/AuthService"); const ValidationService_1 = require("../services/ValidationService"); const SecurityService_1 = require("../services/SecurityService"); const StorageService_1 = require("../services/StorageService"); const AnalyticsService_1 = require("../services/AnalyticsService"); const RateLimitService_1 = require("../services/RateLimitService"); const Package_1 = require("../entities/Package"); const ApiKey_1 = require("../entities/ApiKey"); const multer_1 = __importDefault(require("multer")); const router = (0, express_1.Router)(); const upload = (0, multer_1.default)({ storage: multer_1.default.memoryStorage(), limits: { fileSize: 50 * 1024 * 1024 } }); // 50MB // Health check endpoint router.get('/-/health', async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); // Basic health checks const checks = { database: false, storage: false, cache: false, timestamp: new Date().toISOString() }; try { // Test database connection await packageService.getPackageCount(); checks.database = true; } catch (e) { console.warn('Database health check failed:', e); } try { // Test storage connection const storageService = container_1.Container.get(StorageService_1.StorageService); await storageService.testConnection(); checks.storage = true; } catch (e) { console.warn('Storage health check failed:', e); } const healthy = checks.database && checks.storage; res.status(healthy ? 200 : 503).json({ status: healthy ? 'healthy' : 'unhealthy', checks, version: process.env.npm_package_version || '1.0.0' }); } catch (error) { res.status(503).json({ status: 'error', error: 'Health check failed', timestamp: new Date().toISOString() }); } }); // Helper to get typed user from request const getUser = (req) => getUser(req); // Middleware const authenticate = async (req, res, next) => { try { const authService = container_1.Container.get(AuthService_1.AuthService); const rateLimitService = container_1.Container.get(RateLimitService_1.RateLimitService); const token = req.headers.authorization?.replace('Bearer ', '') || req.headers.authorization?.replace('token ', '') || req.query.token; if (!token) { return res.status(401).json({ error: 'Authentication required' }); } const result = await authService.validateToken(token); if (!result.valid) { return res.status(401).json({ error: 'Invalid authentication token' }); } // Check rate limits const rateLimitResult = await rateLimitService.getUserRateLimit(result.user.id); if (!rateLimitResult.allowed) { return res.status(429).json({ error: 'Rate limit exceeded' }); } req.user = result.user; req.apiKey = result.apiKey; next(); } catch (error) { res.status(500).json({ error: 'Authentication failed' }); } }; const requireScope = (scope) => { return (req, res, next) => { if (!req.apiKey?.hasScope(scope)) { return res.status(403).json({ error: `Insufficient permissions: ${scope} required` }); } next(); }; }; // NPM Registry Compatibility Routes // Search packages (CLI compatibility endpoint) - MUST be before /:name route router.get('/search', async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const { q = '' } = req.query; const searchOptions = { query: q, limit: 20, offset: 0, sortBy: 'relevance', sortOrder: 'desc' }; const results = await packageService.searchPackages(searchOptions); // CLI expects results array const response = { results: results.packages.map(pkg => ({ name: pkg.name, version: pkg.latest_version, description: pkg.description, rating: pkg.quality_metrics?.code_quality || 4.5, downloads: pkg.quality_metrics?.popularity || 1000 })) }; res.json(response); } catch (error) { console.error('Plugin search error:', error); res.status(500).json({ error: 'Search failed', results: [] }); } }); // Get package metadata (NPM format) router.get('/:name', async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService); const packageName = decodeURIComponent(req.params.name); const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check visibility if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) { return res.status(404).json({ error: 'Package not found' }); } // Track package view await analyticsService.trackPackageView(pkg.id, getUser(req)?.id); // Return NPM-compatible format const npmData = pkg.toNpmFormat(); // Add versions const versions = await packageService.getPackageVersions(pkg.id); npmData.versions = {}; npmData.time = { created: pkg.created_at.toISOString(), modified: pkg.updated_at.toISOString() }; for (const version of versions) { npmData.versions[version.version] = version.toNpmFormat(); npmData.time[version.version] = version.published_at?.toISOString() || version.created_at.toISOString(); } res.json(npmData); } catch (error) { console.error('Error getting package:', error); res.status(500).json({ error: 'Failed to get package', code: 'PACKAGE_FETCH_ERROR', details: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // Get specific package version router.get('/:name/:version', async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService); const packageName = decodeURIComponent(req.params.name); const version = req.params.version; const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check visibility if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) { return res.status(404).json({ error: 'Package not found' }); } const packageVersion = await packageService.getPackageVersion(pkg.id, version); if (!packageVersion) { return res.status(404).json({ error: 'Version not found' }); } // Track version view await analyticsService.trackVersionView(packageVersion.id, getUser(req)?.id, req.ip); res.json(packageVersion.toNpmFormat()); } catch (error) { console.error('Error getting package version:', error); res.status(500).json({ error: 'Failed to get package version', code: 'VERSION_FETCH_ERROR', details: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // Download package tarball router.get('/:name/-/:filename', async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const storageService = container_1.Container.get(StorageService_1.StorageService); const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService); const packageName = decodeURIComponent(req.params.name); const filename = req.params.filename; // Extract version from filename (format: package-version.tgz) const versionMatch = filename.match(/^.*?-(\d+\.\d+\.\d+.*?)\.tgz$/); if (!versionMatch) { return res.status(400).json({ error: 'Invalid filename format' }); } const version = versionMatch[1]; const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check visibility if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) { return res.status(404).json({ error: 'Package not found' }); } const packageVersion = await packageService.getPackageVersion(pkg.id, version); if (!packageVersion) { return res.status(404).json({ error: 'Version not found' }); } // Download from storage const downloadResult = await storageService.downloadPackage(packageName, version); if (!downloadResult.success) { return res.status(404).json({ error: 'Package file not found' }); } // Track download await analyticsService.trackDownload(pkg.id, packageVersion.version, getUser(req)?.id); // Update download counts await packageService.incrementDownloadCount(pkg.id, packageVersion.id); // Set headers if (!downloadResult.data) { return res.status(404).json({ error: 'Package file not found' }); } res.set({ 'Content-Type': 'application/octet-stream', 'Content-Length': downloadResult.data.length.toString(), 'Content-Disposition': `attachment; filename="${filename}"`, 'Cache-Control': 'public, max-age=31536000', 'ETag': downloadResult.metadata?.etag }); res.send(downloadResult.data); } catch (error) { console.error('Error downloading package:', error); res.status(500).json({ error: 'Failed to download package', code: 'DOWNLOAD_ERROR', details: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // Publish package router.put('/:name', authenticate, requireScope(ApiKey_1.ApiKeyScope.PUBLISH), upload.single('package'), async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const validationService = container_1.Container.get(ValidationService_1.ValidationService); const securityService = container_1.Container.get(SecurityService_1.SecurityService); const storageService = container_1.Container.get(StorageService_1.StorageService); const packageName = decodeURIComponent(req.params.name); // Handle both file upload and JSON body let packageBuffer; let packageJson; if (req.file) { // Multipart upload packageBuffer = req.file.buffer; } else if (req.body._attachments) { // NPM publish format const attachments = req.body._attachments; const attachmentKey = Object.keys(attachments)[0]; if (!attachmentKey) { return res.status(400).json({ error: 'No package attachment found' }); } const attachment = attachments[attachmentKey]; packageBuffer = Buffer.from(attachment.data, 'base64'); packageJson = req.body.versions?.[Object.keys(req.body.versions)[0]]; } else { return res.status(400).json({ error: 'No package data provided' }); } if (!packageBuffer) { return res.status(400).json({ error: 'Empty package data' }); } // Validate package const validationResult = await validationService.validatePackage(packageBuffer, packageName); if (!validationResult.valid) { return res.status(400).json({ error: 'Package validation failed', errors: validationResult.errors, warnings: validationResult.warnings }); } const version = packageJson?.version || (validationResult.size_analysis && validationResult.size_analysis['package.json']?.version); if (!version) { return res.status(400).json({ error: 'Package version not found' }); } // Check if package/version already exists const existingPackage = await packageService.getPackage(packageName); if (existingPackage) { const existingVersion = await packageService.getPackageVersion(existingPackage.id, version); if (existingVersion) { return res.status(409).json({ error: 'Version already exists' }); } // Check permissions if (!getUser(req).canModifyPackage(existingPackage)) { return res.status(403).json({ error: 'Insufficient permissions' }); } } // Security scan const fakePackageVersion = { version, package: existingPackage || { name: packageName }, }; // Cast as any to satisfy the type, since only name/version are used in logging const securityResult = await securityService.scanPackage(packageBuffer, fakePackageVersion); if (securityResult.status === 'critical') { return res.status(400).json({ error: 'Security scan failed', details: securityResult.threats }); } // Upload to storage const uploadResult = await storageService.uploadPackage(packageName, version, packageBuffer, 'application/octet-stream'); if (!uploadResult.success) { return res.status(500).json({ error: 'Failed to store package' }); } // Create/update package and version const result = await packageService.publishPackage(packageJson || { name: packageName, version }, getUser(req), req.file?.buffer); if (!result.success) { return res.status(500).json({ error: result.errors?.[0] || 'Package publishing failed' }); } if (!result.package || !result.version) { return res.status(500).json({ error: 'Package publishing failed: missing package or version data' }); } res.json({ success: true, package: result.package.toApiFormat(), version: result.version.toApiFormat() }); } catch (error) { console.error('Error publishing package:', error); res.status(500).json({ error: 'Failed to publish package', code: 'PUBLISH_ERROR', details: process.env.NODE_ENV === 'development' ? error.message : undefined }); } }); // Unpublish package version router.delete('/:name/-/:version', authenticate, requireScope(ApiKey_1.ApiKeyScope.UNPUBLISH), async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const storageService = container_1.Container.get(StorageService_1.StorageService); const packageName = decodeURIComponent(req.params.name); const version = req.params.version; const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check permissions if (!getUser(req).canModifyPackage(pkg)) { return res.status(403).json({ error: 'Insufficient permissions' }); } const packageVersion = await packageService.getPackageVersion(pkg.id, version); if (!packageVersion) { return res.status(404).json({ error: 'Version not found' }); } // Delete from storage await storageService.deletePackage(packageName, version); // Remove version from database const result = await packageService.unpublishVersion(packageName, version, getUser(req)); if (!result.success) { return res.status(500).json({ error: result.error }); } res.json({ success: true, message: 'Version unpublished successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to unpublish version' }); } }); // Deprecate package version router.post('/:name/:version/deprecate', authenticate, requireScope(ApiKey_1.ApiKeyScope.DEPRECATE), async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const packageName = decodeURIComponent(req.params.name); const version = req.params.version; const { message } = req.body; const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check permissions if (!getUser(req).canModifyPackage(pkg)) { return res.status(403).json({ error: 'Insufficient permissions' }); } const result = await packageService.deprecateVersion(pkg.id, version, message); if (!result.success) { return res.status(500).json({ error: result.error }); } res.json({ success: true, message: 'Version deprecated successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to deprecate version' }); } }); // Search packages (NPM compatibility) router.get('/-/search', async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const { text = '', size = 20, from = 0, quality = 0.5, popularity = 0.5, maintenance = 0.5 } = req.query; const searchOptions = { query: text, limit: Math.min(parseInt(size) || 20, 100), offset: parseInt(from) || 0, sortBy: 'relevance', sortOrder: 'desc' }; const results = await packageService.searchPackages(searchOptions); // NPM search format const response = { objects: results.packages.map(pkg => ({ package: { name: pkg.name, scope: pkg.scope, version: pkg.latest_version, description: pkg.description, keywords: pkg.keywords || [], date: pkg.last_published?.toISOString(), links: { npm: `https://npmjs.com/package/${pkg.name}`, homepage: pkg.homepage, repository: pkg.repository?.url, bugs: pkg.bugs?.url }, author: pkg.author, publisher: pkg.maintainers?.[0], maintainers: pkg.maintainers || [] }, score: { final: pkg.quality_metrics?.code_quality || 0, detail: { quality: pkg.quality_metrics?.code_quality || 0, popularity: pkg.quality_metrics?.popularity || 0, maintenance: pkg.quality_metrics?.maintenance || 0 } }, searchScore: 1.0 })), total: results.total, time: new Date().toISOString() }; res.json(response); } catch (error) { res.status(500).json({ error: 'Search failed' }); } }); // Get package statistics router.get('/:name/-/stats', async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService); const packageName = decodeURIComponent(req.params.name); const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check visibility if (pkg.visibility === Package_1.PackageVisibility.PRIVATE && (!getUser(req) || !getUser(req).canAccessPackage(pkg))) { return res.status(404).json({ error: 'Package not found' }); } const stats = await analyticsService.getPackageStats(pkg.id); res.json({ package: pkg.name, downloads: stats.downloads, versions: stats.versions, dependents: stats.dependents, last_updated: pkg.updated_at }); } catch (error) { res.status(500).json({ error: 'Failed to get package stats' }); } }); // Add package collaborator router.put('/:name/-/collaborators/:username', authenticate, async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const packageName = decodeURIComponent(req.params.name); const username = req.params.username; const { permissions = ['read'] } = req.body; const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check permissions (only owner can add collaborators) if (pkg.owner_id !== getUser(req).id && !getUser(req).is_admin) { return res.status(403).json({ error: 'Only package owner can add collaborators' }); } const result = await packageService.addCollaborator(pkg.id, username, permissions); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Collaborator added successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to add collaborator' }); } }); // Remove package collaborator router.delete('/:name/-/collaborators/:username', authenticate, async (req, res) => { try { const packageService = container_1.Container.get(PackageService_1.PackageService); const packageName = decodeURIComponent(req.params.name); const username = req.params.username; const pkg = await packageService.getPackage(packageName); if (!pkg) { return res.status(404).json({ error: 'Package not found' }); } // Check permissions if (pkg.owner_id !== getUser(req).id && !getUser(req).is_admin) { return res.status(403).json({ error: 'Only package owner can remove collaborators' }); } const result = await packageService.removeCollaborator(pkg.name, username, getUser(req)); if (!result.success) { return res.status(400).json({ error: result.error }); } res.json({ success: true, message: 'Collaborator removed successfully' }); } catch (error) { res.status(500).json({ error: 'Failed to remove collaborator' }); } }); // Get package download counts router.get('/:name/-/downloads', async (req, res) => { try { const analyticsService = container_1.Container.get(AnalyticsService_1.AnalyticsService); const packageName = decodeURIComponent(req.params.name); const { period = 'week', version } = req.query; const downloads = await analyticsService.getDownloadStats(packageName, period); res.json(downloads); } catch (error) { res.status(500).json({ error: 'Failed to get download stats' }); } }); exports.default = router; //# sourceMappingURL=packages.js.map