jsmdcui 0.8.0 → 0.9.0
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/CHANGELOG.md +66 -0
- package/README.md +77 -1
- package/cdp-maze.js +141 -0
- package/demos/maze.md +6 -1
- package/llm-maze.txt +94 -0
- package/package.json +1 -1
- package/runtime/help/cdp.md +5 -0
- package/runtime/help/help.md +77 -1
- package/runtime/jsplugins/cdp/cdp-server.js +26 -2
- package/runtime/jsplugins/cdp/cdp.js +55 -49
- package/runtime/jsplugins/chapter/chapter.js +2 -1
- package/runtime/jsplugins/example/example.js +2 -0
- package/single-exe/README.md +223 -45
- package/single-exe/compiled.js +6 -3
- package/single-exe/entry.mjs +4 -3
- package/single-exe/packAssets.sh +1 -1
- package/src/cui/kitty-debug.mjs +1 -1
- package/src/index.js +46 -10
- package/src/lua/engine.js +1 -4
- package/src/plugins/js-bridge.js +43 -0
- package/tests/compiled-runtime.test.js +8 -0
- package/tests/demo.test.js +40 -0
- package/tests/kitty-demo.md +1 -1
- package/demo.resized.jpg +0 -0
- package/src/runtime/compiled.js +0 -25
|
@@ -34,6 +34,8 @@ Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
|
34
34
|
micro.getLine(n?) micro.putLine(text, n?) micro.delLine(n?)
|
|
35
35
|
micro.getLines(from?, to?) micro.getLinesCount()
|
|
36
36
|
micro.getAllText() — entire buffer as one string (lines joined by "\n")
|
|
37
|
+
micro.getAllAnsiText() — rendered ANSI document, or plain text when unavailable
|
|
38
|
+
micro.clickBufferCell(x, y) — activate a 1-based MDCUI cell, or goto in other buffers
|
|
37
39
|
micro.putAllText(text) — replace entire buffer content; pushes undo
|
|
38
40
|
micro.getSelection() micro.putSelection(text)
|
|
39
41
|
|
|
@@ -82,7 +84,7 @@ micro.on("init", () => {
|
|
|
82
84
|
},
|
|
83
85
|
async click(x,y,opt){
|
|
84
86
|
x=x||1 ; y=y||1 ;
|
|
85
|
-
await micro.
|
|
87
|
+
await micro.clickBufferCell(x, y);
|
|
86
88
|
},
|
|
87
89
|
async scroll(dx,dy){
|
|
88
90
|
const pane = micro.CurPane();
|
|
@@ -111,54 +113,8 @@ micro.on("init", () => {
|
|
|
111
113
|
bp.Insert(text);
|
|
112
114
|
},
|
|
113
115
|
async press(key, options){
|
|
114
|
-
const
|
|
115
|
-
if (
|
|
116
|
-
|
|
117
|
-
// modifiers bitmask: Alt=1, Ctrl=2, Meta=4, Shift=8
|
|
118
|
-
const mod = options?.modifiers ?? 0;
|
|
119
|
-
const ctrl = !!(mod & 2);
|
|
120
|
-
const shift = !!(mod & 8);
|
|
121
|
-
|
|
122
|
-
if (ctrl) {
|
|
123
|
-
const ctrlMap = {
|
|
124
|
-
a: () => micro.action.SelectAll(),
|
|
125
|
-
c: () => micro.action.Copy(),
|
|
126
|
-
x: () => micro.action.Cut(),
|
|
127
|
-
v: () => micro.action.Paste(),
|
|
128
|
-
z: () => micro.action.Undo(),
|
|
129
|
-
y: () => micro.action.Redo(),
|
|
130
|
-
s: () => micro.action.Save(),
|
|
131
|
-
};
|
|
132
|
-
const h = ctrlMap[key.toLowerCase()];
|
|
133
|
-
if (h) await h();
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const arrowAction = shift
|
|
138
|
-
? { ArrowUp: 'SelectUp', ArrowDown: 'SelectDown', ArrowLeft: 'SelectLeft', ArrowRight: 'SelectRight' }
|
|
139
|
-
: { ArrowUp: 'CursorUp', ArrowDown: 'CursorDown', ArrowLeft: 'CursorLeft', ArrowRight: 'CursorRight' };
|
|
140
|
-
|
|
141
|
-
const keyMap = {
|
|
142
|
-
...arrowAction,
|
|
143
|
-
Enter: () => micro.action.InsertNewline(),
|
|
144
|
-
Backspace: () => micro.action.Backspace(),
|
|
145
|
-
Delete: () => micro.action.Delete(),
|
|
146
|
-
Tab: () => micro.action.InsertTab(),
|
|
147
|
-
Escape: () => micro.action.Escape(),
|
|
148
|
-
Home: () => shift ? micro.action.SelectToStartOfLine() : micro.action.StartOfLine(),
|
|
149
|
-
End: () => shift ? micro.action.SelectToEndOfLine() : micro.action.EndOfLine(),
|
|
150
|
-
PageUp: () => shift ? micro.action.SelectPageUp() : micro.action.CursorPageUp(),
|
|
151
|
-
PageDown: () => shift ? micro.action.SelectPageDown() : micro.action.CursorPageDown(),
|
|
152
|
-
};
|
|
153
|
-
|
|
154
|
-
const entry = keyMap[key];
|
|
155
|
-
if (typeof entry === 'string') {
|
|
156
|
-
await micro.action[entry]();
|
|
157
|
-
} else if (typeof entry === 'function') {
|
|
158
|
-
await entry();
|
|
159
|
-
} else if (key.length === 1) {
|
|
160
|
-
bp.Insert(key);
|
|
161
|
-
}
|
|
116
|
+
const raw = cdpKeyToTerminalInput(key, options);
|
|
117
|
+
if (raw) await micro._dispatchRawInput(raw);
|
|
162
118
|
},
|
|
163
119
|
}
|
|
164
120
|
|
|
@@ -193,3 +149,53 @@ function toInteger(value) {
|
|
|
193
149
|
const number = Number(value);
|
|
194
150
|
return Number.isFinite(number) ? Math.trunc(number) : 0;
|
|
195
151
|
}
|
|
152
|
+
|
|
153
|
+
function cdpKeyToTerminalInput(key, options = {}) {
|
|
154
|
+
const modifiers = Number(options.modifiers) || 0;
|
|
155
|
+
const alt = !!(modifiers & 1);
|
|
156
|
+
const ctrl = !!(modifiers & 2);
|
|
157
|
+
const meta = !!(modifiers & 4);
|
|
158
|
+
const shift = !!(modifiers & 8);
|
|
159
|
+
const modifierCode = 1 + (shift ? 1 : 0) + (alt || meta ? 2 : 0) + (ctrl ? 4 : 0);
|
|
160
|
+
const value = String(key ?? options.text ?? "");
|
|
161
|
+
|
|
162
|
+
const cursorFinal = {
|
|
163
|
+
ArrowUp: "A",
|
|
164
|
+
ArrowDown: "B",
|
|
165
|
+
ArrowRight: "C",
|
|
166
|
+
ArrowLeft: "D",
|
|
167
|
+
Home: "H",
|
|
168
|
+
End: "F",
|
|
169
|
+
}[value];
|
|
170
|
+
if (cursorFinal) {
|
|
171
|
+
return modifierCode === 1
|
|
172
|
+
? `\x1b[${cursorFinal}`
|
|
173
|
+
: `\x1b[1;${modifierCode}${cursorFinal}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (value === "PageUp" || value === "PageDown" || value === "Delete") {
|
|
177
|
+
const number = value === "PageUp" ? 5 : value === "PageDown" ? 6 : 3;
|
|
178
|
+
return modifierCode === 1
|
|
179
|
+
? `\x1b[${number}~`
|
|
180
|
+
: `\x1b[${number};${modifierCode}~`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (value === "Tab") return shift ? "\x1b[Z" : "\t";
|
|
184
|
+
if (value === "Enter") return alt || meta ? "\x1b\r" : "\r";
|
|
185
|
+
if (value === "Backspace") return "\x7f";
|
|
186
|
+
if (value === "Escape") return "\x1b";
|
|
187
|
+
|
|
188
|
+
let text = typeof options.text === "string" && options.text
|
|
189
|
+
? options.text
|
|
190
|
+
: value === "Space" ? " " : value;
|
|
191
|
+
if ([...text].length !== 1) return "";
|
|
192
|
+
|
|
193
|
+
if (ctrl) {
|
|
194
|
+
const code = text.toUpperCase().charCodeAt(0);
|
|
195
|
+
if (code >= 64 && code <= 95) text = String.fromCharCode(code & 31);
|
|
196
|
+
} else if (shift && /^[a-z]$/.test(text)) {
|
|
197
|
+
text = text.toUpperCase();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return alt || meta ? `\x1b${text}` : text;
|
|
201
|
+
}
|
|
@@ -35,6 +35,8 @@ Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
|
35
35
|
micro.getLine(n?) micro.putLine(text, n?) micro.delLine(n?)
|
|
36
36
|
micro.getLines(from?, to?) micro.getLinesCount()
|
|
37
37
|
micro.getAllText() — entire buffer as one string (lines joined by "\n")
|
|
38
|
+
micro.getAllAnsiText() — rendered ANSI document, or plain text when unavailable
|
|
39
|
+
micro.clickBufferCell(x, y) — activate a 1-based MDCUI cell, or goto in other buffers
|
|
38
40
|
micro.putAllText(text) — replace entire buffer content; pushes undo
|
|
39
41
|
micro.getSelection() micro.putSelection(text)
|
|
40
42
|
|
|
@@ -107,4 +109,3 @@ micro.on("init", () => {
|
|
|
107
109
|
});
|
|
108
110
|
|
|
109
111
|
});
|
|
110
|
-
|
|
@@ -35,6 +35,8 @@ Flat buffer helpers (all 1-based line numbers, omit → cursor line):
|
|
|
35
35
|
micro.getLine(n?) micro.putLine(text, n?) micro.delLine(n?)
|
|
36
36
|
micro.getLines(from?, to?) micro.getLinesCount()
|
|
37
37
|
micro.getAllText() — entire buffer as one string (lines joined by "\n")
|
|
38
|
+
micro.getAllAnsiText() — rendered ANSI document, or plain text when unavailable
|
|
39
|
+
micro.clickBufferCell(x, y) — activate a 1-based MDCUI cell, or goto in other buffers
|
|
38
40
|
micro.putAllText(text) — replace entire buffer content; pushes undo
|
|
39
41
|
micro.getSelection() micro.putSelection(text)
|
|
40
42
|
|
package/single-exe/README.md
CHANGED
|
@@ -1,27 +1,231 @@
|
|
|
1
|
-
#
|
|
2
|
-
- I still recommend using the methods in the root README.md
|
|
3
|
-
- Bun's Android build is currently not supported
|
|
1
|
+
# Bun Single-File Executable Bootstrap
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- Then run the build script like this
|
|
3
|
+
Build a Bun application and its runtime assets into one executable while
|
|
4
|
+
keeping the regular main module free of Bun-specific asset imports.
|
|
8
5
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```
|
|
6
|
+
You can copy and adapt this build setup for your own project by changing its
|
|
7
|
+
main-module path and asset list as described below.
|
|
12
8
|
|
|
13
|
-
-
|
|
9
|
+
- Bundle an asset tree alongside the application code in one executable.
|
|
10
|
+
- Keep the regular main module available to the Node.js ESM execution path.
|
|
11
|
+
- Read embedded resources first with an optional external-file fallback.
|
|
12
|
+
- List, extract, or bypass embedded assets with built-in CLI flags.
|
|
13
|
+
- Build for the current platform or pass a Bun cross-compilation target.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
> This workflow is optional; the standard methods in the root README remain
|
|
16
|
+
> available. Bun's Android build is currently not supported.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
## Quick start for jsmdcui
|
|
19
|
+
|
|
20
|
+
From the repository root, build for the current platform:
|
|
21
|
+
|
|
22
|
+
```shell
|
|
23
|
+
bun ./src/index.js --build-exe
|
|
24
|
+
./mdcui --version
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`--build-exe` runs `packAssets.sh` automatically and writes the `mdcui`
|
|
28
|
+
executable to the current directory. To pass a Bun compilation target, use:
|
|
29
|
+
|
|
30
|
+
```shell
|
|
31
|
+
bun ./src/index.js --build-for <target>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
To perform the same steps manually, run these commands from `single-exe/`:
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
bun ./packAssets.sh
|
|
38
|
+
bun build --format=esm --compile --minify --bytecode ./entry.mjs --outfile=mdcui
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Adapting this folder to another project
|
|
42
|
+
|
|
43
|
+
This folder is a reusable starting point, not a drop-in package. The current
|
|
44
|
+
`entry.mjs` and `packAssets.sh` contain jsmdcui paths, so another project must
|
|
45
|
+
complete the following integration steps.
|
|
46
|
+
|
|
47
|
+
The adopting project needs Bun for packing and compiling, a `tar` command for
|
|
48
|
+
`packAssets.sh`, and an ES-module setup. Keep `"type": "module"` in
|
|
49
|
+
`package.json`, or rename the copied `.js` modules to `.mjs` and update their
|
|
50
|
+
imports. The supported uncompiled Node path requires Node.js 20.11 or newer
|
|
51
|
+
because `compiled.js` uses `import.meta.dirname`.
|
|
52
|
+
|
|
53
|
+
`assetsHelper.js` and `compiled.js` are Node-compatible under those conditions.
|
|
54
|
+
`assetsLoader.mjs` and `entry.mjs` are Bun-only and must not be imported by the
|
|
55
|
+
regular Node entry path.
|
|
56
|
+
|
|
57
|
+
### 1. Copy the bootstrap directory
|
|
58
|
+
|
|
59
|
+
Copy the entire `single-exe/` directory to the root of the other project and
|
|
60
|
+
keep its name and relative position unless you also update the paths in
|
|
61
|
+
`compiled.js` and `packAssets.sh`. A typical layout is:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
my-project/
|
|
65
|
+
├── single-exe/
|
|
66
|
+
│ ├── assetsHelper.js
|
|
67
|
+
│ ├── assetsLoader.mjs
|
|
68
|
+
│ ├── compiled.js
|
|
69
|
+
│ ├── entry.mjs
|
|
70
|
+
│ └── packAssets.sh
|
|
71
|
+
└── src/
|
|
72
|
+
└── index.js
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`assets.tar` is generated by `packAssets.sh`; it does not need to exist before
|
|
76
|
+
the first packing step.
|
|
77
|
+
|
|
78
|
+
### 2. Point `entry.mjs` at the main program
|
|
79
|
+
|
|
80
|
+
Keep the asset loader as the first import and change the path passed to the
|
|
81
|
+
final `await import()` so it points to the other project's real entry module:
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
#!/usr/bin/env bun
|
|
85
|
+
|
|
86
|
+
import "./assetsLoader.mjs";
|
|
87
|
+
await globalThis.assetsLoaderPromise;
|
|
88
|
+
await import("../src/index.js");
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
> **Important:** Keep the final import dynamic. Do not replace it with a static
|
|
92
|
+
> `import "../src/index.js"`, because static dependencies are evaluated before
|
|
93
|
+
> the asset Promise is awaited.
|
|
94
|
+
|
|
95
|
+
Only the Bun single-file executable build should use this bootstrap entry.
|
|
96
|
+
Continue to run the regular main module directly when using Node:
|
|
97
|
+
|
|
98
|
+
```shell
|
|
99
|
+
node ./src/index.js
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 3. Choose which files to embed
|
|
103
|
+
|
|
104
|
+
Edit the resource list in `packAssets.sh`; the script runs `tar` for you. It
|
|
105
|
+
changes to the project root, writes `single-exe/assets.tar`, and packs every
|
|
106
|
+
listed runtime resource required by the compiled program. Paths stored in the
|
|
107
|
+
archive become the lookup keys used by `assetsHelper.js`; use keys such as
|
|
108
|
+
`public/app.css`, without a leading `./`. The normal `--build-exe` and
|
|
109
|
+
`--build-for` flows invoke this script automatically, so you do not need to run
|
|
110
|
+
`tar` yourself.
|
|
111
|
+
|
|
112
|
+
For example:
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
#!/bin/sh
|
|
116
|
+
|
|
117
|
+
script_dir=$(dirname "$0")
|
|
118
|
+
cd "$script_dir/.." || exit 1
|
|
119
|
+
|
|
120
|
+
tar -cvf single-exe/assets.tar public templates README.md
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Do not retain jsmdcui's `demos`, `runtime`, `src/cui`, or `testapp.md` entries
|
|
124
|
+
unless the adopting project actually contains and needs them.
|
|
125
|
+
|
|
126
|
+
### 4. Read embedded assets with an external fallback
|
|
127
|
+
|
|
128
|
+
Import the Node-compatible helpers from `assetsHelper.js`. They return `null`
|
|
129
|
+
when the embedded store is unavailable or a path is not present:
|
|
130
|
+
|
|
131
|
+
```js
|
|
132
|
+
import { readFile } from "node:fs/promises";
|
|
133
|
+
import { resolve } from "node:path";
|
|
134
|
+
import { readInternalAssetText } from "../single-exe/assetsHelper.js";
|
|
135
|
+
import { REPO_ROOT } from "../single-exe/compiled.js";
|
|
136
|
+
|
|
137
|
+
async function readResourceText(path) {
|
|
138
|
+
return readInternalAssetText(path) ??
|
|
139
|
+
await readFile(resolve(REPO_ROOT, path), "utf8");
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
In the source tree, `REPO_ROOT` is the project root. In a compiled executable,
|
|
144
|
+
it is the executable's directory, which is also where `--assets-extract`
|
|
145
|
+
places the external resource tree. Apply the same embedded-first fallback to
|
|
146
|
+
every file that must work in both modes. Use `readInternalAssetBytes()` when
|
|
147
|
+
a resource should be returned as bytes instead of decoded text.
|
|
148
|
+
Use `listInternalAssetPaths()` to list embedded asset paths and
|
|
149
|
+
`listInternalAssetDirs()` to list embedded directories.
|
|
150
|
+
|
|
151
|
+
### 5. Add the optional build commands to the regular CLI
|
|
152
|
+
|
|
153
|
+
Before normal argument parsing, call `buildEarlyExit` from the regular main
|
|
154
|
+
module:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
const compiledHelper = await import("../single-exe/compiled.js").catch(() => null);
|
|
158
|
+
await compiledHelper?.buildEarlyExit?.(process.argv, "my-bin");
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
The second argument is the output filename; it defaults to `single.exe` when
|
|
162
|
+
omitted. This enables:
|
|
163
|
+
|
|
164
|
+
```shell
|
|
165
|
+
bun ./src/index.js --build-exe
|
|
166
|
+
bun ./src/index.js --build-for <target>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Both commands run `packAssets.sh` before `bun build`. The second form passes
|
|
170
|
+
`<target>` through as Bun's `--target=<target>` value.
|
|
171
|
+
|
|
172
|
+
Alternatively, build manually from inside `single-exe/`:
|
|
173
|
+
|
|
174
|
+
```shell
|
|
175
|
+
bun ./packAssets.sh
|
|
176
|
+
bun build --format=esm --compile --minify --bytecode ./entry.mjs --outfile=my-bin
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 6. Verify both execution paths
|
|
180
|
+
|
|
181
|
+
Verify the compiled asset archive and then run the program normally:
|
|
182
|
+
|
|
183
|
+
```shell
|
|
184
|
+
./my-bin --assets-list
|
|
185
|
+
./my-bin
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Also run the uncompiled main module with Node.js 20.11 or newer to verify that
|
|
189
|
+
all required files have a working external fallback:
|
|
190
|
+
|
|
191
|
+
```shell
|
|
192
|
+
node ./src/index.js
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Use `./my-bin --assets-extract` to write the packed tree beside the executable.
|
|
196
|
+
After extraction, `./my-bin --assets-external` skips the embedded store and is
|
|
197
|
+
useful for testing the external-resource path.
|
|
198
|
+
|
|
199
|
+
### Why `assetsLoaderPromise` is used
|
|
200
|
+
|
|
201
|
+
A more tightly coupled ESM design could export the assets with top-level
|
|
202
|
+
`await`, then make the main module import `assetsLoader.mjs` directly. This
|
|
203
|
+
project deliberately does not do that because `assetsLoader.mjs` contains
|
|
204
|
+
Bun's compiled-file import:
|
|
205
|
+
|
|
206
|
+
```js
|
|
207
|
+
import assets from "./assets.tar" with { type: "file" };
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
If the regular main module imported `assetsLoader.mjs`, that Bun-specific
|
|
211
|
+
`type: "file"` dependency would enter the main module graph. Node would then be
|
|
212
|
+
unable to load the program before it could select an external-file fallback.
|
|
213
|
+
|
|
214
|
+
Instead, only Bun's `entry.mjs` imports the Bun-specific loader, awaits its
|
|
215
|
+
Promise, and then dynamically imports the regular main module. The main module
|
|
216
|
+
does not import the loader, inspect the Promise, or contain Bun bootstrap code.
|
|
217
|
+
Under the supported Node.js 20.11+ ESM path, Node runs that clean main module
|
|
218
|
+
directly with external files. The Promise is therefore an intentional
|
|
219
|
+
Node-compatibility boundary inside the Bun-only bootstrap, not a failure to use
|
|
220
|
+
a more modern dependency or top-level-`await` design.
|
|
18
221
|
|
|
19
222
|
## Entry Flow
|
|
20
223
|
|
|
21
224
|
- `entry.mjs` imports `assetsLoader.mjs` first
|
|
22
225
|
- `assetsLoader.mjs` loads `assets.tar` with `Bun.Archive` and mounts it as `globalThis.internalAssets`
|
|
23
226
|
- `assetsLoaderPromise` is exposed on `globalThis`
|
|
24
|
-
-
|
|
227
|
+
- `entry.mjs` awaits `assetsLoaderPromise`
|
|
228
|
+
- `entry.mjs` dynamically imports the main program after the assets are ready
|
|
25
229
|
|
|
26
230
|
That keeps the main program bootable even if asset loading reports errors.
|
|
27
231
|
|
|
@@ -32,6 +236,12 @@ That keeps the main program bootable even if asset loading reports errors.
|
|
|
32
236
|
- Asset loading never rejects the bootstrap promise
|
|
33
237
|
- When loading succeeds, the archive is available through `globalThis.internalAssets`
|
|
34
238
|
|
|
239
|
+
This loader is not zero-copy: it materializes every bundled file in memory and
|
|
240
|
+
keeps the resulting bytes in `globalThis.internalAssets`. Large asset archives
|
|
241
|
+
can therefore increase startup and ongoing RAM usage. For workloads where that
|
|
242
|
+
matters, I also wrote an experimental Linux-only zero-copy hack:
|
|
243
|
+
[bun-assets-zerocopy](https://github.com/jjtseng93/bun-assets-zerocopy).
|
|
244
|
+
|
|
35
245
|
## CLI Flags
|
|
36
246
|
|
|
37
247
|
- `--assets-list`
|
|
@@ -46,35 +256,3 @@ That keeps the main program bootable even if asset loading reports errors.
|
|
|
46
256
|
- Skips loading bundled assets into `globalThis.internalAssets`
|
|
47
257
|
- Forces the main program and runtime helpers to use the external file tree
|
|
48
258
|
- Keeps the bootstrap alive while leaving `internalAssets` falsy
|
|
49
|
-
|
|
50
|
-
## Adapting this folder
|
|
51
|
-
|
|
52
|
-
This single-exe folder is intended for reuse by other projects. Usually you
|
|
53
|
-
only need to edit these project-specific files first:
|
|
54
|
-
|
|
55
|
-
- `entry.mjs`
|
|
56
|
-
- `packAssets.sh`
|
|
57
|
-
|
|
58
|
-
Then the main program can import `buildEarlyExit` from `compiled.js` and call
|
|
59
|
-
it before normal CLI parsing. `--build-exe` and `--build-for <target>` will run
|
|
60
|
-
`packAssets.sh` first, so `assets.tar` is generated as part of the build flow.
|
|
61
|
-
|
|
62
|
-
```js
|
|
63
|
-
const compiledHelper = await import("./single-exe/compiled.js").catch(() => null);
|
|
64
|
-
await compiledHelper?.buildEarlyExit?.(process.argv, "my-bin");
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
- `--build-exe` builds with no Bun target.
|
|
68
|
-
- `--build-for <target>` passes `<target>` to `bun build --target=<target>`.
|
|
69
|
-
- The second argument is the output filename. If omitted, it defaults to `single.exe`.
|
|
70
|
-
|
|
71
|
-
The main program should also switch its repo/resource root when running as a
|
|
72
|
-
compiled binary. Use `IS_COMPILED` and `REPO_ROOT` from
|
|
73
|
-
`compiled.js`; when compiled, treat the executable directory as the repo root
|
|
74
|
-
used for external fallback files such as `static/`, `README.md`, or other files
|
|
75
|
-
packed by `packAssets.sh`.
|
|
76
|
-
|
|
77
|
-
```js
|
|
78
|
-
const compiledHelper = await import("./single-exe/compiled.js").catch(() => null);
|
|
79
|
-
const repoRoot = compiledHelper?.REPO_ROOT
|
|
80
|
-
```
|
package/single-exe/compiled.js
CHANGED
|
@@ -12,9 +12,12 @@ const SINGLE_EXE_DIR = resolve(REPO_ROOT, "single-exe");
|
|
|
12
12
|
const SINGLE_EXE_ENTRY = resolve(SINGLE_EXE_DIR, "entry.mjs");
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
export function isCompiledBinary() {
|
|
16
|
-
const
|
|
17
|
-
return Boolean(
|
|
15
|
+
export function isCompiledBinary(argv = process.argv) {
|
|
16
|
+
const entry = argv?.[1];
|
|
17
|
+
return Boolean(
|
|
18
|
+
entry?.startsWith?.("/$bunfs/") ||
|
|
19
|
+
entry?.startsWith?.("B:/~BUN")
|
|
20
|
+
);
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
export function getExeDirname() {
|
package/single-exe/entry.mjs
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
// 1. Injects assets to global.internalAssets
|
|
4
4
|
// as { "./path/in/tar":file.bytes() }
|
|
5
5
|
// 2. Sets global.assetsLoaderPromise
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
import "../src/index.js"
|
|
6
|
+
// 3. Starts the main program only after the assets are ready
|
|
7
|
+
import "./assetsLoader.mjs";
|
|
9
8
|
|
|
9
|
+
await globalThis.assetsLoaderPromise;
|
|
10
|
+
await import("../src/index.js");
|
package/single-exe/packAssets.sh
CHANGED
|
@@ -4,4 +4,4 @@ sd=$(dirname "$0")
|
|
|
4
4
|
|
|
5
5
|
cd "$sd"/..
|
|
6
6
|
|
|
7
|
-
tar -cvf single-exe/assets.tar demos runtime README.md CHANGELOG.md src/cui/server.mjs src/cui/rpc.mjs testapp.md
|
|
7
|
+
tar -cvf single-exe/assets.tar demos runtime README.md CHANGELOG.md cdp-maze.js src/cui/server.mjs src/cui/rpc.mjs testapp.md
|
package/src/cui/kitty-debug.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { appendFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
3
|
|
|
4
|
-
export const KITTY_DEBUG_LOG = resolve(import.meta.
|
|
4
|
+
export const KITTY_DEBUG_LOG = resolve(import.meta.dirname, "../..", "kitty-placement.log");
|
|
5
5
|
export const KITTY_DEBUG_ENABLED = /^(1|true|yes|on)$/i.test(
|
|
6
6
|
String(process.env.JSMDCUI_KITTY_DEBUG ?? ""),
|
|
7
7
|
);
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
-
let mainPromise = globalThis.assetsLoaderPromise ||
|
|
4
|
-
Promise.resolve();
|
|
5
|
-
|
|
6
3
|
const jsStart = globalThis.Bun ? Bun.nanoseconds() : Date.now() * 1e6;
|
|
7
4
|
const checkpoints = [
|
|
8
5
|
{ name: "Bun Engine Boot", time: 0 },
|
|
@@ -147,7 +144,12 @@ if(!globalThis.Bun)
|
|
|
147
144
|
//console.log(process.argv)
|
|
148
145
|
|
|
149
146
|
console.error('Ran by node, changed to run by bun')
|
|
150
|
-
|
|
147
|
+
const launched = child_process.spawnSync(bunbinary, process.argv.slice(1), {
|
|
148
|
+
stdio: "inherit",
|
|
149
|
+
env: process.env,
|
|
150
|
+
});
|
|
151
|
+
if (launched.error) throw launched.error;
|
|
152
|
+
process.exit(launched.status ?? 1);
|
|
151
153
|
}
|
|
152
154
|
catch(e){
|
|
153
155
|
console.log(`
|
|
@@ -1092,6 +1094,7 @@ function parseArgs(argv) {
|
|
|
1092
1094
|
cat: false,
|
|
1093
1095
|
docs: false,
|
|
1094
1096
|
exportReadme: false,
|
|
1097
|
+
exportCdpMaze: false,
|
|
1095
1098
|
changelog: false,
|
|
1096
1099
|
testapp: false,
|
|
1097
1100
|
demoList: false,
|
|
@@ -1103,6 +1106,7 @@ function parseArgs(argv) {
|
|
|
1103
1106
|
debug: false,
|
|
1104
1107
|
profile: false,
|
|
1105
1108
|
plugin: "",
|
|
1109
|
+
cdpMaze: false,
|
|
1106
1110
|
cdpPort: 0,
|
|
1107
1111
|
cdpAddress: "",
|
|
1108
1112
|
kittyMode: "off",
|
|
@@ -1136,9 +1140,14 @@ function parseArgs(argv) {
|
|
|
1136
1140
|
}
|
|
1137
1141
|
else if (arg === "--docs" || arg === "--readme") flags.docs = true;
|
|
1138
1142
|
else if (arg === "--export-readme") flags.exportReadme = true;
|
|
1143
|
+
else if (arg === "--export-cdp-maze") flags.exportCdpMaze = true;
|
|
1139
1144
|
else if (arg === "--changelog") flags.changelog = true;
|
|
1140
1145
|
else if (arg === "--testapp.md") flags.testapp = true;
|
|
1141
1146
|
else if (arg === "--demo-list") flags.demoList = true;
|
|
1147
|
+
else if (arg === "--cdp-maze") {
|
|
1148
|
+
flags.cdpMaze = true;
|
|
1149
|
+
flags.demo = { option: arg, filename: "maze.md", asset: "demos/maze.md" };
|
|
1150
|
+
}
|
|
1142
1151
|
else if (arg === "--demo") {
|
|
1143
1152
|
flags.demo = { option: arg, filename: "testapp.md", asset: "testapp.md" };
|
|
1144
1153
|
}
|
|
@@ -1178,6 +1187,8 @@ function parseArgs(argv) {
|
|
|
1178
1187
|
}
|
|
1179
1188
|
}
|
|
1180
1189
|
|
|
1190
|
+
if (flags.cdpMaze && !flags.cdpPort) flags.cdpPort = 9222;
|
|
1191
|
+
|
|
1181
1192
|
return { flags, files };
|
|
1182
1193
|
}
|
|
1183
1194
|
|
|
@@ -1220,6 +1231,8 @@ Settings:
|
|
|
1220
1231
|
List all setting names and defaults, then exit.
|
|
1221
1232
|
|
|
1222
1233
|
CDP:
|
|
1234
|
+
--cdp-maze
|
|
1235
|
+
Open the maze demo, start CDP on localhost:9222, and solve it automatically
|
|
1223
1236
|
--remote-debugging-port=PORT
|
|
1224
1237
|
Start CDP (Chrome DevTools Protocol) server on PORT at launch
|
|
1225
1238
|
--remote-debugging-address=ADDRESS
|
|
@@ -1234,6 +1247,8 @@ Information:
|
|
|
1234
1247
|
Show ${pkg.name}'s README.md & exit
|
|
1235
1248
|
--export-readme
|
|
1236
1249
|
Write or overwrite ./README.md with the bundled README.md & exit
|
|
1250
|
+
--export-cdp-maze
|
|
1251
|
+
Write or overwrite ./cdp-maze.js with the bundled CDP maze solver & exit
|
|
1237
1252
|
--changelog
|
|
1238
1253
|
Show CHANGELOG.md & exit
|
|
1239
1254
|
-profile, --profile
|
|
@@ -8003,6 +8018,12 @@ async function exportReadme() {
|
|
|
8003
8018
|
console.log(`Wrote ${readmePath}`);
|
|
8004
8019
|
}
|
|
8005
8020
|
|
|
8021
|
+
async function exportCdpMaze() {
|
|
8022
|
+
const outputPath = resolve("cdp-maze.js");
|
|
8023
|
+
await Bun.write(outputPath, await bundledMarkdownSource("cdp-maze.js"));
|
|
8024
|
+
console.log(`Wrote ${outputPath}`);
|
|
8025
|
+
}
|
|
8026
|
+
|
|
8006
8027
|
async function printChangelogDocs() {
|
|
8007
8028
|
const changelog = readInternalAssetText("CHANGELOG.md") ?? await Bun.file(join(REPO_ROOT, "CHANGELOG.md")).text();
|
|
8008
8029
|
process.stdout.write(Bun.markdown.ansi(changelog, { hyperlinks: true }));
|
|
@@ -8124,6 +8145,10 @@ async function main() {
|
|
|
8124
8145
|
await exportReadme();
|
|
8125
8146
|
return;
|
|
8126
8147
|
}
|
|
8148
|
+
if (flags.exportCdpMaze) {
|
|
8149
|
+
await exportCdpMaze();
|
|
8150
|
+
return;
|
|
8151
|
+
}
|
|
8127
8152
|
if (flags.changelog) {
|
|
8128
8153
|
await printChangelogDocs();
|
|
8129
8154
|
return;
|
|
@@ -8370,6 +8395,23 @@ async function main() {
|
|
|
8370
8395
|
if (flags.cdpAddress) cdpArgs.push(`--address=${flags.cdpAddress}`);
|
|
8371
8396
|
await app.handleCommand(`cdp ${cdpArgs.join(" ")}`);
|
|
8372
8397
|
}
|
|
8398
|
+
|
|
8399
|
+
if (flags.cdpMaze) {
|
|
8400
|
+
const solverHost = !flags.cdpAddress || flags.cdpAddress === "0.0.0.0"
|
|
8401
|
+
? "127.0.0.1"
|
|
8402
|
+
: flags.cdpAddress;
|
|
8403
|
+
const solverUrl = `ws://${solverHost}:${flags.cdpPort}/devtools/browser/cdp-server`;
|
|
8404
|
+
setTimeout(async () => {
|
|
8405
|
+
try {
|
|
8406
|
+
const { runCdpMaze } = await import("../cdp-maze.js");
|
|
8407
|
+
app.message = await runCdpMaze(solverUrl);
|
|
8408
|
+
app.render?.();
|
|
8409
|
+
} catch (error) {
|
|
8410
|
+
app.message = `CDP maze solver failed: ${error?.message || error}`;
|
|
8411
|
+
app.render?.();
|
|
8412
|
+
}
|
|
8413
|
+
}, 3000);
|
|
8414
|
+
}
|
|
8373
8415
|
|
|
8374
8416
|
if (flags.profile) {
|
|
8375
8417
|
addCheckpoint("Clipboard Probing");
|
|
@@ -8837,9 +8879,6 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
|
|
|
8837
8879
|
}
|
|
8838
8880
|
|
|
8839
8881
|
|
|
8840
|
-
mainPromise.then(r=>{
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
8882
|
main().catch((error) => {
|
|
8844
8883
|
try {
|
|
8845
8884
|
(_activeTtyStream ?? process.stdin).setRawMode?.(false);
|
|
@@ -8849,6 +8888,3 @@ main().catch((error) => {
|
|
|
8849
8888
|
process.exit(1);
|
|
8850
8889
|
}
|
|
8851
8890
|
});
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
})
|
package/src/lua/engine.js
CHANGED
|
@@ -2,7 +2,6 @@ import { existsSync } from "node:fs";
|
|
|
2
2
|
import { readInternalAssetBytes } from "../runtime/assets.js";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
|
-
import { isCompiledBinary, resolveCompiledBaseDir } from "../runtime/compiled.js";
|
|
6
5
|
import { REPO_ROOT } from "../../single-exe/compiled.js";
|
|
7
6
|
|
|
8
7
|
export async function createLuaEngine() {
|
|
@@ -44,9 +43,7 @@ async function resolveLuaWasmLocation() {
|
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
const fallbackPath =
|
|
48
|
-
? join(resolveCompiledBaseDir({ argv: process.argv }), wasmAssetPath)
|
|
49
|
-
: join(REPO_ROOT, wasmAssetPath);
|
|
46
|
+
const fallbackPath = join(REPO_ROOT, wasmAssetPath);
|
|
50
47
|
|
|
51
48
|
return existsSync(fallbackPath) ? fallbackPath : undefined;
|
|
52
49
|
}
|