bimba-cli 0.7.29 → 0.7.30

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 +57 -68
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.29",
3
+ "version": "0.7.30",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/serve.js CHANGED
@@ -653,7 +653,7 @@ export function serve(entrypoint, flags) {
653
653
  _statusKind = null
654
654
  }
655
655
 
656
- function printStatus(file, state, errors) {
656
+ function printStatus(file, state, errors, options = {}) {
657
657
  // non-TTY (pipes, Claude Code bash, CI): plain newline-terminated output,
658
658
  // no ANSI cursor tricks, no fade-out — so logs stay readable.
659
659
  if (!_isTTY) {
@@ -678,66 +678,45 @@ export function serve(entrypoint, flags) {
678
678
  const now = new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' })
679
679
  const status = state === 'ok' ? theme.success(' ok ') : theme.failure(' fail ')
680
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
+
681
693
  process.stdout.write('\x1b[s')
682
694
  _statusSaved = true
683
695
  _statusFile = file
684
696
  _statusKind = state
685
697
 
686
- if (errors?.length) {
687
- process.stdout.write(` ${theme.folder(now)} ${theme.filename(file)} ${status}\n`)
688
- for (const err of errors) {
689
- try { printerr(err) } catch(_) { process.stdout.write(' ' + err.message + '\n') }
690
- }
691
- } else {
692
- const myId = ++_fadeId
693
- const plainLine = ` ${now} ${file} ok `
694
- const total = plainLine.length
695
- process.stdout.write(` ${theme.folder(now)} ${theme.filename(file)} ${status}`)
696
- for (let i = 1; i <= total; i++) {
697
- _fadeTimers.push(setTimeout(() => {
698
- if (_fadeId !== myId) return
699
- process.stdout.write('\x1b[1D \x1b[1D')
700
- if (i === total) {
701
- _statusSaved = false
702
- _statusFile = null
703
- _statusKind = null
704
- }
705
- }, 5000 + i * 22))
706
- }
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))
707
712
  }
708
713
  }
709
714
 
710
715
  // ── File watcher ───────────────────────────────────────────────────────────
711
716
 
712
717
  const _activeErrors = new Map()
713
-
714
- function renderErrorPanel() {
715
- if (!_isTTY) return false
716
-
717
- cancelFade()
718
- if (_statusSaved) {
719
- process.stdout.write('\x1b[u\x1b[J')
720
- _statusSaved = false
721
- }
722
- _statusFile = null
723
- _statusKind = null
724
-
725
- if (!_activeErrors.size) return false
726
-
727
- process.stdout.write('\x1b[s')
728
- _statusSaved = true
729
- _statusKind = 'errors'
730
-
731
- for (const item of _activeErrors.values()) {
732
- const file = item.file
733
- process.stdout.write(` ${theme.folder(item.time)} ${theme.filename(file)} ${theme.failure(' fail ')}\n`)
734
- for (const err of item.errors) {
735
- try { printerr(err) } catch(_) { process.stdout.write(' ' + err.message + '\n') }
736
- }
737
- }
738
-
739
- return true
740
- }
718
+ const _terminalErrors = new Map()
719
+ const TERMINAL_DUPLICATE_MS = 5000
741
720
 
742
721
  function broadcast(payload) {
743
722
  const msg = JSON.stringify(payload)
@@ -774,6 +753,12 @@ export function serve(entrypoint, flags) {
774
753
  return Array.from(variants).filter(Boolean)
775
754
  }
776
755
 
756
+ function terminalErrorKey(file) {
757
+ const variants = fileVariants(file)
758
+ const rooted = variants.find(variant => srcRel && variant.startsWith(srcRel + '/'))
759
+ return `path:${rooted || variants[0] || normalizeFile(file)}`
760
+ }
761
+
777
762
  function fileCandidates(file) {
778
763
  const candidates = []
779
764
  for (const variant of fileVariants(file)) {
@@ -862,29 +847,42 @@ export function serve(entrypoint, flags) {
862
847
  .join('\n---\n')
863
848
  }
864
849
 
850
+ function terminalErrorSignature(errors) {
851
+ return serializeErrors(errors)
852
+ .map(error => error.message)
853
+ .join('\n---\n')
854
+ }
855
+
865
856
  function showTrackedError(item) {
866
857
  const file = item.file
867
- if (_isTTY) renderErrorPanel()
868
- else printStatus(file, 'fail', item.errors)
858
+ printStatus(file, 'fail', item.errors)
869
859
  broadcast({ type: 'error', file, time: item.time, errors: item.payload })
870
860
  }
871
861
 
872
862
  function reportError(file, errors) {
873
863
  const display = normalizeFile(file)
874
864
  const key = errorKey(display)
865
+ const terminalKey = terminalErrorKey(display)
875
866
  const list = Array.isArray(errors) ? errors : [errors]
876
867
  const signature = errorSignature(list)
868
+ const printSignature = terminalErrorSignature(list)
877
869
  const previous = takeError(display)
870
+ const now = Date.now()
871
+ const recent = _terminalErrors.get(terminalKey)
878
872
  const duplicate = previous?.signature === signature
873
+ || previous?.printSignature === printSignature
874
+ || (recent?.signature === printSignature && now - recent.time < TERMINAL_DUPLICATE_MS)
879
875
 
880
876
  const item = {
881
877
  file: display,
882
878
  signature,
879
+ printSignature,
883
880
  errors: list,
884
881
  payload: serializeErrors(list),
885
882
  time: new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
886
883
  }
887
884
  _activeErrors.set(key, item)
885
+ _terminalErrors.set(terminalKey, { signature: printSignature, time: now })
888
886
 
889
887
  // The terminal is append-only in many real shells. Repeated reports of the
890
888
  // same active error update the browser overlay, but must not print again.
@@ -908,36 +906,27 @@ export function serve(entrypoint, flags) {
908
906
 
909
907
  function clearError(file) {
910
908
  const key = file ? normalizeFile(file) : null
911
- const hadPanel = _statusKind === 'errors'
912
909
  const wasStatusFile = key && _statusFile && sameFile(_statusFile, key)
913
910
  const hadError = key ? !!takeError(key) : _activeErrors.size > 0
914
911
 
915
912
  if (!key) _activeErrors.clear()
916
913
 
917
- let showedNext = false
918
- if (_isTTY && (hadError || wasStatusFile || hadPanel)) {
919
- showedNext = renderErrorPanel()
920
- } else if (!key || hadError || wasStatusFile) {
921
- clearStatus(key)
922
- }
914
+ if (!key || hadError || wasStatusFile) clearStatus(key)
923
915
 
924
916
  broadcast({ type: 'clear-error', file: key })
925
917
 
926
- if (!_isTTY && wasStatusFile && _activeErrors.size) {
927
- const nextItem = Array.from(_activeErrors.values()).at(-1)
928
- showTrackedError(nextItem)
929
- showedNext = true
930
- }
931
-
932
- return { cleared: hadError || wasStatusFile, file: key, showedNext }
918
+ return { cleared: hadError || wasStatusFile, file: key, showedNext: false }
933
919
  }
934
920
 
935
921
  function markSuccess(file) {
936
922
  const key = normalizeFile(file)
937
923
  const result = clearError(key)
938
924
  const active = _activeErrors.size
939
- const shouldPrint = result?.cleared && !result.showedNext && !active
940
- if (shouldPrint) printStatus(key, 'ok')
925
+ const shouldPrint = result?.cleared && !result.showedNext
926
+ if (shouldPrint) {
927
+ _terminalErrors.delete(terminalErrorKey(key))
928
+ printStatus(key, 'ok', null, { sticky: true })
929
+ }
941
930
  return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext, active }
942
931
  }
943
932