aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
324 lines (266 loc) • 8.54 kB
JavaScript
/**
* Migration Rollback Module
*
* Handles rollback from v4.0.4 back to v2.0 structure using backup.
*
* @module cli/commands/migrate/rollback
* @version 1.0.0
* @story 2.14 - Migration Script v2.0 → v4.0.4
*/
const fs = require('fs');
const path = require('path');
const { findLatestBackup, verifyBackup, copyFileWithMetadata } = require('./backup');
const { clearMigrationState } = require('./execute');
/**
* Remove v4.0.4 module directories
* @param {string} aiosCoreDir - Path to .aios-core
* @param {Object} options - Options
* @returns {Promise<Object>} Removal result
*/
async function removeV21Structure(aiosCoreDir, options = {}) {
const { onProgress = () => {} } = options;
const v21Modules = ['core', 'development', 'product', 'infrastructure'];
const result = {
removed: [],
errors: [],
};
onProgress({ phase: 'remove', message: '✓ Removing v4.0.4 structure...' });
for (const moduleName of v21Modules) {
const moduleDir = path.join(aiosCoreDir, moduleName);
if (fs.existsSync(moduleDir)) {
try {
await fs.promises.rm(moduleDir, { recursive: true, force: true });
result.removed.push(moduleDir);
onProgress({ phase: 'removed', message: ` ✓ Removed ${moduleName}/` });
} catch (error) {
result.errors.push({
path: moduleDir,
error: error.message,
});
}
}
}
return result;
}
/**
* Restore files from backup
* @param {Object} backup - Backup info from findLatestBackup
* @param {string} projectRoot - Project root
* @param {Object} options - Options
* @returns {Promise<Object>} Restore result
*/
async function restoreFromBackup(backup, projectRoot, options = {}) {
const { onProgress = () => {}, verifyFirst = true } = options;
const result = {
success: false,
restored: 0,
errors: [],
warnings: [],
};
// Verify backup integrity first
if (verifyFirst) {
onProgress({ phase: 'verify', message: 'Verifying backup integrity...' });
const verification = await verifyBackup(backup.path);
if (!verification.valid) {
result.errors.push({
type: 'verification',
message: 'Backup verification failed',
details: {
missing: verification.missing,
failed: verification.failed,
},
});
return result;
}
onProgress({ phase: 'verified', message: ` ✓ Verified ${verification.verified} files` });
}
const aiosCoreDir = path.join(projectRoot, '.aios-core');
const backupAiosCoreDir = path.join(backup.path, '.aios-core');
// Restore .aios-core files
onProgress({ phase: 'restore', message: '✓ Restoring v2.0 files...' });
for (const file of backup.manifest.files) {
try {
const sourcePath = file.isConfig
? path.join(backup.path, file.relativePath)
: path.join(backupAiosCoreDir, file.relativePath);
const targetPath = file.isConfig
? path.join(projectRoot, file.relativePath)
: path.join(aiosCoreDir, file.relativePath);
await copyFileWithMetadata(sourcePath, targetPath);
result.restored++;
} catch (error) {
result.errors.push({
file: file.relativePath,
error: error.message,
});
}
}
onProgress({ phase: 'restored', message: ` ✓ Restored ${result.restored} files` });
result.success = result.errors.length === 0;
return result;
}
/**
* Execute full rollback operation
* @param {string} projectRoot - Project root
* @param {Object} options - Options
* @returns {Promise<Object>} Rollback result
*/
async function executeRollback(projectRoot, options = {}) {
const { onProgress = () => {}, backupPath = null } = options;
const result = {
success: false,
backup: null,
removal: null,
restore: null,
errors: [],
};
// Find backup
onProgress({ phase: 'find', message: 'Looking for backup...' });
let backup;
if (backupPath) {
// Use specified backup
const manifestPath = path.join(backupPath, 'backup-manifest.json');
if (!fs.existsSync(manifestPath)) {
result.errors.push({ type: 'backup', message: `No manifest found at ${backupPath}` });
return result;
}
const manifest = JSON.parse(await fs.promises.readFile(manifestPath, 'utf8'));
backup = { path: backupPath, manifest };
} else {
// Find latest backup
backup = await findLatestBackup(projectRoot);
if (!backup) {
result.errors.push({ type: 'backup', message: 'No backup found. Cannot rollback.' });
return result;
}
}
result.backup = {
path: backup.path,
date: backup.manifest?.created,
files: backup.manifest?.totalFiles,
size: backup.manifest?.totalSize,
};
onProgress({
phase: 'found',
message: `Found backup: ${path.basename(backup.path)}`,
details: {
created: backup.manifest?.created,
files: backup.manifest?.totalFiles,
},
});
// Step 1: Remove v4.0.4 structure
const aiosCoreDir = path.join(projectRoot, '.aios-core');
result.removal = await removeV21Structure(aiosCoreDir, { onProgress });
if (result.removal.errors.length > 0) {
result.errors.push(...result.removal.errors);
// Continue anyway - partial removal is okay
}
// Step 2: Restore from backup
result.restore = await restoreFromBackup(backup, projectRoot, { onProgress });
if (!result.restore.success) {
result.errors.push(...result.restore.errors);
return result;
}
// Step 3: Clear migration state
await clearMigrationState(projectRoot);
// Step 4: Validate restored structure
onProgress({ phase: 'validate', message: '✓ Validating restored structure...' });
// Simple validation - check a few expected directories exist
const expectedDirs = ['agents', 'tasks', 'registry'];
let validCount = 0;
for (const dir of expectedDirs) {
if (fs.existsSync(path.join(aiosCoreDir, dir))) {
validCount++;
}
}
if (validCount >= 2) {
onProgress({ phase: 'complete', message: '✅ Rollback complete!' });
result.success = true;
} else {
result.warnings = result.warnings || [];
result.warnings.push('Restored structure may be incomplete');
}
return result;
}
/**
* Generate rollback summary output
* @param {Object} result - Rollback result
* @returns {string} Formatted summary
*/
function formatRollbackSummary(result) {
const lines = [];
lines.push('');
lines.push('🔙 AIOS Migration Rollback');
lines.push('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
if (result.backup) {
lines.push('');
lines.push(`Backup: ${path.basename(result.backup.path)}`);
if (result.backup.date) {
lines.push(` Created: ${new Date(result.backup.date).toLocaleString()}`);
}
if (result.backup.files) {
lines.push(` Files: ${result.backup.files}`);
}
}
if (result.success) {
lines.push('');
lines.push('✅ Rollback complete!');
lines.push('');
lines.push('Your project is now at v2.0 state.');
} else {
lines.push('');
lines.push('⚠️ Rollback completed with issues:');
for (const error of result.errors || []) {
if (error.message) {
lines.push(` • ${error.message}`);
} else {
lines.push(` • ${JSON.stringify(error)}`);
}
}
}
if (result.restore) {
lines.push('');
lines.push(`Restored: ${result.restore.restored} files`);
}
if (result.removal?.removed?.length > 0) {
lines.push(`Removed: ${result.removal.removed.length} v4.0.4 directories`);
}
return lines.join('\n');
}
/**
* Check if rollback is possible
* @param {string} projectRoot - Project root
* @returns {Promise<Object>} Rollback status
*/
async function canRollback(projectRoot) {
const backup = await findLatestBackup(projectRoot);
if (!backup) {
return {
canRollback: false,
reason: 'No backup found',
};
}
const verification = await verifyBackup(backup.path);
if (!verification.valid) {
return {
canRollback: false,
reason: 'Backup verification failed',
details: verification,
};
}
return {
canRollback: true,
backup: {
path: backup.path,
date: backup.manifest?.created,
files: backup.manifest?.totalFiles,
},
};
}
module.exports = {
removeV21Structure,
restoreFromBackup,
executeRollback,
formatRollbackSummary,
canRollback,
};