progga 1.0.2 → 1.0.5

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
@@ -1,110 +1,124 @@
1
1
  # progga (প্রজ্ঞা)
2
2
 
3
- > *Progga* means "wisdom" or "insight" in Bengali
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
- Generate comprehensive project documentation in a single markdown file - perfect for sharing your entire codebase context with AI assistants like ChatGPT, Claude, or Gemini.
6
+ ---
6
7
 
7
- ## 🎯 Purpose
8
+ ## Getting Started
8
9
 
9
- Upload one file, understand the entire project. **progga** creates a complete project snapshot that AI assistants can instantly comprehend, making it easy to:
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
12
+ ```bash
13
+ npx progga@latest
14
+ ```
15
+
16
+ This generates a file named:
15
17
 
16
- ## ✨ Features
18
+ ```
19
+ PROJECT_DOCUMENTATION.md
20
+ ```
17
21
 
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
22
+ in the current directory.
23
23
 
24
- ## 🚀 Installation
24
+ ### Run on a specific project
25
25
 
26
- ### Using npx (Recommended - No Installation!)
27
26
  ```bash
28
- npx progga
27
+ progga /path/to/project
29
28
  ```
30
29
 
31
- ### Global Installation
30
+ ### Custom output file
31
+
32
32
  ```bash
33
- npm install -g progga
33
+ progga . my-ai-context.md
34
34
  ```
35
35
 
36
- ## 📖 Usage
36
+ ---
37
37
 
38
- ### Basic Usage
38
+ ## How Progga Works (Short Example)
39
39
 
40
- Generate documentation for current directory:
41
- ```bash
42
- npx progga
40
+ Given a project like:
41
+
42
+ ```
43
+ my-app/
44
+ ├── src/
45
+ │ └── index.js
46
+ ├── package.json
47
+ ├── node_modules/
48
+ └── build/
43
49
  ```
44
50
 
45
- This creates `PROJECT_DOCUMENTATION.md` in your current directory.
51
+ Progga generates a single Markdown file containing:
46
52
 
47
- ### Specify Project Path
48
- ```bash
49
- npx progga /path/to/your/project
50
- ```
53
+ * A clean folder tree (excluding `node_modules`, `build`, etc.)
54
+ * The contents of relevant source files
55
+ * Proper code blocks with language hints
51
56
 
52
- ### Custom Output File
53
- ```bash
54
- npx progga . my-ai-context.md
55
- ```
57
+ Example output structure:
56
58
 
57
- ### Full Example
58
- ```bash
59
- npx progga ./my-app ./docs/ai-context.md
60
- ```
59
+ ````markdown
60
+ # Project Documentation: my-app
61
61
 
62
- ## 💡 How to Use with AI Assistants
62
+ ## Folder Structure
63
+ my-app/
64
+ ├── src/
65
+ │ └── index.js
66
+ ├── package.json
63
67
 
64
- 1. Run `npx progga` in your project directory
65
- 2. Upload the generated `PROJECT_DOCUMENTATION.md` to ChatGPT, Claude, or Gemini
66
- 3. Ask the AI anything about your project!
68
+ ## File Contents
69
+ ### src/index.js
70
+ ```js
71
+ // file content here
72
+ ````
67
73
 
68
- Example prompts after upload:
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"
74
+ You can upload this file directly to an AI and ask questions about the project.
74
75
 
75
- ## 🚫 What Gets Ignored
76
+ ## Project Presets
76
77
 
77
- Automatically excludes:
78
- - `node_modules/`, `.git/`
79
- - Build directories (`dist/`, `build/`, `.next/`)
80
- - Virtual environments (`venv/`, `env/`)
81
- - Cache directories
82
- - Lock files
83
- - Binary files (images, videos, fonts)
78
+ Progga supports project-type presets that control what files are included.
84
79
 
85
- ## 📊 Output Format
86
- ```markdown
87
- # Project Documentation: your-project
80
+ Currently supported:
81
+ - `generic` (default)
82
+ - `flutter` (Android, iOS, Web, Windows, macOS, Linux)
88
83
 
89
- ## 📁 Folder Structure
90
- [Visual tree of all files and folders]
84
+ If no preset is provided, Progga attempts to detect the project type and asks which preset to use.
91
85
 
92
- ## 📄 File Contents
93
- [Complete contents of each file with syntax highlighting]
94
- ```
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
- ## 🌍 Requirements
111
+ Opening an issue to discuss ideas is also encouraged.
97
112
 
98
- - Node.js >= 12.0.0
113
+ ---
99
114
 
100
- ## 📝 License
115
+ ## Requirements
101
116
 
