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.
- package/package.json +1 -1
- package/serve.js +88 -64
package/package.json
CHANGED
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
|
-
// ──
|
|
630
|
+
// ── Live status block (shows only the current compile state) ───────────────
|
|
632
631
|
|
|
633
|
-
let
|
|
634
|
-
let
|
|
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
|
|
641
|
-
|
|
642
|
-
|
|
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
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
888
|
-
//
|
|
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)
|
|
930
|
+
if (!key) {
|
|
931
|
+
_activeErrors.clear()
|
|
932
|
+
_terminalErrors.clear()
|
|
933
|
+
} else if (hadError) {
|
|
934
|
+
_terminalErrors.delete(terminalErrorKey(key))
|
|
935
|
+
}
|
|
913
936
|
|
|
914
|
-
|
|
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
|
|
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
|
-
|
|
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
|