UNPKG

@interopio/desktop-cli

Version:

CLI tool for setting up, building and packaging io.Connect Desktop projects

413 lines 18.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.LocalComponentsStore = void 0; const logger_1 = require("../../../utils/logger"); const fs_1 = require("fs"); const path_1 = require("path"); const error_handler_1 = require("../../../utils/error.handler"); /** * Local Components Store implementation * * This store reads components from a local directory following the same structure as the S3 store. * Expected directory structure: * * componentsDirectory/ * ├── component1/ * │ ├── v1.0.0/ * │ │ ├── component1-v1.0.0-win32.zip * │ │ ├── component1-v1.0.0-darwin.dmg * │ │ └── component1-v1.0.0-darwin-arm64.dmg * │ └── v1.1.0/ * │ ├── component1-v1.1.0-win32.zip * │ └── component1-v1.1.0-darwin.dmg * └── component2/ * └── v2.0.0/ * └── component2-v2.0.0-win32.zip * * Legacy support is maintained for flat file structures in the root directory. */ class LocalComponentsStore { logger = logger_1.Logger.getInstance(); componentsDirectory; constructor(componentsDirectory) { this.componentsDirectory = (0, path_1.resolve)(componentsDirectory); this.validateDirectory(); } getInfo() { return `Local Components Store (${this.componentsDirectory})`; } async getAll() { this.logger.debug(`Scanning local components directory: ${this.componentsDirectory}`); if (!(0, fs_1.existsSync)(this.componentsDirectory)) { this.logger.warn(`Components directory does not exist: ${this.componentsDirectory}`); return []; } const components = []; try { const componentDirs = (0, fs_1.readdirSync)(this.componentsDirectory); for (const componentName of componentDirs) { const componentPath = (0, path_1.join)(this.componentsDirectory, componentName); // Skip hidden directories and files if (componentName.startsWith('.')) { continue; } try { const stat = (0, fs_1.statSync)(componentPath); if (stat.isDirectory()) { // Scan for version directories inside component directory const versionComponents = await this.scanComponentVersions(componentPath, componentName); components.push(...versionComponents); } else if (stat.isFile() && this.isSupportedPackageFile(componentName)) { // Legacy support: Handle single package files in root directory this.logger.warn(`Found legacy component file in root: ${componentName}. Consider moving to component/version structure.`); const component = await this.createComponentFromFile(componentPath, componentName); if (component) { components.push(component); } } } catch (error) { this.logger.warn(`Failed to process component ${componentName}: ${error}`); continue; } } this.logger.info(`Found ${components.length} components in local store`); return components; } catch (error) { this.logger.error(`Failed to scan components directory: ${error}`); throw new Error(`Failed to read local components: ${error}`); } } async download(name, version) { this.logger.debug(`Downloading component ${name}@${version} from local store`); const components = await this.getAll(); // Find matching component, preferring current platform let matchingComponent; const currentPlatform = this.getCurrentPlatform(); if (version === 'latest') { // Find latest version (assuming semantic versioning) const nameMatches = components.filter(c => c.name === name); if (nameMatches.length > 0) { // Prefer current platform const platformMatches = nameMatches.filter(c => c.platform === currentPlatform); matchingComponent = this.getLatestVersion(platformMatches.length > 0 ? platformMatches : nameMatches); } } else { // Look for exact version match, preferring current platform const exactMatches = components.filter(c => c.name === name && c.version === version); const platformMatches = exactMatches.filter(c => c.platform === currentPlatform); matchingComponent = platformMatches.length > 0 ? platformMatches[0] : exactMatches[0]; } if (!matchingComponent) { const availableVersions = components .filter(c => c.name === name) .map(c => `${c.version} (${c.platform})`) .join(', '); throw new error_handler_1.CLIError(`Component ${name}${version ? `@${version}` : ''} not found in local store. `, { suggestions: [ `You are trying to install ${name}@${version} - check that the component name and version are correct`, `Available versions: ${availableVersions || 'none'}`, `Ensure your local store follows the structure: ${this.componentsDirectory}/${name}/v${version}/${name}-v${version}-${currentPlatform}.ext` ] }); } // Use downloadUrl as the file path for local components const filePath = matchingComponent.downloadUrl; if (!filePath || !(0, fs_1.existsSync)(filePath)) { throw new Error(`Component file not found: ${filePath}`); } try { // Read the file as binary data const data = (0, fs_1.readFileSync)(filePath); this.logger.info(`Downloaded ${name}@${matchingComponent.version} (${matchingComponent.platform}) from local store`); return { name: matchingComponent.name, data: data, filename: (0, path_1.basename)(filePath) }; } catch (error) { this.logger.error(`Failed to read component file ${filePath}: ${error}`); throw new Error(`Failed to download component: ${error}`); } } /** * Validate that the components directory exists and is accessible */ validateDirectory() { if (!(0, fs_1.existsSync)(this.componentsDirectory)) { throw new Error(`Components directory does not exist: ${this.componentsDirectory}`); } try { const stat = (0, fs_1.statSync)(this.componentsDirectory); if (!stat.isDirectory()) { throw new Error(`Path is not a directory: ${this.componentsDirectory}`); } } catch (error) { throw new Error(`Cannot access components directory: ${this.componentsDirectory} (${error})`); } } /** * Check if a file is a supported package format */ isSupportedPackageFile(filename) { const supportedExtensions = ['.zip', '.tar.gz', '.tgz', '.dmg', '.exe', '.msi', '.deb', '.rpm']; const ext = (0, path_1.extname)(filename).toLowerCase(); // Handle .tar.gz special case if (filename.toLowerCase().endsWith('.tar.gz')) { return true; } return supportedExtensions.includes(ext); } /** * Create a Component object from a single package file (legacy support) */ async createComponentFromFile(filePath, filename) { try { const parsed = this.parseFilename(filename); if (!parsed) { this.logger.debug(`Could not parse component info from filename: ${filename}`); return null; } const platform = this.detectPlatform(filename); const component = { name: parsed.name, version: parsed.version, platform: platform, downloadUrl: filePath // Store file path as download URL for local components }; this.logger.debug(`Created component from file: ${component.name}@${component.version} (${component.platform})`); return component; } catch (error) { this.logger.error(`Failed to create component from file ${filePath}: ${error}`); return null; } } /** * Create a Component object from structured directory layout * Expected filename format: componentName-vVersion-platform.ext * Following S3 store structure: componentName/vVersion/componentName-vVersion-platform.ext */ async createStructuredComponent(filePath, filename, componentName, version) { try { const platform = this.detectPlatformFromStructuredFilename(filename, componentName, version); const component = { name: componentName, version: version, platform: platform, downloadUrl: filePath // Store file path as download URL for local components }; this.logger.debug(`Created structured component: ${component.name}@${component.version} (${component.platform})`); return component; } catch (error) { this.logger.error(`Failed to create structured component from file ${filePath}: ${error}`); return null; } } /** * Scan component directory for version subdirectories * Expected structure: componentName/vX.Y.Z/componentName-vX.Y.Z-platform.ext */ async scanComponentVersions(componentPath, componentName) { const components = []; try { const versionDirs = (0, fs_1.readdirSync)(componentPath); for (const versionDir of versionDirs) { // Skip hidden directories if (versionDir.startsWith('.')) { continue; } const versionPath = (0, path_1.join)(componentPath, versionDir); const stat = (0, fs_1.statSync)(versionPath); if (stat.isDirectory()) { // Extract version from directory name (remove 'v' prefix if present) const version = versionDir.startsWith('v') ? versionDir.substring(1) : versionDir; // Scan for component files in version directory const versionComponents = await this.scanVersionDirectory(versionPath, componentName, version); components.push(...versionComponents); } else if (stat.isFile() && this.isSupportedPackageFile(versionDir)) { // Legacy support: Handle files directly in component directory this.logger.warn(`Found legacy component file in component directory: ${componentPath}/${versionDir}. Consider moving to version subdirectory.`); const component = await this.createComponentFromFile(versionPath, versionDir); if (component) { component.name = componentName; // Override with directory name components.push(component); } } } } catch (error) { this.logger.warn(`Failed to scan component versions directory ${componentPath}: ${error}`); } return components; } /** * Scan version directory for component package files * Expected structure: vX.Y.Z/componentName-vX.Y.Z-platform.ext */ async scanVersionDirectory(versionPath, componentName, version) { const components = []; try { const files = (0, fs_1.readdirSync)(versionPath); for (const filename of files) { if (filename.startsWith('.')) { continue; } const filePath = (0, path_1.join)(versionPath, filename); const stat = (0, fs_1.statSync)(filePath); if (stat.isFile() && this.isSupportedPackageFile(filename)) { const component = await this.createStructuredComponent(filePath, filename, componentName, version); if (component) { components.push(component); } } } } catch (error) { this.logger.warn(`Failed to scan version directory ${versionPath}: ${error}`); } return components; } /** * Parse component name and version from filename * Supports the format: <component>-v<version>-<platform>.<ext> * e.g. iocd-v10.0.0-darwin-arm64.dmg */ parseFilename(filename) { const nameWithoutExt = (0, path_1.parse)(filename).name; // Primary pattern: <component>-v<version>-<platform> // e.g. iocd-v10.0.0-darwin-arm64 const primaryPattern = /^(.+)-v(\d+\.\d+\.\d+.*?)-(.+)$/; const primaryMatch = nameWithoutExt.match(primaryPattern); if (primaryMatch) { return { name: primaryMatch[1], // component name version: primaryMatch[2] // version (without 'v' prefix) }; } // Fallback patterns for backward compatibility const fallbackPatterns = [ /^(.+)-(\d+\.\d+\.\d+.*)$/, // name-1.0.0 /^(.+)_v?(\d+\.\d+\.\d+.*)$/, // name_v1.0.0 or name_1.0.0 /^(.+)-v(\d+\.\d+\.\d+.*)$/, // name-v1.0.0 ]; for (const pattern of fallbackPatterns) { const match = nameWithoutExt.match(pattern); if (match) { return { name: match[1], version: match[2] }; } } // If no version pattern found, use the whole name and default version return { name: nameWithoutExt, version: 'latest' }; } /** * Detect platform based on filename format and content * Primary: Extract from <component>-v<version>-<platform>.<ext> format * Fallback: Use file extension and naming conventions */ detectPlatform(filename) { const nameWithoutExt = (0, path_1.parse)(filename).name; // Primary: Extract platform from filename format <component>-v<version>-<platform> const platformPattern = /^(.+)-v(\d+\.\d+\.\d+.*?)-(.+)$/; const platformMatch = nameWithoutExt.match(platformPattern); if (platformMatch) { const platformString = platformMatch[3].toLowerCase(); if (platformString.includes('darwin-arm64') || platformString.includes('macos-arm64')) { return 'darwin-arm64'; } if (platformString.includes('darwin') || platformString.includes('macos')) { return 'darwin'; } if (platformString.includes('win32') || platformString.includes('windows')) { return 'win32'; } } // Fallback: Use legacy detection methods const lowerFilename = filename.toLowerCase(); if (lowerFilename.includes('darwin') || lowerFilename.includes('macos') || lowerFilename.includes('mac')) { if (lowerFilename.includes('arm64') || lowerFilename.includes('apple-silicon') || lowerFilename.includes('m1')) { return 'darwin-arm64'; } return 'darwin'; } if (lowerFilename.includes('win') || lowerFilename.endsWith('.exe') || lowerFilename.endsWith('.msi')) { return 'win32'; } if (lowerFilename.endsWith('.dmg')) { // DMG files are typically for macOS return 'darwin'; } // Default to current platform if we can't determine const currentPlatform = process.platform; if (currentPlatform === 'win32') return 'win32'; if (currentPlatform === 'darwin') { // Check if ARM64 based on process.arch return process.arch === 'arm64' ? 'darwin-arm64' : 'darwin'; } // Fallback to darwin if unknown return 'darwin'; } /** * Detect platform for structured filenames following S3 store convention * Expected format: componentName-vVersion-platform.ext */ detectPlatformFromStructuredFilename(filename, componentName, version) { const nameWithoutExt = (0, path_1.parse)(filename).name; // Expected pattern: componentName-vVersion-platform const expectedPrefix = `${componentName}-v${version}-`; if (nameWithoutExt.startsWith(expectedPrefix)) { const platformPart = nameWithoutExt.substring(expectedPrefix.length).toLowerCase(); if (platformPart.includes('darwin-arm64') || platformPart === 'darwin-arm64') { return 'darwin-arm64'; } if (platformPart.includes('darwin') || platformPart === 'darwin') { return 'darwin'; } if (platformPart.includes('win32') || platformPart === 'win32') { return 'win32'; } } // Fallback to general platform detection return this.detectPlatform(filename); } /** * Get the latest version from a list of components with the same name * Simple string comparison - for proper semver, you'd want to use a semver library */ getLatestVersion(components) { return components.reduce((latest, current) => { // Simple string comparison - "latest" always wins, otherwise lexicographic if (current.version === 'latest') return current; if (latest.version === 'latest') return latest; return current.version > latest.version ? current : latest; }); } /** * Get current platform string compatible with component naming */ getCurrentPlatform() { const platform = process.platform; if (platform === 'win32') return 'win32'; if (platform === 'darwin') { // Check if ARM64 based on process.arch return process.arch === 'arm64' ? 'darwin-arm64' : 'darwin'; } // Fallback to darwin if unknown return 'darwin'; } } exports.LocalComponentsStore = LocalComponentsStore; //# sourceMappingURL=local.store.js.map