bimba-cli 0.7.30 → 0.7.31

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 +88 -64
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.30",
3
+ "version": "0.7.31",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/serve.js CHANGED
@@ -3,7 +3,6 @@ import * as compiler from 'imba/compiler'
3
3
  import { mkdirSync, watch, existsSync, statSync, writeFileSync, realpathSync } from 'fs'
4
4
  import path from 'path'
5
5
  import { theme } from './utils.js'
6
- import { printerr } from './plugin.js'
7
6
 
8
7
  // ─── HMR Client (injected into browser) ──────────────────────────────────────
9
8
 
@@ -628,29 +627,60 @@ export function serve(entrypoint, flags) {
628
627
  const srcDir = path.dirname(entrypoint)
629
628
  const sockets = new Set()
630
629
 
631
- // ── Status line (prints current compile result, fades out on success) ──────
630
+ // ── Live status block (shows only the current compile state) ───────────────
632
631
 
633
- let _fadeTimers = []
634
- let _fadeId = 0
632
+ let _statusRows = 0
633
+ let _statusTimer = null
635
634
  let _statusFile = null
636
- let _statusSaved = false
637
- let _statusKind = null
638
635
  const _isTTY = process.stdout.isTTY
639
636
 
640
- function cancelFade() {
641
- _fadeTimers.forEach(t => clearTimeout(t))
642
- _fadeTimers = []
637
+ function stripAnsi(text) {
638
+ return String(text).replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '')
639
+ }
640
+
641
+ function renderedRows(lines) {
642
+ const columns = Math.max(1, process.stdout.columns || 80)
643
+ return lines.reduce((total, line) => {
644
+ const length = stripAnsi(line).length
645
+ return total + Math.max(1, Math.ceil(length / columns))
646
+ }, 0)
643
647
  }
644
648
 
645
649
  function clearStatus(file) {
646
- if (file && _statusFile && _statusFile !== file) return
647
- cancelFade()
648
- if (_statusSaved) {
649
- process.stdout.write('\x1b[u\x1b[J')
650
- _statusSaved = false
650
+ if (file && _statusFile && _statusFile !== file) return false
651
+ if (_statusTimer) {
652
+ clearTimeout(_statusTimer)
653
+ _statusTimer = null
654
+ }
655
+ if (_isTTY && _statusRows) {
656
+ process.stdout.write(`\x1b[${_statusRows}A\r\x1b[J`)
651
657
  }
658
+ _statusRows = 0
652
659
  _statusFile = null
653
- _statusKind = null
660
+ return true
661
+ }
662
+
663
+ function formatErrorLines(errors) {
664
+ const lines = []
665
+ for (const err of errors || []) {
666
+ const message = errorMessage(err)
667
+ const line = errorLine(err)
668
+ lines.push(` ${theme.error(' ' + message + ' ')}${line != null ? theme.margin(` line ${line + 1} `) : ''}`)
669
+ const snippet = errorSnippet(err)
670
+ if (snippet && snippet !== message) {
671
+ lines.push(...String(snippet).split('\n').slice(0, 6).map(item => ` ${theme.code(item)}`))
672
+ }
673
+ lines.push('')
674
+ }
675
+ return lines
676
+ }
677
+
678
+ function statusLines(file, state, errors) {
679
+ const now = new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
680
+ const status = state === 'ok' ? theme.success(' ok ') : theme.failure(' fail ')
681
+ const lines = [` ${theme.folder(now)} ${theme.filename(file)} ${status}`]
682
+ if (errors?.length) lines.push('', ...formatErrorLines(errors))
683
+ return lines
654
684
  }
655
685
 
656
686
  function printStatus(file, state, errors, options = {}) {
@@ -670,46 +700,13 @@ export function serve(entrypoint, flags) {
670
700
  return
671
701
  }
672
702
 
673
- cancelFade()
674
- if (_statusSaved) {
675
- process.stdout.write('\x1b[u\x1b[J')
676
- _statusSaved = false
677
- }
678
- const now = new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
679
- const status = state === 'ok' ? theme.success(' ok ') : theme.failure(' fail ')
680
-
681
- if (errors?.length || options.sticky) {
682
- process.stdout.write(` ${theme.folder(now)} ${theme.filename(file)} ${status}\n`)
683
- if (errors?.length) {
684
- for (const err of errors) {
685
- try { printerr(err) } catch(_) { process.stdout.write(' ' + err.message + '\n') }
686
- }
687
- }
688
- _statusFile = null
689
- _statusKind = null
690
- return
691
- }
692
-
693
- process.stdout.write('\x1b[s')
694
- _statusSaved = true
703
+ clearStatus()
695
704
  _statusFile = file
696
- _statusKind = state
697
-
698
- const myId = ++_fadeId
699
- const plainLine = ` ${now} ${file} ok `
700
- const total = plainLine.length
701
- process.stdout.write(` ${theme.folder(now)} ${theme.filename(file)} ${status}`)
702
- for (let i = 1; i <= total; i++) {
703
- _fadeTimers.push(setTimeout(() => {
704
- if (_fadeId !== myId) return
705
- process.stdout.write('\x1b[1D \x1b[1D')
706
- if (i === total) {
707
- _statusSaved = false
708
- _statusFile = null
709
- _statusKind = null
710
- }
711
- }, 5000 + i * 22))
712
- }
705
+ const lines = statusLines(file, state, errors)
706
+ process.stdout.write(lines.join('\n') + '\n')
707
+ _statusRows = renderedRows(lines)
708
+
709
+ if (options.clearAfter) _statusTimer = setTimeout(() => clearStatus(file), options.clearAfter)
713
710
  }
714
711
 
715
712
  // ── File watcher ───────────────────────────────────────────────────────────
@@ -853,9 +850,29 @@ export function serve(entrypoint, flags) {
853
850
  .join('\n---\n')
854
851
  }
855
852
 
853
+ function renderActiveErrors() {
854
+ if (!_isTTY) return false
855
+ if (!_activeErrors.size) {
856
+ clearStatus()
857
+ return false
858
+ }
859
+
860
+ clearStatus()
861
+ const lines = []
862
+ for (const item of _activeErrors.values()) {
863
+ if (lines.length) lines.push('')
864
+ lines.push(...statusLines(item.file, 'fail', item.errors))
865
+ }
866
+ process.stdout.write(lines.join('\n') + '\n')
867
+ _statusFile = null
868
+ _statusRows = renderedRows(lines)
869
+ return true
870
+ }
871
+
856
872
  function showTrackedError(item) {
857
873
  const file = item.file
858
- printStatus(file, 'fail', item.errors)
874
+ if (_isTTY) renderActiveErrors()
875
+ else printStatus(file, 'fail', item.errors)
859
876
  broadcast({ type: 'error', file, time: item.time, errors: item.payload })
860
877
  }
861
878
 
@@ -884,9 +901,10 @@ export function serve(entrypoint, flags) {
884
901
  _activeErrors.set(key, item)
885
902
  _terminalErrors.set(terminalKey, { signature: printSignature, time: now })
886
903
 
887
- // The terminal is append-only in many real shells. Repeated reports of the
888
- // same active error update the browser overlay, but must not print again.
904
+ // Repeated reports of the same active error update the live status and
905
+ // browser overlay, but do not create another terminal entry.
889
906
  if (duplicate) {
907
+ if (_isTTY) renderActiveErrors()
890
908
  broadcast({ type: 'error', file: display, time: item.time, errors: item.payload })
891
909
  return
892
910
  }
@@ -909,23 +927,29 @@ export function serve(entrypoint, flags) {
909
927
  const wasStatusFile = key && _statusFile && sameFile(_statusFile, key)
910
928
  const hadError = key ? !!takeError(key) : _activeErrors.size > 0
911
929
 
912
- if (!key) _activeErrors.clear()
930
+ if (!key) {
931
+ _activeErrors.clear()
932
+ _terminalErrors.clear()
933
+ } else if (hadError) {
934
+ _terminalErrors.delete(terminalErrorKey(key))
935
+ }
913
936
 
914
- if (!key || hadError || wasStatusFile) clearStatus(key)
937
+ let showedNext = false
938
+ if (_isTTY && _activeErrors.size) showedNext = renderActiveErrors()
939
+ else if (!key || hadError || wasStatusFile) clearStatus(key)
915
940
 
916
941
  broadcast({ type: 'clear-error', file: key })
917
942
 
918
- return { cleared: hadError || wasStatusFile, file: key, showedNext: false }
943
+ return { cleared: hadError || wasStatusFile, file: key, showedNext }
919
944
  }
920
945
 
921
946
  function markSuccess(file) {
922
947
  const key = normalizeFile(file)
923
948
  const result = clearError(key)
924
949
  const active = _activeErrors.size
925
- const shouldPrint = result?.cleared && !result.showedNext
950
+ const shouldPrint = result?.cleared && !result.showedNext && !active
926
951
  if (shouldPrint) {
927
- _terminalErrors.delete(terminalErrorKey(key))
928
- printStatus(key, 'ok', null, { sticky: true })
952
+ printStatus(key, 'ok', null, { clearAfter: 3500 })
929
953
  }
930
954
  return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext, active }
931
955
  }
@@ -1000,7 +1024,7 @@ export function serve(entrypoint, flags) {
1000
1024
  // No change at all — skip
1001
1025
  if (out.changeType === 'none' || out.changeType === 'cached') return
1002
1026
 
1003
- if (!success.printed && !success.showedNext && !success.active) printStatus(rel, 'ok')
1027
+ if (!success.printed && !success.showedNext && !success.active) printStatus(rel, 'ok', null, { clearAfter: 3500 })
1004
1028
  broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
1005
1029
  } catch(e) {
1006
1030
  if (!isCurrentChange(file, version)) return