reverse-engine 0.1.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 CHANGED
@@ -2,94 +2,205 @@
2
2
  import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { readFile, writeFile, mkdir } from 'fs/promises';
5
+ import { existsSync } from 'fs';
6
+ import { execFileSync } from 'child_process';
7
+ import { dirname, join, resolve } from 'path';
8
+ import { createRequire } from 'module';
5
9
  import { analyze } from '../analyzer/index.js';
6
10
  import { generateReport } from '../docgen/index.js';
7
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
+ }
37
+ // ─── 네이티브 바이너리 탐색 ───
38
+ function findNativeBinary() {
39
+ const key = `${process.platform}-${process.arch}`;
40
+ const pkgName = `reverse-engine-${key}`;
41
+ const binName = process.platform === 'win32' ? 'reverseng.exe' : 'reverseng';
42
+ try {
43
+ const req = createRequire(import.meta.url);
44
+ const pkgJsonPath = req.resolve(`${pkgName}/package.json`);
45
+ const binPath = join(dirname(pkgJsonPath), 'bin', binName);
46
+ if (existsSync(binPath))
47
+ return binPath;
48
+ }
49
+ catch { /* not installed */ }
50
+ return null;
51
+ }
52
+ function runNative(args) {
53
+ if (!nativeBin)
54
+ return;
55
+ execFileSync(nativeBin, args, { stdio: 'inherit' });
56
+ }
57
+ const nativeBin = findNativeBinary();
8
58
  const program = new Command();
59
+ if (nativeBin) {
60
+ console.log(chalk.dim(`⚡ native`));
61
+ }
9
62
  program
10
63
  .name('reverse-engine')
11
- .version('0.1.0')
12
- .description('웹 서비스 역분석 자동화 도구 - 소스코드 분석, 문서 생성, 테스트 자동화');
13
- // analyze
64
+ .version('0.2.0')
65
+ .description('웹 서비스 역분석 자동화 도구');
66
+ // ─── analyze ───
14
67
  program
15
- .command('analyze <path>')
68
+ .command('analyze')
69
+ .argument('[path]', '소스코드 경로 (생략하면 현재 디렉토리에서 자동 감지)')
70
+ .option('--framework <name>', '프레임워크 지정', 'auto')
71
+ .option('--include <patterns>', '포함 패턴 (쉼표 구분)')
72
+ .option('-o, --output <dir>', '출력 디렉토리')
16
73
  .description('소스코드 정적 분석')
