tree-fs 0.1.1 → 0.1.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/README.md CHANGED
@@ -6,12 +6,15 @@ It is designed to be the **standard "Paste & Go" receiver for AI-generated code*
6
6
 
7
7
  <a href="https://www.npmjs.com/package/tree-fs"><img src="https://img.shields.io/npm/v/tree-fs.svg?style=flat-square&color=007acc" alt="npm version"></a>
8
8
  <a href="https://www.npmjs.com/package/tree-fs"><img src="https://img.shields.io/npm/dt/tree-fs.svg?style=flat-square&color=success" alt="npm downloads"></a>
9
- <a href="https://github.com/tree-fs/blob/main/LICENSE"><img src="https://img.shields.io/github/license/tree-fs.svg?style=flat-square&color=blue" alt="license"></a>
10
- <a href="https://github.com/tree-fs/stargazers"><img src="https://img.shields.io/github/stars/tree-fs?style=flat-square&logo=github" alt="stars"></a>
11
-
9
+ <a href="https://github.com/mgks/tree-fs/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mgks/tree-fs.svg?style=flat-square&color=blue" alt="license"></a>
10
+ <a href="https://github.com/mgks/tree-fs/stargazers"><img src="https://img.shields.io/github/stars/mgks/tree-fs?style=flat-square&logo=github" alt="stars"></a>
12
11
 
13
12
  ## ⚡ Why tree-fs?
14
13
 
14
+ <p align="">
15
+ <img src="https://github.com/mgks/tree-fs/blob/main/preview.gif?raw=true" width="720">
16
+ </p>
17
+
15
18
  LLMs (ChatGPT, Claude, DeepSeek) are great at planning architectures but bad at executing them.
16
19
  They often output this:
17
20
 
@@ -144,4 +147,4 @@ tree-fs structure.txt --dry-run
144
147
 
145
148
  ## License
146
149
 
147
- MIT
150
+ MIT
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tree-fs",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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/generator.js CHANGED
@@ -14,12 +14,11 @@ function generateFS(tree, baseDir = process.cwd(), options = {}) {
14
14
  let rootPath = baseDir
15
15
 
16
16
  // Intelligent Root Detection:
17
- // If there is exactly one top-level folder, we treat it as the project root.
17
+ // If there is exactly one top-level folder, treat it as the project root.
18
18
  if (nodes.length === 1 && nodes[0].type === "folder") {
19
19
  rootPath = path.join(baseDir, nodes[0].name)
20
20
  nodes = nodes[0].children
21
21
 
22
- // Create the root directory physically
23
22
  if (!dryRun && !fs.existsSync(rootPath)) {
24
23
  fs.mkdirSync(rootPath, { recursive: true })
25
24
  }
@@ -27,9 +26,16 @@ function generateFS(tree, baseDir = process.cwd(), options = {}) {
27
26
 
28
27
  function walk(nodes, currentPath) {
29
28
  for (const node of nodes) {
30
- // Robustness: normalize path separators for cross-platform safety
31
29
  const target = path.join(currentPath, node.name)
32
30
 
31
+ // SECURITY CHECK: Prevent directory traversal
32
+ // If the relative path starts with '..', it's trying to escape the root
33
+ const relative = path.relative(rootPath, target)
34
+ if (relative.startsWith("..") && !path.isAbsolute(relative)) {
35
+ console.warn(`⚠️ Skipping unsafe path: ${node.name}`)
36
+ continue
37
+ }
38
+
33
39
  if (node.type === "folder") {
34
40
  if (!fs.existsSync(target)) {
35
41
  if (!dryRun) fs.mkdirSync(target, { recursive: true })
@@ -43,7 +49,7 @@ function generateFS(tree, baseDir = process.cwd(), options = {}) {
43
49
  continue
44
50
  }
45
51
 
46
- // Ensure parent directory exists (handles cases like "src/utils/helpers.js" as a single line)
52
+ // Ensure parent directory exists (Safety for weird edge cases)
47
53
  const parentDir = path.dirname(target)
48
54
  if (!dryRun && !fs.existsSync(parentDir)) {
49
55
  fs.mkdirSync(parentDir, { recursive: true })
package/src/normaliser.js CHANGED
@@ -1,36 +1,39 @@
1
1
  // src/normaliser.js
2
2
 
3
- // 1. Expanded Regex to catch +, >, and markdown bullets
3
+ // Catch standard tree characters, markdown bullets, and ASCII symbols (+, >, -)
4
4
  const STRIP_REGEX = /^[\s│├└─•*|\-+>]+/
5
5
 
6
6
  function normaliseLines(input) {
7
7
  return input
8
8
  .split("\n")
9
- .map(line => line.replace(/\r/g, ""))
9
+ .map(line => line.replace(/\r/g, "")) // Handle Windows CRLF
10
10
  .filter(Boolean)
11
11
  .map(raw => {
12
- // 2. Detect indentation based on prefix length
13
- const match = raw.match(STRIP_REGEX)
12
+ // 1. Windows Fix: Normalize backslashes to forward slashes immediately
13
+ const normalizedRaw = raw.replace(/\\/g, "/")
14
+
15
+ // 2. Calculate indent based on the full prefix (spaces + tree chars)
16
+ const match = normalizedRaw.match(STRIP_REGEX)
14
17
  const prefixLength = match ? match[0].length : 0
15
-
18
+
16
19
  // 3. Strip comments (anything after ' #')
17
- // We explicitly look for " #" so we don't break "page#1.js"
18
- const commentIndex = raw.indexOf(" #")
19
- let cleaned = commentIndex !== -1 ? raw.substring(0, commentIndex) : raw
20
+ const commentIndex = normalizedRaw.indexOf(" #")
21
+ let cleaned = commentIndex !== -1 ? normalizedRaw.substring(0, commentIndex) : normalizedRaw
20
22
 
21
- // 4. Check for explicit trailing slash (User saying "This is a folder/")
23
+ // 4. Check for explicit trailing slash
22
24
  const endsWithSlash = cleaned.trim().endsWith("/")
23
25
 
26
+ // 5. Clean up the name
24
27
  cleaned = cleaned
25
28
  .replace(STRIP_REGEX, "") // Remove tree characters
26
- .replace(/\/$/, "") // Remove that trailing slash for the name
29
+ .replace(/\/$/, "") // Remove trailing slash for name storage
27
30
  .trim()
28
31
 
29
32
  return {
30
- raw,
33
+ raw: normalizedRaw,
31
34
  indent: prefixLength,
32
35
  name: cleaned,
33
- explicitFolder: endsWithSlash // Pass this signal to the parser
36
+ explicitFolder: endsWithSlash
34
37
  }
35
38
  })
36
39
  .filter(line => line.name.length > 0)