pulse-js-framework 1.2.0 → 1.4.1

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.
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Pulse CLI - File Utilities
3
+ * Shared utilities for file discovery and glob pattern matching
4
+ */
5
+
6
+ import { readdirSync, statSync, existsSync } from 'fs';
7
+ import { join, resolve, dirname, extname } from 'path';
8
+
9
+ /**
10
+ * Find .pulse files matching the given patterns
11
+ * @param {string[]} patterns - Glob patterns or file paths
12
+ * @param {object} options - Options
13
+ * @param {string[]} options.extensions - File extensions to match (default: ['.pulse'])
14
+ * @returns {string[]} Array of absolute file paths
15
+ */
16
+ export function findPulseFiles(patterns, options = {}) {
17
+ const { extensions = ['.pulse'] } = options;
18
+ const files = new Set();
19
+ const root = process.cwd();
20
+
21
+ // Default to current directory if no patterns
22
+ if (patterns.length === 0) {
23
+ patterns = ['.'];
24
+ }
25
+
26
+ for (const pattern of patterns) {
27
+ // Skip options (starting with -)
28
+ if (pattern.startsWith('-')) continue;
29
+
30
+ if (pattern.includes('*')) {
31
+ // Glob pattern
32
+ const matches = globMatch(root, pattern, extensions);
33
+ for (const match of matches) {
34
+ files.add(match);
35
+ }
36
+ } else {
37
+ const fullPath = resolve(root, pattern);
38
+ if (existsSync(fullPath)) {
39
+ const stat = statSync(fullPath);
40
+ if (stat.isDirectory()) {
41
+ // Directory - find all matching files
42
+ walkDir(fullPath, files, extensions);
43
+ } else if (extensions.some(ext => fullPath.endsWith(ext))) {
44
+ files.add(fullPath);
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ return Array.from(files).sort();
51
+ }
52
+
53
+ /**
54
+ * Simple glob matching implementation
55
+ * Supports: **, *, ?
56
+ */
57
+ function globMatch(base, pattern, extensions) {
58
+ const results = [];
59
+ const parts = pattern.split('/').filter(p => p !== '');
60
+
61
+ function match(dir, partIndex) {
62
+ if (!existsSync(dir)) return;
63
+ if (partIndex >= parts.length) return;
64
+
65
+ const part = parts[partIndex];
66
+ const isLast = partIndex === parts.length - 1;
67
+
68
+ if (part === '**') {
69
+ // Match any depth
70
+ if (isLast) {
71
+ // **/*.pulse at the end
72
+ const nextPart = parts[partIndex + 1];
73
+ if (nextPart) {
74
+ matchFilesInDirRecursive(dir, nextPart, extensions, results);
75
+ } else {
76
+ walkDir(dir, results, extensions);
77
+ }
78
+ } else {
79
+ // Continue matching at this level and deeper
80
+ match(dir, partIndex + 1);
81
+ try {
82
+ for (const entry of readdirSync(dir)) {
83
+ const full = join(dir, entry);
84
+ try {
85
+ if (statSync(full).isDirectory()) {
86
+ match(full, partIndex);
87
+ }
88
+ } catch (e) {
89
+ // Skip inaccessible directories
90
+ }
91
+ }
92
+ } catch (e) {
93
+ // Skip inaccessible directories
94
+ }
95
+ }
96
+ } else if (part.includes('*') || part.includes('?')) {
97
+ // Wildcard in filename
98
+ const regex = globToRegex(part);
99
+ try {
100
+ for (const entry of readdirSync(dir)) {
101
+ const full = join(dir, entry);
102
+ if (regex.test(entry)) {
103
+ try {
104
+ const stat = statSync(full);
105
+ if (isLast) {
106
+ if (stat.isFile() && extensions.some(ext => full.endsWith(ext))) {
107
+ results.push(full);
108
+ }
109
+ } else if (stat.isDirectory()) {
110
+ match(full, partIndex + 1);
111
+ }
112
+ } catch (e) {
113
+ // Skip inaccessible files
114
+ }
115
+ }
116
+ }
117
+ } catch (e) {
118
+ // Skip inaccessible directories
119
+ }
120
+ } else {
121
+ // Exact match
122
+ const full = join(dir, part);
123
+ if (existsSync(full)) {
124
+ try {
125
+ const stat = statSync(full);
126
+ if (isLast) {
127
+ if (stat.isFile()) {
128
+ results.push(full);
129
+ } else if (stat.isDirectory()) {
130
+ // If last part is a directory, walk it
131
+ walkDir(full, results, extensions);
132
+ }
133
+ } else if (stat.isDirectory()) {
134
+ match(full, partIndex + 1);
135
+ }
136
+ } catch (e) {
137
+ // Skip inaccessible files
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ match(base, 0);
144
+ return results;
145
+ }
146
+
147
+ /**
148
+ * Match files recursively with a glob pattern
149
+ */
150
+ function matchFilesInDirRecursive(dir, pattern, extensions, results) {
151
+ const regex = globToRegex(pattern);
152
+
153
+ function walk(currentDir) {
154
+ try {
155
+ for (const entry of readdirSync(currentDir)) {
156
+ const full = join(currentDir, entry);
157
+ try {
158
+ const stat = statSync(full);
159
+ if (stat.isDirectory()) {
160
+ walk(full);
161
+ } else if (regex.test(entry) && extensions.some(ext => full.endsWith(ext))) {
162
+ results.push(full);
163
+ }
164
+ } catch (e) {
165
+ // Skip inaccessible files
166
+ }
167
+ }
168
+ } catch (e) {
169
+ // Skip inaccessible directories
170
+ }
171
+ }
172
+
173
+ walk(dir);
174
+ }
175
+
176
+ /**
177
+ * Convert glob pattern to regex
178
+ */
179
+ function globToRegex(pattern) {
180
+ const escaped = pattern
181
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
182
+ .replace(/\*/g, '.*')
183
+ .replace(/\?/g, '.');
184
+ return new RegExp('^' + escaped + '$');
185
+ }
186
+
187
+ /**
188
+ * Walk directory recursively and collect files
189
+ */
190
+ function walkDir(dir, results, extensions) {
191
+ try {
192
+ for (const entry of readdirSync(dir)) {
193
+ // Skip hidden files and node_modules
194
+ if (entry.startsWith('.') || entry === 'node_modules') continue;
195
+
196
+ const full = join(dir, entry);
197
+ try {
198
+ const stat = statSync(full);
199
+ if (stat.isDirectory()) {
200
+ walkDir(full, results, extensions);
201
+ } else if (extensions.some(ext => full.endsWith(ext))) {
202
+ if (results instanceof Set) {
203
+ results.add(full);
204
+ } else {
205
+ results.push(full);
206
+ }
207
+ }
208
+ } catch (e) {
209
+ // Skip inaccessible files
210
+ }
211
+ }
212
+ } catch (e) {
213
+ // Skip inaccessible directories
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Resolve import path relative to importing file
219
+ * @param {string} fromFile - The file containing the import
220
+ * @param {string} importPath - The import path
221
+ * @returns {string|null} Resolved absolute path or null if not found
222
+ */
223
+ export function resolveImportPath(fromFile, importPath) {
224
+ if (!importPath.startsWith('.')) {
225
+ return null; // External or node_modules import
226
+ }
227
+
228
+ const dir = dirname(fromFile);
229
+ let resolved = resolve(dir, importPath);
230
+
231
+ // Try with extensions
232
+ const extensions = ['.pulse', '.js', '/index.pulse', '/index.js'];
233
+ for (const ext of extensions) {
234
+ const withExt = resolved + ext;
235
+ if (existsSync(withExt)) {
236
+ return withExt;
237
+ }
238
+ }
239
+
240
+ // Try exact path
241
+ if (existsSync(resolved)) {
242
+ return resolved;
243
+ }
244
+
245
+ return null;
246
+ }
247
+
248
+ /**
249
+ * Parse CLI arguments into options and file patterns
250
+ * @param {string[]} args - Command line arguments
251
+ * @returns {{ options: object, patterns: string[] }}
252
+ */
253
+ export function parseArgs(args) {
254
+ const options = {};
255
+ const patterns = [];
256
+
257
+ for (let i = 0; i < args.length; i++) {
258
+ const arg = args[i];
259
+ if (arg.startsWith('--')) {
260
+ const key = arg.slice(2);
261
+ // Boolean flags - don't consume next argument
262
+ options[key] = true;
263
+ } else if (arg.startsWith('-') && arg.length === 2) {
264
+ const key = arg.slice(1);
265
+ options[key] = true;
266
+ } else {
267
+ patterns.push(arg);
268
+ }
269
+ }
270
+
271
+ return { options, patterns };
272
+ }
273
+
274
+ /**
275
+ * Format file size in human readable format
276
+ * @param {number} bytes - Size in bytes
277
+ * @returns {string} Formatted size
278
+ */
279
+ export function formatBytes(bytes) {
280
+ if (bytes === 0) return '0 B';
281
+ const k = 1024;
282
+ const sizes = ['B', 'KB', 'MB', 'GB'];
283
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
284
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
285
+ }
286
+
287
+ /**
288
+ * Get relative path from current working directory
289
+ * @param {string} absolutePath - Absolute file path
290
+ * @returns {string} Relative path
291
+ */
292
+ export function relativePath(absolutePath) {
293
+ const cwd = process.cwd();
294
+ if (absolutePath.startsWith(cwd)) {
295
+ return absolutePath.slice(cwd.length + 1);
296
+ }
297
+ return absolutePath;
298
+ }