UNPKG

claudes-office

Version:

CLI tool to initialize Claude's office in your project

400 lines • 17.8 kB
"use strict"; /** * Update command for claudes-office * This command updates an existing office installation */ 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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.executeUpdate = executeUpdate; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const chalk = __importStar(require("chalk")); const inquirer = __importStar(require("inquirer")); const ora_1 = __importDefault(require("ora")); const fileComparison_1 = require("../utilities/fileComparison"); const backupSystem_1 = require("../utilities/backupSystem"); const updateNotifier_1 = require("../utilities/updateNotifier"); /** * Update an existing office installation * @param options - Update options */ async function executeUpdate(options = {}) { const debug = (message) => { if (options.debug) { console.log(chalk.gray(`[DEBUG] ${message}`)); } }; try { debug('Starting update with options: ' + JSON.stringify(options)); // Get current directory const currentDir = process.cwd(); debug(`Current directory: ${currentDir}`); // Check if office directory exists const officeDirName = 'claudes-office'; const officeDir = path.join(currentDir, officeDirName); if (!await fs.pathExists(officeDir)) { console.log(chalk.red.bold('\nāŒ Error: Office not found in this directory.')); console.log(chalk.yellow('Please run `claudes-office init` first to create the office.')); return; } // Welcome message console.log(chalk.bold.cyan('\nšŸ“¦ Claudes Office Update Tool šŸ“¦\n')); console.log(chalk.white('This tool will update your existing office installation with the latest templates.')); console.log(chalk.yellow('Your customizations will be preserved where possible.')); // Get template directory const templateDir = path.join(__dirname, '..', '..', 'template'); const templateOfficeDir = path.join(templateDir, officeDirName); // Determine components to update let componentsToUpdate = []; if (options.components && options.components.length > 0) { componentsToUpdate = options.components; } else { // Get available components const templateEntries = await fs.readdir(templateOfficeDir, { withFileTypes: true }); const availableComponents = templateEntries .filter(entry => entry.isDirectory() || entry.name.endsWith('.md')) .map(entry => entry.name); if (!options.dryRun) { const { selectedComponents } = await inquirer.prompt([ { type: 'checkbox', name: 'selectedComponents', message: 'Select components to update:', choices: availableComponents.map(comp => ({ name: comp, checked: comp !== 'worksessions', // Don't update worksessions by default })), pageSize: 15, validate: (answer) => { if (answer.length < 1) { return 'Please select at least one component to update'; } return true; } } ]); componentsToUpdate = selectedComponents; } else { // In dry run mode, select all components except worksessions componentsToUpdate = availableComponents.filter(comp => comp !== 'worksessions'); } } debug(`Components to update: ${componentsToUpdate.join(', ')}`); // Create a backup if requested let backupInfo; if (options.backup) { const backupSpinner = (0, ora_1.default)({ text: chalk.cyan('Creating backup of existing office...'), color: 'cyan', spinner: 'dots' }).start(); try { backupInfo = await (0, backupSystem_1.createBackup)(officeDir, { name: `update-backup-${new Date().toISOString().replace(/:/g, '-')}`, backupDir: path.join(currentDir, '.office-backups') }); backupSpinner.succeed(chalk.green(`Backup created: ${backupInfo.name} (${(0, backupSystem_1.formatBackupSize)(backupInfo.size)})`)); debug(`Backup created at: ${backupInfo.path}`); } catch (error) { backupSpinner.fail(chalk.red('Failed to create backup')); console.error(chalk.red(`Error: ${error.message}`)); if (!options.force) { console.log(chalk.yellow('Update canceled. Use --force to update without a backup.')); return; } } } // Compare directories to find differences const analysisSpinner = (0, ora_1.default)({ text: chalk.cyan('Analyzing differences...'), color: 'cyan', spinner: 'dots' }).start(); // For each component, compare the template with the existing installation const updateActions = []; for (const component of componentsToUpdate) { const templatePath = path.join(templateOfficeDir, component); const installPath = path.join(officeDir, component); debug(`Comparing component: ${component}`); debug(`Template path: ${templatePath}`); debug(`Install path: ${installPath}`); if (!await fs.pathExists(templatePath)) { debug(`Template component not found: ${component}`); continue; } // For directories, compare contents if ((await fs.stat(templatePath)).isDirectory()) { const comparison = await (0, fileComparison_1.compareDirectories)(templatePath, installPath, { ignoreWhitespace: true, ignorePatterns: ['**/.DS_Store'] }); // Files to add (only in template) for (const item of comparison.onlyInDir1) { if (!item.isDirectory) { updateActions.push({ type: 'add', component, path: item.relativePath, sourcePath: path.join(templatePath, item.relativePath), destPath: path.join(installPath, item.relativePath) }); } } // Files to update (different content) for (const item of comparison.differentFiles) { updateActions.push({ type: 'update', component, path: item.relativePath, sourcePath: path.join(templatePath, item.relativePath), destPath: path.join(installPath, item.relativePath), differences: item.comparison.differences, stats: item.comparison.stats }); } } else { // For individual files, compare content const isInstalled = await fs.pathExists(installPath); if (!isInstalled) { // File doesn't exist, add it updateActions.push({ type: 'add', component, path: component, sourcePath: templatePath, destPath: installPath }); } else { // File exists, check for differences const comparison = await compareFiles(templatePath, installPath, { ignoreWhitespace: true }); if (!comparison.identical) { updateActions.push({ type: 'update', component, path: component, sourcePath: templatePath, destPath: installPath, differences: comparison.differences, stats: comparison.stats }); } } } } analysisSpinner.succeed(chalk.green('Analysis completed')); if (updateActions.length === 0) { console.log(chalk.green.bold('\nāœ… Your office is already up to date!')); return; } // Display summary of changes console.log(chalk.magenta.bold('\nšŸ“‹ Update Summary:')); // Group by component const actionsByComponent = {}; for (const action of updateActions) { if (!actionsByComponent[action.component]) { actionsByComponent[action.component] = { add: [], update: [] }; } if (action.type === 'add') { actionsByComponent[action.component].add.push(action); } else { actionsByComponent[action.component].update.push(action); } } // Display by component for (const component in actionsByComponent) { console.log(chalk.cyan(`\nšŸ“ ${component}:`)); const { add, update } = actionsByComponent[component]; if (add.length > 0) { console.log(chalk.green(` āž• ${add.length} files to add:`)); add.forEach(action => { console.log(chalk.green(` • ${action.path}`)); }); } if (update.length > 0) { console.log(chalk.yellow(` šŸ”„ ${update.length} files to update:`)); update.forEach(action => { console.log(chalk.yellow(` • ${action.path} (+${action.stats.additions} -${action.stats.deletions} ~${action.stats.changes})`)); }); } } // In dry run mode, stop here if (options.dryRun) { console.log(chalk.blue.bold('\nšŸ” Dry run completed. No changes were made.')); console.log(chalk.blue('To apply these changes, run the command without --dry-run')); return; } // Confirm update let proceed = options.force; if (!proceed) { const { confirmUpdate } = await inquirer.prompt([ { type: 'confirm', name: 'confirmUpdate', message: 'Do you want to proceed with these updates?', default: true } ]); proceed = confirmUpdate; } if (!proceed) { console.log(chalk.yellow.bold('\nāŒ Update canceled.')); return; } // Perform the update const updateSpinner = (0, ora_1.default)({ text: chalk.cyan('Updating files...'), color: 'cyan', spinner: 'dots' }).start(); let addedCount = 0; let updatedCount = 0; let errorCount = 0; debug(`Performing ${updateActions.length} update actions`); for (const action of updateActions) { try { // Ensure parent directory exists const parentDir = path.dirname(action.destPath); await fs.ensureDir(parentDir); // Copy the file await fs.copy(action.sourcePath, action.destPath, { overwrite: true }); if (action.type === 'add') { addedCount++; } else { updatedCount++; } debug(`${action.type === 'add' ? 'Added' : 'Updated'}: ${action.destPath}`); } catch (error) { errorCount++; debug(`Error ${action.type === 'add' ? 'adding' : 'updating'} ${action.destPath}: ${error.message}`); } } updateSpinner.succeed(chalk.green('Update completed')); // Display results console.log(chalk.green.bold('\nāœ… Update completed successfully!')); console.log(chalk.white(`${addedCount} files added, ${updatedCount} files updated, ${errorCount} errors`)); if (backupInfo) { console.log(chalk.magenta('\nšŸ’¾ Backup Information:')); console.log(chalk.white(`Backup name: ${backupInfo.name}`)); console.log(chalk.white(`Backup size: ${(0, backupSystem_1.formatBackupSize)(backupInfo.size)}`)); console.log(chalk.white(`Backup path: ${backupInfo.path}`)); console.log(chalk.gray('You can restore this backup if needed with the restore command.')); } // Add update notification to CLAUDE.md try { // Get package version // eslint-disable-next-line @typescript-eslint/no-var-requires const packageJson = require('../../../package.json'); const version = packageJson.version; // Try to append notification to CLAUDE.md const claudeMdPath = path.join(currentDir, 'CLAUDE.md'); if (await fs.pathExists(claudeMdPath)) { const notification = (0, updateNotifier_1.getVersionNotification)(version); const notificationAdded = await (0, updateNotifier_1.appendUpdateNotification)(claudeMdPath, notification); if (notificationAdded) { console.log(chalk.blue('\nšŸ“ Update notification added to CLAUDE.md')); console.log(chalk.gray('Please review the changes and delete the notification section once incorporated.')); } } } catch (error) { // Don't fail the update if notification can't be added debug(`Error adding update notification: ${error.message}`); } console.log(chalk.cyan.bold('\nšŸŽ‰ Your office is now up to date!')); } catch (error) { console.error(chalk.red.bold('\nāŒ Error updating office:'), error.message); if (options.debug) { console.error(chalk.gray('Stack trace:'), error.stack); } process.exit(1); } } /** * Compare two files and return their differences * This is a simplified version of the compareFiles function from fileComparison.ts */ async function compareFiles(filePath1, filePath2, options = {}) { try { // Read file contents const content1 = await fs.readFile(filePath1, 'utf8'); const content2 = await fs.readFile(filePath2, 'utf8'); // Normalize content if needed let normalizedContent1 = content1; let normalizedContent2 = content2; if (options.ignoreWhitespace) { normalizedContent1 = normalizedContent1.replace(/\s+/g, ' ').trim(); normalizedContent2 = normalizedContent2.replace(/\s+/g, ' ').trim(); } // Simple comparison const identical = normalizedContent1 === normalizedContent2; // Mock differences and stats for now (will be replaced with proper diff) const differences = identical ? [] : [ { value: 'Files are different', added: false, removed: false } ]; const stats = { additions: identical ? 0 : 1, deletions: identical ? 0 : 1, changes: 0 }; return { identical, differences, stats }; } catch (error) { // If one of the files doesn't exist, they are not identical return { identical: false, differences: [{ value: 'One or both files do not exist', added: false, removed: false }], stats: { additions: 0, deletions: 0, changes: 0 } }; } } //# sourceMappingURL=update.js.map