bimba-cli 0.7.24 → 0.7.26

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 +111 -20
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.24",
3
+ "version": "0.7.26",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/serve.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { serve as bunServe } from 'bun'
2
2
  import * as compiler from 'imba/compiler'
3
- import { mkdirSync, watch, existsSync, statSync, writeFileSync } from 'fs'
3
+ import { mkdirSync, watch, existsSync, statSync, writeFileSync, realpathSync } from 'fs'
4
4
  import path from 'path'
5
5
  import { theme } from './utils.js'
6
6
  import { printerr } from './plugin.js'
@@ -219,12 +219,19 @@ const hmrClient = `
219
219
  const _compileErrors = new Map();
220
220
 
221
221
  function normalizeFile(file) {
222
- let value = String(file || '').split(String.fromCharCode(92)).join('/');
222
+ let value = String(file || '').split(/[?#]/)[0].split(String.fromCharCode(92)).join('/');
223
223
  while (value.startsWith('./')) value = value.slice(2);
224
224
  while (value.startsWith('/')) value = value.slice(1);
225
225
  return value;
226
226
  }
227
227
 
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
+
228
235
  function escapeHtml(value) {
229
236
  return String(value ?? '').replace(/[&<>"']/g, ch => ({
230
237
  '&': '&amp;',
@@ -281,6 +288,9 @@ const hmrClient = `
281
288
 
