UNPKG

@fission-ai/openspec

Version:

AI-native system for spec-driven development

143 lines 5.37 kB
import { promises as fs } from 'fs'; import path from 'path'; import os from 'os'; /** * Installer for Fish completion scripts. * Fish automatically loads completions from ~/.config/fish/completions/ */ export class FishInstaller { homeDir; constructor(homeDir = os.homedir()) { this.homeDir = homeDir; } /** * Get the installation path for Fish completions * * @returns Installation path */ getInstallationPath() { return path.join(this.homeDir, '.config', 'fish', 'completions', 'openspec.fish'); } /** * Backup an existing completion file if it exists * * @param targetPath - Path to the file to backup * @returns Path to the backup file, or undefined if no backup was needed */ async backupExistingFile(targetPath) { try { await fs.access(targetPath); // File exists, create a backup const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = `${targetPath}.backup-${timestamp}`; await fs.copyFile(targetPath, backupPath); return backupPath; } catch { // File doesn't exist, no backup needed return undefined; } } /** * Install the completion script * * @param completionScript - The completion script content to install * @returns Installation result with status and instructions */ async install(completionScript) { try { const targetPath = this.getInstallationPath(); // Check if already installed with same content let isUpdate = false; try { const existingContent = await fs.readFile(targetPath, 'utf-8'); if (existingContent === completionScript) { // Already installed and up to date return { success: true, installedPath: targetPath, message: 'Completion script is already installed (up to date)', instructions: [ 'The completion script is already installed and up to date.', 'Fish automatically loads completions - they should be available immediately.', ], }; } // File exists but content is different - this is an update isUpdate = true; } catch (error) { // File doesn't exist or can't be read, proceed with installation console.debug(`Unable to read existing completion file at ${targetPath}: ${error.message}`); } // Ensure the directory exists const targetDir = path.dirname(targetPath); await fs.mkdir(targetDir, { recursive: true }); // Backup existing file if updating const backupPath = isUpdate ? await this.backupExistingFile(targetPath) : undefined; // Write the completion script await fs.writeFile(targetPath, completionScript, 'utf-8'); // Determine appropriate message let message; if (isUpdate) { message = backupPath ? 'Completion script updated successfully (previous version backed up)' : 'Completion script updated successfully'; } else { message = 'Completion script installed successfully for Fish'; } return { success: true, installedPath: targetPath, backupPath, message, instructions: [ 'Fish automatically loads completions from ~/.config/fish/completions/', 'Completions are available immediately - no shell restart needed.', ], }; } catch (error) { return { success: false, message: `Failed to install completion script: ${error instanceof Error ? error.message : String(error)}`, }; } } /** * Uninstall the completion script * * @param options - Optional uninstall options * @param options.yes - Skip confirmation prompt (handled by command layer) * @returns Uninstallation result */ async uninstall(options) { try { const targetPath = this.getInstallationPath(); // Check if installed try { await fs.access(targetPath); } catch { return { success: false, message: 'Completion script is not installed', }; } // Remove the completion script await fs.unlink(targetPath); return { success: true, message: 'Completion script uninstalled successfully', }; } catch (error) { return { success: false, message: `Failed to uninstall completion script: ${error instanceof Error ? error.message : String(error)}`, }; } } } //# sourceMappingURL=fish-installer.js.map