combicode 1.5.2 → 1.5.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ # Changelog
4
+
5
+ ## [1.5.4](https://github.com/aaurelions/combicode/compare/combicode-js-v1.5.3...combicode-js-v1.5.4) (2025-12-03)
6
+
7
+ ### Features
8
+
9
+ - **ignore:** implement full support for nested `.gitignore` files, ensuring exclusion rules are applied correctly within subdirectories
10
+
11
+ ### Bug Fixes
12
+
13
+ - **deps:** replace `fast-glob` with `ignore` and recursive directory walking for accurate git-like pattern matching
14
+
15
+ ## [1.5.3](https://github.com/aaurelions/combicode/compare/combicode-js-v1.5.2...combicode-js-v1.5.3) (2025-11-30)
16
+
17
+ ### Bug Fixes
18
+
19
+ - **cli:** prevent the output file (e.g., `combicode.txt`) from recursively including itself in the generated content
20
+
3
21
  ## [1.5.2](https://github.com/aaurelions/combicode/compare/combicode-js-v1.4.0...combicode-js-v1.5.2) (2025-11-30)
4
22
 
5
23
  ### Features
package/README.md CHANGED
@@ -16,6 +16,7 @@ The generated file starts with a system prompt and a file tree overview, priming
16
16
  - **Intelligent Priming:** Starts the output with a system prompt and a file tree, directing the LLM to analyze the entire codebase before responding.
17
17
  - **Intelligent Ignoring:** Automatically skips `node_modules`, `.venv`, `dist`, `.git`, binary files, and other common junk.
18
18
  - **`.gitignore` Aware:** Respects your project's existing `.gitignore` rules out of the box.
19
+ - **Nested Ignore Support:** Correctly handles `.gitignore` files located in subdirectories, ensuring local exclusion rules are respected.
19
20
  - **Zero-Install Usage:** Run it directly with `npx` or `pipx` without polluting your environment.
20
21
  - **Customizable:** Easily filter by file extension or add custom ignore patterns.
21
22
 
package/index.js CHANGED
@@ -4,7 +4,7 @@ const fs = require("fs");
4
4
  const path = require("path");
5
5
  const yargs = require("yargs/yargs");
6
6
  const { hideBin } = require("yargs/helpers");
7
- const glob = require("fast-glob");
7
+ const ignore = require("ignore");
8
8
 
9
9
  const { version } = require("./package.json");
10
10
 
@@ -24,26 +24,14 @@ When answering questions or writing code, adhere strictly to the functions, vari
24
24
  A file tree of the documentation source is provided below for a high-level overview. The subsequent sections contain the full content of each file, clearly marked with a file header.