282
289
  function showError(file, errors, time) {
283
290
  const displayFile = normalizeFile(file);
291
+ for (const key of Array.from(_compileErrors.keys())) {
292
+ if (sameFile(key, displayFile)) _compileErrors.delete(key);
293
+ }
284
294
  _compileErrors.set(displayFile, {
285
295
  errors: Array.isArray(errors) ? errors : [errors],
286
296
  time: time || new Date().toLocaleTimeString(),
@@ -289,8 +299,14 @@ const hmrClient = `
289
299
  }
290
300
 
291
301
  function clearError(file) {
292
- if (file) _compileErrors.delete(normalizeFile(file));
293
- else _compileErrors.clear();
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
+ }
294
310
  renderErrors();
295
311
  }
296
312
 
@@ -691,7 +707,8 @@ export function serve(entrypoint, flags) {
691
707
  _statusSaved = true
692
708
  _statusKind = 'errors'
693
709
 
694
- for (const [file, item] of _activeErrors.entries()) {
710
+ for (const item of _activeErrors.values()) {
711
+ const file = item.file
695
712
  process.stdout.write(` ${theme.folder(item.time)} ${theme.filename(file)} ${theme.failure(' fail ')}\n`)
696
713
  for (const err of item.errors) {
697
714
  try { printerr(err) } catch(_) { process.stdout.write(' ' + err.message + '\n') }
@@ -708,6 +725,7 @@ export function serve(entrypoint, flags) {
708
725
 
709
726
  function normalizeFile(file) {
710
727
  let value = String(file || '')
728
+ value = value.split(/[?#]/)[0]
711
729
  if (path.isAbsolute(value)) {
712
730
  const rel = path.relative(process.cwd(), value)
713
731
  if (!rel.startsWith('..')) value = rel
@@ -721,6 +739,78 @@ export function serve(entrypoint, flags) {
721
739
  const srcRoot = path.resolve(srcDir)
722
740
  const srcRel = normalizeFile(srcRoot)
723
741
 
742
+ function unprefixFile(file) {
743
+ return normalizeFile(file).replace(/^(?:html|css|js|static):/, '')
744
+ }
745
+
746
+ function fileVariants(file) {
747
+ const key = unprefixFile(file)
748
+ const variants = new Set([key])
749
+
750
+ if (srcRel && key.startsWith(srcRel + '/')) variants.add(key.slice(srcRel.length + 1))
751
+ else if (srcRel && key) variants.add(srcRel + '/' + key)
752
+
753
+ return Array.from(variants).filter(Boolean)
754
+ }
755
+
756
+ function fileCandidates(file) {
757
+ const candidates = []
758
+ for (const variant of fileVariants(file)) {
759
+ candidates.push(path.resolve(variant))
760
+ if (!variant.startsWith(srcRel + '/')) candidates.push(path.resolve(srcRoot, variant))
761
+ }
762
+ return candidates
763
+ }
764
+
765
+ function physicalFileKey(file) {
766
+ for (const candidate of fileCandidates(file)) {
767
+ try {
768
+ const stat = statSync(candidate)
769
+ if (!stat.isFile()) continue
770
+ const real = realpathSync(candidate).replaceAll('\\', '/')
771
+ return `fs:${stat.dev}:${stat.ino}:${real}`
772
+ } catch(_) {
773
+ // ignore non-existing aliases
774
+ }
775
+ }
776
+ return null
777
+ }
778
+
779
+ function errorKey(file) {
780
+ return physicalFileKey(file) || `path:${normalizeFile(file)}`
781
+ }
782
+
783
+ function sameFile(left, right) {
784
+ const leftPhysical = physicalFileKey(left)
785
+ const rightPhysical = physicalFileKey(right)
786
+ if (leftPhysical && rightPhysical && leftPhysical === rightPhysical) return true
787
+
788
+ const lefts = fileVariants(left)
789
+ const rights = fileVariants(right)
790
+
791
+ for (const a of lefts) {
792
+ for (const b of rights) {
793
+ if (a === b || a.endsWith('/' + b) || b.endsWith('/' + a)) return true
794
+ }
795
+ }
796
+
797
+ return false
798
+ }
799
+
800
+ function takeError(file) {
801
+ let previous = null
802
+ const target = errorKey(file)
803
+ const keys = Array.from(_activeErrors.keys())
804
+ for (const key of keys) {
805
+ const item = _activeErrors.get(key)
806
+ const storedFile = item?.file || key.replace(/^path:/, '')
807
+ if (key !== target && !sameFile(storedFile, file)) continue
808
+ previous ||= _activeErrors.get(key)
809
+ _activeErrors.delete(key)
810
+ }
811
+ return previous
812
+ }
813
+
724
814
  function errorMessage(error) {
725
815
  return error?.message || String(error)
726
816
  }
@@ -751,19 +841,22 @@ export function serve(entrypoint, flags) {
751
841
  .join('\n---\n')
752
842
  }
753
843
 
754
- function showTrackedError(file, item) {
844
+ function showTrackedError(item) {
845
+ const file = item.file
755
846
  if (_isTTY) renderErrorPanel()
756
847
  else printStatus(file, 'fail', item.errors)
757
848
  broadcast({ type: 'error', file, time: item.time, errors: item.payload })
758
849
  }
759
850
 
760
851
  function reportError(file, errors) {
761
- const key = normalizeFile(file)
852
+ const display = normalizeFile(file)
853
+ const key = errorKey(display)
762
854
  const list = Array.isArray(errors) ? errors : [errors]
763
855
  const signature = errorSignature(list)
764
- const previous = _activeErrors.get(key)
856
+ const previous = takeError(display)
765
857
 
766
858
  const item = {
859
+ file: display,
767
860
  signature,
768
861
  errors: list,
769
862
  payload: serializeErrors(list),
@@ -771,11 +864,11 @@ export function serve(entrypoint, flags) {
771
864
  }
772
865
  _activeErrors.set(key, item)
773
866
  if (previous?.signature === signature && !_isTTY) {
774
- broadcast({ type: 'error', file: key, time: item.time, errors: item.payload })
867
+ broadcast({ type: 'error', file: display, time: item.time, errors: item.payload })
775
868
  return
776
869
  }
777
870
 
778
- showTrackedError(key, item)
871
+ showTrackedError(item)
779
872
  }
780
873
 
781
874
  function errorText(errors) {
@@ -790,26 +883,24 @@ export function serve(entrypoint, flags) {
790
883
 
791
884
  function clearError(file) {
792
885
  const key = file ? normalizeFile(file) : null
793
- const wasStatusFile = key && _statusFile === key
794
- const hadError = key ? _activeErrors.has(key) : _activeErrors.size > 0
886
+ const hadPanel = _statusKind === 'errors'
887
+ const wasStatusFile = key && _statusFile && sameFile(_statusFile, key)
888
+ const hadError = key ? !!takeError(key) : _activeErrors.size > 0
795
889
 
796
- if (key) _activeErrors.delete(key)
797
- else _activeErrors.clear()
798
-
799
- if (key && !hadError && !wasStatusFile) return
890
+ if (!key) _activeErrors.clear()
800
891
 
801
892
  let showedNext = false
802
- if (_isTTY && (hadError || _statusKind === 'errors')) {
893
+ if (_isTTY && (hadError || wasStatusFile || hadPanel)) {
803
894
  showedNext = renderErrorPanel()
804
- } else {
895
+ } else if (!key || hadError || wasStatusFile) {
805
896
  clearStatus(key)
806
897
  }
807
898
 
808
899
  broadcast({ type: 'clear-error', file: key })
809
900
 
810
901
  if (!_isTTY && wasStatusFile && _activeErrors.size) {
811
- const [nextFile, nextItem] = Array.from(_activeErrors.entries()).at(-1)
812
- showTrackedError(nextFile, nextItem)
902
+ const nextItem = Array.from(_activeErrors.values()).at(-1)
903
+ showTrackedError(nextItem)
813
904
  showedNext = true
814
905
  }
815
906