pulse-js-framework 1.11.2 → 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.
- package/cli/analyze.js +21 -8
- package/cli/build.js +83 -56
- package/cli/dev.js +108 -94
- package/cli/docs-test.js +52 -33
- package/cli/index.js +81 -51
- package/cli/mobile.js +92 -40
- package/cli/release.js +64 -46
- package/cli/scaffold.js +14 -13
- package/compiler/lexer.js +55 -54
- package/compiler/parser/core.js +1 -0
- package/compiler/parser/state.js +6 -12
- package/compiler/parser/style.js +17 -20
- package/compiler/parser/view.js +1 -3
- package/compiler/preprocessor.js +124 -262
- package/compiler/sourcemap.js +10 -4
- package/compiler/transformer/expressions.js +122 -106
- package/compiler/transformer/index.js +2 -4
- package/compiler/transformer/style.js +74 -7
- package/compiler/transformer/view.js +86 -36
- package/loader/esbuild-plugin-server-components.js +209 -0
- package/loader/esbuild-plugin.js +41 -93
- package/loader/parcel-plugin.js +37 -97
- package/loader/rollup-plugin-server-components.js +30 -169
- package/loader/rollup-plugin.js +27 -78
- package/loader/shared.js +362 -0
- package/loader/swc-plugin.js +65 -82
- package/loader/vite-plugin-server-components.js +30 -171
- package/loader/vite-plugin.js +25 -10
- package/loader/webpack-loader-server-components.js +21 -134
- package/loader/webpack-loader.js +25 -80
- package/package.json +52 -12
- package/runtime/dom-selector.js +2 -1
- package/runtime/form.js +4 -3
- package/runtime/http.js +6 -1
- package/runtime/logger.js +44 -24
- package/runtime/router/utils.js +14 -7
- package/runtime/security.js +13 -1
- package/runtime/server-components/actions-server.js +23 -19
- package/runtime/server-components/error-sanitizer.js +18 -18
- package/runtime/server-components/security.js +41 -24
- package/runtime/ssr-preload.js +5 -3
- package/runtime/testing.js +759 -0
- package/runtime/utils.js +3 -2
- package/server/utils.js +15 -9
- package/sw/index.js +2 -0
- package/types/loaders.d.ts +1043 -0
- package/compiler/parser/_extract.js +0 -393
- 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,
|
|
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
|
-
|
|
86
|
-
|
|
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:
|
|
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
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
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,
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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
|
|
158
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
150
159
|
|
|
151
|
-
for (const
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
172
|
-
mkdirSync(outDir, { recursive: true });
|
|
173
|
-
}
|
|
178
|
+
mkdirSync(outDir, { recursive: true });
|
|
174
179
|
|
|
175
|
-
const
|
|
180
|
+
const entries = readdirSync(srcDir, { withFileTypes: true });
|
|
176
181
|
|
|
177
|
-
for (const
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
425
|
-
mkdirSync(dest, { recursive: true });
|
|
426
|
-
}
|
|
449
|
+
mkdirSync(dest, { recursive: true });
|
|
427
450
|
|
|
428
|
-
const
|
|
451
|
+
const entries = readdirSync(src, { withFileTypes: true });
|
|
429
452
|
|
|
430
|
-
for (const
|
|
431
|
-
const srcPath = join(src,
|
|
432
|
-
const destPath = join(dest,
|
|
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 (
|
|
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
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
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,
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|