buninu 0.3.0 → 0.4.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.
Files changed (142) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +334 -37
  3. package/apps/bunmsh/CHANGELOG.md +262 -0
  4. package/apps/bunmsh/LICENSE +21 -0
  5. package/apps/bunmsh/LICENSE-MKSH +295 -0
  6. package/apps/bunmsh/PORTING.md +192 -0
  7. package/apps/bunmsh/README.md +801 -0
  8. package/apps/bunmsh/bunmsh +7 -0
  9. package/apps/bunmsh/bunshell.md +61 -0
  10. package/apps/bunmsh/help/README.md +1945 -0
  11. package/apps/bunmsh/help/alias.md +22 -0
  12. package/apps/bunmsh/help/basename.md +38 -0
  13. package/apps/bunmsh/help/builtin.md +25 -0
  14. package/apps/bunmsh/help/bun.md +27 -0
  15. package/apps/bunmsh/help/bunmsh.md +47 -0
  16. package/apps/bunmsh/help/cat.md +31 -0
  17. package/apps/bunmsh/help/catfancy.md +35 -0
  18. package/apps/bunmsh/help/cd.md +22 -0
  19. package/apps/bunmsh/help/chmod.md +27 -0
  20. package/apps/bunmsh/help/clear.md +21 -0
  21. package/apps/bunmsh/help/colon.md +22 -0
  22. package/apps/bunmsh/help/command.md +27 -0
  23. package/apps/bunmsh/help/cp.md +28 -0
  24. package/apps/bunmsh/help/cut.md +25 -0
  25. package/apps/bunmsh/help/date.md +25 -0
  26. package/apps/bunmsh/help/dirname.md +25 -0
  27. package/apps/bunmsh/help/echo.md +25 -0
  28. package/apps/bunmsh/help/env.md +27 -0
  29. package/apps/bunmsh/help/eval.md +22 -0
  30. package/apps/bunmsh/help/exec.md +22 -0
  31. package/apps/bunmsh/help/exit.md +21 -0
  32. package/apps/bunmsh/help/export.md +22 -0
  33. package/apps/bunmsh/help/false.md +22 -0
  34. package/apps/bunmsh/help/find.md +33 -0
  35. package/apps/bunmsh/help/getopts.md +28 -0
  36. package/apps/bunmsh/help/grep.md +34 -0
  37. package/apps/bunmsh/help/head.md +27 -0
  38. package/apps/bunmsh/help/kill.md +27 -0
  39. package/apps/bunmsh/help/ln.md +28 -0
  40. package/apps/bunmsh/help/ls.md +37 -0
  41. package/apps/bunmsh/help/lsbun.md +29 -0
  42. package/apps/bunmsh/help/lsfancy.md +37 -0
  43. package/apps/bunmsh/help/md5sum.md +21 -0
  44. package/apps/bunmsh/help/mkdir.md +27 -0
  45. package/apps/bunmsh/help/mktemp.md +26 -0
  46. package/apps/bunmsh/help/mv.md +26 -0
  47. package/apps/bunmsh/help/print.md +30 -0
  48. package/apps/bunmsh/help/printf.md +25 -0
  49. package/apps/bunmsh/help/pwd.md +21 -0
  50. package/apps/bunmsh/help/read.md +27 -0
  51. package/apps/bunmsh/help/readonly.md +27 -0
  52. package/apps/bunmsh/help/realpath.md +21 -0
  53. package/apps/bunmsh/help/rm.md +33 -0
  54. package/apps/bunmsh/help/rmdir.md +27 -0
  55. package/apps/bunmsh/help/sed.md +29 -0
  56. package/apps/bunmsh/help/seq.md +29 -0
  57. package/apps/bunmsh/help/serve.md +50 -0
  58. package/apps/bunmsh/help/set.md +26 -0
  59. package/apps/bunmsh/help/sha256sum.md +21 -0
  60. package/apps/bunmsh/help/shift.md +23 -0
  61. package/apps/bunmsh/help/sleep.md +25 -0
  62. package/apps/bunmsh/help/sort.md +29 -0
  63. package/apps/bunmsh/help/source.md +23 -0
  64. package/apps/bunmsh/help/tab.md +30 -0
  65. package/apps/bunmsh/help/tac.md +29 -0
  66. package/apps/bunmsh/help/tail.md +27 -0
  67. package/apps/bunmsh/help/tee.md +25 -0
  68. package/apps/bunmsh/help/test.md +30 -0
  69. package/apps/bunmsh/help/time.md +29 -0
  70. package/apps/bunmsh/help/touch.md +26 -0
  71. package/apps/bunmsh/help/tr.md +26 -0
  72. package/apps/bunmsh/help/true.md +22 -0
  73. package/apps/bunmsh/help/type.md +21 -0
  74. package/apps/bunmsh/help/umask.md +22 -0
  75. package/apps/bunmsh/help/unalias.md +28 -0
  76. package/apps/bunmsh/help/uname.md +31 -0
  77. package/apps/bunmsh/help/unset.md +28 -0
  78. package/apps/bunmsh/help/wc.md +27 -0
  79. package/apps/bunmsh/help/whence.md +26 -0
  80. package/apps/bunmsh/help/which.md +41 -0
  81. package/apps/bunmsh/help/yes.md +23 -0
  82. package/apps/bunmsh/package.json +26 -0
  83. package/apps/bunmsh/serve.js +468 -0
  84. package/apps/bunmsh/single-exe/README.md +402 -0
  85. package/apps/bunmsh/single-exe/assetsHelper.js +371 -0
  86. package/apps/bunmsh/single-exe/assetsLoader.mjs +85 -0
  87. package/apps/bunmsh/single-exe/assetsPacker.js +218 -0
  88. package/apps/bunmsh/single-exe/compiled.js +527 -0
  89. package/apps/bunmsh/single-exe/entry.mjs +17 -0
  90. package/apps/bunmsh/src/cat-fancy.js +117 -0
  91. package/apps/bunmsh/src/cat-operands.js +31 -0
  92. package/apps/bunmsh/src/completion.js +247 -0
  93. package/apps/bunmsh/src/environment.js +61 -0
  94. package/apps/bunmsh/src/fancy-ls.js +186 -0
  95. package/apps/bunmsh/src/find.js +119 -0
  96. package/apps/bunmsh/src/history.js +198 -0
  97. package/apps/bunmsh/src/main.js +776 -0
  98. package/apps/bunmsh/src/mouse.js +103 -0
  99. package/apps/bunmsh/src/shell.js +3557 -0
  100. package/apps/bunmsh/test/catfancy.test.js +102 -0
  101. package/apps/bunmsh/test/reference-source.sh +1 -0
  102. package/apps/bunmsh/test/reference.test.js +677 -0
  103. package/apps/bunmsh/test/serve/README.md +3 -0
  104. package/apps/bunmsh/test/serve/bad.json +1 -0
  105. package/apps/bunmsh/test/serve/data.json +1 -0
  106. package/apps/bunmsh/test/serve/data.json5 +1 -0
  107. package/apps/bunmsh/test/serve/data.jsonc +2 -0
  108. package/apps/bunmsh/test/serve/data.jsonl +2 -0
  109. package/apps/bunmsh/test/serve/data.ndjson +1 -0
  110. package/apps/bunmsh/test/serve/data.toml +3 -0
  111. package/apps/bunmsh/test/serve/data.ts +15 -0
  112. package/apps/bunmsh/test/serve/data.xml +1 -0
  113. package/apps/bunmsh/test/serve/data.yaml +3 -0
  114. package/apps/bunmsh/test/serve/data.yml +1 -0
  115. package/apps/bunmsh/test/serve.test.js +241 -0
  116. package/apps/bunmsh/test/shell.test.js +1778 -0
  117. package/apps/bunmsh/test/t.sh +12 -0
  118. package/apps/cmdlist +6 -1
  119. package/apps/jsgotty/gotty.js +65 -76
  120. package/apps/jsgotty/package.json +6 -1
  121. package/apps/jsgotty/single-exe/README.md +358 -54
  122. package/apps/jsgotty/single-exe/assetsHelper.js +293 -12
  123. package/apps/jsgotty/single-exe/assetsLoader.mjs +1 -1
  124. package/apps/jsgotty/single-exe/assetsPacker.js +218 -0
  125. package/apps/jsgotty/single-exe/compiled.js +379 -19
  126. package/apps/jsgotty/single-exe/entry.mjs +16 -2
  127. package/apps/jsgotty/zmodem-node.js +14 -12
  128. package/apps/jsmdcui/CHANGELOG.md +56 -0
  129. package/apps/jsmdcui/package.json +12 -2
  130. package/apps/jsmdcui/single-exe/README.md +298 -375
  131. package/apps/jsmdcui/single-exe/assetsHelper.js +198 -18
  132. package/apps/jsmdcui/single-exe/assetsLoader.mjs +1 -1
  133. package/apps/jsmdcui/single-exe/assetsPacker.js +218 -0
  134. package/apps/jsmdcui/single-exe/compiled.js +291 -5
  135. package/apps/jsmdcui/single-exe/entry.mjs +7 -0
  136. package/apps/jsmdcui/src/index.js +11 -8
  137. package/apps/jsmdcui/tests/jmigt.js +16 -0
  138. package/bin/bunmsh.bat +3 -0
  139. package/bin/bunmsh.sh +5 -0
  140. package/bin/init.js +48 -5
  141. package/bin/install.js +541 -0
  142. package/package.json +4 -2