102
- MIT
117
+ * Node.js 12 or newer (Node 18+ recommended)
103
118
 
104
- ## 🤝 Contributing
119
+ ---
105
120
 
106
- Contributions are welcome! Please feel free to submit a Pull Request.
121
+ ## License
107
122
 
108
- ## 💖 Name Origin
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;
@@ -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
@@ -7,65 +7,29 @@
7
7
 
8
8
  const fs = require('fs');
9
9
  const path = require('path');
10
-
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
- ]);
10
+ const ProfileRegistry = require('./profiles/ProfileRegistry');
11
+ const ProjectTypeDetector = require('./core/ProjectTypeDetector');
12
+ const PresetSelector = require('./core/PresetSelector');
13
+ const Spinner = require('./core/Spinner');
48
14
 
49
15
  /**
50
16
  * Check if path should be ignored
51
17
  */
52
- function shouldIgnore(filePath, basePath) {
18
+ function shouldIgnore(filePath, basePath, profile) {
53
19
  const relativePath = path.relative(basePath, filePath);
54
20
  const parts = relativePath.split(path.sep);
55
-
56
- // Check each part of the path
21
+
57
22
  for (const part of parts) {
58
- if (IGNORE_PATTERNS.has(part)) {
23
+ if (profile.ignorePaths().includes(part)) {
59
24
  return true;
60
25
  }
61
26
  }
62
-
63
- // Check file extension
27
+
64
28
  const ext = path.extname(filePath);
65
- if (IGNORE_EXTENSIONS.has(ext)) {
29
+ if (profile.ignoreExtensions().includes(ext)) {
66
30
  return true;
67
31
  }
68
-
32
+
69
33
  return false;
70
34
  }
71
35
 
@@ -88,43 +52,43 @@ function isDirectoryEmpty(directory, basePath) {
88
52
  /**
89
53
  * Generate tree structure recursively
90
54
  */
91
- function generateTree(directory, prefix = '', isLast = true, basePath = null) {
55
+ function generateTree(directory, prefix = '', isLast = true, basePath = null, profile) {
92
56
  if (basePath === null) {
93
57
  basePath = directory;
94
58
  }
95
-
59
+
96
60
  const treeLines = [];
97
-
61
+
98
62
  try {
99
63
  let items = fs.readdirSync(directory).map(name => {
100
64
  const fullPath = path.join(directory, name);
101
65
  const stats = fs.statSync(fullPath);
102
66
  return { name, fullPath, isDir: stats.isDirectory() };
103
67
  });
104
-
68
+
105
69
  // Filter ignored items
106
- items = items.filter(item => !shouldIgnore(item.fullPath, basePath));
107
-
70
+ items = items.filter(item => !shouldIgnore(item.fullPath, basePath, profile));
71
+
108
72
  // Sort: directories first, then alphabetically
109
73
  items.sort((a, b) => {
110
74
  if (a.isDir !== b.isDir) return a.isDir ? -1 : 1;
111
75
  return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
112
76
  });
113
-
77
+
114
78
  for (let i = 0; i < items.length; i++) {
115
79
  const item = items[i];
116
80
  const isLastItem = i === items.length - 1;
117
-
81
+
118
82
  // Tree characters
119
83
  const connector = isLastItem ? '└── ' : '├── ';
120
84
  const extension = isLastItem ? ' ' : '│ ';
121
-
85
+
122
86
  if (item.isDir) {
123
87
  if (isDirectoryEmpty(item.fullPath, basePath)) {
124
88
  treeLines.push(`${prefix}${connector}${item.name}/ (empty)`);
125
89
  } else {
126
90
  treeLines.push(`${prefix}${connector}${item.name}/`);
127
- const subtree = generateTree(item.fullPath, prefix + extension, isLastItem, basePath);
91
+ const subtree = generateTree(item.fullPath, prefix + extension, isLastItem, basePath, profile);
128
92
  treeLines.push(...subtree);
129
93
  }
130
94
  } else {
@@ -140,35 +104,32 @@ function generateTree(directory, prefix = '', isLast = true, basePath = null) {
140
104
  } catch (err) {
141
105
  // Permission error or other issues
142
106
  }
143
-
107
+
144
108
  return treeLines;
145
109
  }
146
110
 
147
111
  /**
148
112
  * Check if file is binary
149
113
  */
150
- function isBinaryFile(filePath) {
114
+ function isBinaryFile(filePath, profile) {
151
115
  const ext = path.extname(filePath);
152
- if (BINARY_EXTENSIONS.has(ext)) {
116
+ if (profile.binaryExtensions().includes(ext)) {
153
117
  return true;
154
118
  }
155
-
119
+
156
120
  try {
157
121
  const buffer = Buffer.alloc(1024);
158
122
  const fd = fs.openSync(filePath, 'r');
159
123
  const bytesRead = fs.readSync(fd, buffer, 0, 1024, 0);
160
124
  fs.closeSync(fd);
161
-
162
- // Check for null bytes
125
+
163
126
  for (let i = 0; i < bytesRead; i++) {
164
- if (buffer[i] === 0) {
165
- return true;
166
- }
127
+ if (buffer[i] === 0) return true;
167
128
  }
168
- } catch (err) {
129
+ } catch {
169
130
  return true;
170
131
  }
171
-
132
+
172
133
  return false;
173
134
  }
174
135
 
@@ -181,7 +142,7 @@ function readFileContent(filePath) {
181
142
  if (stats.size === 0) {
182
143
  return '(empty file)';
183
144
  }
184
-
145
+
185
146
  return fs.readFileSync(filePath, 'utf-8');
186
147
  } catch (err) {
187
148
  if (err.message.includes('invalid')) {
@@ -223,7 +184,7 @@ function getLanguageFromExtension(filePath) {
223
184
  '.rb': 'ruby',
224
185
  '.php': 'php',
225
186
  };
226
-
187
+
227
188
  const ext = path.extname(filePath);
228
189
  return extMap[ext] || '';
229
190
  }
@@ -231,50 +192,46 @@ function getLanguageFromExtension(filePath) {
231
192
  /**
232
193
  * Collect all files recursively
233
194
  */
234
- function collectFiles(directory, basePath) {
195
+ function collectFiles(directory, basePath, profile) {
235
196
  const files = [];
236
-
197
+
237
198
  try {
238
199
  const items = fs.readdirSync(directory);
239
-
200
+
240
201
  for (const item of items.sort()) {
241
202
  const fullPath = path.join(directory, item);
242
-
243
- if (shouldIgnore(fullPath, basePath)) {
244
- continue;
245
- }
246
-
203
+
204
+ if (shouldIgnore(fullPath, basePath, profile)) continue;
205
+
247
206
  const stats = fs.statSync(fullPath);
248
-
207
+
249
208
  if (stats.isFile()) {
250
- if (!isBinaryFile(fullPath)) {
209
+ if (!isBinaryFile(fullPath, profile)) {
251
210
  files.push(fullPath);
252
211
  }
253
212
  } else if (stats.isDirectory()) {
254
- files.push(...collectFiles(fullPath, basePath));
213
+ files.push(...collectFiles(fullPath, basePath, profile));
255
214
  }
256
215
  }
257
- } catch (err) {
258
- // Permission error or other issues
259
- }
260
-
216
+ } catch { }
217
+
261
218
  return files;
262
219
  }
263
220
 
264
221
  /**
265
222
  * Generate complete documentation markdown file
266
223
  */
267
- function generateDocumentation(projectPath, outputFile) {
224
+ function generateDocumentation(projectPath, outputFile, profile) {
268
225
  const absProjectPath = path.resolve(projectPath);
269
-
226
+
270
227
  if (!fs.existsSync(absProjectPath)) {
271
228
  console.error(`Error: Path '${absProjectPath}' does not exist`);
272
229
  process.exit(1);
273
230
  }
274
-
231
+
275
232
  console.log(`Generating documentation for: ${absProjectPath}`);
276
233
  console.log(`Output file: ${outputFile}`);
277
-
234
+
278
235
  // Delete existing output file if it exists
279
236
  if (fs.existsSync(outputFile)) {
280
237
  try {
@@ -284,43 +241,48 @@ function generateDocumentation(projectPath, outputFile) {
284
241
  console.warn(`Warning: Could not delete existing file: ${err.message}`);
285
242
  }
286
243
  }
287
-
244
+
288
245
  const projectName = path.basename(absProjectPath);
289
246
  let output = '';
290
-
247
+
291
248
  // Write header
292
249
  output += `# Project Documentation: ${projectName}\n\n`;
293
250
  output += `**Generated from:** \`${absProjectPath}\`\n\n`;
294
251
  output += '---\n\n';
295
-
252
+
296
253
  // Write folder structure
297
254
  output += '## 📁 Folder Structure\n\n';
298
255
  output += '```\n';
299
256
  output += `${projectName}/\n`;
300
-
301
- const treeLines = generateTree(absProjectPath, '', true, absProjectPath);
257
+
258
+ const treeSpinner = new Spinner('Generating folder structure');
259
+ const treeLines = generateTree(absProjectPath, '', true, absProjectPath, profile);
260
+ treeSpinner.succeed('Folder structure generated');
261
+
302
262
  for (const line of treeLines) {
303
263
  output += `${line}\n`;
304
264
  }
305
-
265
+
306
266
  output += '```\n\n';
307
267
  output += '---\n\n';
308
-
268
+
309
269
  // Write file contents
310
270
  output += '## 📄 File Contents\n\n';
311
-
312
- const files = collectFiles(absProjectPath, absProjectPath);
313
-
271
+
272
+ const fileSpinner = new Spinner('Collecting files');
273
+ const files = collectFiles(absProjectPath, absProjectPath, profile);
274
+ fileSpinner.succeed(`Collected ${files.length} files`);
275
+
314
276
  for (let i = 0; i < files.length; i++) {
315
277
  const filePath = files[i];
316
278
  const relPath = path.relative(absProjectPath, filePath);
317
279
  console.log(`Processing (${i + 1}/${files.length}): ${relPath}`);
318
-
280
+
319
281
  output += `### \`${relPath}\`\n\n`;
320
-
282
+
321
283
  const content = readFileContent(filePath);
322
284
  const language = getLanguageFromExtension(filePath);
323
-
285
+
324
286
  output += `\`\`\`${language}\n`;
325
287
  output += content;
326
288
  if (!content.endsWith('\n')) {
@@ -329,22 +291,59 @@ function generateDocumentation(projectPath, outputFile) {
329
291
  output += '```\n\n';
330
292
  output += '---\n\n';
331
293
  }
332
-
294
+
333
295
  // Write to file
334
296
  fs.writeFileSync(outputFile, output, 'utf-8');
335
-
297
+
336
298
  console.log(`\n✅ Documentation generated successfully: ${outputFile}`);
337
299
  console.log(`📊 Total files processed: ${files.length}`);
338
300
  }
339
301
 
340
302
  // Main execution
341
- function main() {
303
+ async function main() {
342
304
  const args = process.argv.slice(2);
343
-
344
- const projectPath = args[0] || '.';
345
- const outputFile = args[1] || 'PROJECT_DOCUMENTATION.md';
346
-
347
- generateDocumentation(projectPath, outputFile);
305
+
306
+ let projectPath = '.';
307
+ let outputFile = 'PROJECT_DOCUMENTATION.md';
308
+ let projectType = null;
309
+
310
+ for (let i = 0; i < args.length; i++) {
311
+ const arg = args[i];
312
+
313
+ if (arg === '--project-type' || arg === '--preset') {
314
+ projectType = args[i + 1];
315
+ i++;
316
+ } else if (!projectPath) {
317
+ projectPath = arg;
318
+ } else if (!outputFile) {
319
+ outputFile = arg;
320
+ }
321
+ }
322
+
323
+ let profile = ProfileRegistry.getByName(projectType, projectPath);
324
+
325
+ if (!profile) {
326
+ const detector = new ProjectTypeDetector(projectPath);
327
+ const detections = detector.detect();
328
+
329
+ const flutterSignal = detections.find(d => d.type === 'flutter');
330
+
331
+ if (flutterSignal && process.stdin.isTTY) {
332
+ console.log('');
333
+ console.log(`Detected project type: ${flutterSignal.type}`);
334
+ console.log('');
335
+
336
+ const selectedPreset = await PresetSelector.choose(flutterSignal.type);
337
+ profile = ProfileRegistry.getByName(selectedPreset, projectPath);
338
+ }
339
+ }
340
+
341
+ if (!profile) {
342
+ profile = ProfileRegistry.fallback(projectPath);
343
+ }
344
+
345
+ console.log(`Using profile: ${profile.name}`);
346
+ generateDocumentation(projectPath, outputFile, profile);
348
347
  }
349
348
 
350
349
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progga",
3
- "version": "1.0.2",
3
+ "version": "1.0.5",
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;
@@ -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,24 @@
1
+ const GenericProfile = require('./GenericProfile');
2
+ const FlutterProfile = require('./FlutterProfile');
3
+
4
+ class ProfileRegistry {
5
+ static getByName(name, projectRoot) {
6
+ if (!name) return null;
7
+
8
+ if (name === 'flutter') {
9
+ return new FlutterProfile(projectRoot);
10
+ }
11
+
12
+ if (name === 'generic') {
13
+ return new GenericProfile(projectRoot);
14
+ }
15
+
16
+ return null;
17
+ }
18
+
19
+ static fallback(projectRoot) {
20
+ return new GenericProfile(projectRoot);
21
+ }
22
+ }
23
+
24
+ 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;