bimba-cli 0.7.22 → 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.
- package/package.json +1 -1
- package/serve.js +145 -52
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,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
|
|
227
|
-
|
|
228
|
-
|
|
228
|
+
function escapeHtml(value) {
|
|
229
|
+
return String(value ?? '').replace(/[&<>"']/g, ch => ({
|
|
230
|
+
'&': '&',
|
|
231
|
+
'<': '<',
|
|
232
|
+
'>': '>',
|
|
233
|
+
'"': '"',
|
|
234
|
+
"'": ''',
|
|
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
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
<span
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
|
255
|
-
const
|
|
256
|
-
|
|
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
|
-
|
|
259
|
-
if (
|
|
291
|
+
function clearError(file) {
|
|
292
|
+
if (file) _compileErrors.delete(normalizeFile(file));
|
|
293
|
+
else _compileErrors.clear();
|
|
294
|
+
renderErrors();
|
|
260
295
|
}
|
|
261
296
|
|
|
262
297
|
connect();
|
|
@@ -265,7 +300,7 @@ const hmrClient = `
|
|
|
265
300
|
|
|
266
301
|
// ─── Server-side compile cache ────────────────────────────────────────────────
|
|
267
302
|
|
|
268
|
-
const _compileCache = new Map() // filepath → {
|
|
303
|
+
const _compileCache = new Map() // filepath → { stamp, result }
|
|
269
304
|
const _prevJs = new Map() // filepath → compiled js — for change detection
|
|
270
305
|
const _prevSlots = new Map() // filepath → previous symbol slot count
|
|
271
306
|
const _importScanner = new Bun.Transpiler({ loader: 'js' })
|
|
@@ -367,7 +402,7 @@ async function compileFile(filepath) {
|
|
|
367
402
|
const abs = path.resolve(filepath)
|
|
368
403
|
if (!existsSync(abs)) return missingCompileResult(abs)
|
|
369
404
|
|
|
370
|
-
const file = Bun.file(
|
|
405
|
+
const file = Bun.file(abs)
|
|
371
406
|
let stat
|
|
372
407
|
try {
|
|
373
408
|
stat = await file.stat()
|
|
@@ -375,10 +410,10 @@ async function compileFile(filepath) {
|
|
|
375
410
|
if (isMissingFileError(error)) return missingCompileResult(abs)
|
|
376
411
|
throw error
|
|
377
412
|
}
|
|
378
|
-
const
|
|
413
|
+
const stamp = `${stat.mtimeMs ?? stat.mtime?.getTime?.() ?? 0}:${stat.size ?? 0}`
|
|
379
414
|
|
|
380
415
|
const cached = _compileCache.get(abs)
|
|
381
|
-
if (cached && cached.
|
|
416
|
+
if (cached && cached.stamp === stamp) return _normalizeResult(cached.result, { changeType: 'cached' })
|
|
382
417
|
|
|
383
418
|
let code
|
|
384
419
|
try {
|
|
@@ -406,7 +441,7 @@ async function compileFile(filepath) {
|
|
|
406
441
|
const baked = { js: result.js, errors, slots: result.slots }
|
|
407
442
|
const changeType = _prevJs.get(abs) === baked.js ? 'none' : 'full'
|
|
408
443
|
_prevJs.set(abs, baked.js)
|
|
409
|
-
_compileCache.set(abs, {
|
|
444
|
+
_compileCache.set(abs, { stamp, result: baked })
|
|
410
445
|
return _normalizeResult(baked, { changeType })
|
|
411
446
|
}
|
|
412
447
|
|
|
@@ -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)
|
|
@@ -652,6 +718,9 @@ export function serve(entrypoint, flags) {
|
|
|
652
718
|
return value
|
|
653
719
|
}
|
|
654
720
|
|
|
721
|
+
const srcRoot = path.resolve(srcDir)
|
|
722
|
+
const srcRel = normalizeFile(srcRoot)
|
|
723
|
+
|
|
655
724
|
function errorMessage(error) {
|
|
656
725
|
return error?.message || String(error)
|
|
657
726
|
}
|
|
@@ -683,7 +752,8 @@ export function serve(entrypoint, flags) {
|
|
|
683
752
|
}
|
|
684
753
|
|
|
685
754
|
function showTrackedError(file, item) {
|
|
686
|
-
|
|
755
|
+
if (_isTTY) renderErrorPanel()
|
|
756
|
+
else printStatus(file, 'fail', item.errors)
|
|
687
757
|
broadcast({ type: 'error', file, time: item.time, errors: item.payload })
|
|
688
758
|
}
|
|
689
759
|
|
|
@@ -728,11 +798,16 @@ export function serve(entrypoint, flags) {
|
|
|
728
798
|
|
|
729
799
|
if (key && !hadError && !wasStatusFile) return
|
|
730
800
|
|
|
731
|
-
|
|
801
|
+
let showedNext = false
|
|
802
|
+
if (_isTTY && (hadError || _statusKind === 'errors')) {
|
|
803
|
+
showedNext = renderErrorPanel()
|
|
804
|
+
} else {
|
|
805
|
+
clearStatus(key)
|
|
806
|
+
}
|
|
807
|
+
|
|
732
808
|
broadcast({ type: 'clear-error', file: key })
|
|
733
809
|
|
|
734
|
-
|
|
735
|
-
if (wasStatusFile && _activeErrors.size) {
|
|
810
|
+
if (!_isTTY && wasStatusFile && _activeErrors.size) {
|
|
736
811
|
const [nextFile, nextItem] = Array.from(_activeErrors.entries()).at(-1)
|
|
737
812
|
showTrackedError(nextFile, nextItem)
|
|
738
813
|
showedNext = true
|
|
@@ -744,41 +819,59 @@ export function serve(entrypoint, flags) {
|
|
|
744
819
|
function markSuccess(file) {
|
|
745
820
|
const key = normalizeFile(file)
|
|
746
821
|
const result = clearError(key)
|
|
747
|
-
const
|
|
822
|
+
const active = _activeErrors.size
|
|
823
|
+
const shouldPrint = result?.cleared && !result.showedNext && !active
|
|
748
824
|
if (shouldPrint) printStatus(key, 'ok')
|
|
749
|
-
return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext }
|
|
825
|
+
return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext, active }
|
|
750
826
|
}
|
|
751
827
|
|
|
752
828
|
const _debounce = new Map()
|
|
753
829
|
const _watchVersion = new Map()
|
|
754
830
|
|
|
755
|
-
function
|
|
831
|
+
function watchedFile(filename) {
|
|
756
832
|
filename = filename && String(filename)
|
|
757
|
-
if (!filename
|
|
833
|
+
if (!filename) return null
|
|
834
|
+
|
|
835
|
+
let filepath
|
|
836
|
+
if (path.isAbsolute(filename)) {
|
|
837
|
+
filepath = path.resolve(filename)
|
|
838
|
+
} else {
|
|
839
|
+
const rel = normalizeFile(filename)
|
|
840
|
+
filepath = (rel === srcRel || rel.startsWith(srcRel + '/'))
|
|
841
|
+
? path.resolve(filename)
|
|
842
|
+
: path.resolve(srcRoot, filename)
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
const rel = normalizeFile(filepath)
|
|
846
|
+
return { filepath, rel }
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function scheduleCompile(filename) {
|
|
850
|
+
const file = watchedFile(filename)
|
|
851
|
+
if (!file || !file.rel.endsWith('.imba')) return
|
|
758
852
|
|
|
759
|
-
const version = (_watchVersion.get(
|
|
760
|
-
_watchVersion.set(
|
|
853
|
+
const version = (_watchVersion.get(file.rel) || 0) + 1
|
|
854
|
+
_watchVersion.set(file.rel, version)
|
|
761
855
|
|
|
762
|
-
const pending = _debounce.get(
|
|
856
|
+
const pending = _debounce.get(file.rel)
|
|
763
857
|
if (pending) clearTimeout(pending)
|
|
764
858
|
|
|
765
|
-
_debounce.set(
|
|
766
|
-
_debounce.delete(
|
|
767
|
-
compileChangedFile(
|
|
859
|
+
_debounce.set(file.rel, setTimeout(() => {
|
|
860
|
+
_debounce.delete(file.rel)
|
|
861
|
+
compileChangedFile(file, version)
|
|
768
862
|
}, 150))
|
|
769
863
|
}
|
|
770
864
|
|
|
771
|
-
function isCurrentChange(
|
|
772
|
-
return _watchVersion.get(
|
|
865
|
+
function isCurrentChange(file, version) {
|
|
866
|
+
return _watchVersion.get(file.rel) === version
|
|
773
867
|
}
|
|
774
868
|
|
|
775
|
-
async function compileChangedFile(
|
|
776
|
-
const filepath =
|
|
777
|
-
const rel = normalizeFile(path.join(path.relative('.', srcDir), filename))
|
|
869
|
+
async function compileChangedFile(file, version) {
|
|
870
|
+
const { filepath, rel } = file
|
|
778
871
|
|
|
779
872
|
try {
|
|
780
873
|
if (!existsSync(filepath)) {
|
|
781
|
-
if (!isCurrentChange(
|
|
874
|
+
if (!isCurrentChange(file, version)) return
|
|
782
875
|
dropFileState(filepath)
|
|
783
876
|
clearError(rel)
|
|
784
877
|
return
|
|
@@ -786,7 +879,7 @@ export function serve(entrypoint, flags) {
|
|
|
786
879
|
|
|
787
880
|
const out = await compileFile(filepath)
|
|
788
881
|
|
|
789
|
-
if (!isCurrentChange(
|
|
882
|
+
if (!isCurrentChange(file, version)) return
|
|
790
883
|
if (out.missing) {
|
|
791
884
|
clearError(rel)
|
|
792
885
|
return
|
|
@@ -802,10 +895,10 @@ export function serve(entrypoint, flags) {
|
|
|
802
895
|
// No change at all — skip
|
|
803
896
|
if (out.changeType === 'none' || out.changeType === 'cached') return
|
|
804
897
|
|
|
805
|
-
if (!success.printed && !success.showedNext) printStatus(rel, 'ok')
|
|
898
|
+
if (!success.printed && !success.showedNext && !success.active) printStatus(rel, 'ok')
|
|
806
899
|
broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
|
|
807
900
|
} catch(e) {
|
|
808
|
-
if (!isCurrentChange(
|
|
901
|
+
if (!isCurrentChange(file, version)) return
|
|
809
902
|
if (isMissingFileError(e)) {
|
|
810
903
|
dropFileState(filepath)
|
|
811
904
|
clearError(rel)
|