bimba-cli 0.7.19 → 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.
- package/index.js +5 -2
- package/package.json +1 -1
- package/plugin.js +214 -162
- package/serve.js +277 -56
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
|
-
|
|
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
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
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)].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 => \`
|
|
@@ -353,16 +354,39 @@ function rewriteBareImports(js) {
|
|
|
353
354
|
return js
|
|
354
355
|
}
|
|
355
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
|
+
|
|
356
366
|
async function compileFile(filepath) {
|
|
357
367
|
const abs = path.resolve(filepath)
|
|
368
|
+
if (!existsSync(abs)) return missingCompileResult(abs)
|
|
369
|
+
|
|
358
370
|
const file = Bun.file(filepath)
|
|
359
|
-
|
|
371
|
+
let stat
|
|
372
|
+
try {
|
|
373
|
+
stat = await file.stat()
|
|
374
|
+
} catch (error) {
|
|
375
|
+
if (isMissingFileError(error)) return missingCompileResult(abs)
|
|
376
|
+
throw error
|
|
377
|
+
}
|
|
360
378
|
const mtime = stat.mtime.getTime()
|
|
361
379
|
|
|
362
380
|
const cached = _compileCache.get(abs)
|
|
363
381
|
if (cached && cached.mtime === mtime) return _normalizeResult(cached.result, { changeType: 'cached' })
|
|
364
382
|
|
|
365
|
-
|
|
383
|
+
let code
|
|
384
|
+
try {
|
|
385
|
+
code = await file.text()
|
|
386
|
+
} catch (error) {
|
|
387
|
+
if (isMissingFileError(error)) return missingCompileResult(abs)
|
|
388
|
+
throw error
|
|
389
|
+
}
|
|
366
390
|
const result = compiler.compile(code, {
|
|
367
391
|
sourcePath: filepath,
|
|
368
392
|
platform: 'browser',
|
|
@@ -609,14 +633,108 @@ export function serve(entrypoint, flags) {
|
|
|
609
633
|
|
|
610
634
|
// ── File watcher ───────────────────────────────────────────────────────────
|
|
611
635
|
|
|
636
|
+
const _activeErrors = new Map()
|
|
637
|
+
|
|
612
638
|
function broadcast(payload) {
|
|
613
639
|
const msg = JSON.stringify(payload)
|
|
614
640
|
for (const socket of sockets) socket.send(msg)
|
|
615
641
|
}
|
|
616
642
|
|
|
643
|
+
function normalizeFile(file) {
|
|
644
|
+
let value = String(file || '')
|
|
645
|
+
if (path.isAbsolute(value)) {
|
|
646
|
+
const rel = path.relative(process.cwd(), value)
|
|
647
|
+
if (!rel.startsWith('..')) value = rel
|
|
648
|
+
}
|
|
649
|
+
value = value.replaceAll('\\', '/')
|
|
650
|
+
while (value.startsWith('./')) value = value.slice(2)
|
|
651
|
+
while (value.startsWith('/')) value = value.slice(1)
|
|
652
|
+
return value
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function errorMessage(error) {
|
|
656
|
+
return error?.message || String(error)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function errorLine(error) {
|
|
660
|
+
return error?.range?.start?.line ?? error?.line
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
function errorSnippet(error) {
|
|
664
|
+
try {
|
|
665
|
+
return error?.toSnippet?.() || error?.snippet || error?.stack || errorMessage(error)
|
|
666
|
+
} catch(_) {
|
|
667
|
+
return error?.snippet || error?.stack || errorMessage(error)
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function serializeErrors(errors) {
|
|
672
|
+
return errors.map(error => ({
|
|
673
|
+
message: errorMessage(error),
|
|
674
|
+
line: errorLine(error),
|
|
675
|
+
snippet: errorSnippet(error),
|
|
676
|
+
}))
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
function errorSignature(errors) {
|
|
680
|
+
return serializeErrors(errors)
|
|
681
|
+
.map(error => [error.message, error.line || ''].join('\n'))
|
|
682
|
+
.join('\n---\n')
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
function showTrackedError(file, item) {
|
|
686
|
+
printStatus(file, 'fail', item.errors)
|
|
687
|
+
broadcast({ type: 'error', file, time: item.time, errors: item.payload })
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
function reportError(file, errors) {
|
|
691
|
+
const key = normalizeFile(file)
|
|
692
|
+
const list = Array.isArray(errors) ? errors : [errors]
|
|
693
|
+
const signature = errorSignature(list)
|
|
694
|
+
const previous = _activeErrors.get(key)
|
|
695
|
+
|
|
696
|
+
const item = {
|
|
697
|
+
signature,
|
|
698
|
+
errors: list,
|
|
699
|
+
payload: serializeErrors(list),
|
|
700
|
+
time: new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit', second: '2-digit' }),
|
|
701
|
+
}
|
|
702
|
+
_activeErrors.set(key, item)
|
|
703
|
+
if (previous?.signature === signature && !_isTTY) {
|
|
704
|
+
broadcast({ type: 'error', file: key, time: item.time, errors: item.payload })
|
|
705
|
+
return
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
showTrackedError(key, item)
|
|
709
|
+
}
|
|
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
|
+
|
|
617
721
|
function clearError(file) {
|
|
618
|
-
|
|
619
|
-
|
|
722
|
+
const key = file ? normalizeFile(file) : null
|
|
723
|
+
const wasStatusFile = key && _statusFile === key
|
|
724
|
+
const hadError = key ? _activeErrors.has(key) : _activeErrors.size > 0
|
|
725
|
+
|
|
726
|
+
if (key) _activeErrors.delete(key)
|
|
727
|
+
else _activeErrors.clear()
|
|
728
|
+
|
|
729
|
+
if (key && !hadError && !wasStatusFile) return
|
|
730
|
+
|
|
731
|
+
clearStatus(key)
|
|
732
|
+
broadcast({ type: 'clear-error', file: key })
|
|
733
|
+
|
|
734
|
+
if (wasStatusFile && _activeErrors.size) {
|
|
735
|
+
const [nextFile, nextItem] = Array.from(_activeErrors.entries()).at(-1)
|
|
736
|
+
showTrackedError(nextFile, nextItem)
|
|
737
|
+
}
|
|
620
738
|
}
|
|
621
739
|
|
|
622
740
|
const _debounce = new Map()
|
|
@@ -644,7 +762,7 @@ export function serve(entrypoint, flags) {
|
|
|
644
762
|
|
|
645
763
|
async function compileChangedFile(filename, version) {
|
|
646
764
|
const filepath = path.join(srcDir, filename)
|
|
647
|
-
const rel = path.join(path.relative('.', srcDir), filename)
|
|
765
|
+
const rel = normalizeFile(path.join(path.relative('.', srcDir), filename))
|
|
648
766
|
|
|
649
767
|
try {
|
|
650
768
|
if (!existsSync(filepath)) {
|
|
@@ -657,14 +775,13 @@ export function serve(entrypoint, flags) {
|
|
|
657
775
|
const out = await compileFile(filepath)
|
|
658
776
|
|
|
659
777
|
if (!isCurrentChange(filename, version)) return
|
|
778
|
+
if (out.missing) {
|
|
779
|
+
clearError(rel)
|
|
780
|
+
return
|
|
781
|
+
}
|
|
660
782
|
|
|
661
783
|
if (out.errors?.length) {
|
|
662
|
-
|
|
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
|
-
})) })
|
|
784
|
+
reportError(rel, out.errors)
|
|
668
785
|
return
|
|
669
786
|
}
|
|
670
787
|
|
|
@@ -677,8 +794,12 @@ export function serve(entrypoint, flags) {
|
|
|
677
794
|
broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
|
|
678
795
|
} catch(e) {
|
|
679
796
|
if (!isCurrentChange(filename, version)) return
|
|
680
|
-
|
|
681
|
-
|
|
797
|
+
if (isMissingFileError(e)) {
|
|
798
|
+
dropFileState(filepath)
|
|
799
|
+
clearError(rel)
|
|
800
|
+
return
|
|
801
|
+
}
|
|
802
|
+
reportError(rel, [{ message: e.message, snippet: e.stack || e.message }])
|
|
682
803
|
}
|
|
683
804
|
}
|
|
684
805
|
|
|
@@ -695,6 +816,7 @@ export function serve(entrypoint, flags) {
|
|
|
695
816
|
fetch: async (req, server) => {
|
|
696
817
|
const url = new URL(req.url)
|
|
697
818
|
const pathname = url.pathname
|
|
819
|
+
try {
|
|
698
820
|
|
|
699
821
|
// WebSocket upgrade for HMR
|
|
700
822
|
if (pathname === '/__hmr__') {
|
|
@@ -703,44 +825,57 @@ export function serve(entrypoint, flags) {
|
|
|
703
825
|
|
|
704
826
|
if (pathname.startsWith('/__bimba_vendor__/')) {
|
|
705
827
|
const specifier = vendorSpecifierFromPath(pathname)
|
|
828
|
+
const file = 'vendor:' + (specifier || pathname)
|
|
706
829
|
const bundled = specifier ? await bundleVendor(specifier) : null
|
|
707
830
|
if (bundled?.code) {
|
|
831
|
+
clearError(file)
|
|
708
832
|
return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
|
|
709
833
|
}
|
|
710
834
|
|
|
711
|
-
return
|
|
835
|
+
return errorResponse(file, bundled?.errors || [`Could not bundle vendor module: ${specifier}`])
|
|
712
836
|
}
|
|
713
837
|
|
|
714
838
|
// HTML: index or any .html file
|
|
715
839
|
if (pathname === '/' || pathname.endsWith('.html')) {
|
|
716
840
|
const htmlFile = pathname === '/' ? htmlPath : '.' + pathname
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
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
|
+
}
|
|
721
855
|
}
|
|
722
856
|
|
|
723
857
|
// Imba files: compile on demand and serve as JS
|
|
724
858
|
if (pathname.endsWith('.imba')) {
|
|
725
859
|
const filepath = '.' + pathname
|
|
860
|
+
const file = normalizeFile(pathname)
|
|
726
861
|
try {
|
|
727
862
|
const out = await compileFile(filepath)
|
|
863
|
+
if (out.missing) {
|
|
864
|
+
clearError(file)
|
|
865
|
+
return new Response('Not Found', { status: 404 })
|
|
866
|
+
}
|
|
728
867
|
if (out.errors?.length) {
|
|
729
|
-
|
|
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
|
-
})) })
|
|
736
|
-
return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
868
|
+
return errorResponse(file, out.errors)
|
|
737
869
|
}
|
|
870
|
+
clearError(file)
|
|
738
871
|
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
739
872
|
} catch(e) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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 }])
|
|
744
879
|
}
|
|
745
880
|
}
|
|
746
881
|
|
|
@@ -750,26 +885,50 @@ export function serve(entrypoint, flags) {
|
|
|
750
885
|
if (pathname.endsWith('.css')) {
|
|
751
886
|
const cssPath = resolveFileCandidate(path.join(htmlDir, pathname)) || resolveFileCandidate('.' + pathname)
|
|
752
887
|
const cssFile = cssPath ? Bun.file(cssPath) : null
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
}
|
|
757
895
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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])
|
|
767
913
|
}
|
|
768
914
|
}
|
|
769
915
|
|
|
770
916
|
if (!pathname.startsWith('/node_modules/') && (pathname.endsWith('.js') || pathname.endsWith('.mjs'))) {
|
|
771
917
|
const jsFile = resolveFileCandidate(path.join(htmlDir, pathname)) || resolveFileCandidate('.' + pathname)
|
|
772
|
-
if (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
|
+
}
|
|
773
932
|
}
|
|
774
933
|
|
|
775
934
|
// Direct node_modules URLs (from user import maps or explicit imports)
|
|
@@ -779,24 +938,46 @@ export function serve(entrypoint, flags) {
|
|
|
779
938
|
const resolved = resolveFileCandidate('.' + pathname)
|
|
780
939
|
if (resolved?.endsWith('.imba')) {
|
|
781
940
|
const out = await compileFile(resolved)
|
|
782
|
-
|
|
941
|
+
const file = normalizeFile(resolved)
|
|
942
|
+
if (out.missing) {
|
|
943
|
+
clearError(file)
|
|
944
|
+
return new Response('Not Found', { status: 404 })
|
|
945
|
+
}
|
|
946
|
+
if (out.errors?.length) {
|
|
947
|
+
return errorResponse(file, out.errors)
|
|
948
|
+
}
|
|
949
|
+
clearError(file)
|
|
783
950
|
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
784
951
|
}
|
|
785
952
|
|
|
786
953
|
if (resolved) {
|
|
954
|
+
const file = 'vendor:' + normalizeFile(pathname)
|
|
787
955
|
const bundled = await bundleVendor(path.resolve(resolved))
|
|
788
956
|
if (bundled?.code) {
|
|
957
|
+
clearError(file)
|
|
789
958
|
return new Response(bundled.code, { headers: { 'Content-Type': 'application/javascript' } })
|
|
790
959
|
}
|
|
791
|
-
return
|
|
960
|
+
return errorResponse(file, bundled?.errors || [`Could not bundle ${pathname}`])
|
|
792
961
|
}
|
|
793
962
|
}
|
|
794
963
|
|
|
795
964
|
// Static files: check htmlDir first (for assets relative to HTML), then root
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
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
|
+
}
|
|
800
981
|
|
|
801
982
|
// Try extensions for extensionless paths (e.g. node_modules imports)
|
|
802
983
|
const lastSegment = pathname.split('/').pop()
|
|
@@ -805,23 +986,63 @@ export function serve(entrypoint, flags) {
|
|
|
805
986
|
const imbaPath = '.' + pathname + '.imba'
|
|
806
987
|
if (existsSync(imbaPath)) {
|
|
807
988
|
const out = await compileFile(imbaPath)
|
|
808
|
-
|
|
989
|
+
const file = normalizeFile(imbaPath)
|
|
990
|
+
if (out.missing) {
|
|
991
|
+
clearError(file)
|
|
992
|
+
return new Response('Not Found', { status: 404 })
|
|
993
|
+
}
|
|
994
|
+
if (out.errors?.length) {
|
|
995
|
+
return errorResponse(file, out.errors)
|
|
996
|
+
}
|
|
997
|
+
clearError(file)
|
|
998
|
+
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
809
999
|
}
|
|
810
1000
|
for (const ext of ['.js', '.mjs']) {
|
|
811
1001
|
const withExt = '.' + pathname + ext
|
|
812
|
-
if (existsSync(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
|
+
}
|
|
813
1016
|
}
|
|
814
1017
|
}
|
|
815
1018
|
|
|
816
1019
|
// SPA fallback for extension-less paths
|
|
817
|
-
|
|
1020
|
+
if (!lastSegment.includes('.')) {
|
|
1021
|
+
const file = 'html:' + normalizeFile(htmlPath)
|
|
1022
|
+
try {
|
|
818
1023
|
let html = await Bun.file(htmlPath).text()
|
|
1024
|
+
clearError(file)
|
|
819
1025
|
return new Response(transformHtml(html, entrypoint), {
|
|
820
1026
|
headers: { 'Content-Type': 'text/html' },
|
|
821
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])
|
|
822
1034
|
}
|
|
1035
|
+
}
|
|
823
1036
|
|
|
824
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
|
+
}
|
|
825
1046
|
},
|
|
826
1047
|
|
|
827
1048
|
websocket: {
|