pulse-js-framework 1.4.7 → 1.4.9

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 CHANGED
@@ -136,6 +136,7 @@ style {
136
136
  - CSS scoping (styles are automatically scoped to component)
137
137
  - Native `router {}` and `store {}` blocks
138
138
  - Detailed error messages with line/column info
139
+ - Source map generation (v1.4.9) for debugging original `.pulse` code
139
140
 
140
141
  ### Router & Store DSL (v1.4.0)
141
142
 
@@ -266,7 +267,53 @@ router.start();
266
267
  - Per-route guards (`beforeEnter`)
267
268
  - Global guards (`beforeEach`, `beforeResolve`, `afterEach`)
268
269
  - Scroll restoration
269
- - Lazy-loaded routes (async handlers)
270
+ - Lazy-loaded routes with `lazy()` and `preload()`
271
+ - Middleware pipeline (Koa-style)
272
+
273
+ #### Lazy Loading & Middleware (v1.4.9)
274
+
275
+ ```javascript
276
+ import { createRouter, lazy, preload } from 'pulse-js-framework/runtime/router';
277
+
278
+ // Lazy load components
279
+ const routes = {
280
+ '/': HomePage,
281
+ '/dashboard': lazy(() => import('./Dashboard.js')),
282
+ '/settings': lazy(() => import('./Settings.js'), {
283
+ loading: () => el('div.spinner', 'Loading...'),
284
+ error: (err) => el('div.error', `Failed: ${err.message}`),
285
+ timeout: 5000
286
+ })
287
+ };
288
+
289
+ // Preload on hover
290
+ link.addEventListener('mouseenter', () => preload(routes['/dashboard']));
291
+
292
+ // Middleware
293
+ const router = createRouter({
294
+ routes,
295
+ middleware: [
296
+ // Logger middleware
297
+ async (ctx, next) => {
298
+ console.log('Navigating to:', ctx.to.path);
299
+ await next();
300
+ },
301
+ // Auth middleware
302
+ async (ctx, next) => {
303
+ if (ctx.to.meta.requiresAuth && !isLoggedIn()) {
304
+ return ctx.redirect('/login');
305
+ }
306
+ await next();
307
+ }
308
+ ]
309
+ });
310
+
311
+ // Add middleware dynamically
312
+ const unsubscribe = router.use(async (ctx, next) => {
313
+ ctx.meta.startTime = Date.now();
314
+ await next();
315
+ });
316
+ ```
270
317
 
271
318
  ### Store
272
319
 
@@ -451,11 +498,11 @@ onNativeReady(({ platform }) => {
451
498
 
452
499
  **Available APIs:** Storage, Device Info, Network Status, Toast, Vibration, Clipboard, App Lifecycle
453
500
 
454
- ## VSCode Extension
501
+ ## IDE Extensions
455
502
 
456
- Pulse includes a VSCode extension for `.pulse` files with syntax highlighting and snippets.
503
+ Pulse provides extensions for popular IDEs with syntax highlighting and snippets.
457
504
 
458
- ### Installation
505
+ ### VS Code
459
506
 
460
507
  ```bash
461
508
  # Windows (PowerShell)
@@ -467,12 +514,30 @@ cd vscode-extension
467
514
  bash install.sh
468
515
  ```
469
516
 
470
- Then restart VSCode. You'll get:
517
+ Then restart VS Code. You'll get:
471
518
  - Syntax highlighting for `.pulse` files
472
519
  - Code snippets (`page`, `state`, `view`, `@click`, etc.)
473
520
  - Bracket matching and auto-closing
474
521
  - Comment toggling (Ctrl+/)
475
522
 
523
+ ### IntelliJ IDEA / WebStorm
524
+
525
+ ```bash
526
+ cd intellij-plugin
527
+ ./gradlew buildPlugin
528
+ ```
529
+
530
+ Then install the plugin from `build/distributions/pulse-language-1.0.0.zip`:
531
+ 1. Open **Settings** > **Plugins**
532
+ 2. Click gear icon > **Install Plugin from Disk...**
533
+ 3. Select the `.zip` file and restart
534
+
535
+ Features:
536
+ - Syntax highlighting for `.pulse` files
537
+ - 17 Live Templates (snippets)
538
+ - Code folding for blocks
539
+ - Customizable color scheme
540
+
476
541
  ## TypeScript Support
477
542
 
478
543
  Pulse includes full TypeScript definitions for IDE autocomplete and type checking:
package/cli/dev.js CHANGED
@@ -79,7 +79,7 @@ export async function startDevServer(args) {
79
79
  try {
80
80
  const source = readFileSync(filePath, 'utf-8');
81
81
  const result = compile(source, {
82
- runtime: '/node_modules/pulse-js-framework/runtime/index.js'
82
+ runtime: '/runtime/index.js'
83
83
  });
84
84
 
85
85
  if (result.success) {
@@ -105,6 +105,29 @@ export async function startDevServer(args) {
105
105
  res.end(content);
106
106
  return;
107
107
  }
108
+
109
+ // Check if there's a .pulse file that should be compiled to .js
110
+ const pulseFilePath = filePath.replace(/\.(js|mjs)$/, '.pulse');
111
+ if (existsSync(pulseFilePath)) {
112
+ try {
113
+ const source = readFileSync(pulseFilePath, 'utf-8');
114
+ const result = compile(source, {
115
+ runtime: '/runtime/index.js'
116
+ });
117
+
118
+ if (result.success) {
119
+ res.writeHead(200, { 'Content-Type': 'application/javascript' });
120
+ res.end(result.code);
121
+ } else {
122
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
123
+ res.end(`Compilation error: ${result.errors.map(e => e.message).join('\n')}`);
124
+ }
125
+ } catch (error) {
126
+ res.writeHead(500, { 'Content-Type': 'text/plain' });
127
+ res.end(`Error: ${error.message}`);
128
+ }
129
+ return;
130
+ }
108
131
  }
109
132
 
110
133
  // Handle node_modules
package/cli/lint.js CHANGED
@@ -14,6 +14,9 @@ export const LintRules = {
14
14
  'undefined-reference': { severity: 'error', fixable: false },
15
15
  'duplicate-declaration': { severity: 'error', fixable: false },
16
16
 
17
+ // Security rules (warnings)
18
+ 'xss-vulnerability': { severity: 'warning', fixable: false },
19
+
17
20
  // Usage rules (warnings)
18
21
  'unused-import': { severity: 'warning', fixable: true },
19
22
  'unused-state': { severity: 'warning', fixable: false },
@@ -127,6 +130,9 @@ export class SemanticAnalyzer {
127
130
  // Phase 4: Style checks
128
131
  this.checkStyle();
129
132
 
133
+ // Phase 5: Security checks (XSS detection)
134
+ this.checkSecurity();
135
+
130
136
  return this.diagnostics;
131
137
  }
132
138
 
@@ -519,6 +525,117 @@ export class SemanticAnalyzer {
519
525
  }
520
526
  }
521
527
 
528
+ /**
529
+ * Check for security vulnerabilities (XSS patterns)
530
+ */
531
+ checkSecurity() {
532
+ // Check actions for dangerous DOM manipulation
533
+ if (this.ast.actions && this.ast.actions.functions) {
534
+ for (const fn of this.ast.actions.functions) {
535
+ if (fn.body) {
536
+ this.checkForXSS(fn.body, fn.name, fn.line, fn.column);
537
+ }
538
+ }
539
+ }
540
+
541
+ // Check view for dangerous patterns in directives
542
+ if (this.ast.view) {
543
+ this.checkViewForXSS(this.ast.view);
544
+ }
545
+ }
546
+
547
+ /**
548
+ * Check code string for XSS vulnerabilities
549
+ */
550
+ checkForXSS(code, context, line, column) {
551
+ if (typeof code !== 'string') return;
552
+
553
+ // Dangerous DOM manipulation patterns
554
+ const xssPatterns = [
555
+ {
556
+ pattern: /\.innerHTML\s*=\s*(?!['"]\s*['"])/,
557
+ message: 'Assigning dynamic content to innerHTML can lead to XSS. Use textContent or sanitize input.'
558
+ },
559
+ {
560
+ pattern: /\.outerHTML\s*=\s*(?!['"]\s*['"])/,
561
+ message: 'Assigning dynamic content to outerHTML can lead to XSS. Consider safer alternatives.'
562
+ },
563
+ {
564
+ pattern: /document\.write\s*\(/,
565
+ message: 'document.write() can execute scripts and lead to XSS. Use DOM methods instead.'
566
+ },
567
+ {
568
+ pattern: /\.insertAdjacentHTML\s*\(/,
569
+ message: 'insertAdjacentHTML with unsanitized input can lead to XSS. Sanitize HTML or use DOM methods.'
570
+ },
571
+ {
572
+ pattern: /eval\s*\(/,
573
+ message: 'eval() executes arbitrary code and is a security risk. Avoid using eval().'
574
+ },
575
+ {
576
+ pattern: /new\s+Function\s*\(/,
577
+ message: 'new Function() can execute arbitrary code like eval(). Avoid dynamic function creation.'
578
+ },
579
+ {
580
+ pattern: /setTimeout\s*\(\s*['"`]/,
581
+ message: 'setTimeout with string argument executes code like eval(). Use a function instead.'
582
+ },
583
+ {
584
+ pattern: /setInterval\s*\(\s*['"`]/,
585
+ message: 'setInterval with string argument executes code like eval(). Use a function instead.'
586
+ }
587
+ ];
588
+
589
+ for (const { pattern, message } of xssPatterns) {
590
+ if (pattern.test(code)) {
591
+ this.addDiagnostic('warning', 'xss-vulnerability',
592
+ `Potential XSS in action '${context}': ${message}`,
593
+ line || 1, column || 1);
594
+ }
595
+ }
596
+ }
597
+
598
+ /**
599
+ * Check view nodes for XSS vulnerabilities
600
+ */
601
+ checkViewForXSS(node) {
602
+ if (!node) return;
603
+
604
+ const children = node.children || [];
605
+ for (const child of children) {
606
+ if (!child) continue;
607
+
608
+ // Check for @html directive (if exists in DSL)
609
+ if (child.directives) {
610
+ for (const directive of child.directives) {
611
+ if (directive.name === 'html') {
612
+ this.addDiagnostic('warning', 'xss-vulnerability',
613
+ '@html directive renders raw HTML and can lead to XSS if used with user input. Ensure content is sanitized.',
614
+ directive.line || child.line || 1, directive.column || child.column || 1);
615
+ }
616
+ }
617
+ }
618
+
619
+ // Check directive expressions for dangerous patterns
620
+ if (child.directives) {
621
+ for (const directive of child.directives) {
622
+ const expr = directive.handler || directive.expression || '';
623
+ if (typeof expr === 'string') {
624
+ // Check for innerHTML in expressions
625
+ if (/innerHTML|outerHTML/.test(expr)) {
626
+ this.addDiagnostic('warning', 'xss-vulnerability',
627
+ `Using innerHTML/outerHTML in directive expression can lead to XSS. Use safer alternatives.`,
628
+ directive.line || child.line || 1, directive.column || child.column || 1);
629
+ }
630
+ }
631
+ }
632
+ }
633
+
634
+ // Recurse into children
635
+ this.checkViewForXSS(child);
636
+ }
637
+ }
638
+
522
639
  /**
523
640
  * Add a diagnostic message
524
641
  */
package/compiler/index.js CHANGED
@@ -5,26 +5,51 @@
5
5
  export * from './lexer.js';
6
6
  export * from './parser.js';
7
7
  export * from './transformer.js';
8
+ export * from './sourcemap.js';
8
9
 
9
10
  import { tokenize } from './lexer.js';
10
11
  import { parse } from './parser.js';
11
- import { transform } from './transformer.js';
12
+ import { transform, Transformer } from './transformer.js';
12
13
 
13
14
  /**
14
15
  * Compile Pulse source code to JavaScript
16
+ *
17
+ * @param {string} source - Pulse source code
18
+ * @param {Object} options - Compiler options
19
+ * @param {string} options.runtime - Runtime import path
20
+ * @param {boolean} options.minify - Minify output
21
+ * @param {boolean} options.scopeStyles - Scope CSS with unique prefixes
22
+ * @param {boolean} options.sourceMap - Generate source map
23
+ * @param {string} options.sourceFileName - Original file name (for source maps)
24
+ * @param {boolean} options.inlineSourceMap - Include source map as inline comment
25
+ * @returns {Object} Compilation result
15
26
  */
16
27
  export function compile(source, options = {}) {
17
28
  try {
18
29
  // Parse source to AST
19
30
  const ast = parse(source);
20
31
 
32
+ // Prepare transformer options
33
+ const transformerOptions = {
34
+ ...options,
35
+ sourceContent: options.sourceMap ? source : null
36
+ };
37
+
21
38
  // Transform AST to JavaScript
22
- const code = transform(ast, options);
39
+ const transformer = new Transformer(ast, transformerOptions);
40
+ const result = transformer.transformWithSourceMap();
41
+
42
+ // Add inline source map if requested
43
+ let code = result.code;
44
+ if (options.sourceMap && options.inlineSourceMap && result.sourceMapComment) {
45
+ code = code + '\n' + result.sourceMapComment;
46
+ }
23
47
 
24
48
  return {
25
49
  success: true,
26
50
  code,
27
51
  ast,
52
+ sourceMap: result.sourceMap,
28
53
  errors: []
29
54
  };
30
55
  } catch (error) {
@@ -32,6 +57,7 @@ export function compile(source, options = {}) {
32
57
  success: false,
33
58
  code: null,
34
59
  ast: null,
60
+ sourceMap: null,
35
61
  errors: [{
36
62
  message: error.message,
37
63
  line: error.line,
@@ -55,11 +81,15 @@ export function tokenizeOnly(source) {
55
81
  return tokenize(source);
56
82
  }
57
83
 
84
+ import { SourceMapGenerator, SourceMapConsumer } from './sourcemap.js';
85
+
58
86
  export default {
59
87
  compile,
60
88
  parseOnly,
61
89
  tokenizeOnly,
62
90
  tokenize,
63
91
  parse,
64
- transform
92
+ transform,
93
+ SourceMapGenerator,
94
+ SourceMapConsumer
65
95
  };