@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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/tree.js +183 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@himamshus06/git-auto",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
- * Parses a visual directory tree and creates the corresponding directories.
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
- async function parseAndCreateTree(text) {
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 Error('No tree provided');
19
+ throw new TreeParseError('No tree provided');
12
20
  }
13
21
 
14
- const lines = text.split('\n').filter(line => line.trim() !== '');
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 (const line of lines) {
20
- // 1. Determine depth
21
- // We find the first character that isn't a tree symbol or whitespace
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 prefix = match[1];
26
- let name = match[2].trim();
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
- if (!name) continue;
42
+ const { name, depth } = parsed;
29
43
 
30
- // Depth is determined by the number of 4-character blocks in the prefix.
31
- // For most visual trees, each level of nesting is 4 characters (e.g., "│ " or "├── ").
32
- // We strip the final tree symbol (├ or └) from the length if it's there.
33
- const effectivePrefixLength = prefix.endsWith('├') || prefix.endsWith('└')
34
- ? prefix.length - 1
35
- : prefix.length;
36
- const depth = Math.floor(effectivePrefixLength / 4);
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
- // 2. Adjust stack to current depth
60
+ // Remove anything deeper than the current item
39
61
  while (stack.length > depth) {
40
62
  stack.pop();
41
63
  }
42
64
 
43
- // 3. Clean name (remove trailing slash for directory creation)
44
- const isDirectory = name.endsWith('/') || !name.includes('.');
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
- try {
52
- if (isDirectory) {
53
- fs.mkdirSync(fullPath, { recursive: true });
54
- } else {
55
- // Ensure parent directory exists
56
- const parentDir = path.dirname(fullPath);
57
- fs.mkdirSync(parentDir, { recursive: true });
58
- fs.writeFileSync(fullPath, ''); // Create empty file
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 { created, errors };
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
+ };