progga 1.0.4 → 1.0.6
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 +85 -71
- package/core/PresetSelector.js +23 -0
- package/core/ProjectTypeDetector.js +34 -0
- package/core/Spinner.js +32 -0
- package/index.js +103 -38
- package/package.json +7 -2
- package/profiles/FlutterProfile.js +65 -0
- package/profiles/ProfileRegistry.js +5 -0
- package/profiles/ProjectProfile.js +14 -2
package/README.md
CHANGED
|
@@ -1,110 +1,124 @@
|
|
|
1
1
|
# progga (প্রজ্ঞা)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Progga** is a CLI tool that generates a single Markdown file representing the essential context of a software project.
|
|
4
|
+
The output is optimized for uploading to AI assistants (ChatGPT, Claude, Gemini) so they can understand a project quickly and accurately.
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## Getting Started
|
|
8
9
|
|
|
9
|
-
|
|
10
|
+
### Run with npx (recommended)
|
|
10
11
|
|
|
11
|
-
- 💬 Get AI help with your entire codebase
|
|
12
|
-
- 📤 Share project context without multiple file uploads
|
|
13
|
-
- 🤖 Enable ChatGPT/Claude/Gemini to understand your project structure
|
|
14
|
-
- 📚 Create comprehensive documentation snapshots
|
|
15
|
-
|
|
16
|
-
## ✨ Features
|
|
17
|
-
|
|
18
|
-
- 📁 Visual folder tree structure
|
|
19
|
-
- 📄 All file contents with syntax highlighting
|
|
20
|
-
- 🚫 Automatically ignores dependencies and build artifacts
|
|
21
|
-
- ⚡ One command, one file, complete context
|
|
22
|
-
- 🎯 Optimized for AI consumption
|
|
23
|
-
|
|
24
|
-
## 🚀 Installation
|
|
25
|
-
|
|
26
|
-
### Using npx (Recommended - No Installation!)
|
|
27
12
|
```bash
|
|
28
13
|
npx progga@latest
|
|
29
14
|
```
|
|
30
15
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
16
|
+
This generates a file named:
|
|
17
|
+
|
|
18
|
+
```
|
|
19
|
+
PROJECT_DOCUMENTATION.md
|
|
34
20
|
```
|
|
35
21
|
|
|
36
|
-
|
|
22
|
+
in the current directory.
|
|
37
23
|
|
|
38
|
-
###
|
|
24
|
+
### Run on a specific project
|
|
39
25
|
|
|
40
|
-
Generate documentation for current directory:
|
|
41
26
|
```bash
|
|
42
|
-
|
|
27
|
+
progga /path/to/project
|
|
43
28
|
```
|
|
44
29
|
|
|
45
|
-
|
|
30
|
+
### Custom output file
|
|
46
31
|
|
|
47
|
-
### Specify Project Path
|
|
48
32
|
```bash
|
|
49
|
-
|
|
33
|
+
progga . my-ai-context.md
|
|
50
34
|
```
|
|
51
35
|
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
npx progga@latest . my-ai-context.md
|
|
55
|
-
```
|
|
36
|
+
---
|
|
56
37
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
38
|
+
## How Progga Works (Short Example)
|
|
39
|
+
|
|
40
|
+
Given a project like:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
my-app/
|
|
44
|
+
├── src/
|
|
45
|
+
│ └── index.js
|
|
46
|
+
├── package.json
|
|
47
|
+
├── node_modules/
|
|
48
|
+
└── build/
|
|
60
49
|
```
|
|
61
50
|
|
|
62
|
-
|
|
51
|
+
Progga generates a single Markdown file containing:
|
|
63
52
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
53
|
+
* A clean folder tree (excluding `node_modules`, `build`, etc.)
|
|
54
|
+
* The contents of relevant source files
|
|
55
|
+
* Proper code blocks with language hints
|
|
67
56
|
|
|
68
|
-
Example
|
|
69
|
-
- "Review my code architecture"
|
|
70
|
-
- "Find potential bugs"
|
|
71
|
-
- "Suggest improvements"
|
|
72
|
-
- "Explain how this project works"
|
|
73
|
-
- "Help me add a new feature"
|
|
57
|
+
Example output structure:
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
````markdown
|
|
60
|
+
# Project Documentation: my-app
|
|
76
61
|
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- Lock files
|
|
83
|
-
- Binary files (images, videos, fonts)
|
|
62
|
+
## Folder Structure
|
|
63
|
+
my-app/
|
|
64
|
+
├── src/
|
|
65
|
+
│ └── index.js
|
|
66
|
+
├── package.json
|
|
84
67
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
|
|
68
|
+
## File Contents
|
|
69
|
+
### src/index.js
|
|
70
|
+
```js
|
|
71
|
+
// file content here
|
|
72
|
+
````
|
|
88
73
|
|
|
89
|
-
|
|
90
|
-
[Visual tree of all files and folders]
|
|
74
|
+
You can upload this file directly to an AI and ask questions about the project.
|
|
91
75
|
|
|
92
|
-
##
|
|
93
|
-
|
|
94
|
-
|
|
76
|
+
## Project Presets
|
|
77
|
+
|
|
78
|
+
Progga supports project-type presets that control what files are included.
|
|
79
|
+
|
|
80
|
+
Currently supported:
|
|
81
|
+
- `generic` (default)
|
|
82
|
+
- `flutter` (Android, iOS, Web, Windows, macOS, Linux)
|
|
83
|
+
|
|
84
|
+
If no preset is provided, Progga attempts to detect the project type and asks which preset to use.
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
progga --preset flutter
|
|
88
|
+
````
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Contributing
|
|
93
|
+
|
|
94
|
+
Contributions are welcome.
|
|
95
|
+
|
|
96
|
+
Good areas to contribute:
|
|
97
|
+
|
|
98
|
+
* New project presets (Node.js, Python, Go, etc.)
|
|
99
|
+
* Improving Flutter include-only rules
|
|
100
|
+
* Performance improvements
|
|
101
|
+
* Better project auto-detection
|
|
102
|
+
* Documentation and examples
|
|
103
|
+
|
|
104
|
+
### How to contribute
|
|
105
|
+
|
|
106
|
+
1. Fork the repository
|
|
107
|
+
2. Create a feature branch
|
|
108
|
+
3. Make focused changes
|
|
109
|
+
4. Open a pull request with a clear description
|
|
95
110
|
|
|
96
|
-
|
|
111
|
+
Opening an issue to discuss ideas is also encouraged.
|
|
97
112
|
|
|
98
|
-
|
|
113
|
+
---
|
|
99
114
|
|
|
100
|
-
##
|
|
115
|
+
## Requirements
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
* Node.js 12 or newer (Node 18+ recommended)
|
|
103
118
|
|
|
104
|
-
|
|
119
|
+
---
|
|
105
120
|
|
|
106
|
-
|
|
121
|
+
## License
|
|
107
122
|
|
|
108
|
-
|
|
123
|
+
MIT License
|
|
109
124
|
|
|
110
|
-
**Progga** (প্রজ্ঞা) is a Bengali word meaning "wisdom" or "insight" - representing the wisdom you share with AI assistants about your codebase.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const select = require('@inquirer/select').default;
|
|
2
|
+
|
|
3
|
+
class PresetSelector {
|
|
4
|
+
static async choose(detectedType) {
|
|
5
|
+
const preset = await select({
|
|
6
|
+
message: 'Select how you want to proceed:',
|
|
7
|
+
choices: [
|
|
8
|
+
{
|
|
9
|
+
name: `Use ${detectedType} preset (recommended)`,
|
|
10
|
+
value: detectedType
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'Use generic preset',
|
|
14
|
+
value: 'generic'
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
return preset;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
module.exports = PresetSelector;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
class ProjectTypeDetector {
|
|
5
|
+
constructor(projectRoot) {
|
|
6
|
+
this.projectRoot = projectRoot;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
detect() {
|
|
10
|
+
const signals = [];
|
|
11
|
+
|
|
12
|
+
if (this.isFlutterProject()) {
|
|
13
|
+
signals.push({
|
|
14
|
+
type: 'flutter',
|
|
15
|
+
confidence: 'high',
|
|
16
|
+
reason: 'pubspec.yaml found'
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// future:
|
|
21
|
+
// if (this.isNodeProject()) ...
|
|
22
|
+
// if (this.isPythonProject()) ...
|
|
23
|
+
|
|
24
|
+
return signals;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
isFlutterProject() {
|
|
28
|
+
return fs.existsSync(
|
|
29
|
+
path.join(this.projectRoot, 'pubspec.yaml')
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = ProjectTypeDetector;
|
package/core/Spinner.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const ora = require('ora').default;
|
|
2
|
+
|
|
3
|
+
class Spinner {
|
|
4
|
+
constructor(text) {
|
|
5
|
+
this.enabled = process.stdout.isTTY;
|
|
6
|
+
this.spinner = this.enabled ? ora(text).start() : null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
succeed(text) {
|
|
10
|
+
if (this.spinner) {
|
|
11
|
+
this.spinner.succeed(text);
|
|
12
|
+
} else {
|
|
13
|
+
console.log(`✓ ${text}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fail(text) {
|
|
18
|
+
if (this.spinner) {
|
|
19
|
+
this.spinner.fail(text);
|
|
20
|
+
} else {
|
|
21
|
+
console.error(`✗ ${text}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
update(text) {
|
|
26
|
+
if (this.spinner) {
|
|
27
|
+
this.spinner.text = text;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
module.exports = Spinner;
|
package/index.js
CHANGED
|
@@ -8,8 +8,14 @@
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const ProfileRegistry = require('./profiles/ProfileRegistry');
|
|
11
|
+
const ProjectTypeDetector = require('./core/ProjectTypeDetector');
|
|
12
|
+
const PresetSelector = require('./core/PresetSelector');
|
|
13
|
+
const Spinner = require('./core/Spinner');
|
|
11
14
|
|
|
12
15
|
|
|
16
|
+
const CLI_IGNORE_PATHS = new Set();
|
|
17
|
+
const CLI_IGNORE_EXTENSIONS = new Set();
|
|
18
|
+
|
|
13
19
|
/**
|
|
14
20
|
* Check if path should be ignored
|
|
15
21
|
*/
|
|
@@ -18,13 +24,19 @@ function shouldIgnore(filePath, basePath, profile) {
|
|
|
18
24
|
const parts = relativePath.split(path.sep);
|
|
19
25
|
|
|
20
26
|
for (const part of parts) {
|
|
21
|
-
if (
|
|
27
|
+
if (
|
|
28
|
+
profile.ignorePaths().includes(part) ||
|
|
29
|
+
CLI_IGNORE_PATHS.has(part)
|
|
30
|
+
) {
|
|
22
31
|
return true;
|
|
23
32
|
}
|
|
24
33
|
}
|
|
25
34
|
|
|
26
35
|
const ext = path.extname(filePath);
|
|
27
|
-
if (
|
|
36
|
+
if (
|
|
37
|
+
profile.ignoreExtensions().includes(ext) ||
|
|
38
|
+
CLI_IGNORE_EXTENSIONS.has(ext)
|
|
39
|
+
) {
|
|
28
40
|
return true;
|
|
29
41
|
}
|
|
30
42
|
|
|
@@ -50,43 +62,43 @@ function isDirectoryEmpty(directory, basePath) {
|
|
|
50
62
|
/**
|
|
51
63
|
* Generate tree structure recursively
|
|
52
64
|
*/
|
|
53
|
-
function generateTree(
|
|
65
|
+
function generateTree(directory, prefix = '', isLast = true, basePath = null, profile) {
|
|
54
66
|
if (basePath === null) {
|
|
55
67
|
basePath = directory;
|
|
56
68
|
}
|
|
57
|
-
|
|
69
|
+
|
|
58
70
|
const treeLines = [];
|
|
59
|
-
|
|
71
|
+
|
|
60
72
|
try {
|
|
61
73
|
let items = fs.readdirSync(directory).map(name => {
|
|
62
74
|
const fullPath = path.join(directory, name);
|
|
63
75
|
const stats = fs.statSync(fullPath);
|
|
64
76
|
return { name, fullPath, isDir: stats.isDirectory() };
|
|
65
77
|
});
|
|
66
|
-
|
|
78
|
+
|
|
67
79
|
// Filter ignored items
|
|
68
80
|
items = items.filter(item => !shouldIgnore(item.fullPath, basePath, profile));
|
|
69
|
-
|
|
81
|
+
|
|
70
82
|
// Sort: directories first, then alphabetically
|
|
71
83
|
items.sort((a, b) => {
|
|
72
84
|
if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
|
|
73
85
|
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
74
86
|
});
|
|
75
|
-
|
|
87
|
+
|
|
76
88
|
for (let i = 0; i < items.length; i++) {
|
|
77
89
|
const item = items[i];
|
|
78
90
|
const isLastItem = i === items.length - 1;
|
|
79
|
-
|
|
91
|
+
|
|
80
92
|
// Tree characters
|
|
81
93
|
const connector = isLastItem ? '└── ' : '├── ';
|
|
82
94
|
const extension = isLastItem ? ' ' : '│ ';
|
|
83
|
-
|
|
95
|
+
|
|
84
96
|
if (item.isDir) {
|
|
85
97
|
if (isDirectoryEmpty(item.fullPath, basePath)) {
|
|
86
98
|
treeLines.push(`${prefix}${connector}${item.name}/ (empty)`);
|
|
87
99
|
} else {
|
|
88
100
|
treeLines.push(`${prefix}${connector}${item.name}/`);
|
|
89
|
-
const subtree = generateTree(
|
|
101
|
+
const subtree = generateTree(item.fullPath, prefix + extension, isLastItem, basePath, profile);
|
|
90
102
|
treeLines.push(...subtree);
|
|
91
103
|
}
|
|
92
104
|
} else {
|
|
@@ -102,7 +114,7 @@ function generateTree( directory, prefix = '', isLast = true, basePath = null, p
|
|
|
102
114
|
} catch (err) {
|
|
103
115
|
// Permission error or other issues
|
|
104
116
|
}
|
|
105
|
-
|
|
117
|
+
|
|
106
118
|
return treeLines;
|
|
107
119
|
}
|
|
108
120
|
|
|
@@ -140,7 +152,7 @@ function readFileContent(filePath) {
|
|
|
140
152
|
if (stats.size === 0) {
|
|
141
153
|
return '(empty file)';
|
|
142
154
|
}
|
|
143
|
-
|
|
155
|
+
|
|
144
156
|
return fs.readFileSync(filePath, 'utf-8');
|
|
145
157
|
} catch (err) {
|
|
146
158
|
if (err.message.includes('invalid')) {
|
|
@@ -182,7 +194,7 @@ function getLanguageFromExtension(filePath) {
|
|
|
182
194
|
'.rb': 'ruby',
|
|
183
195
|
'.php': 'php',
|
|
184
196
|
};
|
|
185
|
-
|
|
197
|
+
|
|
186
198
|
const ext = path.extname(filePath);
|
|
187
199
|
return extMap[ext] || '';
|
|
188
200
|
}
|
|
@@ -211,7 +223,7 @@ function collectFiles(directory, basePath, profile) {
|
|
|
211
223
|
files.push(...collectFiles(fullPath, basePath, profile));
|
|
212
224
|
}
|
|
213
225
|
}
|
|
214
|
-
} catch {}
|
|
226
|
+
} catch { }
|
|
215
227
|
|
|
216
228
|
return files;
|
|
217
229
|
}
|
|
@@ -221,15 +233,15 @@ function collectFiles(directory, basePath, profile) {
|
|
|
221
233
|
*/
|
|
222
234
|
function generateDocumentation(projectPath, outputFile, profile) {
|
|
223
235
|
const absProjectPath = path.resolve(projectPath);
|
|
224
|
-
|
|
236
|
+
|
|
225
237
|
if (!fs.existsSync(absProjectPath)) {
|
|
226
238
|
console.error(`Error: Path '${absProjectPath}' does not exist`);
|
|
227
239
|
process.exit(1);
|
|
228
240
|
}
|
|
229
|
-
|
|
241
|
+
|
|
230
242
|
console.log(`Generating documentation for: ${absProjectPath}`);
|
|
231
243
|
console.log(`Output file: ${outputFile}`);
|
|
232
|
-
|
|
244
|
+
|
|
233
245
|
// Delete existing output file if it exists
|
|
234
246
|
if (fs.existsSync(outputFile)) {
|
|
235
247
|
try {
|
|
@@ -239,43 +251,48 @@ function generateDocumentation(projectPath, outputFile, profile) {
|
|
|
239
251
|
console.warn(`Warning: Could not delete existing file: ${err.message}`);
|
|
240
252
|
}
|
|
241
253
|
}
|
|
242
|
-
|
|
254
|
+
|
|
243
255
|
const projectName = path.basename(absProjectPath);
|
|
244
256
|
let output = '';
|
|
245
|
-
|
|
257
|
+
|
|
246
258
|
// Write header
|
|
247
259
|
output += `# Project Documentation: ${projectName}\n\n`;
|
|
248
260
|
output += `**Generated from:** \`${absProjectPath}\`\n\n`;
|
|
249
261
|
output += '---\n\n';
|
|
250
|
-
|
|
262
|
+
|
|
251
263
|
// Write folder structure
|
|
252
264
|
output += '## 📁 Folder Structure\n\n';
|
|
253
265
|
output += '```\n';
|
|
254
266
|
output += `${projectName}/\n`;
|
|
255
|
-
|
|
267
|
+
|
|
268
|
+
const treeSpinner = new Spinner('Generating folder structure');
|
|
256
269
|
const treeLines = generateTree(absProjectPath, '', true, absProjectPath, profile);
|
|
270
|
+
treeSpinner.succeed('Folder structure generated');
|
|
271
|
+
|
|
257
272
|
for (const line of treeLines) {
|
|
258
273
|
output += `${line}\n`;
|
|
259
274
|
}
|
|
260
|
-
|
|
275
|
+
|
|
261
276
|
output += '```\n\n';
|
|
262
277
|
output += '---\n\n';
|
|
263
|
-
|
|
278
|
+
|
|
264
279
|
// Write file contents
|
|
265
280
|
output += '## 📄 File Contents\n\n';
|
|
266
|
-
|
|
281
|
+
|
|
282
|
+
const fileSpinner = new Spinner('Collecting files');
|
|
267
283
|
const files = collectFiles(absProjectPath, absProjectPath, profile);
|
|
268
|
-
|
|
284
|
+
fileSpinner.succeed(`Collected ${files.length} files`);
|
|
285
|
+
|
|
269
286
|
for (let i = 0; i < files.length; i++) {
|
|
270
287
|
const filePath = files[i];
|
|
271
288
|
const relPath = path.relative(absProjectPath, filePath);
|
|
272
289
|
console.log(`Processing (${i + 1}/${files.length}): ${relPath}`);
|
|
273
|
-
|
|
290
|
+
|
|
274
291
|
output += `### \`${relPath}\`\n\n`;
|
|
275
|
-
|
|
292
|
+
|
|
276
293
|
const content = readFileContent(filePath);
|
|
277
294
|
const language = getLanguageFromExtension(filePath);
|
|
278
|
-
|
|
295
|
+
|
|
279
296
|
output += `\`\`\`${language}\n`;
|
|
280
297
|
output += content;
|
|
281
298
|
if (!content.endsWith('\n')) {
|
|
@@ -284,27 +301,75 @@ function generateDocumentation(projectPath, outputFile, profile) {
|
|
|
284
301
|
output += '```\n\n';
|
|
285
302
|
output += '---\n\n';
|
|
286
303
|
}
|
|
287
|
-
|
|
304
|
+
|
|
288
305
|
// Write to file
|
|
289
306
|
fs.writeFileSync(outputFile, output, 'utf-8');
|
|
290
|
-
|
|
307
|
+
|
|
291
308
|
console.log(`\n✅ Documentation generated successfully: ${outputFile}`);
|
|
292
309
|
console.log(`📊 Total files processed: ${files.length}`);
|
|
293
310
|
}
|
|
294
311
|
|
|
295
312
|
// Main execution
|
|
296
|
-
function main() {
|
|
313
|
+
async function main() {
|
|
314
|
+
let cliIgnores = [];
|
|
297
315
|
const args = process.argv.slice(2);
|
|
298
316
|
|
|
299
|
-
|
|
300
|
-
|
|
317
|
+
let projectPath = '.';
|
|
318
|
+
let outputFile = 'PROJECT_DOCUMENTATION.md';
|
|
319
|
+
let projectType = null;
|
|
320
|
+
|
|
321
|
+
for (let i = 0; i < args.length; i++) {
|
|
322
|
+
const arg = args[i];
|
|
323
|
+
|
|
324
|
+
if (arg === '--ignore' || arg === '-i') {
|
|
325
|
+
const value = args[i + 1];
|
|
326
|
+
if (value) {
|
|
327
|
+
value
|
|
328
|
+
.split(',')
|
|
329
|
+
.map(v => v.trim())
|
|
330
|
+
.filter(Boolean)
|
|
331
|
+
.forEach(item => {
|
|
332
|
+
if (item.startsWith('.')) {
|
|
333
|
+
CLI_IGNORE_EXTENSIONS.add(item);
|
|
334
|
+
} else {
|
|
335
|
+
CLI_IGNORE_PATHS.add(item);
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
i++;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
let profile = ProfileRegistry.getByName(projectType, projectPath);
|
|
344
|
+
|
|
345
|
+
if (!profile) {
|
|
346
|
+
const detector = new ProjectTypeDetector(projectPath);
|
|
347
|
+
const detections = detector.detect();
|
|
348
|
+
|
|
349
|
+
const flutterSignal = detections.find(d => d.type === 'flutter');
|
|
301
350
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
351
|
+
if (flutterSignal && process.stdin.isTTY) {
|
|
352
|
+
console.log('');
|
|
353
|
+
console.log(`Detected project type: ${flutterSignal.type}`);
|
|
354
|
+
console.log('');
|
|
355
|
+
|
|
356
|
+
const selectedPreset = await PresetSelector.choose(flutterSignal.type);
|
|
357
|
+
profile = ProfileRegistry.getByName(selectedPreset, projectPath);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
306
360
|
|
|
361
|
+
if (!profile) {
|
|
362
|
+
profile = ProfileRegistry.fallback(projectPath);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (cliIgnores.length) {
|
|
366
|
+
profile.applyCliIgnores(cliIgnores);
|
|
367
|
+
console.log(`🚫 CLI ignores applied: ${cliIgnores.join(', ')}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
console.log(`Using profile: ${profile.name}`);
|
|
307
371
|
generateDocumentation(projectPath, outputFile, profile);
|
|
372
|
+
|
|
308
373
|
}
|
|
309
374
|
|
|
310
375
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progga",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Generate comprehensive project documentation for AI assistants - Share your entire codebase context in one markdown file",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"bin": {
|
|
@@ -37,5 +37,10 @@
|
|
|
37
37
|
"bugs": {
|
|
38
38
|
"url": "https://github.com/Yousuf-Basir/progga/issues"
|
|
39
39
|
},
|
|
40
|
-
"homepage": "https://github.com/Yousuf-Basir/progga#readme"
|
|
40
|
+
"homepage": "https://github.com/Yousuf-Basir/progga#readme",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@inquirer/select": "^5.0.4",
|
|
43
|
+
"inquirer": "^13.2.0",
|
|
44
|
+
"ora": "^9.0.0"
|
|
45
|
+
}
|
|
41
46
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const ProjectProfile = require('./ProjectProfile');
|
|
2
|
+
|
|
3
|
+
class FlutterProfile extends ProjectProfile {
|
|
4
|
+
get name() {
|
|
5
|
+
return 'flutter';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
ignorePaths() {
|
|
9
|
+
return [
|
|
10
|
+
// Dart / Flutter
|
|
11
|
+
'.dart_tool',
|
|
12
|
+
'build',
|
|
13
|
+
'.flutter-plugins',
|
|
14
|
+
'.flutter-plugins-dependencies',
|
|
15
|
+
|
|
16
|
+
// Android
|
|
17
|
+
'android/.gradle',
|
|
18
|
+
'android/build',
|
|
19
|
+
'android/app/build',
|
|
20
|
+
|
|
21
|
+
// iOS / macOS
|
|
22
|
+
'ios/Flutter/ephemeral',
|
|
23
|
+
'ios/Runner.xcodeproj',
|
|
24
|
+
'ios/Runner.xcworkspace',
|
|
25
|
+
'macos/Flutter/ephemeral',
|
|
26
|
+
'macos/Runner.xcodeproj',
|
|
27
|
+
|
|
28
|
+
// Web
|
|
29
|
+
'build/web',
|
|
30
|
+
|
|
31
|
+
// Windows
|
|
32
|
+
'windows/build',
|
|
33
|
+
|
|
34
|
+
// Linux
|
|
35
|
+
'linux/build',
|
|
36
|
+
|
|
37
|
+
// Common IDE noise
|
|
38
|
+
'.git',
|
|
39
|
+
'.idea',
|
|
40
|
+
'.vscode',
|
|
41
|
+
'.DS_Store',
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
ignoreExtensions() {
|
|
46
|
+
return [
|
|
47
|
+
// Assets & binaries
|
|
48
|
+
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico',
|
|
49
|
+
'.mp4', '.mp3', '.wav',
|
|
50
|
+
'.ttf', '.otf', '.woff', '.woff2',
|
|
51
|
+
|
|
52
|
+
// Archives
|
|
53
|
+
'.zip', '.rar', '.tar', '.gz',
|
|
54
|
+
|
|
55
|
+
// Compiled outputs
|
|
56
|
+
'.exe', '.dll', '.so', '.dylib',
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
binaryExtensions() {
|
|
61
|
+
return this.ignoreExtensions();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
module.exports = FlutterProfile;
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
const GenericProfile = require('./GenericProfile');
|
|
2
|
+
const FlutterProfile = require('./FlutterProfile');
|
|
2
3
|
|
|
3
4
|
class ProfileRegistry {
|
|
4
5
|
static getByName(name, projectRoot) {
|
|
5
6
|
if (!name) return null;
|
|
6
7
|
|
|
8
|
+
if (name === 'flutter') {
|
|
9
|
+
return new FlutterProfile(projectRoot);
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
if (name === 'generic') {
|
|
8
13
|
return new GenericProfile(projectRoot);
|
|
9
14
|
}
|
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
class ProjectProfile {
|
|
2
2
|
constructor(projectRoot) {
|
|
3
3
|
this.projectRoot = projectRoot;
|
|
4
|
+
this.cliIgnorePaths = [];
|
|
5
|
+
this.cliIgnoreExtensions = [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
applyCliIgnores(list) {
|
|
9
|
+
for (const item of list) {
|
|
10
|
+
if (item.startsWith('.')) {
|
|
11
|
+
this.cliIgnoreExtensions.push(item);
|
|
12
|
+
} else {
|
|
13
|
+
this.cliIgnorePaths.push(item);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
4
16
|
}
|
|
5
17
|
|
|
6
18
|
get name() {
|
|
@@ -9,12 +21,12 @@ class ProjectProfile {
|
|
|
9
21
|
|
|
10
22
|
/** Paths to fully ignore */
|
|
11
23
|
ignorePaths() {
|
|
12
|
-
return
|
|
24
|
+
return this.cliIgnorePaths;
|
|
13
25
|
}
|
|
14
26
|
|
|
15
27
|
/** Extensions to ignore */
|
|
16
28
|
ignoreExtensions() {
|
|
17
|
-
return
|
|
29
|
+
return this.cliIgnoreExtensions;
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
/** Binary extensions */
|