combicode 1.5.3 → 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,17 @@
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
+
3
15
  ## [1.5.3](https://github.com/aaurelions/combicode/compare/combicode-js-v1.5.2...combicode-js-v1.5.3) (2025-11-30)
4
16
 
5
17
  ### Bug Fixes
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,38 +264,21 @@ 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
- // Calculate the absolute path of the output file to prevent self-inclusion
185
280
  const absoluteOutputPath = path.resolve(projectRoot, argv.output);
186
281
 
187
- let allFiles = await glob("**/*", {
188
- cwd: projectRoot,
189
- dot: true,
190
- ignore: ignorePatterns,
191
- absolute: true,
192
- stats: true,
193
- });
194
-
195
282
  const allowedExtensions = argv.includeExt
196
283
  ? new Set(
197
284
  argv.includeExt
@@ -200,30 +287,35 @@ async function main() {
200
287
  )
201
288
  : null;
202
289
 
203
- const includedFiles = allFiles
204
- .filter((fileObj) => {
205
- const file = fileObj.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
+ );
206
306
 
207
- // Prevent the output file from being included in the list
208
- // We use path.normalize to handle potential differences in separators (e.g., / vs \)
209
- if (path.normalize(file) === absoluteOutputPath) return false;
307
+ includedFiles.sort((a, b) => a.path.localeCompare(b.path));
210
308
 
211
- if (!fileObj.stats || fileObj.stats.isDirectory()) return false;
212
- if (isLikelyBinary(file)) return false;
213
- if (allowedExtensions && !allowedExtensions.has(path.extname(file)))
214
- return false;
215
- return true;
216
- })
217
- .map((fileObj) => ({
218
- path: fileObj.path,
219
- relativePath: path.relative(projectRoot, fileObj.path),
220
- size: fileObj.stats.size,
221
- formattedSize: formatBytes(fileObj.stats.size),
222
- }))
223
- .sort((a, b) => a.path.localeCompare(b.path));
309
+ // Calculate total size of included files
310
+ const totalSizeBytes = includedFiles.reduce(
311
+ (acc, file) => acc + file.size,
312
+ 0
313
+ );
224
314
 
225
315
  if (includedFiles.length === 0) {
226
- 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
+ );
227
319
  process.exit(1);
228
320
  }
229
321
 
@@ -231,23 +323,34 @@ async function main() {
231
323
  console.log("\n📋 Files to be included (Dry Run):\n");
232
324
  const tree = generateFileTree(includedFiles, projectRoot);
233
325
  console.log(tree);
234
- 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`);
235
333
  return;
236
334
  }
237
335
 
238
336
  const outputStream = fs.createWriteStream(argv.output);
337
+ let totalLines = 0;
239
338
 
240
339
  if (!argv.noHeader) {
241
340
  const systemPrompt = argv.llmsTxt
242
341
  ? LLMS_TXT_SYSTEM_PROMPT
243
342
  : DEFAULT_SYSTEM_PROMPT;
244
343
  outputStream.write(systemPrompt + "\n");
344
+ totalLines += systemPrompt.split("\n").length;
345
+
245
346
  outputStream.write("## Project File Tree\n\n");
246
347
  outputStream.write("```\n");
247
348
  const tree = generateFileTree(includedFiles, projectRoot);
248
349
  outputStream.write(tree);
249
350
  outputStream.write("```\n\n");
250
351
  outputStream.write("---\n\n");
352
+
353
+ totalLines += tree.split("\n").length + 5;
251
354
  }
252
355
 
253
356
  for (const fileObj of includedFiles) {
@@ -257,16 +360,24 @@ async function main() {
257
360
  try {
258
361
  const content = fs.readFileSync(fileObj.path, "utf8");
259
362
  outputStream.write(content);
363
+ totalLines += content.split("\n").length;
260
364
  } catch (e) {
261
365
  outputStream.write(`... (error reading file: ${e.message}) ...`);
262
366
  }
263
367
  outputStream.write("\n```\n\n");
368
+ totalLines += 4; // Headers/footers lines
264
369
  }
265
370
  outputStream.end();
266
371
 
372
+ console.log(`\n📊 Summary:`);
267
373
  console.log(
268
- `\n✅ Success! Combined ${includedFiles.length} files into '${argv.output}'.`
374
+ ` • Included: ${includedFiles.length} files (${formatBytes(
375
+ totalSizeBytes
376
+ )})`
269
377
  );
378
+ console.log(` • Ignored: ${stats.ignored} files/dirs`);
379
+ console.log(` • Output: ${argv.output} (~${totalLines} lines)`);
380
+ console.log(`\n✅ Done!`);
270
381
  }
271
382
 
272
383
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "combicode",
3
- "version": "1.5.3",
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
- ]