UNPKG

debug-time-machine

Version:
409 lines (394 loc) 16.7 kB
#!/usr/bin/env node "use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/cli.ts var cli_exports = {}; __export(cli_exports, { startDebugTimeMachine: () => startDebugTimeMachine }); module.exports = __toCommonJS(cli_exports); var import_child_process = require("child_process"); var import_fs = require("fs"); var import_path = __toESM(require("path")); var colors = { red: (text) => `\x1B[31m${text}\x1B[0m`, green: (text) => `\x1B[32m${text}\x1B[0m`, yellow: (text) => `\x1B[33m${text}\x1B[0m`, blue: (text) => `\x1B[34m${text}\x1B[0m`, cyan: (text) => `\x1B[36m${text}\x1B[0m`, gray: (text) => `\x1B[90m${text}\x1B[0m`, bold: (text) => `\x1B[1m${text}\x1B[0m` }; var runningProcesses = []; async function findProjectPath() { const possiblePaths = [ import_path.default.join(__dirname, "..", "..", ".."), // packages/unified에서 3단계 위 process.cwd(), // 현재 작업 디렉토리 import_path.default.join(process.cwd(), "..", "debugTimeMachine"), // 상위 폴더의 debugTimeMachine import_path.default.join(process.env.HOME || "", "Desktop", "Project", "debugTimeMachine") // 일반적인 위치 ]; for (const testPath of possiblePaths) { try { const backendExists = await import_fs.promises.stat(import_path.default.join(testPath, "apps", "backend", "package.json")).then((stat) => stat.isFile()).catch(() => false); const frontendExists = await import_fs.promises.stat(import_path.default.join(testPath, "apps", "frontend", "package.json")).then((stat) => stat.isFile()).catch(() => false); if (backendExists && frontendExists) { console.log(colors.gray(` \u2705 \uD504\uB85C\uC81D\uD2B8 \uACBD\uB85C \uBC1C\uACAC: ${testPath}`)); return testPath; } } catch (e) { } } throw new Error("Debug Time Machine \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. apps/backend\uC640 apps/frontend \uD3F4\uB354\uAC00 \uC788\uB294\uC9C0 \uD655\uC778\uD558\uC138\uC694."); } async function waitForServer(port, maxAttempts = 60) { console.log(colors.gray(` ${port} \uD3EC\uD2B8\uC5D0\uC11C \uC11C\uBC84 \uC751\uB2F5 \uB300\uAE30 \uC911...`)); for (let i = 0; i < maxAttempts; i++) { try { const checkPath = port === 4e3 ? "/health" : "/"; const response = await fetch(`http://localhost:${port}${checkPath}`, { signal: AbortSignal.timeout(2e3) }); if (response.ok) { console.log(colors.green(` \u2705 \uD3EC\uD2B8 ${port} \uC900\uBE44 \uC644\uB8CC!`)); return true; } } catch (error) { if (i > 0 && i % 15 === 0) { console.log(colors.gray(` \uC544\uC9C1 \uB300\uAE30 \uC911... (${i}/${maxAttempts}\uCD08)`)); } } await new Promise((resolve) => setTimeout(resolve, 1e3)); } console.log(colors.red(` \u274C \uD3EC\uD2B8 ${port} \uC11C\uBC84\uAC00 ${maxAttempts}\uCD08 \uD6C4\uC5D0\uB3C4 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`)); return false; } async function openBrowser(url) { try { const platform = process.platform; let command; if (platform === "darwin") { command = "open"; } else if (platform === "win32") { command = "start"; } else { command = "xdg-open"; } (0, import_child_process.spawn)(command, [url], { detached: true, stdio: "ignore" }); } catch (error) { console.log(colors.yellow(`\uBE0C\uB77C\uC6B0\uC800\uB97C \uC790\uB3D9\uC73C\uB85C \uC5F4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC218\uB3D9\uC73C\uB85C \uC5F4\uC5B4\uC8FC\uC138\uC694: ${url}`)); } } function setupProcessCleanup() { const cleanup = () => { console.log(colors.yellow("\n\u{1F9F9} \uD504\uB85C\uC138\uC2A4 \uC815\uB9AC \uC911...")); runningProcesses.forEach(({ name, process: process2 }) => { if (process2 && !process2.killed) { console.log(colors.gray(` \u274C ${name} \uC885\uB8CC \uC911...`)); process2.kill("SIGTERM"); setTimeout(() => { if (!process2.killed) { process2.kill("SIGKILL"); } }, 5e3); } }); console.log(colors.green("\u2705 \uBAA8\uB4E0 \uD504\uB85C\uC138\uC2A4\uAC00 \uC815\uB9AC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.")); process.exit(0); }; process.on("SIGINT", cleanup); process.on("SIGTERM", cleanup); process.on("beforeExit", cleanup); } async function startBackendServer(projectPath) { console.log(colors.blue("\u{1F680} \uBC31\uC5D4\uB4DC \uC11C\uBC84 \uC2DC\uC791 \uC911...")); const backendPath = import_path.default.join(projectPath, "apps", "backend"); try { await import_fs.promises.stat(import_path.default.join(backendPath, "package.json")); } catch (error) { throw new Error(`\uBC31\uC5D4\uB4DC \uD504\uB85C\uC81D\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${backendPath}`); } const backendProcess = (0, import_child_process.spawn)("npm", ["run", "dev"], { cwd: backendPath, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, PORT: "4000" }, shell: true }); const processInfo = { name: "Backend Server", process: backendProcess, port: 4e3, url: "http://localhost:4000" }; backendProcess.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message) { console.log(colors.gray(`[Backend] ${message}`)); } }); backendProcess.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning")) { console.log(colors.red(`[Backend Error] ${message}`)); } }); backendProcess.on("error", (error) => { console.error(colors.red(`\uBC31\uC5D4\uB4DC \uC11C\uBC84 \uC624\uB958: ${error.message}`)); }); backendProcess.on("exit", (code) => { if (code !== 0 && code !== null) { console.log(colors.yellow(`\uBC31\uC5D4\uB4DC \uC11C\uBC84\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCF54\uB4DC: ${code}`)); } }); runningProcesses.push(processInfo); return processInfo; } async function startFrontendUI(projectPath) { console.log(colors.blue("\u{1F3A8} Debug UI \uC2DC\uC791 \uC911...")); const serverCode = ` const express = require('express'); const path = require('path'); const cors = require('cors'); const fs = require('fs'); const app = express(); const port = process.env.PORT || 8080; app.use(cors()); // \uC5EC\uB7EC \uAC00\uB2A5\uD55C frontend \uACBD\uB85C \uC2DC\uB3C4 const possiblePaths = [ path.join(__dirname, '..', '..', '..', 'apps', 'frontend', 'dist'), path.join(process.cwd(), 'apps', 'frontend', 'dist'), path.join(process.cwd(), '..', 'debugTimeMachine', 'apps', 'frontend', 'dist'), path.join(process.env.HOME, 'Desktop', 'Project', 'debugTimeMachine', 'apps', 'frontend', 'dist') ]; let frontendDistPath = null; for (const testPath of possiblePaths) { try { if (fs.existsSync(testPath) && fs.existsSync(path.join(testPath, 'index.html'))) { frontendDistPath = testPath; console.log('Frontend \uACBD\uB85C \uBC1C\uACAC: ' + frontendDistPath); break; } } catch (e) { // \uACBD\uB85C \uC811\uADFC \uC2E4\uD328, \uB2E4\uC74C \uACBD\uB85C \uC2DC\uB3C4 } } if (frontendDistPath) { // \uC2E4\uC81C frontend \uC571 \uC11C\uBE59 app.use(express.static(frontendDistPath)); app.get('*', (req, res) => { res.sendFile(path.join(frontendDistPath, 'index.html')); }); console.log('\u2705 Frontend \uC571\uC744 \uC11C\uBE59 \uC911: ' + frontendDistPath); } else { // Fallback HTML \uC81C\uACF5 const fallbackHTML = \` <!DOCTYPE html> <html> <head> <title>Debug Time Machine</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 40px; background: #f5f5f5; text-align: center; } .container { max-width: 600px; margin: 0 auto; background: white; border-radius: 12px; padding: 40px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .error { color: #e74c3c; margin: 20px 0; } .info { color: #3498db; margin: 20px 0; line-height: 1.6; } code { background: #f8f9fa; padding: 4px 8px; border-radius: 4px; font-family: monospace; } .paths { text-align: left; margin: 20px 0; background: #f8f9fa; padding: 20px; border-radius: 8px; } .paths h4 { margin-top: 0; } </style> </head> <body> <div class="container"> <h1>\u{1F570}\uFE0F Debug Time Machine</h1> <div class="error"> <h3>Frontend \uBE4C\uB4DC \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4</h3> </div> <div class="paths"> <h4>\uD655\uC778\uB41C \uACBD\uB85C\uB4E4:</h4> \${possiblePaths.map(p => '<div><code>' + p + '</code></div>').join('')} </div> <div class="info"> <p><strong>\uBC31\uC5D4\uB4DC\uB294 \uC815\uC0C1\uC801\uC73C\uB85C \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4:</strong></p> <p>WebSocket: <code>ws://localhost:4000/ws</code></p> <p>Health Check: <code>http://localhost:4000/health</code></p> <p>React Hook\uC744 \uC0AC\uC6A9\uD558\uBA74 \uBC31\uC5D4\uB4DC\uC5D0 \uC790\uB3D9\uC73C\uB85C \uC5F0\uACB0\uB429\uB2C8\uB2E4.</p> </div> </div> </body> </html> \`; app.get('*', (req, res) => { res.send(fallbackHTML); }); console.log('\u26A0\uFE0F Frontend \uBE4C\uB4DC \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC5B4 fallback UI\uB97C \uC81C\uACF5\uD569\uB2C8\uB2E4.'); } app.listen(port, () => { console.log('\u{1F3A8} Debug Time Machine UI\uAC00 \uD3EC\uD2B8 ' + port + '\uC5D0\uC11C \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4.'); console.log('URL: http://localhost:' + port); }); `; const tempUIPath = import_path.default.join(process.cwd(), ".debug-time-machine-ui.js"); await import_fs.promises.writeFile(tempUIPath, serverCode); const frontendProcess = (0, import_child_process.spawn)("node", [tempUIPath], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, PORT: "8080" }, shell: false }); const processInfo = { name: "Debug UI", process: frontendProcess, port: 8080, url: "http://localhost:8080" }; frontendProcess.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message) { console.log(colors.gray(`[Debug UI] ${message}`)); } }); frontendProcess.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning")) { console.log(colors.red(`[Debug UI Error] ${message}`)); } }); frontendProcess.on("error", (error) => { console.error(colors.red(`Debug UI \uC624\uB958: ${error.message}`)); }); frontendProcess.on("exit", (code) => { import_fs.promises.unlink(tempUIPath).catch(() => { }); if (code !== 0 && code !== null) { console.log(colors.yellow(`Debug UI\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCF54\uB4DC: ${code}`)); } }); runningProcesses.push(processInfo); return processInfo; } async function startDebugTimeMachine() { console.log(colors.bold(colors.green("\u{1F570}\uFE0F Debug Time Machine \uC2DC\uC791!"))); console.log(""); try { setupProcessCleanup(); const projectPath = await findProjectPath(); const backend = await startBackendServer(projectPath); const frontend = await startFrontendUI(projectPath); console.log(colors.blue("\u23F3 \uC11C\uBC84 \uC900\uBE44 \uB300\uAE30 \uC911...")); const backendReady = await waitForServer(backend.port); if (!backendReady) { throw new Error("\uBC31\uC5D4\uB4DC \uC11C\uBC84\uAC00 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."); } const frontendReady = await waitForServer(frontend.port); if (!frontendReady) { throw new Error("Debug UI \uC11C\uBC84\uAC00 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4."); } console.log(colors.blue("\u{1F310} Debug UI \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uAE30...")); await openBrowser(frontend.url); console.log(""); console.log(colors.bold(colors.green("\u{1F389} Debug Time Machine\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"))); console.log(""); console.log(colors.bold("\u{1F4CD} \uC11C\uBE44\uC2A4 \uC8FC\uC18C:")); console.log(` \u{1F41B} Debug UI: ${colors.cyan(frontend.url)}`); console.log(` \u{1F527} Backend: ${colors.cyan(backend.url)}`); console.log(""); console.log(colors.bold("\u{1F4A1} \uC0AC\uC6A9\uBC95:")); console.log(` \u2022 React \uC571\uC5D0\uC11C ${colors.cyan("useDebugTimeMachine()")} Hook\uC744 \uC0AC\uC6A9\uD558\uC138\uC694`); console.log(" \u2022 localhost:4000\uC5D0 \uC790\uB3D9\uC73C\uB85C \uC5F0\uACB0\uB429\uB2C8\uB2E4"); console.log(" \u2022 Debug UI\uC5D0\uC11C \uC2E4\uC2DC\uAC04 \uC774\uBCA4\uD2B8\uB97C \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4"); console.log(""); console.log(colors.gray("\u26A0\uFE0F \uC885\uB8CC\uD558\uB824\uBA74 Ctrl+C\uB97C \uB204\uB974\uC138\uC694.")); console.log(""); await new Promise((resolve) => { process.on("SIGINT", () => resolve()); process.on("SIGTERM", () => resolve()); }); } catch (error) { console.error(colors.red("\u274C Error:"), error instanceof Error ? error.message : String(error)); runningProcesses.forEach(({ process: process2 }) => { if (process2 && !process2.killed) { process2.kill("SIGTERM"); } }); process.exit(1); } } function showHelp() { console.log(colors.bold("\u{1F570}\uFE0F Debug Time Machine CLI")); console.log(""); console.log(colors.bold("\uC0AC\uC6A9\uBC95:")); console.log(" debug-time-machine [start] Debug Time Machine \uC11C\uBC84 \uC2DC\uC791"); console.log(" debug-time-machine --help \uB3C4\uC6C0\uB9D0 \uBCF4\uAE30"); console.log(""); console.log(colors.bold("\uC608\uC2DC:")); console.log(" debug-time-machine # \uC11C\uBC84 \uC2DC\uC791"); console.log(" debug-time-machine start # \uC11C\uBC84 \uC2DC\uC791"); console.log(""); console.log(colors.bold("\uC11C\uBC84 \uC8FC\uC18C:")); console.log(" Backend: http://localhost:4000"); console.log(" Debug UI: http://localhost:8080"); console.log(""); console.log(colors.bold("\uC694\uAD6C\uC0AC\uD56D:")); console.log(" \u2022 Node.js 18+"); console.log(" \u2022 npm \uB610\uB294 pnpm"); console.log(" \u2022 apps/backend\uC640 apps/frontend \uD3F4\uB354"); console.log(""); } function main() { const args = process.argv.slice(2); const command = args[0]; switch (command) { case void 0: case "start": startDebugTimeMachine(); break; case "--help": case "-h": showHelp(); break; default: console.error(colors.red(`\uC54C \uC218 \uC5C6\uB294 \uBA85\uB839\uC5B4: ${command}`)); console.log(colors.yellow("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uBA85\uB839\uC5B4: start, --help")); process.exit(1); } } if (typeof require !== "undefined" && require.main === module) { main(); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { startDebugTimeMachine });