UNPKG

qraft

Version:

A powerful CLI tool to qraft structured project setups from GitHub template repositories

520 lines 21.3 kB
"use strict"; 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.BoxManager = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const config_1 = require("../utils/config"); const manifestUtils_1 = require("../utils/manifestUtils"); const cacheManager_1 = require("./cacheManager"); const manifestManager_1 = require("./manifestManager"); const registryManager_1 = require("./registryManager"); /** * BoxManager that supports both local and remote GitHub repositories */ class BoxManager { constructor(configManager) { this.registryManager = null; this.cacheManager = null; this.configManager = configManager || new config_1.ConfigManager(); this.manifestManager = new manifestManager_1.ManifestManager(); } /** * Initialize the managers (lazy loading) */ async initializeManagers() { if (!this.registryManager || !this.cacheManager) { const config = await this.configManager.getConfig(); this.registryManager = new registryManager_1.RegistryManager(config); this.cacheManager = new cacheManager_1.CacheManager(config.cache); } } /** * Parse a box reference string and resolve registry * @param reference Box reference (e.g., "n8n", "myorg/n8n") * @param overrideRegistry Optional registry to override the parsed registry * @returns Promise<BoxReference> Parsed and resolved reference */ async parseBoxReference(reference, overrideRegistry) { await this.initializeManagers(); return this.registryManager.parseBoxReference(reference, overrideRegistry); } /** * Resolve registry name to configuration * @param registryName Registry name * @returns Promise<RegistryConfig> Registry configuration */ async resolveRegistry(registryName) { await this.initializeManagers(); return this.registryManager.resolveRegistry(registryName); } /** * Get the effective registry for a box reference * @param reference Box reference * @param overrideRegistry Optional registry override * @returns Promise<string> Effective registry name */ async getEffectiveRegistry(reference, overrideRegistry) { await this.initializeManagers(); return this.registryManager.getEffectiveRegistry(reference, overrideRegistry); } /** * Discover all available boxes from the default registry * @param registryName Optional registry name (uses default if not provided) * @returns Promise<BoxInfo[]> Array of discovered boxes */ async discoverBoxes(registryName) { await this.initializeManagers(); try { const boxNames = await this.registryManager.listBoxes(registryName); const boxes = []; for (const boxName of boxNames) { // Parse box reference with explicit registry override to avoid confusion // between nested paths and registry names const boxRef = await this.parseBoxReference(boxName, registryName); const boxInfo = await this.getBoxInfo(boxRef); if (boxInfo) { boxes.push(boxInfo); } } return boxes.sort((a, b) => a.manifest.name.localeCompare(b.manifest.name)); } catch (error) { throw new Error(`Failed to discover boxes: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get information about a specific box * @param boxRef Box reference or string * @returns Promise<BoxInfo | null> Box information or null if not found */ async getBoxInfo(boxRef) { await this.initializeManagers(); const parsedRef = typeof boxRef === 'string' ? await this.parseBoxReference(boxRef) : boxRef; try { // Check cache first - TEMPORARILY DISABLED FOR DEBUGGING // const cachedEntry = await this.cacheManager!.getCacheEntry(parsedRef); // if (cachedEntry) { // console.log(`DEBUG: BoxManager using cached entry with ${cachedEntry.files.length} files:`, cachedEntry.files); // return { // manifest: cachedEntry.manifest, // path: cachedEntry.localPath, // files: cachedEntry.files // }; // } // Fetch from registry const boxInfo = await this.registryManager.getBoxInfo(parsedRef); if (boxInfo) { // Cache the box info for future use const fileContents = new Map(); // Note: We're not downloading files here, just caching metadata await this.cacheManager.setCacheEntry(parsedRef, boxInfo.manifest, boxInfo.files, fileContents); } return boxInfo; } catch (error) { return null; } } /** * Check if a box exists * @param boxRef Box reference or string * @returns Promise<boolean> True if box exists */ async boxExists(boxRef) { await this.initializeManagers(); const parsedRef = typeof boxRef === 'string' ? await this.parseBoxReference(boxRef) : boxRef; return await this.registryManager.boxExists(parsedRef); } /** * List all available boxes with their basic information * @param registryName Optional registry name * @returns Promise<Array<{name: string, description: string, version: string}>> Simple list of boxes */ async listBoxes(registryName) { const boxes = await this.discoverBoxes(registryName); return boxes.map(box => ({ name: box.manifest.name, description: box.manifest.description, version: box.manifest.version })); } /** * Copy a box to a target directory with GitHub support * @param config Box operation configuration * @param overrideRegistry Optional registry to override the parsed registry * @returns Promise<BoxOperationResult> Result of the operation */ async copyBox(config, overrideRegistry) { await this.initializeManagers(); try { // Parse box reference with optional registry override const boxRef = await this.parseBoxReference(config.boxName, overrideRegistry); // Get box information const boxInfo = await this.getBoxInfo(boxRef); if (!boxInfo) { const effectiveRegistry = await this.getEffectiveRegistry(config.boxName, overrideRegistry); return { success: false, message: `Box '${config.boxName}' not found in registry '${effectiveRegistry}'`, error: new Error(`Box '${config.boxName}' does not exist in registry '${effectiveRegistry}'`) }; } // Determine target directory const targetDir = config.targetDirectory ?? boxInfo.manifest.defaultTarget ?? process.cwd(); const resolvedTargetDir = path.resolve(targetDir); // Download and copy files const results = await this.downloadAndCopyFiles(boxRef, boxInfo, resolvedTargetDir, config.force); // Store manifest locally after successful file operations (unless noSync is enabled) if (!config.nosync) { try { await this.storeManifestLocally(boxRef, boxInfo, resolvedTargetDir); } catch (manifestError) { // Log manifest storage error but don't fail the entire operation console.warn(`Warning: Failed to store manifest locally: ${manifestError instanceof Error ? manifestError.message : 'Unknown error'}`); } } // Analyze results const copiedFiles = results.filter(r => r.success).map(r => r.destination); const skippedFiles = results.filter(r => r.skipped).map(r => r.destination); const failedFiles = results.filter(r => !r.success && !r.skipped); if (failedFiles.length > 0) { return { success: false, message: `Failed to copy ${failedFiles.length} files from box '${config.boxName}'`, copiedFiles, skippedFiles, error: failedFiles[0].error || new Error('Unknown error during file copy') }; } const message = skippedFiles.length > 0 ? `Successfully copied ${copiedFiles.length} files from box '${config.boxName}' (${skippedFiles.length} skipped)` : `Successfully copied ${copiedFiles.length} files from box '${config.boxName}'`; return { success: true, message, copiedFiles, skippedFiles }; } catch (error) { return { success: false, message: `Error copying box '${config.boxName}': ${error instanceof Error ? error.message : 'Unknown error'}`, error: error instanceof Error ? error : new Error('Unknown error') }; } } /** * Download files from GitHub and copy them to target directory * @param boxRef Box reference * @param boxInfo Box information * @param targetDir Target directory * @param force Whether to force overwrite * @returns Promise<Array> File operation results */ async downloadAndCopyFiles(boxRef, boxInfo, targetDir, force) { const results = []; // Ensure .qraft/ is always excluded to prevent recursive boxing const excludePatterns = manifestUtils_1.ManifestUtils.getUpdatedExcludePatterns(boxInfo.manifest.exclude || []); for (const filePath of boxInfo.files) { // Check if file should be excluded if (this.shouldExcludeFile(filePath, excludePatterns)) { results.push({ success: false, skipped: true, destination: path.join(targetDir, filePath) }); continue; } try { const destinationPath = path.join(targetDir, filePath); // Check if destination exists and handle overwrite protection const destinationExists = await fs.pathExists(destinationPath); if (destinationExists && !force) { results.push({ success: false, skipped: true, destination: destinationPath }); continue; } // Try to get file from cache first let fileContent = await this.cacheManager.getCachedFile(boxRef, filePath); if (!fileContent) { // Download from GitHub fileContent = await this.registryManager.downloadFile(boxRef, filePath); // Cache the file const fileContents = new Map(); fileContents.set(filePath, fileContent); await this.cacheManager.setCacheEntry(boxRef, boxInfo.manifest, [filePath], fileContents); } // Ensure destination directory exists await fs.ensureDir(path.dirname(destinationPath)); // Write file await fs.writeFile(destinationPath, fileContent); results.push({ success: true, skipped: false, destination: destinationPath }); } catch (error) { results.push({ success: false, skipped: false, destination: path.join(targetDir, filePath), error: error instanceof Error ? error : new Error('Unknown error') }); } } return results; } /** * Check if a file should be excluded based on patterns * @param filePath Relative file path * @param excludePatterns Array of patterns to match against * @returns boolean True if file should be excluded */ shouldExcludeFile(filePath, excludePatterns) { if (excludePatterns.length === 0) { return false; } const normalizedPath = filePath.replace(/\\/g, '/'); return excludePatterns.some(pattern => { const regexPattern = pattern .replace(/\./g, '\\.') .replace(/\*/g, '.*') .replace(/\?/g, '.'); const regex = new RegExp(`^${regexPattern}$`, 'i'); return regex.test(normalizedPath) || normalizedPath.includes(pattern); }); } /** * Copy a box by name with simplified parameters * @param boxName Name of the box to copy * @param targetDirectory Target directory (optional) * @param force Whether to force overwrite existing files * @param overrideRegistry Optional registry to override the parsed registry * @param nosync Whether to skip creating .qraft directory (no sync tracking) * @returns Promise<BoxOperationResult> Result of the operation */ async copyBoxByName(boxName, targetDirectory, force = false, overrideRegistry, nosync = false) { const config = { boxName, targetDirectory: targetDirectory ?? process.cwd(), force, interactive: false, boxesDirectory: '', // Not used in GitHub mode nosync: nosync }; return this.copyBox(config, overrideRegistry); } /** * List all configured registries * @returns Promise<Array<{name: string, repository: string, isDefault: boolean}>> List of registries */ async listRegistries() { await this.initializeManagers(); const registries = this.registryManager.getRegistries(); return Object.values(registries).map(registry => ({ name: registry.name, repository: registry.repository, isDefault: registry.isDefault || false })); } /** * Get the default registry name * @returns Promise<string> Default registry name */ async getDefaultRegistry() { await this.initializeManagers(); return this.registryManager.getDefaultRegistry(); } /** * Check if a registry has authentication configured * @param registryName Name of the registry * @returns Promise<boolean> True if authentication is available */ async hasAuthentication(registryName) { await this.initializeManagers(); return this.registryManager.hasAuthentication(registryName); } /** * Test authentication for a registry * @param registryName Name of the registry * @returns Promise<{authenticated: boolean, user?: string, error?: string}> Authentication test result */ async testAuthentication(registryName) { await this.initializeManagers(); return this.registryManager.testAuthentication(registryName); } /** * Set authentication token for a registry * @param registryName Name of the registry * @param token GitHub token * @returns Promise<void> */ async setRegistryToken(registryName, token) { await this.configManager.setRegistryToken(registryName, token); // Clear cached Octokit instance to use new token await this.initializeManagers(); } /** * Set global authentication token * @param token GitHub token * @returns Promise<void> */ async setGlobalToken(token) { await this.configManager.setGlobalToken(token); // Clear cached managers to use new token this.registryManager = null; this.cacheManager = null; } /** * Get GitHub token for a registry * @param registryName Name of the registry * @returns Promise<string | undefined> GitHub token */ async getGitHubToken(registryName) { const config = await this.configManager.getConfig(); // First try registry-specific token if (config.registries[registryName]?.token) { return config.registries[registryName].token; } // Fall back to global token return config.globalToken; } /** * Store manifest locally in the target directory * @param boxRef Box reference * @param boxInfo Box information * @param targetDir Target directory * @returns Promise<void> */ async storeManifestLocally(boxRef, boxInfo, targetDir) { try { // Store the manifest with source information await this.manifestManager.storeLocalManifest(targetDir, boxInfo.manifest, boxRef.registry, boxRef.fullReference); } catch (error) { throw new Error(`Failed to store manifest locally: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Check if a box is new or updated compared to local manifest * @param boxInfo Remote box information * @param targetDir Target directory * @returns Promise<'new' | 'updated' | 'identical' | 'unknown'> */ async detectBoxState(boxInfo, targetDir) { try { const localManifest = await this.manifestManager.getLocalManifest(targetDir); if (!localManifest) { return 'new'; } const comparison = this.manifestManager.compareManifests(localManifest.manifest, boxInfo.manifest); if (comparison.isIdentical) { return 'identical'; } else { return 'updated'; } } catch (error) { return 'unknown'; } } /** * Get local manifest for a directory * @param targetDir Target directory * @returns Promise<LocalManifestEntry | null> */ async getLocalManifest(targetDir) { return this.manifestManager.getLocalManifest(targetDir); } /** * Check if local manifest exists * @param targetDir Target directory * @returns Promise<boolean> */ async hasLocalManifest(targetDir) { return this.manifestManager.hasLocalManifest(targetDir); } /** * Synchronize local manifest with remote * @param boxRef Box reference * @param targetDir Target directory * @returns Promise<boolean> True if sync was needed and performed */ async syncManifest(boxRef, targetDir) { try { const boxInfo = await this.getBoxInfo(boxRef); if (!boxInfo) { throw new Error(`Box '${boxRef.fullReference}' not found`); } const state = await this.detectBoxState(boxInfo, targetDir); if (state === 'updated' || state === 'new') { await this.storeManifestLocally(boxRef, boxInfo, targetDir); return true; } return false; } catch (error) { throw new Error(`Failed to sync manifest: ${error instanceof Error ? error.message : 'Unknown error'}`); } } /** * Get manifest manager instance * @returns ManifestManager Manifest manager instance */ getManifestManager() { return this.manifestManager; } /** * Get configuration manager * @returns ConfigManager Configuration manager instance */ getConfigManager() { return this.configManager; } } exports.BoxManager = BoxManager; //# sourceMappingURL=boxManager.js.map