UNPKG

debug-time-machine-cli

Version:

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

1,471 lines (1,420 loc) โ€ข 63.7 kB
"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/index.ts var index_exports = {}; __export(index_exports, { FileUtils: () => FileUtils, Logger: () => Logger, TemplateUtils: () => TemplateUtils, ValidationUtils: () => ValidationUtils, createStartWithAppCommand: () => createStartWithAppCommand, handleRecordingStop: () => handleRecordingStop, handleReplayStop: () => handleReplayStop, handleServerShutdown: () => handleServerShutdown, initCommand: () => initCommand, recordCommand: () => recordCommand, replayCommand: () => replayCommand, startCommand: () => startCommand, startWithAppCommand: () => startWithAppCommand }); module.exports = __toCommonJS(index_exports); // 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/templateUtils.ts var TemplateUtils = class { /** * React ํ…œํ”Œ๋ฆฟ ๋ฐ˜ํ™˜ */ static getReactTemplate(projectName) { return { name: "react", description: "React + TypeScript + Vite \uD15C\uD50C\uB9BF", files: [ { path: "package.json", content: this._generateReactPackageJson(projectName) }, { path: "index.html", content: this._generateReactIndexHtml(projectName) }, { path: "src/main.tsx", content: this._generateReactMainTsx() }, { path: "src/App.tsx", content: this._generateReactAppTsx() }, { path: "src/vite-env.d.ts", content: this._generateViteEnvDts() }, { path: "vite.config.ts", content: this._generateViteConfig() }, { path: "tsconfig.json", content: this._generateReactTsConfig() } ], dependencies: [ "react", "react-dom", "debug-time-machine-core", "debug-time-machine-ui" ], devDependencies: [ "@types/react", "@types/react-dom", "@vitejs/plugin-react", "typescript", "vite" ], scripts: { dev: "vite", build: "tsc && vite build", preview: "vite preview", lint: "eslint . --ext ts,tsx --fix", "type-check": "tsc --noEmit" } }; } /** * Node.js ํ…œํ”Œ๋ฆฟ ๋ฐ˜ํ™˜ */ static getNodeTemplate(projectName) { return { name: "node", description: "Node.js + TypeScript \uD15C\uD50C\uB9BF", files: [ { path: "package.json", content: this._generateNodePackageJson(projectName) }, { path: "src/index.ts", content: this._generateNodeIndexTs() }, { path: "tsconfig.json", content: this._generateNodeTsConfig() } ], dependencies: ["debug-time-machine-core"], devDependencies: ["@types/node", "typescript", "ts-node", "nodemon"], scripts: { start: "node dist/index.js", dev: "nodemon src/index.ts", build: "tsc", "type-check": "tsc --noEmit" } }; } /** * Express ํ…œํ”Œ๋ฆฟ ๋ฐ˜ํ™˜ */ static getExpressTemplate(projectName) { return { name: "express", description: "Express.js + TypeScript \uD15C\uD50C\uB9BF", files: [ { path: "package.json", content: this._generateExpressPackageJson(projectName) }, { path: "src/index.ts", content: this._generateExpressIndexTs() }, { path: "src/server.ts", content: this._generateExpressServerTs() }, { path: "tsconfig.json", content: this._generateNodeTsConfig() } ], dependencies: ["express", "ws", "debug-time-machine-core"], devDependencies: [ "@types/node", "@types/express", "@types/ws", "typescript", "ts-node", "nodemon" ], scripts: { start: "node dist/index.js", dev: "nodemon src/index.ts", build: "tsc", "type-check": "tsc --noEmit" } }; } // Private template generators static _generateReactPackageJson(projectName) { return JSON.stringify( { name: projectName, private: true, version: "0.0.0", type: "module", scripts: {}, dependencies: {}, devDependencies: {} }, null, 2 ); } static _generateReactIndexHtml(projectName) { return `<!doctype html> <html lang="ko"> <head> <meta charset="UTF-8" /> <link rel="icon" type="image/svg+xml" href="/vite.svg" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>${projectName}</title> </head> <body> <div id="root"></div> <script type="module" src="/src/main.tsx"></script> </body> </html>`; } static _generateReactMainTsx() { return `import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App.tsx'; import './index.css'; ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App /> </React.StrictMode>, );`; } static _generateReactAppTsx() { return `import React from 'react'; import { TimeTravelEngine } from 'debug-time-machine-core'; const timeTravelEngine = new TimeTravelEngine(); function App(): JSX.Element { const [count, setCount] = React.useState(0); const handleIncrement = (): void => { const newCount = count + 1; setCount(newCount); // Create snapshot for debugging timeTravelEngine.createSnapshot( { count: newCount }, 'INCREMENT_COUNT' ); }; return ( <div className="App"> <h1>Debug Time Machine React App</h1> <div> <button onClick={handleIncrement}> count is {count} </button> </div> </div> ); } export default App;`; } static _generateViteEnvDts() { return `/// <reference types="vite/client" />`; } static _generateViteConfig() { return `import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], });`; } static _generateReactTsConfig() { return JSON.stringify( { compilerOptions: { target: "ES2020", useDefineForClassFields: true, 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"], references: [{ path: "./tsconfig.node.json" }] }, null, 2 ); } static _generateNodePackageJson(projectName) { return JSON.stringify( { name: projectName, version: "1.0.0", description: "", main: "dist/index.js", scripts: {}, dependencies: {}, devDependencies: {} }, null, 2 ); } static _generateNodeIndexTs() { return `import { TimeTravelEngine } from 'debug-time-machine-core'; const timeTravelEngine = new TimeTravelEngine(); console.log('Debug Time Machine Node.js App Started'); // Example state let appState = { counter: 0, message: 'Hello World', }; // Create initial snapshot timeTravelEngine.createSnapshot(appState, 'INITIAL_STATE'); // Simulate state changes setInterval(() => { appState.counter++; appState.message = \`Counter: \${appState.counter}\`; timeTravelEngine.createSnapshot(appState, 'UPDATE_COUNTER'); console.log('State updated:', appState); }, 2000);`; } static _generateExpressPackageJson(projectName) { return JSON.stringify( { name: projectName, version: "1.0.0", description: "", main: "dist/index.js", scripts: {}, dependencies: {}, devDependencies: {} }, null, 2 ); } static _generateExpressIndexTs() { return `import { startServer } from './server'; const PORT = process.env.PORT || 3000; startServer(Number(PORT)) .then(() => { console.log(\`Server started on port \${PORT}\`); }) .catch((error) => { console.error('Failed to start server:', error); process.exit(1); });`; } static _generateExpressServerTs() { return `import express from 'express'; import { createServer } from 'http'; import { WebSocketServer } from 'ws'; import { TimeTravelEngine } from 'debug-time-machine-core'; const timeTravelEngine = new TimeTravelEngine(); export async function startServer(port: number): Promise<void> { const app = express(); const server = createServer(app); const wss = new WebSocketServer({ server }); app.use(express.json()); // REST endpoints app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); app.get('/api/snapshots', (req, res) => { const snapshots = timeTravelEngine.getAllSnapshots(); res.json(snapshots); }); // WebSocket connection wss.on('connection', (ws) => { console.log('Client connected'); ws.on('message', (message) => { try { const data = JSON.parse(message.toString()); console.log('Received:', data); // Echo back ws.send(JSON.stringify({ type: 'echo', data, timestamp: Date.now(), })); } catch (error) { console.error('Error parsing message:', error); } }); ws.on('close', () => { console.log('Client disconnected'); }); }); return new Promise((resolve) => { server.listen(port, () => { resolve(); }); }); }`; } static _generateNodeTsConfig() { return JSON.stringify( { compilerOptions: { target: "ES2020", module: "commonjs", lib: ["ES2020"], outDir: "./dist", rootDir: "./src", strict: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true, declaration: true, declarationMap: true, sourceMap: true }, include: ["src/**/*"], exclude: ["node_modules", "dist"] }, null, 2 ); } }; // 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!"