fix(core,cli): make standalone binary actually run

Bun's --compile binary previously crashed at startup because:
  1. node:sqlite isn't implemented in Bun 1.3.8 (require returns
     undefined; import throws "No such built-in module")
  2. ink imports react-devtools-core inside its reconciler; even though
     gated by isDev(), the bundled module path failed to resolve at
     runtime

Fixes:
  - Add packages/core/src/sqlite-adapter.ts: a thin DatabaseSync wrapper
    that picks bun:sqlite under Bun and node:sqlite under Node via
    createRequire (so the bundler doesn't statically pull in either).
    Drop-in for the three core files that import DatabaseSync.
  - Install react-devtools-core as a workspace devDependency so it
    resolves at bundle time. The dev-only code path is still gated by
    DEV=true, so it stays inert in production.
  - Revert the prior --external react-devtools-core flag (no longer
    needed and was causing a different runtime error).
  - Mark node-pty external in tsup so esbuild stops choking on the
    homebridge fork's conditional native require()s
    (build/Release/conpty.node etc.) when bundling for the npm package.
  - Update bundle-output test: the bundle now contains both
    bun:sqlite and node:sqlite specifiers (loaded via createRequire).

Verified end-to-end: dist/fn dashboard -p 0 starts cleanly (no PTY,
sqlite, or devtools errors). Core tests 3038/3038, CLI tests 826/826
(up from 822/826 baseline).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsxdsm
2026-04-26 15:25:06 -07:00
parent ed2df90df6
commit 14e41b0ebe
9 changed files with 157 additions and 18 deletions

View File

@@ -338,10 +338,6 @@ function compileBinary(outFile: string, target: string, isCrossCompile: boolean)
target,
"--minify",
"--conditions=source",
// ink imports react-devtools-core dynamically only when DEV=true; mark
// external so Bun's static bundler doesn't try to resolve it at compile.
"--external",
"react-devtools-core",
],
cwd: workspaceRoot,
stdout: "inherit",

View File

@@ -68,10 +68,13 @@ describe("CLI bundle output", () => {
expect(tsupConfig).toContain("cpSync(dashboardClientSrc, dashboardClientDest, { recursive: true });");
});
it("preserves node: prefix in node:sqlite imports", () => {
it("loads sqlite via runtime adapter (bun:sqlite under Bun, node:sqlite under Node)", () => {
const content = readFileSync(bundlePath, "utf-8");
// Should have node:sqlite, not bare "sqlite"
expect(content).toContain('from "node:sqlite"');
// The sqlite-adapter uses createRequire to pick the runtime backend at
// construction time; both specifiers should appear as require() targets.
expect(content).toMatch(/["']bun:sqlite["']/);
expect(content).toMatch(/["']node:sqlite["']/);
// No bare "sqlite" import (we never want to pull in an npm package named sqlite)
expect(content).not.toMatch(/from\s+["']sqlite["'][^s]/);
});

View File

@@ -33,6 +33,10 @@ export default defineConfig({
options.conditions = [...(options.conditions || []), "source"];
},
noExternal: [/^@fusion\//],
// Native module: leave node-pty (aliased to @homebridge fork) out of the
// bundle. esbuild can't statically resolve its conditional native require()s
// (build/Release/pty.node, build/Debug/conpty.node, ...).
external: ["node-pty", "@homebridge/node-pty-prebuilt-multiarch"],
splitting: false,
clean: true,
removeNodeProtocol: false,