17
- .option('--framework <name>', '프레임워크 지정 (auto, react, vue, angular, next)', 'auto')
18
- .option('--include <patterns>', '포함 패턴 (쉼표 구분)', 'src/**/*.{ts,tsx,js,jsx,vue}')
19
- .option('--output <dir>', '출력 디렉토리', './output')
20
- .action(async (sourcePath, opts) => {
21
- console.log(chalk.green(''), '코드 분석 시작:', chalk.cyan(sourcePath));
74
+ .action(async (path, opts) => {
75
+ const sourcePath = path ? resolve(path) : detectProjectRoot();
76
+ const outputDir = resolveOutput(opts.output, sourcePath);
77
+ if (nativeBin) {
78
+ runNative(['analyze', sourcePath, '--framework', opts.framework]);
79
+ return;
80
+ }
81
+ console.log(chalk.green('▶'), '코드 분석:', chalk.cyan(sourcePath));
22
82
  const result = await analyze(sourcePath, {
23
83
  framework: opts.framework,
24
- include: opts.include.split(','),
84
+ include: opts.include?.split(','),
25
85
  });
26
- console.log(chalk.green('✓'), '코드 분석 완료!');
86
+ console.log(chalk.green('✓'), '분석 완료!');
27
87
  console.log(` 프레임워크: ${result.framework}`);
28
- console.log(` 컴포넌트: ${result.components.length}개`);
29
- console.log(` 함수: ${result.functions.length}개`);
30
- console.log(` API: ${result.apiClients.length}개`);
31
- console.log(` 라우트: ${result.routes.length}개`);
32
- console.log(` 의존성: ${result.dependencies.length}개`);
33
- await mkdir(opts.output, { recursive: true });
34
- 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');
35
91
  await writeFile(outputPath, JSON.stringify(result, null, 2));
36
- console.log(` 결과 저장: ${chalk.cyan(outputPath)}`);
92
+ console.log(` ${chalk.cyan(outputPath)}`);
37
93
  });
38
- // report
94
+ // ─── report ───
39
95
  program
40
96
  .command('report')
41
- .description('분석 결과로 리포트 생성')
42
- .requiredOption('--input <file>', '분석 결과 JSON 파일')
43
- .option('--format <formats>', '출력 형식 (excel,mermaid)', 'excel,mermaid')
44
- .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>', '출력 디렉토리')
45
101
  .action(async (opts) => {
46
- console.log(chalk.green('▶'), '리포트 생성 시작');
47
- 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'));
48
110
  const formats = opts.format.split(',');
49
- const outputs = await generateReport(data, { formats, outputDir: opts.output });
50
- console.log(chalk.green('✓'), '리포트 생성 완료!');
111
+ console.log(chalk.green('▶'), '리포트 생성');
112
+ const outputs = await generateReport(data, { formats, outputDir });
113
+ console.log(chalk.green('✓'), '완료!');
51
114
  outputs.forEach(p => console.log(` → ${chalk.cyan(p)}`));
52
115
  });
53
- // test
116
+ // ─── test ───
54
117
  program
55
118
  .command('test')
56
119
  .description('테스트 코드 자동 생성')
57
- .requiredOption('--input <file>', '분석 결과 JSON 파일')
58
- .option('--type <types>', '테스트 종류 (e2e,api)', 'e2e,api')
59
- .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>', '출력 디렉토리')
60
123
  .action(async (opts) => {
61
- console.log(chalk.green('▶'), '테스트 코드 생성 시작');
62
- 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'));
63
132
  const types = opts.type.split(',');
64
- const files = await generateTests(data, { types, outputDir: opts.output });
65
- 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}개 파일)`);
66
136
  files.forEach(p => console.log(` → ${chalk.cyan(p)}`));
67
137
  });
68
- // full
138
+ // ─── full ───
69
139
  program
70
140
  .command('full')
71
- .description('전체 파이프라인 (analyze report → test)')
72
- .requiredOption('--source <path>', '소스코드 경로')
73
- .option('--output <dir>', '출력 디렉토리', './output')
141
+ .argument('[path]', '소스코드 경로 (생략하면 현재 디렉토리)')
74
142
  .option('--framework <name>', '프레임워크', 'auto')
75
- .action(async (opts) => {
76
- console.log(chalk.green('\n◆'), 'ReversEngine 전체 파이프라인 시작\n');
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');
77
153
  // Step 1: 분석
78
154
  console.log(chalk.gray('━'.repeat(50)));
79
- const result = await analyze(opts.source, { framework: opts.framework });
80
- console.log(chalk.green(''), `분석 완료: 컴포넌트 ${result.components.length}, 함수 ${result.functions.length}, API ${result.apiClients.length}, 라우트 ${result.routes.length}`);
81
- await mkdir(opts.output, { recursive: true });
82
- await writeFile(`${opts.output}/analysis.json`, JSON.stringify(result, null, 2));
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
+ }
83
163
  // Step 2: 리포트
164
+ if (existsSync(analysisPath)) {
165
+ const data = JSON.parse(await readFile(analysisPath, 'utf-8'));
166
+ console.log(chalk.gray('━'.repeat(50)));
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();
185
+ return;
186
+ }
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');
192
+ console.log(chalk.gray('━'.repeat(50)));
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(', '));
84
200
  console.log(chalk.gray('━'.repeat(50)));
85
- const reports = await generateReport(result, { outputDir: `${opts.output}/reports` });
86
- console.log(chalk.green('✓'), '리포트 생성 완료!');
87
- reports.forEach(p => console.log(` → ${chalk.cyan(p)}`));
88
- // Step 3: 테스트
201
+ const tests = await generateTests(data, { outputDir: join(outputDir, 'tests') });
202
+ console.log(chalk.green('✓'), `테스트: ${tests.length}개 파일`);
89
203
  console.log(chalk.gray('━'.repeat(50)));
90
- const tests = await generateTests(result, { outputDir: `${opts.output}/tests` });
91
- console.log(chalk.green('✓'), `테스트 생성 완료! (${tests.length}개)`);
92
- tests.forEach(p => console.log(` → ${chalk.cyan(p)}`));
93
- console.log(chalk.green('\n✓'), '전체 파이프라인 완료!', chalk.cyan(opts.output), '\n');
204
+ console.log(chalk.green('\n✓'), '완료!', chalk.dim(outputDir), '\n');
94
205
  });
95
206
  program.parse();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * 플랫폼에 맞는 네이티브 Rust 바이너리를 찾아 반환
3
+ * 없으면 null → JS fallback 사용
4
+ */
5
+ import { type ExecFileSyncOptions } from 'child_process';
6
+ /** 네이티브 바이너리 경로를 찾는다 */
7
+ export declare function findNativeBinary(): string | null;
8
+ /** 네이티브 바이너리로 명령어 실행 */
9
+ export declare function execNative(args: string[], options?: ExecFileSyncOptions): string;
10
+ /** 네이티브 바이너리 사용 가능 여부 */
11
+ export declare function hasNativeBinary(): boolean;
package/dist/native.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * 플랫폼에 맞는 네이티브 Rust 바이너리를 찾아 반환
3
+ * 없으면 null → JS fallback 사용
4
+ */
5
+ import { existsSync } from 'fs';
6
+ import { join, dirname } from 'path';
7
+ import { execFileSync } from 'child_process';
8
+ const PLATFORMS = {
9
+ 'win32-x64': 'reverse-engine-win32-x64',
10
+ 'win32-arm64': 'reverse-engine-win32-arm64',
11
+ 'linux-x64': 'reverse-engine-linux-x64',
12
+ 'linux-arm64': 'reverse-engine-linux-arm64',
13
+ 'darwin-x64': 'reverse-engine-darwin-x64',
14
+ 'darwin-arm64': 'reverse-engine-darwin-arm64',
15
+ };
16
+ /** 네이티브 바이너리 경로를 찾는다 */
17
+ export function findNativeBinary() {
18
+ const key = `${process.platform}-${process.arch}`;
19
+ const pkgName = PLATFORMS[key];
20
+ if (!pkgName)
21
+ return null;
22
+ try {
23
+ // optionalDependencies에서 설치된 패키지 경로
24
+ const pkgJson = require.resolve(`${pkgName}/package.json`);
25
+ const pkgDir = dirname(pkgJson);
26
+ const pkg = JSON.parse(require('fs').readFileSync(pkgJson, 'utf-8'));
27
+ const binPath = join(pkgDir, pkg.main || (process.platform === 'win32' ? 'bin/reverseng.exe' : 'bin/reverseng'));
28
+ if (existsSync(binPath)) {
29
+ return binPath;
30
+ }
31
+ }
32
+ catch {
33
+ // 패키지가 설치되지 않음 → fallback
34
+ }
35
+ return null;
36
+ }
37
+ /** 네이티브 바이너리로 명령어 실행 */
38
+ export function execNative(args, options) {
39
+ const binPath = findNativeBinary();
40
+ if (!binPath) {
41
+ throw new Error('네이티브 바이너리를 찾을 수 없습니다');
42
+ }
43
+ return execFileSync(binPath, args, {
44
+ encoding: 'utf-8',
45
+ stdio: ['pipe', 'pipe', 'inherit'],
46
+ ...options,
47
+ });
48
+ }
49
+ /** 네이티브 바이너리 사용 가능 여부 */
50
+ export function hasNativeBinary() {
51
+ return findNativeBinary() !== null;
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reverse-engine",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "웹 서비스 역분석 자동화 도구 - 소스코드 분석, 문서 생성, 테스트 자동화",
5
5
  "keywords": [
6
6
  "reverse-engineering",
@@ -61,6 +61,14 @@
61
61
  "typescript": "^5.7.0",
62
62
  "tsx": "^4.19.0"
63
63
  },
64
+ "optionalDependencies": {
65
+ "reverse-engine-win32-x64": "0.2.0",
66
+ "reverse-engine-win32-arm64": "0.2.0",
67
+ "reverse-engine-linux-x64": "0.2.0",
68
+ "reverse-engine-linux-arm64": "0.2.0",
69
+ "reverse-engine-darwin-x64": "0.2.0",
70
+ "reverse-engine-darwin-arm64": "0.2.0"
71
+ },
64
72
  "engines": {
65
73
  "node": ">=18.0.0"
66
74
  }