25
25
  `;
26
26
 
27
- function loadDefaultIgnorePatterns() {
28
- const configPath = path.resolve(__dirname, "config", "ignore.json");
29
- try {
30
- const rawConfig = fs.readFileSync(configPath, "utf8");
31
- return JSON.parse(rawConfig);
32
- } catch (err) {
33
- console.error(
34
- `❌ Critical: Could not read or parse bundled ignore config at ${configPath}`
35
- );
36
- process.exit(1);
37
- }
38
- }
39
-
40
- const DEFAULT_IGNORE_PATTERNS = loadDefaultIgnorePatterns();
27
+ // Minimal safety ignores that should always apply
28
+ const SAFETY_IGNORES = [".git", ".DS_Store"];
41
29
 
42
- function isLikelyBinary(file) {
30
+ function isLikelyBinary(filePath) {
43
31
  const buffer = Buffer.alloc(512);
44
32
  let fd;
45
33
  try {
46
- fd = fs.openSync(file, "r");
34
+ fd = fs.openSync(filePath, "r");
47
35
  const bytesRead = fs.readSync(fd, buffer, 0, 512, 0);
48
36
  return buffer.slice(0, bytesRead).includes(0);
49
37
  } catch (e) {
@@ -62,11 +50,129 @@ function formatBytes(bytes, decimals = 1) {
62
50
  return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + "" + sizes[i];
63
51
  }
64
52
 
53
+ /**
54
+ * Recursively walks directories, respecting .gitignore files at each level.
55
+ */
56
+ function walkDirectory(
57
+ currentDir,
58
+ rootDir,
59
+ ignoreChain,
60
+ allowedExts,
61
+ absoluteOutputPath,
62
+ useGitIgnore,
63
+ stats // { scanned: 0, ignored: 0 }
64
+ ) {
65
+ let results = [];
66
+ let currentIgnoreManager = null;
67
+
68
+ // 1. Check for local .gitignore and add to chain for this scope
69
+ if (useGitIgnore) {
70
+ const gitignorePath = path.join(currentDir, ".gitignore");
71
+ if (fs.existsSync(gitignorePath)) {
72
+ try {
73
+ const content = fs.readFileSync(gitignorePath, "utf8");
74
+ const ig = ignore().add(content);
75
+ currentIgnoreManager = { manager: ig, root: currentDir };
76
+ } catch (e) {
77
+ // Warning could go here
78
+ }
79
+ }
80
+ }
81
+
82
+ // Create a new chain for this directory and its children
83
+ const nextIgnoreChain = currentIgnoreManager
84
+ ? [...ignoreChain, currentIgnoreManager]
85
+ : ignoreChain;
86
+
87
+ let entries;
88
+ try {
89
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
90
+ } catch (e) {
91
+ return [];
92
+ }
93
+
94
+ for (const entry of entries) {
95
+ const fullPath = path.join(currentDir, entry.name);
96
+
97
+ // SKIP CHECK: Output file
98
+ if (path.resolve(fullPath) === absoluteOutputPath) continue;
99
+
100
+ // SKIP CHECK: Ignore Chain
101
+ let shouldIgnore = false;
102
+ for (const item of nextIgnoreChain) {
103
+ // Calculate path relative to the specific ignore manager's root
104
+ // IMPORTANT: Normalize to POSIX slashes for 'ignore' package compatibility
105
+ let relToIgnoreRoot = path.relative(item.root, fullPath);
106
+
107
+ if (path.sep === "\\") {
108
+ relToIgnoreRoot = relToIgnoreRoot.replace(/\\/g, "/");
109
+ }
110
+
111
+ // If checking a directory, ensure trailing slash for proper 'ignore' directory matching
112
+ if (entry.isDirectory() && !relToIgnoreRoot.endsWith("/")) {
113
+ relToIgnoreRoot += "/";
114
+ }
115
+
116
+ if (item.manager.ignores(relToIgnoreRoot)) {
117
+ shouldIgnore = true;
118
+ break;
119
+ }
120
+ }
121
+
122
+ if (shouldIgnore) {
123
+ stats.ignored++;
124
+ continue;
125
+ }
126
+
127
+ if (entry.isDirectory()) {
128
+ // Recurse
129
+ results = results.concat(
130
+ walkDirectory(
131
+ fullPath,
132
+ rootDir,
133
+ nextIgnoreChain,
134
+ allowedExts,
135
+ absoluteOutputPath,
136
+ useGitIgnore,
137
+ stats
138
+ )
139
+ );
140
+ } else if (entry.isFile()) {
141
+ // SKIP CHECK: Binary
142
+ if (isLikelyBinary(fullPath)) {
143
+ stats.ignored++;
144
+ continue;
145
+ }
146
+
147
+ // SKIP CHECK: Extensions
148
+ if (allowedExts && !allowedExts.has(path.extname(entry.name))) {
149
+ stats.ignored++;
150
+ continue;
151
+ }
152
+
153
+ try {
154
+ const fileStats = fs.statSync(fullPath);
155
+ const relativeToRoot = path.relative(rootDir, fullPath);
156
+ stats.scanned++;
157
+ results.push({
158
+ path: fullPath,
159
+ relativePath: relativeToRoot,
160
+ size: fileStats.size,
161
+ formattedSize: formatBytes(fileStats.size),
162
+ });
163
+ } catch (e) {
164
+ // Skip inaccessible files
165
+ }
166
+ }
167
+ }
168
+
169
+ return results;
170
+ }
171
+
65
172
  function generateFileTree(filesWithSize, root) {
66
173
  let tree = `${path.basename(root)}/\n`;
67
174
  const structure = {};
68
175
 
69
- // Build the structure
70
176
  filesWithSize.forEach(({ relativePath, formattedSize }) => {
71
177
  const parts = relativePath.split(path.sep);
72
178
  let currentLevel = structure;
@@ -89,7 +195,6 @@ function generateFileTree(filesWithSize, root) {
89
195
  const isLast = index === entries.length - 1;
90
196
  const value = level[entry];
91
197
  const isFile = typeof value === "string";
92
-
93
198
  const connector = isLast ? "└── " : "├── ";
94
199
 
95
200
  if (isFile) {
@@ -112,7 +217,6 @@ async function main() {
112
217
  process.exit(0);
113
218
  }
114
219
 
115
- // Yargs singleton usage works correctly with arguments passed here
116
220
  const argv = yargs(rawArgv)
117
221
  .scriptName("combicode")
118
222
  .usage("$0 [options]")
@@ -160,34 +264,20 @@ async function main() {
160
264
  .alias("h", "help").argv;
161
265
 
162
266
  const projectRoot = process.cwd();
163
- console.log(`✨ Running Combicode in: ${projectRoot}`);
267
+ console.log(`\n✨ Combicode v${version}`);
268
+ console.log(`📂 Root: ${projectRoot}`);
164
269
 
165
- const ignorePatterns = [...DEFAULT_IGNORE_PATTERNS];
270
+ const rootIgnoreManager = ignore();
166
271
 
167
- if (!argv.noGitignore) {
168
- const gitignorePath = path.join(projectRoot, ".gitignore");
169
- if (fs.existsSync(gitignorePath)) {
170
- console.log("🔎 Found and using .gitignore");
171
- const gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
172
- ignorePatterns.push(
173
- ...gitignoreContent
174
- .split(/\r?\n/)
175
- .filter((line) => line && !line.startsWith("#"))
176
- );
177
- }
178
- }
272
+ // Only add minimal safety ignores + CLI excludes.
273
+ // No external JSON config is loaded.
274
+ rootIgnoreManager.add(SAFETY_IGNORES);
179
275
 
180
276
  if (argv.exclude) {
181
- ignorePatterns.push(...argv.exclude.split(","));
277
+ rootIgnoreManager.add(argv.exclude.split(","));
182
278
  }
183
279
 
184
- let allFiles = await glob("**/*", {
185
- cwd: projectRoot,
186
- dot: true,
187
- ignore: ignorePatterns,
188
- absolute: true,
189
- stats: true,
190
- });
280
+ const absoluteOutputPath = path.resolve(projectRoot, argv.output);
191
281
 
192
282
  const allowedExtensions = argv.includeExt
193
283
  ? new Set(
@@ -197,25 +287,35 @@ async function main() {
197
287
  )
198
288
  : null;
199
289
 
200
- const includedFiles = allFiles
201
- .filter((fileObj) => {
202
- const file = fileObj.path;
203
- if (!fileObj.stats || fileObj.stats.isDirectory()) return false;
204
- if (isLikelyBinary(file)) return false;
205
- if (allowedExtensions && !allowedExtensions.has(path.extname(file)))
206
- return false;
207
- return true;
208
- })
209
- .map((fileObj) => ({
210
- path: fileObj.path,
211
- relativePath: path.relative(projectRoot, fileObj.path),
212
- size: fileObj.stats.size,
213
- formattedSize: formatBytes(fileObj.stats.size),
214
- }))
215
- .sort((a, b) => a.path.localeCompare(b.path));
290
+ // Initialize the ignore chain with the root manager
291
+ const ignoreChain = [{ manager: rootIgnoreManager, root: projectRoot }];
292
+
293
+ // Statistics container
294
+ const stats = { scanned: 0, ignored: 0 };
295
+
296
+ // Perform Recursive Walk
297
+ const includedFiles = walkDirectory(
298
+ projectRoot,
299
+ projectRoot,
300
+ ignoreChain,
301
+ allowedExtensions,
302
+ absoluteOutputPath,
303
+ !argv.noGitignore,
304
+ stats
305
+ );
306
+
307
+ includedFiles.sort((a, b) => a.path.localeCompare(b.path));
308
+
309
+ // Calculate total size of included files
310
+ const totalSizeBytes = includedFiles.reduce(
311
+ (acc, file) => acc + file.size,
312
+ 0
313
+ );
216
314
 
217
315
  if (includedFiles.length === 0) {
218
- console.error("❌ No files to include. Check your path or filters.");
316
+ console.error(
317
+ "\n❌ No files to include. Check your path, .gitignore, or filters."
318
+ );
219
319
  process.exit(1);
220
320
  }
221
321
 
@@ -223,23 +323,34 @@ async function main() {
223
323
  console.log("\n📋 Files to be included (Dry Run):\n");
224
324
  const tree = generateFileTree(includedFiles, projectRoot);
225
325
  console.log(tree);
226
- console.log(`\nTotal: ${includedFiles.length} files.`);
326
+ console.log("\n📊 Summary (Dry Run):");
327
+ console.log(
328
+ ` • Included: ${includedFiles.length} files (${formatBytes(
329
+ totalSizeBytes
330
+ )})`
331
+ );
332
+ console.log(` • Ignored: ${stats.ignored} files/dirs`);
227
333
  return;
228
334
  }
229
335
 
230
336
  const outputStream = fs.createWriteStream(argv.output);
337
+ let totalLines = 0;
231
338
 
232
339
  if (!argv.noHeader) {
233
340
  const systemPrompt = argv.llmsTxt
234
341
  ? LLMS_TXT_SYSTEM_PROMPT
235
342
  : DEFAULT_SYSTEM_PROMPT;
236
343
  outputStream.write(systemPrompt + "\n");
344
+ totalLines += systemPrompt.split("\n").length;
345
+
237
346
  outputStream.write("## Project File Tree\n\n");
238
347
  outputStream.write("```\n");
