flatten-tool 1.3.0 → 1.4.0
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/README.md +7 -1
- package/index.ts +73 -2
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ npm install -g flatten-tool
|
|
|
16
16
|
|
|
17
17
|
## Usage
|
|
18
18
|
|
|
19
|
-
By default, the tool merges all file contents into a single Markdown file, with each file's content
|
|
19
|
+
By default, the tool merges all file contents into a single Markdown file, starting with a project file tree for navigation, followed by each file's content under a header with its relative path, in a code block with appropriate language highlighting based on the file extension. Ignores and filters are applied as usual.
|
|
20
20
|
|
|
21
21
|
The `<source>` argument is optional and defaults to the current directory (`.`). The `<target>` argument is also optional and defaults to `flattened.md` (or `flattened/` when using `--directory`).
|
|
22
22
|
|
|
@@ -113,6 +113,12 @@ This project uses Bun for runtime, TypeScript for type safety, and follows the g
|
|
|
113
113
|
|
|
114
114
|
## Changelog
|
|
115
115
|
|
|
116
|
+
### v1.4.0
|
|
117
|
+
- Added project file tree to the beginning of merged Markdown output for better navigation.
|
|
118
|
+
|
|
119
|
+
### v1.3.1
|
|
120
|
+
- Fixed GIF exclusion pattern to work recursively in subdirectories.
|
|
121
|
+
|
|
116
122
|
### v1.3.0
|
|
117
123
|
- Excluded GIF files by default when merging to Markdown to prevent binary content corruption.
|
|
118
124
|
|
package/index.ts
CHANGED
|
@@ -7,11 +7,65 @@ import yargs from 'yargs';
|
|
|
7
7
|
import { hideBin } from 'yargs/helpers';
|
|
8
8
|
import { globby } from 'globby';
|
|
9
9
|
import pkg from './package.json' assert { type: 'json' };
|
|
10
|
+
import treeify from 'treeify';
|
|
10
11
|
|
|
11
12
|
function escapePathComponent(component: string): string {
|
|
12
13
|
return component.replace(/_/g, '__');
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
function buildTreeObject(relPaths: string[]): any {
|
|
17
|
+
const tree: any = {};
|
|
18
|
+
|
|
19
|
+
relPaths.forEach((path) => {
|
|
20
|
+
// Normalize paths
|
|
21
|
+
path = path.replace(/\\/g, '/');
|
|
22
|
+
if (path.startsWith('./')) path = path.slice(2);
|
|
23
|
+
|
|
24
|
+
const parts = path.split('/');
|
|
25
|
+
let node = tree;
|
|
26
|
+
|
|
27
|
+
parts.forEach((part, index) => {
|
|
28
|
+
const isDir = index < parts.length - 1;
|
|
29
|
+
const key = isDir ? `${part}/` : part;
|
|
30
|
+
|
|
31
|
+
if (!node[key]) {
|
|
32
|
+
node[key] = isDir ? {} : null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (isDir) {
|
|
36
|
+
node = node[key];
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Recursively sort: directories first, then files, case-insensitive
|
|
42
|
+
function sortNode(node: any): void {
|
|
43
|
+
if (node === null || typeof node !== 'object') return;
|
|
44
|
+
|
|
45
|
+
const entries = Object.entries(node);
|
|
46
|
+
entries.sort(([a], [b]) => {
|
|
47
|
+
const aIsDir = a.endsWith('/');
|
|
48
|
+
const bIsDir = b.endsWith('/');
|
|
49
|
+
if (aIsDir !== bIsDir) return aIsDir ? -1 : 1; // dirs before files
|
|
50
|
+
return a.toLowerCase().localeCompare(b.toLowerCase());
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Rebuild node in sorted order (preserves insertion order for rendering)
|
|
54
|
+
const sorted: any = {};
|
|
55
|
+
entries.forEach(([key, value]) => {
|
|
56
|
+
sorted[key] = value;
|
|
57
|
+
sortNode(value);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
Object.keys(node).forEach((k) => delete node[k]);
|
|
61
|
+
Object.assign(node, sorted);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
sortNode(tree);
|
|
65
|
+
|
|
66
|
+
return tree;
|
|
67
|
+
}
|
|
68
|
+
|
|
15
69
|
async function removeEmptyDirs(dir: string, root?: string): Promise<void> {
|
|
16
70
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
17
71
|
for (const entry of entries) {
|
|
@@ -49,10 +103,10 @@ export async function flattenDirectory(
|
|
|
49
103
|
|
|
50
104
|
const ignoreFiles = ['.git'];
|
|
51
105
|
if (!flattenToDirectory) {
|
|
52
|
-
ignoreFiles.push('
|
|
106
|
+
ignoreFiles.push('**/*.gif');
|
|
53
107
|
}
|
|
54
108
|
|
|
55
|
-
|
|
109
|
+
const files = await globby(['**', ...negativeIgnores], {
|
|
56
110
|
cwd: absSource,
|
|
57
111
|
gitignore: respectGitignore,
|
|
58
112
|
absolute: true,
|
|
@@ -73,9 +127,26 @@ export async function flattenDirectory(
|
|
|
73
127
|
if (err.code !== 'ENOENT') throw err;
|
|
74
128
|
}
|
|
75
129
|
|
|
130
|
+
const relPaths: string[] = files.map((srcPath) =>
|
|
131
|
+
relative(absSource, srcPath).replace(/\\/g, '/')
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Sort paths for consistent insertion
|
|
135
|
+
relPaths.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
136
|
+
|
|
137
|
+
const treeObj = buildTreeObject(relPaths);
|
|
138
|
+
|
|
139
|
+
// Wrap with root for nice "." header (empty dirs show just ".")
|
|
140
|
+
const rootTree = { '.': treeObj };
|
|
141
|
+
|
|
76
142
|
const writeStream = createWriteStream(absTarget);
|
|
77
143
|
writeStream.setMaxListeners(0);
|
|
78
144
|
|
|
145
|
+
writeStream.write("# Project File Tree\n\n");
|
|
146
|
+
writeStream.write("```\n");
|
|
147
|
+
writeStream.write(treeify.asTree(rootTree));
|
|
148
|
+
writeStream.write("```\n\n");
|
|
149
|
+
|
|
79
150
|
for (const srcPath of files) {
|
|
80
151
|
const relPath = relative(absSource, srcPath).replace(/\\/g, '/');
|
|
81
152
|
let ext = extname(srcPath).slice(1) || 'text';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flatten-tool",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
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",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"globby": "^16.1.0",
|
|
50
50
|
"ignore": "^7.0.5",
|
|
51
51
|
"minimatch": "^10.1.2",
|
|
52
|
+
"treeify": "^1.1.0",
|
|
52
53
|
"yargs": "^18.0.0"
|
|
53
54
|
}
|
|
54
55
|
}
|