pulse-js-framework 1.7.5 → 1.7.8
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 +78 -392
- package/cli/dev.js +14 -0
- package/cli/docs-test.js +633 -0
- package/cli/index.js +313 -31
- package/cli/lint.js +13 -4
- package/cli/logger.js +32 -4
- package/cli/release.js +50 -20
- package/compiler/parser.js +1 -1
- package/package.json +11 -4
- package/runtime/dom-advanced.js +357 -0
- package/runtime/dom-binding.js +230 -0
- package/runtime/dom-conditional.js +133 -0
- package/runtime/dom-element.js +142 -0
- package/runtime/dom-lifecycle.js +178 -0
- package/runtime/dom-list.js +267 -0
- package/runtime/dom-selector.js +267 -0
- package/runtime/dom.js +119 -1279
- package/runtime/form.js +417 -22
- package/runtime/native.js +398 -52
- package/runtime/pulse.js +1 -1
- package/runtime/router.js +6 -5
- package/runtime/store.js +81 -6
- package/types/async.d.ts +310 -0
- package/types/form.d.ts +378 -0
- package/types/index.d.ts +44 -0
- /package/{core → runtime}/errors.js +0 -0
package/cli/index.js
CHANGED
|
@@ -5,9 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { fileURLToPath } from 'url';
|
|
8
|
-
import { dirname, join, resolve } from 'path';
|
|
9
|
-
import { existsSync, mkdirSync, writeFileSync, readFileSync, cpSync } from 'fs';
|
|
8
|
+
import { dirname, join, resolve, relative } from 'path';
|
|
9
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, cpSync, watch } from 'fs';
|
|
10
10
|
import { log } from './logger.js';
|
|
11
|
+
import { findPulseFiles, parseArgs } from './utils/file-utils.js';
|
|
11
12
|
|
|
12
13
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
14
|
const __dirname = dirname(__filename);
|
|
@@ -24,14 +25,98 @@ const commands = {
|
|
|
24
25
|
dev: runDev,
|
|
25
26
|
build: runBuild,
|
|
26
27
|
preview: runPreview,
|
|
27
|
-
compile:
|
|
28
|
+
compile: compileFiles,
|
|
28
29
|
mobile: runMobile,
|
|
29
30
|
lint: runLint,
|
|
30
31
|
format: runFormat,
|
|
31
32
|
analyze: runAnalyze,
|
|
32
|
-
release: runReleaseCmd
|
|
33
|
+
release: runReleaseCmd,
|
|
34
|
+
'docs-test': runDocsTestCmd
|
|
33
35
|
};
|
|
34
36
|
|
|
37
|
+
// Command aliases for common typos
|
|
38
|
+
const commandAliases = {
|
|
39
|
+
'complile': 'compile',
|
|
40
|
+
'comile': 'compile',
|
|
41
|
+
'complie': 'compile',
|
|
42
|
+
'buid': 'build',
|
|
43
|
+
'biuld': 'build',
|
|
44
|
+
'buildd': 'build',
|
|
45
|
+
'devv': 'dev',
|
|
46
|
+
'lnt': 'lint',
|
|
47
|
+
'lintt': 'lint',
|
|
48
|
+
'fromat': 'format',
|
|
49
|
+
'foramt': 'format',
|
|
50
|
+
'formatt': 'format',
|
|
51
|
+
'analize': 'analyze',
|
|
52
|
+
'analzye': 'analyze',
|
|
53
|
+
'anaylze': 'analyze',
|
|
54
|
+
'crate': 'create',
|
|
55
|
+
'craete': 'create',
|
|
56
|
+
'preivew': 'preview',
|
|
57
|
+
'preveiw': 'preview',
|
|
58
|
+
'moble': 'mobile',
|
|
59
|
+
'moblie': 'mobile',
|
|
60
|
+
'relase': 'release',
|
|
61
|
+
'realease': 'release'
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Calculate Levenshtein distance between two strings
|
|
66
|
+
* Used for "Did you mean...?" suggestions
|
|
67
|
+
*/
|
|
68
|
+
function levenshteinDistance(a, b) {
|
|
69
|
+
const matrix = [];
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i <= b.length; i++) {
|
|
72
|
+
matrix[i] = [i];
|
|
73
|
+
}
|
|
74
|
+
for (let j = 0; j <= a.length; j++) {
|
|
75
|
+
matrix[0][j] = j;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (let i = 1; i <= b.length; i++) {
|
|
79
|
+
for (let j = 1; j <= a.length; j++) {
|
|
80
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
81
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
82
|
+
} else {
|
|
83
|
+
matrix[i][j] = Math.min(
|
|
84
|
+
matrix[i - 1][j - 1] + 1,
|
|
85
|
+
matrix[i][j - 1] + 1,
|
|
86
|
+
matrix[i - 1][j] + 1
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return matrix[b.length][a.length];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Suggest similar commands based on input
|
|
97
|
+
*/
|
|
98
|
+
function suggestCommand(input) {
|
|
99
|
+
// Check aliases first
|
|
100
|
+
if (input in commandAliases) {
|
|
101
|
+
return commandAliases[input];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Find closest command using Levenshtein distance
|
|
105
|
+
const allCommands = Object.keys(commands);
|
|
106
|
+
let closest = null;
|
|
107
|
+
let minDistance = Infinity;
|
|
108
|
+
|
|
109
|
+
for (const cmd of allCommands) {
|
|
110
|
+
const distance = levenshteinDistance(input.toLowerCase(), cmd.toLowerCase());
|
|
111
|
+
if (distance < minDistance && distance <= 3) { // Max 3 edits
|
|
112
|
+
minDistance = distance;
|
|
113
|
+
closest = cmd;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return closest;
|
|
118
|
+
}
|
|
119
|
+
|
|
35
120
|
/**
|
|
36
121
|
* Main entry point
|
|
37
122
|
*/
|
|
@@ -43,6 +128,13 @@ async function main() {
|
|
|
43
128
|
await commands[command](args.slice(1));
|
|
44
129
|
} else {
|
|
45
130
|
log.error(`Unknown command: ${command}`);
|
|
131
|
+
|
|
132
|
+
// Try to suggest a similar command
|
|
133
|
+
const suggestion = suggestCommand(command);
|
|
134
|
+
if (suggestion) {
|
|
135
|
+
log.info(`Did you mean: pulse ${suggestion}?`);
|
|
136
|
+
}
|
|
137
|
+
|
|
46
138
|
log.info('Run "pulse help" for usage information.');
|
|
47
139
|
process.exit(1);
|
|
48
140
|
}
|
|
@@ -68,14 +160,23 @@ Commands:
|
|
|
68
160
|
format [files] Format .pulse files consistently
|
|
69
161
|
analyze Analyze bundle size and dependencies
|
|
70
162
|
release <type> Create a new release (patch, minor, major)
|
|
163
|
+
docs-test Test documentation (syntax, imports, HTTP)
|
|
71
164
|
version Show version number
|
|
72
165
|
help Show this help message
|
|
73
166
|
|
|
167
|
+
Compile Options:
|
|
168
|
+
--watch, -w Watch files and recompile on changes
|
|
169
|
+
--dry-run Show what would be compiled without writing
|
|
170
|
+
--output, -o Output directory (default: same as input)
|
|
171
|
+
|
|
74
172
|
Lint Options:
|
|
75
173
|
--fix Auto-fix fixable issues
|
|
174
|
+
--watch, -w Watch files and re-lint on changes
|
|
175
|
+
--dry-run Show fixes without applying (use with --fix)
|
|
76
176
|
|
|
77
177
|
Format Options:
|
|
78
|
-
--check Check formatting without writing
|
|
178
|
+
--check Check formatting without writing (dry-run)
|
|
179
|
+
--watch, -w Watch files and re-format on changes
|
|
79
180
|
--write Write formatted output (default)
|
|
80
181
|
|
|
81
182
|
Analyze Options:
|
|
@@ -83,11 +184,16 @@ Analyze Options:
|
|
|
83
184
|
--verbose Show detailed metrics
|
|
84
185
|
|
|
85
186
|
Release Options:
|
|
86
|
-
--dry-run
|
|
87
|
-
--no-push
|
|
88
|
-
--title <text>
|
|
89
|
-
--skip-prompt
|
|
90
|
-
--
|
|
187
|
+
--dry-run Show what would be done without making changes
|
|
188
|
+
--no-push Create commit and tag but don't push
|
|
189
|
+
--title <text> Release title for changelog
|
|
190
|
+
--skip-prompt Use empty changelog (for automation)
|
|
191
|
+
--skip-docs-test Skip documentation tests before release
|
|
192
|
+
--from-commits Auto-extract changelog from git commits since last tag
|
|
193
|
+
|
|
194
|
+
Docs-test Options:
|
|
195
|
+
--verbose, -v Show detailed output
|
|
196
|
+
--no-http Skip HTTP server tests
|
|
91
197
|
|
|
92
198
|
Examples:
|
|
93
199
|
pulse create my-app
|
|
@@ -99,7 +205,10 @@ Examples:
|
|
|
99
205
|
pulse mobile build android
|
|
100
206
|
pulse mobile run ios
|
|
101
207
|
pulse compile src/App.pulse
|
|
208
|
+
pulse compile src/ --watch
|
|
209
|
+
pulse compile "**/*.pulse" --dry-run
|
|
102
210
|
pulse lint src/
|
|
211
|
+
pulse lint src/ --fix --dry-run
|
|
103
212
|
pulse lint "**/*.pulse" --fix
|
|
104
213
|
pulse format --check
|
|
105
214
|
pulse format src/App.pulse
|
|
@@ -109,6 +218,8 @@ Examples:
|
|
|
109
218
|
pulse release minor --title "New Features"
|
|
110
219
|
pulse release major --dry-run
|
|
111
220
|
pulse release patch --from-commits
|
|
221
|
+
pulse docs-test
|
|
222
|
+
pulse docs-test --verbose
|
|
112
223
|
|
|
113
224
|
Documentation: https://github.com/vincenthirtz/pulse-js-framework
|
|
114
225
|
`);
|
|
@@ -381,38 +492,209 @@ async function runReleaseCmd(args) {
|
|
|
381
492
|
}
|
|
382
493
|
|
|
383
494
|
/**
|
|
384
|
-
*
|
|
495
|
+
* Run documentation tests
|
|
385
496
|
*/
|
|
386
|
-
async function
|
|
387
|
-
const
|
|
497
|
+
async function runDocsTestCmd(args) {
|
|
498
|
+
const { runDocsTestCli } = await import('./docs-test.js');
|
|
499
|
+
await runDocsTestCli(args);
|
|
500
|
+
}
|
|
388
501
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
502
|
+
/**
|
|
503
|
+
* Compile .pulse files to JavaScript
|
|
504
|
+
* Supports multiple files, watch mode, and dry-run
|
|
505
|
+
*/
|
|
506
|
+
async function compileFiles(args) {
|
|
507
|
+
const { options, patterns } = parseArgs(args);
|
|
508
|
+
const watchMode = options.watch || options.w || false;
|
|
509
|
+
const dryRun = options['dry-run'] || false;
|
|
510
|
+
const outputDir = options.output || options.o || null;
|
|
511
|
+
|
|
512
|
+
// Find all .pulse files matching patterns
|
|
513
|
+
const files = findPulseFiles(patterns);
|
|
514
|
+
|
|
515
|
+
if (files.length === 0) {
|
|
516
|
+
log.error('No .pulse files found.');
|
|
517
|
+
log.info('Usage: pulse compile <file.pulse> [options]');
|
|
518
|
+
log.info(' pulse compile src/ --watch');
|
|
392
519
|
process.exit(1);
|
|
393
520
|
}
|
|
394
521
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
522
|
+
const { compile } = await import('../compiler/index.js');
|
|
523
|
+
const cwd = process.cwd();
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Compile a single file and return result
|
|
527
|
+
*/
|
|
528
|
+
function compileOneFile(inputFile) {
|
|
529
|
+
const relPath = relative(cwd, inputFile);
|
|
530
|
+
const source = readFileSync(inputFile, 'utf-8');
|
|
531
|
+
const result = compile(source);
|
|
532
|
+
|
|
533
|
+
// Determine output path
|
|
534
|
+
let outputFile;
|
|
535
|
+
if (outputDir) {
|
|
536
|
+
const baseName = inputFile.replace(/\.pulse$/, '.js').split(/[/\\]/).pop();
|
|
537
|
+
outputFile = join(outputDir, baseName);
|
|
538
|
+
} else {
|
|
539
|
+
outputFile = inputFile.replace(/\.pulse$/, '.js');
|
|
540
|
+
}
|
|
541
|
+
const relOutput = relative(cwd, outputFile);
|
|
542
|
+
|
|
543
|
+
return { inputFile, outputFile, relPath, relOutput, result, source };
|
|
398
544
|
}
|
|
399
545
|
|
|
400
|
-
|
|
546
|
+
/**
|
|
547
|
+
* Process compilation results
|
|
548
|
+
*/
|
|
549
|
+
function processResults(compilations) {
|
|
550
|
+
let successCount = 0;
|
|
551
|
+
let errorCount = 0;
|
|
552
|
+
|
|
553
|
+
for (const { relPath, relOutput, result } of compilations) {
|
|
554
|
+
if (result.success) {
|
|
555
|
+
successCount++;
|
|
556
|
+
} else {
|
|
557
|
+
errorCount++;
|
|
558
|
+
log.error(`\n${relPath}:`);
|
|
559
|
+
for (const error of result.errors) {
|
|
560
|
+
const loc = error.line ? `:${error.line}:${error.column || 1}` : '';
|
|
561
|
+
log.error(` ${relPath}${loc} ${error.message}`);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
401
565
|
|
|
402
|
-
|
|
403
|
-
|
|
566
|
+
return { successCount, errorCount };
|
|
567
|
+
}
|
|
404
568
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
569
|
+
/**
|
|
570
|
+
* Run compilation on all files
|
|
571
|
+
*/
|
|
572
|
+
async function runCompilation() {
|
|
573
|
+
const startTime = Date.now();
|
|
574
|
+
const compilations = files.map(compileOneFile);
|
|
575
|
+
const { successCount, errorCount } = processResults(compilations);
|
|
576
|
+
|
|
577
|
+
if (dryRun) {
|
|
578
|
+
// Dry run - show what would be done
|
|
579
|
+
log.info('\n[Dry Run] Would compile:');
|
|
580
|
+
for (const { relPath, relOutput, result } of compilations) {
|
|
581
|
+
if (result.success) {
|
|
582
|
+
log.info(` ${relPath} -> ${relOutput}`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
log.info(`\n${successCount} file(s) would be compiled, ${errorCount} error(s)`);
|
|
586
|
+
} else {
|
|
587
|
+
// Actually write files
|
|
588
|
+
let writtenCount = 0;
|
|
589
|
+
for (const { outputFile, relPath, relOutput, result } of compilations) {
|
|
590
|
+
if (result.success) {
|
|
591
|
+
// Ensure output directory exists
|
|
592
|
+
const outDir = dirname(outputFile);
|
|
593
|
+
if (!existsSync(outDir)) {
|
|
594
|
+
mkdirSync(outDir, { recursive: true });
|
|
595
|
+
}
|
|
596
|
+
writeFileSync(outputFile, result.code);
|
|
597
|
+
log.info(`Compiled: ${relPath} -> ${relOutput}`);
|
|
598
|
+
writtenCount++;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
const duration = Date.now() - startTime;
|
|
603
|
+
if (files.length > 1) {
|
|
604
|
+
log.info(`\n${writtenCount} file(s) compiled in ${duration}ms`);
|
|
605
|
+
if (errorCount > 0) {
|
|
606
|
+
log.error(`${errorCount} file(s) failed`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
413
609
|
}
|
|
414
|
-
|
|
610
|
+
|
|
611
|
+
return errorCount === 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Initial compilation
|
|
615
|
+
const success = await runCompilation();
|
|
616
|
+
|
|
617
|
+
if (!watchMode) {
|
|
618
|
+
if (!success) {
|
|
619
|
+
process.exit(1);
|
|
620
|
+
}
|
|
621
|
+
return;
|
|
415
622
|
}
|
|
623
|
+
|
|
624
|
+
// Watch mode
|
|
625
|
+
log.info('\nWatching for changes... (Ctrl+C to stop)\n');
|
|
626
|
+
|
|
627
|
+
// Collect directories to watch
|
|
628
|
+
const dirsToWatch = new Set();
|
|
629
|
+
for (const file of files) {
|
|
630
|
+
dirsToWatch.add(dirname(file));
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// Debounce mechanism
|
|
634
|
+
let debounceTimeout = null;
|
|
635
|
+
const changedFiles = new Set();
|
|
636
|
+
|
|
637
|
+
function handleChange(eventType, filename) {
|
|
638
|
+
if (!filename || !filename.endsWith('.pulse')) return;
|
|
639
|
+
|
|
640
|
+
// Find the full path of the changed file
|
|
641
|
+
for (const dir of dirsToWatch) {
|
|
642
|
+
const fullPath = join(dir, filename);
|
|
643
|
+
if (files.includes(fullPath)) {
|
|
644
|
+
changedFiles.add(fullPath);
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// Debounce: wait 100ms before processing
|
|
650
|
+
if (debounceTimeout) {
|
|
651
|
+
clearTimeout(debounceTimeout);
|
|
652
|
+
}
|
|
653
|
+
debounceTimeout = setTimeout(() => {
|
|
654
|
+
if (changedFiles.size > 0) {
|
|
655
|
+
log.info(`\nFile changed: ${[...changedFiles].map(f => relative(cwd, f)).join(', ')}`);
|
|
656
|
+
const filesToCompile = [...changedFiles];
|
|
657
|
+
changedFiles.clear();
|
|
658
|
+
|
|
659
|
+
// Recompile changed files
|
|
660
|
+
for (const file of filesToCompile) {
|
|
661
|
+
const { relPath, relOutput, outputFile, result } = compileOneFile(file);
|
|
662
|
+
if (result.success) {
|
|
663
|
+
if (!dryRun) {
|
|
664
|
+
writeFileSync(outputFile, result.code);
|
|
665
|
+
}
|
|
666
|
+
log.info(`${dryRun ? '[Dry Run] Would compile' : 'Compiled'}: ${relPath} -> ${relOutput}`);
|
|
667
|
+
} else {
|
|
668
|
+
log.error(`\n${relPath}:`);
|
|
669
|
+
for (const error of result.errors) {
|
|
670
|
+
const loc = error.line ? `:${error.line}:${error.column || 1}` : '';
|
|
671
|
+
log.error(` ${relPath}${loc} ${error.message}`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}, 100);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Start watching directories
|
|
680
|
+
const watchers = [];
|
|
681
|
+
for (const dir of dirsToWatch) {
|
|
682
|
+
try {
|
|
683
|
+
const watcher = watch(dir, { recursive: false }, handleChange);
|
|
684
|
+
watchers.push(watcher);
|
|
685
|
+
} catch (e) {
|
|
686
|
+
log.warn(`Could not watch directory: ${dir}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Keep process running
|
|
691
|
+
process.on('SIGINT', () => {
|
|
692
|
+
log.info('\nStopping watch mode...');
|
|
693
|
+
for (const watcher of watchers) {
|
|
694
|
+
watcher.close();
|
|
695
|
+
}
|
|
696
|
+
process.exit(0);
|
|
697
|
+
});
|
|
416
698
|
}
|
|
417
699
|
|
|
418
700
|
// Run main
|
package/cli/lint.js
CHANGED
|
@@ -706,10 +706,13 @@ export async function lintFile(filePath, options = {}) {
|
|
|
706
706
|
* Lint files and return summary
|
|
707
707
|
* @param {string[]} files - Files to lint
|
|
708
708
|
* @param {Object} options - Lint options
|
|
709
|
+
* @param {boolean} options.fix - Auto-fix issues
|
|
710
|
+
* @param {boolean} options.dryRun - Show fixes without applying
|
|
711
|
+
* @param {boolean} options.quiet - Suppress output
|
|
709
712
|
* @returns {Object} Summary with totals
|
|
710
713
|
*/
|
|
711
714
|
async function lintFiles(files, options = {}) {
|
|
712
|
-
const { fix = false, quiet = false } = options;
|
|
715
|
+
const { fix = false, dryRun = false, quiet = false } = options;
|
|
713
716
|
const timer = createTimer();
|
|
714
717
|
|
|
715
718
|
let totalErrors = 0;
|
|
@@ -749,8 +752,14 @@ async function lintFiles(files, options = {}) {
|
|
|
749
752
|
export async function runLint(args) {
|
|
750
753
|
const { options, patterns } = parseArgs(args);
|
|
751
754
|
const fix = options.fix || false;
|
|
755
|
+
const dryRun = options['dry-run'] || false;
|
|
752
756
|
const watchMode = options.watch || options.w || false;
|
|
753
757
|
|
|
758
|
+
// Dry-run only makes sense with --fix
|
|
759
|
+
if (dryRun && !fix) {
|
|
760
|
+
log.warn('Note: --dry-run has no effect without --fix');
|
|
761
|
+
}
|
|
762
|
+
|
|
754
763
|
// Find files to lint
|
|
755
764
|
const files = findPulseFiles(patterns);
|
|
756
765
|
|
|
@@ -760,8 +769,8 @@ export async function runLint(args) {
|
|
|
760
769
|
}
|
|
761
770
|
|
|
762
771
|
// Initial lint run
|
|
763
|
-
log.info(`Linting ${files.length} file(s)
|
|
764
|
-
const summary = await lintFiles(files, { fix });
|
|
772
|
+
log.info(`Linting ${files.length} file(s)...${dryRun ? ' (dry-run)' : ''}\n`);
|
|
773
|
+
const summary = await lintFiles(files, { fix, dryRun });
|
|
765
774
|
|
|
766
775
|
// Print summary
|
|
767
776
|
printLintSummary(summary, files.length);
|
|
@@ -795,7 +804,7 @@ export async function runLint(args) {
|
|
|
795
804
|
debounceTimers.delete(filePath);
|
|
796
805
|
|
|
797
806
|
log.info(`\n[${new Date().toLocaleTimeString()}] File changed: ${relativePath(filePath)}`);
|
|
798
|
-
lintFiles([filePath], { fix }).then(result => {
|
|
807
|
+
lintFiles([filePath], { fix, dryRun }).then(result => {
|
|
799
808
|
printLintSummary(result, 1, true);
|
|
800
809
|
});
|
|
801
810
|
}, 100));
|
package/cli/logger.js
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pulse CLI Logger
|
|
3
|
-
*
|
|
3
|
+
* Adapter for CLI tools using the unified runtime logger
|
|
4
4
|
* @module pulse-cli/logger
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { createLogger, setLogLevel, LogLevel } from '../runtime/logger.js';
|
|
8
|
+
|
|
9
|
+
// Create CLI-namespaced logger
|
|
10
|
+
const cliLogger = createLogger('CLI');
|
|
11
|
+
|
|
7
12
|
/** @type {boolean} */
|
|
8
13
|
let verboseMode = false;
|
|
9
14
|
|
|
@@ -17,6 +22,12 @@ let verboseMode = false;
|
|
|
17
22
|
*/
|
|
18
23
|
export function setVerbose(enabled) {
|
|
19
24
|
verboseMode = enabled;
|
|
25
|
+
// Update global log level to include debug when verbose
|
|
26
|
+
if (enabled) {
|
|
27
|
+
setLogLevel(LogLevel.DEBUG);
|
|
28
|
+
} else {
|
|
29
|
+
setLogLevel(LogLevel.INFO);
|
|
30
|
+
}
|
|
20
31
|
}
|
|
21
32
|
|
|
22
33
|
/**
|
|
@@ -33,6 +44,7 @@ export function isVerbose() {
|
|
|
33
44
|
|
|
34
45
|
/**
|
|
35
46
|
* CLI Logger object with console-like API
|
|
47
|
+
* Uses the runtime logger under the hood for consistency
|
|
36
48
|
* @namespace log
|
|
37
49
|
*/
|
|
38
50
|
export const log = {
|
|
@@ -44,6 +56,7 @@ export const log = {
|
|
|
44
56
|
* log.info('Starting server on port', 3000);
|
|
45
57
|
*/
|
|
46
58
|
info(...args) {
|
|
59
|
+
// Use console directly for CLI output (no namespace prefix for info)
|
|
47
60
|
console.log(...args);
|
|
48
61
|
},
|
|
49
62
|
|
|
@@ -66,7 +79,7 @@ export const log = {
|
|
|
66
79
|
* log.warn('Deprecated feature used');
|
|
67
80
|
*/
|
|
68
81
|
warn(...args) {
|
|
69
|
-
|
|
82
|
+
cliLogger.warn(...args);
|
|
70
83
|
},
|
|
71
84
|
|
|
72
85
|
/**
|
|
@@ -77,7 +90,7 @@ export const log = {
|
|
|
77
90
|
* log.error('Failed to compile:', error.message);
|
|
78
91
|
*/
|
|
79
92
|
error(...args) {
|
|
80
|
-
|
|
93
|
+
cliLogger.error(...args);
|
|
81
94
|
},
|
|
82
95
|
|
|
83
96
|
/**
|
|
@@ -89,7 +102,7 @@ export const log = {
|
|
|
89
102
|
*/
|
|
90
103
|
debug(...args) {
|
|
91
104
|
if (verboseMode) {
|
|
92
|
-
|
|
105
|
+
cliLogger.debug(...args);
|
|
93
106
|
}
|
|
94
107
|
},
|
|
95
108
|
|
|
@@ -116,7 +129,22 @@ export const log = {
|
|
|
116
129
|
*/
|
|
117
130
|
newline() {
|
|
118
131
|
console.log();
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create a child logger with a sub-namespace
|
|
136
|
+
* @param {string} namespace - Child namespace
|
|
137
|
+
* @returns {import('../runtime/logger.js').Logger} Child logger instance
|
|
138
|
+
* @example
|
|
139
|
+
* const buildLog = log.child('Build');
|
|
140
|
+
* buildLog.info('Starting...'); // [CLI:Build] Starting...
|
|
141
|
+
*/
|
|
142
|
+
child(namespace) {
|
|
143
|
+
return cliLogger.child(namespace);
|
|
119
144
|
}
|
|
120
145
|
};
|
|
121
146
|
|
|
147
|
+
// Re-export LogLevel for convenience
|
|
148
|
+
export { LogLevel };
|
|
149
|
+
|
|
122
150
|
export default log;
|
package/cli/release.js
CHANGED
|
@@ -10,13 +10,15 @@
|
|
|
10
10
|
* - Push to remote
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
13
|
+
import { readFileSync, writeFileSync, existsSync, unlinkSync } from 'fs';
|
|
14
14
|
import { join, dirname } from 'path';
|
|
15
15
|
import { fileURLToPath } from 'url';
|
|
16
16
|
import { execSync } from 'child_process';
|
|
17
|
+
import { tmpdir } from 'os';
|
|
17
18
|
import { createInterface } from 'readline';
|
|
18
19
|
import https from 'https';
|
|
19
20
|
import { log } from './logger.js';
|
|
21
|
+
import { runDocsTest } from './docs-test.js';
|
|
20
22
|
|
|
21
23
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
22
24
|
const root = join(__dirname, '..');
|
|
@@ -525,14 +527,15 @@ function gitCommitTagPush(newVersion, title, changes, dryRun = false) {
|
|
|
525
527
|
log.info(' Running: git add -A...');
|
|
526
528
|
execSync('git add -A', { cwd: root, stdio: 'inherit' });
|
|
527
529
|
|
|
528
|
-
// git commit
|
|
530
|
+
// git commit using temp file for cross-platform compatibility
|
|
529
531
|
log.info(' Running: git commit...');
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
stdio: 'inherit'
|
|
534
|
-
|
|
535
|
-
|
|
532
|
+
const tempFile = join(tmpdir(), `pulse-release-${Date.now()}.txt`);
|
|
533
|
+
writeFileSync(tempFile, commitMessage, 'utf-8');
|
|
534
|
+
try {
|
|
535
|
+
execSync(`git commit -F "${tempFile}"`, { cwd: root, stdio: 'inherit' });
|
|
536
|
+
} finally {
|
|
537
|
+
unlinkSync(tempFile);
|
|
538
|
+
}
|
|
536
539
|
|
|
537
540
|
// git tag
|
|
538
541
|
log.info(` Running: git tag v${newVersion}...`);
|
|
@@ -559,12 +562,13 @@ Types:
|
|
|
559
562
|
major Bump major version (1.0.0 -> 2.0.0)
|
|
560
563
|
|
|
561
564
|
Options:
|
|
562
|
-
--dry-run
|
|
563
|
-
--no-push
|
|
564
|
-
--title <text>
|
|
565
|
-
--skip-prompt
|
|
566
|
-
--
|
|
567
|
-
--
|
|
565
|
+
--dry-run Show what would be done without making changes
|
|
566
|
+
--no-push Create commit and tag but don't push
|
|
567
|
+
--title <text> Release title (e.g., "Performance Improvements")
|
|
568
|
+
--skip-prompt Use empty changelog (for automated releases)
|
|
569
|
+
--skip-docs-test Skip documentation validation before release
|
|
570
|
+
--from-commits Auto-extract changelog from git commits since last tag
|
|
571
|
+
--yes, -y Auto-confirm all prompts
|
|
568
572
|
--changes <json> Pass changelog as JSON (e.g., '{"added":["Feature 1"],"fixed":["Bug 1"]}')
|
|
569
573
|
--added <items> Comma-separated list of added features
|
|
570
574
|
--changed <items> Comma-separated list of changes
|
|
@@ -599,6 +603,7 @@ export async function runRelease(args) {
|
|
|
599
603
|
const skipPrompt = args.includes('--skip-prompt');
|
|
600
604
|
const fromCommits = args.includes('--from-commits');
|
|
601
605
|
const autoConfirm = args.includes('--yes') || args.includes('-y');
|
|
606
|
+
const skipDocsTest = args.includes('--skip-docs-test');
|
|
602
607
|
|
|
603
608
|
let title = '';
|
|
604
609
|
const titleIndex = args.indexOf('--title');
|
|
@@ -675,6 +680,30 @@ export async function runRelease(args) {
|
|
|
675
680
|
process.exit(1);
|
|
676
681
|
}
|
|
677
682
|
|
|
683
|
+
// Run documentation tests
|
|
684
|
+
if (!skipDocsTest) {
|
|
685
|
+
log.info('');
|
|
686
|
+
log.info('Running documentation tests...');
|
|
687
|
+
|
|
688
|
+
const docsTestResult = await runDocsTest({ verbose: false, httpTest: true });
|
|
689
|
+
|
|
690
|
+
if (!docsTestResult.success) {
|
|
691
|
+
log.error('Documentation tests failed. Fix errors before releasing.');
|
|
692
|
+
if (!autoConfirm) {
|
|
693
|
+
const proceed = await prompt('Continue anyway? (y/N) ');
|
|
694
|
+
if (proceed.toLowerCase() !== 'y') {
|
|
695
|
+
log.info('Aborted.');
|
|
696
|
+
process.exit(1);
|
|
697
|
+
}
|
|
698
|
+
} else {
|
|
699
|
+
log.error('Aborting release due to documentation errors.');
|
|
700
|
+
process.exit(1);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
} else {
|
|
704
|
+
log.warn('Skipping documentation tests (--skip-docs-test)');
|
|
705
|
+
}
|
|
706
|
+
|
|
678
707
|
// Collect changelog entries
|
|
679
708
|
let changes = { added: [], changed: [], fixed: [], removed: [] };
|
|
680
709
|
|
|
@@ -786,12 +815,13 @@ export async function runRelease(args) {
|
|
|
786
815
|
// Only commit and tag, no push
|
|
787
816
|
const commitMessage = buildCommitMessage(newVersion, title, changes);
|
|
788
817
|
execSync('git add -A', { cwd: root, stdio: 'inherit' });
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
stdio: 'inherit'
|
|
793
|
-
|
|
794
|
-
|
|
818
|
+
const tempFile = join(tmpdir(), `pulse-release-${Date.now()}.txt`);
|
|
819
|
+
writeFileSync(tempFile, commitMessage, 'utf-8');
|
|
820
|
+
try {
|
|
821
|
+
execSync(`git commit -F "${tempFile}"`, { cwd: root, stdio: 'inherit' });
|
|
822
|
+
} finally {
|
|
823
|
+
unlinkSync(tempFile);
|
|
824
|
+
}
|
|
795
825
|
execSync(`git tag -a v${newVersion} -m "Release v${newVersion}"`, { cwd: root, stdio: 'inherit' });
|
|
796
826
|
log.info(' Created commit and tag (--no-push specified)');
|
|
797
827
|
} else {
|
package/compiler/parser.js
CHANGED