bimba-cli 0.7.23 → 0.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/serve.js +104 -31
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.23",
3
+ "version": "0.7.24",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/serve.js CHANGED
@@ -98,7 +98,7 @@ const hmrClient = `
98
98
  }
99
99
 
100
100
  async function _doUpdate(file, slots) {
101
- clearError();
101
+ clearError(file);
102
102
 
103
103
  const bodyBefore = new Set(document.body.children);
104
104
  const tagsBefore = new Set();
@@ -216,6 +216,8 @@ const hmrClient = `
216
216
 
217
217
  // ── Error overlay ──────────────────────────────────────────────────────────
218
218
 
219
+ const _compileErrors = new Map();
220
+
219
221
  function normalizeFile(file) {
220
222
  let value = String(file || '').split(String.fromCharCode(92)).join('/');
221
223
  while (value.startsWith('./')) value = value.slice(2);
@@ -223,10 +225,24 @@ const hmrClient = `
223
225
  return value;
224
226
  }
225
227
 
226
- function showError(file, errors, time) {
227
- const displayFile = normalizeFile(file);
228
- const displayTime = time || new Date().toLocaleTimeString();
228
+ function escapeHtml(value) {
229
+ return String(value ?? '').replace(/[&<>"']/g, ch => ({
230
+ '&': '&amp;',
231
+ '<': '&lt;',
232
+ '>': '&gt;',
233
+ '"': '&quot;',
234
+ "'": '&#39;',
235
+ })[ch]);
236
+ }
237
+
238
+ function renderErrors() {
229
239
  let overlay = document.getElementById('__bimba_error__');
240
+
241
+ if (!_compileErrors.size) {
242
+ if (overlay) overlay.remove();
243
+ return;
244
+ }
245
+
230
246
  if (!overlay) {
231
247
  overlay = document.createElement('div');
232
248
  overlay.id = '__bimba_error__';
@@ -234,29 +250,48 @@ const hmrClient = `
234
250
  overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
235
251
  document.body.appendChild(overlay);
236
252
  }
