@himamshus06/git-auto 1.3.2 → 1.3.4
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/package.json +1 -1
- package/src/tree.js +183 -42
package/package.json
CHANGED
package/src/tree.js
CHANGED
|
@@ -2,70 +2,211 @@ const fs = require('node:fs');
|
|
|
2
2
|
const path = require('node:path');
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
* @param {string} text - The visual directory tree string.
|
|
7
|
-
* @returns {Promise<{created: string[], errors: string[]}>}
|
|
5
|
+
* Custom error for tree parsing failures
|
|
8
6
|
*/
|
|
9
|
-
|
|
7
|
+
class TreeParseError extends Error {
|
|
8
|
+
constructor(message, line) {
|
|
9
|
+
super(line ? `Line ${line}: ${message}` : message);
|
|
10
|
+
this.name = 'TreeParseError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse a visual directory tree into structured items.
|
|
16
|
+
*/
|
|
17
|
+
function parseTree(text) {
|
|
10
18
|
if (!text || text.trim() === '') {
|
|
11
|
-
throw new
|
|
19
|
+
throw new TreeParseError('No tree provided');
|
|
12
20
|
}
|
|
13
21
|
|
|
14
|
-
const lines = text
|
|
22
|
+
const lines = text
|
|
23
|
+
.split(/\r?\n/)
|
|
24
|
+
.filter(line => line.trim() !== '');
|
|
25
|
+
|
|
26
|
+
const result = [];
|
|
15
27
|
const stack = [];
|
|
16
|
-
const created = [];
|
|
17
|
-
const errors = [];
|
|
18
28
|
|
|
19
|
-
for (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
const match = line.match(/^([│\s├└]*)(.*)$/);
|
|
23
|
-
if (!match) continue;
|
|
29
|
+
for (let i = 0; i < lines.length; i++) {
|
|
30
|
+
const line = lines[i];
|
|
31
|
+
const lineNum = i + 1;
|
|
24
32
|
|
|
25
|
-
const
|
|
26
|
-
|
|
33
|
+
const parsed = parseLine(line);
|
|
34
|
+
|
|
35
|
+
if (!parsed) {
|
|
36
|
+
throw new TreeParseError(
|
|
37
|
+
`Unable to parse tree line: "${line}"`,
|
|
38
|
+
lineNum
|
|
39
|
+
);
|
|
40
|
+
}
|
|
27
41
|
|
|
28
|
-
|
|
42
|
+
const { name, depth } = parsed;
|
|
29
43
|
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
44
|
+
// Root must be depth 0
|
|
45
|
+
if (i === 0 && depth !== 0) {
|
|
46
|
+
throw new TreeParseError(
|
|
47
|
+
'Root item must have depth 0',
|
|
48
|
+
lineNum
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Prevent impossible jumps
|
|
53
|
+
if (depth > stack.length) {
|
|
54
|
+
throw new TreeParseError(
|
|
55
|
+
`Nesting depth jump detected`,
|
|
56
|
+
lineNum
|
|
57
|
+
);
|
|
58
|
+
}
|
|
37
59
|
|
|
38
|
-
//
|
|
60
|
+
// Remove anything deeper than the current item
|
|
39
61
|
while (stack.length > depth) {
|
|
40
62
|
stack.pop();
|
|
41
63
|
}
|
|
42
64
|
|
|
43
|
-
//
|
|
44
|
-
const isDirectory =
|
|
45
|
-
const cleanName = isDirectory ? name.replace(/\/$/, '') : name;
|
|
65
|
+
// Detect directory/file
|
|
66
|
+
const isDirectory = detectDirectory(name);
|
|
46
67
|
|
|
68
|
+
const cleanName = isDirectory
|
|
69
|
+
? name.replace(/\/$/, '')
|
|
70
|
+
: name;
|
|
71
|
+
|
|
72
|
+
// Add current item to stack
|
|
47
73
|
stack.push(cleanName);
|
|
48
74
|
|
|
49
|
-
// 4. Form full path
|
|
50
75
|
const fullPath = path.join(...stack);
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
76
|
+
|
|
77
|
+
result.push({
|
|
78
|
+
name: cleanName,
|
|
79
|
+
depth,
|
|
80
|
+
isDirectory,
|
|
81
|
+
fullPath
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Parse one line of a tree.
|
|
90
|
+
*/
|
|
91
|
+
function parseLine(line) {
|
|
92
|
+
// Root:
|
|
93
|
+
// project/
|
|
94
|
+
if (!/^[│|├└+`]/.test(line)) {
|
|
95
|
+
return {
|
|
96
|
+
name: line.trim(),
|
|
97
|
+
depth: 0
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* Match tree indentation.
|
|
103
|
+
*
|
|
104
|
+
* Examples:
|
|
105
|
+
*
|
|
106
|
+
* ├── src/
|
|
107
|
+
* └── package.json
|
|
108
|
+
*
|
|
109
|
+
* │ ├── components/
|
|
110
|
+
* │ └── App.jsx
|
|
111
|
+
*
|
|
112
|
+
* │ │ └── Button.jsx
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
const match = line.match(/^((?:│ | )*)(?:├── |└── |\+-- |`-- )(.*)$/);
|
|
116
|
+
|
|
117
|
+
if (!match) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const indentation = match[1];
|
|
122
|
+
const name = match[2].trim();
|
|
123
|
+
|
|
124
|
+
// Every 4-character indentation block = one level.
|
|
125
|
+
const depth = indentation.length / 4 + 1;
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
name,
|
|
129
|
+
depth
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Determine whether an item is a directory.
|
|
135
|
+
*
|
|
136
|
+
* Best case: directories end with /.
|
|
137
|
+
*
|
|
138
|
+
* For trees that don't include /, this falls back to
|
|
139
|
+
* extension-based detection.
|
|
140
|
+
*/
|
|
141
|
+
function detectDirectory(name) {
|
|
142
|
+
// Explicit directory marker
|
|
143
|
+
if (name.endsWith('/')) {
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Hidden files like .gitignore are files
|
|
148
|
+
if (name.startsWith('.')) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Files with extensions are files
|
|
153
|
+
if (path.extname(name) !== '') {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Otherwise assume directory
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Parse tree and create files/directories.
|
|
163
|
+
*/
|
|
164
|
+
async function parseAndCreateTree(text) {
|
|
165
|
+
const created = [];
|
|
166
|
+
const errors = [];
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const treeStructure = parseTree(text);
|
|
170
|
+
|
|
171
|
+
for (const item of treeStructure) {
|
|
172
|
+
try {
|
|
173
|
+
if (item.isDirectory) {
|
|
174
|
+
fs.mkdirSync(item.fullPath, {
|
|
175
|
+
recursive: true
|
|
176
|
+
});
|
|
177
|
+
} else {
|
|
178
|
+
const parentDir = path.dirname(item.fullPath);
|
|
179
|
+
|
|
180
|
+
fs.mkdirSync(parentDir, {
|
|
181
|
+
recursive: true
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
fs.writeFileSync(item.fullPath, '');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
created.push(item.fullPath);
|
|
188
|
+
} catch (err) {
|
|
189
|
+
errors.push(
|
|
190
|
+
`Failed to create ${item.fullPath}: ${err.message}`
|
|
191
|
+
);
|
|
59
192
|
}
|
|
60
|
-
created.push(fullPath);
|
|
61
|
-
} catch (err) {
|
|
62
|
-
errors.push(`Failed to create ${fullPath}: ${err.message}`);
|
|
63
193
|
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (err instanceof TreeParseError) {
|
|
196
|
+
throw err;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
throw new Error(`Unexpected error: ${err.message}`);
|
|
64
200
|
}
|
|
65
201
|
|
|
66
|
-
return {
|
|
202
|
+
return {
|
|
203
|
+
created,
|
|
204
|
+
errors
|
|
205
|
+
};
|
|
67
206
|
}
|
|
68
207
|
|
|
69
208
|
module.exports = {
|
|
70
|
-
parseAndCreateTree
|
|
71
|
-
|
|
209
|
+
parseAndCreateTree,
|
|
210
|
+
parseTree,
|
|
211
|
+
TreeParseError
|
|
212
|
+
};
|