pulse-js-framework 1.7.23 → 1.7.24

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/release.js CHANGED
@@ -508,6 +508,46 @@ function buildCommitMessage(newVersion, title, changes) {
508
508
  return message;
509
509
  }
510
510
 
511
+ /**
512
+ * Verify that a git tag exists locally
513
+ */
514
+ function verifyTagExists(version) {
515
+ try {
516
+ const tags = execSync('git tag -l', { cwd: root, encoding: 'utf-8' });
517
+ return tags.split('\n').includes(`v${version}`);
518
+ } catch {
519
+ return false;
520
+ }
521
+ }
522
+
523
+ /**
524
+ * Verify that a git tag exists on remote
525
+ */
526
+ function verifyTagOnRemote(version) {
527
+ try {
528
+ const remoteTags = execSync('git ls-remote --tags origin', { cwd: root, encoding: 'utf-8' });
529
+ return remoteTags.includes(`refs/tags/v${version}`);
530
+ } catch {
531
+ return false;
532
+ }
533
+ }
534
+
535
+ /**
536
+ * Execute a git command with error handling
537
+ */
538
+ function execGitCommand(command, description) {
539
+ log.info(` Running: ${description}...`);
540
+ try {
541
+ execSync(command, { cwd: root, stdio: 'inherit' });
542
+ return { success: true };
543
+ } catch (error) {
544
+ log.error(` Failed: ${description}`);
545
+ log.error(` Command: ${command}`);
546
+ log.error(` Error: ${error.message}`);
547
+ return { success: false, error };
548
+ }
549
+ }
550
+
511
551
  /**
512
552
  * Execute git commands
513
553
  */
@@ -521,33 +561,142 @@ function gitCommitTagPush(newVersion, title, changes, dryRun = false) {
521
561
  log.info(` [dry-run] git tag -a v${newVersion} -m "Release v${newVersion}"`);
522
562
  log.info(' [dry-run] git push');
523
563
  log.info(' [dry-run] git push --tags');
524
- return;
564
+ return { success: true };
525
565
  }
526
566
 
527
567
  // git add
528
- log.info(' Running: git add -A...');
529
- execSync('git add -A', { cwd: root, stdio: 'inherit' });
568
+ let result = execGitCommand('git add -A', 'git add -A');
569
+ if (!result.success) {
570
+ return { success: false, stage: 'add', error: result.error };
571
+ }
530
572
 
531
573
  // git commit using temp file for cross-platform compatibility
532
- log.info(' Running: git commit...');
533
574
  const tempFile = join(tmpdir(), `pulse-release-${Date.now()}.txt`);
534
575
  writeFileSync(tempFile, commitMessage, 'utf-8');
535
576
  try {
536
- execSync(`git commit -F "${tempFile}"`, { cwd: root, stdio: 'inherit' });
577
+ result = execGitCommand(`git commit -F "${tempFile}"`, 'git commit');
578
+ if (!result.success) {
579
+ return { success: false, stage: 'commit', error: result.error };
580
+ }
537
581
  } finally {
538
- unlinkSync(tempFile);
582
+ try {
583
+ unlinkSync(tempFile);
584
+ } catch {
585
+ // Ignore cleanup errors
586
+ }
539
587
  }
540
588
 
541
589
  // git tag
542
- log.info(` Running: git tag v${newVersion}...`);
543
- execSync(`git tag -a v${newVersion} -m "Release v${newVersion}"`, { cwd: root, stdio: 'inherit' });
590
+ result = execGitCommand(
591
+ `git tag -a v${newVersion} -m "Release v${newVersion}"`,
592
+ `git tag v${newVersion}`
593
+ );
594
+ if (!result.success) {
595
+ log.error('');
596
+ log.error('Tag creation failed. The commit was created but not tagged.');
597
+ log.error('You can manually create the tag with:');
598
+ log.error(` git tag -a v${newVersion} -m "Release v${newVersion}"`);
599
+ return { success: false, stage: 'tag', error: result.error };
600
+ }
601
+
602
+ // Verify tag was created
603
+ if (!verifyTagExists(newVersion)) {
604
+ log.error('');
605
+ log.error('Tag creation appeared to succeed but tag not found locally.');
606
+ log.error('You can manually create the tag with:');
607
+ log.error(` git tag -a v${newVersion} -m "Release v${newVersion}"`);
608
+ return { success: false, stage: 'tag-verify', error: new Error('Tag not found after creation') };
609
+ }
610
+ log.info(` Verified: tag v${newVersion} exists locally`);
544
611
 
