pulse-js-framework 1.4.3 → 1.4.5

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
@@ -14,6 +14,7 @@ A declarative DOM framework with CSS selector-based structure and reactive pulsa
14
14
  - **Lightweight** - Minimal footprint, maximum performance
15
15
  - **Router & Store** - Built-in SPA routing and state management
16
16
  - **Mobile Apps** - Build native Android & iOS apps (zero dependencies)
17
+ - **TypeScript Support** - Full type definitions for IDE autocomplete
17
18
 
18
19
  ## Installation
19
20
 
@@ -398,6 +399,27 @@ onNativeReady(({ platform }) => {
398
399
 
399
400
  **Available APIs:** Storage, Device Info, Network Status, Toast, Vibration, Clipboard, App Lifecycle
400
401
 
402
+ ## TypeScript Support
403
+
404
+ Pulse includes full TypeScript definitions for IDE autocomplete and type checking:
405
+
406
+ ```typescript
407
+ import { pulse, effect, computed, Pulse } from 'pulse-js-framework/runtime';
408
+ import { el, list, when } from 'pulse-js-framework/runtime';
409
+ import { createRouter, Router } from 'pulse-js-framework/runtime/router';
410
+ import { createStore, Store } from 'pulse-js-framework/runtime/store';
411
+
412
+ // Full autocomplete for all APIs
413
+ const count: Pulse<number> = pulse(0);
414
+ const doubled = computed(() => count.get() * 2);
415
+
416
+ effect(() => {
417
+ console.log(count.get()); // Type-safe access
418
+ });
419
+ ```
420
+
421
+ Types are automatically detected by IDEs (VS Code, WebStorm) without additional configuration.
422
+
401
423
  ## Examples
402
424
 
403
425
  - [Blog](examples/blog) - Full blog app with CRUD, categories, search, dark mode
package/cli/index.js CHANGED
@@ -7,11 +7,14 @@
7
7
  import { fileURLToPath } from 'url';
8
8
  import { dirname, join, resolve } from 'path';
9
9
  import { existsSync, mkdirSync, writeFileSync, readFileSync, cpSync } from 'fs';
10
+ import { log } from './logger.js';
10
11
 
11
12
  const __filename = fileURLToPath(import.meta.url);
12
13
  const __dirname = dirname(__filename);
13
14
 
14
- const VERSION = '1.4.3';
15
+ // Version - read dynamically from package.json
16
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
17
+ const VERSION = pkg.version;
15
18
 
16
19
  // Command handlers
17
20
  const commands = {
@@ -38,8 +41,8 @@ async function main() {
38
41
  if (command in commands) {
39
42
  await commands[command](args.slice(1));
40
43
  } else {
41
- console.error(`Unknown command: ${command}`);
42
- console.log('Run "pulse help" for usage information.');
44
+ log.error(`Unknown command: ${command}`);
45
+ log.info('Run "pulse help" for usage information.');
43
46
  process.exit(1);
44
47
  }
45
48
  }
@@ -48,7 +51,7 @@ async function main() {
48
51
  * Show help message
49
52
  */
50
53
  function showHelp() {
51
- console.log(`
54
+ log.info(`
52
55
  Pulse Framework CLI v${VERSION}
53
56
 
54
57
  Usage: pulse <command> [options]
@@ -102,7 +105,7 @@ Documentation: https://github.com/vincenthirtz/pulse-js-framework
102
105
  * Show version
103
106
  */
104
107
  function showVersion() {
105
- console.log(`Pulse Framework v${VERSION}`);
108
+ log.info(`Pulse Framework v${VERSION}`);
106
109
  }
107
110
 
108
111
  /**
@@ -112,19 +115,19 @@ async function createProject(args) {
112
115
  const projectName = args[0];
113
116
 
114
117
  if (!projectName) {
115
- console.error('Please provide a project name.');
116
- console.log('Usage: pulse create <project-name>');
118
+ log.error('Please provide a project name.');
119
+ log.info('Usage: pulse create <project-name>');
117
120
  process.exit(1);
118
121
  }
119
122
 
120
123
  const projectPath = resolve(process.cwd(), projectName);
121
124
 
122
125
  if (existsSync(projectPath)) {
123
- console.error(`Directory "${projectName}" already exists.`);
126
+ log.error(`Directory "${projectName}" already exists.`);
124
127
  process.exit(1);
125
128
  }
126
129
 
127
- console.log(`Creating new Pulse project: ${projectName}`);
130
+ log.info(`Creating new Pulse project: ${projectName}`);
128
131
 
129
132
  // Create project structure
130
133
  mkdirSync(projectPath);
@@ -281,7 +284,7 @@ dist
281
284
 
282
285
  writeFileSync(join(projectPath, '.gitignore'), gitignore);
283
286
 
284
- console.log(`
287
+ log.info(`
285
288
  Project created successfully!
286
289
 
287
290
  Next steps:
@@ -297,7 +300,7 @@ Happy coding with Pulse!
297
300
  * Run development server
298
301
  */
299
302
  async function runDev(args) {
300
- console.log('Starting Pulse development server...');
303
+ log.info('Starting Pulse development server...');
301
304
 
302
305
  // Use dynamic import for the dev server module
303
306
  const { startDevServer } = await import('./dev.js');
@@ -308,7 +311,7 @@ async function runDev(args) {
308
311
  * Build for production
309
312
  */
310
313
  async function runBuild(args) {
311
- console.log('Building Pulse project for production...');
314
+ log.info('Building Pulse project for production...');
312
315
 
313
316
  const { buildProject } = await import('./build.js');
314
317
  await buildProject(args);
@@ -318,7 +321,7 @@ async function runBuild(args) {
318
321
  * Preview production build
319
322
  */
320
323
  async function runPreview(args) {
321
- console.log('Starting Pulse preview server...');
324
+ log.info('Starting Pulse preview server...');
322
325
 
323
326
  const { previewBuild } = await import('./build.js');
324
327
  await previewBuild(args);
@@ -363,13 +366,13 @@ async function compileFile(args) {
363
366
  const inputFile = args[0];
364
367
 
365
368
  if (!inputFile) {
366
- console.error('Please provide a file to compile.');
367
- console.log('Usage: pulse compile <file.pulse>');
369
+ log.error('Please provide a file to compile.');
370
+ log.info('Usage: pulse compile <file.pulse>');
368
371
  process.exit(1);
369
372
  }
370
373
 
371
374
  if (!existsSync(inputFile)) {
372
- console.error(`File not found: ${inputFile}`);
375
+ log.error(`File not found: ${inputFile}`);
373
376
  process.exit(1);
374
377
  }
375
378
 
@@ -381,11 +384,11 @@ async function compileFile(args) {
381
384
  if (result.success) {
382
385
  const outputFile = inputFile.replace(/\.pulse$/, '.js');
383
386
  writeFileSync(outputFile, result.code);
384
- console.log(`Compiled: ${inputFile} -> ${outputFile}`);
387
+ log.info(`Compiled: ${inputFile} -> ${outputFile}`);
385
388
  } else {
386
- console.error('Compilation failed:');
389
+ log.error('Compilation failed:');
387
390
  for (const error of result.errors) {
388
- console.error(` ${error.message}`);
391
+ log.error(` ${error.message}`);
389
392
  }
390
393
  process.exit(1);
391
394
  }
@@ -393,6 +396,6 @@ async function compileFile(args) {
393
396
 
394
397
  // Run main
395
398
  main().catch(error => {
396
- console.error('Error:', error.message);
399
+ log.error('Error:', error.message);
397
400
  process.exit(1);
398
401
  });
package/cli/logger.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Pulse CLI Logger
3
+ * Lightweight logger for CLI tools with support for verbose mode
4
+ * @module pulse-cli/logger
5
+ */
6
+
7
+ /** @type {boolean} */
8
+ let verboseMode = false;
9
+
10
+ /**
11
+ * Enable or disable verbose mode for debug output
12
+ * @param {boolean} enabled - Whether to enable verbose mode
13
+ * @returns {void}
14
+ * @example
15
+ * setVerbose(true);
16
+ * log.debug('This will now be shown');
17
+ */
18
+ export function setVerbose(enabled) {
19
+ verboseMode = enabled;
20
+ }
21
+
22
+ /**
23
+ * Check if verbose mode is currently enabled
24
+ * @returns {boolean} True if verbose mode is enabled
25
+ * @example
26
+ * if (isVerbose()) {
27
+ * // Perform additional logging
28
+ * }
29
+ */
30
+ export function isVerbose() {
31
+ return verboseMode;
32
+ }
33
+
34
+ /**
35
+ * CLI Logger object with console-like API
36
+ * @namespace log
37
+ */
38
+ export const log = {
39
+ /**
40
+ * Log an info message (always shown)
41
+ * @param {...*} args - Values to log
42
+ * @returns {void}
43
+ * @example
44
+ * log.info('Starting server on port', 3000);
45
+ */
46
+ info(...args) {
47
+ console.log(...args);
48
+ },
49
+
50
+ /**
51
+ * Log a success message (always shown)
52
+ * @param {...*} args - Values to log
53
+ * @returns {void}
54
+ * @example
55
+ * log.success('Build completed successfully!');
56
+ */
57
+ success(...args) {
58
+ console.log(...args);
59
+ },
60
+
61
+ /**
62
+ * Log a warning message
63
+ * @param {...*} args - Values to log
64
+ * @returns {void}
65
+ * @example
66
+ * log.warn('Deprecated feature used');
67
+ */
68
+ warn(...args) {
69
+ console.warn(...args);
70
+ },
71
+
72
+ /**
73
+ * Log an error message
74
+ * @param {...*} args - Values to log
75
+ * @returns {void}
76
+ * @example
77
+ * log.error('Failed to compile:', error.message);
78
+ */
79
+ error(...args) {
80
+ console.error(...args);
81
+ },
82
+
83
+ /**
84
+ * Log a debug message (only shown in verbose mode)
85
+ * @param {...*} args - Values to log
86
+ * @returns {void}
87
+ * @example
88
+ * log.debug('Processing file:', filename);
89
+ */
90
+ debug(...args) {
91
+ if (verboseMode) {
92
+ console.log('[debug]', ...args);
93
+ }
94
+ },
95
+
96
+ /**
97
+ * Log a verbose message (only shown in verbose mode)
98
+ * @param {...*} args - Values to log
99
+ * @returns {void}
100
+ * @example
101
+ * log.verbose('Additional details:', data);
102
+ */
103
+ verbose(...args) {
104
+ if (verboseMode) {
105
+ console.log(...args);
106
+ }
107
+ },
108
+
109
+ /**
110
+ * Print a blank line for spacing
111
+ * @returns {void}
112
+ * @example
113
+ * log.info('Section 1');
114
+ * log.newline();
115
+ * log.info('Section 2');
116
+ */
117
+ newline() {
118
+ console.log();
119
+ }
120
+ };
121
+
122
+ export default log;
package/cli/mobile.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { existsSync, mkdirSync, readFileSync, writeFileSync, cpSync, readdirSync } from 'fs';
7
- import { join, resolve, dirname } from 'path';
7
+ import { join, dirname } from 'path';
8
8
  import { fileURLToPath } from 'url';
9
9
  import { execSync } from 'child_process';
10
10
 
@@ -14,6 +14,38 @@ const __dirname = dirname(__filename);
14
14
  const MOBILE_DIR = 'mobile';
15
15
  const CONFIG_FILE = 'pulse.mobile.json';
16
16
 
17
+ // ============================================================================
18
+ // Helper functions
19
+ // ============================================================================
20
+
21
+ /** Create directory if it doesn't exist */
22
+ const mkdirp = (path) => !existsSync(path) && mkdirSync(path, { recursive: true });
23
+
24
+ /** Create multiple directories at once */
25
+ const mkdirs = (base, dirs) => dirs.forEach(d => mkdirp(join(base, d)));
26
+
27
+ /** Write file (auto-creates parent directories) */
28
+ const writeFile = (path, content) => {
29
+ mkdirp(dirname(path));
30
+ writeFileSync(path, content.trim());
31
+ };
32
+
33
+ /** Apply template variables to content */
34
+ const applyTemplate = (content, config) => content
35
+ .replace(/\{\{APP_NAME\}\}/g, config.name)
36
+ .replace(/\{\{DISPLAY_NAME\}\}/g, config.displayName)
37
+ .replace(/\{\{PACKAGE_ID\}\}/g, config.packageId)
38
+ .replace(/\{\{VERSION\}\}/g, config.version)
39
+ .replace(/\{\{MIN_SDK\}\}/g, String(config.android?.minSdkVersion || 24))
40
+ .replace(/\{\{TARGET_SDK\}\}/g, String(config.android?.targetSdkVersion || 34))
41
+ .replace(/\{\{COMPILE_SDK\}\}/g, String(config.android?.compileSdkVersion || 34))
42
+ .replace(/\{\{IOS_TARGET\}\}/g, config.ios?.deploymentTarget || '13.0');
43
+
44
+ /** Convert string to PascalCase */
45
+ const toPascalCase = (str) => str
46
+ .replace(/[-_](.)/g, (_, c) => c.toUpperCase())
47
+ .replace(/^(.)/, (_, c) => c.toUpperCase());
48
+
17
49
  /**
18
50
  * Handle mobile subcommands
19
51
  */
@@ -419,67 +451,35 @@ function loadConfig(root) {
419
451
  return JSON.parse(readFileSync(configPath, 'utf-8'));
420
452
  }
421
453
 
422
- /**
423
- * Copy and process template files
424
- */
454
+ /** Copy and process template files */
425
455
  function copyAndProcessTemplate(src, dest, config) {
426
- if (!existsSync(src)) {
427
- return;
428
- }
429
-
430
- mkdirSync(dest, { recursive: true });
456
+ if (!existsSync(src)) return;
457
+ mkdirp(dest);
431
458
 
432
- const files = readdirSync(src, { withFileTypes: true });
433
-
434
- for (const file of files) {
459
+ for (const file of readdirSync(src, { withFileTypes: true })) {
435
460
  const srcPath = join(src, file.name);
436
461
  const destPath = join(dest, file.name);
437
462
 
438
463
  if (file.isDirectory()) {
439
464
  copyAndProcessTemplate(srcPath, destPath, config);
440
465
  } else {
441
- let content = readFileSync(srcPath, 'utf-8');
442
-
443
- // Replace template variables
444
- content = content
445
- .replace(/\{\{APP_NAME\}\}/g, config.name)
446
- .replace(/\{\{DISPLAY_NAME\}\}/g, config.displayName)
447
- .replace(/\{\{PACKAGE_ID\}\}/g, config.packageId)
448
- .replace(/\{\{VERSION\}\}/g, config.version)
449
- .replace(/\{\{MIN_SDK\}\}/g, String(config.android?.minSdkVersion || 24))
450
- .replace(/\{\{TARGET_SDK\}\}/g, String(config.android?.targetSdkVersion || 34))
451
- .replace(/\{\{COMPILE_SDK\}\}/g, String(config.android?.compileSdkVersion || 34))
452
- .replace(/\{\{IOS_TARGET\}\}/g, config.ios?.deploymentTarget || '13.0');
453
-
454
- writeFileSync(destPath, content);
466
+ writeFileSync(destPath, applyTemplate(readFileSync(srcPath, 'utf-8'), config));
455
467
  }
456
468
  }
457
469
  }
458
470
 
459
- /**
460
- * Create Android project from scratch
461
- */
471
+ /** Create Android project from scratch */
462
472
  function createAndroidProject(androidDir, config) {
463
473
  const packagePath = config.packageId.replace(/\./g, '/');
464
474
 
465
475
  // Create directory structure
466
- const dirs = [
476
+ mkdirs(androidDir, [
467
477
  'app/src/main/java/' + packagePath,
468
- 'app/src/main/res/layout',
469
- 'app/src/main/res/values',
470
- 'app/src/main/res/drawable',
471
- 'app/src/main/res/mipmap-hdpi',
472
- 'app/src/main/res/mipmap-mdpi',
473
- 'app/src/main/res/mipmap-xhdpi',
474
- 'app/src/main/res/mipmap-xxhdpi',
475
- 'app/src/main/res/mipmap-xxxhdpi',
476
- 'app/src/main/assets/www',
477
- 'gradle/wrapper'
478
- ];
479
-
480
- for (const dir of dirs) {
481
- mkdirSync(join(androidDir, dir), { recursive: true });
482
- }
478
+ 'app/src/main/res/layout', 'app/src/main/res/values', 'app/src/main/res/drawable',
479
+ 'app/src/main/res/mipmap-hdpi', 'app/src/main/res/mipmap-mdpi',
480
+ 'app/src/main/res/mipmap-xhdpi', 'app/src/main/res/mipmap-xxhdpi', 'app/src/main/res/mipmap-xxxhdpi',
481
+ 'app/src/main/assets/www', 'gradle/wrapper'
482
+ ]);
483
483
 
484
484
  // MainActivity.java
485
485
  writeFileSync(join(androidDir, 'app/src/main/java', packagePath, 'MainActivity.java'), `
@@ -906,21 +906,9 @@ gradle %*
906
906
  `.trim());
907
907
  }
908
908
 
909
- /**
910
- * Create iOS project from scratch
911
- */
909
+ /** Create iOS project from scratch */
912
910
  function createIOSProject(iosDir, config) {
913
- // Create directory structure
914
- const dirs = [
915
- 'PulseApp',
916
- 'PulseApp/www',
917
- 'PulseApp/Assets.xcassets',
918
- 'PulseApp.xcodeproj'
919
- ];
920
-
921
- for (const dir of dirs) {
922
- mkdirSync(join(iosDir, dir), { recursive: true });
923
- }
911
+ mkdirs(iosDir, ['PulseApp', 'PulseApp/www', 'PulseApp/Assets.xcassets', 'PulseApp.xcodeproj']);
924
912
 
925
913
  // AppDelegate.swift
926
914
  writeFileSync(join(iosDir, 'PulseApp/AppDelegate.swift'), `
@@ -1430,18 +1418,7 @@ function generateXcodeProject(config) {
1430
1418
  `;
1431
1419
  }
1432
1420
 
1433
- /**
1434
- * Convert string to PascalCase
1435
- */
1436
- function toPascalCase(str) {
1437
- return str
1438
- .replace(/[-_](.)/g, (_, char) => char.toUpperCase())
1439
- .replace(/^(.)/, (_, char) => char.toUpperCase());
1440
- }
1441
-
1442
- /**
1443
- * Show mobile help
1444
- */
1421
+ /** Show mobile help */
1445
1422
  function showMobileHelp() {
1446
1423
  console.log(`
1447
1424
  Pulse Mobile - Zero-Dependency Mobile Platform