239
348
  const tree = generateFileTree(includedFiles, projectRoot);
240
349
  outputStream.write(tree);
241
350
  outputStream.write("```\n\n");
242
351
  outputStream.write("---\n\n");
352
+
353
+ totalLines += tree.split("\n").length + 5;
243
354
  }
244
355
 
245
356
  for (const fileObj of includedFiles) {
@@ -249,16 +360,24 @@ async function main() {
249
360
  try {
250
361
  const content = fs.readFileSync(fileObj.path, "utf8");
251
362
  outputStream.write(content);
363
+ totalLines += content.split("\n").length;
252
364
  } catch (e) {
253
365
  outputStream.write(`... (error reading file: ${e.message}) ...`);
254
366
  }
255
367
  outputStream.write("\n```\n\n");
368
+ totalLines += 4; // Headers/footers lines
256
369
  }
257
370
  outputStream.end();
258
371
 
372
+ console.log(`\n📊 Summary:`);
259
373
  console.log(
260
- `\n✅ Success! Combined ${includedFiles.length} files into '${argv.output}'.`
374
+ ` • Included: ${includedFiles.length} files (${formatBytes(
375
+ totalSizeBytes
376
+ )})`
261
377
  );
378
+ console.log(` • Ignored: ${stats.ignored} files/dirs`);
379
+ console.log(` • Output: ${argv.output} (~${totalLines} lines)`);
380
+ console.log(`\n✅ Done!`);
262
381
  }
