El patrón Command convierte una acción en un objeto ejecutable y reversible.
1. El Problema de Partida
scripts/migrate-posts.js aplica varias operaciones sobre archivos .mdx (renombrar slug, mover de categoría, actualizar translation_id). Si una operación a mitad de la migración falla, no hay forma de deshacer las anteriores porque están escritas como funciones sueltas que mutan el filesystem directamente.
2. La Solución Command
// src/lib/migration/commands.js
import fs from 'fs';
class RenameSlugCommand {
constructor(oldPath, newPath) {
this.oldPath = oldPath;
this.newPath = newPath;
}
execute() { fs.renameSync(this.oldPath, this.newPath); }
undo() { fs.renameSync(this.newPath, this.oldPath); }
}
class MigrationRunner {
history = [];
run(command) {
command.execute();
this.history.push(command);
}
rollback() {
while (this.history.length) {
this.history.pop().undo();
}
}
}
export { RenameSlugCommand, MigrationRunner };3. Uso
// scripts/migrate-posts.js
import { RenameSlugCommand, MigrationRunner } from '@/lib/migration/commands';
const runner = new MigrationRunner();
try {
runner.run(new RenameSlugCommand('content/knowledge/es/old-slug.mdx', 'content/knowledge/es/new-slug.mdx'));
runner.run(new RenameSlugCommand('content/knowledge/en/old-slug.mdx', 'content/knowledge/en/new-slug.mdx'));
} catch (err) {
console.error('Migración fallida, revirtiendo...', err);
runner.rollback(); // Deshace todos los renombrados ya aplicados
}4. Ventajas
- Rollback automático: si falla el comando 2, el comando 1 se deshace solo.
- Historial auditable:
runner.historydocumenta exactamente qué se ejecutó y en qué orden. - Extensible: añadir
MoveCategoryCommandoUpdateTranslationIdCommandno tocaMigrationRunner.