pulse-js-framework 1.4.1 → 1.4.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 +414 -414
- package/cli/analyze.js +499 -499
- package/cli/build.js +341 -341
- package/cli/format.js +704 -704
- package/cli/index.js +398 -398
- package/cli/lint.js +642 -642
- package/cli/utils/file-utils.js +298 -298
- package/compiler/lexer.js +766 -766
- package/compiler/parser.js +1797 -1797
- package/compiler/transformer.js +1332 -1332
- package/index.js +1 -1
- package/package.json +68 -68
- package/runtime/router.js +596 -596
package/cli/utils/file-utils.js
CHANGED
|
@@ -1,298 +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
|
-
}
|
|
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
|
+
}
|