require("dotenv").config(); const path = require("path"); const fs = require("fs"); const {execSync} = require("child_process"); const connectDB = require("../config/database"); const migrationHelper = require("../utils/migrationHelper"); /** * Tự động phát hiện tất cả các file script trong thư mục scripts * Loại trừ các file quản lý migration và file không phải .js */ function discoverMigrations() { const scriptsDir = __dirname; const files = fs.readdirSync(scriptsDir); // Danh sách các file quản lý migration cần loại trừ const excludeFiles = [ "migrate-all.js", "migrate-status.js", "migrate-rollback.js", "migrate-fresh.js", "make-migration.js", ]; const migrations = files .filter((file) => { // Lấy tất cả file .js, trừ các file quản lý return file.endsWith(".js") && !excludeFiles.includes(file); }) .map((file) => { // Tạo tên migration từ tên file (bỏ .js) const name = file.replace(".js", ""); return { name: name, script: file, }; }) .sort((a, b) => { // Sắp xếp theo tên để đảm bảo thứ tự nhất quán return a.name.localeCompare(b.name); }); return migrations; } // Tự động phát hiện migrations const migrations = discoverMigrations(); /** * Chạy migration script (suppress output) * Sử dụng child_process để chạy script độc lập vì các script tự quản lý DB connection * Output từ script sẽ bị suppress để chỉ hiển thị status */ async function runMigrationScript(migration) { return new Promise((resolve, reject) => { try { // Chạy script bằng child_process với stdio: 'pipe' để suppress output // Nhưng vẫn capture stderr để có thể hiển thị lỗi nếu cần const result = execSync(`node scripts/${migration.script}`, { stdio: ["ignore", "pipe", "pipe"], // stdin: ignore, stdout: pipe, stderr: pipe cwd: path.join(__dirname, ".."), encoding: "utf8", }); resolve(); } catch (error) { // Attach stderr vào error để có thể hiển thị sau if (error.stderr) { error.stderr = error.stderr; } reject(error); } }); } /** * Hàm chính để chạy tất cả migrations với tracking */ async function runAllMigrations() { const mongoose = require("mongoose"); let ownConn = false; try { const wasConnected = mongoose.connection.readyState === 1; await connectDB(); if (!wasConnected) ownConn = true; const batch = (await migrationHelper.getLastBatch()) + 1; const results = []; // Kiểm tra và chạy từng migration for (let i = 0; i < migrations.length; i++) { const migration = migrations[i]; try { // Kiểm tra xem migration đã chạy chưa const hasRun = await migrationHelper.hasRun(migration.name); if (hasRun) { results.push({name: migration.name, status: "SKIPPED"}); continue; } // Chạy migration script (output bị suppress) await runMigrationScript(migration); if (mongoose.connection.readyState !== 1) { await connectDB(); } await migrationHelper.markAsRun(migration.name, batch); results.push({name: migration.name, status: "DONE"}); } catch (error) { results.push({ name: migration.name, status: "FAIL", error: error.message, }); // Hiển thị bảng kết quả trước khi exit displayResults(results); console.error( `\n❌ Migration "${migration.name}" failed: ${error.message}` ); if (error.stderr) { console.error(error.stderr.toString()); } if (ownConn && mongoose.connection.readyState === 1) { await mongoose.disconnect(); } process.exit(1); } } // Hiển thị bảng kết quả displayResults(results); if (ownConn && mongoose.connection.readyState === 1) { await mongoose.disconnect(); } process.exit(0); } catch (error) { console.error("\n❌ Error:", error.message); if (ownConn && mongoose.connection.readyState === 1) { await mongoose.disconnect(); } process.exit(1); } } /** * Hiển thị bảng kết quả migration đơn giản */ function displayResults(results) { console.log("\nRunning migrations...\n"); // Tìm độ dài tên migration dài nhất để format bảng const maxNameLength = Math.max(...results.map((r) => r.name.length), 20); const statusWidth = 10; const totalWidth = maxNameLength + statusWidth + 7; // 7 = spaces and separators // Header console.log("=".repeat(totalWidth)); console.log( `${"Migration".padEnd(maxNameLength)} | ${"Status".padEnd(statusWidth)}` ); console.log("=".repeat(totalWidth)); // Rows results.forEach((result) => { let statusText = ""; if (result.status === "DONE") { statusText = "DONE".padEnd(statusWidth); } else if (result.status === "SKIPPED") { statusText = "SKIPPED".padEnd(statusWidth); } else if (result.status === "FAIL") { statusText = "FAIL".padEnd(statusWidth); } console.log(`${result.name.padEnd(maxNameLength)} | ${statusText}`); }); // Footer console.log("=".repeat(totalWidth)); // Summary const doneCount = results.filter((r) => r.status === "DONE").length; const skippedCount = results.filter((r) => r.status === "SKIPPED").length; const failCount = results.filter((r) => r.status === "FAIL").length; console.log(""); if (doneCount > 0) { console.log(`${doneCount} migration(s) completed`); } if (skippedCount > 0) { console.log(`${skippedCount} migration(s) skipped`); } if (failCount > 0) { console.log(`${failCount} migration(s) failed`); } console.log(""); } // Chạy hàm chính runAllMigrations();