pulse-js-framework 1.7.3 → 1.7.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/cli/format.js CHANGED
@@ -3,9 +3,11 @@
3
3
  * Formats .pulse files consistently
4
4
  */
5
5
 
6
- import { readFileSync, writeFileSync } from 'fs';
6
+ import { readFileSync, writeFileSync, watch } from 'fs';
7
+ import { dirname } from 'path';
7
8
  import { findPulseFiles, parseArgs, relativePath } from './utils/file-utils.js';
8
9
  import { log } from './logger.js';
10
+ import { createTimer, formatDuration } from './utils/cli-ui.js';
9
11
 
10
12
  /**
11
13
  * Default format options
@@ -641,6 +643,7 @@ export async function runFormat(args) {
641
643
  const { options, patterns } = parseArgs(args);
642
644
  const check = options.check || false;
643
645
  const write = !check; // Default to write unless --check is specified
646
+ const watchMode = options.watch || options.w || false;
644
647
 
645
648
  // Find files to format
646
649
  const files = findPulseFiles(patterns);
@@ -650,7 +653,57 @@ export async function runFormat(args) {
650
653
  return;
651
654
  }
652
655
 
653
- log.info(`${check ? 'Checking' : 'Formatting'} ${files.length} file(s)...\n`);
656
+ // Run initial format
657
+ const result = await runFormatOnFiles(files, { check, write, options });
658
+
659
+ // If watch mode, set up file watchers
660
+ if (watchMode) {
661
+ if (check) {
662
+ log.warn('--watch mode is not available with --check');
663
+ return;
664
+ }
665
+
666
+ log.info('\nWatching for changes... (Ctrl+C to stop)\n');
667
+
668
+ // Get unique directories to watch
669
+ const watchedDirs = new Set(files.map(f => dirname(f)));
670
+
671
+ // Debounce timer
672
+ let debounceTimer = null;
673
+ const debounceDelay = 100;
674
+
675
+ for (const dir of watchedDirs) {
676
+ watch(dir, { recursive: false }, (_eventType, filename) => {
677
+ if (!filename || !filename.endsWith('.pulse')) return;
678
+
679
+ // Debounce rapid changes
680
+ if (debounceTimer) clearTimeout(debounceTimer);
681
+ debounceTimer = setTimeout(() => {
682
+ const changedFiles = findPulseFiles(patterns);
683
+ runFormatOnFiles(changedFiles, { check: false, write: true, options, isRerun: true })
684
+ .then(() => {
685
+ log.info('Watching for changes...\n');
686
+ });
687
+ }, debounceDelay);
688
+ });
689
+ }
690
+
691
+ // Keep process alive
692
+ process.stdin.resume();
693
+ } else if (check && result.changedCount > 0) {
694
+ process.exit(1);
695
+ }
696
+ }
697
+
698
+ /**
699
+ * Run format on a list of files
700
+ */
701
+ async function runFormatOnFiles(files, { check, write, options, isRerun = false }) {
702
+ const timer = createTimer();
703
+
704
+ if (!isRerun) {
705
+ log.info(`${check ? 'Checking' : 'Formatting'} ${files.length} file(s)...\n`);
706
+ }
654
707
 
655
708
  let changedCount = 0;
656
709
  let errorCount = 0;
@@ -675,12 +728,14 @@ export async function runFormat(args) {
675
728
  log.info(` ${relPath} - formatted`);
676
729
  }
677
730
  } else {
678
- if (!check) {
731
+ if (!check && !isRerun) {
679
732
  log.info(` ${relPath} - unchanged`);
680
733
  }
681
734
  }
682
735
  }
683
736
 
737
+ const elapsed = timer.elapsed();
738
+
684
739
  // Summary
685
740
  log.info('\n' + '─'.repeat(60));
686
741
 