545
612
  // git push
546
- log.info(' Running: git push...');
547
- execSync('git push', { cwd: root, stdio: 'inherit' });
613
+ result = execGitCommand('git push', 'git push');
614
+ if (!result.success) {
615
+ log.error('');
616
+ log.error('Push failed. Commit and tag were created locally.');
617
+ log.error('You can manually push with:');
618
+ log.error(' git push && git push --tags');
619
+ return { success: false, stage: 'push', error: result.error };
620
+ }
621
+
622
+ // git push --tags
623
+ result = execGitCommand('git push --tags', 'git push --tags');
624
+ if (!result.success) {
625
+ log.error('');
626
+ log.error('Tag push failed. The tag exists locally but not on remote.');
627
+ log.error('You can manually push tags with:');
628
+ log.error(' git push --tags');
629
+ return { success: false, stage: 'push-tags', error: result.error };
630
+ }
631
+
632
+ // Verify tag was pushed to remote
633
+ if (!verifyTagOnRemote(newVersion)) {
634
+ log.warn('');
635
+ log.warn('Tag push appeared to succeed but tag not found on remote.');
636
+ log.warn('You may need to manually verify or push with:');
637
+ log.warn(' git push --tags');
638
+ } else {
639
+ log.info(` Verified: tag v${newVersion} exists on remote`);
640
+ }
548
641
 
549
- log.info(' Running: git push --tags...');
550
- execSync('git push --tags', { cwd: root, stdio: 'inherit' });
642
+ return { success: true };
643
+ }
644
+
645
+ /**
646
+ * Execute git commands without pushing (--no-push mode)
647
+ */
648
+ function gitCommitTagNoPush(newVersion, title, changes) {
649
+ const commitMessage = buildCommitMessage(newVersion, title, changes);
650
+
651
+ // git add
652
+ let result = execGitCommand('git add -A', 'git add -A');
653
+ if (!result.success) {
654
+ return { success: false, stage: 'add', error: result.error };
655
+ }
656
+
657
+ // git commit using temp file for cross-platform compatibility
658
+ const tempFile = join(tmpdir(), `pulse-release-${Date.now()}.txt`);
659
+ writeFileSync(tempFile, commitMessage, 'utf-8');
660
+ try {
661
+ result = execGitCommand(`git commit -F "${tempFile}"`, 'git commit');
662
+ if (!result.success) {
663
+ return { success: false, stage: 'commit', error: result.error };
664
+ }
665
+ } finally {
666
+ try {
667
+ unlinkSync(tempFile);
668
+ } catch {
669
+ // Ignore cleanup errors
670
+ }
671
+ }
672
+
673
+ // git tag
674
+ result = execGitCommand(
675
+ `git tag -a v${newVersion} -m "Release v${newVersion}"`,
676
+ `git tag v${newVersion}`
677
+ );
678
+ if (!result.success) {
679
+ log.error('');
680
+ log.error('Tag creation failed. The commit was created but not tagged.');
681
+ log.error('You can manually create the tag with:');
682
+ log.error(` git tag -a v${newVersion} -m "Release v${newVersion}"`);
683
+ return { success: false, stage: 'tag', error: result.error };
684
+ }
685
+
686
+ // Verify tag was created
687
+ if (!verifyTagExists(newVersion)) {
688
+ log.error('');
689
+ log.error('Tag creation appeared to succeed but tag not found locally.');
690
+ log.error('You can manually create the tag with:');
691
+ log.error(` git tag -a v${newVersion} -m "Release v${newVersion}"`);
692
+ return { success: false, stage: 'tag-verify', error: new Error('Tag not found after creation') };
693
+ }
694
+
695
+ log.info(` Verified: tag v${newVersion} exists locally`);
696
+ log.info(' Created commit and tag (--no-push specified)');
697
+ log.info(' To push later, run: git push && git push --tags');
698
+
699
+ return { success: true };
551
700
  }
552
701
 
