debug-time-machine-cli
Version:
๐ Debug Time Machine CLI - ์์ ์๋ํ๋ React ๋๋ฒ๊น ๋๊ตฌ
1,471 lines (1,420 loc) โข 63.7 kB
JavaScript
"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!"