import { readdirSync } from 'node:fs' import { join } from 'node:path' import { spawnSync } from 'node:child_process' const rootDir = process.cwd() const envFile = process.env.ENV_FILE || '.env.development' const serviceName = process.env.DB_SERVICE || 'sori-studio-db' const databaseName = process.env.POSTGRES_DB || 'sori_studio' const databaseUser = process.env.POSTGRES_USER || 'sori_studio' const migrationsDir = join(rootDir, 'db', 'migrations') const containerMigrationsDir = '/docker-entrypoint-initdb.d' /** * 명령 실행 결과 확인 * @param {string} command - 실행할 명령 * @param {string[]} args - 명령 인자 * @param {Object} options - 실행 옵션 * @returns {void} */ const runCommand = (command, args, options = {}) => { const result = spawnSync(command, args, { cwd: rootDir, env: { ...process.env, ENV_FILE: envFile }, stdio: 'inherit', ...options }) if (result.status !== 0) { process.exit(result.status || 1) } } /** * 마이그레이션 파일 목록 조회 * @returns {string[]} SQL 파일 목록 */ const getMigrationFiles = () => readdirSync(migrationsDir) .filter((fileName) => fileName.endsWith('.sql')) .sort((firstFile, secondFile) => firstFile.localeCompare(secondFile)) /** * 개발 DB 컨테이너 시작 * @returns {void} */ const startDatabase = () => { runCommand('docker', [ 'compose', '--env-file', envFile, 'up', '-d', serviceName ]) } /** * 개발 DB 준비 상태 확인 * @returns {void} */ const waitForDatabase = () => { for (let attempt = 1; attempt <= 20; attempt += 1) { const result = spawnSync('docker', [ 'exec', serviceName, 'pg_isready', '-U', databaseUser, '-d', databaseName ], { cwd: rootDir, stdio: 'ignore' }) if (result.status === 0) { return } spawnSync('sleep', ['1']) } console.error('개발 DB가 준비되지 않았습니다.') process.exit(1) } /** * SQL 마이그레이션 파일 실행 * @param {string} fileName - 실행할 SQL 파일명 * @returns {void} */ const runMigrationFile = (fileName) => { runCommand('docker', [ 'exec', serviceName, 'psql', '-v', 'ON_ERROR_STOP=1', '-U', databaseUser, '-d', databaseName, '-f', `${containerMigrationsDir}/${fileName}` ]) } /** * 개발 DB 마이그레이션 실행 * @returns {void} */ const migrateDevelopmentDatabase = () => { startDatabase() waitForDatabase() for (const fileName of getMigrationFiles()) { runMigrationFile(fileName) } } migrateDevelopmentDatabase()