bimba-cli 0.7.20 → 0.7.21

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 +174 -37
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.20",
3
+ "version": "0.7.21",
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
@@ -742,6 +775,10 @@ export function serve(entrypoint, flags) {
742
775
  const out = await compileFile(filepath)
743
776
 
744
777
  if (!isCurrentChange(filename, version)) return
778
+ if (out.missing) {
779
+ clearError(rel)
780
+ return
781
+ }
745
782
 
746
783
  if (out.errors?.length) {
747
784
  reportError(rel, out.errors)
@@ -757,6 +794,11 @@ export function serve(entrypoint, flags) {
757
794
  broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
758
795
  } catch(e) {
759
796
  if (!isCurrentChange(filename, version)) return
797
+ if (isMissingFileError(e)) {
798
+ dropFileState(filepath)
799
+ clearError(rel)
800
+ return
801
+ }
760
802
  reportError(rel, [{ message: e.message, snippet: e.stack || e.message }])
761
803
  }
762
804
  }
@@ -774,6 +816,7 @@ export function serve(entrypoint, flags) {
774
816
  fetch: async (req, server) => {
775
817
  const url = new URL(req.url)
776
818
  const pathname = url.pathname
819
+ try {
777
820
 
778
821
  // WebSocket upgrade for HMR
779
822
  if (pathname === '/__hmr__') {
@@ -782,21 +825,33 @@ export function serve(entrypoint, flags) {
782
825
 
783
826
  if (pathname.startsWith('/__bimba_vendor__/')) {
784
827
  const specifier = vendorSpecifierFromPath(pathname)
828
+ const file = 'vendor:' + (specifier || pathname)
785
829
  const bundled = specifier ? await bundleVendor(specifier) : null
786
830
  if (bundled?.code) {
831
+ clearError(file)
787
832
  return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
788
833
  }
789
834
 
790
- return new Response((bundled?.errors || [`Could not bundle vendor module: ${specifier}`]).join('\n'), { status: 500 })
835
+ return errorResponse(file, bundled?.errors || [`Could not bundle vendor module: ${specifier}`])
791
836
  }
792
837
 
793
838
  // HTML: index or any .html file
794
839
  if (pathname === '/' || pathname.endsWith('.html')) {
795
840
  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
- })
841
+ const file = 'html:' + normalizeFile(htmlFile)
842
+ try {
843
+ let html = await Bun.file(htmlFile).text()
844
+ clearError(file)
845
+ return new Response(transformHtml(html, entrypoint), {
846
+ headers: { 'Content-Type': 'text/html' },
847
+ })
848
+ } catch (error) {
849
+ if (isMissingFileError(error)) {
850
+ clearError(file)
851
+ return new Response('Not Found', { status: 404 })
852
+ }
853
+ return errorResponse(file, [error])
854
+ }
800
855
  }
801
856
 
802
857
  // Imba files: compile on demand and serve as JS
@@ -805,15 +860,22 @@ export function serve(entrypoint, flags) {
805
860
  const file = normalizeFile(pathname)
806
861
  try {
807
862
  const out = await compileFile(filepath)
863
+ if (out.missing) {
864
+ clearError(file)
865
+ return new Response('Not Found', { status: 404 })
866
+ }
808
867
  if (out.errors?.length) {
809
- reportError(file, out.errors)
810
- return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
868
+ return errorResponse(file, out.errors)
811
869
  }
812
870
  clearError(file)
813
871
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
814
872
  } catch(e) {
815
- reportError(file, [{ message: e.message, snippet: e.stack || e.message }])
816
- return new Response(e.message, { status: 500 })
873
+ if (isMissingFileError(e)) {
874
+ dropFileState(filepath)
875
+ clearError(file)
876
+ return new Response('Not Found', { status: 404 })
877
+ }
878
+ return errorResponse(file, [{ message: e.message, snippet: e.stack || e.message }])
817
879
  }
818
880
  }
819
881
 
@@ -823,26 +885,50 @@ export function serve(entrypoint, flags) {
823
885
  if (pathname.endsWith('.css')) {
824
886
  const cssPath = resolveFileCandidate(path.join(htmlDir, pathname)) || resolveFileCandidate('.' + pathname)
825
887
  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
- }
888
+ const file = 'css:' + normalizeFile(cssPath || pathname)
889
+ try {
890
+ if (cssFile && await cssFile.exists()) {
891
+ if (req.headers.get('sec-fetch-dest') === 'style') {
892
+ clearError(file)
893
+ return new Response(cssFile, { headers: { 'Content-Type': 'text/css' } })
894
+ }
830
895
 
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' } })
896
+ const css = await cssFile.text()
897
+ const id = JSON.stringify(pathname)
898
+ const js = [
899
+ `const id = ${id};`,
900
+ `let el = document.querySelector('style[data-bimba-css=' + JSON.stringify(id) + ']');`,
901
+ `if (!el) { el = document.createElement('style'); el.setAttribute('data-bimba-css', id); document.head.appendChild(el); }`,
902
+ `el.textContent = ${JSON.stringify(css)};`,
903
+ ].join('\n')
904
+ clearError(file)
905
+ return new Response(js, { headers: { 'Content-Type': 'application/javascript' } })
906
+ }
907
+ } catch (error) {
908
+ if (isMissingFileError(error)) {
909
+ clearError(file)
910
+ return new Response('Not Found', { status: 404 })
911
+ }
912
+ return errorResponse(file, [error])
840
913
  }
841
914
  }
842
915
 
843
916
  if (!pathname.startsWith('/node_modules/') && (pathname.endsWith('.js') || pathname.endsWith('.mjs'))) {
844
917
  const jsFile = resolveFileCandidate(path.join(htmlDir, pathname)) || resolveFileCandidate('.' + pathname)
845
- if (jsFile) return serveJavaScriptFile(jsFile)
918
+ if (jsFile) {
919
+ const file = 'js:' + normalizeFile(jsFile)
920
+ try {
921
+ const response = await serveJavaScriptFile(jsFile)
922
+ clearError(file)
923
+ return response
924
+ } catch (error) {
925
+ if (isMissingFileError(error)) {
926
+ clearError(file)
927
+ return new Response('Not Found', { status: 404 })
928
+ }
929
+ return errorResponse(file, [error])
930
+ }
931
+ }
846
932
  }
847
933
 
848
934
  // Direct node_modules URLs (from user import maps or explicit imports)
@@ -853,28 +939,45 @@ export function serve(entrypoint, flags) {
853
939
  if (resolved?.endsWith('.imba')) {
854
940
  const out = await compileFile(resolved)
855
941
  const file = normalizeFile(resolved)
942
+ if (out.missing) {
943
+ clearError(file)
944
+ return new Response('Not Found', { status: 404 })
945
+ }
856
946
  if (out.errors?.length) {
857
- reportError(file, out.errors)
858
- return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
947
+ return errorResponse(file, out.errors)
859
948
  }
860
949
  clearError(file)
861
950
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
862
951
  }
863
952
 
864
953
  if (resolved) {
954
+ const file = 'vendor:' + normalizeFile(pathname)
865
955
  const bundled = await bundleVendor(path.resolve(resolved))
866
956
  if (bundled?.code) {
957
+ clearError(file)
867
958
  return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
868
959
  }
869
- return new Response((bundled?.errors || [`Could not bundle ${pathname}`]).join('\n'), { status: 500 })
960
+ return errorResponse(file, bundled?.errors || [`Could not bundle ${pathname}`])
870
961
  }
871
962
  }
872
963
 
873
964
  // 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)
965
+ try {
966
+ const inHtmlDirPath = path.join(htmlDir, pathname)
967
+ const inHtmlDir = Bun.file(inHtmlDirPath)
968
+ if (await inHtmlDir.exists()) {
969
+ clearError('static:' + normalizeFile(inHtmlDirPath))
970
+ return new Response(inHtmlDir)
971
+ }
972
+ const inRootPath = '.' + pathname
973
+ const inRoot = Bun.file(inRootPath)
974
+ if (await inRoot.exists()) {
975
+ clearError('static:' + normalizeFile(inRootPath))
976
+ return new Response(inRoot)
977
+ }
978
+ } catch (error) {
979
+ if (!isMissingFileError(error)) return errorResponse('static:' + normalizeFile(pathname), [error])
980
+ }
878
981
 
879
982
  // Try extensions for extensionless paths (e.g. node_modules imports)
880
983
  const lastSegment = pathname.split('/').pop()
@@ -884,28 +987,62 @@ export function serve(entrypoint, flags) {
884
987
  if (existsSync(imbaPath)) {
885
988
  const out = await compileFile(imbaPath)
886
989
  const file = normalizeFile(imbaPath)
990
+ if (out.missing) {
991
+ clearError(file)
992
+ return new Response('Not Found', { status: 404 })
993
+ }
887
994
  if (out.errors?.length) {
888
- reportError(file, out.errors)
889
- return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
995
+ return errorResponse(file, out.errors)
890
996
  }
891
997
  clearError(file)
892
998
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
893
999
  }
894
1000
  for (const ext of ['.js', '.mjs']) {
895
1001
  const withExt = '.' + pathname + ext
896
- if (existsSync(withExt)) return serveJavaScriptFile(withExt)
1002
+ if (existsSync(withExt)) {
1003
+ const file = 'js:' + normalizeFile(withExt)
1004
+ try {
1005
+ const response = await serveJavaScriptFile(withExt)
1006
+ clearError(file)
1007
+ return response
1008
+ } catch (error) {
1009
+ if (isMissingFileError(error)) {
1010
+ clearError(file)
1011
+ return new Response('Not Found', { status: 404 })
1012
+ }
1013
+ return errorResponse(file, [error])
1014
+ }
1015
+ }
897
1016
  }
898
1017
  }
899
1018
 
900
1019
  // SPA fallback for extension-less paths
901
- if (!lastSegment.includes('.')) {
1020
+ if (!lastSegment.includes('.')) {
1021
+ const file = 'html:' + normalizeFile(htmlPath)
1022
+ try {
902
1023
  let html = await Bun.file(htmlPath).text()
1024
+ clearError(file)
903
1025
  return new Response(transformHtml(html, entrypoint), {
904
1026
  headers: { 'Content-Type': 'text/html' },
905
1027
  })
1028
+ } catch (error) {
1029
+ if (isMissingFileError(error)) {
1030
+ clearError(file)
1031
+ return new Response('Not Found', { status: 404 })
1032
+ }
1033
+ return errorResponse(file, [error])
906
1034
  }
1035
+ }
907
1036
 
908
1037
  return new Response('Not Found', { status: 404 })
1038
+ } catch (error) {
1039
+ const file = 'server:' + normalizeFile(pathname || req.url)
1040
+ if (isMissingFileError(error)) {
1041
+ clearError(file)
1042
+ return new Response('Not Found', { status: 404 })
1043
+ }
1044
+ return errorResponse(file, [error])
1045
+ }
909
1046
  },
910
1047
 
911
1048
  websocket: {