237
- overlay.dataset.file = displayFile;
238
- overlay.innerHTML = \`
239
- <div style="background:#1a1a1a;border:1px solid #ff4444;border-radius:8px;max-width:860px;width:100%;max-height:90vh;overflow:auto;box-shadow:0 0 40px rgba(255,68,68,.3)">
240
- <div style="background:#ff4444;color:#fff;padding:10px 16px;font-size:13px;font-weight:600;display:flex;justify-content:space-between;align-items:center">
241
- <span>Compile error — \${displayFile} <span style="opacity:.75;font-weight:400">\${displayTime}</span></span>
242
- <span onclick="document.getElementById('__bimba_error__').remove()" style="cursor:pointer;opacity:.7;font-size:16px">✕</span>
243
- </div>
244
- \${errors.map(err => \`
245
- <div style="padding:16px;border-bottom:1px solid #333">
246
- <div style="color:#ff8080;font-size:13px;margin-bottom:10px">\${err.message}\${err.line ? \` <span style="color:#888">line \${err.line}</span>\` : ''}</div>
247
- \${err.snippet ? \`<pre style="margin:0;padding:10px;background:#111;border-radius:4px;font-size:12px;line-height:1.6;color:#ccc;overflow-x:auto;white-space:pre">\${err.snippet.replace(/</g,'&lt;')}</pre>\` : ''}
248
- </div>
249
- \`).join('')}
250
- </div>
251
- \`;
253
+
254
+ const files = Array.from(_compileErrors.entries());
255
+ overlay.innerHTML =
256
+ '<div style="background:#1a1a1a;border:1px solid #ff4444;border-radius:8px;max-width:900px;width:100%;max-height:90vh;overflow:auto;box-shadow:0 0 40px rgba(255,68,68,.3)">' +
257
+ '<div style="background:#ff4444;color:#fff;padding:10px 16px;font-size:13px;font-weight:600;display:flex;justify-content:space-between;align-items:center">' +
258
+ '<span>Compile errors — ' + files.length + '</span>' +
259
+ '<span onclick="document.getElementById(\\'__bimba_error__\\').remove()" style="cursor:pointer;opacity:.7;font-size:16px">✕</span>' +
260
+ '</div>' +
261
+ files.map(([displayFile, item]) => {
262
+ const errors = item.errors || [];
263
+ return '<div style="border-bottom:1px solid #333">' +
264
+ '<div style="padding:10px 16px;background:#241616;color:#ffd1d1;font-size:13px;font-weight:600;display:flex;justify-content:space-between;gap:16px">' +
265
+ '<span>' + escapeHtml(displayFile) + '</span>' +
266
+ '<span style="opacity:.75;font-weight:400">' + escapeHtml(item.time || '') + '</span>' +
267
+ '</div>' +
268
+ errors.map(err =>
269
+ '<div style="padding:16px;border-top:1px solid #333">' +
270
+ '<div style="color:#ff8080;font-size:13px;margin-bottom:10px">' +
271
+ escapeHtml(err.message) +
272
+ (err.line ? ' <span style="color:#888">line ' + escapeHtml(err.line) + '</span>' : '') +
273
+ '</div>' +
274
+ (err.snippet ? '<pre style="margin:0;padding:10px;background:#111;border-radius:4px;font-size:12px;line-height:1.6;color:#ccc;overflow-x:auto;white-space:pre">' + escapeHtml(err.snippet) + '</pre>' : '') +
275
+ '</div>'
276
+ ).join('') +
277
+ '</div>';
278
+ }).join('') +
279
+ '</div>';
252
280
  }
253
281
 
254
- function clearError(file) {
255
- const overlay = document.getElementById('__bimba_error__');
256
- if (!overlay) return;
282
+ function showError(file, errors, time) {
283
+ const displayFile = normalizeFile(file);
284
+ _compileErrors.set(displayFile, {
285
+ errors: Array.isArray(errors) ? errors : [errors],
286
+ time: time || new Date().toLocaleTimeString(),
287
+ });
288
+ renderErrors();
289
+ }
257
290
 
258
- const activeFile = overlay.dataset.file;
259
- if (!file || !activeFile || activeFile === normalizeFile(file)) overlay.remove();
291
+ function clearError(file) {
292
+ if (file) _compileErrors.delete(normalizeFile(file));
293
+ else _compileErrors.clear();
294
+ renderErrors();
260
295
  }
261
296
 
262
297
  connect();
@@ -562,6 +597,7 @@ export function serve(entrypoint, flags) {
562
597
  let _fadeId = 0
563
598
  let _statusFile = null
564
599
  let _statusSaved = false
600
+ let _statusKind = null
565
601
  const _isTTY = process.stdout.isTTY
566
602
 
567
603
  function cancelFade() {
@@ -577,6 +613,7 @@ export function serve(entrypoint, flags) {
577
613
  _statusSaved = false
578
614
  }
579
615
  _statusFile = null
616
+ _statusKind = null
580
617
  }
581
618
 
582
619
  function printStatus(file, state, errors) {
@@ -607,6 +644,7 @@ export function serve(entrypoint, flags) {
607
644
  process.stdout.write('\x1b[s')
608
645
  _statusSaved = true
609
646
  _statusFile = file
647
+ _statusKind = state
610
648
 
611
649
  if (errors?.length) {
612
650
  process.stdout.write(` ${theme.folder(now)} ${theme.filename(file)} ${status}\n`)
@@ -625,6 +663,7 @@ export function serve(entrypoint, flags) {
625
663
  if (i === total) {
626
664
  _statusSaved = false
627
665
  _statusFile = null
666
+ _statusKind = null
628
667
  }
629
668
  }, 5000 + i * 22))
630
669
  }
@@ -635,6 +674,33 @@ export function serve(entrypoint, flags) {
635
674
 
636
675
  const _activeErrors = new Map()
637
676
 
677
+ function renderErrorPanel() {
678
+ if (!_isTTY) return false
679
+
680
+ cancelFade()
681
+ if (_statusSaved) {
682
+ process.stdout.write('\x1b[u\x1b[J')
683
+ _statusSaved = false
684
+ }
685
+ _statusFile = null
686
+ _statusKind = null
687
+
688
+ if (!_activeErrors.size) return false
689
+
690
+ process.stdout.write('\x1b[s')
691
+ _statusSaved = true
692
+ _statusKind = 'errors'
693
+
694
+ for (const [file, item] of _activeErrors.entries()) {
695
+ process.stdout.write(` ${theme.folder(item.time)} ${theme.filename(file)} ${theme.failure(' fail ')}\n`)
696
+ for (const err of item.errors) {
697
+ try { printerr(err) } catch(_) { process.stdout.write(' ' + err.message + '\n') }
698
+ }
699
+ }
700
+
701
+ return true
702
+ }
703
+
638
704
  function broadcast(payload) {
639
705
  const msg = JSON.stringify(payload)
640
706
  for (const socket of sockets) socket.send(msg)
@@ -686,7 +752,8 @@ export function serve(entrypoint, flags) {
686
752
  }
687
753
 
688
754
  function showTrackedError(file, item) {
689
- printStatus(file, 'fail', item.errors)
755
+ if (_isTTY) renderErrorPanel()
756
+ else printStatus(file, 'fail', item.errors)
690
757
  broadcast({ type: 'error', file, time: item.time, errors: item.payload })
691
758
  }
692
759
 
@@ -731,11 +798,16 @@ export function serve(entrypoint, flags) {
731
798
 
732
799
  if (key && !hadError && !wasStatusFile) return
733
800
 
734
- clearStatus(key)
801
+ let showedNext = false
802
+ if (_isTTY && (hadError || _statusKind === 'errors')) {
803
+ showedNext = renderErrorPanel()
804
+ } else {
805
+ clearStatus(key)
806
+ }
807
+
735
808
  broadcast({ type: 'clear-error', file: key })
736
809
 
737
- let showedNext = false
738
- if (wasStatusFile && _activeErrors.size) {
810
+ if (!_isTTY && wasStatusFile && _activeErrors.size) {
739
811
  const [nextFile, nextItem] = Array.from(_activeErrors.entries()).at(-1)
740
812
  showTrackedError(nextFile, nextItem)
741
813
  showedNext = true
@@ -747,9 +819,10 @@ export function serve(entrypoint, flags) {
747
819
  function markSuccess(file) {
748
820
  const key = normalizeFile(file)
749
821
  const result = clearError(key)
750
- const shouldPrint = result?.cleared && !result.showedNext
822
+ const active = _activeErrors.size
823
+ const shouldPrint = result?.cleared && !result.showedNext && !active
751
824
  if (shouldPrint) printStatus(key, 'ok')
752
- return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext }
825
+ return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext, active }
753
826
  }
754
827
 
755
828
  const _debounce = new Map()
@@ -822,7 +895,7 @@ export function serve(entrypoint, flags) {
822
895
  // No change at all — skip
823
896
  if (out.changeType === 'none' || out.changeType === 'cached') return
824
897
 
825
- if (!success.printed && !success.showedNext) printStatus(rel, 'ok')
898
+ if (!success.printed && !success.showedNext && !success.active) printStatus(rel, 'ok')
826
899
  broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
827
900
  } catch(e) {
828
901
  if (!isCurrentChange(file, version)) return