claudes-office
Version:
CLI tool to initialize Claude's office in your project
400 lines ⢠17.8 kB
JavaScript
;
/**
* 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