@@ -37,6 +37,112 @@ const targetIsWindows = BUN_TARGET.includes("windows") || process.platform === "
3737const binaryName = targetIsWindows ? "executor-sidecar.exe" : "executor-sidecar" ;
3838const sidecarBinary = resolve ( SIDECAR_OUT_DIR , binaryName ) ;
3939
40+ /**
41+ * Normalized `<os>-<arch>[-<abi>]` key for the compile target, derived from
42+ * BUN_TARGET (`bun` = the runner's own platform). Matches the keys used by
43+ * apps/cli/src/build.ts's native-binding maps.
44+ */
45+ const targetKey =
46+ BUN_TARGET === "bun"
47+ ? `${ process . platform } -${ process . arch } `
48+ : BUN_TARGET . replace ( / ^ b u n - / , "" ) . replace ( / ^ w i n d o w s - / , "win32-" ) ;
49+
50+ const targetIsCurrentPlatform = targetKey === `${ process . platform } -${ process . arch } ` ;
51+
52+ // `bun build --compile` does not bundle `.node` native addons into bunfs, so
53+ // the sidecar's eager `require('@libsql/<plat>')` (and the keychain plugin's
54+ // lazy keyring load) would fail at runtime. We stage each binding next to the
55+ // binary; src/sidecar/native-bindings.ts (the sidecar's first import) points
56+ // the loaders at them via EXECUTOR_LIBSQL_NATIVE_PATH /
57+ // EXECUTOR_KEYRING_NATIVE_PATH. Mirrors apps/cli/src/build.ts.
58+ const LIBSQL_NATIVE_VERSION = "0.5.29" ;
59+ const resolveLibsqlNative = ( ) : string => {
60+ const platformMap : Record < string , string > = {
61+ "darwin-arm64" : "darwin-arm64" ,
62+ "darwin-x64" : "darwin-x64" ,
63+ // The compiled binary runs on Bun, which libSQL's loader treats as glibc
64+ // (its musl->gnu workaround), so non-musl linux targets need the -gnu binding.
65+ "linux-arm64" : "linux-arm64-gnu" ,
66+ "linux-x64" : "linux-x64-gnu" ,
67+ "win32-arm64" : "win32-arm64-msvc" ,
68+ "win32-x64" : "win32-x64-msvc" ,
69+ } ;
70+ const target = platformMap [ targetKey ] ;
71+ if ( ! target ) {
72+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: build-time fatal
73+ throw new Error ( `No @libsql native binding mapping for target ${ targetKey } ` ) ;
74+ }
75+ const pkg = `@libsql/${ target } ` ;
76+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: build-time resolution falls back to bun's store layout
77+ try {
78+ const req = createRequire ( join ( APPS_LOCAL , "package.json" ) ) ;
79+ return join ( dirname ( req . resolve ( `${ pkg } /package.json` ) ) , "index.node" ) ;
80+ } catch {
81+ const bunPath = join (
82+ REPO_ROOT ,
83+ `node_modules/.bun/${ pkg . replace ( "/" , "+" ) } @${ LIBSQL_NATIVE_VERSION } /node_modules/${ pkg } /index.node` ,
84+ ) ;
85+ if ( ! existsSync ( bunPath ) ) {
86+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: build-time fatal
87+ throw new Error (
88+ `Cannot resolve ${ pkg } for the sidecar. Run \`bun install --cpu=* --os=*\` so cross-target native bindings are present.` ,
89+ ) ;
90+ }
91+ return bunPath ;
92+ }
93+ } ;
94+
95+ const resolveKeyringNative = ( ) : string => {
96+ const platformMap : Record < string , { pkg : string ; node : string } > = {
97+ "darwin-arm64" : {
98+ pkg : "@napi-rs/keyring-darwin-arm64" ,
99+ node : "keyring.darwin-arm64.node" ,
100+ } ,
101+ "darwin-x64" : {
102+ pkg : "@napi-rs/keyring-darwin-x64" ,
103+ node : "keyring.darwin-x64.node" ,
104+ } ,
105+ "linux-arm64" : {
106+ pkg : "@napi-rs/keyring-linux-arm64-gnu" ,
107+ node : "keyring.linux-arm64-gnu.node" ,
108+ } ,
109+ "linux-x64" : {
110+ pkg : "@napi-rs/keyring-linux-x64-gnu" ,
111+ node : "keyring.linux-x64-gnu.node" ,
112+ } ,
113+ "win32-arm64" : {
114+ pkg : "@napi-rs/keyring-win32-arm64-msvc" ,
115+ node : "keyring.win32-arm64-msvc.node" ,
116+ } ,
117+ "win32-x64" : {
118+ pkg : "@napi-rs/keyring-win32-x64-msvc" ,
119+ node : "keyring.win32-x64-msvc.node" ,
120+ } ,
121+ } ;
122+ const entry = platformMap [ targetKey ] ;
123+ if ( ! entry ) {
124+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: build-time fatal
125+ throw new Error ( `No @napi-rs/keyring native binding mapping for target ${ targetKey } ` ) ;
126+ }
127+ // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: build-time resolution falls back to bun's store layout
128+ try {
129+ const req = createRequire ( join ( REPO_ROOT , "node_modules" , "@napi-rs/keyring" , "package.json" ) ) ;
130+ return join ( dirname ( req . resolve ( `${ entry . pkg } /package.json` ) ) , entry . node ) ;
131+ } catch {
132+ const bunPath = join (
133+ REPO_ROOT ,
134+ `node_modules/.bun/${ entry . pkg . replace ( "/" , "+" ) } @1.2.0/node_modules/${ entry . pkg } /${ entry . node } ` ,
135+ ) ;
136+ if ( ! existsSync ( bunPath ) ) {
137+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: build-time fatal
138+ throw new Error (
139+ `Cannot resolve ${ entry . pkg } for the sidecar. Run \`bun install --cpu=* --os=*\` so cross-target native bindings are present.` ,
140+ ) ;
141+ }
142+ return bunPath ;
143+ }
144+ } ;
145+
40146// QuickJS ships its WASM as a side asset; `bun build --compile` can't pull
41147// it into bunfs, so we stage it next to the binary and the sidecar entry
42148// preloads it via `setQuickJSModule` before any server import.
@@ -54,11 +160,15 @@ const resolveQuickJsWasmPath = (): string => {
54160 return wasmPath ;
55161} ;
56162
57- // Drizzle's migrator takes a folder path at runtime. The compiled sidecar
58- // cannot rely on apps/local/drizzle existing on disk, so inline every migration
59- // as text and let apps/local extract them to a temp folder during startup.
163+ // The v1→v2 data migration replays the legacy v1 drizzle chain
164+ // (apps/local/drizzle-legacy-v1) before reading a legacy database. The
165+ // compiled sidecar cannot rely on that folder existing on disk, so inline
166+ // every migration as text and let apps/local extract them to a temp folder
167+ // during startup. Mirrors apps/cli/src/build.ts — embedding the wrong dir
168+ // (e.g. the v2 chain in drizzle/) makes the sidecar treat every real legacy
169+ // database as "history does not match" and skip the replay.
60170const createEmbeddedMigrationsSource = async ( ) => {
61- const migrationsDir = resolve ( APPS_LOCAL , "drizzle" ) ;
171+ const migrationsDir = resolve ( APPS_LOCAL , "drizzle-legacy-v1 " ) ;
62172 const files = ( await Array . fromAsync ( new Bun . Glob ( "**/*" ) . scan ( { cwd : migrationsDir } ) ) )
63173 . map ( ( file ) => file . replaceAll ( "\\" , "/" ) )
64174 . sort ( ) ;
@@ -86,6 +196,20 @@ if (!existsSync(APPS_LOCAL_DIST)) {
86196 ) ;
87197}
88198
199+ // Cross-target builds (e.g. the mac x64 leg on an arm64 runner) need the other
200+ // platform's optional native packages on disk before we can stage them.
201+ // `--cpu=* --os=*` extracts them all without modifying the lockfile. Mirrors
202+ // apps/cli/src/build.ts.
203+ if ( ! targetIsCurrentPlatform ) {
204+ console . log ( "[build-sidecar] installing optional native deps for all platforms..." ) ;
205+ await $ `bun install --frozen-lockfile --cpu=* --os=*` . cwd ( REPO_ROOT ) ;
206+ }
207+
208+ // Resolve the native bindings up front so a missing platform package fails the
209+ // build before the (slow) compile, and cross-target builds get a clear message.
210+ const libsqlNativePath = resolveLibsqlNative ( ) ;
211+ const keyringNativePath = resolveKeyringNative ( ) ;
212+
89213await rm ( SIDECAR_OUT_DIR , { recursive : true , force : true } ) ;
90214await rm ( WEB_UI_OUT_DIR , { recursive : true , force : true } ) ;
91215await mkdir ( SIDECAR_OUT_DIR , { recursive : true } ) ;
@@ -108,6 +232,10 @@ try {
108232 console . log ( `[build-sidecar] staging QuickJS WASM → ${ SIDECAR_OUT_DIR } ` ) ;
109233 await cp ( resolveQuickJsWasmPath ( ) , join ( SIDECAR_OUT_DIR , "emscripten-module.wasm" ) ) ;
110234
235+ console . log ( `[build-sidecar] staging native bindings (${ targetKey } ) → ${ SIDECAR_OUT_DIR } ` ) ;
236+ await cp ( libsqlNativePath , join ( SIDECAR_OUT_DIR , "libsql.node" ) ) ;
237+ await cp ( keyringNativePath , join ( SIDECAR_OUT_DIR , "keyring.node" ) ) ;
238+
111239 console . log ( `[build-sidecar] staging web UI → ${ WEB_UI_OUT_DIR } ` ) ;
112240 await cp ( APPS_LOCAL_DIST , WEB_UI_OUT_DIR , { recursive : true } ) ;
113241} finally {
0 commit comments