reverse-engine 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of reverse-engine might be problematic. Click here for more details.
- package/dist/cli/index.js +133 -74
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4,11 +4,36 @@ import chalk from 'chalk';
|
|
|
4
4
|
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
5
5
|
import { existsSync } from 'fs';
|
|
6
6
|
import { execFileSync } from 'child_process';
|
|
7
|
-
import { dirname, join } from 'path';
|
|
7
|
+
import { dirname, join, resolve } from 'path';
|
|
8
8
|
import { createRequire } from 'module';
|
|
9
9
|
import { analyze } from '../analyzer/index.js';
|
|
10
10
|
import { generateReport } from '../docgen/index.js';
|
|
11
11
|
import { generateTests } from '../testgen/index.js';
|
|
12
|
+
// ─── 기본값 ───
|
|
13
|
+
const DEFAULT_OUTPUT = '.reverse-engine';
|
|
14
|
+
/** 프로젝트 루트 자동 감지: package.json 또는 .git이 있는 디렉토리 */
|
|
15
|
+
function detectProjectRoot(startDir = process.cwd()) {
|
|
16
|
+
let dir = resolve(startDir);
|
|
17
|
+
while (dir) {
|
|
18
|
+
if (existsSync(join(dir, 'package.json')) ||
|
|
19
|
+
existsSync(join(dir, 'pyproject.toml')) ||
|
|
20
|
+
existsSync(join(dir, '.git'))) {
|
|
21
|
+
return dir;
|
|
22
|
+
}
|
|
23
|
+
const parent = dirname(dir);
|
|
24
|
+
if (parent === dir)
|
|
25
|
+
break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
return process.cwd();
|
|
29
|
+
}
|
|
30
|
+
/** 출력 디렉토리 경로 resolve */
|
|
31
|
+
function resolveOutput(outputOpt, sourcePath) {
|
|
32
|
+
if (outputOpt)
|
|
33
|
+
return resolve(outputOpt);
|
|
34
|
+
const base = sourcePath ? resolve(sourcePath) : process.cwd();
|
|
35
|
+
return join(base, DEFAULT_OUTPUT);
|
|
36
|
+
}
|
|
12
37
|
// ─── 네이티브 바이너리 탐색 ───
|
|
13
38
|
function findNativeBinary() {
|
|
14
39
|
const key = `${process.platform}-${process.arch}`;
|
|
@@ -32,116 +57,150 @@ function runNative(args) {
|
|
|
32
57
|
const nativeBin = findNativeBinary();
|
|
33
58
|
const program = new Command();
|
|
34
59
|
if (nativeBin) {
|
|
35
|
-
console.log(chalk.dim(`⚡
|
|
60
|
+
console.log(chalk.dim(`⚡ native`));
|
|
36
61
|
}
|
|
37
62
|
program
|
|
38
63
|
.name('reverse-engine')
|
|
39
|
-
.version('0.
|
|
40
|
-
.description('웹 서비스 역분석 자동화 도구
|
|
41
|
-
// analyze
|
|
64
|
+
.version('0.2.0')
|
|
65
|
+
.description('웹 서비스 역분석 자동화 도구');
|
|
66
|
+
// ─── analyze ───
|
|
42
67
|
program
|
|
43
|
-
.command('analyze
|
|
68
|
+
.command('analyze')
|
|
69
|
+
.argument('[path]', '소스코드 경로 (생략하면 현재 디렉토리에서 자동 감지)')
|
|
70
|
+
.option('--framework <name>', '프레임워크 지정', 'auto')
|
|
71
|
+
.option('--include <patterns>', '포함 패턴 (쉼표 구분)')
|
|
72
|
+
.option('-o, --output <dir>', '출력 디렉토리')
|
|
44
73
|
.description('소스코드 정적 분석')
|
|
45
|
-
.
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.action(async (sourcePath, opts) => {
|
|
49
|
-
// 네이티브 바이너리가 있으면 Rust 사용 (5~10x 빠름)
|
|
74
|
+
.action(async (path, opts) => {
|
|
75
|
+
const sourcePath = path ? resolve(path) : detectProjectRoot();
|
|
76
|
+
const outputDir = resolveOutput(opts.output, sourcePath);
|
|
50
77
|
if (nativeBin) {
|
|
51
78
|
runNative(['analyze', sourcePath, '--framework', opts.framework]);
|
|
52
79
|
return;
|
|
53
80
|
}
|
|
54
|
-
console.log(chalk.green('▶'), '코드
|
|
81
|
+
console.log(chalk.green('▶'), '코드 분석:', chalk.cyan(sourcePath));
|
|
55
82
|
const result = await analyze(sourcePath, {
|
|
56
83
|
framework: opts.framework,
|
|
57
|
-
include: opts.include
|
|
84
|
+
include: opts.include?.split(','),
|
|
58
85
|
});
|
|
59
|
-
console.log(chalk.green('✓'), '
|
|
86
|
+
console.log(chalk.green('✓'), '분석 완료!');
|
|
60
87
|
console.log(` 프레임워크: ${result.framework}`);
|
|
61
|
-
console.log(`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
console.log(` 라우트: ${result.routes.length}개`);
|
|
65
|
-
console.log(` 의존성: ${result.dependencies.length}개`);
|
|
66
|
-
await mkdir(opts.output, { recursive: true });
|
|
67
|
-
const outputPath = `${opts.output}/analysis.json`;
|
|
88
|
+
console.log(` 컴포넌트 ${result.components.length} | 함수 ${result.functions.length} | API ${result.apiClients.length} | 라우트 ${result.routes.length} | 의존성 ${result.dependencies.length}`);
|
|
89
|
+
await mkdir(outputDir, { recursive: true });
|
|
90
|
+
const outputPath = join(outputDir, 'analysis.json');
|
|
68
91
|
await writeFile(outputPath, JSON.stringify(result, null, 2));
|
|
69
|
-
console.log(`
|
|
92
|
+
console.log(` → ${chalk.cyan(outputPath)}`);
|
|
70
93
|
});
|
|
71
|
-
// report
|
|
94
|
+
// ─── report ───
|
|
72
95
|
program
|
|
73
96
|
.command('report')
|
|
74
|
-
.description('분석 결과로 리포트 생성')
|
|
75
|
-
.
|
|
76
|
-
.option('--format <formats>', '출력 형식
|
|
77
|
-
.option('--output <dir>', '출력 디렉토리'
|
|
97
|
+
.description('분석 결과로 리포트 생성 (Excel, Mermaid)')
|
|
98
|
+
.option('--input <file>', '분석 결과 JSON (생략하면 .reverse-engine/analysis.json)')
|
|
99
|
+
.option('--format <formats>', '출력 형식', 'excel,mermaid')
|
|
100
|
+
.option('-o, --output <dir>', '출력 디렉토리')
|
|
78
101
|
.action(async (opts) => {
|
|
79
|
-
|
|
80
|
-
|
|
102
|
+
const inputPath = opts.input || join(DEFAULT_OUTPUT, 'analysis.json');
|
|
103
|
+
if (!existsSync(inputPath)) {
|
|
104
|
+
console.log(chalk.red('✗'), `분석 결과를 찾을 수 없습니다: ${inputPath}`);
|
|
105
|
+
console.log(` 먼저 ${chalk.cyan('reverse-engine analyze')} 를 실행하세요.`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const outputDir = opts.output || join(dirname(inputPath), 'reports');
|
|
109
|
+
const data = JSON.parse(await readFile(inputPath, 'utf-8'));
|
|
81
110
|
const formats = opts.format.split(',');
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
console.log(chalk.green('▶'), '리포트 생성');
|
|
112
|
+
const outputs = await generateReport(data, { formats, outputDir });
|
|
113
|
+
console.log(chalk.green('✓'), '완료!');
|
|
84
114
|
outputs.forEach(p => console.log(` → ${chalk.cyan(p)}`));
|
|
85
115
|
});
|
|
86
|
-
// test
|
|
116
|
+
// ─── test ───
|
|
87
117
|
program
|
|
88
118
|
.command('test')
|
|
89
119
|
.description('테스트 코드 자동 생성')
|
|
90
|
-
.
|
|
91
|
-
.option('--type <types>', '테스트 종류
|
|
92
|
-
.option('--output <dir>', '출력 디렉토리'
|
|
120
|
+
.option('--input <file>', '분석 결과 JSON (생략하면 .reverse-engine/analysis.json)')
|
|
121
|
+
.option('--type <types>', '테스트 종류', 'e2e,api')
|
|
122
|
+
.option('-o, --output <dir>', '출력 디렉토리')
|
|
93
123
|
.action(async (opts) => {
|
|
94
|
-
|
|
95
|
-
|
|
124
|
+
const inputPath = opts.input || join(DEFAULT_OUTPUT, 'analysis.json');
|
|
125
|
+
if (!existsSync(inputPath)) {
|
|
126
|
+
console.log(chalk.red('✗'), `분석 결과를 찾을 수 없습니다: ${inputPath}`);
|
|
127
|
+
console.log(` 먼저 ${chalk.cyan('reverse-engine analyze')} 를 실행하세요.`);
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
const outputDir = opts.output || join(dirname(inputPath), 'tests');
|
|
131
|
+
const data = JSON.parse(await readFile(inputPath, 'utf-8'));
|
|
96
132
|
const types = opts.type.split(',');
|
|
97
|
-
|
|
98
|
-
|
|
133
|
+
console.log(chalk.green('▶'), '테스트 코드 생성');
|
|
134
|
+
const files = await generateTests(data, { types, outputDir });
|
|
135
|
+
console.log(chalk.green('✓'), `완료! (${files.length}개 파일)`);
|
|
99
136
|
files.forEach(p => console.log(` → ${chalk.cyan(p)}`));
|
|
100
137
|
});
|
|
101
|
-
// full
|
|
138
|
+
// ─── full ───
|
|
102
139
|
program
|
|
103
140
|
.command('full')
|
|
104
|
-
.
|
|
105
|
-
.requiredOption('--source <path>', '소스코드 경로')
|
|
106
|
-
.option('--output <dir>', '출력 디렉토리', './output')
|
|
141
|
+
.argument('[path]', '소스코드 경로 (생략하면 현재 디렉토리)')
|
|
107
142
|
.option('--framework <name>', '프레임워크', 'auto')
|
|
108
|
-
.
|
|
109
|
-
|
|
110
|
-
|
|
143
|
+
.option('-o, --output <dir>', '출력 디렉토리 (기본: <프로젝트>/.reverse-engine)')
|
|
144
|
+
.description('전체 파이프라인 (analyze → report → test)')
|
|
145
|
+
.action(async (path, opts) => {
|
|
146
|
+
const sourcePath = path ? resolve(path) : detectProjectRoot();
|
|
147
|
+
const outputDir = resolveOutput(opts.output, sourcePath);
|
|
148
|
+
console.log(chalk.green('\n◆'), 'ReversEngine', nativeBin ? chalk.dim('⚡') : '', '\n');
|
|
149
|
+
console.log(` 소스: ${chalk.cyan(sourcePath)}`);
|
|
150
|
+
console.log(` 출력: ${chalk.cyan(outputDir)}\n`);
|
|
151
|
+
await mkdir(outputDir, { recursive: true });
|
|
152
|
+
const analysisPath = join(outputDir, 'analysis.json');
|
|
153
|
+
// Step 1: 분석
|
|
154
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
111
155
|
if (nativeBin) {
|
|
156
|
+
runNative(['analyze', sourcePath, '--framework', opts.framework || 'auto']);
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
const result = await analyze(sourcePath, { framework: opts.framework });
|
|
160
|
+
console.log(chalk.green('✓'), `분석: 컴포넌트 ${result.components.length} | 함수 ${result.functions.length} | API ${result.apiClients.length} | 라우트 ${result.routes.length}`);
|
|
161
|
+
await writeFile(analysisPath, JSON.stringify(result, null, 2));
|
|
162
|
+
}
|
|
163
|
+
// Step 2: 리포트
|
|
164
|
+
if (existsSync(analysisPath)) {
|
|
165
|
+
const data = JSON.parse(await readFile(analysisPath, 'utf-8'));
|
|
112
166
|
console.log(chalk.gray('━'.repeat(50)));
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
167
|
+
const reports = await generateReport(data, { outputDir: join(outputDir, 'reports') });
|
|
168
|
+
console.log(chalk.green('✓'), '리포트:', reports.map(p => chalk.cyan(p.split('/').pop())).join(', '));
|
|
169
|
+
// Step 3: 테스트
|
|
170
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
171
|
+
const tests = await generateTests(data, { outputDir: join(outputDir, 'tests') });
|
|
172
|
+
console.log(chalk.green('✓'), `테스트: ${tests.length}개 파일`);
|
|
173
|
+
}
|
|
174
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
175
|
+
console.log(chalk.green('\n✓'), '완료!', chalk.dim(outputDir), '\n');
|
|
176
|
+
});
|
|
177
|
+
// ─── 기본 명령 (인자 없이 실행 시 full과 동일) ───
|
|
178
|
+
program
|
|
179
|
+
.action(async () => {
|
|
180
|
+
// 아무 서브커맨드 없이 실행하면 full 실행
|
|
181
|
+
const sourcePath = detectProjectRoot();
|
|
182
|
+
const outputDir = resolveOutput(undefined, sourcePath);
|
|
183
|
+
if (!existsSync(join(sourcePath, 'package.json')) && !existsSync(join(sourcePath, '.git'))) {
|
|
184
|
+
program.help();
|
|
127
185
|
return;
|
|
128
186
|
}
|
|
129
|
-
|
|
187
|
+
console.log(chalk.green('\n◆'), 'ReversEngine', nativeBin ? chalk.dim('⚡') : '', '\n');
|
|
188
|
+
console.log(` 소스: ${chalk.cyan(sourcePath)}`);
|
|
189
|
+
console.log(` 출력: ${chalk.cyan(outputDir)}\n`);
|
|
190
|
+
await mkdir(outputDir, { recursive: true });
|
|
191
|
+
const analysisPath = join(outputDir, 'analysis.json');
|
|
130
192
|
console.log(chalk.gray('━'.repeat(50)));
|
|
131
|
-
const result = await analyze(
|
|
132
|
-
console.log(chalk.green('✓'),
|
|
133
|
-
await
|
|
134
|
-
|
|
135
|
-
|
|
193
|
+
const result = await analyze(sourcePath, {});
|
|
194
|
+
console.log(chalk.green('✓'), `분석: 컴포넌트 ${result.components.length} | 함수 ${result.functions.length} | API ${result.apiClients.length} | 라우트 ${result.routes.length}`);
|
|
195
|
+
await writeFile(analysisPath, JSON.stringify(result, null, 2));
|
|
196
|
+
const data = JSON.parse(await readFile(analysisPath, 'utf-8'));
|
|
197
|
+
console.log(chalk.gray('━'.repeat(50)));
|
|
198
|
+
const reports = await generateReport(data, { outputDir: join(outputDir, 'reports') });
|
|
199
|
+
console.log(chalk.green('✓'), '리포트:', reports.map(p => chalk.cyan(p.split('/').pop())).join(', '));
|
|
136
200
|
console.log(chalk.gray('━'.repeat(50)));
|
|
137
|
-
const
|
|
138
|
-
console.log(chalk.green('✓'),
|
|
139
|
-
reports.forEach(p => console.log(` → ${chalk.cyan(p)}`));
|
|
140
|
-
// Step 3: 테스트
|
|
201
|
+
const tests = await generateTests(data, { outputDir: join(outputDir, 'tests') });
|
|
202
|
+
console.log(chalk.green('✓'), `테스트: ${tests.length}개 파일`);
|
|
141
203
|
console.log(chalk.gray('━'.repeat(50)));
|
|
142
|
-
|
|
143
|
-
console.log(chalk.green('✓'), `테스트 생성 완료! (${tests.length}개)`);
|
|
144
|
-
tests.forEach(p => console.log(` → ${chalk.cyan(p)}`));
|
|
145
|
-
console.log(chalk.green('\n✓'), '전체 파이프라인 완료!', chalk.cyan(opts.output), '\n');
|
|
204
|
+
console.log(chalk.green('\n✓'), '완료!', chalk.dim(outputDir), '\n');
|
|
146
205
|
});
|
|
147
206
|
program.parse();
|