pulse-js-framework 1.11.3 → 1.11.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 (48) hide show
  1. package/cli/analyze.js +21 -8
  2. package/cli/build.js +83 -56
  3. package/cli/dev.js +108 -94
  4. package/cli/docs-test.js +52 -33
  5. package/cli/index.js +81 -51
  6. package/cli/mobile.js +92 -40
  7. package/cli/release.js +64 -46
  8. package/cli/scaffold.js +14 -13
  9. package/compiler/lexer.js +55 -54
  10. package/compiler/parser/core.js +1 -0
  11. package/compiler/parser/state.js +6 -12
  12. package/compiler/parser/style.js +17 -20
  13. package/compiler/parser/view.js +1 -3
  14. package/compiler/preprocessor.js +124 -262
  15. package/compiler/sourcemap.js +10 -4
  16. package/compiler/transformer/expressions.js +122 -106
  17. package/compiler/transformer/index.js +2 -4
  18. package/compiler/transformer/style.js +74 -7
  19. package/compiler/transformer/view.js +86 -36
  20. package/loader/esbuild-plugin-server-components.js +209 -0
  21. package/loader/esbuild-plugin.js +41 -93
  22. package/loader/parcel-plugin.js +37 -97
  23. package/loader/rollup-plugin-server-components.js +30 -169
  24. package/loader/rollup-plugin.js +27 -78
  25. package/loader/shared.js +362 -0
  26. package/loader/swc-plugin.js +65 -82
  27. package/loader/vite-plugin-server-components.js +30 -171
  28. package/loader/vite-plugin.js +25 -10
  29. package/loader/webpack-loader-server-components.js +21 -134
  30. package/loader/webpack-loader.js +25 -80
  31. package/package.json +52 -12
  32. package/runtime/dom-selector.js +2 -1
  33. package/runtime/form.js +4 -3
  34. package/runtime/http.js +6 -1
  35. package/runtime/logger.js +44 -24
  36. package/runtime/router/utils.js +14 -7
  37. package/runtime/security.js +13 -1
  38. package/runtime/server-components/actions-server.js +23 -19
  39. package/runtime/server-components/error-sanitizer.js +18 -18
  40. package/runtime/server-components/security.js +41 -24
  41. package/runtime/ssr-preload.js +5 -3
  42. package/runtime/testing.js +759 -0
  43. package/runtime/utils.js +3 -2
  44. package/server/utils.js +15 -9
  45. package/sw/index.js +2 -0
  46. package/types/loaders.d.ts +1043 -0
  47. package/compiler/parser/_extract.js +0 -393
  48. package/loader/README.md +0 -509
package/cli/analyze.js CHANGED
@@ -3,7 +3,7 @@
3
3
  * Analyzes bundle size and dependencies
4
4
  */
5
5
 
6
- import { readFileSync, statSync, existsSync } from 'fs';
6
+ import { readFileSync, statSync, readdirSync } from 'fs';
7
7
  import { join, dirname, basename, relative } from 'path';
8
8
  import { findPulseFiles, parseArgs, formatBytes, relativePath, resolveImportPath } from './utils/file-utils.js';
9
9
  import { log } from './logger.js';
