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 +70 -5
- package/cli/dev.js +24 -1
- package/cli/lint.js +117 -0
- package/compiler/index.js +33 -3
- package/compiler/sourcemap.js +360 -0
- package/compiler/transformer.js +108 -2
- package/package.json +4 -2
- package/runtime/router.js +374 -8
- package/types/hmr.d.ts +112 -0
- package/types/index.d.ts +25 -1
- package/types/router.d.ts +89 -0
- package/types/sourcemap.d.ts +126 -0
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 (
|
|
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
|
-
##
|
|
501
|
+
## IDE Extensions
|
|
455
502
|
|
|
456
|
-
Pulse
|
|
503
|
+
Pulse provides extensions for popular IDEs with syntax highlighting and snippets.
|
|
457
504
|
|
|
458
|
-
###
|
|
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
|
|
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: '/
|
|
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
|
|
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
|
};
|