bunmicro 1.0.0 → 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 +26 -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/highlight/parser.js +20 -11
- package/src/index.js +403 -52
- package/src/lua/engine.js +42 -8
- package/src/platform/clipboard.js +3 -0
- 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/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 {
|
|
@@ -256,6 +256,9 @@ export function osc52Clipboard(stdout) {
|
|
|
256
256
|
}
|
|
257
257
|
|
|
258
258
|
export async function probeOSC52(ttyIn, ttyOut, timeoutMs) {
|
|
259
|
+
return true;
|
|
260
|
+
// Almost no terminal reliably supports probing OSC 52, so treat it as
|
|
261
|
+
// available and let the actual write path fail or work on its own.
|
|
259
262
|
if (process.env.TMUX) return true;
|
|
260
263
|
return new Promise((resolve) => {
|
|
261
264
|
let done = false;
|
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
|
+
}
|