@@ -1,80 +1,384 @@
1
- # This is completely optional
2
- - I still recommend using the methods in the root README.md
3
- - Bun's Android build is currently not supported
1
+ # Single-file executable bootstrap
4
2
 
5
- # Usage
6
- - First run `bun ./packAssets.sh` to bundle the assets into `assets.tar`
7
- - Then run the build script like this
3
+ Bundles a project's runtime assets into one `bun build --compile` binary, so
4
+ the executable carries its own `static/`, templates, documentation and so on.
8
5
 
9
- ```shell
10
- bun build --compile --bytecode --minify ./entry.mjs --outfile=binname
11
- ```
6
+ Assets are declared once, in `package.json`:
12
7
 
13
- - You'll get a binname executable
8
+ ```json
9
+ {
10
+ "assets": ["static", "README.md", "README.en.md"]
11
+ }
12
+ ```
13
+
14
+ Paths are relative to the project root. Folders are packed recursively; only
15
+ regular files are stored, so symlinks and empty folders leave no trace.
16
+
17
+ ## The two back ends
18
+
19
+ The default packs everything into a gzipped `assets.tar` that is embedded as a
20
+ single file and unpacked into memory at startup. Setting `ASSETS_BUNFS=1` at
21
+ build time switches to `bun build --compile --asset`, which stores the files
22
+ individually inside the binary's virtual filesystem instead.
23
+
24
+ Reading code never sees the difference — `assetsHelper` answers the same keys
25
+ either way. The trade is binary size against startup cost:
26
+
27
+ | | tar (default) | `ASSETS_BUNFS=1` |
28
+ | --- | --- | --- |
29
+ | Stored as | one gzipped archive | individual files, uncompressed |
30
+ | Size, for jsgotty's 3.45 MB of assets | **0.70 MB** | 3.45 MB |
31
+ | Work before `main` runs | decode the archive, copy every file into memory | **none** |
32
+ | Startup cost, same assets | ~16 ms | **0 ms** |
33
+ | Resident memory | whole payload, always | only what is read |
34
+ | Maturity | in use | experimental |
35
+
36
+ Text compresses well, so the tar usually wins on size by a wide margin — for
37
+ jsgotty most of the payload is `static/`, whose JavaScript and CSS shrink to a
38
+ fifth. What it costs is paid on every run, whether or not the program touches a
39
+ single asset. Reach for `ASSETS_BUNFS=1` when that startup work, or holding the
40
+ whole payload in memory, matters more than the extra megabytes on disk.
41
+
42
+ ## Adopting it in another project
43
+
44
+ Copy `single-exe/` to the project root. Nothing inside it hard-codes its own
45
+ name or depth, so it can be renamed or moved — but the importing code has to
46
+ follow, so staying at the root is the easy path. Three edits point it at the
47
+ project around it.
48
+
49
+ 1. **`assetsPacker.js`, the first two lines.** They are the only place the
50
+ asset root is written down, and a static import specifier cannot be
51
+ computed, so both must be literal:
52
+
53
+ ```js
54
+ import pkg from "../package.json" with { type: "json" };
55
+ export const ASSETS_ROOT = "..";
56
+ ```
57
+
58
+ 2. **`entry.mjs`, the import of the program.** It names what to start, and
59
+ entry.mjs is the compiled entry point in both back ends:
60
+
61
+ ```js
62
+ await import("../src/index.js");
63
+ ```
64
+
65
+ **Keep the `await`.** It reads like a formality — nothing here needs the
66
+ module object — but together with the `await globalThis.assetsLoaderPromise`
67
+ line above it, it is the only top-level `await` the compiled program writes
68
+ anywhere, and `--bytecode` reads exactly that to decide the program starts
69
+ asynchronously. Turn both into `.then(…)` and the build produces an
70
+ executable that exits 0 without running a line.
71
+ [Why](#the-await-in-entrymjs-is-load-bearing) explains the trap.
72
+
73
+ 3. **`package.json`.** Add the `assets` array shown above.
74
+
75
+ Everything else — `compiled.js`, `assetsHelper.js`, `assetsLoader.mjs` — is
76
+ project independent, with one exception: `buildHtmlBundleImageMap` defaults its
77
+ attribute name to jsmdcui's `data-mdcui-src`, so pass your own.
78
+
79
+ Node's own entry path must not import `assetsLoader.mjs` or `entry.mjs`; those
80
+ are Bun only. `assetsHelper.js` and `compiled.js` run under Node 20.11+.
81
+
82
+ ## Reading assets
83
+
84
+ Import from `assetsHelper.js`. Keys are the same package-relative paths that
85
+ appear in `package.json`.
86
+
87
+ | Function | Returns |
88
+ | --- | --- |
89
+ | `readInternalAssetText(path)` | string, or `null` when not embedded |
90
+ | `readInternalAssetBytes(path)` | `Uint8Array`, or `null` |
91
+ | `readAssetText(path)` | string, falling back to the file on disk |
92
+ | `readAssetBytes(path)` | `Uint8Array`, same fallback |
93
+ | `hasInternalAssets()` | whether this package has anything embedded |
94
+ | `listInternalAssetPaths(prefix?)` | every embedded path under `prefix` |
95
+ | `listInternalAssetDirs(prefix?)` | the immediate child names under `prefix` |
96
+ | `assetPath(...parts)` | joins parts into a key |
97
+ | `getAssetKey(path)` | the key as stored, namespace included |
98
+ | `assetDiskPath(path)` | where the disk fallback reads that key from |
99
+ | `SELF` | this package's namespace, `assets/<name>@<version>` |
100
+
101
+ The read functions are synchronous for embedded assets on both back ends.
102
+ `readAssetText` / `readAssetBytes` are async only because of the disk
103
+ fallback, which is what makes the same code work from a source checkout.
104
+
105
+ **Only the read functions fall back to disk.** The listing functions report
106
+ what is embedded and nothing else, so from a source checkout they return an
107
+ empty array. Branch on `hasInternalAssets()` and read the directory yourself:
108
+
109
+ ```js
110
+ import { readdirSync } from "node:fs";
111
+ import { join } from "node:path";
112
+ import { readAssetText, hasInternalAssets, listInternalAssetDirs } from "../single-exe/assetsHelper.js";
113
+ import { REPO_ROOT } from "../single-exe/compiled.js";
114
+
115
+ // Embedded or on disk, this one already works either way.
116
+ const index = await readAssetText("static/index.html");
117
+
118
+ // Listing does not, so pick the source.
119
+ const staticDirs = hasInternalAssets()
120
+ ? listInternalAssetDirs("static")
121
+ : readdirSync(join(REPO_ROOT, "static"));
122
+ ```
123
+
124
+ ## Building
125
+
126
+ `compiled.js` exports the build entry points. Wire `buildEarlyExit` into the
127
+ CLI and it handles `--build-exe` and `--build-for <target>`:
128
+
129
+ ```js
130
+ import { buildEarlyExit, IS_COMPILED, REPO_ROOT } from "./single-exe/compiled.js";
131
+
132
+ await buildEarlyExit(process.argv, "myapp");
133
+ ```
134
+
135
+ `REPO_ROOT` is the project root in a checkout and the executable's own folder
136
+ once compiled, which is where `--assets-extract` writes. `IS_COMPILED` tells
137
+ the two apart.
138
+
139
+ ### Chaining file/folder assets across dependencies
140
+
141
+ A build first bundles once to write a metafile, then treats every
142
+ `assetsPacker.js` appearing in its `inputs` as a participant. Each package
143
+ vendors its own `single-exe/`, and its `assetsHelper` imports its
144
+ `assetsPacker`, so a packer lands in that graph exactly when the package is
145
+ reachable from the entry point. There is no registration step, and anything
146
+ tree-shaken away brings nothing with it.
147
+
148
+ The packers then run one after another, each adding its files under its own
149
+ `assets/<name>@<version>/` namespace — appending into the shared `assets.tar`
150
+ by default, or copying into the shared `build/assets` tree under
151
+ `ASSETS_BUNFS=1`. Only once every packer has finished does the real compile
152
+ run, so the archive is complete and, by default, compressed by then.
153
+
154
+ That namespace is why `ASSETS_BUNFS=1` stages every package into one fixed
155
+ `build/assets`. `--asset` keeps only the **basename** of the path it is given
156
+ and preserves everything below it, so the folder's own name becomes the root
157
+ inside the binary:
158
+
159
+ ```
160
+ --asset ./build/assets with build/assets/jsgotty@1.1.6/static/index.html
161
+ -> assets/jsgotty@1.1.6/static/index.html
162
+ ```
163
+
164
+ Pass `./build/assets/jsgotty@1.1.6` instead and `build/assets/` is gone: the
165
+ files land at `jsgotty@1.1.6/...` and every package gets its own root. One
166
+ folder named `assets`, everyone copying into it, is what makes the keys come
167
+ out the same as the ones the tar back end writes.
168
+
169
+ `assetsPacker.js` also runs standalone; `--help` documents its flags.
170
+
171
+ ## Why the loader hands over a Promise
172
+
173
+ `entry.mjs` imports `assetsLoader.mjs`, awaits `globalThis.assetsLoaderPromise`,
174
+ and only then imports the main program. The indirection is deliberate: the
175
+ loader carries a Bun-only import,
14
176
 
15
- # Single Executable Intro
177
+ ```js
178
+ import assets from "./assets.tar" with { type: "file" };
179
+ ```
180
+
181
+ and if the main program imported the loader, that dependency would land in its
182
+ module graph. Node could then no longer load the program at all — not even to
183
+ fall back to reading the assets from disk.
184
+
185
+ Keeping the loader behind `entry.mjs` means the main program never mentions it,
186
+ so Node 20.11+ runs the same file directly against on-disk assets while Bun
187
+ runs it through the bootstrap. The Promise is that boundary, not a workaround
188
+ for missing top-level await.
189
+
190
+ `ASSETS_BUNFS=1` sidesteps the question: there is no tar to load, so the build
191
+ writes `.bunfs-entry.mjs` next to `entry.mjs` — the same file with the loader
192
+ import removed — compiles that, and deletes it afterwards. A copy rather than a
193
+ stub, because entry.mjs may do more than import the program: a CommonJS main
194
+ that starts itself from `require.main === module` needs the exported starter
195
+ called instead, and that call has to survive. jsgotty is exactly that case; see
196
+ [below](#the-entry-import-calls-an-exported-starter).
197
+
198
+ ## The `await` in entry.mjs is load-bearing
16
199
 
17
- This folder contains the Bun single-exe bootstrap used by the project.
200
+ `--bytecode` decides whether a program starts asynchronously by looking for a
201
+ top-level `await` **written in some file's own source**. The bundler moves a
202
+ module into a lazy init function whenever something dynamically imports it, and
203
+ a module moved that way takes its top-level `await` with it — what stays behind
204
+ is an `await init_x()` the bundler wrote itself, which does not count. If no
205
+ file wrote one, the executable is evaluated on the synchronous path, its
206
+ suspended startup is dropped, and it exits 0 having run nothing. No error, at
207
+ any `--minify` setting, however small the program.
18
208
 
19
- ## Entry Flow
209
+ `entry.mjs` is immune because it writes its own, and one is enough. Every other
210
+ `await` in the graph — all of `assetsLoader.mjs`'s, for one — sits inside a
211
+ function and cannot help. It is also why bunfs builds compile a copy of that
212
+ file rather than handing the main program to `bun build` directly: a main
213
+ program that reaches top-level await only through its imports writes none of
214
+ its own.
20
215
 
21
- - `entry.mjs` imports `assetsLoader.mjs` first
22
- - `assetsLoader.mjs` loads `assets.tar` with `Bun.Archive` and mounts it as `globalThis.internalAssets`
23
- - `assetsLoaderPromise` is exposed on `globalThis`
24
- - The main program waits for `assetsLoaderPromise` if it exists
216
+ Upstream: `src/bundler/linker_context/postProcessJSChunk.rs` skips exactly the
217
+ modules the bundler moved, still true on oven-sh/bun `main` at 8eb5b6e2b5
218
+ (2026-08-22). Until that changes, treat entry.mjs's `await`s as API.
25
219
 
26
- That keeps the main program bootable even if asset loading reports errors.
220
+ ## Environment variables
27
221
 
28
- ## Assets Loading
222
+ | Variable | When | Effect |
223
+ | --- | --- | --- |
224
+ | `ASSETS_BUNFS=1` | build | use `--asset` instead of the tar |
225
+ | `ASSETS_NO_GZIP=1` | build | leave `assets.tar` uncompressed |
226
+ | `ASSETS_DEBUG=1` | run | print how long unpacking took |
29
227
 
30
- - Bundled assets are loaded sequentially with `await file.bytes()`
31
- - Load failures are collected and printed to `stderr`
32
- - Asset loading never rejects the bootstrap promise
33
- - When loading succeeds, the archive is available through `globalThis.internalAssets`
228
+ ## Runtime flags
34
229
 
35
- ## CLI Flags
230
+ | Flag | Effect |
231
+ | --- | --- |
232
+ | `--assets-list` | print every embedded path and exit |
233
+ | `--assets-extract` | write the assets next to the executable and exit |
234
+ | `--assets-external` | ignore embedded assets and read from disk |
36
235
 
37
- - `--assets-list`
38
- - Lists all entries inside bundled `assets.tar`
39
- - Exits early before the main program starts
236
+ These are handled by `assetsLoader.mjs`, so they exist in tar builds only.
40
237
 
41
- - `--assets-extract`
42
- - Extracts bundled assets to the same directory as the executable
43
- - Exits early before the main program starts
238
+ ## Advanced usage
44
239
 
45
- - `--assets-external`
46
- - Skips loading bundled assets into `globalThis.internalAssets`
47
- - Forces the main program and runtime helpers to use the external file tree
48
- - Keeps the bootstrap alive while leaving `internalAssets` falsy
240
+ ### Forwarding arguments to `bun build`
49
241
 
50
- ## Adapting this folder
242
+ Anything after `--build-exe`, or after the target given to `--build-for`, is
243
+ passed straight through to `bun build`:
51
244
 
52
- This single-exe folder is intended for reuse by other projects. Usually you
53
- only need to edit these project-specific files first:
245
+ ```shell
246
+ bun ./gotty.js --build-exe --sourcemap
247
+ bun ./gotty.js --build-for bun-linux-x64 --sourcemap
248
+ ```
249
+
250
+ Order matters. The flags have to come after the build switch, or Bun consumes
251
+ them before the program ever starts:
252
+
253
+ ```shell
254
+ # Wrong: Bun parses this define before any of your code runs
255
+ bun --define MY_APP=../app.md ./gotty.js --build-exe
54
256
 
55
- - `entry.mjs`
56
- - `packAssets.sh`
257
+ # Right
258
+ bun ./gotty.js --build-exe --define MY_APP=../app.md
259
+ ```
260
+
261
+ ### `--define` with a string value
57
262
 
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.
263
+ **Bun expects `--define` values to be JSON literals, so a string has to arrive
264
+ already quoted.** A bare path is read as an identifier and the build fails or
265
+ inlines something unintended. Quoting it through a shell is awkward, so
266
+ `compiled.js` exports a helper that does it:
61
267
 
62
268
  ```js
63
- const compiledHelper = await import("./single-exe/compiled.js").catch(() => null);
64
- await compiledHelper?.buildEarlyExit?.(process.argv, "my-bin");
269
+ import { buildEarlyExit, stringifyNonPrimitiveDefineValues } from "./single-exe/compiled.js";
270
+
271
+ stringifyNonPrimitiveDefineValues(process.argv, "MY_APP");
272
+ await buildEarlyExit(process.argv, "my-bin");
65
273
  ```
66
274
 
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`.
275
+ Call it before `buildEarlyExit`, once per define name you want treated as a
276
+ string. It rewrites the value in place:
70
277
 
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`.
278
+ ```js
279
+ ["--define", "MY_APP=../app.md"] // what the user typed
280
+ ["--define", 'MY_APP="../app.md"'] // what Bun receives
281
+ ```
282
+
283
+ `--define=MY_APP=../app.md` works the same way. Numbers, booleans, `null` and
284
+ `undefined` are left alone, so only genuine strings get quoted.
285
+
286
+ ### Images from an imported HTML bundle
287
+
288
+ A compiled binary exposes an imported HTML entry as a `homepage` object:
289
+ `homepage.index` is the compiled HTML and `homepage.files` lists the
290
+ content-hashed assets by their path inside the binary. `assetsHelper` can map
291
+ the *original* image references back to those paths, without copying any image
292
+ bytes into memory.
293
+
294
+ Bun rewrites `src` during the build, so keep the original reference in a custom
295
+ attribute of your own choosing:
296
+
297
+ ```html
298
+ <!-- source -->
299
+ <img src="./images/photo.jpg" data-original-image="./images/photo.jpg">
300
+
301
+ <!-- compiled -->
302
+ <img src="/photo-abcd1234.jpg" data-original-image="./images/photo.jpg">
303
+ ```
304
+
305
+ Then build the map once, in a compiled binary only:
76
306
 
77
307
  ```js
78
- const compiledHelper = await import("./single-exe/compiled.js").catch(() => null);
79
- const repoRoot = compiledHelper?.REPO_ROOT
308
+ import { buildHtmlBundleImageMap, canonicalHtmlBundleImageHref } from "../single-exe/assetsHelper.js";
309
+ import { IS_COMPILED } from "../single-exe/compiled.js";
310
+
311
+ const images = IS_COMPILED
312
+ ? await buildHtmlBundleImageMap(homepage, "data-original-image")
313
+ : null;
314
+
315
+ const path = images?.get(canonicalHtmlBundleImageHref(originalHref));
316
+ const bytes = path ? await Bun.file(path).bytes() : null;
317
+ ```
318
+
319
+ `buildHtmlBundleImageMap` returns a `Map` from canonical original reference to
320
+ the path inside the binary; it decodes HTML entities and percent encoding so a
321
+ reference written either way still matches. Reading stays lazy — the bytes are
322
+ only touched when you use the returned path.
323
+
324
+ Lower level, if you already hold a rewritten public path such as
325
+ `/photo-abcd1234.jpg`: `findHtmlBundleAsset(homepage, publicPath, options)`
326
+ finds its `homepage.files` entry, `findHtmlBundleImageAsset()` restricts that
327
+ to `image/*`, and `htmlBundleImageAssetPath()` returns just the path.
328
+
329
+ Never hard-code `/$bunfs/root/` or `B:/~BUN/`; always use the path that
330
+ `homepage.files` reports.
331
+
332
+ ## jsgotty specifics
333
+
334
+ The rest of this file is generic. These names belong to jsgotty itself and are
335
+ not reserved by the bootstrap — an adopting project picks its own.
336
+
337
+ Building is optional; running `bun gotty.js` from a checkout, as the root
338
+ `README.md` describes, remains the usual way to run jsgotty.
339
+
340
+ ```shell
341
+ bun ./gotty.js --build-exe
342
+ bun ./gotty.js --build-for bun-linux-x64
343
+ ```
344
+
345
+ Both produce `jsgt`: `gotty.js` calls `buildEarlyExit(process.argv, "jsgt")`
346
+ from `bootstrap()`, before any CLI parsing, so the build switches exit before
347
+ a server is ever started. Neither works from an already compiled binary.
348
+
349
+ The three packed entries — `static`, `README.md`, `README.en.md` — are what the
350
+ HTTP server hands out: `index.html`, `manifest.json`, `help.md`, the frontend
351
+ JavaScript and CSS, and the two READMEs behind the help pages.
352
+
353
+ ### The entry import calls an exported starter
354
+
355
+ `gotty.js` is CommonJS and starts itself from `require.main === module`. With
356
+ `entry.mjs` as the bundle's entry point that guard is inlined to `false`, so
357
+ the import cannot rely on it and calls the export instead:
358
+
359
+ ```js
360
+ const { bootstrap } = await import("../gotty.js");
361
+
362
+ await bootstrap();
363
+ ```
364
+
365
+ Both back ends go through it: `ASSETS_BUNFS=1` compiles a copy of `entry.mjs`,
366
+ so `gotty.js` is never the entry point and the guard stays `false` in either
367
+ mode. Handing `gotty.js` to `bun build` directly instead would compile it as
368
+ the entry, Bun would rewrite the guard to `import.meta.main` — true — and it
369
+ would start itself; that path is not taken, and `bootstrap` is what starts the
370
+ program in both builds.
371
+
372
+ ### `packAssets.sh`
373
+
374
+ The original implementation, kept as a reference: a hand-written tar of the
375
+ three asset entries, from before `assetsPacker.js` and its `package.json`
376
+ declaration existed. `buildExecutable()` in `compiled.js` is the matching
377
+ build path and still runs it, but `buildExe` now points at `buildExeAssets`,
378
+ so nothing in the current flow calls either. It is the shortest way to see
379
+ what the packer produces, and it still packs an `assets.tar` by hand:
380
+
381
+ ```shell
382
+ bun ./single-exe/packAssets.sh
383
+ bun build --compile --bytecode --minify ./single-exe/entry.mjs --outfile=jsgt
80
384
  ```