553
702
  /**
@@ -811,25 +960,24 @@ export async function runRelease(args) {
811
960
  log.info('');
812
961
  log.info('Git operations...');
813
962
 
963
+ let gitResult = { success: true };
964
+
814
965
  if (!dryRun) {
815
966
  if (noPush) {
816
967
  // Only commit and tag, no push
817
- const commitMessage = buildCommitMessage(newVersion, title, changes);
818
- execSync('git add -A', { cwd: root, stdio: 'inherit' });
819
- const tempFile = join(tmpdir(), `pulse-release-${Date.now()}.txt`);
820
- writeFileSync(tempFile, commitMessage, 'utf-8');
821
- try {
822
- execSync(`git commit -F "${tempFile}"`, { cwd: root, stdio: 'inherit' });
823
- } finally {
824
- unlinkSync(tempFile);
825
- }
826
- execSync(`git tag -a v${newVersion} -m "Release v${newVersion}"`, { cwd: root, stdio: 'inherit' });
827
- log.info(' Created commit and tag (--no-push specified)');
968
+ gitResult = gitCommitTagNoPush(newVersion, title, changes);
828
969
  } else {
829
- gitCommitTagPush(newVersion, title, changes, false);
970
+ gitResult = gitCommitTagPush(newVersion, title, changes, false);
830
971
  }
831
972
  } else {
832
- gitCommitTagPush(newVersion, title, changes, true);
973
+ gitResult = gitCommitTagPush(newVersion, title, changes, true);
974
+ }
975
+
976
+ if (!gitResult.success) {
977
+ log.error('');
978
+ log.error(`Release failed at stage: ${gitResult.stage}`);
979
+ log.error('Please fix the issue and retry, or complete the release manually.');
980
+ process.exit(1);
833
981
  }
834
982
 
835
983
  log.info('');
@@ -2,32 +2,42 @@
2
2
  * Pulse Vite Plugin
3
3
  *
4
4
  * Enables .pulse file support in Vite projects
5
+ * Extracts CSS to virtual .css modules so Vite's CSS pipeline handles them
6
+ * (prevents JS minifier from corrupting CSS in template literals)
5
7
  */
6
8
 
7
9
  import { compile } from '../compiler/index.js';
8
10
  import { existsSync } from 'fs';
9
11
  import { resolve, dirname } from 'path';
10
12
 
13
+ // Virtual module ID for extracted CSS (uses .css extension so Vite treats it as CSS)
14
+ const VIRTUAL_CSS_SUFFIX = '.pulse.css';
15
+
11
16
  /**
12
17
  * Create Pulse Vite plugin
13
18
  */
