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 +18 -0
- package/README.md +1 -0
- package/index.js +180 -61
- package/package.json +2 -4
- package/test/test.js +99 -36
- package/config/ignore.json +0 -53
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
|
|
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,34 +264,20 @@ 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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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
|
-
]
|