tree-fs 0.1.4 → 0.1.6
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 +26 -7
- package/bin/tree-fs.js +43 -9
- package/package.json +1 -1
- package/src/normaliser.js +27 -21
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ It is designed to be the **standard "Paste & Go" receiver for AI-generated code*
|
|
|
18
18
|
LLMs (ChatGPT, Claude, DeepSeek) are great at planning architectures but bad at executing them.
|
|
19
19
|
They often output this:
|
|
20
20
|
|
|
21
|
-
```
|
|
21
|
+
```bash
|
|
22
22
|
my-app
|
|
23
23
|
├── src
|
|
24
24
|
│ ├── index.js
|
|
@@ -87,13 +87,13 @@ generateFS(tree, path.resolve(__dirname, "./output"))
|
|
|
87
87
|
console.log("Structure created!")
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
##
|
|
90
|
+
## { } Syntax Guide & Robustness
|
|
91
91
|
|
|
92
92
|
tree-fs is built to handle the "messy reality" of text inputs.
|
|
93
93
|
|
|
94
94
|
### 1. Comments are ignored
|
|
95
95
|
Great for annotated AI outputs.
|
|
96
|
-
```
|
|
96
|
+
```bash
|
|
97
97
|
src
|
|
98
98
|
├── auth.js # Handles JWT tokens
|
|
99
99
|
└── db.js # Connection logic
|
|
@@ -102,7 +102,7 @@ src
|
|
|
102
102
|
|
|
103
103
|
### 2. Explicit Folders
|
|
104
104
|
If a name looks like a file but is actually a folder (e.g., version numbers), end it with a slash `/`.
|
|
105
|
-
```
|
|
105
|
+
```bash
|
|
106
106
|
api
|
|
107
107
|
├── v1.5/ <-- Created as a folder
|
|
108
108
|
└── v2.0/ <-- Created as a folder
|
|
@@ -110,7 +110,7 @@ api
|
|
|
110
110
|
|
|
111
111
|
### 3. Smart Nesting
|
|
112
112
|
If an item has children indented below it, it is **automatically treated as a folder**, even if it has a dot.
|
|
113
|
-
```
|
|
113
|
+
```bash
|
|
114
114
|
app
|
|
115
115
|
└── v2.5 <-- Treated as folder because it has a child
|
|
116
116
|
└── migrator.js
|
|
@@ -118,7 +118,7 @@ app
|
|
|
118
118
|
|
|
119
119
|
### 4. Markdown & Symbols
|
|
120
120
|
We handle standard tree characters, ASCII art, and bullets.
|
|
121
|
-
```
|
|
121
|
+
```bash
|
|
122
122
|
project
|
|
123
123
|
- src
|
|
124
124
|
+ components
|
|
@@ -133,7 +133,7 @@ Known files without extensions are correctly identified as files.
|
|
|
133
133
|
|
|
134
134
|
### 6. Indicators & Comments
|
|
135
135
|
We strip out common markers used to highlight specific files in documentation.
|
|
136
|
-
```
|
|
136
|
+
```bash
|
|
137
137
|
project
|
|
138
138
|
├── src/ <-- Working directory
|
|
139
139
|
├── utils.js // Deprecated
|
|
@@ -142,6 +142,25 @@ project
|
|
|
142
142
|
|
|
143
143
|
*Result: Creates folder src and files utils.js, .env. All comments are ignored.*
|
|
144
144
|
|
|
145
|
+
### 7. Rich Text & Emojis
|
|
146
|
+
We automatically clean up "decorative" trees often generated by newer AI models (like Claude or GPT-4o), regardless of where the decoration is placed.
|
|
147
|
+
|
|
148
|
+
**It handles:**
|
|
149
|
+
* **Leading/Trailing Emojis:** `📁 src`, `index.js 🚀`, `✨ components ✨`
|
|
150
|
+
* **Explanations:** `index.js (Core Logic)`, `main.py (Entry)`
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
my-project
|
|
154
|
+
├── 📁 src 🚀
|
|
155
|
+
│ ├── main.js (The Brain) 🧠
|
|
156
|
+
│ └── theme.css (Dark Mode)
|
|
157
|
+
└── 📄 package.json 📦
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
*Result: Creates folder src and files main.js, theme.css, package.json.*
|
|
161
|
+
|
|
162
|
+
**Note:** Internal emojis (logo_🔥.png) and filenames with parentheses (image(1).png) are preserved. We only strip "detached" decorations separated by spaces.
|
|
163
|
+
|
|
145
164
|
## 📦 CI/CD Integration
|
|
146
165
|
|
|
147
166
|
You can use `tree-fs` to scaffold environments in GitHub Actions or pipelines.
|
package/bin/tree-fs.js
CHANGED
|
@@ -1,10 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require("fs")
|
|
4
|
+
const path = require("path")
|
|
4
5
|
const readline = require("readline")
|
|
5
6
|
const { parseTree, generateFS } = require("../src")
|
|
7
|
+
const pkg = require("../package.json")
|
|
6
8
|
|
|
7
9
|
const args = process.argv.slice(2)
|
|
10
|
+
|
|
11
|
+
// Standard Flags
|
|
12
|
+
if (args.includes("--version") || args.includes("-v")) {
|
|
13
|
+
console.log(pkg.version)
|
|
14
|
+
process.exit(0)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
18
|
+
console.log(`
|
|
19
|
+
Usage: tree-fs [file] [options]
|
|
20
|
+
|
|
21
|
+
Generate a real file system from a text-based directory tree.
|
|
22
|
+
|
|
23
|
+
Arguments:
|
|
24
|
+
file Text file containing the tree (optional)
|
|
25
|
+
If ignored, tree-fs runs in interactive mode.
|
|
26
|
+
|
|
27
|
+
Options:
|
|
28
|
+
-h, --help Show this help message
|
|
29
|
+
-v, --version Show version number
|
|
30
|
+
--dry-run Simulate execution without writing files
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
npx tree-fs # Interactive mode
|
|
34
|
+
npx tree-fs structure.txt # Generate from file
|
|
35
|
+
npx tree-fs doc.md --dry-run # Preview changes
|
|
36
|
+
`)
|
|
37
|
+
process.exit(0)
|
|
38
|
+
}
|
|
39
|
+
|
|
8
40
|
const dryRun = args.includes("--dry-run")
|
|
9
41
|
|
|
10
42
|
async function readInteractiveInput() {
|
|
@@ -13,13 +45,12 @@ async function readInteractiveInput() {
|
|
|
13
45
|
const rl = readline.createInterface({
|
|
14
46
|
input: process.stdin,
|
|
15
47
|
output: process.stdout,
|
|
16
|
-
terminal: false
|
|
48
|
+
terminal: false
|
|
17
49
|
})
|
|
18
50
|
|
|
19
51
|
const lines = []
|
|
20
52
|
|
|
21
53
|
for await (const line of rl) {
|
|
22
|
-
// If the line is empty (user hit Enter on a new line), we are done.
|
|
23
54
|
if (line.trim() === "") {
|
|
24
55
|
rl.close()
|
|
25
56
|
break
|
|
@@ -33,16 +64,19 @@ async function readInteractiveInput() {
|
|
|
33
64
|
async function main() {
|
|
34
65
|
let input = ""
|
|
35
66
|
|
|
36
|
-
//
|
|
37
|
-
|
|
67
|
+
// 1. Check for file input (Any arg that isn't a flag)
|
|
68
|
+
const fileArg = args.find(arg => !arg.startsWith("-"))
|
|
69
|
+
|
|
70
|
+
if (fileArg) {
|
|
38
71
|
try {
|
|
39
|
-
input = fs.readFileSync(
|
|
72
|
+
input = fs.readFileSync(fileArg, "utf8")
|
|
40
73
|
} catch (err) {
|
|
41
|
-
console.error(`Error reading file: ${
|
|
74
|
+
console.error(`Error reading file: ${fileArg}`)
|
|
75
|
+
console.error(err.message)
|
|
42
76
|
process.exit(1)
|
|
43
77
|
}
|
|
44
|
-
}
|
|
45
|
-
// Interactive
|
|
78
|
+
}
|
|
79
|
+
// 2. Interactive Mode
|
|
46
80
|
else {
|
|
47
81
|
input = await readInteractiveInput()
|
|
48
82
|
}
|
|
@@ -55,7 +89,7 @@ async function main() {
|
|
|
55
89
|
try {
|
|
56
90
|
const tree = parseTree(input)
|
|
57
91
|
generateFS(tree, process.cwd(), { dryRun })
|
|
58
|
-
|
|
92
|
+
|
|
59
93
|
if (dryRun) {
|
|
60
94
|
console.log("Dry run complete. No files written.")
|
|
61
95
|
} else {
|
package/package.json
CHANGED
package/src/normaliser.js
CHANGED
|
@@ -1,52 +1,58 @@
|
|
|
1
1
|
// src/normaliser.js
|
|
2
2
|
|
|
3
|
-
//
|
|
3
|
+
// 1. Tree Characters & Bullets
|
|
4
4
|
const STRIP_REGEX = /^[\s│├└─•*|\-+>]+/
|
|
5
5
|
|
|
6
|
+
// 2. Leading Emojis (e.g. "📁 src")
|
|
7
|
+
const LEADING_EMOJI_REGEX = /^[\p{Emoji}\u200d\ufe0f]+\s*/u
|
|
8
|
+
|
|
9
|
+
// 3. Trailing Emojis (e.g. "index.js 🚀")
|
|
10
|
+
// Must have a space before it to preserve files like "logo_🔥.png"
|
|
11
|
+
const TRAILING_EMOJI_REGEX = /\s+[\p{Emoji}\u200d\ufe0f]+$/u
|
|
12
|
+
|
|
13
|
+
// 4. Trailing Parenthesis (e.g. "index.js (Logic)")
|
|
14
|
+
const PAREN_COMMENT_REGEX = /\s+\([^)]+\)$/
|
|
15
|
+
|
|
6
16
|
function normaliseLines(input) {
|
|
7
17
|
return input
|
|
8
18
|
.split("\n")
|
|
9
|
-
.map(line => line.replace(/\r/g, ""))
|
|
19
|
+
.map(line => line.replace(/\r/g, ""))
|
|
10
20
|
.filter(Boolean)
|
|
11
21
|
.map(raw => {
|
|
12
|
-
//
|
|
22
|
+
// A. Normalize slashes
|
|
13
23
|
const normalizedRaw = raw.replace(/\\/g, "/")
|
|
14
24
|
|
|
15
|
-
//
|
|
16
|
-
const
|
|
17
|
-
const prefixLength =
|
|
25
|
+
// B. Calculate Indent
|
|
26
|
+
const treeMatch = normalizedRaw.match(STRIP_REGEX)
|
|
27
|
+
const prefixLength = treeMatch ? treeMatch[0].length : 0
|
|
18
28
|
|
|
19
|
-
//
|
|
20
|
-
// Look for multiple comment styles. We use "space + marker" to avoid false positives.
|
|
29
|
+
// C. Strip Explicit Comments (#, //, <--)
|
|
21
30
|
const commentMarkers = [" #", " <--", " //"]
|
|
22
31
|
let splitIndex = -1
|
|
23
|
-
|
|
24
32
|
for (const marker of commentMarkers) {
|
|
25
33
|
const idx = normalizedRaw.indexOf(marker)
|
|
26
34
|
if (idx !== -1) {
|
|
27
|
-
|
|
28
|
-
if (splitIndex === -1 || idx < splitIndex) {
|
|
29
|
-
splitIndex = idx
|
|
30
|
-
}
|
|
35
|
+
if (splitIndex === -1 || idx < splitIndex) splitIndex = idx
|
|
31
36
|
}
|
|
32
37
|
}
|
|
33
|
-
|
|
34
38
|
let cleaned = splitIndex !== -1 ? normalizedRaw.substring(0, splitIndex) : normalizedRaw
|
|
35
39
|
|
|
36
|
-
//
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// 5. Clean up the name
|
|
40
|
+
// D. Deep Cleaning Chain
|
|
41
|
+
// We repeat the trailing checks to handle mixed cases like "file.js (Logic) 🚀"
|
|
40
42
|
cleaned = cleaned
|
|
41
|
-
.replace(STRIP_REGEX, "")
|
|
42
|
-
.replace(
|
|
43
|
+
.replace(STRIP_REGEX, "") // Remove tree chars
|
|
44
|
+
.replace(LEADING_EMOJI_REGEX, "") // Remove leading emojis
|
|
45
|
+
.replace(TRAILING_EMOJI_REGEX, "") // Remove trailing emojis (Pass 1)
|
|
46
|
+
.replace(PAREN_COMMENT_REGEX, "") // Remove trailing parens
|
|
47
|
+
.replace(TRAILING_EMOJI_REGEX, "") // Remove trailing emojis (Pass 2 - catches leftovers)
|
|
48
|
+
.replace(/\/$/, "") // Remove trailing slash
|
|
43
49
|
.trim()
|
|
44
50
|
|
|
45
51
|
return {
|
|
46
52
|
raw: normalizedRaw,
|
|
47
53
|
indent: prefixLength,
|
|
48
54
|
name: cleaned,
|
|
49
|
-
explicitFolder:
|
|
55
|
+
explicitFolder: normalizedRaw.trim().endsWith("/")
|
|
50
56
|
}
|
|
51
57
|
})
|
|
52
58
|
.filter(line => line.name.length > 0)
|