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.

Files changed (2) hide show
  1. package/dist/cli/index.js +133 -74
  2. 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(`⚡ 네이티브 Rust 바이너리 사용중`));
60
+ console.log(chalk.dim(`⚡ native`));
36
61
  }
37
62
  program
38
63
  .name('reverse-engine')
39
- .version('0.1.0')
40
- .description('웹 서비스 역분석 자동화 도구 - 소스코드 분석, 문서 생성, 테스트 자동화');
41
- // analyze
64
+ .version('0.2.0')
65
+ .description('웹 서비스 역분석 자동화 도구');
66
+ // ─── analyze ───
42
67
  program
43
- .command('analyze <path>')
68
+ .command('analyze')
69
+ .argument('[path]', '소스코드 경로 (생략하면 현재 디렉토리에서 자동 감지)')
70
+ .option('--framework <name>', '프레임워크 지정', 'auto')
71
+ .option('--include <patterns>', '포함 패턴 (쉼표 구분)')
72
+ .option('-o, --output <dir>', '출력 디렉토리')
44
73
  .description('소스코드 정적 분석')
45
- .option('--framework <name>', '프레임워크 지정 (auto, react, vue, angular, next)', 'auto')
46
- .option('--include <patterns>', '포함 패턴 (쉼표 구분)', 'src/**/*.{ts,tsx,js,jsx,vue}')
47
- .option('--output <dir>', '출력 디렉토리', './output')
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('▶'), '코드 분석 시작:', chalk.cyan(sourcePath));
81
+ console.log(chalk.green('▶'), '코드 분석:', chalk.cyan(sourcePath));
55
82
  const result = await analyze(sourcePath, {
56
83
  framework: opts.framework,
57
- include: opts.include.split(','),
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(` 컴포넌트: ${result.components.length}개`);
62
- console.log(` 함수: ${result.functions.length}개`);
63
- console.log(` API: ${result.apiClients.length}개`);
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(` 결과 저장: ${chalk.cyan(outputPath)}`);
92
+ console.log(` ${chalk.cyan(outputPath)}`);
70
93
  });
71
- // report
94
+ // ─── report ───
72
95
  program
73
96
  .command('report')
74
- .description('분석 결과로 리포트 생성')
75
- .requiredOption('--input <file>', '분석 결과 JSON 파일')
76
- .option('--format <formats>', '출력 형식 (excel,mermaid)', 'excel,mermaid')
77
- .option('--output <dir>', '출력 디렉토리', './output/reports')
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
- console.log(chalk.green('▶'), '리포트 생성 시작');
80
- const data = JSON.parse(await readFile(opts.input, 'utf-8'));
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
- const outputs = await generateReport(data, { formats, outputDir: opts.output });
83
- console.log(chalk.green('✓'), '리포트 생성 완료!');
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
- .requiredOption('--input <file>', '분석 결과 JSON 파일')
91
- .option('--type <types>', '테스트 종류 (e2e,api)', 'e2e,api')
92
- .option('--output <dir>', '출력 디렉토리', './output/tests')
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
- console.log(chalk.green('▶'), '테스트 코드 생성 시작');
95
- const data = JSON.parse(await readFile(opts.input, 'utf-8'));
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
- const files = await generateTests(data, { types, outputDir: opts.output });
98
- console.log(chalk.green('✓'), `테스트 코드 생성 완료! (${files.length}개 파일)`);
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
- .description('전체 파이프라인 (analyze report → test)')
105
- .requiredOption('--source <path>', '소스코드 경로')
106
- .option('--output <dir>', '출력 디렉토리', './output')
141
+ .argument('[path]', '소스코드 경로 (생략하면 현재 디렉토리)')
107
142
  .option('--framework <name>', '프레임워크', 'auto')
108
- .action(async (opts) => {
109
- console.log(chalk.green('\n◆'), 'ReversEngine 전체 파이프라인 시작', nativeBin ? chalk.dim('(⚡ native)') : '', '\n');
110
- // Step 1: 분석 — 네이티브가 있으면 Rust 사용
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
- runNative(['analyze', opts.source, '--framework', opts.framework || 'auto']);
114
- const analysisPath = `${opts.output}/analysis.json`;
115
- if (existsSync(analysisPath)) {
116
- const result = JSON.parse(await readFile(analysisPath, 'utf-8'));
117
- console.log(chalk.gray(''.repeat(50)));
118
- const reports = await generateReport(result, { outputDir: `${opts.output}/reports` });
119
- console.log(chalk.green('✓'), '리포트 생성 완료!');
120
- reports.forEach((p) => console.log(` → ${chalk.cyan(p)}`));
121
- console.log(chalk.gray(''.repeat(50)));
122
- const tests = await generateTests(result, { outputDir: `${opts.output}/tests` });
123
- console.log(chalk.green('✓'), `테스트 생성 완료! (${tests.length}개)`);
124
- tests.forEach((p) => console.log(` → ${chalk.cyan(p)}`));
125
- }
126
- console.log(chalk.green('\n✓'), '전체 파이프라인 완료!', chalk.cyan(opts.output), '\n');
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
- // JS fallback Step 1: 분석
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(opts.source, { framework: opts.framework });
132
- console.log(chalk.green('✓'), `분석 완료: 컴포넌트 ${result.components.length}, 함수 ${result.functions.length}, API ${result.apiClients.length}, 라우트 ${result.routes.length}`);
133
- await mkdir(opts.output, { recursive: true });
134
- await writeFile(`${opts.output}/analysis.json`, JSON.stringify(result, null, 2));
135
- // Step 2: 리포트
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 reports = await generateReport(result, { outputDir: `${opts.output}/reports` });
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
- const tests = await generateTests(result, { outputDir: `${opts.output}/tests` });
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reverse-engine",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "웹 서비스 역분석 자동화 도구 - 소스코드 분석, 문서 생성, 테스트 자동화",
5
5
  "keywords": [
6
6
  "reverse-engineering",