bimba-cli 0.7.20 → 0.7.22

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 (3) hide show
  1. package/package.json +1 -1
  2. package/plugin.js +1 -1
  3. package/serve.js +191 -42
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.20",
3
+ "version": "0.7.22",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/plugin.js CHANGED
@@ -48,7 +48,7 @@ function compileErrorSnippet(error) {
48
48
 
49
49
  function compileErrorSignature(errors) {
50
50
  return errors
51
- .map(error => [compileErrorMessage(error), compileErrorLine(error), compileErrorSnippet(error)].join('\n'))
51
+ .map(error => [compileErrorMessage(error), compileErrorLine(error)].join('\n'))
52
52
  .join('\n---\n');
53
53
  }
54
54
 
package/serve.js CHANGED
@@ -354,16 +354,39 @@ function rewriteBareImports(js) {
354
354
  return js
355
355
  }
356
356
 
357
+ function isMissingFileError(error) {
358
+ return error?.code === 'ENOENT' || String(error?.message || error).includes('ENOENT: no such file or directory')
359
+ }
360
+
361
+ function missingCompileResult(filepath) {
362
+ dropFileState(filepath)
363
+ return { js: '', errors: [], slots: null, changeType: 'missing', missing: true }
364
+ }
365
+
357
366
  async function compileFile(filepath) {
358
367
  const abs = path.resolve(filepath)
368
+ if (!existsSync(abs)) return missingCompileResult(abs)
369
+
359
370
  const file = Bun.file(filepath)
360
- const stat = await file.stat()
371
+ let stat
372
+ try {
373
+ stat = await file.stat()
374
+ } catch (error) {
375
+ if (isMissingFileError(error)) return missingCompileResult(abs)
376
+ throw error
377
+ }
361
378
  const mtime = stat.mtime.getTime()
362
379
 
363
380
  const cached = _compileCache.get(abs)
364
381
  if (cached && cached.mtime === mtime) return _normalizeResult(cached.result, { changeType: 'cached' })
365
382
 
366
- const code = await file.text()
383
+ let code
384
+ try {
385
+ code = await file.text()
386
+ } catch (error) {
387
+ if (isMissingFileError(error)) return missingCompileResult(abs)
388
+ throw error
389
+ }
367
390
  const result = compiler.compile(code, {
368
391
  sourcePath: filepath,
369
392
  platform: 'browser',
@@ -655,7 +678,7 @@ export function serve(entrypoint, flags) {
655
678
 
656
679
  function errorSignature(errors) {
657
680
  return serializeErrors(errors)
658
- .map(error => [error.message, error.line || '', error.snippet || ''].join('\n'))
681
+ .map(error => [error.message, error.line || ''].join('\n'))
659
682
  .join('\n---\n')
660
683
  }
661
684
 
@@ -685,6 +708,16 @@ export function serve(entrypoint, flags) {
685
708
  showTrackedError(key, item)
686
709
  }
687
710
 
711
+ function errorText(errors) {
712
+ const list = Array.isArray(errors) ? errors : [errors]
713
+ return list.map(errorMessage).join('\n')
714
+ }
715
+
716
+ function errorResponse(file, errors, status = 500) {
717
+ reportError(file, errors)
718
+ return new Response(errorText(errors), { status })
719
+ }
720
+
688
721
  function clearError(file) {
689
722
  const key = file ? normalizeFile(file) : null
690
723
  const wasStatusFile = key && _statusFile === key
@@ -698,10 +731,22 @@ export function serve(entrypoint, flags) {
698
731
  clearStatus(key)
699
732
  broadcast({ type: 'clear-error', file: key })
700
733
 
734
+ let showedNext = false
701
735
  if (wasStatusFile && _activeErrors.size) {
702
736
  const [nextFile, nextItem] = Array.from(_activeErrors.entries()).at(-1)
703
737
  showTrackedError(nextFile, nextItem)
738
+ showedNext = true
704
739
  }
740
+
741
+ return { cleared: hadError || wasStatusFile, file: key, showedNext }
742
+ }
743
+
744
+ function markSuccess(file) {
745
+ const key = normalizeFile(file)
746
+ const result = clearError(key)
747
+ const shouldPrint = result?.cleared && !result.showedNext
748
+ if (shouldPrint) printStatus(key, 'ok')
749
+ return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext }
705
750
  }
706
751
 
707
752
  const _debounce = new Map()
@@ -742,21 +787,30 @@ export function serve(entrypoint, flags) {
742
787
  const out = await compileFile(filepath)
743
788
 
744
789
  if (!isCurrentChange(filename, version)) return
790
+ if (out.missing) {
791
+ clearError(rel)
792
+ return
793
+ }
745
794
 
746
795
  if (out.errors?.length) {
747
796
  reportError(rel, out.errors)
748
797
  return
749
798
  }
750
799
 
751
- clearError(rel)
800
+ const success = markSuccess(rel)
752
801
 
753
802
  // No change at all — skip
754
803
  if (out.changeType === 'none' || out.changeType === 'cached') return
755
804
 
756
- printStatus(rel, 'ok')
805
+ if (!success.printed && !success.showedNext) printStatus(rel, 'ok')
757
806
  broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
758
807
  } catch(e) {
759
808
  if (!isCurrentChange(filename, version)) return
809
+ if (isMissingFileError(e)) {
810
+ dropFileState(filepath)
811
+ clearError(rel)
812
+ return
813
+ }
760
814
  reportError(rel, [{ message: e.message, snippet: e.stack || e.message }])
761
815
  }
762
816
  }
@@ -774,6 +828,7 @@ export function serve(entrypoint, flags) {
774
828
  fetch: async (req, server) => {
775
829
  const url = new URL(req.url)
776
830
  const pathname = url.pathname
831
+ try {
777
832
 
778
833
  // WebSocket upgrade for HMR
779
834
  if (pathname === '/__hmr__') {
@@ -782,21 +837,33 @@ export function serve(entrypoint, flags) {
782
837
 
783
838
  if (pathname.startsWith('/__bimba_vendor__/')) {
784
839
  const specifier = vendorSpecifierFromPath(pathname)
840
+ const file = 'vendor:' + (specifier || pathname)
785
841
  const bundled = specifier ? await bundleVendor(specifier) : null
786
842
  if (bundled?.code) {
843
+ markSuccess(file)
787
844
  return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
788
845
  }
789
846
 
790
- return new Response((bundled?.errors || [`Could not bundle vendor module: ${specifier}`]).join('\n'), { status: 500 })
847
+ return errorResponse(file, bundled?.errors || [`Could not bundle vendor module: ${specifier}`])
791
848
  }
792
849
 
793
850
  // HTML: index or any .html file
794
851
  if (pathname === '/' || pathname.endsWith('.html')) {
795
852
  const htmlFile = pathname === '/' ? htmlPath : '.' + pathname
796
- let html = await Bun.file(htmlFile).text()
797
- return new Response(transformHtml(html, entrypoint), {
798
- headers: { 'Content-Type': 'text/html' },
799
- })
853
+ const file = 'html:' + normalizeFile(htmlFile)
854
+ try {
855
+ let html = await Bun.file(htmlFile).text()
856
+ markSuccess(file)
857
+ return new Response(transformHtml(html, entrypoint), {
858
+ headers: { 'Content-Type': 'text/html' },
859
+ })
860
+ } catch (error) {
861
+ if (isMissingFileError(error)) {
862
+ clearError(file)
863
+ return new Response('Not Found', { status: 404 })
864
+ }
865
+ return errorResponse(file, [error])
866
+ }
800
867
  }
801
868
 
802
869
  // Imba files: compile on demand and serve as JS
@@ -805,15 +872,22 @@ export function serve(entrypoint, flags) {
805
872
  const file = normalizeFile(pathname)
806
873
  try {
807
874
  const out = await compileFile(filepath)
875
+ if (out.missing) {
876
+ clearError(file)
877
+ return new Response('Not Found', { status: 404 })
878
+ }
808
879
  if (out.errors?.length) {
809
- reportError(file, out.errors)
810
- return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
880
+ return errorResponse(file, out.errors)
811
881
  }
812
- clearError(file)
882
+ markSuccess(file)
813
883
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
814
884
  } catch(e) {
815
- reportError(file, [{ message: e.message, snippet: e.stack || e.message }])
816
- return new Response(e.message, { status: 500 })
885
+ if (isMissingFileError(e)) {
886
+ dropFileState(filepath)
887
+ clearError(file)
888
+ return new Response('Not Found', { status: 404 })
889
+ }
890
+ return errorResponse(file, [{ message: e.message, snippet: e.stack || e.message }])
817
891
  }
818
892
  }
819
893
 
@@ -823,26 +897,50 @@ export function serve(entrypoint, flags) {
823
897
  if (pathname.endsWith('.css')) {
824
898
  const cssPath = resolveFileCandidate(path.join(htmlDir, pathname)) || resolveFileCandidate('.' + pathname)
825
899
  const cssFile = cssPath ? Bun.file(cssPath) : null
826
- if (cssFile && await cssFile.exists()) {
827
- if (req.headers.get('sec-fetch-dest') === 'style') {
828
- return new Response(cssFile, { headers: { 'Content-Type': 'text/css' } })
829
- }
900
+ const file = 'css:' + normalizeFile(cssPath || pathname)
901
+ try {
902
+ if (cssFile && await cssFile.exists()) {
903
+ if (req.headers.get('sec-fetch-dest') === 'style') {
904
+ markSuccess(file)
905
+ return new Response(cssFile, { headers: { 'Content-Type': 'text/css' } })
906
+ }
830
907
 
831
- const css = await cssFile.text()
832
- const id = JSON.stringify(pathname)
833
- const js = [
834
- `const id = ${id};`,
835
- `let el = document.querySelector('style[data-bimba-css=' + JSON.stringify(id) + ']');`,
836
- `if (!el) { el = document.createElement('style'); el.setAttribute('data-bimba-css', id); document.head.appendChild(el); }`,
837
- `el.textContent = ${JSON.stringify(css)};`,
838
- ].join('\n')
839
- return new Response(js, { headers: { 'Content-Type': 'application/javascript' } })
908
+ const css = await cssFile.text()
909
+ const id = JSON.stringify(pathname)
910
+ const js = [
911
+ `const id = ${id};`,
912
+ `let el = document.querySelector('style[data-bimba-css=' + JSON.stringify(id) + ']');`,
913
+ `if (!el) { el = document.createElement('style'); el.setAttribute('data-bimba-css', id); document.head.appendChild(el); }`,
914
+ `el.textContent = ${JSON.stringify(css)};`,
915
+ ].join('\n')
916
+ markSuccess(file)
917
+ return new Response(js, { headers: { 'Content-Type': 'application/javascript' } })
918
+ }
919
+ } catch (error) {
920
+ if (isMissingFileError(error)) {
921
+ clearError(file)
922
+ return new Response('Not Found', { status: 404 })
923
+ }
924
+ return errorResponse(file, [error])
840
925
  }
841
926
  }
842
927
 
843
928
  if (!pathname.startsWith('/node_modules/') && (pathname.endsWith('.js') || pathname.endsWith('.mjs'))) {
844
929
  const jsFile = resolveFileCandidate(path.join(htmlDir, pathname)) || resolveFileCandidate('.' + pathname)
845
- if (jsFile) return serveJavaScriptFile(jsFile)
930
+ if (jsFile) {
931
+ const file = 'js:' + normalizeFile(jsFile)
932
+ try {
933
+ const response = await serveJavaScriptFile(jsFile)
934
+ markSuccess(file)
935
+ return response
936
+ } catch (error) {
937
+ if (isMissingFileError(error)) {
938
+ clearError(file)
939
+ return new Response('Not Found', { status: 404 })
940
+ }
941
+ return errorResponse(file, [error])
942
+ }
943
+ }
846
944
  }
847
945
 
848
946
  // Direct node_modules URLs (from user import maps or explicit imports)
@@ -853,28 +951,45 @@ export function serve(entrypoint, flags) {
853
951
  if (resolved?.endsWith('.imba')) {
854
952
  const out = await compileFile(resolved)
855
953
  const file = normalizeFile(resolved)
954
+ if (out.missing) {
955
+ clearError(file)
956
+ return new Response('Not Found', { status: 404 })
957
+ }
856
958
  if (out.errors?.length) {
857
- reportError(file, out.errors)
858
- return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
959
+ return errorResponse(file, out.errors)
859
960
  }
860
- clearError(file)
961
+ markSuccess(file)
861
962
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
862
963
  }
863
964
 
864
965
  if (resolved) {
966
+ const file = 'vendor:' + normalizeFile(pathname)
865
967
  const bundled = await bundleVendor(path.resolve(resolved))
866
968
  if (bundled?.code) {
969
+ markSuccess(file)
867
970
  return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
868
971
  }
869
- return new Response((bundled?.errors || [`Could not bundle ${pathname}`]).join('\n'), { status: 500 })
972
+ return errorResponse(file, bundled?.errors || [`Could not bundle ${pathname}`])
870
973
  }
871
974
  }
872
975
 
873
976
  // Static files: check htmlDir first (for assets relative to HTML), then root
874
- const inHtmlDir = Bun.file(path.join(htmlDir, pathname))
875
- if (await inHtmlDir.exists()) return new Response(inHtmlDir)
876
- const inRoot = Bun.file('.' + pathname)
877
- if (await inRoot.exists()) return new Response(inRoot)
977
+ try {
978
+ const inHtmlDirPath = path.join(htmlDir, pathname)
979
+ const inHtmlDir = Bun.file(inHtmlDirPath)
980
+ if (await inHtmlDir.exists()) {
981
+ markSuccess('static:' + normalizeFile(inHtmlDirPath))
982
+ return new Response(inHtmlDir)
983
+ }
984
+ const inRootPath = '.' + pathname
985
+ const inRoot = Bun.file(inRootPath)
986
+ if (await inRoot.exists()) {
987
+ markSuccess('static:' + normalizeFile(inRootPath))
988
+ return new Response(inRoot)
989
+ }
990
+ } catch (error) {
991
+ if (!isMissingFileError(error)) return errorResponse('static:' + normalizeFile(pathname), [error])
992
+ }
878
993
 
879
994
  // Try extensions for extensionless paths (e.g. node_modules imports)
880
995
  const lastSegment = pathname.split('/').pop()
@@ -884,28 +999,62 @@ export function serve(entrypoint, flags) {
884
999
  if (existsSync(imbaPath)) {
885
1000
  const out = await compileFile(imbaPath)
886
1001
  const file = normalizeFile(imbaPath)
1002
+ if (out.missing) {
1003
+ clearError(file)
1004
+ return new Response('Not Found', { status: 404 })
1005
+ }
887
1006
  if (out.errors?.length) {
888
- reportError(file, out.errors)
889
- return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
1007
+ return errorResponse(file, out.errors)
890
1008
  }
891
- clearError(file)
1009
+ markSuccess(file)
892
1010
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
893
1011
  }
894
1012
  for (const ext of ['.js', '.mjs']) {
895
1013
  const withExt = '.' + pathname + ext
896
- if (existsSync(withExt)) return serveJavaScriptFile(withExt)
1014
+ if (existsSync(withExt)) {
1015
+ const file = 'js:' + normalizeFile(withExt)
1016
+ try {
1017
+ const response = await serveJavaScriptFile(withExt)
1018
+ markSuccess(file)
1019
+ return response
1020
+ } catch (error) {
1021
+ if (isMissingFileError(error)) {
1022
+ clearError(file)
1023
+ return new Response('Not Found', { status: 404 })
1024
+ }
1025
+ return errorResponse(file, [error])
1026
+ }
1027
+ }
897
1028
  }
898
1029
  }
899
1030
 
900
1031
  // SPA fallback for extension-less paths
901
- if (!lastSegment.includes('.')) {
1032
+ if (!lastSegment.includes('.')) {
1033
+ const file = 'html:' + normalizeFile(htmlPath)
1034
+ try {
902
1035
  let html = await Bun.file(htmlPath).text()
1036
+ markSuccess(file)
903
1037
  return new Response(transformHtml(html, entrypoint), {
904
1038
  headers: { 'Content-Type': 'text/html' },
905
1039
  })
1040
+ } catch (error) {
1041
+ if (isMissingFileError(error)) {
1042
+ clearError(file)
1043
+ return new Response('Not Found', { status: 404 })
1044
+ }
1045
+ return errorResponse(file, [error])
906
1046
  }
1047
+ }
907
1048
 
908
1049
  return new Response('Not Found', { status: 404 })
1050
+ } catch (error) {
1051
+ const file = 'server:' + normalizeFile(pathname || req.url)
1052
+ if (isMissingFileError(error)) {
1053
+ clearError(file)
1054
+ return new Response('Not Found', { status: 404 })
1055
+ }
1056
+ return errorResponse(file, [error])
1057
+ }
909
1058
  },
910
1059
 
911
1060
  websocket: {