progga 1.0.0 → 1.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 +5 -5
- package/index.js +50 -80
- package/package.json +2 -2
- package/profiles/GenericProfile.js +52 -0
- package/profiles/ProfileRegistry.js +19 -0
- package/profiles/ProjectProfile.js +31 -0
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Upload one file, understand the entire project. **progga** creates a complete pr
|
|
|
25
25
|
|
|
26
26
|
### Using npx (Recommended - No Installation!)
|
|
27
27
|
```bash
|
|
28
|
-
npx progga
|
|
28
|
+
npx progga@latest
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
### Global Installation
|
|
@@ -39,24 +39,24 @@ npm install -g progga
|
|
|
39
39
|
|
|
40
40
|
Generate documentation for current directory:
|
|
41
41
|
```bash
|
|
42
|
-
npx progga
|
|
42
|
+
npx progga@latest
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
This creates `PROJECT_DOCUMENTATION.md` in your current directory.
|
|
46
46
|
|
|
47
47
|
### Specify Project Path
|
|
48
48
|
```bash
|
|
49
|
-
npx progga /path/to/your/project
|
|
49
|
+
npx progga@latest /path/to/your/project
|
|
50
50
|
```
|
|
51
51
|
|
|
52
52
|
### Custom Output File
|
|
53
53
|
```bash
|
|
54
|
-
npx progga . my-ai-context.md
|
|
54
|
+
npx progga@latest . my-ai-context.md
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
### Full Example
|
|
58
58
|
```bash
|
|
59
|
-
npx progga ./my-app ./docs/ai-context.md
|
|
59
|
+
npx progga@latest ./my-app ./docs/ai-context.md
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
## 💡 How to Use with AI Assistants
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Project Documentation Generator
|
|
@@ -7,65 +7,27 @@
|
|
|
7
7
|
|
|
8
8
|
const fs = require('fs');
|
|
9
9
|
const path = require('path');
|
|
10
|
+
const ProfileRegistry = require('./profiles/ProfileRegistry');
|
|
10
11
|
|
|
11
|
-
// Configure what to ignore
|
|
12
|
-
const IGNORE_PATTERNS = new Set([
|
|
13
|
-
'node_modules',
|
|
14
|
-
'.git',
|
|
15
|
-
'__pycache__',
|
|
16
|
-
'.vscode',
|
|
17
|
-
'dist',
|
|
18
|
-
'build',
|
|
19
|
-
'.next',
|
|
20
|
-
'venv',
|
|
21
|
-
'env',
|
|
22
|
-
'.env',
|
|
23
|
-
'coverage',
|
|
24
|
-
'.pytest_cache',
|
|
25
|
-
'.DS_Store',
|
|
26
|
-
'package-lock.json',
|
|
27
|
-
'yarn.lock',
|
|
28
|
-
'pnpm-lock.yaml',
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
// File extensions to exclude
|
|
32
|
-
const IGNORE_EXTENSIONS = new Set([
|
|
33
|
-
'.pyc',
|
|
34
|
-
'.pyo',
|
|
35
|
-
'.so',
|
|
36
|
-
'.dylib',
|
|
37
|
-
'.exe',
|
|
38
|
-
'.dll',
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
// Binary file extensions to skip
|
|
42
|
-
const BINARY_EXTENSIONS = new Set([
|
|
43
|
-
'.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg',
|
|
44
|
-
'.pdf', '.zip', '.tar', '.gz', '.rar',
|
|
45
|
-
'.mp4', '.mp3', '.wav',
|
|
46
|
-
'.woff', '.woff2', '.ttf', '.eot',
|
|
47
|
-
]);
|
|
48
12
|
|
|
49
13
|
/**
|
|
50
14
|
* Check if path should be ignored
|
|
51
15
|
*/
|
|
52
|
-
function shouldIgnore(filePath, basePath) {
|
|
16
|
+
function shouldIgnore(filePath, basePath, profile) {
|
|
53
17
|
const relativePath = path.relative(basePath, filePath);
|
|
54
18
|
const parts = relativePath.split(path.sep);
|
|
55
|
-
|
|
56
|
-
// Check each part of the path
|
|
19
|
+
|
|
57
20
|
for (const part of parts) {
|
|
58
|
-
if (
|
|
21
|
+
if (profile.ignorePaths().includes(part)) {
|
|
59
22
|
return true;
|
|
60
23
|
}
|
|
61
24
|
}
|
|
62
|
-
|
|
63
|
-
// Check file extension
|
|
25
|
+
|
|
64
26
|
const ext = path.extname(filePath);
|
|
65
|
-
if (
|
|
27
|
+
if (profile.ignoreExtensions().includes(ext)) {
|
|
66
28
|
return true;
|
|
67
29
|
}
|
|
68
|
-
|
|
30
|
+
|
|
69
31
|
return false;
|
|
70
32
|
}
|
|
71
33
|
|
|
@@ -88,7 +50,7 @@ function isDirectoryEmpty(directory, basePath) {
|
|
|
88
50
|
/**
|
|
89
51
|
* Generate tree structure recursively
|
|
90
52
|
*/
|
|
91
|
-
function generateTree(directory, prefix = '', isLast = true, basePath = null) {
|
|
53
|
+
function generateTree( directory, prefix = '', isLast = true, basePath = null, profile ) {
|
|
92
54
|
if (basePath === null) {
|
|
93
55
|
basePath = directory;
|
|
94
56
|
}
|
|
@@ -103,7 +65,7 @@ function generateTree(directory, prefix = '', isLast = true, basePath = null) {
|
|
|
103
65
|
});
|
|
104
66
|
|
|
105
67
|
// Filter ignored items
|
|
106
|
-
items = items.filter(item => !shouldIgnore(item.fullPath, basePath));
|
|
68
|
+
items = items.filter(item => !shouldIgnore(item.fullPath, basePath, profile));
|
|
107
69
|
|
|
108
70
|
// Sort: directories first, then alphabetically
|
|
109
71
|
items.sort((a, b) => {
|
|
@@ -124,7 +86,7 @@ function generateTree(directory, prefix = '', isLast = true, basePath = null) {
|
|
|
124
86
|
treeLines.push(`${prefix}${connector}${item.name}/ (empty)`);
|
|
125
87
|
} else {
|
|
126
88
|
treeLines.push(`${prefix}${connector}${item.name}/`);
|
|
127
|
-
const subtree = generateTree(item.fullPath, prefix + extension, isLastItem, basePath);
|
|
89
|
+
const subtree = generateTree( item.fullPath, prefix + extension, isLastItem, basePath, profile );
|
|
128
90
|
treeLines.push(...subtree);
|
|
129
91
|
}
|
|
130
92
|
} else {
|
|
@@ -147,28 +109,25 @@ function generateTree(directory, prefix = '', isLast = true, basePath = null) {
|
|
|
147
109
|
/**
|
|
148
110
|
* Check if file is binary
|
|
149
111
|
*/
|
|
150
|
-
function isBinaryFile(filePath) {
|
|
112
|
+
function isBinaryFile(filePath, profile) {
|
|
151
113
|
const ext = path.extname(filePath);
|
|
152
|
-
if (
|
|
114
|
+
if (profile.binaryExtensions().includes(ext)) {
|
|
153
115
|
return true;
|
|
154
116
|
}
|
|
155
|
-
|
|
117
|
+
|
|
156
118
|
try {
|
|
157
119
|
const buffer = Buffer.alloc(1024);
|
|
158
120
|
const fd = fs.openSync(filePath, 'r');
|
|
159
121
|
const bytesRead = fs.readSync(fd, buffer, 0, 1024, 0);
|
|
160
122
|
fs.closeSync(fd);
|
|
161
|
-
|
|
162
|
-
// Check for null bytes
|
|
123
|
+
|
|
163
124
|
for (let i = 0; i < bytesRead; i++) {
|
|
164
|
-
if (buffer[i] === 0)
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
125
|
+
if (buffer[i] === 0) return true;
|
|
167
126
|
}
|
|
168
|
-
} catch
|
|
127
|
+
} catch {
|
|
169
128
|
return true;
|
|
170
129
|
}
|
|
171
|
-
|
|
130
|
+
|
|
172
131
|
return false;
|
|
173
132
|
}
|
|
174
133
|
|
|
@@ -231,40 +190,36 @@ function getLanguageFromExtension(filePath) {
|
|
|
231
190
|
/**
|
|
232
191
|
* Collect all files recursively
|
|
233
192
|
*/
|
|
234
|
-
function collectFiles(directory, basePath) {
|
|
193
|
+
function collectFiles(directory, basePath, profile) {
|
|
235
194
|
const files = [];
|
|
236
|
-
|
|
195
|
+
|
|
237
196
|
try {
|
|
238
197
|
const items = fs.readdirSync(directory);
|
|
239
|
-
|
|
198
|
+
|
|
240
199
|
for (const item of items.sort()) {
|
|
241
200
|
const fullPath = path.join(directory, item);
|
|
242
|
-
|
|
243
|
-
if (shouldIgnore(fullPath, basePath))
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
|
|
201
|
+
|
|
202
|
+
if (shouldIgnore(fullPath, basePath, profile)) continue;
|
|
203
|
+
|
|
247
204
|
const stats = fs.statSync(fullPath);
|
|
248
|
-
|
|
205
|
+
|
|
249
206
|
if (stats.isFile()) {
|
|
250
|
-
if (!isBinaryFile(fullPath)) {
|
|
207
|
+
if (!isBinaryFile(fullPath, profile)) {
|
|
251
208
|
files.push(fullPath);
|
|
252
209
|
}
|
|
253
210
|
} else if (stats.isDirectory()) {
|
|
254
|
-
files.push(...collectFiles(fullPath, basePath));
|
|
211
|
+
files.push(...collectFiles(fullPath, basePath, profile));
|
|
255
212
|
}
|
|
256
213
|
}
|
|
257
|
-
} catch
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
|
|
214
|
+
} catch {}
|
|
215
|
+
|
|
261
216
|
return files;
|
|
262
217
|
}
|
|
263
218
|
|
|
264
219
|
/**
|
|
265
220
|
* Generate complete documentation markdown file
|
|
266
221
|
*/
|
|
267
|
-
function generateDocumentation(projectPath, outputFile) {
|
|
222
|
+
function generateDocumentation(projectPath, outputFile, profile) {
|
|
268
223
|
const absProjectPath = path.resolve(projectPath);
|
|
269
224
|
|
|
270
225
|
if (!fs.existsSync(absProjectPath)) {
|
|
@@ -275,6 +230,16 @@ function generateDocumentation(projectPath, outputFile) {
|
|
|
275
230
|
console.log(`Generating documentation for: ${absProjectPath}`);
|
|
276
231
|
console.log(`Output file: ${outputFile}`);
|
|
277
232
|
|
|
233
|
+
// Delete existing output file if it exists
|
|
234
|
+
if (fs.existsSync(outputFile)) {
|
|
235
|
+
try {
|
|
236
|
+
fs.unlinkSync(outputFile);
|
|
237
|
+
console.log(`✓ Deleted existing file: ${outputFile}`);
|
|
238
|
+
} catch (err) {
|
|
239
|
+
console.warn(`Warning: Could not delete existing file: ${err.message}`);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
278
243
|
const projectName = path.basename(absProjectPath);
|
|
279
244
|
let output = '';
|
|
280
245
|
|
|
@@ -288,7 +253,7 @@ function generateDocumentation(projectPath, outputFile) {
|
|
|
288
253
|
output += '```\n';
|
|
289
254
|
output += `${projectName}/\n`;
|
|
290
255
|
|
|
291
|
-
const treeLines = generateTree(absProjectPath, '', true, absProjectPath);
|
|
256
|
+
const treeLines = generateTree(absProjectPath, '', true, absProjectPath, profile);
|
|
292
257
|
for (const line of treeLines) {
|
|
293
258
|
output += `${line}\n`;
|
|
294
259
|
}
|
|
@@ -299,7 +264,7 @@ function generateDocumentation(projectPath, outputFile) {
|
|
|
299
264
|
// Write file contents
|
|
300
265
|
output += '## 📄 File Contents\n\n';
|
|
301
266
|
|
|
302
|
-
const files = collectFiles(absProjectPath, absProjectPath);
|
|
267
|
+
const files = collectFiles(absProjectPath, absProjectPath, profile);
|
|
303
268
|
|
|
304
269
|
for (let i = 0; i < files.length; i++) {
|
|
305
270
|
const filePath = files[i];
|
|
@@ -330,11 +295,16 @@ function generateDocumentation(projectPath, outputFile) {
|
|
|
330
295
|
// Main execution
|
|
331
296
|
function main() {
|
|
332
297
|
const args = process.argv.slice(2);
|
|
333
|
-
|
|
298
|
+
|
|
334
299
|
const projectPath = args[0] || '.';
|
|
335
300
|
const outputFile = args[1] || 'PROJECT_DOCUMENTATION.md';
|
|
336
|
-
|
|
337
|
-
|
|
301
|
+
|
|
302
|
+
// future: parse --project-type
|
|
303
|
+
const profile =
|
|
304
|
+
ProfileRegistry.getByName(null, projectPath) ||
|
|
305
|
+
ProfileRegistry.fallback(projectPath);
|
|
306
|
+
|
|
307
|
+
generateDocumentation(projectPath, outputFile, profile);
|
|
338
308
|
}
|
|
339
309
|
|
|
340
310
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progga",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
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": {
|
|
@@ -38,4 +38,4 @@
|
|
|
38
38
|
"url": "https://github.com/Yousuf-Basir/progga/issues"
|
|
39
39
|
},
|
|
40
40
|
"homepage": "https://github.com/Yousuf-Basir/progga#readme"
|
|
41
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const ProjectProfile = require('./ProjectProfile');
|
|
2
|
+
|
|
3
|
+
class GenericProfile extends ProjectProfile {
|
|
4
|
+
get name() {
|
|
5
|
+
return 'generic';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
ignorePaths() {
|
|
9
|
+
return [
|
|
10
|
+
'node_modules',
|
|
11
|
+
'.git',
|
|
12
|
+
'__pycache__',
|
|
13
|
+
'.vscode',
|
|
14
|
+
'dist',
|
|
15
|
+
'build',
|
|
16
|
+
'.next',
|
|
17
|
+
'venv',
|
|
18
|
+
'env',
|
|
19
|
+
'.env',
|
|
20
|
+
'coverage',
|
|
21
|
+
'.pytest_cache',
|
|
22
|
+
'.DS_Store',
|
|
23
|
+
'package-lock.json',
|
|
24
|
+
'yarn.lock',
|
|
25
|
+
'pnpm-lock.yaml',
|
|
26
|
+
'bun.lock',
|
|
27
|
+
'.turbo'
|
|
28
|
+
];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ignoreExtensions() {
|
|
32
|
+
return [
|
|
33
|
+
'.pyc',
|
|
34
|
+
'.pyo',
|
|
35
|
+
'.so',
|
|
36
|
+
'.dylib',
|
|
37
|
+
'.exe',
|
|
38
|
+
'.dll',
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
binaryExtensions() {
|
|
43
|
+
return [
|
|
44
|
+
'.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg',
|
|
45
|
+
'.pdf', '.zip', '.tar', '.gz', '.rar',
|
|
46
|
+
'.mp4', '.mp3', '.wav',
|
|
47
|
+
'.woff', '.woff2', '.ttf', '.eot',
|
|
48
|
+
];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = GenericProfile;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const GenericProfile = require('./GenericProfile');
|
|
2
|
+
|
|
3
|
+
class ProfileRegistry {
|
|
4
|
+
static getByName(name, projectRoot) {
|
|
5
|
+
if (!name) return null;
|
|
6
|
+
|
|
7
|
+
if (name === 'generic') {
|
|
8
|
+
return new GenericProfile(projectRoot);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static fallback(projectRoot) {
|
|
15
|
+
return new GenericProfile(projectRoot);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = ProfileRegistry;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
class ProjectProfile {
|
|
2
|
+
constructor(projectRoot) {
|
|
3
|
+
this.projectRoot = projectRoot;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
get name() {
|
|
7
|
+
return 'base';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Paths to fully ignore */
|
|
11
|
+
ignorePaths() {
|
|
12
|
+
return [];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Extensions to ignore */
|
|
16
|
+
ignoreExtensions() {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Binary extensions */
|
|
21
|
+
binaryExtensions() {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Should this profile auto-detect the project */
|
|
26
|
+
detect() {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = ProjectProfile;
|