@@ -690,16 +745,17 @@ export async function runFormat(args) {
690
745
 
691
746
  if (check) {
692
747
  if (changedCount > 0) {
693
- log.error(`✗ ${changedCount} file(s) need formatting`);
694
- process.exit(1);
748
+ log.error(`✗ ${changedCount} file(s) need formatting (${formatDuration(elapsed)})`);
695
749
  } else {
696
- log.success(`✓ All ${files.length} file(s) are properly formatted`);
750
+ log.success(`✓ All ${files.length} file(s) are properly formatted (${formatDuration(elapsed)})`);
697
751
  }
698
752
  } else {
699
753
  if (changedCount > 0) {
700
- log.success(`✓ ${changedCount} file(s) formatted`);
754
+ log.success(`✓ ${changedCount} file(s) formatted (${formatDuration(elapsed)})`);
701
755
  } else {
702
- log.success(`✓ All ${files.length} file(s) were already formatted`);
756
+ log.success(`✓ All ${files.length} file(s) were already formatted (${formatDuration(elapsed)})`);
703
757
  }
704
758
  }
759
+
760
+ return { changedCount, errorCount };
705
761
  }
package/cli/lint.js CHANGED
@@ -3,9 +3,11 @@
3
3
  * Validates .pulse files for errors and style issues
4
4
  */
5
5
 
6
- import { readFileSync, writeFileSync } from 'fs';
6
+ import { readFileSync, writeFileSync, watch } from 'fs';
7
+ import { dirname } from 'path';
7
8
  import { findPulseFiles, parseArgs, relativePath } from './utils/file-utils.js';
8
9
  import { log } from './logger.js';
10
+ import { createTimer, formatDuration } from './utils/cli-ui.js';
9
11
 
10
12
  /**
11
13
  * Lint rules configuration
@@ -701,21 +703,14 @@ export async function lintFile(filePath, options = {}) {
701
703
  }
702
704
 
703
705
  /**
704
- * Main lint command handler
706
+ * Lint files and return summary
707
+ * @param {string[]} files - Files to lint
708
+ * @param {Object} options - Lint options
709
+ * @returns {Object} Summary with totals
705
710
  */
