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 +22 -0
- package/cli/index.js +23 -20
- package/cli/logger.js +122 -0
- package/cli/mobile.js +48 -71
- package/compiler/parser.js +57 -136
- package/compiler/transformer.js +75 -197
- package/index.js +8 -2
- package/package.json +53 -8
- package/runtime/dom.js +6 -3
- package/runtime/index.js +2 -0
- package/runtime/logger.js +304 -0
- package/runtime/native.js +7 -4
- package/runtime/pulse.js +308 -25
- package/runtime/store.js +227 -19
- package/types/dom.d.ts +288 -0
- package/types/index.d.ts +135 -0
- package/types/logger.d.ts +122 -0
- package/types/pulse.d.ts +149 -0
- package/types/router.d.ts +197 -0
- package/types/store.d.ts +170 -0
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
|
-
|
|
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
|
-
|
|
42
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
126
|
+
log.error(`Directory "${projectName}" already exists.`);
|
|
124
127
|
process.exit(1);
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
387
|
+
log.info(`Compiled: ${inputFile} -> ${outputFile}`);
|
|
385
388
|
} else {
|
|
386
|
-
|
|
389
|
+
log.error('Compilation failed:');
|
|
387
390
|
for (const error of result.errors) {
|
|
388
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
mkdirSync(dest, { recursive: true });
|
|
456
|
+
if (!existsSync(src)) return;
|
|
457
|
+
mkdirp(dest);
|
|
431
458
|
|
|
432
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
476
|
+
mkdirs(androidDir, [
|
|
467
477
|
'app/src/main/java/' + packagePath,
|
|
468
|
-
'app/src/main/res/layout',
|
|
469
|
-
'app/src/main/res/
|
|
470
|
-
'app/src/main/res/
|
|
471
|
-
'app/src/main/
|
|
472
|
-
|
|
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
|
-
|
|
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
|