@@ -82,12 +82,20 @@ async function analyzeFiles(files, parse) {
82
82
 
83
83
  for (const file of files) {
84
84
  try {
85
- const stats = statSync(file);
86
- const source = readFileSync(file, 'utf-8');
85
+ let source;
86
+ let fileSize;
87
+ try {
88
+ const buffer = readFileSync(file);
89
+ source = buffer.toString('utf-8');
90
+ fileSize = buffer.length;
91
+ } catch (e) {
92
+ if (e.code !== 'ENOENT') throw e;
93
+ continue;
94
+ }
87
95
 
88
96
  const info = {
89
97
  path: relativePath(file),
90
- size: stats.size,
98
+ size: fileSize,
91
99
  sizeFormatted: formatBytes(stats.size),
92
100
  lines: source.split('\n').length
93
101
  };
@@ -548,10 +556,15 @@ export async function runAnalyze(args) {
548
556
  const root = patterns[0] || '.';
549
557
 
550
558
  // Check if src directory exists
551
- if (!existsSync(join(root, 'src'))) {
552
- log.error('Error: No src/ directory found.');
553
- log.info('Run this command from your Pulse project root.');
554
- process.exit(1);
559
+ try {
560
+ readdirSync(join(root, 'src'));
561
+ } catch (err) {
562
+ if (err.code === 'ENOENT' || err.code === 'ENOTDIR') {
563
+ log.error('Error: No src/ directory found.');
564
+ log.info('Run this command from your Pulse project root.');
565
+ process.exit(1);
566
+ }
567
+ throw err;
555
568
  }
556
569
 
557
570
  const timer = createTimer();
package/cli/build.js CHANGED
@@ -4,7 +4,7 @@
4
4
  * Builds Pulse projects for production
5
5
  */
6
6
 
7
- import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, copyFileSync } from 'fs';
7
+ import { readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync } from 'fs';
8
8
  import { join, extname, relative, dirname } from 'path';
9
9
  import { compile } from '../compiler/index.js';
10
10
  import { preprocessStylesSync, isSassAvailable, getSassVersion } from '../compiler/preprocessor.js';
@@ -39,13 +39,12 @@ export async function buildProject(args) {
39
39
  // Check if vite is available
40
40
  try {
41
41
  const viteConfig = join(root, 'vite.config.js');
42
- if (existsSync(viteConfig)) {
43
- const spinner = createSpinner('Building with Vite...');
44
- const { build } = await import('vite');
45
- await build({ root });
46
- spinner.success(`Built with Vite in ${timer.format()}`);
47
- return;
48
- }
42
+ readFileSync(viteConfig, 'utf-8'); // Throws ENOENT if missing
43
+ const spinner = createSpinner('Building with Vite...');
44
+ const { build } = await import('vite');
45
+ await build({ root });
46
+ spinner.success(`Built with Vite in ${timer.format()}`);
47
+ return;
49
48
  } catch (e) {
50
49
  // Vite not available, use built-in build
51
50
  }
@@ -60,22 +59,29 @@ export async function buildProject(args) {
60
59
  }
61
60
 
62
61
  // Create output directory
63
- if (!existsSync(outDir)) {
64
- mkdirSync(outDir, { recursive: true });
65
- }
62
+ mkdirSync(outDir, { recursive: true });
66
63
 
67
64
  // Copy public files
68
65
  const publicDir = join(root, 'public');
69
- if (existsSync(publicDir)) {
66
+ try {
67
+ readdirSync(publicDir);
70
68
  copyDir(publicDir, outDir);
69
+ } catch (e) {
70
+ if (e.code !== 'ENOENT') throw e;
71
+ // No public directory, skip
71
72
  }
72
73
 
73
74
  // Count files for progress bar
74
75
  const srcDir = join(root, 'src');
75
- const fileCount = existsSync(srcDir) ? countFiles(srcDir) : 0;
76
+ let fileCount = 0;
77
+ try {
78
+ fileCount = countFiles(srcDir);
79
+ } catch (e) {
80
+ if (e.code !== 'ENOENT') throw e;
81
+ }
76
82
 
77
83
  // Process source files with progress bar
78
- if (existsSync(srcDir) && fileCount > 0) {
84
+ if (fileCount > 0) {
79
85
  const progress = createProgressBar({
80
86
  total: fileCount,
81
87
  label: 'Compiling',
@@ -87,7 +93,7 @@ export async function buildProject(args) {
87
93
 
88
94
  // Copy and process index.html
89
95
  const indexHtml = join(root, 'index.html');
90
- if (existsSync(indexHtml)) {
96
+ try {
91
97
  let html = readFileSync(indexHtml, 'utf-8');
92
98
 
93
99
  // Update script paths for production
@@ -100,6 +106,9 @@ export async function buildProject(args) {
100
106
  html = html.replace(/\.pulse"/g, '.js"');
101
107
 
102
108
  writeFileSync(join(outDir, 'index.html'), html);
109
+ } catch (e) {
110
+ if (e.code !== 'ENOENT') throw e;
111
+ // No index.html, skip
103
112
  }
104
113
 
105
114
  // Bundle runtime
@@ -146,13 +155,11 @@ To preview:
146
155
  */
147
156
  function countFiles(dir) {
148
157
  let count = 0;
149
- const files = readdirSync(dir);
158
+ const entries = readdirSync(dir, { withFileTypes: true });
150
159
 
151
- for (const file of files) {
152
- const fullPath = join(dir, file);
153
- const stat = statSync(fullPath);
154
- if (stat.isDirectory()) {
155
- count += countFiles(fullPath);
160
+ for (const entry of entries) {
161
+ if (entry.isDirectory()) {
162
+ count += countFiles(join(dir, entry.name));
156
163
  } else {
157
164
  count++;
158
165
  }
@@ -168,21 +175,25 @@ function countFiles(dir) {
168
175
  * @param {Object} [progress] - Progress bar instance
169
176
  */
170
177
  function processDirectory(srcDir, outDir, progress = null) {
171
- if (!existsSync(outDir)) {
172
- mkdirSync(outDir, { recursive: true });
173
- }
178
+ mkdirSync(outDir, { recursive: true });
174
179
 
175
- const files = readdirSync(srcDir);
180
+ const entries = readdirSync(srcDir, { withFileTypes: true });
176
181
 
177
- for (const file of files) {
182
+ for (const entry of entries) {
183
+ const file = entry.name;
178
184
  const srcPath = join(srcDir, file);
179
- const stat = statSync(srcPath);
180
185
 
181
- if (stat.isDirectory()) {
186
+ if (entry.isDirectory()) {
182
187
  processDirectory(srcPath, join(outDir, file), progress);
183
188
  } else if (file.endsWith('.pulse')) {
184
189
  // Compile .pulse files
185
- const source = readFileSync(srcPath, 'utf-8');
190
+ let source;
191
+ try {
192
+ source = readFileSync(srcPath, 'utf-8');
193
+ } catch (e) {
194
+ if (e.code !== 'ENOENT') throw e;
195
+ continue;
196
+ }
186
197
  const result = compile(source, {
187
198
  runtime: './runtime.js',
188
199
  minify: true
@@ -212,7 +223,13 @@ function processDirectory(srcDir, outDir, progress = null) {
212
223
  }
213
224
 
214
225
  const outPath = join(outDir, file.replace('.pulse', '.js'));
215
- writeFileSync(outPath, code);
226
+ try {
227
+ writeFileSync(outPath, code);
228
+ } catch (writeErr) {
229
+ log.error(` Failed to write ${outPath}: ${writeErr.message}`);
230
+ if (progress) progress.tick();
231
+ continue;
232
+ }
216
233
  } else {
217
234
  log.error(` Error compiling ${file}:`);
218
235
  for (const error of result.errors) {
@@ -222,7 +239,13 @@ function processDirectory(srcDir, outDir, progress = null) {
222
239
  if (progress) progress.tick();
223
240
  } else if (file.endsWith('.js') || file.endsWith('.mjs')) {
224
241
  // Process JS files - rewrite imports
225
- let content = readFileSync(srcPath, 'utf-8');
242
+ let content;
243
+ try {
244
+ content = readFileSync(srcPath, 'utf-8');
245
+ } catch (e) {
246
+ if (e.code !== 'ENOENT') throw e;
247
+ continue;
248
+ }
226
249
 
227
250
  // Rewrite .pulse imports to .js
228
251
  content = content.replace(/from\s+['"]([^'"]+)\.pulse['"]/g, "from '$1.js'");
@@ -283,8 +306,10 @@ function readRuntimeFile(filename) {
283
306
  ];
284
307
 
285
308
  for (const path of paths) {
286
- if (existsSync(path)) {
309
+ try {
287
310
  return readFileSync(path, 'utf-8');
311
+ } catch (e) {
312
+ if (e.code !== 'ENOENT') throw e;
288
313
  }
289
314
  }
290
315
 
@@ -421,18 +446,15 @@ export function minifyJS(code) {
421
446
  * Copy a directory recursively
422
447
  */
423
448
  function copyDir(src, dest) {
424
- if (!existsSync(dest)) {
425
- mkdirSync(dest, { recursive: true });
426
- }
449
+ mkdirSync(dest, { recursive: true });
427
450
 
428
- const files = readdirSync(src);
451
+ const entries = readdirSync(src, { withFileTypes: true });
429
452
 
430
- for (const file of files) {
431
- const srcPath = join(src, file);
432
- const destPath = join(dest, file);
433
- const stat = statSync(srcPath);
453
+ for (const entry of entries) {
454
+ const srcPath = join(src, entry.name);
455
+ const destPath = join(dest, entry.name);
434
456
 
435
- if (stat.isDirectory()) {
457
+ if (entry.isDirectory()) {
436
458
  copyDir(srcPath, destPath);
437
459
  } else {
438
460
  copyFileSync(srcPath, destPath);
@@ -448,24 +470,28 @@ export async function previewBuild(args) {
448
470
  const root = process.cwd();
449
471
  const distDir = join(root, 'dist');
450
472
 
451
- if (!existsSync(distDir)) {
452
- log.error('No dist folder found. Run "pulse build" first.');
453
- process.exit(1);
473
+ try {
474
+ readdirSync(distDir);
475
+ } catch (e) {
476
+ if (e.code === 'ENOENT' || e.code === 'ENOTDIR') {
477
+ log.error('No dist folder found. Run "pulse build" first.');
478
+ process.exit(1);
479
+ }
480
+ throw e;
454
481
  }
455
482
 
456
483
  // Check if vite is available for preview
457
484
  try {
458
485
  const viteConfig = join(root, 'vite.config.js');
459
- if (existsSync(viteConfig)) {
460
- log.info('Using Vite preview...');
461
- const { preview } = await import('vite');
462
- const server = await preview({
463
- root,
464
- preview: { port }
465
- });
466
- server.printUrls();
467
- return;
468
- }
486
+ readFileSync(viteConfig, 'utf-8'); // Throws ENOENT if missing
487
+ log.info('Using Vite preview...');
488
+ const { preview } = await import('vite');
489
+ const server = await preview({
490
+ root,
491
+ preview: { port }
492
+ });
493
+ server.printUrls();
494
+ return;
469
495
  } catch (e) {
470
496
  // Vite not available, use built-in server
471
497
  }
@@ -497,7 +523,7 @@ export async function previewBuild(args) {
497
523
 
498
524
  const filePath = join(distDir, pathname);
499
525
 
500
- if (existsSync(filePath) && statSync(filePath).isFile()) {
526
+ try {
501
527
  const ext = filePath.substring(filePath.lastIndexOf('.'));
502
528
  const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
503
529
 
@@ -506,7 +532,8 @@ export async function previewBuild(args) {
506
532
  'Cache-Control': 'public, max-age=31536000'
507
533
  });
508
534
  res.end(readFileSync(filePath));
509
- } else {
535
+ } catch (e) {
536
+ if (e.code !== 'ENOENT') throw e;
510
537
  res.writeHead(404, { 'Content-Type': 'text/plain' });
511
538
  res.end('Not Found');
512
539
  }
package/cli/dev.js CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import { createServer } from 'http';
8
- import { readFileSync, existsSync, statSync, watch } from 'fs';
8
+ import { readFileSync, watch } from 'fs';
9
9
  import { join, extname, resolve, dirname } from 'path';
10
10
  import { compile } from '../compiler/index.js';
11
11
  import { preprocessStylesSync, isSassAvailable, getSassVersion } from '../compiler/preprocessor.js';
@@ -76,17 +76,16 @@ export async function startDevServer(args) {
76
76
  // Check if vite is available, use it if so
77
77
  try {
78
78
  const viteConfig = join(root, 'vite.config.js');
79
- if (existsSync(viteConfig)) {
80
- log.info('Vite config detected, using Vite...');
81
- const { createServer: createViteServer } = await import('vite');
82
- const server = await createViteServer({
83
- root,
84
- server: { port }
85
- });
86
- await server.listen();
87
- server.printUrls();
88
- return;
89
- }
79
+ readFileSync(viteConfig, 'utf-8'); // Throws ENOENT if missing
80
+ log.info('Vite config detected, using Vite...');
81
+ const { createServer: createViteServer } = await import('vite');
82
+ const server = await createViteServer({
83
+ root,
84
+ server: { port }
85
+ });
86
+ await server.listen();
87
+ server.printUrls();
88
+ return;
90
89
  } catch (e) {
91
90
  // Vite not available, use built-in server
92
91
  }
@@ -128,60 +127,61 @@ export async function startDevServer(args) {
128
127
 
129
128
  // Check for .pulse files and compile them
130
129
  if (pathname.endsWith('.pulse')) {
131
- if (existsSync(filePath)) {
132
- try {
133
- const source = readFileSync(filePath, 'utf-8');
134
- const result = compile(source, {
135
- runtime: '/runtime/index.js',
136
- sourceMap: true,
137
- inlineSourceMap: true,
138
- sourceFileName: pathname
139
- });
130
+ try {
131
+ const source = readFileSync(filePath, 'utf-8');
132
+ const result = compile(source, {
133
+ runtime: '/runtime/index.js',
134
+ sourceMap: true,
135
+ inlineSourceMap: true,
136
+ sourceFileName: pathname
137
+ });
140
138
 
141
- if (result.success) {
142
- let code = result.code;
143
-
144
- // Preprocess SASS/SCSS if available
145
- if (sassAvailable) {
146
- const stylesMatch = code.match(/const styles = `([\s\S]*?)`;/);
147
- if (stylesMatch) {
148
- try {
149
- const preprocessed = preprocessStylesSync(stylesMatch[1], {
150
- filename: filePath,
151
- loadPaths: [dirname(filePath)]
152
- });
153
- if (preprocessed.wasSass) {
154
- code = code.replace(stylesMatch[0], `const styles = \`${preprocessed.css}\`;`);
155
- }
156
- } catch (sassError) {
157
- console.warn(`[Pulse] SASS warning: ${sassError.message}`);
139
+ if (result.success) {
140
+ let code = result.code;
141
+
142
+ // Preprocess SASS/SCSS if available
143
+ if (sassAvailable) {
144
+ const stylesMatch = code.match(/const styles = `([\s\S]*?)`;/);
145
+ if (stylesMatch) {
146
+ try {
147
+ const preprocessed = preprocessStylesSync(stylesMatch[1], {
148
+ filename: filePath,
149
+ loadPaths: [dirname(filePath)]
150
+ });
151
+ if (preprocessed.wasSass) {
152
+ code = code.replace(stylesMatch[0], `const styles = \`${preprocessed.css}\`;`);
158
153
  }
154
+ } catch (sassError) {
155
+ console.warn(`[Pulse] SASS warning: ${sassError.message}`);
159
156
  }
160
157
  }
161
-
162
- res.writeHead(200, {
163
- 'Content-Type': 'application/javascript',
164
- 'Cache-Control': 'no-cache, no-store, must-revalidate'
165
- });
166
- res.end(code);
167
- } else {
168
- const errorDetails = result.errors.map(e => `${e.message} at line ${e.line || '?'}:${e.column || '?'}`).join('\n');
169
- console.error(`[Pulse] Compilation error in ${filePath}:`, result.errors);
170
- res.writeHead(500, { 'Content-Type': 'text/plain' });
171
- res.end(`Compilation error: ${errorDetails}\nFile: ${filePath}`);
172
158
  }
173
- } catch (error) {
159
+
160
+ res.writeHead(200, {
161
+ 'Content-Type': 'application/javascript',
162
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
163
+ });
164
+ res.end(code);
165
+ } else {
166
+ const errorDetails = result.errors.map(e => `${e.message} at line ${e.line || '?'}:${e.column || '?'}`).join('\n');
167
+ console.error(`[Pulse] Compilation error in ${filePath}:`, result.errors);
168
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
169
+ res.end(`Compilation error: ${errorDetails}\nFile: ${filePath}`);
170
+ }
171
+ return;
172
+ } catch (error) {
173
+ if (error.code !== 'ENOENT') {
174
174
  console.error(`[Pulse] Error compiling ${filePath}:`, error);
175
175
  res.writeHead(500, { 'Content-Type': 'text/plain' });
176
176
  res.end(`Error: ${error.message}\nFile: ${filePath}`);
177
+ return;
177
178
  }
178
- return;
179
179
  }
180
180
  }
181
181
 
182
182
  // Serve JS files
183
183
  if (pathname.endsWith('.js') || pathname.endsWith('.mjs')) {
184
- if (existsSync(filePath)) {
184
+ try {
185
185
  const content = readFileSync(filePath, 'utf-8');
186
186
  res.writeHead(200, {
187
187
  'Content-Type': 'application/javascript',
@@ -189,62 +189,65 @@ export async function startDevServer(args) {
189
189
  });
190
190
  res.end(content);
191
191
  return;
192
+ } catch (e) {
193
+ if (e.code !== 'ENOENT') throw e;
192
194
  }
193
195
 
194
196
  // Check if there's a .pulse file that should be compiled to .js
195
197
  const pulseFilePath = filePath.replace(/\.(js|mjs)$/, '.pulse');
196
- if (existsSync(pulseFilePath)) {
197
- try {
198
- const source = readFileSync(pulseFilePath, 'utf-8');
199
- const result = compile(source, {
200
- runtime: '/runtime/index.js',
201
- sourceMap: true,
202
- inlineSourceMap: true,
203
- sourceFileName: pulseFilePath.replace(root, '')
204
- });
198
+ try {
199
+ const source = readFileSync(pulseFilePath, 'utf-8');
200
+ const result = compile(source, {
201
+ runtime: '/runtime/index.js',
202
+ sourceMap: true,
203
+ inlineSourceMap: true,
204
+ sourceFileName: pulseFilePath.replace(root, '')
205
+ });
205
206
 
206
- if (result.success) {
207
- let code = result.code;
208
-
209
- // Preprocess SASS/SCSS if available
210
- if (sassAvailable) {
211
- const stylesMatch = code.match(/const styles = `([\s\S]*?)`;/);
212
- if (stylesMatch) {
213
- try {
214
- const preprocessed = preprocessStylesSync(stylesMatch[1], {
215
- filename: pulseFilePath,
216
- loadPaths: [dirname(pulseFilePath)]
217
- });
218
- if (preprocessed.wasSass) {
219
- code = code.replace(stylesMatch[0], `const styles = \`${preprocessed.css}\`;`);
220
- }
221
- } catch (sassError) {
222
- console.warn(`[Pulse] SASS warning: ${sassError.message}`);
207
+ if (result.success) {
208
+ let code = result.code;
209
+
210
+ // Preprocess SASS/SCSS if available
211
+ if (sassAvailable) {
212
+ const stylesMatch = code.match(/const styles = `([\s\S]*?)`;/);
213
+ if (stylesMatch) {
214
+ try {
215
+ const preprocessed = preprocessStylesSync(stylesMatch[1], {
216
+ filename: pulseFilePath,
217
+ loadPaths: [dirname(pulseFilePath)]
218
+ });
219
+ if (preprocessed.wasSass) {
220
+ code = code.replace(stylesMatch[0], `const styles = \`${preprocessed.css}\`;`);
223
221
  }
222
+ } catch (sassError) {
223
+ console.warn(`[Pulse] SASS warning: ${sassError.message}`);
224
224
  }
225
225
  }
226
-
227
- res.writeHead(200, {
228
- 'Content-Type': 'application/javascript',
229
- 'Cache-Control': 'no-cache, no-store, must-revalidate'
230
- });
231
- res.end(code);
232
- } else {
233
- res.writeHead(500, { 'Content-Type': 'text/plain' });
234
- res.end(`Compilation error: ${result.errors.map(e => e.message).join('\n')}`);
235
226
  }
236
- } catch (error) {
227
+
228
+ res.writeHead(200, {
229
+ 'Content-Type': 'application/javascript',
230
+ 'Cache-Control': 'no-cache, no-store, must-revalidate'
231
+ });
232
+ res.end(code);
233
+ } else {
237
234
  res.writeHead(500, { 'Content-Type': 'text/plain' });
238
- res.end(`Error: ${error.message}`);
235
+ res.end(`Compilation error: ${result.errors.map(e => e.message).join('\n')}`);
239
236
  }
240
237
  return;
238
+ } catch (error) {
239
+ if (error.code !== 'ENOENT') {
240
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
241
+ res.end(`Error: ${error.message}`);
242
+ return;
243
+ }
241
244
  }
242
245
  }
243
246
 
244
247
  // Handle node_modules
245
248
  if (pathname.startsWith('/node_modules/pulse-js-framework/')) {
246
249
  const modulePath = join(root, '..', 'pulse', pathname.replace('/node_modules/pulse-js-framework/', ''));
247
- if (existsSync(modulePath)) {
250
+ try {
248
251
  const content = readFileSync(modulePath, 'utf-8');
249
252
  res.writeHead(200, {
250
253
  'Content-Type': 'application/javascript',
@@ -252,6 +255,8 @@ export async function startDevServer(args) {
252
255
  });
253
256
  res.end(content);
254
257
  return;
258
+ } catch (e) {
259
+ if (e.code !== 'ENOENT') throw e;
255
260
  }
256
261
  }
257
262
 
@@ -267,7 +272,7 @@ export async function startDevServer(args) {
267
272
  ];
268
273
 
269
274
  for (const runtimePath of possiblePaths) {
270
- if (existsSync(runtimePath)) {
275
+ try {
271
276
  const content = readFileSync(runtimePath, 'utf-8');
272
277
  res.writeHead(200, {
273
278
  'Content-Type': 'application/javascript',
@@ -275,12 +280,14 @@ export async function startDevServer(args) {
275
280
  });
276
281
  res.end(content);
277
282
  return;
283
+ } catch (e) {
284
+ if (e.code !== 'ENOENT') throw e;
278
285
  }
279
286
  }
280
287
  }
281
288
 
282
289
  // Serve static files
283
- if (existsSync(filePath) && statSync(filePath).isFile()) {
290
+ try {
284
291
  const ext = extname(filePath);
285
292
  const mimeType = MIME_TYPES[ext] || 'application/octet-stream';
286
293
 
@@ -294,6 +301,8 @@ export async function startDevServer(args) {
294
301
  res.writeHead(200, { 'Content-Type': mimeType });
295
302
  res.end(content);
296
303
  return;
304
+ } catch (e) {
305
+ if (e.code !== 'ENOENT') throw e;
297
306
  }
298
307
 
299
308
  // SPA fallback: serve index.html for routes without file extensions
@@ -301,12 +310,14 @@ export async function startDevServer(args) {
301
310
  const ext = extname(pathname);
302
311
  if (!ext || ext === '') {
303
312
  const indexPath = join(root, 'index.html');
304
- if (existsSync(indexPath)) {
313
+ try {
305
314
  let content = readFileSync(indexPath, 'utf-8');
306
315
  content = content.replace('</body>', LIVERELOAD_SCRIPT);
307
316
  res.writeHead(200, { 'Content-Type': 'text/html' });
308
317
  res.end(content);
309
318
  return;
319
+ } catch (e) {
320
+ if (e.code !== 'ENOENT') throw e;
310
321
  }
311
322
  }
312
323
 
@@ -336,7 +347,7 @@ function watchFiles(root) {
336
347
  const srcDir = join(root, 'src');
337
348
  let debounceTimer = null;
338
349
 
339
- if (existsSync(srcDir)) {
350
+ try {
340
351
  watch(srcDir, { recursive: true }, (eventType, filename) => {
341
352
  if (filename && (filename.endsWith('.pulse') || filename.endsWith('.js') || filename.endsWith('.css'))) {
342
353
  // Debounce to avoid multiple reloads
@@ -348,6 +359,9 @@ function watchFiles(root) {
348
359
  }
349
360
  });
350
361
  log.debug('Watching src/ for changes...');
362
+ } catch (e) {
363
+ if (e.code !== 'ENOENT') throw e;
364
+ // No src directory, skip watching
351
365
  }
352
366
  }
353
367