tree-fs 0.1.0 ā 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 +9 -13
- package/package.json +3 -3
- package/src/generator.js +10 -4
- package/src/normaliser.js +15 -12
package/README.md
CHANGED
|
@@ -4,13 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
It is designed to be the **standard "Paste & Go" receiver for AI-generated code**.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
+
<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/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>
|
|
11
11
|
|
|
12
12
|
## ā” Why tree-fs?
|
|
13
13
|
|
|
14
|
+
<p align="">
|
|
15
|
+
<img src="https://github.com/mgks/tree-fs/blob/main/preview.gif?raw=true" width="720">
|
|
16
|
+
</p>
|
|
17
|
+
|
|
14
18
|
LLMs (ChatGPT, Claude, DeepSeek) are great at planning architectures but bad at executing them.
|
|
15
19
|
They often output this:
|
|
16
20
|
|
|
@@ -31,8 +35,6 @@ Copying that structure manually is tedious. **tree-fs** makes it instant.
|
|
|
31
35
|
* **Smart:** Distinguishes `v1.0` (folder) from `v1.0.js` (file) automatically.
|
|
32
36
|
* **Zero Dependencies:** Installs in seconds.
|
|
33
37
|
|
|
34
|
-
---
|
|
35
|
-
|
|
36
38
|
## š Usage
|
|
37
39
|
|
|
38
40
|
### 1. Interactive Mode (The "Paste" Workflow)
|
|
@@ -85,8 +87,6 @@ generateFS(tree, path.resolve(__dirname, "./output"))
|
|
|
85
87
|
console.log("Structure created!")
|
|
86
88
|
```
|
|
87
89
|
|
|
88
|
-
---
|
|
89
|
-
|
|
90
90
|
## š” Syntax Guide & Robustness
|
|
91
91
|
|
|
92
92
|
tree-fs is built to handle the "messy reality" of text inputs.
|
|
@@ -131,8 +131,6 @@ project
|
|
|
131
131
|
Known files without extensions are correctly identified as files.
|
|
132
132
|
* `Dockerfile`, `Makefile`, `LICENSE`, `Procfile`, `.gitignore`, `Jenkinsfile`
|
|
133
133
|
|
|
134
|
-
---
|
|
135
|
-
|
|
136
134
|
## š¦ CI/CD Integration
|
|
137
135
|
|
|
138
136
|
You can use `tree-fs` to scaffold environments in GitHub Actions or pipelines.
|
|
@@ -147,8 +145,6 @@ To test without writing files (Dry Run):
|
|
|
147
145
|
tree-fs structure.txt --dry-run
|
|
148
146
|
```
|
|
149
147
|
|
|
150
|
-
---
|
|
151
|
-
|
|
152
148
|
## License
|
|
153
149
|
|
|
154
|
-
MIT
|
|
150
|
+
MIT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tree-fs",
|
|
3
|
-
"version": "0.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"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"node": ">=14.0.0"
|
|
18
18
|
},
|
|
19
19
|
"scripts": {
|
|
20
|
-
"test": "echo \"Error: no test specified\" && exit
|
|
20
|
+
"test": "echo \"Error: no test specified\" && exit 0",
|
|
21
21
|
"start": "node bin/tree-fs.js"
|
|
22
22
|
},
|
|
23
23
|
"repository": {
|
|
@@ -40,5 +40,5 @@
|
|
|
40
40
|
"bugs": {
|
|
41
41
|
"url": "https://github.com/mgks/tree-fs/issues"
|
|
42
42
|
},
|
|
43
|
-
"homepage": "https://github.com/mgks/tree-fs"
|
|
43
|
+
"homepage": "https://github.com/mgks/tree-fs#readme"
|
|
44
44
|
}
|
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)
|