14
19
  export default function pulsePlugin(options = {}) {
15
20
  const {
16
- include = /\.pulse$/,
17
21
  exclude = /node_modules/,
18
22
  sourceMap = true
19
23
  } = options;
20
24
 
25
+ // Store extracted CSS for each .pulse module
26
+ const cssMap = new Map();
27
+
21
28
  return {
22
29
  name: 'vite-plugin-pulse',
23
30
  enforce: 'pre',
24
31
 
25
32
  /**
26
- * Resolve .pulse files and .js imports that map to .pulse files
27
- * The compiler transforms .pulse imports to .js, so we need to
28
- * resolve them back to .pulse for Vite to process them
33
+ * Resolve .pulse files and virtual CSS modules
29
34
  */
30
35
  resolveId(id, importer) {
36
+ // Handle virtual CSS module resolution
37
+ if (id.endsWith(VIRTUAL_CSS_SUFFIX)) {
38
+ return '\0' + id;
39
+ }
40
+
31
41
  // Direct .pulse imports - resolve to absolute path
32
42
  if (id.endsWith('.pulse') && importer) {
33
43
  const importerDir = dirname(importer);
@@ -38,7 +48,6 @@ export default function pulsePlugin(options = {}) {
38
48
  }
39
49
 
40
50
  // Check if a .js import has a corresponding .pulse file
41
- // This handles the compiler's transformation of .pulse -> .js imports
42
51
  if (id.endsWith('.js') && importer) {
43
52
  const pulseId = id.replace(/\.js$/, '.pulse');
44
53
  const importerDir = dirname(importer);
@@ -52,8 +61,22 @@ export default function pulsePlugin(options = {}) {
52
61
  return null;
53
62
  },
54
63
 
64
+ /**
65
+ * Load virtual CSS modules
66
+ */
67
+ load(id) {
68
+ // Virtual modules start with \0
69
+ if (id.startsWith('\0') && id.endsWith(VIRTUAL_CSS_SUFFIX)) {
70
+ const pulseId = id.slice(1, -VIRTUAL_CSS_SUFFIX.length + '.pulse'.length);
71
+ const css = cssMap.get(pulseId);
72
+ return css || '';
73
+ }
74
+ return null;
75
+ },
76
+
55
77
  /**
56
78
  * Transform .pulse files to JavaScript
79
+ * CSS is extracted to a virtual .css module that Vite processes separately
57
80
  */
58
81
  transform(code, id) {
59
82
  if (!id.endsWith('.pulse')) {
@@ -79,8 +102,27 @@ export default function pulsePlugin(options = {}) {
79
102
  return null;
80
103
  }
81
104
 
105
+ let outputCode = result.code;
106
+
107
+ // Extract CSS from compiled output and move to virtual CSS module
108
+ const stylesMatch = outputCode.match(/const styles = `([\s\S]*?)`;/);
109
+ if (stylesMatch) {
110
+ const css = stylesMatch[1];
111
+ const virtualCssId = id + '.css';
112
+
113
+ // Store CSS for the virtual module loader
114
+ cssMap.set(id, css);
115
+
116
+ // Replace inline style injection with CSS import
117
+ // Vite will process this through its CSS pipeline (not JS minifier)
118
+ outputCode = outputCode.replace(
119
+ /\/\/ Styles\nconst styles = `[\s\S]*?`;\n\/\/ Inject styles\nconst styleEl = document\.createElement\("style"\);\nstyleEl\.textContent = styles;\ndocument\.head\.appendChild\(styleEl\);/,
120
+ `// Styles extracted to virtual CSS module\nimport "${virtualCssId}";`
121
+ );
122
+ }
123
+
82
124
  return {
83
- code: result.code,
125
+ code: outputCode,
84
126
  map: result.map || null
85
127
  };
86
128
  } catch (error) {
@@ -92,7 +134,7 @@ export default function pulsePlugin(options = {}) {
92
134
  /**
93
135
  * Handle hot module replacement
94
136
  */
95
- handleHotUpdate({ file, server, modules }) {
137
+ handleHotUpdate({ file, server }) {
96
138
  if (file.endsWith('.pulse')) {
97
139
  console.log(`[Pulse] HMR update: ${file}`);
98
140
 
@@ -102,8 +144,14 @@ export default function pulsePlugin(options = {}) {
102
144
  server.moduleGraph.invalidateModule(module);
103
145
  }
104
146
 
147
+ // Also invalidate the associated virtual CSS module
148
+ const virtualCssId = '\0' + file + '.css';
149
+ const cssModule = server.moduleGraph.getModuleById(virtualCssId);
150
+ if (cssModule) {
151
+ server.moduleGraph.invalidateModule(cssModule);
152
+ }
153
+
105
154
  // Send HMR update instead of full reload
106
- // The module will handle its own state preservation via hmrRuntime
107
155
  server.ws.send({
108
156
  type: 'update',
109
157
  updates: [{
@@ -123,7 +171,7 @@ export default function pulsePlugin(options = {}) {
123
171
  * Configure dev server
124
172
  */
125
173
  configureServer(server) {
126
- server.middlewares.use((req, res, next) => {
174
+ server.middlewares.use((_req, _res, next) => {
127
175
  // Add any custom middleware here
128
176
  next();
129
177
  });
@@ -133,11 +181,8 @@ export default function pulsePlugin(options = {}) {
133
181
  * Build hooks
134
182
  */
135
183
  buildStart() {
136
- console.log('[Pulse] Build started');
137
- },
138
-
139
- buildEnd() {
140
- console.log('[Pulse] Build completed');
184
+ // Clear CSS map on new build
185
+ cssMap.clear();
141
186
  }
142
187
  };
143
188
  }
@@ -191,9 +236,9 @@ export const utils = {
191
236
  },
192
237
 
193
238
  /**
194
- * Create a virtual module ID
239
+ * Get the virtual CSS module ID for a Pulse file
195
240
  */
196
- createVirtualId(id) {
197
- return `\0${id}`;
241
+ getVirtualCssId(id) {
242
+ return id + '.css';
198
243
  }
199
244
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pulse-js-framework",
3
- "version": "1.7.23",
3
+ "version": "1.7.24",
4
4
  "description": "A declarative DOM framework with CSS selector-based structure and reactive pulsations",
5
5
  "type": "module",
6
6
  "main": "index.js",