deno task compile embeds the prebuilt matrix (--include); the loader resolves repo paths via URL (the compiled vfs does not normalize '..'), and addon staging falls back from the sibling .node shim to a version-keyed OS-cache extraction (atomic tmp+rename) when the artifact lives in the read-only embedded vfs — verified end to end: compiled binary boots Jazz healthy AND mesh up from the cache-extracted addon, clean SIGINT shutdown. jazz-napi loads fine under deno compile.
- commit
- c2cfafb747e91b16f9683f699c7e230583edf424
- parent
- 8c7e9edbf680d5aba9f079947fa5a10d39ef85cf
- author
- Dami <dami.etoile@gmail.com>
| M | .gitignore | 3 | +- |
| M | deno.json | 3 | +- |
| D | native/ | bin | |
| M | src/ | 80 | +++++++++++++++++++++++------- |
| M | src/ | 10 | +++- |
@@ -3,4 +3,5 @@ lofi-node-data/
33 data/44 *.log55 native/iroh-js/target/6-native/iroh-js/*.node6+native/iroh-js/**/*.node7+dist/@@ -16,7 +16,8 @@
1616 "tasks": {1717 "check": "deno check mod.ts cli.ts testing/mod.ts tests/",1818 "test": "deno test -A tests/",19- "start": "deno run -A cli.ts start"19+ "start": "deno run -A cli.ts start",20+ "compile": "deno compile -A --include native/iroh-js/prebuilt --output dist/lofi-node cli.ts"2021 },2122 "fmt": {2223 "lineWidth": 100Binary file.
@@ -102,6 +102,59 @@ export interface IrohAddon {
102102 103103 const require = createRequire(import.meta.url);104104 105+/** Cache key for extracted artifacts — bump when the vendored tag or106+ * lofi_ext surface changes (see native/iroh-js/UPSTREAM.md). */107+export const IROH_JS_VERSION = "1.1.0-lofi.1";108+109+function osCacheDir(): string {110+ const home = Deno.env.get("HOME") ?? Deno.env.get("USERPROFILE") ?? ".";111+ switch (Deno.build.os) {112+ case "darwin":113+ return `${home}/Library/Caches`;114+ case "windows":115+ return Deno.env.get("LOCALAPPDATA") ?? `${home}/AppData/Local`;116+ default:117+ return Deno.env.get("XDG_CACHE_HOME") ?? `${home}/.cache`;118+ }119+}120+121+/** Give require() a real on-disk .node file. Prefer a sibling shim next to122+ * the artifact (dev flow: cargo output, refreshed on mtime). When the123+ * artifact lives in a read-only place — a `deno compile` binary's embedded124+ * vfs — extract to a version-keyed OS cache instead (atomic: tmp + rename,125+ * safe under concurrent launches). */126+function stageNodeArtifact(srcPath: string): string {127+ if (srcPath.endsWith(".node")) return srcPath;128+ const shim = srcPath.replace(/\.(dylib|so|dll)$/, ".node");129+ try {130+ const src = Deno.statSync(srcPath);131+ let stale = true;132+ try {133+ const dst = Deno.statSync(shim);134+ stale = (src.mtime?.getTime() ?? 1) > (dst.mtime?.getTime() ?? 0);135+ } catch {136+ // no shim yet137+ }138+ if (stale) Deno.copyFileSync(srcPath, shim);139+ return shim;140+ } catch {141+ // Sibling not writable (embedded vfs / read-only install): cache extract.142+ }143+ const dir = `${osCacheDir()}/lofi-node/iroh-js-${IROH_JS_VERSION}-${Deno.build.os}-${Deno.build.arch}`;144+ const target = `${dir}/iroh.node`;145+ try {146+ Deno.statSync(target);147+ return target; // version-keyed: presence means done148+ } catch {149+ // extract below150+ }151+ Deno.mkdirSync(dir, { recursive: true });152+ const tmp = `${target}.${Deno.pid}.tmp`;153+ Deno.writeFileSync(tmp, Deno.readFileSync(srcPath));154+ Deno.renameSync(tmp, target);155+ return target;156+}157+105158 /** Load + probe the addon. Throws MeshUnavailableError with a precise reason106159 * (no silent degradation — lofi's boot-gate ethos). */107160 export function loadIrohAddon(explicitPath?: string): IrohAddon {@@ -116,24 +169,15 @@ export function loadIrohAddon(explicitPath?: string): IrohAddon {
116169 );117170 }118171 // require() only loads native addons from `.node` files — cargo emits119- // .dylib/.so/.dll, so maintain a sibling .node copy (refreshed on mtime).120- let loadPath = resolved.path;121- if (!loadPath.endsWith(".node")) {122- const shim = loadPath.replace(/\.(dylib|so|dll)$/, ".node");123- try {124- const src = Deno.statSync(loadPath);125- let stale = true;126- try {127- const dst = Deno.statSync(shim);128- stale = (src.mtime?.getTime() ?? 1) > (dst.mtime?.getTime() ?? 0);129- } catch {130- // no shim yet131- }132- if (stale) Deno.copyFileSync(loadPath, shim);133- loadPath = shim;134- } catch (e) {135- throw new MeshUnavailableError(`could not stage .node shim for ${loadPath}: ${(e as Error).message}`);136- }172+ // .dylib/.so/.dll, and a compiled binary's embedded artifacts aren't173+ // directly loadable at all; stage a real .node file either way.174+ let loadPath: string;175+ try {176+ loadPath = stageNodeArtifact(resolved.path);177+ } catch (e) {178+ throw new MeshUnavailableError(179+ `could not stage .node artifact for ${resolved.path}: ${(e as Error).message}`,180+ );137181 }138182 let addon: IrohAddon;139183 try {@@ -29,7 +29,11 @@ function exists(path: string): boolean {
2929 }3030 }3131 32-const HERE = new URL(".", import.meta.url).pathname;32+/** Repo-relative candidate, resolved through URL so `..` segments normalize —33+ * the embedded vfs of a `deno compile` binary does not resolve them itself. */34+function repoPath(rel: string): string {35+ return new URL(`../../${rel}`, import.meta.url).pathname;36+}3337 3438 export function resolveIrohLib(explicitPath?: string): IrohLibResolution {3539 const platform = `${Deno.build.os}-${Deno.build.arch}`;@@ -40,9 +44,9 @@ export function resolveIrohLib(explicitPath?: string): IrohLibResolution {
4044 const candidates = [4145 explicitPath,4246 Deno.env.get("LOFI_NODE_IROH"),43- `${HERE}../../native/iroh-js/target/release/${triple.cargoFile}`,47+ repoPath(`native/iroh-js/target/release/${triple.cargoFile}`),4448 `./native/iroh-js/target/release/${triple.cargoFile}`,45- `${HERE}../../native/iroh-js/prebuilt/${triple.dir}/${triple.cargoFile}`,49+ repoPath(`native/iroh-js/prebuilt/${triple.dir}/${triple.cargoFile}`),4650 ];4751 for (const candidate of candidates) {4852 if (!candidate) continue;