combicode 1.4.0 โ†’ 1.5.2

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,42 +1,45 @@
1
1
  # Changelog
2
2
 
3
- ## [1.4.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.3.0...combicode-js-v1.4.0) (2025-08-13)
3
+ ## [1.5.2](https://github.com/aaurelions/combicode/compare/combicode-js-v1.4.0...combicode-js-v1.5.2) (2025-11-30)
4
+
5
+ ### Features
4
6
 
7
+ - **tree:** display file sizes in the generated file tree (e.g., `[1.2KB]`)
8
+ - **output:** improve file header format (`### **FILE:**`) for better LLM parsing
9
+ - **tests:** add integration test suite using native Node.js modules
10
+
11
+ ## [1.4.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.3.0...combicode-js-v1.4.0) (2025-08-13)
5
12
 
6
13
  ### Features
7
14
 
8
- * Add --llms-txt flag for llms.txt documentation context ([0817554](https://github.com/aaurelions/combicode/commit/081755435594b0ca5208609b2724eb47bd73c2dc))
15
+ - Add --llms-txt flag for llms.txt documentation context ([0817554](https://github.com/aaurelions/combicode/commit/081755435594b0ca5208609b2724eb47bd73c2dc))
9
16
 
10
17
  ## [1.3.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.2.1...combicode-js-v1.3.0) (2025-06-25)
11
18
 
12
-
13
19
  ### Features
14
20
 
15
- * Add package-lock.json for reproducible builds ([06a417a](https://github.com/aaurelions/combicode/commit/06a417a155e9b72e26d0e091d181cfb8c53f0d28))
16
- * **ci:** implement independent package versioning for monorepo ([d02cf23](https://github.com/aaurelions/combicode/commit/d02cf233239c7af8db19061f34b769178334b388))
17
- * improve CLI output and version reporting ([7963a10](https://github.com/aaurelions/combicode/commit/7963a10782c2626608750de53023d37d327d51b2))
18
- * improve CLI output and version reporting ([e74f6d8](https://github.com/aaurelions/combicode/commit/e74f6d8fbed4f9cdf8ad82f3dae87069f66f7bb6))
19
-
21
+ - Add package-lock.json for reproducible builds ([06a417a](https://github.com/aaurelions/combicode/commit/06a417a155e9b72e26d0e091d181cfb8c53f0d28))
22
+ - **ci:** implement independent package versioning for monorepo ([d02cf23](https://github.com/aaurelions/combicode/commit/d02cf233239c7af8db19061f34b769178334b388))
23
+ - improve CLI output and version reporting ([7963a10](https://github.com/aaurelions/combicode/commit/7963a10782c2626608750de53023d37d327d51b2))
24
+ - improve CLI output and version reporting ([e74f6d8](https://github.com/aaurelions/combicode/commit/e74f6d8fbed4f9cdf8ad82f3dae87069f66f7bb6))
20
25
 
21
26
  ### Bug Fixes
22
27
 
23
- * **ci:** permission error ([156b76d](https://github.com/aaurelions/combicode/commit/156b76d3ab1550123df2ded6b1da5d6e2e2cc008))
24
- * **npm:** Set public access for publishing and bump version to 1.0.1 ([6c91eb7](https://github.com/aaurelions/combicode/commit/6c91eb714c81ec0201bb0fcfad8ad9fb4124cd7e))
25
- * Use scoped npm package name and bump python version ([8a1b347](https://github.com/aaurelions/combicode/commit/8a1b347f6c54c9762acf354ef289c293d3ef21a3))
28
+ - **ci:** permission error ([156b76d](https://github.com/aaurelions/combicode/commit/156b76d3ab1550123df2ded6b1da5d6e2e2cc008))
29
+ - **npm:** Set public access for publishing and bump version to 1.0.1 ([6c91eb7](https://github.com/aaurelions/combicode/commit/6c91eb714c81ec0201bb0fcfad8ad9fb4124cd7e))
30
+ - Use scoped npm package name and bump python version ([8a1b347](https://github.com/aaurelions/combicode/commit/8a1b347f6c54c9762acf354ef289c293d3ef21a3))
26
31
 
27
32
  ## [1.2.0](https://github.com/aaurelions/combicode/compare/combicode-js-v1.1.0...combicode-js-v1.2.0) (2025-06-25)
28
33
 
29
-
30
34
  ### Features
31
35
 
32
- * Add package-lock.json for reproducible builds ([06a417a](https://github.com/aaurelions/combicode/commit/06a417a155e9b72e26d0e091d181cfb8c53f0d28))
33
- * **ci:** implement independent package versioning for monorepo ([d02cf23](https://github.com/aaurelions/combicode/commit/d02cf233239c7af8db19061f34b769178334b388))
34
- * improve CLI output and version reporting ([7963a10](https://github.com/aaurelions/combicode/commit/7963a10782c2626608750de53023d37d327d51b2))
35
- * improve CLI output and version reporting ([e74f6d8](https://github.com/aaurelions/combicode/commit/e74f6d8fbed4f9cdf8ad82f3dae87069f66f7bb6))
36
-
36
+ - Add package-lock.json for reproducible builds ([06a417a](https://github.com/aaurelions/combicode/commit/06a417a155e9b72e26d0e091d181cfb8c53f0d28))
37
+ - **ci:** implement independent package versioning for monorepo ([d02cf23](https://github.com/aaurelions/combicode/commit/d02cf233239c7af8db19061f34b769178334b388))
38
+ - improve CLI output and version reporting ([7963a10](https://github.com/aaurelions/combicode/commit/7963a10782c2626608750de53023d37d327d51b2))
39
+ - improve CLI output and version reporting ([e74f6d8](https://github.com/aaurelions/combicode/commit/e74f6d8fbed4f9cdf8ad82f3dae87069f66f7bb6))
37
40
 
38
41
  ### Bug Fixes
39
42
 
40
- * **ci:** permission error ([156b76d](https://github.com/aaurelions/combicode/commit/156b76d3ab1550123df2ded6b1da5d6e2e2cc008))
41
- * **npm:** Set public access for publishing and bump version to 1.0.1 ([6c91eb7](https://github.com/aaurelions/combicode/commit/6c91eb714c81ec0201bb0fcfad8ad9fb4124cd7e))
42
- * Use scoped npm package name and bump python version ([8a1b347](https://github.com/aaurelions/combicode/commit/8a1b347f6c54c9762acf354ef289c293d3ef21a3))
43
+ - **ci:** permission error ([156b76d](https://github.com/aaurelions/combicode/commit/156b76d3ab1550123df2ded6b1da5d6e2e2cc008))
44
+ - **npm:** Set public access for publishing and bump version to 1.0.1 ([6c91eb7](https://github.com/aaurelions/combicode/commit/6c91eb714c81ec0201bb0fcfad8ad9fb4124cd7e))
45
+ - Use scoped npm package name and bump python version ([8a1b347](https://github.com/aaurelions/combicode/commit/8a1b347f6c54c9762acf354ef289c293d3ef21a3))
package/index.js CHANGED
@@ -10,7 +10,7 @@ const { version } = require("./package.json");
10
10
 
11
11
  const DEFAULT_SYSTEM_PROMPT = `You are an expert software architect. The user is providing you with the complete source code for a project, contained in a single file. Your task is to meticulously analyze the provided codebase to gain a comprehensive understanding of its structure, functionality, dependencies, and overall architecture.
12
12
 
13
- A file tree is provided below to give you a high-level overview. The subsequent sections contain the full content of each file, clearly marked with "// FILE: <path>".
13
+ A file tree is provided below to give you a high-level overview. The subsequent sections contain the full content of each file, clearly marked with a file header.
14
14
 
15
15
  Your instructions are:
16
16
  1. **Analyze Thoroughly:** Read through every file to understand its purpose and how it interacts with other files.
@@ -21,7 +21,7 @@ const LLMS_TXT_SYSTEM_PROMPT = `You are an expert software architect. The user i
21
21
 
22
22
  When answering questions or writing code, adhere strictly to the functions, variables, and methods described in this context. Do not use or suggest any deprecated or older functionalities that are not present here.
23
23
 
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 "// FILE: <path>".
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
27
  function loadDefaultIgnorePatterns() {
@@ -53,18 +53,33 @@ function isLikelyBinary(file) {
53
53
  }
54
54
  }
55
55
 
56
- function generateFileTree(files, root) {
56
+ function formatBytes(bytes, decimals = 1) {
57
+ if (bytes === 0) return "0 B";
58
+ const k = 1024;
59
+ const dm = decimals < 0 ? 0 : decimals;
60
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
61
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
62
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + "" + sizes[i];
63
+ }
64
+
65
+ function generateFileTree(filesWithSize, root) {
57
66
  let tree = `${path.basename(root)}/\n`;
58
67
  const structure = {};
59
68
 
60
- files.forEach((file) => {
61
- const parts = file.split(path.sep);
69
+ // Build the structure
70
+ filesWithSize.forEach(({ relativePath, formattedSize }) => {
71
+ const parts = relativePath.split(path.sep);
62
72
  let currentLevel = structure;
63
- parts.forEach((part) => {
64
- if (!currentLevel[part]) {
65
- currentLevel[part] = {};
73
+ parts.forEach((part, index) => {
74
+ const isFile = index === parts.length - 1;
75
+ if (isFile) {
76
+ currentLevel[part] = formattedSize;
77
+ } else {
78
+ if (!currentLevel[part]) {
79
+ currentLevel[part] = {};
80
+ }
81
+ currentLevel = currentLevel[part];
66
82
  }
67
- currentLevel = currentLevel[part];
68
83
  });
69
84
  });
70
85
 
@@ -72,9 +87,16 @@ function generateFileTree(files, root) {
72
87
  const entries = Object.keys(level);
73
88
  entries.forEach((entry, index) => {
74
89
  const isLast = index === entries.length - 1;
75
- tree += `${prefix}${isLast ? "โ””โ”€โ”€ " : "โ”œโ”€โ”€ "}${entry}\n`;
76
- if (Object.keys(level[entry]).length > 0) {
77
- buildTree(level[entry], `${prefix}${isLast ? " " : "โ”‚ "}`);
90
+ const value = level[entry];
91
+ const isFile = typeof value === "string";
92
+
93
+ const connector = isLast ? "โ””โ”€โ”€ " : "โ”œโ”€โ”€ ";
94
+
95
+ if (isFile) {
96
+ tree += `${prefix}${connector}[${value}] ${entry}\n`;
97
+ } else {
98
+ tree += `${prefix}${connector}${entry}\n`;
99
+ buildTree(value, `${prefix}${isLast ? " " : "โ”‚ "}`);
78
100
  }
79
101
  });
80
102
  };
@@ -90,6 +112,7 @@ async function main() {
90
112
  process.exit(0);
91
113
  }
92
114
 
115
+ // Yargs singleton usage works correctly with arguments passed here
93
116
  const argv = yargs(rawArgv)
94
117
  .scriptName("combicode")
95
118
  .usage("$0 [options]")
@@ -163,7 +186,7 @@ async function main() {
163
186
  dot: true,
164
187
  ignore: ignorePatterns,
165
188
  absolute: true,
166
- stats: false,
189
+ stats: true,
167
190
  });
168
191
 
169
192
  const allowedExtensions = argv.includeExt
@@ -175,28 +198,30 @@ async function main() {
175
198
  : null;
176
199
 
177
200
  const includedFiles = allFiles
178
- .filter((file) => {
179
- const stats = fs.statSync(file, { throwIfNoEntry: false });
180
- if (!stats || stats.isDirectory()) return false;
201
+ .filter((fileObj) => {
202
+ const file = fileObj.path;
203
+ if (!fileObj.stats || fileObj.stats.isDirectory()) return false;
181
204
  if (isLikelyBinary(file)) return false;
182
205
  if (allowedExtensions && !allowedExtensions.has(path.extname(file)))
183
206
  return false;
184
207
  return true;
185
208
  })
186
- .sort();
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));
187
216
 
188
217
  if (includedFiles.length === 0) {
189
218
  console.error("โŒ No files to include. Check your path or filters.");
190
219
  process.exit(1);
191
220
  }
192
221
 
193
- const relativeFiles = includedFiles.map((file) =>
194
- path.relative(projectRoot, file)
195
- );
196
-
197
222
  if (argv.dryRun) {
198
223
  console.log("\n๐Ÿ“‹ Files to be included (Dry Run):\n");
199
- const tree = generateFileTree(relativeFiles, projectRoot);
224
+ const tree = generateFileTree(includedFiles, projectRoot);
200
225
  console.log(tree);
201
226
  console.log(`\nTotal: ${includedFiles.length} files.`);
202
227
  return;
@@ -211,18 +236,18 @@ async function main() {
211
236
  outputStream.write(systemPrompt + "\n");
212
237
  outputStream.write("## Project File Tree\n\n");
213
238
  outputStream.write("```\n");
214
- const tree = generateFileTree(relativeFiles, projectRoot);
239
+ const tree = generateFileTree(includedFiles, projectRoot);
215
240
  outputStream.write(tree);
216
241
  outputStream.write("```\n\n");
217
242
  outputStream.write("---\n\n");
218
243
  }
219
244
 
220
- for (const file of includedFiles) {
221
- const relativePath = path.relative(projectRoot, file).replace(/\\/g, "/");
222
- outputStream.write(`// FILE: ${relativePath}` + "\n");
245
+ for (const fileObj of includedFiles) {
246
+ const relativePath = fileObj.relativePath.replace(/\\/g, "/");
247
+ outputStream.write(`### **FILE:** \`${relativePath}\`\n`);
223
248
  outputStream.write("```\n");
224
249
  try {
225
- const content = fs.readFileSync(file, "utf8");
250
+ const content = fs.readFileSync(fileObj.path, "utf8");
226
251
  outputStream.write(content);
227
252
  } catch (e) {
228
253
  outputStream.write(`... (error reading file: ${e.message}) ...`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "combicode",
3
- "version": "1.4.0",
3
+ "version": "1.5.2",
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,7 +10,9 @@
10
10
  "access": "public"
11
11
  },
12
12
  "scripts": {
13
- "test": "echo \"Error: no test specified\" && exit 1"
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
+ "test": "node test/test.js"
14
16
  },
15
17
  "repository": {
16
18
  "type": "git",
@@ -32,7 +34,7 @@
32
34
  "author": "A. Aurelions",
33
35
  "license": "MIT",
34
36
  "dependencies": {
35
- "fast-glob": "^3.3.1",
37
+ "fast-glob": "^3.3.3",
36
38
  "yargs": "^17.7.2"
37
39
  }
38
40
  }
package/test/test.js ADDED
@@ -0,0 +1,90 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const { execSync } = require("child_process");
4
+ const assert = require("assert");
5
+
6
+ const CLI_PATH = path.resolve(__dirname, "../index.js");
7
+ const TEST_DIR = path.resolve(__dirname, "temp_env");
8
+ const OUTPUT_FILE = path.join(TEST_DIR, "combicode.txt");
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");
24
+ }
25
+
26
+ // Teardown: Cleanup temp directory
27
+ function teardown() {
28
+ if (fs.existsSync(TEST_DIR)) {
29
+ fs.rmSync(TEST_DIR, { recursive: true, force: true });
30
+ }
31
+ }
32
+
33
+ function runTest() {
34
+ console.log("๐Ÿงช Starting Node.js Integration Tests...");
35
+
36
+ try {
37
+ setup();
38
+
39
+ // 1. Test Version Flag
40
+ console.log(" Checking --version...");
41
+ const versionOutput = execSync(`node ${CLI_PATH} --version`).toString();
42
+ assert.match(versionOutput, /Combicode \(JavaScript\), version/);
43
+
44
+ // 2. Test Dry Run
45
+ console.log(" Checking --dry-run...");
46
+ const dryRunOutput = execSync(`node ${CLI_PATH} --dry-run`, {
47
+ cwd: TEST_DIR,
48
+ }).toString();
49
+ 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\]/);
52
+
53
+ // 3. Test Actual Generation
54
+ console.log(" Checking file generation...");
55
+ execSync(`node ${CLI_PATH} --output combicode.txt`, { cwd: TEST_DIR });
56
+
57
+ assert.ok(fs.existsSync(OUTPUT_FILE), "Output file should exist");
58
+
59
+ const content = fs.readFileSync(OUTPUT_FILE, "utf8");
60
+
61
+ // Check for System Prompt
62
+ assert.ok(
63
+ content.includes("You are an expert software architect"),
64
+ "System prompt missing"
65
+ );
66
+
67
+ // Check for Tree structure
68
+ assert.ok(content.includes("subdir"), "Tree should show subdirectory");
69
+
70
+ // Check for new Header format
71
+ assert.ok(
72
+ content.includes("### **FILE:** `alpha.js`"),
73
+ "New header format missing for alpha.js"
74
+ );
75
+ assert.ok(
76
+ content.includes("### **FILE:** `subdir/beta.txt`"),
77
+ "New header format missing for beta.txt"
78
+ );
79
+
80
+ console.log("โœ… All Node.js tests passed!");
81
+ } catch (error) {
82
+ console.error("โŒ Test Failed:", error.message);
83
+ if (error.stdout) console.log(error.stdout.toString());
84
+ process.exit(1);
85
+ } finally {
86
+ teardown();
87
+ }
88
+ }
89
+
90
+ runTest();