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 +7 -4
- package/package.json +1 -1
- package/src/generator.js +10 -4
- package/src/normaliser.js +15 -12
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
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,
|
|
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 (
|
|
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
|
-
//
|
|
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
|
-
//
|
|
13
|
-
const
|
|
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
|
-
|
|
18
|
-
|
|
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
|
|
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
|
|
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
|
|
36
|
+
explicitFolder: endsWithSlash
|
|
34
37
|
}
|
|
35
38
|
})
|
|
36
39
|
.filter(line => line.name.length > 0)
|