263
382
 
264
383
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "combicode",
3
- "version": "1.5.2",
3
+ "version": "1.5.4",
4
4
  "description": "A CLI tool to combine a project's codebase into a single file for LLM context.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -10,8 +10,6 @@
10
10
  "access": "public"
11
11
  },
12
12
  "scripts": {
13
- "prepack": "mkdir -p config && cp ../configs/ignore.json config/ignore.json",
14
- "pretest": "mkdir -p config && cp ../configs/ignore.json config/ignore.json",
15
13
  "test": "node test/test.js"
16
14
  },
17
15
  "repository": {
@@ -34,7 +32,7 @@
34
32
  "author": "A. Aurelions",
35
33
  "license": "MIT",
36
34
  "dependencies": {
37
- "fast-glob": "^3.3.3",
35
+ "ignore": "^5.3.0",
38
36
  "yargs": "^17.7.2"
39
37
  }
40
38
  }
package/test/test.js CHANGED
@@ -7,20 +7,17 @@ const CLI_PATH = path.resolve(__dirname, "../index.js");
7
7
  const TEST_DIR = path.resolve(__dirname, "temp_env");
8
8
  const OUTPUT_FILE = path.join(TEST_DIR, "combicode.txt");
9
9
 
10
- // Setup: Create a temp directory with dummy files
11
- function setup() {
12
- if (fs.existsSync(TEST_DIR)) {
13
- fs.rmSync(TEST_DIR, { recursive: true, force: true });
14
- }
15
- fs.mkdirSync(TEST_DIR);
16
-
17
- // Create a dummy JS file
18
- fs.writeFileSync(path.join(TEST_DIR, "alpha.js"), "console.log('alpha');");
19
-
20
- // Create a dummy text file in a subdir
21
- const subDir = path.join(TEST_DIR, "subdir");
22
- fs.mkdirSync(subDir);
23
- fs.writeFileSync(path.join(subDir, "beta.txt"), "Hello World");
10
+ // Helper to create directory structure
11
+ function createStructure(base, structure) {
12
+ Object.entries(structure).forEach(([name, content]) => {
13
+ const fullPath = path.join(base, name);
14
+ if (typeof content === "object") {
15
+ fs.mkdirSync(fullPath);
16
+ createStructure(fullPath, content);
17
+ } else {
18
+ fs.writeFileSync(fullPath, content);
19
+ }
20
+ });
24
21
  }
25
22
 
26
23
  // Teardown: Cleanup temp directory
@@ -34,53 +31,119 @@ function runTest() {
34
31
  console.log("🧪 Starting Node.js Integration Tests...");
35
32
 
36
33
  try {
37
- setup();
34
+ // Clean start
35
+ teardown();
36
+ fs.mkdirSync(TEST_DIR);
38
37
 
39
- // 1. Test Version Flag
40
- console.log(" Checking --version...");
38
+ // --- Scenario 1: Basic Functionality ---
39
+ console.log(" [1/4] Checking Basic Functionality & Version...");
41
40
  const versionOutput = execSync(`node ${CLI_PATH} --version`).toString();
42
41
  assert.match(versionOutput, /Combicode \(JavaScript\), version/);
43
42
 
44
- // 2. Test Dry Run
45
- console.log(" Checking --dry-run...");
43
+ createStructure(TEST_DIR, {
44
+ "alpha.js": "console.log('alpha');",
45
+ subdir: {
46
+ "beta.txt": "Hello World",
47
+ },
48
+ });
49
+
50
+ // Capture dry-run output to verify structure
46
51
  const dryRunOutput = execSync(`node ${CLI_PATH} --dry-run`, {
47
52
  cwd: TEST_DIR,
48
53
  }).toString();
49
54
  assert.match(dryRunOutput, /Files to be included \(Dry Run\)/);
50
- // Check for file size format in tree (e.g., [21B])
51
- assert.match(dryRunOutput, /\[\d+(\.\d+)?[KM]?B\]/);
55
+ assert.match(dryRunOutput, /\[\d+(\.\d+)?[KM]?B\]/); // Size check
52
56
 
53
- // 3. Test Actual Generation
54
- console.log(" Checking file generation...");
55
- execSync(`node ${CLI_PATH} --output combicode.txt`, { cwd: TEST_DIR });
57
+ // Run generation
58
+ execSync(`node ${CLI_PATH} --output combicode.txt`, {
59
+ cwd: TEST_DIR,
60
+ stdio: "inherit",
61
+ });
56
62
 
57
63
  assert.ok(fs.existsSync(OUTPUT_FILE), "Output file should exist");
64
+ let content = fs.readFileSync(OUTPUT_FILE, "utf8");
65
+ assert.ok(content.includes("### **FILE:** `alpha.js`"));
66
+ assert.ok(content.includes("### **FILE:** `subdir/beta.txt`"));
58
67
 
59
- const content = fs.readFileSync(OUTPUT_FILE, "utf8");
68
+ // --- Scenario 2: Nested .gitignore Support ---
69
+ console.log(" [2/4] Checking Nested .gitignore Support...");
70
+ teardown();
71
+ fs.mkdirSync(TEST_DIR);
72
+
73
+ createStructure(TEST_DIR, {
74
+ "root.js": "root",
75
+ "ignore_me_root.log": "log",
76
+ ".gitignore": "*.log",
77
+ nested: {
78
+ "child.js": "child",
79
+ "ignore_me_child.tmp": "tmp",
80
+ ".gitignore": "*.tmp",
81
+ deep: {
82
+ "deep.js": "deep",
83
+ "ignore_local.txt": "txt",
84
+ ".gitignore": "ignore_local.txt",
85
+ },
86
+ },
87
+ });
88
+
89
+ execSync(`node ${CLI_PATH} -o combicode.txt`, {
90
+ cwd: TEST_DIR,
91
+ stdio: "inherit",
92
+ });
93
+ content = fs.readFileSync(OUTPUT_FILE, "utf8");
60
94
 
61
- // Check for System Prompt
95
+ // Should include:
96
+ assert.ok(content.includes("### **FILE:** `root.js`"), "root.js missing");
62
97
  assert.ok(
63
- content.includes("You are an expert software architect"),
64
- "System prompt missing"
98
+ content.includes("### **FILE:** `nested/child.js`"),
99
+ "child.js missing"
100
+ );
101
+ assert.ok(
102
+ content.includes("### **FILE:** `nested/deep/deep.js`"),
103
+ "deep.js missing"
65
104
  );
66
105
 
67
- // Check for Tree structure
68
- assert.ok(content.includes("subdir"), "Tree should show subdirectory");
106
+ // Should exclude (Checking Headers, not content):
107
+ assert.ok(
108
+ !content.includes("### **FILE:** `ignore_me_root.log`"),
109
+ "Root gitignore failed (*.log)"
110
+ );
111
+ assert.ok(
112
+ !content.includes("### **FILE:** `nested/ignore_me_child.tmp`"),
113
+ "Nested gitignore failed (*.tmp)"
114
+ );
115
+ assert.ok(
116
+ !content.includes("### **FILE:** `nested/deep/ignore_local.txt`"),
117
+ "Deep nested gitignore failed (specific file)"
118
+ );
69
119
 
70
- // Check for new Header format
120
+ // --- Scenario 3: CLI Exclude Override ---
121
+ console.log(" [3/4] Checking CLI Exclude Flags...");
122
+ execSync(`node ${CLI_PATH} -o combicode.txt -e "**/deep.js"`, {
123
+ cwd: TEST_DIR,
124
+ stdio: "inherit",
125
+ });
126
+ content = fs.readFileSync(OUTPUT_FILE, "utf8");
71
127
  assert.ok(
72
- content.includes("### **FILE:** `alpha.js`"),
73
- "New header format missing for alpha.js"
128
+ !content.includes("### **FILE:** `nested/deep/deep.js`"),
129
+ "CLI exclude flag failed"
74
130
  );
131
+
132
+ // --- Scenario 4: Output File Self-Exclusion ---
133
+ console.log(" [4/4] Checking Output File Self-Exclusion...");
134
+ execSync(`node ${CLI_PATH} -o combicode.txt`, {
135
+ cwd: TEST_DIR,
136
+ stdio: "inherit",
137
+ });
138
+ content = fs.readFileSync(OUTPUT_FILE, "utf8");
75
139
  assert.ok(
76
- content.includes("### **FILE:** `subdir/beta.txt`"),
77
- "New header format missing for beta.txt"
140
+ !content.includes("### **FILE:** `combicode.txt`"),
141
+ "Output file included itself"
78
142
  );
79
143
 
80
144
  console.log("✅ All Node.js tests passed!");
81
145
  } catch (error) {
82
146
  console.error("❌ Test Failed:", error.message);
83
- if (error.stdout) console.log(error.stdout.toString());
84
147
  process.exit(1);
85
148
  } finally {
86
149
  teardown();
@@ -1,53 +0,0 @@
1
- [
2
- "**/node_modules/**",
3
- "**/.git/**",
4
- "**/.vscode/**",
5
- "**/.idea/**",
6
- "**/*.log",
7
- "**/.env",
8
- "**/*.lock",
9
- "**/.venv/**",
10
- "**/venv/**",
11
- "**/env/**",
12
- "**/__pycache__/**",
13
- "**/*.pyc",
14
- "**/*.egg-info/**",
15
- "**/build/**",
16
- "**/dist/**",
17
- "**/.pytest_cache/**",
18
- "**/.npm/**",
19
- "**/pnpm-lock.yaml",
20
- "**/package-lock.json",
21
- "**/.next/**",
22
- "**/.DS_Store",
23
- "**/Thumbs.db",
24
- "**/*.png",
25
- "**/*.jpg",
26
- "**/*.jpeg",
27
- "**/*.gif",
28
- "**/*.ico",
29
- "**/*.svg",
30
- "**/*.webp",
31
- "**/*.mp3",
32
- "**/*.wav",
33
- "**/*.flac",
34
- "**/*.mp4",
35
- "**/*.mov",
36
- "**/*.avi",
37
- "**/*.zip",
38
- "**/*.tar.gz",
39
- "**/*.rar",
40
- "**/*.pdf",
41
- "**/*.doc",
42
- "**/*.docx",
43
- "**/*.xls",
44
- "**/*.xlsx",
45
- "**/*.dll",
46
- "**/*.exe",
47
- "**/*.so",
48
- "**/*.a",
49
- "**/*.lib",
50
- "**/*.o",
51
- "**/*.bin",
52
- "**/*.iso"
53
- ]