UNPKG

recoder-code

Version:

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

547 lines • 24.2 kB
"use strict"; /** * Package Service - Handles plugin package operations */ 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __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.PackageService = void 0; const Package_1 = require("../entities/Package"); const PackageVersion_1 = require("../entities/PackageVersion"); const User_1 = require("../entities/User"); //import { PackageStats } from '../entities/PackageStats'; const semver = __importStar(require("semver")); const crypto = __importStar(require("crypto")); const SecurityService_1 = require("./SecurityService"); const StorageService_1 = require("./StorageService"); const ValidationService_1 = require("./ValidationService"); // import { NotificationService } from './NotificationService'; // import { QueueService } from './QueueService'; const config_1 = __importDefault(require("../config")); const database_1 = require("../database"); // Simple logger for now const logger = { info: (msg, ...args) => console.log(`[INFO] ${msg}`, ...args), error: (msg, ...args) => console.error(`[ERROR] ${msg}`, ...args), warn: (msg, ...args) => console.warn(`[WARN] ${msg}`, ...args) }; class PackageService { constructor() { this.packageRepo = database_1.AppDataSource.getRepository(Package_1.Package); this.versionRepo = database_1.AppDataSource.getRepository(PackageVersion_1.PackageVersion); this.userRepo = database_1.AppDataSource.getRepository(User_1.User); this.securityService = new SecurityService_1.SecurityService(); this.storageService = new StorageService_1.StorageService(); this.validationService = new ValidationService_1.ValidationService(config_1.default); } async publishPackage(packageData, user, tarballBuffer) { const errors = []; try { logger.info(`Publishing package: ${packageData.name}@${packageData.version} by ${user.username}`); // Basic package data validation if (!packageData.name || !packageData.version) { return { success: false, errors: ['Package name and version are required'] }; } // Check if package exists let existingPackage = await this.packageRepo.findOne({ where: { name: packageData.name }, relations: ['versions', 'maintainers'] }); // If package doesn't exist, create it if (!existingPackage) { existingPackage = this.packageRepo.create({ name: packageData.name, description: packageData.description || '', keywords: packageData.keywords || [], author: { name: packageData.author?.name || user.username, email: packageData.author?.email || user.email }, license: packageData.license || 'MIT', homepage: packageData.homepage, repository: packageData.repository, bugs: packageData.bugs, categories: packageData.categories || [], owner: user, created_at: new Date(), updated_at: new Date() }); await this.packageRepo.save(existingPackage); logger.info(`Created new package: ${packageData.name}`); } else { // Check if user has permission to publish const isOwner = existingPackage.owner?.id === user.id; if (!isOwner && !user.is_admin) { return { success: false, errors: ['You do not have permission to publish to this package'] }; } // Update package metadata existingPackage.description = packageData.description || existingPackage.description; existingPackage.keywords = packageData.keywords || existingPackage.keywords; existingPackage.license = packageData.license || existingPackage.license; existingPackage.homepage = packageData.homepage || existingPackage.homepage; existingPackage.repository = packageData.repository || existingPackage.repository; existingPackage.bugs = packageData.bugs || existingPackage.bugs; existingPackage.categories = packageData.categories || existingPackage.categories; existingPackage.updated_at = new Date(); await this.packageRepo.save(existingPackage); } // Check if version already exists const existingVersion = await this.versionRepo.findOne({ where: { package: { id: existingPackage.id }, version: packageData.version } }); if (existingVersion) { return { success: false, errors: [`Version ${packageData.version} already exists`] }; } // Process tarball let tarballPath; let shasum; let integrity; let size = 0; if (tarballBuffer) { // Basic tarball validation if (tarballBuffer.length === 0) { return { success: false, errors: ['Empty tarball'] }; } if (tarballBuffer.length > 50 * 1024 * 1024) { // 50MB limit return { success: false, errors: ['Tarball too large'] }; } // Calculate checksums shasum = crypto.createHash('sha1').update(tarballBuffer).digest('hex'); integrity = `sha512-${crypto.createHash('sha512').update(tarballBuffer).digest('base64')}`; size = tarballBuffer.length; // Store tarball (simplified) tarballPath = `tarballs/${existingPackage.name}-${packageData.version}.tgz`; } // Create package version const packageVersion = this.versionRepo.create({ package: existingPackage, package_id: existingPackage.id, version: packageData.version, description: packageData.description || '', main: packageData.main || 'index.js', engines: packageData.engines || {}, dependencies: packageData.dependencies || {}, dev_dependencies: packageData.devDependencies || {}, peer_dependencies: packageData.peerDependencies || {}, dist: { tarball: tarballPath || '', shasum: shasum || '', integrity: integrity, size: size }, published_by: user, published_by_id: user.id, published_at: new Date() }); await this.versionRepo.save(packageVersion); // Update package stats await this.updatePackageStats(existingPackage.id, 'publish'); // Background tasks would be queued here // await this.queueService.addJob('package-published', {...}); // Notifications would be sent here // await this.notificationService.notifyPackagePublished(...); logger.info(`Successfully published: ${packageData.name}@${packageData.version}`); return { success: true, package: existingPackage, version: packageVersion }; } catch (error) { logger.error(`Failed to publish package: ${error?.message || String(error)}`, error); return { success: false, errors: [`Internal error: ${error?.message || String(error)}`] }; } } async getPackage(name) { return this.packageRepo.findOne({ where: { name }, relations: ['versions', 'owner'] }); } async getPackageVersion(name, version) { return this.versionRepo.findOne({ where: { package: { name }, version }, relations: ['package', 'publishedBy'] }); } async searchPackages(options) { const { query, category, keywords, author, license, minDownloads = 0, sortBy = 'relevance', sortOrder = 'desc', limit = 20, offset = 0 } = options; const queryBuilder = this.packageRepo.createQueryBuilder('package') .leftJoinAndSelect('package.versions', 'versions') .leftJoinAndSelect('package.owner', 'owner'); // Text search if (query) { queryBuilder.andWhere('(package.name ILIKE :query OR package.description ILIKE :query OR package.keywords::text ILIKE :query)', { query: `%${query}%` }); } // Category filter if (category) { queryBuilder.andWhere(':category = ANY(package.categories)', { category }); } // Keywords filter if (keywords && keywords.length > 0) { queryBuilder.andWhere('package.keywords && :keywords', { keywords }); } // Author filter if (author) { queryBuilder.andWhere('package.author ILIKE :author', { author: `%${author}%` }); } // License filter if (license) { queryBuilder.andWhere('package.license = :license', { license }); } // Minimum downloads filter if (minDownloads > 0) { queryBuilder.andWhere('package.download_count >= :minDownloads', { minDownloads }); } // Sorting switch (sortBy) { case 'downloads': queryBuilder.orderBy('package.download_count', sortOrder.toUpperCase()); break; case 'updated': queryBuilder.orderBy('package.updated_at', sortOrder.toUpperCase()); break; case 'created': queryBuilder.orderBy('package.created_at', sortOrder.toUpperCase()); break; default: // Relevance (default) if (query) { queryBuilder.orderBy('package.download_count', 'DESC'); } else { queryBuilder.orderBy('package.download_count', 'DESC'); } } // Pagination queryBuilder.skip(offset).take(limit); const [packages, total] = await queryBuilder.getManyAndCount(); return { packages, total, page: Math.floor(offset / limit) + 1, limit }; } async downloadPackage(name, version) { try { const packageVersion = await this.getPackageVersion(name, version); if (!packageVersion) { return { success: false, error: 'Package version not found' }; } if (!packageVersion.dist?.tarball) { return { success: false, error: 'Tarball not available' }; } // Update download stats await this.updatePackageStats(packageVersion.package.id, 'download'); // Get download stream (simplified) const stream = undefined; // Would implement actual stream here const filename = `${name}-${version}.tgz`; return { success: true, stream, filename, contentType: 'application/octet-stream', size: packageVersion.dist?.size }; } catch (error) { logger.error(`Failed to download package: ${error?.message || String(error)}`, error); return { success: false, error: 'Download failed' }; } } async unpublishPackage(name, version, user) { try { const pkg = await this.getPackage(name); if (!pkg) { return { success: false, error: 'Package not found' }; } // Check permissions const isMaintainer = pkg.maintainers.some(m => m.name === user.username || m.email === user.email); if (!isMaintainer && !user.is_admin) { return { success: false, error: 'Permission denied' }; } const packageVersion = await this.getPackageVersion(name, version); if (!packageVersion) { return { success: false, error: 'Version not found' }; } // Check if this is the only version const versionCount = await this.versionRepo.count({ where: { package: { id: pkg.id } } }); if (versionCount === 1) { // Delete entire package await this.deletePackage(name, user); } else { // Delete version await this.versionRepo.remove(packageVersion); // Delete tarball if (packageVersion.dist?.tarball) { // await this.storageService.deleteTarball(packageVersion.dist?.tarball); } // Update package timestamp pkg.updated_at = new Date(); await this.packageRepo.save(pkg); } logger.info(`Unpublished: ${name}@${version} by ${user.username}`); return { success: true }; } catch (error) { logger.error(`Failed to unpublish package: ${error?.message || String(error)}`, error); return { success: false, error: 'Unpublish failed' }; } } async deletePackage(name, user) { try { const pkg = await this.getPackage(name); if (!pkg) { return { success: false, error: 'Package not found' }; } // Check permissions const isMaintainer = pkg.maintainers.some(m => m.name === user.username || m.email === user.email); if (!isMaintainer && !user.is_admin) { return { success: false, error: 'Permission denied' }; } // Delete all tarballs for (const version of pkg.versions) { if (version.dist?.tarball) { // await this.storageService.deleteTarball(version.dist?.tarball); } } // Delete package and all related data (cascade) await this.packageRepo.remove(pkg); logger.info(`Deleted package: ${name} by ${user.username}`); return { success: true }; } catch (error) { logger.error(`Failed to delete package: ${error?.message || String(error)}`, error); return { success: false, error: 'Delete failed' }; } } async getPackageStats(name) { const pkg = await this.getPackage(name); if (!pkg || !pkg.stats) return null; return { packageName: pkg.name, totalDownloads: pkg.download_count || 0, monthlyDownloads: pkg.stats.monthly_downloads || 0, weeklyDownloads: pkg.stats.weekly_downloads || 0, lastUpdateDate: pkg.updated_at }; } async getPopularPackages(limit = 10) { return this.packageRepo.createQueryBuilder('package') .leftJoinAndSelect('package.versions', 'versions') .leftJoinAndSelect('package.owner', 'owner') .orderBy('package.download_count', 'DESC') .take(limit) .getMany(); } async getRecentlyUpdated(limit = 10) { return this.packageRepo.find({ relations: ['versions', 'owner'], order: { updated_at: 'DESC' }, take: limit }); } async addMaintainer(packageName, username, currentUser) { try { const pkg = await this.getPackage(packageName); if (!pkg) { return { success: false, error: 'Package not found' }; } // Check permissions const isMaintainer = pkg.maintainers.some(m => m.name === currentUser.username || m.email === currentUser.email); if (!isMaintainer && !currentUser.is_admin) { return { success: false, error: 'Permission denied' }; } const newMaintainer = await this.userRepo.findOne({ where: { username } }); if (!newMaintainer) { return { success: false, error: 'User not found' }; } // Check if already a maintainer const isAlreadyMaintainer = pkg.maintainers.some(m => m.name === newMaintainer.username || m.email === newMaintainer.email); if (isAlreadyMaintainer) { return { success: false, error: 'User is already a maintainer' }; } pkg.maintainers.push({ name: newMaintainer.username, email: newMaintainer.email }); await this.packageRepo.save(pkg); logger.info(`Added maintainer ${username} to package ${packageName}`); return { success: true }; } catch (error) { logger.error(`Failed to add maintainer: ${error?.message || String(error)}`, error); return { success: false, error: 'Failed to add maintainer' }; } } async removeMaintainer(packageName, username, currentUser) { try { const pkg = await this.getPackage(packageName); if (!pkg) { return { success: false, error: 'Package not found' }; } // Check permissions const isMaintainer = pkg.maintainers.some(m => m.name === currentUser.username || m.email === currentUser.email); if (!isMaintainer && !currentUser.is_admin) { return { success: false, error: 'Permission denied' }; } // Can't remove the last maintainer if (pkg.maintainers.length === 1) { return { success: false, error: 'Cannot remove the last maintainer' }; } const maintainerToRemove = pkg.maintainers.find(m => m.name === username); if (!maintainerToRemove) { return { success: false, error: 'User is not a maintainer' }; } pkg.maintainers = pkg.maintainers.filter(m => m.name !== maintainerToRemove.name); await this.packageRepo.save(pkg); logger.info(`Removed maintainer ${username} from package ${packageName}`); return { success: true }; } catch (error) { logger.error(`Failed to remove maintainer: ${error?.message || String(error)}`, error); return { success: false, error: 'Failed to remove maintainer' }; } } async updatePackageStats(packageId, action) { const pkg = await this.packageRepo.findOne({ where: { id: packageId } }); if (!pkg) return; switch (action) { case 'download': pkg.download_count += 1; if (!pkg.stats) { pkg.stats = { weekly_downloads: 0, monthly_downloads: 0, yearly_downloads: 0, dependents: 0, dependencies: 0 }; } pkg.stats.weekly_downloads += 1; pkg.stats.monthly_downloads += 1; pkg.stats.yearly_downloads += 1; break; case 'publish': pkg.version_count += 1; pkg.last_published = new Date(); break; } await this.packageRepo.save(pkg); } async getLatestVersion(packageName) { const versions = await this.versionRepo.find({ where: { package: { name: packageName } }, select: ['version'] }); if (versions.length === 0) return null; const sortedVersions = versions .map(v => v.version) .sort((a, b) => semver.rcompare(a, b)); return sortedVersions[0]; } async getVersions(packageName) { const versions = await this.versionRepo.find({ where: { package: { name: packageName } }, select: ['version'], order: { published_at: 'DESC' } }); return versions.map(v => v.version); } async getPackageVersions(packageName) { return await this.versionRepo.find({ where: { package: { name: packageName } }, order: { published_at: 'DESC' } }); } async incrementDownloadCount(packageName, version) { try { const packageVersion = await this.getPackageVersion(packageName, version); if (packageVersion) { packageVersion.download_count = (packageVersion.download_count || 0) + 1; await this.versionRepo.save(packageVersion); } } catch (error) { logger.error(`Failed to increment download count for ${packageName}@${version}:`, error); } } async unpublishVersion(packageName, version, user) { return this.unpublishPackage(packageName, version, user); } async deprecateVersion(packageName, version, user, message) { try { const packageVersion = await this.getPackageVersion(packageName, version); if (!packageVersion) { return { success: false, error: 'Version not found' }; } const pkg = await this.getPackage(packageName); if (!pkg) { return { success: false, error: 'Package not found' }; } // Check permissions const isMaintainer = pkg.maintainers.some(m => m.name === user.username || m.email === user.email); if (!isMaintainer && !user.is_admin) { return { success: false, error: 'Permission denied' }; } // Mark version as deprecated packageVersion.deprecate(message); await this.versionRepo.save(packageVersion); logger.info(`Deprecated version: ${packageName}@${version} by ${user.username}`); return { success: true }; } catch (error) { logger.error(`Failed to deprecate version: ${error?.message || String(error)}`, error); return { success: false, error: 'Deprecation failed' }; } } async addCollaborator(packageName, username, currentUser) { return this.addMaintainer(packageName, username, currentUser); } async removeCollaborator(packageName, username, currentUser) { return this.removeMaintainer(packageName, username, currentUser); } async getPackageCount() { try { return await this.packageRepo.count(); } catch (error) { logger.error('Failed to get package count:', error); throw error; } } } exports.PackageService = PackageService; //# sourceMappingURL=PackageService.js.map