UNPKG

@controlplane/cli

Version:

Control Plane Corporation CLI

183 lines 6.85 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CompletionInstaller = void 0; const fs = require("fs"); const path = require("path"); const constants_1 = require("./constants"); const logger_1 = require("../../util/logger"); const io_1 = require("../../util/io"); /** * Manages shell completion script installation and configuration * Handles everything manually without external dependencies */ class CompletionInstaller { constructor(name, completer) { this.name = name; this.completer = completer; } // Public Methods // /** * Installs completion scripts and configures shell RC files */ async install(rcPath, shell) { let selectedShell; let shellRcPath; // Clean up any existing completion scripts this.cleanupExistingScripts(); // Determine shell and RC path if (rcPath && shell) { selectedShell = shell; shellRcPath = rcPath; } else if (rcPath) { selectedShell = this.shellFromPath(rcPath); shellRcPath = rcPath; } else if (shell) { selectedShell = shell; shellRcPath = this.getDefaultPath(shell); } else { throw new Error('Either path or shell must be provided'); } // Expand ~ to home directory shellRcPath = (0, io_1.resolvePath)(shellRcPath); // Write the completion script this.writeCompletionScript(selectedShell); // Add source line to shell RC file this.addSourceLineToRc(selectedShell, shellRcPath); return selectedShell; } /** * Uninstalls completion scripts and removes shell configuration */ async uninstall() { // Remove source lines from all shell RC files for (const shell of constants_1.SUPPORTED_SHELLS) { const rcPath = (0, io_1.resolvePath)(this.getDefaultPath(shell)); if (fs.existsSync(rcPath)) { this.removeSourceLineFromRc(rcPath); } } // Remove completion scripts this.cleanupExistingScripts(); } // Private Methods // /** * Removes existing completion scripts for the package */ cleanupExistingScripts() { for (const shell of constants_1.SUPPORTED_SHELLS) { const filepath = path.join(constants_1.COMPLETION_DIR, `${this.name}.${shell}`); if (fs.existsSync(filepath)) { fs.unlinkSync(filepath); } } } /** * Determines shell type from RC file path */ shellFromPath(path) { if (path.includes('zshrc') || path.includes('zsh')) { return 'zsh'; } else if (path.includes('fish')) { return 'fish'; } return 'bash'; } /** * Gets the default RC file path for a shell */ getDefaultPath(shell) { return constants_1.DEFAULT_SHELL_PATHS[shell] || constants_1.DEFAULT_SHELL_PATHS.bash; } /** * Writes the completion script */ writeCompletionScript(shell) { // Ensure completion directory exists if (!fs.existsSync(constants_1.COMPLETION_DIR)) { fs.mkdirSync(constants_1.COMPLETION_DIR, { recursive: true }); } const filename = path.join(constants_1.COMPLETION_DIR, `${this.name}.${shell}`); const script = this.getProcessedScript(shell); fs.writeFileSync(filename, script); logger_1.logger.debug(`Wrote completion script to ${filename}`); return filename; } /** * Processes the completion script template by replacing placeholders */ getProcessedScript(shell) { const template = this.getScriptTemplate(shell); return template.replace(/\{pkgname\}/g, this.name).replace(/\{completer\}/g, this.completer); } /** * Gets the completion script template for the given shell */ getScriptTemplate(shell) { const filename = `${shell}.sh`; const filepath = path.join(constants_1.SCRIPTS_DIR, filename); try { return fs.readFileSync(filepath, 'utf8'); } catch (err) { throw new Error(`Failed to load completion script for ${shell}: ${err.message}`); } } /** * Adds source line to shell RC file */ addSourceLineToRc(shell, rcPath) { // Ensure RC file directory exists const rcDir = path.dirname(rcPath); if (!fs.existsSync(rcDir)) { fs.mkdirSync(rcDir, { recursive: true }); } // Create RC file if it doesn't exist if (!fs.existsSync(rcPath)) { const defaultContent = shell === 'fish' ? 'if status is-interactive\n# Commands to run in interactive sessions can go here\nend\n' : ''; fs.writeFileSync(rcPath, defaultContent); logger_1.logger.debug(`Created ${rcPath}`); } let content = fs.readFileSync(rcPath, 'utf8'); // Check if source line already exists if (content.includes(`# ${this.name} completion`)) { logger_1.logger.debug(`Source line already exists in ${rcPath}`); return; } // Get source line from template const completionPath = path.join(constants_1.COMPLETION_DIR, `${this.name}.${shell}`); const config = constants_1.SOURCE_LINE_CONFIGS[shell] || constants_1.SOURCE_LINE_CONFIGS.bash; const sourceLine = config.template.replace(/\{name\}/g, this.name).replace(/\{path\}/g, completionPath); content += sourceLine; fs.writeFileSync(rcPath, content); logger_1.logger.debug(`Added source line to ${rcPath}`); } /** * Removes source line from shell RC file * Only removes the exact patterns we add - nothing else */ removeSourceLineFromRc(rcPath) { if (!fs.existsSync(rcPath)) { return; } let content = fs.readFileSync(rcPath, 'utf8'); const originalContent = content; // Escape special regex characters in the name const escapedName = this.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Build removal patterns from the same configs used for adding const patterns = Object.values(constants_1.SOURCE_LINE_CONFIGS).map((config) => new RegExp(config.removalPattern.replace(/\{name\}/g, escapedName), 'g')); for (const pattern of patterns) { content = content.replace(pattern, ''); } // Only update if necessary if (content !== originalContent) { fs.writeFileSync(rcPath, content); logger_1.logger.debug(`Removed source line from ${rcPath}`); } } } exports.CompletionInstaller = CompletionInstaller; //# sourceMappingURL=installer.js.map