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 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
- ```text
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
- ## 💡 Syntax Guide & Robustness
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
- ```text
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
- ```text
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
- ```text
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
- ```text
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
- ```text
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 // This helps handle raw paste streams better
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
- // Programmatic / CI / file input
37
- if (args[0] && !args[0].startsWith("--")) {
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(args[0], "utf8")
72
+ input = fs.readFileSync(fileArg, "utf8")
40
73
  } catch (err) {
41
- console.error(`Error reading file: ${args[0]}`)
74
+ console.error(`Error reading file: ${fileArg}`)
75
+ console.error(err.message)
42
76
  process.exit(1)
43
77
  }
44
- }
45
- // Interactive mode
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-fs",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Generate file system structures from text-based directory trees. The standard receiver for AI-generated project scaffolding.",
5
5
  "bin": {
6
6
  "tree-fs": "bin/tree-fs.js"
package/src/normaliser.js CHANGED
@@ -1,52 +1,58 @@
1
1
  // src/normaliser.js
2
2
 
3
- // Catch standard tree characters, markdown bullets, and ASCII symbols (+, >, -)
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, "")) // Handle Windows CRLF
19
+ .map(line => line.replace(/\r/g, ""))
10
20
  .filter(Boolean)
11
21
  .map(raw => {
12
- // 1. Windows Fix: Normalize backslashes to forward slashes immediately
22
+ // A. Normalize slashes
13
23
  const normalizedRaw = raw.replace(/\\/g, "/")
14
24
 
15
- // 2. Calculate indent based on the full prefix (spaces + tree chars)
16
- const match = normalizedRaw.match(STRIP_REGEX)
17
- const prefixLength = match ? match[0].length : 0
25
+ // B. Calculate Indent
26
+ const treeMatch = normalizedRaw.match(STRIP_REGEX)
27
+ const prefixLength = treeMatch ? treeMatch[0].length : 0
18
28
 
19
- // 3. Strip comments
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
- // Keep the earliest marker found
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
- // 4. Check for explicit trailing slash
37
- const endsWithSlash = cleaned.trim().endsWith("/")
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, "") // Remove tree characters
42
- .replace(/\/$/, "") // Remove trailing slash for name storage
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: endsWithSlash
55
+ explicitFolder: normalizedRaw.trim().endsWith("/")
50
56
  }
51
57
  })
52
58
  .filter(line => line.name.length > 0)