@controlplane/cli
Version:
Control Plane Corporation CLI
183 lines • 6.85 kB
JavaScript
;
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