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 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 (IGNORE_PATTERNS.has(part)) {
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 (IGNORE_EXTENSIONS.has(ext)) {
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 (BINARY_EXTENSIONS.has(ext)) {
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 (err) {
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
- continue;
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 (err) {
258
- // Permission error or other issues
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
- generateDocumentation(projectPath, outputFile);
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.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;