bimba-cli 0.7.13 → 0.7.14
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/README.md +19 -0
- package/index.js +17 -1
- package/package.json +1 -1
- package/typecheck.js +248 -0
package/README.md
CHANGED
|
@@ -90,6 +90,21 @@ With watch:
|
|
|
90
90
|
bunx bimba src/index.imba --outdir public/js --watch --clearcache
|
|
91
91
|
```
|
|
92
92
|
|
|
93
|
+
### TypeScript diagnostics for Imba files
|
|
94
|
+
|
|
95
|
+
To check TypeScript diagnostics reported by the Imba language-service plugin:
|
|
96
|
+
```bash
|
|
97
|
+
bunx bimba --typecheck
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
By default this scans `src/` when it exists, otherwise the project root. You can also pass a specific file or folder:
|
|
101
|
+
```bash
|
|
102
|
+
bunx bimba src/index.imba --typecheck
|
|
103
|
+
bunx bimba src --typecheck
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
This mode requires `typescript` in the project and `typescript-imba-plugin` either in `node_modules` or in an installed Imba editor extension.
|
|
107
|
+
|
|
93
108
|
---
|
|
94
109
|
|
|
95
110
|
### All CLI flags
|
|
@@ -106,6 +121,10 @@ bunx bimba src/index.imba --outdir public/js --watch --clearcache
|
|
|
106
121
|
|
|
107
122
|
`--target <browser|node>` — platform flag passed to the Imba compiler (default: `browser`). The `node` value does not work under Bun.
|
|
108
123
|
|
|
124
|
+
`--typecheck` — check TypeScript diagnostics in `.imba` files using `tsserver` and `typescript-imba-plugin`.
|
|
125
|
+
|
|
126
|
+
`--tscheck` — alias for `--typecheck`.
|
|
127
|
+
|
|
109
128
|
`--serve` — start dev server with HMR instead of bundling.
|
|
110
129
|
|
|
111
130
|
`--port <number>` — port for the dev server (default: `5200`). Used with `--serve`.
|
package/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import fs from 'fs'
|
|
|
7
7
|
import path from 'path';
|
|
8
8
|
import { rmSync } from "node:fs";
|
|
9
9
|
import { serve } from './serve.js';
|
|
10
|
+
import { checkImbaTypes } from './typecheck.js';
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
let flags = {}
|
|
@@ -28,6 +29,8 @@ try {
|
|
|
28
29
|
serve: { type: 'boolean' },
|
|
29
30
|
port: { type: 'string' },
|
|
30
31
|
html: { type: 'string' },
|
|
32
|
+
typecheck: { type: 'boolean' },
|
|
33
|
+
tscheck: { type: 'boolean' },
|
|
31
34
|
},
|
|
32
35
|
allowNegative: true,
|
|
33
36
|
strict: true,
|
|
@@ -74,6 +77,8 @@ if(flags.help) {
|
|
|
74
77
|
console.log(" "+theme.flags('--external <package>')+" Exclude package from bundle (repeatable, e.g. --external ws --external node-pty)");
|
|
75
78
|
console.log(" "+theme.flags('--watch')+" Watch for changes in the entrypoint folder");
|
|
76
79
|
console.log(" "+theme.flags('--clearcache')+" Clear cache on exit, works only when in watch mode");
|
|
80
|
+
console.log(" "+theme.flags('--typecheck')+" Check TypeScript diagnostics in .imba files");
|
|
81
|
+
console.log(" "+theme.flags('--tscheck')+" Alias for --typecheck");
|
|
77
82
|
console.log("");
|
|
78
83
|
console.log("Dev server (HMR):");
|
|
79
84
|
console.log(" "+theme.flags('--serve')+" Start dev server with Hot Module Replacement");
|
|
@@ -86,8 +91,19 @@ if(flags.help) {
|
|
|
86
91
|
|
|
87
92
|
let bundling = false;
|
|
88
93
|
|
|
94
|
+
// typecheck mode
|
|
95
|
+
if (flags.typecheck || flags.tscheck) {
|
|
96
|
+
try {
|
|
97
|
+
const success = await checkImbaTypes(entrypoint);
|
|
98
|
+
process.exit(success ? 0 : 1);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
console.log(theme.failure(' Failure ') + ` ${error.message}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
89
105
|
// serve mode
|
|
90
|
-
if (flags.serve) {
|
|
106
|
+
else if (flags.serve) {
|
|
91
107
|
if (!entrypoint) {
|
|
92
108
|
console.log("");
|
|
93
109
|
console.log("You should provide entrypoint: "+theme.flags('bimba file.imba --serve'));
|
package/package.json
CHANGED
package/typecheck.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import { theme } from './utils.js';
|
|
7
|
+
|
|
8
|
+
const require = createRequire(import.meta.url);
|
|
9
|
+
|
|
10
|
+
const SKIP_DIRS = new Set([
|
|
11
|
+
'.bimba',
|
|
12
|
+
'.cache',
|
|
13
|
+
'.git',
|
|
14
|
+
'build',
|
|
15
|
+
'dist',
|
|
16
|
+
'node_modules',
|
|
17
|
+
'public',
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
function canResolve(request, from) {
|
|
21
|
+
try {
|
|
22
|
+
return require.resolve(request, { paths: [from] });
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function findTypeScript(cwd) {
|
|
30
|
+
const tsserver = canResolve('typescript/lib/tsserver.js', cwd);
|
|
31
|
+
if (tsserver) return tsserver;
|
|
32
|
+
|
|
33
|
+
throw new Error('Could not find TypeScript. Install it in this project: bun add -d typescript');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function findPluginProbe(cwd) {
|
|
37
|
+
const localProbe = path.join(cwd, 'node_modules');
|
|
38
|
+
if (canResolve('typescript-imba-plugin', localProbe)) return localProbe;
|
|
39
|
+
|
|
40
|
+
const extensionRoots = [
|
|
41
|
+
path.join(os.homedir(), '.vscode', 'extensions'),
|
|
42
|
+
path.join(os.homedir(), '.cursor', 'extensions'),
|
|
43
|
+
path.join(os.homedir(), '.windsurf', 'extensions'),
|
|
44
|
+
path.join(os.homedir(), '.kiro', 'extensions'),
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
for (const root of extensionRoots) {
|
|
48
|
+
if (!fs.existsSync(root)) continue;
|
|
49
|
+
|
|
50
|
+
for (const entry of fs.readdirSync(root)) {
|
|
51
|
+
const probe = path.join(root, entry, 'node_modules');
|
|
52
|
+
if (canResolve('typescript-imba-plugin', probe)) return probe;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
throw new Error('Could not find typescript-imba-plugin. Install the Imba VSCode extension or add the plugin to node_modules.');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function getScanRoot(entrypoint, cwd) {
|
|
60
|
+
if (!entrypoint) {
|
|
61
|
+
const src = path.join(cwd, 'src');
|
|
62
|
+
return fs.existsSync(src) ? src : cwd;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const resolved = path.resolve(cwd, entrypoint);
|
|
66
|
+
if (!fs.existsSync(resolved)) {
|
|
67
|
+
throw new Error(`The specified typecheck path does not exist: ${entrypoint}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stat = fs.statSync(resolved);
|
|
71
|
+
return stat.isDirectory() ? resolved : path.dirname(resolved);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function collectImbaFiles(root) {
|
|
75
|
+
const files = [];
|
|
76
|
+
|
|
77
|
+
function walk(dir) {
|
|
78
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
79
|
+
if (entry.isDirectory()) {
|
|
80
|
+
if (!SKIP_DIRS.has(entry.name)) walk(path.join(dir, entry.name));
|
|
81
|
+
}
|
|
82
|
+
else if (entry.isFile() && entry.name.endsWith('.imba')) {
|
|
83
|
+
files.push(path.join(dir, entry.name));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
walk(root);
|
|
89
|
+
files.sort();
|
|
90
|
+
return files;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function parseMessages(buffer, onMessage) {
|
|
94
|
+
while (true) {
|
|
95
|
+
const text = buffer.toString('utf8');
|
|
96
|
+
const headerEnd = text.indexOf('\r\n\r\n');
|
|
97
|
+
if (headerEnd < 0) return buffer;
|
|
98
|
+
|
|
99
|
+
const match = /Content-Length: (\d+)/i.exec(text.slice(0, headerEnd));
|
|
100
|
+
if (!match) return buffer;
|
|
101
|
+
|
|
102
|
+
const length = Number(match[1]);
|
|
103
|
+
const bodyStart = Buffer.byteLength(text.slice(0, headerEnd + 4));
|
|
104
|
+
if (buffer.length < bodyStart + length) return buffer;
|
|
105
|
+
|
|
106
|
+
const body = buffer.slice(bodyStart, bodyStart + length).toString('utf8');
|
|
107
|
+
buffer = buffer.slice(bodyStart + length);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
onMessage(JSON.parse(body));
|
|
111
|
+
}
|
|
112
|
+
catch {}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function flattenMessage(text) {
|
|
117
|
+
if (typeof text == 'string') return text;
|
|
118
|
+
if (!text) return '';
|
|
119
|
+
if (text.messageText) {
|
|
120
|
+
const next = Array.isArray(text.next) ? text.next.map(flattenMessage) : [];
|
|
121
|
+
return [flattenMessage(text.messageText), ...next].filter(Boolean).join(' ');
|
|
122
|
+
}
|
|
123
|
+
return String(text);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function uniqueDiagnostics(diagnostics) {
|
|
127
|
+
const unique = Array.from(new Map(diagnostics.map(item => [item.key, item])).values());
|
|
128
|
+
unique.sort((a, b) => {
|
|
129
|
+
return a.file.localeCompare(b.file)
|
|
130
|
+
|| (a.start?.line || 0) - (b.start?.line || 0)
|
|
131
|
+
|| (a.start?.offset || 0) - (b.start?.offset || 0)
|
|
132
|
+
|| String(a.code).localeCompare(String(b.code));
|
|
133
|
+
});
|
|
134
|
+
return unique;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function printDiagnostics(cwd, diagnostics) {
|
|
138
|
+
for (const item of diagnostics) {
|
|
139
|
+
const rel = path.relative(cwd, item.file);
|
|
140
|
+
const line = item.start?.line || 0;
|
|
141
|
+
const offset = item.start?.offset || 0;
|
|
142
|
+
const code = item.code ? `TS${item.code}` : 'TS';
|
|
143
|
+
const category = item.category || 'error';
|
|
144
|
+
const text = flattenMessage(item.text);
|
|
145
|
+
|
|
146
|
+
console.log(`${theme.filedir(rel)}:${line}:${offset} ${theme.action(item.kind)} ${theme.failure(` ${code} `)} ${category}: ${text}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function send(server, seq, command, args) {
|
|
151
|
+
server.stdin.write(JSON.stringify({ seq: seq.value++, type: 'request', command, arguments: args }) + '\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function checkImbaTypes(entrypoint, options = {}) {
|
|
155
|
+
const cwd = options.cwd || process.cwd();
|
|
156
|
+
const timeout = Number(options.timeout || process.env.BIMBA_TYPECHECK_TIMEOUT || process.env.IMBA_TS_CHECK_TIMEOUT || 12000);
|
|
157
|
+
const scanRoot = getScanRoot(entrypoint, cwd);
|
|
158
|
+
const files = collectImbaFiles(scanRoot);
|
|
159
|
+
|
|
160
|
+
if (!files.length) {
|
|
161
|
+
console.log(theme.success('Success') + ` No Imba files found in ${theme.filedir(path.relative(cwd, scanRoot) || '.')}`);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const tsserver = findTypeScript(cwd);
|
|
166
|
+
const pluginProbe = findPluginProbe(cwd);
|
|
167
|
+
const runner = process.env.BIMBA_NODE || process.env.NODE || 'node';
|
|
168
|
+
|
|
169
|
+
console.log(theme.folder('──────────────────────────────────────────────────────────────────────'));
|
|
170
|
+
console.log(theme.start(`Start checking TypeScript diagnostics for ${theme.count(files.length)} Imba file${files.length > 1 ? 's' : ''}`));
|
|
171
|
+
|
|
172
|
+
return await new Promise((resolve) => {
|
|
173
|
+
let settled = false;
|
|
174
|
+
let buffer = Buffer.alloc(0);
|
|
175
|
+
const seq = { value: 1 };
|
|
176
|
+
const diagnostics = [];
|
|
177
|
+
|
|
178
|
+
const server = spawn(runner, [
|
|
179
|
+
tsserver,
|
|
180
|
+
'--globalPlugins',
|
|
181
|
+
'typescript-imba-plugin',
|
|
182
|
+
'--pluginProbeLocations',
|
|
183
|
+
pluginProbe,
|
|
184
|
+
], { cwd });
|
|
185
|
+
|
|
186
|
+
function finish(success) {
|
|
187
|
+
if (settled) return;
|
|
188
|
+
settled = true;
|
|
189
|
+
clearTimeout(timer);
|
|
190
|
+
server.kill();
|
|
191
|
+
resolve(success);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const timer = setTimeout(() => {
|
|
195
|
+
const unique = uniqueDiagnostics(diagnostics);
|
|
196
|
+
|
|
197
|
+
if (!unique.length) {
|
|
198
|
+
console.log(theme.success('Success') + ' No Imba TypeScript diagnostics');
|
|
199
|
+
finish(true);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
printDiagnostics(cwd, unique);
|
|
204
|
+
console.log(theme.failure(' Failure ') + ` TypeScript found ${theme.count(unique.length)} diagnostic${unique.length > 1 ? 's' : ''}`);
|
|
205
|
+
finish(false);
|
|
206
|
+
}, timeout);
|
|
207
|
+
|
|
208
|
+
server.on('error', (error) => {
|
|
209
|
+
console.log(theme.failure(' Failure ') + ` Could not start ${runner}: ${error.message}`);
|
|
210
|
+
finish(false);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
server.stderr.on('data', chunk => process.stderr.write(chunk));
|
|
214
|
+
|
|
215
|
+
server.stdout.on('data', chunk => {
|
|
216
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
217
|
+
buffer = parseMessages(buffer, msg => {
|
|
218
|
+
if (msg.type != 'event' || !/Diag$/.test(msg.event)) return;
|
|
219
|
+
if (!msg.body?.diagnostics?.length) return;
|
|
220
|
+
|
|
221
|
+
for (const diagnostic of msg.body.diagnostics) {
|
|
222
|
+
const key = [
|
|
223
|
+
msg.event,
|
|
224
|
+
msg.body.file,
|
|
225
|
+
diagnostic.start?.line,
|
|
226
|
+
diagnostic.start?.offset,
|
|
227
|
+
diagnostic.code,
|
|
228
|
+
flattenMessage(diagnostic.text),
|
|
229
|
+
].join('\0');
|
|
230
|
+
|
|
231
|
+
diagnostics.push({
|
|
232
|
+
key,
|
|
233
|
+
kind: msg.event,
|
|
234
|
+
file: msg.body.file,
|
|
235
|
+
...diagnostic,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
setTimeout(() => {
|
|
242
|
+
if (settled) return;
|
|
243
|
+
send(server, seq, 'configure', { preferences: {}, hostInfo: 'bimba-typecheck' });
|
|
244
|
+
for (const file of files) send(server, seq, 'open', { file, projectRootPath: cwd });
|
|
245
|
+
send(server, seq, 'geterr', { files, delay: 0 });
|
|
246
|
+
}, 100);
|
|
247
|
+
});
|
|
248
|
+
}
|