bimba-cli 0.7.19 → 0.7.20

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 (4) hide show
  1. package/index.js +5 -2
  2. package/package.json +1 -1
  3. package/plugin.js +214 -162
  4. package/serve.js +110 -26
package/index.js CHANGED
@@ -168,6 +168,7 @@ async function bundle() {
168
168
  stats.failed = 0
169
169
  stats.compiled = 0
170
170
  stats.errors = 0
171
+ stats.reported = 0
171
172
  stats.bundled = 0
172
173
 
173
174
  const start = Date.now();
@@ -208,8 +209,10 @@ async function bundle() {
208
209
  }
209
210
  }
210
211
 
211
- if(stats.failed)
212
- console.log(theme.start(theme.failure(" Failure ") + theme.filename(` Imba compiler failed to proceed ${stats.failed} file${stats.failed > 1 ? 's' : ''}`)));
212
+ if(stats.failed) {
213
+ if (stats.reported)
214
+ console.log(theme.start(theme.failure(" Failure ") + theme.filename(` Imba compiler failed to proceed ${stats.failed} file${stats.failed > 1 ? 's' : ''}`)));
215
+ }
213
216
  else
214
217
  console.log(theme.start(theme.success("Success") +` It took ${theme.time(Date.now() - start)} ms to bundle ${theme.count(stats.bundled)} file${stats.bundled > 1 ? 's' : ''} to the folder: ${theme.filedir(flags.outdir)}`));