706
- export async function runLint(args) {
707
- const { options, patterns } = parseArgs(args);
708
- const fix = options.fix || false;
709
-
710
- // Find files to lint
711
- const files = findPulseFiles(patterns);
712
-
713
- if (files.length === 0) {
714
- log.info('No .pulse files found to lint.');
715
- return;
716
- }
717
-
718
- log.info(`Linting ${files.length} file(s)...\n`);
711
+ async function lintFiles(files, options = {}) {
712
+ const { fix = false, quiet = false } = options;
713
+ const timer = createTimer();
719
714
 
720
715
  let totalErrors = 0;
721
716
  let totalWarnings = 0;
@@ -725,7 +720,7 @@ export async function runLint(args) {
725
720
  const result = await lintFile(file, { fix });
726
721
  const relPath = relativePath(file);
727
722
 
728
- if (result.diagnostics.length > 0) {
723
+ if (result.diagnostics.length > 0 && !quiet) {
729
724
  log.info(`\n${relPath}`);
730
725
 
731
726
  for (const diag of result.diagnostics) {
@@ -740,21 +735,111 @@ export async function runLint(args) {
740
735
  }
741
736
  }
742
737
 
743
- // Summary
738
+ return {
739
+ errors: totalErrors,
740
+ warnings: totalWarnings,
741
+ info: totalInfo,
742
+ elapsed: timer.elapsed()
743
+ };
744
+ }
745
+
746
+ /**
747
+ * Main lint command handler
748
+ */
749
+ export async function runLint(args) {
750
+ const { options, patterns } = parseArgs(args);
751
+ const fix = options.fix || false;
752
+ const watchMode = options.watch || options.w || false;
753
+
754
+ // Find files to lint
755
+ const files = findPulseFiles(patterns);
756
+
757
+ if (files.length === 0) {
758
+ log.info('No .pulse files found to lint.');
759
+ return;
760
+ }
761
+
762
+ // Initial lint run
763
+ log.info(`Linting ${files.length} file(s)...\n`);
764
+ const summary = await lintFiles(files, { fix });
765
+
766
+ // Print summary
767
+ printLintSummary(summary, files.length);
768
+
769
+ // Watch mode
770
+ if (watchMode) {
771
+ log.info('\nWatching for changes... (Ctrl+C to stop)\n');
772
+
773
+ const watchedDirs = new Set();
774
+ const debounceTimers = new Map();
775
+
776
+ // Collect directories to watch
777
+ for (const file of files) {
778
+ watchedDirs.add(dirname(file));
779
+ }
780
+
781
+ // Watch each directory
782
+ for (const dir of watchedDirs) {
783
+ watch(dir, { recursive: false }, (_eventType, filename) => {
784
+ if (!filename || !filename.endsWith('.pulse')) return;
785
+
786
+ const filePath = files.find(f => f.endsWith(filename));
787
+ if (!filePath) return;
788
+
789
+ // Debounce rapid changes
790
+ if (debounceTimers.has(filePath)) {
791
+ clearTimeout(debounceTimers.get(filePath));
792
+ }
793
+
794
+ debounceTimers.set(filePath, setTimeout(() => {
795
+ debounceTimers.delete(filePath);
796
+
797
+ log.info(`\n[${new Date().toLocaleTimeString()}] File changed: ${relativePath(filePath)}`);
798
+ lintFiles([filePath], { fix }).then(result => {
799
+ printLintSummary(result, 1, true);
800
+ });
801
+ }, 100));
802
+ });
803
+ }
804
+
805
+ // Keep process running
806
+ return new Promise(() => {});
807
+ } else {
808
+ // Exit with error code if errors found
809
+ if (summary.errors > 0) {
810
+ process.exit(1);
811
+ }
812
+ }
813
+ }
814
+
815
+ /**
816
+ * Print lint summary
817
+ */
818
+ function printLintSummary(summary, fileCount, compact = false) {
819
+ const { errors, warnings, info, elapsed } = summary;
820
+ const timeStr = formatDuration(elapsed);
821
+
822
+ if (compact) {
823
+ const parts = [];
824
+ if (errors > 0) parts.push(`${errors} error(s)`);
825
+ if (warnings > 0) parts.push(`${warnings} warning(s)`);
826
+ if (parts.length === 0) {
827
+ log.success(`✓ Passed (${timeStr})`);
828
+ } else {
829
+ log.error(`✗ ${parts.join(', ')} (${timeStr})`);
830
+ }
831
+ return;
832
+ }
833
+
744
834
  log.info('\n' + '─'.repeat(60));
745
835
  const parts = [];
746
- if (totalErrors > 0) parts.push(`${totalErrors} error(s)`);
747
- if (totalWarnings > 0) parts.push(`${totalWarnings} warning(s)`);
748
- if (totalInfo > 0) parts.push(`${totalInfo} info`);
836
+ if (errors > 0) parts.push(`${errors} error(s)`);
837
+ if (warnings > 0) parts.push(`${warnings} warning(s)`);
838
+ if (info > 0) parts.push(`${info} info`);
749
839
 
750
840
  if (parts.length === 0) {
751
- log.success(`✓ ${files.length} file(s) passed`);
841
+ log.success(`✓ ${fileCount} file(s) passed (${timeStr})`);
752
842
  } else {
753
- log.error(`✗ ${parts.join(', ')} in ${files.length} file(s)`);
754
- }
755
-
756
- // Exit with error code if errors found
757
- if (totalErrors > 0) {
758
- process.exit(1);
843
+ log.error(`✗ ${parts.join(', ')} in ${fileCount} file(s) (${timeStr})`);
759
844
  }
760
845
  }