flatten-tool 1.6.1 → 1.6.3

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.
Files changed (3) hide show
  1. package/README.md +13 -0
  2. package/index.ts +55 -13
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,6 +4,9 @@
4
4
 
5
5
  A CLI utility to flatten directory structures, with perfect GitHub Flavored Markdown compatibility.
6
6
 
7
+ [![Video thumbnail](https://i.ytimg.com/vi/LCbSoK0Mkjk/maxresdefault.jpg)](https://youtu.be/LCbSoK0Mkjk)
8
+ *Watch the YouTube video for an example of why you might need to flatten project files into a single document for AI discussions and code reviews.*
9
+
7
10
  [![asciicast](docs/demo.gif)](https://asciinema.org/a/ThswNC1vrdlK0wdD)
8
11
 
9
12
  ## Installation
@@ -140,6 +143,16 @@ This project uses Bun for runtime, TypeScript for type safety, and follows the g
140
143
 
141
144
  ## Changelog
142
145
 
146
+ ### v1.6.3
147
+ - Enhanced safety: Automatically exclude common binary file extensions (PNG, JPEG, PDF, archives, executables, etc.) when merging to Markdown to prevent corruption.
148
+ - Added `--ignore` CLI option: Allow additional glob patterns to ignore (e.g., `*.log`, `temp/**`).
149
+ - Minor clean-ups: Improved variable naming and code consistency.
150
+ - Added YouTube video link in README demonstrating use case for AI discussions.
151
+
152
+ ### v1.6.2
153
+ - Updated AGENTS.md with revised coding guidelines.
154
+ - Added '..' links in subdirectory file trees for navigation to parent directories.
155
+
143
156
  ### v1.6.1
144
157
  - Added instructions for running flatten-tool directly with npx and bunx.
145
158
 
package/index.ts CHANGED
@@ -67,20 +67,28 @@ export async function flattenDirectory(
67
67
  return;
68
68
  }
69
69
 
70
- const negativeIgnores = ignorePatterns.map(pattern => `!${pattern}`);
70
+ // Patterns explicitly ignored (in addition to .gitignore when enabled)
71
+ const extraIgnores = ignorePatterns.map(pattern => `!${pattern}`);
71
72
 
72
- const ignoreFiles = ['.git'];
73
+ const defaultIgnores = ['.git'];
73
74
  if (!flattenToDirectory) {
74
- ignoreFiles.push('**/*.gif');
75
+ // Common binary/media extensions that would corrupt UTF-8 text output
76
+ const binaryExts = [
77
+ 'gif', 'png', 'jpg', 'jpeg', 'webp', 'svg', 'bmp', 'ico',
78
+ 'pdf', 'zip', 'tar', 'gz', 'xz', '7z',
79
+ 'mp3', 'mp4', 'webm', 'ogg', 'wav',
80
+ 'exe', 'dll', 'so', 'dylib', 'bin'
81
+ ];
82
+ defaultIgnores.push(`**/*.{${binaryExts.join(',')}}`);
75
83
  }
76
84
 
77
- const files = await globby(['**', ...negativeIgnores], {
85
+ const files = await globby(['**', ...extraIgnores], {
78
86
  cwd: absSource,
79
87
  gitignore: respectGitignore,
80
88
  absolute: true,
81
89
  dot: true,
82
90
  onlyFiles: true,
83
- ignore: ignoreFiles,
91
+ ignore: defaultIgnores,
84
92
  });
85
93
 
86
94
  if (!flattenToDirectory) {
@@ -147,6 +155,8 @@ export async function flattenDirectory(
147
155
  }
148
156
  }
149
157
 
158
+ sections.push({ path: '', headerText: 'Project File Tree' });
159
+
150
160
  collectSections(treeObj, '');
151
161
 
152
162
  const anchorMap = new Map<string, string>();
@@ -161,10 +171,21 @@ export async function flattenDirectory(
161
171
  node: any,
162
172
  depth: number = 0,
163
173
  prefix: string = '',
164
- anchorMap: Map<string, string>
174
+ anchorMap: Map<string, string>,
175
+ parentPath: string | null // null for global root (no ..)
165
176
  ): string {
166
177
  let result = '';
167
178
  const indent = ' '.repeat(depth);
179
+
180
+ // Add .. if we have a parent
181
+ if (parentPath !== null) {
182
+ const parentAnchor = anchorMap.get(parentPath) ?? '';
183
+ result += `${indent}- [..](#${parentAnchor})\n`;
184
+ }
185
+
186
+ // Determine indentation for direct children
187
+ const entryIndent = ' '.repeat(depth);
188
+
168
189
  const entries: [string, any][] = Object.entries(node);
169
190
 
170
191
  entries.sort(([a], [b]) => {
@@ -180,17 +201,27 @@ export async function flattenDirectory(
180
201
  const display = isDir ? name + '/' : name;
181
202
  const pathHere = prefix ? `${prefix}/${name}` : name;
182
203
  const anchor = anchorMap.get(pathHere) ?? '';
183
- result += `${indent}- [${display}](#${anchor})\n`;
204
+
205
+ result += `${entryIndent}- [${display}](#${anchor})\n`;
206
+
184
207
  if (isDir) {
185
- result += renderMarkdownTree(value, depth + 1, pathHere, anchorMap);
208
+ // Recurse with increased depth; child's parent is current prefix
209
+ result += renderMarkdownTree(
210
+ value,
211
+ depth + 1,
212
+ pathHere,
213
+ anchorMap,
214
+ prefix
215
+ );
186
216
  }
187
217
  }
218
+
188
219
  return result;
189
220
  }
190
221
 
191
222
  // Render global tree with correct anchors
192
- let treeMarkdown = "# Project File Tree\n\n- .\n";
193
- treeMarkdown += renderMarkdownTree(treeObj, 1, '', anchorMap);
223
+ let treeMarkdown = "# Project File Tree\n\n";
224
+ treeMarkdown += renderMarkdownTree(treeObj, 0, '', anchorMap, null);
194
225
  treeMarkdown += "\n\n";
195
226
 
196
227
  const writeStream = createWriteStream(absTarget);
@@ -230,8 +261,12 @@ export async function flattenDirectory(
230
261
  if (currentPath) {
231
262
  writeStream.write(`# ${currentPath}\n\n`);
232
263
  writeStream.write(`File Tree\n\n`);
233
- writeStream.write(`- .\n`);
234
- writeStream.write(renderMarkdownTree(node, 1, currentPath, anchorMap));
264
+
265
+ const parentPath = currentPath.includes('/')
266
+ ? currentPath.slice(0, currentPath.lastIndexOf('/'))
267
+ : '';
268
+
269
+ writeStream.write(renderMarkdownTree(node, 0, currentPath, anchorMap, parentPath));
235
270
  writeStream.write('\n');
236
271
  }
237
272
 
@@ -334,13 +369,20 @@ if (import.meta.url === `file://${process.argv[1]}`) {
334
369
  type: 'boolean',
335
370
  default: false,
336
371
  })
372
+ .option('ignore', {
373
+ alias: 'i',
374
+ type: 'string',
375
+ array: true,
376
+ describe: 'Additional glob patterns to ignore (e.g. "*.log" "temp/**")',
377
+ default: [],
378
+ })
337
379
  }, async (argv) => {
338
380
  let source = argv.source as string; // now always defined (default '.')
339
381
  let target = argv.target as string; // may be undefined
340
382
 
341
383
  const move: boolean = argv.move as boolean;
342
384
  const overwrite: boolean = argv.overwrite as boolean;
343
- const ignorePatterns: string[] = argv.ignore as string[] || [];
385
+ const ignorePatterns: string[] = (argv.ignore as string[] | undefined) ?? [];
344
386
  const respectGitignore: boolean = argv.gitignore as boolean;
345
387
  const flattenToDirectory: boolean = argv.directory as boolean;
346
388
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flatten-tool",
3
- "version": "1.6.1",
3
+ "version": "1.6.3",
4
4
  "description": "CLI tool to flatten directory structures: merge files into a single Markdown file (default) or copy/move to a flat directory with escaped filenames. Respects .gitignore, supports move/overwrite, and more.",
5
5
  "module": "index.ts",
6
6
  "type": "module",