215
218
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bimba-cli",
3
- "version": "0.7.19",
3
+ "version": "0.7.20",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/HeapVoid/bimba.git"
package/plugin.js CHANGED
@@ -1,162 +1,214 @@
1
- import { plugin } from "bun";
2
- import {theme} from './utils.js';
3
- import * as compiler from 'imba/compiler'
4
- import dir from 'path'
5
- import fs from 'fs'
6
- import { Glob } from "bun";
7
- import { unlink } from "node:fs/promises";
8
-
9
- export const cache = dir.join(process.cwd(), '.cache')
10
- if (!fs.existsSync(cache)){ fs.mkdirSync(cache);}
11
-
12
- // this should be reset from outside to get results of entrypoint building
13
- export let stats = {
14
- failed: 0,
15
- compiled: 0,
16
- cached: 0,
17
- bundled: 0,
18
- errors: 0,
19
- };
20
-
21
- // Target platform for the Imba compiler: 'browser' or 'node'
22
- // Set via setTarget() from the CLI before building
23
- export let target = 'browser';
24
- export function setTarget(t) { target = t; }
25
-
26
- export const imbaPlugin = {
27
- name: "imba",
28
- async setup(build) {
29
-
30
- // when an .imba file is imported...
31
- build.onLoad({ filter: /\.imba$/ }, async ({ path }) => {
32
-
33
- const f = dir.parse(path)
34
- let contents = '';
35
-
36
- // return the cached version if exists (include target in hash to avoid cross-platform cache hits)
37
- const cached = dir.join(cache, Bun.hash(path + ':' + target) + '_' + fs.statSync(path).mtimeMs + '.js');
38
- if (fs.existsSync(cached)) {
39
- stats.bundled++;
40
- stats.cached++;
41
- return {
42
- contents: await Bun.file(cached).text(),
43
- loader: "js",
44
- };
45
- }
46
-
47
- // clear previous cached version
48
- const glob = new Glob(Bun.hash(path + ':' + target) + '_' + "*.js");
49
- for await (const file of glob.scan(cache)) if (fs.existsSync(dir.join(cache, file))) unlink(dir.join(cache, file));
50
-
51
- // if no cached version read and compile it with the imba compiler
52
- const file = await Bun.file(path).text();
53
- const platform = target === 'node' || target === 'bun' ? 'node' : 'browser';
54
- const out = compiler.compile(file, {
55
- sourcePath: path,
56
- platform: platform,
57
- comments: false
58
- })
59
-
60
- // the file has been successfully compiled
61
- if (!out.errors?.length) {
62
- console.log(theme.action("compiling: ") + theme.folder(dir.join(f.dir,'/')) + theme.filename(f.base) + " - " + theme.success("compiled"));
63
- stats.bundled++;
64
- stats.compiled++;
65
- contents = out.js;
66
- await Bun.write(cached, contents);
67
- }
68
- // there were errors during compilation
69
- else {
70
- console.log(theme.action("compiling: ") + theme.folder(dir.join(f.dir,'/')) + theme.filename(f.base) + " - " + theme.failure(" fail "));
71
- stats.failed++;
72
- for (let i = 0; i < out.errors.length; i++) {
73
- if(out.errors[i]) printerr(out.errors[i]);
74
- }
75
- stats.errors++;
76
- }
77
-
78
- // and return the compiled source code as "js"
79
- return {
80
- contents,
81
- loader: "js",
82
- };
83
- });
84
- }
85
- };
86
-
87
- plugin(imbaPlugin);
88
-
89
-
90
- // -------------------------------------------------------------------------------
91
- // print pretty messages produced by the imba compiler
92
- // -------------------------------------------------------------------------------
93
-
94
- // print an error generated by the imba compiler
95
- export function printerr(err) {
96
-
97
- // halper function to produce empty strings
98
- const fill = (len = 0) => {return new Array(len + 1).join(' ')}
99
-
100
- // gather the needed information from the compiler error
101
- const snippet = err.toSnippet().split("\n");
102
- const errs = snippet[2] ? snippet[2].indexOf('^') : -1;
103
-
104
- // no source context available — print compact fallback
105
- if (!snippet[1] || errs === -1) {
106
- console.log('');
107
- console.log(fill(10) + theme.error(" " + err.message + " "));
108
- console.log('');
109
- return;
110
- }
111
-
112
- const display = {
113
- error: " " + err.message + " ",
114
- outdent: fill(10),
115
- source: snippet[1] + " ",
116
- margin: " line " + (err.range.start.line + 1) + " ",
117
- errs: errs,
118
- erre: snippet[2].lastIndexOf('^') + 1,
119
- };
120
-
121
- // calculate parameters for priniting a message
122
- const center = display.margin.length + display.errs + Math.floor((display.erre - display.errs) / 2);
123
- const half = Math.ceil((display.error.length - 1) / 2);
124
- const start = Math.max(0, center - half);
125
- const end = start + display.error.length;
126
- const total = Math.max(display.margin.length + display.source.length, end);
127
-
128
- // print emtpy line
129
- console.log('');
130
-
131
- // print line with the error message
132
- console.log(
133
- display.outdent +
134
- theme.margin(fill(Math.min(start, display.margin.length))) +
135
- theme.code(fill(Math.max(0, start - display.margin.length))) +
136
- theme.error(display.error) +
137
- theme.margin(fill(Math.max(0, display.margin.length - end))) +
138
- theme.code(fill(Math.min(total - display.margin.length, total - end)))
139
- );
140
-
141
- // print line with the source code
142
- console.log(
143
- display.outdent +
144
- theme.margin(display.margin) +
145
- theme.code(display.source.slice(0,display.errs)) +
146
- theme.error(display.source.slice(display.errs,display.erre)) +
147
- theme.code(display.source.slice(display.erre)) +
148
- theme.code(fill(total - display.source.length - display.margin.length))
149
- );
150
-
151
- // print empty line to balance the view
152
- // later we can put something usefull here
153
- // for example a link to online docs about the error
154
- console.log(
155
- display.outdent +
156
- theme.margin(fill(display.margin.length)) +
157
- theme.code(fill(total - display.margin.length))
158
- );
159
-
160
- // print emtpy line
161
- console.log('');
162
- }
1
+ import { plugin } from "bun";
2
+ import {theme} from './utils.js';
3
+ import * as compiler from 'imba/compiler'
4
+ import dir from 'path'
5
+ import fs from 'fs'
6
+ import { Glob } from "bun";
7
+ import { unlink } from "node:fs/promises";
8
+
9
+ export const cache = dir.join(process.cwd(), '.cache')
10
+ if (!fs.existsSync(cache)){ fs.mkdirSync(cache);}
11
+
12
+ // this should be reset from outside to get results of entrypoint building
13
+ export let stats = {
14
+ failed: 0,
15
+ compiled: 0,
16
+ cached: 0,
17
+ bundled: 0,
18
+ errors: 0,
19
+ reported: 0,
20
+ };
21
+
22
+ const _activeCompileErrors = new Map();
23
+
24
+ function normalizeCompilePath(filepath) {
25
+ let value = String(filepath || '');
26
+ if (dir.isAbsolute(value)) {
27
+ const rel = dir.relative(process.cwd(), value);
28
+ if (!rel.startsWith('..')) value = rel;
29
+ }
30
+ return value.replaceAll('\\', '/');
31
+ }
32
+
33
+ function compileErrorMessage(error) {
34
+ return error?.message || String(error);
35
+ }
36
+
37
+ function compileErrorLine(error) {
38
+ return error?.range?.start?.line ?? error?.line ?? '';
39
+ }
40
+
41
+ function compileErrorSnippet(error) {
42
+ try {
43
+ return error?.toSnippet?.() || error?.snippet || error?.stack || compileErrorMessage(error);
44
+ } catch(_) {
45
+ return error?.snippet || error?.stack || compileErrorMessage(error);
46
+ }
47
+ }
48
+
49
+ function compileErrorSignature(errors) {
50
+ return errors
51
+ .map(error => [compileErrorMessage(error), compileErrorLine(error), compileErrorSnippet(error)].join('\n'))
52
+ .join('\n---\n');
53
+ }
54
+
55
+ function shouldPrintCompileError(filepath, errors) {
56
+ const key = normalizeCompilePath(filepath);
57
+ const signature = compileErrorSignature(errors);
58
+ const previous = _activeCompileErrors.get(key);
59
+ _activeCompileErrors.set(key, { signature, time: Date.now() });
60
+ return previous?.signature !== signature;
61
+ }
62
+
63
+ function clearCompileError(filepath) {
64
+ _activeCompileErrors.delete(normalizeCompilePath(filepath));
65
+ }
66
+
67
+ // Target platform for the Imba compiler: 'browser' or 'node'
68
+ // Set via setTarget() from the CLI before building
69
+ export let target = 'browser';
70
+ export function setTarget(t) { target = t; }
71
+
72
+ export const imbaPlugin = {
73
+ name: "imba",
74
+ async setup(build) {
75
+
76
+ // when an .imba file is imported...
77
+ build.onLoad({ filter: /\.imba$/ }, async ({ path }) => {
78
+
79
+ const f = dir.parse(path)
80
+ let contents = '';
81
+
82
+ // return the cached version if exists (include target in hash to avoid cross-platform cache hits)
83
+ const cached = dir.join(cache, Bun.hash(path + ':' + target) + '_' + fs.statSync(path).mtimeMs + '.js');
84
+ if (fs.existsSync(cached)) {
85
+ clearCompileError(path);
86
+ stats.bundled++;
87
+ stats.cached++;
88
+ return {
89
+ contents: await Bun.file(cached).text(),
90
+ loader: "js",
91
+ };
92
+ }
93
+
94
+ // clear previous cached version
95
+ const glob = new Glob(Bun.hash(path + ':' + target) + '_' + "*.js");
96
+ for await (const file of glob.scan(cache)) if (fs.existsSync(dir.join(cache, file))) unlink(dir.join(cache, file));
97
+
98
+ // if no cached version read and compile it with the imba compiler
99
+ const file = await Bun.file(path).text();
100
+ const platform = target === 'node' || target === 'bun' ? 'node' : 'browser';
101
+ const out = compiler.compile(file, {
102
+ sourcePath: path,
103
+ platform: platform,
104
+ comments: false
105
+ })
106
+
107
+ // the file has been successfully compiled
108
+ if (!out.errors?.length) {
109
+ clearCompileError(path);
110
+ console.log(theme.action("compiling: ") + theme.folder(dir.join(f.dir,'/')) + theme.filename(f.base) + " - " + theme.success("compiled"));
111
+ stats.bundled++;
112
+ stats.compiled++;
113
+ contents = out.js;
114
+ await Bun.write(cached, contents);
115
+ }
116
+ // there were errors during compilation
117
+ else {
118
+ const shouldPrint = shouldPrintCompileError(path, out.errors);
119
+ if (shouldPrint) console.log(theme.action("compiling: ") + theme.folder(dir.join(f.dir,'/')) + theme.filename(f.base) + " - " + theme.failure(" fail "));
120
+ stats.failed++;
121
+ if (shouldPrint) {
122
+ stats.reported++;
123
+ for (let i = 0; i < out.errors.length; i++) {
124
+ if(out.errors[i]) printerr(out.errors[i]);
125
+ }
126
+ }
127
+ stats.errors++;
128
+ }
129
+
130
+ // and return the compiled source code as "js"
131
+ return {
132
+ contents,
133
+ loader: "js",
134
+ };
135
+ });
136
+ }
137
+ };
138
+
139
+ plugin(imbaPlugin);
140
+
141
+
142
+ // -------------------------------------------------------------------------------
143
+ // print pretty messages produced by the imba compiler
144
+ // -------------------------------------------------------------------------------
145
+
146
+ // print an error generated by the imba compiler
147
+ export function printerr(err) {
148
+
149
+ // halper function to produce empty strings
150
+ const fill = (len = 0) => {return new Array(len + 1).join(' ')}
151
+
152
+ // gather the needed information from the compiler error
153
+ const snippet = err.toSnippet().split("\n");
154
+ const errs = snippet[2] ? snippet[2].indexOf('^') : -1;
155
+
156
+ // no source context available — print compact fallback
157
+ if (!snippet[1] || errs === -1) {
158
+ console.log('');
159
+ console.log(fill(10) + theme.error(" " + err.message + " "));
160
+ console.log('');
161
+ return;
162
+ }
163
+
164
+ const display = {
165
+ error: " " + err.message + " ",
166
+ outdent: fill(10),
167
+ source: snippet[1] + " ",
168
+ margin: " line " + (err.range.start.line + 1) + " ",
169
+ errs: errs,
170
+ erre: snippet[2].lastIndexOf('^') + 1,
171
+ };
172
+
173
+ // calculate parameters for priniting a message
174
+ const center = display.margin.length + display.errs + Math.floor((display.erre - display.errs) / 2);
175
+ const half = Math.ceil((display.error.length - 1) / 2);
176
+ const start = Math.max(0, center - half);
177
+ const end = start + display.error.length;
178
+ const total = Math.max(display.margin.length + display.source.length, end);
179
+
180
+ // print emtpy line
181
+ console.log('');
182
+
183
+ // print line with the error message
184
+ console.log(
185
+ display.outdent +
186
+ theme.margin(fill(Math.min(start, display.margin.length))) +
187
+ theme.code(fill(Math.max(0, start - display.margin.length))) +
188
+ theme.error(display.error) +
189
+ theme.margin(fill(Math.max(0, display.margin.length - end))) +
190
+ theme.code(fill(Math.min(total - display.margin.length, total - end)))
191
+ );
192
+
193
+ // print line with the source code
194
+ console.log(
195
+ display.outdent +
196
+ theme.margin(display.margin) +
197
+ theme.code(display.source.slice(0,display.errs)) +
198
+ theme.error(display.source.slice(display.errs,display.erre)) +
199
+ theme.code(display.source.slice(display.erre)) +
200
+ theme.code(fill(total - display.source.length - display.margin.length))
201
+ );
202
+
203
+ // print empty line to balance the view
204
+ // later we can put something usefull here
205
+ // for example a link to online docs about the error
206
+ console.log(
207
+ display.outdent +
208
+ theme.margin(fill(display.margin.length)) +
209
+ theme.code(fill(total - display.margin.length))
210
+ );
211
+
212
+ // print emtpy line
213
+ console.log('');
214
+ }
package/serve.js CHANGED
@@ -207,7 +207,7 @@ const hmrClient = `
207
207
  const msg = JSON.parse(e.data);
208
208
  if (msg.type === 'update') _applyUpdate(msg.file, msg.slots);
209
209
  else if (msg.type === 'reload') location.reload();
210
- else if (msg.type === 'error') showError(msg.file, msg.errors);
210
+ else if (msg.type === 'error') showError(msg.file, msg.errors, msg.time);
211
211
  else if (msg.type === 'clear-error') clearError(msg.file);
212
212
  };
213
213
 
@@ -223,8 +223,9 @@ const hmrClient = `
223
223
  return value;
