@parseme/cli 0.0.3 → 0.0.4
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/README.md +163 -156
- package/dist/{cli.js → cli/cli.js} +59 -49
- package/dist/{prompt.d.ts → cli/prompt.d.ts} +0 -1
- package/dist/{prompt.js → cli/prompt.js} +0 -9
- package/dist/{analyzers → core/analyzers}/ast-analyzer.js +7 -6
- package/dist/{analyzers → core/analyzers}/framework-detector.d.ts +0 -3
- package/dist/{analyzers → core/analyzers}/framework-detector.js +0 -4
- package/dist/{analyzers → core/analyzers}/git-analyzer.d.ts +0 -3
- package/dist/{analyzers → core/analyzers}/git-analyzer.js +0 -4
- package/dist/{analyzers → core/analyzers}/pattern-detector.d.ts +0 -3
- package/dist/{analyzers → core/analyzers}/pattern-detector.js +52 -47
- package/dist/{analyzers → core/analyzers}/project-analyzer.d.ts +2 -0
- package/dist/{analyzers → core/analyzers}/project-analyzer.js +19 -5
- package/dist/core/config.d.ts +20 -0
- package/dist/{config.js → core/config.js} +80 -68
- package/dist/{builders → core}/context-builder.d.ts +3 -8
- package/dist/{builders → core}/context-builder.js +7 -84
- package/dist/{generator.d.ts → core/generator.d.ts} +0 -3
- package/dist/{generator.js → core/generator.js} +8 -13
- package/dist/core/types/analyzer-types.d.ts +39 -0
- package/dist/core/types/analyzer-types.js +2 -0
- package/dist/{config.d.ts → core/types/config-types.d.ts} +8 -15
- package/dist/core/types/config-types.js +2 -0
- package/dist/core/types/generator-types.d.ts +8 -0
- package/dist/core/types/generator-types.js +2 -0
- package/dist/core/types/index.d.ts +4 -0
- package/dist/core/types/index.js +5 -0
- package/dist/core/types/project-types.d.ts +30 -0
- package/dist/core/types/project-types.js +2 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/core/types.js +2 -0
- package/dist/index.d.ts +3 -4
- package/dist/index.js +2 -2
- package/package.json +3 -4
- package/dist/types.d.ts +0 -84
- package/dist/types.js +0 -2
- /package/dist/{cli.d.ts → cli/cli.d.ts} +0 -0
- /package/dist/{analyzers → core/analyzers}/ast-analyzer.d.ts +0 -0
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { join } from 'path';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { prompt } from './prompt.js';
|
|
5
|
+
import { ParsemeConfig } from '../core/config.js';
|
|
6
|
+
import { ParsemeGenerator } from '../core/generator.js';
|
|
7
7
|
const program = new Command();
|
|
8
|
-
async function promptForMissingConfig(config
|
|
9
|
-
|
|
10
|
-
// Only prompt if running interactively (stdin is a TTY) and not in CI
|
|
11
|
-
if (!process.stdin.isTTY || process.env.CI) {
|
|
12
|
-
return finalConfig;
|
|
13
|
-
}
|
|
14
|
-
// Prompt for README suggestion if not set via CLI or config
|
|
15
|
-
if (cliOptions.readmeSuggestion === undefined && config.readmeSuggestion === undefined) {
|
|
16
|
-
const wantReadmeSuggestion = await confirmPrompt('Show README.md section suggestion for AI agents?', true);
|
|
17
|
-
finalConfig.readmeSuggestion = wantReadmeSuggestion;
|
|
18
|
-
}
|
|
19
|
-
return finalConfig;
|
|
8
|
+
async function promptForMissingConfig(config) {
|
|
9
|
+
return { ...config };
|
|
20
10
|
}
|
|
21
11
|
program.name('parseme').description('AI Project Context Generator').version('0.1.0');
|
|
22
12
|
// Main command - just run parseme
|
|
@@ -26,11 +16,10 @@ program
|
|
|
26
16
|
.option('-o, --output <path>', 'Output file path')
|
|
27
17
|
.option('-r, --root <path>', 'Root directory to analyze')
|
|
28
18
|
.option('--context-dir <path>', 'Context directory path (default: parseme-context)')
|
|
29
|
-
.option('--
|
|
19
|
+
.option('--file-types <types...>', 'File types to analyze (e.g., ts tsx js jsx)')
|
|
30
20
|
.option('--exclude <patterns...>', 'Exclude patterns (glob)')
|
|
31
21
|
.option('--no-git', 'Disable git information')
|
|
32
22
|
.option('--max-depth <number>', 'Maximum directory depth', parseInt)
|
|
33
|
-
.option('--no-readme-suggestion', 'Disable README.md section suggestion')
|
|
34
23
|
.action(async (options) => {
|
|
35
24
|
try {
|
|
36
25
|
// Convert CLI options to config format
|
|
@@ -38,14 +27,16 @@ program
|
|
|
38
27
|
...(options.output && { outputPath: options.output }),
|
|
39
28
|
...(options.root && { rootDir: options.root }),
|
|
40
29
|
...(options.contextDir && { contextDir: options.contextDir }),
|
|
41
|
-
...(options.
|
|
30
|
+
...(options.fileTypes && { analyzeFileTypes: options.fileTypes }),
|
|
42
31
|
...(options.exclude && { excludePatterns: options.exclude }),
|
|
43
32
|
...(options.git === false && { includeGitInfo: false }),
|
|
44
33
|
...(options.maxDepth && { maxDepth: options.maxDepth }),
|
|
45
|
-
...(options.readmeSuggestion === false && { readmeSuggestion: false }),
|
|
46
34
|
};
|
|
47
|
-
const configFromFile = await ParsemeConfig.fromFile(options.config
|
|
48
|
-
|
|
35
|
+
const configFromFile = await ParsemeConfig.fromFile(options.config, {
|
|
36
|
+
showWarnings: true,
|
|
37
|
+
throwOnNotFound: true,
|
|
38
|
+
});
|
|
39
|
+
const interactiveConfig = await promptForMissingConfig(configFromFile.get());
|
|
49
40
|
// Merge: CLI options > interactive prompts > config file > defaults
|
|
50
41
|
const finalConfig = {
|
|
51
42
|
...interactiveConfig,
|
|
@@ -55,19 +46,12 @@ program
|
|
|
55
46
|
const generator = new ParsemeGenerator(config.get());
|
|
56
47
|
await generator.generateToFile();
|
|
57
48
|
console.log('Context generated successfully');
|
|
58
|
-
console.log('Tip: Add parseme as a git hook to keep context auto-updated! See README for setup instructions.');
|
|
59
|
-
const shouldShowReadmeSuggestion = finalConfig.readmeSuggestion !== false;
|
|
60
|
-
if (shouldShowReadmeSuggestion) {
|
|
61
|
-
console.log('Tip: Add this section to your README.md to help AI agents find the context:');
|
|
62
|
-
console.log('');
|
|
63
|
-
console.log('## For AI Assistants');
|
|
64
|
-
console.log('This project includes AI-optimized documentation:');
|
|
65
|
-
console.log('- `PARSEME.md` - Main project context and overview');
|
|
66
|
-
console.log('- `parseme-context/` - Detailed JSON files with code structure, dependencies, and git info');
|
|
67
|
-
console.log('');
|
|
68
|
-
}
|
|
69
49
|
}
|
|
70
50
|
catch (error) {
|
|
51
|
+
if (error instanceof Error && error.message.includes('No configuration file found')) {
|
|
52
|
+
console.error(error.message);
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
71
55
|
console.error('Failed to generate context:', error);
|
|
72
56
|
process.exit(1);
|
|
73
57
|
}
|
|
@@ -76,7 +60,7 @@ program
|
|
|
76
60
|
.command('init')
|
|
77
61
|
.description('Initialize parseme configuration')
|
|
78
62
|
.option('-f, --force', 'Overwrite existing config')
|
|
79
|
-
.option('--format <format>', 'Config format:
|
|
63
|
+
.option('--format <format>', 'Config format: json, ts, or js', 'json')
|
|
80
64
|
.action(async (options) => {
|
|
81
65
|
try {
|
|
82
66
|
// Validate format
|
|
@@ -85,7 +69,6 @@ program
|
|
|
85
69
|
process.exit(1);
|
|
86
70
|
}
|
|
87
71
|
const configPath = join(process.cwd(), `parseme.config.${options.format}`);
|
|
88
|
-
const config = new ParsemeConfig();
|
|
89
72
|
// Check if config already exists
|
|
90
73
|
if (!options.force) {
|
|
91
74
|
try {
|
|
@@ -98,12 +81,44 @@ program
|
|
|
98
81
|
// File doesn't exist, continue
|
|
99
82
|
}
|
|
100
83
|
}
|
|
84
|
+
// Build config with only user-specified values
|
|
85
|
+
const userConfig = {};
|
|
86
|
+
// Only prompt if interactive (TTY) and not in CI
|
|
87
|
+
if (process.stdin.isTTY && !process.env.CI) {
|
|
88
|
+
// Ask about context directory path
|
|
89
|
+
// Set context directory to what user entered (or default if they pressed enter)
|
|
90
|
+
userConfig.contextDir = await prompt({
|
|
91
|
+
message: 'Context directory path',
|
|
92
|
+
defaultValue: 'parseme-context',
|
|
93
|
+
});
|
|
94
|
+
// Ask about exclude patterns
|
|
95
|
+
const defaultExcludePatterns = ['node_modules/**', 'dist/**', '.git/**'];
|
|
96
|
+
const excludePatternsAnswer = await prompt({
|
|
97
|
+
message: 'Exclude patterns (comma-separated glob patterns, patterns from .gitignore will be ignored by default as well)',
|
|
98
|
+
defaultValue: defaultExcludePatterns.join(', '),
|
|
99
|
+
});
|
|
100
|
+
// Always set exclude patterns to what user entered (or defaults if they pressed enter)
|
|
101
|
+
userConfig.excludePatterns = excludePatternsAnswer
|
|
102
|
+
.split(',')
|
|
103
|
+
.map((p) => p.trim())
|
|
104
|
+
.filter((p) => p.length > 0);
|
|
105
|
+
}
|
|
106
|
+
const config = new ParsemeConfig(userConfig);
|
|
101
107
|
await config.save(configPath);
|
|
102
108
|
console.log(`Configuration file created at ${configPath}`);
|
|
103
109
|
if (options.format === 'ts') {
|
|
104
110
|
console.log('For TypeScript configs, ensure tsx or ts-node is available to load .ts files');
|
|
105
111
|
}
|
|
106
|
-
console.log('Add "parseme": "parseme" to your package.json scripts');
|
|
112
|
+
console.log('Tip: Add "parseme": "parseme" to your package.json scripts for easier manual execution or hook integration');
|
|
113
|
+
console.log('Tip: Add parseme as a git hook to keep context auto-updated! See README for setup instructions.');
|
|
114
|
+
console.log('');
|
|
115
|
+
console.log('Tip: Add this section to your README.md to help AI agents find the context:');
|
|
116
|
+
console.log('');
|
|
117
|
+
console.log('## Instructions For AI Agents');
|
|
118
|
+
console.log('This project includes AI-optimized documentation for efficient context providing:');
|
|
119
|
+
console.log('- `PARSEME.md` - Project overview with links to detailed context files');
|
|
120
|
+
console.log('- `parseme-context/` - Structured data files (AST analysis, dependencies, routes, git info)');
|
|
121
|
+
console.log('');
|
|
107
122
|
}
|
|
108
123
|
catch (error) {
|
|
109
124
|
console.error('Failed to create configuration:', error);
|
|
@@ -115,27 +130,22 @@ if (process.argv.length <= 2) {
|
|
|
115
130
|
// Run the default action
|
|
116
131
|
(async () => {
|
|
117
132
|
try {
|
|
118
|
-
const configFromFile = await ParsemeConfig.fromFile(
|
|
119
|
-
|
|
133
|
+
const configFromFile = await ParsemeConfig.fromFile(undefined, {
|
|
134
|
+
showWarnings: true,
|
|
135
|
+
throwOnNotFound: true,
|
|
136
|
+
});
|
|
137
|
+
const interactiveConfig = await promptForMissingConfig(configFromFile.get());
|
|
120
138
|
const config = new ParsemeConfig(interactiveConfig);
|
|
121
139
|
const generator = new ParsemeGenerator(config.get());
|
|
122
140
|
await generator.generateToFile();
|
|
123
141
|
console.log('Context generated successfully');
|
|
124
|
-
console.log('Tip: Add parseme as a git hook to keep context auto-updated! See README for setup instructions.');
|
|
125
|
-
const shouldShowReadmeSuggestion = interactiveConfig.readmeSuggestion !== false;
|
|
126
|
-
if (shouldShowReadmeSuggestion) {
|
|
127
|
-
console.log('Tip: Add this section to your README.md to help AI agents find the context:');
|
|
128
|
-
console.log('');
|
|
129
|
-
console.log('## For AI Assistants');
|
|
130
|
-
console.log('This project includes AI-optimized documentation:');
|
|
131
|
-
console.log('- `PARSEME.md` - Main project context and overview');
|
|
132
|
-
console.log('- `parseme-context/` - Detailed JSON files with code structure, dependencies, and git info');
|
|
133
|
-
console.log('');
|
|
134
|
-
}
|
|
135
142
|
}
|
|
136
143
|
catch (error) {
|
|
144
|
+
if (error instanceof Error && error.message.includes('No configuration file found')) {
|
|
145
|
+
console.error(error.message);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
137
148
|
console.error('Failed to generate context:', error);
|
|
138
|
-
console.log('Run "parseme init" to create a configuration file');
|
|
139
149
|
process.exit(1);
|
|
140
150
|
}
|
|
141
151
|
})();
|
|
@@ -31,12 +31,3 @@ export async function prompt(options) {
|
|
|
31
31
|
});
|
|
32
32
|
});
|
|
33
33
|
}
|
|
34
|
-
export async function confirmPrompt(message, defaultValue = true) {
|
|
35
|
-
const answer = await prompt({
|
|
36
|
-
message,
|
|
37
|
-
defaultValue: defaultValue ? 'y' : 'n',
|
|
38
|
-
choices: ['y', 'n', 'yes', 'no'],
|
|
39
|
-
});
|
|
40
|
-
const normalized = answer.toLowerCase();
|
|
41
|
-
return normalized === 'y' || normalized === 'yes';
|
|
42
|
-
}
|
|
@@ -15,11 +15,12 @@ export class ASTAnalyzer {
|
|
|
15
15
|
this.ig = ignore();
|
|
16
16
|
const configData = this.config.get();
|
|
17
17
|
this.ig.add(configData.excludePatterns || []);
|
|
18
|
-
this.patternDetector = new PatternDetector(
|
|
18
|
+
this.patternDetector = new PatternDetector();
|
|
19
19
|
}
|
|
20
20
|
async analyzeProject(rootDir) {
|
|
21
21
|
const configData = this.config.get();
|
|
22
|
-
const
|
|
22
|
+
const fileTypes = configData.analyzeFileTypes || ['ts', 'tsx', 'js', 'jsx'];
|
|
23
|
+
const patterns = fileTypes.map((type) => `**/*.${type}`);
|
|
23
24
|
const files = await glob(patterns, {
|
|
24
25
|
cwd: rootDir,
|
|
25
26
|
absolute: true,
|
|
@@ -58,7 +59,7 @@ export class ASTAnalyzer {
|
|
|
58
59
|
const patterns = this.patternDetector.analyzePatterns(ast, relativePath, content);
|
|
59
60
|
const analysis = {
|
|
60
61
|
path: relativePath,
|
|
61
|
-
type: this.determineFileType(relativePath,
|
|
62
|
+
type: this.determineFileType(relativePath, patterns),
|
|
62
63
|
exports: [],
|
|
63
64
|
imports: [],
|
|
64
65
|
functions: [],
|
|
@@ -116,8 +117,8 @@ export class ASTAnalyzer {
|
|
|
116
117
|
});
|
|
117
118
|
return analysis;
|
|
118
119
|
}
|
|
119
|
-
catch
|
|
120
|
-
console.warn(`Failed to parse ${relativePath}
|
|
120
|
+
catch {
|
|
121
|
+
console.warn(`Failed to parse ${relativePath}`);
|
|
121
122
|
return null;
|
|
122
123
|
}
|
|
123
124
|
}
|
|
@@ -144,7 +145,7 @@ export class ASTAnalyzer {
|
|
|
144
145
|
],
|
|
145
146
|
});
|
|
146
147
|
}
|
|
147
|
-
determineFileType(relativePath,
|
|
148
|
+
determineFileType(relativePath, patterns) {
|
|
148
149
|
// Use pattern analysis to determine file type dynamically
|
|
149
150
|
if (patterns.endpoints.length > 0) {
|
|
150
151
|
return 'route';
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { ParsemeConfig } from '../config.js';
|
|
2
1
|
import type { ProjectInfo, FrameworkInfo } from '../types.js';
|
|
3
2
|
export declare class FrameworkDetector {
|
|
4
|
-
private readonly config;
|
|
5
|
-
constructor(config: ParsemeConfig);
|
|
6
3
|
detect(projectInfo: ProjectInfo): Promise<FrameworkInfo>;
|
|
7
4
|
private detectExpress;
|
|
8
5
|
private detectFastify;
|
|
@@ -1,8 +1,5 @@
|
|
|
1
|
-
import type { ParsemeConfig } from '../config.js';
|
|
2
1
|
import type { GitInfo } from '../types.js';
|
|
3
2
|
export declare class GitAnalyzer {
|
|
4
|
-
private readonly config;
|
|
5
|
-
constructor(config: ParsemeConfig);
|
|
6
3
|
analyze(rootDir: string): Promise<GitInfo | null>;
|
|
7
4
|
private getCurrentBranch;
|
|
8
5
|
private getLastCommit;
|
|
@@ -2,10 +2,6 @@ import { exec } from 'child_process';
|
|
|
2
2
|
import { promisify } from 'util';
|
|
3
3
|
const execAsync = promisify(exec);
|
|
4
4
|
export class GitAnalyzer {
|
|
5
|
-
config;
|
|
6
|
-
constructor(config) {
|
|
7
|
-
this.config = config;
|
|
8
|
-
}
|
|
9
5
|
async analyze(rootDir) {
|
|
10
6
|
try {
|
|
11
7
|
// Check if this is a git repository
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
|
-
import type { ParsemeConfig } from '../config.js';
|
|
3
2
|
import type { ComponentInfo, RouteInfo } from '../types.js';
|
|
4
3
|
export interface PatternAnalysis {
|
|
5
4
|
endpoints: EndpointInfo[];
|
|
@@ -51,8 +50,6 @@ export interface UtilityInfo {
|
|
|
51
50
|
type: 'helper' | 'lib' | 'hook' | 'composable';
|
|
52
51
|
}
|
|
53
52
|
export declare class PatternDetector {
|
|
54
|
-
private readonly config;
|
|
55
|
-
constructor(config: ParsemeConfig);
|
|
56
53
|
analyzePatterns(ast: t.File, filePath: string, _content: string): PatternAnalysis;
|
|
57
54
|
private hasJSXReturn;
|
|
58
55
|
}
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import traverse from '@babel/traverse';
|
|
2
2
|
import * as t from '@babel/types';
|
|
3
3
|
export class PatternDetector {
|
|
4
|
-
config;
|
|
5
|
-
constructor(config) {
|
|
6
|
-
this.config = config;
|
|
7
|
-
}
|
|
8
4
|
analyzePatterns(ast, filePath, _content) {
|
|
9
5
|
const analysis = {
|
|
10
6
|
endpoints: [],
|
|
@@ -17,25 +13,6 @@ export class PatternDetector {
|
|
|
17
13
|
};
|
|
18
14
|
// Use a single traverse call to detect all patterns
|
|
19
15
|
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
16
|
// Detect decorator-based routes: @Get(), @Post(), etc.
|
|
40
17
|
ClassMethod: (path) => {
|
|
41
18
|
const decorators = path.node.decorators;
|
|
@@ -63,6 +40,58 @@ export class PatternDetector {
|
|
|
63
40
|
});
|
|
64
41
|
}
|
|
65
42
|
},
|
|
43
|
+
// Detect Express/Router-style routes: app.get(), router.post(), etc.
|
|
44
|
+
CallExpression: (path) => {
|
|
45
|
+
const { callee, arguments: args } = path.node;
|
|
46
|
+
if (t.isMemberExpression(callee) && t.isIdentifier(callee.property)) {
|
|
47
|
+
const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head'];
|
|
48
|
+
const methodName = callee.property.name;
|
|
49
|
+
if (httpMethods.includes(methodName) && args.length >= 2) {
|
|
50
|
+
const routePath = args[0];
|
|
51
|
+
if (t.isStringLiteral(routePath)) {
|
|
52
|
+
analysis.endpoints.push({
|
|
53
|
+
method: methodName.toUpperCase(),
|
|
54
|
+
path: routePath.value,
|
|
55
|
+
handler: 'anonymous',
|
|
56
|
+
file: filePath,
|
|
57
|
+
line: path.node.loc?.start.line || 0,
|
|
58
|
+
type: 'rest',
|
|
59
|
+
framework: 'express',
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
// Detect TypeScript interfaces and type aliases
|
|
66
|
+
TSInterfaceDeclaration: (path) => {
|
|
67
|
+
const interfaceName = path.node.id.name;
|
|
68
|
+
const fields = path.node.body.body
|
|
69
|
+
.filter((member) => t.isTSPropertySignature(member))
|
|
70
|
+
.map((member) => {
|
|
71
|
+
if (t.isIdentifier(member.key)) {
|
|
72
|
+
return member.key.name;
|
|
73
|
+
}
|
|
74
|
+
return '';
|
|
75
|
+
})
|
|
76
|
+
.filter((name) => name !== '');
|
|
77
|
+
analysis.models.push({
|
|
78
|
+
name: interfaceName,
|
|
79
|
+
file: filePath,
|
|
80
|
+
line: path.node.loc?.start.line || 0,
|
|
81
|
+
fields,
|
|
82
|
+
type: 'interface',
|
|
83
|
+
});
|
|
84
|
+
},
|
|
85
|
+
TSTypeAliasDeclaration: (path) => {
|
|
86
|
+
const typeName = path.node.id.name;
|
|
87
|
+
analysis.models.push({
|
|
88
|
+
name: typeName,
|
|
89
|
+
file: filePath,
|
|
90
|
+
line: path.node.loc?.start.line || 0,
|
|
91
|
+
fields: [],
|
|
92
|
+
type: 'type',
|
|
93
|
+
});
|
|
94
|
+
},
|
|
66
95
|
// Detect React components
|
|
67
96
|
FunctionDeclaration: (path) => {
|
|
68
97
|
const functionName = path.node.id?.name;
|
|
@@ -141,30 +170,6 @@ export class PatternDetector {
|
|
|
141
170
|
});
|
|
142
171
|
}
|
|
143
172
|
},
|
|
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
173
|
});
|
|
169
174
|
return analysis;
|
|
170
175
|
}
|
|
@@ -2,6 +2,7 @@ import type { ParsemeConfig } from '../config.js';
|
|
|
2
2
|
import type { ProjectInfo } from '../types.js';
|
|
3
3
|
export declare class ProjectAnalyzer {
|
|
4
4
|
private readonly config;
|
|
5
|
+
private readonly ig;
|
|
5
6
|
constructor(config: ParsemeConfig);
|
|
6
7
|
analyze(rootDir: string): Promise<ProjectInfo>;
|
|
7
8
|
private detectProjectType;
|
|
@@ -11,4 +12,5 @@ export declare class ProjectAnalyzer {
|
|
|
11
12
|
private detectEntryPoints;
|
|
12
13
|
private detectOutputTargets;
|
|
13
14
|
private hasAppDependencies;
|
|
15
|
+
getAllProjectFiles(rootDir: string): Promise<string[]>;
|
|
14
16
|
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { readFile, access, readdir, stat } from 'fs/promises';
|
|
2
|
-
import { join, basename } from 'path';
|
|
2
|
+
import { join, basename, relative } from 'path';
|
|
3
|
+
import ignore from 'ignore';
|
|
3
4
|
export class ProjectAnalyzer {
|
|
4
5
|
config;
|
|
6
|
+
ig;
|
|
5
7
|
constructor(config) {
|
|
6
8
|
this.config = config;
|
|
9
|
+
this.ig = ignore();
|
|
10
|
+
const configData = this.config.get();
|
|
11
|
+
this.ig.add(configData.excludePatterns || []);
|
|
7
12
|
}
|
|
8
13
|
async analyze(rootDir) {
|
|
9
14
|
const packageJsonPath = join(rootDir, 'package.json');
|
|
@@ -30,7 +35,7 @@ export class ProjectAnalyzer {
|
|
|
30
35
|
name: basename(rootDir),
|
|
31
36
|
type: await this.detectProjectType(rootDir),
|
|
32
37
|
category: 'unknown',
|
|
33
|
-
packageManager: '
|
|
38
|
+
packageManager: 'unknown',
|
|
34
39
|
dependencies: {},
|
|
35
40
|
devDependencies: {},
|
|
36
41
|
scripts: {},
|
|
@@ -41,7 +46,7 @@ export class ProjectAnalyzer {
|
|
|
41
46
|
}
|
|
42
47
|
async detectProjectType(rootDir) {
|
|
43
48
|
try {
|
|
44
|
-
const files = await this.getFilesRecursive(rootDir, 2); // Only check 2 levels deep
|
|
49
|
+
const files = await this.getFilesRecursive(rootDir, rootDir, 2); // Only check 2 levels deep
|
|
45
50
|
const tsFiles = files.filter((f) => f.endsWith('.ts') && !f.endsWith('.d.ts'));
|
|
46
51
|
const jsFiles = files.filter((f) => f.endsWith('.js'));
|
|
47
52
|
if (tsFiles.length > 0 && jsFiles.length > 0) {
|
|
@@ -74,7 +79,7 @@ export class ProjectAnalyzer {
|
|
|
74
79
|
}
|
|
75
80
|
return 'npm';
|
|
76
81
|
}
|
|
77
|
-
async getFilesRecursive(dir, maxDepth) {
|
|
82
|
+
async getFilesRecursive(dir, rootDir, maxDepth) {
|
|
78
83
|
if (maxDepth <= 0) {
|
|
79
84
|
return [];
|
|
80
85
|
}
|
|
@@ -83,12 +88,17 @@ export class ProjectAnalyzer {
|
|
|
83
88
|
const files = [];
|
|
84
89
|
for (const entry of entries) {
|
|
85
90
|
const fullPath = join(dir, entry);
|
|
91
|
+
const relativePath = relative(rootDir, fullPath);
|
|
92
|
+
// Skip if ignored by exclude patterns
|
|
93
|
+
if (this.ig.ignores(relativePath)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
86
96
|
const stats = await stat(fullPath);
|
|
87
97
|
if (stats.isFile()) {
|
|
88
98
|
files.push(fullPath);
|
|
89
99
|
}
|
|
90
100
|
else if (stats.isDirectory() && !entry.startsWith('.') && entry !== 'node_modules') {
|
|
91
|
-
files.push(...(await this.getFilesRecursive(fullPath, maxDepth - 1)));
|
|
101
|
+
files.push(...(await this.getFilesRecursive(fullPath, rootDir, maxDepth - 1)));
|
|
92
102
|
}
|
|
93
103
|
}
|
|
94
104
|
return files;
|
|
@@ -202,4 +212,8 @@ export class ProjectAnalyzer {
|
|
|
202
212
|
];
|
|
203
213
|
return appIndicators.some((indicator) => deps[indicator]);
|
|
204
214
|
}
|
|
215
|
+
async getAllProjectFiles(rootDir) {
|
|
216
|
+
const allFiles = await this.getFilesRecursive(rootDir, rootDir, Infinity);
|
|
217
|
+
return allFiles.map((file) => relative(rootDir, file));
|
|
218
|
+
}
|
|
205
219
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ParsemeConfigFile } from './types.js';
|
|
2
|
+
export declare class ParsemeConfig {
|
|
3
|
+
private readonly config;
|
|
4
|
+
private readonly userConfig;
|
|
5
|
+
constructor(config?: Partial<ParsemeConfigFile>);
|
|
6
|
+
static fromFileWithOptions(configPath?: string, cliOptions?: Partial<ParsemeConfigFile>, options?: {
|
|
7
|
+
showWarnings?: boolean;
|
|
8
|
+
}): Promise<ParsemeConfig>;
|
|
9
|
+
static fromFile(configPath?: string, options?: {
|
|
10
|
+
showWarnings?: boolean;
|
|
11
|
+
throwOnNotFound?: boolean;
|
|
12
|
+
}): Promise<ParsemeConfig>;
|
|
13
|
+
private mergeWithDefaults;
|
|
14
|
+
get(): ParsemeConfigFile;
|
|
15
|
+
save(path?: string): Promise<void>;
|
|
16
|
+
private generateJSConfig;
|
|
17
|
+
private generateTSConfig;
|
|
18
|
+
private mergeExcludePatterns;
|
|
19
|
+
private readGitignorePatterns;
|
|
20
|
+
}
|