@parseme/cli 0.0.2
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.
- package/LICENSE +21 -0
- package/README.md +453 -0
- package/dist/analyzers/ast-analyzer.d.ts +12 -0
- package/dist/analyzers/ast-analyzer.js +176 -0
- package/dist/analyzers/framework-detector.d.ts +12 -0
- package/dist/analyzers/framework-detector.js +180 -0
- package/dist/analyzers/git-analyzer.d.ts +13 -0
- package/dist/analyzers/git-analyzer.js +92 -0
- package/dist/analyzers/pattern-detector.d.ts +58 -0
- package/dist/analyzers/pattern-detector.js +174 -0
- package/dist/analyzers/project-analyzer.d.ts +14 -0
- package/dist/analyzers/project-analyzer.js +205 -0
- package/dist/builders/context-builder.d.ts +35 -0
- package/dist/builders/context-builder.js +332 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +145 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +203 -0
- package/dist/generator.d.ts +15 -0
- package/dist/generator.js +88 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/prompt.d.ts +8 -0
- package/dist/prompt.js +42 -0
- package/dist/types.d.ts +82 -0
- package/dist/types.js +2 -0
- package/package.json +81 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
export class FrameworkDetector {
|
|
2
|
+
config;
|
|
3
|
+
constructor(config) {
|
|
4
|
+
this.config = config;
|
|
5
|
+
}
|
|
6
|
+
async detect(projectInfo) {
|
|
7
|
+
const deps = { ...projectInfo.dependencies, ...projectInfo.devDependencies };
|
|
8
|
+
// Check for frameworks in dependencies
|
|
9
|
+
if (deps['@nestjs/core'] || deps['@nestjs/common']) {
|
|
10
|
+
return this.detectNestJS(deps);
|
|
11
|
+
}
|
|
12
|
+
if (deps['fastify']) {
|
|
13
|
+
return this.detectFastify(deps);
|
|
14
|
+
}
|
|
15
|
+
if (deps['express']) {
|
|
16
|
+
return this.detectExpress(deps);
|
|
17
|
+
}
|
|
18
|
+
if (deps['koa']) {
|
|
19
|
+
return this.detectKoa(deps);
|
|
20
|
+
}
|
|
21
|
+
if (deps['@hapi/hapi']) {
|
|
22
|
+
return this.detectHapi(deps);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
name: 'unknown',
|
|
26
|
+
features: [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
detectExpress(deps) {
|
|
30
|
+
const features = [];
|
|
31
|
+
// Detect common Express features
|
|
32
|
+
if (deps['express-session']) {
|
|
33
|
+
features.push('sessions');
|
|
34
|
+
}
|
|
35
|
+
if (deps['passport']) {
|
|
36
|
+
features.push('authentication');
|
|
37
|
+
}
|
|
38
|
+
if (deps['express-rate-limit']) {
|
|
39
|
+
features.push('rate-limiting');
|
|
40
|
+
}
|
|
41
|
+
if (deps['helmet']) {
|
|
42
|
+
features.push('security');
|
|
43
|
+
}
|
|
44
|
+
if (deps['cors']) {
|
|
45
|
+
features.push('cors');
|
|
46
|
+
}
|
|
47
|
+
if (deps['body-parser']) {
|
|
48
|
+
features.push('body-parsing');
|
|
49
|
+
}
|
|
50
|
+
if (deps['express-validator']) {
|
|
51
|
+
features.push('validation');
|
|
52
|
+
}
|
|
53
|
+
if (deps['multer']) {
|
|
54
|
+
features.push('file-upload');
|
|
55
|
+
}
|
|
56
|
+
if (deps['express-static']) {
|
|
57
|
+
features.push('static-files');
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
name: 'express',
|
|
61
|
+
version: deps['express'],
|
|
62
|
+
features,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
detectFastify(deps) {
|
|
66
|
+
const features = [];
|
|
67
|
+
if (deps['@fastify/cors']) {
|
|
68
|
+
features.push('cors');
|
|
69
|
+
}
|
|
70
|
+
if (deps['@fastify/helmet']) {
|
|
71
|
+
features.push('security');
|
|
72
|
+
}
|
|
73
|
+
if (deps['@fastify/rate-limit']) {
|
|
74
|
+
features.push('rate-limiting');
|
|
75
|
+
}
|
|
76
|
+
if (deps['@fastify/multipart']) {
|
|
77
|
+
features.push('file-upload');
|
|
78
|
+
}
|
|
79
|
+
if (deps['@fastify/static']) {
|
|
80
|
+
features.push('static-files');
|
|
81
|
+
}
|
|
82
|
+
if (deps['@fastify/jwt']) {
|
|
83
|
+
features.push('jwt');
|
|
84
|
+
}
|
|
85
|
+
if (deps['@fastify/session']) {
|
|
86
|
+
features.push('sessions');
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
name: 'fastify',
|
|
90
|
+
version: deps['fastify'],
|
|
91
|
+
features,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
detectNestJS(deps) {
|
|
95
|
+
const features = [];
|
|
96
|
+
if (deps['@nestjs/typeorm'] || deps['@nestjs/mongoose']) {
|
|
97
|
+
features.push('orm');
|
|
98
|
+
}
|
|
99
|
+
if (deps['@nestjs/passport']) {
|
|
100
|
+
features.push('authentication');
|
|
101
|
+
}
|
|
102
|
+
if (deps['@nestjs/jwt']) {
|
|
103
|
+
features.push('jwt');
|
|
104
|
+
}
|
|
105
|
+
if (deps['@nestjs/swagger']) {
|
|
106
|
+
features.push('swagger');
|
|
107
|
+
}
|
|
108
|
+
if (deps['@nestjs/graphql']) {
|
|
109
|
+
features.push('graphql');
|
|
110
|
+
}
|
|
111
|
+
if (deps['@nestjs/websockets']) {
|
|
112
|
+
features.push('websockets');
|
|
113
|
+
}
|
|
114
|
+
if (deps['@nestjs/microservices']) {
|
|
115
|
+
features.push('microservices');
|
|
116
|
+
}
|
|
117
|
+
if (deps['@nestjs/testing']) {
|
|
118
|
+
features.push('testing');
|
|
119
|
+
}
|
|
120
|
+
// NestJS uses decorators by default
|
|
121
|
+
features.push('decorators', 'dependency-injection', 'modules');
|
|
122
|
+
return {
|
|
123
|
+
name: 'nestjs',
|
|
124
|
+
version: deps['@nestjs/core'],
|
|
125
|
+
features,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
detectKoa(deps) {
|
|
129
|
+
const features = [];
|
|
130
|
+
if (deps['@koa/cors']) {
|
|
131
|
+
features.push('cors');
|
|
132
|
+
}
|
|
133
|
+
if (deps['koa-helmet']) {
|
|
134
|
+
features.push('security');
|
|
135
|
+
}
|
|
136
|
+
if (deps['koa-ratelimit']) {
|
|
137
|
+
features.push('rate-limiting');
|
|
138
|
+
}
|
|
139
|
+
if (deps['koa-multer']) {
|
|
140
|
+
features.push('file-upload');
|
|
141
|
+
}
|
|
142
|
+
if (deps['koa-static']) {
|
|
143
|
+
features.push('static-files');
|
|
144
|
+
}
|
|
145
|
+
if (deps['koa-session']) {
|
|
146
|
+
features.push('sessions');
|
|
147
|
+
}
|
|
148
|
+
if (deps['koa-bodyparser']) {
|
|
149
|
+
features.push('body-parsing');
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
name: 'koa',
|
|
153
|
+
version: deps['koa'],
|
|
154
|
+
features,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
detectHapi(deps) {
|
|
158
|
+
const features = [];
|
|
159
|
+
if (deps['@hapi/cookie']) {
|
|
160
|
+
features.push('sessions');
|
|
161
|
+
}
|
|
162
|
+
if (deps['@hapi/jwt']) {
|
|
163
|
+
features.push('jwt');
|
|
164
|
+
}
|
|
165
|
+
if (deps['@hapi/inert']) {
|
|
166
|
+
features.push('static-files');
|
|
167
|
+
}
|
|
168
|
+
if (deps['@hapi/vision']) {
|
|
169
|
+
features.push('templates');
|
|
170
|
+
}
|
|
171
|
+
if (deps['@hapi/boom']) {
|
|
172
|
+
features.push('error-handling');
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
name: 'hapi',
|
|
176
|
+
version: deps['@hapi/hapi'],
|
|
177
|
+
features,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ParsemeConfig } from '../config.js';
|
|
2
|
+
import type { GitInfo } from '../types.js';
|
|
3
|
+
export declare class GitAnalyzer {
|
|
4
|
+
private readonly config;
|
|
5
|
+
constructor(config: ParsemeConfig);
|
|
6
|
+
analyze(rootDir: string): Promise<GitInfo | undefined>;
|
|
7
|
+
private getCurrentBranch;
|
|
8
|
+
private getLastCommit;
|
|
9
|
+
private getStatus;
|
|
10
|
+
private getChangedFiles;
|
|
11
|
+
getFileLastModified(filePath: string, rootDir: string): Promise<string | undefined>;
|
|
12
|
+
getFileHistory(filePath: string, rootDir: string, limit?: number): Promise<string[]>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
const execAsync = promisify(exec);
|
|
4
|
+
export class GitAnalyzer {
|
|
5
|
+
config;
|
|
6
|
+
constructor(config) {
|
|
7
|
+
this.config = config;
|
|
8
|
+
}
|
|
9
|
+
async analyze(rootDir) {
|
|
10
|
+
try {
|
|
11
|
+
// Check if this is a git repository
|
|
12
|
+
await execAsync('git rev-parse --git-dir', { cwd: rootDir });
|
|
13
|
+
const [branch, lastCommit, status, changedFiles] = await Promise.all([
|
|
14
|
+
this.getCurrentBranch(rootDir),
|
|
15
|
+
this.getLastCommit(rootDir),
|
|
16
|
+
this.getStatus(rootDir),
|
|
17
|
+
this.getChangedFiles(rootDir),
|
|
18
|
+
]);
|
|
19
|
+
return {
|
|
20
|
+
branch,
|
|
21
|
+
lastCommit,
|
|
22
|
+
status,
|
|
23
|
+
changedFiles,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Not a git repository or git not available
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async getCurrentBranch(rootDir) {
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await execAsync('git branch --show-current', { cwd: rootDir });
|
|
34
|
+
return stdout.trim();
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return 'unknown';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
async getLastCommit(rootDir) {
|
|
41
|
+
try {
|
|
42
|
+
const { stdout } = await execAsync('git log -1 --format="%H %s"', { cwd: rootDir });
|
|
43
|
+
return stdout.trim();
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return 'No commits';
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async getStatus(rootDir) {
|
|
50
|
+
try {
|
|
51
|
+
const { stdout } = await execAsync('git status --porcelain', { cwd: rootDir });
|
|
52
|
+
return stdout.trim() ? 'dirty' : 'clean';
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return 'clean';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async getChangedFiles(rootDir) {
|
|
59
|
+
try {
|
|
60
|
+
const { stdout } = await execAsync('git status --porcelain', { cwd: rootDir });
|
|
61
|
+
return stdout
|
|
62
|
+
.split('\n')
|
|
63
|
+
.filter((line) => line.trim())
|
|
64
|
+
.map((line) => line.substring(3)); // Remove status prefix
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
async getFileLastModified(filePath, rootDir) {
|
|
71
|
+
try {
|
|
72
|
+
const { stdout } = await execAsync(`git log -1 --format="%H %ai" -- "${filePath}"`, {
|
|
73
|
+
cwd: rootDir,
|
|
74
|
+
});
|
|
75
|
+
return stdout.trim();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async getFileHistory(filePath, rootDir, limit = 5) {
|
|
82
|
+
try {
|
|
83
|
+
const { stdout } = await execAsync(`git log --oneline -${limit} -- "${filePath}"`, {
|
|
84
|
+
cwd: rootDir,
|
|
85
|
+
});
|
|
86
|
+
return stdout.split('\n').filter((line) => line.trim());
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return [];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import * as t from '@babel/types';
|
|
2
|
+
import type { ParsemeConfig } from '../config.js';
|
|
3
|
+
import type { ComponentInfo, RouteInfo } from '../types.js';
|
|
4
|
+
export interface PatternAnalysis {
|
|
5
|
+
endpoints: EndpointInfo[];
|
|
6
|
+
components: ComponentInfo[];
|
|
7
|
+
services: ServiceInfo[];
|
|
8
|
+
models: ModelInfo[];
|
|
9
|
+
configs: ConfigInfo[];
|
|
10
|
+
middleware: MiddlewareInfo[];
|
|
11
|
+
utilities: UtilityInfo[];
|
|
12
|
+
}
|
|
13
|
+
export interface EndpointInfo extends RouteInfo {
|
|
14
|
+
type: 'rest' | 'graphql' | 'websocket' | 'rpc' | 'unknown';
|
|
15
|
+
framework?: string;
|
|
16
|
+
decorator?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ServiceInfo {
|
|
19
|
+
name: string;
|
|
20
|
+
file: string;
|
|
21
|
+
line: number;
|
|
22
|
+
methods: string[];
|
|
23
|
+
dependencies: string[];
|
|
24
|
+
type: 'class' | 'function' | 'object';
|
|
25
|
+
}
|
|
26
|
+
export interface ModelInfo {
|
|
27
|
+
name: string;
|
|
28
|
+
file: string;
|
|
29
|
+
line: number;
|
|
30
|
+
fields: string[];
|
|
31
|
+
type: 'interface' | 'type' | 'class' | 'schema';
|
|
32
|
+
}
|
|
33
|
+
export interface ConfigInfo {
|
|
34
|
+
name: string;
|
|
35
|
+
file: string;
|
|
36
|
+
line: number;
|
|
37
|
+
keys: string[];
|
|
38
|
+
type: 'object' | 'class' | 'env';
|
|
39
|
+
}
|
|
40
|
+
export interface MiddlewareInfo {
|
|
41
|
+
name: string;
|
|
42
|
+
file: string;
|
|
43
|
+
line: number;
|
|
44
|
+
type: 'function' | 'class' | 'decorator';
|
|
45
|
+
}
|
|
46
|
+
export interface UtilityInfo {
|
|
47
|
+
name: string;
|
|
48
|
+
file: string;
|
|
49
|
+
line: number;
|
|
50
|
+
functions: string[];
|
|
51
|
+
type: 'helper' | 'lib' | 'hook' | 'composable';
|
|
52
|
+
}
|
|
53
|
+
export declare class PatternDetector {
|
|
54
|
+
private readonly config;
|
|
55
|
+
constructor(config: ParsemeConfig);
|
|
56
|
+
analyzePatterns(ast: t.File, filePath: string, _content: string): PatternAnalysis;
|
|
57
|
+
private hasJSXReturn;
|
|
58
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import traverse from '@babel/traverse';
|
|
2
|
+
import * as t from '@babel/types';
|
|
3
|
+
export class PatternDetector {
|
|
4
|
+
config;
|
|
5
|
+
constructor(config) {
|
|
6
|
+
this.config = config;
|
|
7
|
+
}
|
|
8
|
+
analyzePatterns(ast, filePath, _content) {
|
|
9
|
+
const analysis = {
|
|
10
|
+
endpoints: [],
|
|
11
|
+
components: [],
|
|
12
|
+
services: [],
|
|
13
|
+
models: [],
|
|
14
|
+
configs: [],
|
|
15
|
+
middleware: [],
|
|
16
|
+
utilities: [],
|
|
17
|
+
};
|
|
18
|
+
// Use a single traverse call to detect all patterns
|
|
19
|
+
traverse.default(ast, {
|
|
20
|
+
// Detect Express-style routes: app.get(), router.post(), etc.
|
|
21
|
+
CallExpression: (path) => {
|
|
22
|
+
if (t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.property)) {
|
|
23
|
+
const methodName = path.node.callee.property.name;
|
|
24
|
+
const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'all'];
|
|
25
|
+
if (httpMethods.includes(methodName)) {
|
|
26
|
+
const routeArg = path.node.arguments[0];
|
|
27
|
+
const routePath = t.isStringLiteral(routeArg) ? routeArg.value : '/';
|
|
28
|
+
analysis.endpoints.push({
|
|
29
|
+
method: methodName.toUpperCase(),
|
|
30
|
+
path: routePath,
|
|
31
|
+
handler: 'callback',
|
|
32
|
+
file: filePath,
|
|
33
|
+
line: path.node.loc?.start.line || 0,
|
|
34
|
+
type: 'rest',
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
// Detect decorator-based routes: @Get(), @Post(), etc.
|
|
40
|
+
ClassMethod: (path) => {
|
|
41
|
+
const decorators = path.node.decorators;
|
|
42
|
+
if (decorators) {
|
|
43
|
+
decorators.forEach((decorator) => {
|
|
44
|
+
if (t.isDecorator(decorator) && t.isCallExpression(decorator.expression)) {
|
|
45
|
+
const callee = decorator.expression.callee;
|
|
46
|
+
if (t.isIdentifier(callee)) {
|
|
47
|
+
const httpMethods = ['Get', 'Post', 'Put', 'Delete', 'Patch', 'Options', 'Head'];
|
|
48
|
+
if (httpMethods.includes(callee.name)) {
|
|
49
|
+
const route = decorator.expression.arguments[0];
|
|
50
|
+
const routePath = t.isStringLiteral(route) ? route.value : '/';
|
|
51
|
+
analysis.endpoints.push({
|
|
52
|
+
method: callee.name.toUpperCase(),
|
|
53
|
+
path: routePath,
|
|
54
|
+
handler: t.isIdentifier(path.node.key) ? path.node.key.name : 'anonymous',
|
|
55
|
+
file: filePath,
|
|
56
|
+
line: path.node.loc?.start.line || 0,
|
|
57
|
+
type: 'rest',
|
|
58
|
+
decorator: callee.name,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
// Detect React components
|
|
67
|
+
FunctionDeclaration: (path) => {
|
|
68
|
+
const functionName = path.node.id?.name;
|
|
69
|
+
if (functionName) {
|
|
70
|
+
// React hooks
|
|
71
|
+
if (functionName.startsWith('use') && functionName.length > 3) {
|
|
72
|
+
analysis.utilities.push({
|
|
73
|
+
name: functionName,
|
|
74
|
+
file: filePath,
|
|
75
|
+
line: path.node.loc?.start.line || 0,
|
|
76
|
+
functions: [functionName],
|
|
77
|
+
type: 'hook',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// React components (check for JSX return)
|
|
81
|
+
if (this.hasJSXReturn(path.node.body)) {
|
|
82
|
+
analysis.components.push({
|
|
83
|
+
name: functionName,
|
|
84
|
+
file: filePath,
|
|
85
|
+
line: path.node.loc?.start.line || 0,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
// Middleware functions (req, res, next pattern)
|
|
89
|
+
const params = path.node.params;
|
|
90
|
+
if (params.length >= 3) {
|
|
91
|
+
const paramNames = params
|
|
92
|
+
.filter((param) => t.isIdentifier(param))
|
|
93
|
+
.map((param) => param.name);
|
|
94
|
+
const isMiddleware = paramNames.some((name) => ['req', 'request', 'res', 'response', 'next', 'ctx', 'context'].includes(name.toLowerCase()));
|
|
95
|
+
if (isMiddleware) {
|
|
96
|
+
analysis.middleware.push({
|
|
97
|
+
name: functionName,
|
|
98
|
+
file: filePath,
|
|
99
|
+
line: path.node.loc?.start.line || 0,
|
|
100
|
+
type: 'function',
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
// Detect service classes and components
|
|
107
|
+
ClassDeclaration: (path) => {
|
|
108
|
+
const className = path.node.id?.name;
|
|
109
|
+
if (!className) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
// Service classes
|
|
113
|
+
const hasInjectableDecorator = path.node.decorators?.some((decorator) => t.isDecorator(decorator) &&
|
|
114
|
+
t.isCallExpression(decorator.expression) &&
|
|
115
|
+
t.isIdentifier(decorator.expression.callee) &&
|
|
116
|
+
decorator.expression.callee.name === 'Injectable');
|
|
117
|
+
const isServiceClass = className.endsWith('Service') ||
|
|
118
|
+
className.endsWith('Repository') ||
|
|
119
|
+
className.endsWith('Manager') ||
|
|
120
|
+
hasInjectableDecorator;
|
|
121
|
+
if (isServiceClass) {
|
|
122
|
+
const methods = path.node.body.body
|
|
123
|
+
.filter((member) => t.isClassMethod(member) && t.isIdentifier(member.key))
|
|
124
|
+
.map((member) => member.key.name);
|
|
125
|
+
analysis.services.push({
|
|
126
|
+
name: className,
|
|
127
|
+
file: filePath,
|
|
128
|
+
line: path.node.loc?.start.line || 0,
|
|
129
|
+
methods,
|
|
130
|
+
dependencies: [],
|
|
131
|
+
type: 'class',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// React class components (check for render method)
|
|
135
|
+
const hasRenderMethod = path.node.body.body.some((member) => t.isClassMethod(member) && t.isIdentifier(member.key) && member.key.name === 'render');
|
|
136
|
+
if (hasRenderMethod) {
|
|
137
|
+
analysis.components.push({
|
|
138
|
+
name: className,
|
|
139
|
+
file: filePath,
|
|
140
|
+
line: path.node.loc?.start.line || 0,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
// Detect TypeScript interfaces
|
|
145
|
+
TSInterfaceDeclaration: (path) => {
|
|
146
|
+
const interfaceName = path.node.id.name;
|
|
147
|
+
const fields = path.node.body.body
|
|
148
|
+
.filter((member) => t.isTSPropertySignature(member) && t.isIdentifier(member.key))
|
|
149
|
+
.map((member) => member.key.name);
|
|
150
|
+
analysis.models.push({
|
|
151
|
+
name: interfaceName,
|
|
152
|
+
file: filePath,
|
|
153
|
+
line: path.node.loc?.start.line || 0,
|
|
154
|
+
fields,
|
|
155
|
+
type: 'interface',
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
// Detect type aliases
|
|
159
|
+
TSTypeAliasDeclaration: (path) => {
|
|
160
|
+
analysis.models.push({
|
|
161
|
+
name: path.node.id.name,
|
|
162
|
+
file: filePath,
|
|
163
|
+
line: path.node.loc?.start.line || 0,
|
|
164
|
+
fields: [],
|
|
165
|
+
type: 'type',
|
|
166
|
+
});
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
return analysis;
|
|
170
|
+
}
|
|
171
|
+
hasJSXReturn(body) {
|
|
172
|
+
return body.body.some((stmt) => t.isReturnStatement(stmt) && stmt.argument && t.isJSXElement(stmt.argument));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ParsemeConfig } from '../config.js';
|
|
2
|
+
import type { ProjectInfo } from '../types.js';
|
|
3
|
+
export declare class ProjectAnalyzer {
|
|
4
|
+
private readonly config;
|
|
5
|
+
constructor(config: ParsemeConfig);
|
|
6
|
+
analyze(rootDir: string): Promise<ProjectInfo>;
|
|
7
|
+
private detectProjectType;
|
|
8
|
+
private detectPackageManager;
|
|
9
|
+
private getFilesRecursive;
|
|
10
|
+
private detectProjectCategory;
|
|
11
|
+
private detectEntryPoints;
|
|
12
|
+
private detectOutputTargets;
|
|
13
|
+
private hasAppDependencies;
|
|
14
|
+
}
|