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.
- package/package.json +1 -1
- package/plugin.js +1 -1
- package/serve.js +191 -42
package/package.json
CHANGED
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)
|
|
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
|
-
|
|
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
|
-
|
|
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 || ''
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
-
|
|
810
|
-
return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
880
|
+
return errorResponse(file, out.errors)
|
|
811
881
|
}
|
|
812
|
-
|
|
882
|
+
markSuccess(file)
|
|
813
883
|
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
814
884
|
} catch(e) {
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
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)
|
|
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
|
-
|
|
858
|
-
return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
959
|
+
return errorResponse(file, out.errors)
|
|
859
960
|
}
|
|
860
|
-
|
|
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
|
|
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
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
889
|
-
return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
1007
|
+
return errorResponse(file, out.errors)
|
|
890
1008
|
}
|
|
891
|
-
|
|
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))
|
|
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
|
-
|
|
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: {
|