tree-fs 0.1.1 → 0.1.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.
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
 
@@ -128,6 +131,14 @@ project
128
131
  Known files without extensions are correctly identified as files.
129
132
  * `Dockerfile`, `Makefile`, `LICENSE`, `Procfile`, `.gitignore`, `Jenkinsfile`
130
133
 
134
+ ### 6. Indicators & Comments
135
+ We strip out common markers used to highlight specific files in documentation.
136
+ ```text
137
+ project
138
+ ├── src/ <-- Working directory
139
+ ├── utils.js // Deprecated
140
+ └── .env # Do not commit
141
+
131
142
  ## 📦 CI/CD Integration
132
143
 
133
144
  You can use `tree-fs` to scaffold environments in GitHub Actions or pipelines.
@@ -144,4 +155,4 @@ tree-fs structure.txt --dry-run
144
155
 
145
156
  ## License
146
157
 
147
- MIT
158
+ 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.3",
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,52 @@
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
-
16
- // 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
18
 
21
- // 4. Check for explicit trailing slash (User saying "This is a folder/")
19
+ // 3. Strip comments
20
+ // Look for multiple comment styles. We use "space + marker" to avoid false positives.
21
+ const commentMarkers = [" #", " <--", " //"]
22
+ let splitIndex = -1
23
+
24
+ for (const marker of commentMarkers) {
25
+ const idx = normalizedRaw.indexOf(marker)
26
+ if (idx !== -1) {
27
+ // Keep the earliest marker found
28
+ if (splitIndex === -1 || idx < splitIndex) {
29
+ splitIndex = idx
30
+ }
31
+ }
32
+ }
33
+
34
+ let cleaned = splitIndex !== -1 ? normalizedRaw.substring(0, splitIndex) : normalizedRaw
35
+
36
+ // 4. Check for explicit trailing slash
22
37
  const endsWithSlash = cleaned.trim().endsWith("/")
23
38
 
39
+ // 5. Clean up the name
24
40
  cleaned = cleaned
25
41
  .replace(STRIP_REGEX, "") // Remove tree characters
26
- .replace(/\/$/, "") // Remove that trailing slash for the name
42
+ .replace(/\/$/, "") // Remove trailing slash for name storage
27
43
  .trim()
28
44
 
29
45
  return {
30
- raw,
46
+ raw: normalizedRaw,
31
47
  indent: prefixLength,
32
48
  name: cleaned,
33
- explicitFolder: endsWithSlash // Pass this signal to the parser
49
+ explicitFolder: endsWithSlash
34
50
  }
35
51
  })
36
52
  .filter(line => line.name.length > 0)