#!/usr/bin/env bash set -euo pipefail # Build universal native dependencies (better-sqlite3 + sqlite-vec) for the packaged bundle. # Relies on the universal Node runtime prepared in dist/runtime/node-universal. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" export PATH="$REPO_ROOT/scripts:$PATH" DIST_ROOT="${DIST_ROOT:-$REPO_ROOT/dist/local-app}" APP_DIR="${APP_DIR:-$DIST_ROOT/app}" NODE_RUNTIME_DIR="${NODE_RUNTIME_DIR:-$REPO_ROOT/dist/runtime/node-universal}" NODE_BIN="$NODE_RUNTIME_DIR/bin/node" NPM_CLI="$NODE_RUNTIME_DIR/lib/node_modules/npm/bin/npm-cli.js" SQLITE_VEC_REPO="${SQLITE_VEC_REPO:-https://github.com/asg017/sqlite-vec.git}" SQLITE_VEC_REF="${SQLITE_VEC_REF:-main}" SQLITE_VEC_SOURCE_DIR="${SQLITE_VEC_SOURCE_DIR:-$REPO_ROOT/dist/.deps/sqlite-vec-src}" SQLITE_VEC_OUTPUT="${SQLITE_VEC_OUTPUT:-$DIST_ROOT/vendor/sqlite-extensions/vec0.dylib}" SQLITE_VEC_VENDOR_COPY="${SQLITE_VEC_VENDOR_COPY:-$REPO_ROOT/vendor/sqlite-extensions/vec0.dylib}" TMP_DIR="$(mktemp -d -t rah-native-XXXX)" cleanup() { rm -rf "$TMP_DIR" } trap cleanup EXIT ensure_prerequisites() { if [ ! -x "$NODE_BIN" ]; then echo "❌ Universal Node runtime missing at $NODE_BIN" >&2 echo " Run scripts/build-universal-node.sh first." >&2 exit 1 fi if [ ! -d "$APP_DIR" ]; then echo "❌ Next.js standalone output missing at $APP_DIR" >&2 echo " Ensure scripts/build-production.sh has copied the app bundle." >&2 exit 1 fi if [ ! -d "$APP_DIR/node_modules/better-sqlite3" ]; then echo "❌ better-sqlite3 node module missing from $APP_DIR/node_modules." >&2 echo " Did you run npm run build before invoking this script?" >&2 exit 1 fi if ! command -v arch >/dev/null 2>&1; then echo "❌ arch command not found. Install Xcode command line tools." >&2 exit 1 fi if arch -x86_64 /usr/bin/true >/dev/null 2>&1; then : else echo "❌ Rosetta is required to build x86_64 binaries. Install with 'softwareupdate --install-rosetta'." >&2 exit 1 fi if ! command -v lipo >/dev/null 2>&1; then echo "❌ lipo not found. Install Xcode command line tools (xcode-select --install)." >&2 exit 1 fi if ! command -v git >/dev/null 2>&1; then echo "❌ git not found. Install git to fetch sqlite-vec source." >&2 exit 1 fi } ensure_sqlite_vec_source() { if [ -d "$SQLITE_VEC_SOURCE_DIR/.git" ]; then return fi echo "⬇️ Cloning sqlite-vec sources into $SQLITE_VEC_SOURCE_DIR" mkdir -p "$(dirname "$SQLITE_VEC_SOURCE_DIR")" git clone --depth 1 --branch "$SQLITE_VEC_REF" "$SQLITE_VEC_REPO" "$SQLITE_VEC_SOURCE_DIR" } refresh_better_sqlite3_sources() { local root_module="$REPO_ROOT/node_modules/better-sqlite3" local target_module="$APP_DIR/node_modules/better-sqlite3" if [ ! -d "$root_module" ]; then echo "❌ Root better-sqlite3 module missing at $root_module" >&2 echo " Run npm install in the repo root before packaging." >&2 exit 1 fi rm -rf "$target_module" mkdir -p "$target_module" rsync -a "$root_module/" "$target_module/" } arch_prefix() { local arch="$1" if [ "$arch" = "x86_64" ]; then echo "arch -x86_64" else echo "arch -arm64" fi } codesign_if_available() { local target="$1" if ! command -v codesign >/dev/null 2>&1; then return fi local identity="${CODESIGN_IDENTITY:--}" local extra_opts=() if [ "${CODESIGN_HARDENED_RUNTIME:-1}" = "1" ]; then extra_opts=(--options runtime) fi local codesign_cmd=(codesign --force --timestamp --sign "$identity" "${extra_opts[@]}" "$target") "${codesign_cmd[@]}" >/dev/null 2>&1 || \ codesign --force --timestamp --sign - "${extra_opts[@]}" "$target" >/dev/null 2>&1 || true } rebuild_better_sqlite3_for_arch() { local arch="$1" local output="$TMP_DIR/better_sqlite3.${arch}.node" echo "⚙️ Rebuilding better-sqlite3 for ${arch}" rm -rf "$APP_DIR/node_modules/better-sqlite3/build" local arch_cmd arch_cmd=($(arch_prefix "$arch")) ( cd "$APP_DIR" PATH="$NODE_RUNTIME_DIR/bin:$PATH" \ npm_config_build_from_source=true \ npm_config_fallback_to_build=false \ npm_config_arch="$arch" \ npm_config_target_arch="$arch" \ CFLAGS="-arch $arch" \ CXXFLAGS="-arch $arch" \ LDFLAGS="-arch $arch" \ "${arch_cmd[@]}" "$NODE_BIN" "$NPM_CLI" rebuild better-sqlite3 --build-from-source ) if [ ! -f "$APP_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node" ]; then echo "❌ Failed to build better-sqlite3 for ${arch}" >&2 exit 1 fi cp "$APP_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node" "$output" } combine_better_sqlite3() { local arm_file="$TMP_DIR/better_sqlite3.arm64.node" local x64_file="$TMP_DIR/better_sqlite3.x86_64.node" local target="$APP_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node" lipo -create "$arm_file" "$x64_file" -output "$target" codesign_if_available "$target" rm -f "$APP_DIR/node_modules/better-sqlite3/build/Release/test_extension.node" echo "✓ better-sqlite3 universal binary ready at $target" } build_sqlite_vec_for_arch() { local arch="$1" local output="$TMP_DIR/vec0.${arch}.dylib" echo "⚙️ Building sqlite-vec (vec0) for ${arch}" local include_dir="${SQLITE3_INCLUDE_DIR:-}" local lib_dir="${SQLITE3_LIB_DIR:-}" if { [ -z "$include_dir" ] || [ -z "$lib_dir" ]; } && command -v brew >/dev/null 2>&1; then local brew_prefix brew_prefix="$(brew --prefix sqlite 2>/dev/null || true)" if [ -n "$brew_prefix" ]; then if [ -z "$include_dir" ] && [ -d "$brew_prefix/include" ]; then include_dir="$brew_prefix/include" fi if [ -z "$lib_dir" ] && [ -d "$brew_prefix/lib" ]; then lib_dir="$brew_prefix/lib" fi fi fi local cflags="-O3 -fPIC -arch $arch -undefined dynamic_lookup" if [ -n "$include_dir" ]; then cflags="$cflags -I${include_dir}" fi if [ -n "$lib_dir" ]; then cflags="$cflags -L${lib_dir}" fi local arch_cmd arch_cmd=($(arch_prefix "$arch")) local -a env_vars=( "CC=clang" "CFLAGS=$cflags" ) if [ -n "$include_dir" ] || [ -n "$lib_dir" ]; then env_vars+=("USE_BREW_SQLITE=1") if [ -n "$include_dir" ]; then env_vars+=("SQLITE_INCLUDE_PATH=-I${include_dir}") fi if [ -n "$lib_dir" ]; then env_vars+=("SQLITE_LIB_PATH=-L${lib_dir}") fi fi ( cd "$SQLITE_VEC_SOURCE_DIR" make clean >/dev/null 2>&1 || true "${arch_cmd[@]}" env "${env_vars[@]}" make loadable ) local built_path="$SQLITE_VEC_SOURCE_DIR/dist/vec0.dylib" if [ ! -f "$built_path" ]; then echo "❌ sqlite-vec build failed for ${arch}. Inspect make output above." >&2 exit 1 fi mv "$built_path" "$output" } combine_sqlite_vec() { mkdir -p "$(dirname "$SQLITE_VEC_OUTPUT")" mkdir -p "$(dirname "$SQLITE_VEC_VENDOR_COPY")" lipo -create \ "$TMP_DIR/vec0.arm64.dylib" \ "$TMP_DIR/vec0.x86_64.dylib" \ -output "$SQLITE_VEC_OUTPUT" cp "$SQLITE_VEC_OUTPUT" "$SQLITE_VEC_VENDOR_COPY" codesign_if_available "$SQLITE_VEC_OUTPUT" codesign_if_available "$SQLITE_VEC_VENDOR_COPY" echo "✓ sqlite-vec universal dylib ready at $SQLITE_VEC_OUTPUT" } codesign_additional_binaries() { local -a extras=( "$DIST_ROOT/bin/yt-dlp" "$APP_DIR/node_modules/@img/sharp-darwin-arm64/lib/sharp-darwin-arm64.node" "$APP_DIR/node_modules/@img/sharp-darwin-x64/lib/sharp-darwin-x64.node" ) for target in "${extras[@]}"; do if [ -e "$target" ]; then codesign_if_available "$target" fi done local glob for glob in \ "$APP_DIR/node_modules/@img/sharp-libvips-darwin-arm64/lib/"* \ "$APP_DIR/node_modules/@img/sharp-libvips-darwin-x64/lib/"*; do if [ -e "$glob" ]; then codesign_if_available "$glob" fi done } main() { ensure_prerequisites ensure_sqlite_vec_source refresh_better_sqlite3_sources rebuild_better_sqlite3_for_arch arm64 rebuild_better_sqlite3_for_arch x86_64 combine_better_sqlite3 build_sqlite_vec_for_arch arm64 build_sqlite_vec_for_arch x86_64 combine_sqlite_vec codesign_additional_binaries } main "$@"