bimba-cli 0.7.23 → 0.7.25
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 +160 -38
package/package.json
CHANGED
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,17 +216,40 @@ const hmrClient = `
|
|
|
216
216
|
|
|
217
217
|
// ── Error overlay ──────────────────────────────────────────────────────────
|
|
218
218
|
|
|
219
|
+
const _compileErrors = new Map();
|
|
220
|
+
|
|
219
221
|
function normalizeFile(file) {
|
|
220
|
-
let value = String(file || '').split(String.fromCharCode(92)).join('/');
|
|
222
|
+
let value = String(file || '').split(/[?#]/)[0].split(String.fromCharCode(92)).join('/');
|
|
221
223
|
while (value.startsWith('./')) value = value.slice(2);
|
|
222
224
|
while (value.startsWith('/')) value = value.slice(1);
|
|
223
225
|
return value;
|
|
224
226
|
}
|
|
225
227
|
|
|
226
|
-
function
|
|
227
|
-
const
|
|
228
|
-
const
|
|
228
|
+
function sameFile(left, right) {
|
|
229
|
+
const a = normalizeFile(left);
|
|
230
|
+
const b = normalizeFile(right);
|
|
231
|
+
if (!a || !b) return false;
|
|
232
|
+
return a === b || a.endsWith('/' + b) || b.endsWith('/' + a);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function escapeHtml(value) {
|
|
236
|
+
return String(value ?? '').replace(/[&<>"']/g, ch => ({
|
|
237
|
+
'&': '&',
|
|
238
|
+
'<': '<',
|
|
239
|
+
'>': '>',
|
|
240
|
+
'"': '"',
|
|
241
|
+
"'": ''',
|
|
242
|
+
})[ch]);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function renderErrors() {
|
|
229
246
|
let overlay = document.getElementById('__bimba_error__');
|
|
247
|
+
|
|
248
|
+
if (!_compileErrors.size) {
|
|
249
|
+
if (overlay) overlay.remove();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
230
253
|
if (!overlay) {
|
|
231
254
|
overlay = document.createElement('div');
|
|
232
255
|
overlay.id = '__bimba_error__';
|
|
@@ -234,29 +257,57 @@ const hmrClient = `
|
|
|
234
257
|
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
|
|
235
258
|
document.body.appendChild(overlay);
|
|
236
259
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<span
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
260
|
+
|
|
261
|
+
const files = Array.from(_compileErrors.entries());
|
|
262
|
+
overlay.innerHTML =
|
|
263
|
+
'<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)">' +
|
|
264
|
+
'<div style="background:#ff4444;color:#fff;padding:10px 16px;font-size:13px;font-weight:600;display:flex;justify-content:space-between;align-items:center">' +
|
|
265
|
+
'<span>Compile errors — ' + files.length + '</span>' +
|
|
266
|
+
'<span onclick="document.getElementById(\\'__bimba_error__\\').remove()" style="cursor:pointer;opacity:.7;font-size:16px">✕</span>' +
|
|
267
|
+
'</div>' +
|
|
268
|
+
files.map(([displayFile, item]) => {
|
|
269
|
+
const errors = item.errors || [];
|
|
270
|
+
return '<div style="border-bottom:1px solid #333">' +
|
|
271
|
+
'<div style="padding:10px 16px;background:#241616;color:#ffd1d1;font-size:13px;font-weight:600;display:flex;justify-content:space-between;gap:16px">' +
|
|
272
|
+
'<span>' + escapeHtml(displayFile) + '</span>' +
|
|
273
|
+
'<span style="opacity:.75;font-weight:400">' + escapeHtml(item.time || '') + '</span>' +
|
|
274
|
+
'</div>' +
|
|
275
|
+
errors.map(err =>
|
|
276
|
+
'<div style="padding:16px;border-top:1px solid #333">' +
|
|
277
|
+
'<div style="color:#ff8080;font-size:13px;margin-bottom:10px">' +
|
|
278
|
+
escapeHtml(err.message) +
|
|
279
|
+
(err.line ? ' <span style="color:#888">line ' + escapeHtml(err.line) + '</span>' : '') +
|
|
280
|
+
'</div>' +
|
|
281
|
+
(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>' : '') +
|
|
282
|
+
'</div>'
|
|
283
|
+
).join('') +
|
|
284
|
+
'</div>';
|
|
285
|
+
}).join('') +
|
|
286
|
+
'</div>';
|
|
252
287
|
}
|
|
253
288
|
|
|
254
|
-
function
|
|
255
|
-
const
|
|
256
|
-
|
|
289
|
+
function showError(file, errors, time) {
|
|
290
|
+
const displayFile = normalizeFile(file);
|
|
291
|
+
for (const key of Array.from(_compileErrors.keys())) {
|
|
292
|
+
if (sameFile(key, displayFile)) _compileErrors.delete(key);
|
|
293
|
+
}
|
|
294
|
+
_compileErrors.set(displayFile, {
|
|
295
|
+
errors: Array.isArray(errors) ? errors : [errors],
|
|
296
|
+
time: time || new Date().toLocaleTimeString(),
|
|
297
|
+
});
|
|
298
|
+
renderErrors();
|
|
299
|
+
}
|
|
257
300
|
|
|
258
|
-
|
|
259
|
-
if (
|
|
301
|
+
function clearError(file) {
|
|
302
|
+
if (file) {
|
|
303
|
+
const displayFile = normalizeFile(file);
|
|
304
|
+
for (const key of Array.from(_compileErrors.keys())) {
|
|
305
|
+
if (sameFile(key, displayFile)) _compileErrors.delete(key);
|
|
306
|
+
}
|
|
307
|
+
} else {
|
|
308
|
+
_compileErrors.clear();
|
|
309
|
+
}
|
|
310
|
+
renderErrors();
|
|
260
311
|
}
|
|
261
312
|
|
|
262
313
|
connect();
|
|
@@ -562,6 +613,7 @@ export function serve(entrypoint, flags) {
|
|
|
562
613
|
let _fadeId = 0
|
|
563
614
|
let _statusFile = null
|
|
564
615
|
let _statusSaved = false
|
|
616
|
+
let _statusKind = null
|
|
565
617
|
const _isTTY = process.stdout.isTTY
|
|
566
618
|
|
|
567
619
|
function cancelFade() {
|
|
@@ -577,6 +629,7 @@ export function serve(entrypoint, flags) {
|
|
|
577
629
|
_statusSaved = false
|
|
578
630
|
}
|
|
579
631
|
_statusFile = null
|
|
632
|
+
_statusKind = null
|
|
580
633
|
}
|
|
581
634
|
|
|
582
635
|
function printStatus(file, state, errors) {
|
|
@@ -607,6 +660,7 @@ export function serve(entrypoint, flags) {
|
|
|
607
660
|
process.stdout.write('\x1b[s')
|
|
608
661
|
_statusSaved = true
|
|
609
662
|
_statusFile = file
|
|
663
|
+
_statusKind = state
|
|
610
664
|
|
|
611
665
|
if (errors?.length) {
|
|
612
666
|
process.stdout.write(` ${theme.folder(now)} ${theme.filename(file)} ${status}\n`)
|
|
@@ -625,6 +679,7 @@ export function serve(entrypoint, flags) {
|
|
|
625
679
|
if (i === total) {
|
|
626
680
|
_statusSaved = false
|
|
627
681
|
_statusFile = null
|
|
682
|
+
_statusKind = null
|
|
628
683
|
}
|
|
629
684
|
}, 5000 + i * 22))
|
|
630
685
|
}
|
|
@@ -635,6 +690,33 @@ export function serve(entrypoint, flags) {
|
|
|
635
690
|
|
|
636
691
|
const _activeErrors = new Map()
|
|
637
692
|
|
|
693
|
+
function renderErrorPanel() {
|
|
694
|
+
if (!_isTTY) return false
|
|
695
|
+
|
|
696
|
+
cancelFade()
|
|
697
|
+
if (_statusSaved) {
|
|
698
|
+
process.stdout.write('\x1b[u\x1b[J')
|
|
699
|
+
_statusSaved = false
|
|
700
|
+
}
|
|
701
|
+
_statusFile = null
|
|
702
|
+
_statusKind = null
|
|
703
|
+
|
|
704
|
+
if (!_activeErrors.size) return false
|
|
705
|
+
|
|
706
|
+
process.stdout.write('\x1b[s')
|
|
707
|
+
_statusSaved = true
|
|
708
|
+
_statusKind = 'errors'
|
|
709
|
+
|
|
710
|
+
for (const [file, item] of _activeErrors.entries()) {
|
|
711
|
+
process.stdout.write(` ${theme.folder(item.time)} ${theme.filename(file)} ${theme.failure(' fail ')}\n`)
|
|
712
|
+
for (const err of item.errors) {
|
|
713
|
+
try { printerr(err) } catch(_) { process.stdout.write(' ' + err.message + '\n') }
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
return true
|
|
718
|
+
}
|
|
719
|
+
|
|
638
720
|
function broadcast(payload) {
|
|
639
721
|
const msg = JSON.stringify(payload)
|
|
640
722
|
for (const socket of sockets) socket.send(msg)
|
|
@@ -642,6 +724,7 @@ export function serve(entrypoint, flags) {
|
|
|
642
724
|
|
|
643
725
|
function normalizeFile(file) {
|
|
644
726
|
let value = String(file || '')
|
|
727
|
+
value = value.split(/[?#]/)[0]
|
|
645
728
|
if (path.isAbsolute(value)) {
|
|
646
729
|
const rel = path.relative(process.cwd(), value)
|
|
647
730
|
if (!rel.startsWith('..')) value = rel
|
|
@@ -655,6 +738,40 @@ export function serve(entrypoint, flags) {
|
|
|
655
738
|
const srcRoot = path.resolve(srcDir)
|
|
656
739
|
const srcRel = normalizeFile(srcRoot)
|
|
657
740
|
|
|
741
|
+
function fileVariants(file) {
|
|
742
|
+
const key = normalizeFile(file)
|
|
743
|
+
const variants = new Set([key])
|
|
744
|
+
|
|
745
|
+
if (srcRel && key.startsWith(srcRel + '/')) variants.add(key.slice(srcRel.length + 1))
|
|
746
|
+
else if (srcRel && key) variants.add(srcRel + '/' + key)
|
|
747
|
+
|
|
748
|
+
return Array.from(variants).filter(Boolean)
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function sameFile(left, right) {
|
|
752
|
+
const lefts = fileVariants(left)
|
|
753
|
+
const rights = fileVariants(right)
|
|
754
|
+
|
|
755
|
+
for (const a of lefts) {
|
|
756
|
+
for (const b of rights) {
|
|
757
|
+
if (a === b || a.endsWith('/' + b) || b.endsWith('/' + a)) return true
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return false
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function takeError(file) {
|
|
765
|
+
let previous = null
|
|
766
|
+
const keys = Array.from(_activeErrors.keys())
|
|
767
|
+
for (const key of keys) {
|
|
768
|
+
if (!sameFile(key, file)) continue
|
|
769
|
+
previous ||= _activeErrors.get(key)
|
|
770
|
+
_activeErrors.delete(key)
|
|
771
|
+
}
|
|
772
|
+
return previous
|
|
773
|
+
}
|
|
774
|
+
|
|
658
775
|
function errorMessage(error) {
|
|
659
776
|
return error?.message || String(error)
|
|
660
777
|
}
|
|
@@ -686,7 +803,8 @@ export function serve(entrypoint, flags) {
|
|
|
686
803
|
}
|
|
687
804
|
|
|
688
805
|
function showTrackedError(file, item) {
|
|
689
|
-
|
|
806
|
+
if (_isTTY) renderErrorPanel()
|
|
807
|
+
else printStatus(file, 'fail', item.errors)
|
|
690
808
|
broadcast({ type: 'error', file, time: item.time, errors: item.payload })
|
|
691
809
|
}
|
|
692
810
|
|
|
@@ -694,7 +812,7 @@ export function serve(entrypoint, flags) {
|
|
|
694
812
|
const key = normalizeFile(file)
|
|
695
813
|
const list = Array.isArray(errors) ? errors : [errors]
|
|
696
814
|
const signature = errorSignature(list)
|
|
697
|
-
const previous =
|
|
815
|
+
const previous = takeError(key)
|
|
698
816
|
|
|
699
817
|
const item = {
|
|
700
818
|
signature,
|
|
@@ -723,19 +841,22 @@ export function serve(entrypoint, flags) {
|
|
|
723
841
|
|
|
724
842
|
function clearError(file) {
|
|
725
843
|
const key = file ? normalizeFile(file) : null
|
|
726
|
-
const
|
|
727
|
-
const
|
|
844
|
+
const hadPanel = _statusKind === 'errors'
|
|
845
|
+
const wasStatusFile = key && _statusFile && sameFile(_statusFile, key)
|
|
846
|
+
const hadError = key ? !!takeError(key) : _activeErrors.size > 0
|
|
728
847
|
|
|
729
|
-
if (key) _activeErrors.
|
|
730
|
-
else _activeErrors.clear()
|
|
848
|
+
if (!key) _activeErrors.clear()
|
|
731
849
|
|
|
732
|
-
|
|
850
|
+
let showedNext = false
|
|
851
|
+
if (_isTTY && (hadError || wasStatusFile || hadPanel)) {
|
|
852
|
+
showedNext = renderErrorPanel()
|
|
853
|
+
} else if (!key || hadError || wasStatusFile) {
|
|
854
|
+
clearStatus(key)
|
|
855
|
+
}
|
|
733
856
|
|
|
734
|
-
clearStatus(key)
|
|
735
857
|
broadcast({ type: 'clear-error', file: key })
|
|
736
858
|
|
|
737
|
-
|
|
738
|
-
if (wasStatusFile && _activeErrors.size) {
|
|
859
|
+
if (!_isTTY && wasStatusFile && _activeErrors.size) {
|
|
739
860
|
const [nextFile, nextItem] = Array.from(_activeErrors.entries()).at(-1)
|
|
740
861
|
showTrackedError(nextFile, nextItem)
|
|
741
862
|
showedNext = true
|
|
@@ -747,9 +868,10 @@ export function serve(entrypoint, flags) {
|
|
|
747
868
|
function markSuccess(file) {
|
|
748
869
|
const key = normalizeFile(file)
|
|
749
870
|
const result = clearError(key)
|
|
750
|
-
const
|
|
871
|
+
const active = _activeErrors.size
|
|
872
|
+
const shouldPrint = result?.cleared && !result.showedNext && !active
|
|
751
873
|
if (shouldPrint) printStatus(key, 'ok')
|
|
752
|
-
return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext }
|
|
874
|
+
return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext, active }
|
|
753
875
|
}
|
|
754
876
|
|
|
755
877
|
const _debounce = new Map()
|
|
@@ -822,7 +944,7 @@ export function serve(entrypoint, flags) {
|
|
|
822
944
|
// No change at all — skip
|
|
823
945
|
if (out.changeType === 'none' || out.changeType === 'cached') return
|
|
824
946
|
|
|
825
|
-
if (!success.printed && !success.showedNext) printStatus(rel, 'ok')
|
|
947
|
+
if (!success.printed && !success.showedNext && !success.active) printStatus(rel, 'ok')
|
|
826
948
|
broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
|
|
827
949
|
} catch(e) {
|
|
828
950
|
if (!isCurrentChange(file, version)) return
|