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 +12 -0
- package/README.md +1 -0
- package/index.js +177 -66
- package/package.json +2 -4
- package/test/test.js +99 -36
- package/config/ignore.json +0 -53
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
|
|
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
|
-
|
|
28
|
-
|
|
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(
|
|
30
|
+
function isLikelyBinary(filePath) {
|
|
43
31
|
const buffer = Buffer.alloc(512);
|
|
44
32
|
let fd;
|
|
45
33
|
try {
|
|
46
|
-
fd = fs.openSync(
|
|
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(
|
|
267
|
+
console.log(`\n✨ Combicode v${version}`);
|
|
268
|
+
console.log(`📂 Root: ${projectRoot}`);
|
|
164
269
|
|
|
165
|
-
const
|
|
270
|
+
const rootIgnoreManager = ignore();
|
|
166
271
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
+
"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
|
-
"
|
|
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
|
-
//
|
|
11
|
-
function
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
34
|
+
// Clean start
|
|
35
|
+
teardown();
|
|
36
|
+
fs.mkdirSync(TEST_DIR);
|
|
38
37
|
|
|
39
|
-
// 1
|
|
40
|
-
console.log(" Checking
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
51
|
-
assert.match(dryRunOutput, /\[\d+(\.\d+)?[KM]?B\]/);
|
|
55
|
+
assert.match(dryRunOutput, /\[\d+(\.\d+)?[KM]?B\]/); // Size check
|
|
52
56
|
|
|
53
|
-
//
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
95
|
+
// Should include:
|
|
96
|
+
assert.ok(content.includes("### **FILE:** `root.js`"), "root.js missing");
|
|
62
97
|
assert.ok(
|
|
63
|
-
content.includes("
|
|
64
|
-
"
|
|
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
|
-
//
|
|
68
|
-
assert.ok(
|
|
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
|
-
//
|
|
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:** `
|
|
73
|
-
"
|
|
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:** `
|
|
77
|
-
"
|
|
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();
|
package/config/ignore.json
DELETED
|
@@ -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
|
-
]
|