2026-03-05 20:44:48 +01:00
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Bundle qmd into a self-contained directory for Tauri resource embedding.
|
|
|
|
|
#
|
|
|
|
|
# Output: src-tauri/resources/qmd/
|
|
|
|
|
# qmd — compiled standalone binary
|
|
|
|
|
# node_modules/sqlite-vec/ — JS shim for sqlite-vec
|
|
|
|
|
# node_modules/sqlite-vec-darwin-arm64/ — native .dylib (arm64)
|
|
|
|
|
# node_modules/sqlite-vec-darwin-x64/ — native .dylib (x64)
|
|
|
|
|
# node_modules/node-llama-cpp/ — stub (keyword search only)
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
|
|
|
ROOT="$SCRIPT_DIR/.."
|
|
|
|
|
OUT="$ROOT/src-tauri/resources/qmd"
|
|
|
|
|
|
|
|
|
|
# ---------- locate tools ----------
|
|
|
|
|
find_bun() {
|
|
|
|
|
for c in \
|
|
|
|
|
"$HOME/.bun/bin/bun" \
|
|
|
|
|
"/opt/homebrew/bin/bun" \
|
|
|
|
|
"/usr/local/bin/bun"; do
|
|
|
|
|
[[ -x "$c" ]] && { echo "$c"; return 0; }
|
|
|
|
|
done
|
|
|
|
|
command -v bun 2>/dev/null && return 0
|
|
|
|
|
return 1
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BUN=$(find_bun) || { echo "ERROR: bun not found — install from https://bun.sh" >&2; exit 1; }
|
|
|
|
|
echo "Using bun: $BUN"
|
|
|
|
|
|
|
|
|
|
# ---------- locate qmd source ----------
|
2026-03-06 08:22:20 +01:00
|
|
|
# Prefer bundled source in tools/qmd/ (works in CI and dev),
|
|
|
|
|
# then fall back to globally installed qmd on dev machines.
|
2026-03-05 20:44:48 +01:00
|
|
|
QMD_SRC=""
|
|
|
|
|
for c in \
|
2026-03-06 08:22:20 +01:00
|
|
|
"$ROOT/tools/qmd" \
|
2026-03-05 20:44:48 +01:00
|
|
|
"$HOME/.bun/install/global/node_modules/qmd" \
|
|
|
|
|
"/opt/homebrew/lib/node_modules/qmd" \
|
|
|
|
|
"/usr/local/lib/node_modules/qmd"; do
|
|
|
|
|
[[ -f "$c/src/qmd.ts" ]] && { QMD_SRC="$c"; break; }
|
|
|
|
|
done
|
|
|
|
|
|
2026-03-06 08:22:20 +01:00
|
|
|
[[ -n "$QMD_SRC" ]] || { echo "ERROR: qmd source not found. tools/qmd/ is missing or incomplete." >&2; exit 1; }
|
2026-03-05 20:44:48 +01:00
|
|
|
echo "Using qmd source: $QMD_SRC"
|
|
|
|
|
|
2026-03-06 08:22:20 +01:00
|
|
|
# Install qmd dependencies if needed (for CI where node_modules don't exist yet)
|
|
|
|
|
if [[ ! -d "$QMD_SRC/node_modules" ]]; then
|
|
|
|
|
echo "Installing qmd dependencies..."
|
|
|
|
|
(cd "$QMD_SRC" && "$BUN" install --frozen-lockfile)
|
|
|
|
|
fi
|
|
|
|
|
|
2026-03-05 20:44:48 +01:00
|
|
|
# ---------- compile ----------
|
|
|
|
|
echo "Compiling qmd with bun build --compile..."
|
|
|
|
|
mkdir -p "$OUT"
|
|
|
|
|
|
2026-03-06 08:22:20 +01:00
|
|
|
(cd "$QMD_SRC" && "$BUN" build --compile \
|
|
|
|
|
"src/qmd.ts" \
|
2026-03-05 20:44:48 +01:00
|
|
|
--outfile "$OUT/qmd" \
|
|
|
|
|
--external node-llama-cpp \
|
|
|
|
|
--external sqlite-vec \
|
|
|
|
|
--external sqlite-vec-darwin-arm64 \
|
2026-03-06 08:22:20 +01:00
|
|
|
--external sqlite-vec-darwin-x64)
|
2026-03-05 20:44:48 +01:00
|
|
|
|
|
|
|
|
chmod +x "$OUT/qmd"
|
|
|
|
|
|
|
|
|
|
# ---------- bundle sqlite-vec ----------
|
|
|
|
|
echo "Bundling sqlite-vec native extensions..."
|
|
|
|
|
|
2026-03-06 08:22:20 +01:00
|
|
|
# Find sqlite-vec packages — prefer node_modules in QMD_SRC (after bun install),
|
|
|
|
|
# fall back to bun global cache for dev machines.
|
|
|
|
|
NM="$QMD_SRC/node_modules"
|
|
|
|
|
|
|
|
|
|
find_pkg() {
|
|
|
|
|
local pkg="$1"
|
|
|
|
|
# Check node_modules from bun install in QMD_SRC first
|
|
|
|
|
if [[ -d "$NM/$pkg" ]]; then
|
|
|
|
|
echo "$NM/$pkg"; return 0
|
|
|
|
|
fi
|
|
|
|
|
# Fall back to bun global cache
|
|
|
|
|
local cache_dir
|
|
|
|
|
cache_dir=$(find "$HOME/.bun/install/cache" -maxdepth 1 -name "${pkg}@*" -type d 2>/dev/null | head -1)
|
|
|
|
|
[[ -n "$cache_dir" ]] && echo "$cache_dir" && return 0
|
|
|
|
|
return 1
|
|
|
|
|
}
|
2026-03-05 20:44:48 +01:00
|
|
|
|
|
|
|
|
# sqlite-vec JS shim
|
2026-03-06 08:22:20 +01:00
|
|
|
SQLVEC_DIR=$(find_pkg "sqlite-vec") || { echo "ERROR: sqlite-vec not found" >&2; exit 1; }
|
2026-03-05 20:44:48 +01:00
|
|
|
mkdir -p "$OUT/node_modules/sqlite-vec"
|
|
|
|
|
cp "$SQLVEC_DIR/index.mjs" "$OUT/node_modules/sqlite-vec/index.mjs"
|
|
|
|
|
cp "$SQLVEC_DIR/package.json" "$OUT/node_modules/sqlite-vec/package.json"
|
|
|
|
|
[[ -f "$SQLVEC_DIR/index.cjs" ]] && cp "$SQLVEC_DIR/index.cjs" "$OUT/node_modules/sqlite-vec/index.cjs"
|
|
|
|
|
|
|
|
|
|
# sqlite-vec-darwin-arm64
|
2026-03-06 08:22:20 +01:00
|
|
|
ARM64_DIR=$(find_pkg "sqlite-vec-darwin-arm64") || true
|
2026-03-05 20:44:48 +01:00
|
|
|
if [[ -n "$ARM64_DIR" ]]; then
|
|
|
|
|
mkdir -p "$OUT/node_modules/sqlite-vec-darwin-arm64"
|
|
|
|
|
cp "$ARM64_DIR/vec0.dylib" "$OUT/node_modules/sqlite-vec-darwin-arm64/vec0.dylib"
|
|
|
|
|
cp "$ARM64_DIR/package.json" "$OUT/node_modules/sqlite-vec-darwin-arm64/package.json"
|
|
|
|
|
echo " ✓ arm64 dylib"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# sqlite-vec-darwin-x64
|
2026-03-06 08:22:20 +01:00
|
|
|
X64_DIR=$(find_pkg "sqlite-vec-darwin-x64") || true
|
2026-03-05 20:44:48 +01:00
|
|
|
if [[ -n "$X64_DIR" ]]; then
|
|
|
|
|
mkdir -p "$OUT/node_modules/sqlite-vec-darwin-x64"
|
|
|
|
|
cp "$X64_DIR/vec0.dylib" "$OUT/node_modules/sqlite-vec-darwin-x64/vec0.dylib"
|
|
|
|
|
cp "$X64_DIR/package.json" "$OUT/node_modules/sqlite-vec-darwin-x64/package.json"
|
|
|
|
|
echo " ✓ x64 dylib"
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# ---------- stub node-llama-cpp ----------
|
|
|
|
|
echo "Creating node-llama-cpp stub (keyword search only)..."
|
|
|
|
|
mkdir -p "$OUT/node_modules/node-llama-cpp"
|
|
|
|
|
|
|
|
|
|
cat > "$OUT/node_modules/node-llama-cpp/package.json" << 'PJSON'
|
|
|
|
|
{"name":"node-llama-cpp","version":"0.0.0-stub","type":"module","main":"index.js"}
|
|
|
|
|
PJSON
|
|
|
|
|
|
|
|
|
|
cat > "$OUT/node_modules/node-llama-cpp/index.js" << 'STUB'
|
|
|
|
|
// Stub: node-llama-cpp not bundled — semantic search unavailable, keyword search works.
|
|
|
|
|
const unavailable = (name) => (...args) => {
|
|
|
|
|
throw new Error(`${name}() unavailable: node-llama-cpp not bundled. Keyword search still works.`);
|
|
|
|
|
};
|
|
|
|
|
export const getLlama = unavailable("getLlama");
|
|
|
|
|
export const resolveModelFile = unavailable("resolveModelFile");
|
|
|
|
|
export class LlamaChatSession {
|
|
|
|
|
constructor() { throw new Error("LlamaChatSession unavailable"); }
|
|
|
|
|
}
|
|
|
|
|
export const LlamaLogLevel = { Error: 0, Warn: 1, Info: 2, Debug: 3 };
|
|
|
|
|
STUB
|
|
|
|
|
|
2026-03-06 08:43:54 +01:00
|
|
|
# ---------- code signing (macOS) ----------
|
|
|
|
|
# In CI (APPLE_SIGNING_IDENTITY set): sign with Developer ID + hardened runtime (required for notarization)
|
|
|
|
|
# In dev (no identity): ad-hoc sign to remove quarantine
|
2026-03-05 22:13:03 +01:00
|
|
|
if [[ "$(uname)" == "Darwin" ]] && command -v codesign &>/dev/null; then
|
2026-03-06 08:43:54 +01:00
|
|
|
SIGN_ID="${APPLE_SIGNING_IDENTITY:--}"
|
|
|
|
|
if [[ "$SIGN_ID" != "-" ]]; then
|
|
|
|
|
echo "Signing bundled binaries with Developer ID: $SIGN_ID"
|
|
|
|
|
SIGN_OPTS=(--force --sign "$SIGN_ID" --options runtime --timestamp)
|
|
|
|
|
else
|
|
|
|
|
echo "Ad-hoc signing bundled binaries (dev mode)..."
|
|
|
|
|
SIGN_OPTS=(--force --sign -)
|
|
|
|
|
fi
|
|
|
|
|
codesign "${SIGN_OPTS[@]}" "$OUT/qmd" 2>/dev/null && echo " ✓ qmd signed" || echo " ⚠ qmd signing failed (non-fatal)"
|
|
|
|
|
while IFS= read -r -d '' dylib; do
|
|
|
|
|
codesign "${SIGN_OPTS[@]}" "$dylib" 2>/dev/null && echo " ✓ $(basename "$dylib") signed" || echo " ⚠ $(basename "$dylib") signing failed (non-fatal)"
|
|
|
|
|
done < <(find "$OUT/node_modules" -name "*.dylib" -print0)
|
2026-03-05 22:13:03 +01:00
|
|
|
fi
|
|
|
|
|
|
2026-03-05 20:44:48 +01:00
|
|
|
# ---------- summary ----------
|
|
|
|
|
echo ""
|
|
|
|
|
echo "qmd bundled → $OUT/"
|
|
|
|
|
du -sh "$OUT/qmd"
|
|
|
|
|
du -sh "$OUT/node_modules"
|
|
|
|
|
echo "Done."
|