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 +15 -4
- package/package.json +1 -1
- package/src/generator.js +10 -4
- package/src/normaliser.js +29 -13
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
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,52 @@
|
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
|
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
|
|
49
|
+
explicitFolder: endsWithSlash
|
|
34
50
|
}
|
|
35
51
|
})
|
|
36
52
|
.filter(line => line.name.length > 0)
|