bimba-cli 0.7.18 → 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.
- package/index.js +22 -4
- package/package.json +1 -1
- package/plugin.js +214 -162
- package/serve.js +123 -28
package/index.js
CHANGED
|
@@ -90,6 +90,8 @@ if(flags.help) {
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
let bundling = false;
|
|
93
|
+
let rebuildQueued = false;
|
|
94
|
+
let watchTimer = null;
|
|
93
95
|
|
|
94
96
|
// typecheck mode
|
|
95
97
|
if (flags.typecheck || flags.tscheck) {
|
|
@@ -131,7 +133,13 @@ else {
|
|
|
131
133
|
|
|
132
134
|
function watch(callback) {
|
|
133
135
|
if (flags.watch) {
|
|
134
|
-
const watcher = fs.watch(path.dirname(entrypoint), {recursive: true},
|
|
136
|
+
const watcher = fs.watch(path.dirname(entrypoint), {recursive: true}, () => {
|
|
137
|
+
if (watchTimer) clearTimeout(watchTimer);
|
|
138
|
+
watchTimer = setTimeout(() => {
|
|
139
|
+
watchTimer = null;
|
|
140
|
+
callback();
|
|
141
|
+
}, 150);
|
|
142
|
+
});
|
|
135
143
|
|
|
136
144
|
process.on("SIGINT", () => {
|
|
137
145
|
if(flags.clearcache) rmSync(cache, { recursive: true, force: true });
|
|
@@ -146,7 +154,10 @@ function watch(callback) {
|
|
|
146
154
|
|
|
147
155
|
|
|
148
156
|
async function bundle() {
|
|
149
|
-
if (bundling)
|
|
157
|
+
if (bundling) {
|
|
158
|
+
rebuildQueued = true;
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
150
161
|
bundling = true;
|
|
151
162
|
|
|
152
163
|
if (!fs.existsSync(entrypoint)) {
|
|
@@ -157,6 +168,7 @@ async function bundle() {
|
|
|
157
168
|
stats.failed = 0
|
|
158
169
|
stats.compiled = 0
|
|
159
170
|
stats.errors = 0
|
|
171
|
+
stats.reported = 0
|
|
160
172
|
stats.bundled = 0
|
|
161
173
|
|
|
162
174
|
const start = Date.now();
|
|
@@ -197,8 +209,10 @@ async function bundle() {
|
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
211
|
|
|
200
|
-
if(stats.failed)
|
|
201
|
-
|
|
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
|
+
}
|
|
202
216
|
else
|
|
203
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)}`));
|
|
204
218
|
|
|
@@ -220,5 +234,9 @@ async function bundle() {
|
|
|
220
234
|
}
|
|
221
235
|
finally {
|
|
222
236
|
bundling = false;
|
|
237
|
+
if (rebuildQueued) {
|
|
238
|
+
rebuildQueued = false;
|
|
239
|
+
queueMicrotask(bundle);
|
|
240
|
+
}
|
|
223
241
|
};
|
|
224
242
|
}
|
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), 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
|
|
|
@@ -216,7 +216,16 @@ const hmrClient = `
|
|
|
216
216
|
|
|
217
217
|
// ── Error overlay ──────────────────────────────────────────────────────────
|
|
218
218
|
|
|
219
|
-
function
|
|
219
|
+
function normalizeFile(file) {
|
|
220
|
+
let value = String(file || '').split(String.fromCharCode(92)).join('/');
|
|
221
|
+
while (value.startsWith('./')) value = value.slice(2);
|
|
222
|
+
while (value.startsWith('/')) value = value.slice(1);
|
|
223
|
+
return value;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function showError(file, errors, time) {
|
|
227
|
+
const displayFile = normalizeFile(file);
|
|
228
|
+
const displayTime = time || new Date().toLocaleTimeString();
|
|
220
229
|
let overlay = document.getElementById('__bimba_error__');
|
|
221
230
|
if (!overlay) {
|
|
222
231
|
overlay = document.createElement('div');
|
|
@@ -225,11 +234,11 @@ const hmrClient = `
|
|
|
225
234
|
overlay.addEventListener('click', e => { if (e.target === overlay) overlay.remove(); });
|
|
226
235
|
document.body.appendChild(overlay);
|
|
227
236
|
}
|
|
228
|
-
overlay.dataset.file =
|
|
237
|
+
overlay.dataset.file = displayFile;
|
|
229
238
|
overlay.innerHTML = \`
|
|
230
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)">
|
|
231
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">
|
|
232
|
-
<span>Compile error — \${
|
|
241
|
+
<span>Compile error — \${displayFile} <span style="opacity:.75;font-weight:400">\${displayTime}</span></span>
|
|
233
242
|
<span onclick="document.getElementById('__bimba_error__').remove()" style="cursor:pointer;opacity:.7;font-size:16px">✕</span>
|
|
234
243
|
</div>
|
|
235
244
|
\${errors.map(err => \`
|
|
@@ -244,7 +253,10 @@ const hmrClient = `
|
|
|
244
253
|
|
|
245
254
|
function clearError(file) {
|
|
246
255
|
const overlay = document.getElementById('__bimba_error__');
|
|
247
|
-
if (
|
|
256
|
+
if (!overlay) return;
|
|
257
|
+
|
|
258
|
+
const activeFile = overlay.dataset.file;
|
|
259
|
+
if (!file || !activeFile || activeFile === normalizeFile(file)) overlay.remove();
|
|
248
260
|
}
|
|
249
261
|
|
|
250
262
|
connect();
|
|
@@ -598,14 +610,98 @@ export function serve(entrypoint, flags) {
|
|
|
598
610
|
|
|
599
611
|
// ── File watcher ───────────────────────────────────────────────────────────
|
|
600
612
|
|
|
613
|
+
const _activeErrors = new Map()
|
|
614
|
+
|
|
601
615
|
function broadcast(payload) {
|
|
602
616
|
const msg = JSON.stringify(payload)
|
|
603
617
|
for (const socket of sockets) socket.send(msg)
|
|
604
618
|
}
|
|
605
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
|
+
|
|
606
688
|
function clearError(file) {
|
|
607
|
-
|
|
608
|
-
|
|
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
|
+
}
|
|
609
705
|
}
|
|
610
706
|
|
|
611
707
|
const _debounce = new Map()
|
|
@@ -633,7 +729,7 @@ export function serve(entrypoint, flags) {
|
|
|
633
729
|
|
|
634
730
|
async function compileChangedFile(filename, version) {
|
|
635
731
|
const filepath = path.join(srcDir, filename)
|
|
636
|
-
const rel = path.join(path.relative('.', srcDir), filename)
|
|
732
|
+
const rel = normalizeFile(path.join(path.relative('.', srcDir), filename))
|
|
637
733
|
|
|
638
734
|
try {
|
|
639
735
|
if (!existsSync(filepath)) {
|
|
@@ -648,12 +744,7 @@ export function serve(entrypoint, flags) {
|
|
|
648
744
|
if (!isCurrentChange(filename, version)) return
|
|
649
745
|
|
|
650
746
|
if (out.errors?.length) {
|
|
651
|
-
|
|
652
|
-
broadcast({ type: 'error', file: rel, errors: out.errors.map(e => ({
|
|
653
|
-
message: e.message,
|
|
654
|
-
line: e.range?.start?.line,
|
|
655
|
-
snippet: e.toSnippet(),
|
|
656
|
-
})) })
|
|
747
|
+
reportError(rel, out.errors)
|
|
657
748
|
return
|
|
658
749
|
}
|
|
659
750
|
|
|
@@ -666,8 +757,7 @@ export function serve(entrypoint, flags) {
|
|
|
666
757
|
broadcast({ type: 'update', file: rel, slots: out.slots || 'shifted' })
|
|
667
758
|
} catch(e) {
|
|
668
759
|
if (!isCurrentChange(filename, version)) return
|
|
669
|
-
|
|
670
|
-
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 }])
|
|
671
761
|
}
|
|
672
762
|
}
|
|
673
763
|
|
|
@@ -712,23 +802,17 @@ export function serve(entrypoint, flags) {
|
|
|
712
802
|
// Imba files: compile on demand and serve as JS
|
|
713
803
|
if (pathname.endsWith('.imba')) {
|
|
714
804
|
const filepath = '.' + pathname
|
|
805
|
+
const file = normalizeFile(pathname)
|
|
715
806
|
try {
|
|
716
807
|
const out = await compileFile(filepath)
|
|
717
808
|
if (out.errors?.length) {
|
|
718
|
-
|
|
719
|
-
printStatus(file, 'fail', out.errors)
|
|
720
|
-
broadcast({ type: 'error', file, errors: out.errors.map(e => ({
|
|
721
|
-
message: e.message,
|
|
722
|
-
line: e.range?.start?.line,
|
|
723
|
-
snippet: e.toSnippet(),
|
|
724
|
-
})) })
|
|
809
|
+
reportError(file, out.errors)
|
|
725
810
|
return new Response(out.errors.map(e => e.message).join('\n'), { status: 500 })
|
|
726
811
|
}
|
|
812
|
+
clearError(file)
|
|
727
813
|
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
728
814
|
} catch(e) {
|
|
729
|
-
|
|
730
|
-
printStatus(file, 'fail', [{ message: e.message }])
|
|
731
|
-
broadcast({ type: 'error', file, errors: [{ message: e.message, snippet: e.stack || e.message }] })
|
|
815
|
+
reportError(file, [{ message: e.message, snippet: e.stack || e.message }])
|
|
732
816
|
return new Response(e.message, { status: 500 })
|
|
733
817
|
}
|
|
734
818
|
}
|
|
@@ -768,7 +852,12 @@ export function serve(entrypoint, flags) {
|
|
|
768
852
|
const resolved = resolveFileCandidate('.' + pathname)
|
|
769
853
|
if (resolved?.endsWith('.imba')) {
|
|
770
854
|
const out = await compileFile(resolved)
|
|
771
|
-
|
|
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)
|
|
772
861
|
return new Response(out.js, { headers: { 'Content-Type': 'application/javascript' } })
|
|
773
862
|
}
|
|
774
863
|
|
|
@@ -794,7 +883,13 @@ export function serve(entrypoint, flags) {
|
|
|
794
883
|
const imbaPath = '.' + pathname + '.imba'
|
|
795
884
|
if (existsSync(imbaPath)) {
|
|
796
885
|
const out = await compileFile(imbaPath)
|
|
797
|
-
|
|
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' } })
|
|
798
893
|
}
|
|
799
894
|
for (const ext of ['.js', '.mjs']) {
|
|
800
895
|
const withExt = '.' + pathname + ext
|