UNPKG

debug-time-machine-cli

Version:

๐Ÿš€ Debug Time Machine CLI - ์™„์ „ ์ž๋™ํ™”๋œ React ๋””๋ฒ„๊น… ๋„๊ตฌ

1,244 lines (1,218 loc) โ€ข 55.4 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 __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 )); // src/bin/cli.ts var import_commander2 = require("commander"); var import_chalk4 = __toESM(require("chalk")); // src/commands/initCommand.ts var import_path = __toESM(require("path")); var import_fs_extra = __toESM(require("fs-extra")); var import_chalk = __toESM(require("chalk")); async function initCommand(options) { try { console.log(import_chalk.default.bold.blue("\u{1F680} Debug Time Machine \uD504\uB85C\uC81D\uD2B8 \uCD08\uAE30\uD654")); console.log(""); const projectName = options.name || "debug-time-machine-project"; const template = options.template || "react"; console.log(`\u{1F4C1} \uD504\uB85C\uC81D\uD2B8 \uC774\uB984: ${import_chalk.default.cyan(projectName)}`); console.log(`\u{1F4CB} \uD15C\uD50C\uB9BF: ${import_chalk.default.cyan(template)}`); console.log(""); const projectPath = import_path.default.join(process.cwd(), projectName); if (await import_fs_extra.default.pathExists(projectPath)) { console.error(import_chalk.default.red(`\u274C \uB514\uB809\uD1A0\uB9AC\uAC00 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${projectPath}`)); return; } console.log(import_chalk.default.blue("\u{1F4E6} \uD504\uB85C\uC81D\uD2B8 \uC0DD\uC131 \uC911...")); await import_fs_extra.default.ensureDir(projectPath); await createTemplateFiles(projectPath, projectName, template); console.log(import_chalk.default.green("\u2705 \uD504\uB85C\uC81D\uD2B8\uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uC0DD\uC131\uB418\uC5C8\uC2B5\uB2C8\uB2E4!")); console.log(""); console.log(import_chalk.default.bold("\uB2E4\uC74C \uB2E8\uACC4:")); console.log(` ${import_chalk.default.cyan("cd")} ${projectName}`); console.log(` ${import_chalk.default.cyan("npm install")}`); console.log(` ${import_chalk.default.cyan("npm run debug")}`); console.log(""); } catch (error) { console.error(import_chalk.default.red("\u274C \uD504\uB85C\uC81D\uD2B8 \uC0DD\uC131 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4:")); console.error(error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"); } } async function createTemplateFiles(projectPath, projectName, template) { const packageJson = { name: projectName, version: "1.0.0", description: "Debug Time Machine \uD504\uB85C\uC81D\uD2B8", scripts: { dev: template === "react" ? "vite --port 3000" : "node index.js", debug: `debug-time-machine start-with-app '${template === "react" ? "vite --port 3000" : "node index.js"}'`, build: template === "react" ? "vite build" : 'echo "Build complete"' }, dependencies: { "debug-time-machine-react-client": "^1.0.0", ...template === "react" ? { react: "^18.3.1", "react-dom": "^18.3.1" } : {} }, devDependencies: { ...template === "react" ? { "@types/react": "^18.3.1", "@types/react-dom": "^18.3.0", "@vitejs/plugin-react": "^4.2.1", typescript: "^5.4.5", vite: "^5.2.8" } : {} } }; await import_fs_extra.default.writeJson(import_path.default.join(projectPath, "package.json"), packageJson, { spaces: 2 }); if (template === "react") { await createReactTemplate(projectPath); } else { await createNodeTemplate(projectPath); } const readme = `# ${projectName} Debug Time Machine \uD504\uB85C\uC81D\uD2B8\uC785\uB2C8\uB2E4. ## \uC2DC\uC791\uD558\uAE30 \`\`\`bash npm install npm run debug # Debug Time Machine\uACFC \uD568\uAED8 \uC2E4\uD589 \`\`\` ## \uC77C\uBC18 \uC2E4\uD589 \`\`\`bash npm run dev # \uC77C\uBC18 \uC2E4\uD589 \`\`\` `; await import_fs_extra.default.writeFile(import_path.default.join(projectPath, "README.md"), readme); } async function createReactTemplate(projectPath) { const indexHtml = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Debug Time Machine App</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html> `; await import_fs_extra.default.writeFile(import_path.default.join(projectPath, "index.html"), indexHtml); const viteConfig = `import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], }) `; await import_fs_extra.default.writeFile(import_path.default.join(projectPath, "vite.config.ts"), viteConfig); const tsConfig = { compilerOptions: { target: "ES2020", lib: ["ES2020", "DOM", "DOM.Iterable"], module: "ESNext", skipLibCheck: true, moduleResolution: "bundler", allowImportingTsExtensions: true, resolveJsonModule: true, isolatedModules: true, noEmit: true, jsx: "react-jsx", strict: true, noUnusedLocals: true, noUnusedParameters: true, noFallthroughCasesInSwitch: true }, include: ["src"] }; await import_fs_extra.default.writeJson(import_path.default.join(projectPath, "tsconfig.json"), tsConfig, { spaces: 2 }); const srcPath = import_path.default.join(projectPath, "src"); await import_fs_extra.default.ensureDir(srcPath); const mainTsx = `import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode>, ) `; await import_fs_extra.default.writeFile(import_path.default.join(srcPath, "main.tsx"), mainTsx); const appTsx = `import { useState } from 'react' import { useDebugTimeMachine } from 'debug-time-machine-react-client' function App() { // Debug Time Machine Hook \uCD94\uAC00 useDebugTimeMachine({ autoConnect: true }); const [count, setCount] = useState(0) return ( <div style={{ padding: '2rem', fontFamily: 'Arial, sans-serif' }}> <h1>\u{1F570}\uFE0F Debug Time Machine App</h1> <p>Debug Time Machine\uC774 \uC124\uC815\uB41C React \uC571\uC785\uB2C8\uB2E4.</p> <div style={{ margin: '2rem 0' }}> <h2>\uCE74\uC6B4\uD130: {count}</h2> <button onClick={() => setCount(count + 1)} style={{ margin: '0 10px', padding: '10px 20px' }} > \uC99D\uAC00 (+) </button> <button onClick={() => setCount(count - 1)} style={{ margin: '0 10px', padding: '10px 20px' }} > \uAC10\uC18C (-) </button> </div> <div style={{ background: '#f0f0f0', padding: '1rem', borderRadius: '8px' }}> <h3>\u{1F41B} Debug \uBAA8\uB4DC \uC2E4\uD589 \uBC29\uBC95:</h3> <code>npm run debug</code> <p>\uC704 \uBA85\uB839\uC5B4\uB85C \uC2E4\uD589\uD558\uBA74 Debug Time Machine\uC774 \uC790\uB3D9\uC73C\uB85C \uC2DC\uC791\uB429\uB2C8\uB2E4!</p> </div> </div> ) } export default App `; await import_fs_extra.default.writeFile(import_path.default.join(srcPath, "App.tsx"), appTsx); } async function createNodeTemplate(projectPath) { const indexJs = `const { useDebugTimeMachine } = require('debug-time-machine-react-client'); console.log('\u{1F570}\uFE0F Debug Time Machine Node.js App'); console.log('Debug Time Machine\uC774 \uC124\uC815\uB41C Node.js \uC571\uC785\uB2C8\uB2E4.'); // \uAC04\uB2E8\uD55C HTTP \uC11C\uBC84 const http = require('http'); const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(\` <h1>\u{1F570}\uFE0F Debug Time Machine Node.js App</h1> <p>Debug Time Machine\uC774 \uC124\uC815\uB41C Node.js \uC11C\uBC84\uC785\uB2C8\uB2E4.</p> <p>\uD3EC\uD2B8: 3000</p> \`); }); server.listen(3000, () => { console.log('\uC11C\uBC84\uAC00 \uD3EC\uD2B8 3000\uC5D0\uC11C \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4.'); console.log('http://localhost:3000'); }); `; await import_fs_extra.default.writeFile(import_path.default.join(projectPath, "index.js"), indexJs); } // src/utils/logger.ts var import_chalk2 = __toESM(require("chalk")); var import_ora = __toESM(require("ora")); var Logger = class { /** * ์„ฑ๊ณต ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ */ static success(message) { console.log(import_chalk2.default.green("\u2713"), message); } /** * ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ */ static error(message) { console.error(import_chalk2.default.red("\u2717"), message); } /** * ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ */ static warning(message) { console.warn(import_chalk2.default.yellow("\u26A0"), message); } /** * ์ •๋ณด ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ */ static info(message) { console.log(import_chalk2.default.blue("\u2139"), message); } /** * ๋””๋ฒ„๊ทธ ๋ฉ”์‹œ์ง€ ์ถœ๋ ฅ */ static debug(message) { if (process.env.DEBUG) { console.log(import_chalk2.default.gray("\u{1F41B}"), message); } } /** * ์Šคํ”ผ๋„ˆ ์‹œ์ž‘ */ static startSpinner(message) { this._spinner = (0, import_ora.default)(message).start(); } /** * ์Šคํ”ผ๋„ˆ ์„ฑ๊ณต์œผ๋กœ ์ข…๋ฃŒ */ static succeedSpinner(message) { if (this._spinner) { this._spinner.succeed(message); this._spinner = null; } } /** * ์Šคํ”ผ๋„ˆ ์‹คํŒจ๋กœ ์ข…๋ฃŒ */ static failSpinner(message) { if (this._spinner) { this._spinner.fail(message); this._spinner = null; } } /** * ์Šคํ”ผ๋„ˆ ์ค‘๋‹จ */ static stopSpinner() { if (this._spinner) { this._spinner.stop(); this._spinner = null; } } /** * ์ œ๋ชฉ ์ถœ๋ ฅ */ static title(message) { console.log(); console.log(import_chalk2.default.bold.cyan(message)); console.log(import_chalk2.default.cyan("=".repeat(message.length))); } /** * ๋ถ€์ œ๋ชฉ ์ถœ๋ ฅ */ static subtitle(message) { console.log(); console.log(import_chalk2.default.bold(message)); console.log(import_chalk2.default.gray("-".repeat(message.length))); } /** * ๋นˆ ์ค„ ์ถœ๋ ฅ */ static newLine() { console.log(); } }; Logger._spinner = null; // src/utils/fileUtils.ts var import_fs_extra2 = __toESM(require("fs-extra")); var import_path2 = __toESM(require("path")); var FileUtils = class { /** * ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ */ static async directoryExists(dirPath) { try { const stats = await import_fs_extra2.default.stat(dirPath); return stats.isDirectory(); } catch { return false; } } /** * ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ */ static async fileExists(filePath) { try { const stats = await import_fs_extra2.default.stat(filePath); return stats.isFile(); } catch { return false; } } /** * ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ */ static async createDirectory(dirPath) { try { await import_fs_extra2.default.ensureDir(dirPath); Logger.debug(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131: ${dirPath}`); } catch (error) { throw new Error(`\uB514\uB809\uD1A0\uB9AC \uC0DD\uC131 \uC2E4\uD328: ${dirPath} - ${error}`); } } /** * ํŒŒ์ผ ์“ฐ๊ธฐ */ static async writeFile(filePath, content) { try { await import_fs_extra2.default.ensureDir(import_path2.default.dirname(filePath)); await import_fs_extra2.default.writeFile(filePath, content, "utf8"); Logger.debug(`\uD30C\uC77C \uC0DD\uC131: ${filePath}`); } catch (error) { throw new Error(`\uD30C\uC77C \uC4F0\uAE30 \uC2E4\uD328: ${filePath} - ${error}`); } } /** * ํŒŒ์ผ ์ฝ๊ธฐ */ static async readFile(filePath) { try { return await import_fs_extra2.default.readFile(filePath, "utf8"); } catch (error) { throw new Error(`\uD30C\uC77C \uC77D\uAE30 \uC2E4\uD328: ${filePath} - ${error}`); } } /** * JSON ํŒŒ์ผ ์ฝ๊ธฐ */ static async readJsonFile(filePath) { try { return await import_fs_extra2.default.readJson(filePath); } catch (error) { throw new Error(`JSON \uD30C\uC77C \uC77D\uAE30 \uC2E4\uD328: ${filePath} - ${error}`); } } /** * JSON ํŒŒ์ผ ์“ฐ๊ธฐ */ static async writeJsonFile(filePath, data) { try { await import_fs_extra2.default.ensureDir(import_path2.default.dirname(filePath)); await import_fs_extra2.default.writeJson(filePath, data, { spaces: 2 }); Logger.debug(`JSON \uD30C\uC77C \uC0DD\uC131: ${filePath}`); } catch (error) { throw new Error(`JSON \uD30C\uC77C \uC4F0\uAE30 \uC2E4\uD328: ${filePath} - ${error}`); } } /** * ํŒŒ์ผ ๋ณต์‚ฌ */ static async copyFile(src, dest) { try { await import_fs_extra2.default.ensureDir(import_path2.default.dirname(dest)); await import_fs_extra2.default.copy(src, dest); Logger.debug(`\uD30C\uC77C \uBCF5\uC0AC: ${src} -> ${dest}`); } catch (error) { throw new Error(`\uD30C\uC77C \uBCF5\uC0AC \uC2E4\uD328: ${src} -> ${dest} - ${error}`); } } /** * ๋””๋ ‰ํ† ๋ฆฌ ๋ณต์‚ฌ */ static async copyDirectory(src, dest) { try { await import_fs_extra2.default.copy(src, dest); Logger.debug(`\uB514\uB809\uD1A0\uB9AC \uBCF5\uC0AC: ${src} -> ${dest}`); } catch (error) { throw new Error(`\uB514\uB809\uD1A0\uB9AC \uBCF5\uC0AC \uC2E4\uD328: ${src} -> ${dest} - ${error}`); } } /** * ํŒŒ์ผ ๋˜๋Š” ๋””๋ ‰ํ† ๋ฆฌ ์‚ญ์ œ */ static async remove(targetPath) { try { await import_fs_extra2.default.remove(targetPath); Logger.debug(`\uC0AD\uC81C: ${targetPath}`); } catch (error) { throw new Error(`\uC0AD\uC81C \uC2E4\uD328: ${targetPath} - ${error}`); } } /** * ์ƒ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋กœ ๋ณ€ํ™˜ */ static resolvePath(...paths) { return import_path2.default.resolve(...paths); } /** * ํ˜„์žฌ ์ž‘์—… ๋””๋ ‰ํ† ๋ฆฌ ๋ฐ˜ํ™˜ */ static getCurrentDirectory() { return process.cwd(); } }; // src/utils/validationUtils.ts var ValidationUtils = class { /** * ํ”„๋กœ์ ํŠธ ์ด๋ฆ„ ๊ฒ€์ฆ */ static validateProjectName(name) { const validNameRegex = /^[a-z0-9-_]+$/; if (!name) { Logger.error("\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC774 \uD544\uC694\uD569\uB2C8\uB2E4."); return false; } if (name.length < 2) { Logger.error("\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC740 \uCD5C\uC18C 2\uC790 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4."); return false; } if (name.length > 50) { Logger.error("\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC740 \uCD5C\uB300 50\uC790\uAE4C\uC9C0 \uAC00\uB2A5\uD569\uB2C8\uB2E4."); return false; } if (!validNameRegex.test(name)) { Logger.error("\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC740 \uC18C\uBB38\uC790, \uC22B\uC790, \uD558\uC774\uD508(-), \uC5B8\uB354\uC2A4\uCF54\uC5B4(_)\uB9CC \uC0AC\uC6A9 \uAC00\uB2A5\uD569\uB2C8\uB2E4."); return false; } if (name.startsWith("-") || name.endsWith("-")) { Logger.error("\uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC740 \uD558\uC774\uD508(-)\uC73C\uB85C \uC2DC\uC791\uD558\uAC70\uB098 \uB05D\uB0A0 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."); return false; } return true; } /** * ํฌํŠธ ๋ฒˆํ˜ธ ๊ฒ€์ฆ */ static validatePort(port) { const portNumber = parseInt(port, 10); if (isNaN(portNumber)) { Logger.error("\uD3EC\uD2B8 \uBC88\uD638\uB294 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4."); return false; } if (portNumber < 1 || portNumber > 65535) { Logger.error("\uD3EC\uD2B8 \uBC88\uD638\uB294 1-65535 \uBC94\uC704\uC5EC\uC57C \uD569\uB2C8\uB2E4."); return false; } if (portNumber < 1024) { Logger.warning("1024 \uBBF8\uB9CC\uC758 \uD3EC\uD2B8\uB294 \uAD00\uB9AC\uC790 \uAD8C\uD55C\uC774 \uD544\uC694\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."); } return true; } /** * ํ˜ธ์ŠคํŠธ ์ฃผ์†Œ ๊ฒ€์ฆ */ static validateHost(host) { if (!host) { Logger.error("\uD638\uC2A4\uD2B8 \uC8FC\uC18C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."); return false; } const hostnameRegex = /^[a-zA-Z0-9.-]+$/; if (!hostnameRegex.test(host)) { Logger.error("\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uD638\uC2A4\uD2B8 \uC8FC\uC18C \uD615\uC2DD\uC785\uB2C8\uB2E4."); return false; } return true; } /** * ํŒŒ์ผ ๊ฒฝ๋กœ ๊ฒ€์ฆ */ static validateFilePath(filePath) { if (!filePath) { Logger.error("\uD30C\uC77C \uACBD\uB85C\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4."); return false; } const invalidChars = /[<>:"|?*]/; if (invalidChars.test(filePath)) { Logger.error("\uD30C\uC77C \uACBD\uB85C\uC5D0 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uBB38\uC790\uAC00 \uD3EC\uD568\uB418\uC5B4 \uC788\uC2B5\uB2C8\uB2E4."); return false; } return true; } /** * ์žฌ์ƒ ์†๋„ ๊ฒ€์ฆ */ static validateSpeed(speed) { const speedNumber = parseFloat(speed); if (isNaN(speedNumber)) { Logger.error("\uC7AC\uC0DD \uC18D\uB3C4\uB294 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4."); return false; } if (speedNumber <= 0) { Logger.error("\uC7AC\uC0DD \uC18D\uB3C4\uB294 0\uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4."); return false; } if (speedNumber > 10) { Logger.warning("\uC7AC\uC0DD \uC18D\uB3C4\uAC00 \uB108\uBB34 \uBE60\uB97C \uC218 \uC788\uC2B5\uB2C8\uB2E4."); } return true; } /** * ์‹œ๊ฐ„ ๊ฒ€์ฆ (์ดˆ ๋‹จ์œ„) */ static validateDuration(duration) { const durationNumber = parseInt(duration, 10); if (isNaN(durationNumber)) { Logger.error("\uC2DC\uAC04\uC740 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4."); return false; } if (durationNumber <= 0) { Logger.error("\uC2DC\uAC04\uC740 0\uBCF4\uB2E4 \uCEE4\uC57C \uD569\uB2C8\uB2E4."); return false; } if (durationNumber > 3600) { Logger.warning("\uAE30\uB85D \uC2DC\uAC04\uC774 1\uC2DC\uAC04\uC744 \uCD08\uACFC\uD569\uB2C8\uB2E4."); } return true; } }; // src/commands/startCommand.ts async function startCommand(options) { try { Logger.title("Debug Time Machine \uC11C\uBC84 \uC2DC\uC791"); if (!ValidationUtils.validatePort(options.port)) { return { success: false, message: "\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uD3EC\uD2B8 \uBC88\uD638\uC785\uB2C8\uB2E4." }; } if (!ValidationUtils.validateHost(options.host)) { return { success: false, message: "\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uD638\uC2A4\uD2B8 \uC8FC\uC18C\uC785\uB2C8\uB2E4." }; } const port = parseInt(options.port, 10); const host = options.host; Logger.info(`\uC11C\uBC84 \uC124\uC815:`); Logger.info(` \uD638\uC2A4\uD2B8: ${host}`); Logger.info(` \uD3EC\uD2B8: ${port}`); Logger.newLine(); Logger.startSpinner("\uC11C\uBC84 \uC2DC\uC791 \uC911..."); await simulateServerStart(host, port); Logger.succeedSpinner("\uC11C\uBC84\uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"); Logger.newLine(); Logger.success(`Debug Time Machine \uC11C\uBC84\uAC00 http://${host}:${port}\uC5D0\uC11C \uC2E4\uD589 \uC911\uC785\uB2C8\uB2E4.`); Logger.info("\uC11C\uBC84\uB97C \uC911\uB2E8\uD558\uB824\uBA74 Ctrl+C\uB97C \uB204\uB974\uC138\uC694."); Logger.newLine(); return { success: true, message: "\uC11C\uBC84\uAC00 \uC131\uACF5\uC801\uC73C\uB85C \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", data: { host, port } }; } catch (error) { Logger.failSpinner("\uC11C\uBC84 \uC2DC\uC791 \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4."); Logger.error(error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"); return { success: false, message: "\uC11C\uBC84 \uC2DC\uC791\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4." }; } } async function simulateServerStart(host, port) { return new Promise((resolve) => { setTimeout(() => { resolve(); }, 2e3); }); } function handleServerShutdown() { Logger.newLine(); Logger.info("\uC11C\uBC84\uB97C \uC911\uB2E8\uD558\uB294 \uC911..."); Logger.success("\uC11C\uBC84\uAC00 \uC548\uC804\uD558\uAC8C \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."); process.exit(0); } process.on("SIGINT", handleServerShutdown); process.on("SIGTERM", handleServerShutdown); // src/commands/startWithAppCommand.ts var import_commander = require("commander"); var import_chalk3 = __toESM(require("chalk")); var import_child_process = require("child_process"); var import_path3 = __toESM(require("path")); var import_fs = require("fs"); var runningProcesses = []; async function findAvailablePort(startPort) { for (let port = startPort; port <= startPort + 10; port++) { try { const response = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(1e3) }); continue; } catch (error) { return port; } } throw new Error(`\uD3EC\uD2B8 ${startPort}-${startPort + 10} \uBC94\uC704\uC5D0\uC11C \uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD3EC\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.`); } async function waitForServer(port, maxAttempts = 60) { console.log(import_chalk3.default.gray(` ${port} \uD3EC\uD2B8\uC5D0\uC11C \uC11C\uBC84 \uC751\uB2F5 \uB300\uAE30 \uC911...`)); for (let i = 0; i < maxAttempts; i++) { try { const response = await fetch(`http://localhost:${port}/health`, { signal: AbortSignal.timeout(2e3) }); if (response.ok) { console.log(import_chalk3.default.green(` \u2705 \uD3EC\uD2B8 ${port} \uC900\uBE44 \uC644\uB8CC!`)); return true; } } catch (error) { if (i > 0 && i % 15 === 0) { console.log(import_chalk3.default.gray(` \uC544\uC9C1 \uB300\uAE30 \uC911... (${i}/${maxAttempts}\uCD08)`)); } } await new Promise((resolve) => setTimeout(resolve, 1e3)); } console.log(import_chalk3.default.red(` \u274C \uD3EC\uD2B8 ${port} \uC11C\uBC84\uAC00 ${maxAttempts}\uCD08 \uD6C4\uC5D0\uB3C4 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`)); console.log(import_chalk3.default.yellow(` \u{1F50D} \uC218\uB3D9 \uD655\uC778: curl http://localhost:${port}/health`)); return false; } async function openBrowser(url) { try { const { default: open } = await import("open"); await open(url); } catch (error) { console.log(import_chalk3.default.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(import_chalk3.default.yellow("\n\u{1F9F9} \uD504\uB85C\uC138\uC2A4 \uC815\uB9AC \uC911...")); runningProcesses.forEach(({ name, process: process2 }) => { if (process2 && !process2.killed) { console.log(import_chalk3.default.gray(` \u274C ${name} \uC885\uB8CC \uC911...`)); process2.kill("SIGTERM"); setTimeout(() => { if (!process2.killed) { process2.kill("SIGKILL"); } }, 5e3); } }); console.log(import_chalk3.default.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() { console.log(import_chalk3.default.blue("\u{1F680} \uBC31\uC5D4\uB4DC \uC11C\uBC84 \uC2DC\uC791 \uC911...")); const backendAppPaths = [ import_path3.default.join(process.cwd(), "apps/backend"), import_path3.default.join(process.cwd(), "../apps/backend"), import_path3.default.join(process.cwd(), "../../apps/backend") ]; for (const backendAppPath of backendAppPaths) { try { const packageJsonPath = import_path3.default.join(backendAppPath, "package.json"); await import_fs.promises.access(packageJsonPath); console.log(import_chalk3.default.gray(`\uC2E4\uC81C Backend \uC571\uC744 \uC2E4\uD589: ${backendAppPath}`)); const backendProcess2 = (0, import_child_process.spawn)("pnpm", ["run", "dev"], { cwd: backendAppPath, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, PORT: "4000", NODE_ENV: "development" }, shell: false }); const processInfo2 = { name: "Backend Server (Dev)", process: backendProcess2, port: 4e3, url: "http://localhost:4000" }; backendProcess2.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("[nodemon]")) { console.log(import_chalk3.default.gray(`[Backend] ${message}`)); } }); backendProcess2.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning")) { console.log(import_chalk3.default.red(`[Backend Error] ${message}`)); } }); backendProcess2.on("error", (error) => { console.error(import_chalk3.default.red(`\uBC31\uC5D4\uB4DC \uC11C\uBC84 \uC624\uB958: ${error.message}`)); }); backendProcess2.on("exit", (code) => { if (code !== 0 && code !== null) { console.log(import_chalk3.default.yellow(`\uBC31\uC5D4\uB4DC \uC11C\uBC84\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCF54\uB4DC: ${code}`)); } }); runningProcesses.push(processInfo2); return processInfo2; } catch { continue; } } console.log(import_chalk3.default.gray("\uC2E4\uC81C Backend \uC571\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uBC88\uB4E4\uB41C \uC11C\uBC84\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.")); const backendServerPaths = [ import_path3.default.join(process.cwd(), "node_modules/debug-time-machine-cli/bundled/backend-server.js"), import_path3.default.join(__dirname, "../bundled/backend-server.js"), import_path3.default.join(__dirname, "../../bundled/backend-server.js"), import_path3.default.join(__dirname, "../../../bundled/backend-server.js") ]; let backendServerPath = null; for (const p of backendServerPaths) { try { await import_fs.promises.access(p); backendServerPath = p; console.log(import_chalk3.default.gray(`\uBC88\uB4E4\uB41C Backend \uC11C\uBC84 \uC2E4\uD589: ${p}`)); break; } catch { } } if (!backendServerPath) { console.log(import_chalk3.default.red("\uBC31\uC5D4\uB4DC \uC11C\uBC84 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC74C \uACBD\uB85C\uB4E4\uC744 \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4:")); backendServerPaths.forEach((p) => console.log(import_chalk3.default.gray(` - ${p}`))); throw new Error("\uBC88\uB4E4\uB41C \uBC31\uC5D4\uB4DC \uC11C\uBC84\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."); } const backendProcess = (0, import_child_process.spawn)("node", [backendServerPath], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, PORT: "4000" }, shell: false }); const processInfo = { name: "Backend Server (Bundled)", process: backendProcess, port: 4e3, url: "http://localhost:4000" }; backendProcess.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message) { console.log(import_chalk3.default.gray(`[Backend] ${message}`)); } }); backendProcess.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning")) { console.log(import_chalk3.default.red(`[Backend Error] ${message}`)); } }); backendProcess.on("error", (error) => { console.error(import_chalk3.default.red(`\uBC31\uC5D4\uB4DC \uC11C\uBC84 \uC624\uB958: ${error.message}`)); }); backendProcess.on("exit", (code) => { if (code !== 0 && code !== null) { console.log(import_chalk3.default.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() { console.log(import_chalk3.default.blue("\u{1F3A8} Debug UI \uC2DC\uC791 \uC911...")); const frontendAppPaths = [ import_path3.default.join(process.cwd(), "apps/frontend"), import_path3.default.join(process.cwd(), "../apps/frontend"), import_path3.default.join(process.cwd(), "../../apps/frontend") ]; for (const frontendAppPath of frontendAppPaths) { try { const packageJsonPath = import_path3.default.join(frontendAppPath, "package.json"); await import_fs.promises.access(packageJsonPath); console.log(import_chalk3.default.gray(`\uC2E4\uC81C Frontend \uC571\uC744 Vite\uB85C \uC2E4\uD589: ${frontendAppPath}`)); const frontendProcess2 = (0, import_child_process.spawn)("pnpm", ["run", "dev", "--port", "8080"], { cwd: frontendAppPath, stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, PORT: "8080" }, shell: false }); const processInfo2 = { name: "Debug UI (Vite)", process: frontendProcess2, port: 8080, url: "http://localhost:8080" }; frontendProcess2.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("ready in")) { console.log(import_chalk3.default.gray(`[Debug UI] ${message}`)); } }); frontendProcess2.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning") && !message.includes("Local:") && !message.includes("\u279C")) { console.log(import_chalk3.default.red(`[Debug UI Error] ${message}`)); } }); frontendProcess2.on("error", (error) => { console.error(import_chalk3.default.red(`Debug UI \uC624\uB958: ${error.message}`)); }); frontendProcess2.on("exit", (code) => { if (code !== 0 && code !== null) { console.log(import_chalk3.default.yellow(`Debug UI\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCF54\uB4DC: ${code}`)); } }); runningProcesses.push(processInfo2); return processInfo2; } catch { continue; } } console.log(import_chalk3.default.gray("\uC2E4\uC81C Frontend \uC571\uC744 \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uBC88\uB4E4\uB41C \uC11C\uBC84\uB97C \uC0AC\uC6A9\uD569\uB2C8\uB2E4.")); let frontendPort = 8080; try { frontendPort = await findAvailablePort(8080); if (frontendPort !== 8080) { console.log(import_chalk3.default.yellow(`\uD3EC\uD2B8 8080\uC774 \uC0AC\uC6A9 \uC911\uC774\uBBC0\uB85C \uD3EC\uD2B8 ${frontendPort}\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4.`)); } } catch (error) { console.log(import_chalk3.default.red("\uC0AC\uC6A9 \uAC00\uB2A5\uD55C \uD3EC\uD2B8\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uAE30\uBCF8 \uD3EC\uD2B8 8080\uC744 \uC0AC\uC6A9\uD569\uB2C8\uB2E4.")); frontendPort = 8080; } const frontendServerPaths = [ import_path3.default.join(process.cwd(), "node_modules/debug-time-machine-cli/bundled/frontend-server.js"), import_path3.default.join(__dirname, "../bundled/frontend-server.js"), import_path3.default.join(__dirname, "../../bundled/frontend-server.js"), import_path3.default.join(__dirname, "../../../bundled/frontend-server.js") ]; let frontendServerPath = null; for (const p of frontendServerPaths) { try { await import_fs.promises.access(p); frontendServerPath = p; console.log(import_chalk3.default.gray(`\uBC88\uB4E4\uB41C Frontend \uC11C\uBC84 \uC2E4\uD589: ${p}`)); break; } catch { } } if (!frontendServerPath) { console.log(import_chalk3.default.red("\uD504\uB860\uD2B8\uC5D4\uB4DC \uC11C\uBC84 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC74C \uACBD\uB85C\uB4E4\uC744 \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4:")); frontendServerPaths.forEach((p) => console.log(import_chalk3.default.gray(` - ${p}`))); throw new Error("\uBC88\uB4E4\uB41C \uD504\uB860\uD2B8\uC5D4\uB4DC \uC11C\uBC84\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."); } const frontendProcess = (0, import_child_process.spawn)("node", [frontendServerPath], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, PORT: frontendPort.toString() }, shell: false }); const processInfo = { name: "Debug UI (Bundled)", process: frontendProcess, port: frontendPort, url: `http://localhost:${frontendPort}` }; frontendProcess.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message) { console.log(import_chalk3.default.gray(`[Debug UI] ${message}`)); } }); frontendProcess.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning")) { console.log(import_chalk3.default.red(`[Debug UI Error] ${message}`)); } }); frontendProcess.on("error", (error) => { console.error(import_chalk3.default.red(`Debug UI \uC624\uB958: ${error.message}`)); }); frontendProcess.on("exit", (code) => { if (code !== 0 && code !== null) { console.log(import_chalk3.default.yellow(`Debug UI\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCF54\uB4DC: ${code}`)); } }); runningProcesses.push(processInfo); return processInfo; } async function startUserApp(command) { console.log(import_chalk3.default.blue(`\u{1F3AF} \uC0AC\uC6A9\uC790 \uC571 \uC2DC\uC791: ${command}`)); const parts = command.split(" ").filter((part) => part.trim() !== ""); const [cmd, ...args] = parts; const userAppProcess = (0, import_child_process.spawn)(cmd, args, { stdio: ["ignore", "pipe", "pipe"], shell: true, cwd: process.cwd(), env: { ...process.env, PORT: "3000" // ์‚ฌ์šฉ์ž ์•ฑ์„ 3000 ํฌํŠธ๋กœ ๊ณ ์ • } }); userAppProcess.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("webpack")) { console.log(import_chalk3.default.gray(`[User App] ${message}`)); } }); userAppProcess.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning")) { console.log(import_chalk3.default.yellow(`[User App Error] ${message}`)); } }); userAppProcess.on("error", (error) => { console.error(import_chalk3.default.red(`\uC0AC\uC6A9\uC790 \uC571 \uC624\uB958: ${error.message}`)); }); userAppProcess.on("exit", (code) => { console.log(import_chalk3.default.yellow(`\uC0AC\uC6A9\uC790 \uC571\uC774 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCF54\uB4DC: ${code}`)); process.exit(code || 0); }); console.log(import_chalk3.default.gray(" \uC0AC\uC6A9\uC790 \uC571\uC774 \uC2DC\uC791\uB420 \uB54C\uAE4C\uC9C0 \uB300\uAE30 \uC911...")); let userAppReady = false; for (let i = 0; i < 30; i++) { try { const response = await fetch("http://localhost:3000", { signal: AbortSignal.timeout(2e3) }); if (response.ok || response.status < 500) { userAppReady = true; console.log(import_chalk3.default.green(" \u2705 \uC0AC\uC6A9\uC790 \uC571 \uC900\uBE44 \uC644\uB8CC!")); break; } } catch { } if (i % 5 === 0 && i > 0) { console.log(import_chalk3.default.gray(` \uC544\uC9C1 \uB300\uAE30 \uC911... (${i}/30\uCD08)`)); } await new Promise((resolve) => setTimeout(resolve, 1e3)); } if (!userAppReady) { console.log(import_chalk3.default.red(" \u274C \uC0AC\uC6A9\uC790 \uC571\uC774 30\uCD08 \uD6C4\uC5D0\uB3C4 \uC900\uBE44\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.")); console.log(import_chalk3.default.yellow(" \uC218\uB3D9\uC73C\uB85C \uD655\uC778: curl http://localhost:3000")); } console.log(import_chalk3.default.blue("\u{1F504} Debug Time Machine \uD504\uB85D\uC2DC \uC2DC\uC791 \uC911...")); const proxyServerPaths = [ import_path3.default.join(process.cwd(), "node_modules/debug-time-machine-cli/bundled/inject-to-user-app.js"), import_path3.default.join(__dirname, "../bundled/inject-to-user-app.js"), import_path3.default.join(__dirname, "../../bundled/inject-to-user-app.js"), import_path3.default.join(__dirname, "../../../bundled/inject-to-user-app.js") ]; let proxyServerPath = null; for (const p of proxyServerPaths) { try { await import_fs.promises.access(p); proxyServerPath = p; console.log(import_chalk3.default.gray(`\uD504\uB85D\uC2DC \uC11C\uBC84 \uC2E4\uD589: ${p}`)); break; } catch { } } if (!proxyServerPath) { console.log(import_chalk3.default.red("\uD504\uB85D\uC2DC \uC11C\uBC84 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uB2E4\uC74C \uACBD\uB85C\uB4E4\uC744 \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4:")); proxyServerPaths.forEach((p) => console.log(import_chalk3.default.gray(` - ${p}`))); throw new Error("\uD504\uB85D\uC2DC \uC11C\uBC84\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."); } const proxyProcess = (0, import_child_process.spawn)("node", [proxyServerPath], { stdio: ["ignore", "pipe", "pipe"], env: { ...process.env, INJECTOR_PORT: "3001", USER_APP_PORT: "3000" }, shell: false }); proxyProcess.stdout?.on("data", (data) => { const message = data.toString().trim(); if (message) { console.log(import_chalk3.default.gray(`[Proxy] ${message}`)); } }); proxyProcess.stderr?.on("data", (data) => { const message = data.toString().trim(); if (message && !message.includes("warning")) { console.log(import_chalk3.default.red(`[Proxy Error] ${message}`)); } }); proxyProcess.on("error", (error) => { console.error(import_chalk3.default.red(`\uD504\uB85D\uC2DC \uC11C\uBC84 \uC624\uB958: ${error.message}`)); }); proxyProcess.on("exit", (code) => { if (code !== 0 && code !== null) { console.log(import_chalk3.default.yellow(`\uD504\uB85D\uC2DC \uC11C\uBC84\uAC00 \uC885\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uCF54\uB4DC: ${code}`)); } }); const processInfo = { name: "User App with Debug Proxy", process: userAppProcess, // ์ฃผ ํ”„๋กœ์„ธ์Šค๋Š” ์‚ฌ์šฉ์ž ์•ฑ port: 3001, // ์‚ฌ์šฉ์ž๋Š” ํ”„๋ก์‹œ ํฌํŠธ์— ์ ‘์† url: "http://localhost:3001" // ํ”„๋ก์‹œ URL }; runningProcesses.push(processInfo); runningProcesses.push({ name: "Debug Proxy Server", process: proxyProcess, port: 3001, url: "http://localhost:3001" }); return processInfo; } async function startWithAppCommand(appCommand, options) { console.log(import_chalk3.default.bold.green("\u{1F41B} Debug Time Machine \uC790\uB3D9 \uC2DC\uC791!")); console.log(import_chalk3.default.gray(`\uC2E4\uD589\uD560 \uC571: ${appCommand}`)); console.log(""); try { setupProcessCleanup(); const backend = await startBackendServer(); const frontend = await startFrontendUI(); console.log(import_chalk3.default.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."); } console.log(import_chalk3.default.green("\u2705 \uBC31\uC5D4\uB4DC \uC11C\uBC84 \uC900\uBE44 \uC644\uB8CC")); 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(import_chalk3.default.green("\u2705 Debug UI \uC11C\uBC84 \uC900\uBE44 \uC644\uB8CC")); if (!options.noBrowser) { console.log(import_chalk3.default.blue("\u{1F310} Debug UI \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uAE30...")); await openBrowser(frontend.url); } console.log(""); const userApp = await startUserApp(appCommand); console.log(""); console.log(import_chalk3.default.bold.green("\u{1F389} Debug Time Machine\uC774 \uC131\uACF5\uC801\uC73C\uB85C \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4!")); console.log(""); console.log(import_chalk3.default.bold("\u{1F4CD} \uC11C\uBE44\uC2A4 \uC8FC\uC18C:")); console.log(` \u{1F3AF} Your App: ${import_chalk3.default.cyan(userApp.url)} ${import_chalk3.default.gray("(Debug \uAE30\uB2A5 \uC790\uB3D9 \uC8FC\uC785)")}`); console.log(` \u{1F41B} Debug UI: ${import_chalk3.default.cyan(frontend.url)}`); console.log(` \u{1F527} Backend: ${import_chalk3.default.cyan(backend.url)}`); console.log(""); console.log(import_chalk3.default.bold("\u{1F4A1} \uC911\uC694 \uC548\uB0B4:")); console.log(` \u2022 ${import_chalk3.default.cyan("http://localhost:3001")} ${import_chalk3.default.white("\u2190 \uC774 \uC8FC\uC18C\uB85C \uC811\uC18D\uD558\uC138\uC694!")}`); console.log(` \u2022 Debug Time Machine\uC774 \uC790\uB3D9\uC73C\uB85C \uC8FC\uC785\uB418\uC5B4 Hook \uC5C6\uC774\uB3C4 \uC774\uBCA4\uD2B8\uAC00 \uCEA1\uCC98\uB429\uB2C8\uB2E4`); console.log(` \u2022 \uAE30\uC874 http://localhost:3000\uC740 \uC6D0\uBCF8 \uC571\uC774\uBBC0\uB85C Debug \uAE30\uB2A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4`); console.log(""); console.log(import_chalk3.default.gray("\u{1F504} \uC791\uB3D9 \uC6D0\uB9AC: \uC0AC\uC6A9\uC790 \uC571(3000) \u2192 \uD504\uB85D\uC2DC(3001) \u2192 \uBE0C\uB77C\uC6B0\uC800")); console.log(import_chalk3.default.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(import_chalk3.default.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 createStartWithAppCommand() { return new import_commander.Command("start-with-app").description("Debug Time Machine\uACFC \uD568\uAED8 \uC571\uC744 \uC790\uB3D9\uC73C\uB85C \uC2DC\uC791\uD569\uB2C8\uB2E4").argument("<command>", '\uC2E4\uD589\uD560 \uC571 \uBA85\uB839\uC5B4 (\uC608: "npm start", "yarn dev")').option("--no-browser", "Debug UI\uB97C \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC790\uB3D9\uC73C\uB85C \uC5F4\uC9C0 \uC54A\uC74C").action(startWithAppCommand); } // src/commands/recordCommand.ts async function recordCommand(options) { try { Logger.title("\uC560\uD50C\uB9AC\uCF00\uC774\uC158 \uC0C1\uD0DC \uAE30\uB85D \uC2DC\uC791"); if (!ValidationUtils.validateFilePath(options.output)) { return { success: false, message: "\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uCD9C\uB825 \uD30C\uC77C \uACBD\uB85C\uC785\uB2C8\uB2E4." }; } if (!ValidationUtils.validateDuration(options.duration)) { return { success: false, message: "\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uAE30\uB85D \uC2DC\uAC04\uC785\uB2C8\uB2E4." }; } const outputFile = options.output; const duration = parseInt(options.duration, 10); Logger.info(`\uAE30\uB85D \uC124\uC815:`); Logger.info(` \uCD9C\uB825 \uD30C\uC77C: ${outputFile}`); Logger.info(` \uAE30\uB85D \uC2DC\uAC04: ${duration}\uCD08`); Logger.newLine(); if (await FileUtils.fileExists(outputFile)) { Logger.warning(`\uD30C\uC77C\uC774 \uC774\uBBF8 \uC874\uC7AC\uD569\uB2C8\uB2E4: ${outputFile}`); Logger.info("\uAE30\uC874 \uD30C\uC77C\uC744 \uB36E\uC5B4\uC501\uB2C8\uB2E4."); } Logger.startSpinner("\uC0C1\uD0DC \uAE30\uB85D \uC911..."); const recordingData = await startRecording(duration); await FileUtils.writeJsonFile(outputFile, recordingData); Logger.succeedSpinner("\uC0C1\uD0DC \uAE30\uB85D\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"); Logger.newLine(); Logger.success(`\uAE30\uB85D \uD30C\uC77C\uC774 \uC800\uC7A5\uB418\uC5C8\uC2B5\uB2C8\uB2E4: ${outputFile}`); Logger.info(`\uCD1D ${recordingData.snapshots.length}\uAC1C\uC758 \uC2A4\uB0C5\uC0F7\uC774 \uAE30\uB85D\uB418\uC5C8\uC2B5\uB2C8\uB2E4.`); Logger.info(`\uAE30\uB85D \uC2DC\uAC04: ${recordingData.duration}\uCD08`); Logger.newLine(); return { success: true, message: "\uC0C1\uD0DC \uAE30\uB85D\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", data: { outputFile, snapshotCount: recordingData.snapshots.length, duration: recordingData.duration } }; } catch (error) { Logger.failSpinner("\uC0C1\uD0DC \uAE30\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4."); Logger.error(error instanceof Error ? error.message : "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"); return { success: false, message: "\uC0C1\uD0DC \uAE30\uB85D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4." }; } } async function startRecording(duration) { const startTime = Date.now(); const snapshots = []; return new Promise((resolve) => { const interval = setInterval(() => { const currentTime = Date.now(); const elapsed = Math.floor((currentTime - startTime) / 1e3); const snapshot = { id: `snapshot-${snapshots.length + 1}`, timestamp: currentTime, state: { counter: snapshots.length + 1, randomValue: Math.random(), timestamp: currentTime }, actionType: "AUTO_RECORD", description: `\uC790\uB3D9 \uAE30\uB85D \uC2A4\uB0C5\uC0F7 #${snapshots.length + 1}` }; snapshots.push(snapshot); if (elapsed >= duration) { clearInterval(interval); resolve({ version: "1.0.0", startTime, endTime: currentTime, duration: elapsed, snapshots, metadata: { recordedBy: "debug-time-machine-cli", totalSnapshots: snapshots.length, averageInterval: duration / snapshots.length } }); } }, 1e3); }); } function handleRecordingStop() { Logger.newLine(); Logger.info("\uAE30\uB85D\uC744 \uC911\uB2E8\uD558\uB294 \uC911..."); Logger.warning("\uAE30\uB85D\uC774 \uC911\uB2E8\uB418\uC5C8\uC2B5\uB2C8\uB2E4."); process.exit(0); } process.on("SIGINT", handleRecordingStop); process.on("SIGTERM", handleRecordingStop); // src/commands/replayCommand.ts async function replayCommand(options) { try { Logger.title("\uAE30\uB85D\uB41C \uC0C1\uD0DC \uC7AC\uC0DD \uC2DC\uC791"); if (!ValidationUtils.validateFilePath(options.input)) { return { success: false, message: "\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uC785\uB825 \uD30C\uC77C \uACBD\uB85C\uC785\uB2C8\uB2E4." }; } if (!ValidationUtils.validateSpeed(options.speed)) { return { success: false, message: "\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uC7AC\uC0DD \uC18D\uB3C4\uC785\uB2C8\uB2E4." }; } const inputFile = options.input; const speed = parseFloat(options.speed); if (!await FileUtils.fileExists(inputFile)) { Logger.error(`\uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: ${inputFile}`); return { success: false, message: "\uC785\uB825 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." }; } Logger.info(`\uC7AC\uC0DD \uC124\uC815:`); Logger.info(` \uC785\uB825 \uD30C\uC77C: ${inputFile}`); Logger.info(` \uC7AC\uC0DD \uC18D\uB3C4: ${speed}x`); Logger.newLine(); Logger.startSpinner("\uAE30\uB85D \uD30C\uC77C\uC744 \uB85C\uB4DC\uD558\uB294 \uC911..."); const recordingData = await FileUtils.readJsonFile(inputFile); if (!validateRecordingData(recordingData)) { Logger.failSpinner("\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uAE30\uB85D \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4."); return { success: false, message: "\uC62C\uBC14\uB974\uC9C0 \uC54A\uC740 \uAE30\uB85D \uD30C\uC77C \uD615\uC2DD\uC785\uB2C8\uB2E4." }; } Logger.succeedSpinner("\uAE30\uB85D \uD30C\uC77C\uC744 \uC131\uACF5\uC801\uC73C\uB85C \uB85C\uB4DC\uD588\uC2B5\uB2C8\uB2E4."); Logger.info(`\uAE30\uB85D \uC815\uBCF4:`); Logger.info(` \uCD1D \uC2A4\uB0C5\uC0F7 \uC218: ${recordingData.snapshots.length}`); Logger.info(` \uAE30\uB85D \uC2DC\uAC04: ${recordingData.duration}\uCD08`); Logger.info(` \uAE30\uB85D \uB0A0\uC9DC: ${new Date(recordingData.startTime).toLocaleString()}`); Logger.newLine(); Logger.startSpinner("\uC7AC\uC0DD \uC2DC\uC791..."); const replayResult = await startReplay(recordingData, speed); Logger.succeedSpinner("\uC7AC\uC0DD\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4!"); Logger.newLine(); Logger.success(`\uCD1D ${replayResult.processedSnapshots}\uAC1C\uC758 \uC2A4\uB0C5\uC0F7\uC744 \uC7AC\uC0DD\uD588\uC2B5\uB2C8\uB2E4.`); Logger.info(`\uC7AC\uC0DD \uC2DC\uAC04: ${replayResult.actualDuration}\uCD08`); Logger.newLine(); return { success: true, message: "\uC7AC\uC0DD\uC774 \uC644\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4.", data: replayResult }; } catch (error) { Logger.failSpinner("\uC7AC\uC0DD \uC911 \uC624\uB958\uAC00 \u