bimba-cli 0.1.1

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 ADDED
@@ -0,0 +1,15 @@
1
+ # bimba-cli
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.0.26. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
package/bunfig.toml ADDED
@@ -0,0 +1 @@
1
+ preload = ["./plugin.ts"]
package/index.ts ADDED
@@ -0,0 +1,148 @@
1
+ #!~/.bun/bin bun
2
+
3
+ import { parseArgs } from "util";
4
+ import ansis from 'ansis';
5
+ import { imbaPlugin, stats } from './plugin.ts'
6
+ import fs from 'fs'
7
+ import path from 'path';
8
+ import { $ } from "bun";
9
+
10
+ let flags = {};
11
+ let folders = {};
12
+
13
+ try {
14
+ const { values, positionals } = parseArgs({
15
+ args: Bun.argv,
16
+ options: {
17
+ watch: { type: 'boolean' },
18
+ outdir: { type: 'string' },
19
+ help: { type: 'boolean' },
20
+ minify: { type: 'boolean' },
21
+ target: { type: 'string' },
22
+ sourcemap: { type: 'string' },
23
+ },
24
+ strict: true,
25
+ allowPositionals: true,
26
+ });
27
+ flags = values;
28
+ flags.entry = Bun.argv[2];
29
+ folders = positionals;
30
+ }
31
+ catch (error) {
32
+ if (error instanceof Error)
33
+ console.log(error.message);
34
+ else
35
+ console.log("Could not resolve CLI arguments. Read help to know them: " + theme.flags('--entry file.imba'));
36
+ process.exit(1);
37
+ }
38
+
39
+ const theme = {
40
+ flags: ansis.fg(5),
41
+ count: ansis.fg(15).bold,
42
+ start: ansis.fg(252).bg(233),
43
+ filedir: ansis.fg(15),
44
+ success: ansis.fg(40),
45
+ failure: ansis.fg(196),
46
+ time: ansis.fg(41),
47
+ link: ansis.fg(15),
48
+ online: ansis.fg(40).bg(22)
49
+ };
50
+
51
+ // help: more on bun building params here: https://bun.sh/docs/bundler
52
+ if(flags.help) {
53
+ console.log("");
54
+ console.log("Bimba requeres an .imba file as the first argument.");
55
+ console.log("By default the provided .imba file will be launched (the project will be compiled on the fly by Bun):");
56
+ console.log(" "+theme.flags('bimba <file.imba>')+" The entrypoint of the imba project");
57
+ console.log("");
58
+ console.log("To compile Imba project to .js file the outdir should be specified:");
59
+ console.log(" "+theme.flags('--outdir <folder>')+" Compile imba files to specified folder");
60
+ console.log("");
61
+ console.log("There are other options that could be used when compiling .imba files:");
62
+ console.log(" "+theme.flags('--minify')+" Minify compiled .js files");
63
+ console.log(" "+theme.flags('--sourcemap <inline|external|none>')+" How should be sourcemap files be included in the .js");
64
+ console.log(" "+theme.flags('--platform <browser|node>')+" Flag that will be passed to Imba compiler ('node' value does not work in Bun)");
65
+ console.log("");
66
+ console.log("The watch switch is applied in both scenarios (when running and when building Imba sources):");
67
+ console.log(" "+theme.flags('--watch')+" Watch for changes in the entrypoint folder");
68
+ console.log("");
69
+ process.exit(1);
70
+ }
71
+
72
+
73
+ // no entrypoint
74
+ if(!flags.entry) {
75
+ console.log("");
76
+ console.log("No entrypoint provided: "+theme.flags('bimba file.imba'));
77
+ console.log("For more information: "+theme.flags('--help'));
78
+ console.log("");
79
+ process.exit(1);
80
+ }
81
+
82
+
83
+ // build
84
+ if(flags.outdir){
85
+ bundle();
86
+ watch(bundle);
87
+ }
88
+ else {
89
+ run();
90
+ watch(run);
91
+ }
92
+
93
+ function watch(callback) {
94
+ if (flags.watch) {
95
+ const watcher = fs.watch(path.dirname(flags.entry), {recursive: true}, async (event, filename) => ( callback() ));
96
+
97
+ process.on("SIGINT", () => {
98
+ if(watcher) {
99
+ watcher.close();
100
+ process.exit(0);
101
+ }
102
+ });
103
+ }
104
+ }
105
+
106
+ async function run() {
107
+ await $`bun run ${path.resolve(flags.entry)}`;
108
+ }
109
+
110
+
111
+ async function bundle() {
112
+ if (!fs.existsSync(flags.entry)) {
113
+ console.log(theme.failure('Error.') + ` The specified entrypoint does not exist: ${theme.filedir(flags.entry)}`);
114
+ process.exit(1);
115
+ }
116
+
117
+ //if (!fs.existsSync(flags.bundle)){ fs.mkdirSync(flags.bundle);}
118
+
119
+ stats.failed = 0
120
+ stats.compiled = 0
121
+ stats.errors = 0
122
+ stats.bundled = 0
123
+
124
+ const start = Date.now();
125
+
126
+ console.log("──────────────────────────────────────────────────────────────────────");
127
+ console.log(theme.start(`Start building the Imba entrypoint: ${theme.filedir(flags.entry)}`));
128
+
129
+ const result = await Bun.build({
130
+ entrypoints: [flags.entry],
131
+ outdir: flags.outdir,
132
+ target: flags.target || 'browser',
133
+ sourcemap: flags.sourcemap || 'none',
134
+ minify: flags.minify || true,
135
+ plugins: [imbaPlugin]
136
+ });
137
+
138
+ if(stats.failed)
139
+ console.log(theme.start(theme.failure("Failure.") +` Imba compiler failed to proceed ${theme.count(stats.failed)} file${stats.failed > 1 ? 's' : ''}`));
140
+ else
141
+ 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)}`));
142
+
143
+ if(!result.success && !stats.errors){
144
+ for (const log of result.logs) {
145
+ console.log(log);
146
+ }
147
+ }
148
+ }
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "bimba-cli",
3
+ "version": "0.1.1",
4
+ "repository": "github:HeapVoid/bimba",
5
+ "module": "index.ts",
6
+ "bin": {
7
+ "bimba": "./index.ts"
8
+ },
9
+ "description": "The CLI tool to run Imba projects under Bun",
10
+ "keywords": ["imba", "bun", "plugin"],
11
+ "license": "MIT",
12
+ "type": "module",
13
+ "devDependencies": {
14
+ "ansis": "^2.2.0",
15
+ "imba": "^2.0.0-alpha.234"
16
+ }
17
+ }
package/plugin.ts ADDED
@@ -0,0 +1,217 @@
1
+ import { plugin, type BunPlugin } from "bun";
2
+ import ansis from 'ansis';
3
+ import * as compiler from 'imba/compiler'
4
+ import dir from 'path'
5
+ import fs from 'fs'
6
+
7
+ // theme for messages printed in terminal
8
+ // color pallete can be seen here: https://raw.githubusercontent.com/webdiscus/ansis/master/docs/img/ansi256.png
9
+ // for more details read the project page: https://github.com/webdiscus/ansis
10
+ const theme = {
11
+ action: ansis.fg(237),
12
+ folder: ansis.fg(240),
13
+ filename: ansis.fg(15),
14
+ success: ansis.fg(40),
15
+ failure: ansis.fg(15).bg(124),
16
+ };
17
+
18
+ const cache = process.cwd() + '/.cache/';
19
+ if (!fs.existsSync(cache)){ fs.mkdirSync(cache);}
20
+
21
+ // this should be reset from outside to get results of entrypoint building
22
+ export let stats = {
23
+ failed: 0,
24
+ compiled: 0,
25
+ cached: 0,
26
+ bundled: 0,
27
+ errors: 0
28
+ };
29
+
30
+ export const imbaPlugin: BunPlugin = {
31
+ name: "imba",
32
+ async setup(build) {
33
+
34
+ // when there is import without file extension
35
+ build.onResolve({filter: /^.*[^.]{5}$/ }, ({ path, importer }) => {
36
+
37
+ let filename = path;
38
+ // resolve relative path
39
+ if (path.startsWith('.')) { filename = dir.resolve(dir.dirname(importer), filename) };
40
+
41
+ // assume that the file is .js
42
+ try { return {path: Bun.resolveSync(filename + '.js', '.')}}
43
+ catch (error) {
44
+ // assume that the file is .mjs
45
+ try { return {path: Bun.resolveSync(filename + '.mjs', '.')}}
46
+ catch (error) {
47
+ // assume that the file is .cs
48
+ try { return {path: Bun.resolveSync(filename + '.cjs', '.')}}
49
+ catch (error) {
50
+ // assume that the file is .imba
51
+ try { return {path: Bun.resolveSync(filename + '.imba', '.')}}
52
+ catch (error) {
53
+ // if direct resolution failed
54
+ filename += '.imba';
55
+
56
+ // assume that the relative path should be resolved relative to importer
57
+ let fn = dir.resolve(dir.dirname(importer), filename);
58
+ if (fs.existsSync(fn)) return {path: fn};
59
+ // assume that the relative path should be resolved relative to node_modules
60
+ fn = dir.resolve('./node_modules', filename);
61
+ if (fs.existsSync(fn)) return {path: fn};
62
+ // assume that the relative path should be resolved relative to project root
63
+ fn = dir.resolve(process.cwd(), filename);
64
+ if (fs.existsSync(fn)) return {path: fn};
65
+
66
+ // if the path still is unresolved throw error and leave the further resolution on Bun's resolver
67
+ if (error instanceof Error) {
68
+ throw new Error(error.message);
69
+ }
70
+ else
71
+ throw new Error('Could not resolve file: ' + path);
72
+ }
73
+ }
74
+ }
75
+ }
76
+ })
77
+
78
+ // when an .imba file is imported...
79
+ build.onLoad({ filter: /\.imba$/ }, async ({ path }) => {
80
+
81
+ const f = dir.parse(path)
82
+ let contents = '';
83
+
84
+ // return the cached version if it exists
85
+ const cached = cache + Bun.hash(path + fs.statSync(path).mtimeMs) + '_' + f.name + '.js';;
86
+ if (fs.existsSync(cached)) {
87
+ stats.bundled++;
88
+ stats.cached++;
89
+ //console.log(theme.action("cached: ") + theme.folder(f.dir + '/') + theme.filename(f.base) + " - " + theme.success("ok"));
90
+ return {
91
+ contents: await Bun.file(cached).text(),
92
+ loader: "js",
93
+ };
94
+ }
95
+
96
+ // if no cached version read and compile it with the imba compiler
97
+ const file = await Bun.file(path).text();
98
+ const out = compiler.compile(file, {
99
+ sourcePath: path,
100
+ platform: 'browser'
101
+ })
102
+
103
+ // print about file complitaion
104
+ console.write(theme.action("compiling: ") + theme.folder(f.dir + '/') + theme.filename(f.base) + " - ");
105
+
106
+ // the file has been successfully compiled
107
+ if (!out.errors || !out.errors.length) {
108
+ stats.bundled++;
109
+ stats.compiled++;
110
+ contents = out.js;
111
+ await Bun.write(cached, contents);
112
+ console.write(theme.success("cached" + "\n"));
113
+ }
114
+ // there were errors during compilation
115
+ else {
116
+ console.write(theme.failure(" fail ") + "\n");
117
+ stats.failed++;
118
+ for (let i = 0; i < out.errors.length; i++) {
119
+ if(out.errors[i]) printerr(out.errors[i]);
120
+ }
121
+ stats.errors++;
122
+ }
123
+
124
+ // and return the compiled source code as "js"
125
+ return {
126
+ contents,
127
+ loader: "js",
128
+ };
129
+ });
130
+ }
131
+ };
132
+
133
+ plugin(imbaPlugin);
134
+
135
+
136
+ // -------------------------------------------------------------------------------
137
+ // print pretty messages produced by the imba compiler
138
+ // -------------------------------------------------------------------------------
139
+
140
+ type imbaCompilerError = {
141
+ toSnippet: Function;
142
+ toError: Function;
143
+ message: string;
144
+ range: {
145
+ start: {
146
+ line: number
147
+ }
148
+ }
149
+ }
150
+
151
+ // print an error generated by the imba compiler
152
+ function printerr(err: imbaCompilerError) {
153
+
154
+ // halper function to produce empty strings
155
+ const fill = (len = 0) => {return new Array(len + 1).join(' ')}
156
+
157
+ // set color theme for an error message
158
+ const colors = {
159
+ code: ansis.fg(252).bg(238),
160
+ margin: ansis.fg(229).bg(145),
161
+ error: ansis.fg(196).bg(52),
162
+ ecode: ansis.fg(196).bg(238).bold,
163
+ };
164
+
165
+ // gather the needed information from the compiler error
166
+ const snippet = err.toSnippet().split("\n");
167
+ const display = {
168
+ error: " " + err.message + " ",
169
+ outdent: fill(10),
170
+ source: snippet[1] + " ",
171
+ margin: " line " + err.range.start.line + " ",
172
+ errs: snippet[2].indexOf('^'),
173
+ erre: snippet[2].lastIndexOf('^') + 1,
174
+ };
175
+
176
+ // calculate parameters for priniting a message
177
+ const center = display.margin.length + display.errs + Math.floor((display.erre - display.errs) / 2);
178
+ const half = Math.ceil((display.error.length - 1) / 2);
179
+ const start = Math.max(0, center - half);
180
+ const end = start + display.error.length;
181
+ const total = Math.max(display.margin.length + display.source.length, end);
182
+
183
+ // print emtpy line
184
+ console.log('');
185
+
186
+ // print line with the error message
187
+ console.log(
188
+ display.outdent +
189
+ colors.margin(fill(Math.min(start, display.margin.length))) +
190
+ colors.code(fill(Math.max(0, start - display.margin.length))) +
191
+ colors.error(display.error) +
192
+ colors.margin(fill(Math.max(0, display.margin.length - end))) +
193
+ colors.code(fill(Math.min(total - display.margin.length, total - end)))
194
+ );
195
+
196
+ // print line with the source code
197
+ console.log(
198
+ display.outdent +
199
+ colors.margin(display.margin) +
200
+ colors.code(display.source.slice(0,display.errs)) +
201
+ colors.error(display.source.slice(display.errs,display.erre)) +
202
+ colors.code(display.source.slice(display.erre)) +
203
+ colors.code(fill(total - display.source.length - display.margin.length))
204
+ );
205
+
206
+ // print empty line to balance the view
207
+ // later we can put something usefull here
208
+ // for example a link to online docs about the error
209
+ console.log(
210
+ display.outdent +
211
+ colors.margin(fill(display.margin.length)) +
212
+ colors.code(fill(total - display.margin.length))
213
+ );
214
+
215
+ // print emtpy line
216
+ console.log('');
217
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["ESNext"],
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "moduleDetection": "force",
7
+ "jsx": "react-jsx",
8
+ "allowJs": true,
9
+
10
+ /* Bundler mode */
11
+ "moduleResolution": "bundler",
12
+ "allowImportingTsExtensions": true,
13
+ "verbatimModuleSyntax": true,
14
+ "noEmit": true,
15
+
16
+ /* Linting */
17
+ "skipLibCheck": true,
18
+ "strict": true,
19
+ "noFallthroughCasesInSwitch": true,
20
+ "forceConsistentCasingInFileNames": true
21
+ }
22
+ }