bimba-cli 0.7.31 → 0.7.32

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.31",
3
+ "version": "0.7.32",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/serve.js CHANGED
@@ -632,6 +632,9 @@ export function serve(entrypoint, flags) {
632
632
  let _statusRows = 0
633
633
  let _statusTimer = null
634
634
  let _statusFile = null
635
+ let _statusInline = false
636
+ let _statusWidth = 0
637
+ let _eraseTimers = []
635
638
  const _isTTY = process.stdout.isTTY
636
639
 
637
640
  function stripAnsi(text) {
@@ -652,11 +655,17 @@ export function serve(entrypoint, flags) {
652
655
  clearTimeout(_statusTimer)
653
656
  _statusTimer = null
654
657
  }
655
- if (_isTTY && _statusRows) {
658
+ _eraseTimers.forEach(timer => clearTimeout(timer))
659
+ _eraseTimers = []
660
+ if (_isTTY && _statusInline) {
661
+ process.stdout.write('\r\x1b[J')
662
+ } else if (_isTTY && _statusRows) {
656
663
  process.stdout.write(`\x1b[${_statusRows}A\r\x1b[J`)
657
664
  }
658
665
  _statusRows = 0
659
666
  _statusFile = null
667
+ _statusInline = false
668
+ _statusWidth = 0
660
669
  return true
661
670
  }
662
671
 
@@ -703,12 +712,46 @@ export function serve(entrypoint, flags) {
703
712
  clearStatus()
704
713
  _statusFile = file
705
714
  const lines = statusLines(file, state, errors)
715
+ if (options.fadeAfter && lines.length === 1) {
716
+ const plain = stripAnsi(lines[0])
717
+ process.stdout.write(lines[0])
718
+ _statusRows = 1
719
+ _statusInline = true
720
+ _statusWidth = plain.length
721
+ _statusTimer = setTimeout(() => eraseStatus(file), options.fadeAfter)
722
+ return
723
+ }
724
+
706
725
  process.stdout.write(lines.join('\n') + '\n')
707
726
  _statusRows = renderedRows(lines)
708
727
 
709
728
  if (options.clearAfter) _statusTimer = setTimeout(() => clearStatus(file), options.clearAfter)
710
729
  }
711
730
 
731
+ function eraseStatus(file) {
732
+ if (file && _statusFile && _statusFile !== file) return
733
+ _statusTimer = null
734
+ if (!_isTTY || !_statusInline) {
735
+ clearStatus(file)
736
+ return
737
+ }
738
+
739
+ const total = _statusWidth
740
+ for (let i = 1; i <= total; i++) {
741
+ _eraseTimers.push(setTimeout(() => {
742
+ if (!_statusInline || _statusFile !== file) return
743
+ process.stdout.write('\x1b[1D \x1b[1D')
744
+ if (i === total) {
745
+ _statusRows = 0
746
+ _statusFile = null
747
+ _statusInline = false
748
+ _statusWidth = 0
749
+ _eraseTimers = []
750
+ }
751
+ }, i * 22))
752
+ }
753
+ }
754
+
712
755
  // ── File watcher ───────────────────────────────────────────────────────────
713
756
 
714
757
  const _activeErrors = new Map()
@@ -869,6 +912,49 @@ export function serve(entrypoint, flags) {
869
912
  return true
870
913
  }
871
914
 
915
+ function existingFileForError(file) {
916
+ for (const candidate of fileCandidates(file)) {
917
+ try {
918
+ const stat = statSync(candidate)
919
+ if (stat.isFile()) return candidate
920
+ } catch(_) {
921
+ // ignore aliases that no longer exist
922
+ }
923
+ }
924
+ return null
925
+ }
926
+
927
+ async function reconcileActiveErrors() {
928
+ if (!_activeErrors.size) return false
929
+
930
+ let changed = false
931
+ const items = Array.from(_activeErrors.values())
932
+ for (const item of items) {
933
+ const filepath = existingFileForError(item.file)
934
+ if (!filepath) {
935
+ if (takeError(item.file)) {
936
+ _terminalErrors.delete(terminalErrorKey(item.file))
937
+ broadcast({ type: 'clear-error', file: item.file })
938
+ changed = true
939
+ }
940
+ continue
941
+ }
942
+
943
+ if (!filepath.endsWith('.imba')) continue
944
+
945
+ const out = await compileFile(filepath)
946
+ if (out.errors?.length) continue
947
+
948
+ if (takeError(item.file)) {
949
+ _terminalErrors.delete(terminalErrorKey(item.file))
950
+ broadcast({ type: 'clear-error', file: item.file })
951
+ changed = true
952
+ }
953
+ }
954
+
955
+ return changed
956
+ }
957
+
872
958
  function showTrackedError(item) {
873
959
  const file = item.file
874
960
  if (_isTTY) renderActiveErrors()
@@ -943,15 +1029,20 @@ export function serve(entrypoint, flags) {
943
1029
  return { cleared: hadError || wasStatusFile, file: key, showedNext }
944
1030
  }
945
1031
 
946
- function markSuccess(file) {
1032
+ async function markSuccess(file) {
947
1033
  const key = normalizeFile(file)
948
1034
  const result = clearError(key)
1035
+ const reconciled = await reconcileActiveErrors()
949
1036
  const active = _activeErrors.size
950
- const shouldPrint = result?.cleared && !result.showedNext && !active
1037
+ let showedNext = false
1038
+ if (active && _isTTY) showedNext = renderActiveErrors()
1039
+ else if (_isTTY && (reconciled || result.showedNext)) clearStatus()
1040
+ else showedNext = result.showedNext
1041
+ const shouldPrint = (result?.cleared || reconciled) && !showedNext && !active
951
1042
  if (shouldPrint) {
952
- printStatus(key, 'ok', null, { clearAfter: 3500 })
1043
+ printStatus(key, 'ok', null, { fadeAfter: 3500 })
953
1044
  }
954
- return { cleared: !!result?.cleared, printed: !!shouldPrint, showedNext: !!result?.showedNext, active }
1045
+ return { cleared: !!result?.cleared || reconciled, printed: !!shouldPrint, showedNext: !!showedNext, active }
955
1046
  }
956
1047
 
957
1048
  const _debounce = new Map()
@@ -1019,12 +1110,12 @@ export function serve(entrypoint, flags) {
1019
1110
  return
1020
1111
  }
1021
1112
 
1022
- const success = markSuccess(rel)
1113
+ const success = await markSuccess(rel)
1023
1114
 
1024
1115
  // No change at all — skip
1025
1116
  if (out.changeType === 'none' || out.changeType === 'cached') return
1026
1117
 
1027
- if (!success.printed && !success.showedNext && !success.active) printStatus(rel, 'ok', null, { clearAfter: 3500 })
1118
+ if (!success.printed && !success.showedNext && !success.active) printStatus(rel, 'ok', null, { fadeAfter: 3500 })
1028
1119
  broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
1029
1120
  } catch(e) {
1030
1121
  if (!isCurrentChange(file, version)) return
@@ -1062,7 +1153,7 @@ export function serve(entrypoint, flags) {
1062
1153
  const file = 'vendor:' + (specifier || pathname)
1063
1154
  const bundled = specifier ? await bundleVendor(specifier) : null
1064
1155
  if (bundled?.code) {
1065
- markSuccess(file)
1156
+ await markSuccess(file)
1066
1157
  return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
1067
1158
  }
1068
1159
 
@@ -1075,7 +1166,7 @@ export function serve(entrypoint, flags) {
1075
1166
  const file = 'html:' + normalizeFile(htmlFile)
1076
1167
  try {
1077
1168
  let html = await Bun.file(htmlFile).text()
1078
- markSuccess(file)
1169
+ await markSuccess(file)
1079
1170
  return new Response(transformHtml(html, entrypoint), {
1080
1171
  headers: { 'Content-Type': 'text/html' },
1081
1172
  })
@@ -1101,7 +1192,7 @@ export function serve(entrypoint, flags) {
1101
1192
  if (out.errors?.length) {
1102
1193
  return errorResponse(file, out.errors)
1103
1194
  }
1104
- markSuccess(file)
1195
+ await markSuccess(file)
1105
1196
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
1106
1197
  } catch(e) {
1107
1198
  if (isMissingFileError(e)) {
@@ -1123,7 +1214,7 @@ export function serve(entrypoint, flags) {
1123
1214
  try {
1124
1215
  if (cssFile && await cssFile.exists()) {
1125
1216
  if (req.headers.get('sec-fetch-dest') === 'style') {
1126
- markSuccess(file)
1217
+ await markSuccess(file)
1127
1218
  return new Response(cssFile, { headers: { 'Content-Type': 'text/css' } })
1128
1219
  }
1129
1220
 
@@ -1135,7 +1226,7 @@ export function serve(entrypoint, flags) {
1135
1226
  `if (!el) { el = document.createElement('style'); el.setAttribute('data-bimba-css', id); document.head.appendChild(el); }`,
1136
1227
  `el.textContent = ${JSON.stringify(css)};`,
1137
1228
  ].join('\n')
1138
- markSuccess(file)
1229
+ await markSuccess(file)
1139
1230
  return new Response(js, { headers: { 'Content-Type': 'application/javascript' } })
1140
1231
  }
1141
1232
  } catch (error) {
@@ -1153,7 +1244,7 @@ export function serve(entrypoint, flags) {
1153
1244
  const file = 'js:' + normalizeFile(jsFile)
1154
1245
  try {
1155
1246
  const response = await serveJavaScriptFile(jsFile)
1156
- markSuccess(file)
1247
+ await markSuccess(file)
1157
1248
  return response
1158
1249
  } catch (error) {
1159
1250
  if (isMissingFileError(error)) {
@@ -1180,7 +1271,7 @@ export function serve(entrypoint, flags) {
1180
1271
  if (out.errors?.length) {
1181
1272
  return errorResponse(file, out.errors)
1182
1273
  }
1183
- markSuccess(file)
1274
+ await markSuccess(file)
1184
1275
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
1185
1276
  }
1186
1277
 
@@ -1188,7 +1279,7 @@ export function serve(entrypoint, flags) {
1188
1279
  const file = 'vendor:' + normalizeFile(pathname)
1189
1280
  const bundled = await bundleVendor(path.resolve(resolved))
1190
1281
  if (bundled?.code) {
1191
- markSuccess(file)
1282
+ await markSuccess(file)
1192
1283
  return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
1193
1284
  }
1194
1285
  return errorResponse(file, bundled?.errors || [`Could not bundle ${pathname}`])
@@ -1200,13 +1291,13 @@ export function serve(entrypoint, flags) {
1200
1291
  const inHtmlDirPath = path.join(htmlDir, pathname)
1201
1292
  const inHtmlDir = Bun.file(inHtmlDirPath)
1202
1293
  if (await inHtmlDir.exists()) {
1203
- markSuccess('static:' + normalizeFile(inHtmlDirPath))
1294
+ await markSuccess('static:' + normalizeFile(inHtmlDirPath))
1204
1295
  return new Response(inHtmlDir)
1205
1296
  }
1206
1297
  const inRootPath = '.' + pathname
1207
1298
  const inRoot = Bun.file(inRootPath)
1208
1299
  if (await inRoot.exists()) {
1209
- markSuccess('static:' + normalizeFile(inRootPath))
1300
+ await markSuccess('static:' + normalizeFile(inRootPath))
1210
1301
  return new Response(inRoot)
1211
1302
  }
1212
1303
  } catch (error) {
@@ -1228,7 +1319,7 @@ export function serve(entrypoint, flags) {
1228
1319
  if (out.errors?.length) {
1229
1320
  return errorResponse(file, out.errors)
1230
1321
  }
1231
- markSuccess(file)
1322
+ await markSuccess(file)
1232
1323
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
1233
1324
  }
1234
1325
  for (const ext of ['.js', '.mjs']) {
@@ -1237,7 +1328,7 @@ export function serve(entrypoint, flags) {
1237
1328
  const file = 'js:' + normalizeFile(withExt)
1238
1329
  try {
1239
1330
  const response = await serveJavaScriptFile(withExt)
1240
- markSuccess(file)
1331
+ await markSuccess(file)
1241
1332
  return response
1242
1333
  } catch (error) {
1243
1334
  if (isMissingFileError(error)) {
@@ -1255,7 +1346,7 @@ export function serve(entrypoint, flags) {
1255
1346
  const file = 'html:' + normalizeFile(htmlPath)
1256
1347
  try {
1257
1348
  let html = await Bun.file(htmlPath).text()
1258
- markSuccess(file)
1349
+ await markSuccess(file)
1259
1350
  return new Response(transformHtml(html, entrypoint), {
1260
1351
  headers: { 'Content-Type': 'text/html' },
1261
1352
  })