bunmicro 1.0.2 → 1.0.3
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 +10 -0
- package/package.json +1 -1
- package/single-exe/README.md +49 -0
- package/single-exe/assets.tar +0 -0
- package/single-exe/assetsLoader.mjs +85 -0
- package/single-exe/entry.mjs +9 -0
- package/single-exe/packAssets.sh +7 -0
- package/src/config/config.js +2 -1
- package/src/index.js +163 -24
- package/src/lua/engine.js +42 -8
- package/src/plugins/js-bridge.js +65 -2
- package/src/plugins/manager.js +56 -1
- package/src/runtime/assets.js +90 -0
- package/src/runtime/compiled.js +25 -0
- package/src/runtime/encodings.js +5 -0
- package/src/runtime/registry.js +39 -3
- package/tests/cmphex3b64.js +95 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.0.3] - 2026-06-27
|
|
4
|
+
- Added hex3 series encodings
|
|
5
|
+
* hex3gz / hex3zst add compressed binary-view encodings
|
|
6
|
+
- Added experimental single-file exe support
|
|
7
|
+
* bundled assets loader
|
|
8
|
+
* internal assets fallback to external file tree when needed
|
|
9
|
+
- Added build helpers for single-exe packaging
|
|
10
|
+
* `--build-exe` runs the asset pack + compile flow
|
|
11
|
+
* `--build-for <target>` runs the same flow with an explicit Bun build target
|
|
12
|
+
|
|
3
13
|
## [1.0.2] - 2026-06-24
|
|
4
14
|
- Added startup profiling flags
|
|
5
15
|
* -profile / --profile
|
package/package.json
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
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
|
|
4
|
+
|
|
5
|
+
# Usage
|
|
6
|
+
- First run `bun ./packAssets.sh` to bundle the assets into `assets.tar`
|
|
7
|
+
- Then run the build script like this
|
|
8
|
+
|
|
9
|
+
```shell
|
|
10
|
+
bun build --compile --bytecode --minify ./entry.mjs --outfile=bmi
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
- You'll get a bmi executable
|
|
14
|
+
|
|
15
|
+
# Single Executable Intro
|
|
16
|
+
|
|
17
|
+
This folder contains the Bun single-exe bootstrap used by the project.
|
|
18
|
+
|
|
19
|
+
## Entry Flow
|
|
20
|
+
|
|
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
|
+
- `../src/index.js` waits for `assetsLoaderPromise` if it exists
|
|
25
|
+
|
|
26
|
+
That keeps the main program bootable even if asset loading reports errors.
|
|
27
|
+
|
|
28
|
+
## Assets Loading
|
|
29
|
+
|
|
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`
|
|
34
|
+
|
|
35
|
+
## CLI Flags
|
|
36
|
+
|
|
37
|
+
- `--assets-list`
|
|
38
|
+
- Lists all entries inside bundled `assets.tar`
|
|
39
|
+
- Exits early before the main program starts
|
|
40
|
+
|
|
41
|
+
- `--assets-extract`
|
|
42
|
+
- Extracts bundled assets to the same directory as the executable
|
|
43
|
+
- Exits early before the main program starts
|
|
44
|
+
|
|
45
|
+
- `--assets-external`
|
|
46
|
+
- Skips loading bundled assets into `globalThis.internalAssets`
|
|
47
|
+
- Forces `../src/index.js` and runtime helpers to use the external file tree
|
|
48
|
+
- Keeps the bootstrap alive while leaving `internalAssets` falsy
|
|
49
|
+
|
|
Binary file
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import assets from "./assets.tar" with { type: "file" };
|
|
2
|
+
import { resolveCompiledBaseDir, isCompiledBinary } from "../src/runtime/compiled.js";
|
|
3
|
+
|
|
4
|
+
const forceExternalAssets = process.argv.includes("--assets-external");
|
|
5
|
+
const debugAssetsLoader = Boolean(process.env.BUNMICRO_DEBUG);
|
|
6
|
+
if (forceExternalAssets) {
|
|
7
|
+
const flagIndex = process.argv.indexOf("--assets-external");
|
|
8
|
+
if (flagIndex >= 0) process.argv.splice(flagIndex, 1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
globalThis.internalAssets = forceExternalAssets ? null : Object.create(null);
|
|
12
|
+
globalThis.assetsLoaderPromise = main(process.argv).catch((error) => {
|
|
13
|
+
console.error("# assets loader failed");
|
|
14
|
+
console.error(error);
|
|
15
|
+
return globalThis.internalAssets;
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
async function main(argv) {
|
|
19
|
+
|
|
20
|
+
if (forceExternalAssets &&
|
|
21
|
+
!argv.includes("--assets-list") &&
|
|
22
|
+
!argv.includes("--assets-extract")) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const startedAt = debugAssetsLoader ? Bun.nanoseconds() : 0;
|
|
27
|
+
|
|
28
|
+
const tarball = await Bun.file(assets).bytes();
|
|
29
|
+
const archive = new Bun.Archive(tarball);
|
|
30
|
+
|
|
31
|
+
await cliEarlyExit(archive, argv);
|
|
32
|
+
|
|
33
|
+
const files = await archive.files();
|
|
34
|
+
const entries = [...files.entries()];
|
|
35
|
+
const assetsByPath = Object.create(null);
|
|
36
|
+
const errors = [];
|
|
37
|
+
|
|
38
|
+
for (const [path, file] of entries) {
|
|
39
|
+
try {
|
|
40
|
+
assetsByPath[path] = await file.bytes();
|
|
41
|
+
} catch (reason) {
|
|
42
|
+
errors.push({ path, reason });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (errors.length > 0) {
|
|
47
|
+
console.error(`# Failed to load ${errors.length} bundled asset(s):`);
|
|
48
|
+
for (const { path, reason } of errors) {
|
|
49
|
+
console.error(`- ${path}`);
|
|
50
|
+
if (reason) {
|
|
51
|
+
console.error(reason);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (debugAssetsLoader) {
|
|
56
|
+
const elapsedMs = (Bun.nanoseconds() - startedAt) / 1e6;
|
|
57
|
+
console.error(`Loaded assets: ${elapsedMs.toFixed(3)} ms`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
globalThis.internalAssets = assetsByPath;
|
|
61
|
+
return assetsByPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function cliEarlyExit(archive, argv) {
|
|
65
|
+
if (argv.includes("--assets-list")) {
|
|
66
|
+
const files = await archive.files();
|
|
67
|
+
for (const path of [...files.keys()].sort()) {
|
|
68
|
+
console.log(path);
|
|
69
|
+
}
|
|
70
|
+
process.exit(0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (argv.includes("--assets-extract")) {
|
|
74
|
+
if (isCompiledBinary(argv))
|
|
75
|
+
{
|
|
76
|
+
const targetDir = resolveCompiledBaseDir({ argv });
|
|
77
|
+
const extracted = await archive.extract(targetDir);
|
|
78
|
+
console.log(`Extracted ${extracted} asset(s) to ${targetDir}`);
|
|
79
|
+
} // if isCompiled
|
|
80
|
+
else
|
|
81
|
+
console.log("Assets extraction should only be used in a Bun compiled single-file executable");
|
|
82
|
+
|
|
83
|
+
process.exit(0);
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/config/config.js
CHANGED
|
@@ -4,6 +4,7 @@ import { homedir } from "node:os";
|
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
//import { Glob } from "bun";
|
|
6
6
|
import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./defaults.js";
|
|
7
|
+
import { isHex3Encoding } from "../runtime/encodings.js";
|
|
7
8
|
|
|
8
9
|
export class Config {
|
|
9
10
|
constructor({ configDir = "" } = {}) {
|
|
@@ -105,7 +106,7 @@ function normalizeSetting(key, value) {
|
|
|
105
106
|
function validateOption(option, value) {
|
|
106
107
|
if (option === "encoding") {
|
|
107
108
|
const encoding = String(value || "utf-8");
|
|
108
|
-
if (encoding
|
|
109
|
+
if (isHex3Encoding(encoding)) return;
|
|
109
110
|
try { new TextDecoder(encoding); }
|
|
110
111
|
catch { throw new Error(`Invalid encoding: ${value}`); }
|
|
111
112
|
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
|
+
let mainPromise = globalThis.assetsLoaderPromise ||
|
|
4
|
+
Promise.resolve();
|
|
5
|
+
|
|
3
6
|
const jsStart = globalThis.Bun ? Bun.nanoseconds() : Date.now() * 1e6;
|
|
4
7
|
const checkpoints = [
|
|
5
8
|
{ name: "Bun Engine Boot", time: 0 },
|
|
@@ -99,6 +102,7 @@ import { Config } from "./config/config.js";
|
|
|
99
102
|
import { defaultAllSettings, OPTION_CHOICES, LOCAL_SETTINGS } from "./config/defaults.js";
|
|
100
103
|
import { cleanConfig } from "./config/clean.js";
|
|
101
104
|
import { RuntimeRegistry, RTColorscheme, RTHelp } from "./runtime/registry.js";
|
|
105
|
+
import { assetPath, hasInternalAssets, listInternalAssetDirs, readInternalAssetText } from "./runtime/assets.js";
|
|
102
106
|
import { PluginManager } from "./plugins/manager.js";
|
|
103
107
|
import { JsPluginManager, buildMicroGlobal, runAction, listActions } from "./plugins/js-bridge.js";
|
|
104
108
|
import { Colorscheme } from "./config/colorscheme.js";
|
|
@@ -113,9 +117,11 @@ import { shellSplit } from "./shell/shell.js";
|
|
|
113
117
|
import { styleToAnsi } from "./display/ansi-style.js";
|
|
114
118
|
import { encodeBinaryToBuffer, decodeBinaryBytes } from "./buffer/fixed3-codec.js";
|
|
115
119
|
import { writeBackup, removeBackup, applyBackup } from "./buffer/backup.js";
|
|
120
|
+
import { isHex3Encoding } from "./runtime/encodings.js";
|
|
116
121
|
import { createInterface } from "node:readline/promises";
|
|
117
122
|
|
|
118
123
|
import pkg from "../package.json" with { type: "json" };
|
|
124
|
+
import { isCompiledBinary, resolveCompiledBaseDir } from "./runtime/compiled.js";
|
|
119
125
|
|
|
120
126
|
|
|
121
127
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -159,7 +165,13 @@ Windows
|
|
|
159
165
|
}
|
|
160
166
|
|
|
161
167
|
const VERSION = pkg.version;
|
|
162
|
-
const
|
|
168
|
+
const IS_COMPILED = isCompiledBinary(process.argv);
|
|
169
|
+
const REPO_ROOT = IS_COMPILED
|
|
170
|
+
? resolveCompiledBaseDir({ argv: process.argv })
|
|
171
|
+
: resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
172
|
+
const SINGLE_EXE_DIR = resolve(REPO_ROOT, "single-exe");
|
|
173
|
+
const SINGLE_EXE_ENTRY = resolve(SINGLE_EXE_DIR, "entry.mjs");
|
|
174
|
+
const DEFAULT_BUILD_OUTFILE = "bmi";
|
|
163
175
|
const decoder = new TextDecoder();
|
|
164
176
|
let _activeTtyStream = null; // set in App.start() for use by the global error handler
|
|
165
177
|
|
|
@@ -253,13 +265,44 @@ function isHttpUrl(value) {
|
|
|
253
265
|
}
|
|
254
266
|
|
|
255
267
|
function decodeTextBytesWithEncoding(bytes, encoding = "utf-8") {
|
|
256
|
-
|
|
257
|
-
|
|
268
|
+
const normalized = normalizeEncodingLabel(encoding);
|
|
269
|
+
if (normalized === "hex3gz") {
|
|
270
|
+
const decoded = decodeHex3Bytes(Bun.gunzipSync(bytes));
|
|
271
|
+
return { ...decoded, encoding: "hex3gz" };
|
|
272
|
+
}
|
|
273
|
+
if (normalized === "hex3zst") {
|
|
274
|
+
const decoded = decodeHex3Bytes(Bun.zstdDecompressSync(bytes));
|
|
275
|
+
return { ...decoded, encoding: "hex3zst" };
|
|
276
|
+
}
|
|
277
|
+
if (normalized === "hex3") {
|
|
278
|
+
return decodeHex3Bytes(bytes);
|
|
258
279
|
}
|
|
259
|
-
const decoder = new TextDecoder(
|
|
280
|
+
const decoder = new TextDecoder(normalized);
|
|
260
281
|
return { text: decoder.decode(bytes), encoding: decoder.encoding };
|
|
261
282
|
}
|
|
262
283
|
|
|
284
|
+
function encodeTextBytesWithEncoding(text, encoding = "utf-8") {
|
|
285
|
+
const normalized = normalizeEncodingLabel(encoding);
|
|
286
|
+
if (normalized === "hex3gz") {
|
|
287
|
+
return Bun.gzipSync(encodeHex3Text(text));
|
|
288
|
+
}
|
|
289
|
+
if (normalized === "hex3zst") {
|
|
290
|
+
return Bun.zstdCompressSync(encodeHex3Text(text));
|
|
291
|
+
}
|
|
292
|
+
if (normalized === "hex3") {
|
|
293
|
+
return encodeHex3Text(text);
|
|
294
|
+
}
|
|
295
|
+
return new TextEncoder().encode(String(text));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function decodeHex3Bytes(bytes) {
|
|
299
|
+
return { text: encodeBinaryToBuffer(bytes).toString("latin1"), encoding: "hex3" };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function encodeHex3Text(text) {
|
|
303
|
+
return decodeBinaryBytes(Buffer.from(text, "latin1"));
|
|
304
|
+
}
|
|
305
|
+
|
|
263
306
|
async function readTextFileWithEncoding(path, encoding = "utf-8") {
|
|
264
307
|
const bytes = new Uint8Array(await Bun.file(path).arrayBuffer());
|
|
265
308
|
return decodeTextBytesWithEncoding(bytes, encoding);
|
|
@@ -272,7 +315,7 @@ async function fetchTextWithEncoding(url, encoding = "utf-8") {
|
|
|
272
315
|
|
|
273
316
|
function normalizeEncodingLabel(encoding = "utf-8") {
|
|
274
317
|
const s = String(encoding || "utf-8");
|
|
275
|
-
if (s
|
|
318
|
+
if (isHex3Encoding(s)) return s.toLowerCase();
|
|
276
319
|
return new TextDecoder(s).encoding;
|
|
277
320
|
}
|
|
278
321
|
|
|
@@ -602,6 +645,8 @@ function parseArgs(argv) {
|
|
|
602
645
|
cat: false,
|
|
603
646
|
docs: false,
|
|
604
647
|
changelog: false,
|
|
648
|
+
buildExe: false,
|
|
649
|
+
buildFor: "",
|
|
605
650
|
configDir: "",
|
|
606
651
|
debug: false,
|
|
607
652
|
profile: false,
|
|
@@ -626,8 +671,16 @@ function parseArgs(argv) {
|
|
|
626
671
|
else if (arg === "--hex3") {
|
|
627
672
|
flags.settings.set("encoding", "hex3");
|
|
628
673
|
}
|
|
674
|
+
else if (arg === "--hex3gz") {
|
|
675
|
+
flags.settings.set("encoding", "hex3gz");
|
|
676
|
+
}
|
|
677
|
+
else if (arg === "--hex3zst") {
|
|
678
|
+
flags.settings.set("encoding", "hex3zst");
|
|
679
|
+
}
|
|
629
680
|
else if (arg === "--docs" || arg === "--readme") flags.docs = true;
|
|
630
681
|
else if (arg === "--changelog") flags.changelog = true;
|
|
682
|
+
else if (arg === "--build-exe") flags.buildExe = true;
|
|
683
|
+
else if (arg === "--build-for") flags.buildFor = argv[++i] ?? "";
|
|
631
684
|
else if (arg === "-debug") flags.debug = true;
|
|
632
685
|
else if (arg === "-profile" || arg === "--profile") flags.profile = true;
|
|
633
686
|
else if (arg === "-config-dir") flags.configDir = argv[++i] ?? "";
|
|
@@ -682,8 +735,9 @@ function usage() {
|
|
|
682
735
|
" Syntax-highlight file(s) and write to stdout, then exit (.md uses Bun.markdown.ansi)\n",
|
|
683
736
|
"--xxd, --hexdump",
|
|
684
737
|
" Hex3 dump file(s) and write to stdout (same as --cat -encoding hex3)\n",
|
|
685
|
-
"--hex3",
|
|
686
|
-
" Set -encoding hex3 for this session
|
|
738
|
+
"--hex3, --hex3gz, --hex3zst",
|
|
739
|
+
" Set -encoding hex3, hex3gz, or hex3zst for this session",
|
|
740
|
+
" hex3 shows raw bytes; gz/zst variants compress the same hex3 view\n",
|
|
687
741
|
"-help, -h, --help",
|
|
688
742
|
" Show this help & exit",
|
|
689
743
|
"-version, -V, --version",
|
|
@@ -701,6 +755,50 @@ function usage() {
|
|
|
701
755
|
].join("\n");
|
|
702
756
|
}
|
|
703
757
|
|
|
758
|
+
async function buildExecutable(target = "") {
|
|
759
|
+
const outfile = resolve(process.cwd(), DEFAULT_BUILD_OUTFILE);
|
|
760
|
+
const normalizedTarget = String(target || "").trim();
|
|
761
|
+
|
|
762
|
+
const steps = [
|
|
763
|
+
{
|
|
764
|
+
label: "Pack assets",
|
|
765
|
+
cwd: SINGLE_EXE_DIR,
|
|
766
|
+
cmd: "bun",
|
|
767
|
+
args: ["./packAssets.sh"],
|
|
768
|
+
},
|
|
769
|
+
{
|
|
770
|
+
label: "Compile executable",
|
|
771
|
+
cwd: process.cwd(),
|
|
772
|
+
cmd: "bun",
|
|
773
|
+
args: [
|
|
774
|
+
"build",
|
|
775
|
+
"--compile",
|
|
776
|
+
"--bytecode",
|
|
777
|
+
"--minify",
|
|
778
|
+
SINGLE_EXE_ENTRY,
|
|
779
|
+
`--outfile=${DEFAULT_BUILD_OUTFILE}`,
|
|
780
|
+
...(normalizedTarget ? [`--target=${normalizedTarget}`] : []),
|
|
781
|
+
],
|
|
782
|
+
},
|
|
783
|
+
];
|
|
784
|
+
|
|
785
|
+
for (const step of steps) {
|
|
786
|
+
console.log('');
|
|
787
|
+
console.log(Bun.markdown.ansi('## '+step.label));
|
|
788
|
+
|
|
789
|
+
const result = child_process.spawnSync(step.cmd, step.args, {
|
|
790
|
+
cwd: step.cwd,
|
|
791
|
+
stdio: "inherit",
|
|
792
|
+
env: process.env,
|
|
793
|
+
});
|
|
794
|
+
if (result.status !== 0) {
|
|
795
|
+
process.exit(result.status ?? 1);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
console.log(`Built executable: ${outfile}`);
|
|
800
|
+
}
|
|
801
|
+
|
|
704
802
|
function parseInput(args) {
|
|
705
803
|
const files = [];
|
|
706
804
|
const command = {
|
|
@@ -1283,9 +1381,9 @@ class BufferModel {
|
|
|
1283
1381
|
if (this._backupRevision === backupRevision) this._backupRequested = false;
|
|
1284
1382
|
this._forceKeepBackup = true;
|
|
1285
1383
|
}
|
|
1286
|
-
if (this.encoding
|
|
1384
|
+
if (isHex3Encoding(this.encoding)) {
|
|
1287
1385
|
try {
|
|
1288
|
-
await Bun.write(targetPath,
|
|
1386
|
+
await Bun.write(targetPath, encodeTextBytesWithEncoding(text, this.encoding));
|
|
1289
1387
|
} finally {
|
|
1290
1388
|
this._forceKeepBackup = false;
|
|
1291
1389
|
}
|
|
@@ -4143,7 +4241,7 @@ class App {
|
|
|
4143
4241
|
if (!force && this.buffer?.readonly) { this.message = "Can't save under readonly mode"; return; }
|
|
4144
4242
|
try {
|
|
4145
4243
|
const enc = normalizeEncodingLabel(this.buffer?.encoding);
|
|
4146
|
-
if (enc !== "utf-8" && enc
|
|
4244
|
+
if (enc !== "utf-8" && !isHex3Encoding(enc)) {
|
|
4147
4245
|
this.openYNPrompt("Save in UTF-8?(y,n)", async (answer) => {
|
|
4148
4246
|
if (answer === "y") await this.saveUtf8();
|
|
4149
4247
|
});
|
|
@@ -4560,7 +4658,8 @@ class App {
|
|
|
4560
4658
|
const saveArgs = [...cmdArgs];
|
|
4561
4659
|
const saveForce = saveArgs[0] === "-f" && (saveArgs.shift(), true);
|
|
4562
4660
|
if (!saveForce && buf?.readonly) { this.message = "Can't save under readonly mode"; break; }
|
|
4563
|
-
|
|
4661
|
+
const bufferEncoding = normalizeEncodingLabel(buf?.encoding);
|
|
4662
|
+
if (saveArgs.length > 0 && bufferEncoding !== "utf-8" && !isHex3Encoding(bufferEncoding)) {
|
|
4564
4663
|
const target = resolve(expandHome(saveArgs[0]));
|
|
4565
4664
|
this.openYNPrompt("Save in UTF-8?(y,n)", async (answer) => {
|
|
4566
4665
|
if (answer === "y") {
|
|
@@ -5435,14 +5534,14 @@ const COMMAND_NAMES = [
|
|
|
5435
5534
|
];
|
|
5436
5535
|
|
|
5437
5536
|
const SUPPORTED_ENCODING_LABELS = [
|
|
5438
|
-
"hex3",
|
|
5537
|
+
"hex3", "hex3gz", "hex3zst",
|
|
5439
5538
|
"utf-8", "utf-16le", "utf-16be",
|
|
5440
5539
|
"windows-1252", "iso-8859-1", "latin1",
|
|
5441
5540
|
"big5", "gbk", "gb18030",
|
|
5442
5541
|
"shift_jis", "sjis", "euc-jp", "iso-2022-jp",
|
|
5443
5542
|
"euc-kr", "ks_c_5601-1987",
|
|
5444
5543
|
].filter((encoding) => {
|
|
5445
|
-
if (encoding
|
|
5544
|
+
if (isHex3Encoding(encoding)) return true;
|
|
5446
5545
|
try { new TextDecoder(encoding); return true; }
|
|
5447
5546
|
catch { return false; }
|
|
5448
5547
|
});
|
|
@@ -6703,18 +6802,38 @@ async function loadBuffers(files, command) {
|
|
|
6703
6802
|
}
|
|
6704
6803
|
|
|
6705
6804
|
async function printReadmeDocs() {
|
|
6706
|
-
const readme = await Bun.file(join(REPO_ROOT, "README.md")).text();
|
|
6805
|
+
const readme = readInternalAssetText("README.md") ?? await Bun.file(join(REPO_ROOT, "README.md")).text();
|
|
6707
6806
|
process.stdout.write(Bun.markdown.ansi(readme, { hyperlinks: true }));
|
|
6708
6807
|
}
|
|
6709
6808
|
|
|
6710
6809
|
async function printChangelogDocs() {
|
|
6711
|
-
const changelog = await Bun.file(join(REPO_ROOT, "CHANGELOG.md")).text();
|
|
6810
|
+
const changelog = readInternalAssetText("CHANGELOG.md") ?? await Bun.file(join(REPO_ROOT, "CHANGELOG.md")).text();
|
|
6712
6811
|
process.stdout.write(Bun.markdown.ansi(changelog, { hyperlinks: true }));
|
|
6713
6812
|
}
|
|
6714
6813
|
|
|
6715
6814
|
async function main() {
|
|
6716
6815
|
addCheckpoint("Argument Parsing");
|
|
6717
6816
|
const { flags, files: rawFiles } = parseArgs(process.argv.slice(2));
|
|
6817
|
+
if (flags.buildExe && flags.buildFor) {
|
|
6818
|
+
console.error("--build-exe and --build-for are separate paths; use only one");
|
|
6819
|
+
process.exit(1);
|
|
6820
|
+
}
|
|
6821
|
+
if (flags.buildExe) {
|
|
6822
|
+
if (IS_COMPILED) {
|
|
6823
|
+
console.error("--build-exe is only available in the source tree");
|
|
6824
|
+
process.exit(1);
|
|
6825
|
+
}
|
|
6826
|
+
await buildExecutable();
|
|
6827
|
+
return;
|
|
6828
|
+
}
|
|
6829
|
+
if (flags.buildFor) {
|
|
6830
|
+
if (IS_COMPILED) {
|
|
6831
|
+
console.error("--build-for is only available in the source tree");
|
|
6832
|
+
process.exit(1);
|
|
6833
|
+
}
|
|
6834
|
+
await buildExecutable(flags.buildFor);
|
|
6835
|
+
return;
|
|
6836
|
+
}
|
|
6718
6837
|
if (flags.help) {
|
|
6719
6838
|
console.log(usage());
|
|
6720
6839
|
return;
|
|
@@ -6783,17 +6902,30 @@ async function main() {
|
|
|
6783
6902
|
|
|
6784
6903
|
if (flags.plugin === "list") {
|
|
6785
6904
|
const luaList = plugins.list();
|
|
6786
|
-
const jsDirs = [
|
|
6787
|
-
{ dir: join(REPO_ROOT, "runtime", "jsplugins"), builtin: true },
|
|
6788
|
-
{ dir: join(config.configDir, "jsplug"), builtin: false },
|
|
6789
|
-
];
|
|
6790
6905
|
const jsItems = [];
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6906
|
+
const builtinJsPluginNames = hasInternalAssets()
|
|
6907
|
+
? listInternalAssetDirs(assetPath("runtime", "jsplugins"))
|
|
6908
|
+
: [];
|
|
6909
|
+
if (builtinJsPluginNames.length > 0) {
|
|
6910
|
+
for (const name of builtinJsPluginNames) {
|
|
6911
|
+
jsItems.push({ name, builtin: true });
|
|
6912
|
+
}
|
|
6913
|
+
} else {
|
|
6914
|
+
const builtinJsDir = join(REPO_ROOT, "runtime", "jsplugins");
|
|
6915
|
+
if (existsSync(builtinJsDir)) {
|
|
6916
|
+
for (const entry of readdirSync(builtinJsDir, { withFileTypes: true })) {
|
|
6917
|
+
if (!entry.isDirectory()) continue;
|
|
6918
|
+
if (existsSync(join(builtinJsDir, entry.name, `${entry.name}.js`)))
|
|
6919
|
+
jsItems.push({ name: entry.name, builtin: true });
|
|
6920
|
+
}
|
|
6921
|
+
}
|
|
6922
|
+
}
|
|
6923
|
+
const userJsDir = join(config.configDir, "jsplug");
|
|
6924
|
+
if (existsSync(userJsDir)) {
|
|
6925
|
+
for (const entry of readdirSync(userJsDir, { withFileTypes: true })) {
|
|
6794
6926
|
if (!entry.isDirectory()) continue;
|
|
6795
|
-
if (existsSync(join(
|
|
6796
|
-
jsItems.push({ name: entry.name, builtin });
|
|
6927
|
+
if (existsSync(join(userJsDir, entry.name, `${entry.name}.js`)))
|
|
6928
|
+
jsItems.push({ name: entry.name, builtin: false });
|
|
6797
6929
|
}
|
|
6798
6930
|
}
|
|
6799
6931
|
const fmtTag = (p) => p.builtin ? " *(built-in)*" : "";
|
|
@@ -7380,6 +7512,10 @@ async function catFiles(files, colorscheme, syntaxDefinitions, encoding = DEFAUL
|
|
|
7380
7512
|
}
|
|
7381
7513
|
}
|
|
7382
7514
|
|
|
7515
|
+
|
|
7516
|
+
mainPromise.then(r=>{
|
|
7517
|
+
|
|
7518
|
+
|
|
7383
7519
|
main().catch((error) => {
|
|
7384
7520
|
try {
|
|
7385
7521
|
(_activeTtyStream ?? process.stdin).setRawMode?.(false);
|
|
@@ -7389,3 +7525,6 @@ main().catch((error) => {
|
|
|
7389
7525
|
process.exit(1);
|
|
7390
7526
|
}
|
|
7391
7527
|
});
|
|
7528
|
+
|
|
7529
|
+
|
|
7530
|
+
})
|
package/src/lua/engine.js
CHANGED
|
@@ -1,21 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readInternalAssetBytes } from "../runtime/assets.js";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { isCompiledBinary, resolveCompiledBaseDir } from "../runtime/compiled.js";
|
|
7
|
+
|
|
8
|
+
const SOURCE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
9
|
+
|
|
1
10
|
export async function createLuaEngine() {
|
|
2
|
-
|
|
11
|
+
let wasmoon = null;
|
|
12
|
+
try {
|
|
13
|
+
wasmoon = await import("wasmoon");
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (
|
|
16
|
+
error?.code !== "ERR_MODULE_NOT_FOUND" &&
|
|
17
|
+
!/Cannot find package/.test(String(error?.message))
|
|
18
|
+
) {
|
|
19
|
+
throw error;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
3
22
|
if (!wasmoon?.LuaFactory) {
|
|
4
23
|
throw new Error("Lua support requires the WASM runtime `wasmoon`. Install it with `bun add wasmoon`.");
|
|
5
24
|
}
|
|
6
25
|
|
|
7
|
-
const
|
|
26
|
+
const wasmLocation = await resolveLuaWasmLocation();
|
|
27
|
+
const factory = new wasmoon.LuaFactory(wasmLocation);
|
|
8
28
|
const lua = await factory.createEngine();
|
|
9
29
|
return new WasmoonEngine(lua);
|
|
10
30
|
}
|
|
11
31
|
|
|
12
|
-
async function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
async function resolveLuaWasmLocation() {
|
|
33
|
+
const wasmBytes = readInternalAssetBytes("node_modules/wasmoon/dist/glue.wasm");
|
|
34
|
+
if (wasmBytes) {
|
|
35
|
+
const wasmPath = join(tmpdir(), "bunmicro-wasmoon-glue.wasm");
|
|
36
|
+
try {
|
|
37
|
+
const existing = await Bun.file(wasmPath).bytes().catch(() => null);
|
|
38
|
+
if (!existing || existing.byteLength !== wasmBytes.byteLength) {
|
|
39
|
+
await Bun.write(wasmPath, wasmBytes);
|
|
40
|
+
}
|
|
41
|
+
return wasmPath;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error("# failed to stage bundled wasmoon wasm");
|
|
44
|
+
console.error(error);
|
|
45
|
+
}
|
|
18
46
|
}
|
|
47
|
+
|
|
48
|
+
const fallbackPath = isCompiledBinary(process.argv)
|
|
49
|
+
? join(resolveCompiledBaseDir({ argv: process.argv }), "node_modules", "wasmoon", "dist", "glue.wasm")
|
|
50
|
+
: join(SOURCE_ROOT, "node_modules", "wasmoon", "dist", "glue.wasm");
|
|
51
|
+
|
|
52
|
+
return existsSync(fallbackPath) ? fallbackPath : undefined;
|
|
19
53
|
}
|
|
20
54
|
|
|
21
55
|
class WasmoonEngine {
|
package/src/plugins/js-bridge.js
CHANGED
|
@@ -1,15 +1,50 @@
|
|
|
1
|
-
import { existsSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
2
2
|
import { readdir } from "node:fs/promises";
|
|
3
|
-
import { join, basename, extname } from "node:path";
|
|
3
|
+
import { dirname, join, basename, extname } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { assetPath, hasInternalAssets, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetBytes } from "../runtime/assets.js";
|
|
4
7
|
import { newMessage, newMessageAtLine, MTError, MTWarning, MTInfo } from "../buffer/message.js";
|
|
5
8
|
import { Loc } from "../buffer/loc.js";
|
|
6
9
|
|
|
7
10
|
// ── Action registry ──────────────────────────────────────────────────────────
|
|
8
11
|
|
|
9
12
|
const ACTIONS = new Map();
|
|
13
|
+
const INTERNAL_JSPLUGIN_STAGE_ROOT = join(tmpdir(), "bunmicro-jsplugins");
|
|
14
|
+
let internalJsPluginStagePromise = null;
|
|
10
15
|
|
|
11
16
|
function reg(name, fn) { ACTIONS.set(name, fn); }
|
|
12
17
|
|
|
18
|
+
function stageInternalJsPlugins() {
|
|
19
|
+
if (!internalJsPluginStagePromise) {
|
|
20
|
+
internalJsPluginStagePromise = _stageInternalJsPlugins().catch((error) => {
|
|
21
|
+
console.error("# failed to stage internal JS plugins");
|
|
22
|
+
console.error(error);
|
|
23
|
+
return null;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
return internalJsPluginStagePromise;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function _stageInternalJsPlugins() {
|
|
30
|
+
const prefix = assetPath("runtime", "jsplugins");
|
|
31
|
+
const paths = listInternalAssetPaths(prefix);
|
|
32
|
+
if (paths.length === 0) return null;
|
|
33
|
+
|
|
34
|
+
mkdirSync(INTERNAL_JSPLUGIN_STAGE_ROOT, { recursive: true });
|
|
35
|
+
await Bun.write(join(INTERNAL_JSPLUGIN_STAGE_ROOT, "package.json"), JSON.stringify({ type: "module" }));
|
|
36
|
+
|
|
37
|
+
for (const assetPathName of paths) {
|
|
38
|
+
const bytes = readInternalAssetBytes(assetPathName);
|
|
39
|
+
if (!bytes) continue;
|
|
40
|
+
const stagedPath = join(INTERNAL_JSPLUGIN_STAGE_ROOT, ...assetPathName.split("/"));
|
|
41
|
+
mkdirSync(dirname(stagedPath), { recursive: true });
|
|
42
|
+
await Bun.write(stagedPath, bytes);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return INTERNAL_JSPLUGIN_STAGE_ROOT;
|
|
46
|
+
}
|
|
47
|
+
|
|
13
48
|
function _actIndentStr(buf) {
|
|
14
49
|
if (buf?.Settings?.tabstospaces) return " ".repeat(buf?.Settings?.tabsize ?? 4);
|
|
15
50
|
return "\t";
|
|
@@ -550,6 +585,10 @@ export class JsPluginManager {
|
|
|
550
585
|
// Scan and load all JS plugins from given directories
|
|
551
586
|
async loadFrom(dirs) {
|
|
552
587
|
for (const { dir, builtin } of dirs) {
|
|
588
|
+
if (builtin && hasInternalAssets()) {
|
|
589
|
+
const loadedFromAssets = await this._loadFromInternalAssets(dir, builtin);
|
|
590
|
+
if (loadedFromAssets) continue;
|
|
591
|
+
}
|
|
553
592
|
if (!existsSync(dir)) continue;
|
|
554
593
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
555
594
|
for (const entry of entries) {
|
|
@@ -562,6 +601,30 @@ export class JsPluginManager {
|
|
|
562
601
|
}
|
|
563
602
|
}
|
|
564
603
|
|
|
604
|
+
async _loadFromInternalAssets(dir, builtin) {
|
|
605
|
+
const prefix = assetPath("runtime", "jsplugins");
|
|
606
|
+
const stageRoot = await stageInternalJsPlugins();
|
|
607
|
+
if (!stageRoot) return false;
|
|
608
|
+
|
|
609
|
+
const pluginNames = listInternalAssetDirs(prefix);
|
|
610
|
+
if (pluginNames.length === 0) return false;
|
|
611
|
+
|
|
612
|
+
let loadedAny = false;
|
|
613
|
+
for (const pluginName of pluginNames) {
|
|
614
|
+
const stagedMainPath = join(stageRoot, prefix, pluginName, `${pluginName}.js`);
|
|
615
|
+
if (!existsSync(stagedMainPath)) continue;
|
|
616
|
+
try {
|
|
617
|
+
await import(pathToFileURL(stagedMainPath).href);
|
|
618
|
+
this._loaded.push({ path: assetPath(prefix, pluginName, `${pluginName}.js`), name: pluginName, builtin, loaded: true });
|
|
619
|
+
loadedAny = true;
|
|
620
|
+
} catch (e) {
|
|
621
|
+
this._loaded.push({ path: assetPath(prefix, pluginName, `${pluginName}.js`), name: pluginName, builtin, loaded: false, error: e.message });
|
|
622
|
+
console.error(`[jsplugin] failed to load ${pluginName}: ${e.message}`);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return loadedAny;
|
|
626
|
+
}
|
|
627
|
+
|
|
565
628
|
async _loadFile(path, name, builtin) {
|
|
566
629
|
try {
|
|
567
630
|
await import(path);
|
package/src/plugins/manager.js
CHANGED
|
@@ -4,6 +4,7 @@ import { basename, dirname, extname, join, sep } from "node:path";
|
|
|
4
4
|
import { fetchHttp, downloadFile } from "../platform/commands.js";
|
|
5
5
|
import { extractAndStrip } from "../platform/archive.js";
|
|
6
6
|
import { createLuaEngine } from "../lua/engine.js";
|
|
7
|
+
import { assetPath, hasInternalAssets, internalAssetSource, listInternalAssetDirs, listInternalAssetPaths, readInternalAssetText } from "../runtime/assets.js";
|
|
7
8
|
import { execCommand, runBackgroundShell, runCommand } from "../shell/shell.js";
|
|
8
9
|
import { Loc } from "../buffer/loc.js";
|
|
9
10
|
import { BTDefault, BTHelp, BTInfo, BTLog, BTRaw, BTScratch, byteOffset, BufferCore } from "../buffer/buffer.js";
|
|
@@ -107,10 +108,24 @@ export class PluginManager {
|
|
|
107
108
|
}
|
|
108
109
|
|
|
109
110
|
async scanBuiltinPlugins() {
|
|
111
|
+
const userNames = new Set(this.plugins.map((plugin) => plugin.name));
|
|
112
|
+
const internalPrefix = assetPath("runtime", "plugins");
|
|
113
|
+
|
|
114
|
+
if (hasInternalAssets()) {
|
|
115
|
+
const entries = listInternalAssetDirs(internalPrefix);
|
|
116
|
+
if (entries.length > 0) {
|
|
117
|
+
for (const entryName of entries) {
|
|
118
|
+
if (userNames.has(entryName)) continue;
|
|
119
|
+
const plugin = await scanPluginDirectoryFromAssets(assetPath(internalPrefix, entryName), entryName, true);
|
|
120
|
+
if (plugin) this.plugins.push(plugin);
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
110
126
|
const plugdir = join(this.repoRoot, "runtime", "plugins");
|
|
111
127
|
if (!existsSync(plugdir)) return;
|
|
112
128
|
const entries = await readdir(plugdir, { withFileTypes: true });
|
|
113
|
-
const userNames = new Set(this.plugins.map((plugin) => plugin.name));
|
|
114
129
|
for (const entry of entries) {
|
|
115
130
|
if (!entry.isDirectory() || userNames.has(entry.name)) continue;
|
|
116
131
|
const plugin = await scanPluginDirectory(join(plugdir, entry.name), entry.name, true);
|
|
@@ -412,6 +427,16 @@ export class PluginManager {
|
|
|
412
427
|
addPluginRuntimeFile(plugin, kind, relpath) {
|
|
413
428
|
const owner = this.plugins.find((p) => p.name === plugin);
|
|
414
429
|
if (!owner) return;
|
|
430
|
+
const internalPath = assetPath("runtime", "plugins", owner.dirName, relpath);
|
|
431
|
+
const internalText = readInternalAssetText(internalPath);
|
|
432
|
+
if (internalText != null) {
|
|
433
|
+
this.runtime.files[kind].push({
|
|
434
|
+
name: basename(relpath, extname(relpath)),
|
|
435
|
+
path: internalPath,
|
|
436
|
+
text: async () => internalText,
|
|
437
|
+
});
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
415
440
|
const path = join(this.repoRoot, "runtime", "plugins", owner.dirName, relpath);
|
|
416
441
|
this.runtime.files[kind].push({ name: basename(path, extname(path)), path, text: async () => await readFile(path, "utf8") });
|
|
417
442
|
}
|
|
@@ -589,6 +614,14 @@ async function scanPluginDirectory(dir, defaultName, builtin) {
|
|
|
589
614
|
}
|
|
590
615
|
|
|
591
616
|
function fileSource(path) {
|
|
617
|
+
const internalText = readInternalAssetText(path);
|
|
618
|
+
if (internalText != null) {
|
|
619
|
+
return {
|
|
620
|
+
name: basename(path),
|
|
621
|
+
path,
|
|
622
|
+
text: async () => internalText,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
592
625
|
return {
|
|
593
626
|
name: basename(path),
|
|
594
627
|
path,
|
|
@@ -596,6 +629,28 @@ function fileSource(path) {
|
|
|
596
629
|
};
|
|
597
630
|
}
|
|
598
631
|
|
|
632
|
+
async function scanPluginDirectoryFromAssets(prefix, defaultName, builtin) {
|
|
633
|
+
const entries = listInternalAssetPaths(prefix);
|
|
634
|
+
const srcs = [];
|
|
635
|
+
let info = null;
|
|
636
|
+
let name = defaultName;
|
|
637
|
+
const base = `${assetPath(prefix)}/`;
|
|
638
|
+
|
|
639
|
+
for (const fullPath of entries) {
|
|
640
|
+
const rel = fullPath.slice(base.length);
|
|
641
|
+
if (!rel || rel.includes("/")) continue;
|
|
642
|
+
if (rel.endsWith(".lua")) {
|
|
643
|
+
srcs.push(internalAssetSource(fullPath));
|
|
644
|
+
} else if (rel.endsWith(".json")) {
|
|
645
|
+
const parsed = JSON.parse(readInternalAssetText(fullPath) ?? "null");
|
|
646
|
+
info = Array.isArray(parsed) ? parsed[0] : parsed;
|
|
647
|
+
if (info?.Name) name = info.Name;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
if (!VALID_PLUGIN_NAME.test(name) || srcs.length === 0) return null;
|
|
651
|
+
return new Plugin({ name, dirName: basename(prefix), srcs, info, builtin });
|
|
652
|
+
}
|
|
653
|
+
|
|
599
654
|
function humanizeBytes(bytes) {
|
|
600
655
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
601
656
|
let value = Number(bytes);
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
const textDecoder = new TextDecoder();
|
|
2
|
+
const textEncoder = new TextEncoder();
|
|
3
|
+
|
|
4
|
+
export function hasInternalAssets() {
|
|
5
|
+
return Boolean(getAssetStore());
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function assetPath(...parts) {
|
|
9
|
+
return parts
|
|
10
|
+
.flatMap((part) => String(part).split(/[\\/]+/))
|
|
11
|
+
.filter(Boolean)
|
|
12
|
+
.join("/");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function listInternalAssetPaths(prefix = "") {
|
|
16
|
+
const store = getAssetStore();
|
|
17
|
+
if (!store) return [];
|
|
18
|
+
|
|
19
|
+
const normalizedPrefix = assetPath(prefix);
|
|
20
|
+
const entries = iterateAssetKeys(store);
|
|
21
|
+
if (!normalizedPrefix) {
|
|
22
|
+
return entries.sort();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const base = `${normalizedPrefix}/`;
|
|
26
|
+
return entries.filter((path) => path === normalizedPrefix || path.startsWith(base)).sort();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function listInternalAssetDirs(prefix = "") {
|
|
30
|
+
const normalizedPrefix = assetPath(prefix);
|
|
31
|
+
const base = normalizedPrefix ? `${normalizedPrefix}/` : "";
|
|
32
|
+
const dirs = new Set();
|
|
33
|
+
|
|
34
|
+
for (const path of listInternalAssetPaths(prefix)) {
|
|
35
|
+
const rest = normalizedPrefix ? path.slice(base.length) : path;
|
|
36
|
+
const [dir] = rest.split("/");
|
|
37
|
+
if (dir) dirs.add(dir);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return [...dirs].sort();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function getInternalAsset(path) {
|
|
44
|
+
const store = getAssetStore();
|
|
45
|
+
if (!store) return null;
|
|
46
|
+
const key = assetPath(path);
|
|
47
|
+
if (store instanceof Map) return store.get(key) ?? null;
|
|
48
|
+
return store[key] ?? store[path] ?? null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function readInternalAssetBytes(path) {
|
|
52
|
+
const value = getInternalAsset(path);
|
|
53
|
+
if (value == null) return null;
|
|
54
|
+
if (value instanceof Uint8Array) return value;
|
|
55
|
+
if (ArrayBuffer.isView(value)) {
|
|
56
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
57
|
+
}
|
|
58
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
59
|
+
if (typeof value === "string") return textEncoder.encode(value);
|
|
60
|
+
return textEncoder.encode(String(value));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function readInternalAssetText(path) {
|
|
64
|
+
const bytes = readInternalAssetBytes(path);
|
|
65
|
+
if (!bytes) return null;
|
|
66
|
+
return textDecoder.decode(bytes);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function internalAssetSource(path) {
|
|
70
|
+
return {
|
|
71
|
+
name: path.split("/").pop() ?? path,
|
|
72
|
+
path,
|
|
73
|
+
async text() {
|
|
74
|
+
return readInternalAssetText(path) ?? "";
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getAssetStore() {
|
|
80
|
+
const store = globalThis.internalAssets;
|
|
81
|
+
if (!store) return null;
|
|
82
|
+
if (store instanceof Map) return store;
|
|
83
|
+
if (typeof store === "object") return store;
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function iterateAssetKeys(store) {
|
|
88
|
+
if (store instanceof Map) return [...store.keys()].map(String);
|
|
89
|
+
return Object.keys(store);
|
|
90
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { basename, dirname, resolve } from "node:path";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
export function isCompiledBinary(argv = process.argv) {
|
|
6
|
+
return Boolean(argv?.[1]?.startsWith?.("/$bunfs/"));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function resolveCompiledBaseDir({ argv = process.argv, execPath = process.execPath } = {}) {
|
|
10
|
+
const bn = basename(execPath);
|
|
11
|
+
if (bn.startsWith("ld") ||
|
|
12
|
+
bn.startsWith("libld") ||
|
|
13
|
+
bn.startsWith("linker") ) {
|
|
14
|
+
const realArgv = readFileSync("/proc/self/cmdline", "utf8").match(/[^\0]+/g);
|
|
15
|
+
return dirname(realArgv?.[1] ?? execPath);
|
|
16
|
+
}
|
|
17
|
+
return dirname(execPath) || process.cwd();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveRepoRoot(importMetaUrl, options = {}) {
|
|
21
|
+
if (isCompiledBinary(options.argv)) {
|
|
22
|
+
return resolveCompiledBaseDir(options);
|
|
23
|
+
}
|
|
24
|
+
return resolve(dirname(fileURLToPath(importMetaUrl)), "..");
|
|
25
|
+
}
|
package/src/runtime/registry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import { basename, extname, join } from "node:path";
|
|
4
|
+
import { assetPath, hasInternalAssets, listInternalAssetPaths, readInternalAssetBytes } from "./assets.js";
|
|
4
5
|
|
|
5
6
|
export const RTColorscheme = 0;
|
|
6
7
|
export const RTSyntax = 1;
|
|
@@ -29,6 +30,10 @@ export class RuntimeRegistry {
|
|
|
29
30
|
|
|
30
31
|
async addRuntimeKind(kind, dir, extension, user) {
|
|
31
32
|
if (user) await this.addDirectory(kind, join(this.configDir, dir), extension, true);
|
|
33
|
+
const internalPrefix = assetPath("runtime", dir);
|
|
34
|
+
if (hasInternalAssets() && await this.addInternalDirectory(kind, internalPrefix, extension, false)) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
32
37
|
await this.addDirectory(kind, join(this.repoRoot, "runtime", dir), extension, false);
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -47,6 +52,31 @@ export class RuntimeRegistry {
|
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
async addInternalDirectory(kind, prefix, extension, real) {
|
|
56
|
+
const entries = listInternalAssetPaths(prefix);
|
|
57
|
+
if (entries.length === 0) return false;
|
|
58
|
+
|
|
59
|
+
const base = `${assetPath(prefix)}/`;
|
|
60
|
+
let added = false;
|
|
61
|
+
for (const path of entries) {
|
|
62
|
+
const rel = path.slice(base.length);
|
|
63
|
+
if (!rel || rel.includes("/")) continue;
|
|
64
|
+
if (!rel.endsWith(extension)) continue;
|
|
65
|
+
|
|
66
|
+
const data = readInternalAssetBytes(path);
|
|
67
|
+
if (!data) continue;
|
|
68
|
+
const file = new MemoryRuntimeFile(path, data);
|
|
69
|
+
if (!real && this.realFiles[kind].some((f) => f.name === file.name)) {
|
|
70
|
+
this.fallbackFiles[kind].push(file);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
this.files[kind].push(file);
|
|
74
|
+
if (real) this.realFiles[kind].push(file);
|
|
75
|
+
added = true;
|
|
76
|
+
}
|
|
77
|
+
return added;
|
|
78
|
+
}
|
|
79
|
+
|
|
50
80
|
addMemoryFile(kind, name, data) {
|
|
51
81
|
this.files[kind].push(new MemoryRuntimeFile(name, data));
|
|
52
82
|
}
|
|
@@ -85,14 +115,20 @@ class MemoryRuntimeFile {
|
|
|
85
115
|
this.name = basename(name, extname(name));
|
|
86
116
|
this.path = name;
|
|
87
117
|
this.real = false;
|
|
88
|
-
this._data =
|
|
118
|
+
this._data = data;
|
|
89
119
|
}
|
|
90
120
|
|
|
91
121
|
async data() {
|
|
92
|
-
|
|
122
|
+
if (this._data instanceof Uint8Array) return this._data;
|
|
123
|
+
if (ArrayBuffer.isView(this._data)) {
|
|
124
|
+
return new Uint8Array(this._data.buffer, this._data.byteOffset, this._data.byteLength);
|
|
125
|
+
}
|
|
126
|
+
if (this._data instanceof ArrayBuffer) return new Uint8Array(this._data);
|
|
127
|
+
return new TextEncoder().encode(String(this._data));
|
|
93
128
|
}
|
|
94
129
|
|
|
95
130
|
async text() {
|
|
96
|
-
return this._data;
|
|
131
|
+
if (typeof this._data === "string") return this._data;
|
|
132
|
+
return new TextDecoder().decode(await this.data());
|
|
97
133
|
}
|
|
98
134
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
decodeBinaryStrict,
|
|
4
|
+
encodeBinary,
|
|
5
|
+
} from "../src/buffer/fixed3-codec.js";
|
|
6
|
+
|
|
7
|
+
const SIZES = [1024, 64 * 1024, 1024 * 1024];
|
|
8
|
+
const ITERATIONS = {
|
|
9
|
+
1024: 20_000,
|
|
10
|
+
[64 * 1024]: 2_000,
|
|
11
|
+
[1024 * 1024]: 200,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function makeRandom(size) {
|
|
15
|
+
return randomBytes(size);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeAscii(size) {
|
|
19
|
+
const buf = Buffer.allocUnsafe(size);
|
|
20
|
+
for (let i = 0; i < size; i++) {
|
|
21
|
+
buf[i] = 0x20 + (i % 95);
|
|
22
|
+
}
|
|
23
|
+
return buf;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function mbps(bytes, ns) {
|
|
27
|
+
return (bytes / (1024 * 1024)) / (Number(ns) / 1e9);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function bench(name, fn, iters, payloadSize) {
|
|
31
|
+
const warmup = Math.min(200, Math.max(20, Math.floor(iters / 20)));
|
|
32
|
+
for (let i = 0; i < warmup; i++) fn();
|
|
33
|
+
|
|
34
|
+
const start = process.hrtime.bigint();
|
|
35
|
+
let sink = 0;
|
|
36
|
+
for (let i = 0; i < iters; i++) {
|
|
37
|
+
sink ^= fn();
|
|
38
|
+
}
|
|
39
|
+
const elapsed = process.hrtime.bigint() - start;
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
iters,
|
|
44
|
+
ms: Number(elapsed) / 1e6,
|
|
45
|
+
mbps: mbps(payloadSize * iters, elapsed),
|
|
46
|
+
sink,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function runCase(label, input) {
|
|
51
|
+
const fixedEncoded = encodeBinary(input);
|
|
52
|
+
const base64Encoded = input.toString("base64");
|
|
53
|
+
|
|
54
|
+
if (!decodeBinaryStrict(fixedEncoded).equals(input)) {
|
|
55
|
+
throw new Error(`hex3 roundtrip failed for ${label}`);
|
|
56
|
+
}
|
|
57
|
+
if (!Buffer.from(base64Encoded, "base64").equals(input)) {
|
|
58
|
+
throw new Error(`base64 roundtrip failed for ${label}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const iters = ITERATIONS[input.length] ?? Math.max(50, Math.floor(100 * 1024 * 1024 / input.length));
|
|
62
|
+
|
|
63
|
+
const results = [
|
|
64
|
+
bench(
|
|
65
|
+
"hex3 encode+decode",
|
|
66
|
+
() => {
|
|
67
|
+
const encoded = encodeBinary(input);
|
|
68
|
+
return decodeBinaryStrict(encoded).length;
|
|
69
|
+
},
|
|
70
|
+
iters,
|
|
71
|
+
input.length,
|
|
72
|
+
),
|
|
73
|
+
bench(
|
|
74
|
+
"base64 encode+decode",
|
|
75
|
+
() => {
|
|
76
|
+
const encoded = input.toString("base64");
|
|
77
|
+
return Buffer.from(encoded, "base64").length;
|
|
78
|
+
},
|
|
79
|
+
iters,
|
|
80
|
+
input.length,
|
|
81
|
+
),
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
console.log(`\n${label} (${input.length.toLocaleString()} bytes, ${iters.toLocaleString()} iterations)`);
|
|
85
|
+
for (const r of results) {
|
|
86
|
+
console.log(
|
|
87
|
+
`${r.name.padEnd(22)} ${r.ms.toFixed(1).padStart(10)} ms ${r.mbps.toFixed(1).padStart(8)} MiB/s sink=${r.sink}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
for (const size of SIZES) {
|
|
93
|
+
runCase("random", makeRandom(size));
|
|
94
|
+
runCase("ascii", makeAscii(size));
|
|
95
|
+
}
|