224
224
  }
225
225
 
226
- function showError(file, errors) {
226
+ function showError(file, errors, time) {
227
227
  const displayFile = normalizeFile(file);
228
+ const displayTime = time || new Date().toLocaleTimeString();
228
229
  let overlay = document.getElementById('__bimba_error__');
229
230
  if (!overlay) {
230
231
  overlay = document.createElement('div');
@@ -237,7 +238,7 @@ const hmrClient = `
237
238
  overlay.innerHTML = \`
238
239
  <div style="background:#1a1a1a;border:1px solid #ff4444;border-radius:8px;max-width:860px;width:100%;max-height:90vh;overflow:auto;box-shadow:0 0 40px rgba(255,68,68,.3)">
239
240
  <div style="background:#ff4444;color:#fff;padding:10px 16px;font-size:13px;font-weight:600;display:flex;justify-content:space-between;align-items:center">
240
- <span>Compile error — \${displayFile}</span>
241
+ <span>Compile error — \${displayFile} <span style="opacity:.75;font-weight:400">\${displayTime}</span></span>
241
242
  <span onclick="document.getElementById('__bimba_error__').remove()" style="cursor:pointer;opacity:.7;font-size:16px">✕</span>
242
243
  </div>
243
244
  \${errors.map(err => \`
@@ -609,14 +610,98 @@ export function serve(entrypoint, flags) {
609
610
 
610
611
  // ── File watcher ───────────────────────────────────────────────────────────
611
612
 
613
+ const _activeErrors = new Map()
614
+
612
615
  function broadcast(payload) {
613
616
  const msg = JSON.stringify(payload)
614
617
  for (const socket of sockets) socket.send(msg)
615
618
  }
616
619
 
620
+ function normalizeFile(file) {
621
+ let value = String(file || '')
622
+ if (path.isAbsolute(value)) {
623
+ const rel = path.relative(process.cwd(), value)
624
+ if (!rel.startsWith('..')) value = rel
625
+ }
626
+ value = value.replaceAll('\\', '/')
627
+ while (value.startsWith('./')) value = value.slice(2)
628
+ while (value.startsWith('/')) value = value.slice(1)
629
+ return value
630
+ }
631
+
632
+ function errorMessage(error) {
633
+ return error?.message || String(error)
634
+ }
635
+
636
+ function errorLine(error) {
637
+ return error?.range?.start?.line ?? error?.line
638
+ }
639
+
640
+ function errorSnippet(error) {
641
+ try {
642
+ return error?.toSnippet?.() || error?.snippet || error?.stack || errorMessage(error)
643
+ } catch(_) {
644
+ return error?.snippet || error?.stack || errorMessage(error)
645
+ }
646
+ }
647
+
648
+ function serializeErrors(errors) {
649
+ return errors.map(error => ({
650
+ message: errorMessage(error),
651
+ line: errorLine(error),
652
+ snippet: errorSnippet(error),
653
+ }))
654
+ }
655
+
656
+ function errorSignature(errors) {
657
+ return serializeErrors(errors)
658
+ .map(error => [error.message, error.line || '', error.snippet || ''].join('\n'))
659
+ .join('\n---\n')
660
+ }
661
+
662
+ function showTrackedError(file, item) {
663
+ printStatus(file, 'fail', item.errors)
664
+ broadcast({ type: 'error', file, time: item.time, errors: item.payload })
665
+ }
666
+
667
+ function reportError(file, errors) {
668
+ const key = normalizeFile(file)
669
+ const list = Array.isArray(errors) ? errors : [errors]
670
+ const signature = errorSignature(list)
671
+ const previous = _activeErrors.get(key)
672
+
673
+ const item = {
674
+ signature,
675
+ errors: list,
676
+ payload: serializeErrors(list),
677
+ time: new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
678
+ }
679
+ _activeErrors.set(key, item)
680
+ if (previous?.signature === signature && !_isTTY) {
681
+ broadcast({ type: 'error', file: key, time: item.time, errors: item.payload })
682
+ return
683
+ }
684
+
685
+ showTrackedError(key, item)
686
+ }
687
+
617
688
  function clearError(file) {
618
- clearStatus(file)
619
- broadcast({ type: 'clear-error', file })
689
+ const key = file ? normalizeFile(file) : null
690
+ const wasStatusFile = key && _statusFile === key
691
+ const hadError = key ? _activeErrors.has(key) : _activeErrors.size > 0
692
+
693
+ if (key) _activeErrors.delete(key)
694
+ else _activeErrors.clear()
695
+
696
+ if (key && !hadError && !wasStatusFile) return
697
+
698
+ clearStatus(key)
699
+ broadcast({ type: 'clear-error', file: key })
700
+
701
+ if (wasStatusFile && _activeErrors.size) {
702
+ const [nextFile, nextItem] = Array.from(_activeErrors.entries()).at(-1)
703
+ showTrackedError(nextFile, nextItem)
704
+ }
620
705
  }
621
706
 
622
707
  const _debounce = new Map()
@@ -644,7 +729,7 @@ export function serve(entrypoint, flags) {
644
729
 
645
730
  async function compileChangedFile(filename, version) {
646
731
  const filepath = path.join(srcDir, filename)
647
- const rel = path.join(path.relative('.', srcDir), filename).replaceAll('\\', '/')
732
+ const rel = normalizeFile(path.join(path.relative('.', srcDir), filename))
648
733
 
649
734
  try {
650
735
  if (!existsSync(filepath)) {
@@ -659,12 +744,7 @@ export function serve(entrypoint, flags) {
659
744
  if (!isCurrentChange(filename, version)) return
660
745
 
661
746
  if (out.errors?.length) {
662
- printStatus(rel, 'fail', out.errors)
663
- broadcast({ type: 'error', file: rel, errors: out.errors.map(e => ({
664
- message: e.message,
665
- line: e.range?.start?.line,
666
- snippet: e.toSnippet(),
667
- })) })
747
+ reportError(rel, out.errors)
668
748
  return
669
749
  }
670
750
 
@@ -677,8 +757,7 @@ export function serve(entrypoint, flags) {
677
757
  broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
678
758
  } catch(e) {
679
759
  if (!isCurrentChange(filename, version)) return
680
- printStatus(rel, 'fail', [{ message: e.message }])
681
- broadcast({ type: 'error', file: rel, errors: [{ message: e.message, snippet: e.stack || e.message }] })
760
+ reportError(rel, [{ message: e.message, snippet: e.stack || e.message }])
682
761
  }
683
762
  }
684
763
 
@@ -723,23 +802,17 @@ export function serve(entrypoint, flags) {
723
802
  // Imba files: compile on demand and serve as JS
724
803
  if (pathname.endsWith('.imba')) {
725
804
  const filepath = '.' + pathname
805
+ const file = normalizeFile(pathname)
726
806
  try {
727
807
  const out = await compileFile(filepath)
728
808
  if (out.errors?.length) {
729
- const file = pathname.replace(/^\//, '')
730
- printStatus(file, 'fail', out.errors)
731
- broadcast({ type: 'error', file, errors: out.errors.map(e => ({
732
- message: e.message,
733
- line: e.range?.start?.line,
734
- snippet: e.toSnippet(),
735
- })) })
809
+ reportError(file, out.errors)
736
810
  return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
737
811
  }
812
+ clearError(file)
738
813
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
739
814
  } catch(e) {
740
- const file = pathname.replace(/^\//, '')
741
- printStatus(file, 'fail', [{ message: e.message }])
742
- broadcast({ type: 'error', file, errors: [{ message: e.message, snippet: e.stack || e.message }] })
815
+ reportError(file, [{ message: e.message, snippet: e.stack || e.message }])
743
816
  return new Response(e.message, { status: 500 })
744
817
  }
745
818
  }
@@ -779,7 +852,12 @@ export function serve(entrypoint, flags) {
779
852
  const resolved = resolveFileCandidate('.' + pathname)
780
853
  if (resolved?.endsWith('.imba')) {
781
854
  const out = await compileFile(resolved)
782
- if (out.errors?.length) return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
855
+ const file = normalizeFile(resolved)
856
+ if (out.errors?.length) {
857
+ reportError(file, out.errors)
858
+ return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
859
+ }
860
+ clearError(file)
783
861
  return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
784
862
  }
785
863
 
@@ -805,7 +883,13 @@ export function serve(entrypoint, flags) {
805
883
  const imbaPath = '.' + pathname + '.imba'
806
884
  if (existsSync(imbaPath)) {
807
885
  const out = await compileFile(imbaPath)
808
- if (!out.errors?.length) return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
886
+ const file = normalizeFile(imbaPath)
887
+ if (out.errors?.length) {
888
+ reportError(file, out.errors)
889
+ return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
890
+ }
891
+ clearError(file)
892
+ return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
809
893
  }
810
894
  for (const ext of ['.js', '.mjs']) {
811
895
  const withExt = '.' + pathname + ext